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