mirror of
https://codeberg.org/icewind/SMB.git
synced 2026-06-03 17:24:07 +02:00
Add option to set file modes
This commit is contained in:
parent
3a6d77bc78
commit
3888ae6b43
9 changed files with 235 additions and 89 deletions
|
|
@ -29,6 +29,10 @@ before_install:
|
||||||
path = /home/test
|
path = /home/test
|
||||||
guest ok = yes
|
guest ok = yes
|
||||||
writeable = yes
|
writeable = yes
|
||||||
|
map archive = yes
|
||||||
|
map system = yes
|
||||||
|
map hidden = yes
|
||||||
|
create mask = 0777
|
||||||
inherit permissions = yes" | sudo tee -a /etc/samba/smb.conf
|
inherit permissions = yes" | sudo tee -a /etc/samba/smb.conf
|
||||||
- sudo service smbd restart
|
- sudo service smbd restart
|
||||||
- testparm -s
|
- testparm -s
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ class FileInfo implements IFileInfo {
|
||||||
const MODE_VOLUME_ID = 0x08;
|
const MODE_VOLUME_ID = 0x08;
|
||||||
const MODE_DIRECTORY = 0x10;
|
const MODE_DIRECTORY = 0x10;
|
||||||
const MODE_ARCHIVE = 0x20;
|
const MODE_ARCHIVE = 0x20;
|
||||||
|
const MODE_NORMAL = 0x80;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
|
|
@ -108,4 +109,18 @@ class FileInfo implements IFileInfo {
|
||||||
public function isHidden() {
|
public function isHidden() {
|
||||||
return (bool)($this->mode & self::MODE_HIDDEN);
|
return (bool)($this->mode & self::MODE_HIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isSystem() {
|
||||||
|
return (bool)($this->mode & self::MODE_SYSTEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isArchived() {
|
||||||
|
return (bool)($this->mode & self::MODE_ARCHIVE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,4 +42,14 @@ interface IFileInfo {
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isHidden();
|
public function isHidden();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isSystem();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isArchived();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,16 +87,6 @@ interface IShare {
|
||||||
/**
|
/**
|
||||||
* List the content of a remote folder
|
* List the content of a remote folder
|
||||||
*
|
*
|
||||||
* Returns a nested array in the format of
|
|
||||||
* [
|
|
||||||
* $name => [
|
|
||||||
* 'size' => $size,
|
|
||||||
* 'type' => $type,
|
|
||||||
* 'time' => $mtime
|
|
||||||
* ],
|
|
||||||
* ...
|
|
||||||
* ]
|
|
||||||
*
|
|
||||||
* @param $path
|
* @param $path
|
||||||
* @return \Icewind\SMB\IFileInfo[]
|
* @return \Icewind\SMB\IFileInfo[]
|
||||||
*
|
*
|
||||||
|
|
@ -105,6 +95,14 @@ interface IShare {
|
||||||
*/
|
*/
|
||||||
public function dir($path);
|
public function dir($path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
* @return \Icewind\SMB\IFileInfo
|
||||||
|
*
|
||||||
|
* @throws \Icewind\SMB\NotFoundException
|
||||||
|
*/
|
||||||
|
public function stat($path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a folder on the share
|
* Create a folder on the share
|
||||||
*
|
*
|
||||||
|
|
@ -126,4 +124,11 @@ interface IShare {
|
||||||
* @throws \Icewind\SMB\InvalidTypeException
|
* @throws \Icewind\SMB\InvalidTypeException
|
||||||
*/
|
*/
|
||||||
public function rmdir($path);
|
public function rmdir($path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
* @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function setMode($path, $mode);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ class NativeFileInfo implements IFileInfo {
|
||||||
*/
|
*/
|
||||||
protected function stat() {
|
protected function stat() {
|
||||||
if (!$this->statCache) {
|
if (!$this->statCache) {
|
||||||
$this->statCache = $this->share->stat($this->getPath());
|
$this->statCache = $this->share->getStat($this->getPath());
|
||||||
}
|
}
|
||||||
return $this->statCache;
|
return $this->statCache;
|
||||||
}
|
}
|
||||||
|
|
@ -119,4 +119,20 @@ class NativeFileInfo implements IFileInfo {
|
||||||
$mode = $this->getMode();
|
$mode = $this->getMode();
|
||||||
return (bool)($mode & FileInfo::MODE_HIDDEN);
|
return (bool)($mode & FileInfo::MODE_HIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isSystem() {
|
||||||
|
$mode = $this->getMode();
|
||||||
|
return (bool)($mode & FileInfo::MODE_SYSTEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isArchived() {
|
||||||
|
$mode = $this->getMode();
|
||||||
|
return (bool)($mode & FileInfo::MODE_ARCHIVE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,15 @@ class NativeShare implements IShare {
|
||||||
return $files;
|
return $files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
* @return \Icewind\SMB\IFileInfo[]
|
||||||
|
*/
|
||||||
public function stat($path) {
|
public function stat($path) {
|
||||||
|
return new NativeFileInfo($this, $path, basename($path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStat($path) {
|
||||||
$this->connect();
|
$this->connect();
|
||||||
return $this->state->stat($this->buildUrl($path));
|
return $this->state->stat($this->buildUrl($path));
|
||||||
}
|
}
|
||||||
|
|
@ -250,6 +258,33 @@ class NativeShare implements IShare {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get extended attributes for the path
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* @param string $attribute attribute to get the info
|
||||||
|
* @param mixed $value
|
||||||
|
* @return string the attribute value
|
||||||
|
*/
|
||||||
|
public function setAttribute($path, $attribute, $value) {
|
||||||
|
$this->connect();
|
||||||
|
|
||||||
|
if ($attribute === 'system.dos_attr.mode' and is_int($value)) {
|
||||||
|
$value = '0x' . dechex($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->state->setxattr($this->buildUrl($path), $attribute, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
* @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function setMode($path, $mode) {
|
||||||
|
return $this->setAttribute($path, 'system.dos_attr.mode', $mode);
|
||||||
|
}
|
||||||
|
|
||||||
public function __destruct() {
|
public function __destruct() {
|
||||||
unset($this->state);
|
unset($this->state);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,13 +32,7 @@ class NativeState {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->handlerSet = true;
|
$this->handlerSet = true;
|
||||||
$state = $this;
|
set_error_handler(array($this, 'handleError'));
|
||||||
set_error_handler(function ($errorNumber, $errorString) use ($state) {
|
|
||||||
/**
|
|
||||||
* @var \Icewind\SMB\NativeState $state
|
|
||||||
*/
|
|
||||||
$state->handleError($errorString);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function restoreErrorHandler() {
|
protected function restoreErrorHandler() {
|
||||||
|
|
@ -49,7 +43,7 @@ class NativeState {
|
||||||
restore_error_handler();
|
restore_error_handler();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function handleError($errorString = '') {
|
protected function handleError($errorNumber = 0, $errorString = '') {
|
||||||
$error = smbclient_state_errno($this->state);
|
$error = smbclient_state_errno($this->state);
|
||||||
switch ($error) {
|
switch ($error) {
|
||||||
// see error.h
|
// see error.h
|
||||||
|
|
@ -343,60 +337,6 @@ class NativeState {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $uri
|
|
||||||
* @param string $mode
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function chmod($uri, $mode) {
|
|
||||||
$this->setErrorHandler();
|
|
||||||
$result = smbclient_chmod($this->state, $uri, $mode);
|
|
||||||
$this->restoreErrorHandler();
|
|
||||||
|
|
||||||
if ($result === false) {
|
|
||||||
$this->handleError();
|
|
||||||
}
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $uri
|
|
||||||
* @param int $mtime
|
|
||||||
* @param int $atime
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function utimes($uri, $mtime = null, $atime = null) {
|
|
||||||
if ($mtime = null) {
|
|
||||||
$mtime = time();
|
|
||||||
}
|
|
||||||
if ($atime = null) {
|
|
||||||
$atime = $mtime;
|
|
||||||
}
|
|
||||||
$this->setErrorHandler();
|
|
||||||
$result = smbclient_utimes($this->state, $uri, $mtime, $atime);
|
|
||||||
$this->restoreErrorHandler();
|
|
||||||
|
|
||||||
if ($result === false) {
|
|
||||||
$this->handleError();
|
|
||||||
}
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $uri
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function listxattr($uri) {
|
|
||||||
$this->setErrorHandler();
|
|
||||||
$result = smbclient_listxattr($this->state, $uri);
|
|
||||||
$this->restoreErrorHandler();
|
|
||||||
|
|
||||||
if ($result === false) {
|
|
||||||
$this->handleError();
|
|
||||||
}
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $uri
|
* @param string $uri
|
||||||
* @param string $key
|
* @param string $key
|
||||||
|
|
@ -431,22 +371,6 @@ class NativeState {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $uri
|
|
||||||
* @param string $key
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function removexattr($uri, $key) {
|
|
||||||
$this->setErrorHandler();
|
|
||||||
$result = smbclient_removexattr($this->state, $uri, $key);
|
|
||||||
$this->restoreErrorHandler();
|
|
||||||
|
|
||||||
if ($result === false) {
|
|
||||||
$this->handleError();
|
|
||||||
}
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __destruct() {
|
public function __destruct() {
|
||||||
if ($this->connected) {
|
if ($this->connected) {
|
||||||
smbclient_state_free($this->state);
|
smbclient_state_free($this->state);
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,34 @@ class Share implements IShare {
|
||||||
return $content;
|
return $content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
* @return \Icewind\SMB\IFileInfo[]
|
||||||
|
*/
|
||||||
|
public function stat($path) {
|
||||||
|
$escapedPath = $this->escapePath($path);
|
||||||
|
$output = $this->execute('allinfo ' . $escapedPath);
|
||||||
|
if (count($output) < 3) {
|
||||||
|
$this->parseOutput($output);
|
||||||
|
}
|
||||||
|
$mtime = 0;
|
||||||
|
$mode = 0;
|
||||||
|
$size = 0;
|
||||||
|
foreach ($output as $line) {
|
||||||
|
list($name, $value) = explode(':', $line, 2);
|
||||||
|
$value = trim($value);
|
||||||
|
if ($name === 'write_time') {
|
||||||
|
$mtime = $value;
|
||||||
|
} else if ($name === 'attributes') {
|
||||||
|
$mode = $this->parseMode($value);
|
||||||
|
} else if ($name === 'stream') {
|
||||||
|
list(, $size,) = explode(' ', $value);
|
||||||
|
$size = intval($size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new FileInfo($path, basename($path), $size, $mtime, $mode);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a folder on the share
|
* Create a folder on the share
|
||||||
*
|
*
|
||||||
|
|
@ -271,6 +299,37 @@ class Share implements IShare {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
* @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function setMode($path, $mode) {
|
||||||
|
$modeString = '';
|
||||||
|
if ($mode & FileInfo::MODE_READONLY) {
|
||||||
|
$modeString .= 'r';
|
||||||
|
}
|
||||||
|
if ($mode & FileInfo::MODE_HIDDEN) {
|
||||||
|
$modeString .= 'h';
|
||||||
|
}
|
||||||
|
if ($mode & FileInfo::MODE_ARCHIVE) {
|
||||||
|
$modeString .= 'a';
|
||||||
|
}
|
||||||
|
if ($mode & FileInfo::MODE_SYSTEM) {
|
||||||
|
$modeString .= 's';
|
||||||
|
}
|
||||||
|
$path = $this->escapePath($path);
|
||||||
|
|
||||||
|
// first reset the mode to normal
|
||||||
|
$cmd = 'setmode ' . $path . ' -rsha';
|
||||||
|
$output = $this->execute($cmd);
|
||||||
|
|
||||||
|
// then set the modes we want
|
||||||
|
$cmd = 'setmode ' . $path . ' ' . $modeString;
|
||||||
|
$output = $this->execute($cmd);
|
||||||
|
return $this->parseOutput($output);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $mode
|
* @param string $mode
|
||||||
* @return string
|
* @return string
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@
|
||||||
|
|
||||||
namespace Icewind\SMB\Test;
|
namespace Icewind\SMB\Test;
|
||||||
|
|
||||||
|
use Icewind\SMB\FileInfo;
|
||||||
|
use Icewind\SMB\IFileInfo;
|
||||||
|
use Icewind\SMB\IShare;
|
||||||
|
|
||||||
abstract class AbstractShare extends \PHPUnit_Framework_TestCase {
|
abstract class AbstractShare extends \PHPUnit_Framework_TestCase {
|
||||||
/**
|
/**
|
||||||
* @var \Icewind\SMB\Server $server
|
* @var \Icewind\SMB\Server $server
|
||||||
|
|
@ -368,4 +372,78 @@ abstract class AbstractShare extends \PHPUnit_Framework_TestCase {
|
||||||
$this->assertFalse($fileEntry->isReadOnly());
|
$this->assertFalse($fileEntry->isReadOnly());
|
||||||
$this->assertFalse($fileEntry->isReadOnly());
|
$this->assertFalse($fileEntry->isReadOnly());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider nameProvider
|
||||||
|
*/
|
||||||
|
public function testStat($name) {
|
||||||
|
$txtFile = $this->getTextFile();
|
||||||
|
$size = filesize($txtFile);
|
||||||
|
|
||||||
|
$this->share->put($txtFile, $this->root . '/' . $name);
|
||||||
|
unlink($txtFile);
|
||||||
|
|
||||||
|
$info = $this->share->stat($this->root . '/' . $name);
|
||||||
|
$this->assertEquals($size, $info->getSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* note setting archive and system bit is not supported
|
||||||
|
*
|
||||||
|
* @dataProvider nameProvider
|
||||||
|
*/
|
||||||
|
public function testSetMode($name) {
|
||||||
|
$txtFile = $this->getTextFile();
|
||||||
|
|
||||||
|
$this->share->put($txtFile, $this->root . '/' . $name);
|
||||||
|
|
||||||
|
$this->share->setMode($this->root . '/' . $name, FileInfo::MODE_NORMAL);
|
||||||
|
$info = $this->share->stat($this->root . '/' . $name);
|
||||||
|
$this->assertFalse($info->isReadOnly());
|
||||||
|
$this->assertFalse($info->isArchived());
|
||||||
|
$this->assertFalse($info->isSystem());
|
||||||
|
$this->assertFalse($info->isHidden());
|
||||||
|
|
||||||
|
$this->share->setMode($this->root . '/' . $name, FileInfo::MODE_READONLY);
|
||||||
|
$info = $this->share->stat($this->root . '/' . $name);
|
||||||
|
$this->assertTrue($info->isReadOnly());
|
||||||
|
$this->assertFalse($info->isArchived());
|
||||||
|
$this->assertFalse($info->isSystem());
|
||||||
|
$this->assertFalse($info->isHidden());
|
||||||
|
|
||||||
|
$this->share->setMode($this->root . '/' . $name, FileInfo::MODE_ARCHIVE);
|
||||||
|
$info = $this->share->stat($this->root . '/' . $name);
|
||||||
|
$this->assertFalse($info->isReadOnly());
|
||||||
|
$this->assertTrue($info->isArchived());
|
||||||
|
$this->assertFalse($info->isSystem());
|
||||||
|
$this->assertFalse($info->isHidden());
|
||||||
|
|
||||||
|
$this->share->setMode($this->root . '/' . $name, FileInfo::MODE_READONLY | FileInfo::MODE_ARCHIVE);
|
||||||
|
$info = $this->share->stat($this->root . '/' . $name);
|
||||||
|
$this->assertTrue($info->isReadOnly());
|
||||||
|
$this->assertTrue($info->isArchived());
|
||||||
|
$this->assertFalse($info->isSystem());
|
||||||
|
$this->assertFalse($info->isHidden());
|
||||||
|
|
||||||
|
$this->share->setMode($this->root . '/' . $name, FileInfo::MODE_HIDDEN);
|
||||||
|
$info = $this->share->stat($this->root . '/' . $name);
|
||||||
|
$this->assertFalse($info->isReadOnly());
|
||||||
|
$this->assertFalse($info->isArchived());
|
||||||
|
$this->assertFalse($info->isSystem());
|
||||||
|
$this->assertTrue($info->isHidden());
|
||||||
|
|
||||||
|
$this->share->setMode($this->root . '/' . $name, FileInfo::MODE_SYSTEM);
|
||||||
|
$info = $this->share->stat($this->root . '/' . $name);
|
||||||
|
$this->assertFalse($info->isReadOnly());
|
||||||
|
$this->assertFalse($info->isArchived());
|
||||||
|
$this->assertTrue($info->isSystem());
|
||||||
|
$this->assertFalse($info->isHidden());
|
||||||
|
|
||||||
|
$this->share->setMode($this->root . '/' . $name, FileInfo::MODE_NORMAL);
|
||||||
|
$info = $this->share->stat($this->root . '/' . $name);
|
||||||
|
$this->assertFalse($info->isReadOnly());
|
||||||
|
$this->assertFalse($info->isArchived());
|
||||||
|
$this->assertFalse($info->isSystem());
|
||||||
|
$this->assertFalse($info->isHidden());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue