File:  [Coherent Logic Development] / freem / src / fma_gedit.c
Revision 1.4: download - view: text, annotated - select for diffs
Sun Mar 30 01:36:58 2025 UTC (2 days, 22 hours ago) by snw
Branches: MAIN
CVS tags: HEAD
Make it easier to bring back fma_gedit, fix double-free in global handler, limit $CHAR to 7-bit ASCII

/*
 *   $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>