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