001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hdfs.server.namenode;
019
020import org.apache.hadoop.HadoopIllegalArgumentException;
021import org.apache.hadoop.fs.PathIsNotDirectoryException;
022import org.apache.hadoop.fs.StorageType;
023import org.apache.hadoop.fs.UnresolvedLinkException;
024import org.apache.hadoop.fs.XAttr;
025import org.apache.hadoop.fs.XAttrSetFlag;
026import org.apache.hadoop.fs.permission.FsAction;
027import org.apache.hadoop.fs.permission.FsPermission;
028import org.apache.hadoop.hdfs.protocol.Block;
029import org.apache.hadoop.hdfs.protocol.BlockStoragePolicy;
030import org.apache.hadoop.hdfs.protocol.HdfsConstants;
031import org.apache.hadoop.hdfs.protocol.HdfsFileStatus;
032import org.apache.hadoop.hdfs.protocol.QuotaExceededException;
033import org.apache.hadoop.hdfs.protocol.SnapshotAccessControlException;
034import org.apache.hadoop.hdfs.server.blockmanagement.BlockManager;
035import org.apache.hadoop.hdfs.server.blockmanagement.BlockStoragePolicySuite;
036import org.apache.hadoop.hdfs.util.EnumCounters;
037import org.apache.hadoop.security.AccessControlException;
038
039import java.io.FileNotFoundException;
040import java.io.IOException;
041import java.util.Arrays;
042import java.util.EnumSet;
043import java.util.List;
044
045import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_ACCESSTIME_PRECISION_KEY;
046import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_QUOTA_BY_STORAGETYPE_ENABLED_KEY;
047import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_STORAGE_POLICY_ENABLED_KEY;
048
049public class FSDirAttrOp {
050  static HdfsFileStatus setPermission(
051      FSDirectory fsd, final String srcArg, FsPermission permission)
052      throws IOException {
053    String src = srcArg;
054    FSPermissionChecker pc = fsd.getPermissionChecker();
055    INodesInPath iip;
056    fsd.writeLock();
057    try {
058      iip = fsd.resolvePathForWrite(pc, src);
059      src = iip.getPath();
060      fsd.checkOwner(pc, iip);
061      unprotectedSetPermission(fsd, src, permission);
062    } finally {
063      fsd.writeUnlock();
064    }
065    fsd.getEditLog().logSetPermissions(src, permission);
066    return fsd.getAuditFileInfo(iip);
067  }
068
069  static HdfsFileStatus setOwner(
070      FSDirectory fsd, String src, String username, String group)
071      throws IOException {
072    FSPermissionChecker pc = fsd.getPermissionChecker();
073    INodesInPath iip;
074    fsd.writeLock();
075    try {
076      iip = fsd.resolvePathForWrite(pc, src);
077      src = iip.getPath();
078      fsd.checkOwner(pc, iip);
079      if (!pc.isSuperUser()) {
080        if (username != null && !pc.getUser().equals(username)) {
081          throw new AccessControlException("User " + username
082              + " is not a super user (non-super user cannot change owner).");
083        }
084        if (group != null && !pc.containsGroup(group)) {
085          throw new AccessControlException(
086              "User " + username + " does not belong to " + group);
087        }
088      }
089      unprotectedSetOwner(fsd, src, username, group);
090    } finally {
091      fsd.writeUnlock();
092    }
093    fsd.getEditLog().logSetOwner(src, username, group);
094    return fsd.getAuditFileInfo(iip);
095  }
096
097  static HdfsFileStatus setTimes(
098      FSDirectory fsd, String src, long mtime, long atime)
099      throws IOException {
100    if (!fsd.isAccessTimeSupported() && atime != -1) {
101      throw new IOException(
102          "Access time for hdfs is not configured. " +
103              " Please set " + DFS_NAMENODE_ACCESSTIME_PRECISION_KEY
104              + " configuration parameter.");
105    }
106
107    FSPermissionChecker pc = fsd.getPermissionChecker();
108
109    INodesInPath iip;
110    fsd.writeLock();
111    try {
112      iip = fsd.resolvePathForWrite(pc, src);
113      src = iip.getPath();
114      // Write access is required to set access and modification times
115      if (fsd.isPermissionEnabled()) {
116        fsd.checkPathAccess(pc, iip, FsAction.WRITE);
117      }
118      final INode inode = iip.getLastINode();
119      if (inode == null) {
120        throw new FileNotFoundException("File/Directory " + src +
121                                            " does not exist.");
122      }
123      boolean changed = unprotectedSetTimes(fsd, inode, mtime, atime, true,
124                                            iip.getLatestSnapshotId());
125      if (changed) {
126        fsd.getEditLog().logTimes(src, mtime, atime);
127      }
128    } finally {
129      fsd.writeUnlock();
130    }
131    return fsd.getAuditFileInfo(iip);
132  }
133
134  static boolean setReplication(
135      FSDirectory fsd, BlockManager bm, String src, final short replication)
136      throws IOException {
137    bm.verifyReplication(src, replication, null);
138    final boolean isFile;
139    FSPermissionChecker pc = fsd.getPermissionChecker();
140    fsd.writeLock();
141    try {
142      final INodesInPath iip = fsd.resolvePathForWrite(pc, src);
143      src = iip.getPath();
144      if (fsd.isPermissionEnabled()) {
145        fsd.checkPathAccess(pc, iip, FsAction.WRITE);
146      }
147
148      final short[] blockRepls = new short[2]; // 0: old, 1: new
149      final Block[] blocks = unprotectedSetReplication(fsd, src, replication,
150                                                       blockRepls);
151      isFile = blocks != null;
152      if (isFile) {
153        fsd.getEditLog().logSetReplication(src, replication);
154        bm.setReplication(blockRepls[0], blockRepls[1], src, blocks);
155      }
156    } finally {
157      fsd.writeUnlock();
158    }
159    return isFile;
160  }
161
162  static HdfsFileStatus setStoragePolicy(
163      FSDirectory fsd, BlockManager bm, String src, final String policyName)
164      throws IOException {
165    if (!fsd.isStoragePolicyEnabled()) {
166      throw new IOException(
167          "Failed to set storage policy since "
168              + DFS_STORAGE_POLICY_ENABLED_KEY + " is set to false.");
169    }
170    FSPermissionChecker pc = fsd.getPermissionChecker();
171    INodesInPath iip;
172    fsd.writeLock();
173    try {
174      src = FSDirectory.resolvePath(src, fsd);
175      iip = fsd.getINodesInPath4Write(src);
176
177      if (fsd.isPermissionEnabled()) {
178        fsd.checkPathAccess(pc, iip, FsAction.WRITE);
179      }
180
181      // get the corresponding policy and make sure the policy name is valid
182      BlockStoragePolicy policy = bm.getStoragePolicy(policyName);
183      if (policy == null) {
184        throw new HadoopIllegalArgumentException(
185            "Cannot find a block policy with the name " + policyName);
186      }
187      unprotectedSetStoragePolicy(fsd, bm, iip, policy.getId());
188      fsd.getEditLog().logSetStoragePolicy(src, policy.getId());
189    } finally {
190      fsd.writeUnlock();
191    }
192    return fsd.getAuditFileInfo(iip);
193  }
194
195  static BlockStoragePolicy[] getStoragePolicies(BlockManager bm)
196      throws IOException {
197    return bm.getStoragePolicies();
198  }
199
200  static long getPreferredBlockSize(FSDirectory fsd, String src)
201      throws IOException {
202    FSPermissionChecker pc = fsd.getPermissionChecker();
203    fsd.readLock();
204    try {
205      final INodesInPath iip = fsd.resolvePath(pc, src, false);
206      src = iip.getPath();
207      if (fsd.isPermissionEnabled()) {
208        fsd.checkTraverse(pc, iip);
209      }
210      return INodeFile.valueOf(iip.getLastINode(), src)
211          .getPreferredBlockSize();
212    } finally {
213      fsd.readUnlock();
214    }
215  }
216
217  /**
218   * Set the namespace, storagespace and typespace quota for a directory.
219   *
220   * Note: This does not support ".inodes" relative path.
221   */
222  static void setQuota(FSDirectory fsd, String src, long nsQuota, long ssQuota,
223      StorageType type) throws IOException {
224    if (fsd.isPermissionEnabled()) {
225      FSPermissionChecker pc = fsd.getPermissionChecker();
226      pc.checkSuperuserPrivilege();
227    }
228
229    fsd.writeLock();
230    try {
231      INodeDirectory changed = unprotectedSetQuota(fsd, src, nsQuota, ssQuota, type);
232      if (changed != null) {
233        final QuotaCounts q = changed.getQuotaCounts();
234        if (type == null) {
235          fsd.getEditLog().logSetQuota(src, q.getNameSpace(), q.getStorageSpace());
236        } else {
237          fsd.getEditLog().logSetQuotaByStorageType(
238              src, q.getTypeSpaces().get(type), type);
239        }
240      }
241    } finally {
242      fsd.writeUnlock();
243    }
244  }
245
246  static void unprotectedSetPermission(
247      FSDirectory fsd, String src, FsPermission permissions)
248      throws FileNotFoundException, UnresolvedLinkException,
249             QuotaExceededException, SnapshotAccessControlException {
250    assert fsd.hasWriteLock();
251    final INodesInPath inodesInPath = fsd.getINodesInPath4Write(src, true);
252    final INode inode = inodesInPath.getLastINode();
253    if (inode == null) {
254      throw new FileNotFoundException("File does not exist: " + src);
255    }
256    int snapshotId = inodesInPath.getLatestSnapshotId();
257    inode.setPermission(permissions, snapshotId);
258  }
259
260  static void unprotectedSetOwner(
261      FSDirectory fsd, String src, String username, String groupname)
262      throws FileNotFoundException, UnresolvedLinkException,
263      QuotaExceededException, SnapshotAccessControlException {
264    assert fsd.hasWriteLock();
265    final INodesInPath inodesInPath = fsd.getINodesInPath4Write(src, true);
266    INode inode = inodesInPath.getLastINode();
267    if (inode == null) {
268      throw new FileNotFoundException("File does not exist: " + src);
269    }
270    if (username != null) {
271      inode = inode.setUser(username, inodesInPath.getLatestSnapshotId());
272    }
273    if (groupname != null) {
274      inode.setGroup(groupname, inodesInPath.getLatestSnapshotId());
275    }
276  }
277
278  static boolean setTimes(
279      FSDirectory fsd, INode inode, long mtime, long atime, boolean force,
280      int latestSnapshotId) throws QuotaExceededException {
281    fsd.writeLock();
282    try {
283      return unprotectedSetTimes(fsd, inode, mtime, atime, force,
284                                 latestSnapshotId);
285    } finally {
286      fsd.writeUnlock();
287    }
288  }
289
290  static boolean unprotectedSetTimes(
291      FSDirectory fsd, String src, long mtime, long atime, boolean force)
292      throws UnresolvedLinkException, QuotaExceededException {
293    assert fsd.hasWriteLock();
294    final INodesInPath i = fsd.getINodesInPath(src, true);
295    return unprotectedSetTimes(fsd, i.getLastINode(), mtime, atime,
296                               force, i.getLatestSnapshotId());
297  }
298
299  /**
300   * See {@link org.apache.hadoop.hdfs.protocol.ClientProtocol#setQuota(String,
301   *     long, long, StorageType)}
302   * for the contract.
303   * Sets quota for for a directory.
304   * @return INodeDirectory if any of the quotas have changed. null otherwise.
305   * @throws FileNotFoundException if the path does not exist.
306   * @throws PathIsNotDirectoryException if the path is not a directory.
307   * @throws QuotaExceededException if the directory tree size is
308   *                                greater than the given quota
309   * @throws UnresolvedLinkException if a symlink is encountered in src.
310   * @throws SnapshotAccessControlException if path is in RO snapshot
311   */
312  static INodeDirectory unprotectedSetQuota(
313      FSDirectory fsd, String src, long nsQuota, long ssQuota, StorageType type)
314      throws FileNotFoundException, PathIsNotDirectoryException,
315      QuotaExceededException, UnresolvedLinkException,
316      SnapshotAccessControlException, UnsupportedActionException {
317    assert fsd.hasWriteLock();
318    // sanity check
319    if ((nsQuota < 0 && nsQuota != HdfsConstants.QUOTA_DONT_SET &&
320         nsQuota != HdfsConstants.QUOTA_RESET) ||
321        (ssQuota < 0 && ssQuota != HdfsConstants.QUOTA_DONT_SET &&
322          ssQuota != HdfsConstants.QUOTA_RESET)) {
323      throw new IllegalArgumentException("Illegal value for nsQuota or " +
324                                         "ssQuota : " + nsQuota + " and " +
325                                         ssQuota);
326    }
327    // sanity check for quota by storage type
328    if ((type != null) && (!fsd.isQuotaByStorageTypeEnabled() ||
329        nsQuota != HdfsConstants.QUOTA_DONT_SET)) {
330      throw new UnsupportedActionException(
331          "Failed to set quota by storage type because either" +
332          DFS_QUOTA_BY_STORAGETYPE_ENABLED_KEY + " is set to " +
333          fsd.isQuotaByStorageTypeEnabled() + " or nsQuota value is illegal " +
334          nsQuota);
335    }
336
337    String srcs = FSDirectory.normalizePath(src);
338    final INodesInPath iip = fsd.getINodesInPath4Write(srcs, true);
339    INodeDirectory dirNode = INodeDirectory.valueOf(iip.getLastINode(), srcs);
340    if (dirNode.isRoot() && nsQuota == HdfsConstants.QUOTA_RESET) {
341      throw new IllegalArgumentException("Cannot clear namespace quota on root.");
342    } else { // a directory inode
343      final QuotaCounts oldQuota = dirNode.getQuotaCounts();
344      final long oldNsQuota = oldQuota.getNameSpace();
345      final long oldSsQuota = oldQuota.getStorageSpace();
346
347      if (nsQuota == HdfsConstants.QUOTA_DONT_SET) {
348        nsQuota = oldNsQuota;
349      }
350      if (ssQuota == HdfsConstants.QUOTA_DONT_SET) {
351        ssQuota = oldSsQuota;
352      }
353
354      // unchanged space/namespace quota
355      if (type == null && oldNsQuota == nsQuota && oldSsQuota == ssQuota) {
356        return null;
357      }
358
359      // unchanged type quota
360      if (type != null) {
361          EnumCounters<StorageType> oldTypeQuotas = oldQuota.getTypeSpaces();
362          if (oldTypeQuotas != null && oldTypeQuotas.get(type) == ssQuota) {
363              return null;
364          }
365      }
366
367      final int latest = iip.getLatestSnapshotId();
368      dirNode.recordModification(latest);
369      dirNode.setQuota(fsd.getBlockStoragePolicySuite(), nsQuota, ssQuota, type);
370      return dirNode;
371    }
372  }
373
374  static Block[] unprotectedSetReplication(
375      FSDirectory fsd, String src, short replication, short[] blockRepls)
376      throws QuotaExceededException, UnresolvedLinkException,
377             SnapshotAccessControlException {
378    assert fsd.hasWriteLock();
379
380    final INodesInPath iip = fsd.getINodesInPath4Write(src, true);
381    final INode inode = iip.getLastINode();
382    if (inode == null || !inode.isFile()) {
383      return null;
384    }
385    INodeFile file = inode.asFile();
386    final short oldBR = file.getBlockReplication();
387    long size = file.computeFileSize(true, true);
388
389    // before setFileReplication, check for increasing block replication.
390    // if replication > oldBR, then newBR == replication.
391    // if replication < oldBR, we don't know newBR yet.
392    if (replication > oldBR) {
393      fsd.updateCount(iip, 0L, size, oldBR, replication, true);
394    }
395
396    file.setFileReplication(replication, iip.getLatestSnapshotId());
397
398    final short newBR = file.getBlockReplication();
399    // check newBR < oldBR case.
400    if (newBR < oldBR) {
401      fsd.updateCount(iip, 0L, size, oldBR, newBR, true);
402    }
403
404    if (blockRepls != null) {
405      blockRepls[0] = oldBR;
406      blockRepls[1] = newBR;
407    }
408    return file.getBlocks();
409  }
410
411  static void unprotectedSetStoragePolicy(
412      FSDirectory fsd, BlockManager bm, INodesInPath iip, byte policyId)
413      throws IOException {
414    assert fsd.hasWriteLock();
415    final INode inode = iip.getLastINode();
416    if (inode == null) {
417      throw new FileNotFoundException("File/Directory does not exist: "
418          + iip.getPath());
419    }
420    final int snapshotId = iip.getLatestSnapshotId();
421    if (inode.isFile()) {
422      BlockStoragePolicy newPolicy = bm.getStoragePolicy(policyId);
423      if (newPolicy.isCopyOnCreateFile()) {
424        throw new HadoopIllegalArgumentException(
425            "Policy " + newPolicy + " cannot be set after file creation.");
426      }
427
428      BlockStoragePolicy currentPolicy =
429          bm.getStoragePolicy(inode.getLocalStoragePolicyID());
430
431      if (currentPolicy != null && currentPolicy.isCopyOnCreateFile()) {
432        throw new HadoopIllegalArgumentException(
433            "Existing policy " + currentPolicy.getName() +
434                " cannot be changed after file creation.");
435      }
436      inode.asFile().setStoragePolicyID(policyId, snapshotId);
437    } else if (inode.isDirectory()) {
438      setDirStoragePolicy(fsd, inode.asDirectory(), policyId, snapshotId);
439    } else {
440      throw new FileNotFoundException(iip.getPath()
441          + " is not a file or directory");
442    }
443  }
444
445  private static void setDirStoragePolicy(
446      FSDirectory fsd, INodeDirectory inode, byte policyId,
447      int latestSnapshotId) throws IOException {
448    List<XAttr> existingXAttrs = XAttrStorage.readINodeXAttrs(inode);
449    XAttr xAttr = BlockStoragePolicySuite.buildXAttr(policyId);
450    List<XAttr> newXAttrs = FSDirXAttrOp.setINodeXAttrs(fsd, existingXAttrs,
451                                                        Arrays.asList(xAttr),
452                                                        EnumSet.of(
453                                                            XAttrSetFlag.CREATE,
454                                                            XAttrSetFlag.REPLACE));
455    XAttrStorage.updateINodeXAttrs(inode, newXAttrs, latestSnapshotId);
456  }
457
458  private static boolean unprotectedSetTimes(
459      FSDirectory fsd, INode inode, long mtime, long atime, boolean force,
460      int latest) throws QuotaExceededException {
461    assert fsd.hasWriteLock();
462    boolean status = false;
463    if (mtime != -1) {
464      inode = inode.setModificationTime(mtime, latest);
465      status = true;
466    }
467    // if the last access time update was within the last precision interval,
468    // then no need to store access time
469    if (atime != -1 && (status || force || atime > inode.getAccessTime() +
470        fsd.getFSNamesystem().getAccessTimePrecision())) {
471      inode.setAccessTime(atime, latest);
472      status = true;
473    }
474    return status;
475  }
476}