util  Check-in [196f94b613]

Overview
Comment:add shit, updates
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 196f94b613f23e130e98ecbce7965ed5baa3a9b15a6a05c8b5d997e966d28be1
User & Date: lexi on 2019-04-17 16:52:18
Other Links: manifest | tags
Context
2019-04-17
17:02
added more comments check-in: 4b7174b913 user: lexi tags: trunk
16:52
add shit, updates check-in: 196f94b613 user: lexi tags: trunk
2019-04-14
03:07
rewrite safekill to use X properties instead of hacking a flag into the window class check-in: 795f87aa54 user: lexi tags: trunk
Changes

Modified bgrd.c from [1814ca81a5] to [d69c7e1283].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

82
83
84

85
86
87
88

89
90
91
92

93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/* [ʞ] bgrd.c
 *  ~ lexi hale <lexi@hale.su>
 *  $ cc -Ofast bgrd.c -lutil -obgrd
 *  © affero general public license 3.0

 * i am angry beyond words that this program had to
 * be written
 *
 * bgrd "background read" is a tool for launching an 
 * application and retrieving the first line of output.
 * this is a nontrivial task for a number of reasons,
 * all of which are incredibly stupid, however, most
 * saliently: buffered io.
 *
 * the suckmore web browser `surf` has an option '-x'
 * which is intended to print the X window id of the
 * window it opens, so it can be captured and used in
 * scripts. so far so good.
 *
 * except unix has two distinctly different concepts
 * of IO. there's POSIX IO, and then there's libc IO.
 *
 * POSIX IO uses the shit in <fcntl.h> and <unistd.h>;
 * syscalls like read(2), write(2), and pipe(2) - the
 * good, simple shit God made unix for. this is really
 * bare-metal; these are basically C wrappers over
 * kernel syscalls. POSIX IO uses plain old ints as
 * file descriptors, and it doesn't fuck around. when
 * you say "write," god dammit, it WRITES.
 *
 * libc is a very different beast. libc has opinions.
 * libc has abstractions. libc has its own entire
 * goddamn DSL by which to specify format strings,
 * because apparently someone felt called to reinvent
 * FORTRAN except worse. printf(), you know, the first
 * function they ever teach you in C 101? (more like CS
 * 403 these days, but aghhh) it's actually a heinously
 * complicated, dangerous, slow, insecure mess that drags
 * a ridiculous amount of background bullshit into the
 * frame. such as BUFFERING.
 *
 * libc, you see, is too good to just wrap write() and
 * read(). no, it also has to decide when to SEND them.
 * this is responsible for that behavior every c coder
 * trips over eventually - you know the one, where if
 * you don't end your format string in '\n' it isn't
 * printed, sometimes even if the program exits. this
 * is not actually a POSIX thing; it's a libc thing.
 *
 * libc has a couple different kinds of buffering tactics
 * (set with setvbuf(), a function nobody seems to know
 * exists) that it uses in different circumstances.
 * the printf \n gotcha behavior is what's known as
 * "line buffering." however, because libc thinks it's
 * fucking smart or something, it's not content to just
 * pick one predictable behavior and stick to it. oh no.
 *
 * ever noticed how programs can seem to tell whether
 * they're connected to a terminal (and thus can output
 * all those fancy ansi formatting codes), or whether
 * you're redirecting their stdout to a file? that's 
 * because there's more than one kind of pipe. the kind
 * you create with pipe(2) - and the kind you create with
 * openpty(2).
 *
 * a pty is, essentially, a kind of pipe that carries
 * extra information around, the information you access
 * via ioctl and termios. it's designed to imitate a TTY,
 * so that a shell can create one, aim a process at it,
 * and then that process can draw TUIs and shit on it.
 * and some programs are designed to behave differently
 * depending on whether they're hooked up to a pipe or a
 * pty.
 *
 * libc, tragically, is among them.
 *
 * if libc notices it's hooked up to a pipe instead of a pty,
 * it will change its default buffering strategy. newlines
 * will suddenly cease flushing stdout, and libc will
 * only print its buffer in one of two circumstances: the
 * buffer is filled up, or the program exits.

 *
 * this is a problem if you are, say, trying to output a
 * handle that scripts can use to control the running

 * program.
 *
 * the `surf` developers had a couple of options. they
 * could have simply broken out the POSIX headers and

 * sent the X id to stdout with a call to write(2), the
 * correct thing to do. they could have thrown in a call
 * to setvbuf(3) to explicitly pick a buffering strategy
 * compatible with their usecase, the sensibly wrong

 * thing to do. they could have explicitly flushed stdout
 * after printf(3)'ing to it, the dumb and error-pront
 * thing to do.
 *
 * instead, they did *nothing.*
 *
 * so if you run `surf -x` from a terminal, great!
 * you'll see it print the x window id first thing.
 * you'll then try to capture it via any number of
 * increasing desperate means, all of which will fail
 * hilariously. finally, you'll spend four goddamn hours
 * after midnight reading source code and manpages and
 * frantically googling around and digging up unix lore
 * and finally, FINALLY figure out what the batshit FUCK
 * is going on, and write this goddamn utility to hack
 * around the suckmore crowd's suckitude.
 *
 * so i figured i'd save you some time.
 *
 * i am probably going to submit a PR eventually because
 * holy hell this is just so monumentally bozotic.
 *
 * in the mean time, you can use this extremely
 * no-bullshit wrapper by running `set surfwin (bgrd
 * (which surf) surf -x <params>` or whatever the bash
 * equivalent is and it will immediately launch surf in
 * the background, printing the X window and exiting
 * as soon as it hits a newline. it should be adaptable
 * to similar scenarios if you find yourself dealing with
 * similarly broken software tho.
 *
 * in conclusion, read lenin. */

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>





|
|

|
|
|
|


|
|
|


|
|

|
|
|
|
|
|
|

|
|
|
|
|
|
|

|


|
|
|
|
|
|
|


|
|
|
|
|
|

|
|
|
|
|



|
|

|
|
|
|




|
|
|
|
|
>

|
<
>


<
|
>
|
|
|
<
>

|




|
|
|
|
|
|
|
|
|




|


|
|
|
|
|
|
|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

85
86
87

88
89
90
91
92

93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/* [ʞ] bgrd.c
 *  ~ lexi hale <lexi@hale.su>
 *  $ cc -Ofast bgrd.c -lutil -obgrd
 *  © affero general public license 3.0

 * i am  angry beyond words  that this program had  to be
 * written
 *
 * bgrd  "background read"  is  a tool  for launching  an
 * application and  retrieving the first line  of output.
 * this is  a nontrivial  task for  a number  of reasons,
 * all  of which  are  incredibly  stupid, however,  most
 * saliently: buffered io.
 *
 * the  suckmore web  browser `surf`  has an  option '-x'
 * which  is intended  to print  the X  window id  of the
 * window it  opens, so  it can be  captured and  used in
 * scripts. so far so good.
 *
 * except unix  has two distinctly different  concepts of
 * IO. there's POSIX IO, and then there's libc IO.
 *
 * POSIX IO  uses the  shit in <fcntl.h>  and <unistd.h>;
 * syscalls  like read(2),  write(2), and  pipe(2) -  the
 * good, simple  shit God made  unix for. this  is really
 * bare-metal; these are basically C wrappers over kernel
 * syscalls.  POSIX  IO  uses  plain  old  ints  as  file
 * descriptors, and it doesn't  fuck around. when you say
 * "write," god dammit, it WRITES.
 *
 * libc  is a  very different  beast. libc  has opinions.
 * libc has abstractions. libc has its own entire goddamn
 * DSL  by  which  to  specify  format  strings,  because
 * apparently  someone felt  called  to reinvent  FORTRAN
 * except worse.  printf(), you know, the  first function
 * they  ever teach  you  in  C 101?  (more  like CS  403
 * these  days,  but  aghhh) it's  actually  a  heinously
 * complicated, dangerous, slow, insecure mess that drags
 * a ridiculous  amount of  background bullshit  into the
 * frame. such as BUFFERING.
 *
 * libc, you  see, is too  good to just wrap  write() and
 * read(). no, it  also has to decide when  to SEND them.
 * this is  responsible for  that behavior every  c coder
 * trips over eventually - you know the one, where if you
 * don't end your format string in '\n' it isn't printed,
 * sometimes  even  if the  program  exits.  this is  not
 * actually a POSIX thing; it's a libc thing.
 *
 * libc has a couple different kinds of buffering tactics
 * (set with  setvbuf(), a function nobody  seems to know
 * exists) that  it uses in different  circumstances. the
 * printf  \n gotcha  behavior is  what's known  as "line
 * buffering." however, because  libc thinks it's fucking
 * smart or something, it's not  content to just pick one
 * predictable behavior and stick to it. oh no.
 *
 * ever  noticed how  programs can  seem to  tell whether
 * they're connected  to a terminal (and  thus can output
 * all  those fancy  ansi formatting  codes), or  whether
 * you're  redirecting their  stdout  to  a file?  that's
 * because there's more  than one kind of  pipe. the kind
 * you create with pipe(2) - and the kind you create with
 * openpty(2).
 *
 * a pty  is, essentially,  a kind  of pipe  that carries
 * extra information  around, the information  you access
 * via ioctl and termios. it's designed to imitate a TTY,
 * so that a  shell can create one, aim a  process at it,
 * and then  that process can  draw TUIs and shit  on it.
 * and some  programs are designed to  behave differently
 * depending on whether they're hooked  up to a pipe or a
 * pty.
 *
 * libc, tragically, is among them.
 *
 * if libc notices it's hooked up  to a pipe instead of a
 * pty, it  will change  its default  buffering strategy.
 * newlines  will  suddenly  cease flushing  stdout,  and
 * libc  will  only  print  its  buffer  in  one  of  two
 * circumstances: the buffer is filled up, or the program
 * exits.
 *
 * this is a problem if you  are, say, trying to output a

 * handle  that scripts  can use  to control  the running
 * program.
 *

 * the `surf`  developers had  a couple of  options. they
 * could  have simply  broken out  the POSIX  headers and
 * sent the X  id to stdout with a call  to write(2), the
 * correct thing to do. they  could have thrown in a call
 * to setvbuf(3) to explicitly  pick a buffering strategy

 * compatible  with  their  usecase, the  sensibly  wrong
 * thing to do. they could have explicitly flushed stdout
 * after printf(3)'ing  to it,  the dumb  and error-pront
 * thing to do.
 *
 * instead, they did *nothing.*
 *
 * so  if  you run  `surf  -x`  from a  terminal,  great!
 * you'll  see it  print  the x  window  id first  thing.
 * you'll  then  try to  capture  it  via any  number  of
 * increasing  desperate means,  all of  which will  fail
 * hilariously. finally, you'll  spend four goddamn hours
 * after midnight  reading source  code and  manpages and
 * frantically googling  around and digging up  unix lore
 * and finally, FINALLY figure  out what the batshit FUCK
 * is going  on, and write  this goddamn utility  to hack
 * around the suckmore crowd's suckitude.
 *
 * so i figured i'd save you some time.
 *
 * i am probably going to  submit a PR eventually because
 * holy hell this is just so monumentally bozotic.
 *
 * in  the   mean  time,  you  can   use  this  extremely
 * no-bullshit  wrapper  by  running `set  surfwin  (bgrd
 * (which surf)  surf -x  <params>` or whatever  the bash
 * equivalent is  and it will immediately  launch surf in
 * the background,  printing the X window  and exiting as
 * soon as it  hits a newline. it should  be adaptable to
 * similar scenarios  if you  find yourself  dealing with
 * similarly broken software tho.
 *
 * in conclusion, read lenin. */

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

Added smake.c version [8f8960ef17].



































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/* [ʞ] smake.c <c.hale.su/lexi/util>
 *  ~ lexi hale <lexi@hale.su>
 *  $ cc -Ofast smake.c -osmake
 *  © affero general public license

        	    * I N C O M P L E T E *
			| basic functionality is still |
			|  heavily  under development  |

 * a replacement  for sassc  --watch, smake  maintains an
 * in-memory  dependency graph  for  the specified  files
 * and  automatically re-compiles  them  when  they or  a
 * dependency of theirs changes

 TODO add switch to generate makefile from constructed
      graph and exit */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

enum       e { ok, badname, nofile };
enum    kind { css, sass, scss, bad };
typedef enum { false, true } bool;

#define corebufsz 1024
char corebuf[corebufsz];
void* extbuf, * bufptr;
size_t run = 0;
#define max(a,b) (a > b ? a : b)

void* alloc(size_t sz) {
	if((bufptr + sz) < ((void*)corebuf + corebufsz)) goto mkptr;
	else {
		if (run == 0) {
			run = corebufsz;
			extbuf = malloc(run);
			bufptr = extbuf + sz;
			return extbuf;
		} else {
			if ((bufptr + sz) < (extbuf + run)) goto mkptr;
			else {
				run += max(512, sz+128);
				size_t rsz = bufptr-extbuf;
				extbuf = realloc(extbuf, run);
				bufptr = extbuf + rsz + sz;
				return bufptr - sz;
			}
		}
	}
	// this point should never be reached
	mkptr: {
		void* ret = bufptr;
		bufptr += sz;
		return ret;
	}
}



struct file {
	char* src;
	char* out;
	enum kind kind;
	struct dep* deplink;
};

struct dep { struct file* f; struct dep* nextdep; };

void readfile(struct file* f, char* src) {
	f->src = src;

	int fd = open(src, O_RDONLY);
	int sz = lseek(fd,0,SEEK_END); lseek(fd,0,SEEK_SET);
	char* file = mmap(0,sz,PROT_READ,MAP_PRIVATE,fd,0);
	close(fd);

	char namebuf[256];
	char* namecur = namebuf;
	char* cur = file;

	char strqt = 0;

read_start:
	if (*cur == 0) goto read_done;
	if (*cur == '"')  { ++ cur; strqt = '"';  goto read_string; }
	if (*cur == '\'') { ++ cur; strqt = '\''; goto read_string; }
	if (*cur == '(')  { ++ cur; strqt = ')';  goto read_string; }
	if (*cur == '[')  { ++ cur; strqt = ']';  goto read_string; }
	if (*cur == '/') {
		switch(cur[1]) {
			case '/': goto skip_line;
			case '*': cur += 2; goto read_ml_comment;
			case   0: goto read_done;
		}
		++cur; goto read_start;
	}
	if (*cur == '@') {
		if (strncmp(cur+1, "import", 6) == 0) {
			cur += 7; goto read_import;
		}
	}
	++cur; goto read_start;

read_import:
	if (*cur == ' ' || *cur == '\t' || *cur == '\n') {
		++cur; goto read_import;
	} else if (*cur == 'u' && cur[1] == 'r' && cur[2] == 'l') {
		strqt=')'; goto read_string;
	} else if (*cur == '"' || *cur=='\'') {
		strqt=*cur;
		while(*++cur != strqt) {
			if (*cur == '\\' && cur[1] == strqt) {
				*namecur++=strqt; cur += 2;
				// FIXME support other escapes
			} else {
				*namecur++=*cur;
			}
		}
		*namecur = 0;
		printf("found import to %s;\n", namebuf);
		namecur = namebuf;
	}
	++cur; goto read_start;

read_string:
	if (*cur == 0) goto read_done; //unterminated string!?
	if (*cur++ == strqt) goto read_start;
	goto read_string;

read_ml_comment:
	if (*cur == 0) goto read_done; //unterminated comment!
	if (*cur == '*' && cur[1] == '/') {
		cur += 2;
		goto read_start;
	}
	++cur; goto read_ml_comment;

skip_line:
	if (*cur == 0) goto read_done;
	if (*cur++ == '\n') goto read_start;
	goto skip_line;

read_done:

	munmap(file,sz);
}

enum e parsename(struct file* f) {
	char* ext = NULL;
	char* path = f->src;
	
	while(*path++!=0) {
		if(*path == '.') ext = path + 1;
	}

	
	if(ext == NULL) return badname;

	// quickly determine file type
	if        (ext[0] == 's'
			&& ext[2] == 's'
			&& ext[3] == 's') {
		if      (ext[1] == 'c') f -> kind = scss;
		else if (ext[1] == 'a') f -> kind = sass;
		else                    f -> kind = bad;
	} else if (ext[0] == 'c'
			&& ext[1] == 's'
			&& ext[2] == 's' )  f -> kind = css;
	else                        f -> kind = bad;

	if(f -> kind == sass || f -> kind == scss) {
		size_t sz = (ext - f->src);
		f->out = alloc(sz + 4);
		strncpy(f->out, f->src, sz);
		f->out[sz+0] = 'c';
		f->out[sz+1] = 's';
		f->out[sz+2] = 's';
		f->out[sz+3] = 0;
	} else f -> out = f -> src;

	printf("src %s; dst %s; ext %s;\n", f->src,f->out,ext);
}

int main(int argc, char** argv) {
	bufptr = corebuf;
	struct file top;
	readfile(&top, argv[1]);
	parsename(&top);
}

Added xpriv.c version [c67b168cf4].











































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
/* [ʞ] xpriv.c <c.hale.su/lexi/util>
 *  ~ lexi hale <lexi@hale.su>
 *  $ cc -Ofast xpriv.c -lrt -lutil -lX11 -oxpriv
 *  © affero general public license

 * xpriv.c is a tool for a very specific use case i have.
 * for security's  sake, i  don't tie my  ssh keys  to my
 * login session.  when i intend  to use them, i  spawn a
 * special terminal  and load the keys  into memory, then
 * exit that terminal when  i'm done using them. however,
 * i often lose  track of or accidentally  kill that win-
 * dow,  despite  adding  a  visual cue  to  its  prompt.
 * safekill.c was one half of  my attempt to address this
 * problem; this is the other.
 *
 * xpriv performs several different tasks to accomplish a
 * single  purpose: i  can  hit a  single keystroke  that
 * will either  conjure up  a new privileged  session, or
 * switch to one  that's already active if  it exists. it
 * does this  by first  checking for  the existence  of a
 * shared  memory  segment. if  it  doesn't  find it,  it
 * starts  a  new  session;  if it  *does*  find  it,  it
 * retrieves the  X11 window  ID from that  shared memory
 * and sends  a _NET_ACTIVE_WINDOW client message  to the
 * root X window. the  window manager interprets message,
 * activating the window.
 *
 * the  flag -k  can also  be passed,  in which  case the
 * utility instructs the running process to liquidate its
 * subprocesses and exit itself.
 *
 * if the shared  memory does not exist,  xpriv creates a
 * new instance  of urxvt. this  instance is told  to run
 * the command  “xpriv -a”  instead of the  user’s normal
 * shell. the -a flag instructs xpriv to get the terminal
 * window’s  ID from  the $WINDOWID  environment variable
 * which urxvt  sets. after this, a  ssh-agent process is
 * launched. spriv waits until it has opened a socket and
 * then runs ssh-add without parameters to add the user's
 * default keys to the session.
 *
 * xpriv  does  its  best   to  clean  up  after  itself,
 * killing all sensitive processes and their children and
 * removing the  shmem segment  when it  is no  longer in
 * use, even if exits somewhat abnormally. if you have to
 * kill -9 xpriv  at any point tho, you can  make it work
 * again (on linux) with
 *   rm /dev/shm/k.xpriv:(xpriv binary basename)
 *
 * you can have multiple xpriv sessions by creating soft-
 * links to the binary with a different name for each.

 TODO send signal to urxvtd directly instead of launching
      urxvtc as a separate process
	  
 TODO make shell & commands performed configurable, with
      flags to control or supplant ssh-agent 

 TODO add a randomizer call that works on BSD
 
 TODO document flags
 
 TODO implement/remove lock flag

 TODO add flag to bring window to current desktop */
 

#include <pwd.h>
#include <pty.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <X11/Xlib.h>
#include <errno.h>
#include <sys/random.h> //TODO bsd compat

#define shmem_prefix "/k.xpriv:"

typedef enum { false, true } bool;
enum mode { mode_usage, mode_go, mode_register,
	mode_kill, mode_lock };

enum res { ok, fail_parse, fail_arg, fail_opt, fail_shm,
               fail_mem, fail_pty, fail_nop, fail_win,
               fail_wid, fail_x11};
enum res bad(enum res code) {
	if (code == ok) return ok;

	write(1,"\e[1m|e|\e[0m ",12);
	const char* msg; uint8_t len;
	switch(code) {
		#define say(x) { msg = (x "\n"); len = (sizeof x) + 1; break; }
		case fail_parse: say("could not parse command-line options; pass -u for usage");
		case fail_arg  : say("invalid argument provided");
		case fail_opt  : say("no such option");
		case fail_shm  : say("instance already running");
		case fail_mem  : say("could not map memory");
		case fail_pty  : say("could not alloc pty");
		case fail_nop  : say("no instance running");
		case fail_win  : say("cannot open display");
		case fail_wid  : say("WINDOWID not defined");
		case fail_x11  : say("X server refuses to handle request");
		#undef say
	}
	write(1, msg, len);
	return code;
}

struct signal {
	Window    wid;
	pid_t     pid;
	pid_t     agent;
	enum mode op;
}*global;

char* itoa(unsigned long i, char* buf, size_t bufsz) {
	char* cur = buf + bufsz;
	while (cur >= buf) {
		*--cur='0' + i % 10;
		if (i < 10) break; else i /= 10;
	}
	return cur;
}

bool run;

struct termios initial_state;

void sigusr(int a) { if (global -> op == mode_kill) run = false; }
void sigterm(int a) { run = false; }

enum res register_window(const char* id, bool weak) {
	// the id field denotes the path to the shared memory
	// in use, and allows the user to have multiple
	// contexts by creating aliases to the binary
	int fd = shm_open(id, O_CREAT | O_EXCL | O_RDWR, 0600);
	ftruncate(fd, sizeof(struct signal));
	if (fd == -1) return fail_shm;
	
	struct signal* s = mmap(0, sizeof(struct signal),
			PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if(s == MAP_FAILED) return fail_mem;
	else global = s;

	Display* xdpy = XOpenDisplay(NULL);
	Atom xvital;

	/* x11 */ {
		if (xdpy == NULL) return fail_win;
		Window win;
		const char* winid_s;
		if (!(winid_s = getenv("WINDOWID"))) return fail_wid;
		win=(Window)strtol(winid_s,NULL,10);
		if(weak == false) xvital = XInternAtom(xdpy,"_k_vital",false);
		s -> wid = win;
	}

	pid_t child;
	if(child = fork()) {
		s -> pid = child;
		sigset_t mask, oldmask;
		sigemptyset(&mask);
		sigaddset(&mask, SIGUSR1);
		sigprocmask(SIG_BLOCK, &mask, &oldmask);

		signal(SIGUSR1, sigusr);
		signal(SIGTERM, sigterm);
		signal(SIGCHLD, sigterm);
		signal(SIGHUP, sigterm);

		run = true;
		while(run == true) {
			sigsuspend(&oldmask);
		}

		if(s -> op == mode_kill) {
			kill(s -> pid, SIGTERM);
		}
		kill(s -> agent, SIGTERM);
		sigprocmask(SIG_BLOCK, &mask, NULL);
		shm_unlink(id);
		if (!weak) {
			XDeleteProperty(xdpy,s -> wid,xvital);
			XSync(xdpy,false);
		}
		XCloseDisplay(xdpy);
	} else {
		// now we start ssh-agent and set the proper environment
		// variables
		pid_t ssha;

		/* messy part */ {
			const char* tmp; //tmpdir defined?
			if (!(tmp = getenv("TMPDIR"))) tmp = "/tmp";
			size_t tmpsz = strlen(tmp);
			
			char sockn[tmpsz + 1 + sizeof "ssh."
				+ 11];
			strcpy(sockn, tmp);
			sockn[tmpsz] = '/';
			strcpy(sockn+tmpsz+1,"ssh.");
			
			// first, gen a random identifier so we have the ability
			// to know where the socket winds up
			uint8_t* rndid = sockn+tmpsz+1+4;
			getrandom(rndid, 11, 0);
			
			// assuming ascii…
			for(uint8_t*i=rndid;i<rndid+11;++i) {
				*i = '0' + (*i % (25 * 2 + 10));
				if (*i > '9') *i += 7;
				if (*i > 'Z') *i += ('a' - 'Z');
			}

			if (ssha = fork()) {
				char pid_s_buf[16];
				char* pid_s = itoa(ssha, pid_s_buf, sizeof(pid_s_buf));
				while (access(sockn, F_OK));
					// avoid nasty race condition
				setenv("SSH_AGENT_PID", pid_s, true);
				setenv("SSH_AUTH_SOCK", sockn, true);
				s -> agent = ssha;
			} else {
				close(1); close(0);
				execlp("ssh-agent","ssh-agent","-D", "-a",sockn,0);
			}
		}
		
		pid_t sad;
		int p;
		if (sad = fork()) {
			int added;
			waitpid(sad, &added, 0);
			if (added == 0) {
				if (weak == false) {
					XChangeProperty(xdpy,s -> wid,xvital,xvital,8,PropModeReplace,"\01", 1);
					XSync(xdpy,false);
				}
				write(1,"\033c",3);
				execlp("fish","fish",NULL);
			} else { return ok; }
		} else {
			execlp("ssh-add","ssh-add",NULL);
		}
	}

	return ok; // this is kind of pointless but w/e
}
enum res kill_window(const char* id) {
	int fd = shm_open(id, O_RDWR, 0600);
	if (fd == -1) return fail_nop;

	struct signal* s = mmap(0, sizeof(struct signal),
			PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if(s == MAP_FAILED) return fail_mem;

	s -> op = mode_kill;
	kill(s -> pid, SIGUSR1);

	return ok;
}
enum res activate_window(Window w) {
	Display* dpy = XOpenDisplay(NULL);
	XEvent ev;
	long mask = SubstructureRedirectMask | SubstructureNotifyMask;
	ev.xclient.type       = ClientMessage;
	ev.xclient.serial     = 0;
	ev.xclient.send_event = True;
	ev.xclient.message_type = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False);
	ev.xclient.display    = dpy;
	ev.xclient.window     = w;
	ev.xclient.format     = 32;
	ev.xclient.data.l[0]  = 2;
	ev.xclient.data.l[1]  = CurrentTime;
	ev.xclient.data.l[2]  = 0;
	
	if(!XSendEvent(dpy, DefaultRootWindow(dpy), False, mask, &ev)) return fail_x11;

	XSync(dpy,false);
	return ok;
}

int main(int sz, char** argv) {
	enum mode op = mode_go;
	bool init_weak = false;

	for(int i = 1; i<sz; ++i) {
		char* v = argv[i];
		if (*v != '-') return bad(fail_arg);
		char* opt = v + 1;
	read_opt:
		switch(*opt) {
			case 'a': op = mode_register; break;
			case 'k': op = mode_kill; break;
			case 'l': op = mode_lock; break;
			case 'h': op = mode_usage; break;
			case 'w': init_weak = true; break;
			default: return bad(fail_opt);
		}
		if(opt[1] != 0) { ++opt; goto read_opt; }
	}

	size_t nsz;
	const char* basename, *p;
	for (p = argv[0]; *p!=0; ++p) {
		if(*p == '/') basename = p + 1;
	}
	nsz = p - basename;
	char shid[nsz + sizeof shmem_prefix + 0];
	strncpy(shid,shmem_prefix,sizeof shmem_prefix);
	strncpy(shid + sizeof shmem_prefix - 1, basename, nsz);

	if (op == mode_go) {
		int fd;
		if ((fd = shm_open(shid, O_RDWR, 0600)) == -1) {
			execlp("urxvtc", "urxvtc", "-bg", "[80]#4b0024",
					"-e", argv[0], (init_weak?"-aw":"-a"), 0);
		} else {
			struct signal*s = mmap(0, sizeof(struct signal),
					PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
			return bad(activate_window(s->wid));
		}
	} else if (op == mode_register)
		return bad(register_window(shid,init_weak));
	else if (op == mode_kill)
		return bad(kill_window(shid));
	else if (op == mode_usage) {
		write(1,"\e[1musage:\e[0m ",15);
		write(1, argv[0], strlen(argv[0]));
		write(1, " [-aklw [arg]]\n",16);
		return fail_parse;
	}
}