-
-
Notifications
You must be signed in to change notification settings - Fork 73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Instrumented path based operations using hooks defined in Checker
#325
Conversation
test("group") { | ||
test - prep { wd => | ||
test("checker") - prepChecker { wd => | ||
if (Unix()) { | ||
// Only works as root :( | ||
if (false) { | ||
intercept[WriteDenied] { | ||
os.owner.set(rd / "File.txt", "nobody") | ||
} | ||
|
||
val originalOwner = os.owner(wd / "File.txt") | ||
|
||
os.owner.set(wd / "File.txt", "nobody") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The original "group" test was testing owner
operations.
def copyOne(p: Path): file.Path = { | ||
def copyOne(p: Path): Unit = { | ||
val target = to / p.relativeTo(from) | ||
if (mergeFolders && isDir(p, followLinks) && isDir(target, followLinks)) { | ||
// nothing to do | ||
target.wrapped | ||
} else { | ||
Files.copy(p.wrapped, target.wrapped, opts1 ++ opts2 ++ opts3: _*) | ||
} | ||
} | ||
|
||
copyOne(from) | ||
if (stat(from, followLinks = followLinks).isDir) walk(from).map(copyOne) | ||
if (stat(from, followLinks = followLinks).isDir) for (p <- walk(from)) copyOne(p) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A minor optimization that eliminates the creation of an unused collection.
This isn't required for the original ticket.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice optimization! Would walk(from).foreach(copyOne)
be more idiomatic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was trying to avoid this.
// check read preemptively in case "dest" is created | ||
for (source <- sources) checker.value.onRead(source.src) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, the implementation should be refactored to defer file creation until all data is collected.
@lihaoyi Please review. |
Thanks @ajaychandran, I think this looks good. Could you add Scaladoc to the |
Let's instrument these as well. For the purposes of Mill's (and probably others), we also want to block code from making decisions based on filesystem metadata outside their designated sandboxes, not just file contents |
@lihaoyi Any thoughts on this? |
I had removed Scaladoc from the PR description. Do we need more than this? /**
* Defines hooks for path based operations.
*
* This, in conjunction with [[checker]], can be used to implement custom checks like
* - restricting operations to some path(s)
* - logging operations
*/
trait Checker {
/** A hook for a read operation on `path`. */
def onRead(path: ReadablePath): Unit
/** A hook for a write operation on `path`. */
def onWrite(path: Path): Unit
}``` |
@ajaychandran I'm not sure what you mean by special check handling. Could you elaborate?
I think for the purposes of this ticket, let's stick with a simple |
@lihaoyi
Adding a hook for each core operation lets |
@@ -38,6 +42,7 @@ object makeDir extends Function1[Path, Unit] { | |||
object all extends Function1[Path, Unit] { | |||
def apply(path: Path): Unit = apply(path, null, true) | |||
def apply(path: Path, perms: PermSet = null, acceptLinkedDirectory: Boolean = true): Unit = { | |||
checker.value.onWrite(path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The original implementation creates any necessary enclosing directories via java.nio.file.Files#createDirectories
.
Here the checker is only checking for write access on the full path.
Don't we need to apply it to the enclosing directories as well?
I see this would entail providing a shim for java.nio.file.Files#createDirectories
.
Good point on the symlinks and enclosing folders. In the end this is not going to be an airtight sandbox, so it's a judgement call exactly where to draw the line.
I'm a bit reluctant to try and match the entire OS-Lib API in the For the purposes of this pull request, could you add an |
@ajaychandran not everything needs to be experimental, only the public APIs you added: |
@lihaoyi This is ready for another review. |
Updated |
@@ -59,6 +59,8 @@ package object os { | |||
|
|||
val sub: SubPath = SubPath.sub | |||
|
|||
val checker: DynamicVariable[Checker] = new DynamicVariable[Checker](Checker.Nop) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about adding these for ergonomics?
def withChecker[T](c: Checker)(op: => T): T =
checker.withValue(c)(op)
def checkReadAccess(path: os.Path): Unit = checker.value.onRead(path)
def checkWriteAccess(path: os.Path): Unit = checker.value.onWrite(path)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for now we can punt on these, since it's a pretty advanced feature so being easy to use isn't as important
@ajaychandran code changes look good. Last request here is to move all the checker tests into one separate test suite, rather than having them mixed in with all the non-checker tests. That will make it easier to skim them and audit them as necessary. After that i will merge this and cut a milestone release for experimentation in Mill |
@lihaoyi Checker tests have been moved. Some compound operations like |
Another option is to delegate some sort of caching solution to the implementor of Checker. |
@ajaychandran pushed a tag for 0.11.4-M1 https://github.com/com-lihaoyi/os-lib/actions/runs/11537940922 hopefully it goes out soon and you can try it out in Mill. If you're going to work on the downstream tasks, we can hold off on payment for now to save on transfer fees. If not I can transfer the first portion of the bounty using the details we used before |
@lihaoyi Thanks. Lets hold off payment. |
Instrumented path based operations using hooks defined in
Checker
.Exceptions
The following operations were not instrumented:
followLink
,readLink
list
,walk
exists
,isLink
,isFile
,isDir
watch
Future work
move
andsymlink
.ReadablePath
represent escape hatches. These cannot be "plugged" without breaking binary compatibility.This resolves part 1 of mill #3746.