Fix buffer stomping; retool Makefile.

The Makefile is now useful for building ke(1) to aid in more rapid
development. This works because ke doesn't (yet) need any special
non-system libraries.
This commit is contained in:
Kyle Isom 2023-10-12 15:15:09 -07:00
parent ba27e132e3
commit 7cec414e3d
7 changed files with 161 additions and 27 deletions

4
.gitignore vendored
View File

@ -15,3 +15,7 @@ cmake-build-*
ke ke
kge kge
# trashme is a test file generated to test the editor on without ruining
# other files.
trashme.txt

View File

@ -23,6 +23,7 @@
#include <filesystem> #include <filesystem>
#include <iostream>
#include <iterator> #include <iterator>
#include <optional> #include <optional>
#include <random> #include <random>
@ -31,6 +32,9 @@
#include "Buffer.h" #include "Buffer.h"
static constexpr auto statusOK = Buffer::FileStatus::FileStatusOK;
static std::string static std::string
anonymousName() anonymousName()
{ {
@ -45,7 +49,7 @@ anonymousName()
Buffer::Buffer() Buffer::Buffer()
: dirty(false), name(anonymousName()) : dirty(false), name(anonymousName()), status(statusOK)
{ {
} }
@ -61,9 +65,14 @@ Buffer::Buffer(std::filesystem::path fPath)
: dirty(false), path(OptString(fPath.string())) : dirty(false), path(OptString(fPath.string()))
{ {
this->name = fPath.filename().string(); this->name = fPath.filename().string();
this->Cursor() = Cursor(); this->cursor = Cursor();
this->file = OptFile(fPath.string()); this->file = OptFile(fPath.string());
if (this->Exists()) {
std::cout << "file exists, refreshing\n";
/// \todo Should I signal an error here, or is it
/// okay for this to be a best-effort thing?
this->status = this->Refresh();
}
} }
@ -106,6 +115,7 @@ Buffer::Flush(OptString altPath)
return Buffer::FileStatus::FileStatusInvalidPermissions; return Buffer::FileStatus::FileStatusInvalidPermissions;
} }
realFile.MarkReadOnly();
return Buffer::FileStatus::FileStatusIOFailed; return Buffer::FileStatus::FileStatusIOFailed;
} }
@ -131,13 +141,17 @@ Buffer::Flush(OptString altPath)
Buffer::FileStatus Buffer::FileStatus
Buffer::Refresh() Buffer::Refresh()
{ {
/// We can't actually refresh a virtual buffer. Unlike flush,
/// it doesn't make sense to Refresh from an alternate file.
if (this->IsVirtual()) { if (this->IsVirtual()) {
std::cerr << "[!] virtual file, bailing\n";
return Buffer::FileStatus::FileStatusVirtual; return Buffer::FileStatus::FileStatusVirtual;
} }
auto realFile = this->file.value(); auto realFile = this->file.value();
auto handle = realFile.Refresh(); auto handle = realFile.Refresh();
if (!handle) { if (!handle) {
std::cerr << "[!] file doesn't exist\n";
return FileStatus::FileStatusNonExistent; return FileStatus::FileStatusNonExistent;
} }
@ -147,8 +161,11 @@ Buffer::Refresh()
/// \todo Moral quandary: if the file disappears from disk, we /// \todo Moral quandary: if the file disappears from disk, we
/// probably want to know that, but maybe clearing the /// probably want to know that, but maybe clearing the
/// contents first isn't the right move. /// contents first isn't the right move. Maybe mark the
/// file as ReadOnly?
if (!realHandle->good()) { if (!realHandle->good()) {
std::cerr << "[!] handle isn't good";
realHandle->close();
delete realHandle; delete realHandle;
return FileStatus::FileStatusIOFailed; return FileStatus::FileStatusIOFailed;
} }
@ -167,6 +184,7 @@ Buffer::Refresh()
status = FileStatus::FileStatusIOFailed; status = FileStatus::FileStatusIOFailed;
} }
realHandle->close(); realHandle->close();
delete realHandle;
return status; return status;
} }
@ -246,3 +264,52 @@ Buffer::FileStatusToString(Buffer::FileStatus status)
} }
} }
bool
Buffer::Exists()
{
if (!this->file) {
return false;
}
return this->file.value().Exists();
}
void
Buffer::PrintBufferStatus(std::ostream &os)
{
os << "buffer:<" << this->name << ">[";
if (this->IsVirtual()) {
os << "virt]";
} else {
os << "file:";
if (this->Exists()) {
os << "real";
} else {
os << "new";
}
os << ":";
auto &realFile = this->file.value();
if (realFile.IsReadWriteable()) {
os << "rw";
} else if (realFile.IsReadable()) {
os << "r-";
} else if (realFile.IsWriteable()) {
os << "-w";
} else {
os << " ";
}
if (realFile.IsReadOnly()) {
os << "@";
} else {
os << " ";
}
os << ":" << this->Size() << "
}
return os;
}

View File

@ -36,27 +36,37 @@ public:
/// The file operation succeeded correctly. /// The file operation succeeded correctly.
FileStatusOK = 0, FileStatusOK = 0,
/// There was an error, but it wasn't handled correctly.
/// This is mostly for debugging purposes, and shouldn't
/// be seen by users.
///
/// \detail This is set at the beginning of failable
/// operations, and indicates that the right
/// status wasn't set correctly.
FileStatusUnspecifiedError = 1,
/// The file can't be written to because it is marked /// The file can't be written to because it is marked
/// read-only. This refers to a buffer being marked as /// read-only. This refers to a buffer being marked as
/// read-only, not to whether the underlying file is /// read-only, not to whether the underlying file is
/// actually read-only. /// actually read-only.
FileStatusReadOnly = 1, FileStatusReadOnly = 2,
/// There was an I/O error trying to write to the file. /// There was an I/O error trying to write to the file.
FileStatusIOFailed = 2, FileStatusIOFailed = 3,
/// The Buffer couldn't be flushed because it is a virtual /// The Buffer couldn't be flushed because it is a virtual
/// buffer. If the user explicitly tried to save the buffer, /// buffer. If the user explicitly tried to save the buffer,
/// they should be prompted for a path. /// they should be prompted for a path.
FileStatusVirtual = 3, FileStatusVirtual = 4,
/// The underlying file doesn't have the right permissions; /// The underlying file doesn't have the right permissions;
/// for example, it's not writeable if the user is trying to /// for example, it's not writeable if the user is trying to
/// write the file. /// write the file.
FileStatusInvalidPermissions = 4, FileStatusInvalidPermissions = 5,
/// The underlying file doesn't exist. /// The underlying file doesn't exist.
FileStatusNonExistent = 5, FileStatusNonExistent = 6,
}; };
static std::string FileStatusToString(FileStatus status); static std::string FileStatusToString(FileStatus status);
@ -100,7 +110,7 @@ public:
void ChangePath(std::string newPath); void ChangePath(std::string newPath);
Cursor Cursor() Cursor Where()
{ return this->cursor; } { return this->cursor; }
void MarkDirty(); void MarkDirty();
@ -109,16 +119,25 @@ public:
size_t Size(); size_t Size();
/// Does this buffer have a current, on-disk file?
///
/// \return True if this is a file buffer with an extant source
/// file.
bool Exists();
/// PrintBufferStatus is a debug tool to capture the current
/// buffer state.
void PrintBufferStatus(std::ostream &os);
private: private:
void clearContents(); void clearContents();
class Cursor cursor; Cursor cursor;
bool dirty; bool dirty;
std::string name; std::string name;
OptString path; OptString path;
OptFile file; OptFile file;
BufferContents contents; BufferContents contents;
FileStatus status;
}; };

View File

@ -64,10 +64,10 @@ OptOutFileStream File::Flush()
return std::nullopt; return std::nullopt;
} }
return {new std::ofstream(this->path, this->mode|std::ios::trunc)}; return {new std::ofstream(this->path, std::ios::out|std::ios::trunc)};
} }
return {new std::ofstream(this->path, this->mode)}; return {new std::ofstream(this->path, std::ios::out)};
} }
@ -78,7 +78,7 @@ File::Refresh()
return std::nullopt; return std::nullopt;
} }
return {new std::ifstream(this->path, this->mode)}; return {new std::ifstream(this->path, std::ios::in)};
} }

2
File.h
View File

@ -80,12 +80,10 @@ public:
/// IsReadWriteable checks whether the current user has both /// IsReadWriteable checks whether the current user has both
/// read and write permissions on the file. /// read and write permissions on the file.
bool IsReadWriteable(); bool IsReadWriteable();
private: private:
std::filesystem::path path; std::filesystem::path path;
bool readOnly; bool readOnly;
std::ios::openmode mode;
}; };

View File

@ -1,7 +1,36 @@
.PHONY: all ### quick Makefile to build ke in the top-level. It will never be used
all: ### kge; ke doesn't use any non-system libraries. vim, emacs, and joe
mkdir -p build && cd build && cmake .. && make ### all support building from a Makefile in the root directory of the
### project, so it's useful to quickly test builds when editing remote.
CC := clang
CXX := clang++
CXXFLAGS := -Werror -Wall -Wextra -g -fsanitize=address -std=c++17
LDFLAGS :=
SOURCES := Buffer.cc Buffer.h \
Cursor.cc Cursor.h \
Defs.cc Defs.h \
File.cc File.h \
main.cc
OBJS := $(patsubst %.cc,%.o,$(filter %.cc,$(SOURCES)))
TARGET := ke
.PHONY: all
all: tags trashme $(TARGET)
./$(TARGET) trashme.txt
.PHONY: trashme
trashme:
cp Makefile trashme.txt
.PHONY: cmake
cmake: build
cd build && cmake --config Debug --build ..
build:
mkdir -p build
# TODO: run these from ./scripts/install-dependencies.sh
PHONY: deps PHONY: deps
deps: deps:
sudo apt-get install doxygen scdoc sudo apt-get install doxygen scdoc
@ -12,4 +41,20 @@ gui-deps:
.PHONY: .PHONY:
clean: clean:
rm -f *.o $(TARGET)
tags: $(SOURCES)
ctags --declarations -o $@ $(SOURCES)
ke: $(OBJS)
$(CXX) -o $@ $(CXXFLAGS) $(LDFLAGS) $(OBJS)
.PHONY: cclean
cclean:
rm -r build cmake-build-* rm -r build cmake-build-*
print-%: ; @echo '$(subst ','\'',$*=$($*))'
%.o:%.cc
$(CXX) -c -o $@ $(CXXFLAGS) $<

View File

@ -56,11 +56,11 @@ main(int argc, char *argv[])
for (int i = 1; i < argc; i++) { for (int i = 1; i < argc; i++) {
std::filesystem::path path(argv[i]); std::filesystem::path path(argv[i]);
std::cout << "[+] target: " << path << "\n "; std::cout << "[+] target: " << path << "\n";
auto buffer = Buffer(path); auto buffer = Buffer(path);
std::cout << "\t[+] created buffer " << buffer.Name() << "\n"; std::cout << "\t[+] created buffer " << buffer.Name() << "\n";
auto status = buffer.Refresh(); /*
if (!Buffer::StatusOK(status)) { if (!Buffer::StatusOK(status)) {
std::cerr << "[!] failed to read buffer "; std::cerr << "[!] failed to read buffer ";
std::cerr << buffer.Name() << "\n"; std::cerr << buffer.Name() << "\n";
@ -69,6 +69,7 @@ main(int argc, char *argv[])
std::cerr << "\n"; std::cerr << "\n";
continue; continue;
} }
*/
std::cout << "\t[+] loaded buffer " << buffer.Name() std::cout << "\t[+] loaded buffer " << buffer.Name()
<< " of " << buffer.Size() << " bytes.\n"; << " of " << buffer.Size() << " bytes.\n";