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 */ 018 019package org.apache.hadoop.hdfs.server.namenode; 020 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.OutputStream; 024import java.util.ArrayList; 025import java.util.Iterator; 026import java.util.List; 027import java.util.Map; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031import org.apache.hadoop.HadoopIllegalArgumentException; 032import org.apache.hadoop.classification.InterfaceAudience; 033import org.apache.hadoop.fs.permission.AclEntry; 034import org.apache.hadoop.fs.permission.AclEntryScope; 035import org.apache.hadoop.fs.permission.AclEntryType; 036import org.apache.hadoop.fs.permission.FsAction; 037import org.apache.hadoop.fs.permission.FsPermission; 038import org.apache.hadoop.fs.permission.PermissionStatus; 039import org.apache.hadoop.fs.XAttr; 040import org.apache.hadoop.hdfs.protocol.Block; 041import org.apache.hadoop.hdfs.protocol.proto.HdfsProtos.BlockProto; 042import org.apache.hadoop.hdfs.protocolPB.PBHelper; 043import org.apache.hadoop.hdfs.server.blockmanagement.BlockInfo; 044import org.apache.hadoop.hdfs.server.blockmanagement.BlockInfoUnderConstruction; 045import org.apache.hadoop.hdfs.server.blockmanagement.BlockManager; 046import org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf.LoaderContext; 047import org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf.SaverContext; 048import org.apache.hadoop.hdfs.server.namenode.FsImageProto.FileSummary; 049import org.apache.hadoop.hdfs.server.namenode.FsImageProto.FilesUnderConstructionSection.FileUnderConstructionEntry; 050import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeDirectorySection; 051import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection; 052import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection.AclFeatureProto; 053import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection.XAttrCompactProto; 054import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection.XAttrFeatureProto; 055import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot; 056import org.apache.hadoop.hdfs.util.ReadOnlyList; 057 058import com.google.common.base.Preconditions; 059import com.google.common.collect.ImmutableList; 060import com.google.protobuf.ByteString; 061 062@InterfaceAudience.Private 063public final class FSImageFormatPBINode { 064 private final static long USER_GROUP_STRID_MASK = (1 << 24) - 1; 065 private final static int USER_STRID_OFFSET = 40; 066 private final static int GROUP_STRID_OFFSET = 16; 067 private static final Log LOG = LogFactory.getLog(FSImageFormatPBINode.class); 068 069 private static final int ACL_ENTRY_NAME_MASK = (1 << 24) - 1; 070 private static final int ACL_ENTRY_NAME_OFFSET = 6; 071 private static final int ACL_ENTRY_TYPE_OFFSET = 3; 072 private static final int ACL_ENTRY_SCOPE_OFFSET = 5; 073 private static final int ACL_ENTRY_PERM_MASK = 7; 074 private static final int ACL_ENTRY_TYPE_MASK = 3; 075 private static final int ACL_ENTRY_SCOPE_MASK = 1; 076 private static final FsAction[] FSACTION_VALUES = FsAction.values(); 077 private static final AclEntryScope[] ACL_ENTRY_SCOPE_VALUES = AclEntryScope 078 .values(); 079 private static final AclEntryType[] ACL_ENTRY_TYPE_VALUES = AclEntryType 080 .values(); 081 082 private static final int XATTR_NAMESPACE_MASK = 3; 083 private static final int XATTR_NAMESPACE_OFFSET = 30; 084 private static final int XATTR_NAME_MASK = (1 << 24) - 1; 085 private static final int XATTR_NAME_OFFSET = 6; 086 private static final XAttr.NameSpace[] XATTR_NAMESPACE_VALUES = 087 XAttr.NameSpace.values(); 088 089 090 public final static class Loader { 091 public static PermissionStatus loadPermission(long id, 092 final String[] stringTable) { 093 short perm = (short) (id & ((1 << GROUP_STRID_OFFSET) - 1)); 094 int gsid = (int) ((id >> GROUP_STRID_OFFSET) & USER_GROUP_STRID_MASK); 095 int usid = (int) ((id >> USER_STRID_OFFSET) & USER_GROUP_STRID_MASK); 096 return new PermissionStatus(stringTable[usid], stringTable[gsid], 097 new FsPermission(perm)); 098 } 099 100 public static ImmutableList<AclEntry> loadAclEntries( 101 AclFeatureProto proto, final String[] stringTable) { 102 ImmutableList.Builder<AclEntry> b = ImmutableList.builder(); 103 for (int v : proto.getEntriesList()) { 104 int p = v & ACL_ENTRY_PERM_MASK; 105 int t = (v >> ACL_ENTRY_TYPE_OFFSET) & ACL_ENTRY_TYPE_MASK; 106 int s = (v >> ACL_ENTRY_SCOPE_OFFSET) & ACL_ENTRY_SCOPE_MASK; 107 int nid = (v >> ACL_ENTRY_NAME_OFFSET) & ACL_ENTRY_NAME_MASK; 108 String name = stringTable[nid]; 109 b.add(new AclEntry.Builder().setName(name) 110 .setPermission(FSACTION_VALUES[p]) 111 .setScope(ACL_ENTRY_SCOPE_VALUES[s]) 112 .setType(ACL_ENTRY_TYPE_VALUES[t]).build()); 113 } 114 return b.build(); 115 } 116 117 public static ImmutableList<XAttr> loadXAttrs( 118 XAttrFeatureProto proto, final String[] stringTable) { 119 ImmutableList.Builder<XAttr> b = ImmutableList.builder(); 120 for (XAttrCompactProto xAttrCompactProto : proto.getXAttrsList()) { 121 int v = xAttrCompactProto.getName(); 122 int nid = (v >> XATTR_NAME_OFFSET) & XATTR_NAME_MASK; 123 int ns = (v >> XATTR_NAMESPACE_OFFSET) & XATTR_NAMESPACE_MASK; 124 String name = stringTable[nid]; 125 byte[] value = null; 126 if (xAttrCompactProto.getValue() != null) { 127 value = xAttrCompactProto.getValue().toByteArray(); 128 } 129 b.add(new XAttr.Builder().setNameSpace(XATTR_NAMESPACE_VALUES[ns]) 130 .setName(name).setValue(value).build()); 131 } 132 133 return b.build(); 134 } 135 136 public static INodeDirectory loadINodeDirectory(INodeSection.INode n, 137 LoaderContext state) { 138 assert n.getType() == INodeSection.INode.Type.DIRECTORY; 139 INodeSection.INodeDirectory d = n.getDirectory(); 140 141 final PermissionStatus permissions = loadPermission(d.getPermission(), 142 state.getStringTable()); 143 final INodeDirectory dir = new INodeDirectory(n.getId(), n.getName() 144 .toByteArray(), permissions, d.getModificationTime()); 145 146 final long nsQuota = d.getNsQuota(), dsQuota = d.getDsQuota(); 147 if (nsQuota >= 0 || dsQuota >= 0) { 148 dir.addDirectoryWithQuotaFeature(nsQuota, dsQuota); 149 } 150 151 if (d.hasAcl()) { 152 dir.addAclFeature(new AclFeature(loadAclEntries(d.getAcl(), 153 state.getStringTable()))); 154 } 155 if (d.hasXAttrs()) { 156 dir.addXAttrFeature(new XAttrFeature( 157 loadXAttrs(d.getXAttrs(), state.getStringTable()))); 158 } 159 return dir; 160 } 161 162 public static void updateBlocksMap(INodeFile file, BlockManager bm) { 163 // Add file->block mapping 164 final BlockInfo[] blocks = file.getBlocks(); 165 if (blocks != null) { 166 for (int i = 0; i < blocks.length; i++) { 167 file.setBlock(i, bm.addBlockCollection(blocks[i], file)); 168 } 169 } 170 } 171 172 private final FSDirectory dir; 173 private final FSNamesystem fsn; 174 private final FSImageFormatProtobuf.Loader parent; 175 176 Loader(FSNamesystem fsn, final FSImageFormatProtobuf.Loader parent) { 177 this.fsn = fsn; 178 this.dir = fsn.dir; 179 this.parent = parent; 180 } 181 182 void loadINodeDirectorySection(InputStream in) throws IOException { 183 final List<INodeReference> refList = parent.getLoaderContext() 184 .getRefList(); 185 while (true) { 186 INodeDirectorySection.DirEntry e = INodeDirectorySection.DirEntry 187 .parseDelimitedFrom(in); 188 // note that in is a LimitedInputStream 189 if (e == null) { 190 break; 191 } 192 INodeDirectory p = dir.getInode(e.getParent()).asDirectory(); 193 for (long id : e.getChildrenList()) { 194 INode child = dir.getInode(id); 195 addToParent(p, child); 196 } 197 for (int refId : e.getRefChildrenList()) { 198 INodeReference ref = refList.get(refId); 199 addToParent(p, ref); 200 } 201 } 202 } 203 204 void loadINodeSection(InputStream in) throws IOException { 205 INodeSection s = INodeSection.parseDelimitedFrom(in); 206 fsn.resetLastInodeId(s.getLastInodeId()); 207 LOG.info("Loading " + s.getNumInodes() + " INodes."); 208 for (int i = 0; i < s.getNumInodes(); ++i) { 209 INodeSection.INode p = INodeSection.INode.parseDelimitedFrom(in); 210 if (p.getId() == INodeId.ROOT_INODE_ID) { 211 loadRootINode(p); 212 } else { 213 INode n = loadINode(p); 214 dir.addToInodeMap(n); 215 } 216 } 217 } 218 219 /** 220 * Load the under-construction files section, and update the lease map 221 */ 222 void loadFilesUnderConstructionSection(InputStream in) throws IOException { 223 while (true) { 224 FileUnderConstructionEntry entry = FileUnderConstructionEntry 225 .parseDelimitedFrom(in); 226 if (entry == null) { 227 break; 228 } 229 // update the lease manager 230 INodeFile file = dir.getInode(entry.getInodeId()).asFile(); 231 FileUnderConstructionFeature uc = file.getFileUnderConstructionFeature(); 232 Preconditions.checkState(uc != null); // file must be under-construction 233 fsn.leaseManager.addLease(uc.getClientName(), entry.getFullPath()); 234 } 235 } 236 237 private void addToParent(INodeDirectory parent, INode child) { 238 if (parent == dir.rootDir && FSDirectory.isReservedName(child)) { 239 throw new HadoopIllegalArgumentException("File name \"" 240 + child.getLocalName() + "\" is reserved. Please " 241 + " change the name of the existing file or directory to another " 242 + "name before upgrading to this release."); 243 } 244 // NOTE: This does not update space counts for parents 245 if (!parent.addChild(child)) { 246 return; 247 } 248 dir.cacheName(child); 249 250 if (child.isFile()) { 251 updateBlocksMap(child.asFile(), fsn.getBlockManager()); 252 } 253 } 254 255 private INode loadINode(INodeSection.INode n) { 256 switch (n.getType()) { 257 case FILE: 258 return loadINodeFile(n); 259 case DIRECTORY: 260 return loadINodeDirectory(n, parent.getLoaderContext()); 261 case SYMLINK: 262 return loadINodeSymlink(n); 263 default: 264 break; 265 } 266 return null; 267 } 268 269 private INodeFile loadINodeFile(INodeSection.INode n) { 270 assert n.getType() == INodeSection.INode.Type.FILE; 271 INodeSection.INodeFile f = n.getFile(); 272 List<BlockProto> bp = f.getBlocksList(); 273 short replication = (short) f.getReplication(); 274 LoaderContext state = parent.getLoaderContext(); 275 276 BlockInfo[] blocks = new BlockInfo[bp.size()]; 277 for (int i = 0, e = bp.size(); i < e; ++i) { 278 blocks[i] = new BlockInfo(PBHelper.convert(bp.get(i)), replication); 279 } 280 final PermissionStatus permissions = loadPermission(f.getPermission(), 281 parent.getLoaderContext().getStringTable()); 282 283 final INodeFile file = new INodeFile(n.getId(), 284 n.getName().toByteArray(), permissions, f.getModificationTime(), 285 f.getAccessTime(), blocks, replication, f.getPreferredBlockSize()); 286 287 if (f.hasAcl()) { 288 file.addAclFeature(new AclFeature(loadAclEntries(f.getAcl(), 289 state.getStringTable()))); 290 } 291 292 if (f.hasXAttrs()) { 293 file.addXAttrFeature(new XAttrFeature( 294 loadXAttrs(f.getXAttrs(), state.getStringTable()))); 295 } 296 297 // under-construction information 298 if (f.hasFileUC()) { 299 INodeSection.FileUnderConstructionFeature uc = f.getFileUC(); 300 file.toUnderConstruction(uc.getClientName(), uc.getClientMachine()); 301 if (blocks.length > 0) { 302 BlockInfo lastBlk = file.getLastBlock(); 303 // replace the last block of file 304 file.setBlock(file.numBlocks() - 1, new BlockInfoUnderConstruction( 305 lastBlk, replication)); 306 } 307 } 308 return file; 309 } 310 311 312 private INodeSymlink loadINodeSymlink(INodeSection.INode n) { 313 assert n.getType() == INodeSection.INode.Type.SYMLINK; 314 INodeSection.INodeSymlink s = n.getSymlink(); 315 final PermissionStatus permissions = loadPermission(s.getPermission(), 316 parent.getLoaderContext().getStringTable()); 317 INodeSymlink sym = new INodeSymlink(n.getId(), n.getName().toByteArray(), 318 permissions, s.getModificationTime(), s.getAccessTime(), 319 s.getTarget().toStringUtf8()); 320 return sym; 321 } 322 323 private void loadRootINode(INodeSection.INode p) { 324 INodeDirectory root = loadINodeDirectory(p, parent.getLoaderContext()); 325 final Quota.Counts q = root.getQuotaCounts(); 326 final long nsQuota = q.get(Quota.NAMESPACE); 327 final long dsQuota = q.get(Quota.DISKSPACE); 328 if (nsQuota != -1 || dsQuota != -1) { 329 dir.rootDir.getDirectoryWithQuotaFeature().setQuota(nsQuota, dsQuota); 330 } 331 dir.rootDir.cloneModificationTime(root); 332 dir.rootDir.clonePermissionStatus(root); 333 // root dir supports having extended attributes according to POSIX 334 final XAttrFeature f = root.getXAttrFeature(); 335 if (f != null) { 336 dir.rootDir.addXAttrFeature(f); 337 } 338 } 339 } 340 341 public final static class Saver { 342 private static long buildPermissionStatus(INodeAttributes n, 343 final SaverContext.DeduplicationMap<String> stringMap) { 344 long userId = stringMap.getId(n.getUserName()); 345 long groupId = stringMap.getId(n.getGroupName()); 346 return ((userId & USER_GROUP_STRID_MASK) << USER_STRID_OFFSET) 347 | ((groupId & USER_GROUP_STRID_MASK) << GROUP_STRID_OFFSET) 348 | n.getFsPermissionShort(); 349 } 350 351 private static AclFeatureProto.Builder buildAclEntries(AclFeature f, 352 final SaverContext.DeduplicationMap<String> map) { 353 AclFeatureProto.Builder b = AclFeatureProto.newBuilder(); 354 for (AclEntry e : f.getEntries()) { 355 int v = ((map.getId(e.getName()) & ACL_ENTRY_NAME_MASK) << ACL_ENTRY_NAME_OFFSET) 356 | (e.getType().ordinal() << ACL_ENTRY_TYPE_OFFSET) 357 | (e.getScope().ordinal() << ACL_ENTRY_SCOPE_OFFSET) 358 | (e.getPermission().ordinal()); 359 b.addEntries(v); 360 } 361 return b; 362 } 363 364 private static XAttrFeatureProto.Builder buildXAttrs(XAttrFeature f, 365 final SaverContext.DeduplicationMap<String> stringMap) { 366 XAttrFeatureProto.Builder b = XAttrFeatureProto.newBuilder(); 367 for (XAttr a : f.getXAttrs()) { 368 XAttrCompactProto.Builder xAttrCompactBuilder = XAttrCompactProto. 369 newBuilder(); 370 int v = ((a.getNameSpace().ordinal() & XATTR_NAMESPACE_MASK) << 371 XATTR_NAMESPACE_OFFSET) 372 | ((stringMap.getId(a.getName()) & XATTR_NAME_MASK) << 373 XATTR_NAME_OFFSET); 374 xAttrCompactBuilder.setName(v); 375 if (a.getValue() != null) { 376 xAttrCompactBuilder.setValue(PBHelper.getByteString(a.getValue())); 377 } 378 b.addXAttrs(xAttrCompactBuilder.build()); 379 } 380 381 return b; 382 } 383 384 public static INodeSection.INodeFile.Builder buildINodeFile( 385 INodeFileAttributes file, final SaverContext state) { 386 INodeSection.INodeFile.Builder b = INodeSection.INodeFile.newBuilder() 387 .setAccessTime(file.getAccessTime()) 388 .setModificationTime(file.getModificationTime()) 389 .setPermission(buildPermissionStatus(file, state.getStringMap())) 390 .setPreferredBlockSize(file.getPreferredBlockSize()) 391 .setReplication(file.getFileReplication()); 392 393 AclFeature f = file.getAclFeature(); 394 if (f != null) { 395 b.setAcl(buildAclEntries(f, state.getStringMap())); 396 } 397 XAttrFeature xAttrFeature = file.getXAttrFeature(); 398 if (xAttrFeature != null) { 399 b.setXAttrs(buildXAttrs(xAttrFeature, state.getStringMap())); 400 } 401 return b; 402 } 403 404 public static INodeSection.INodeDirectory.Builder buildINodeDirectory( 405 INodeDirectoryAttributes dir, final SaverContext state) { 406 Quota.Counts quota = dir.getQuotaCounts(); 407 INodeSection.INodeDirectory.Builder b = INodeSection.INodeDirectory 408 .newBuilder().setModificationTime(dir.getModificationTime()) 409 .setNsQuota(quota.get(Quota.NAMESPACE)) 410 .setDsQuota(quota.get(Quota.DISKSPACE)) 411 .setPermission(buildPermissionStatus(dir, state.getStringMap())); 412 413 AclFeature f = dir.getAclFeature(); 414 if (f != null) { 415 b.setAcl(buildAclEntries(f, state.getStringMap())); 416 } 417 XAttrFeature xAttrFeature = dir.getXAttrFeature(); 418 if (xAttrFeature != null) { 419 b.setXAttrs(buildXAttrs(xAttrFeature, state.getStringMap())); 420 } 421 return b; 422 } 423 424 private final FSNamesystem fsn; 425 private final FileSummary.Builder summary; 426 private final SaveNamespaceContext context; 427 private final FSImageFormatProtobuf.Saver parent; 428 429 Saver(FSImageFormatProtobuf.Saver parent, FileSummary.Builder summary) { 430 this.parent = parent; 431 this.summary = summary; 432 this.context = parent.getContext(); 433 this.fsn = context.getSourceNamesystem(); 434 } 435 436 void serializeINodeDirectorySection(OutputStream out) throws IOException { 437 Iterator<INodeWithAdditionalFields> iter = fsn.getFSDirectory() 438 .getINodeMap().getMapIterator(); 439 final ArrayList<INodeReference> refList = parent.getSaverContext() 440 .getRefList(); 441 int i = 0; 442 while (iter.hasNext()) { 443 INodeWithAdditionalFields n = iter.next(); 444 if (!n.isDirectory()) { 445 continue; 446 } 447 448 ReadOnlyList<INode> children = n.asDirectory().getChildrenList( 449 Snapshot.CURRENT_STATE_ID); 450 if (children.size() > 0) { 451 INodeDirectorySection.DirEntry.Builder b = INodeDirectorySection. 452 DirEntry.newBuilder().setParent(n.getId()); 453 for (INode inode : children) { 454 if (!inode.isReference()) { 455 b.addChildren(inode.getId()); 456 } else { 457 refList.add(inode.asReference()); 458 b.addRefChildren(refList.size() - 1); 459 } 460 } 461 INodeDirectorySection.DirEntry e = b.build(); 462 e.writeDelimitedTo(out); 463 } 464 465 ++i; 466 if (i % FSImageFormatProtobuf.Saver.CHECK_CANCEL_INTERVAL == 0) { 467 context.checkCancelled(); 468 } 469 } 470 parent.commitSection(summary, 471 FSImageFormatProtobuf.SectionName.INODE_DIR); 472 } 473 474 void serializeINodeSection(OutputStream out) throws IOException { 475 INodeMap inodesMap = fsn.dir.getINodeMap(); 476 477 INodeSection.Builder b = INodeSection.newBuilder() 478 .setLastInodeId(fsn.getLastInodeId()).setNumInodes(inodesMap.size()); 479 INodeSection s = b.build(); 480 s.writeDelimitedTo(out); 481 482 int i = 0; 483 Iterator<INodeWithAdditionalFields> iter = inodesMap.getMapIterator(); 484 while (iter.hasNext()) { 485 INodeWithAdditionalFields n = iter.next(); 486 save(out, n); 487 ++i; 488 if (i % FSImageFormatProtobuf.Saver.CHECK_CANCEL_INTERVAL == 0) { 489 context.checkCancelled(); 490 } 491 } 492 parent.commitSection(summary, FSImageFormatProtobuf.SectionName.INODE); 493 } 494 495 void serializeFilesUCSection(OutputStream out) throws IOException { 496 Map<String, INodeFile> ucMap = fsn.getFilesUnderConstruction(); 497 for (Map.Entry<String, INodeFile> entry : ucMap.entrySet()) { 498 String path = entry.getKey(); 499 INodeFile file = entry.getValue(); 500 FileUnderConstructionEntry.Builder b = FileUnderConstructionEntry 501 .newBuilder().setInodeId(file.getId()).setFullPath(path); 502 FileUnderConstructionEntry e = b.build(); 503 e.writeDelimitedTo(out); 504 } 505 parent.commitSection(summary, 506 FSImageFormatProtobuf.SectionName.FILES_UNDERCONSTRUCTION); 507 } 508 509 private void save(OutputStream out, INode n) throws IOException { 510 if (n.isDirectory()) { 511 save(out, n.asDirectory()); 512 } else if (n.isFile()) { 513 save(out, n.asFile()); 514 } else if (n.isSymlink()) { 515 save(out, n.asSymlink()); 516 } 517 } 518 519 private void save(OutputStream out, INodeDirectory n) throws IOException { 520 INodeSection.INodeDirectory.Builder b = buildINodeDirectory(n, 521 parent.getSaverContext()); 522 INodeSection.INode r = buildINodeCommon(n) 523 .setType(INodeSection.INode.Type.DIRECTORY).setDirectory(b).build(); 524 r.writeDelimitedTo(out); 525 } 526 527 private void save(OutputStream out, INodeFile n) throws IOException { 528 INodeSection.INodeFile.Builder b = buildINodeFile(n, 529 parent.getSaverContext()); 530 531 if (n.getBlocks() != null) { 532 for (Block block : n.getBlocks()) { 533 b.addBlocks(PBHelper.convert(block)); 534 } 535 } 536 537 FileUnderConstructionFeature uc = n.getFileUnderConstructionFeature(); 538 if (uc != null) { 539 INodeSection.FileUnderConstructionFeature f = 540 INodeSection.FileUnderConstructionFeature 541 .newBuilder().setClientName(uc.getClientName()) 542 .setClientMachine(uc.getClientMachine()).build(); 543 b.setFileUC(f); 544 } 545 546 INodeSection.INode r = buildINodeCommon(n) 547 .setType(INodeSection.INode.Type.FILE).setFile(b).build(); 548 r.writeDelimitedTo(out); 549 } 550 551 private void save(OutputStream out, INodeSymlink n) throws IOException { 552 SaverContext state = parent.getSaverContext(); 553 INodeSection.INodeSymlink.Builder b = INodeSection.INodeSymlink 554 .newBuilder() 555 .setPermission(buildPermissionStatus(n, state.getStringMap())) 556 .setTarget(ByteString.copyFrom(n.getSymlink())) 557 .setModificationTime(n.getModificationTime()) 558 .setAccessTime(n.getAccessTime()); 559 560 INodeSection.INode r = buildINodeCommon(n) 561 .setType(INodeSection.INode.Type.SYMLINK).setSymlink(b).build(); 562 r.writeDelimitedTo(out); 563 } 564 565 private final INodeSection.INode.Builder buildINodeCommon(INode n) { 566 return INodeSection.INode.newBuilder() 567 .setId(n.getId()) 568 .setName(ByteString.copyFrom(n.getLocalNameBytes())); 569 } 570 } 571 572 private FSImageFormatPBINode() { 573 } 574}