sysdig icon indicating copy to clipboard operation
sysdig copied to clipboard

TTY as human readable

Open arossert opened this issue 7 years ago • 8 comments

According to this #792, the TTY value of a thread is numeric (tty_nr). How can we convert this to a human readable value like /dev/pts/11? As far as I understand from the PR, we cannot use the thread FD's (0, 1 & 2) in order to get this value?

I want to create a fix for that (and submit a PR) but I can't find any information on how to achieve this online

arossert avatar Aug 24 '17 11:08 arossert

That would be nice.

I can give you two inspiration points:

  1. Since both ps and top show the TTY as a user friendly string, just look into their code and you'll find the function dev_to_tty() that does the resolution. Essentially they rely on a bunch of information in /proc/tty + stuff in /dev.

  2. It might also be worth exploring what you can get on the kernel side. In particular, struct tty_driver has some strings in it that could be useful, I haven't checked very well.

gianlucaborello avatar Aug 24 '17 16:08 gianlucaborello

Thanks for the information. I will try to take a look

arossert avatar Aug 25 '17 09:08 arossert

@gianlucaborello I have looked in the struct tty_driver and I was able to get the tty by doing this:

snprintf(buf, buflen, "/dev/%s/%d", driver->name, tty->index);

I'm not sure that this will always work (it worked in my small test on Ubuntu 14.04, I will try to do more test).

Also I have a piece of code based on dev_to_tty() function:

static int guess_name(char *const buf, int maj, int min) {
	struct stat sbuf;
	int t0, t1;
	int tmpmin = min;
	switch (maj) {
		case 4:
			if (min < 64) {
				sprintf(buf, "/dev/tty%d", min);
				break;
			}
			if (min < 128) /* to 255 on newer systems */
			{
				sprintf(buf, "/dev/ttyS%d", min - 64);
				break;
			}
			tmpmin = min & 0x3f;  /* FALL THROUGH */
		case 3:      /* /dev/[pt]ty[p-za-o][0-9a-z] is 936 */
			t0 = "pqrstuvwxyzabcde"[tmpmin >> 4];
			t1 = "0123456789abcdef"[tmpmin & 0x0f];
			sprintf(buf, "/dev/tty%c%c", t0, t1);
			break;
		case 17:
			sprintf(buf, "/dev/ttyH%d", min);
			break;
		case 19:
			sprintf(buf, "/dev/ttyC%d", min);
			break;
		case 22:
			sprintf(buf, "/dev/ttyD%d", min);
			break; /* devices.txt */
		case 23:
			sprintf(buf, "/dev/ttyD%d", min);
			break; /* driver code */
		case 24:
			sprintf(buf, "/dev/ttyE%d", min);
			break;
		case 32:
			sprintf(buf, "/dev/ttyX%d", min);
			break;
		case 43:
			sprintf(buf, "/dev/ttyI%d", min);
			break;
		case 46:
			sprintf(buf, "/dev/ttyR%d", min);
			break;
		case 48:
			sprintf(buf, "/dev/ttyL%d", min);
			break;
		case 57:
			sprintf(buf, "/dev/ttyP%d", min);
			break;
		case 71:
			sprintf(buf, "/dev/ttyF%d", min);
			break;
		case 75:
			sprintf(buf, "/dev/ttyW%d", min);
			break;
		case 78:
			sprintf(buf, "/dev/ttyM%d", min);
			break; /* conflict */
		case 105:
			sprintf(buf, "/dev/ttyV%d", min);
			break;
		case 112:
			sprintf(buf, "/dev/ttyM%d", min);
			break; /* conflict */
			/* 136 ... 143 are /dev/pts/0, /dev/pts/1, /dev/pts/2 ... */
		case 136 ... 143:
			sprintf(buf, "/dev/pts/%d", min + (maj - 136) * 256);
			break;
		case 148:
			sprintf(buf, "/dev/ttyT%d", min);
			break;
		case 154:
			sprintf(buf, "/dev/ttySR%d", min);
			break;
		case 156:
			sprintf(buf, "/dev/ttySR%d", min + 256);
			break;
		case 164:
			sprintf(buf, "/dev/ttyCH%d", min);
			break;
		case 166:
			sprintf(buf, "/dev/ttyACM%d", min);
			break; /* bummer, 9-char */
		case 172:
			sprintf(buf, "/dev/ttyMX%d", min);
			break;
		case 174:
			sprintf(buf, "/dev/ttySI%d", min);
			break;
		case 188:
			sprintf(buf, "/dev/ttyUSB%d", min);
			break; /* bummer, 9-char */
		default:
			return 0;
	}

	if (stat(buf, &sbuf) < 0) {
		return 0;
	}
	if (min != minor(sbuf.st_rdev)) {
		return 0;
	}
	if (maj != major(sbuf.st_rdev)) {
		return 0;
	}

	return 1;
}

int tty_nr_to_tty(char *ret, int chop, int dev) {
	static char buf[PATH_MAX];
	char *tmp = buf;
	int i = 0;
	int c;
	if ((short) dev == (short) -1) {
		strcpy(ret, "?");
		return 1;
	}

	if (guess_name(tmp, major(dev), minor(dev)) != 1) {
		strcpy(ret, "?");
		return 1;
	}

	/* gotta check before we chop or we may chop someone else's memory */
	if (tmp + chop - buf <= sizeof(buf)) {
		tmp[chop] = '\0';
	}

	/* replace non-ASCII characters with '?' and return the number of chars */
	for (;;) {
		c = *tmp;
		tmp++;
		if (!c) { break; }
		i++;
		if (c <= ' ') {
			c = '?';
		}
		if (c > 126) {
			c = '?';
		}
		*ret = c;
		ret++;
	}
	*ret = '\0';

	return i;
}

I think that this is the only thing that we need to get the TTY device, what do you think? Do you think that it should be implemented in the kernel module or only in the userspace?

I will be happy to get your opinion on this.

arossert avatar Oct 26 '17 09:10 arossert

@gianlucaborello after more investigation I think that the struct tty_driver and struct tty_struct have enoght information so the tty_nr_to_tty() is not neccecery.

I have a couple of questions on how to implement this feature:

  1. What information to extract:

    • Use struct tty_struct member char name[64] that will give the name in this format pts12 that represents /dev/pts/12.
    • As I mentioned in the previous comment we can use struct tty_driver to build the full path like so snprintf(buf, buflen, "/dev/%s/%d", driver->name, tty->index);
  2. Where to put this information:

    • Replace the current int_32 tty value that actully represent the tty_nr to char tty[SCAP_MAX_ENV_SIZE+1]
    • Rename the current int32_t tty to int32_t tty_nr and add a new char tty[SCAP_MAX_ENV_SIZE+1]
    • Add a new char ttyname[SCAP_MAX_ENV_SIZE+1]

After deciding on this 2 subject I will submit a PR for that.

arossert avatar Nov 06 '17 08:11 arossert

Hi,

My non-educated check point list (because I haven't worked close enough to ttys) is:

  • Prefer, if possible, a syntax consistent with top/w/who/...: that seems to be pts/0, pts/1, ...
  • Make sure everything works fine inside containers too.
  • Make sure you not only get tty information at runtime from struct tty_driver like you mention, but also from /proc at startup during the initial state creation.
  • ttyname as field is fine, but don't add a 4KB (SCAP_MAX_ENV_SIZE) string to each thread in sinsp, the cost of that would be prohibitive. Either keep a small string (like we do for m_comm and such) or build a map external to the thread table where you keep the mapping tty_nr -> tty_name (assuming that makes sense, please check the logic).

Hope that helps somehow.

gianlucaborello avatar Nov 06 '17 17:11 gianlucaborello

Hi,

Thanks for the input.

Regarding your comments:

  • the syntax that usually is used is /dev/pts/1, I will do this using snprintf but not sure if this is the best way to construct a string in the kernel.
  • I will test this with containers as well.
  • I have extract the information from /proc using the stdin (/proc/<pid>/fd/0).
  • The SCAP_MAX_ENV_SIZE was a mistake, it should be SCAP_MAX_PATH_SIZE (can be smaller if we know that the syntax will be /dev/pts/...).

I will work on that and submit a PR.

arossert avatar Nov 06 '17 17:11 arossert

Hi,

I'm a begginer.

I spent a while reading this post but as a non experienced user, I don't feel like editing the source code myself.

Looking into the PR, I've seen some errors but @arossert claimed that some of these solutions would work for most cases.

Should I try any of these?

Thanks.

pqhais avatar Feb 04 '21 12:02 pqhais

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

github-actions[bot] avatar Feb 24 '23 02:02 github-actions[bot]