HDFS-10955. Pass IIP for FSDirAttr methods. Contributed by Daryn Sharp.

This commit is contained in:
Kihwal Lee 2016-10-06 16:31:29 -05:00
parent a28ffd0fde
commit e3a9666d28
3 changed files with 83 additions and 92 deletions

View File

@ -50,9 +50,8 @@
public class FSDirAttrOp { public class FSDirAttrOp {
static HdfsFileStatus setPermission( static HdfsFileStatus setPermission(
FSDirectory fsd, final String srcArg, FsPermission permission) FSDirectory fsd, final String src, FsPermission permission)
throws IOException { throws IOException {
String src = srcArg;
if (FSDirectory.isExactReservedName(src)) { if (FSDirectory.isExactReservedName(src)) {
throw new InvalidPathException(src); throw new InvalidPathException(src);
} }
@ -61,13 +60,12 @@ static HdfsFileStatus setPermission(
fsd.writeLock(); fsd.writeLock();
try { try {
iip = fsd.resolvePathForWrite(pc, src); iip = fsd.resolvePathForWrite(pc, src);
src = iip.getPath();
fsd.checkOwner(pc, iip); fsd.checkOwner(pc, iip);
unprotectedSetPermission(fsd, src, permission); unprotectedSetPermission(fsd, iip, permission);
} finally { } finally {
fsd.writeUnlock(); fsd.writeUnlock();
} }
fsd.getEditLog().logSetPermissions(src, permission); fsd.getEditLog().logSetPermissions(iip.getPath(), permission);
return fsd.getAuditFileInfo(iip); return fsd.getAuditFileInfo(iip);
} }
@ -82,7 +80,6 @@ static HdfsFileStatus setOwner(
fsd.writeLock(); fsd.writeLock();
try { try {
iip = fsd.resolvePathForWrite(pc, src); iip = fsd.resolvePathForWrite(pc, src);
src = iip.getPath();
fsd.checkOwner(pc, iip); fsd.checkOwner(pc, iip);
if (!pc.isSuperUser()) { if (!pc.isSuperUser()) {
if (username != null && !pc.getUser().equals(username)) { if (username != null && !pc.getUser().equals(username)) {
@ -92,11 +89,11 @@ static HdfsFileStatus setOwner(
throw new AccessControlException("User does not belong to " + group); throw new AccessControlException("User does not belong to " + group);
} }
} }
unprotectedSetOwner(fsd, src, username, group); unprotectedSetOwner(fsd, iip, username, group);
} finally { } finally {
fsd.writeUnlock(); fsd.writeUnlock();
} }
fsd.getEditLog().logSetOwner(src, username, group); fsd.getEditLog().logSetOwner(iip.getPath(), username, group);
return fsd.getAuditFileInfo(iip); return fsd.getAuditFileInfo(iip);
} }
@ -109,20 +106,18 @@ static HdfsFileStatus setTimes(
fsd.writeLock(); fsd.writeLock();
try { try {
iip = fsd.resolvePathForWrite(pc, src); iip = fsd.resolvePathForWrite(pc, src);
src = iip.getPath();
// Write access is required to set access and modification times // Write access is required to set access and modification times
if (fsd.isPermissionEnabled()) { if (fsd.isPermissionEnabled()) {
fsd.checkPathAccess(pc, iip, FsAction.WRITE); fsd.checkPathAccess(pc, iip, FsAction.WRITE);
} }
final INode inode = iip.getLastINode(); final INode inode = iip.getLastINode();
if (inode == null) { if (inode == null) {
throw new FileNotFoundException("File/Directory " + src + throw new FileNotFoundException("File/Directory " + iip.getPath() +
" does not exist."); " does not exist.");
} }
boolean changed = unprotectedSetTimes(fsd, inode, mtime, atime, true, boolean changed = unprotectedSetTimes(fsd, iip, mtime, atime, true);
iip.getLatestSnapshotId());
if (changed) { if (changed) {
fsd.getEditLog().logTimes(src, mtime, atime); fsd.getEditLog().logTimes(iip.getPath(), mtime, atime);
} }
} finally { } finally {
fsd.writeUnlock(); fsd.writeUnlock();
@ -139,16 +134,15 @@ static boolean setReplication(
fsd.writeLock(); fsd.writeLock();
try { try {
final INodesInPath iip = fsd.resolvePathForWrite(pc, src); final INodesInPath iip = fsd.resolvePathForWrite(pc, src);
src = iip.getPath();
if (fsd.isPermissionEnabled()) { if (fsd.isPermissionEnabled()) {
fsd.checkPathAccess(pc, iip, FsAction.WRITE); fsd.checkPathAccess(pc, iip, FsAction.WRITE);
} }
final BlockInfo[] blocks = unprotectedSetReplication(fsd, src, final BlockInfo[] blocks = unprotectedSetReplication(fsd, iip,
replication); replication);
isFile = blocks != null; isFile = blocks != null;
if (isFile) { if (isFile) {
fsd.getEditLog().logSetReplication(src, replication); fsd.getEditLog().logSetReplication(iip.getPath(), replication);
} }
} finally { } finally {
fsd.writeUnlock(); fsd.writeUnlock();
@ -186,15 +180,14 @@ static HdfsFileStatus setStoragePolicy(FSDirectory fsd, BlockManager bm,
INodesInPath iip; INodesInPath iip;
fsd.writeLock(); fsd.writeLock();
try { try {
src = FSDirectory.resolvePath(src, fsd); iip = fsd.resolvePathForWrite(pc, src);
iip = fsd.getINodesInPath4Write(src);
if (fsd.isPermissionEnabled()) { if (fsd.isPermissionEnabled()) {
fsd.checkPathAccess(pc, iip, FsAction.WRITE); fsd.checkPathAccess(pc, iip, FsAction.WRITE);
} }
unprotectedSetStoragePolicy(fsd, bm, iip, policyId); unprotectedSetStoragePolicy(fsd, bm, iip, policyId);
fsd.getEditLog().logSetStoragePolicy(src, policyId); fsd.getEditLog().logSetStoragePolicy(iip.getPath(), policyId);
} finally { } finally {
fsd.writeUnlock(); fsd.writeUnlock();
} }
@ -232,11 +225,10 @@ static long getPreferredBlockSize(FSDirectory fsd, String src)
fsd.readLock(); fsd.readLock();
try { try {
final INodesInPath iip = fsd.resolvePath(pc, src, false); final INodesInPath iip = fsd.resolvePath(pc, src, false);
src = iip.getPath();
if (fsd.isPermissionEnabled()) { if (fsd.isPermissionEnabled()) {
fsd.checkTraverse(pc, iip); fsd.checkTraverse(pc, iip);
} }
return INodeFile.valueOf(iip.getLastINode(), src) return INodeFile.valueOf(iip.getLastINode(), iip.getPath())
.getPreferredBlockSize(); .getPreferredBlockSize();
} finally { } finally {
fsd.readUnlock(); fsd.readUnlock();
@ -250,14 +242,16 @@ static long getPreferredBlockSize(FSDirectory fsd, String src)
*/ */
static void setQuota(FSDirectory fsd, String src, long nsQuota, long ssQuota, static void setQuota(FSDirectory fsd, String src, long nsQuota, long ssQuota,
StorageType type) throws IOException { StorageType type) throws IOException {
FSPermissionChecker pc = fsd.getPermissionChecker();
if (fsd.isPermissionEnabled()) { if (fsd.isPermissionEnabled()) {
FSPermissionChecker pc = fsd.getPermissionChecker();
pc.checkSuperuserPrivilege(); pc.checkSuperuserPrivilege();
} }
fsd.writeLock(); fsd.writeLock();
try { try {
INodeDirectory changed = unprotectedSetQuota(fsd, src, nsQuota, ssQuota, type); INodesInPath iip = fsd.resolvePathForWrite(pc, src);
INodeDirectory changed =
unprotectedSetQuota(fsd, iip, nsQuota, ssQuota, type);
if (changed != null) { if (changed != null) {
final QuotaCounts q = changed.getQuotaCounts(); final QuotaCounts q = changed.getQuotaCounts();
if (type == null) { if (type == null) {
@ -273,58 +267,40 @@ static void setQuota(FSDirectory fsd, String src, long nsQuota, long ssQuota,
} }
static void unprotectedSetPermission( static void unprotectedSetPermission(
FSDirectory fsd, String src, FsPermission permissions) FSDirectory fsd, INodesInPath iip, FsPermission permissions)
throws FileNotFoundException, UnresolvedLinkException, throws FileNotFoundException, UnresolvedLinkException,
QuotaExceededException, SnapshotAccessControlException { QuotaExceededException, SnapshotAccessControlException {
assert fsd.hasWriteLock(); assert fsd.hasWriteLock();
final INodesInPath inodesInPath = fsd.getINodesInPath4Write(src, true); final INode inode = FSDirectory.resolveLastINode(iip);
final INode inode = inodesInPath.getLastINode(); int snapshotId = iip.getLatestSnapshotId();
if (inode == null) {
throw new FileNotFoundException("File does not exist: " + src);
}
int snapshotId = inodesInPath.getLatestSnapshotId();
inode.setPermission(permissions, snapshotId); inode.setPermission(permissions, snapshotId);
} }
static void unprotectedSetOwner( static void unprotectedSetOwner(
FSDirectory fsd, String src, String username, String groupname) FSDirectory fsd, INodesInPath iip, String username, String groupname)
throws FileNotFoundException, UnresolvedLinkException, throws FileNotFoundException, UnresolvedLinkException,
QuotaExceededException, SnapshotAccessControlException { QuotaExceededException, SnapshotAccessControlException {
assert fsd.hasWriteLock(); assert fsd.hasWriteLock();
final INodesInPath inodesInPath = fsd.getINodesInPath4Write(src, true); final INode inode = FSDirectory.resolveLastINode(iip);
INode inode = inodesInPath.getLastINode();
if (inode == null) {
throw new FileNotFoundException("File does not exist: " + src);
}
if (username != null) { if (username != null) {
inode = inode.setUser(username, inodesInPath.getLatestSnapshotId()); inode.setUser(username, iip.getLatestSnapshotId());
} }
if (groupname != null) { if (groupname != null) {
inode.setGroup(groupname, inodesInPath.getLatestSnapshotId()); inode.setGroup(groupname, iip.getLatestSnapshotId());
} }
} }
static boolean setTimes( static boolean setTimes(
FSDirectory fsd, INode inode, long mtime, long atime, boolean force, FSDirectory fsd, INodesInPath iip, long mtime, long atime, boolean force)
int latestSnapshotId) throws QuotaExceededException { throws QuotaExceededException {
fsd.writeLock(); fsd.writeLock();
try { try {
return unprotectedSetTimes(fsd, inode, mtime, atime, force, return unprotectedSetTimes(fsd, iip, mtime, atime, force);
latestSnapshotId);
} finally { } finally {
fsd.writeUnlock(); fsd.writeUnlock();
} }
} }
static boolean unprotectedSetTimes(
FSDirectory fsd, String src, long mtime, long atime, boolean force)
throws UnresolvedLinkException, QuotaExceededException {
assert fsd.hasWriteLock();
final INodesInPath i = fsd.getINodesInPath(src, true);
return unprotectedSetTimes(fsd, i.getLastINode(), mtime, atime,
force, i.getLatestSnapshotId());
}
/** /**
* See {@link org.apache.hadoop.hdfs.protocol.ClientProtocol#setQuota(String, * See {@link org.apache.hadoop.hdfs.protocol.ClientProtocol#setQuota(String,
* long, long, StorageType)} * long, long, StorageType)}
@ -339,7 +315,8 @@ static boolean unprotectedSetTimes(
* @throws SnapshotAccessControlException if path is in RO snapshot * @throws SnapshotAccessControlException if path is in RO snapshot
*/ */
static INodeDirectory unprotectedSetQuota( static INodeDirectory unprotectedSetQuota(
FSDirectory fsd, String src, long nsQuota, long ssQuota, StorageType type) FSDirectory fsd, INodesInPath iip, long nsQuota,
long ssQuota, StorageType type)
throws FileNotFoundException, PathIsNotDirectoryException, throws FileNotFoundException, PathIsNotDirectoryException,
QuotaExceededException, UnresolvedLinkException, QuotaExceededException, UnresolvedLinkException,
SnapshotAccessControlException, UnsupportedActionException { SnapshotAccessControlException, UnsupportedActionException {
@ -363,9 +340,8 @@ static INodeDirectory unprotectedSetQuota(
nsQuota); nsQuota);
} }
String srcs = FSDirectory.normalizePath(src); INodeDirectory dirNode =
final INodesInPath iip = fsd.getINodesInPath4Write(srcs, true); INodeDirectory.valueOf(iip.getLastINode(), iip.getPath());
INodeDirectory dirNode = INodeDirectory.valueOf(iip.getLastINode(), srcs);
if (dirNode.isRoot() && nsQuota == HdfsConstants.QUOTA_RESET) { if (dirNode.isRoot() && nsQuota == HdfsConstants.QUOTA_RESET) {
throw new IllegalArgumentException("Cannot clear namespace quota on root."); throw new IllegalArgumentException("Cannot clear namespace quota on root.");
} else { // a directory inode } else { // a directory inode
@ -401,13 +377,12 @@ static INodeDirectory unprotectedSetQuota(
} }
static BlockInfo[] unprotectedSetReplication( static BlockInfo[] unprotectedSetReplication(
FSDirectory fsd, String src, short replication) FSDirectory fsd, INodesInPath iip, short replication)
throws QuotaExceededException, UnresolvedLinkException, throws QuotaExceededException, UnresolvedLinkException,
SnapshotAccessControlException { SnapshotAccessControlException {
assert fsd.hasWriteLock(); assert fsd.hasWriteLock();
final BlockManager bm = fsd.getBlockManager(); final BlockManager bm = fsd.getBlockManager();
final INodesInPath iip = fsd.getINodesInPath4Write(src, true);
final INode inode = iip.getLastINode(); final INode inode = iip.getLastINode();
if (inode == null || !inode.isFile()) { if (inode == null || !inode.isFile()) {
return null; return null;
@ -437,10 +412,10 @@ static BlockInfo[] unprotectedSetReplication(
if (oldBR != -1) { if (oldBR != -1) {
if (oldBR > targetReplication) { if (oldBR > targetReplication) {
FSDirectory.LOG.info("Decreasing replication from {} to {} for {}", FSDirectory.LOG.info("Decreasing replication from {} to {} for {}",
oldBR, targetReplication, src); oldBR, targetReplication, iip.getPath());
} else { } else {
FSDirectory.LOG.info("Increasing replication from {} to {} for {}", FSDirectory.LOG.info("Increasing replication from {} to {} for {}",
oldBR, targetReplication, src); oldBR, targetReplication, iip.getPath());
} }
} }
return file.getBlocks(); return file.getBlocks();
@ -475,8 +450,7 @@ static void unprotectedSetStoragePolicy(FSDirectory fsd, BlockManager bm,
} }
inode.asFile().setStoragePolicyID(policyId, snapshotId); inode.asFile().setStoragePolicyID(policyId, snapshotId);
} else if (inode.isDirectory()) { } else if (inode.isDirectory()) {
setDirStoragePolicy(fsd, inode.asDirectory(), policyId, setDirStoragePolicy(fsd, iip, policyId);
snapshotId);
} else { } else {
throw new FileNotFoundException(iip.getPath() throw new FileNotFoundException(iip.getPath()
+ " is not a file or directory"); + " is not a file or directory");
@ -484,8 +458,8 @@ static void unprotectedSetStoragePolicy(FSDirectory fsd, BlockManager bm,
} }
private static void setDirStoragePolicy( private static void setDirStoragePolicy(
FSDirectory fsd, INodeDirectory inode, byte policyId, FSDirectory fsd, INodesInPath iip, byte policyId) throws IOException {
int latestSnapshotId) throws IOException { INode inode = FSDirectory.resolveLastINode(iip);
List<XAttr> existingXAttrs = XAttrStorage.readINodeXAttrs(inode); List<XAttr> existingXAttrs = XAttrStorage.readINodeXAttrs(inode);
XAttr xAttr = BlockStoragePolicySuite.buildXAttr(policyId); XAttr xAttr = BlockStoragePolicySuite.buildXAttr(policyId);
List<XAttr> newXAttrs = null; List<XAttr> newXAttrs = null;
@ -500,14 +474,16 @@ private static void setDirStoragePolicy(
Arrays.asList(xAttr), Arrays.asList(xAttr),
EnumSet.of(XAttrSetFlag.CREATE, XAttrSetFlag.REPLACE)); EnumSet.of(XAttrSetFlag.CREATE, XAttrSetFlag.REPLACE));
} }
XAttrStorage.updateINodeXAttrs(inode, newXAttrs, latestSnapshotId); XAttrStorage.updateINodeXAttrs(inode, newXAttrs, iip.getLatestSnapshotId());
} }
private static boolean unprotectedSetTimes( static boolean unprotectedSetTimes(
FSDirectory fsd, INode inode, long mtime, long atime, boolean force, FSDirectory fsd, INodesInPath iip, long mtime, long atime, boolean force)
int latest) throws QuotaExceededException { throws QuotaExceededException {
assert fsd.hasWriteLock(); assert fsd.hasWriteLock();
boolean status = false; boolean status = false;
INode inode = iip.getLastINode();
int latest = iip.getLatestSnapshotId();
if (mtime != -1) { if (mtime != -1) {
inode = inode.setModificationTime(mtime, latest); inode = inode.setModificationTime(mtime, latest);
status = true; status = true;

View File

@ -508,10 +508,12 @@ private long applyEditLogOp(FSEditLogOp op, FSDirectory fsDir,
} }
case OP_SET_REPLICATION: { case OP_SET_REPLICATION: {
SetReplicationOp setReplicationOp = (SetReplicationOp)op; SetReplicationOp setReplicationOp = (SetReplicationOp)op;
String src = renameReservedPathsOnUpgrade(
setReplicationOp.path, logVersion);
INodesInPath iip = fsDir.getINodesInPath4Write(src);
short replication = fsNamesys.getBlockManager().adjustReplication( short replication = fsNamesys.getBlockManager().adjustReplication(
setReplicationOp.replication); setReplicationOp.replication);
FSDirAttrOp.unprotectedSetReplication(fsDir, renameReservedPathsOnUpgrade( FSDirAttrOp.unprotectedSetReplication(fsDir, iip, replication);
setReplicationOp.path, logVersion), replication);
break; break;
} }
case OP_CONCAT_DELETE: { case OP_CONCAT_DELETE: {
@ -576,52 +578,66 @@ fsDir, renameReservedPathsOnUpgrade(deleteOp.path, logVersion),
} }
case OP_SET_PERMISSIONS: { case OP_SET_PERMISSIONS: {
SetPermissionsOp setPermissionsOp = (SetPermissionsOp)op; SetPermissionsOp setPermissionsOp = (SetPermissionsOp)op;
FSDirAttrOp.unprotectedSetPermission(fsDir, renameReservedPathsOnUpgrade( final String src =
setPermissionsOp.src, logVersion), setPermissionsOp.permissions); renameReservedPathsOnUpgrade(setPermissionsOp.src, logVersion);
final INodesInPath iip = fsDir.getINodesInPath4Write(src);
FSDirAttrOp.unprotectedSetPermission(fsDir, iip,
setPermissionsOp.permissions);
break; break;
} }
case OP_SET_OWNER: { case OP_SET_OWNER: {
SetOwnerOp setOwnerOp = (SetOwnerOp)op; SetOwnerOp setOwnerOp = (SetOwnerOp)op;
FSDirAttrOp.unprotectedSetOwner( final String src = renameReservedPathsOnUpgrade(
fsDir, renameReservedPathsOnUpgrade(setOwnerOp.src, logVersion), setOwnerOp.src, logVersion);
final INodesInPath iip = fsDir.getINodesInPath4Write(src);
FSDirAttrOp.unprotectedSetOwner(fsDir, iip,
setOwnerOp.username, setOwnerOp.groupname); setOwnerOp.username, setOwnerOp.groupname);
break; break;
} }
case OP_SET_NS_QUOTA: { case OP_SET_NS_QUOTA: {
SetNSQuotaOp setNSQuotaOp = (SetNSQuotaOp)op; SetNSQuotaOp setNSQuotaOp = (SetNSQuotaOp)op;
FSDirAttrOp.unprotectedSetQuota( final String src = renameReservedPathsOnUpgrade(
fsDir, renameReservedPathsOnUpgrade(setNSQuotaOp.src, logVersion), setNSQuotaOp.src, logVersion);
final INodesInPath iip = fsDir.getINodesInPath4Write(src);
FSDirAttrOp.unprotectedSetQuota(fsDir, iip,
setNSQuotaOp.nsQuota, HdfsConstants.QUOTA_DONT_SET, null); setNSQuotaOp.nsQuota, HdfsConstants.QUOTA_DONT_SET, null);
break; break;
} }
case OP_CLEAR_NS_QUOTA: { case OP_CLEAR_NS_QUOTA: {
ClearNSQuotaOp clearNSQuotaOp = (ClearNSQuotaOp)op; ClearNSQuotaOp clearNSQuotaOp = (ClearNSQuotaOp)op;
FSDirAttrOp.unprotectedSetQuota( final String src = renameReservedPathsOnUpgrade(
fsDir, renameReservedPathsOnUpgrade(clearNSQuotaOp.src, logVersion), clearNSQuotaOp.src, logVersion);
final INodesInPath iip = fsDir.getINodesInPath4Write(src);
FSDirAttrOp.unprotectedSetQuota(fsDir, iip,
HdfsConstants.QUOTA_RESET, HdfsConstants.QUOTA_DONT_SET, null); HdfsConstants.QUOTA_RESET, HdfsConstants.QUOTA_DONT_SET, null);
break; break;
} }
case OP_SET_QUOTA: {
case OP_SET_QUOTA:
SetQuotaOp setQuotaOp = (SetQuotaOp) op; SetQuotaOp setQuotaOp = (SetQuotaOp) op;
FSDirAttrOp.unprotectedSetQuota(fsDir, final String src = renameReservedPathsOnUpgrade(
renameReservedPathsOnUpgrade(setQuotaOp.src, logVersion), setQuotaOp.src, logVersion);
final INodesInPath iip = fsDir.getINodesInPath4Write(src);
FSDirAttrOp.unprotectedSetQuota(fsDir, iip,
setQuotaOp.nsQuota, setQuotaOp.dsQuota, null); setQuotaOp.nsQuota, setQuotaOp.dsQuota, null);
break; break;
}
case OP_SET_QUOTA_BY_STORAGETYPE: case OP_SET_QUOTA_BY_STORAGETYPE: {
FSEditLogOp.SetQuotaByStorageTypeOp setQuotaByStorageTypeOp = FSEditLogOp.SetQuotaByStorageTypeOp setQuotaByStorageTypeOp =
(FSEditLogOp.SetQuotaByStorageTypeOp) op; (FSEditLogOp.SetQuotaByStorageTypeOp) op;
FSDirAttrOp.unprotectedSetQuota(fsDir, final String src = renameReservedPathsOnUpgrade(
renameReservedPathsOnUpgrade(setQuotaByStorageTypeOp.src, logVersion), setQuotaByStorageTypeOp.src, logVersion);
final INodesInPath iip = fsDir.getINodesInPath4Write(src);
FSDirAttrOp.unprotectedSetQuota(fsDir, iip,
HdfsConstants.QUOTA_DONT_SET, setQuotaByStorageTypeOp.dsQuota, HdfsConstants.QUOTA_DONT_SET, setQuotaByStorageTypeOp.dsQuota,
setQuotaByStorageTypeOp.type); setQuotaByStorageTypeOp.type);
break; break;
}
case OP_TIMES: { case OP_TIMES: {
TimesOp timesOp = (TimesOp)op; TimesOp timesOp = (TimesOp)op;
FSDirAttrOp.unprotectedSetTimes( final String src = renameReservedPathsOnUpgrade(
fsDir, renameReservedPathsOnUpgrade(timesOp.path, logVersion), timesOp.path, logVersion);
final INodesInPath iip = fsDir.getINodesInPath4Write(src);
FSDirAttrOp.unprotectedSetTimes(fsDir, iip,
timesOp.mtime, timesOp.atime, true); timesOp.mtime, timesOp.atime, true);
break; break;
} }

View File

@ -1784,8 +1784,7 @@ LocatedBlocks getBlockLocations(String clientMachine, String srcArg,
boolean updateAccessTime = inode != null && boolean updateAccessTime = inode != null &&
now > inode.getAccessTime() + dir.getAccessTimePrecision(); now > inode.getAccessTime() + dir.getAccessTimePrecision();
if (!isInSafeMode() && updateAccessTime) { if (!isInSafeMode() && updateAccessTime) {
boolean changed = FSDirAttrOp.setTimes(dir, boolean changed = FSDirAttrOp.setTimes(dir, iip, -1, now, false);
inode, -1, now, false, iip.getLatestSnapshotId());
if (changed) { if (changed) {
getEditLog().logTimes(src, -1, now); getEditLog().logTimes(src, -1, now);
} }