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}