Skip to content
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

[#57] Added support for hard links. #113

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,40 @@
@JsonIgnoreProperties(ignoreUnknown = true)
public class IRODSClientConfig
{
@JsonProperty("host") private String host_;
@JsonProperty("port") private int port_;
@JsonProperty("zone") private String zone_;
@JsonProperty("default_resource") private String defResc_;
@JsonProperty("ssl_negotiation_policy") private String sslNegPolicy_;
@JsonProperty("connection_timeout_in_seconds") private int connTimeout_;
@JsonProperty("proxy_admin_account") private IRODSProxyAdminAccountConfig proxyAdminAcctConfig_;

// @formatter:off
@JsonProperty("host") private String host_;
@JsonProperty("port") private int port_;
@JsonProperty("zone") private String zone_;
@JsonProperty("default_resource") private String defResc_;
@JsonProperty("ssl_negotiation_policy") private String sslNegPolicy_;
@JsonProperty("connection_timeout_in_seconds") private int connTimeout_;
@JsonProperty("hard_links_rule_engine_plugin_instance_name") private String hardLinksPluginInstName_;
@JsonProperty("proxy_admin_account") private IRODSProxyAdminAccountConfig proxyAdminAcctConfig_;

@JsonCreator
IRODSClientConfig(@JsonProperty("host") String _host,
@JsonProperty("port") Integer _port,
@JsonProperty("zone") String _zone,
@JsonProperty("default_resource") String _defaultResource,
@JsonProperty("ssl_negotiation_policy") String _sslNegotiationPolicy,
@JsonProperty("connection_timeout_in_seconds") Integer _connTimeout,
@JsonProperty("proxy_admin_account") IRODSProxyAdminAccountConfig _proxyAdminAcctConfig)
IRODSClientConfig(@JsonProperty("host") String _host,
@JsonProperty("port") Integer _port,
@JsonProperty("zone") String _zone,
@JsonProperty("default_resource") String _defaultResource,
@JsonProperty("ssl_negotiation_policy") String _sslNegotiationPolicy,
@JsonProperty("connection_timeout_in_seconds") Integer _connTimeout,
@JsonProperty("hard_links_rule_engine_plugin_instance_name") String _hardLinksPluginInstName,
@JsonProperty("proxy_admin_account") IRODSProxyAdminAccountConfig _proxyAdminAcctConfig)
{
ConfigUtils.throwIfNull(_host, "host");
ConfigUtils.throwIfNull(_port, "port");
ConfigUtils.throwIfNull(_zone, "zone");
ConfigUtils.throwIfNull(_defaultResource, "default_resource");
ConfigUtils.throwIfNull(_sslNegotiationPolicy, "ssl_negotiation_policy");
ConfigUtils.throwIfNull(_hardLinksPluginInstName, "hard_links_rule_engine_plugin_instance_name");
ConfigUtils.throwIfNull(_proxyAdminAcctConfig, "proxy_admin_account");

host_ = _host;
port_ = _port;
zone_ = _zone;
defResc_ = _defaultResource;
sslNegPolicy_ = _sslNegotiationPolicy;
hardLinksPluginInstName_ = _hardLinksPluginInstName;
proxyAdminAcctConfig_ = _proxyAdminAcctConfig;

setConnectionTimeout(_connTimeout);
Expand Down Expand Up @@ -80,6 +84,12 @@ public int getConnectionTimeout()
return connTimeout_;
}

@JsonIgnore
public String getHardLinksRuleEnginePluginInstanceName()
{
return hardLinksPluginInstName_;
}

@JsonIgnore
public IRODSProxyAdminAccountConfig getIRODSProxyAdminAcctConfig()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
import java.nio.file.Paths;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -75,6 +77,8 @@
import org.irods.jargon.core.pub.DataObjectAO;
import org.irods.jargon.core.pub.IRODSAccessObjectFactory;
import org.irods.jargon.core.pub.IRODSFileSystemAO;
import org.irods.jargon.core.pub.IRODSGenQueryExecutor;
import org.irods.jargon.core.pub.RuleProcessingAO;
import org.irods.jargon.core.pub.UserAO;
import org.irods.jargon.core.pub.UserGroupAO;
import org.irods.jargon.core.pub.domain.ObjStat;
Expand All @@ -85,11 +89,20 @@
import org.irods.jargon.core.pub.io.IRODSFile;
import org.irods.jargon.core.pub.io.IRODSFileFactory;
import org.irods.jargon.core.pub.io.IRODSRandomAccessFile;
import org.irods.jargon.core.query.AbstractIRODSGenQuery.RowCountOptions;
import org.irods.jargon.core.query.CollectionAndDataObjectListingEntry;
import org.irods.jargon.core.query.CollectionAndDataObjectListingEntry.ObjectType;
import org.irods.jargon.core.query.IRODSGenQuery;
import org.irods.jargon.core.query.IRODSQueryResultRow;
import org.irods.jargon.core.query.IRODSQueryResultSet;
import org.irods.jargon.core.query.JargonQueryException;
import org.irods.jargon.core.rule.IRODSRuleExecResult;
import org.irods.jargon.core.rule.IrodsRuleInvocationTypeEnum;
import org.irods.jargon.core.rule.RuleInvocationConfiguration;
import org.irods.nfsrods.config.IRODSClientConfig;
import org.irods.nfsrods.config.IRODSProxyAdminAccountConfig;
import org.irods.nfsrods.config.ServerConfig;
import org.irods.nfsrods.utils.JSONUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -103,6 +116,7 @@ public class IRODSVirtualFileSystem implements VirtualFileSystem, AclCheckable
private static final long FIXED_TIMESTAMP = System.currentTimeMillis();
private static final FsStat FILE_SYSTEM_STAT_INFO = new FsStat(0, 0, 0, 0);

private final IRODSClientConfig irodsSvrConfig_;
private final IRODSAccessObjectFactory factory_;
private final IRODSIdMapper idMapper_;
private final InodeToPathMapper inodeToPathMapper_;
Expand Down Expand Up @@ -132,28 +146,27 @@ public IRODSVirtualFileSystem(ServerConfig _config,
CacheManager _cacheManager)
throws DataNotFoundException, JargonException
{
irodsSvrConfig_ = _config.getIRODSClientConfig();
factory_ = _factory;
idMapper_ = _idMapper;
inodeToPathMapper_ = new InodeToPathMapper(_config, _factory);

IRODSClientConfig rodsSvrConfig = _config.getIRODSClientConfig();

ROOT_COLLECTION = Paths.get("/");
ZONE_COLLECTION = ROOT_COLLECTION.resolve(rodsSvrConfig.getZone());
ZONE_COLLECTION = ROOT_COLLECTION.resolve(irodsSvrConfig_.getZone());
HOME_COLLECTION = ZONE_COLLECTION.resolve("home");
PUBLIC_COLLECTION = ZONE_COLLECTION.resolve("public");
TRASH_COLLECTION = ZONE_COLLECTION.resolve("trash");

IRODSProxyAdminAccountConfig proxyConfig = rodsSvrConfig.getIRODSProxyAdminAcctConfig();
Path proxyUserHomeCollection = Paths.get("/", rodsSvrConfig.getZone(), "home", proxyConfig.getUsername());
IRODSProxyAdminAccountConfig proxyConfig = irodsSvrConfig_.getIRODSProxyAdminAcctConfig();
Path proxyUserHomeCollection = Paths.get("/", irodsSvrConfig_.getZone(), "home", proxyConfig.getUsername());
// @formatter:off
adminAcct_ = IRODSAccount.instance(rodsSvrConfig.getHost(),
rodsSvrConfig.getPort(),
adminAcct_ = IRODSAccount.instance(irodsSvrConfig_.getHost(),
irodsSvrConfig_.getPort(),
proxyConfig.getUsername(),
proxyConfig.getPassword(),
proxyUserHomeCollection.toString(),
rodsSvrConfig.getZone(),
rodsSvrConfig.getDefaultResource());
irodsSvrConfig_.getZone(),
irodsSvrConfig_.getDefaultResource());
// @formatter:on

readWriteAclWhitelist_ = new ReadWriteAclWhitelist(factory_, adminAcct_);
Expand Down Expand Up @@ -644,7 +657,7 @@ public Access checkAcl(Subject _subject, Inode _inode, int _accessMask) throws C
}

String path = getPath(toInodeNumber(_inode)).toString();

// Key (String) => <user_id>#<access_mask>#<path>
// Value (Access) => ALLOW/DENY
// Cached stat information must be scoped to the user due to the permissions
Expand Down Expand Up @@ -832,7 +845,64 @@ public boolean hasIOLayout(Inode _inode) throws IOException
@Override
public Inode link(Inode _parent, Inode _existing, String _target, Subject _subject) throws IOException
{
throw new UnsupportedOperationException("Not supported");
log_.debug("vfs::link");

Path linkParentPath = getPath(toInodeNumber(_parent));
Path linkTargetPath = getPath(toInodeNumber(_existing));
Path linkNamePath = linkParentPath.resolve(_target);

log_.debug("list - _parent = {}", linkParentPath); // The parent directory of _target.
log_.debug("list - _existing = {}", linkTargetPath); // The file or directory to link to.
log_.debug("list - _target = {}", _target); // The filename or absolute path of the link.
log_.debug("list - _subject = {}", Subjects.getUid(_subject)); // The UID of the user who executed this command.
Comment on lines +854 to +857
Copy link
Member

Choose a reason for hiding this comment

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

list -> link


// _parent + _target => the absolute path of the hard link.

try
{
IRODSAccount acct = getCurrentIRODSUser().getAccount();
Optional<String> replicaNumber = getReplicaNumberOfLatestGoodReplica(acct, linkTargetPath);

if (!replicaNumber.isPresent())
{
throw new NoEntException(String.format("No good replica to link for [%s]", linkTargetPath));
}

Map<String, String> jsonInput = new HashMap<>();
jsonInput.put("operation", "hard_link_create");
jsonInput.put("logical_path", linkTargetPath.toString());
jsonInput.put("replica_number", replicaNumber.get());
jsonInput.put("link_name", linkNamePath.toString());

RuleProcessingAO rpao = factory_.getRuleProcessingAO(acct);
RuleInvocationConfiguration ctx = new RuleInvocationConfiguration();
ctx.setIrodsRuleInvocationTypeEnum(IrodsRuleInvocationTypeEnum.OTHER);
ctx.setRuleEngineSpecifier(irodsSvrConfig_.getHardLinksRuleEnginePluginInstanceName());

StringBuilder sb = new StringBuilder(JSONUtils.toJSON(jsonInput));
sb.append("\nINPUT null\nOUTPUT ruleExecOut\n");

IRODSRuleExecResult result = rpao.executeRule(sb.toString(), null, ctx);

if (!result.getRuleExecOut().isEmpty() || !result.getRuleExecErr().isEmpty())
{
throw new IOException("Unexpected output on creation of hard link");
}

long newInodeNumber = inodeToPathMapper_.getAndIncrementFileID();
inodeToPathMapper_.map(newInodeNumber, linkNamePath);

return toFh(newInodeNumber);
}
catch (JargonException | JargonQueryException e)
{
log_.error(e.getMessage());
throw new IOException(e);
}
finally
{
closeCurrentConnection();
}
}

@Override
Expand Down Expand Up @@ -1126,6 +1196,7 @@ public int read(Inode _inode, byte[] _data, long _offset, int _count) throws IOE
@Override
public String readlink(Inode _inode) throws IOException
{
log_.debug("vfs::readlink");
throw new UnsupportedOperationException("Not supported");
}

Expand Down Expand Up @@ -1299,7 +1370,7 @@ private Stat statPath(Path _path, long _inodeNumber) throws IOException

stat.setUid(userId);
stat.setGid(groupId);
stat.setNlink(1);
stat.setNlink(1); // TODO: Should hard links affect this number?
Copy link
Member

Choose a reason for hiding this comment

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

we'd increase it and then... decrease it somewhere else?

stat.setDev(17);
stat.setIno((int) _inodeNumber);
stat.setRdev(0);
Expand Down Expand Up @@ -1553,6 +1624,33 @@ private boolean isAdministrator(String _userName) throws JargonException

return user.getUserType() == UserTypeEnum.RODS_ADMIN;
}

private Optional<String> getReplicaNumberOfLatestGoodReplica(IRODSAccount _acct, Path _logicalPath)
throws JargonException, JargonQueryException
{
// @formatter:off
String gql = String.format("select max(DATA_MODIFY_TIME) " +
"where " +
" COLL_NAME = '%s' and" +
" DATA_NAME = '%s' and" +
" DATA_REPL_STATUS = '1'",
_logicalPath.getParent(),
_logicalPath.getFileName());
// @formatter:on
final int rowsDesired = 1;
IRODSGenQuery genQuery = IRODSGenQuery.instance(gql, rowsDesired, RowCountOptions.ROW_COUNT_FOR_THIS_RESULT);
IRODSGenQueryExecutor executor = factory_.getIRODSGenQueryExecutor(_acct);

final int partialStartIndex = 0;
IRODSQueryResultSet resultSet = executor.executeIRODSQueryAndCloseResult(genQuery, partialStartIndex);

for (IRODSQueryResultRow row : resultSet.getResults())
{
return Optional.of(row.getColumn(0));
}

return Optional.empty();
}

private void closeCurrentConnection() throws IOException
{
Expand Down