process_windows.odin 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  1. #+private file
  2. package os2
  3. import "base:runtime"
  4. import "core:strings"
  5. import win32 "core:sys/windows"
  6. import "core:time"
  7. @(private="package")
  8. _exit :: proc "contextless" (code: int) -> ! {
  9. win32.ExitProcess(u32(code))
  10. }
  11. @(private="package")
  12. _get_uid :: proc() -> int {
  13. return -1
  14. }
  15. @(private="package")
  16. _get_euid :: proc() -> int {
  17. return -1
  18. }
  19. @(private="package")
  20. _get_gid :: proc() -> int {
  21. return -1
  22. }
  23. @(private="package")
  24. _get_egid :: proc() -> int {
  25. return -1
  26. }
  27. @(private="package")
  28. _get_pid :: proc() -> int {
  29. return int(win32.GetCurrentProcessId())
  30. }
  31. @(private="package")
  32. _get_ppid :: proc() -> int {
  33. our_pid := win32.GetCurrentProcessId()
  34. snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0)
  35. if snap == win32.INVALID_HANDLE_VALUE {
  36. return -1
  37. }
  38. defer win32.CloseHandle(snap)
  39. entry := win32.PROCESSENTRY32W { dwSize = size_of(win32.PROCESSENTRY32W) }
  40. for status := win32.Process32FirstW(snap, &entry); status; /**/ {
  41. if entry.th32ProcessID == our_pid {
  42. return int(entry.th32ParentProcessID)
  43. }
  44. status = win32.Process32NextW(snap, &entry)
  45. }
  46. return -1
  47. }
  48. @(private="package")
  49. _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
  50. snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0)
  51. if snap == win32.INVALID_HANDLE_VALUE {
  52. err = _get_platform_error()
  53. return
  54. }
  55. list_d := make([dynamic]int, allocator) or_return
  56. entry := win32.PROCESSENTRY32W{dwSize = size_of(win32.PROCESSENTRY32W)}
  57. status := win32.Process32FirstW(snap, &entry)
  58. for status {
  59. append(&list_d, int(entry.th32ProcessID))
  60. status = win32.Process32NextW(snap, &entry)
  61. }
  62. list = list_d[:]
  63. return
  64. }
  65. @(require_results)
  66. read_memory_as_struct :: proc(h: win32.HANDLE, addr: rawptr, dest: ^$T) -> (bytes_read: uint, err: Error) {
  67. if !win32.ReadProcessMemory(h, addr, dest, size_of(T), &bytes_read) {
  68. err = _get_platform_error()
  69. }
  70. return
  71. }
  72. @(require_results)
  73. read_memory_as_slice :: proc(h: win32.HANDLE, addr: rawptr, dest: []$T) -> (bytes_read: uint, err: Error) {
  74. if !win32.ReadProcessMemory(h, addr, raw_data(dest), len(dest)*size_of(T), &bytes_read) {
  75. err = _get_platform_error()
  76. }
  77. return
  78. }
  79. @(private="package")
  80. _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
  81. info.pid = pid
  82. // Note(flysand): Open the process handle right away to prevent some race
  83. // conditions. Once the handle is open, the process will be kept alive by
  84. // the OS.
  85. ph := win32.INVALID_HANDLE_VALUE
  86. if selection >= {.Command_Line, .Environment, .Working_Dir, .Username} {
  87. ph = win32.OpenProcess(
  88. win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.PROCESS_VM_READ,
  89. false,
  90. u32(pid),
  91. )
  92. if ph == win32.INVALID_HANDLE_VALUE {
  93. err = _get_platform_error()
  94. return
  95. }
  96. }
  97. defer if ph != win32.INVALID_HANDLE_VALUE {
  98. win32.CloseHandle(ph)
  99. }
  100. snapshot_process: if selection >= {.PPid, .Priority} {
  101. entry, entry_err := _process_entry_by_pid(info.pid)
  102. if entry_err != nil {
  103. err = entry_err
  104. if entry_err == General_Error.Not_Exist {
  105. return
  106. } else {
  107. break snapshot_process
  108. }
  109. }
  110. if .PPid in selection {
  111. info.fields += {.PPid}
  112. info.ppid = int(entry.th32ParentProcessID)
  113. }
  114. if .Priority in selection {
  115. info.fields += {.Priority}
  116. info.priority = int(entry.pcPriClassBase)
  117. }
  118. }
  119. snapshot_modules: if .Executable_Path in selection {
  120. exe_path: string
  121. exe_path, err = _process_exe_by_pid(pid, allocator)
  122. if _, ok := err.(runtime.Allocator_Error); ok {
  123. return
  124. } else if err != nil {
  125. break snapshot_modules
  126. }
  127. info.executable_path = exe_path
  128. info.fields += {.Executable_Path}
  129. }
  130. read_peb: if selection >= {.Command_Line, .Environment, .Working_Dir} {
  131. process_info_size: u32
  132. process_info: win32.PROCESS_BASIC_INFORMATION
  133. status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size)
  134. if status != 0 {
  135. // TODO(flysand): There's probably a mismatch between NTSTATUS and
  136. // windows userland error codes, I haven't checked.
  137. err = Platform_Error(status)
  138. break read_peb
  139. }
  140. assert(process_info.PebBaseAddress != nil)
  141. process_peb: win32.PEB
  142. _, err = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb)
  143. if err != nil {
  144. break read_peb
  145. }
  146. process_params: win32.RTL_USER_PROCESS_PARAMETERS
  147. _, err = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params)
  148. if err != nil {
  149. break read_peb
  150. }
  151. temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
  152. if selection >= {.Command_Line, .Command_Args} {
  153. temp_allocator_scope(temp_allocator)
  154. cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return
  155. _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
  156. if err != nil {
  157. break read_peb
  158. }
  159. if .Command_Line in selection {
  160. info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return
  161. info.fields += {.Command_Line}
  162. }
  163. if .Command_Args in selection {
  164. info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return
  165. info.fields += {.Command_Args}
  166. }
  167. }
  168. if .Environment in selection {
  169. temp_allocator_scope(temp_allocator)
  170. env_len := process_params.EnvironmentSize / 2
  171. envs_w := make([]u16, env_len, temp_allocator) or_return
  172. _, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
  173. if err != nil {
  174. break read_peb
  175. }
  176. info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return
  177. info.fields += {.Environment}
  178. }
  179. if .Working_Dir in selection {
  180. temp_allocator_scope(temp_allocator)
  181. cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return
  182. _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
  183. if err != nil {
  184. break read_peb
  185. }
  186. info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return
  187. info.fields += {.Working_Dir}
  188. }
  189. }
  190. read_username: if .Username in selection {
  191. username: string
  192. username, err = _get_process_user(ph, allocator)
  193. if _, ok := err.(runtime.Allocator_Error); ok {
  194. return
  195. } else if err != nil {
  196. break read_username
  197. }
  198. info.username = username
  199. info.fields += {.Username}
  200. }
  201. err = nil
  202. return
  203. }
  204. @(private="package")
  205. _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
  206. pid := process.pid
  207. info.pid = pid
  208. // Data obtained from process snapshots
  209. snapshot_process: if selection >= {.PPid, .Priority} {
  210. entry, entry_err := _process_entry_by_pid(info.pid)
  211. if entry_err != nil {
  212. err = entry_err
  213. if entry_err == General_Error.Not_Exist {
  214. return
  215. } else {
  216. break snapshot_process
  217. }
  218. }
  219. if .PPid in selection {
  220. info.fields += {.PPid}
  221. info.ppid = int(entry.th32ParentProcessID)
  222. }
  223. if .Priority in selection {
  224. info.fields += {.Priority}
  225. info.priority = int(entry.pcPriClassBase)
  226. }
  227. }
  228. snapshot_module: if .Executable_Path in selection {
  229. exe_path: string
  230. exe_path, err = _process_exe_by_pid(pid, allocator)
  231. if _, ok := err.(runtime.Allocator_Error); ok {
  232. return
  233. } else if err != nil {
  234. break snapshot_module
  235. }
  236. info.executable_path = exe_path
  237. info.fields += {.Executable_Path}
  238. }
  239. ph := win32.HANDLE(process.handle)
  240. read_peb: if selection >= {.Command_Line, .Environment, .Working_Dir} {
  241. process_info_size: u32
  242. process_info: win32.PROCESS_BASIC_INFORMATION
  243. status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size)
  244. if status != 0 {
  245. // TODO(flysand): There's probably a mismatch between NTSTATUS and
  246. // windows userland error codes, I haven't checked.
  247. err = Platform_Error(status)
  248. return
  249. }
  250. assert(process_info.PebBaseAddress != nil)
  251. process_peb: win32.PEB
  252. _, err = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb)
  253. if err != nil {
  254. break read_peb
  255. }
  256. process_params: win32.RTL_USER_PROCESS_PARAMETERS
  257. _, err = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params)
  258. if err != nil {
  259. break read_peb
  260. }
  261. temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
  262. if selection >= {.Command_Line, .Command_Args} {
  263. temp_allocator_scope(temp_allocator)
  264. cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return
  265. _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
  266. if err != nil {
  267. break read_peb
  268. }
  269. if .Command_Line in selection {
  270. info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return
  271. info.fields += {.Command_Line}
  272. }
  273. if .Command_Args in selection {
  274. info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return
  275. info.fields += {.Command_Args}
  276. }
  277. }
  278. if .Environment in selection {
  279. temp_allocator_scope(temp_allocator)
  280. env_len := process_params.EnvironmentSize / 2
  281. envs_w := make([]u16, env_len, temp_allocator) or_return
  282. _, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
  283. if err != nil {
  284. break read_peb
  285. }
  286. info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return
  287. info.fields += {.Environment}
  288. }
  289. if .Working_Dir in selection {
  290. temp_allocator_scope(temp_allocator)
  291. cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return
  292. _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
  293. if err != nil {
  294. break read_peb
  295. }
  296. info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return
  297. info.fields += {.Working_Dir}
  298. }
  299. }
  300. read_username: if .Username in selection {
  301. username: string
  302. username, err = _get_process_user(ph, allocator)
  303. if _, ok := err.(runtime.Allocator_Error); ok {
  304. return
  305. } else if err != nil {
  306. break read_username
  307. }
  308. info.username = username
  309. info.fields += {.Username}
  310. }
  311. err = nil
  312. return
  313. }
  314. @(private="package")
  315. _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
  316. info.pid = get_pid()
  317. snapshot_process: if selection >= {.PPid, .Priority} {
  318. entry, entry_err := _process_entry_by_pid(info.pid)
  319. if entry_err != nil {
  320. err = entry_err
  321. if entry_err == General_Error.Not_Exist {
  322. return
  323. } else {
  324. break snapshot_process
  325. }
  326. }
  327. if .PPid in selection {
  328. info.fields += {.PPid}
  329. info.ppid = int(entry.th32ProcessID)
  330. }
  331. if .Priority in selection {
  332. info.fields += {.Priority}
  333. info.priority = int(entry.pcPriClassBase)
  334. }
  335. }
  336. module_filename: if .Executable_Path in selection {
  337. exe_filename_w: [256]u16
  338. path_len := win32.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w))
  339. assert(path_len > 0)
  340. info.executable_path = win32_utf16_to_utf8(exe_filename_w[:path_len], allocator) or_return
  341. info.fields += {.Executable_Path}
  342. }
  343. command_line: if selection >= {.Command_Line, .Command_Args} {
  344. command_line_w := win32.GetCommandLineW()
  345. assert(command_line_w != nil)
  346. if .Command_Line in selection {
  347. info.command_line = win32_wstring_to_utf8(command_line_w, allocator) or_return
  348. info.fields += {.Command_Line}
  349. }
  350. if .Command_Args in selection {
  351. info.command_args = _parse_command_line(command_line_w, allocator) or_return
  352. info.fields += {.Command_Args}
  353. }
  354. }
  355. read_environment: if .Environment in selection {
  356. env_block := win32.GetEnvironmentStringsW()
  357. assert(env_block != nil)
  358. info.environment = _parse_environment_block(env_block, allocator) or_return
  359. info.fields += {.Environment}
  360. }
  361. read_username: if .Username in selection {
  362. process_handle := win32.GetCurrentProcess()
  363. username: string
  364. username, err = _get_process_user(process_handle, allocator)
  365. if _, ok := err.(runtime.Allocator_Error); ok {
  366. return
  367. } else if err != nil {
  368. break read_username
  369. }
  370. info.username = username
  371. info.fields += {.Username}
  372. }
  373. if .Working_Dir in selection {
  374. // TODO(flysand): Implement this by reading PEB
  375. err = .Mode_Not_Implemented
  376. return
  377. }
  378. err = nil
  379. return
  380. }
  381. @(private="package")
  382. _process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) {
  383. // Note(flysand): The handle will be used for querying information so we
  384. // take the necessary permissions right away.
  385. dwDesiredAccess := win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.SYNCHRONIZE
  386. if .Mem_Read in flags {
  387. dwDesiredAccess |= win32.PROCESS_VM_READ
  388. }
  389. if .Mem_Write in flags {
  390. dwDesiredAccess |= win32.PROCESS_VM_WRITE
  391. }
  392. handle := win32.OpenProcess(
  393. dwDesiredAccess,
  394. false,
  395. u32(pid),
  396. )
  397. if handle == win32.INVALID_HANDLE_VALUE {
  398. err = _get_platform_error()
  399. } else {
  400. process = {pid = pid, handle = uintptr(handle)}
  401. }
  402. return
  403. }
  404. @(private="package")
  405. _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
  406. temp_allocator := TEMP_ALLOCATOR_GUARD({})
  407. command_line := _build_command_line(desc.command, temp_allocator)
  408. command_line_w := win32_utf8_to_wstring(command_line, temp_allocator) or_return
  409. environment := desc.env
  410. if desc.env == nil {
  411. environment = environ(temp_allocator) or_return
  412. }
  413. environment_block := _build_environment_block(environment, temp_allocator)
  414. environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator) or_return
  415. stderr_handle: win32.HANDLE
  416. stdout_handle: win32.HANDLE
  417. stdin_handle: win32.HANDLE
  418. null_handle: win32.HANDLE
  419. if desc.stdout == nil || desc.stderr == nil || desc.stdin == nil {
  420. null_handle = win32.CreateFileW(
  421. win32.L("NUL"),
  422. win32.GENERIC_READ|win32.GENERIC_WRITE,
  423. win32.FILE_SHARE_READ|win32.FILE_SHARE_WRITE,
  424. &win32.SECURITY_ATTRIBUTES{
  425. nLength = size_of(win32.SECURITY_ATTRIBUTES),
  426. bInheritHandle = true,
  427. },
  428. win32.OPEN_EXISTING,
  429. win32.FILE_ATTRIBUTE_NORMAL,
  430. nil,
  431. )
  432. // Opening NUL should always succeed.
  433. assert(null_handle != nil)
  434. }
  435. // NOTE(laytan): I believe it is fine to close this handle right after CreateProcess,
  436. // and we don't have to hold onto this until the process exits.
  437. defer if null_handle != nil {
  438. win32.CloseHandle(null_handle)
  439. }
  440. if desc.stdout == nil {
  441. stdout_handle = null_handle
  442. } else {
  443. stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd)
  444. }
  445. if desc.stderr == nil {
  446. stderr_handle = null_handle
  447. } else {
  448. stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd)
  449. }
  450. if desc.stdin == nil {
  451. stdin_handle = null_handle
  452. } else {
  453. stdin_handle = win32.HANDLE((^File_Impl)(desc.stdin.impl).fd)
  454. }
  455. working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator) or_else nil) if len(desc.working_dir) > 0 else nil
  456. process_info: win32.PROCESS_INFORMATION
  457. ok := win32.CreateProcessW(
  458. nil,
  459. command_line_w,
  460. nil,
  461. nil,
  462. true,
  463. win32.CREATE_UNICODE_ENVIRONMENT|win32.NORMAL_PRIORITY_CLASS,
  464. raw_data(environment_block_w),
  465. working_dir_w,
  466. &win32.STARTUPINFOW{
  467. cb = size_of(win32.STARTUPINFOW),
  468. hStdError = stderr_handle,
  469. hStdOutput = stdout_handle,
  470. hStdInput = stdin_handle,
  471. dwFlags = win32.STARTF_USESTDHANDLES,
  472. },
  473. &process_info,
  474. )
  475. if !ok {
  476. err = _get_platform_error()
  477. return
  478. }
  479. process = {pid = int(process_info.dwProcessId), handle = uintptr(process_info.hProcess)}
  480. return
  481. }
  482. @(private="package")
  483. _process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) {
  484. handle := win32.HANDLE(process.handle)
  485. timeout_ms := u32(timeout / time.Millisecond) if timeout >= 0 else win32.INFINITE
  486. switch win32.WaitForSingleObject(handle, timeout_ms) {
  487. case win32.WAIT_OBJECT_0:
  488. exit_code: u32
  489. if !win32.GetExitCodeProcess(handle, &exit_code) {
  490. err =_get_platform_error()
  491. return
  492. }
  493. time_created: win32.FILETIME
  494. time_exited: win32.FILETIME
  495. time_kernel: win32.FILETIME
  496. time_user: win32.FILETIME
  497. if !win32.GetProcessTimes(handle, &time_created, &time_exited, &time_kernel, &time_user) {
  498. err = _get_platform_error()
  499. return
  500. }
  501. process_state = {
  502. exit_code = int(exit_code),
  503. exited = true,
  504. pid = process.pid,
  505. success = true,
  506. system_time = _filetime_to_duration(time_kernel),
  507. user_time = _filetime_to_duration(time_user),
  508. }
  509. return
  510. case win32.WAIT_TIMEOUT:
  511. err = General_Error.Timeout
  512. return
  513. case:
  514. err = _get_platform_error()
  515. return
  516. }
  517. }
  518. @(private="package")
  519. _process_close :: proc(process: Process) -> Error {
  520. if !win32.CloseHandle(win32.HANDLE(process.handle)) {
  521. return _get_platform_error()
  522. }
  523. return nil
  524. }
  525. @(private="package")
  526. _process_kill :: proc(process: Process) -> Error {
  527. // Note(flysand): This is different than what the task manager's "kill process"
  528. // functionality does, as we don't try to send WM_CLOSE message first. This
  529. // is quite a rough way to kill the process, which should be consistent with
  530. // linux. The error code 9 is to mimic SIGKILL event.
  531. if !win32.TerminateProcess(win32.HANDLE(process.handle), 9) {
  532. return _get_platform_error()
  533. }
  534. return nil
  535. }
  536. _filetime_to_duration :: proc(filetime: win32.FILETIME) -> time.Duration {
  537. ticks := u64(filetime.dwHighDateTime)<<32 | u64(filetime.dwLowDateTime)
  538. return time.Duration(ticks * 100)
  539. }
  540. _process_entry_by_pid :: proc(pid: int) -> (entry: win32.PROCESSENTRY32W, err: Error) {
  541. snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0)
  542. if snap == win32.INVALID_HANDLE_VALUE {
  543. err = _get_platform_error()
  544. return
  545. }
  546. defer win32.CloseHandle(snap)
  547. entry = win32.PROCESSENTRY32W{dwSize = size_of(win32.PROCESSENTRY32W)}
  548. status := win32.Process32FirstW(snap, &entry)
  549. for status {
  550. if u32(pid) == entry.th32ProcessID {
  551. return
  552. }
  553. status = win32.Process32NextW(snap, &entry)
  554. }
  555. err = General_Error.Not_Exist
  556. return
  557. }
  558. // Note(flysand): Not sure which way it's better to get the executable path:
  559. // via toolhelp snapshots or by reading other process' PEB memory. I have
  560. // a slight suspicion that if both exe path and command line are desired,
  561. // it's faster to just read both from PEB, but maybe the toolhelp snapshots
  562. // are just better...?
  563. @(private="package")
  564. _process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path: string, err: Error) {
  565. snap := win32.CreateToolhelp32Snapshot(
  566. win32.TH32CS_SNAPMODULE|win32.TH32CS_SNAPMODULE32,
  567. u32(pid),
  568. )
  569. if snap == win32.INVALID_HANDLE_VALUE {
  570. err =_get_platform_error()
  571. return
  572. }
  573. defer win32.CloseHandle(snap)
  574. entry := win32.MODULEENTRY32W { dwSize = size_of(win32.MODULEENTRY32W) }
  575. status := win32.Module32FirstW(snap, &entry)
  576. if !status {
  577. err =_get_platform_error()
  578. return
  579. }
  580. return win32_wstring_to_utf8(raw_data(entry.szExePath[:]), allocator)
  581. }
  582. _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) {
  583. temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
  584. token_handle: win32.HANDLE
  585. if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) {
  586. err = _get_platform_error()
  587. return
  588. }
  589. token_user_size: u32
  590. if !win32.GetTokenInformation(token_handle, .TokenUser, nil, 0, &token_user_size) {
  591. // Note(flysand): Make sure the buffer too small error comes out, and not any other error
  592. err = _get_platform_error()
  593. if v, ok := is_platform_error(err); !ok || v != i32(win32.ERROR_INSUFFICIENT_BUFFER) {
  594. return
  595. }
  596. err = nil
  597. }
  598. token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator) or_return))
  599. if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) {
  600. err = _get_platform_error()
  601. return
  602. }
  603. sid_type: win32.SID_NAME_USE
  604. username_w: [256]u16
  605. domain_w: [256]u16
  606. username_chrs := u32(256)
  607. domain_chrs := u32(256)
  608. if !win32.LookupAccountSidW(nil, token_user.User.Sid, &username_w[0], &username_chrs, &domain_w[0], &domain_chrs, &sid_type) {
  609. err = _get_platform_error()
  610. return
  611. }
  612. username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator) or_return
  613. domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator) or_return
  614. return strings.concatenate({domain, "\\", username}, allocator)
  615. }
  616. _parse_command_line :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> (argv: []string, err: Error) {
  617. argc: i32
  618. argv_w := win32.CommandLineToArgvW(cmd_line_w, &argc)
  619. if argv_w == nil {
  620. return nil, _get_platform_error()
  621. }
  622. argv = make([]string, argc, allocator) or_return
  623. defer if err != nil {
  624. for arg in argv {
  625. delete(arg, allocator)
  626. }
  627. delete(argv, allocator)
  628. }
  629. for arg_w, i in argv_w[:argc] {
  630. argv[i] = win32_wstring_to_utf8(arg_w, allocator) or_return
  631. }
  632. return
  633. }
  634. _build_command_line :: proc(command: []string, allocator: runtime.Allocator) -> string {
  635. _write_byte_n_times :: #force_inline proc(builder: ^strings.Builder, b: byte, n: int) {
  636. for _ in 0 ..< n {
  637. strings.write_byte(builder, b)
  638. }
  639. }
  640. builder := strings.builder_make(allocator)
  641. for arg, i in command {
  642. if i != 0 {
  643. strings.write_byte(&builder, ' ')
  644. }
  645. j := 0
  646. if strings.contains_any(arg, "()[]{}^=;!'+,`~\" ") {
  647. strings.write_byte(&builder, '"')
  648. for j < len(arg) {
  649. backslashes := 0
  650. for j < len(arg) && arg[j] == '\\' {
  651. backslashes += 1
  652. j += 1
  653. }
  654. if j == len(arg) {
  655. _write_byte_n_times(&builder, '\\', 2*backslashes)
  656. break
  657. } else if arg[j] == '"' {
  658. _write_byte_n_times(&builder, '\\', 2*backslashes+1)
  659. strings.write_byte(&builder, arg[j])
  660. } else {
  661. _write_byte_n_times(&builder, '\\', backslashes)
  662. strings.write_byte(&builder, arg[j])
  663. }
  664. j += 1
  665. }
  666. strings.write_byte(&builder, '"')
  667. } else {
  668. strings.write_string(&builder, arg)
  669. }
  670. }
  671. return strings.to_string(builder)
  672. }
  673. _parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> (envs: []string, err: Error) {
  674. zt_count := 0
  675. for idx := 0; true; {
  676. if block[idx] == 0x0000 {
  677. zt_count += 1
  678. if block[idx+1] == 0x0000 {
  679. zt_count += 1
  680. break
  681. }
  682. }
  683. idx += 1
  684. }
  685. // Note(flysand): Each string in the environment block is terminated
  686. // by a NUL character. In addition, the environment block itself is
  687. // terminated by a NUL character. So the number of strings in the
  688. // environment block is the number of NUL character minus the
  689. // block terminator.
  690. env_count := zt_count - 1
  691. envs = make([]string, env_count, allocator) or_return
  692. defer if err != nil {
  693. for env in envs {
  694. delete(env, allocator)
  695. }
  696. delete(envs, allocator)
  697. }
  698. env_idx := 0
  699. last_idx := 0
  700. idx := 0
  701. for block[idx] != 0x0000 {
  702. for block[idx] != 0x0000 {
  703. idx += 1
  704. }
  705. env_w := block[last_idx:idx]
  706. envs[env_idx] = win32_utf16_to_utf8(env_w, allocator) or_return
  707. env_idx += 1
  708. idx += 1
  709. last_idx = idx
  710. }
  711. return
  712. }
  713. _build_environment_block :: proc(environment: []string, allocator: runtime.Allocator) -> string {
  714. builder := strings.builder_make(allocator)
  715. loop: #reverse for kv, cur_idx in environment {
  716. eq_idx := strings.index_byte(kv, '=')
  717. assert(eq_idx >= 0, "Malformed environment string. Expected '=' to separate keys and values")
  718. key := kv[:eq_idx]
  719. for old_kv in environment[cur_idx+1:] {
  720. old_key := old_kv[:strings.index_byte(old_kv, '=')]
  721. if key == old_key {
  722. continue loop
  723. }
  724. }
  725. strings.write_string(&builder, kv)
  726. strings.write_byte(&builder, 0)
  727. }
  728. // Note(flysand): In addition to the NUL-terminator for each string, the
  729. // environment block itself is NUL-terminated.
  730. strings.write_byte(&builder, 0)
  731. return strings.to_string(builder)
  732. }