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