So I have to write a basic shell in C for school, no pipes, no redirections, I just have to execute the binarys and code a few builtins.
I already did most of that, but now I would like to implement some keyboard shortcuts, like ctrl+L to clear the screen, up/down to navigate through commands history, ctrl+D to exit the shell and so on.
The problem is, I have no idea how to read input without the user pressing enter.
Also I should mention that I can only use a very limited panel of functions, the only function I can use to read input is the system call read().
If anyone has an idea it would be great
Generally use the readline library to read input. It supports defining shortcuts, history, auto completion, ... and is meant for that purpose.
If you are not allowed to use it, I guess your teacher wants you to concentrate on important parts of the task rather than getting fancy.
If you just want to play around a bit, you may start your shell using the rlwrap command:
rlwrap your_shell
rlwrap can be used to add readline functionality to arbitrary commands which read from stdin.
Related
I am dealing with an issue in Ubuntu. I want to get current keyboard cursor position in terminal via Gcc
any assist...
"Terminal" is a program, or more accurately a description of a large class of programs, which implements a graphical interface emulating an external terminal (which would have been connected to your computer via a serial cable, or in some similar fashion). Your program communicates with the terminal emulator through a kind of bidirectional pipe (a "pseudoterminal") implemented by the operating system; to your program it looks like a pair of ordinary streams (stdin and stdout).
Linux itself has a terminal emulator, called "the console", which can be used instead of a window manager. Few programmers use it these days, but it's still there if you want to experiment. The console is a "terminal" (and there are usually several of them which you can switch between using a control+function key). As you might expect from the words "terminal" and "pseudoterminal", these basically look the same to your application.
There are a ton of details, which I'm skipping over because it would take a book to describe the whole thing.
The only connection between your program and the terminal (or pseudoterminal) is that you can send it a stream of characters, and you can receive a stream of characters from it. There is no other communication. There's no hidden operating system interface, because the terminal emulator is not part of the operating system. It's not even part of the window manager. It's just another userland application running without special privileges, just like your application.
You often want to do things other than just send characters to the output device. Maybe you want to clear the screen, or move the cursor to another location, or change the colour of the text or the background. All of these things are done by sending specially coded sequences interspersed with the text you're displaying. The operating system doesn't mediate or verify these sequences, and there's no definitive standard for how the terminal emulator should interpret them, but there is common framework which most terminal emulators conform to, to some extent, which makes it possible to actually write code which doesn't need to know exactly which terminal emulator is being used at the moment. The terminfo library is commonly used to describe the available terminals; by convention, the environment variable TERM contains the name of the relevant terminfo configuration, and that configuration can be used to create concrete control sequence strings suitable for the configured terminal [Note 1].
Now let's get back to your initial question: "how do I find out the current cursor location?" That's one of a small number of possible queries, which are also implemented as control sequences. Specifically, you send the terminal a control sequnce which asks it where the cursor is (usually the four characters \x1B[6n) and the terminal eventually replies with a control sequence which might look something like \x1B12,7R meaning that the cursor was on row 12 at column 7 at the moment that the control sequence was sent [Note 2]. So you could use terminfo to help you send the query and then attempt to parse the reply when it comes.
Note that the response is not synchronous with the query, since the user could be typing while the query is sent. (However, the response is sent as a contiguous sequence.) So part of the parsing process is disentangling the user input from the query response.
My guess is that you don't actually want to do all that work. In most cases, if you want to write a console application which does something less boring than just write output sequentially to a terminal window, you should use ncurses (also maintained by Thomas Dickey) or some other similar library. Ncurses takes full responsibility for maintaining the console image, jumping through the necessary hoops to communicate with the terminal emulator; one of its features is to keep track of the current cursor position [Note 3].
Another option, if you are only trying to provide better line editing and tab completion, is to use the GNU Readline library, or similar interfaces available for other operating systems.
Notes
This might or might not be the terminal you're actually using, since TERM is just an environment variable. You could set it yourself if you wanted it.
I took those codes from man 4 console_codes; another good source of information is Thomas Dickey's terse list of code sequences understood by xterm.
As far as I know, Ncurses does not use the cursor-position query to figure out where the cursor is on the screen. It maintains its own copy of the screen being displayed, which includes the current cursor position. You can use the macro getyx() to ask for what it considers the current cursor position.
I'm trying to write a mini shell in C for a school project, and what I want to do is doing a sort of command history (like in shell), when I press the UP key it writes the previous input into the input part, DOWN does the opposite, etc..., and you can edit it before pressing enter to send it to the program, like this (sorry for the bad english): [] represents the user cursor
my_shell$ some input wrote by me
my_shell$ []
my_shell$ some other input
my_shell$ []
and now if I press UP
my_shell$ some other input[]
If I press UP again
my_shell$ some input wrote by me[]
I'm allowed to use termcaps and some other functions isatty, ttyname, ttyslot, ioctl, getenv, tcsetattr, tcgetattr, tgetent, tgetflag, tgetnum, tgetstr, tgoto, tputs.
The problem is I can't understand the documentation of ioctl and tty functions, and I can't find well explained tutorials on these functions with examples, and I can't find the documentation for what I'm trying to do with them.
Can someone explain me those functions in an understandable way ? And how should I apply them for what I'm trying to do (I'm searching for a Linux-MacOs compatibility way)
Thanks for your help.
What you are asking is non trivial and will require a fair amount of work. Basically, what you need to do is use tcsetattr to put the terminal into non-canonical mode where the terminal's input line buffering is disabled and every keystroke will be returned immediately rather than waiting for a newline. You will then have to process every keystroke yourself, including backspace/delete and up/down arrows. Because of what you want to do with line editing, you'll probably also have to disable echoing and do the echo yourself.
You'll need to maintain the current line buffer yourself, and you'll also need a data structure that stores all the older lines of input (the history) and when an up-arrow is hit, you'll need to erase what you've currently buffered for the input, and erase it from the screen, then copy the history into your current buffer and echo it to the terminal.
Another complication is that keys like up-arraow and down-arrow are not part of ascii, so won't be read as a single byte -- instead they'll be multibyte escape sequences (likely beginning with an ESC ('\x1b') character). You can use tgetstr to quesry the terminal database to figure out what they are, or just hardcode your shell to use the ANSI sequences which are what almost all terminals you will see these days use.
Say I want to change the behavior of kill for educational reasons. If a user directly types it in the shell, then nothing will happen. If some other program/entity-who-is-not-the-user calls it, it performs normally. A wrapping if-statement is probably sufficient, but what do I put in that if?
Edit I don't want to do this in the shell. I'm asking about kernel programming.
In line 2296 of the kernel source, kill is defined. I will wrap an if statement around the code inside. In that statement, there should be a check to see whether the one who called this was the user or just some process. The check is the part I don't know how to implement.
Regarding security
Goal:
Block the user from directly calling kill from any shell
Literally everything else is fine and will not be blocked
While other answers are technically true, I think they're being too strict regarding the question. What you want to do it not possible to do in a 100% reliable way, but you can get pretty close by making some reasonable assumptions.
Specifically if you define an interactive kill as:
called by process owned by a logged in user
called directly from/by a process named like a shell (it may be a new process, or it may be a built-in operation)
called by a process which is connected to a serial/pseudo-terminal (possibly also belonging to the logged in user)
then you can check for each of those properties when processing a syscall and make your choice that way.
There are ways this will not be reliable (sudo + expect + sh should work around most of these checks), but it may be enough to have fun with. How to implement those checks is a longer story and probably each point would deserve its own question. Check the documentation about users and pty devices - that should give you a good idea.
Edit: Actually, this may be even possible to implement as a LKM. Selinux can do similar kind of checks.
It looks you are quite confused and do not understand what exactly a system call is and how does a Linux computer works. Everything is done inside some process thru system calls.
there should be a check to see whether the one who called this was directly done by the user or just some process
The above sentence has no sense. Everything is done by some process thru some system call. The notion of user exists only as an "attribute" of processes, see credentials(7) (so "directly done by the user" is vague). Read syscalls(2) and spend several days reading about Advanced Linux Programming, then ask a more focused question.
(I really believe you should not dare patching the kernel without knowing quite well what the ALP book above is explaining; then you would ask your question differently)
You should spend also several days or weeks reading about Operating Systems and Computer Architecture. You need to get a more precise idea of how a computer works, and that will take times (perhaps many years) and any answer here cannot cover all of it.
When the user types kill, he probably uses the shell builtin (type which kill and type kill) and the shell calls kill(2). When the user types /bin/kill he is execve(2) a program which will call kill(2). And the command might not come from the terminal (e.g. echo kill $$ | sh, the command is then coming from a pipe, or echo kill 1234|at midnight the kill is happening outside of user interaction and without any user interactively using the computer, the command being read from some file in /var/spool/cron/atjobs/, see atd(8)) In both cases the kernel only sees a SYS_kill system call.
BTW, modifying the kernel's behavior on kill could affect a lot of system software, so be careful when doing that. Read also signal(7) (some signals are not coming from a kill(2)).
You might use isatty(STDIN_FILENO) (see isatty(3)) to detect if a program is run in a terminal (no need to patch the kernel, you could just patch the shell). but I gave several cases where it is not. You -and your user- could also write a desktop application (using GTK or Qt) calling kill(2) and started on the desktop (it probably won't have any terminal attached when running, read about X11).
See also the notion of session and setsid(2); recent systemd based Linuxes have a notion of multi-seat which I am not familiar with (I don't know what kernel stuff is related to it).
If you only want to change the behavior of interactive terminals running some (well identified) shells, you need only to change the shell -with chsh(1)- (e.g. patch it to remove its kill builtin, and perhaps to avoid the shell doing an execve(2) of /bin/kill), no need to patch the kernel. But this won't prohibit the advanced user to code a small C program calling kill(2) (or even code his own shell in C and use it), compile his C source code, and run his freshly compiled ELF executable. See also restricted shell in bash.
If you just want to learn by making the exercise to patch the kernel and change its behavior for the kill(2) syscall, you need to define what process state you want to filter. So think in terms of processes making the kill(2) syscall, not in terms of "user" (processes do have several user ids)
BTW, patching the kernel is very difficult (if you want that to be reliable and safe), since by definition it is affecting your entire Linux system. The rule of thumb is to avoid patching the kernel when possible .... In your case, it looks like patching the shell could be enough for your goals, so prefer patching the shell (or perhaps patching the libc which is practically used by all shells...) to patching the kernel. See also LD_PRELOAD tricks.
Perhaps you just want the uid 1234 (assuming 1234 is the uid of your user) to be denied by your patched kernel using the kill(2) syscall (so he will need to have a setuid executable to do that), but your question is not formulated this way. That is probably simple to achieve, perhaps by adding in kill_ok_by_cred (near line 692 on Linux 4.4 file kernel/signal.c) something as simple as
if (uid_eq(1234, tcred->uid))
return 0;
But I might be completely wrong (I never patched the kernel, except for some drivers). Surely in a few hours Craig Ester would give a more authoritative answer.
You can use aliases to change the behavior of commands. Aliases are only applied at interactive shells. Shell scripts ignore them. For example:
$ alias kill='echo hello'
$ kill
hello
If you want an alias to be available all the time, you could add it to ~/.bashrc (or whatever the equivalent file is if your shell isn't bash).
I am writing application in C programming language that enables to monitor remote computers system information, number of logged users, free memory and so on.
I will write gathered info to standard output. But usually there will be more information then one single window of terminal, so I will need to implement some sort of 'scrolling' through results.
The easiest solution is I think to print for example first 25 rows, and then wait for user to push up or down and rewrite all rows accordingly.
Is there some easier/more elegant way to handle such output on terminal?
EDIT: forgot to mention, I would like to refresh the data if some new input comes from some remote computer, for example: number of processes changes.
Sounds like you need curses.
Here's a guide to the ncurses library.
It's an old school GUI library for terminals. Things like top and make menuconfig use it, so it's on every system. It allows you to stop thinking in terms of "print 25 lines and refresh" and more in terms of "put data in the text area which is scrollable".
Use an external pager, such as more (or less) to paginate the output. The strength of Unix is in combining simple commands, creating pipelines instead of reinventing functionality that already exists.
I'm making a program that displays some info in ncurses, and then opens vim (using system) to allow the user to edit a file. After vim is exited, though, the ncurses screen won't redraw. refresh and wrefresh don't do anything, resulting in the complete trashing of my beautiful menu.
So, I get sent back to the command line. The menu items redraw when I move to them. Moving around a bit results in something that looks like this:
As you can see, I no longer am in my pretty ncurses environment,.
I could tear down ncurses completely and set things up again, but then some stuff (like menu position) is not preserved.
How do I do this correctly? Is there a better way to call some external program and return here gracefully?
I've never had to restart curses entirely.
what if you do something like
def_prog_mode() then
endwin()
execute system call
and refresh() should restore it
Separate your program state from the curses state.
The only clean way I know of is to stop and restart curses entirely. If your program has a clean notion of its internal state (as it should), then it should be easy to go back to the same position.
Good luck!