/****************************************************************************** ** freetty(8): Print to stdout a string '/dev/ttyN' which corresponds ** ** to the next unused virtual console. ** ** Cleverly deals with the 'executables with whitespace' in ** ** the process table problem. ** ** ** ** Uses the Linux '/proc' filesystem. ** ** ** ** Copyright 1994 by Charles Blake ** ** chuckb@alice.wonderland.caltech.edu ** ******************************************************************************/ #include #include #include #include #define BUFSIZE 256 /* Some rigamarole is required if we are to be able to handle the most general unix process table. The only characters forbidden in a filename are '\0' and '/'. Thus arbitrary whitespace *and* unbalanced '(' ')' characters may be in a command name. Hence there is no possible fscanf format string to read the "(command)" field in /proc/PID/stat. We have to use /proc/PID/cmdline to get the full argv[0]. Here we have to be careful because it is irratic whether fgetc will find EOF before it finds a terminating '\0' (which rindex and strlen require). Finally we can use rindex to find the basename, and strlen to see how many chars we have to skip (past the PID field) to get to the fields past (command). Whew. All this in the name of generality. Field 7 is the tty device minor number (the virtual console if 1 <= # <= 8). We just keep tabs of which of these have come up as we loop over all the stat files in /proc/[0-9]*. */ int main(int argc, char** argv) { DIR* proc; FILE* file; struct dirent* entry; char ch, *s, buf[BUFSIZE]; int i, j, ttys[9]={0,0,0,0,0,0,0,0,0}; proc = opendir("/proc"); while ( (entry = readdir(proc)) != NULL ) { if( entry->d_name[0] >= '0' && entry->d_name[0] <= '9' ) { /* The tricky part: skip (command) */ sprintf(buf, "/proc/%s/cmdline", entry->d_name); file = fopen(buf, "r"); for(i=0; (buf[i]=fgetc(file)) > 0; i++) ; fclose(file); buf[i]='\0'; /* IMPORTANT: EOF && \0 ORDER IS HIGHLY IRRATIC */ if ((s = rindex(buf,'/')) == NULL) /*buf NEEDS TO BE \0 TERMINATED*/ i = 3 + strlen(buf); /* buf may not have any '/' at all */ else i = 3 + strlen(s); sprintf(buf, "/proc/%s/stat", entry->d_name); file = fopen(buf, "r"); fscanf(file,"%d", &j); /* skip PID */ for(; i>0; i--) ch = fgetc(file); /* Now we can finally do the basic thing -- read the tty, this relies upon fscanf processing it's arguments in order! */ fscanf(file, "%s %d %d %d %d", buf, &i, &i, &i, &i); fclose(file); if (i >= 1 && i <= 8) ttys[i] = 1; } } closedir(proc); if (argv[1][1]=='a') { for (i=1; i<=8; ++i) /* output all non-taken ttys */ if (!ttys[i]) printf("/dev/tty%d ", i); } else { for (i=1; ttys[i]; ++i) /* output first non-taken tty */ ; printf("/dev/tty%d", i); } return 0; }