Skip to content

feat: use a predictable working dir#5

Open
upils wants to merge 5 commits intorecutfrom
feat/chisel-dir
Open

feat: use a predictable working dir#5
upils wants to merge 5 commits intorecutfrom
feat/chisel-dir

Conversation

@upils
Copy link
Copy Markdown
Owner

@upils upils commented Mar 25, 2026

  • Have you signed the CLA?

This PR implements two conceptual changes:

  • When running the slicer, Chisel always creates a .chisel working directory
    at the root of the target directory. Existing entry at the same path is remove
    to ensure the creation of this directory. This directory is removed at the end
    only if it empty. This accomodates future use cases that might leave content
    in it on purpose between executions.
  • When recutting an existing rootfs, a rootfs directory is created under the
    .chisel working dir to cut the new content in an isolated place. This
    directory is removed at the end of the process.

@upils upils marked this pull request as ready for review March 25, 2026 10:44
Copy link
Copy Markdown

@letFunny letFunny left a comment

Choose a reason for hiding this comment

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

Thanks Paul, this looks very good, only a few minor comments

const manifestMode fs.FileMode = 0644
const (
manifestMode fs.FileMode = 0o644
rootWorkdir = ".chisel"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think the naming is a bit off here. This is not necessarily a working directory, right now we have only one usage but from the conversation the other day it could evolve into doing more things. It also makes it harder later to differentiate between .chisel and .chisel/<workdir_for_recut>.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

I tried different names and settled on this one because I thought it was in fact a "working directory". As you can see in the rest of the code, the subdirectory holding the new rootfs is not named "workdir" anymore, to avoid confusion.

metadataDir or stateDir are other potential candidates but I am not really convinced they better encompass what could be stored in this directory. Do you have other ideas?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I have a slight preference for stateDir or something similar yes

func mkWorkdir(workdir string, mode os.FileMode) (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("cannot create working directory: %w", err)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Does this need to be %w?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

As this is a "helper" function, I did not want to make too many assumptions on how the returned error was used. In the current implementation, you are right that this is not strictly needed because returned errors are immediately returned as is. However, I wanted to keep consistency with other places in this package where most os errors are wrapped.

This is an internal package, so we are the only ones that could depend in the behavior of this API. If/when we start unwrapping errors returned by Run I would prefer having consistency and avoid being surprised that some errors cannot be unwrapped like others.

Do you agree?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Okay this makes sense, so that the caller could check os.IsExists and the like

This is an internal package, so we are the only ones that could depend in the behavior of this API

In general, the design should not depend too much on whether it is internal or external as the same principles apply. In this case this is dealing with os so it is okay to use %w, in other functions, even if internal, it is not if we don't want to rely on the errors.

// TODO: Use a predetermined name and reserve it. The release validation
// would ensure it cannot be used in chisel-releases.
tmpWorkDir, err := os.MkdirTemp(targetDir, "chisel-workdir-*")
rootfsPath := filepath.Join(rootWorkdirPath, "rootfs")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The naming is a bit weird now, tmpWorkDir indicated this was temporary and a working directory suggesting we will do something to it later to install the files definitely but now rootfsPath loses both connotations.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

See my other comment regarding the "working dir" connotation.

However I was on the fence about the "temporary" aspect of it. I am adding at least this aspect back to the name.

// The root working directory must only be removed if empty.
// This call will do so or silently fail.
os.Remove(rootWorkdirPath)
}()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You can also check for error here and return it from the outer function. This might be worth it if we want to differentiate empty dir vs no permissions, the latter is an error.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

I did that at first, but then I saw that in the whole code base we never deal with returning the error of the outer function and the error of the deferred function. In the vast majority of cases we even ignore the error of whatever is called in the defer. Returning both errors should be done carefully to not hide the error of the outer function and to make sure the user can make sense of the "complete" error displayed.

I do not like that this is creating a blind spot though, so I would like to discuss it with G.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Just to be clear I meant: if the error is nil but os.Remove fails then we can return the latter. But let's discuss it with G.

Copy link
Copy Markdown

@letFunny letFunny left a comment

Choose a reason for hiding this comment

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

Thanks Paul, this is almost ready, just a couple of questions.

},
error: `cannot create working directory: existing entry at .*/\.chisel is not a directory`,
}, {
summary: "Run keeps non-empty .chisel directory",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This might also make it somewhat more flexible for future compatibility. It is a little bit hard to know at this point.

err := os.Chmod(opts.TargetDir, 0o555)
c.Assert(err, IsNil)
},
error: `cannot create working directory: mkdir .*/\.chisel: permission denied`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is a good error message, thanks.

},
filesystem: map[string]string{
"/.chisel/": "dir 0755",
"/.chisel/keep": "file 0644 6ca7ea2f",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

We need to be careful if we ever implement restarts from crashes to use the existence of .chisel to tell us that we can go ahead and resume from a restart. That will break when future versions do not remove this directory.

I am so-so on keeping it or removing it. I see in one of the comments that you state a requirements to not remove it when not empty, can you explain why?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

I am so-so on keeping it or removing it. I see in one of the comments that you state a requirements to not remove it when not empty, can you explain why?

That was the agreement with Gustavo. This directory might be used in the future by chisel to store other things that we may want to keep between executions, so already have this logic of keeping it if not empty makes sense.

So given this is a general purpose directory, we should not use its existence as an indicator it is fine to resume operation after a crash. If/when we implement crash recovery, we should probably rely on something inside this directory.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Great! That will work

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.

2 participants