From 683552c4c9a3dfee4ce603157bb2cf18d64fbcfc Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 30 Dec 2022 14:49:46 -0500 Subject: bfstd: New wrappers for dirname()/basename() --- src/bftw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 8c88101..5f3ebde 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -793,7 +793,7 @@ static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) { if (ftwbuf->depth == 0) { // Compute the name offset for root paths like "foo/bar" - ftwbuf->nameoff = xbasename(ftwbuf->path) - ftwbuf->path; + ftwbuf->nameoff = xbaseoff(ftwbuf->path); } if (ftwbuf->error != 0) { -- cgit v1.2.3 From 9463fdd30d392c98de7b5712d30dfbaeada40e99 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 25 Jan 2023 16:14:11 -0500 Subject: Replace license boilerplate with SPDX tags And while I'm at it, remove years from copyright declarations. Link: https://spdx.dev/about/ Link: https://daniel.haxx.se/blog/2023/01/08/copyright-without-years/ --- LICENSE | 33 +++++++++++++++++++++------------ Makefile | 17 ++--------------- completions/bfs.bash | 21 ++++----------------- src/bar.c | 17 ++--------------- src/bar.h | 17 ++--------------- src/bfstd.c | 17 ++--------------- src/bfstd.h | 17 ++--------------- src/bftw.c | 17 ++--------------- src/bftw.h | 17 ++--------------- src/color.c | 17 ++--------------- src/color.h | 17 ++--------------- src/config.h | 17 ++--------------- src/ctx.c | 17 ++--------------- src/ctx.h | 17 ++--------------- src/darray.c | 17 ++--------------- src/darray.h | 17 ++--------------- src/diag.c | 17 ++--------------- src/diag.h | 17 ++--------------- src/dir.c | 17 ++--------------- src/dir.h | 17 ++--------------- src/dstring.c | 17 ++--------------- src/dstring.h | 17 ++--------------- src/eval.c | 17 ++--------------- src/eval.h | 17 ++--------------- src/exec.c | 17 ++--------------- src/exec.h | 17 ++--------------- src/expr.h | 17 ++--------------- src/fsade.c | 17 ++--------------- src/fsade.h | 17 ++--------------- src/main.c | 17 ++--------------- src/mtab.c | 17 ++--------------- src/mtab.h | 17 ++--------------- src/opt.c | 17 ++--------------- src/opt.h | 17 ++--------------- src/parse.c | 17 ++--------------- src/parse.h | 17 ++--------------- src/printf.c | 17 ++--------------- src/printf.h | 17 ++--------------- src/pwcache.c | 17 ++--------------- src/pwcache.h | 17 ++--------------- src/stat.c | 17 ++--------------- src/stat.h | 17 ++--------------- src/trie.c | 17 ++--------------- src/trie.h | 17 ++--------------- src/typo.c | 17 ++--------------- src/typo.h | 17 ++--------------- src/xregex.c | 17 ++--------------- src/xregex.h | 18 ++---------------- src/xspawn.c | 17 ++--------------- src/xspawn.h | 17 ++--------------- src/xtime.c | 17 ++--------------- src/xtime.h | 17 ++--------------- tests/bfstd.c | 17 ++--------------- tests/find-color.sh | 17 ++--------------- tests/ls-color.sh | 17 ++--------------- tests/mksock.c | 17 ++--------------- tests/tests.sh | 17 ++--------------- tests/trie.c | 17 ++--------------- tests/xtimegm.c | 17 ++--------------- tests/xtouch.c | 17 ++--------------- 60 files changed, 141 insertions(+), 900 deletions(-) (limited to 'src/bftw.c') diff --git a/LICENSE b/LICENSE index 069b145..290e3d3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,12 +1,21 @@ -Copyright (C) 2015-2021 Tavian Barnes - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +Copyright © 2015-2023 Tavian Barnes and the bfs contributors + +Permission to use, copy, modify, and/or distribute this software for any purpose with or +without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT +SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR +ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE +USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +bfs is licensed under the Zero Clause BSD License. Individual files contain the following +tag instead of the full license text: + + SPDX-License-Identifier: 0BSD + +This enables machine processing of license information based on the SPDX License +Identifiers that are available here: https://spdx.org/licenses/ diff --git a/Makefile b/Makefile index b39a88a..0247b47 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,5 @@ -############################################################################ -# bfs # -# Copyright (C) 2015-2023 Tavian Barnes # -# # -# Permission to use, copy, modify, and/or distribute this software for any # -# purpose with or without fee is hereby granted. # -# # -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -############################################################################ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD ifneq ($(wildcard .git),) VERSION := $(shell git describe --always 2>/dev/null) diff --git a/completions/bfs.bash b/completions/bfs.bash index f734ab1..2f52e8d 100644 --- a/completions/bfs.bash +++ b/completions/bfs.bash @@ -1,21 +1,8 @@ -# bash completion script for bfs +# Copyright © Benjamin Mundt +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD -############################################################################ -# bfs # -# Copyright (C) 2020 Benjamin Mundt # -# Copyright (C) 2021 Tavian Barnes # -# # -# Permission to use, copy, modify, and/or distribute this software for any # -# purpose with or without fee is hereby granted. # -# # -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -############################################################################ +# bash completion script for bfs _bfs() { local cur prev words cword diff --git a/src/bar.c b/src/bar.c index 37d33c8..bd5d381 100644 --- a/src/bar.c +++ b/src/bar.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "bar.h" #include "bfstd.h" diff --git a/src/bar.h b/src/bar.h index 3e509d6..20d92a9 100644 --- a/src/bar.h +++ b/src/bar.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * A terminal status bar. diff --git a/src/bfstd.c b/src/bfstd.c index 3a37250..437e9c9 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2016-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "bfstd.h" #include "config.h" diff --git a/src/bfstd.h b/src/bfstd.h index 79307cc..0e11b66 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2016-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * Standard library wrappers and polyfills. diff --git a/src/bftw.c b/src/bftw.c index 5f3ebde..9feca79 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * The bftw() implementation consists of the following components: diff --git a/src/bftw.h b/src/bftw.h index c458e1b..77697ed 100644 --- a/src/bftw.h +++ b/src/bftw.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2021 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * A file-walking API based on nftw(). diff --git a/src/color.c b/src/color.c index 7c16ec5..589e631 100644 --- a/src/color.c +++ b/src/color.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "color.h" #include "bfstd.h" diff --git a/src/color.h b/src/color.h index 5b350cc..737c34b 100644 --- a/src/color.h +++ b/src/color.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2021 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * Utilities for colored output on ANSI terminals. diff --git a/src/config.h b/src/config.h index 810f913..5ae9c82 100644 --- a/src/config.h +++ b/src/config.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * Configuration and feature/platform detection. diff --git a/src/ctx.c b/src/ctx.c index 0403299..ff4a2a7 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "ctx.h" #include "color.h" diff --git a/src/ctx.h b/src/ctx.h index 6755d02..4c748b7 100644 --- a/src/ctx.h +++ b/src/ctx.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * bfs execution context. diff --git a/src/darray.c b/src/darray.c index 6585d30..42b8397 100644 --- a/src/darray.c +++ b/src/darray.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2019-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "darray.h" #include diff --git a/src/darray.h b/src/darray.h index 4464381..cc6cc42 100644 --- a/src/darray.h +++ b/src/darray.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2019-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * A dynamic array library. diff --git a/src/diag.c b/src/diag.c index b02473a..53db98e 100644 --- a/src/diag.c +++ b/src/diag.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2019-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "diag.h" #include "bfstd.h" diff --git a/src/diag.h b/src/diag.h index 8d0b19f..2952e30 100644 --- a/src/diag.h +++ b/src/diag.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2019-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * Formatters for diagnostic messages. diff --git a/src/dir.c b/src/dir.c index 2081cc5..3d18eb2 100644 --- a/src/dir.c +++ b/src/dir.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2021-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "dir.h" #include "bfstd.h" diff --git a/src/dir.h b/src/dir.h index 69344c6..01eaaba 100644 --- a/src/dir.h +++ b/src/dir.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2021 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * Directories and their contents. diff --git a/src/dstring.c b/src/dstring.c index f344d09..9112e54 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2016-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "dstring.h" #include diff --git a/src/dstring.h b/src/dstring.h index df20a04..ee3b345 100644 --- a/src/dstring.h +++ b/src/dstring.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2016-2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * A dynamic string library. diff --git a/src/eval.c b/src/eval.c index d297af1..53ce605 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * Implementation of all the primary expressions. diff --git a/src/eval.h b/src/eval.h index a50dc4e..3d70319 100644 --- a/src/eval.h +++ b/src/eval.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * The evaluation functions that implement primary expressions like -name, diff --git a/src/exec.c b/src/exec.c index 8630469..6bde1c1 100644 --- a/src/exec.c +++ b/src/exec.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2017-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "exec.h" #include "bfstd.h" diff --git a/src/exec.h b/src/exec.h index a3e3c71..9d4192d 100644 --- a/src/exec.h +++ b/src/exec.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2017-2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * Implementation of -exec/-execdir/-ok/-okdir. diff --git a/src/expr.h b/src/expr.h index a52007a..1628cac 100644 --- a/src/expr.h +++ b/src/expr.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * The expression tree representation. diff --git a/src/fsade.c b/src/fsade.c index 45969d1..a61a6b8 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2019-2021 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "fsade.h" #include "config.h" diff --git a/src/fsade.h b/src/fsade.h index f45c6fd..9bef892 100644 --- a/src/fsade.h +++ b/src/fsade.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2019-2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * A facade over (file)system features that are (un)implemented differently diff --git a/src/main.c b/src/main.c index fbddbe5..4f99580 100644 --- a/src/main.c +++ b/src/main.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * - main(): the entry point for bfs(1), a breadth-first version of find(1) diff --git a/src/mtab.c b/src/mtab.c index 07d7a53..ae6dfd2 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2017-2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "mtab.h" #include "bfstd.h" diff --git a/src/mtab.h b/src/mtab.h index 807539d..5dfdf6c 100644 --- a/src/mtab.h +++ b/src/mtab.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2017-2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * A facade over platform-specific APIs for enumerating mounted filesystems. diff --git a/src/opt.c b/src/opt.c index e76e216..5505b7b 100644 --- a/src/opt.c +++ b/src/opt.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2017-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * The expression optimizer. Different optimization levels are supported: diff --git a/src/opt.h b/src/opt.h index 5f8180d..28cadb9 100644 --- a/src/opt.h +++ b/src/opt.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * Optimization. diff --git a/src/parse.c b/src/parse.c index f2582e0..30bf56d 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * The command line parser. Expressions are parsed by recursive descent, with a diff --git a/src/parse.h b/src/parse.h index 7e29a03..6895c9f 100644 --- a/src/parse.h +++ b/src/parse.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * bfs command line parsing. diff --git a/src/printf.c b/src/printf.c index 7c0c8db..1b4f2d4 100644 --- a/src/printf.c +++ b/src/printf.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2017-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "printf.h" #include "bfstd.h" diff --git a/src/printf.h b/src/printf.h index a8c5f2a..2bff087 100644 --- a/src/printf.h +++ b/src/printf.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2017-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * Implementation of -printf/-fprintf. diff --git a/src/pwcache.c b/src/pwcache.c index 868ec8f..5026dee 100644 --- a/src/pwcache.c +++ b/src/pwcache.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "pwcache.h" #include "darray.h" diff --git a/src/pwcache.h b/src/pwcache.h index f1ca0bf..b6c0b67 100644 --- a/src/pwcache.h +++ b/src/pwcache.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * A caching wrapper for /etc/{passwd,group}. diff --git a/src/stat.c b/src/stat.c index 94dedef..aaa5eac 100644 --- a/src/stat.c +++ b/src/stat.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2018-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "stat.h" #include "bfstd.h" diff --git a/src/stat.h b/src/stat.h index 44cbfad..7a70146 100644 --- a/src/stat.h +++ b/src/stat.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2018-2019 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * A facade over the stat() API that unifies some details that diverge between diff --git a/src/trie.c b/src/trie.c index 08a99b5..77c43cc 100644 --- a/src/trie.c +++ b/src/trie.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2019-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * This is an implementation of a "qp trie," as documented at diff --git a/src/trie.h b/src/trie.h index 2ede6ea..03ee64d 100644 --- a/src/trie.h +++ b/src/trie.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2019-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #ifndef BFS_TRIE_H #define BFS_TRIE_H diff --git a/src/typo.c b/src/typo.c index c16cab4..305711d 100644 --- a/src/typo.c +++ b/src/typo.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2016 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "typo.h" #include diff --git a/src/typo.h b/src/typo.h index 0347aae..13eaa67 100644 --- a/src/typo.h +++ b/src/typo.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2016 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #ifndef BFS_TYPO_H #define BFS_TYPO_H diff --git a/src/xregex.c b/src/xregex.c index 6f0e5a1..5f5480f 100644 --- a/src/xregex.c +++ b/src/xregex.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "xregex.h" #include "config.h" diff --git a/src/xregex.h b/src/xregex.h index b2f56a5..998a2b0 100644 --- a/src/xregex.h +++ b/src/xregex.h @@ -1,19 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2022 Tavian Barnes and bfs * - * contributors * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes and the bfs contributors +// SPDX-License-Identifier: 0BSD #ifndef BFS_XREGEX_H #define BFS_XREGEX_H diff --git a/src/xspawn.c b/src/xspawn.c index f76267b..a30c264 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2018-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "xspawn.h" #include "bfstd.h" diff --git a/src/xspawn.h b/src/xspawn.h index cd6a42e..3dbf5d2 100644 --- a/src/xspawn.h +++ b/src/xspawn.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2018-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * A process-spawning library inspired by posix_spawn(). diff --git a/src/xtime.c b/src/xtime.c index 079d42a..82690d0 100644 --- a/src/xtime.c +++ b/src/xtime.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "xtime.h" #include diff --git a/src/xtime.h b/src/xtime.h index b49cd04..75d1f4e 100644 --- a/src/xtime.h +++ b/src/xtime.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * Date/time handling. diff --git a/tests/bfstd.c b/tests/bfstd.c index 4a8181b..8c61072 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "../src/bfstd.h" #include diff --git a/tests/find-color.sh b/tests/find-color.sh index ecdd5af..47de2a2 100755 --- a/tests/find-color.sh +++ b/tests/find-color.sh @@ -1,20 +1,7 @@ #!/usr/bin/env bash -############################################################################ -# bfs # -# Copyright (C) 2019 Tavian Barnes # -# # -# Permission to use, copy, modify, and/or distribute this software for any # -# purpose with or without fee is hereby granted. # -# # -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -############################################################################ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD set -e diff --git a/tests/ls-color.sh b/tests/ls-color.sh index c82a58d..6d33f53 100755 --- a/tests/ls-color.sh +++ b/tests/ls-color.sh @@ -1,20 +1,7 @@ #!/usr/bin/env bash -############################################################################ -# bfs # -# Copyright (C) 2019 Tavian Barnes # -# # -# Permission to use, copy, modify, and/or distribute this software for any # -# purpose with or without fee is hereby granted. # -# # -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -############################################################################ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD # Prints the "ground truth" coloring of a path using ls diff --git a/tests/mksock.c b/tests/mksock.c index 5068bc8..05edbeb 100644 --- a/tests/mksock.c +++ b/tests/mksock.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2019 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * There's no standard Unix utility that creates a socket file, so this small diff --git a/tests/tests.sh b/tests/tests.sh index e57db4e..98d332c 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -1,20 +1,7 @@ #!/usr/bin/env bash -############################################################################ -# bfs # -# Copyright (C) 2015-2022 Tavian Barnes # -# # -# Permission to use, copy, modify, and/or distribute this software for any # -# purpose with or without fee is hereby granted. # -# # -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -############################################################################ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD set -euP umask 022 diff --git a/tests/trie.c b/tests/trie.c index 88e92da..c2af18a 100644 --- a/tests/trie.c +++ b/tests/trie.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #undef NDEBUG diff --git a/tests/xtimegm.c b/tests/xtimegm.c index d774b9e..bab64ba 100644 --- a/tests/xtimegm.c +++ b/tests/xtimegm.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "../src/xtime.h" #include diff --git a/tests/xtouch.c b/tests/xtouch.c index 9a91ec7..506c73d 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "../src/bfstd.h" #include "../src/xtime.h" -- cgit v1.2.3 From e40855d3b98aa3f65c19608b3d8c70f54b714063 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 28 Mar 2023 10:41:39 -0400 Subject: bftw: Refactor bftw_queue --- src/bftw.c | 143 ++++++++++++++++++++++++------------------------------------- 1 file changed, 55 insertions(+), 88 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 9feca79..fdc6be6 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -10,8 +10,7 @@ * - struct bftw_cache: An LRU list of bftw_file's with open file descriptors, * used for openat() to minimize the amount of path re-traversals. * - * - struct bftw_queue: The queue of bftw_file's left to explore. Implemented - * as a simple circular buffer. + * - struct bftw_queue: A linked list of bftw_file's left to explore. * * - struct bftw_state: Represents the current state of the traversal, allowing * various helper functions to take fewer parameters. @@ -395,28 +394,33 @@ static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) { } /** - * A queue of bftw_file's to examine. + * A queue of bftw_file's. */ struct bftw_queue { - /** The head of the queue. */ struct bftw_file *head; - /** The insertion target. */ - struct bftw_file **target; + struct bftw_file **tail; }; /** Initialize a bftw_queue. */ static void bftw_queue_init(struct bftw_queue *queue) { queue->head = NULL; - queue->target = &queue->head; + queue->tail = &queue->head; } /** Add a file to a bftw_queue. */ static void bftw_queue_push(struct bftw_queue *queue, struct bftw_file *file) { assert(file->next == NULL); + *queue->tail = file; + queue->tail = &file->next; +} - file->next = *queue->target; - *queue->target = file; - queue->target = &file->next; +/** Append a whole queue to the tail of another. */ +static void bftw_queue_extend(struct bftw_queue *dest, struct bftw_queue *src) { + if (src->head) { + *dest->tail = src->head; + dest->tail = src->tail; + bftw_queue_init(src); + } } /** Pop the next file from the head of the queue. */ @@ -424,71 +428,42 @@ static struct bftw_file *bftw_queue_pop(struct bftw_queue *queue) { struct bftw_file *file = queue->head; queue->head = file->next; file->next = NULL; - if (queue->target == &file->next) { - queue->target = &queue->head; + if (!queue->head) { + queue->tail = &queue->head; } return file; } -/** The split phase of mergesort. */ -static struct bftw_file **bftw_sort_split(struct bftw_file **head, struct bftw_file **tail) { - struct bftw_file **tortoise = head, **hare = head; - - while (*hare != *tail) { - tortoise = &(*tortoise)->next; - hare = &(*hare)->next; - if (*hare != *tail) { - hare = &(*hare)->next; - } +/** Sort a queue by filename. */ +static void bftw_queue_sort(struct bftw_queue *queue) { + if (!queue->head || !queue->head->next) { + return; } - return tortoise; -} + struct bftw_queue left, right; + bftw_queue_init(&left); + bftw_queue_init(&right); -/** The merge phase of mergesort. */ -static struct bftw_file **bftw_sort_merge(struct bftw_file **head, struct bftw_file **mid, struct bftw_file **tail) { - struct bftw_file *left = *head, *right = *mid, *end = *tail; - *mid = NULL; - *tail = NULL; - - while (left || right) { - struct bftw_file *next; - if (left && (!right || strcoll(left->name, right->name) <= 0)) { - next = left; - left = left->next; - } else { - next = right; - right = right->next; - } - - *head = next; - head = &next->next; + // Split + for (struct bftw_file *hare = queue->head; hare && (hare = hare->next); hare = hare->next) { + bftw_queue_push(&left, bftw_queue_pop(queue)); } + bftw_queue_extend(&right, queue); - *head = end; - return head; -} + // Recurse + bftw_queue_sort(&left); + bftw_queue_sort(&right); -/** - * Sort a (sub-)list of files. - * - * @param head - * The head of the (sub-)list to sort. - * @param tail - * The tail of the (sub-)list to sort. - * @return - * The new tail of the (sub-)list. - */ -static struct bftw_file **bftw_sort_files(struct bftw_file **head, struct bftw_file **tail) { - struct bftw_file **mid = bftw_sort_split(head, tail); - if (*mid == *head || *mid == *tail) { - return tail; + // Merge + while (left.head && right.head) { + if (strcoll(left.head->name, right.head->name) <= 0) { + bftw_queue_push(queue, bftw_queue_pop(&left)); + } else { + bftw_queue_push(queue, bftw_queue_pop(&right)); + } } - - mid = bftw_sort_files(head, mid); - tail = bftw_sort_files(mid, tail); - - return bftw_sort_merge(head, mid, tail); + bftw_queue_extend(queue, &left); + bftw_queue_extend(queue, &right); } /** @@ -511,10 +486,10 @@ struct bftw_state { /** The cache of open directories. */ struct bftw_cache cache; - /** The queue of directories left to explore. */ + /** The queue of files left to explore. */ struct bftw_queue queue; - /** The start of the current batch of files. */ - struct bftw_file **batch; + /** A batch of files to enqueue. */ + struct bftw_queue batch; /** The current path. */ char *path; @@ -560,7 +535,7 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg bftw_cache_init(&state->cache, args->nopenfd); bftw_queue_init(&state->queue); - state->batch = NULL; + bftw_queue_init(&state->batch); state->file = NULL; state->previous = NULL; @@ -920,8 +895,7 @@ static int bftw_push(struct bftw_state *state, const char *name, bool fill_id) { bftw_fill_id(file, &state->ftwbuf); } - bftw_queue_push(&state->queue, file); - + bftw_queue_push(&state->batch, file); return 0; } @@ -1101,11 +1075,11 @@ static enum bftw_action bftw_gc_file(struct bftw_state *state, enum bftw_gc_flag } /** - * Drain all the entries from a bftw_queue. + * Drain all the files from the queue. */ -static void bftw_drain_queue(struct bftw_state *state, struct bftw_queue *queue) { - while (queue->head) { - state->file = bftw_queue_pop(queue); +static void bftw_drain_queue(struct bftw_state *state) { + while (state->queue.head) { + state->file = bftw_queue_pop(&state->queue); bftw_gc_file(state, BFTW_VISIT_NONE); } } @@ -1122,7 +1096,7 @@ static int bftw_state_destroy(struct bftw_state *state) { bftw_closedir(state, BFTW_VISIT_NONE); bftw_gc_file(state, BFTW_VISIT_NONE); - bftw_drain_queue(state, &state->queue); + bftw_drain_queue(state); bftw_cache_destroy(&state->cache); @@ -1130,19 +1104,16 @@ static int bftw_state_destroy(struct bftw_state *state) { return state->error ? -1 : 0; } -/** Start a batch of files. */ -static void bftw_batch_start(struct bftw_state *state) { - if (state->strategy == BFTW_DFS) { - state->queue.target = &state->queue.head; - } - state->batch = state->queue.target; -} - /** Finish adding a batch of files. */ static void bftw_batch_finish(struct bftw_state *state) { if (state->flags & BFTW_SORT) { - state->queue.target = bftw_sort_files(state->batch, state->queue.target); + bftw_queue_sort(&state->batch); + } + + if (state->strategy == BFTW_DFS) { + bftw_queue_extend(&state->batch, &state->queue); } + bftw_queue_extend(&state->queue, &state->batch); } /** @@ -1156,7 +1127,6 @@ static int bftw_stream(const struct bftw_args *args) { assert(!(state.flags & (BFTW_SORT | BFTW_BUFFER))); - bftw_batch_start(&state); for (size_t i = 0; i < args->npaths; ++i) { const char *path = args->paths[i]; @@ -1178,7 +1148,6 @@ static int bftw_stream(const struct bftw_args *args) { while (bftw_pop(&state) > 0) { bftw_opendir(&state); - bftw_batch_start(&state); while (bftw_readdir(&state) > 0) { const char *name = state.de->name; @@ -1218,7 +1187,6 @@ static int bftw_batch(const struct bftw_args *args) { return -1; } - bftw_batch_start(&state); for (size_t i = 0; i < args->npaths; ++i) { if (bftw_push(&state, args->paths[i], false) != 0) { goto done; @@ -1241,7 +1209,6 @@ static int bftw_batch(const struct bftw_args *args) { bftw_opendir(&state); - bftw_batch_start(&state); while (bftw_readdir(&state) > 0) { if (bftw_push(&state, state.de->name, false) != 0) { goto done; -- cgit v1.2.3 From 7888fbababd22190e9f919fc272957426a27969e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 28 Mar 2023 14:18:36 -0400 Subject: bftw: Use list.h for the queue and LRU lists --- src/bftw.c | 311 ++++++++++++++++++------------------------------------------- 1 file changed, 93 insertions(+), 218 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index fdc6be6..9ff526d 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -10,8 +10,6 @@ * - struct bftw_cache: An LRU list of bftw_file's with open file descriptors, * used for openat() to minimize the amount of path re-traversals. * - * - struct bftw_queue: A linked list of bftw_file's left to explore. - * * - struct bftw_state: Represents the current state of the traversal, allowing * various helper functions to take fewer parameters. */ @@ -21,6 +19,7 @@ #include "config.h" #include "dir.h" #include "dstring.h" +#include "list.h" #include "mtab.h" #include "stat.h" #include "trie.h" @@ -40,13 +39,11 @@ struct bftw_file { struct bftw_file *parent; /** The root under which this file was found. */ struct bftw_file *root; - /** The next file in the queue, if any. */ - struct bftw_file *next; - /** The previous file in the LRU list. */ - struct bftw_file *lru_prev; - /** The next file in the LRU list. */ - struct bftw_file *lru_next; + /** Queue link. */ + struct slink link; + /** LRU link. */ + struct link lru; /** This file's depth in the walk. */ size_t depth; @@ -71,143 +68,89 @@ struct bftw_file { char name[]; }; +/** Move from a list entry to a bftw_file. */ +static struct bftw_file *bftw_file_entry(struct slink *link) { + return BFS_CONTAINER_OF(link, struct bftw_file, link); +} + +/** Move from an LRU entry to a bftw_file. */ +static struct bftw_file *bftw_lru_file(struct link *link) { + return BFS_CONTAINER_OF(link, struct bftw_file, lru); +} + /** * A cache of open directories. */ struct bftw_cache { - /** The head of the LRU list. */ - struct bftw_file *head; + /** The LRU list. */ + struct list list; /** The insertion target for the LRU list. */ - struct bftw_file *target; - /** The tail of the LRU list. */ - struct bftw_file *tail; + struct link *target; /** The remaining capacity of the LRU list. */ size_t capacity; }; /** Initialize a cache. */ static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) { - cache->head = NULL; + list_init(&cache->list); cache->target = NULL; - cache->tail = NULL; cache->capacity = capacity; } -/** Destroy a cache. */ -static void bftw_cache_destroy(struct bftw_cache *cache) { - assert(!cache->tail); - assert(!cache->target); - assert(!cache->head); -} - -/** Add a bftw_file to the cache. */ -static void bftw_cache_add(struct bftw_cache *cache, struct bftw_file *file) { - assert(cache->capacity > 0); - assert(file->fd >= 0); - assert(!file->lru_prev); - assert(!file->lru_next); - - if (cache->target) { - file->lru_prev = cache->target; - file->lru_next = cache->target->lru_next; - } else { - file->lru_next = cache->head; - } - - if (file->lru_prev) { - file->lru_prev->lru_next = file; - } else { - cache->head = file; - } - - if (file->lru_next) { - file->lru_next->lru_prev = file; - } else { - cache->tail = file; - } - - // Prefer to keep the root paths open by keeping them at the head of the list - if (file->depth == 0) { - cache->target = file; - } - - --cache->capacity; -} - /** Remove a bftw_file from the cache. */ static void bftw_cache_remove(struct bftw_cache *cache, struct bftw_file *file) { - if (cache->target == file) { - cache->target = file->lru_prev; + if (cache->target == &file->lru) { + cache->target = cache->target->prev; } - if (file->lru_prev) { - assert(cache->head != file); - file->lru_prev->lru_next = file->lru_next; - } else { - assert(cache->head == file); - cache->head = file->lru_next; - } - - if (file->lru_next) { - assert(cache->tail != file); - file->lru_next->lru_prev = file->lru_prev; - } else { - assert(cache->tail == file); - cache->tail = file->lru_prev; - } + list_remove(&cache->list, &file->lru); - file->lru_prev = NULL; - file->lru_next = NULL; ++cache->capacity; } -/** Mark a cache entry as recently used. */ -static void bftw_cache_use(struct bftw_cache *cache, struct bftw_file *file) { - bftw_cache_remove(cache, file); - bftw_cache_add(cache, file); -} - /** Close a bftw_file. */ static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) { assert(file->fd >= 0); - bftw_cache_remove(cache, file); + if (list_attached(&cache->list, &file->lru)) { + bftw_cache_remove(cache, file); + } xclose(file->fd); file->fd = -1; } -/** Pop a directory from the cache. */ -static void bftw_cache_pop(struct bftw_cache *cache) { - assert(cache->tail); - bftw_file_close(cache, cache->tail); +/** Pop the least recently used directory from the cache. */ +static int bftw_cache_pop(struct bftw_cache *cache) { + if (list_is_empty(&cache->list)) { + return -1; + } + + struct bftw_file *file = bftw_lru_file(cache->list.tail); + bftw_file_close(cache, file); + return 0; } -/** - * Shrink the cache, to recover from EMFILE. - * - * @param cache - * The cache in question. - * @param saved - * A bftw_file that must be preserved. - * @return - * 0 if successfully shrunk, otherwise -1. - */ -static int bftw_cache_shrink(struct bftw_cache *cache, const struct bftw_file *saved) { - struct bftw_file *file = cache->tail; - if (!file) { +/** Add a bftw_file to the cache. */ +static int bftw_cache_add(struct bftw_cache *cache, struct bftw_file *file) { + assert(file->fd >= 0); + + if (cache->capacity == 0 && bftw_cache_pop(cache) != 0) { + bftw_file_close(cache, file); + errno = EMFILE; return -1; } - if (file == saved) { - file = file->lru_prev; - if (!file) { - return -1; - } + assert(cache->capacity > 0); + --cache->capacity; + + list_insert_after(&cache->list, cache->target, &file->lru); + + // Prefer to keep the root paths open by keeping them at the head of the list + if (file->depth == 0) { + cache->target = &file->lru; } - bftw_file_close(cache, file); - cache->capacity = 0; return 0; } @@ -220,6 +163,12 @@ static size_t bftw_child_nameoff(const struct bftw_file *parent) { return ret; } +/** Destroy a cache. */ +static void bftw_cache_destroy(struct bftw_cache *cache) { + assert(!cache->target); + assert(list_is_empty(&cache->list)); +} + /** Create a new bftw_file. */ static struct bftw_file *bftw_file_new(struct bftw_file *parent, const char *name) { size_t namelen = strlen(name); @@ -243,10 +192,8 @@ static struct bftw_file *bftw_file_new(struct bftw_file *parent, const char *nam file->nameoff = 0; } - file->next = NULL; - - file->lru_prev = NULL; - file->lru_next = NULL; + slink_init(&file->link); + link_init(&file->lru); file->refcount = 1; file->fd = -1; @@ -282,7 +229,8 @@ static int bftw_file_openat(struct bftw_cache *cache, struct bftw_file *file, st int at_fd = AT_FDCWD; if (base) { - bftw_cache_use(cache, base); + // Remove base from the cache temporarily so it stays open + bftw_cache_remove(cache, base); at_fd = base->fd; } @@ -290,21 +238,22 @@ static int bftw_file_openat(struct bftw_cache *cache, struct bftw_file *file, st int fd = openat(at_fd, at_path, flags); if (fd < 0 && errno == EMFILE) { - if (bftw_cache_shrink(cache, base) == 0) { + if (bftw_cache_pop(cache) == 0) { fd = openat(at_fd, at_path, flags); } + cache->capacity = 1; } - if (fd >= 0) { - if (cache->capacity == 0) { - bftw_cache_pop(cache); - } + if (base) { + bftw_cache_add(cache, base); + } + if (fd >= 0) { file->fd = fd; bftw_cache_add(cache, file); } - return fd; + return file->fd; } /** @@ -337,28 +286,19 @@ static int bftw_file_open(struct bftw_cache *cache, struct bftw_file *file, cons } // Handle ENAMETOOLONG by manually traversing the path component-by-component - - // Use the ->next linked list to temporarily hold the reversed parent - // chain between base and file - struct bftw_file *cur; - for (cur = file; cur->parent != base; cur = cur->parent) { - cur->parent->next = cur; + struct slist parents; + slist_init(&parents); + for (struct bftw_file *cur = file; cur != base; cur = cur->parent) { + slist_prepend(&parents, &cur->link); } - // Open the files in the chain one by one - for (base = cur; base; base = base->next) { - fd = bftw_file_openat(cache, base, base->parent, base->name); - if (fd < 0 || base == file) { - break; + LIST_DRAIN(&parents, struct bftw_file, cur) { + if (!cur->parent || cur->parent->fd >= 0) { + bftw_file_openat(cache, cur, cur->parent, cur->name); } } - // Clear out the linked list - for (struct bftw_file *next = cur->next; cur != file; cur = next, next = next->next) { - cur->next = NULL; - } - - return fd; + return file->fd; } /** @@ -393,79 +333,6 @@ static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) { free(file); } -/** - * A queue of bftw_file's. - */ -struct bftw_queue { - struct bftw_file *head; - struct bftw_file **tail; -}; - -/** Initialize a bftw_queue. */ -static void bftw_queue_init(struct bftw_queue *queue) { - queue->head = NULL; - queue->tail = &queue->head; -} - -/** Add a file to a bftw_queue. */ -static void bftw_queue_push(struct bftw_queue *queue, struct bftw_file *file) { - assert(file->next == NULL); - *queue->tail = file; - queue->tail = &file->next; -} - -/** Append a whole queue to the tail of another. */ -static void bftw_queue_extend(struct bftw_queue *dest, struct bftw_queue *src) { - if (src->head) { - *dest->tail = src->head; - dest->tail = src->tail; - bftw_queue_init(src); - } -} - -/** Pop the next file from the head of the queue. */ -static struct bftw_file *bftw_queue_pop(struct bftw_queue *queue) { - struct bftw_file *file = queue->head; - queue->head = file->next; - file->next = NULL; - if (!queue->head) { - queue->tail = &queue->head; - } - return file; -} - -/** Sort a queue by filename. */ -static void bftw_queue_sort(struct bftw_queue *queue) { - if (!queue->head || !queue->head->next) { - return; - } - - struct bftw_queue left, right; - bftw_queue_init(&left); - bftw_queue_init(&right); - - // Split - for (struct bftw_file *hare = queue->head; hare && (hare = hare->next); hare = hare->next) { - bftw_queue_push(&left, bftw_queue_pop(queue)); - } - bftw_queue_extend(&right, queue); - - // Recurse - bftw_queue_sort(&left); - bftw_queue_sort(&right); - - // Merge - while (left.head && right.head) { - if (strcoll(left.head->name, right.head->name) <= 0) { - bftw_queue_push(queue, bftw_queue_pop(&left)); - } else { - bftw_queue_push(queue, bftw_queue_pop(&right)); - } - } - bftw_queue_extend(queue, &left); - bftw_queue_extend(queue, &right); -} - /** * Holds the current state of the bftw() traversal. */ @@ -487,9 +354,9 @@ struct bftw_state { /** The cache of open directories. */ struct bftw_cache cache; /** The queue of files left to explore. */ - struct bftw_queue queue; + struct slist queue; /** A batch of files to enqueue. */ - struct bftw_queue batch; + struct slist batch; /** The current path. */ char *path; @@ -534,8 +401,9 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg } bftw_cache_init(&state->cache, args->nopenfd); - bftw_queue_init(&state->queue); - bftw_queue_init(&state->batch); + + slist_init(&state->queue); + slist_init(&state->batch); state->file = NULL; state->previous = NULL; @@ -895,7 +763,7 @@ static int bftw_push(struct bftw_state *state, const char *name, bool fill_id) { bftw_fill_id(file, &state->ftwbuf); } - bftw_queue_push(&state->batch, file); + slist_append(&state->batch, &file->link); return 0; } @@ -942,7 +810,7 @@ static int bftw_pop(struct bftw_state *state) { return 0; } - state->file = bftw_queue_pop(&state->queue); + state->file = bftw_file_entry(slist_pop(&state->queue)); if (bftw_build_path(state) != 0) { return -1; @@ -1078,8 +946,8 @@ static enum bftw_action bftw_gc_file(struct bftw_state *state, enum bftw_gc_flag * Drain all the files from the queue. */ static void bftw_drain_queue(struct bftw_state *state) { - while (state->queue.head) { - state->file = bftw_queue_pop(&state->queue); + LIST_DRAIN(&state->queue, struct bftw_file, file) { + state->file = file; bftw_gc_file(state, BFTW_VISIT_NONE); } } @@ -1104,16 +972,23 @@ static int bftw_state_destroy(struct bftw_state *state) { return state->error ? -1 : 0; } +/** Comparison function for BFTW_SORT. */ +static bool bftw_file_cmp(struct slink *left, struct slink *right, const void *ptr) { + struct bftw_file *lfile = bftw_file_entry(left); + struct bftw_file *rfile = bftw_file_entry(right); + return strcoll(lfile->name, rfile->name) <= 0; +} + /** Finish adding a batch of files. */ static void bftw_batch_finish(struct bftw_state *state) { if (state->flags & BFTW_SORT) { - bftw_queue_sort(&state->batch); + slist_sort(&state->batch, bftw_file_cmp, NULL); } if (state->strategy == BFTW_DFS) { - bftw_queue_extend(&state->batch, &state->queue); + slist_extend(&state->batch, &state->queue); } - bftw_queue_extend(&state->queue, &state->batch); + slist_extend(&state->queue, &state->batch); } /** -- cgit v1.2.3 From f75f3de63888702f29f48bcf2691291403720b9d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 29 Mar 2023 13:25:18 -0400 Subject: list: New helper macros for converting entries to items --- src/bftw.c | 18 +++++------------- src/list.h | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 65 insertions(+), 16 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 9ff526d..aa36b87 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -69,14 +69,8 @@ struct bftw_file { }; /** Move from a list entry to a bftw_file. */ -static struct bftw_file *bftw_file_entry(struct slink *link) { - return BFS_CONTAINER_OF(link, struct bftw_file, link); -} - -/** Move from an LRU entry to a bftw_file. */ -static struct bftw_file *bftw_lru_file(struct link *link) { - return BFS_CONTAINER_OF(link, struct bftw_file, lru); -} +#define BFTW_FILE(...) \ + LIST_ITEM(struct bftw_file, __VA_ARGS__) /** * A cache of open directories. @@ -126,7 +120,7 @@ static int bftw_cache_pop(struct bftw_cache *cache) { return -1; } - struct bftw_file *file = bftw_lru_file(cache->list.tail); + struct bftw_file *file = BFTW_FILE(cache->list.tail, lru); bftw_file_close(cache, file); return 0; } @@ -810,7 +804,7 @@ static int bftw_pop(struct bftw_state *state) { return 0; } - state->file = bftw_file_entry(slist_pop(&state->queue)); + state->file = BFTW_FILE(slist_pop(&state->queue)); if (bftw_build_path(state) != 0) { return -1; @@ -974,9 +968,7 @@ static int bftw_state_destroy(struct bftw_state *state) { /** Comparison function for BFTW_SORT. */ static bool bftw_file_cmp(struct slink *left, struct slink *right, const void *ptr) { - struct bftw_file *lfile = bftw_file_entry(left); - struct bftw_file *rfile = bftw_file_entry(right); - return strcoll(lfile->name, rfile->name) <= 0; + return strcoll(BFTW_FILE(left)->name, BFTW_FILE(right)->name) <= 0; } /** Finish adding a batch of files. */ diff --git a/src/list.h b/src/list.h index 1985413..b47bed7 100644 --- a/src/list.h +++ b/src/list.h @@ -107,10 +107,67 @@ struct link *list_pop(struct list *list); /** Check if a link is attached to a list. */ bool list_attached(const struct list *list, const struct link *link); +// LIST_ITEM() helper +#define LIST_ITEM_IMPL(type, entry, member, ...) \ + BFS_CONTAINER_OF(entry, type, member) + +/** + * Convert a list entry to its container. + * + * @param type + * The type of the list entries. + * @param entry + * The list entry to convert. + * @param member + * The name of the list link field (default: link). + * @return + * The item that contains the given entry. + */ +#define LIST_ITEM(...) \ + LIST_ITEM_IMPL(__VA_ARGS__, link,) + +// LIST_NEXT() helper +#define LIST_NEXT_IMPL(type, entry, member, ...) \ + LIST_ITEM(type, (entry)->member.next, member) + +/** + * Get the next item in a list. + * + * @param type + * The type of the list entries. + * @param entry + * The current entry. + * @param member + * The name of the list link field (default: link). + * @return + * The next item in the list. + */ +#define LIST_NEXT(...) \ + LIST_NEXT_IMPL(__VA_ARGS__, link,) + +// LIST_PREV() helper +#define LIST_PREV_IMPL(type, entry, member, ...) \ + LIST_ITEM(type, (entry)->member.prev, member) + +/** + * Get the previous entry in a list. + * + * @param type + * The type of the list entries. + * @param entry + * The current entry. + * @param member + * The name of the list link field (default: link). + * @return + * The previous item in the list. + */ +#define LIST_PREV(...) \ + LIST_PREV_IMPL(__VA_ARGS__, link,) + // Helper for LIST_FOR_EACH_*() #define LIST_FOR_EACH_IMPL(entry, type, i, member, ...) \ - for (type *_next, *i = BFS_CONTAINER_OF(entry, type, member); \ - i && (_next = BFS_CONTAINER_OF(i->member.next, type, member), true); \ + for (type *_next, *i = LIST_ITEM(type, entry, member); \ + i && (_next = LIST_NEXT(type, i, member), true); \ i = _next) /** @@ -150,7 +207,7 @@ bool list_attached(const struct list *list, const struct link *link); // Helper for LIST_DRAIN() #define LIST_DRAIN_IMPL(list, type, i, member, ...) \ - for (type *i; (i = BFS_CONTAINER_OF(LIST_POP(list), type, member));) + for (type *i; (i = LIST_ITEM(type, LIST_POP(list), member));) /** * Drain the entries from a list. -- cgit v1.2.3 From 86f4b4f7180bca73a734249e67dada708f8275ff Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 31 Mar 2023 16:19:45 -0400 Subject: list: Use macros instead of type-erased lists --- Makefile | 1 - src/bftw.c | 136 ++++++++++++------ src/config.h | 11 -- src/list.c | 169 ---------------------- src/list.h | 447 ++++++++++++++++++++++++++++++++++++++--------------------- src/trie.c | 8 +- src/trie.h | 9 +- src/xspawn.c | 15 +- src/xspawn.h | 4 +- tests/trie.c | 2 + 10 files changed, 403 insertions(+), 399 deletions(-) delete mode 100644 src/list.c (limited to 'src/bftw.c') diff --git a/Makefile b/Makefile index 428a0f7..b509065 100644 --- a/Makefile +++ b/Makefile @@ -223,7 +223,6 @@ LIBBFS := \ $(OBJ)/src/eval.o \ $(OBJ)/src/exec.o \ $(OBJ)/src/fsade.o \ - $(OBJ)/src/list.o \ $(OBJ)/src/mtab.o \ $(OBJ)/src/opt.o \ $(OBJ)/src/parse.o \ diff --git a/src/bftw.c b/src/bftw.c index aa36b87..7bc724a 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -7,6 +7,8 @@ * - struct bftw_file: A file that has been encountered during the traversal. * They have reference-counted links to their parents in the directory tree. * + * - struct bftw_list: A linked list of bftw_file's. + * * - struct bftw_cache: An LRU list of bftw_file's with open file descriptors, * used for openat() to minimize the amount of path re-traversals. * @@ -39,11 +41,14 @@ struct bftw_file { struct bftw_file *parent; /** The root under which this file was found. */ struct bftw_file *root; + /** The next file in the queue, if any. */ + struct bftw_file *next; - /** Queue link. */ - struct slink link; - /** LRU link. */ - struct link lru; + /** LRU list links. */ + struct { + struct bftw_file *prev; + struct bftw_file *next; + } lru; /** This file's depth in the walk. */ size_t depth; @@ -68,36 +73,42 @@ struct bftw_file { char name[]; }; -/** Move from a list entry to a bftw_file. */ -#define BFTW_FILE(...) \ - LIST_ITEM(struct bftw_file, __VA_ARGS__) +/** + * A linked list of bftw_file's. + */ +struct bftw_list { + struct bftw_file *head; + struct bftw_file **tail; +}; /** * A cache of open directories. */ struct bftw_cache { - /** The LRU list. */ - struct list list; + /** The head of the LRU list. */ + struct bftw_file *head; + /** The tail of the LRU list. */ + struct bftw_file *tail; /** The insertion target for the LRU list. */ - struct link *target; + struct bftw_file *target; /** The remaining capacity of the LRU list. */ size_t capacity; }; /** Initialize a cache. */ static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) { - list_init(&cache->list); + LIST_INIT(cache); cache->target = NULL; cache->capacity = capacity; } /** Remove a bftw_file from the cache. */ static void bftw_cache_remove(struct bftw_cache *cache, struct bftw_file *file) { - if (cache->target == &file->lru) { - cache->target = cache->target->prev; + if (cache->target == file) { + cache->target = file->lru.prev; } - list_remove(&cache->list, &file->lru); + LIST_REMOVE(cache, file, lru); ++cache->capacity; } @@ -106,7 +117,7 @@ static void bftw_cache_remove(struct bftw_cache *cache, struct bftw_file *file) static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) { assert(file->fd >= 0); - if (list_attached(&cache->list, &file->lru)) { + if (LIST_ATTACHED(cache, file, lru)) { bftw_cache_remove(cache, file); } @@ -116,11 +127,11 @@ static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) { /** Pop the least recently used directory from the cache. */ static int bftw_cache_pop(struct bftw_cache *cache) { - if (list_is_empty(&cache->list)) { + struct bftw_file *file = cache->tail; + if (!file) { return -1; } - struct bftw_file *file = BFTW_FILE(cache->list.tail, lru); bftw_file_close(cache, file); return 0; } @@ -138,11 +149,11 @@ static int bftw_cache_add(struct bftw_cache *cache, struct bftw_file *file) { assert(cache->capacity > 0); --cache->capacity; - list_insert_after(&cache->list, cache->target, &file->lru); + LIST_INSERT(cache, cache->target, file, lru); // Prefer to keep the root paths open by keeping them at the head of the list if (file->depth == 0) { - cache->target = &file->lru; + cache->target = file; } return 0; @@ -159,8 +170,9 @@ static size_t bftw_child_nameoff(const struct bftw_file *parent) { /** Destroy a cache. */ static void bftw_cache_destroy(struct bftw_cache *cache) { + assert(!cache->head); + assert(!cache->tail); assert(!cache->target); - assert(list_is_empty(&cache->list)); } /** Create a new bftw_file. */ @@ -186,8 +198,8 @@ static struct bftw_file *bftw_file_new(struct bftw_file *parent, const char *nam file->nameoff = 0; } - slink_init(&file->link); - link_init(&file->lru); + file->next = NULL; + file->lru.prev = file->lru.next = NULL; file->refcount = 1; file->fd = -1; @@ -280,16 +292,19 @@ static int bftw_file_open(struct bftw_cache *cache, struct bftw_file *file, cons } // Handle ENAMETOOLONG by manually traversing the path component-by-component - struct slist parents; - slist_init(&parents); - for (struct bftw_file *cur = file; cur != base; cur = cur->parent) { - slist_prepend(&parents, &cur->link); + struct bftw_list parents; + SLIST_INIT(&parents); + + struct bftw_file *cur; + for (cur = file; cur != base; cur = cur->parent) { + SLIST_PREPEND(&parents, cur); } - LIST_DRAIN(&parents, struct bftw_file, cur) { + while ((cur = parents.head)) { if (!cur->parent || cur->parent->fd >= 0) { bftw_file_openat(cache, cur, cur->parent, cur->name); } + SLIST_POP(&parents); } return file->fd; @@ -348,9 +363,9 @@ struct bftw_state { /** The cache of open directories. */ struct bftw_cache cache; /** The queue of files left to explore. */ - struct slist queue; + struct bftw_list queue; /** A batch of files to enqueue. */ - struct slist batch; + struct bftw_list batch; /** The current path. */ char *path; @@ -396,8 +411,8 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg bftw_cache_init(&state->cache, args->nopenfd); - slist_init(&state->queue); - slist_init(&state->batch); + SLIST_INIT(&state->queue); + SLIST_INIT(&state->batch); state->file = NULL; state->previous = NULL; @@ -757,7 +772,7 @@ static int bftw_push(struct bftw_state *state, const char *name, bool fill_id) { bftw_fill_id(file, &state->ftwbuf); } - slist_append(&state->batch, &file->link); + SLIST_APPEND(&state->batch, file); return 0; } @@ -800,11 +815,12 @@ static int bftw_build_path(struct bftw_state *state) { * Pop the next file from the queue. */ static int bftw_pop(struct bftw_state *state) { - if (!state->queue.head) { + state->file = state->queue.head; + if (!state->file) { return 0; } - state->file = BFTW_FILE(slist_pop(&state->queue)); + SLIST_POP(&state->queue); if (bftw_build_path(state) != 0) { return -1; @@ -940,8 +956,10 @@ static enum bftw_action bftw_gc_file(struct bftw_state *state, enum bftw_gc_flag * Drain all the files from the queue. */ static void bftw_drain_queue(struct bftw_state *state) { - LIST_DRAIN(&state->queue, struct bftw_file, file) { - state->file = file; + while (state->queue.head) { + state->file = state->queue.head; + SLIST_POP(&state->queue); + bftw_gc_file(state, BFTW_VISIT_NONE); } } @@ -966,21 +984,55 @@ static int bftw_state_destroy(struct bftw_state *state) { return state->error ? -1 : 0; } -/** Comparison function for BFTW_SORT. */ -static bool bftw_file_cmp(struct slink *left, struct slink *right, const void *ptr) { - return strcoll(BFTW_FILE(left)->name, BFTW_FILE(right)->name) <= 0; +/** Sort a bftw_list by filename. */ +static void bftw_list_sort(struct bftw_list *queue) { + if (!queue->head || !queue->head->next) { + return; + } + + struct bftw_list left, right; + SLIST_INIT(&left); + SLIST_INIT(&right); + + // Split + for (struct bftw_file *hare = queue->head; hare && (hare = hare->next); hare = hare->next) { + struct bftw_file *tortoise = queue->head; + SLIST_POP(queue); + SLIST_APPEND(&left, tortoise); + } + SLIST_EXTEND(&right, queue); + + // Recurse + bftw_list_sort(&left); + bftw_list_sort(&right); + + // Merge + while (left.head && right.head) { + struct bftw_file *lf = left.head; + struct bftw_file *rf = right.head; + + if (strcoll(lf->name, rf->name) <= 0) { + SLIST_POP(&left); + SLIST_APPEND(queue, lf); + } else { + SLIST_POP(&right); + SLIST_APPEND(queue, rf); + } + } + SLIST_EXTEND(queue, &left); + SLIST_EXTEND(queue, &right); } /** Finish adding a batch of files. */ static void bftw_batch_finish(struct bftw_state *state) { if (state->flags & BFTW_SORT) { - slist_sort(&state->batch, bftw_file_cmp, NULL); + bftw_list_sort(&state->batch); } if (state->strategy == BFTW_DFS) { - slist_extend(&state->batch, &state->queue); + SLIST_EXTEND(&state->batch, &state->queue); } - slist_extend(&state->queue, &state->batch); + SLIST_EXTEND(&state->queue, &state->batch); } /** diff --git a/src/config.h b/src/config.h index cee8511..34ae11d 100644 --- a/src/config.h +++ b/src/config.h @@ -147,17 +147,6 @@ */ #define BFS_COUNTOF(array) (sizeof(array) / sizeof(0[array])) -// BFS_CONTAINER_OF() helper -static inline char *bfs_container_offset(char *ptr, ptrdiff_t offset, size_t unused) { - return ptr ? ptr - offset : NULL; -} - -/** - * Move a pointer from a field to its outer struct. - */ -#define BFS_CONTAINER_OF(ptr, type, member) \ - ((type *)bfs_container_offset((char *)(ptr), offsetof(type, member), sizeof((ptr) - &((type *)NULL)->member))) - // Lower bound on BFS_FLEX_SIZEOF() #define BFS_FLEX_LB(type, member, length) (offsetof(type, member) + sizeof(((type *)NULL)->member[0]) * (length)) diff --git a/src/list.c b/src/list.c deleted file mode 100644 index dffee19..0000000 --- a/src/list.c +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include "list.h" -#include -#include - -void slink_init(struct slink *link) { - link->next = NULL; -} - -void slist_init(struct slist *list) { - list->head = NULL; - list->tail = &list->head; -} - -bool slist_is_empty(const struct slist *list) { - return !list->head; -} - -void slist_append(struct slist *list, struct slink *link) { - assert(!link->next); - *list->tail = link; - list->tail = &link->next; -} - -void slist_prepend(struct slist *list, struct slink *link) { - assert(!link->next); - if (!list->head) { - list->tail = &link->next; - } - link->next = list->head; - list->head = link; -} - -void slist_extend(struct slist *dest, struct slist *src) { - if (src->head) { - *dest->tail = src->head; - dest->tail = src->tail; - slist_init(src); - } -} - -struct slink *slist_pop(struct slist *list) { - struct slink *head = list->head; - if (!head) { - return NULL; - } - - list->head = head->next; - if (!list->head) { - list->tail = &list->head; - } - - head->next = NULL; - return head; -} - -void slist_sort(struct slist *list, slist_cmp_fn *cmp_fn, const void *ptr) { - if (!list->head || !list->head->next) { - return; - } - - struct slist left, right; - slist_init(&left); - slist_init(&right); - - // Split - for (struct slink *hare = list->head; hare && (hare = hare->next); hare = hare->next) { - slist_append(&left, slist_pop(list)); - } - slist_extend(&right, list); - - // Recurse - slist_sort(&left, cmp_fn, ptr); - slist_sort(&right, cmp_fn, ptr); - - // Merge - while (left.head && right.head) { - if (cmp_fn(left.head, right.head, ptr)) { - slist_append(list, slist_pop(&left)); - } else { - slist_append(list, slist_pop(&right)); - } - } - slist_extend(list, &left); - slist_extend(list, &right); -} - -void link_init(struct link *link) { - link->prev = NULL; - link->next = NULL; -} - -void list_init(struct list *list) { - list->head = NULL; - list->tail = NULL; -} - -bool list_is_empty(const struct list *list) { - return !list->head; -} - -void list_append(struct list *list, struct link *link) { - list_insert_after(list, list->tail, link); -} - -void list_prepend(struct list *list, struct link *link) { - list_insert_after(list, NULL, link); -} - -void list_insert_after(struct list *list, struct link *target, struct link *link) { - assert(!list_attached(list, link)); - - if (target) { - link->prev = target; - link->next = target->next; - } else { - link->next = list->head; - } - - if (link->prev) { - link->prev->next = link; - } else { - list->head = link; - } - - if (link->next) { - link->next->prev = link; - } else { - list->tail = link; - } -} - -void list_remove(struct list *list, struct link *link) { - if (link->prev) { - assert(list->head != link); - link->prev->next = link->next; - } else { - assert(list->head == link); - list->head = link->next; - } - - if (link->next) { - assert(list->tail != link); - link->next->prev = link->prev; - } else { - assert(list->tail == link); - list->tail = link->prev; - } - - link->prev = NULL; - link->next = NULL; -} - -struct link *list_pop(struct list *list) { - struct link *head = list->head; - if (!head) { - return NULL; - } - - list_remove(list, head); - return head; -} - -bool list_attached(const struct list *list, const struct link *link) { - return link->prev || list->head == link - || link->next || list->tail == link; -} diff --git a/src/list.h b/src/list.h index b47bed7..c43be68 100644 --- a/src/list.h +++ b/src/list.h @@ -3,225 +3,354 @@ /** * Intrusive linked lists. + * + * Singly-linked lists are declared like this: + * + * struct item { + * struct item *next; + * }; + * + * struct list { + * struct item *head; + * struct item **tail; + * }; + * + * The SLIST_*() macros manipulate singly-linked lists. + * + * struct list list; + * SLIST_INIT(&list); + * + * struct item item; + * SLIST_APPEND(&list, &item); + * + * Doubly linked lists are similar: + * + * struct item { + * struct item *next; + * struct item *prev; + * }; + * + * struct list { + * struct item *head; + * struct item *tail; + * }; + * + * struct list list; + * LIST_INIT(&list); + * + * struct item item; + * LIST_APPEND(&list, &item); + * + * Items can be on multiple lists at once: + * + * struct item { + * struct { + * struct item *next; + * } chain; + * + * struct { + * struct item *next; + * struct item *prev; + * } lru; + * }; + * + * struct items { + * struct { + * struct item *head; + * struct item **tail; + * } queue; + * + * struct { + * struct item *head; + * struct item *tail; + * } cache; + * }; + * + * struct items items; + * SLIST_INIT(&items.queue); + * LIST_INIT(&items.cache); + * + * struct item item; + * SLIST_APPEND(&items.queue, &item, chain); + * LIST_APPEND(&items.cache, &item, lru); */ #ifndef BFS_LIST_H #define BFS_LIST_H -#include "config.h" -#include +#include /** - * A singly-linked list entry. + * Initialize a singly-linked list. + * + * @param list + * The list to initialize. + * + * --- + * + * Like many macros in this file, this macro delegates the bulk of its work to + * some helper macros. We explicitly parenthesize (list) here so the helpers + * don't have to. */ -struct slink { - struct slink *next; -}; +#define SLIST_INIT(list) \ + LIST_BLOCK_(SLIST_INIT_((list))) -/** Initialize a list entry. */ -void slink_init(struct slink *link); +#define SLIST_INIT_(list) \ + (list)->head = NULL; \ + (list)->tail = &(list)->head; /** - * A singly-linked list. + * Wraps a group of statements in a block. */ -struct slist { - struct slink *head; - struct slink **tail; -}; - -/** Initialize an empty list. */ -void slist_init(struct slist *list); - -/** Check if a list is empty. */ -bool slist_is_empty(const struct slist *list); - -/** Add an entry at the tail of the list. */ -void slist_append(struct slist *list, struct slink *link); - -/** Add an entry at the head of the list. */ -void slist_prepend(struct slist *list, struct slink *link); - -/** Add an entire list at the tail of the list. */ -void slist_extend(struct slist *dest, struct slist *src); - -/** Remove the head of the list. */ -struct slink *slist_pop(struct slist *list); +#define LIST_BLOCK_(block) do { block } while (0) /** - * Comparison function type for slist_sort(). + * Add an item to the tail of a singly-linked list. * - * @param left - * The left-hand side of the comparison. - * @param right - * The right-hand side of the comparison. - * @param ptr - * An arbitrary pointer passed to slist_sort(). - * @return - * Whether left <= right. + * @param list + * The list to modify. + * @param item + * The item to append. + * @param link (optional) + * If specified, use item->link.next rather than item->next. + * + * --- + * + * We play some tricks with variadic macros to handle the optional parameter: + * + * SLIST_APPEND(list, item) => { + * *list->tail = item; + * list->tail = &item->next; + * } + * + * SLIST_APPEND(list, item, link) => { + * *list->tail = item; + * list->tail = &item->link.next; + * } + * + * The first trick is that + * + * #define SLIST_APPEND(list, item, ...) + * + * won't work because both commas are required (until C23; see N3033). As a + * workaround, we dispatch to another macro and add a trailing comma. + * + * SLIST_APPEND(list, item) => SLIST_APPEND_(list, item, ) + * SLIST_APPEND(list, item, link) => SLIST_APPEND_(list, item, link, ) */ -typedef bool slist_cmp_fn(struct slink *left, struct slink *right, const void *ptr); +#define SLIST_APPEND(list, ...) SLIST_APPEND_(list, __VA_ARGS__, ) -/** Sort a list. */ -void slist_sort(struct slist *list, slist_cmp_fn *cmp_fn, const void *ptr); +/** + * Now we need a way to generate either ->next or ->link.next depending on + * whether the link parameter was passed. The approach is based on + * + * #define FOO(...) BAR(__VA_ARGS__, 1, 2, ) + * #define BAR(x, y, z, ...) z + * + * FOO(a) => 2 + * FOO(a, b) => 1 + * + * The LIST_NEXT_() macro uses this technique: + * + * LIST_NEXT_() => LIST_LINK_(next, ) + * LIST_NEXT_(link, ) => LIST_LINK_(next, link, ) + */ +#define LIST_NEXT_(...) LIST_LINK_(next, __VA_ARGS__) /** - * A doubly-linked list entry. + * LIST_LINK_() dispatches to yet another macro: + * + * LIST_LINK_(next, ) => LIST_LINK__(next, , , . , , ) + * LIST_LINK_(next, link, ) => LIST_LINK__(next, link, , , . , , ) */ -struct link { - struct link *prev; - struct link *next; -}; +#define LIST_LINK_(dir, ...) LIST_LINK__(dir, __VA_ARGS__, , . , , ) -/** Initialize a list entry. */ -void link_init(struct link *link); +/** + * And finally, LIST_LINK__() adds the link and the dot if necessary. + * + * dir link blank ignored dot + * v v v v v + * LIST_LINK__(next, , , . , , ) => next + * LIST_LINK__(next, link, , , . , , ) => link . next + * ^ ^ ^ ^ ^ + * dir link blank ignored dot + */ +#define LIST_LINK__(dir, link, blank, ignored, dot, ...) link dot dir /** - * A doubly-linked list. + * SLIST_APPEND_() uses LIST_NEXT_() to generate the right name for the list + * link, and finally delegates to the actual implementation. + * + * SLIST_APPEND_(list, item, ) => SLIST_APPEND__((list), (item), next) + * SLIST_APPEND_(list, item, link, ) => SLIST_APPEND__((list), (item), link.next) */ -struct list { - struct link *head; - struct link *tail; -}; +#define SLIST_APPEND_(list, item, ...) \ + LIST_BLOCK_(SLIST_APPEND__((list), (item), LIST_NEXT_(__VA_ARGS__))) -/** Initialize an empty list. */ -void list_init(struct list *list); +#define SLIST_APPEND__(list, item, next) \ + *list->tail = item; \ + list->tail = &item->next; -/** Check if a list is empty. */ -bool list_is_empty(const struct list *list); +/** + * Add an item to the head of a singly-linked list. + * + * @param list + * The list to modify. + * @param item + * The item to prepend. + * @param link (optional) + * If specified, use item->link.next rather than item->next. + */ +#define SLIST_PREPEND(list, ...) SLIST_PREPEND_(list, __VA_ARGS__, ) -/** Add an entry at the tail of the list. */ -void list_append(struct list *list, struct link *link); +#define SLIST_PREPEND_(list, item, ...) \ + LIST_BLOCK_(SLIST_PREPEND__((list), (item), LIST_NEXT_(__VA_ARGS__))) -/** Add an entry at the head of the list. */ -void list_prepend(struct list *list, struct link *link); +#define SLIST_PREPEND__(list, item, next) \ + list->tail = list->head ? list->tail : &item->next; \ + item->next = list->head; \ + list->head = item; -/** Insert an entry after the target entry. */ -void list_insert_after(struct list *list, struct link *target, struct link *link); +/** + * Add an entire singly-linked list to the tail of another. + * + * @param dest + * The destination list. + * @param src + * The source list. + */ +#define SLIST_EXTEND(dest, src) \ + LIST_BLOCK_(SLIST_EXTEND_((dest), (src))) -/** Remove an entry from a list. */ -void list_remove(struct list *list, struct link *link); +#define SLIST_EXTEND_(dest, src) \ + if (src->head) { \ + *dest->tail = src->head; \ + dest->tail = src->tail; \ + SLIST_INIT(src); \ + } -/** Remove the head of the list. */ -struct link *list_pop(struct list *list); +/** + * Pop the head off a singly-linked list. + * + * @param list + * The list to pop from. + * @param link (optional) + * If specified, use head->link.next rather than head->next. + */ +#define SLIST_POP(...) SLIST_POP_(__VA_ARGS__, ) -/** Check if a link is attached to a list. */ -bool list_attached(const struct list *list, const struct link *link); +#define SLIST_POP_(list, ...) \ + LIST_BLOCK_(SLIST_POP__((list), LIST_NEXT_(__VA_ARGS__))) -// LIST_ITEM() helper -#define LIST_ITEM_IMPL(type, entry, member, ...) \ - BFS_CONTAINER_OF(entry, type, member) +#define SLIST_POP__(list, next) \ + void *_next = (void *)list->head->next; \ + list->head->next = NULL; \ + list->head = _next; \ + list->tail = list->head ? list->tail : &list->head; /** - * Convert a list entry to its container. + * Initialize a doubly-linked list. * - * @param type - * The type of the list entries. - * @param entry - * The list entry to convert. - * @param member - * The name of the list link field (default: link). - * @return - * The item that contains the given entry. + * @param list + * The list to initialize. */ -#define LIST_ITEM(...) \ - LIST_ITEM_IMPL(__VA_ARGS__, link,) +#define LIST_INIT(list) \ + LIST_BLOCK_(LIST_INIT_((list))) -// LIST_NEXT() helper -#define LIST_NEXT_IMPL(type, entry, member, ...) \ - LIST_ITEM(type, (entry)->member.next, member) +#define LIST_INIT_(list) \ + list->head = list->tail = NULL; /** - * Get the next item in a list. - * - * @param type - * The type of the list entries. - * @param entry - * The current entry. - * @param member - * The name of the list link field (default: link). - * @return - * The next item in the list. + * LIST_PREV_() => prev + * LIST_PREV_(link, ) => link.prev */ -#define LIST_NEXT(...) \ - LIST_NEXT_IMPL(__VA_ARGS__, link,) - -// LIST_PREV() helper -#define LIST_PREV_IMPL(type, entry, member, ...) \ - LIST_ITEM(type, (entry)->member.prev, member) +#define LIST_PREV_(...) LIST_LINK_(prev, __VA_ARGS__) /** - * Get the previous entry in a list. + * Add an item to the tail of a doubly-linked list. * - * @param type - * The type of the list entries. - * @param entry - * The current entry. - * @param member - * The name of the list link field (default: link). - * @return - * The previous item in the list. + * @param list + * The list to modify. + * @param item + * The item to append. + * @param link (optional) + * If specified, use item->link.{prev,next} rather than item->{prev,next}. */ -#define LIST_PREV(...) \ - LIST_PREV_IMPL(__VA_ARGS__, link,) +#define LIST_APPEND(list, ...) LIST_INSERT(list, (list)->tail, __VA_ARGS__) -// Helper for LIST_FOR_EACH_*() -#define LIST_FOR_EACH_IMPL(entry, type, i, member, ...) \ - for (type *_next, *i = LIST_ITEM(type, entry, member); \ - i && (_next = LIST_NEXT(type, i, member), true); \ - i = _next) +/** + * Add an item to the head of a doubly-linked list. + * + * @param list + * The list to modify. + * @param item + * The item to prepend. + * @param link (optional) + * If specified, use item->link.{prev,next} rather than item->{prev,next}. + */ +#define LIST_PREPEND(list, ...) LIST_INSERT(list, NULL, __VA_ARGS__) /** - * Iterate over a list from the given entry. + * Insert into a doubly-linked list after the given cursor. * - * @param entry - * The entry to start from. - * @param type - * The type of the list entries. - * @param i - * The name of the loop variable, declared as type *i. - * @param member - * The name of the list link field (default: link). + * @param list + * The list to initialize. + * @param cursor + * Insert after this element. + * @param item + * The item to insert. + * @param link (optional) + * If specified, use item->link.{prev,next} rather than item->{prev,next}. */ -#define LIST_FOR_EACH_FROM(...) \ - LIST_FOR_EACH_IMPL(__VA_ARGS__, link,) +#define LIST_INSERT(list, cursor, ...) LIST_INSERT_(list, cursor, __VA_ARGS__, ) + +#define LIST_INSERT_(list, cursor, item, ...) \ + LIST_BLOCK_(LIST_INSERT__((list), (cursor), (item), LIST_PREV_(__VA_ARGS__), LIST_NEXT_(__VA_ARGS__))) + +#define LIST_INSERT__(list, cursor, item, prev, next) \ + item->prev = cursor; \ + item->next = cursor ? cursor->next : list->head; \ + *(item->prev ? &item->prev->next : &list->head) = item; \ + *(item->next ? &item->next->prev : &list->tail) = item; /** - * Iterate over a list. + * Remove an item from a doubly-linked list. * * @param list - * The list to iterate over. - * @param type - * The type of the list entries. - * @param i - * The name of the loop variable, declared as type *i. - * @param member - * The name of the list link field (default: link). + * The list to modify. + * @param item + * The item to remove. + * @param link (optional) + * If specified, use item->link.{prev,next} rather than item->{prev,next}. */ -#define LIST_FOR_EACH(list, ...) \ - LIST_FOR_EACH_FROM((list)->head, __VA_ARGS__) +#define LIST_REMOVE(list, ...) LIST_REMOVE_(list, __VA_ARGS__, ) -// Pop from a list or slist -#define LIST_POP(l) _Generic((l), \ - struct list *: list_pop((struct list *)l), \ - struct slist *: slist_pop((struct slist *)l)) +#define LIST_REMOVE_(list, item, ...) \ + LIST_BLOCK_(LIST_REMOVE__((list), (item), LIST_PREV_(__VA_ARGS__), LIST_NEXT_(__VA_ARGS__))) -// Helper for LIST_DRAIN() -#define LIST_DRAIN_IMPL(list, type, i, member, ...) \ - for (type *i; (i = LIST_ITEM(type, LIST_POP(list), member));) +#define LIST_REMOVE__(list, item, prev, next) \ + *(item->prev ? &item->prev->next : &list->head) = item->next; \ + *(item->next ? &item->next->prev : &list->tail) = item->prev; \ + item->prev = item->next = NULL; /** - * Drain the entries from a list. + * Check if an item is attached to a doubly-linked list. * * @param list - * The list to drain. - * @param type - * The type of the list entries. - * @param i - * The name of the loop variable, declared as type *i. - * @param member - * The name of the list link field (default: link). - */ -#define LIST_DRAIN(...) \ - LIST_DRAIN_IMPL(__VA_ARGS__, link,) + * The list to check. + * @param item + * The item to check. + * @param link (optional) + * If specified, use item->link.{prev,next} rather than item->{prev,next}. + * @return + * Whether the item is attached to the list. + */ +#define LIST_ATTACHED(list, ...) LIST_ATTACHED_(list, __VA_ARGS__, ) + +#define LIST_ATTACHED_(list, item, ...) \ + LIST_ATTACHED__((list), (item), LIST_PREV_(__VA_ARGS__), LIST_NEXT_(__VA_ARGS__)) + +#define LIST_ATTACHED__(list, item, prev, next) \ + (item->prev || item->next || list->head == item || list->tail == item) #endif // BFS_LIST_H diff --git a/src/trie.c b/src/trie.c index 7b00f4b..43df9dc 100644 --- a/src/trie.c +++ b/src/trie.c @@ -83,6 +83,7 @@ #include "trie.h" #include "config.h" +#include "list.h" #include #include #include @@ -163,7 +164,7 @@ static uintptr_t trie_encode_node(const struct trie_node *node) { void trie_init(struct trie *trie) { trie->root = 0; - list_init(&trie->leaves); + LIST_INIT(trie); } /** Check if a number is a power of two. */ @@ -340,8 +341,7 @@ static struct trie_leaf *trie_leaf_alloc(struct trie *trie, const void *key, siz return NULL; } - link_init(&leaf->link); - list_append(&trie->leaves, &leaf->link); + LIST_APPEND(trie, leaf); leaf->value = NULL; leaf->length = length; @@ -352,7 +352,7 @@ static struct trie_leaf *trie_leaf_alloc(struct trie *trie, const void *key, siz /** Free a leaf. */ static void trie_leaf_free(struct trie *trie, struct trie_leaf *leaf) { - list_remove(&trie->leaves, &leaf->link); + LIST_REMOVE(trie, leaf); free(leaf); } diff --git a/src/trie.h b/src/trie.h index 6e1e875..58974aa 100644 --- a/src/trie.h +++ b/src/trie.h @@ -5,7 +5,6 @@ #define BFS_TRIE_H #include "config.h" -#include "list.h" #include #include #include @@ -15,7 +14,7 @@ */ struct trie_leaf { /** Linked list of leaves, in insertion order. */ - struct link link; + struct trie_leaf *prev, *next; /** An arbitrary value associated with this leaf. */ void *value; /** The length of the key in bytes. */ @@ -31,7 +30,7 @@ struct trie { /** Pointer to the root node/leaf. */ uintptr_t root; /** Linked list of leaves. */ - struct list leaves; + struct trie_leaf *head, *tail; }; /** @@ -134,6 +133,8 @@ void trie_destroy(struct trie *trie); * Iterate over the leaves of a trie. */ #define TRIE_FOR_EACH(trie, leaf) \ - LIST_FOR_EACH(&(trie)->leaves, struct trie_leaf, leaf) + for (struct trie_leaf *leaf = (trie)->head, *_next; \ + leaf && (_next = leaf->next, true); \ + leaf = _next) #endif // BFS_TRIE_H diff --git a/src/xspawn.c b/src/xspawn.c index e6ce0de..a185200 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -33,7 +33,7 @@ enum bfs_spawn_op { * A spawn action. */ struct bfs_spawn_action { - struct slink link; + struct bfs_spawn_action *next; enum bfs_spawn_op op; int in_fd; @@ -44,12 +44,14 @@ struct bfs_spawn_action { int bfs_spawn_init(struct bfs_spawn *ctx) { ctx->flags = 0; - slist_init(&ctx->actions); + SLIST_INIT(ctx); return 0; } int bfs_spawn_destroy(struct bfs_spawn *ctx) { - LIST_DRAIN(&ctx->actions, struct bfs_spawn_action, action) { + while (ctx->head) { + struct bfs_spawn_action *action = ctx->head; + SLIST_POP(ctx); free(action); } @@ -68,12 +70,12 @@ static struct bfs_spawn_action *bfs_spawn_add(struct bfs_spawn *ctx, enum bfs_sp return NULL; } - slink_init(&action->link); + action->next = NULL; action->op = op; action->in_fd = -1; action->out_fd = -1; - slist_append(&ctx->actions, &action->link); + SLIST_APPEND(ctx, action); return action; } @@ -138,8 +140,7 @@ int bfs_spawn_addsetrlimit(struct bfs_spawn *ctx, int resource, const struct rli static void bfs_spawn_exec(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) { xclose(pipefd[0]); - const struct slink *head = ctx ? ctx->actions.head : NULL; - LIST_FOR_EACH_FROM(head, struct bfs_spawn_action, action) { + for (const struct bfs_spawn_action *action = ctx ? ctx->head : NULL; action; action = action->next) { // Move the error-reporting pipe out of the way if necessary... if (action->out_fd == pipefd[1]) { int fd = dup_cloexec(pipefd[1]); diff --git a/src/xspawn.h b/src/xspawn.h index 7e673f1..d9b4a2e 100644 --- a/src/xspawn.h +++ b/src/xspawn.h @@ -8,7 +8,6 @@ #ifndef BFS_XSPAWN_H #define BFS_XSPAWN_H -#include "list.h" #include #include @@ -25,7 +24,8 @@ enum bfs_spawn_flags { */ struct bfs_spawn { enum bfs_spawn_flags flags; - struct slist actions; + struct bfs_spawn_action *head; + struct bfs_spawn_action **tail; }; /** diff --git a/tests/trie.c b/tests/trie.c index 65660a9..c2af18a 100644 --- a/tests/trie.c +++ b/tests/trie.c @@ -74,6 +74,8 @@ int main(void) { size_t i = 0; TRIE_FOR_EACH(&trie, leaf) { assert(leaf == trie_find_str(&trie, keys[i])); + assert(!leaf->prev || leaf->prev->next == leaf); + assert(!leaf->next || leaf->next->prev == leaf); ++i; } assert(i == nkeys); -- cgit v1.2.3 From f2ec5c1538c82f7007935647d5e5b59ea10a6f83 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 12 Apr 2023 10:01:44 -0400 Subject: bftw: Merge bftw_closedir() into bftw_gc() --- src/bftw.c | 106 +++++++++++++++++++++++++++---------------------------------- 1 file changed, 47 insertions(+), 59 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 7bc724a..5cbe0c2 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -705,6 +705,10 @@ static void bftw_fill_id(struct bftw_file *file, const struct BFTW *ftwbuf) { * Visit a path, invoking the callback. */ static enum bftw_action bftw_visit(struct bftw_state *state, const char *name, enum bftw_visit visit) { + if (visit == BFTW_POST && !(state->flags & BFTW_POST_ORDER)) { + return BFTW_PRUNE; + } + if (bftw_update_path(state, name) != 0) { state->error = errno; return BFTW_STOP; @@ -814,13 +818,23 @@ static int bftw_build_path(struct bftw_state *state) { /** * Pop the next file from the queue. */ -static int bftw_pop(struct bftw_state *state) { +static bool bftw_pop(struct bftw_state *state) { state->file = state->queue.head; - if (!state->file) { - return 0; + if (state->file) { + SLIST_POP(&state->queue); + return true; + } else { + return false; } +} - SLIST_POP(&state->queue); +/** + * Start processing the next file in the queue. + */ +static int bftw_next(struct bftw_state *state) { + if (!bftw_pop(state)) { + return 0; + } if (bftw_build_path(state) != 0) { return -1; @@ -871,23 +885,25 @@ static int bftw_readdir(struct bftw_state *state) { enum bftw_gc_flags { /** Don't visit anything. */ BFTW_VISIT_NONE = 0, + /** Report directory errors. */ + BFTW_VISIT_ERROR = 1 << 0, /** Visit the file itself. */ - BFTW_VISIT_FILE = 1 << 0, + BFTW_VISIT_FILE = 1 << 1, /** Visit the file's ancestors. */ - BFTW_VISIT_PARENTS = 1 << 1, + BFTW_VISIT_PARENTS = 1 << 2, /** Visit both the file and its ancestors. */ - BFTW_VISIT_ALL = BFTW_VISIT_FILE | BFTW_VISIT_PARENTS, + BFTW_VISIT_ALL = BFTW_VISIT_ERROR | BFTW_VISIT_FILE | BFTW_VISIT_PARENTS, }; /** - * Close the current directory. + * Garbage collect the current file and its parents. */ -static enum bftw_action bftw_closedir(struct bftw_state *state, enum bftw_gc_flags flags) { - struct bftw_file *file = state->file; +static enum bftw_action bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { enum bftw_action ret = BFTW_CONTINUE; if (state->dir) { - assert(file->fd >= 0); + struct bftw_file *file = state->file; + assert(file && file->fd >= 0); if (file->refcount > 1) { // Keep the fd around if any subdirectories exist @@ -902,32 +918,22 @@ static enum bftw_action bftw_closedir(struct bftw_state *state, enum bftw_gc_fla } } - state->de = NULL; state->dir = NULL; + state->de = NULL; if (state->direrror != 0) { - if (flags & BFTW_VISIT_FILE) { - ret = bftw_visit(state, NULL, BFTW_PRE); + if (flags & BFTW_VISIT_ERROR) { + if (bftw_visit(state, NULL, BFTW_PRE) == BFTW_STOP) { + ret = BFTW_STOP; + flags = 0; + } } else { state->error = state->direrror; } state->direrror = 0; } - return ret; -} - -/** - * Finalize and free a file we're done with. - */ -static enum bftw_action bftw_gc_file(struct bftw_state *state, enum bftw_gc_flags flags) { - enum bftw_action ret = BFTW_CONTINUE; - - if (!(state->flags & BFTW_POST_ORDER)) { - flags = 0; - } - bool visit = flags & BFTW_VISIT_FILE; - + enum bftw_gc_flags visit = BFTW_VISIT_FILE; while (state->file) { struct bftw_file *file = state->file; if (--file->refcount > 0) { @@ -935,11 +941,13 @@ static enum bftw_action bftw_gc_file(struct bftw_state *state, enum bftw_gc_flag break; } - if (visit && bftw_visit(state, NULL, BFTW_POST) == BFTW_STOP) { - ret = BFTW_STOP; - flags &= ~BFTW_VISIT_PARENTS; + if (flags & visit) { + if (bftw_visit(state, NULL, BFTW_POST) == BFTW_STOP) { + ret = BFTW_STOP; + flags = 0; + } } - visit = flags & BFTW_VISIT_PARENTS; + visit = BFTW_VISIT_PARENTS; struct bftw_file *parent = file->parent; if (state->previous == file) { @@ -952,18 +960,6 @@ static enum bftw_action bftw_gc_file(struct bftw_state *state, enum bftw_gc_flag return ret; } -/** - * Drain all the files from the queue. - */ -static void bftw_drain_queue(struct bftw_state *state) { - while (state->queue.head) { - state->file = state->queue.head; - SLIST_POP(&state->queue); - - bftw_gc_file(state, BFTW_VISIT_NONE); - } -} - /** * Dispose of the bftw() state. * @@ -973,10 +969,9 @@ static void bftw_drain_queue(struct bftw_state *state) { static int bftw_state_destroy(struct bftw_state *state) { dstrfree(state->path); - bftw_closedir(state, BFTW_VISIT_NONE); - - bftw_gc_file(state, BFTW_VISIT_NONE); - bftw_drain_queue(state); + do { + bftw_gc(state, BFTW_VISIT_NONE); + } while (bftw_pop(state)); bftw_cache_destroy(&state->cache); @@ -1064,7 +1059,7 @@ static int bftw_stream(const struct bftw_args *args) { } bftw_batch_finish(&state); - while (bftw_pop(&state) > 0) { + while (bftw_next(&state) > 0) { bftw_opendir(&state); while (bftw_readdir(&state) > 0) { @@ -1085,10 +1080,7 @@ static int bftw_stream(const struct bftw_args *args) { } bftw_batch_finish(&state); - if (bftw_closedir(&state, BFTW_VISIT_ALL) == BFTW_STOP) { - goto done; - } - if (bftw_gc_file(&state, BFTW_VISIT_ALL) == BFTW_STOP) { + if (bftw_gc(&state, BFTW_VISIT_ALL) == BFTW_STOP) { goto done; } } @@ -1113,7 +1105,7 @@ static int bftw_batch(const struct bftw_args *args) { } bftw_batch_finish(&state); - while (bftw_pop(&state) > 0) { + while (bftw_next(&state) > 0) { enum bftw_gc_flags gcflags = BFTW_VISIT_ALL; switch (bftw_visit(&state, NULL, BFTW_PRE)) { @@ -1135,12 +1127,8 @@ static int bftw_batch(const struct bftw_args *args) { } bftw_batch_finish(&state); - if (bftw_closedir(&state, gcflags) == BFTW_STOP) { - goto done; - } - next: - if (bftw_gc_file(&state, gcflags) == BFTW_STOP) { + if (bftw_gc(&state, gcflags) == BFTW_STOP) { goto done; } } -- cgit v1.2.3 From c1b16b49988ecff17ae30978ea14798d95b80018 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 5 May 2023 17:49:44 -0400 Subject: bftw: Use separate dir/file queues --- src/bftw.c | 394 +++++++++++++++++++++++++++---------------------------------- 1 file changed, 176 insertions(+), 218 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 5cbe0c2..e4dc411 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -362,8 +362,10 @@ struct bftw_state { /** The cache of open directories. */ struct bftw_cache cache; - /** The queue of files left to explore. */ - struct bftw_list queue; + /** The queue of directories to read. */ + struct bftw_list dirs; + /** The queue of files to visit. */ + struct bftw_list files; /** A batch of files to enqueue. */ struct bftw_list batch; @@ -397,6 +399,10 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg state->strategy = args->strategy; state->mtab = args->mtab; + if ((state->flags & BFTW_SORT) || state->strategy == BFTW_DFS) { + state->flags |= BFTW_BUFFER; + } + state->error = 0; if (args->nopenfd < 1) { @@ -411,7 +417,8 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg bftw_cache_init(&state->cache, args->nopenfd); - SLIST_INIT(&state->queue); + SLIST_INIT(&state->dirs); + SLIST_INIT(&state->files); SLIST_INIT(&state->batch); state->file = NULL; @@ -497,22 +504,47 @@ enum bfs_type bftw_type(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { } /** - * Update the path for the current file. + * Build the path to the current file. */ -static int bftw_update_path(struct bftw_state *state, const char *name) { +static int bftw_build_path(struct bftw_state *state, const char *name) { const struct bftw_file *file = state->file; - size_t length = file ? file->nameoff + file->namelen : 0; - assert(dstrlen(state->path) >= length); - dstresize(&state->path, length); + size_t pathlen = file ? file->nameoff + file->namelen : 0; + if (dstresize(&state->path, pathlen) != 0) { + state->error = errno; + return -1; + } + + // Try to find a common ancestor with the existing path + const struct bftw_file *ancestor = state->previous; + while (ancestor && ancestor->depth > file->depth) { + ancestor = ancestor->parent; + } + + // Build the path backwards + while (file && file != ancestor) { + if (file->nameoff > 0) { + state->path[file->nameoff - 1] = '/'; + } + memcpy(state->path + file->nameoff, file->name, file->namelen); + + if (ancestor && ancestor->depth == file->depth) { + ancestor = ancestor->parent; + } + file = file->parent; + } + + state->previous = state->file; if (name) { - if (length > 0 && state->path[length - 1] != '/') { + if (pathlen > 0 && state->path[pathlen - 1] != '/') { if (dstrapp(&state->path, '/') != 0) { + state->error = errno; return -1; } } if (dstrcat(&state->path, name) != 0) { + state->error = errno; return -1; } } @@ -689,28 +721,15 @@ static bool bftw_is_mount(struct bftw_state *state, const char *name) { return statbuf && statbuf->dev != parent->dev; } -/** Fill file identity information from an ftwbuf. */ -static void bftw_fill_id(struct bftw_file *file, const struct BFTW *ftwbuf) { - const struct bfs_stat *statbuf = ftwbuf->stat_cache.buf; - if (!statbuf || (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) { - statbuf = ftwbuf->lstat_cache.buf; - } - if (statbuf) { - file->dev = statbuf->dev; - file->ino = statbuf->ino; - } -} - /** - * Visit a path, invoking the callback. + * Invoke the callback. */ -static enum bftw_action bftw_visit(struct bftw_state *state, const char *name, enum bftw_visit visit) { +static enum bftw_action bftw_call_back(struct bftw_state *state, const char *name, enum bftw_visit visit) { if (visit == BFTW_POST && !(state->flags & BFTW_POST_ORDER)) { return BFTW_PRUNE; } - if (bftw_update_path(state, name) != 0) { - state->error = errno; + if (bftw_build_path(state, name) != 0) { return BFTW_STOP; } @@ -730,132 +749,74 @@ static enum bftw_action bftw_visit(struct bftw_state *state, const char *name, e enum bftw_action ret = state->callback(ftwbuf, state->ptr); switch (ret) { case BFTW_CONTINUE: - break; + if (visit != BFTW_PRE) { + return BFTW_PRUNE; + } + if (ftwbuf->type != BFS_DIR) { + return BFTW_PRUNE; + } + if ((state->flags & BFTW_PRUNE_MOUNTS) && bftw_is_mount(state, name)) { + return BFTW_PRUNE; + } + BFS_FALLTHROUGH; case BFTW_PRUNE: case BFTW_STOP: - goto done; + return ret; + default: state->error = EINVAL; return BFTW_STOP; } - - if (visit != BFTW_PRE || ftwbuf->type != BFS_DIR) { - ret = BFTW_PRUNE; - goto done; - } - - if ((state->flags & BFTW_PRUNE_MOUNTS) && bftw_is_mount(state, name)) { - ret = BFTW_PRUNE; - goto done; - } - -done: - if (state->file && !name) { - bftw_fill_id(state->file, ftwbuf); - } - - return ret; } -/** - * Push a new file onto the queue. - */ -static int bftw_push(struct bftw_state *state, const char *name, bool fill_id) { - struct bftw_file *parent = state->file; - struct bftw_file *file = bftw_file_new(parent, name); - if (!file) { - state->error = errno; - return -1; - } +/** Pop a directory to read from the queue. */ +static bool bftw_pop_dir(struct bftw_state *state) { + assert(!state->file); - if (state->de) { - file->type = state->de->type; + if (!state->dirs.head) { + return false; } - if (fill_id) { - bftw_fill_id(file, &state->ftwbuf); + if (state->files.head && state->strategy == BFTW_BFS) { + return false; } - SLIST_APPEND(&state->batch, file); - return 0; + state->file = state->dirs.head; + SLIST_POP(&state->dirs); + return true; } -/** - * Build the path to the current file. - */ -static int bftw_build_path(struct bftw_state *state) { - const struct bftw_file *file = state->file; - - size_t pathlen = file->nameoff + file->namelen; - if (dstresize(&state->path, pathlen) != 0) { - state->error = errno; - return -1; - } - - // Try to find a common ancestor with the existing path - const struct bftw_file *ancestor = state->previous; - while (ancestor && ancestor->depth > file->depth) { - ancestor = ancestor->parent; - } - - // Build the path backwards - while (file && file != ancestor) { - if (file->nameoff > 0) { - state->path[file->nameoff - 1] = '/'; - } - memcpy(state->path + file->nameoff, file->name, file->namelen); - - if (ancestor && ancestor->depth == file->depth) { - ancestor = ancestor->parent; - } - file = file->parent; - } - - state->previous = state->file; - return 0; -} +/** Pop a file to visit from the queue. */ +static bool bftw_pop_file(struct bftw_state *state) { + assert(!state->file); -/** - * Pop the next file from the queue. - */ -static bool bftw_pop(struct bftw_state *state) { - state->file = state->queue.head; + state->file = state->files.head; if (state->file) { - SLIST_POP(&state->queue); + SLIST_POP(&state->files); return true; } else { return false; } } -/** - * Start processing the next file in the queue. - */ -static int bftw_next(struct bftw_state *state) { - if (!bftw_pop(state)) { - return 0; - } - - if (bftw_build_path(state) != 0) { - return -1; - } - - return 1; -} - /** * Open the current directory. */ -static void bftw_opendir(struct bftw_state *state) { +static int bftw_opendir(struct bftw_state *state) { assert(!state->dir); assert(!state->de); state->direrror = 0; + if (bftw_build_path(state, NULL) != 0) { + return -1; + } + state->dir = bftw_file_opendir(&state->cache, state->file, state->path); if (!state->dir) { state->direrror = errno; } + return 0; } /** @@ -898,8 +859,8 @@ enum bftw_gc_flags { /** * Garbage collect the current file and its parents. */ -static enum bftw_action bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { - enum bftw_action ret = BFTW_CONTINUE; +static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { + int ret = 0; if (state->dir) { struct bftw_file *file = state->file; @@ -923,8 +884,8 @@ static enum bftw_action bftw_gc(struct bftw_state *state, enum bftw_gc_flags fla if (state->direrror != 0) { if (flags & BFTW_VISIT_ERROR) { - if (bftw_visit(state, NULL, BFTW_PRE) == BFTW_STOP) { - ret = BFTW_STOP; + if (bftw_call_back(state, NULL, BFTW_PRE) == BFTW_STOP) { + ret = -1; flags = 0; } } else { @@ -942,8 +903,8 @@ static enum bftw_action bftw_gc(struct bftw_state *state, enum bftw_gc_flags fla } if (flags & visit) { - if (bftw_visit(state, NULL, BFTW_POST) == BFTW_STOP) { - ret = BFTW_STOP; + if (bftw_call_back(state, NULL, BFTW_POST) == BFTW_STOP) { + ret = -1; flags = 0; } } @@ -969,9 +930,10 @@ static enum bftw_action bftw_gc(struct bftw_state *state, enum bftw_gc_flags fla static int bftw_state_destroy(struct bftw_state *state) { dstrfree(state->path); + SLIST_EXTEND(&state->files, &state->batch); do { bftw_gc(state, BFTW_VISIT_NONE); - } while (bftw_pop(state)); + } while (bftw_pop_dir(state) || bftw_pop_file(state)); bftw_cache_destroy(&state->cache); @@ -980,8 +942,8 @@ static int bftw_state_destroy(struct bftw_state *state) { } /** Sort a bftw_list by filename. */ -static void bftw_list_sort(struct bftw_list *queue) { - if (!queue->head || !queue->head->next) { +static void bftw_list_sort(struct bftw_list *list) { + if (!list->head || !list->head->next) { return; } @@ -990,12 +952,12 @@ static void bftw_list_sort(struct bftw_list *queue) { SLIST_INIT(&right); // Split - for (struct bftw_file *hare = queue->head; hare && (hare = hare->next); hare = hare->next) { - struct bftw_file *tortoise = queue->head; - SLIST_POP(queue); + for (struct bftw_file *hare = list->head; hare && (hare = hare->next); hare = hare->next) { + struct bftw_file *tortoise = list->head; + SLIST_POP(list); SLIST_APPEND(&left, tortoise); } - SLIST_EXTEND(&right, queue); + SLIST_EXTEND(&right, list); // Recurse bftw_list_sort(&left); @@ -1008,14 +970,14 @@ static void bftw_list_sort(struct bftw_list *queue) { if (strcoll(lf->name, rf->name) <= 0) { SLIST_POP(&left); - SLIST_APPEND(queue, lf); + SLIST_APPEND(list, lf); } else { SLIST_POP(&right); - SLIST_APPEND(queue, rf); + SLIST_APPEND(list, rf); } } - SLIST_EXTEND(queue, &left); - SLIST_EXTEND(queue, &right); + SLIST_EXTEND(list, &left); + SLIST_EXTEND(list, &right); } /** Finish adding a batch of files. */ @@ -1024,112 +986,119 @@ static void bftw_batch_finish(struct bftw_state *state) { bftw_list_sort(&state->batch); } - if (state->strategy == BFTW_DFS) { - SLIST_EXTEND(&state->batch, &state->queue); + if (state->strategy != BFTW_BFS) { + SLIST_EXTEND(&state->batch, &state->files); } - SLIST_EXTEND(&state->queue, &state->batch); + SLIST_EXTEND(&state->files, &state->batch); } -/** - * Streaming mode: visit files as they are encountered. - */ -static int bftw_stream(const struct bftw_args *args) { - struct bftw_state state; - if (bftw_state_init(&state, args) != 0) { +/** Close the current directory. */ +static int bftw_closedir(struct bftw_state *state) { + if (bftw_gc(state, BFTW_VISIT_ALL) != 0) { return -1; } - assert(!(state.flags & (BFTW_SORT | BFTW_BUFFER))); + bftw_batch_finish(state); + return 0; +} - for (size_t i = 0; i < args->npaths; ++i) { - const char *path = args->paths[i]; +/** Fill file identity information from an ftwbuf. */ +static void bftw_save_ftwbuf(struct bftw_file *file, const struct BFTW *ftwbuf) { + file->type = ftwbuf->type; - switch (bftw_visit(&state, path, BFTW_PRE)) { - case BFTW_CONTINUE: - break; - case BFTW_PRUNE: - continue; - case BFTW_STOP: - goto done; + const struct bfs_stat *statbuf = ftwbuf->stat_cache.buf; + if (!statbuf || (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) { + statbuf = ftwbuf->lstat_cache.buf; + } + if (statbuf) { + file->dev = statbuf->dev; + file->ino = statbuf->ino; + } +} + +/** Visit and/or enqueue the current file. */ +static int bftw_visit(struct bftw_state *state, const char *name) { + struct bftw_file *file = state->file; + + if (name && (state->flags & BFTW_BUFFER)) { + file = bftw_file_new(file, name); + if (!file) { + state->error = errno; + return -1; } - if (bftw_push(&state, path, true) != 0) { - goto done; + if (state->de) { + file->type = state->de->type; } - } - bftw_batch_finish(&state); - while (bftw_next(&state) > 0) { - bftw_opendir(&state); + SLIST_APPEND(&state->batch, file); + return 0; + } - while (bftw_readdir(&state) > 0) { - const char *name = state.de->name; + switch (bftw_call_back(state, name, BFTW_PRE)) { + case BFTW_CONTINUE: + if (name) { + file = bftw_file_new(state->file, name); + } else { + state->file = NULL; + } + if (!file) { + state->error = errno; + return -1; + } - switch (bftw_visit(&state, name, BFTW_PRE)) { - case BFTW_CONTINUE: - break; - case BFTW_PRUNE: - continue; - case BFTW_STOP: - goto done; - } + bftw_save_ftwbuf(file, &state->ftwbuf); + SLIST_APPEND(&state->dirs, file); + return 0; - if (bftw_push(&state, name, true) != 0) { - goto done; - } + case BFTW_PRUNE: + if (file && !name) { + return bftw_gc(state, BFTW_VISIT_PARENTS); + } else { + return 0; } - bftw_batch_finish(&state); - if (bftw_gc(&state, BFTW_VISIT_ALL) == BFTW_STOP) { - goto done; - } + default: + return -1; } - -done: - return bftw_state_destroy(&state); } /** - * Batching mode: queue up all children before visiting them. + * bftw() implementation for simple breadth-/depth-first search. */ -static int bftw_batch(const struct bftw_args *args) { +static int bftw_impl(const struct bftw_args *args) { struct bftw_state state; if (bftw_state_init(&state, args) != 0) { return -1; } for (size_t i = 0; i < args->npaths; ++i) { - if (bftw_push(&state, args->paths[i], false) != 0) { + if (bftw_visit(&state, args->paths[i]) != 0) { goto done; } } bftw_batch_finish(&state); - while (bftw_next(&state) > 0) { - enum bftw_gc_flags gcflags = BFTW_VISIT_ALL; - - switch (bftw_visit(&state, NULL, BFTW_PRE)) { - case BFTW_CONTINUE: - break; - case BFTW_PRUNE: - gcflags &= ~BFTW_VISIT_FILE; - goto next; - case BFTW_STOP: - goto done; - } - - bftw_opendir(&state); - - while (bftw_readdir(&state) > 0) { - if (bftw_push(&state, state.de->name, false) != 0) { + while (true) { + while (bftw_pop_dir(&state)) { + if (bftw_opendir(&state) != 0) { + goto done; + } + while (bftw_readdir(&state) > 0) { + if (bftw_visit(&state, state.de->name) != 0) { + goto done; + } + } + if (bftw_closedir(&state) != 0) { goto done; } } - bftw_batch_finish(&state); - next: - if (bftw_gc(&state, gcflags) == BFTW_STOP) { - goto done; + if (!bftw_pop_file(&state)) { + break; + } + if (bftw_visit(&state, NULL) != 0) { + break; } } @@ -1137,15 +1106,6 @@ done: return bftw_state_destroy(&state); } -/** Select bftw_stream() or bftw_batch() appropriately. */ -static int bftw_auto(const struct bftw_args *args) { - if (args->flags & (BFTW_SORT | BFTW_BUFFER)) { - return bftw_batch(args); - } else { - return bftw_stream(args); - } -} - /** * Iterative deepening search state. */ @@ -1247,7 +1207,6 @@ static void bftw_ids_init(const struct bftw_args *args, struct bftw_ids_state *s ids_args->callback = bftw_ids_callback; ids_args->ptr = state; ids_args->flags &= ~BFTW_POST_ORDER; - ids_args->strategy = BFTW_DFS; } /** Finish an iterative deepening search. */ @@ -1277,7 +1236,7 @@ static int bftw_ids(const struct bftw_args *args) { while (!state.quit && !state.bottom) { state.bottom = true; - if (bftw_auto(&ids_args) != 0) { + if (bftw_impl(&ids_args) != 0) { state.error = errno; state.quit = true; } @@ -1294,7 +1253,7 @@ static int bftw_ids(const struct bftw_args *args) { --state.max_depth; --state.min_depth; - if (bftw_auto(&ids_args) != 0) { + if (bftw_impl(&ids_args) != 0) { state.error = errno; state.quit = true; } @@ -1315,7 +1274,7 @@ static int bftw_eds(const struct bftw_args *args) { while (!state.quit && !state.bottom) { state.bottom = true; - if (bftw_auto(&ids_args) != 0) { + if (bftw_impl(&ids_args) != 0) { state.error = errno; state.quit = true; } @@ -1329,7 +1288,7 @@ static int bftw_eds(const struct bftw_args *args) { state.min_depth = 0; ids_args.flags |= BFTW_POST_ORDER; - if (bftw_auto(&ids_args) != 0) { + if (bftw_impl(&ids_args) != 0) { state.error = errno; } } @@ -1340,9 +1299,8 @@ static int bftw_eds(const struct bftw_args *args) { int bftw(const struct bftw_args *args) { switch (args->strategy) { case BFTW_BFS: - return bftw_auto(args); case BFTW_DFS: - return bftw_batch(args); + return bftw_impl(args); case BFTW_IDS: return bftw_ids(args); case BFTW_EDS: -- cgit v1.2.3 From 3929ddedca049ac6f9dcbe88f85233b6ec1a415a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 10 May 2023 15:43:31 -0400 Subject: config: s/BFS_FLEX_SIZEOF/flex_sizeof/ --- src/bftw.c | 2 +- src/config.h | 4 ++-- src/dstring.c | 2 +- src/trie.c | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index e4dc411..d2f1e36 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -178,7 +178,7 @@ static void bftw_cache_destroy(struct bftw_cache *cache) { /** Create a new bftw_file. */ static struct bftw_file *bftw_file_new(struct bftw_file *parent, const char *name) { size_t namelen = strlen(name); - size_t size = BFS_FLEX_SIZEOF(struct bftw_file, name, namelen + 1); + size_t size = flex_sizeof(struct bftw_file, name, namelen + 1); struct bftw_file *file = malloc(size); if (!file) { diff --git a/src/config.h b/src/config.h index 60bc930..aa54491 100644 --- a/src/config.h +++ b/src/config.h @@ -136,7 +136,7 @@ */ #define countof(array) (sizeof(array) / sizeof(0[array])) -// Lower bound on BFS_FLEX_SIZEOF() +// Lower bound on flex_sizeof() #define BFS_FLEX_LB(type, member, length) (offsetof(type, member) + sizeof(((type *)NULL)->member[0]) * (length)) // Maximum macro for BFS_FLEX_SIZE() @@ -153,7 +153,7 @@ * @param length * The length of the flexible array. */ -#define BFS_FLEX_SIZEOF(type, member, length) \ +#define flex_sizeof(type, member, length) \ (sizeof(type) <= BFS_FLEX_LB(type, member, 0) \ ? BFS_FLEX_LB(type, member, length) \ : BFS_FLEX_MAX(sizeof(type), BFS_FLEX_LB(type, member, length))) diff --git a/src/dstring.c b/src/dstring.c index 9112e54..05efb10 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -24,7 +24,7 @@ static struct dstring *dstrheader(const char *dstr) { /** Get the correct size for a dstring with the given capacity. */ static size_t dstrsize(size_t capacity) { - return BFS_FLEX_SIZEOF(struct dstring, data, capacity + 1); + return flex_sizeof(struct dstring, data, capacity + 1); } /** Allocate a dstring with the given contents. */ diff --git a/src/trie.c b/src/trie.c index 9f2e45f..bfe97e6 100644 --- a/src/trie.c +++ b/src/trie.c @@ -336,7 +336,7 @@ struct trie_leaf *trie_find_prefix(const struct trie *trie, const char *key) { /** Create a new leaf, holding a copy of the given key. */ static struct trie_leaf *trie_leaf_alloc(struct trie *trie, const void *key, size_t length) { - struct trie_leaf *leaf = malloc(BFS_FLEX_SIZEOF(struct trie_leaf, key, length)); + struct trie_leaf *leaf = malloc(flex_sizeof(struct trie_leaf, key, length)); if (!leaf) { return NULL; } @@ -363,7 +363,7 @@ static size_t trie_node_size(unsigned int size) { // Node size must be a power of two assert(is_power_of_two(size)); - return BFS_FLEX_SIZEOF(struct trie_node, children, size); + return flex_sizeof(struct trie_node, children, size); } #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ -- cgit v1.2.3 From 87d8d5928325f951f40b5f87292a17586b85943b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 10 May 2023 15:44:59 -0400 Subject: config: s/BFS_FALLTHROUGH/fallthru/ --- src/bftw.c | 2 +- src/config.h | 6 +++--- src/eval.c | 4 ++-- src/parse.c | 18 +++++++++--------- src/printf.c | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index d2f1e36..56701a7 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -758,7 +758,7 @@ static enum bftw_action bftw_call_back(struct bftw_state *state, const char *nam if ((state->flags & BFTW_PRUNE_MOUNTS) && bftw_is_mount(state, name)) { return BFTW_PRUNE; } - BFS_FALLTHROUGH; + fallthru; case BFTW_PRUNE: case BFTW_STOP: return ret; diff --git a/src/config.h b/src/config.h index b9f2638..229bafb 100644 --- a/src/config.h +++ b/src/config.h @@ -173,11 +173,11 @@ * Silence compiler warnings about switch/case fall-throughs. */ #if __has_c_attribute(fallthrough) -# define BFS_FALLTHROUGH [[fallthrough]] +# define fallthru [[fallthrough]] #elif __has_attribute(fallthrough) -# define BFS_FALLTHROUGH __attribute__((fallthrough)) +# define fallthru __attribute__((fallthrough)) #else -# define BFS_FALLTHROUGH ((void)0) +# define fallthru ((void)0) #endif /** diff --git a/src/eval.c b/src/eval.c index 687ba32..7444ec9 100644 --- a/src/eval.c +++ b/src/eval.c @@ -241,10 +241,10 @@ bool eval_time(const struct bfs_expr *expr, struct bfs_eval *state) { switch (expr->time_unit) { case BFS_DAYS: diff /= 60*24; - BFS_FALLTHROUGH; + fallthru; case BFS_MINUTES: diff /= 60; - BFS_FALLTHROUGH; + fallthru; case BFS_SECONDS: break; } diff --git a/src/parse.c b/src/parse.c index 7dd851a..15f38a4 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1082,16 +1082,16 @@ static struct bfs_expr *parse_time(struct parser_state *state, int field, int ar switch (*tail) { case 'w': time *= 7; - BFS_FALLTHROUGH; + fallthru; case 'd': time *= 24; - BFS_FALLTHROUGH; + fallthru; case 'h': time *= 60; - BFS_FALLTHROUGH; + fallthru; case 'm': time *= 60; - BFS_FALLTHROUGH; + fallthru; case 's': break; default: @@ -1974,7 +1974,7 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct case MODE_CLAUSE: who = 0; mstate = MODE_WHO; - BFS_FALLTHROUGH; + fallthru; case MODE_WHO: switch (*i) { @@ -2001,7 +2001,7 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct case MODE_EQUALS: expr->file_mode &= ~who; expr->dir_mode &= ~who; - BFS_FALLTHROUGH; + fallthru; case MODE_PLUS: expr->file_mode |= file_change; expr->dir_mode |= dir_change; @@ -2011,7 +2011,7 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct expr->dir_mode &= ~dir_change; break; } - BFS_FALLTHROUGH; + fallthru; case MODE_ACTION: if (who == 0) { @@ -2093,7 +2093,7 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct break; case 'x': file_change |= who & 0111; - BFS_FALLTHROUGH; + fallthru; case 'X': dir_change |= who & 0111; break; @@ -2156,7 +2156,7 @@ static struct bfs_expr *parse_perm(struct parser_state *state, int field, int ar ++mode; break; } - BFS_FALLTHROUGH; + fallthru; default: expr->mode_cmp = BFS_MODE_EQUAL; break; diff --git a/src/printf.c b/src/printf.c index 726d54d..454fbee 100644 --- a/src/printf.c +++ b/src/printf.c @@ -680,7 +680,7 @@ int bfs_printf_parse(const struct bfs_ctx *ctx, struct bfs_expr *expr, const cha case '0': case '+': must_be_numeric = true; - BFS_FALLTHROUGH; + fallthru; case ' ': case '-': if (strchr(directive.str, c)) { -- cgit v1.2.3 From 59f87eed2b930af2f31fd1d1fb2589f80f426ee0 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 11 May 2023 10:12:35 -0400 Subject: config: Provide and In anticipation of C23, since those headers won't be necessary any more. --- src/bfstd.c | 1 - src/bfstd.h | 2 +- src/bftw.c | 1 - src/color.c | 1 - src/color.h | 1 - src/config.h | 6 +++++- src/ctx.h | 2 +- src/diag.h | 1 - src/dir.c | 2 -- src/eval.h | 2 +- src/exec.c | 1 - src/expr.h | 2 +- src/fsade.h | 1 - src/main.c | 2 +- src/mtab.c | 1 - src/mtab.h | 2 +- src/opt.c | 1 - src/parse.c | 1 - src/printf.c | 1 - src/pwcache.c | 2 +- src/stat.c | 1 - src/trie.c | 1 - src/trie.h | 1 - src/xspawn.c | 1 - src/xtime.c | 2 +- tests/xtimegm.c | 2 +- tests/xtouch.c | 2 +- 27 files changed, 15 insertions(+), 28 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bfstd.c b/src/bfstd.c index 1dc322b..932f2c4 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include diff --git a/src/bfstd.h b/src/bfstd.h index 028e4e6..e4fd1f1 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -8,7 +8,7 @@ #ifndef BFS_BFSTD_H #define BFS_BFSTD_H -#include +#include "config.h" #include // #include diff --git a/src/bftw.c b/src/bftw.c index 56701a7..14805de 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include diff --git a/src/color.c b/src/color.c index 589e631..eeadf98 100644 --- a/src/color.c +++ b/src/color.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include diff --git a/src/color.h b/src/color.h index 737c34b..3db2b07 100644 --- a/src/color.h +++ b/src/color.h @@ -10,7 +10,6 @@ #include "config.h" #include -#include #include /** diff --git a/src/config.h b/src/config.h index 229bafb..4408feb 100644 --- a/src/config.h +++ b/src/config.h @@ -8,9 +8,13 @@ #ifndef BFS_CONFIG_H #define BFS_CONFIG_H -#include #include +#if __STDC_VERSION__ < 202311L +# include +# include +#endif + // bfs packaging configuration #ifndef BFS_COMMAND diff --git a/src/ctx.h b/src/ctx.h index 4c748b7..0dc9f08 100644 --- a/src/ctx.h +++ b/src/ctx.h @@ -9,8 +9,8 @@ #define BFS_CTX_H #include "bftw.h" +#include "config.h" #include "trie.h" -#include #include #include #include diff --git a/src/diag.h b/src/diag.h index 2952e30..987d4b4 100644 --- a/src/diag.h +++ b/src/diag.h @@ -11,7 +11,6 @@ #include "ctx.h" #include "config.h" #include -#include struct bfs_expr; diff --git a/src/dir.c b/src/dir.c index eb6e3e0..30db5df 100644 --- a/src/dir.c +++ b/src/dir.c @@ -8,8 +8,6 @@ #include #include #include -#include -#include #include #include #include diff --git a/src/eval.h b/src/eval.h index 3d70319..bdb9440 100644 --- a/src/eval.h +++ b/src/eval.h @@ -9,7 +9,7 @@ #ifndef BFS_EVAL_H #define BFS_EVAL_H -#include +#include "config.h" struct bfs_ctx; struct bfs_expr; diff --git a/src/exec.c b/src/exec.c index 6bde1c1..7f22d36 100644 --- a/src/exec.c +++ b/src/exec.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include diff --git a/src/expr.h b/src/expr.h index 1628cac..356689d 100644 --- a/src/expr.h +++ b/src/expr.h @@ -9,9 +9,9 @@ #define BFS_EXPR_H #include "color.h" +#include "config.h" #include "eval.h" #include "stat.h" -#include #include #include #include diff --git a/src/fsade.h b/src/fsade.h index 557da26..0d9ecaf 100644 --- a/src/fsade.h +++ b/src/fsade.h @@ -10,7 +10,6 @@ #define BFS_FSADE_H #include "config.h" -#include #define BFS_CAN_CHECK_ACL BFS_USE_SYS_ACL_H diff --git a/src/main.c b/src/main.c index 4f99580..24a5035 100644 --- a/src/main.c +++ b/src/main.c @@ -40,13 +40,13 @@ */ #include "bfstd.h" +#include "config.h" #include "ctx.h" #include "eval.h" #include "parse.h" #include #include #include -#include #include #include #include diff --git a/src/mtab.c b/src/mtab.c index 27f1743..1d1ad94 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -9,7 +9,6 @@ #include "trie.h" #include #include -#include #include #include #include diff --git a/src/mtab.h b/src/mtab.h index 5dfdf6c..ca4372c 100644 --- a/src/mtab.h +++ b/src/mtab.h @@ -8,7 +8,7 @@ #ifndef BFS_MTAB_H #define BFS_MTAB_H -#include +#include "config.h" struct bfs_stat; diff --git a/src/opt.c b/src/opt.c index 731dd10..4ce9425 100644 --- a/src/opt.c +++ b/src/opt.c @@ -39,7 +39,6 @@ #include #include #include -#include #include #include #include diff --git a/src/parse.c b/src/parse.c index 15f38a4..55f1e74 100644 --- a/src/parse.c +++ b/src/parse.c @@ -37,7 +37,6 @@ #include #include #include -#include #include #include #include diff --git a/src/printf.c b/src/printf.c index 454fbee..9ccc216 100644 --- a/src/printf.c +++ b/src/printf.c @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include diff --git a/src/pwcache.c b/src/pwcache.c index 5026dee..f52e4e1 100644 --- a/src/pwcache.c +++ b/src/pwcache.c @@ -2,12 +2,12 @@ // SPDX-License-Identifier: 0BSD #include "pwcache.h" +#include "config.h" #include "darray.h" #include "trie.h" #include #include #include -#include #include #include #include diff --git a/src/stat.c b/src/stat.c index f3d9046..7973d71 100644 --- a/src/stat.c +++ b/src/stat.c @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include diff --git a/src/trie.c b/src/trie.c index bfe97e6..e0e00ff 100644 --- a/src/trie.c +++ b/src/trie.c @@ -86,7 +86,6 @@ #include "list.h" #include #include -#include #include #include #include diff --git a/src/trie.h b/src/trie.h index 58974aa..6bd211e 100644 --- a/src/trie.h +++ b/src/trie.h @@ -5,7 +5,6 @@ #define BFS_TRIE_H #include "config.h" -#include #include #include diff --git a/src/xspawn.c b/src/xspawn.c index a6d18a3..00fb76e 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -7,7 +7,6 @@ #include "list.h" #include #include -#include #include #include #include diff --git a/src/xtime.c b/src/xtime.c index 82690d0..406d694 100644 --- a/src/xtime.c +++ b/src/xtime.c @@ -2,9 +2,9 @@ // SPDX-License-Identifier: 0BSD #include "xtime.h" +#include "config.h" #include #include -#include #include #include #include diff --git a/tests/xtimegm.c b/tests/xtimegm.c index bab64ba..b2479b7 100644 --- a/tests/xtimegm.c +++ b/tests/xtimegm.c @@ -2,7 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "../src/xtime.h" -#include +#include "../src/config.h" #include #include #include diff --git a/tests/xtouch.c b/tests/xtouch.c index 7e29547..50416ba 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -2,10 +2,10 @@ // SPDX-License-Identifier: 0BSD #include "../src/bfstd.h" +#include "../src/config.h" #include "../src/xtime.h" #include #include -#include #include #include #include -- cgit v1.2.3 From 526133c11eb9a26a4cffb20bcd10bcbb36d940de Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 May 2023 16:44:30 -0400 Subject: Switch from assert() to bfs_assert()/bfs_verify() --- src/bfstd.c | 3 +- src/bftw.c | 28 ++++++++-------- src/color.c | 5 ++- src/diag.c | 3 +- src/dir.c | 4 +-- src/eval.c | 11 +++---- src/exec.c | 7 ++-- src/opt.c | 13 ++++---- src/parse.c | 7 ++-- src/printf.c | 5 ++- src/trie.c | 33 ++++++++++--------- src/xregex.c | 4 +-- tests/bfstd.c | 13 ++++---- tests/bit.c | 100 +++++++++++++++++++++++++++++----------------------------- tests/trie.c | 56 ++++++++++++++++---------------- 15 files changed, 140 insertions(+), 152 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bfstd.c b/src/bfstd.c index 91383a2..37f5276 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -5,7 +5,6 @@ #include "config.h" #include "diag.h" #include "xregex.h" -#include #include #include #include @@ -393,7 +392,7 @@ void close_quietly(int fd) { int xclose(int fd) { int ret = close(fd); if (ret != 0) { - assert(errno != EBADF); + bfs_verify(errno != EBADF); } return ret; } diff --git a/src/bftw.c b/src/bftw.c index 14805de..6ec5cfa 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -19,13 +19,13 @@ #include "bftw.h" #include "bfstd.h" #include "config.h" +#include "diag.h" #include "dir.h" #include "dstring.h" #include "list.h" #include "mtab.h" #include "stat.h" #include "trie.h" -#include #include #include #include @@ -114,7 +114,7 @@ static void bftw_cache_remove(struct bftw_cache *cache, struct bftw_file *file) /** Close a bftw_file. */ static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) { - assert(file->fd >= 0); + bfs_assert(file->fd >= 0); if (LIST_ATTACHED(cache, file, lru)) { bftw_cache_remove(cache, file); @@ -137,7 +137,7 @@ static int bftw_cache_pop(struct bftw_cache *cache) { /** Add a bftw_file to the cache. */ static int bftw_cache_add(struct bftw_cache *cache, struct bftw_file *file) { - assert(file->fd >= 0); + bfs_assert(file->fd >= 0); if (cache->capacity == 0 && bftw_cache_pop(cache) != 0) { bftw_file_close(cache, file); @@ -145,7 +145,7 @@ static int bftw_cache_add(struct bftw_cache *cache, struct bftw_file *file) { return -1; } - assert(cache->capacity > 0); + bfs_assert(cache->capacity > 0); --cache->capacity; LIST_INSERT(cache, cache->target, file, lru); @@ -169,9 +169,9 @@ static size_t bftw_child_nameoff(const struct bftw_file *parent) { /** Destroy a cache. */ static void bftw_cache_destroy(struct bftw_cache *cache) { - assert(!cache->head); - assert(!cache->tail); - assert(!cache->target); + bfs_assert(!cache->head); + bfs_assert(!cache->tail); + bfs_assert(!cache->target); } /** Create a new bftw_file. */ @@ -230,7 +230,7 @@ static struct bftw_file *bftw_file_new(struct bftw_file *parent, const char *nam * The opened file descriptor, or negative on error. */ static int bftw_file_openat(struct bftw_cache *cache, struct bftw_file *file, struct bftw_file *base, const char *at_path) { - assert(file->fd < 0); + bfs_assert(file->fd < 0); int at_fd = AT_FDCWD; if (base) { @@ -332,7 +332,7 @@ static struct bfs_dir *bftw_file_opendir(struct bftw_cache *cache, struct bftw_f /** Free a bftw_file. */ static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) { - assert(file->refcount == 0); + bfs_assert(file->refcount == 0); if (file->fd >= 0) { bftw_file_close(cache, file); @@ -770,7 +770,7 @@ static enum bftw_action bftw_call_back(struct bftw_state *state, const char *nam /** Pop a directory to read from the queue. */ static bool bftw_pop_dir(struct bftw_state *state) { - assert(!state->file); + bfs_assert(!state->file); if (!state->dirs.head) { return false; @@ -787,7 +787,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { /** Pop a file to visit from the queue. */ static bool bftw_pop_file(struct bftw_state *state) { - assert(!state->file); + bfs_assert(!state->file); state->file = state->files.head; if (state->file) { @@ -802,8 +802,8 @@ static bool bftw_pop_file(struct bftw_state *state) { * Open the current directory. */ static int bftw_opendir(struct bftw_state *state) { - assert(!state->dir); - assert(!state->de); + bfs_assert(!state->dir); + bfs_assert(!state->de); state->direrror = 0; @@ -863,7 +863,7 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { if (state->dir) { struct bftw_file *file = state->file; - assert(file && file->fd >= 0); + bfs_assert(file && file->fd >= 0); if (file->refcount > 1) { // Keep the fd around if any subdirectories exist diff --git a/src/color.c b/src/color.c index 43ea9a4..a723084 100644 --- a/src/color.c +++ b/src/color.c @@ -12,7 +12,6 @@ #include "fsade.h" #include "stat.h" #include "trie.h" -#include #include #include #include @@ -699,7 +698,7 @@ static int print_colored(CFILE *cfile, const char *esc, const char *str, size_t /** Find the offset of the first broken path component. */ static ssize_t first_broken_offset(const char *path, const struct BFTW *ftwbuf, enum bfs_stat_flags flags, size_t max) { ssize_t ret = max; - assert(ret >= 0); + bfs_assert(ret >= 0); if (bftw_type(ftwbuf, flags) != BFS_ERROR) { goto out; @@ -1100,7 +1099,7 @@ static int cbuff(CFILE *cfile, const char *format, ...) { } int cvfprintf(CFILE *cfile, const char *format, va_list args) { - assert(dstrlen(cfile->buffer) == 0); + bfs_assert(dstrlen(cfile->buffer) == 0); int ret = -1; if (cvbuff(cfile, format, args) == 0) { diff --git a/src/diag.c b/src/diag.c index d7ffaa6..04e3678 100644 --- a/src/diag.c +++ b/src/diag.c @@ -6,7 +6,6 @@ #include "ctx.h" #include "color.h" #include "expr.h" -#include #include #include #include @@ -117,7 +116,7 @@ static bool highlight_expr_recursive(const struct bfs_ctx *ctx, const struct bfs for (size_t i = 0; i < ctx->argc; ++i) { if (&ctx->argv[i] == expr->argv) { for (size_t j = 0; j < expr->argc; ++j) { - assert(i + j < ctx->argc); + bfs_assert(i + j < ctx->argc); args[i + j] = true; ret = true; } diff --git a/src/dir.c b/src/dir.c index 6739f10..d9ee63b 100644 --- a/src/dir.c +++ b/src/dir.c @@ -4,7 +4,7 @@ #include "dir.h" #include "bfstd.h" #include "config.h" -#include +#include "diag.h" #include #include #include @@ -249,7 +249,7 @@ int bfs_closedir(struct bfs_dir *dir) { int ret = xclose(dir->fd); #else int ret = closedir(dir->dir); - assert(ret == 0 || errno != EBADF); + bfs_verify(ret == 0 || errno != EBADF); #endif free(dir); return ret; diff --git a/src/eval.c b/src/eval.c index 6cad3a9..342debd 100644 --- a/src/eval.c +++ b/src/eval.c @@ -26,7 +26,6 @@ #include "trie.h" #include "xregex.h" #include "xtime.h" -#include #include #include #include @@ -990,7 +989,7 @@ static bool eval_expr(struct bfs_expr *expr, struct bfs_eval *state) { } } - assert(!state->quit); + bfs_assert(!state->quit); bool ret = expr->eval_fn(expr, state); @@ -1006,10 +1005,10 @@ static bool eval_expr(struct bfs_expr *expr, struct bfs_eval *state) { } if (bfs_expr_never_returns(expr)) { - assert(state->quit); + bfs_assert(state->quit); } else if (!state->quit) { - assert(!expr->always_true || ret); - assert(!expr->always_false || !ret); + bfs_assert(!expr->always_true || ret); + bfs_assert(!expr->always_false || !ret); } return ret; @@ -1511,7 +1510,7 @@ static void dump_bftw_flags(enum bftw_flags flags) { DEBUG_FLAG(flags, BFTW_SORT); DEBUG_FLAG(flags, BFTW_BUFFER); - assert(!flags); + bfs_assert(flags == 0, "Missing bftw flag 0x%X", flags); } /** diff --git a/src/exec.c b/src/exec.c index 7f22d36..5912ad6 100644 --- a/src/exec.c +++ b/src/exec.c @@ -10,7 +10,6 @@ #include "diag.h" #include "dstring.h" #include "xspawn.h" -#include #include #include #include @@ -276,12 +275,12 @@ static void bfs_exec_free_arg(char *arg, const char *tmpl) { /** Open a file to use as the working directory. */ static int bfs_exec_openwd(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { - assert(execbuf->wd_fd < 0); - assert(!execbuf->wd_path); + bfs_assert(execbuf->wd_fd < 0); + bfs_assert(!execbuf->wd_path); if (ftwbuf->at_fd != AT_FDCWD) { // Rely on at_fd being the immediate parent - assert(xbaseoff(ftwbuf->at_path) == 0); + bfs_assert(xbaseoff(ftwbuf->at_path) == 0); execbuf->wd_fd = ftwbuf->at_fd; if (!(execbuf->flags & BFS_EXEC_MULTI)) { diff --git a/src/opt.c b/src/opt.c index 4ce9425..4699af4 100644 --- a/src/opt.c +++ b/src/opt.c @@ -35,7 +35,6 @@ #include "exec.h" #include "expr.h" #include "pwcache.h" -#include #include #include #include @@ -308,7 +307,7 @@ struct opt_state { /** Log an optimization. */ BFS_FORMATTER(3, 4) static bool opt_debug(const struct opt_state *state, int level, const char *format, ...) { - assert(state->ctx->optlevel >= level); + bfs_assert(state->ctx->optlevel >= level); if (bfs_debug(state->ctx, DEBUG_OPT, "${cyn}-O%d${rs}: ", level)) { va_list args; @@ -387,7 +386,7 @@ static struct bfs_expr *de_morgan(const struct opt_state *state, struct bfs_expr has_parent = false; } - assert(expr->eval_fn == eval_and || expr->eval_fn == eval_or); + bfs_assert(expr->eval_fn == eval_and || expr->eval_fn == eval_or); if (expr->eval_fn == eval_and) { expr->eval_fn = eval_or; expr->argv = &fake_or_arg; @@ -446,7 +445,7 @@ static struct bfs_expr *optimize_expr_recursive(struct opt_state *state, struct * Optimize a negation. */ static struct bfs_expr *optimize_not_expr(const struct opt_state *state, struct bfs_expr *expr) { - assert(expr->eval_fn == eval_not); + bfs_assert(expr->eval_fn == eval_not); struct bfs_expr *rhs = expr->rhs; @@ -498,7 +497,7 @@ fail: /** Optimize a conjunction. */ static struct bfs_expr *optimize_and_expr(const struct opt_state *state, struct bfs_expr *expr) { - assert(expr->eval_fn == eval_and); + bfs_assert(expr->eval_fn == eval_and); struct bfs_expr *lhs = expr->lhs; struct bfs_expr *rhs = expr->rhs; @@ -569,7 +568,7 @@ fail: /** Optimize a disjunction. */ static struct bfs_expr *optimize_or_expr(const struct opt_state *state, struct bfs_expr *expr) { - assert(expr->eval_fn == eval_or); + bfs_assert(expr->eval_fn == eval_or); struct bfs_expr *lhs = expr->lhs; struct bfs_expr *rhs = expr->rhs; @@ -673,7 +672,7 @@ static struct bfs_expr *ignore_result(const struct opt_state *state, struct bfs_ /** Optimize a comma expression. */ static struct bfs_expr *optimize_comma_expr(const struct opt_state *state, struct bfs_expr *expr) { - assert(expr->eval_fn == eval_comma); + bfs_assert(expr->eval_fn == eval_comma); struct bfs_expr *lhs = expr->lhs; struct bfs_expr *rhs = expr->rhs; diff --git a/src/parse.c b/src/parse.c index 807892c..1a04e68 100644 --- a/src/parse.c +++ b/src/parse.c @@ -29,7 +29,6 @@ #include "xregex.h" #include "xspawn.h" #include "xtime.h" -#include #include #include #include @@ -134,7 +133,7 @@ static struct bfs_expr *new_unary_expr(bfs_eval_fn *eval_fn, struct bfs_expr *rh expr->lhs = NULL; expr->rhs = rhs; - assert(bfs_expr_is_parent(expr)); + bfs_assert(bfs_expr_is_parent(expr)); expr->persistent_fds = rhs->persistent_fds; expr->ephemeral_fds = rhs->ephemeral_fds; @@ -154,7 +153,7 @@ static struct bfs_expr *new_binary_expr(bfs_eval_fn *eval_fn, struct bfs_expr *l expr->lhs = lhs; expr->rhs = rhs; - assert(bfs_expr_is_parent(expr)); + bfs_assert(bfs_expr_is_parent(expr)); expr->persistent_fds = lhs->persistent_fds + rhs->persistent_fds; if (lhs->ephemeral_fds > rhs->ephemeral_fds) { @@ -263,7 +262,7 @@ static void init_highlight(const struct bfs_ctx *ctx, bool *args) { static void highlight_args(const struct bfs_ctx *ctx, char **argv, size_t argc, bool *args) { size_t i = argv - ctx->argv; for (size_t j = 0; j < argc; ++j) { - assert(i + j < ctx->argc); + bfs_assert(i + j < ctx->argc); args[i + j] = true; } } diff --git a/src/printf.c b/src/printf.c index 9ccc216..6520d2d 100644 --- a/src/printf.c +++ b/src/printf.c @@ -16,7 +16,6 @@ #include "pwcache.h" #include "stat.h" #include "xtime.h" -#include #include #include #include @@ -74,7 +73,7 @@ static bool should_color(CFILE *cfile, const struct bfs_printf *directive) { #define BFS_PRINTF_BUF(buf, format, ...) \ char buf[256]; \ int ret = snprintf(buf, sizeof(buf), format, __VA_ARGS__); \ - assert(ret >= 0 && (size_t)ret < sizeof(buf)); \ + bfs_assert(ret >= 0 && (size_t)ret < sizeof(buf)); \ (void)ret /** @@ -190,7 +189,7 @@ static int bfs_printf_strftime(CFILE *cfile, const struct bfs_printf *directive, break; } - assert(ret >= 0 && (size_t)ret < sizeof(buf)); + bfs_assert(ret >= 0 && (size_t)ret < sizeof(buf)); (void)ret; return dyn_fprintf(cfile->file, directive, buf); diff --git a/src/trie.c b/src/trie.c index a2921de..8543eb1 100644 --- a/src/trie.c +++ b/src/trie.c @@ -86,7 +86,6 @@ #include "config.h" #include "diag.h" #include "list.h" -#include #include #include #include @@ -139,27 +138,27 @@ static bool trie_is_leaf(uintptr_t ptr) { /** Decode a pointer to a leaf. */ static struct trie_leaf *trie_decode_leaf(uintptr_t ptr) { - assert(trie_is_leaf(ptr)); + bfs_assert(trie_is_leaf(ptr)); return (struct trie_leaf *)(ptr ^ 1); } /** Encode a pointer to a leaf. */ static uintptr_t trie_encode_leaf(const struct trie_leaf *leaf) { uintptr_t ptr = (uintptr_t)leaf ^ 1; - assert(trie_is_leaf(ptr)); + bfs_assert(trie_is_leaf(ptr)); return ptr; } /** Decode a pointer to an internal node. */ static struct trie_node *trie_decode_node(uintptr_t ptr) { - assert(!trie_is_leaf(ptr)); + bfs_assert(!trie_is_leaf(ptr)); return (struct trie_node *)ptr; } /** Encode a pointer to an internal node. */ static uintptr_t trie_encode_node(const struct trie_node *node) { uintptr_t ptr = (uintptr_t)node; - assert(!trie_is_leaf(ptr)); + bfs_assert(!trie_is_leaf(ptr)); return ptr; } @@ -341,9 +340,9 @@ static void trie_leaf_free(struct trie *trie, struct trie_leaf *leaf) { /** Compute the size of a trie node with a certain number of children. */ static size_t trie_node_size(unsigned int size) { // Empty nodes aren't supported - assert(size > 0); + bfs_assert(size > 0); // Node size must be a power of two - assert(has_single_bit(size)); + bfs_assert(has_single_bit(size)); return flex_sizeof(struct trie_node, children, size); } @@ -435,7 +434,7 @@ static struct trie_leaf *trie_node_insert(struct trie *trie, uintptr_t *ptr, str unsigned int bit = 1U << nibble; // The child must not already be present - assert(!(node->bitmap & bit)); + bfs_assert(!(node->bitmap & bit)); node->bitmap |= bit; unsigned int target = count_ones(node->bitmap & (bit - 1)); @@ -474,7 +473,7 @@ static struct trie_leaf *trie_node_insert(struct trie *trie, uintptr_t *ptr, str static uintptr_t *trie_jump(uintptr_t *ptr, const char *key, size_t *offset) { // We only ever need to jump to leaf nodes, since internal nodes are // guaranteed to be within OFFSET_MAX anyway - assert(trie_is_leaf(*ptr)); + bfs_assert(trie_is_leaf(*ptr)); struct trie_node *node = malloc(trie_node_size(1)); if (!node) { @@ -512,7 +511,7 @@ static uintptr_t *trie_jump(uintptr_t *ptr, const char *key, size_t *offset) { static struct trie_leaf *trie_split(struct trie *trie, uintptr_t *ptr, struct trie_leaf *leaf, struct trie_leaf *rep, size_t offset, size_t mismatch) { unsigned char key_nibble = trie_key_nibble(leaf->key, mismatch); unsigned char rep_nibble = trie_key_nibble(rep->key, mismatch); - assert(key_nibble != rep_nibble); + bfs_assert(key_nibble != rep_nibble); struct trie_node *node = malloc(trie_node_size(2)); if (!node) { @@ -570,11 +569,11 @@ static struct trie_leaf *trie_insert_mem_impl(struct trie *trie, const void *key unsigned char nibble = trie_key_nibble(key, offset); unsigned int bit = 1U << nibble; if (node->bitmap & bit) { - assert(offset < mismatch); + bfs_assert(offset < mismatch); unsigned int index = count_ones(node->bitmap & (bit - 1)); ptr = &node->children[index]; } else { - assert(offset == mismatch); + bfs_assert(offset == mismatch); return trie_node_insert(trie, ptr, leaf, nibble); } } @@ -600,7 +599,7 @@ static void trie_free_singletons(struct trie *trie, uintptr_t ptr) { struct trie_node *node = trie_decode_node(ptr); // Make sure the bitmap is a power of two, i.e. it has just one child - assert(has_single_bit(node->bitmap)); + bfs_assert(has_single_bit(node->bitmap)); ptr = node->children[0]; free(node); @@ -651,12 +650,12 @@ static void trie_remove_impl(struct trie *trie, struct trie_leaf *leaf) { while (!trie_is_leaf(*child)) { struct trie_node *node = trie_decode_node(*child); offset += node->offset; - assert((offset >> 1) < leaf->length); + bfs_assert((offset >> 1) < leaf->length); unsigned char nibble = trie_key_nibble(leaf->key, offset); unsigned int bit = 1U << nibble; unsigned int bitmap = node->bitmap; - assert(bitmap & bit); + bfs_assert(bitmap & bit); unsigned int index = count_ones(bitmap & (bit - 1)); // Advance the parent pointer, unless this node had only one child @@ -669,7 +668,7 @@ static void trie_remove_impl(struct trie *trie, struct trie_leaf *leaf) { child = &node->children[index]; } - assert(trie_decode_leaf(*child) == leaf); + bfs_assert(trie_decode_leaf(*child) == leaf); if (!parent) { trie_free_singletons(trie, trie->root); @@ -683,7 +682,7 @@ static void trie_remove_impl(struct trie *trie, struct trie_leaf *leaf) { node->bitmap ^= child_bit; unsigned int parent_size = count_ones(node->bitmap); - assert(parent_size > 0); + bfs_assert(parent_size > 0); if (parent_size == 1 && trie_collapse_node(parent, node, child_index) == 0) { return; } diff --git a/src/xregex.c b/src/xregex.c index a7153b7..1143f23 100644 --- a/src/xregex.c +++ b/src/xregex.c @@ -3,7 +3,7 @@ #include "xregex.h" #include "config.h" -#include +#include "diag.h" #include #include #include @@ -140,7 +140,7 @@ int bfs_regcomp(struct bfs_regex **preg, const char *pattern, enum bfs_regex_typ syntax = ONIG_SYNTAX_GREP; break; } - assert(syntax); + bfs_assert(syntax); OnigOptionType options = syntax->options; if (flags & BFS_REGEX_ICASE) { diff --git a/tests/bfstd.c b/tests/bfstd.c index a986a23..1812a00 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -1,10 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD -#undef NDEBUG #include "../src/bfstd.h" #include "../src/config.h" -#include +#include "../src/diag.h" #include #include #include @@ -39,11 +38,11 @@ int main(void) { alignas(64) int foo; int bar[]; }; - assert(flex_sizeof(struct flexible, bar, 0) >= sizeof(struct flexible)); - assert(flex_sizeof(struct flexible, bar, 16) % alignof(struct flexible) == 0); - assert(flex_sizeof(struct flexible, bar, SIZE_MAX / sizeof(int) + 1) - == align_floor(alignof(struct flexible), SIZE_MAX)); - assert(flex_sizeof_impl(8, 16, 4, 4, 1) == 16); + bfs_verify(flex_sizeof(struct flexible, bar, 0) >= sizeof(struct flexible)); + bfs_verify(flex_sizeof(struct flexible, bar, 16) % alignof(struct flexible) == 0); + bfs_verify(flex_sizeof(struct flexible, bar, SIZE_MAX / sizeof(int) + 1) + == align_floor(alignof(struct flexible), SIZE_MAX)); + bfs_verify(flex_sizeof_impl(8, 16, 4, 4, 1) == 16); // From man 3p basename check_base_dir("usr", ".", "usr"); diff --git a/tests/bit.c b/tests/bit.c index 7a7b0f3..cb339f4 100644 --- a/tests/bit.c +++ b/tests/bit.c @@ -1,11 +1,8 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD -#undef NDEBUG - #include "../src/bit.h" #include "../src/diag.h" -#include #include #include #include @@ -54,68 +51,71 @@ bfs_static_assert(UINTMAX_MAX == UWIDTH_MAX(UINTMAX_WIDTH)); bfs_static_assert(INTMAX_MIN == IWIDTH_MIN(INTMAX_WIDTH)); bfs_static_assert(INTMAX_MAX == IWIDTH_MAX(INTMAX_WIDTH)); +#define verify_eq(a, b) \ + bfs_verify((a) == (b), "(0x%jX) %s != %s (0x%jX)", (uintmax_t)(a), #a, #b, (uintmax_t)(b)) + int main(void) { - assert(bswap((uint8_t)0x12) == 0x12); - assert(bswap((uint16_t)0x1234) == 0x3412); - assert(bswap((uint32_t)0x12345678) == 0x78563412); - assert(bswap((uint64_t)0x1234567812345678) == 0x7856341278563412); - - assert(count_ones(0x0) == 0); - assert(count_ones(0x1) == 1); - assert(count_ones(0x2) == 1); - assert(count_ones(0x3) == 2); - assert(count_ones(0x137F) == 10); - - assert(count_zeros(0) == INT_WIDTH); - assert(count_zeros(0L) == LONG_WIDTH); - assert(count_zeros(0LL) == LLONG_WIDTH); - assert(count_zeros((uint8_t)0) == 8); - assert(count_zeros((uint16_t)0) == 16); - assert(count_zeros((uint32_t)0) == 32); - assert(count_zeros((uint64_t)0) == 64); - - assert(rotate_left((uint8_t)0xA1, 4) == 0x1A); - assert(rotate_left((uint16_t)0x1234, 12) == 0x4123); - assert(rotate_left((uint32_t)0x12345678, 20) == 0x67812345); - assert(rotate_left((uint32_t)0x12345678, 0) == 0x12345678); - - assert(rotate_right((uint8_t)0xA1, 4) == 0x1A); - assert(rotate_right((uint16_t)0x1234, 12) == 0x2341); - assert(rotate_right((uint32_t)0x12345678, 20) == 0x45678123); - assert(rotate_right((uint32_t)0x12345678, 0) == 0x12345678); + verify_eq(bswap((uint8_t)0x12), 0x12); + verify_eq(bswap((uint16_t)0x1234), 0x3412); + verify_eq(bswap((uint32_t)0x12345678), 0x78563412); + verify_eq(bswap((uint64_t)0x1234567812345678), 0x7856341278563412); + + verify_eq(count_ones(0x0), 0); + verify_eq(count_ones(0x1), 1); + verify_eq(count_ones(0x2), 1); + verify_eq(count_ones(0x3), 2); + verify_eq(count_ones(0x137F), 10); + + verify_eq(count_zeros(0), INT_WIDTH); + verify_eq(count_zeros(0L), LONG_WIDTH); + verify_eq(count_zeros(0LL), LLONG_WIDTH); + verify_eq(count_zeros((uint8_t)0), 8); + verify_eq(count_zeros((uint16_t)0), 16); + verify_eq(count_zeros((uint32_t)0), 32); + verify_eq(count_zeros((uint64_t)0), 64); + + verify_eq(rotate_left((uint8_t)0xA1, 4), 0x1A); + verify_eq(rotate_left((uint16_t)0x1234, 12), 0x4123); + verify_eq(rotate_left((uint32_t)0x12345678, 20), 0x67812345); + verify_eq(rotate_left((uint32_t)0x12345678, 0), 0x12345678); + + verify_eq(rotate_right((uint8_t)0xA1, 4), 0x1A); + verify_eq(rotate_right((uint16_t)0x1234, 12), 0x2341); + verify_eq(rotate_right((uint32_t)0x12345678, 20), 0x45678123); + verify_eq(rotate_right((uint32_t)0x12345678, 0), 0x12345678); for (int i = 0; i < 16; ++i) { uint16_t n = (uint16_t)1 << i; for (int j = i; j < 16; ++j) { uint16_t m = (uint16_t)1 << j; uint16_t nm = n | m; - assert(count_ones(nm) == 1 + (n != m)); - assert(count_zeros(nm) == 15 - (n != m)); - assert(leading_zeros(nm) == 15 - j); - assert(trailing_zeros(nm) == i); - assert(first_leading_one(nm) == j + 1); - assert(first_trailing_one(nm) == i + 1); - assert(bit_width(nm) == j + 1); - assert(bit_floor(nm) == m); + verify_eq(count_ones(nm), 1 + (n != m)); + verify_eq(count_zeros(nm), 15 - (n != m)); + verify_eq(leading_zeros(nm), 15 - j); + verify_eq(trailing_zeros(nm), i); + verify_eq(first_leading_one(nm), j + 1); + verify_eq(first_trailing_one(nm), i + 1); + verify_eq(bit_width(nm), j + 1); + verify_eq(bit_floor(nm), m); if (n == m) { - assert(bit_ceil(nm) == m); - assert(has_single_bit(nm)); + verify_eq(bit_ceil(nm), m); + bfs_verify(has_single_bit(nm)); } else { if (j < 15) { - assert(bit_ceil(nm) == (m << 1)); + verify_eq(bit_ceil(nm), (m << 1)); } - assert(!has_single_bit(nm)); + bfs_verify(!has_single_bit(nm)); } } } - assert(leading_zeros((uint16_t)0) == 16); - assert(trailing_zeros((uint16_t)0) == 16); - assert(first_leading_one(0) == 0); - assert(first_trailing_one(0) == 0); - assert(bit_width(0) == 0); - assert(bit_floor(0) == 0); - assert(bit_ceil(0) == 1); + verify_eq(leading_zeros((uint16_t)0), 16); + verify_eq(trailing_zeros((uint16_t)0), 16); + verify_eq(first_leading_one(0), 0); + verify_eq(first_trailing_one(0), 0); + verify_eq(bit_width(0), 0); + verify_eq(bit_floor(0), 0); + verify_eq(bit_ceil(0), 1); return EXIT_SUCCESS; } diff --git a/tests/trie.c b/tests/trie.c index ced14d4..e687f96 100644 --- a/tests/trie.c +++ b/tests/trie.c @@ -1,11 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD -#undef NDEBUG - #include "../src/trie.h" #include "../src/config.h" -#include +#include "../src/diag.h" #include #include @@ -45,7 +43,7 @@ int main(void) { trie_init(&trie); for (size_t i = 0; i < nkeys; ++i) { - assert(!trie_find_str(&trie, keys[i])); + bfs_verify(!trie_find_str(&trie, keys[i])); const char *prefix = NULL; for (size_t j = 0; j < i; ++j) { @@ -58,38 +56,38 @@ int main(void) { struct trie_leaf *leaf = trie_find_prefix(&trie, keys[i]); if (prefix) { - assert(leaf); - assert(strcmp(prefix, leaf->key) == 0); + bfs_verify(leaf); + bfs_verify(strcmp(prefix, leaf->key) == 0); } else { - assert(!leaf); + bfs_verify(!leaf); } leaf = trie_insert_str(&trie, keys[i]); - assert(leaf); - assert(strcmp(keys[i], leaf->key) == 0); - assert(leaf->length == strlen(keys[i]) + 1); + bfs_verify(leaf); + bfs_verify(strcmp(keys[i], leaf->key) == 0); + bfs_verify(leaf->length == strlen(keys[i]) + 1); } { size_t i = 0; TRIE_FOR_EACH(&trie, leaf) { - assert(leaf == trie_find_str(&trie, keys[i])); - assert(!leaf->prev || leaf->prev->next == leaf); - assert(!leaf->next || leaf->next->prev == leaf); + bfs_verify(leaf == trie_find_str(&trie, keys[i])); + bfs_verify(!leaf->prev || leaf->prev->next == leaf); + bfs_verify(!leaf->next || leaf->next->prev == leaf); ++i; } - assert(i == nkeys); + bfs_verify(i == nkeys); } for (size_t i = 0; i < nkeys; ++i) { struct trie_leaf *leaf = trie_find_str(&trie, keys[i]); - assert(leaf); - assert(strcmp(keys[i], leaf->key) == 0); - assert(leaf->length == strlen(keys[i]) + 1); + bfs_verify(leaf); + bfs_verify(strcmp(keys[i], leaf->key) == 0); + bfs_verify(leaf->length == strlen(keys[i]) + 1); trie_remove(&trie, leaf); leaf = trie_find_str(&trie, keys[i]); - assert(!leaf); + bfs_verify(!leaf); const char *postfix = NULL; for (size_t j = i + 1; j < nkeys; ++j) { @@ -102,33 +100,33 @@ int main(void) { leaf = trie_find_postfix(&trie, keys[i]); if (postfix) { - assert(leaf); - assert(strcmp(postfix, leaf->key) == 0); + bfs_verify(leaf); + bfs_verify(strcmp(postfix, leaf->key) == 0); } else { - assert(!leaf); + bfs_verify(!leaf); } } TRIE_FOR_EACH(&trie, leaf) { - assert(false); + bfs_verify(false); } // This tests the "jump" node handling on 32-bit platforms size_t longsize = 1 << 20; char *longstr = malloc(longsize); - assert(longstr); + bfs_verify(longstr); memset(longstr, 0xAC, longsize); - assert(!trie_find_mem(&trie, longstr, longsize)); - assert(trie_insert_mem(&trie, longstr, longsize)); + bfs_verify(!trie_find_mem(&trie, longstr, longsize)); + bfs_verify(trie_insert_mem(&trie, longstr, longsize)); memset(longstr + longsize/2, 0xAB, longsize/2); - assert(!trie_find_mem(&trie, longstr, longsize)); - assert(trie_insert_mem(&trie, longstr, longsize)); + bfs_verify(!trie_find_mem(&trie, longstr, longsize)); + bfs_verify(trie_insert_mem(&trie, longstr, longsize)); memset(longstr, 0xAA, longsize/2); - assert(!trie_find_mem(&trie, longstr, longsize)); - assert(trie_insert_mem(&trie, longstr, longsize)); + bfs_verify(!trie_find_mem(&trie, longstr, longsize)); + bfs_verify(trie_insert_mem(&trie, longstr, longsize)); free(longstr); trie_destroy(&trie); -- cgit v1.2.3 From bc952b51f48905392cec5685853fbb44565057a8 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 20 May 2023 14:37:56 -0400 Subject: list: Return the removed item from SLIST_POP() --- src/bftw.c | 6 ++---- src/list.h | 21 ++++++++++++++++----- src/xspawn.c | 4 +--- 3 files changed, 19 insertions(+), 12 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 6ec5cfa..966f03d 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -780,8 +780,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { return false; } - state->file = state->dirs.head; - SLIST_POP(&state->dirs); + state->file = SLIST_POP(&state->dirs); return true; } @@ -952,8 +951,7 @@ static void bftw_list_sort(struct bftw_list *list) { // Split for (struct bftw_file *hare = list->head; hare && (hare = hare->next); hare = hare->next) { - struct bftw_file *tortoise = list->head; - SLIST_POP(list); + struct bftw_file *tortoise = SLIST_POP(list); SLIST_APPEND(&left, tortoise); } SLIST_EXTEND(&right, list); diff --git a/src/list.h b/src/list.h index 87e2bf2..89f00ed 100644 --- a/src/list.h +++ b/src/list.h @@ -79,6 +79,7 @@ #define BFS_LIST_H #include +#include /** * Initialize a singly-linked list. @@ -250,17 +251,27 @@ * A pointer to the item to remove, either &list->head or &prev->next. * @param node (optional) * If specified, use item->node.next rather than item->next. + * @return + * The removed item. */ #define SLIST_REMOVE(list, ...) SLIST_REMOVE_(list, __VA_ARGS__, ) #define SLIST_REMOVE_(list, cursor, ...) \ - LIST_BLOCK_(SLIST_REMOVE__((list), (cursor), LIST_NEXT_(__VA_ARGS__))) + SLIST_REMOVE__((list), (cursor), LIST_NEXT_(__VA_ARGS__)) #define SLIST_REMOVE__(list, cursor, next) \ - void *_next = (void *)(*cursor)->next; \ - (*cursor)->next = NULL; \ - *cursor = _next; \ - list->tail = _next ? list->tail : cursor; + (list->tail = (*cursor)->next ? list->tail : cursor, \ + slist_remove_impl(*cursor, cursor, &(*cursor)->next, list->tail, sizeof(*cursor))) + +// Helper for SLIST_REMOVE() +static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void *tail, size_t size) { + // ret = *cursor; + // *cursor = ret->next; + memcpy(cursor, next, size); + // ret->next = *list->tail; (NULL) + memcpy(next, tail, size); + return ret; +} /** * Pop the head off a singly-linked list. diff --git a/src/xspawn.c b/src/xspawn.c index 00fb76e..740e38e 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -49,9 +49,7 @@ int bfs_spawn_init(struct bfs_spawn *ctx) { int bfs_spawn_destroy(struct bfs_spawn *ctx) { while (ctx->head) { - struct bfs_spawn_action *action = ctx->head; - SLIST_POP(ctx); - free(action); + free(SLIST_POP(ctx)); } return 0; -- cgit v1.2.3 From eef75524aec3910097cb6923c30b898ad98179fe Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 24 May 2023 11:54:33 -0400 Subject: list: Allow popping from an empty list --- src/bftw.c | 19 +++---------------- src/list.h | 4 +++- 2 files changed, 6 insertions(+), 17 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 966f03d..916a56b 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -299,11 +299,10 @@ static int bftw_file_open(struct bftw_cache *cache, struct bftw_file *file, cons SLIST_PREPEND(&parents, cur); } - while ((cur = parents.head)) { + while ((cur = SLIST_POP(&parents))) { if (!cur->parent || cur->parent->fd >= 0) { bftw_file_openat(cache, cur, cur->parent, cur->name); } - SLIST_POP(&parents); } return file->fd; @@ -772,29 +771,17 @@ static enum bftw_action bftw_call_back(struct bftw_state *state, const char *nam static bool bftw_pop_dir(struct bftw_state *state) { bfs_assert(!state->file); - if (!state->dirs.head) { - return false; - } - if (state->files.head && state->strategy == BFTW_BFS) { return false; } - state->file = SLIST_POP(&state->dirs); - return true; + return (state->file = SLIST_POP(&state->dirs)); } /** Pop a file to visit from the queue. */ static bool bftw_pop_file(struct bftw_state *state) { bfs_assert(!state->file); - - state->file = state->files.head; - if (state->file) { - SLIST_POP(&state->files); - return true; - } else { - return false; - } + return (state->file = SLIST_POP(&state->files)); } /** diff --git a/src/list.h b/src/list.h index 89f00ed..a284c25 100644 --- a/src/list.h +++ b/src/list.h @@ -280,11 +280,13 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void * The list to pop from. * @param node (optional) * If specified, use head->node.next rather than head->next. + * @return + * The popped item, or NULL if the list was empty. */ #define SLIST_POP(...) SLIST_POP_(__VA_ARGS__, ) #define SLIST_POP_(list, ...) \ - SLIST_REMOVE_(list, &(list)->head, __VA_ARGS__) + ((list)->head ? SLIST_REMOVE_(list, &(list)->head, __VA_ARGS__) : NULL) /** * Initialize a doubly-linked list. -- cgit v1.2.3 From 0c10a1e984bc2bed1fe3a7dadbd94a9c24139a91 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 11 Apr 2023 22:06:42 -0400 Subject: dir: Add a flag to bfs_freedir() to force the fd to stay the same --- src/bftw.c | 4 ++-- src/dir.c | 18 +++++++++++++----- src/dir.h | 10 ++++++++-- src/ioq.c | 1 + 4 files changed, 24 insertions(+), 9 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 916a56b..a3e8d9b 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -853,13 +853,13 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { if (file->refcount > 1) { // Keep the fd around if any subdirectories exist - file->fd = bfs_freedir(state->dir); + file->fd = bfs_freedir(state->dir, false); } else { - bfs_closedir(state->dir); file->fd = -1; } if (file->fd < 0) { + bfs_closedir(state->dir); bftw_cache_remove(&state->cache, file); } } diff --git a/src/dir.c b/src/dir.c index 01d26db..126f473 100644 --- a/src/dir.c +++ b/src/dir.c @@ -243,13 +243,15 @@ int bfs_closedir(struct bfs_dir *dir) { int ret = xclose(dir->fd); #else int ret = closedir(dir->dir); - bfs_verify(ret == 0 || errno != EBADF); + if (ret != 0) { + bfs_verify(errno != EBADF); + } #endif free(dir); return ret; } -int bfs_freedir(struct bfs_dir *dir) { +int bfs_freedir(struct bfs_dir *dir, bool same_fd) { #if BFS_GETDENTS int ret = dir->fd; free(dir); @@ -257,10 +259,16 @@ int bfs_freedir(struct bfs_dir *dir) { int ret = fdclosedir(dir->dir); free(dir); #else + if (same_fd) { + errno = ENOTSUP; + return -1; + } + int ret = dup_cloexec(dirfd(dir->dir)); - int error = errno; - bfs_closedir(dir); - errno = error; + if (ret >= 0) { + bfs_closedir(dir); + } #endif + return ret; } diff --git a/src/dir.h b/src/dir.h index 01eaaba..e0fe913 100644 --- a/src/dir.h +++ b/src/dir.h @@ -8,6 +8,7 @@ #ifndef BFS_DIR_H #define BFS_DIR_H +#include "config.h" #include /** @@ -103,9 +104,14 @@ int bfs_closedir(struct bfs_dir *dir); * * @param dir * The directory to free. + * @param same_fd + * If true, require that the returned file descriptor is the same one + * that bfs_dirfd() would have returned. Otherwise, it may be a new + * file descriptor for the same directory. * @return - * The file descriptor on success, or -1 on failure. + * On success, a file descriptor for the directory is returned. On + * failure, -1 is returned, and the directory remains open. */ -int bfs_freedir(struct bfs_dir *dir); +int bfs_freedir(struct bfs_dir *dir, bool same_fd); #endif // BFS_DIR_H diff --git a/src/ioq.c b/src/ioq.c index e09c2a9..1b61fb0 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -155,6 +155,7 @@ static void *ioq_work(void *ptr) { sanitize_uninit(cmd); struct ioq_res *res = &cmd->res; + res->ptr = req.ptr; res->dir = bfs_opendir(req.dfd, req.path); res->error = errno; ioqq_push(ioq->ready, cmd); -- cgit v1.2.3 From 0cca5b64e1355af5d2c3d935da4e110982273703 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 30 Mar 2023 12:39:37 -0400 Subject: bftw: Implement open file pinning --- src/bftw.c | 132 ++++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 100 insertions(+), 32 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index a3e8d9b..4a49457 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -51,11 +51,15 @@ struct bftw_file { /** This file's depth in the walk. */ size_t depth; - /** Reference count. */ + /** Reference count (for ->parent). */ size_t refcount; + /** Pin count (for ->fd). */ + size_t pincount; /** An open descriptor to this file, or -1. */ int fd; + /** An open directory for this file, if any. */ + struct bfs_dir *dir; /** This file's type, if known. */ enum bfs_type type; @@ -101,27 +105,58 @@ static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) { cache->capacity = capacity; } -/** Remove a bftw_file from the cache. */ -static void bftw_cache_remove(struct bftw_cache *cache, struct bftw_file *file) { +/** Remove a bftw_file from the LRU list. */ +static void bftw_lru_remove(struct bftw_cache *cache, struct bftw_file *file) { if (cache->target == file) { cache->target = file->lru.prev; } LIST_REMOVE(cache, file, lru); +} +/** Remove a bftw_file from the cache. */ +static void bftw_cache_remove(struct bftw_cache *cache, struct bftw_file *file) { + bftw_lru_remove(cache, file); ++cache->capacity; } /** Close a bftw_file. */ static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) { bfs_assert(file->fd >= 0); + bfs_assert(file->pincount == 0); - if (LIST_ATTACHED(cache, file, lru)) { - bftw_cache_remove(cache, file); + if (file->dir) { + bfs_assert(file->fd == bfs_dirfd(file->dir)); + bfs_closedir(file->dir); + file->dir = NULL; + } else { + xclose(file->fd); } - xclose(file->fd); file->fd = -1; + bftw_cache_remove(cache, file); +} + +/** Free an open directory. */ +static void bftw_file_freedir(struct bftw_cache *cache, struct bftw_file *file) { + if (!file->dir) { + return; + } + + // Try to keep an open fd if any children exist + bool reffed = file->refcount > 1; + // Keep the fd the same if it's pinned + bool pinned = file->pincount > 0; + + if (reffed || pinned) { + int fd = bfs_freedir(file->dir, pinned); + if (fd >= 0) { + file->fd = fd; + file->dir = NULL; + } + } else { + bftw_file_close(cache, file); + } } /** Pop the least recently used directory from the cache. */ @@ -135,6 +170,18 @@ static int bftw_cache_pop(struct bftw_cache *cache) { return 0; } +/** Add a bftw_file to the LRU list. */ +static void bftw_lru_add(struct bftw_cache *cache, struct bftw_file *file) { + bfs_assert(file->fd >= 0); + + LIST_INSERT(cache, cache->target, file, lru); + + // Prefer to keep the root paths open by keeping them at the head of the list + if (file->depth == 0) { + cache->target = file; + } +} + /** Add a bftw_file to the cache. */ static int bftw_cache_add(struct bftw_cache *cache, struct bftw_file *file) { bfs_assert(file->fd >= 0); @@ -148,14 +195,28 @@ static int bftw_cache_add(struct bftw_cache *cache, struct bftw_file *file) { bfs_assert(cache->capacity > 0); --cache->capacity; - LIST_INSERT(cache, cache->target, file, lru); + bftw_lru_add(cache, file); + return 0; +} - // Prefer to keep the root paths open by keeping them at the head of the list - if (file->depth == 0) { - cache->target = file; +/** Pin a cache entry so it won't be closed. */ +static void bftw_cache_pin(struct bftw_cache *cache, struct bftw_file *file) { + bfs_assert(file->fd >= 0); + + if (file->pincount++ == 0) { + bftw_lru_remove(cache, file); } +} - return 0; +/** Unpin a cache entry. */ +static void bftw_cache_unpin(struct bftw_cache *cache, struct bftw_file *file) { + bfs_assert(file->fd >= 0); + bfs_assert(file->pincount > 0); + + if (--file->pincount == 0) { + bftw_lru_add(cache, file); + bftw_file_freedir(cache, file); + } } /** Compute the name offset of a child path. */ @@ -201,7 +262,9 @@ static struct bftw_file *bftw_file_new(struct bftw_file *parent, const char *nam file->lru.prev = file->lru.next = NULL; file->refcount = 1; + file->pincount = 0; file->fd = -1; + file->dir = NULL; file->type = BFS_UNKNOWN; file->dev = -1; @@ -234,8 +297,7 @@ static int bftw_file_openat(struct bftw_cache *cache, struct bftw_file *file, st int at_fd = AT_FDCWD; if (base) { - // Remove base from the cache temporarily so it stays open - bftw_cache_remove(cache, base); + bftw_cache_pin(cache, base); at_fd = base->fd; } @@ -250,7 +312,7 @@ static int bftw_file_openat(struct bftw_cache *cache, struct bftw_file *file, st } if (base) { - bftw_cache_add(cache, base); + bftw_cache_unpin(cache, base); } if (fd >= 0) { @@ -308,6 +370,19 @@ static int bftw_file_open(struct bftw_cache *cache, struct bftw_file *file, cons return file->fd; } +/** + * Associate an open directory with a bftw_file. + */ +static void bftw_file_set_dir(struct bftw_cache *cache, struct bftw_file *file, struct bfs_dir *dir) { + bfs_assert(!file->dir); + file->dir = dir; + + if (file->fd < 0) { + file->fd = bfs_dirfd(dir); + bftw_cache_add(cache, file); + } +} + /** * Open a bftw_file as a directory. * @@ -326,7 +401,11 @@ static struct bfs_dir *bftw_file_opendir(struct bftw_cache *cache, struct bftw_f return NULL; } - return bfs_opendir(fd, NULL); + struct bfs_dir *dir = bfs_opendir(fd, NULL); + if (dir) { + bftw_file_set_dir(cache, file, dir); + } + return dir; } /** Free a bftw_file. */ @@ -798,7 +877,9 @@ static int bftw_opendir(struct bftw_state *state) { } state->dir = bftw_file_opendir(&state->cache, state->file, state->path); - if (!state->dir) { + if (state->dir) { + bftw_cache_pin(&state->cache, state->file); + } else { state->direrror = errno; } return 0; @@ -848,22 +929,9 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { int ret = 0; if (state->dir) { - struct bftw_file *file = state->file; - bfs_assert(file && file->fd >= 0); - - if (file->refcount > 1) { - // Keep the fd around if any subdirectories exist - file->fd = bfs_freedir(state->dir, false); - } else { - file->fd = -1; - } - - if (file->fd < 0) { - bfs_closedir(state->dir); - bftw_cache_remove(&state->cache, file); - } + bftw_cache_unpin(&state->cache, state->file); + bftw_file_freedir(&state->cache, state->file); } - state->dir = NULL; state->de = NULL; @@ -876,8 +944,8 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { } else { state->error = state->direrror; } - state->direrror = 0; } + state->direrror = 0; enum bftw_gc_flags visit = BFTW_VISIT_FILE; while (state->file) { -- cgit v1.2.3 From c023cceb3f50d92ed565ea3f085883f86de0f3f0 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 9 Jun 2023 16:34:41 -0400 Subject: bftw: Use an I/O queue to open directories Parallelism is controlled by the new -j flag. --- docs/bfs.1 | 8 +++- src/bftw.c | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- src/bftw.h | 2 + src/ctx.c | 1 + src/ctx.h | 2 + src/eval.c | 22 +++++++++ src/parse.c | 26 +++++++++- 7 files changed, 206 insertions(+), 9 deletions(-) (limited to 'src/bftw.c') diff --git a/docs/bfs.1 b/docs/bfs.1 index 53a9831..bc82457 100644 --- a/docs/bfs.1 +++ b/docs/bfs.1 @@ -171,12 +171,18 @@ consumes too much memory. .TP .I eds Exponential deepening search. -A compromise between breadth- and depth-first search, which searches exponentially increasing depth ranges (e.g 0-1, 1-2, 2-4, 4-8, etc.). +A compromise between breadth- and depth-first search, which searches exponentially increasing depth ranges (e.g. 0-1, 1-2, 2-4, 4-8, etc.). Provides many of the benefits of breadth-first search with depth-first's reduced memory consumption. Typically far faster than .B \-S .IR ids . .RE +.TP +\fB\-j\fIN\fR +Search with +.I N +threads in parallel (default: number of CPUs, up to +.IR 8 ). .SH OPERATORS .TP \fB( \fIexpression \fB)\fR diff --git a/src/bftw.c b/src/bftw.c index 4a49457..e711963 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -22,6 +22,7 @@ #include "diag.h" #include "dir.h" #include "dstring.h" +#include "ioq.h" #include "list.h" #include "mtab.h" #include "stat.h" @@ -58,6 +59,8 @@ struct bftw_file { size_t pincount; /** An open descriptor to this file, or -1. */ int fd; + /** Whether this file has a pending ioq request. */ + bool ioqueued; /** An open directory for this file, if any. */ struct bfs_dir *dir; @@ -264,6 +267,7 @@ static struct bftw_file *bftw_file_new(struct bftw_file *parent, const char *nam file->refcount = 1; file->pincount = 0; file->fd = -1; + file->ioqueued = false; file->dir = NULL; file->type = BFS_UNKNOWN; @@ -439,6 +443,8 @@ struct bftw_state { /** The cache of open directories. */ struct bftw_cache cache; + /** The async I/O queue. */ + struct ioq *ioq; /** The queue of directories to read. */ struct bftw_list dirs; /** The queue of files to visit. */ @@ -494,6 +500,25 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg bftw_cache_init(&state->cache, args->nopenfd); + size_t qdepth = args->nopenfd - 1; + if (qdepth > 1024) { + qdepth = 1024; + } + + size_t nthreads = args->nthreads; + if (nthreads > qdepth) { + nthreads = qdepth; + } + + state->ioq = NULL; + if (nthreads > 0) { + state->ioq = ioq_create(qdepth, nthreads); + if (!state->ioq) { + dstrfree(state->path); + return -1; + } + } + SLIST_INIT(&state->dirs); SLIST_INIT(&state->files); SLIST_INIT(&state->batch); @@ -846,15 +871,122 @@ static enum bftw_action bftw_call_back(struct bftw_state *state, const char *nam } } +/** Push a directory onto the queue. */ +static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { + bfs_assert(file->type == BFS_DIR); + + struct bftw_cache *cache = &state->cache; + + if (!state->ioq) { + goto append; + } + + int dfd = AT_FDCWD; + if (file->parent) { + dfd = file->parent->fd; + if (dfd < 0) { + goto append; + } + bftw_cache_pin(cache, file->parent); + } + + if (cache->capacity == 0) { + if (bftw_cache_pop(cache) != 0) { + goto unpin; + } + } + --cache->capacity; + + if (ioq_opendir(state->ioq, dfd, file->name, file) != 0) { + ++cache->capacity; + goto unpin; + } + + file->ioqueued = true; + + if (state->flags & BFTW_SORT) { + goto append; + } else { + return; + } + +unpin: + if (file->parent) { + bftw_cache_unpin(cache, file->parent); + } +append: + SLIST_APPEND(&state->dirs, file); +} + +/** Pop a response from the I/O queue. */ +static int bftw_ioq_pop(struct bftw_state *state, bool block) { + if (!state->ioq) { + return -1; + } + + struct ioq_res *res; + if (block) { + res = ioq_pop(state->ioq); + } else { + res = ioq_trypop(state->ioq); + } + + if (!res) { + return -1; + } + + struct bftw_cache *cache = &state->cache; + ++cache->capacity; + + struct bftw_file *file = res->ptr; + file->ioqueued = false; + + if (file->parent) { + bftw_cache_unpin(cache, file->parent); + } + + if (res->dir) { + bftw_file_set_dir(cache, file, res->dir); + } + + ioq_free(state->ioq, res); + + if (!(state->flags & BFTW_SORT)) { + SLIST_PREPEND(&state->dirs, file); + } + + return 0; +} + /** Pop a directory to read from the queue. */ static bool bftw_pop_dir(struct bftw_state *state) { bfs_assert(!state->file); - if (state->files.head && state->strategy == BFTW_BFS) { + bool have_dirs = state->dirs.head; + bool have_files = state->files.head; + bool have_room = state->cache.capacity > 0; + + if (state->flags & BFTW_SORT) { + // Keep strict breadth-first order when sorting + if (state->strategy != BFTW_DFS && have_files) { + return false; + } + } else { + // Block if we have no other files/dirs to visit, or no room in the cache + bool block = !(have_dirs || have_files) || !have_room; + bftw_ioq_pop(state, block); + } + + struct bftw_file *dir = state->file = SLIST_POP(&state->dirs); + if (!dir) { return false; } - return (state->file = SLIST_POP(&state->dirs)); + while (dir->ioqueued) { + bftw_ioq_pop(state, true); + } + + return true; } /** Pop a file to visit from the queue. */ @@ -872,16 +1004,22 @@ static int bftw_opendir(struct bftw_state *state) { state->direrror = 0; - if (bftw_build_path(state, NULL) != 0) { - return -1; + struct bftw_file *file = state->file; + if (file->dir) { + state->dir = file->dir; + } else { + if (bftw_build_path(state, NULL) != 0) { + return -1; + } + state->dir = bftw_file_opendir(&state->cache, file, state->path); } - state->dir = bftw_file_opendir(&state->cache, state->file, state->path); if (state->dir) { - bftw_cache_pin(&state->cache, state->file); + bftw_cache_pin(&state->cache, file); } else { state->direrror = errno; } + return 0; } @@ -988,6 +1126,8 @@ static int bftw_state_destroy(struct bftw_state *state) { bftw_gc(state, BFTW_VISIT_NONE); } while (bftw_pop_dir(state) || bftw_pop_file(state)); + ioq_destroy(state->ioq); + bftw_cache_destroy(&state->cache); errno = state->error; @@ -1100,7 +1240,7 @@ static int bftw_visit(struct bftw_state *state, const char *name) { } bftw_save_ftwbuf(file, &state->ftwbuf); - SLIST_APPEND(&state->dirs, file); + bftw_push_dir(state, file); return 0; case BFTW_PRUNE: diff --git a/src/bftw.h b/src/bftw.h index 77697ed..940532c 100644 --- a/src/bftw.h +++ b/src/bftw.h @@ -186,6 +186,8 @@ struct bftw_args { void *ptr; /** The maximum number of file descriptors to keep open. */ int nopenfd; + /** The maximum number of threads to use. */ + int nthreads; /** Flags that control bftw() behaviour. */ enum bftw_flags flags; /** The search strategy to use. */ diff --git a/src/ctx.c b/src/ctx.c index c4b2fb2..e8ce0e8 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -56,6 +56,7 @@ struct bfs_ctx *bfs_ctx_new(void) { ctx->maxdepth = INT_MAX; ctx->flags = BFTW_RECOVER; ctx->strategy = BFTW_BFS; + ctx->threads = 0; ctx->optlevel = 3; ctx->debug = 0; ctx->ignore_races = false; diff --git a/src/ctx.h b/src/ctx.h index 0dc9f08..2b8e8cb 100644 --- a/src/ctx.h +++ b/src/ctx.h @@ -68,6 +68,8 @@ struct bfs_ctx { /** bftw() search strategy. */ enum bftw_strategy strategy; + /** Threads (-j). */ + int threads; /** Optimization level (-O). */ int optlevel; /** Debugging flags (-D). */ diff --git a/src/eval.c b/src/eval.c index e2c19a9..b9bce6c 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1505,6 +1505,19 @@ static int infer_fdlimit(const struct bfs_ctx *ctx, int limit) { return ret; } +static int infer_nproc(void) { + long nproc = sysconf(_SC_NPROCESSORS_ONLN); + + if (nproc < 0) { + nproc = 0; + } else if (nproc > 8) { + // Not much speedup after 8 threads + nproc = 8; + } + + return nproc; +} + /** * Dump the bftw() flags for -D search. */ @@ -1593,12 +1606,20 @@ int bfs_eval(const struct bfs_ctx *ctx) { int fdlimit = raise_fdlimit(ctx); fdlimit = infer_fdlimit(ctx, fdlimit); + int nthreads; + if (ctx->threads > 0) { + nthreads = ctx->threads - 1; + } else { + nthreads = infer_nproc(); + } + struct bftw_args bftw_args = { .paths = ctx->paths, .npaths = darray_length(ctx->paths), .callback = eval_callback, .ptr = &args, .nopenfd = fdlimit, + .nthreads = nthreads, .flags = ctx->flags, .strategy = ctx->strategy, .mtab = bfs_ctx_mtab(ctx), @@ -1618,6 +1639,7 @@ int bfs_eval(const struct bfs_ctx *ctx) { fprintf(stderr, "\t.callback = eval_callback,\n"); fprintf(stderr, "\t.ptr = &args,\n"); fprintf(stderr, "\t.nopenfd = %d,\n", bftw_args.nopenfd); + fprintf(stderr, "\t.nthreads = %d,\n", bftw_args.nthreads); fprintf(stderr, "\t.flags = "); dump_bftw_flags(bftw_args.flags); fprintf(stderr, ",\n\t.strategy = %s,\n", dump_bftw_strategy(bftw_args.strategy)); diff --git a/src/parse.c b/src/parse.c index 59a1e7d..96def14 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1636,6 +1636,23 @@ static struct bfs_expr *parse_inum(struct parser_state *state, int arg1, int arg return parse_test_icmp(state, eval_inum); } +/** + * Parse -j. + */ +static struct bfs_expr *parse_jobs(struct parser_state *state, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_flag(state); + if (!expr) { + return NULL; + } + + if (!parse_int(state, expr->argv, expr->argv[0] + 2, &state->ctx->threads, IF_INT | IF_UNSIGNED)) { + bfs_expr_free(expr); + return NULL; + } + + return expr; +} + /** * Parse -links N. */ @@ -2753,7 +2770,9 @@ static struct bfs_expr *parse_help(struct parser_state *state, int arg1, int arg cfprintf(cout, " Enable optimization level ${bld}N${rs} (default: ${bld}3${rs})\n"); cfprintf(cout, " ${cyn}-S${rs} ${bld}bfs${rs}|${bld}dfs${rs}|${bld}ids${rs}|${bld}eds${rs}\n"); cfprintf(cout, " Use ${bld}b${rs}readth-${bld}f${rs}irst/${bld}d${rs}epth-${bld}f${rs}irst/${bld}i${rs}terative/${bld}e${rs}xponential ${bld}d${rs}eepening ${bld}s${rs}earch\n"); - cfprintf(cout, " (default: ${cyn}-S${rs} ${bld}bfs${rs})\n\n"); + cfprintf(cout, " (default: ${cyn}-S${rs} ${bld}bfs${rs})\n"); + cfprintf(cout, " ${cyn}-j${bld}N${rs}\n"); + cfprintf(cout, " Search with ${bld}N${rs} threads in parallel (default: number of CPUs, up to ${bld}8${rs})\n\n"); cfprintf(cout, "${bld}Operators:${rs}\n\n"); @@ -3060,6 +3079,7 @@ static const struct table_entry parse_table[] = { {"-ipath", T_TEST, parse_path, true}, {"-iregex", T_TEST, parse_regex, BFS_REGEX_ICASE}, {"-iwholename", T_TEST, parse_path, true}, + {"-j", T_FLAG, parse_jobs, 0, 0, true}, {"-links", T_TEST, parse_links}, {"-lname", T_TEST, parse_lname, false}, {"-ls", T_ACTION, parse_ls}, @@ -3552,6 +3572,10 @@ void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag) { cfprintf(cerr, " ${cyn}-O${bld}%d${rs}", ctx->optlevel); } + if (ctx->threads > 0) { + cfprintf(cerr, " ${cyn}-j${bld}%d${rs}", ctx->threads); + } + cfprintf(cerr, " ${cyn}-S${rs} ${bld}%s${rs}", bftw_strategy_name(ctx->strategy)); enum debug_flags debug = ctx->debug; -- cgit v1.2.3 From 90ded13e589b0089167ef25ca3d26be599dfec9b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 19 Jun 2023 12:11:36 -0400 Subject: alloc: New header for memory allocation utilities --- Makefile | 3 +- src/alloc.c | 50 ++++++++++++++++++++ src/alloc.h | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/bfstd.c | 13 ----- src/bfstd.h | 12 ----- src/bftw.c | 5 +- src/color.c | 5 +- src/config.h | 50 -------------------- src/ctx.c | 31 ++---------- src/dstring.c | 3 +- src/exec.c | 26 ++++------ src/ioq.c | 26 ++++------ src/ioq.h | 4 +- src/main.c | 1 + src/mtab.c | 5 +- src/parse.c | 28 ++--------- src/pwcache.c | 5 +- src/trie.c | 7 ++- src/xregex.c | 3 +- src/xspawn.c | 3 +- tests/alloc.c | 24 ++++++++++ tests/bfstd.c | 13 +---- 22 files changed, 271 insertions(+), 195 deletions(-) create mode 100644 src/alloc.c create mode 100644 src/alloc.h create mode 100644 tests/alloc.c (limited to 'src/bftw.c') diff --git a/Makefile b/Makefile index fb28e29..d38f581 100644 --- a/Makefile +++ b/Makefile @@ -217,6 +217,7 @@ $(OBJ)/FLAGS: $(OBJ)/FLAGS.new # All object files except the entry point LIBBFS := \ + $(OBJ)/src/alloc.o \ $(OBJ)/src/bar.o \ $(OBJ)/src/bfstd.o \ $(OBJ)/src/bftw.o \ @@ -246,7 +247,7 @@ LIBBFS := \ $(BIN)/bfs: $(OBJ)/src/main.o $(LIBBFS) # Standalone unit tests -UNITS := bfstd bit trie xtimegm +UNITS := alloc bfstd bit trie xtimegm UNIT_TESTS := $(UNITS:%=$(BIN)/tests/%) UNIT_CHECKS := $(UNITS:%=check-%) diff --git a/src/alloc.c b/src/alloc.c new file mode 100644 index 0000000..0003108 --- /dev/null +++ b/src/alloc.c @@ -0,0 +1,50 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include "alloc.h" +#include "bit.h" +#include "diag.h" +#include +#include +#include + +/** Portable aligned_alloc()/posix_memalign(). */ +static void *xmemalign(size_t align, size_t size) { + bfs_assert(has_single_bit(align)); + bfs_assert(align >= sizeof(void *)); + bfs_assert((size & (align - 1)) == 0); + +#if __APPLE__ + void *ptr = NULL; + errno = posix_memalign(&ptr, align, size); + return ptr; +#else + return aligned_alloc(align, size); +#endif +} + +void *alloc(size_t align, size_t size) { + bfs_assert(has_single_bit(align)); + bfs_assert((size & (align - 1)) == 0); + + if (align <= alignof(max_align_t)) { + return malloc(size); + } else { + return xmemalign(align, size); + } +} + +void *zalloc(size_t align, size_t size) { + bfs_assert(has_single_bit(align)); + bfs_assert((size & (align - 1)) == 0); + + if (align <= alignof(max_align_t)) { + return calloc(1, size); + } + + void *ret = xmemalign(align, size); + if (ret) { + memset(ret, 0, size); + } + return ret; +} diff --git a/src/alloc.h b/src/alloc.h new file mode 100644 index 0000000..899a4ec --- /dev/null +++ b/src/alloc.h @@ -0,0 +1,149 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +/** + * Memory allocation. + */ + +#ifndef BFS_ALLOC_H +#define BFS_ALLOC_H + +#include "config.h" +#include + +/** Round down to a multiple of an alignment. */ +static inline size_t align_floor(size_t align, size_t size) { + return size & ~(align - 1); +} + +/** Round up to a multiple of an alignment. */ +static inline size_t align_ceil(size_t align, size_t size) { + return align_floor(align, size + align - 1); +} + +/** + * Saturating array size. + * + * @param align + * Array element alignment. + * @param size + * Array element size. + * @param count + * Array element count. + * @return + * size * count, saturating to the maximum aligned value on overflow. + */ +static inline size_t array_size(size_t align, size_t size, size_t count) { + size_t ret = size * count; + return ret / size == count ? ret : ~(align - 1); +} + +/** Saturating array sizeof. */ +#define sizeof_array(type, count) \ + array_size(alignof(type), sizeof(type), count) + +/** Size of a struct/union field. */ +#define sizeof_member(type, member) \ + sizeof(((type *)NULL)->member) + +/** + * Saturating flexible struct size. + * + * @param align + * Struct alignment. + * @param min + * Minimum struct size. + * @param offset + * Flexible array member offset. + * @param size + * Flexible array element size. + * @param count + * Flexible array element count. + * @return + * The size of the struct with count flexible array elements. Saturates + * to the maximum aligned value on overflow. + */ +static inline size_t flex_size(size_t align, size_t min, size_t offset, size_t size, size_t count) { + size_t ret = size * count; + size_t overflow = ret / size != count; + + size_t extra = offset + align - 1; + ret += extra; + overflow |= ret < extra; + ret |= -overflow; + ret = align_floor(align, ret); + + // Make sure flex_sizeof(type, member, 0) >= sizeof(type), even if the + // type has more padding than necessary for alignment + if (min > align_ceil(align, offset)) { + ret = ret < min ? min : ret; + } + + return ret; +} + +/** + * Computes the size of a flexible struct. + * + * @param type + * The type of the struct containing the flexible array. + * @param member + * The name of the flexible array member. + * @param count + * The length of the flexible array. + * @return + * The size of the struct with count flexible array elements. Saturates + * to the maximum aligned value on overflow. + */ +#define sizeof_flex(type, member, count) \ + flex_size(alignof(type), sizeof(type), offsetof(type, member), sizeof_member(type, member[0]), count) + +/** + * General memory allocator. + * + * @param align + * The required alignment. + * @param size + * The size of the allocation. + * @return + * The allocated memory, or NULL on failure. + */ +void *alloc(size_t align, size_t size); + +/** + * Zero-initialized memory allocator. + * + * @param align + * The required alignment. + * @param size + * The size of the allocation. + * @return + * The allocated memory, or NULL on failure. + */ +void *zalloc(size_t align, size_t size); + +/** Allocate memory for the given type. */ +#define ALLOC(type) \ + (type *)alloc(alignof(type), sizeof(type)) + +/** Allocate zeroed memory for the given type. */ +#define ZALLOC(type) \ + (type *)zalloc(alignof(type), sizeof(type)) + +/** Allocate memory for an array. */ +#define ALLOC_ARRAY(type, count) \ + (type *)alloc(alignof(type), sizeof_array(type, count)); + +/** Allocate zeroed memory for an array. */ +#define ZALLOC_ARRAY(type, count) \ + (type *)zalloc(alignof(type), sizeof_array(type, count)); + +/** Allocate memory for a flexible struct. */ +#define ALLOC_FLEX(type, member, count) \ + (type *)alloc(alignof(type), sizeof_flex(type, member, count)) + +/** Allocate zeroed memory for a flexible struct. */ +#define ZALLOC_FLEX(type, member, count) \ + (type *)zalloc(alignof(type), sizeof_flex(type, member, count)) + +#endif // BFS_ALLOC_H diff --git a/src/bfstd.c b/src/bfstd.c index 856c76c..0e8ba5f 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -143,19 +143,6 @@ char *xgetdelim(FILE *file, char delim) { } } -void *xmemalign(size_t align, size_t size) { - bfs_assert(has_single_bit(align)); - bfs_assert((size & (align - 1)) == 0); - -#if __APPLE__ - void *ptr = NULL; - errno = posix_memalign(&ptr, align, size); - return ptr; -#else - return aligned_alloc(align, size); -#endif -} - /** Compile and execute a regular expression for xrpmatch(). */ static int xrpregex(nl_item item, const char *response) { const char *pattern = nl_langinfo(item); diff --git a/src/bfstd.h b/src/bfstd.h index 6f2e21e..cafe28f 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -105,18 +105,6 @@ char *xgetdelim(FILE *file, char delim); // #include -/** - * Portable version of aligned_alloc()/posix_memalign(). - * - * @param align - * The allocation's alignment. - * @param size - * The allocation's size. - * @return - * The allocation, or NULL on failure. - */ -void *xmemalign(size_t align, size_t size); - /** * Process a yes/no prompt. * diff --git a/src/bftw.c b/src/bftw.c index e711963..7ab14c7 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -17,6 +17,7 @@ */ #include "bftw.h" +#include "alloc.h" #include "bfstd.h" #include "config.h" #include "diag.h" @@ -241,9 +242,7 @@ static void bftw_cache_destroy(struct bftw_cache *cache) { /** Create a new bftw_file. */ static struct bftw_file *bftw_file_new(struct bftw_file *parent, const char *name) { size_t namelen = strlen(name); - size_t size = flex_sizeof(struct bftw_file, name, namelen + 1); - - struct bftw_file *file = malloc(size); + struct bftw_file *file = ALLOC_FLEX(struct bftw_file, name, namelen + 1); if (!file) { return NULL; } diff --git a/src/color.c b/src/color.c index 1edd8b5..b54ad53 100644 --- a/src/color.c +++ b/src/color.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "color.h" +#include "alloc.h" #include "bfstd.h" #include "bftw.h" #include "config.h" @@ -404,7 +405,7 @@ static void parse_gnu_ls_colors(struct colors *colors, const char *ls_colors) { } struct colors *parse_colors(void) { - struct colors *colors = malloc(sizeof(struct colors)); + struct colors *colors = ALLOC(struct colors); if (!colors) { return NULL; } @@ -497,7 +498,7 @@ void free_colors(struct colors *colors) { } CFILE *cfwrap(FILE *file, const struct colors *colors, bool close) { - CFILE *cfile = malloc(sizeof(*cfile)); + CFILE *cfile = ALLOC(CFILE); if (!cfile) { return NULL; } diff --git a/src/config.h b/src/config.h index 73348ac..1671a0d 100644 --- a/src/config.h +++ b/src/config.h @@ -141,56 +141,6 @@ */ #define countof(array) (sizeof(array) / sizeof(0[array])) -/** - * Round down to a multiple of an alignment. - */ -static inline size_t align_floor(size_t align, size_t size) { - return size & ~(align - 1); -} - -/** - * Round up to a multiple of an alignment. - */ -static inline size_t align_ceil(size_t align, size_t size) { - return align_floor(align, size + align - 1); -} - -/** - * Computes the size of a struct containing a flexible array member of the given - * length. - * - * @param type - * The type of the struct containing the flexible array. - * @param member - * The name of the flexible array member. - * @param count - * The length of the flexible array. - */ -#define flex_sizeof(type, member, count) \ - flex_sizeof_impl(alignof(type), sizeof(type), offsetof(type, member), sizeof(((type *)NULL)->member[0]), count) - -static inline size_t flex_sizeof_impl(size_t align, size_t min, size_t offset, size_t size, size_t count) { - size_t ret = size * count; - size_t overflow = ret / size != count; - - ret += offset; - overflow |= ret < offset; - - size_t mask = align - 1; - ret += mask; - overflow |= ret < mask; - ret |= -overflow; - ret &= ~mask; - - // Make sure flex_sizeof(type, member, 0) >= sizeof(type), even if the - // type has more padding than necessary for alignment - if (min > align_ceil(align, offset) && ret < min) { - ret = min; - } - - return ret; -} - /** * False sharing/destructive interference/largest cache line size. */ diff --git a/src/ctx.c b/src/ctx.c index e8ce0e8..a940bed 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "ctx.h" +#include "alloc.h" #include "color.h" #include "darray.h" #include "diag.h" @@ -42,43 +43,17 @@ const char *debug_flag_name(enum debug_flags flag) { } struct bfs_ctx *bfs_ctx_new(void) { - struct bfs_ctx *ctx = malloc(sizeof(*ctx)); + struct bfs_ctx *ctx = ZALLOC(struct bfs_ctx); if (!ctx) { return NULL; } - ctx->argv = NULL; - ctx->paths = NULL; - ctx->expr = NULL; - ctx->exclude = NULL; - - ctx->mindepth = 0; ctx->maxdepth = INT_MAX; ctx->flags = BFTW_RECOVER; ctx->strategy = BFTW_BFS; - ctx->threads = 0; ctx->optlevel = 3; - ctx->debug = 0; - ctx->ignore_races = false; - ctx->posixly_correct = false; - ctx->status = false; - ctx->unique = false; - ctx->warn = false; - ctx->xargs_safe = false; - - ctx->colors = NULL; - ctx->colors_error = 0; - ctx->cout = NULL; - ctx->cerr = NULL; - - ctx->users = NULL; - ctx->groups = NULL; - - ctx->mtab = NULL; - ctx->mtab_error = 0; trie_init(&ctx->files); - ctx->nfiles = 0; struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) != 0) { @@ -155,7 +130,7 @@ CFILE *bfs_ctx_dedup(struct bfs_ctx *ctx, CFILE *cfile, const char *path) { return ctx_file->cfile; } - leaf->value = ctx_file = malloc(sizeof(*ctx_file)); + leaf->value = ctx_file = ALLOC(struct bfs_ctx_file); if (!ctx_file) { trie_remove(&ctx->files, leaf); return NULL; diff --git a/src/dstring.c b/src/dstring.c index 2c9869d..7ca74d0 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "dstring.h" +#include "alloc.h" #include "diag.h" #include #include @@ -24,7 +25,7 @@ static struct dstring *dstrheader(const char *dstr) { /** Get the correct size for a dstring with the given capacity. */ static size_t dstrsize(size_t capacity) { - return flex_sizeof(struct dstring, data, capacity + 1); + return sizeof_flex(struct dstring, data, capacity + 1); } /** Allocate a dstring with the given contents. */ diff --git a/src/exec.c b/src/exec.c index 5912ad6..ea7f897 100644 --- a/src/exec.c +++ b/src/exec.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "exec.h" +#include "alloc.h" #include "bfstd.h" #include "bftw.h" #include "ctx.h" @@ -124,26 +125,16 @@ static void bfs_exec_parse_error(const struct bfs_ctx *ctx, const struct bfs_exe } struct bfs_exec *bfs_exec_parse(const struct bfs_ctx *ctx, char **argv, enum bfs_exec_flags flags) { - struct bfs_exec *execbuf = malloc(sizeof(*execbuf)); + struct bfs_exec *execbuf = ZALLOC(struct bfs_exec); if (!execbuf) { - bfs_perror(ctx, "malloc()"); + bfs_perror(ctx, "zalloc()"); goto fail; } execbuf->flags = flags; execbuf->ctx = ctx; execbuf->tmpl_argv = argv + 1; - execbuf->tmpl_argc = 0; - execbuf->argv = NULL; - execbuf->argc = 0; - execbuf->argv_cap = 0; - execbuf->arg_size = 0; - execbuf->arg_max = 0; - execbuf->arg_min = 0; execbuf->wd_fd = -1; - execbuf->wd_path = NULL; - execbuf->wd_len = 0; - execbuf->ret = 0; while (true) { const char *arg = execbuf->tmpl_argv[execbuf->tmpl_argc]; @@ -176,9 +167,9 @@ struct bfs_exec *bfs_exec_parse(const struct bfs_ctx *ctx, char **argv, enum bfs } execbuf->argv_cap = execbuf->tmpl_argc + 1; - execbuf->argv = malloc(execbuf->argv_cap*sizeof(*execbuf->argv)); + execbuf->argv = ALLOC_ARRAY(char *, execbuf->argv_cap); if (!execbuf->argv) { - bfs_perror(ctx, "malloc()"); + bfs_perror(ctx, "alloc()"); goto fail; } @@ -224,9 +215,8 @@ static char *bfs_exec_format_path(const struct bfs_exec *execbuf, const struct B return NULL; } - strcpy(path, "./"); - strcpy(path + 2, name); - + char *cur = stpcpy(path, "./"); + cur = stpcpy(cur, name); return path; } @@ -612,7 +602,7 @@ static int bfs_exec_push(struct bfs_exec *execbuf, char *arg) { if (execbuf->argc + 1 >= execbuf->argv_cap) { size_t cap = 2*execbuf->argv_cap; - char **argv = realloc(execbuf->argv, cap*sizeof(*argv)); + char **argv = realloc(execbuf->argv, sizeof_array(char *, cap)); if (!argv) { return -1; } diff --git a/src/ioq.c b/src/ioq.c index 5550c91..3e304ce 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "ioq.h" +#include "alloc.h" #include "atomic.h" #include "bfstd.h" #include "bit.h" @@ -114,7 +115,7 @@ static struct ioqq *ioqq_create(size_t size) { // Circular buffer size must be a power of two size = bit_ceil(size); - struct ioqq *ioqq = xmemalign(alignof(struct ioqq), flex_sizeof(struct ioqq, slots, size)); + struct ioqq *ioqq = ALLOC_FLEX(struct ioqq, slots, size); if (!ioqq) { return NULL; } @@ -124,7 +125,7 @@ static struct ioqq *ioqq_create(size_t size) { // Use a pool of monitors size_t nmonitors = size < 64 ? size : 64; - ioqq->monitors = xmemalign(alignof(struct ioq_monitor), nmonitors * sizeof(struct ioq_monitor)); + ioqq->monitors = ALLOC_ARRAY(struct ioq_monitor, nmonitors); if (!ioqq->monitors) { ioqq_destroy(ioqq); return NULL; @@ -273,7 +274,7 @@ struct ioq { /** The number of background threads. */ size_t nthreads; /** The background threads themselves. */ - pthread_t *threads; + pthread_t threads[]; }; /** Background thread entry point. */ @@ -303,18 +304,13 @@ static void *ioq_work(void *ptr) { return NULL; } -struct ioq *ioq_create(size_t depth, size_t threads) { - struct ioq *ioq = malloc(sizeof(*ioq)); +struct ioq *ioq_create(size_t depth, size_t nthreads) { + struct ioq *ioq = ZALLOC_FLEX(struct ioq, threads, nthreads); if (!ioq) { goto fail; } ioq->depth = depth; - ioq->size = 0; - - ioq->pending = NULL; - ioq->ready = NULL; - ioq->nthreads = 0; ioq->pending = ioqq_create(depth); if (!ioq->pending) { @@ -326,12 +322,7 @@ struct ioq *ioq_create(size_t depth, size_t threads) { goto fail; } - ioq->threads = malloc(threads * sizeof(ioq->threads[0])); - if (!ioq->threads) { - goto fail; - } - - for (size_t i = 0; i < threads; ++i) { + for (size_t i = 0; i < nthreads; ++i) { errno = pthread_create(&ioq->threads[i], NULL, ioq_work, ioq); if (errno != 0) { goto fail; @@ -354,7 +345,7 @@ int ioq_opendir(struct ioq *ioq, int dfd, const char *path, void *ptr) { return -1; } - union ioq_cmd *cmd = malloc(sizeof(*cmd)); + union ioq_cmd *cmd = ALLOC(union ioq_cmd); if (!cmd) { return -1; } @@ -412,7 +403,6 @@ void ioq_destroy(struct ioq *ioq) { abort(); } } - free(ioq->threads); ioqq_destroy(ioq->ready); ioqq_destroy(ioq->pending); diff --git a/src/ioq.h b/src/ioq.h index 9492034..0af5779 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -33,12 +33,12 @@ struct ioq_res { * * @param depth * The maximum depth of the queue. - * @param threads + * @param nthreads * The maximum number of background threads. * @return * The new I/O queue, or NULL on failure. */ -struct ioq *ioq_create(size_t depth, size_t threads); +struct ioq *ioq_create(size_t depth, size_t nthreads); /** * Asynchronous bfs_opendir(). diff --git a/src/main.c b/src/main.c index 76dde86..b7a08c1 100644 --- a/src/main.c +++ b/src/main.c @@ -20,6 +20,7 @@ * - bftw.[ch] (an extended version of nftw(3)) * * - Utilities: + * - alloc.[ch] (memory allocation) * - atomic.h (atomic operations) * - bar.[ch] (a terminal status bar) * - bit.h (bit manipulation) diff --git a/src/mtab.c b/src/mtab.c index 1d1ad94..e5c25ba 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "mtab.h" +#include "alloc.h" #include "bfstd.h" #include "config.h" #include "darray.h" @@ -87,15 +88,13 @@ fail: } struct bfs_mtab *bfs_mtab_parse(void) { - struct bfs_mtab *mtab = malloc(sizeof(*mtab)); + struct bfs_mtab *mtab = ZALLOC(struct bfs_mtab); if (!mtab) { return NULL; } - mtab->entries = NULL; trie_init(&mtab->names); trie_init(&mtab->types); - mtab->types_filled = false; int error = 0; diff --git a/src/parse.c b/src/parse.c index 64e08cd..cf4f696 100644 --- a/src/parse.c +++ b/src/parse.c @@ -9,6 +9,7 @@ */ #include "parse.h" +#include "alloc.h" #include "bfstd.h" #include "bftw.h" #include "color.h" @@ -55,39 +56,16 @@ static char *fake_print_arg = "-print"; static char *fake_true_arg = "-true"; struct bfs_expr *bfs_expr_new(bfs_eval_fn *eval_fn, size_t argc, char **argv) { - struct bfs_expr *expr = malloc(sizeof(*expr)); + struct bfs_expr *expr = ZALLOC(struct bfs_expr); if (!expr) { - perror("malloc()"); + perror("zalloc()"); return NULL; } expr->eval_fn = eval_fn; expr->argc = argc; expr->argv = argv; - expr->persistent_fds = 0; - expr->ephemeral_fds = 0; - expr->pure = false; - expr->always_true = false; - expr->always_false = false; - expr->cost = 0.0; expr->probability = 0.5; - expr->evaluations = 0; - expr->successes = 0; - expr->elapsed.tv_sec = 0; - expr->elapsed.tv_nsec = 0; - - // Prevent bfs_expr_free() from freeing uninitialized pointers on error paths - if (bfs_expr_is_parent(expr)) { - expr->lhs = NULL; - expr->rhs = NULL; - } else if (eval_fn == eval_exec) { - expr->exec = NULL; - } else if (eval_fn == eval_fprintf) { - expr->printf = NULL; - } else if (eval_fn == eval_regex) { - expr->regex = NULL; - } - return expr; } diff --git a/src/pwcache.c b/src/pwcache.c index f52e4e1..9f32eb0 100644 --- a/src/pwcache.c +++ b/src/pwcache.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "pwcache.h" +#include "alloc.h" #include "config.h" #include "darray.h" #include "trie.h" @@ -71,7 +72,7 @@ struct bfs_users { }; struct bfs_users *bfs_users_new(void) { - struct bfs_users *users = malloc(sizeof(*users)); + struct bfs_users *users = ALLOC(struct bfs_users); if (!users) { return NULL; } @@ -144,7 +145,7 @@ struct bfs_groups { }; struct bfs_groups *bfs_groups_new(void) { - struct bfs_groups *groups = malloc(sizeof(*groups)); + struct bfs_groups *groups = ALLOC(struct bfs_groups); if (!groups) { return NULL; } diff --git a/src/trie.c b/src/trie.c index 8543eb1..19423cf 100644 --- a/src/trie.c +++ b/src/trie.c @@ -82,6 +82,7 @@ */ #include "trie.h" +#include "alloc.h" #include "bit.h" #include "config.h" #include "diag.h" @@ -317,7 +318,7 @@ struct trie_leaf *trie_find_prefix(const struct trie *trie, const char *key) { /** Create a new leaf, holding a copy of the given key. */ static struct trie_leaf *trie_leaf_alloc(struct trie *trie, const void *key, size_t length) { - struct trie_leaf *leaf = malloc(flex_sizeof(struct trie_leaf, key, length)); + struct trie_leaf *leaf = ALLOC_FLEX(struct trie_leaf, key, length); if (!leaf) { return NULL; } @@ -339,12 +340,10 @@ static void trie_leaf_free(struct trie *trie, struct trie_leaf *leaf) { /** Compute the size of a trie node with a certain number of children. */ static size_t trie_node_size(unsigned int size) { - // Empty nodes aren't supported - bfs_assert(size > 0); // Node size must be a power of two bfs_assert(has_single_bit(size)); - return flex_sizeof(struct trie_node, children, size); + return sizeof_flex(struct trie_node, children, size); } #if ENDIAN_NATIVE == ENDIAN_LITTLE diff --git a/src/xregex.c b/src/xregex.c index 89c2e90..ab5f793 100644 --- a/src/xregex.c +++ b/src/xregex.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "xregex.h" +#include "alloc.h" #include "config.h" #include "diag.h" #include "sanity.h" @@ -115,7 +116,7 @@ static int bfs_onig_initialize(OnigEncoding *enc) { #endif int bfs_regcomp(struct bfs_regex **preg, const char *pattern, enum bfs_regex_type type, enum bfs_regcomp_flags flags) { - struct bfs_regex *regex = *preg = malloc(sizeof(*regex)); + struct bfs_regex *regex = *preg = ALLOC(struct bfs_regex); if (!regex) { return -1; } diff --git a/src/xspawn.c b/src/xspawn.c index 740e38e..2cabdcc 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "xspawn.h" +#include "alloc.h" #include "bfstd.h" #include "config.h" #include "list.h" @@ -62,7 +63,7 @@ int bfs_spawn_setflags(struct bfs_spawn *ctx, enum bfs_spawn_flags flags) { /** Add a spawn action to the chain. */ static struct bfs_spawn_action *bfs_spawn_add(struct bfs_spawn *ctx, enum bfs_spawn_op op) { - struct bfs_spawn_action *action = malloc(sizeof(*action)); + struct bfs_spawn_action *action = ALLOC(struct bfs_spawn_action); if (!action) { return NULL; } diff --git a/tests/alloc.c b/tests/alloc.c new file mode 100644 index 0000000..91b1b43 --- /dev/null +++ b/tests/alloc.c @@ -0,0 +1,24 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include "../src/alloc.h" +#include "../src/diag.h" +#include + +int main(void) { + // Check sizeof_flex() + struct flexible { + alignas(64) int foo; + int bar[]; + }; + bfs_verify(sizeof_flex(struct flexible, bar, 0) >= sizeof(struct flexible)); + bfs_verify(sizeof_flex(struct flexible, bar, 16) % alignof(struct flexible) == 0); + bfs_verify(sizeof_flex(struct flexible, bar, SIZE_MAX / sizeof(int) + 1) + == align_floor(alignof(struct flexible), SIZE_MAX)); + + // Corner case: sizeof(type) > align_ceil(alignof(type), offsetof(type, member)) + // Doesn't happen in typical ABIs + bfs_verify(flex_size(8, 16, 4, 4, 1) == 16); + + return EXIT_SUCCESS; +} diff --git a/tests/bfstd.c b/tests/bfstd.c index 7fea9b5..fa854a8 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -24,17 +24,6 @@ static void check_base_dir(const char *path, const char *dir, const char *base) } int main(void) { - // Check flex_sizeof() - struct flexible { - alignas(64) int foo; - int bar[]; - }; - bfs_verify(flex_sizeof(struct flexible, bar, 0) >= sizeof(struct flexible)); - bfs_verify(flex_sizeof(struct flexible, bar, 16) % alignof(struct flexible) == 0); - bfs_verify(flex_sizeof(struct flexible, bar, SIZE_MAX / sizeof(int) + 1) - == align_floor(alignof(struct flexible), SIZE_MAX)); - bfs_verify(flex_sizeof_impl(8, 16, 4, 4, 1) == 16); - // From man 3p basename check_base_dir("usr", ".", "usr"); check_base_dir("usr/", ".", "usr"); @@ -46,4 +35,6 @@ int main(void) { check_base_dir("/usr/lib", "/usr", "lib"); check_base_dir("//usr//lib//", "//usr", "lib"); check_base_dir("/home//dwc//test", "/home//dwc", "test"); + + return EXIT_SUCCESS; } -- cgit v1.2.3 From 1649d37f7c88f8a5930fdd4c1ff1b59212da90ee Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 28 Oct 2022 21:27:20 -0400 Subject: bftw: Arena-allocate struct bftw_file --- src/bftw.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 7ab14c7..d0491e0 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -100,6 +100,9 @@ struct bftw_cache { struct bftw_file *target; /** The remaining capacity of the LRU list. */ size_t capacity; + + /** bftw_file arena. */ + struct varena files; }; /** Initialize a cache. */ @@ -107,6 +110,7 @@ static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) { LIST_INIT(cache); cache->target = NULL; cache->capacity = capacity; + VARENA_INIT(&cache->files, struct bftw_file, name); } /** Remove a bftw_file from the LRU list. */ @@ -237,12 +241,14 @@ static void bftw_cache_destroy(struct bftw_cache *cache) { bfs_assert(!cache->head); bfs_assert(!cache->tail); bfs_assert(!cache->target); + + varena_destroy(&cache->files); } /** Create a new bftw_file. */ -static struct bftw_file *bftw_file_new(struct bftw_file *parent, const char *name) { +static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_file *parent, const char *name) { size_t namelen = strlen(name); - struct bftw_file *file = ALLOC_FLEX(struct bftw_file, name, namelen + 1); + struct bftw_file *file = varena_alloc(&cache->files, namelen + 1); if (!file) { return NULL; } @@ -419,7 +425,7 @@ static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) { bftw_file_close(cache, file); } - free(file); + varena_free(&cache->files, file, file->namelen + 1); } /** @@ -1212,7 +1218,7 @@ static int bftw_visit(struct bftw_state *state, const char *name) { struct bftw_file *file = state->file; if (name && (state->flags & BFTW_BUFFER)) { - file = bftw_file_new(file, name); + file = bftw_file_new(&state->cache, file, name); if (!file) { state->error = errno; return -1; @@ -1229,7 +1235,7 @@ static int bftw_visit(struct bftw_state *state, const char *name) { switch (bftw_call_back(state, name, BFTW_PRE)) { case BFTW_CONTINUE: if (name) { - file = bftw_file_new(state->file, name); + file = bftw_file_new(&state->cache, state->file, name); } else { state->file = NULL; } -- cgit v1.2.3 From a1490d98a1aebb3bfbd3873613977d0341ec7f98 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 20 Jun 2023 12:02:08 -0400 Subject: dir: Arena-allocate directories --- src/bftw.c | 37 +++++++++++++++++++++++++++++-------- src/dir.c | 44 +++++++++++++++++++++++--------------------- src/dir.h | 27 +++++++++++++++++++++++---- src/eval.c | 34 ++++++++++++++++++++++------------ src/ioq.c | 15 ++++++++++----- src/ioq.h | 4 +++- 6 files changed, 110 insertions(+), 51 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index d0491e0..69e41a2 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -103,6 +103,8 @@ struct bftw_cache { /** bftw_file arena. */ struct varena files; + /** bfs_dir arena. */ + struct arena dirs; }; /** Initialize a cache. */ @@ -111,6 +113,7 @@ static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) { cache->target = NULL; cache->capacity = capacity; VARENA_INIT(&cache->files, struct bftw_file, name); + bfs_dir_arena(&cache->dirs); } /** Remove a bftw_file from the LRU list. */ @@ -136,6 +139,7 @@ static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) { if (file->dir) { bfs_assert(file->fd == bfs_dirfd(file->dir)); bfs_closedir(file->dir); + arena_free(&cache->dirs, file->dir); file->dir = NULL; } else { xclose(file->fd); @@ -157,9 +161,10 @@ static void bftw_file_freedir(struct bftw_cache *cache, struct bftw_file *file) bool pinned = file->pincount > 0; if (reffed || pinned) { - int fd = bfs_freedir(file->dir, pinned); + int fd = bfs_fdclosedir(file->dir, pinned); if (fd >= 0) { file->fd = fd; + arena_free(&cache->dirs, file->dir); file->dir = NULL; } } else { @@ -243,6 +248,7 @@ static void bftw_cache_destroy(struct bftw_cache *cache) { bfs_assert(!cache->target); varena_destroy(&cache->files); + arena_destroy(&cache->dirs); } /** Create a new bftw_file. */ @@ -410,10 +416,17 @@ static struct bfs_dir *bftw_file_opendir(struct bftw_cache *cache, struct bftw_f return NULL; } - struct bfs_dir *dir = bfs_opendir(fd, NULL); - if (dir) { - bftw_file_set_dir(cache, file, dir); + struct bfs_dir *dir = arena_alloc(&cache->dirs); + if (!dir) { + return NULL; + } + + if (bfs_opendir(dir, fd, NULL) != 0) { + arena_free(&cache->dirs, dir); + return NULL; } + + bftw_file_set_dir(cache, file, dir); return dir; } @@ -900,14 +913,18 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { goto unpin; } } - --cache->capacity; - if (ioq_opendir(state->ioq, dfd, file->name, file) != 0) { - ++cache->capacity; + struct bfs_dir *dir = arena_alloc(&state->cache.dirs); + if (!dir) { goto unpin; } + if (ioq_opendir(state->ioq, dir, dfd, file->name, file) != 0) { + goto free; + } + file->ioqueued = true; + --cache->capacity; if (state->flags & BFTW_SORT) { goto append; @@ -915,6 +932,8 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { return; } +free: + arena_free(&state->cache.dirs, dir); unpin: if (file->parent) { bftw_cache_unpin(cache, file->parent); @@ -950,7 +969,9 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { bftw_cache_unpin(cache, file->parent); } - if (res->dir) { + if (res->error) { + arena_free(&state->cache.dirs, res->dir); + } else { bftw_file_set_dir(cache, file, res->dir); } diff --git a/src/dir.c b/src/dir.c index b9fd74d..a24b572 100644 --- a/src/dir.c +++ b/src/dir.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "dir.h" +#include "alloc.h" #include "bfstd.h" #include "config.h" #include "diag.h" @@ -120,26 +121,26 @@ struct bfs_dir { # define DIR_SIZE sizeof(struct bfs_dir) #endif -struct bfs_dir *bfs_opendir(int at_fd, const char *at_path) { - struct bfs_dir *dir = malloc(DIR_SIZE); - if (!dir) { - return NULL; - } +struct bfs_dir *bfs_allocdir(void) { + return malloc(DIR_SIZE); +} + +void bfs_dir_arena(struct arena *arena) { + arena_init(arena, alignof(struct bfs_dir), DIR_SIZE); +} +int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path) { int fd; if (at_path) { fd = openat(at_fd, at_path, O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (fd < 0) { + return -1; + } } else if (at_fd >= 0) { fd = at_fd; } else { - free(dir); errno = EBADF; - return NULL; - } - - if (fd < 0) { - free(dir); - return NULL; + return -1; } #if BFS_GETDENTS @@ -152,14 +153,13 @@ struct bfs_dir *bfs_opendir(int at_fd, const char *at_path) { if (at_path) { close_quietly(fd); } - free(dir); - return NULL; + return -1; } dir->de = NULL; #endif dir->eof = false; - return dir; + return 0; } int bfs_dirfd(const struct bfs_dir *dir) { @@ -291,17 +291,16 @@ int bfs_closedir(struct bfs_dir *dir) { bfs_verify(errno != EBADF); } #endif - free(dir); + + sanitize_uninit(dir, DIR_SIZE); return ret; } -int bfs_freedir(struct bfs_dir *dir, bool same_fd) { +int bfs_fdclosedir(struct bfs_dir *dir, bool same_fd) { #if BFS_GETDENTS int ret = dir->fd; - free(dir); #elif __FreeBSD__ int ret = fdclosedir(dir->dir); - free(dir); #else if (same_fd) { errno = ENOTSUP; @@ -309,10 +308,13 @@ int bfs_freedir(struct bfs_dir *dir, bool same_fd) { } int ret = dup_cloexec(dirfd(dir->dir)); - if (ret >= 0) { - bfs_closedir(dir); + if (ret < 0) { + return -1; } + + bfs_closedir(dir); #endif + sanitize_uninit(dir, DIR_SIZE); return ret; } diff --git a/src/dir.h b/src/dir.h index 6fe7ae2..16f592e 100644 --- a/src/dir.h +++ b/src/dir.h @@ -8,6 +8,7 @@ #ifndef BFS_DIR_H #define BFS_DIR_H +#include "alloc.h" #include "config.h" #include @@ -61,18 +62,36 @@ struct bfs_dirent { const char *name; }; +/** + * Allocate space for a directory. + * + * @return + * An allocated, unopen directory, or NULL on failure. + */ +struct bfs_dir *bfs_allocdir(void); + +/** + * Initialize an arena for directories. + * + * @param arena + * The arena to initialize. + */ +void bfs_dir_arena(struct arena *arena); + /** * Open a directory. * + * @param dir + * The allocated directory. * @param at_fd * The base directory for path resolution. * @param at_path * The path of the directory to open, relative to at_fd. Pass NULL to * open at_fd itself. * @return - * The opened directory, or NULL on failure. + * 0 on success, or -1 on failure. */ -struct bfs_dir *bfs_opendir(int at_fd, const char *at_path); +int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path); /** * Get the file descriptor for a directory. @@ -110,7 +129,7 @@ int bfs_readdir(struct bfs_dir *dir, struct bfs_dirent *de); int bfs_closedir(struct bfs_dir *dir); /** - * Free a directory, keeping an open file descriptor to it. + * Extract the file descriptor from an open directory. * * @param dir * The directory to free. @@ -122,6 +141,6 @@ int bfs_closedir(struct bfs_dir *dir); * On success, a file descriptor for the directory is returned. On * failure, -1 is returned, and the directory remains open. */ -int bfs_freedir(struct bfs_dir *dir, bool same_fd); +int bfs_fdclosedir(struct bfs_dir *dir, bool same_fd); #endif // BFS_DIR_H diff --git a/src/eval.c b/src/eval.c index f16821e..5f27681 100644 --- a/src/eval.c +++ b/src/eval.c @@ -421,10 +421,15 @@ bool eval_empty(const struct bfs_expr *expr, struct bfs_eval *state) { const struct BFTW *ftwbuf = state->ftwbuf; if (ftwbuf->type == BFS_DIR) { - struct bfs_dir *dir = bfs_opendir(ftwbuf->at_fd, ftwbuf->at_path); + struct bfs_dir *dir = bfs_allocdir(); if (!dir) { eval_report_error(state); - goto done; + return ret; + } + + if (bfs_opendir(dir, ftwbuf->at_fd, ftwbuf->at_path) != 0) { + eval_report_error(state); + return ret; } int did_read = bfs_readdir(dir, NULL); @@ -435,6 +440,7 @@ bool eval_empty(const struct bfs_expr *expr, struct bfs_eval *state) { } bfs_closedir(dir); + free(dir); } else if (ftwbuf->type == BFS_REG) { const struct bfs_stat *statbuf = eval_stat(state); if (statbuf) { @@ -442,7 +448,6 @@ bool eval_empty(const struct bfs_expr *expr, struct bfs_eval *state) { } } -done: return ret; } @@ -1495,20 +1500,25 @@ static int infer_fdlimit(const struct bfs_ctx *ctx, int limit) { // Check /proc/self/fd for the current number of open fds, if possible // (we may have inherited more than just the standard ones) - struct bfs_dir *dir = bfs_opendir(AT_FDCWD, "/proc/self/fd"); + struct bfs_dir *dir = bfs_allocdir(); if (!dir) { - dir = bfs_opendir(AT_FDCWD, "/dev/fd"); + goto done; } - if (dir) { - // Account for 'dir' itself - nopen = -1; - while (bfs_readdir(dir, NULL) > 0) { - ++nopen; - } + if (bfs_opendir(dir, AT_FDCWD, "/proc/self/fd") != 0 + && bfs_opendir(dir, AT_FDCWD, "/dev/fd") != 0) { + goto done; + } - bfs_closedir(dir); + // Account for 'dir' itself + nopen = -1; + + while (bfs_readdir(dir, NULL) > 0) { + ++nopen; } + bfs_closedir(dir); +done: + free(dir); int ret = limit - nopen; ret -= ctx->expr->persistent_fds; diff --git a/src/ioq.c b/src/ioq.c index 33316fa..47b082a 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -20,9 +20,11 @@ * An I/O queue request. */ struct ioq_req { + /** Directory allocation. */ + struct bfs_dir *dir; /** Base file descriptor for openat(). */ int dfd; - /** Relative path to dfd. */ + /** Path to open, relative to dfd. */ const char *path; /** Arbitrary user data. */ @@ -295,10 +297,12 @@ static void *ioq_work(void *ptr) { struct ioq_res *res = &cmd->res; res->ptr = req.ptr; - res->dir = bfs_opendir(req.dfd, req.path); - res->error = errno; - if (res->dir) { + res->dir = req.dir; + res->error = 0; + if (bfs_opendir(req.dir, req.dfd, req.path) == 0) { bfs_polldir(res->dir); + } else { + res->error = errno; } ioqq_push(ioq->ready, cmd); @@ -344,7 +348,7 @@ fail: return NULL; } -int ioq_opendir(struct ioq *ioq, int dfd, const char *path, void *ptr) { +int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, void *ptr) { if (ioq->size >= ioq->depth) { return -1; } @@ -355,6 +359,7 @@ int ioq_opendir(struct ioq *ioq, int dfd, const char *path, void *ptr) { } struct ioq_req *req = &cmd->req; + req->dir = dir; req->dfd = dfd; req->path = path; req->ptr = ptr; diff --git a/src/ioq.h b/src/ioq.h index 0af5779..50e02b1 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -45,6 +45,8 @@ struct ioq *ioq_create(size_t depth, size_t nthreads); * * @param ioq * The I/O queue. + * @param dir + * The allocated directory. * @param dfd * The base file descriptor. * @param path @@ -54,7 +56,7 @@ struct ioq *ioq_create(size_t depth, size_t nthreads); * @return * 0 on success, or -1 on failure. */ -int ioq_opendir(struct ioq *ioq, int dfd, const char *path, void *ptr); +int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, void *ptr); /** * Pop a response from the queue. -- cgit v1.2.3 From abd29143d805fa16c65489d5b1d79428943d0187 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 26 Jun 2023 11:47:41 -0400 Subject: ioq: New ioq_cancel() function --- src/bftw.c | 4 ++++ src/ioq.c | 27 ++++++++++++++++++++------- src/ioq.h | 5 +++++ 3 files changed, 29 insertions(+), 7 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 69e41a2..2bdf12d 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -1147,6 +1147,10 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { static int bftw_state_destroy(struct bftw_state *state) { dstrfree(state->path); + if (state->ioq) { + ioq_cancel(state->ioq); + } + SLIST_EXTEND(&state->files, &state->batch); do { bftw_gc(state, BFTW_VISIT_NONE); diff --git a/src/ioq.c b/src/ioq.c index 617bd5f..457ead7 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -264,6 +264,8 @@ struct ioq { size_t depth; /** The current size of the queue. */ size_t size; + /** Cancellation flag. */ + atomic bool cancel; /** ioq_cmd command arena. */ struct arena cmds; @@ -289,17 +291,22 @@ static void *ioq_work(void *ptr) { break; } + bool cancel = load(&ioq->cancel, relaxed); + struct ioq_req req = cmd->req; sanitize_uninit(cmd); struct ioq_res *res = &cmd->res; res->ptr = req.ptr; res->dir = req.dir; - res->error = 0; - if (bfs_opendir(req.dir, req.dfd, req.path) == 0) { - bfs_polldir(res->dir); - } else { + + if (cancel) { + res->error = EINTR; + } else if (bfs_opendir(req.dir, req.dfd, req.path) != 0) { res->error = errno; + } else { + res->error = 0; + bfs_polldir(res->dir); } ioqq_push(ioq->ready, cmd); @@ -394,14 +401,20 @@ void ioq_free(struct ioq *ioq, struct ioq_res *res) { arena_free(&ioq->cmds, (union ioq_cmd *)res); } +void ioq_cancel(struct ioq *ioq) { + if (!exchange(&ioq->cancel, true, relaxed)) { + for (size_t i = 0; i < ioq->nthreads; ++i) { + ioqq_push(ioq->pending, &IOQ_STOP); + } + } +} + void ioq_destroy(struct ioq *ioq) { if (!ioq) { return; } - for (size_t i = 0; i < ioq->nthreads; ++i) { - ioqq_push(ioq->pending, &IOQ_STOP); - } + ioq_cancel(ioq); for (size_t i = 0; i < ioq->nthreads; ++i) { if (pthread_join(ioq->threads[i], NULL) != 0) { diff --git a/src/ioq.h b/src/ioq.h index 50e02b1..064e2e2 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -88,6 +88,11 @@ struct ioq_res *ioq_trypop(struct ioq *ioq); */ void ioq_free(struct ioq *ioq, struct ioq_res *res); +/** + * Cancel any pending I/O operations. + */ +void ioq_cancel(struct ioq *ioq); + /** * Stop and destroy an I/O queue. */ -- cgit v1.2.3 From 35d357793c35ed0263d57a439323027c80a4a5b7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 4 Jul 2023 15:03:37 -0400 Subject: bftw: If the ioq is full, try to pop before ioq_opendir() --- src/bftw.c | 131 ++++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 82 insertions(+), 49 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 2bdf12d..ec67817 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -461,8 +461,12 @@ struct bftw_state { /** The cache of open directories. */ struct bftw_cache cache; + /** The async I/O queue. */ struct ioq *ioq; + /** The number of I/O threads. */ + size_t nthreads; + /** The queue of directories to read. */ struct bftw_list dirs; /** The queue of files to visit. */ @@ -536,6 +540,7 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg return -1; } } + state->nthreads = nthreads; SLIST_INIT(&state->dirs); SLIST_INIT(&state->files); @@ -889,13 +894,81 @@ static enum bftw_action bftw_call_back(struct bftw_state *state, const char *nam } } +/** Pop a response from the I/O queue. */ +static int bftw_ioq_pop(struct bftw_state *state, bool block) { + struct ioq *ioq = state->ioq; + if (!ioq) { + return -1; + } + + struct ioq_res *res = block ? ioq_pop(ioq) : ioq_trypop(ioq); + if (!res) { + return -1; + } + + struct bftw_cache *cache = &state->cache; + ++cache->capacity; + + struct bftw_file *file = res->ptr; + file->ioqueued = false; + + if (file->parent) { + bftw_cache_unpin(cache, file->parent); + } + + if (res->error) { + arena_free(&cache->dirs, res->dir); + } else { + bftw_file_set_dir(cache, file, res->dir); + } + + ioq_free(ioq, res); + + if (!(state->flags & BFTW_SORT)) { + SLIST_PREPEND(&state->dirs, file); + } + + return 0; +} + +/** Try to reserve space in the I/O queue. */ +static int bftw_ioq_reserve(struct bftw_state *state) { + struct ioq *ioq = state->ioq; + if (!ioq) { + return -1; + } + + if (ioq_capacity(ioq) > 0) { + return 0; + } + + // With more than two threads, it is faster to wait for an I/O operation + // to complete than it is to do it ourselves + bool block = state->nthreads > 2; + if (bftw_ioq_pop(state, block) < 0) { + return -1; + } + + return 0; +} + +/** Try to reserve space in the cache. */ +static int bftw_cache_reserve(struct bftw_state *state) { + struct bftw_cache *cache = &state->cache; + if (cache->capacity > 0) { + return 0; + } + + return bftw_cache_pop(cache); +} + /** Push a directory onto the queue. */ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { bfs_assert(file->type == BFS_DIR); struct bftw_cache *cache = &state->cache; - if (!state->ioq) { + if (bftw_ioq_reserve(state) != 0) { goto append; } @@ -908,13 +981,11 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { bftw_cache_pin(cache, file->parent); } - if (cache->capacity == 0) { - if (bftw_cache_pop(cache) != 0) { - goto unpin; - } + if (bftw_cache_reserve(state) != 0) { + goto unpin; } - struct bfs_dir *dir = arena_alloc(&state->cache.dirs); + struct bfs_dir *dir = arena_alloc(&cache->dirs); if (!dir) { goto unpin; } @@ -927,13 +998,17 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { --cache->capacity; if (state->flags & BFTW_SORT) { + // When sorting, dirs are always kept in order in state->dirs, + // and we wait for the ioq to open them before popping goto append; } else { + // When not sorting, dirs are not added to state->dirs until + // popped from the ioq return; } free: - arena_free(&state->cache.dirs, dir); + arena_free(&cache->dirs, dir); unpin: if (file->parent) { bftw_cache_unpin(cache, file->parent); @@ -942,48 +1017,6 @@ append: SLIST_APPEND(&state->dirs, file); } -/** Pop a response from the I/O queue. */ -static int bftw_ioq_pop(struct bftw_state *state, bool block) { - if (!state->ioq) { - return -1; - } - - struct ioq_res *res; - if (block) { - res = ioq_pop(state->ioq); - } else { - res = ioq_trypop(state->ioq); - } - - if (!res) { - return -1; - } - - struct bftw_cache *cache = &state->cache; - ++cache->capacity; - - struct bftw_file *file = res->ptr; - file->ioqueued = false; - - if (file->parent) { - bftw_cache_unpin(cache, file->parent); - } - - if (res->error) { - arena_free(&state->cache.dirs, res->dir); - } else { - bftw_file_set_dir(cache, file, res->dir); - } - - ioq_free(state->ioq, res); - - if (!(state->flags & BFTW_SORT)) { - SLIST_PREPEND(&state->dirs, file); - } - - return 0; -} - /** Pop a directory to read from the queue. */ static bool bftw_pop_dir(struct bftw_state *state) { bfs_assert(!state->file); -- cgit v1.2.3 From 222ac5ba4fbab0ab880e36423d0f1338e39b02c7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 4 Jul 2023 15:17:23 -0400 Subject: ioq: Implement async close() and closedir() --- src/bftw.c | 41 ++++++++------- src/ioq.c | 175 ++++++++++++++++++++++++++++++++++++------------------------- src/ioq.h | 81 ++++++++++++++++++++++++---- 3 files changed, 197 insertions(+), 100 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index ec67817..64f221b 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -901,34 +901,39 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { return -1; } - struct ioq_res *res = block ? ioq_pop(ioq) : ioq_trypop(ioq); - if (!res) { + struct ioq_ent *ent = block ? ioq_pop(ioq) : ioq_trypop(ioq); + if (!ent) { return -1; } struct bftw_cache *cache = &state->cache; - ++cache->capacity; - - struct bftw_file *file = res->ptr; - file->ioqueued = false; + struct bftw_file *file; + struct bfs_dir *dir; - if (file->parent) { - bftw_cache_unpin(cache, file->parent); - } + enum ioq_op op = ent->op; + if (op == IOQ_OPENDIR) { + file = ent->ptr; + file->ioqueued = false; - if (res->error) { - arena_free(&cache->dirs, res->dir); - } else { - bftw_file_set_dir(cache, file, res->dir); - } + ++cache->capacity; + if (file->parent) { + bftw_cache_unpin(cache, file->parent); + } - ioq_free(ioq, res); + dir = ent->opendir.dir; + if (ent->ret == 0) { + bftw_file_set_dir(cache, file, dir); + } else { + arena_free(&cache->dirs, dir); + } - if (!(state->flags & BFTW_SORT)) { - SLIST_PREPEND(&state->dirs, file); + if (!(state->flags & BFTW_SORT)) { + SLIST_PREPEND(&state->dirs, file); + } } - return 0; + ioq_free(ioq, ent); + return op; } /** Try to reserve space in the I/O queue. */ diff --git a/src/ioq.c b/src/ioq.c index 5673c77..0544044 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -16,29 +16,6 @@ #include #include -/** - * An I/O queue request. - */ -struct ioq_req { - /** Directory allocation. */ - struct bfs_dir *dir; - /** Base file descriptor for openat(). */ - int dfd; - /** Path to open, relative to dfd. */ - const char *path; - - /** Arbitrary user data. */ - void *ptr; -}; - -/** - * An I/O queue command. - */ -union ioq_cmd { - struct ioq_req req; - struct ioq_res res; -}; - /** * A monitor for an I/O queue slot. */ @@ -101,7 +78,7 @@ bfs_static_assert(IOQ_STRIDE % 2 == 1); /** Slot flag bit to indicate waiters. */ #define IOQ_BLOCKED ((uintptr_t)1) -bfs_static_assert(alignof(union ioq_cmd) > 1); +bfs_static_assert(alignof(struct ioq_ent) > 1); /** Destroy an I/O command queue. */ static void ioqq_destroy(struct ioqq *ioqq) { @@ -189,12 +166,12 @@ static void ioqq_wake(struct ioqq *ioqq, size_t i) { cond_broadcast(&monitor->cond); } -/** Push a command onto the queue. */ -static void ioqq_push(struct ioqq *ioqq, union ioq_cmd *cmd) { +/** Push an entry onto the queue. */ +static void ioqq_push(struct ioqq *ioqq, struct ioq_ent *ent) { size_t i = fetch_add(&ioqq->head, IOQ_STRIDE, relaxed); atomic uintptr_t *slot = &ioqq->slots[i & ioqq->slot_mask]; - uintptr_t addr = (uintptr_t)cmd; + uintptr_t addr = (uintptr_t)ent; bfs_assert(!(addr & IOQ_BLOCKED)); uintptr_t prev = load(slot, relaxed); @@ -212,8 +189,8 @@ static void ioqq_push(struct ioqq *ioqq, union ioq_cmd *cmd) { } } -/** Pop a command from a queue. */ -static union ioq_cmd *ioqq_pop(struct ioqq *ioqq) { +/** Pop an entry from the queue. */ +static struct ioq_ent *ioqq_pop(struct ioqq *ioqq) { size_t i = fetch_add(&ioqq->tail, IOQ_STRIDE, relaxed); atomic uintptr_t *slot = &ioqq->slots[i & ioqq->slot_mask]; @@ -232,11 +209,11 @@ static union ioq_cmd *ioqq_pop(struct ioqq *ioqq) { } prev &= ~IOQ_BLOCKED; - return (union ioq_cmd *)prev; + return (struct ioq_ent *)prev; } -/** Pop a command from a queue if one is available. */ -static union ioq_cmd *ioqq_trypop(struct ioqq *ioqq) { +/** Pop an entry from the queue if one is available. */ +static struct ioq_ent *ioqq_trypop(struct ioqq *ioqq) { size_t i = load(&ioqq->tail, relaxed); atomic uintptr_t *slot = &ioqq->slots[i & ioqq->slot_mask]; @@ -257,11 +234,11 @@ static union ioq_cmd *ioqq_trypop(struct ioqq *ioqq) { bfs_assert(j == i, "ioqq_trypop() only supports a single consumer"); (void)j; - return (union ioq_cmd *)prev; + return (struct ioq_ent *)prev; } /** Sentinel stop command. */ -static union ioq_cmd IOQ_STOP; +static struct ioq_ent IOQ_STOP; struct ioq { /** The depth of the queue. */ @@ -271,8 +248,8 @@ struct ioq { /** Cancellation flag. */ atomic bool cancel; - /** ioq_cmd command arena. */ - struct arena cmds; + /** ioq_ent arena. */ + struct arena ents; /** Pending I/O requests. */ struct ioqq *pending; @@ -290,30 +267,50 @@ static void *ioq_work(void *ptr) { struct ioq *ioq = ptr; while (true) { - union ioq_cmd *cmd = ioqq_pop(ioq->pending); - if (cmd == &IOQ_STOP) { + struct ioq_ent *ent = ioqq_pop(ioq->pending); + if (ent == &IOQ_STOP) { break; } bool cancel = load(&ioq->cancel, relaxed); - struct ioq_req req = cmd->req; - sanitize_uninit(cmd); + ent->ret = -1; + + switch (ent->op) { + case IOQ_CLOSE: + // Always close(), even if we're cancelled, just like a real EINTR + ent->ret = xclose(ent->close.fd); + break; + + case IOQ_OPENDIR: + if (!cancel) { + struct ioq_opendir *args = &ent->opendir; + ent->ret = bfs_opendir(args->dir, args->dfd, args->path); + if (ent->ret == 0) { + bfs_polldir(args->dir); + } + } + break; + + case IOQ_CLOSEDIR: + ent->ret = bfs_closedir(ent->closedir.dir); + break; - struct ioq_res *res = &cmd->res; - res->ptr = req.ptr; - res->dir = req.dir; + default: + bfs_bug("Unknown ioq_op %d", (int)ent->op); + errno = ENOSYS; + break; + } if (cancel) { - res->error = EINTR; - } else if (bfs_opendir(req.dir, req.dfd, req.path) != 0) { - res->error = errno; + ent->error = EINTR; + } else if (ent->ret < 0) { + ent->error = errno; } else { - res->error = 0; - bfs_polldir(res->dir); + ent->error = 0; } - ioqq_push(ioq->ready, cmd); + ioqq_push(ioq->ready, ent); } return NULL; @@ -326,7 +323,7 @@ struct ioq *ioq_create(size_t depth, size_t nthreads) { } ioq->depth = depth; - ARENA_INIT(&ioq->cmds, union ioq_cmd); + ARENA_INIT(&ioq->ents, struct ioq_ent); ioq->pending = ioqq_create(depth); if (!ioq->pending) { @@ -359,54 +356,88 @@ size_t ioq_capacity(const struct ioq *ioq) { return ioq->depth - ioq->size; } -int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, void *ptr) { +static struct ioq_ent *ioq_request(struct ioq *ioq, enum ioq_op op, void *ptr) { + if (load(&ioq->cancel, relaxed)) { + errno = EINTR; + return NULL; + } + if (ioq->size >= ioq->depth) { + errno = EAGAIN; + return NULL; + } + + struct ioq_ent *ent = arena_alloc(&ioq->ents); + if (!ent) { + return NULL; + } + + ent->op = op; + ent->ptr = ptr; + ++ioq->size; + return ent; +} + +int ioq_close(struct ioq *ioq, int fd, void *ptr) { + struct ioq_ent *ent = ioq_request(ioq, IOQ_CLOSE, ptr); + if (!ent) { return -1; } - union ioq_cmd *cmd = arena_alloc(&ioq->cmds); - if (!cmd) { + ent->close.fd = fd; + + ioqq_push(ioq->pending, ent); + return 0; +} + +int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, void *ptr) { + struct ioq_ent *ent = ioq_request(ioq, IOQ_OPENDIR, ptr); + if (!ent) { return -1; } - struct ioq_req *req = &cmd->req; - req->dir = dir; - req->dfd = dfd; - req->path = path; - req->ptr = ptr; + struct ioq_opendir *args = &ent->opendir; + args->dir = dir; + args->dfd = dfd; + args->path = path; - ioqq_push(ioq->pending, cmd); - ++ioq->size; + ioqq_push(ioq->pending, ent); return 0; } -struct ioq_res *ioq_pop(struct ioq *ioq) { - if (ioq->size == 0) { - return NULL; +int ioq_closedir(struct ioq *ioq, struct bfs_dir *dir, void *ptr) { + struct ioq_ent *ent = ioq_request(ioq, IOQ_CLOSEDIR, ptr); + if (!ent) { + return -1; } - union ioq_cmd *cmd = ioqq_pop(ioq->ready); - return &cmd->res; + ent->closedir.dir = dir; + + ioqq_push(ioq->pending, ent); + return 0; } -struct ioq_res *ioq_trypop(struct ioq *ioq) { +struct ioq_ent *ioq_pop(struct ioq *ioq) { if (ioq->size == 0) { return NULL; } - union ioq_cmd *cmd = ioqq_trypop(ioq->ready); - if (!cmd) { + return ioqq_pop(ioq->ready); +} + +struct ioq_ent *ioq_trypop(struct ioq *ioq) { + if (ioq->size == 0) { return NULL; } - return &cmd->res; + return ioqq_trypop(ioq->ready); } -void ioq_free(struct ioq *ioq, struct ioq_res *res) { +void ioq_free(struct ioq *ioq, struct ioq_ent *ent) { bfs_assert(ioq->size > 0); --ioq->size; - arena_free(&ioq->cmds, (union ioq_cmd *)res); + arena_free(&ioq->ents, ent); } void ioq_cancel(struct ioq *ioq) { @@ -431,7 +462,7 @@ void ioq_destroy(struct ioq *ioq) { ioqq_destroy(ioq->ready); ioqq_destroy(ioq->pending); - arena_destroy(&ioq->cmds); + arena_destroy(&ioq->ents); free(ioq); } diff --git a/src/ioq.h b/src/ioq.h index 9901293..99c18c2 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -16,16 +16,49 @@ struct ioq; /** - * An I/O queue response. + * I/O queue operations. */ -struct ioq_res { - /** The opened directory. */ - struct bfs_dir *dir; +enum ioq_op { + /** ioq_close(). */ + IOQ_CLOSE, + /** ioq_opendir(). */ + IOQ_OPENDIR, + /** ioq_closedir(). */ + IOQ_CLOSEDIR, +}; + +/** + * An I/O queue entry. + */ +struct ioq_ent { + /** The I/O operation. */ + enum ioq_op op; + + /** The return value of the operation. */ + int ret; /** The error code, if the operation failed. */ int error; /** Arbitrary user data. */ void *ptr; + + /** Operation-specific arguments. */ + union { + /** ioq_close() args. */ + struct ioq_close { + int fd; + } close; + /** ioq_opendir() args. */ + struct ioq_opendir { + struct bfs_dir *dir; + int dfd; + const char *path; + } opendir; + /** ioq_closedir() args. */ + struct ioq_closedir { + struct bfs_dir *dir; + } closedir; + }; }; /** @@ -45,6 +78,20 @@ struct ioq *ioq_create(size_t depth, size_t nthreads); */ size_t ioq_capacity(const struct ioq *ioq); +/** + * Asynchronous close(). + * + * @param ioq + * The I/O queue. + * @param fd + * The fd to close. + * @param ptr + * An arbitrary pointer to associate with the request. + * @return + * 0 on success, or -1 on failure. + */ +int ioq_close(struct ioq *ioq, int fd, void *ptr); + /** * Asynchronous bfs_opendir(). * @@ -63,6 +110,20 @@ size_t ioq_capacity(const struct ioq *ioq); */ int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, void *ptr); +/** + * Asynchronous bfs_closedir(). + * + * @param ioq + * The I/O queue. + * @param dir + * The directory to close. + * @param ptr + * An arbitrary pointer to associate with the request. + * @return + * 0 on success, or -1 on failure. + */ +int ioq_closedir(struct ioq *ioq, struct bfs_dir *dir, void *ptr); + /** * Pop a response from the queue. * @@ -71,7 +132,7 @@ int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, * @return * The next response, or NULL. */ -struct ioq_res *ioq_pop(struct ioq *ioq); +struct ioq_ent *ioq_pop(struct ioq *ioq); /** * Pop a response from the queue, without blocking. @@ -81,17 +142,17 @@ struct ioq_res *ioq_pop(struct ioq *ioq); * @return * The next response, or NULL. */ -struct ioq_res *ioq_trypop(struct ioq *ioq); +struct ioq_ent *ioq_trypop(struct ioq *ioq); /** - * Free a response. + * Free a queue entry. * * @param ioq * The I/O queue. - * @param res - * The response to free. + * @param ent + * The entry to free. */ -void ioq_free(struct ioq *ioq, struct ioq_res *res); +void ioq_free(struct ioq *ioq, struct ioq_ent *ent); /** * Cancel any pending I/O operations. -- cgit v1.2.3 From 739317470873422c1023eab22c570c374a10f498 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 4 Jul 2023 15:37:54 -0400 Subject: bftw: Try to close files asynchronously --- src/bftw.c | 175 ++++++++++++++++++++++++++++++++++++++++++++++++------------- src/dir.c | 20 ++----- src/dir.h | 30 +++++++---- 3 files changed, 163 insertions(+), 62 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 64f221b..aec804b 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -149,29 +149,6 @@ static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) { bftw_cache_remove(cache, file); } -/** Free an open directory. */ -static void bftw_file_freedir(struct bftw_cache *cache, struct bftw_file *file) { - if (!file->dir) { - return; - } - - // Try to keep an open fd if any children exist - bool reffed = file->refcount > 1; - // Keep the fd the same if it's pinned - bool pinned = file->pincount > 0; - - if (reffed || pinned) { - int fd = bfs_fdclosedir(file->dir, pinned); - if (fd >= 0) { - file->fd = fd; - arena_free(&cache->dirs, file->dir); - file->dir = NULL; - } - } else { - bftw_file_close(cache, file); - } -} - /** Pop the least recently used directory from the cache. */ static int bftw_cache_pop(struct bftw_cache *cache) { struct bftw_file *file = cache->tail; @@ -228,7 +205,6 @@ static void bftw_cache_unpin(struct bftw_cache *cache, struct bftw_file *file) { if (--file->pincount == 0) { bftw_lru_add(cache, file); - bftw_file_freedir(cache, file); } } @@ -911,7 +887,18 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { struct bfs_dir *dir; enum ioq_op op = ent->op; - if (op == IOQ_OPENDIR) { + switch (op) { + case IOQ_CLOSE: + ++cache->capacity; + break; + + case IOQ_CLOSEDIR: + ++cache->capacity; + dir = ent->closedir.dir; + arena_free(&cache->dirs, dir); + break; + + case IOQ_OPENDIR: file = ent->ptr; file->ioqueued = false; @@ -930,6 +917,7 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { if (!(state->flags & BFTW_SORT)) { SLIST_PREPEND(&state->dirs, file); } + break; } ioq_free(ioq, ent); @@ -964,6 +952,12 @@ static int bftw_cache_reserve(struct bftw_state *state) { return 0; } + while (bftw_ioq_pop(state, false) >= 0) { + if (cache->capacity > 0) { + return 0; + } + } + return bftw_cache_pop(cache); } @@ -1022,13 +1016,106 @@ append: SLIST_APPEND(&state->dirs, file); } +/** Close a directory, asynchronously if possible. */ +static int bftw_ioq_closedir(struct bftw_state *state, struct bfs_dir *dir) { + if (bftw_ioq_reserve(state) == 0) { + if (ioq_closedir(state->ioq, dir, NULL) == 0) { + return 0; + } + } + + struct bftw_cache *cache = &state->cache; + int ret = bfs_closedir(dir); + arena_free(&cache->dirs, dir); + ++cache->capacity; + return ret; +} + +/** Close a file descriptor, asynchronously if possible. */ +static int bftw_ioq_close(struct bftw_state *state, int fd) { + if (bftw_ioq_reserve(state) == 0) { + if (ioq_close(state->ioq, fd, NULL) == 0) { + return 0; + } + } + + struct bftw_cache *cache = &state->cache; + int ret = xclose(fd); + ++cache->capacity; + return ret; +} + +/** Close a file, asynchronously if possible. */ +static int bftw_close(struct bftw_state *state, struct bftw_file *file) { + bfs_assert(file->fd >= 0); + bfs_assert(file->pincount == 0); + + struct bfs_dir *dir = file->dir; + int fd = file->fd; + + bftw_lru_remove(&state->cache, file); + file->dir = NULL; + file->fd = -1; + + if (dir) { + return bftw_ioq_closedir(state, dir); + } else { + return bftw_ioq_close(state, fd); + } +} + +/** Free an open directory. */ +static int bftw_unwrapdir(struct bftw_state *state, struct bftw_file *file) { + struct bfs_dir *dir = file->dir; + if (!dir) { + return 0; + } + + struct bftw_cache *cache = &state->cache; + + // Try to keep an open fd if any children exist + bool reffed = file->refcount > 1; + // Keep the fd the same if it's pinned + bool pinned = file->pincount > 0; + +#if BFS_USE_UNWRAPDIR + if (reffed || pinned) { + bfs_unwrapdir(dir); + arena_free(&cache->dirs, dir); + file->dir = NULL; + return 0; + } +#else + if (pinned) { + return -1; + } +#endif + + if (!reffed) { + return bftw_close(state, file); + } + + if (bftw_cache_reserve(state) != 0) { + return -1; + } + + int fd = dup_cloexec(file->fd); + if (fd < 0) { + return -1; + } + --cache->capacity; + + file->dir = NULL; + file->fd = fd; + return bftw_ioq_closedir(state, dir); +} + /** Pop a directory to read from the queue. */ static bool bftw_pop_dir(struct bftw_state *state) { bfs_assert(!state->file); - bool have_dirs = state->dirs.head; + struct bftw_file *dir = state->dirs.head; bool have_files = state->files.head; - bool have_room = state->cache.capacity > 0; if (state->flags & BFTW_SORT) { // Keep strict breadth-first order when sorting @@ -1036,16 +1123,25 @@ static bool bftw_pop_dir(struct bftw_state *state) { return false; } } else { - // Block if we have no other files/dirs to visit, or no room in the cache - bool block = !(have_dirs || have_files) || !have_room; - bftw_ioq_pop(state, block); + while (!dir || !dir->dir) { + // Block if we have no other files/dirs to visit, or no room in the cache + bool have_room = state->cache.capacity > 0; + bool block = !(dir || have_files) || !have_room; + + if (bftw_ioq_pop(state, block) < 0) { + break; + } + dir = state->dirs.head; + } } - struct bftw_file *dir = state->file = SLIST_POP(&state->dirs); if (!dir) { return false; } + SLIST_POP(&state->dirs); + state->file = dir; + while (dir->ioqueued) { bftw_ioq_pop(state, true); } @@ -1132,7 +1228,7 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { if (state->dir) { bftw_cache_unpin(&state->cache, state->file); - bftw_file_freedir(&state->cache, state->file); + bftw_unwrapdir(state, state->file); } state->dir = NULL; state->de = NULL; @@ -1169,8 +1265,12 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { if (state->previous == file) { state->previous = parent; } - bftw_file_free(&state->cache, file); state->file = parent; + + if (file->fd >= 0) { + bftw_close(state, file); + } + bftw_file_free(&state->cache, file); } return ret; @@ -1185,8 +1285,11 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { static int bftw_state_destroy(struct bftw_state *state) { dstrfree(state->path); - if (state->ioq) { - ioq_cancel(state->ioq); + struct ioq *ioq = state->ioq; + if (ioq) { + ioq_cancel(ioq); + while (bftw_ioq_pop(state, true) >= 0); + state->ioq = NULL; } SLIST_EXTEND(&state->files, &state->batch); @@ -1194,7 +1297,7 @@ static int bftw_state_destroy(struct bftw_state *state) { bftw_gc(state, BFTW_VISIT_NONE); } while (bftw_pop_dir(state) || bftw_pop_file(state)); - ioq_destroy(state->ioq); + ioq_destroy(ioq); bftw_cache_destroy(&state->cache); diff --git a/src/dir.c b/src/dir.c index 685bac5..0304674 100644 --- a/src/dir.c +++ b/src/dir.c @@ -15,10 +15,6 @@ #include #include -#ifndef BFS_USE_GETDENTS -# define BFS_USE_GETDENTS (__linux__ || __FreeBSD__) -#endif - #if BFS_USE_GETDENTS # if __linux__ # include @@ -296,25 +292,15 @@ int bfs_closedir(struct bfs_dir *dir) { return ret; } -int bfs_fdclosedir(struct bfs_dir *dir, bool same_fd) { +#if BFS_USE_UNWRAPDIR +int bfs_unwrapdir(struct bfs_dir *dir) { #if BFS_USE_GETDENTS int ret = dir->fd; #elif __FreeBSD__ int ret = fdclosedir(dir->dir); -#else - if (same_fd) { - errno = ENOTSUP; - return -1; - } - - int ret = dup_cloexec(dirfd(dir->dir)); - if (ret < 0) { - return -1; - } - - bfs_closedir(dir); #endif sanitize_uninit(dir, DIR_SIZE); return ret; } +#endif diff --git a/src/dir.h b/src/dir.h index 16f592e..1137ff5 100644 --- a/src/dir.h +++ b/src/dir.h @@ -12,6 +12,14 @@ #include "config.h" #include +/** + * Whether the implementation uses the getdents() syscall directly, rather than + * libc's readdir(). + */ +#ifndef BFS_USE_GETDENTS +# define BFS_USE_GETDENTS (__linux__ || __FreeBSD__) +#endif + /** * A directory. */ @@ -129,18 +137,22 @@ int bfs_readdir(struct bfs_dir *dir, struct bfs_dirent *de); int bfs_closedir(struct bfs_dir *dir); /** - * Extract the file descriptor from an open directory. + * Whether the bfs_unwrapdir() function is supported. + */ +#ifndef BFS_USE_UNWRAPDIR +# define BFS_USE_UNWRAPDIR (BFS_USE_GETDENTS || __FreeBSD__) +#endif + +#if BFS_USE_UNWRAPDIR +/** + * Detach the file descriptor from an open directory. * * @param dir - * The directory to free. - * @param same_fd - * If true, require that the returned file descriptor is the same one - * that bfs_dirfd() would have returned. Otherwise, it may be a new - * file descriptor for the same directory. + * The directory to detach. * @return - * On success, a file descriptor for the directory is returned. On - * failure, -1 is returned, and the directory remains open. + * The file descriptor of the directory. */ -int bfs_fdclosedir(struct bfs_dir *dir, bool same_fd); +int bfs_unwrapdir(struct bfs_dir *dir); +#endif #endif // BFS_DIR_H -- cgit v1.2.3 From cf1eee4af7c9cd7e888d4e2ffa6339bce7efcb92 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 17 Jul 2023 17:15:47 -0400 Subject: bftw: Add bfs_dir allocation wrappers --- src/bftw.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index aec804b..553363e 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -116,6 +116,16 @@ static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) { bfs_dir_arena(&cache->dirs); } +/** Allocate a directory. */ +static struct bfs_dir *bftw_allocdir(struct bftw_cache *cache) { + return arena_alloc(&cache->dirs); +} + +/** Free a directory. */ +static void bftw_freedir(struct bftw_cache *cache, struct bfs_dir *dir) { + arena_free(&cache->dirs, dir); +} + /** Remove a bftw_file from the LRU list. */ static void bftw_lru_remove(struct bftw_cache *cache, struct bftw_file *file) { if (cache->target == file) { @@ -139,7 +149,7 @@ static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) { if (file->dir) { bfs_assert(file->fd == bfs_dirfd(file->dir)); bfs_closedir(file->dir); - arena_free(&cache->dirs, file->dir); + bftw_freedir(cache, file->dir); file->dir = NULL; } else { xclose(file->fd); @@ -392,13 +402,13 @@ static struct bfs_dir *bftw_file_opendir(struct bftw_cache *cache, struct bftw_f return NULL; } - struct bfs_dir *dir = arena_alloc(&cache->dirs); + struct bfs_dir *dir = bftw_allocdir(cache); if (!dir) { return NULL; } if (bfs_opendir(dir, fd, NULL) != 0) { - arena_free(&cache->dirs, dir); + bftw_freedir(cache, dir); return NULL; } @@ -895,7 +905,7 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { case IOQ_CLOSEDIR: ++cache->capacity; dir = ent->closedir.dir; - arena_free(&cache->dirs, dir); + bftw_freedir(cache, dir); break; case IOQ_OPENDIR: @@ -911,7 +921,7 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { if (ent->ret == 0) { bftw_file_set_dir(cache, file, dir); } else { - arena_free(&cache->dirs, dir); + bftw_freedir(cache, dir); } if (!(state->flags & BFTW_SORT)) { @@ -984,7 +994,7 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { goto unpin; } - struct bfs_dir *dir = arena_alloc(&cache->dirs); + struct bfs_dir *dir = bftw_allocdir(cache); if (!dir) { goto unpin; } @@ -1007,7 +1017,7 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { } free: - arena_free(&cache->dirs, dir); + bftw_freedir(cache, dir); unpin: if (file->parent) { bftw_cache_unpin(cache, file->parent); @@ -1026,7 +1036,7 @@ static int bftw_ioq_closedir(struct bftw_state *state, struct bfs_dir *dir) { struct bftw_cache *cache = &state->cache; int ret = bfs_closedir(dir); - arena_free(&cache->dirs, dir); + bftw_freedir(cache, dir); ++cache->capacity; return ret; } @@ -1081,7 +1091,7 @@ static int bftw_unwrapdir(struct bftw_state *state, struct bftw_file *file) { #if BFS_USE_UNWRAPDIR if (reffed || pinned) { bfs_unwrapdir(dir); - arena_free(&cache->dirs, dir); + bftw_freedir(cache, dir); file->dir = NULL; return 0; } -- cgit v1.2.3 From 3cc581c0295465fd5dc2fc818aa92f48dd87788e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 17 Jul 2023 19:03:30 -0400 Subject: bftw: Pass the whole bftw_state to bftw_openat() This required shuffling a lot of code around. Hopefully the new order makes more sense. --- src/bftw.c | 1263 +++++++++++++++++++++++++++++------------------------------- 1 file changed, 602 insertions(+), 661 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 553363e..2262591 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -34,6 +34,78 @@ #include #include +/** Caching bfs_stat(). */ +static const struct bfs_stat *bftw_stat_impl(struct BFTW *ftwbuf, struct bftw_stat *cache, enum bfs_stat_flags flags) { + if (!cache->buf) { + if (cache->error) { + errno = cache->error; + } else if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, flags, &cache->storage) == 0) { + cache->buf = &cache->storage; + } else { + cache->error = errno; + } + } + + return cache->buf; +} + +const struct bfs_stat *bftw_stat(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { + struct BFTW *mutbuf = (struct BFTW *)ftwbuf; + const struct bfs_stat *ret; + + if (flags & BFS_STAT_NOFOLLOW) { + ret = bftw_stat_impl(mutbuf, &mutbuf->lstat_cache, BFS_STAT_NOFOLLOW); + if (ret && !S_ISLNK(ret->mode) && !mutbuf->stat_cache.buf) { + // Non-link, so share stat info + mutbuf->stat_cache.buf = ret; + } + } else { + ret = bftw_stat_impl(mutbuf, &mutbuf->stat_cache, BFS_STAT_FOLLOW); + if (!ret && (flags & BFS_STAT_TRYFOLLOW) && is_nonexistence_error(errno)) { + ret = bftw_stat_impl(mutbuf, &mutbuf->lstat_cache, BFS_STAT_NOFOLLOW); + } + } + + return ret; +} + +const struct bfs_stat *bftw_cached_stat(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { + if (flags & BFS_STAT_NOFOLLOW) { + return ftwbuf->lstat_cache.buf; + } else if (ftwbuf->stat_cache.buf) { + return ftwbuf->stat_cache.buf; + } else if ((flags & BFS_STAT_TRYFOLLOW) && is_nonexistence_error(ftwbuf->stat_cache.error)) { + return ftwbuf->lstat_cache.buf; + } else { + return NULL; + } +} + +enum bfs_type bftw_type(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { + if (flags & BFS_STAT_NOFOLLOW) { + if (ftwbuf->type == BFS_LNK || (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) { + return ftwbuf->type; + } + } else if (flags & BFS_STAT_TRYFOLLOW) { + if (ftwbuf->type != BFS_LNK || (ftwbuf->stat_flags & BFS_STAT_TRYFOLLOW)) { + return ftwbuf->type; + } + } else { + if (ftwbuf->type != BFS_LNK) { + return ftwbuf->type; + } else if (ftwbuf->stat_flags & BFS_STAT_TRYFOLLOW) { + return BFS_ERROR; + } + } + + const struct bfs_stat *statbuf = bftw_stat(ftwbuf, flags); + if (statbuf) { + return bfs_mode_to_type(statbuf->mode); + } else { + return BFS_ERROR; + } +} + /** * A file. */ @@ -277,103 +349,7 @@ static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_fil return file; } -/** - * Open a bftw_file relative to another one. - * - * @param cache - * The cache to hold the file. - * @param file - * The file to open. - * @param base - * The base directory for the relative path (may be NULL). - * @param at_fd - * The base file descriptor, AT_FDCWD if base == NULL. - * @param at_path - * The relative path to the file. - * @return - * The opened file descriptor, or negative on error. - */ -static int bftw_file_openat(struct bftw_cache *cache, struct bftw_file *file, struct bftw_file *base, const char *at_path) { - bfs_assert(file->fd < 0); - - int at_fd = AT_FDCWD; - if (base) { - bftw_cache_pin(cache, base); - at_fd = base->fd; - } - - int flags = O_RDONLY | O_CLOEXEC | O_DIRECTORY; - int fd = openat(at_fd, at_path, flags); - - if (fd < 0 && errno == EMFILE) { - if (bftw_cache_pop(cache) == 0) { - fd = openat(at_fd, at_path, flags); - } - cache->capacity = 1; - } - - if (base) { - bftw_cache_unpin(cache, base); - } - - if (fd >= 0) { - file->fd = fd; - bftw_cache_add(cache, file); - } - - return file->fd; -} - -/** - * Open a bftw_file. - * - * @param cache - * The cache to hold the file. - * @param file - * The file to open. - * @param path - * The full path to the file. - * @return - * The opened file descriptor, or negative on error. - */ -static int bftw_file_open(struct bftw_cache *cache, struct bftw_file *file, const char *path) { - // Find the nearest open ancestor - struct bftw_file *base = file; - do { - base = base->parent; - } while (base && base->fd < 0); - - const char *at_path = path; - if (base) { - at_path += bftw_child_nameoff(base); - } - - int fd = bftw_file_openat(cache, file, base, at_path); - if (fd >= 0 || errno != ENAMETOOLONG) { - return fd; - } - - // Handle ENAMETOOLONG by manually traversing the path component-by-component - struct bftw_list parents; - SLIST_INIT(&parents); - - struct bftw_file *cur; - for (cur = file; cur != base; cur = cur->parent) { - SLIST_PREPEND(&parents, cur); - } - - while ((cur = SLIST_POP(&parents))) { - if (!cur->parent || cur->parent->fd >= 0) { - bftw_file_openat(cache, cur, cur->parent, cur->name); - } - } - - return file->fd; -} - -/** - * Associate an open directory with a bftw_file. - */ +/** Associate an open directory with a bftw_file. */ static void bftw_file_set_dir(struct bftw_cache *cache, struct bftw_file *file, struct bfs_dir *dir) { bfs_assert(!file->dir); file->dir = dir; @@ -384,38 +360,6 @@ static void bftw_file_set_dir(struct bftw_cache *cache, struct bftw_file *file, } } -/** - * Open a bftw_file as a directory. - * - * @param cache - * The cache to hold the file. - * @param file - * The directory to open. - * @param path - * The full path to the directory. - * @return - * The opened directory, or NULL on error. - */ -static struct bfs_dir *bftw_file_opendir(struct bftw_cache *cache, struct bftw_file *file, const char *path) { - int fd = bftw_file_open(cache, file, path); - if (fd < 0) { - return NULL; - } - - struct bfs_dir *dir = bftw_allocdir(cache); - if (!dir) { - return NULL; - } - - if (bfs_opendir(dir, fd, NULL) != 0) { - bftw_freedir(cache, dir); - return NULL; - } - - bftw_file_set_dir(cache, file, dir); - return dir; -} - /** Free a bftw_file. */ static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) { bfs_assert(file->refcount == 0); @@ -480,9 +424,7 @@ struct bftw_state { struct BFTW ftwbuf; }; -/** - * Initialize the bftw() state. - */ +/** Initialize the bftw() state. */ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *args) { state->callback = args->callback; state->ptr = args->ptr; @@ -542,676 +484,677 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg return 0; } -/** Cached bfs_stat(). */ -static const struct bfs_stat *bftw_stat_impl(struct BFTW *ftwbuf, struct bftw_stat *cache, enum bfs_stat_flags flags) { - if (!cache->buf) { - if (cache->error) { - errno = cache->error; - } else if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, flags, &cache->storage) == 0) { - cache->buf = &cache->storage; - } else { - cache->error = errno; - } +/** Pop a response from the I/O queue. */ +static int bftw_ioq_pop(struct bftw_state *state, bool block) { + struct ioq *ioq = state->ioq; + if (!ioq) { + return -1; } - return cache->buf; -} + struct ioq_ent *ent = block ? ioq_pop(ioq) : ioq_trypop(ioq); + if (!ent) { + return -1; + } -const struct bfs_stat *bftw_stat(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { - struct BFTW *mutbuf = (struct BFTW *)ftwbuf; - const struct bfs_stat *ret; + struct bftw_cache *cache = &state->cache; + struct bftw_file *file; + struct bfs_dir *dir; - if (flags & BFS_STAT_NOFOLLOW) { - ret = bftw_stat_impl(mutbuf, &mutbuf->lstat_cache, BFS_STAT_NOFOLLOW); - if (ret && !S_ISLNK(ret->mode) && !mutbuf->stat_cache.buf) { - // Non-link, so share stat info - mutbuf->stat_cache.buf = ret; - } - } else { - ret = bftw_stat_impl(mutbuf, &mutbuf->stat_cache, BFS_STAT_FOLLOW); - if (!ret && (flags & BFS_STAT_TRYFOLLOW) && is_nonexistence_error(errno)) { - ret = bftw_stat_impl(mutbuf, &mutbuf->lstat_cache, BFS_STAT_NOFOLLOW); - } - } + enum ioq_op op = ent->op; + switch (op) { + case IOQ_CLOSE: + ++cache->capacity; + break; - return ret; -} + case IOQ_CLOSEDIR: + ++cache->capacity; + dir = ent->closedir.dir; + bftw_freedir(cache, dir); + break; -const struct bfs_stat *bftw_cached_stat(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { - if (flags & BFS_STAT_NOFOLLOW) { - return ftwbuf->lstat_cache.buf; - } else if (ftwbuf->stat_cache.buf) { - return ftwbuf->stat_cache.buf; - } else if ((flags & BFS_STAT_TRYFOLLOW) && is_nonexistence_error(ftwbuf->stat_cache.error)) { - return ftwbuf->lstat_cache.buf; - } else { - return NULL; - } -} + case IOQ_OPENDIR: + file = ent->ptr; + file->ioqueued = false; -enum bfs_type bftw_type(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { - if (flags & BFS_STAT_NOFOLLOW) { - if (ftwbuf->type == BFS_LNK || (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) { - return ftwbuf->type; + ++cache->capacity; + if (file->parent) { + bftw_cache_unpin(cache, file->parent); } - } else if (flags & BFS_STAT_TRYFOLLOW) { - if (ftwbuf->type != BFS_LNK || (ftwbuf->stat_flags & BFS_STAT_TRYFOLLOW)) { - return ftwbuf->type; + + dir = ent->opendir.dir; + if (ent->ret == 0) { + bftw_file_set_dir(cache, file, dir); + } else { + bftw_freedir(cache, dir); } - } else { - if (ftwbuf->type != BFS_LNK) { - return ftwbuf->type; - } else if (ftwbuf->stat_flags & BFS_STAT_TRYFOLLOW) { - return BFS_ERROR; + + if (!(state->flags & BFTW_SORT)) { + SLIST_PREPEND(&state->dirs, file); } + break; } - const struct bfs_stat *statbuf = bftw_stat(ftwbuf, flags); - if (statbuf) { - return bfs_mode_to_type(statbuf->mode); - } else { - return BFS_ERROR; - } + ioq_free(ioq, ent); + return op; } -/** - * Build the path to the current file. - */ -static int bftw_build_path(struct bftw_state *state, const char *name) { - const struct bftw_file *file = state->file; - - size_t pathlen = file ? file->nameoff + file->namelen : 0; - if (dstresize(&state->path, pathlen) != 0) { - state->error = errno; +/** Try to reserve space in the I/O queue. */ +static int bftw_ioq_reserve(struct bftw_state *state) { + struct ioq *ioq = state->ioq; + if (!ioq) { return -1; } - // Try to find a common ancestor with the existing path - const struct bftw_file *ancestor = state->previous; - while (ancestor && ancestor->depth > file->depth) { - ancestor = ancestor->parent; + if (ioq_capacity(ioq) > 0) { + return 0; } - // Build the path backwards - while (file && file != ancestor) { - if (file->nameoff > 0) { - state->path[file->nameoff - 1] = '/'; - } - memcpy(state->path + file->nameoff, file->name, file->namelen); - - if (ancestor && ancestor->depth == file->depth) { - ancestor = ancestor->parent; - } - file = file->parent; + // With more than two threads, it is faster to wait for an I/O operation + // to complete than it is to do it ourselves + bool block = state->nthreads > 2; + if (bftw_ioq_pop(state, block) < 0) { + return -1; } - state->previous = state->file; + return 0; +} - if (name) { - if (pathlen > 0 && state->path[pathlen - 1] != '/') { - if (dstrapp(&state->path, '/') != 0) { - state->error = errno; - return -1; - } - } - if (dstrcat(&state->path, name) != 0) { - state->error = errno; - return -1; +/** Try to reserve space in the cache. */ +static int bftw_cache_reserve(struct bftw_state *state) { + struct bftw_cache *cache = &state->cache; + if (cache->capacity > 0) { + return 0; + } + + while (bftw_ioq_pop(state, true) >= 0) { + if (cache->capacity > 0) { + return 0; } } - return 0; + return bftw_cache_pop(cache); } -/** Check if a stat() call is needed for this visit. */ -static bool bftw_need_stat(const struct bftw_state *state) { - if (state->flags & BFTW_STAT) { - return true; +/** Open a bftw_file relative to another one. */ +static int bftw_file_openat(struct bftw_state *state, struct bftw_file *file, struct bftw_file *base, const char *at_path) { + bfs_assert(file->fd < 0); + + struct bftw_cache *cache = &state->cache; + + int at_fd = AT_FDCWD; + if (base) { + bftw_cache_pin(cache, base); + at_fd = base->fd; } - const struct BFTW *ftwbuf = &state->ftwbuf; - if (ftwbuf->type == BFS_UNKNOWN) { - return true; + int flags = O_RDONLY | O_CLOEXEC | O_DIRECTORY; + int fd = openat(at_fd, at_path, flags); + + if (fd < 0 && errno == EMFILE) { + if (bftw_cache_pop(cache) == 0) { + fd = openat(at_fd, at_path, flags); + } + cache->capacity = 1; } - if (ftwbuf->type == BFS_LNK && !(ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) { - return true; + if (base) { + bftw_cache_unpin(cache, base); } - if (ftwbuf->type == BFS_DIR) { - if (state->flags & (BFTW_DETECT_CYCLES | BFTW_SKIP_MOUNTS | BFTW_PRUNE_MOUNTS)) { - return true; - } -#if __linux__ - } else if (state->mtab) { - // Linux fills in d_type from the underlying inode, even when - // the directory entry is a bind mount point. In that case, we - // need to stat() to get the correct type. We don't need to - // check for directories because they can only be mounted over - // by other directories. - if (bfs_might_be_mount(state->mtab, ftwbuf->path)) { - return true; - } -#endif + if (fd >= 0) { + file->fd = fd; + bftw_cache_add(cache, file); } - return false; + return file->fd; } -/** Initialize bftw_stat cache. */ -static void bftw_stat_init(struct bftw_stat *cache) { - cache->buf = NULL; - cache->error = 0; -} +/** Open a bftw_file. */ +static int bftw_file_open(struct bftw_state *state, struct bftw_file *file, const char *path) { + // Find the nearest open ancestor + struct bftw_file *base = file; + do { + base = base->parent; + } while (base && base->fd < 0); -/** - * Open a file if necessary. - * - * @param file - * The file to open. - * @param path - * The path to that file or one of its descendants. - * @return - * The opened file descriptor, or -1 on error. - */ -static int bftw_ensure_open(struct bftw_cache *cache, struct bftw_file *file, const char *path) { - int ret = file->fd; + const char *at_path = path; + if (base) { + at_path += bftw_child_nameoff(base); + } - if (ret < 0) { - char *copy = strndup(path, file->nameoff + file->namelen); - if (!copy) { - return -1; - } + int fd = bftw_file_openat(state, file, base, at_path); + if (fd >= 0 || errno != ENAMETOOLONG) { + return fd; + } - ret = bftw_file_open(cache, file, copy); - free(copy); + // Handle ENAMETOOLONG by manually traversing the path component-by-component + struct bftw_list parents; + SLIST_INIT(&parents); + + struct bftw_file *cur; + for (cur = file; cur != base; cur = cur->parent) { + SLIST_PREPEND(&parents, cur); } - return ret; + while ((cur = SLIST_POP(&parents))) { + if (!cur->parent || cur->parent->fd >= 0) { + bftw_file_openat(state, cur, cur->parent, cur->name); + } + } + + return file->fd; } -/** - * Initialize the buffers with data about the current path. - */ -static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) { - struct bftw_file *file = state->file; - const struct bfs_dirent *de = state->de; +/** Push a directory onto the queue. */ +static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { + bfs_assert(file->type == BFS_DIR); - struct BFTW *ftwbuf = &state->ftwbuf; - ftwbuf->path = state->path; - ftwbuf->root = file ? file->root->name : ftwbuf->path; - ftwbuf->depth = 0; - ftwbuf->visit = visit; - ftwbuf->type = BFS_UNKNOWN; - ftwbuf->error = state->direrror; - ftwbuf->at_fd = AT_FDCWD; - ftwbuf->at_path = ftwbuf->path; - ftwbuf->stat_flags = BFS_STAT_NOFOLLOW; - bftw_stat_init(&ftwbuf->lstat_cache); - bftw_stat_init(&ftwbuf->stat_cache); + struct bftw_cache *cache = &state->cache; - struct bftw_file *parent = NULL; - if (de) { - parent = file; - ftwbuf->depth = file->depth + 1; - ftwbuf->type = de->type; - ftwbuf->nameoff = bftw_child_nameoff(file); - } else if (file) { - parent = file->parent; - ftwbuf->depth = file->depth; - ftwbuf->type = file->type; - ftwbuf->nameoff = file->nameoff; + if (bftw_ioq_reserve(state) != 0) { + goto append; } - if (parent) { - // Try to ensure the immediate parent is open, to avoid ENAMETOOLONG - if (bftw_ensure_open(&state->cache, parent, state->path) >= 0) { - ftwbuf->at_fd = parent->fd; - ftwbuf->at_path += ftwbuf->nameoff; - } else { - ftwbuf->error = errno; + int dfd = AT_FDCWD; + if (file->parent) { + dfd = file->parent->fd; + if (dfd < 0) { + goto append; } + bftw_cache_pin(cache, file->parent); } - if (ftwbuf->depth == 0) { - // Compute the name offset for root paths like "foo/bar" - ftwbuf->nameoff = xbaseoff(ftwbuf->path); + if (bftw_cache_reserve(state) != 0) { + goto unpin; } - if (ftwbuf->error != 0) { - ftwbuf->type = BFS_ERROR; - return; + struct bfs_dir *dir = bftw_allocdir(cache); + if (!dir) { + goto unpin; } - int follow_flags = BFTW_FOLLOW_ALL; - if (ftwbuf->depth == 0) { - follow_flags |= BFTW_FOLLOW_ROOTS; - } - bool follow = state->flags & follow_flags; - if (follow) { - ftwbuf->stat_flags = BFS_STAT_TRYFOLLOW; + if (ioq_opendir(state->ioq, dir, dfd, file->name, file) != 0) { + goto free; } - const struct bfs_stat *statbuf = NULL; - if (bftw_need_stat(state)) { - statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); - if (statbuf) { - ftwbuf->type = bfs_mode_to_type(statbuf->mode); - } else { - ftwbuf->type = BFS_ERROR; - ftwbuf->error = errno; - return; - } + file->ioqueued = true; + --cache->capacity; + + if (state->flags & BFTW_SORT) { + // When sorting, dirs are always kept in order in state->dirs, + // and we wait for the ioq to open them before popping + goto append; + } else { + // When not sorting, dirs are not added to state->dirs until + // popped from the ioq + return; } - if (ftwbuf->type == BFS_DIR && (state->flags & BFTW_DETECT_CYCLES)) { - for (const struct bftw_file *ancestor = parent; ancestor; ancestor = ancestor->parent) { - if (ancestor->dev == statbuf->dev && ancestor->ino == statbuf->ino) { - ftwbuf->type = BFS_ERROR; - ftwbuf->error = ELOOP; - return; - } - } +free: + bftw_freedir(cache, dir); +unpin: + if (file->parent) { + bftw_cache_unpin(cache, file->parent); } +append: + SLIST_APPEND(&state->dirs, file); } -/** Check if the current file is a mount point. */ -static bool bftw_is_mount(struct bftw_state *state, const char *name) { - const struct bftw_file *file = state->file; - if (!file) { - return false; +/** Close a directory, asynchronously if possible. */ +static int bftw_ioq_closedir(struct bftw_state *state, struct bfs_dir *dir) { + if (bftw_ioq_reserve(state) == 0) { + if (ioq_closedir(state->ioq, dir, NULL) == 0) { + return 0; + } } - const struct bftw_file *parent = name ? file : file->parent; - if (!parent) { - return false; + struct bftw_cache *cache = &state->cache; + int ret = bfs_closedir(dir); + bftw_freedir(cache, dir); + ++cache->capacity; + return ret; +} + +/** Close a file descriptor, asynchronously if possible. */ +static int bftw_ioq_close(struct bftw_state *state, int fd) { + if (bftw_ioq_reserve(state) == 0) { + if (ioq_close(state->ioq, fd, NULL) == 0) { + return 0; + } } - const struct BFTW *ftwbuf = &state->ftwbuf; - const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); - return statbuf && statbuf->dev != parent->dev; + struct bftw_cache *cache = &state->cache; + int ret = xclose(fd); + ++cache->capacity; + return ret; } -/** - * Invoke the callback. - */ -static enum bftw_action bftw_call_back(struct bftw_state *state, const char *name, enum bftw_visit visit) { - if (visit == BFTW_POST && !(state->flags & BFTW_POST_ORDER)) { - return BFTW_PRUNE; - } +/** Close a file, asynchronously if possible. */ +static int bftw_close(struct bftw_state *state, struct bftw_file *file) { + bfs_assert(file->fd >= 0); + bfs_assert(file->pincount == 0); - if (bftw_build_path(state, name) != 0) { - return BFTW_STOP; - } + struct bfs_dir *dir = file->dir; + int fd = file->fd; - const struct BFTW *ftwbuf = &state->ftwbuf; - bftw_init_ftwbuf(state, visit); + bftw_lru_remove(&state->cache, file); + file->dir = NULL; + file->fd = -1; - // Never give the callback BFS_ERROR unless BFTW_RECOVER is specified - if (ftwbuf->type == BFS_ERROR && !(state->flags & BFTW_RECOVER)) { - state->error = ftwbuf->error; - return BFTW_STOP; + if (dir) { + return bftw_ioq_closedir(state, dir); + } else { + return bftw_ioq_close(state, fd); } +} - if ((state->flags & BFTW_SKIP_MOUNTS) && bftw_is_mount(state, name)) { - return BFTW_PRUNE; +/** Free an open directory. */ +static int bftw_unwrapdir(struct bftw_state *state, struct bftw_file *file) { + struct bfs_dir *dir = file->dir; + if (!dir) { + return 0; } - enum bftw_action ret = state->callback(ftwbuf, state->ptr); - switch (ret) { - case BFTW_CONTINUE: - if (visit != BFTW_PRE) { - return BFTW_PRUNE; - } - if (ftwbuf->type != BFS_DIR) { - return BFTW_PRUNE; - } - if ((state->flags & BFTW_PRUNE_MOUNTS) && bftw_is_mount(state, name)) { - return BFTW_PRUNE; - } - fallthru; - case BFTW_PRUNE: - case BFTW_STOP: - return ret; + struct bftw_cache *cache = &state->cache; - default: - state->error = EINVAL; - return BFTW_STOP; + // Try to keep an open fd if any children exist + bool reffed = file->refcount > 1; + // Keep the fd the same if it's pinned + bool pinned = file->pincount > 0; + +#if BFS_USE_UNWRAPDIR + if (reffed || pinned) { + bfs_unwrapdir(dir); + bftw_freedir(cache, dir); + file->dir = NULL; + return 0; } -} +#else + if (pinned) { + return -1; + } +#endif -/** Pop a response from the I/O queue. */ -static int bftw_ioq_pop(struct bftw_state *state, bool block) { - struct ioq *ioq = state->ioq; - if (!ioq) { + if (!reffed) { + return bftw_close(state, file); + } + + if (bftw_cache_reserve(state) != 0) { return -1; } - struct ioq_ent *ent = block ? ioq_pop(ioq) : ioq_trypop(ioq); - if (!ent) { + int fd = dup_cloexec(file->fd); + if (fd < 0) { return -1; } + --cache->capacity; - struct bftw_cache *cache = &state->cache; - struct bftw_file *file; - struct bfs_dir *dir; + file->dir = NULL; + file->fd = fd; + return bftw_ioq_closedir(state, dir); +} - enum ioq_op op = ent->op; - switch (op) { - case IOQ_CLOSE: - ++cache->capacity; - break; +/** Pop a directory to read from the queue. */ +static bool bftw_pop_dir(struct bftw_state *state) { + bfs_assert(!state->file); - case IOQ_CLOSEDIR: - ++cache->capacity; - dir = ent->closedir.dir; - bftw_freedir(cache, dir); - break; + struct bftw_file *dir = state->dirs.head; + bool have_files = state->files.head; - case IOQ_OPENDIR: - file = ent->ptr; - file->ioqueued = false; + if (state->flags & BFTW_SORT) { + // Keep strict breadth-first order when sorting + if (state->strategy != BFTW_DFS && have_files) { + return false; + } + } else { + while (!dir || !dir->dir) { + // Block if we have no other files/dirs to visit, or no room in the cache + bool have_room = state->cache.capacity > 0; + bool block = !(dir || have_files) || !have_room; - ++cache->capacity; - if (file->parent) { - bftw_cache_unpin(cache, file->parent); + if (bftw_ioq_pop(state, block) < 0) { + break; + } + dir = state->dirs.head; } + } - dir = ent->opendir.dir; - if (ent->ret == 0) { - bftw_file_set_dir(cache, file, dir); - } else { - bftw_freedir(cache, dir); - } + if (!dir) { + return false; + } - if (!(state->flags & BFTW_SORT)) { - SLIST_PREPEND(&state->dirs, file); - } - break; + SLIST_POP(&state->dirs); + state->file = dir; + + while (dir->ioqueued) { + bftw_ioq_pop(state, true); } - ioq_free(ioq, ent); - return op; + return true; } -/** Try to reserve space in the I/O queue. */ -static int bftw_ioq_reserve(struct bftw_state *state) { - struct ioq *ioq = state->ioq; - if (!ioq) { +/** Pop a file to visit from the queue. */ +static bool bftw_pop_file(struct bftw_state *state) { + bfs_assert(!state->file); + return (state->file = SLIST_POP(&state->files)); +} + +/** Build the path to the current file. */ +static int bftw_build_path(struct bftw_state *state, const char *name) { + const struct bftw_file *file = state->file; + + size_t pathlen = file ? file->nameoff + file->namelen : 0; + if (dstresize(&state->path, pathlen) != 0) { + state->error = errno; return -1; } - if (ioq_capacity(ioq) > 0) { - return 0; + // Try to find a common ancestor with the existing path + const struct bftw_file *ancestor = state->previous; + while (ancestor && ancestor->depth > file->depth) { + ancestor = ancestor->parent; } - // With more than two threads, it is faster to wait for an I/O operation - // to complete than it is to do it ourselves - bool block = state->nthreads > 2; - if (bftw_ioq_pop(state, block) < 0) { - return -1; + // Build the path backwards + while (file && file != ancestor) { + if (file->nameoff > 0) { + state->path[file->nameoff - 1] = '/'; + } + memcpy(state->path + file->nameoff, file->name, file->namelen); + + if (ancestor && ancestor->depth == file->depth) { + ancestor = ancestor->parent; + } + file = file->parent; + } + + state->previous = state->file; + + if (name) { + if (pathlen > 0 && state->path[pathlen - 1] != '/') { + if (dstrapp(&state->path, '/') != 0) { + state->error = errno; + return -1; + } + } + if (dstrcat(&state->path, name) != 0) { + state->error = errno; + return -1; + } } return 0; } -/** Try to reserve space in the cache. */ -static int bftw_cache_reserve(struct bftw_state *state) { +/** Open a bftw_file as a directory. */ +static struct bfs_dir *bftw_file_opendir(struct bftw_state *state, struct bftw_file *file, const char *path) { + int fd = bftw_file_open(state, file, path); + if (fd < 0) { + return NULL; + } + struct bftw_cache *cache = &state->cache; - if (cache->capacity > 0) { - return 0; + struct bfs_dir *dir = bftw_allocdir(cache); + if (!dir) { + return NULL; } - while (bftw_ioq_pop(state, false) >= 0) { - if (cache->capacity > 0) { - return 0; - } + if (bfs_opendir(dir, fd, NULL) != 0) { + bftw_freedir(cache, dir); + return NULL; } - return bftw_cache_pop(cache); + bftw_file_set_dir(cache, file, dir); + return dir; } -/** Push a directory onto the queue. */ -static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { - bfs_assert(file->type == BFS_DIR); - - struct bftw_cache *cache = &state->cache; +/** Open the current directory. */ +static int bftw_opendir(struct bftw_state *state) { + bfs_assert(!state->dir); + bfs_assert(!state->de); - if (bftw_ioq_reserve(state) != 0) { - goto append; - } + state->direrror = 0; - int dfd = AT_FDCWD; - if (file->parent) { - dfd = file->parent->fd; - if (dfd < 0) { - goto append; + struct bftw_file *file = state->file; + if (file->dir) { + state->dir = file->dir; + } else { + if (bftw_build_path(state, NULL) != 0) { + return -1; } - bftw_cache_pin(cache, file->parent); + state->dir = bftw_file_opendir(state, file, state->path); } - if (bftw_cache_reserve(state) != 0) { - goto unpin; + if (state->dir) { + bftw_cache_pin(&state->cache, file); + } else { + state->direrror = errno; } - struct bfs_dir *dir = bftw_allocdir(cache); - if (!dir) { - goto unpin; - } + return 0; +} - if (ioq_opendir(state->ioq, dir, dfd, file->name, file) != 0) { - goto free; +/** Read an entry from the current directory. */ +static int bftw_readdir(struct bftw_state *state) { + if (!state->dir) { + return -1; } - file->ioqueued = true; - --cache->capacity; - - if (state->flags & BFTW_SORT) { - // When sorting, dirs are always kept in order in state->dirs, - // and we wait for the ioq to open them before popping - goto append; + int ret = bfs_readdir(state->dir, &state->de_storage); + if (ret > 0) { + state->de = &state->de_storage; + } else if (ret == 0) { + state->de = NULL; } else { - // When not sorting, dirs are not added to state->dirs until - // popped from the ioq - return; + state->de = NULL; + state->direrror = errno; } -free: - bftw_freedir(cache, dir); -unpin: - if (file->parent) { - bftw_cache_unpin(cache, file->parent); - } -append: - SLIST_APPEND(&state->dirs, file); + return ret; } -/** Close a directory, asynchronously if possible. */ -static int bftw_ioq_closedir(struct bftw_state *state, struct bfs_dir *dir) { - if (bftw_ioq_reserve(state) == 0) { - if (ioq_closedir(state->ioq, dir, NULL) == 0) { - return 0; - } +/** Check if a stat() call is needed for this visit. */ +static bool bftw_need_stat(const struct bftw_state *state) { + if (state->flags & BFTW_STAT) { + return true; } - struct bftw_cache *cache = &state->cache; - int ret = bfs_closedir(dir); - bftw_freedir(cache, dir); - ++cache->capacity; - return ret; -} + const struct BFTW *ftwbuf = &state->ftwbuf; + if (ftwbuf->type == BFS_UNKNOWN) { + return true; + } -/** Close a file descriptor, asynchronously if possible. */ -static int bftw_ioq_close(struct bftw_state *state, int fd) { - if (bftw_ioq_reserve(state) == 0) { - if (ioq_close(state->ioq, fd, NULL) == 0) { - return 0; + if (ftwbuf->type == BFS_LNK && !(ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) { + return true; + } + + if (ftwbuf->type == BFS_DIR) { + if (state->flags & (BFTW_DETECT_CYCLES | BFTW_SKIP_MOUNTS | BFTW_PRUNE_MOUNTS)) { + return true; + } +#if __linux__ + } else if (state->mtab) { + // Linux fills in d_type from the underlying inode, even when + // the directory entry is a bind mount point. In that case, we + // need to stat() to get the correct type. We don't need to + // check for directories because they can only be mounted over + // by other directories. + if (bfs_might_be_mount(state->mtab, ftwbuf->path)) { + return true; } +#endif } - struct bftw_cache *cache = &state->cache; - int ret = xclose(fd); - ++cache->capacity; - return ret; + return false; } -/** Close a file, asynchronously if possible. */ -static int bftw_close(struct bftw_state *state, struct bftw_file *file) { - bfs_assert(file->fd >= 0); - bfs_assert(file->pincount == 0); +/** Initialize bftw_stat cache. */ +static void bftw_stat_init(struct bftw_stat *cache) { + cache->buf = NULL; + cache->error = 0; +} - struct bfs_dir *dir = file->dir; - int fd = file->fd; +/** Open a file if necessary. */ +static int bftw_ensure_open(struct bftw_state *state, struct bftw_file *file, const char *path) { + int ret = file->fd; - bftw_lru_remove(&state->cache, file); - file->dir = NULL; - file->fd = -1; + if (ret < 0) { + char *copy = strndup(path, file->nameoff + file->namelen); + if (!copy) { + return -1; + } - if (dir) { - return bftw_ioq_closedir(state, dir); - } else { - return bftw_ioq_close(state, fd); + ret = bftw_file_open(state, file, copy); + free(copy); } + + return ret; } -/** Free an open directory. */ -static int bftw_unwrapdir(struct bftw_state *state, struct bftw_file *file) { - struct bfs_dir *dir = file->dir; - if (!dir) { - return 0; +/** Initialize the buffers with data about the current path. */ +static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) { + struct bftw_file *file = state->file; + const struct bfs_dirent *de = state->de; + + struct BFTW *ftwbuf = &state->ftwbuf; + ftwbuf->path = state->path; + ftwbuf->root = file ? file->root->name : ftwbuf->path; + ftwbuf->depth = 0; + ftwbuf->visit = visit; + ftwbuf->type = BFS_UNKNOWN; + ftwbuf->error = state->direrror; + ftwbuf->at_fd = AT_FDCWD; + ftwbuf->at_path = ftwbuf->path; + ftwbuf->stat_flags = BFS_STAT_NOFOLLOW; + bftw_stat_init(&ftwbuf->lstat_cache); + bftw_stat_init(&ftwbuf->stat_cache); + + struct bftw_file *parent = NULL; + if (de) { + parent = file; + ftwbuf->depth = file->depth + 1; + ftwbuf->type = de->type; + ftwbuf->nameoff = bftw_child_nameoff(file); + } else if (file) { + parent = file->parent; + ftwbuf->depth = file->depth; + ftwbuf->type = file->type; + ftwbuf->nameoff = file->nameoff; } - struct bftw_cache *cache = &state->cache; - - // Try to keep an open fd if any children exist - bool reffed = file->refcount > 1; - // Keep the fd the same if it's pinned - bool pinned = file->pincount > 0; - -#if BFS_USE_UNWRAPDIR - if (reffed || pinned) { - bfs_unwrapdir(dir); - bftw_freedir(cache, dir); - file->dir = NULL; - return 0; - } -#else - if (pinned) { - return -1; + if (parent) { + // Try to ensure the immediate parent is open, to avoid ENAMETOOLONG + if (bftw_ensure_open(state, parent, state->path) >= 0) { + ftwbuf->at_fd = parent->fd; + ftwbuf->at_path += ftwbuf->nameoff; + } else { + ftwbuf->error = errno; + } } -#endif - if (!reffed) { - return bftw_close(state, file); + if (ftwbuf->depth == 0) { + // Compute the name offset for root paths like "foo/bar" + ftwbuf->nameoff = xbaseoff(ftwbuf->path); } - if (bftw_cache_reserve(state) != 0) { - return -1; + if (ftwbuf->error != 0) { + ftwbuf->type = BFS_ERROR; + return; } - int fd = dup_cloexec(file->fd); - if (fd < 0) { - return -1; + int follow_flags = BFTW_FOLLOW_ALL; + if (ftwbuf->depth == 0) { + follow_flags |= BFTW_FOLLOW_ROOTS; + } + bool follow = state->flags & follow_flags; + if (follow) { + ftwbuf->stat_flags = BFS_STAT_TRYFOLLOW; } - --cache->capacity; - - file->dir = NULL; - file->fd = fd; - return bftw_ioq_closedir(state, dir); -} - -/** Pop a directory to read from the queue. */ -static bool bftw_pop_dir(struct bftw_state *state) { - bfs_assert(!state->file); - - struct bftw_file *dir = state->dirs.head; - bool have_files = state->files.head; - if (state->flags & BFTW_SORT) { - // Keep strict breadth-first order when sorting - if (state->strategy != BFTW_DFS && have_files) { - return false; + const struct bfs_stat *statbuf = NULL; + if (bftw_need_stat(state)) { + statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); + if (statbuf) { + ftwbuf->type = bfs_mode_to_type(statbuf->mode); + } else { + ftwbuf->type = BFS_ERROR; + ftwbuf->error = errno; + return; } - } else { - while (!dir || !dir->dir) { - // Block if we have no other files/dirs to visit, or no room in the cache - bool have_room = state->cache.capacity > 0; - bool block = !(dir || have_files) || !have_room; + } - if (bftw_ioq_pop(state, block) < 0) { - break; + if (ftwbuf->type == BFS_DIR && (state->flags & BFTW_DETECT_CYCLES)) { + for (const struct bftw_file *ancestor = parent; ancestor; ancestor = ancestor->parent) { + if (ancestor->dev == statbuf->dev && ancestor->ino == statbuf->ino) { + ftwbuf->type = BFS_ERROR; + ftwbuf->error = ELOOP; + return; } - dir = state->dirs.head; } } +} - if (!dir) { +/** Check if the current file is a mount point. */ +static bool bftw_is_mount(struct bftw_state *state, const char *name) { + const struct bftw_file *file = state->file; + if (!file) { return false; } - SLIST_POP(&state->dirs); - state->file = dir; - - while (dir->ioqueued) { - bftw_ioq_pop(state, true); + const struct bftw_file *parent = name ? file : file->parent; + if (!parent) { + return false; } - return true; -} - -/** Pop a file to visit from the queue. */ -static bool bftw_pop_file(struct bftw_state *state) { - bfs_assert(!state->file); - return (state->file = SLIST_POP(&state->files)); + const struct BFTW *ftwbuf = &state->ftwbuf; + const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); + return statbuf && statbuf->dev != parent->dev; } -/** - * Open the current directory. - */ -static int bftw_opendir(struct bftw_state *state) { - bfs_assert(!state->dir); - bfs_assert(!state->de); - - state->direrror = 0; - - struct bftw_file *file = state->file; - if (file->dir) { - state->dir = file->dir; - } else { - if (bftw_build_path(state, NULL) != 0) { - return -1; - } - state->dir = bftw_file_opendir(&state->cache, file, state->path); +/** Invoke the callback. */ +static enum bftw_action bftw_call_back(struct bftw_state *state, const char *name, enum bftw_visit visit) { + if (visit == BFTW_POST && !(state->flags & BFTW_POST_ORDER)) { + return BFTW_PRUNE; } - if (state->dir) { - bftw_cache_pin(&state->cache, file); - } else { - state->direrror = errno; + if (bftw_build_path(state, name) != 0) { + return BFTW_STOP; } - return 0; -} + const struct BFTW *ftwbuf = &state->ftwbuf; + bftw_init_ftwbuf(state, visit); -/** - * Read an entry from the current directory. - */ -static int bftw_readdir(struct bftw_state *state) { - if (!state->dir) { - return -1; + // Never give the callback BFS_ERROR unless BFTW_RECOVER is specified + if (ftwbuf->type == BFS_ERROR && !(state->flags & BFTW_RECOVER)) { + state->error = ftwbuf->error; + return BFTW_STOP; } - int ret = bfs_readdir(state->dir, &state->de_storage); - if (ret > 0) { - state->de = &state->de_storage; - } else if (ret == 0) { - state->de = NULL; - } else { - state->de = NULL; - state->direrror = errno; + if ((state->flags & BFTW_SKIP_MOUNTS) && bftw_is_mount(state, name)) { + return BFTW_PRUNE; } - return ret; + enum bftw_action ret = state->callback(ftwbuf, state->ptr); + switch (ret) { + case BFTW_CONTINUE: + if (visit != BFTW_PRE) { + return BFTW_PRUNE; + } + if (ftwbuf->type != BFS_DIR) { + return BFTW_PRUNE; + } + if ((state->flags & BFTW_PRUNE_MOUNTS) && bftw_is_mount(state, name)) { + return BFTW_PRUNE; + } + fallthru; + case BFTW_PRUNE: + case BFTW_STOP: + return ret; + + default: + state->error = EINVAL; + return BFTW_STOP; + } } /** @@ -1230,9 +1173,7 @@ enum bftw_gc_flags { BFTW_VISIT_ALL = BFTW_VISIT_ERROR | BFTW_VISIT_FILE | BFTW_VISIT_PARENTS, }; -/** - * Garbage collect the current file and its parents. - */ +/** Garbage collect the current file and its parents. */ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { int ret = 0; @@ -1286,35 +1227,6 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { return ret; } -/** - * Dispose of the bftw() state. - * - * @return - * The bftw() return value. - */ -static int bftw_state_destroy(struct bftw_state *state) { - dstrfree(state->path); - - struct ioq *ioq = state->ioq; - if (ioq) { - ioq_cancel(ioq); - while (bftw_ioq_pop(state, true) >= 0); - state->ioq = NULL; - } - - SLIST_EXTEND(&state->files, &state->batch); - do { - bftw_gc(state, BFTW_VISIT_NONE); - } while (bftw_pop_dir(state) || bftw_pop_file(state)); - - ioq_destroy(ioq); - - bftw_cache_destroy(&state->cache); - - errno = state->error; - return state->error ? -1 : 0; -} - /** Sort a bftw_list by filename. */ static void bftw_list_sort(struct bftw_list *list) { if (!list->head || !list->head->next) { @@ -1436,6 +1348,35 @@ static int bftw_visit(struct bftw_state *state, const char *name) { } } +/** + * Dispose of the bftw() state. + * + * @return + * The bftw() return value. + */ +static int bftw_state_destroy(struct bftw_state *state) { + dstrfree(state->path); + + struct ioq *ioq = state->ioq; + if (ioq) { + ioq_cancel(ioq); + while (bftw_ioq_pop(state, true) >= 0); + state->ioq = NULL; + } + + SLIST_EXTEND(&state->files, &state->batch); + do { + bftw_gc(state, BFTW_VISIT_NONE); + } while (bftw_pop_dir(state) || bftw_pop_file(state)); + + ioq_destroy(ioq); + + bftw_cache_destroy(&state->cache); + + errno = state->error; + return state->error ? -1 : 0; +} + /** * bftw() implementation for simple breadth-/depth-first search. */ -- cgit v1.2.3 From 196c36bb13de94c977a3e360dc5fa529efe2c5ca Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 17 Jul 2023 19:05:23 -0400 Subject: bftw: Reserve space in the cache before opening files This fixes a storm of EMFILE retries observed with -j1 on a very large directory tree. Fixes: 7888fbababd22190e9f919fc272957426a27969e --- src/bftw.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 2262591..9d85966 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -572,7 +572,13 @@ static int bftw_cache_reserve(struct bftw_state *state) { } } - return bftw_cache_pop(cache); + if (bftw_cache_pop(cache) != 0) { + errno = EMFILE; + return -1; + } + + bfs_assert(cache->capacity > 0); + return 0; } /** Open a bftw_file relative to another one. */ @@ -587,8 +593,13 @@ static int bftw_file_openat(struct bftw_state *state, struct bftw_file *file, st at_fd = base->fd; } + int fd = -1; + if (bftw_cache_reserve(state) != 0) { + goto unpin; + } + int flags = O_RDONLY | O_CLOEXEC | O_DIRECTORY; - int fd = openat(at_fd, at_path, flags); + fd = openat(at_fd, at_path, flags); if (fd < 0 && errno == EMFILE) { if (bftw_cache_pop(cache) == 0) { @@ -597,6 +608,7 @@ static int bftw_file_openat(struct bftw_state *state, struct bftw_file *file, st cache->capacity = 1; } +unpin: if (base) { bftw_cache_unpin(cache, base); } @@ -606,7 +618,7 @@ static int bftw_file_openat(struct bftw_state *state, struct bftw_file *file, st bftw_cache_add(cache, file); } - return file->fd; + return fd; } /** Open a bftw_file. */ -- cgit v1.2.3 From d24941bb85f8ccc41a9894871e7cca7a02de066c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 17 Jul 2023 19:58:39 -0400 Subject: bftw: Check that file->fd == bfs_dirfd(file->dir) earlier This has the potential to fail on at least one known platform: macports with the legacysupport implementation of fdopendir(). Link: https://github.com/macports/macports-ports/pull/19047#issuecomment-1636059809 --- src/bftw.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 9d85966..f8d0ac5 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -219,7 +219,6 @@ static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) { bfs_assert(file->pincount == 0); if (file->dir) { - bfs_assert(file->fd == bfs_dirfd(file->dir)); bfs_closedir(file->dir); bftw_freedir(cache, file->dir); file->dir = NULL; @@ -354,7 +353,9 @@ static void bftw_file_set_dir(struct bftw_cache *cache, struct bftw_file *file, bfs_assert(!file->dir); file->dir = dir; - if (file->fd < 0) { + if (file->fd >= 0) { + bfs_assert(file->fd == bfs_dirfd(dir)); + } else { file->fd = bfs_dirfd(dir); bftw_cache_add(cache, file); } -- cgit v1.2.3 From d3f4fd1f5fc7e9502d5fb0087d9ef6c1566655c2 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 17 Jul 2023 18:40:32 -0400 Subject: bftw: Use separate queues for open and closed directories --- src/bftw.c | 204 ++++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 115 insertions(+), 89 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index f8d0ac5..a412c26 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -114,10 +114,15 @@ struct bftw_file { struct bftw_file *parent; /** The root under which this file was found. */ struct bftw_file *root; - /** The next file in the queue, if any. */ - struct bftw_file *next; - /** LRU list links. */ + /** The next directory to open. */ + struct { struct bftw_file *next; } to_open; + /** The next directory to read. */ + struct { struct bftw_file *next; } to_read; + /** The next file to visit. */ + struct { struct bftw_file *next; } to_visit; + + /** LRU list node. */ struct { struct bftw_file *prev; struct bftw_file *next; @@ -329,7 +334,9 @@ static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_fil file->nameoff = 0; } - file->next = NULL; + file->to_open.next = NULL; + file->to_read.next = NULL; + file->to_visit.next = NULL; file->lru.prev = file->lru.next = NULL; file->refcount = 1; @@ -398,10 +405,13 @@ struct bftw_state { /** The number of I/O threads. */ size_t nthreads; + /** The queue of directories to open. */ + struct bftw_list to_open; /** The queue of directories to read. */ - struct bftw_list dirs; + struct bftw_list to_read; + /** The queue of files to visit. */ - struct bftw_list files; + struct bftw_list to_visit; /** A batch of files to enqueue. */ struct bftw_list batch; @@ -471,8 +481,10 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg } state->nthreads = nthreads; - SLIST_INIT(&state->dirs); - SLIST_INIT(&state->files); + SLIST_INIT(&state->to_open); + SLIST_INIT(&state->to_read); + + SLIST_INIT(&state->to_visit); SLIST_INIT(&state->batch); state->file = NULL; @@ -530,7 +542,7 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { } if (!(state->flags & BFTW_SORT)) { - SLIST_PREPEND(&state->dirs, file); + SLIST_PREPEND(&state->to_read, file, to_read); } break; } @@ -646,10 +658,10 @@ static int bftw_file_open(struct bftw_state *state, struct bftw_file *file, cons struct bftw_file *cur; for (cur = file; cur != base; cur = cur->parent) { - SLIST_PREPEND(&parents, cur); + SLIST_PREPEND(&parents, cur, to_open); } - while ((cur = SLIST_POP(&parents))) { + while ((cur = SLIST_POP(&parents, to_open))) { if (!cur->parent || cur->parent->fd >= 0) { bftw_file_openat(state, cur, cur->parent, cur->name); } @@ -658,61 +670,6 @@ static int bftw_file_open(struct bftw_state *state, struct bftw_file *file, cons return file->fd; } -/** Push a directory onto the queue. */ -static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { - bfs_assert(file->type == BFS_DIR); - - struct bftw_cache *cache = &state->cache; - - if (bftw_ioq_reserve(state) != 0) { - goto append; - } - - int dfd = AT_FDCWD; - if (file->parent) { - dfd = file->parent->fd; - if (dfd < 0) { - goto append; - } - bftw_cache_pin(cache, file->parent); - } - - if (bftw_cache_reserve(state) != 0) { - goto unpin; - } - - struct bfs_dir *dir = bftw_allocdir(cache); - if (!dir) { - goto unpin; - } - - if (ioq_opendir(state->ioq, dir, dfd, file->name, file) != 0) { - goto free; - } - - file->ioqueued = true; - --cache->capacity; - - if (state->flags & BFTW_SORT) { - // When sorting, dirs are always kept in order in state->dirs, - // and we wait for the ioq to open them before popping - goto append; - } else { - // When not sorting, dirs are not added to state->dirs until - // popped from the ioq - return; - } - -free: - bftw_freedir(cache, dir); -unpin: - if (file->parent) { - bftw_cache_unpin(cache, file->parent); - } -append: - SLIST_APPEND(&state->dirs, file); -} - /** Close a directory, asynchronously if possible. */ static int bftw_ioq_closedir(struct bftw_state *state, struct bfs_dir *dir) { if (bftw_ioq_reserve(state) == 0) { @@ -807,12 +764,76 @@ static int bftw_unwrapdir(struct bftw_state *state, struct bftw_file *file) { return bftw_ioq_closedir(state, dir); } +/** Open a directory asynchronously. */ +static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { + if (bftw_ioq_reserve(state) != 0) { + goto fail; + } + + int dfd = AT_FDCWD; + struct bftw_cache *cache = &state->cache; + struct bftw_file *parent = file->parent; + if (parent) { + dfd = parent->fd; + if (dfd < 0) { + goto fail; + } + bftw_cache_pin(cache, parent); + } + + if (bftw_cache_reserve(state) != 0) { + goto unpin; + } + + struct bfs_dir *dir = bftw_allocdir(cache); + if (!dir) { + goto unpin; + } + + if (ioq_opendir(state->ioq, dir, dfd, file->name, file) != 0) { + goto free; + } + + file->ioqueued = true; + --cache->capacity; + return 0; + +free: + bftw_freedir(cache, dir); +unpin: + if (parent) { + bftw_cache_unpin(cache, parent); + } +fail: + return -1; +} + +/** Push a directory onto the queue. */ +static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { + bfs_assert(file->type == BFS_DIR); + + SLIST_APPEND(&state->to_open, file, to_open); + + if (state->flags & BFTW_SORT) { + // When sorting, directories are kept in order on the to_read + // list; otherwise, they are only added once they are open + SLIST_APPEND(&state->to_read, file, to_read); + } + + while (state->to_open.head) { + if (bftw_ioq_opendir(state, state->to_open.head) == 0) { + SLIST_POP(&state->to_open, to_open); + } else { + break; + } + } +} + /** Pop a directory to read from the queue. */ static bool bftw_pop_dir(struct bftw_state *state) { bfs_assert(!state->file); - struct bftw_file *dir = state->dirs.head; - bool have_files = state->files.head; + bool have_files = state->to_visit.head; if (state->flags & BFTW_SORT) { // Keep strict breadth-first order when sorting @@ -820,36 +841,39 @@ static bool bftw_pop_dir(struct bftw_state *state) { return false; } } else { - while (!dir || !dir->dir) { + while (!state->to_read.head) { // Block if we have no other files/dirs to visit, or no room in the cache + bool have_dirs = state->to_open.head; bool have_room = state->cache.capacity > 0; - bool block = !(dir || have_files) || !have_room; + bool block = !(have_dirs || have_files) || !have_room; if (bftw_ioq_pop(state, block) < 0) { break; } - dir = state->dirs.head; } } - if (!dir) { + struct bftw_file *file = SLIST_POP(&state->to_read, to_read); + if (!file || file == state->to_open.head) { + file = SLIST_POP(&state->to_open, to_open); + } + if (!file) { return false; } - SLIST_POP(&state->dirs); - state->file = dir; - - while (dir->ioqueued) { + while (file->ioqueued) { bftw_ioq_pop(state, true); } + state->file = file; return true; } /** Pop a file to visit from the queue. */ static bool bftw_pop_file(struct bftw_state *state) { bfs_assert(!state->file); - return (state->file = SLIST_POP(&state->files)); + state->file = SLIST_POP(&state->to_visit, to_visit); + return state->file; } /** Build the path to the current file. */ @@ -1242,7 +1266,7 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { /** Sort a bftw_list by filename. */ static void bftw_list_sort(struct bftw_list *list) { - if (!list->head || !list->head->next) { + if (!list->head || !list->head->to_visit.next) { return; } @@ -1251,9 +1275,11 @@ static void bftw_list_sort(struct bftw_list *list) { SLIST_INIT(&right); // Split - for (struct bftw_file *hare = list->head; hare && (hare = hare->next); hare = hare->next) { - struct bftw_file *tortoise = SLIST_POP(list); - SLIST_APPEND(&left, tortoise); + for (struct bftw_file *hare = list->head; + hare && (hare = hare->to_visit.next); + hare = hare->to_visit.next) { + struct bftw_file *tortoise = SLIST_POP(list, to_visit); + SLIST_APPEND(&left, tortoise, to_visit); } SLIST_EXTEND(&right, list); @@ -1267,11 +1293,11 @@ static void bftw_list_sort(struct bftw_list *list) { struct bftw_file *rf = right.head; if (strcoll(lf->name, rf->name) <= 0) { - SLIST_POP(&left); - SLIST_APPEND(list, lf); + SLIST_POP(&left, to_visit); + SLIST_APPEND(list, lf, to_visit); } else { - SLIST_POP(&right); - SLIST_APPEND(list, rf); + SLIST_POP(&right, to_visit); + SLIST_APPEND(list, rf, to_visit); } } SLIST_EXTEND(list, &left); @@ -1285,9 +1311,9 @@ static void bftw_batch_finish(struct bftw_state *state) { } if (state->strategy != BFTW_BFS) { - SLIST_EXTEND(&state->batch, &state->files); + SLIST_EXTEND(&state->batch, &state->to_visit); } - SLIST_EXTEND(&state->files, &state->batch); + SLIST_EXTEND(&state->to_visit, &state->batch); } /** Close the current directory. */ @@ -1329,7 +1355,7 @@ static int bftw_visit(struct bftw_state *state, const char *name) { file->type = state->de->type; } - SLIST_APPEND(&state->batch, file); + SLIST_APPEND(&state->batch, file, to_visit); return 0; } @@ -1377,7 +1403,7 @@ static int bftw_state_destroy(struct bftw_state *state) { state->ioq = NULL; } - SLIST_EXTEND(&state->files, &state->batch); + SLIST_EXTEND(&state->to_visit, &state->batch); do { bftw_gc(state, BFTW_VISIT_NONE); } while (bftw_pop_dir(state) || bftw_pop_file(state)); -- cgit v1.2.3 From eb466474e7b5ab79bbeccb2d99bb7b1cfc57af5f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 17 Jul 2023 20:30:57 -0400 Subject: bftw: Add dirs to the end of the queue in bftw_ioq_pop() I tried this before in #105 but it led to performance regressions. The key to avoiding those regressions is to put some backpressure on how many bfs_dir's can be allocated simultaneously. --- src/bftw.c | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index a412c26..0b90f55 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -366,6 +366,8 @@ static void bftw_file_set_dir(struct bftw_cache *cache, struct bftw_file *file, file->fd = bfs_dirfd(dir); bftw_cache_add(cache, file); } + + bftw_cache_pin(cache, file); } /** Free a bftw_file. */ @@ -409,6 +411,8 @@ struct bftw_state { struct bftw_list to_open; /** The queue of directories to read. */ struct bftw_list to_read; + /** Available capacity in to_read. */ + size_t dirlimit; /** The queue of files to visit. */ struct bftw_list to_visit; @@ -483,6 +487,7 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg SLIST_INIT(&state->to_open); SLIST_INIT(&state->to_read); + state->dirlimit = qdepth; SLIST_INIT(&state->to_visit); SLIST_INIT(&state->batch); @@ -539,10 +544,11 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { bftw_file_set_dir(cache, file, dir); } else { bftw_freedir(cache, dir); + ++state->dirlimit; } if (!(state->flags & BFTW_SORT)) { - SLIST_PREPEND(&state->to_read, file, to_read); + SLIST_APPEND(&state->to_read, file, to_read); } break; } @@ -766,6 +772,10 @@ static int bftw_unwrapdir(struct bftw_state *state, struct bftw_file *file) { /** Open a directory asynchronously. */ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { + if (state->dirlimit == 0) { + goto fail; + } + if (bftw_ioq_reserve(state) != 0) { goto fail; } @@ -796,6 +806,7 @@ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { file->ioqueued = true; --cache->capacity; + --state->dirlimit; return 0; free: @@ -861,6 +872,10 @@ static bool bftw_pop_dir(struct bftw_state *state) { return false; } + if (file->dir) { + ++state->dirlimit; + } + while (file->ioqueued) { bftw_ioq_pop(state, true); } @@ -953,18 +968,17 @@ static int bftw_opendir(struct bftw_state *state) { state->direrror = 0; struct bftw_file *file = state->file; - if (file->dir) { - state->dir = file->dir; - } else { - if (bftw_build_path(state, NULL) != 0) { - return -1; - } - state->dir = bftw_file_opendir(state, file, state->path); + state->dir = file->dir; + if (state->dir) { + return 0; } - if (state->dir) { - bftw_cache_pin(&state->cache, file); - } else { + if (bftw_build_path(state, NULL) != 0) { + return -1; + } + + state->dir = bftw_file_opendir(state, file, state->path); + if (!state->dir) { state->direrror = errno; } -- cgit v1.2.3 From 815798e1eea7fc8dacd5acab40202ec4d251d517 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 18 Jul 2023 11:43:21 -0400 Subject: bftw: Add a queue of directories to unwrap For !BFS_USE_UNWRAPDIR, if a file is still pinned in bftw_closedir(), it has to stay open until its pincount drops to zero. Since this happens in bftw_ioq_pop(), we can't immediately call bftw_unwrapdir() as that adds to the ioq. Instead, add it to a list that gets drained by the next bftw_gc(). --- src/bftw.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 0b90f55..dd34d64 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -119,6 +119,8 @@ struct bftw_file { struct { struct bftw_file *next; } to_open; /** The next directory to read. */ struct { struct bftw_file *next; } to_read; + /** The next directory to close. */ + struct { struct bftw_file *next; } to_close; /** The next file to visit. */ struct { struct bftw_file *next; } to_visit; @@ -336,6 +338,7 @@ static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_fil file->to_open.next = NULL; file->to_read.next = NULL; + file->to_close.next = NULL; file->to_visit.next = NULL; file->lru.prev = file->lru.next = NULL; @@ -411,6 +414,8 @@ struct bftw_state { struct bftw_list to_open; /** The queue of directories to read. */ struct bftw_list to_read; + /** The queue of unpinned directories to unwrap. */ + struct bftw_list to_close; /** Available capacity in to_read. */ size_t dirlimit; @@ -487,6 +492,7 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg SLIST_INIT(&state->to_open); SLIST_INIT(&state->to_read); + SLIST_INIT(&state->to_close); state->dirlimit = qdepth; SLIST_INIT(&state->to_visit); @@ -516,6 +522,7 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { struct bftw_cache *cache = &state->cache; struct bftw_file *file; + struct bftw_file *parent; struct bfs_dir *dir; enum ioq_op op = ent->op; @@ -535,8 +542,12 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { file->ioqueued = false; ++cache->capacity; - if (file->parent) { - bftw_cache_unpin(cache, file->parent); + parent = file->parent; + if (parent) { + bftw_cache_unpin(cache, parent); + if (parent->pincount == 0 && parent->dir) { + SLIST_APPEND(&state->to_close, parent, to_close); + } } dir = ent->opendir.dir; @@ -1228,9 +1239,10 @@ enum bftw_gc_flags { static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { int ret = 0; - if (state->dir) { - bftw_cache_unpin(&state->cache, state->file); - bftw_unwrapdir(state, state->file); + struct bftw_file *file = state->file; + if (file && file->dir) { + bftw_cache_unpin(&state->cache, file); + SLIST_APPEND(&state->to_close, file, to_close); } state->dir = NULL; state->de = NULL; @@ -1247,9 +1259,12 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { } state->direrror = 0; + while ((file = SLIST_POP(&state->to_close, to_close))) { + bftw_unwrapdir(state, file); + } + enum bftw_gc_flags visit = BFTW_VISIT_FILE; - while (state->file) { - struct bftw_file *file = state->file; + while ((file = state->file)) { if (--file->refcount > 0) { state->file = NULL; break; -- cgit v1.2.3 From 0d5bcc9e5c53f64024afbad19b1a01ae9b2937af Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 18 Jul 2023 11:56:41 -0400 Subject: bftw: Use a larger ioq depth Now that the dirlimit provides backpressure on the number of open directories, we can use a uniformly larger queue depth for increased performance. The current parameters were tuned with a small grid search on my workstation. --- src/bftw.c | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index dd34d64..c66e607 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -462,42 +462,32 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg errno = EMFILE; return -1; } - - state->path = dstralloc(0); - if (!state->path) { - return -1; - } - bftw_cache_init(&state->cache, args->nopenfd); - size_t qdepth = args->nopenfd - 1; - if (qdepth > 1024) { - qdepth = 1024; - } - - size_t nthreads = args->nthreads; - if (nthreads > qdepth) { - nthreads = qdepth; - } - - state->ioq = NULL; - if (nthreads > 0) { - state->ioq = ioq_create(qdepth, nthreads); + state->nthreads = args->nthreads; + if (state->nthreads > 0) { + state->ioq = ioq_create(4096, state->nthreads); if (!state->ioq) { - dstrfree(state->path); return -1; } + } else { + state->ioq = NULL; } - state->nthreads = nthreads; SLIST_INIT(&state->to_open); SLIST_INIT(&state->to_read); SLIST_INIT(&state->to_close); - state->dirlimit = qdepth; + + size_t dirlimit = args->nopenfd - 1; + if (dirlimit > 1024) { + dirlimit = 1024; + } + state->dirlimit = dirlimit; SLIST_INIT(&state->to_visit); SLIST_INIT(&state->batch); + state->path = NULL; state->file = NULL; state->previous = NULL; -- cgit v1.2.3 From 0e72e5c985db9a131aea3f3e9238a8916c470d8e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 18 Jul 2023 12:14:07 -0400 Subject: bftw: Use bftw_file->next for multiple lists A file can be on the to_open and to_read lists at the same time, but otherwise only one list, so we can save memory by sharing the pointers. --- src/bftw.c | 50 +++++++++++++++++++++----------------------------- 1 file changed, 21 insertions(+), 29 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index c66e607..5d28612 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -115,14 +115,10 @@ struct bftw_file { /** The root under which this file was found. */ struct bftw_file *root; - /** The next directory to open. */ - struct { struct bftw_file *next; } to_open; + /** The next file to open/close/visit. */ + struct bftw_file *next; /** The next directory to read. */ struct { struct bftw_file *next; } to_read; - /** The next directory to close. */ - struct { struct bftw_file *next; } to_close; - /** The next file to visit. */ - struct { struct bftw_file *next; } to_visit; /** LRU list node. */ struct { @@ -336,10 +332,8 @@ static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_fil file->nameoff = 0; } - file->to_open.next = NULL; + file->next = NULL; file->to_read.next = NULL; - file->to_close.next = NULL; - file->to_visit.next = NULL; file->lru.prev = file->lru.next = NULL; file->refcount = 1; @@ -536,7 +530,7 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { if (parent) { bftw_cache_unpin(cache, parent); if (parent->pincount == 0 && parent->dir) { - SLIST_APPEND(&state->to_close, parent, to_close); + SLIST_APPEND(&state->to_close, parent); } } @@ -665,10 +659,10 @@ static int bftw_file_open(struct bftw_state *state, struct bftw_file *file, cons struct bftw_file *cur; for (cur = file; cur != base; cur = cur->parent) { - SLIST_PREPEND(&parents, cur, to_open); + SLIST_PREPEND(&parents, cur); } - while ((cur = SLIST_POP(&parents, to_open))) { + while ((cur = SLIST_POP(&parents))) { if (!cur->parent || cur->parent->fd >= 0) { bftw_file_openat(state, cur, cur->parent, cur->name); } @@ -824,7 +818,7 @@ fail: static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { bfs_assert(file->type == BFS_DIR); - SLIST_APPEND(&state->to_open, file, to_open); + SLIST_APPEND(&state->to_open, file); if (state->flags & BFTW_SORT) { // When sorting, directories are kept in order on the to_read @@ -834,7 +828,7 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { while (state->to_open.head) { if (bftw_ioq_opendir(state, state->to_open.head) == 0) { - SLIST_POP(&state->to_open, to_open); + SLIST_POP(&state->to_open); } else { break; } @@ -867,7 +861,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { struct bftw_file *file = SLIST_POP(&state->to_read, to_read); if (!file || file == state->to_open.head) { - file = SLIST_POP(&state->to_open, to_open); + file = SLIST_POP(&state->to_open); } if (!file) { return false; @@ -888,7 +882,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { /** Pop a file to visit from the queue. */ static bool bftw_pop_file(struct bftw_state *state) { bfs_assert(!state->file); - state->file = SLIST_POP(&state->to_visit, to_visit); + state->file = SLIST_POP(&state->to_visit); return state->file; } @@ -1232,7 +1226,7 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { struct bftw_file *file = state->file; if (file && file->dir) { bftw_cache_unpin(&state->cache, file); - SLIST_APPEND(&state->to_close, file, to_close); + SLIST_APPEND(&state->to_close, file); } state->dir = NULL; state->de = NULL; @@ -1249,7 +1243,7 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { } state->direrror = 0; - while ((file = SLIST_POP(&state->to_close, to_close))) { + while ((file = SLIST_POP(&state->to_close))) { bftw_unwrapdir(state, file); } @@ -1285,7 +1279,7 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { /** Sort a bftw_list by filename. */ static void bftw_list_sort(struct bftw_list *list) { - if (!list->head || !list->head->to_visit.next) { + if (!list->head || !list->head->next) { return; } @@ -1294,11 +1288,9 @@ static void bftw_list_sort(struct bftw_list *list) { SLIST_INIT(&right); // Split - for (struct bftw_file *hare = list->head; - hare && (hare = hare->to_visit.next); - hare = hare->to_visit.next) { - struct bftw_file *tortoise = SLIST_POP(list, to_visit); - SLIST_APPEND(&left, tortoise, to_visit); + for (struct bftw_file *hare = list->head; hare && (hare = hare->next); hare = hare->next) { + struct bftw_file *tortoise = SLIST_POP(list); + SLIST_APPEND(&left, tortoise); } SLIST_EXTEND(&right, list); @@ -1312,11 +1304,11 @@ static void bftw_list_sort(struct bftw_list *list) { struct bftw_file *rf = right.head; if (strcoll(lf->name, rf->name) <= 0) { - SLIST_POP(&left, to_visit); - SLIST_APPEND(list, lf, to_visit); + SLIST_POP(&left); + SLIST_APPEND(list, lf); } else { - SLIST_POP(&right, to_visit); - SLIST_APPEND(list, rf, to_visit); + SLIST_POP(&right); + SLIST_APPEND(list, rf); } } SLIST_EXTEND(list, &left); @@ -1374,7 +1366,7 @@ static int bftw_visit(struct bftw_state *state, const char *name) { file->type = state->de->type; } - SLIST_APPEND(&state->batch, file, to_visit); + SLIST_APPEND(&state->batch, file); return 0; } -- cgit v1.2.3 From 37dd040e04bd23293b6e46f8f5af22ea07717894 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 6 Sep 2023 09:30:47 -0400 Subject: bftw: Enforce the dirlimit strictly The previous accounting didn't fully control the number of allocated bfs_dirs, as the dirlimit was incremented once we popped the directory, not when we freed it. --- src/bftw.c | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 5d28612..810cd8a 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -180,6 +180,8 @@ struct bftw_cache { struct varena files; /** bfs_dir arena. */ struct arena dirs; + /** Remaining bfs_dir capacity. */ + size_t dirlimit; }; /** Initialize a cache. */ @@ -187,17 +189,30 @@ static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) { LIST_INIT(cache); cache->target = NULL; cache->capacity = capacity; + VARENA_INIT(&cache->files, struct bftw_file, name); bfs_dir_arena(&cache->dirs); + + cache->dirlimit = capacity - 1; + if (cache->dirlimit > 1024) { + cache->dirlimit = 1024; + } } /** Allocate a directory. */ static struct bfs_dir *bftw_allocdir(struct bftw_cache *cache) { + if (cache->dirlimit == 0) { + errno = ENOMEM; + return NULL; + } + --cache->dirlimit; + return arena_alloc(&cache->dirs); } /** Free a directory. */ static void bftw_freedir(struct bftw_cache *cache, struct bfs_dir *dir) { + ++cache->dirlimit; arena_free(&cache->dirs, dir); } @@ -410,8 +425,6 @@ struct bftw_state { struct bftw_list to_read; /** The queue of unpinned directories to unwrap. */ struct bftw_list to_close; - /** Available capacity in to_read. */ - size_t dirlimit; /** The queue of files to visit. */ struct bftw_list to_visit; @@ -472,12 +485,6 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg SLIST_INIT(&state->to_read); SLIST_INIT(&state->to_close); - size_t dirlimit = args->nopenfd - 1; - if (dirlimit > 1024) { - dirlimit = 1024; - } - state->dirlimit = dirlimit; - SLIST_INIT(&state->to_visit); SLIST_INIT(&state->batch); @@ -539,7 +546,6 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { bftw_file_set_dir(cache, file, dir); } else { bftw_freedir(cache, dir); - ++state->dirlimit; } if (!(state->flags & BFTW_SORT)) { @@ -767,10 +773,6 @@ static int bftw_unwrapdir(struct bftw_state *state, struct bftw_file *file) { /** Open a directory asynchronously. */ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { - if (state->dirlimit == 0) { - goto fail; - } - if (bftw_ioq_reserve(state) != 0) { goto fail; } @@ -801,7 +803,6 @@ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { file->ioqueued = true; --cache->capacity; - --state->dirlimit; return 0; free: @@ -839,6 +840,7 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { static bool bftw_pop_dir(struct bftw_state *state) { bfs_assert(!state->file); + struct bftw_cache *cache = &state->cache; bool have_files = state->to_visit.head; if (state->flags & BFTW_SORT) { @@ -850,7 +852,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { while (!state->to_read.head) { // Block if we have no other files/dirs to visit, or no room in the cache bool have_dirs = state->to_open.head; - bool have_room = state->cache.capacity > 0; + bool have_room = cache->capacity > 0 && cache->dirlimit > 0; bool block = !(have_dirs || have_files) || !have_room; if (bftw_ioq_pop(state, block) < 0) { @@ -867,10 +869,6 @@ static bool bftw_pop_dir(struct bftw_state *state) { return false; } - if (file->dir) { - ++state->dirlimit; - } - while (file->ioqueued) { bftw_ioq_pop(state, true); } -- cgit v1.2.3 From 5f1616912ba3a7a23ce6bce02df3791b73da38ab Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 13 Sep 2023 11:39:50 -0400 Subject: bftw: Share the bftw_state between iterations of ids/eds --- src/bftw.c | 143 ++++++++++++++++++++++++++++++------------------------------- 1 file changed, 71 insertions(+), 72 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 810cd8a..e6b8cd5 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -397,6 +397,10 @@ static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) { * Holds the current state of the bftw() traversal. */ struct bftw_state { + /** The path(s) to start from. */ + const char **paths; + /** The number of starting paths. */ + size_t npaths; /** bftw() callback. */ bftw_callback *callback; /** bftw() callback data. */ @@ -453,6 +457,8 @@ struct bftw_state { /** Initialize the bftw() state. */ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *args) { + state->paths = args->paths; + state->npaths = args->npaths; state->callback = args->callback; state->ptr = args->ptr; state->flags = args->flags; @@ -1426,45 +1432,52 @@ static int bftw_state_destroy(struct bftw_state *state) { } /** - * bftw() implementation for simple breadth-/depth-first search. + * Shared implementation for all search strategies. */ -static int bftw_impl(const struct bftw_args *args) { - struct bftw_state state; - if (bftw_state_init(&state, args) != 0) { - return -1; - } - - for (size_t i = 0; i < args->npaths; ++i) { - if (bftw_visit(&state, args->paths[i]) != 0) { - goto done; +static int bftw_impl(struct bftw_state *state) { + for (size_t i = 0; i < state->npaths; ++i) { + if (bftw_visit(state, state->paths[i]) != 0) { + return -1; } } - bftw_batch_finish(&state); + bftw_batch_finish(state); while (true) { - while (bftw_pop_dir(&state)) { - if (bftw_opendir(&state) != 0) { - goto done; + while (bftw_pop_dir(state)) { + if (bftw_opendir(state) != 0) { + return -1; } - while (bftw_readdir(&state) > 0) { - if (bftw_visit(&state, state.de->name) != 0) { - goto done; + while (bftw_readdir(state) > 0) { + if (bftw_visit(state, state->de->name) != 0) { + return -1; } } - if (bftw_closedir(&state) != 0) { - goto done; + if (bftw_closedir(state) != 0) { + return -1; } } - if (!bftw_pop_file(&state)) { + if (!bftw_pop_file(state)) { break; } - if (bftw_visit(&state, NULL) != 0) { + if (bftw_visit(state, NULL) != 0) { break; } } -done: + return 0; +} + +/** + * bftw() implementation for simple breadth-/depth-first search. + */ +static int bftw_walk(const struct bftw_args *args) { + struct bftw_state state; + if (bftw_state_init(&state, args) != 0) { + return -1; + } + + bftw_impl(&state); return bftw_state_destroy(&state); } @@ -1472,6 +1485,8 @@ done: * Iterative deepening search state. */ struct bftw_ids_state { + /** Nested walk state. */ + struct bftw_state nested; /** The wrapped callback. */ bftw_callback *delegate; /** The wrapped callback arguments. */ @@ -1486,12 +1501,8 @@ struct bftw_ids_state { size_t max_depth; /** The set of pruned paths. */ struct trie pruned; - /** An error code to report. */ - int error; /** Whether the bottom has been found. */ bool bottom; - /** Whether to quit the search. */ - bool quit; }; /** Iterative deepening callback function. */ @@ -1535,17 +1546,17 @@ static enum bftw_action bftw_ids_callback(const struct BFTW *ftwbuf, void *ptr) ret = BFTW_PRUNE; } break; + case BFTW_PRUNE: if (ftwbuf->type == BFS_DIR) { if (!trie_insert_str(&state->pruned, ftwbuf->path)) { - state->error = errno; - state->quit = true; + state->nested.error = errno; ret = BFTW_STOP; } } break; + case BFTW_STOP: - state->quit = true; break; } @@ -1553,7 +1564,7 @@ static enum bftw_action bftw_ids_callback(const struct BFTW *ftwbuf, void *ptr) } /** Initialize iterative deepening state. */ -static void bftw_ids_init(const struct bftw_args *args, struct bftw_ids_state *state, struct bftw_args *ids_args) { +static int bftw_ids_init(struct bftw_ids_state *state, const struct bftw_args *args) { state->delegate = args->callback; state->ptr = args->ptr; state->visit = BFTW_PRE; @@ -1561,30 +1572,19 @@ static void bftw_ids_init(const struct bftw_args *args, struct bftw_ids_state *s state->min_depth = 0; state->max_depth = 1; trie_init(&state->pruned); - state->error = 0; state->bottom = false; - state->quit = false; - *ids_args = *args; - ids_args->callback = bftw_ids_callback; - ids_args->ptr = state; - ids_args->flags &= ~BFTW_POST_ORDER; + struct bftw_args ids_args = *args; + ids_args.callback = bftw_ids_callback; + ids_args.ptr = state; + ids_args.flags &= ~BFTW_POST_ORDER; + return bftw_state_init(&state->nested, &ids_args); } /** Finish an iterative deepening search. */ -static int bftw_ids_finish(struct bftw_ids_state *state) { - int ret = 0; - - if (state->error) { - ret = -1; - } else { - state->error = errno; - } - +static int bftw_ids_destroy(struct bftw_ids_state *state) { trie_destroy(&state->pruned); - - errno = state->error; - return ret; + return bftw_state_destroy(&state->nested); } /** @@ -1592,15 +1592,15 @@ static int bftw_ids_finish(struct bftw_ids_state *state) { */ static int bftw_ids(const struct bftw_args *args) { struct bftw_ids_state state; - struct bftw_args ids_args; - bftw_ids_init(args, &state, &ids_args); + if (bftw_ids_init(&state, args) != 0) { + return -1; + } - while (!state.quit && !state.bottom) { + while (!state.bottom) { state.bottom = true; - if (bftw_impl(&ids_args) != 0) { - state.error = errno; - state.quit = true; + if (bftw_impl(&state.nested) != 0) { + goto done; } ++state.min_depth; @@ -1611,18 +1611,18 @@ static int bftw_ids(const struct bftw_args *args) { state.visit = BFTW_POST; state.force_visit = true; - while (!state.quit && state.min_depth > 0) { + while (state.min_depth > 0) { --state.max_depth; --state.min_depth; - if (bftw_impl(&ids_args) != 0) { - state.error = errno; - state.quit = true; + if (bftw_impl(&state.nested) != 0) { + goto done; } } } - return bftw_ids_finish(&state); +done: + return bftw_ids_destroy(&state); } /** @@ -1630,39 +1630,38 @@ static int bftw_ids(const struct bftw_args *args) { */ static int bftw_eds(const struct bftw_args *args) { struct bftw_ids_state state; - struct bftw_args ids_args; - bftw_ids_init(args, &state, &ids_args); + if (bftw_ids_init(&state, args) != 0) { + return -1; + } - while (!state.quit && !state.bottom) { + while (!state.bottom) { state.bottom = true; - if (bftw_impl(&ids_args) != 0) { - state.error = errno; - state.quit = true; + if (bftw_impl(&state.nested) != 0) { + goto done; } state.min_depth = state.max_depth; state.max_depth *= 2; } - if (!state.quit && (args->flags & BFTW_POST_ORDER)) { + if (args->flags & BFTW_POST_ORDER) { state.visit = BFTW_POST; state.min_depth = 0; - ids_args.flags |= BFTW_POST_ORDER; + state.nested.flags |= BFTW_POST_ORDER; - if (bftw_impl(&ids_args) != 0) { - state.error = errno; - } + bftw_impl(&state.nested); } - return bftw_ids_finish(&state); +done: + return bftw_ids_destroy(&state); } int bftw(const struct bftw_args *args) { switch (args->strategy) { case BFTW_BFS: case BFTW_DFS: - return bftw_impl(args); + return bftw_walk(args); case BFTW_IDS: return bftw_ids(args); case BFTW_EDS: -- cgit v1.2.3 From 76253d272c50ba08002a47395e47a4f9ce4fb53e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 25 Sep 2023 13:58:19 -0400 Subject: Use the new list macros --- src/bftw.c | 24 +++++++++++------------- src/color.c | 2 +- src/ctx.c | 4 ++-- src/trie.h | 7 +++---- src/xspawn.c | 11 +++++------ tests/trie.c | 4 ++-- 6 files changed, 24 insertions(+), 28 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index e6b8cd5..f3060ce 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -318,8 +318,7 @@ static size_t bftw_child_nameoff(const struct bftw_file *parent) { /** Destroy a cache. */ static void bftw_cache_destroy(struct bftw_cache *cache) { - bfs_assert(!cache->head); - bfs_assert(!cache->tail); + bfs_assert(LIST_EMPTY(cache)); bfs_assert(!cache->target); varena_destroy(&cache->files); @@ -347,9 +346,9 @@ static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_fil file->nameoff = 0; } - file->next = NULL; - file->to_read.next = NULL; - file->lru.prev = file->lru.next = NULL; + SLIST_ITEM_INIT(file); + SLIST_ITEM_INIT(file, to_read); + LIST_ITEM_INIT(file, lru); file->refcount = 1; file->pincount = 0; @@ -833,12 +832,11 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { SLIST_APPEND(&state->to_read, file, to_read); } - while (state->to_open.head) { - if (bftw_ioq_opendir(state, state->to_open.head) == 0) { - SLIST_POP(&state->to_open); - } else { + for_slist (struct bftw_file, dir, &state->to_open) { + if (bftw_ioq_opendir(state, dir) != 0) { break; } + SLIST_POP(&state->to_open); } } @@ -847,7 +845,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { bfs_assert(!state->file); struct bftw_cache *cache = &state->cache; - bool have_files = state->to_visit.head; + bool have_files = !SLIST_EMPTY(&state->to_visit); if (state->flags & BFTW_SORT) { // Keep strict breadth-first order when sorting @@ -855,9 +853,9 @@ static bool bftw_pop_dir(struct bftw_state *state) { return false; } } else { - while (!state->to_read.head) { + while (SLIST_EMPTY(&state->to_read)) { // Block if we have no other files/dirs to visit, or no room in the cache - bool have_dirs = state->to_open.head; + bool have_dirs = !SLIST_EMPTY(&state->to_open); bool have_room = cache->capacity > 0 && cache->dirlimit > 0; bool block = !(have_dirs || have_files) || !have_room; @@ -1303,7 +1301,7 @@ static void bftw_list_sort(struct bftw_list *list) { bftw_list_sort(&right); // Merge - while (left.head && right.head) { + while (!SLIST_EMPTY(&left) && !SLIST_EMPTY(&right)) { struct bftw_file *lf = left.head; struct bftw_file *rf = right.head; diff --git a/src/color.c b/src/color.c index b9a788b..5e78c6c 100644 --- a/src/color.c +++ b/src/color.c @@ -328,7 +328,7 @@ fail: static int build_iext_trie(struct colors *colors) { trie_clear(&colors->iext_trie); - TRIE_FOR_EACH(&colors->ext_trie, leaf) { + for_trie (leaf, &colors->ext_trie) { size_t len = leaf->length - 1; if (colors->ext_len < len) { colors->ext_len = len; diff --git a/src/ctx.c b/src/ctx.c index a940bed..3a44e68 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -152,7 +152,7 @@ void bfs_ctx_flush(const struct bfs_ctx *ctx) { // - the user sees everything relevant before an -ok[dir] prompt // - output from commands is interleaved consistently with bfs // - executed commands can rely on I/O from other bfs actions - TRIE_FOR_EACH(&ctx->files, leaf) { + for_trie (leaf, &ctx->files) { struct bfs_ctx_file *ctx_file = leaf->value; CFILE *cfile = ctx_file->cfile; if (fflush(cfile->file) == 0) { @@ -239,7 +239,7 @@ int bfs_ctx_free(struct bfs_ctx *ctx) { bfs_groups_free(ctx->groups); bfs_users_free(ctx->users); - TRIE_FOR_EACH(&ctx->files, leaf) { + for_trie (leaf, &ctx->files) { struct bfs_ctx_file *ctx_file = leaf->value; if (ctx_file->error) { diff --git a/src/trie.h b/src/trie.h index dfaae15..2f51db5 100644 --- a/src/trie.h +++ b/src/trie.h @@ -6,6 +6,7 @@ #include "config.h" #include "alloc.h" +#include "list.h" #include #include @@ -141,9 +142,7 @@ void trie_destroy(struct trie *trie); /** * Iterate over the leaves of a trie. */ -#define TRIE_FOR_EACH(trie, leaf) \ - for (struct trie_leaf *leaf = (trie)->head, *_next; \ - leaf && (_next = leaf->next, true); \ - leaf = _next) +#define for_trie(leaf, trie) \ + for_list(struct trie_leaf, leaf, trie) #endif // BFS_TRIE_H diff --git a/src/xspawn.c b/src/xspawn.c index 2cabdcc..80bafef 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -49,8 +49,8 @@ int bfs_spawn_init(struct bfs_spawn *ctx) { } int bfs_spawn_destroy(struct bfs_spawn *ctx) { - while (ctx->head) { - free(SLIST_POP(ctx)); + for_slist (struct bfs_spawn_action, action, ctx) { + free(action); } return 0; @@ -68,7 +68,7 @@ static struct bfs_spawn_action *bfs_spawn_add(struct bfs_spawn *ctx, enum bfs_sp return NULL; } - action->next = NULL; + SLIST_ITEM_INIT(action); action->op = op; action->in_fd = -1; action->out_fd = -1; @@ -138,7 +138,7 @@ int bfs_spawn_addsetrlimit(struct bfs_spawn *ctx, int resource, const struct rli static void bfs_spawn_exec(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) { xclose(pipefd[0]); - for (const struct bfs_spawn_action *action = ctx ? ctx->head : NULL; action; action = action->next) { + for_slist (const struct bfs_spawn_action, action, ctx) { // Move the error-reporting pipe out of the way if necessary... if (action->out_fd == pipefd[1]) { int fd = dup_cloexec(pipefd[1]); @@ -199,9 +199,8 @@ pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char envp = environ; } - enum bfs_spawn_flags flags = ctx ? ctx->flags : 0; char *resolved = NULL; - if (flags & BFS_SPAWN_USEPATH) { + if (ctx->flags & BFS_SPAWN_USEPATH) { exe = resolved = bfs_spawn_resolve(exe); if (!resolved) { return -1; diff --git a/tests/trie.c b/tests/trie.c index e687f96..6ea94a2 100644 --- a/tests/trie.c +++ b/tests/trie.c @@ -70,7 +70,7 @@ int main(void) { { size_t i = 0; - TRIE_FOR_EACH(&trie, leaf) { + for_trie (leaf, &trie) { bfs_verify(leaf == trie_find_str(&trie, keys[i])); bfs_verify(!leaf->prev || leaf->prev->next == leaf); bfs_verify(!leaf->next || leaf->next->prev == leaf); @@ -107,7 +107,7 @@ int main(void) { } } - TRIE_FOR_EACH(&trie, leaf) { + for_trie (leaf, &trie) { bfs_verify(false); } -- cgit v1.2.3 From dccb52556730ff060bcccbe764cef4b13b3d5712 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 26 Sep 2023 12:48:21 -0400 Subject: dstring: New dchar typedef for dynamic strings --- src/bar.c | 2 +- src/bftw.c | 2 +- src/color.c | 6 +++--- src/color.h | 3 ++- src/diag.c | 2 +- src/dstring.c | 55 ++++++++++++++++++++++++++------------------------- src/dstring.h | 63 ++++++++++++++++++++++++++++++++++++++--------------------- src/eval.c | 4 ++-- src/exec.c | 4 ++-- src/fsade.c | 4 ++-- src/printf.c | 4 ++-- 11 files changed, 85 insertions(+), 64 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bar.c b/src/bar.c index d2c663c..e0b9393 100644 --- a/src/bar.c +++ b/src/bar.c @@ -129,7 +129,7 @@ BFS_FORMATTER(2, 3) static int bfs_bar_printf(struct bfs_bar *bar, const char *format, ...) { va_list args; va_start(args, format); - char *str = dstrvprintf(format, args); + dchar *str = dstrvprintf(format, args); va_end(args); if (!str) { diff --git a/src/bftw.c b/src/bftw.c index f3060ce..5e5f4a5 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -435,7 +435,7 @@ struct bftw_state { struct bftw_list batch; /** The current path. */ - char *path; + dchar *path; /** The current file. */ struct bftw_file *file; /** The previous file. */ diff --git a/src/color.c b/src/color.c index 5e78c6c..8d0b995 100644 --- a/src/color.c +++ b/src/color.c @@ -558,8 +558,8 @@ static int unescape(char **str, const char *value, char end, const char **next) /** Parse the GNU $LS_COLORS format. */ static int parse_gnu_ls_colors(struct colors *colors, const char *ls_colors) { int ret = -1; - char *key = NULL; - char *value = NULL; + dchar *key = NULL; + dchar *value = NULL; for (const char *chunk = ls_colors, *next; chunk; chunk = next) { if (chunk[0] == '*') { @@ -943,7 +943,7 @@ static ssize_t first_broken_offset(const char *path, const struct BFTW *ftwbuf, goto out; } - char *at_path; + dchar *at_path; int at_fd; if (path == ftwbuf->path) { if (ftwbuf->depth == 0) { diff --git a/src/color.h b/src/color.h index 0d46c33..b118f77 100644 --- a/src/color.h +++ b/src/color.h @@ -9,6 +9,7 @@ #define BFS_COLOR_H #include "config.h" +#include "dstring.h" #include #include @@ -41,7 +42,7 @@ typedef struct CFILE { /** The color table to use, if any. */ const struct colors *colors; /** A buffer for colored formatting. */ - char *buffer; + dchar *buffer; /** Whether the next ${rs} is actually necessary. */ bool need_reset; /** Whether to close the underlying stream. */ diff --git a/src/diag.c b/src/diag.c index 0590847..fa9db39 100644 --- a/src/diag.c +++ b/src/diag.c @@ -158,7 +158,7 @@ static void bfs_argv_diag(const struct bfs_ctx *ctx, const bool args[], bool war bfs_error_prefix(ctx); } - char **argv = ZALLOC_ARRAY(char *, ctx->argc); + dchar **argv = ZALLOC_ARRAY(dchar *, ctx->argc); if (!argv) { return; } diff --git a/src/dstring.c b/src/dstring.c index 60a7df9..ef4e733 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -4,6 +4,7 @@ #include "dstring.h" #include "alloc.h" #include "bit.h" +#include "config.h" #include "diag.h" #include #include @@ -16,11 +17,11 @@ struct dstring { size_t capacity; size_t length; - char data[]; + alignas(dchar) char data[]; }; /** Get the string header from the string data pointer. */ -static struct dstring *dstrheader(const char *dstr) { +static struct dstring *dstrheader(const dchar *dstr) { return (struct dstring *)(dstr - offsetof(struct dstring, data)); } @@ -30,7 +31,7 @@ static size_t dstrsize(size_t capacity) { } /** Allocate a dstring with the given contents. */ -static char *dstralloc_impl(size_t capacity, size_t length, const char *data) { +static dchar *dstralloc_impl(size_t capacity, size_t length, const char *data) { // Avoid reallocations for small strings if (capacity < 7) { capacity = 7; @@ -49,31 +50,31 @@ static char *dstralloc_impl(size_t capacity, size_t length, const char *data) { return header->data; } -char *dstralloc(size_t capacity) { +dchar *dstralloc(size_t capacity) { return dstralloc_impl(capacity, 0, ""); } -char *dstrdup(const char *str) { +dchar *dstrdup(const char *str) { return dstrxdup(str, strlen(str)); } -char *dstrndup(const char *str, size_t n) { +dchar *dstrndup(const char *str, size_t n) { return dstrxdup(str, strnlen(str, n)); } -char *dstrddup(const char *dstr) { +dchar *dstrddup(const dchar *dstr) { return dstrxdup(dstr, dstrlen(dstr)); } -char *dstrxdup(const char *str, size_t len) { +dchar *dstrxdup(const char *str, size_t len) { return dstralloc_impl(len, len, str); } -size_t dstrlen(const char *dstr) { +size_t dstrlen(const dchar *dstr) { return dstrheader(dstr)->length; } -int dstreserve(char **dstr, size_t capacity) { +int dstreserve(dchar **dstr, size_t capacity) { if (!*dstr) { *dstr = dstralloc(capacity); return *dstr ? 0 : -1; @@ -96,7 +97,7 @@ int dstreserve(char **dstr, size_t capacity) { return 0; } -int dstresize(char **dstr, size_t length) { +int dstresize(dchar **dstr, size_t length) { if (dstreserve(dstr, length) != 0) { return -1; } @@ -107,19 +108,19 @@ int dstresize(char **dstr, size_t length) { return 0; } -int dstrcat(char **dest, const char *src) { +int dstrcat(dchar **dest, const char *src) { return dstrxcat(dest, src, strlen(src)); } -int dstrncat(char **dest, const char *src, size_t n) { +int dstrncat(dchar **dest, const char *src, size_t n) { return dstrxcat(dest, src, strnlen(src, n)); } -int dstrdcat(char **dest, const char *src) { +int dstrdcat(dchar **dest, const dchar *src) { return dstrxcat(dest, src, dstrlen(src)); } -int dstrxcat(char **dest, const char *src, size_t len) { +int dstrxcat(dchar **dest, const char *src, size_t len) { size_t oldlen = dstrlen(*dest); size_t newlen = oldlen + len; @@ -131,23 +132,23 @@ int dstrxcat(char **dest, const char *src, size_t len) { return 0; } -int dstrapp(char **str, char c) { +int dstrapp(dchar **str, char c) { return dstrxcat(str, &c, 1); } -int dstrcpy(char **dest, const char *src) { +int dstrcpy(dchar **dest, const char *src) { return dstrxcpy(dest, src, strlen(src)); } -int dstrncpy(char **dest, const char *src, size_t n) { +int dstrncpy(dchar **dest, const char *src, size_t n) { return dstrxcpy(dest, src, strnlen(src, n)); } -int dstrdcpy(char **dest, const char *src) { +int dstrdcpy(dchar **dest, const dchar *src) { return dstrxcpy(dest, src, dstrlen(src)); } -int dstrxcpy(char **dest, const char *src, size_t len) { +int dstrxcpy(dchar **dest, const char *src, size_t len) { if (dstresize(dest, len) != 0) { return -1; } @@ -160,7 +161,7 @@ char *dstrprintf(const char *format, ...) { va_list args; va_start(args, format); - char *str = dstrvprintf(format, args); + dchar *str = dstrvprintf(format, args); va_end(args); return str; @@ -168,7 +169,7 @@ char *dstrprintf(const char *format, ...) { char *dstrvprintf(const char *format, va_list args) { // Guess a capacity to try to avoid reallocating - char *str = dstralloc(2*strlen(format)); + dchar *str = dstralloc(2*strlen(format)); if (!str) { return NULL; } @@ -181,7 +182,7 @@ char *dstrvprintf(const char *format, va_list args) { return str; } -int dstrcatf(char **str, const char *format, ...) { +int dstrcatf(dchar **str, const char *format, ...) { va_list args; va_start(args, format); @@ -191,7 +192,7 @@ int dstrcatf(char **str, const char *format, ...) { return ret; } -int dstrvcatf(char **str, const char *format, va_list args) { +int dstrvcatf(dchar **str, const char *format, va_list args) { // Guess a capacity to try to avoid calling vsnprintf() twice size_t len = dstrlen(*str); dstreserve(str, len + 2*strlen(format)); @@ -232,11 +233,11 @@ fail: return -1; } -int dstrescat(char **dest, const char *str, enum wesc_flags flags) { +int dstrescat(dchar **dest, const char *str, enum wesc_flags flags) { return dstrnescat(dest, str, SIZE_MAX, flags); } -int dstrnescat(char **dest, const char *str, size_t n, enum wesc_flags flags) { +int dstrnescat(dchar **dest, const char *str, size_t n, enum wesc_flags flags) { size_t len = *dest ? dstrlen(*dest) : 0; // Worst case growth is `ccc...` => $'\xCC\xCC\xCC...' @@ -254,7 +255,7 @@ int dstrnescat(char **dest, const char *str, size_t n, enum wesc_flags flags) { return dstresize(dest, cur - *dest); } -void dstrfree(char *dstr) { +void dstrfree(dchar *dstr) { if (dstr) { free(dstrheader(dstr)); } diff --git a/src/dstring.h b/src/dstring.h index 88ca79f..91a600c 100644 --- a/src/dstring.h +++ b/src/dstring.h @@ -13,13 +13,32 @@ #include #include +/** Marker type for dynamic strings. */ +#if __clang__ +// Abuse __attribute__(aligned) to make a type that allows +// +// dchar * -> char * +// +// conversions, but warns on +// +// char * -> dchar * +// +// (with Clang's -Walign-mismatch). The alignment is not a lie, due to the +// layout of struct dstring, but we only enable this on Clang because GCC +// tracks alignment through array accesses, reporting UBSan errors on (and +// maybe even miscompiling) dstr[1]. +typedef __attribute__((aligned(alignof(size_t)))) char dchar; +#else +typedef char dchar; +#endif + /** * Allocate a dynamic string. * * @param capacity * The initial capacity of the string. */ -char *dstralloc(size_t capacity); +dchar *dstralloc(size_t capacity); /** * Create a dynamic copy of a string. @@ -27,7 +46,7 @@ char *dstralloc(size_t capacity); * @param str * The NUL-terminated string to copy. */ -char *dstrdup(const char *str); +dchar *dstrdup(const char *str); /** * Create a length-limited dynamic copy of a string. @@ -37,7 +56,7 @@ char *dstrdup(const char *str); * @param n * The maximum number of characters to copy from str. */ -char *dstrndup(const char *str, size_t n); +dchar *dstrndup(const char *str, size_t n); /** * Create a dynamic copy of a dynamic string. @@ -45,7 +64,7 @@ char *dstrndup(const char *str, size_t n); * @param dstr * The dynamic string to copy. */ -char *dstrddup(const char *dstr); +dchar *dstrddup(const dchar *dstr); /** * Create an exact-sized dynamic copy of a string. @@ -55,7 +74,7 @@ char *dstrddup(const char *dstr); * @param len * The length of the string, which may include internal NUL bytes. */ -char *dstrxdup(const char *str, size_t len); +dchar *dstrxdup(const char *str, size_t len); /** * Get a dynamic string's length. @@ -65,7 +84,7 @@ char *dstrxdup(const char *str, size_t len); * @return * The length of dstr. */ -size_t dstrlen(const char *dstr); +size_t dstrlen(const dchar *dstr); /** * Reserve some capacity in a dynamic string. @@ -77,7 +96,7 @@ size_t dstrlen(const char *dstr); * @return * 0 on success, -1 on failure. */ -int dstreserve(char **dstr, size_t capacity); +int dstreserve(dchar **dstr, size_t capacity); /** * Resize a dynamic string. @@ -89,7 +108,7 @@ int dstreserve(char **dstr, size_t capacity); * @return * 0 on success, -1 on failure. */ -int dstresize(char **dstr, size_t length); +int dstresize(dchar **dstr, size_t length); /** * Append to a dynamic string. @@ -100,7 +119,7 @@ int dstresize(char **dstr, size_t length); * The string to append. * @return 0 on success, -1 on failure. */ -int dstrcat(char **dest, const char *src); +int dstrcat(dchar **dest, const char *src); /** * Append to a dynamic string. @@ -114,7 +133,7 @@ int dstrcat(char **dest, const char *src); * @return * 0 on success, -1 on failure. */ -int dstrncat(char **dest, const char *src, size_t n); +int dstrncat(dchar **dest, const char *src, size_t n); /** * Append a dynamic string to another dynamic string. @@ -126,7 +145,7 @@ int dstrncat(char **dest, const char *src, size_t n); * @return * 0 on success, -1 on failure. */ -int dstrdcat(char **dest, const char *src); +int dstrdcat(dchar **dest, const dchar *src); /** * Append to a dynamic string. @@ -140,7 +159,7 @@ int dstrdcat(char **dest, const char *src); * @return * 0 on success, -1 on failure. */ -int dstrxcat(char **dest, const char *src, size_t len); +int dstrxcat(dchar **dest, const char *src, size_t len); /** * Append a single character to a dynamic string. @@ -152,7 +171,7 @@ int dstrxcat(char **dest, const char *src, size_t len); * @return * 0 on success, -1 on failure. */ -int dstrapp(char **str, char c); +int dstrapp(dchar **str, char c); /** * Copy a string into a dynamic string. @@ -164,7 +183,7 @@ int dstrapp(char **str, char c); * @returns * 0 on success, -1 on failure. */ -int dstrcpy(char **dest, const char *str); +int dstrcpy(dchar **dest, const char *str); /** * Copy a dynamic string into another one. @@ -176,7 +195,7 @@ int dstrcpy(char **dest, const char *str); * @returns * 0 on success, -1 on failure. */ -int dstrdcpy(char **dest, const char *str); +int dstrdcpy(dchar **dest, const dchar *str); /** * Copy a string into a dynamic string. @@ -190,7 +209,7 @@ int dstrdcpy(char **dest, const char *str); * @returns * 0 on success, -1 on failure. */ -int dstrncpy(char **dest, const char *str, size_t n); +int dstrncpy(dchar **dest, const char *str, size_t n); /** * Copy a string into a dynamic string. @@ -204,7 +223,7 @@ int dstrncpy(char **dest, const char *str, size_t n); * @returns * 0 on success, -1 on failure. */ -int dstrxcpy(char **dest, const char *str, size_t len); +int dstrxcpy(dchar **dest, const char *str, size_t len); /** * Create a dynamic string from a format string. @@ -245,7 +264,7 @@ char *dstrvprintf(const char *format, va_list args); * 0 on success, -1 on failure. */ BFS_FORMATTER(2, 3) -int dstrcatf(char **str, const char *format, ...); +int dstrcatf(dchar **str, const char *format, ...); /** * Format some text from a va_list onto the end of a dynamic string. @@ -260,7 +279,7 @@ int dstrcatf(char **str, const char *format, ...); * 0 on success, -1 on failure. */ BFS_FORMATTER(2, 0) -int dstrvcatf(char **str, const char *format, va_list args); +int dstrvcatf(dchar **str, const char *format, va_list args); /** * Concatenate while shell-escaping. @@ -274,7 +293,7 @@ int dstrvcatf(char **str, const char *format, va_list args); * @return * 0 on success, -1 on failure. */ -int dstrescat(char **dest, const char *str, enum wesc_flags flags); +int dstrescat(dchar **dest, const char *str, enum wesc_flags flags); /** * Concatenate while shell-escaping. @@ -290,7 +309,7 @@ int dstrescat(char **dest, const char *str, enum wesc_flags flags); * @return * 0 on success, -1 on failure. */ -int dstrnescat(char **dest, const char *str, size_t n, enum wesc_flags flags); +int dstrnescat(dchar **dest, const char *str, size_t n, enum wesc_flags flags); /** * Free a dynamic string. @@ -298,6 +317,6 @@ int dstrnescat(char **dest, const char *str, size_t n, enum wesc_flags flags); * @param dstr * The string to free. */ -void dstrfree(char *dstr); +void dstrfree(dchar *dstr); #endif // BFS_DSTRING_H diff --git a/src/eval.c b/src/eval.c index 0faf533..3550751 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1115,7 +1115,7 @@ static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct time const struct BFTW *ftwbuf = state->ftwbuf; - char *rhs = dstrprintf(" (visited: %zu, depth: %2zu)", count, ftwbuf->depth); + dchar *rhs = dstrprintf(" (visited: %zu, depth: %2zu)", count, ftwbuf->depth); if (!rhs) { return; } @@ -1126,7 +1126,7 @@ static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct time rhslen = 0; } - char *status = dstralloc(0); + dchar *status = dstralloc(0); if (!status) { goto out_rhs; } diff --git a/src/exec.c b/src/exec.c index ea7f897..7b55522 100644 --- a/src/exec.c +++ b/src/exec.c @@ -227,7 +227,7 @@ static char *bfs_exec_format_arg(char *arg, const char *path) { return arg; } - char *ret = dstralloc(0); + dchar *ret = dstralloc(0); if (!ret) { return NULL; } @@ -259,7 +259,7 @@ err: /** Free a formatted argument. */ static void bfs_exec_free_arg(char *arg, const char *tmpl) { if (arg != tmpl) { - dstrfree(arg); + dstrfree((dchar *)arg); } } diff --git a/src/fsade.c b/src/fsade.c index c401426..8dec5a8 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -37,7 +37,7 @@ static const char *fake_at(const struct BFTW *ftwbuf) { static atomic int proc_works = -1; - char *path = NULL; + dchar *path = NULL; if (ftwbuf->at_fd == AT_FDCWD || load(&proc_works, relaxed) == 0) { goto fail; } @@ -69,7 +69,7 @@ fail: static void free_fake_at(const struct BFTW *ftwbuf, const char *path) { if (path != ftwbuf->path) { - dstrfree((char *)path); + dstrfree((dchar *)path); } } diff --git a/src/printf.c b/src/printf.c index f0910fa..5de5a28 100644 --- a/src/printf.c +++ b/src/printf.c @@ -38,7 +38,7 @@ struct bfs_printf { /** The printing function to invoke. */ bfs_printf_fn *fn; /** String data associated with this directive. */ - char *str; + dchar *str; /** The stat field to print. */ enum bfs_stat_field stat_field; /** Character data associated with this directive. */ @@ -596,7 +596,7 @@ static int append_directive(const struct bfs_ctx *ctx, struct bfs_printf **forma int bfs_printf_parse(const struct bfs_ctx *ctx, struct bfs_expr *expr, const char *format) { expr->printf = NULL; - char *literal = dstralloc(0); + dchar *literal = dstralloc(0); if (!literal) { bfs_perror(ctx, "dstralloc()"); goto error; -- cgit v1.2.3 From 1afa241472709b32baf5e3e1fd3ba6ebd5fd1bf6 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 11 Jul 2023 14:04:40 -0400 Subject: ioq: Use io_uring Closes #65. --- src/bftw.c | 23 ++++- src/ioq.c | 307 +++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 288 insertions(+), 42 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 5e5f4a5..902a3fa 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -470,21 +470,34 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg state->error = 0; - if (args->nopenfd < 1) { + if (args->nopenfd < 2) { errno = EMFILE; return -1; } - bftw_cache_init(&state->cache, args->nopenfd); - state->nthreads = args->nthreads; - if (state->nthreads > 0) { - state->ioq = ioq_create(4096, state->nthreads); + size_t nopenfd = args->nopenfd; + size_t qdepth = 4096; + size_t nthreads = args->nthreads; + +#if BFS_USE_LIBURING + // io_uring uses one fd per ring, ioq uses one ring per thread + if (nthreads >= nopenfd - 1) { + nthreads = nopenfd - 2; + } + nopenfd -= nthreads; +#endif + + bftw_cache_init(&state->cache, nopenfd); + + if (nthreads > 0) { + state->ioq = ioq_create(qdepth, nthreads); if (!state->ioq) { return -1; } } else { state->ioq = NULL; } + state->nthreads = nthreads; SLIST_INIT(&state->to_open); SLIST_INIT(&state->to_read); diff --git a/src/ioq.c b/src/ioq.c index d3ba2de..04b9c0d 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -16,6 +16,10 @@ #include #include +#if BFS_USE_LIBURING +# include +#endif + /** * A monitor for an I/O queue slot. */ @@ -280,6 +284,21 @@ static struct ioq_ent *ioqq_trypop(struct ioqq *ioqq) { /** Sentinel stop command. */ static struct ioq_ent IOQ_STOP; +/** I/O queue thread-specific data. */ +struct ioq_thread { + /** The thread handle. */ + pthread_t id; + /** Pointer back to the I/O queue. */ + struct ioq *parent; + +#if BFS_USE_LIBURING + /** io_uring instance. */ + struct io_uring ring; + /** Any error that occurred initializing the ring. */ + int ring_err; +#endif +}; + struct ioq { /** The depth of the queue. */ size_t depth; @@ -299,60 +318,247 @@ struct ioq { /** The number of background threads. */ size_t nthreads; /** The background threads themselves. */ - pthread_t threads[]; + struct ioq_thread threads[]; }; -/** Background thread entry point. */ -static void *ioq_work(void *ptr) { - struct ioq *ioq = ptr; +/** Cancel a request if we need to. */ +static bool ioq_check_cancel(struct ioq *ioq, struct ioq_ent *ent) { + if (!load(&ioq->cancel, relaxed)) { + return false; + } - while (true) { - struct ioq_ent *ent = ioqq_pop(ioq->pending); - if (ent == &IOQ_STOP) { - break; + // Always close(), even if we're cancelled, just like a real EINTR + if (ent->op == IOQ_CLOSE || ent->op == IOQ_CLOSEDIR) { + return false; + } + + ent->ret = -1; + ent->error = EINTR; + ioqq_push(ioq->ready, ent); + return true; +} + +/** Handle a single request synchronously. */ +static void ioq_handle(struct ioq *ioq, struct ioq_ent *ent) { + int ret; + + switch (ent->op) { + case IOQ_CLOSE: + ret = xclose(ent->close.fd); + break; + + case IOQ_OPENDIR: + ret = bfs_opendir(ent->opendir.dir, ent->opendir.dfd, ent->opendir.path); + if (ret == 0) { + bfs_polldir(ent->opendir.dir); } + break; + + case IOQ_CLOSEDIR: + ret = bfs_closedir(ent->closedir.dir); + break; + + default: + bfs_bug("Unknown ioq_op %d", (int)ent->op); + ret = -1; + errno = ENOSYS; + break; + } + + ent->ret = ret; + ent->error = ret == 0 ? 0 : errno; + + ioqq_push(ioq->ready, ent); +} - bool cancel = load(&ioq->cancel, relaxed); +#if BFS_USE_LIBURING +/** io_uring worker state. */ +struct ioq_ring_state { + /** The I/O queue. */ + struct ioq *ioq; + /** The io_uring. */ + struct io_uring *ring; + /** The current ioq->pending slot. */ + ioq_slot *slot; + /** Number of prepped, unsubmitted SQEs. */ + size_t prepped; + /** Number of submitted, unreaped SQEs. */ + size_t submitted; + /** Whether to stop the loop. */ + bool stop; +}; + +/** Pop a request for ioq_ring_prep(). */ +static struct ioq_ent *ioq_ring_pop(struct ioq_ring_state *state) { + if (state->stop) { + return NULL; + } + + // Advance to the next slot if necessary + struct ioq *ioq = state->ioq; + if (!state->slot) { + state->slot = ioqq_read(ioq->pending); + } + + // Block if we have nothing else to do + bool block = !state->prepped && !state->submitted; + struct ioq_ent *ret = ioq_slot_pop(ioq->pending, state->slot, block); + + if (ret) { + // Got an entry, move to the next slot next time + state->slot = NULL; + } + + if (ret == &IOQ_STOP) { + state->stop = true; + ret = NULL; + } + + return ret; +} - ent->ret = -1; +/** Prep a single SQE. */ +static void ioq_prep_sqe(struct io_uring_sqe *sqe, struct ioq_ent *ent) { + switch (ent->op) { + case IOQ_CLOSE: + io_uring_prep_close(sqe, ent->close.fd); + break; + + case IOQ_OPENDIR: + io_uring_prep_openat(sqe, ent->opendir.dfd, ent->opendir.path, O_RDONLY | O_CLOEXEC | O_DIRECTORY, 0); + break; + +#if BFS_USE_UNWRAPDIR + case IOQ_CLOSEDIR: + io_uring_prep_close(sqe, bfs_unwrapdir(ent->closedir.dir)); + break; +#endif + + default: + bfs_bug("Unknown ioq_op %d", (int)ent->op); + io_uring_prep_nop(sqe); + break; + } - switch (ent->op) { - case IOQ_CLOSE: - // Always close(), even if we're cancelled, just like a real EINTR - ent->ret = xclose(ent->close.fd); + io_uring_sqe_set_data(sqe, ent); +} + +/** Prep a batch of SQEs. */ +static bool ioq_ring_prep(struct ioq_ring_state *state) { + struct ioq *ioq = state->ioq; + struct io_uring *ring = state->ring; + + while (io_uring_sq_space_left(ring)) { + struct ioq_ent *ent = ioq_ring_pop(state); + if (!ent) { break; + } + + if (ioq_check_cancel(ioq, ent)) { + continue; + } - case IOQ_OPENDIR: - if (!cancel) { - struct ioq_opendir *args = &ent->opendir; - ent->ret = bfs_opendir(args->dir, args->dfd, args->path); - if (ent->ret == 0) { - bfs_polldir(args->dir); - } +#if !BFS_USE_UNWRAPDIR + if (ent->op == IOQ_CLOSEDIR) { + ioq_handle(ioq, ent); + continue; + } +#endif + + struct io_uring_sqe *sqe = io_uring_get_sqe(ring); + ioq_prep_sqe(sqe, ent); + ++state->prepped; + } + + return state->prepped || state->submitted; +} + +/** Reap a batch of SQEs. */ +static void ioq_ring_reap(struct ioq_ring_state *state) { + struct ioq *ioq = state->ioq; + struct io_uring *ring = state->ring; + + while (state->prepped) { + int ret = io_uring_submit_and_wait(ring, 1); + if (ret > 0) { + state->prepped -= ret; + state->submitted += ret; + } + } + + while (state->submitted) { + struct io_uring_cqe *cqe; + if (io_uring_wait_cqe(ring, &cqe) < 0) { + continue; + } + + struct ioq_ent *ent = io_uring_cqe_get_data(cqe); + ent->ret = cqe->res >= 0 ? cqe->res : -1; + ent->error = cqe->res < 0 ? -cqe->res : 0; + io_uring_cqe_seen(ring, cqe); + --state->submitted; + + if (ent->op == IOQ_OPENDIR && ent->ret >= 0) { + int fd = ent->ret; + if (ioq_check_cancel(ioq, ent)) { + xclose(fd); + continue; } - break; - case IOQ_CLOSEDIR: - ent->ret = bfs_closedir(ent->closedir.dir); - break; + ent->ret = bfs_opendir(ent->opendir.dir, fd, NULL); + if (ent->ret == 0) { + // TODO: io_uring_prep_getdents() + bfs_polldir(ent->opendir.dir); + } else { + ent->error = errno; + } + } + + ioqq_push(ioq->ready, ent); + } +} + +/** io_uring worker loop. */ +static void ioq_ring_work(struct ioq_thread *thread) { + struct ioq_ring_state state = { + .ioq = thread->parent, + .ring = &thread->ring, + }; - default: - bfs_bug("Unknown ioq_op %d", (int)ent->op); - errno = ENOSYS; + while (ioq_ring_prep(&state)) { + ioq_ring_reap(&state); + } +} +#endif + +/** Synchronous syscall loop. */ +static void ioq_sync_work(struct ioq_thread *thread) { + struct ioq *ioq = thread->parent; + + while (true) { + struct ioq_ent *ent = ioqq_pop(ioq->pending); + if (ent == &IOQ_STOP) { break; } - if (cancel) { - ent->error = EINTR; - } else if (ent->ret < 0) { - ent->error = errno; - } else { - ent->error = 0; + if (!ioq_check_cancel(ioq, ent)) { + ioq_handle(ioq, ent); } + } +} - ioqq_push(ioq->ready, ent); +/** Background thread entry point. */ +static void *ioq_work(void *ptr) { + struct ioq_thread *thread = ptr; + +#if BFS_USE_LIBURING + if (thread->ring_err == 0) { + ioq_ring_work(thread); + return NULL; } +#endif + ioq_sync_work(thread); return NULL; } @@ -376,7 +582,30 @@ struct ioq *ioq_create(size_t depth, size_t nthreads) { } for (size_t i = 0; i < nthreads; ++i) { - if (thread_create(&ioq->threads[i], NULL, ioq_work, ioq) != 0) { + struct ioq_thread *thread = &ioq->threads[i]; + thread->parent = ioq; + +#if BFS_USE_LIBURING + struct ioq_thread *prev = i ? &ioq->threads[i - 1] : NULL; + if (prev && prev->ring_err) { + thread->ring_err = prev->ring_err; + } else { + // Share io-wq workers between rings + struct io_uring_params params = {0}; + if (prev) { + params.flags |= IORING_SETUP_ATTACH_WQ; + params.wq_fd = prev->ring.ring_fd; + } + + size_t entries = depth / nthreads; + if (entries < 16) { + entries = 16; + } + thread->ring_err = -io_uring_queue_init_params(entries, &thread->ring, ¶ms); + } +#endif + + if (thread_create(&thread->id, NULL, ioq_work, thread) != 0) { goto fail; } ++ioq->nthreads; @@ -496,7 +725,11 @@ void ioq_destroy(struct ioq *ioq) { ioq_cancel(ioq); for (size_t i = 0; i < ioq->nthreads; ++i) { - thread_join(ioq->threads[i], NULL); + struct ioq_thread *thread = &ioq->threads[i]; + thread_join(thread->id, NULL); +#if BFS_USE_LIBURING + io_uring_queue_exit(&thread->ring); +#endif } ioqq_destroy(ioq->ready); -- cgit v1.2.3 From ed2a50d63b25a7b72a32be07a33331544f587296 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 09:44:27 -0400 Subject: bftw: Let iterative deepening work depth-first when sorting --- src/bftw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 902a3fa..c58001d 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -862,7 +862,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { if (state->flags & BFTW_SORT) { // Keep strict breadth-first order when sorting - if (state->strategy != BFTW_DFS && have_files) { + if (state->strategy == BFTW_BFS && have_files) { return false; } } else { -- cgit v1.2.3 From a029d95b5736a74879f32089514a5a6b63d6efbc Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 11:24:49 -0400 Subject: bftw: Fix unbuffered depth-first searches bftw() implements depth-first search by appending files to a batch, then prepending the batch to the queue. When we switched to separate file/ directory queues, this was only implemented for the file queue. Unbuffered searches don't use the file queue, so they were all breadth- first in practice. This meant that iterative deepening (-S ids) was actually "iterative deepening *breadth*-first search," a horrible strategy with no advantage over regular breadth-first search. Now it performs iterative deepening *depth*-first search, which at least limits its memory consumption. Fixes: c1b16b49988ecff17ae30978ea14798d95b80018 --- src/bftw.c | 56 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 15 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index c58001d..6eec42c 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -422,6 +422,8 @@ struct bftw_state { /** The number of I/O threads. */ size_t nthreads; + /** A batch of directories to open. */ + struct bftw_list dir_batch; /** The queue of directories to open. */ struct bftw_list to_open; /** The queue of directories to read. */ @@ -429,10 +431,10 @@ struct bftw_state { /** The queue of unpinned directories to unwrap. */ struct bftw_list to_close; + /** A batch of files to enqueue. */ + struct bftw_list file_batch; /** The queue of files to visit. */ struct bftw_list to_visit; - /** A batch of files to enqueue. */ - struct bftw_list batch; /** The current path. */ dchar *path; @@ -499,12 +501,14 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg } state->nthreads = nthreads; + + SLIST_INIT(&state->dir_batch); SLIST_INIT(&state->to_open); SLIST_INIT(&state->to_read); SLIST_INIT(&state->to_close); + SLIST_INIT(&state->file_batch); SLIST_INIT(&state->to_visit); - SLIST_INIT(&state->batch); state->path = NULL; state->file = NULL; @@ -833,11 +837,32 @@ fail: return -1; } +/** Open a batch of directories asynchronously. */ +static void bftw_ioq_opendirs(struct bftw_state *state, struct bftw_list *queue) { + for_slist (struct bftw_file, dir, queue) { + if (bftw_ioq_opendir(state, dir) != 0) { + break; + } + SLIST_POP(queue); + } +} + /** Push a directory onto the queue. */ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { bfs_assert(file->type == BFS_DIR); - SLIST_APPEND(&state->to_open, file); + struct bftw_list *queue; + if (state->strategy == BFTW_BFS || (state->flags & BFTW_BUFFER)) { + // In breadth-first mode, or if we're already buffering files, + // we can push directly to the to_open queue + queue = &state->to_open; + } else { + // For a depth-first, unbuffered search, add directories to a + // batch, then push the patch to the front of the queue + queue = &state->dir_batch; + } + + SLIST_APPEND(queue, file); if (state->flags & BFTW_SORT) { // When sorting, directories are kept in order on the to_read @@ -845,12 +870,7 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { SLIST_APPEND(&state->to_read, file, to_read); } - for_slist (struct bftw_file, dir, &state->to_open) { - if (bftw_ioq_opendir(state, dir) != 0) { - break; - } - SLIST_POP(&state->to_open); - } + bftw_ioq_opendirs(state, queue); } /** Pop a directory to read from the queue. */ @@ -1333,13 +1353,18 @@ static void bftw_list_sort(struct bftw_list *list) { /** Finish adding a batch of files. */ static void bftw_batch_finish(struct bftw_state *state) { if (state->flags & BFTW_SORT) { - bftw_list_sort(&state->batch); + bftw_list_sort(&state->file_batch); } if (state->strategy != BFTW_BFS) { - SLIST_EXTEND(&state->batch, &state->to_visit); + SLIST_EXTEND(&state->dir_batch, &state->to_open); + SLIST_EXTEND(&state->file_batch, &state->to_visit); } - SLIST_EXTEND(&state->to_visit, &state->batch); + + SLIST_EXTEND(&state->to_open, &state->dir_batch); + SLIST_EXTEND(&state->to_visit, &state->file_batch); + + bftw_ioq_opendirs(state, &state->to_open); } /** Close the current directory. */ @@ -1381,7 +1406,7 @@ static int bftw_visit(struct bftw_state *state, const char *name) { file->type = state->de->type; } - SLIST_APPEND(&state->batch, file); + SLIST_APPEND(&state->file_batch, file); return 0; } @@ -1429,7 +1454,8 @@ static int bftw_state_destroy(struct bftw_state *state) { state->ioq = NULL; } - SLIST_EXTEND(&state->to_visit, &state->batch); + SLIST_EXTEND(&state->to_open, &state->dir_batch); + SLIST_EXTEND(&state->to_visit, &state->file_batch); do { bftw_gc(state, BFTW_VISIT_NONE); } while (bftw_pop_dir(state) || bftw_pop_file(state)); -- cgit v1.2.3 From 257227326fe60fe70e80433fd34d1ebcb2f9f623 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 10:41:04 -0400 Subject: bftw: Don't force buffering for parallel dfs --- src/bftw.c | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 6eec42c..c0bff75 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -456,6 +456,33 @@ struct bftw_state { struct BFTW ftwbuf; }; +/** Check if we have to buffer files before visiting them. */ +static bool bftw_must_buffer(const struct bftw_state *state) { + if (state->flags & BFTW_SORT) { + // Have to buffer the files to sort them + return true; + } + + if (state->strategy == BFTW_DFS && state->nthreads == 0) { + // Without buffering, we would get a not-quite-depth-first + // ordering: + // + // a + // b + // a/c + // a/c/d + // b/e + // b/e/f + // + // This is okay for iterative deepening, since the caller only + // sees files at the target depth. We also deem it okay for + // parallel searches, since the order is unpredictable anyway. + return true; + } + + return false; +} + /** Initialize the bftw() state. */ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *args) { state->paths = args->paths; @@ -465,11 +492,6 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg state->flags = args->flags; state->strategy = args->strategy; state->mtab = args->mtab; - - if ((state->flags & BFTW_SORT) || state->strategy == BFTW_DFS) { - state->flags |= BFTW_BUFFER; - } - state->error = 0; if (args->nopenfd < 2) { @@ -501,6 +523,9 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg } state->nthreads = nthreads; + if (bftw_must_buffer(state)) { + state->flags |= BFTW_BUFFER; + } SLIST_INIT(&state->dir_batch); SLIST_INIT(&state->to_open); -- cgit v1.2.3 From 773f4a446f03da62d88e6d17be49fdc0a3e38465 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 15:13:02 -0400 Subject: bftw: Fix to_close list corruption with !BFS_USE_UNWRAPDIR It's possible for pincount to drop to zero, then get incremented and drop back to zero again. If this happens, we shouldn't add it to the to_close list twice. This should fix the intermittent hang on the macOS CI. Fixes: 815798e1eea7fc8dacd5acab40202ec4d251d517 --- src/bftw.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index c0bff75..1995e12 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -546,6 +546,17 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg return 0; } +/** Unpin a directory, and possibly queue it for unwrapping. */ +static void bftw_unpin_dir(struct bftw_state *state, struct bftw_file *file, bool force) { + bftw_cache_unpin(&state->cache, file); + + if (file->dir && (force || file->pincount == 0)) { + if (!SLIST_ATTACHED(&state->to_close, file)) { + SLIST_APPEND(&state->to_close, file); + } + } +} + /** Pop a response from the I/O queue. */ static int bftw_ioq_pop(struct bftw_state *state, bool block) { struct ioq *ioq = state->ioq; @@ -582,10 +593,7 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { ++cache->capacity; parent = file->parent; if (parent) { - bftw_cache_unpin(cache, parent); - if (parent->pincount == 0 && parent->dir) { - SLIST_APPEND(&state->to_close, parent); - } + bftw_unpin_dir(state, parent, false); } dir = ent->opendir.dir; @@ -1285,8 +1293,7 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { struct bftw_file *file = state->file; if (file && file->dir) { - bftw_cache_unpin(&state->cache, file); - SLIST_APPEND(&state->to_close, file); + bftw_unpin_dir(state, file, true); } state->dir = NULL; state->de = NULL; -- cgit v1.2.3 From d64a32e03b4d9a1336ebfa66d7d08d96e9847a6a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 15:50:59 -0400 Subject: bftw: Make sure we don't close a directory while we unwrap it bftw_cache_reserve() can lead to bftw_cache_pop(), which could close the directory we're trying to unwrap! If that happened, we would try dup_cloexec(-1), which would fail with EBADF, so there was no observable bug. But it's better to avoid the whole situation. --- src/bftw.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 1995e12..06a0085 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -811,8 +811,12 @@ static int bftw_unwrapdir(struct bftw_state *state, struct bftw_file *file) { return bftw_close(state, file); } - if (bftw_cache_reserve(state) != 0) { - return -1; + // Make room for dup() + bftw_cache_pin(cache, file); + int ret = bftw_cache_reserve(state); + bftw_cache_unpin(cache, file); + if (ret != 0) { + return ret; } int fd = dup_cloexec(file->fd); -- cgit v1.2.3 From 214a1f9215d33d4b9f34a3d258da1e1f4e3eb01f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 16 Oct 2023 16:44:46 -0400 Subject: dir: Add a flags parameter to bfs_opendir() --- src/bftw.c | 4 ++-- src/dir.c | 31 ++++++++++++++++++++----------- src/dir.h | 12 +++++++++++- src/eval.c | 6 +++--- src/ioq.c | 7 ++++--- src/ioq.h | 6 +++++- 6 files changed, 45 insertions(+), 21 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 06a0085..e2f1a66 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -856,7 +856,7 @@ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { goto unpin; } - if (ioq_opendir(state->ioq, dir, dfd, file->name, file) != 0) { + if (ioq_opendir(state->ioq, dir, dfd, file->name, 0, file) != 0) { goto free; } @@ -1018,7 +1018,7 @@ static struct bfs_dir *bftw_file_opendir(struct bftw_state *state, struct bftw_f return NULL; } - if (bfs_opendir(dir, fd, NULL) != 0) { + if (bfs_opendir(dir, fd, NULL, 0) != 0) { bftw_freedir(cache, dir); return NULL; } diff --git a/src/dir.c b/src/dir.c index a7423e9..dee02e5 100644 --- a/src/dir.c +++ b/src/dir.c @@ -96,18 +96,26 @@ enum bfs_type bfs_mode_to_type(mode_t mode) { } } +/** + * Private directory flags. + */ +enum { + /** We've reached the end of the directory. */ + BFS_DIR_EOF = BFS_DIR_PRIVATE << 0, +}; + struct bfs_dir { + unsigned int flags; + #if BFS_USE_GETDENTS - alignas(sys_dirent) int fd; + int fd; unsigned short pos; unsigned short size; - // sys_dirent buf[]; + alignas(sys_dirent) char buf[]; #else DIR *dir; struct dirent *de; #endif - - bool eof; }; #if BFS_USE_GETDENTS @@ -125,7 +133,7 @@ void bfs_dir_arena(struct arena *arena) { arena_init(arena, alignof(struct bfs_dir), DIR_SIZE); } -int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path) { +int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path, enum bfs_dir_flags flags) { int fd; if (at_path) { fd = openat(at_fd, at_path, O_RDONLY | O_CLOEXEC | O_DIRECTORY); @@ -139,6 +147,8 @@ int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path) { return -1; } + dir->flags = flags; + #if BFS_USE_GETDENTS dir->fd = fd; dir->pos = 0; @@ -154,7 +164,6 @@ int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path) { dir->de = NULL; #endif - dir->eof = false; return 0; } @@ -170,14 +179,14 @@ int bfs_polldir(struct bfs_dir *dir) { #if BFS_USE_GETDENTS if (dir->pos < dir->size) { return 1; - } else if (dir->eof) { + } else if (dir->flags & BFS_DIR_EOF) { return 0; } char *buf = (char *)(dir + 1); ssize_t size = bfs_getdents(dir->fd, buf, BUF_SIZE); if (size == 0) { - dir->eof = true; + dir->flags |= BFS_DIR_EOF; return 0; } else if (size < 0) { return -1; @@ -194,7 +203,7 @@ int bfs_polldir(struct bfs_dir *dir) { if (size > 0) { dir->size += size; } else if (size == 0) { - dir->eof = true; + dir->flags |= BFS_DIR_EOF; } } @@ -202,7 +211,7 @@ int bfs_polldir(struct bfs_dir *dir) { #else // !BFS_USE_GETDENTS if (dir->de) { return 1; - } else if (dir->eof) { + } else if (dir->flags & BFS_DIR_EOF) { return 0; } @@ -211,7 +220,7 @@ int bfs_polldir(struct bfs_dir *dir) { if (dir->de) { return 1; } else if (errno == 0) { - dir->eof = true; + dir->flags |= BFS_DIR_EOF; return 0; } else { return -1; diff --git a/src/dir.h b/src/dir.h index 1137ff5..7e0cbba 100644 --- a/src/dir.h +++ b/src/dir.h @@ -86,6 +86,14 @@ struct bfs_dir *bfs_allocdir(void); */ void bfs_dir_arena(struct arena *arena); +/** + * bfs_opendir() flags. + */ +enum bfs_dir_flags { + /** @internal Start of private flags. */ + BFS_DIR_PRIVATE = 1 << 0, +}; + /** * Open a directory. * @@ -96,10 +104,12 @@ void bfs_dir_arena(struct arena *arena); * @param at_path * The path of the directory to open, relative to at_fd. Pass NULL to * open at_fd itself. + * @param flags + * Flags that control which directory entries are listed. * @return * 0 on success, or -1 on failure. */ -int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path); +int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path, enum bfs_dir_flags flags); /** * Get the file descriptor for a directory. diff --git a/src/eval.c b/src/eval.c index e0dd97b..adf7a0b 100644 --- a/src/eval.c +++ b/src/eval.c @@ -427,7 +427,7 @@ bool eval_empty(const struct bfs_expr *expr, struct bfs_eval *state) { return ret; } - if (bfs_opendir(dir, ftwbuf->at_fd, ftwbuf->at_path) != 0) { + if (bfs_opendir(dir, ftwbuf->at_fd, ftwbuf->at_path, 0) != 0) { eval_report_error(state); return ret; } @@ -1517,8 +1517,8 @@ static int infer_fdlimit(const struct bfs_ctx *ctx, int limit) { goto done; } - if (bfs_opendir(dir, AT_FDCWD, "/proc/self/fd") != 0 - && bfs_opendir(dir, AT_FDCWD, "/dev/fd") != 0) { + if (bfs_opendir(dir, AT_FDCWD, "/proc/self/fd", 0) != 0 + && bfs_opendir(dir, AT_FDCWD, "/dev/fd", 0) != 0) { goto done; } diff --git a/src/ioq.c b/src/ioq.c index 04b9c0d..8c1bdbe 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -348,7 +348,7 @@ static void ioq_handle(struct ioq *ioq, struct ioq_ent *ent) { break; case IOQ_OPENDIR: - ret = bfs_opendir(ent->opendir.dir, ent->opendir.dfd, ent->opendir.path); + ret = bfs_opendir(ent->opendir.dir, ent->opendir.dfd, ent->opendir.path, ent->opendir.flags); if (ret == 0) { bfs_polldir(ent->opendir.dir); } @@ -505,7 +505,7 @@ static void ioq_ring_reap(struct ioq_ring_state *state) { continue; } - ent->ret = bfs_opendir(ent->opendir.dir, fd, NULL); + ent->ret = bfs_opendir(ent->opendir.dir, fd, NULL, ent->opendir.flags); if (ent->ret == 0) { // TODO: io_uring_prep_getdents() bfs_polldir(ent->opendir.dir); @@ -659,7 +659,7 @@ int ioq_close(struct ioq *ioq, int fd, void *ptr) { return 0; } -int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, void *ptr) { +int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, enum bfs_dir_flags flags, void *ptr) { struct ioq_ent *ent = ioq_request(ioq, IOQ_OPENDIR, ptr); if (!ent) { return -1; @@ -669,6 +669,7 @@ int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, args->dir = dir; args->dfd = dfd; args->path = path; + args->flags = flags; ioqq_push(ioq->pending, ent); return 0; diff --git a/src/ioq.h b/src/ioq.h index 99c18c2..eab89ec 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -8,6 +8,7 @@ #ifndef BFS_IOQ_H #define BFS_IOQ_H +#include "dir.h" #include /** @@ -53,6 +54,7 @@ struct ioq_ent { struct bfs_dir *dir; int dfd; const char *path; + enum bfs_dir_flags flags; } opendir; /** ioq_closedir() args. */ struct ioq_closedir { @@ -103,12 +105,14 @@ int ioq_close(struct ioq *ioq, int fd, void *ptr); * The base file descriptor. * @param path * The path to open, relative to dfd. + * @param flags + * Flags that control which directory entries are listed. * @param ptr * An arbitrary pointer to associate with the request. * @return * 0 on success, or -1 on failure. */ -int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, void *ptr); +int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, enum bfs_dir_flags flags, void *ptr); /** * Asynchronous bfs_closedir(). -- cgit v1.2.3 From 68949cf1b9cb5336ea06ad7f87db8e28b620f2ac Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 17 Oct 2023 11:55:19 -0400 Subject: bftw: New flag to control whiteout visibility --- src/bftw.c | 18 ++++++++++++++++-- src/bftw.h | 2 ++ src/eval.c | 1 + 3 files changed, 19 insertions(+), 2 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index e2f1a66..dff949f 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -41,6 +41,13 @@ static const struct bfs_stat *bftw_stat_impl(struct BFTW *ftwbuf, struct bftw_st errno = cache->error; } else if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, flags, &cache->storage) == 0) { cache->buf = &cache->storage; +#ifdef S_IFWHT + } else if (errno == ENOENT && ftwbuf->type == BFS_WHT) { + // This matches the behavior of FTS_WHITEOUT on BSD + memset(&cache->storage, 0, sizeof(cache->storage)); + cache->storage.mode = S_IFWHT; + cache->buf = &cache->storage; +#endif } else { cache->error = errno; } @@ -410,6 +417,8 @@ struct bftw_state { enum bftw_strategy strategy; /** The mount table. */ const struct bfs_mtab *mtab; + /** bfs_opendir() flags. */ + enum bfs_dir_flags dir_flags; /** The appropriate errno value, if any. */ int error; @@ -492,6 +501,7 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg state->flags = args->flags; state->strategy = args->strategy; state->mtab = args->mtab; + state->dir_flags = 0; state->error = 0; if (args->nopenfd < 2) { @@ -527,6 +537,10 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg state->flags |= BFTW_BUFFER; } + if (state->flags & BFTW_WHITEOUTS) { + state->dir_flags |= BFS_DIR_WHITEOUTS; + } + SLIST_INIT(&state->dir_batch); SLIST_INIT(&state->to_open); SLIST_INIT(&state->to_read); @@ -856,7 +870,7 @@ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { goto unpin; } - if (ioq_opendir(state->ioq, dir, dfd, file->name, 0, file) != 0) { + if (ioq_opendir(state->ioq, dir, dfd, file->name, state->dir_flags, file) != 0) { goto free; } @@ -1018,7 +1032,7 @@ static struct bfs_dir *bftw_file_opendir(struct bftw_state *state, struct bftw_f return NULL; } - if (bfs_opendir(dir, fd, NULL, 0) != 0) { + if (bfs_opendir(dir, fd, NULL, state->dir_flags) != 0) { bftw_freedir(cache, dir); return NULL; } diff --git a/src/bftw.h b/src/bftw.h index 940532c..2b36b8b 100644 --- a/src/bftw.h +++ b/src/bftw.h @@ -156,6 +156,8 @@ enum bftw_flags { BFTW_SORT = 1 << 8, /** Read each directory into memory before processing its children. */ BFTW_BUFFER = 1 << 9, + /** Include whiteouts in the search results. */ + BFTW_WHITEOUTS = 1 << 10, }; /** diff --git a/src/eval.c b/src/eval.c index adf7a0b..3d396fa 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1572,6 +1572,7 @@ static void dump_bftw_flags(enum bftw_flags flags) { DEBUG_FLAG(flags, BFTW_PRUNE_MOUNTS); DEBUG_FLAG(flags, BFTW_SORT); DEBUG_FLAG(flags, BFTW_BUFFER); + DEBUG_FLAG(flags, BFTW_WHITEOUTS); bfs_assert(flags == 0, "Missing bftw flag 0x%X", flags); } -- cgit v1.2.3 From 06ee53d5b1fc58073aaa3d57f6073256d13502f2 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 31 Oct 2023 12:30:07 -0400 Subject: bftw: Leave work for the main thread if profitable --- src/bftw.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index dff949f..3982515 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -627,6 +627,13 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { return op; } +/** Check if we should block on the I/O queue even if not strictly necessary. */ +static bool bftw_ioq_block(const struct bftw_state *state) { + // With more than two threads, it is faster to wait for an I/O + // operation to complete than it is to do it ourselves + return state->nthreads > 2; +} + /** Try to reserve space in the I/O queue. */ static int bftw_ioq_reserve(struct bftw_state *state) { struct ioq *ioq = state->ioq; @@ -638,10 +645,7 @@ static int bftw_ioq_reserve(struct bftw_state *state) { return 0; } - // With more than two threads, it is faster to wait for an I/O operation - // to complete than it is to do it ourselves - bool block = state->nthreads > 2; - if (bftw_ioq_pop(state, block) < 0) { + if (bftw_ioq_pop(state, bftw_ioq_block(state)) < 0) { return -1; } @@ -895,6 +899,11 @@ static void bftw_ioq_opendirs(struct bftw_state *state, struct bftw_list *queue) break; } SLIST_POP(queue); + + if (!bftw_ioq_block(state)) { + // Leave some work for the main thread + break; + } } } -- cgit v1.2.3 From b2ab7a151fca517f4879e76e626ec85ad3de97c7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 1 Nov 2023 11:18:39 -0400 Subject: bftw: Improve ioq balancing logic --- src/bftw.c | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 3982515..5a55037 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -430,6 +430,8 @@ struct bftw_state { struct ioq *ioq; /** The number of I/O threads. */ size_t nthreads; + /** Tracks the imbalance between main thread and background I/O. */ + long imbalance; /** A batch of directories to open. */ struct bftw_list dir_batch; @@ -541,6 +543,8 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg state->dir_flags |= BFS_DIR_WHITEOUTS; } + state->imbalance = 0; + SLIST_INIT(&state->dir_batch); SLIST_INIT(&state->to_open); SLIST_INIT(&state->to_read); @@ -571,6 +575,12 @@ static void bftw_unpin_dir(struct bftw_state *state, struct bftw_file *file, boo } } +/** Adjust the I/O queue balance. */ +static void bftw_ioq_balance(struct bftw_state *state, long delta) { + // Avoid signed overflow + state->imbalance = (unsigned long)state->imbalance + (unsigned long)delta; +} + /** Pop a response from the I/O queue. */ static int bftw_ioq_pop(struct bftw_state *state, bool block) { struct ioq *ioq = state->ioq; @@ -627,13 +637,6 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { return op; } -/** Check if we should block on the I/O queue even if not strictly necessary. */ -static bool bftw_ioq_block(const struct bftw_state *state) { - // With more than two threads, it is faster to wait for an I/O - // operation to complete than it is to do it ourselves - return state->nthreads > 2; -} - /** Try to reserve space in the I/O queue. */ static int bftw_ioq_reserve(struct bftw_state *state) { struct ioq *ioq = state->ioq; @@ -641,11 +644,19 @@ static int bftw_ioq_reserve(struct bftw_state *state) { return -1; } + // With only one background thread, we should balance I/O between it and + // the main thread. With more than one background thread, it's faster + // to wait on background I/O than it is to do it on the main thread. + bool balance = state->nthreads <= 1; + if (balance && state->imbalance < 0) { + return -1; + } + if (ioq_capacity(ioq) > 0) { return 0; } - if (bftw_ioq_pop(state, bftw_ioq_block(state)) < 0) { + if (bftw_ioq_pop(state, !balance) < 0) { return -1; } @@ -880,6 +891,7 @@ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { file->ioqueued = true; --cache->capacity; + bftw_ioq_balance(state, -1); return 0; free: @@ -899,11 +911,6 @@ static void bftw_ioq_opendirs(struct bftw_state *state, struct bftw_list *queue) break; } SLIST_POP(queue); - - if (!bftw_ioq_block(state)) { - // Leave some work for the main thread - break; - } } } @@ -1041,6 +1048,8 @@ static struct bfs_dir *bftw_file_opendir(struct bftw_state *state, struct bftw_f return NULL; } + bftw_ioq_balance(state, +1); + if (bfs_opendir(dir, fd, NULL, state->dir_flags) != 0) { bftw_freedir(cache, dir); return NULL; -- cgit v1.2.3 From 5fe11b94b38bfb4d43637e05ac24da0d7d72b9ea Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 7 Nov 2023 16:43:35 -0500 Subject: ioq: Implement a better non-blocking pop --- src/bftw.c | 2 +- src/ioq.c | 276 +++++++++++++++++++++++++++++++++++++++++-------------------- src/ioq.h | 13 +-- 3 files changed, 189 insertions(+), 102 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 5a55037..0b74cd9 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -588,7 +588,7 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { return -1; } - struct ioq_ent *ent = block ? ioq_pop(ioq) : ioq_trypop(ioq); + struct ioq_ent *ent = ioq_pop(ioq, block); if (!ent) { return -1; } diff --git a/src/ioq.c b/src/ioq.c index 8c1bdbe..244b2cc 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -1,6 +1,127 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +/** + * An asynchronous I/O queue implementation. + * + * struct ioq is composed of two separate queues: + * + * struct ioqq *pending; // Pending I/O requests + * struct ioqq *ready; // Ready I/O responses + * + * Worker threads pop requests from `pending`, execute them, and push them back + * to the `ready` queue. The main thread pushes requests to `pending` and pops + * them from `ready`. + * + * struct ioqq is a blocking MPMC queue (though it could be SPMC/MPSC for + * pending/ready respectively). It is implemented as a circular buffer: + * + * size_t mask; // (1 << N) - 1 + * [padding] + * size_t head; // Writer index + * [padding] + * size_t tail; // Reader index + * [padding] + * ioq_slot slots[1 << N]; // Queue contents + * + * Pushes are implemented with an unconditional + * + * fetch_add(&ioqq->head, IOQ_STRIDE) + * + * which scales better on many architectures than compare-and-swap (see [1] for + * details). Pops are implemented similarly. We add IOQ_STRIDE rather than 1 + * so that successive queue elements are on different cache lines, but the + * exposition below uses 1 for simplicity. + * + * Since the fetch-and-adds are unconditional, non-blocking readers can get + * ahead of writers: + * + * Reader Writer + * ──────────────── ────────────────────── + * head: 0 → 1 + * slots[0]: empty + * tail: 0 → 1 + * slots[0]: empty → full + * head: 1 → 2 + * slots[1]: empty! + * + * To avoid this, non-blocking reads (ioqq_pop(ioqq, false)) must mark the slots + * somehow so that writers can skip them: + * + * Reader Writer + * ─────────────────────── ─────────────────────── + * head: 0 → 1 + * slots[0]: empty → skip + * tail: 0 → 1 + * slots[0]: skip → empty + * tail: 1 → 2 + * slots[1]: empty → full + * head: 1 → 2 + * slots[1]: full → empty + * + * As well, a reader might "lap" a writer (or another reader), so slots need to + * count how many times they should be skipped: + * + * Reader Writer + * ────────────────────────── ───────────────────────── + * head: 0 → 1 + * slots[0]: empty → skip(1) + * head: 1 → 2 + * slots[1]: empty → skip(1) + * ... + * head: M → 0 + * slots[M]: empty → skip(1) + * head: 0 → 1 + * slots[0]: skip(1 → 2) + * tail: 0 → 1 + * slots[0]: skip(2 → 1) + * tail: 1 → 2 + * slots[1]: skip(1) → empty + * ... + * tail: M → 0 + * slots[M]: skip(1) → empty + * tail: 0 → 1 + * slots[0]: skip(1) → empty + * tail: 1 → 2 + * slots[1]: empty → full + * head: 1 → 2 + * slots[1]: full → empty + * + * As described in [1], this approach is susceptible to livelock if readers stay + * ahead of writers. This is okay for us because we don't retry failed non- + * blocking reads. + * + * The slot representation uses tag bits to hold either a pointer or skip(N): + * + * IOQ_SKIP (highest bit) IOQ_BLOCKED (lowest bit) + * ↓ ↓ + * 0 0 0 ... 0 0 0 + * └──────────┬──────────┘ + * │ + * value bits + * + * If IOQ_SKIP is unset, the value bits hold a pointer (or zero/NULL for empty). + * If IOQ_SKIP is set, the value bits hold a negative skip count. Writers can + * reduce the skip count by adding 1 to the value bits, and when the count hits + * zero, the carry will automatically clear IOQ_SKIP: + * + * IOQ_SKIP IOQ_BLOCKED + * ↓ ↓ + * 1 1 1 ... 1 0 0 skip(2) + * 1 1 1 ... 1 1 0 skip(1) + * 0 0 0 ... 0 0 0 empty + * + * The IOQ_BLOCKED flag is used to track sleeping waiters, futex-style. To wait + * for a slot to change, waiters call ioq_slot_wait() which sets IOQ_BLOCKED and + * goes to sleep. Whenever a slot is updated, if the old value had IOQ_BLOCKED + * set, ioq_slot_wake() must be called to wake up that waiter. + * + * Blocking/waking uses a pool of monitors (mutex, condition variable pairs). + * Slots are assigned round-robin to a monitor from the pool. + * + * [1]: https://arxiv.org/abs/2201.02179 + */ + #include "ioq.h" #include "alloc.h" #include "atomic.h" @@ -51,24 +172,15 @@ static void ioq_monitor_destroy(struct ioq_monitor *monitor) { /** A single entry in a command queue. */ typedef atomic uintptr_t ioq_slot; -/** Slot flag bit to indicate waiters. */ +/** Someone might be waiting on this slot. */ #define IOQ_BLOCKED ((uintptr_t)1) -bfs_static_assert(alignof(struct ioq_ent) > 1); - -/** Check if a slot has waiters. */ -static bool ioq_slot_blocked(uintptr_t value) { - return value & IOQ_BLOCKED; -} - -/** Extract the pointer from a slot. */ -static struct ioq_ent *ioq_slot_ptr(uintptr_t value) { - return (struct ioq_ent *)(value & ~IOQ_BLOCKED); -} +/** The next push(es) should skip this slot. */ +#define IOQ_SKIP ((uintptr_t)1 << (UINTPTR_WIDTH - 1)) +/** Amount to add for an additional skip. */ +#define IOQ_SKIP_ONE (~IOQ_BLOCKED) -/** Check if a slot is empty. */ -static bool ioq_slot_empty(uintptr_t value) { - return !ioq_slot_ptr(value); -} +// Need room for two flag bits +bfs_static_assert(alignof(struct ioq_ent) > 2); /** * An MPMC queue of I/O commands. @@ -205,80 +317,85 @@ static void ioq_slot_wake(struct ioqq *ioqq, ioq_slot *slot) { cond_broadcast(&monitor->cond); } -/** Get the next slot for writing. */ -static ioq_slot *ioqq_write(struct ioqq *ioqq) { - size_t i = fetch_add(&ioqq->head, IOQ_STRIDE, relaxed); - return &ioqq->slots[i & ioqq->slot_mask]; -} - /** Push an entry into a slot. */ -static void ioq_slot_push(struct ioqq *ioqq, ioq_slot *slot, struct ioq_ent *ent) { - uintptr_t addr = (uintptr_t)ent; - bfs_assert(!ioq_slot_blocked(addr)); - +static bool ioq_slot_push(struct ioqq *ioqq, ioq_slot *slot, struct ioq_ent *ent) { uintptr_t prev = load(slot, relaxed); - do { - while (!ioq_slot_empty(prev)) { + while (true) { + uintptr_t next; + if (prev & IOQ_SKIP) { + // skip(1) → empty + // skip(n) → skip(n - 1) + next = (prev - IOQ_SKIP_ONE) & ~IOQ_BLOCKED; + } else if (prev > IOQ_BLOCKED) { + // full(ptr) → wait prev = ioq_slot_wait(ioqq, slot, prev); + continue; + } else { + // empty → full(ptr) + next = (uintptr_t)ent >> 1; } - } while (!compare_exchange_weak(slot, &prev, addr, release, relaxed)); - if (ioq_slot_blocked(prev)) { + if (compare_exchange_weak(slot, &prev, next, release, relaxed)) { + break; + } + } + + if (prev & IOQ_BLOCKED) { ioq_slot_wake(ioqq, slot); } + + return !(prev & IOQ_SKIP); } /** Push an entry onto the queue. */ static void ioqq_push(struct ioqq *ioqq, struct ioq_ent *ent) { - ioq_slot *slot = ioqq_write(ioqq); - ioq_slot_push(ioqq, slot, ent); -} - -/** Get the next slot for reading. */ -static ioq_slot *ioqq_read(struct ioqq *ioqq) { - size_t i = fetch_add(&ioqq->tail, IOQ_STRIDE, relaxed); - return &ioqq->slots[i & ioqq->slot_mask]; + while (true) { + size_t i = fetch_add(&ioqq->head, IOQ_STRIDE, relaxed); + ioq_slot *slot = &ioqq->slots[i & ioqq->slot_mask]; + if (ioq_slot_push(ioqq, slot, ent)) { + break; + } + } } /** (Try to) pop an entry from a slot. */ static struct ioq_ent *ioq_slot_pop(struct ioqq *ioqq, ioq_slot *slot, bool block) { uintptr_t prev = load(slot, relaxed); - do { - while (ioq_slot_empty(prev)) { - if (block) { - prev = ioq_slot_wait(ioqq, slot, prev); - } else { - return NULL; - } + while (true) { + // empty → skip(1) + // skip(n) → skip(n + 1) + // full(ptr) → full(ptr - 1) + uintptr_t next = prev + IOQ_SKIP_ONE; + // skip(n) → ~IOQ_BLOCKED + // full(ptr) → 0 + next &= (next & IOQ_SKIP) ? ~IOQ_BLOCKED : 0; + + if (block && next) { + prev = ioq_slot_wait(ioqq, slot, prev); + continue; + } + + if (compare_exchange_weak(slot, &prev, next, acquire, relaxed)) { + break; } - } while (!compare_exchange_weak(slot, &prev, 0, acquire, relaxed)); + } - if (ioq_slot_blocked(prev)) { + if (prev & IOQ_BLOCKED) { ioq_slot_wake(ioqq, slot); } - return ioq_slot_ptr(prev); + // empty → 0 + // skip(n) → 0 + // full(ptr) → ptr + prev &= (prev & IOQ_SKIP) ? 0 : ~IOQ_BLOCKED; + return (struct ioq_ent *)(prev << 1); } /** Pop an entry from the queue. */ -static struct ioq_ent *ioqq_pop(struct ioqq *ioqq) { - ioq_slot *slot = ioqq_read(ioqq); - return ioq_slot_pop(ioqq, slot, true); -} - -/** Pop an entry from the queue if one is available. */ -static struct ioq_ent *ioqq_trypop(struct ioqq *ioqq) { - size_t i = load(&ioqq->tail, relaxed); +static struct ioq_ent *ioqq_pop(struct ioqq *ioqq, bool block) { + size_t i = fetch_add(&ioqq->tail, IOQ_STRIDE, relaxed); ioq_slot *slot = &ioqq->slots[i & ioqq->slot_mask]; - - struct ioq_ent *ret = ioq_slot_pop(ioqq, slot, false); - if (ret) { - size_t j = exchange(&ioqq->tail, i + IOQ_STRIDE, relaxed); - bfs_assert(j == i, "Detected multiple consumers"); - (void)j; - } - - return ret; + return ioq_slot_pop(ioqq, slot, block); } /** Sentinel stop command. */ @@ -378,8 +495,6 @@ struct ioq_ring_state { struct ioq *ioq; /** The io_uring. */ struct io_uring *ring; - /** The current ioq->pending slot. */ - ioq_slot *slot; /** Number of prepped, unsubmitted SQEs. */ size_t prepped; /** Number of submitted, unreaped SQEs. */ @@ -394,20 +509,9 @@ static struct ioq_ent *ioq_ring_pop(struct ioq_ring_state *state) { return NULL; } - // Advance to the next slot if necessary - struct ioq *ioq = state->ioq; - if (!state->slot) { - state->slot = ioqq_read(ioq->pending); - } - // Block if we have nothing else to do bool block = !state->prepped && !state->submitted; - struct ioq_ent *ret = ioq_slot_pop(ioq->pending, state->slot, block); - - if (ret) { - // Got an entry, move to the next slot next time - state->slot = NULL; - } + struct ioq_ent *ret = ioqq_pop(state->ioq->pending, block); if (ret == &IOQ_STOP) { state->stop = true; @@ -536,7 +640,7 @@ static void ioq_sync_work(struct ioq_thread *thread) { struct ioq *ioq = thread->parent; while (true) { - struct ioq_ent *ent = ioqq_pop(ioq->pending); + struct ioq_ent *ent = ioqq_pop(ioq->pending, true); if (ent == &IOQ_STOP) { break; } @@ -687,20 +791,12 @@ int ioq_closedir(struct ioq *ioq, struct bfs_dir *dir, void *ptr) { return 0; } -struct ioq_ent *ioq_pop(struct ioq *ioq) { - if (ioq->size == 0) { - return NULL; - } - - return ioqq_pop(ioq->ready); -} - -struct ioq_ent *ioq_trypop(struct ioq *ioq) { +struct ioq_ent *ioq_pop(struct ioq *ioq, bool block) { if (ioq->size == 0) { return NULL; } - return ioqq_trypop(ioq->ready); + return ioqq_pop(ioq->ready, block); } void ioq_free(struct ioq *ioq, struct ioq_ent *ent) { diff --git a/src/ioq.h b/src/ioq.h index eab89ec..87727cb 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -8,6 +8,7 @@ #ifndef BFS_IOQ_H #define BFS_IOQ_H +#include "config.h" #include "dir.h" #include @@ -136,17 +137,7 @@ int ioq_closedir(struct ioq *ioq, struct bfs_dir *dir, void *ptr); * @return * The next response, or NULL. */ -struct ioq_ent *ioq_pop(struct ioq *ioq); - -/** - * Pop a response from the queue, without blocking. - * - * @param ioq - * The I/O queue. - * @return - * The next response, or NULL. - */ -struct ioq_ent *ioq_trypop(struct ioq *ioq); +struct ioq_ent *ioq_pop(struct ioq *ioq, bool block); /** * Free a queue entry. -- cgit v1.2.3 From 45fb1d952c3b262278a3b22e9c7d60cca19a5407 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 3 Jan 2024 17:17:35 -0500 Subject: Work around DragonFly BSD kernel bug DragonFly's x86_64 assembly implementation of copyinstr() checks the wrong pointer when deciding whether to return EFAULT or ENAMETOOLONG, causing it to always return EFAULT for overlong paths. Work around it by treating EFAULT the same as ENAMETOOLONG on DragonFly. Link: https://twitter.com/tavianator/status/1742991411203485713 --- src/bftw.c | 14 +++++++++++++- tests/xtouch.c | 6 ++++++ 2 files changed, 19 insertions(+), 1 deletion(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 0b74cd9..355cb54 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -739,10 +739,22 @@ static int bftw_file_open(struct bftw_state *state, struct bftw_file *file, cons } int fd = bftw_file_openat(state, file, base, at_path); - if (fd >= 0 || errno != ENAMETOOLONG) { + if (fd >= 0) { return fd; } + switch (errno) { + case ENAMETOOLONG: +#if __DragonFly__ + // https://twitter.com/tavianator/status/1742991411203485713 + case EFAULT: +#endif + break; + + default: + return -1; + } + // Handle ENAMETOOLONG by manually traversing the path component-by-component struct bftw_list parents; SLIST_INIT(&parents); diff --git a/tests/xtouch.c b/tests/xtouch.c index 260a3a3..ed8bbee 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -70,13 +70,19 @@ static int open_parent(const struct args *args, const char **path) { switch (errno) { case ENAMETOOLONG: +#if __DragonFly__ + // https://twitter.com/tavianator/status/1742991411203485713 + case EFAULT: +#endif break; + case ENOENT: if (args->flags & CREATE_PARENTS) { break; } else { goto err; } + default: goto err; } -- cgit v1.2.3 From e9588c49d5539ded993f720fc6855d6fa878c997 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 13 Jan 2024 12:42:42 -0500 Subject: bfstd: New {error,errno}_is_like() functions We used to have is_nonexistence_error() to consistently treat ENOENT and ENOTDIR the same. Recently, we started considering EFAULT the same as ENAMETOOLONG on DragonFly BSD to work around a kernel bug. Unify both of these behind a more generic interface. --- src/bfstd.c | 23 +++++++++++++++++++++-- src/bfstd.h | 18 ++++++++++++++++-- src/bftw.c | 18 +++--------------- src/eval.c | 2 +- src/stat.c | 2 +- tests/xtouch.c | 17 +++-------------- 6 files changed, 45 insertions(+), 35 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bfstd.c b/src/bfstd.c index c0e61cb..15e8667 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -36,8 +36,27 @@ # include #endif -bool is_nonexistence_error(int error) { - return error == ENOENT || errno == ENOTDIR; +bool error_is_like(int error, int category) { + if (error == category) { + return true; + } + + switch (category) { + case ENOENT: + return error == ENOTDIR; + +#if __DragonFly__ + // https://twitter.com/tavianator/status/1742991411203485713 + case ENAMETOOLONG: + return error == EFAULT; +#endif + } + + return false; +} + +bool errno_is_like(int category) { + return error_is_like(errno, category); } char *xdirname(const char *path) { diff --git a/src/bfstd.h b/src/bfstd.h index 0fcb892..8953b9b 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -45,9 +45,23 @@ // #include /** - * Return whether an error code is due to a path not existing. + * Check if an error code is "like" another one. For example, ENOTDIR is + * like ENOENT because they can both be triggered by non-existent paths. + * + * @param error + * The error code to check. + * @param category + * The category to test for. Known categories include ENOENT and + * ENAMETOOLONG. + * @return + * Whether the error belongs to the given category. + */ +bool error_is_like(int error, int category); + +/** + * Equivalent to error_is_like(errno, category). */ -bool is_nonexistence_error(int error); +bool errno_is_like(int category); #include diff --git a/src/bftw.c b/src/bftw.c index 355cb54..49f07df 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -68,7 +68,7 @@ const struct bfs_stat *bftw_stat(const struct BFTW *ftwbuf, enum bfs_stat_flags } } else { ret = bftw_stat_impl(mutbuf, &mutbuf->stat_cache, BFS_STAT_FOLLOW); - if (!ret && (flags & BFS_STAT_TRYFOLLOW) && is_nonexistence_error(errno)) { + if (!ret && (flags & BFS_STAT_TRYFOLLOW) && errno_is_like(ENOENT)) { ret = bftw_stat_impl(mutbuf, &mutbuf->lstat_cache, BFS_STAT_NOFOLLOW); } } @@ -81,7 +81,7 @@ const struct bfs_stat *bftw_cached_stat(const struct BFTW *ftwbuf, enum bfs_stat return ftwbuf->lstat_cache.buf; } else if (ftwbuf->stat_cache.buf) { return ftwbuf->stat_cache.buf; - } else if ((flags & BFS_STAT_TRYFOLLOW) && is_nonexistence_error(ftwbuf->stat_cache.error)) { + } else if ((flags & BFS_STAT_TRYFOLLOW) && error_is_like(ftwbuf->stat_cache.error, ENOENT)) { return ftwbuf->lstat_cache.buf; } else { return NULL; @@ -739,22 +739,10 @@ static int bftw_file_open(struct bftw_state *state, struct bftw_file *file, cons } int fd = bftw_file_openat(state, file, base, at_path); - if (fd >= 0) { + if (fd >= 0 || !errno_is_like(ENAMETOOLONG)) { return fd; } - switch (errno) { - case ENAMETOOLONG: -#if __DragonFly__ - // https://twitter.com/tavianator/status/1742991411203485713 - case EFAULT: -#endif - break; - - default: - return -1; - } - // Handle ENAMETOOLONG by manually traversing the path component-by-component struct bftw_list parents; SLIST_INIT(&parents); diff --git a/src/eval.c b/src/eval.c index 130b366..1a814b3 100644 --- a/src/eval.c +++ b/src/eval.c @@ -80,7 +80,7 @@ static void eval_error(struct bfs_eval *state, const char *format, ...) { */ static bool eval_should_ignore(const struct bfs_eval *state, int error) { return state->ctx->ignore_races - && is_nonexistence_error(error) + && error_is_like(error, ENOENT) && state->ftwbuf->depth > 0; } diff --git a/src/stat.c b/src/stat.c index d7387c6..91aa092 100644 --- a/src/stat.c +++ b/src/stat.c @@ -260,7 +260,7 @@ static int bfs_stat_tryfollow(int at_fd, const char *at_path, int at_flags, int if (ret != 0 && (bfs_flags & (BFS_STAT_NOFOLLOW | BFS_STAT_TRYFOLLOW)) == BFS_STAT_TRYFOLLOW - && is_nonexistence_error(errno)) + && errno_is_like(ENOENT)) { at_flags |= AT_SYMLINK_NOFOLLOW; ret = bfs_stat_explicit(at_fd, at_path, at_flags, x_flags, buf); diff --git a/tests/xtouch.c b/tests/xtouch.c index ed8bbee..6099128 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -68,22 +68,11 @@ static int open_parent(const struct args *args, const char **path) { goto done; } - switch (errno) { - case ENAMETOOLONG: -#if __DragonFly__ - // https://twitter.com/tavianator/status/1742991411203485713 - case EFAULT: -#endif - break; - - case ENOENT: - if (args->flags & CREATE_PARENTS) { - break; - } else { + if (errno == ENOENT) { + if (!(args->flags & CREATE_PARENTS)) { goto err; } - - default: + } else if (!errno_is_like(ENAMETOOLONG)) { goto err; } -- cgit v1.2.3 From e766015d60c927aae03b8e43d956c6976c16b2ba Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 13 Jan 2024 15:54:46 -0500 Subject: ioq: Use the negative errno convention --- src/bfstd.c | 9 +++++++++ src/bfstd.h | 10 ++++++++++ src/bftw.c | 2 +- src/ioq.c | 32 +++++++++++--------------------- src/ioq.h | 6 ++---- 5 files changed, 33 insertions(+), 26 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bfstd.c b/src/bfstd.c index 15e8667..0a9b87c 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -59,6 +59,15 @@ bool errno_is_like(int category) { return error_is_like(errno, category); } +int try(int ret) { + if (ret >= 0) { + return ret; + } else { + bfs_assert(errno > 0, "errno should be positive, was %d\n", errno); + return -errno; + } +} + char *xdirname(const char *path) { size_t i = xbaseoff(path); diff --git a/src/bfstd.h b/src/bfstd.h index 8953b9b..d160c88 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -63,6 +63,16 @@ bool error_is_like(int error, int category); */ bool errno_is_like(int category); +/** + * Apply the "negative errno" convention. + * + * @param ret + * The return value of the attempted operation. + * @return + * ret, if non-negative, otherwise -errno. + */ +int try(int ret); + #include #ifndef O_EXEC diff --git a/src/bftw.c b/src/bftw.c index 49f07df..952b090 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -621,7 +621,7 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { } dir = ent->opendir.dir; - if (ent->ret == 0) { + if (ent->result >= 0) { bftw_file_set_dir(cache, file, dir); } else { bftw_freedir(cache, dir); diff --git a/src/ioq.c b/src/ioq.c index 2739338..89ebb3e 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -455,42 +455,35 @@ static bool ioq_check_cancel(struct ioq *ioq, struct ioq_ent *ent) { return false; } - ent->ret = -1; - ent->error = EINTR; + ent->result = -EINTR; ioqq_push(ioq->ready, ent); return true; } /** Handle a single request synchronously. */ static void ioq_handle(struct ioq *ioq, struct ioq_ent *ent) { - int ret; - switch (ent->op) { case IOQ_CLOSE: - ret = xclose(ent->close.fd); + ent->result = try(xclose(ent->close.fd)); break; case IOQ_OPENDIR: - ret = bfs_opendir(ent->opendir.dir, ent->opendir.dfd, ent->opendir.path, ent->opendir.flags); - if (ret == 0) { + ent->result = try(bfs_opendir(ent->opendir.dir, ent->opendir.dfd, ent->opendir.path, ent->opendir.flags)); + if (ent->result >= 0) { bfs_polldir(ent->opendir.dir); } break; case IOQ_CLOSEDIR: - ret = bfs_closedir(ent->closedir.dir); + ent->result = try(bfs_closedir(ent->closedir.dir)); break; default: bfs_bug("Unknown ioq_op %d", (int)ent->op); - ret = -1; - errno = ENOSYS; + ent->result = -ENOSYS; break; } - ent->ret = ret; - ent->error = ret == 0 ? 0 : errno; - ioqq_push(ioq->ready, ent); } @@ -603,24 +596,21 @@ static void ioq_ring_reap(struct ioq_ring_state *state) { } struct ioq_ent *ent = io_uring_cqe_get_data(cqe); - ent->ret = cqe->res >= 0 ? cqe->res : -1; - ent->error = cqe->res < 0 ? -cqe->res : 0; + ent->result = cqe->res; io_uring_cqe_seen(ring, cqe); --state->submitted; - if (ent->op == IOQ_OPENDIR && ent->ret >= 0) { - int fd = ent->ret; + if (ent->op == IOQ_OPENDIR && ent->result >= 0) { + int fd = ent->result; if (ioq_check_cancel(ioq, ent)) { xclose(fd); continue; } - ent->ret = bfs_opendir(ent->opendir.dir, fd, NULL, ent->opendir.flags); - if (ent->ret == 0) { + ent->result = try(bfs_opendir(ent->opendir.dir, fd, NULL, ent->opendir.flags)); + if (ent->result >= 0) { // TODO: io_uring_prep_getdents() bfs_polldir(ent->opendir.dir); - } else { - ent->error = errno; } } diff --git a/src/ioq.h b/src/ioq.h index 87727cb..e1e5052 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -36,10 +36,8 @@ struct ioq_ent { /** The I/O operation. */ enum ioq_op op; - /** The return value of the operation. */ - int ret; - /** The error code, if the operation failed. */ - int error; + /** The return value (on success) or negative error code (on failure). */ + int result; /** Arbitrary user data. */ void *ptr; -- cgit v1.2.3 From 18597572f62ab18824df6bab6c30d2b645921969 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 23 Aug 2023 13:10:10 -0400 Subject: ioq: Implement ioq_stat() --- src/bftw.c | 3 ++ src/ioq.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------- src/ioq.h | 31 +++++++++++++++++++ 3 files changed, 124 insertions(+), 10 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 952b090..02dd051 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -631,6 +631,9 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { SLIST_APPEND(&state->to_read, file, to_read); } break; + + case IOQ_STAT: + break; } ioq_free(ioq, ent); diff --git a/src/ioq.c b/src/ioq.c index 3d32dfe..3172f0a 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -131,6 +131,7 @@ #include "diag.h" #include "dir.h" #include "sanity.h" +#include "stat.h" #include "thread.h" #include #include @@ -432,6 +433,10 @@ struct ioq { /** ioq_ent arena. */ struct arena ents; +#if BFS_USE_LIBURING && BFS_USE_STATX + /** struct statx arena. */ + struct arena xbufs; +#endif /** Pending I/O requests. */ struct ioqq *pending; @@ -479,6 +484,12 @@ static void ioq_dispatch_sync(struct ioq *ioq, struct ioq_ent *ent) { case IOQ_CLOSEDIR: ent->result = try(bfs_closedir(ent->closedir.dir)); return; + + case IOQ_STAT: { + struct ioq_stat *args = &ent->stat; + ent->result = try(bfs_stat(args->dfd, args->path, args->flags, args->buf)); + return; + } } bfs_bug("Unknown ioq_op %d", (int)ent->op); @@ -549,6 +560,17 @@ static struct io_uring_sqe *ioq_dispatch_async(struct io_uring *ring, struct ioq io_uring_prep_close(sqe, bfs_unwrapdir(ent->closedir.dir)); #endif return sqe; + + case IOQ_STAT: { +#if BFS_USE_STATX + sqe = io_uring_get_sqe(ring); + struct ioq_stat *args = &ent->stat; + int flags = bfs_statx_flags(args->flags); + unsigned int mask = STATX_BASIC_STATS | STATX_BTIME; + io_uring_prep_statx(sqe, args->dfd, args->path, flags, mask, args->xbuf); +#endif + return sqe; + } } bfs_bug("Unknown ioq_op %d", (int)ent->op); @@ -597,21 +619,41 @@ static void ioq_reap_cqe(struct ioq_ring_state *state, struct io_uring_cqe *cqe) io_uring_cqe_seen(ring, cqe); --state->submitted; - if (ent->op == IOQ_OPENDIR && ent->result >= 0) { - int fd = ent->result; - if (ioq_check_cancel(ioq, ent)) { - xclose(fd); - return; + if (ent->result < 0) { + goto push; + } + + switch (ent->op) { + case IOQ_OPENDIR: { + int fd = ent->result; + if (ioq_check_cancel(ioq, ent)) { + xclose(fd); + return; + } + + struct ioq_opendir *args = &ent->opendir; + ent->result = try(bfs_opendir(args->dir, fd, NULL, args->flags)); + if (ent->result >= 0) { + // TODO: io_uring_prep_getdents() + bfs_polldir(args->dir); + } + + break; } - struct ioq_opendir *args = &ent->opendir; - ent->result = try(bfs_opendir(args->dir, fd, NULL, args->flags)); - if (ent->result >= 0) { - // TODO: io_uring_prep_getdents() - bfs_polldir(args->dir); +#if BFS_USE_STATX + case IOQ_STAT: { + struct ioq_stat *args = &ent->stat; + ent->result = try(bfs_statx_convert(args->buf, args->xbuf)); + break; } +#endif + + default: + break; } +push: ioqq_push(ioq->ready, ent); } @@ -689,8 +731,13 @@ struct ioq *ioq_create(size_t depth, size_t nthreads) { } ioq->depth = depth; + ARENA_INIT(&ioq->ents, struct ioq_ent); +#if BFS_USE_LIBURING && BFS_USE_STATX + ARENA_INIT(&ioq->xbufs, struct statx); +#endif + ioq->pending = ioqq_create(depth); if (!ioq->pending) { goto fail; @@ -807,6 +854,30 @@ int ioq_closedir(struct ioq *ioq, struct bfs_dir *dir, void *ptr) { return 0; } +int ioq_stat(struct ioq *ioq, int dfd, const char *path, enum bfs_stat_flags flags, struct bfs_stat *buf, void *ptr) { + struct ioq_ent *ent = ioq_request(ioq, IOQ_STAT, ptr); + if (!ent) { + return -1; + } + + struct ioq_stat *args = &ent->stat; + args->dfd = dfd; + args->path = path; + args->flags = flags; + args->buf = buf; + +#if BFS_USE_LIBURING && BFS_USE_STATX + args->xbuf = arena_alloc(&ioq->xbufs); + if (!args->xbuf) { + ioq_free(ioq, ent); + return -1; + } +#endif + + ioqq_push(ioq->pending, ent); + return 0; +} + struct ioq_ent *ioq_pop(struct ioq *ioq, bool block) { if (ioq->size == 0) { return NULL; @@ -819,6 +890,12 @@ void ioq_free(struct ioq *ioq, struct ioq_ent *ent) { bfs_assert(ioq->size > 0); --ioq->size; +#if BFS_USE_LIBURING && BFS_USE_STATX + if (ent->op == IOQ_STAT) { + arena_free(&ioq->xbufs, ent->stat.xbuf); + } +#endif + arena_free(&ioq->ents, ent); } @@ -848,6 +925,9 @@ void ioq_destroy(struct ioq *ioq) { ioqq_destroy(ioq->ready); ioqq_destroy(ioq->pending); +#if BFS_USE_LIBURING && BFS_USE_STATX + arena_destroy(&ioq->xbufs); +#endif arena_destroy(&ioq->ents); free(ioq); diff --git a/src/ioq.h b/src/ioq.h index e1e5052..77aabaa 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -10,6 +10,7 @@ #include "config.h" #include "dir.h" +#include "stat.h" #include /** @@ -27,6 +28,8 @@ enum ioq_op { IOQ_OPENDIR, /** ioq_closedir(). */ IOQ_CLOSEDIR, + /** ioq_stat(). */ + IOQ_STAT, }; /** @@ -59,6 +62,14 @@ struct ioq_ent { struct ioq_closedir { struct bfs_dir *dir; } closedir; + /** ioq_stat() args. */ + struct ioq_stat { + int dfd; + const char *path; + enum bfs_stat_flags flags; + struct bfs_stat *buf; + void *xbuf; + } stat; }; }; @@ -127,6 +138,26 @@ int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, */ int ioq_closedir(struct ioq *ioq, struct bfs_dir *dir, void *ptr); +/** + * Asynchronous bfs_stat(). + * + * @param ioq + * The I/O queue. + * @param dfd + * The base file descriptor. + * @param path + * The path to stat, relative to dfd. + * @param flags + * Flags that affect the lookup. + * @param buf + * A place to store the stat buffer, if successful. + * @param ptr + * An arbitrary pointer to associate with the request. + * @return + * 0 on success, or -1 on failure. + */ +int ioq_stat(struct ioq *ioq, int dfd, const char *path, enum bfs_stat_flags flags, struct bfs_stat *buf, void *ptr); + /** * Pop a response from the queue. * -- cgit v1.2.3 From 43ec427535e2d21a3d8ec36d98889f1bc0515938 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 26 Jan 2024 13:32:26 -0500 Subject: bftw: New bftw_queue abstraction --- src/bftw.c | 366 ++++++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 292 insertions(+), 74 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 02dd051..ce08dba 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -9,6 +9,8 @@ * * - struct bftw_list: A linked list of bftw_file's. * + * - struct bftw_queue: A multi-stage queue of bftw_file's. + * * - struct bftw_cache: An LRU list of bftw_file's with open file descriptors, * used for openat() to minimize the amount of path re-traversals. * @@ -125,7 +127,7 @@ struct bftw_file { /** The next file to open/close/visit. */ struct bftw_file *next; /** The next directory to read. */ - struct { struct bftw_file *next; } to_read; + struct { struct bftw_file *next; } ready; /** LRU list node. */ struct { @@ -170,6 +172,254 @@ struct bftw_list { struct bftw_file **tail; }; +/** + * bftw_queue flags. + */ +enum bftw_qflags { + /** Track the sync/async service balance. */ + BFTW_QBALANCE = 1 << 0, + /** Buffer files before adding them to the queue. */ + BFTW_QBUFFER = 1 << 1, + /** Use LIFO (stack/DFS) ordering. */ + BFTW_QLIFO = 1 << 2, + /** Maintain a strict order. */ + BFTW_QORDER = 1 << 3, +}; + +/** + * A queue of bftw_file's that may be serviced asynchronously. + * + * A bftw_queue comprises three linked lists each tracking different stages. + * When BFTW_QBUFFER is set, files are initially pushed to the buffer: + * + * ╔═══╗ ╔═══╦═══╗ + * buffer: ║ 𝘩 ║ ║ 𝘩 ║ 𝘪 ║ + * ╠═══╬═══╦═══╗ ╠═══╬═══╬═══╗ + * waiting: ║ e ║ f ║ g ║ → ║ e ║ f ║ g ║ + * ╠═══╬═══╬═══╬═══╗ ╠═══╬═══╬═══╬═══╗ + * ready: ║ 𝕒 ║ 𝕓 ║ 𝕔 ║ 𝕕 ║ ║ 𝕒 ║ 𝕓 ║ 𝕔 ║ 𝕕 ║ + * ╚═══╩═══╩═══╩═══╝ ╚═══╩═══╩═══╩═══╝ + * + * When bftw_queue_flush() is called, the files in the buffer are appended to + * the waiting list (or prepended, if BFTW_QLIFO is set): + * + * ╔═╗ + * buffer: ║ ║ + * ╠═╩═╦═══╦═══╦═══╦═══╗ + * waiting: ║ e ║ f ║ g ║ h ║ i ║ + * ╠═══╬═══╬═══╬═══╬═══╝ + * ready: ║ 𝕒 ║ 𝕓 ║ 𝕔 ║ 𝕕 ║ + * ╚═══╩═══╩═══╩═══╝ + * + * Using the buffer gives a more natural ordering for BFTW_QLIFO, and allows + * files to be sorted before adding them to the waiting list. If BFTW_QBUFFER + * is not set, files are pushed directly to the waiting list instead. + * + * Files on the waiting list are waiting to be "serviced" asynchronously by the + * ioq (for example, by an ioq_opendir() or ioq_stat() call). While they are + * being serviced, they are detached from the queue by bftw_queue_detach() and + * are not tracked by the queue at all: + * + * ╔═╗ + * buffer: ║ ║ + * ╠═╩═╦═══╦═══╗ ⎛ ┌───┬───┐ ⎞ + * waiting: ║ g ║ h ║ i ║ ⎜ ioq: │ 𝓮 │ 𝓯 │ ⎟ + * ╠═══╬═══╬═══╬═══╗ ⎝ └───┴───┘ ⎠ + * ready: ║ 𝕒 ║ 𝕓 ║ 𝕔 ║ 𝕕 ║ + * ╚═══╩═══╩═══╩═══╝ + * + * When their async service is complete, files are reattached to the queue by + * bftw_queue_attach(), this time on the ready list: + * + * ╔═╗ + * buffer: ║ ║ + * ╠═╩═╦═══╦═══╗ ⎛ ┌───┐ ⎞ + * waiting: ║ g ║ h ║ i ║ ⎜ ioq: │ 𝓮 │ ⎟ + * ╠═══╬═══╬═══╬═══╦═══╗ ⎝ └───┘ ⎠ + * ready: ║ 𝕒 ║ 𝕓 ║ 𝕔 ║ 𝕕 ║ 𝕗 ║ + * ╚═══╩═══╩═══╩═══╩═══╝ + * + * Files are added to the ready list in the order they are finished by the ioq. + * bftw_queue_pop() pops a file from the ready list if possible. Otherwise, it + * pops from the waiting list, and the file must be serviced synchronously. + * + * However, if BFTW_QORDER is set, files must be popped in the exact order they + * are added to the waiting list (to maintain sorted order). In this case, + * files are added to the waiting and ready lists at the same time. The + * file->ioqueued flag is set while it is in-service, so that bftw() can wait + * for it to be truly ready before using it. + * + * ╔═╗ + * buffer: ║ ║ + * ╠═╩═╦═══╦═══╗ ⎛ ┌───┐ ⎞ + * waiting: ║ g ║ h ║ i ║ ⎜ ioq: │ 𝓮 │ ⎟ + * ╠═══╬═══╬═══╬═══╦═══╦═══╦═══╦═══╦═══╗ ⎝ └───┘ ⎠ + * ready: ║ 𝕒 ║ 𝕓 ║ 𝕔 ║ 𝕕 ║ 𝓮 ║ 𝕗 ║ g ║ h ║ i ║ + * ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝ + * + * If BFTW_QBALANCE is set, queue->imbalance tracks the delta between async + * service (negative) and synchronous service (positive). The queue is + * considered "balanced" when this number is non-negative. Only a balanced + * queue will perform any async service, ensuring work is fairly distributed + * between the main thread and the ioq. + * + * BFTW_QBALANCE is only set for single-threaded ioqs. When an ioq has multiple + * threads, it is faster to wait for the ioq to complete an operation than it is + * to perform it on the main thread. + */ +struct bftw_queue { + /** Queue flags. */ + enum bftw_qflags flags; + /** A buffer of files to be enqueued together. */ + struct bftw_list buffer; + /** A list of files which are waiting to be serviced. */ + struct bftw_list waiting; + /** A list of already-serviced files. */ + struct bftw_list ready; + /** Tracks the imbalance between synchronous and async service. */ + unsigned long imbalance; +}; + +/** Initialize a queue. */ +static void bftw_queue_init(struct bftw_queue *queue, enum bftw_qflags flags) { + queue->flags = flags; + SLIST_INIT(&queue->buffer); + SLIST_INIT(&queue->waiting); + SLIST_INIT(&queue->ready); + queue->imbalance = 0; +} + +/** Add a file to the queue. */ +static void bftw_queue_push(struct bftw_queue *queue, struct bftw_file *file) { + if (queue->flags & BFTW_QBUFFER) { + SLIST_APPEND(&queue->buffer, file); + } else if (queue->flags & BFTW_QLIFO) { + SLIST_PREPEND(&queue->waiting, file); + if (queue->flags & BFTW_QORDER) { + SLIST_PREPEND(&queue->ready, file, ready); + } + } else { + SLIST_APPEND(&queue->waiting, file); + if (queue->flags & BFTW_QORDER) { + SLIST_APPEND(&queue->ready, file, ready); + } + } +} + +/** Add any buffered files to the queue. */ +static void bftw_queue_flush(struct bftw_queue *queue) { + if (!(queue->flags & BFTW_QBUFFER)) { + return; + } + + if (queue->flags & BFTW_QORDER) { + // When sorting, add files to the ready list at the same time + // (and in the same order) as they are added to the waiting list + struct bftw_file **cursor = (queue->flags & BFTW_QLIFO) + ? &queue->ready.head + : queue->ready.tail; + for_slist (struct bftw_file, file, &queue->buffer) { + cursor = SLIST_INSERT(&queue->ready, cursor, file, ready); + } + } + + if (queue->flags & BFTW_QLIFO) { + SLIST_EXTEND(&queue->buffer, &queue->waiting); + } + + SLIST_EXTEND(&queue->waiting, &queue->buffer); +} + +/** Update the queue imbalance. */ +static void bftw_queue_balance(struct bftw_queue *queue, long delta) { + queue->imbalance += delta; +} + +/** Check if the queue is properly balanced for async work. */ +static bool bftw_queue_balanced(const struct bftw_queue *queue) { + if (queue->flags & BFTW_QBALANCE) { + return (long)queue->imbalance >= 0; + } else { + return true; + } +} + +/** Detatch the next waiting file to service it asynchronously. */ +static void bftw_queue_detach(struct bftw_queue *queue, struct bftw_file *file) { + if (file == SLIST_HEAD(&queue->buffer)) { + // To maintain order, we can't detach any files until they're + // added to the waiting/ready lists + bfs_assert(!(queue->flags & BFTW_QORDER)); + SLIST_POP(&queue->buffer); + } else if (file == SLIST_HEAD(&queue->waiting)) { + SLIST_POP(&queue->waiting); + } else { + bfs_bug("Detached file was not buffered or waiting"); + } + + file->ioqueued = true; + bftw_queue_balance(queue, -1); +} + +/** Reattach a serviced file to the queue. */ +static void bftw_queue_attach(struct bftw_queue *queue, struct bftw_file *file) { + file->ioqueued = false; + + if (!(queue->flags & BFTW_QORDER)) { + SLIST_APPEND(&queue->ready, file, ready); + } +} + +/** Get the next waiting file. */ +static struct bftw_file *bftw_queue_waiting(const struct bftw_queue *queue) { + if (!(queue->flags & BFTW_QBUFFER)) { + return SLIST_HEAD(&queue->waiting); + } + + if (queue->flags & BFTW_QORDER) { + // Don't detach files until they're on the waiting/ready lists + return SLIST_HEAD(&queue->waiting); + } + + const struct bftw_list *prefix = &queue->waiting; + const struct bftw_list *suffix = &queue->buffer; + if (queue->flags & BFTW_QLIFO) { + prefix = &queue->buffer; + suffix = &queue->waiting; + } + + struct bftw_file *file = SLIST_HEAD(prefix); + if (!file) { + file = SLIST_HEAD(suffix); + } + return file; +} + +/** Get the next ready file. */ +static struct bftw_file *bftw_queue_ready(const struct bftw_queue *queue) { + return SLIST_HEAD(&queue->ready); +} + +/** Pop a file from the queue. */ +static struct bftw_file *bftw_queue_pop(struct bftw_queue *queue) { + // Don't pop until we've had a chance to sort the buffer + bfs_assert(SLIST_EMPTY(&queue->buffer)); + + struct bftw_file *file = SLIST_POP(&queue->ready, ready); + + if (!file || file == SLIST_HEAD(&queue->waiting)) { + // If no files are ready, try the waiting list. Or, if + // BFTW_QORDER is set, we may need to pop from both lists. + file = SLIST_POP(&queue->waiting); + if (file) { + // This file will be serviced synchronously + bftw_queue_balance(queue, +1); + } + } + + return file; +} + /** * A cache of open directories. */ @@ -354,7 +604,7 @@ static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_fil } SLIST_ITEM_INIT(file); - SLIST_ITEM_INIT(file, to_read); + SLIST_ITEM_INIT(file, ready); LIST_ITEM_INIT(file, lru); file->refcount = 1; @@ -430,17 +680,11 @@ struct bftw_state { struct ioq *ioq; /** The number of I/O threads. */ size_t nthreads; - /** Tracks the imbalance between main thread and background I/O. */ - long imbalance; - - /** A batch of directories to open. */ - struct bftw_list dir_batch; - /** The queue of directories to open. */ - struct bftw_list to_open; - /** The queue of directories to read. */ - struct bftw_list to_read; + /** The queue of unpinned directories to unwrap. */ struct bftw_list to_close; + /** The queue of directories to open/read. */ + struct bftw_queue dirq; /** A batch of files to enqueue. */ struct bftw_list file_batch; @@ -543,13 +787,22 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg state->dir_flags |= BFS_DIR_WHITEOUTS; } - state->imbalance = 0; - - SLIST_INIT(&state->dir_batch); - SLIST_INIT(&state->to_open); - SLIST_INIT(&state->to_read); SLIST_INIT(&state->to_close); + enum bftw_qflags qflags = 0; + if (state->strategy != BFTW_BFS && !(state->flags & BFTW_BUFFER)) { + // For a depth-first, unbuffered search, queue directories in + // LIFO order + qflags |= BFTW_QBUFFER | BFTW_QLIFO; + } + if (state->flags & BFTW_SORT) { + qflags |= BFTW_QORDER; + } + if (nthreads == 1) { + qflags |= BFTW_QBALANCE; + } + bftw_queue_init(&state->dirq, qflags); + SLIST_INIT(&state->file_batch); SLIST_INIT(&state->to_visit); @@ -575,12 +828,6 @@ static void bftw_unpin_dir(struct bftw_state *state, struct bftw_file *file, boo } } -/** Adjust the I/O queue balance. */ -static void bftw_ioq_balance(struct bftw_state *state, long delta) { - // Avoid signed overflow - state->imbalance = (unsigned long)state->imbalance + (unsigned long)delta; -} - /** Pop a response from the I/O queue. */ static int bftw_ioq_pop(struct bftw_state *state, bool block) { struct ioq *ioq = state->ioq; @@ -612,7 +859,6 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { case IOQ_OPENDIR: file = ent->ptr; - file->ioqueued = false; ++cache->capacity; parent = file->parent; @@ -627,9 +873,7 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { bftw_freedir(cache, dir); } - if (!(state->flags & BFTW_SORT)) { - SLIST_APPEND(&state->to_read, file, to_read); - } + bftw_queue_attach(&state->dirq, file); break; case IOQ_STAT: @@ -647,19 +891,14 @@ static int bftw_ioq_reserve(struct bftw_state *state) { return -1; } - // With only one background thread, we should balance I/O between it and - // the main thread. With more than one background thread, it's faster - // to wait on background I/O than it is to do it on the main thread. - bool balance = state->nthreads <= 1; - if (balance && state->imbalance < 0) { - return -1; - } - if (ioq_capacity(ioq) > 0) { return 0; } - if (bftw_ioq_pop(state, !balance) < 0) { + // With more than one background thread, it's faster to wait on + // background I/O than it is to do it on the main thread + bool block = state->nthreads > 1; + if (bftw_ioq_pop(state, block) < 0) { return -1; } @@ -892,9 +1131,7 @@ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { goto free; } - file->ioqueued = true; --cache->capacity; - bftw_ioq_balance(state, -1); return 0; free: @@ -908,39 +1145,26 @@ fail: } /** Open a batch of directories asynchronously. */ -static void bftw_ioq_opendirs(struct bftw_state *state, struct bftw_list *queue) { - for_slist (struct bftw_file, dir, queue) { - if (bftw_ioq_opendir(state, dir) != 0) { +static void bftw_ioq_opendirs(struct bftw_state *state) { + while (bftw_queue_balanced(&state->dirq)) { + struct bftw_file *dir = bftw_queue_waiting(&state->dirq); + if (!dir) { + break; + } + + if (bftw_ioq_opendir(state, dir) == 0) { + bftw_queue_detach(&state->dirq, dir); + } else { break; } - SLIST_POP(queue); } } /** Push a directory onto the queue. */ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { bfs_assert(file->type == BFS_DIR); - - struct bftw_list *queue; - if (state->strategy == BFTW_BFS || (state->flags & BFTW_BUFFER)) { - // In breadth-first mode, or if we're already buffering files, - // we can push directly to the to_open queue - queue = &state->to_open; - } else { - // For a depth-first, unbuffered search, add directories to a - // batch, then push the patch to the front of the queue - queue = &state->dir_batch; - } - - SLIST_APPEND(queue, file); - - if (state->flags & BFTW_SORT) { - // When sorting, directories are kept in order on the to_read - // list; otherwise, they are only added once they are open - SLIST_APPEND(&state->to_read, file, to_read); - } - - bftw_ioq_opendirs(state, queue); + bftw_queue_push(&state->dirq, file); + bftw_ioq_opendirs(state); } /** Pop a directory to read from the queue. */ @@ -956,9 +1180,9 @@ static bool bftw_pop_dir(struct bftw_state *state) { return false; } } else { - while (SLIST_EMPTY(&state->to_read)) { + while (!bftw_queue_ready(&state->dirq)) { // Block if we have no other files/dirs to visit, or no room in the cache - bool have_dirs = !SLIST_EMPTY(&state->to_open); + bool have_dirs = bftw_queue_waiting(&state->dirq); bool have_room = cache->capacity > 0 && cache->dirlimit > 0; bool block = !(have_dirs || have_files) || !have_room; @@ -968,10 +1192,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { } } - struct bftw_file *file = SLIST_POP(&state->to_read, to_read); - if (!file || file == state->to_open.head) { - file = SLIST_POP(&state->to_open); - } + struct bftw_file *file = bftw_queue_pop(&state->dirq); if (!file) { return false; } @@ -1051,8 +1272,6 @@ static struct bfs_dir *bftw_file_opendir(struct bftw_state *state, struct bftw_f return NULL; } - bftw_ioq_balance(state, +1); - if (bfs_opendir(dir, fd, NULL, state->dir_flags) != 0) { bftw_freedir(cache, dir); return NULL; @@ -1428,14 +1647,13 @@ static void bftw_batch_finish(struct bftw_state *state) { } if (state->strategy != BFTW_BFS) { - SLIST_EXTEND(&state->dir_batch, &state->to_open); SLIST_EXTEND(&state->file_batch, &state->to_visit); } - SLIST_EXTEND(&state->to_open, &state->dir_batch); SLIST_EXTEND(&state->to_visit, &state->file_batch); - bftw_ioq_opendirs(state, &state->to_open); + bftw_queue_flush(&state->dirq); + bftw_ioq_opendirs(state); } /** Close the current directory. */ @@ -1525,7 +1743,7 @@ static int bftw_state_destroy(struct bftw_state *state) { state->ioq = NULL; } - SLIST_EXTEND(&state->to_open, &state->dir_batch); + bftw_queue_flush(&state->dirq); SLIST_EXTEND(&state->to_visit, &state->file_batch); do { bftw_gc(state, BFTW_VISIT_NONE); -- cgit v1.2.3 From 62c998c159b4502c116a0dba505b3d775b451954 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 29 Jan 2024 19:21:56 -0500 Subject: bftw: Use a bftw_queue for files too --- src/bftw.c | 57 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 26 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index ce08dba..400307b 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -400,6 +400,13 @@ static struct bftw_file *bftw_queue_ready(const struct bftw_queue *queue) { return SLIST_HEAD(&queue->ready); } +/** Check if a queue is empty. */ +static bool bftw_queue_empty(const struct bftw_queue *queue) { + return SLIST_EMPTY(&queue->buffer) + && SLIST_EMPTY(&queue->waiting) + && SLIST_EMPTY(&queue->ready); +} + /** Pop a file from the queue. */ static struct bftw_file *bftw_queue_pop(struct bftw_queue *queue) { // Don't pop until we've had a chance to sort the buffer @@ -683,14 +690,11 @@ struct bftw_state { /** The queue of unpinned directories to unwrap. */ struct bftw_list to_close; + /** The queue of files to visit. */ + struct bftw_queue fileq; /** The queue of directories to open/read. */ struct bftw_queue dirq; - /** A batch of files to enqueue. */ - struct bftw_list file_batch; - /** The queue of files to visit. */ - struct bftw_list to_visit; - /** The current path. */ dchar *path; /** The current file. */ @@ -790,21 +794,26 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg SLIST_INIT(&state->to_close); enum bftw_qflags qflags = 0; - if (state->strategy != BFTW_BFS && !(state->flags & BFTW_BUFFER)) { - // For a depth-first, unbuffered search, queue directories in - // LIFO order + if (state->strategy != BFTW_BFS) { qflags |= BFTW_QBUFFER | BFTW_QLIFO; } + if (state->flags & BFTW_BUFFER) { + qflags |= BFTW_QBUFFER; + } if (state->flags & BFTW_SORT) { qflags |= BFTW_QORDER; } if (nthreads == 1) { qflags |= BFTW_QBALANCE; } - bftw_queue_init(&state->dirq, qflags); + bftw_queue_init(&state->fileq, qflags); - SLIST_INIT(&state->file_batch); - SLIST_INIT(&state->to_visit); + if (state->strategy == BFTW_BFS || (state->flags & BFTW_BUFFER)) { + // In breadth-first mode, or if we're already buffering files, + // directories can be queued in FIFO order + qflags &= ~(BFTW_QBUFFER | BFTW_QLIFO); + } + bftw_queue_init(&state->dirq, qflags); state->path = NULL; state->file = NULL; @@ -1172,10 +1181,10 @@ static bool bftw_pop_dir(struct bftw_state *state) { bfs_assert(!state->file); struct bftw_cache *cache = &state->cache; - bool have_files = !SLIST_EMPTY(&state->to_visit); if (state->flags & BFTW_SORT) { // Keep strict breadth-first order when sorting + bool have_files = bftw_queue_ready(&state->fileq); if (state->strategy == BFTW_BFS && have_files) { return false; } @@ -1183,6 +1192,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { while (!bftw_queue_ready(&state->dirq)) { // Block if we have no other files/dirs to visit, or no room in the cache bool have_dirs = bftw_queue_waiting(&state->dirq); + bool have_files = !bftw_queue_empty(&state->fileq); bool have_room = cache->capacity > 0 && cache->dirlimit > 0; bool block = !(have_dirs || have_files) || !have_room; @@ -1208,7 +1218,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { /** Pop a file to visit from the queue. */ static bool bftw_pop_file(struct bftw_state *state) { bfs_assert(!state->file); - state->file = SLIST_POP(&state->to_visit); + state->file = bftw_queue_pop(&state->fileq); return state->file; } @@ -1640,17 +1650,12 @@ static void bftw_list_sort(struct bftw_list *list) { SLIST_EXTEND(list, &right); } -/** Finish adding a batch of files. */ -static void bftw_batch_finish(struct bftw_state *state) { +/** Flush all the queue buffers. */ +static void bftw_flush(struct bftw_state *state) { if (state->flags & BFTW_SORT) { - bftw_list_sort(&state->file_batch); + bftw_list_sort(&state->fileq.buffer); } - - if (state->strategy != BFTW_BFS) { - SLIST_EXTEND(&state->file_batch, &state->to_visit); - } - - SLIST_EXTEND(&state->to_visit, &state->file_batch); + bftw_queue_flush(&state->fileq); bftw_queue_flush(&state->dirq); bftw_ioq_opendirs(state); @@ -1662,7 +1667,7 @@ static int bftw_closedir(struct bftw_state *state) { return -1; } - bftw_batch_finish(state); + bftw_flush(state); return 0; } @@ -1695,7 +1700,7 @@ static int bftw_visit(struct bftw_state *state, const char *name) { file->type = state->de->type; } - SLIST_APPEND(&state->file_batch, file); + bftw_queue_push(&state->fileq, file); return 0; } @@ -1744,7 +1749,7 @@ static int bftw_state_destroy(struct bftw_state *state) { } bftw_queue_flush(&state->dirq); - SLIST_EXTEND(&state->to_visit, &state->file_batch); + bftw_queue_flush(&state->fileq); do { bftw_gc(state, BFTW_VISIT_NONE); } while (bftw_pop_dir(state) || bftw_pop_file(state)); @@ -1766,7 +1771,7 @@ static int bftw_impl(struct bftw_state *state) { return -1; } } - bftw_batch_finish(state); + bftw_flush(state); while (true) { while (bftw_pop_dir(state)) { -- cgit v1.2.3 From 8ecd1dc4a60e063251963305b98888236b8d9825 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 30 Jan 2024 14:00:03 -0500 Subject: bftw: Optimize -s -j2 searches Maintaining balance and strict ordering at the same time forces too much work onto the main thread. --- src/bftw.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 400307b..ab930f1 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -802,8 +802,7 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg } if (state->flags & BFTW_SORT) { qflags |= BFTW_QORDER; - } - if (nthreads == 1) { + } else if (nthreads == 1) { qflags |= BFTW_QBALANCE; } bftw_queue_init(&state->fileq, qflags); -- cgit v1.2.3 From 670ebd97fb431e830b1500b2e7e8013b121fb2c5 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 31 Jan 2024 20:58:43 -0500 Subject: bftw: Actually stop if the callback returns BFTW_STOP Otherwise, bftw_ids() or bftw_eds() might keep going! Fixes: 5f16169 ("bftw: Share the bftw_state between iterations of ids/eds") --- src/bftw.c | 2 +- tests/bsd/s_quit.out | 1 + tests/bsd/s_quit.sh | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 tests/bsd/s_quit.out create mode 100644 tests/bsd/s_quit.sh (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index ab930f1..c9ba67e 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -1791,7 +1791,7 @@ static int bftw_impl(struct bftw_state *state) { break; } if (bftw_visit(state, NULL) != 0) { - break; + return -1; } } diff --git a/tests/bsd/s_quit.out b/tests/bsd/s_quit.out new file mode 100644 index 0000000..5ea492b --- /dev/null +++ b/tests/bsd/s_quit.out @@ -0,0 +1 @@ +basic/j/foo diff --git a/tests/bsd/s_quit.sh b/tests/bsd/s_quit.sh new file mode 100644 index 0000000..6bd55ab --- /dev/null +++ b/tests/bsd/s_quit.sh @@ -0,0 +1,4 @@ +# Regression test: bfs -S ids -s -name foo -quit would not actually quit, +# ending up in a confused state and erroring/crashing + +bfs_diff -s basic -name foo -print -quit -- cgit v1.2.3 From bb060c513214aec07993946764180c17106b4344 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 1 Feb 2024 09:28:36 -0500 Subject: bftw: Kill trivial bftw_queue_balance() helper --- src/bftw.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index c9ba67e..7b32db6 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -330,11 +330,6 @@ static void bftw_queue_flush(struct bftw_queue *queue) { SLIST_EXTEND(&queue->waiting, &queue->buffer); } -/** Update the queue imbalance. */ -static void bftw_queue_balance(struct bftw_queue *queue, long delta) { - queue->imbalance += delta; -} - /** Check if the queue is properly balanced for async work. */ static bool bftw_queue_balanced(const struct bftw_queue *queue) { if (queue->flags & BFTW_QBALANCE) { @@ -358,7 +353,7 @@ static void bftw_queue_detach(struct bftw_queue *queue, struct bftw_file *file) } file->ioqueued = true; - bftw_queue_balance(queue, -1); + --queue->imbalance; } /** Reattach a serviced file to the queue. */ @@ -420,7 +415,7 @@ static struct bftw_file *bftw_queue_pop(struct bftw_queue *queue) { file = SLIST_POP(&queue->waiting); if (file) { // This file will be serviced synchronously - bftw_queue_balance(queue, +1); + ++queue->imbalance; } } -- cgit v1.2.3 From 76ffc8d30cb1160d55d855d8ac630a2b9075fbcf Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 1 Feb 2024 09:38:11 -0500 Subject: bftw: Allow forcing bfs_dir allocation from the main thread When sorting, we can be forced to pop an unopened directory. If enough other directories are already open, that can lead to ENOMEM when we try to open it synchronously. To avoid this, force allocations from the main thread to be attempted even if they would go over the limit. Also, fix the accounting in bftw_allocdir() if allocation fails. --- src/bftw.c | 47 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 12 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 7b32db6..b3c0ba7 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -437,10 +437,13 @@ struct bftw_cache { /** bftw_file arena. */ struct varena files; + /** bfs_dir arena. */ struct arena dirs; /** Remaining bfs_dir capacity. */ - size_t dirlimit; + size_t dir_limit; + /** Excess force-allocated bfs_dirs. */ + size_t dir_excess; }; /** Initialize a cache. */ @@ -450,28 +453,48 @@ static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) { cache->capacity = capacity; VARENA_INIT(&cache->files, struct bftw_file, name); + bfs_dir_arena(&cache->dirs); - cache->dirlimit = capacity - 1; - if (cache->dirlimit > 1024) { - cache->dirlimit = 1024; + cache->dir_limit = capacity - 1; + if (cache->dir_limit > 1024) { + cache->dir_limit = 1024; } + + cache->dir_excess = 0; } /** Allocate a directory. */ -static struct bfs_dir *bftw_allocdir(struct bftw_cache *cache) { - if (cache->dirlimit == 0) { +static struct bfs_dir *bftw_allocdir(struct bftw_cache *cache, bool force) { + size_t limit = cache->dir_limit; + size_t excess = cache->dir_excess; + + if (cache->dir_limit > 0) { + --cache->dir_limit; + } else if (force) { + ++cache->dir_excess; + } else { errno = ENOMEM; return NULL; } - --cache->dirlimit; - return arena_alloc(&cache->dirs); + struct bfs_dir *dir = arena_alloc(&cache->dirs); + if (!dir) { + cache->dir_limit = limit; + cache->dir_excess = excess; + } + + return dir; } /** Free a directory. */ static void bftw_freedir(struct bftw_cache *cache, struct bfs_dir *dir) { - ++cache->dirlimit; + if (cache->dir_excess > 0) { + --cache->dir_excess; + } else { + ++cache->dir_limit; + } + arena_free(&cache->dirs, dir); } @@ -1125,7 +1148,7 @@ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { goto unpin; } - struct bfs_dir *dir = bftw_allocdir(cache); + struct bfs_dir *dir = bftw_allocdir(cache, false); if (!dir) { goto unpin; } @@ -1187,7 +1210,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { // Block if we have no other files/dirs to visit, or no room in the cache bool have_dirs = bftw_queue_waiting(&state->dirq); bool have_files = !bftw_queue_empty(&state->fileq); - bool have_room = cache->capacity > 0 && cache->dirlimit > 0; + bool have_room = cache->capacity > 0; bool block = !(have_dirs || have_files) || !have_room; if (bftw_ioq_pop(state, block) < 0) { @@ -1271,7 +1294,7 @@ static struct bfs_dir *bftw_file_opendir(struct bftw_state *state, struct bftw_f } struct bftw_cache *cache = &state->cache; - struct bfs_dir *dir = bftw_allocdir(cache); + struct bfs_dir *dir = bftw_allocdir(cache, true); if (!dir) { return NULL; } -- cgit v1.2.3 From 710c083ff02eb1cc5b8daa6778784f3d1cd3c08d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 1 Feb 2024 09:46:04 -0500 Subject: bftw: Don't immediately pin open directories It is undesirable to close a directory that we haven't read yet to free up cache capacity, but it's worse to fail to open the next directory because too many upcoming directories are pinned. This could happen when sorting, because then we can't prioritize the already-opened ones. --- src/bftw.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index b3c0ba7..e29bd27 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -659,8 +659,6 @@ static void bftw_file_set_dir(struct bftw_cache *cache, struct bftw_file *file, file->fd = bfs_dirfd(dir); bftw_cache_add(cache, file); } - - bftw_cache_pin(cache, file); } /** Free a bftw_file. */ @@ -1318,7 +1316,7 @@ static int bftw_opendir(struct bftw_state *state) { struct bftw_file *file = state->file; state->dir = file->dir; if (state->dir) { - return 0; + goto pin; } if (bftw_build_path(state, NULL) != 0) { @@ -1328,8 +1326,11 @@ static int bftw_opendir(struct bftw_state *state) { state->dir = bftw_file_opendir(state, file, state->path); if (!state->dir) { state->direrror = errno; + return 0; } +pin: + bftw_cache_pin(&state->cache, file); return 0; } @@ -1577,7 +1578,7 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { int ret = 0; struct bftw_file *file = state->file; - if (file && file->dir) { + if (file && state->dir) { bftw_unpin_dir(state, file, true); } state->dir = NULL; -- cgit v1.2.3 From 85e8344637ac4ac28e3638764f28d411781b8e96 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 1 Feb 2024 09:54:47 -0500 Subject: bftw: Always block in bftw_pop_dir() with multiple threads --- src/bftw.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index e29bd27..debf92c 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -1191,27 +1191,38 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { bftw_ioq_opendirs(state); } +/** Check if we should block on the ioq when popping a directory. */ +static bool bftw_block_for_dir(const struct bftw_state *state) { + // Always block with more than one background thread + if (state->nthreads > 1) { + return true; + } + + // Block if the cache is full + if (state->cache.capacity == 0) { + return true; + } + + // Block if we have no other files/dirs to visit + if (!bftw_queue_waiting(&state->dirq) && bftw_queue_empty(&state->fileq)) { + return true; + } + + return false; +} + /** Pop a directory to read from the queue. */ static bool bftw_pop_dir(struct bftw_state *state) { bfs_assert(!state->file); - struct bftw_cache *cache = &state->cache; - if (state->flags & BFTW_SORT) { // Keep strict breadth-first order when sorting - bool have_files = bftw_queue_ready(&state->fileq); - if (state->strategy == BFTW_BFS && have_files) { + if (state->strategy == BFTW_BFS && bftw_queue_ready(&state->fileq)) { return false; } } else { while (!bftw_queue_ready(&state->dirq)) { - // Block if we have no other files/dirs to visit, or no room in the cache - bool have_dirs = bftw_queue_waiting(&state->dirq); - bool have_files = !bftw_queue_empty(&state->fileq); - bool have_room = cache->capacity > 0; - bool block = !(have_dirs || have_files) || !have_room; - - if (bftw_ioq_pop(state, block) < 0) { + if (bftw_ioq_pop(state, bftw_block_for_dir(state)) < 0) { break; } } -- cgit v1.2.3 From 10b6da04521cf3f65f3c47bece8e2e5e6e664d6d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 5 Feb 2024 13:54:01 -0500 Subject: mtab: Take the basename directly in bfs_might_be_mount() This avoids some hot xbaseoff() calls. --- src/bftw.c | 2 +- src/mtab.c | 3 +-- src/mtab.h | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index debf92c..d392aed 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -1390,7 +1390,7 @@ static bool bftw_need_stat(const struct bftw_state *state) { // need to stat() to get the correct type. We don't need to // check for directories because they can only be mounted over // by other directories. - if (bfs_might_be_mount(state->mtab, ftwbuf->path)) { + if (bfs_might_be_mount(state->mtab, ftwbuf->path + ftwbuf->nameoff)) { return true; } #endif diff --git a/src/mtab.c b/src/mtab.c index cc726a2..c76f403 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -271,8 +271,7 @@ const char *bfs_fstype(const struct bfs_mtab *mtab, const struct bfs_stat *statb } } -bool bfs_might_be_mount(const struct bfs_mtab *mtab, const char *path) { - const char *name = path + xbaseoff(path); +bool bfs_might_be_mount(const struct bfs_mtab *mtab, const char *name) { return trie_find_str(&mtab->names, name); } diff --git a/src/mtab.h b/src/mtab.h index ca4372c..d99d78f 100644 --- a/src/mtab.h +++ b/src/mtab.h @@ -43,12 +43,12 @@ const char *bfs_fstype(const struct bfs_mtab *mtab, const struct bfs_stat *statb * * @param mtab * The current mount table. - * @param path - * The path to check. + * @param name + * The name of the file to check. * @return * Whether the named file could be a mount point. */ -bool bfs_might_be_mount(const struct bfs_mtab *mtab, const char *path); +bool bfs_might_be_mount(const struct bfs_mtab *mtab, const char *name); /** * Free a mount table. -- cgit v1.2.3 From 89ecb2a08467cd8aa6ba70f8519df494652cac96 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 5 Feb 2024 14:02:55 -0500 Subject: bftw: stat() files asynchronously --- src/bftw.c | 677 +++++++++++++++++++++++++++++++++------------- src/bftw.h | 20 +- src/eval.c | 20 +- tests/bfs/execdir_plus.sh | 2 +- 4 files changed, 507 insertions(+), 212 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index d392aed..664651c 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -36,58 +36,130 @@ #include #include +/** Initialize a bftw_stat cache. */ +static void bftw_stat_init(struct bftw_stat *bufs, struct bfs_stat *stat_buf, struct bfs_stat *lstat_buf) { + bufs->stat_buf = stat_buf; + bufs->lstat_buf = lstat_buf; + bufs->stat_err = -1; + bufs->lstat_err = -1; +} + +/** Fill a bftw_stat cache from another one. */ +static void bftw_stat_fill(struct bftw_stat *dest, const struct bftw_stat *src) { + if (dest->stat_err < 0 && src->stat_err >= 0) { + dest->stat_buf = src->stat_buf; + dest->stat_err = src->stat_err; + } + + if (dest->lstat_err < 0 && src->lstat_err >= 0) { + dest->lstat_buf = src->lstat_buf; + dest->lstat_err = src->lstat_err; + } +} + +/** Cache a bfs_stat() result. */ +static void bftw_stat_cache(struct bftw_stat *bufs, enum bfs_stat_flags flags, const struct bfs_stat *buf, int err) { + if (flags & BFS_STAT_NOFOLLOW) { + bufs->lstat_buf = buf; + bufs->lstat_err = err; + if (err || !S_ISLNK(buf->mode)) { + // Non-link, so share stat info + bufs->stat_buf = buf; + bufs->stat_err = err; + } + } else if (flags & BFS_STAT_TRYFOLLOW) { + if (err) { + bufs->stat_err = err; + } else if (S_ISLNK(buf->mode)) { + bufs->lstat_buf = buf; + bufs->lstat_err = err; + bufs->stat_err = ENOENT; + } else { + bufs->stat_buf = buf; + bufs->stat_err = err; + } + } else { + bufs->stat_buf = buf; + bufs->stat_err = err; + } +} + /** Caching bfs_stat(). */ -static const struct bfs_stat *bftw_stat_impl(struct BFTW *ftwbuf, struct bftw_stat *cache, enum bfs_stat_flags flags) { - if (!cache->buf) { - if (cache->error) { - errno = cache->error; - } else if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, flags, &cache->storage) == 0) { - cache->buf = &cache->storage; +static const struct bfs_stat *bftw_stat_impl(struct BFTW *ftwbuf, enum bfs_stat_flags flags) { + struct bftw_stat *bufs = &ftwbuf->stat_bufs; + struct bfs_stat *buf; + + if (flags & BFS_STAT_NOFOLLOW) { + buf = (struct bfs_stat *)bufs->lstat_buf; + if (bufs->lstat_err == 0) { + return buf; + } else if (bufs->lstat_err > 0) { + errno = bufs->lstat_err; + return NULL; + } + } else { + buf = (struct bfs_stat *)bufs->stat_buf; + if (bufs->stat_err == 0) { + return buf; + } else if (bufs->stat_err > 0) { + errno = bufs->stat_err; + return NULL; + } + } + + struct bfs_stat *ret; + int err; + if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, flags, buf) == 0) { + ret = buf; + err = 0; #ifdef S_IFWHT - } else if (errno == ENOENT && ftwbuf->type == BFS_WHT) { - // This matches the behavior of FTS_WHITEOUT on BSD - memset(&cache->storage, 0, sizeof(cache->storage)); - cache->storage.mode = S_IFWHT; - cache->buf = &cache->storage; + } else if (errno == ENOENT && ftwbuf->type == BFS_WHT) { + // This matches the behavior of FTS_WHITEOUT on BSD + ret = memset(buf, 0, sizeof(*buf)); + ret->mode = S_IFWHT; + err = 0; #endif - } else { - cache->error = errno; - } + } else { + ret = NULL; + err = errno; } - return cache->buf; + bftw_stat_cache(bufs, flags, ret, err); + return ret; } const struct bfs_stat *bftw_stat(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { struct BFTW *mutbuf = (struct BFTW *)ftwbuf; const struct bfs_stat *ret; - if (flags & BFS_STAT_NOFOLLOW) { - ret = bftw_stat_impl(mutbuf, &mutbuf->lstat_cache, BFS_STAT_NOFOLLOW); - if (ret && !S_ISLNK(ret->mode) && !mutbuf->stat_cache.buf) { - // Non-link, so share stat info - mutbuf->stat_cache.buf = ret; + if (flags & BFS_STAT_TRYFOLLOW) { + ret = bftw_stat_impl(mutbuf, BFS_STAT_FOLLOW); + if (!ret && errno_is_like(ENOENT)) { + ret = bftw_stat_impl(mutbuf, BFS_STAT_NOFOLLOW); } } else { - ret = bftw_stat_impl(mutbuf, &mutbuf->stat_cache, BFS_STAT_FOLLOW); - if (!ret && (flags & BFS_STAT_TRYFOLLOW) && errno_is_like(ENOENT)) { - ret = bftw_stat_impl(mutbuf, &mutbuf->lstat_cache, BFS_STAT_NOFOLLOW); - } + ret = bftw_stat_impl(mutbuf, flags); } return ret; } const struct bfs_stat *bftw_cached_stat(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { + const struct bftw_stat *bufs = &ftwbuf->stat_bufs; + if (flags & BFS_STAT_NOFOLLOW) { - return ftwbuf->lstat_cache.buf; - } else if (ftwbuf->stat_cache.buf) { - return ftwbuf->stat_cache.buf; - } else if ((flags & BFS_STAT_TRYFOLLOW) && error_is_like(ftwbuf->stat_cache.error, ENOENT)) { - return ftwbuf->lstat_cache.buf; - } else { - return NULL; + if (bufs->lstat_err == 0) { + return bufs->lstat_buf; + } + } else if (bufs->stat_err == 0) { + return bufs->stat_buf; + } else if ((flags & BFS_STAT_TRYFOLLOW) && error_is_like(bufs->stat_err, ENOENT)) { + if (bufs->lstat_err == 0) { + return bufs->lstat_buf; + } } + + return NULL; } enum bfs_type bftw_type(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { @@ -156,6 +228,9 @@ struct bftw_file { /** The inode number, for cycle detection. */ ino_t ino; + /** Cached bfs_stat() info. */ + struct bftw_stat stat_bufs; + /** The offset of this file in the full path. */ size_t nameoff; /** The length of the file's name. */ @@ -276,6 +351,10 @@ struct bftw_queue { struct bftw_list waiting; /** A list of already-serviced files. */ struct bftw_list ready; + /** The current size of the queue. */ + size_t size; + /** The number of files currently in-service. */ + size_t ioqueued; /** Tracks the imbalance between synchronous and async service. */ unsigned long imbalance; }; @@ -286,6 +365,8 @@ static void bftw_queue_init(struct bftw_queue *queue, enum bftw_qflags flags) { SLIST_INIT(&queue->buffer); SLIST_INIT(&queue->waiting); SLIST_INIT(&queue->ready); + queue->size = 0; + queue->ioqueued = 0; queue->imbalance = 0; } @@ -304,6 +385,8 @@ static void bftw_queue_push(struct bftw_queue *queue, struct bftw_file *file) { SLIST_APPEND(&queue->ready, file, ready); } } + + ++queue->size; } /** Add any buffered files to the queue. */ @@ -339,8 +422,19 @@ static bool bftw_queue_balanced(const struct bftw_queue *queue) { } } -/** Detatch the next waiting file to service it asynchronously. */ -static void bftw_queue_detach(struct bftw_queue *queue, struct bftw_file *file) { +/** Update the queue balance for (a)sync service. */ +static void bftw_queue_rebalance(struct bftw_queue *queue, bool async) { + if (async) { + --queue->imbalance; + } else { + ++queue->imbalance; + } +} + +/** Detatch the next waiting file. */ +static void bftw_queue_detach(struct bftw_queue *queue, struct bftw_file *file, bool async) { + bfs_assert(!file->ioqueued); + if (file == SLIST_HEAD(&queue->buffer)) { // To maintain order, we can't detach any files until they're // added to the waiting/ready lists @@ -352,19 +446,34 @@ static void bftw_queue_detach(struct bftw_queue *queue, struct bftw_file *file) bfs_bug("Detached file was not buffered or waiting"); } - file->ioqueued = true; - --queue->imbalance; + if (async) { + file->ioqueued = true; + ++queue->ioqueued; + bftw_queue_rebalance(queue, true); + } } /** Reattach a serviced file to the queue. */ -static void bftw_queue_attach(struct bftw_queue *queue, struct bftw_file *file) { - file->ioqueued = false; +static void bftw_queue_attach(struct bftw_queue *queue, struct bftw_file *file, bool async) { + if (async) { + bfs_assert(file->ioqueued); + file->ioqueued = false; + --queue->ioqueued; + } else { + bfs_assert(!file->ioqueued); + } if (!(queue->flags & BFTW_QORDER)) { SLIST_APPEND(&queue->ready, file, ready); } } +/** Make a file ready immediately. */ +static void bftw_queue_skip(struct bftw_queue *queue, struct bftw_file *file) { + bftw_queue_detach(queue, file, false); + bftw_queue_attach(queue, file, false); +} + /** Get the next waiting file. */ static struct bftw_file *bftw_queue_waiting(const struct bftw_queue *queue) { if (!(queue->flags & BFTW_QBUFFER)) { @@ -395,13 +504,6 @@ static struct bftw_file *bftw_queue_ready(const struct bftw_queue *queue) { return SLIST_HEAD(&queue->ready); } -/** Check if a queue is empty. */ -static bool bftw_queue_empty(const struct bftw_queue *queue) { - return SLIST_EMPTY(&queue->buffer) - && SLIST_EMPTY(&queue->waiting) - && SLIST_EMPTY(&queue->ready); -} - /** Pop a file from the queue. */ static struct bftw_file *bftw_queue_pop(struct bftw_queue *queue) { // Don't pop until we've had a chance to sort the buffer @@ -413,10 +515,10 @@ static struct bftw_file *bftw_queue_pop(struct bftw_queue *queue) { // If no files are ready, try the waiting list. Or, if // BFTW_QORDER is set, we may need to pop from both lists. file = SLIST_POP(&queue->waiting); - if (file) { - // This file will be serviced synchronously - ++queue->imbalance; - } + } + + if (file) { + --queue->size; } return file; @@ -444,6 +546,9 @@ struct bftw_cache { size_t dir_limit; /** Excess force-allocated bfs_dirs. */ size_t dir_excess; + + /** bfs_stat arena. */ + struct arena stat_bufs; }; /** Initialize a cache. */ @@ -462,6 +567,8 @@ static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) { } cache->dir_excess = 0; + + ARENA_INIT(&cache->stat_bufs, struct bfs_stat); } /** Allocate a directory. */ @@ -603,8 +710,9 @@ static void bftw_cache_destroy(struct bftw_cache *cache) { bfs_assert(LIST_EMPTY(cache)); bfs_assert(!cache->target); - varena_destroy(&cache->files); + arena_destroy(&cache->stat_bufs); arena_destroy(&cache->dirs); + varena_destroy(&cache->files); } /** Create a new bftw_file. */ @@ -642,6 +750,8 @@ static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_fil file->dev = -1; file->ino = -1; + bftw_stat_init(&file->stat_bufs, NULL, NULL); + file->namelen = namelen; memcpy(file->name, name, namelen + 1); @@ -661,6 +771,21 @@ static void bftw_file_set_dir(struct bftw_cache *cache, struct bftw_file *file, } } +/** Free a file's cached stat() buffers. */ +static void bftw_stat_recycle(struct bftw_cache *cache, struct bftw_file *file) { + struct bftw_stat *bufs = &file->stat_bufs; + + struct bfs_stat *stat_buf = (struct bfs_stat *)bufs->stat_buf; + struct bfs_stat *lstat_buf = (struct bfs_stat *)bufs->lstat_buf; + if (stat_buf) { + arena_free(&cache->stat_bufs, stat_buf); + } else if (lstat_buf) { + arena_free(&cache->stat_bufs, lstat_buf); + } + + bftw_stat_init(bufs, NULL, NULL); +} + /** Free a bftw_file. */ static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) { bfs_assert(file->refcount == 0); @@ -669,6 +794,8 @@ static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) { bftw_file_close(cache, file); } + bftw_stat_recycle(cache, file); + varena_free(&cache->files, file, file->namelen + 1); } @@ -729,6 +856,10 @@ struct bftw_state { /** Extra data about the current file. */ struct BFTW ftwbuf; + /** stat() buffer storage. */ + struct bfs_stat stat_buf; + /** lstat() buffer storage. */ + struct bfs_stat lstat_buf; }; /** Check if we have to buffer files before visiting them. */ @@ -755,6 +886,11 @@ static bool bftw_must_buffer(const struct bftw_state *state) { return true; } + if ((state->flags & BFTW_STAT) && state->nthreads > 1) { + // We will be buffering every file anyway for ioq_stat() + return true; + } + return false; } @@ -841,19 +977,32 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg return 0; } -/** Unpin a directory, and possibly queue it for unwrapping. */ -static void bftw_unpin_dir(struct bftw_state *state, struct bftw_file *file, bool force) { - bftw_cache_unpin(&state->cache, file); +/** Queue a directory for unwrapping. */ +static void bftw_delayed_unwrap(struct bftw_state *state, struct bftw_file *file) { + bfs_assert(file->dir); - if (file->dir && (force || file->pincount == 0)) { - if (!SLIST_ATTACHED(&state->to_close, file)) { - SLIST_APPEND(&state->to_close, file); - } + if (!SLIST_ATTACHED(&state->to_close, file, ready)) { + SLIST_APPEND(&state->to_close, file, ready); + } +} + +/** Unpin a file's parent. */ +static void bftw_unpin_parent(struct bftw_state *state, struct bftw_file *file, bool unwrap) { + struct bftw_file *parent = file->parent; + if (!parent) { + return; + } + + bftw_cache_unpin(&state->cache, parent); + + if (unwrap && parent->dir && parent->pincount == 0) { + bftw_delayed_unwrap(state, parent); } } /** Pop a response from the I/O queue. */ static int bftw_ioq_pop(struct bftw_state *state, bool block) { + struct bftw_cache *cache = &state->cache; struct ioq *ioq = state->ioq; if (!ioq) { return -1; @@ -864,10 +1013,10 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { return -1; } - struct bftw_cache *cache = &state->cache; - struct bftw_file *file; - struct bftw_file *parent; - struct bfs_dir *dir; + struct bftw_file *file = ent->ptr; + if (file) { + bftw_unpin_parent(state, file, true); + } enum ioq_op op = ent->op; switch (op) { @@ -877,30 +1026,30 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { case IOQ_CLOSEDIR: ++cache->capacity; - dir = ent->closedir.dir; - bftw_freedir(cache, dir); + bftw_freedir(cache, ent->closedir.dir); break; case IOQ_OPENDIR: - file = ent->ptr; - ++cache->capacity; - parent = file->parent; - if (parent) { - bftw_unpin_dir(state, parent, false); - } - dir = ent->opendir.dir; if (ent->result >= 0) { - bftw_file_set_dir(cache, file, dir); + bftw_file_set_dir(cache, file, ent->opendir.dir); } else { - bftw_freedir(cache, dir); + bftw_freedir(cache, ent->opendir.dir); } - bftw_queue_attach(&state->dirq, file); + bftw_queue_attach(&state->dirq, file, true); break; case IOQ_STAT: + if (ent->result >= 0) { + bftw_stat_cache(&file->stat_bufs, ent->stat.flags, ent->stat.buf, 0); + } else { + arena_free(&cache->stat_bufs, ent->stat.buf); + bftw_stat_cache(&file->stat_bufs, ent->stat.flags, NULL, -ent->result); + } + + bftw_queue_attach(&state->fileq, file, true); break; } @@ -1125,21 +1274,34 @@ static int bftw_unwrapdir(struct bftw_state *state, struct bftw_file *file) { return bftw_ioq_closedir(state, dir); } +/** Try to pin a file's parent. */ +static int bftw_pin_parent(struct bftw_state *state, struct bftw_file *file) { + struct bftw_file *parent = file->parent; + if (!parent) { + return AT_FDCWD; + } + + int fd = parent->fd; + if (fd < 0) { + bfs_static_assert(AT_FDCWD != -1); + return -1; + } + + bftw_cache_pin(&state->cache, parent); + return fd; +} + /** Open a directory asynchronously. */ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { + struct bftw_cache *cache = &state->cache; + if (bftw_ioq_reserve(state) != 0) { goto fail; } - int dfd = AT_FDCWD; - struct bftw_cache *cache = &state->cache; - struct bftw_file *parent = file->parent; - if (parent) { - dfd = parent->fd; - if (dfd < 0) { - goto fail; - } - bftw_cache_pin(cache, parent); + int dfd = bftw_pin_parent(state, file); + if (dfd < 0 && dfd != AT_FDCWD) { + goto fail; } if (bftw_cache_reserve(state) != 0) { @@ -1161,9 +1323,7 @@ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { free: bftw_freedir(cache, dir); unpin: - if (parent) { - bftw_cache_unpin(cache, parent); - } + bftw_unpin_parent(state, file, false); fail: return -1; } @@ -1177,7 +1337,7 @@ static void bftw_ioq_opendirs(struct bftw_state *state) { } if (bftw_ioq_opendir(state, dir) == 0) { - bftw_queue_detach(&state->dirq, dir); + bftw_queue_detach(&state->dirq, dir, true); } else { break; } @@ -1191,24 +1351,36 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { bftw_ioq_opendirs(state); } -/** Check if we should block on the ioq when popping a directory. */ -static bool bftw_block_for_dir(const struct bftw_state *state) { - // Always block with more than one background thread - if (state->nthreads > 1) { - return true; +/** Pop a file from a queue, then activate it. */ +static bool bftw_pop(struct bftw_state *state, struct bftw_queue *queue) { + if (queue->size == 0) { + return false; } - // Block if the cache is full - if (state->cache.capacity == 0) { - return true; + while (!bftw_queue_ready(queue) && queue->ioqueued > 0) { + bool block = true; + if (bftw_queue_waiting(queue) && state->nthreads == 1) { + // With only one background thread, balance the work + // between it and the main thread + block = false; + } + + if (bftw_ioq_pop(state, block) < 0) { + break; + } } - // Block if we have no other files/dirs to visit - if (!bftw_queue_waiting(&state->dirq) && bftw_queue_empty(&state->fileq)) { - return true; + struct bftw_file *file = bftw_queue_pop(queue); + if (!file) { + return false; } - return false; + while (file->ioqueued) { + bftw_ioq_pop(state, true); + } + + state->file = file; + return true; } /** Pop a directory to read from the queue. */ @@ -1220,32 +1392,143 @@ static bool bftw_pop_dir(struct bftw_state *state) { if (state->strategy == BFTW_BFS && bftw_queue_ready(&state->fileq)) { return false; } + } else if (!bftw_queue_ready(&state->dirq)) { + // Don't block if we have files ready to visit + if (bftw_queue_ready(&state->fileq)) { + return false; + } + } + + return bftw_pop(state, &state->dirq); +} + +/** Figure out bfs_stat() flags. */ +static enum bfs_stat_flags bftw_stat_flags(const struct bftw_state *state, size_t depth) { + enum bftw_flags mask = BFTW_FOLLOW_ALL; + if (depth == 0) { + mask |= BFTW_FOLLOW_ROOTS; + } + + if (state->flags & mask) { + return BFS_STAT_TRYFOLLOW; } else { - while (!bftw_queue_ready(&state->dirq)) { - if (bftw_ioq_pop(state, bftw_block_for_dir(state)) < 0) { - break; - } + return BFS_STAT_NOFOLLOW; + } +} + +/** Check if a stat() call is necessary. */ +static bool bftw_must_stat(const struct bftw_state *state, size_t depth, enum bfs_type type, const char *name) { + if (state->flags & BFTW_STAT) { + return true; + } + + switch (type) { + case BFS_UNKNOWN: + return true; + + case BFS_DIR: + return state->flags & (BFTW_DETECT_CYCLES | BFTW_SKIP_MOUNTS | BFTW_PRUNE_MOUNTS); + + case BFS_LNK: + if (!(bftw_stat_flags(state, depth) & BFS_STAT_NOFOLLOW)) { + return true; + } + fallthru; + + default: +#if __linux__ + if (state->mtab && bfs_might_be_mount(state->mtab, name)) { + return true; } +#endif + return false; } +} - struct bftw_file *file = bftw_queue_pop(&state->dirq); - if (!file) { +/** stat() a file asynchronously. */ +static int bftw_ioq_stat(struct bftw_state *state, struct bftw_file *file) { + if (bftw_ioq_reserve(state) != 0) { + goto fail; + } + + int dfd = bftw_pin_parent(state, file); + if (dfd < 0 && dfd != AT_FDCWD) { + goto fail; + } + + struct bftw_cache *cache = &state->cache; + struct bfs_stat *buf = arena_alloc(&cache->stat_bufs); + if (!buf) { + goto unpin; + } + + enum bfs_stat_flags flags = bftw_stat_flags(state, file->depth); + if (ioq_stat(state->ioq, dfd, file->name, flags, buf, file) != 0) { + goto free; + } + + return 0; + +free: + arena_free(&cache->stat_bufs, buf); +unpin: + bftw_unpin_parent(state, file, false); +fail: + return -1; +} + +/** Check if we should stat() a file asynchronously. */ +static bool bftw_should_ioq_stat(struct bftw_state *state, struct bftw_file *file) { + // To avoid surprising users too much, process the roots in order + if (file->depth == 0) { return false; } - while (file->ioqueued) { - bftw_ioq_pop(state, true); +#ifdef S_IFWHT + // ioq_stat() does not do whiteout emulation like bftw_stat_impl() + if (file->type == BFS_WHT) { + return false; } +#endif - state->file = file; - return true; + return bftw_must_stat(state, file->depth, file->type, file->name); +} + +/** Call stat() on files that need it. */ +static void bftw_stat_files(struct bftw_state *state) { + while (true) { + struct bftw_file *file = bftw_queue_waiting(&state->fileq); + if (!file) { + break; + } + + if (!bftw_should_ioq_stat(state, file)) { + bftw_queue_skip(&state->fileq, file); + continue; + } + + if (!bftw_queue_balanced(&state->fileq)) { + break; + } + + if (bftw_ioq_stat(state, file) == 0) { + bftw_queue_detach(&state->fileq, file, true); + } else { + break; + } + } +} + +/** Push a file onto the queue. */ +static void bftw_push_file(struct bftw_state *state, struct bftw_file *file) { + bftw_queue_push(&state->fileq, file); + bftw_stat_files(state); } /** Pop a file to visit from the queue. */ static bool bftw_pop_file(struct bftw_state *state) { bfs_assert(!state->file); - state->file = bftw_queue_pop(&state->fileq); - return state->file; + return bftw_pop(state, &state->fileq); } /** Build the path to the current file. */ @@ -1334,6 +1617,8 @@ static int bftw_opendir(struct bftw_state *state) { return -1; } + bftw_queue_rebalance(&state->dirq, false); + state->dir = bftw_file_opendir(state, file, state->path); if (!state->dir) { state->direrror = errno; @@ -1364,47 +1649,6 @@ static int bftw_readdir(struct bftw_state *state) { return ret; } -/** Check if a stat() call is needed for this visit. */ -static bool bftw_need_stat(const struct bftw_state *state) { - if (state->flags & BFTW_STAT) { - return true; - } - - const struct BFTW *ftwbuf = &state->ftwbuf; - if (ftwbuf->type == BFS_UNKNOWN) { - return true; - } - - if (ftwbuf->type == BFS_LNK && !(ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) { - return true; - } - - if (ftwbuf->type == BFS_DIR) { - if (state->flags & (BFTW_DETECT_CYCLES | BFTW_SKIP_MOUNTS | BFTW_PRUNE_MOUNTS)) { - return true; - } -#if __linux__ - } else if (state->mtab) { - // Linux fills in d_type from the underlying inode, even when - // the directory entry is a bind mount point. In that case, we - // need to stat() to get the correct type. We don't need to - // check for directories because they can only be mounted over - // by other directories. - if (bfs_might_be_mount(state->mtab, ftwbuf->path + ftwbuf->nameoff)) { - return true; - } -#endif - } - - return false; -} - -/** Initialize bftw_stat cache. */ -static void bftw_stat_init(struct bftw_stat *cache) { - cache->buf = NULL; - cache->error = 0; -} - /** Open a file if necessary. */ static int bftw_ensure_open(struct bftw_state *state, struct bftw_file *file, const char *path) { int ret = file->fd; @@ -1436,9 +1680,7 @@ static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) { ftwbuf->error = state->direrror; ftwbuf->at_fd = AT_FDCWD; ftwbuf->at_path = ftwbuf->path; - ftwbuf->stat_flags = BFS_STAT_NOFOLLOW; - bftw_stat_init(&ftwbuf->lstat_cache); - bftw_stat_init(&ftwbuf->stat_cache); + bftw_stat_init(&ftwbuf->stat_bufs, &state->stat_buf, &state->lstat_buf); struct bftw_file *parent = NULL; if (de) { @@ -1451,6 +1693,7 @@ static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) { ftwbuf->depth = file->depth; ftwbuf->type = file->type; ftwbuf->nameoff = file->nameoff; + bftw_stat_fill(&ftwbuf->stat_bufs, &file->stat_bufs); } if (parent) { @@ -1468,22 +1711,15 @@ static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) { ftwbuf->nameoff = xbaseoff(ftwbuf->path); } + ftwbuf->stat_flags = bftw_stat_flags(state, ftwbuf->depth); + if (ftwbuf->error != 0) { ftwbuf->type = BFS_ERROR; return; } - int follow_flags = BFTW_FOLLOW_ALL; - if (ftwbuf->depth == 0) { - follow_flags |= BFTW_FOLLOW_ROOTS; - } - bool follow = state->flags & follow_flags; - if (follow) { - ftwbuf->stat_flags = BFS_STAT_TRYFOLLOW; - } - const struct bfs_stat *statbuf = NULL; - if (bftw_need_stat(state)) { + if (bftw_must_stat(state, ftwbuf->depth, ftwbuf->type, ftwbuf->path + ftwbuf->nameoff)) { statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (statbuf) { ftwbuf->type = bfs_mode_to_type(statbuf->mode); @@ -1522,6 +1758,11 @@ static bool bftw_is_mount(struct bftw_state *state, const char *name) { return statbuf && statbuf->dev != parent->dev; } +/** Check if bfs_stat() was called from the main thread. */ +static bool bftw_stat_was_sync(const struct bftw_state *state, const struct bfs_stat *buf) { + return buf == &state->stat_buf || buf == &state->lstat_buf; +} + /** Invoke the callback. */ static enum bftw_action bftw_call_back(struct bftw_state *state, const char *name, enum bftw_visit visit) { if (visit == BFTW_POST && !(state->flags & BFTW_POST_ORDER)) { @@ -1541,31 +1782,43 @@ static enum bftw_action bftw_call_back(struct bftw_state *state, const char *nam return BFTW_STOP; } + enum bftw_action ret = BFTW_PRUNE; if ((state->flags & BFTW_SKIP_MOUNTS) && bftw_is_mount(state, name)) { - return BFTW_PRUNE; + goto done; } - enum bftw_action ret = state->callback(ftwbuf, state->ptr); + ret = state->callback(ftwbuf, state->ptr); switch (ret) { case BFTW_CONTINUE: - if (visit != BFTW_PRE) { - return BFTW_PRUNE; - } - if (ftwbuf->type != BFS_DIR) { - return BFTW_PRUNE; - } - if ((state->flags & BFTW_PRUNE_MOUNTS) && bftw_is_mount(state, name)) { - return BFTW_PRUNE; + if (visit != BFTW_PRE || ftwbuf->type != BFS_DIR) { + ret = BFTW_PRUNE; + } else if (state->flags & BFTW_PRUNE_MOUNTS) { + if (bftw_is_mount(state, name)) { + ret = BFTW_PRUNE; + } } - fallthru; + break; + case BFTW_PRUNE: case BFTW_STOP: - return ret; + break; default: state->error = EINVAL; return BFTW_STOP; } + +done: + if (state->fileq.flags & BFTW_QBALANCE) { + // Detect any main-thread stat() calls to rebalance the queue + const struct bfs_stat *buf = bftw_cached_stat(ftwbuf, BFS_STAT_FOLLOW); + const struct bfs_stat *lbuf = bftw_cached_stat(ftwbuf, BFS_STAT_NOFOLLOW); + if (bftw_stat_was_sync(state, buf) || bftw_stat_was_sync(state, lbuf)) { + bftw_queue_rebalance(&state->fileq, false); + } + } + + return ret; } /** @@ -1589,8 +1842,13 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { int ret = 0; struct bftw_file *file = state->file; - if (file && state->dir) { - bftw_unpin_dir(state, file, true); + if (file) { + if (state->dir) { + bftw_cache_unpin(&state->cache, file); + } + if (file->dir) { + bftw_delayed_unwrap(state, file); + } } state->dir = NULL; state->de = NULL; @@ -1607,7 +1865,7 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { } state->direrror = 0; - while ((file = SLIST_POP(&state->to_close))) { + while ((file = SLIST_POP(&state->to_close, ready))) { bftw_unwrapdir(state, file); } @@ -1685,6 +1943,7 @@ static void bftw_flush(struct bftw_state *state) { bftw_list_sort(&state->fileq.buffer); } bftw_queue_flush(&state->fileq); + bftw_stat_files(state); bftw_queue_flush(&state->dirq); bftw_ioq_opendirs(state); @@ -1704,22 +1963,46 @@ static int bftw_closedir(struct bftw_state *state) { static void bftw_save_ftwbuf(struct bftw_file *file, const struct BFTW *ftwbuf) { file->type = ftwbuf->type; - const struct bfs_stat *statbuf = ftwbuf->stat_cache.buf; - if (!statbuf || (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) { - statbuf = ftwbuf->lstat_cache.buf; - } + const struct bfs_stat *statbuf = bftw_cached_stat(ftwbuf, ftwbuf->stat_flags); if (statbuf) { file->dev = statbuf->dev; file->ino = statbuf->ino; } } +/** Check if we should buffer a file instead of visiting it. */ +static bool bftw_buffer_file(const struct bftw_state *state, const struct bftw_file *file, const char *name) { + if (!name) { + // Already buffered + return false; + } + + if (state->flags & BFTW_BUFFER) { + return true; + } + + // If we need to call stat(), and can do it async, buffer this file + if (!state->ioq) { + return false; + } + + if (!bftw_queue_balanced(&state->fileq)) { + // stat() would run synchronously anyway + return false; + } + + size_t depth = file ? file->depth + 1 : 1; + enum bfs_type type = state->de ? state->de->type : BFS_UNKNOWN; + return bftw_must_stat(state, depth, type, name); +} + /** Visit and/or enqueue the current file. */ static int bftw_visit(struct bftw_state *state, const char *name) { + struct bftw_cache *cache = &state->cache; struct bftw_file *file = state->file; - if (name && (state->flags & BFTW_BUFFER)) { - file = bftw_file_new(&state->cache, file, name); + if (bftw_buffer_file(state, file, name)) { + file = bftw_file_new(cache, file, name); if (!file) { state->error = errno; return -1; @@ -1729,14 +2012,14 @@ static int bftw_visit(struct bftw_state *state, const char *name) { file->type = state->de->type; } - bftw_queue_push(&state->fileq, file); + bftw_push_file(state, file); return 0; } switch (bftw_call_back(state, name, BFTW_PRE)) { case BFTW_CONTINUE: if (name) { - file = bftw_file_new(&state->cache, state->file, name); + file = bftw_file_new(cache, state->file, name); } else { state->file = NULL; } @@ -1746,6 +2029,7 @@ static int bftw_visit(struct bftw_state *state, const char *name) { } bftw_save_ftwbuf(file, &state->ftwbuf); + bftw_stat_recycle(cache, file); bftw_push_dir(state, file); return 0; @@ -1757,10 +2041,22 @@ static int bftw_visit(struct bftw_state *state, const char *name) { } default: + if (file && !name) { + bftw_gc(state, BFTW_VISIT_NONE); + } return -1; } } +/** Drain a bftw_queue. */ +static void bftw_drain(struct bftw_state *state, struct bftw_queue *queue) { + bftw_queue_flush(queue); + + while (bftw_pop(state, queue)) { + bftw_gc(state, BFTW_VISIT_NONE); + } +} + /** * Dispose of the bftw() state. * @@ -1777,11 +2073,9 @@ static int bftw_state_destroy(struct bftw_state *state) { state->ioq = NULL; } - bftw_queue_flush(&state->dirq); - bftw_queue_flush(&state->fileq); - do { - bftw_gc(state, BFTW_VISIT_NONE); - } while (bftw_pop_dir(state) || bftw_pop_file(state)); + bftw_gc(state, BFTW_VISIT_NONE); + bftw_drain(state, &state->dirq); + bftw_drain(state, &state->fileq); ioq_destroy(ioq); @@ -1823,6 +2117,7 @@ static int bftw_impl(struct bftw_state *state) { if (bftw_visit(state, NULL) != 0) { return -1; } + bftw_flush(state); } return 0; diff --git a/src/bftw.h b/src/bftw.h index e325d14..2805361 100644 --- a/src/bftw.h +++ b/src/bftw.h @@ -26,12 +26,14 @@ enum bftw_visit { * Cached bfs_stat() info for a file. */ struct bftw_stat { - /** A pointer to the bfs_stat() buffer, if available. */ - const struct bfs_stat *buf; - /** Storage for the bfs_stat() buffer, if needed. */ - struct bfs_stat storage; - /** The cached error code, if any. */ - int error; + /** The bfs_stat(BFS_STAT_FOLLOW) buffer. */ + const struct bfs_stat *stat_buf; + /** The bfs_stat(BFS_STAT_NOFOLLOW) buffer. */ + const struct bfs_stat *lstat_buf; + /** The cached bfs_stat(BFS_STAT_FOLLOW) error. */ + int stat_err; + /** The cached bfs_stat(BFS_STAT_NOFOLLOW) error. */ + int lstat_err; }; /** @@ -62,10 +64,8 @@ struct BFTW { /** Flags for bfs_stat(). */ enum bfs_stat_flags stat_flags; - /** Cached bfs_stat() info for BFS_STAT_NOFOLLOW. */ - struct bftw_stat lstat_cache; - /** Cached bfs_stat() info for BFS_STAT_FOLLOW. */ - struct bftw_stat stat_cache; + /** Cached bfs_stat() info. */ + struct bftw_stat stat_bufs; }; /** diff --git a/src/eval.c b/src/eval.c index 6dc73d8..dfeaa1e 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1234,7 +1234,7 @@ static bool eval_file_unique(struct bfs_eval *state, struct trie *seen) { /** * Log a stat() call. */ -static void debug_stat(const struct bfs_ctx *ctx, const struct BFTW *ftwbuf, const struct bftw_stat *cache, enum bfs_stat_flags flags) { +static void debug_stat(const struct bfs_ctx *ctx, const struct BFTW *ftwbuf, enum bfs_stat_flags flags, int err) { bfs_debug_prefix(ctx, DEBUG_STAT); fprintf(stderr, "bfs_stat("); @@ -1254,10 +1254,10 @@ static void debug_stat(const struct bfs_ctx *ctx, const struct BFTW *ftwbuf, con DEBUG_FLAG(flags, BFS_STAT_TRYFOLLOW); DEBUG_FLAG(flags, BFS_STAT_NOSYNC); - fprintf(stderr, ") == %d", cache->buf ? 0 : -1); + fprintf(stderr, ") == %d", err ? 0 : -1); - if (cache->error) { - fprintf(stderr, " [%d]", cache->error); + if (err) { + fprintf(stderr, " [%d]", err); } fprintf(stderr, "\n"); @@ -1271,14 +1271,14 @@ static void debug_stats(const struct bfs_ctx *ctx, const struct BFTW *ftwbuf) { return; } - const struct bfs_stat *statbuf = ftwbuf->stat_cache.buf; - if (statbuf || ftwbuf->stat_cache.error) { - debug_stat(ctx, ftwbuf, &ftwbuf->stat_cache, BFS_STAT_FOLLOW); + const struct bftw_stat *bufs = &ftwbuf->stat_bufs; + + if (bufs->stat_err >= 0) { + debug_stat(ctx, ftwbuf, BFS_STAT_FOLLOW, bufs->stat_err); } - const struct bfs_stat *lstatbuf = ftwbuf->lstat_cache.buf; - if ((lstatbuf && lstatbuf != statbuf) || ftwbuf->lstat_cache.error) { - debug_stat(ctx, ftwbuf, &ftwbuf->lstat_cache, BFS_STAT_NOFOLLOW); + if (bufs->lstat_err >= 0) { + debug_stat(ctx, ftwbuf, BFS_STAT_NOFOLLOW, bufs->lstat_err); } } diff --git a/tests/bfs/execdir_plus.sh b/tests/bfs/execdir_plus.sh index f66b898..6f24bdc 100644 --- a/tests/bfs/execdir_plus.sh +++ b/tests/bfs/execdir_plus.sh @@ -1,4 +1,4 @@ tree=$(invoke_bfs -D tree 2>&1 -quit) [[ "$tree" == *"-S dfs"* ]] && skip -bfs_diff basic -execdir "$TESTS/sort-args.sh" {} + +bfs_diff -j1 basic -execdir "$TESTS/sort-args.sh" {} + -- cgit v1.2.3 From 16ba60f128f6a099f4d47291e570019a7c897f00 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 14 Feb 2024 11:16:17 -0500 Subject: bftw: Document which bftw_file nodes go with which lists --- src/bftw.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 664651c..6f52299 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -196,12 +196,26 @@ struct bftw_file { /** The root under which this file was found. */ struct bftw_file *root; - /** The next file to open/close/visit. */ + /** + * List node for: + * + * bftw_queue::buffer + * bftw_queue::waiting + * bftw_file_open()::parents + */ struct bftw_file *next; - /** The next directory to read. */ + + /** + * List node for: + * + * bftw_queue::ready + * bftw_state::to_close + */ struct { struct bftw_file *next; } ready; - /** LRU list node. */ + /** + * List node for bftw_cache. + */ struct { struct bftw_file *prev; struct bftw_file *next; -- cgit v1.2.3 From 2c3ef3a06ee1f951f6d68be6d0d3f6a1822b05b7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 11 Mar 2024 09:51:03 -0400 Subject: Re-run include-what-you-use --- src/alloc.h | 1 + src/bfstd.c | 6 +++--- src/bftw.c | 1 + src/color.h | 1 - src/ctx.c | 1 + src/ctx.h | 2 ++ src/diag.c | 2 +- src/dir.h | 4 ++-- src/dstring.c | 2 ++ src/eval.c | 4 ++-- src/expr.c | 4 ++-- src/expr.h | 1 - src/ioq.c | 5 +++-- src/main.c | 1 + src/opt.c | 3 ++- src/parse.c | 3 +-- src/printf.c | 3 ++- src/trie.c | 2 -- src/trie.h | 1 - src/xspawn.c | 1 - src/xtime.c | 1 - tests/alloc.c | 1 + tests/bfstd.c | 3 --- tests/bit.c | 2 +- tests/ioq.c | 2 ++ tests/main.c | 3 --- tests/xtime.c | 4 ++-- tests/xtouch.c | 1 + 28 files changed, 33 insertions(+), 32 deletions(-) (limited to 'src/bftw.c') diff --git a/src/alloc.h b/src/alloc.h index 60dd738..ae055bc 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -10,6 +10,7 @@ #include "config.h" #include +#include #include /** Check if a size is properly aligned. */ diff --git a/src/bfstd.c b/src/bfstd.c index ce4aa49..c6c2e7f 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -2,18 +2,19 @@ // SPDX-License-Identifier: 0BSD #include "bfstd.h" -#include "bit.h" #include "config.h" #include "diag.h" #include "sanity.h" #include "thread.h" #include "xregex.h" -#include #include #include #include +#include #include #include +#include +#include #include #include #include @@ -24,7 +25,6 @@ #include #include #include -#include #if BFS_USE_SYS_SYSMACROS_H # include diff --git a/src/bftw.c b/src/bftw.c index 6f52299..50b8b02 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -35,6 +35,7 @@ #include #include #include +#include /** Initialize a bftw_stat cache. */ static void bftw_stat_init(struct bftw_stat *bufs, struct bfs_stat *stat_buf, struct bfs_stat *lstat_buf) { diff --git a/src/color.h b/src/color.h index 85633a4..e3e7973 100644 --- a/src/color.h +++ b/src/color.h @@ -10,7 +10,6 @@ #include "config.h" #include "dstring.h" -#include #include /** diff --git a/src/ctx.c b/src/ctx.c index 6c84f75..f5b28c7 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -6,6 +6,7 @@ #include "color.h" #include "diag.h" #include "expr.h" +#include "list.h" #include "mtab.h" #include "pwcache.h" #include "stat.h" diff --git a/src/ctx.h b/src/ctx.h index aa91f2c..e14db21 100644 --- a/src/ctx.h +++ b/src/ctx.h @@ -18,6 +18,8 @@ #include #include +struct CFILE; + /** * The execution context for bfs. */ diff --git a/src/diag.c b/src/diag.c index 656fa89..cb27b92 100644 --- a/src/diag.c +++ b/src/diag.c @@ -11,8 +11,8 @@ #include "expr.h" #include #include +#include #include -#include /** bfs_diagf() implementation. */ attr(printf(2, 0)) diff --git a/src/dir.h b/src/dir.h index b11d454..18d907e 100644 --- a/src/dir.h +++ b/src/dir.h @@ -8,8 +8,6 @@ #ifndef BFS_DIR_H #define BFS_DIR_H -#include "alloc.h" -#include "config.h" #include /** @@ -78,6 +76,8 @@ struct bfs_dirent { */ struct bfs_dir *bfs_allocdir(void); +struct arena; + /** * Initialize an arena for directories. * diff --git a/src/dstring.c b/src/dstring.c index bc18308..10b0fad 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -7,6 +7,8 @@ #include "config.h" #include "diag.h" #include +#include +#include #include #include #include diff --git a/src/eval.c b/src/eval.c index 1711001..9e55964 100644 --- a/src/eval.c +++ b/src/eval.c @@ -25,7 +25,6 @@ #include "stat.h" #include "trie.h" #include "xregex.h" -#include "xtime.h" #include #include #include @@ -36,8 +35,9 @@ #include #include #include +#include #include -#include +#include #include #include #include diff --git a/src/expr.c b/src/expr.c index 3e0033f..5784220 100644 --- a/src/expr.c +++ b/src/expr.c @@ -4,12 +4,12 @@ #include "expr.h" #include "alloc.h" #include "ctx.h" +#include "diag.h" #include "eval.h" #include "exec.h" +#include "list.h" #include "printf.h" #include "xregex.h" -#include -#include #include struct bfs_expr *bfs_expr_new(struct bfs_ctx *ctx, bfs_eval_fn *eval_fn, size_t argc, char **argv) { diff --git a/src/expr.h b/src/expr.h index 957b04a..75cb5fd 100644 --- a/src/expr.h +++ b/src/expr.h @@ -12,7 +12,6 @@ #include "config.h" #include "eval.h" #include "stat.h" -#include #include #include diff --git a/src/ioq.c b/src/ioq.c index 00c3b86..37eed7d 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -126,13 +126,14 @@ #include "config.h" #include "diag.h" #include "dir.h" -#include "sanity.h" #include "stat.h" #include "thread.h" -#include #include +#include #include +#include #include +#include #if BFS_USE_LIBURING # include diff --git a/src/main.c b/src/main.c index 16a2576..e120f03 100644 --- a/src/main.c +++ b/src/main.c @@ -48,6 +48,7 @@ #include "bfstd.h" #include "config.h" #include "ctx.h" +#include "diag.h" #include "eval.h" #include "parse.h" #include diff --git a/src/opt.c b/src/opt.c index 74145ac..76965de 100644 --- a/src/opt.c +++ b/src/opt.c @@ -26,11 +26,13 @@ */ #include "opt.h" +#include "bftw.h" #include "bit.h" #include "color.h" #include "config.h" #include "ctx.h" #include "diag.h" +#include "dir.h" #include "eval.h" #include "exec.h" #include "expr.h" @@ -40,7 +42,6 @@ #include #include #include -#include #include static char *fake_and_arg = "-and"; diff --git a/src/parse.c b/src/parse.c index b26a50f..3b7386d 100644 --- a/src/parse.c +++ b/src/parse.c @@ -42,8 +42,7 @@ #include #include #include -#include -#include +#include #include #include diff --git a/src/printf.c b/src/printf.c index 34ed606..487f039 100644 --- a/src/printf.c +++ b/src/printf.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "printf.h" +#include "alloc.h" #include "bfstd.h" #include "bftw.h" #include "color.h" @@ -14,10 +15,10 @@ #include "mtab.h" #include "pwcache.h" #include "stat.h" -#include "xtime.h" #include #include #include +#include #include #include #include diff --git a/src/trie.c b/src/trie.c index bd5300d..1ffb23a 100644 --- a/src/trie.c +++ b/src/trie.c @@ -87,9 +87,7 @@ #include "config.h" #include "diag.h" #include "list.h" -#include #include -#include #include bfs_static_assert(CHAR_WIDTH == 8); diff --git a/src/trie.h b/src/trie.h index 02088f1..4288d76 100644 --- a/src/trie.h +++ b/src/trie.h @@ -5,7 +5,6 @@ #define BFS_TRIE_H #include "alloc.h" -#include "config.h" #include "list.h" #include #include diff --git a/src/xspawn.c b/src/xspawn.c index 8d6108b..6a94d3d 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -12,7 +12,6 @@ #include #include #include -#include #include #if BFS_USE_PATHS_H diff --git a/src/xtime.c b/src/xtime.c index 05f0e1a..5b259ab 100644 --- a/src/xtime.c +++ b/src/xtime.c @@ -7,7 +7,6 @@ #include "diag.h" #include #include -#include #include #include #include diff --git a/tests/alloc.c b/tests/alloc.c index 4ce23d4..9f08111 100644 --- a/tests/alloc.c +++ b/tests/alloc.c @@ -3,6 +3,7 @@ #include "tests.h" #include "../src/alloc.h" +#include "../src/config.h" #include "../src/diag.h" #include #include diff --git a/tests/bfstd.c b/tests/bfstd.c index 0ded5de..26abdb6 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -7,9 +7,6 @@ #include "../src/diag.h" #include #include -#include -#include -#include #include #include diff --git a/tests/bit.c b/tests/bit.c index b944748..3d66ce3 100644 --- a/tests/bit.c +++ b/tests/bit.c @@ -3,10 +3,10 @@ #include "tests.h" #include "../src/bit.h" +#include "../src/config.h" #include "../src/diag.h" #include #include -#include bfs_static_assert(UMAX_WIDTH(0x1) == 1); bfs_static_assert(UMAX_WIDTH(0x3) == 2); diff --git a/tests/ioq.c b/tests/ioq.c index 56e1886..1ce8f75 100644 --- a/tests/ioq.c +++ b/tests/ioq.c @@ -4,10 +4,12 @@ #include "tests.h" #include "../src/ioq.h" #include "../src/bfstd.h" +#include "../src/config.h" #include "../src/diag.h" #include "../src/dir.h" #include #include +#include /** * Test for blocking within ioq_slot_push(). diff --git a/tests/main.c b/tests/main.c index 38438b2..8849e8c 100644 --- a/tests/main.c +++ b/tests/main.c @@ -6,11 +6,8 @@ */ #include "tests.h" -#include "../src/bfstd.h" #include "../src/color.h" #include "../src/config.h" -#include "../src/diag.h" -#include #include #include #include diff --git a/tests/xtime.c b/tests/xtime.c index 3f1fec2..c8dc00b 100644 --- a/tests/xtime.c +++ b/tests/xtime.c @@ -5,10 +5,10 @@ #include "../src/xtime.h" #include "../src/bfstd.h" #include "../src/config.h" +#include "../src/diag.h" #include +#include #include -#include -#include #include static bool tm_equal(const struct tm *tma, const struct tm *tmb) { diff --git a/tests/xtouch.c b/tests/xtouch.c index 8c5c5f3..fad272f 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include -- cgit v1.2.3 From 9827b2bae1d3864dffb06b50e2f59af594154693 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 22 Mar 2024 14:16:58 -0400 Subject: bftw: Use a signed integer for dir_limit --- src/bftw.c | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) (limited to 'src/bftw.c') diff --git a/src/bftw.c b/src/bftw.c index 50b8b02..6130c44 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -558,9 +558,7 @@ struct bftw_cache { /** bfs_dir arena. */ struct arena dirs; /** Remaining bfs_dir capacity. */ - size_t dir_limit; - /** Excess force-allocated bfs_dirs. */ - size_t dir_excess; + int dir_limit; /** bfs_stat arena. */ struct arena stat_bufs; @@ -576,47 +574,32 @@ static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) { bfs_dir_arena(&cache->dirs); - cache->dir_limit = capacity - 1; - if (cache->dir_limit > 1024) { + if (cache->capacity > 1024) { cache->dir_limit = 1024; + } else { + cache->dir_limit = capacity - 1; } - cache->dir_excess = 0; - ARENA_INIT(&cache->stat_bufs, struct bfs_stat); } /** Allocate a directory. */ static struct bfs_dir *bftw_allocdir(struct bftw_cache *cache, bool force) { - size_t limit = cache->dir_limit; - size_t excess = cache->dir_excess; - - if (cache->dir_limit > 0) { - --cache->dir_limit; - } else if (force) { - ++cache->dir_excess; - } else { + if (!force && cache->dir_limit <= 0) { errno = ENOMEM; return NULL; } struct bfs_dir *dir = arena_alloc(&cache->dirs); - if (!dir) { - cache->dir_limit = limit; - cache->dir_excess = excess; + if (dir) { + --cache->dir_limit; } - return dir; } /** Free a directory. */ static void bftw_freedir(struct bftw_cache *cache, struct bfs_dir *dir) { - if (cache->dir_excess > 0) { - --cache->dir_excess; - } else { - ++cache->dir_limit; - } - + ++cache->dir_limit; arena_free(&cache->dirs, dir); } -- cgit v1.2.3 From c66379749f423413913b406609dfe9311ba6e555 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 Apr 2024 14:53:56 -0400 Subject: Rename config.h to prelude.h --- src/alloc.c | 2 +- src/alloc.h | 2 +- src/bar.c | 2 +- src/bfstd.c | 2 +- src/bfstd.h | 2 +- src/bftw.c | 2 +- src/bit.h | 2 +- src/color.c | 2 +- src/color.h | 2 +- src/config.h | 377 --------------------------------------------------------- src/ctx.h | 2 +- src/diag.c | 2 +- src/diag.h | 2 +- src/dir.c | 2 +- src/dstring.c | 2 +- src/dstring.h | 2 +- src/eval.c | 2 +- src/eval.h | 2 +- src/exec.c | 2 +- src/expr.h | 2 +- src/fsade.c | 2 +- src/fsade.h | 2 +- src/ioq.c | 2 +- src/ioq.h | 2 +- src/main.c | 4 +- src/mtab.c | 2 +- src/mtab.h | 2 +- src/opt.c | 2 +- src/parse.c | 2 +- src/prelude.h | 377 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/printf.c | 2 +- src/pwcache.c | 2 +- src/sanity.h | 2 +- src/stat.c | 2 +- src/stat.h | 2 +- src/thread.c | 2 +- src/thread.h | 2 +- src/trie.c | 2 +- src/xregex.c | 2 +- src/xspawn.c | 2 +- src/xspawn.h | 2 +- src/xtime.c | 2 +- tests/alloc.c | 2 +- tests/bfstd.c | 2 +- tests/bit.c | 2 +- tests/ioq.c | 2 +- tests/main.c | 2 +- tests/tests.h | 2 +- tests/trie.c | 2 +- tests/xspawn.c | 2 +- tests/xtime.c | 2 +- tests/xtouch.c | 2 +- 52 files changed, 428 insertions(+), 428 deletions(-) delete mode 100644 src/config.h create mode 100644 src/prelude.h (limited to 'src/bftw.c') diff --git a/src/alloc.c b/src/alloc.c index b65d0c5..ec8608f 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "alloc.h" #include "bit.h" -#include "config.h" #include "diag.h" #include "sanity.h" #include diff --git a/src/alloc.h b/src/alloc.h index ae055bc..095134a 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -8,7 +8,7 @@ #ifndef BFS_ALLOC_H #define BFS_ALLOC_H -#include "config.h" +#include "prelude.h" #include #include #include diff --git a/src/bar.c b/src/bar.c index 8ab4112..184d9a0 100644 --- a/src/bar.c +++ b/src/bar.c @@ -1,11 +1,11 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "bar.h" #include "atomic.h" #include "bfstd.h" #include "bit.h" -#include "config.h" #include "dstring.h" #include #include diff --git a/src/bfstd.c b/src/bfstd.c index 2499f00..e1b4804 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "bfstd.h" #include "bit.h" -#include "config.h" #include "diag.h" #include "sanity.h" #include "thread.h" diff --git a/src/bfstd.h b/src/bfstd.h index fc22971..42f5d5b 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -8,7 +8,7 @@ #ifndef BFS_BFSTD_H #define BFS_BFSTD_H -#include "config.h" +#include "prelude.h" #include "sanity.h" #include diff --git a/src/bftw.c b/src/bftw.c index 6130c44..c4d3c17 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -18,10 +18,10 @@ * various helper functions to take fewer parameters. */ +#include "prelude.h" #include "bftw.h" #include "alloc.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include "dir.h" #include "dstring.h" diff --git a/src/bit.h b/src/bit.h index 69df21e..17cfbcf 100644 --- a/src/bit.h +++ b/src/bit.h @@ -8,7 +8,7 @@ #ifndef BFS_BIT_H #define BFS_BIT_H -#include "config.h" +#include "prelude.h" #include #include diff --git a/src/color.c b/src/color.c index 8c32a68..f004bf2 100644 --- a/src/color.c +++ b/src/color.c @@ -1,11 +1,11 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "color.h" #include "alloc.h" #include "bfstd.h" #include "bftw.h" -#include "config.h" #include "diag.h" #include "dir.h" #include "dstring.h" diff --git a/src/color.h b/src/color.h index e3e7973..3278cd6 100644 --- a/src/color.h +++ b/src/color.h @@ -8,7 +8,7 @@ #ifndef BFS_COLOR_H #define BFS_COLOR_H -#include "config.h" +#include "prelude.h" #include "dstring.h" #include diff --git a/src/config.h b/src/config.h deleted file mode 100644 index 2eff1fc..0000000 --- a/src/config.h +++ /dev/null @@ -1,377 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -/** - * Configuration and feature/platform detection. - */ - -#ifndef BFS_CONFIG_H -#define BFS_CONFIG_H - -// Possible __STDC_VERSION__ values - -#define C95 199409L -#define C99 199901L -#define C11 201112L -#define C17 201710L -#define C23 202311L - -#include - -#if __STDC_VERSION__ < C23 -# include -# include -# include -#endif - -// bfs packaging configuration - -#ifndef BFS_COMMAND -# define BFS_COMMAND "bfs" -#endif -#ifndef BFS_HOMEPAGE -# define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html" -#endif - -// This is a symbol instead of a literal so we don't have to rebuild everything -// when the version number changes -extern const char bfs_version[]; - -// Check for system headers - -#ifdef __has_include - -#if __has_include() -# define BFS_HAS_MNTENT_H true -#endif -#if __has_include() -# define BFS_HAS_PATHS_H true -#endif -#if __has_include() -# define BFS_HAS_SYS_ACL_H true -#endif -#if __has_include() -# define BFS_HAS_SYS_CAPABILITY_H true -#endif -#if __has_include() -# define BFS_HAS_SYS_EXTATTR_H true -#endif -#if __has_include() -# define BFS_HAS_SYS_MKDEV_H true -#endif -#if __has_include() -# define BFS_HAS_SYS_PARAM_H true -#endif -#if __has_include() -# define BFS_HAS_SYS_SYSMACROS_H true -#endif -#if __has_include() -# define BFS_HAS_SYS_XATTR_H true -#endif -#if __has_include() -# define BFS_HAS_THREADS_H true -#endif -#if __has_include() -# define BFS_HAS_UTIL_H true -#endif - -#else // !__has_include - -#define BFS_HAS_MNTENT_H __GLIBC__ -#define BFS_HAS_PATHS_H true -#define BFS_HAS_SYS_ACL_H true -#define BFS_HAS_SYS_CAPABILITY_H __linux__ -#define BFS_HAS_SYS_EXTATTR_H __FreeBSD__ -#define BFS_HAS_SYS_MKDEV_H false -#define BFS_HAS_SYS_PARAM_H true -#define BFS_HAS_SYS_SYSMACROS_H __GLIBC__ -#define BFS_HAS_SYS_XATTR_H __linux__ -#define BFS_HAS_THREADS_H (!__STDC_NO_THREADS__) -#define BFS_HAS_UTIL_H __NetBSD__ - -#endif // !__has_include - -#ifndef BFS_USE_MNTENT_H -# define BFS_USE_MNTENT_H BFS_HAS_MNTENT_H -#endif -#ifndef BFS_USE_PATHS_H -# define BFS_USE_PATHS_H BFS_HAS_PATHS_H -#endif -#ifndef BFS_USE_SYS_ACL_H -# define BFS_USE_SYS_ACL_H (BFS_HAS_SYS_ACL_H && !__illumos__ && (!__linux__ || BFS_USE_LIBACL)) -#endif -#ifndef BFS_USE_SYS_CAPABILITY_H -# define BFS_USE_SYS_CAPABILITY_H (BFS_HAS_SYS_CAPABILITY_H && !__FreeBSD__ && (!__linux__ || BFS_USE_LIBCAP)) -#endif -#ifndef BFS_USE_SYS_EXTATTR_H -# define BFS_USE_SYS_EXTATTR_H (BFS_HAS_SYS_EXTATTR_H && !__DragonFly__) -#endif -#ifndef BFS_USE_SYS_MKDEV_H -# define BFS_USE_SYS_MKDEV_H BFS_HAS_SYS_MKDEV_H -#endif -#ifndef BFS_USE_SYS_PARAM_H -# define BFS_USE_SYS_PARAM_H BFS_HAS_SYS_PARAM_H -#endif -#ifndef BFS_USE_SYS_SYSMACROS_H -# define BFS_USE_SYS_SYSMACROS_H BFS_HAS_SYS_SYSMACROS_H -#endif -#ifndef BFS_USE_SYS_XATTR_H -# define BFS_USE_SYS_XATTR_H BFS_HAS_SYS_XATTR_H -#endif -#ifndef BFS_USE_THREADS_H -# define BFS_USE_THREADS_H BFS_HAS_THREADS_H -#endif -#ifndef BFS_USE_UTIL_H -# define BFS_USE_UTIL_H BFS_HAS_UTIL_H -#endif - -// Stub out feature detection on old/incompatible compilers - -#ifndef __has_feature -# define __has_feature(feat) false -#endif - -#ifndef __has_c_attribute -# define __has_c_attribute(attr) false -#endif - -#ifndef __has_attribute -# define __has_attribute(attr) false -#endif - -// Platform detection - -// Get the definition of BSD if available -#if BFS_USE_SYS_PARAM_H -# include -#endif - -#ifndef __GLIBC_PREREQ -# define __GLIBC_PREREQ(maj, min) false -#endif - -#ifndef __NetBSD_Prereq__ -# define __NetBSD_Prereq__(maj, min, patch) false -#endif - -// Fundamental utilities - -/** - * Get the length of an array. - */ -#define countof(array) (sizeof(array) / sizeof(0[array])) - -/** - * False sharing/destructive interference/largest cache line size. - */ -#ifdef __GCC_DESTRUCTIVE_SIZE -# define FALSE_SHARING_SIZE __GCC_DESTRUCTIVE_SIZE -#else -# define FALSE_SHARING_SIZE 64 -#endif - -/** - * True sharing/constructive interference/smallest cache line size. - */ -#ifdef __GCC_CONSTRUCTIVE_SIZE -# define TRUE_SHARING_SIZE __GCC_CONSTRUCTIVE_SIZE -#else -# define TRUE_SHARING_SIZE 64 -#endif - -/** - * Alignment specifier that avoids false sharing. - */ -#define cache_align alignas(FALSE_SHARING_SIZE) - -#if __COSMOPOLITAN__ -typedef long double max_align_t; -#endif - -// Wrappers for attributes - -/** - * Silence warnings about switch/case fall-throughs. - */ -#if __has_attribute(fallthrough) -# define fallthru __attribute__((fallthrough)) -#else -# define fallthru ((void)0) -#endif - -/** - * Silence warnings about unused declarations. - */ -#if __has_attribute(unused) -# define attr_maybe_unused __attribute__((unused)) -#else -# define attr_maybe_unused -#endif - -/** - * Warn if a value is unused. - */ -#if __has_attribute(warn_unused_result) -# define attr_nodiscard __attribute__((warn_unused_result)) -#else -# define attr_nodiscard -#endif - -/** - * Hint to avoid inlining a function. - */ -#if __has_attribute(noinline) -# define attr_noinline __attribute__((noinline)) -#else -# define attr_noinline -#endif - -/** - * Hint that a function is unlikely to be called. - */ -#if __has_attribute(cold) -# define attr_cold attr_noinline __attribute__((cold)) -#else -# define attr_cold attr_noinline -#endif - -/** - * Adds compiler warnings for bad printf()-style function calls, if supported. - */ -#if __has_attribute(format) -# define attr_printf(fmt, args) __attribute__((format(printf, fmt, args))) -#else -# define attr_printf(fmt, args) -#endif - -/** - * Annotates allocator-like functions. - */ -#if __has_attribute(malloc) -# if __GNUC__ >= 11 && !__OPTIMIZE__ // malloc(deallocator) disables inlining on GCC -# define attr_malloc(...) attr_nodiscard __attribute__((malloc(__VA_ARGS__))) -# else -# define attr_malloc(...) attr_nodiscard __attribute__((malloc)) -# endif -#else -# define attr_malloc(...) attr_nodiscard -#endif - -/** - * Specifies that a function returns allocations with a given alignment. - */ -#if __has_attribute(alloc_align) -# define attr_alloc_align(param) __attribute__((alloc_align(param))) -#else -# define attr_alloc_align(param) -#endif - -/** - * Specifies that a function returns allocations with a given size. - */ -#if __has_attribute(alloc_size) -# define attr_alloc_size(...) __attribute__((alloc_size(__VA_ARGS__))) -#else -# define attr_alloc_size(...) -#endif - -/** - * Shorthand for attr_alloc_align() and attr_alloc_size(). - */ -#define attr_aligned_alloc(align, ...) \ - attr_alloc_align(align) \ - attr_alloc_size(__VA_ARGS__) - -/** - * Check if function multiversioning via GNU indirect functions (ifunc) is supported. - */ -#ifndef BFS_USE_TARGET_CLONES -# if __has_attribute(target_clones) && (__GLIBC__ || __FreeBSD__) -# define BFS_USE_TARGET_CLONES true -# endif -#endif - -/** - * Apply the target_clones attribute, if available. - */ -#if BFS_USE_TARGET_CLONES -# define attr_target_clones(...) __attribute__((target_clones(__VA_ARGS__))) -#else -# define attr_target_clones(...) -#endif - -/** - * Shorthand for multiple attributes at once. attr(a, b(c), d) is equivalent to - * - * attr_a - * attr_b(c) - * attr_d - */ -#define attr(...) \ - attr__(attr_##__VA_ARGS__, none, none, none, none, none, none, none, none, none, ) - -/** - * attr() helper. For exposition, pretend we support only 2 args, instead of 9. - * There are a few cases: - * - * attr() - * => attr__(attr_, none, none) - * => attr_ => - * attr_none => - * attr_too_many_none() => - * - * attr(a) - * => attr__(attr_a, none, none) - * => attr_a => __attribute__((a)) - * attr_none => - * attr_too_many_none() => - * - * attr(a, b(c)) - * => attr__(attr_a, b(c), none, none) - * => attr_a => __attribute__((a)) - * attr_b(c) => __attribute__((b(c))) - * attr_too_many_none(none) => - * - * attr(a, b(c), d) - * => attr__(attr_a, b(c), d, none, none) - * => attr_a => __attribute__((a)) - * attr_b(c) => __attribute__((b(c))) - * attr_too_many_d(none, none) => error - * - * Some attribute names are the same as standard library functions, e.g. printf. - * Standard libraries are permitted to define these functions as macros, like - * - * #define printf(...) __builtin_printf(__VA_ARGS__) - * - * The token paste in - * - * #define attr(...) attr__(attr_##__VA_ARGS__, none, none) - * - * is necessary to prevent macro expansion before evaluating attr__(). - * Otherwise, we could get - * - * attr(printf(1, 2)) - * => attr__(__builtin_printf(1, 2), none, none) - * => attr____builtin_printf(1, 2) - * => error - */ -#define attr__(a1, a2, a3, a4, a5, a6, a7, a8, a9, none, ...) \ - a1 \ - attr_##a2 \ - attr_##a3 \ - attr_##a4 \ - attr_##a5 \ - attr_##a6 \ - attr_##a7 \ - attr_##a8 \ - attr_##a9 \ - attr_too_many_##none(__VA_ARGS__) - -// Ignore `attr_none` from expanding 1-9 argument attr(a1, a2, ...) -#define attr_none -// Ignore `attr_` from expanding 0-argument attr() -#define attr_ -// Only trigger an error on more than 9 arguments -#define attr_too_many_none(...) - -#endif // BFS_CONFIG_H diff --git a/src/ctx.h b/src/ctx.h index e14db21..fc3020c 100644 --- a/src/ctx.h +++ b/src/ctx.h @@ -8,9 +8,9 @@ #ifndef BFS_CTX_H #define BFS_CTX_H +#include "prelude.h" #include "alloc.h" #include "bftw.h" -#include "config.h" #include "diag.h" #include "expr.h" #include "trie.h" diff --git a/src/diag.c b/src/diag.c index cb27b92..deb6f26 100644 --- a/src/diag.c +++ b/src/diag.c @@ -1,11 +1,11 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "diag.h" #include "alloc.h" #include "bfstd.h" #include "color.h" -#include "config.h" #include "ctx.h" #include "dstring.h" #include "expr.h" diff --git a/src/diag.h b/src/diag.h index 4054c48..2b13609 100644 --- a/src/diag.h +++ b/src/diag.h @@ -8,7 +8,7 @@ #ifndef BFS_DIAG_H #define BFS_DIAG_H -#include "config.h" +#include "prelude.h" #include /** diff --git a/src/dir.c b/src/dir.c index 98518f2..53c9be3 100644 --- a/src/dir.c +++ b/src/dir.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "dir.h" #include "alloc.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include "sanity.h" #include "trie.h" diff --git a/src/dstring.c b/src/dstring.c index 10b0fad..913dda8 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "dstring.h" #include "alloc.h" #include "bit.h" -#include "config.h" #include "diag.h" #include #include diff --git a/src/dstring.h b/src/dstring.h index 6006199..9ea7eb9 100644 --- a/src/dstring.h +++ b/src/dstring.h @@ -8,8 +8,8 @@ #ifndef BFS_DSTRING_H #define BFS_DSTRING_H +#include "prelude.h" #include "bfstd.h" -#include "config.h" #include #include diff --git a/src/eval.c b/src/eval.c index d0112c2..b103912 100644 --- a/src/eval.c +++ b/src/eval.c @@ -5,12 +5,12 @@ * Implementation of all the primary expressions. */ +#include "prelude.h" #include "eval.h" #include "bar.h" #include "bfstd.h" #include "bftw.h" #include "color.h" -#include "config.h" #include "ctx.h" #include "diag.h" #include "dir.h" diff --git a/src/eval.h b/src/eval.h index ae43628..4dd7996 100644 --- a/src/eval.h +++ b/src/eval.h @@ -9,7 +9,7 @@ #ifndef BFS_EVAL_H #define BFS_EVAL_H -#include "config.h" +#include "prelude.h" struct bfs_ctx; struct bfs_expr; diff --git a/src/exec.c b/src/exec.c index 60bfd28..e782d49 100644 --- a/src/exec.c +++ b/src/exec.c @@ -1,12 +1,12 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "exec.h" #include "alloc.h" #include "bfstd.h" #include "bftw.h" #include "color.h" -#include "config.h" #include "ctx.h" #include "diag.h" #include "dstring.h" diff --git a/src/expr.h b/src/expr.h index 75cb5fd..7bcace7 100644 --- a/src/expr.h +++ b/src/expr.h @@ -8,8 +8,8 @@ #ifndef BFS_EXPR_H #define BFS_EXPR_H +#include "prelude.h" #include "color.h" -#include "config.h" #include "eval.h" #include "stat.h" #include diff --git a/src/fsade.c b/src/fsade.c index 0810c7f..34a4d57 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -1,11 +1,11 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "fsade.h" #include "atomic.h" #include "bfstd.h" #include "bftw.h" -#include "config.h" #include "dir.h" #include "dstring.h" #include "sanity.h" diff --git a/src/fsade.h b/src/fsade.h index 1f1dbfc..6300852 100644 --- a/src/fsade.h +++ b/src/fsade.h @@ -9,7 +9,7 @@ #ifndef BFS_FSADE_H #define BFS_FSADE_H -#include "config.h" +#include "prelude.h" #define BFS_CAN_CHECK_ACL BFS_USE_SYS_ACL_H diff --git a/src/ioq.c b/src/ioq.c index b936681..189bdac 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -118,12 +118,12 @@ * [1]: https://arxiv.org/abs/2201.02179 */ +#include "prelude.h" #include "ioq.h" #include "alloc.h" #include "atomic.h" #include "bfstd.h" #include "bit.h" -#include "config.h" #include "diag.h" #include "dir.h" #include "stat.h" diff --git a/src/ioq.h b/src/ioq.h index 818eea6..d8e1573 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -8,7 +8,7 @@ #ifndef BFS_IOQ_H #define BFS_IOQ_H -#include "config.h" +#include "prelude.h" #include "dir.h" #include "stat.h" #include diff --git a/src/main.c b/src/main.c index e120f03..9d8b206 100644 --- a/src/main.c +++ b/src/main.c @@ -26,7 +26,7 @@ * - bit.h (bit manipulation) * - bfstd.[ch] (standard library wrappers/polyfills) * - color.[ch] (for pretty terminal colors) - * - config.h (configuration and feature/platform detection) + * - prelude.h (configuration and feature/platform detection) * - diag.[ch] (formats diagnostic messages) * - dir.[ch] (a directory API facade) * - dstring.[ch] (a dynamic string library) @@ -45,8 +45,8 @@ * - xtime.[ch] (date/time handling utilities) */ +#include "prelude.h" #include "bfstd.h" -#include "config.h" #include "ctx.h" #include "diag.h" #include "eval.h" diff --git a/src/mtab.c b/src/mtab.c index 86ae151..7905d14 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "mtab.h" #include "alloc.h" #include "bfstd.h" -#include "config.h" #include "stat.h" #include "trie.h" #include diff --git a/src/mtab.h b/src/mtab.h index d99d78f..67290c2 100644 --- a/src/mtab.h +++ b/src/mtab.h @@ -8,7 +8,7 @@ #ifndef BFS_MTAB_H #define BFS_MTAB_H -#include "config.h" +#include "prelude.h" struct bfs_stat; diff --git a/src/opt.c b/src/opt.c index b74b4e1..ffc795b 100644 --- a/src/opt.c +++ b/src/opt.c @@ -25,11 +25,11 @@ * effects are reachable at all, skipping the traversal if not. */ +#include "prelude.h" #include "opt.h" #include "bftw.h" #include "bit.h" #include "color.h" -#include "config.h" #include "ctx.h" #include "diag.h" #include "dir.h" diff --git a/src/parse.c b/src/parse.c index a3e32fe..c2ae58f 100644 --- a/src/parse.c +++ b/src/parse.c @@ -8,12 +8,12 @@ * flags like always-true options, and skipping over paths wherever they appear. */ +#include "prelude.h" #include "parse.h" #include "alloc.h" #include "bfstd.h" #include "bftw.h" #include "color.h" -#include "config.h" #include "ctx.h" #include "diag.h" #include "dir.h" diff --git a/src/prelude.h b/src/prelude.h new file mode 100644 index 0000000..c3a0752 --- /dev/null +++ b/src/prelude.h @@ -0,0 +1,377 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +/** + * Configuration and feature/platform detection. + */ + +#ifndef BFS_PRELUDE_H +#define BFS_PRELUDE_H + +// Possible __STDC_VERSION__ values + +#define C95 199409L +#define C99 199901L +#define C11 201112L +#define C17 201710L +#define C23 202311L + +#include + +#if __STDC_VERSION__ < C23 +# include +# include +# include +#endif + +// bfs packaging configuration + +#ifndef BFS_COMMAND +# define BFS_COMMAND "bfs" +#endif +#ifndef BFS_HOMEPAGE +# define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html" +#endif + +// This is a symbol instead of a literal so we don't have to rebuild everything +// when the version number changes +extern const char bfs_version[]; + +// Check for system headers + +#ifdef __has_include + +#if __has_include() +# define BFS_HAS_MNTENT_H true +#endif +#if __has_include() +# define BFS_HAS_PATHS_H true +#endif +#if __has_include() +# define BFS_HAS_SYS_ACL_H true +#endif +#if __has_include() +# define BFS_HAS_SYS_CAPABILITY_H true +#endif +#if __has_include() +# define BFS_HAS_SYS_EXTATTR_H true +#endif +#if __has_include() +# define BFS_HAS_SYS_MKDEV_H true +#endif +#if __has_include() +# define BFS_HAS_SYS_PARAM_H true +#endif +#if __has_include() +# define BFS_HAS_SYS_SYSMACROS_H true +#endif +#if __has_include() +# define BFS_HAS_SYS_XATTR_H true +#endif +#if __has_include() +# define BFS_HAS_THREADS_H true +#endif +#if __has_include() +# define BFS_HAS_UTIL_H true +#endif + +#else // !__has_include + +#define BFS_HAS_MNTENT_H __GLIBC__ +#define BFS_HAS_PATHS_H true +#define BFS_HAS_SYS_ACL_H true +#define BFS_HAS_SYS_CAPABILITY_H __linux__ +#define BFS_HAS_SYS_EXTATTR_H __FreeBSD__ +#define BFS_HAS_SYS_MKDEV_H false +#define BFS_HAS_SYS_PARAM_H true +#define BFS_HAS_SYS_SYSMACROS_H __GLIBC__ +#define BFS_HAS_SYS_XATTR_H __linux__ +#define BFS_HAS_THREADS_H (!__STDC_NO_THREADS__) +#define BFS_HAS_UTIL_H __NetBSD__ + +#endif // !__has_include + +#ifndef BFS_USE_MNTENT_H +# define BFS_USE_MNTENT_H BFS_HAS_MNTENT_H +#endif +#ifndef BFS_USE_PATHS_H +# define BFS_USE_PATHS_H BFS_HAS_PATHS_H +#endif +#ifndef BFS_USE_SYS_ACL_H +# define BFS_USE_SYS_ACL_H (BFS_HAS_SYS_ACL_H && !__illumos__ && (!__linux__ || BFS_USE_LIBACL)) +#endif +#ifndef BFS_USE_SYS_CAPABILITY_H +# define BFS_USE_SYS_CAPABILITY_H (BFS_HAS_SYS_CAPABILITY_H && !__FreeBSD__ && (!__linux__ || BFS_USE_LIBCAP)) +#endif +#ifndef BFS_USE_SYS_EXTATTR_H +# define BFS_USE_SYS_EXTATTR_H (BFS_HAS_SYS_EXTATTR_H && !__DragonFly__) +#endif +#ifndef BFS_USE_SYS_MKDEV_H +# define BFS_USE_SYS_MKDEV_H BFS_HAS_SYS_MKDEV_H +#endif +#ifndef BFS_USE_SYS_PARAM_H +# define BFS_USE_SYS_PARAM_H BFS_HAS_SYS_PARAM_H +#endif +#ifndef BFS_USE_SYS_SYSMACROS_H +# define BFS_USE_SYS_SYSMACROS_H BFS_HAS_SYS_SYSMACROS_H +#endif +#ifndef BFS_USE_SYS_XATTR_H +# define BFS_USE_SYS_XATTR_H BFS_HAS_SYS_XATTR_H +#endif +#ifndef BFS_USE_THREADS_H +# define BFS_USE_THREADS_H BFS_HAS_THREADS_H +#endif +#ifndef BFS_USE_UTIL_H +# define BFS_USE_UTIL_H BFS_HAS_UTIL_H +#endif + +// Stub out feature detection on old/incompatible compilers + +#ifndef __has_feature +# define __has_feature(feat) false +#endif + +#ifndef __has_c_attribute +# define __has_c_attribute(attr) false +#endif + +#ifndef __has_attribute +# define __has_attribute(attr) false +#endif + +// Platform detection + +// Get the definition of BSD if available +#if BFS_USE_SYS_PARAM_H +# include +#endif + +#ifndef __GLIBC_PREREQ +# define __GLIBC_PREREQ(maj, min) false +#endif + +#ifndef __NetBSD_Prereq__ +# define __NetBSD_Prereq__(maj, min, patch) false +#endif + +// Fundamental utilities + +/** + * Get the length of an array. + */ +#define countof(array) (sizeof(array) / sizeof(0[array])) + +/** + * False sharing/destructive interference/largest cache line size. + */ +#ifdef __GCC_DESTRUCTIVE_SIZE +# define FALSE_SHARING_SIZE __GCC_DESTRUCTIVE_SIZE +#else +# define FALSE_SHARING_SIZE 64 +#endif + +/** + * True sharing/constructive interference/smallest cache line size. + */ +#ifdef __GCC_CONSTRUCTIVE_SIZE +# define TRUE_SHARING_SIZE __GCC_CONSTRUCTIVE_SIZE +#else +# define TRUE_SHARING_SIZE 64 +#endif + +/** + * Alignment specifier that avoids false sharing. + */ +#define cache_align alignas(FALSE_SHARING_SIZE) + +#if __COSMOPOLITAN__ +typedef long double max_align_t; +#endif + +// Wrappers for attributes + +/** + * Silence warnings about switch/case fall-throughs. + */ +#if __has_attribute(fallthrough) +# define fallthru __attribute__((fallthrough)) +#else +# define fallthru ((void)0) +#endif + +/** + * Silence warnings about unused declarations. + */ +#if __has_attribute(unused) +# define attr_maybe_unused __attribute__((unused)) +#else +# define attr_maybe_unused +#endif + +/** + * Warn if a value is unused. + */ +#if __has_attribute(warn_unused_result) +# define attr_nodiscard __attribute__((warn_unused_result)) +#else +# define attr_nodiscard +#endif + +/** + * Hint to avoid inlining a function. + */ +#if __has_attribute(noinline) +# define attr_noinline __attribute__((noinline)) +#else +# define attr_noinline +#endif + +/** + * Hint that a function is unlikely to be called. + */ +#if __has_attribute(cold) +# define attr_cold attr_noinline __attribute__((cold)) +#else +# define attr_cold attr_noinline +#endif + +/** + * Adds compiler warnings for bad printf()-style function calls, if supported. + */ +#if __has_attribute(format) +# define attr_printf(fmt, args) __attribute__((format(printf, fmt, args))) +#else +# define attr_printf(fmt, args) +#endif + +/** + * Annotates allocator-like functions. + */ +#if __has_attribute(malloc) +# if __GNUC__ >= 11 && !__OPTIMIZE__ // malloc(deallocator) disables inlining on GCC +# define attr_malloc(...) attr_nodiscard __attribute__((malloc(__VA_ARGS__))) +# else +# define attr_malloc(...) attr_nodiscard __attribute__((malloc)) +# endif +#else +# define attr_malloc(...) attr_nodiscard +#endif + +/** + * Specifies that a function returns allocations with a given alignment. + */ +#if __has_attribute(alloc_align) +# define attr_alloc_align(param) __attribute__((alloc_align(param))) +#else +# define attr_alloc_align(param) +#endif + +/** + * Specifies that a function returns allocations with a given size. + */ +#if __has_attribute(alloc_size) +# define attr_alloc_size(...) __attribute__((alloc_size(__VA_ARGS__))) +#else +# define attr_alloc_size(...) +#endif + +/** + * Shorthand for attr_alloc_align() and attr_alloc_size(). + */ +#define attr_aligned_alloc(align, ...) \ + attr_alloc_align(align) \ + attr_alloc_size(__VA_ARGS__) + +/** + * Check if function multiversioning via GNU indirect functions (ifunc) is supported. + */ +#ifndef BFS_USE_TARGET_CLONES +# if __has_attribute(target_clones) && (__GLIBC__ || __FreeBSD__) +# define BFS_USE_TARGET_CLONES true +# endif +#endif + +/** + * Apply the target_clones attribute, if available. + */ +#if BFS_USE_TARGET_CLONES +# define attr_target_clones(...) __attribute__((target_clones(__VA_ARGS__))) +#else +# define attr_target_clones(...) +#endif + +/** + * Shorthand for multiple attributes at once. attr(a, b(c), d) is equivalent to + * + * attr_a + * attr_b(c) + * attr_d + */ +#define attr(...) \ + attr__(attr_##__VA_ARGS__, none, none, none, none, none, none, none, none, none, ) + +/** + * attr() helper. For exposition, pretend we support only 2 args, instead of 9. + * There are a few cases: + * + * attr() + * => attr__(attr_, none, none) + * => attr_ => + * attr_none => + * attr_too_many_none() => + * + * attr(a) + * => attr__(attr_a, none, none) + * => attr_a => __attribute__((a)) + * attr_none => + * attr_too_many_none() => + * + * attr(a, b(c)) + * => attr__(attr_a, b(c), none, none) + * => attr_a => __attribute__((a)) + * attr_b(c) => __attribute__((b(c))) + * attr_too_many_none(none) => + * + * attr(a, b(c), d) + * => attr__(attr_a, b(c), d, none, none) + * => attr_a => __attribute__((a)) + * attr_b(c) => __attribute__((b(c))) + * attr_too_many_d(none, none) => error + * + * Some attribute names are the same as standard library functions, e.g. printf. + * Standard libraries are permitted to define these functions as macros, like + * + * #define printf(...) __builtin_printf(__VA_ARGS__) + * + * The token paste in + * + * #define attr(...) attr__(attr_##__VA_ARGS__, none, none) + * + * is necessary to prevent macro expansion before evaluating attr__(). + * Otherwise, we could get + * + * attr(printf(1, 2)) + * => attr__(__builtin_printf(1, 2), none, none) + * => attr____builtin_printf(1, 2) + * => error + */ +#define attr__(a1, a2, a3, a4, a5, a6, a7, a8, a9, none, ...) \ + a1 \ + attr_##a2 \ + attr_##a3 \ + attr_##a4 \ + attr_##a5 \ + attr_##a6 \ + attr_##a7 \ + attr_##a8 \ + attr_##a9 \ + attr_too_many_##none(__VA_ARGS__) + +// Ignore `attr_none` from expanding 1-9 argument attr(a1, a2, ...) +#define attr_none +// Ignore `attr_` from expanding 0-argument attr() +#define attr_ +// Only trigger an error on more than 9 arguments +#define attr_too_many_none(...) + +#endif // BFS_PRELUDE_H diff --git a/src/printf.c b/src/printf.c index 3b8269e..4df399b 100644 --- a/src/printf.c +++ b/src/printf.c @@ -1,12 +1,12 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "printf.h" #include "alloc.h" #include "bfstd.h" #include "bftw.h" #include "color.h" -#include "config.h" #include "ctx.h" #include "diag.h" #include "dir.h" diff --git a/src/pwcache.c b/src/pwcache.c index 79437d8..af8c237 100644 --- a/src/pwcache.c +++ b/src/pwcache.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "pwcache.h" #include "alloc.h" -#include "config.h" #include "trie.h" #include #include diff --git a/src/sanity.h b/src/sanity.h index 423e6ff..e168b8f 100644 --- a/src/sanity.h +++ b/src/sanity.h @@ -8,7 +8,7 @@ #ifndef BFS_SANITY_H #define BFS_SANITY_H -#include "config.h" +#include "prelude.h" #include #if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) diff --git a/src/stat.c b/src/stat.c index 2f2743b..eca5bab 100644 --- a/src/stat.c +++ b/src/stat.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "stat.h" #include "atomic.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include "sanity.h" #include diff --git a/src/stat.h b/src/stat.h index 856a2ca..1fdd263 100644 --- a/src/stat.h +++ b/src/stat.h @@ -12,7 +12,7 @@ #ifndef BFS_STAT_H #define BFS_STAT_H -#include "config.h" +#include "prelude.h" #include #include #include diff --git a/src/thread.c b/src/thread.c index 200d8c3..3793896 100644 --- a/src/thread.c +++ b/src/thread.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "thread.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include #include diff --git a/src/thread.h b/src/thread.h index 8174fe4..db11bd8 100644 --- a/src/thread.h +++ b/src/thread.h @@ -8,7 +8,7 @@ #ifndef BFS_THREAD_H #define BFS_THREAD_H -#include "config.h" +#include "prelude.h" #include #if __STDC_VERSION__ < C23 && !defined(thread_local) diff --git a/src/trie.c b/src/trie.c index f275064..808953e 100644 --- a/src/trie.c +++ b/src/trie.c @@ -81,10 +81,10 @@ * and insert intermediate singleton "jump" nodes when necessary. */ +#include "prelude.h" #include "trie.h" #include "alloc.h" #include "bit.h" -#include "config.h" #include "diag.h" #include "list.h" #include diff --git a/src/xregex.c b/src/xregex.c index 3df27f0..c2711bc 100644 --- a/src/xregex.c +++ b/src/xregex.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "xregex.h" #include "alloc.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include "sanity.h" #include "thread.h" diff --git a/src/xspawn.c b/src/xspawn.c index 347625d..113d7ec 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "xspawn.h" #include "alloc.h" #include "bfstd.h" -#include "config.h" #include "list.h" #include #include diff --git a/src/xspawn.h b/src/xspawn.h index a20cbd0..6a8f54a 100644 --- a/src/xspawn.h +++ b/src/xspawn.h @@ -8,7 +8,7 @@ #ifndef BFS_XSPAWN_H #define BFS_XSPAWN_H -#include "config.h" +#include "prelude.h" #include #include #include diff --git a/src/xtime.c b/src/xtime.c index bcf6dd3..91ed915 100644 --- a/src/xtime.c +++ b/src/xtime.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "xtime.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include #include diff --git a/tests/alloc.c b/tests/alloc.c index 54b84ba..6c0defd 100644 --- a/tests/alloc.c +++ b/tests/alloc.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" #include "alloc.h" -#include "config.h" #include "diag.h" #include #include diff --git a/tests/bfstd.c b/tests/bfstd.c index 5e408ca..07b68b0 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include #include diff --git a/tests/bit.c b/tests/bit.c index b444e50..674d1b2 100644 --- a/tests/bit.c +++ b/tests/bit.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" #include "bit.h" -#include "config.h" #include "diag.h" #include #include diff --git a/tests/ioq.c b/tests/ioq.c index a69f2bf..ef5ee3b 100644 --- a/tests/ioq.c +++ b/tests/ioq.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" #include "ioq.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include "dir.h" #include diff --git a/tests/main.c b/tests/main.c index 281c417..429772b 100644 --- a/tests/main.c +++ b/tests/main.c @@ -5,10 +5,10 @@ * Entry point for unit tests. */ +#include "prelude.h" #include "tests.h" #include "bfstd.h" #include "color.h" -#include "config.h" #include #include #include diff --git a/tests/tests.h b/tests/tests.h index d61ffd7..9078938 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -8,7 +8,7 @@ #ifndef BFS_TESTS_H #define BFS_TESTS_H -#include "config.h" +#include "prelude.h" #include "diag.h" /** Unit test function type. */ diff --git a/tests/trie.c b/tests/trie.c index 2a6eb48..4667322 100644 --- a/tests/trie.c +++ b/tests/trie.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" #include "trie.h" -#include "config.h" #include "diag.h" #include #include diff --git a/tests/xspawn.c b/tests/xspawn.c index fd8362e..7362aa5 100644 --- a/tests/xspawn.c +++ b/tests/xspawn.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" #include "alloc.h" #include "bfstd.h" -#include "config.h" #include "dstring.h" #include "xspawn.h" #include diff --git a/tests/xtime.c b/tests/xtime.c index fd7aa0f..a7c63d2 100644 --- a/tests/xtime.c +++ b/tests/xtime.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" #include "xtime.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include #include diff --git a/tests/xtouch.c b/tests/xtouch.c index b1daec7..82d749d 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -1,8 +1,8 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "bfstd.h" -#include "config.h" #include "sanity.h" #include "xtime.h" #include -- cgit v1.2.3