[GH-ISSUE #90] Give a sensible error on nonexistent directories #35

Closed
opened 2026-06-08 11:25:24 +03:00 by zhus · 14 comments
Owner

Originally created by @ghost on GitHub (May 8, 2020).
Original GitHub issue: https://github.com/bootandy/dust/issues/90

$ ls foo
ls: foo: No such file or directory
$ dust foo
Did not have permissions for all directories
$ 

This error message is wrong. I'd expect "no such file or directory" instead like ls.

Originally created by @ghost on GitHub (May 8, 2020). Original GitHub issue: https://github.com/bootandy/dust/issues/90 ``` $ ls foo ls: foo: No such file or directory $ dust foo Did not have permissions for all directories $ ``` This error message is wrong. I'd expect "no such file or directory" instead like ls.
zhus closed this issue 2026-06-08 11:25:24 +03:00
Author
Owner

@bootandy commented on GitHub (May 8, 2020):

Agreed, I'll look into this.

<!-- gh-comment-id:625712388 --> @bootandy commented on GitHub (May 8, 2020): Agreed, I'll look into this.
Author
Owner

@agudeloromero commented on GitHub (Sep 7, 2023):

Hi there,

I am having the issue below and changing the permission didn't work.
Any advice?

Thanks in advance :)

$ll
drwxrwxrwx  foo

$dust foo
Did not have permissions for all directories 

<!-- gh-comment-id:1709588116 --> @agudeloromero commented on GitHub (Sep 7, 2023): Hi there, I am having the issue below and changing the permission didn't work. Any advice? Thanks in advance :) ``` $ll drwxrwxrwx foo $dust foo Did not have permissions for all directories ```
Author
Owner

@bootandy commented on GitHub (Sep 7, 2023):

can you run dust --version.

I can't replicate the above on my box.

<!-- gh-comment-id:1709615704 --> @bootandy commented on GitHub (Sep 7, 2023): can you run `dust --version`. I can't replicate the above on my box.
Author
Owner

@SteveLauC commented on GitHub (Dec 16, 2023):

Same here, tested with the latest commit c8b61d2f46f5c1740bc93971deeb90c03b77cf68:

$ dust --version
Dust 0.8.6

$ uname -a
Linux debian 6.1.0-16-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.67-1 (2023-12-12) x86_64 GNU/Linux

$ df -Th .
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/nvme0n1p4 ext4  440G   68G  350G  17% /home

$ ll
Permissions Links Size User  Group Date Modified Name
drwxr-xr-x      2    - steve steve 30 Nov 10:07  .
drwx------     31    - steve steve 16 Dec 10:08  ..

$ dust foo
Did not have permissions for all directories
<!-- gh-comment-id:1858680613 --> @SteveLauC commented on GitHub (Dec 16, 2023): Same here, tested with the latest commit `c8b61d2f46f5c1740bc93971deeb90c03b77cf68`: ```sh $ dust --version Dust 0.8.6 $ uname -a Linux debian 6.1.0-16-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.67-1 (2023-12-12) x86_64 GNU/Linux $ df -Th . Filesystem Type Size Used Avail Use% Mounted on /dev/nvme0n1p4 ext4 440G 68G 350G 17% /home $ ll Permissions Links Size User Group Date Modified Name drwxr-xr-x 2 - steve steve 30 Nov 10:07 . drwx------ 31 - steve steve 16 Dec 10:08 .. $ dust foo Did not have permissions for all directories ```
Author
Owner

@SteveLauC commented on GitHub (Dec 16, 2023):

Well, I checked out the source code, seems that PR #130 has been reverted? And the current behavior comes from the walk() function:

fn walk(dir: PathBuf, walk_data: &WalkData, depth: usize) -> Option<Node> {
    let prog_data = &walk_data.progress_data;
    let mut children = vec![];

    if let Ok(entries) = fs::read_dir(&dir) {
        children = entries
            .into_iter()
            .par_bridge()
            .filter_map(|entry| {
                if let Ok(ref entry) = entry {
                    // uncommenting the below line gives simpler code but
                    // rayon doesn't parallelize as well giving a 3X performance drop
                    // hence we unravel the recursion a bit

                    // return walk(entry.path(), walk_data, depth)

                    if !ignore_file(entry, walk_data) {
                        if let Ok(data) = entry.file_type() {
                            if data.is_dir() || (walk_data.follow_links && data.is_symlink()) {
                                return walk(entry.path(), walk_data, depth + 1);
                            }

                            let node = build_node(
                                entry.path(),
                                vec![],
                                walk_data.filter_regex,
                                walk_data.invert_filter_regex,
                                walk_data.use_apparent_size,
                                data.is_symlink(),
                                data.is_file(),
                                walk_data.by_filecount,
                                depth,
                            );

                            prog_data.num_files.fetch_add(1, ORDERING);
                            if let Some(ref file) = node {
                                prog_data.total_file_size.fetch_add(file.size, ORDERING);
                            }

                            return node;
                        }
                    }
                } else {
                    prog_data.no_permissions.store(true, ORDERING)
                }
                None
            })
            .collect();
    } else if !dir.is_file() {
        walk_data.progress_data.no_permissions.store(true, ORDERING)
    }
    build_node(
        dir,
        children,
        walk_data.filter_regex,
        walk_data.invert_filter_regex,
        walk_data.use_apparent_size,
        false,
        false,
        walk_data.by_filecount,
        depth,
    )
}

When walk()ing a non-existing file, !dir.is_file() would be true, then the no_permissions field wil be set to true, then we will get that error Did not have permissions for all directories.

<!-- gh-comment-id:1858690235 --> @SteveLauC commented on GitHub (Dec 16, 2023): Well, I checked out the source code, seems that PR #130 has been reverted? And the current behavior comes from the `walk()` function: ```rs fn walk(dir: PathBuf, walk_data: &WalkData, depth: usize) -> Option<Node> { let prog_data = &walk_data.progress_data; let mut children = vec![]; if let Ok(entries) = fs::read_dir(&dir) { children = entries .into_iter() .par_bridge() .filter_map(|entry| { if let Ok(ref entry) = entry { // uncommenting the below line gives simpler code but // rayon doesn't parallelize as well giving a 3X performance drop // hence we unravel the recursion a bit // return walk(entry.path(), walk_data, depth) if !ignore_file(entry, walk_data) { if let Ok(data) = entry.file_type() { if data.is_dir() || (walk_data.follow_links && data.is_symlink()) { return walk(entry.path(), walk_data, depth + 1); } let node = build_node( entry.path(), vec![], walk_data.filter_regex, walk_data.invert_filter_regex, walk_data.use_apparent_size, data.is_symlink(), data.is_file(), walk_data.by_filecount, depth, ); prog_data.num_files.fetch_add(1, ORDERING); if let Some(ref file) = node { prog_data.total_file_size.fetch_add(file.size, ORDERING); } return node; } } } else { prog_data.no_permissions.store(true, ORDERING) } None }) .collect(); } else if !dir.is_file() { walk_data.progress_data.no_permissions.store(true, ORDERING) } build_node( dir, children, walk_data.filter_regex, walk_data.invert_filter_regex, walk_data.use_apparent_size, false, false, walk_data.by_filecount, depth, ) } ``` When `walk()`ing a non-existing file, `!dir.is_file()` would be true, then the `no_permissions` field wil be set to `true`, then we will get that error `Did not have permissions for all directories`.
Author
Owner

@SteveLauC commented on GitHub (Dec 16, 2023):

Since dust uses rayon to iterate over the files under a directory, the current error-handling logic is probably hard to deal with such cases, what about we use a MPSC channel to connect the rayon threads and the main thread, when a rayon thread fails to do some I/O operation, it sends the error info to the main thread, then the main thread will stop the running threads and exit with the received error message

<!-- gh-comment-id:1858691096 --> @SteveLauC commented on GitHub (Dec 16, 2023): Since dust uses rayon to iterate over the files under a directory, the current error-handling logic is probably hard to deal with such cases, what about we use a MPSC channel to connect the rayon threads and the main thread, when a rayon thread fails to do some I/O operation, it sends the error info to the main thread, then the main thread will stop the running threads and exit with the received error message
Author
Owner

@bootandy commented on GitHub (Jan 4, 2024):

I think you are referring to something subtly different @SteveLauC - What you seem to be saying (correct me if I'm wrong) is that if the file doesn't exist it should print No such file or directory instead of Did not have permissions for all directories.

The thinking behind this is to merge both similar errors: No such file or directory and cannot open directory 'foo': Permission denied into one error:Did not have permissions for all directories.

The reason for the above decision is to minimize error output, early versions would print several rows like this:
cannot open directory 'tmp2': Permission denied, cannot open directory 'tmp3': Permission denied, cannot open directory 'tmp4': Permission denied
and your 'good' output was lost in a sea of permission errors.

I'm not entirely happy with the above decision, but it seemed like the best compromise - also that error message has confused other people. I will change the text to make it clearer. If you have any thoughts please comment.

then the main thread will stop the running threads and exit with the received error message

This doesn't happen, the thread keeps running

<!-- gh-comment-id:1876000058 --> @bootandy commented on GitHub (Jan 4, 2024): I think you are referring to something subtly different @SteveLauC - What you seem to be saying (correct me if I'm wrong) is that if the file doesn't exist it should print ` No such file or directory` instead of `Did not have permissions for all directories.` The thinking behind this is to merge both similar errors: ` No such file or directory` and `cannot open directory 'foo': Permission denied` into one error:`Did not have permissions for all directories.` The reason for the above decision is to minimize error output, early versions would print several rows like this: ` cannot open directory 'tmp2': Permission denied`, `cannot open directory 'tmp3': Permission denied`, `cannot open directory 'tmp4': Permission denied` and your 'good' output was lost in a sea of permission errors. I'm not entirely happy with the above decision, but it seemed like the best compromise - also that error message has confused other people. I will change the text to make it clearer. If you have any thoughts please comment. > then the main thread will stop the running threads and exit with the received error message This doesn't happen, the thread keeps running
Author
Owner

@SteveLauC commented on GitHub (Jan 4, 2024):

I think you are referring to something subtly different @SteveLauC - What you seem to be saying (correct me if I'm wrong) is that if the file doesn't exist it should print No such file or directory instead of Did not have permissions for all directories.

Yes, I was talking about the error message, this is exactly what this issue wants:

$ ls foo
ls: foo: No such file or directory
$ dust foo
Did not have permissions for all directories
$ 

This error message is wrong. I'd expect "no such file or directory" instead like ls.


The thinking behind this is to merge both similar errors: No such file or directory and cannot open directory 'foo': Permission
denied into one error:Did not have permissions for all directories.

The reason for the above decision is to minimize error output, early versions would print several rows like this:
cannot open directory 'tmp2': Permission denied, cannot open directory 'tmp3': Permission denied, cannot open directory
'tmp4': Permission denied and your 'good' output was lost in a sea of permission errors.

I'm not entirely happy with the above decision, but it seemed like the best compromise - also that error message has
confused other people. I will change the text to make it clearer. If you have any thoughts please comment.

I agree that the error handling here is kinda hard and it depends what you want


This doesn't happen, the thread keeps running

Since dust uses rayon to iterate over the files under a directory, the current error-handling logic is probably hard to deal with such cases, what about we use a MPSC channel to connect the rayon threads and the main thread, when a rayon thread fails to do some I/O operation, it sends the error info to the main thread, then the main thread will stop the running threads and exit with the received error message

My last comment is indeed incorrect, we cannot stop a thread that is blocked/running, unless we abort the main thread, my last comment is more like that we join() the rayon threads, and whenever any one of them gets an error, we just print the error message and exit. Though as I said,

it depends what you want

I am not sure if this model is what you want.

For error message propagation, I think it might be better to use Box<dyn Error> or Anyhow::Error since there could be tons of error cases.

<!-- gh-comment-id:1876194419 --> @SteveLauC commented on GitHub (Jan 4, 2024): > I think you are referring to something subtly different @SteveLauC - What you seem to be saying (correct me if I'm wrong) is that if the file doesn't exist it should print No such file or directory instead of Did not have permissions for all directories. Yes, I was talking about the error message, this is exactly what this issue wants: > ``` > $ ls foo > ls: foo: No such file or directory > $ dust foo > Did not have permissions for all directories > $ > ``` > > This error message is wrong. I'd expect "no such file or directory" instead like ls. ----- > The thinking behind this is to merge both similar errors: No such file or directory and cannot open directory 'foo': Permission > denied into one error:Did not have permissions for all directories. > > The reason for the above decision is to minimize error output, early versions would print several rows like this: > cannot open directory 'tmp2': Permission denied, cannot open directory 'tmp3': Permission denied, cannot open directory > 'tmp4': Permission denied and your 'good' output was lost in a sea of permission errors. > > I'm not entirely happy with the above decision, but it seemed like the best compromise - also that error message has > confused other people. I will change the text to make it clearer. If you have any thoughts please comment. I agree that the error handling here is kinda hard and it depends what you want ----- > This doesn't happen, the thread keeps running > Since dust uses rayon to iterate over the files under a directory, the current error-handling logic is probably hard to deal with such cases, what about we use a MPSC channel to connect the rayon threads and the main thread, when a rayon thread fails to do some I/O operation, it sends the error info to the main thread, then the main thread will stop the running threads and exit with the received error message My last comment is indeed incorrect, we cannot stop a thread that is blocked/running, unless we abort the main thread, my last comment is more like that we `join()` the rayon threads, and whenever any one of them gets an error, we just print the error message and exit. Though as I said, > it depends what you want I am not sure if this model is what you want. For error message propagation, I think it might be better to use `Box<dyn Error>` or `Anyhow::Error` since there could be tons of error cases.
Author
Owner

@bootandy commented on GitHub (Jan 6, 2024):

I'm rethinking error messages in this branch: https://github.com/bootandy/dust/pull/360

this will bring back the 'no such file or directory' message

<!-- gh-comment-id:1879616715 --> @bootandy commented on GitHub (Jan 6, 2024): I'm rethinking error messages in this branch: https://github.com/bootandy/dust/pull/360 this will bring back the 'no such file or directory' message
Author
Owner

@bootandy commented on GitHub (Jan 7, 2024):

https://github.com/bootandy/dust/pull/361
version2 ^

<!-- gh-comment-id:1880029030 --> @bootandy commented on GitHub (Jan 7, 2024): https://github.com/bootandy/dust/pull/361 version2 ^
Author
Owner

@d-Rickyy-b commented on GitHub (Mar 30, 2024):

Since I might not be the only one, it might be helpful for others too. I got this error on every directory I tried, using the snap version of dust. When running as non-root, at least the home directory works, but no other dir. Using the binary from the github releases section works fine.

This is due to the way how snap handles applications and their permissions. This can be partly fixed by using the connect command.

$ snap connect dust:home
$ snap connect dust:mount-observe
$ snap connect dust:removable-media
$ snap connect dust:system-backup

Anyway, I found that using dust as a snap application is just not a smart idea.

<!-- gh-comment-id:2028079600 --> @d-Rickyy-b commented on GitHub (Mar 30, 2024): Since I might not be the only one, it might be helpful for others too. I got this error on **every directory** I tried, using the [snap version](https://github.com/daniejstriata/dust-snap) of dust. When running as non-root, at least the home directory works, but no other dir. Using the binary from the github releases section works fine. This is due to the way how snap handles applications and their permissions. This can be partly fixed by using the connect command. ``` $ snap connect dust:home $ snap connect dust:mount-observe $ snap connect dust:removable-media $ snap connect dust:system-backup ``` Anyway, I found that using dust as a snap application is just not a smart idea.
Author
Owner

@NBOOK commented on GitHub (Jul 12, 2025):

I think it would also be a good idea to return a sensible code when directories don't exist. I'll reuse the example above:

$ (ls foo; echo return: $?)
ls: cannot access 'foo': No such file or directory
return: 2

It seems that dust will return 0 regardless:

$ (dust foo; echo return: $?)
No such file or directory: foo
0B ┌── (total)│█                                                                  │   0%
return: 0
<!-- gh-comment-id:3065835963 --> @NBOOK commented on GitHub (Jul 12, 2025): I think it would also be a good idea to return a sensible code when directories don't exist. I'll reuse the example above: ``` $ (ls foo; echo return: $?) ls: cannot access 'foo': No such file or directory return: 2 ``` It seems that dust will return 0 regardless: ``` $ (dust foo; echo return: $?) No such file or directory: foo 0B ┌── (total)│█ │ 0% return: 0 ```
Author
Owner

@bootandy commented on GitHub (Jul 29, 2025):

Hmmm, yes for that example I agree it should exit with a 1.

Question:
dust dir_exists dir_not_exists

If you run dust with 2 dirs ^ and 1 does not exist and 1 does. Is that still a 'success' ?

<!-- gh-comment-id:3129890346 --> @bootandy commented on GitHub (Jul 29, 2025): Hmmm, yes for that example I agree it should exit with a 1. Question: `dust dir_exists dir_not_exists` If you run dust with 2 dirs ^ and 1 does not exist and 1 does. Is that still a 'success' ?
Author
Owner

@bootandy commented on GitHub (Aug 1, 2025):

behavour changed in v1.2.3

<!-- gh-comment-id:3141409371 --> @bootandy commented on GitHub (Aug 1, 2025): behavour changed in v1.2.3
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bootandy/archived-dust#35