Skip to content

Show a file's permissions, and make one's own files executable or not - #103

Open
JaxonWright wants to merge 2 commits into
omacom:mainfrom
JaxonWright:executable-and-permissions
Open

JaxonWright wants to merge 2 commits into
omacom:mainfrom
JaxonWright:executable-and-permissions

Conversation

@JaxonWright

@JaxonWright JaxonWright commented Nov 12, 2026 •

Copy link
Copy Markdown

You can't tell from Files whether a script will run, or make it run. The usual case is a downloaded install.sh, or one just written in a project. Today that needs a terminal and chmod +x. This adds a Permissions row to the details pane and a Make Executable entry to the menu.

What you'd notice

  • A Permissions row in the details pane, under Modified, written the way ls -l writes them: rw-r--r--, rwxr-xr-x. It's there for any file or folder on a local or mounted drive, but not in the Trash (nor inside a trashed folder), where nothing changes them. The row keeps its place while the permissions are read, so moving through files with the arrows doesn't shift the rows below it.
  • Make Executable in the right-click menu on your own files, right under Duplicate. Once the file runs, the entry reads Make Not Executable. It works on a selection too, and then it reads Make Not Executable only when every selected file already runs. The details pane updates as soon as you pick it.

Folders, links and other people's files don't get the entry. A folder's x means something else, and only a file's owner may change its mode.

Before After picking Make Executable
The menu on a rw-r--r-- script, with Make Executable highlighted The same script: the row reads rwxr-xr-x and the menu offers Make Not Executable

A folder's menu has no entry, and its row shows the folder's own permissions:

A folder's menu: Duplicate is followed by Copy, and the pane shows rwxr-xr-x

How it decides

  • Make Executable lets whoever may read the file run it, so a file you keep private stays private. rw-r--r-- becomes rwxr-xr-x, the same as chmod +x. A private rw------- becomes rwx------, where chmod +x would let everyone run it (rwx--x--x).
  • Make Not Executable takes running away from everyone.
  • If the change fails, the window says which files and why ("Couldn't make run.sh executable."), like the other file operations. A FAT or exFAT stick, for example, shows its files as yours but may refuse the change. I haven't tried one.
  • The file is opened once (O_PATH | O_NOFOLLOW), checked through that handle, and changed through it (its /proc/self/fd link, since fchmod() won't take an O_PATH handle). A link put anywhere in the path after the menu opened can't send the change to another file, and the new mode comes from the same file that's changed.

What changed

  • Backend::describe() reads the item's mode in its background read and adds permissions to the details. Until now it skipped the read for anything but pictures, text and trashed items. Now every local item gets one stat, off the UI thread, with the same skip for selections the cursor has already moved past.
    • These reads now run on a pool of four threads of their own, not the shared pool that folders are listed on. Moving across folders on a mount that stopped answering leaves one stuck stat per folder. In the shared pool, those stuck reads eventually stopped any folder from listing, even a local one. Greptile caught this, and I confirmed it on a real window: with a FUSE mount frozen by SIGSTOP, I moved across 20 of its folders and then opened a local folder. (main and both builds of this PR here also carry Tell the trash from the path, without asking every drive #93's fix, so leaving the dead mount doesn't stall first.)

      Three runs of the same steps. main lists the local folder; this PR's first push leaves it empty; with its own pool it lists again.

      Selecting a file there is a different, older problem: the Opens with row asks for its type on the UI thread (mimeFor()), so the window waits until the mount answers. That's worth its own PR.

  • FileOps gains canSetExecutable, executable and setExecutable, and a modesChanged(paths) signal. The change itself runs on the UI thread, like Rename and New Folder do; the menu stats the selection there as it opens, as it does for Burn and Extract. The details pane listens for that signal, because a mode change leaves the folder listing as it was, so the pane would otherwise keep showing the old permissions.
  • The menu asks once when it opens, the way Burn does, so no binding stats files.
  • Five English strings in en.json.

The mode isn't added to the folder listing's entries. #26 and #92 are reworking the listing and its memory, and only the selected item needs it.

Not in this PR: undo. The undo log records moves and renames. Picking the entry again puts the file back as it was.

Verification

I drove a real Files window on Hyprland: selected a rw-r--r-- script, opened the menu from the keyboard (Alt+⏎) and picked Make Executable. The file was rwxr-xr-x on disk, the row said rwxr-xr-x, and the reopened menu offered Make Not Executable. Picking that put the file back to rw-r--r--, and the row followed. A folder's menu has no entry, and its row shows the folder's own permissions. I only drove a single file in the window. The selection rules (one ineligible item hides the entry, mixed states offer Make Executable) are covered by the unit test.

Tests (suite 194 → 196, all pass):

  • fileOpsMakesFilesExecutable covers 644 to 755 and back, 600 to 700 and back, and a selection that also holds a folder (the folder is left alone, and modesChanged names only the files). It also refuses a folder, a link, an empty selection and a file you don't own.
  • backendShowsWhoMayReadWriteAndRun reads rw-r----- for a file and rwxr-xr-t for a sticky folder, for a PDF, which the old early return would have skipped.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UGcPV5q4WWVxgL57jZ2Vm7

The details pane gains a Permissions row, as ls writes them, read with
the rest of the details off the UI thread; it keeps its place while
they're read, and isn't shown in the trash. The menu on one's own files
gains Make Executable, or Make Not Executable when they all run
already: it lets whoever may read a file run it, so a private file
stays private, and takes running away from everyone. Folders, whose x
means something else, links, and other people's files don't get it.
The pane reads the item again when its permissions change, since the
folder looks the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UGcPV5q4WWVxgL57jZ2Vm7
@greptile-apps

greptile-apps Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The changes affect Folio's file details and file-action workflows.

Permission changes still need to move off the UI thread before merging.

Findings

  1. P1 Permission changes freeze the window ▶

Summary

Adds a Permissions row and a menu action to make owned files executable or not.

  • People can make their own files executable from the file menu.
  • The details pane shows the selected file or folder's permissions.

Reviews (2) · Last reviewed commit: "Keep the details' reads off the listing ..." · Reviewed by Greptile

Comment thread src/fileops.cpp Resolved
Comment thread src/backend.cpp Resolved
Comment thread src/fileops.cpp Outdated
Comment on lines +1043 to +1047
void FileOps::setExecutable(const QStringList &paths, bool on) {
QStringList changed, failures;
for (const QString &path : paths) {
struct stat st;
if (!ownFile(path, &st))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Permission changes freeze the window

setExecutable() changes every selected file directly on the UI thread. On a slow mounted drive, each fchmodat() can wait on the server; a large selection adds those waits together. The window cannot repaint, respond, or cancel until the loop returns. Run the changes through the existing background-job path and report their results afterward.

Knowledge Base Used: File operation backend

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this one as it is, on purpose. Rename and New Folder also change the disk on the UI thread (FileOps::rename, FileOps::newFolder), and the menu stats the selection on the UI thread as it opens, as it does for Burn and Extract, so on a stalled mount the window has already waited before the change is picked. Moving only the chmod to the background job path would add the busy lane and progress reporting for something that's one syscall per file on a drive that answers. I've noted this in the description so the maintainer can decide.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s fair. The existing UI-thread filesystem operations and menu-time stats make this consistent with the current interaction model, and the added selection size is bounded by the same selection the menu already inspects. I’ll treat synchronous permission changes as an intentional tradeoff, not a review finding.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

Comment thread src/PreviewPane.qml Resolved
…looked at

Moving across folders on a mount that stopped answering left a stuck
stat per folder in the shared pool, and once those filled it no folder
could be listed: the readings now have four threads of their own. The
permissions are changed through the handle they were checked on, so a
link put anywhere in the path since can't send the change to another
file. And the row is hidden by the folder being the trash, known at
once, rather than by the note read later, which showed it for a moment
and kept it for what's inside a trashed folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UGcPV5q4WWVxgL57jZ2Vm7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant