0

I have written a program that creates a sidescrolling marquee effect, and it works perfectly on my Mac's terminal. However, when I run it on my raspberry pi (Raspbian Linux), the effect messes up and starts printing on a new line and doesn't scroll the full length of text. Can anyone figure out what the issue is? I have tried for days now.

// Compile: gcc marquee.c -o marquee -lncurses
// Usage: ./marquee filename length row col speed
// Reads text from a file and displays 'length' number of chars 
// scrolling sideways at a given 'row, col' position at some indicated 'speed'

#include    <curses.h>
#include    <string.h>
#include    <unistd.h>
#include    <stdio.h>
#include    <stdlib.h> 
#include    <fcntl.h>

#define ROW 10

int main(int ac, char *av[])
{
    if(ac != 6){
        printf("marquee [fileName] [row] [col] [speed (1-99)]\n");
        perror("Insuffecient argument count\n"); 
        exit(1); 
    }

    char message[256];
    int text_length;
    int i;
    int k;  
    int orgPos = atoi(av[4]);
    int pos;
    int row = atoi(av[3]);  
    int dir = 1; 
    int maxPos = atoi(av[2]);

    int speed = atoi(av[5]);

    int filedesc = open(av[1], O_RDONLY); 

    if(filedesc < 0) {
        perror("Could not open file");
        exit(1); 
    } 

    if(speed < 10) 
        speed = 500000; 
    if(speed >= 10 && speed < 20) 
        speed = 250000; 
    if(speed >= 20 && speed < 30) 
        speed = 120000; 
    if(speed >= 30 && speed < 40) 
        speed = 100000; 
    if(speed >= 40 && speed < 60) 
        speed = 80000; 
    if(speed >= 60 && speed < 70) 
        speed = 60000; 
    if(speed >= 70 && speed < 80) 
        speed = 40000; 
    if(speed >= 80 && speed < 90) 
        speed = 20000; 
    if(speed >= 90 && speed < 95) 
        speed = 10000; 
    if(speed >= 95 && speed <= 99) 
        speed = 5000; 

    int bytesRead = 0; 
    while(bytesRead == read(filedesc, message, 256) > 0){

    }

    // Get text length
    text_length = strlen(message);

    initscr(); // initialize curses
    clear();
    curs_set(0);

    while(1) {
        clear(); // clear last drawn iteration

        pos = orgPos;

        char * scroll;
        scroll = malloc(2*maxPos);

        for(i = 0; i<2*maxPos; i++) 
        {
            scroll[i] = message[i%text_length];
        }

        for(i = 0; i < 1000; i++){
                mvaddnstr(row, orgPos, &scroll[i%maxPos], maxPos);
                usleep(speed);
                refresh();
        }
    }

    endwin();

    if(close(filedesc) == -1) 
    {
        perror("Error closing file"); 
        exit(1); 
    }
}

There are surely many improvements to be made here, but please let the aim of your debugging be figuring out why this will not run correctly on linux. Here is a sample test case:

$ ./marquee scroll.txt 25 0 0 50

scroll.txt contains the following:

Hello, this is a test for the scrolling marquee. 
jmglynn
  • 31
  • 1
  • 4
  • 1
    Wrong compilation command: should use `gcc -Wall -Wextra -g marquee.c -o marquee -lncurses` then [use the `gdb` debugger](https://sourceware.org/gdb/current/onlinedocs/gdb/). Be [scared](https://stackoverflow.com/a/25636788/841108) of [undefined behavior](https://en.wikipedia.org/wiki/Undefined_behavior) – Basile Starynkevitch May 01 '18 at 07:30
  • Also, Stack Overflow is *not* a *fix-my-bugs* service, so your question is off-topic. – Basile Starynkevitch May 01 '18 at 07:32
  • 1
    I meant to pose the question more so as "why does it behave differently between the two OS's?," as one works just fine, the other not; not so much "fix my bugs." – jmglynn May 01 '18 at 07:35
  • It does not work file (even on your laptop), you still have an undefined behavior. – Basile Starynkevitch May 01 '18 at 07:41
  • I ran your program on Solaris 11.4, (the only change required was to include `` rather than ``). It ran just fine for me, with no terminal window corruption as you mention happens on linux. When it comes to terminal output differences my first step is to check the terminal type. On Solaris my default termtype is `xterm-256color`. On my Mac running 10.13.4 it is `xterm`. I then tried several different termtypes: TERM=ansi TERM=xterm TERM=xterms TERM=at386 TERM=dtterm and they all showed the non-corrupted, scrolling output. – James McPherson May 01 '18 at 07:46
  • @JAmesMcPherson: it is not a matter of `TERM`. My answer give some clues about that UB – Basile Starynkevitch May 01 '18 at 08:35

1 Answers1

5

You have an automatic variable char message[256];. You don't explicitly initialize it, so it contains garbage at start. It is difficult (or even impossible, think of ASLR or of how the environment is passed, perhaps thru the third argument to main) to predict what exact garbage is in it.

You are using read(filedesc, message, 256) but read(2) does not add any NUL byte at end of message unless that byte was in the file; so using strlen(message) is undefined behavior (UB) and you should be scared.

it works perfectly

Wrong. Sometimes UB appears to work by accident (and not "perfectly") when you are out of luck. That is why it is undefined. This might be the case under MacOSX. But even if your program appears (wrongly, and by bad luck) to work, it stays very buggy.

UB is undefined, so there is no explanation on the behavior differences without diving into gory implementation details. If you really care about these (but you really should not), study the source of your compiler, your kernel code (and what happens after execve(2), in particular in crt0), the generated assembler code (with gcc -S -fverbose-asm -O) to understand what kind of garbage data is inside message. You could spend years to understand all the gory details.

You should replace (notice that your == is very wrong)

     while(bytesRead == read(filedesc, message, 256) > 0){ //BAD

with at least (using the comma operator and an assignment expression) something which clears message and keeps the read byte count:

    while ((memset(message, 0, sizeof(message)),
           (byteread = read(filedesc, message, sizeof(message)-1) > 0) {

Since the last byte of message is never read, it keeps its 0 value (and byteread is always less than 256, the sizeof(message) ...).

There are other bugs in your program. Stay scared (and see this for inspiration).

Try to use the debugger. With cross-compilation, it is possible to use the gdb debugger for remote debugging. Pass -g -Wall to your cross-compiler, if it is some GCC. Perhaps use valgrind on your Raspberry Pi.

Maybe use also some static program analyzer like clang-analyzer or Frama-C. Caveat, they could require additional skills and annotations (e.g. in ACSL for Frama-C) to be useful and can give lots of false alarms (remember that the Halting Problem is undecidable, so don't expect too much from static program analyzers and compilers).

BTW, the correct thinking about uninitialized memory is to believe (this is a useful fiction) that it carries some kind of "disease" which propagates to every thing using it. Of course, that is not what happens inside the computer. But thinking this way will help you to avoid UB.

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • `or even impossible, think of ASLR` ASLR randomize virtual address space, not the actual page frames. I don't believe it affects the prediction on the content of an uninitialized memory – izac89 May 01 '18 at 08:57
  • ASLR may (and on Linux does) randomize the initial stack segment address range so affects the stack frame of `main`, e.g. by the `env` third argument to `main` – Basile Starynkevitch May 01 '18 at 09:04