/*
* $Id: fma_gedit.c,v 1.4 2025/03/30 01:36:58 snw Exp $
* FreeM global editor
*
*
* Author: Serena Willis <snw@coherent-logic.com>
* Copyright (C) 1998 MUG Deutschland
* Copyright (C) 2023, 2025 Coherent Logic Development LLC
*
*
* This file is part of FreeM.
*
* FreeM is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FreeM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero Public License for more details.
*
* You should have received a copy of the GNU Affero Public License
* along with FreeM. If not, see <https://www.gnu.org/licenses/>.
*
* $Log: fma_gedit.c,v $
* Revision 1.4 2025/03/30 01:36:58 snw
* Make it easier to bring back fma_gedit, fix double-free in global handler, limit $CHAR to 7-bit ASCII
*
* Revision 1.3 2025/03/09 19:14:24 snw
* First phase of REUSE compliance and header reformat
*
*
* SPDX-FileCopyrightText: (C) 2025 Coherent Logic Development LLC
* SPDX-License-Identifier: AGPL-3.0-or-later
**/
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "fmadm.h"
#include "fma_globals.h"
#if defined HAVE_NCURSESW_CURSES_H
# include <ncursesw/curses.h>
#elif defined HAVE_NCURSESW_H
# include <ncursesw.h>
#elif defined HAVE_NCURSES_CURSES_H
# include <ncurses/curses.h>
#elif defined HAVE_NCURSES_H
# include <ncurses.h>
#elif defined HAVE_CURSES_H
# include <curses.h>
#else
# error "SysV or X/Open-compatible Curses header file required"
#endif
#define GE_MXGBL 100
typedef struct ge_key {
char key[256];
char data[256];
struct ge_key *next;
} ge_key;
typedef struct ge_blockinfo {
int keylen;
int keyoffs;
char key[STRLEN];
int datalen;
char data[STRLEN];
long llptr;
long rlptr;
long offset;
long blockcount;
int collation;
int btype;
char bt_desc[40];
long free_offset;
long keyct;
ge_key *key_head;
} ge_blockinfo;
typedef struct ge_buf {
char name[256];
char namespace[256];
char pth[4096];
short fd;
long blockct;
long gblsize;
int blocknum;
char block_r[BLOCKLEN];
ge_blockinfo block;
short block_dirty;
short is_free;
} ge_buf;
ge_buf ge_buffers[GE_MXGBL];
int ge_curbuf;
int ge_nextbuf;
int ge_rows;
int ge_columns;
WINDOW *wge_key;
WINDOW *wge_key_border;
WINDOW *wge_node;
WINDOW *wge_node_border;
WINDOW *wge_msg_border;
WINDOW *wge_msg;
WINDOW *wge_block_border;
WINDOW *wge_block;
WINDOW *wge_global_border;
WINDOW *wge_global;
WINDOW *wge_command;
WINDOW *wge_cur;
void ge_set_wintitle(WINDOW *border_window, char *title);
void ge_wininit(void);
void ge_update_gbl(void);
void ge_select_buffer(int buf);
short ge_select_block(int buf, long blocknum);
void ge_update_block(void);
int ge_open_global(char *gblname);
void ge_init_buffers(void);
void ge_eventloop(void);
void ge_decode_key(const char *key, char *buf);
void ge_win_next(void);
void ge_win_previous(void);
#define SPC ' '
int fma_globals_edit(int optc, char **opts)
{
int editbuf;
ge_curbuf = 0;
ge_nextbuf = 0;
ge_init_buffers ();
initscr ();
ge_wininit ();
wge_cur = wge_command;
wprintw (wge_msg, "FreeM Global Editor\n");
wprintw (wge_msg, " Copyright (C) 2023 Coherent Logic Development LLC\n\n");
wprintw(wge_msg, "optc = %d\n\n", optc);
wattron (wge_msg, A_BOLD);
wprintw (wge_msg, "Left and right arrow keys navigate blocks; 'q' to quit.\n\n");
wattroff (wge_msg, A_BOLD);
wrefresh (wge_msg);
if (optc == 2) {
if ((editbuf = ge_open_global (opts[fma_base_opt])) == -1) {
wprintw (wge_msg, "fmadm: cannot open global %s\n", opts[fma_base_opt]);
}
else {
ge_select_buffer (editbuf);
}
}
ge_eventloop ();
endwin ();
return 0;
}
void ge_win_next(void)
{
/*
switch (wge_cur) {
case wge_key:
wge_cur = wge_node;
break;
case wge_node:
wge_cur = wge_global;
break;
case wge_global:
wge_cur = wge_block;
break;
case wge_block:
wge_cur = wge_command;
break;
case wge_command:
wge_cur = wge_msg;
break;
case wge_msg:
wge_cur = wge_key;
break;
}
*/
}
void ge_win_previous(void)
{
}
void ge_eventloop(void)
{
int c;
short quit_flag;
quit_flag = FALSE;
while (!quit_flag) {
noecho ();
c = wgetch (wge_command);
echo ();
switch (c) {
case 'q':
case 'Q':
quit_flag = TRUE;
break;
case KEY_LEFT:
if (ge_buffers[ge_curbuf].blocknum != 0) {
ge_select_block (ge_curbuf, ge_buffers[ge_curbuf].blocknum - 1);
}
else {
ge_select_block (ge_curbuf, ge_buffers[ge_curbuf].blockct - 1);
}
break;
case KEY_RIGHT:
if (ge_buffers[ge_curbuf].blocknum + 1 < ge_buffers[ge_curbuf].blockct) {
ge_select_block (ge_curbuf, ge_buffers[ge_curbuf].blocknum + 1);
}
else {
ge_select_block (ge_curbuf, 0);
}
break;
}
}
}
void ge_init_buffers(void)
{
register int i;
register int j;
for (i = 0; i < GE_MXGBL; i++) {
ge_buffers[i].name[0] = '\0';
ge_buffers[i].namespace[0] = '\0';
ge_buffers[i].pth[0] = '\0';
ge_buffers[i].fd = 0;
ge_buffers[i].blocknum = 0;
ge_buffers[i].block_dirty = FALSE;
ge_buffers[i].is_free = TRUE;
ge_buffers[i].blockct = 0;
ge_buffers[i].gblsize = 0;
for (j = 0; j < BLOCKLEN; j++) {
ge_buffers[i].block_r[j] = '\0';
}
}
}
void ge_wininit(void)
{
int half;
getmaxyx (stdscr, ge_rows, ge_columns);
half = ge_rows / 2;
/* messages window */
wge_msg_border = newwin (10, ge_columns - 41, ge_rows - 12, 0);
wge_msg = newwin (8, ge_columns - 43, ge_rows - 11, 1);
ge_set_wintitle (wge_msg_border, "Messages");
wrefresh (wge_msg_border);
wrefresh (wge_msg);
/* global window */
wge_global_border = newwin (half, 40, 0, ge_columns - 40);
wge_global = newwin (half - 2, 38, 1, ge_columns - 39);
ge_set_wintitle (wge_global_border, "No Global Selected");
wrefresh (wge_global_border);
/* block window */
wge_block_border = newwin (half - 1, 40, half, ge_columns - 40);
wge_block = newwin (half - 3, 37, half + 1, ge_columns - 38);
ge_set_wintitle (wge_block_border, "Block");
wrefresh (wge_block_border);
/* command window */
wge_command = newwin (1, ge_columns, ge_rows - 1, 0);
wrefresh (wge_command);
scrollok (wge_msg, TRUE);
keypad (wge_command, TRUE);
keypad (wge_msg, TRUE);
keypad (wge_global, TRUE);
keypad (wge_block, TRUE);
curs_set (0);
}
void ge_update_gbl(void)
{
char wintit[4096];
wclear (wge_global);
snprintf (wintit, 4095, "%s [#%d]", ge_buffers[ge_curbuf].name, ge_curbuf);
ge_set_wintitle (wge_global_border, wintit);
wattron (wge_global, A_BOLD);
wprintw (wge_global, "GLOBAL: ");
wattroff (wge_global, A_BOLD);
wprintw (wge_global, "%s\n", ge_buffers[ge_curbuf].name);
wattron (wge_global, A_BOLD);
wprintw (wge_global, "NAMESPACE: ");
wattroff (wge_global, A_BOLD);
wprintw (wge_global, "%s\n", ge_buffers[ge_curbuf].namespace);
wattron (wge_global, A_BOLD);
wprintw (wge_global, "BLOCK SIZE: ");
wattroff (wge_global, A_BOLD);
wprintw (wge_global, "%d bytes\n", BLOCKLEN);
wattron (wge_global, A_BOLD);
wprintw (wge_global, "BLOCK COUNT: ");
wattroff (wge_global, A_BOLD);
wprintw (wge_global, "%d\n", ge_buffers[ge_curbuf].blockct);
wattron (wge_global, A_BOLD);
wprintw (wge_global, "FILE SIZE: ");
wattroff (wge_global, A_BOLD);
wprintw (wge_global, "%d bytes\n", ge_buffers[ge_curbuf].gblsize);
wrefresh (wge_global);
}
void ge_select_buffer(int buf)
{
wprintw (wge_msg, "Selected buffer %d\n", buf);
wrefresh (wge_msg);
ge_curbuf = buf;
ge_update_gbl ();
}
short ge_select_block(int buf, long blocknum)
{
ge_blockinfo *b;
char *br;
char key[256];
char decoded[4096];
long i;
long j;
long k;
long length;
if ((blocknum < 0) || (blocknum > (ge_buffers[buf].blockct - 1))) {
wprintw (wge_msg, "Block number for global %s must be between 0 and %d\n", ge_buffers[buf].name, ge_buffers[buf].blockct - 1);
return FALSE;
}
b = &(ge_buffers[buf].block);
br = ge_buffers[buf].block_r;
lseek (ge_buffers[buf].fd, blocknum * BLOCKLEN, 0);
read (ge_buffers[buf].fd, br, BLOCKLEN);
ge_buffers[buf].blocknum = blocknum;
ge_buffers[buf].block_dirty = FALSE;
b->btype = br[BTYP];
switch (b->btype) {
case DATA:
snprintf (b->bt_desc, 39, "DATA");
break;
case POINTER:
snprintf (b->bt_desc, 39, "POINTER");
break;
case BOTTOM:
snprintf (b->bt_desc, 39, "BTM PTR");
break;
case EMPTY:
snprintf (b->bt_desc, 39, "EMPTY");
break;
case FBLK:
snprintf (b->bt_desc, 39, "FBLK");
break;
default:
snprintf (b->bt_desc, 39, "ILLEGAL TYPE");
break;
}
if (blocknum == ROOT) strcat (b->bt_desc, " [ROOT]");
if (blocknum != ROOT) {
b->llptr = UNSIGN (br[LLPTR]) * 65536 + UNSIGN (br[LLPTR + 1]) * 256 + UNSIGN(br[LLPTR + 2]);
b->rlptr = UNSIGN (br[RLPTR]) * 65536 + UNSIGN (br[RLPTR + 1]) * 256 + UNSIGN(br[RLPTR + 2]);
}
else {
b->blockcount = UNSIGN (br[LLPTR]) * 65536 + UNSIGN (br[LLPTR + 1]) * 256 + UNSIGN(br[LLPTR + 2]);
b->free_offset = UNSIGN (br[RLPTR]) * 65536 + UNSIGN (br[RLPTR + 1]) * 256 + UNSIGN(br[RLPTR + 2]);
}
b->offset = UNSIGN (br[OFFS]) * 256 + UNSIGN (br[OFFS + 1]);
b->keyct = 0;
if (b->btype == FBLK) goto skip_keydec;
i = 0;
while (i < b->offset) {
length = UNSIGN (br[i++]);
k = UNSIGN (br[i++]);
if ((i + length) > b->offset) break;
for (j = 0; j < length; j++) {
key[k++] = br[i++];
}
key[k] = g_EOL;
ge_decode_key (key, decoded);
decoded[stlen (decoded)] = '\0';
wprintw (wge_msg, "found key %s\n", decoded);
b->keyct++;
}
skip_keydec:
ge_update_block ();
return TRUE;
}
void ge_decode_key(const char *key, char *buf)
{
int ch;
short ch0;
short i;
short j;
short k;
short typ;
j = 0;
i = 0;
k = 1;
buf[j++] = '(';
while ((ch = UNSIGN (key[i++])) != g_EOL) {
if (k) {
k = 0;
if ((typ = (ch > SPC))) {
buf[j++] = '"';
}
}
ch0 = (ch >= SPC ? (ch >> 1) : (ch < 20 ? (ch >> 1) + '0' : (ch >> 1) + SPC));
if (ch0 == DEL) {
if (((ch = UNSIGN (key[i++])) >> 1) == DEL) {
ch0 += DEL;
ch = UNSIGN (key[i++]);
}
ch0 += (ch >> 1);
buf[j] = '<';
buf[++j] = '0' + ch0 / 100;
buf[++j] = '0' + (ch0 % 100) / 10;
buf[++j] = '0' + ch0 % 10;
buf[++j] = '>';
}
else {
buf[j] = ch0;
}
if (buf[j++] == '"') {
buf[j++] = '"';
}
if (ch & 01) {
if (typ) buf[j++] = '"';
buf[j++] = ',';
k = 1;
}
}
buf[j--] = 0;
buf[j] = ')';
if (j == 0) buf[0] = 0;
while (j >= 0) {
if ((ch = buf[--j]) < SPC || ch >= DEL) break;
}
}
void ge_update_block(void)
{
char wintit[4096];
wclear (wge_block);
snprintf (wintit, 4095, "Block %d of %d", ge_buffers[ge_curbuf].blocknum, ge_buffers[ge_curbuf].blockct);
ge_set_wintitle (wge_block_border, wintit);
wattron (wge_block, A_BOLD);
wprintw (wge_block, "TYPE: ");
wattroff (wge_block, A_BOLD);
wprintw (wge_block, "%s\n", ge_buffers[ge_curbuf].block.bt_desc);
if (ge_buffers[ge_curbuf].blocknum != ROOT) {
wattron (wge_block, A_BOLD);
wprintw (wge_block, "LEFT LINK POINTER: ");
wattroff (wge_block, A_BOLD);
wprintw (wge_block, "%d\n", ge_buffers[ge_curbuf].block.llptr);
wattron (wge_block, A_BOLD);
wprintw (wge_block, "RIGHT LINK POINTER: ");
wattroff (wge_block, A_BOLD);
wprintw (wge_block, "%d\n", ge_buffers[ge_curbuf].block.rlptr);
}
else {
wattron (wge_block, A_BOLD);
wprintw (wge_block, "BLOCK COUNT: ");
wattroff (wge_block, A_BOLD);
wprintw (wge_block, "%d\n", ge_buffers[ge_curbuf].block.blockcount);
wattron (wge_block, A_BOLD);
wprintw (wge_block, "FREE OFFSET: ");
wattroff (wge_block, A_BOLD);
wprintw (wge_block, "%d\n", ge_buffers[ge_curbuf].block.free_offset);
}
wattron (wge_block, A_BOLD);
wprintw (wge_block, "KEY COUNT: ");
wattroff (wge_block, A_BOLD);
wprintw (wge_block, "%d\n", ge_buffers[ge_curbuf].block.keyct);
wrefresh (wge_block);
}
int ge_open_global(char *gblname)
{
char gpath[4096];
int buf;
struct stat sb;
buf = ge_nextbuf++;
snprintf (gpath, 4095, "%s/%s", fma_global_path, gblname);
wprintw (wge_msg, "Opening global %s [path %s, namespace %s]... ", gblname, gpath, fma_namespace);
wrefresh (wge_msg);
if ((ge_buffers[buf].fd = open (gpath, 0)) == -1) {
wprintw (wge_msg, "[FAIL]\n");
wrefresh (wge_msg);
return -1;
}
else {
wprintw (wge_msg, "[OK]\n");
wrefresh (wge_msg);
}
fstat (ge_buffers[buf].fd, &sb);
ge_buffers[buf].gblsize = sb.st_size;
ge_buffers[buf].blockct = sb.st_size / BLOCKLEN;
strcpy (ge_buffers[buf].name, gblname);
strcpy (ge_buffers[buf].namespace, fma_namespace);
strcpy (ge_buffers[buf].pth, gpath);
ge_buffers[buf].blocknum = 0;
ge_buffers[buf].block_dirty = FALSE;
ge_curbuf = buf;
ge_select_block (buf, 0);
return buf;
}
void ge_set_wintitle(WINDOW *border_window, char *title)
{
box (border_window, 0, 0);
wattron (border_window, A_BOLD);
mvwprintw (border_window, 0, 3, title);
wattroff (border_window, A_BOLD);
wrefresh (border_window);
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>