xref: /haiku/src/apps/diskprobe/DataEditor.cpp (revision 6f80a9801fedbe7355c4360bd204ba746ec3ec2d)
1 /*
2  * Copyright 2004-2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "DataEditor.h"
8 
9 #include <Autolock.h>
10 #include <NodeMonitor.h>
11 #include <Debug.h>
12 #include <Directory.h>
13 #include <Drivers.h>
14 #include <fs_attr.h>
15 #include <fs_info.h>
16 
17 #include <stdlib.h>
18 #include <string.h>
19 #include <unistd.h>
20 #include <ctype.h>
21 #include <errno.h>
22 
23 
24 #ifdef TRACE
25 #	undef TRACE
26 #endif
27 
28 //#define TRACE_DATA_EDITOR
29 #ifdef TRACE_DATA_EDITOR
30 #	define TRACE(x) printf x
31 #else
32 #	define TRACE(x) ;
33 #endif
34 
35 
36 class StateWatcher {
37 	public:
38 		StateWatcher(DataEditor &editor);
39 		~StateWatcher();
40 
41 	private:
42 		DataEditor	&fEditor;
43 		bool		fCouldUndo;
44 		bool		fCouldRedo;
45 		bool		fWasModified;
46 };
47 
48 class DataChange {
49 	public:
50 		virtual ~DataChange();
51 
52 		virtual void Apply(off_t offset, uint8 *buffer, size_t size) = 0;
53 		virtual void Revert(off_t offset, uint8 *buffer, size_t size) = 0;
54 		virtual bool Merge(DataChange *change);
55 		virtual void GetRange(off_t fileSize, off_t &_offset, off_t &_size) = 0;
56 };
57 
58 class ReplaceChange : public DataChange {
59 	public:
60 		ReplaceChange(off_t offset, const uint8 *data, size_t size);
61 		~ReplaceChange();
62 
63 		virtual void Apply(off_t offset, uint8 *buffer, size_t size);
64 		virtual void Revert(off_t offset, uint8 *buffer, size_t size);
65 		virtual bool Merge(DataChange *change);
66 		virtual void GetRange(off_t fileSize, off_t &_offset, off_t &_size);
67 
68 	private:
69 		void Normalize(off_t bufferOffset, size_t bufferSize,
70 			off_t &offset, size_t &dataOffset, size_t &size);
71 
72 		uint8 	*fNewData;
73 		uint8 	*fOldData;
74 		size_t	fSize;
75 		off_t	fOffset;
76 };
77 
78 
79 //---------------------
80 
81 
82 #ifdef TRACE_DATA_EDITOR
83 
84 #define DUMPED_BLOCK_SIZE 16
85 
86 static void
87 dump_block(const uint8 *buffer, int size, const char *prefix)
88 {
89 	for (int i = 0; i < size;) {
90 		int start = i;
91 
92 		printf(prefix);
93 		for (; i < start + DUMPED_BLOCK_SIZE; i++) {
94 			if (!(i % 4))
95 				printf(" ");
96 
97 			if (i >= size)
98 				printf("  ");
99 			else
100 				printf("%02x", *(unsigned char *)(buffer + i));
101 		}
102 		printf("  ");
103 
104 		for (i = start; i < start + DUMPED_BLOCK_SIZE; i++) {
105 			if (i < size) {
106 				char c = buffer[i];
107 
108 				if (c < 30)
109 					printf(".");
110 				else
111 					printf("%c", c);
112 			} else
113 				break;
114 		}
115 		printf("\n");
116 	}
117 }
118 #endif	// TRACE_DATA_EDITOR
119 
120 
121 static int
122 CompareCaseInsensitive(const uint8 *a, const uint8 *b, size_t size)
123 {
124 	for (size_t i = 0; i < size; i++) {
125 		uint8 diff = tolower(a[i]) - tolower(b[i]);
126 		if (diff)
127 			return diff;
128 	}
129 
130 	return 0;
131 }
132 
133 
134 static int
135 CompareOffsets(const off_t *a, const off_t *b)
136 {
137 	if (*a < *b)
138 		return -1;
139 	else if (*a > *b)
140 		return 1;
141 
142 	return 0;
143 }
144 
145 
146 //	#pragma mark -
147 
148 
149 StateWatcher::StateWatcher(DataEditor &editor)
150 	:
151 	fEditor(editor)
152 {
153 	fCouldUndo = editor.CanUndo();
154 	fCouldRedo = editor.CanRedo();
155 	fWasModified = editor.IsModified();
156 }
157 
158 
159 StateWatcher::~StateWatcher()
160 {
161 	BMessage update;
162 	if (fCouldRedo != fEditor.CanRedo())
163 		update.AddBool("can_redo", fEditor.CanRedo());
164 	if (fCouldUndo != fEditor.CanUndo())
165 		update.AddBool("can_undo", fEditor.CanUndo());
166 	if (fWasModified != fEditor.IsModified())
167 		update.AddBool("modified", fEditor.IsModified());
168 
169 	// only send an update if we have to report anything
170 	if (!update.IsEmpty())
171 		fEditor.SendNotices(kMsgDataEditorStateChange, &update);
172 }
173 
174 
175 //	#pragma mark -
176 
177 
178 DataChange::~DataChange()
179 {
180 }
181 
182 
183 bool
184 DataChange::Merge(DataChange *change)
185 {
186 	return false;
187 }
188 
189 
190 //	#pragma mark -
191 
192 
193 ReplaceChange::ReplaceChange(off_t offset, const uint8 *data, size_t size)
194 {
195 	fOffset = offset;
196 
197 	fNewData = (uint8 *)malloc(size);
198 	fOldData = (uint8 *)malloc(size);
199 	if (fNewData != NULL && fOldData != NULL) {
200 		memcpy(fNewData, data, size);
201 		fSize = size;
202 	} else
203 		fSize = 0;
204 }
205 
206 
207 ReplaceChange::~ReplaceChange()
208 {
209 	free(fNewData);
210 	free(fOldData);
211 }
212 
213 
214 /** Normalizes the supplied offset & size pair to be limited by
215  *	the buffer offset and size.
216  *	All parameters must have been initialized before calling this
217  *	method.
218  */
219 
220 void
221 ReplaceChange::Normalize(off_t bufferOffset, size_t bufferSize, off_t &offset,
222 	size_t &dataOffset, size_t &size)
223 {
224 	if (fOffset < bufferOffset) {
225 		offset = bufferOffset;
226 		dataOffset = bufferOffset - fOffset;
227 		size -= dataOffset;
228 	}
229 
230 	if (offset + size > bufferOffset + bufferSize)
231 		size = bufferOffset + bufferSize - offset;
232 }
233 
234 
235 void
236 ReplaceChange::Apply(off_t bufferOffset, uint8 *buffer, size_t bufferSize)
237 {
238 	// is it in our range?
239 	if (fOffset > (bufferOffset + (off_t)bufferSize)
240 		|| (fOffset + (off_t)fSize) < bufferOffset) {
241 		return;
242 	}
243 
244 	// don't change anything outside the supplied buffer
245 	off_t offset = fOffset;
246 	size_t dataOffset = 0;
247 	size_t size = fSize;
248 	Normalize(bufferOffset, bufferSize, offset, dataOffset, size);
249 	if (size == 0)
250 		return;
251 
252 #ifdef TRACE_DATA_EDITOR
253 	printf("Apply %p (buffer offset = %Ld):\n", this, bufferOffset);
254 	dump_block(buffer + offset - bufferOffset, size, "old:");
255 	dump_block(fNewData + dataOffset, size, "new:");
256 #endif
257 
258 	// now we can safely exchange the buffer!
259 	memcpy(fOldData + dataOffset, buffer + offset - bufferOffset, size);
260 	memcpy(buffer + offset - bufferOffset, fNewData + dataOffset, size);
261 }
262 
263 
264 void
265 ReplaceChange::Revert(off_t bufferOffset, uint8 *buffer, size_t bufferSize)
266 {
267 	// is it in our range?
268 	if (fOffset - bufferOffset > (off_t)bufferSize
269 		|| fOffset + (off_t)fSize < bufferOffset) {
270 		return;
271 	}
272 
273 	// don't change anything outside the supplied buffer
274 	off_t offset = fOffset;
275 	size_t dataOffset = 0;
276 	size_t size = fSize;
277 	Normalize(bufferOffset, bufferSize, offset, dataOffset, size);
278 	if (size == 0)
279 		return;
280 
281 #ifdef TRACE_DATA_EDITOR
282 	printf("Revert %p (buffer offset = %Ld):\n", this, bufferOffset);
283 	dump_block(buffer + offset - bufferOffset, size, "old:");
284 	dump_block(fOldData + dataOffset, size, "new:");
285 #endif
286 
287 	// now we can safely revert the buffer!
288 	memcpy(buffer + offset - bufferOffset, fOldData + dataOffset, size);
289 }
290 
291 
292 bool
293 ReplaceChange::Merge(DataChange *_change)
294 {
295 	ReplaceChange *change = dynamic_cast<ReplaceChange *>(_change);
296 	if (change == NULL)
297 		return false;
298 
299 	if (change->fOffset + change->fSize == fOffset + fSize
300 		&& change->fSize == 1) {
301 		// this is a special case - the new change changed the last byte of
302 		// the old change: we do this since the same byte is changed twice
303 		// in hex mode editing.
304 		fNewData[fSize - 1] = change->fNewData[0];
305 #ifdef TRACE_DATA_EDITOR
306 		printf("Merge one byte %p (offset = %Ld, size = %lu):\n", this, fOffset,
307 			fSize);
308 		dump_block(fOldData, fSize, "old:");
309 		dump_block(fNewData, fSize, "new:");
310 #endif
311 		return true;
312 	}
313 
314 	// are the changes adjacent?
315 
316 	if (change->fOffset + (off_t)change->fSize != fOffset
317 		&& fOffset + (off_t)fSize != change->fOffset)
318 		return false;
319 
320 	// okay, we can try to merge the two changes
321 
322 	uint8 *newData = fOffset < change->fOffset ? fNewData : change->fNewData;
323 	size_t size = fSize + change->fSize;
324 
325 	if ((newData = (uint8 *)realloc(newData, size)) == NULL)
326 		return false;
327 
328 	uint8 *oldData = fOffset < change->fOffset ? fOldData : change->fOldData;
329 	if ((oldData = (uint8 *)realloc(oldData, size)) == NULL) {
330 		fNewData = (uint8 *)realloc(newData, fSize);
331 			// if this fails, we can't do anything about it
332 		return false;
333 	}
334 
335 	if (fOffset < change->fOffset) {
336 		memcpy(newData + fSize, change->fNewData, change->fSize);
337 		memcpy(oldData + fSize, change->fOldData, change->fSize);
338 	} else {
339 		memcpy(newData + change->fSize, fNewData, fSize);
340 		memcpy(oldData + change->fSize, fOldData, fSize);
341 		change->fNewData = fNewData;
342 		change->fOldData = fOldData;
343 			// transfer ownership, so that they will be deleted with the change
344 		fOffset = change->fOffset;
345 	}
346 
347 	fNewData = newData;
348 	fOldData = oldData;
349 	fSize = size;
350 
351 #ifdef TRACE_DATA_EDITOR
352 	printf("Merge %p (offset = %Ld, size = %lu):\n", this, fOffset, fSize);
353 	dump_block(fOldData, fSize, "old:");
354 	dump_block(fNewData, fSize, "new:");
355 #endif
356 
357 	return true;
358 }
359 
360 
361 void
362 ReplaceChange::GetRange(off_t /*fileSize*/, off_t &_offset, off_t &_size)
363 {
364 	_offset = fOffset;
365 	_size = fSize;
366 }
367 
368 
369 //	#pragma mark -
370 
371 
372 DataEditor::DataEditor()
373 	:
374 	BLocker("data view"),
375 	fAttribute(NULL)
376 {
377 }
378 
379 
380 DataEditor::DataEditor(entry_ref &ref, const char *attribute)
381 	:
382 	BLocker("data view"),
383 	fAttribute(NULL)
384 {
385 	SetTo(ref, attribute);
386 }
387 
388 
389 DataEditor::DataEditor(BEntry &entry, const char *attribute)
390 	:
391 	BLocker("data view"),
392 	fAttribute(NULL)
393 {
394 	SetTo(entry, attribute);
395 }
396 
397 
398 DataEditor::DataEditor(const DataEditor &editor)
399 	:
400 	BLocker("data view"),
401 	fAttribute(NULL)
402 {
403 }
404 
405 
406 DataEditor::~DataEditor()
407 {
408 	free((void*)fAttribute);
409 }
410 
411 
412 status_t
413 DataEditor::SetTo(const char *path, const char *attribute)
414 {
415 	BEntry entry(path);
416 	return SetTo(entry, attribute);
417 }
418 
419 
420 status_t
421 DataEditor::SetTo(entry_ref &ref, const char *attribute)
422 {
423 	BEntry entry(&ref);
424 	return SetTo(entry, attribute);
425 }
426 
427 
428 status_t
429 DataEditor::SetTo(BEntry &entry, const char *attribute)
430 {
431 	fSize = 0;
432 	fLastChange = fFirstChange = NULL;
433 	fChangesFromSaved = 0;
434 	fView = NULL;
435 	fRealViewOffset = 0;
436 	fViewOffset = 0;
437 	fRealViewSize = fViewSize = fBlockSize = 512;
438 
439 	free((void*)fAttribute);
440 	if (attribute != NULL)
441 		fAttribute = strdup(attribute);
442 	else
443 		fAttribute = NULL;
444 
445 	struct stat stat;
446 	status_t status = entry.GetStat(&stat);
447 	if (status < B_OK)
448 		return status;
449 
450 	entry.GetRef(&fAttributeRef);
451 
452 	bool isFileSystem = false;
453 
454 	if (entry.IsDirectory()) {
455 		// we redirect root directories to their volumes
456 		BDirectory directory(&entry);
457 		if (directory.InitCheck() == B_OK && directory.IsRootDirectory()) {
458 			fs_info info;
459 			if (fs_stat_dev(stat.st_dev, &info) != 0)
460 				return errno;
461 
462 			status = entry.SetTo(info.device_name);
463 			if (status < B_OK)
464 				return status;
465 
466 			entry.GetStat(&stat);
467 
468 			fBlockSize = info.block_size;
469 			if (fBlockSize > 0 && fBlockSize <= 65536)
470 				isFileSystem = true;
471 		}
472 	}
473 
474 	status = fFile.SetTo(&entry, B_READ_WRITE);
475 	if (status < B_OK) {
476 		// try to open read only
477 		status = fFile.SetTo(&entry, B_READ_ONLY);
478 		if (status < B_OK)
479 			return status;
480 
481 		fIsReadOnly = true;
482 	} else
483 		fIsReadOnly = false;
484 
485 	entry.GetRef(&fRef);
486 	fIsDevice = S_ISBLK(stat.st_mode) || S_ISCHR(stat.st_mode);
487 
488 	if (IsAttribute()) {
489 		BNode node(&fAttributeRef);
490 		attr_info info;
491 		status = node.GetAttrInfo(fAttribute, &info);
492 		if (status != B_OK) {
493 			fFile.Unset();
494 			return status;
495 		}
496 
497 		fSize = info.size;
498 		fType = info.type;
499 	} else if (fIsDevice) {
500 		device_geometry geometry;
501 		int device = fFile.Dup();
502 		if (device < 0 || ioctl(device, B_GET_GEOMETRY, &geometry,
503 				sizeof(geometry)) < 0) {
504 			if (device >= 0)
505 				close(device);
506 			fFile.Unset();
507 			return B_ERROR;
508 		}
509 		close(device);
510 
511 		fSize = 1LL * geometry.head_count * geometry.cylinder_count
512 			* geometry.sectors_per_track * geometry.bytes_per_sector;
513 		if (fSize < 0)
514 			fSize = 0;
515 		if (!isFileSystem)
516 			fBlockSize = geometry.bytes_per_sector;
517 	} else if (entry.IsDirectory() || entry.IsSymLink()) {
518 		fSize = 0;
519 		fIsReadOnly = true;
520 	} else {
521 		status = fFile.GetSize(&fSize);
522 		if (status < B_OK) {
523 			fFile.Unset();
524 			return status;
525 		}
526 	}
527 
528 	if (fBlockSize == 0)
529 		fBlockSize = 512;
530 
531 	fRealViewSize = fViewSize = fBlockSize;
532 	fNeedsUpdate = true;
533 
534 	return B_OK;
535 }
536 
537 
538 status_t
539 DataEditor::InitCheck()
540 {
541 	return fFile.InitCheck();
542 }
543 
544 
545 void
546 DataEditor::AddChange(DataChange *change)
547 {
548 	if (change == NULL)
549 		return;
550 
551 	StateWatcher watcher(*this);
552 		// update state observers
553 
554 	RemoveRedos();
555 	change->Apply(fRealViewOffset, fView, fRealViewSize);
556 
557 	SendNotices(change);
558 		// update observers
559 
560 	// try to join changes
561 	if (fLastChange == NULL || !fLastChange->Merge(change)) {
562 		fChanges.AddItem(change);
563 		fLastChange = change;
564 		fChangesFromSaved++;
565 	} else
566 		delete change;
567 }
568 
569 
570 status_t
571 DataEditor::Replace(off_t offset, const uint8 *data, size_t length)
572 {
573 	if (IsReadOnly())
574 		return B_NOT_ALLOWED;
575 
576 	BAutolock locker(this);
577 
578 	if (offset >= fSize)
579 		return B_BAD_VALUE;
580 	if (offset + (off_t)length > fSize)
581 		length = fSize - offset;
582 
583 	if (fNeedsUpdate) {
584 		status_t status = Update();
585 		if (status < B_OK)
586 			return status;
587 	}
588 
589 	ReplaceChange *change = new ReplaceChange(offset, data, length);
590 	AddChange(change);
591 
592 	return B_OK;
593 }
594 
595 
596 status_t
597 DataEditor::Remove(off_t offset, off_t length)
598 {
599 	if (IsReadOnly())
600 		return B_NOT_ALLOWED;
601 
602 	BAutolock locker(this);
603 
604 	// not yet implemented
605 	// ToDo: this needs some changes to the whole change mechanism
606 
607 	return B_ERROR;
608 }
609 
610 
611 status_t
612 DataEditor::Insert(off_t offset, const uint8 *text, size_t length)
613 {
614 	if (IsReadOnly())
615 		return B_NOT_ALLOWED;
616 
617 	BAutolock locker(this);
618 
619 	// not yet implemented
620 	// ToDo: this needs some changes to the whole change mechanism
621 
622 	return B_ERROR;
623 }
624 
625 
626 void
627 DataEditor::ApplyChanges()
628 {
629 	if (fLastChange == NULL && fFirstChange == NULL)
630 		return;
631 
632 	int32 firstIndex = fFirstChange != NULL ? fChanges.IndexOf(fFirstChange) + 1
633 		: 0;
634 	int32 lastIndex = fChanges.IndexOf(fLastChange);
635 
636 	if (fChangesFromSaved >= 0) {
637 		// ascend changes
638 		TRACE(("ApplyChanges(): ascend from %ld to %ld\n", firstIndex,
639 			lastIndex));
640 
641 		for (int32 i = firstIndex; i <= lastIndex; i++) {
642 			DataChange *change = fChanges.ItemAt(i);
643 			change->Apply(fRealViewOffset, fView, fRealViewSize);
644 		}
645 	} else {
646 		// descend changes
647 		TRACE(("ApplyChanges(): descend from %ld to %ld\n", firstIndex - 1,
648 			lastIndex));
649 
650 		for (int32 i = firstIndex - 1; i > lastIndex; i--) {
651 			DataChange *change = fChanges.ItemAt(i);
652 			change->Revert(fRealViewOffset, fView, fRealViewSize);
653 		}
654 	}
655 }
656 
657 
658 status_t
659 DataEditor::Save()
660 {
661 	BAutolock locker(this);
662 
663 	if (!IsModified())
664 		return B_OK;
665 
666 	StateWatcher watcher(*this);
667 
668 	// Do we need to ascend or descend the list of changes?
669 
670 	int32 firstIndex = fFirstChange != NULL ? fChanges.IndexOf(fFirstChange) + 1
671 		: 0;
672 	int32 lastIndex = fChanges.IndexOf(fLastChange);
673 	if (fChangesFromSaved < 0 && firstIndex != lastIndex) {
674 		// swap indices
675 		ASSERT(firstIndex > lastIndex);
676 
677 		int32 temp = firstIndex - 1;
678 		firstIndex = lastIndex;
679 		lastIndex = temp;
680 	}
681 
682 	if (firstIndex < 0)
683 		firstIndex = 0;
684 	if (lastIndex > fChanges.CountItems() - 1)
685 		lastIndex = fChanges.CountItems();
686 
687 	// Collect ranges of data we need to write.
688 
689 	// ToDo: This is a very simple implementation and could be drastically
690 	// improved by having items that save ranges, not just offsets. If Insert()
691 	// and Remove() are going to be implemented, it should be improved that
692 	// way to reduce the memory footprint to something acceptable.
693 
694 	BObjectList<off_t> list;
695 	for (int32 i = firstIndex; i <= lastIndex; i++) {
696 		DataChange *change = fChanges.ItemAt(i);
697 
698 		off_t offset, size;
699 		change->GetRange(FileSize(), offset, size);
700 		offset -= offset % BlockSize();
701 
702 		while (size > 0) {
703 			list.BinaryInsertCopyUnique(offset, CompareOffsets);
704 			offset += BlockSize();
705 			size -= BlockSize();
706 		}
707 	}
708 
709 	// read in data and apply changes, write it back to disk
710 
711 	off_t oldOffset = fViewOffset;
712 
713 	for (int32 i = 0; i < list.CountItems(); i++) {
714 		off_t offset = *list.ItemAt(i);
715 
716 		// load the data into our view
717 		SetViewOffset(offset, false);
718 
719 		if (fNeedsUpdate) {
720 			status_t status = Update();
721 			if (status < B_OK)
722 				return status;
723 		}
724 
725 		// save back to disk
726 
727 		// don't try to change the file size
728 		size_t size = fRealViewSize;
729 		if (fRealViewOffset + (off_t)fRealViewSize > (off_t)fSize)
730 			size = fSize - fRealViewOffset;
731 
732 		ssize_t bytesWritten;
733 		if (IsAttribute()) {
734 			bytesWritten = fFile.WriteAttr(fAttribute, fType, fRealViewOffset,
735 				fView, size);
736 		} else
737 			bytesWritten = fFile.WriteAt(fRealViewOffset, fView, size);
738 
739 		if (bytesWritten < B_OK)
740 			return bytesWritten;
741 	}
742 
743 	// update state
744 
745 	SetViewOffset(oldOffset, false);
746 	if (fNeedsUpdate)
747 		Update();
748 
749 	fChangesFromSaved = 0;
750 	fFirstChange = fLastChange;
751 
752 	return B_OK;
753 }
754 
755 
756 /** This method will be called by DataEditor::AddChange()
757  *	immediately before a change is applied.
758  *	It removes all pending redo nodes from the list that would
759  *	come after the current change.
760  */
761 
762 void
763 DataEditor::RemoveRedos()
764 {
765 	int32 start = fChanges.IndexOf(fLastChange) + 1;
766 
767 	for (int32 i = fChanges.CountItems(); i-- > start; ) {
768 		DataChange *change = fChanges.RemoveItemAt(i);
769 		delete change;
770 	}
771 }
772 
773 
774 status_t
775 DataEditor::Undo()
776 {
777 	BAutolock locker(this);
778 
779 	if (!CanUndo())
780 		return B_ERROR;
781 
782 	StateWatcher watcher(*this);
783 		// update state observers
784 
785 	DataChange *undoChange = fLastChange;
786 
787 	int32 index = fChanges.IndexOf(undoChange);
788 	fChangesFromSaved--;
789 	undoChange->Revert(fRealViewOffset, fView, fRealViewSize);
790 
791 	if (index > 0)
792 		fLastChange = fChanges.ItemAt(index - 1);
793 	else
794 		fLastChange = NULL;
795 
796 	// update observers
797 	SendNotices(undoChange);
798 
799 	return B_OK;
800 }
801 
802 
803 status_t
804 DataEditor::Redo()
805 {
806 	BAutolock locker(this);
807 
808 	if (!CanRedo())
809 		return B_ERROR;
810 
811 	StateWatcher watcher(*this);
812 		// update state observers
813 
814 	int32 index = fChanges.IndexOf(fLastChange);
815 	fLastChange = fChanges.ItemAt(index + 1);
816 	fChangesFromSaved++;
817 
818 	fLastChange->Apply(fRealViewOffset, fView, fRealViewSize);
819 
820 	// update observers
821 	SendNotices(fLastChange);
822 
823 	return B_OK;
824 }
825 
826 
827 bool
828 DataEditor::CanUndo() const
829 {
830 	return fLastChange != NULL;
831 }
832 
833 
834 bool
835 DataEditor::CanRedo() const
836 {
837 	return fChanges.IndexOf(fLastChange) < fChanges.CountItems() - 1;
838 }
839 
840 
841 status_t
842 DataEditor::SetFileSize(off_t size)
843 {
844 	// ToDo: implement me!
845 	//fSize = size;
846 	//return B_OK;
847 	return B_ERROR;
848 }
849 
850 
851 status_t
852 DataEditor::SetViewOffset(off_t offset, bool sendNotices)
853 {
854 	BAutolock locker(this);
855 
856 	if (fView == NULL) {
857 		status_t status = SetViewSize(fViewSize);
858 		if (status < B_OK)
859 			return status;
860 	}
861 
862 	if (offset < 0 || offset > fSize)
863 		return B_BAD_VALUE;
864 
865 	offset = (offset / fViewSize) * fViewSize;
866 	if (offset == fViewOffset)
867 		return B_OK;
868 
869 	fViewOffset = offset;
870 	fRealViewOffset = (fViewOffset / fBlockSize) * fBlockSize;
871 	fNeedsUpdate = true;
872 
873 	if (sendNotices) {
874 		BMessage update;
875 		update.AddInt64("offset", fViewOffset);
876 		SendNotices(kMsgDataEditorParameterChange, &update);
877 	}
878 
879 	return B_OK;
880 }
881 
882 
883 status_t
884 DataEditor::SetViewOffset(off_t offset)
885 {
886 	return SetViewOffset(offset, true);
887 }
888 
889 
890 status_t
891 DataEditor::SetViewSize(size_t size, bool sendNotices)
892 {
893 	BAutolock locker(this);
894 
895 	size_t realSize = (size + fBlockSize - 1) & ~(fBlockSize - 1);
896 		// round to the next multiple of block size
897 
898 	if (realSize == fRealViewSize && fViewSize == size && fView != NULL)
899 		return B_OK;
900 
901 	if (realSize == 0)
902 		return B_BAD_VALUE;
903 
904 	if (realSize != fRealViewSize || fView == NULL) {
905 		uint8 *view = (uint8 *)realloc(fView, realSize);
906 		if (view == NULL)
907 			return B_NO_MEMORY;
908 
909 		fView = view;
910 		fRealViewSize = realSize;
911 	}
912 
913 	fViewSize = size;
914 	fNeedsUpdate = true;
915 
916 	// let's correct the view offset if necessary
917 	if (fViewOffset % size)
918 		SetViewOffset(fViewOffset);
919 
920 	if (sendNotices) {
921 		BMessage update;
922 		update.AddInt32("view_size", size);
923 		SendNotices(kMsgDataEditorParameterChange, &update);
924 	}
925 
926 	return B_OK;
927 }
928 
929 
930 status_t
931 DataEditor::SetViewSize(size_t size)
932 {
933 	return SetViewSize(size, true);
934 }
935 
936 
937 status_t
938 DataEditor::SetBlockSize(size_t size)
939 {
940 	BAutolock locker(this);
941 
942 	fBlockSize = size;
943 	status_t status = SetViewOffset(fViewOffset, false);
944 		// this will trigger an update of the real view offset
945 	if (status == B_OK)
946 		status = SetViewSize(fViewSize, false);
947 
948 	BMessage update;
949 	update.AddInt32("block_size", size);
950 	update.AddInt64("offset", fViewOffset);
951 	SendNotices(kMsgDataEditorParameterChange, &update);
952 
953 	return status;
954 }
955 
956 
957 status_t
958 DataEditor::Update()
959 {
960 	ssize_t bytesRead;
961 	if (IsAttribute()) {
962 		BNode node(&fAttributeRef);
963 		bytesRead = node.ReadAttr(fAttribute, fType, fRealViewOffset, fView,
964 			fRealViewSize);
965 	} else
966 		bytesRead = fFile.ReadAt(fRealViewOffset, fView, fRealViewSize);
967 
968 	if (bytesRead < B_OK)
969 		return bytesRead;
970 
971 	if ((size_t)bytesRead < fRealViewSize) {
972 		// make sure the rest of data is cleared
973 		memset(fView + bytesRead, 0, fRealViewSize - bytesRead);
974 	}
975 
976 	ApplyChanges();
977 	fNeedsUpdate = false;
978 
979 	return B_OK;
980 }
981 
982 
983 status_t
984 DataEditor::UpdateIfNeeded(bool *_updated)
985 {
986 	if (!fNeedsUpdate) {
987 		if (_updated)
988 			*_updated = false;
989 		return B_OK;
990 	}
991 
992 	status_t status = B_OK;
993 
994 	if (fView == NULL)
995 		status = SetViewOffset(fViewOffset);
996 
997 	if (status == B_OK && fNeedsUpdate) {
998 		status = Update();
999 		if (status == B_OK && _updated)
1000 			*_updated = true;
1001 	}
1002 
1003 	return status;
1004 }
1005 
1006 
1007 status_t
1008 DataEditor::ForceUpdate()
1009 {
1010 	BAutolock locker(this);
1011 
1012 	status_t status = B_OK;
1013 
1014 	off_t newSize = fSize;
1015 	if (IsAttribute()) {
1016 		// update attribute size (we ignore the type for now)
1017 		attr_info info;
1018 		status = fFile.GetAttrInfo(fAttribute, &info);
1019 		if (status != B_OK) {
1020 			// The attribute may have just been removed before
1021 			// it gets rewritten, so we don't do anything
1022 			// else here (we just set the file size to 0)
1023 			newSize = 0;
1024 		} else
1025 			newSize = info.size;
1026 	} else if (!IsDevice()) {
1027 		// update file size
1028 
1029 		if (fFile.GetSize(&newSize) != B_OK)
1030 			return B_ERROR;
1031 	}
1032 
1033 	if (fSize != newSize) {
1034 		fSize = newSize;
1035 
1036 		// update observers
1037 		BMessage update;
1038 		update.AddInt64("file_size", newSize);
1039 		SendNotices(kMsgDataEditorParameterChange, &update);
1040 	}
1041 
1042 	if (fView == NULL)
1043 		status = SetViewOffset(fViewOffset);
1044 	else {
1045 		BMessage update;
1046 		update.AddInt64("offset", fViewOffset);
1047 		update.AddInt64("size", fViewSize);
1048 		SendNotices(kMsgDataEditorUpdate, &update);
1049 	}
1050 
1051 	if (status == B_OK)
1052 		status = Update();
1053 
1054 	return status;
1055 }
1056 
1057 
1058 status_t
1059 DataEditor::GetViewBuffer(const uint8 **_buffer)
1060 {
1061 	if (!IsLocked())
1062 		debugger("DataEditor: view not locked");
1063 
1064 	status_t status = UpdateIfNeeded();
1065 	if (status != B_OK)
1066 		return status;
1067 	if (fView == NULL)
1068 		return B_NO_INIT;
1069 
1070 	*_buffer = fView + fViewOffset - fRealViewOffset;
1071 	return B_OK;
1072 }
1073 
1074 
1075 off_t
1076 DataEditor::Find(off_t startPosition, const uint8 *data, size_t dataSize,
1077 	bool caseInsensitive, bool cyclic, BMessenger progressMonitor,
1078 	volatile bool *stop)
1079 {
1080 	if (data == NULL || dataSize == 0)
1081 		return B_BAD_VALUE;
1082 
1083 	if (startPosition < 0)
1084 		startPosition = 0;
1085 
1086 	BAutolock locker(this);
1087 
1088 	typedef int (*compare_func)(const uint8 *a, const uint8 *b, size_t size);
1089 	compare_func compareFunc;
1090 	if (caseInsensitive)
1091 		compareFunc = CompareCaseInsensitive;
1092 	else
1093 		compareFunc = (compare_func)memcmp;
1094 
1095 	bool savedIsReadOnly = fIsReadOnly;
1096 	fIsReadOnly = true;
1097 	off_t savedOffset = fViewOffset;
1098 	off_t position = (startPosition / fRealViewSize) * fRealViewSize;
1099 	size_t firstByte = startPosition % fRealViewSize;
1100 	size_t matchLastOffset = 0;
1101 	off_t foundAt = B_ENTRY_NOT_FOUND;
1102 	bool noStop = false;
1103 	if (stop == NULL)
1104 		stop = &noStop;
1105 
1106 	// start progress monitor
1107 	{
1108 		BMessage progress(kMsgDataEditorFindProgress);
1109 		progress.AddBool("running", true);
1110 		progressMonitor.SendMessage(&progress);
1111 	}
1112 
1113 	bigtime_t lastReport = 0;
1114 
1115 	off_t blocks = fSize;
1116 	if (!cyclic)
1117 		blocks -= position;
1118 	blocks = (blocks + fRealViewSize - 1) / fRealViewSize;
1119 
1120 	for (; blocks-- > 0 && !*stop; position += fRealViewSize) {
1121 		if (position > fSize)
1122 			position = 0;
1123 
1124 		SetViewOffset(position, false);
1125 		if (fNeedsUpdate)
1126 			Update();
1127 
1128 		bigtime_t current = system_time();
1129 		if (lastReport + 500000LL < current) {
1130 			// report the progress two times a second
1131 			BMessage progress(kMsgDataEditorFindProgress);
1132 			progress.AddInt64("position", position);
1133 			progressMonitor.SendMessage(&progress);
1134 
1135 			lastReport = current;
1136 		}
1137 
1138 		// search for data in current block
1139 
1140 		if (matchLastOffset != 0) {
1141 			// we had a partial match in the previous block, let's
1142 			// check if it is a whole match
1143 			if (!compareFunc(fView, data + matchLastOffset,
1144 					dataSize - matchLastOffset)) {
1145 				matchLastOffset = 0;
1146 				break;
1147 			}
1148 
1149 			foundAt = B_ENTRY_NOT_FOUND;
1150 			matchLastOffset = 0;
1151 		}
1152 
1153 		for (size_t i = firstByte; i < fRealViewSize; i++) {
1154 			if (position + (off_t)(i + dataSize) > (off_t)fSize)
1155 				break;
1156 
1157 			if (!compareFunc(fView + i, data, 1)) {
1158 				// one byte matches, compare the rest
1159 				size_t size = dataSize - 1;
1160 				size_t offset = i + 1;
1161 
1162 				if (offset + size > fRealViewSize)
1163 					size = fRealViewSize - offset;
1164 
1165 				if (size == 0 || !compareFunc(fView + offset, data + 1, size)) {
1166 					foundAt = position + i;
1167 
1168 					if (size != dataSize - 1) {
1169 						// only a partial match, we have to check the start
1170 						// of the next block
1171 						matchLastOffset = size + 1;
1172 					}
1173 					break;
1174 				}
1175 			}
1176 		}
1177 
1178 		if (foundAt >= 0 && matchLastOffset == 0)
1179 			break;
1180 
1181 		firstByte = 0;
1182 	}
1183 
1184 	fIsReadOnly = savedIsReadOnly;
1185 
1186 	if (foundAt >= 0 && matchLastOffset != 0)
1187 		foundAt = B_ENTRY_NOT_FOUND;
1188 
1189 	// stop progress monitor
1190 	{
1191 		BMessage progress(kMsgDataEditorFindProgress);
1192 		progress.AddBool("running", false);
1193 		progress.AddInt64("position", foundAt >= 0 ? foundAt : savedOffset);
1194 		progressMonitor.SendMessage(&progress);
1195 	}
1196 
1197 	SetViewOffset(savedOffset, false);
1198 		// this is to ensure that we're sending an update when
1199 		// we're set to the found data offset
1200 
1201 	if (foundAt < 0 && *stop)
1202 		return B_INTERRUPTED;
1203 
1204 	return foundAt;
1205 }
1206 
1207 
1208 void
1209 DataEditor::SendNotices(DataChange *change)
1210 {
1211 	off_t offset, size;
1212 	change->GetRange(FileSize(), offset, size);
1213 
1214 	// update observer
1215 	BMessage update;
1216 	update.AddInt64("offset", offset);
1217 	update.AddInt64("size", size);
1218 	SendNotices(kMsgDataEditorUpdate, &update);
1219 }
1220 
1221 
1222 void
1223 DataEditor::SendNotices(uint32 what, BMessage *message)
1224 {
1225 	if (fObservers.CountItems() == 0)
1226 		return;
1227 
1228 	BMessage *notice;
1229 	if (message) {
1230 		notice = new BMessage(*message);
1231 		notice->what = what;
1232 	} else
1233 		notice = new BMessage(what);
1234 
1235 	for (int32 i = fObservers.CountItems(); i-- > 0;) {
1236 		BMessenger *messenger = fObservers.ItemAt(i);
1237 		messenger->SendMessage(notice);
1238 	}
1239 
1240 	delete notice;
1241 }
1242 
1243 
1244 status_t
1245 DataEditor::StartWatching(BMessenger target)
1246 {
1247 	BAutolock locker(this);
1248 
1249 	node_ref node;
1250 	status_t status = fFile.GetNodeRef(&node);
1251 	if (status < B_OK)
1252 		return status;
1253 
1254 	fObservers.AddItem(new BMessenger(target));
1255 
1256 	return watch_node(&node, B_WATCH_STAT | B_WATCH_ATTR, target);
1257 }
1258 
1259 
1260 status_t
1261 DataEditor::StartWatching(BHandler *handler, BLooper *looper)
1262 {
1263 	return StartWatching(BMessenger(handler, looper));
1264 }
1265 
1266 
1267 void
1268 DataEditor::StopWatching(BMessenger target)
1269 {
1270 	BAutolock locker(this);
1271 
1272 	for (int32 i = fObservers.CountItems(); i-- > 0;) {
1273 		BMessenger *messenger = fObservers.ItemAt(i);
1274 		if (*messenger == target) {
1275 			fObservers.RemoveItemAt(i);
1276 			delete messenger;
1277 			break;
1278 		}
1279 	}
1280 
1281 	stop_watching(target);
1282 }
1283 
1284 
1285 void
1286 DataEditor::StopWatching(BHandler *handler, BLooper *looper)
1287 {
1288 	StopWatching(BMessenger(handler, looper));
1289 }
1290 
1291