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.snapshot;
019
020import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Loader.loadINodeDirectory;
021import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Loader.loadPermission;
022import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Loader.updateBlocksMap;
023import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Saver.buildINodeDirectory;
024import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.Saver.buildINodeFile;
025
026import java.io.IOException;
027import java.io.InputStream;
028import java.io.OutputStream;
029import java.util.ArrayList;
030import java.util.Collections;
031import java.util.Comparator;
032import java.util.HashMap;
033import java.util.Iterator;
034import java.util.List;
035import java.util.Map;
036
037import com.google.common.collect.ImmutableList;
038import org.apache.hadoop.classification.InterfaceAudience;
039import org.apache.hadoop.fs.permission.PermissionStatus;
040import org.apache.hadoop.fs.StorageType;
041import org.apache.hadoop.hdfs.protocol.Block;
042import org.apache.hadoop.hdfs.protocol.HdfsConstants;
043import org.apache.hadoop.hdfs.protocol.proto.HdfsProtos.BlockProto;
044import org.apache.hadoop.hdfs.protocolPB.PBHelper;
045import org.apache.hadoop.hdfs.server.blockmanagement.BlockInfoContiguous;
046import org.apache.hadoop.hdfs.server.namenode.AclEntryStatusFormat;
047import org.apache.hadoop.hdfs.server.namenode.AclFeature;
048import org.apache.hadoop.hdfs.server.namenode.FSDirectory;
049import org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode;
050import org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf;
051import org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf.LoaderContext;
052import org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf.SectionName;
053import org.apache.hadoop.hdfs.server.namenode.FSNamesystem;
054import org.apache.hadoop.hdfs.server.namenode.FsImageProto.FileSummary;
055import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeReferenceSection;
056import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection;
057import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SnapshotDiffSection;
058import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SnapshotDiffSection.CreatedListEntry;
059import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SnapshotDiffSection.DiffEntry.Type;
060import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SnapshotSection;
061import org.apache.hadoop.hdfs.server.namenode.INode;
062import org.apache.hadoop.hdfs.server.namenode.INodeDirectory;
063import org.apache.hadoop.hdfs.server.namenode.INodeDirectoryAttributes;
064import org.apache.hadoop.hdfs.server.namenode.INodeFile;
065import org.apache.hadoop.hdfs.server.namenode.INodeFileAttributes;
066import org.apache.hadoop.hdfs.server.namenode.INodeMap;
067import org.apache.hadoop.hdfs.server.namenode.INodeReference;
068import org.apache.hadoop.hdfs.server.namenode.INodeReference.DstReference;
069import org.apache.hadoop.hdfs.server.namenode.INodeReference.WithCount;
070import org.apache.hadoop.hdfs.server.namenode.INodeReference.WithName;
071import org.apache.hadoop.hdfs.server.namenode.INodeWithAdditionalFields;
072import org.apache.hadoop.hdfs.server.namenode.QuotaByStorageTypeEntry;
073import org.apache.hadoop.hdfs.server.namenode.SaveNamespaceContext;
074import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature.DirectoryDiff;
075import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature.DirectoryDiffList;
076import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot.Root;
077import org.apache.hadoop.hdfs.server.namenode.XAttrFeature;
078import org.apache.hadoop.hdfs.util.Diff.ListType;
079import org.apache.hadoop.hdfs.util.EnumCounters;
080
081import com.google.common.base.Preconditions;
082import com.google.protobuf.ByteString;
083
084@InterfaceAudience.Private
085public class FSImageFormatPBSnapshot {
086  /**
087   * Loading snapshot related information from protobuf based FSImage
088   */
089  public final static class Loader {
090    private final FSNamesystem fsn;
091    private final FSDirectory fsDir;
092    private final FSImageFormatProtobuf.Loader parent;
093    private final Map<Integer, Snapshot> snapshotMap;
094
095    public Loader(FSNamesystem fsn, FSImageFormatProtobuf.Loader parent) {
096      this.fsn = fsn;
097      this.fsDir = fsn.getFSDirectory();
098      this.snapshotMap = new HashMap<Integer, Snapshot>();
099      this.parent = parent;
100    }
101
102    /**
103     * The sequence of the ref node in refList must be strictly the same with
104     * the sequence in fsimage
105     */
106    public void loadINodeReferenceSection(InputStream in) throws IOException {
107      final List<INodeReference> refList = parent.getLoaderContext()
108          .getRefList();
109      while (true) {
110        INodeReferenceSection.INodeReference e = INodeReferenceSection
111            .INodeReference.parseDelimitedFrom(in);
112        if (e == null) {
113          break;
114        }
115        INodeReference ref = loadINodeReference(e);
116        refList.add(ref);
117      }
118    }
119
120    private INodeReference loadINodeReference(
121        INodeReferenceSection.INodeReference r) throws IOException {
122      long referredId = r.getReferredId();
123      INode referred = fsDir.getInode(referredId);
124      WithCount withCount = (WithCount) referred.getParentReference();
125      if (withCount == null) {
126        withCount = new INodeReference.WithCount(null, referred);
127      }
128      final INodeReference ref;
129      if (r.hasDstSnapshotId()) { // DstReference
130        ref = new INodeReference.DstReference(null, withCount,
131            r.getDstSnapshotId());
132      } else {
133        ref = new INodeReference.WithName(null, withCount, r.getName()
134            .toByteArray(), r.getLastSnapshotId());
135      }
136      return ref;
137    }
138
139    /**
140     * Load the snapshots section from fsimage. Also add snapshottable feature
141     * to snapshottable directories.
142     */
143    public void loadSnapshotSection(InputStream in) throws IOException {
144      SnapshotManager sm = fsn.getSnapshotManager();
145      SnapshotSection section = SnapshotSection.parseDelimitedFrom(in);
146      int snum = section.getNumSnapshots();
147      sm.setNumSnapshots(snum);
148      sm.setSnapshotCounter(section.getSnapshotCounter());
149      for (long sdirId : section.getSnapshottableDirList()) {
150        INodeDirectory dir = fsDir.getInode(sdirId).asDirectory();
151        if (!dir.isSnapshottable()) {
152          dir.addSnapshottableFeature();
153        } else {
154          // dir is root, and admin set root to snapshottable before
155          dir.setSnapshotQuota(DirectorySnapshottableFeature.SNAPSHOT_LIMIT);
156        }
157        sm.addSnapshottable(dir);
158      }
159      loadSnapshots(in, snum);
160    }
161
162    private void loadSnapshots(InputStream in, int size) throws IOException {
163      for (int i = 0; i < size; i++) {
164        SnapshotSection.Snapshot pbs = SnapshotSection.Snapshot
165            .parseDelimitedFrom(in);
166        INodeDirectory root = loadINodeDirectory(pbs.getRoot(),
167            parent.getLoaderContext());
168        int sid = pbs.getSnapshotId();
169        INodeDirectory parent = fsDir.getInode(root.getId()).asDirectory();
170        Snapshot snapshot = new Snapshot(sid, root, parent);
171        // add the snapshot to parent, since we follow the sequence of
172        // snapshotsByNames when saving, we do not need to sort when loading
173        parent.getDirectorySnapshottableFeature().addSnapshot(snapshot);
174        snapshotMap.put(sid, snapshot);
175      }
176    }
177
178    /**
179     * Load the snapshot diff section from fsimage.
180     */
181    public void loadSnapshotDiffSection(InputStream in) throws IOException {
182      final List<INodeReference> refList = parent.getLoaderContext()
183          .getRefList();
184      while (true) {
185        SnapshotDiffSection.DiffEntry entry = SnapshotDiffSection.DiffEntry
186            .parseDelimitedFrom(in);
187        if (entry == null) {
188          break;
189        }
190        long inodeId = entry.getInodeId();
191        INode inode = fsDir.getInode(inodeId);
192        SnapshotDiffSection.DiffEntry.Type type = entry.getType();
193        switch (type) {
194        case FILEDIFF:
195          loadFileDiffList(in, inode.asFile(), entry.getNumOfDiff());
196          break;
197        case DIRECTORYDIFF:
198          loadDirectoryDiffList(in, inode.asDirectory(), entry.getNumOfDiff(),
199              refList);
200          break;
201        }
202      }
203    }
204
205    /** Load FileDiff list for a file with snapshot feature */
206    private void loadFileDiffList(InputStream in, INodeFile file, int size)
207        throws IOException {
208      final FileDiffList diffs = new FileDiffList();
209      final LoaderContext state = parent.getLoaderContext();
210      for (int i = 0; i < size; i++) {
211        SnapshotDiffSection.FileDiff pbf = SnapshotDiffSection.FileDiff
212            .parseDelimitedFrom(in);
213        INodeFileAttributes copy = null;
214        if (pbf.hasSnapshotCopy()) {
215          INodeSection.INodeFile fileInPb = pbf.getSnapshotCopy();
216          PermissionStatus permission = loadPermission(
217              fileInPb.getPermission(), state.getStringTable());
218
219          AclFeature acl = null;
220          if (fileInPb.hasAcl()) {
221            int[] entries = AclEntryStatusFormat
222                .toInt(FSImageFormatPBINode.Loader.loadAclEntries(
223                    fileInPb.getAcl(), state.getStringTable()));
224            acl = new AclFeature(entries);
225          }
226          XAttrFeature xAttrs = null;
227          if (fileInPb.hasXAttrs()) {
228            xAttrs = new XAttrFeature(FSImageFormatPBINode.Loader.loadXAttrs(
229                fileInPb.getXAttrs(), state.getStringTable()));
230          }
231
232          copy = new INodeFileAttributes.SnapshotCopy(pbf.getName()
233              .toByteArray(), permission, acl, fileInPb.getModificationTime(),
234              fileInPb.getAccessTime(), (short) fileInPb.getReplication(),
235              fileInPb.getPreferredBlockSize(),
236              (byte)fileInPb.getStoragePolicyID(), 
237              xAttrs);
238        }
239
240        FileDiff diff = new FileDiff(pbf.getSnapshotId(), copy, null,
241            pbf.getFileSize());
242        List<BlockProto> bpl = pbf.getBlocksList();
243        BlockInfoContiguous[] blocks = new BlockInfoContiguous[bpl.size()];
244        for(int j = 0, e = bpl.size(); j < e; ++j) {
245          Block blk = PBHelper.convert(bpl.get(j));
246          BlockInfoContiguous storedBlock =  fsn.getBlockManager().getStoredBlock(blk);
247          if(storedBlock == null) {
248            storedBlock = fsn.getBlockManager().addBlockCollection(
249                new BlockInfoContiguous(blk, copy.getFileReplication()), file);
250          }
251          blocks[j] = storedBlock;
252        }
253        if(blocks.length > 0) {
254          diff.setBlocks(blocks);
255        }
256        diffs.addFirst(diff);
257      }
258      file.addSnapshotFeature(diffs);
259    }
260
261    /** Load the created list in a DirectoryDiff */
262    private List<INode> loadCreatedList(InputStream in, INodeDirectory dir,
263        int size) throws IOException {
264      List<INode> clist = new ArrayList<INode>(size);
265      for (long c = 0; c < size; c++) {
266        CreatedListEntry entry = CreatedListEntry.parseDelimitedFrom(in);
267        INode created = SnapshotFSImageFormat.loadCreated(entry.getName()
268            .toByteArray(), dir);
269        clist.add(created);
270      }
271      return clist;
272    }
273
274    private void addToDeletedList(INode dnode, INodeDirectory parent) {
275      dnode.setParent(parent);
276      if (dnode.isFile()) {
277        updateBlocksMap(dnode.asFile(), fsn.getBlockManager());
278      }
279    }
280
281    /**
282     * Load the deleted list in a DirectoryDiff
283     */
284    private List<INode> loadDeletedList(final List<INodeReference> refList,
285        InputStream in, INodeDirectory dir, List<Long> deletedNodes,
286        List<Integer> deletedRefNodes)
287        throws IOException {
288      List<INode> dlist = new ArrayList<INode>(deletedRefNodes.size()
289          + deletedNodes.size());
290      // load non-reference inodes
291      for (long deletedId : deletedNodes) {
292        INode deleted = fsDir.getInode(deletedId);
293        dlist.add(deleted);
294        addToDeletedList(deleted, dir);
295      }
296      // load reference nodes in the deleted list
297      for (int refId : deletedRefNodes) {
298        INodeReference deletedRef = refList.get(refId);
299        dlist.add(deletedRef);
300        addToDeletedList(deletedRef, dir);
301      }
302
303      Collections.sort(dlist, new Comparator<INode>() {
304        @Override
305        public int compare(INode n1, INode n2) {
306          return n1.compareTo(n2.getLocalNameBytes());
307        }
308      });
309      return dlist;
310    }
311
312    /** Load DirectoryDiff list for a directory with snapshot feature */
313    private void loadDirectoryDiffList(InputStream in, INodeDirectory dir,
314        int size, final List<INodeReference> refList) throws IOException {
315      if (!dir.isWithSnapshot()) {
316        dir.addSnapshotFeature(null);
317      }
318      DirectoryDiffList diffs = dir.getDiffs();
319      final LoaderContext state = parent.getLoaderContext();
320
321      for (int i = 0; i < size; i++) {
322        // load a directory diff
323        SnapshotDiffSection.DirectoryDiff diffInPb = SnapshotDiffSection.
324            DirectoryDiff.parseDelimitedFrom(in);
325        final int snapshotId = diffInPb.getSnapshotId();
326        final Snapshot snapshot = snapshotMap.get(snapshotId);
327        int childrenSize = diffInPb.getChildrenSize();
328        boolean useRoot = diffInPb.getIsSnapshotRoot();
329        INodeDirectoryAttributes copy = null;
330        if (useRoot) {
331          copy = snapshot.getRoot();
332        } else if (diffInPb.hasSnapshotCopy()) {
333          INodeSection.INodeDirectory dirCopyInPb = diffInPb.getSnapshotCopy();
334          final byte[] name = diffInPb.getName().toByteArray();
335          PermissionStatus permission = loadPermission(
336              dirCopyInPb.getPermission(), state.getStringTable());
337          AclFeature acl = null;
338          if (dirCopyInPb.hasAcl()) {
339            int[] entries = AclEntryStatusFormat
340                .toInt(FSImageFormatPBINode.Loader.loadAclEntries(
341                    dirCopyInPb.getAcl(), state.getStringTable()));
342            acl = new AclFeature(entries);
343          }
344          XAttrFeature xAttrs = null;
345          if (dirCopyInPb.hasXAttrs()) {
346            xAttrs = new XAttrFeature(FSImageFormatPBINode.Loader.loadXAttrs(
347                dirCopyInPb.getXAttrs(), state.getStringTable()));
348          }
349
350          long modTime = dirCopyInPb.getModificationTime();
351          boolean noQuota = dirCopyInPb.getNsQuota() == -1
352              && dirCopyInPb.getDsQuota() == -1
353              && (!dirCopyInPb.hasTypeQuotas());
354
355          if (noQuota) {
356            copy = new INodeDirectoryAttributes.SnapshotCopy(name,
357              permission, acl, modTime, xAttrs);
358          } else {
359            EnumCounters<StorageType> typeQuotas = null;
360            if (dirCopyInPb.hasTypeQuotas()) {
361              ImmutableList<QuotaByStorageTypeEntry> qes =
362                  FSImageFormatPBINode.Loader.loadQuotaByStorageTypeEntries(
363                      dirCopyInPb.getTypeQuotas());
364              typeQuotas = new EnumCounters<StorageType>(StorageType.class,
365                  HdfsConstants.QUOTA_RESET);
366              for (QuotaByStorageTypeEntry qe : qes) {
367                if (qe.getQuota() >= 0 && qe.getStorageType() != null &&
368                    qe.getStorageType().supportTypeQuota()) {
369                  typeQuotas.set(qe.getStorageType(), qe.getQuota());
370                }
371              }
372            }
373            copy = new INodeDirectoryAttributes.CopyWithQuota(name, permission,
374                acl, modTime, dirCopyInPb.getNsQuota(),
375                dirCopyInPb.getDsQuota(), typeQuotas, xAttrs);
376          }
377        }
378        // load created list
379        List<INode> clist = loadCreatedList(in, dir,
380            diffInPb.getCreatedListSize());
381        // load deleted list
382        List<INode> dlist = loadDeletedList(refList, in, dir,
383            diffInPb.getDeletedINodeList(), diffInPb.getDeletedINodeRefList());
384        // create the directory diff
385        DirectoryDiff diff = new DirectoryDiff(snapshotId, copy, null,
386            childrenSize, clist, dlist, useRoot);
387        diffs.addFirst(diff);
388      }
389    }
390  }
391
392  /**
393   * Saving snapshot related information to protobuf based FSImage
394   */
395  public final static class Saver {
396    private final FSNamesystem fsn;
397    private final FileSummary.Builder headers;
398    private final FSImageFormatProtobuf.Saver parent;
399    private final SaveNamespaceContext context;
400
401    public Saver(FSImageFormatProtobuf.Saver parent,
402        FileSummary.Builder headers, SaveNamespaceContext context,
403        FSNamesystem fsn) {
404      this.parent = parent;
405      this.headers = headers;
406      this.context = context;
407      this.fsn = fsn;
408    }
409
410    /**
411     * save all the snapshottable directories and snapshots to fsimage
412     */
413    public void serializeSnapshotSection(OutputStream out) throws IOException {
414      SnapshotManager sm = fsn.getSnapshotManager();
415      SnapshotSection.Builder b = SnapshotSection.newBuilder()
416          .setSnapshotCounter(sm.getSnapshotCounter())
417          .setNumSnapshots(sm.getNumSnapshots());
418
419      INodeDirectory[] snapshottables = sm.getSnapshottableDirs();
420      for (INodeDirectory sdir : snapshottables) {
421        b.addSnapshottableDir(sdir.getId());
422      }
423      b.build().writeDelimitedTo(out);
424      int i = 0;
425      for(INodeDirectory sdir : snapshottables) {
426        for (Snapshot s : sdir.getDirectorySnapshottableFeature()
427            .getSnapshotList()) {
428          Root sroot = s.getRoot();
429          SnapshotSection.Snapshot.Builder sb = SnapshotSection.Snapshot
430              .newBuilder().setSnapshotId(s.getId());
431          INodeSection.INodeDirectory.Builder db = buildINodeDirectory(sroot,
432              parent.getSaverContext());
433          INodeSection.INode r = INodeSection.INode.newBuilder()
434              .setId(sroot.getId())
435              .setType(INodeSection.INode.Type.DIRECTORY)
436              .setName(ByteString.copyFrom(sroot.getLocalNameBytes()))
437              .setDirectory(db).build();
438          sb.setRoot(r).build().writeDelimitedTo(out);
439          i++;
440          if (i % FSImageFormatProtobuf.Saver.CHECK_CANCEL_INTERVAL == 0) {
441            context.checkCancelled();
442          }
443        }
444      }
445      Preconditions.checkState(i == sm.getNumSnapshots());
446      parent.commitSection(headers, FSImageFormatProtobuf.SectionName.SNAPSHOT);
447    }
448
449    /**
450     * This can only be called after serializing both INode_Dir and SnapshotDiff
451     */
452    public void serializeINodeReferenceSection(OutputStream out)
453        throws IOException {
454      final List<INodeReference> refList = parent.getSaverContext()
455          .getRefList();
456      for (INodeReference ref : refList) {
457        INodeReferenceSection.INodeReference.Builder rb = buildINodeReference(ref);
458        rb.build().writeDelimitedTo(out);
459      }
460      parent.commitSection(headers, SectionName.INODE_REFERENCE);
461    }
462
463    private INodeReferenceSection.INodeReference.Builder buildINodeReference(
464        INodeReference ref) throws IOException {
465      INodeReferenceSection.INodeReference.Builder rb =
466          INodeReferenceSection.INodeReference.newBuilder().
467            setReferredId(ref.getId());
468      if (ref instanceof WithName) {
469        rb.setLastSnapshotId(((WithName) ref).getLastSnapshotId()).setName(
470            ByteString.copyFrom(ref.getLocalNameBytes()));
471      } else if (ref instanceof DstReference) {
472        rb.setDstSnapshotId(ref.getDstSnapshotId());
473      }
474      return rb;
475    }
476
477    /**
478     * save all the snapshot diff to fsimage
479     */
480    public void serializeSnapshotDiffSection(OutputStream out)
481        throws IOException {
482      INodeMap inodesMap = fsn.getFSDirectory().getINodeMap();
483      final List<INodeReference> refList = parent.getSaverContext()
484          .getRefList();
485      int i = 0;
486      Iterator<INodeWithAdditionalFields> iter = inodesMap.getMapIterator();
487      while (iter.hasNext()) {
488        INodeWithAdditionalFields inode = iter.next();
489        if (inode.isFile()) {
490          serializeFileDiffList(inode.asFile(), out);
491        } else if (inode.isDirectory()) {
492          serializeDirDiffList(inode.asDirectory(), refList, out);
493        }
494        ++i;
495        if (i % FSImageFormatProtobuf.Saver.CHECK_CANCEL_INTERVAL == 0) {
496          context.checkCancelled();
497        }
498      }
499      parent.commitSection(headers,
500          FSImageFormatProtobuf.SectionName.SNAPSHOT_DIFF);
501    }
502
503    private void serializeFileDiffList(INodeFile file, OutputStream out)
504        throws IOException {
505      FileWithSnapshotFeature sf = file.getFileWithSnapshotFeature();
506      if (sf != null) {
507        List<FileDiff> diffList = sf.getDiffs().asList();
508        SnapshotDiffSection.DiffEntry entry = SnapshotDiffSection.DiffEntry
509            .newBuilder().setInodeId(file.getId()).setType(Type.FILEDIFF)
510            .setNumOfDiff(diffList.size()).build();
511        entry.writeDelimitedTo(out);
512        for (int i = diffList.size() - 1; i >= 0; i--) {
513          FileDiff diff = diffList.get(i);
514          SnapshotDiffSection.FileDiff.Builder fb = SnapshotDiffSection.FileDiff
515              .newBuilder().setSnapshotId(diff.getSnapshotId())
516              .setFileSize(diff.getFileSize());
517          if(diff.getBlocks() != null) {
518            for(Block block : diff.getBlocks()) {
519              fb.addBlocks(PBHelper.convert(block));
520            }
521          }
522          INodeFileAttributes copy = diff.snapshotINode;
523          if (copy != null) {
524            fb.setName(ByteString.copyFrom(copy.getLocalNameBytes()))
525                .setSnapshotCopy(buildINodeFile(copy, parent.getSaverContext()));
526          }
527          fb.build().writeDelimitedTo(out);
528        }
529      }
530    }
531
532    private void saveCreatedList(List<INode> created, OutputStream out)
533        throws IOException {
534      // local names of the created list member
535      for (INode c : created) {
536        SnapshotDiffSection.CreatedListEntry.newBuilder()
537            .setName(ByteString.copyFrom(c.getLocalNameBytes())).build()
538            .writeDelimitedTo(out);
539      }
540    }
541
542    private void serializeDirDiffList(INodeDirectory dir,
543        final List<INodeReference> refList, OutputStream out)
544        throws IOException {
545      DirectoryWithSnapshotFeature sf = dir.getDirectoryWithSnapshotFeature();
546      if (sf != null) {
547        List<DirectoryDiff> diffList = sf.getDiffs().asList();
548        SnapshotDiffSection.DiffEntry entry = SnapshotDiffSection.DiffEntry
549            .newBuilder().setInodeId(dir.getId()).setType(Type.DIRECTORYDIFF)
550            .setNumOfDiff(diffList.size()).build();
551        entry.writeDelimitedTo(out);
552        for (int i = diffList.size() - 1; i >= 0; i--) { // reverse order!
553          DirectoryDiff diff = diffList.get(i);
554          SnapshotDiffSection.DirectoryDiff.Builder db = SnapshotDiffSection.
555              DirectoryDiff.newBuilder().setSnapshotId(diff.getSnapshotId())
556                           .setChildrenSize(diff.getChildrenSize())
557                           .setIsSnapshotRoot(diff.isSnapshotRoot());
558          INodeDirectoryAttributes copy = diff.snapshotINode;
559          if (!diff.isSnapshotRoot() && copy != null) {
560            db.setName(ByteString.copyFrom(copy.getLocalNameBytes()))
561                .setSnapshotCopy(
562                    buildINodeDirectory(copy, parent.getSaverContext()));
563          }
564          // process created list and deleted list
565          List<INode> created = diff.getChildrenDiff()
566              .getList(ListType.CREATED);
567          db.setCreatedListSize(created.size());
568          List<INode> deleted = diff.getChildrenDiff().getList(ListType.DELETED);
569          for (INode d : deleted) {
570            if (d.isReference()) {
571              refList.add(d.asReference());
572              db.addDeletedINodeRef(refList.size() - 1);
573            } else {
574              db.addDeletedINode(d.getId());
575            }
576          }
577          db.build().writeDelimitedTo(out);
578          saveCreatedList(created, out);
579        }
580      }
581    }
582  }
583
584  private FSImageFormatPBSnapshot(){}
585}