@@ -146,25 +146,36 @@ enum Mode {
146146pub struct CatalogLock {
147147 file : File ,
148148 control : File ,
149+ root : File ,
149150}
150151
151152impl CatalogLock {
152153 pub fn shared ( catalog : & Path ) -> Result < Self > {
153- Self :: acquire ( catalog, Mode :: Shared , false )
154+ Self :: acquire ( catalog, Mode :: Shared , false , true )
155+ }
156+
157+ /// Acquire the existing catalog lock without initializing missing control state.
158+ pub ( crate ) fn shared_existing ( catalog : & Path ) -> Result < Self > {
159+ Self :: acquire ( catalog, Mode :: Shared , false , false )
154160 }
155161
156162 pub fn exclusive ( catalog : & Path ) -> Result < Self > {
157- Self :: acquire ( catalog, Mode :: Exclusive , false )
163+ Self :: acquire ( catalog, Mode :: Exclusive , false , true )
158164 }
159165
160166 /// The whole-catalog transaction is the only operation allowed to inspect and recover an
161167 /// incomplete apply. Every other declaration reader/writer must keep using `shared` or
162168 /// `exclusive`, which fail closed while the marker exists.
163169 pub ( crate ) fn exclusive_for_catalog_apply ( catalog : & Path ) -> Result < Self > {
164- Self :: acquire ( catalog, Mode :: Exclusive , true )
170+ Self :: acquire ( catalog, Mode :: Exclusive , true , true )
165171 }
166172
167- fn acquire ( catalog : & Path , mode : Mode , allow_incomplete_apply : bool ) -> Result < Self > {
173+ fn acquire (
174+ catalog : & Path ,
175+ mode : Mode ,
176+ allow_incomplete_apply : bool ,
177+ initialize : bool ,
178+ ) -> Result < Self > {
168179 let catalog = catalog
169180 . canonicalize ( )
170181 . with_context ( || format ! ( "canonicalize catalog root {}" , catalog. display( ) ) ) ?;
@@ -175,6 +186,8 @@ impl CatalogLock {
175186 "catalog root is not a real directory: {}" ,
176187 catalog. display( )
177188 ) ;
189+ let root = crate :: catalog_transaction:: open_dir_beneath ( & catalog, & catalog)
190+ . with_context ( || format ! ( "open catalog root capability {}" , catalog. display( ) ) ) ?;
178191
179192 let control = catalog. join ( CONTROL_DIR ) ;
180193 let control_branch = match fs:: symlink_metadata ( & control) {
@@ -187,6 +200,11 @@ impl CatalogLock {
187200 "observed"
188201 }
189202 Err ( error) if error. kind ( ) == std:: io:: ErrorKind :: NotFound => {
203+ anyhow:: ensure!(
204+ initialize,
205+ "catalog control directory is absent: {}" ,
206+ control. display( )
207+ ) ;
190208 test_control_creation_checkpoint ( ) ;
191209 let branch = match fs:: create_dir ( & control) {
192210 Ok ( ( ) ) => {
@@ -221,33 +239,45 @@ impl CatalogLock {
221239 // incomplete marker. Persist its catalog-parent entry before any caller can publish
222240 // declaration leaves. This is unconditional after observing a real control dir: its creator
223241 // may have crashed after mkdir but before its own parent fsync.
224- File :: open ( & catalog)
225- . with_context ( || format ! ( "open catalog root {}" , catalog. display( ) ) ) ?
226- . sync_all ( )
242+ root. sync_all ( )
227243 . with_context ( || format ! ( "sync catalog root {}" , catalog. display( ) ) ) ?;
228244 #[ cfg( debug_assertions) ]
229245 if let Ok ( path) = std:: env:: var ( "ST2_TEST_CATALOG_CONTROL_BRANCH" ) {
230246 let _ = fs:: write ( path, control_branch) ;
231247 }
232248
233- let control_file = retained_control ( & catalog) ?
234- . context ( "catalog control directory disappeared while acquiring its lock" ) ?
235- . 0 ;
249+ let control_file = crate :: catalog_transaction:: openat_dir_nofollow (
250+ & root,
251+ std:: ffi:: OsStr :: new ( CONTROL_DIR ) ,
252+ )
253+ . context ( "catalog control directory disappeared while acquiring its lock" ) ?;
236254 let control = crate :: catalog_transaction:: retained_dir_path ( & control_file) ?;
237255 let path = control. join ( LOCK_FILE ) ;
238- let file = OpenOptions :: new ( )
256+ let mut options = OpenOptions :: new ( ) ;
257+ options
239258 . read ( true )
240259 . write ( true )
241- . create ( true )
242260 . mode ( 0o600 )
243- . custom_flags ( libc:: O_CLOEXEC | libc:: O_NOFOLLOW )
261+ . custom_flags ( libc:: O_CLOEXEC | libc:: O_NOFOLLOW ) ;
262+ if initialize {
263+ options. create ( true ) ;
264+ }
265+ let file = options
244266 . open ( & path)
245267 . with_context ( || format ! ( "open catalog authoring lock {}" , path. display( ) ) ) ?;
246268 let operation = match mode {
247269 Mode :: Shared => libc:: LOCK_SH ,
248270 Mode :: Exclusive => libc:: LOCK_EX ,
249271 } ;
250272 #[ cfg( debug_assertions) ]
273+ if let Ok ( path) = std:: env:: var ( "ST2_TEST_CATALOG_LOCK_ANY_ATTEMPT" ) {
274+ let value = match mode {
275+ Mode :: Shared => b"shared" . as_slice ( ) ,
276+ Mode :: Exclusive => b"exclusive" . as_slice ( ) ,
277+ } ;
278+ let _ = fs:: write ( path, value) ;
279+ }
280+ #[ cfg( debug_assertions) ]
251281 if matches ! ( mode, Mode :: Exclusive )
252282 && let Ok ( path) = std:: env:: var ( "ST2_TEST_CATALOG_LOCK_ATTEMPT" )
253283 {
@@ -285,6 +315,7 @@ impl CatalogLock {
285315 let lock = Self {
286316 file,
287317 control : control_file,
318+ root,
288319 } ;
289320 if matches ! ( mode, Mode :: Exclusive ) {
290321 lock. recover_generation_intent ( ) ?;
@@ -297,6 +328,14 @@ impl CatalogLock {
297328 advance_generation ( & self . control )
298329 }
299330
331+ pub ( crate ) fn generation ( & self ) -> Result < Option < u64 > > {
332+ read_generation_from_control ( & self . control )
333+ }
334+
335+ pub ( crate ) fn root ( & self ) -> & File {
336+ & self . root
337+ }
338+
300339 pub ( crate ) fn begin_generation_commit ( & self ) -> Result < GenerationCommit < ' _ > > {
301340 let control = crate :: catalog_transaction:: retained_dir_path ( & self . control ) ?;
302341 let intent = control. join ( GENERATION_INTENT_FILE ) ;
0 commit comments