The Pedigree Project 0.1
ConsoleCommon.cc
1/*
2 * Copyright (c) 2008-2014, Pedigree Developers
3 *
4 * Please see the CONTRIB file in the root of the source tree for a full
5 * list of contributors.
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include "pedigree/kernel/LockGuard.h"
21#include "pedigree/kernel/Log.h"
22#include "pedigree/kernel/process/Mutex.h"
23#include "pedigree/kernel/process/TerminationDeferral.h"
24#include "pedigree/kernel/processor/types.h"
25#include "pedigree/kernel/syscallError.h"
26#include "pedigree/kernel/utilities/Buffer.h"
27#include "pedigree/kernel/utilities/String.h"
28#include "pedigree/kernel/utilities/assert.h"
29#include "pedigree/kernel/utilities/utility.h"
30
31#include "Console.h"
32#include "ConsoleDefines.h"
33#include "modules/system/vfs/File.h"
34
35class Filesystem;
36
37extern const char defaultControl[MAX_CONTROL_CHAR];
38
39ConsoleFile::ConsoleFile(size_t consoleNumber, String consoleName, Filesystem* pFs)
40 : ConsoleFile(consoleNumber, consoleName, pFs, nullptr) {}
41
42ConsoleFile::ConsoleFile(size_t consoleNumber, String consoleName, Filesystem* pFs, File* pParent)
43 : File(consoleName, 0, 0, 0, 0xdeadbeef, pFs, 0, pParent),
44 m_pOther(0),
45 m_Flags(DEFAULT_FLAGS),
46 m_Rows(25),
47 m_Cols(80),
48 m_IoLock(),
49 m_IoChanged(),
50 m_HangingUp(false),
51 m_IoState(SharedPointer<ConsoleIoState>::tryAllocate()),
52 m_ControlState(),
53 m_ConsoleNumber(consoleNumber),
54 m_ConsoleName(consoleName) {
55 MemoryCopy(m_ControlChars, defaultControl, MAX_CONTROL_CHAR);
56
57 // r/w for all (todo: when a console is locked, it should become owned
58 // by the locking user)
59 setPermissionsOnly(FILE_UR | FILE_UW | FILE_GR | FILE_GW | FILE_OR | FILE_OW);
60 setUidOnly(0);
61 setGidOnly(0);
62}
63
64ConsoleIoState::ConsoleIoState()
65 : operations(),
66 input(PTY_BUFFER_SIZE),
67 output(PTY_BUFFER_SIZE),
68 inputLock(),
69 line(),
70 lineSize(0),
71 firstNewline(~0UL),
72 physicalWake(0, true),
73 m_Revoked(false) {}
74
75ConsoleIoState::~ConsoleIoState() {
76 // Unused consoles and unpublished replacements never pass through hangup.
77 // Epoch users retain a shared owner until their operation lease is released.
78 closeAdmission();
79 cancelAndDrain();
80}
81
82bool ConsoleIoState::revoked() const {
83 return __atomic_load_n(&m_Revoked, __ATOMIC_ACQUIRE);
84}
85
86void ConsoleIoState::closeAdmission() {
87 __atomic_store_n(&m_Revoked, true, __ATOMIC_RELEASE);
88 operations.close();
89}
90
91void ConsoleIoState::cancelAndDrain() {
92 input.disableReads();
93 input.disableWrites();
94 output.disableReads();
95 output.disableWrites();
96 physicalWake.release();
97 operations.wait();
98}
99
100ConsoleFile* ConsoleFile::stateOwner() {
101 return isMaster() ? m_pOther : this;
102}
103
104SharedPointer<ConsoleIoState> ConsoleFile::captureOpenEpoch(bool waitForReopen) {
105 ConsoleFile* owner = stateOwner();
106 LockGuard<Mutex> guard(owner->m_IoLock);
107 while (waitForReopen && owner->m_HangingUp)
108 owner->m_IoChanged.waitForCompletion(owner->m_IoLock);
109 if (waitForReopen && !owner->m_IoState)
111 return owner->m_IoState;
112}
113
114bool ConsoleFile::beginRevocation(SharedPointer<ConsoleIoState>& retired) {
115 assert(!retired);
116 ConsoleFile* owner = stateOwner();
117 LockGuard<Mutex> guard(owner->m_IoLock);
118 if (owner->m_HangingUp)
119 return false;
120 owner->m_HangingUp = true;
121 retired = owner->m_IoState;
122 if (retired)
123 retired->closeAdmission();
124 return true;
125}
126
127void ConsoleFile::finishRevocation(const SharedPointer<ConsoleIoState>& retired,
128 const SharedPointer<ConsoleIoState>& replacement) {
129 TerminationDeferral deferral;
130 ConsoleFile* owner = stateOwner();
131 if (retired)
132 retired->cancelAndDrain();
133 {
134 LockGuard<Mutex> guard(owner->m_IoLock);
135 assert(owner->m_HangingUp && owner->m_IoState.get() == retired.get());
136 owner->m_IoState = replacement;
137 owner->m_Flags = DEFAULT_FLAGS;
138 MemoryCopy(owner->m_ControlChars, defaultControl, MAX_CONTROL_CHAR);
139 owner->m_HangingUp = false;
140 owner->m_IoChanged.broadcast();
141 }
142 changed();
143}
144
145SharedPointer<ConsoleControlState> ConsoleFile::controlState() {
146 ConsoleFile* owner = stateOwner();
147 LockGuard<Mutex> guard(owner->m_IoLock);
148 return owner->m_ControlState;
149}
150
151void ConsoleFile::setControlState(const SharedPointer<ConsoleControlState>& state) {
152 ConsoleFile* owner = stateOwner();
154 {
155 LockGuard<Mutex> guard(owner->m_IoLock);
156 retired = pedigree_std::move(owner->m_ControlState);
157 owner->m_ControlState = state;
158 }
159}
160
161uint64_t ConsoleFile::readEpoch(const SharedPointer<ConsoleIoState>& epoch, uint64_t size,
162 uintptr_t buffer, bool canBlock) {
163 TerminationDeferral deferral;
164 OperationBarrier::Lease operation;
165 if (!epoch || !epoch->operations.tryAcquire(operation) || epoch->revoked())
166 return 0;
167 uint64_t amount = readIo(*epoch, size, buffer, canBlock);
168 return epoch->revoked() ? 0 : amount;
169}
170
171uint64_t ConsoleFile::writeEpoch(const SharedPointer<ConsoleIoState>& epoch, uint64_t size,
172 uintptr_t buffer, bool canBlock) {
173 TerminationDeferral deferral;
174 OperationBarrier::Lease operation;
175 if (!epoch || !epoch->operations.tryAcquire(operation) || epoch->revoked()) {
176 SYSCALL_ERROR(IoError);
177 return 0;
178 }
179 uint64_t amount = writeIo(*epoch, size, buffer, canBlock);
180 if (epoch->revoked() && !amount)
181 SYSCALL_ERROR(IoError);
182 return amount;
183}
184
185void ConsoleFile::changed() {
186 dataChanged();
187 if (m_pOther)
189}
190
191int ConsoleFile::select(bool writing, int timeout) {
192 auto state = captureOpenEpoch(false);
193 if (!state)
194 return 0;
195 Buffer<char>& source = isMaster() ? state->output : state->input;
196 Buffer<char>& destination = isMaster() ? state->input : state->output;
197 return (writing ? destination.canWrite(timeout > 0) : source.canRead(timeout > 0)) ? 1 : 0;
198}
199
200ReadyMask ConsoleFile::queryEpoch(const SharedPointer<ConsoleIoState>& epoch, bool reading,
201 bool writing) {
202 if (!epoch || epoch->revoked())
203 return ReadyRead | ReadyWrite | ReadyError | ReadyHangup;
204 // Physical terminals have no PTY peer, including serial ports without a VT number.
205 if (!m_pOther) {
206 return File::queryReady(reading, writing);
207 }
208 ReadyMask ready = ReadyNone;
209 Buffer<char>& source = isMaster() ? epoch->output : epoch->input;
210 Buffer<char>& destination = isMaster() ? epoch->input : epoch->output;
211 if (reading && source.canRead(false))
212 ready |= ReadyRead;
213 if (writing && destination.canWrite(false))
214 ready |= ReadyWrite;
215 return ready;
216}
217
218ReadyMask ConsoleFile::queryReady(bool reading, bool writing) {
219 return queryEpoch(captureOpenEpoch(false), reading, writing);
220}
221
222size_t ConsoleFile::readableBytes(ConsoleIoState& state) {
223 return (isMaster() ? state.output : state.input).getDataSize();
224}
225
226ReadinessGenerations ConsoleFile::epochGenerations(const SharedPointer<ConsoleIoState>& epoch) {
228 if (epoch) {
229 result.read = (isMaster() ? epoch->output : epoch->input).readableGeneration();
230 result.write = (isMaster() ? epoch->input : epoch->output).writableGeneration();
231 result.error = result.hangup = epoch->revoked() ? 1 : 0;
232 }
233 return result;
234}
235
237 return epochGenerations(captureOpenEpoch(false));
238}
239
240size_t ConsoleFile::outputLineDiscipline(char* buf, size_t len, size_t maxSz, size_t flags) {
241 // Make sure we always have the latest flags from the slave.
242 size_t slaveFlags = flags;
243
244 // Post-process output if enabled.
245 if (slaveFlags & (ConsoleManager::OPostProcess)) {
246 const size_t tmpSize = maxSz > len ? maxSz : len;
247 char* tmpBuff = new char[tmpSize];
248 if (!tmpBuff) {
249 return len;
250 }
251 size_t realSize = len;
252
253 char* pC = buf;
254 for (size_t i = 0, j = 0; j < len; j++) {
255 bool bInsert = true;
256
257 // OCRNL: Map CR to NL on output
258 if (pC[j] == '\r' && (slaveFlags & ConsoleManager::OMapCRToNL)) {
259 tmpBuff[i++] = '\n';
260 continue;
261 }
262
263 // ONLCR: Map NL to CR-NL on output
264 else if (pC[j] == '\n' && (slaveFlags & ConsoleManager::OMapNLToCRNL)) {
265 if (realSize >= maxSz) {
266 // We do not have any room to add in the mapped character.
267 // Drop it.
268 WARNING(
269 "Console ignored an NL -> CRNL conversion due to a "
270 "full buffer.");
271 tmpBuff[i++] = '\n';
272 continue;
273 }
274
275 realSize++;
276
277 // Add the newline and the caused carriage return
278 tmpBuff[i++] = '\r';
279 tmpBuff[i++] = '\n';
280
281 continue;
282 }
283
284 // ONLRET: NL performs CR function
285 if (pC[j] == '\n' && (slaveFlags & ConsoleManager::ONLCausesCR)) {
286 tmpBuff[i++] = '\r';
287 continue;
288 }
289
290 if (bInsert) {
291 tmpBuff[i++] = pC[j];
292 }
293 }
294
295 MemoryCopy(buf, tmpBuff, realSize);
296 delete[] tmpBuff;
297 len = realSize;
298 }
299
300 return len;
301}
302
303size_t ConsoleFile::processInput(char* buf, size_t len) {
304 // Perform input processing.
305 char* pC = buf;
306 size_t realLen = len;
307 for (size_t i = 0; i < len; i++) {
308 if (m_Flags & ConsoleManager::IStripToSevenBits)
309 pC[i] = static_cast<uint8_t>(pC[i]) & 0x7F;
310 if (m_Flags & ConsoleManager::LCookedMode) {
311 if (pC[i] == m_ControlChars[VEOF]) {
312 // Zero-length read: EOF.
313 realLen = 0;
314 break;
315 }
316 }
317
318 if (pC[i] == '\n' && (m_Flags & ConsoleManager::IMapNLToCR))
319 pC[i] = '\r';
320 else if (pC[i] == '\r' && (m_Flags & ConsoleManager::IMapCRToNL))
321 pC[i] = '\n';
322 else if (pC[i] == '\r' && (m_Flags & ConsoleManager::IIgnoreCR)) {
323 MemoryCopy(buf + i, buf + i + 1, len - i - 1);
324 i--; // Need to process this byte again, its contents have changed.
325 realLen--;
326 }
327 }
328
329 return realLen;
330}
331
332size_t ConsoleFile::inputLineDiscipline(ConsoleIoState& state, char* buf, size_t len, bool canBlock,
333 size_t flags, const char* controlChars) {
334 LockGuard<Mutex> inputGuard(state.inputLock);
335 if (state.revoked()) {
336 return 0;
337 }
338 // Make sure we always have the latest flags from the slave.
339 if (flags == ~0U) {
340 flags = m_pOther->m_Flags;
341 }
342 size_t slaveFlags = flags;
343 if (controlChars == 0) {
344 controlChars = m_pOther->m_ControlChars;
345 }
346 const char* slaveControlChars = controlChars;
347
348 size_t localWritten = 0;
349
350 // Handle temios local modes
351 if (slaveFlags & (ConsoleManager::LCookedMode | ConsoleManager::LEcho)) {
352 // Whether or not the application buffer has already been filled
353 bool bAppBufferComplete = false;
354
355 // Used for raw mode - just a buffer for erase echo etc.
356 char* destBuff = new char[len];
357 if (!destBuff) {
358 return 0;
359 }
360 size_t destBuffOffset = 0;
361
362 // Iterate over the buffer
363 while (!bAppBufferComplete) {
364 for (size_t i = 0; i < len && !state.revoked(); i++) {
365 if (state.lineSize == LINEBUFFER_MAXIMUM) {
366 state.input.write(state.line, state.lineSize, canBlock);
367 state.lineSize = 0;
368 state.firstNewline = ~0UL;
369 }
370 // Handle incoming newline
371 bool isCanonical = (slaveFlags & ConsoleManager::LCookedMode);
372 if (isCanonical && (buf[i] == slaveControlChars[VEOF])) {
373 // EOF. Write it and it alone to the slave.
374 state.input.write(&buf[i], 1, canBlock);
375 delete[] destBuff;
376 changed();
377 return i + 1;
378 }
379
380 if ((buf[i] == '\r') || (isCanonical && (buf[i] == slaveControlChars[VEOL]))) {
381 // LEcho - output the newline. LCookedMode - handle line
382 // buffer.
383 if ((slaveFlags & ConsoleManager::LEcho) || (slaveFlags & ConsoleManager::LCookedMode)) {
384 // Only echo the newline if we are supposed to
385 state.line[state.lineSize++] = '\n';
386 if ((slaveFlags & ConsoleManager::LEchoNewline) ||
387 (slaveFlags & ConsoleManager::LEcho)) {
388 char tmp[] = {'\n', 0};
389 state.output.write(tmp, 1);
390 ++localWritten;
391 }
392
393 if ((slaveFlags & ConsoleManager::LCookedMode) && !bAppBufferComplete) {
394 // Transmit full buffer to slave.
395 size_t realSize = state.lineSize;
396 if (state.firstNewline < realSize) {
397 realSize = state.firstNewline;
398 state.firstNewline = ~0UL;
399 }
400
401 state.input.write(state.line, realSize, canBlock);
402
403 // And now move the buffer over the space we just
404 // consumed
405 uint64_t nConsumedBytes = state.lineSize - realSize;
406 if (nConsumedBytes) // If zero, the buffer was
407 // consumed completely
408 MemoryCopy(state.line, &state.line[realSize], nConsumedBytes);
409
410 // Reduce the buffer size now
411 state.lineSize -= realSize;
412
413 // The buffer has been filled!
414 bAppBufferComplete = true;
415 } else if ((slaveFlags & ConsoleManager::LCookedMode) && (state.firstNewline == ~0UL)) {
416 // Application buffer has already been filled, let
417 // future runs know where the limit is
418 state.firstNewline = state.lineSize - 1;
419 } else if (!(slaveFlags & ConsoleManager::LCookedMode)) {
420 // Inject this byte into the slave...
421 destBuff[destBuffOffset++] = buf[i];
422 }
423
424 // Ignore the \n if one is present
425 if (i + 1 < len && buf[i + 1] == '\n')
426 i++;
427 }
428 } else if (buf[i] == m_ControlChars[VERASE]) {
429 if (slaveFlags & (ConsoleManager::LCookedMode | ConsoleManager::LEchoErase)) {
430 if ((slaveFlags & ConsoleManager::LCookedMode) && state.lineSize) {
431 char ctl[3] = {'\x08', ' ', '\x08'};
432 state.output.write(ctl, 3);
433 state.lineSize--;
434 ++localWritten;
435 } else if ((!(slaveFlags & ConsoleManager::LCookedMode)) && destBuffOffset) {
436 char ctl[3] = {'\x08', ' ', '\x08'};
437 state.output.write(ctl, 3);
438 destBuffOffset--;
439 ++localWritten;
440 }
441 }
442 } else {
443 // Do we need to handle this character differently?
444 if (isControlCharacter(slaveFlags, buf[i], controlChars)) {
445 // So, normally we'll be fine to print nicely, but if
446 // we can't write to the ring buffer, we must not try
447 // to do so. This event may be necessary to unblock the
448 // buffer!
449 if (!state.output.canWrite(false)) {
450 // Forcefully clear out bytes so we can write what
451 // we need to to the ring buffer.
452 WARNING(
453 "Console: dropping bytes to be able to "
454 "render visual control code (e.g. ^C)");
455 char tmp[3];
456 state.output.read(tmp, 3);
457 }
458
459 // Write it to the master nicely (eg, ^C, ^D)
460 char ctl_c = '@' + buf[i];
461 char ctl[3] = {'^', ctl_c, '\n'};
462 state.output.write(ctl, 3);
463 ++localWritten;
464
465 notifyControlCharacter(buf[i], controlChars);
466 continue;
467 }
468
469 // Write the character to the slave
470 if (slaveFlags & ConsoleManager::LEcho) {
471 state.output.write(&buf[i], 1);
472 ++localWritten;
473 }
474
475 // Add to the buffer
476 if (slaveFlags & ConsoleManager::LCookedMode)
477 state.line[state.lineSize++] = buf[i];
478 else {
479 destBuff[destBuffOffset++] = buf[i];
480 }
481 }
482 }
483
484 // We appear to have hit the top of the line buffer!
485 if (state.lineSize >= LINEBUFFER_MAXIMUM) {
486 // Our best bet is to return early, giving the application what
487 // we can of the line buffer
488 size_t numBytesToRemove = state.lineSize;
489
490 // Copy the buffer across
491 state.input.write(state.line, numBytesToRemove, canBlock);
492
493 // And now move the buffer over the space we just consumed
494 uint64_t nConsumedBytes = state.lineSize - numBytesToRemove;
495 if (nConsumedBytes) // If zero, the buffer was consumed
496 // completely
497 MemoryCopy(state.line, &state.line[numBytesToRemove], nConsumedBytes);
498
499 // Reduce the buffer size now
500 state.lineSize -= numBytesToRemove;
501 }
502
504 break;
505 }
506
507 if (destBuffOffset) {
508 state.input.write(destBuff, destBuffOffset, canBlock);
509 }
510
511 delete[] destBuff;
512 } else {
513 size_t consumed = 0;
514 while (consumed < len && !state.revoked()) {
515 if (isControlCharacter(slaveFlags, buf[consumed], controlChars)) {
516 notifyControlCharacter(buf[consumed++], controlChars);
517 continue;
518 }
519
520 size_t end = consumed + 1;
521 while (end < len && !isControlCharacter(slaveFlags, buf[end], controlChars)) {
522 ++end;
523 }
524
525 // Return a short write once space runs out. Waiting after publishing
526 // input would withhold the readiness notification a poll reader needs
527 // to drain it; ignoring a short write would silently lose paste data.
528 if (!state.input.canWrite(canBlock && !consumed)) {
529 break;
530 }
531 size_t amount = state.input.writeAvailable(buf + consumed, end - consumed);
532 consumed += amount;
533 if (consumed < end) {
534 break;
535 }
536 }
537 len = consumed;
538 }
539
540 // Wake up anything waiting on data to read from us.
541 (void)localWritten;
542 changed();
543 return len;
544}
545
546bool ConsoleFile::isControlCharacter(size_t flags, char check, const char* controlChars) {
547 // ISIG?
548 if (flags & ConsoleManager::LGenerateEvent) {
549 if (check && (check == controlChars[VINTR] || check == controlChars[VQUIT] ||
550 check == controlChars[VSUSP])) {
551 return true;
552 }
553 }
554 return false;
555}
556
557void ConsoleFile::notifyControlCharacter(char cause, const char* controlChars) {
558 auto control = controlState();
559 if (!control)
560 return;
561 // The retained callback does not need the console mutex or an event ack.
562 // Its POSIX implementation queues signals without dispatching user code.
563 if (cause == controlChars[VINTR])
564 control->controlCharacter(ConsoleControlState::Character::Interrupt);
565 else if (cause == controlChars[VQUIT])
566 control->controlCharacter(ConsoleControlState::Character::Quit);
567 else if (cause == controlChars[VSUSP])
568 control->controlCharacter(ConsoleControlState::Character::Suspend);
569}
size_t write(const T *buffer, size_t count, bool block=true)
Definition Buffer.cc:103
void disableReads()
Definition Buffer.cc:422
void disableWrites()
Definition Buffer.cc:406
bool canRead(bool block)
Definition Buffer.cc:525
bool canWrite(bool block)
Definition Buffer.cc:491
size_t writeAvailable(const T *buffer, size_t count, bool atomic=false)
Definition Buffer.cc:201
size_t read(T *buffer, size_t count, bool block=true)
Definition Buffer.cc:296
ConsoleFile * m_pOther
Other side of the console.
Definition Console.h:160
ReadyMask queryReady(bool reading, bool writing) override
ReadinessGenerations readinessGenerations() override
virtual int select(bool bWriting, int timeout)
select - check and optionally for a particular state.
static size_t outputLineDiscipline(char *buf, size_t len, size_t maxSz, size_t flags=0)
Output line discipline.
size_t inputLineDiscipline(ConsoleIoState &state, char *buf, size_t len, bool canBlock, size_t flags=~0U, const char *controlChars=nullptr)
Input line discipline.
size_t processInput(char *buf, size_t len)
Input processing.
Definition File.h:75
virtual ReadyMask queryReady(bool reading, bool writing)
Definition File.cc:1054
void dataChanged()
Definition File.cc:1452
void release(size_t n=1)
Definition Semaphore.cc:549
static SharedPointer< T > tryAllocate(Args...)
T * get() const