1
/****************************************************************************
3
| Copyright (c) 2007 Novell, Inc.
6
| This program is free software; you can redistribute it and/or
7
| modify it under the terms of version 2 of the GNU General Public License as
8
| published by the Free Software Foundation.
10
| This program is distributed in the hope that it will be useful,
11
| but WITHOUT ANY WARRANTY; without even the implied warranty of
12
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
| GNU General Public License for more details.
15
| You should have received a copy of the GNU General Public License
16
| along with this program; if not, contact Novell, Inc.
18
| To contact Novell about this file by physical or electronic mail,
19
| you may find current contact information at www.novell.com
22
|***************************************************************************/
28
using System.Threading;
29
using System.Collections;
30
using System.Security.Cryptography;
33
using Simias.Sync.Delta;
38
// This is used if configure.in detected mono 1.1.13 or newer
39
using Mono.Unix.Native;
50
/// Class to handle file operations for a file to be synced out.
52
public abstract class OutFile : SyncFile
56
StreamStream workStream;
59
/// Gets the output stream.
61
protected StreamStream OutStream
63
get { return workStream; }
68
#region Constructor / Finalizer
71
/// Constructs an OutFile object.
73
/// <param name="collection">The collection that the node belongs to.</param>
74
protected OutFile(Collection collection) :
89
#region public methods.
92
/// Reads data into the buffer.
94
/// <param name="stream">The stream to read into.</param>
95
/// <param name="count">The number of bytes to read.</param>
96
/// <returns></returns>
97
public int Read(Stream stream, int count)
101
//Log.log.Debug("Reading File {0} : offset = {1}", file, ReadPosition);
102
return workStream.Read(stream, count);
106
Log.log.Debug(ex, "Failed Reading {0}", file);
112
/// Get the platform file handle.
114
public StreamStream outStream
116
get {return workStream;}
121
/// Gets or Sets the file position.
123
public long ReadPosition
125
get { return workStream.Position; }
126
set { workStream.Position = value; }
130
/// Gets the length of the stream.
134
get { return workStream.Length; }
139
#region protected methods.
142
/// Called to open the file.
144
/// <param name="node">The node that represents the file.</param>
145
/// <param name="sessionID">The unique session ID.</param>
146
protected void Open(BaseFileNode node, string sessionID)
148
SetupFileNames(node, sessionID);
149
Log.log.Debug("Opening File {0}", file);
150
FileInfo fi = new FileInfo(file);
151
if (Store.IsEnterpriseServer || fi.Length > (1024 * 100000))
153
workStream = new StreamStream(File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read));
158
// This file is being pushed make a copy to work from.
159
File.Copy(file, workFile, true);
160
File.SetAttributes(workFile, FileAttributes.Normal);
161
workStream = new StreamStream(File.Open(workFile, FileMode.Open, FileAccess.Read));
166
/// Called to create the hash map for the uploaded file.
168
public void CreateHashMap()
170
map.CreateHashMapFile();
174
/// Called to delete the hash map since it is not stored in the simias client
176
public void DeleteHashMap()
182
/// Called to get the hash map stream for the uploaded file.
184
public FileStream GetHashMap(out int entryCount, out int blockSize)
186
//TBD find out why old node was used in server
187
//arg. false, here the create hash map is obtained
188
return map.GetHashMapStream(out entryCount, out blockSize, false, node.LocalIncarnation);
192
/// Called to close the file and cleanup resources.
194
protected void Close()
196
Log.log.Debug("Closing File {0}", file);
202
#region private methods.
205
/// Called to close the file and cleanup.
207
/// <param name="InFinalizer">true if called from the finalizer.</param>
208
private void Close(bool InFinalizer)
211
GC.SuppressFinalize(this);
213
if (workStream != null)
218
// We need to delete the temp file.
219
if (workFile != null)
220
File.Delete(workFile);
231
/// Class to handle files that are being imported.
233
public abstract class InFile : SyncFile
237
/// <summary>Stream to the Incoming file.</summary>
238
StreamStream workStream;
239
/// <summary>Stream to the Original file.</summary>
241
/// <summary>The partially downloaded file.</summary>
243
/// <summary>The Old Node if it exists.</summary>
244
protected BaseFileNode oldNode;
248
#region Constructor / Finalizer.
251
/// Constructs an InFile object.
253
/// <param name="collection">The collection that the node belongs to.</param>
254
protected InFile(Collection collection) :
269
#region public methods.
272
/// Reads data into the buffer.
274
/// <param name="buffer">The buffer to read into.</param>
275
/// <param name="offset">The offset in the buffer to read into.</param>
276
/// <param name="count">The number of bytes to read.</param>
277
/// <returns></returns>
278
public int Read(byte[] buffer, int offset, int count)
280
if (stream != null) return stream.Read(buffer, offset, count);
285
/// Writes data from buffer into file.
287
/// <param name="stream">The stream to write.</param>
288
/// <param name="count">The number of bytes to write.</param>
289
public void Write(Stream stream, int count)
291
//Log.log.Debug("Writing File {0} : offset = {1}", file, WritePosition);
292
workStream.Write(stream, count);
295
// BUGBUG Encryption Here.
296
// Add decryption here.
298
/// Writes data from buffer into file.
300
/// <param name="stream">The stream to write.</param>
301
/// <param name="count">The number of bytes to write.</param>
302
/// <param name="count">The encryption Key.</param>
303
public int Write(Stream stream, int count, int actualCount, string encryptionAlgorithm, string encryptionKey)
305
workStream.Write(stream, count, actualCount, encryptionAlgorithm, encryptionKey);
311
/// Copyt the data from the original file into the new file.
313
/// <param name="originalOffset">The offset in the original file to copy from.</param>
314
/// <param name="offset">The offset in the file where the data is to be written.</param>
315
/// <param name="count">The number of bytes to write.</param>
316
public void Copy(long originalOffset, long offset, int count)
320
ReadPosition = originalOffset;
321
WritePosition = offset;
322
workStream.Write(stream, count);
327
/// Set the Length of the file.
329
/// <param name="length">The size to set.</param>
330
public void SetLength(long length)
332
workStream.SetLength(length);
338
public StreamStream inStream
340
get {return workStream;}
344
/// Gets the original stream.
346
public FileStream ReadStream
353
/// Gets or Sets the file position.
355
public long ReadPosition
357
get { return (stream == null ? 0 : stream.Position); }
358
set { if (stream != null) stream.Position = value; }
362
/// Gets or Sets the file position.
364
public long WritePosition
366
get { return workStream.Position; }
367
set { workStream.Position = value; }
371
/// Gets the length of the stream.
375
get { return node.Length; }
380
#region protected methods.
383
/// Called to open the file.
385
/// <param name="node">The node that represents the file.</param>
386
protected void Open(BaseFileNode node)
388
SetupFileNames(node, "");
389
CheckForNameConflict();
390
Log.log.Debug("Opening File {0}", file);
391
// Open the file so that it cannot be modified.
392
oldNode = collection.GetNodeByID(node.ID) as BaseFileNode;
397
stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.None);
400
catch (FileNotFoundException)
402
// Check to see if we have a partially downloaded file to delta sync with.
403
if (collection.Role == SyncRoles.Slave && File.Exists(workFile))
405
if (File.Exists(partialFile))
406
File.Delete(partialFile);
407
partialFile = workFile + ".part";
408
File.Move(workFile, partialFile);
409
stream = File.Open(partialFile, FileMode.Open, FileAccess.Read, FileShare.None);
411
else if (oldNode != null)
413
// The file may have been renamed.
414
string oldPath = oldNode.GetFullPath(collection);
416
stream = File.Open(oldPath, FileMode.Open, FileAccess.Read, FileShare.None);
419
// Create the file in the parent directory and then move to the work area.
420
// This will insure that the proper attributes are set.
421
// This was added to support EFS (Encrypted File System).
422
string createName = Path.Combine(Path.GetDirectoryName(file), Path.GetFileName(workFile));
423
FileStream tmpStream = File.Open(createName, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
424
if (File.Exists(workFile))
426
File.Delete(workFile);
428
// Make sure we have enough space for the file.
431
tmpStream.SetLength(node.Length);
435
if (MyEnvironment.Unix)
437
if (node.Properties.GetSingleProperty(SyncFile.ModeProperty) != null)
439
// Get the posix mode flags for the file.
441
if (Syscall.stat(createName, out sStat) == 0)
443
// Now or in the execute bit and set it on the file.
444
FilePermissions fp = sStat.st_mode | FilePermissions.S_IXUSR;
445
Syscall.chmod(createName, fp);
451
File.Move(createName, workFile);
455
if (tmpStream != null)
457
throw new InsufficientStorageException();
459
workStream = new StreamStream(File.Open(workFile, FileMode.Truncate, FileAccess.ReadWrite, FileShare.None));
463
/// Called to close the file and cleanup resources.
465
protected void Close(bool commit)
467
Log.log.Debug("Closing File {0}", file);
468
Close (false, commit);
473
#region private methods.
476
/// Called to cleanup any resources and close the file.
478
/// <param name="InFinalizer"></param>
479
/// <param name="commit"></param>
480
private void Close(bool InFinalizer, bool commit)
484
GC.SuppressFinalize(this);
491
if (workStream != null)
498
if (File.Exists(file))
500
string tmpFile = file + ".~stmp";
501
File.Move(file, tmpFile);
504
File.Move(workFile, file);
505
File.Delete(tmpFile);
510
File.Move(tmpFile, file);
516
File.Move(workFile, file);
519
FileInfo fi = new FileInfo(file);
520
fi.LastWriteTime = node.LastWriteTime;
521
fi.CreationTime = node.CreationTime;
524
// Check if this was a rename.
525
// If the old path does not equal the new path
526
// Delete the old file.
527
string oldPath = oldNode.GetFullPath(collection);
530
if (MyEnvironment.Windows)
532
if (string.Compare(oldPath, file, true) != 0)
533
File.Delete(oldPath);
538
File.Delete(oldPath);
545
// We need to delete the temp file if we are the master.
546
// On the client leave for a delta sync.
547
if (workFile != null)
549
if (collection.Role == SyncRoles.Master || (collection.Role == SyncRoles.Slave && File.Exists(file)))
551
File.Delete(workFile);
555
if (partialFile != null)
556
File.Delete(partialFile);
567
/// Class used to determine the common data between two files.
568
/// This is done from a copy of the local file and a map of hash code for the server file.
570
public abstract class SyncFile
574
bool nameConflict = false;
575
bool dateConflict = false;
576
protected Node conflictingNode = null;
577
/// <summary>Used to signal to stop upload or downloading the file.</summary>
578
protected bool stopping = false;
579
/// <summary>The Collection the file belongs to.</summary>
580
protected Collection collection;
581
/// <summary> The node that represents the file.</summary>
582
protected BaseFileNode node;
583
/// <summary>The ID of the node.</summary>
584
protected string nodeID;
585
/// <summary>The maximun size of a transfer.</summary>
586
protected const int MaxXFerSize = 1024 * 256;
587
/// <summary>The name of the actual file.</summary>
588
protected string file;
589
/// <summary>The name of the working file.</summary>
590
protected string workFile;
592
/// The HashMap for this file.
594
protected HashMap map;
595
/// <summary>The Prefix of the working file.</summary>
596
const string WorkFilePrefix = ".simias.wf.";
597
static string workBinDir = "WorkArea";
598
static string workBin;
599
// '/' is left out on purpose because all systems disallow this char.
600
public static char[] InvalidChars = {'\\', ':', '*', '?', '\"', '<', '>', '|'};
602
/// <summary>Used to publish Sync events.</summary>
603
static public EventPublisher eventPublisher = new EventPublisher();
604
static internal string ModeProperty = "FAMode";
614
#region protected methods.
619
/// <param name="collection">The collection that the node belongs to.</param>
620
protected SyncFile(Collection collection)
622
this.collection = collection;
626
/// Called to get the name of the file and workFile;
628
/// <param name="node">The node that represents the file.</param>
629
/// <param name="sessionID">The unique session ID.</param>
630
protected void SetupFileNames(BaseFileNode node, string sessionID)
633
this.nodeID = node.ID;
636
this.file = node.GetFullPath(collection);
640
// If this failed the file name has illegal characters.
645
workBin = Path.Combine(collection.StorePath, workBinDir);
646
if (!Directory.Exists(workBin))
647
Directory.CreateDirectory(workBin);
649
this.workFile = Path.Combine(workBin, WorkFilePrefix + node.ID + sessionID);
653
/// Checks for a name conflict.
655
/// <returns>True if conflict.</returns>
656
protected bool CheckForNameConflict()
660
// Look up the FsPath property (StoreFileNodes don't have this property set).
661
Property property = node.Properties.GetSingleProperty(PropertyTags.FileSystemPath);
662
if (property != null)
664
string path = property.Value.ToString();
666
nodeList = collection.Search(PropertyTags.FileSystemPath, path, SearchOp.Equal);
667
foreach (ShallowNode sn in nodeList)
669
if (sn.ID != node.ID)
671
conflictingNode = collection.GetNodeByID(sn.ID);
676
// Now make sure we don't have any illegal characters.
677
if (!IsNameValid(path))
682
node = Conflict.CreateNameConflict(collection, node) as BaseFileNode;
683
file = Conflict.GetFileConflictPath(collection, node);
684
if (conflictingNode != null)
687
FileNode tmpFn = conflictingNode as FileNode;
688
DirNode tmpDn = conflictingNode as DirNode;
691
cnPath = tmpFn.GetFullPath(collection);
695
cnPath = tmpDn.GetFullPath(collection);
697
conflictingNode = Conflict.CreateNameConflict(collection, conflictingNode, cnPath);
698
Conflict.LinkConflictingNodes(conflictingNode, node);
707
/// Called to see if a node with the same name exists.
709
/// <param name="collection">The collection that contains the node.</param>
710
/// <param name="parent">The parent node</param>
711
/// <param name="name">The leaf name of the file.</param>
712
/// <returns>true if allowed.</returns>
713
public static bool DoesNodeExist(Collection collection, DirNode parent, string name)
715
string path = parent.Properties.GetSingleProperty(PropertyTags.FileSystemPath).Value.ToString() + "/" + name;
717
nodeList = collection.Search(PropertyTags.FileSystemPath, path, SearchOp.Equal);
718
if (nodeList.Count > 0)
727
/// Gets or Sets a NameConflict.
729
protected bool NameConflict
731
get {return nameConflict;}
732
set {nameConflict = value;}
736
/// Gets or Sets a NameConflict.
738
protected bool DateConflict
740
get {return dateConflict;}
741
set {dateConflict = value;}
746
#region public methods.
749
/// Delete ther file and map file.
751
/// <param name="collection">The collection that the node belongs to.</param>
752
/// <param name="node">The node that represents the file.</param>
753
/// <param name="path">The full path to the file.</param>
754
public static void DeleteFile(Collection collection, BaseFileNode node, string path)
756
if (File.Exists(path))
760
// Now delete the map file.
761
HashMap.Delete(collection, node);
767
/// Get the file name.
771
get { return Path.GetFileName(file); }
775
/// Tells the file to stop and return.
779
set { stopping = value; }
783
/// Tests if the file name is valid.
785
/// <param name="name">The file name.</param>
786
/// <returns>true if valid.</returns>
787
public static bool IsNameValid(string name)
789
return name.IndexOfAny(InvalidChars) == -1 ? true : false;
793
/// Tests if the relative path is valid.
795
/// <param name="path">The path.</param>
796
/// <returns>true if valid</returns>
797
public static bool IsRelativePathValid(string path)
799
return path.IndexOfAny(InvalidChars) == -1 ? true : false;
803
/// Test if encryption is enabled
805
public bool IsEncryptionEnabled()
807
string EncryptionAlgorithm="";
808
Property p = collection.Properties.FindSingleValue(PropertyTags.EncryptionType);
809
EncryptionAlgorithm = (p!=null) ? (string) p.Value as string : "";
810
if(EncryptionAlgorithm =="")
817
/// Gets the crypto key
819
public bool GetCryptoKey(out string EncryptionKey)
823
string EncryptionAlgorithm="";
824
Property p = collection.Properties.FindSingleValue(PropertyTags.EncryptionType);
825
EncryptionAlgorithm = (p!=null) ? (string) p.Value as string : "";
826
if(EncryptionAlgorithm != "")
828
p = collection.Properties.FindSingleValue(PropertyTags.EncryptionKey);
829
string EncryptedKey = (p!=null) ? (string) p.Value as string : null;
831
Store store = Store.GetStore();
832
string Passphrase = store.GetPassPhrase(collection.Domain);
833
if(Passphrase ==null)
834
throw new CollectionStoreException("Passphrase not provided");
836
//Hash the passphrase and use it for encryption and decryption
837
PassphraseHash hash = new PassphraseHash();
838
byte[] passphrase = hash.HashPassPhrase(Passphrase);
840
Key key = new Key(EncryptedKey);//send the key size and algorithm
841
key.DecrypytKey(passphrase, out EncryptionKey);//send the passphrase to decrypt the key
843
p = collection.Properties.FindSingleValue(PropertyTags.EncryptionBlob);
844
string EncryptionBlob = (p!=null) ? (string) p.Value as string : null;
845
if(EncryptionBlob == null)
846
throw new CollectionStoreException("The specified cryptographic key not found");
848
Key hashKey = new Key(EncryptionKey);
849
if(hashKey.HashKey() != EncryptionBlob)
850
throw new CollectionStoreException("The specified cryptographic key does not match");
874
/// class to approximate amount of data that is out of sync with master
875
/// Note that this is worst-case of data that may need to be sent from
876
/// this collection to the master. It does not include data that may need
877
/// to be retrieved from the master. It also does not account for
878
/// delta-sync algorithms that may reduce what needs to be sent
880
public class SyncSize
885
/// <param name="col"></param>
886
/// <param name="nodeCount"></param>
887
/// <param name="maxBytesToSend"></param>
888
public static void CalculateSendSize(Collection col, out uint nodeCount, out ulong maxBytesToSend)
890
Log.log.Debug("starting to calculate size to send to master for collection {0}", col.Name);
894
SyncClient.GetCountToSync(col.ID, out nodeCount);