@@ -230,60 +230,91 @@ func LinkAt(oldparent *os.File, oldname string, newparent *os.File, newname stri
230230 return
231231}
232232
233- // RemoveChildren recursively removes all files and subdirectories
234- // within the directory pointed to by dirFile. Removes all it can but returns
235- // the first error, if any.
233+ // RemoveChildren removes all files and subdirectories within the directory
234+ // pointed to by dirFile using an explicit stack instead of recursion. Removes
235+ // all it can but returns the first error, if any.
236236func RemoveChildren (dirFile * os.File ) error {
237237 var firstErr error
238238
239- // Rewind directory pointer to ensure we start from the beginning
240- if _ , err := dirFile .Seek (0 , io .SeekStart ); err != nil {
241- return err
239+ // Each stack frame is one of two kinds:
240+ // expand: dir != nil – read dir's children, unlink files, push subdirs
241+ // rmdir: dir == nil – rmdir name from parent (a dup'd fd), then close parent
242+ type frame struct {
243+ dir * os.File // non-nil: expand this directory
244+ name string // rmdir: child name to remove from parent
245+ parent * os.File // rmdir: dup'd parent fd (closed after use)
246+ close bool // expand: close dir when done (false only for the root)
242247 }
243248
244- for {
245- // Read names in small chunks to handle very large directories
246- entries , err := dirFile .ReadDir (64 )
247- if err != nil {
248- if errors .Is (err , io .EOF ) {
249- break
249+ stack := []frame {{dir : dirFile , close : false }}
250+
251+ for len (stack ) > 0 {
252+ f := stack [len (stack )- 1 ]
253+ stack = stack [:len (stack )- 1 ]
254+
255+ if f .dir == nil {
256+ // rmdir sentinel: the subdirectory is now empty, remove it.
257+ if err := RemoveDirAt (f .parent , f .name ); err != nil && firstErr == nil {
258+ firstErr = err
250259 }
260+ f .parent .Close ()
261+ continue
262+ }
263+
264+ // Rewind so we always start from the beginning of the directory.
265+ if _ , err := f .dir .Seek (0 , io .SeekStart ); err != nil {
251266 if firstErr == nil {
252- firstErr = & os. PathError { Op : "readdirnames" , Path : dirFile . Name (), Err : err }
267+ firstErr = err
253268 }
254- break
269+ if f .close {
270+ f .dir .Close ()
271+ }
272+ continue
255273 }
256274
257- for _ , entry := range entries {
258- name := entry .Name ()
259- if entry .IsDir () {
260- // Open subdirectory relative to parent FD
261- var childFile * os.File
262- if childFile , err = OpenDirAt (dirFile , name ); err != nil {
263- if firstErr == nil {
264- firstErr = err
275+ for {
276+ // Read entries in small chunks to handle very large directories.
277+ entries , err := f .dir .ReadDir (64 )
278+ for _ , entry := range entries {
279+ name := entry .Name ()
280+ if entry .IsDir () {
281+ childFile , openErr := OpenDirAt (f .dir , name )
282+ if openErr != nil {
283+ if firstErr == nil {
284+ firstErr = openErr
285+ }
286+ continue
265287 }
266- continue
267- }
268- err = RemoveChildren (childFile )
269- childFile .Close ()
270- if err == nil {
271- // Remove the empty subdirectory
272- if err = RemoveDirAt (dirFile , name ); err != nil && firstErr == nil {
273- firstErr = err
288+ parentDup , dupErr := DupFile (f .dir )
289+ if dupErr != nil {
290+ if firstErr == nil {
291+ firstErr = dupErr
292+ }
293+ childFile .Close ()
294+ continue
295+ }
296+ // Push rmdir sentinel first; LIFO ensures the expand frame
297+ // below is processed before this rmdir sentinel.
298+ stack = append (stack , frame {name : name , parent : parentDup }, frame {dir : childFile , close : true })
299+ } else {
300+ if unlinkErr := UnlinkAt (f .dir , name ); unlinkErr != nil && firstErr == nil {
301+ firstErr = unlinkErr
274302 }
275- } else if firstErr == nil {
276- firstErr = err
277303 }
278- childFile .Close ()
279- } else {
280- // Remove file/symlink
281- if err = UnlinkAt (dirFile , name ); err != nil && firstErr == nil {
282- firstErr = err
304+ }
305+ if err != nil {
306+ if ! errors .Is (err , io .EOF ) && firstErr == nil {
307+ firstErr = & os.PathError {Op : "readdir" , Path : f .dir .Name (), Err : err }
283308 }
309+ break
284310 }
285311 }
312+
313+ if f .close {
314+ f .dir .Close ()
315+ }
286316 }
317+
287318 _ , _ = dirFile .Seek (0 , io .SeekStart )
288319 return firstErr
289320}
0 commit comments