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 java.util.zip.CheckedInputStream;
021import java.util.zip.Checksum;
022import java.util.EnumMap;
023
024import org.apache.hadoop.fs.ChecksumException;
025import org.apache.hadoop.classification.InterfaceAudience;
026import org.apache.hadoop.classification.InterfaceStability;
027import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
028import org.apache.hadoop.fs.Options.Rename;
029import org.apache.hadoop.fs.permission.FsPermission;
030import org.apache.hadoop.fs.permission.PermissionStatus;
031import org.apache.hadoop.hdfs.protocol.Block;
032import org.apache.hadoop.hdfs.protocol.DatanodeID;
033import org.apache.hadoop.hdfs.protocol.LayoutVersion;
034import org.apache.hadoop.hdfs.protocol.LayoutVersion.Feature;
035import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor;
036import org.apache.hadoop.hdfs.server.common.GenerationStamp;
037import org.apache.hadoop.util.PureJavaCrc32;
038
039import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.*;
040import org.apache.hadoop.security.token.delegation.DelegationKey;
041import org.apache.hadoop.io.BytesWritable;
042import org.apache.hadoop.io.DataOutputBuffer;
043import org.apache.hadoop.io.ArrayWritable;
044import org.apache.hadoop.io.Writable;
045import org.apache.hadoop.io.WritableFactories;
046import org.apache.hadoop.io.WritableFactory;
047import org.apache.hadoop.hdfs.DeprecatedUTF8;
048
049import java.io.DataInput;
050import java.io.DataOutput;
051import java.io.DataInputStream;
052import java.io.DataOutputStream;
053import java.io.IOException;
054import java.io.EOFException;
055
056/**
057 * Helper classes for reading the ops from an InputStream.
058 * All ops derive from FSEditLogOp and are only
059 * instantiated from Reader#readOp()
060 */
061@InterfaceAudience.Private
062@InterfaceStability.Unstable
063public abstract class FSEditLogOp {
064  final FSEditLogOpCodes opCode;
065  long txid;
066
067
068  @SuppressWarnings("deprecation")
069  private static ThreadLocal<EnumMap<FSEditLogOpCodes, FSEditLogOp>> opInstances =
070    new ThreadLocal<EnumMap<FSEditLogOpCodes, FSEditLogOp>>() {
071      @Override
072      protected EnumMap<FSEditLogOpCodes, FSEditLogOp> initialValue() {
073        EnumMap<FSEditLogOpCodes, FSEditLogOp> instances 
074          = new EnumMap<FSEditLogOpCodes, FSEditLogOp>(FSEditLogOpCodes.class);
075        instances.put(OP_ADD, new AddOp());
076        instances.put(OP_CLOSE, new CloseOp());
077        instances.put(OP_SET_REPLICATION, new SetReplicationOp());
078        instances.put(OP_CONCAT_DELETE, new ConcatDeleteOp());
079        instances.put(OP_RENAME_OLD, new RenameOldOp());
080        instances.put(OP_DELETE, new DeleteOp());
081        instances.put(OP_MKDIR, new MkdirOp());
082        instances.put(OP_SET_GENSTAMP, new SetGenstampOp());
083        instances.put(OP_DATANODE_ADD, new DatanodeAddOp());
084        instances.put(OP_DATANODE_REMOVE, new DatanodeRemoveOp());
085        instances.put(OP_SET_PERMISSIONS, new SetPermissionsOp());
086        instances.put(OP_SET_OWNER, new SetOwnerOp());
087        instances.put(OP_SET_NS_QUOTA, new SetNSQuotaOp());
088        instances.put(OP_CLEAR_NS_QUOTA, new ClearNSQuotaOp());
089        instances.put(OP_SET_QUOTA, new SetQuotaOp());
090        instances.put(OP_TIMES, new TimesOp());
091        instances.put(OP_SYMLINK, new SymlinkOp());
092        instances.put(OP_RENAME, new RenameOp());
093        instances.put(OP_REASSIGN_LEASE, new ReassignLeaseOp());
094        instances.put(OP_GET_DELEGATION_TOKEN, new GetDelegationTokenOp());
095        instances.put(OP_RENEW_DELEGATION_TOKEN, new RenewDelegationTokenOp());
096        instances.put(OP_CANCEL_DELEGATION_TOKEN, 
097                      new CancelDelegationTokenOp());
098        instances.put(OP_UPDATE_MASTER_KEY, new UpdateMasterKeyOp());
099        instances.put(OP_START_LOG_SEGMENT,
100                      new LogSegmentOp(OP_START_LOG_SEGMENT));
101        instances.put(OP_END_LOG_SEGMENT,
102                      new LogSegmentOp(OP_END_LOG_SEGMENT));
103        return instances;
104      }
105  };
106
107  /**
108   * Constructor for an EditLog Op. EditLog ops cannot be constructed
109   * directly, but only through Reader#readOp.
110   */
111  private FSEditLogOp(FSEditLogOpCodes opCode) {
112    this.opCode = opCode;
113    this.txid = 0;
114  }
115
116  public void setTransactionId(long txid) {
117    this.txid = txid;
118  }
119
120  abstract void readFields(DataInputStream in, int logVersion)
121      throws IOException;
122
123  abstract void writeFields(DataOutputStream out)
124      throws IOException;
125
126  @SuppressWarnings("unchecked")
127  static abstract class AddCloseOp extends FSEditLogOp {
128    int length;
129    String path;
130    short replication;
131    long mtime;
132    long atime;
133    long blockSize;
134    Block[] blocks;
135    PermissionStatus permissions;
136    String clientName;
137    String clientMachine;
138    //final DatanodeDescriptor[] dataNodeDescriptors; UNUSED
139
140    private AddCloseOp(FSEditLogOpCodes opCode) {
141      super(opCode);
142      assert(opCode == OP_ADD || opCode == OP_CLOSE);
143    }
144
145    <T extends AddCloseOp> T setPath(String path) {
146      this.path = path;
147      return (T)this;
148    }
149
150    <T extends AddCloseOp> T setReplication(short replication) {
151      this.replication = replication;
152      return (T)this;
153    }
154
155    <T extends AddCloseOp> T setModificationTime(long mtime) {
156      this.mtime = mtime;
157      return (T)this;
158    }
159
160    <T extends AddCloseOp> T setAccessTime(long atime) {
161      this.atime = atime;
162      return (T)this;
163    }
164
165    <T extends AddCloseOp> T setBlockSize(long blockSize) {
166      this.blockSize = blockSize;
167      return (T)this;
168    }
169
170    <T extends AddCloseOp> T setBlocks(Block[] blocks) {
171      this.blocks = blocks;
172      return (T)this;
173    }
174
175    <T extends AddCloseOp> T setPermissionStatus(PermissionStatus permissions) {
176      this.permissions = permissions;
177      return (T)this;
178    }
179
180    <T extends AddCloseOp> T setClientName(String clientName) {
181      this.clientName = clientName;
182      return (T)this;
183    }
184
185    <T extends AddCloseOp> T setClientMachine(String clientMachine) {
186      this.clientMachine = clientMachine;
187      return (T)this;
188    }
189
190    @Override 
191    void writeFields(DataOutputStream out) throws IOException {
192      FSImageSerialization.writeString(path, out);
193      FSImageSerialization.writeShort(replication, out);
194      FSImageSerialization.writeLong(mtime, out);
195      FSImageSerialization.writeLong(atime, out);
196      FSImageSerialization.writeLong(blockSize, out);
197      new ArrayWritable(Block.class, blocks).write(out);
198      permissions.write(out);
199
200      if (this.opCode == OP_ADD) {
201        FSImageSerialization.writeString(clientName,out);
202        FSImageSerialization.writeString(clientMachine,out);
203      }
204    }
205
206    @Override
207    void readFields(DataInputStream in, int logVersion)
208        throws IOException {
209      // versions > 0 support per file replication
210      // get name and replication
211      if (!LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
212        this.length = in.readInt();
213      }
214      if (-7 == logVersion && length != 3||
215          -17 < logVersion && logVersion < -7 && length != 4 ||
216          (logVersion <= -17 && length != 5 && !LayoutVersion.supports(
217              Feature.EDITLOG_OP_OPTIMIZATION, logVersion))) {
218        throw new IOException("Incorrect data format."  +
219                              " logVersion is " + logVersion +
220                              " but writables.length is " +
221                              length + ". ");
222      }
223      this.path = FSImageSerialization.readString(in);
224
225      if (LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
226        this.replication = FSImageSerialization.readShort(in);
227        this.mtime = FSImageSerialization.readLong(in);
228      } else {
229        this.replication = readShort(in);
230        this.mtime = readLong(in);
231      }
232
233      if (LayoutVersion.supports(Feature.FILE_ACCESS_TIME, logVersion)) {
234        if (LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
235          this.atime = FSImageSerialization.readLong(in);
236        } else {
237          this.atime = readLong(in);
238        }
239      } else {
240        this.atime = 0;
241      }
242      if (logVersion < -7) {
243        if (LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
244          this.blockSize = FSImageSerialization.readLong(in);
245        } else {
246          this.blockSize = readLong(in);
247        }
248      } else {
249        this.blockSize = 0;
250      }
251
252      // get blocks
253      this.blocks = readBlocks(in, logVersion);
254
255      if (logVersion <= -11) {
256        this.permissions = PermissionStatus.read(in);
257      } else {
258        this.permissions = null;
259      }
260
261      // clientname, clientMachine and block locations of last block.
262      if (this.opCode == OP_ADD && logVersion <= -12) {
263        this.clientName = FSImageSerialization.readString(in);
264        this.clientMachine = FSImageSerialization.readString(in);
265        if (-13 <= logVersion) {
266          readDatanodeDescriptorArray(in);
267        }
268      } else {
269        this.clientName = "";
270        this.clientMachine = "";
271      }
272    }
273
274    /** This method is defined for compatibility reason. */
275    private static DatanodeDescriptor[] readDatanodeDescriptorArray(DataInput in)
276        throws IOException {
277      DatanodeDescriptor[] locations = new DatanodeDescriptor[in.readInt()];
278        for (int i = 0; i < locations.length; i++) {
279          locations[i] = new DatanodeDescriptor();
280          locations[i].readFieldsFromFSEditLog(in);
281        }
282        return locations;
283    }
284
285    private static Block[] readBlocks(
286        DataInputStream in,
287        int logVersion) throws IOException {
288      int numBlocks = in.readInt();
289      Block[] blocks = new Block[numBlocks];
290      for (int i = 0; i < numBlocks; i++) {
291        Block blk = new Block();
292        if (logVersion <= -14) {
293          blk.readFields(in);
294        } else {
295          BlockTwo oldblk = new BlockTwo();
296          oldblk.readFields(in);
297          blk.set(oldblk.blkid, oldblk.len,
298                  GenerationStamp.GRANDFATHER_GENERATION_STAMP);
299        }
300        blocks[i] = blk;
301      }
302      return blocks;
303    }
304  }
305
306  static class AddOp extends AddCloseOp {
307    private AddOp() {
308      super(OP_ADD);
309    }
310
311    static AddOp getInstance() {
312      return (AddOp)opInstances.get().get(OP_ADD);
313    }
314  }
315
316  static class CloseOp extends AddCloseOp {
317    private CloseOp() {
318      super(OP_CLOSE);
319    }
320
321    static CloseOp getInstance() {
322      return (CloseOp)opInstances.get().get(OP_CLOSE);
323    }
324  }
325
326  static class SetReplicationOp extends FSEditLogOp {
327    String path;
328    short replication;
329
330    private SetReplicationOp() {
331      super(OP_SET_REPLICATION);
332    }
333
334    static SetReplicationOp getInstance() {
335      return (SetReplicationOp)opInstances.get()
336        .get(OP_SET_REPLICATION);
337    }
338
339    SetReplicationOp setPath(String path) {
340      this.path = path;
341      return this;
342    }
343
344    SetReplicationOp setReplication(short replication) {
345      this.replication = replication;
346      return this;
347    }
348
349    @Override 
350    void writeFields(DataOutputStream out) throws IOException {
351      FSImageSerialization.writeString(path, out);
352      FSImageSerialization.writeShort(replication, out);
353    }
354    
355    @Override
356    void readFields(DataInputStream in, int logVersion)
357        throws IOException {
358      this.path = FSImageSerialization.readString(in);
359      if (LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
360        this.replication = FSImageSerialization.readShort(in);
361      } else {
362        this.replication = readShort(in);
363      }
364    }
365  }
366
367  static class ConcatDeleteOp extends FSEditLogOp {
368    int length;
369    String trg;
370    String[] srcs;
371    long timestamp;
372
373    private ConcatDeleteOp() {
374      super(OP_CONCAT_DELETE);
375    }
376
377    static ConcatDeleteOp getInstance() {
378      return (ConcatDeleteOp)opInstances.get()
379        .get(OP_CONCAT_DELETE);
380    }
381
382    ConcatDeleteOp setTarget(String trg) {
383      this.trg = trg;
384      return this;
385    }
386
387    ConcatDeleteOp setSources(String[] srcs) {
388      this.srcs = srcs;
389      return this;
390    }
391
392    ConcatDeleteOp setTimestamp(long timestamp) {
393      this.timestamp = timestamp;
394      return this;
395    }
396
397    @Override 
398    void writeFields(DataOutputStream out) throws IOException {
399      FSImageSerialization.writeString(trg, out);
400            
401      DeprecatedUTF8 info[] = new DeprecatedUTF8[srcs.length];
402      int idx = 0;
403      for(int i=0; i<srcs.length; i++) {
404        info[idx++] = new DeprecatedUTF8(srcs[i]);
405      }
406      new ArrayWritable(DeprecatedUTF8.class, info).write(out);
407
408      FSImageSerialization.writeLong(timestamp, out);
409    }
410
411    @Override
412    void readFields(DataInputStream in, int logVersion)
413        throws IOException {
414      if (!LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
415        this.length = in.readInt();
416        if (length < 3) { // trg, srcs.., timestamp
417          throw new IOException("Incorrect data format. "
418              + "Concat delete operation.");
419        }
420      }
421      this.trg = FSImageSerialization.readString(in);
422      int srcSize = 0;
423      if (LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
424        srcSize = in.readInt();
425      } else {
426        srcSize = this.length - 1 - 1; // trg and timestamp
427      }
428      this.srcs = new String [srcSize];
429      for(int i=0; i<srcSize;i++) {
430        srcs[i]= FSImageSerialization.readString(in);
431      }
432      
433      if (LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
434        this.timestamp = FSImageSerialization.readLong(in);
435      } else {
436        this.timestamp = readLong(in);
437      }
438    }
439  }
440
441  static class RenameOldOp extends FSEditLogOp {
442    int length;
443    String src;
444    String dst;
445    long timestamp;
446
447    private RenameOldOp() {
448      super(OP_RENAME_OLD);
449    }
450
451    static RenameOldOp getInstance() {
452      return (RenameOldOp)opInstances.get()
453        .get(OP_RENAME_OLD);
454    }
455
456    RenameOldOp setSource(String src) {
457      this.src = src;
458      return this;
459    }
460
461    RenameOldOp setDestination(String dst) {
462      this.dst = dst;
463      return this;
464    }
465
466    RenameOldOp setTimestamp(long timestamp) {
467      this.timestamp = timestamp;
468      return this;
469    }
470
471    @Override 
472    void writeFields(DataOutputStream out) throws IOException {
473      FSImageSerialization.writeString(src, out);
474      FSImageSerialization.writeString(dst, out);
475      FSImageSerialization.writeLong(timestamp, out);
476    }
477
478    @Override
479    void readFields(DataInputStream in, int logVersion)
480        throws IOException {
481      if (!LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
482        this.length = in.readInt();
483        if (this.length != 3) {
484          throw new IOException("Incorrect data format. "
485              + "Old rename operation.");
486        }
487      }
488      this.src = FSImageSerialization.readString(in);
489      this.dst = FSImageSerialization.readString(in);
490      if (LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
491        this.timestamp = FSImageSerialization.readLong(in);
492      } else {
493        this.timestamp = readLong(in);
494      }
495    }
496  }
497
498  static class DeleteOp extends FSEditLogOp {
499    int length;
500    String path;
501    long timestamp;
502
503    private DeleteOp() {
504      super(OP_DELETE);
505    }
506
507    static DeleteOp getInstance() {
508      return (DeleteOp)opInstances.get()
509        .get(OP_DELETE);
510    }
511
512    DeleteOp setPath(String path) {
513      this.path = path;
514      return this;
515    }
516
517    DeleteOp setTimestamp(long timestamp) {
518      this.timestamp = timestamp;
519      return this;
520    }
521
522    @Override 
523    void writeFields(DataOutputStream out) throws IOException {
524      FSImageSerialization.writeString(path, out);
525      FSImageSerialization.writeLong(timestamp, out);
526    }
527
528    @Override
529    void readFields(DataInputStream in, int logVersion)
530        throws IOException {
531      if (!LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
532        this.length = in.readInt();
533        if (this.length != 2) {
534          throw new IOException("Incorrect data format. " + "delete operation.");
535        }
536      }
537      this.path = FSImageSerialization.readString(in);
538      if (LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
539        this.timestamp = FSImageSerialization.readLong(in);
540      } else {
541        this.timestamp = readLong(in);
542      }
543    }
544  }
545
546  static class MkdirOp extends FSEditLogOp {
547    int length;
548    String path;
549    long timestamp;
550    PermissionStatus permissions;
551
552    private MkdirOp() {
553      super(OP_MKDIR);
554    }
555    
556    static MkdirOp getInstance() {
557      return (MkdirOp)opInstances.get()
558        .get(OP_MKDIR);
559    }
560
561    MkdirOp setPath(String path) {
562      this.path = path;
563      return this;
564    }
565
566    MkdirOp setTimestamp(long timestamp) {
567      this.timestamp = timestamp;
568      return this;
569    }
570
571    MkdirOp setPermissionStatus(PermissionStatus permissions) {
572      this.permissions = permissions;
573      return this;
574    }
575
576    @Override 
577    void writeFields(DataOutputStream out) throws IOException {
578      FSImageSerialization.writeString(path, out);
579      FSImageSerialization.writeLong(timestamp, out); // mtime
580      FSImageSerialization.writeLong(timestamp, out); // atime, unused at this
581      permissions.write(out);
582    }
583    
584    @Override
585    void readFields(DataInputStream in, int logVersion)
586        throws IOException {
587
588      if (!LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
589        this.length = in.readInt();
590      }
591      if (-17 < logVersion && length != 2 ||
592          logVersion <= -17 && length != 3
593          && !LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
594        throw new IOException("Incorrect data format. "
595                              + "Mkdir operation.");
596      }
597      this.path = FSImageSerialization.readString(in);
598      if (LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
599        this.timestamp = FSImageSerialization.readLong(in);
600      } else {
601        this.timestamp = readLong(in);
602      }
603
604      // The disk format stores atimes for directories as well.
605      // However, currently this is not being updated/used because of
606      // performance reasons.
607      if (LayoutVersion.supports(Feature.FILE_ACCESS_TIME, logVersion)) {
608        /* unused this.atime = */
609        if (LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
610          FSImageSerialization.readLong(in);
611        } else {
612          readLong(in);
613        }
614      }
615
616      if (logVersion <= -11) {
617        this.permissions = PermissionStatus.read(in);
618      } else {
619        this.permissions = null;
620      }
621    }
622  }
623
624  static class SetGenstampOp extends FSEditLogOp {
625    long genStamp;
626
627    private SetGenstampOp() {
628      super(OP_SET_GENSTAMP);
629    }
630
631    static SetGenstampOp getInstance() {
632      return (SetGenstampOp)opInstances.get()
633        .get(OP_SET_GENSTAMP);
634    }
635
636    SetGenstampOp setGenerationStamp(long genStamp) {
637      this.genStamp = genStamp;
638      return this;
639    }
640    
641    @Override 
642    void writeFields(DataOutputStream out) throws IOException {
643      FSImageSerialization.writeLong(genStamp, out);
644    }
645    
646    @Override
647    void readFields(DataInputStream in, int logVersion)
648        throws IOException {
649      this.genStamp = FSImageSerialization.readLong(in);
650    }
651  }
652
653  @SuppressWarnings("deprecation")
654  static class DatanodeAddOp extends FSEditLogOp {
655    private DatanodeAddOp() {
656      super(OP_DATANODE_ADD);
657    }
658
659    static DatanodeAddOp getInstance() {
660      return (DatanodeAddOp)opInstances.get()
661        .get(OP_DATANODE_ADD);
662    }
663
664    @Override 
665    void writeFields(DataOutputStream out) throws IOException {
666      throw new IOException("Deprecated, should not write");
667    }
668
669    @Override
670    void readFields(DataInputStream in, int logVersion)
671        throws IOException {
672      //Datanodes are not persistent any more.
673      FSImageSerialization.DatanodeImage.skipOne(in);
674    }
675  }
676
677  @SuppressWarnings("deprecation")
678  static class DatanodeRemoveOp extends FSEditLogOp {
679    private DatanodeRemoveOp() {
680      super(OP_DATANODE_REMOVE);
681    }
682
683    static DatanodeRemoveOp getInstance() {
684      return (DatanodeRemoveOp)opInstances.get()
685        .get(OP_DATANODE_REMOVE);
686    }
687
688    @Override 
689    void writeFields(DataOutputStream out) throws IOException {
690      throw new IOException("Deprecated, should not write");
691    }
692
693    @Override
694    void readFields(DataInputStream in, int logVersion)
695        throws IOException {
696      DatanodeID nodeID = new DatanodeID();
697      nodeID.readFields(in);
698      //Datanodes are not persistent any more.
699    }
700  }
701
702  static class SetPermissionsOp extends FSEditLogOp {
703    String src;
704    FsPermission permissions;
705
706    private SetPermissionsOp() {
707      super(OP_SET_PERMISSIONS);
708    }
709
710    static SetPermissionsOp getInstance() {
711      return (SetPermissionsOp)opInstances.get()
712        .get(OP_SET_PERMISSIONS);
713    }
714
715    SetPermissionsOp setSource(String src) {
716      this.src = src;
717      return this;
718    }
719
720    SetPermissionsOp setPermissions(FsPermission permissions) {
721      this.permissions = permissions;
722      return this;
723    }
724
725    @Override 
726    void writeFields(DataOutputStream out) throws IOException {
727      FSImageSerialization.writeString(src, out);
728      permissions.write(out);
729     }
730 
731    @Override
732    void readFields(DataInputStream in, int logVersion)
733        throws IOException {
734      this.src = FSImageSerialization.readString(in);
735      this.permissions = FsPermission.read(in);
736    }
737  }
738
739  static class SetOwnerOp extends FSEditLogOp {
740    String src;
741    String username;
742    String groupname;
743
744    private SetOwnerOp() {
745      super(OP_SET_OWNER);
746    }
747
748    static SetOwnerOp getInstance() {
749      return (SetOwnerOp)opInstances.get()
750        .get(OP_SET_OWNER);
751    }
752
753    SetOwnerOp setSource(String src) {
754      this.src = src;
755      return this;
756    }
757
758    SetOwnerOp setUser(String username) {
759      this.username = username;
760      return this;
761    }
762
763    SetOwnerOp setGroup(String groupname) {
764      this.groupname = groupname;
765      return this;
766    }
767
768    @Override 
769    void writeFields(DataOutputStream out) throws IOException {
770      FSImageSerialization.writeString(src, out);
771      FSImageSerialization.writeString(username == null ? "" : username, out);
772      FSImageSerialization.writeString(groupname == null ? "" : groupname, out);
773    }
774
775    @Override
776    void readFields(DataInputStream in, int logVersion)
777        throws IOException {
778      this.src = FSImageSerialization.readString(in);
779      this.username = FSImageSerialization.readString_EmptyAsNull(in);
780      this.groupname = FSImageSerialization.readString_EmptyAsNull(in);
781    }
782  }
783
784  static class SetNSQuotaOp extends FSEditLogOp {
785    String src;
786    long nsQuota;
787
788    private SetNSQuotaOp() {
789      super(OP_SET_NS_QUOTA);
790    }
791
792    static SetNSQuotaOp getInstance() {
793      return (SetNSQuotaOp)opInstances.get()
794        .get(OP_SET_NS_QUOTA);
795    }
796
797    @Override 
798    void writeFields(DataOutputStream out) throws IOException {
799      throw new IOException("Deprecated");      
800    }
801
802    @Override
803    void readFields(DataInputStream in, int logVersion)
804        throws IOException {
805      this.src = FSImageSerialization.readString(in);
806      this.nsQuota = FSImageSerialization.readLong(in);
807    }
808  }
809
810  static class ClearNSQuotaOp extends FSEditLogOp {
811    String src;
812
813    private ClearNSQuotaOp() {
814      super(OP_CLEAR_NS_QUOTA);
815    }
816
817    static ClearNSQuotaOp getInstance() {
818      return (ClearNSQuotaOp)opInstances.get()
819        .get(OP_CLEAR_NS_QUOTA);
820    }
821
822    @Override 
823    void writeFields(DataOutputStream out) throws IOException {
824      throw new IOException("Deprecated");      
825    }
826
827    @Override
828    void readFields(DataInputStream in, int logVersion)
829        throws IOException {
830      this.src = FSImageSerialization.readString(in);
831    }
832  }
833
834  static class SetQuotaOp extends FSEditLogOp {
835    String src;
836    long nsQuota;
837    long dsQuota;
838
839    private SetQuotaOp() {
840      super(OP_SET_QUOTA);
841    }
842
843    static SetQuotaOp getInstance() {
844      return (SetQuotaOp)opInstances.get()
845        .get(OP_SET_QUOTA);
846    }
847
848    SetQuotaOp setSource(String src) {
849      this.src = src;
850      return this;
851    }
852
853    SetQuotaOp setNSQuota(long nsQuota) {
854      this.nsQuota = nsQuota;
855      return this;
856    }
857
858    SetQuotaOp setDSQuota(long dsQuota) {
859      this.dsQuota = dsQuota;
860      return this;
861    }
862
863    @Override 
864    void writeFields(DataOutputStream out) throws IOException {
865      FSImageSerialization.writeString(src, out);
866      FSImageSerialization.writeLong(nsQuota, out);
867      FSImageSerialization.writeLong(dsQuota, out);
868    }
869
870    @Override
871    void readFields(DataInputStream in, int logVersion)
872        throws IOException {
873      this.src = FSImageSerialization.readString(in);
874      this.nsQuota = FSImageSerialization.readLong(in);
875      this.dsQuota = FSImageSerialization.readLong(in);
876    }
877  }
878
879  static class TimesOp extends FSEditLogOp {
880    int length;
881    String path;
882    long mtime;
883    long atime;
884
885    private TimesOp() {
886      super(OP_TIMES);
887    }
888
889    static TimesOp getInstance() {
890      return (TimesOp)opInstances.get()
891        .get(OP_TIMES);
892    }
893
894    TimesOp setPath(String path) {
895      this.path = path;
896      return this;
897    }
898
899    TimesOp setModificationTime(long mtime) {
900      this.mtime = mtime;
901      return this;
902    }
903
904    TimesOp setAccessTime(long atime) {
905      this.atime = atime;
906      return this;
907    }
908
909    @Override 
910    void writeFields(DataOutputStream out) throws IOException {
911      FSImageSerialization.writeString(path, out);
912      FSImageSerialization.writeLong(mtime, out);
913      FSImageSerialization.writeLong(atime, out);
914    }
915
916    @Override
917    void readFields(DataInputStream in, int logVersion)
918        throws IOException {
919      if (!LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
920        this.length = in.readInt();
921        if (length != 3) {
922          throw new IOException("Incorrect data format. " + "times operation.");
923        }
924      }
925      this.path = FSImageSerialization.readString(in);
926
927      if (LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
928        this.mtime = FSImageSerialization.readLong(in);
929        this.atime = FSImageSerialization.readLong(in);
930      } else {
931        this.mtime = readLong(in);
932        this.atime = readLong(in);
933      }
934    }
935  }
936
937  static class SymlinkOp extends FSEditLogOp {
938    int length;
939    String path;
940    String value;
941    long mtime;
942    long atime;
943    PermissionStatus permissionStatus;
944
945    private SymlinkOp() {
946      super(OP_SYMLINK);
947    }
948
949    static SymlinkOp getInstance() {
950      return (SymlinkOp)opInstances.get()
951        .get(OP_SYMLINK);
952    }
953
954    SymlinkOp setPath(String path) {
955      this.path = path;
956      return this;
957    }
958
959    SymlinkOp setValue(String value) {
960      this.value = value;
961      return this;
962    }
963
964    SymlinkOp setModificationTime(long mtime) {
965      this.mtime = mtime;
966      return this;
967    }
968
969    SymlinkOp setAccessTime(long atime) {
970      this.atime = atime;
971      return this;
972    }
973
974    SymlinkOp setPermissionStatus(PermissionStatus permissionStatus) {
975      this.permissionStatus = permissionStatus;
976      return this;
977    }
978
979    @Override 
980    void writeFields(DataOutputStream out) throws IOException {
981      FSImageSerialization.writeString(path, out);
982      FSImageSerialization.writeString(value, out);
983      FSImageSerialization.writeLong(mtime, out);
984      FSImageSerialization.writeLong(atime, out);
985      permissionStatus.write(out);
986    }
987
988    @Override
989    void readFields(DataInputStream in, int logVersion)
990        throws IOException {
991      if (!LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
992        this.length = in.readInt();
993        if (this.length != 4) {
994          throw new IOException("Incorrect data format. "
995              + "symlink operation.");
996        }
997      }
998      this.path = FSImageSerialization.readString(in);
999      this.value = FSImageSerialization.readString(in);
1000
1001      if (LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
1002        this.mtime = FSImageSerialization.readLong(in);
1003        this.atime = FSImageSerialization.readLong(in);
1004      } else {
1005        this.mtime = readLong(in);
1006        this.atime = readLong(in);
1007      }
1008      this.permissionStatus = PermissionStatus.read(in);
1009    }
1010  }
1011
1012  static class RenameOp extends FSEditLogOp {
1013    int length;
1014    String src;
1015    String dst;
1016    long timestamp;
1017    Rename[] options;
1018
1019    private RenameOp() {
1020      super(OP_RENAME);
1021    }
1022
1023    static RenameOp getInstance() {
1024      return (RenameOp)opInstances.get()
1025        .get(OP_RENAME);
1026    }
1027
1028    RenameOp setSource(String src) {
1029      this.src = src;
1030      return this;
1031    }
1032
1033    RenameOp setDestination(String dst) {
1034      this.dst = dst;
1035      return this;
1036    }
1037    
1038    RenameOp setTimestamp(long timestamp) {
1039      this.timestamp = timestamp;
1040      return this;
1041    }
1042    
1043    RenameOp setOptions(Rename[] options) {
1044      this.options = options;
1045      return this;
1046    }
1047
1048    @Override 
1049    void writeFields(DataOutputStream out) throws IOException {
1050      FSImageSerialization.writeString(src, out);
1051      FSImageSerialization.writeString(dst, out);
1052      FSImageSerialization.writeLong(timestamp, out);
1053      toBytesWritable(options).write(out);
1054    }
1055
1056    @Override
1057    void readFields(DataInputStream in, int logVersion)
1058        throws IOException {
1059      if (!LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
1060        this.length = in.readInt();
1061        if (this.length != 3) {
1062          throw new IOException("Incorrect data format. " + "Rename operation.");
1063        }
1064      }
1065      this.src = FSImageSerialization.readString(in);
1066      this.dst = FSImageSerialization.readString(in);
1067
1068      if (LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
1069        this.timestamp = FSImageSerialization.readLong(in);
1070      } else {
1071        this.timestamp = readLong(in);
1072      }
1073      this.options = readRenameOptions(in);
1074    }
1075
1076    private static Rename[] readRenameOptions(DataInputStream in) throws IOException {
1077      BytesWritable writable = new BytesWritable();
1078      writable.readFields(in);
1079
1080      byte[] bytes = writable.getBytes();
1081      Rename[] options = new Rename[bytes.length];
1082
1083      for (int i = 0; i < bytes.length; i++) {
1084        options[i] = Rename.valueOf(bytes[i]);
1085      }
1086      return options;
1087    }
1088
1089    static BytesWritable toBytesWritable(Rename... options) {
1090      byte[] bytes = new byte[options.length];
1091      for (int i = 0; i < options.length; i++) {
1092        bytes[i] = options[i].value();
1093      }
1094      return new BytesWritable(bytes);
1095    }
1096  }
1097
1098  static class ReassignLeaseOp extends FSEditLogOp {
1099    String leaseHolder;
1100    String path;
1101    String newHolder;
1102
1103    private ReassignLeaseOp() {
1104      super(OP_REASSIGN_LEASE);
1105    }
1106
1107    static ReassignLeaseOp getInstance() {
1108      return (ReassignLeaseOp)opInstances.get()
1109        .get(OP_REASSIGN_LEASE);
1110    }
1111
1112    ReassignLeaseOp setLeaseHolder(String leaseHolder) {
1113      this.leaseHolder = leaseHolder;
1114      return this;
1115    }
1116
1117    ReassignLeaseOp setPath(String path) {
1118      this.path = path;
1119      return this;
1120    }
1121
1122    ReassignLeaseOp setNewHolder(String newHolder) {
1123      this.newHolder = newHolder;
1124      return this;
1125    }
1126
1127    @Override 
1128    void writeFields(DataOutputStream out) throws IOException {
1129      FSImageSerialization.writeString(leaseHolder, out);
1130      FSImageSerialization.writeString(path, out);
1131      FSImageSerialization.writeString(newHolder, out);
1132    }
1133
1134    @Override
1135    void readFields(DataInputStream in, int logVersion)
1136        throws IOException {
1137      this.leaseHolder = FSImageSerialization.readString(in);
1138      this.path = FSImageSerialization.readString(in);
1139      this.newHolder = FSImageSerialization.readString(in);
1140    }
1141  }
1142
1143  static class GetDelegationTokenOp extends FSEditLogOp {
1144    DelegationTokenIdentifier token;
1145    long expiryTime;
1146
1147    private GetDelegationTokenOp() {
1148      super(OP_GET_DELEGATION_TOKEN);
1149    }
1150
1151    static GetDelegationTokenOp getInstance() {
1152      return (GetDelegationTokenOp)opInstances.get()
1153        .get(OP_GET_DELEGATION_TOKEN);
1154    }
1155
1156    GetDelegationTokenOp setDelegationTokenIdentifier(
1157        DelegationTokenIdentifier token) {
1158      this.token = token;
1159      return this;
1160    }
1161
1162    GetDelegationTokenOp setExpiryTime(long expiryTime) {
1163      this.expiryTime = expiryTime;
1164      return this;
1165    }
1166
1167    @Override 
1168    void writeFields(DataOutputStream out) throws IOException {
1169      token.write(out);
1170      FSImageSerialization.writeLong(expiryTime, out);
1171    }
1172
1173    @Override
1174    void readFields(DataInputStream in, int logVersion)
1175        throws IOException {
1176      this.token = new DelegationTokenIdentifier();
1177      this.token.readFields(in);
1178      if (LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
1179        this.expiryTime = FSImageSerialization.readLong(in);
1180      } else {
1181        this.expiryTime = readLong(in);
1182      }
1183    }
1184  }
1185
1186  static class RenewDelegationTokenOp extends FSEditLogOp {
1187    DelegationTokenIdentifier token;
1188    long expiryTime;
1189
1190    private RenewDelegationTokenOp() {
1191      super(OP_RENEW_DELEGATION_TOKEN);
1192    }
1193
1194    static RenewDelegationTokenOp getInstance() {
1195      return (RenewDelegationTokenOp)opInstances.get()
1196          .get(OP_RENEW_DELEGATION_TOKEN);
1197    }
1198
1199    RenewDelegationTokenOp setDelegationTokenIdentifier(
1200        DelegationTokenIdentifier token) {
1201      this.token = token;
1202      return this;
1203    }
1204
1205    RenewDelegationTokenOp setExpiryTime(long expiryTime) {
1206      this.expiryTime = expiryTime;
1207      return this;
1208    }
1209
1210    @Override 
1211    void writeFields(DataOutputStream out) throws IOException {
1212      token.write(out);
1213      FSImageSerialization.writeLong(expiryTime, out);
1214    }
1215
1216    @Override
1217    void readFields(DataInputStream in, int logVersion)
1218        throws IOException {
1219      this.token = new DelegationTokenIdentifier();
1220      this.token.readFields(in);
1221      if (LayoutVersion.supports(Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
1222        this.expiryTime = FSImageSerialization.readLong(in);
1223      } else {
1224        this.expiryTime = readLong(in);
1225      }
1226    }
1227  }
1228
1229  static class CancelDelegationTokenOp extends FSEditLogOp {
1230    DelegationTokenIdentifier token;
1231
1232    private CancelDelegationTokenOp() {
1233      super(OP_CANCEL_DELEGATION_TOKEN);
1234    }
1235
1236    static CancelDelegationTokenOp getInstance() {
1237      return (CancelDelegationTokenOp)opInstances.get()
1238          .get(OP_CANCEL_DELEGATION_TOKEN);
1239    }
1240
1241    CancelDelegationTokenOp setDelegationTokenIdentifier(
1242        DelegationTokenIdentifier token) {
1243      this.token = token;
1244      return this;
1245    }
1246
1247    @Override 
1248    void writeFields(DataOutputStream out) throws IOException {
1249      token.write(out);
1250    }
1251
1252    @Override
1253    void readFields(DataInputStream in, int logVersion)
1254        throws IOException {
1255      this.token = new DelegationTokenIdentifier();
1256      this.token.readFields(in);
1257    }
1258  }
1259
1260  static class UpdateMasterKeyOp extends FSEditLogOp {
1261    DelegationKey key;
1262
1263    private UpdateMasterKeyOp() {
1264      super(OP_UPDATE_MASTER_KEY);
1265    }
1266
1267    static UpdateMasterKeyOp getInstance() {
1268      return (UpdateMasterKeyOp)opInstances.get()
1269          .get(OP_UPDATE_MASTER_KEY);
1270    }
1271
1272    UpdateMasterKeyOp setDelegationKey(DelegationKey key) {
1273      this.key = key;
1274      return this;
1275    }
1276    
1277    @Override 
1278    void writeFields(DataOutputStream out) throws IOException {
1279      key.write(out);
1280    }
1281
1282    @Override
1283    void readFields(DataInputStream in, int logVersion)
1284        throws IOException {
1285      this.key = new DelegationKey();
1286      this.key.readFields(in);
1287    }
1288  }
1289  
1290  static class LogSegmentOp extends FSEditLogOp {
1291    private LogSegmentOp(FSEditLogOpCodes code) {
1292      super(code);
1293      assert code == OP_START_LOG_SEGMENT ||
1294             code == OP_END_LOG_SEGMENT : "Bad op: " + code;
1295    }
1296
1297    static LogSegmentOp getInstance(FSEditLogOpCodes code) {
1298      return (LogSegmentOp)opInstances.get().get(code);
1299    }
1300
1301    public void readFields(DataInputStream in, int logVersion)
1302        throws IOException {
1303      // no data stored in these ops yet
1304    }
1305
1306    @Override
1307    void writeFields(DataOutputStream out) throws IOException {
1308      // no data stored
1309    }
1310  }
1311
1312  static class InvalidOp extends FSEditLogOp {
1313    private InvalidOp() {
1314      super(OP_INVALID);
1315    }
1316
1317    static InvalidOp getInstance() {
1318      return (InvalidOp)opInstances.get().get(OP_INVALID);
1319    }
1320
1321    @Override 
1322    void writeFields(DataOutputStream out) throws IOException {
1323    }
1324    
1325    @Override
1326    void readFields(DataInputStream in, int logVersion)
1327        throws IOException {
1328      // nothing to read
1329    }
1330  }
1331
1332  static private short readShort(DataInputStream in) throws IOException {
1333    return Short.parseShort(FSImageSerialization.readString(in));
1334  }
1335
1336  static private long readLong(DataInputStream in) throws IOException {
1337    return Long.parseLong(FSImageSerialization.readString(in));
1338  }
1339
1340  /**
1341   * A class to read in blocks stored in the old format. The only two
1342   * fields in the block were blockid and length.
1343   */
1344  static class BlockTwo implements Writable {
1345    long blkid;
1346    long len;
1347
1348    static {                                      // register a ctor
1349      WritableFactories.setFactory
1350        (BlockTwo.class,
1351         new WritableFactory() {
1352           public Writable newInstance() { return new BlockTwo(); }
1353         });
1354    }
1355
1356
1357    BlockTwo() {
1358      blkid = 0;
1359      len = 0;
1360    }
1361    /////////////////////////////////////
1362    // Writable
1363    /////////////////////////////////////
1364    public void write(DataOutput out) throws IOException {
1365      out.writeLong(blkid);
1366      out.writeLong(len);
1367    }
1368
1369    public void readFields(DataInput in) throws IOException {
1370      this.blkid = in.readLong();
1371      this.len = in.readLong();
1372    }
1373  }
1374
1375  /**
1376   * Class for writing editlog ops
1377   */
1378  public static class Writer {
1379    private final DataOutputBuffer buf;
1380    private final Checksum checksum;
1381
1382    public Writer(DataOutputBuffer out) {
1383      this.buf = out;
1384      this.checksum = new PureJavaCrc32();
1385    }
1386
1387    /**
1388     * Write an operation to the output stream
1389     * 
1390     * @param op The operation to write
1391     * @throws IOException if an error occurs during writing.
1392     */
1393    public void writeOp(FSEditLogOp op) throws IOException {
1394      int start = buf.getLength();
1395      buf.writeByte(op.opCode.getOpCode());
1396      buf.writeLong(op.txid);
1397      op.writeFields(buf);
1398      int end = buf.getLength();
1399      checksum.reset();
1400      checksum.update(buf.getData(), start, end-start);
1401      int sum = (int)checksum.getValue();
1402      buf.writeInt(sum);
1403    }
1404  }
1405
1406  /**
1407   * Class for reading editlog ops from a stream
1408   */
1409  public static class Reader {
1410    private final DataInputStream in;
1411    private final int logVersion;
1412    private final Checksum checksum;
1413
1414    /**
1415     * Construct the reader
1416     * @param in The stream to read from.
1417     * @param logVersion The version of the data coming from the stream.
1418     */
1419    @SuppressWarnings("deprecation")
1420    public Reader(DataInputStream in, int logVersion) {
1421      this.logVersion = logVersion;
1422      if (LayoutVersion.supports(Feature.EDITS_CHESKUM, logVersion)) {
1423        this.checksum = new PureJavaCrc32();
1424      } else {
1425        this.checksum = null;
1426      }
1427
1428      if (this.checksum != null) {
1429        this.in = new DataInputStream(
1430            new CheckedInputStream(in, this.checksum));
1431      } else {
1432        this.in = in;
1433      }
1434    }
1435
1436    /**
1437     * Read an operation from the input stream.
1438     * 
1439     * Note that the objects returned from this method may be re-used by future
1440     * calls to the same method.
1441     * 
1442     * @return the operation read from the stream, or null at the end of the file
1443     * @throws IOException on error.
1444     */
1445    public FSEditLogOp readOp() throws IOException {
1446      if (checksum != null) {
1447        checksum.reset();
1448      }
1449
1450      in.mark(1);
1451
1452      byte opCodeByte;
1453      try {
1454        opCodeByte = in.readByte();
1455      } catch (EOFException eof) {
1456        // EOF at an opcode boundary is expected.
1457        return null;
1458      }
1459
1460      FSEditLogOpCodes opCode = FSEditLogOpCodes.fromByte(opCodeByte);
1461      if (opCode == OP_INVALID) {
1462        in.reset(); // reset back to end of file if somebody reads it again
1463        return null;
1464      }
1465
1466      FSEditLogOp op = opInstances.get().get(opCode);
1467      if (op == null) {
1468        throw new IOException("Read invalid opcode " + opCode);
1469      }
1470
1471      if (LayoutVersion.supports(Feature.STORED_TXIDS, logVersion)) {
1472        // Read the txid
1473        op.setTransactionId(in.readLong());
1474      }
1475
1476      op.readFields(in, logVersion);
1477
1478      validateChecksum(in, checksum, op.txid);
1479      return op;
1480    }
1481
1482    /**
1483     * Validate a transaction's checksum
1484     */
1485    private void validateChecksum(DataInputStream in,
1486                                  Checksum checksum,
1487                                  long txid)
1488        throws IOException {
1489      if (checksum != null) {
1490        int calculatedChecksum = (int)checksum.getValue();
1491        int readChecksum = in.readInt(); // read in checksum
1492        if (readChecksum != calculatedChecksum) {
1493          throw new ChecksumException(
1494              "Transaction is corrupt. Calculated checksum is " +
1495              calculatedChecksum + " but read checksum " + readChecksum, txid);
1496        }
1497      }
1498    }
1499  }
1500}