From 1c0a2565ff7ee2c54c0396131e1b7b24b4045fa3 Mon Sep 17 00:00:00 2001 From: Stefan Reitshamer Date: Fri, 3 Feb 2017 10:04:07 -0500 Subject: [PATCH] Updated to be compatible with backups created by Arq 5. --- ArqRestoreCommand.h | 8 +- ArqRestoreCommand.m | 733 +- ArqSalt.h | 16 +- ArqSalt.m | 111 +- BackupSet.h | 10 +- BackupSet.m | 38 +- BaseTargetConnection.h | 73 - BaseTargetConnection.m | 167 - BlobKey.h | 21 +- BlobKey.m | 41 +- BlobKeyIO.h | 7 +- BlobKeyIO.m | 7 +- Bucket.h | 35 +- Bucket.m | 234 +- BucketExclude.h | 4 +- BucketExclude.m | 4 +- BucketExcludeSet.h | 4 +- BucketExcludeSet.m | 4 +- .../s3/S3DeleteReceiver.h => ByteSize.h | 13 +- ByteSize.m | 53 + ...ogleDriveErrorResult.h => CacheOwnership.h | 19 +- CacheOwnership.m | 59 + CocoaLumberjack/CocoaLumberjack.h | 81 + CocoaLumberjack/DDASLLogCapture.h | 32 + CocoaLumberjack/DDASLLogCapture.m | 230 + CocoaLumberjack/DDASLLogger.h | 54 + CocoaLumberjack/DDASLLogger.m | 121 + CocoaLumberjack/DDAbstractDatabaseLogger.h | 112 + CocoaLumberjack/DDAbstractDatabaseLogger.m | 660 + CocoaLumberjack/DDAssertMacros.h | 26 + CocoaLumberjack/DDContextFilterLogFormatter.h | 75 + CocoaLumberjack/DDContextFilterLogFormatter.m | 191 + CocoaLumberjack/DDDispatchQueueLogFormatter.h | 135 + CocoaLumberjack/DDDispatchQueueLogFormatter.m | 247 + CocoaLumberjack/DDFileLogger.h | 391 + CocoaLumberjack/DDFileLogger.m | 1527 + CocoaLumberjack/DDLegacyMacros.h | 75 + CocoaLumberjack/DDLog+LOGV.h | 83 + CocoaLumberjack/DDLog.h | 545 + CocoaLumberjack/DDLog.m | 1163 + CocoaLumberjack/DDLogMacros.h | 82 + CocoaLumberjack/DDMultiFormatter.h | 42 + CocoaLumberjack/DDMultiFormatter.m | 141 + CocoaLumberjack/DDTTYLogger.h | 176 + CocoaLumberjack/DDTTYLogger.m | 1482 + DeleteDelegate.h | 39 + GoogleDriveTargetConnection.m | 127 - NSString_slashed.h | 3 +- NSString_slashed.m | 4 +- .../GoogleDriveFactory.h => ReflogEntry.h | 28 +- ReflogEntry.m | 99 + S3TargetConnection.m | 126 - SFTPTargetConnection.m | 155 - .../GoogleDriveRemoteFS.h => System.h | 16 +- System.m | 78 + Target.h | 77 +- Target.m | 308 +- TargetConnection.h | 46 +- TargetConnection.m | 550 + TargetFactory.h | 64 + TargetFactory.m | 296 + TargetSchedule.m | 147 - UserAndComputer.h | 4 +- UserAndComputer.m | 4 +- UserLibrary_Arq.h | 3 +- UserLibrary_Arq.m | 8 +- arq_restore.m | 27 +- arq_restore.xcodeproj/project.pbxproj | 880 +- arq_restore_Prefix.pch | 4 +- cocoastack/.DS_Store | Bin 0 -> 6148 bytes cocoastack/Item.h | 61 + cocoastack/Item.m | 88 + cocoastack/ItemsDB.h | 62 + cocoastack/ItemsDB.m | 160 + cocoastack/TargetItemsDB.h | 60 + cocoastack/TargetItemsDB.m | 848 + cocoastack/aws/AWSQueryError.h | 4 +- cocoastack/aws/AWSQueryError.m | 8 +- cocoastack/aws/AWSQueryRequest.h | 4 +- cocoastack/aws/AWSQueryRequest.m | 4 +- cocoastack/aws/AWSQueryResponse.h | 4 +- cocoastack/aws/AWSQueryResponse.m | 4 +- cocoastack/aws/AWSRegion.h | 8 +- cocoastack/aws/AWSRegion.m | 142 +- cocoastack/aws/SignatureV2Provider.h | 4 +- cocoastack/aws/SignatureV2Provider.m | 4 +- cocoastack/crypto/CryptoKey.h | 6 +- cocoastack/crypto/CryptoKey.m | 14 +- .../S3RemoteFS.h => crypto/HMACSHA256.h} | 13 +- cocoastack/crypto/HMACSHA256.m | 45 + cocoastack/crypto/MD5Hash.h | 5 +- cocoastack/crypto/MD5Hash.m | 12 +- cocoastack/crypto/OpenSSL.h | 4 +- cocoastack/crypto/OpenSSL.m | 7 +- cocoastack/crypto/OpenSSLCryptoKey.h | 6 +- cocoastack/crypto/OpenSSLCryptoKey.m | 62 +- cocoastack/crypto/SHA1Hash.h | 3 +- cocoastack/crypto/SHA1Hash.m | 4 +- cocoastack/crypto/SHA256Hash.h | 4 +- cocoastack/crypto/SHA256Hash.m | 4 +- cocoastack/glacier/GlacierAuthorization.h | 4 +- cocoastack/glacier/GlacierAuthorization.m | 12 +- .../glacier/GlacierAuthorizationProvider.h | 4 +- .../glacier/GlacierAuthorizationProvider.m | 3 +- cocoastack/glacier/GlacierJob.h | 3 +- cocoastack/glacier/GlacierJob.m | 3 +- cocoastack/glacier/GlacierJobLister.h | 4 +- cocoastack/glacier/GlacierJobLister.m | 4 +- cocoastack/glacier/GlacierRequest.h | 4 +- cocoastack/glacier/GlacierRequest.m | 6 +- cocoastack/glacier/GlacierResponse.h | 4 +- cocoastack/glacier/GlacierResponse.m | 4 +- cocoastack/glacier/GlacierService.h | 6 +- cocoastack/glacier/GlacierService.m | 21 +- cocoastack/glacier/GlacierSigner.h | 4 +- cocoastack/glacier/ISO8601Date.h | 20 +- cocoastack/glacier/ISO8601Date.m | 81 +- cocoastack/glacier/LocalGlacierSigner.h | 3 +- cocoastack/glacier/LocalGlacierSigner.m | 4 +- cocoastack/glacier/NSError_Glacier.h | 3 +- cocoastack/glacier/NSError_Glacier.m | 5 +- cocoastack/glacier/SHA256TreeHash.h | 3 +- cocoastack/glacier/SHA256TreeHash.m | 3 +- cocoastack/glacier/Vault.h | 3 +- cocoastack/glacier/Vault.m | 7 +- cocoastack/glacier/VaultDeleter.h | 3 +- cocoastack/glacier/VaultDeleter.m | 3 +- cocoastack/glacier/VaultDeleterDelegate.h | 3 +- cocoastack/glacier/VaultLister.h | 3 +- cocoastack/glacier/VaultLister.m | 3 +- cocoastack/googledrive/GoogleDrive.h | 75 - cocoastack/googledrive/GoogleDrive.m | 588 - .../googledrive/GoogleDriveErrorResult.m | 89 - cocoastack/googledrive/GoogleDriveFactory.m | 100 - .../googledrive/GoogleDriveFolderLister.m | 152 - cocoastack/googledrive/GoogleDriveRequest.h | 77 - cocoastack/googledrive/GoogleDriveRequest.m | 315 - cocoastack/http/HTTP.h | 4 +- cocoastack/http/HTTPConnection.h | 5 +- cocoastack/http/HTTPConnectionFactory.h | 2 +- cocoastack/http/HTTPConnectionFactory.m | 118 +- cocoastack/http/HTTPInputStream.h | 2 +- cocoastack/http/HTTPInputStream.m | 3 +- cocoastack/http/HTTPThrottle.h | 7 +- cocoastack/http/HTTPThrottle.m | 10 +- cocoastack/http/NSDictionary_HTTP.h | 3 +- cocoastack/http/NSDictionary_HTTP.m | 2 +- cocoastack/http/RFC2616DateFormatter.h | 6 +- cocoastack/http/RFC2616DateFormatter.m | 13 +- cocoastack/http/URLConnection.h | 15 +- cocoastack/http/URLConnection.m | 101 +- cocoastack/io/BooleanIO.h | 3 +- cocoastack/io/BooleanIO.m | 3 +- cocoastack/io/BufferedInputStream.h | 4 +- cocoastack/io/BufferedInputStream.m | 22 +- cocoastack/io/BufferedOutputStream.h | 3 +- cocoastack/io/BufferedOutputStream.m | 9 +- cocoastack/io/ChunkedInputStream.h | 3 +- cocoastack/io/ChunkedInputStream.m | 8 +- cocoastack/io/DataIO.h | 3 +- cocoastack/io/DataIO.m | 4 +- cocoastack/io/DataInputStream.h | 2 +- cocoastack/io/DataInputStream.m | 8 +- cocoastack/io/DataOutputStream.h | 3 +- cocoastack/io/DataOutputStream.m | 2 +- cocoastack/io/DataTransferDelegate.h | 4 +- cocoastack/io/DateIO.h | 3 +- cocoastack/io/DateIO.m | 3 +- cocoastack/io/DoubleIO.h | 4 +- cocoastack/io/DoubleIO.m | 4 +- cocoastack/io/FDInputStream.h | 3 +- cocoastack/io/FDInputStream.m | 7 +- cocoastack/io/FDOutputStream.h | 4 +- cocoastack/io/FDOutputStream.m | 4 +- cocoastack/io/FileInputStream.h | 4 +- cocoastack/io/FileInputStream.m | 11 +- cocoastack/io/FileOutputStream.h | 4 +- cocoastack/io/FileOutputStream.m | 15 +- cocoastack/io/GunzipInputStream.h | 2 +- cocoastack/io/GunzipInputStream.m | 6 +- cocoastack/io/InputStream.h | 4 +- cocoastack/io/InputStreams.h | 3 +- cocoastack/io/InputStreams.m | 18 +- cocoastack/io/IntegerIO.h | 2 +- cocoastack/io/IntegerIO.m | 3 +- cocoastack/io/NSData-InputStream.h | 2 +- cocoastack/io/NSData-InputStream.m | 3 +- cocoastack/io/NSErrorIO.h | 5 +- cocoastack/io/NSErrorIO.m | 28 +- cocoastack/io/NSFileManager_extra.h | 8 +- cocoastack/io/NSFileManager_extra.m | 27 +- cocoastack/io/NetMonitor.h | 4 +- cocoastack/io/NetMonitor.m | 5 +- cocoastack/io/OutputStream.h | 2 +- cocoastack/io/Streams.h | 7 +- cocoastack/io/Streams.m | 84 +- cocoastack/io/StringIO.h | 2 +- cocoastack/io/StringIO.m | 5 +- cocoastack/keychain/BaseKeychain.h | 41 + cocoastack/keychain/BaseKeychain.m | 942 + cocoastack/keychain/Keychain.h | 45 + cocoastack/keychain/KeychainFactory.h | 42 + cocoastack/keychain/KeychainFactory.m | 42 + cocoastack/keychain/KeychainItem.h | 48 + .../KeychainItem.m} | 51 +- cocoastack/plist/ArrayNode.h | 4 +- cocoastack/plist/ArrayNode.m | 4 +- cocoastack/plist/BinaryPListReader.h | 5 +- cocoastack/plist/BinaryPListReader.m | 4 +- cocoastack/plist/BinaryPListWriter.h | 2 +- cocoastack/plist/BinaryPListWriter.m | 4 +- cocoastack/plist/BooleanNode.h | 4 +- cocoastack/plist/BooleanNode.m | 4 +- cocoastack/plist/DictNode.h | 4 +- cocoastack/plist/DictNode.m | 9 +- cocoastack/plist/IntegerNode.h | 4 +- cocoastack/plist/IntegerNode.m | 4 +- cocoastack/plist/PListNode.h | 2 +- cocoastack/plist/PListNodeType.h | 4 +- cocoastack/plist/RealNode.h | 3 +- cocoastack/plist/RealNode.m | 3 +- cocoastack/plist/StringNode.h | 4 +- cocoastack/plist/StringNode.m | 4 +- cocoastack/plist/XMLPListReader.h | 4 +- cocoastack/plist/XMLPListReader.m | 10 +- cocoastack/plist/XMLPListWriter.h | 3 +- cocoastack/plist/XMLPListWriter.m | 6 +- cocoastack/plist/XMLPlistParser.h | 3 +- cocoastack/plist/XMLPlistParser.m | 3 +- cocoastack/remotefs/GoogleDriveRemoteFS.m | 149 - cocoastack/remotefs/ItemFS.h | 63 + .../ItemFSFileDeleter.h} | 29 +- cocoastack/remotefs/ItemFSFileDeleter.m | 91 + cocoastack/remotefs/ItemFSFileDeleterWorker.h | 47 + cocoastack/remotefs/ItemFSFileDeleterWorker.m | 73 + cocoastack/remotefs/LocalItemFS.h | 45 + cocoastack/remotefs/LocalItemFS.m | 317 + cocoastack/remotefs/RemoteFS.h | 45 +- cocoastack/remotefs/RemoteFS.m | 584 + cocoastack/remotefs/RemoteFSFileDeleter.h | 59 + cocoastack/remotefs/RemoteFSFileDeleter.m | 109 + .../remotefs/RemoteFSFileDeleterWorker.h | 47 + .../remotefs/RemoteFSFileDeleterWorker.m | 86 + cocoastack/remotefs/S3RemoteFS.m | 172 - cocoastack/remotefs/SFTPRemoteFS.m | 579 - cocoastack/s3/LifecycleConfiguration.m | 98 - cocoastack/s3/LocalS3Signer.h | 4 +- cocoastack/s3/LocalS3Signer.m | 4 +- cocoastack/s3/PathReceiver.h | 3 +- cocoastack/s3/PathReceiver.m | 3 +- cocoastack/s3/RemoteS3Signer.m | 92 - cocoastack/s3/S3AuthorizationProvider.h | 20 +- .../s3/S3AuthorizationProviderFactory.h | 50 + .../s3/S3AuthorizationProviderFactory.m | 61 + cocoastack/s3/S3ErrorResult.h | 6 +- cocoastack/s3/S3ErrorResult.m | 15 +- cocoastack/s3/S3Lister.h | 31 +- cocoastack/s3/S3Lister.m | 263 +- cocoastack/s3/S3MultiDeleteResponse.h | 2 +- cocoastack/s3/S3MultiDeleteResponse.m | 3 +- cocoastack/s3/S3ObjectMetadata.h | 6 +- cocoastack/s3/S3ObjectMetadata.m | 18 +- cocoastack/s3/S3ObjectReceiver.h | 2 +- cocoastack/s3/S3ObjectReceiver.m | 4 +- .../S3ObjectsLister.h} | 37 +- cocoastack/s3/S3ObjectsLister.m | 128 + .../S3ObjectsListerWorker.h} | 27 +- cocoastack/s3/S3ObjectsListerWorker.m | 88 + cocoastack/s3/S3Owner.h | 2 +- cocoastack/s3/S3Owner.m | 3 +- cocoastack/s3/S3Receiver.h | 2 +- cocoastack/s3/S3Request.h | 12 +- cocoastack/s3/S3Request.m | 63 +- cocoastack/s3/S3Service.h | 47 +- cocoastack/s3/S3Service.m | 478 +- .../s3/S3SignatureV1AuthorizationProvider.h | 43 + ...m => S3SignatureV1AuthorizationProvider.m} | 101 +- ...h => S3SignatureV4AuthorizationProvider.h} | 19 +- .../s3/S3SignatureV4AuthorizationProvider.m | 256 + cocoastack/s3/S3Signer.h | 3 +- cocoastack/sftp/SFTPServer.h | 76 - cocoastack/sftp/SFTPServer.m | 1264 - cocoastack/shared/Computer.h | 9 +- cocoastack/shared/Computer.m | 23 +- cocoastack/shared/ExePath.h | 38 + cocoastack/shared/ExePath.m | 49 + cocoastack/shared/FSStat.h | 51 + cocoastack/shared/FSStat.m | 220 + cocoastack/shared/FileACL.h | 3 +- cocoastack/shared/FileACL.m | 6 +- .../shared/FlockFile.h | 20 +- cocoastack/shared/FlockFile.m | 137 + cocoastack/shared/HSLog.h | 39 +- cocoastack/shared/HSLog.m | 116 +- cocoastack/shared/HSLogFileManager.h | 42 + cocoastack/shared/HSLogFileManager.m | 94 + .../shared/LZ4Compressor.h | 18 +- cocoastack/shared/LZ4Compressor.m | 118 + cocoastack/shared/NSData-GZip.h | 4 +- cocoastack/shared/NSData-GZip.m | 30 +- cocoastack/shared/NSData-LZ4.h | 39 + cocoastack/shared/NSData-LZ4.m | 49 + cocoastack/shared/NSData-Random.h | 38 + cocoastack/shared/NSData-Random.m | 47 + cocoastack/shared/NSErrorCodes.h | 10 +- cocoastack/shared/NSError_extra.h | 4 +- cocoastack/shared/NSError_extra.m | 80 +- cocoastack/shared/NSObject_extra.h | 3 +- cocoastack/shared/NSObject_extra.m | 3 +- cocoastack/shared/NSString_extra.h | 6 +- cocoastack/shared/NSString_extra.m | 29 +- cocoastack/shared/NSXMLNode_extra.h | 2 +- cocoastack/shared/NSXMLNode_extra.m | 4 +- cocoastack/shared/OSStatusDescription.h | 2 +- cocoastack/shared/OSStatusDescription.m | 3 +- cocoastack/shared/RFC822.h | 2 +- cocoastack/shared/RFC822.m | 3 +- cocoastack/shared/SetNSError.h | 7 +- cocoastack/shared/Sysctl.h | 2 +- cocoastack/shared/Sysctl.m | 3 +- cocoastack/shared/UserLibrary.h | 3 +- cocoastack/shared/UserLibrary.m | 3 +- cocoastack/shared/Volume.h | 62 + .../shared/Volume.m | 95 +- cocoastack/shared/lz4.c | 1516 + cocoastack/shared/lz4.h | 360 + cocoastack/sns/CreateTopicResponse.h | 3 +- cocoastack/sns/CreateTopicResponse.m | 3 +- cocoastack/sns/ListTopicsResponse.h | 3 +- cocoastack/sns/ListTopicsResponse.m | 3 +- cocoastack/sns/SNS.h | 4 +- cocoastack/sns/SNS.m | 33 +- cocoastack/sns/SubscribeResponse.h | 4 +- cocoastack/sns/SubscribeResponse.m | 3 +- cocoastack/sqs/CreateQueueResponse.h | 3 +- cocoastack/sqs/CreateQueueResponse.m | 3 +- cocoastack/sqs/GetQueueAttributesResponse.h | 3 +- cocoastack/sqs/GetQueueAttributesResponse.m | 3 +- cocoastack/sqs/ListQueuesResponse.h | 2 +- cocoastack/sqs/ListQueuesResponse.m | 4 +- cocoastack/sqs/ReceiveMessageResponse.h | 2 +- cocoastack/sqs/ReceiveMessageResponse.m | 3 +- cocoastack/sqs/SQS.h | 3 +- cocoastack/sqs/SQS.m | 45 +- cocoastack/sqs/SQSMessage.h | 3 +- cocoastack/sqs/SQSMessage.m | 4 +- cocoastack/storage/StorageType.h | 3 +- commonrestore/CalculateItem.h | 4 +- commonrestore/CalculateItem.m | 3 +- commonrestore/GlacierRequestItem.h | 4 +- commonrestore/GlacierRequestItem.m | 3 +- commonrestore/RestoreItem.h | 3 +- commonrestore/RestoreItem.m | 81 +- commonrestore/Restorer.h | 4 +- fmdb/FMDB.h | 5 + fmdb/FMDatabase.h | 1086 + fmdb/FMDatabase.m | 1434 + fmdb/FMDatabaseAdditions.h | 281 + fmdb/FMDatabaseAdditions.m | 224 + fmdb/FMDatabasePool.h | 204 + fmdb/FMDatabasePool.m | 273 + fmdb/FMDatabaseQueue.h | 185 + fmdb/FMDatabaseQueue.m | 236 + fmdb/FMResultSet.h | 469 + fmdb/FMResultSet.m | 416 + glacierrestore/GlacierPack.h | 10 +- glacierrestore/GlacierPack.m | 14 +- glacierrestore/GlacierPackIndex.h | 11 +- glacierrestore/GlacierPackIndex.m | 40 +- glacierrestore/GlacierPackSet.h | 9 +- glacierrestore/GlacierPackSet.m | 11 +- glacierrestore/GlacierRestorer.h | 3 +- glacierrestore/GlacierRestorer.m | 108 +- glacierrestore/GlacierRestorerDelegate.h | 4 +- glacierrestore/GlacierRestorerParamSet.h | 14 +- glacierrestore/GlacierRestorerParamSet.m | 111 +- libssh2/include/channel.h | 141 - libssh2/include/comp.h | 45 - libssh2/include/crypto.h | 120 - libssh2/include/libgcrypt.h | 154 - libssh2/include/libssh2.h | 1189 - libssh2/include/libssh2_config.h | 232 - libssh2/include/libssh2_priv.h | 1040 - libssh2/include/libssh2_publickey.h | 118 - libssh2/include/libssh2_sftp.h | 345 - libssh2/include/mac.h | 67 - libssh2/include/misc.h | 94 - libssh2/include/openssl.h | 187 - libssh2/include/packet.h | 76 - libssh2/include/session.h | 93 - libssh2/include/sftp.h | 230 - libssh2/include/transport.h | 87 - libssh2/include/userauth.h | 50 - libssh2/lib/libssh2.1.dylib | Bin 225916 -> 0 bytes libssh2/src/agent.c | 793 - libssh2/src/channel.c | 2571 - libssh2/src/comp.c | 376 - libssh2/src/crypt.c | 336 - libssh2/src/global.c | 78 - libssh2/src/hostkey.c | 487 - libssh2/src/keepalive.c | 98 - libssh2/src/kex.c | 2013 - libssh2/src/knownhost.c | 1149 - libssh2/src/libgcrypt.c | 587 - libssh2/src/mac.c | 314 - libssh2/src/misc.c | 612 - libssh2/src/openssl.c | 804 - libssh2/src/packet.c | 1243 - libssh2/src/pem.c | 213 - libssh2/src/publickey.c | 1058 - libssh2/src/scp.c | 1085 - libssh2/src/session.c | 1751 - libssh2/src/sftp.c | 3285 - libssh2/src/transport.c | 886 - libssh2/src/userauth.c | 1687 - libssh2/src/version.c | 54 - repo/BinarySHA1.h | 2 +- repo/BinarySHA1.m | 4 +- repo/Commit.h | 30 +- repo/Commit.m | 77 +- repo/CommitFailedFile.h | 4 +- repo/CommitFailedFile.m | 12 +- repo/CommitList.h | 70 + repo/CommitList.m | 189 + repo/EncryptionDatFile.h | 92 + repo/EncryptionDatFile.m | 442 + repo/Fark.h | 52 +- repo/{FarkImpl.m => Fark.m} | 454 +- repo/FileAttributes.h | 5 +- repo/FileAttributes.m | 28 +- repo/KeyValuePair.h | 44 + .../KeyValuePair.m | 28 +- repo/NSData-Compress.h | 43 + repo/NSData-Compress.m | 59 + repo/Node.h | 5 +- repo/Node.m | 166 +- repo/ObjectEncryptor.h | 82 + repo/ObjectEncryptor.m | 157 + repo/ObjectEncryptorImpl.h | 50 + repo/ObjectEncryptorV1.h | 62 + repo/ObjectEncryptorV1.m | 177 + repo/{FarkImpl.h => ObjectEncryptorV2.h} | 33 +- repo/ObjectEncryptorV2.m | 398 + repo/PIELoader.h | 65 + repo/PIELoader.m | 111 + repo/PIELoaderWorker.h | 47 + repo/PIELoaderWorker.m | 143 + repo/PackBuilder.h | 75 + repo/PackBuilder.m | 288 + repo/PackBuilderEntry.h | 44 + repo/PackBuilderEntry.m | 52 + repo/PackId.h | 2 +- repo/PackId.m | 11 +- repo/PackIndex.h | 5 +- repo/PackIndex.m | 8 +- repo/PackIndexEntry.h | 3 +- repo/PackIndexEntry.m | 3 +- repo/PackIndexGenerator.h | 45 + repo/PackIndexGenerator.m | 181 + repo/PackSet.h | 53 +- repo/PackSet.m | 434 +- s3restore/S3Restorer.h => repo/PackSetDB.h | 50 +- repo/PackSetDB.m | 495 + repo/Repo.h | 67 +- repo/Repo.m | 568 +- repo/S3PIEInputStream.h | 55 + repo/S3PIEInputStream.m | 147 + repo/SynchronousPackSet.h | 71 + repo/SynchronousPackSet.m | 156 + repo/Tree.h | 10 +- repo/Tree.m | 156 +- repo/XAttrSet.h | 4 +- repo/XAttrSet.m | 39 +- s3glacierrestore/S3GlacierRestorer.h | 8 +- s3glacierrestore/S3GlacierRestorer.m | 149 +- s3glacierrestore/S3GlacierRestorerDelegate.h | 3 +- s3glacierrestore/S3GlacierRestorerParamSet.h | 11 +- s3glacierrestore/S3GlacierRestorerParamSet.m | 110 +- s3restore/S3Restorer.m | 322 - s3restore/S3RestorerParamSet.m | 100 - s3restore/StandardRestoreItem.h | 53 + s3restore/StandardRestoreItem.m | 559 + s3restore/StandardRestoreWorker.h | 44 + s3restore/StandardRestoreWorker.m | 84 + s3restore/StandardRestorer.h | 92 + s3restore/StandardRestorer.m | 406 + ...rDelegate.h => StandardRestorerDelegate.h} | 17 +- s3restore/StandardRestorerDelegateMux.h | 43 + s3restore/StandardRestorerDelegateMux.m | 89 + s3restore/StandardRestorerError.h | 42 + s3restore/StandardRestorerError.m | 54 + ...rParamSet.h => StandardRestorerParamSet.h} | 14 +- s3restore/StandardRestorerParamSet.m | 189 + sqlite3.c | 196848 +++++++++++++++ sqlite3.h | 10188 + 495 files changed, 239512 insertions(+), 34100 deletions(-) delete mode 100644 BaseTargetConnection.h delete mode 100644 BaseTargetConnection.m rename cocoastack/s3/S3DeleteReceiver.h => ByteSize.h (86%) create mode 100644 ByteSize.m rename cocoastack/googledrive/GoogleDriveErrorResult.h => CacheOwnership.h (83%) create mode 100644 CacheOwnership.m create mode 100644 CocoaLumberjack/CocoaLumberjack.h create mode 100644 CocoaLumberjack/DDASLLogCapture.h create mode 100644 CocoaLumberjack/DDASLLogCapture.m create mode 100644 CocoaLumberjack/DDASLLogger.h create mode 100644 CocoaLumberjack/DDASLLogger.m create mode 100644 CocoaLumberjack/DDAbstractDatabaseLogger.h create mode 100644 CocoaLumberjack/DDAbstractDatabaseLogger.m create mode 100644 CocoaLumberjack/DDAssertMacros.h create mode 100644 CocoaLumberjack/DDContextFilterLogFormatter.h create mode 100644 CocoaLumberjack/DDContextFilterLogFormatter.m create mode 100644 CocoaLumberjack/DDDispatchQueueLogFormatter.h create mode 100644 CocoaLumberjack/DDDispatchQueueLogFormatter.m create mode 100644 CocoaLumberjack/DDFileLogger.h create mode 100644 CocoaLumberjack/DDFileLogger.m create mode 100644 CocoaLumberjack/DDLegacyMacros.h create mode 100644 CocoaLumberjack/DDLog+LOGV.h create mode 100644 CocoaLumberjack/DDLog.h create mode 100644 CocoaLumberjack/DDLog.m create mode 100644 CocoaLumberjack/DDLogMacros.h create mode 100644 CocoaLumberjack/DDMultiFormatter.h create mode 100644 CocoaLumberjack/DDMultiFormatter.m create mode 100644 CocoaLumberjack/DDTTYLogger.h create mode 100644 CocoaLumberjack/DDTTYLogger.m create mode 100644 DeleteDelegate.h delete mode 100644 GoogleDriveTargetConnection.m rename cocoastack/googledrive/GoogleDriveFactory.h => ReflogEntry.h (74%) create mode 100644 ReflogEntry.m delete mode 100644 S3TargetConnection.m delete mode 100644 SFTPTargetConnection.m rename cocoastack/remotefs/GoogleDriveRemoteFS.h => System.h (84%) create mode 100644 System.m create mode 100644 TargetConnection.m create mode 100644 TargetFactory.h create mode 100644 TargetFactory.m delete mode 100644 TargetSchedule.m create mode 100644 cocoastack/.DS_Store create mode 100644 cocoastack/Item.h create mode 100644 cocoastack/Item.m create mode 100644 cocoastack/ItemsDB.h create mode 100644 cocoastack/ItemsDB.m create mode 100644 cocoastack/TargetItemsDB.h create mode 100644 cocoastack/TargetItemsDB.m rename cocoastack/{remotefs/S3RemoteFS.h => crypto/HMACSHA256.h} (85%) create mode 100644 cocoastack/crypto/HMACSHA256.m delete mode 100644 cocoastack/googledrive/GoogleDrive.h delete mode 100644 cocoastack/googledrive/GoogleDrive.m delete mode 100644 cocoastack/googledrive/GoogleDriveErrorResult.m delete mode 100644 cocoastack/googledrive/GoogleDriveFactory.m delete mode 100644 cocoastack/googledrive/GoogleDriveFolderLister.m delete mode 100644 cocoastack/googledrive/GoogleDriveRequest.h delete mode 100644 cocoastack/googledrive/GoogleDriveRequest.m create mode 100644 cocoastack/keychain/BaseKeychain.h create mode 100644 cocoastack/keychain/BaseKeychain.m create mode 100644 cocoastack/keychain/Keychain.h create mode 100644 cocoastack/keychain/KeychainFactory.h create mode 100644 cocoastack/keychain/KeychainFactory.m create mode 100644 cocoastack/keychain/KeychainItem.h rename cocoastack/{s3/S3DeleteReceiver.m => keychain/KeychainItem.m} (63%) delete mode 100644 cocoastack/remotefs/GoogleDriveRemoteFS.m create mode 100644 cocoastack/remotefs/ItemFS.h rename cocoastack/{s3/LifecycleConfiguration.h => remotefs/ItemFSFileDeleter.h} (69%) create mode 100644 cocoastack/remotefs/ItemFSFileDeleter.m create mode 100644 cocoastack/remotefs/ItemFSFileDeleterWorker.h create mode 100644 cocoastack/remotefs/ItemFSFileDeleterWorker.m create mode 100644 cocoastack/remotefs/LocalItemFS.h create mode 100644 cocoastack/remotefs/LocalItemFS.m create mode 100644 cocoastack/remotefs/RemoteFS.m create mode 100644 cocoastack/remotefs/RemoteFSFileDeleter.h create mode 100644 cocoastack/remotefs/RemoteFSFileDeleter.m create mode 100644 cocoastack/remotefs/RemoteFSFileDeleterWorker.h create mode 100644 cocoastack/remotefs/RemoteFSFileDeleterWorker.m delete mode 100644 cocoastack/remotefs/S3RemoteFS.m delete mode 100644 cocoastack/remotefs/SFTPRemoteFS.m delete mode 100644 cocoastack/s3/LifecycleConfiguration.m delete mode 100644 cocoastack/s3/RemoteS3Signer.m create mode 100644 cocoastack/s3/S3AuthorizationProviderFactory.h create mode 100644 cocoastack/s3/S3AuthorizationProviderFactory.m rename cocoastack/{googledrive/GoogleDriveFolderLister.h => s3/S3ObjectsLister.h} (61%) create mode 100644 cocoastack/s3/S3ObjectsLister.m rename cocoastack/{remotefs/SFTPRemoteFS.h => s3/S3ObjectsListerWorker.h} (71%) create mode 100644 cocoastack/s3/S3ObjectsListerWorker.m create mode 100644 cocoastack/s3/S3SignatureV1AuthorizationProvider.h rename cocoastack/s3/{S3AuthorizationProvider.m => S3SignatureV1AuthorizationProvider.m} (73%) rename cocoastack/s3/{RemoteS3Signer.h => S3SignatureV4AuthorizationProvider.h} (77%) create mode 100644 cocoastack/s3/S3SignatureV4AuthorizationProvider.m delete mode 100644 cocoastack/sftp/SFTPServer.h delete mode 100644 cocoastack/sftp/SFTPServer.m create mode 100644 cocoastack/shared/ExePath.h create mode 100644 cocoastack/shared/ExePath.m create mode 100644 cocoastack/shared/FSStat.h create mode 100644 cocoastack/shared/FSStat.m rename SFTPTargetConnection.h => cocoastack/shared/FlockFile.h (81%) create mode 100644 cocoastack/shared/FlockFile.m create mode 100644 cocoastack/shared/HSLogFileManager.h create mode 100644 cocoastack/shared/HSLogFileManager.m rename S3TargetConnection.h => cocoastack/shared/LZ4Compressor.h (81%) create mode 100644 cocoastack/shared/LZ4Compressor.m create mode 100644 cocoastack/shared/NSData-LZ4.h create mode 100644 cocoastack/shared/NSData-LZ4.m create mode 100644 cocoastack/shared/NSData-Random.h create mode 100644 cocoastack/shared/NSData-Random.m create mode 100644 cocoastack/shared/Volume.h rename TargetSchedule.h => cocoastack/shared/Volume.m (50%) create mode 100644 cocoastack/shared/lz4.c create mode 100644 cocoastack/shared/lz4.h create mode 100644 fmdb/FMDB.h create mode 100644 fmdb/FMDatabase.h create mode 100644 fmdb/FMDatabase.m create mode 100644 fmdb/FMDatabaseAdditions.h create mode 100644 fmdb/FMDatabaseAdditions.m create mode 100644 fmdb/FMDatabasePool.h create mode 100644 fmdb/FMDatabasePool.m create mode 100644 fmdb/FMDatabaseQueue.h create mode 100644 fmdb/FMDatabaseQueue.m create mode 100644 fmdb/FMResultSet.h create mode 100644 fmdb/FMResultSet.m delete mode 100644 libssh2/include/channel.h delete mode 100644 libssh2/include/comp.h delete mode 100644 libssh2/include/crypto.h delete mode 100644 libssh2/include/libgcrypt.h delete mode 100644 libssh2/include/libssh2.h delete mode 100644 libssh2/include/libssh2_config.h delete mode 100644 libssh2/include/libssh2_priv.h delete mode 100644 libssh2/include/libssh2_publickey.h delete mode 100644 libssh2/include/libssh2_sftp.h delete mode 100644 libssh2/include/mac.h delete mode 100644 libssh2/include/misc.h delete mode 100644 libssh2/include/openssl.h delete mode 100644 libssh2/include/packet.h delete mode 100644 libssh2/include/session.h delete mode 100644 libssh2/include/sftp.h delete mode 100644 libssh2/include/transport.h delete mode 100644 libssh2/include/userauth.h delete mode 100755 libssh2/lib/libssh2.1.dylib delete mode 100644 libssh2/src/agent.c delete mode 100644 libssh2/src/channel.c delete mode 100644 libssh2/src/comp.c delete mode 100644 libssh2/src/crypt.c delete mode 100644 libssh2/src/global.c delete mode 100644 libssh2/src/hostkey.c delete mode 100644 libssh2/src/keepalive.c delete mode 100644 libssh2/src/kex.c delete mode 100644 libssh2/src/knownhost.c delete mode 100644 libssh2/src/libgcrypt.c delete mode 100644 libssh2/src/mac.c delete mode 100644 libssh2/src/misc.c delete mode 100644 libssh2/src/openssl.c delete mode 100644 libssh2/src/packet.c delete mode 100644 libssh2/src/pem.c delete mode 100644 libssh2/src/publickey.c delete mode 100644 libssh2/src/scp.c delete mode 100644 libssh2/src/session.c delete mode 100644 libssh2/src/sftp.c delete mode 100644 libssh2/src/transport.c delete mode 100644 libssh2/src/userauth.c delete mode 100644 libssh2/src/version.c create mode 100644 repo/CommitList.h create mode 100644 repo/CommitList.m create mode 100644 repo/EncryptionDatFile.h create mode 100644 repo/EncryptionDatFile.m rename repo/{FarkImpl.m => Fark.m} (54%) create mode 100644 repo/KeyValuePair.h rename GoogleDriveTargetConnection.h => repo/KeyValuePair.m (79%) create mode 100644 repo/NSData-Compress.h create mode 100644 repo/NSData-Compress.m create mode 100644 repo/ObjectEncryptor.h create mode 100644 repo/ObjectEncryptor.m create mode 100644 repo/ObjectEncryptorImpl.h create mode 100644 repo/ObjectEncryptorV1.h create mode 100644 repo/ObjectEncryptorV1.m rename repo/{FarkImpl.h => ObjectEncryptorV2.h} (73%) create mode 100644 repo/ObjectEncryptorV2.m create mode 100644 repo/PIELoader.h create mode 100644 repo/PIELoader.m create mode 100644 repo/PIELoaderWorker.h create mode 100644 repo/PIELoaderWorker.m create mode 100644 repo/PackBuilder.h create mode 100644 repo/PackBuilder.m create mode 100644 repo/PackBuilderEntry.h create mode 100644 repo/PackBuilderEntry.m create mode 100644 repo/PackIndexGenerator.h create mode 100644 repo/PackIndexGenerator.m rename s3restore/S3Restorer.h => repo/PackSetDB.h (62%) create mode 100644 repo/PackSetDB.m create mode 100644 repo/S3PIEInputStream.h create mode 100644 repo/S3PIEInputStream.m create mode 100644 repo/SynchronousPackSet.h create mode 100644 repo/SynchronousPackSet.m delete mode 100644 s3restore/S3Restorer.m delete mode 100644 s3restore/S3RestorerParamSet.m create mode 100644 s3restore/StandardRestoreItem.h create mode 100644 s3restore/StandardRestoreItem.m create mode 100644 s3restore/StandardRestoreWorker.h create mode 100644 s3restore/StandardRestoreWorker.m create mode 100644 s3restore/StandardRestorer.h create mode 100644 s3restore/StandardRestorer.m rename s3restore/{S3RestorerDelegate.h => StandardRestorerDelegate.h} (74%) create mode 100644 s3restore/StandardRestorerDelegateMux.h create mode 100644 s3restore/StandardRestorerDelegateMux.m create mode 100644 s3restore/StandardRestorerError.h create mode 100644 s3restore/StandardRestorerError.m rename s3restore/{S3RestorerParamSet.h => StandardRestorerParamSet.h} (90%) create mode 100644 s3restore/StandardRestorerParamSet.m create mode 100644 sqlite3.c create mode 100644 sqlite3.h diff --git a/ArqRestoreCommand.h b/ArqRestoreCommand.h index 9395a91..423d9fc 100644 --- a/ArqRestoreCommand.h +++ b/ArqRestoreCommand.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2009-2014, Stefan Reitshamer http://www.haystacksoftware.com + Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.com All rights reserved. @@ -31,14 +31,14 @@ */ -#import "S3RestorerDelegate.h" + +#import "StandardRestorerDelegate.h" #import "S3GlacierRestorerDelegate.h" #import "GlacierRestorerDelegate.h" @class Target; -@interface ArqRestoreCommand : NSObject { - Target *target; +@interface ArqRestoreCommand : NSObject { unsigned long long maxRequested; unsigned long long maxTransfer; } diff --git a/ArqRestoreCommand.m b/ArqRestoreCommand.m index 2eb6789..14b0743 100644 --- a/ArqRestoreCommand.m +++ b/ArqRestoreCommand.m @@ -1,5 +1,5 @@ /* - Copyright (c) 2009-2014, Stefan Reitshamer http://www.haystacksoftware.com + Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.com All rights reserved. @@ -30,6 +30,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + + #import "ArqRestoreCommand.h" #import "Target.h" #import "AWSRegion.h" @@ -38,24 +40,27 @@ #import "UserAndComputer.h" #import "Bucket.h" #import "Repo.h" -#import "S3RestorerParamSet.h" +#import "StandardRestorerParamSet.h" #import "Tree.h" #import "Commit.h" +#import "Node.h" #import "BlobKey.h" -#import "S3Restorer.h" +#import "StandardRestorer.h" #import "S3GlacierRestorerParamSet.h" #import "S3GlacierRestorer.h" #import "GlacierRestorerParamSet.h" #import "GlacierRestorer.h" #import "S3AuthorizationProvider.h" +#import "S3AuthorizationProviderFactory.h" +#import "NSString_extra.h" +#import "TargetFactory.h" +#import "RegexKitLite.h" +#import "BackupSet.h" +#import "ExePath.h" +#import "AWSRegion.h" @implementation ArqRestoreCommand -- (void)dealloc { - [target release]; - [super dealloc]; -} - - (NSString *)errorDomain { return @"ArqRestoreCommandErrorDomain"; } @@ -71,60 +76,29 @@ return NO; } - int index = 1; - if ([[args objectAtIndex:1] isEqualToString:@"-l"]) { - if ([args count] < 4) { - SETNSERROR([self errorDomain], ERROR_USAGE, @"missing arguments"); - return NO; - } - setHSLogLevel(hsLogLevelForName([args objectAtIndex:2])); - index += 2; + if ([args count] > 3 && [[args objectAtIndex:1] isEqualToString:@"-l"]) { + [[HSLog sharedHSLog] setHSLogLevel:[HSLog hsLogLevelForName:[args objectAtIndex:2]]]; + args = [NSMutableArray arrayWithArray:[args subarrayWithRange:NSMakeRange(2, [args count] - 2)]]; } - NSString *cmd = [args objectAtIndex:index]; + NSString *cmd = [args objectAtIndex:1]; - int targetParamsIndex = index + 1; - if ([cmd isEqualToString:@"listcomputers"]) { - // Valid command, but no additional args. - + if ([cmd isEqualToString:@"listtargets"]) { + return [self listTargets:error]; + } else if ([cmd isEqualToString:@"addtarget"]) { + return [self addTarget:args error:error]; + } else if ([cmd isEqualToString:@"deletetarget"]) { + return [self deleteTarget:args error:error]; + } else if ([cmd isEqualToString:@"listcomputers"]) { + return [self listComputers:args error:error]; } else if ([cmd isEqualToString:@"listfolders"]) { - if ((argc - targetParamsIndex) < 2) { - SETNSERROR([self errorDomain], ERROR_USAGE, @"missing arguments for listfolders command"); - return NO; - } - targetParamsIndex += 2; + return [self listFolders:args error:error]; + } else if ([cmd isEqualToString:@"listtree"]) { + return [self listTree:args error:error]; } else if ([cmd isEqualToString:@"restore"]) { - if ((argc - targetParamsIndex) < 4) { - SETNSERROR([self errorDomain], ERROR_USAGE, @"missing arguments"); - return NO; - } - targetParamsIndex += 4; - } else { - SETNSERROR([self errorDomain], ERROR_USAGE, @"unknown command: %@", cmd); - return NO; - } - - if (targetParamsIndex >= argc) { - SETNSERROR([self errorDomain], ERROR_USAGE, @"missing target type params"); - return NO; - } - target = [[self targetForParams:[args subarrayWithRange:NSMakeRange(targetParamsIndex, argc - targetParamsIndex)] error:error] retain]; - if (target == nil) { - return NO; - } - - if ([cmd isEqualToString:@"listcomputers"]) { - if (![self listComputers:error]) { - return NO; - } - } else if ([cmd isEqualToString:@"listfolders"]) { - if (![self listBucketsForComputerUUID:[args objectAtIndex:index+1] encryptionPassword:[args objectAtIndex:index+2] error:error]) { - return NO; - } - } else if ([cmd isEqualToString:@"restore"]) { - if (![self restoreComputerUUID:[args objectAtIndex:index+1] bucketUUID:[args objectAtIndex:index+3] encryptionPassword:[args objectAtIndex:index+2] restoreBytesPerSecond:[args objectAtIndex:index+4] error:error]) { - return NO; - } + return [self restore:args error:error]; + } else if ([cmd isEqualToString:@"clearcache"]) { + return [self clearCache:args error:error]; } else { SETNSERROR([self errorDomain], ERROR_USAGE, @"unknown command: %@", cmd); return NO; @@ -135,156 +109,126 @@ #pragma mark internal -- (Target *)targetForParams:(NSArray *)theParams error:(NSError **)error { - NSString *theTargetType = [theParams objectAtIndex:0]; - - Target *ret = nil; - if ([theTargetType isEqualToString:@"aws"]) { - if ([theParams count] != 4) { - SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid aws parameters"); - return nil; - } - - NSString *theAccessKey = [theParams objectAtIndex:1]; - NSString *theSecretKey = [theParams objectAtIndex:2]; - NSString *theBucketName = [theParams objectAtIndex:3]; - AWSRegion *awsRegion = [self awsRegionForAccessKey:theAccessKey secretKey:theSecretKey bucketName:theBucketName error:error]; - if (awsRegion == nil) { - return nil; - } - NSURL *s3Endpoint = [awsRegion s3EndpointWithSSL:YES]; - int port = [[s3Endpoint port] intValue]; - NSString *portString = @""; - if (port != 0) { - portString = [NSString stringWithFormat:@":%d", port]; - } - NSURL *targetEndpoint = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@@%@%@/%@", [s3Endpoint scheme], theAccessKey, [s3Endpoint host], portString, theBucketName]]; - ret = [[[Target alloc] initWithEndpoint:targetEndpoint secret:theSecretKey passphrase:nil] autorelease]; - } else if ([theTargetType isEqualToString:@"sftp"]) { - if ([theParams count] != 6 && [theParams count] != 7) { - SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid sftp parameters"); - return nil; - } - - NSString *hostname = [theParams objectAtIndex:1]; - int port = [[theParams objectAtIndex:2] intValue]; - NSString *path = [theParams objectAtIndex:3]; - NSString *username = [theParams objectAtIndex:4]; - NSString *secret = [theParams objectAtIndex:5]; - NSString *keyfilePassphrase = [theParams count] > 6 ? [theParams objectAtIndex:6] : nil; - - if (![path hasPrefix:@"/"]) { - path = [@"/~/" stringByAppendingString:path]; - } - NSString *escapedPath = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)path, NULL, (CFStringRef)@"!*'();:@&=+$,?%#[]", kCFStringEncodingUTF8); - NSString *escapedUsername = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)username, NULL, (CFStringRef)@"!*'();:@&=+$,?%#[]", kCFStringEncodingUTF8); - NSURL *endpoint = [NSURL URLWithString:[NSString stringWithFormat:@"sftp://%@@%@:%d%@", escapedUsername, hostname, port, escapedPath]]; +- (BOOL)listTargets:(NSError **)error { + printf("%-20s %s\n", "nickname:", "url:"); + for (Target *target in [[TargetFactory sharedTargetFactory] sortedTargets]) { + printf("%-20s %s\n", [[target nickname] UTF8String], [[[target endpoint] description] UTF8String]); + } + return YES; +} - ret = [[[Target alloc] initWithEndpoint:endpoint secret:secret passphrase:keyfilePassphrase] autorelease]; - } else if ([theTargetType isEqualToString:@"greenqloud"] - || [theTargetType isEqualToString:@"dreamobjects"] - || [theTargetType isEqualToString:@"googlecloudstorage"]) { - if ([theParams count] != 4) { - SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid %@ parameters", theTargetType); - return nil; +- (BOOL)addTarget:(NSArray *)args error:(NSError **)error { + if ([args count] < 5) { + SETNSERROR([self errorDomain], ERROR_USAGE, @"missing arguments"); + return NO; + } + NSString *targetUUID = [NSString stringWithRandomUUID]; + NSString *targetNickname = [args objectAtIndex:2]; + NSString *targetType = [args objectAtIndex:3]; + + NSURL *endpoint = nil; + NSString *secret = nil; + NSString *passphrase = nil; + NSString *oAuth2ClientId = nil; + NSString *oAuth2ClientSecret = nil; + NSString *oAuth2RedirectURI = nil; + + if ([targetType isEqualToString:@"aws"]) { + if ([args count] != 6) { + SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid arguments"); + return NO; } - NSString *theAccessKey = [theParams objectAtIndex:1]; - NSString *theSecretKey = [theParams objectAtIndex:2]; - NSString *theBucketName = [theParams objectAtIndex:3]; - NSString *theHostname = nil; - if ([theTargetType isEqualToString:@"greenqloud"]) { - theHostname = @"s.greenqloud.com"; - } else if ([theTargetType isEqualToString:@"dreamobjects"]) { - theHostname = @"objects.dreamhost.com"; - } else if ([theTargetType isEqualToString:@"googlecloudstorage"]) { - theHostname = @"storage.googleapis.com"; - } else { - SETNSERROR([self errorDomain], ERROR_USAGE, @"no hostname for target type: %@", theTargetType); - return nil; + NSString *accessKeyId = [args objectAtIndex:4]; + AWSRegion *usEast1 = [AWSRegion usEast1]; + NSString *urlString = [NSString stringWithFormat:@"https://%@@%@/any_bucket", accessKeyId, [[usEast1 s3EndpointWithSSL:NO] host]]; + + endpoint = [NSURL URLWithString:urlString]; + secret = [args objectAtIndex:5]; + + } else if ([targetType isEqualToString:@"local"]) { + if ([args count] != 5) { + SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid arguments"); + return NO; } - NSURL *endpoint = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@@%@/%@", theAccessKey, theHostname, theBucketName]]; - ret = [[[Target alloc] initWithEndpoint:endpoint secret:theSecretKey passphrase:nil] autorelease]; - } else if ([theTargetType isEqualToString:@"s3compatible"]) { - if ([theParams count] != 5) { - SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid %@ parameters", theTargetType); - return nil; - } - - NSURL *theURL = [NSURL URLWithString:[theParams objectAtIndex:1]]; - if (theURL == nil) { - SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid url %@", [theParams objectAtIndex:1]); - return nil; - } - - NSString *theAccessKey = [theParams objectAtIndex:2]; - NSString *theSecretKey = [theParams objectAtIndex:3]; - NSString *theBucketName = [theParams objectAtIndex:4]; - - NSMutableString *urlString = [NSMutableString stringWithString:[theURL scheme]]; - [urlString appendFormat:@"://%@@%@", theAccessKey, [theURL host]]; - NSNumber *port = [theURL port]; - if (port != nil) { - [urlString appendFormat:@":%d", [port intValue]]; - } - [urlString appendString:@"/"]; - [urlString appendString:theBucketName]; - NSURL *endpoint = [NSURL URLWithString:urlString]; - ret = [[[Target alloc] initWithEndpoint:endpoint secret:theSecretKey passphrase:nil] autorelease]; - - } else if ([theTargetType isEqualToString:@"googledrive"]) { - if ([theParams count] != 3) { - SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid googledrive parameters"); - return nil; - } - - NSString *theRefreshToken = [theParams objectAtIndex:1]; - NSString *thePath = [theParams objectAtIndex:2]; - - NSString *escapedPath = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)thePath, CFSTR("/"), CFSTR("@?=&+"), kCFStringEncodingUTF8); - [escapedPath autorelease]; - - NSURL *endpoint = [NSURL URLWithString:[NSString stringWithFormat:@"googledrive://unknown_email_address@www.googleapis.com%@", escapedPath]]; - ret = [[[Target alloc] initWithEndpoint:endpoint secret:theRefreshToken passphrase:nil] autorelease]; + endpoint = [NSURL fileURLWithPath:[args objectAtIndex:4]]; + secret = @"unused"; } else { - SETNSERROR([self errorDomain], ERROR_USAGE, @"unknown target type: %@", theTargetType); - return nil; + SETNSERROR([self errorDomain], -1, @"unknown target type: %@", targetType); + return NO; } - return ret; -} - -- (AWSRegion *)awsRegionForAccessKey:(NSString *)theAccessKey secretKey:(NSString *)theSecretKey bucketName:(NSString *)theBucketName error:(NSError **)error { - S3AuthorizationProvider *sap = [[[S3AuthorizationProvider alloc] initWithAccessKey:theAccessKey secretKey:theSecretKey] autorelease]; - NSURL *endpoint = [[AWSRegion usEast1] s3EndpointWithSSL:YES]; - S3Service *s3 = [[[S3Service alloc] initWithS3AuthorizationProvider:sap endpoint:endpoint useAmazonRRS:NO] autorelease]; - NSString *location = [s3 locationOfS3Bucket:theBucketName targetConnectionDelegate:nil error:error]; - if (location == nil) { - return nil; + Target *target = [[[Target alloc] initWithUUID:targetUUID nickname:targetNickname endpoint:endpoint awsRequestSignatureVersion:4] autorelease]; + [target setOAuth2ClientId:oAuth2ClientId]; + [target setOAuth2RedirectURI:oAuth2RedirectURI]; + if (![[TargetFactory sharedTargetFactory] saveTarget:target error:error]) { + return NO; } - return [AWSRegion regionWithLocation:location]; + if (![target setSecret:secret trustedAppPaths:[NSArray arrayWithObject:[ExePath exePath]] error:error]) { + return NO; + } + if (passphrase != nil) { + if (![target setPassphrase:passphrase trustedAppPaths:[NSArray arrayWithObject:[ExePath exePath]] error:error]) { + return NO; + } + } + if (oAuth2ClientSecret != nil) { + if (![target setOAuth2ClientSecret:oAuth2ClientSecret trustedAppPaths:[NSArray arrayWithObject:[ExePath exePath]] error:error]) { + return NO; + } + } + + return YES; +} +- (BOOL)deleteTarget:(NSArray *)args error:(NSError **)error { + if ([args count] != 3) { + SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid arguments"); + return NO; + } + Target *target = [[TargetFactory sharedTargetFactory] targetWithNickname:[args objectAtIndex:2]]; + if (target == nil) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"target not found"); + return NO; + } + TargetConnection *conn = [[target newConnection:error] autorelease]; + if (conn == nil) { + return NO; + } + if (![conn clearAllCachedData:error]) { + return NO; + } + + return [[TargetFactory sharedTargetFactory] deleteTarget:target error:error]; } -- (BOOL)listComputers:(NSError **)error { - NSArray *expandedTargetList = [self expandedTargetList:error]; +- (BOOL)listComputers:(NSArray *)args error:(NSError **)error { + if ([args count] != 3) { + SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid arguments"); + return NO; + } + Target *target = [[TargetFactory sharedTargetFactory] targetWithNickname:[args objectAtIndex:2]]; + if (target == nil) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"target not found"); + return NO; + } + NSArray *expandedTargetList = [self expandedTargetListForTarget:target error:error]; if (expandedTargetList == nil) { return NO; } - NSMutableArray *ret = [NSMutableArray array]; for (Target *theTarget in expandedTargetList) { NSError *myError = nil; HSLogDebug(@"getting backup sets for %@", theTarget); - NSArray *backupSets = [BackupSet allBackupSetsForTarget:theTarget targetConnectionDelegate:nil error:&myError]; + NSArray *backupSets = [BackupSet allBackupSetsForTarget:theTarget targetConnectionDelegate:nil activityListener:nil error:&myError]; if (backupSets == nil) { if ([myError isErrorWithDomain:[S3Service errorDomain] code:S3SERVICE_ERROR_AMAZON_ERROR] && [[[myError userInfo] objectForKey:@"HTTPStatusCode"] intValue] == 403) { HSLogError(@"access denied getting backup sets for %@", theTarget); } else { HSLogError(@"error getting backup sets for %@: %@", theTarget, myError); SETERRORFROMMYERROR; - return nil; + return NO; } } else { printf("target: %s\n", [[theTarget endpointDisplayName] UTF8String]); @@ -294,65 +238,32 @@ } } } - return ret; -} -- (NSArray *)expandedTargetList:(NSError **)error { - NSMutableArray *expandedTargetList = [NSMutableArray arrayWithObject:target]; -// if ([target targetType] == kTargetAWS -// || [target targetType] == kTargetDreamObjects -// || [target targetType] == kTargetGoogleCloudStorage -// || [target targetType] == kTargetGreenQloud -// || [target targetType] == kTargetS3Compatible) { -// NSError *myError = nil; -// NSArray *targets = [self expandedTargetsForS3Target:target error:&myError]; -// if (targets == nil) { -// HSLogError(@"failed to expand target list for %@: %@", target, myError); -// } else { -// [expandedTargetList setArray:targets]; -// HSLogDebug(@"expandedTargetList is now: %@", expandedTargetList); -// } -// } - return expandedTargetList; -} -- (NSArray *)expandedTargetsForS3Target:(Target *)theTarget error:(NSError **)error { - S3Service *s3 = [theTarget s3:error]; - if (s3 == nil) { - return nil; - } - NSArray *s3BucketNames = [s3 s3BucketNamesWithTargetConnectionDelegate:nil error:error]; - if (s3BucketNames == nil) { - return nil; - } - HSLogDebug(@"s3BucketNames for %@: %@", theTarget, s3BucketNames); - - NSURL *originalEndpoint = [theTarget endpoint]; - NSMutableArray *ret = [NSMutableArray array]; - - for (NSString *s3BucketName in s3BucketNames) { - NSURL *endpoint = nil; - if ([theTarget targetType] == kTargetAWS) { - NSString *location = [s3 locationOfS3Bucket:s3BucketName targetConnectionDelegate:nil error:error]; - if (location == nil) { - return nil; - } - AWSRegion *awsRegion = [AWSRegion regionWithLocation:location]; - HSLogDebug(@"awsRegion for s3BucketName %@: %@", s3BucketName, location); - - NSURL *s3Endpoint = [awsRegion s3EndpointWithSSL:YES]; - HSLogDebug(@"s3Endpoint: %@", s3Endpoint); - endpoint = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@@%@/%@", [originalEndpoint user], [s3Endpoint host], s3BucketName]]; - } else { - endpoint = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@@%@/%@", [originalEndpoint scheme], [originalEndpoint user], [originalEndpoint host], s3BucketName]]; - } - HSLogDebug(@"endpoint: %@", endpoint); - - Target *theTarget = [[[Target alloc] initWithEndpoint:endpoint secret:[theTarget secret:NULL] passphrase:[theTarget passphrase:NULL]] autorelease]; - [ret addObject:theTarget]; - } - return ret; + return YES; } -- (BOOL)listBucketsForComputerUUID:(NSString *)theComputerUUID encryptionPassword:(NSString *)theEncryptionPassword error:(NSError **)error { + +- (BOOL)listFolders:(NSArray *)args error:(NSError **)error { + if ([args count] != 5) { + SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid arguments"); + return NO; + } + Target *target = [[TargetFactory sharedTargetFactory] targetWithNickname:[args objectAtIndex:2]]; + if (target == nil) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"target not found"); + return NO; + } + + NSString *theComputerUUID = [args objectAtIndex:3]; + NSString *theEncryptionPassword = [args objectAtIndex:4]; + + BackupSet *backupSet = [self backupSetForTarget:target computerUUID:theComputerUUID error:error]; + if (backupSet == nil) { + return NO; + } + + // Reset Target: + target = [backupSet target]; + NSArray *buckets = [Bucket bucketsWithTarget:target computerUUID:theComputerUUID encryptionPassword:theEncryptionPassword targetConnectionDelegate:nil error:error]; if (buckets == nil) { return NO; @@ -366,151 +277,359 @@ printf("\t\tuuid %s\n", [[bucket bucketUUID] UTF8String]); } - return YES; } -- (BOOL)restoreComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID encryptionPassword:(NSString *)theEncryptionPassword restoreBytesPerSecond:(NSString *)theRestoreBytesPerSecond error:(NSError **)error { - Bucket *myBucket = nil; - NSArray *expandedTargetList = [self expandedTargetList:error]; - if (expandedTargetList == nil) { +- (BOOL)listTree:(NSArray *)args error:(NSError **)error { + if ([args count] != 6) { + SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid arguments"); return NO; } - for (Target *theTarget in expandedTargetList) { - NSArray *buckets = [Bucket bucketsWithTarget:theTarget computerUUID:theComputerUUID encryptionPassword:theEncryptionPassword targetConnectionDelegate:nil error:error]; - if (buckets == nil) { - return NO; - } - for (Bucket *bucket in buckets) { - if ([[bucket bucketUUID] isEqualToString:theBucketUUID]) { - myBucket = bucket; - break; - } - } - - if (myBucket != nil) { + Target *target = [[TargetFactory sharedTargetFactory] targetWithNickname:[args objectAtIndex:2]]; + if (target == nil) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"target not found"); + return NO; + } + + NSString *theComputerUUID = [args objectAtIndex:3]; + NSString *theEncryptionPassword = [args objectAtIndex:4]; + NSString *theBucketUUID = [args objectAtIndex:5]; + + BackupSet *backupSet = [self backupSetForTarget:target computerUUID:theComputerUUID error:error]; + if (backupSet == nil) { + return NO; + } + + // Reset Target: + target = [backupSet target]; + + NSArray *buckets = [Bucket bucketsWithTarget:target computerUUID:theComputerUUID encryptionPassword:theEncryptionPassword targetConnectionDelegate:nil error:error]; + if (buckets == nil) { + return NO; + } + Bucket *matchingBucket = nil; + for (Bucket *bucket in buckets) { + if ([[bucket bucketUUID] isEqualToString:theBucketUUID]) { + matchingBucket = bucket; break; } } - if (myBucket == nil) { + if (matchingBucket == nil) { SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"folder %@ not found", theBucketUUID); return NO; } - Repo *repo = [[[Repo alloc] initWithBucket:myBucket encryptionPassword:theEncryptionPassword targetUID:getuid() targetGID:getgid() loadExistingMutablePackFiles:NO targetConnectionDelegate:nil repoDelegate:nil error:error] autorelease]; + printf("target %s\n", [[target endpointDisplayName] UTF8String]); + printf("computer %s\n", [theComputerUUID UTF8String]); + printf("folder %s\n", [theBucketUUID UTF8String]); + + Repo *repo = [[[Repo alloc] initWithBucket:matchingBucket encryptionPassword:theEncryptionPassword targetConnectionDelegate:nil repoDelegate:nil activityListener:nil error:error] autorelease]; if (repo == nil) { return NO; } + BlobKey *headBlobKey = [repo headBlobKey:error]; + if (headBlobKey == nil) { + return NO; + } + Commit *head = [repo commitForBlobKey:headBlobKey error:error]; + if (head == nil) { + return NO; + } + Tree *rootTree = [repo treeForBlobKey:[head treeBlobKey] error:error]; + if (rootTree == nil) { + return NO; + } + return [self printTree:rootTree repo:repo relativePath:@"" error:error]; +} +- (BOOL)printTree:(Tree *)theTree repo:(Repo *)theRepo relativePath:(NSString *)theRelativePath error:(NSError **)error { + for (NSString *childName in [theTree childNodeNames]) { + NSString *childRelativePath = [theRelativePath stringByAppendingFormat:@"/%@", childName]; + Node *childNode = [theTree childNodeWithName:childName]; + if ([childNode isTree]) { + printf("%s:\n", [childRelativePath UTF8String]); + Tree *childTree = [theRepo treeForBlobKey:[childNode treeBlobKey] error:error]; + if (childTree == nil) { + return NO; + } + if (![self printTree:childTree + repo:theRepo + relativePath:childRelativePath + error:error]) { + return NO; + } + } else { + printf("%s\n", [childRelativePath UTF8String]); + } + } + return YES; +} + +- (BOOL)restore:(NSArray *)args error:(NSError **)error { + if ([args count] != 6) { + SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid arguments"); + return NO; + } + Target *target = [[TargetFactory sharedTargetFactory] targetWithNickname:[args objectAtIndex:2]]; + if (target == nil) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"target not found"); + return NO; + } + NSString *theComputerUUID = [args objectAtIndex:3]; + NSString *theEncryptionPassword = [args objectAtIndex:4]; + NSString *theBucketUUID = [args objectAtIndex:5]; + BackupSet *backupSet = [self backupSetForTarget:target computerUUID:theComputerUUID error:error]; + if (backupSet == nil) { + return NO; + } + + // Reset Target: + target = [backupSet target]; + + NSArray *buckets = [Bucket bucketsWithTarget:target computerUUID:theComputerUUID encryptionPassword:theEncryptionPassword targetConnectionDelegate:nil error:error]; + if (buckets == nil) { + return NO; + } + Bucket *matchingBucket = nil; + for (Bucket *bucket in buckets) { + if ([[bucket bucketUUID] isEqualToString:theBucketUUID]) { + matchingBucket = bucket; + break; + } + } + if (matchingBucket == nil) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"folder %@ not found", theBucketUUID); + return NO; + } + + printf("target %s\n", [[target endpointDisplayName] UTF8String]); + printf("computer %s\n", [theComputerUUID UTF8String]); + printf("folder %s\n", [theBucketUUID UTF8String]); + + Repo *repo = [[[Repo alloc] initWithBucket:matchingBucket encryptionPassword:theEncryptionPassword targetConnectionDelegate:nil repoDelegate:nil activityListener:nil error:error] autorelease]; + if (repo == nil) { + return NO; + } BlobKey *commitBlobKey = [repo headBlobKey:error]; if (commitBlobKey == nil) { return NO; } - Commit *commit = [repo commitForBlobKey:commitBlobKey dataSize:NULL error:error]; + Commit *commit = [repo commitForBlobKey:commitBlobKey error:error]; if (commit == nil) { return NO; } - NSString *destinationPath = [[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:[[myBucket localPath] lastPathComponent]]; + NSString *destinationPath = [[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:[[matchingBucket localPath] lastPathComponent]]; if ([[NSFileManager defaultManager] fileExistsAtPath:destinationPath]) { SETNSERROR([self errorDomain], -1, @"%@ already exists", destinationPath); return NO; } - printf("target %s\n", [[[myBucket target] endpointDisplayName] UTF8String]); - printf("computer %s\n", [[myBucket computerUUID] UTF8String]); - printf("\nrestoring folder %s to %s\n\n", [[myBucket localPath] UTF8String], [destinationPath UTF8String]); + printf("\nrestoring folder %s to %s\n\n", [[matchingBucket localPath] UTF8String], [destinationPath UTF8String]); + + int bytesPerSecond = 500000; AWSRegion *region = [AWSRegion regionWithS3Endpoint:[target endpoint]]; BOOL isGlacierDestination = [region supportsGlacier]; - if ([myBucket storageType] == StorageTypeGlacier && isGlacierDestination) { - int bytesPerSecond = [theRestoreBytesPerSecond intValue]; - if (bytesPerSecond == 0) { - SETNSERROR([self errorDomain], -1, @"invalid bytes_per_second %@", theRestoreBytesPerSecond); - return NO; - } - - GlacierRestorerParamSet *paramSet = [[[GlacierRestorerParamSet alloc] initWithBucket:myBucket + if ([matchingBucket storageType] == StorageTypeGlacier && isGlacierDestination) { + GlacierRestorerParamSet *paramSet = [[[GlacierRestorerParamSet alloc] initWithBucket:matchingBucket encryptionPassword:theEncryptionPassword downloadBytesPerSecond:bytesPerSecond + glacierRetrievalTier:GLACIER_RETRIEVAL_TIER_EXPEDITED commitBlobKey:commitBlobKey - rootItemName:[[myBucket localPath] lastPathComponent] + rootItemName:[[matchingBucket localPath] lastPathComponent] treeVersion:CURRENT_TREE_VERSION - treeIsCompressed:[[commit treeBlobKey] compressed] treeBlobKey:[commit treeBlobKey] nodeName:nil targetUID:getuid() targetGID:getgid() useTargetUIDAndGID:YES destinationPath:destinationPath - logLevel:global_hslog_level] autorelease]; + logLevel:[[HSLog sharedHSLog] hsLogLevel]] autorelease]; [[[GlacierRestorer alloc] initWithGlacierRestorerParamSet:paramSet delegate:self] autorelease]; - } else if ([myBucket storageType] == StorageTypeS3Glacier && isGlacierDestination) { - int bytesPerSecond = [theRestoreBytesPerSecond intValue]; - if (bytesPerSecond == 0) { - SETNSERROR([self errorDomain], -1, @"invalid bytes_per_second %@", theRestoreBytesPerSecond); - return NO; - } - - S3GlacierRestorerParamSet *paramSet = [[[S3GlacierRestorerParamSet alloc] initWithBucket:myBucket + } else if ([matchingBucket storageType] == StorageTypeS3Glacier && isGlacierDestination) { + S3GlacierRestorerParamSet *paramSet = [[[S3GlacierRestorerParamSet alloc] initWithBucket:matchingBucket encryptionPassword:theEncryptionPassword downloadBytesPerSecond:bytesPerSecond + glacierRetrievalTier:GLACIER_RETRIEVAL_TIER_EXPEDITED commitBlobKey:commitBlobKey - rootItemName:[[myBucket localPath] lastPathComponent] + rootItemName:[[matchingBucket localPath] lastPathComponent] treeVersion:CURRENT_TREE_VERSION - treeIsCompressed:[[commit treeBlobKey] compressed] treeBlobKey:[commit treeBlobKey] nodeName:nil targetUID:getuid() targetGID:getgid() useTargetUIDAndGID:YES destinationPath:destinationPath - logLevel:global_hslog_level] autorelease]; + logLevel:[[HSLog sharedHSLog] hsLogLevel]] autorelease]; S3GlacierRestorer *restorer = [[[S3GlacierRestorer alloc] initWithS3GlacierRestorerParamSet:paramSet delegate:self] autorelease]; [restorer run]; } else { - S3RestorerParamSet *paramSet = [[[S3RestorerParamSet alloc] initWithBucket:myBucket - encryptionPassword:theEncryptionPassword - commitBlobKey:commitBlobKey - rootItemName:[[myBucket localPath] lastPathComponent] - treeVersion:CURRENT_TREE_VERSION - treeIsCompressed:[[commit treeBlobKey] compressed] - treeBlobKey:[commit treeBlobKey] - nodeName:nil - targetUID:getuid() - targetGID:getgid() - useTargetUIDAndGID:YES - destinationPath:destinationPath - logLevel:global_hslog_level] autorelease]; - [[[S3Restorer alloc] initWithParamSet:paramSet delegate:self] autorelease]; + StandardRestorerParamSet *paramSet = [[[StandardRestorerParamSet alloc] initWithBucket:matchingBucket + encryptionPassword:theEncryptionPassword + commitBlobKey:commitBlobKey + rootItemName:[[matchingBucket localPath] lastPathComponent] + treeVersion:CURRENT_TREE_VERSION + treeBlobKey:[commit treeBlobKey] + nodeName:nil + targetUID:getuid() + targetGID:getgid() + useTargetUIDAndGID:YES + destinationPath:destinationPath + logLevel:[[HSLog sharedHSLog] hsLogLevel]] autorelease]; + [[[StandardRestorer alloc] initWithParamSet:paramSet delegate:self] autorelease]; } return YES; } +- (BOOL)clearCache:(NSArray *)args error:(NSError **)error { + if ([args count] != 3) { + SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid arguments"); + return NO; + } + Target *target = [[TargetFactory sharedTargetFactory] targetWithNickname:[args objectAtIndex:2]]; + if (target == nil) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"target not found"); + return NO; + } + TargetConnection *conn = [[target newConnection:error] autorelease]; + if (conn == nil) { + return NO; + } + return [conn clearAllCachedData:error]; +} -#pragma mark S3RestorerDelegate + +- (BackupSet *)backupSetForTarget:(Target *)theInitialTarget computerUUID:(NSString *)theComputerUUID error:(NSError **)error { + NSArray *expandedTargetList = [self expandedTargetListForTarget:theInitialTarget error:error]; + if (expandedTargetList == nil) { + return NO; + } + + for (Target *theTarget in expandedTargetList) { + NSError *myError = nil; + NSArray *backupSets = [BackupSet allBackupSetsForTarget:theTarget targetConnectionDelegate:nil activityListener:nil error:&myError]; + if (backupSets == nil) { + if ([myError isErrorWithDomain:[S3Service errorDomain] code:S3SERVICE_ERROR_AMAZON_ERROR] && [[[myError userInfo] objectForKey:@"HTTPStatusCode"] intValue] == 403) { + HSLogError(@"access denied getting backup sets for %@", theTarget); + } else { + HSLogError(@"error getting backup sets for %@: %@", theTarget, myError); + SETERRORFROMMYERROR; + return NO; + } + } else { + for (BackupSet *backupSet in backupSets) { + if ([[backupSet computerUUID] isEqualToString:theComputerUUID]) { + return backupSet; + } + } + } + } + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"backup set %@ not found at target\n", theComputerUUID); + return nil; +} + +- (NSArray *)expandedTargetListForTarget:(Target *)theTarget error:(NSError **)error { + NSArray *targets = nil; + + if ([theTarget targetType] == kTargetAWS) { + targets = [self expandedTargetsForS3Target:theTarget error:error]; + } else { + targets = [NSArray arrayWithObject:theTarget]; + } + return targets; +} +- (NSArray *)expandedTargetsForS3Target:(Target *)theTarget error:(NSError **)error { + NSString *theSecretKey = [theTarget secret:error]; + if (theSecretKey == nil) { + return nil; + } + S3Service *s3 = nil; + if ([AWSRegion regionWithS3Endpoint:[theTarget endpoint]] != nil) { + // It's S3. Get bucket name list from us-east-1 region. + NSURL *usEast1Endpoint = [[AWSRegion usEast1] s3EndpointWithSSL:YES]; + id sap = [[S3AuthorizationProviderFactory sharedS3AuthorizationProviderFactory] providerForEndpoint:usEast1Endpoint + accessKey:[[theTarget endpoint] user] + secretKey:theSecretKey + signatureVersion:4 + awsRegion:[AWSRegion usEast1]]; + s3 = [[[S3Service alloc] initWithS3AuthorizationProvider:sap endpoint:usEast1Endpoint] autorelease]; + } else { + s3 = [theTarget s3:error]; + if (s3 == nil) { + return nil; + } + } + NSArray *s3BucketNames = [s3 s3BucketNamesWithTargetConnectionDelegate:nil error:error]; + if (s3BucketNames == nil) { + return nil; + } + HSLogDebug(@"s3BucketNames for %@: %@", theTarget, s3BucketNames); + + NSURL *originalEndpoint = [theTarget endpoint]; + NSMutableArray *ret = [NSMutableArray array]; + + // WARNING: This is a hack! We're creating this Target using the same UUID so that the keychain lookups work! + NSString *targetUUID = [theTarget targetUUID]; + + for (NSString *s3BucketName in s3BucketNames) { + NSURL *endpoint = nil; + if ([theTarget targetType] == kTargetAWS) { + NSError *myError = nil; + NSString *location = [s3 locationOfS3Bucket:s3BucketName targetConnectionDelegate:nil error:&myError]; + if (location == nil) { + HSLogError(@"failed to get location of %@: %@", s3BucketName, myError); + } else { + AWSRegion *awsRegion = [AWSRegion regionWithLocation:location]; + HSLogDebug(@"awsRegion for s3BucketName %@: %@", s3BucketName, location); + + NSURL *s3Endpoint = [awsRegion s3EndpointWithSSL:YES]; + HSLogDebug(@"s3Endpoint: %@", s3Endpoint); + endpoint = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@@%@/%@", [originalEndpoint user], [s3Endpoint host], s3BucketName]]; + } + } else { + NSNumber *originalPort = [originalEndpoint port]; + NSString *portString = (originalPort == nil) ? @"" : [NSString stringWithFormat:@":%d", [originalPort intValue]]; + endpoint = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@@%@%@/%@", [originalEndpoint scheme], [originalEndpoint user], [originalEndpoint host], portString, s3BucketName]]; + } + + if (endpoint != nil) { + HSLogDebug(@"endpoint: %@", endpoint); + + Target *target = [[[Target alloc] initWithUUID:targetUUID + nickname:s3BucketName + endpoint:endpoint + awsRequestSignatureVersion:[theTarget awsRequestSignatureVersion]] autorelease]; + [ret addObject:target]; + } + } + return ret; +} + + +#pragma mark StandardRestorerDelegate // Methods return YES if cancel is requested. -- (BOOL)s3RestorerMessageDidChange:(NSString *)message { +- (BOOL)standardRestorerMessageDidChange:(NSString *)message { printf("status: %s\n", [message UTF8String]); return NO; } -- (BOOL)s3RestorerBytesTransferredDidChange:(NSNumber *)theTransferred { +- (BOOL)standardRestorerFileBytesRestoredDidChange:(NSNumber *)theTransferred { return NO; } -- (BOOL)s3RestorerTotalBytesToTransferDidChange:(NSNumber *)theTotal { +- (BOOL)standardRestorerTotalFileBytesToRestoreDidChange:(NSNumber *)theTotal { return NO; } -- (BOOL)s3RestorerErrorMessage:(NSString *)theErrorMessage didOccurForPath:(NSString *)thePath { +- (BOOL)standardRestorerErrorMessage:(NSString *)theErrorMessage didOccurForPath:(NSString *)thePath { printf("%s error: %s\n", [thePath UTF8String], [theErrorMessage UTF8String]); return NO; } -- (BOOL)s3RestorerDidSucceed { +- (BOOL)standardRestorerDidSucceed { return NO; } -- (BOOL)s3RestorerDidFail:(NSError *)error { +- (BOOL)standardRestorerDidFail:(NSError *)error { printf("failed: %s\n", [[error localizedDescription] UTF8String]); return NO; } diff --git a/ArqSalt.h b/ArqSalt.h index 308e7f9..7fb3ce8 100644 --- a/ArqSalt.h +++ b/ArqSalt.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2009-2014, Stefan Reitshamer http://www.haystacksoftware.com + Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.com All rights reserved. @@ -31,6 +31,7 @@ */ + #import "TargetConnection.h" @class AWSRegion; @class Target; @@ -38,15 +39,12 @@ @interface ArqSalt : NSObject { Target *target; - uid_t uid; - gid_t gid; NSString *computerUUID; } - (id)initWithTarget:(Target *)theTarget - targetUID:(uid_t)theTargetUID - targetGID:(gid_t)theTargetGID - computerUUID:(NSString *)theComputerUUID; -- (NSData *)saltWithTargetConnectionDelegate:(id )theDelegate error:(NSError **)error; -- (BOOL)saveSalt:(NSData *)theSalt targetConnectionDelegate:(id )theDelegate error:(NSError **)error; -- (NSData *)createSaltWithTargetConnectionDelegate:(id )theDelegate error:(NSError **)error; + computerUUID:(NSString *)theComputerUUID + error:(NSError **)error; + +- (BOOL)ensureSaltExistsAtTargetWithTargetConnectionDelegate:(id )theDelegate error:(NSError **)error; +- (NSData *)saltDataWithTargetConnectionDelegate:(id )theDelegate error:(NSError **)error; @end diff --git a/ArqSalt.m b/ArqSalt.m index e061e65..4a31695 100644 --- a/ArqSalt.m +++ b/ArqSalt.m @@ -1,5 +1,5 @@ /* - Copyright (c) 2009-2014, Stefan Reitshamer http://www.haystacksoftware.com + Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.com All rights reserved. @@ -31,12 +31,14 @@ */ + #import "ArqSalt.h" #import "NSFileManager_extra.h" #import "UserLibrary_Arq.h" #import "Target.h" #import "TargetConnection.h" #import "Streams.h" +#import "CacheOwnership.h" #define SALT_LENGTH (8) @@ -44,13 +46,15 @@ @implementation ArqSalt - (id)initWithTarget:(Target *)theTarget - targetUID:(uid_t)theTargetUID - targetGID:(gid_t)theTargetGID - computerUUID:(NSString *)theComputerUUID { + computerUUID:(NSString *)theComputerUUID + error:(NSError **)error { + if (theComputerUUID == nil) { + SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"no computer UUID given for salt file"); + return nil; + } + if (self = [super init]) { target = [theTarget retain]; - uid = theTargetUID; - gid = theTargetGID; computerUUID = [theComputerUUID retain]; } return self; @@ -61,53 +65,82 @@ [super dealloc]; } -- (NSData *)saltWithTargetConnectionDelegate:(id)theDelegate error:(NSError **)error { - NSData *ret = [NSData dataWithContentsOfFile:[self localPath] options:NSUncachedRead error:error]; - if (ret == nil) { - id targetConnection = [target newConnection]; - do { - ret = [targetConnection saltDataForComputerUUID:computerUUID delegate:theDelegate error:error]; - if (ret != nil) { - NSError *myError = nil; - if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:[self localPath] targetUID:uid targetGID:gid error:&myError] - || ![Streams writeData:ret atomicallyToFile:[self localPath] targetUID:uid targetGID:gid bytesWritten:NULL error:&myError]) { - HSLogError(@"error caching salt data to %@: %@", [self localPath], myError); - } - } - } while(0); - [targetConnection release]; - } - return ret; +- (NSString *)errorDomain { + return @"ArqSaltErrorDomain"; } -- (BOOL)saveSalt:(NSData *)theSalt targetConnectionDelegate:(id)theDelegate error:(NSError **)error { - id targetConnection = [target newConnection]; - BOOL ret = YES; - do { - ret = [targetConnection setSaltData:theSalt forComputerUUID:computerUUID delegate:theDelegate error:error] - && [[NSFileManager defaultManager] ensureParentPathExistsForPath:[self localPath] targetUID:uid targetGID:gid error:error] - && [Streams writeData:theSalt atomicallyToFile:[self localPath] targetUID:uid targetGID:gid bytesWritten:NULL error:error]; - } while (0); + +- (BOOL)ensureSaltExistsAtTargetWithTargetConnectionDelegate:(id )theDelegate error:(NSError **)error { + TargetConnection *targetConnection = [target newConnection:error]; + if (targetConnection == nil) { + return NO; + } + BOOL ret = [self ensureSaltExistsAtTargetWithTargetConnection:targetConnection targetConnectionDelegate:theDelegate error:error]; [targetConnection release]; return ret; } -- (NSData *)createSaltWithTargetConnectionDelegate:(id)theDelegate error:(NSError **)error { - NSData *theSalt = [self createRandomSalt]; - if (![self saveSalt:theSalt targetConnectionDelegate:theDelegate error:error]) { - return nil; +- (BOOL)ensureSaltExistsAtTargetWithTargetConnection:(TargetConnection *)targetConnection targetConnectionDelegate:(id )theDelegate error:(NSError **)error { + NSError *myError = nil; + NSData *saltData = [targetConnection saltDataForComputerUUID:computerUUID delegate:theDelegate error:&myError]; + if (saltData == nil) { + if ([myError code] != ERROR_NOT_FOUND) { + HSLogError(@"error getting salt from target: %@", myError); + SETERRORFROMMYERROR; + return NO; + } + + + // Try to replace salt file with file from cache. + + saltData = [NSData dataWithContentsOfFile:[self localPath] options:NSUncachedRead error:&myError]; + if (saltData == nil) { + if ([myError code] != ERROR_NOT_FOUND) { + HSLogError(@"error reading cached salt file: %@", myError); + } + SETNSERROR([self errorDomain], -1, @"salt data not found at target or in cache"); + return NO; + } + + if (![targetConnection setSaltData:saltData forComputerUUID:computerUUID delegate:theDelegate error:error]) { + return NO; + } } - return theSalt; + return YES; } - +- (NSData *)saltDataWithTargetConnectionDelegate:(id)theDelegate error:(NSError **)error { + NSData *ret = [NSData dataWithContentsOfFile:[self localPath] options:NSUncachedRead error:error]; + if (ret == nil) { + ret = [self saltFromTargetWithTargetConnectionDelegate:theDelegate error:error]; + } + return ret; +} + #pragma mark internal - (NSData *)createRandomSalt { unsigned char buf[SALT_LENGTH]; for (NSUInteger i = 0; i < SALT_LENGTH; i++) { - buf[i] = (unsigned char)(rand() % 256); + buf[i] = (unsigned char)arc4random_uniform(256); } return [[[NSData alloc] initWithBytes:buf length:SALT_LENGTH] autorelease]; } - (NSString *)localPath { - return [NSString stringWithFormat:@"%@/Cache.noindex/%@/%@/salt.dat", [UserLibrary arqUserLibraryPath], [target targetUUID], computerUUID]; + return [NSString stringWithFormat:@"%@/%@/%@/salt.dat", [UserLibrary arqCachePath], [target targetUUID], computerUUID]; +} +- (NSData *)saltFromTargetWithTargetConnectionDelegate:(id )theDelegate error:(NSError **)error { + NSData *ret = nil; + TargetConnection *targetConnection = [target newConnection:error]; + if (targetConnection == nil) { + return nil; + } + ret = [targetConnection saltDataForComputerUUID:computerUUID delegate:theDelegate error:error]; + if (ret != nil) { + NSError *myError = nil; + if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:[self localPath] targetUID:[[CacheOwnership sharedCacheOwnership] uid] targetGID:[[CacheOwnership sharedCacheOwnership] gid] error:&myError] + || ![Streams writeData:ret atomicallyToFile:[self localPath] targetUID:[[CacheOwnership sharedCacheOwnership] uid] targetGID:[[CacheOwnership sharedCacheOwnership] gid] bytesWritten:NULL error:&myError]) { + HSLogError(@"error caching salt data to %@: %@", [self localPath], myError); + } + } + [targetConnection release]; + return ret; } @end diff --git a/BackupSet.h b/BackupSet.h index 03b7a8c..5c5da3f 100644 --- a/BackupSet.h +++ b/BackupSet.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2009-2014, Stefan Reitshamer http://www.haystacksoftware.com + Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.com All rights reserved. @@ -32,18 +32,24 @@ + @class UserAndComputer; @class AppConfig; @class Target; @protocol TargetConnectionDelegate; +@protocol BackupSetActivityListener +- (void)backupSetActivity:(NSString *)theActivity; +@end + + @interface BackupSet : NSObject { Target *target; NSString *computerUUID; UserAndComputer *uac; } -+ (NSArray *)allBackupSetsForTarget:(Target *)theTarget targetConnectionDelegate:(id )theDelegate error:(NSError **)error; ++ (NSArray *)allBackupSetsForTarget:(Target *)theTarget targetConnectionDelegate:(id )theDelegate activityListener:(id )theActivityListener error:(NSError **)error; - (id)initWithTarget:(Target *)theTarget computerUUID:(NSString *)theComputerUUID diff --git a/BackupSet.m b/BackupSet.m index 3418e82..11001b5 100644 --- a/BackupSet.m +++ b/BackupSet.m @@ -1,5 +1,5 @@ /* - Copyright (c) 2009-2014, Stefan Reitshamer http://www.haystacksoftware.com + Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.com All rights reserved. @@ -31,51 +31,63 @@ */ + #import "BackupSet.h" #import "S3AuthorizationProvider.h" #import "S3Service.h" #import "GlacierAuthorizationProvider.h" #import "GlacierService.h" #import "UserAndComputer.h" -#import "S3DeleteReceiver.h" #import "CryptoKey.h" #import "RegexKitLite.h" #import "BlobKey.h" #import "Commit.h" #import "S3ObjectMetadata.h" -#import "ArqSalt.h" #import "AWSRegion.h" #import "Bucket.h" #import "Target.h" #import "TargetConnection.h" #import "Repo.h" +#import "UserLibrary_Arq.h" +#import "NSString+SBJSON.h" @implementation BackupSet -+ (NSArray *)allBackupSetsForTarget:(Target *)theTarget targetConnectionDelegate:(id )theDelegate error:(NSError **)error { - id targetConnection = [[theTarget newConnection] autorelease]; ++ (NSArray *)allBackupSetsForTarget:(Target *)theTarget targetConnectionDelegate:(id )theDelegate activityListener:(id)theActivityListener error:(NSError **)error { + TargetConnection *targetConnection = [theTarget newConnection:error]; + if (targetConnection == nil) { + return nil; + } + NSArray *ret = [BackupSet allBackupSetsForTarget:theTarget targetConnection:targetConnection targetConnectionDelegate:theDelegate activityListener:theActivityListener error:error]; + [targetConnection release]; + return ret; +} ++ (NSArray *)allBackupSetsForTarget:(Target *)theTarget targetConnection:(TargetConnection *)targetConnection targetConnectionDelegate:(id )theDelegate activityListener:(id)theActivityListener error:(NSError **)error { NSArray *theComputerUUIDs = [targetConnection computerUUIDsWithDelegate:theDelegate error:error]; if (theComputerUUIDs == nil) { return nil; } - + NSMutableArray *ret = [NSMutableArray array]; - for (NSString *theComputerUUID in theComputerUUIDs) { + for (NSUInteger i = 0; i < [theComputerUUIDs count]; i++) { + [theActivityListener backupSetActivity:[NSString stringWithFormat:@"Loading backup set %ld of %ld at %@", i+1, [theComputerUUIDs count], [theTarget description]]]; + + NSString *theComputerUUID = [theComputerUUIDs objectAtIndex:i]; NSError *uacError = nil; + UserAndComputer *uac = nil; NSData *uacData = [targetConnection computerInfoForComputerUUID:theComputerUUID delegate:theDelegate error:&uacError]; if (uacData == nil) { HSLogWarn(@"unable to read %@ (skipping): %@", theComputerUUID, [uacError localizedDescription]); } else { - UserAndComputer *uac = [[[UserAndComputer alloc] initWithXMLData:uacData error:&uacError] autorelease]; + uac = [[[UserAndComputer alloc] initWithXMLData:uacData error:&uacError] autorelease]; if (uac == nil) { HSLogError(@"error parsing UserAndComputer data %@: %@", theComputerUUID, uacError); - } else { - BackupSet *backupSet = [[[BackupSet alloc] initWithTarget:theTarget - computerUUID:theComputerUUID - userAndComputer:uac] autorelease]; - [ret addObject:backupSet]; } } + BackupSet *backupSet = [[[BackupSet alloc] initWithTarget:theTarget + computerUUID:theComputerUUID + userAndComputer:uac] autorelease]; + [ret addObject:backupSet]; } NSSortDescriptor *descriptor = [[[NSSortDescriptor alloc] initWithKey:@"description" ascending:YES] autorelease]; [ret sortUsingDescriptors:[NSArray arrayWithObject:descriptor]]; diff --git a/BaseTargetConnection.h b/BaseTargetConnection.h deleted file mode 100644 index eb3e99f..0000000 --- a/BaseTargetConnection.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - Copyright (c) 2009-2014, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - -@class Target; -@protocol RemoteFS; -@protocol TargetConnectionDelegate; - - -@interface BaseTargetConnection : NSObject { - Target *target; - id remoteFS; - NSString *pathPrefix; -} - -- (id)initWithTarget:(Target *)theTarget remoteFS:(id )theRemoteFS; - -- (NSArray *)computerUUIDsWithDelegate:(id )theDelegate error:(NSError **)error; -- (NSArray *)bucketUUIDsForComputerUUID:(NSString *)theComputerUUID deleted:(BOOL)deleted delegate:(id )theDelegate error:(NSError **)error; - -- (NSData *)bucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id )theDelegate error:(NSError **)error; -- (BOOL)saveBucketPlistData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id )theDelegate error:(NSError **)error; -- (BOOL)deleteBucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id )theDelegate error:(NSError **)error; - -- (NSData *)computerInfoForComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error; -- (BOOL)saveComputerInfo:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error; - -- (NSArray *)objectsWithPrefix:(NSString *)thePrefix delegate:(id )theDelegate error:(NSError **)error; -- (NSArray *)pathsWithPrefix:(NSString *)thePrefix delegate:(id )theDelegate error:(NSError **)error; -- (BOOL)deleteObjectsForComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error; -- (BOOL)deletePaths:(NSArray *)thePaths delegate:(id )theDelegate error:(NSError **)error; - -- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize delegate:(id )theDelegate error:(NSError **)error; -- (NSData *)contentsOfFileAtPath:(NSString *)thePath delegate:(id )theDelegate error:(NSError **)error; -- (BOOL)writeData:(NSData *)theData toFileAtPath:(NSString *)thePath dataTransferDelegate:(id )theDataTransferDelegate targetConnectionDelegate:(id )theTargetConnectionDelegate error:(NSError **)error; -- (BOOL)removeItemAtPath:(NSString *)thePath delegate:(id )theDelegate error:(NSError **)error; -- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath delegate:(id )theDelegate error:(NSError **)error; -- (NSNumber *)isObjectRestoredAtPath:(NSString *)thePath delegate:(id )theDelegate error:(NSError **)error; -- (BOOL)restoreObjectAtPath:(NSString *)thePath forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring delegate:(id )theDelegate error:(NSError **)error; - -- (NSData *)saltDataForComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error; -- (BOOL)setSaltData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error; - -@end diff --git a/BaseTargetConnection.m b/BaseTargetConnection.m deleted file mode 100644 index 0722256..0000000 --- a/BaseTargetConnection.m +++ /dev/null @@ -1,167 +0,0 @@ -/* - Copyright (c) 2009-2014, Stefan Reitshamer http://www.haystacksoftware.com - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of - their contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - -#import "Target.h" -#import "RemoteFS.h" -#import "BaseTargetConnection.h" -#import "RegexKitLite.h" -#import "TargetConnection.h" - - -@implementation BaseTargetConnection -- (id)initWithTarget:(Target *)theTarget remoteFS:(id)theRemoteFS { - if (self = [super init]) { - target = [theTarget retain]; - remoteFS = [theRemoteFS retain]; - - if ([[[theTarget endpoint] path] isEqualToString:@"/"]) { - pathPrefix = [@"" retain]; - } else { - pathPrefix = [[[theTarget endpoint] path] retain]; - } - } - return self; -} -- (void)dealloc { - [target release]; - [remoteFS release]; - [pathPrefix release]; - [super dealloc]; -} - -- (NSArray *)computerUUIDsWithDelegate:(id )theDelegate error:(NSError **)error { - NSArray *computerUUIDs = [remoteFS contentsOfDirectoryAtPath:[[target endpoint] path] targetConnectionDelegate:theDelegate error:error]; - if (computerUUIDs == nil) { - return nil; - } - - NSMutableArray *ret = [NSMutableArray array]; - for (NSString *computerUUID in computerUUIDs) { - if ([computerUUID rangeOfRegex:@"^(\\S{8}-\\S{4}-\\S{4}-\\S{4}-\\S{12})$"].location != NSNotFound) { - [ret addObject:computerUUID]; - } - } - return ret; -} - -- (NSArray *)bucketUUIDsForComputerUUID:(NSString *)theComputerUUID deleted:(BOOL)deleted delegate:(id )theDelegate error:(NSError **)error { - NSString *subdir = deleted ? @"deletedbuckets" : @"buckets"; - NSString *bucketsPrefix = [NSString stringWithFormat:@"%@/%@/%@/", pathPrefix, theComputerUUID, subdir]; - NSArray *bucketUUIDs = [remoteFS contentsOfDirectoryAtPath:bucketsPrefix targetConnectionDelegate:theDelegate error:error]; - if (bucketUUIDs == nil) { - return nil; - } - - NSMutableArray *ret = [NSMutableArray array]; - for (NSString *bucketUUID in bucketUUIDs) { - if ([bucketUUID rangeOfRegex:@"^(\\S{8}-\\S{4}-\\S{4}-\\S{4}-\\S{12})$"].location != NSNotFound) { - [ret addObject:bucketUUID]; - } - } - return ret; -} - -- (NSData *)bucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id )theDelegate error:(NSError **)error { - NSString *subdir = deleted ? @"deletedbuckets" : @"buckets"; - NSString *path = [NSString stringWithFormat:@"%@/%@/%@/%@", pathPrefix, theComputerUUID, subdir, theBucketUUID]; - return [remoteFS contentsOfFileAtPath:path dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error]; -} -- (BOOL)saveBucketPlistData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id )theDelegate error:(NSError **)error { - NSString *subdir = deleted ? @"deletedbuckets" : @"buckets"; - NSString *path = [NSString stringWithFormat:@"%@/%@/%@/%@", pathPrefix, theComputerUUID, subdir, theBucketUUID]; - return [remoteFS writeData:theData atomicallyToFileAtPath:path dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error]; -} -- (BOOL)deleteBucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id )theDelegate error:(NSError **)error { - NSString *subdir = deleted ? @"deletedbuckets" : @"buckets"; - NSString *path = [NSString stringWithFormat:@"%@/%@/%@/%@", pathPrefix, theComputerUUID, subdir, theBucketUUID]; - return [remoteFS removeItemAtPath:path targetConnectionDelegate:theDelegate error:error]; -} - -- (NSData *)computerInfoForComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error { - NSString *path = [NSString stringWithFormat:@"%@/%@/computerinfo", pathPrefix, theComputerUUID]; - return [remoteFS contentsOfFileAtPath:path dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error]; -} -- (BOOL)saveComputerInfo:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error { - NSString *path = [NSString stringWithFormat:@"%@/%@/computerinfo", pathPrefix, theComputerUUID]; - return [remoteFS writeData:theData atomicallyToFileAtPath:path dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error]; -} - -- (NSArray *)objectsWithPrefix:(NSString *)thePrefix delegate:(id )theDelegate error:(NSError **)error { - return [remoteFS objectsAtPath:thePrefix targetConnectionDelegate:theDelegate error:error]; -} -- (NSArray *)pathsWithPrefix:(NSString *)thePrefix delegate:(id )theDelegate error:(NSError **)error { - return [remoteFS pathsOfObjectsAtPath:thePrefix targetConnectionDelegate:theDelegate error:error]; -} -- (BOOL)deleteObjectsForComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error { - return [remoteFS removeItemAtPath:[NSString stringWithFormat:@"%@/%@", pathPrefix, theComputerUUID] targetConnectionDelegate:theDelegate error:error]; -} -- (BOOL)deletePaths:(NSArray *)thePaths delegate:(id )theDelegate error:(NSError **)error { - for (NSString *path in thePaths) { - if (![remoteFS removeItemAtPath:path targetConnectionDelegate:theDelegate error:error]) { - return NO; - } - } - return YES; -} - -- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize delegate:(id )theDelegate error:(NSError **)error { - return [remoteFS fileExistsAtPath:thePath dataSize:theDataSize targetConnectionDelegate:theDelegate error:error]; -} -- (NSData *)contentsOfFileAtPath:(NSString *)thePath delegate:(id )theDelegate error:(NSError **)error { - return [remoteFS contentsOfFileAtPath:thePath dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error]; -} -- (BOOL)writeData:(NSData *)theData toFileAtPath:(NSString *)thePath dataTransferDelegate:(id )theDataTransferDelegate targetConnectionDelegate:(id )theTargetConnectionDelegate error:(NSError **)error { - return [remoteFS writeData:theData atomicallyToFileAtPath:thePath dataTransferDelegate:theDataTransferDelegate targetConnectionDelegate:theTargetConnectionDelegate error:error]; -} -- (BOOL)removeItemAtPath:(NSString *)thePath delegate:(id )theDelegate error:(NSError **)error { - return [remoteFS removeItemAtPath:thePath targetConnectionDelegate:theDelegate error:error]; -} -- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath delegate:(id )theDelegate error:(NSError **)error { - return [remoteFS sizeOfItemAtPath:thePath targetConnectionDelegate:theDelegate error:error]; -} -- (NSNumber *)isObjectRestoredAtPath:(NSString *)thePath delegate:(id )theDelegate error:(NSError **)error { - return [remoteFS isObjectRestoredAtPath:thePath targetConnectionDelegate:theDelegate error:error]; -} -- (BOOL)restoreObjectAtPath:(NSString *)thePath forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring delegate:(id )theDelegate error:(NSError **)error { - return [remoteFS restoreObjectAtPath:thePath forDays:theDays alreadyRestoredOrRestoring:alreadyRestoredOrRestoring targetConnectionDelegate:theDelegate error:error]; -} - -- (NSData *)saltDataForComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error { - NSString *s3Path = [NSString stringWithFormat:@"%@/%@/salt", pathPrefix, theComputerUUID]; - return [remoteFS contentsOfFileAtPath:s3Path dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error]; -} -- (BOOL)setSaltData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id )theDelegate error:(NSError **)error { - NSString *s3Path = [NSString stringWithFormat:@"%@/%@/salt", pathPrefix, theComputerUUID]; - return [remoteFS writeData:theData atomicallyToFileAtPath:s3Path dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error]; -} -@end diff --git a/BlobKey.h b/BlobKey.h index 39134d8..935cbde 100644 --- a/BlobKey.h +++ b/BlobKey.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2009-2014, Stefan Reitshamer http://www.haystacksoftware.com + Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.com All rights reserved. @@ -31,10 +31,18 @@ */ + #import "StorageType.h" @class BufferedInputStream; +typedef enum { + BlobKeyCompressionNone = 0, + BlobKeyCompressionGzip = 1, + BlobKeyCompressionLZ4 = 2 +} BlobKeyCompressionType; + + @interface BlobKey : NSObject { StorageType storageType; NSString *archiveId; @@ -42,11 +50,12 @@ NSDate *archiveUploadedDate; unsigned char *sha1Bytes; BOOL stretchEncryptionKey; - BOOL compressed; + BlobKeyCompressionType compressionType; } -- (id)initWithSHA1:(NSString *)theSHA1 archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate compressed:(BOOL)isCompressed error:(NSError **)error; -- (id)initWithSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed error:(NSError **)error; -- (id)initWithStorageType:(StorageType)theStorageType archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate sha1:(NSString *)theSHA1 stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed error:(NSError **)error; + +- (id)initWithSHA1:(NSString *)theSHA1 archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate compressionType:(BlobKeyCompressionType)theCompressionType error:(NSError **)error; +- (id)initWithSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType stretchEncryptionKey:(BOOL)isStretchedKey compressionType:(BlobKeyCompressionType)theCompressionType error:(NSError **)error; +- (id)initWithStorageType:(StorageType)theStorageType archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate sha1:(NSString *)theSHA1 stretchEncryptionKey:(BOOL)isStretchedKey compressionType:(BlobKeyCompressionType)theCompressionType error:(NSError **)error; - (id)initCopyOfBlobKey:(BlobKey *)theBlobKey withStorageType:(StorageType)theStorageType; - (StorageType)storageType; @@ -56,6 +65,6 @@ - (NSString *)sha1; - (unsigned char *)sha1Bytes; - (BOOL)stretchEncryptionKey; -- (BOOL)compressed; +- (BlobKeyCompressionType)compressionType; - (BOOL)isEqualToBlobKey:(BlobKey *)other; @end diff --git a/BlobKey.m b/BlobKey.m index c519545..f7c5b8b 100644 --- a/BlobKey.m +++ b/BlobKey.m @@ -1,5 +1,5 @@ /* - Copyright (c) 2009-2014, Stefan Reitshamer http://www.haystacksoftware.com + Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.com All rights reserved. @@ -31,6 +31,7 @@ */ + #import "BlobKey.h" #import "BufferedInputStream.h" #import "StringIO.h" @@ -42,7 +43,7 @@ @implementation BlobKey -- (id)initWithSHA1:(NSString *)theSHA1 archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate compressed:(BOOL)isCompressed error:(NSError **)error { +- (id)initWithSHA1:(NSString *)theSHA1 archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate compressionType:(BlobKeyCompressionType)theCompressionType error:(NSError **)error { if (self = [super init]) { storageType = StorageTypeGlacier; @@ -52,7 +53,7 @@ return nil; } if ([sha1Data length] != 20) { - SETNSERROR(@"BlobKeyErrorDomain", -1, @"invalid sha1 %@ for BlobKey (must be 20 bytes)", theSHA1); + SETNSERROR(@"BlobKeyErrorDomain", -1, @"invalid sha1 '%@' for BlobKey (must be 20 bytes)", theSHA1); [self release]; return nil; } @@ -62,11 +63,11 @@ archiveId = [theArchiveId retain]; archiveSize = theArchiveSize; archiveUploadedDate = [theArchiveUploadedDate retain]; - compressed = isCompressed; + compressionType = theCompressionType; } return self; } -- (id)initWithSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed error:(NSError **)error { +- (id)initWithSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType stretchEncryptionKey:(BOOL)isStretchedKey compressionType:(BlobKeyCompressionType)theCompressionType error:(NSError **)error { if (self = [super init]) { storageType = theStorageType; @@ -76,7 +77,7 @@ return nil; } if ([sha1Data length] != 20) { - SETNSERROR(@"BlobKeyErrorDomain", -1, @"invalid sha1 %@ for BlobKey (must be 20 bytes)", theSHA1); + SETNSERROR(@"BlobKeyErrorDomain", -1, @"invalid sha1 '%@' for BlobKey (must be 20 bytes)", theSHA1); [self release]; return nil; } @@ -84,11 +85,11 @@ memcpy(sha1Bytes, [sha1Data bytes], 20); stretchEncryptionKey = isStretchedKey; - compressed = isCompressed; + compressionType = theCompressionType; } return self; } -- (id)initWithStorageType:(StorageType)theStorageType archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate sha1:(NSString *)theSHA1 stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed error:(NSError **)error { +- (id)initWithStorageType:(StorageType)theStorageType archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate sha1:(NSString *)theSHA1 stretchEncryptionKey:(BOOL)isStretchedKey compressionType:(BlobKeyCompressionType)theCompressionType error:(NSError **)error { if (self = [super init]) { storageType = theStorageType; archiveId = [theArchiveId retain]; @@ -101,7 +102,7 @@ return nil; } if ([sha1Data length] != 20) { - SETNSERROR(@"BlobKeyErrorDomain", -1, @"invalid sha1 %@ for BlobKey (must be 20 bytes)", theSHA1); + SETNSERROR(@"BlobKeyErrorDomain", -1, @"invalid sha1 '%@' for BlobKey (must be 20 bytes)", theSHA1); [self release]; return nil; } @@ -109,7 +110,7 @@ memcpy(sha1Bytes, [sha1Data bytes], 20); stretchEncryptionKey = isStretchedKey; - compressed = isCompressed; + compressionType = theCompressionType; } return self; } @@ -120,7 +121,7 @@ archiveUploadedDate:[theBlobKey archiveUploadedDate] sha1Bytes:[theBlobKey sha1Bytes] stretchEncryptionKey:[theBlobKey stretchEncryptionKey] - compressed:[theBlobKey compressed]]; + compressionType:[theBlobKey compressionType]]; } - (void)dealloc { [archiveId release]; @@ -150,8 +151,8 @@ - (BOOL)stretchEncryptionKey { return stretchEncryptionKey; } -- (BOOL)compressed { - return compressed; +- (BlobKeyCompressionType)compressionType { + return compressionType; } - (BOOL)isEqualToBlobKey:(BlobKey *)other { if (memcmp(sha1Bytes, [other sha1Bytes], 20) != 0) { @@ -166,17 +167,17 @@ #pragma mark NSCopying - (id)copyWithZone:(NSZone *)zone { - return [[BlobKey alloc] initWithStorageType:storageType archiveId:archiveId archiveSize:archiveSize archiveUploadedDate:archiveUploadedDate sha1Bytes:sha1Bytes stretchEncryptionKey:stretchEncryptionKey compressed:compressed]; + return [[BlobKey alloc] initWithStorageType:storageType archiveId:archiveId archiveSize:archiveSize archiveUploadedDate:archiveUploadedDate sha1Bytes:sha1Bytes stretchEncryptionKey:stretchEncryptionKey compressionType:compressionType]; } #pragma mark NSObject - (NSString *)description { if (storageType == StorageTypeS3 || storageType == StorageTypeS3Glacier) { - NSString *type = storageType == StorageTypeS3 ? @"S3" : @"S3Glacier"; - return [NSString stringWithFormat:@"", [self sha1], type, (stretchEncryptionKey ? @"YES" : @"NO"), (compressed ? @"YES" : @"NO")]; + NSString *type = storageType == StorageTypeS3 ? @"Standard" : @"S3Glacier"; + return [NSString stringWithFormat:@"", [self sha1], type, (stretchEncryptionKey ? @"YES" : @"NO"), compressionType]; } - return [NSString stringWithFormat:@"", [self sha1], archiveId, archiveSize, [self archiveUploadedDate], (stretchEncryptionKey ? @"YES" : @"NO"), (compressed ? @"YES" : @"NO")]; + return [NSString stringWithFormat:@"", [self sha1], archiveId, archiveSize, [self archiveUploadedDate], (stretchEncryptionKey ? @"YES" : @"NO"), compressionType]; } - (BOOL)isEqual:(id)anObject { if (![anObject isKindOfClass:[BlobKey class]]) { @@ -190,7 +191,7 @@ && [NSObject equalObjects:archiveId and:[other archiveId]] && archiveSize == [other archiveSize] && [NSObject equalObjects:archiveUploadedDate and:[other archiveUploadedDate]] - && compressed == [other compressed]; + && compressionType == [other compressionType]; } - (NSUInteger)hash { return (NSUInteger)(*sha1Bytes); @@ -198,7 +199,7 @@ #pragma mark internal -- (id)initWithStorageType:(StorageType)theStorageType archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate sha1Bytes:(unsigned char *)theSHA1Bytes stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed { +- (id)initWithStorageType:(StorageType)theStorageType archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate sha1Bytes:(unsigned char *)theSHA1Bytes stretchEncryptionKey:(BOOL)isStretchedKey compressionType:(BlobKeyCompressionType)theCompressionType { if (self = [super init]) { storageType = theStorageType; archiveId = [theArchiveId retain]; @@ -210,7 +211,7 @@ memcpy(sha1Bytes, theSHA1Bytes, 20); stretchEncryptionKey = isStretchedKey; - compressed = isCompressed; + compressionType = theCompressionType; } return self; } diff --git a/BlobKeyIO.h b/BlobKeyIO.h index 4b00010..663489f 100644 --- a/BlobKeyIO.h +++ b/BlobKeyIO.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2009-2014, Stefan Reitshamer http://www.haystacksoftware.com + Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.com All rights reserved. @@ -31,9 +31,10 @@ */ + @class BufferedInputStream; @class BufferedOutputStream; -@class BlobKey; +#import "BlobKey.h" @interface BlobKeyIO : NSObject { @@ -41,5 +42,5 @@ } + (void)write:(BlobKey *)theBlobKey to:(NSMutableData *)data; + (BOOL)write:(BlobKey *)theBlobKey to:(BufferedOutputStream *)os error:(NSError **)error; -+ (BOOL)read:(BlobKey **)theBlobKey from:(BufferedInputStream *)is treeVersion:(int)theTreeVersion compressed:(BOOL)isCompressed error:(NSError **)error; ++ (BOOL)read:(BlobKey **)theBlobKey from:(BufferedInputStream *)is treeVersion:(int)theTreeVersion compressionType:(BlobKeyCompressionType)theCompressionType error:(NSError **)error; @end diff --git a/BlobKeyIO.m b/BlobKeyIO.m index a41547a..9e7ebd2 100644 --- a/BlobKeyIO.m +++ b/BlobKeyIO.m @@ -1,5 +1,5 @@ /* - Copyright (c) 2009-2014, Stefan Reitshamer http://www.haystacksoftware.com + Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.com All rights reserved. @@ -31,6 +31,7 @@ */ + #import "BlobKeyIO.h" #import "BooleanIO.h" #import "StringIO.h" @@ -57,7 +58,7 @@ && [IntegerIO writeUInt64:[theBlobKey archiveSize] to:os error:error] && [DateIO write:[theBlobKey archiveUploadedDate] to:os error:error]; } -+ (BOOL)read:(BlobKey **)theBlobKey from:(BufferedInputStream *)is treeVersion:(int)theTreeVersion compressed:(BOOL)isCompressed error:(NSError **)error { ++ (BOOL)read:(BlobKey **)theBlobKey from:(BufferedInputStream *)is treeVersion:(int)theTreeVersion compressionType:(BlobKeyCompressionType)theCompressionType error:(NSError **)error { NSString *dataSHA1; BOOL stretchEncryptionKey = NO; StorageType storageType = StorageTypeS3; @@ -86,7 +87,7 @@ // If the sha1 is nil, it must have been a nil BlobKey, so we return nil here. *theBlobKey = nil; } else { - *theBlobKey = [[[BlobKey alloc] initWithStorageType:storageType archiveId:archiveId archiveSize:archiveSize archiveUploadedDate:archiveUploadedDate sha1:dataSHA1 stretchEncryptionKey:stretchEncryptionKey compressed:isCompressed error:error] autorelease]; + *theBlobKey = [[[BlobKey alloc] initWithStorageType:storageType archiveId:archiveId archiveSize:archiveSize archiveUploadedDate:archiveUploadedDate sha1:dataSHA1 stretchEncryptionKey:stretchEncryptionKey compressionType:theCompressionType error:error] autorelease]; if (*theBlobKey == nil) { return NO; } diff --git a/Bucket.h b/Bucket.h index 0bebd89..fe050a4 100644 --- a/Bucket.h +++ b/Bucket.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2009-2014, Stefan Reitshamer http://www.haystacksoftware.com + Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.com All rights reserved. @@ -31,6 +31,8 @@ */ + + #import "StorageType.h" @class AWSRegion; @class DictNode; @@ -40,8 +42,13 @@ @class BufferedInputStream; @class BufferedOutputStream; @protocol TargetConnectionDelegate; +@class TargetConnection; +@protocol BucketActivityListener +- (void)bucketActivity:(NSString *)theActivity; +@end + enum { BucketPathMixedState = -1, BucketPathOffState = 0, @@ -59,10 +66,12 @@ typedef NSInteger BucketPathState; StorageType storageType; NSMutableArray *ignoredRelativePaths; BucketExcludeSet *excludeSet; - NSMutableArray *stringArrayPairs; NSString *vaultName; NSDate *vaultCreatedDate; NSDate *plistDeletedDate; + BOOL skipDuringBackup; + BOOL excludeItemsWithTimeMachineExcludeMetadataFlag; + BOOL skipIfNotMounted; } + (NSArray *)bucketsWithTarget:(Target *)theTarget @@ -71,18 +80,18 @@ typedef NSInteger BucketPathState; targetConnectionDelegate:(id )theTCD error:(NSError **)error; ++ (NSArray *)bucketsWithTarget:(Target *)theTarget + computerUUID:(NSString *)theComputerUUID + encryptionPassword:(NSString *)theEncryptionPassword + targetConnectionDelegate:(id )theTCD + activityListener:(id )theActivityListener + error:(NSError **)error; + + (NSArray *)bucketUUIDsWithTarget:(Target *)theTarget computerUUID:(NSString *)theComputerUUID - encryptionPassword:(NSString *)theEncryptionPassword targetConnectionDelegate:(id )theTCD error:(NSError **)error; -+ (NSArray *)deletedBucketsWithTarget:(Target *)theTarget - computerUUID:(NSString *)theComputerUUID - encryptionPassword:(NSString *)theEncryptionPassword - targetConnectionDelegate:(id )theTCD - error:(NSError **)error; - + (NSString *)errorDomain; - (id)initWithTarget:(Target *)theTarget @@ -106,10 +115,12 @@ typedef NSInteger BucketPathState; - (NSString *)vaultName; - (NSDate *)vaultCreatedDate; - (NSDate *)plistDeletedDate; +- (BOOL)skipDuringBackup; +- (BOOL)excludeItemsWithTimeMachineExcludeMetadataFlag; - (BucketPathState)stateForPath:(NSString *)thePath ignoreExcludes:(BOOL)ignoreExcludes; -- (void)setIgnoredRelativePaths:(NSSet *)theSet; - (NSSet *)ignoredRelativePaths; -- (void)enteredPath:(NSString *)thePath; -- (void)leftPath:(NSString *)thePath; +- (BOOL)skipIfNotMounted; - (NSData *)toXMLData; +- (BOOL)writeTo:(BufferedOutputStream *)theBOS error:(NSError **)error; +- (void)writeTo:(NSMutableData *)data; @end diff --git a/Bucket.m b/Bucket.m index 5ff5893..1e0080e 100644 --- a/Bucket.m +++ b/Bucket.m @@ -1,5 +1,5 @@ /* - Copyright (c) 2009-2014, Stefan Reitshamer http://www.haystacksoftware.com + Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.com All rights reserved. @@ -31,29 +31,33 @@ */ + #import "Bucket.h" #import "DictNode.h" #import "ArrayNode.h" #import "StringNode.h" +#import "BooleanNode.h" #import "NSString_slashed.h" #import "BucketExcludeSet.h" #import "S3AuthorizationProvider.h" #import "S3Service.h" #import "UserLibrary_Arq.h" #import "S3Service.h" +#import "FSStat.h" +#import "Volume.h" #import "StorageType.h" #import "GlacierService.h" #import "AWSRegion.h" #import "RegexKitLite.h" #import "AWSRegion.h" -#import "CryptoKey.h" +#import "ObjectEncryptor.h" #import "Target.h" #import "TargetConnection.h" #import "GlacierAuthorizationProvider.h" #import "GlacierService.h" -#import "ArqSalt.h" #import "DataIO.h" #import "StringIO.h" +#import "NSString_extra.h" #define BUCKET_PLIST_SALT "BucketPL" @@ -98,9 +102,28 @@ encryptionPassword:(NSString *)theEncryptionPassword targetConnectionDelegate:(id )theTCD error:(NSError **)error { - id targetConnection = [theTarget newConnection]; + return [Bucket bucketsWithTarget:theTarget + computerUUID:theComputerUUID + encryptionPassword:(NSString *)theEncryptionPassword + targetConnectionDelegate:theTCD + activityListener:nil + error:error]; +} ++ (NSArray *)bucketsWithTarget:(Target *)theTarget + computerUUID:(NSString *)theComputerUUID + encryptionPassword:(NSString *)theEncryptionPassword + targetConnectionDelegate:(id )theTCD + activityListener:(id )theActivityListener + error:(NSError **)error { + HSLogDebug(@"bucketsWithTarget: theTarget=%@ endpoint=%@ path=%@", theTarget, [theTarget endpoint], [[theTarget endpoint] path]); + + TargetConnection *targetConnection = [theTarget newConnection:error]; + if (targetConnection == nil) { + return nil; + } NSMutableArray *ret = [NSMutableArray array]; do { + [theActivityListener bucketActivity:@"Loading folder list..."]; NSArray *bucketUUIDs = [targetConnection bucketUUIDsForComputerUUID:theComputerUUID deleted:NO delegate:theTCD error:error]; if (bucketUUIDs == nil) { if (error != NULL) { @@ -109,9 +132,18 @@ ret = nil; break; } - for (NSString *bucketUUID in bucketUUIDs) { + for (NSUInteger i = 0; i < [bucketUUIDs count]; i++) { + [theActivityListener bucketActivity:[NSString stringWithFormat:@"Loading folder %ld of %ld", i+1, [bucketUUIDs count]]]; + + NSString *bucketUUID = [bucketUUIDs objectAtIndex:i]; NSError *myError = nil; - Bucket *bucket = [Bucket bucketWithTarget:theTarget targetConnection:targetConnection computerUUID:theComputerUUID bucketUUID:bucketUUID encryptionPassword:theEncryptionPassword error:&myError]; + Bucket *bucket = [Bucket bucketWithTarget:theTarget + targetConnection:targetConnection + computerUUID:theComputerUUID + encryptionPassword:theEncryptionPassword + bucketUUID:bucketUUID + targetConnectionDelegate:theTCD + error:&myError]; if (bucket == nil) { HSLogError(@"failed to load bucket plist for %@/%@: %@", theComputerUUID, bucketUUID, myError); if ([myError code] != ERROR_INVALID_PLIST_XML) { @@ -127,61 +159,23 @@ [ret sortUsingDescriptors:[NSArray arrayWithObject:descriptor]]; } while(0); [targetConnection release]; + HSLogDebug(@"returning %ld buckets for computer %@", [ret count], theComputerUUID); return ret; } + (NSArray *)bucketUUIDsWithTarget:(Target *)theTarget computerUUID:(NSString *)theComputerUUID - encryptionPassword:(NSString *)theEncryptionPassword targetConnectionDelegate:(id )theTCD error:(NSError **)error { - id targetConnection = [[theTarget newConnection] autorelease]; - return [targetConnection bucketUUIDsForComputerUUID:theComputerUUID deleted:NO delegate:theTCD error:error]; -} -+ (NSArray *)deletedBucketsWithTarget:(Target *)theTarget - computerUUID:(NSString *)theComputerUUID - encryptionPassword:(NSString *)theEncryptionPassword - targetConnectionDelegate:(id )theTCD - error:(NSError **)error { - HSLogDebug(@"deletedBucketsWithTarget: theTarget=%@ endpoint=%@ path=%@", theTarget, [theTarget endpoint], [[theTarget endpoint] path]); - - NSData *salt = [NSData dataWithBytes:BUCKET_PLIST_SALT length:8]; - CryptoKey *cryptoKey = [[[CryptoKey alloc] initWithPassword:theEncryptionPassword salt:salt error:error] autorelease]; - if (cryptoKey == nil) { + TargetConnection *targetConnection = [theTarget newConnection:error]; + if (targetConnection == nil) { return nil; } - - NSMutableArray *ret = [NSMutableArray array]; - id targetConnection = [[theTarget newConnection] autorelease]; - NSArray *deletedBucketUUIDs = [targetConnection bucketUUIDsForComputerUUID:theComputerUUID deleted:YES delegate:theTCD error:error]; - if (deletedBucketUUIDs == nil) { - return nil; - } - for (NSString *bucketUUID in deletedBucketUUIDs) { - NSData *data = [targetConnection bucketPlistDataForComputerUUID:theComputerUUID bucketUUID:bucketUUID deleted:YES delegate:theTCD error:error]; - if (data == nil) { - return nil; - } - if (!strncmp([data bytes], "encrypted", 9)) { - NSData *encryptedData = [data subdataWithRange:NSMakeRange(9, [data length] - 9)]; - data = [cryptoKey decrypt:encryptedData error:error]; - if (data == nil) { - return nil; - } - } - NSError *myError = nil; - DictNode *plist = [DictNode dictNodeWithXMLData:data error:&myError]; - if (plist == nil) { - SETNSERROR(@"BucketErrorDomain", -1, @"error parsing bucket plist %@ %@: %@", theComputerUUID, bucketUUID, [myError localizedDescription]); - return nil; - } - Bucket *bucket = [[[Bucket alloc] initWithTarget:theTarget plist:plist] autorelease]; - [ret addObject:bucket]; - } - NSSortDescriptor *descriptor = [[[NSSortDescriptor alloc] initWithKey:@"bucketName" ascending:YES selector:@selector(caseInsensitiveCompare:)] autorelease]; - [ret sortUsingDescriptors:[NSArray arrayWithObject:descriptor]]; + NSArray *ret = [targetConnection bucketUUIDsForComputerUUID:theComputerUUID deleted:NO delegate:theTCD error:error]; + [targetConnection release]; return ret; } + + (NSString *)errorDomain { return @"BucketErrorDomain"; } @@ -203,7 +197,7 @@ storageType = theStorageType; ignoredRelativePaths = [[NSMutableArray alloc] init]; excludeSet = [[BucketExcludeSet alloc] init]; - stringArrayPairs = [[NSMutableArray alloc] init]; + excludeItemsWithTimeMachineExcludeMetadataFlag = NO; // Default to false because things might get unexpectedly skipped, e.g. VMWare VMs } return self; } @@ -239,7 +233,6 @@ [localMountPoint release]; [ignoredRelativePaths release]; [excludeSet release]; - [stringArrayPairs release]; [vaultName release]; [vaultCreatedDate release]; [plistDeletedDate release]; @@ -279,24 +272,34 @@ - (NSDate *)plistDeletedDate { return plistDeletedDate; } +- (BOOL)skipDuringBackup { + return skipDuringBackup; +} +- (BOOL)excludeItemsWithTimeMachineExcludeMetadataFlag { + return excludeItemsWithTimeMachineExcludeMetadataFlag; +} - (BucketPathState)stateForPath:(NSString *)thePath ignoreExcludes:(BOOL)ignoreExcludes { if ([ignoredRelativePaths containsObject:@""]) { return BucketPathOffState; } NSInteger ret = BucketPathOnState; - NSString *relativePath = [thePath substringFromIndex:[localPath length]]; - for (NSString *ignoredRelativePath in ignoredRelativePaths) { - if ([relativePath isEqualToString:ignoredRelativePath] - || ([relativePath hasPrefix:ignoredRelativePath] && ([relativePath length] > [ignoredRelativePath length]) && ([relativePath characterAtIndex:[ignoredRelativePath length]] == '/')) - ) { - ret = BucketPathOffState; - break; - } else if (([ignoredRelativePath hasPrefix:relativePath] || [relativePath length] == 0) - && ([ignoredRelativePath length] > [relativePath length]) - && ([relativePath isEqualToString:@""] || [relativePath isEqualToString:@"/"] || [ignoredRelativePath characterAtIndex:[relativePath length]] == '/')) { - ret = BucketPathMixedState; - break; + if ([thePath length] <= [localPath length]) { + HSLogDebug(@"path %@ isn't longer than localPath %@", thePath, localPath); + } else { + NSString *relativePath = [thePath substringFromIndex:[localPath length]]; + for (NSString *ignoredRelativePath in ignoredRelativePaths) { + if ([relativePath isEqualToString:ignoredRelativePath] + || ([relativePath hasPrefix:ignoredRelativePath] && ([relativePath length] > [ignoredRelativePath length]) && ([relativePath characterAtIndex:[ignoredRelativePath length]] == '/')) + ) { + ret = BucketPathOffState; + break; + } else if (([ignoredRelativePath hasPrefix:relativePath] || [relativePath length] == 0) + && ([ignoredRelativePath length] > [relativePath length]) + && ([relativePath isEqualToString:@""] || [relativePath isEqualToString:@"/"] || [ignoredRelativePath characterAtIndex:[relativePath length]] == '/')) { + ret = BucketPathMixedState; + break; + } } } if (!ignoreExcludes && [excludeSet matchesFullPath:thePath filename:[thePath lastPathComponent]]) { @@ -304,38 +307,11 @@ } return ret; } -- (void)setIgnoredRelativePaths:(NSSet *)theSet { - [ignoredRelativePaths setArray:[theSet allObjects]]; -} - (NSSet *)ignoredRelativePaths { return [NSSet setWithArray:ignoredRelativePaths]; } -- (void)enteredPath:(NSString *)thePath { - NSMutableArray *relativePathsToSkip = [NSMutableArray array]; - if (![thePath isEqualToString:localPath]) { - NSString *relativePath = [thePath substringFromIndex:[localPath length]]; - for (NSString *ignored in ignoredRelativePaths) { - BOOL applicable = NO; - if ([ignored hasPrefix:relativePath]) { - if ([ignored isEqualToString:relativePath] || ([ignored characterAtIndex:[relativePath length]] == '/')) { - applicable = YES; - } - } - if (!applicable) { - [relativePathsToSkip addObject:ignored]; - } - } - } - StringArrayPair *sap = [[StringArrayPair alloc] initWithLeft:thePath right:relativePathsToSkip]; - [stringArrayPairs addObject:sap]; - [sap release]; - [ignoredRelativePaths removeObjectsInArray:relativePathsToSkip]; -} -- (void)leftPath:(NSString *)thePath { - StringArrayPair *sap = [stringArrayPairs lastObject]; - NSAssert([[sap left] isEqualToString:thePath], @"must leave last path on the stack!"); - [ignoredRelativePaths addObjectsFromArray:[sap right]]; - [stringArrayPairs removeLastObject]; +- (BOOL)skipIfNotMounted { + return skipIfNotMounted; } - (NSData *)toXMLData { DictNode *plist = [[[DictNode alloc] init] autorelease]; @@ -346,13 +322,17 @@ [plist putString:localPath forKey:@"LocalPath"]; [plist putString:localMountPoint forKey:@"LocalMountPoint"]; [plist putInt:storageType forKey:@"StorageType"]; - [plist putString:vaultName forKey:@"VaultName"]; + if (vaultName != nil) { + [plist putString:vaultName forKey:@"VaultName"]; + } if (vaultCreatedDate != nil) { [plist putDouble:[vaultCreatedDate timeIntervalSinceReferenceDate] forKey:@"VaultCreatedTime"]; } if (plistDeletedDate != nil) { [plist putDouble:[plistDeletedDate timeIntervalSinceReferenceDate] forKey:@"PlistDeletedTime"]; } + [plist putBoolean:skipDuringBackup forKey:@"SkipDuringBackup"]; + [plist putBoolean:excludeItemsWithTimeMachineExcludeMetadataFlag forKey:@"ExcludeItemsWithTimeMachineExcludeMetadataFlag"]; ArrayNode *ignoredRelativePathsNode = [[[ArrayNode alloc] init] autorelease]; [plist put:ignoredRelativePathsNode forKey:@"IgnoredRelativePaths"]; NSArray *sortedRelativePaths = [ignoredRelativePaths sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; @@ -360,14 +340,21 @@ [ignoredRelativePathsNode add:[[[StringNode alloc] initWithString:ignoredRelativePath] autorelease]]; } [plist put:[excludeSet toPlist] forKey:@"Excludes"]; + [plist putBoolean:skipIfNotMounted forKey:@"SkipIfNotMounted"]; return [plist XMLData]; } - +- (BOOL)writeTo:(BufferedOutputStream *)theBOS error:(NSError **)error { + NSData *data = [self toXMLData]; + return [target writeTo:theBOS error:error] && [DataIO write:data to:theBOS error:error]; +} +- (void)writeTo:(NSMutableData *)data { + [target writeTo:data]; + [DataIO write:[self toXMLData] to:data]; +} #pragma mark NSCopying - (id)copyWithZone:(NSZone *)zone { BucketExcludeSet *excludeSetCopy = [excludeSet copyWithZone:zone]; - NSMutableArray *stringArrayPairsCopy = [stringArrayPairs copyWithZone:zone]; NSMutableArray *ignoredRelativePathsCopy = [[NSMutableArray alloc] initWithArray:ignoredRelativePaths copyItems:YES]; Bucket *ret = [[Bucket alloc] initWithTarget:target bucketUUID:bucketUUID @@ -378,37 +365,44 @@ storageType:storageType ignoredRelativePaths:ignoredRelativePathsCopy excludeSet:excludeSetCopy - stringArrayPairs:stringArrayPairsCopy vaultName:vaultName vaultCreatedDate:vaultCreatedDate - plistDeletedDate:plistDeletedDate]; + plistDeletedDate:plistDeletedDate + skipDuringBackup:skipDuringBackup + excludeItemsWithTimeMachineExcludeMetadataFlag:excludeItemsWithTimeMachineExcludeMetadataFlag + skipIfNotMounted:skipIfNotMounted]; [excludeSetCopy release]; - [stringArrayPairsCopy release]; [ignoredRelativePathsCopy release]; return ret; } #pragma mark NSObject - (NSString *)description { - return [NSString stringWithFormat:@"", bucketUUID, localPath, (unsigned long)[ignoredRelativePaths count]]; + NSUInteger ignoredCount = [ignoredRelativePaths count]; + return [NSString stringWithFormat:@"", bucketUUID, localPath, (unsigned long)ignoredCount, (ignoredCount == 1 ? @"" : @"s")]; } #pragma mark internal + (Bucket *)bucketWithTarget:(Target *)theTarget - targetConnection:(id )theTargetConnection + targetConnection:(TargetConnection *)theTargetConnection computerUUID:(NSString *)theComputerUUID - bucketUUID:(NSString *)theBucketUUID encryptionPassword:(NSString *)theEncryptionPassword + bucketUUID:(NSString *)theBucketUUID + targetConnectionDelegate:(id )theTCD error:(NSError **)error { - NSData *salt = [NSData dataWithBytes:BUCKET_PLIST_SALT length:8]; - CryptoKey *cryptoKey = [[[CryptoKey alloc] initWithPassword:theEncryptionPassword salt:salt error:error] autorelease]; - if (cryptoKey == nil) { + ObjectEncryptor *encryptor = [[[ObjectEncryptor alloc] initWithTarget:theTarget + computerUUID:theComputerUUID + encryptionPassword:theEncryptionPassword + customV1Salt:[NSData dataWithBytes:BUCKET_PLIST_SALT length:strlen(BUCKET_PLIST_SALT)] + targetConnectionDelegate:nil + error:error] autorelease]; + if (encryptor == nil) { return nil; } - + BOOL encrypted = NO; - NSData *data = [theTargetConnection bucketPlistDataForComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:NO delegate:nil error:error]; + NSData *data = [theTargetConnection bucketPlistDataForComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:NO delegate:theTCD error:error]; if (data == nil) { return nil; } @@ -419,7 +413,7 @@ if (length >= 9 && !strncmp([data bytes], "encrypted", length)) { encrypted = YES; NSData *encryptedData = [data subdataWithRange:NSMakeRange(9, [data length] - 9)]; - data = [cryptoKey decrypt:encryptedData error:error]; + data = [encryptor decryptedDataForObject:encryptedData error:error]; if (data == nil) { return nil; } @@ -456,6 +450,12 @@ if ([thePlist containsKey:@"PlistDeletedTime"]) { plistDeletedDate = [[NSDate alloc] initWithTimeIntervalSinceReferenceDate:[[thePlist realNodeForKey:@"PlistDeletedTime"] doubleValue]]; } + if ([thePlist containsKey:@"SkipDuringBackup"]){ + skipDuringBackup = [[thePlist booleanNodeForKey:@"SkipDuringBackup"] booleanValue]; + } + if ([thePlist containsKey:@"ExcludeItemsWithTimeMachineExcludeMetadataFlag"]) { + excludeItemsWithTimeMachineExcludeMetadataFlag = [[thePlist booleanNodeForKey:@"ExcludeItemsWithTimeMachineExcludeMetadataFlag"] booleanValue]; + } ignoredRelativePaths = [[NSMutableArray alloc] init]; ArrayNode *ignoredPathsNode = [thePlist arrayNodeForKey:@"IgnoredRelativePaths"]; for (NSUInteger index = 0; index < [ignoredPathsNode size]; index++) { @@ -464,7 +464,10 @@ [self sortIgnoredRelativePaths]; excludeSet = [[BucketExcludeSet alloc] init]; [excludeSet loadFromPlist:[thePlist dictNodeForKey:@"Excludes"] localPath:localPath]; - stringArrayPairs = [[NSMutableArray alloc] init]; + + if ([thePlist containsKey:@"SkipIfNotMounted"]) { + skipIfNotMounted = [[thePlist booleanNodeForKey:@"SkipIfNotMounted"] booleanValue]; + } } return self; } @@ -475,6 +478,9 @@ [ignoredRelativePaths setArray:[set allObjects]]; [ignoredRelativePaths sortUsingSelector:@selector(compareByLength:)]; } + + + - (id)initWithTarget:(Target *)theTarget bucketUUID:(NSString *)theBucketUUID bucketName:(NSString *)theBucketName @@ -484,10 +490,12 @@ storageType:(int)theStorageType ignoredRelativePaths:(NSMutableArray *)theIgnoredRelativePaths excludeSet:(BucketExcludeSet *)theExcludeSet - stringArrayPairs:(NSMutableArray *)theStringArrayPairs vaultName:(NSString *)theVaultName vaultCreatedDate:(NSDate *)theVaultCreatedDate - plistDeletedDate:(NSDate *)thePlistDeletedDate { + plistDeletedDate:(NSDate *)thePlistDeletedDate + skipDuringBackup:(BOOL)theSkipDuringBackup +excludeItemsWithTimeMachineExcludeMetadataFlag:(BOOL)theExcludeItemsWithTimeMachineExcludeMetadataFlag + skipIfNotMounted:(BOOL)theSkipIfNotMounted { if (self = [super init]) { target = [theTarget retain]; bucketUUID = [theBucketUUID retain]; @@ -498,10 +506,12 @@ ignoredRelativePaths:(NSMutableArray *)theIgnoredRelativePaths storageType = theStorageType; ignoredRelativePaths = [theIgnoredRelativePaths retain]; excludeSet = [theExcludeSet retain]; - stringArrayPairs = [theStringArrayPairs retain]; vaultName = [theVaultName retain]; vaultCreatedDate = [theVaultCreatedDate retain]; plistDeletedDate = [thePlistDeletedDate retain]; + skipDuringBackup = theSkipDuringBackup; + excludeItemsWithTimeMachineExcludeMetadataFlag = theExcludeItemsWithTimeMachineExcludeMetadataFlag; + skipIfNotMounted = theSkipIfNotMounted; } return self; } diff --git a/BucketExclude.h b/BucketExclude.h index 681e7d5..7247c4b 100644 --- a/BucketExclude.h +++ b/BucketExclude.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2009-2014, Stefan Reitshamer http://www.haystacksoftware.com + Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.com All rights reserved. @@ -32,6 +32,8 @@ + + @class DictNode; enum { diff --git a/BucketExclude.m b/BucketExclude.m index c5fd283..703973f 100644 --- a/BucketExclude.m +++ b/BucketExclude.m @@ -1,5 +1,5 @@ /* - Copyright (c) 2009-2014, Stefan Reitshamer http://www.haystacksoftware.com + Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.com All rights reserved. @@ -31,6 +31,8 @@ */ + + #import "BucketExclude.h" #import "DictNode.h" #import "IntegerNode.h" diff --git a/BucketExcludeSet.h b/BucketExcludeSet.h index 3680c4f..d39f803 100644 --- a/BucketExcludeSet.h +++ b/BucketExcludeSet.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2009-2014, Stefan Reitshamer http://www.haystacksoftware.com + Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.com All rights reserved. @@ -32,6 +32,8 @@ + + #import "BucketExclude.h" @class DictNode; diff --git a/BucketExcludeSet.m b/BucketExcludeSet.m index c457460..8b2e1cd 100644 --- a/BucketExcludeSet.m +++ b/BucketExcludeSet.m @@ -1,5 +1,5 @@ /* - Copyright (c) 2009-2014, Stefan Reitshamer http://www.haystacksoftware.com + Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.com All rights reserved. @@ -31,6 +31,8 @@ */ + + #import "BucketExcludeSet.h" #import "BucketExclude.h" #import "DictNode.h" diff --git a/cocoastack/s3/S3DeleteReceiver.h b/ByteSize.h similarity index 86% rename from cocoastack/s3/S3DeleteReceiver.h rename to ByteSize.h index d46df30..7a969cf 100644 --- a/cocoastack/s3/S3DeleteReceiver.h +++ b/ByteSize.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2009-2014, Stefan Reitshamer http://www.haystacksoftware.com + Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.com All rights reserved. @@ -32,12 +32,11 @@ -#import "S3Receiver.h" -@class S3Service; -@interface S3DeleteReceiver : NSObject { - S3Service *s3; + + +@interface ByteSize : NSObject { + } -- (id)initWithS3Service:(S3Service *)theS3; - ++ (NSString *)descriptionForSize:(unsigned long long)value; @end diff --git a/ByteSize.m b/ByteSize.m new file mode 100644 index 0000000..37e3368 --- /dev/null +++ b/ByteSize.m @@ -0,0 +1,53 @@ +/* + Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + + +#import "ByteSize.h" + + +@implementation ByteSize ++ (NSString *)descriptionForSize:(unsigned long long)value { + if (value > 1000000000L) { + double d = ((double)value) / 1000000000.0; + return [NSString stringWithFormat:@"%0.3f GB", d]; + } else if (value > 1000000L) { + return [NSString stringWithFormat:@"%1.1f MB", ((double)value / 1000000.0)]; + } else if (value > 1000L) { + return [NSString stringWithFormat:@"%1.1f KB", ((double)value) / 1000.0]; + } else if (value > 0) { + return [NSString stringWithFormat:@"%qu byte%@", value, (value == 0 ? @"" : @"s")]; + } + return @"0"; +} + +@end diff --git a/cocoastack/googledrive/GoogleDriveErrorResult.h b/CacheOwnership.h similarity index 83% rename from cocoastack/googledrive/GoogleDriveErrorResult.h rename to CacheOwnership.h index bc2fcf9..bd47253 100644 --- a/cocoastack/googledrive/GoogleDriveErrorResult.h +++ b/CacheOwnership.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2009-2014, Stefan Reitshamer http://www.haystacksoftware.com + Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.com All rights reserved. @@ -32,10 +32,17 @@ -@interface GoogleDriveErrorResult : NSObject { - NSError *myError; -} -- (id)initWithAction:(NSString *)theAction data:(NSData *)theData contentType:(NSString *)theContentType httpErrorCode:(int)theHTTPStatusCode; +#import "CWLSynthesizeSingleton.h" + + +@interface CacheOwnership : NSObject { + uid_t uid; + gid_t gid; +} +CWL_DECLARE_SINGLETON_FOR_CLASS(CacheOwnership); + +- (void)setUID:(uid_t)theUID gid:(gid_t)theGID; +- (uid_t)uid; +- (gid_t)gid; -- (NSError *)error; @end diff --git a/CacheOwnership.m b/CacheOwnership.m new file mode 100644 index 0000000..f6d7d96 --- /dev/null +++ b/CacheOwnership.m @@ -0,0 +1,59 @@ +/* + Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + + +#import "CacheOwnership.h" + +@implementation CacheOwnership +CWL_SYNTHESIZE_SINGLETON_FOR_CLASS(CacheOwnership) + +- (id)init { + if (self = [super init]) { + uid = getuid(); + gid = getgid(); + } + return self; +} +- (void)setUID:(uid_t)theUID gid:(gid_t)theGID { + uid = theUID; + gid = theGID; +} +- (uid_t)uid { + return uid; +} +- (gid_t)gid { + return gid; +} + + +@end diff --git a/CocoaLumberjack/CocoaLumberjack.h b/CocoaLumberjack/CocoaLumberjack.h new file mode 100644 index 0000000..0b568fb --- /dev/null +++ b/CocoaLumberjack/CocoaLumberjack.h @@ -0,0 +1,81 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2015, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +/** + * Welcome to CocoaLumberjack! + * + * The project page has a wealth of documentation if you have any questions. + * https://github.com/CocoaLumberjack/CocoaLumberjack + * + * If you're new to the project you may wish to read "Getting Started" at: + * Documentation/GettingStarted.md + * + * Otherwise, here is a quick refresher. + * There are three steps to using the macros: + * + * Step 1: + * Import the header in your implementation or prefix file: + * + * #import + * + * Step 2: + * Define your logging level in your implementation file: + * + * // Log levels: off, error, warn, info, verbose + * static const DDLogLevel ddLogLevel = DDLogLevelVerbose; + * + * Step 2 [3rd party frameworks]: + * + * Define your LOG_LEVEL_DEF to a different variable/function than ddLogLevel: + * + * // #undef LOG_LEVEL_DEF // Undefine first only if needed + * #define LOG_LEVEL_DEF myLibLogLevel + * + * Define your logging level in your implementation file: + * + * // Log levels: off, error, warn, info, verbose + * static const DDLogLevel myLibLogLevel = DDLogLevelVerbose; + * + * Step 3: + * Replace your NSLog statements with DDLog statements according to the severity of the message. + * + * NSLog(@"Fatal error, no dohickey found!"); -> DDLogError(@"Fatal error, no dohickey found!"); + * + * DDLog works exactly the same as NSLog. + * This means you can pass it multiple variables just like NSLog. + **/ + +#import + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +// Core +#import "DDLog.h" + +// Main macros +#import "DDLogMacros.h" +#import "DDAssertMacros.h" + +// Capture ASL +#import "DDASLLogCapture.h" + +// Loggers +#import "DDTTYLogger.h" +#import "DDASLLogger.h" +#import "DDFileLogger.h" + diff --git a/CocoaLumberjack/DDASLLogCapture.h b/CocoaLumberjack/DDASLLogCapture.h new file mode 100644 index 0000000..c87d0b1 --- /dev/null +++ b/CocoaLumberjack/DDASLLogCapture.h @@ -0,0 +1,32 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2015, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import "DDASLLogger.h" + +@protocol DDLogger; + +/** + * This class provides the ability to capture the ASL (Apple System Logs) + */ +@interface DDASLLogCapture : NSObject + ++ (void)start; ++ (void)stop; + +// Default log level: DDLogLevelVerbose (i.e. capture all ASL messages). ++ (DDLogLevel)captureLevel; ++ (void)setCaptureLevel:(DDLogLevel)level; + +@end diff --git a/CocoaLumberjack/DDASLLogCapture.m b/CocoaLumberjack/DDASLLogCapture.m new file mode 100644 index 0000000..bb7f4c8 --- /dev/null +++ b/CocoaLumberjack/DDASLLogCapture.m @@ -0,0 +1,230 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2015, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import "DDASLLogCapture.h" + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +#import "DDLog.h" + +#include +#include +#include +#include + +static BOOL _cancel = YES; +static DDLogLevel _captureLevel = DDLogLevelVerbose; + +#ifdef __IPHONE_8_0 + #define DDASL_IOS_PIVOT_VERSION __IPHONE_8_0 +#endif +#ifdef __MAC_10_10 + #define DDASL_OSX_PIVOT_VERSION __MAC_10_10 +#endif + +@implementation DDASLLogCapture + +static aslmsg (*dd_asl_next)(aslresponse obj); +static void (*dd_asl_release)(aslresponse obj); + ++ (void)initialize +{ + #if (defined(DDASL_IOS_PIVOT_VERSION) && __IPHONE_OS_VERSION_MAX_ALLOWED >= DDASL_IOS_PIVOT_VERSION) || (defined(DDASL_OSX_PIVOT_VERSION) && __MAC_OS_X_VERSION_MAX_ALLOWED >= DDASL_OSX_PIVOT_VERSION) + #if __IPHONE_OS_VERSION_MIN_REQUIRED < DDASL_IOS_PIVOT_VERSION || __MAC_OS_X_VERSION_MIN_REQUIRED < DDASL_OSX_PIVOT_VERSION + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" + // Building on falsely advertised SDK, targeting deprecated API + dd_asl_next = &aslresponse_next; + dd_asl_release = &aslresponse_free; + #pragma GCC diagnostic pop + #else + // Building on lastest, correct SDK, targeting latest API + dd_asl_next = &asl_next; + dd_asl_release = &asl_release; + #endif + #else + // Building on old SDKs, targeting deprecated API + dd_asl_next = &aslresponse_next; + dd_asl_release = &aslresponse_free; + #endif +} + ++ (void)start { + // Ignore subsequent calls + if (!_cancel) { + return; + } + + _cancel = NO; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { + [self captureAslLogs]; + }); +} + ++ (void)stop { + _cancel = YES; +} + ++ (DDLogLevel)captureLevel { + return _captureLevel; +} + ++ (void)setCaptureLevel:(DDLogLevel)level { + _captureLevel = level; +} + +#pragma mark - Private methods + ++ (void)configureAslQuery:(aslmsg)query { + const char param[] = "7"; // ASL_LEVEL_DEBUG, which is everything. We'll rely on regular DDlog log level to filter + + asl_set_query(query, ASL_KEY_LEVEL, param, ASL_QUERY_OP_LESS_EQUAL | ASL_QUERY_OP_NUMERIC); + + // Don't retrieve logs from our own DDASLLogger + asl_set_query(query, kDDASLKeyDDLog, kDDASLDDLogValue, ASL_QUERY_OP_NOT_EQUAL); + +#if !TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + int processId = [[NSProcessInfo processInfo] processIdentifier]; + char pid[16]; + sprintf(pid, "%d", processId); + asl_set_query(query, ASL_KEY_PID, pid, ASL_QUERY_OP_EQUAL | ASL_QUERY_OP_NUMERIC); +#endif +} + ++ (void)aslMessageReceived:(aslmsg)msg { + const char* messageCString = asl_get( msg, ASL_KEY_MSG ); + if ( messageCString == NULL ) + return; + + int flag; + BOOL async; + + const char* levelCString = asl_get(msg, ASL_KEY_LEVEL); + switch (levelCString? atoi(levelCString) : 0) { + // By default all NSLog's with a ASL_LEVEL_WARNING level + case ASL_LEVEL_EMERG : + case ASL_LEVEL_ALERT : + case ASL_LEVEL_CRIT : flag = DDLogFlagError; async = NO; break; + case ASL_LEVEL_ERR : flag = DDLogFlagWarning; async = YES; break; + case ASL_LEVEL_WARNING : flag = DDLogFlagInfo; async = YES; break; + case ASL_LEVEL_NOTICE : flag = DDLogFlagDebug; async = YES; break; + case ASL_LEVEL_INFO : + case ASL_LEVEL_DEBUG : + default : flag = DDLogFlagVerbose; async = YES; break; + } + + if (!(_captureLevel & flag)) { + return; + } + + // NSString * sender = [NSString stringWithCString:asl_get(msg, ASL_KEY_SENDER) encoding:NSUTF8StringEncoding]; + NSString *message = @(messageCString); + + const char* secondsCString = asl_get( msg, ASL_KEY_TIME ); + const char* nanoCString = asl_get( msg, ASL_KEY_TIME_NSEC ); + NSTimeInterval seconds = secondsCString ? strtod(secondsCString, NULL) : [NSDate timeIntervalSinceReferenceDate] - NSTimeIntervalSince1970; + double nanoSeconds = nanoCString? strtod(nanoCString, NULL) : 0; + NSTimeInterval totalSeconds = seconds + (nanoSeconds / 1e9); + + NSDate *timeStamp = [NSDate dateWithTimeIntervalSince1970:totalSeconds]; + + DDLogMessage *logMessage = [[DDLogMessage alloc]initWithMessage:message + level:_captureLevel + flag:flag + context:0 + file:@"DDASLLogCapture" + function:0 + line:0 + tag:nil + options:0 + timestamp:timeStamp]; + + [DDLog log:async message:logMessage]; +} + ++ (void)captureAslLogs { + @autoreleasepool + { + /* + We use ASL_KEY_MSG_ID to see each message once, but there's no + obvious way to get the "next" ID. To bootstrap the process, we'll + search by timestamp until we've seen a message. + */ + + struct timeval timeval = { + .tv_sec = 0 + }; + gettimeofday(&timeval, NULL); + unsigned long long startTime = timeval.tv_sec; + __block unsigned long long lastSeenID = 0; + + /* + syslogd posts kNotifyASLDBUpdate (com.apple.system.logger.message) + through the notify API when it saves messages to the ASL database. + There is some coalescing - currently it is sent at most twice per + second - but there is no documented guarantee about this. In any + case, there may be multiple messages per notification. + + Notify notifications don't carry any payload, so we need to search + for the messages. + */ + int notifyToken = 0; // Can be used to unregister with notify_cancel(). + notify_register_dispatch(kNotifyASLDBUpdate, ¬ifyToken, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(int token) + { + // At least one message has been posted; build a search query. + @autoreleasepool + { + aslmsg query = asl_new(ASL_TYPE_QUERY); + char stringValue[64]; + + if (lastSeenID > 0) { + snprintf(stringValue, sizeof stringValue, "%llu", lastSeenID); + asl_set_query(query, ASL_KEY_MSG_ID, stringValue, ASL_QUERY_OP_GREATER | ASL_QUERY_OP_NUMERIC); + } else { + snprintf(stringValue, sizeof stringValue, "%llu", startTime); + asl_set_query(query, ASL_KEY_TIME, stringValue, ASL_QUERY_OP_GREATER_EQUAL | ASL_QUERY_OP_NUMERIC); + } + + [self configureAslQuery:query]; + + // Iterate over new messages. + aslmsg msg; + aslresponse response = asl_search(NULL, query); + + while ((msg = dd_asl_next(response))) + { + [self aslMessageReceived:msg]; + + // Keep track of which messages we've seen. + lastSeenID = atoll(asl_get(msg, ASL_KEY_MSG_ID)); + } + dd_asl_release(response); + asl_free(query); + + if (_cancel) { + notify_cancel(token); + return; + } + + } + }); + } +} + +@end diff --git a/CocoaLumberjack/DDASLLogger.h b/CocoaLumberjack/DDASLLogger.h new file mode 100644 index 0000000..57c5f58 --- /dev/null +++ b/CocoaLumberjack/DDASLLogger.h @@ -0,0 +1,54 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2015, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +#import "DDLog.h" + +// Custom key set on messages sent to ASL +extern const char* const kDDASLKeyDDLog; + +// Value set for kDDASLKeyDDLog +extern const char* const kDDASLDDLogValue; + +/** + * This class provides a logger for the Apple System Log facility. + * + * As described in the "Getting Started" page, + * the traditional NSLog() function directs it's output to two places: + * + * - Apple System Log + * - StdErr (if stderr is a TTY) so log statements show up in Xcode console + * + * To duplicate NSLog() functionality you can simply add this logger and a tty logger. + * However, if you instead choose to use file logging (for faster performance), + * you may choose to use a file logger and a tty logger. + **/ + +@interface DDASLLogger : DDAbstractLogger + ++ (instancetype)sharedInstance; + +// Inherited from DDAbstractLogger + +// - (id )logFormatter; +// - (void)setLogFormatter:(id )formatter; + +@end diff --git a/CocoaLumberjack/DDASLLogger.m b/CocoaLumberjack/DDASLLogger.m new file mode 100644 index 0000000..90061c8 --- /dev/null +++ b/CocoaLumberjack/DDASLLogger.m @@ -0,0 +1,121 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2015, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import "DDASLLogger.h" +#import + +#if !__has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +const char* const kDDASLKeyDDLog = "DDLog"; + +const char* const kDDASLDDLogValue = "1"; + +static DDASLLogger *sharedInstance; + +@interface DDASLLogger () { + aslclient _client; +} + +@end + + +@implementation DDASLLogger + ++ (instancetype)sharedInstance { + static dispatch_once_t DDASLLoggerOnceToken; + + dispatch_once(&DDASLLoggerOnceToken, ^{ + sharedInstance = [[[self class] alloc] init]; + }); + + return sharedInstance; +} + +- (instancetype)init { + if (sharedInstance != nil) { + return nil; + } + + if ((self = [super init])) { + // A default asl client is provided for the main thread, + // but background threads need to create their own client. + + _client = asl_open(NULL, "com.apple.console", 0); + } + + return self; +} + +- (void)logMessage:(DDLogMessage *)logMessage { + // Skip captured log messages + if ([logMessage->_fileName isEqualToString:@"DDASLLogCapture"]) { + return; + } + + NSString * message = _logFormatter ? [_logFormatter formatLogMessage:logMessage] : logMessage->_message; + + if (logMessage) { + const char *msg = [message UTF8String]; + + size_t aslLogLevel; + switch (logMessage->_flag) { + // Note: By default ASL will filter anything above level 5 (Notice). + // So our mappings shouldn't go above that level. + case DDLogFlagError : aslLogLevel = ASL_LEVEL_CRIT; break; + case DDLogFlagWarning : aslLogLevel = ASL_LEVEL_ERR; break; + case DDLogFlagInfo : aslLogLevel = ASL_LEVEL_WARNING; break; // Regular NSLog's level + case DDLogFlagDebug : + case DDLogFlagVerbose : + default : aslLogLevel = ASL_LEVEL_NOTICE; break; + } + + static char const *const level_strings[] = { "0", "1", "2", "3", "4", "5", "6", "7" }; + + // NSLog uses the current euid to set the ASL_KEY_READ_UID. + uid_t const readUID = geteuid(); + + char readUIDString[16]; +#ifndef NS_BLOCK_ASSERTIONS + int l = snprintf(readUIDString, sizeof(readUIDString), "%d", readUID); +#else + snprintf(readUIDString, sizeof(readUIDString), "%d", readUID); +#endif + + NSAssert(l < sizeof(readUIDString), + @"Formatted euid is too long."); + NSAssert(aslLogLevel < (sizeof(level_strings) / sizeof(level_strings[0])), + @"Unhandled ASL log level."); + + aslmsg m = asl_new(ASL_TYPE_MSG); + if (m != NULL) { + if (asl_set(m, ASL_KEY_LEVEL, level_strings[aslLogLevel]) == 0 && + asl_set(m, ASL_KEY_MSG, msg) == 0 && + asl_set(m, ASL_KEY_READ_UID, readUIDString) == 0 && + asl_set(m, kDDASLKeyDDLog, kDDASLDDLogValue) == 0) { + asl_send(_client, m); + } + asl_free(m); + } + //TODO handle asl_* failures non-silently? + } +} + +- (NSString *)loggerName { + return @"cocoa.lumberjack.aslLogger"; +} + +@end diff --git a/CocoaLumberjack/DDAbstractDatabaseLogger.h b/CocoaLumberjack/DDAbstractDatabaseLogger.h new file mode 100644 index 0000000..745a91b --- /dev/null +++ b/CocoaLumberjack/DDAbstractDatabaseLogger.h @@ -0,0 +1,112 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2015, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +#import "DDLog.h" + +/** + * This class provides an abstract implementation of a database logger. + * + * That is, it provides the base implementation for a database logger to build atop of. + * All that is needed for a concrete database logger is to extend this class + * and override the methods in the implementation file that are prefixed with "db_". + **/ + +@interface DDAbstractDatabaseLogger : DDAbstractLogger { + +@protected + NSUInteger _saveThreshold; + NSTimeInterval _saveInterval; + NSTimeInterval _maxAge; + NSTimeInterval _deleteInterval; + BOOL _deleteOnEverySave; + + BOOL _saveTimerSuspended; + NSUInteger _unsavedCount; + dispatch_time_t _unsavedTime; + dispatch_source_t _saveTimer; + dispatch_time_t _lastDeleteTime; + dispatch_source_t _deleteTimer; +} + +/** + * Specifies how often to save the data to disk. + * Since saving is an expensive operation (disk io) it is not done after every log statement. + * These properties allow you to configure how/when the logger saves to disk. + * + * A save is done when either (whichever happens first): + * + * - The number of unsaved log entries reaches saveThreshold + * - The amount of time since the oldest unsaved log entry was created reaches saveInterval + * + * You can optionally disable the saveThreshold by setting it to zero. + * If you disable the saveThreshold you are entirely dependent on the saveInterval. + * + * You can optionally disable the saveInterval by setting it to zero (or a negative value). + * If you disable the saveInterval you are entirely dependent on the saveThreshold. + * + * It's not wise to disable both saveThreshold and saveInterval. + * + * The default saveThreshold is 500. + * The default saveInterval is 60 seconds. + **/ +@property (assign, readwrite) NSUInteger saveThreshold; +@property (assign, readwrite) NSTimeInterval saveInterval; + +/** + * It is likely you don't want the log entries to persist forever. + * Doing so would allow the database to grow infinitely large over time. + * + * The maxAge property provides a way to specify how old a log statement can get + * before it should get deleted from the database. + * + * The deleteInterval specifies how often to sweep for old log entries. + * Since deleting is an expensive operation (disk io) is is done on a fixed interval. + * + * An alternative to the deleteInterval is the deleteOnEverySave option. + * This specifies that old log entries should be deleted during every save operation. + * + * You can optionally disable the maxAge by setting it to zero (or a negative value). + * If you disable the maxAge then old log statements are not deleted. + * + * You can optionally disable the deleteInterval by setting it to zero (or a negative value). + * + * If you disable both deleteInterval and deleteOnEverySave then old log statements are not deleted. + * + * It's not wise to enable both deleteInterval and deleteOnEverySave. + * + * The default maxAge is 7 days. + * The default deleteInterval is 5 minutes. + * The default deleteOnEverySave is NO. + **/ +@property (assign, readwrite) NSTimeInterval maxAge; +@property (assign, readwrite) NSTimeInterval deleteInterval; +@property (assign, readwrite) BOOL deleteOnEverySave; + +/** + * Forces a save of any pending log entries (flushes log entries to disk). + **/ +- (void)savePendingLogEntries; + +/** + * Removes any log entries that are older than maxAge. + **/ +- (void)deleteOldLogEntries; + +@end diff --git a/CocoaLumberjack/DDAbstractDatabaseLogger.m b/CocoaLumberjack/DDAbstractDatabaseLogger.m new file mode 100644 index 0000000..c8782de --- /dev/null +++ b/CocoaLumberjack/DDAbstractDatabaseLogger.m @@ -0,0 +1,660 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2015, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import "DDAbstractDatabaseLogger.h" +#import + + +#if !__has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +@interface DDAbstractDatabaseLogger () + +- (void)destroySaveTimer; +- (void)destroyDeleteTimer; + +@end + +#pragma mark - + +@implementation DDAbstractDatabaseLogger + +- (instancetype)init { + if ((self = [super init])) { + _saveThreshold = 500; + _saveInterval = 60; // 60 seconds + _maxAge = (60 * 60 * 24 * 7); // 7 days + _deleteInterval = (60 * 5); // 5 minutes + } + + return self; +} + +- (void)dealloc { + [self destroySaveTimer]; + [self destroyDeleteTimer]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Override Me +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)db_log:(DDLogMessage *)logMessage { + // Override me and add your implementation. + // + // Return YES if an item was added to the buffer. + // Return NO if the logMessage was ignored. + + return NO; +} + +- (void)db_save { + // Override me and add your implementation. +} + +- (void)db_delete { + // Override me and add your implementation. +} + +- (void)db_saveAndDelete { + // Override me and add your implementation. +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Private API +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)performSaveAndSuspendSaveTimer { + if (_unsavedCount > 0) { + if (_deleteOnEverySave) { + [self db_saveAndDelete]; + } else { + [self db_save]; + } + } + + _unsavedCount = 0; + _unsavedTime = 0; + + if (_saveTimer && !_saveTimerSuspended) { + dispatch_suspend(_saveTimer); + _saveTimerSuspended = YES; + } +} + +- (void)performDelete { + if (_maxAge > 0.0) { + [self db_delete]; + + _lastDeleteTime = dispatch_time(DISPATCH_TIME_NOW, 0); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Timers +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)destroySaveTimer { + if (_saveTimer) { + dispatch_source_cancel(_saveTimer); + + if (_saveTimerSuspended) { + // Must resume a timer before releasing it (or it will crash) + dispatch_resume(_saveTimer); + _saveTimerSuspended = NO; + } + + #if !OS_OBJECT_USE_OBJC + dispatch_release(_saveTimer); + #endif + _saveTimer = NULL; + } +} + +- (void)updateAndResumeSaveTimer { + if ((_saveTimer != NULL) && (_saveInterval > 0.0) && (_unsavedTime > 0.0)) { + uint64_t interval = (uint64_t)(_saveInterval * NSEC_PER_SEC); + dispatch_time_t startTime = dispatch_time(_unsavedTime, interval); + + dispatch_source_set_timer(_saveTimer, startTime, interval, 1.0); + + if (_saveTimerSuspended) { + dispatch_resume(_saveTimer); + _saveTimerSuspended = NO; + } + } +} + +- (void)createSuspendedSaveTimer { + if ((_saveTimer == NULL) && (_saveInterval > 0.0)) { + _saveTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.loggerQueue); + + dispatch_source_set_event_handler(_saveTimer, ^{ @autoreleasepool { + [self performSaveAndSuspendSaveTimer]; + } }); + + _saveTimerSuspended = YES; + } +} + +- (void)destroyDeleteTimer { + if (_deleteTimer) { + dispatch_source_cancel(_deleteTimer); + #if !OS_OBJECT_USE_OBJC + dispatch_release(_deleteTimer); + #endif + _deleteTimer = NULL; + } +} + +- (void)updateDeleteTimer { + if ((_deleteTimer != NULL) && (_deleteInterval > 0.0) && (_maxAge > 0.0)) { + uint64_t interval = (uint64_t)(_deleteInterval * NSEC_PER_SEC); + dispatch_time_t startTime; + + if (_lastDeleteTime > 0) { + startTime = dispatch_time(_lastDeleteTime, interval); + } else { + startTime = dispatch_time(DISPATCH_TIME_NOW, interval); + } + + dispatch_source_set_timer(_deleteTimer, startTime, interval, 1.0); + } +} + +- (void)createAndStartDeleteTimer { + if ((_deleteTimer == NULL) && (_deleteInterval > 0.0) && (_maxAge > 0.0)) { + _deleteTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.loggerQueue); + + if (_deleteTimer != NULL) { + dispatch_source_set_event_handler(_deleteTimer, ^{ @autoreleasepool { + [self performDelete]; + } }); + + [self updateDeleteTimer]; + + if (_deleteTimer != NULL) { + dispatch_resume(_deleteTimer); + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Configuration +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSUInteger)saveThreshold { + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + __block NSUInteger result; + + dispatch_sync(globalLoggingQueue, ^{ + dispatch_sync(self.loggerQueue, ^{ + result = _saveThreshold; + }); + }); + + return result; +} + +- (void)setSaveThreshold:(NSUInteger)threshold { + dispatch_block_t block = ^{ + @autoreleasepool { + if (_saveThreshold != threshold) { + _saveThreshold = threshold; + + // Since the saveThreshold has changed, + // we check to see if the current unsavedCount has surpassed the new threshold. + // + // If it has, we immediately save the log. + + if ((_unsavedCount >= _saveThreshold) && (_saveThreshold > 0)) { + [self performSaveAndSuspendSaveTimer]; + } + } + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (NSTimeInterval)saveInterval { + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + __block NSTimeInterval result; + + dispatch_sync(globalLoggingQueue, ^{ + dispatch_sync(self.loggerQueue, ^{ + result = _saveInterval; + }); + }); + + return result; +} + +- (void)setSaveInterval:(NSTimeInterval)interval { + dispatch_block_t block = ^{ + @autoreleasepool { + // C99 recommended floating point comparison macro + // Read: isLessThanOrGreaterThan(floatA, floatB) + + if (/* saveInterval != interval */ islessgreater(_saveInterval, interval)) { + _saveInterval = interval; + + // There are several cases we need to handle here. + // + // 1. If the saveInterval was previously enabled and it just got disabled, + // then we need to stop the saveTimer. (And we might as well release it.) + // + // 2. If the saveInterval was previously disabled and it just got enabled, + // then we need to setup the saveTimer. (Plus we might need to do an immediate save.) + // + // 3. If the saveInterval increased, then we need to reset the timer so that it fires at the later date. + // + // 4. If the saveInterval decreased, then we need to reset the timer so that it fires at an earlier date. + // (Plus we might need to do an immediate save.) + + if (_saveInterval > 0.0) { + if (_saveTimer == NULL) { + // Handles #2 + // + // Since the saveTimer uses the unsavedTime to calculate it's first fireDate, + // if a save is needed the timer will fire immediately. + + [self createSuspendedSaveTimer]; + [self updateAndResumeSaveTimer]; + } else { + // Handles #3 + // Handles #4 + // + // Since the saveTimer uses the unsavedTime to calculate it's first fireDate, + // if a save is needed the timer will fire immediately. + + [self updateAndResumeSaveTimer]; + } + } else if (_saveTimer) { + // Handles #1 + + [self destroySaveTimer]; + } + } + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (NSTimeInterval)maxAge { + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + __block NSTimeInterval result; + + dispatch_sync(globalLoggingQueue, ^{ + dispatch_sync(self.loggerQueue, ^{ + result = _maxAge; + }); + }); + + return result; +} + +- (void)setMaxAge:(NSTimeInterval)interval { + dispatch_block_t block = ^{ + @autoreleasepool { + // C99 recommended floating point comparison macro + // Read: isLessThanOrGreaterThan(floatA, floatB) + + if (/* maxAge != interval */ islessgreater(_maxAge, interval)) { + NSTimeInterval oldMaxAge = _maxAge; + NSTimeInterval newMaxAge = interval; + + _maxAge = interval; + + // There are several cases we need to handle here. + // + // 1. If the maxAge was previously enabled and it just got disabled, + // then we need to stop the deleteTimer. (And we might as well release it.) + // + // 2. If the maxAge was previously disabled and it just got enabled, + // then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.) + // + // 3. If the maxAge was increased, + // then we don't need to do anything. + // + // 4. If the maxAge was decreased, + // then we should do an immediate delete. + + BOOL shouldDeleteNow = NO; + + if (oldMaxAge > 0.0) { + if (newMaxAge <= 0.0) { + // Handles #1 + + [self destroyDeleteTimer]; + } else if (oldMaxAge > newMaxAge) { + // Handles #4 + shouldDeleteNow = YES; + } + } else if (newMaxAge > 0.0) { + // Handles #2 + shouldDeleteNow = YES; + } + + if (shouldDeleteNow) { + [self performDelete]; + + if (_deleteTimer) { + [self updateDeleteTimer]; + } else { + [self createAndStartDeleteTimer]; + } + } + } + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (NSTimeInterval)deleteInterval { + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + __block NSTimeInterval result; + + dispatch_sync(globalLoggingQueue, ^{ + dispatch_sync(self.loggerQueue, ^{ + result = _deleteInterval; + }); + }); + + return result; +} + +- (void)setDeleteInterval:(NSTimeInterval)interval { + dispatch_block_t block = ^{ + @autoreleasepool { + // C99 recommended floating point comparison macro + // Read: isLessThanOrGreaterThan(floatA, floatB) + + if (/* deleteInterval != interval */ islessgreater(_deleteInterval, interval)) { + _deleteInterval = interval; + + // There are several cases we need to handle here. + // + // 1. If the deleteInterval was previously enabled and it just got disabled, + // then we need to stop the deleteTimer. (And we might as well release it.) + // + // 2. If the deleteInterval was previously disabled and it just got enabled, + // then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.) + // + // 3. If the deleteInterval increased, then we need to reset the timer so that it fires at the later date. + // + // 4. If the deleteInterval decreased, then we need to reset the timer so that it fires at an earlier date. + // (Plus we might need to do an immediate delete.) + + if (_deleteInterval > 0.0) { + if (_deleteTimer == NULL) { + // Handles #2 + // + // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate, + // if a delete is needed the timer will fire immediately. + + [self createAndStartDeleteTimer]; + } else { + // Handles #3 + // Handles #4 + // + // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate, + // if a save is needed the timer will fire immediately. + + [self updateDeleteTimer]; + } + } else if (_deleteTimer) { + // Handles #1 + + [self destroyDeleteTimer]; + } + } + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (BOOL)deleteOnEverySave { + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + __block BOOL result; + + dispatch_sync(globalLoggingQueue, ^{ + dispatch_sync(self.loggerQueue, ^{ + result = _deleteOnEverySave; + }); + }); + + return result; +} + +- (void)setDeleteOnEverySave:(BOOL)flag { + dispatch_block_t block = ^{ + _deleteOnEverySave = flag; + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Public API +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)savePendingLogEntries { + dispatch_block_t block = ^{ + @autoreleasepool { + [self performSaveAndSuspendSaveTimer]; + } + }; + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + dispatch_async(self.loggerQueue, block); + } +} + +- (void)deleteOldLogEntries { + dispatch_block_t block = ^{ + @autoreleasepool { + [self performDelete]; + } + }; + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + dispatch_async(self.loggerQueue, block); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark DDLogger +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)didAddLogger { + // If you override me be sure to invoke [super didAddLogger]; + + [self createSuspendedSaveTimer]; + + [self createAndStartDeleteTimer]; +} + +- (void)willRemoveLogger { + // If you override me be sure to invoke [super willRemoveLogger]; + + [self performSaveAndSuspendSaveTimer]; + + [self destroySaveTimer]; + [self destroyDeleteTimer]; +} + +- (void)logMessage:(DDLogMessage *)logMessage { + if ([self db_log:logMessage]) { + BOOL firstUnsavedEntry = (++_unsavedCount == 1); + + if ((_unsavedCount >= _saveThreshold) && (_saveThreshold > 0)) { + [self performSaveAndSuspendSaveTimer]; + } else if (firstUnsavedEntry) { + _unsavedTime = dispatch_time(DISPATCH_TIME_NOW, 0); + [self updateAndResumeSaveTimer]; + } + } +} + +- (void)flush { + // This method is invoked by DDLog's flushLog method. + // + // It is called automatically when the application quits, + // or if the developer invokes DDLog's flushLog method prior to crashing or something. + + [self performSaveAndSuspendSaveTimer]; +} + +@end diff --git a/CocoaLumberjack/DDAssertMacros.h b/CocoaLumberjack/DDAssertMacros.h new file mode 100644 index 0000000..870d31f --- /dev/null +++ b/CocoaLumberjack/DDAssertMacros.h @@ -0,0 +1,26 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2015, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +/** + * NSAsset replacement that will output a log message even when assertions are disabled. + **/ +#define DDAssert(condition, frmt, ...) \ + if (!(condition)) { \ + NSString *description = [NSString stringWithFormat:frmt, ## __VA_ARGS__]; \ + DDLogError(@"%@", description); \ + NSAssert(NO, description); \ + } +#define DDAssertCondition(condition) DDAssert(condition, @"Condition not satisfied: %s", #condition) + diff --git a/CocoaLumberjack/DDContextFilterLogFormatter.h b/CocoaLumberjack/DDContextFilterLogFormatter.h new file mode 100644 index 0000000..975cd96 --- /dev/null +++ b/CocoaLumberjack/DDContextFilterLogFormatter.h @@ -0,0 +1,75 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2015, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +#import "DDLog.h" + +/** + * This class provides a log formatter that filters log statements from a logging context not on the whitelist. + * + * A log formatter can be added to any logger to format and/or filter its output. + * You can learn more about log formatters here: + * Documentation/CustomFormatters.md + * + * You can learn more about logging context's here: + * Documentation/CustomContext.md + * + * But here's a quick overview / refresher: + * + * Every log statement has a logging context. + * These come from the underlying logging macros defined in DDLog.h. + * The default logging context is zero. + * You can define multiple logging context's for use in your application. + * For example, logically separate parts of your app each have a different logging context. + * Also 3rd party frameworks that make use of Lumberjack generally use their own dedicated logging context. + **/ +@interface DDContextWhitelistFilterLogFormatter : NSObject + +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +- (void)addToWhitelist:(NSUInteger)loggingContext; +- (void)removeFromWhitelist:(NSUInteger)loggingContext; + +@property (readonly, copy) NSArray *whitelist; + +- (BOOL)isOnWhitelist:(NSUInteger)loggingContext; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * This class provides a log formatter that filters log statements from a logging context on the blacklist. + **/ +@interface DDContextBlacklistFilterLogFormatter : NSObject + +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +- (void)addToBlacklist:(NSUInteger)loggingContext; +- (void)removeFromBlacklist:(NSUInteger)loggingContext; + +@property (readonly, copy) NSArray *blacklist; + +- (BOOL)isOnBlacklist:(NSUInteger)loggingContext; + +@end diff --git a/CocoaLumberjack/DDContextFilterLogFormatter.m b/CocoaLumberjack/DDContextFilterLogFormatter.m new file mode 100644 index 0000000..14a6ae9 --- /dev/null +++ b/CocoaLumberjack/DDContextFilterLogFormatter.m @@ -0,0 +1,191 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2015, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import "DDContextFilterLogFormatter.h" +#import + +#if !__has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +@interface DDLoggingContextSet : NSObject + +- (void)addToSet:(NSUInteger)loggingContext; +- (void)removeFromSet:(NSUInteger)loggingContext; + +@property (readonly, copy) NSArray *currentSet; + +- (BOOL)isInSet:(NSUInteger)loggingContext; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface DDContextWhitelistFilterLogFormatter () { + DDLoggingContextSet *_contextSet; +} + +@end + + +@implementation DDContextWhitelistFilterLogFormatter + +- (instancetype)init { + if ((self = [super init])) { + _contextSet = [[DDLoggingContextSet alloc] init]; + } + + return self; +} + +- (void)addToWhitelist:(NSUInteger)loggingContext { + [_contextSet addToSet:loggingContext]; +} + +- (void)removeFromWhitelist:(NSUInteger)loggingContext { + [_contextSet removeFromSet:loggingContext]; +} + +- (NSArray *)whitelist { + return [_contextSet currentSet]; +} + +- (BOOL)isOnWhitelist:(NSUInteger)loggingContext { + return [_contextSet isInSet:loggingContext]; +} + +- (NSString *)formatLogMessage:(DDLogMessage *)logMessage { + if ([self isOnWhitelist:logMessage->_context]) { + return logMessage->_message; + } else { + return nil; + } +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface DDContextBlacklistFilterLogFormatter () { + DDLoggingContextSet *_contextSet; +} + +@end + + +@implementation DDContextBlacklistFilterLogFormatter + +- (instancetype)init { + if ((self = [super init])) { + _contextSet = [[DDLoggingContextSet alloc] init]; + } + + return self; +} + +- (void)addToBlacklist:(NSUInteger)loggingContext { + [_contextSet addToSet:loggingContext]; +} + +- (void)removeFromBlacklist:(NSUInteger)loggingContext { + [_contextSet removeFromSet:loggingContext]; +} + +- (NSArray *)blacklist { + return [_contextSet currentSet]; +} + +- (BOOL)isOnBlacklist:(NSUInteger)loggingContext { + return [_contextSet isInSet:loggingContext]; +} + +- (NSString *)formatLogMessage:(DDLogMessage *)logMessage { + if ([self isOnBlacklist:logMessage->_context]) { + return nil; + } else { + return logMessage->_message; + } +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +@interface DDLoggingContextSet () { + OSSpinLock _lock; + NSMutableSet *_set; +} + +@end + + +@implementation DDLoggingContextSet + +- (instancetype)init { + if ((self = [super init])) { + _set = [[NSMutableSet alloc] init]; + } + + return self; +} + +- (void)addToSet:(NSUInteger)loggingContext { + OSSpinLockLock(&_lock); + { + [_set addObject:@(loggingContext)]; + } + OSSpinLockUnlock(&_lock); +} + +- (void)removeFromSet:(NSUInteger)loggingContext { + OSSpinLockLock(&_lock); + { + [_set removeObject:@(loggingContext)]; + } + OSSpinLockUnlock(&_lock); +} + +- (NSArray *)currentSet { + NSArray *result = nil; + + OSSpinLockLock(&_lock); + { + result = [_set allObjects]; + } + OSSpinLockUnlock(&_lock); + + return result; +} + +- (BOOL)isInSet:(NSUInteger)loggingContext { + BOOL result = NO; + + OSSpinLockLock(&_lock); + { + result = [_set containsObject:@(loggingContext)]; + } + OSSpinLockUnlock(&_lock); + + return result; +} + +@end diff --git a/CocoaLumberjack/DDDispatchQueueLogFormatter.h b/CocoaLumberjack/DDDispatchQueueLogFormatter.h new file mode 100644 index 0000000..05eb800 --- /dev/null +++ b/CocoaLumberjack/DDDispatchQueueLogFormatter.h @@ -0,0 +1,135 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2015, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import +#import + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +#import "DDLog.h" + + +/** + * This class provides a log formatter that prints the dispatch_queue label instead of the mach_thread_id. + * + * A log formatter can be added to any logger to format and/or filter its output. + * You can learn more about log formatters here: + * Documentation/CustomFormatters.md + * + * A typical NSLog (or DDTTYLogger) prints detailed info as [:]. + * For example: + * + * 2011-10-17 20:21:45.435 AppName[19928:5207] Your log message here + * + * Where: + * - 19928 = process id + * - 5207 = thread id (mach_thread_id printed in hex) + * + * When using grand central dispatch (GCD), this information is less useful. + * This is because a single serial dispatch queue may be run on any thread from an internally managed thread pool. + * For example: + * + * 2011-10-17 20:32:31.111 AppName[19954:4d07] Message from my_serial_dispatch_queue + * 2011-10-17 20:32:31.112 AppName[19954:5207] Message from my_serial_dispatch_queue + * 2011-10-17 20:32:31.113 AppName[19954:2c55] Message from my_serial_dispatch_queue + * + * This formatter allows you to replace the standard [box:info] with the dispatch_queue name. + * For example: + * + * 2011-10-17 20:32:31.111 AppName[img-scaling] Message from my_serial_dispatch_queue + * 2011-10-17 20:32:31.112 AppName[img-scaling] Message from my_serial_dispatch_queue + * 2011-10-17 20:32:31.113 AppName[img-scaling] Message from my_serial_dispatch_queue + * + * If the dispatch_queue doesn't have a set name, then it falls back to the thread name. + * If the current thread doesn't have a set name, then it falls back to the mach_thread_id in hex (like normal). + * + * Note: If manually creating your own background threads (via NSThread/alloc/init or NSThread/detachNeThread), + * you can use [[NSThread currentThread] setName:(NSString *)]. + **/ +@interface DDDispatchQueueLogFormatter : NSObject + +/** + * Standard init method. + * Configure using properties as desired. + **/ +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +/** + * The minQueueLength restricts the minimum size of the [detail box]. + * If the minQueueLength is set to 0, there is no restriction. + * + * For example, say a dispatch_queue has a label of "diskIO": + * + * If the minQueueLength is 0: [diskIO] + * If the minQueueLength is 4: [diskIO] + * If the minQueueLength is 5: [diskIO] + * If the minQueueLength is 6: [diskIO] + * If the minQueueLength is 7: [diskIO ] + * If the minQueueLength is 8: [diskIO ] + * + * The default minQueueLength is 0 (no minimum, so [detail box] won't be padded). + * + * If you want every [detail box] to have the exact same width, + * set both minQueueLength and maxQueueLength to the same value. + **/ +@property (assign, atomic) NSUInteger minQueueLength; + +/** + * The maxQueueLength restricts the number of characters that will be inside the [detail box]. + * If the maxQueueLength is 0, there is no restriction. + * + * For example, say a dispatch_queue has a label of "diskIO": + * + * If the maxQueueLength is 0: [diskIO] + * If the maxQueueLength is 4: [disk] + * If the maxQueueLength is 5: [diskI] + * If the maxQueueLength is 6: [diskIO] + * If the maxQueueLength is 7: [diskIO] + * If the maxQueueLength is 8: [diskIO] + * + * The default maxQueueLength is 0 (no maximum, so [detail box] won't be truncated). + * + * If you want every [detail box] to have the exact same width, + * set both minQueueLength and maxQueueLength to the same value. + **/ +@property (assign, atomic) NSUInteger maxQueueLength; + +/** + * Sometimes queue labels have long names like "com.apple.main-queue", + * but you'd prefer something shorter like simply "main". + * + * This method allows you to set such preferred replacements. + * The above example is set by default. + * + * To remove/undo a previous replacement, invoke this method with nil for the 'shortLabel' parameter. + **/ +- (NSString *)replacementStringForQueueLabel:(NSString *)longLabel; +- (void)setReplacementString:(NSString *)shortLabel forQueueLabel:(NSString *)longLabel; + +@end + +/** + * Method declarations that make it easier to extend/modify DDDispatchQueueLogFormatter + **/ +@interface DDDispatchQueueLogFormatter (OverridableMethods) + +- (NSString *)stringFromDate:(NSDate *)date; +- (NSString *)queueThreadLabelForLogMessage:(DDLogMessage *)logMessage; +- (NSString *)formatLogMessage:(DDLogMessage *)logMessage; + +@end diff --git a/CocoaLumberjack/DDDispatchQueueLogFormatter.m b/CocoaLumberjack/DDDispatchQueueLogFormatter.m new file mode 100644 index 0000000..9b81c06 --- /dev/null +++ b/CocoaLumberjack/DDDispatchQueueLogFormatter.m @@ -0,0 +1,247 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2015, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import "DDDispatchQueueLogFormatter.h" +#import + + +#if !__has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +@interface DDDispatchQueueLogFormatter () { + NSString *_dateFormatString; + + int32_t _atomicLoggerCount; + NSDateFormatter *_threadUnsafeDateFormatter; // Use [self stringFromDate] + + OSSpinLock _lock; + + NSUInteger _minQueueLength; // _prefix == Only access via atomic property + NSUInteger _maxQueueLength; // _prefix == Only access via atomic property + NSMutableDictionary *_replacements; // _prefix == Only access from within spinlock +} + +@end + + +@implementation DDDispatchQueueLogFormatter + +- (instancetype)init { + if ((self = [super init])) { + _dateFormatString = @"yyyy-MM-dd HH:mm:ss:SSS"; + + _atomicLoggerCount = 0; + _threadUnsafeDateFormatter = nil; + + _minQueueLength = 0; + _maxQueueLength = 0; + _replacements = [[NSMutableDictionary alloc] init]; + + // Set default replacements: + + _replacements[@"com.apple.main-thread"] = @"main"; + } + + return self; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Configuration +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@synthesize minQueueLength = _minQueueLength; +@synthesize maxQueueLength = _maxQueueLength; + +- (NSString *)replacementStringForQueueLabel:(NSString *)longLabel { + NSString *result = nil; + + OSSpinLockLock(&_lock); + { + result = _replacements[longLabel]; + } + OSSpinLockUnlock(&_lock); + + return result; +} + +- (void)setReplacementString:(NSString *)shortLabel forQueueLabel:(NSString *)longLabel { + OSSpinLockLock(&_lock); + { + if (shortLabel) { + _replacements[longLabel] = shortLabel; + } else { + [_replacements removeObjectForKey:longLabel]; + } + } + OSSpinLockUnlock(&_lock); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark DDLogFormatter +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSString *)stringFromDate:(NSDate *)date { + int32_t loggerCount = OSAtomicAdd32(0, &_atomicLoggerCount); + + NSString *calendarIdentifier = nil; + +#if defined(__IPHONE_8_0) || defined(__MAC_10_10) + calendarIdentifier = NSCalendarIdentifierGregorian; +#else + calendarIdentifier = NSGregorianCalendar; +#endif + + if (loggerCount <= 1) { + // Single-threaded mode. + + if (_threadUnsafeDateFormatter == nil) { + _threadUnsafeDateFormatter = [[NSDateFormatter alloc] init]; + [_threadUnsafeDateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + [_threadUnsafeDateFormatter setDateFormat:_dateFormatString]; + } + + [_threadUnsafeDateFormatter setCalendar:[[NSCalendar alloc] initWithCalendarIdentifier:calendarIdentifier]]; + return [_threadUnsafeDateFormatter stringFromDate:date]; + } else { + // Multi-threaded mode. + // NSDateFormatter is NOT thread-safe. + + NSString *key = @"DispatchQueueLogFormatter_NSDateFormatter"; + + NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary]; + NSDateFormatter *dateFormatter = threadDictionary[key]; + + if (dateFormatter == nil) { + dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + [dateFormatter setDateFormat:_dateFormatString]; + + threadDictionary[key] = dateFormatter; + } + + [dateFormatter setCalendar:[[NSCalendar alloc] initWithCalendarIdentifier:calendarIdentifier]]; + return [dateFormatter stringFromDate:date]; + } +} + +- (NSString *)queueThreadLabelForLogMessage:(DDLogMessage *)logMessage { + // As per the DDLogFormatter contract, this method is always invoked on the same thread/dispatch_queue + + NSUInteger minQueueLength = self.minQueueLength; + NSUInteger maxQueueLength = self.maxQueueLength; + + // Get the name of the queue, thread, or machID (whichever we are to use). + + NSString *queueThreadLabel = nil; + + BOOL useQueueLabel = YES; + BOOL useThreadName = NO; + + if (logMessage->_queueLabel) { + // If you manually create a thread, it's dispatch_queue will have one of the thread names below. + // Since all such threads have the same name, we'd prefer to use the threadName or the machThreadID. + + NSArray *names = @[ + @"com.apple.root.low-priority", + @"com.apple.root.default-priority", + @"com.apple.root.high-priority", + @"com.apple.root.low-overcommit-priority", + @"com.apple.root.default-overcommit-priority", + @"com.apple.root.high-overcommit-priority" + ]; + + for (NSString * name in names) { + if ([logMessage->_queueLabel isEqualToString:name]) { + useQueueLabel = NO; + useThreadName = [logMessage->_threadName length] > 0; + break; + } + } + } else { + useQueueLabel = NO; + useThreadName = [logMessage->_threadName length] > 0; + } + + if (useQueueLabel || useThreadName) { + NSString *fullLabel; + NSString *abrvLabel; + + if (useQueueLabel) { + fullLabel = logMessage->_queueLabel; + } else { + fullLabel = logMessage->_threadName; + } + + OSSpinLockLock(&_lock); + { + abrvLabel = _replacements[fullLabel]; + } + OSSpinLockUnlock(&_lock); + + if (abrvLabel) { + queueThreadLabel = abrvLabel; + } else { + queueThreadLabel = fullLabel; + } + } else { + queueThreadLabel = logMessage->_threadID; + } + + // Now use the thread label in the output + + NSUInteger labelLength = [queueThreadLabel length]; + + // labelLength > maxQueueLength : truncate + // labelLength < minQueueLength : padding + // : exact + + if ((maxQueueLength > 0) && (labelLength > maxQueueLength)) { + // Truncate + + return [queueThreadLabel substringToIndex:maxQueueLength]; + } else if (labelLength < minQueueLength) { + // Padding + + NSUInteger numSpaces = minQueueLength - labelLength; + + char spaces[numSpaces + 1]; + memset(spaces, ' ', numSpaces); + spaces[numSpaces] = '\0'; + + return [NSString stringWithFormat:@"%@%s", queueThreadLabel, spaces]; + } else { + // Exact + + return queueThreadLabel; + } +} + +- (NSString *)formatLogMessage:(DDLogMessage *)logMessage { + NSString *timestamp = [self stringFromDate:(logMessage->_timestamp)]; + NSString *queueThreadLabel = [self queueThreadLabelForLogMessage:logMessage]; + + return [NSString stringWithFormat:@"%@ [%@] %@", timestamp, queueThreadLabel, logMessage->_message]; +} + +- (void)didAddToLogger:(id __attribute__((unused)))logger { + OSAtomicIncrement32(&_atomicLoggerCount); +} + +- (void)willRemoveFromLogger:(id __attribute__((unused)))logger { + OSAtomicDecrement32(&_atomicLoggerCount); +} + +@end diff --git a/CocoaLumberjack/DDFileLogger.h b/CocoaLumberjack/DDFileLogger.h new file mode 100644 index 0000000..d2f9ebb --- /dev/null +++ b/CocoaLumberjack/DDFileLogger.h @@ -0,0 +1,391 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2015, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +#import "DDLog.h" + +@class DDLogFileInfo; + +/** + * This class provides a logger to write log statements to a file. + **/ + + +// Default configuration and safety/sanity values. +// +// maximumFileSize -> kDDDefaultLogMaxFileSize +// rollingFrequency -> kDDDefaultLogRollingFrequency +// maximumNumberOfLogFiles -> kDDDefaultLogMaxNumLogFiles +// logFilesDiskQuota -> kDDDefaultLogFilesDiskQuota +// +// You should carefully consider the proper configuration values for your application. + +extern unsigned long long const kDDDefaultLogMaxFileSize; +extern NSTimeInterval const kDDDefaultLogRollingFrequency; +extern NSUInteger const kDDDefaultLogMaxNumLogFiles; +extern unsigned long long const kDDDefaultLogFilesDiskQuota; + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// The LogFileManager protocol is designed to allow you to control all aspects of your log files. +// +// The primary purpose of this is to allow you to do something with the log files after they have been rolled. +// Perhaps you want to compress them to save disk space. +// Perhaps you want to upload them to an FTP server. +// Perhaps you want to run some analytics on the file. +// +// A default LogFileManager is, of course, provided. +// The default LogFileManager simply deletes old log files according to the maximumNumberOfLogFiles property. +// +// This protocol provides various methods to fetch the list of log files. +// +// There are two variants: sorted and unsorted. +// If sorting is not necessary, the unsorted variant is obviously faster. +// The sorted variant will return an array sorted by when the log files were created, +// with the most recently created log file at index 0, and the oldest log file at the end of the array. +// +// You can fetch only the log file paths (full path including name), log file names (name only), +// or an array of DDLogFileInfo objects. +// The DDLogFileInfo class is documented below, and provides a handy wrapper that +// gives you easy access to various file attributes such as the creation date or the file size. + +@protocol DDLogFileManager +@required + +// Public properties + +/** + * The maximum number of archived log files to keep on disk. + * For example, if this property is set to 3, + * then the LogFileManager will only keep 3 archived log files (plus the current active log file) on disk. + * Once the active log file is rolled/archived, then the oldest of the existing 3 rolled/archived log files is deleted. + * + * You may optionally disable this option by setting it to zero. + **/ +@property (readwrite, assign, atomic) NSUInteger maximumNumberOfLogFiles; + +/** + * The maximum space that logs can take. On rolling logfile all old logfiles that exceed logFilesDiskQuota will + * be deleted. + * + * You may optionally disable this option by setting it to zero. + **/ +@property (readwrite, assign, atomic) unsigned long long logFilesDiskQuota; + +// Public methods + +- (NSString *)logsDirectory; + +- (NSArray *)unsortedLogFilePaths; +- (NSArray *)unsortedLogFileNames; +- (NSArray *)unsortedLogFileInfos; + +- (NSArray *)sortedLogFilePaths; +- (NSArray *)sortedLogFileNames; +- (NSArray *)sortedLogFileInfos; + +// Private methods (only to be used by DDFileLogger) + +- (NSString *)createNewLogFile; + +@optional + +// Notifications from DDFileLogger + +- (void)didArchiveLogFile:(NSString *)logFilePath; +- (void)didRollAndArchiveLogFile:(NSString *)logFilePath; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Default log file manager. + * + * All log files are placed inside the logsDirectory. + * If a specific logsDirectory isn't specified, the default directory is used. + * On Mac, this is in ~/Library/Logs/. + * On iPhone, this is in ~/Library/Caches/Logs. + * + * Log files are named "