process_windows.odin 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  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. defer if err != nil {
  83. free_process_info(info, allocator)
  84. }
  85. // Data obtained from process snapshots
  86. if selection >= {.PPid, .Priority} {
  87. entry, entry_err := _process_entry_by_pid(info.pid)
  88. if entry_err != nil {
  89. err = General_Error.Not_Exist
  90. return
  91. }
  92. if .PPid in selection {
  93. info.fields += {.PPid}
  94. info.ppid = int(entry.th32ParentProcessID)
  95. }
  96. if .Priority in selection {
  97. info.fields += {.Priority}
  98. info.priority = int(entry.pcPriClassBase)
  99. }
  100. }
  101. if .Executable_Path in selection { // snap module
  102. info.executable_path = _process_exe_by_pid(pid, allocator) or_return
  103. info.fields += {.Executable_Path}
  104. }
  105. ph := win32.INVALID_HANDLE_VALUE
  106. if selection >= {.Command_Line, .Environment, .Working_Dir, .Username} { // need process handle
  107. ph = win32.OpenProcess(
  108. win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.PROCESS_VM_READ,
  109. false,
  110. u32(pid),
  111. )
  112. if ph == win32.INVALID_HANDLE_VALUE {
  113. err = _get_platform_error()
  114. return
  115. }
  116. }
  117. defer if ph != win32.INVALID_HANDLE_VALUE {
  118. win32.CloseHandle(ph)
  119. }
  120. if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb
  121. process_info_size: u32
  122. process_info: win32.PROCESS_BASIC_INFORMATION
  123. status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size)
  124. if status != 0 {
  125. // TODO(flysand): There's probably a mismatch between NTSTATUS and
  126. // windows userland error codes, I haven't checked.
  127. err = Platform_Error(status)
  128. return
  129. }
  130. if process_info.PebBaseAddress == nil {
  131. // Not sure what the error is
  132. err = General_Error.Unsupported
  133. return
  134. }
  135. process_peb: win32.PEB
  136. _ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return
  137. process_params: win32.RTL_USER_PROCESS_PARAMETERS
  138. _ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return
  139. if selection >= {.Command_Line, .Command_Args} {
  140. TEMP_ALLOCATOR_GUARD()
  141. cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
  142. _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return
  143. if .Command_Line in selection {
  144. info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return
  145. info.fields += {.Command_Line}
  146. }
  147. if .Command_Args in selection {
  148. info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return
  149. info.fields += {.Command_Args}
  150. }
  151. }
  152. if .Environment in selection {
  153. TEMP_ALLOCATOR_GUARD()
  154. env_len := process_params.EnvironmentSize / 2
  155. envs_w := make([]u16, env_len, temp_allocator()) or_return
  156. _ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return
  157. info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return
  158. info.fields += {.Environment}
  159. }
  160. if .Working_Dir in selection {
  161. TEMP_ALLOCATOR_GUARD()
  162. cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
  163. _ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return
  164. info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return
  165. info.fields += {.Working_Dir}
  166. }
  167. }
  168. if .Username in selection {
  169. info.username = _get_process_user(ph, allocator) or_return
  170. info.fields += {.Username}
  171. }
  172. err = nil
  173. return
  174. }
  175. @(private="package")
  176. _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
  177. pid := process.pid
  178. info.pid = pid
  179. defer if err != nil {
  180. free_process_info(info, allocator)
  181. }
  182. // Data obtained from process snapshots
  183. if selection >= {.PPid, .Priority} { // snap process
  184. entry, entry_err := _process_entry_by_pid(info.pid)
  185. if entry_err != nil {
  186. err = General_Error.Not_Exist
  187. return
  188. }
  189. if .PPid in selection {
  190. info.fields += {.PPid}
  191. info.ppid = int(entry.th32ParentProcessID)
  192. }
  193. if .Priority in selection {
  194. info.fields += {.Priority}
  195. info.priority = int(entry.pcPriClassBase)
  196. }
  197. }
  198. if .Executable_Path in selection { // snap module
  199. info.executable_path = _process_exe_by_pid(pid, allocator) or_return
  200. info.fields += {.Executable_Path}
  201. }
  202. ph := win32.HANDLE(process.handle)
  203. if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb
  204. process_info_size: u32
  205. process_info: win32.PROCESS_BASIC_INFORMATION
  206. status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size)
  207. if status != 0 {
  208. // TODO(flysand): There's probably a mismatch between NTSTATUS and
  209. // windows userland error codes, I haven't checked.
  210. err = Platform_Error(status)
  211. return
  212. }
  213. if process_info.PebBaseAddress == nil {
  214. // Not sure what the error is
  215. err = General_Error.Unsupported
  216. return
  217. }
  218. process_peb: win32.PEB
  219. _ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return
  220. process_params: win32.RTL_USER_PROCESS_PARAMETERS
  221. _ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return
  222. if selection >= {.Command_Line, .Command_Args} {
  223. TEMP_ALLOCATOR_GUARD()
  224. cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
  225. _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return
  226. if .Command_Line in selection {
  227. info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return
  228. info.fields += {.Command_Line}
  229. }
  230. if .Command_Args in selection {
  231. info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return
  232. info.fields += {.Command_Args}
  233. }
  234. }
  235. if .Environment in selection {
  236. TEMP_ALLOCATOR_GUARD()
  237. env_len := process_params.EnvironmentSize / 2
  238. envs_w := make([]u16, env_len, temp_allocator()) or_return
  239. _ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return
  240. info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return
  241. info.fields += {.Environment}
  242. }
  243. if .Working_Dir in selection {
  244. TEMP_ALLOCATOR_GUARD()
  245. cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
  246. _ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return
  247. info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return
  248. info.fields += {.Working_Dir}
  249. }
  250. }
  251. if .Username in selection {
  252. info.username = _get_process_user(ph, allocator) or_return
  253. info.fields += {.Username}
  254. }
  255. err = nil
  256. return
  257. }
  258. @(private="package")
  259. _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
  260. info.pid = get_pid()
  261. defer if err != nil {
  262. free_process_info(info, allocator)
  263. }
  264. if selection >= {.PPid, .Priority} { // snap process
  265. entry, entry_err := _process_entry_by_pid(info.pid)
  266. if entry_err != nil {
  267. err = General_Error.Not_Exist
  268. return
  269. }
  270. if .PPid in selection {
  271. info.fields += {.PPid}
  272. info.ppid = int(entry.th32ProcessID)
  273. }
  274. if .Priority in selection {
  275. info.fields += {.Priority}
  276. info.priority = int(entry.pcPriClassBase)
  277. }
  278. }
  279. if .Executable_Path in selection {
  280. exe_filename_w: [256]u16
  281. path_len := win32.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w))
  282. info.executable_path = win32_utf16_to_utf8(exe_filename_w[:path_len], allocator) or_return
  283. info.fields += {.Executable_Path}
  284. }
  285. if selection >= {.Command_Line, .Command_Args} {
  286. command_line_w := win32.GetCommandLineW()
  287. if .Command_Line in selection {
  288. info.command_line = win32_wstring_to_utf8(command_line_w, allocator) or_return
  289. info.fields += {.Command_Line}
  290. }
  291. if .Command_Args in selection {
  292. info.command_args = _parse_command_line(command_line_w, allocator) or_return
  293. info.fields += {.Command_Args}
  294. }
  295. }
  296. if .Environment in selection {
  297. env_block := win32.GetEnvironmentStringsW()
  298. info.environment = _parse_environment_block(env_block, allocator) or_return
  299. info.fields += {.Environment}
  300. }
  301. if .Username in selection {
  302. process_handle := win32.GetCurrentProcess()
  303. info.username = _get_process_user(process_handle, allocator) or_return
  304. info.fields += {.Username}
  305. }
  306. if .Working_Dir in selection {
  307. // TODO(flysand): Implement this by reading PEB
  308. err = .Mode_Not_Implemented
  309. return
  310. }
  311. err = nil
  312. return
  313. }
  314. @(private="package")
  315. _process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) {
  316. // Note(flysand): The handle will be used for querying information so we
  317. // take the necessary permissions right away.
  318. dwDesiredAccess := win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.SYNCHRONIZE
  319. if .Mem_Read in flags {
  320. dwDesiredAccess |= win32.PROCESS_VM_READ
  321. }
  322. if .Mem_Write in flags {
  323. dwDesiredAccess |= win32.PROCESS_VM_WRITE
  324. }
  325. handle := win32.OpenProcess(
  326. dwDesiredAccess,
  327. false,
  328. u32(pid),
  329. )
  330. if handle == win32.INVALID_HANDLE_VALUE {
  331. err = _get_platform_error()
  332. } else {
  333. process = {pid = pid, handle = uintptr(handle)}
  334. }
  335. return
  336. }
  337. @(private="package")
  338. _Sys_Process_Attributes :: struct {}
  339. @(private="package")
  340. _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
  341. TEMP_ALLOCATOR_GUARD()
  342. command_line := _build_command_line(desc.command, temp_allocator())
  343. command_line_w := win32_utf8_to_wstring(command_line, temp_allocator()) or_return
  344. environment := desc.env
  345. if desc.env == nil {
  346. environment = environ(temp_allocator())
  347. }
  348. environment_block := _build_environment_block(environment, temp_allocator())
  349. environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator()) or_return
  350. stderr_handle := win32.GetStdHandle(win32.STD_ERROR_HANDLE)
  351. stdout_handle := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE)
  352. stdin_handle := win32.GetStdHandle(win32.STD_INPUT_HANDLE)
  353. if desc.stdout != nil {
  354. stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd)
  355. }
  356. if desc.stderr != nil {
  357. stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd)
  358. }
  359. if desc.stdin != nil {
  360. stdin_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd)
  361. }
  362. working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator()) or_else nil) if len(desc.working_dir) > 0 else nil
  363. process_info: win32.PROCESS_INFORMATION
  364. ok := win32.CreateProcessW(
  365. nil,
  366. command_line_w,
  367. nil,
  368. nil,
  369. true,
  370. win32.CREATE_UNICODE_ENVIRONMENT|win32.NORMAL_PRIORITY_CLASS,
  371. raw_data(environment_block_w),
  372. working_dir_w,
  373. &win32.STARTUPINFOW{
  374. cb = size_of(win32.STARTUPINFOW),
  375. hStdError = stderr_handle,
  376. hStdOutput = stdout_handle,
  377. hStdInput = stdin_handle,
  378. dwFlags = win32.STARTF_USESTDHANDLES,
  379. },
  380. &process_info,
  381. )
  382. if !ok {
  383. err = _get_platform_error()
  384. return
  385. }
  386. process = {pid = int(process_info.dwProcessId), handle = uintptr(process_info.hProcess)}
  387. return
  388. }
  389. @(private="package")
  390. _process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) {
  391. handle := win32.HANDLE(process.handle)
  392. timeout_ms := u32(timeout / time.Millisecond) if timeout >= 0 else win32.INFINITE
  393. switch win32.WaitForSingleObject(handle, timeout_ms) {
  394. case win32.WAIT_OBJECT_0:
  395. exit_code: u32
  396. if !win32.GetExitCodeProcess(handle, &exit_code) {
  397. err =_get_platform_error()
  398. return
  399. }
  400. time_created: win32.FILETIME
  401. time_exited: win32.FILETIME
  402. time_kernel: win32.FILETIME
  403. time_user: win32.FILETIME
  404. if !win32.GetProcessTimes(handle, &time_created, &time_exited, &time_kernel, &time_user) {
  405. err = _get_platform_error()
  406. return
  407. }
  408. process_state = {
  409. exit_code = int(exit_code),
  410. exited = true,
  411. pid = process.pid,
  412. success = true,
  413. system_time = _filetime_to_duration(time_kernel),
  414. user_time = _filetime_to_duration(time_user),
  415. }
  416. return
  417. case win32.WAIT_TIMEOUT:
  418. err = General_Error.Timeout
  419. return
  420. case:
  421. err = _get_platform_error()
  422. return
  423. }
  424. }
  425. @(private="package")
  426. _process_close :: proc(process: Process) -> Error {
  427. if !win32.CloseHandle(win32.HANDLE(process.handle)) {
  428. return _get_platform_error()
  429. }
  430. return nil
  431. }
  432. @(private="package")
  433. _process_kill :: proc(process: Process) -> Error {
  434. // Note(flysand): This is different than what the task manager's "kill process"
  435. // functionality does, as we don't try to send WM_CLOSE message first. This
  436. // is quite a rough way to kill the process, which should be consistent with
  437. // linux. The error code 9 is to mimic SIGKILL event.
  438. if !win32.TerminateProcess(win32.HANDLE(process.handle), 9) {
  439. return _get_platform_error()
  440. }
  441. return nil
  442. }
  443. _filetime_to_duration :: proc(filetime: win32.FILETIME) -> time.Duration {
  444. ticks := u64(filetime.dwHighDateTime)<<32 | u64(filetime.dwLowDateTime)
  445. return time.Duration(ticks * 100)
  446. }
  447. _process_entry_by_pid :: proc(pid: int) -> (entry: win32.PROCESSENTRY32W, err: Error) {
  448. snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0)
  449. if snap == win32.INVALID_HANDLE_VALUE {
  450. err = _get_platform_error()
  451. return
  452. }
  453. defer win32.CloseHandle(snap)
  454. entry = win32.PROCESSENTRY32W{dwSize = size_of(win32.PROCESSENTRY32W)}
  455. status := win32.Process32FirstW(snap, &entry)
  456. for status {
  457. if u32(pid) == entry.th32ProcessID {
  458. return
  459. }
  460. status = win32.Process32NextW(snap, &entry)
  461. }
  462. err = General_Error.Not_Exist
  463. return
  464. }
  465. // Note(flysand): Not sure which way it's better to get the executable path:
  466. // via toolhelp snapshots or by reading other process' PEB memory. I have
  467. // a slight suspicion that if both exe path and command line are desired,
  468. // it's faster to just read both from PEB, but maybe the toolhelp snapshots
  469. // are just better...?
  470. @(private="package")
  471. _process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path: string, err: Error) {
  472. snap := win32.CreateToolhelp32Snapshot(
  473. win32.TH32CS_SNAPMODULE|win32.TH32CS_SNAPMODULE32,
  474. u32(pid),
  475. )
  476. if snap == win32.INVALID_HANDLE_VALUE {
  477. err =_get_platform_error()
  478. return
  479. }
  480. defer win32.CloseHandle(snap)
  481. entry := win32.MODULEENTRY32W { dwSize = size_of(win32.MODULEENTRY32W) }
  482. status := win32.Module32FirstW(snap, &entry)
  483. if !status {
  484. err =_get_platform_error()
  485. return
  486. }
  487. return win32_wstring_to_utf8(raw_data(entry.szExePath[:]), allocator)
  488. }
  489. _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) {
  490. TEMP_ALLOCATOR_GUARD()
  491. token_handle: win32.HANDLE
  492. if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) {
  493. err = _get_platform_error()
  494. return
  495. }
  496. token_user_size: u32
  497. if !win32.GetTokenInformation(token_handle, .TokenUser, nil, 0, &token_user_size) {
  498. // Note(flysand): Make sure the buffer too small error comes out, and not any other error
  499. err = _get_platform_error()
  500. if v, ok := is_platform_error(err); !ok || v != i32(win32.ERROR_INSUFFICIENT_BUFFER) {
  501. return
  502. }
  503. err = nil
  504. }
  505. token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator()) or_return))
  506. if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) {
  507. err = _get_platform_error()
  508. return
  509. }
  510. sid_type: win32.SID_NAME_USE
  511. username_w: [256]u16
  512. domain_w: [256]u16
  513. username_chrs := u32(256)
  514. domain_chrs := u32(256)
  515. if !win32.LookupAccountSidW(nil, token_user.User.Sid, &username_w[0], &username_chrs, &domain_w[0], &domain_chrs, &sid_type) {
  516. err = _get_platform_error()
  517. return
  518. }
  519. username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator()) or_return
  520. domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) or_return
  521. return strings.concatenate({domain, "\\", username}, allocator)
  522. }
  523. _parse_command_line :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> (argv: []string, err: Error) {
  524. argc: i32
  525. argv_w := win32.CommandLineToArgvW(cmd_line_w, &argc)
  526. if argv_w == nil {
  527. return nil, _get_platform_error()
  528. }
  529. argv = make([]string, argc, allocator) or_return
  530. defer if err != nil {
  531. for arg in argv {
  532. delete(arg, allocator)
  533. }
  534. delete(argv, allocator)
  535. }
  536. for arg_w, i in argv_w[:argc] {
  537. argv[i] = win32_wstring_to_utf8(arg_w, allocator) or_return
  538. }
  539. return
  540. }
  541. _build_command_line :: proc(command: []string, allocator: runtime.Allocator) -> string {
  542. _write_byte_n_times :: #force_inline proc(builder: ^strings.Builder, b: byte, n: int) {
  543. for _ in 0 ..< n {
  544. strings.write_byte(builder, b)
  545. }
  546. }
  547. builder := strings.builder_make(allocator)
  548. for arg, i in command {
  549. if i != 0 {
  550. strings.write_byte(&builder, ' ')
  551. }
  552. j := 0
  553. strings.write_byte(&builder, '"')
  554. for j < len(arg) {
  555. backslashes := 0
  556. for j < len(arg) && arg[j] == '\\' {
  557. backslashes += 1
  558. j += 1
  559. }
  560. if j == len(arg) {
  561. _write_byte_n_times(&builder, '\\', 2*backslashes)
  562. break
  563. } else if arg[j] == '"' {
  564. _write_byte_n_times(&builder, '\\', 2*backslashes+1)
  565. strings.write_byte(&builder, '"')
  566. } else {
  567. _write_byte_n_times(&builder, '\\', backslashes)
  568. strings.write_byte(&builder, arg[j])
  569. }
  570. j += 1
  571. }
  572. strings.write_byte(&builder, '"')
  573. }
  574. return strings.to_string(builder)
  575. }
  576. _parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> (envs: []string, err: Error) {
  577. zt_count := 0
  578. for idx := 0; true; {
  579. if block[idx] == 0x0000 {
  580. zt_count += 1
  581. if block[idx+1] == 0x0000 {
  582. zt_count += 1
  583. break
  584. }
  585. }
  586. idx += 1
  587. }
  588. // Note(flysand): Each string in the environment block is terminated
  589. // by a NUL character. In addition, the environment block itself is
  590. // terminated by a NUL character. So the number of strings in the
  591. // environment block is the number of NUL character minus the
  592. // block terminator.
  593. env_count := zt_count - 1
  594. envs = make([]string, env_count, allocator) or_return
  595. defer if err != nil {
  596. for env in envs {
  597. delete(env, allocator)
  598. }
  599. delete(envs, allocator)
  600. }
  601. env_idx := 0
  602. last_idx := 0
  603. idx := 0
  604. for block[idx] != 0x0000 {
  605. for block[idx] != 0x0000 {
  606. idx += 1
  607. }
  608. env_w := block[last_idx:idx]
  609. envs[env_idx] = win32_utf16_to_utf8(env_w, allocator) or_return
  610. env_idx += 1
  611. idx += 1
  612. last_idx = idx
  613. }
  614. return
  615. }
  616. _build_environment_block :: proc(environment: []string, allocator: runtime.Allocator) -> string {
  617. builder := strings.builder_make(allocator)
  618. loop: #reverse for kv, cur_idx in environment {
  619. eq_idx := strings.index_byte(kv, '=')
  620. assert(eq_idx >= 0, "Malformed environment string. Expected '=' to separate keys and values")
  621. key := kv[:eq_idx]
  622. for old_kv in environment[cur_idx+1:] {
  623. old_key := old_kv[:strings.index_byte(old_kv, '=')]
  624. if key == old_key {
  625. continue loop
  626. }
  627. }
  628. strings.write_string(&builder, kv)
  629. strings.write_byte(&builder, 0)
  630. }
  631. // Note(flysand): In addition to the NUL-terminator for each string, the
  632. // environment block itself is NUL-terminated.
  633. strings.write_byte(&builder, 0)
  634. return strings.to_string(builder)
  635. }