Skip to content

Commit

Permalink
Merge pull request #76 from eed3si9n/wip/permission
Browse files Browse the repository at this point in the history
POSIX permission operations
  • Loading branch information
dwijnand authored Sep 14, 2017
2 parents c0f265f + 71bd6ca commit 62004b2
Show file tree
Hide file tree
Showing 3 changed files with 310 additions and 1 deletion.
83 changes: 83 additions & 0 deletions io/src/main/scala/sbt/io/IO.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import java.io.{
import java.io.{ ObjectInputStream, ObjectStreamClass }
import java.net.{ URI, URISyntaxException, URL }
import java.nio.charset.Charset
import java.nio.file.FileSystems
import java.nio.file.attribute.PosixFilePermissions
import java.util.Properties
import java.util.jar.{ Attributes, JarEntry, JarOutputStream, Manifest }
import java.util.zip.{ CRC32, ZipEntry, ZipInputStream, ZipOutputStream }
Expand All @@ -27,6 +29,7 @@ import scala.collection.mutable.{ HashMap, HashSet }
import scala.reflect.{ Manifest => SManifest }
import scala.util.control.NonFatal
import scala.util.control.Exception._
import scala.collection.JavaConverters._
import Function.tupled

/** A collection of File, URL, and I/O utility methods.*/
Expand Down Expand Up @@ -1009,4 +1012,84 @@ object IO {
}
}

/** Returns `true` if the filesystem supports POSIX file attribute view. */
def isPosix: Boolean = hasPosixFileAttributeView

/** Returns `true` if the filesystem supports POSIX file attribute view. */
lazy val hasPosixFileAttributeView: Boolean = supportedFileAttributeViews.contains("posix")

/** Returns `true` if the filesystem supports file owner attribute view. */
lazy val hasFileOwnerAttributeView: Boolean = supportedFileAttributeViews.contains("owner")

/** Returns `true` if the filesystem supports DOS file attribute view. */
lazy val hasDosFileAttributeView: Boolean = supportedFileAttributeViews.contains("dos")

/** Returns `true` if the filesystem supports ACL file attribute view. */
lazy val hasAclFileAttributeView: Boolean = supportedFileAttributeViews.contains("acl")

/** Returns `true` if the filesystem supports basic file attribute view. */
lazy val hasBasicFileAttributeView: Boolean = supportedFileAttributeViews.contains("basic")

/** Returns `true` if the filesystem supports user-defined file attribute view. */
lazy val hasUserDefinedFileAttributeView: Boolean = supportedFileAttributeViews.contains("user")

private[this] lazy val supportedFileAttributeViews: Set[String] = {
FileSystems.getDefault.supportedFileAttributeViews.asScala.toSet
}

/**
* Updates permission of this file.
* This operation requires underlying filesystem to support `IO.isPosix`.
*
* @param file
* @param permissions Must be 9 character POSIX permission representation e.g. "rwxr-x---"
*/
def setPermissions(file: File, permissions: String): Unit = {
Path(file).setPermissions(PosixFilePermissions.fromString(permissions).asScala.toSet)
}

/**
* An alias for `setPermissions`. Updates permission of this file.
* This operation requires underlying filesystem to support `IO.isPosix`.
*
* @param permissions Must be 9 character POSIX permission representation e.g. "rwxr-x---"
* @param file
*/
def chmod(permissions: String, file: File): Unit = setPermissions(file, permissions)

/**
* Updates the file owner.
* This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`.
*
* @param file
* @param owner
*/
def setOwner(file: File, owner: String): Unit = Path(file).setOwner(owner)

/**
* An alias for `setOwner`. Updates the file owner.
* This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`.
*
* @param owner
* @param file
*/
def chown(owner: String, file: File): Unit = setOwner(file, owner)

/**
* Updates the group owner of the file.
* This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`.
*
* @param file
* @param group
*/
def setGroup(file: File, group: String): Unit = Path(file).setGroup(group)

/**
* An alias for setGroup. Updates the group owner of the file.
* This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`.
*
* @param group
* @param file
*/
def chgrp(group: String, file: File): Unit = setGroup(file, group)
}
201 changes: 200 additions & 1 deletion io/src/main/scala/sbt/io/Path.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import java.io.File
import java.net.URL
import scala.collection.mutable
import IO.wrapNull
import java.nio.file.attribute._
import java.nio.file.{ Path => NioPath, LinkOption, FileSystem, Files }
import scala.collection.JavaConverters._

final class RichFile(val asFile: File) extends AnyVal {
final class RichFile(val asFile: File) extends AnyVal with RichNioPath {
def /(component: String): File = if (component == ".") asFile else new File(asFile, component)

/** True if and only if the wrapped file exists.*/
Expand Down Expand Up @@ -64,6 +67,201 @@ final class RichFile(val asFile: File) extends AnyVal {
def hash: Array[Byte] = Hash(asFile)
def hashString: String = Hash.toHex(hash)
def hashStringHalf: String = Hash.halve(hashString)

override def asPath: NioPath = asFile.toPath

private[sbt] override def linkOptions: Vector[LinkOption] = Vector.empty

def withLinkOptions(linkOption: LinkOption*): LinkOptionPath =
new LinkOptionPath(asPath, linkOption.toVector)
}

final class LinkOptionPath(p: NioPath, lo: Vector[LinkOption]) extends RichNioPath {
override val asPath: NioPath = p
private[sbt] val linkOptions: Vector[LinkOption] = lo
}

sealed trait RichNioPath extends Any {
def asPath: NioPath

private[sbt] def linkOptions: Vector[LinkOption]

/**
* Returns this file's POSIX permissions.
* This operation requires underlying filesystem to support `IO.isPosix`.
*/
def permissions: Set[PosixFilePermission] =
Files.getPosixFilePermissions(asPath, linkOptions: _*).asScala.toSet

/**
* Returns this file's POSIX permissions.
* This operation requires underlying filesystem to support `IO.isPosix`.
*/
def permissionsAsString: String =
PosixFilePermissions.toString(permissions.asJava)

/**
* Updates permission of this file.
* This operation requires underlying filesystem to support `IO.isPosix`.
*
* @param permissions
*/
def setPermissions(permissions: Set[PosixFilePermission]): Unit = {
Files.setPosixFilePermissions(asPath, permissions.asJava)
()
}

/**
* Adds permission to this file.
* This operation requires underlying filesystem to support `IO.isPosix`.
*
* @param permission
*/
def addPermission(permission: PosixFilePermission): Unit =
setPermissions(permissions + permission)

/**
* Removes permission from this file.
* This operation requires underlying filesystem to support `IO.isPosix`.
*
* @param permission
*/
def removePermission(permission: PosixFilePermission): Unit =
setPermissions(permissions - permission)

/**
* Tests if this file has the given permission.
* This operation requires underlying filesystem to support `IO.isPosix`.
*
* @param permission
*/
def testPermission(permission: PosixFilePermission): Boolean =
permissions(permission)

/**
* Tests if this file has the owner+read permission.
* This operation requires underlying filesystem to support `IO.isPosix`.
*/
def isOwnerReadable: Boolean =
testPermission(PosixFilePermission.OWNER_READ)

/**
* Tests if this file has the owner+write permission.
* This operation requires underlying filesystem to support `IO.isPosix`.
*/
def isOwnerWritable: Boolean =
testPermission(PosixFilePermission.OWNER_WRITE)

/**
* Tests if this file has the owner+execute permission.
* This operation requires underlying filesystem to support `IO.isPosix`.
*/
def isOwnerExecutable: Boolean =
testPermission(PosixFilePermission.OWNER_EXECUTE)

/**
* Tests if this file has the group+read permission.
* This operation requires underlying filesystem to support `IO.isPosix`.
*/
def isGroupReadable: Boolean =
testPermission(PosixFilePermission.GROUP_READ)

/**
* Tests if this file has the group+write permission.
* This operation requires underlying filesystem to support `IO.isPosix`.
*/
def isGroupWritable: Boolean =
testPermission(PosixFilePermission.GROUP_WRITE)

/**
* Tests if this file has the group+execute permission.
* This operation requires underlying filesystem to support `IO.isPosix`.
*/
def isGroupExecutable: Boolean =
testPermission(PosixFilePermission.GROUP_EXECUTE)

/**
* Tests if this file has the others+read permission.
* This operation requires underlying filesystem to support `IO.isPosix`.
*/
def isOthersReadable: Boolean =
testPermission(PosixFilePermission.OTHERS_READ)

/**
* Tests if this file has the others+write permission.
* This operation requires underlying filesystem to support `IO.isPosix`.
*/
def isOthersWritable: Boolean =
testPermission(PosixFilePermission.OTHERS_WRITE)

/**
* Tests if this file has the others+execute permission.
* This operation requires underlying filesystem to support `IO.isPosix`.
*/
def isOthersExecutable: Boolean =
testPermission(PosixFilePermission.OTHERS_EXECUTE)

def attributes: BasicFileAttributes =
Files.readAttributes(asPath, classOf[BasicFileAttributes], linkOptions: _*)

def posixAttributes: PosixFileAttributes =
Files.readAttributes(asPath, classOf[PosixFileAttributes], linkOptions: _*)

def dosAttributes: DosFileAttributes =
Files.readAttributes(asPath, classOf[DosFileAttributes], linkOptions: _*)

def aclFileAttributeView: AclFileAttributeView =
Files.getFileAttributeView(asPath, classOf[AclFileAttributeView], linkOptions: _*)

/**
* Returns the owner of a file.
* This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`.
*/
def owner: UserPrincipal =
Files.getOwner(asPath, linkOptions: _*)

/**
* Returns the owner of a file.
* This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`.
*/
def ownerName: String = owner.getName

/**
* Returns the group owner of a file.
* This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`.
*/
def group: GroupPrincipal = posixAttributes.group()

/**
* Returns the group owner of a file.
* This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`.
*/
def groupName: String = group.getName

/**
* Updates the file owner.
* This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`.
*
* @param owner
*/
def setOwner(owner: String): Unit = {
val fileSystem: FileSystem = asPath.getFileSystem
Files.setOwner(asPath, fileSystem.getUserPrincipalLookupService.lookupPrincipalByName(owner))
()
}

/**
* Updates the group owner of the file.
* This operation requires underlying filesystem to support `IO.hasFileOwnerAttributeView`.
*
* @param group
*/
def setGroup(group: String): Unit = {
val fileSystem: FileSystem = asPath.getFileSystem
Files.setOwner(asPath,
fileSystem.getUserPrincipalLookupService.lookupPrincipalByGroupName(group))
()
}
}

object Path extends Mapper {
Expand All @@ -87,6 +285,7 @@ object Path extends Mapper {

def toURLs(files: Seq[File]): Array[URL] = files.map(_.toURI.toURL).toArray

private[sbt] val defaultLinkOptions: Vector[LinkOption] = Vector.empty
}

object PathFinder {
Expand Down
27 changes: 27 additions & 0 deletions io/src/test/scala/sbt/io/FileSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package sbt.io

import org.scalatest._
import sbt.io.syntax._
import java.nio.file.attribute.PosixFilePermission

class FileSpec extends FlatSpec with Matchers {
"files" should "set/unset permissions" in {
IO.withTemporaryDirectory { dir =>
val t1 = dir / "foo.txt"
IO.write(t1, "foo")
if (IO.isPosix) {
//an[UnsupportedOperationException] should be thrownBy t1.dosAttributes
t1.permissions(PosixFilePermission.OWNER_EXECUTE) shouldBe false

t1.addPermission(PosixFilePermission.OWNER_EXECUTE)
t1.addPermission(PosixFilePermission.GROUP_WRITE)
t1.testPermission(PosixFilePermission.OWNER_EXECUTE) shouldBe true
t1.permissionsAsString shouldBe "rwxrw-r--"

t1.removePermission(PosixFilePermission.OWNER_EXECUTE)
t1.isOwnerExecutable shouldBe false
t1.permissionsAsString shouldBe "rw-rw-r--"
} else ()
}
}
}

0 comments on commit 62004b2

Please sign in to comment.