@@ -82,6 +82,11 @@ @interface SNTConfigurator ()
8282@property (atomic ) NSMutableDictionary * configState;
8383@property (atomic ) NSDictionary * state;
8484
85+ // / Working dictionary for an in-progress `performSyncStateBatch:` transaction.
86+ // / Non-nil only while inside the batch's block. Always accessed on the main
87+ // / thread, matching the convention enforced by `updateSyncStateForKey:value:`.
88+ @property (nonatomic ) NSMutableDictionary * batchedSyncState;
89+
8590@property (readonly , nonatomic ) NSString * syncStateFilePath;
8691@property (readonly , nonatomic ) NSString * stateFilePath;
8792
@@ -1960,6 +1965,10 @@ - (NSDictionary*)extraMetricLabels {
19601965// /
19611966- (void )updateSyncStateForKey : (NSString *)key value : (id )value {
19621967 void (^block)(void ) = ^{
1968+ if (self.batchedSyncState ) {
1969+ self.batchedSyncState [key] = value;
1970+ return ;
1971+ }
19631972 NSMutableDictionary * syncState = self.syncState .mutableCopy ;
19641973 syncState[key] = value;
19651974 self.syncState = syncState;
@@ -1974,6 +1983,28 @@ - (void)updateSyncStateForKey:(NSString*)key value:(id)value {
19741983 }
19751984}
19761985
1986+ - (BOOL )performSyncStateBatch : (void (NS_NOESCAPE ^)(void ))block {
1987+ __block BOOL committed = NO ;
1988+ void (^run)(void ) = ^{
1989+ if (self.batchedSyncState != nil ) {
1990+ NSAssert (NO , @" Sync-state batches do not nest" );
1991+ LOGE (@" Sync-state batches do not nest; skipping nested batch" );
1992+ return ;
1993+ }
1994+ self.batchedSyncState = self.syncState .mutableCopy ;
1995+ block ();
1996+ self.syncState = self.batchedSyncState ;
1997+ self.batchedSyncState = nil ;
1998+ committed = [self saveSyncStateToDisk ];
1999+ };
2000+ if ([NSThread isMainThread ]) {
2001+ run ();
2002+ } else {
2003+ dispatch_sync (dispatch_get_main_queue (), run);
2004+ }
2005+ return committed;
2006+ }
2007+
19772008// /
19782009// / Read the saved syncState.
19792010// /
@@ -2043,27 +2074,47 @@ - (BOOL)migrateDeprecatedSyncStateKeys {
20432074}
20442075
20452076// /
2046- // / Saves the current effective syncState to disk.
2077+ // / Saves the current effective syncState to disk. Returns YES if the write
2078+ // / succeeded; NO if the authorizer denied the operation or the underlying
2079+ // / file write failed.
20472080// /
2048- - (void )saveSyncStateToDisk {
2081+ - (BOOL )saveSyncStateToDisk {
20492082 if (!self.syncStateAccessAuthorizerBlock ()) {
2050- return ;
2083+ return NO ;
20512084 }
20522085
20532086 NSMutableDictionary * syncState = self.syncState .mutableCopy ;
20542087 syncState[kAllowedPathRegexKey ] = [syncState[kAllowedPathRegexKey ] pattern ];
20552088 syncState[kBlockedPathRegexKey ] = [syncState[kBlockedPathRegexKey ] pattern ];
2056- [syncState writeToFile: self .syncStateFilePath atomically: YES ];
2089+ if (![syncState writeToFile: self .syncStateFilePath atomically: YES ]) {
2090+ LOGE (@" Failed to write sync state to %@ " , self.syncStateFilePath );
2091+ return NO ;
2092+ }
20572093 [[NSFileManager defaultManager ] setAttributes: @{NSFilePosixPermissions : @0600 }
20582094 ofItemAtPath: self .syncStateFilePath
20592095 error: NULL ];
2096+ return YES ;
20602097}
20612098
20622099- (void )clearSyncState {
2063- self.syncState = [NSMutableDictionary dictionary ];
2064- // TODO: Start a timer to flush the state to disk. On startup, Santa should
2065- // check for the presence of the state file and, if no SyncBaseURL is
2066- // configured, start the timer to clear sync state and flush to disk.
2100+ // Intentionally not gated on `syncStateAccessAuthorizerBlock`: the authorizer
2101+ // requires `syncBaseURL != nil`, but the SNTSyncdQueue caller invokes this
2102+ // method precisely *because* `syncBaseURL` went to nil. Gating here would
2103+ // make the cleanup unreachable in production. Disk removal still requires
2104+ // root, which santad already has.
2105+ void (^block)(void ) = ^{
2106+ if (self.batchedSyncState ) {
2107+ [self .batchedSyncState removeAllObjects ];
2108+ return ;
2109+ }
2110+ self.syncState = [NSMutableDictionary dictionary ];
2111+ [[NSFileManager defaultManager ] removeItemAtPath: self .syncStateFilePath error: NULL ];
2112+ };
2113+ if ([NSThread isMainThread ]) {
2114+ block ();
2115+ } else {
2116+ dispatch_sync (dispatch_get_main_queue (), block);
2117+ }
20672118}
20682119
20692120- (NSArray *)entitlementsPrefixFilter {
0 commit comments