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