0

I am trying to debug my call to termios function tcgetattr(). It returns -1 as the result and I searched internet to find out more details about what the call fails. I found that I can make a call to explain_tcgetattr() to get more details about the error. In the code below I added the method signature to my interface so that the proxy can implement it, but I get the exception I have added below.

My code:

package org.example;

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Structure;

import java.io.IOException;
import java.util.Arrays;

public class App {

    private static LibC.Termios originalAttributes;
    private static int rows = 10;
    private static int columns = 10;

    public static void main(String[] args) throws IOException {
        // System.out.println("Hello World");
        /*System.out.println("\033[4;44;31mHello World\033[0mHello");
        System.out.println("\033[2J");
        System.out.println("\033[5H");*/

        enableRawMode();
        initEditor();

        while (true){
            refreshScreen();
            int key = readKey();
            handleKey(key);
        }

    }

    private static void initEditor() {
        LibC.Winsize windowSize = getWindowSize();
        columns = windowSize.ws_col;
        rows = windowSize.ws_row;
    }

    private static void refreshScreen() {
        StringBuilder builder = new StringBuilder();

        builder.append("\033[2J");
        builder.append("\033[H");

        for (int i = 0; i < rows - 1; i++) {
            builder.append("~\r\n");
        }

        String statusMessage = "Marco Code's Editor - v0.0.1";
        builder.append("\033[7m")
                .append(statusMessage)
                .append(" ".repeat(Math.max(0, columns - statusMessage.length())))
                .append("\033[0m");

        builder.append("\033[H");
        System.out.print(builder);
    }


    private static int readKey() throws IOException {
        return System.in.read();
    }

    private static void handleKey(int key) {
        if (key == 'q') {
            System.out.print("\033[2J");
            System.out.print("\033[H");
            LibC.INSTANCE.tcsetattr(LibC.SYSTEM_OUT_FD, LibC.TCSAFLUSH, originalAttributes);
            System.exit(0);
        }
    }



    private static void enableRawMode() {
        LibC.Termios termios = new LibC.Termios();
        int rc = LibC.INSTANCE.tcgetattr(LibC.SYSTEM_OUT_FD, termios);

        if (rc != 0) {
            System.err.println("There was a problem calling tcgetattr: "+rc);
            System.out.println(LibC.INSTANCE.explain_tcgetattr(LibC.SYSTEM_OUT_FD, termios));
            System.exit(rc);
        }

        originalAttributes = LibC.Termios.of(termios);

        termios.c_lflag &= ~(LibC.ECHO | LibC.ICANON | LibC.IEXTEN | LibC.ISIG);
        termios.c_iflag &= ~(LibC.IXON | LibC.ICRNL);
        termios.c_oflag &= ~(LibC.OPOST);

       /* termios.c_cc[LibC.VMIN] = 0;
        termios.c_cc[LibC.VTIME] = 1;*/

        LibC.INSTANCE.tcsetattr(LibC.SYSTEM_OUT_FD, LibC.TCSAFLUSH, termios);
    }

    private static LibC.Winsize getWindowSize() {
        final LibC.Winsize winsize = new LibC.Winsize();
        final int rc = LibC.INSTANCE.ioctl(LibC.SYSTEM_OUT_FD, LibC.TIOCGWINSZ, winsize);

        if (rc != 0) {
            System.err.println("ioctl failed with return code[={}]" + rc);
            System.exit(1);
        }

        return winsize;
    }

}

interface LibC extends Library {

    int SYSTEM_OUT_FD = 0;
    int ISIG = 1, ICANON = 2, ECHO = 10, TCSAFLUSH = 2,
            IXON = 2000, ICRNL = 400, IEXTEN = 100000, OPOST = 1, VMIN = 6, VTIME = 5, TIOCGWINSZ = 0x5413;

    // we're loading the C standard library for POSIX systems
    LibC INSTANCE = Native.load("c", LibC.class);

    @Structure.FieldOrder(value = {"ws_row", "ws_col", "ws_xpixel", "ws_ypixel"})
    class Winsize extends Structure {
        public short ws_row, ws_col, ws_xpixel, ws_ypixel;
    }



    @Structure.FieldOrder(value = {"c_iflag", "c_oflag", "c_cflag", "c_lflag", "c_cc"})
    class Termios extends Structure {
        public int c_iflag, c_oflag, c_cflag, c_lflag;

        public byte[] c_cc = new byte[19];

        public Termios() {
        }

        public static Termios of(Termios t) {
            Termios copy = new Termios();
            copy.c_iflag = t.c_iflag;
            copy.c_oflag = t.c_oflag;
            copy.c_cflag = t.c_cflag;
            copy.c_lflag = t.c_lflag;
            copy.c_cc = t.c_cc.clone();
            return copy;
        }

        @Override
        public String toString() {
            return "Termios{" +
                    "c_iflag=" + c_iflag +
                    ", c_oflag=" + c_oflag +
                    ", c_cflag=" + c_cflag +
                    ", c_lflag=" + c_lflag +
                    ", c_cc=" + Arrays.toString(c_cc) +
                    '}';
        }
    }


    int tcgetattr(int fd, Termios termios);

    int tcsetattr(int fd, int optional_actions,
                  Termios termios);

    int ioctl(int fd, int opt, Winsize winsize);

    //THIS GIVES LINKAGE ERROR
    int explain_tcgetattr(int fildes, Termios termios);

}

Exception:

There was a problem calling tcgetattr: -1
Exception in thread "main" java.lang.UnsatisfiedLinkError: Error looking up function 'explain_tcgetattr': /snap/openjdk/1465/jdk/bin/java: undefined symbol: explain_tcgetattr
    at com.sun.jna.Function.<init>(Function.java:252)
    at com.sun.jna.NativeLibrary.getFunction(NativeLibrary.java:620)
    at com.sun.jna.NativeLibrary.getFunction(NativeLibrary.java:596)
    at com.sun.jna.NativeLibrary.getFunction(NativeLibrary.java:582)
    at com.sun.jna.Library$Handler.invoke(Library.java:248)
    at org.example.$Proxy0.explain_tcgetattr(Unknown Source)
    at org.example.App.enableRawMode(App.java:81)
    at org.example.App.main(App.java:22)

Below is the description from man page:

enter image description here

Also I am not sure if String is the Java equivalent of const char * in C. Please advise.

Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63
Shivam...
  • 409
  • 1
  • 8
  • 21
  • That function is actually in `libexplain` so your code needs to link to that. Yes is the answer to your last question. Fun fact: null-terminated strings in C but Java strings can *contain* null at any point. – g00se Jul 22 '23 at 13:27
  • When a syscall, such as **tcgetattr()**, returns -1, the program is expected to access the global variable **errno** for further explanation. Each possible numeric value of **errno** has a mnemonic or macro name, which is explained in the **errno** man page. – sawdust Jul 24 '23 at 04:41

1 Answers1

0

The tcgetattr() man page references the POSIX spec which defines the possible failures, both of which are associated with the file descriptor:

The tcgetattr() function shall fail if:

[EBADF] The fildes argument is not a valid file descriptor. 
[ENOTTY] The file associated with fildes is not a terminal.

So your file descriptor may be invalid, or may not be a terminal (it could be a pipe, for example). As you are executing in Java, it's likely that the JVM has taken over the file descriptor and it is not acting as you might expect it to.

You've defined your output file descriptor as 0, which is actually stdin; stdout is typically 1:

int SYSTEM_OUT_FD = 0;

It's possible neither of these will actually work. You're executing in Java which really obfuscates the native int value of the standard input/output file descriptors. This presentation claims the following code will fetch it for you:

import sun.misc.SharedSecrets;

public static int getFileDescriptor(FileDescriptor fd) {
    return SharedSecrets.getJavaIOFileDescriptorAccess().get(fd);
}

public static int getFileDescriptor(InputStream is) {
    return getFileDescriptor(((FileInputStream)is).getFD());
}

As far as your debugging errors, the comments have indicated you need another library for the explain function (which is a fancy wrapper around fetching errno which you can do with just the C library to get one of the two file-descriptor-related errors).

Also check your structure mapping. You've included the "at least the following members" list from the man page, but your header file may contain more elements or a different array size. See this termios.h file for example which has one byte less than yours and c_cc array offset by a byte.

#define NCCS 17
struct termios {
    unsigned long c_iflag;      /* input mode flags */
    unsigned long c_oflag;      /* output mode flags */
    unsigned long c_cflag;      /* control mode flags */
    unsigned long c_lflag;      /* local mode flags */
    unsigned char c_line;       /* line discipline */
    unsigned char c_cc[NCCS];   /* control characters */
};
Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63