The code displayed on this page is not copyrighted by me but by the owners of respective repositories as also mentioned in the headers of the various files.
Executable seed!
Executable seed!
7F 45 4C 46 01 01 01 03 00 00 00 00 00 00 00 00 02 00 03 00 01 00 00 00 54 80 04 08 34 00 00 00 00 00 00 00 00 00 00 00 34 00 20 00 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 80 04 08 00 80 04 08 80 02 00 00 80 02 00 00 07 00 00 00 01 00 00 00 58 5B 5B 85 DB 75 06 50 BB 3A 82 04 08 31 C9 6A 05 58 CD 80 85 C0 7E 6C A3 68 82 04 08 58 89 E5 6A 2D 58 31 DB CD 80 A3 78 82 04 08 A1 70 82 04 08 E8 3F 01 00 00 89 C3 89 D9 31 C0 A3 7C 82 04 08 A3 6C 82 04 08 E8 95 00 00 00 85 C0 74 05 89 01 83 C1 04 A1 6C 82 04 08 85 C0 74 E9 39 CB 74 CB E8 4F 00 00 00 8B 03 85 C0 74 18 53 6A 02 58 CD 80 5B 85 C0 7C 0D 75 12 6A 0B 58 89 EA 89 D9 8B 1B CD 80 6A 01 5B 89 D8 CD 80 89 C3 B9 7C 82 04 08 31 D2 6A 07 58 CD 80 A1 7C 82 04 08 85 C0 74 8A B8 48 82 04 08 E8 07 01 00 00 EB D6 31 DB 6A 01 58 CD 80 53 B8 43 82 04 08 E8 F3 00 00 00 8B 03 E8 EC 00 00 00 83 C3 04 6A 20 58 E8 FE 00 00 00 39 CB 75 EA 6A 0A 58 E8 F2 00 00 00 5B C3 53 51 A1 74 82 04 08 E8 89 00 00 00 89 C3 89 C1 E8 9F 00 00 00 3C FC 74 B5 3C 20 74 42 3C 09 74 3E 3C 0A 75 0A 6A 01 58 A3 6C 82 04 08 EB 30 3C 22 75 07 E8 32 00 00 00 EB 25 3C 23 75 0F E8 40 00 00 00 6A 01 58 A3 6C 82 04 08 EB 12 3C 5C 75 07 E8 5E 00 00 00 EB 07 88 01 83 C1 01 EB B1 39 CB 75 02 31 DB 89 D8 59 5B C3 E8 45 00 00 00 3C FC 0F 84 2D FF FF FF 3C 22 74 07 88 01 83 C1 01 EB E8 C3 E8 2C 00 00 00 3C FC 0F 84 14 FF FF FF 3C 0A 75 EF C3 53 51 52 8B 1D 78 82 04 08 01 C3 6A 2D 58 CD 80 A1 78 82 04 08 89 1D 78 82 04 08 5A 59 5B C3 53 51 52 6A FC 58 50 8D 0C 24 8B 1D 68 82 04 08 6A 03 58 6A 01 5A CD 80 58 3C FC 5A 59 5B C3 53 51 89 C3 85 C0 74 12 31 C0 8A 03 85 C0 74 0A E8 08 00 00 00 83 C3 01 EB EE 59 5B C3 53 51 52 50 8D 0C 24 6A 01 5B 6A 04 58 89 DA CD 80 58 5A 59 5B C3 6B 61 65 6D 2E 78 38 36 00 20 2B 3E 20 00 53 75 62 70 72 6F 63 65 73 73 20 65 72 72 6F 72 0A 41 42 4F 52 54 49 4E 47 20 48 41 52 44 0A 00 00 00 00 00 00 00 00 00 00 04 00 00 00 10 00 00 00 00 00 00 00 00 00 00
7F 45 4C 46 01 01 01 03 00 00 00 00 00 00 00 00 02 00 03 00 01 00 00 00 54 80 04 08 34 00 00 00 00 00 00 00 00 00 00 00 34 00 20 00 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 80 04 08 00 80 04 08 00 01 00 00 00 01 00 00 07 00 00 00 01 00 00 00 58 5B 5B 31 C9 31 D2 6A 05 58 CD 80 89 C6 5B 66 B9 41 02 66 BA C0 01 6A 05 58 CD 80 89 C2 6A FF 5D 31 FF E8 68 00 00 00 E8 1B 00 00 00 85 C0 7C F2 85 ED 7D 06 89 C7 31 ED EB E8 C1 E7 04 01 F8 4D E8 39 00 00 00 EB DB 3C 23 74 1E 3C 3B 74 1A 3C 30 7C 1F 3C 3A 7C 1F 3C 41 7C 17 3C 47 7C 1C 3C 61 7C 0F 3C 67 7C 12 EB 09 E8 21 00 00 00 3C 0A 75 F7 6A FF 58 C3 2C 30 C3 2C 20 2C 37 C3 89 D3 52 6A 01 5A 50 89 E1 6A 04 58 CD 80 5B 5A C3 52 6A 01 5A 53 89 E1 89 F3 6A 03 58 CD 80 85 C0 74 03 58 5A C3 31 DB 6A 01 58 CD 80
## Copyright (C) 2019 Jeremiah Orians ## Copyright (C) 2022 Andrius Å tikonas # # SPDX-License-Identifier: GPL-3.0-or-later ## ELF Header #:ELF_base 7F 45 4C 46 # e_ident[EI_MAG0-3] ELF's magic number 01 # e_ident[EI_CLASS] Indicating 32 bit 01 # e_ident[EI_DATA] Indicating little endianness 01 # e_ident[EI_VERSION] Indicating original elf 03 # e_ident[EI_OSABI] Set at 3 because FreeBSD is strict 00 # e_ident[EI_ABIVERSION] Set at 0 because no one cares 00 00 00 00 00 00 00 # e_ident[EI_PAD] 02 00 # e_type Indicating Executable 03 00 # e_machine Indicating x86 01 00 00 00 # e_version Indicating original elf 54 80 04 08 # e_entry Address of the entry point 34 00 00 00 # e_phoff Address of program header table 00 00 00 00 # e_shoff Address of section header table 00 00 00 00 # e_flags 34 00 # e_ehsize Indicating our 52 Byte header 20 00 # e_phentsize size of a program header table 01 00 # e_phnum number of entries in program table 00 00 # e_shentsize size of a section header table 00 00 # e_shnum number of entries in section table 00 00 # e_shstrndx index of the section names ## Program Header #:ELF_program_headers #:ELF_program_header__text 01 00 00 00 # ph_type: PT-LOAD = 1 00 00 00 00 # ph_offset 00 80 04 08 # ph_vaddr 00 80 04 08 # ph_physaddr 00 01 00 00 # ph_filesz 00 01 00 00 # ph_memsz 07 00 00 00 # ph_flags: PF-X|PF-W|PF-R = 7 01 00 00 00 # ph_align #:ELF_text # Where the ELF Header is going to hit # Simply jump to _start # Our main function # :_start ; (0x8048054) 58 ; pop_eax # Get the number of arguments 5B ; pop_ebx # Get the program name 5B ; pop_ebx # Get the actual input name 31C9 ; xor_ecx,ecx # prepare read_only, ecx = 0 31D2 ; xor_edx,edx # Extra sure, edx = 0 6A 05 ; push !5 # prepare to set eax to 5 58 ; pop_eax # the syscall number for open() CD 80 ; int !0x80 # Now open that damn file 89C6 ; mov_esi,eax # Preserve the file pointer we were given 5B ; pop_ebx # Get the actual output name 66B9 4102 ; mov_cx, @577 # Prepare file as O_WRONLY|O_CREAT|O_TRUNC 66BA C001 ; mov_dx, @448 # Prepare file as RWX for owner only (700 in octal) 6A 05 ; push !5 # prepare to set eax to 5 58 ; pop_eax # the syscall number for open() CD 80 ; int !0x80 # Now open that damn file 89C2 ; mov_edx,eax # Preserve the file pointer we were given # Our flag for byte processing 6A FF ; push !-1 5D ; pop_ebp # ebp = -1 # temp storage for the sum 31FF ; xor_edi,edi # edi = 0 #:loop ; (0x8048077) # Read a byte E8 68000000 ; call %Read_byte # process byte E8 1B000000 ; call %hex # Deal with -1 values 85C0 ; test_eax,eax 7C F2 ; jl !loop # deal with toggle 85ED ; test_ebp,ebp # jump if ebp >= 0 7D 06 ; jge !print # process first byte of pair 89C7 ; mov_edi,eax 31ED ; xor_ebp,ebp # ebp = 0 EB E8 ; jmp !loop # process second byte of pair #:print ; (0x804808F) # update the sum and store in output C1E7 04 ; shl_edi, !4 01F8 ; add_eax,edi # flip the toggle 4D ; DEC_EBP # ebp = -1 E8 39000000 ; call %write_byte EB DB ; jmp !loop #:hex ; (0x804809C) # Purge Comment Lines (#) 3C 23 ; cmp_al, !35 74 1E ; je !purge_comment # Purge Comment Lines (;) 3C 3B ; cmp_al, !59 74 1A ; je !purge_comment # deal all ascii less than 0 3C 30 ; cmp_al, !48 7C 1F ; jl !ascii_other # deal with 0-9 3C 3A ; cmp_al, !58 7C 1F ; jl !ascii_num # deal with all ascii less than A 3C 41 ; cmp_al, !65 7C 17 ; jl !ascii_other # deal with A-F 3C 47 ; cmp_al, !71 7C 1C ; jl !ascii_high # deal with all ascii less than a 3C 61 ; cmp_al, !97 7C 0F ; jl !ascii_other # deal with a-f 3C 67 ; cmp_al, !103 7C 12 ; jl !ascii_low # The rest that remains needs to be ignored EB 09 ; jmp !ascii_other #:purge_comment ; (0x80480BE) # Read a byte E8 21000000 ; call %Read_byte # Loop if not LF 3C 0A ; cmp_al, !10 75 F7 ; jne !purge_comment # Otherwise return -1 #:ascii_other ; (0x80480C7) 6A FF ; push !-1 58 ; pop_eax # return -1 C3 ; ret #:ascii_num ; (0x80480CB) 2C 30 ; sub_al, !48 C3 ; ret #:ascii_low ; (0x80480CE) 2C 20 ; sub_al, !32 # convert to uppercase #:ascii_high ; (0x80480D0) 2C 37 ; sub_al, !55 C3 ; ret # Writes byte stored in al #:write_byte ; (0x80480D3) # Print our Hex 89D3 ; mov_ebx,edx # Where are we writing to 52 ; push_edx # protect fout 6A 01 ; push !1 # prepare to set edx to 1 5A ; pop_edx # set the size of chars we want 50 ; push_eax # Move output to stack 89E1 ; mov_ecx,esp # What we are writing 6A 04 ; push !4 # prepare to set eax to 4 58 ; pop_eax # the syscall number for write CD 80 ; int !0x80 # call the Kernel 5B ; pop_ebx # deallocate stack 5A ; pop_edx # restore fout C3 ; ret #:Read_byte ; (0x80480E4) # Attempt to read 1 byte from Input file 52 ; push_edx # protect fout 6A 01 ; push !1 # prepare to set edx to 1 5A ; pop_edx # set the size of chars we want 53 ; push_ebx # allocate stack 89E1 ; mov_ecx,esp # Where to put it 89F3 ; mov_ebx,esi # Where are we reading from 6A 03 ; push !3 # prepare to set eax to 3 58 ; pop_eax # the syscall number for read CD 80 ; int !0x80 # call the Kernel 85C0 ; test_eax,eax # check what we got 74 03 ; je !Done # Got EOF call it done # load byte 58 ; pop_eax # load char 5A ; pop_edx # restore fout C3 ; ret #:Done ; (0x80480F9) # program completed Successfully 31DB ; xor_ebx,ebx # All is well, ebx = 0 6A 01 ; push !1 58 ; pop_eax # put the exit syscall number in eax CD 80 ; int !0x80 # Call it a good day #:ELF_end
# SPDX-FileCopyrightText: 2020 Jeremiah Orians # SPDX-FileCopyrightText: 2022 Andrius Å tikonas # # SPDX-License-Identifier: GPL-3.0-or-later ## ELF Header # :ELF_base ; (0x8048000) 7F 45 4C 46 # e_ident[EI_MAG0-3] ELF's magic number 01 # e_ident[EI_CLASS] Indicating 32 bit 01 # e_ident[EI_DATA] Indicating little endianness 01 # e_ident[EI_VERSION] Indicating original elf 03 # e_ident[EI_OSABI] Set at 3 because FreeBSD is strict 00 # e_ident[EI_ABIVERSION] Set at 0 because none cares 00 00 00 00 00 00 00 # e_ident[EI_PAD] 02 00 # e_type Indicating Executable 03 00 # e_machine Indicating x86 01 00 00 00 # e_version Indicating original elf 54 80 04 08 # e_entry Address of the entry point 34 00 00 00 # e_phoff Address of program header table 00 00 00 00 # e_shoff Address of section header table 00 00 00 00 # e_flags 34 00 # e_ehsize Indicating our 52 Byte header 20 00 # e_phentsize size of a program header table 01 00 # e_phnum number of entries in program table 00 00 # e_shentsize size of a section header table 00 00 # e_shnum number of entries in section table 00 00 # e_shstrndx index of the section names ## Program Header # :ELF_program_headers # :ELF_program_header__text 01 00 00 00 # ph_type: PT-LOAD = 1 00 00 00 00 # ph_offset 00 80 04 08 # ph_vaddr 00 80 04 08 # ph_physaddr 80 02 00 00 # ph_filesz 80 02 00 00 # ph_memsz 07 00 00 00 # ph_flags: PF-X|PF-W|PF-R = 7 01 00 00 00 # ph_align # :ELF_text # :_start ; (0x8048054) 58 ; pop_eax # Get the number of arguments 5B ; pop_ebx # Get the program name 5B ; pop_ebx # Get the actual input name 85DB ; test_ebx,ebx # Check for missing output 75 06 ; jne !_start_out # Have real input 50 ; push_eax # Need to adjust stack BB 3A820408 ; mov_ebx, &default_file # Use "kaem.x86" # :_start_out ; (0x8048061) 31C9 ; xor_ecx,ecx # prepare read_only 6A 05 ; push !5 58 ; pop_eax # the syscall number for open() CD80 ; int !0x80 # Now open that damn file 85C0 ; test_eax,eax # IF NULL We couldn't open the file 7E 6C ; jle !Exit_Failure # Abort hard A3 68820408 ; mov_[DWORD],eax &script # Set input pointer 58 ; pop_eax # Get start of envp 89E5 ; mov_ebp,esp # Protect envp 6A 2D ; push !45 58 ; pop_eax # the Syscall # for SYS_BRK 31DB ; xor_ebx,ebx # Get current brk CD80 ; int !0x80 # Let the kernel do the work A3 78820408 ; mov_[DWORD],eax &MALLOC # Set our malloc pointer # Where the main work gets done # Using EBX for tokens and ECX for tokens[i] # :main_loop ; (0x8048080) A1 70820408 ; mov_eax,[DWORD] &max_args # Using 256 char* of space E8 3F010000 ; call %malloc # get it 89C3 ; mov_ebx,eax # set tokens 89D9 ; mov_ecx,ebx # I = 0 31C0 ; xor_eax,eax # Using 0 A3 7C820408 ; mov_[DWORD],eax &status # status = 0 A3 6C820408 ; mov_[DWORD],eax &command_done # command_done = 0 # Using EAX for result and EBX for tokens[i] # :collect_command ; (0x804809A) E8 95000000 ; call %collect_token # Get another token 85C0 ; test_eax,eax # if NULL == result 74 05 ; je !collect_command_comment # It is a comment, don't store 8901 ; mov_[ecx],eax # tokens[i] = result 83C1 04 ; add_ecx, !4 # i = i + 1 (adjusted for char* size) # :collect_command_comment ; (0x80480A8) A1 6C820408 ; mov_eax,[DWORD] &command_done # Using command_done 85C0 ; test_eax,eax # IF 0 == command_done 74 E9 ; je !collect_command # keep looping # Deal with line comments 39CB ; cmp_ebx,ecx # if 0 < i 74 CB ; je !main_loop # It was a comment E8 4F000000 ; call %print_command # print the command 8B03 ; mov_eax,[ebx] # program = tokens[0] 85C0 ; test_eax,eax # IF NULL == program 74 18 ; je !Exit_Failure # Some shit went down, abort 53 ; push_ebx # Protect Tokens 6A 02 ; push !2 58 ; pop_eax # FORKing CD 80 ; int !0x80 # int f = FORK() 5B ; pop_ebx # Restore Tokens 85C0 ; test_eax,eax # Check fork 7C 0D ; jl !Exit_Failure # IF f == -1 abort hard 75 12 ; jne !collect_command_parent # IF f == 0 it is child # Deal with child case 6A 0B ; push !11 58 ; pop_eax # EXECVE 89EA ; mov_edx,ebp # third arg = envp 89D9 ; mov_ecx,ebx # second arg = tokens 8B1B ; mov_ebx,[ebx] # program = tokens[0] CD 80 ; int !0x80 # execve(program, tokens, envp) # return error # Exit_Failure function # Receives nothing # And aborts hard # Does NOT return # :Exit_Failure ; (0x80480D8) 6A 01 ; push !1 5B ; pop_ebx # All is wrong 89D8 ; mov_eax,ebx # put the exit syscall number in eax CD 80 ; int !0x80 # Call it a bad day # :collect_command_parent ; (0x80480DF) 89C3 ; mov_ebx,eax # first arg = f B9 7C820408 ; mov_ecx, &status # second arg = &status 31D2 ; xor_edx,edx # third arg = NULL 6A 07 ; push !7 58 ; pop_eax # WAITPID CD 80 ; int !0x80 # waitpid(f, &status, 0) A1 7C820408 ; mov_eax,[DWORD] &status # Using status 85C0 ; test_eax,eax # IF 0 == status 74 8A ; je !main_loop # Loop forever # Deal with very unhappy case B8 48820408 ; mov_eax, &hard # Using "Subprocess error\nABORTING HARD\n" E8 07010000 ; call %File_Print # Print it EB D6 ; jmp !Exit_Failure # return error # :Done ; (0x8048102) # program completed Successfully 31DB ; xor_ebx,ebx # All is well 6A 01 ; push !1 58 ; pop_eax # put the exit syscall number in eax CD 80 ; int !0x80 # Call it a good day # print_command function # Receives tokens[j] in EBX and tokens[i] in ECX # Modifies EAX # :print_command ; (0x8048109) 53 ; push_ebx # Protect EBX B8 43820408 ; mov_eax, &prefix # using " +> " E8 F3000000 ; call %File_Print # print it # :print_command_loop ; (0x8048114) 8B03 ; mov_eax,[ebx] # using tokens[j] E8 EC000000 ; call %File_Print # print it 83C3 04 ; add_ebx, !4 # j = j + 1 6A 20 ; push !32 58 ; pop_eax # using ' ' E8 FE000000 ; call %fputc # print it 39CB ; cmp_ebx,ecx # IF j < i 75 EA ; jne !print_command_loop # otherwise keep looping 6A 0A ; push !10 58 ; pop_eax # using '\n' E8 F2000000 ; call %fputc # print it 5B ; pop_ebx # Restore EBX C3 ; ret # collect_token function # Receives nothing # Overwrites EAX # Uses EAX as C, EBX as token and ECX as token[i] # :collect_token ; (0x8048134) 53 ; push_ebx # Protect EBX 51 ; push_ecx # Protect ECX A1 74820408 ; mov_eax,[DWORD] &max_string # Using max_string E8 89000000 ; call %malloc # allocate space 89C3 ; mov_ebx,eax # token = malloc(max_string) 89C1 ; mov_ecx,eax # i = 0; set token[i] # :collect_token_loop ; (0x8048144) E8 9F000000 ; call %fgetc # c = fgetc(input) 3C FC ; cmp_al, !-4 # if C == EOF 74 B5 ; je !Done # We are done 3C 20 ; cmp_al, !32 # IF C == ' ' 74 42 ; je !collect_token_done # Space terminates token 3C 09 ; cmp_al, !9 # IF C == '\t' 74 3E ; je !collect_token_done # tab terminates token 3C 0A ; cmp_al, !10 # IF C == '\n' 75 0A ; jne !collect_token_string # otherwise check next # It is a newline 6A 01 ; push !1 58 ; pop_eax # Using 1 A3 6C820408 ; mov_[DWORD],eax &command_done # Set command_done = TRUE EB 30 ; jmp !collect_token_done # Be done # :collect_token_string ; (0x8048163) 3C 22 ; cmp_al, !34 # IF C == '\"' 75 07 ; jne !collect_token_comment # otherwise check next # It is a RAW STRING E8 32000000 ; call %collect_string # Get the rest of the string EB 25 ; jmp !collect_token_done # Be done # :collect_token_comment ; (0x804816E) 3C 23 ; cmp_al, !35 # IF C == '#' 75 0F ; jne !collect_token_escape # otherwise check next # It is a line comment E8 40000000 ; call %collect_comment # Read it all 6A 01 ; push !1 58 ; pop_eax # Using 1 A3 6C820408 ; mov_[DWORD],eax &command_done # Set command_done = TRUE EB 12 ; jmp !collect_token_done # Be done # :collect_token_escape ; (0x8048181) 3C 5C ; cmp_al, !92 # IF C == '\\' 75 07 ; jne !collect_token_other # otherwise just store it # It is an escape char E8 5E000000 ; call %fgetc # Read the char to drop EB 07 ; jmp !collect_token_done # Be done # :collect_token_other ; (0x804818C) 8801 ; mov_[ecx],al # token[i] = C 83C1 01 ; add_ecx, !1 # i = i + 1 EB B1 ; jmp !collect_token_loop # Keep going # :collect_token_done ; (0x8048193) 39CB ; cmp_ebx,ecx # IF i == 0 75 02 ; jne !collect_token_good # otherwise return the token 31DB ; xor_ebx,ebx # token = NULL # :collect_token_good ; (0x8048199) 89D8 ; mov_eax,ebx # Return token 59 ; pop_ecx # Restore ECX 5B ; pop_ebx # Restore EBX C3 ; ret # collect_string function # Receives target[index] in ECX # Modifies EAX # Uses EAX as C # :collect_string ; (0x804819E) E8 45000000 ; call %fgetc # C = fgetc(input) 3C FC ; cmp_al, !-4 # if C == EOF 0F84 2DFFFFFF ; je32 %Exit_Failure # Something went horribly wrong 3C 22 ; cmp_al, !34 # IF C == '\"' 74 07 ; je !collect_string_done # be done # deal with inside of string 8801 ; mov_[ecx],al # target[index] = C 83C1 01 ; add_ecx, !1 # index = index + 1 EB E8 ; jmp !collect_string # Keep going # :collect_string_done ; (0x80481B6) C3 ; ret # collect_comment function # Receives nothing # Modifies EAX # uses EAX as Int C # Just throws away everything it reads # :collect_comment ; (0x80481B7) E8 2C000000 ; call %fgetc # C = fgetc(input) 3C FC ; cmp_al, !-4 # IF C == EOF 0F84 14FFFFFF ; je32 %Exit_Failure # abort hard 3C 0A ; cmp_al, !10 # IF C == '\n' 75 EF ; jne !collect_comment # otherwise keep looping C3 ; ret ;; Malloc isn't actually required if the program being built fits in the initial memory ;; However, it doesn't take much to add it. ;; Requires [MALLOC] to be initialized and EAX to have the number of desired bytes # :malloc ; (0x80481C9) 53 ; push_ebx # Protect EBX 51 ; push_ecx # Protect ECX 52 ; push_edx # Protect EDX 8B1D 78820408 ; mov_ebx,[DWORD] &MALLOC # Using the current pointer 01C3 ; add_ebx,eax # Request the number of desired bytes 6A 2D ; push !45 58 ; pop_eax # the Syscall # for SYS_BRK CD 80 ; int !0x80 # call the Kernel A1 78820408 ; mov_eax,[DWORD] &MALLOC # Return pointer 891D 78820408 ; mov_[DWORD],ebx &MALLOC # Update pointer 5A ; pop_edx # Restore EDX 59 ; pop_ecx # Restore ECX 5B ; pop_ebx # Restore EBX C3 ; ret # fgetc function # Loads FILE* from [script] # Returns -4 (EOF) or char in AL # :fgetc ; (0x80481E8) 53 ; push_ebx # Protect EBX 51 ; push_ecx # Protect ECX 52 ; push_edx # Protect EDX 6A FC ; push !-4 58 ; pop_eax # Put EOF in eax 50 ; push_eax # Assume bad (If nothing read, value will remain EOF) 8D0C24 ; lea_ecx,[esp] # Get stack address 8B1D 68820408 ; mov_ebx,[DWORD] &script # Where are we reading from 6A 03 ; push !3 58 ; pop_eax # the syscall number for read 6A 01 ; push !1 5A ; pop_edx # set the size of chars we want CD 80 ; int !0x80 # call the Kernel 58 ; pop_eax # Get either char or EOF 3C FC ; cmp_al, !-4 # Check for EOF # :fgetc_done ; (0x8048203) 5A ; pop_edx # Restore EDX 59 ; pop_ecx # Restore ECX 5B ; pop_ebx # Restore EBX C3 ; ret # File_Print function # Receives CHAR* in EAX # calls fputc for every non-null char # :File_Print ; (0x8048207) 53 ; push_ebx # Protect EBX 51 ; push_ecx # Protect ECX 89C3 ; mov_ebx,eax # Protect S 85C0 ; test_eax,eax # Protect against nulls 74 12 ; je !File_Print_Done # Simply don't try to print them # :File_Print_Loop ; (0x804820F) 31C0 ; xor_eax,eax # Zero eax 8A03 ; mov_al,[ebx] # Read byte 85C0 ; test_eax,eax # Check for NULL 74 0A ; je !File_Print_Done # Stop at NULL E8 08000000 ; call %fputc # write it 83C3 01 ; add_ebx, !1 # S = S + 1 EB EE ; jmp !File_Print_Loop # Keep going # :File_Print_Done ; (0x8048221) 59 ; pop_ecx # Restore ECX 5B ; pop_ebx # Restore EBX C3 ; ret # fputc function # receives CHAR in EAX and load FILE* from stdout # writes char and returns # :fputc ; (0x8048224) 53 ; push_ebx # Protect EBX 51 ; push_ecx # Protect ECX 52 ; push_edx # Protect EDX 50 ; push_eax # We are writing eax 8D0C24 ; lea_ecx,[esp] # Get stack address 6A 01 ; push !1 5B ; pop_ebx # Write to target file 6A 04 ; push !4 58 ; pop_eax # the syscall number for write 89DA ; mov_edx,ebx # set the size of chars we want CD 80 ; int !0x80 # call the Kernel 58 ; pop_eax # Restore stack 5A ; pop_edx # Restore EDX 59 ; pop_ecx # Restore ECX 5B ; pop_ebx # Restore EBX C3 ; ret # :default_file ; (0x804823A) 6B61656D2E78383600 # "kaem.x86" # :prefix ; (0x8048243) 202B3E2000 # " +> " # :hard ; (0x8048248) 53756270726F63657373206572726F720A41424F5254494E4720484152440A00 # "Subprocess error\nABORTING HARD\n" # :script ; (0x8048268) 00000000 # :command_done ; (0x804826C) 00000000 # :max_args ; (0x8048270) 00040000 # :max_string ; (0x8048274) 00100000 # :MALLOC ; (0x8048278) 00000000 # :status ; (0x804827C) 00000000 # :ELF_end ; (0x8048280)
### Copyright (C) 2016 Jeremiah Orians ### This file is part of stage0. ### ### stage0 is free software: you can redistribute it and/or modify ### it under the terms of the GNU General Public License as published by ### the Free Software Foundation, either version 3 of the License, or ### (at your option) any later version. ### ### stage0 is distributed in the hope that it will be useful, ### but WITHOUT ANY WARRANTY; without even the implied warranty of ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ### GNU General Public License for more details. ### ### You should have received a copy of the GNU General Public License ### along with stage0. If not, see <http://www.gnu.org/licenses/>. ## ELF Header #:ELF_base 7F 45 4C 46 # e_ident[EI_MAG0-3] ELF's magic number 01 # e_ident[EI_CLASS] Indicating 32 bit 01 # e_ident[EI_DATA] Indicating little endianness 01 # e_ident[EI_VERSION] Indicating original elf 03 # e_ident[EI_OSABI] Set at 3 because FreeBSD is strict 00 # e_ident[EI_ABIVERSION] Set at 0 because none cares 00 00 00 00 00 00 00 # e_ident[EI_PAD] 02 00 # e_type Indicating Executable 03 00 # e_machine Indicating x86 01 00 00 00 # e_version Indicating original elf 54 80 04 08 # e_entry Address of the entry point 34 00 00 00 # e_phoff Address of program header table 00 00 00 00 # e_shoff Address of section header table 00 00 00 00 # e_flags 34 00 # e_ehsize Indicating our 52 Byte header 20 00 # e_phentsize size of a program header table 01 00 # e_phnum number of entries in program table 00 00 # e_shentsize size of a section header table 00 00 # e_shnum number of entries in section table 00 00 # e_shstrndx index of the section names ## Program Header #:ELF_program_headers #:ELF_program_header__text 01 00 00 00 # ph_type: PT-LOAD = 1 00 00 00 00 # ph_offset 00 80 04 08 # ph_vaddr 00 80 04 08 # ph_physaddr B1 02 00 00 # ph_filesz B1 02 00 00 # ph_memsz 07 00 00 00 # ph_flags: PF-X|PF-W|PF-R = 7 01 00 00 00 # ph_align #:ELF_text #:_start ; (0x8048054) 58 # POP_EAX ; Get the number of arguments 5B # POP_EBX ; Get the program name 5B # POP_EBX ; Get the actual input name B9 00000000 # LOADI32_ECX %0 ; prepare read_only BA 00000000 # LOADI32_EDX %0 ; extra sure B8 05000000 # LOADI32_EAX %5 ; the syscall number for open() CD80 # INT_80 ; Now open that damn file A3 A9820408 # STORE32_Absolute32_eax &fin ; Preserve the file pointer we were given 5B # POP_EBX ; Get the actual output name B9 41020000 # LOADI32_ECX %577 ; Prepare file as O_WRONLY|O_CREAT|O_TRUNC BA C0010000 # LOADI32_EDX %448 ; Prepare file as RWX for owner only (700 in octal) B8 05000000 # LOADI32_EAX %5 ; the syscall number for open() CD80 # INT_80 ; Now open that damn file A3 AD820408 # STORE32_Absolute32_eax &fout ; Preserve the file pointer we were given BD FFFFFFFF # LOADI32_EBP %-1 ; Our flag for byte processing BE 00000000 # LOADI32_ESI %0 ; temp storage for the sum BF 00000000 # LOADI32_EDI %0 ; Our starting IP E8 30000000 # CALLI32 %First_pass ; Process it ; rewind input file 8B1D A9820408 # LOAD32_Absolute32_ebx &fin ; Using our input file B9 00000000 # LOADI32_ECX %0 ; Offset Zero BA 00000000 # LOADI32_EDX %0 ; Whence Zero B8 13000000 # LOADI32_EAX %19 ; lseek CD80 # INT_80 BD FFFFFFFF # LOADI32_EBP %-1 ; Our flag for byte processing BE 00000000 # LOADI32_ESI %0 ; temp storage for the sum BF 00000000 # LOADI32_EDI %0 ; Our starting IP E8 B8000000 # CALLI32 %Second_pass ; Process it E9 62010000 # JMP32 %Done #:First_pass ; (0x80480C8) E8 69010000 # CALLI32 %Read_byte ; Deal with EOF 83F8 FC # CMPI8_EAX !-4 0F84 4E000000 # JE32 %First_pass_done ; Check for : 83F8 3A # CMPI8_EAX !58 0F85 05000000 # JNE32 %First_pass_0 ; Deal with label E8 A1010000 # CALLI32 %StoreLabel #:First_pass_0 ; (0x80480E4) ; Check for % 83F8 25 # CMPI8_EAX !37 0F84 2A000000 # JE32 %First_pass_pointer ; Deal with everything else E8 33000000 # CALLI32 %hex ; Process our char ; Deal with EOF 83F8 FC # CMPI8_EAX !-4 0F84 29000000 # JE32 %First_pass_done ; deal with -1 values 83F8 00 # CMPI8_EAX !0 0F8C C4FFFFFF # JL32 %First_pass ; deal with toggle 83FD 00 # CMPI8_EBP !0 0F84 03000000 # JE32 %First_pass_1 83C7 01 # ADDI8_EDI !1 ; Increment IP #:First_pass_1 ; (0x8048110) F7D5 # NOT_EBP E9 B1FFFFFF # JMP32 %First_pass #:First_pass_pointer ; (0x8048117) ; Deal with Pointer to label E8 1A010000 # CALLI32 %Read_byte ; Drop the char 83C7 04 # ADDI8_EDI !4 ; Increment IP E9 A4FFFFFF # JMP32 %First_pass ; Loop again #:First_pass_done ; (0x8048124) C3 # RET #:hex ; (0x8048125) ; deal with EOF 83F8 FC # CMPI8_EAX !-4 0F84 AE000000 # JE32 %EOF ; deal with line comments starting with # 83F8 23 # CMPI8_EAX !35 0F84 B8000000 # JE32 %ascii_comment ; deal with line comments starting with ; 83F8 3B # CMPI8_EAX !59 0F84 AF000000 # JE32 %ascii_comment ; deal all ascii less than 0 83F8 30 # CMPI8_EAX !48 0F8C A0000000 # JL32 %ascii_other ; deal with 0-9 83F8 3A # CMPI8_EAX !58 0F8C 8B000000 # JL32 %ascii_num ; deal with all ascii less than A 83F8 41 # CMPI8_EAX !65 0F8C 8E000000 # JL32 %ascii_other ; deal with A-F 83F8 47 # CMPI8_EAX !71 0F8C 81000000 # JL32 %ascii_high ;deal with all ascii less than a 83F8 61 # CMPI8_EAX !97 0F8C 7C000000 # JL32 %ascii_other ;deal with a-f 83F8 67 # CMPI8_EAX !103 0F8C 6B000000 # JL32 %ascii_low ; The rest that remains needs to be ignored E9 6E000000 # JMP32 %ascii_other #:Second_pass ; (0x804817B) E8 B6000000 # CALLI32 %Read_byte ; Deal with EOF 83F8 FC # CMPI8_EAX !-4 0F84 52000000 # JE32 %Second_pass_done ; Simply drop the label 83F8 3A # CMPI8_EAX !58 0F85 0A000000 # JNE32 %Second_pass_0 E8 9F000000 # CALLI32 %Read_byte E9 DFFFFFFF # JMP32 %Second_pass #:Second_pass_0 ; (0x804819C) ; Deal with pointer 83F8 25 # CMPI8_EAX !37 0F85 0A000000 # JNE32 %Second_pass_1 E8 E3000000 # CALLI32 %StorePointer E9 CCFFFFFF # JMP32 %Second_pass #:Second_pass_1 ; (0x80481AF) ; Deal with everything else E8 71FFFFFF # CALLI32 %hex ; Process our char ; Deal with EOF 83F8 FC # CMPI8_EAX !-4 0F84 1E000000 # JE32 %Second_pass_done ; deal with -1 values 83F8 00 # CMPI8_EAX !0 0F8C B5FFFFFF # JL32 %Second_pass ; deal with toggle 83FD 00 # CMPI8_EBP !0 0F84 3D000000 # JE32 %print ; process first byte of pair 89C6 # COPY_EAX_to_ESI BD 00000000 # LOADI32_EBP %0 E9 A0FFFFFF # JMP32 %Second_pass #:Second_pass_done ; (0x80481DB) C3 # RET #:EOF ; (0x80481DC) C3 # RET #:ascii_num ; (0x80481DD) 83E8 30 # SUBI8_EAX !48 C3 # RET #:ascii_low ; (0x80481E1) 83E8 57 # SUBI8_EAX !87 C3 # RET #:ascii_high ; (0x80481E5) 83E8 37 # SUBI8_EAX !55 C3 # RET #:ascii_other ; (0x80481E9) B8 FFFFFFFF # LOADI32_EAX %-1 C3 # RET #:ascii_comment ; (0x80481EF) E8 42000000 # CALLI32 %Read_byte 83F8 0D # CMPI8_EAX !13 0F84 09000000 # JE32 %ascii_comment_cr 83F8 0A # CMPI8_EAX !10 0F85 E9FFFFFF # JNE32 %ascii_comment #:ascii_comment_cr ; (0x8048206) B8 FFFFFFFF # LOADI32_EAX %-1 C3 # RET ; process second byte of pair #:print ; (0x804820C) ; update the sum and store in output C1E6 04 # SHLI8_ESI !4 01F0 # ADD_ESI_to_EAX A2 B1820408 # STORE8_Absolute32_al &table ; flip the toggle F7D5 # NOT_EBP ; Print our first Hex BA 01000000 # LOADI32_EDX %1 ; set the size of chars we want E8 42000000 # CALLI32 %print_chars 83C7 01 # ADDI8_EDI !1 ; Increment IP E9 51FFFFFF # JMP32 %Second_pass #:Done ; (0x804822A) ; program completed Successfully BB 00000000 # LOADI32_EBX %0 ; All is well B8 01000000 # LOADI32_EAX %1 ; put the exit syscall number in eax CD80 # INT_80 ; Call it a good day #:Read_byte ; (0x8048236) ; Attempt to read 1 byte from STDIN BA 01000000 # LOADI32_EDX %1 ; set the size of chars we want B9 B1820408 # LOADI32_ECX &table ; Where to put it 8B1D A9820408 # LOAD32_Absolute32_ebx &fin ; Where are we reading from B8 03000000 # LOADI32_EAX %3 ; the syscall number for read CD80 # INT_80 ; call the Kernel 85C0 # TEST ; check what we got 0F84 09000000 # JE32 %Read_byte_1 ; Got EOF call it done ; load byte A0 B1820408 # LOAD8_Absolute32_al &table ; load char 0FB6C0 # MOVZX ; We have to zero extend it to use it C3 # RET ; Deal with EOF #:Read_byte_1 ; (0x804825E) B8 FCFFFFFF # LOADI32_EAX %-4 ; Put EOF in eax C3 # RET #:print_chars ; (0x8048264) B9 B1820408 # LOADI32_ECX &table ; What we are writing 8B1D AD820408 # LOAD32_Absolute32_ebx &fout ; Write to target file B8 04000000 # LOADI32_EAX %4 ; the syscall number for write CD80 # INT_80 ; call the Kernel C3 # RET #:Get_table_target ; (0x8048277) E8 BAFFFFFF # CALLI32 %Read_byte ; Get single char label C1E0 02 # SHLI8_EAX !2 ; Each label in table takes 4 bytes to store 05 B1820408 # ADDI32_EAX &table ; Calculate offset C3 # RET #:StoreLabel ; (0x8048285) E8 EDFFFFFF # CALLI32 %Get_table_target 8938 # STORE32_EDI_into_Address_EAX ; Write out pointer to table C3 # RET #:StorePointer ; (0x804828D) 83C7 04 # ADDI8_EDI !4 ; Increment IP E8 E2FFFFFF # CALLI32 %Get_table_target ; Get address of pointer 8B00 # LOAD32_Address_EAX_into_EAX ; Get pointer 29F8 # SUB_EDI_from_EAX ; target - ip A3 B1820408 # STORE32_Absolute32_eax &table ; put value in output BA 04000000 # LOADI32_EDX %4 ; set the size of chars we want E8 BCFFFFFF # CALLI32 %print_chars C3 # RET #:fin ; (0x80482A9) 00000000 # NULL #:fout ; (0x80482AD) 00000000 # NULL #:table ; (0x80482B1) #:ELF_end
;; Copyright (C) 2017 Jeremiah Orians ;; This file is part of stage0. ;; ;; stage0 is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; ;; stage0 is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with stage0. If not, see <http://www.gnu.org/licenses/>. ## ELF Header 7F 45 4C 46 # e_ident[EI_MAG0-3] ELF's magic number 01 # e_ident[EI_CLASS] Indicating 32 bit 01 # e_ident[EI_DATA] Indicating little endianness 01 # e_ident[EI_VERSION] Indicating original elf 03 # e_ident[EI_OSABI] Set at 3 because FreeBSD is strict 00 # e_ident[EI_ABIVERSION] Set at 0 because none cares 00 00 00 00 00 00 00 # e_ident[EI_PAD] 02 00 # e_type Indicating Executable 03 00 # e_machine Indicating x86 01 00 00 00 # e_version Indicating original elf 54 80 04 08 # e_entry Address of the entry point 34 00 00 00 # e_phoff Address of program header table 00 00 00 00 # e_shoff Address of section header table 00 00 00 00 # e_flags 34 00 # e_ehsize Indicating our 52 Byte header 20 00 # e_phentsize size of a program header table 01 00 # e_phnum number of entries in program table 00 00 # e_shentsize size of a section header table 00 00 # e_shnum number of entries in section table 00 00 # e_shstrndx index of the section names ## Program Header 01 00 00 00 # ph_type: PT-LOAD = 1 00 00 00 00 # ph_offset 00 80 04 08 # ph_vaddr 00 80 04 08 # ph_physaddr 7E 05 00 00 # ph_filesz 7E 05 00 00 # ph_memsz 07 00 00 00 # ph_flags: PF-X|PF-W|PF-R = 7 01 00 00 00 # ph_align ;; Register usage: ;; EAX, EDX, ECX, EBX => Temps ;; EDI => IP ;; EBP => MALLOC ;; ESI => HEAD ;; Struct format: (size 24) ;; NEXT => 0 ;; TARGET => 8 ;; NAME => 16 ; Where the ELF Header is going to hit ; Simply jump to _start ; Our main function #:_start BB 00000000 ; LOADI32_EBX %0 ; Get current pointer E8 %x ; CALL32 %malloc ; Get current HEAP 89C3 ; COPY_EAX_to_EBX ; Using current 89C5 ; COPY_EAX_to_EBP ; Setup MALLOC 81C3 0000C000 ; ADDI32_EBX %12582912 ; Create space for temp [12M] E8 %x ; CALL32 %malloc ; Give ourselves 8192000 bytes to work with 58 ; POP_EAX ; Get the number of arguments 5B ; POP_EBX ; Get the program name 5B ; POP_EBX ; Get the actual input name B9 00000000 ; LOADI32_ECX %0 ; prepare read_only BA 00000000 ; LOADI32_EDX %0 ; Really sure B8 05000000 ; LOADI32_EAX %5 ; the syscall number for open() CD80 ; INT_80 ; Now open that damn file A3 6E850408 ; STORE32_Absolute32_eax &Input ; Preserve the file pointer we were given 5B ; POP_EBX ; Get the actual output name B9 41020000 ; LOADI32_ECX %577 ; Prepare file as O_WRONLY|O_CREAT|O_TRUNC BA C0010000 ; LOADI32_EDX %448 ; Prepare file as RWX for owner only (700 in octal) B8 05000000 ; LOADI32_EAX %5 ; the syscall number for open() CD80 ; INT_80 ; Now open that damn file 83F8 00 ; CMPI8_EAX !0 ; Check for missing output 0F8F %a ; JG32 %_start_out ; Have real input B8 01000000 ; LOADI32_EAX %1 ; Use stdout :a #:_start_out A3 72850408 ; STORE32_Absolute32_eax &Output ; Preserve the file pointer we were given E8 %I ; CALL32 %ClearScratch ; Zero scratch B8 FFFFFFFF ; LOADI32_EAX %-1 ; Our flag for byte processing A3 6A850408 ; STORE32_Absolute32_eax &Flag ; Set B8 00000000 ; LOADI32_EAX %0 ; temp storage for the sum A3 66850408 ; STORE32_Absolute32_eax &High ; Set BF 00800408 ; LOADI32_EDI %0x8048000 ; Our starting IP BE 00000000 ; LOADI32_ESI %0 ; HEAD = NULL E8 %b ; CALL32 %First_pass ; Process it ; rewind input file 8B1D 6E850408 ; LOAD32_Absolute32_ebx &Input ; Using our input file B9 00000000 ; LOADI32_ECX %0 ; Offset Zero BA 00000000 ; LOADI32_EDX %0 ; Whence Zero B8 13000000 ; LOADI32_EAX %19 ; lseek 56 ; PUSH_ESI ; Protect HEAD CD80 ; INT_80 5E ; POP_ESI ; Restore HEAD B8 FFFFFFFF ; LOADI32_EAX %-1 ; Our flag for byte processing A3 6A850408 ; STORE32_Absolute32_eax &Flag ; Set B8 00000000 ; LOADI32_EAX %0 ; temp storage for the sum A3 66850408 ; STORE32_Absolute32_eax &High ; Set BF 00800408 ; LOADI32_EDI %0x8048000 ; Our starting IP E8 %l ; CALL32 %Second_pass ; Process it E9 %w ; JMP32 %Done :b #:First_pass E8 %y ; CALL32 %Read_byte ; Deal with EOF 83F8 FC ; CMPI8_EAX !-4 0F84 %j ; JE32 %First_pass_done ; Check for : 83F8 3A ; CMPI8_EAX !0x3A 0F85 %c ; JNE32 %First_pass_0 ; Deal with label E9 %D ; JMP32 %StoreLabel :c #:First_pass_0 ; Check for ! 83F8 21 ; CMPI8_EAX !0x21 0F84 %i ; JE32 %First_pass_pointer ; Check for @ 83F8 40 ; CMPI8_EAX !0x40 0F84 %i ; JE32 %First_pass_pointer ; Check for $ 83F8 24 ; CMPI8_EAX !0x24 0F84 %i ; JE32 %First_pass_pointer ; Check for % 83F8 25 ; CMPI8_EAX !0x25 0F84 %i ; JE32 %First_pass_pointer ; Check for & 83F8 26 ; CMPI8_EAX !0x26 0F84 %i ; JE32 %First_pass_pointer ; Deal with everything else E8 %k ; CALL32 %hex ; Process our char ; Deal with EOF 83F8 FC ; CMPI8_EAX !-4 0F84 %j ; JE32 %First_pass_done ; deal with -1 values 83F8 00 ; CMPI8_EAX !0 0F8C %b ; JL32 %First_pass ; deal with toggle A1 6A850408 ; LOAD32_Absolute32_eax &Flag 83F8 00 ; CMPI8_EAX !0 0F84 %d ; JE32 %First_pass_1 83C7 01 ; ADDI8_EDI !1 ; Increment IP :d #:First_pass_1 F7D0 ; NOT_EAX A3 6A850408 ; STORE32_Absolute32_eax &Flag E9 %b ; JMP32 %First_pass :e #:Update_Pointer ; Check for ! 83F8 21 ; CMPI8_EAX !0x21 0F84 %h ; JE32 %Update_Pointer_1 ; Check for @ 83F8 40 ; CMPI8_EAX !0x40 0F84 %g ; JE32 %Update_Pointer_2 ; Check for $ 83F8 24 ; CMPI8_EAX !0x24 0F84 %g ; JE32 %Update_Pointer_2 ; Check for % 83F8 25 ; CMPI8_EAX !0x25 0F84 %f ; JE32 %Update_Pointer_4 ; Check for & 83F8 26 ; CMPI8_EAX !0x26 0F84 %f ; JE32 %Update_Pointer_4 ;; deal with bad input E8 %R ; CALL32 %fail :f #:Update_Pointer_4 83C7 02 ; ADDI8_EDI !2 ; Increment IP :g #:Update_Pointer_2 83C7 01 ; ADDI8_EDI !1 ; Increment IP :h #:Update_Pointer_1 83C7 01 ; ADDI8_EDI !1 ; Increment IP C3 ; RET :i #:First_pass_pointer ; Deal with Pointer to label E8 %e ; CALL32 %Update_Pointer ; Increment IP BB 7A850408 ; LOADI32_EBX &table ; Using scratch E8 %B ; CALL32 %consume_token ; Read token E8 %I ; CALL32 %ClearScratch ; Throw away token 83F8 3E ; CMPI8_EAX !0x3E ; check for '>' 0F85 %b ; JNE32 %First_pass ; Loop again ;; Deal with %label>label case BB 7A850408 ; LOADI32_EBX &table ; Write to scratch E8 %B ; CALL32 %consume_token ; get token E8 %I ; CALL32 %ClearScratch ; Clean up after ourselves E9 %b ; JMP32 %First_pass ; Loop again :j # :First_pass_done C3 ; RET :k #:hex ; deal with EOF 83F8 FC ; CMPI8_EAX !-4 0F84 %o ; JE32 %EOF ; deal with line comments starting with # 83F8 23 ; CMPI8_EAX !0x23 0F84 %t ; JE32 %ascii_comment ; deal with line comments starting with ; 83F8 3B ; CMPI8_EAX !0x3B 0F84 %t ; JE32 %ascii_comment ; deal all ascii less than 0 83F8 30 ; CMPI8_EAX !0x30 0F8C %s ; JL32 %ascii_other ; deal with 0-9 83F8 3A ; CMPI8_EAX !0x3A 0F8C %p ; JL32 %ascii_num ; deal with all ascii less than A 83F8 41 ; CMPI8_EAX !0x41 0F8C %s ; JL32 %ascii_other ; deal with A-F 83F8 47 ; CMPI8_EAX !0x47 0F8C %r ; JL32 %ascii_high ;deal with all ascii less than a 83F8 61 ; CMPI8_EAX !0x61 0F8C %s ; JL32 %ascii_other ;deal with a-f 83F8 67 ; CMPI8_EAX !0x67 0F8C %q ; JL32 %ascii_low ; The rest that remains needs to be ignored E9 %s ; JMP32 %ascii_other :l #:Second_pass E8 %y ; CALL32 %Read_byte ; Deal with EOF 83F8 FC ; CMPI8_EAX !-4 0F84 %n ; JE32 %Second_pass_done ; Simply drop the label 83F8 3A ; CMPI8_EAX !0x3A 0F85 %m ; JNE32 %Second_pass_0 BB 7A850408 ; LOADI32_EBX &table ; Using scratch E8 %B ; CALL32 %consume_token ; Read token E8 %I ; CALL32 %ClearScratch ; Throw away token E9 %l ; JMP32 %Second_pass :m #:Second_pass_0 ; Deal with % pointer 83F8 25 ; CMPI8_EAX !0x25 0F84 %M ; JE32 %StorePointer_rel4 ; Deal with @ pointer 83F8 40 ; CMPI8_EAX !0x40 0F84 %N ; JE32 %StorePointer_rel2 ; Deal with ! pointer 83F8 21 ; CMPI8_EAX !0x21 0F84 %O ; JE32 %StorePointer_rel1 ; Deal with & pointer 83F8 26 ; CMPI8_EAX !0x26 0F84 %P ; JE32 %StorePointer_abs4 ; Deal with $ pointer 83F8 24 ; CMPI8_EAX !0x24 0F84 %Q ; JE32 %StorePointer_abs2 #:Second_pass_1 ; Deal with everything else E8 %k ; CALL32 %hex ; Process our char ; Deal with EOF 83F8 FC ; CMPI8_EAX !-4 0F84 %n ; JE32 %Second_pass_done ; deal with -1 values 83F8 00 ; CMPI8_EAX !0 0F8C %l ; JL32 %Second_pass ; deal with toggle 8B1D 6A850408 ; LOAD32_Absolute32_ebx &Flag 83FB 00 ; CMPI8_EBX !0 0F84 %v ; JE32 %print ; process first byte of pair C1E0 04 ; SHLI8_EAX !4 A3 66850408 ; STORE32_Absolute32_eax &High B8 00000000 ; LOADI32_EAX %0 A3 6A850408 ; STORE32_Absolute32_eax &Flag E9 %l ; JMP32 %Second_pass :n #:Second_pass_done C3 ; RET :o #:EOF C3 ; RET :p #:ascii_num 83E8 30 ; SUBI8_EAX !0x30 C3 ; RET :q #:ascii_low 83E8 57 ; SUBI8_EAX !0x57 C3 ; RET :r #:ascii_high 83E8 37 ; SUBI8_EAX !0x37 C3 ; RET :s #:ascii_other B8 FFFFFFFF ; LOADI32_EAX %-1 C3 ; RET :t #:ascii_comment E8 %y ; CALL32 %Read_byte 83F8 0D ; CMPI8_EAX !0x0D 0F84 %u ; JE32 %ascii_comment_cr 83F8 0A ; CMPI8_EAX !0x0A 0F85 %t ; JNE32 %ascii_comment :u #:ascii_comment_cr B8 FFFFFFFF ; LOADI32_EAX %-1 C3 ; RET ; process second byte of pair :v #:print ; update the sum and store in output 0305 66850408 ; ADD32_Absolute32_eax &High ; Print our first Hex BA 01000000 ; LOADI32_EDX %1 ; set the size of chars we want E8 %A ; CALL32 %print_chars ; flip the toggle A1 6A850408 ; LOAD32_Absolute32_eax &Flag F7D0 ; NOT_EAX A3 6A850408 ; STORE32_Absolute32_eax &Flag 83C7 01 ; ADDI8_EDI !1 ; Increment IP E9 %l ; JMP32 %Second_pass :w #:Done ; program completed Successfully BB 00000000 ; LOADI32_EBX %0 ; All is well B8 01000000 ; LOADI32_EAX %1 ; put the exit syscall number in eax CD80 ; INT_80 ; Call it a good day ;; Malloc isn't actually required if the program being built fits in the initial memory ;; However, it doesn't take much to add it. ;; Requires a value in EBX :x #:malloc B8 2D000000 ; LOADI32_EAX %45 ; the Syscall # for SYS_BRK 56 ; PUSH_ESI ; Protect esi 57 ; PUSH_EDI ; Protect edi CD80 ; INT_80 ; call the Kernel 5F ; POP_EDI ; Restore edi 5E ; POP_ESI ; Restore esi C3 ; RET :y #:Read_byte 52 ; PUSH_EDX ; Protect EDX 51 ; PUSH_ECX ; Protect ECX 53 ; PUSH_EBX ; Protect EBX B8 FCFFFFFF ; LOADI32_EAX %-4 ; Put EOF in eax 50 ; PUSH_EAX ; Assume bad (If nothing read, value will remain EOF) 8D0C24 ; LEA32_ECX_from_esp ; Get stack address 8B1D 6E850408 ; LOAD32_Absolute32_ebx &Input ; Where are we reading from B8 03000000 ; LOADI32_EAX %3 ; the syscall number for read BA 01000000 ; LOADI32_EDX %1 ; set the size of chars we want CD80 ; INT_80 ; call the Kernel 85C0 ; TEST ; check what we got 58 ; POP_EAX ; Get either char or EOF 0F84 %z ; JE32 %Read_byte_1 ; Got EOF call it done 0FB6C0 ; MOVZX_al ; We have to zero extend it to use it 5B ; POP_EBX ; Restore EBX 59 ; POP_ECX ; Restore ECX 5A ; POP_EDX ; Restore EDX C3 ; RET ; Deal with EOF :z #:Read_byte_1 5B 59 5A C3 ; RET :A #:print_chars 52 ; PUSH_EDX ; Protect EDX 51 ; PUSH_ECX ; protect ECX 53 ; PUSH_EBX ; protect EBX 50 ; PUSH_EAX ; We are writing eax 8D0C24 ; LEA32_ECX_from_esp ; Get stack address 8B1D 72850408 ; LOAD32_Absolute32_ebx &Output ; Write to target file B8 04000000 ; LOADI32_EAX %4 ; the syscall number for write ;; EDX contains the number of bytes to write CD80 ; INT_80 ; call the Kernel 58 ; POP_EAX ; Restore stack 5B ; POP_EBX ; Restore EBX 59 ; POP_ECX ; Restore ECX 5A ; POP_EDX ; Restore EDX C3 ; RET ;; Receives pointer in EBX ;; Writes out char and updates EBX :B #:consume_token E8 %y ; CALL32 %Read_byte ; Consume_token ; Check for \t 83F8 09 ; CMPI8_EAX !0x09 0F84 %C ; JE32 %consume_token_done ; Check for \n 83F8 0A ; CMPI8_EAX !0x0A 0F84 %C ; JE32 %consume_token_done ; Check for ' ' 83F8 20 ; CMPI8_EAX !0x20 0F84 %C ; JE32 %consume_token_done ; Check for '>' 83F8 3E ; CMPI8_EAX !0x3E 0F84 %C ; JE32 %consume_token_done ;; Looks like we are still reading token 8803 ; STORE8_al_into_Address_EBX ; Store char 83C3 01 ; ADDI8_EBX !1 ; Point to next spot E9 %B ; JMP32 %consume_token ; loop until done :C #:consume_token_done B9 00000000 ; LOADI32_ECX %0 ; Pad with nulls 890B ; STORE32_ECX_into_Address_EBX 83C3 04 ; ADDI8_EBX !4 C3 ; RET :D #:StoreLabel 89E8 ; COPY_EBP_to_EAX ; ENTRY 83C5 18 ; ADDI8_EBP !24 ; CALLOC 8978 08 ; STORE32_EDI_into_Address_EAX_Immediate8 !8 ; ENTRY->TARGET = IP 8930 ; STORE32_ESI_into_Address_EAX ; ENTRY->NEXT = JUMP_TABLE 89C6 ; COPY_EAX_to_ESI ; JUMP_TABLE = ENTRY 896E 10 ; STORE32_EBP_into_Address_ESI_Immediate8 !16 ; ENTRY->NAME = TOKEN 89EB ; COPY_EBP_to_EBX ; Write Starting after struct E8 %B ; CALL32 %consume_token ; Collect whole string 89DD ; COPY_EBX_to_EBP ; Update HEAP E9 %b ; JMP32 %First_pass :E #:GetTarget 53 ; PUSH_EBX ; protect ebx 51 ; PUSH_ECX ; protect ecx 52 ; PUSH_EDX ; protect edx 56 ; PUSH_ESI ; protect JUMP_TABLE B9 7A850408 ; LOADI32_ECX &table ; Reset scratch 8B56 10 ; LOAD32_EDX_from_ESI_Immediate8 !16 ; I->NAME :F #:GetTarget_loop 8A01 ; LOAD8_al_from_ECX ; I->NAME[0] 8A1A ; LOAD8_bl_from_EDX ; scratch[0] 0FB6DB ; MOVZX_bl ; Zero extend 0FB6C0 ; MOVZX_al ; Zero extend 38D8 ; CMP_al_bl ; IF TOKEN == I->NAME 0F85 %G ; JNE32 %GetTarget_miss ; Oops 83C1 01 ; ADDI8_ECX !1 83C2 01 ; ADDI8_EDX !1 3C 00 ; CMPI8_al !0 0F85 %F ; JNE32 %GetTarget_loop ; Loop until E9 %H ; JMP32 %GetTarget_done ; Match ;; Miss :G #:GetTarget_miss 8B36 ; LOAD32_ESI_from_ESI ; I = I->NEXT 83FE 00 ; CMPI8_ESI !0 ; IF NULL == I 0F84 %R ; JE32 %fail ; Abort hard 8B56 10 ; LOAD32_EDX_from_ESI_Immediate8 !16 ; I->NAME B9 7A850408 ; LOADI32_ECX &table ; Reset scratch E9 %F ; JMP32 %GetTarget_loop :H #:GetTarget_done 8B46 08 ; LOAD32_EAX_from_ESI_Immediate8 !8 ; Get address 5E ; POP_ESI ; Restore JUMP_TABLE 5A ; POP_EDX ; Restore EDX 59 ; POP_ECX ; Restore ECX 5B ; POP_EBX ; Restore EBX C3 ; RET :I #:ClearScratch 50 ; PUSH_EAX ; Protect against changes 53 ; PUSH_EBX ; And overwrites 51 ; PUSH_ECX ; While we work BB 7A850408 ; LOADI32_EBX &table ; Where our table is B8 00000000 ; LOADI32_EAX %0 ; Using null :J #:ClearScratch_loop 8B0B ; LOAD32_ECX_from_EBX ; Get current value 8803 ; STORE8_al_into_Address_EBX ; Because we want null 83C3 01 ; ADDI8_EBX !1 ; Increment 83F9 00 ; CMPI8_ECX !0 ; Check if we hit null 0F85 %J ; JNE32 %ClearScratch_loop ; Keep looping 59 ; POP_ECX ; Restore 5B ; POP_EBX ; Damage 58 ; POP_EAX ; Entirely C3 ; RET :K #:StorePointer E8 %e ; CALL32 %Update_Pointer ; Increment IP BB 7A850408 ; LOADI32_EBX &table ; Write to scratch E8 %B ; CALL32 %consume_token ; get token 50 ; PUSH_EAX ; Protect base_sep_p B8 7A850408 ; LOADI32_EAX &table ; Pointer to scratch E8 %E ; CALL32 %GetTarget ; Get address of pointer E8 %I ; CALL32 %ClearScratch ; Clean up after ourselves 89FA ; COPY_EDI_to_EDX ; base = IP 5B ; POP_EBX ; Restore base_sep_p 83FB 3E ; CMPI8_EBX !0x3E ; If base_sep_p == '>' 0F85 %L ; JNE32 %StorePointer_done ; If not ;; Deal with %label>label case 50 ; PUSH_EAX ; We need to preserve main target BB 7A850408 ; LOADI32_EBX &table ; Write to scratch E8 %B ; CALL32 %consume_token ; get token B8 7A850408 ; LOADI32_EAX &table ; Pointer to scratch E8 %E ; CALL32 %GetTarget ; Get address of pointer E8 %I ; CALL32 %ClearScratch ; Clean up after ourselves 89C2 ; COPY_EAX_to_EDX ; Use our new base 58 ; POP_EAX ; Restore main target :L #:StorePointer_done C3 ; RET :M #:StorePointer_rel4 E8 %K ; CALL32 %StorePointer ; Do Common 29D0 ; SUB_EDX_from_EAX ; target - ip BA 04000000 ; LOADI32_EDX %4 ; set the size of chars we want E8 %A ; CALL32 %print_chars E8 %I ; CALL32 %ClearScratch ; Clean up after ourselves E9 %l ; JMP32 %Second_pass :N #:StorePointer_rel2 E8 %K ; CALL32 %StorePointer ; Do Common 29D0 ; SUB_EDX_from_EAX ; target - ip BA 02000000 ; LOADI32_EDX %2 ; set the size of chars we want E8 %A ; CALL32 %print_chars E8 %I ; CALL32 %ClearScratch ; Clean up after ourselves E9 %l ; JMP32 %Second_pass :O #:StorePointer_rel1 E8 %K ; CALL32 %StorePointer ; Do Common 29D0 ; SUB_EDX_from_EAX ; target - ip BA 01000000 ; LOADI32_EDX %1 ; set the size of chars we want E8 %A ; CALL32 %print_chars E8 %I ; CALL32 %ClearScratch ; Clean up after ourselves E9 %l ; JMP32 %Second_pass :P #:StorePointer_abs4 E8 %K ; CALL32 %StorePointer ; Do Common BA 04000000 ; LOADI32_EDX %4 ; set the size of chars we want E8 %A ; CALL32 %print_chars E8 %I ; CALL32 %ClearScratch ; Clean up after ourselves E9 %l ; JMP32 %Second_pass :Q #:StorePointer_abs2 E8 %K ; CALL32 %StorePointer ; Do Common BA 02000000 ; LOADI32_EDX %2 ; set the size of chars we want E8 %A ; CALL32 %print_chars E8 %I ; CALL32 %ClearScratch ; Clean up after ourselves E9 %l ; JMP32 %Second_pass :R #:fail ; Some shit went wrong BB 01000000 ; LOADI32_EBX %1 ; All is wrong B8 01000000 ; LOADI32_EAX %1 ; put the exit syscall number in eax CD80 ; INT_80 ; Call it a good day #:High (0x8048566) 00000000 ; NULL #:Flag (0x804856A) 00000000 ; NULL #:Input (0x804856E) 00000000 ; NULL #:Output (0x8048572) 00000000 ; NULL #:write (0x8048576) 00000000 ; NULL #:table (0x804857A) 00000000 ; NULL #:ELF_end
;; Copyright (C) 2019 Jeremiah Orians ;; This file is part of mescc-tools. ;; ;; mescc-tools is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; ;; mescc-tools is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with mescc-tools. If not, see <http://www.gnu.org/licenses/>. ;; Register usage: ;; EBP => OUTPUT ;; EDI => Buffer ;; ESI => INPUT ## ELF Header :ELF_base 7F 45 4C 46 # e_ident[EI_MAG0-3] ELF's magic number 01 # e_ident[EI_CLASS] Indicating 32 bit 01 # e_ident[EI_DATA] Indicating little endianness 01 # e_ident[EI_VERSION] Indicating original elf 03 # e_ident[EI_OSABI] Set at 3 because FreeBSD is strict 00 # e_ident[EI_ABIVERSION] Set at 0 because none cares 00 00 00 00 00 00 00 # e_ident[EI_PAD] 02 00 # e_type Indicating Executable 03 00 # e_machine Indicating x86 01 00 00 00 # e_version Indicating original elf &_start # e_entry Address of the entry point %ELF_program_headers>ELF_base # e_phoff Address of program header table 00 00 00 00 # e_shoff Address of section header table 00 00 00 00 # e_flags 34 00 # e_ehsize Indicating our 52 Byte header 20 00 # e_phentsize size of a program header table 01 00 # e_phnum number of entries in program table 00 00 # e_shentsize size of a section header table 00 00 # e_shnum number of entries in section table 00 00 # e_shstrndx index of the section names ## Program Header :ELF_program_headers :ELF_program_header__text 01 00 00 00 # ph_type: PT-LOAD = 1 00 00 00 00 # ph_offset &ELF_base # ph_vaddr &ELF_base # ph_physaddr %ELF_end>ELF_base # ph_filesz %ELF_end>ELF_base # ph_memsz 07 00 00 00 # ph_flags: PF-X|PF-W|PF-R = 7 01 00 00 00 # ph_align :ELF_text ; Where the ELF Header is going to hit ; Simply jump to _start ; Our main function :_start 58 # POP_EAX ; Get the number of arguments 5B # POP_EBX ; Get the program name 5B # POP_EBX ; Get the actual output name 66B9 4102 # LOADI16_CX @577 ; Prepare file as O_WRONLY|O_CREAT|O_TRUNC 66BA 8001 # LOADI16_DX @384 ; Prepare file as RW for owner only (600 in octal) 6A 05 # PUSH !5 ; prepare to set eax to 5 58 # POP_EAX ; the syscall number for open() CD80 # INT_80 ; Now open that file 89C5 # COPY_EAX_to_EBP ; Preserve the file pointer we were given 6A 2D # PUSH !45 58 # POP_EAX ; the Syscall # for SYS_BRK 31DB # XOR_EBX_EBX ; Get current brk CD80 # INT_80 ; Let the kernel do the work 89C7 # COPY_EAX_to_EDI ; Set our malloc pointer 6A 2D # PUSH !45 58 # POP_EAX ; the Syscall # for SYS_BRK 89FB # COPY_EDI_to_EBX ; Using current pointer 81C3 00001000 # ADDI32_EBX %0x100000 ; Allocate 1MB CD80 # INT_80 ; Let the kernel do the work :core 5B # POP_EBX ; Get the actual input name 85DB # TEST_EBX_EBX ; Check for null string 74 !done # JE8 !done ; Hit null be done 31C9 # XOR_ECX_ECX ; prepare read_only 31D2 # XOR_EDX_EDX ; prevent any interactions 6A 05 # PUSH !5 58 # POP_EAX ; the syscall number for open() CD80 # INT_80 ; Now open that damn file 89C6 # COPY_EAX_to_ESI ; Protect INPUT :keep BA 00001000 # LOADI32_EDX %0x100000 ; set the size of chars we want 89F9 # COPY_EDI_to_ECX ; Where to put it 89F3 # COPY_ESI_to_EBX ; Where are we reading from 6A03 # PUSH !3 58 # POP_EAX ; the syscall number for read CD80 # INT_80 ; call the Kernel 50 # PUSH_EAX ; Protect the number of bytes read 89C2 # COPY_EAX_to_EDX ; Number of bytes to write 89F9 # COPY_EDI_to_ECX ; What we are writing 89EB # COPY_EBP_to_EBX ; Write to target file 6A 04 # PUSH !4 58 # POP_EAX ; the syscall number for write CD80 # INT_80 ; call the Kernel 58 # POP_EAX ; Get bytes read 3D 00001000 # CMPI32_EAX %0x100000 ; Check if buffer was fully used 74 !keep # JE8 !keep ; Keep looping if was full EB !core # JMP8 !core ; Otherwise move to next file :done ; program completed Successfully 31DB # XOR_EBX_EBX ; All is well 6A 01 # PUSH !1 58 # POP_EAX ; put the exit syscall number in eax CD80 # INT_80 ; Call it a good day :ELF_end
### Copyright (C) 2016 Jeremiah Orians ### Copyright (C) 2017 Jan Nieuwenhuizen <janneke@gnu.org> ### This file is part of stage0. ### ### stage0 is free software: you can redistribute it and/or modify ### it under the terms of the GNU General Public License as published by ### the Free Software Foundation, either version 3 of the License, or ### (at your option) any later version. ### ### stage0 is distributed in the hope that it will be useful, ### but WITHOUT ANY WARRANTY; without even the implied warranty of ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ### GNU General Public License for more details. ### ### You should have received a copy of the GNU General Public License ### along with stage0. If not, see <http://www.gnu.org/licenses/>. ### stage0's hex2 format ### !<label> 1 byte relative ### $<label> 2 byte address ### @<label> 2 byte relative ### &<label> 4 byte address ### %<label> 4 byte relative ### elf32.hex2: 32 bit elf header in hex2 ### if you wish to use this header, you need to add :ELF_end to the end of your ### M1 or hex2 files. ## ELF Header :ELF_base 7F 45 4C 46 # e_ident[EI_MAG0-3] ELF's magic number 01 # e_ident[EI_CLASS] Indicating 32 bit 01 # e_ident[EI_DATA] Indicating little endianness 01 # e_ident[EI_VERSION] Indicating original elf 03 # e_ident[EI_OSABI] Set at 3 because FreeBSD is strict 00 # e_ident[EI_ABIVERSION] Set at 0 because none cares 00 00 00 00 00 00 00 # e_ident[EI_PAD] 02 00 # e_type Indicating Executable 03 00 # e_machine Indicating x86 01 00 00 00 # e_version Indicating original elf &_start # e_entry Address of the entry point %ELF_program_headers>ELF_base # e_phoff Address of program header table 00 00 00 00 # e_shoff Address of section header table 00 00 00 00 # e_flags 34 00 # e_ehsize Indicating our 52 Byte header 20 00 # e_phentsize size of a program header table 01 00 # e_phnum number of entries in program table 00 00 # e_shentsize size of a section header table 00 00 # e_shnum number of entries in section table 00 00 # e_shstrndx index of the section names ## Program Header :ELF_program_headers :ELF_program_header__text 01 00 00 00 # ph_type: PT-LOAD = 1 00 00 00 00 # ph_offset &ELF_base # ph_vaddr &ELF_base # ph_physaddr %ELF_end>ELF_base # ph_filesz %ELF_end>ELF_base # ph_memsz 07 00 00 00 # ph_flags: PF-X|PF-W|PF-R = 7 01 00 00 00 # ph_align :ELF_text
;; Copyright (C) 2017 Jeremiah Orians ;; This file is part of stage0. ;; ;; stage0 is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; ;; stage0 is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with stage0. If not, see <http://www.gnu.org/licenses/>. ;; Register usage: ;; EAX, ECX, EBX => Temps ;; EDI => MALLOC ;; EBP => HEAD ;; [Output] => Output_file ;; [Input] => Input_file ;; Struct format: (size 32) ;; NEXT => 0 ;; TYPE => 8 ;; TEXT => 16 ;; EXPRESSION => 24 ;; Types ;; None => 0 ;; MACRO => 1 ;; STRING => 2 ; Where the ELF Header is going to hit ; Simply jump to _start ; Our main function :_start 58 ; POP_EAX ; Get the number of arguments 5B ; POP_EBX ; Get the program name 5B ; POP_EBX ; Get the actual input name B9 00000000 ; LOADI32_ECX %0 ; prepare read_only B8 05000000 ; LOADI32_EAX %5 ; the syscall number for open() CD80 ; INT_80 ; Now open that damn file A3 &Input ; STORE32_Absolute32_eax &Input ; Preserve the file pointer we were given 5B ; POP_EBX ; Get the actual output name B9 41020000 ; LOADI32_ECX %577 ; Prepare file as O_WRONLY|O_CREAT|O_TRUNC BA 80010000 ; LOADI32_EDX %384 ; Prepare file as RW for owner only (600 in octal) B8 05000000 ; LOADI32_EAX %5 ; the syscall number for open() CD80 ; INT_80 ; Now open that damn file 83F8 00 ; CMPI8_EAX !0 ; Check for missing output 0F8F %_start_out ; JG32 %_start_out ; Have real input B8 01000000 ; LOADI32_EAX %1 ; Use stdout :_start_out A3 &Output ; STORE32_Absolute32_eax &Output ; Preserve the file pointer we were given B8 2D000000 ; LOADI32_EAX %45 ; the Syscall # for SYS_BRK BB 00000000 ; LOADI32_EBX %0 ; Get current brk CD80 ; INT_80 ; Let the kernel do the work 89C7 ; COPY_EAX_to_EDI ; Set our malloc pointer E8 %Tokenize_Line ; CALL32 %Tokenize_Line ; Get all lines 89E8 ; COPY_EBP_to_EAX ; prepare for Reverse_List E8 %Reverse_List ; CALL32 %Reverse_List ; Correct order 89C5 ; COPY_EAX_to_EBP ; Update HEAD E8 %Identify_Macros ; CALL32 %Identify_Macros ; Find the DEFINEs E8 %Line_Macro ; CALL32 %Line_Macro ; Apply the DEFINEs E8 %Process_String ; CALL32 %Process_String ; Handle strings E8 %Eval_Immediates ; CALL32 %Eval_Immediates ; Handle Numbers E8 %Preserve_Other ; CALL32 %Preserve_Other ; Collect the remaining E8 %Print_Hex ; CALL32 %Print_Hex ; Output our results :Done ; program completed Successfully BB 00000000 ; LOADI32_EBX %0 ; All is well B8 01000000 ; LOADI32_EAX %1 ; put the exit syscall number in eax CD80 ; INT_80 ; Call it a good day ;; Tokenize_Line Function ;; Using input file [Input] and Head EBP ;; Creates a linked list of structs ;; Uses EBX for in_set strings, ECX for Int C and EDX for Struct Token* p :Tokenize_Line 53 ; PUSH_EBX ; Protect EBX 51 ; PUSH_ECX ; Protect ECX 52 ; PUSH_EDX ; Protect EDX :restart E8 %fgetc ; CALL32 %fgetc ; Read a char 83F8 FC ; CMPI8_EAX !-4 ; Check for EOF 0F84 %done ; JE32 %done ; File is collected 0FB6C0 ; MOVZX_al ; We have to zero extend it to use it 89C1 ; COPY_EAX_to_ECX ; Protect C BB &comments ; LOADI32_EBX &comments ; Get pointer to "#;" E8 %In_Set ; CALL32 %In_Set ; Check for comments 83F8 01 ; CMPI8_EAX !1 ; If comments 0F84 %Purge_LineComment ; JE32 %Purge_LineComment ; try again 89C8 ; COPY_ECX_to_EAX ; put C in place for check BB &terminators ; LOADI32_EBX &terminators ; Get pointer to "\n\t " E8 %In_Set ; CALL32 %In_Set ; Check for terminators 83F8 01 ; CMPI8_EAX !1 ; If terminator 0F84 %restart ; JE32 %restart ; try again B8 20000000 ; LOADI32_EAX %32 ; Malloc the struct P E8 %malloc ; CALL32 %malloc ; Get pointer to P 89C2 ; COPY_EAX_to_EDX ; Protect P 892A ; STORE32_EBP_into_Address_EDX ; P->NEXT = HEAD 89D5 ; COPY_EDX_to_EBP ; HEAD = P 89C8 ; COPY_ECX_to_EAX ; put C in place for check BB &string_char ; LOADI32_EBX &string_char ; Get pointer to "\"'" E8 %In_Set ; CALL32 %In_Set ; Check for string chars 83F8 01 ; CMPI8_EAX !1 ; If string char 0F84 %Store_String ; JE32 %Store_String ; Get string E8 %Store_Atom ; CALL32 %Store_Atom ; Get whole token E9 %restart ; JMP32 %restart :done 5A ; POP_EDX ; Restore EDX 59 ; POP_ECX ; Restore ECX 5B ; POP_EBX ; Restore EBX C3 ; RET ;; fgetc function ;; Receives FILE* in [Input] ;; Returns -4 (EOF) or char in EAX :fgetc 52 ; PUSH_EDX ; Protect EDX 51 ; PUSH_ECX ; Protect ECX 53 ; PUSH_EBX ; Protect EBX B8 FCFFFFFF ; LOADI32_EAX %-4 ; Put EOF in eax 50 ; PUSH_EAX ; Assume bad (If nothing read, value will remain EOF) 8D0C24 ; LEA32_ECX_from_esp ; Get stack address 8B1D &Input ; LOAD32_Absolute32_ebx &Input ; Where are we reading from B8 03000000 ; LOADI32_EAX %3 ; the syscall number for read BA 01000000 ; LOADI32_EDX %1 ; set the size of chars we want CD80 ; INT_80 ; call the Kernel 58 ; POP_EAX ; Get either char or EOF 5B ; POP_EBX ; Restore EBX 59 ; POP_ECX ; Restore ECX 5A ; POP_EDX ; Restore EDX C3 ; RET ;; Malloc isn't actually required if the program being built fits in the initial memory ;; However, it doesn't take much to add it. ;; Requires EDI to be initialized and EAX to have the number of desired bytes :malloc 52 ; PUSH_EDX ; Protect EDX 51 ; PUSH_ECX ; Protect ECX 53 ; PUSH_EBX ; Protect EBX 89FB ; COPY_EDI_to_EBX ; Using the current pointer 01C3 ; ADD_EAX_to_EBX ; Request the number of desired bytes B8 2D000000 ; LOADI32_EAX %45 ; the Syscall # for SYS_BRK CD80 ; INT_80 ; call the Kernel 89F8 ; COPY_EDI_to_EAX ; Return pointer 89DF ; COPY_EBX_to_EDI ; Update pointer 5B ; POP_EBX ; Restore EBX 59 ; POP_ECX ; Restore ECX 5A ; POP_EDX ; Restore EDX C3 ; RET ;; Purge_LineComment function ;; Reads chars until LF and jumps to restart :Purge_LineComment E8 %fgetc ; CALL32 %fgetc ; Get a char 0FB6C0 ; MOVZX_al ; Zero extend 83F8 0A ; CMPI8_EAX !10 ; While not LF 0F85 %Purge_LineComment ; JNE32 %Purge_LineComment ; Keep reading E9 %restart ; JMP32 %restart ;; Store_String Function ;; Receives C in ECX, HEAD in EDX and Input file in [Output] ;; Uses EBX for terminator, ECX for C and EDX for string :Store_String 53 ; PUSH_EBX ; Protect EBX 51 ; PUSH_ECX ; Protect ECX 52 ; PUSH_EDX ; Protect EDX B8 02000000 ; LOADI32_EAX %2 ; Using TYPE STRING 8942 08 ; STORE32_EAX_into_Address_EDX_Immediate8 !8 ; HEAD->TYPE = STRING B8 00010000 ; LOADI32_EAX %256 ; Malloc the string E8 %malloc ; CALL32 %malloc ; Get pointer to P 8942 10 ; STORE32_EAX_into_Address_EDX_Immediate8 !16 ; HEAD->TEXT = STRING 89CB ; COPY_ECX_to_EBX ; Protect terminator 89C2 ; COPY_EAX_to_EDX ; Protect string pointer :Store_String_Loop 880A ; STORE8_cl_into_Address_EDX ; write byte E8 %fgetc ; CALL32 %fgetc ; read next char 0FB6C0 ; MOVZX_al ; Zero extend it 89C1 ; COPY_EAX_to_ECX ; Update C 83C2 01 ; ADDI8_EDX !1 ; STRING = STRING + 1 39D9 ; CMP_EBX_ECX ; See if we hit terminator 0F85 %Store_String_Loop ; JNE32 %Store_String_Loop ; Otherwise keep looping 5A ; POP_EDX ; Restore EDX 59 ; POP_ECX ; Restore ECX 5B ; POP_EBX ; Restore EBX 89D0 ; COPY_EDX_to_EAX ; return HEAD E9 %restart ; JMP32 %restart ;; Store_Atom Function ;; Receives C in ECX, HEAD in EDX and Input file in [Input] ;; Uses EBX for in_set strings, ECX for C and EDX for string :Store_Atom 53 ; PUSH_EBX ; Protect EBX 51 ; PUSH_ECX ; Protect ECX 52 ; PUSH_EDX ; Protect EDX B8 00010000 ; LOADI32_EAX %256 ; Malloc the string E8 %malloc ; CALL32 %malloc ; Get pointer to P 8942 10 ; STORE32_EAX_into_Address_EDX_Immediate8 !16 ; HEAD->TEXT = STRING BB &terminators ; LOADI32_EBX &terminators ; Get pointer to "\n\t " 89C2 ; COPY_EAX_to_EDX ; Protect string pointer :Store_Atom_loop 880A ; STORE8_cl_into_Address_EDX ; write byte E8 %fgetc ; CALL32 %fgetc ; read next char 0FB6C0 ; MOVZX_al ; Zero extend it 89C1 ; COPY_EAX_to_ECX ; Update C 83C2 01 ; ADDI8_EDX !1 ; STRING = STRING + 1 E8 %In_Set ; CALL32 %In_Set ; Check for terminators 83F8 00 ; CMPI8_EAX !0 ; Check for "\n\t " 0F84 %Store_Atom_loop ; JE32 %Store_Atom_loop ; Loop otherwise 5A ; POP_EDX ; Restore EDX 59 ; POP_ECX ; Restore ECX 5B ; POP_EBX ; Restore EBX 89D0 ; COPY_EDX_to_EAX ; return HEAD C3 ; RET ;; In_Set function ;; Receives Char C in EAX and CHAR* in EBX ;; Returns 1 if true, zero if false in EAX :In_Set 53 ; PUSH_EBX ; Protect EBX 51 ; PUSH_ECX ; Protect ECX :In_Set_loop 8A0B ; LOAD8_cl_from_EBX ; Read char 0FB6C9 ; MOVZX_cl ; Zero extend it 39C8 ; CMP_EAX_ECX ; See if they match 0F84 %In_Set_True ; JE32 %In_Set_True ; return true 83F9 00 ; CMPI8_ECX !0 ; Check for NULL 0F84 %In_Set_False ; JE32 %In_Set_False ; return false 83C3 01 ; ADDI8_EBX !1 ; s = s + 1 E9 %In_Set_loop ; JMP32 %In_Set_loop ; Keep looping :In_Set_True B8 01000000 ; LOADI32_EAX %1 ; Set True 59 ; POP_ECX ; Restore ECX 5B ; POP_EBX ; Restore EBX C3 ; RET :In_Set_False B8 00000000 ; LOADI32_EAX %0 ; Set FALSE 59 ; POP_ECX ; Restore ECX 5B ; POP_EBX ; Restore EBX C3 ; RET ;; Char sets :terminators 0A092000 ; "\n\t " :comments 3B2300 ; ";#" :string_char 22 27 00 ; "\"\'" ;; Reverse_List function ;; Receives List in EAX ;; Returns the list reversed in EAX :Reverse_List 53 ; PUSH_EBX ; Protect EBX 51 ; PUSH_ECX ; Protect ECX 89C3 ; COPY_EAX_to_EBX ; Set HEAD B8 00000000 ; LOADI32_EAX %0 ; ROOT = NULL :Reverse_List_Loop 83FB 00 ; CMPI8_EBX !0 ; WHILE HEAD != NULL 0F84 %Reverse_List_Done ; JE32 %Reverse_List_Done ; Stop otherwise 8B0B ; LOAD32_ECX_from_EBX ; NEXT = HEAD->NEXT 8903 ; STORE32_EAX_into_Address_EBX ; HEAD->NEXT = ROOT 89D8 ; COPY_EBX_to_EAX ; ROOT = HEAD 89CB ; COPY_ECX_to_EBX ; HEAD = NEXT E9 %Reverse_List_Loop ; JMP32 %Reverse_List_Loop ; Keep Going :Reverse_List_Done 59 ; POP_ECX ; Restore ECX 5B ; POP_EBX ; Restore EBX C3 ; RET ;; Identify_Macros function ;; Receives List in EAX ;; Updates the list in place; does not modify registers ;; Uses EBX for DEFINE, ECX for I :Identify_Macros 50 ; PUSH_EAX ; Protect EAX 53 ; PUSH_EBX ; Protect EBX 51 ; PUSH_ECX ; Protect ECX 52 ; PUSH_EDX ; Protect EDX BB &DEFINE_str ; LOADI32_EBX &DEFINE_str ; Setup define string 89C1 ; COPY_EAX_to_ECX ; I = HEAD :Identify_Macros_Loop 8B41 10 ; LOAD32_EAX_from_ECX_Immediate8 !16 ; I->TEXT E8 %match ; CALL32 %match ; IF "DEFINE" == I->TEXT 83F8 00 ; CMPI8_EAX !0 ; Check if match 0F85 %Identify_Macros_Next ; JNE32 %Identify_Macros_Next ; Skip the work ;; Deal with MACRO B8 01000000 ; LOADI32_EAX %1 ; Using MACRO 8941 08 ; STORE32_EAX_into_Address_ECX_Immediate8 !8 ; I->TYPE = MACRO 8B01 ; LOAD32_EAX_from_ECX ; I->NEXT 8B40 10 ; LOAD32_EAX_from_EAX_Immediate8 !16 ; I->NEXT->TEXT 8941 10 ; STORE32_EAX_into_Address_ECX_Immediate8 !16 ; I->TEXT = I->NEXT->TEXT 8B01 ; LOAD32_EAX_from_ECX ; I->NEXT 8B00 ; LOAD32_EAX_from_EAX ; I->NEXT->NEXT 8B40 10 ; LOAD32_EAX_from_EAX_Immediate8 !16 ; I->NEXT->NEXT->TEXT 8941 18 ; STORE32_EAX_into_Address_ECX_Immediate8 !24 ; I->EXPRESSION = I->NEXT->NEXT->TEXT 8B01 ; LOAD32_EAX_from_ECX ; I->NEXT 8B00 ; LOAD32_EAX_from_EAX ; I->NEXT->NEXT 8B00 ; LOAD32_EAX_from_EAX ; I->NEXT->NEXT->NEXT 8901 ; STORE32_EAX_into_Address_ECX ; I->NEXT = I->NEXT->NEXT->NEXT :Identify_Macros_Next 8B09 ; LOAD32_ECX_from_ECX ; I = I->NEXT 83F9 00 ; CMPI8_ECX !0 ; Check for NULL 0F85 %Identify_Macros_Loop ; JNE32 %Identify_Macros_Loop ; Keep looping otherwise 5A ; POP_EDX ; Restore EDX 59 ; POP_ECX ; Restore ECX 5B ; POP_EBX ; Restore EBX 58 ; POP_EAX ; Restore EAX C3 ; RET :DEFINE_str 444546494E4500 ; "DEFINE" ;; match function ;; Receives CHAR* in EAX and CHAR* in EBX ;; Returns 0 (TRUE) or 1 (FALSE) in EAX :match 53 ; PUSH_EBX ; Protect EBX 51 ; PUSH_ECX ; Protect ECX 52 ; PUSH_EDX ; Protect EDX 89C1 ; COPY_EAX_to_ECX ; S1 in place 89DA ; COPY_EBX_to_EDX ; S2 in place :match_Loop 8A01 ; LOAD8_al_from_ECX ; S1[0] 0FB6C0 ; MOVZX_al ; Make it useful 8A1A ; LOAD8_bl_from_EDX ; S2[0] 0FB6DB ; MOVZX_bl ; Make it useful 39D8 ; CMP_EAX_EBX ; See if they match 0F85 %match_False ; JNE32 %match_False ; If not 83C1 01 ; ADDI8_ECX !1 ; S1 = S1 + 1 83C2 01 ; ADDI8_EDX !1 ; S2 = S2 + 1 83F8 00 ; CMPI8_EAX !0 ; If reached end of string 0F84 %match_Done ; JE32 %match_Done ; Perfect match E9 %match_Loop ; JMP32 %match_Loop ; Otherwise keep looping :match_False B8 01000000 ; LOADI32_EAX %1 ; Return false :match_Done 5A ; POP_EDX ; Restore EDX 59 ; POP_ECX ; Restore ECX 5B ; POP_EBX ; Restore EBX C3 ; RET ;; Line_Macro function ;; Receives List in EAX ;; Updates the list in place; does not modify registers ;; Uses EAX for I, EBX for I->TEXT, ECX for I->EXPRESSION :Line_Macro 50 ; PUSH_EAX ; Protect EAX 53 ; PUSH_EBX ; Protect EBX 51 ; PUSH_ECX ; Protect ECX 52 ; PUSH_EDX ; Protect EDX :Line_Macro_Loop 8B58 08 ; LOAD32_EBX_from_EAX_Immediate8 !8 ; I->TYPE 83FB 01 ; CMPI8_EBX !1 ; IF MACRO == I->TYPE 0F85 %Line_Macro_Next ; JNE32 %Line_Macro_Next ; Otherwise move on ;; Is a macro apply 8B58 10 ; LOAD32_EBX_from_EAX_Immediate8 !16 ; I->TEXT 8B48 18 ; LOAD32_ECX_from_EAX_Immediate8 !24 ; I->EXPRESSION 8B00 ; LOAD32_EAX_from_EAX ; I->NEXT E8 %Set_Expression ; CALL32 %Set_Expression ; Apply it E9 %Line_Macro_Loop ; JMP32 %Line_Macro_Loop ; Move on to next :Line_Macro_Next 8B00 ; LOAD32_EAX_from_EAX ; I->NEXT 83F8 00 ; CMPI8_EAX !0 ; Check for NULL 0F85 %Line_Macro_Loop ; JNE32 %Line_Macro_Loop ; Keep going 5A ; POP_EDX ; Restore EDX 59 ; POP_ECX ; Restore ECX 5B ; POP_EBX ; Restore EBX 58 ; POP_EAX ; Restore EAX C3 ; RET ;; Set_Expression function ;; Receives List in EAX, CHAR* in EBX and CHAR* in ECX ;; Updates the list in place; does not modify registers ;; Uses EBX for C, ECX for EXP and EDX for I :Set_Expression 50 ; PUSH_EAX ; Protect EAX 53 ; PUSH_EBX ; Protect EBX 51 ; PUSH_ECX ; Protect ECX 52 ; PUSH_EDX ; Protect EDX 89C2 ; COPY_EAX_to_EDX ; Set I :Set_Expression_Loop 8B42 08 ; LOAD32_EAX_from_EDX_Immediate8 !8 ; I->TYPE 83F8 01 ; CMPI8_EAX !1 ; IF MACRO == I->TYPE 0F84 %Set_Expression_Next ; JE32 %Set_Expression_Next ; Ignore and move on 8B42 10 ; LOAD32_EAX_from_EDX_Immediate8 !16 ; I->TEXT E8 %match ; CALL32 %match ; Check for match 83F8 00 ; CMPI8_EAX !0 ; If match 0F85 %Set_Expression_Next ; JNE32 %Set_Expression_Next ; Otherwise next ;; We have a non-macro match 894A 18 ; STORE32_ECX_into_Address_EDX_Immediate8 !24 ; I->EXPRESSION = EXP :Set_Expression_Next 8B12 ; LOAD32_EDX_from_EDX ; I = I->NEXT 83FA 00 ; CMPI8_EDX !0 ; IF NULL == I 0F85 %Set_Expression_Loop ; JNE32 %Set_Expression_Loop ; Otherwise keep looping 5A ; POP_EDX ; Restore EDX 59 ; POP_ECX ; Restore ECX 5B ; POP_EBX ; Restore EBX 58 ; POP_EAX ; Restore EAX C3 ; RET ;; Process_String function ;; Receives List in EAX ;; Update the list in place; does not modify registers ;; Uses EBX for I->TEXT, ECX for I and EDX for S :Process_String 50 ; PUSH_EAX ; Protect EAX 53 ; PUSH_EBX ; Protect EBX 51 ; PUSH_ECX ; Protect ECX 52 ; PUSH_EDX ; Protect EDX 89C1 ; COPY_EAX_to_ECX ; I = HEAD :Process_String_loop 8B41 08 ; LOAD32_EAX_from_ECX_Immediate8 !8 ; I->TYPE 83F8 02 ; CMPI8_EAX !2 ; IF STRING == I->TYPE 0F85 %Process_String_Next ; JNE32 %Process_String_Next ; Skip to next 8B59 10 ; LOAD32_EBX_from_ECX_Immediate8 !16 ; I->TEXT 8A03 ; LOAD8_al_from_EBX ; I->TEXT[0] 0FB6C0 ; MOVZX_al ; make it useful 83F8 27 ; CMPI8_EAX !39 ; IF '\'' == I->TEXT[0] 0F85 %Process_String_Raw ; JNE32 %Process_String_Raw ; Deal with '\"' ;; Deal with '\'' 83C3 01 ; ADDI8_EBX !1 ; I->TEXT + 1 8959 18 ; STORE32_EBX_into_Address_ECX_Immediate8 !24 ; I->EXPRESSION = I->TEXT + 1 E9 %Process_String_Next ; JMP32 %Process_String_Next ; Move on to next :Process_String_Raw 89D8 ; COPY_EBX_to_EAX ; Get length of I->TEXT E8 %string_length ; CALL32 %string_length ; Do it C1E8 02 ; SHRI8_EAX !2 ; LENGTH = LENGTH >> 2 83C0 01 ; ADDI8_EAX !1 ; LENGTH = LENGTH + 1 C1E0 03 ; SHLI8_EAX !3 ; LENGTH = LENGTH << 3 E8 %malloc ; CALL32 %malloc ; Get string 89DA ; COPY_EBX_to_EDX ; S = I->TEXT 83C2 01 ; ADDI8_EDX !1 ; S = S + 1 8941 18 ; STORE32_EAX_into_Address_ECX_Immediate8 !24 ; I->EXPRESSION = hexify 89C3 ; COPY_EAX_to_EBX ; Put hexify buffer in ebx :Process_String_Raw_Loop 8A02 ; LOAD8_al_from_EDX ; Read 1 chars 0FB6C0 ; MOVZX_al ; Make it useful 83C2 01 ; ADDI8_EDX !1 ; S = S + 1 3C 00 ; CMPI8_AL !0 ; Check for NULL 9C ; PUSH_FLAGS ; Protect condition E8 %hex8 ; CALL32 %hex8 ; write them all 9D ; POP_FLAGS ; restore condition 0F85 %Process_String_Raw_Loop ; JNE32 %Process_String_Raw_Loop ; Keep looping :Process_String_Next 8B09 ; LOAD32_ECX_from_ECX ; I = I->NEXT 83F9 00 ; CMPI8_ECX !0 ; IF NULL == I 0F85 %Process_String_loop ; JNE32 %Process_String_loop ; Otherwise keep looping 5A ; POP_EDX ; Restore EDX 59 ; POP_ECX ; Restore ECX 5B ; POP_EBX ; Restore EBX 58 ; POP_EAX ; Restore EAX C3 ; RET ;; string_length function ;; Receives CHAR* in EAX ;; Returns INT in EAX ;; Uses EAX for CH, EBX for S and ECX for INDEX :string_length 53 ; PUSH_EBX ; Protect EBX 51 ; PUSH_ECX ; Protect ECX 89C3 ; COPY_EAX_to_EBX ; Set S B9 00000000 ; LOADI32_ECX %0 ; INDEX = 0 :string_length_loop 8A040B ; LOAD8_al_from_EBX_indexed_ECX ; S[0] 0FB6C0 ; MOVZX_al ; make it useful 83F8 00 ; CMPI8_EAX !0 ; IF NULL == S[0] 0F84 %string_length_done ; JE32 %string_length_done ; Stop 83C1 01 ; ADDI8_ECX !1 ; INDEX = INDEX + 1 E9 %string_length_loop ; JMP32 %string_length_loop ; Keep going :string_length_done 89C8 ; COPY_ECX_to_EAX ; RETURN INDEX 59 ; POP_ECX ; Restore ECX 5B ; POP_EBX ; Restore EBX C3 ; RET ;; Eval_Immediates function ;; Receives List in EAX ;; Updates the list in place; does not modify registers ;; Uses EBX for I->TEXT[0], ECX for I->TEXT[1] and EDX for I :Eval_Immediates 50 ; PUSH_EAX ; Protect EAX 53 ; PUSH_EBX ; Protect EBX 51 ; PUSH_ECX ; Protect ECX 52 ; PUSH_EDX ; Protect EDX 89C2 ; COPY_EAX_to_EDX ; I = HEAD :Eval_Immediates_Loop ;; Check for MACRO 8B42 08 ; LOAD32_EAX_from_EDX_Immediate8 !8 ; I->TYPE 83F8 01 ; CMPI8_EAX !1 ; IF MACRO == I-TYPE 0F84 %Eval_Immediates_Next ; JE32 %Eval_Immediates_Next ; Skip to next ;; Check for NULL EXPRESSION 8B42 18 ; LOAD32_EAX_from_EDX_Immediate8 !24 ; I->EXPRESSION 83F8 00 ; CMPI8_EAX !0 ; IF NULL == I->EXPRESSION 0F85 %Eval_Immediates_Next ; JNE32 %Eval_Immediates_Next ; Skip to next ;; Check if number 8B42 10 ; LOAD32_EAX_from_EDX_Immediate8 !16 ; I->TEXT 8A18 ; LOAD8_bl_from_EAX ; I->TEXT[0] 0FB6DB ; MOVZX_bl ; Extend to use 83C0 01 ; ADDI8_EAX !1 ; I->TEXT + 1 8A08 ; LOAD8_cl_from_EAX ; I->TEXT[1] 0FB6C9 ; MOVZX_cl ; Extend to use E8 %numerate_string ; CALL32 %numerate_string ; Convert string to INT 83F8 00 ; CMPI8_EAX !0 ; IF 0 == numerate_number(I->TEXT + 1) 0F85 %Eval_Immediates_value ; JNE32 %Eval_Immediates_value ; Has a value ;; Last chance for Immediate 83F9 30 ; CMPI8_ECX !48 ; If '0' == I->TEXT[1] 0F85 %Eval_Immediates_Next ; JNE32 %Eval_Immediates_Next ; Skip to next :Eval_Immediates_value E8 %express_number ; CALL32 %express_number ; Convert value to hex string 8942 18 ; STORE32_EAX_into_Address_EDX_Immediate8 !24 ; I->EXPRESSION = express_number(value, I-TEXT[0]) :Eval_Immediates_Next 8B12 ; LOAD32_EDX_from_EDX ; I = I->NEXT 83FA 00 ; CMPI8_EDX !0 ; IF NULL == I 0F85 %Eval_Immediates_Loop ; JNE32 %Eval_Immediates_Loop ; Otherwise keep looping 5A ; POP_EDX ; Restore EDX 59 ; POP_ECX ; Restore ECX 5B ; POP_EBX ; Restore EBX 58 ; POP_EAX ; Restore EAX C3 ; RET ;; numerate_string function ;; Receives CHAR* in EAX ;; Returns value of CHAR* in EAX ;; Uses EAX for VALUE, EBX for S, ECX for CH and EDI for NEGATIVE? :numerate_string 53 ; PUSH_EBX ; Protect EBX 51 ; PUSH_ECX ; Protect ECX 52 ; PUSH_EDX ; Protect EDX 57 ; PUSH_EDI ; Protect EDI 89C3 ; COPY_EAX_to_EBX ; put S in correct place B8 00000000 ; LOADI32_EAX %0 ; Initialize to Zero :numerate_string_loop 8A4B 01 ; LOAD8_cl_from_EBX_Immediate8 !1 ; S[1] 0FB6C9 ; MOVZX_cl ; make it useful 83F9 78 ; CMPI8_ECX !120 ; IF 'x' == S[1] 0F84 %numerate_hex ; JE32 %numerate_hex ; Deal with hex input ;; Assume decimal input B9 00000000 ; LOADI32_ECX %0 ; Assume no negation 8A0B ; LOAD8_cl_from_EBX ; S[0] 0FB6C9 ; MOVZX_cl ; make it useful 83F9 2D ; CMPI8_ECX !45 ; IF '-' == S[0] 0F85 %numerate_decimal ; JNE32 %numerate_decimal ; Skip negation BF 01000000 ; LOADI32_EDI %1 ; Set FLAG 83C3 01 ; ADDI8_EBX !1 ; S = S + 1 :numerate_decimal 8A0B ; LOAD8_cl_from_EBX ; S[0] 0FB6C9 ; MOVZX_cl ; make it useful 83F9 00 ; CMPI8_ECX !0 ; IF NULL == S[0] 0F84 %numerate_decimal_done ; JE32 %numerate_decimal_done ; We are done 6BC0 0A ; IMULI8_EAX !10 ; VALUE = VALUE * 10 83E9 30 ; SUBI8_ECX !48 ; CH = CH - '0' 83F9 09 ; CMPI8_ECX !9 ; Check for illegal 0F8F %numerate_string_fail ; JG32 %numerate_string_fail ; If CH > '9' 83F9 00 ; CMPI8_ECX !0 ; Check for illegal 0F8C %numerate_string_fail ; JL32 %numerate_string_fail ; IF CH < 0 01C8 ; ADD_ECX_to_EAX ; VALUE = VALUE + CH 83C3 01 ; ADDI8_EBX !1 ; S = S + 1 E9 %numerate_decimal ; JMP32 %numerate_decimal ; Keep looping :numerate_decimal_done 83FF 01 ; CMPI8_EDI !1 ; Check if need to negate 0F85 %numerate_string_done ; JNE32 %numerate_string_done ; Nope 6BC0 FF ; IMULI8_EAX !-1 ; VALUE = VALUE * -1 E9 %numerate_string_done ; JMP32 %numerate_string_done ; Done :numerate_hex 83C3 02 ; ADDI8_EBX !2 ; S = S + 2 :numerate_hex_loop 8A0B ; LOAD8_cl_from_EBX ; S[0] 0FB6C9 ; MOVZX_cl ; make it useful 83F9 00 ; CMPI8_ECX !0 ; IF NULL == S[0] 0F84 %numerate_string_done ; JE32 %numerate_string_done ; We are done C1E0 04 ; SHLI8_EAX !4 ; VALUE = VALUE << 4 83E9 30 ; SUBI8_ECX !48 ; CH = CH - '0' 83F9 0A ; CMPI8_ECX !10 ; IF 10 >= CH 0F8C %numerate_hex_digit ; JL32 %numerate_hex_digit ; NO 83E9 07 ; SUBI8_ECX !7 ; Push A-F into range :numerate_hex_digit 83F9 0F ; CMPI8_ECX !15 ; Check for illegal 0F8F %numerate_string_fail ; JG32 %numerate_string_fail ; If CH > 'F' 83F9 00 ; CMPI8_ECX !0 ; Check for illegal 0F8C %numerate_string_fail ; JL32 %numerate_string_fail ; IF CH < 0 01C8 ; ADD_ECX_to_EAX ; VALUE = VALUE + CH 83C3 01 ; ADDI8_EBX !1 ; S = S + 1 E9 %numerate_hex_loop ; JMP32 %numerate_hex_loop ; Keep looping :numerate_string_fail B8 00000000 ; LOADI32_EAX %0 ; return ZERO :numerate_string_done 5F ; POP_EDI ; Restore EDI 5A ; POP_EDX ; Restore EDX 59 ; POP_ECX ; Restore ECX 5B ; POP_EBX ; Restore EBX C3 ; RET ;; express_number function ;; Receives INT in EAX and CHAR in EBX ;; Allocates a string and expresses the value in hex ;; Returns string in EAX ;; Uses EAX for VALUE, EBX for S and ECX for CH :express_number 53 ; PUSH_EBX ; Protect EBX 51 ; PUSH_ECX ; Protect ECX 52 ; PUSH_EDX ; Protect EDX 89D9 ; COPY_EBX_to_ECX ; Put CH in right place 89C3 ; COPY_EAX_to_EBX ; Protect VALUE 83F9 25 ; CMPI8_ECX !37 ; IF '%' == CH 0F85 %express_number2 ; JNE32 %express_number2 ; Otherwise try @ B8 09000000 ; LOADI32_EAX %9 ; We need 3bytes E8 %malloc ; CALL32 %malloc ; Get S pointer 93 ; XCHG_EAX_EBX ; Put S and VALUE in place 53 ; PUSH_EBX ; Protect S E8 %hex32l ; CALL32 %hex32l ; Store 32bits E9 %express_number_done ; JMP32 %express_number_done ; done :express_number2 83F9 40 ; CMPI8_ECX !64 ; IF '@' == CH 0F85 %express_number1 ; JNE32 %express_number1 ; Othrewise try ! B8 05000000 ; LOADI32_EAX %5 ; We need 3bytes E8 %malloc ; CALL32 %malloc ; Get S pointer 93 ; XCHG_EAX_EBX ; Put S and VALUE in place 53 ; PUSH_EBX ; Protect S E8 %hex16l ; CALL32 %hex16l ; Store 16bits E9 %express_number_done ; JMP32 %express_number_done ; done :express_number1 B8 03000000 ; LOADI32_EAX %3 ; We need 3bytes E8 %malloc ; CALL32 %malloc ; Get S pointer 93 ; XCHG_EAX_EBX ; Put S and VALUE in place 53 ; PUSH_EBX ; Protect S E8 %hex8 ; CALL32 %hex8 ; Store 8bit :express_number_done 58 ; POP_EAX ; Restore S 5A ; POP_EDX ; Restore EDX 59 ; POP_ECX ; Restore ECX 5B ; POP_EBX ; Restore EBX C3 ; RET ;; HEX to ascii routine ;; Receives INT in EAX and CHAR* in EBX ;; Stores ascii of INT in CHAR* ;; Returns only modifying EAX :hex64l 50 ; PUSH_EAX ; Protect top 32 E8 %hex32l ; CALL32 %hex32l ; Store it 58 ; POP_EAX ; do top 32 C1E8 20 ; SHRI8_EAX !32 ; do bottom 32 first :hex32l 50 ; PUSH_EAX ; Protect top 16 E8 %hex16l ; CALL32 %hex16l ; Store it 58 ; POP_EAX ; do top 16 C1E8 10 ; SHRI8_EAX !16 ; do bottom 16 first :hex16l 50 ; PUSH_EAX ; Protect top byte E8 %hex8 ; CALL32 %hex8 ; Store it 58 ; POP_EAX ; do high byte C1E8 08 ; SHRI8_EAX !8 ; do bottom byte first :hex8 50 ; PUSH_EAX ; Protect bottom nibble C1E8 04 ; SHRI8_EAX !4 ; do high nibble first E8 %hex4 ; CALL32 %hex4 ; Store it 58 ; POP_EAX ; do low nibble :hex4 83E0 0F ; ANDI8_EAX !0xF ; isolate nibble 04 30 ; ADDI8_AL !48 ; convert to ascii 3C 39 ; CMPI8_AL !57 ; valid digit? 0F86 %hex1 ; JBE32 %hex1 ; yes 04 07 ; ADDI8_AL !7 ; use alpha range :hex1 8803 ; STORE8_al_into_Address_EBX ; store result 83C3 01 ; ADDI8_EBX !1 ; next position C3 ; RET ;; Preserve_Other function ;; Receives List in EAX ;; Updates the list in place; does not modify registers ;; Uses EAX for I, EBX for I->TEXT :Preserve_Other 50 ; PUSH_EAX ; Protect EAX 53 ; PUSH_EBX ; Protect EBX 51 ; PUSH_ECX ; Protect ECX 52 ; PUSH_EDX ; Protect EDX :Preserve_Other_Loop 8B58 18 ; LOAD32_EBX_from_EAX_Immediate8 !24 ; I->EXPRESSION 83FB 00 ; CMPI8_EBX !0 ; IF NULL == I->EXPRESSION 0F85 %Preserve_Other_Next ; JNE32 %Preserve_Other_Next ; Otherwise next ;; Needs preserving 8B58 10 ; LOAD32_EBX_from_EAX_Immediate8 !16 ; I->TEXT 8958 18 ; STORE32_EBX_into_Address_EAX_Immediate8 !24 ; I->EXPRESSION = I->TEXT :Preserve_Other_Next 8B00 ; LOAD32_EAX_from_EAX ; I = I->NEXT 83F8 00 ; CMPI8_EAX !0 ; IF NULL == I 0F85 %Preserve_Other_Loop ; JNE32 %Preserve_Other_Loop ; Otherwise keep looping 5A ; POP_EDX ; Restore EDX 59 ; POP_ECX ; Restore ECX 5B ; POP_EBX ; Restore EBX 58 ; POP_EAX ; Restore EAX C3 ; RET ;; Print_Hex function ;; Receives list in EAX ;; walks the list and prints the I->EXPRESSION for all nodes followed by newline ;; Uses EBX for I :Print_Hex 53 ; PUSH_EBX ; Protect EBX 51 ; PUSH_ECX ; Protect ECX 89EB ; COPY_EBP_to_EBX ; I = Head :Print_Hex_Loop 8B43 08 ; LOAD32_EAX_from_EBX_Immediate8 !8 ; I->TYPE 83F8 01 ; CMPI8_EAX !1 ; IF MACRO == I->TYPE 0F84 %Print_Hex_Next ; JE32 %Print_Hex_Next ; Skip 8B43 18 ; LOAD32_EAX_from_EBX_Immediate8 !24 ; Using EXPRESSION E8 %File_Print ; CALL32 %File_Print ; Print it B8 0A000000 ; LOADI32_EAX %10 ; NEWLINE E8 %fputc ; CALL32 %fputc ; Append it :Print_Hex_Next 8B1B ; LOAD32_EBX_from_EBX ; Iterate to next Token 83FB 00 ; CMPI8_EBX !0 ; Check for NULL 0F85 %Print_Hex_Loop ; JNE32 %Print_Hex_Loop ; Otherwise keep looping 59 ; POP_ECX ; Restore ECX 5B ; POP_EBX ; Restore EBX C3 ; RET ;; File_Print function ;; Receives CHAR* in EAX ;; calls fputc for every non-null char :File_Print 53 ; PUSH_EBX ; Protect EBX 51 ; PUSH_ECX ; Protect ECX 89C3 ; COPY_EAX_to_EBX ; Protect S 83F8 00 ; CMPI8_EAX !0 ; Protect against nulls 0F84 %File_Print_Done ; JE32 %File_Print_Done ; Simply don't try to print them :File_Print_Loop 8A03 ; LOAD8_al_from_EBX ; Read byte 0FB6C0 ; MOVZX_al ; zero extend 83F8 00 ; CMPI8_EAX !0 ; Check for NULL 0F84 %File_Print_Done ; JE32 %File_Print_Done ; Stop at NULL E8 %fputc ; CALL32 %fputc ; write it 83C3 01 ; ADDI8_EBX !1 ; S = S + 1 E9 %File_Print_Loop ; JMP32 %File_Print_Loop ; Keep going :File_Print_Done 59 ; POP_ECX ; Restore ECX 5B ; POP_EBX ; Restore EBX C3 ; RET ;; fputc function ;; receives CHAR in EAX and FILE* in [Output] ;; writes char and returns :fputc 52 ; PUSH_EDX ; Protect EDX 51 ; PUSH_ECX ; protect ECX 53 ; PUSH_EBX ; protect EBX 50 ; PUSH_EAX ; We are writing eax 8D0C24 ; LEA32_ECX_from_esp ; Get stack address 8B1D &Output ; LOAD32_Absolute32_ebx &Output ; Write to target file B8 04000000 ; LOADI32_EAX %4 ; the syscall number for write BA 01000000 ; LOADI32_EDX %1 ; set the size of chars we want CD80 ; INT_80 ; call the Kernel 58 ; POP_EAX ; Restore stack 5B ; POP_EBX ; Restore EBX 59 ; POP_ECX ; Restore ECX 5A ; POP_EDX ; Restore EDX C3 ; RET :Output 00000000 ; NULL :Input 00000000 ; NULL :ELF_end
;; Copyright (C) 2017 Jeremiah Orians ;; This file is part of stage0. ;; ;; stage0 is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; ;; stage0 is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with stage0. If not, see <http://www.gnu.org/licenses/>. DEFINE ADDI8_EAX 83C0 DEFINE ADDI8_EBX 83C3 DEFINE ADDI8_ECX 83C1 DEFINE ADDI8_EDX 83C2 DEFINE ADDI8_ESI 83C6 DEFINE ADD_eax_into_ebx 01C3 DEFINE ADD_ebx_into_eax 01D8 DEFINE ADD_ecx_into_eax 01C8 DEFINE ADD_edi_into_ecx 01F9 DEFINE AND_EAX_EBX 21D8 DEFINE ANDI32_EAX 25 DEFINE CALL_EAX FFD0 DEFINE CALL32 E8 DEFINE CMPI8_EAX 83F8 DEFINE CMPI8_EBP 83fd DEFINE CMPI8_EBX 83FB DEFINE CMPI8_ECX 83F9 DEFINE CMPI8_EDX 83FA DEFINE CMPI8_ESI 83FE DEFINE CMP_EAX_EBX 39D8 DEFINE CMP_EAX_ECX 39C8 DEFINE CMP_EBX_ECX 39D9 DEFINE CMP_ECX_EBX 39CB DEFINE CMP_EDI_ESI 39FE DEFINE CMP_EBX_EDX 39D3 DEFINE COPY_EAX_to_EBP 89C5 DEFINE COPY_EAX_to_EBX 89C3 DEFINE COPY_EAX_to_ECX 89C1 DEFINE COPY_EAX_to_EDX 89C2 DEFINE COPY_EAX_to_ESI 89C6 DEFINE COPY_EBP_to_EAX 89E8 DEFINE COPY_EBX_to_EAX 89D8 DEFINE COPY_EBX_to_ECX 89D9 DEFINE COPY_EBX_to_EDX 89DA DEFINE COPY_ECX_to_EAX 89C8 DEFINE COPY_ECX_to_EBX 89CB DEFINE COPY_EDI_to_ESI 89FE DEFINE COPY_EDX_to_EAX 89D0 DEFINE COPY_EDX_to_EBP 89D5 DEFINE COPY_EDX_to_EBX 89D3 DEFINE COPY_ESI_to_EAX 89F0 DEFINE COPY_ESI_to_EDI 89F7 DEFINE IDIV_EBX F7FB DEFINE IMUL_EAX_by_EBX 0FAFC3 DEFINE IMULI8_EAX 6BC0 DEFINE IMULI8_EBP 6BED DEFINE INT_80 CD80 DEFINE JBE8 76 DEFINE JE32 0F84 DEFINE JG32 0F8F DEFINE JG8 7F DEFINE JL32 0F8C DEFINE JLE32 0F8E DEFINE JMP32 E9 DEFINE JNE32 0F85 DEFINE LEA32_ECX_from_esp 8D0C24 DEFINE LOAD32_Absolute32_eax A1 DEFINE LOAD32_Absolute32_ebx 8B1D DEFINE LOAD32_Absolute32_ecx 8B0D DEFINE LOAD32_EAX_from_EAX 8B00 DEFINE LOAD32_EAX_from_EAX_Immediate8 8B40 DEFINE LOAD32_EAX_from_EBP_Immediate8 8B45 DEFINE LOAD32_EAX_from_EBX 8B03 DEFINE LOAD32_EAX_from_EBX_Immediate8 8B43 DEFINE LOAD32_EAX_from_ECX_Immediate8 8B41 DEFINE LOAD32_EAX_from_EDX_Immediate8 8B42 DEFINE LOAD32_EBP_from_EBP 8B6D00 DEFINE LOAD32_EBX_from_EAX_Immediate8 8B58 DEFINE LOAD32_EBX_from_EBX 8B1B DEFINE LOAD32_EBX_from_EBX_Immediate8 8B5B DEFINE LOAD32_EBX_from_ECX_Immediate8 8B59 DEFINE LOAD32_ECX_from_EAX_Immediate8 8B48 DEFINE LOAD32_ECX_from_EBX 8B0B DEFINE LOAD32_ECX_from_ECX 8B09 DEFINE LOAD32_ECX_from_ECX_Immediate8 8B49 DEFINE LOAD32_ECX_from_EDX_Immediate8 8B4A DEFINE LOAD32_EDI_from_EDX_Immediate8 8B7A DEFINE LOAD32_EDX_from_EDX_Immediate8 8B52 DEFINE LOAD8_al_from_EAX 8A00 DEFINE LOAD8_al_from_EBX 8A03 DEFINE LOAD8_al_from_ECX 8A01 DEFINE LOAD8_al_from_EDX 8A02 DEFINE LOAD8_bl_from_EBX 8A1B DEFINE LOAD8_bl_from_ECX 8A19 DEFINE LOAD8_bl_from_EDX 8A1A DEFINE LOAD8_cl_from_EBX 8A0B DEFINE LOAD8_cl_from_EBX_Immediate8 8A4B DEFINE LOADI32_EAX B8 DEFINE LOADI32_EBX BB DEFINE LOADI32_ECX B9 DEFINE LOADI32_EDI BF DEFINE LOADI32_EDX BA DEFINE LOADI32_ESI BE DEFINE MOVZX_al 0FB6C0 DEFINE MOVZX_bl 0FB6DB DEFINE MOVZX_cl 0FB6C9 DEFINE NULL 00000000 DEFINE POP_EAX 58 DEFINE POP_EBP 5D DEFINE POP_EBX 5B DEFINE POP_ECX 59 DEFINE POP_EDI 5F DEFINE POP_EDX 5A DEFINE POP_ESI 5E DEFINE PUSH_EAX 50 DEFINE PUSH_EBP 55 DEFINE PUSH_EBX 53 DEFINE PUSH_ECX 51 DEFINE PUSH_EDI 57 DEFINE PUSH_EDX 52 DEFINE PUSH_ESI 56 DEFINE RETURN C3 DEFINE SALI8_EAX C1E0 DEFINE SHRI8_EAX C1E8 DEFINE SHRI8_EBX C1EB DEFINE STORE32_Absolute32_eax A3 DEFINE STORE32_Absolute32_ebx 891D DEFINE STORE32_Absolute32_ecx 890D DEFINE STORE32_Absolute32_edx 8915 DEFINE STORE32_EAX_into_EBP_Immediate8 8945 DEFINE STORE32_EAX_into_EBX 8903 DEFINE STORE32_EAX_into_ECX_Immediate8 8941 DEFINE STORE32_EAX_into_EDX 8902 DEFINE STORE32_EAX_into_EDX_Immediate8 8942 DEFINE STORE32_EBP_into_EDX_Immediate8 896A DEFINE STORE32_EBX_into_EAX 8918 DEFINE STORE32_EBX_into_EAX_Immediate8 8958 DEFINE STORE32_EBX_into_EDX_Immediate8 895A DEFINE STORE32_ECX_into_EAX 8908 DEFINE STORE32_ECX_into_EAX_Immediate8 8948 DEFINE STORE32_ECX_into_EDX_Immediate8 894A DEFINE STORE32_EDX_into_EAX_Immediate8 8950 DEFINE STORE32_EDX_into_EBP_Immediate8 8955 DEFINE STORE32_ESI_into_EBP_Immedate8 8975 DEFINE STORE32_ESI_into_EDX_Immedate8 8972 DEFINE STORE8_al_into_Address_EBX 8803 DEFINE STORE8_al_into_Address_ECX 8801 DEFINE STORE8_al_into_Address_ESI 8806 DEFINE STORE8_bl_into_Address_ECX 8819 DEFINE SUBI8_EAX 83E8 DEFINE SUBI8_ECX 83E9 DEFINE SUBI8_ESI 83EE DEFINE SWAP_EAX_EBX 93 ;; Register usage: ;; EAX => Temps ;; Struct TYPE format: (size 28) ;; NEXT => 0 ;; SIZE => 4 ;; OFFSET => 8 ;; INDIRECT => 12 ;; MEMBERS => 16 ;; TYPE => 20 ;; NAME => 24 ;; Struct TOKEN_LIST format: (size 20) ;; NEXT => 0 ;; LOCALS/PREV => 4 ;; S => 8 ;; TYPE => 12 ;; ARGS/DEPTH => 16 ; Where the ELF Header is going to hit ; Simply jump to _start ; Our main function :_start POP_EAX ; Get the number of arguments POP_EBX ; Get the program name POP_EBX ; Get the actual input name LOADI32_ECX %0 ; prepare read_only LOADI32_EAX %5 ; the syscall number for open() INT_80 ; Now open that damn file STORE32_Absolute32_eax &Input_file ; Preserve the file pointer we were given POP_EBX ; Get the actual output name LOADI32_ECX %577 ; Prepare file as O_WRONLY|O_CREAT|O_TRUNC LOADI32_EDX %384 ; Prepare file as RW for owner only (600 in octal) LOADI32_EAX %5 ; the syscall number for open() INT_80 ; Now open that damn file CMPI8_EAX !0 ; Check for missing output JG32 %_start_out ; Have real input LOADI32_EAX %1 ; Use stdout :_start_out STORE32_Absolute32_eax &Output_file ; Preserve the file pointer we were given LOADI32_EAX %45 ; the Syscall # for SYS_BRK LOADI32_EBX %0 ; Get current brk INT_80 ; Let the kernel do the work STORE32_Absolute32_eax &MALLOC ; Set our malloc pointer LOADI32_EAX %0 ; HEAD = NULL CALL32 %read_all_tokens ; Read all tokens CALL32 %Reverse_List ; Reverse order ; CALL32 %debug_list ; Try to figure out what is wrong STORE32_Absolute32_eax &global_token ; Set global_token CALL32 %program ; Convert into program LOADI32_EAX &header_string1 ; Our header string CALL32 %File_Print ; Print it LOAD32_Absolute32_eax &output_list ; Our output_list CALL32 %recursive_output ; Print core program ; LOADI32_EAX &header_string2 ; Our Enable debug ; CALL32 %File_Print ; Print it LOADI32_EAX &header_string3 ; Our second label CALL32 %File_Print ; Print it LOAD32_Absolute32_eax &globals_list ; Our globals CALL32 %recursive_output ; Get them LOADI32_EAX &header_string4 ; Our final header CALL32 %File_Print ; Print it LOAD32_Absolute32_eax &strings_list ; Our strings CALL32 %recursive_output ; Get them LOADI32_EAX &header_string5 ; Make this a bare assembly CALL32 %File_Print ; Print it :Done ; program completed Successfully LOADI32_EBX %0 ; All is well LOADI32_EAX %1 ; put the exit syscall number in eax INT_80 ; Call it a good day :header_string1 " # Core program " :header_string2 " :ELF_data " :header_string3 " # Program global variables " :header_string4 " # Program strings " :header_string5 " :ELF_end " ;; read_all_tokens function ;; Receives Token_List* in EAX ;; Tokenizes all input and returns updated list in EAX ;; Returns TOKEN in EAX ;; Uses EAX for C :read_all_tokens STORE32_Absolute32_eax &Token CALL32 %fgetc :read_all_tokens_loop CMPI8_EAX !-4 ; Check for EOF JE32 %read_all_tokens_done ; Stop if found CALL32 %get_token ; Read all tokens JMP32 %read_all_tokens_loop ; Loop :read_all_tokens_done LOAD32_Absolute32_eax &Token RETURN ;; get_token function ;; Receives INT in EAX ;; Makes a list of TOKEN_LIST ;; C and STRING_INDEX are stored in memory, ECX is used for S and EDX is used for current ;; Returns C in EAX :get_token PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX PUSH_EDX ; Protect EDX STORE32_Absolute32_eax &C ; Set C LOADI32_EAX %20 ; Malloc CURRENT CALL32 %malloc ; Get Pointer COPY_EAX_to_EDX ; Set CURRENT LOADI32_EAX %256 ; Malloc the string CALL32 %malloc ; Get pointer to S COPY_EAX_to_ECX ; Set S STORE32_ECX_into_EDX_Immediate8 !8 ; CURRENT->S = S :reset STORE32_Absolute32_ecx &string_index ; S[0] LOAD32_Absolute32_eax &C ; Using C CALL32 %clear_white_space ; Clear WhiteSpace STORE32_Absolute32_eax &C ; Set C CMPI8_EAX !-4 ; Check for EOF JE32 %get_token_abort ; if EOF abort CMPI8_EAX !35 ; Check for '#' JNE32 %get_token_alpha ; Nope ;; Deal with # line comments CALL32 %purge_macro ; Let it handle it STORE32_Absolute32_eax &C ; Set C JMP32 %reset ; Try again :get_token_alpha LOAD32_Absolute32_eax &C ; Send C LOADI32_EBX &alphas ; Get alphanumerics CALL32 %In_Set ; See if in set CMPI8_EAX !1 ; IF TRUE JNE32 %get_token_symbol ; Otherwise ;; Store keywords LOAD32_Absolute32_eax &C ; Send C CALL32 %preserve_keyword ; Store STORE32_Absolute32_eax &C ; Set C JMP32 %get_token_done ; Be done with this token :get_token_symbol LOAD32_Absolute32_eax &C ; Send C LOADI32_EBX &symbols ; Get symbols CALL32 %In_Set ; See if in set CMPI8_EAX !1 ; IF TRUE JNE32 %get_token_strings ; Otherwise ;; Store symbols LOAD32_Absolute32_eax &C ; Send C CALL32 %preserve_symbol ; Store STORE32_Absolute32_eax &C ; Set C JMP32 %get_token_done ; Be done with this token :get_token_strings LOAD32_Absolute32_eax &C ; Send C LOADI32_EBX &strings ; Get strings CALL32 %In_Set ; See if in set CMPI8_EAX !1 ; IF TRUE JNE32 %get_token_comment ; Otherwise ;; Store String LOAD32_Absolute32_eax &C ; Send C CALL32 %consume_word ; Store STORE32_Absolute32_eax &C ; Set C JMP32 %get_token_done ; Be done with this token :get_token_comment LOAD32_Absolute32_eax &C ; Send C CMPI8_EAX !47 ; IF '/' == C JNE32 %get_token_else ; Otherwise CALL32 %consume_byte ; Hope it just is '/' STORE32_Absolute32_eax &C ; Set C CMPI8_EAX !42 ; IF '*' we have '/*' JNE32 %get_token_comment_line ; Check for '//' ;; Deal with /* block comments */ CALL32 %fgetc ; get next C STORE32_Absolute32_eax &C ; Set C :get_token_comment_block_outer LOAD32_Absolute32_eax &C ; Using C CMPI8_EAX !47 ; IF '/' != C JE32 %get_token_comment_block_done ; be done :get_token_comment_block_inner LOAD32_Absolute32_eax &C ; Using C CMPI8_EAX !42 ; IF '*' != C JE32 %get_token_comment_block_iter ; jump over ;; Deal with inner loop CALL32 %fgetc ; get next C STORE32_Absolute32_eax &C ; Set C JMP32 %get_token_comment_block_inner ; keep going :get_token_comment_block_iter CALL32 %fgetc ; get next C STORE32_Absolute32_eax &C ; Set C JMP32 %get_token_comment_block_outer :get_token_comment_block_done CALL32 %fgetc ; get next C STORE32_Absolute32_eax &C ; Set C JMP32 %reset ; throw away, try again :get_token_comment_line CMPI8_EAX !47 ; IF '/' we have // JNE32 %get_token_done ; keep if just '/' ;; Deal with // line comment CALL32 %fgetc ; drop to match STORE32_Absolute32_eax &C ; Set C JMP32 %reset ; throw away, try again :get_token_else LOAD32_Absolute32_eax &C ; Send C CALL32 %consume_byte STORE32_Absolute32_eax &C ; Set C :get_token_done LOAD32_Absolute32_eax &Token ; TOKEN STORE32_EAX_into_EDX_Immediate8 !4 ; CURRENT->PREV = TOKEN STORE32_EAX_into_EDX ; CURRENT->NEXT = TOKEN STORE32_Absolute32_edx &Token ; TOKEN = CURRENT :get_token_abort POP_EDX ; Restore EDX POP_ECX ; Restore ECX POP_EBX ; Restore EBX LOAD32_Absolute32_eax &C ; Return C RETURN ;; Malloc isn't actually required if the program being built fits in the initial memory ;; However, it doesn't take much to add it. ;; Requires [MALLOC] to be initialized and EAX to have the number of desired bytes :malloc PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX PUSH_EDX ; Protect EDX LOAD32_Absolute32_ebx &MALLOC ; Using the current pointer ADD_eax_into_ebx ; Request the number of desired bytes LOADI32_EAX %45 ; the Syscall # for SYS_BRK INT_80 ; call the Kernel LOAD32_Absolute32_eax &MALLOC ; Return pointer STORE32_Absolute32_ebx &MALLOC ; Update pointer POP_EDX ; Restore EDX POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; clear_white_space function ;; Receives INT C in EAX ;; Returns first non-whitespace char in EAX :clear_white_space CMPI8_EAX !32 ; Check for ' ' JE32 %clear_white_space_wipe ; wipe it out CMPI8_EAX !10 ; Check for '\n' JE32 %clear_white_space_wipe ; wipe it output CMPI8_EAX !9 ; Check for '\t' JNE32 %clear_white_space_done ; looks like non-whitespace :clear_white_space_wipe CALL32 %fgetc ; Read a new byte CMPI8_EAX !-4 ; Check for EOF JE32 %clear_white_space_done ; Short circuit JMP32 %clear_white_space ; iterate :clear_white_space_done RETURN ;; In_Set function ;; Receives Char C in EAX and CHAR* in EBX ;; Returns 1 if true, zero if false in EAX :In_Set PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX :In_Set_loop LOAD8_cl_from_EBX ; Read char MOVZX_cl ; Zero extend it CMP_EAX_ECX ; See if they match JE32 %In_Set_True ; return true CMPI8_ECX !0 ; Check for NULL JE32 %In_Set_False ; return false ADDI8_EBX !1 ; s = s + 1 JMP32 %In_Set_loop ; Keep looping :In_Set_True LOADI32_EAX %1 ; Set True POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :In_Set_False LOADI32_EAX %0 ; Set FALSE POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :alphas "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" :symbols "<=>|&!-" :strings '22 27 00' ;; purge_macro function ;; Receives CH in EAX ;; Reads chars until Line feed is read ;; returns line feed :purge_macro CALL32 %fgetc ; read next char CMPI8_EAX !10 ; Check for '\n' JNE32 %purge_macro ; Keep going RETURN ;; preserve_keyword function ;; Receives INT C in EAX ;; collects all chars in keyword ;; Returns C in EAX ;; Uses ECX for INT C :preserve_keyword PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX COPY_EAX_to_ECX ; Setup C LOADI32_EBX &alphas ; Concerning ourselves with "abc.." :preserve_keyword_loop CALL32 %In_Set ; Check if alphanumerics CMPI8_EAX !1 ; IF TRUE JNE32 %preserve_keyword_label ; Otherwise check for label COPY_ECX_to_EAX ; Pass C CALL32 %consume_byte ; consume that byte COPY_EAX_to_ECX ; Update C JMP32 %preserve_keyword_loop ; keep looping :preserve_keyword_label COPY_ECX_to_EAX ; Fix return CMPI8_EAX !58 ; Check for ':' JNE32 %preserve_keyword_done ; be done ;; Fix our goto label CALL32 %fixup_label ; Fix the label LOADI32_EAX %32 ; Return Whitespace :preserve_keyword_done POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; preserve_symbol function ;; Receives INT C in EAX ;; collects all chars in symbol ;; Returns C in EAX ;; Uses ECX for INT C :preserve_symbol PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX COPY_EAX_to_ECX ; Setup C LOADI32_EBX &symbols ; Concerning ourselves with "<=>.." :preserve_symbol_loop CALL32 %In_Set ; Check if symbol CMPI8_EAX !1 ; IF TRUE JNE32 %preserve_symbol_done ; Otherwise be done COPY_ECX_to_EAX ; Pass C CALL32 %consume_byte ; consume that byte COPY_EAX_to_ECX ; Update C JMP32 %preserve_symbol_loop ; keep looping :preserve_symbol_done COPY_ECX_to_EAX ; Fix return POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; consume_word function ;; receives INT C in EAX ;; returns INT C in EAX ;; Uses EAX for C, EBX for FREQ and ECX for ESCAPE :consume_word PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX COPY_EAX_to_EBX ; FREQ = C LOADI32_ECX %0 ; ESCAPE = FALSE :consume_word_loop CMPI8_ECX !0 ; IF !ESCAPE JNE32 %consume_word_escape ; Enable escape CMPI8_EAX !92 ; if '\\' JNE32 %consume_word_iter ; keep state LOADI32_ECX %1 ; ESCAPE = TRUE JMP32 %consume_word_iter ; keep going :consume_word_escape LOADI32_ECX %0 ; ESCAPE = FALSE :consume_word_iter CALL32 %consume_byte ; read next char CMPI8_ECX !0 ; IF ESCAPE JNE32 %consume_word_loop ; keep looping CMP_EAX_EBX ; IF C != FREQ JNE32 %consume_word_loop ; keep going CALL32 %fgetc ; return next char POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; consume_byte function ;; Receives INT C in EAX ;; Inserts C into string S, updates String S ;; Returns Next char in EAX :consume_byte PUSH_EBX ; Protect EBX LOAD32_Absolute32_ebx &string_index ; S[0] STORE8_al_into_Address_EBX ; S[0] = C ADDI8_EBX !1 ; S = S + 1 STORE32_Absolute32_ebx &string_index ; Update S CALL32 %fgetc POP_EBX ; Restore EBX RETURN ;; fixup_label function ;; Receives S in ECX ;; prepends ':' to string and returns registers unchanged ;; Uses EAX for HOLD, EBX for PREV and ECX for S[0] :fixup_label PUSH_EAX ; Protect EAX PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX LOADI32_EAX %58 ; HOLD = ':' LOAD32_ECX_from_EDX_Immediate8 !8 ; HOLD_STRING[0] :fixup_label_loop COPY_EAX_to_EBX ; PREV = HOLD LOAD8_al_from_ECX ; HOLD = HOLD_STRING[I] MOVZX_al ; make useful STORE8_bl_into_Address_ECX ; HOLD_STRING[I] = PREV ADDI8_ECX !1 ; I = I + 1 CMPI8_EAX !0 ; IF NULL == HOLD JNE32 %fixup_label_loop ; Keep looping POP_ECX ; Restore ECX POP_EBX ; Restore EBX POP_EAX ; Restore EAX RETURN ;; fgetc function ;; Loads FILE* from [INPUT_FILE] ;; Returns -4 (EOF) or char in EAX :fgetc PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX PUSH_EDX ; Protect EDX LOADI32_EAX %-4 ; Put EOF in eax PUSH_EAX ; Assume bad (If nothing read, value will remain EOF) LEA32_ECX_from_esp ; Get stack address LOAD32_Absolute32_ebx &Input_file ; Where are we reading from LOADI32_EAX %3 ; the syscall number for read LOADI32_EDX %1 ; set the size of chars we want INT_80 ; call the Kernel POP_EAX ; Get either char or EOF CMPI8_EAX !-4 ; Check for EOF JE32 %fgetc_done ; Return as is MOVZX_al ; Make it useful :fgetc_done POP_EDX ; Restore EDX POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; Reverse_List function ;; Receives List in EAX ;; Returns the list reversed in EAX :Reverse_List PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX COPY_EAX_to_EBX ; Set HEAD LOADI32_EAX %0 ; ROOT = NULL :Reverse_List_Loop CMPI8_EBX !0 ; WHILE HEAD != NULL JE32 %Reverse_List_Done ; Stop otherwise LOAD32_ECX_from_EBX ; NEXT = HEAD->NEXT STORE32_EAX_into_EBX ; HEAD->NEXT = ROOT COPY_EBX_to_EAX ; ROOT = HEAD COPY_ECX_to_EBX ; HEAD = NEXT JMP32 %Reverse_List_Loop ; Keep Going :Reverse_List_Done POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; recursive_output function ;; Receives list in EAX ;; walks the list and prints the I->S for all nodes backwards ;; Uses EBX for I :recursive_output PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX CMPI8_EAX !0 ; Check for NULL JE32 %recursive_output_done ; Skip the work COPY_EAX_to_EBX ; I = Head LOAD32_EAX_from_EBX ; Iterate to next Token CALL32 %recursive_output ; Recurse LOAD32_EAX_from_EBX_Immediate8 !8 ; Using S CALL32 %File_Print ; Print it :recursive_output_done POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; File_Print function ;; Receives CHAR* in EAX ;; calls fputc for every non-null char :File_Print PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX COPY_EAX_to_EBX ; Protect S CMPI8_EAX !0 ; Protect against nulls JE32 %File_Print_Done ; Simply don't try to print them :File_Print_Loop LOAD8_al_from_EBX ; Read byte MOVZX_al ; zero extend CMPI8_EAX !0 ; Check for NULL JE32 %File_Print_Done ; Stop at NULL CALL32 %fputc ; write it ADDI8_EBX !1 ; S = S + 1 JMP32 %File_Print_Loop ; Keep going :File_Print_Done POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; fputc function ;; receives CHAR in EAX and load FILE* from [OUTPUT_FILE] ;; writes char and returns :fputc PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX PUSH_EDX ; Protect EDX PUSH_EAX ; We are writing eax LEA32_ECX_from_esp ; Get stack address LOAD32_Absolute32_ebx &Output_file ; Write to target file LOADI32_EAX %4 ; the syscall number for write LOADI32_EDX %1 ; set the size of chars we want INT_80 ; call the Kernel POP_EAX ; Restore stack POP_EDX ; Restore EDX POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; program function ;; receives nothing, returns nothing ;; Uses EAX for type_size :program ;; The binary initialized the globals to null, so we can skip those steps PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX :new_type LOAD32_Absolute32_eax &global_token ; Using global_token CMPI8_EAX !0 ; Check if NULL JE32 %program_done ; Be done if null LOAD32_EBX_from_EAX_Immediate8 !8 ; GLOBAL_TOKEN->S LOADI32_EAX &constant ; "CONSTANT" CALL32 %match ; IF GLOBAL_TOKEN->S == "CONSTANT" CMPI8_EAX !0 ; If true JNE32 %program_else ; Looks like not a constant ;; Deal with minimal constant case LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->next STORE32_Absolute32_eax &global_token ; global_token = global_token->next LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S LOADI32_EBX %0 ; NULL LOAD32_Absolute32_ecx &global_constant_list ; global_constant_list CALL32 %sym_declare ; Declare that constant STORE32_Absolute32_eax &global_constant_list ; global_constant_list = sym_declare(global_token->s, NULL, global_constant_list); LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX ; global_token->next STORE32_EBX_into_EAX_Immediate8 !16 ; global_constant_list->arguments = global_token->next LOAD32_EBX_from_EBX ; global_token->next->next STORE32_Absolute32_ebx &global_token ; global_token = global_token->next->next JMP32 %new_type ; go around again :program_else CALL32 %type_name ; Figure out the type_size CMPI8_EAX !0 ; IF NULL == type_size JE32 %new_type ; it was a new type ;; Add to global symbol table COPY_EAX_to_EBX ; put type_size in the right spot LOAD32_Absolute32_eax &global_token ; Using global token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S LOAD32_Absolute32_ecx &global_symbol_list ; Using global_symbol_list CALL32 %sym_declare ; Declare symbol STORE32_Absolute32_eax &global_symbol_list ; global_symbol_list = sym_declare(global_token->s, type_size, global_symbol_list); LOAD32_Absolute32_ebx &global_token ; Using global token LOAD32_EBX_from_EBX ; global_token->next STORE32_Absolute32_ebx &global_token ; global_token = global_token->next LOAD32_Absolute32_ebx &global_token ; Using global token LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->S LOADI32_EAX &semicolon ; ";" CALL32 %match ; if(match(";", global_token->s)) CMPI8_EAX !0 ; If true JNE32 %program_function ; looks like not a match ;; Deal with the global variable LOAD32_Absolute32_ebx &globals_list ; Using globals_list LOADI32_EAX &program_string_0 ; ":GLOBAL_" CALL32 %emit ; Emit it COPY_EAX_to_EBX ; update globals_list LOAD32_Absolute32_eax &global_token ; Using global token LOAD32_EAX_from_EAX_Immediate8 !4 ; global token->prev LOAD32_EAX_from_EAX_Immediate8 !8 ; global token->prev->s CALL32 %emit ; Emit it COPY_EAX_to_EBX ; update globals_list LOADI32_EAX &program_string_1 ; "\nNULL\n" CALL32 %emit ; Emit it STORE32_Absolute32_eax &globals_list ; update globals_list LOAD32_Absolute32_eax &global_token ; Using global token LOAD32_EAX_from_EAX ; global_token->next STORE32_Absolute32_eax &global_token ; global_token = global_token->next JMP32 %new_type ; go around again :program_function LOAD32_Absolute32_ebx &global_token ; Using global token LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->S LOADI32_EAX &open_paren ; "(" CALL32 %match ; if(match(";", global_token->s)) CMPI8_EAX !0 ; If true JNE32 %program_error ; Otherwise deal with error case ;; Deal with function definition CALL32 %declare_function ; Lets get the parsing rolling JMP32 %new_type ; Keep looping through functions :program_error ;; Deal with the case of something we don't support ;; NOT IMPLEMENTED :program_done POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; Strings needed by the program function :program_string_0 ":GLOBAL_" :program_string_1 " NULL " ;; declare_function function ;; Receives nothing and returns nothing ;; Sets current function and adds it to the global function list :declare_function PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX LOADI32_EAX %0 ; Using NULL STORE32_Absolute32_eax ¤t_count ; current_count = 0 LOAD32_Absolute32_eax &global_token ; Using global token LOAD32_EAX_from_EAX_Immediate8 !4 ; global token->prev LOAD32_EAX_from_EAX_Immediate8 !8 ; global token->prev->s LOADI32_EBX %0 ; NULL LOAD32_Absolute32_ecx &global_function_list ; global_function_list CALL32 %sym_declare ; sym_declare(global_token->prev->s, NULL, global_function_list); STORE32_Absolute32_eax &function ; function = sym_declare(global_token->prev->s, NULL, global_function_list); STORE32_Absolute32_eax &global_function_list ; global_function_list = function CALL32 %collect_arguments ; collect all of the function arguments LOAD32_Absolute32_eax &global_token ; Using global token LOAD32_EAX_from_EAX_Immediate8 !8 ; global token->s LOADI32_EBX &semicolon ; ";" CALL32 %match ; IF global token->s == ";" CMPI8_EAX !0 ; If true JNE32 %declare_function_full ; It was a prototype ;; Deal with prototypes LOAD32_Absolute32_eax &global_token ; Using global token LOAD32_EAX_from_EAX ; global token->next STORE32_Absolute32_eax &global_token ; global token = global token->next JMP32 %declare_function_done ; Move on :declare_function_full ;; Deal with full function definitions LOADI32_EAX &declare_function_string_0 ; "# Defining function " CALL32 %emit_out ; emit it LOAD32_Absolute32_eax &function ; function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->s CALL32 %emit_out ; emit it LOADI32_EAX &declare_function_string_1 ; "\n:FUNCTION_" CALL32 %emit_out ; emit it LOAD32_Absolute32_eax &function ; function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->s CALL32 %emit_out ; emit it LOADI32_EAX &declare_function_string_3 ; "\n" CALL32 %emit_out ; emit it CALL32 %statement ; Recursively get the function pieces LOAD32_Absolute32_eax &output_list ; output LOAD32_EAX_from_EAX_Immediate8 !8 ; output->s LOADI32_EBX &declare_function_string_2 ; "ret\n" CALL32 %match ; IF output->s == "ret\n" CMPI8_EAX !0 ; If true we can skip adding it JE32 %declare_function_done ; otherwise we need to add it ;; Add the return to the end of a function lacking a return; LOADI32_EAX &declare_function_string_2 ; "ret\n" CALL32 %emit_out ; emit it :declare_function_done POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :declare_function_string_0 "# Defining function " :declare_function_string_1 " :FUNCTION_" :declare_function_string_2 "ret " :declare_function_string_3 " " ;; collect_arguments function ;; Receives nothing ;; Returns Nothing ;; Adds arguments to the function definition ;; holds struct type* type_size in ECX, then replace with struct token_list* a in ECX when type_size is used :collect_arguments PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->next STORE32_Absolute32_eax &global_token ; global_token = global_token->next :collect_arguments_loop LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->S LOADI32_EAX &close_paren ; ")" CALL32 %match ; IF global_token->S == ")" CMPI8_EAX !0 ; we reached the end JE32 %collect_arguments_done ; be done ;; deal with the case of there are arguments CALL32 %type_name ; Get the type COPY_EAX_to_ECX ; put type_size safely out of the way LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->S LOADI32_EAX &close_paren ; ")" CALL32 %match ; IF global_token->S == ")" CMPI8_EAX !0 ; is a foo(int, char,void) case JE32 %collect_arguments_common ; deal with commas ;; Trying second else LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->S LOADI32_EAX &comma ; "," CALL32 %match ; IF global_token->S == "," CMPI8_EAX !0 ; then deal with the common JE32 %collect_arguments_common ; case of commas between arguments ;; deal with foo(int a, char b) LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S COPY_ECX_to_EBX ; put type_size in the right place LOAD32_Absolute32_ecx &function ; Using function LOAD32_ECX_from_ECX_Immediate8 !16 ; function->args CALL32 %sym_declare ; sym_declare(global_token->s, type_size, function->arguments); COPY_EAX_to_ECX ; put a in a safe place LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !16 ; function->args CMPI8_EAX !0 ; IF function->args == NULL JNE32 %collect_arguments_another ; otherwise it isn't the first ;; Deal with the case of first argument in the function LOADI32_EAX %-4 ; -4 STORE32_EAX_into_ECX_Immediate8 !16 ; a->depth = -4 JMP32 %collect_arguments_next ; get to next :collect_arguments_another ;; deal with the case of non-first arguments LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !16 ; function->args LOAD32_EAX_from_EAX_Immediate8 !16 ; function->args->depth SUBI8_EAX !4 ; function->args->depth - 4 STORE32_EAX_into_ECX_Immediate8 !16 ; a->depth = function->args->depth - 4 :collect_arguments_next LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->next STORE32_Absolute32_eax &global_token ; global_token = global_token->next LOAD32_Absolute32_eax &function ; Using function STORE32_ECX_into_EAX_Immediate8 !16 ; function->args = a :collect_arguments_common LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->S LOADI32_EAX &comma ; "," CALL32 %match ; IF global_token->S == "," CMPI8_EAX !0 ; then deal with the comma JNE32 %collect_arguments_loop ; otherwise loop ;; keep foo(bar(), 1) expressions working LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->next STORE32_Absolute32_eax &global_token ; global_token = global_token->next JMP32 %collect_arguments_loop ; keep going :collect_arguments_done LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->next STORE32_Absolute32_eax &global_token ; global_token = global_token->next POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; statement function ;; Receives nothing ;; Returns nothing ;; Walks down global_token recursively to collect the contents of the function :statement PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->S LOADI32_EAX &open_curly_brace ; "{" CALL32 %match ; IF global_token->S == "{" JNE32 %statement_label ; otherwise try label ;; deal with { statement } CALL32 %recursive_statement ; Statements inside of statements for days JMP32 %statement_done ; Be done :statement_label LOAD8_al_from_EBX ; global_token->S[0] MOVZX_al ; make it useful CMPI8_EAX !58 ; IF global_token->S == ':' JNE32 %statement_local ; otherwise try locals ;; deal with labels COPY_EBX_to_EAX ; put global_token->S in the right spot CALL32 %emit_out ; emit it LOADI32_EAX &statement_string_0 ; Using "\t#C goto label\n" CALL32 %emit_out ; emit it LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->next STORE32_Absolute32_eax &global_token ; global_token = global_token->next JMP32 %statement_done ; be done :statement_local COPY_EBX_to_EAX ; put global_token->S in the right place LOADI32_EBX &prim_types ; pointer to primative types CALL32 %lookup_type ; See if found CMPI8_EAX !0 ; IF NULL == lookup_type(global_token->S, prim_types) JNE32 %statement_local_success ; Sweet a new local ;; Second chance LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->S LOADI32_EAX &struct ; "struct" CALL32 %match ; IF global_token->S == "struct" CMPI8_EAX !0 ; then we are a local JNE32 %statement_if ; otherwise try IF :statement_local_success CALL32 %collect_local ; Grab those locals JMP32 %statement_done ; be done :statement_if LOADI32_EAX &if_string ; Using "if" CALL32 %match ; IF global_token->S == "if" CMPI8_EAX !0 ; then we have an if statement JNE32 %statement_do ; otherwise try DO ;; Deal with IF statement CALL32 %process_if ; DO IT JMP32 %statement_done ; be done :statement_do LOADI32_EAX &do_string ; Using "do" CALL32 %match ; IF global_token->S == "do" CMPI8_EAX !0 ; then we have a do statement JNE32 %statement_while ; otherwise try WHILE ;; Deal with DO statement CALL32 %process_do ; DO IT JMP32 %statement_done ; be done :statement_while LOADI32_EAX &while_string ; Using "while" CALL32 %match ; IF global_token->S == "while" CMPI8_EAX !0 ; then we have a while statement JNE32 %statement_for ; otherwise try FOR ;; Deal with WHILE statement CALL32 %process_while ; DO IT JMP32 %statement_done ; be done :statement_for LOADI32_EAX &for_string ; Using "for" CALL32 %match ; IF global_token->S == "for" CMPI8_EAX !0 ; then we have a for statement JNE32 %statement_asm ; otherwise try ASM ;; Deal with FOR statement CALL32 %process_for ; DO IT JMP32 %statement_done ; be done :statement_asm LOADI32_EAX &asm_string ; Using "asm" CALL32 %match ; IF global_token->S == "asm" CMPI8_EAX !0 ; then we have an asm statement JNE32 %statement_goto ; otherwise try GOTO ;; Deal with ASM statement CALL32 %process_asm ; Hit it JMP32 %statement_done ; be done :statement_goto LOADI32_EAX &goto_string ; Using "goto" CALL32 %match ; IF global_token->S == "goto" CMPI8_EAX !0 ; then we have a goto statement JNE32 %statement_return ; Otherwise try RETURN ;; Deal with GOTO statement LOADI32_EAX &statement_string_1 ; Using "jmp %" CALL32 %emit_out ; emit it LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->next STORE32_Absolute32_eax &global_token ; global_token = global_token->next LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S CALL32 %emit_out ; emit it LOADI32_EAX &statement_string_2 ; Using "\n" CALL32 %emit_out ; emit it LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->next STORE32_Absolute32_eax &global_token ; global_token = global_token->next LOADI32_EAX &statement_string_4 ; Using "ERROR in statement\nMissing ;\n" LOADI32_EBX &semicolon ; Using ";" CALL32 %require_match ; Make sure it has the required JMP32 %statement_done ; Be done :statement_return LOADI32_EAX &return_string ; Using "return" CALL32 %match ; IF global_token->S == "return" CMPI8_EAX !0 ; then we have a return statement JNE32 %statement_break ; Otherwise try BREAK ;; Deal with RETURN Statement CALL32 %return_result ; Return anything they want JMP32 %statement_done ; be done :statement_break LOADI32_EAX &break_string ; Using "break" CALL32 %match ; IF global_token->S == "break" CMPI8_EAX !0 ; then we have a break statement JNE32 %statement_continue ; Otherwise try CONTINUE ;; Deal with BREAK statement CALL32 %process_break ; Lets do some damage JMP32 %statement_done ; be done :statement_continue LOADI32_EAX &continue_string ; Using "continue" CALL32 %match ; IF global_token->S == "continue" CMPI8_EAX !0 ; then we have a continue statement JNE32 %statement_else ; Otherwise we are punting to an expression ;; Deal with CONTINUE statement LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->next STORE32_Absolute32_eax &global_token ; global_token = global_token->next LOADI32_EAX &statement_string_3 ; Using "\n#continue statement\n" CALL32 %emit_out ; emit it LOADI32_EAX &statement_string_4 ; Using "ERROR in statement\nMissing ;\n" LOADI32_EBX &semicolon ; Using ";" CALL32 %require_match ; Don't forget the ";" JMP32 %statement_done ; Be done :statement_else CALL32 %expression ; Collect expression LOADI32_EAX &statement_string_4 ; Using "ERROR in statement\nMissing ;\n" LOADI32_EBX &semicolon ; Using ";" CALL32 %require_match ; make sure we have it :statement_done POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :statement_string_0 " #C goto label " :statement_string_1 "jmp %" :statement_string_2 " " :statement_string_3 " #continue statement " :statement_string_4 "ERROR in statement Missing ; " ;; recursive_statement function ;; Receives nothing ;; Returns nothing ;; Walks the global_token list to build the contents of statements ;; Uses struct token_list* frame in ECX :recursive_statement PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->next STORE32_Absolute32_eax &global_token ; global_token = global_token->next LOAD32_Absolute32_ecx &function ; Using function LOAD32_ECX_from_ECX_Immediate8 !4 ; frame = function->locals :recursive_statement_loop LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->S LOADI32_EAX &close_curly_brace ; Using "}" CALL32 %match ; IF global_token->S == "}" CMPI8_EAX !0 ; Then we are done recursing JE32 %recursive_statement_cleanup ; and then we clean up ;; Deal with the recursive calls CALL32 %statement ; Deal with another statement JMP32 %recursive_statement_loop ; loop some more :recursive_statement_cleanup LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->next STORE32_Absolute32_eax &global_token ; global_token = global_token->next LOADI32_EAX &recursive_statement_string_0 ; Using "ret\n" LOAD32_Absolute32_ebx &output_list ; Using output LOAD32_EBX_from_EBX_Immediate8 !8 ; output->S CALL32 %match ; IF output->S == "ret\n" CMPI8_EAX !0 ; Then we can skip the clean up JE32 %recursive_statement_done ; and be done ;; Deal with cleanup LOAD32_Absolute32_ebx &function ; Using function LOAD32_EBX_from_EBX_Immediate8 !4 ; i = function->locals LOADI32_EAX &recursive_statement_string_1 ; Using "pop_ebx\t# _recursive_statement_locals\n" :recursive_statement_locals CMP_ECX_EBX ; IF frame != i JE32 %recursive_statement_done ; Otherwise be done ;; Lets emit CALL32 %emit_out ; emit it LOAD32_EBX_from_EBX ; i = i->next JMP32 %recursive_statement_locals ; keep going :recursive_statement_done LOAD32_Absolute32_eax &function ; Using function STORE32_ECX_into_EAX_Immediate8 !4 ; function->locals = frame POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :recursive_statement_string_0 "ret " :recursive_statement_string_1 "pop_ebx # _recursive_statement_locals " ;; return_result function ;; Receives nothing ;; Returns nothing ;; Cleans up function and generates return ;; Also handles returning expressions :return_result PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->next STORE32_Absolute32_eax &global_token ; global_token = global_token->next LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S LOAD8_al_from_EAX ; global_token->S[0] MOVZX_al ; make it useful CMPI8_EAX !59 ; If global_token->S[0] == ';' JE32 %return_result_cleanup ; Go straight to cleanup CALL32 %expression ; get the expression we are returning :return_result_cleanup LOADI32_EAX &return_result_string_0 ; Using "ERROR in return_result\nMISSING ;\n" LOADI32_EBX &semicolon ; Using ";" CALL32 %require_match ; Make sure we have it LOAD32_Absolute32_ebx &function ; Using function LOAD32_EBX_from_EBX_Immediate8 !4 ; function->locals LOADI32_EAX &return_result_string_1 ; Using "pop_ebx\t# _return_result_locals\n" :return_result_locals CMPI8_EBX !0 ; IF NULL == i JE32 %return_result_done ; Be done CALL32 %emit_out ; Emit out pop LOAD32_EBX_from_EBX ; i = i->NEXT JMP32 %return_result_locals ; Keep going :return_result_done LOADI32_EAX &return_result_string_2 ; Using "ret\n" CALL32 %emit_out ; Emit it POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :return_result_string_0 "ERROR in return_result MISSING ; " :return_result_string_1 "pop_ebx # _return_result_locals " :return_result_string_2 "ret " ;; collect_local function ;; Receives nothing ;; Returns nothing ;; Walks global_token list to create function locals ;; Uses ECX for struct token_list* A :collect_local PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX CALL32 %type_name ; Get the local's type COPY_EAX_to_EBX ; Put struct type* type_size in the right place LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S LOAD32_Absolute32_ecx &function ; Using function LOAD32_ECX_from_ECX_Immediate8 !4 ; function->locals CALL32 %sym_declare ; Declare it COPY_EAX_to_ECX ; put it away safely ;; Try for main LOADI32_EAX &main_string ; Using "main" LOAD32_Absolute32_ebx &function ; Using function LOAD32_EBX_from_EBX_Immediate8 !8 ; function->S CALL32 %match ; IF match("main", function->s) CMPI8_EAX !0 ; possible JNE32 %collect_local_fresh ; try to see if fresh function ;; Ok we are in main, now to see if main is fresh LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !4 ; function->locals CMPI8_EAX !0 ; IF NULL == function->locals JNE32 %collect_local_fresh ; try to see if fresh function ;; Sweet we are in a fresh main LOADI32_EAX %-20 ; We start at -20 STORE32_EAX_into_ECX_Immediate8 !16 ; a->DEPTH = -20 JMP32 %collect_local_common ; Go to the commons :collect_local_fresh LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !16 ; function->args CMPI8_EAX !0 ; IF NULL == function->args JNE32 %collect_local_first ; Otherwise see if first LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !4 ; function->locals CMPI8_EAX !0 ; IF NULL == function->locals JNE32 %collect_local_first ; Otherwise try first ;; Sweet we are in a fresh function LOADI32_EAX %-8 ; We start at -8 STORE32_EAX_into_ECX_Immediate8 !16 ; a->DEPTH = -8 JMP32 %collect_local_common ; Go to the commons :collect_local_first LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !4 ; function->locals CMPI8_EAX !0 ; IF NULL == function->locals JNE32 %collect_local_else ; Looks like we are just another local ;; Ok we are the first local LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !16 ; function->args LOAD32_EAX_from_EAX_Immediate8 !16 ; function->args->depth SUBI8_EAX !8 ; function->arguments->depth - 8 STORE32_EAX_into_ECX_Immediate8 !16 ; a->DEPTH = function->arguments->depth - 8 JMP32 %collect_local_common ; Go to the commons :collect_local_else ;; Always the last to know LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !4 ; function->locals LOAD32_EAX_from_EAX_Immediate8 !16 ; function->locals->depth SUBI8_EAX !4 ; function->locals->depth - 4 STORE32_EAX_into_ECX_Immediate8 !16 ; a->DEPTH = function->locals->depth - 4 :collect_local_common LOAD32_Absolute32_eax &function ; Using function STORE32_ECX_into_EAX_Immediate8 !4 ; function->locals = a LOAD32_ECX_from_ECX_Immediate8 !8 ; a->S LOADI32_EAX &collect_local_string_0 ; Using "# Defining local " CALL32 %emit_out ; emit it LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S CALL32 %emit_out ; emit it LOADI32_EAX &collect_local_string_1 ; Using "\n" CALL32 %emit_out ; emit it LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT LOAD32_EBX_from_EAX_Immediate8 !8 ; global_token->S LOADI32_EAX &equal ; Using "=" CALL32 %match ; IF match("=", global_token->s) CMPI8_EAX !0 ; Deal with assignment JNE32 %collect_local_done ; Otherwise finish it ;; Deal with assignment LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT CALL32 %expression ; Recurse :collect_local_done LOADI32_EAX &collect_local_string_2 ; Using "ERROR in collect_local\nMissing ;\n" LOADI32_EBX &semicolon ; Using ";" CALL32 %require_match ; Make sure we have it LOADI32_EAX &collect_local_string_3 ; Using "push_eax\t#" CALL32 %emit_out ; emit it COPY_ECX_to_EAX ; put A->S where it belongs CALL32 %emit_out ; emit it LOADI32_EAX &collect_local_string_1 ; Using "\n" CALL32 %emit_out ; emit it POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :collect_local_string_0 "# Defining local " :collect_local_string_1 " " :collect_local_string_2 "ERROR in collect_local Missing ; " :collect_local_string_3 "push_eax #" ;; process_asm function ;; Receives nothing ;; Returns nothing ;; Simply inlines the asm statements ;; Uses EBX for global_token temp storage :process_asm PUSH_EBX ; Protect EBX LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT LOADI32_EAX &process_asm_string_0 ; Using "ERROR in process_asm\nMISSING (\n" LOADI32_EBX &open_paren ; Using "(" CALL32 %require_match ; Make sure we have it LOAD32_Absolute32_ebx &global_token ; Using global_token :process_asm_iter LOAD32_EAX_from_EBX_Immediate8 !8 ; global_token->S LOAD8_al_from_EAX ; global_token->S[0] MOVZX_al ; Make it useful CMPI8_EAX !34 ; IF global_token->S[0] == '\"' JNE32 %process_asm_done ; Otherwise be done LOAD32_EAX_from_EBX_Immediate8 !8 ; global_token->S ADDI8_EAX !1 ; global_token->S + 1 CALL32 %emit_out ; Emit it LOADI32_EAX &process_asm_string_1 ; Using "\n" CALL32 %emit_out ; Emit it LOAD32_EBX_from_EBX ; global_token->NEXT STORE32_Absolute32_ebx &global_token ; global_token = global_token->NEXT JMP32 %process_asm_iter ; keep going :process_asm_done LOADI32_EAX &process_asm_string_2 ; Using "ERROR in process_asm\nMISSING )\n" LOADI32_EBX &close_paren ; Using ")" CALL32 %require_match ; Make sure we have it LOADI32_EAX &process_asm_string_3 ; Using "ERROR in process_asm\nMISSING ;\n" LOADI32_EBX &semicolon ; Using ";" CALL32 %require_match ; Make sure we have it POP_EBX ; Restore EBX RETURN :process_asm_string_0 "ERROR in process_asm MISSING ( " :process_asm_string_1 " " :process_asm_string_2 "ERROR in process_asm MISSING ) " :process_asm_string_3 "ERROR in process_asm MISSING ; " ;; process_if function ;; Receives nothing ;; Returns Nothing ;; Increments current_count recurses into expression + statement ;; Uses ECX for char* NUMBER_STRING :process_if PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX LOAD32_Absolute32_eax ¤t_count ; Using current count COPY_EAX_to_EBX ; Preparing for update ADDI8_EBX !1 ; current_count + 1 STORE32_Absolute32_ebx ¤t_count ; current_count = current_count + 1 CALL32 %numerate_number ; convert to string COPY_EAX_to_ECX ; put NUMBER_STRING in place LOADI32_EAX &process_if_string_0 ; Using "# IF_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S COPY_ECX_to_EBX ; Passing NUMBER_STRING CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT LOADI32_EAX &process_if_string_1 ; Using "ERROR in process_if\nMISSING (\n" LOADI32_EBX &open_paren ; Using "(" CALL32 %require_match ; Make sure we have it CALL32 %expression ; Recurse to get the IF(...) part LOADI32_EAX &process_if_string_2 ; Using "test_eax,eax\nje %ELSE_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S COPY_ECX_to_EBX ; Passing NUMBER_STRING CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) LOADI32_EAX &process_if_string_3 ; Using "ERROR in process_if\nMISSING )\n" LOADI32_EBX &close_paren ; Using ")" CALL32 %require_match ; Make sure we have it CALL32 %statement ; Recursive to get the IF(){...} part LOADI32_EAX &process_if_string_4 ; Using "jmp %_END_IF_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S COPY_ECX_to_EBX ; Passing NUMBER_STRING CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) LOADI32_EAX &process_if_string_5 ; Using ":ELSE_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->S LOADI32_EAX &else_string ; Using "else" CALL32 %match ; IF global_token->S == "else" CMPI8_EAX !0 ; Then we need to collect the else too JNE32 %process_if_done ; Otherwise finish up ;; deal with else statement LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT CALL32 %statement ; Recurse to get the ELSE {...} part :process_if_done LOADI32_EAX &process_if_string_6 ; Using ":_END_IF_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S COPY_ECX_to_EBX ; Passing NUMBER_STRING CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :process_if_string_0 "# IF_" :process_if_string_1 "ERROR in process_if MISSING ( " :process_if_string_2 "test_eax,eax je %ELSE_" :process_if_string_3 "ERROR in process_if MISSING ) " :process_if_string_4 "jmp %_END_IF_" :process_if_string_5 ":ELSE_" :process_if_string_6 ":_END_IF_" ;; save_break_frame microfunction ;; Overwrites EAX and EBX ;; Saves break frame on stack ;; Returns to caller :save_break_frame POP_EBX ; Save return Address LOAD32_Absolute32_eax &break_frame ; Get break_frame PUSH_EAX ; Store as nested_locals LOAD32_Absolute32_eax &break_target_head ; Get break_target_head PUSH_EAX ; Store as nested_break_head LOAD32_Absolute32_eax &break_target_func ; Get break_target_func PUSH_EAX ; Store as nested_break_func LOAD32_Absolute32_eax &break_target_num ; Get break_target_num PUSH_EAX ; Store as nested_break_num PUSH_EBX ; Put return back in place RETURN ; Return to caller ;; restore_break_frame microfunction ;; Overwrites EAX and EBX ;; Restores break frame from stack ;; Returns to caller :restore_break_frame POP_EBX ; Save return Address POP_EAX ; Get nested_break_num STORE32_Absolute32_eax &break_target_num ; Restore break_target_num POP_EAX ; Get nested_break_func STORE32_Absolute32_eax &break_target_func ; Restore break_target_func POP_EAX ; Get nested_break_head STORE32_Absolute32_eax &break_target_head ; Restore break_target_head POP_EAX ; Get nested_locals STORE32_Absolute32_eax &break_frame ; Restore break_frame PUSH_EBX ; Put return back in place RETURN ; Return to caller ;; set_break_frame microfunction ;; Receives char* head in EAX and char* num in EBX ;; Overwrites EAX and EBX ;; Returns to calling function :set_break_frame STORE32_Absolute32_eax &break_target_head ; update break_target_head STORE32_Absolute32_ebx &break_target_num ; update break_target_num LOAD32_Absolute32_ebx &function ; Using function LOAD32_EAX_from_EBX_Immediate8 !4 ; function->LOCALS STORE32_Absolute32_eax &break_frame ; break_frame = function->LOCALS LOAD32_EAX_from_EBX_Immediate8 !8 ; function->S STORE32_Absolute32_eax &break_target_func ; break_target_func = function->S RETURN ; Return to sender ;; process_do function ;; Receives Nothing ;; Returns Nothing ;; Increments current_count and leverages save/restore_break_frame pieces ;; Uses ECX for char* NUMBER_STRING :process_do PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX CALL32 %save_break_frame ; Save the frame LOAD32_Absolute32_eax ¤t_count ; Using current count COPY_EAX_to_EBX ; Preparing for update ADDI8_EBX !1 ; current_count + 1 STORE32_Absolute32_ebx ¤t_count ; current_count = current_count + 1 CALL32 %numerate_number ; convert to string COPY_EAX_to_ECX ; put NUMBER_STRING in place LOADI32_EAX &process_do_string_0 ; Using "DO_END_" COPY_ECX_to_EBX ; Passing NUMBER_STRING CALL32 %set_break_frame ; Set the frame LOADI32_EAX &process_do_string_1 ; Using ":DO_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S COPY_ECX_to_EBX ; Passing NUMBER_STRING CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT CALL32 %statement ; Do the DO {...} part LOADI32_EAX &process_do_string_2 ; Using "ERROR in process_do\nMISSING while\n" LOADI32_EBX &while_string ; Using "while" CALL32 %require_match ; Make sure we have it LOADI32_EAX &process_do_string_3 ; Using "ERROR in process_do\nMISSING (\n" LOADI32_EBX &open_paren ; Using "(" CALL32 %require_match ; Make sure we have it CALL32 %expression ; Do the WHILE (...) part LOADI32_EAX &process_do_string_4 ; Using "ERROR in process_do\nMISSING )\n" LOADI32_EBX &close_paren ; Using ")" CALL32 %require_match ; Make sure we have it LOADI32_EAX &process_do_string_5 ; Using "ERROR in process_do\nMISSING ;\n" LOADI32_EBX &semicolon ; Using ";" CALL32 %require_match ; Make sure we have it LOADI32_EAX &process_do_string_6 ; Using "test_eax,eax\njne %DO_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S COPY_ECX_to_EBX ; Passing NUMBER_STRING CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) LOADI32_EAX &process_do_string_7 ; Using ":DO_END_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) CALL32 %restore_break_frame ; Restore the old break frame POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :process_do_string_0 "DO_END_" :process_do_string_1 ":DO_" :process_do_string_2 "ERROR in process_do MISSING while " :process_do_string_3 "ERROR in process_do MISSING ( " :process_do_string_4 "ERROR in process_do MISSING ) " :process_do_string_5 "ERROR in process_do MISSING ; " :process_do_string_6 "test_eax,eax jne %DO_" :process_do_string_7 ":DO_END_" ;; process_while function ;; Receives nothing ;; Returns nothing ;; Increments current_count and leverages save/restore_break_frame pieces ;; Uses ECX for char* NUMBER_STRING :process_while PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX CALL32 %save_break_frame ; Save break_frame LOAD32_Absolute32_eax ¤t_count ; Using current count COPY_EAX_to_EBX ; Preparing for update ADDI8_EBX !1 ; current_count + 1 STORE32_Absolute32_ebx ¤t_count ; current_count = current_count + 1 CALL32 %numerate_number ; convert to string COPY_EAX_to_ECX ; put NUMBER_STRING in place LOADI32_EAX &process_while_string_0 ; Using "END_WHILE_" COPY_ECX_to_EBX ; Passing NUMBER_STRING CALL32 %set_break_frame ; Set it and forget it LOADI32_EAX &process_while_string_1 ; Using ":WHILE_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S COPY_ECX_to_EBX ; Passing NUMBER_STRING CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT LOADI32_EAX &process_while_string_2 ; Using "ERROR in process_while\nMISSING (\n" LOADI32_EBX &open_paren ; Using "(" CALL32 %require_match ; Make sure we have it CALL32 %expression ; Deal with the WHILE (...) part LOADI32_EAX &process_while_string_3 ; Using "test_eax,eax\nje %END_WHILE_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S COPY_ECX_to_EBX ; Passing NUMBER_STRING CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) LOADI32_EAX &process_while_string_4 ; Using "# THEN_while_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) LOADI32_EAX &process_while_string_5 ; Using "ERROR in process_while\nMISSING )\n" LOADI32_EBX &close_paren ; Using ")" CALL32 %require_match ; Make sure we have it CALL32 %statement ; Deal with the {....} part LOADI32_EAX &process_while_string_6 ; Using "jmp %WHILE_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S COPY_ECX_to_EBX ; Passing NUMBER_STRING CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) LOADI32_EAX &process_while_string_7 ; Using ":END_WHILE_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) CALL32 %restore_break_frame ; Restore the old break frame POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :process_while_string_0 "END_WHILE_" :process_while_string_1 ":WHILE_" :process_while_string_2 "ERROR in process_while MISSING ( " :process_while_string_3 "test_eax,eax je %END_WHILE_" :process_while_string_4 "# THEN_while_" :process_while_string_5 "ERROR in process_while MISSING ) " :process_while_string_6 "jmp %WHILE_" :process_while_string_7 ":END_WHILE_" ;; process_for function ;; Receives Nothing ;; Returns Nothing ;; Increments current_count and leverages save/restore_break_frame pieces ;; Uses ECX for char* NUMBER_STRING :process_for PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX CALL32 %save_break_frame ; Save the frame LOAD32_Absolute32_eax ¤t_count ; Using current count COPY_EAX_to_EBX ; Preparing for update ADDI8_EBX !1 ; current_count + 1 STORE32_Absolute32_ebx ¤t_count ; current_count = current_count + 1 CALL32 %numerate_number ; convert to string COPY_EAX_to_ECX ; put NUMBER_STRING in place LOADI32_EAX &process_for_string_0 ; Using "FOR_END_" COPY_ECX_to_EBX ; Passing NUMBER_STRING CALL32 %set_break_frame ; Set it and forget it LOADI32_EAX &process_for_string_1 ; Using "# FOR_initialization_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S COPY_ECX_to_EBX ; Passing NUMBER_STRING CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT LOADI32_EAX &process_for_string_2 ; Using "ERROR in process_for\nMISSING (\n" LOADI32_EBX &open_paren ; Using "(" CALL32 %require_match ; Make Sure we have it LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->S LOADI32_EAX &semicolon ; Using ";" CALL32 %match ; IF global_token->S == ";" CMPI8_EAX !0 ; Then no initializer JE32 %process_for_terminator ; And skip getting the expression ;; Deal with FOR (...; case CALL32 %expression ; Get the FOR ( ... ; part :process_for_terminator LOADI32_EAX &process_for_string_3 ; Using ":FOR_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S COPY_ECX_to_EBX ; Passing NUMBER_STRING CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) LOADI32_EAX &process_for_string_4 ; Using "ERROR in process_for\nMISSING ;1\n" LOADI32_EBX &semicolon ; Using ";" CALL32 %require_match ; Make sure we have it CALL32 %expression ; Get the FOR ( ; ... ; Part LOADI32_EAX &process_for_string_5 ; Using "test_eax,eax\nje %FOR_END_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S COPY_ECX_to_EBX ; Passing NUMBER_STRING CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) LOADI32_EAX &process_for_string_6 ; Using "jmp %FOR_THEN_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) LOADI32_EAX &process_for_string_7 ; Using ":FOR_ITER_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) LOADI32_EAX &process_for_string_8 ; Using "ERROR in process_for\nMISSING ;2\n" LOADI32_EBX &semicolon ; Using ";" CALL32 %require_match ; Make sure we have it CALL32 %expression ; Get the FOR (;;...) part LOADI32_EAX &process_for_string_9 ; Using "jmp %FOR_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S COPY_ECX_to_EBX ; Passing NUMBER_STRING CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) LOADI32_EAX &process_for_string_10 ; Using ":FOR_THEN_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) LOADI32_EAX &process_for_string_11 ; Using "ERROR in process_for\nMISSING )\n" LOADI32_EBX &close_paren ; Using ")" CALL32 %require_match ; Make sure we have it CALL32 %statement ; Get FOR (;;) {...} part LOADI32_EAX &process_for_string_12 ; Using "jmp %FOR_ITER_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S COPY_ECX_to_EBX ; Passing NUMBER_STRING CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) LOADI32_EAX &process_for_string_13 ; Using ":FOR_END_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S CALL32 %uniqueID_out ; uniqueID_out(function->s, number_string) CALL32 %restore_break_frame ; Restore the old break frame POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :process_for_string_0 "FOR_END_" :process_for_string_1 "# FOR_initialization_" :process_for_string_2 "ERROR in process_for MISSING ( " :process_for_string_3 ":FOR_" :process_for_string_4 "ERROR in process_for MISSING ;1 " :process_for_string_5 "test_eax,eax je %FOR_END_" :process_for_string_6 "jmp %FOR_THEN_" :process_for_string_7 ":FOR_ITER_" :process_for_string_8 "ERROR in process_for MISSING ;2 " :process_for_string_9 "jmp %FOR_" :process_for_string_10 ":FOR_THEN_" :process_for_string_11 "ERROR in process_for MISSING ) " :process_for_string_12 "jmp %FOR_ITER_" :process_for_string_13 ":FOR_END_" ;; process_break function ;; Receives nothing ;; Returns nothing ;; Handles the break out of loops case ;; Uses EBX for struct token_list* break_frame and ECX for struct token_list* I :process_break PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX LOAD32_Absolute32_eax &break_target_head ; Catch big error CMPI8_EAX !0 ; IF(NULL == break_target_head) JE32 %process_break_bad ; I'm sorry Mr White but you have stage-3 lung cancer LOAD32_Absolute32_eax &function ; Using function LOAD32_ECX_from_EAX_Immediate8 !4 ; I = function->LOCALS LOAD32_Absolute32_ebx &break_frame ; Put break_frame in the right spot LOADI32_EAX &process_break_string_1 ; Using "pop_ebx\t# break_cleanup_locals\n" :process_break_iter CMPI8_ECX !0 ; IF (NULL == I) JE32 %process_break_cleaned ; We are done CMP_EBX_ECX ; IF I != break_frame JE32 %process_break_cleaned ; We are done CALL32 %emit_out ; Emit it LOAD32_ECX_from_ECX ; I = I->NEXT JMP32 %process_break_iter ; Keep looping :process_break_cleaned LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT LOADI32_EAX &process_break_string_2 ; Using "jmp %" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &break_target_head ; Get what we are in CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &break_target_func ; Get what function we are in CALL32 %emit_out ; Emit it LOADI32_EAX &underline ; Using "_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &break_target_num ; Get dem digits CALL32 %emit_out ; Emit it LOADI32_EAX &process_break_string_3 ; Using "\n" CALL32 %emit_out ; Emit it LOADI32_EAX &process_break_string_4 ; Using "ERROR in break statement\nMissing ;\n" LOADI32_EBX &semicolon ; Using ";" CALL32 %require_match ; Make sure we have it POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :process_break_bad ;; Breaking badly LOADI32_EAX %2 ; Using standard error STORE32_Absolute32_eax &Output_file ; write to standard error ; CALL32 %line_error ; Write useful debug info COPY_ECX_to_EAX ; put S in the right place CALL32 %File_Print ; print it LOADI32_EAX &process_break_string_0 ; Ending string CALL32 %File_Print ; print it JMP32 %Exit_Failure ; Abort Hard :process_break_string_0 "Not inside of a loop or case statement" :process_break_string_1 "pop_ebx # break_cleanup_locals " :process_break_string_2 "jmp %" :process_break_string_3 " " :process_break_string_4 "ERROR in break statement Missing ; " ;; expression function ;; Receives Nothing ;; Returns Nothing ;; Walks global_token and updates output_list ;; Uses EAX and EBX for match and ECX for char* store :expression PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX CALL32 %bitwise_expr ; Collect bitwise expressions LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->S LOADI32_EAX &equal ; "=" CALL32 %match ; IF global_token->S == "=" CMPI8_EAX !0 ; We have to deal with assignment JNE32 %expression_done ; Looks like nope ;; Deal with possible assignment LOADI32_ECX &expression_string_1 ; Assume "mov_[ebx],al\n" by default LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX_Immediate8 !4 ; global_token->PREV LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->PREV->S LOADI32_EAX &close_bracket ; Using "]" CALL32 %match ; IF global_token->S == "]" CMPI8_EAX !0 ; Then we might have a char JNE32 %expression_int ; Otherwise INT LOAD32_Absolute32_ebx ¤t_target ; Using current_target LOAD32_EBX_from_EBX_Immediate8 !24 ; current_target->NAME LOADI32_EAX &type_char_indirect_name ; Using "char*" CALL32 %match ; Intentional inefficiency because I feel like it CMPI8_EAX !0 ; IF current_target->NAME == "char*" JNE32 %expression_int ; Do char anyway JMP32 %expression_common ; Looks like we have to use "mov_[ebx],al\n" :expression_int LOADI32_ECX &expression_string_0 ; Use "mov_[ebx],eax\n" :expression_common LOADI32_EAX &expression ; Passing expression CALL32 %common_recursion ; Recurse COPY_ECX_to_EAX ; Using Store CALL32 %emit_out ; Emit it LOADI32_EAX %0 ; Using NULL STORE32_Absolute32_eax ¤t_target ; current_target = NULL :expression_done POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :expression_string_0 "mov_[ebx],eax " :expression_string_1 "mov_[ebx],al " ;; bitwise_expr function ;; Receives nothing ;; Returns nothing ;; Walks global_token list and updates output list ;; Just calls other functions :bitwise_expr CALL32 %relational_expr ; Walk up the tree CALL32 %bitwise_expr_stub ; Let general recursion do the work RETURN ;; bitwise_expr_stub function ;; Receives nothing ;; Returns Nothing ;; Just calls general_recursion a bunch ;; Uses EAX, EBX, ECX and EDX for passing constants to general recursion :bitwise_expr_stub PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX PUSH_EDX ; Protect EDX LOADI32_EAX &relational_expr ; Using relational_expr LOADI32_EBX &bitwise_expr_stub_string_0 ; Using "and_eax,ebx\n" LOADI32_ECX &bitwise_and ; Using "&" LOADI32_EDX &bitwise_expr_stub ; And recurse CALL32 %general_recursion ; Hit it LOADI32_EAX &relational_expr ; Using relational_expr LOADI32_EBX &bitwise_expr_stub_string_0 ; Using "and_eax,ebx\n" LOADI32_ECX &logical_and ; Using "&&" LOADI32_EDX &bitwise_expr_stub ; And recurse CALL32 %general_recursion ; Hit it LOADI32_EAX &relational_expr ; Using relational_expr LOADI32_EBX &bitwise_expr_stub_string_1 ; Using "or_eax,ebx\n" LOADI32_ECX &bitwise_or ; Using "|" LOADI32_EDX &bitwise_expr_stub ; And recurse CALL32 %general_recursion ; Hit it LOADI32_EAX &relational_expr ; Using relational_expr LOADI32_EBX &bitwise_expr_stub_string_1 ; Using "or_eax,ebx\n" LOADI32_ECX &logical_or ; Using "||" LOADI32_EDX &bitwise_expr_stub ; And recurse CALL32 %general_recursion ; Hit it LOADI32_EAX &relational_expr ; Using relational_expr LOADI32_EBX &bitwise_expr_stub_string_2 ; Using "xor_eax,ebx\n" LOADI32_ECX &bitwise_xor ; Using "^" LOADI32_EDX &bitwise_expr_stub ; And recurse CALL32 %general_recursion ; Hit it POP_EDX ; Restore EDX POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :bitwise_expr_stub_string_0 "and_eax,ebx " :bitwise_expr_stub_string_1 "or_eax,ebx " :bitwise_expr_stub_string_2 "xor_eax,ebx " ;; relational_expr function ;; Receives nothing ;; Returns Nothing ;; Walks global_token list and updates output list ;; just calls other function :relational_expr CALL32 %additive_expr ; Walk up the tree CALL32 %relational_expr_stub ; Recurse RETURN ;; relational_expr_stub function ;; Receives nothing ;; Returns Nothing ;; Just calls general_recursion a bunch ;; Uses EAX, EBX, ECX and EDX for passing constants to general recursion :relational_expr_stub PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX PUSH_EDX ; Protect EDX LOADI32_EAX &additive_expr ; Using additive_expr LOADI32_EBX &relational_expr_stub_string_0 ; Using "cmp\nsetl_al\nmovzx_eax,al\n" LOADI32_ECX &less_than_string ; Using "<" LOADI32_EDX &relational_expr_stub ; And recurse CALL32 %general_recursion ; Hit it LOADI32_EAX &additive_expr ; Using additive_expr LOADI32_EBX &relational_expr_stub_string_1 ; Using "cmp\nsetle_al\nmovzx_eax,al\n" LOADI32_ECX &less_than_equal_string ; Using "<=" LOADI32_EDX &relational_expr_stub ; And recurse CALL32 %general_recursion ; Hit it LOADI32_EAX &additive_expr ; Using additive_expr LOADI32_EBX &relational_expr_stub_string_2 ; Using "cmp\nsetge_al\nmovzx_eax,al\n" LOADI32_ECX &greater_than_equal_string ; Using ">=" LOADI32_EDX &relational_expr_stub ; And recurse CALL32 %general_recursion ; Hit it LOADI32_EAX &additive_expr ; Using additive_expr LOADI32_EBX &relational_expr_stub_string_3 ; Using "cmp\nsetg_al\nmovzx_eax,al\n" LOADI32_ECX &greater_than_string ; Using ">" LOADI32_EDX &relational_expr_stub ; And recurse CALL32 %general_recursion ; Hit it LOADI32_EAX &additive_expr ; Using additive_expr LOADI32_EBX &relational_expr_stub_string_4 ; Using "cmp\nsete_al\nmovzx_eax,al\n" LOADI32_ECX &equal_to_string ; Using "==" LOADI32_EDX &relational_expr_stub ; And recurse CALL32 %general_recursion ; Hit it LOADI32_EAX &additive_expr ; Using additive_expr LOADI32_EBX &relational_expr_stub_string_5 ; Using "cmp\nsetne_al\nmovzx_eax,al\n" LOADI32_ECX ¬_equal_string ; Using "!=" LOADI32_EDX &relational_expr_stub ; And recurse CALL32 %general_recursion ; Hit it POP_EDX ; Restore EDX POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :relational_expr_stub_string_0 "cmp setl_al movzx_eax,al " :relational_expr_stub_string_1 "cmp setle_al movzx_eax,al " :relational_expr_stub_string_2 "cmp setge_al movzx_eax,al " :relational_expr_stub_string_3 "cmp setg_al movzx_eax,al " :relational_expr_stub_string_4 "cmp sete_al movzx_eax,al " :relational_expr_stub_string_5 "cmp setne_al movzx_eax,al " ;; additive_expr function ;; Receives nothing ;; Returns Nothing ;; Walks global_token list and updates output list ;; just calls other function :additive_expr CALL32 %postfix_expr ; Walk up the tree CALL32 %additive_expr_stub ; Recurse RETURN ;; additive_expr_stub function ;; Receives nothing ;; Returns Nothing ;; Just calls general_recursion a bunch ;; Uses EAX, EBX, ECX and EDX for passing constants to general recursion :additive_expr_stub PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX PUSH_EDX ; Protect EDX LOADI32_EAX &postfix_expr ; Using postfix_expr LOADI32_EBX &additive_expr_stub_string_0 ; Using "add_eax,ebx\n" LOADI32_ECX &plus_string ; Using "+" LOADI32_EDX &additive_expr_stub ; And recurse CALL32 %general_recursion ; Hit it LOADI32_EAX &postfix_expr ; Using postfix_expr LOADI32_EBX &additive_expr_stub_string_1 ; Using "sub_ebx,eax\nmov_eax,ebx\n" LOADI32_ECX &minus_string ; Using "-" LOADI32_EDX &additive_expr_stub ; And recurse CALL32 %general_recursion ; Hit it LOADI32_EAX &postfix_expr ; Using postfix_expr LOADI32_EBX &additive_expr_stub_string_2 ; Using "mul_ebx\n" LOADI32_ECX &multiply_string ; Using "*" LOADI32_EDX &additive_expr_stub ; And recurse CALL32 %general_recursion ; Hit it LOADI32_EAX &postfix_expr ; Using postfix_expr LOADI32_EBX &additive_expr_stub_string_3 ; Using "xchg_ebx,eax\nmov_edx, %0\ndiv_ebx\n" LOADI32_ECX ÷_string ; Using "/" LOADI32_EDX &additive_expr_stub ; And recurse CALL32 %general_recursion ; Hit it LOADI32_EAX &postfix_expr ; Using postfix_expr LOADI32_EBX &additive_expr_stub_string_4 ; Using "xchg_ebx,eax\nmov_edx, %0\ndiv_ebx\nmov_eax,edx\n" LOADI32_ECX &modulus_string ; Using "%" LOADI32_EDX &additive_expr_stub ; And recurse CALL32 %general_recursion ; Hit it LOADI32_EAX &postfix_expr ; Using postfix_expr LOADI32_EBX &additive_expr_stub_string_5 ; Using "mov_ecx,eax\nmov_eax,ebx\nsal_eax,cl\n" LOADI32_ECX &left_shift_string ; Using "<<" LOADI32_EDX &additive_expr_stub ; And recurse CALL32 %general_recursion ; Hit it LOADI32_EAX &postfix_expr ; Using postfix_expr LOADI32_EBX &additive_expr_stub_string_6 ; Using "mov_ecx,eax\nmov_eax,ebx\nsar_eax,cl\n" LOADI32_ECX &right_shift_string ; Using ">>" LOADI32_EDX &additive_expr_stub ; And recurse CALL32 %general_recursion ; Hit it POP_EDX ; Restore EDX POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :additive_expr_stub_string_0 "add_eax,ebx " :additive_expr_stub_string_1 "sub_ebx,eax mov_eax,ebx " :additive_expr_stub_string_2 "mul_ebx " :additive_expr_stub_string_3 "xchg_ebx,eax mov_edx, %0 div_ebx " :additive_expr_stub_string_4 "xchg_ebx,eax mov_edx, %0 div_ebx mov_eax,edx " :additive_expr_stub_string_5 "mov_ecx,eax mov_eax,ebx sal_eax,cl " :additive_expr_stub_string_6 "mov_ecx,eax mov_eax,ebx sar_eax,cl " ;; postfix_expr function ;; Receives nothing ;; Returns Nothing ;; Walks global_token list and updates output list ;; just calls other function :postfix_expr CALL32 %primary_expr ; Walk up the tree CALL32 %postfix_expr_stub ; Recurse RETURN ;; postfix_expr_stub function ;; Receives nothing ;; Returns Nothing ;; Checks for "[" and "->" and deals with them otherwise does nothing ;; Uses EAX, EBX, ECX and EDX for passing constants to general recursion :postfix_expr_stub PUSH_EBX ; Protect EBX LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->S LOADI32_EAX &open_bracket ; Using "[" CALL32 %match ; IF global_token->S == "[" CMPI8_EAX !0 ; then we have an array JNE32 %postfix_expr_stub_arrow ; Otherwise try arrow ;; Deal with array CALL32 %postfix_expr_array ; Get it CALL32 %postfix_expr_stub ; Recurse :postfix_expr_stub_arrow LOADI32_EAX &arrow_string ; Using "->" CALL32 %match ; IF global_token->S == "->" CMPI8_EAX !0 ; Then we need to deal with struct offsets JNE32 %postfix_expr_stub_done ; Otherwise be done ;; Deal with arrow CALL32 %postfix_expr_arrow ; Get it CALL32 %postfix_expr_stub ; Recurse :postfix_expr_stub_done POP_EBX ; Restore EBX RETURN ;; unary_expr_sizeof function ;; Receives nothing ;; Returns nothing ;; Uses ECX for A->SIZE :unary_expr_sizeof PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT LOADI32_EAX &unary_expr_sizeof_string_0 ; Using "ERROR in unary_expr\nMissing (\n" LOADI32_EBX &open_paren ; Using "(" CALL32 %require_match ; Make sure we have it CALL32 %type_name ; Get the type LOAD32_ECX_from_EAX_Immediate8 !4 ; Set A->TYPE LOADI32_EAX &unary_expr_sizeof_string_1 ; Using "ERROR in unary_expr\nMissing )\n" LOADI32_EBX &close_paren ; Using ")" CALL32 %require_match ; Make sure we have it LOADI32_EAX &unary_expr_sizeof_string_2 ; Using "mov_eax, %" CALL32 %emit_out ; Emit it COPY_ECX_to_EAX ; Put A->SIZE in the right place CALL32 %numerate_number ; Turn into string CALL32 %emit_out ; Emit it LOADI32_EAX &unary_expr_sizeof_string_3 ; Using "\n" CALL32 %emit_out ; Emit it POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :unary_expr_sizeof_string_0 "ERROR in unary_expr Missing ( " :unary_expr_sizeof_string_1 "ERROR in unary_expr Missing ) " :unary_expr_sizeof_string_2 "mov_eax, %" :unary_expr_sizeof_string_3 " " ;; postfix_expr_array function ;; Receives Nothing ;; Returns Nothing ;; Uses EBX for struct type* ARRAY and ECX for char* ASSIGN :postfix_expr_array PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX LOAD32_Absolute32_eax ¤t_target ; ARRAY = current_target PUSH_EAX ; Protect it LOADI32_EAX &expression ; Using expression CALL32 %common_recursion ; Recurse POP_EBX ; Restore array STORE32_Absolute32_ebx ¤t_target ; current_target = ARRAY LOADI32_ECX &postfix_expr_array_string_0 ; ASSIGN = "mov_eax,[eax]\n" LOADI32_EAX &type_char_indirect_name ; Using "char*" LOAD32_EBX_from_EBX_Immediate8 !24 ; current_target->NAME CALL32 %match ; IF current_target->NAME == "char*" CMPI8_EAX !0 ; load a byte JNE32 %postfix_expr_array_large ; Otherwise adjust ;; Deal with loading byte LOADI32_ECX &postfix_expr_array_string_1 ; ASSIGN = "movsx_eax,BYTE_PTR_[eax]\n" JMP32 %postfix_expr_array_common ; Do the next bit :postfix_expr_array_large ;; deal with arrays made of things other than chars LOADI32_EAX &postfix_expr_array_string_2 ; Using "sal_eax, !" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax ¤t_target ; Using current_target LOAD32_EAX_from_EAX_Immediate8 !12 ; current_target->INDIRECT LOAD32_EAX_from_EAX_Immediate8 !4 ; current_target->INDIRECT->SIZE CALL32 %ceil_log2 ; ceil_log2(current_target->indirect->size) CALL32 %numerate_number ; numerate_number(ceil_log2(current_target->indirect->size)) CALL32 %emit_out ; Emit it LOADI32_EAX &postfix_expr_array_string_3 ; Using "\n" CALL32 %emit_out ; Emit it :postfix_expr_array_common LOADI32_EAX &postfix_expr_array_string_4 ; Using "add_eax,ebx\n" CALL32 %emit_out ; Emit it LOADI32_EAX &postfix_expr_array_string_5 ; Using "ERROR in postfix_expr\nMissing ]\n" LOADI32_EBX &close_bracket ; Using "]" CALL32 %require_match ; Make sure we have it LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->S LOADI32_EAX &equal ; Using "=" CALL32 %match ; IF global_token->S == "=" CMPI8_EAX !0 ; We need to preserve address JNE32 %postfix_expr_array_done ; Otherwise be done ;; Clearing out assign LOADI32_ECX &postfix_expr_array_string_6 ; ASSIGN = "" :postfix_expr_array_done COPY_ECX_to_EAX ; Using ASSIGN CALL32 %emit_out ; Emit it POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :postfix_expr_array_string_0 "mov_eax,[eax] " :postfix_expr_array_string_1 "movsx_eax,BYTE_PTR_[eax] " :postfix_expr_array_string_2 "sal_eax, !" :postfix_expr_array_string_3 " " :postfix_expr_array_string_4 "add_eax,ebx " :postfix_expr_array_string_5 "ERROR in postfix_expr Missing ] " :postfix_expr_array_string_6 "" ;; ceil_log2 function ;; Receives int a in EAX ;; Performs log2 on A and ;; Returns result in EAX ;; Uses EBX for INT A and ECX for INT RESULT :ceil_log2 PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX LOADI32_ECX %0 ; RESULT = 0 COPY_EAX_to_EBX ; put A in right place SUBI8_EAX !1 ; (A - 1) AND_EAX_EBX ; A & (A - 1) CMPI8_EAX !0 ; IF 0 == (A & (A - 1)) JNE32 %ceil_log2_iter ; Starting from -1 LOADI32_ECX %-1 ; RESULT = -1 :ceil_log2_iter CMPI8_EBX !0 ; IF A > 0 JLE32 %ceil_log2_done ; Otherwise be done ADDI8_ECX !1 ; RESULT = RESULT + 1 SHRI8_EBX !1 ; A = A >> 1 JMP32 %ceil_log2_iter ; Keep looping :ceil_log2_done COPY_ECX_to_EAX ; Return RESULT POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; postfix_expr_arrow function ;; Receives nothing ;; Returns nothing ;; Emits a bunch and updates current_target ;; Uses EBX for struct type* I :postfix_expr_arrow PUSH_EBX ; Protect EBX LOADI32_EAX &postfix_expr_arrow_string_0 ; Using "# looking up offset\n" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT LOAD32_EBX_from_EAX_Immediate8 !8 ; Using global_token->S LOAD32_Absolute32_eax ¤t_target ; Using current_target CALL32 %lookup_member ; lookup_member(current_target, global_token->s) COPY_EAX_to_EBX ; struct type* I = lookup_member(current_target, global_token->s) LOAD32_EAX_from_EAX_Immediate8 !20 ; I->TYPE STORE32_Absolute32_eax ¤t_target ; current_target = I->TYPE LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT LOAD32_EAX_from_EBX_Immediate8 !8 ; I->OFFSET CMPI8_EAX !0 ; IF 0 != I->OFFSET JE32 %postfix_expr_arrow_first ; Then we don't need to do an offset ;; Deal with needing an offset LOADI32_EAX &postfix_expr_arrow_string_1 ; Using "# -> offset calculation\nmov_ebx, %" CALL32 %emit_out ; Emit it LOAD32_EAX_from_EBX_Immediate8 !8 ; I->OFFSET CALL32 %numerate_number ; Convert to string CALL32 %emit_out ; Emit it LOADI32_EAX &postfix_expr_arrow_string_2 ; Using "\nadd_eax,ebx\n" CALL32 %emit_out ; Emit it :postfix_expr_arrow_first LOAD32_EAX_from_EBX_Immediate8 !4 ; I->SIZE CMPI8_EAX !4 ; IF I->SIZE >= 4 JL32 %postfix_expr_arrow_done ; Otherwise be done ;; Last chance for load LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EBX_from_EAX_Immediate8 !8 ; global_token->S LOADI32_EAX &equal ; Using "=" CALL32 %match ; IF global_token->S == "=" CMPI8_EAX !0 ; Then we have assignment and should not load JE32 %postfix_expr_arrow_done ; Be done ;; Deal with load case LOADI32_EAX &postfix_expr_arrow_string_3 ; Using "mov_eax,[eax]\n" CALL32 %emit_out ; Emit it :postfix_expr_arrow_done POP_EBX ; Restore EBX RETURN :postfix_expr_arrow_string_0 "# looking up offset " :postfix_expr_arrow_string_1 "# -> offset calculation mov_ebx, %" :postfix_expr_arrow_string_2 " add_eax,ebx " :postfix_expr_arrow_string_3 "mov_eax,[eax] " ;; primary_expr function ;; Receives nothing ;; Returns nothing :primary_expr PUSH_EBX ; Protect EBX LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EBX_from_EAX_Immediate8 !8 ; global_token->S LOADI32_EAX &sizeof_string ; Using "sizeof" CALL32 %match ; See if match CMPI8_EAX !0 ; IF match JNE32 %primary_expr_neg ; Otherwise try negatives ;; Deal with sizeof CALL32 %unary_expr_sizeof ; Lets do this JMP32 %primary_expr_done ; Be done :primary_expr_neg LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S LOAD8_al_from_EAX ; global_token->S[0] MOVZX_al ; Make it useful CMPI8_EAX !45 ; IF global_token->S[0] == "-" JNE32 %primary_expr_not ; Otherwise try logical NOT ;; Deal with negative numbers LOADI32_EAX &primary_expr_string_0 ; Using "mov_eax, %0\n" CALL32 %emit_out ; Emit it LOADI32_EAX &postfix_expr ; Passing postfix_expr CALL32 %common_recursion ; Get what it is notting LOADI32_EAX &primary_expr_string_1 ; Using "sub_ebx,eax\nmov_eax,ebx\n" CALL32 %emit_out ; Emit it JMP32 %primary_expr_done ; Be done :primary_expr_not LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S LOAD8_al_from_EAX ; global_token->S[0] MOVZX_al ; Make it useful CMPI8_EAX !33 ; IF global_token->S[0] == "!" JNE32 %primary_expr_bin ; Otherwise try '~' ;; Deal with logical not LOADI32_EAX &primary_expr_string_2 ; Using "mov_eax, %1\n" CALL32 %emit_out ; Emit it LOADI32_EAX &postfix_expr ; Passing postfix_expr CALL32 %common_recursion ; Get what it is notting LOADI32_EAX &primary_expr_string_3 ; Using "xor_eax,ebx\n" CALL32 %emit_out ; Emit it JMP32 %primary_expr_done ; Be done :primary_expr_bin LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S LOAD8_al_from_EAX ; global_token->S[0] MOVZX_al ; Make it useful CMPI8_EAX !126 ; IF global_token->S[0] == "~" JNE32 %primary_expr_paren ; Otherwise try paren ;; Deal with binary NOT LOADI32_EAX &postfix_expr ; Passing postfix_expr CALL32 %common_recursion ; Get what it is notting LOADI32_EAX &primary_expr_string_4 ; Using "not_eax\n" CALL32 %emit_out ; Emit it JMP32 %primary_expr_done ; Be done :primary_expr_paren LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S LOAD8_al_from_EAX ; global_token->S[0] MOVZX_al ; Make it useful CMPI8_EAX !40 ; IF global_token->S[0] == "(" JNE32 %primary_expr_ch ; Otherwise try char ;; deal with nesting LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT CALL32 %expression ; Lets recurse LOADI32_EAX &primary_expr_string_5 ; Using "Error in Primary expression\nDidn't get )\n" LOADI32_EBX &close_paren ; Using ")" CALL32 %require_match ; Make sure we have it JMP32 %primary_expr_done ; Be done :primary_expr_ch LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S LOAD8_al_from_EAX ; global_token->S[0] MOVZX_al ; Make it useful CMPI8_EAX !39 ; Using "'" JNE32 %primary_expr_str ; Otherwise try string ;; Deal with chars CALL32 %primary_expr_char ; Handle that char JMP32 %primary_expr_done ; Be done :primary_expr_str LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S LOAD8_al_from_EAX ; global_token->S[0] MOVZX_al ; Make it useful CMPI8_EAX !34 ; Using '\"' JNE32 %primary_expr_var ; Otherwise try a variable ;; Deal with strings CALL32 %primary_expr_string ; Handle that string JMP32 %primary_expr_done ; Be done :primary_expr_var LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S LOAD8_al_from_EAX ; global_token->S[0] MOVZX_al ; Make it useful LOADI32_EBX &primary_expr_string_6 ; Using "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_" CALL32 %In_Set ; See if we have a match CMPI8_EAX !1 ; IF match JNE32 %primary_expr_num ; otherwise try number ;; Deal with variables CALL32 %primary_expr_variable ; Deal with variable JMP32 %primary_expr_done ; Be done :primary_expr_num LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S LOAD8_al_from_EAX ; global_token->S[0] MOVZX_al ; Make it useful LOADI32_EBX &primary_expr_string_7 ; Using "0123456789" CALL32 %In_Set ; See if we have a match CMPI8_EAX !1 ; IF match JNE32 %primary_expr_fail ; otherwise we failed hard ;; Deal with numbers CALL32 %primary_expr_number ; Collect the number JMP32 %primary_expr_done ; Be done :primary_expr_fail ;; looks like we hit bad input ;; abort before it gets bad CALL32 %primary_expr_failure ; No match means failure :primary_expr_done POP_EBX ; Restore EBX RETURN :primary_expr_string_0 "mov_eax, %0 " :primary_expr_string_1 "sub_ebx,eax mov_eax,ebx " :primary_expr_string_2 "mov_eax, %1 " :primary_expr_string_3 "xor_eax,ebx " :primary_expr_string_4 "not_eax " :primary_expr_string_5 "Error in Primary expression Didn't get ) " :primary_expr_string_6 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_" :primary_expr_string_7 "0123456789" ;; primary_expr_variable function ;; Receives nothing ;; Returns nothing ;; Walks global and updates output ;; Uses EAX for struct token_list* a and ECX for char* S :primary_expr_variable PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_ECX_from_EAX_Immediate8 !8 ; S = global_token->S LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT COPY_ECX_to_EAX ; Using S LOAD32_Absolute32_ebx &global_constant_list ; Using global_constant_list CALL32 %sym_lookup ; sym_lookup(s, global_constant_list) CMPI8_EAX !0 ; IF NULL == sym_lookup(s, global_constant_list) JE32 %primary_expr_variable_local ; Try locals next ;; Deal with constant load LOAD32_EBX_from_EAX_Immediate8 !16 ; a->ARGS LOADI32_EAX &primary_expr_variable_string_2 ; Using "mov_eax, %" CALL32 %emit_out ; Emit it LOAD32_EAX_from_EBX_Immediate8 !8 ; a->ARGS->S CALL32 %emit_out ; Emit it LOADI32_EAX &primary_expr_variable_string_1 ; Using "\n" CALL32 %emit_out ; Emit it JMP32 %primary_expr_variable_done ; Be done :primary_expr_variable_local COPY_ECX_to_EAX ; Using S LOAD32_Absolute32_ebx &function ; Using function LOAD32_EBX_from_EBX_Immediate8 !4 ; function->locals CALL32 %sym_lookup ; sym_lookup(s, function->locals) CMPI8_EAX !0 ; IF NULL == sym_lookup(s, function->locals) JE32 %primary_expr_variable_arguments ; try arguments next ;; Deal with local load CALL32 %variable_load ; Collect it JMP32 %primary_expr_variable_done ; Be done :primary_expr_variable_arguments COPY_ECX_to_EAX ; Using S LOAD32_Absolute32_ebx &function ; Using function LOAD32_EBX_from_EBX_Immediate8 !16 ; function->args CALL32 %sym_lookup ; sym_lookup(s, function->args) CMPI8_EAX !0 ; IF NULL == sym_lookup(s, global_constant_list) JE32 %primary_expr_variable_function ; try functions next ;; Deal with argument load CALL32 %variable_load ; Collect it JMP32 %primary_expr_variable_done ; Be done :primary_expr_variable_function COPY_ECX_to_EAX ; Using S LOAD32_Absolute32_ebx &global_function_list ; Using global_function_list CALL32 %sym_lookup ; sym_lookup(s, global_function_list) CMPI8_EAX !0 ; IF NULL == sym_lookup(s, global_function_list) JE32 %primary_expr_variable_global ; try globals next ;; Deal with functions CALL32 %function_load ; Deal with the function JMP32 %primary_expr_variable_done ; Be done :primary_expr_variable_global COPY_ECX_to_EAX ; Using S LOAD32_Absolute32_ebx &global_symbol_list ; Using global_symbol_list CALL32 %sym_lookup ; sym_lookup(s, global_symbol_list) CMPI8_EAX !0 ; IF NULL == sym_lookup(s, global_symbol_list) JE32 %primary_expr_variable_error ; Give up ;; Deal with globals CALL32 %global_load ; Collect that global JMP32 %primary_expr_variable_done ; Be done :primary_expr_variable_error LOADI32_EAX %2 ; Using standard error STORE32_Absolute32_eax &Output_file ; write to standard error ; CALL32 %line_error ; Write useful debug info COPY_ECX_to_EAX ; put S in the right place CALL32 %File_Print ; print it LOADI32_EAX &primary_expr_variable_string_0 ; Ending string CALL32 %File_Print ; print it JMP32 %Exit_Failure ; Abort Hard :primary_expr_variable_done POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :primary_expr_variable_string_0 " is not a defined symbol " :primary_expr_variable_string_1 " " :primary_expr_variable_string_2 "mov_eax, %" ;; function_call function ;; Receives char* S in EAX and int BOOL in EBX ;; Builds stack frames before and tears them down after function calls ;; Uses ECX for char* S, EDX for int BOOL, ESI for PASSED :function_call PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX PUSH_EDX ; Protect EDX PUSH_ESI ; Protect ESI COPY_EAX_to_ECX ; Put S in place COPY_EBX_to_EDX ; Put BOOL in place LOADI32_ESI %0 ; PASSED = 0 LOADI32_EAX &function_call_string_0 ; Using "ERROR in process_expression_list\nNo ( was found\n" LOADI32_EBX &open_paren ; Using "(" CALL32 %require_match ; Make sure we have it LOADI32_EAX &function_call_string_1 ; Using "push_edi\t# Prevent overwriting in recursion\n" CALL32 %emit_out ; Emit it LOADI32_EAX &function_call_string_2 ; Using "push_ebp\t# Protect the old base pointer\n" CALL32 %emit_out ; Emit it LOADI32_EAX &function_call_string_3 ; Using "mov_edi,esp\t# Copy new base pointer\n" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S LOAD8_al_from_EAX ; global_token->S[0] MOVZX_al ; Make it useful CMPI8_EAX !41 ; IF global_token->S[0] == ")" JE32 %function_call_gen_done ; Then no arguments to send ;; looks like we have arguments to collect CALL32 %expression ; Collect the argument LOADI32_EAX &function_call_string_4 ; Using "push_eax\t#_process_expression1\n" CALL32 %emit_out ; Emit it LOADI32_ESI %1 ; PASSED = 1 :function_call_gen_iter LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S LOAD8_al_from_EAX ; global_token->S[0] MOVZX_al ; Make it useful CMPI8_EAX !44 ; IF global_token->S[0] == "," JNE32 %function_call_gen_done ; Otherwise we are done LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT CALL32 %expression ; Collect the argument LOADI32_EAX &function_call_string_5 ; Using "push_eax\t#_process_expression2\n" CALL32 %emit_out ; Emit it ADDI8_ESI !1 ; PASSED = PASSED + 1 JMP32 %function_call_gen_iter ; Keep trying :function_call_gen_done ;; All is collected LOADI32_EAX &function_call_string_6 ; Using "ERROR in process_expression_list\nNo ) was found\n" LOADI32_EBX &close_paren ; Using ")" CALL32 %require_match ; Make sure we have it CMPI8_EDX !0 ; IF(BOOL == TRUE) JNE32 %function_call_static ; Otherwise it is a static call ;; Deal with a passed function pointer LOADI32_EAX &function_call_string_7 ; Using "lea_eax,[ebp+DWORD] %" CALL32 %emit_out ; Emit it COPY_ECX_to_EAX ; Using S CALL32 %emit_out ; Emit it LOADI32_EAX &function_call_string_8 ; Using "\nmov_eax,[eax]\n" CALL32 %emit_out ; Emit it LOADI32_EAX &function_call_string_9 ; Using "mov_ebp,edi\n" CALL32 %emit_out ; Emit it LOADI32_EAX &function_call_string_10 ; Using "call_eax\n" CALL32 %emit_out ; Emit it LOADI32_EAX &function_call_string_13 ; Using "pop_ebx\t# _process_expression_locals\n" JMP32 %function_call_cleanup ; Clean up :function_call_static ;; Deal with fixed function name LOADI32_EAX &function_call_string_9 ; Using "mov_ebp,edi\n" CALL32 %emit_out ; Emit it LOADI32_EAX &function_call_string_11 ; Using "call %FUNCTION_" CALL32 %emit_out ; Emit it COPY_ECX_to_EAX ; Using S CALL32 %emit_out ; Emit it LOADI32_EAX &function_call_string_12 ; Using "\n" CALL32 %emit_out ; Emit it LOADI32_EAX &function_call_string_13 ; Using "pop_ebx\t# _process_expression_locals\n" :function_call_cleanup CMPI8_ESI !0 ; IF PASSED > 0 JLE32 %function_call_done ; Otherwise be done ;; The desired string is already in EAX CALL32 %emit_out ; Emit it SUBI8_ESI !1 ; PASSED = PASSED - 1 JMP32 %function_call_cleanup ; Keep going :function_call_done LOADI32_EAX &function_call_string_14 ; Using "pop_ebp\t# Restore old base pointer\n" CALL32 %emit_out ; Emit it LOADI32_EAX &function_call_string_15 ; Using "pop_edi\t# Prevent overwrite\n" CALL32 %emit_out ; Emit it POP_ESI ; Restore ESI POP_EDX ; Restore EDX POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :function_call_string_0 "ERROR in process_expression_list No ( was found " :function_call_string_1 "push_edi # Prevent overwriting in recursion " :function_call_string_2 "push_ebp # Protect the old base pointer " :function_call_string_3 "mov_edi,esp # Copy new base pointer " :function_call_string_4 "push_eax #_process_expression1 " :function_call_string_5 "push_eax #_process_expression2 " :function_call_string_6 "ERROR in process_expression_list No ) was found " :function_call_string_7 "lea_eax,[ebp+DWORD] %" :function_call_string_8 " mov_eax,[eax] " :function_call_string_9 "mov_ebp,edi " :function_call_string_10 "call_eax " :function_call_string_11 "call %FUNCTION_" :function_call_string_12 " " :function_call_string_13 "pop_ebx # _process_expression_locals " :function_call_string_14 "pop_ebp # Restore old base pointer " :function_call_string_15 "pop_edi # Prevent overwrite " ;; variable_load function ;; Receives struct token_list* A in EAX ;; Returns nothing ;; Updates output and current_target ;; Uses ECX for A :variable_load PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX COPY_EAX_to_ECX ; Protect A LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->S LOADI32_EAX &open_paren ; Using "(" CALL32 %match ; IF global_token->S == "(" CMPI8_EAX !0 ; Then it might be a function JNE32 %variable_load_regular ; Otherwise it is regular LOAD32_EBX_from_ECX_Immediate8 !12 ; A->TYPE LOAD32_EBX_from_EBX_Immediate8 !24 ; A->TYPE->NAME LOADI32_EAX &type_function_name ; Using "FUNCTION" CALL32 %match ; IF A->TYPE->NAME == "FUNCTION" CMPI8_EAX !0 ; Then it must be a function JNE32 %variable_load_regular ; otherwise just another regular ;; deal with function LOAD32_EAX_from_ECX_Immediate8 !16 ; A->DEPTH CALL32 %numerate_number ; Convert to string LOADI32_EBX %0 ; pass 0 for true CALL32 %function_call ; Create the function call JMP32 %variable_load_done ; Be done :variable_load_regular LOAD32_EAX_from_ECX_Immediate8 !12 ; A->TYPE STORE32_Absolute32_eax ¤t_target ; current_target = A->TYPE LOADI32_EAX &variable_load_string_0 ; Using "lea_eax,[ebp+DWORD] %" CALL32 %emit_out ; Emit it LOAD32_EAX_from_ECX_Immediate8 !16 ; A->DEPTH CALL32 %numerate_number ; Convert to string CALL32 %emit_out ; Emit it LOADI32_EAX &variable_load_string_1 ; Using "\n" CALL32 %emit_out ; Emit it ;; Check for special case of assignment LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->S LOADI32_EAX &equal ; Using "=" CALL32 %match ; IF global_token->S == "=" CMPI8_EAX !0 ; Then we skip loading JE32 %variable_load_done ; And be done ;; Deal with common case LOADI32_EAX &variable_load_string_2 ; Using "mov_eax,[eax]\n" CALL32 %emit_out ; Emit it :variable_load_done POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :variable_load_string_0 "lea_eax,[ebp+DWORD] %" :variable_load_string_1 " " :variable_load_string_2 "mov_eax,[eax] " ;; function_load function ;; Receives struct token_list* a in EAX ;; Returns nothing ;; Uses ECX to hold A->S :function_load PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX LOAD32_EAX_from_EAX_Immediate8 !8 ; A->S COPY_EAX_to_ECX ; Protect A->S LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->S LOADI32_EAX &open_paren ; Using "(" CALL32 %match ; IF global_token->S == "(" CMPI8_EAX !0 ; The we need to do a function call JNE32 %function_load_regular ; Otherwise just load it's address ;; Deal with function call COPY_ECX_to_EAX ; Using A->S LOADI32_EBX %1 ; Using FALSE CALL32 %function_call ; Deal with it JMP32 %function_load_done ; Be done :function_load_regular LOADI32_EAX &function_load_string_0 ; Using "mov_eax, &FUNCTION_" CALL32 %emit_out ; Emit it COPY_ECX_to_EAX ; Using A->S CALL32 %emit_out ; Emit it LOADI32_EAX &function_load_string_1 ; Using "\n" CALL32 %emit_out ; Emit it :function_load_done POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :function_load_string_0 "mov_eax, &FUNCTION_" :function_load_string_1 " " ;; global_load function ;; Receives struct token_list* A in EAX ;; Returns nothing ;; Uses EBX to hold A->S :global_load PUSH_EBX ; Protect EBX COPY_EAX_to_EBX ; Set as A LOAD32_EBX_from_EBX_Immediate8 !8 ; Set as A->S LOAD32_EAX_from_EAX_Immediate8 !12 ; A->TYPE STORE32_Absolute32_eax ¤t_target ; current_target = A->TYPE LOADI32_EAX &global_load_string_0 ; Using "mov_eax, &GLOBAL_" CALL32 %emit_out ; Emit it COPY_EBX_to_EAX ; Using A->S CALL32 %emit_out ; Emit it LOADI32_EAX &global_load_string_1 ; Using "\n" CALL32 %emit_out ; Emit it LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->S LOADI32_EAX &equal ; "=" CALL32 %match ; IF global_token->S == "=" CMPI8_EAX !0 ; We need to skip for assignment JE32 %global_load_done ; and be done ;; Otherwise we are loading the contents LOADI32_EAX &global_load_string_2 ; Using "mov_eax,[eax]\n" CALL32 %emit_out ; Emit it :global_load_done POP_EBX ; Restore EBX RETURN :global_load_string_0 "mov_eax, &GLOBAL_" :global_load_string_1 " " :global_load_string_2 "mov_eax,[eax] " ;; sym_lookup function ;; Receives char* S in EAX and struct token_list* symbol_list in EBX ;; Uses I->S in EAX, S in EBX and I in ECX ;; Returns match or NULL :sym_lookup PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX COPY_EBX_to_ECX ; I = symbol_list COPY_EAX_to_EBX ; Put S in the right place :sym_lookup_iter CMPI8_ECX !0 ; IF NULL == I JE32 %sym_lookup_done ; We failed to find match LOAD32_EAX_from_ECX_Immediate8 !8 ; Using I->S CALL32 %match ; IF I->S == S CMPI8_EAX !0 ; then be done JE32 %sym_lookup_done ; Failed LOAD32_ECX_from_ECX ; I = I->NEXT JMP32 %sym_lookup_iter ; otherwise keep looping :sym_lookup_done COPY_ECX_to_EAX ; Return I POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; primary_expr_number function ;; Receives nothing ;; Returns nothing ;; Simply uses current global token to update output and then steps to next global_token :primary_expr_number LOADI32_EAX &primary_expr_number_string_0 ; Using "mov_eax, %" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S CALL32 %emit_out ; Emit it LOADI32_EAX &primary_expr_number_string_1 ; Using "\n" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT RETURN :primary_expr_number_string_0 "mov_eax, %" :primary_expr_number_string_1 " " ;; primary_expr_string function ;; receives nothing ;; Returns nothing ;; creates entries for string and calls to generate string output ;; uses ECX for char* number_string :primary_expr_string PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX LOAD32_Absolute32_ebx ¤t_count ; Using current_count COPY_EBX_to_EAX ; And putting it in the right place CALL32 %numerate_number ; Get the string COPY_EAX_to_ECX ; protect number_string ADDI8_EBX !1 ; current_count + 1 STORE32_Absolute32_ebx ¤t_count ; current_count = current_count + 1 LOADI32_EAX &primary_expr_string_string_0 ; Using "mov_eax, &STRING_" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S COPY_ECX_to_EBX ; Put number_string in the right place CALL32 %uniqueID_out ; Make it unique ;; Generate the target LOADI32_EAX &primary_expr_string_string_1 ; Using ":STRING_" LOAD32_Absolute32_ebx &strings_list ; Using strings_list CALL32 %emit ; Emit it COPY_EAX_to_EBX ; put new strings_list in place LOAD32_Absolute32_eax &function ; Using function LOAD32_EAX_from_EAX_Immediate8 !8 ; function->S CALL32 %uniqueID ; Make it unique COPY_EAX_to_EBX ; put new strings_list in place ;; Parse the string LOAD32_Absolute32_eax &global_token ; Using global token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S CALL32 %parse_string ; convert to useful form CALL32 %emit ; Emit it STORE32_Absolute32_eax &strings_list ; Update Strings _list LOAD32_Absolute32_eax &global_token ; Using global token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :primary_expr_string_string_0 "mov_eax, &STRING_" :primary_expr_string_string_1 ":STRING_" ;; primary_expr_char function ;; Receives nothing ;; Returns nothing ;; Updates output_list using global_token :primary_expr_char PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX LOADI32_EAX &primary_expr_char_string_0 ; Using "mov_eax, %" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &global_token ; Using global token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S ADDI8_EAX !1 ; global_token->S + 1 CALL32 %escape_lookup ; Get the char CALL32 %numerate_number ; Convert to string CALL32 %emit_out ; emit it LOADI32_EAX &primary_expr_char_string_1 ; Using "\n" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &global_token ; Using global token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :primary_expr_char_string_0 "mov_eax, %" :primary_expr_char_string_1 " " ;; primary_expr_failure function ;; Receives nothing ;; Does not return but aborts hard ;; Complains about the bad input :primary_expr_failure ; CALL32 %line_error ; Get line of issue LOADI32_EAX %2 ; Using Standard error STORE32_Absolute32_eax &Output_file ; write to standard error LOADI32_EAX &primary_expr_failure_string_0 ; Using "Received " CALL32 %File_Print ; Print it LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S CALL32 %File_Print ; Print it LOADI32_EAX &primary_expr_failure_string_1 ; Using " in primary_expr\n" CALL32 %File_Print ; Print it JMP32 %Exit_Failure ; Abort Hard :primary_expr_failure_string_0 "Received " :primary_expr_failure_string_1 " in primary_expr " ;; general_recursion function ;; Receives FUNCTION F in EAX, char* S in EBX, char* name in ECX and FUNCTION iterate in EDX ;; Returns nothing ;; Uses ECX for char* S, EDX for FUNCTION iterate and EBP for FUNCTION F ;; But generally recurses a shitload :general_recursion PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX PUSH_EDX ; Protect EDX PUSH_EBP ; Protect EBP COPY_EAX_to_EBP ; Protect F COPY_ECX_to_EAX ; Put name in the right place COPY_EBX_to_ECX ; Protect S LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->S CALL32 %match ; IF match(name, global_token->s) CMPI8_EAX !0 ; If true we do JNE32 %general_recursion_done ; Otherwise skip it ;; Deal with the recursion COPY_EBP_to_EAX ; Put F in the right place CALL32 %common_recursion ; Recurse COPY_ECX_to_EAX ; Put S in the right place CALL32 %emit_out ; Emit it COPY_EDX_to_EAX ; Put iterate in the right place CALL_EAX ; Down the rabbit hole :general_recursion_done POP_EBP ; Restore EBP POP_EDX ; Restore EDX POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; promote_type function ;; Receives struct type* a in EAX and struct type* b in EBX ;; Returns the most recent type in EAX ;; Uses EAX for struct type* I, ECX for struct type* A and EDX for struct type* B :promote_type PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX PUSH_EDX ; Protect EDX CMPI8_EBX !0 ; IF NULL == B JE32 %promote_type_done ; Just return A COPY_EAX_to_ECX ; Put A in place COPY_EBX_to_EDX ; Put B in place COPY_EBX_to_EAX ; IF NULL == A CMPI8_ECX !0 ; Then we just return B JE32 %promote_type_done ; Be done ;; Looks like we need to walk the list LOAD32_ECX_from_ECX_Immediate8 !24 ; A->NAME LOAD32_EDX_from_EDX_Immediate8 !24 ; B->NAME LOAD32_Absolute32_eax &global_types ; I = global_types :promote_type_iter CMPI8_EAX !0 ; IF NULL == I JE32 %promote_type_done ; Just be done LOAD32_EBX_from_EAX_Immediate8 !24 ; I->NAME CMP_ECX_EBX ; IF(A->NAME == I->NAME) JE32 %promote_type_done ; Be done CMP_EBX_EDX ; IF(B->NAME == I->NAME) JE32 %promote_type_done ; Be done LOAD32_EBX_from_EAX_Immediate8 !12 ; I->INDIRECT LOAD32_EBX_from_EBX_Immediate8 !24 ; I->INDIRECT->NAME CMP_ECX_EBX ; IF(A->NAME == I->INDIRECT->NAME) JE32 %promote_type_done ; Be done CMP_EBX_EDX ; IF(B->NAME == I->INDIRECT->NAME) JE32 %promote_type_done ; Be done LOAD32_EAX_from_EAX ; I = I->NEXT JMP32 %promote_type_iter ; Keep going :promote_type_done POP_EDX ; Restore EDX POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; common_recursion function ;; Receives FUNCTION F in EAX ;; Returns Nothing ;; Walks global_token list and update output_list ;; Updates current_target ;; Uses EBX to hold FUNCTION F and struct type* last_type :common_recursion PUSH_EBX ; Protect EBX COPY_EAX_to_EBX ; Put FUNCTION F safely out of the way LOADI32_EAX &common_recursion_string_0 ; Using "push_eax\t#_common_recursion\n" CALL32 %emit_out ; Emit it LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT COPY_EBX_to_EAX ; Prepare for function call LOAD32_Absolute32_ebx ¤t_target ; Get last type CALL_EAX ; F(); LOAD32_Absolute32_eax ¤t_target ; Get current_target CALL32 %promote_type ; get the right type STORE32_Absolute32_eax ¤t_target ; Set new current_target LOADI32_EAX &common_recursion_string_1 ; Using "pop_ebx\t# _common_recursion\n" CALL32 %emit_out ; Emit it POP_EBX ; Restore EBX RETURN :common_recursion_string_0 "push_eax #_common_recursion " :common_recursion_string_1 "pop_ebx # _common_recursion " ;; require_match function ;; Receives char* message in EAX and char* required in EBX ;; Returns nothing ;; Uses ECX to hold message and updates global_token :require_match PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX COPY_EAX_to_ECX ; put the message somewhere safe LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S CALL32 %match ; IF required == global_token->S CMPI8_EAX !0 ; we are fine JE32 %require_match_good ; otherwise pain ;; Deal will bad times ; CALL32 %line_error ; Tell user what went wrong LOADI32_EAX %2 ; Using standard error STORE32_Absolute32_eax &Output_file ; write to standard error COPY_ECX_to_EAX ; using our message CALL32 %File_Print ; Print it JMP32 %Exit_Failure ; Abort HARD :require_match_good LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->next STORE32_Absolute32_eax &global_token ; global_token = global_token->next POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; uniqueID Function ;; Receives char *S in EAX, struct token_list* l in EBX and char* num in ECX ;; Returns updated struct token_list* L in EAX :uniqueID PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX CALL32 %emit ; emit(s, l) COPY_EAX_to_EBX ; Put L in correct place LOADI32_EAX &underline ; Using "_" CALL32 %emit ; emit("_", l) COPY_EAX_to_EBX ; Put L in correct place COPY_ECX_to_EAX ; Put num in correct place CALL32 %emit ; emit(num, l) COPY_EAX_to_EBX ; Put L in correct place LOADI32_EAX &uniqueID_string_0 ; Using "\n" CALL32 %emit ; emit("\n", l) POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :uniqueID_string_0 " " ;; uniqueID_out function ;; Receives char* S in EAX and char* num in EBX ;; Returns nothing :uniqueID_out PUSH_EAX ; Protect EAX PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX COPY_EBX_to_ECX ; Put num in right spot LOAD32_Absolute32_ebx &output_list ; Using output_list CALL32 %uniqueID ; Get updated list STORE32_Absolute32_eax &output_list ; output_list = uniqueID(s, output_list, num) POP_ECX ; Restore ECX POP_EBX ; Restore EBX POP_EAX ; Restore EAX RETURN ;; emit_out function ;; Receives char* S in EAX ;; Returns nothing ;; Updates output_list ;; MUST NOT ALTER REGISTERS :emit_out PUSH_EAX ; Protect EAX PUSH_EBX ; Protect EBX LOAD32_Absolute32_ebx &output_list ; Using output_list CALL32 %emit ; emit it STORE32_Absolute32_eax &output_list ; update it POP_EBX ; Restore EBX POP_EAX ; Restore EAX RETURN ;; emit function ;; Receives char *s in EAX and struct token_list* head in EBX ;; Returns struct token_list* T in EAX :emit PUSH_ECX ; Protect ECX COPY_EAX_to_ECX ; put S out of the way LOADI32_EAX %20 ; sizeof(struct token_list) CALL32 %malloc ; get T STORE32_EBX_into_EAX ; t->next = head; STORE32_ECX_into_EAX_Immediate8 !8 ; t->s = s; POP_ECX ; Restore ECX RETURN ;; escape_lookup function ;; Receives char* c in EAX ;; Returns integer value of char in EAX ;; Aborts hard if unknown escape is received ;; Uses ECX to hold char* C :escape_lookup PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX COPY_EAX_to_ECX ; Put char* C in safe place LOAD8_al_from_ECX ; Load c[0] MOVZX_al ; make it useful CMPI8_EAX !92 ; If '\\' != c[0] JNE32 %escape_lookup_done ; Be done COPY_ECX_to_EBX ; Prepare for walk ADDI8_EBX !1 ; increment LOAD8_bl_from_EBX ; load c[1] MOVZX_bl ; make it useful CMPI8_EBX !120 ; Check if \x?? JE32 %escape_lookup_hex ; Deal with hex ;; Deal with \? escapes LOADI32_EAX %10 ; Guess "\n" CMPI8_EBX !110 ; If n JE32 %escape_lookup_done ; Be done LOADI32_EAX %9 ; Guess "\t" CMPI8_EBX !116 ; If t JE32 %escape_lookup_done ; Be done COPY_EBX_to_EAX ; "\\", "'" and '\"' all encode as themselves CMPI8_EBX !92 ; If "\\" JE32 %escape_lookup_done ; Be done CMPI8_EBX !39 ; IF "'" JE32 %escape_lookup_done ; Be done CMPI8_EBX !34 ; IF '\"' JE32 %escape_lookup_done ; Be done LOADI32_EAX %13 ; Guess "\r" CMPI8_EBX !114 ; IF r JE32 %escape_lookup_done ; Be done ;; Looks like we have no clue what we are doing ;; Aborting hard LOADI32_EAX %2 ; Using Standard error STORE32_Absolute32_eax &Output_file ; write to standard error LOADI32_EAX &escape_lookup_string_0 ; Using "Unknown escape received: " CALL32 %File_Print ; Print it COPY_ECX_to_EAX ; Using C CALL32 %File_Print ; Print it LOADI32_EAX &escape_lookup_string_1 ; Using " Unable to process\n" CALL32 %File_Print ; Print it JMP32 %Exit_Failure ; Abort Hard :escape_lookup_done POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :escape_lookup_hex ;; Give up on C and just assume they know what they are doing ADDI8_ECX !2 ; increment LOAD8_al_from_ECX ; c[2] MOVZX_al ; make it useful ADDI8_ECX !1 ; increment CALL32 %char2hex ; Get the hex value SALI8_EAX !4 ; c << 4 LOAD8_bl_from_ECX ; c[3] MOVZX_bl ; make it useful SWAP_EAX_EBX ; protect c << 4 CALL32 %char2hex ; Get the hex value ADD_ebx_into_eax ; hex(c[2]) << 4 + hex(c[3]) JMP32 %escape_lookup_done ; Be done :escape_lookup_string_0 "Unknown escape received: " :escape_lookup_string_1 " Unable to process " ;; char2hex function ;; Receives char in EAX ;; Returns hex or aborts hard :char2hex SUBI8_EAX !48 ; Try 0-9 CMPI8_EAX !10 ; Otherwise fun times JL32 %char2hex_done ; Be done ;; Deal with A-F ANDI32_EAX %0xDF ; Unset High bit turning a-f into A-F SUBI8_EAX !7 ; Shift down into position CMPI8_EAX !10 ; Everything below A is bad JL32 %char2hex_fail ; And should fail CMPI8_EAX !16 ; Make sure we are below F JL32 %char2hex_done ; If so be done :char2hex_fail ;; Time to fail hard LOADI32_EAX %2 ; Using Standard error STORE32_Absolute32_eax &Output_file ; write to standard error LOADI32_EAX &char2hex_string_0 ; Using "Tried to print non-hex number\n" CALL32 %File_Print ; Print it JMP32 %Exit_Failure ; Abort Hard :char2hex_done RETURN :char2hex_string_0 "Tried to print non-hex number " ;; parse_string function ;; Receives char* string in EAX ;; Returns cleaned up string ;; Protects char* string in EBX :parse_string PUSH_EBX ; Protect EBX COPY_EAX_to_EBX ; Protect char* string CALL32 %weird ; Determine if we have a weird string CMPI8_EAX !0 ; If weird JE32 %parse_string_weird ; Deal with it ;; Dealing with regular string COPY_EBX_to_EAX ; Passing Char* string CALL32 %collect_regular_string ; Collect it JMP32 %parse_string_done ; Be done :parse_string_weird COPY_EBX_to_EAX ; Passing Char* string CALL32 %collect_weird_string ; Collect it :parse_string_done POP_EBX ; Restore EBX RETURN ;; weird function ;; Receives char* string in EAX ;; Returns true(0) or false(1) in EAX ;; Uses ECX to hold char* string :weird PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX COPY_EAX_to_ECX ; Place string in safe place ADDI8_ECX !1 ; increment past the '\"' :weird_reset LOAD8_al_from_ECX ; Load a char MOVZX_al ; Make it useful CMPI8_EAX !0 ; IF NULL == C JE32 %weird_false ; Nothing weird found CMPI8_EAX !92 ; IF '\\' JNE32 %weird_escaped ; Deal with escaping ;; Deal with escape COPY_ECX_to_EAX ; We are passing the string CALL32 %escape_lookup ; to look it up ADDI8_ECX !1 ; string = string + 1 LOAD8_bl_from_ECX ; get string[1] MOVZX_bl ; make it useful CMPI8_EBX !120 ; IF 'x' == string[1] JNE32 %weird_escaped ; otherwise skip the gap ADDI8_ECX !2 ; string = string + 2 :weird_escaped PUSH_EAX ; Protect C in case we need it LOADI32_EBX &weird_string_0 ; Use "\t\n !#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" CALL32 %In_Set ; To find if weird CMPI8_EAX !1 ; IF TRUE POP_EAX ; Restore C JNE32 %weird_true ; Then not weird ADDI8_ECX !1 ; string = string + 1 ;; Last chance for weird LOADI32_EBX &weird_string_1 ; Use "\t\n\r " CALL32 %In_Set ; Check for special case CMPI8_EAX !1 ; IF TRUE JNE32 %weird_reset ; Otherwise not in the special case ;; Deal with possible special case LOAD8_al_from_ECX ; Load string[1] MOVZX_al ; Make it useful CMPI8_EAX !58 ; IF string[1] == ":" JE32 %weird_true ; Then we hit the special case JMP32 %weird_reset ; Keep trying :weird_done POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :weird_true LOADI32_EAX %0 ; Return true JMP32 %weird_done ; Be done :weird_false LOADI32_EAX %1 ; Return false JMP32 %weird_done ; Be done :weird_string_0 " !#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" :weird_string_1 '09 0A 0D 20' ; "\t\n\r " ;; collect_regular_string function ;; Receives char* string in EAX ;; Malloc and creates new string to return in EAX ;; Uses ECX for return string and EDX for passed string :collect_regular_string PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX PUSH_EDX ; Protect EDX COPY_EAX_to_EDX ; Protect our passed string LOADI32_EAX %256 ; We need 256bytes of storage CALL32 %malloc ; Get our new pointer COPY_EAX_to_ECX ; put it in place PUSH_EAX ; protect until done :collect_regular_string_reset LOAD8_al_from_EDX ; string[0] MOVZX_al ; Make it useful CMPI8_EAX !0 ; See if we hit the end JE32 %collect_regular_string_done ; And be done CMPI8_EAX !92 ; IF string[0] == '\\' JE32 %collect_regular_string_escaped ; Deal with that mess ;; deal with boring char STORE8_al_into_Address_ECX ; hold_string[index] = string[0] ADDI8_ECX !1 ; Increment it ADDI8_EDX !1 ; Increment it JMP32 %collect_regular_string_reset ; And keep going :collect_regular_string_escaped COPY_EDX_to_EAX ; Using string CALL32 %escape_lookup ; Get the char STORE8_al_into_Address_ECX ; hold_string[index] = escape_lookup(string) ADDI8_EDX !1 ; Increment it ADDI8_ECX !1 ; Increment it LOAD8_al_from_EDX ; string[0] MOVZX_al ; Make it useful ADDI8_EDX !1 ; Increment it CMPI8_EAX !120 ; IF 'x' == string[1] JNE32 %collect_regular_string_reset ; Otherwise keep going ADDI8_EDX !2 ; Increment it JMP32 %collect_regular_string_reset ; Keep going :collect_regular_string_done LOADI32_EAX %34 ; Using '\"' STORE8_al_into_Address_ECX ; hold_string[index] = '\"' ADDI8_ECX !1 ; Increment it LOADI32_EAX %10 ; Using "\n" STORE8_al_into_Address_ECX ; hold_string[index] = '\n' POP_EAX ; Return our new string POP_EDX ; Restore EDX POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; collect_weird_string function ;; Receives char* string in EAX ;; Mallocs and returns char* hold in EAX ;; Uses ECX for char* hold and EDX for char* string :collect_weird_string PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX PUSH_EDX ; Protect EDX COPY_EAX_to_EDX ; Protect our passed string LOADI32_EAX %512 ; We need 512bytes of storage CALL32 %malloc ; Get our new pointer COPY_EAX_to_ECX ; put it in place PUSH_EAX ; protect until done LOADI32_EAX %39 ; Using "'" STORE8_al_into_Address_ECX ; hold_string[index] = "'" ADDI8_ECX !1 ; Increment it ADDI8_EDX !1 ; Increment it :collect_weird_string_reset LOAD8_al_from_EDX ; Read a byte MOVZX_al ; Make it useful CMPI8_EAX !0 ; IF NULL == string[0] JE32 %collect_weird_string_done ; Be done LOADI32_EAX %32 ; Using ' ' STORE8_al_into_Address_ECX ; hold_string[index] = ' ' ADDI8_ECX !1 ; Increment it COPY_EDX_to_EAX ; Using string CALL32 %escape_lookup ; Get the char CALL32 %hex8 ; Update ECX LOAD8_al_from_EDX ; Read a byte MOVZX_al ; Make it useful ADDI8_EDX !1 ; Increment it CMPI8_EAX !92 ; IF string[0] == '\\' JNE32 %collect_weird_string_reset ; Otherwise keep going LOAD8_al_from_EDX ; Read a byte MOVZX_al ; Make it useful ADDI8_EDX !1 ; Increment it CMPI8_EAX !120 ; IF 'x' == string[1] JNE32 %collect_weird_string_reset ; Otherwise keep going ADDI8_EDX !2 ; Increment it JMP32 %collect_weird_string_reset ; Keep going :collect_weird_string_done LOADI32_EAX %32 ; Using ' ' STORE8_al_into_Address_ECX ; hold_string[index] = ' ' ADDI8_ECX !1 ; Increment it LOADI32_EAX %48 ; Using '0' STORE8_al_into_Address_ECX ; hold_string[index] = '0' ADDI8_ECX !1 ; Increment it STORE8_al_into_Address_ECX ; hold_string[index] = '0' ADDI8_ECX !1 ; Increment it LOADI32_EAX %39 ; Using "'" STORE8_al_into_Address_ECX ; hold_string[index] = "'" ADDI8_ECX !1 ; Increment it LOADI32_EAX %10 ; Using "\n" STORE8_al_into_Address_ECX ; hold_string[index] = '\n' POP_EAX ; Return our new string POP_EDX ; Restore EDX POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; HEX to ascii routine ;; Receives INT in EAX and CHAR* in ECX ;; Stores ascii of INT in CHAR* ;; Returns only modifying EAX and ECX :hex8 PUSH_EAX ; Protect bottom nibble SHRI8_EAX !4 ; do high nibble first CALL32 %hex4 ; Store it POP_EAX ; do low nibble :hex4 ANDI32_EAX %0xF ; isolate nibble ADDI8_EAX !48 ; convert to ascii CMPI8_EAX !57 ; valid digit? JBE8 !hex1 ; yes ADDI8_EAX !7 ; use alpha range :hex1 STORE8_al_into_Address_ECX ; store result ADDI8_ECX !1 ; next position RETURN ;; type_name function ;; Receives nothing ;; Returns type_size in EAX ;; Uses ECX for STRUCT TYPE* RET :type_name PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX_Immediate8 !8 ; global_token->S LOADI32_EAX &struct ; Using "struct" CALL32 %match ; IF global_token->S == "struct" COPY_EAX_to_ECX ; Protect structure CMPI8_EAX !0 ; need to skip over "struct" JNE32 %type_name_native ; otherwise keep going ;; Deal with possible STRUCTs LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX ; global_token->next STORE32_Absolute32_ebx &global_token ; global_token = global_token->next LOAD32_EAX_from_EBX_Immediate8 !8 ; global_token->S LOAD32_Absolute32_ebx &global_types ; get all known types CALL32 %lookup_type ; Find type if possible COPY_EAX_to_ECX ; Set ret CMPI8_EAX !0 ; IF NULL == ret JNE32 %type_name_common ; We have to create struct ;; Create a struct CALL32 %create_struct ; Create a new struct LOADI32_ECX %0 ; We wish to return NULL JMP32 %type_name_done ; be done :type_name_native ;; Deal only with native types COPY_EBX_to_EAX ; Put global_token->S in the right place LOAD32_Absolute32_ebx &global_types ; get all known types CALL32 %lookup_type ; Find the type if possible COPY_EAX_to_ECX ; Set ret CMPI8_EAX !0 ; IF NULL == ret JNE32 %type_name_common ; We need to abort hard ;; Aborting hard LOADI32_EAX %2 ; Using Standard error STORE32_Absolute32_eax &Output_file ; write to standard error LOADI32_EAX &type_name_string_0 ; Print header CALL32 %File_Print ; Print it LOAD32_Absolute32_eax &global_token ; Using global token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S CALL32 %File_Print ; Print it LOADI32_EAX &type_name_string_1 ; Print footer CALL32 %File_Print ; Print it ; CALL32 %line_error ; Give details JMP32 %Exit_Failure ; Abort :type_name_common LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX ; global_token->next STORE32_Absolute32_ebx &global_token ; global_token = global_token->next :type_name_iter LOAD32_EAX_from_EBX_Immediate8 !8 ; global_token->S LOAD8_al_from_EAX ; global_token->S[0] MOVZX_al ; make it useful CMPI8_EAX !42 ; IF global_token->S[0] == '*' JNE32 %type_name_done ; recurse ;; Deal with char** LOAD32_ECX_from_ECX_Immediate8 !12 ; ret = ret->indirect LOAD32_Absolute32_ebx &global_token ; Using global_token LOAD32_EBX_from_EBX ; global_token->next STORE32_Absolute32_ebx &global_token ; global_token = global_token->next JMP32 %type_name_iter ; keep looping :type_name_done COPY_ECX_to_EAX ; put ret in the right place POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :type_name_string_0 "Unknown type " :type_name_string_1 " " ;; lookup_type function ;; Receives char* s in EAX and struct type* start in EBX ;; Returns struct type* in EAX ;; Uses EBX for S and ECX for I :lookup_type PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX COPY_EBX_to_ECX ; I = Start COPY_EAX_to_EBX ; Put S in place :lookup_type_iter CMPI8_ECX !0 ; Check if I == NULL JE32 %lookup_type_done ; return NULL LOAD32_EAX_from_ECX_Immediate8 !24 ; I->NAME CALL32 %match ; Check if matching CMPI8_EAX !0 ; IF I->NAME == S JE32 %lookup_type_done ; return it LOAD32_ECX_from_ECX ; Otherwise I = I->NEXT JMP32 %lookup_type_iter ; And keep looping :lookup_type_done COPY_ECX_to_EAX ; return either I or NULL POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; create_struct function ;; Receives nothing ;; Returns nothing ;; Uses global_token to malloc a struct's definition ;; Uses ECX for int OFFSET, EDX for struct type* head, EBP for struct type* I, ;; EDI for member_size (Which is passed) and ESI for LAST ;; EAX and EBX are used for scratch :create_struct PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX PUSH_EDX ; Protect EDX PUSH_EBP ; Protect EBP PUSH_EDI ; Protect EDI PUSH_ESI ; Protect ESI LOADI32_ECX %0 ; OFFSET = 0 LOADI32_EDI %0 ; member_size = 0 LOADI32_EAX %28 ; sizeof(struct type) CALL32 %malloc ; malloc(sizeof(struct type)) COPY_EAX_to_EDX ; Set HEAD LOADI32_EAX %28 ; sizeof(struct type) CALL32 %malloc ; malloc(sizeof(struct type)) COPY_EAX_to_EBP ; Set I LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S STORE32_EAX_into_EDX_Immediate8 !24 ; HEAD->NAME = global_token->S STORE32_EAX_into_EBP_Immediate8 !24 ; I->NAME = global_token->S STORE32_EBP_into_EDX_Immediate8 !12 ; HEAD->INDIRECT = I STORE32_EDX_into_EBP_Immediate8 !12 ; I->INDIRECT = HEAD LOAD32_Absolute32_eax &global_types ; Using global_types STORE32_EAX_into_EDX ; HEAD->NEXT = global_types STORE32_Absolute32_edx &global_types ; global_types = HEAD LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT LOADI32_EAX %4 ; Using register size STORE32_EAX_into_EBP_Immediate8 !4 ; I->SIZE = register size LOADI32_EAX &create_struct_string_0 ; Using "ERROR in create_struct\n Missing {\n" LOADI32_EBX &open_curly_brace ; Using "{" CALL32 %require_match ; Make sure we have it LOADI32_ESI %0 ; LAST = NULL :create_struct_iter LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S LOAD8_al_from_EAX ; global_token->S[0] MOVZX_al ; Make it useful CMPI8_EAX !125 ; IF global_token->S[0] == "}" JE32 %create_struct_done ; be done ;; Looks like we are adding members ;; Lets see if it is a union LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S LOADI32_EBX &union ; Using "union" CALL32 %match ; IF match(global_token->s, "union") CMPI8_EAX !0 ; Deal with union JNE32 %create_struct_single ; Otherwise deal with singles ;; Deal with union COPY_ESI_to_EAX ; Put last in right place COPY_ECX_to_EBX ; put offset in right place CALL32 %build_union ; ASSEMBLE COPY_EAX_to_ESI ; last = build_union(last, offset) ADD_edi_into_ecx ; offset = offset + member_size LOADI32_EAX &create_struct_string_1 ; Using "ERROR in create_struct\n Missing ;\n" LOADI32_EBX &semicolon ; Using ";" CALL32 %require_match ; Make sure we have it JMP32 %create_struct_iter ; keep going :create_struct_single ;; deal with singles COPY_ESI_to_EAX ; Put last in right place COPY_ECX_to_EBX ; put offset in right place CALL32 %build_member ; ASSEMBLE COPY_EAX_to_ESI ; last = build_union(last, offset) ADD_edi_into_ecx ; offset = offset + member_size LOADI32_EAX &create_struct_string_1 ; Using "ERROR in create_struct\n Missing ;\n" LOADI32_EBX &semicolon ; Using ";" CALL32 %require_match ; Make sure we have it JMP32 %create_struct_iter ; keep going :create_struct_done LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT LOADI32_EAX &create_struct_string_1 ; Using "ERROR in create_struct\n Missing ;\n" LOADI32_EBX &semicolon ; Using ";" CALL32 %require_match ; Make sure we have it STORE32_ECX_into_EDX_Immediate8 !4 ; HEAD->SIZE = OFFSET STORE32_ESI_into_EDX_Immedate8 !16 ; HEAD->MEMBERS = LAST STORE32_ESI_into_EBP_Immedate8 !16 ; I->MEMBERS = LAST POP_ESI ; Restore ESI POP_EDI ; Restore EDI POP_EBP ; Restore EBP POP_EDX ; Restore EDX POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :create_struct_string_0 "ERROR in create_struct Missing { " :create_struct_string_1 "ERROR in create_struct Missing ; " ;; lookup_member function ;; Receives struct type* parent in EAX and char* name in EBX ;; Returns struct type* I in EAX ;; Uses char* NAME in EBX, ECX for struct type* I and EDX to hold parent for errors ;; Aborts hard if not found :lookup_member PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX PUSH_EDX ; Protect EDX COPY_EAX_to_EDX ; Protect Parent LOAD32_ECX_from_EAX_Immediate8 !16 ; struct type* I = parent->MEMBERS :lookup_member_iter CMPI8_ECX !0 ; IF I == NULL JE32 %lookup_member_fail ; Abort HARD LOAD32_EAX_from_ECX_Immediate8 !24 ; Using I->NAME CALL32 %match ; IF I->NAME == NAME CMPI8_EAX !0 ; Then we have found the member COPY_ECX_to_EAX ; Prepare for return LOAD32_ECX_from_ECX_Immediate8 !16 ; Prepare for loop I = I->MEMBERS JNE32 %lookup_member_iter ; Looks like we are looping ;; I is already in EAX POP_EDX ; Restore EDX POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :lookup_member_fail LOADI32_EAX %2 ; Using Standard error STORE32_Absolute32_eax &Output_file ; write to standard error LOADI32_EAX &lookup_member_string_0 ; Using "ERROR in lookup_member " CALL32 %File_Print ; print it LOAD32_EAX_from_EDX_Immediate8 !24 ; PARENT->NAME CALL32 %File_Print ; print it LOADI32_EAX &arrow_string ; Using "->" CALL32 %File_Print ; print it LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S CALL32 %File_Print ; print it LOADI32_EAX &lookup_member_string_1 ; Using " does not exist\n" CALL32 %File_Print ; print it ; CALL32 %line_error ; Write useful debug info LOADI32_EAX &lookup_member_string_2 ; Using "\n" CALL32 %File_Print ; print it JMP32 %Exit_Failure ; Abort Hard :lookup_member_string_0 "ERROR in lookup_member " :lookup_member_string_1 " does not exist " :lookup_member_string_2 " " ;; build_member function ;; Receives struct type* last in EAX, int offset in EBX and global member_size in EDI ;; Updates member_size in EDI and returns struct type* I in EAX ;; Uses ECX for struct type* member_type and EDX for struct type* I :build_member PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX PUSH_EDX ; Protect EDX COPY_EAX_to_EDX ; Put last out of the way LOADI32_EAX %28 ; Allocate type CALL32 %malloc ; Get I STORE32_EDX_into_EAX_Immediate8 !16 ; I->MEMBERS = LAST STORE32_EBX_into_EAX_Immediate8 !8 ; I->OFFSET = OFFSET COPY_EAX_to_EDX ; Put I in place CALL32 %type_name ; Get member_type COPY_EAX_to_ECX ; Put in place STORE32_ECX_into_EDX_Immediate8 !20 ; I->TYPE = MEMBER_TYPE LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EBX_from_EAX_Immediate8 !8 ; global_token->S STORE32_EBX_into_EDX_Immediate8 !24 ; I->NAME = global_token->S LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT ;; Check if we have an array LOAD32_EBX_from_EAX_Immediate8 !8 ; global_token->S LOADI32_EAX &open_bracket ; Using "[" CALL32 %match ; IF global_token->S == "[" CMPI8_EAX !0 ; Then we have to deal with arrays in our structs JE32 %build_member_array ; So deal with that pain ;; Deal with non-array case LOAD32_EAX_from_ECX_Immediate8 !4 ; member_type->SIZE STORE32_EAX_into_EDX_Immediate8 !4 ; I->SIZE = member_type->SIZE JMP32 %build_member_done ; Be done :build_member_array LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S CALL32 %numerate_string ; convert number LOAD32_EBX_from_ECX_Immediate8 !20 ; member_type->TYPE LOAD32_EBX_from_EBX_Immediate8 !4 ; member_type->TYPE->SIZE IMUL_EAX_by_EBX ; member_type->type->size * numerate_string(global_token->s) STORE32_EAX_into_EDX_Immediate8 !4 ; I->SIZE = member_type->type->size * numerate_string(global_token->s) LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT LOADI32_EAX &build_member_string_0 ; Using "Struct only supports [num] form\n" LOADI32_EBX &close_bracket ; Using "]" CALL32 %require_match ; Make sure we have it :build_member_done LOAD32_EDI_from_EDX_Immediate8 !4 ; MEMBER_SIZE = I->SIZE STORE32_ECX_into_EDX_Immediate8 !20 ; I->TYPE = MEMBER_TYPE COPY_EDX_to_EAX ; Return I POP_EDX ; Restore EDX POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :build_member_string_0 "Struct only supports [num] form " ;; build_union function ;; Receives struct type* last in EAX, int offset in EBX and global member_size in EDI ;; Updates member_size in EDI and returns struct type* LAST in EAX ;; Uses ECX for struct type* last, EDX for int offset, ESI for int size and EDI for int member_size :build_union PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX PUSH_EDX ; Protect EDX PUSH_ESI ; Protect ESI COPY_EAX_to_ECX ; Put LAST in right spot COPY_EBX_to_EDX ; Put OFFSET in right spot LOADI32_ESI %0 ; SIZE = 0 LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT LOADI32_EAX &build_union_string_0 ; Using "ERROR in build_union\nMissing {\n" LOADI32_EBX &open_curly_brace ; Using "{" CALL32 %require_match ; Make sure we have it :build_union_iter LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX_Immediate8 !8 ; global_token->S LOAD8_al_from_EAX ; global_token->S[0] MOVZX_al ; make it useful CMPI8_EAX !125 ; IF global_token->S[0] == "}" JE32 %build_union_done ; Be done ;; Collect union member COPY_ECX_to_EAX ; Passing LAST COPY_EDX_to_EBX ; Passing offset CALL32 %build_member ; build_member(last, offset) COPY_EAX_to_ECX ; last = build_member(last, offset) CMP_EDI_ESI ; IF member_size > size JG32 %build_union_size ; Then update size ;; deal with member_size > size COPY_EDI_to_ESI ; SIZE = MEMBER_SIZE :build_union_size LOADI32_EAX &build_union_string_1 ; Using "ERROR in build_union\nMissing ;\n" LOADI32_EBX &semicolon ; Using ";" CALL32 %require_match ; Make sure we have it JMP32 %build_union_iter ; Keep going :build_union_done COPY_ESI_to_EDI ; MEMBER_SIZE = SIZE LOAD32_Absolute32_eax &global_token ; Using global_token LOAD32_EAX_from_EAX ; global_token->NEXT STORE32_Absolute32_eax &global_token ; global_token = global_token->NEXT COPY_ECX_to_EAX ; Return last POP_ESI ; Restore ESI POP_EDX ; Restore EDX POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :build_union_string_0 "ERROR in build_union Missing { " :build_union_string_1 "ERROR in build_union Missing ; " ;; sym_declare function ;; Receives char *s in EAX, struct type* t in EBX, and struct token_list* list in ECX ;; Returns struct token_list* in EAX ;; Uses EAX for A :sym_declare PUSH_EDX ; Protect EDX COPY_EAX_to_EDX ; Get char *S safely out of the way LOADI32_EAX %20 ; Using sizeof(struct token_list) CALL32 %malloc ; Get pointer to A STORE32_ECX_into_EAX ; A->NEXT = LIST STORE32_EDX_into_EAX_Immediate8 !8 ; A->S = S STORE32_EBX_into_EAX_Immediate8 !12 ; A->TYPE = T POP_EDX ; Restore EDX RETURN ;; match function ;; Receives CHAR* in EAX and CHAR* in EBX ;; Returns 0 (TRUE) or 1 (FALSE) in EAX :match PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX PUSH_EDX ; Protect EDX COPY_EAX_to_ECX ; S1 in place COPY_EBX_to_EDX ; S2 in place :match_Loop LOAD8_al_from_ECX ; S1[0] MOVZX_al ; Make it useful LOAD8_bl_from_EDX ; S2[0] MOVZX_bl ; Make it useful CMP_EAX_EBX ; See if they match JNE32 %match_False ; If not ADDI8_ECX !1 ; S1 = S1 + 1 ADDI8_EDX !1 ; S2 = S2 + 1 CMPI8_EAX !0 ; If reached end of string JE32 %match_Done ; Perfect match JMP32 %match_Loop ; Otherwise keep looping :match_False LOADI32_EAX %1 ; Return false :match_Done POP_EDX ; Restore EDX POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; numerate_number function ;; Receives an INT A in EAX ;; Returns char* result in EAX ;; Allocates 16 bytes of memory ;; Behaves badly when given a negative number too large ;; Uses EAX for temp, EBX for DIVISOR, EDX for mod/0, ESI for result[i] and EBP for A :numerate_number PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX PUSH_EDX ; Protect EDX PUSH_ESI ; Protect ESI PUSH_EBP ; Protect EBP COPY_EAX_to_EBP ; Protect A LOADI32_EAX %16 ; 16bytes CALL32 %malloc ; Get our pointer PUSH_EAX ; Protect our pointer COPY_EAX_to_ESI ; put pointer in right place LOADI32_EBX %0x3B9ACA00 ; Set divisor to largest positive number that fits in 32bits CMPI8_EBP !0 ; Deal with 0 case JE32 %numerate_number_ZERO ; If it is JG32 %numerate_number_positive ; If it is positive ;; Deal with negative case LOADI32_EAX %45 ; Using "-" STORE8_al_into_Address_ESI ; Write it ADDI8_ESI !1 ; increment IMULI8_EBP !-1 ; A = A * -1 :numerate_number_positive LOADI32_EDX %0 ; Set top to 0 COPY_EBP_to_EAX ; Using A as bottom IDIV_EBX ; edx:eax % ebx -> edx + edx:eax / ebx -> eax [Even if we don't want it] CMPI8_EAX !0 ; IF 0 == (a / divisor) JNE32 %numerate_number_iter ; Clean up those leading Zeros LOADI32_EDX %0 ; Set top to 0 COPY_EBX_to_EAX ; Using Divisor for bottom LOADI32_EBX %10 ; Make this shit work because idiv 10 doesn't work IDIV_EBX ; edx:eax % 10 -> edx + edx:eax / 10 -> eax [Even if we don't want it] COPY_EAX_to_EBX ; Update divisor JMP32 %numerate_number_positive ; Keep collecting :numerate_number_iter CMPI8_EBX !0 ; IF DIVISOR < 0 JLE32 %numerate_number_done ; Be done LOADI32_EDX %0 ; Set top to 0 COPY_EBP_to_EAX ; Using A as bottom IDIV_EBX ; edx:eax % ebx -> edx + edx:eax / ebx -> eax [Even if we don't want it] ADDI8_EAX !48 ; ((a / divisor) + 48) STORE8_al_into_Address_ESI ; Write it COPY_EDX_to_EBP ; a = a % divisor LOADI32_EDX %0 ; Set top to 0 COPY_EBX_to_EAX ; Using Divisor for bottom LOADI32_EBX %10 ; Make this shit work because idiv 10 doesn't work IDIV_EBX ; edx:eax % 10 -> edx + edx:eax / 10 -> eax [Even if we don't want it] COPY_EAX_to_EBX ; Update divisor ADDI8_ESI !1 ; increment JMP32 %numerate_number_iter ; Keep going :numerate_number_done POP_EAX ; Restore our result POP_EBP ; Restore EBP POP_ESI ; Restore ESI POP_EDX ; Restore EDX POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN :numerate_number_ZERO LOADI32_EAX %48 ; Using '0' STORE8_al_into_Address_ESI ; Write it ADDI8_ESI !1 ; increment JMP32 %numerate_number_done ; Be done ;; numerate_string function ;; Receives CHAR* in EAX ;; Returns value of CHAR* in EAX ;; Uses EAX for VALUE, EBX for S, ECX for CH and ESI for NEGATIVE? :numerate_string PUSH_EBX ; Protect EBX PUSH_ECX ; Protect ECX PUSH_EDX ; Protect EDX PUSH_ESI ; Protect ESI COPY_EAX_to_EBX ; put S in correct place LOADI32_EAX %0 ; Initialize to Zero :numerate_string_loop LOAD8_cl_from_EBX_Immediate8 !1 ; S[1] MOVZX_cl ; make it useful CMPI8_ECX !120 ; IF 'x' == S[1] JE32 %numerate_hex ; Deal with hex input ;; Assume decimal input LOADI32_ESI %0 ; Assume no negation LOAD8_cl_from_EBX ; S[0] MOVZX_cl ; make it useful CMPI8_ECX !45 ; IF '-' == S[0] JNE32 %numerate_decimal ; Skip negation LOADI32_ESI %1 ; Set FLAG ADDI8_EBX !1 ; S = S + 1 :numerate_decimal LOAD8_cl_from_EBX ; S[0] MOVZX_cl ; make it useful CMPI8_ECX !0 ; IF NULL == S[0] JE32 %numerate_decimal_done ; We are done IMULI8_EAX !10 ; VALUE = VALUE * 10 SUBI8_ECX !48 ; CH = CH - '0' CMPI8_ECX !9 ; Check for illegal JG32 %numerate_string_fail ; If CH > '9' CMPI8_ECX !0 ; Check for illegal JL32 %numerate_string_fail ; IF CH < 0 ADD_ecx_into_eax ; VALUE = VALUE + CH ADDI8_EBX !1 ; S = S + 1 JMP32 %numerate_decimal ; Keep looping :numerate_decimal_done CMPI8_ESI !1 ; Check if need to negate JNE32 %numerate_string_done ; Nope IMULI8_EAX !-1 ; VALUE = VALUE * -1 JMP32 %numerate_string_done ; Done :numerate_hex ADDI8_EBX !2 ; S = S + 2 :numerate_hex_loop LOAD8_cl_from_EBX ; S[0] MOVZX_cl ; make it useful CMPI8_ECX !0 ; IF NULL == S[0] JE32 %numerate_string_done ; We are done SALI8_EAX !4 ; VALUE = VALUE << 4 SUBI8_ECX !48 ; CH = CH - '0' CMPI8_ECX !10 ; IF 10 >= CH JL32 %numerate_hex_digit ; NO SUBI8_ECX !7 ; Push A-F into range :numerate_hex_digit CMPI8_ECX !15 ; Check for illegal JG32 %numerate_string_fail ; If CH > 'F' CMPI8_ECX !0 ; Check for illegal JL32 %numerate_string_fail ; IF CH < 0 ADD_ecx_into_eax ; VALUE = VALUE + CH ADDI8_EBX !1 ; S = S + 1 JMP32 %numerate_hex_loop ; Keep looping :numerate_string_fail LOADI32_EAX %0 ; return ZERO :numerate_string_done POP_ESI ; Restore ESI POP_EDX ; Restore EDX POP_ECX ; Restore ECX POP_EBX ; Restore EBX RETURN ;; Exit_Failure function ;; Receives nothing ;; And aborts hard ;; Does NOT return :Exit_Failure LOADI32_EBX %1 ; All is wrong LOADI32_EAX %1 ; put the exit syscall number in eax INT_80 ; Call it a bad day ;; Keywords :union "union" :struct "struct" :constant "CONSTANT" :main_string "main" :argc_string "argc" :argv_string "argv" :if_string "if" :else_string "else" :do_string "do" :while_string "while" :for_string "for" :asm_string "asm" :goto_string "goto" :return_string "return" :break_string "break" :continue_string "continue" :sizeof_string "sizeof" :plus_string "+" :minus_string "-" :multiply_string "*" :divide_string "/" :modulus_string "%" :left_shift_string "<<" :right_shift_string ">>" :less_than_string "<" :less_than_equal_string "<=" :greater_than_equal_string ">=" :greater_than_string ">" :equal_to_string "==" :not_equal_string "!=" :bitwise_and "&" :logical_and "&&" :bitwise_or "|" :logical_or "||" :bitwise_xor "^" :arrow_string "->" ;; Frequently Used strings ;; Generally used by require_match :open_curly_brace "{" :close_curly_brace "}" :open_paren "(" :close_paren ")" :open_bracket "[" :close_bracket "]" :comma "," :semicolon ";" :equal "=" :percent "%" :newline "\n" :underline "_" :prim_types :type_void &type_int ; NEXT %4 ; SIZE %0 ; OFFSET &type_void ; INDIRECT %0 ; MEMBERS &type_void ; TYPE &type_void_name ; NAME :type_void_name "void" :type_int &type_char ; NEXT %4 ; SIZE %0 ; OFFSET &type_int ; INDIRECT %0 ; MEMBERS &type_int ; TYPE &type_int_name ; NAME :type_int_name "int" :type_char &type_file ; NEXT %1 ; SIZE %0 ; OFFSET &type_char_indirect ; INDIRECT %0 ; MEMBERS &type_char ; TYPE &type_char_name ; NAME :type_char_name "char" :type_char_indirect &type_file ; NEXT %4 ; SIZE %0 ; OFFSET &type_char_double_indirect ; INDIRECT %0 ; MEMBERS &type_char_indirect ; TYPE &type_char_indirect_name ; NAME :type_char_indirect_name "char*" :type_char_double_indirect &type_file ; NEXT %4 ; SIZE %0 ; OFFSET &type_char_double_indirect ; INDIRECT %0 ; MEMBERS &type_char_indirect ; TYPE &type_char_double_indirect_name ; NAME :type_char_double_indirect_name "char**" :type_file &type_function ; NEXT %4 ; SIZE %0 ; OFFSET &type_file ; INDIRECT %0 ; MEMBERS &type_file ; TYPE &type_file_name ; NAME :type_file_name "FILE" :type_function &type_unsigned ; NEXT %4 ; SIZE %0 ; OFFSET &type_function ; INDIRECT %0 ; MEMBERS &type_function ; TYPE &type_function_name ; NAME :type_function_name "FUNCTION" :type_unsigned &type_long ; NEXT %4 ; SIZE %0 ; OFFSET &type_unsigned ; INDIRECT %0 ; MEMBERS &type_unsigned ; TYPE &type_unsigned_name ; NAME :type_unsigned_name "unsigned" :type_long %0 ; NEXT %4 ; SIZE %0 ; OFFSET &type_long ; INDIRECT %0 ; MEMBERS &type_long ; TYPE &type_long_name ; NAME :type_long_name "long" ;; debug_list function ;; Receives struct token_list* in EAX ;; Prints contents of list and exits ;; Does NOT return :debug_list COPY_EAX_to_EBP ; Protect the list pointer LOADI32_EAX %2 ; Using Standard error STORE32_Absolute32_eax &Output_file ; write to standard error :debug_list_iter ;; Header LOADI32_EAX &debug_list_string0 ; Using our first string CALL32 %File_Print ; Print it COPY_EBP_to_EAX ; Use address of pointer CALL32 %numerate_number ; Convert it into string CALL32 %File_Print ; Print it ;; NEXT LOADI32_EAX &debug_list_string1 ; Using our second string CALL32 %File_Print ; Print it LOAD32_EAX_from_EBP_Immediate8 !0 ; Use address of pointer CALL32 %numerate_number ; Convert it into string CALL32 %File_Print ; Print it ;; PREV LOADI32_EAX &debug_list_string2 ; Using our third string CALL32 %File_Print ; Print it LOAD32_EAX_from_EBP_Immediate8 !4 ; Use address of pointer CALL32 %numerate_number ; Convert it into string CALL32 %File_Print ; Print it ;; S LOADI32_EAX &debug_list_string3 ; Using our fourth string CALL32 %File_Print ; Print it LOAD32_EAX_from_EBP_Immediate8 !8 ; Use address of pointer CALL32 %numerate_number ; Convert it into string CALL32 %File_Print ; Print it ;; S Contents LOADI32_EAX &debug_list_string4 ; Using our fifth string CALL32 %File_Print ; Print it LOAD32_EAX_from_EBP_Immediate8 !8 ; Use address of string CMPI8_EAX !0 ; IF NULL Pointer JNE32 %debug_list_null ; otherwise display LOADI32_EAX &debug_list_string_null ; Give meaningful message instead :debug_list_null CALL32 %File_Print ; Print it ;; TYPE LOADI32_EAX &debug_list_string5 ; Using our sixth string CALL32 %File_Print ; Print it LOAD32_EAX_from_EBP_Immediate8 !12 ; Use address of pointer CALL32 %numerate_number ; Convert it into string CALL32 %File_Print ; Print it ;; ARGS/DEPTH LOADI32_EAX &debug_list_string6 ; Using our seventh string CALL32 %File_Print ; Print it LOAD32_EAX_from_EBP_Immediate8 !16 ; Use address of pointer CALL32 %numerate_number ; Convert it into string CALL32 %File_Print ; Print it LOADI32_EAX %10 ; Add "\n" CALL32 %fputc ; print it CALL32 %fputc ; print it LOAD32_EBP_from_EBP ; TOKEN = TOKEN->NEXT CMPI8_EBP !0 ; Check if NULL JNE32 %debug_list_iter ; iterate otherwise LOADI32_EBX %666 ; All is HELL LOADI32_EAX %1 ; put the exit syscall number in eax INT_80 ; Call it a bad day :debug_list_string0 "Token_list node at address: " :debug_list_string1 " NEXT address: " :debug_list_string2 " PREV address: " :debug_list_string3 " S address: " :debug_list_string4 " The contents of S are: " :debug_list_string5 " TYPE address: " :debug_list_string6 " ARGUMENTS address: " :debug_list_string_null ">::<NULL>::<" :Address_of NULL :C NULL :Input_file NULL :MALLOC NULL :Output_file NULL :Token NULL :break_frame NULL :break_target_func NULL :break_target_head NULL :break_target_num NULL :current_count NULL :current_target NULL :function NULL :global_constant_list NULL :global_function_list NULL :global_symbol_list NULL :global_token NULL :global_types &prim_types :globals_list NULL :output_list NULL :string_index NULL :strings_list NULL :ELF_end
/* Copyright (C) 2016 Jeremiah Orians * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ // CONSTANT stdin 0 // CONSTANT stdout 1 // CONSTANT stderr 2 // CONSTANT EOF 0xFFFFFFFF // CONSTANT NULL 0 // CONSTANT EXIT_FAILURE 1 // CONSTANT EXIT_SUCCESS 0 // CONSTANT TRUE 1 // CONSTANT FALSE 0 int fgetc(FILE* f) { asm("mov_eax, %3" "lea_ebx,[esp+DWORD] %4" "mov_ebx,[ebx]" "push_ebx" "mov_ecx,esp" "mov_edx, %1" "int !0x80" "test_eax,eax" "pop_eax" "jne %FUNCTION_fgetc_Done" "mov_eax, %-1" ":FUNCTION_fgetc_Done"); } void fputc(char s, FILE* f) { asm("mov_eax, %4" "lea_ebx,[esp+DWORD] %4" "mov_ebx,[ebx]" "lea_ecx,[esp+DWORD] %8" "mov_edx, %1" "int !0x80"); } void fputs(char* s, FILE* f) { while(0 != s[0]) { fputc(s[0], f); s = s + 1; } } FILE* open(char* name, int flag, int mode) { asm("lea_ebx,[esp+DWORD] %12" "mov_ebx,[ebx]" "lea_ecx,[esp+DWORD] %8" "mov_ecx,[ecx]" "lea_edx,[esp+DWORD] %4" "mov_edx,[edx]" "mov_eax, %5" "int !0x80"); } FILE* fopen(char* filename, char* mode) { FILE* f; if('w' == mode[0]) { /* 577 is O_WRONLY|O_CREAT|O_TRUNC, 384 is 600 in octal */ f = open(filename, 577 , 384); } else { /* Everything else is a read */ f = open(filename, 0, 0); } /* Negative numbers are error codes */ if(0 > f) { return 0; } return f; } int close(int fd) { asm("lea_ebx,[esp+DWORD] %4" "mov_ebx,[ebx]" "mov_eax, %6" "int !0x80"); } int fclose(FILE* stream) { int error = close(stream); return error; } int brk(void *addr) { asm("mov_eax,[esp+DWORD] %4" "push_eax" "mov_eax, %45" "pop_ebx" "int !0x80"); } long _malloc_ptr; long _brk_ptr; void* malloc(int size) { if(NULL == _brk_ptr) { _brk_ptr = brk(0); _malloc_ptr = _brk_ptr; } if(_brk_ptr < _malloc_ptr + size) { _brk_ptr = brk(_malloc_ptr + size); if(-1 == _brk_ptr) return 0; } long old_malloc = _malloc_ptr; _malloc_ptr = _malloc_ptr + size; return old_malloc; } int strlen(char* str ) { int i = 0; while(0 != str[i]) i = i + 1; return i; } void* memset(void* ptr, int value, int num) { char* s; for(s = ptr; 0 < num; num = num - 1) { s[0] = value; s = s + 1; } } void* calloc(int count, int size) { void* ret = malloc(count * size); if(NULL == ret) return NULL; memset(ret, 0, (count * size)); return ret; } void free(void* l) { return; } void exit(int value) { asm("pop_ebx" "pop_ebx" "mov_eax, %1" "int !0x80"); }
/* Copyright (C) 2016 Jeremiah Orians * Copyright (C) 2020 deesix <deesix@tuta.io> * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> #include <stdio.h> #include <string.h> // CONSTANT FALSE 0 #define FALSE 0 // CONSTANT TRUE 1 #define TRUE 1 // CONSTANT KNIGHT_NATIVE 1 #define KNIGHT_NATIVE 1 // CONSTANT KNIGHT_POSIX 2 #define KNIGHT_POSIX 2 // CONSTANT X86 3 #define X86 3 // CONSTANT AMD64 4 #define AMD64 4 // CONSTANT ARMV7L 5 #define ARMV7L 5 // CONSTANT AARCH64 6 #define AARCH64 6 // CONSTANT RISCV32 7 #define RISCV32 7 // CONSTANT RISCV64 8 #define RISCV64 8 void copy_string(char* target, char* source, int max); int in_set(int c, char* s); int match(char* a, char* b); void require(int bool, char* error); void reset_hold_string(); struct type { struct type* next; int size; int offset; int is_signed; struct type* indirect; struct type* members; struct type* type; char* name; }; struct token_list { struct token_list* next; union { struct token_list* locals; struct token_list* prev; }; char* s; union { struct type* type; char* filename; }; union { struct token_list* arguments; int depth; int linenumber; }; }; #include "cc_globals.h"
/* Copyright (C) 2016 Jeremiah Orians * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> #include <stdlib.h> #define TRUE 1 #define FALSE 0 void require(int bool, char* error) { if(!bool) { fputs(error, stderr); exit(EXIT_FAILURE); } } int match(char* a, char* b) { if((NULL == a) && (NULL == b)) return TRUE; if(NULL == a) return FALSE; if(NULL == b) return FALSE; int i = -1; do { i = i + 1; if(a[i] != b[i]) { return FALSE; } } while((0 != a[i]) && (0 !=b[i])); return TRUE; } int in_set(int c, char* s) { /* NULL set is always false */ if(NULL == s) return FALSE; while(0 != s[0]) { if(c == s[0]) return TRUE; s = s + 1; } return FALSE; } /* INTERNAL ONLY */ int __index_number(char* s, char c) { int i = 0; while(s[i] != c) { i = i + 1; if(0 == s[i]) return -1; } return i; } /* INTERNAL ONLY */ int __toupper(int c) { if(in_set(c, "abcdefghijklmnopqrstuvwxyz")) return (c & 0xDF); return c; } /* INTERNAL ONLY */ int __set_reader(char* set, int mult, char* input) { int n = 0; int i = 0; int hold; int negative_p = FALSE; if(input[0] == '-') { negative_p = TRUE; i = i + 1; } while(in_set(input[i], set)) { n = n * mult; hold = __index_number(set, __toupper(input[i])); /* Input managed to change between in_set and index_number */ if(-1 == hold) return 0; n = n + hold; i = i + 1; } /* loop exited before NULL and thus invalid input */ if(0 != input[i]) return 0; if(negative_p) { n = 0 - n; } return n; } int strtoint(char *a) { int result = 0; /* If NULL string */ if(0 == a[0]) { result = 0; } /* Deal with binary */ else if ('0' == a[0] && 'b' == a[1]) { result = __set_reader("01", 2, a+2); } /* Deal with hex */ else if ('0' == a[0] && 'x' == a[1]) { result = __set_reader("0123456789ABCDEFabcdef", 16, a+2); } /* Deal with octal */ else if('0' == a[0]) { result = __set_reader("01234567", 8, a+1); } /* Deal with decimal */ else { result = __set_reader("0123456789", 10, a); } /* Deal with sign extension for 64bit hosts */ if(0 != (0x80000000 & result)) result = (0xFFFFFFFF << 31) | result; return result; } char* int2str(int x, int base, int signed_p) { require(1 < base, "int2str doesn't support a base less than 2\n"); require(37 > base, "int2str doesn't support a base more than 36\n"); /* Be overly conservative and save space for 32binary digits and padding null */ char* p = calloc(34, sizeof(char)); /* if calloc fails return null to let calling code deal with it */ if(NULL == p) return p; p = p + 32; unsigned i; int sign_p = FALSE; char* table = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; if(signed_p && (10 == base) && (0 != (x & 0x80000000))) { /* Truncate to 31bits */ i = -x & 0x7FFFFFFF; if(0 == i) return "-2147483648"; sign_p = TRUE; } /* Truncate to 32bits */ else i = x & (0x7FFFFFFF | (1 << 31)); do { p[0] = table[i % base]; p = p - 1; i = i / base; } while(0 < i); if(sign_p) { p[0] = '-'; p = p - 1; } return p + 1; }
/* Copyright (C) 2016 Jeremiah Orians * Copyright (C) 2020 deesix <deesix@tuta.io> * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ /* What types we have */ struct type* global_types; struct type* prim_types; /* What we are currently working on */ struct token_list* global_token; /* Output reorder collections*/ struct token_list* output_list; struct token_list* strings_list; struct token_list* globals_list; /* Make our string collection more efficient */ char* hold_string; int string_index; /* Our Target Architecture */ int Architecture; int register_size; int MAX_STRING; struct type* integer; /* enable bootstrap-mode */ int BOOTSTRAP_MODE; /* enable preprocessor-only mode */ int PREPROCESSOR_MODE;
/* Copyright (C) 2016 Jeremiah Orians * Copyright (C) 2021 Andrius Å tikonas <andrius@stikonas.eu> * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #include "cc.h" int strtoint(char *a); /* Globals */ FILE* input; struct token_list* token; int line; char* file; int grab_byte() { int c = fgetc(input); if(10 == c) line = line + 1; return c; } int clearWhiteSpace(int c) { if((32 == c) || (9 == c)) return clearWhiteSpace(grab_byte()); return c; } int consume_byte(int c) { hold_string[string_index] = c; string_index = string_index + 1; require(MAX_STRING > string_index, "Token exceeded MAX_STRING char limit\nuse --max-string number to increase\n"); return grab_byte(); } int preserve_string(int c) { int frequent = c; int escape = FALSE; do { if(!escape && '\\' == c ) escape = TRUE; else escape = FALSE; c = consume_byte(c); require(EOF != c, "Unterminated string\n"); } while(escape || (c != frequent)); return grab_byte(); } void copy_string(char* target, char* source, int max) { int i = 0; while(0 != source[i]) { target[i] = source[i]; i = i + 1; if(i == max) break; } } void fixup_label() { int hold = ':'; int prev; int i = 0; do { prev = hold; hold = hold_string[i]; hold_string[i] = prev; i = i + 1; } while(0 != hold); } int preserve_keyword(int c, char* S) { while(in_set(c, S)) { c = consume_byte(c); } return c; } void reset_hold_string() { int i = MAX_STRING; while(0 <= i) { hold_string[i] = 0; i = i - 1; } string_index = 0; } /* note if this is the first token in the list, head needs fixing up */ struct token_list* eat_token(struct token_list* token) { if(NULL != token->prev) { token->prev->next = token->next; } /* update backlinks */ if(NULL != token->next) { token->next->prev = token->prev; } return token->next; } struct token_list* eat_until_newline(struct token_list* head) { while (NULL != head) { if('\n' == head->s[0]) { return head; } else { head = eat_token(head); } } return NULL; } struct token_list* remove_line_comments(struct token_list* head) { struct token_list* first = NULL; while (NULL != head) { if(match("//", head->s)) { head = eat_until_newline(head); } else { if(NULL == first) { first = head; } head = head->next; } } return first; } struct token_list* remove_line_comment_tokens(struct token_list* head) { struct token_list* first = NULL; while (NULL != head) { if(match("//", head->s)) { head = eat_token(head); } else { if(NULL == first) { first = head; } head = head->next; } } return first; } struct token_list* remove_preprocessor_directives(struct token_list* head) { struct token_list* first = NULL; while (NULL != head) { if('#' == head->s[0]) { head = eat_until_newline(head); } else { if(NULL == first) { first = head; } head = head->next; } } return first; } void new_token(char* s, int size) { struct token_list* current = calloc(1, sizeof(struct token_list)); require(NULL != current, "Exhausted memory while getting token\n"); /* More efficiently allocate memory for string */ current->s = calloc(size, sizeof(char)); require(NULL != current->s, "Exhausted memory while trying to copy a token\n"); copy_string(current->s, s, MAX_STRING); current->prev = token; current->next = token; current->linenumber = line; current->filename = file; token = current; } int get_token(int c) { struct token_list* current = calloc(1, sizeof(struct token_list)); require(NULL != current, "Exhausted memory while getting token\n"); reset: reset_hold_string(); string_index = 0; c = clearWhiteSpace(c); if(c == EOF) { free(current); return c; } else if('#' == c) { c = consume_byte(c); c = preserve_keyword(c, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"); } else if(in_set(c, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_")) { c = preserve_keyword(c, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"); if(':' == c) { fixup_label(); c = ' '; } } else if(in_set(c, "<=>|&!^%")) { c = preserve_keyword(c, "<=>|&!^%"); } else if(in_set(c, "'\"")) { c = preserve_string(c); } else if(c == '/') { c = consume_byte(c); if(c == '*') { c = grab_byte(); while(c != '/') { while(c != '*') { c = grab_byte(); require(EOF != c, "Hit EOF inside of block comment\n"); } c = grab_byte(); require(EOF != c, "Hit EOF inside of block comment\n"); } c = grab_byte(); goto reset; } else if(c == '/') { c = consume_byte(c); } else if(c == '=') { c = consume_byte(c); } } else if (c == '\n') { c = consume_byte(c); } else if(c == '*') { c = consume_byte(c); if(c == '=') { c = consume_byte(c); } } else if(c == '+') { c = consume_byte(c); if(c == '=') { c = consume_byte(c); } if(c == '+') { c = consume_byte(c); } } else if(c == '-') { c = consume_byte(c); if(c == '=') { c = consume_byte(c); } if(c == '>') { c = consume_byte(c); } if(c == '-') { c = consume_byte(c); } } else { c = consume_byte(c); } new_token(hold_string, string_index + 2); return c; } int consume_filename(int c) { reset_hold_string(); int done = FALSE; while(!done) { if(c == EOF) { fputs("we don't support EOF as a filename in #FILENAME statements\n", stderr); exit(EXIT_FAILURE); } else if((32 == c) || (9 == c) || (c == '\n')) { c = grab_byte(); } else { do { c = consume_byte(c); require(EOF != c, "Unterminated filename in #FILENAME\n"); } while((32 != c) && (9 != c) && ('\n' != c)); done = TRUE; } } /* with just a little extra to put in the matching at the end */ new_token(hold_string, string_index + 3); return c; } int change_filename(int ch) { require(EOF != ch, "#FILENAME failed to receive filename\n"); /* Remove the #FILENAME */ token = token->next; /* Get new filename */ ch = consume_filename(ch); file = token->s; /* Remove it from the processing list */ token = token->next; require(EOF != ch, "#FILENAME failed to receive filename\n"); /* Get new line number */ ch = get_token(ch); line = strtoint(token->s); if(0 == line) { if('0' != token->s[0]) { fputs("non-line number: ", stderr); fputs(token->s, stderr); fputs(" provided to #FILENAME\n", stderr); exit(EXIT_FAILURE); } } /* Remove it from the processing list */ token = token->next; return ch; } struct token_list* reverse_list(struct token_list* head) { struct token_list* root = NULL; struct token_list* next; while(NULL != head) { next = head->next; head->next = root; root = head; head = next; } return root; } struct token_list* read_all_tokens(FILE* a, struct token_list* current, char* filename) { input = a; line = 1; file = filename; token = current; int ch = grab_byte(); while(EOF != ch) { ch = get_token(ch); require(NULL != token, "Empty files don't need to be compiled\n"); if(match("#FILENAME", token->s)) ch = change_filename(ch); } return token; }
/* Copyright (C) 2016 Jeremiah Orians * Copyright (C) 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #include "cc.h" #include <stdint.h> struct token_list* emit(char *s, struct token_list* head); void require(int bool, char* error); char upcase(char a) { if(in_set(a, "abcdefghijklmnopqrstuvwxyz")) { a = a - 32; } return a; } int char2hex(int c) { if (c >= '0' && c <= '9') return (c - 48); else if (c >= 'a' && c <= 'f') return (c - 87); else if (c >= 'A' && c <= 'F') return (c - 55); else return -1; } int hexify(int c, int high) { int i = char2hex(c); if(0 > i) { fputs("Tried to print non-hex number\n", stderr); exit(EXIT_FAILURE); } if(high) { i = i << 4; } return i; } int escape_lookup(char* c); int weird(char* string) { int c; string = string + 1; weird_reset: c = string[0]; if(0 == c) return FALSE; if('\\' == c) { c = escape_lookup(string); if('x' == string[1]) string = string + 2; string = string + 1; } if(!in_set(c, "\t\n !#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~")) return TRUE; if(in_set(c, " \t\n\r") && (':' == string[1])) return TRUE; string = string + 1; goto weird_reset; } /* Lookup escape values */ int escape_lookup(char* c) { if('\\' != c[0]) return c[0]; if(c[1] == 'x') { int t1 = hexify(c[2], TRUE); int t2 = hexify(c[3], FALSE); return t1 + t2; } else if(c[1] == '0') return 0; else if(c[1] == 'a') return 7; else if(c[1] == 'b') return 8; else if(c[1] == 't') return 9; else if(c[1] == 'n') return 10; else if(c[1] == 'v') return 11; else if(c[1] == 'f') return 12; else if(c[1] == 'r') return 13; else if(c[1] == 'e') return 27; else if(c[1] == '"') return 34; else if(c[1] == '\'') return 39; else if(c[1] == '\\') return 92; fputs("Unknown escape received: ", stderr); fputs(c, stderr); fputs(" Unable to process\n", stderr); exit(EXIT_FAILURE); } /* Deal with human strings */ char* collect_regular_string(char* string) { string_index = 0; collect_regular_string_reset: require((MAX_STRING - 3) > string_index, "Attempt at parsing regular string exceeds max length\n"); if(string[0] == '\\') { hold_string[string_index] = escape_lookup(string); if (string[1] == 'x') string = string + 2; string = string + 2; } else { hold_string[string_index] = string[0]; string = string + 1; } string_index = string_index + 1; if(string[0] != 0) goto collect_regular_string_reset; hold_string[string_index] = '"'; hold_string[string_index + 1] = '\n'; char* message = calloc(string_index + 3, sizeof(char)); require(NULL != message, "Exhausted memory while storing regular string\n"); copy_string(message, hold_string, string_index + 2); reset_hold_string(); return message; } /* Deal with non-human strings */ char* collect_weird_string(char* string) { string_index = 1; int temp; char* table = "0123456789ABCDEF"; hold_string[0] = '\''; collect_weird_string_reset: require((MAX_STRING - 6) > string_index, "Attempt at parsing weird string exceeds max length\n"); string = string + 1; hold_string[string_index] = ' '; temp = escape_lookup(string) & 0xFF; hold_string[string_index + 1] = table[(temp >> 4)]; hold_string[string_index + 2] = table[(temp & 15)]; if(string[0] == '\\') { if(string[1] == 'x') string = string + 2; string = string + 1; } string_index = string_index + 3; if(string[1] != 0) goto collect_weird_string_reset; hold_string[string_index] = ' '; hold_string[string_index + 1] = '0'; hold_string[string_index + 2] = '0'; hold_string[string_index + 3] = '\''; hold_string[string_index + 4] = '\n'; char* hold = calloc(string_index + 6, sizeof(char)); require(NULL != hold, "Exhausted available memory while attempting to collect a weird string\n"); copy_string(hold, hold_string, string_index + 5); reset_hold_string(); return hold; } /* Parse string to deal with hex characters*/ char* parse_string(char* string) { /* the string */ if(weird(string)) return collect_weird_string(string); else return collect_regular_string(string); }
/* Copyright (C) 2016 Jeremiah Orians * Copyright (C) 2020 deesix <deesix@tuta.io> * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #include "cc.h" /* Imported functions */ int strtoint(char *a); void line_error(); void require(int bool, char* error); /* enable easy primitive extension */ struct type* add_primitive(struct type* a) { if(NULL == prim_types) return a; struct type* i = prim_types; while(NULL != i->next) { i = i->next; } i->next = a; return prim_types; } /* enable easy primitive creation */ struct type* new_primitive(char* name0, char* name1, char* name2, int size, int sign) { /* Create type** */ struct type* a = calloc(1, sizeof(struct type)); require(NULL != a, "Exhausted memory while declaring new primitive**\n"); a->name = name2; a->size = register_size; a->indirect = a; a->is_signed = sign; /* Create type* */ struct type* b = calloc(1, sizeof(struct type)); require(NULL != b, "Exhausted memory while declaring new primitive*\n"); b->name = name1; b->size = register_size; b->is_signed = sign; b->indirect = a; a->type = b; struct type* r = calloc(1, sizeof(struct type)); require(NULL != r, "Exhausted memory while declaring new primitive\n"); r->name = name0; r->size = size; r->is_signed = sign; r->indirect = b; r->type = r; b->type = r; return r; } /* Initialize default types */ void initialize_types() { if(AMD64 == Architecture || AARCH64 == Architecture || RISCV64 == Architecture) register_size = 8; else register_size = 4; /* Define void */ struct type* hold = new_primitive("void", "void*", "void**", register_size, FALSE); prim_types = add_primitive(hold); /* Define unsigned LONG */ hold = new_primitive("SCM","SCM*", "SCM**", register_size, FALSE); prim_types = add_primitive(hold); /* Define LONG */ hold = new_primitive("long", "long*", "long**", register_size, TRUE); prim_types = add_primitive(hold); /* Define UNSIGNED */ hold = new_primitive("unsigned", "unsigned*", "unsigned**", register_size, FALSE); prim_types = add_primitive(hold); /* Define int */ integer = new_primitive("int", "int*", "int**", register_size, TRUE); prim_types = add_primitive(integer); /* Define uint32_t */ hold = new_primitive("uint32_t", "uint32_t*", "uint32_t**", 4, FALSE); prim_types = add_primitive(hold); /* Define int32_t */ hold = new_primitive("int32_t", "int32_t*", "int32_t**", 4, TRUE); prim_types = add_primitive(hold); /* Define uint16_t */ hold = new_primitive("uint16_t", "uint16_t*", "uint16_t**", 2, FALSE); prim_types = add_primitive(hold); /* Define int16_t */ hold = new_primitive("int16_t", "int16_t*", "int16_t**", 2, TRUE); prim_types = add_primitive(hold); /* Define uint8_t */ hold = new_primitive("uint8_t", "uint8_t*", "uint8_t**", 1, FALSE); prim_types = add_primitive(hold); /* Define int8_t */ hold = new_primitive("int8_t", "int8_t*", "int8_t**", 1, TRUE); prim_types = add_primitive(hold); /* Define char */ hold = new_primitive("char", "char*", "char**", 1, TRUE); prim_types = add_primitive(hold); /* Define FUNCTION */ hold = new_primitive("FUNCTION", "FUNCTION*", "FUNCTION**", register_size, FALSE); prim_types = add_primitive(hold); if(BOOTSTRAP_MODE) { /* Define FILE */ hold = new_primitive("FILE", "FILE*", "FILE**", register_size, TRUE); prim_types = add_primitive(hold); /* Primitives mes.c wanted */ hold = new_primitive("size_t", "size_t*", "size_t**", register_size, FALSE); prim_types = add_primitive(hold); hold = new_primitive("ssize_t", "ssize_t*", "ssize_t**", register_size, FALSE); prim_types = add_primitive(hold); } global_types = prim_types; } struct type* lookup_type(char* s, struct type* start) { struct type* i; for(i = start; NULL != i; i = i->next) { if(match(i->name, s)) { return i; } } return NULL; } struct type* lookup_member(struct type* parent, char* name) { struct type* i; require(NULL != parent, "Not a valid struct type\n"); for(i = parent->members; NULL != i; i = i->members) { if(match(i->name, name)) return i; } fputs("ERROR in lookup_member ", stderr); fputs(parent->name, stderr); fputs("->", stderr); fputs(global_token->s, stderr); fputs(" does not exist\n", stderr); line_error(); fputs("\n", stderr); exit(EXIT_FAILURE); } struct type* type_name(); void require_match(char* message, char* required); int member_size; struct type* build_member(struct type* last, int offset) { struct type* i = calloc(1, sizeof(struct type)); require(NULL != i, "Exhausted memory while building a struct member\n"); i->members = last; i->offset = offset; struct type* member_type = type_name(); require(NULL != member_type, "struct member type can not be invalid\n"); i->type = member_type; i->name = global_token->s; global_token = global_token->next; require(NULL != global_token, "struct member can not be EOF terminated\n"); /* Check to see if array */ if(match( "[", global_token->s)) { global_token = global_token->next; require(NULL != global_token, "struct member arrays can not be EOF sized\n"); i->size = member_type->type->size * strtoint(global_token->s); if(0 == i->size) { fputs("Struct only supports [num] form\n", stderr); exit(EXIT_FAILURE); } global_token = global_token->next; require_match("Struct only supports [num] form\n", "]"); } else { i->size = member_type->size; } member_size = i->size; return i; } struct type* build_union(struct type* last, int offset) { int size = 0; global_token = global_token->next; require_match("ERROR in build_union\nMissing {\n", "{"); while('}' != global_token->s[0]) { last = build_member(last, offset); if(member_size > size) { size = member_size; } require_match("ERROR in build_union\nMissing ;\n", ";"); require(NULL != global_token, "Unterminated union\n"); } member_size = size; global_token = global_token->next; return last; } void create_struct() { int offset = 0; member_size = 0; struct type* head = calloc(1, sizeof(struct type)); require(NULL != head, "Exhausted memory while creating a struct\n"); struct type* i = calloc(1, sizeof(struct type)); require(NULL != i, "Exhausted memory while creating a struct indirection\n"); struct type* ii = calloc(1, sizeof(struct type)); require(NULL != ii, "Exhausted memory while creating a struct double indirection\n"); head->name = global_token->s; head->type = head; head->indirect = i; head->next = global_types; i->name = global_token->s; i->type = head; i->indirect = ii; i->size = register_size; ii->name = global_token->s; ii->type = i; ii->indirect = ii; ii->size = register_size; global_types = head; global_token = global_token->next; require_match("ERROR in create_struct\n Missing {\n", "{"); struct type* last = NULL; require(NULL != global_token, "Incomplete struct definition at end of file\n"); while('}' != global_token->s[0]) { if(match(global_token->s, "union")) { last = build_union(last, offset); } else { last = build_member(last, offset); } offset = offset + member_size; require_match("ERROR in create_struct\n Missing ;\n", ";"); require(NULL != global_token, "Unterminated struct\n"); } global_token = global_token->next; require_match("ERROR in create_struct\n Missing ;\n", ";"); head->size = offset; head->members = last; i->members = last; } struct type* type_name() { struct type* ret; require(NULL != global_token, "Received EOF instead of type name\n"); if(match("extern", global_token->s)) { global_token = global_token->next; require(NULL != global_token, "unfinished type definition in extern\n"); } if(match("struct", global_token->s)) { global_token = global_token->next; require(NULL != global_token, "structs can not have a EOF type name\n"); ret = lookup_type(global_token->s, global_types); if(NULL == ret) { create_struct(); return NULL; } } else { ret = lookup_type(global_token->s, global_types); if(NULL == ret) { fputs("Unknown type ", stderr); fputs(global_token->s, stderr); fputs("\n", stderr); line_error(); fputs("\n", stderr); exit(EXIT_FAILURE); } } global_token = global_token->next; require(NULL != global_token, "unfinished type definition\n"); if(match("const", global_token->s)) { global_token = global_token->next; require(NULL != global_token, "unfinished type definition in const\n"); } while(global_token->s[0] == '*') { ret = ret->indirect; global_token = global_token->next; require(NULL != global_token, "unfinished type definition in indirection\n"); } return ret; } struct type* mirror_type(struct type* source, char* name) { struct type* head = calloc(1, sizeof(struct type)); require(NULL != head, "Exhausted memory while creating a struct\n"); struct type* i = calloc(1, sizeof(struct type)); require(NULL != i, "Exhausted memory while creating a struct indirection\n"); head->name = name; i->name = name; head->size = source->size; i->size = source->indirect->size; head->offset = source->offset; i->offset = source->indirect->offset; head->is_signed = source->is_signed; i->is_signed = source->indirect->is_signed; head->indirect = i; i->indirect = head; head->members = source->members; i->members = source->indirect->members; head->type = head; i->type = i; return head; }
/* Copyright (C) 2016 Jeremiah Orians * Copyright (C) 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * Copyright (C) 2020 deesix <deesix@tuta.io> * Copyright (C) 2021 Andrius Å tikonas <andrius@stikonas.eu> * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #include "cc.h" #include "gcc_req.h" #include <stdint.h> /* Global lists */ struct token_list* global_symbol_list; struct token_list* global_function_list; struct token_list* global_constant_list; /* Core lists for this file */ struct token_list* function; /* What we are currently working on */ struct type* current_target; char* break_target_head; char* break_target_func; char* break_target_num; char* continue_target_head; struct token_list* break_frame; int current_count; int Address_of; /* Imported functions */ char* int2str(int x, int base, int signed_p); int strtoint(char *a); char* parse_string(char* string); int escape_lookup(char* c); void require(int bool, char* error); struct token_list* reverse_list(struct token_list* head); struct type* mirror_type(struct type* source, char* name); struct type* add_primitive(struct type* a); struct token_list* emit(char *s, struct token_list* head) { struct token_list* t = calloc(1, sizeof(struct token_list)); require(NULL != t, "Exhausted memory while generating token to emit\n"); t->next = head; t->s = s; return t; } void emit_out(char* s) { output_list = emit(s, output_list); } struct token_list* uniqueID(char* s, struct token_list* l, char* num) { l = emit("\n", emit(num, emit("_", emit(s, l)))); return l; } void uniqueID_out(char* s, char* num) { output_list = uniqueID(s, output_list, num); } struct token_list* sym_declare(char *s, struct type* t, struct token_list* list) { struct token_list* a = calloc(1, sizeof(struct token_list)); require(NULL != a, "Exhausted memory while attempting to declare a symbol\n"); a->next = list; a->s = s; a->type = t; return a; } struct token_list* sym_lookup(char *s, struct token_list* symbol_list) { struct token_list* i; for(i = symbol_list; NULL != i; i = i->next) { if(match(i->s, s)) return i; } return NULL; } void line_error_token(struct token_list *token) { if(NULL == token) { fputs("EOF reached inside of line_error\n", stderr); fputs("problem at end of file\n", stderr); return; } fputs(token->filename, stderr); fputs(":", stderr); fputs(int2str(token->linenumber, 10, TRUE), stderr); fputs(":", stderr); } void line_error() { line_error_token(global_token); } void require_match(char* message, char* required) { if(NULL == global_token) { line_error(); fputs("EOF reached inside of require match\n", stderr); fputs("problem at end of file\n", stderr); fputs(message, stderr); exit(EXIT_FAILURE); } if(!match(global_token->s, required)) { line_error(); fputs(message, stderr); exit(EXIT_FAILURE); } global_token = global_token->next; } void maybe_bootstrap_error(char* feature) { if (BOOTSTRAP_MODE) { line_error(); fputs(feature, stderr); fputs(" is not supported in --bootstrap-mode\n", stderr); exit(EXIT_FAILURE); } } void expression(); void function_call(char* s, int bool) { require_match("ERROR in process_expression_list\nNo ( was found\n", "("); require(NULL != global_token, "Improper function call\n"); int passed = 0; if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) { emit_out("PUSHR R13 R15\t# Prevent overwriting in recursion\n"); emit_out("PUSHR R14 R15\t# Protect the old base pointer\n"); emit_out("COPY R13 R15\t# Copy new base pointer\n"); } else if(X86 == Architecture) { emit_out("push_edi\t# Prevent overwriting in recursion\n"); emit_out("push_ebp\t# Protect the old base pointer\n"); emit_out("mov_edi,esp\t# Copy new base pointer\n"); } else if(AMD64 == Architecture) { emit_out("push_rdi\t# Prevent overwriting in recursion\n"); emit_out("push_rbp\t# Protect the old base pointer\n"); emit_out("mov_rdi,rsp\t# Copy new base pointer\n"); } else if(ARMV7L == Architecture) { emit_out("{R11} PUSH_ALWAYS\t# Prevent overwriting in recursion\n"); emit_out("{BP} PUSH_ALWAYS\t# Protect the old base pointer\n"); emit_out("'0' SP R11 NO_SHIFT MOVE_ALWAYS\t# Copy new base pointer\n"); } else if(AARCH64 == Architecture) { emit_out("PUSH_X16\t# Protect a tmp register we're going to use\n"); emit_out("PUSH_LR\t# Protect the old return pointer (link)\n"); emit_out("PUSH_BP\t# Protect the old base pointer\n"); emit_out("SET_X16_FROM_SP\t# The base pointer to-be\n"); } else if(RISCV32 == Architecture) { emit_out("RD_SP RS1_SP !-12 ADDI\t# Allocate stack\n"); emit_out("RS1_SP RS2_RA @4 SW\t# Protect the old return pointer\n"); emit_out("RS1_SP RS2_FP SW\t# Protect the old frame pointer\n"); emit_out("RS1_SP RS2_TP @8 SW\t# Protect temp register we are going to use\n"); emit_out("RD_TP RS1_SP MV\t# The base pointer to-be\n"); } else if(RISCV64 == Architecture) { emit_out("RD_SP RS1_SP !-24 ADDI\t# Allocate stack\n"); emit_out("RS1_SP RS2_RA @8 SD\t# Protect the old return pointer\n"); emit_out("RS1_SP RS2_FP SD\t# Protect the old frame pointer\n"); emit_out("RS1_SP RS2_TP @16 SD\t# Protect temp register we are going to use\n"); emit_out("RD_TP RS1_SP MV\t# The base pointer to-be\n"); } if(global_token->s[0] != ')') { expression(); require(NULL != global_token, "incomplete function call, received EOF instead of )\n"); if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("PUSHR R0 R15\t#_process_expression1\n"); else if(X86 == Architecture) emit_out("push_eax\t#_process_expression1\n"); else if(AMD64 == Architecture) emit_out("push_rax\t#_process_expression1\n"); else if(ARMV7L == Architecture) emit_out("{R0} PUSH_ALWAYS\t#_process_expression1\n"); else if(AARCH64 == Architecture) emit_out("PUSH_X0\t#_process_expression1\n"); else if(RISCV32 == Architecture) emit_out("RD_SP RS1_SP !-4 ADDI\nRS1_SP RS2_A0 SW\t#_process_expression1\n"); else if(RISCV64 == Architecture) emit_out("RD_SP RS1_SP !-8 ADDI\nRS1_SP RS2_A0 SD\t#_process_expression1\n"); passed = 1; while(global_token->s[0] == ',') { global_token = global_token->next; require(NULL != global_token, "incomplete function call, received EOF instead of argument\n"); expression(); if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("PUSHR R0 R15\t#_process_expression2\n"); else if(X86 == Architecture) emit_out("push_eax\t#_process_expression2\n"); else if(AMD64 == Architecture) emit_out("push_rax\t#_process_expression2\n"); else if(ARMV7L == Architecture) emit_out("{R0} PUSH_ALWAYS\t#_process_expression2\n"); else if(AARCH64 == Architecture) emit_out("PUSH_X0\t#_process_expression2\n"); else if(RISCV32 == Architecture) emit_out("RD_SP RS1_SP !-4 ADDI\nRS1_SP RS2_A0 SW\t#_process_expression2\n"); else if(RISCV64 == Architecture) emit_out("RD_SP RS1_SP !-8 ADDI\nRS1_SP RS2_A0 SD\t#_process_expression2\n"); passed = passed + 1; } } require_match("ERROR in process_expression_list\nNo ) was found\n", ")"); if(TRUE == bool) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) { emit_out("LOAD R0 R14 "); emit_out(s); emit_out("\nMOVE R14 R13\n"); emit_out("CALL R0 R15\n"); } else if(X86 == Architecture) { emit_out("lea_eax,[ebp+DWORD] %"); emit_out(s); emit_out("\nmov_eax,[eax]\n"); emit_out("mov_ebp,edi\n"); emit_out("call_eax\n"); } else if(AMD64 == Architecture) { emit_out("lea_rax,[rbp+DWORD] %"); emit_out(s); emit_out("\nmov_rax,[rax]\n"); emit_out("mov_rbp,rdi\n"); emit_out("call_rax\n"); } else if(ARMV7L == Architecture) { emit_out("!"); emit_out(s); emit_out(" R0 SUB BP ARITH_ALWAYS\n"); emit_out("!0 R0 LOAD32 R0 MEMORY\n"); emit_out("{LR} PUSH_ALWAYS\t# Protect the old link register\n"); emit_out("'0' R11 BP NO_SHIFT MOVE_ALWAYS\n"); emit_out("'3' R0 CALL_REG_ALWAYS\n"); emit_out("{LR} POP_ALWAYS\t# Prevent overwrite\n"); } else if(AARCH64 == Architecture) { emit_out("SET_X0_FROM_BP\n"); emit_out("LOAD_W1_AHEAD\nSKIP_32_DATA\n%"); emit_out(s); emit_out("\nSUB_X0_X0_X1\n"); emit_out("DEREF_X0\n"); emit_out("SET_BP_FROM_X16\n"); emit_out("SET_X16_FROM_X0\n"); emit_out("BLR_X16\n"); } else if(RISCV32 == Architecture) { emit_out("RD_A0 RS1_FP !"); emit_out(s); emit_out(" ADDI\n"); emit_out("RD_A0 RS1_A0 LW\n"); emit_out("RD_FP RS1_TP MV\n"); emit_out("RD_RA RS1_A0 JALR\n"); } else if(RISCV64 == Architecture) { emit_out("RD_A0 RS1_FP !"); emit_out(s); emit_out(" ADDI\n"); emit_out("RD_A0 RS1_A0 LD\n"); emit_out("RD_FP RS1_TP MV\n"); emit_out("RD_RA RS1_A0 JALR\n"); } } else { if((KNIGHT_NATIVE == Architecture) || (KNIGHT_POSIX == Architecture)) { emit_out("MOVE R14 R13\n"); emit_out("LOADR R0 4\nJUMP 4\n&FUNCTION_"); emit_out(s); emit_out("\nCALL R0 R15\n"); } else if(X86 == Architecture) { emit_out("mov_ebp,edi\n"); emit_out("call %FUNCTION_"); emit_out(s); emit_out("\n"); } else if(AMD64 == Architecture) { emit_out("mov_rbp,rdi\n"); emit_out("call %FUNCTION_"); emit_out(s); emit_out("\n"); } else if(ARMV7L == Architecture) { emit_out("{LR} PUSH_ALWAYS\t# Protect the old link register\n"); emit_out("'0' R11 BP NO_SHIFT MOVE_ALWAYS\n"); emit_out("^~FUNCTION_"); emit_out(s); emit_out(" CALL_ALWAYS\n"); emit_out("{LR} POP_ALWAYS\t# Restore the old link register\n"); } else if(AARCH64 == Architecture) { emit_out("SET_BP_FROM_X16\n"); emit_out("LOAD_W16_AHEAD\nSKIP_32_DATA\n&FUNCTION_"); emit_out(s); emit_out("\n"); emit_out("BLR_X16\n"); } else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) { emit_out("RD_FP RS1_TP MV\n"); emit_out("RD_RA $FUNCTION_"); emit_out(s); emit_out(" JAL\n"); } } for(; passed > 0; passed = passed - 1) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("POPR R1 R15\t# _process_expression_locals\n"); else if(X86 == Architecture) emit_out("pop_ebx\t# _process_expression_locals\n"); else if(AMD64 == Architecture) emit_out("pop_rbx\t# _process_expression_locals\n"); else if(ARMV7L == Architecture) emit_out("{R1} POP_ALWAYS\t# _process_expression_locals\n"); else if(AARCH64 == Architecture) emit_out("POP_X1\t# _process_expression_locals\n"); else if(RISCV32 == Architecture) emit_out("RD_A1 RS1_SP LW\t# _process_expression_locals\nRD_SP RS1_SP !4 ADDI\n"); else if(RISCV64 == Architecture) emit_out("RD_A1 RS1_SP LD\t# _process_expression_locals\nRD_SP RS1_SP !8 ADDI\n"); } if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) { emit_out("POPR R14 R15\t# Restore old base pointer\n"); emit_out("POPR R13 R15\t# Prevent overwrite\n"); } else if(X86 == Architecture) { emit_out("pop_ebp\t# Restore old base pointer\n"); emit_out("pop_edi\t# Prevent overwrite\n"); } else if(AMD64 == Architecture) { emit_out("pop_rbp\t# Restore old base pointer\n"); emit_out("pop_rdi\t# Prevent overwrite\n"); } else if(ARMV7L == Architecture) { emit_out("{BP} POP_ALWAYS\t# Restore old base pointer\n"); emit_out("{R11} POP_ALWAYS\t# Prevent overwrite\n"); } else if(AARCH64 == Architecture) { emit_out("POP_BP\t# Restore the old base pointer\n"); emit_out("POP_LR\t# Restore the old return pointer (link)\n"); emit_out("POP_X16\t# Restore a register we used as tmp\n"); } else if(RISCV32 == Architecture) { emit_out("RD_FP RS1_SP LW\t# Restore old frame pointer\n"); emit_out("RD_TP RS1_SP !8 LW\t# Restore temp register\n"); emit_out("RD_RA RS1_SP !4 LW\t# Restore return address\n"); emit_out("RD_SP RS1_SP !12 ADDI\t# Deallocate stack\n"); } else if(RISCV64 == Architecture) { emit_out("RD_FP RS1_SP LD\t# Restore old frame pointer\n"); emit_out("RD_TP RS1_SP !16 LD\t# Restore temp register\n"); emit_out("RD_RA RS1_SP !8 LD\t# Restore return address\n"); emit_out("RD_SP RS1_SP !24 ADDI\t# Deallocate stack\n"); } } void constant_load(struct token_list* a) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("LOADI R0 "); else if(X86 == Architecture) emit_out("mov_eax, %"); else if(AMD64 == Architecture) emit_out("mov_rax, %"); else if(ARMV7L == Architecture) emit_out("!0 R0 LOAD32 R15 MEMORY\n~0 JUMP_ALWAYS\n%"); else if(AARCH64 == Architecture) emit_out("LOAD_W0_AHEAD\nSKIP_32_DATA\n%"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) { emit_out("RD_A0 ~"); emit_out(a->arguments->s); emit_out(" LUI\nRD_A0 RS1_A0 !"); } emit_out(a->arguments->s); if(RISCV32 == Architecture) emit_out(" ADDI\n"); else if(RISCV64 == Architecture) emit_out(" ADDIW\n"); emit_out("\n"); } char* load_value_signed(unsigned size) { if(size == 1) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) return "LOAD8 R0 R0 0\n"; else if(X86 == Architecture) return "movsx_eax,BYTE_PTR_[eax]\n"; else if(AMD64 == Architecture) return "movsx_rax,BYTE_PTR_[rax]\n"; else if(ARMV7L == Architecture) return "LOADS8 R0 LOAD R0 HALF_MEMORY\n"; else if(AARCH64 == Architecture) return "LDRSB_X0_[X0]\n"; else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) return "RD_A0 RS1_A0 LB\n"; } else if(size == 2) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) return "LOAD16 R0 R0 0\n"; else if(X86 == Architecture) return "movsx_eax,WORD_PTR_[eax]\n"; else if(AMD64 == Architecture) return "movsx_rax,WORD_PTR_[rax]\n"; else if(ARMV7L == Architecture) return "LOADS16 R0 LOAD R0 HALF_MEMORY\n"; else if(AARCH64 == Architecture) return "LDRSH_X0_[X0]\n"; else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) return "RD_A0 RS1_A0 LH\n"; } else if(size == 4) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) return "LOAD R0 R0 0\n"; else if(X86 == Architecture) return "mov_eax,[eax]\n"; else if(AMD64 == Architecture) return "movsx_rax,DWORD_PTR_[rax]\n"; else if(ARMV7L == Architecture) return "!0 R0 LOAD32 R0 MEMORY\n"; else if(AARCH64 == Architecture) return "LDR_W0_[X0]\n"; else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) return "RD_A0 RS1_A0 LW\n"; } else if(size == 8) { if(AMD64 == Architecture) return "mov_rax,[rax]\n"; else if(AARCH64 == Architecture) return "DEREF_X0\n"; else if(RISCV64 == Architecture) return "RD_A0 RS1_A0 LD\n"; } line_error(); fputs(" Got unsupported size ", stderr); fputs(int2str(size, 10, TRUE), stderr); fputs(" when trying to load value.\n", stderr); exit(EXIT_FAILURE); } char* load_value_unsigned(unsigned size) { if(size == 1) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) return "LOADU8 R0 R0 0\n"; else if(X86 == Architecture) return "movzx_eax,BYTE_PTR_[eax]\n"; else if(AMD64 == Architecture) return "movzx_rax,BYTE_PTR_[rax]\n"; else if(ARMV7L == Architecture) return "!0 R0 LOAD R0 MEMORY\n"; else if(AARCH64 == Architecture) return "DEREF_X0_BYTE\n"; else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) return "RD_A0 RS1_A0 LBU\n"; } else if(size == 2) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) return "LOADU16 R0 R0 0\n"; else if(X86 == Architecture) return "movzx_eax,WORD_PTR_[eax]\n"; else if(AMD64 == Architecture) return "movzx_rax,WORD_PTR_[rax]\n"; else if(ARMV7L == Architecture) return "NO_OFFSET R0 LOAD R0 HALF_MEMORY\n"; else if(AARCH64 == Architecture) return "LDRH_W0_[X0]\n"; else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) return "RD_A0 RS1_A0 LHU\n"; } else if(size == 4) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) return "LOAD R0 R0 0\n"; else if(X86 == Architecture) return "mov_eax,[eax]\n"; else if(AMD64 == Architecture) return "mov_eax,[rax]\n"; else if(ARMV7L == Architecture) return "!0 R0 LOAD32 R0 MEMORY\n"; else if(AARCH64 == Architecture) return "LDR_W0_[X0]\n"; else if(RISCV32 == Architecture) return "RD_A0 RS1_A0 LW\n"; else if(RISCV64 == Architecture) return "RD_A0 RS1_A0 LWU\n"; } else if(size == 8) { if(AMD64 == Architecture) return "mov_rax,[rax]\n"; else if(AARCH64 == Architecture) return "DEREF_X0\n"; else if(RISCV64 == Architecture) return "RD_A0 RS1_A0 LD\n"; } line_error(); fputs(" Got unsupported size ", stderr); fputs(int2str(size, 10, TRUE), stderr); fputs(" when trying to load value.\n", stderr); exit(EXIT_FAILURE); } char* load_value(unsigned size, int is_signed) { if(is_signed) return load_value_signed(size); return load_value_unsigned(size); } char* store_value(unsigned size) { if(size == 1) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) return "STORE8 R0 R1 0\n"; else if(X86 == Architecture) return "mov_[ebx],al\n"; else if(AMD64 == Architecture) return "mov_[rbx],al\n"; else if(ARMV7L == Architecture) return "!0 R0 STORE8 R1 MEMORY\n"; else if(AARCH64 == Architecture) return "STR_BYTE_W0_[X1]\n"; else if(RISCV32 == Architecture) return "RS1_A1 RS2_A0 SB\n"; else if(RISCV64 == Architecture) return "RS1_A1 RS2_A0 SB\n"; } else if(size == 2) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) return "STORE16 R0 R1 0\n"; else if(X86 == Architecture) return "mov_[ebx],ax\n"; else if(AMD64 == Architecture) return "mov_[rbx],ax\n"; else if(ARMV7L == Architecture) return "NO_OFFSET R0 STORE16 R1 HALF_MEMORY\n"; else if(AARCH64 == Architecture) return "STRH_W0_[X1]\n"; else if(RISCV32 == Architecture || RISCV64 == Architecture) return "RS1_A1 RS2_A0 SH\n"; } else if(size == 4) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) return "STORE R0 R1 0\n"; else if(X86 == Architecture) return "mov_[ebx],eax\n"; else if(AMD64 == Architecture) return "mov_[rbx],eax\n"; else if(ARMV7L == Architecture) return "!0 R0 STORE32 R1 MEMORY\n"; else if(AARCH64 == Architecture) return "STR_W0_[X1]\n"; else if(RISCV32 == Architecture || RISCV64 == Architecture) return "RS1_A1 RS2_A0 SW\n"; } else if(size == 8) { if(AMD64 == Architecture) return "mov_[rbx],rax\n"; else if(AARCH64 == Architecture) return "STR_X0_[X1]\n"; else if(RISCV64 == Architecture) return "RS1_A1 RS2_A0 SD\n"; } /* Should not happen but print error message. */ fputs("Got unsupported size ", stderr); fputs(int2str(size, 10, TRUE), stderr); fputs(" when storing number in register.\n", stderr); line_error(); exit(EXIT_FAILURE); } int is_compound_assignment(char* token) { if(match("+=", token)) return TRUE; else if(match("-=", token)) return TRUE; else if(match("*=", token)) return TRUE; else if(match("/=", token)) return TRUE; else if(match("%=", token)) return TRUE; else if(match("<<=", token)) return TRUE; else if(match(">>=", token)) return TRUE; else if(match("&=", token)) return TRUE; else if(match("^=", token)) return TRUE; else if(match("|=", token)) return TRUE; return FALSE; } void postfix_expr_stub(); void variable_load(struct token_list* a, int num_dereference) { require(NULL != global_token, "incomplete variable load received\n"); if((match("FUNCTION", a->type->name) || match("FUNCTION*", a->type->name)) && match("(", global_token->s)) { function_call(int2str(a->depth, 10, TRUE), TRUE); return; } current_target = a->type; if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("ADDI R0 R14 "); else if(X86 == Architecture) emit_out("lea_eax,[ebp+DWORD] %"); else if(AMD64 == Architecture) emit_out("lea_rax,[rbp+DWORD] %"); else if(ARMV7L == Architecture) emit_out("!"); else if(AARCH64 == Architecture) emit_out("SET_X0_FROM_BP\nLOAD_W1_AHEAD\nSKIP_32_DATA\n%"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("RD_A0 RS1_FP !"); emit_out(int2str(a->depth, 10, TRUE)); if(ARMV7L == Architecture) emit_out(" R0 SUB BP ARITH_ALWAYS"); else if(AARCH64 == Architecture) emit_out("\nSUB_X0_X0_X1\n"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out(" ADDI"); emit_out("\n"); if(TRUE == Address_of) return; if(match(".", global_token->s)) { postfix_expr_stub(); return; } if(!match("=", global_token->s) && !is_compound_assignment(global_token->s)) { emit_out(load_value(current_target->size, current_target->is_signed)); } while (num_dereference > 0) { current_target = current_target->type; emit_out(load_value(current_target->size, current_target->is_signed)); num_dereference = num_dereference - 1; } } void function_load(struct token_list* a) { require(NULL != global_token, "incomplete function load\n"); if(match("(", global_token->s)) { function_call(a->s, FALSE); return; } if((KNIGHT_NATIVE == Architecture) || (KNIGHT_POSIX == Architecture)) emit_out("LOADR R0 4\nJUMP 4\n&FUNCTION_"); else if(X86 == Architecture) emit_out("mov_eax, &FUNCTION_"); else if(AMD64 == Architecture) emit_out("lea_rax,[rip+DWORD] %FUNCTION_"); else if(ARMV7L == Architecture) emit_out("!0 R0 LOAD32 R15 MEMORY\n~0 JUMP_ALWAYS\n&FUNCTION_"); else if(AARCH64 == Architecture) emit_out("LOAD_W0_AHEAD\nSKIP_32_DATA\n&FUNCTION_"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("RD_A0 ~FUNCTION_"); emit_out(a->s); if(RISCV32 == Architecture) { emit_out(" AUIPC\n"); emit_out("RD_A0 RS1_A0 !FUNCTION_"); emit_out(a->s); emit_out(" ADDI"); } else if(RISCV64 == Architecture) { emit_out(" AUIPC\n"); emit_out("RD_A0 RS1_A0 !FUNCTION_"); emit_out(a->s); emit_out(" ADDIW"); } emit_out("\n"); } void global_load(struct token_list* a) { current_target = a->type; if((KNIGHT_NATIVE == Architecture) || (KNIGHT_POSIX == Architecture)) emit_out("LOADR R0 4\nJUMP 4\n&GLOBAL_"); else if(X86 == Architecture) emit_out("mov_eax, &GLOBAL_"); else if(AMD64 == Architecture) emit_out("lea_rax,[rip+DWORD] %GLOBAL_"); else if(ARMV7L == Architecture) emit_out("!0 R0 LOAD32 R15 MEMORY\n~0 JUMP_ALWAYS\n&GLOBAL_"); else if(AARCH64 == Architecture) emit_out("LOAD_W0_AHEAD\nSKIP_32_DATA\n&GLOBAL_"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("RD_A0 ~GLOBAL_"); emit_out(a->s); if((RISCV32 == Architecture) || (RISCV64 == Architecture)) { emit_out(" AUIPC\n"); emit_out("RD_A0 RS1_A0 !GLOBAL_"); emit_out(a->s); emit_out(" ADDI"); } emit_out("\n"); require(NULL != global_token, "unterminated global load\n"); if(TRUE == Address_of) return; if(match(".", global_token->s)) { postfix_expr_stub(); return; } if(match("=", global_token->s) || is_compound_assignment(global_token->s)) return; emit_out(load_value(register_size, current_target->is_signed)); } /* * primary-expr: * FAILURE * "String" * 'Char' * [0-9]* * [a-z,A-Z]* * ( expression ) */ void primary_expr_failure() { require(NULL != global_token, "hit EOF when expecting primary expression\n"); line_error(); fputs("Received ", stderr); fputs(global_token->s, stderr); fputs(" in primary_expr\n", stderr); exit(EXIT_FAILURE); } void primary_expr_string() { char* number_string = int2str(current_count, 10, TRUE); current_count = current_count + 1; if((KNIGHT_NATIVE == Architecture) || (KNIGHT_POSIX == Architecture)) emit_out("LOADR R0 4\nJUMP 4\n&STRING_"); else if(X86 == Architecture) emit_out("mov_eax, &STRING_"); else if(AMD64 == Architecture) emit_out("lea_rax,[rip+DWORD] %STRING_"); else if(ARMV7L == Architecture) emit_out("!0 R0 LOAD32 R15 MEMORY\n~0 JUMP_ALWAYS\n&STRING_"); else if(AARCH64 == Architecture) emit_out("LOAD_W0_AHEAD\nSKIP_32_DATA\n&STRING_"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("RD_A0 ~STRING_"); uniqueID_out(function->s, number_string); if((RISCV32 == Architecture) || (RISCV64 == Architecture)) { emit_out("AUIPC\n"); emit_out("RD_A0 RS1_A0 !STRING_"); uniqueID_out(function->s, number_string); emit_out("ADDI\n"); } /* The target */ strings_list = emit(":STRING_", strings_list); strings_list = uniqueID(function->s, strings_list, number_string); /* catch case of just "foo" from segfaulting */ require(NULL != global_token->next, "a string by itself is not valid C\n"); /* Parse the string */ if('"' != global_token->next->s[0]) { strings_list = emit(parse_string(global_token->s), strings_list); global_token = global_token->next; } else { char* s = calloc(MAX_STRING, sizeof(char)); /* prefix leading string */ s[0] = '"'; int i = 1; int j; while('"' == global_token->s[0]) { /* Step past the leading '"' */ j = 1; /* Copy the rest of the string as is */ while(0 != global_token->s[j]) { require(i < MAX_STRING, "concat string exceeded max string length\n"); s[i] = global_token->s[j]; i = i + 1; j = j + 1; } /* Move on to the next token */ global_token = global_token->next; require(NULL != global_token, "multi-string null is not valid C\n"); } /* Now use it */ strings_list = emit(parse_string(s), strings_list); } } void primary_expr_char() { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("LOADI R0 "); else if(X86 == Architecture) emit_out("mov_eax, %"); else if(AMD64 == Architecture) emit_out("mov_rax, %"); else if(ARMV7L == Architecture) emit_out("!"); else if(AARCH64 == Architecture) emit_out("LOAD_W0_AHEAD\nSKIP_32_DATA\n%"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("RD_A0 !"); emit_out(int2str(escape_lookup(global_token->s + 1), 10, TRUE)); if(ARMV7L == Architecture) emit_out(" R0 LOADI8_ALWAYS"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out(" ADDI"); emit_out("\n"); global_token = global_token->next; } int hex2char(int c) { if((c >= 0) && (c <= 9)) return (c + 48); else if((c >= 10) && (c <= 15)) return (c + 55); else return -1; } char* number_to_hex(int a, int bytes) { require(bytes > 0, "number to hex must have a positive number of bytes greater than zero\n"); char* result = calloc(1 + (bytes << 1), sizeof(char)); if(NULL == result) { fputs("calloc failed in number_to_hex\n", stderr); exit(EXIT_FAILURE); } int i = 0; int divisor = (bytes << 3); require(divisor > 0, "unexpected wrap around in number_to_hex\n"); /* Simply collect numbers until divisor is gone */ while(0 != divisor) { divisor = divisor - 4; result[i] = hex2char((a >> divisor) & 0xF); i = i + 1; } return result; } void primary_expr_number() { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) { int size = strtoint(global_token->s); if((32767 > size) && (size > -32768)) { emit_out("LOADI R0 "); emit_out(global_token->s); } else { emit_out("LOADR R0 4\nJUMP 4\n'"); emit_out(number_to_hex(size, register_size)); emit_out("'"); } } else if(X86 == Architecture) { emit_out("mov_eax, %"); emit_out(global_token->s); } else if(AMD64 == Architecture) { emit_out("mov_rax, %"); emit_out(global_token->s); } else if(ARMV7L == Architecture) { emit_out("!0 R0 LOAD32 R15 MEMORY\n~0 JUMP_ALWAYS\n%"); emit_out(global_token->s); } else if(AARCH64 == Architecture) { emit_out("LOAD_W0_AHEAD\nSKIP_32_DATA\n%"); emit_out(global_token->s); } else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) { int size = strtoint(global_token->s); if((2047 > size) && (size > -2048)) { emit_out("RD_A0 !"); emit_out(global_token->s); emit_out(" ADDI"); } else if (0 == (size >> 30)) { emit_out("RD_A0 ~"); emit_out(global_token->s); emit_out(" LUI\n"); emit_out("RD_A0 RS1_A0 !"); emit_out(global_token->s); emit_out(" ADDI"); } else { int high = size >> 30; int low = ((size >> 30) << 30) ^ size; emit_out("RD_A0 ~"); emit_out(int2str(high, 10, TRUE)); emit_out(" LUI\n"); emit_out("RD_A0 RS1_A0 !"); emit_out(int2str(high, 10, TRUE)); emit_out(" ADDI\n"); emit_out("RD_A0 RS1_A0 RS2_X30 SLLI\n"); emit_out("RD_T1 ~"); emit_out(int2str(low, 10, TRUE)); emit_out(" LUI\n"); emit_out("RD_T1 RS1_T1 !"); emit_out(int2str(low, 10, TRUE)); emit_out(" ADDI\n"); emit_out("RD_A0 RS1_A0 RS2_T1 OR\n"); } } emit_out("\n"); global_token = global_token->next; } void primary_expr_variable() { int num_dereference = 0; while(global_token->s[0] == '*') { global_token = global_token->next; require(NULL != global_token, "Walked off the end of a variable dereference\n"); num_dereference = num_dereference + 1; } char* s = global_token->s; global_token = global_token->next; struct token_list* a = sym_lookup(s, global_constant_list); if(NULL != a) { constant_load(a); return; } a = sym_lookup(s, function->locals); if(NULL != a) { variable_load(a, num_dereference); return; } a = sym_lookup(s, function->arguments); if(NULL != a) { variable_load(a, num_dereference); return; } a = sym_lookup(s, global_function_list); if(NULL != a) { function_load(a); return; } a = sym_lookup(s, global_symbol_list); if(NULL != a) { global_load(a); return; } line_error(); fputs(s ,stderr); fputs(" is not a defined symbol\n", stderr); exit(EXIT_FAILURE); } void primary_expr(); struct type* promote_type(struct type* a, struct type* b) { require(NULL != b, "impossible case 1 in promote_type\n"); require(NULL != a, "impossible case 2 in promote_type\n"); if(a == b) return a; struct type* i; for(i = global_types; NULL != i; i = i->next) { if(a->name == i->name) break; if(b->name == i->name) break; if(a->name == i->indirect->name) break; if(b->name == i->indirect->name) break; if(a->name == i->indirect->indirect->name) break; if(b->name == i->indirect->indirect->name) break; } require(NULL != i, "impossible case 3 in promote_type\n"); return i; } void common_recursion(FUNCTION f) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("PUSHR R0 R15\t#_common_recursion\n"); else if(X86 == Architecture) emit_out("push_eax\t#_common_recursion\n"); else if(AMD64 == Architecture) emit_out("push_rax\t#_common_recursion\n"); else if(ARMV7L == Architecture) emit_out("{R0} PUSH_ALWAYS\t#_common_recursion\n"); else if(AARCH64 == Architecture) emit_out("PUSH_X0\t#_common_recursion\n"); else if(RISCV32 == Architecture) emit_out("RD_SP RS1_SP !-4 ADDI\t# _common_recursion\nRS1_SP RS2_A0 SW\n"); else if(RISCV64 == Architecture) emit_out("RD_SP RS1_SP !-8 ADDI\t# _common_recursion\nRS1_SP RS2_A0 SD\n"); struct type* last_type = current_target; global_token = global_token->next; require(NULL != global_token, "Received EOF in common_recursion\n"); f(); current_target = promote_type(current_target, last_type); if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("POPR R1 R15\t# _common_recursion\n"); else if(X86 == Architecture) emit_out("pop_ebx\t# _common_recursion\n"); else if(AMD64 == Architecture) emit_out("pop_rbx\t# _common_recursion\n"); else if(ARMV7L == Architecture) emit_out("{R1} POP_ALWAYS\t# _common_recursion\n"); else if(AARCH64 == Architecture) emit_out("POP_X1\t# _common_recursion\n"); else if(RISCV32 == Architecture) emit_out("RD_A1 RS1_SP LW\nRD_SP RS1_SP !4 ADDI\t# _common_recursion\n"); else if(RISCV64 == Architecture) emit_out("RD_A1 RS1_SP LD\nRD_SP RS1_SP !8 ADDI\t# _common_recursion\n"); } void general_recursion(FUNCTION f, char* s, char* name, FUNCTION iterate) { require(NULL != global_token, "Received EOF in general_recursion\n"); if(match(name, global_token->s)) { common_recursion(f); emit_out(s); iterate(); } } void arithmetic_recursion(FUNCTION f, char* s1, char* s2, char* name, FUNCTION iterate) { require(NULL != global_token, "Received EOF in arithmetic_recursion\n"); if(match(name, global_token->s)) { common_recursion(f); if(NULL == current_target) { emit_out(s1); } else if(current_target->is_signed) { emit_out(s1); } else { emit_out(s2); } iterate(); } } /* * postfix-expr: * primary-expr * postfix-expr [ expression ] * postfix-expr ( expression-list-opt ) * postfix-expr -> member * postfix-expr . member */ struct type* lookup_member(struct type* parent, char* name); void postfix_expr_arrow() { emit_out("# looking up offset\n"); global_token = global_token->next; require(NULL != global_token, "naked -> not allowed\n"); struct type* i = lookup_member(current_target, global_token->s); current_target = i->type; global_token = global_token->next; require(NULL != global_token, "Unterminated -> expression not allowed\n"); if(0 != i->offset) { emit_out("# -> offset calculation\n"); if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) { emit_out("ADDUI R0 R0 "); emit_out(int2str(i->offset, 10, TRUE)); emit_out("\n"); } else if(X86 == Architecture) { emit_out("mov_ebx, %"); emit_out(int2str(i->offset, 10, TRUE)); emit_out("\nadd_eax,ebx\n"); } else if(AMD64 == Architecture) { emit_out("mov_rbx, %"); emit_out(int2str(i->offset, 10, TRUE)); emit_out("\nadd_rax,rbx\n"); } else if(ARMV7L == Architecture) { emit_out("!0 R1 LOAD32 R15 MEMORY\n~0 JUMP_ALWAYS\n%"); emit_out(int2str(i->offset, 10, TRUE)); emit_out("\n'0' R0 R0 ADD R1 ARITH2_ALWAYS\n"); } else if(AARCH64 == Architecture) { emit_out("LOAD_W1_AHEAD\nSKIP_32_DATA\n%"); emit_out(int2str(i->offset, 10, TRUE)); emit_out("\nADD_X0_X1_X0\n"); } else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) { emit_out("RD_A1 !"); emit_out(int2str(i->offset, 10, TRUE)); emit_out(" ADDI\n"); emit_out("RD_A0 RS1_A1 RS2_A0 ADD\n"); } } /* We don't yet support assigning structs to structs */ if((!match("=", global_token->s) && !is_compound_assignment(global_token->s) && (register_size >= i->size))) { emit_out(load_value(i->size, i->is_signed)); } } void postfix_expr_dot() { maybe_bootstrap_error("Member access using ."); emit_out("# looking up offset\n"); global_token = global_token->next; require(NULL != global_token, "naked . not allowed\n"); struct type* i = lookup_member(current_target, global_token->s); current_target = i->type; global_token = global_token->next; require(NULL != global_token, "Unterminated . expression not allowed\n"); if(0 != i->offset) { emit_out("# . offset calculation\n"); if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) { emit_out("ADDUI R0 R0 "); emit_out(int2str(i->offset, 10, TRUE)); emit_out("\n"); } else if(X86 == Architecture) { emit_out("mov_ebx, %"); emit_out(int2str(i->offset, 10, TRUE)); emit_out("\nadd_eax,ebx\n"); } else if(AMD64 == Architecture) { emit_out("mov_rbx, %"); emit_out(int2str(i->offset, 10, TRUE)); emit_out("\nadd_rax,rbx\n"); } else if(ARMV7L == Architecture) { emit_out("!0 R1 LOAD32 R15 MEMORY\n~0 JUMP_ALWAYS\n%"); emit_out(int2str(i->offset, 10, TRUE)); emit_out("\n'0' R0 R0 ADD R1 ARITH2_ALWAYS\n"); } else if(AARCH64 == Architecture) { emit_out("LOAD_W1_AHEAD\nSKIP_32_DATA\n%"); emit_out(int2str(i->offset, 10, TRUE)); emit_out("\nADD_X0_X1_X0\n"); } else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) { emit_out("RD_A1 !"); emit_out(int2str(i->offset, 10, TRUE)); emit_out(" ADDI\n"); emit_out("RD_A0 RS1_A1 RS2_A0 ADD\n"); } } if(match("=", global_token->s) || is_compound_assignment(global_token->s)) return; if(match("[", global_token->s)) return; emit_out(load_value(current_target->size, current_target->is_signed)); } void postfix_expr_array() { struct type* array = current_target; common_recursion(expression); current_target = array; require(NULL != current_target, "Arrays only apply to variables\n"); char* assign = load_value(register_size, current_target->is_signed); /* Add support for Ints */ if(match("char*", current_target->name)) { assign = load_value(1, TRUE); } else { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("PUSHR R1 R15\nLOADI R1 "); else if(X86 == Architecture) emit_out("push_ebx\nmov_ebx, %"); else if(AMD64 == Architecture) emit_out("push_rbx\nmov_rbx, %"); else if(ARMV7L == Architecture) emit_out("{R1} PUSH_ALWAYS\n!0 R1 LOAD32 R15 MEMORY\n~0 JUMP_ALWAYS\n%"); else if(AARCH64 == Architecture) emit_out("PUSH_X1\nLOAD_W1_AHEAD\nSKIP_32_DATA\n%"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("RD_A2 RS1_A1 ADDI\nRD_A1 !"); emit_out(int2str(current_target->type->size, 10, TRUE)); if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out(" ADDI"); if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("\nMULU R0 R1 R0\nPOPR R1 R15\n"); else if(X86 == Architecture) emit_out("\nmul_ebx\npop_ebx\n"); else if(AMD64 == Architecture) emit_out("\nmul_rbx\npop_rbx\n"); else if(ARMV7L == Architecture) emit_out("\n'9' R0 '0' R1 MUL R0 ARITH2_ALWAYS\n{R1} POP_ALWAYS\n"); else if(AARCH64 == Architecture) emit_out("\nMUL_X0_X1_X0\nPOP_X1\n"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("\nRD_A0 RS1_A1 RS2_A0 MUL\nRD_A1 RS1_A2 ADDI\n"); } if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("ADD R0 R0 R1\n"); else if(X86 == Architecture) emit_out("add_eax,ebx\n"); else if(AMD64 == Architecture) emit_out("add_rax,rbx\n"); else if(ARMV7L == Architecture) emit_out("'0' R0 R0 ADD R1 ARITH2_ALWAYS\n"); else if(AARCH64 == Architecture) emit_out("ADD_X0_X1_X0\n"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("RD_A0 RS1_A1 RS2_A0 ADD\n"); require_match("ERROR in postfix_expr\nMissing ]\n", "]"); require(NULL != global_token, "truncated array expression\n"); if(match("=", global_token->s) || is_compound_assignment(global_token->s) || match(".", global_token->s)) { assign = ""; } if(match("[", global_token->s)) { current_target = current_target->type; } emit_out(assign); } /* * unary-expr: * &postfix-expr * - postfix-expr * !postfix-expr * sizeof ( type ) */ struct type* type_name(); void unary_expr_sizeof() { global_token = global_token->next; require(NULL != global_token, "Received EOF when starting sizeof\n"); require_match("ERROR in unary_expr\nMissing (\n", "("); struct type* a = type_name(); require_match("ERROR in unary_expr\nMissing )\n", ")"); if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("LOADUI R0 "); else if(X86 == Architecture) emit_out("mov_eax, %"); else if(AMD64 == Architecture) emit_out("mov_rax, %"); else if(ARMV7L == Architecture) emit_out("!"); else if(AARCH64 == Architecture) emit_out("LOAD_W0_AHEAD\nSKIP_32_DATA\n%"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("RD_A0 !"); emit_out(int2str(a->size, 10, TRUE)); if(ARMV7L == Architecture) emit_out(" R0 LOADI8_ALWAYS"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out(" ADDI"); emit_out("\n"); } void postfix_expr_stub() { require(NULL != global_token, "Unexpected EOF, improperly terminated primary expression\n"); if(match("[", global_token->s)) { postfix_expr_array(); postfix_expr_stub(); } if(match("->", global_token->s)) { postfix_expr_arrow(); postfix_expr_stub(); } if(match(".", global_token->s)) { postfix_expr_dot(); postfix_expr_stub(); } } void postfix_expr() { primary_expr(); postfix_expr_stub(); } /* * additive-expr: * postfix-expr * additive-expr * postfix-expr * additive-expr / postfix-expr * additive-expr % postfix-expr * additive-expr + postfix-expr * additive-expr - postfix-expr * additive-expr << postfix-expr * additive-expr >> postfix-expr */ void additive_expr_stub() { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) { arithmetic_recursion(postfix_expr, "ADD R0 R1 R0\n", "ADDU R0 R1 R0\n", "+", additive_expr_stub); arithmetic_recursion(postfix_expr, "SUB R0 R1 R0\n", "SUBU R0 R1 R0\n", "-", additive_expr_stub); arithmetic_recursion(postfix_expr, "MUL R0 R1 R0\n", "MULU R0 R1 R0\n", "*", additive_expr_stub); arithmetic_recursion(postfix_expr, "DIV R0 R1 R0\n", "DIVU R0 R1 R0\n", "/", additive_expr_stub); arithmetic_recursion(postfix_expr, "MOD R0 R1 R0\n", "MODU R0 R1 R0\n", "%", additive_expr_stub); arithmetic_recursion(postfix_expr, "SAL R0 R1 R0\n", "SL0 R0 R1 R0\n", "<<", additive_expr_stub); arithmetic_recursion(postfix_expr, "SAR R0 R1 R0\n", "SR0 R0 R1 R0\n", ">>", additive_expr_stub); } else if(X86 == Architecture) { arithmetic_recursion(postfix_expr, "add_eax,ebx\n", "add_eax,ebx\n", "+", additive_expr_stub); arithmetic_recursion(postfix_expr, "sub_ebx,eax\nmov_eax,ebx\n", "sub_ebx,eax\nmov_eax,ebx\n", "-", additive_expr_stub); arithmetic_recursion(postfix_expr, "imul_ebx\n", "mul_ebx\n", "*", additive_expr_stub); arithmetic_recursion(postfix_expr, "xchg_ebx,eax\ncdq\nidiv_ebx\n", "xchg_ebx,eax\nmov_edx, %0\ndiv_ebx\n", "/", additive_expr_stub); arithmetic_recursion(postfix_expr, "xchg_ebx,eax\ncdq\nidiv_ebx\nmov_eax,edx\n", "xchg_ebx,eax\nmov_edx, %0\ndiv_ebx\nmov_eax,edx\n", "%", additive_expr_stub); arithmetic_recursion(postfix_expr, "mov_ecx,eax\nmov_eax,ebx\nsal_eax,cl\n", "mov_ecx,eax\nmov_eax,ebx\nshl_eax,cl\n", "<<", additive_expr_stub); arithmetic_recursion(postfix_expr, "mov_ecx,eax\nmov_eax,ebx\nsar_eax,cl\n", "mov_ecx,eax\nmov_eax,ebx\nshr_eax,cl\n", ">>", additive_expr_stub); } else if(AMD64 == Architecture) { arithmetic_recursion(postfix_expr, "add_rax,rbx\n", "add_rax,rbx\n", "+", additive_expr_stub); arithmetic_recursion(postfix_expr, "sub_rbx,rax\nmov_rax,rbx\n", "sub_rbx,rax\nmov_rax,rbx\n", "-", additive_expr_stub); arithmetic_recursion(postfix_expr, "imul_rbx\n", "mul_rbx\n", "*", additive_expr_stub); arithmetic_recursion(postfix_expr, "xchg_rbx,rax\ncqo\nidiv_rbx\n", "xchg_rbx,rax\nmov_rdx, %0\ndiv_rbx\n", "/", additive_expr_stub); arithmetic_recursion(postfix_expr, "xchg_rbx,rax\ncqo\nidiv_rbx\nmov_rax,rdx\n", "xchg_rbx,rax\nmov_rdx, %0\ndiv_rbx\nmov_rax,rdx\n", "%", additive_expr_stub); arithmetic_recursion(postfix_expr, "mov_rcx,rax\nmov_rax,rbx\nsal_rax,cl\n", "mov_rcx,rax\nmov_rax,rbx\nshl_rax,cl\n", "<<", additive_expr_stub); arithmetic_recursion(postfix_expr, "mov_rcx,rax\nmov_rax,rbx\nsar_rax,cl\n", "mov_rcx,rax\nmov_rax,rbx\nshr_rax,cl\n", ">>", additive_expr_stub); } else if(ARMV7L == Architecture) { arithmetic_recursion(postfix_expr, "'0' R0 R0 ADD R1 ARITH2_ALWAYS\n", "'0' R0 R0 ADD R1 ARITH2_ALWAYS\n", "+", additive_expr_stub); arithmetic_recursion(postfix_expr, "'0' R0 R0 SUB R1 ARITH2_ALWAYS\n", "'0' R0 R0 SUB R1 ARITH2_ALWAYS\n", "-", additive_expr_stub); arithmetic_recursion(postfix_expr, "'9' R0 '0' R1 MULS R0 ARITH2_ALWAYS\n", "'9' R0 '0' R1 MUL R0 ARITH2_ALWAYS\n", "*", additive_expr_stub); arithmetic_recursion(postfix_expr, "{LR} PUSH_ALWAYS\n^~divides CALL_ALWAYS\n{LR} POP_ALWAYS\n", "{LR} PUSH_ALWAYS\n^~divide CALL_ALWAYS\n{LR} POP_ALWAYS\n", "/", additive_expr_stub); arithmetic_recursion(postfix_expr, "{LR} PUSH_ALWAYS\n^~moduluss CALL_ALWAYS\n{LR} POP_ALWAYS\n", "{LR} PUSH_ALWAYS\n^~modulus CALL_ALWAYS\n{LR} POP_ALWAYS\n", "%", additive_expr_stub); arithmetic_recursion(postfix_expr, "LEFT R1 R0 R0 SHIFT AUX_ALWAYS\n", "LEFT R1 R0 R0 SHIFT AUX_ALWAYS\n", "<<", additive_expr_stub); arithmetic_recursion(postfix_expr, "ARITH_RIGHT R1 R0 R0 SHIFT AUX_ALWAYS\n", "RIGHT R1 R0 R0 SHIFT AUX_ALWAYS\n", ">>", additive_expr_stub); } else if(AARCH64 == Architecture) { general_recursion(postfix_expr, "ADD_X0_X1_X0\n", "+", additive_expr_stub); general_recursion(postfix_expr, "SUB_X0_X1_X0\n", "-", additive_expr_stub); general_recursion(postfix_expr, "MUL_X0_X1_X0\n", "*", additive_expr_stub); arithmetic_recursion(postfix_expr, "SDIV_X0_X1_X0\n", "UDIV_X0_X1_X0\n", "/", additive_expr_stub); arithmetic_recursion(postfix_expr, "SDIV_X2_X1_X0\nMSUB_X0_X0_X2_X1\n", "UDIV_X2_X1_X0\nMSUB_X0_X0_X2_X1\n", "%", additive_expr_stub); general_recursion(postfix_expr, "LSHIFT_X0_X1_X0\n", "<<", additive_expr_stub); arithmetic_recursion(postfix_expr, "ARITH_RSHIFT_X0_X1_X0\n", "LOGICAL_RSHIFT_X0_X1_X0\n", ">>", additive_expr_stub); } else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) { general_recursion(postfix_expr, "RD_A0 RS1_A1 RS2_A0 ADD\n", "+", additive_expr_stub); general_recursion(postfix_expr, "RD_A0 RS1_A1 RS2_A0 SUB\n", "-", additive_expr_stub); general_recursion(postfix_expr, "RD_A0 RS1_A1 RS2_A0 MUL\n", "*", additive_expr_stub); arithmetic_recursion(postfix_expr, "RD_A0 RS1_A1 RS2_A0 DIV\n", "RD_A0 RS1_A1 RS2_A0 DIVU\n", "/", additive_expr_stub); arithmetic_recursion(postfix_expr, "RD_A0 RS1_A1 RS2_A0 REM\n", "RD_A0 RS1_A1 RS2_A0 REMU\n", "%", additive_expr_stub); general_recursion(postfix_expr, "RD_A0 RS1_A1 RS2_A0 SLL\n", "<<", additive_expr_stub); arithmetic_recursion(postfix_expr, "RD_A0 RS1_A1 RS2_A0 SRA\n", "RD_A0 RS1_A1 RS2_A0 SRL\n", ">>", additive_expr_stub); } } void additive_expr() { postfix_expr(); additive_expr_stub(); } /* * relational-expr: * additive_expr * relational-expr < additive_expr * relational-expr <= additive_expr * relational-expr >= additive_expr * relational-expr > additive_expr */ void relational_expr_stub() { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) { arithmetic_recursion(additive_expr, "CMP R0 R1 R0\nSET.L R0 R0 1\n", "CMPU R0 R1 R0\nSET.L R0 R0 1\n", "<", relational_expr_stub); arithmetic_recursion(additive_expr, "CMP R0 R1 R0\nSET.LE R0 R0 1\n", "CMPU R0 R1 R0\nSET.LE R0 R0 1\n", "<=", relational_expr_stub); arithmetic_recursion(additive_expr, "CMP R0 R1 R0\nSET.GE R0 R0 1\n", "CMPU R0 R1 R0\nSET.GE R0 R0 1\n", ">=", relational_expr_stub); arithmetic_recursion(additive_expr, "CMP R0 R1 R0\nSET.G R0 R0 1\n", "CMPU R0 R1 R0\nSET.G R0 R0 1\n", ">", relational_expr_stub); arithmetic_recursion(additive_expr, "CMP R0 R1 R0\nSET.E R0 R0 1\n", "CMPU R0 R1 R0\nSET.E R0 R0 1\n", "==", relational_expr_stub); arithmetic_recursion(additive_expr, "CMP R0 R1 R0\nSET.NE R0 R0 1\n", "CMPU R0 R1 R0\nSET.NE R0 R0 1\n", "!=", relational_expr_stub); } else if(X86 == Architecture) { arithmetic_recursion(additive_expr, "cmp\nsetl_al\nmovzx_eax,al\n", "cmp\nsetb_al\nmovzx_eax,al\n", "<", relational_expr_stub); arithmetic_recursion(additive_expr, "cmp\nsetle_al\nmovzx_eax,al\n", "cmp\nsetbe_al\nmovzx_eax,al\n", "<=", relational_expr_stub); arithmetic_recursion(additive_expr, "cmp\nsetge_al\nmovzx_eax,al\n", "cmp\nsetae_al\nmovzx_eax,al\n", ">=", relational_expr_stub); arithmetic_recursion(additive_expr, "cmp\nsetg_al\nmovzx_eax,al\n", "cmp\nseta_al\nmovzx_eax,al\n", ">", relational_expr_stub); general_recursion(additive_expr, "cmp\nsete_al\nmovzx_eax,al\n", "==", relational_expr_stub); general_recursion(additive_expr, "cmp\nsetne_al\nmovzx_eax,al\n", "!=", relational_expr_stub); } else if(AMD64 == Architecture) { arithmetic_recursion(additive_expr, "cmp_rbx,rax\nsetl_al\nmovzx_rax,al\n", "cmp_rbx,rax\nsetb_al\nmovzx_rax,al\n", "<", relational_expr_stub); arithmetic_recursion(additive_expr, "cmp_rbx,rax\nsetle_al\nmovzx_rax,al\n", "cmp_rbx,rax\nsetbe_al\nmovzx_rax,al\n", "<=", relational_expr_stub); arithmetic_recursion(additive_expr, "cmp_rbx,rax\nsetge_al\nmovzx_rax,al\n", "cmp_rbx,rax\nsetae_al\nmovzx_rax,al\n", ">=", relational_expr_stub); arithmetic_recursion(additive_expr, "cmp_rbx,rax\nsetg_al\nmovzx_rax,al\n", "cmp_rbx,rax\nseta_al\nmovzx_rax,al\n", ">", relational_expr_stub); general_recursion(additive_expr, "cmp_rbx,rax\nsete_al\nmovzx_rax,al\n", "==", relational_expr_stub); general_recursion(additive_expr, "cmp_rbx,rax\nsetne_al\nmovzx_rax,al\n", "!=", relational_expr_stub); } else if(ARMV7L == Architecture) { arithmetic_recursion(additive_expr, "'0' R0 CMP R1 AUX_ALWAYS\n!0 R0 LOADI8_ALWAYS\n!1 R0 LOADI8_L\n", "'0' R0 CMP R1 AUX_ALWAYS\n!0 R0 LOADI8_ALWAYS\n!1 R0 LOADI8_LO\n", "<", relational_expr_stub); arithmetic_recursion(additive_expr, "'0' R0 CMP R1 AUX_ALWAYS\n!0 R0 LOADI8_ALWAYS\n!1 R0 LOADI8_LE\n", "'0' R0 CMP R1 AUX_ALWAYS\n!0 R0 LOADI8_ALWAYS\n!1 R0 LOADI8_LS\n", "<=", relational_expr_stub); arithmetic_recursion(additive_expr, "'0' R0 CMP R1 AUX_ALWAYS\n!0 R0 LOADI8_ALWAYS\n!1 R0 LOADI8_GE\n", "'0' R0 CMP R1 AUX_ALWAYS\n!0 R0 LOADI8_ALWAYS\n!1 R0 LOADI8_HS\n", ">=", relational_expr_stub); arithmetic_recursion(additive_expr, "'0' R0 CMP R1 AUX_ALWAYS\n!0 R0 LOADI8_ALWAYS\n!1 R0 LOADI8_G\n", "'0' R0 CMP R1 AUX_ALWAYS\n!0 R0 LOADI8_ALWAYS\n!1 R0 LOADI8_HI\n", ">", relational_expr_stub); general_recursion(additive_expr, "'0' R0 CMP R1 AUX_ALWAYS\n!0 R0 LOADI8_ALWAYS\n!1 R0 LOADI8_EQUAL\n", "==", relational_expr_stub); general_recursion(additive_expr, "'0' R0 CMP R1 AUX_ALWAYS\n!0 R0 LOADI8_ALWAYS\n!1 R0 LOADI8_NE\n", "!=", relational_expr_stub); } else if(AARCH64 == Architecture) { arithmetic_recursion(additive_expr, "CMP_X1_X0\nSET_X0_TO_1\nSKIP_INST_LT\nSET_X0_TO_0\n", "CMP_X1_X0\nSET_X0_TO_1\nSKIP_INST_LO\nSET_X0_TO_0\n", "<", relational_expr_stub); arithmetic_recursion(additive_expr, "CMP_X1_X0\nSET_X0_TO_1\nSKIP_INST_LE\nSET_X0_TO_0\n", "CMP_X1_X0\nSET_X0_TO_1\nSKIP_INST_LS\nSET_X0_TO_0\n", "<=", relational_expr_stub); arithmetic_recursion(additive_expr, "CMP_X1_X0\nSET_X0_TO_1\nSKIP_INST_GE\nSET_X0_TO_0\n", "CMP_X1_X0\nSET_X0_TO_1\nSKIP_INST_HS\nSET_X0_TO_0\n", ">=", relational_expr_stub); arithmetic_recursion(additive_expr, "CMP_X1_X0\nSET_X0_TO_1\nSKIP_INST_GT\nSET_X0_TO_0\n", "CMP_X1_X0\nSET_X0_TO_1\nSKIP_INST_HI\nSET_X0_TO_0\n", ">", relational_expr_stub); general_recursion(additive_expr, "CMP_X1_X0\nSET_X0_TO_1\nSKIP_INST_EQ\nSET_X0_TO_0\n", "==", relational_expr_stub); general_recursion(additive_expr, "CMP_X1_X0\nSET_X0_TO_1\nSKIP_INST_NE\nSET_X0_TO_0\n", "!=", relational_expr_stub); } else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) { arithmetic_recursion(additive_expr, "RD_A0 RS1_A1 RS2_A0 SLT\n", "RD_A0 RS1_A1 RS2_A0 SLTU\n", "<", relational_expr_stub); arithmetic_recursion(additive_expr, "RD_A0 RS1_A0 RS2_A1 SLT\nRD_A0 RS1_A0 !1 XORI\n", "RD_A0 RS1_A0 RS2_A1 SLTU\nRD_A0 RS1_A0 !1 XORI\n", "<=", relational_expr_stub); arithmetic_recursion(additive_expr, "RD_A0 RS1_A1 RS2_A0 SLT\nRD_A0 RS1_A0 !1 XORI\n", "RD_A0 RS1_A1 RS2_A0 SLTU\nRD_A0 RS1_A0 !1 XORI\n", ">=", relational_expr_stub); arithmetic_recursion(additive_expr, "RD_A0 RS1_A0 RS2_A1 SLT\n", "RD_A0 RS1_A0 RS2_A1 SLTU\n", ">", relational_expr_stub); general_recursion(additive_expr, "RD_A0 RS1_A0 RS2_A1 SUB\nRD_A0 RS1_A0 !1 SLTIU\n", "==", relational_expr_stub); general_recursion(additive_expr, "RD_A0 RS1_A0 RS2_A1 SUB\nRD_A0 RS2_A0 SLTU\n", "!=", relational_expr_stub); } } void relational_expr() { additive_expr(); relational_expr_stub(); } /* * bitwise-expr: * relational-expr * bitwise-expr & bitwise-expr * bitwise-expr && bitwise-expr * bitwise-expr | bitwise-expr * bitwise-expr || bitwise-expr * bitwise-expr ^ bitwise-expr */ void bitwise_expr_stub() { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) { general_recursion(relational_expr, "AND R0 R0 R1\n", "&", bitwise_expr_stub); general_recursion(relational_expr, "AND R0 R0 R1\n", "&&", bitwise_expr_stub); general_recursion(relational_expr, "OR R0 R0 R1\n", "|", bitwise_expr_stub); general_recursion(relational_expr, "OR R0 R0 R1\n", "||", bitwise_expr_stub); general_recursion(relational_expr, "XOR R0 R0 R1\n", "^", bitwise_expr_stub); } else if(X86 == Architecture) { general_recursion(relational_expr, "and_eax,ebx\n", "&", bitwise_expr_stub); general_recursion(relational_expr, "and_eax,ebx\n", "&&", bitwise_expr_stub); general_recursion(relational_expr, "or_eax,ebx\n", "|", bitwise_expr_stub); general_recursion(relational_expr, "or_eax,ebx\n", "||", bitwise_expr_stub); general_recursion(relational_expr, "xor_eax,ebx\n", "^", bitwise_expr_stub); } else if(AMD64 == Architecture) { general_recursion(relational_expr, "and_rax,rbx\n", "&", bitwise_expr_stub); general_recursion(relational_expr, "and_rax,rbx\n", "&&", bitwise_expr_stub); general_recursion(relational_expr, "or_rax,rbx\n", "|", bitwise_expr_stub); general_recursion(relational_expr, "or_rax,rbx\n", "||", bitwise_expr_stub); general_recursion(relational_expr, "xor_rax,rbx\n", "^", bitwise_expr_stub); } else if(ARMV7L == Architecture) { general_recursion(relational_expr, "NO_SHIFT R0 R0 AND R1 ARITH2_ALWAYS\n", "&", bitwise_expr_stub); general_recursion(relational_expr, "NO_SHIFT R0 R0 AND R1 ARITH2_ALWAYS\n", "&&", bitwise_expr_stub); general_recursion(relational_expr, "NO_SHIFT R0 R0 OR R1 AUX_ALWAYS\n", "|", bitwise_expr_stub); general_recursion(relational_expr, "NO_SHIFT R0 R0 OR R1 AUX_ALWAYS\n", "||", bitwise_expr_stub); general_recursion(relational_expr, "'0' R0 R0 XOR R1 ARITH2_ALWAYS\n", "^", bitwise_expr_stub); } else if(AARCH64 == Architecture) { general_recursion(relational_expr, "AND_X0_X1_X0\n", "&", bitwise_expr_stub); general_recursion(relational_expr, "AND_X0_X1_X0\n", "&&", bitwise_expr_stub); general_recursion(relational_expr, "OR_X0_X1_X0\n", "|", bitwise_expr_stub); general_recursion(relational_expr, "OR_X0_X1_X0\n", "||", bitwise_expr_stub); general_recursion(relational_expr, "XOR_X0_X1_X0\n", "^", bitwise_expr_stub); } else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) { general_recursion(relational_expr, "RD_A0 RS1_A1 RS2_A0 AND\n", "&", bitwise_expr_stub); general_recursion(relational_expr, "RD_A0 RS1_A1 RS2_A0 AND\n", "&&", bitwise_expr_stub); general_recursion(relational_expr, "RD_A0 RS1_A1 RS2_A0 OR\n", "|", bitwise_expr_stub); general_recursion(relational_expr, "RD_A0 RS1_A1 RS2_A0 OR\n", "||", bitwise_expr_stub); general_recursion(relational_expr, "RD_A0 RS1_A1 RS2_A0 XOR\n", "^", bitwise_expr_stub); } } void bitwise_expr() { relational_expr(); bitwise_expr_stub(); } /* * expression: * bitwise-or-expr * bitwise-or-expr = expression */ void primary_expr() { require(NULL != global_token, "Received EOF where primary expression expected\n"); if(match("&", global_token->s)) { Address_of = TRUE; global_token = global_token->next; require(NULL != global_token, "Received EOF after & where primary expression expected\n"); } else { Address_of = FALSE; } if(match("sizeof", global_token->s)) unary_expr_sizeof(); else if('-' == global_token->s[0]) { if(X86 == Architecture) emit_out("mov_eax, %0\n"); else if(AMD64 == Architecture) emit_out("mov_rax, %0\n"); else if(ARMV7L == Architecture) emit_out("!0 R0 LOADI8_ALWAYS\n"); else if(AARCH64 == Architecture) emit_out("SET_X0_TO_0\n"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("RD_A0 MV\n"); common_recursion(primary_expr); if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("NEG R0 R0\n"); else if(X86 == Architecture) emit_out("sub_ebx,eax\nmov_eax,ebx\n"); else if(AMD64 == Architecture) emit_out("sub_rbx,rax\nmov_rax,rbx\n"); else if(ARMV7L == Architecture) emit_out("'0' R0 R0 SUB R1 ARITH2_ALWAYS\n"); else if(AARCH64 == Architecture) emit_out("SUB_X0_X1_X0\n"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("RD_A0 RS1_A1 RS2_A0 SUB\n"); } else if('!' == global_token->s[0]) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("LOADI R0 1\n"); else if(X86 == Architecture) emit_out("mov_eax, %1\n"); else if(AMD64 == Architecture) emit_out("mov_rax, %1\n"); else if(ARMV7L == Architecture) emit_out("!1 R0 LOADI8_ALWAYS\n"); else if(AARCH64 == Architecture) emit_out("SET_X0_TO_1\n"); common_recursion(postfix_expr); if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("CMPU R0 R1 R0\nSET.G R0 R0 1\n"); else if(X86 == Architecture) emit_out("cmp\nseta_al\nmovzx_eax,al\n"); else if(AMD64 == Architecture) emit_out("cmp_rbx,rax\nseta_al\nmovzx_rax,al\n"); else if(ARMV7L == Architecture) emit_out("'0' R0 CMP R1 AUX_ALWAYS\n!0 R0 LOADI8_ALWAYS\n!1 R0 LOADI8_HI\n"); else if(AARCH64 == Architecture) emit_out("CMP_X1_X0\nSET_X0_TO_1\nSKIP_INST_HI\nSET_X0_TO_0\n"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("RD_A0 RS1_A0 !1 SLTIU\n"); } else if('~' == global_token->s[0]) { common_recursion(postfix_expr); if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("NOT R0 R0\n"); else if(X86 == Architecture) emit_out("not_eax\n"); else if(AMD64 == Architecture) emit_out("not_rax\n"); else if(ARMV7L == Architecture) emit_out("'0' R0 R0 MVN_ALWAYS\n"); else if(AARCH64 == Architecture) emit_out("MVN_X0\n"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("RD_A0 RS1_A0 NOT\n"); } else if(global_token->s[0] == '(') { global_token = global_token->next; expression(); require_match("Error in Primary expression\nDidn't get )\n", ")"); } else if(global_token->s[0] == '\'') primary_expr_char(); else if(global_token->s[0] == '"') primary_expr_string(); else if(in_set(global_token->s[0], "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_")) primary_expr_variable(); else if(global_token->s[0] == '*') primary_expr_variable(); else if(in_set(global_token->s[0], "0123456789")) primary_expr_number(); else primary_expr_failure(); } char* compound_operation(char* operator, int is_signed) { char* operation = ""; if(match("+=", operator)) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) { if(is_signed) operation = "ADD R0 R1 R0\n"; else operation = "ADDU R0 R1 R0\n"; } else if(X86 == Architecture) operation = "add_eax,ebx\n"; else if(AMD64 == Architecture) operation = "add_rax,rbx\n"; else if(ARMV7L == Architecture) operation = "'0' R0 R0 ADD R1 ARITH2_ALWAYS\n"; else if(AARCH64 == Architecture) operation = "ADD_X0_X1_X0\n"; else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) operation = "RD_A0 RS1_A1 RS2_A0 ADD\n"; } else if(match("-=", operator)) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) { if(is_signed) operation = "SUB R0 R1 R0\n"; else operation = "SUBU R0 R1 R0\n"; } else if(X86 == Architecture) operation = "sub_ebx,eax\nmov_eax,ebx\n"; else if(AMD64 == Architecture) operation = "sub_rbx,rax\nmov_rax,rbx\n"; else if(ARMV7L == Architecture) operation = "'0' R0 R0 SUB R1 ARITH2_ALWAYS\n"; else if(AARCH64 == Architecture) operation = "SUB_X0_X1_X0\n"; else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) operation = "RD_A0 RS1_A1 RS2_A0 SUB\n"; } else if(match("*=", operator)) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) { if(is_signed) operation = "MUL R0 R1 R0\n"; else operation = "MULU R0 R1 R0\n"; } else if(X86 == Architecture) { if(is_signed) operation = "imul_ebx\n"; else operation = "mul_ebx\n"; } else if(AMD64 == Architecture) { if(is_signed) operation = "imul_rbx\n"; else operation = "mul_rbx\n"; } else if(ARMV7L == Architecture) operation = "'9' R0 '0' R1 MULS R0 ARITH2_ALWAYS\n"; else if(AARCH64 == Architecture) operation = "MUL_X0_X1_X0\n"; else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) operation = "RD_A0 RS1_A1 RS2_A0 MUL\n"; } else if(match("/=", operator)) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) { if(is_signed) operation = "DIV R0 R1 R0\n"; else operation = "DIVU R0 R1 R0\n"; } else if(X86 == Architecture) { if (is_signed) operation = "xchg_ebx,eax\ncdq\nidiv_ebx\n"; else operation = "xchg_ebx,eax\nmov_edx, %0\ndiv_ebx\n"; } else if(AMD64 == Architecture) { if(is_signed) operation = "xchg_rbx,rax\ncqo\nidiv_rbx\n"; else operation = "xchg_rbx,rax\nmov_rdx, %0\ndiv_rbx\n"; } else if(ARMV7L == Architecture) { if(is_signed) operation = "{LR} PUSH_ALWAYS\n^~divides CALL_ALWAYS\n{LR} POP_ALWAYS\n"; else operation = "{LR} PUSH_ALWAYS\n^~divide CALL_ALWAYS\n{LR} POP_ALWAYS\n"; } else if(AARCH64 == Architecture) { if(is_signed) operation = "SDIV_X0_X1_X0\n"; else operation = "UDIV_X0_X1_X0\n"; } else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) { if(is_signed) operation = "RD_A0 RS1_A1 RS2_A0 DIV\n"; else operation = "RD_A0 RS1_A1 RS2_A0 DIVU\n"; } } else if(match("%=", operator)) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) { if(is_signed) operation = "MOD R0 R1 R0\n"; else operation = "MODU R0 R1 R0\n"; } else if(X86 == Architecture) { if(is_signed) operation = "xchg_ebx,eax\ncdq\nidiv_ebx\nmov_eax,edx\n"; else operation = "xchg_ebx,eax\nmov_edx, %0\ndiv_ebx\nmov_eax,edx\n"; } else if(AMD64 == Architecture) { if(is_signed) operation = "xchg_rbx,rax\ncqo\nidiv_rbx\nmov_rax,rdx\n"; else operation = "xchg_rbx,rax\nmov_rdx, %0\ndiv_rbx\nmov_rax,rdx\n"; } else if(ARMV7L == Architecture) { if(is_signed) operation = "{LR} PUSH_ALWAYS\n^~moduluss CALL_ALWAYS\n{LR} POP_ALWAYS\n"; else operation = "{LR} PUSH_ALWAYS\n^~modulus CALL_ALWAYS\n{LR} POP_ALWAYS\n"; } else if(AARCH64 == Architecture) { if(is_signed) operation = "SDIV_X2_X1_X0\nMSUB_X0_X0_X2_X1\n"; else operation = "UDIV_X2_X1_X0\nMSUB_X0_X0_X2_X1\n"; } else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) { if(is_signed) operation = "RD_A0 RS1_A1 RS2_A0 REM\n"; else operation = "RD_A0 RS1_A1 RS2_A0 REMU\n"; } } else if(match("<<=", operator)) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) { if(is_signed) operation = "SAL R0 R1 R0\n"; else operation = "SL0 R0 R1 R0\n"; } else if(X86 == Architecture) { if(is_signed) operation = "mov_ecx,eax\nmov_eax,ebx\nsal_eax,cl\n"; else operation = "mov_ecx,eax\nmov_eax,ebx\nshl_eax,cl\n"; } else if(AMD64 == Architecture) { if(is_signed) operation = "mov_rcx,rax\nmov_rax,rbx\nsal_rax,cl\n"; else operation = "mov_rcx,rax\nmov_rax,rbx\nshl_rax,cl\n"; } else if(ARMV7L == Architecture) operation = "LEFT R1 R0 R0 SHIFT AUX_ALWAYS\n"; else if(AARCH64 == Architecture) operation = "LSHIFT_X0_X1_X0\n"; else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) operation = "RD_A0 RS1_A1 RS2_A0 SLL\n"; } else if(match(">>=", operator)) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) { if(is_signed) operation = "SAR R0 R1 R0\n"; else operation = "SR0 R0 R1 R0\n"; } else if(X86 == Architecture) { if(is_signed) operation = "mov_ecx,eax\nmov_eax,ebx\nsar_eax,cl\n"; else operation = "mov_ecx,eax\nmov_eax,ebx\nshr_eax,cl\n"; } else if(AMD64 == Architecture) { if(is_signed) operation = "mov_rcx,rax\nmov_rax,rbx\nsar_rax,cl\n"; else operation = "mov_rcx,rax\nmov_rax,rbx\nshr_rax,cl\n"; } else if(ARMV7L == Architecture) { if(is_signed) operation = "ARITH_RIGHT R1 R0 R0 SHIFT AUX_ALWAYS\n"; else operation = "RIGHT R1 R0 R0 SHIFT AUX_ALWAYS\n"; } else if(AARCH64 == Architecture) { if(is_signed) operation = "ARITH_RSHIFT_X0_X1_X0\n"; else operation = "LOGICAL_RSHIFT_X0_X1_X0\n"; } else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) { if(is_signed) operation = "RD_A0 RS1_A1 RS2_A0 SRA\n"; else operation = "RD_A0 RS1_A1 RS2_A0 SRL\n"; } } else if(match("&=", operator)) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) operation = "AND R0 R0 R1\n"; else if(X86 == Architecture) operation = "and_eax,ebx\n"; else if(AMD64 == Architecture) operation = "and_rax,rbx\n"; else if(ARMV7L == Architecture) operation = "NO_SHIFT R0 R0 AND R1 ARITH2_ALWAYS\n"; else if(AARCH64 == Architecture) operation = "AND_X0_X1_X0\n"; else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) operation = "RD_A0 RS1_A1 RS2_A0 AND\n"; } else if(match("^=", operator)) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) operation = "XOR R0 R0 R1\n"; else if(X86 == Architecture) operation = "xor_eax,ebx\n"; else if(AMD64 == Architecture) operation = "xor_rax,rbx\n"; else if(ARMV7L == Architecture) operation = "'0' R0 R0 XOR R1 ARITH2_ALWAYS\n"; else if(AARCH64 == Architecture) operation = "XOR_X0_X1_X0\n"; else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) operation = "RD_A0 RS1_A1 RS2_A0 XOR\n"; } else if(match("|=", operator)) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) operation = "OR R0 R0 R1\n"; else if(X86 == Architecture) operation = "or_eax,ebx\n"; else if(AMD64 == Architecture) operation = "or_rax,rbx\n"; else if(ARMV7L == Architecture) operation = "NO_SHIFT R0 R0 OR R1 AUX_ALWAYS\n"; else if(AARCH64 == Architecture) operation = "OR_X0_X1_X0\n"; else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) operation = "RD_A0 RS1_A1 RS2_A0 OR\n"; } else { fputs("Found illegal compound assignment operator: ", stderr); fputs(operator, stderr); fputc('\n', stderr); exit(EXIT_FAILURE); } return operation; } void expression() { bitwise_expr(); if(match("=", global_token->s)) { char* store = ""; if(match("]", global_token->prev->s)) { store = store_value(current_target->type->size); } else { store = store_value(current_target->size); } common_recursion(expression); emit_out(store); current_target = integer; } else if(is_compound_assignment(global_token->s)) { maybe_bootstrap_error("compound operator"); char* push = ""; char* load = ""; char* operation = ""; char* pop = ""; char* store = ""; struct type* last_type = current_target; if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) push = "PUSHR R1 R15\n"; else if(X86 == Architecture) push = "push_ebx\n"; else if(AMD64 == Architecture) push = "push_rbx\n"; else if(ARMV7L == Architecture) push = "{R1} PUSH_ALWAYS\n"; else if(AARCH64 == Architecture) push = "PUSH_X1\n"; else if(RISCV32 == Architecture) push = "RS1_SP RS2_A1 @-4 SW\n"; else if(RISCV64 == Architecture) push = "RS1_SP RS2_A1 @-8 SD\n"; if(!match("]", global_token->prev->s) || !match("char*", current_target->name)) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) load = "LOAD R1 R1 0\n"; else if(X86 == Architecture) load = "mov_ebx,[ebx]\n"; else if(AMD64 == Architecture) load = "mov_rbx,[rbx]\n"; else if(ARMV7L == Architecture) load = "!0 R1 LOAD32 R1 MEMORY\n"; else if(AARCH64 == Architecture) load = "DEREF_X1\n"; else if(RISCV32 == Architecture) load = "RD_A1 RS1_A1 LW\n"; else if(RISCV64 == Architecture) load = "RD_A1 RS1_A1 LD\n"; } else { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) load = "LOAD8 R1 R1 0\n"; else if(X86 == Architecture) load = "movsx_ebx,BYTE_PTR_[ebx]\n"; else if(AMD64 == Architecture) load = "movsx_rbx,BYTE_PTR_[rbx]\n"; else if(ARMV7L == Architecture) load = "LOADU8 R1 LOAD R1 MEMORY\n"; else if(AARCH64 == Architecture) load = "DEREF_X1_BYTE\n"; else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) load = "RD_A1 RS1_A1 LBU\n"; } char *operator = global_token->s; if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) pop = "POPR R1 R15\n"; else if(X86 == Architecture) pop = "pop_ebx\n"; else if(AMD64 == Architecture) pop = "pop_rbx\n"; else if(ARMV7L == Architecture) pop = "{R1} POP_ALWAYS\n"; else if(AARCH64 == Architecture) pop = "POP_X1\n"; else if(RISCV32 == Architecture) pop = "RD_A1 RS1_SP !-4 LW\n"; else if(RISCV64 == Architecture) pop = "RD_A1 RS1_SP !-8 LD\n"; if(match("]", global_token->prev->s)) { store = store_value(current_target->type->size); } else { store = store_value(current_target->size); } common_recursion(expression); current_target = promote_type(current_target, last_type); emit_out(push); emit_out(load); operation = compound_operation(operator, current_target->is_signed); emit_out(operation); emit_out(pop); emit_out(store); current_target = integer; } } int iskeywordp(char* s) { if(match("auto", s)) return TRUE; if(match("break", s)) return TRUE; if(match("case", s)) return TRUE; if(match("char", s)) return TRUE; if(match("const", s)) return TRUE; if(match("continue", s)) return TRUE; if(match("default", s)) return TRUE; if(match("do", s)) return TRUE; if(match("double", s)) return TRUE; if(match("else", s)) return TRUE; if(match("enum", s)) return TRUE; if(match("extern", s)) return TRUE; if(match("float", s)) return TRUE; if(match("for", s)) return TRUE; if(match("goto", s)) return TRUE; if(match("if", s)) return TRUE; if(match("int", s)) return TRUE; if(match("long", s)) return TRUE; if(match("register", s)) return TRUE; if(match("return", s)) return TRUE; if(match("short", s)) return TRUE; if(match("signed", s)) return TRUE; if(match("sizeof", s)) return TRUE; if(match("static", s)) return TRUE; if(match("struct", s)) return TRUE; if(match("switch", s)) return TRUE; if(match("typedef", s)) return TRUE; if(match("union", s)) return TRUE; if(match("unsigned", s)) return TRUE; if(match("void", s)) return TRUE; if(match("volatile", s)) return TRUE; if(match("while", s)) return TRUE; return FALSE; } /* Similar to integer division a / b but rounds up */ unsigned ceil_div(unsigned a, unsigned b) { return (a + b - 1) / b; } /* Process local variable */ void collect_local() { if(NULL != break_target_func) { fputs("Local variable initialized inside of loop in file: ", stderr); line_error(); fputs("\nMove the variable outside of the loop to resolve\n", stderr); fputs("Otherwise the binary will segfault while running\n", stderr); exit(EXIT_FAILURE); } struct type* type_size = type_name(); require(NULL != global_token, "Received EOF while collecting locals\n"); require(!in_set(global_token->s[0], "[{(<=>)}]|&!^%;:'\""), "forbidden character in local variable name\n"); require(!iskeywordp(global_token->s), "You are not allowed to use a keyword as a local variable name\n"); require(NULL != type_size, "Must have non-null type\n"); struct token_list* a = sym_declare(global_token->s, type_size, function->locals); if(match("main", function->s) && (NULL == function->locals)) { if(KNIGHT_NATIVE == Architecture) a->depth = register_size; else if(KNIGHT_POSIX == Architecture) a->depth = 20; else if(X86 == Architecture) a->depth = -20; else if(AMD64 == Architecture) a->depth = -40; else if(ARMV7L == Architecture) a->depth = 16; else if(AARCH64 == Architecture) a->depth = 32; /* argc, argv, envp and the local (8 bytes each) */ else if(RISCV32 == Architecture) a->depth = -16; else if(RISCV64 == Architecture) a->depth = -32; } else if((NULL == function->arguments) && (NULL == function->locals)) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) a->depth = register_size; else if(X86 == Architecture) a->depth = -8; else if(AMD64 == Architecture) a->depth = -16; else if(ARMV7L == Architecture) a->depth = 8; else if(AARCH64 == Architecture) a->depth = register_size; else if(RISCV32 == Architecture) a->depth = -4; else if(RISCV64 == Architecture) a->depth = -8; } else if(NULL == function->locals) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) a->depth = function->arguments->depth + 8; else if(X86 == Architecture) a->depth = function->arguments->depth - 8; else if(AMD64 == Architecture) a->depth = function->arguments->depth - 16; else if(ARMV7L == Architecture) a->depth = function->arguments->depth + 8; else if(AARCH64 == Architecture) a->depth = function->arguments->depth + register_size; else if(RISCV32 == Architecture) a->depth = function->arguments->depth - 4; else if(RISCV64 == Architecture) a->depth = function->arguments->depth - 8; } else { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) a->depth = function->locals->depth + register_size; else if(X86 == Architecture) a->depth = function->locals->depth - register_size; else if(AMD64 == Architecture) a->depth = function->locals->depth - register_size; else if(ARMV7L == Architecture) a->depth = function->locals->depth + register_size; else if(AARCH64 == Architecture) a->depth = function->locals->depth + register_size; else if(RISCV32 == Architecture) a->depth = function->locals->depth - register_size; else if(RISCV64 == Architecture) a->depth = function->locals->depth - register_size; } /* Adjust the depth of local structs. When stack grows downwards, we want them to start at the bottom of allocated space. */ unsigned struct_depth_adjustment = (ceil_div(a->type->size, register_size) - 1) * register_size; if(KNIGHT_POSIX == Architecture) a->depth = a->depth + struct_depth_adjustment; else if(KNIGHT_NATIVE == Architecture) a->depth = a->depth + struct_depth_adjustment; else if(X86 == Architecture) a->depth = a->depth - struct_depth_adjustment; else if(AMD64 == Architecture) a->depth = a->depth - struct_depth_adjustment; else if(ARMV7L == Architecture) a->depth = a->depth + struct_depth_adjustment; else if(AARCH64 == Architecture) a->depth = a->depth + struct_depth_adjustment; else if(RISCV32 == Architecture) a->depth = a->depth - struct_depth_adjustment; else if(RISCV64 == Architecture) a->depth = a->depth - struct_depth_adjustment; function->locals = a; emit_out("# Defining local "); emit_out(global_token->s); emit_out("\n"); global_token = global_token->next; require(NULL != global_token, "incomplete local missing name\n"); if(match("=", global_token->s)) { global_token = global_token->next; require(NULL != global_token, "incomplete local assignment\n"); expression(); } require_match("ERROR in collect_local\nMissing ;\n", ";"); unsigned i = (a->type->size + register_size - 1) / register_size; while(i != 0) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("PUSHR R0 R15\t#"); else if(X86 == Architecture) emit_out("push_eax\t#"); else if(AMD64 == Architecture) emit_out("push_rax\t#"); else if(ARMV7L == Architecture) emit_out("{R0} PUSH_ALWAYS\t#"); else if(AARCH64 == Architecture) emit_out("PUSH_X0\t#"); else if(RISCV32 == Architecture) emit_out("RD_SP RS1_SP !-4 ADDI\nRS1_SP RS2_A0 SW\t#"); else if(RISCV64 == Architecture) emit_out("RD_SP RS1_SP !-8 ADDI\nRS1_SP RS2_A0 SD\t#"); emit_out(a->s); emit_out("\n"); i = i - 1; } } void statement(); /* Evaluate if statements */ void process_if() { char* number_string = int2str(current_count, 10, TRUE); current_count = current_count + 1; emit_out("# IF_"); uniqueID_out(function->s, number_string); global_token = global_token->next; require_match("ERROR in process_if\nMISSING (\n", "("); expression(); if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("JUMP.Z R0 @ELSE_"); else if(X86 == Architecture) emit_out("test_eax,eax\nje %ELSE_"); else if(AMD64 == Architecture) emit_out("test_rax,rax\nje %ELSE_"); else if(ARMV7L == Architecture) emit_out("!0 CMPI8 R0 IMM_ALWAYS\n^~ELSE_"); else if(AARCH64 == Architecture) emit_out("CBNZ_X0_PAST_BR\nLOAD_W16_AHEAD\nSKIP_32_DATA\n&ELSE_"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("RS1_A0 @8 BNEZ\n$ELSE_"); uniqueID_out(function->s, number_string); if(ARMV7L == Architecture) emit_out(" JUMP_EQUAL\n"); else if(AARCH64 == Architecture) emit_out("\nBR_X16\n"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("JAL\n"); require_match("ERROR in process_if\nMISSING )\n", ")"); statement(); require(NULL != global_token, "Reached EOF inside of function\n"); if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("JUMP @_END_IF_"); else if(X86 == Architecture) emit_out("jmp %_END_IF_"); else if(AMD64 == Architecture) emit_out("jmp %_END_IF_"); else if(ARMV7L == Architecture) emit_out("^~_END_IF_"); else if(AARCH64 == Architecture) emit_out("LOAD_W16_AHEAD\nSKIP_32_DATA\n&_END_IF_"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("$_END_IF_"); uniqueID_out(function->s, number_string); if(ARMV7L == Architecture) emit_out(" JUMP_ALWAYS\n"); else if(AARCH64 == Architecture) emit_out("\nBR_X16\n"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("JAL\n"); emit_out(":ELSE_"); uniqueID_out(function->s, number_string); if(match("else", global_token->s)) { global_token = global_token->next; require(NULL != global_token, "Received EOF where an else statement expected\n"); statement(); require(NULL != global_token, "Reached EOF inside of function\n"); } emit_out(":_END_IF_"); uniqueID_out(function->s, number_string); } void process_for() { struct token_list* nested_locals = break_frame; char* nested_break_head = break_target_head; char* nested_break_func = break_target_func; char* nested_break_num = break_target_num; char* nested_continue_head = continue_target_head; char* number_string = int2str(current_count, 10, TRUE); current_count = current_count + 1; break_target_head = "FOR_END_"; continue_target_head = "FOR_ITER_"; break_target_num = number_string; break_frame = function->locals; break_target_func = function->s; emit_out("# FOR_initialization_"); uniqueID_out(function->s, number_string); global_token = global_token->next; require_match("ERROR in process_for\nMISSING (\n", "("); if(!match(";",global_token->s)) { expression(); } emit_out(":FOR_"); uniqueID_out(function->s, number_string); require_match("ERROR in process_for\nMISSING ;1\n", ";"); expression(); if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("JUMP.Z R0 @FOR_END_"); else if(X86 == Architecture) emit_out("test_eax,eax\nje %FOR_END_"); else if(AMD64 == Architecture) emit_out("test_rax,rax\nje %FOR_END_"); else if(ARMV7L == Architecture) emit_out("!0 CMPI8 R0 IMM_ALWAYS\n^~FOR_END_"); else if(AARCH64 == Architecture) emit_out("CBNZ_X0_PAST_BR\nLOAD_W16_AHEAD\nSKIP_32_DATA\n&FOR_END_"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("RS1_A0 @8 BNEZ\n$FOR_END_"); uniqueID_out(function->s, number_string); if(ARMV7L == Architecture) emit_out(" JUMP_EQUAL\n"); else if(AARCH64 == Architecture) emit_out("\nBR_X16\n"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("JAL\n"); if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("JUMP @FOR_THEN_"); else if(X86 == Architecture) emit_out("jmp %FOR_THEN_"); else if(AMD64 == Architecture) emit_out("jmp %FOR_THEN_"); else if(ARMV7L == Architecture) emit_out("^~FOR_THEN_"); else if(AARCH64 == Architecture) emit_out("LOAD_W16_AHEAD\nSKIP_32_DATA\n&FOR_THEN_"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("$FOR_THEN_"); uniqueID_out(function->s, number_string); if(ARMV7L == Architecture) emit_out(" JUMP_ALWAYS\n"); else if(AARCH64 == Architecture) emit_out("\nBR_X16\n"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("JAL\n"); emit_out(":FOR_ITER_"); uniqueID_out(function->s, number_string); require_match("ERROR in process_for\nMISSING ;2\n", ";"); expression(); if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("JUMP @FOR_"); else if(X86 == Architecture) emit_out("jmp %FOR_"); else if(AMD64 == Architecture) emit_out("jmp %FOR_"); else if(ARMV7L == Architecture) emit_out("^~FOR_"); else if(AARCH64 == Architecture) emit_out("LOAD_W16_AHEAD\nSKIP_32_DATA\n&FOR_"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("$FOR_"); uniqueID_out(function->s, number_string); if(ARMV7L == Architecture) emit_out(" JUMP_ALWAYS\n"); else if(AARCH64 == Architecture) emit_out("\nBR_X16\n"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("JAL\n"); emit_out(":FOR_THEN_"); uniqueID_out(function->s, number_string); require_match("ERROR in process_for\nMISSING )\n", ")"); statement(); require(NULL != global_token, "Reached EOF inside of function\n"); if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("JUMP @FOR_ITER_"); else if(X86 == Architecture) emit_out("jmp %FOR_ITER_"); else if(AMD64 == Architecture) emit_out("jmp %FOR_ITER_"); else if(ARMV7L == Architecture) emit_out("^~FOR_ITER_"); else if(AARCH64 == Architecture) emit_out("LOAD_W16_AHEAD\nSKIP_32_DATA\n&FOR_ITER_"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("$FOR_ITER_"); uniqueID_out(function->s, number_string); if(ARMV7L == Architecture) emit_out(" JUMP_ALWAYS\n"); else if(AARCH64 == Architecture) emit_out("\nBR_X16\n"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("JAL\n"); emit_out(":FOR_END_"); uniqueID_out(function->s, number_string); break_target_head = nested_break_head; break_target_func = nested_break_func; break_target_num = nested_break_num; continue_target_head = nested_continue_head; break_frame = nested_locals; } /* Process Assembly statements */ void process_asm() { global_token = global_token->next; require_match("ERROR in process_asm\nMISSING (\n", "("); while('"' == global_token->s[0]) { emit_out((global_token->s + 1)); emit_out("\n"); global_token = global_token->next; require(NULL != global_token, "Received EOF inside asm statement\n"); } require_match("ERROR in process_asm\nMISSING )\n", ")"); require_match("ERROR in process_asm\nMISSING ;\n", ";"); } /* Process do while loops */ void process_do() { struct token_list* nested_locals = break_frame; char* nested_break_head = break_target_head; char* nested_break_func = break_target_func; char* nested_break_num = break_target_num; char* nested_continue_head = continue_target_head; char* number_string = int2str(current_count, 10, TRUE); current_count = current_count + 1; break_target_head = "DO_END_"; continue_target_head = "DO_TEST_"; break_target_num = number_string; break_frame = function->locals; break_target_func = function->s; emit_out(":DO_"); uniqueID_out(function->s, number_string); global_token = global_token->next; require(NULL != global_token, "Received EOF where do statement is expected\n"); statement(); require(NULL != global_token, "Reached EOF inside of function\n"); emit_out(":DO_TEST_"); uniqueID_out(function->s, number_string); require_match("ERROR in process_do\nMISSING while\n", "while"); require_match("ERROR in process_do\nMISSING (\n", "("); expression(); require_match("ERROR in process_do\nMISSING )\n", ")"); require_match("ERROR in process_do\nMISSING ;\n", ";"); if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("JUMP.NZ R0 @DO_"); else if(X86 == Architecture) emit_out("test_eax,eax\njne %DO_"); else if(AMD64 == Architecture) emit_out("test_rax,rax\njne %DO_"); else if(ARMV7L == Architecture) emit_out("!0 CMPI8 R0 IMM_ALWAYS\n^~DO_"); else if(AARCH64 == Architecture) emit_out("CBZ_X0_PAST_BR\nLOAD_W16_AHEAD\nSKIP_32_DATA\n&DO_"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("RS1_A0 @DO_END_"); uniqueID_out(function->s, number_string); if(ARMV7L == Architecture) emit_out(" JUMP_NE\n"); else if(AARCH64 == Architecture) emit_out("\nBR_X16\n"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) { emit_out("BEQZ\n$DO_"); uniqueID_out(function->s, number_string); emit_out("JAL\n"); } emit_out(":DO_END_"); uniqueID_out(function->s, number_string); break_frame = nested_locals; break_target_head = nested_break_head; break_target_func = nested_break_func; break_target_num = nested_break_num; continue_target_head = nested_continue_head; } /* Process while loops */ void process_while() { struct token_list* nested_locals = break_frame; char* nested_break_head = break_target_head; char* nested_break_func = break_target_func; char* nested_break_num = break_target_num; char* nested_continue_head = continue_target_head; char* number_string = int2str(current_count, 10, TRUE); current_count = current_count + 1; break_target_head = "END_WHILE_"; continue_target_head = "WHILE_"; break_target_num = number_string; break_frame = function->locals; break_target_func = function->s; emit_out(":WHILE_"); uniqueID_out(function->s, number_string); global_token = global_token->next; require_match("ERROR in process_while\nMISSING (\n", "("); expression(); if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("JUMP.Z R0 @END_WHILE_"); else if(X86 == Architecture) emit_out("test_eax,eax\nje %END_WHILE_"); else if(AMD64 == Architecture) emit_out("test_rax,rax\nje %END_WHILE_"); else if(ARMV7L == Architecture) emit_out("!0 CMPI8 R0 IMM_ALWAYS\n^~END_WHILE_"); else if(AARCH64 == Architecture) emit_out("CBNZ_X0_PAST_BR\nLOAD_W16_AHEAD\nSKIP_32_DATA\n&END_WHILE_"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("RS1_A0 @8 BNEZ\n$END_WHILE_"); uniqueID_out(function->s, number_string); if(ARMV7L == Architecture) emit_out(" JUMP_EQUAL\t"); else if(AARCH64 == Architecture) emit_out("\nBR_X16\n"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("JAL\n"); emit_out("# THEN_while_"); uniqueID_out(function->s, number_string); require_match("ERROR in process_while\nMISSING )\n", ")"); statement(); require(NULL != global_token, "Reached EOF inside of function\n"); if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("JUMP @WHILE_"); else if(X86 == Architecture) emit_out("jmp %WHILE_"); else if(AMD64 == Architecture) emit_out("jmp %WHILE_"); else if(ARMV7L == Architecture) emit_out("^~WHILE_"); else if(AARCH64 == Architecture) emit_out("LOAD_W16_AHEAD\nSKIP_32_DATA\n&WHILE_"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("$WHILE_"); uniqueID_out(function->s, number_string); if(ARMV7L == Architecture) emit_out(" JUMP_ALWAYS\n"); else if(AARCH64 == Architecture) emit_out("\nBR_X16\n"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("JAL\n"); emit_out(":END_WHILE_"); uniqueID_out(function->s, number_string); break_target_head = nested_break_head; break_target_func = nested_break_func; break_target_num = nested_break_num; continue_target_head = nested_continue_head; break_frame = nested_locals; } /* Ensure that functions return */ void return_result() { global_token = global_token->next; require(NULL != global_token, "Incomplete return statement received\n"); if(global_token->s[0] != ';') expression(); require_match("ERROR in return_result\nMISSING ;\n", ";"); struct token_list* i; unsigned size_local_var; for(i = function->locals; NULL != i; i = i->next) { size_local_var = ceil_div(i->type->size, register_size); while(size_local_var != 0) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("POPR R1 R15\t# _return_result_locals\n"); else if(X86 == Architecture) emit_out("pop_ebx\t# _return_result_locals\n"); else if(AMD64 == Architecture) emit_out("pop_rbx\t# _return_result_locals\n"); else if(ARMV7L == Architecture) emit_out("{R1} POP_ALWAYS\t# _return_result_locals\n"); else if(AARCH64 == Architecture) emit_out("POP_X1\t# _return_result_locals\n"); else if(RISCV32 == Architecture) emit_out("RD_A1 RS1_SP LW # _return_result_locals\nRD_SP RS1_SP !4 ADDI\n"); else if(RISCV64 == Architecture) emit_out("RD_A1 RS1_SP LD # _return_result_locals\nRD_SP RS1_SP !8 ADDI\n"); size_local_var = size_local_var - 1; } } if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("RET R15\n"); else if(X86 == Architecture) emit_out("ret\n"); else if(AMD64 == Architecture) emit_out("ret\n"); else if(ARMV7L == Architecture) emit_out("'1' LR RETURN\n"); else if(AARCH64 == Architecture) emit_out("RETURN\n"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("RETURN\n"); } void process_break() { if(NULL == break_target_head) { line_error(); fputs("Not inside of a loop or case statement\n", stderr); exit(EXIT_FAILURE); } struct token_list* i = function->locals; while(i != break_frame) { if(NULL == i) break; if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("POPR R1 R15\t# break_cleanup_locals\n"); else if(X86 == Architecture) emit_out("pop_ebx\t# break_cleanup_locals\n"); else if(AMD64 == Architecture) emit_out("pop_rbx\t# break_cleanup_locals\n"); else if(ARMV7L == Architecture) emit_out("{R1} POP_ALWAYS\t# break_cleanup_locals\n"); else if(AARCH64 == Architecture) emit_out("POP_X1\t# break_cleanup_locals\n"); else if(RISCV32 == Architecture) emit_out("RD_A1 RS1_SP LW\t# break_cleanup_locals\nRD_SP RS1_SP !4 ADDI\n"); else if(RISCV64 == Architecture) emit_out("RD_A1 RS1_SP LD\t# break_cleanup_locals\nRD_SP RS1_SP !8 ADDI\n"); i = i->next; } global_token = global_token->next; if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("JUMP @"); else if(X86 == Architecture) emit_out("jmp %"); else if(AMD64 == Architecture) emit_out("jmp %"); else if(ARMV7L == Architecture) emit_out("^~"); else if(AARCH64 == Architecture) emit_out("LOAD_W16_AHEAD\nSKIP_32_DATA\n&"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("$"); emit_out(break_target_head); emit_out(break_target_func); emit_out("_"); emit_out(break_target_num); if(ARMV7L == Architecture) emit_out(" JUMP_ALWAYS"); else if(AARCH64 == Architecture) emit_out("\nBR_X16"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out(" JAL"); emit_out("\n"); require_match("ERROR in break statement\nMissing ;\n", ";"); } void process_continue() { if(NULL == continue_target_head) { line_error(); fputs("Not inside of a loop\n", stderr); exit(EXIT_FAILURE); } global_token = global_token->next; if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("JUMP @"); else if(X86 == Architecture) emit_out("jmp %"); else if(AMD64 == Architecture) emit_out("jmp %"); else if(ARMV7L == Architecture) emit_out("^~"); else if(AARCH64 == Architecture) emit_out("LOAD_W16_AHEAD\nSKIP_32_DATA\n&"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("$"); emit_out(continue_target_head); emit_out(break_target_func); emit_out("_"); emit_out(break_target_num); if(ARMV7L == Architecture) emit_out(" JUMP_ALWAYS"); else if(AARCH64 == Architecture) emit_out("\nBR_X16"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out(" JAL"); emit_out("\n"); require_match("ERROR in continue statement\nMissing ;\n", ";"); } void recursive_statement() { global_token = global_token->next; require(NULL != global_token, "Received EOF in recursive statement\n"); struct token_list* frame = function->locals; while(!match("}", global_token->s)) { statement(); require(NULL != global_token, "Received EOF in recursive statement prior to }\n"); } global_token = global_token->next; /* Clean up any locals added */ if(((X86 == Architecture) && !match("ret\n", output_list->s)) || ((AMD64 == Architecture) && !match("ret\n", output_list->s)) || (((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) && !match("RET R15\n", output_list->s)) || ((ARMV7L == Architecture) && !match("'1' LR RETURN\n", output_list->s)) || ((AARCH64 == Architecture) && !match("RETURN\n", output_list->s)) || (((RISCV32 == Architecture) || (RISCV64 == Architecture)) && !match("RETURN\n", output_list->s))) { struct token_list* i; for(i = function->locals; frame != i; i = i->next) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("POPR R1 R15\t# _recursive_statement_locals\n"); else if(X86 == Architecture) emit_out( "pop_ebx\t# _recursive_statement_locals\n"); else if(AMD64 == Architecture) emit_out("pop_rbx\t# _recursive_statement_locals\n"); else if(ARMV7L == Architecture) emit_out("{R1} POP_ALWAYS\t# _recursive_statement_locals\n"); else if(AARCH64 == Architecture) emit_out("POP_X1\t# _recursive_statement_locals\n"); else if(RISCV32 == Architecture) emit_out("RD_A1 RS1_SP LW\t# _recursive_statement_locals\nRD_SP RS1_SP !4 ADDI\n"); else if(RISCV64 == Architecture) emit_out("RD_A1 RS1_SP LD\t# _recursive_statement_locals\nRD_SP RS1_SP !8 ADDI\n"); } } function->locals = frame; } /* * statement: * { statement-list-opt } * type-name identifier ; * type-name identifier = expression; * if ( expression ) statement * if ( expression ) statement else statement * do statement while ( expression ) ; * while ( expression ) statement * for ( expression ; expression ; expression ) statement * asm ( "assembly" ... "assembly" ) ; * goto label ; * label: * return ; * break ; * expr ; */ struct type* lookup_type(char* s, struct type* start); void statement() { require(NULL != global_token, "expected a C statement but received EOF\n"); /* Always an integer until told otherwise */ current_target = integer; if(global_token->s[0] == '{') { recursive_statement(); } else if(':' == global_token->s[0]) { emit_out(global_token->s); emit_out("\t#C goto label\n"); global_token = global_token->next; } else if((NULL != lookup_type(global_token->s, prim_types)) || match("struct", global_token->s)) { collect_local(); } else if(match("if", global_token->s)) { process_if(); } else if(match("do", global_token->s)) { process_do(); } else if(match("while", global_token->s)) { process_while(); } else if(match("for", global_token->s)) { process_for(); } else if(match("asm", global_token->s)) { process_asm(); } else if(match("goto", global_token->s)) { global_token = global_token->next; require(NULL != global_token, "naked goto is not supported\n"); if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) emit_out("JUMP @"); else if(X86 == Architecture) emit_out("jmp %"); else if(AMD64 == Architecture) emit_out("jmp %"); else if(ARMV7L == Architecture) emit_out("^~"); else if(AARCH64 == Architecture) emit_out("LOAD_W16_AHEAD\nSKIP_32_DATA\n&"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out("$"); emit_out(global_token->s); if(ARMV7L == Architecture) emit_out(" JUMP_ALWAYS"); else if(AARCH64 == Architecture) emit_out("\nBR_X16"); else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) emit_out(" JAL"); emit_out("\n"); global_token = global_token->next; require_match("ERROR in statement\nMissing ;\n", ";"); } else if(match("return", global_token->s)) { return_result(); } else if(match("break", global_token->s)) { process_break(); } else if(match("continue", global_token->s)) { process_continue(); } else { expression(); require_match("ERROR in statement\nMISSING ;\n", ";"); } } /* Collect function arguments */ void collect_arguments() { global_token = global_token->next; require(NULL != global_token, "Received EOF when attempting to collect arguments\n"); struct type* type_size; struct token_list* a; while(!match(")", global_token->s)) { type_size = type_name(); require(NULL != global_token, "Received EOF when attempting to collect arguments\n"); require(NULL != type_size, "Must have non-null type\n"); if(global_token->s[0] == ')') { /* foo(int,char,void) doesn't need anything done */ continue; } else if(global_token->s[0] != ',') { /* deal with foo(int a, char b) */ require(!in_set(global_token->s[0], "[{(<=>)}]|&!^%;:'\""), "forbidden character in argument variable name\n"); require(!iskeywordp(global_token->s), "You are not allowed to use a keyword as a argument variable name\n"); a = sym_declare(global_token->s, type_size, function->arguments); if(NULL == function->arguments) { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) a->depth = 0; else if(X86 == Architecture) a->depth = -4; else if(AMD64 == Architecture) a->depth = -8; else if(ARMV7L == Architecture) a->depth = 4; else if(AARCH64 == Architecture) a->depth = register_size; else if(RISCV32 == Architecture) a->depth = -4; else if(RISCV64 == Architecture) a->depth = -8; } else { if((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) a->depth = function->arguments->depth + register_size; else if(X86 == Architecture) a->depth = function->arguments->depth - register_size; else if(AMD64 == Architecture) a->depth = function->arguments->depth - register_size; else if(ARMV7L == Architecture) a->depth = function->arguments->depth + register_size; else if(AARCH64 == Architecture) a->depth = function->arguments->depth + register_size; else if(RISCV32 == Architecture) a->depth = function->arguments->depth - register_size; else if(RISCV64 == Architecture) a->depth = function->arguments->depth - register_size; } global_token = global_token->next; require(NULL != global_token, "Incomplete argument list\n"); function->arguments = a; } /* ignore trailing comma (needed for foo(bar(), 1); expressions*/ if(global_token->s[0] == ',') { global_token = global_token->next; require(NULL != global_token, "naked comma in collect arguments\n"); } require(NULL != global_token, "Argument list never completed\n"); } global_token = global_token->next; } void declare_function() { current_count = 0; function = sym_declare(global_token->prev->s, NULL, global_function_list); /* allow previously defined functions to be looked up */ global_function_list = function; if((KNIGHT_NATIVE == Architecture) && match("main", function->s)) { require_match("Impossible error ( vanished\n", "("); require_match("Reality ERROR (USING KNIGHT-NATIVE)\nHardware does not support arguments\nthus neither can main on this architecture\ntry tape_01 and tape_02 instead\n", ")"); } else collect_arguments(); require(NULL != global_token, "Function definitions either need to be prototypes or full\n"); /* If just a prototype don't waste time */ if(global_token->s[0] == ';') global_token = global_token->next; else { emit_out("# Defining function "); emit_out(function->s); emit_out("\n"); emit_out(":FUNCTION_"); emit_out(function->s); emit_out("\n"); statement(); /* Prevent duplicate RETURNS */ if(((KNIGHT_POSIX == Architecture) || (KNIGHT_NATIVE == Architecture)) && !match("RET R15\n", output_list->s)) emit_out("RET R15\n"); else if((X86 == Architecture) && !match("ret\n", output_list->s)) emit_out("ret\n"); else if((AMD64 == Architecture) && !match("ret\n", output_list->s)) emit_out("ret\n"); else if((ARMV7L == Architecture) && !match("'1' LR RETURN\n", output_list->s)) emit_out("'1' LR RETURN\n"); else if((AARCH64 == Architecture) && !match("RETURN\n", output_list->s)) emit_out("RETURN\n"); else if((RISCV32 == Architecture) && !match("RETURN\n", output_list->s)) emit_out("RETURN\n"); else if((RISCV64 == Architecture) && !match("RETURN\n", output_list->s)) emit_out("RETURN\n"); } } void global_constant() { global_token = global_token->next; require(NULL != global_token, "CONSTANT lacks a name\n"); global_constant_list = sym_declare(global_token->s, NULL, global_constant_list); require(NULL != global_token->next, "CONSTANT lacks a value\n"); if(match("sizeof", global_token->next->s)) { global_token = global_token->next->next; require_match("ERROR in CONSTANT with sizeof\nMissing (\n", "("); struct type* a = type_name(); require_match("ERROR in CONSTANT with sizeof\nMissing )\n", ")"); global_token->prev->s = int2str(a->size, 10, TRUE); global_constant_list->arguments = global_token->prev; } else { global_constant_list->arguments = global_token->next; global_token = global_token->next->next; } } struct type* global_typedef() { struct type* type_size; /* typedef $TYPE $NAME; */ global_token = global_token->next; type_size = type_name(); require(NULL != global_token, "Received EOF while reading typedef\n"); type_size = mirror_type(type_size, global_token->s); add_primitive(type_size); global_token = global_token->next; require_match("ERROR in typedef statement\nMissing ;\n", ";"); return type_size; } void global_static_array(struct type* type_size, struct token_list* name) { int size; maybe_bootstrap_error("global array definitions"); globals_list = emit(":GLOBAL_", globals_list); globals_list = emit(name->s, globals_list); globals_list = emit("\n&GLOBAL_STORAGE_", globals_list); globals_list = emit(name->s, globals_list); if (AARCH64 == Architecture || AMD64 == Architecture || RISCV64 == Architecture) { globals_list = emit(" %0", globals_list); } globals_list = emit("\n:GLOBAL_STORAGE_", globals_list); globals_list = emit(name->s, globals_list); require(NULL != global_token->next, "Unterminated global\n"); global_token = global_token->next; /* Make sure not negative */ if(match("-", global_token->s)) { line_error(); fputs("Negative values are not supported for allocated arrays\n", stderr); exit(EXIT_FAILURE); } /* length */ size = strtoint(global_token->s) * type_size->size; /* Stop bad states */ if((size < 0) || (size > 0x100000)) { line_error(); fputs("M2-Planet is very inefficient so you probably don't want to allocate over 1MB into your binary for NULLs\n", stderr); exit(EXIT_FAILURE); } /* Ensure properly closed */ global_token = global_token->next; require_match("missing close bracket\n", "]"); require_match("missing ;\n", ";"); globals_list = emit("\n'", globals_list); while (0 != size) { globals_list = emit(" 00", globals_list); size = size - 1; } globals_list = emit("'\n", globals_list); } void global_assignment() { /* Store the global's value*/ globals_list = emit(":GLOBAL_", globals_list); globals_list = emit(global_token->prev->s, globals_list); globals_list = emit("\n", globals_list); global_token = global_token->next; require(NULL != global_token, "Global locals value in assignment\n"); if(in_set(global_token->s[0], "0123456789")) { /* Assume Int */ globals_list = emit("%", globals_list); globals_list = emit(global_token->s, globals_list); globals_list = emit("\n", globals_list); } else if(('"' == global_token->s[0])) { /* Assume a string*/ globals_list = emit("&GLOBAL_", globals_list); globals_list = emit(global_token->prev->prev->s, globals_list); globals_list = emit("_contents\n", globals_list); globals_list = emit(":GLOBAL_", globals_list); globals_list = emit(global_token->prev->prev->s, globals_list); globals_list = emit("_contents\n", globals_list); globals_list = emit(parse_string(global_token->s), globals_list); } else { line_error(); fputs("Received ", stderr); fputs(global_token->s, stderr); fputs(" in program\n", stderr); exit(EXIT_FAILURE); } global_token = global_token->next; require_match("ERROR in Program\nMissing ;\n", ";"); } /* * program: * declaration * declaration program * * declaration: * CONSTANT identifer value * typedef identifer type; * type-name identifier ; * type-name identifier = value ; * type-name identifier [ value ]; * type-name identifier ( parameter-list ) ; * type-name identifier ( parameter-list ) statement * * parameter-list: * parameter-declaration * parameter-list, parameter-declaration * * parameter-declaration: * type-name identifier-opt */ void program() { unsigned i; function = NULL; Address_of = FALSE; struct type* type_size; new_type: /* Deal with garbage input */ if (NULL == global_token) return; require('#' != global_token->s[0], "unhandled macro directive\n"); require(!match("\n", global_token->s), "unexpected newline token\n"); /* Handle cc_* CONSTANT statements */ if(match("CONSTANT", global_token->s)) { global_constant(); goto new_type; } /* Handle c typedef statements */ if(match("typedef", global_token->s)) { type_size = global_typedef(); goto new_type; } type_size = type_name(); /* Deal with case of struct definitions */ if(NULL == type_size) goto new_type; require(NULL != global_token->next, "Unterminated global\n"); /* Add to global symbol table */ global_symbol_list = sym_declare(global_token->s, type_size, global_symbol_list); global_token = global_token->next; /* Deal with global variables */ if(match(";", global_token->s)) { /* Ensure enough bytes are allocated to store global variable. In some cases it allocates too much but that is harmless. */ globals_list = emit(":GLOBAL_", globals_list); globals_list = emit(global_token->prev->s, globals_list); /* round up division */ i = ceil_div(type_size->size, register_size); globals_list = emit("\n", globals_list); while(i != 0) { globals_list = emit("NULL\n", globals_list); i = i - 1; } global_token = global_token->next; goto new_type; } /* Deal with global functions */ if(match("(", global_token->s)) { declare_function(); goto new_type; } /* Deal with assignment to a global variable */ if(match("=", global_token->s)) { global_assignment(); goto new_type; } /* Deal with global static arrays */ if(match("[", global_token->s)) { global_static_array(type_size, global_token->prev); goto new_type; } /* Everything else is just an error */ line_error(); fputs("Received ", stderr); fputs(global_token->s, stderr); fputs(" in program\n", stderr); exit(EXIT_FAILURE); } void recursive_output(struct token_list* head, FILE* out) { struct token_list* i = reverse_list(head); while(NULL != i) { fputs(i->s, out); i = i->next; } } void output_tokens(struct token_list *i, FILE* out) { while(NULL != i) { fputs(i->s, out); fputs(" ", out); i = i->next; } }
/* Copyright (C) 2021 Sanne Wouda * Copyright (C) 2021 Andrius Å tikonas <andrius@stikonas.eu> * Copyright (C) 2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #include "cc.h" #include "gcc_req.h" void require(int bool, char* error); int strtoint(char* a); void line_error_token(struct token_list* list); struct token_list* eat_token(struct token_list* head); struct conditional_inclusion { struct conditional_inclusion* prev; int include; /* 1 == include, 0 == skip */ int previous_condition_matched; /* 1 == all subsequent conditions treated as FALSE */ }; struct macro_list { struct macro_list* next; char* symbol; struct token_list* expansion; }; struct macro_list* macro_env; struct conditional_inclusion* conditional_inclusion_top; /* point where we are currently modifying the global_token list */ struct token_list* macro_token; void init_macro_env(char* sym, char* value, char* source, int num) { struct macro_list* hold = macro_env; macro_env = calloc(1, sizeof(struct macro_list)); macro_env->symbol = sym; macro_env->next = hold; macro_env->expansion = calloc(1, sizeof(struct token_list)); macro_env->expansion->s = value; macro_env->expansion->filename = source; macro_env->expansion->linenumber = num; } void eat_current_token() { int update_global_token = FALSE; if (macro_token == global_token) update_global_token = TRUE; macro_token = eat_token(macro_token); if(update_global_token) global_token = macro_token; } void eat_newline_tokens() { macro_token = global_token; while(TRUE) { if(NULL == macro_token) return; if(match("\n", macro_token->s)) { eat_current_token(); } else { macro_token = macro_token->next; } } } /* returns the first token inserted; inserts *before* point */ struct token_list* insert_tokens(struct token_list* point, struct token_list* token) { struct token_list* copy; struct token_list* first = NULL; while (NULL != token) { copy = calloc(1, sizeof(struct token_list)); copy->s = token->s; copy->filename = token->filename; copy->linenumber = token->linenumber; if(NULL == first) { first = copy; } copy->next = point; if (NULL != point) { copy->prev = point->prev; if(NULL != point->prev) { point->prev->next = copy; } point->prev = copy; } token = token->next; } return first; } struct macro_list* lookup_macro(struct token_list* token) { if(NULL == token) { line_error_token(macro_token); fputs("null token received in lookup_macro\n", stderr); exit(EXIT_FAILURE); } struct macro_list* hold = macro_env; while (NULL != hold) { if (match(token->s, hold->symbol)) { /* found! */ return hold; } hold = hold->next; } /* not found! */ return NULL; } void remove_macro(struct token_list* token) { if(NULL == token) { line_error_token(macro_token); fputs("received a null in remove_macro\n", stderr); exit(EXIT_FAILURE); } struct macro_list* hold = macro_env; struct macro_list* temp; /* Deal with the first element */ if (match(token->s, hold->symbol)) { macro_env = hold->next; free(hold); return; } /* Remove element form the middle of linked list */ while (NULL != hold->next) { if (match(token->s, hold->next->symbol)) { temp = hold->next; hold->next = hold->next->next; free(temp); return; } hold = hold->next; } /* nothing to undefine */ return; } int macro_expression(); int macro_variable() { int value = 0; struct macro_list* hold = lookup_macro(macro_token); if (NULL != hold) { if(NULL == hold->expansion) { line_error_token(macro_token); fputs("hold->expansion is a null\n", stderr); exit(EXIT_FAILURE); } value = strtoint(hold->expansion->s); } eat_current_token(); return value; } int macro_number() { int result = strtoint(macro_token->s); eat_current_token(); return result; } int macro_primary_expr() { int defined_has_paren = FALSE; int hold; require(NULL != macro_token, "got an EOF terminated macro primary expression\n"); if('-' == macro_token->s[0]) { eat_current_token(); return -macro_primary_expr(); } else if('!' == macro_token->s[0]) { eat_current_token(); return !macro_primary_expr(); } else if('(' == macro_token->s[0]) { eat_current_token(); hold = macro_expression(); require(')' == macro_token->s[0], "missing ) in macro expression\n"); eat_current_token(); return hold; } else if(match("defined", macro_token->s)) { eat_current_token(); require(NULL != macro_token, "got an EOF terminated macro defined expression\n"); if('(' == macro_token->s[0]) { defined_has_paren = TRUE; eat_current_token(); } if (NULL != lookup_macro(macro_token)) { hold = TRUE; } else { hold = FALSE; } eat_current_token(); if(TRUE == defined_has_paren) { if(NULL == macro_token) { line_error_token(macro_token); fputs("unterminated define ( statement\n", stderr); exit(EXIT_FAILURE); } require(')' == macro_token->s[0], "missing close parenthesis for defined()\n"); eat_current_token(); } return hold; } else if(in_set(macro_token->s[0], "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_")) { return macro_variable(); } else if(in_set(macro_token->s[0], "0123456789")) { return macro_number(); } else { return 0; /* FIXME: error handling */ } } int macro_additive_expr() { int lhs = macro_primary_expr(); int hold; require(NULL != macro_token, "got an EOF terminated macro additive expression\n"); if(match("+", macro_token->s)) { eat_current_token(); return lhs + macro_additive_expr(); } else if(match("-", macro_token->s)) { eat_current_token(); return lhs - macro_additive_expr(); } else if(match("*", macro_token->s)) { eat_current_token(); return lhs * macro_additive_expr(); } else if(match("/", macro_token->s)) { eat_current_token(); hold = macro_additive_expr(); require(0 != hold, "divide by zero not valid even in C macros\n"); return lhs / hold; } else if(match("%", macro_token->s)) { eat_current_token(); hold = macro_additive_expr(); require(0 != hold, "modulus by zero not valid even in C macros\n"); return lhs % hold; } else if(match(">>", macro_token->s)) { eat_current_token(); return lhs >> macro_additive_expr(); } else if(match("<<", macro_token->s)) { eat_current_token(); return lhs << macro_additive_expr(); } else { return lhs; } } int macro_relational_expr() { int lhs = macro_additive_expr(); if(match("<", macro_token->s)) { eat_current_token(); return lhs < macro_relational_expr(); } else if(match("<=", macro_token->s)) { eat_current_token(); return lhs <= macro_relational_expr(); } else if(match(">=", macro_token->s)) { eat_current_token(); return lhs >= macro_relational_expr(); } else if(match(">", macro_token->s)) { eat_current_token(); return lhs > macro_relational_expr(); } else if(match("==", macro_token->s)) { eat_current_token(); return lhs == macro_relational_expr(); } else if(match("!=", macro_token->s)) { eat_current_token(); return lhs != macro_relational_expr(); } else { return lhs; } } int macro_bitwise_expr() { int rhs; int lhs = macro_relational_expr(); if(match("&", macro_token->s)) { eat_current_token(); return lhs & macro_bitwise_expr(); } else if(match("&&", macro_token->s)) { eat_current_token(); rhs = macro_bitwise_expr(); return lhs && rhs; } else if(match("|", macro_token->s)) { eat_current_token(); rhs = macro_bitwise_expr(); return lhs | rhs; } else if(match("||", macro_token->s)) { eat_current_token(); rhs = macro_bitwise_expr(); return lhs || rhs; } else if(match("^", macro_token->s)) { eat_current_token(); rhs = macro_bitwise_expr(); return lhs ^ rhs; } else { return lhs; } } int macro_expression() { return macro_bitwise_expr(); } void handle_define() { struct macro_list* hold; struct token_list* expansion_end = NULL; /* don't use #define statements from non-included blocks */ int conditional_define = TRUE; if(NULL != conditional_inclusion_top) { if(FALSE == conditional_inclusion_top->include) { conditional_define = FALSE; } } eat_current_token(); require(NULL != macro_token, "got an EOF terminated #define\n"); require('\n' != macro_token->s[0], "unexpected newline after #define\n"); /* insert new macro */ hold = calloc(1, sizeof(struct macro_list)); hold->symbol = macro_token->s; hold->next = macro_env; /* provided it isn't in a non-included block */ if(conditional_define) macro_env = hold; /* discard the macro name */ eat_current_token(); while (TRUE) { require(NULL != macro_token, "got an EOF terminated #define\n"); if ('\n' == macro_token->s[0]) { if(NULL == expansion_end) { hold->expansion = NULL; expansion_end = macro_token; return; } expansion_end->next = NULL; return; } require(NULL != hold, "#define got something it can't handle\n"); expansion_end = macro_token; /* in the first iteration, we set the first token of the expansion, if it exists */ if (NULL == hold->expansion) { hold->expansion = macro_token; } /* throw away if not used */ if(!conditional_define && (NULL != hold)) { free(hold); hold = NULL; } eat_current_token(); } } void handle_undef() { eat_current_token(); remove_macro(macro_token); eat_current_token(); } void handle_error(int warning_p) { /* don't use #error statements from non-included blocks */ int conditional_error = TRUE; if(NULL != conditional_inclusion_top) { if(FALSE == conditional_inclusion_top->include) { conditional_error = FALSE; } } eat_current_token(); /* provided it isn't in a non-included block */ if(conditional_error) { line_error_token(macro_token); if(warning_p) fputs(" warning: #warning ", stderr); else fputs(" error: #error ", stderr); while (TRUE) { require(NULL != macro_token, "\nFailed to properly terminate error message with \\n\n"); if ('\n' == macro_token->s[0]) break; fputs(macro_token->s, stderr); macro_token = macro_token->next; fputs(" ", stderr); } fputs("\n", stderr); if(!warning_p) exit(EXIT_FAILURE); } while (TRUE) { require(NULL != macro_token, "\nFailed to properly terminate error message with \\n\n"); /* discard the error */ if ('\n' == macro_token->s[0]) { return; } eat_current_token(); } } void eat_block(); void macro_directive() { struct conditional_inclusion *t; int result; /* FIXME: whitespace is allowed between "#"" and "if" */ if(match("#if", macro_token->s)) { eat_current_token(); /* evaluate constant integer expression */ result = macro_expression(); /* push conditional inclusion */ t = calloc(1, sizeof(struct conditional_inclusion)); t->prev = conditional_inclusion_top; conditional_inclusion_top = t; t->include = TRUE; if(FALSE == result) { t->include = FALSE; eat_block(); } t->previous_condition_matched = t->include; } else if(match("#ifdef", macro_token->s)) { eat_current_token(); require(NULL != macro_token, "got an EOF terminated macro defined expression\n"); if (NULL != lookup_macro(macro_token)) { result = TRUE; eat_current_token(); } else { result = FALSE; eat_block(); } /* push conditional inclusion */ t = calloc(1, sizeof(struct conditional_inclusion)); t->prev = conditional_inclusion_top; conditional_inclusion_top = t; t->include = TRUE; if(FALSE == result) { t->include = FALSE; } t->previous_condition_matched = t->include; } else if(match("#ifndef", macro_token->s)) { eat_current_token(); require(NULL != macro_token, "got an EOF terminated macro defined expression\n"); if (NULL != lookup_macro(macro_token)) { result = FALSE; } else { result = TRUE; eat_current_token(); } /* push conditional inclusion */ t = calloc(1, sizeof(struct conditional_inclusion)); t->prev = conditional_inclusion_top; conditional_inclusion_top = t; t->include = TRUE; if(FALSE == result) { t->include = FALSE; eat_block(); } t->previous_condition_matched = t->include; } else if(match("#elif", macro_token->s)) { eat_current_token(); result = macro_expression(); require(NULL != conditional_inclusion_top, "#elif without leading #if\n"); conditional_inclusion_top->include = result && !conditional_inclusion_top->previous_condition_matched; conditional_inclusion_top->previous_condition_matched = conditional_inclusion_top->previous_condition_matched || conditional_inclusion_top->include; if(FALSE == result) { eat_block(); } } else if(match("#else", macro_token->s)) { eat_current_token(); require(NULL != conditional_inclusion_top, "#else without leading #if\n"); conditional_inclusion_top->include = !conditional_inclusion_top->previous_condition_matched; if(FALSE == conditional_inclusion_top->include) { eat_block(); } } else if(match("#endif", macro_token->s)) { if(NULL == conditional_inclusion_top) { line_error_token(macro_token); fputs("unexpected #endif\n", stderr); exit(EXIT_FAILURE); } eat_current_token(); /* pop conditional inclusion */ t = conditional_inclusion_top; conditional_inclusion_top = conditional_inclusion_top->prev; free(t); } else if(match("#define", macro_token->s)) { handle_define(); } else if(match("#undef", macro_token->s)) { handle_undef(); } else if(match("#error", macro_token->s)) { handle_error(FALSE); } else if(match("#warning", macro_token->s)) { handle_error(TRUE); } else { if(!match("#include", macro_token->s)) { /* Put a big fat warning but see if we can just ignore */ fputs(">>WARNING<<\n>>WARNING<<\n", stderr); line_error_token(macro_token); fputs("feature: ", stderr); fputs(macro_token->s, stderr); fputs(" unsupported in M2-Planet\nIgnoring line, may result in bugs\n>>WARNING<<\n>>WARNING<<\n\n", stderr); } /* unhandled macro directive; let's eat until a newline; om nom nom */ while(TRUE) { if(NULL == macro_token) { return; } if('\n' == macro_token->s[0]) { return; } eat_current_token(); } } } void eat_until_endif() { /* This #if block is nested inside of an #if block that needs to be dropped, lose EVERYTHING */ do { require(NULL != macro_token, "Unterminated #if block\n"); if(match("#if", macro_token->s) || match("#ifdef", macro_token->s) || match("#ifndef", macro_token->s)) { eat_current_token(); eat_until_endif(); } eat_current_token(); require(NULL != macro_token, "Unterminated #if block\n"); } while(!match("#endif", macro_token->s)); } void eat_block() { /* This conditional #if block is wrong, drop everything until the #elif/#else/#endif */ do { if(match("#if", macro_token->s) || match("#ifdef", macro_token->s) || match("#ifndef", macro_token->s)) { eat_current_token(); eat_until_endif(); } eat_current_token(); require(NULL != macro_token, "Unterminated #if block\n"); if(match("#elif", macro_token->s)) break; if(match("#else", macro_token->s)) break; if(match("#endif", macro_token->s)) break; } while(TRUE); require(NULL != macro_token->prev, "impossible #if block\n"); /* rewind the newline */ if(match("\n", macro_token->prev->s)) macro_token = macro_token->prev; } struct token_list* maybe_expand(struct token_list* token) { if(NULL == token) { line_error_token(macro_token); fputs("maybe_expand passed a null token\n", stderr); exit(EXIT_FAILURE); } struct macro_list* hold = lookup_macro(token); struct token_list* hold2; if(NULL == token->next) { line_error_token(macro_token); fputs("we can't expand a null token: ", stderr); fputs(token->s, stderr); fputc('\n', stderr); exit(EXIT_FAILURE); } if (NULL == hold) { return token->next; } token = eat_token(token); if (NULL == hold->expansion) { return token->next; } hold2 = insert_tokens(token, hold->expansion); return hold2->next; } void preprocess() { int start_of_line = TRUE; macro_token = global_token; while(NULL != macro_token) { if(start_of_line && '#' == macro_token->s[0]) { macro_directive(); if(macro_token) { if('\n' != macro_token->s[0]) { line_error_token(macro_token); fputs("newline expected at end of macro directive\n", stderr); fputs("found: '", stderr); fputs(macro_token->s, stderr); fputs("'\n", stderr); exit(EXIT_FAILURE); } } } else if('\n' == macro_token->s[0]) { start_of_line = TRUE; macro_token = macro_token->next; } else { start_of_line = FALSE; if(NULL == conditional_inclusion_top) { macro_token = maybe_expand(macro_token); } else if(!conditional_inclusion_top->include) { /* rewrite the token stream to exclude the current token */ eat_block(); start_of_line = TRUE; } else { macro_token = maybe_expand(macro_token); } } } }
/* Copyright (C) 2016 Jeremiah Orians * Copyright (C) 2020 deesix <deesix@tuta.io> * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #include<stdlib.h> #include<stdio.h> #include<string.h> #include"cc.h" /* The core functions */ void initialize_types(); struct token_list* read_all_tokens(FILE* a, struct token_list* current, char* filename); struct token_list* reverse_list(struct token_list* head); struct token_list* remove_line_comments(struct token_list* head); struct token_list* remove_line_comment_tokens(struct token_list* head); struct token_list* remove_preprocessor_directives(struct token_list* head); void eat_newline_tokens(); void init_macro_env(char* sym, char* value, char* source, int num); void preprocess(); void program(); void recursive_output(struct token_list* i, FILE* out); void output_tokens(struct token_list *i, FILE* out); int strtoint(char *a); int main(int argc, char** argv) { MAX_STRING = 4096; BOOTSTRAP_MODE = FALSE; PREPROCESSOR_MODE = FALSE; int DEBUG = FALSE; FILE* in = stdin; FILE* destination_file = stdout; Architecture = 0; /* catch unset */ init_macro_env("__M2__", "42", "__INTERNAL_M2__", 0); /* Setup __M2__ */ char* arch; char* name; char* hold; int env=0; char* val; int i = 1; while(i <= argc) { if(NULL == argv[i]) { i = i + 1; } else if(match(argv[i], "-f") || match(argv[i], "--file")) { if(NULL == hold_string) { hold_string = calloc(MAX_STRING + 4, sizeof(char)); require(NULL != hold_string, "Impossible Exhaustion has occurred\n"); } name = argv[i + 1]; if(NULL == name) { fputs("did not receive a file name\n", stderr); exit(EXIT_FAILURE); } in = fopen(name, "r"); if(NULL == in) { fputs("Unable to open for reading file: ", stderr); fputs(name, stderr); fputs("\n Aborting to avoid problems\n", stderr); exit(EXIT_FAILURE); } global_token = read_all_tokens(in, global_token, name); fclose(in); i = i + 2; } else if(match(argv[i], "-o") || match(argv[i], "--output")) { destination_file = fopen(argv[i + 1], "w"); if(NULL == destination_file) { fputs("Unable to open for writing file: ", stderr); fputs(argv[i + 1], stderr); fputs("\n Aborting to avoid problems\n", stderr); exit(EXIT_FAILURE); } i = i + 2; } else if(match(argv[i], "-A") || match(argv[i], "--architecture")) { arch = argv[i + 1]; if(match("knight-native", arch)) { Architecture = KNIGHT_NATIVE; init_macro_env("__knight__", "1", "--architecture", env); env = env + 1; } else if(match("knight-posix", arch)) { Architecture = KNIGHT_POSIX; init_macro_env("__knight_posix__", "1", "--architecture", env); env = env + 1; } else if(match("x86", arch)) { Architecture = X86; init_macro_env("__i386__", "1", "--architecture", env); env = env + 1; } else if(match("amd64", arch)) { Architecture = AMD64; init_macro_env("__x86_64__", "1", "--architecture", env); env = env + 1; } else if(match("armv7l", arch)) { Architecture = ARMV7L; init_macro_env("__arm__", "1", "--architecture", env); env = env + 1; } else if(match("aarch64", arch)) { Architecture = AARCH64; init_macro_env("__aarch64__", "1", "--architecture", env); env = env + 1; } else if(match("riscv32", arch)) { Architecture = RISCV32; init_macro_env("__riscv", "1", "--architecture", env); init_macro_env("__riscv_xlen", "32", "--architecture", env + 1); env = env + 2; } else if(match("riscv64", arch)) { Architecture = RISCV64; init_macro_env("__riscv", "1", "--architecture", env); init_macro_env("__riscv_xlen", "64", "--architecture", env + 1); env = env + 2; } else { fputs("Unknown architecture: ", stderr); fputs(arch, stderr); fputs(" know values are: knight-native, knight-posix, x86, amd64, armv7l, aarch64, riscv32 and riscv64\n", stderr); exit(EXIT_FAILURE); } i = i + 2; } else if(match(argv[i], "--max-string")) { hold = argv[i+1]; if(NULL == hold) { fputs("--max-string requires a numeric argument\n", stderr); exit(EXIT_FAILURE); } MAX_STRING = strtoint(hold); require(0 < MAX_STRING, "Not a valid string size\nAbort and fix your --max-string\n"); i = i + 2; } else if(match(argv[i], "--bootstrap-mode")) { BOOTSTRAP_MODE = TRUE; i = i + 1; } else if(match(argv[i], "-g") || match(argv[i], "--debug")) { DEBUG = TRUE; i = i + 1; } else if(match(argv[i], "-h") || match(argv[i], "--help")) { fputs(" -f input file\n -o output file\n --help for this message\n --version for file version\n", stdout); exit(EXIT_SUCCESS); } else if(match(argv[i], "-E")) { PREPROCESSOR_MODE = TRUE; i = i + 1; } else if(match(argv[i], "-D")) { val = argv[i+1]; if(NULL == val) { fputs("-D requires an argument", stderr); exit(EXIT_FAILURE); } while(0 != val[0]) { if('=' == val[0]) { val[0] = 0; val = val + 1; break; } val = val + 1; } init_macro_env(argv[i+1], val, "__ARGV__", env); env = env + 1; i = i + 2; } else if(match(argv[i], "-V") || match(argv[i], "--version")) { fputs("M2-Planet v1.10.0\n", stderr); exit(EXIT_SUCCESS); } else { fputs("UNKNOWN ARGUMENT\n", stdout); exit(EXIT_FAILURE); } } /* Deal with special case of architecture not being set */ if(0 == Architecture) { Architecture = KNIGHT_NATIVE; init_macro_env("__knight__", "1", "--architecture", env); } /* Deal with special case of wanting to read from standard input */ if(stdin == in) { hold_string = calloc(MAX_STRING + 4, sizeof(char)); require(NULL != hold_string, "Impossible Exhaustion has occurred\n"); global_token = read_all_tokens(in, global_token, "STDIN"); } if(NULL == global_token) { fputs("Either no input files were given or they were empty\n", stderr); exit(EXIT_FAILURE); } global_token = reverse_list(global_token); if (BOOTSTRAP_MODE) { global_token = remove_line_comment_tokens(global_token); global_token = remove_preprocessor_directives(global_token); } else { global_token = remove_line_comments(global_token); preprocess(); } if (PREPROCESSOR_MODE) { fputs("\n/* Preprocessed source */\n", destination_file); output_tokens(global_token, destination_file); goto exit_success; } /* the main parser doesn't know how to handle newline tokens */ eat_newline_tokens(); initialize_types(); reset_hold_string(); output_list = NULL; program(); /* Output the program we have compiled */ fputs("\n# Core program\n", destination_file); recursive_output(output_list, destination_file); if(KNIGHT_NATIVE == Architecture) fputs("\n", destination_file); else if(DEBUG) fputs("\n:ELF_data\n", destination_file); fputs("\n# Program global variables\n", destination_file); recursive_output(globals_list, destination_file); fputs("\n# Program strings\n", destination_file); recursive_output(strings_list, destination_file); if(KNIGHT_NATIVE == Architecture) fputs("\n:STACK\n", destination_file); else if(!DEBUG) fputs("\n:ELF_end\n", destination_file); exit_success: if (destination_file != stdout) { fclose(destination_file); } return EXIT_SUCCESS; }
## Copyright (C) 2017 Jeremiah Orians ## This file is part of M2-Planet. ## ## M2-Planet is free software: you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation, either version 3 of the License, or ## (at your option) any later version. ## ## M2-Planet is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. DEFINE add_eax, 81C0 DEFINE add_ebp, 81C5 DEFINE add_ebx,eax 01C3 DEFINE add_eax,ebp 01E8 DEFINE add_eax,ebx 01D8 DEFINE and_eax,ebx 21D8 DEFINE call E8 DEFINE call_eax FFD0 DEFINE cmp 39C3 DEFINE cdq 99 DEFINE div_ebx F7F3 DEFINE idiv_ebx F7FB DEFINE int CD DEFINE je 0F84 DEFINE jne 0F85 DEFINE jmp E9 DEFINE lea_eax,[ebp+DWORD] 8D85 DEFINE lea_eax,[esp+DWORD] 8D8424 DEFINE lea_ebx,[esp+DWORD] 8D9C24 DEFINE lea_ecx,[esp+DWORD] 8D8C24 DEFINE lea_edx,[esp+DWORD] 8D9424 DEFINE mov_eax,[esp+DWORD] 8B8424 DEFINE mov_eax,ebp 89E8 DEFINE mov_eax,ebx 89D8 DEFINE mov_eax,ebx 89D8 DEFINE mov_eax,edx 89D0 DEFINE mov_ebx,eax 89C3 DEFINE mov_ecx,eax 89C1 DEFINE mov_ecx,esp 89E1 DEFINE mov_edi,esp 89E7 DEFINE mov_ebp,edi 89fd DEFINE mov_ebp,esp 89E5 DEFINE mov_eax, B8 DEFINE mov_ebx, BB DEFINE mov_edx, BA DEFINE mov_eax,[eax] 8B00 DEFINE mov_ebx,[ebx] 8B1B DEFINE mov_ecx,[ecx] 8B09 DEFINE mov_edx,[edx] 8B12 DEFINE mov_[ebx],al 8803 DEFINE mov_[ebx],eax 8903 DEFINE movsx_eax,BYTE_PTR_[eax] 0FBE00 DEFINE movsx_ebx,BYTE_PTR_[ebx] 0FBE1B DEFINE movzx_eax,al 0FB6C0 DEFINE mul_ebx F7E3 DEFINE imul_ebx F7EB DEFINE NULL 00000000 DEFINE not_eax F7D0 DEFINE or_eax,ebx 09D8 DEFINE pop_eax 58 DEFINE pop_ebx 5B DEFINE pop_ebp 5D DEFINE pop_edi 5F DEFINE push_eax 50 DEFINE push_ebx 53 DEFINE push_ebp 55 DEFINE push_edi 57 DEFINE ret C3 DEFINE sal_eax, C1E0 DEFINE sal_eax,cl D3F0 DEFINE shl_eax,cl D3E0 DEFINE sar_eax,cl D3F8 DEFINE shr_eax,cl D3E8 DEFINE seta_al 0F97C0 DEFINE setae_al 0F93C0 DEFINE setb_al 0F92C0 DEFINE setbe_al 0F96C0 DEFINE sete_al 0F94C0 DEFINE setle_al 0F9EC0 DEFINE setl_al 0F9CC0 DEFINE setge_al 0F9DC0 DEFINE setg_al 0F9FC0 DEFINE setne_al 0F95C0 DEFINE sub_ebx,eax 29C3 DEFINE test_eax,eax 85C0 DEFINE xchg_ebx,eax 93 DEFINE xor_eax,ebx 31D8
## Copyright (C) 2016 Jeremiah Orians ## This file is part of stage0. ## ## stage0 is free software: you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation, either version 3 of the License, or ## (at your option) any later version. ## ## stage0 is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with stage0. If not, see <http://www.gnu.org/licenses/>. :_start mov_ebp,esp ; Protect esp ;; Prepare argv lea_eax,[ebp+DWORD] %4 ; ARGV_address = EBP + 4 push_eax ; Put argv on the stack ;; Prepare envp mov_eax,ebp ; Address we need to load from mov_eax,[eax] ; Get ARGC add_eax, %2 ; OFFSET = ARGC + 2 sal_eax, !2 ; OFFSET = OFFSET * WORDSIZE add_eax,ebp ; ENVP_address = ESP + OFFSET push_eax ; Put envp on the stack ;; Stack offset add_ebp, %4 ; Fix ebp ;; Perform the main loop call %FUNCTION_main ;; Exit to kernel mov_ebx,eax ; Using the return code given by main mov_eax, %1 ; Syscall exit int !0x80 ; Exit with that code
/* -*- c-file-style: "linux";indent-tabs-mode:t -*- */ /* Copyright (C) 2016 Jeremiah Orians * Copyright (C) 2017 Jan Nieuwenhuizen <janneke@gnu.org> * This file is part of mescc-tools. * * mescc-tools is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> // CONSTANT HEX 16 #define HEX 16 // CONSTANT OCTAL 8 #define OCTAL 8 // CONSTANT BINARY 2 #define BINARY 2 /*********************************************************** * Needed for current implementation of little endian * * Can be used to support little bit endian instruction * * sets if we ever find one that might be useful * * But I seriously doubt it * ***********************************************************/ void reverseBitOrder(char* c, int ByteMode) { if(NULL == c) return; if(0 == c[1]) return; int hold = c[0]; if(HEX == ByteMode) { c[0] = c[1]; c[1] = hold; reverseBitOrder(c+2, ByteMode); } else if(OCTAL == ByteMode) { c[0] = c[2]; c[2] = hold; reverseBitOrder(c+3, ByteMode); } else if(BINARY == ByteMode) { c[0] = c[7]; c[7] = hold; hold = c[1]; c[1] = c[6]; c[6] = hold; hold = c[2]; c[2] = c[5]; c[5] = hold; hold = c[3]; c[3] = c[4]; c[4] = hold; reverseBitOrder(c+8, ByteMode); } } void LittleEndian(char* start, int ByteMode) { char* end = start; char* c = start; while(0 != end[0]) end = end + 1; int hold; for(end = end - 1; start < end; start = start + 1) { hold = start[0]; start[0] = end[0]; end[0] = hold; end = end - 1; } /* The above makes a reversed bit order */ reverseBitOrder(c, ByteMode); } int hex2char(int c) { if((c >= 0) && (c <= 9)) return (c + 48); else if((c >= 10) && (c <= 15)) return (c + 55); else return -1; } int stringify(char* s, int digits, int divisor, int value, int shift) { int i = value; if(digits > 1) { i = stringify(s+1, (digits - 1), divisor, value, shift); } s[0] = hex2char(i & (divisor - 1)); return (i >> shift); }
/* -*- c-file-style: "linux";indent-tabs-mode:t -*- */ /* Copyright (C) 2017 Jeremiah Orians * Copyright (C) 2017 Jan Nieuwenhuizen <janneke@gnu.org> * This file is part of mescc-tools * * mescc-tools is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/stat.h> #include "M2libc/bootstrappable.h" // CONSTANT max_string 4096 #define max_string 4096 int BITSIZE; int BigEndian; // CONSTANT HEX 16 #define HEX 16 // CONSTANT OCTAL 8 #define OCTAL 8 // CONSTANT BINARY 2 #define BINARY 2 /* Strings needed for constants */ char* zero_8; char* zero_16; char* zero_32; char* one_16; char* one_32; char* two_8; char* two_32; char* three_32; char* six_32; char* sixteen_32; char* twentyfour_32; /* Imported from stringify.c */ int stringify(char* s, int digits, int divisor, int value, int shift); void LittleEndian(char* start, int ByteMode); struct entry { struct entry* next; char* name; }; FILE* output; struct entry* jump_table; int count; char* entry; void consume_token(FILE* source_file, char* s) { int i = 0; int c = fgetc(source_file); require(EOF != c, "Can not have an EOF token\n"); do { s[i] = c; i = i + 1; require(max_string > i, "Token exceeds token length restriction\n"); c = fgetc(source_file); if(EOF == c) break; } while(!in_set(c, " \t\n>")); } void storeLabel(FILE* source_file) { struct entry* entry = calloc(1, sizeof(struct entry)); /* Prepend to list */ entry->next = jump_table; jump_table = entry; /* Store string */ entry->name = calloc((max_string + 1), sizeof(char)); consume_token(source_file, entry->name); count = count + 1; } void line_Comment(FILE* source_file) { int c = fgetc(source_file); while(!in_set(c, "\n\r")) { if(EOF == c) break; c = fgetc(source_file); } } void purge_string(FILE* source_file) { int c = fgetc(source_file); while((EOF != c) && ('"' != c)) { c = fgetc(source_file); } } void first_pass(struct entry* input) { if(NULL == input) return; first_pass(input->next); FILE* source_file = fopen(input->name, "r"); if(NULL == source_file) { fputs("The file: ", stderr); fputs(input->name, stderr); fputs(" can not be opened!\n", stderr); exit(EXIT_FAILURE); } int c; for(c = fgetc(source_file); EOF != c; c = fgetc(source_file)) { /* Check for and deal with label */ if(58 == c) { storeLabel(source_file); } /* Check for and deal with line comments */ else if (c == '#' || c == ';') { line_Comment(source_file); } else if ('"' == c) { purge_string(source_file); } } fclose(source_file); } void output_string_table(struct entry* node) { fputs("\n# Generated string table\n:ELF_str\n", output); fputs(zero_8, output); fputs("\t# NULL string\n", output); struct entry* i; for(i = node; NULL != i; i = i->next) { fputs(":ELF_str_", output); fputs(i->name, output); fputs("\t\"", output); fputs(i->name, output); fputs("\"\n", output); } fputs("# END Generated string table\n\n", output); } void output_symbol_table(struct entry* node) { fputs("\n# Generated symbol table\n:ELF_sym\n# Required NULL symbol entry\n", output); if(64 == BITSIZE) { fputs(zero_32, output); fputs("\t# st_name\n", output); fputs(zero_8, output); fputs("\t# st_info\n", output); fputs(zero_8, output); fputs("\t# st_other\n", output); fputs(one_16, output); fputs("\t# st_shndx\n", output); fputs(zero_32, output); fputc(' ', output); fputs(zero_32, output); fputs("\t# st_value\n", output); fputs(zero_32, output); fputc(' ', output); fputs(zero_32, output); fputs("\t# st_size\n\n", output); } else { fputs(zero_32, output); fputs("\t# st_name\n", output); fputs(zero_32, output); fputs("\t# st_value\n", output); fputs(zero_32, output); fputs("\t# st_size\n", output); fputs(zero_8, output); fputs("\t# st_info\n", output); fputs(zero_8, output); fputs("\t# st_other\n", output); fputs(one_16, output); fputs("\t# st_shndx\n\n", output); } struct entry* i; for(i = node; NULL != i; i = i->next) { fputs("%ELF_str_", output); fputs(i->name, output); fputs(">ELF_str\t# st_name\n", output); if(64 == BITSIZE) { fputs(two_8, output); fputs("\t# st_info (FUNC)\n", output); if(('_' == i->name[0]) && !match(entry, i->name)) { fputs(two_8, output); fputs("\t# st_other (hidden)\n", output); } else { fputs(zero_8, output); fputs("\t# st_other (other)\n", output); } fputs(one_16, output); fputs("\t# st_shndx\n", output); fputs("&", output); fputs(i->name, output); fputc(' ', output); fputs(zero_32, output); fputs("\t# st_value\n", output); fputs(zero_32, output); fputc(' ', output); fputs(zero_32, output); fputs("\t# st_size (unknown size)\n\n", output); } else { fputs("&", output); fputs(i->name, output); fputs("\t#st_value\n", output); fputs(zero_32, output); fputs("\t# st_size (unknown size)\n", output); fputs(two_8, output); fputs("\t# st_info (FUNC)\n", output); if(('_' == i->name[0]) && !match(entry, i->name)) { fputs(two_8, output); fputs("\t# st_other (hidden)\n", output); } else { fputs(zero_8, output); fputs("\t# st_other (default)\n", output); } fputs(one_16, output); fputs("\t# st_shndx\n\n", output); } } fputs("# END Generated symbol table\n", output); } struct entry* reverse_list(struct entry* head) { struct entry* root = NULL; struct entry* next; while(NULL != head) { next = head->next; head->next = root; root = head; head = next; } return root; } void write_int(char* field, char* label) { fputs(field, output); fputs("\t#", output); fputs(label, output); fputc('\n', output); } void write_register(char* field, char* label) { /* $field section in the section headers are different size for 32 and 64bits */ /* The below is broken for BigEndian */ fputs(field, output); if(64 == BITSIZE) { fputc(' ', output); fputs(zero_32, output); } fputs("\t#", output); fputs(label, output); fputc('\n', output); } void write_section(char* label, char* name, char* type, char* flags, char* address, char* offset, char* size, char* link, char* info, char* entry) { /* Write label */ fputc('\n', output); fputs(label, output); fputc('\n', output); write_int(name, "sh_name"); write_int(type, "sh_type"); write_register(flags, "sh_flags"); write_register(address, "sh_addr"); write_register(offset, "sh_offset"); write_register(size, "sh_size"); write_int(link, "sh_link"); /* Deal with the ugly case of stubs */ fputs(info, output); fputs("\t#sh_info\n", output); /* Alignment section in the section headers are different size for 32 and 64bits */ /* The below is broken for BigEndian */ if(64 == BITSIZE) { fputs(one_32, output); fputc(' ', output); fputs(zero_32, output); fputs("\t#sh_addralign\n", output); } else { fputs(one_32, output); fputs("\t#sh_addralign\n", output); } write_register(entry, "sh_entsize"); } char* get_string(int value, int size, int ByteMode, int shift) { char* ch = calloc(42, sizeof(char)); require(NULL != ch, "Exhausted available memory\n"); ch[0] = '\''; stringify(ch+1, size, ByteMode, value, shift); if(!BigEndian) LittleEndian(ch+1, ByteMode); int i = 0; while(0 != ch[i]) { i = i + 1; } ch[i] = '\''; return ch; } char* setup_string(int value, int number_of_bytes, int ByteMode) { int shift; int size; if(HEX == ByteMode) { size = 2; shift = 4; } else if(OCTAL == ByteMode) { size = 3; shift = 3; } else if(BINARY == ByteMode) { size = 8; shift = 1; } else { fputs("reached impossible mode\n", stderr); exit(EXIT_FAILURE); } return get_string(value, number_of_bytes *size, ByteMode, shift); } void setup_strings(int ByteMode) { zero_8 = setup_string(0, 1, ByteMode); zero_16 = setup_string(0, 2, ByteMode); zero_32 = setup_string(0, 4, ByteMode); one_16 = setup_string(1, 2, ByteMode); one_32 = setup_string(1, 4, ByteMode); two_8 = setup_string(2, 1, ByteMode); two_32 = setup_string(2, 4, ByteMode); three_32 = setup_string(3, 4, ByteMode); six_32 = setup_string(6, 4, ByteMode); sixteen_32 = setup_string(16, 4, ByteMode); twentyfour_32 = setup_string(24, 4, ByteMode); } /* Standard C main program */ int main(int argc, char **argv) { jump_table = NULL; struct entry* input = NULL; output = stdout; char* output_file = ""; entry = ""; BITSIZE = 32; count = 1; BigEndian = TRUE; int ByteMode = HEX; int set = FALSE; struct entry* temp; struct entry* head; int option_index = 1; while(option_index <= argc) { if(NULL == argv[option_index]) { option_index = option_index + 1; } else if(match(argv[option_index], "-h") || match(argv[option_index], "--help")) { fputs("Usage: ", stderr); fputs(argv[0], stderr); fputs(" --file FILENAME1 {--file FILENAME2} --output FILENAME\n", stderr); exit(EXIT_SUCCESS); } else if(match(argv[option_index], "--64")) { BITSIZE = 64; option_index = option_index + 1; } else if(match(argv[option_index], "-f") || match(argv[option_index], "--file")) { temp = calloc(1, sizeof(struct entry)); temp->name = argv[option_index + 1]; temp->next = input; input = temp; option_index = option_index + 2; } else if(match(argv[option_index], "-o") || match(argv[option_index], "--output")) { output_file = argv[option_index + 1]; output = fopen(output_file, "w"); if(NULL == output) { fputs("The file: ", stderr); fputs(input->name, stderr); fputs(" can not be opened!\n", stderr); exit(EXIT_FAILURE); } option_index = option_index + 2; } else if(match(argv[option_index], "-b") || match(argv[option_index], "--binary")) { ByteMode = BINARY; option_index = option_index + 1; } else if(match(argv[option_index], "-O") || match(argv[option_index], "--octal")) { ByteMode = OCTAL; option_index = option_index + 1; } else if(match(argv[option_index], "-X") || match(argv[option_index], "--hex")) { ByteMode = HEX; option_index = option_index + 1; } else if(match(argv[option_index], "--big-endian")) { BigEndian = TRUE; set = TRUE; option_index = option_index + 1; } else if(match(argv[option_index], "--little-endian")) { BigEndian = FALSE; set = TRUE; option_index = option_index + 1; } else if(match(argv[option_index], "-V") || match(argv[option_index], "--version")) { fputs("blood-elf 2.0.0\n(Basically Launches Odd Object Dump ExecutabLe Files\n", stdout); exit(EXIT_SUCCESS); } else if(match(argv[option_index], "--entry")) { head = calloc(1, sizeof(struct entry)); /* Include _start or any other entry from your .hex2 */ head->next = jump_table; jump_table = head; jump_table->name = argv[option_index + 1]; /* However only the last one will be exempt from the _name hidden rule */ entry = argv[option_index + 1]; option_index = option_index + 2; count = count + 1; } else { fputs("Unknown option\n", stderr); exit(EXIT_FAILURE); } } /* Make sure we have a program tape to run */ if (NULL == input) { return EXIT_FAILURE; } /* Force setting of endianness */ if(!set) { fputs("either --little-endian or --big-endian MUST be set\n", stderr); return EXIT_FAILURE; } /* Setup the ugly formating because RISC-V sucks */ setup_strings(ByteMode); /* Get all of the labels */ first_pass(input); /* Reverse their order */ jump_table = reverse_list(jump_table); /* Create sections */ /* Create string names for sections */ fputs("# Generated sections\n:ELF_shstr\n", output); fputs(zero_8, output); fputs("\t# NULL\n", output); fputs(":ELF_shstr__text\n\".text\"\n", output); fputs(":ELF_shstr__shstr\n\".shstrtab\"\n", output); fputs(":ELF_shstr__sym\n\".symtab\"\n", output); fputs(":ELF_shstr__str\n\".strtab\"\n", output); /* Create NULL section header as is required by the Spec. So dumb and waste of bytes*/ write_section(":ELF_section_headers", zero_32, zero_32, zero_32, zero_32, zero_32, zero_32, zero_32, zero_32, zero_32); write_section(":ELF_section_header_text", "%ELF_shstr__text>ELF_shstr", one_32, six_32, "&ELF_text", "%ELF_text>ELF_base", "%ELF_data>ELF_text", zero_32, zero_32, zero_32); write_section(":ELF_section_header_shstr", "%ELF_shstr__shstr>ELF_shstr", three_32, zero_32, "&ELF_shstr", "%ELF_shstr>ELF_base", "%ELF_section_headers>ELF_shstr", zero_32, zero_32, zero_32); write_section(":ELF_section_header_str", "%ELF_shstr__str>ELF_shstr", three_32, zero_32, "&ELF_str", "%ELF_str>ELF_base", "%ELF_sym>ELF_str", zero_32, zero_32, zero_32); if(64 == BITSIZE) write_section(":ELF_section_header_sym", "%ELF_shstr__sym>ELF_shstr", two_32, zero_32, "&ELF_sym", "%ELF_sym>ELF_base", "%ELF_end>ELF_sym", three_32, setup_string(count, 4, ByteMode), twentyfour_32); else write_section(":ELF_section_header_sym", "%ELF_shstr__sym>ELF_shstr", two_32, zero_32, "&ELF_sym", "%ELF_sym>ELF_base", "%ELF_end>ELF_sym", three_32, setup_string(count, 4, ByteMode), sixteen_32); /* Create dwarf stubs needed for objdump -d to get function names */ output_string_table(jump_table); output_symbol_table(jump_table); fputs("\n:ELF_end\n", output); /* Close output file */ fclose(output); return EXIT_SUCCESS; }
## Copyright (C) 2017 Jeremiah Orians ## This file is part of M2-Planet. ## ## M2-Planet is free software: you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation, either version 3 of the License, or ## (at your option) any later version. ## ## M2-Planet is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. DEFINE add_eax, 81C0 DEFINE add_ebp, 81C5 DEFINE add_ebx,eax 01C3 DEFINE add_eax,ebp 01E8 DEFINE add_eax,ebx 01D8 DEFINE and_eax,ebx 21D8 DEFINE call E8 DEFINE call_eax FFD0 DEFINE cmp 39C3 DEFINE cdq 99 DEFINE div_ebx F7F3 DEFINE idiv_ebx F7FB DEFINE int CD DEFINE je 0F84 DEFINE jne 0F85 DEFINE jmp E9 DEFINE lea_eax,[ebp+DWORD] 8D85 DEFINE lea_eax,[esp+DWORD] 8D8424 DEFINE lea_ebx,[esp+DWORD] 8D9C24 DEFINE lea_ecx,[esp+DWORD] 8D8C24 DEFINE lea_edx,[esp+DWORD] 8D9424 DEFINE mov_eax,[esp+DWORD] 8B8424 DEFINE mov_eax,ebp 89E8 DEFINE mov_eax,ebx 89D8 DEFINE mov_eax,ebx 89D8 DEFINE mov_eax,edx 89D0 DEFINE mov_ebx,eax 89C3 DEFINE mov_ecx,eax 89C1 DEFINE mov_ecx,esp 89E1 DEFINE mov_edi,esp 89E7 DEFINE mov_ebp,edi 89fd DEFINE mov_ebp,esp 89E5 DEFINE mov_eax, B8 DEFINE mov_ebx, BB DEFINE mov_edx, BA DEFINE mov_eax,[eax] 8B00 DEFINE mov_ebx,[ebx] 8B1B DEFINE mov_ecx,[ecx] 8B09 DEFINE mov_edx,[edx] 8B12 DEFINE mov_[ebx],al 8803 DEFINE mov_[ebx],ax 668903 DEFINE mov_[ebx],eax 8903 DEFINE movsx_eax,BYTE_PTR_[eax] 0FBE00 DEFINE movsx_ebx,BYTE_PTR_[ebx] 0FBE1B DEFINE movsx_eax,WORD_PTR_[eax] 0FBF00 DEFINE movzx_eax,BYTE_PTR_[eax] 0FB600 DEFINE movzx_eax,WORD_PTR_[eax] 0FB700 DEFINE movzx_eax,al 0FB6C0 DEFINE mul_ebx F7E3 DEFINE imul_ebx F7EB DEFINE NULL 00000000 DEFINE not_eax F7D0 DEFINE or_eax,ebx 09D8 DEFINE pop_eax 58 DEFINE pop_ebx 5B DEFINE pop_ebp 5D DEFINE pop_edi 5F DEFINE push_eax 50 DEFINE push_ebx 53 DEFINE push_ebp 55 DEFINE push_edi 57 DEFINE ret C3 DEFINE sal_eax, C1E0 DEFINE sal_eax,cl D3F0 DEFINE shl_eax,cl D3E0 DEFINE sar_eax,cl D3F8 DEFINE shr_eax,cl D3E8 DEFINE seta_al 0F97C0 DEFINE setae_al 0F93C0 DEFINE setb_al 0F92C0 DEFINE setbe_al 0F96C0 DEFINE sete_al 0F94C0 DEFINE setle_al 0F9EC0 DEFINE setl_al 0F9CC0 DEFINE setge_al 0F9DC0 DEFINE setg_al 0F9FC0 DEFINE setne_al 0F95C0 DEFINE sub_ebx,eax 29C3 DEFINE test_eax,eax 85C0 DEFINE xchg_ebx,eax 93 DEFINE xor_eax,ebx 31D8
## Copyright (C) 2016 Jeremiah Orians ## This file is part of M2-Planet. ## ## M2-Planet is free software: you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation, either version 3 of the License, or ## (at your option) any later version. ## ## M2-Planet is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. :_start mov_ebp,esp ; Protect esp ;; Prepare argv lea_eax,[ebp+DWORD] %4 ; ARGV_address = EBP + 4 push_eax ; Put argv on the stack ;; Prepare envp mov_eax,ebp ; Address we need to load from mov_eax,[eax] ; Get ARGC add_eax, %2 ; OFFSET = ARGC + 2 sal_eax, !2 ; OFFSET = OFFSET * WORDSIZE add_eax,ebp ; ENVP_address = ESP + OFFSET push_eax ; Put envp on the stack ;; Stack offset add_ebp, %4 ; Fix ebp ;; Perform the main loop call %FUNCTION_main push_eax ; Put return on stack push_eax ; so that _exit gets the value ;; Exit to kernel :FUNCTION_exit :FUNCTION__exit mov_ebx,eax ; Using the return code given by main mov_eax, %1 ; Syscall exit int !0x80 ; Exit with that code
### Copyright (C) 2016 Jeremiah Orians ### Copyright (C) 2017 Jan Nieuwenhuizen <janneke@gnu.org> ### This file is part of M2-Planet. ### ### M2-Planet is free software: you can redistribute it and/or modify ### it under the terms of the GNU General Public License as published by ### the Free Software Foundation, either version 3 of the License, or ### (at your option) any later version. ### ### M2-Planet is distributed in the hope that it will be useful, ### but WITHOUT ANY WARRANTY; without even the implied warranty of ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ### GNU General Public License for more details. ### ### You should have received a copy of the GNU General Public License ### along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. ### stage0's hex2 format ### !<label> 1 byte relative ### $<label> 2 byte address ### @<label> 2 byte relative ### &<label> 4 byte address ### %<label> 4 byte relative ### if you wish to use this header, you need to add :ELF_end to the end of your ### M1 or hex2 files. ## ELF Header :ELF_base 7F 45 4C 46 # e_ident[EI_MAG0-3] ELF's magic number 01 # e_ident[EI_CLASS] Indicating 32 bit 01 # e_ident[EI_DATA] Indicating little endianness 01 # e_ident[EI_VERSION] Indicating original elf 03 # e_ident[EI_OSABI] Set at 3 because FreeBSD is strict 00 # e_ident[EI_ABIVERSION] See above 00 00 00 00 00 00 00 # e_ident[EI_PAD] 02 00 # e_type Indicating Executable 03 00 # e_machine Indicating 386 01 00 00 00 # e_version Indicating original elf &_start # e_entry Address of the entry point %ELF_program_headers>ELF_base # e_phoff Address of program header table 00 00 00 00 # e_shoff Address of section header table 00 00 00 00 # e_flags 34 00 # e_ehsize Indicating our 52 Byte header 20 00 # e_phentsize size of a program header table 01 00 # e_phnum number of entries in program table 00 00 # e_shentsize size of a section header table 00 00 # e_shnum number of entries in section table 00 00 # e_shstrndx index of the section names :ELF_program_headers :ELF_program_header__text 01 00 00 00 # ph_type: PT-LOAD = 1 00 00 00 00 # ph_offset &ELF_base # ph_vaddr &ELF_base # ph_physaddr %ELF_end>ELF_base # ph_filesz %ELF_end>ELF_base # ph_memsz 07 00 00 00 # ph_flags: PF-X|PF-W|PF-R = 7 01 00 00 00 # ph_alignment :ELF_text
/* -*- c-file-style: "linux";indent-tabs-mode:t -*- */ /* Copyright (C) 2016 Jeremiah Orians * Copyright (C) 2017 Jan Nieuwenhuizen <janneke@gnu.org> * This file is part of mescc-tools. * * mescc-tools is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> #include <stdio.h> #include <string.h> #include "M2libc/bootstrappable.h" /* Internal processing Constants */ // CONSTANT max_string 4096 #define max_string 4096 // CONSTANT PROCESSED 1 #define PROCESSED 1 // CONSTANT STR 2 #define STR 2 // CONSTANT NEWLINE 3 #define NEWLINE 3 /* Unique code for each architecture */ // CONSTANT KNIGHT 0 #define KNIGHT 0 // CONSTANT X86 3 #define X86 0x03 // CONSTANT AMD64 62 #define AMD64 0x3E // CONSTANT ARMV7L 40 #define ARMV7L 0x28 // CONSTANT AARM64 183 #define AARM64 0xB7 // CONSTANT PPC64LE 21 #define PPC64LE 0x15 // CONSTANT RISCV32 243 #define RISCV32 0xF3 // CONSTANT RISCV64 65779 #define RISCV64 0x100F3 /* Because RISC-V unlike all other architectures does get a seperate e_machine when changing from 32 to 64bit */ /* How do you want that output? */ // CONSTANT HEX 16 #define HEX 16 // CONSTANT OCTAL 8 #define OCTAL 8 // CONSTANT BINARY 2 #define BINARY 2 /* Imported from stringify.c */ int stringify(char* s, int digits, int divisor, int value, int shift); void LittleEndian(char* start, int ByteMode); struct blob { struct blob* next; int type; char* Text; char* Expression; struct blob* hash_next; }; struct Token { struct Token* next; struct blob* contents; char* filename; int linenumber; }; /* Globals */ FILE* source_file; FILE* destination_file; int BigEndian; int ByteMode; int Architecture; int linenumber; struct Token* token_list; struct blob* blob_list; struct blob* define_blob; struct blob* newline_blob; int blob_count; char* SCRATCH; struct blob** hash_table; void line_error(char* filename, int linenumber) { fputs(filename, stderr); fputs(":", stderr); fputs(int2str(linenumber,10, FALSE), stderr); fputs(" :", stderr); } void ClearScratch() { int i = 0; int c = SCRATCH[i]; while(0 != c) { SCRATCH[i] = 0; i = i + 1; c = SCRATCH[i]; } } int GetHash(char* s) { int i = 5381; while(0 != s[0]) { i = (i << 5) + i + s[0]; s = s + 1; } return i & 0xFFFF; } struct blob* FindBlob() { int hash = GetHash(SCRATCH); struct blob* i = hash_table[hash]; while(NULL != i) { if(match(SCRATCH, i->Text)) return i; i = i->hash_next; } return NULL; } void AddHash(struct blob* a, char* s) { int i = GetHash(s); a->hash_next = hash_table[i]; hash_table[i] = a; } void NewBlob(int size) { blob_count = blob_count + 1; struct blob* a = calloc(1, sizeof(struct blob)); require(NULL != a, "Exhausted available memory\n"); a->Text = calloc(size + 1, sizeof(char)); require(NULL != a->Text, "Exhausted available memory\n"); int i = 0; while(i <= size) { a->Text[i] = SCRATCH[i]; i = i + 1; } a->next = blob_list; blob_list = a; AddHash(a, SCRATCH); } struct Token* newToken(char* filename, int linenumber) { struct Token* p; p = calloc (1, sizeof (struct Token)); require(NULL != p, "Exhausted available memory\n"); p->filename = filename; p->linenumber = linenumber; return p; } struct Token* reverse_list(struct Token* head) { struct Token* root = NULL; struct Token* next; while(NULL != head) { next = head->next; head->next = root; root = head; head = next; } return root; } void purge_lineComment() { int c = fgetc(source_file); while(!in_set(c, "\n\r")) { if(EOF == c) break; c = fgetc(source_file); } } struct Token* append_newline(struct Token* head, char* filename) { linenumber = linenumber + 1; if(NULL == head) return NULL; if(NEWLINE == head->contents->type) {/* Don't waste whitespace*/ return head; } struct Token* lf = newToken(filename, linenumber); lf->contents = newline_blob; lf->next = head; return lf; } struct Token* store_atom(struct Token* head, char c, char* filename) { ClearScratch(); int ch = c; int i = 0; do { SCRATCH[i] = ch; ch = fgetc(source_file); i = i + 1; if(i >= max_string) { fputs("storing atom of size larger than max_string\n", stderr); line_error(filename, linenumber); fputc('\n', stderr); exit(EXIT_FAILURE); } if(EOF == ch) break; } while (!in_set(ch, "\t\n ")); head->contents = FindBlob(); if(NULL == head->contents) { NewBlob(i); head->contents = blob_list; } if('\n' == ch) { return append_newline(head, filename); } return head; } struct blob* store_string(char c, char* filename) { ClearScratch(); int ch = c; int i = 0; do { SCRATCH[i] = ch; i = i + 1; if('\n' == ch) linenumber = linenumber + 1; ch = fgetc(source_file); require(EOF != ch, "Unmatched \"!\n"); if(max_string == i) { line_error(filename, linenumber); fputs("String: ", stderr); fputs(SCRATCH, stderr); fputs(" exceeds max string size\n", stderr); exit(EXIT_FAILURE); } } while(ch != c); struct blob* a = FindBlob(); if(NULL == a) { NewBlob(i); a = blob_list; a->type = STR; } return a; } struct Token* Tokenize_Line(struct Token* head, char* filename) { int c; struct Token* p; linenumber = 1; do { restart: c = fgetc(source_file); if(in_set(c, ";#")) { purge_lineComment(); head = append_newline(head, filename); goto restart; } if(in_set(c, "\t ")) { goto restart; } if('\n' == c) { head = append_newline(head, filename); goto restart; } if(EOF == c) { head = append_newline(head, filename); goto done; } p = newToken(filename, linenumber); p->next = head; if(in_set(c, "'\"")) { p->contents = store_string(c, filename); } else { p = store_atom(p, c, filename); } head = p; } while(TRUE); done: return head; } void line_macro(struct Token* p) { struct Token* i; for(i = p; NULL != i; i = i->next) { if(define_blob == i->contents) { require(NULL != i->next, "Macro name must exist\n"); require(NULL != i->next->next, "Macro value must exist\n"); if(PROCESSED == i->next->contents->type) { line_error(i->filename, i->linenumber); fputs("Multiple definitions for macro ", stderr); fputs(i->next->contents->Text, stderr); fputs("\n", stderr); exit(EXIT_FAILURE); } i->contents = newline_blob; if (STR == i->next->next->contents->type) { i->contents->Expression = i->next->next->contents->Text + 1; } else { i->next->contents->Expression = i->next->next->contents->Text; } i->next = i->next->next->next; } } } void hexify_string(struct blob* p) { char* table = "0123456789ABCDEF"; int i = strlen(p->Text); int size; if(HEX == ByteMode) size = (((i << 1) + i) + 12); else if(OCTAL == ByteMode) size = (i << 2) + 1; else if(BINARY == ByteMode) size = (i << 3) + i + 1; else size = 1; require(1 != size, "hexify_string lacked a valid bytemode\n"); char* d = calloc(size, sizeof(char)); require(NULL != d, "Exhausted available memory\n"); p->Expression = d; char* S = p->Text; if((KNIGHT == Architecture) && (HEX == ByteMode)) { i = (((((i - 1) >> 2) + 1) << 3) + i); while( 0 < i) { i = i - 1; d[i] = '0'; } } if(HEX == ByteMode) { while(0 != S[0]) { S = S + 1; d[0] = table[S[0] >> 4]; d[1] = table[S[0] & 0xF]; d[2] = ' '; d = d + 3; } } else if(OCTAL == ByteMode) { while(0 != S[0]) { S = S + 1; d[0] = table[S[0] >> 6]; d[1] = table[(S[0] >> 3) & 0x7]; d[2] = table[S[0] & 0x7]; d[3] = ' '; d = d + 4; } } else if(BINARY == ByteMode) { while(0 != S[0]) { S = S + 1; d[0] = table[S[0] >> 7]; d[1] = table[(S[0] >> 6) & 0x1]; d[2] = table[(S[0] >> 5) & 0x1]; d[3] = table[(S[0] >> 4) & 0x1]; d[4] = table[(S[0] >> 3) & 0x1]; d[5] = table[(S[0] >> 2) & 0x1]; d[6] = table[(S[0] >> 1) & 0x1]; d[7] = table[S[0] & 0x1]; d[8] = ' '; d = d + 9; } } } void process_string(struct blob* p) { struct blob* i; for(i = p; NULL != i; i = i->next) { if(STR == i->type) { if('\'' == i->Text[0]) { i->Expression = i->Text + 1; } else if('"' == i->Text[0]) { hexify_string(i); } } } } char* pad_nulls(int size, char* nil) { if(0 == size) return nil; require(size > 0, "negative null padding not possible\n"); if(HEX == ByteMode) size = size * 2; else if (OCTAL == ByteMode) size = size * 3; else if (BINARY == ByteMode) size = size * 8; char* s = calloc(size + 1, sizeof(char)); require(NULL != s, "Exhausted available memory\n"); int i = 0; while(i < size) { s[i] = '0'; i = i + 1; } return s; } void preserve_other(struct blob* p) { struct blob* i; char c; for(i = p; NULL != i; i = i->next) { if((NULL == i->Expression) && !(i->type & PROCESSED)) { c = i->Text[0]; if(in_set(c, "!@$~%&:^")) { i->Expression = i->Text; } else if('<' == c) { i->Expression = pad_nulls(strtoint(i->Text + 1), i->Text); } } } } void bound_values(int displacement, int number_of_bytes, int low, int high) { if((high < displacement) || (displacement < low)) { fputs("A displacement of ", stderr); fputs(int2str(displacement, 10, TRUE), stderr); fputs(" does not fit in ", stderr); fputs(int2str(number_of_bytes, 10, TRUE), stderr); fputs(" bytes\n", stderr); exit(EXIT_FAILURE); } } void range_check(int displacement, int number_of_bytes) { if(4 == number_of_bytes) return; else if(3 == number_of_bytes) { bound_values(displacement, number_of_bytes, -8388608, 16777216); return; } else if(2 == number_of_bytes) { bound_values(displacement, number_of_bytes, -32768, 65535); return; } else if(1 == number_of_bytes) { bound_values(displacement, number_of_bytes, -128, 255); return; } fputs("Received an invalid number of bytes in range_check\n", stderr); exit(EXIT_FAILURE); } char* express_number(int value, char c) { char* ch = calloc(42, sizeof(char)); require(NULL != ch, "Exhausted available memory\n"); int size; int number_of_bytes; int shift; if('!' == c) { number_of_bytes = 1; value = value & 0xFF; } else if('@' == c) { number_of_bytes = 2; value = value & 0xFFFF; } else if('~' == c) { number_of_bytes = 3; value = value & 0xFFFFFF; } else if('%' == c) { number_of_bytes = 4; value = value & 0xFFFFFFFF; } else { fputs("Given symbol ", stderr); fputc(c, stderr); fputs(" to express immediate value ", stderr); fputs(int2str(value, 10, TRUE), stderr); fputc('\n', stderr); exit(EXIT_FAILURE); } range_check(value, number_of_bytes); if(HEX == ByteMode) { size = number_of_bytes * 2; shift = 4; } else if(OCTAL == ByteMode) { size = number_of_bytes * 3; shift = 3; } else if(BINARY == ByteMode) { size = number_of_bytes * 8; shift = 1; } else { fputs("Got invalid ByteMode in express_number\n", stderr); exit(EXIT_FAILURE); } stringify(ch, size, ByteMode, value, shift); if(!BigEndian) LittleEndian(ch, ByteMode); return ch; } char* express_word(int value, char c) { char* s = calloc(43, sizeof(char)); s[0] = '.'; char* ch = s + 1; require(NULL != ch, "Exhausted available memory\n"); int size; int shift; int immediate; if('!' == c) { /* Corresponds to RISC-V I format */ immediate = (value & 0xFFF) << 20; } else if('@' == c) { /* Corresponds to RISC-V S format */ immediate = ((value & 0x1F) << 7) | ((value & 0xFE0) << (31 - 11)); } else if('~' == c) { /* Corresponds with RISC-V U format */ if ((value & 0xFFF) < 0x800) { immediate = value & 0xFFFFF000; } else { immediate = (value & 0xFFFFF000) + 0x1000; } } else if('%' == c) { /* provides an option for 32bit immediate constants */ immediate = value & 0xFFFFFFFF; /* Drop the leading . */ ch = s; } else { fputs("Given symbol ", stderr); fputc(c, stderr); fputs(" to express immediate value ", stderr); fputs(int2str(value, 10, TRUE), stderr); fputc('\n', stderr); exit(EXIT_FAILURE); } if(HEX == ByteMode) { size = 4 * 2; shift = 4; } else if(OCTAL == ByteMode) { size = 4 * 3; shift = 3; } else if(BINARY == ByteMode) { size = 4 * 8; shift = 1; } else { fputs("Got invalid ByteMode in express_number\n", stderr); exit(EXIT_FAILURE); } stringify(ch, size, ByteMode, immediate, shift); if(!BigEndian) LittleEndian(ch, ByteMode); return s; } void eval_immediates(struct blob* p) { struct blob* i; int value; for(i = p; NULL != i; i = i->next) { if(PROCESSED == i->type) continue; else if(NEWLINE == i->type) continue; else if('<' == i->Text[0]) continue; else if(NULL == i->Expression) { if((X86 == Architecture) || (AMD64 == Architecture) || (ARMV7L == Architecture) || (AARM64 == Architecture) || (PPC64LE == Architecture)) { if(in_set(i->Text[0], "%~@!")) { value = strtoint(i->Text + 1); if(('0' == i->Text[1]) || (0 != value)) { i->Expression = express_number(value, i->Text[0]); } } } else if((RISCV32 == Architecture) || (RISCV64 == Architecture)) { if(in_set(i->Text[0], "%~@!")) { value = strtoint(i->Text + 1); if(('0' == i->Text[1]) || (0 != value)) { i->Expression = express_word(value, i->Text[0]); } } } else if(KNIGHT == Architecture) { value = strtoint(i->Text); if(('0' == i->Text[0]) || (0 != value)) { i->Expression = express_number(value, '@'); } } else { fputs("Unknown architecture received in eval_immediates\n", stderr); exit(EXIT_FAILURE); } } } } void print_hex(struct Token* p) { struct Token* i; for(i = p; NULL != i; i = i->next) { if(NEWLINE == i->contents->type) { if(NULL == i->next) fputc('\n', destination_file); else if(NEWLINE != i->next->contents->type) fputc('\n', destination_file); } else if(NULL != i->contents->Expression) { fputs(i->contents->Expression, destination_file); if(NEWLINE != i->next->contents->type) fputc(' ', destination_file); } else { line_error(i->filename, i->linenumber); fputs("Received invalid other; ", stderr); fputs(i->contents->Text, stderr); fputs("\n", stderr); exit(EXIT_FAILURE); } } } /* Standard C main program */ int main(int argc, char **argv) { BigEndian = TRUE; Architecture = KNIGHT; destination_file = stdout; ByteMode = HEX; char* filename; char* arch; blob_count = 2; hash_table = calloc(65537, sizeof(struct blob*)); require(NULL != hash_table, "failed to allocate hash_table\n"); /* Create newline blob */ newline_blob = calloc(1, sizeof(struct blob)); require(NULL != newline_blob, "failed to allocate newline_blob\n"); newline_blob->Text = "\n"; newline_blob->Expression = "\n"; newline_blob->type = NEWLINE; AddHash(newline_blob, "\n"); /* Start the blob list with DEFINE and newline */ blob_list = calloc(1, sizeof(struct blob)); require(NULL != blob_list, "failed to allocate DEFINE blob\n"); blob_list->Text = "DEFINE"; define_blob = blob_list; blob_list->next = newline_blob; AddHash(define_blob, "DEFINE"); /* Initialize scratch */ SCRATCH = calloc(max_string + 1, sizeof(char)); require(NULL != SCRATCH, "failed to allocate SCRATCH buffer"); int option_index = 1; while(option_index <= argc) { if(NULL == argv[option_index]) { option_index = option_index + 1; } else if(match(argv[option_index], "--big-endian")) { BigEndian = TRUE; option_index = option_index + 1; } else if(match(argv[option_index], "--little-endian")) { BigEndian = FALSE; option_index = option_index + 1; } else if(match(argv[option_index], "-A") || match(argv[option_index], "--architecture")) { arch = argv[option_index + 1]; if(match("knight-native", arch) || match("knight-posix", arch)) Architecture = KNIGHT; else if(match("x86", arch)) Architecture = X86; else if(match("amd64", arch)) Architecture = AMD64; else if(match("armv7l", arch)) Architecture = ARMV7L; else if(match("aarch64", arch)) Architecture = AARM64; else if(match("ppc64le", arch)) Architecture = PPC64LE; else if(match("riscv32", arch)) Architecture = RISCV32; else if(match("riscv64", arch)) Architecture = RISCV64; else { fputs("Unknown architecture: ", stderr); fputs(arch, stderr); fputs(" know values are: knight-native, knight-posix, x86, amd64, armv7l, aarch64, ppc64le, riscv32 and riscv64", stderr); exit(EXIT_FAILURE); } option_index = option_index + 2; } else if(match(argv[option_index], "-b") || match(argv[option_index], "--binary")) { ByteMode = BINARY; option_index = option_index + 1; } else if(match(argv[option_index], "-h") || match(argv[option_index], "--help")) { fputs("Usage: ", stderr); fputs(argv[0], stderr); fputs(" --file FILENAME1 {-f FILENAME2} (--big-endian|--little-endian) ", stderr); fputs("[--architecture name]\nArchitectures: knight-native, knight-posix, x86, amd64, armv7, riscv32 and riscv64\n", stderr); fputs("To leverage octal or binary output: --octal, --binary\n", stderr); exit(EXIT_SUCCESS); } else if(match(argv[option_index], "-f") || match(argv[option_index], "--file")) { filename = argv[option_index + 1]; source_file = fopen(filename, "r"); if(NULL == source_file) { fputs("The file: ", stderr); fputs(argv[option_index + 1], stderr); fputs(" can not be opened!\n", stderr); exit(EXIT_FAILURE); } token_list = Tokenize_Line(token_list, filename); fclose(source_file); option_index = option_index + 2; } else if(match(argv[option_index], "-o") || match(argv[option_index], "--output")) { destination_file = fopen(argv[option_index + 1], "w"); if(NULL == destination_file) { fputs("The file: ", stderr); fputs(argv[option_index + 1], stderr); fputs(" can not be opened!\n", stderr); exit(EXIT_FAILURE); } option_index = option_index + 2; } else if(match(argv[option_index], "-O") || match(argv[option_index], "--octal")) { ByteMode = OCTAL; option_index = option_index + 1; } else if(match(argv[option_index], "-V") || match(argv[option_index], "--version")) { fputs("M1 1.4.0\n", stdout); exit(EXIT_SUCCESS); } else { fputs("Unknown option\n", stderr); exit(EXIT_FAILURE); } } if(NULL == token_list) { fputs("Either no input files were given or they were empty\n", stderr); exit(EXIT_FAILURE); } token_list = reverse_list(token_list); line_macro(token_list); process_string(blob_list); eval_immediates(blob_list); preserve_other(blob_list); print_hex(token_list); fclose(destination_file); return EXIT_SUCCESS; }
### Copyright (C) 2016 Jeremiah Orians ### Copyright (C) 2017 Jan Nieuwenhuizen <janneke@gnu.org> ### This file is part of M2-Planet. ### ### M2-Planet is free software: you can redistribute it and/or modify ### it under the terms of the GNU General Public License as published by ### the Free Software Foundation, either version 3 of the License, or ### (at your option) any later version. ### ### M2-Planet is distributed in the hope that it will be useful, ### but WITHOUT ANY WARRANTY; without even the implied warranty of ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ### GNU General Public License for more details. ### ### You should have received a copy of the GNU General Public License ### along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. ### stage0's hex2 format ### !<label> 1 byte relative ### $<label> 2 byte address ### @<label> 2 byte relative ### &<label> 4 byte address ### %<label> 4 byte relative ### if you wish to use this header, you need to add :ELF_end to the end of your ### M1 or hex2 files. ## ELF Header :ELF_base 7F 45 4C 46 # e_ident[EI_MAG0-3] ELF's magic number 01 # e_ident[EI_CLASS] Indicating 32 bit 01 # e_ident[EI_DATA] Indicating little endianness 01 # e_ident[EI_VERSION] Indicating original elf 03 # e_ident[EI_OSABI] Set at 3 because FreeBSD is strict 00 # e_ident[EI_ABIVERSION] See above 00 00 00 00 00 00 00 # e_ident[EI_PAD] 02 00 # e_type Indicating Executable 03 00 # e_machine Indicating 386 01 00 00 00 # e_version Indicating original elf &_start # e_entry Address of the entry point %ELF_program_headers>ELF_base # e_phoff Address of program header table %ELF_section_headers>ELF_base # e_shoff Address of section header table 00 00 00 00 # e_flags 34 00 # e_ehsize Indicating our 52 Byte header 20 00 # e_phentsize size of a program header table 01 00 # e_phnum number of entries in program table 28 00 # e_shentsize size of a section header table 05 00 # e_shnum number of entries in section table 02 00 # e_shstrndx index of the section names :ELF_program_headers :ELF_program_header__text 01 00 00 00 # ph_type: PT-LOAD = 1 00 00 00 00 # ph_offset &ELF_base # ph_vaddr &ELF_base # ph_physaddr %ELF_end>ELF_base # ph_filesz %ELF_end>ELF_base # ph_memsz 07 00 00 00 # ph_flags: PF-X|PF-W|PF-R = 7 01 00 00 00 # ph_alignment :ELF_text
/* Copyright (C) 2016 Jeremiah Orians * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #ifndef _SYS_TYPES_H #define _SYS_TYPES_H #ifndef __M2__ #include "../gcc_req.h" #endif typedef SCM ulong; typedef long ssize_t; typedef int pid_t; typedef long intptr_t; typedef ulong uintptr_t; typedef long clock_t; typedef int mode_t; typedef long dev_t; #endif
/* Copyright (C) 2016 Jeremiah Orians * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #ifndef _STDDEF_H #define _STDDEF_H #include <sys/types.h> #define NULL 0 typedef long ptrdiff_t; typedef ulong size_t; #endif
/* Copyright (C) 2020 Jeremiah Orians * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #ifndef _UNISTD_C #define _UNISTD_C #define NULL 0 #define __PATH_MAX 4096 void* malloc(unsigned size); int access(char* pathname, int mode) { asm("lea_ebx,[esp+DWORD] %8" "mov_ebx,[ebx]" "lea_ecx,[esp+DWORD] %4" "mov_ecx,[ecx]" "mov_eax, %33" "int !0x80"); } int chdir(char* path) { asm("lea_ebx,[esp+DWORD] %4" "mov_ebx,[ebx]" "mov_eax, %12" "int !0x80"); } int fchdir(int fd) { asm("lea_ebx,[esp+DWORD] %4" "mov_ebx,[ebx]" "mov_eax, %133" "int !0x80"); } /* Defined in the libc */ void _exit(int value); int fork() { asm("mov_eax, %2" "mov_ebx, %0" "int !0x80"); } int waitpid (int pid, int* status_ptr, int options) { asm("lea_ebx,[esp+DWORD] %12" "mov_ebx,[ebx]" "lea_ecx,[esp+DWORD] %8" "mov_ecx,[ecx]" "lea_edx,[esp+DWORD] %4" "mov_edx,[edx]" "mov_eax, %7" "int !0x80"); } int execve(char* file_name, char** argv, char** envp) { asm("lea_ebx,[esp+DWORD] %12" "mov_ebx,[ebx]" "lea_ecx,[esp+DWORD] %8" "mov_ecx,[ecx]" "lea_edx,[esp+DWORD] %4" "mov_edx,[edx]" "mov_eax, %11" "int !0x80"); } int read(int fd, char* buf, unsigned count) { asm("lea_ebx,[esp+DWORD] %12" "mov_ebx,[ebx]" "lea_ecx,[esp+DWORD] %8" "mov_ecx,[ecx]" "lea_edx,[esp+DWORD] %4" "mov_edx,[edx]" "mov_eax, %3" "int !0x80"); } int write(int fd, char* buf, unsigned count) { asm("lea_ebx,[esp+DWORD] %12" "mov_ebx,[ebx]" "lea_ecx,[esp+DWORD] %8" "mov_ecx,[ecx]" "lea_edx,[esp+DWORD] %4" "mov_edx,[edx]" "mov_eax, %4" "int !0x80"); } int lseek(int fd, int offset, int whence) { asm("lea_ebx,[esp+DWORD] %12" "mov_ebx,[ebx]" "lea_ecx,[esp+DWORD] %8" "mov_ecx,[ecx]" "lea_edx,[esp+DWORD] %4" "mov_edx,[edx]" "mov_eax, %19" "int !0x80"); } int close(int fd) { asm("lea_ebx,[esp+DWORD] %4" "mov_ebx,[ebx]" "mov_eax, %6" "int !0x80"); } int unlink (char *filename) { asm("lea_ebx,[esp+DWORD] %4" "mov_ebx,[ebx]" "mov_eax, %10" "int !0x80"); } int _getcwd(char* buf, int size) { asm("lea_ebx,[esp+DWORD] %8" "mov_ebx,[ebx]" "lea_ecx,[esp+DWORD] %4" "mov_ecx,[ecx]" "mov_eax, %183" "int !0x80"); } char* getcwd(char* buf, unsigned size) { int c = _getcwd(buf, size); if(0 == c) return NULL; return buf; } char* getwd(char* buf) { return getcwd(buf, __PATH_MAX); } char* get_current_dir_name() { return getcwd(malloc(__PATH_MAX), __PATH_MAX); } int brk(void *addr) { asm("mov_eax,[esp+DWORD] %4" "push_eax" "mov_eax, %45" "pop_ebx" "int !0x80"); } struct utsname { char sysname[65]; /* Operating system name (e.g., "Linux") */ char nodename[65]; /* Name within "some implementation-defined network" */ char release[65]; /* Operating system release (e.g., "2.6.28") */ char version[65]; /* Operating system version */ char machine[65]; /* Hardware identifier */ }; int uname(struct utsname* unameData) { asm("lea_ebx,[esp+DWORD] %4" "mov_ebx,[ebx]" "mov_eax, %109" "int !0x80"); } #endif
/* Copyright (C) 2016 Jeremiah Orians * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __FCNTL_C #define __FCNTL_C #define O_RDONLY 0 #define O_WRONLY 1 #define O_RDWR 2 #define O_CREAT 00100 #define O_EXCL 00200 #define O_TRUNC 001000 #define O_APPEND 002000 #define S_IXUSR 00100 #define S_IWUSR 00200 #define S_IRUSR 00400 #define S_IRWXU 00700 int _open(char* name, int flag, int mode) { asm("lea_ebx,[esp+DWORD] %12" "mov_ebx,[ebx]" "lea_ecx,[esp+DWORD] %8" "mov_ecx,[ecx]" "lea_edx,[esp+DWORD] %4" "mov_edx,[edx]" "mov_eax, %5" "int !0x80"); } #define STDIN_FILENO 0 #define STDOUT_FILENO 1 #define STDERR_FILENO 2 #endif
/* Copyright (C) 2016 Jeremiah Orians * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #ifndef _FCNTL_C #define _FCNTL_C #ifdef __M2__ #if __uefi__ #include <uefi/fcntl.c> #elif __i386__ #include <x86/linux/fcntl.c> #elif __x86_64__ #include <amd64/linux/fcntl.c> #elif __arm__ #include <armv7l/linux/fcntl.c> #elif __aarch64__ #include <aarch64/linux/fcntl.c> #elif __riscv && __riscv_xlen==32 #include <riscv32/linux/fcntl.c> #elif __riscv && __riscv_xlen==64 #include <riscv64/linux/fcntl.c> #elif __knight_posix__ #include <knight/linux/fcntl.c> #elif __knight__ #include <knight/native/fcntl.c> #else #error arch not supported #endif #else extern int _open(char* name, int flag, int mode); #endif int errno; int open(char* name, int flag, int mode) { int fd = _open(name, flag, mode); if(0 > fd) { errno = -fd; fd = -1; } return fd; } #endif
/* Copyright (C) 2020 Jeremiah Orians * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #ifndef _SYS_STAT_C #define _SYS_STAT_C #include <sys/types.h> #define S_IRWXU 00700 #define S_IXUSR 00100 #define S_IWUSR 00200 #define S_IRUSR 00400 #define S_ISUID 04000 #define S_ISGID 02000 #define S_IXGRP 00010 #define S_IXOTH 00001 #define S_IRGRP 00040 #define S_IROTH 00004 #define S_IWGRP 00020 #define S_IWOTH 00002 #define S_IRWXG 00070 #define S_IRWXO 00007 int chmod(char *pathname, int mode) { asm("lea_ebx,[esp+DWORD] %8" "mov_ebx,[ebx]" "lea_ecx,[esp+DWORD] %4" "mov_ecx,[ecx]" "mov_eax, %15" "int !0x80"); } int fchmod(int a, mode_t b) { asm("lea_ebx,[esp+DWORD] %8" "mov_ebx,[ebx]" "lea_ecx,[esp+DWORD] %4" "mov_ecx,[ecx]" "mov_eax, %94" "int !0x80"); } int mkdir(char const* a, mode_t b) { asm("lea_ebx,[esp+DWORD] %8" "mov_ebx,[ebx]" "lea_ecx,[esp+DWORD] %4" "mov_ecx,[ecx]" "mov_eax, %39" "int !0x80"); } int mknod(char const* a, mode_t b, dev_t c) { asm("lea_ebx,[esp+DWORD] %12" "mov_ebx,[ebx]" "lea_ecx,[esp+DWORD] %8" "mov_ecx,[ecx]" "lea_edx,[esp+DWORD] %4" "mov_edx,[edx]" "mov_eax, %14" "int !0x80"); } mode_t umask(mode_t m) { asm("lea_ebx,[esp+DWORD] %4" "mov_ebx,[ebx]" "mov_eax, %60" "int !0x80"); } #endif
/* Copyright (C) 2016 Jeremiah Orians * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #include <unistd.h> #include <sys/stat.h> #include <fcntl.h> #define EXIT_FAILURE 1 #define EXIT_SUCCESS 0 #define _IN_USE 1 #define _NOT_IN_USE 0 typedef char wchar_t; void exit(int value); struct _malloc_node { struct _malloc_node *next; void* block; size_t size; int used; }; struct _malloc_node* _allocated_list; struct _malloc_node* _free_list; /******************************** * The core POSIX malloc * ********************************/ long _malloc_ptr; long _brk_ptr; void* _malloc_brk(unsigned size) { if(NULL == _brk_ptr) { _brk_ptr = brk(0); _malloc_ptr = _brk_ptr; } if(_brk_ptr < _malloc_ptr + size) { _brk_ptr = brk(_malloc_ptr + size); if(-1 == _brk_ptr) return 0; } long old_malloc = _malloc_ptr; _malloc_ptr = _malloc_ptr + size; return old_malloc; } void __init_malloc() { _free_list = NULL; _allocated_list = NULL; return; } /************************************************************************ * Handle with the tricky insert behaviors for our nodes * * As free lists must be sorted from smallest to biggest to enable * * cheap first fit logic * * The free function however is rarely called, so it can kick sand and * * do things the hard way * ************************************************************************/ void _malloc_insert_block(struct _malloc_node* n, int used) { /* Allocated block doesn't care about order */ if(_IN_USE == used) { /* Literally just be done as fast as possible */ n->next = _allocated_list; _allocated_list = n; return; } /* sanity check garbage */ if(_NOT_IN_USE != used) exit(EXIT_FAILURE); if(_NOT_IN_USE != n->used) exit(EXIT_FAILURE); if(NULL != n->next) exit(EXIT_FAILURE); /* Free block really does care about order */ struct _malloc_node* i = _free_list; struct _malloc_node* last = NULL; while(NULL != i) { /* sort smallest to largest */ if(n->size <= i->size) { /* Connect */ n->next = i; /* If smallest yet */ if(NULL == last) _free_list = n; /* or just another average block */ else last->next = n; return; } /* iterate */ last = i; i = i->next; } /* looks like we are the only one */ if(NULL == last) _free_list = n; /* or we are the biggest yet */ else last->next = n; } /************************************************************************ * We only mark a block as unused, we don't actually deallocate it here * * But rather shove it into our _free_list * ************************************************************************/ void free(void* ptr) { /* just in case someone needs to quickly turn it off */ #ifndef _MALLOC_DISABLE_FREE struct _malloc_node* i = _allocated_list; struct _malloc_node* last = NULL; /* walk the whole freaking list if needed to do so */ while(NULL != i) { /* did we find it? */ if(i->block == ptr) { /* detach the block */ if(NULL == last) _allocated_list = i->next; /* in a way that doesn't break the allocated list */ else last->next = i->next; /* insert into free'd list */ i->used = _NOT_IN_USE; i->next = NULL; _malloc_insert_block(i, _NOT_IN_USE); return; } /* iterate */ last = i; i = i->next; } /* we received a pointer to a block that wasn't allocated */ /* Bail *HARD* because I don't want to cover this edge case */ exit(EXIT_FAILURE); #endif /* if free is disabled, there is nothing to do */ return; } /************************************************************************ * find if there is any "FREED" blocks big enough to sit on our memory * * budget's face and ruin its life. Respectfully of course * ************************************************************************/ void* _malloc_find_free(unsigned size) { struct _malloc_node* i = _free_list; struct _malloc_node* last = NULL; /* Walk the whole list if need be */ while(NULL != i) { /* see if anything in it is equal or bigger than what I need */ if((_NOT_IN_USE == i->used) && (i->size > size)) { /* disconnect from list ensuring we don't break free doing so */ if(NULL == last) _free_list = i->next; else last->next = i->next; /* insert into allocated list */ i->used = _IN_USE; i->next = NULL; _malloc_insert_block(i, _IN_USE); return i->block; } /* iterate (will loop forever if you get this wrong) */ last = i; i = i->next; } /* Couldn't find anything big enough */ return NULL; } /************************************************************************ * Well we couldn't find any memory good enough to satisfy our needs so * * we are going to have to go beg for some memory on the street corner * ************************************************************************/ void* _malloc_add_new(unsigned size) { struct _malloc_node* n; #ifdef __uefi__ n = _malloc_uefi(sizeof(struct _malloc_node)); /* Check if we were beaten */ if(NULL == n) return NULL; n->block = _malloc_uefi(size); #else n = _malloc_brk(sizeof(struct _malloc_node)); /* Check if we were beaten */ if(NULL == n) return NULL; n->block = _malloc_brk(size); #endif /* check if we were robbed */ if(NULL == n->block) return NULL; /* Looks like we made it home safely */ n->size = size; n->next = NULL; n->used = _IN_USE; /* lets pop the cork and party */ _malloc_insert_block(n, _IN_USE); return n->block; } /************************************************************************ * Safely iterates over all malloc nodes and frees them * ************************************************************************/ void __malloc_node_iter(struct _malloc_node* node, FUNCTION _free) { struct _malloc_node* current; while(node != NULL) { current = node; node = node->next; _free(current->block); _free(current); } } /************************************************************************ * Runs a callback with all previously allocated nodes. * * This can be useful if operating system does not do any clean up. * ************************************************************************/ void* _malloc_release_all(FUNCTION _free) { __malloc_node_iter(_allocated_list, _free); __malloc_node_iter(_free_list, _free); } /************************************************************************ * Provide a POSIX standardish malloc function to keep things working * ************************************************************************/ void* malloc(unsigned size) { /* skip allocating nothing */ if(0 == size) return NULL; /* use one of the standard block sizes */ size_t max = 1 << 30; size_t used = 256; while(used < size) { used = used << 1; /* fail big allocations */ if(used > max) return NULL; } /* try the cabinets around the house */ void* ptr = _malloc_find_free(used); /* looks like we need to get some more from the street corner */ if(NULL == ptr) { ptr = _malloc_add_new(used); } /* hopefully you can handle NULL pointers, good luck */ return ptr; } /************************************************************************ * Provide a POSIX standardish memset function to keep things working * ************************************************************************/ void* memset(void* ptr, int value, int num) { char* s; /* basically walk the block 1 byte at a time and set it to any value you want */ for(s = ptr; 0 < num; num = num - 1) { s[0] = value; s = s + 1; } return ptr; } /************************************************************************ * Provide a POSIX standardish calloc function to keep things working * ************************************************************************/ void* calloc(int count, int size) { /* if things get allocated, we are good*/ void* ret = malloc(count * size); /* otherwise good luck */ if(NULL == ret) return NULL; memset(ret, 0, (count * size)); return ret; } /* USED EXCLUSIVELY BY MKSTEMP */ void __set_name(char* s, int i) { s[5] = '0' + (i % 10); i = i / 10; s[4] = '0' + (i % 10); i = i / 10; s[3] = '0' + (i % 10); i = i / 10; s[2] = '0' + (i % 10); i = i / 10; s[1] = '0' + (i % 10); i = i / 10; s[0] = '0' + i; } /************************************************************************ * Provide a POSIX standardish mkstemp function to keep things working * ************************************************************************/ int mkstemp(char *template) { /* get length of template */ int i = 0; while(0 != template[i]) i = i + 1; i = i - 1; /* String MUST be more than 6 characters in length */ if(i < 6) return -1; /* Sanity check the string matches the template requirements */ int count = 6; int c; while(count > 0) { c = template[i]; /* last 6 chars must be X */ if('X' != c) return -1; template[i] = '0'; i = i - 1; count = count - 1; } int fd = -1; count = -1; /* open will return -17 or other values */ while(0 > fd) { /* Just give up after the planet has blown up */ if(9000 < count) return -1; /* Try up to 9000 unique filenames before stopping */ count = count + 1; __set_name(template+i+1, count); /* Pray we can */ fd = open(template, O_RDWR | O_CREAT | O_EXCL, 00600); } /* well that only took count many tries */ return fd; } /************************************************************************ * wcstombs - convert a wide-character string to a multibyte string * * because seriously UEFI??? UTF-16 is a bad design choice but I guess * * they were drinking pretty hard when they designed UEFI; it is DOS * * but somehow they magically found ways of making it worse * ************************************************************************/ size_t wcstombs(char* dest, char* src, size_t n) { int i = 0; do { /* UTF-16 is 2bytes per char and that first byte maps good enough to ASCII */ dest[i] = src[2 * i]; if(dest[i] == 0) { break; } i = i + 1; n = n - 1; } while (n != 0); return i; } /************************************************************************ * getenv - get an environmental variable * ************************************************************************/ size_t _strlen(char const* str) { size_t i = 0; while(0 != str[i]) i = i + 1; return i; } int _strncmp(char const* lhs, char const* rhs, size_t count) { size_t i = 0; while(count > i) { if(0 == lhs[i]) break; if(lhs[i] != rhs[i]) return lhs[i] - rhs[i]; i = i + 1; } return 0; } char** _envp; char* getenv (char const* name) { char** p = _envp; char* q; int length = _strlen(name); while (p[0] != 0) { if(_strncmp(name, p[0], length) == 0) { q = p[0] + length; if(q[0] == '=') return q + 1; } p += sizeof(char**); /* M2 pointer arithemtic */ } return 0; } /************************************************************************ * setenv - set an environmental variable * ************************************************************************/ char* _strcpy(char* dest, char const* src) { int i = 0; while (0 != src[i]) { dest[i] = src[i]; i = i + 1; } dest[i] = 0; return dest; } int setenv(char const *s, char const *v, int overwrite_p) { char** p = _envp; int length = _strlen(s); char* q; while (p[0] != 0) { if (_strncmp (s, p[0], length) == 0) { q = p[0] + length; if (q[0] == '=') break; } p += sizeof(char**); /* M2 pointer arithemtic */ } char *entry = malloc (length + _strlen(v) + 2); int end_p = p[0] == 0; p[0] = entry; _strcpy(entry, s); _strcpy(entry + length, "="); _strcpy(entry + length + 1, v); entry[length + _strlen(v) + 2] = 0; if (end_p != 0) p[1] = 0; return 0; }
/* Copyright (C) 2016 Jeremiah Orians * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #ifndef _STDIO_H #define _STDIO_H #ifdef __M2__ /* Actual format of FILE */ struct __IO_FILE { int fd; int bufmode; /* O_RDONLY = 0, O_WRONLY = 1 */ int bufpos; int file_pos; int buflen; char* buffer; struct __IO_FILE* next; struct __IO_FILE* prev; }; /* Now give us the FILE we all love */ typedef struct __IO_FILE FILE; #include <stdio.c> #else #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> /* Required constants */ /* For file I/O*/ #define EOF -1 #define BUFSIZ 4096 /* For lseek */ #define SEEK_SET 0 #define SEEK_CUR 1 #define SEEK_END 2 /* Actual format of FILE */ struct __IO_FILE { int fd; int bufmode; /* 0 = no buffer, 1 = read, 2 = write */ int bufpos; int buflen; char* buffer; }; /* Now give us the FILE we all love */ typedef struct __IO_FILE FILE; /* Required variables */ extern FILE* stdin; extern FILE* stdout; extern FILE* stderr; /* Standard C functions */ /* Getting */ extern int fgetc(FILE* f); extern int getchar(); extern char* fgets(char* str, int count, FILE* stream); extern size_t fread( void* buffer, size_t size, size_t count, FILE* stream ); /* Putting */ extern void fputc(char s, FILE* f); extern void putchar(char s); extern int fputs(char const* str, FILE* stream); extern int puts(char const* str); extern size_t fwrite(void const* buffer, size_t size, size_t count, FILE* stream ); /* File management */ extern FILE* fopen(char const* filename, char const* mode); extern int fclose(FILE* stream); extern int fflush(FILE* stream); /* File Positioning */ extern int ungetc(int ch, FILE* stream); extern long ftell(FILE* stream); extern int fseek(FILE* f, long offset, int whence); extern void rewind(FILE* f); #endif #endif
/* Copyright (C) 2016 Jeremiah Orians * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #include <stddef.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> /* Required constants */ /* For file I/O*/ #define EOF 0xFFFFFFFF #define BUFSIZ 0x1000 /* For lseek */ #define SEEK_SET 0 #define SEEK_CUR 1 #define SEEK_END 2 /* Required variables */ FILE* stdin; FILE* stdout; FILE* stderr; FILE* __list; void __init_io() { __list = NULL; stdin = calloc(1, sizeof(FILE)); stdin->fd = STDIN_FILENO; stdin->bufmode = O_RDONLY; stdin->buflen = 1; stdin->buffer = calloc(2, sizeof(char)); stdout = calloc(1, sizeof(FILE)); stdout->fd = STDOUT_FILENO; stdout->bufmode = O_WRONLY; stdout->buflen = 512; stdout->buffer = calloc(514, sizeof(char)); stderr = calloc(1, sizeof(FILE)); stderr->fd = STDERR_FILENO; stderr->bufmode = O_WRONLY; stderr->buflen = 512; stderr->buffer = calloc(514, sizeof(char)); } /* Flush all IO on exit */ int fflush(FILE* stream); void __kill_io() { fflush(stdout); fflush(stderr); while(NULL != __list) { fflush(__list); __list = __list->next; } } /* Standard C functions */ /* Getting */ int read(int fd, char* buf, unsigned count); int fgetc(FILE* f) { /* Only read on read buffers */ if(O_WRONLY == f->bufmode) return EOF; /* Deal with stdin */ if(STDIN_FILENO == f->fd) { f->bufpos = 0; int r = read(f->fd, f->buffer, 1); /* Catch special case of STDIN gets nothing (AN EOF) */ if(0 == r) return EOF; } /* Catch EOF */ if(f->buflen <= f->bufpos) return EOF; /* Deal with standard case */ int ret = f->buffer[f->bufpos]; f->bufpos = f->bufpos + 1; /* Ensure 0xFF doesn't return EOF */ return (ret & 0xFF); } size_t fread( void* buffer, size_t size, size_t count, FILE* stream ) { if(0 == size) return 0; if(0 == count) return 0; long n = size + count - 1; char* p = buffer; long i; unsigned c; for(i = 0; i < n; i = i + 1) { c = fgetc(stream); if(EOF == c) return (i/size); p[i] = c; } return (i/size); } int getchar() { return fgetc(stdin); } char* fgets(char* str, int count, FILE* stream) { int i = 0; int ch; while(i < count) { ch = fgetc(stream); if(EOF == ch) break; str[i] = ch; i = i + 1; if('\n' == ch) break; } return str; } /* Putting */ void fputc(char s, FILE* f) { /* Only write on write buffers */ if(O_RDONLY == f->bufmode) return; /* Add to buffer */ f->buffer[f->bufpos] = s; f->bufpos = f->bufpos + 1; /* Flush if full or '\n' */ if(f->bufpos == f->buflen) fflush(f); else if(('\n' == s) && (2 >= f->fd)) fflush(f); } size_t fwrite(void const* buffer, size_t size, size_t count, FILE* stream ) { long n = size * count; if(0 == n) return 0; char* p = buffer; int c; long i; for(i=0; i < n; i = i + 1) { c = p[i]; fputc(c, stream); } return (i/size); } void putchar(char s) { fputc(s, stdout); } int fputs(char const* str, FILE* stream) { while(0 != str[0]) { fputc(str[0], stream); str = str + 1; } return 0; } int puts(char const* str) { fputs(str, stdout); fputc('\n', stdout); return 0; } int lseek(int fd, int offset, int whence); /* File management */ FILE* fopen(char const* filename, char const* mode) { int f; FILE* fi = calloc(1, sizeof(FILE)); fi->next = __list; if(NULL != __list) __list->prev = fi; __list = fi; int size; if('w' == mode[0]) f = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 00600); else f = open(filename, 0, 0); /* Everything else is a read */ /* Negative numbers are error codes */ if(0 > f) { return 0; } if('w' == mode[0]) { /* Buffer as much as possible */ fi->buffer = malloc(BUFSIZ * sizeof(char)); fi->buflen = BUFSIZ; fi->bufmode = O_WRONLY; } else { /* Get enough buffer to read it all */ size = lseek(f, 0, SEEK_END); fi->buffer = malloc((size + 1) * sizeof(char)); fi->buflen = size; fi->bufmode = O_RDONLY; /* Now read it all */ lseek(f, 0, SEEK_SET); read(f, fi->buffer, size); } fi->fd = f; return fi; } FILE* fdopen(int fd, char* mode) { FILE* fi = calloc(1, sizeof(FILE)); fi->next = __list; if(NULL != __list) __list->prev = fi; __list = fi; int size; if('w' == mode[0]) { /* Buffer as much as possible */ fi->buffer = malloc(BUFSIZ * sizeof(char)); fi->buflen = BUFSIZ; fi->bufmode = O_WRONLY; } else { /* Get enough buffer to read it all */ size = lseek(fd, 0, SEEK_END); fi->buffer = malloc((size + 1) * sizeof(char)); fi->buflen = size; fi->bufmode = O_RDONLY; /* Now read it all */ lseek(fd, 0, SEEK_SET); read(fd, fi->buffer, size); } fi->fd = fd; return fi; } int write(int fd, char* buf, unsigned count); int fflush(FILE* stream) { /* We only need to flush on writes */ if(O_RDONLY == stream->bufmode) return 0; /* If nothing to flush */ if(0 ==stream->bufpos) return 0; /* The actual flushing */ int error = write(stream->fd, stream->buffer, stream->bufpos); /* Keep track of position */ stream->file_pos = stream->file_pos + stream->bufpos; stream->bufpos = 0; return error; } int close(int fd); int fclose(FILE* stream) { /* Deal with STDIN, STDOUT and STDERR */ /* No close for you */ if(2 >= stream->fd) return 0; /* We only need to flush on writes */ if(O_WRONLY == stream->bufmode) { fflush(stream); } /* Need to keep the File Descriptor for a moment */ int fd = stream->fd; /* Remove from __list */ if(NULL != stream->prev) stream->prev->next = stream->next; if(NULL != stream->next) stream->next->prev = stream->prev; /* Deal with special case of first node in __list */ if (__list == stream) __list = __list->next; /* Free up the buffer and struct used for FILE */ free(stream->buffer); free(stream); /* Do the actual closing */ return close(fd); } int unlink(char* filename); /* File Removal */ int remove(char *pathname) { return unlink(pathname); } /* File Positioning */ int ungetc(int ch, FILE* stream) { /* Deal with STDIN, STDOUT and STDERR */ /* No ungetc for you */ if(2 >= stream->fd) return EOF; /* You can't unget on a write stream! */ if(O_WRONLY == stream->bufmode) return EOF; /* Don't underflow */ if(0 == stream->bufpos) return EOF; /* Don't let crap be shoved into read stream */ if(stream->buffer[stream->bufpos - 1] != ch) return EOF; stream->bufpos = stream->bufpos - 1; return ch; } long ftell(FILE* stream) { /* Deal with STDIN, STDOUT and STDERR */ /* No ftell for you */ if(2 >= stream->fd) return 0; /* Deal with buffered output */ if(O_WRONLY == stream->bufmode) return stream->file_pos + stream->bufpos; /* Deal with read */ return stream->bufpos; } int fseek(FILE* f, long offset, int whence) { /* Deal with STDIN, STDOUT and STDERR */ /* No seek and destroy missions */ if(2 >= f->fd) return 0; /* Deal with ugly case */ if(O_WRONLY == f->bufmode) { fflush(f); return lseek(f->fd, offset, whence); } /* Deal with read mode */ int pos; if(SEEK_SET == whence) { pos = offset; } else if(SEEK_CUR == whence) { pos = f->bufpos + offset; } else if(SEEK_END == whence) { pos = f->buflen + offset; } else return -1; if(pos < 0) return -1; if(pos > f->buflen) return -1; f->bufpos = pos; return pos; } void rewind(FILE* f) { fseek(f, 0, SEEK_SET); }
/* -*- c-file-style: "linux";indent-tabs-mode:t -*- */ /* Copyright (C) 2017 Jeremiah Orians * Copyright (C) 2017 Jan Nieuwenhuizen <janneke@gnu.org> * This file is part of mescc-tools * * mescc-tools is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/stat.h> #include "M2libc/bootstrappable.h" #define max_string 4096 #define TRUE 1 #define FALSE 0 #define KNIGHT 0 #define X86 0x03 #define AMD64 0x3E #define ARMV7L 0x28 #define AARM64 0xB7 #define PPC64LE 0x15 #define RISCV32 0xF3 #define RISCV64 0x100F3 /* Because RISC-V unlike all other architectures does get a seperate e_machine when changing from 32 to 64bit */ #define HEX 16 #define OCTAL 8 #define BINARY 2 struct input_files { struct input_files* next; char* filename; }; struct entry { struct entry* next; unsigned target; char* name; };
/* -*- c-file-style: "linux";indent-tabs-mode:t -*- */ /* Copyright (C) 2017 Jeremiah Orians * Copyright (C) 2017 Jan Nieuwenhuizen <janneke@gnu.org> * This file is part of mescc-tools * * mescc-tools is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools. If not, see <http://www.gnu.org/licenses/>. */ #include "hex2_globals.h" /* Globals */ FILE* output; struct entry** jump_tables; int BigEndian; int Base_Address; int Architecture; int ByteMode; int exec_enable; int ip; char* scratch; char* filename; int linenumber; int ALIGNED; /* For processing bytes */ int hold; int toggle; void line_error() { fputs(filename, stderr); fputs(":", stderr); fputs(int2str(linenumber, 10, FALSE), stderr); fputs(" :", stderr); } int consume_token(FILE* source_file) { int i = 0; int c = fgetc(source_file); while(!in_set(c, " \t\n>")) { scratch[i] = c; i = i + 1; c = fgetc(source_file); require(max_string > i, "Consumed token exceeds length restriction\n"); if(EOF == c) break; } return c; } int Throwaway_token(FILE* source_file) { int c; do { c = fgetc(source_file); if(EOF == c) break; } while(!in_set(c, " \t\n>")); return c; } int length(char* s) { int i = 0; while(0 != s[i]) i = i + 1; return i; } void Clear_Scratch(char* s) { do { s[0] = 0; s = s + 1; } while(0 != s[0]); } void Copy_String(char* a, char* b) { while(0 != a[0]) { b[0] = a[0]; a = a + 1; b = b + 1; } } int GetHash(char* s) { int i = 5381; while(0 != s[0]) { i = i * 31 + s[0]; s = s + 1; } return (i & 0xFFFF); } unsigned GetTarget(char* c) { struct entry* i; for(i = jump_tables[GetHash(c)]; NULL != i; i = i->next) { if(match(c, i->name)) { return i->target; } } fputs("Target label ", stderr); fputs(c, stderr); fputs(" is not valid\n", stderr); exit(EXIT_FAILURE); } int storeLabel(FILE* source_file, int ip) { struct entry* entry = calloc(1, sizeof(struct entry)); require(NULL != entry, "failed to allocate entry\n"); /* Ensure we have target address */ entry->target = ip; /* Store string */ int c = consume_token(source_file); entry->name = calloc(length(scratch) + 1, sizeof(char)); require(NULL != entry->name, "failed to allocate entry->name\n"); Copy_String(scratch, entry->name); Clear_Scratch(scratch); /* Prepend to list */ int h = GetHash(entry->name); entry->next = jump_tables[h]; jump_tables[h] = entry; return c; } void range_check(int displacement, int number_of_bytes) { if(4 == number_of_bytes) return; else if (3 == number_of_bytes) { if((8388607 < displacement) || (displacement < -8388608)) { fputs("A displacement of ", stderr); fputs(int2str(displacement, 10, TRUE), stderr); fputs(" does not fit in 3 bytes\n", stderr); exit(EXIT_FAILURE); } return; } else if (2 == number_of_bytes) { if((32767 < displacement) || (displacement < -32768)) { fputs("A displacement of ", stderr); fputs(int2str(displacement, 10, TRUE), stderr); fputs(" does not fit in 2 bytes\n", stderr); exit(EXIT_FAILURE); } return; } else if (1 == number_of_bytes) { if((127 < displacement) || (displacement < -128)) { fputs("A displacement of ", stderr); fputs(int2str(displacement, 10, TRUE), stderr); fputs(" does not fit in 1 byte\n", stderr); exit(EXIT_FAILURE); } return; } fputs("Invalid number of bytes given\n", stderr); exit(EXIT_FAILURE); } void outputPointer(int displacement, int number_of_bytes) { unsigned value = displacement; /* HALT HARD if we are going to do something BAD*/ range_check(displacement, number_of_bytes); if(BigEndian) { /* Deal with BigEndian */ if(4 == number_of_bytes) fputc((value >> 24), output); if(3 <= number_of_bytes) fputc(((value >> 16)%256), output); if(2 <= number_of_bytes) fputc(((value >> 8)%256), output); if(1 <= number_of_bytes) fputc((value % 256), output); } else { /* Deal with LittleEndian */ unsigned byte; while(number_of_bytes > 0) { byte = value % 256; value = value / 256; fputc(byte, output); number_of_bytes = number_of_bytes - 1; } } } int Architectural_displacement(int target, int base) { if(KNIGHT == Architecture) return (target - base); else if(X86 == Architecture) return (target - base); else if(AMD64 == Architecture) return (target - base); else if(ALIGNED && (ARMV7L == Architecture)) { ALIGNED = FALSE; /* Note: Branch displacements on ARM are in number of instructions to skip, basically. */ if (target & 3) { line_error(); fputs("error: Unaligned branch target: ", stderr); fputs(scratch, stderr); fputs(", aborting\n", stderr); exit(EXIT_FAILURE); } /* * The "fetch" stage already moved forward by 8 from the * beginning of the instruction because it is already * prefetching the next instruction. * Compensate for it by subtracting the space for * two instructions (including the branch instruction). * and the size of the aligned immediate. */ return (((target - base + (base & 3)) >> 2) - 2); } else if(ARMV7L == Architecture) { /* * The size of the offset is 8 according to the spec but that value is * based on the end of the immediate, which the documentation gets wrong * and needs to be adjusted to the size of the immediate. * Eg 1byte immediate => -8 + 1 = -7 */ return ((target - base) - 8 + (3 & base)); } else if(ALIGNED && (AARM64 == Architecture)) { ALIGNED = FALSE; return (target - (~3 & base)) >> 2; } else if (AARM64 == Architecture) { return ((target - base) - 8 + (3 & base)); } else if(ALIGNED && (PPC64LE == Architecture)) { ALIGNED = FALSE; /* set Link register with branch */ return (target - (base & 0xFFFFFFFC )) | 1; } else if(PPC64LE == Architecture) { /* DO *NOT* set link register with branch */ return (target - (base & 0xFFFFFFFC)); } else if(RISCV32 == Architecture || RISCV64 == Architecture) return (target - base); fputs("Unknown Architecture, aborting before harm is done\n", stderr); exit(EXIT_FAILURE); } void Update_Pointer(char ch) { /* Calculate pointer size*/ if(in_set(ch, "%&")) ip = ip + 4; /* Deal with % and & */ else if(in_set(ch, "@$")) ip = ip + 2; /* Deal with @ and $ */ else if('~' == ch) ip = ip + 3; /* Deal with ~ */ else if('!' == ch) ip = ip + 1; /* Deal with ! */ else { line_error(); fputs("storePointer given unknown\n", stderr); exit(EXIT_FAILURE); } } void storePointer(char ch, FILE* source_file) { /* Get string of pointer */ Clear_Scratch(scratch); Update_Pointer(ch); int base_sep_p = consume_token(source_file); /* Lookup token */ int target = GetTarget(scratch); int displacement; int base = ip; /* Change relative base address to :<base> */ if ('>' == base_sep_p) { Clear_Scratch(scratch); consume_token (source_file); base = GetTarget (scratch); /* Force universality of behavior */ displacement = (target - base); } else { displacement = Architectural_displacement(target, base); } /* output calculated difference */ if('!' == ch) outputPointer(displacement, 1); /* Deal with ! */ else if('$' == ch) outputPointer(target, 2); /* Deal with $ */ else if('@' == ch) outputPointer(displacement, 2); /* Deal with @ */ else if('~' == ch) outputPointer(displacement, 3); /* Deal with ~ */ else if('&' == ch) outputPointer(target, 4); /* Deal with & */ else if('%' == ch) outputPointer(displacement, 4); /* Deal with % */ else { line_error(); fputs("error: storePointer reached impossible case: ch=", stderr); fputc(ch, stderr); fputs("\n", stderr); exit(EXIT_FAILURE); } } void line_Comment(FILE* source_file) { int c = fgetc(source_file); while(!in_set(c, "\n\r")) { if(EOF == c) break; c = fgetc(source_file); } linenumber = linenumber + 1; } int hex(int c, FILE* source_file) { if (in_set(c, "0123456789")) return (c - 48); else if (in_set(c, "abcdef")) return (c - 87); else if (in_set(c, "ABCDEF")) return (c - 55); else if (in_set(c, "#;")) line_Comment(source_file); else if ('\n' == c) linenumber = linenumber + 1; return -1; } int octal(int c, FILE* source_file) { if (in_set(c, "01234567")) return (c - 48); else if (in_set(c, "#;")) line_Comment(source_file); else if ('\n' == c) linenumber = linenumber + 1; return -1; } int binary(int c, FILE* source_file) { if (in_set(c, "01")) return (c - 48); else if (in_set(c, "#;")) line_Comment(source_file); else if ('\n' == c) linenumber = linenumber + 1; return -1; } void process_byte(char c, FILE* source_file, int write) { if(HEX == ByteMode) { if(0 <= hex(c, source_file)) { if(toggle) { if(write) fputc(((hold * 16)) + hex(c, source_file), output); ip = ip + 1; hold = 0; } else { hold = hex(c, source_file); } toggle = !toggle; } } else if(OCTAL ==ByteMode) { if(0 <= octal(c, source_file)) { if(2 == toggle) { if(write) fputc(((hold * 8)) + octal(c, source_file), output); ip = ip + 1; hold = 0; toggle = 0; } else if(1 == toggle) { hold = ((hold * 8) + octal(c, source_file)); toggle = 2; } else { hold = octal(c, source_file); toggle = 1; } } } else if(BINARY == ByteMode) { if(0 <= binary(c, source_file)) { if(7 == toggle) { if(write) fputc((hold * 2) + binary(c, source_file), output); ip = ip + 1; hold = 0; toggle = 0; } else { hold = ((hold * 2) + binary(c, source_file)); toggle = toggle + 1; } } } } void pad_to_align(int write) { if((ARMV7L == Architecture) || (AARM64 == Architecture) || (RISCV32 == Architecture) || (RISCV64 == Architecture)) { if(1 == (ip & 0x1)) { ip = ip + 1; if(write) fputc('\0', output); } if(2 == (ip & 0x2)) { ip = ip + 2; if(write) { fputc('\0', output); fputc('\0', output); } } } } void first_pass(struct input_files* input) { if(NULL == input) return; first_pass(input->next); filename = input->filename; linenumber = 1; FILE* source_file = fopen(filename, "r"); if(NULL == source_file) { fputs("The file: ", stderr); fputs(input->filename, stderr); fputs(" can not be opened!\n", stderr); exit(EXIT_FAILURE); } toggle = FALSE; int c; for(c = fgetc(source_file); EOF != c; c = fgetc(source_file)) { /* Check for and deal with label */ if(':' == c) { c = storeLabel(source_file, ip); } /* check for and deal with relative/absolute pointers to labels */ if(in_set(c, "!@$~%&")) { /* deal with 1byte pointer !; 2byte pointers (@ and $); 3byte pointers ~; 4byte pointers (% and &) */ Update_Pointer(c); c = Throwaway_token(source_file); if ('>' == c) { /* deal with label>base */ c = Throwaway_token(source_file); } } else if('<' == c) { pad_to_align(FALSE); } else if('^' == c) { /* Just ignore */ continue; } else process_byte(c, source_file, FALSE); } fclose(source_file); } void second_pass(struct input_files* input) { if(NULL == input) return; second_pass(input->next); filename = input->filename; linenumber = 1; FILE* source_file = fopen(filename, "r"); /* Something that should never happen */ if(NULL == source_file) { fputs("The file: ", stderr); fputs(input->filename, stderr); fputs(" can not be opened!\nWTF-pass2\n", stderr); exit(EXIT_FAILURE); } toggle = FALSE; hold = 0; int c; for(c = fgetc(source_file); EOF != c; c = fgetc(source_file)) { if(':' == c) c = Throwaway_token(source_file); /* Deal with : */ else if(in_set(c, "!@$~%&")) storePointer(c, source_file); /* Deal with !, @, $, ~, % and & */ else if('<' == c) pad_to_align(TRUE); else if('^' == c) ALIGNED = TRUE; else process_byte(c, source_file, TRUE); } fclose(source_file); }
/* -*- c-file-style: "linux";indent-tabs-mode:t -*- */ /* Copyright (C) 2017 Jeremiah Orians * Copyright (C) 2017 Jan Nieuwenhuizen <janneke@gnu.org> * This file is part of mescc-tools * * mescc-tools is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools. If not, see <http://www.gnu.org/licenses/>. */ #include "hex2_globals.h" unsigned shiftregister; unsigned tempword; int updates; void outOfRange(char* s, int value) { line_error(); fputs("error: value ", stderr); fputs(int2str(value, 10, TRUE), stderr); fputs(" out of range for field type ", stderr); fputs(s, stderr); fputs("\n", stderr); exit(EXIT_FAILURE); } void UpdateShiftRegister(char ch, int value) { if ('.' == ch) { unsigned swap; /* Assume the user knows what they are doing */ if(!BigEndian) { /* Swap from big-endian to little endian order */ swap = (((value >> 24) & 0xFF) | ((value << 8) & 0xFF0000) | ((value >> 8) & 0xFF00) | ((value & 0xFF) << 24)); } else { /* Big endian needs no change */ swap = value; } /* we just take the 4 bytes after the . and shove in the shift register */ swap = swap & ((0xFFFF << 16) | 0xFFFF); shiftregister = shiftregister ^ swap; } else if ('!' == ch) { /* Corresponds to RISC-V I format */ /* Will need architecture specific logic if more architectures go this route */ /* no range check because it needs to work with labels for lui/addi + AUIPC combos */ /* !label is used in the second instruction of AUIPC combo but we want an offset from */ /* the first instruction */ value = value + 4; tempword = (value & 0xFFF) << 20; /* Update shift register */ tempword = tempword & ((0xFFFF << 16) | 0xFFFF); shiftregister = shiftregister ^ tempword; } else if ('@' == ch) { /* Corresponds to RISC-V B format (formerly known as SB) */ /* Will need architecture specific logic if more architectures go this route */ if ((value < -0x1000 || value > 0xFFF) || (value & 1)) outOfRange("B", value); /* Prepare the immediate's word */ tempword = ((value & 0x1E) << 7) | ((value & 0x7E0) << (31 - 11)) | ((value & 0x800) >> 4) | ((value & 0x1000) << (31 - 12)); tempword = tempword & ((0xFFFF << 16) | 0xFFFF); /* Update shift register */ shiftregister = shiftregister ^ tempword; } else if ('$' == ch) { /* Corresponds with RISC-V J format (formerly known as UJ) */ /* Will need architecture specific logic if more architectures go this route */ if ((value < -0x100000 || value > 0xFFFFF) || (value & 1)) outOfRange("J", value); tempword = ((value & 0x7FE) << (30 - 10)) | ((value & 0x800) << (20 - 11)) | ((value & 0xFF000)) | ((value & 0x100000) << (31 - 20)); tempword = tempword & ((0xFFFF << 16) | 0xFFFF); shiftregister = shiftregister ^ tempword; } else if ('~' == ch) { /* Corresponds with RISC-V U format */ /* Will need architecture specific logic if more architectures go this route */ if ((value & 0xFFF) < 0x800) tempword = value & (0xFFFFF << 12); else tempword = (value & (0xFFFFF << 12)) + 0x1000; tempword = tempword & ((0xFFFF << 16) | 0xFFFF); shiftregister = shiftregister ^ tempword; } else { line_error(); fputs("error: UpdateShiftRegister reached impossible case: ch=", stderr); fputc(ch, stderr); fputs("\n", stderr); exit(EXIT_FAILURE); } } void WordStorePointer(char ch, FILE* source_file) { /* Get string of pointer */ ip = ip + 4; Clear_Scratch(scratch); int base_sep_p = consume_token(source_file); /* Lookup token */ int target = GetTarget(scratch); int displacement; int base = ip; /* Change relative base address to :<base> */ if ('>' == base_sep_p) { Clear_Scratch(scratch); consume_token (source_file); base = GetTarget (scratch); /* Force universality of behavior */ displacement = (target - base); } else { displacement = Architectural_displacement(target, base); } /* output calculated difference */ if('&' == ch) outputPointer(target, 4); /* Deal with & */ else if('%' == ch) outputPointer(displacement, 4); /* Deal with % */ else { line_error(); fputs("error: WordStorePointer reached impossible case: ch=", stderr); fputc(ch, stderr); fputs("\n", stderr); exit(EXIT_FAILURE); } } unsigned sr_nextb() { unsigned rv = shiftregister & 0xff; shiftregister = shiftregister >> 8; return rv; } void DoByte(char c, FILE* source_file, int write, int update) { if(HEX == ByteMode) { if(0 <= hex(c, source_file)) { if(toggle) { if(write) fputc(((hold * 16)) + hex(c, source_file) ^ sr_nextb(), output); ip = ip + 1; if(update) { hold = (hold * 16) + hex(c, source_file); tempword = (tempword << 8) ^ hold; updates = updates + 1; } hold = 0; } else { hold = hex(c, source_file); } toggle = !toggle; } } else if(OCTAL ==ByteMode) { if(0 <= octal(c, source_file)) { if(2 == toggle) { if(write) fputc(((hold * 8)) + octal(c, source_file) ^ sr_nextb(), output); ip = ip + 1; if(update) { hold = ((hold * 8) + octal(c, source_file)); tempword = (tempword << 8) ^ hold; updates = updates + 1; } hold = 0; toggle = 0; } else if(1 == toggle) { hold = ((hold * 8) + octal(c, source_file)); toggle = 2; } else { hold = octal(c, source_file); toggle = 1; } } } else if(BINARY == ByteMode) { if(0 <= binary(c, source_file)) { if(7 == toggle) { if(write) fputc((hold * 2) + binary(c, source_file) ^ sr_nextb(), output); ip = ip + 1; if(update) { hold = ((hold * 2) + binary(c, source_file)); tempword = (tempword << 8) ^ hold; updates = updates + 1; } hold = 0; toggle = 0; } else { hold = ((hold * 2) + binary(c, source_file)); toggle = toggle + 1; } } } } void WordFirstPass(struct input_files* input) { if(NULL == input) return; WordFirstPass(input->next); filename = input->filename; linenumber = 1; FILE* source_file = fopen(filename, "r"); if(NULL == source_file) { fputs("The file: ", stderr); fputs(input->filename, stderr); fputs(" can not be opened!\n", stderr); exit(EXIT_FAILURE); } toggle = FALSE; int c; for(c = fgetc(source_file); EOF != c; c = fgetc(source_file)) { /* Check for and deal with label */ if(':' == c) { c = storeLabel(source_file, ip); } /* check for and deal with relative/absolute pointers to labels */ if('.' == c) { /* Read architecture specific number of bytes for what is defined as a word */ /* 4bytes in RISC-V's case */ updates = 0; tempword = 0; while (updates < 4) { c = fgetc(source_file); DoByte(c, source_file, FALSE, TRUE); } ip = ip - 4; } else if(in_set(c, "!@$~")) { /* Don't update IP */ c = Throwaway_token(source_file); } else if(in_set(c, "%&")) { ip = ip + 4; c = Throwaway_token(source_file); if ('>' == c) { /* deal with label>base */ c = Throwaway_token(source_file); } } else if('<' == c) { pad_to_align(FALSE); } else if('^' == c) { /* Just ignore */ continue; } else DoByte(c, source_file, FALSE, FALSE); } fclose(source_file); } void WordSecondPass(struct input_files* input) { shiftregister = 0; tempword = 0; if(NULL == input) return; WordSecondPass(input->next); filename = input->filename; linenumber = 1; FILE* source_file = fopen(filename, "r"); /* Something that should never happen */ if(NULL == source_file) { fputs("The file: ", stderr); fputs(input->filename, stderr); fputs(" can not be opened!\nWTF-pass2\n", stderr); exit(EXIT_FAILURE); } toggle = FALSE; hold = 0; int c; for(c = fgetc(source_file); EOF != c; c = fgetc(source_file)) { if(':' == c) c = Throwaway_token(source_file); /* Deal with : */ else if('.' == c) { /* Read architecture specific number of bytes for what is defined as a word */ /* 4bytes in RISC-V's case */ updates = 0; tempword = 0; while (updates < 4) { c = fgetc(source_file); DoByte(c, source_file, FALSE, TRUE); } UpdateShiftRegister('.', tempword); ip = ip - 4; } else if(in_set(c, "%&")) WordStorePointer(c, source_file); /* Deal with % and & */ else if(in_set(c, "!@$~")) { Clear_Scratch(scratch); consume_token(source_file); UpdateShiftRegister(c, Architectural_displacement(GetTarget(scratch), ip)); /* Play with shift register */ } else if('<' == c) pad_to_align(TRUE); else if('^' == c) ALIGNED = TRUE; else DoByte(c, source_file, TRUE, FALSE); } fclose(source_file); }
/* -*- c-file-style: "linux";indent-tabs-mode:t -*- */ /* Copyright (C) 2017 Jeremiah Orians * Copyright (C) 2017 Jan Nieuwenhuizen <janneke@gnu.org> * This file is part of mescc-tools * * mescc-tools is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools. If not, see <http://www.gnu.org/licenses/>. */ #include "hex2_globals.h" /* The essential functions */ void first_pass(struct input_files* input); void second_pass(struct input_files* input); void WordFirstPass(struct input_files* input); void WordSecondPass(struct input_files* input); /* Standard C main program */ int main(int argc, char **argv) { int InsaneArchitecture = FALSE; ALIGNED = FALSE; BigEndian = TRUE; jump_tables = calloc(65537, sizeof(struct entry*)); require(NULL != jump_tables, "Failed to allocate our jump_tables\n"); Architecture = KNIGHT; Base_Address = 0; struct input_files* input = NULL; output = stdout; char* output_file = ""; exec_enable = TRUE; ByteMode = HEX; scratch = calloc(max_string + 1, sizeof(char)); require(NULL != scratch, "failed to allocate our scratch buffer\n"); char* arch; struct input_files* temp; int option_index = 1; while(option_index <= argc) { if(NULL == argv[option_index]) { option_index = option_index + 1; } else if(match(argv[option_index], "--big-endian")) { BigEndian = TRUE; option_index = option_index + 1; } else if(match(argv[option_index], "--little-endian")) { BigEndian = FALSE; option_index = option_index + 1; } else if(match(argv[option_index], "--non-executable")) { exec_enable = FALSE; option_index = option_index + 1; } else if(match(argv[option_index], "-A") || match(argv[option_index], "--architecture")) { arch = argv[option_index + 1]; if(match("knight-native", arch) || match("knight-posix", arch)) Architecture = KNIGHT; else if(match("x86", arch)) Architecture = X86; else if(match("amd64", arch)) Architecture = AMD64; else if(match("armv7l", arch)) Architecture = ARMV7L; else if(match("aarch64", arch)) Architecture = AARM64; else if(match("ppc64le", arch)) Architecture = PPC64LE; else if(match("riscv32", arch)) Architecture = RISCV32; else if(match("riscv64", arch)) Architecture = RISCV64; else { fputs("Unknown architecture: ", stderr); fputs(arch, stderr); fputs(" know values are: knight-native, knight-posix, x86, amd64, armv7l, riscv32 and riscv64", stderr); } option_index = option_index + 2; } else if(match(argv[option_index], "-b") || match(argv[option_index], "--binary")) { ByteMode = BINARY; option_index = option_index + 1; } else if(match(argv[option_index], "-B") || match(argv[option_index], "--base-address")) { Base_Address = strtoint(argv[option_index + 1]); option_index = option_index + 2; } else if(match(argv[option_index], "-h") || match(argv[option_index], "--help")) { fputs("Usage: ", stderr); fputs(argv[0], stderr); fputs(" --file FILENAME1 {-f FILENAME2} (--big-endian|--little-endian)", stderr); fputs(" [--base-address 0x12345] [--architecture name]\nArchitecture:", stderr); fputs(" knight-native, knight-posix, x86, amd64, armv7l, aarch64, riscv32 and riscv64\n", stderr); fputs("To leverage octal or binary input: --octal, --binary\n", stderr); exit(EXIT_SUCCESS); } else if(match(argv[option_index], "-f") || match(argv[option_index], "--file")) { temp = calloc(1, sizeof(struct input_files)); require(NULL != temp, "failed to allocate file for processing\n"); temp->filename = argv[option_index + 1]; temp->next = input; input = temp; option_index = option_index + 2; } else if(match(argv[option_index], "-o") || match(argv[option_index], "--output")) { output_file = argv[option_index + 1]; output = fopen(output_file, "w"); if(NULL == output) { fputs("The file: ", stderr); fputs(argv[option_index + 1], stderr); fputs(" can not be opened!\n", stderr); exit(EXIT_FAILURE); } option_index = option_index + 2; } else if(match(argv[option_index], "-O") || match(argv[option_index], "--octal")) { ByteMode = OCTAL; option_index = option_index + 1; } else if(match(argv[option_index], "-V") || match(argv[option_index], "--version")) { fputs("hex2 1.4.0\n", stdout); exit(EXIT_SUCCESS); } else { fputs("Unknown option\n", stderr); exit(EXIT_FAILURE); } } if((Architecture == RISCV32) || (Architecture == RISCV64)) { /* Forcing me to use words instead of just byting into the problem */ InsaneArchitecture = TRUE; } /* Catch a common mistake */ if((KNIGHT != Architecture) && (0 == Base_Address)) { fputs(">> WARNING <<\n>> WARNING <<\n>> WARNING <<\n", stderr); fputs("If you are not generating a ROM image this binary will likely not work\n", stderr); } /* Catch implicitly false assumptions */ if(BigEndian && ((X86 == Architecture) || ( AMD64 == Architecture) || (ARMV7L == Architecture) || (AARM64 == Architecture) || (RISCV32 == Architecture) || (RISCV64 == Architecture))) { fputs(">> WARNING <<\n>> WARNING <<\n>> WARNING <<\n", stderr); fputs("You have specified big endian output on likely a little endian processor\n", stderr); fputs("if this is a mistake please pass --little-endian next time\n", stderr); } /* Make sure we have a program tape to run */ if (NULL == input) { return EXIT_FAILURE; } /* Get all of the labels */ ip = Base_Address; if(InsaneArchitecture) WordFirstPass(input); else first_pass(input); /* Fix all the references*/ ip = Base_Address; if(InsaneArchitecture) WordSecondPass(input); else second_pass(input); /* flush all writes */ fflush(output); /* Set file as executable */ if(exec_enable && (output != stdout)) { /* Close output file */ fclose(output); if(0 != chmod(output_file, 0750)) { fputs("Unable to change permissions\n", stderr); exit(EXIT_FAILURE); } } return EXIT_SUCCESS; }
## Copyright (C) 2016 Jeremiah Orians ## This file is part of M2-Planet. ## ## M2-Planet is free software: you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation, either version 3 of the License, or ## (at your option) any later version. ## ## M2-Planet is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. :_start mov_ebp,esp ; Protect esp ;; Prepare argv lea_eax,[ebp+DWORD] %4 ; ARGV_address = EBP + 4 push_eax ; Put argv on the stack ;; Prepare envp mov_eax,ebp ; Address we need to load from mov_eax,[eax] ; Get ARGC add_eax, %2 ; OFFSET = ARGC + 2 sal_eax, !2 ; OFFSET = OFFSET * WORDSIZE add_eax,ebp ; ENVP_address = ESP + OFFSET push_eax ; Put envp on the stack mov_ebx, &GLOBAL__envp ; Get _envp global mov_[ebx],eax ; Save environment to _envp ;; Stack offset add_ebp, %4 ; Fix ebp ;; Setup for malloc call %FUNCTION___init_malloc ;; Setup for FILE* call %FUNCTION___init_io ;; Perform the main loop call %FUNCTION_main push_eax ; Put return on stack push_eax ; so that _exit gets the value :FUNCTION_exit call %FUNCTION___kill_io :FUNCTION__exit pop_ebx pop_ebx mov_eax, %1 int !0x80 :GLOBAL__envp NULL
/* Copyright (C) 2016 Jeremiah Orians * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #include <stddef.h> char* strcpy(char* dest, char const* src) { int i = 0; while (0 != src[i]) { dest[i] = src[i]; i = i + 1; } dest[i] = 0; return dest; } char* strncpy(char* dest, char const* src, size_t count) { if(0 == count) return dest; size_t i = 0; while(0 != src[i]) { dest[i] = src[i]; i = i + 1; if(count == i) return dest; } while(i <= count) { dest[i] = 0; i = i + 1; } return dest; } char* strcat(char* dest, char const* src) { int i = 0; int j = 0; while(0 != dest[i]) i = i + 1; while(0 != src[j]) { dest[i] = src[j]; i = i + 1; j = j + 1; } dest[i] = 0; return dest; } char* strncat(char* dest, char const* src, size_t count) { size_t i = 0; size_t j = 0; while(0 != dest[i]) i = i + 1; while(0 != src[j]) { if(count == j) { dest[i] = 0; return dest; } dest[i] = src[j]; i = i + 1; j = j + 1; } dest[i] = 0; return dest; } size_t strlen(char const* str ) { size_t i = 0; while(0 != str[i]) i = i + 1; return i; } size_t strnlen_s(char const* str, size_t strsz ) { size_t i = 0; while(0 != str[i]) { if(strsz == i) return i; i = i + 1; } return i; } int strcmp(char const* lhs, char const* rhs ) { int i = 0; while(0 != lhs[i]) { if(lhs[i] != rhs[i]) return lhs[i] - rhs[i]; i = i + 1; } return lhs[i] - rhs[i]; } int strncmp(char const* lhs, char const* rhs, size_t count) { size_t i = 0; while(count > i) { if(0 == lhs[i]) break; if(lhs[i] != rhs[i]) return lhs[i] - rhs[i]; i = i + 1; } return 0; } char* strchr(char const* str, int ch) { char* p = str; while(ch != p[0]) { if(0 == p[0]) return NULL; p = p + 1; } if(0 == p[0]) return NULL; return p; } char* strrchr(char const* str, int ch) { char* p = str; int i = 0; while(0 != p[i]) i = i + 1; while(ch != p[i]) { if(0 == i) return NULL; i = i - 1; } return (p + i); } size_t strspn(char const* dest, char const* src) { if(0 == dest[0]) return 0; int i = 0; while(NULL != strchr(src, dest[i])) i = i + 1; return i; } size_t strcspn(char const* dest, char const* src) { int i = 0; while(NULL == strchr(src, dest[i])) i = i + 1; return i; } char* strpbrk(char const* dest, char const* breakset) { char* p = dest; char* s; while(0 != p[0]) { s = strchr(breakset, p[0]); if(NULL != s) return strchr(p, s[0]); p = p + 1; } return p; } void* memset(void* dest, int ch, size_t count) { if(NULL == dest) return dest; size_t i = 0; char* s = dest; while(i < count) { s[i] = ch; i = i + 1; } return dest; } void* memcpy(void* dest, void const* src, size_t count) { if(NULL == dest) return dest; if(NULL == src) return NULL; char* s1 = dest; char const* s2 = src; size_t i = 0; while(i < count) { s1[i] = s2[i]; i = i + 1; } return dest; } void* memmove(void* dest, void const* src, size_t count) { if (dest < src) return memcpy (dest, src, count); char *p = dest; char const *q = src; count = count - 1; while (count >= 0) { p[count] = q[count]; count = count - 1; } return dest; } int memcmp(void const* lhs, void const* rhs, size_t count) { if(0 == count) return 0; size_t i = 0; count = count - 1; char const* s1 = lhs; char const* s2 = rhs; while(i < count) { if(s1[i] != s2[i]) break; i = i + 1; } return (s1[i] - s2[i]); }
/* Copyright (C) 2016-2020 Jeremiah Orians * Copyright (C) 2020 fosslinux * This file is part of mescc-tools. * * mescc-tools is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> #include "../M2libc/bootstrappable.h" /* * DEFINES */ #define FALSE 0 #define TRUE 1 // CONSTANT SUCCESS 0 #define SUCCESS 0 // CONSTANT FAILURE 1 #define FAILURE 1 #define MAX_STRING 4096 #define MAX_ARRAY 256 /* * Here is the token struct. It is used for both the token linked-list and * env linked-list. */ struct Token { /* * For the token linked-list, this stores the token; for the env linked-list * this stores the value of the variable. */ char* value; /* * Used only for the env linked-list. It holds a string containing the * name of the var. */ char* var; /* * This struct stores a node of a singly linked list, store the pointer to * the next node. */ struct Token* next; }; #include "kaem_globals.h"
/* * Copyright (C) 2020 fosslinux * This file is part of mescc-tools. * * mescc-tools is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include "kaem.h" /* Prototypes from other files */ int array_length(char** array); char* env_lookup(char* variable); /* * VARIABLE HANDLING FUNCTIONS */ /* Substitute a variable into n->value */ int run_substitution(char* var_name, struct Token* n) { char* value = env_lookup(var_name); /* If there is nothing to substitute, don't substitute anything! */ if(value != NULL) { char* s = calloc(MAX_STRING, sizeof(char)); s = strcat(s, n->value); s = strcat(s, value); n->value = s; return TRUE; } return FALSE; } /* Handle ${var:-text} format of variables - i.e. ifset format */ int variable_substitute_ifset(char* input, struct Token* n, int index) { /* * In ${var:-text} format, we evaluate like follows. * If var is set as an envar, then we substitute the contents of that * envar. If it is not set, we substitute alternative text. * * In this function, we assume that input is the raw token, * n->value is everything already done in variable_substitute, * index is where we are up to in input. offset is for n->value. */ /* * Check if we should even be performing this function. * We perform this function when we come across ${var:-text} syntax. */ int index_old = index; int perform = FALSE; int input_length = strlen(input); while(index < input_length) { /* Loop over each character */ if(input[index] == ':' && input[index + 1] == '-') { /* Yes, this is (most likely) ${var:-text} format. */ perform = TRUE; break; } index = index + 1; } /* Don't perform it if we shouldn't */ if(perform == FALSE) return index_old; index = index_old; /* * Get offset. * offset is the difference between the index of the variable we write to * in the following blocks and input. * This stays relatively constant. */ int offset = index; /* Get the variable name */ char* var_name = calloc(MAX_STRING, sizeof(char)); require(var_name != NULL, "Memory initialization of var_name in variable_substitute_ifset failed\n"); while(input[index] != ':') { /* Copy into var_name until :- */ var_name[index - offset] = input[index]; index = index + 1; } /* Skip over :- */ index = index + 2; offset = index; /* Get the alternative text */ char* text = calloc(MAX_STRING, sizeof(char)); require(text != NULL, "Memory initialization of text in variable_substitute_ifset failed\n"); while(input[index] != '}') { /* Copy into text until } */ require(input_length > index, "IMPROPERLY TERMINATED VARIABLE\nABORTING HARD\n"); text[index - offset] = input[index]; index = index + 1; } /* Do the substitution */ if(run_substitution(var_name, n) == FALSE) { /* The variable was not found. Substitute the alternative text. */ char* s = calloc(MAX_STRING, sizeof(char)); s = strcat(s, n->value); s = strcat(s, text); n->value = s; } return index; } /* Controls substitution for ${variable} and derivatives */ int variable_substitute(char* input, struct Token* n, int index) { /* NOTE: index is the pos of input */ index = index + 1; /* We don't want the { */ /* * Check for "special" types * If we do find a special type we delegate the substitution to it * and return here; as we are done... there's nothing more do do in * that case. */ int index_old = index; index = variable_substitute_ifset(input, n, index); if(index != index_old) return index; /* Reset index */ index = index_old; /* * If we reach here it is a normal substitution * Let's do it! */ /* Initialize var_name and offset */ char* var_name = calloc(MAX_STRING, sizeof(char)); require(var_name != NULL, "Memory initialization of var_name in variable_substitute failed\n"); int offset = index; /* Get the variable name */ int substitute_done = FALSE; char c; while(substitute_done == FALSE) { c = input[index]; require(MAX_STRING > index, "LINE IS TOO LONG\nABORTING HARD\n"); if(EOF == c || '\n' == c || index > strlen(input)) { /* We never should hit EOF, EOL or run past the end of the line while collecting a variable */ fputs("IMPROPERLY TERMINATED VARIABLE!\nABORTING HARD\n", stderr); exit(EXIT_FAILURE); } else if('\\' == c) { /* Drop the \ - poor mans escaping. */ index = index + 1; } else if('}' == c) { /* End of variable name */ substitute_done = TRUE; } else { var_name[index - offset] = c; index = index + 1; } } /* Substitute the variable */ run_substitution(var_name, n); return index; } /* Function to concatenate all command line arguments */ void variable_all(char** argv, struct Token* n) { fflush(stdout); /* index refernences the index of n->value, unlike other functions */ int index = 0; int argv_length = array_length(argv); int i = 0; char* argv_element = calloc(MAX_STRING, sizeof(char)); char* hold = argv[i]; n->value = argv_element; /* Assuming the form kaem -f script or kaem -f script -- 123 we want matching results to bash, so skip the kaem, -f and script */ while(!match("--", hold)) { i = i + 1; hold = argv[i]; if(argv_length == i) break; } /* put i = i + 1 in the for initialization to skip past the -- */ for(; i < argv_length; i = i + 1) { /* Ends up with (n->value) (argv[i]) */ /* If we don't do this we get jumbled results in M2-Planet */ hold = argv[i]; strcpy(argv_element + index, hold); index = index + strlen(hold); /* Add space on the end */ n->value[index] = ' '; index = index + 1; } /* Remove trailing space */ index = index - 1; n->value[index] = 0; } /* Function controlling substitution of variables */ void handle_variables(char** argv, struct Token* n) { /* NOTE: index is the position of input */ int index = 0; /* Create input */ char* input = calloc(MAX_STRING, sizeof(char)); require(input != NULL, "Memory initialization of input in collect_variable failed\n"); strcpy(input, n->value); /* Reset n->value */ n->value = calloc(MAX_STRING, sizeof(char)); require(n->value != NULL, "Memory initialization of n->value in collect_variable failed\n"); /* Copy everything up to the $ */ /* * TODO: Not need allocation of input before this check if there is no * variable in it. */ while(input[index] != '$') { if(input[index] == 0) { /* No variable in it */ n->value = input; return; /* We don't need to do anything more */ } n->value[index] = input[index]; index = index + 1; } /* Must be outside the loop */ int offset; substitute: index = index + 1; /* We are uninterested in the $ */ /* Run the substitution */ if(input[index] == '{') { /* Handle everything ${ related */ index = variable_substitute(input, n, index); index = index + 1; /* We don't want the closing } */ } else if(input[index] == '@') { /* Handles $@ */ index = index + 1; /* We don't want the @ */ variable_all(argv, n); } else { /* We don't know that */ fputs("IMPROPERLY USED VARIABLE!\nOnly ${foo} and $@ format are accepted at this time.\nABORTING HARD\n", stderr); exit(EXIT_FAILURE); } offset = strlen(n->value) - index; /* Copy everything from the end of the variable to the end of the token */ while(input[index] != 0) { if(input[index] == '$') { /* We have found another variable */ fflush(stdout); goto substitute; } n->value[index + offset] = input[index]; index = index + 1; } }
/* Copyright (C) 2016-2020 Jeremiah Orians * Copyright (C) 2020 fosslinux * This file is part of mescc-tools. * * mescc-tools is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools. If not, see <http://www.gnu.org/licenses/>. */ #include "kaem.h" int command_done; int VERBOSE; int VERBOSE_EXIT; int STRICT; int INIT_MODE; int FUZZING; int WARNINGS; char* KAEM_BINARY; char* PATH; /* Token linked-list; stores the tokens of each line */ struct Token* token; /* Env linked-list; stores the environment variables */ struct Token* env; /* Alias linked-list; stores the aliases */ struct Token* alias;
/* Copyright (C) 2016-2020 Jeremiah Orians * Copyright (C) 2020 fosslinux * Copyright (C) 2021 Andrius Å tikonas * This file is part of mescc-tools. * * mescc-tools is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sys/wait.h> #include <string.h> #include "kaem.h" /* Prototypes from other files */ void handle_variables(char** argv, struct Token* n); /* * UTILITY FUNCTIONS */ /* Function to find a character in a string */ char* find_char(char* string, char a) { if(0 == string[0]) { return NULL; } while(a != string[0]) { string = string + 1; if(0 == string[0]) { return string; } } return string; } /* Function to find the length of a char**; an array of strings */ int array_length(char** array) { int length = 0; while(array[length] != NULL) { length = length + 1; } return length; } /* Search for a variable in the token linked-list */ char* token_lookup(char* variable, struct Token* token) { /* Start at the head */ struct Token* n = token; /* Loop over the linked-list */ while(n != NULL) { if(match(variable, n->var)) { /* We have found the correct node */ return n->value; /* Done */ } /* Nope, try the next */ n = n->next; } /* We didn't find anything! */ return NULL; } /* Search for a variable in the env linked-list */ char* env_lookup(char* variable) { return token_lookup(variable, env); } /* Search for a variable in the alias linked-list */ char* alias_lookup(char* variable) { return token_lookup(variable, alias); } /* Find the full path to an executable */ char* find_executable(char* name) { if(match("", name)) { return NULL; } if(('.' == name[0]) || ('/' == name[0])) { /* assume names that start with . or / are relative or absolute */ return name; } char* trial = calloc(MAX_STRING, sizeof(char)); char* MPATH = calloc(MAX_STRING, sizeof(char)); /* Modified PATH */ require(MPATH != NULL, "Memory initialization of MPATH in find_executable failed\n"); strcpy(MPATH, PATH); FILE* t; char* next = find_char(MPATH, ':'); int index; int offset; int mpath_length; int name_length; int trial_length; while(NULL != next) { /* Reset trial */ trial_length = strlen(trial); for(index = 0; index < trial_length; index = index + 1) { trial[index] = 0; } next[0] = 0; /* prepend_string(MPATH, prepend_string("/", name)) */ mpath_length = strlen(MPATH); for(index = 0; index < mpath_length; index = index + 1) { require(MAX_STRING > index, "Element of PATH is too long\n"); trial[index] = MPATH[index]; } trial[index] = '/'; offset = strlen(trial); name_length = strlen(name); for(index = 0; index < name_length; index = index + 1) { require(MAX_STRING > index, "Element of PATH is too long\n"); trial[index + offset] = name[index]; } /* Try the trial */ require(strlen(trial) < MAX_STRING, "COMMAND TOO LONG!\nABORTING HARD\n"); t = fopen(trial, "r"); if(NULL != t) { fclose(t); return trial; } MPATH = next + 1; next = find_char(MPATH, ':'); } return NULL; } /* Function to convert a Token linked-list into an array of strings */ char** list_to_array(struct Token* s) { struct Token* n; n = s; char** array = calloc(MAX_ARRAY, sizeof(char*)); require(array != NULL, "Memory initialization of array in conversion of list to array failed\n"); char* element = calloc(MAX_STRING, sizeof(char)); require(element != NULL, "Memory initialization of element in conversion of list to array failed\n"); int index = 0; int i; int value_length; int var_length; int offset; while(n != NULL) { /* Loop through each node and assign it to an array index */ array[index] = calloc(MAX_STRING, sizeof(char)); require(array[index] != NULL, "Memory initialization of array[index] in conversion of list to array failed\n"); /* Bounds checking */ /* No easy way to tell which it is, output generic message */ require(index < MAX_ARRAY, "SCRIPT TOO LONG or TOO MANY ENVARS\nABORTING HARD\n"); if(n->var == NULL) { /* It is a line */ array[index] = n->value; } else { /* It is a var */ /* prepend_string(n->var, prepend_string("=", n->value)) */ var_length = strlen(n->var); for(i = 0; i < var_length; i = i + 1) { element[i] = n->var[i]; } element[i] = '='; i = i + 1; offset = i; value_length = strlen(n->value); for(i = 0; i < value_length; i = i + 1) { element[i + offset] = n->value[i]; } } /* Insert elements if not empty */ if(!match("", element)) { strcpy(array[index], element); } n = n->next; index = index + 1; /* Reset element */ for(i = 0; i < MAX_STRING; i = i + 1) { element[i] = 0; } } return array; } /* Function to handle the correct options for escapes */ int handle_escape(int c) { if(c == '\n') { /* Do nothing - eat up the newline */ return -1; } else if('n' == c) { /* Add a newline to the token */ return '\n'; } else if('r' == c) { /* Add a return to the token */ return '\r'; } else if('\\' == c) { /* Add a real backslash to the token */ return '\\'; } else { /* Just add it to the token (eg, quotes) */ return c; } } /* * TOKEN COLLECTION FUNCTIONS */ /* Function for skipping over line comments */ void collect_comment(FILE* input) { int c; /* Eat up the comment, one character at a time */ /* * Sanity check that the comment ends with \n. * Remove the comment from the FILE* */ do { c = fgetc(input); /* We reached an EOF!! */ require(EOF != c, "IMPROPERLY TERMINATED LINE COMMENT!\nABORTING HARD\n"); } while('\n' != c); /* We can now be sure it ended with \n -- and have purged the comment */ } /* Function for collecting strings and removing the "" pair that goes with them */ int collect_string(FILE* input, char* n, int index) { int string_done = FALSE; int c; do { /* Bounds check */ require(MAX_STRING > index, "LINE IS TOO LONG\nABORTING HARD\n"); c = fgetc(input); require(EOF != c, "IMPROPERLY TERMINATED STRING!\nABORTING HARD\n"); if('\\' == c) { /* We are escaping the next character */ /* This correctly handles escaped quotes as it just returns the quote */ c = fgetc(input); c = handle_escape(c); n[index] = c; index = index + 1; } else if('"' == c) { /* End of string */ string_done = TRUE; } else { n[index] = c; index = index + 1; } } while(string_done == FALSE); return index; } /* Function to parse and assign token->value */ int collect_token(FILE* input, char* n, int last_index) { int c; int cc; int token_done = FALSE; int index = 0; do { /* Loop over each character in the token */ c = fgetc(input); /* Bounds checking */ require(MAX_STRING > index, "LINE IS TOO LONG\nABORTING HARD\n"); if(EOF == c) { /* End of file -- this means script complete */ /* We don't actually exit here. This logically makes more sense; * let the code follow its natural path of execution and exit * sucessfuly at the end of main(). */ token_done = TRUE; command_done = TRUE; return -1; } else if((' ' == c) || ('\t' == c)) { /* Space and tab are token separators */ token_done = TRUE; } else if(('\n' == c) || (';' == c)) { /* Command terminates at the end of a line or at semicolon */ command_done = TRUE; token_done = TRUE; if(0 == index) { index = last_index; } } else if('"' == c) { /* Handle strings -- everything between a pair of "" */ index = collect_string(input, n, index); token_done = TRUE; } else if('#' == c) { /* Handle line comments */ collect_comment(input); command_done = TRUE; token_done = TRUE; if(0 == index) { index = last_index; } } else if('\\' == c) { /* Support for escapes */ c = fgetc(input); /* Skips over \, gets the next char */ cc = handle_escape(c); if(-1 != cc) { /* We need to put it into the token */ n[index] = cc; } index = index + 1; } else if(0 == c) { /* We have come to the end of the token */ token_done = TRUE; } else { /* It's a character to assign */ n[index] = c; index = index + 1; } } while(token_done == FALSE); return index; } /* Function to parse string and assign token->value */ int collect_alias_token(char* input, char* n, int index) { int c; int cc; int token_done = FALSE; int output_index = 0; do { /* Loop over each character in the token */ c = input[index]; index = index + 1; if((' ' == c) || ('\t' == c)) { /* Space and tab are token separators */ token_done = TRUE; } else if('\\' == c) { /* Support for escapes */ c = input[index]; index = index + 1; cc = handle_escape(c); /* We need to put it into the token */ n[output_index] = cc; output_index = output_index + 1; } else if(0 == c) { /* We have come to the end of the token */ token_done = TRUE; index = 0; } else { /* It's a character to assign */ n[output_index] = c; output_index = output_index + 1; } } while(token_done == FALSE); return index; } /* * EXECUTION FUNCTIONS * Note: All of the builtins return SUCCESS (0) when they exit successfully * and FAILURE (1) when they fail. */ /* Function to check if the token is an envar */ int is_envar(char* token) { int i = 0; int token_length = strlen(token); while(i < token_length) { if(token[i] == '=') { return FAILURE; } i = i + 1; } return SUCCESS; } /* Add an envar */ void add_envar() { /* Pointers to strings we want */ char* name = calloc(strlen(token->value) + 4, sizeof(char)); char* value = token->value; char* newvalue; int i = 0; /* Isolate the name */ while('=' != value[i]) { name[i] = value[i]; i = i + 1; } /* Isolate the value */ newvalue = name + i + 2; value = value + i + 1; i = 0; require(0 != value[i], "add_envar received improper variable\n"); while(0 != value[i]) { newvalue[i] = value[i]; i = i + 1; } /* If we are in init-mode and this is the first var env == NULL, rectify */ if(env == NULL) { env = calloc(1, sizeof(struct Token)); require(env != NULL, "Memory initialization of env failed\n"); env->var = name; /* Add our first variable */ } /* * If the name of the envar is PATH, then we need to set our (internal) * global PATH value. */ if(match(name, "PATH")) { strcpy(PATH, newvalue); } struct Token* n = env; /* Find match if possible */ while(!match(name, n->var)) { if(NULL == n->next) { n->next = calloc(1, sizeof(struct Token)); require(n->next != NULL, "Memory initialization of next env node in add_envar failed\n"); n->next->var = name; } /* Loop will match and exit */ n = n->next; } /* Since we found the variable we need only to set it to its new value */ n->value = newvalue; } /* Add an alias */ void add_alias() { token = token->next; /* Skip the actual alias */ if(token->next == NULL) { /* No arguments */ char** array = list_to_array(alias); int index = 0; while(array[index] != NULL) { fputs(array[index], stdout); fputc('\n', stdout); index = index + 1; } fflush(stdout); return; } if(!is_envar(token->value)) { char** array = list_to_array(token); int index = 0; while(array[index] != NULL) { fputs(array[index], stdout); fputc(' ', stdout); index = index + 1; } fputc('\n', stdout); fflush(stdout); return; } /* Pointers to strings we want */ char* name = calloc(strlen(token->value) + 4, sizeof(char)); char* value = token->value; char* newvalue; int i = 0; /* Isolate the name */ while('=' != value[i]) { name[i] = value[i]; i = i + 1; } /* Isolate the value */ newvalue = name + i + 2; value = value + i + 1; i = 0; require(0 != value[i], "add_alias received improper variable\n"); while(0 != value[i]) { newvalue[i] = value[i]; i = i + 1; } /* If this is the first alias, rectify */ if(alias == NULL) { alias = calloc(1, sizeof(struct Token)); require(alias != NULL, "Memory initialization of alias failed\n"); alias->var = name; /* Add our first variable */ } struct Token* n = alias; /* Find match if possible */ while(!match(name, n->var)) { if(NULL == n->next) { n->next = calloc(1, sizeof(struct Token)); require(n->next != NULL, "Memory initialization of next alias node in alias failed\n"); n->next->var = name; } /* Loop will match and exit */ n = n->next; } /* Since we found the variable we need only to set it to its new value */ n->value = newvalue; } /* cd builtin */ int cd() { if(NULL == token->next) { return FAILURE; } token = token->next; if(NULL == token->value) { return FAILURE; } int ret = chdir(token->value); if(0 > ret) { return FAILURE; } return SUCCESS; } /* pwd builtin */ int pwd() { char* path = calloc(MAX_STRING, sizeof(char)); require(path != NULL, "Memory initialization of path in pwd failed\n"); getcwd(path, MAX_STRING); require(!match("", path), "getcwd() failed\n"); fputs(path, stdout); fputs("\n", stdout); return SUCCESS; } /* set builtin */ int set() { /* Get the options */ int i; if(NULL == token->next) { goto cleanup_set; } token = token->next; if(NULL == token->value) { goto cleanup_set; } char* options = calloc(MAX_STRING, sizeof(char)); require(options != NULL, "Memory initialization of options in set failed\n"); int last_position = strlen(token->value) - 1; for(i = 0; i < last_position; i = i + 1) { options[i] = token->value[i + 1]; } /* Parse the options */ int options_length = strlen(options); for(i = 0; i < options_length; i = i + 1) { if(options[i] == 'a') { /* set -a is on by default and cannot be disabled at this time */ if(WARNINGS) { fputs("set -a is on by default and cannot be disabled\n", stdout); } continue; } else if(options[i] == 'e') { /* Fail on failure */ STRICT = TRUE; } else if(options[i] == 'x') { /* Show commands as executed */ /* TODO: this currently behaves like -v. Make it do what it should */ VERBOSE = TRUE; /* * Output the set -x because VERBOSE didn't catch it before. * We don't do just -x because we support multiple options in one command, * eg set -ex. */ fputs(" +> set -", stdout); fputs(options, stdout); fputs("\n", stdout); fflush(stdout); } else { /* Invalid */ fputc(options[i], stderr); fputs(" is an invalid set option!\n", stderr); exit(EXIT_FAILURE); } } return SUCCESS; cleanup_set: return FAILURE; } /* echo builtin */ void echo() { if(token->next == NULL) { /* No arguments */ fputs("\n", stdout); return; } if(token->next->value == NULL) { /* No arguments */ fputs("\n", stdout); return; } token = token->next; /* Skip the actual echo */ while(token != NULL) { /* Output each argument to echo to stdout */ if(token->value == NULL) { break; } fputs(token->value, stdout); if(NULL != token->next) { /* M2-Planet doesn't short circuit */ if(NULL != token->next->value) fputc(' ', stdout); } token = token->next; } fputs("\n", stdout); } /* unset builtin */ void unset() { struct Token* e; /* We support multiple variables on the same line */ struct Token* t; t = token->next; while(t != NULL) { e = env; /* Look for the variable; we operate on ->next because we need to remove ->next */ while(e->next != NULL) { if(NULL == t->value) { break; } if(match(e->next->var, t->value)) { break; } e = e->next; } t = t->next; /* If it's NULL nothing was found */ if(e->next == NULL) { continue; } /* Otherwise there is something to unset */ e->next = e->next->next; } } void execute(FILE* script, char** argv); int _execute(FILE* script, char** argv); int collect_command(FILE* script, char** argv); /* if builtin */ void if_cmd(FILE* script, char** argv) { int index; int old_VERBOSE; token = token->next; /* Skip the actual if */ /* Do not check for successful exit status */ int if_status = _execute(script, argv); old_VERBOSE = VERBOSE; VERBOSE = VERBOSE && !if_status; do { index = collect_command(script, argv); require(index != -1, "Unexpected EOF, improperly terminated if statement.\n"); if(0 == index) { continue; } if(0 == if_status) { /* Stuff to exec */ execute(script, argv); } if(match(token->value, "else")) { if_status = !if_status; } } while(!match(token->value, "fi")); VERBOSE = old_VERBOSE; } int what_exit(char* program, int status) { /*********************************************************************************** * If the low-order 8 bits of w_status are equal to 0x7F or zero, the child * * process has stopped. If the low-order 8 bits of w_status are non-zero and are * * not equal to 0x7F, the child process terminated due to a signal otherwise, the * * child process terminated due to an exit() call. * * * * In the event it was a signal that stopped the process the top 8 bits of * * w_status contain the signal that caused the process to stop. * * * * In the event it was terminated the bottom 7 bits of w_status contain the * * terminating error number for the process. * * * * If bit 0x80 of w_status is set, a core dump was produced. * ***********************************************************************************/ int WIFEXITED = !(status & 0x7F); int WEXITSTATUS = (status & 0xFF00) >> 8; int WTERMSIG = status & 0x7F; int WCOREDUMP = status & 0x80; int WIFSIGNALED = !((0x7F == WTERMSIG) || (0 == WTERMSIG)); int WIFSTOPPED = ((0x7F == WTERMSIG) && (0 == WCOREDUMP)); if(WIFEXITED) { if(VERBOSE_EXIT) { fputc('\n', stderr); fputs(program, stderr); fputs(" normal termination, exit status = ", stderr); fputs(int2str(WEXITSTATUS, 10, TRUE), stderr); fputs("\n\n\n", stderr); } return WEXITSTATUS; } else if (WIFSIGNALED) { fputc('\n', stderr); fputs(program, stderr); fputs(" abnormal termination, signal number = ", stderr); fputs(int2str(WTERMSIG, 10, TRUE), stderr); fputc('\n', stderr); if(WCOREDUMP) fputs("core dumped\n", stderr); return WTERMSIG; } else if(WIFSTOPPED) { fputc('\n', stderr); fputs(program, stderr); fputs(" child stopped, signal number = ", stderr); fputs(int2str(WEXITSTATUS, 10, TRUE), stderr); fputc('\n', stderr); return WEXITSTATUS; } fputc('\n', stderr); fputs(program, stderr); fputs(" :: something crazy happened with execve\nI'm just gonna get the hell out of here\n", stderr); exit(EXIT_FAILURE); } /* Execute program and check for error */ void execute(FILE* script, char** argv) { int status = _execute(script, argv); if(STRICT == TRUE && (0 != status)) { /* Clearly the script hit an issue that should never have happened */ fputs("Subprocess error ", stderr); fputs(int2str(status, 10, TRUE), stderr); fputs("\nABORTING HARD\n", stderr); exit(EXIT_FAILURE); } } /* Execute program */ int _execute(FILE* script, char** argv) { /* Run the command */ /* rc = return code */ int rc; /* exec without forking */ int exec = FALSE; /* Actually do the execution */ if(is_envar(token->value) == TRUE) { add_envar(); return 0; } else if(match(token->value, "cd")) { rc = cd(); if(STRICT) { require(rc == SUCCESS, "cd failed!\n"); } return 0; } else if(match(token->value, "set")) { rc = set(); if(STRICT) { require(rc == SUCCESS, "set failed!\n"); } return 0; } else if(match(token->value, "alias")) { add_alias(); return 0; } else if(match(token->value, "pwd")) { rc = pwd(); if(STRICT) { require(rc == SUCCESS, "pwd failed!\n"); } return 0; } else if(match(token->value, "echo")) { echo(); return 0; } else if(match(token->value, "unset")) { unset(); return 0; } else if(match(token->value, "exec")) { token = token->next; /* Skip the actual exec */ exec = TRUE; } else if(match(token->value, "if")) { if_cmd(script, argv); return 0; } else if(match(token->value, "then")) { /* ignore */ return 0; } else if(match(token->value, "else")) { /* ignore */ return 0; } else if(match(token->value, "fi")) { /* ignore */ return 0; } /* If it is not a builtin, run it as an executable */ int status; /* i.e. return code */ char** array; char** envp; /* Get the full path to the executable */ char* program = find_executable(token->value); /* Check we can find the executable */ if(NULL == program) { if(STRICT == TRUE) { fputs("WHILE EXECUTING ", stderr); fputs(token->value, stderr); fputs(" NOT FOUND!\nABORTING HARD\n", stderr); exit(EXIT_FAILURE); } /* If we are not strict simply return */ return 0; } int f = 0; #ifdef __uefi__ array = list_to_array(token); envp = list_to_array(env); return spawn(program, array, envp); #else if(!exec) { f = fork(); } /* Ensure fork succeeded */ if(f == -1) { fputs("WHILE EXECUTING ", stderr); fputs(token->value, stderr); fputs(" fork() FAILED\nABORTING HARD\n", stderr); exit(EXIT_FAILURE); } else if(f == 0) { /* Child */ /************************************************************** * Fuzzing produces random stuff; we don't want it running * * dangerous commands. So we just don't execve. * * But, we still do the list_to_array calls to check for * * segfaults. * **************************************************************/ array = list_to_array(token); envp = list_to_array(env); if(FALSE == FUZZING) { /* We are not fuzzing */ /* execve() returns only on error */ execve(program, array, envp); } /* Prevent infinite loops */ _exit(EXIT_FAILURE); } /* Otherwise we are the parent */ /* And we should wait for it to complete */ waitpid(f, &status, 0); return what_exit(program, status); #endif } int collect_command(FILE* script, char** argv) { command_done = FALSE; /* Initialize token */ struct Token* n; n = calloc(1, sizeof(struct Token)); require(n != NULL, "Memory initialization of token in collect_command failed\n"); char* s = calloc(MAX_STRING, sizeof(char)); require(s != NULL, "Memory initialization of token in collect_command failed\n"); token = n; int index = 0; int alias_index; char* alias_string; /* Get the tokens */ while(command_done == FALSE) { index = collect_token(script, s, index); /* Don't allocate another node if the current one yielded nothing, OR * if we are done. */ if(match(s, "")) { continue; } alias_string = alias_lookup(s); alias_index = 0; do { if(alias_string != NULL) { alias_index = collect_alias_token(alias_string, s, alias_index); } /* add to token */ n->value = s; s = calloc(MAX_STRING, sizeof(char)); require(s != NULL, "Memory initialization of next token node in collect_command failed\n"); /* Deal with variables */ handle_variables(argv, n); /* If the variable expands into nothing */ if(match(n->value, " ")) { n->value = NULL; continue; } /* Prepare for next loop */ n->next = calloc(1, sizeof(struct Token)); require(n->next != NULL, "Memory initialization of next token node in collect_command failed\n"); n = n->next; } while(alias_index != 0); } /* -1 means the script is done */ if(EOF == index) { return index; } /* Output the command if verbose is set */ /* Also if there is nothing in the command skip over */ if(VERBOSE && !match(token->value, "") && !match(token->value, NULL)) { n = token; fputs(" +>", stdout); while(n != NULL) { /* Print out each token token */ fputs(" ", stdout); /* M2-Planet doesn't let us do this in the while */ if(n->value != NULL) { if(!match(n->value, "")) { fputs(n->value, stdout); } } n = n->next; } fputc('\n', stdout); fflush(stdout); } return index; } /* Function for executing our programs with desired arguments */ void run_script(FILE* script, char** argv) { int index; while(TRUE) { /* * Tokens has to be reset each time, as we need a new linked-list for * each line. * See, the program flows like this as a high level overview: * Get line -> Sanitize line and perform variable replacement etc -> * Execute line -> Next. * We don't need the previous lines once they are done with, so tokens * are hence for each line. */ index = collect_command(script, argv); /* -1 means the script is done */ if(EOF == index) { break; } if(0 == index) { continue; } /* Stuff to exec */ execute(script, argv); } } /* Function to populate env */ void populate_env(char** envp) { /* You can't populate a NULL environment */ if(NULL == envp) { return; } /* avoid empty arrays */ int max = array_length(envp); if(0 == max) { return; } /* Initialize env and n */ env = calloc(1, sizeof(struct Token)); require(env != NULL, "Memory initialization of env failed\n"); struct Token* n; n = env; int i; int j; int k; char* envp_line; for(i = 0; i < max; i = i + 1) { n->var = calloc(MAX_STRING, sizeof(char)); require(n->var != NULL, "Memory initialization of n->var in population of env failed\n"); n->value = calloc(MAX_STRING, sizeof(char)); require(n->value != NULL, "Memory initialization of n->var in population of env failed\n"); j = 0; /* * envp is weird. * When referencing envp[i]'s characters directly, they were all jumbled. * So just copy envp[i] to envp_line, and work with that - that seems * to fix it. */ envp_line = calloc(MAX_STRING, sizeof(char)); require(envp_line != NULL, "Memory initialization of envp_line in population of env failed\n"); strcpy(envp_line, envp[i]); while(envp_line[j] != '=') { /* Copy over everything up to = to var */ n->var[j] = envp_line[j]; j = j + 1; } /* If we get strange input, we need to ignore it */ if(n->var == NULL) { continue; } j = j + 1; /* Skip over = */ k = 0; /* As envp[i] will continue as j but n->value begins at 0 */ while(envp_line[j] != 0) { /* Copy everything else to value */ n->value[k] = envp_line[j]; j = j + 1; k = k + 1; } /* Sometimes, we get lines like VAR=, indicating nothing is in the variable */ if(n->value == NULL) { n->value = ""; } /* Advance to next part of linked list */ n->next = calloc(1, sizeof(struct Token)); require(n->next != NULL, "Memory initialization of n->next in population of env failed\n"); n = n->next; } /* Get rid of node on the end */ n = NULL; /* Also destroy the n->next reference */ n = env; while(n->next->var != NULL) { n = n->next; } n->next = NULL; } int main(int argc, char** argv, char** envp) { VERBOSE = FALSE; VERBOSE_EXIT = FALSE; STRICT = TRUE; FUZZING = FALSE; WARNINGS = FALSE; char* filename = "kaem.run"; FILE* script = NULL; /* Initalize structs */ token = calloc(1, sizeof(struct Token)); require(token != NULL, "Memory initialization of token failed\n"); if(NULL != argv[0]) KAEM_BINARY = argv[0]; else KAEM_BINARY = "./bin/kaem"; int i = 1; /* Loop over arguments */ while(i <= argc) { if(NULL == argv[i]) { /* Ignore the argument */ i = i + 1; } else if(match(argv[i], "-h") || match(argv[i], "--help")) { /* Help information */ fputs("Usage: ", stdout); fputs(argv[0], stdout); fputs(" [-h | --help] [-V | --version] [--file filename | -f filename] [-i | --init-mode] [-v | --verbose] [--non-strict] [--warn] [--fuzz]\n", stdout); exit(EXIT_SUCCESS); } else if(match(argv[i], "-f") || match(argv[i], "--file")) { /* Set the filename */ if(argv[i + 1] != NULL) { filename = argv[i + 1]; } i = i + 2; } else if(match(argv[i], "-i") || match(argv[i], "--init-mode")) { /* init mode does not populate env */ INIT_MODE = TRUE; i = i + 1; } else if(match(argv[i], "-V") || match(argv[i], "--version")) { /* Output version */ fputs("kaem version 1.4.0\n", stdout); exit(EXIT_SUCCESS); } else if(match(argv[i], "-v") || match(argv[i], "--verbose")) { /* Set verbose */ VERBOSE = TRUE; i = i + 1; } else if(match(argv[i], "--strict")) { /* it is a NOP */ STRICT = TRUE; i = i + 1; } else if(match(argv[i], "--non-strict")) { /* Set strict */ STRICT = FALSE; i = i + 1; } else if(match(argv[i], "--warn")) { /* Set warnings */ WARNINGS = TRUE; i = i + 1; } else if(match(argv[i], "--fuzz")) { /* Set fuzzing */ FUZZING = TRUE; i = i + 1; } else if(match(argv[i], "--show-exit-codes")) { /* show exit codes */ VERBOSE_EXIT = TRUE; i = i + 1; } else if(match(argv[i], "--")) { /* Nothing more after this */ break; } else { /* We don't know this argument */ fputs("UNKNOWN ARGUMENT\n", stdout); exit(EXIT_FAILURE); } } /* Populate env */ if(INIT_MODE == FALSE) { populate_env(envp); } /* make sure SHELL is set */ if(NULL == env_lookup("SHELL")) { struct Token* shell = calloc(1, sizeof(struct Token)); require(NULL != shell, "unable to create SHELL environment variable\n"); shell->next = env; shell->var = "SHELL"; shell->value= KAEM_BINARY; env = shell; } /* Populate PATH variable * We don't need to calloc() because env_lookup() does this for us. */ PATH = env_lookup("PATH"); /* Populate USERNAME variable */ char* USERNAME = env_lookup("LOGNAME"); /* Handle edge cases */ if((NULL == PATH) && (NULL == USERNAME)) { /* We didn't find either of PATH or USERNAME -- use a generic PATH */ PATH = calloc(MAX_STRING, sizeof(char)); require(PATH != NULL, "Memory initialization of PATH failed\n"); strcpy(PATH, "/root/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"); } else if(NULL == PATH) { /* We did find a username but not a PATH -- use a generic PATH but with /home/USERNAME */ PATH = calloc(MAX_STRING, sizeof(char)); PATH = strcat(PATH, "/home/"); PATH = strcat(PATH, USERNAME); PATH = strcat(PATH, "/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games"); } /* Open the script */ script = fopen(filename, "r"); if(NULL == script) { fputs("The file: ", stderr); fputs(filename, stderr); fputs(" can not be opened!\n", stderr); exit(EXIT_FAILURE); } /* Run the commands */ run_script(script, argv); /* Cleanup */ fclose(script); return EXIT_SUCCESS; }
/* Copyright (C) 2016 Jeremiah Orians * Copyright (C) 2020 deesix <deesix@tuta.io> * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> #include <stdio.h> #include <string.h> #define FALSE 0 #define TRUE 1 int in_set(int c, char* s); int match(char* a, char* b); void require(int bool, char* error); char* int2str(int x, int base, int signed_p); void reset_hold_string(); struct type { struct type* next; int size; int offset; int is_signed; struct type* indirect; struct type* members; struct type* type; char* name; }; struct token_list { struct token_list* next; union { struct token_list* locals; struct token_list* prev; }; char* s; union { struct type* type; char* filename; }; union { struct token_list* arguments; struct token_list* expansion; int depth; int linenumber; }; }; #include "cc_globals.h"
/* Copyright (C) 2016 Jeremiah Orians * Copyright (C) 2020 deesix <deesix@tuta.io> * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ /* What types we have */ struct type* global_types; struct type* prim_types; /* What we are currently working on */ struct token_list* global_token; /* Output reorder collections*/ struct token_list* output_list; struct token_list* strings_list; struct token_list* globals_list; /* Make our string collection more efficient */ char* hold_string; int string_index; int MAX_STRING; /* enable preprocessor-only mode */ int PREPROCESSOR_MODE; /* enable spawn behavior to be effective */ char* M2LIBC_PATH; char* Architecture; char* OperatingSystem; int WORDSIZE; int ENDIAN; char* BASEADDRESS; int STDIO_USED; char* TEMPDIR; /* So we don't shoot ourself in the face */ int FUZZING; int DIRTY_MODE; int DEBUG_LEVEL;
/* Copyright (C) 2016 Jeremiah Orians * Copyright (C) 2020 deesix <deesix@tuta.io> * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #include"cc.h" #include <sys/utsname.h> void init_macro_env(char* sym, char* value, char* source, int num); char* env_lookup(char* variable); void clear_string(char* s); struct utsname* get_uname_data() { struct utsname* unameData = calloc(1, sizeof(struct utsname)); require(NULL != unameData, "unameData calloc failed\n"); uname(unameData); if(4 <= DEBUG_LEVEL) { fputs("utsname details: ", stderr); fputs(unameData->sysname, stderr); fputc(' ', stderr); fputs(unameData->machine, stderr); fputc('\n', stderr); } return unameData; } void setup_env() { if(2 <= DEBUG_LEVEL) fputs("Starting setup_env\n", stderr); char* ARCH; if(NULL != Architecture) { ARCH = Architecture; } else { ARCH = NULL; struct utsname* unameData = get_uname_data(); if(match("i386", unameData->machine) || match("i486", unameData->machine) || match("i586", unameData->machine) || match("i686", unameData->machine) || match("i686-pae", unameData->machine)) ARCH = "x86"; else if(match("x86_64", unameData->machine)) ARCH = "amd64"; else ARCH = unameData->machine; if(3 <= DEBUG_LEVEL) { fputs("Architecture selected: ", stderr); fputs(ARCH, stderr); fputc('\n', stderr); } /* Check for override */ char* hold = env_lookup("ARCHITECTURE_OVERRIDE"); if(NULL != hold) { ARCH = hold; if(3 <= DEBUG_LEVEL) { fputs("environmental override for ARCH: ", stderr); fputs(ARCH, stderr); fputc('\n', stderr); } } free(unameData); } /* Set desired architecture */ WORDSIZE = 32; ENDIAN = FALSE; BASEADDRESS = "0x0"; if(match("knight-native", ARCH)) { if(4 <= DEBUG_LEVEL) fputs("Using knight-native architecture\n", stderr); ENDIAN = TRUE; Architecture = "knight-native"; } else if(match("knight-posix", ARCH)) { if(4 <= DEBUG_LEVEL) fputs("Using knight-posix architecture\n", stderr); ENDIAN = TRUE; Architecture = "knight-posix"; } else if(match("x86", ARCH)) { if(4 <= DEBUG_LEVEL) fputs("Using x86 architecture\n", stderr); BASEADDRESS = "0x8048000"; Architecture = "x86"; init_macro_env("__i386__", "1", "--architecture", 0); } else if(match("amd64", ARCH)) { if(4 <= DEBUG_LEVEL) fputs("Using amd64 architecture\n", stderr); BASEADDRESS = "0x00600000"; Architecture = "amd64"; WORDSIZE = 64; init_macro_env("__x86_64__", "1", "--architecture", 0); } else if(match("armv7l", ARCH)) { if(4 <= DEBUG_LEVEL) fputs("Using armv7l architecture\n", stderr); BASEADDRESS = "0x10000"; Architecture = "armv7l"; init_macro_env("__arm__", "1", "--architecture", 0); } else if(match("aarch64", ARCH)) { if(4 <= DEBUG_LEVEL) fputs("Using aarch64 architecture\n", stderr); BASEADDRESS = "0x400000"; Architecture = "aarch64"; WORDSIZE = 64; init_macro_env("__aarch64__", "1", "--architecture", 0); } else if(match("riscv32", ARCH)) { if(4 <= DEBUG_LEVEL) fputs("Using riscv32 architecture\n", stderr); BASEADDRESS = "0x600000"; Architecture = "riscv32"; init_macro_env("__riscv", "1", "--architecture", 0); init_macro_env("__riscv_xlen", "32", "--architecture", 1); } else if(match("riscv64", ARCH)) { if(4 <= DEBUG_LEVEL) fputs("Using riscv64 architecture\n", stderr); BASEADDRESS = "0x600000"; Architecture = "riscv64"; WORDSIZE = 64; init_macro_env("__riscv", "1", "--architecture", 0); init_macro_env("__riscv_xlen", "64", "--architecture", 1); } else { fputs("Unknown architecture: ", stderr); fputs(ARCH, stderr); fputs(" know values are: knight-native, knight-posix, x86, amd64, armv7l, aarch64, riscv32 and riscv64\n", stderr); exit(EXIT_FAILURE); } /* Setup Operating System */ if(NULL == OperatingSystem) { OperatingSystem = "Linux"; if(3 <= DEBUG_LEVEL) { fputs("Operating System selected: ", stderr); fputs(OperatingSystem, stderr); fputc('\n', stderr); } /* Check for override */ char* hold = env_lookup("OS_OVERRIDE"); if(NULL != hold) { OperatingSystem = hold; if(3 <= DEBUG_LEVEL) { fputs("environmental override for OS: ", stderr); fputs(OperatingSystem, stderr); fputc('\n', stderr); } } } if(match("UEFI", OperatingSystem)) { if(4 <= DEBUG_LEVEL) fputs("Using UEFI\n", stderr); BASEADDRESS = "0x0"; OperatingSystem = "UEFI"; init_macro_env("__uefi__", "1", "--os", 0); } if(2 <= DEBUG_LEVEL) fputs("setup_env successful\n", stderr); } struct Token { /* * For the token linked-list, this stores the token; for the env linked-list * this stores the value of the variable. */ char* value; /* * Used only for the env linked-list. It holds a string containing the * name of the var. */ char* var; /* * This struct stores a node of a singly linked list, store the pointer to * the next node. */ struct Token* next; }; struct Token* env; int array_length(char** array) { int length = 0; while(array[length] != NULL) { length = length + 1; } return length; } /* Search for a variable in the token linked-list */ char* token_lookup(char* variable, struct Token* token) { if(6 <= DEBUG_LEVEL) { fputs("in token_lookup\nLooking for: ", stderr); fputs(variable, stderr); fputc('\n', stderr); } /* Start at the head */ struct Token* n = token; /* Loop over the linked-list */ while(n != NULL) { if(15 <= DEBUG_LEVEL) { fputs(n->var, stderr); fputc('\n', stderr); } if(match(variable, n->var)) { if(6 <= DEBUG_LEVEL) fputs("match found in token_lookup\n", stderr); /* We have found the correct node */ return n->value; /* Done */ } /* Nope, try the next */ n = n->next; } /* We didn't find anything! */ return NULL; } /* Search for a variable in the env linked-list */ char* env_lookup(char* variable) { return token_lookup(variable, env); } char* envp_hold; int envp_index; void reset_envp_hold() { clear_string(envp_hold); envp_index = 0; } void push_env_byte(int c) { envp_hold[envp_index] = c; envp_index = envp_index + 1; require(4096 > envp_index, "Token exceeded 4096 char envp limit\n"); } struct Token* process_env_variable(char* envp_line, struct Token* n) { struct Token* node = calloc(1, sizeof(struct Token)); require(node != NULL, "Memory initialization of node failed\n"); reset_envp_hold(); int i = 0; while(envp_line[i] != '=') { /* Copy over everything up to = to var */ push_env_byte(envp_line[i]); i = i + 1; } node->var = calloc(i + 2, sizeof(char)); require(node->var != NULL, "Memory initialization of n->var in population of env failed\n"); strcpy(node->var, envp_hold); i = i + 1; /* Skip over = */ reset_envp_hold(); while(envp_line[i] != 0) { /* Copy everything else to value */ push_env_byte(envp_line[i]); i = i + 1; } /* Sometimes, we get lines like VAR=, indicating nothing is in the variable */ if(0 == strlen(envp_hold)) { node->value = ""; } else { /* but looks like we got something so, lets use it */ node->value = calloc(strlen(envp_hold) + 2, sizeof(char)); require(node->value != NULL, "Memory initialization of n->var in population of env failed\n"); strcpy(node->value, envp_hold); } node->next = n; return node; } void populate_env(char** envp) { if(2 <= DEBUG_LEVEL) fputs("populate_env started\n", stderr); /* You can't populate a NULL environment */ if(NULL == envp) { if(3 <= DEBUG_LEVEL) fputs("NULL envp\n", stderr); return; } /* avoid empty arrays */ int max = array_length(envp); if(0 == max) { if(3 <= DEBUG_LEVEL) fputs("Empty envp\n", stderr); return; } /* Initialize env and n */ env = NULL; int i; envp_hold = calloc(4096, sizeof(char)); require(envp_hold != NULL, "Memory initialization of envp_hold in population of env failed\n"); char* envp_line = calloc(4096, sizeof(char)); require(envp_line != NULL, "Memory initialization of envp_line in population of env failed\n"); if(3 <= DEBUG_LEVEL) fputs("starting env loop\n", stderr); for(i = 0; i < max; i = i + 1) { /* * envp is weird. * When referencing envp[i]'s characters directly, they were all jumbled. * So just copy envp[i] to envp_line, and work with that - that seems * to fix it. */ clear_string(envp_line); require(4096 > strlen(envp[i]), "envp line exceeds 4096byte limit\n"); strcpy(envp_line, envp[i]); if(9 <= DEBUG_LEVEL) { fputs("trying envp_line: ", stderr); fputs(envp_line, stderr); fputc('\n', stderr); } env = process_env_variable(envp_line, env); if(9 <= DEBUG_LEVEL) { fputs("got var of: ", stderr); fputs(env->var, stderr); fputs("\nAnd value of: ", stderr); fputs(env->value, stderr); fputc('\n', stderr); } } free(envp_line); free(envp_hold); if(3 <= DEBUG_LEVEL) { fputs("\n\nenv loop successful\n", stderr); fputs(int2str(i, 10, FALSE), stderr); fputs(" envp records processed\n\n", stderr); } require(NULL != env, "can't have an empty environment from the creation of a non-null environment\n"); if(2 <= DEBUG_LEVEL) fputs("populate_env successful\n", stderr); }
/* Copyright (C) 2016 Jeremiah Orians * Copyright (C) 2021 Andrius Å tikonas <andrius@stikonas.eu> * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #include "cc.h" char* env_lookup(char* variable); char* int2str(int x, int base, int signed_p); struct visited { struct visited* prev; char* name; }; /* Globals */ FILE* input; struct token_list* token; int line; char* file; struct visited* vision; int previously_seen(char* s) { struct visited* v = vision; while(NULL != v) { if(match(v->name, s)) return TRUE; v = v->prev; } return FALSE; } void just_seen(char* s) { struct visited* hold = calloc(1, sizeof(struct visited)); hold->prev = vision; hold->name = s; vision = hold; } int grab_byte() { int c = fgetc(input); if(10 == c) line = line + 1; return c; } void push_byte(int c) { hold_string[string_index] = c; string_index = string_index + 1; require(MAX_STRING > string_index, "Token exceeded MAX_STRING char limit\nuse --max-string number to increase\n"); } int consume_byte(int c) { push_byte(c); return grab_byte(); } int preserve_string(int c) { int frequent = c; int escape = FALSE; do { if(!escape && '\\' == c ) escape = TRUE; else escape = FALSE; c = consume_byte(c); require(EOF != c, "Unterminated string\n"); } while(escape || (c != frequent)); c = consume_byte(frequent); return c; } void copy_string(char* target, char* source, int max) { int i = 0; while(0 != source[i]) { target[i] = source[i]; i = i + 1; if(i == max) break; } } int preserve_keyword(int c, char* S) { while(in_set(c, S)) { c = consume_byte(c); } return c; } void clear_string(char* s) { int i = 0; while(0 != s[i]) { s[i] = 0; i = i + 1; require(i < MAX_STRING, "string exceeded max string size while clearing string\n"); } } void reset_hold_string() { clear_string(hold_string); string_index = 0; } /* note if this is the first token in the list, head needs fixing up */ struct token_list* eat_token(struct token_list* token) { if(NULL != token->prev) { token->prev->next = token->next; } /* update backlinks */ if(NULL != token->next) { token->next->prev = token->prev; } return token->next; } void new_token(char* s, int size) { struct token_list* current = calloc(1, sizeof(struct token_list)); require(NULL != current, "Exhausted memory while getting token\n"); /* More efficiently allocate memory for string */ current->s = calloc(size, sizeof(char)); require(NULL != current->s, "Exhausted memory while trying to copy a token\n"); copy_string(current->s, s, MAX_STRING); current->prev = token; current->next = token; current->linenumber = line; current->filename = file; token = current; } int get_token(int c) { reset_hold_string(); if(c == EOF) { return c; } else if((32 == c) || (9 == c) || (c == '\n')) { c = consume_byte(c); } else if('#' == c) { c = consume_byte(c); c = preserve_keyword(c, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"); } else if(in_set(c, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_")) { c = preserve_keyword(c, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_:"); } else if(in_set(c, "<=>|&!^%")) { c = preserve_keyword(c, "<=>|&!^%"); } else if(in_set(c, "'\"")) { c = preserve_string(c); } else if(c == '/') { c = consume_byte(c); if(c == '*') { c = consume_byte(c); while(c != '/') { while(c != '*') { c = consume_byte(c); require(EOF != c, "Hit EOF inside of block comment\n"); } c = consume_byte(c); require(EOF != c, "Hit EOF inside of block comment\n"); } c = consume_byte(c); } else if(c == '/') { while(c != '\n') { c = consume_byte(c); require(EOF != c, "Hit EOF inside of line comment\n"); } c = consume_byte(c); } else if(c == '=') { c = consume_byte(c); } } else if(c == '*') { c = consume_byte(c); if(c == '=') { c = consume_byte(c); } } else if(c == '+') { c = consume_byte(c); if(c == '=') { c = consume_byte(c); } if(c == '+') { c = consume_byte(c); } } else if(c == '-') { c = consume_byte(c); if(c == '=') { c = consume_byte(c); } if(c == '>') { c = consume_byte(c); } if(c == '-') { c = consume_byte(c); } } else { c = consume_byte(c); } return c; } struct token_list* reverse_list(struct token_list* head) { struct token_list* root = NULL; struct token_list* next; while(NULL != head) { next = head->next; head->next = root; root = head; head = next; } return root; } int read_include(int c) { reset_hold_string(); int done = FALSE; int ch; while(!done) { if(c == EOF) { fputs("we don't support EOF as a filename in #include statements\n", stderr); exit(EXIT_FAILURE); } else if((32 == c) || (9 == c) || (c == '\n')) { c = grab_byte(); } else if(('"' == c) || ('<' == c)) { if('<' == c) c = '>'; ch = c; do { c = consume_byte(c); require(EOF != c, "Unterminated filename in #include\n"); } while(c != ch); if('>' == ch) hold_string[0] = '<'; done = TRUE; } } return c; } void insert_file_header(char* name, int line) { char* hold_line = int2str(line, 10, FALSE); reset_hold_string(); strcat(hold_string, "// #FILENAME "); strcat(hold_string, name); strcat(hold_string, " "); strcat(hold_string, hold_line); new_token(hold_string, strlen(hold_string)+2); new_token("\n", 3); } struct token_list* read_all_tokens(FILE* a, struct token_list* current, char* filename, int include); int include_file(int ch, int include_file) { /* The old state to restore to */ char* hold_filename = file; FILE* hold_input = input; int hold_number; /* The new file to load */ char* new_filename; FILE* new_file; require(EOF != ch, "#include failed to receive filename\n"); /* Remove the #include */ token = token->next; /* Get new filename */ read_include(ch); /* with just a little extra to put in the matching at the end */ new_token(hold_string, string_index + 3); ch = '\n'; new_filename = token->s; /* Remove name from stream */ token = token->next; /* Try to open the file */ if('<' == new_filename[0]) { if(match("stdio.h", new_filename + 1)) STDIO_USED = TRUE; reset_hold_string(); strcat(hold_string, M2LIBC_PATH); strcat(hold_string, "/"); strcat(hold_string, new_filename + 1); strcat(new_filename, ">"); new_file = fopen(hold_string, "r"); } else { if(match("M2libc/bootstrappable.h", new_filename+1)) { reset_hold_string(); strcat(hold_string, M2LIBC_PATH); strcat(hold_string, "/bootstrappable.h"); new_file = fopen(hold_string, "r"); } else new_file = fopen(new_filename+1, "r"); strcat(new_filename, "\""); } /* prevent multiple visits */ if(previously_seen(new_filename)) return ch; just_seen(new_filename); /* special case this compatibility crap */ if(match("\"../gcc_req.h\"", new_filename) || match("\"gcc_req.h\"", new_filename)) return ch; if(include_file) { fputs("reading file: ", stderr); fputs(new_filename, stderr); fputc('\n', stderr); } /* catch garbage input */ if(NULL == new_file) { fputs("unable to read file: ", stderr); fputs(new_filename, stderr); fputs("\nAborting hard!\n", stderr); exit(EXIT_FAILURE); } /* protect our current line number */ hold_number = line + 1; /* Read the new file */ if(include_file) read_all_tokens(new_file, token, new_filename, include_file); /* put back old file info */ insert_file_header(hold_filename, hold_number); /* resume reading old file */ input = hold_input; line = hold_number; file = hold_filename; return ch; } struct token_list* read_all_tokens(FILE* a, struct token_list* current, char* filename, int include) { token = current; insert_file_header(filename, 1); input = a; line = 1; file = filename; int ch = grab_byte(); while(EOF != ch) { ch = get_token(ch); new_token(hold_string, string_index + 2); if(match("#include", token->s)) ch = include_file(ch, include); } return token; }
/* Copyright (C) 2016 Jeremiah Orians * Copyright (C) 2020 deesix <deesix@tuta.io> * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #include"cc.h" #include <unistd.h> #include <sys/wait.h> #define MAX_ARRAY 256 char* env_lookup(char* variable); /* Function to find a character in a string */ char* find_char(char* string, char a) { if(0 == string[0]) { return NULL; } while(a != string[0]) { string = string + 1; if(0 == string[0]) { return string; } } return string; } /* Find the full path to an executable */ char* find_executable(char* name) { char* PATH = env_lookup("PATH"); require(NULL != PATH, "No PATH found\nAborting\n"); if(match("", name)) { return NULL; } if(('.' == name[0]) || ('/' == name[0])) { /* assume names that start with . or / are relative or absolute */ return name; } char* trial = calloc(MAX_STRING, sizeof(char)); char* MPATH = calloc(MAX_STRING, sizeof(char)); /* Modified PATH */ require(MPATH != NULL, "Memory initialization of MPATH in find_executable failed\n"); strcpy(MPATH, PATH); FILE* t; char* next = find_char(MPATH, ':'); int index; int offset; int mpath_length; int name_length; int trial_length; while(NULL != next) { /* Reset trial */ trial_length = strlen(trial); for(index = 0; index < trial_length; index = index + 1) { trial[index] = 0; } next[0] = 0; /* prepend_string(MPATH, prepend_string("/", name)) */ mpath_length = strlen(MPATH); for(index = 0; index < mpath_length; index = index + 1) { require(MAX_STRING > index, "Element of PATH is too long\n"); trial[index] = MPATH[index]; } trial[index] = '/'; offset = strlen(trial); name_length = strlen(name); for(index = 0; index < name_length; index = index + 1) { require(MAX_STRING > index, "Element of PATH is too long\n"); trial[index + offset] = name[index]; } /* Try the trial */ trial_length = strlen(trial); require(trial_length < MAX_STRING, "COMMAND TOO LONG!\nABORTING HARD\n"); t = fopen(trial, "r"); if(NULL != t) { fclose(t); return trial; } MPATH = next + 1; next = find_char(MPATH, ':'); } return NULL; } void sanity_command_check(char** array) { int i = 0; char* s = array[0]; while(NULL != s) { fputs(s, stderr); fputc(' ', stderr); i = i + 1; s = array[i]; } fputc('\n', stderr); } int what_exit(char* program, int status) { /*********************************************************************************** * If the low-order 8 bits of w_status are equal to 0x7F or zero, the child * * process has stopped. If the low-order 8 bits of w_status are non-zero and are * * not equal to 0x7F, the child process terminated due to a signal otherwise, the * * child process terminated due to an exit() call. * * * * In the event it was a signal that stopped the process the top 8 bits of * * w_status contain the signal that caused the process to stop. * * * * In the event it was terminated the bottom 7 bits of w_status contain the * * terminating error number for the process. * * * * If bit 0x80 of w_status is set, a core dump was produced. * ***********************************************************************************/ if(DEBUG_LEVEL > 6) { fputs("in what_exit with char* program of: ", stderr); fputs(program, stderr); fputs("\nAnd int status of: 0x", stderr); fputs(int2str(status, 16, FALSE), stderr); fputc('\n', stderr); } int WIFEXITED = !(status & 0x7F); int WEXITSTATUS = (status & 0xFF00) >> 8; int WTERMSIG = status & 0x7F; int WCOREDUMP = status & 0x80; int WIFSIGNALED = !((0x7F == WTERMSIG) || (0 == WTERMSIG)); int WIFSTOPPED = ((0x7F == WTERMSIG) && (0 == WCOREDUMP)); if(WIFEXITED) { if(DEBUG_LEVEL > 2) { fputc('\n', stderr); fputs(program, stderr); fputs(" normal termination, exit status = ", stderr); fputs(int2str(WEXITSTATUS, 10, TRUE), stderr); fputc('\n', stderr); } return WEXITSTATUS; } else if (WIFSIGNALED) { fputc('\n', stderr); fputs(program, stderr); fputs(" abnormal termination, signal number = ", stderr); fputs(int2str(WTERMSIG, 10, TRUE), stderr); fputc('\n', stderr); if(WCOREDUMP) fputs("core dumped\n", stderr); return WTERMSIG; } else if(WIFSTOPPED) { fputc('\n', stderr); fputs(program, stderr); fputs(" child stopped, signal number = ", stderr); fputs(int2str(WEXITSTATUS, 10, TRUE), stderr); fputc('\n', stderr); return WEXITSTATUS; } fputc('\n', stderr); fputs(program, stderr); fputs(" :: something crazy happened with execve\nI'm just gonna get the hell out of here\n", stderr); exit(EXIT_FAILURE); } void _execute(char* name, char** array, char** envp) { int status; /* i.e. return code */ /* Get the full path to the executable */ char* program = find_executable(name); /* Check we can find the executable */ if(NULL == program) { fputs("WHILE EXECUTING ", stderr); fputs(name, stderr); fputs(" NOT FOUND!\nABORTING HARD\n", stderr); exit(EXIT_FAILURE); } sanity_command_check(array); int result; #ifdef __uefi__ result = spawn(program, array, envp); #else int f = fork(); /* Ensure fork succeeded */ if(f == -1) { fputs("WHILE EXECUTING ", stderr); fputs(name, stderr); fputs("fork() FAILED\nABORTING HARD\n", stderr); exit(EXIT_FAILURE); } else if(f == 0) { /* Child */ /************************************************************** * Fuzzing produces random stuff; we don't want it running * * dangerous commands. So we just don't execve. * **************************************************************/ if(FALSE == FUZZING) { /* We are not fuzzing */ /* execve() returns only on error */ execve(program, array, envp); fputs("Unable to execute: ", stderr); fputs(program, stderr); fputs("\nPlease check file permissions and that it is a valid binary\n", stderr); } /* Prevent infinite loops */ _exit(EXIT_FAILURE); } /* Otherwise we are the parent */ /* And we should wait for it to complete */ waitpid(f, &status, 0); result = what_exit(program, status); #endif if(0 != result) { fputs("Subprocess: ", stderr); fputs(program, stderr); fputs(" error\nAborting for safety\n", stderr); exit(result); } } void insert_array(char** array, int index, char* string) { int size = strlen(string); array[index] = calloc(size+2, sizeof(char)); strcpy(array[index], string); } void spawn_hex2(char* input, char* output, char* architecture, char** envp, int debug) { char* hex2; #ifdef __uefi__ hex2 = "hex2.efi"; #else hex2 = "hex2"; #endif char* elf_header = calloc(MAX_STRING, sizeof(char)); elf_header = strcat(elf_header, M2LIBC_PATH); elf_header = strcat(elf_header, "/"); elf_header = strcat(elf_header, architecture); if(match("UEFI", OperatingSystem)) elf_header = strcat(elf_header, "/uefi/PE32-"); else elf_header = strcat(elf_header, "/ELF-"); elf_header = strcat(elf_header, architecture); if(debug) { elf_header = strcat(elf_header, "-debug.hex2"); } else { elf_header = strcat(elf_header, ".hex2"); } fputs("# starting hex2 linking\n", stdout); char** array = calloc(MAX_ARRAY, sizeof(char*)); insert_array(array, 0, hex2); insert_array(array, 1, "--file"); insert_array(array, 2, elf_header); insert_array(array, 3, "--file"); insert_array(array, 4, input); insert_array(array, 5, "--output"); insert_array(array, 6, output); insert_array(array, 7, "--architecture"); insert_array(array, 8, architecture); insert_array(array, 9, "--base-address"); insert_array(array, 10, BASEADDRESS); if(ENDIAN) { insert_array(array, 11, "--big-endian"); } else { insert_array(array, 11, "--little-endian"); } _execute(hex2, array, envp); } void spawn_M1(char* input, char* debug_file, char* output, char* architecture, char** envp, int debug_flag) { char* M1; #ifdef __uefi__ M1 = "M1.efi"; #else M1 = "M1"; #endif fputs("# starting M1 assembly\n", stdout); char* definitions = calloc(MAX_STRING, sizeof(char)); definitions = strcat(definitions, M2LIBC_PATH); definitions = strcat(definitions, "/"); definitions = strcat(definitions, architecture); definitions = strcat(definitions, "/"); definitions = strcat(definitions, architecture); definitions = strcat(definitions, "_defs.M1"); char* libc = calloc(MAX_STRING, sizeof(char)); libc = strcat(libc, M2LIBC_PATH); libc = strcat(libc, "/"); libc = strcat(libc, architecture); if(match("UEFI", OperatingSystem)) { libc = strcat(libc, "/uefi/libc-full.M1"); } else if(STDIO_USED) { libc = strcat(libc, "/libc-full.M1"); } else { libc = strcat(libc, "/libc-core.M1"); } char** array = calloc(MAX_ARRAY, sizeof(char*)); insert_array(array, 0, M1); insert_array(array, 1, "--file"); insert_array(array, 2, definitions); insert_array(array, 3, "--file"); insert_array(array, 4, libc); insert_array(array, 5, "--file"); insert_array(array, 6, input); if(ENDIAN) { insert_array(array, 7, "--big-endian"); } else { insert_array(array, 7, "--little-endian"); } insert_array(array, 8, "--architecture"); insert_array(array, 9, architecture); if(debug_flag) { insert_array(array, 10, "--file"); insert_array(array, 11, debug_file); insert_array(array, 12, "--output"); insert_array(array, 13, output); } else { insert_array(array, 10, "--output"); insert_array(array, 11, output); } _execute(M1, array, envp); } void spawn_blood_elf(char* input, char* output, char** envp, int large_flag) { char* blood_elf; #ifdef __uefi__ blood_elf = "blood-elf.efi"; #else blood_elf = "blood-elf"; #endif fputs("# starting Blood-elf stub generation\n", stdout); char** array = calloc(MAX_ARRAY, sizeof(char*)); insert_array(array, 0,blood_elf); insert_array(array, 1, "--file"); insert_array(array, 2, input); if(ENDIAN) { insert_array(array, 3, "--big-endian"); } else { insert_array(array, 3, "--little-endian"); } insert_array(array, 4, "--output"); insert_array(array, 5, output); if(large_flag) insert_array(array, 6, "--64"); _execute(blood_elf, array, envp); } void spawn_M2(char* input, char* output, char* architecture, char** envp, int debug_flag) { char* M2_Planet; #ifdef __uefi__ M2_Planet = "M2-Planet.efi"; #else M2_Planet = "M2-Planet"; #endif fputs("# starting M2-Planet build\n", stdout); char** array = calloc(MAX_ARRAY, sizeof(char*)); insert_array(array, 0, M2_Planet); insert_array(array, 1, "--file"); insert_array(array, 2, input); insert_array(array, 3, "--output"); insert_array(array, 4, output); insert_array(array, 5, "--architecture"); insert_array(array, 6, architecture); if(debug_flag) insert_array(array, 7, "--debug"); _execute(M2_Planet, array, envp); } void spawn_processes(int debug_flag, char* prefix, char* preprocessed_file, char* destination, char** envp) { int large_flag = FALSE; if(WORDSIZE > 32) large_flag = TRUE; if(match("UEFI", OperatingSystem)) { debug_flag = FALSE; } char* M2_output = calloc(100, sizeof(char)); strcpy(M2_output, prefix); strcat(M2_output, "/M2-Planet-XXXXXX"); int i = mkstemp(M2_output); if(-1 != i) { close(i); spawn_M2(preprocessed_file, M2_output, Architecture, envp, debug_flag); } else { fputs("unable to get a tempfile for M2-Planet output\n", stderr); exit(EXIT_FAILURE); } char* blood_output = ""; if(debug_flag) { blood_output = calloc(100, sizeof(char)); strcpy(blood_output, prefix); strcat(blood_output, "/blood-elf-XXXXXX"); i = mkstemp(blood_output); if(-1 != i) { close(i); spawn_blood_elf(M2_output, blood_output, envp, large_flag); } else { fputs("unable to get a tempfile for blood-elf output\n", stderr); exit(EXIT_FAILURE); } } char* M1_output = calloc(100, sizeof(char)); strcpy(M1_output, prefix); strcat(M1_output, "/M1-macro-XXXXXX"); i = mkstemp(M1_output); if(-1 != i) { close(i); spawn_M1(M2_output, blood_output, M1_output, Architecture, envp, debug_flag); } else { fputs("unable to get a tempfile for M1 output\n", stderr); exit(EXIT_FAILURE); } /* We no longer need the M2-Planet tempfile output */ if(!DIRTY_MODE) remove(M2_output); /* Nor the blood-elf output anymore if it exists */ if(!match("", blood_output)) { if(!DIRTY_MODE) remove(blood_output); } /* Build the final binary */ spawn_hex2(M1_output, destination, Architecture, envp, debug_flag); /* clean up after ourselves*/ if(!DIRTY_MODE) remove(M1_output); }
/* Copyright (C) 2016 Jeremiah Orians * Copyright (C) 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * Copyright (C) 2020 deesix <deesix@tuta.io> * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #include "cc.h" #include "gcc_req.h" #include <stdint.h> /* Imported functions */ char* int2str(int x, int base, int signed_p); void line_error_token(struct token_list *token) { if(NULL == token) { fputs("EOF reached inside of line_error\n", stderr); fputs("problem at end of file\n", stderr); return; } fputs(token->filename, stderr); fputs(":", stderr); fputs(int2str(token->linenumber, 10, TRUE), stderr); fputs(":", stderr); } void line_error() { line_error_token(global_token); } void require_match(char* message, char* required) { require(NULL != global_token, "EOF reached inside of require match\n"); if(!match(global_token->s, required)) { line_error(); fputs(message, stderr); exit(EXIT_FAILURE); } global_token = global_token->next; require(NULL != global_token, "EOF after require match occurred\n"); } void output_tokens(struct token_list *i, FILE* out) { while(NULL != i) { fputs(i->s, out); i = i->next; } }
/* Copyright (C) 2021 Sanne Wouda * Copyright (C) 2021 Andrius Å tikonas <andrius@stikonas.eu> * Copyright (C) 2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #include "cc.h" #include "gcc_req.h" void require(int bool, char* error); int strtoint(char* a); void line_error_token(struct token_list* list); struct token_list* eat_token(struct token_list* head); struct token_list* reverse_list(struct token_list* head); struct conditional_inclusion { struct conditional_inclusion* prev; int include; /* 1 == include, 0 == skip */ int previous_condition_matched; /* 1 == all subsequent conditions treated as FALSE */ }; struct macro_list { struct macro_list* next; char* symbol; struct token_list* expansion; struct token_list* arguments; }; struct macro_list* macro_env; struct conditional_inclusion* conditional_inclusion_top; /* point where we are currently modifying the global_token list */ struct token_list* macro_token; void init_macro_env(char* sym, char* value, char* source, int num) { struct macro_list* hold = macro_env; macro_env = calloc(1, sizeof(struct macro_list)); macro_env->symbol = sym; macro_env->next = hold; macro_env->expansion = calloc(1, sizeof(struct token_list)); macro_env->expansion->s = value; macro_env->expansion->filename = source; macro_env->expansion->linenumber = num; } void _eat_current_token(int eat_whitespace) { int update_global_token = FALSE; if (macro_token == global_token) update_global_token = TRUE; macro_token = eat_token(macro_token); if(eat_whitespace) { while (macro_token->s[0] == ' ') macro_token = eat_token(macro_token); } if(update_global_token) global_token = macro_token; } void eat_current_token() { _eat_current_token(TRUE); } void eat_current_token_without_space() { _eat_current_token(FALSE); } struct token_list* lookup_token(struct token_list* token, struct token_list* arguments) { if(NULL == token) { fputs("null token received in token\n", stderr); exit(EXIT_FAILURE); } struct token_list* hold = arguments; while (NULL != hold) { if (match(token->s, hold->s)) { /* found! */ return hold->expansion; } hold = hold->next; } /* not found! */ return NULL; } /* returns the first token inserted; inserts *before* point */ struct token_list* insert_tokens(struct token_list* point, struct token_list* token) { struct token_list* copy; struct token_list* first = NULL; while (NULL != token) { copy = calloc(1, sizeof(struct token_list)); copy->s = token->s; copy->filename = token->filename; copy->linenumber = token->linenumber; if(NULL == first) { first = copy; } copy->next = point; if (NULL != point) { copy->prev = point->prev; if(NULL != point->prev) { point->prev->next = copy; } point->prev = copy; } token = token->next; } return first; } /* returns the first token inserted; inserts *before* point */ struct token_list* copy_list(struct token_list* token) { struct token_list* copy; struct token_list* prev = NULL; while (NULL != token) { copy = calloc(1, sizeof(struct token_list)); copy->s = token->s; copy->next = prev; copy->prev = prev; prev = copy; token = token->next; } copy = reverse_list(copy); return copy; } struct macro_list* lookup_macro(struct token_list* token) { if(NULL == token) { line_error_token(macro_token); fputs("null token received in lookup_macro\n", stderr); exit(EXIT_FAILURE); } struct macro_list* hold = macro_env; while (NULL != hold) { if (match(token->s, hold->symbol)) { /* found! */ return hold; } hold = hold->next; } /* not found! */ return NULL; } void remove_macro(struct token_list* token) { if(NULL == token) { line_error_token(macro_token); fputs("received a null in remove_macro\n", stderr); exit(EXIT_FAILURE); } struct macro_list* hold = macro_env; struct macro_list* temp; /* Deal with the first element */ if (match(token->s, hold->symbol)) { macro_env = hold->next; free(hold); return; } /* Remove element form the middle of linked list */ while (NULL != hold->next) { if (match(token->s, hold->next->symbol)) { temp = hold->next; hold->next = hold->next->next; free(temp); return; } hold = hold->next; } /* nothing to undefine */ return; } int macro_expression(); int macro_variable() { int value = 0; struct macro_list* hold = lookup_macro(macro_token); if (NULL != hold) { if(NULL == hold->expansion) { line_error_token(macro_token); fputs("hold->expansion is a null\n", stderr); exit(EXIT_FAILURE); } value = strtoint(hold->expansion->s); } eat_current_token(); return value; } int macro_number() { int result = strtoint(macro_token->s); eat_current_token(); return result; } int macro_primary_expr() { int defined_has_paren = FALSE; int hold; require(NULL != macro_token, "got an EOF terminated macro primary expression\n"); if('-' == macro_token->s[0]) { eat_current_token(); return -macro_primary_expr(); } else if('!' == macro_token->s[0]) { eat_current_token(); return !macro_primary_expr(); } else if('(' == macro_token->s[0]) { eat_current_token(); hold = macro_expression(); require(')' == macro_token->s[0], "missing ) in macro expression\n"); eat_current_token(); return hold; } else if(match("defined", macro_token->s)) { eat_current_token(); require(NULL != macro_token, "got an EOF terminated macro defined expression\n"); if('(' == macro_token->s[0]) { defined_has_paren = TRUE; eat_current_token(); } if (NULL != lookup_macro(macro_token)) { hold = TRUE; } else { hold = FALSE; } eat_current_token(); if(TRUE == defined_has_paren) { if(NULL == macro_token) { line_error_token(macro_token); fputs("unterminated define ( statement\n", stderr); exit(EXIT_FAILURE); } require(')' == macro_token->s[0], "missing close parenthesis for defined()\n"); eat_current_token(); } return hold; } else if(in_set(macro_token->s[0], "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_")) { return macro_variable(); } else if(in_set(macro_token->s[0], "0123456789")) { return macro_number(); } else { return 0; /* FIXME: error handling */ } } int macro_additive_expr() { int lhs = macro_primary_expr(); int hold; require(NULL != macro_token, "got an EOF terminated macro additive expression\n"); if(match("+", macro_token->s)) { eat_current_token(); return lhs + macro_additive_expr(); } else if(match("-", macro_token->s)) { eat_current_token(); return lhs - macro_additive_expr(); } else if(match("*", macro_token->s)) { eat_current_token(); return lhs * macro_additive_expr(); } else if(match("/", macro_token->s)) { eat_current_token(); hold = macro_additive_expr(); require(0 != hold, "divide by zero not valid even in C macros\n"); return lhs / hold; } else if(match("%", macro_token->s)) { eat_current_token(); hold = macro_additive_expr(); require(0 != hold, "modulus by zero not valid even in C macros\n"); return lhs % hold; } else if(match(">>", macro_token->s)) { eat_current_token(); return lhs >> macro_additive_expr(); } else if(match("<<", macro_token->s)) { eat_current_token(); return lhs << macro_additive_expr(); } else { return lhs; } } int macro_relational_expr() { int lhs = macro_additive_expr(); if(match("<", macro_token->s)) { eat_current_token(); return lhs < macro_relational_expr(); } else if(match("<=", macro_token->s)) { eat_current_token(); return lhs <= macro_relational_expr(); } else if(match(">=", macro_token->s)) { eat_current_token(); return lhs >= macro_relational_expr(); } else if(match(">", macro_token->s)) { eat_current_token(); return lhs > macro_relational_expr(); } else if(match("==", macro_token->s)) { eat_current_token(); return lhs == macro_relational_expr(); } else if(match("!=", macro_token->s)) { eat_current_token(); return lhs != macro_relational_expr(); } else { return lhs; } } int macro_bitwise_expr() { int rhs; int lhs = macro_relational_expr(); if(match("&", macro_token->s)) { eat_current_token(); return lhs & macro_bitwise_expr(); } else if(match("&&", macro_token->s)) { eat_current_token(); rhs = macro_bitwise_expr(); return lhs && rhs; } else if(match("|", macro_token->s)) { eat_current_token(); rhs = macro_bitwise_expr(); return lhs | rhs; } else if(match("||", macro_token->s)) { eat_current_token(); rhs = macro_bitwise_expr(); return lhs || rhs; } else if(match("^", macro_token->s)) { eat_current_token(); rhs = macro_bitwise_expr(); return lhs ^ rhs; } else { return lhs; } } int macro_expression() { return macro_bitwise_expr(); } void handle_define() { struct macro_list* hold; struct token_list* arg; struct token_list* expansion_end = NULL; /* don't use #define statements from non-included blocks */ int conditional_define = TRUE; if(NULL != conditional_inclusion_top) { if(FALSE == conditional_inclusion_top->include) { conditional_define = FALSE; } } eat_current_token(); require(NULL != macro_token, "got an EOF terminated #define\n"); require('\n' != macro_token->s[0], "unexpected newline after #define\n"); /* insert new macro */ hold = calloc(1, sizeof(struct macro_list)); hold->symbol = macro_token->s; hold->next = macro_env; /* provided it isn't in a non-included block */ if(conditional_define) macro_env = hold; /* discard the macro name */ eat_current_token_without_space(); /* Handle macro arguments */ if(macro_token->s[0] == '(') { /* discard ( */ eat_current_token(); require(NULL != macro_token, "got an EOF terminated #define\n"); if(macro_token->s[0] != ')') { arg = calloc(1, sizeof(struct token_list)); arg->s = macro_token->s; hold->arguments = arg; eat_current_token(); require(NULL != macro_token, "incomplete macro call\n"); while(macro_token->s[0] == ',') { eat_current_token(); require(NULL != macro_token, "incomplete macro call, got an EOF instead of an argument\n"); arg = calloc(1, sizeof(struct token_list)); arg->s = macro_token->s; arg->next = hold->arguments; hold->arguments = arg; eat_current_token(); require(NULL != macro_token, "incomplete macro call\n"); } } eat_current_token(); /* Reverse argument list */ hold->arguments = reverse_list(hold->arguments); require(NULL != macro_token, "got an EOF terminated #define\n"); } else if(macro_token->s[0] == ' ') { eat_current_token(); } while (TRUE) { require(NULL != macro_token, "got an EOF terminated #define\n"); if ('\n' == macro_token->s[0]) { if(NULL == expansion_end) { hold->expansion = NULL; expansion_end = macro_token; return; } expansion_end->next = NULL; return; } else if(('/' == macro_token->s[0]) && ('*' == macro_token->s[1])) { eat_current_token(); continue; } else if(('/' == macro_token->s[0]) && ('/' == macro_token->s[1])) { macro_token->s = "\n"; if(NULL == expansion_end) { hold->expansion = NULL; expansion_end = macro_token; return; } expansion_end->next = NULL; return; } if(NULL == hold) { eat_current_token(); continue; } expansion_end = macro_token; /* in the first iteration, we set the first token of the expansion, if it exists */ if (NULL == hold->expansion) { hold->expansion = macro_token; } /* throw away if not used */ if(!conditional_define && (NULL != hold)) { free(hold); hold = NULL; } eat_current_token(); } } void handle_undef() { eat_current_token(); remove_macro(macro_token); eat_current_token(); } void handle_error(int warning_p) { /* don't use #error statements from non-included blocks */ int conditional_error = TRUE; if(NULL != conditional_inclusion_top) { if(FALSE == conditional_inclusion_top->include) { conditional_error = FALSE; } } eat_current_token(); /* provided it isn't in a non-included block */ if(conditional_error) { line_error_token(macro_token); if(warning_p) fputs(" warning: #warning ", stderr); else fputs(" error: #error ", stderr); while (TRUE) { if ('\n' == macro_token->s[0]) break; fputs(macro_token->s, stderr); macro_token = macro_token->next; } fputs("\n", stderr); if(!warning_p) exit(EXIT_FAILURE); } while (TRUE) { /* discard the error */ if ('\n' == macro_token->s[0]) { return; } eat_current_token(); } } void macro_directive() { struct conditional_inclusion *t; int result; /* FIXME: whitespace is allowed between "#"" and "if" */ if(match("#if", macro_token->s)) { eat_current_token(); /* evaluate constant integer expression */ result = macro_expression(); /* push conditional inclusion */ t = calloc(1, sizeof(struct conditional_inclusion)); t->prev = conditional_inclusion_top; conditional_inclusion_top = t; t->include = TRUE; if(FALSE == result) { t->include = FALSE; } t->previous_condition_matched = t->include; } else if(match("#ifdef", macro_token->s)) { eat_current_token(); require(NULL != macro_token, "got an EOF terminated macro defined expression\n"); if (NULL != lookup_macro(macro_token)) { result = TRUE; } else { result = FALSE; } eat_current_token(); /* push conditional inclusion */ t = calloc(1, sizeof(struct conditional_inclusion)); t->prev = conditional_inclusion_top; conditional_inclusion_top = t; t->include = TRUE; if(FALSE == result) { t->include = FALSE; } t->previous_condition_matched = t->include; } else if(match("#ifndef", macro_token->s)) { eat_current_token(); require(NULL != macro_token, "got an EOF terminated macro defined expression\n"); if (NULL != lookup_macro(macro_token)) { result = FALSE; } else { result = TRUE; } eat_current_token(); /* push conditional inclusion */ t = calloc(1, sizeof(struct conditional_inclusion)); t->prev = conditional_inclusion_top; conditional_inclusion_top = t; t->include = TRUE; if(FALSE == result) { t->include = FALSE; } t->previous_condition_matched = t->include; } else if(match("#elif", macro_token->s)) { eat_current_token(); result = macro_expression(); require(NULL != conditional_inclusion_top, "#elif without leading #if\n"); conditional_inclusion_top->include = result && !conditional_inclusion_top->previous_condition_matched; conditional_inclusion_top->previous_condition_matched = conditional_inclusion_top->previous_condition_matched || conditional_inclusion_top->include; } else if(match("#else", macro_token->s)) { eat_current_token(); require(NULL != conditional_inclusion_top, "#else without leading #if\n"); conditional_inclusion_top->include = !conditional_inclusion_top->previous_condition_matched; } else if(match("#endif", macro_token->s)) { if(NULL == conditional_inclusion_top) { line_error_token(macro_token); fputs("unexpected #endif\n", stderr); exit(EXIT_FAILURE); } eat_current_token(); /* pop conditional inclusion */ t = conditional_inclusion_top; conditional_inclusion_top = conditional_inclusion_top->prev; free(t); } else if(match("#define", macro_token->s)) { handle_define(); } else if(match("#undef", macro_token->s)) { handle_undef(); } else if(match("#error", macro_token->s)) { handle_error(FALSE); } else if(match("#warning", macro_token->s)) { handle_error(TRUE); } else if(match("#FILENAME", macro_token->s)) { while(TRUE) { if(NULL == macro_token) { return; } if('\n' == macro_token->s[0]) { return; } eat_current_token(); } } else { /* Put a big fat warning but see if we can just ignore */ fputs(">>WARNING<<\n>>WARNING<<\n", stderr); line_error_token(macro_token); fputs("feature: ", stderr); fputs(macro_token->s, stderr); fputs(" unsupported in M2-Planet\nIgnoring line, may result in bugs\n>>WARNING<<\n>>WARNING<<\n\n", stderr); /* unhandled macro directive; let's eat until a newline; om nom nom */ while(TRUE) { if(NULL == macro_token) { return; } if('\n' == macro_token->s[0]) { return; } eat_current_token(); } } } struct token_list* expand_macro_functions(struct token_list* expansion, struct token_list* arguments) { struct token_list* expanded_token; struct token_list* head; struct token_list* hold; /* Same as head unless head == NULL */ head = copy_list(expansion); while(NULL != head) { expanded_token = lookup_token(head, arguments); hold = head; if(NULL != expanded_token) { insert_tokens(head, expanded_token); hold = head->prev; head = eat_token(head); } else { head = head->next; } } while(NULL != hold->prev) hold = hold->prev; return hold; } void eat_until_endif() { /* This #if block is nested inside of an #if block that needs to be dropped, lose EVERYTHING */ do { if(match("#if", macro_token->s) || match("#ifdef", macro_token->s) || match("#ifndef", macro_token->s)) { eat_current_token(); eat_until_endif(); } eat_current_token(); require(NULL != macro_token, "Unterminated #if block\n"); } while(!match("#endif", macro_token->s)); } void eat_block() { /* This conditional #if block is wrong, drop everything until the #elif/#else/#endif */ do { if(match("#if", macro_token->s) || match("#ifdef", macro_token->s) || match("#ifndef", macro_token->s)) { eat_current_token(); eat_until_endif(); } eat_current_token(); require(NULL != macro_token, "Unterminated #if block\n"); } while(!match("#elif", macro_token->s) && !match("#else", macro_token->s) && !match("#endif", macro_token->s)); } struct token_list* maybe_expand(struct token_list* token) { if(NULL == token) { line_error_token(macro_token); fputs("maybe_expand passed a null token\n", stderr); exit(EXIT_FAILURE); } struct macro_list* hold = lookup_macro(token); struct token_list* hold2; struct token_list* hold3; struct token_list* hold4; if(NULL == token->next) { line_error_token(macro_token); fputs("we can't expand a null token: ", stderr); fputs(token->s, stderr); fputc('\n', stderr); exit(EXIT_FAILURE); } if (NULL == hold) { return token->next; } if(match("__M2__", token->s)) return token->next; token = eat_token(token); if (NULL == hold->expansion) { return token->next; } /* Match macro arguments with stored names */ hold3 = hold->arguments; if(NULL != hold3) { if(token->s[0] == ' ') { token = eat_token(token); } require('(' == token->s[0], "missing open parenthesis for macro function\n"); token = eat_token(token); require(NULL != token, "got an EOF terminated macro function\n"); do { hold2 = calloc(1, sizeof(struct token_list)); hold2->s = token->s; hold2->next = hold->arguments->expansion; hold->arguments->expansion = hold2; token = eat_token(token); require(NULL != token, "incomplete macro call\n"); if(token->s[0] == ',') { hold->arguments->expansion = reverse_list(hold->arguments->expansion); hold->arguments = hold->arguments->next; require(NULL != hold->arguments, "too many arguments in macro call\n"); token = eat_token(token); require(NULL != token, "incomplete macro call\n"); } } while(token->s[0] != ')'); hold->arguments->expansion = reverse_list(hold->arguments->expansion); hold->arguments = hold3; token = eat_token(token); } hold4 = expand_macro_functions(hold->expansion, hold->arguments); hold4 = insert_tokens(token, hold4); return hold4; } void preprocess() { int start_of_line = TRUE; macro_token = global_token; while(NULL != macro_token) { if(start_of_line && '#' == macro_token->s[0]) { macro_directive(); if(macro_token) { if('\n' != macro_token->s[0]) { line_error_token(macro_token); fputs("newline expected at end of macro directive\n", stderr); fputs("found: '", stderr); fputs(macro_token->s, stderr); fputs("'\n", stderr); exit(EXIT_FAILURE); } } } else if('\n' == macro_token->s[0]) { start_of_line = TRUE; macro_token = macro_token->next; } else { start_of_line = FALSE; if(NULL == conditional_inclusion_top) { macro_token = maybe_expand(macro_token); } else if(!conditional_inclusion_top->include) { /* rewrite the token stream to exclude the current token */ eat_block(); start_of_line = TRUE; } else { macro_token = maybe_expand(macro_token); } } } }
/* Copyright (C) 2016, 2021 Jeremiah Orians * Copyright (C) 2020 deesix <deesix@tuta.io> * Copyright (C) 2020 Gabriel Wicki * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #include"cc.h" #include <unistd.h> /* The core functions */ void populate_env(char** envp); void setup_env(); char* env_lookup(char* variable); void initialize_types(); struct token_list* read_all_tokens(FILE* a, struct token_list* current, char* filename, int include); struct token_list* reverse_list(struct token_list* head); void init_macro_env(char* sym, char* value, char* source, int num); void preprocess(); void output_tokens(struct token_list *i, FILE* out); int strtoint(char *a); void spawn_processes(int debug_flag, char* prefix, char* preprocessed_file, char* destination, char** envp); int follow_includes; void prechecks(int argc, char** argv) { int env = 0; char* hold; int i = 1; while(i <= argc) { if(NULL == argv[i]) { i += 1; } else if(match(argv[i], "--debug-mode")) { hold = argv[i+1]; require(NULL != hold, "--debug-mode requires an argument\n"); DEBUG_LEVEL = strtoint(hold); if(0 == DEBUG_LEVEL) { require(match("0", hold), "--debug-mode values must be numbers\n" "and level 0 needed to be expressed as 0\n"); } fputs("DEBUG_LEVEL set to: ", stderr); fputs(hold, stderr); fputc('\n', stderr); i+= 2; } else if(match(argv[i], "-A") || match(argv[i], "--architecture")) { hold = argv[i+1]; require(NULL != hold, "--architecture needs to be passed an architecture\n"); Architecture = hold; i += 2; } else if(match(argv[i], "--os") || match(argv[i], "--operating-system")) { hold = argv[i+1]; require(NULL != hold, "--operating-system needs to be passed an operating system\n"); OperatingSystem = hold; i += 2; } else if(match(argv[i], "--max-string")) { hold = argv[i+1]; require(NULL != hold, "--max-string requires a numeric argument\n"); MAX_STRING = strtoint(hold); require(0 < MAX_STRING, "Not a valid string size\nAbort and fix your --max-string\n"); i += 2; } else if(match(argv[i], "--no-includes")) { follow_includes = FALSE; i+= 1; } else if(match(argv[i], "-I")) { hold = argv[i+1]; if(NULL == hold) { fputs("-I requires a PATH\n", stderr); exit(EXIT_FAILURE); } if(1 <= DEBUG_LEVEL) { fputs("M2LIBC_PATH set by -I to ", stderr); fputs(hold, stderr); fputc('\n', stderr); } M2LIBC_PATH = hold; i += 2; } else if(match(argv[i], "-D")) { hold = argv[i+1]; if(NULL == hold) { fputs("-D requires an argument", stderr); exit(EXIT_FAILURE); } while(0 != hold[0]) { if('=' == hold[0]) { hold[0] = 0; hold = hold + 1; break; } hold = hold + 1; } init_macro_env(argv[i+1], hold, "__ARGV__", env); env = env + 1; i += 2; } else { i += 1; } } } int main(int argc, char** argv, char** envp) { /**************************************************************************** * Zero means no debugging messages and larger positive values means more * * chatty output. Level 15 means EVERYTHING but 7 should cover most magic * ****************************************************************************/ DEBUG_LEVEL = 0; /* Setup __M2__ (It is very very special *DO NOT MESS WITH IT* ) */ init_macro_env("__M2__", "__M2__", "__INTERNAL_M2__", 0); /* Our fun globals */ FUZZING = FALSE; MAX_STRING = 65536; PREPROCESSOR_MODE = FALSE; STDIO_USED = FALSE; DIRTY_MODE = FALSE; Architecture = NULL; OperatingSystem = NULL; /* Our fun locals */ int debug_flag = TRUE; FILE* in = stdin; FILE* tempfile; char* destination_name = "a.out"; FILE* destination_file = stdout; char* name; int DUMP_MODE = FALSE; follow_includes = TRUE; /* Try to get our needed updates */ prechecks(argc, argv); /* Get the environmental bits */ if(1 <= DEBUG_LEVEL) fputs("Starting to setup Environment\n", stderr); populate_env(envp); setup_env(); if(1 <= DEBUG_LEVEL) fputs("Environment setup\n", stderr); M2LIBC_PATH = env_lookup("M2LIBC_PATH"); if(NULL == M2LIBC_PATH) M2LIBC_PATH = "./M2libc"; else if(1 <= DEBUG_LEVEL) { fputs("M2LIBC_PATH set by environment variable to ", stderr); fputs(M2LIBC_PATH, stderr); fputc('\n', stderr); } TEMPDIR = env_lookup("TMPDIR"); if(NULL == TEMPDIR) TEMPDIR = "/tmp"; else if(1 <= DEBUG_LEVEL) { fputs("TEMPDIR set by environment variable to ", stderr); fputs(TEMPDIR, stderr); fputc('\n', stderr); } int i = 1; while(i <= argc) { if(NULL == argv[i]) { i += 1; } else if(match(argv[i], "-E") || match(argv[i], "--preprocess-only")) { PREPROCESSOR_MODE = TRUE; i += 1; } else if(match(argv[i], "--dump-mode")) { DUMP_MODE = TRUE; i+= 1; } else if(match(argv[i], "--dirty-mode")) { DIRTY_MODE = TRUE; i+= 1; } else if(match(argv[i], "--no-includes")) { /* Handled by precheck*/ i+= 1; } else if(match(argv[i], "--debug-mode")) { /* Handled by precheck */ i+= 2; } else if(match(argv[i], "-A") || match(argv[i], "--architecture")) { /* Handled by precheck */ i += 2; } else if(match(argv[i], "--os") || match(argv[i], "--operating-system")) { /* Handled by precheck */ i += 2; } else if(match(argv[i], "-f") || match(argv[i], "--file")) { if(NULL == hold_string) { hold_string = calloc(MAX_STRING + 4, sizeof(char)); require(NULL != hold_string, "Impossible Exhaustion has occured\n"); } name = argv[i + 1]; if(NULL == name) { fputs("did not receive a file name\n", stderr); exit(EXIT_FAILURE); } in = fopen(name, "r"); if(NULL == in) { fputs("Unable to open for reading file: ", stderr); fputs(name, stderr); fputs("\n Aborting to avoid problems\n", stderr); exit(EXIT_FAILURE); } global_token = read_all_tokens(in, global_token, name, follow_includes); fclose(in); i += 2; } else if(match(argv[i], "-o") || match(argv[i], "--output")) { destination_name = argv[i + 1]; require(NULL != destination_name, "--output option requires a filename to follow\n"); destination_file = fopen(destination_name, "w"); if(NULL == destination_file) { fputs("Unable to open for writing file: ", stderr); fputs(argv[i + 1], stderr); fputs("\n Aborting to avoid problems\n", stderr); exit(EXIT_FAILURE); } i += 2; } else if(match(argv[i], "--max-string")) { /* handled by precheck */ i += 2; } else if(match(argv[i], "-I")) { /* Handled by precheck */ i += 2; } else if(match(argv[i], "-D")) { /* Handled by precheck */ i += 2; } else if(match(argv[i], "-h") || match(argv[i], "--help")) { fputs(" -f input file\n -o output file\n --help for this message\n --version for file version\n-E or --preprocess-only\n--max-string N (N is a number)\n--fuzz\n--no-debug\n", stdout); exit(EXIT_SUCCESS); } else if(match(argv[i], "-V") || match(argv[i], "--version")) { fputs("M2-Mesoplanet v1.10.0\n", stderr); exit(EXIT_SUCCESS); } else if(match(argv[i], "--fuzz")) { /* Set fuzzing */ FUZZING = TRUE; i += 1; } else if(match(argv[i], "--no-debug")) { /* strip things down */ debug_flag = FALSE; i += 1; } else if(match(argv[i], "--temp-directory")) { name = argv[i+1]; if(NULL == name) { fputs("--temp-directory requires a PATH\n", stderr); exit(EXIT_FAILURE); } if(1 <= DEBUG_LEVEL) { fputs("TEMPDIR set by --temp-directory to ", stderr); fputs(name, stderr); fputc('\n', stderr); } TEMPDIR = name; i += 2; } else { if(5 <= DEBUG_LEVEL) { fputs("on index: ", stderr); fputs(int2str(i, 10, TRUE), stderr); fputc('\n', stderr); } fputs("UNKNOWN ARGUMENT: ", stdout); fputs(argv[i], stdout); fputc('\n', stdout); exit(EXIT_FAILURE); } } if(1 <= DEBUG_LEVEL) fputs("READ all files\n", stderr); /* Deal with special case of wanting to read from standard input */ if(stdin == in) { hold_string = calloc(MAX_STRING, sizeof(char)); require(NULL != hold_string, "Impossible Exhaustion has occured\n"); global_token = read_all_tokens(in, global_token, "STDIN", follow_includes); } if(NULL == global_token) { fputs("Either no input files were given or they were empty\n", stderr); exit(EXIT_FAILURE); } if(1 <= DEBUG_LEVEL) fputs("Start to reverse list\n", stderr); global_token = reverse_list(global_token); if(1 <= DEBUG_LEVEL) fputs("List reversed\n", stderr); if(DUMP_MODE) { output_tokens(global_token, destination_file); exit(EXIT_SUCCESS); } preprocess(); if(PREPROCESSOR_MODE) { fputs("/* M2-Mesoplanet Preprocessed source */\n", destination_file); output_tokens(global_token, destination_file); fclose(destination_file); } else { /* Ensure we can write to the temp directory */ int permissions = access(TEMPDIR, 0); if(0 != permissions) { fputs("unable to access: ", stderr); fputs(TEMPDIR, stderr); fputs(" for use as a temp directory\nPlease use --temp-directory to set a directory you can use or set the TMPDIR variable\n", stderr); exit(EXIT_FAILURE); } name = calloc(100, sizeof(char)); strcpy(name, TEMPDIR); strcat(name, "/M2-Mesoplanet-XXXXXX"); i = mkstemp(name); tempfile = fdopen(i, "w"); if(NULL != tempfile) { /* Our preprocessed crap */ output_tokens(global_token, tempfile); fclose(tempfile); /* Make me a real binary */ spawn_processes(debug_flag, TEMPDIR, name, destination_name, envp); /* And clean up the donkey */ if(!DIRTY_MODE) remove(name); } else { fputs("unable to get a tempfile for M2-Mesoplanet output\n", stderr); exit(EXIT_FAILURE); } } return EXIT_SUCCESS; }
/* -*- c-file-style: "linux";indent-tabs-mode:t -*- */ /* Copyright (C) 2017 Jeremiah Orians * This file is part of mescc-tools. * * mescc-tools is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> #include <stdlib.h> #include <sys/utsname.h> int match(char* a, char* b); #define TRUE 1 //CONSTANT TRUE 1 #define FALSE 0 //CONSTANT FALSE 0 /* Standard C main program */ int main(int argc, char **argv) { int exact = FALSE; int override = FALSE; char* override_string; int option_index = 1; struct utsname* unameData = calloc(1, sizeof(struct utsname)); uname(unameData); while(option_index <= argc) { if(NULL == argv[option_index]) { option_index = option_index + 1; } else if(match(argv[option_index], "--exact")) { exact = TRUE; option_index = option_index + 1; } else if(match(argv[option_index], "--override")) { override = TRUE; if((option_index + 1) < argc) { override_string = argv[option_index + 1]; option_index = option_index + 2; } else { fputs("--override requires an actual override string\n", stderr); exit(EXIT_FAILURE); } } else if(match(argv[option_index], "--os") || match(argv[option_index], "--OS")) { if(override) fputs(override_string, stdout); else fputs(unameData->sysname, stdout); fputc('\n', stdout); exit(EXIT_SUCCESS); } else if(match(argv[option_index], "--blood")) { if(override) fputs(override_string, stdout); else if(match("aarch64", unameData->machine) || match("amd64", unameData->machine) || match("ppc64le", unameData->machine) || match("riscv64", unameData->machine) || match("x86_64", unameData->machine)) fputs("--64", stdout); fputc('\n', stdout); exit(EXIT_SUCCESS); } else if(match(argv[option_index], "--endian")) { if(override) fputs(override_string, stdout); else if(match("aarch64", unameData->machine) || match("amd64", unameData->machine) || match("ppc64le", unameData->machine) || match("riscv64", unameData->machine) || match("x86_64", unameData->machine) || match("i386", unameData->machine) || match("i486", unameData->machine) || match("i586", unameData->machine) || match("i686", unameData->machine) || match("i686-pae", unameData->machine))fputs("--little-endian", stdout); else fputs("--big-endian", stdout); fputc('\n', stdout); exit(EXIT_SUCCESS); } else if(match(argv[option_index], "--hex2")) { if(override) fputs(override_string, stdout); else if(match("aarch64", unameData->machine)) fputs("0x400000", stdout); else if(match("armv7l", unameData->machine)) fputs("0x10000", stdout); else if(match("amd64", unameData->machine) || match("x86_64", unameData->machine)) fputs("0x600000", stdout); else if(match("ppc64le", unameData->machine)) fputs("0x10000", stdout); else if(match("riscv64", unameData->machine)) fputs("0x600000", stdout); else if(match("i386", unameData->machine) || match("i486", unameData->machine) || match("i586", unameData->machine) || match("i686", unameData->machine) || match("i686-pae", unameData->machine)) fputs("0x08048000", stdout); else fputs("0x0", stdout); fputc('\n', stdout); exit(EXIT_SUCCESS); } else if(match(argv[option_index], "-V") || match(argv[option_index], "--version")) { fputs("get_machine 1.4.0\n", stdout); exit(EXIT_SUCCESS); } else if(match(argv[option_index], "-h") || match(argv[option_index], "--help")) { fputs("If you want exact architecture use --exact\n", stderr); fputs("If you want to know the Operating system use --os\n", stderr); fputs("If you wish to override the output to anything you want use --override\n", stderr); exit(EXIT_SUCCESS); } else { fputs("Unknown option\n", stderr); exit(EXIT_FAILURE); } } if(override) fputs(override_string, stdout); else if(!exact) { if(match("i386", unameData->machine) || match("i486", unameData->machine) || match("i586", unameData->machine) || match("i686", unameData->machine) || match("i686-pae", unameData->machine)) fputs("x86", stdout); else if(match("x86_64", unameData->machine)) fputs("amd64", stdout); else fputs(unameData->machine, stdout); } else fputs(unameData->machine, stdout); fputs("\n", stdout); return EXIT_SUCCESS; }
/* Copyright (C) 2021 Bastian Bittorf <bb@npl.de> * Copyright (C) 2021 Alain Mosnier <alain@wanamoon.net> * Copyright (C) 2017-2021 Jan Venekamp * Copyright (C) 2021 Jeremiah Orians * This file is part of mescc-tools * * mescc-tools is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> #include <stdio.h> #include <stdlib.h> #include "M2libc/bootstrappable.h" #define CHUNK_SIZE 64 #define TOTAL_LEN_LEN 8 int mask; /* * Initialize array of round constants: * (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311): */ unsigned* init_k() { unsigned* k = calloc(65, sizeof(unsigned)); k[0] = 0x428a2f98; k[1] = 0x71374491; k[2] = 0xb5c0fbcf; k[3] = 0xe9b5dba5; k[4] = 0x3956c25b; k[5] = 0x59f111f1; k[6] = 0x923f82a4; k[7] = 0xab1c5ed5; k[8] = 0xd807aa98; k[9] = 0x12835b01; k[10] = 0x243185be; k[11] = 0x550c7dc3; k[12] = 0x72be5d74; k[13] = 0x80deb1fe; k[14] = 0x9bdc06a7; k[15] = 0xc19bf174; k[16] = 0xe49b69c1; k[17] = 0xefbe4786; k[18] = 0x0fc19dc6; k[19] = 0x240ca1cc; k[20] = 0x2de92c6f; k[21] = 0x4a7484aa; k[22] = 0x5cb0a9dc; k[23] = 0x76f988da; k[24] = 0x983e5152; k[25] = 0xa831c66d; k[26] = 0xb00327c8; k[27] = 0xbf597fc7; k[28] = 0xc6e00bf3; k[29] = 0xd5a79147; k[30] = 0x06ca6351; k[31] = 0x14292967; k[32] = 0x27b70a85; k[33] = 0x2e1b2138; k[34] = 0x4d2c6dfc; k[35] = 0x53380d13; k[36] = 0x650a7354; k[37] = 0x766a0abb; k[38] = 0x81c2c92e; k[39] = 0x92722c85; k[40] = 0xa2bfe8a1; k[41] = 0xa81a664b; k[42] = 0xc24b8b70; k[43] = 0xc76c51a3; k[44] = 0xd192e819; k[45] = 0xd6990624; k[46] = 0xf40e3585; k[47] = 0x106aa070; k[48] = 0x19a4c116; k[49] = 0x1e376c08; k[50] = 0x2748774c; k[51] = 0x34b0bcb5; k[52] = 0x391c0cb3; k[53] = 0x4ed8aa4a; k[54] = 0x5b9cca4f; k[55] = 0x682e6ff3; k[56] = 0x748f82ee; k[57] = 0x78a5636f; k[58] = 0x84c87814; k[59] = 0x8cc70208; k[60] = 0x90befffa; k[61] = 0xa4506ceb; k[62] = 0xbef9a3f7; k[63] = 0xc67178f2; return k; } unsigned* init_h() { unsigned* h = calloc(9, sizeof(unsigned)); h[0] = 0x6a09e667; h[1] = 0xbb67ae85; h[2] = 0x3c6ef372; h[3] = 0xa54ff53a; h[4] = 0x510e527f; h[5] = 0x9b05688c; h[6] = 0x1f83d9ab; h[7] = 0x5be0cd19; return h; } struct buffer_state { char* p; size_t len; size_t total_len; int single_one_delivered; /* bool */ int total_len_delivered; /* bool */ }; unsigned right_rot(unsigned value, unsigned count) { /* * Defined behaviour in standard C for all count where 0 < count < 32, * which is what we need here. */ value &= mask; int hold1 = (value >> count) & mask; int hold2 = (value << (32 - count)) & mask; int hold = (hold1 | hold2) & mask; return hold; } void init_buf_state(struct buffer_state * state, char* input, size_t len) { state->p = input; state->len = len; state->total_len = len; state->single_one_delivered = 0; state->total_len_delivered = 0; } /* Return value: bool */ int calc_chunk(char* chunk, struct buffer_state * state) { size_t space_in_chunk; if(state->total_len_delivered) { return 0; } if(state->len >= CHUNK_SIZE) { memcpy(chunk, state->p, CHUNK_SIZE); state->p += CHUNK_SIZE; state->len -= CHUNK_SIZE; return 1; } memcpy(chunk, state->p, state->len); chunk += state->len; space_in_chunk = CHUNK_SIZE - state->len; state->p += state->len; state->len = 0; /* If we are here, space_in_chunk is one at minimum. */ if(!state->single_one_delivered) { chunk[0] = 0x80; chunk += 1; space_in_chunk -= 1; state->single_one_delivered = 1; } /* * Now: * - either there is enough space left for the total length, and we can conclude, * - or there is too little space left, and we have to pad the rest of this chunk with zeroes. * In the latter case, we will conclude at the next invocation of this function. */ if(space_in_chunk >= TOTAL_LEN_LEN) { size_t left = space_in_chunk - TOTAL_LEN_LEN; size_t len = state->total_len; int i; memset(chunk, 0x00, left); chunk += left; /* Storing of len * 8 as a big endian 64-bit without overflow. */ chunk[7] = (len << 3); len >>= 5; for(i = 6; i >= 0; i -= 1) { chunk[i] = len; len >>= 8; } state->total_len_delivered = 1; } else { memset(chunk, 0x00, space_in_chunk); } return 1; } /* * Limitations: * - Since input is a pointer in RAM, the data to hash should be in RAM, which could be a problem * for large data sizes. * - SHA algorithms theoretically operate on bit strings. However, this implementation has no support * for bit string lengths that are not multiples of eight, and it really operates on arrays of bytes. * In particular, the len parameter is a number of bytes. */ void calc_sha_256(char* hash, char* input, size_t len) { /* * Note 1: All integers (expect indexes) are 32-bit unsigned integers and addition is calculated modulo 2^32. * Note 2: For each round, there is one round constant k[i] and one entry in the message schedule array w[i], 0 = i = 63 * Note 3: The compression function uses 8 working variables, a through h * Note 4: Big-endian convention is used when expressing the constants in this pseudocode, * and when parsing message block data from bytes to words, for example, * the first word of the input message "abc" after padding is 0x61626380 */ /* * Initialize hash values: * (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19): */ unsigned* k = init_k(); unsigned* h = init_h(); unsigned i; unsigned j; unsigned hold1; unsigned hold2; /* 512-bit chunks is what we will operate on. */ char* chunk = calloc(65, sizeof(char)); struct buffer_state* state = calloc(1, sizeof(struct buffer_state)); init_buf_state(state, input, len); unsigned* ah = calloc(9, sizeof(unsigned)); char *p; unsigned* w = calloc(17, sizeof(unsigned)); unsigned s0; unsigned s1; unsigned ch; unsigned temp1; unsigned temp2; unsigned maj; while(calc_chunk(chunk, state)) { p = chunk; /* Initialize working variables to current hash value: */ for(i = 0; i < 8; i += 1) { ah[i] = h[i]; } /* Compression function main loop: */ for(i = 0; i < 4; i += 1) { /* * The w-array is really w[64], but since we only need * 16 of them at a time, we save stack by calculating * 16 at a time. * * This optimization was not there initially and the * rest of the comments about w[64] are kept in their * initial state. */ /* * create a 64-entry message schedule array w[0..63] of 32-bit words * (The initial values in w[0..63] don't matter, so many implementations zero them here) * copy chunk into first 16 words w[0..15] of the message schedule array */ for(j = 0; j < 16; j += 1) { if(i == 0) { w[j] = ((p[0] & 0xFF) << 24) | ((p[1] & 0xFF) << 16) | ((p[2] & 0xFF) << 8) | (p[3] & 0xFF); p += 4; } else { /* Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array: */ hold1 = (j + 1) & 0xf; hold2 = w[hold1]; s0 = right_rot(hold2, 7) ^ right_rot(hold2, 18) ^ ((hold2 & mask) >> 3); hold1 = (j + 14) & 0xf; hold2 = w[hold1]; s1 = right_rot(hold2, 17) ^ right_rot(hold2, 19) ^ ((hold2 & mask) >> 10); w[j] += s0 + w[(j + 9) & 0xf] + s1; } s1 = right_rot(ah[4], 6) ^ right_rot(ah[4], 11) ^ right_rot(ah[4], 25); ch = (ah[4] & ah[5]) ^ (~ah[4] & ah[6]); temp1 = ah[7] + s1 + ch + k[i << 4 | j] + w[j]; s0 = right_rot(ah[0], 2) ^ right_rot(ah[0], 13) ^ right_rot(ah[0], 22); maj = (ah[0] & ah[1]) ^ (ah[0] & ah[2]) ^ (ah[1] & ah[2]); temp2 = s0 + maj; ah[7] = ah[6]; ah[6] = ah[5]; ah[5] = ah[4]; ah[4] = ah[3] + temp1; ah[3] = ah[2]; ah[2] = ah[1]; ah[1] = ah[0]; ah[0] = temp1 + temp2; } } /* Add the compressed chunk to the current hash value: */ for(i = 0; i < 8; i += 1) { h[i] += ah[i]; } } /* Produce the final hash value (big-endian): */ i = 0; for(j = 0; i < 8; i += 1) { hash[j] = ((h[i] >> 24) & 0xFF); j += 1; hash[j] = ((h[i] >> 16) & 0xFF); j += 1; hash[j] = ((h[i] >> 8) & 0xFF); j += 1; hash[j] = (h[i] & 0xFF); j += 1; } } struct list { int found; char* name; FILE* f; size_t size; char* buffer; char* hash; struct list* next; }; void bad_checkfile(char* filename) { fputs(filename, stdout); puts(": no properly formatted SHA256 checksum lines found"); } int hex2int(char c, char* filename) { if((c >= '0') && (c <= '9')) return (c - 48); else if((c >= 'a') && (c <= 'f')) return (c - 87); else if ((c >= 'F') && (c <= 'F')) return (c - 55); bad_checkfile(filename); exit(EXIT_FAILURE); } char* hash_to_string(char* a) { char* table = "0123456789abcdef"; char* r = calloc(66, sizeof(char)); int i; int j = 0; int c; for(i = 0; i < 32; i += 1) { c = a[i] & 0xFF; r[j] = table[(c >> 4)]; j += 1; r[j] = table[(c & 0xF)]; j += 1; } return r; } int check_file(char* b, char* filename) { int r = TRUE; size_t i; int hold1; int hold2; FILE* f; char* name = calloc(4097, sizeof(char)); char* hash = calloc(33, sizeof(char)); char* hash2 = calloc(33, sizeof(char)); size_t size; char* buffer; go_again: for(i = 0; i < 32; i += 1) { hold1 = hex2int(b[0], filename); hold2 = hex2int(b[1], filename); hash[i] = (hold1 << 4) + hold2; b += 2; } if((' ' != b[0]) || (' ' != b[1])) { bad_checkfile(filename); exit(EXIT_FAILURE); } b += 2; for(i = 0; i < 4096; i += 1) { if('\n' == b[0]) { name[i] = 0; b += 1; break; } name[i] = b[0]; b += 1; } f = fopen(name, "r"); if(NULL == f) { fputs(name, stdout); puts(": No such file or directory"); exit(EXIT_FAILURE); } else { fseek(f, 0, SEEK_END); size = ftell(f); rewind(f); buffer = calloc(size + 1, sizeof(char)); fread(buffer, sizeof(char), size, f); calc_sha_256(hash2, buffer, size); if(match(hash_to_string(hash), hash_to_string(hash2))) { fputs(name, stdout); puts(": OK"); } else { fputs(name, stdout); fputs(": FAILED\nWanted: ", stdout); fputs(hash_to_string(hash), stdout); fputs("\nReceived: ", stdout); puts(hash_to_string(hash2)); r = FALSE; } } if(0 == b[0]) return r; goto go_again; } /* reverse the linked list */ void reverse(struct list** head) { struct list* prev = NULL; struct list* current = *head; struct list* next = NULL; while (current != NULL) { next = current->next; current->next = prev; prev = current; current = next; } *head = prev; } int main(int argc, char **argv) { struct list* l = NULL; struct list* t = NULL; size_t read; int check = FALSE; int r = TRUE; char* output_file = ""; FILE* output = stdout; mask = (0x7FFFFFFF << 1) | 0x1; int i = 1; while(i <= argc) { if(NULL == argv[i]) { i += 1; } else if(match(argv[i], "-c") || match(argv[i], "--check")) { check = TRUE; i += 1; } else if (match(argv[i], "-o") || match(argv[i], "--output")) { output_file = argv[i + 1]; i += 2; if (output != stdout) { fclose(output); } output = fopen(output_file, "w"); require(output != NULL, "Output file cannot be opened!\n"); } else if(match(argv[i], "-h") || match(argv[i], "--help")) { puts("Usage: sha256sum <file> [--check]"); exit(EXIT_SUCCESS); } else { t = calloc(1, sizeof(struct list)); t->hash = calloc(33, sizeof(char)); t->name = argv[i]; t->f = fopen(t->name, "r"); if(NULL != t->f) { t->found = TRUE; fseek(t->f, 0, SEEK_END); t->size = ftell(t->f); rewind(t->f); t->buffer = calloc(t->size + 1, sizeof(char)); read = fread(t->buffer, sizeof(char), t->size, t->f); } t->next = l; l = t; i += 1; } } reverse(&l); if(check) { while(NULL != l) { if(l->found) { if(!check_file(l->buffer, l->name)) r = FALSE; } else { fputs(l->name, stdout); puts(": No such file or directory"); exit(EXIT_FAILURE); } l = l->next; } } else { while(NULL != l) { if(l->found) { calc_sha_256(l->hash, l->buffer, l->size); fputs(hash_to_string(l->hash), output); fputs(" ", output); fputs(l->name, output); fputc('\n', output); } else { fputs(l->name, output); fputs(": No such file or directory\n", output); exit(EXIT_FAILURE); } l = l->next; } } if (output != stdout) { fclose(output); } if(r) return 0; else return 1; }
/* Copyright (C) 2016 Jeremiah Orians * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #ifndef _STRING_H #define _STRING_H #ifdef __M2__ #include <string.c> #else #include <stddef.h> /* String manipulation */ char* strcpy(char* dest, char const* src); char* strncpy(char* dest, char const* src, size_t count); char* strcat(char* dest, char const* src); char* strncat(char* dest, char const* src, size_t count); /* String examination */ size_t strlen(char const* str ); size_t strnlen_s(char const* str, size_t strsz ); int strcmp(char const* lhs, char const* rhs ); int strncmp(char const* lhs, char const* rhs, size_t count); char* strchr(char const* str, int ch); char* strrchr(char const* str, int ch); size_t strspn(char const* dest, char const* src); size_t strcspn(char const* dest, char const* src); char* strpbrk(char const* dest, char const* breakset); /* Memory manipulation */ void* memset(void* dest, int ch, size_t count); void* memcpy(void* dest, void const* src, size_t count); void* memmove(void* dest, void const* src, size_t count); int memcmp(void const* lhs, void const* rhs, size_t count); void* memchr(void const* ptr, int ch, size_t count); #endif #endif
/* Copyright (C) 2020 Jeremiah Orians * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #ifndef _SYS_STAT_H #define _SYS_STAT_H #ifdef __M2__ #if __uefi__ #include <uefi/sys/stat.c> #elif __i386__ #include <x86/linux/sys/stat.c> #elif __x86_64__ #include <amd64/linux/sys/stat.c> #elif __arm__ #include <armv7l/linux/sys/stat.c> #elif __aarch64__ #include <aarch64/linux/sys/stat.c> #elif __riscv && __riscv_xlen==32 #include <riscv32/linux/sys/stat.c> #elif __riscv && __riscv_xlen==64 #include <riscv64/linux/sys/stat.c> #else #error arch not supported #endif #else #include <sys/types.h> #define S_IRWXU 00700 #define S_IXUSR 00100 #define S_IWUSR 00200 #define S_IRUSR 00400 #define S_ISUID 04000 #define S_ISGID 02000 #define S_IXGRP 00010 #define S_IXOTH 00001 #define S_IRGRP 00040 #define S_IROTH 00004 #define S_IWGRP 00020 #define S_IWOTH 00002 #define S_IRWXG 00070 #define S_IRWXO 00007 int chmod(char *pathname, int mode); int fchmod(int a, mode_t b); int mkdir(char const* a, mode_t b); int mknod(char const* a, mode_t b, dev_t c); mode_t umask(mode_t m); #endif #endif
/* Copyright (C) 2016 Jeremiah Orians * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #ifndef _FCNTL_H #define _FCNTL_H #ifdef __M2__ #include <fcntl.c> #else #define O_RDONLY 0 #define O_WRONLY 1 #define O_RDWR 2 #define O_CREAT 00100 #define O_EXCL 00200 #define O_TRUNC 001000 #define O_APPEND 002000 #define S_IXUSR 00100 #define S_IWUSR 00200 #define S_IRUSR 00400 #define S_IRWXU 00700 extern int open(char* name, int flag, int mode); #define STDIN_FILENO 0 #define STDOUT_FILENO 1 #define STDERR_FILENO 2 #endif #endif
/* Copyright (C) 2020 Jeremiah Orians * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #ifndef _UNISTD_H #define _UNISTD_H #ifdef __M2__ #if __uefi__ #include <uefi/unistd.c> #elif __i386__ #include <x86/linux/unistd.c> #elif __x86_64__ #include <amd64/linux/unistd.c> #elif __arm__ #include <armv7l/linux/unistd.c> #elif __aarch64__ #include <aarch64/linux/unistd.c> #elif __riscv && __riscv_xlen==32 #include <riscv32/linux/unistd.c> #elif __riscv && __riscv_xlen==64 #include <riscv64/linux/unistd.c> #else #error arch not supported #endif #else #define NULL 0 #define __PATH_MAX 4096 void* malloc(unsigned size); int access(char* pathname, int mode); int chdir(char* path); int fchdir(int fd); void _exit(int value); int fork(); int waitpid (int pid, int* status_ptr, int options); int execve(char* file_name, char** argv, char** envp); int read(int fd, char* buf, unsigned count); int write(int fd, char* buf, unsigned count); int lseek(int fd, int offset, int whence); int close(int fd); int unlink (char *filename); int _getcwd(char* buf, int size); char* getcwd(char* buf, unsigned size); char* getwd(char* buf); char* get_current_dir_name(); int brk(void *addr); struct utsname { char sysname[65]; /* Operating system name (e.g., "Linux") */ char nodename[65]; /* Name within "some implementation-defined network" */ char release[65]; /* Operating system release (e.g., "2.6.28") */ char version[65]; /* Operating system version */ char machine[65]; /* Hardware identifier */ }; int uname(struct utsname* unameData); #endif #endif
/* Copyright (C) 2016 Jeremiah Orians * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #ifndef _STDLIB_H #define _STDLIB_H #include <unistd.h> #ifdef __M2__ #include <stdlib.c> #else #define EXIT_FAILURE 1 #define EXIT_SUCCESS 0 extern void exit(int value); extern long _malloc_ptr; extern long _brk_ptr; extern void free(void* l); extern void* malloc(unsigned size); extern void* memset(void* ptr, int value, int num); extern void* calloc(int count, int size); extern char *getenv(const char *name); size_t wcstombs(char* dest, const wchar_t* src, size_t n); #endif #endif
/* Copyright (C) 2016 Jeremiah Orians * This file is part of M2-Planet. * * M2-Planet is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * M2-Planet is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. */ #ifndef _BOOTSTRAPPABLE_H #define _BOOTSTRAPPABLE_H /* Essential common CONSTANTS*/ #define TRUE 1 #define FALSE 0 #ifdef __M2__ #include <bootstrappable.c> #else /* Universally useful functions */ void require(int bool, char* error); int match(char* a, char* b); int in_set(int c, char* s); int strtoint(char *a); char* int2str(int x, int base, int signed_p); #endif #endif
/* Copyright (C) 2021 Andrius Å tikonas * This file is part of mescc-tools-extra * * mescc-tools-extra is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools-extra is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools-extra. If not, see <http://www.gnu.org/licenses/>. */ /* * "match" can be used to compare strings. It is useful to write conditional * code in kaem. * * Usage: match string1 string2 * Returns: 0 if strings match */ #include <stdio.h> #include <string.h> #include "M2libc/bootstrappable.h" int main(int argc, char **argv) { if(argc != 3) { fputs("match needs exactly 2 arguments.\n", stderr); return 2; } return !match(argv[1], argv[2]); }
/* Copyright (C) 2009 Tim Kientzle * Copyright (C) 2021 Jeremiah Orians * Copyright (C) 2021 Andrius Å tikonas * This file is part of mescc-tools-extra * * mescc-tools-extra is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools-extra is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools-extra. If not, see <http://www.gnu.org/licenses/>. */ /* * "mkdir" can be used to create empty directories. It can also create * required parent directories. * * Usage: mkdir <dir1>/<dir2> <dir3> * * These are all highly standard and portable headers. */ #include <stdio.h> #include <string.h> /* This is for mkdir(); this may need to be changed for some platforms. */ #include <sys/stat.h> /* For mkdir() */ #include <stdlib.h> #include "M2libc/bootstrappable.h" int parents; /* Create a directory, including parent directories as necessary. */ void create_dir(char *pathname, int mode) { char *p; int r; /* Strip trailing '/' */ if(pathname[strlen(pathname) - 1] == '/') { pathname[strlen(pathname) - 1] = '\0'; } /* Try creating the directory. */ r = mkdir(pathname, mode); if((r != 0) && parents) { /* On failure, try creating parent directory. */ p = strrchr(pathname, '/'); if(p != NULL) { p[0] = '\0'; create_dir(pathname, 0755); p[0] = '/'; r = mkdir(pathname, mode); } } if((r != 0) && !parents) { fputs("Could not create directory ", stderr); fputs(pathname, stderr); fputc('\n', stderr); exit(EXIT_FAILURE); } } int main(int argc, char **argv) { /* This adds some quasi-compatibility with GNU coreutils' mkdir. */ parents = FALSE; int i; for(i = 1; argc > i; i = i + 1) { if(match(argv[i], "-p")) parents = TRUE; else create_dir(argv[i], 0755); } return 0; }
/* Copyright (C) 2009 Tim Kientzle * Copyright (C) 2021 Jeremiah Orians * This file is part of mescc-tools-extra * * mescc-tools-extra is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools-extra is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools-extra. If not, see <http://www.gnu.org/licenses/>. */ /* * "untar" is an extremely simple tar extractor: * * A single C source file, so it should be easy to compile * and run on any system with a C compiler. * * Extremely portable standard C. The only non-ANSI function * used is mkdir(). * * Reads basic ustar tar archives. * * Does not require libarchive or any other special library. * * To compile: cc -o untar untar.c * * Usage: untar <archive> * * In particular, this program should be sufficient to extract the * distribution for libarchive, allowing people to bootstrap * libarchive on systems that do not already have a tar program. * * To unpack libarchive-x.y.z.tar.gz: * * gunzip libarchive-x.y.z.tar.gz * * untar libarchive-x.y.z.tar * * Written by Tim Kientzle, March 2009. * * Released into the public domain. */ /* These are all highly standard and portable headers. */ #include <stdio.h> #include <stdlib.h> #include <string.h> /* This is for mkdir(); this may need to be changed for some platforms. */ #include <sys/stat.h> /* For mkdir() */ #include "M2libc/bootstrappable.h" int FUZZING; int VERBOSE; int STRICT; /* Parse an octal number, ignoring leading and trailing nonsense. */ int parseoct(char const* p, size_t n) { int i = 0; int h; while(((p[0] < '0') || (p[0] > '7')) && (n > 0)) { p = p + 1; n = n - 1; } while((p[0] >= '0') && (p[0] <= '7') && (n > 0)) { i = i << 3; h = p[0]; i = i + h - 48; p = p + 1; n = n - 1; } return i; } /* Returns true if this is 512 zero bytes. */ int is_end_of_archive(char const* p) { int n; for(n = 511; n >= 0; n = n - 1) { if(p[n] != 0) { return FALSE; } } return TRUE; } /* Create a directory, including parent directories as necessary. */ void create_dir(char *pathname, int mode) { char *p; int r; /* Strip trailing '/' */ if(pathname[strlen(pathname) - 1] == '/') { pathname[strlen(pathname) - 1] = '\0'; } /* Try creating the directory. */ if(!FUZZING) { r = mkdir(pathname, mode); if(r != 0) { /* On failure, try creating parent directory. */ p = strrchr(pathname, '/'); if(p != NULL) { p[0] = '\0'; create_dir(pathname, 0755); p[0] = '/'; r = mkdir(pathname, mode); } } if(r != 0) { fputs("Could not create directory ", stderr); fputs(pathname, stderr); fputc('\n', stderr); } } } /* Create a file, including parent directory as necessary. */ FILE* create_file(char *pathname) { if(FUZZING) return NULL; FILE* f; f = fopen(pathname, "w"); if(f == NULL) { /* Try creating parent dir and then creating file. */ char *p = strrchr(pathname, '/'); if(p != NULL) { p[0] = '\0'; create_dir(pathname, 0755); p[0] = '/'; f = fopen(pathname, "w"); } } return f; } /* Verify the tar checksum. */ int verify_checksum(char const* p) { int n; int u = 0; unsigned h; for(n = 0; n < 512; n = n + 1) { /* Standard tar checksum adds unsigned bytes. */ if((n < 148) || (n > 155)) { h = p[n]; u = u + h; } else { u = u + 0x20; } } int r = parseoct(p + 148, 8); return (u == r); } /* Extract a tar archive. */ int untar(FILE *a, char const* path) { char* buff = calloc(514, sizeof(char)); FILE* f = NULL; size_t bytes_read; size_t bytes_written; int filesize; int op; if(VERBOSE) { fputs("Extracting from ", stdout); puts(path); } while(TRUE) { memset(buff, 0, 514); bytes_read = fread(buff, sizeof(char), 512, a); if(bytes_read < 512) { fputs("Short read on ", stderr); fputs(path, stderr); fputs(": expected 512, got ", stderr); fputs(int2str(bytes_read, 10, TRUE), stderr); fputc('\n', stderr); return FALSE; } if(is_end_of_archive(buff)) { if(VERBOSE) { fputs("End of ", stdout); puts(path); } return TRUE; } if(!verify_checksum(buff)) { fputs("Checksum failure\n", stderr); return FALSE; } filesize = parseoct(buff + 124, 12); op = buff[156]; if('1' == op) { if(STRICT) { fputs("unable to create hardlinks\n", stderr); exit(EXIT_FAILURE); } fputs(" Ignoring hardlink ", stdout); puts(buff); } else if('2' == op) { if(STRICT) { fputs("unable to create symlinks\n", stderr); exit(EXIT_FAILURE); } fputs(" Ignoring symlink ", stdout); puts(buff); } else if('3' == op) { if(STRICT) { fputs("unable to create character devices\n", stderr); exit(EXIT_FAILURE); } fputs(" Ignoring character device ", stdout); puts(buff); } else if('4' == op) { if(STRICT) { fputs("unable to create block devices\n", stderr); exit(EXIT_FAILURE); } fputs(" Ignoring block device ", stdout); puts(buff); } else if('5' == op) { if(VERBOSE) { fputs(" Extracting dir ", stdout); puts(buff); } create_dir(buff, parseoct(buff + 100, 8)); filesize = 0; } else if('6' == op) { if(STRICT) { fputs("unable to create FIFO\n", stderr); exit(EXIT_FAILURE); } fputs(" Ignoring FIFO ", stdout); puts(buff); } else { if(VERBOSE) { fputs(" Extracting file ", stdout); puts(buff); } f = create_file(buff); } while(filesize > 0) { bytes_read = fread(buff, 1, 512, a); if(bytes_read < 512) { fputs("Short read on ", stderr); fputs(path, stderr); fputs(": Expected 512, got ", stderr); puts(int2str(bytes_read, 10, TRUE)); return FALSE; } if(filesize < 512) { bytes_read = filesize; } if(f != NULL) { if(!FUZZING) { bytes_written = fwrite(buff, 1, bytes_read, f); if(bytes_written != bytes_read) { fputs("Failed write\n", stderr); fclose(f); f = NULL; } } } filesize = filesize - bytes_read; } if(f != NULL) { fclose(f); f = NULL; } } return TRUE; } struct files_queue { char* name; FILE* f; struct files_queue* next; }; int main(int argc, char **argv) { struct files_queue* list = NULL; struct files_queue* a; STRICT = TRUE; FUZZING = FALSE; int r; int i = 1; while (i < argc) { if(NULL == argv[i]) { i = i + 1; } else if(match(argv[i], "-f") || match(argv[i], "--file")) { a = calloc(1, sizeof(struct files_queue)); require(NULL != a, "failed to allocate enough memory to even get the file name\n"); a->next = list; a->name = argv[i+1]; require(NULL != a->name, "the --file option requires a filename to be given\n"); a->f = fopen(a->name, "r"); if(a->f == NULL) { fputs("Unable to open ", stderr); fputs(a->name, stderr); fputc('\n', stderr); if(STRICT) exit(EXIT_FAILURE); } list = a; i = i + 2; } else if(match(argv[i], "--chaos") || match(argv[i], "--fuzz-mode") || match(argv[i], "--fuzzing")) { FUZZING = TRUE; fputs("fuzz-mode enabled, preparing for chaos\n", stderr); i = i + 1; } else if(match(argv[i], "-v") || match(argv[i], "--verbose")) { VERBOSE = TRUE; i = i + 1; } else if(match(argv[i], "--non-strict") || match(argv[i], "--bad-decisions-mode") || match(argv[i], "--drunk-mode")) { STRICT = FALSE; fputs("non-strict mode enabled, preparing for chaos\n", stderr); i = i + 1; } else if(match(argv[i], "-h") || match(argv[i], "--help")) { fputs("Usage: ", stderr); fputs(argv[0], stderr); fputs(" --file $input.gz\n", stderr); fputs("--verbose to print list of extracted files\n", stderr); fputs("--help to get this message\n", stderr); fputs("--fuzz-mode if you wish to fuzz this application safely\n", stderr); fputs("--non-strict if you wish to just ignore files not existing\n", stderr); exit(EXIT_SUCCESS); } else { fputs("Unknown option:", stderr); fputs(argv[i], stderr); fputs("\nAborting to avoid problems\n", stderr); exit(EXIT_FAILURE); } } /* Process the queue one file at a time */ while(NULL != list) { r = untar(list->f, list->name); fputs("The extraction of ", stderr); fputs(list->name, stderr); if(r) fputs(" was successful\n", stderr); else fputs(" produced errors\n", stderr); fclose(list->f); list = list->next; } return 0; }
/* Copyright (C) 2002-2013 Mark Adler, all rights reserved * Copyright (C) 2021 Jeremiah Orians * This file is part of mescc-tools-extra * * mescc-tools-extra is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools-extra is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools-extra. If not, see <http://www.gnu.org/licenses/>. */ /* puff.c * Copyright (C) 2002-2013 Mark Adler, all rights reserved * version 2.3, 21 Jan 2013 * This software is provided 'as-is', without any express or implied * warranty. In no event will the author be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. * Mark Adler madler@alumni.caltech.edu */ /* ungz.c is a gz file decompression utility that leverages puff.c to provide * the deflate algorithm with multiple modifications to enable being built by * M2-Planet with M2libc. * * * puff.c is a simple inflate written to be an unambiguous way to specify the * deflate format. It is not written for speed but rather simplicity. As a * side benefit, this code might actually be useful when small code is more * important than speed, such as bootstrap applications. For typical deflate * data, zlib's inflate() is about four times as fast as puff(). zlib's * inflate compiles to around 20K on my machine, whereas puff.c compiles to * around 4K on my machine (a PowerPC using GNU cc). If the faster decode() * function here is used, then puff() is only twice as slow as zlib's * inflate(). * * All dynamically allocated memory comes from the stack. The stack required * is less than 2K bytes. This code is compatible with 16-bit int's and * assumes that long's are at least 32 bits. puff.c uses the short data type, * assumed to be 16 bits, for arrays in order to conserve memory. The code * works whether integers are stored big endian or little endian. * * In the comments below are "Format notes" that describe the inflate process * and document some of the less obvious aspects of the format. This source * code is meant to supplement RFC 1951, which formally describes the deflate * format: * * http://www.zlib.org/rfc-deflate.html */ /* * Change history: * * 1.0 10 Feb 2002 - First version * 1.1 17 Feb 2002 - Clarifications of some comments and notes * - Update puff() dest and source pointers on negative * errors to facilitate debugging deflators * - Remove longest from struct huffman -- not needed * - Simplify offs[] index in construct() * - Add input size and checking, using longjmp() to * maintain easy readability * - Use short data type for large arrays * - Use pointers instead of long to specify source and * destination sizes to avoid arbitrary 4 GB limits * 1.2 17 Mar 2002 - Add faster version of decode(), doubles speed (!), * but leave simple version for readabilty * - Make sure invalid distances detected if pointers * are 16 bits * - Fix fixed codes table error * - Provide a scanning mode for determining size of * uncompressed data * 1.3 20 Mar 2002 - Go back to lengths for puff() parameters [Gailly] * - Add a puff.h file for the interface * - Add braces in puff() for else do [Gailly] * - Use indexes instead of pointers for readability * 1.4 31 Mar 2002 - Simplify construct() code set check * - Fix some comments * - Add FIXLCODES #define * 1.5 6 Apr 2002 - Minor comment fixes * 1.6 7 Aug 2002 - Minor format changes * 1.7 3 Mar 2003 - Added test code for distribution * - Added zlib-like license * 1.8 9 Jan 2004 - Added some comments on no distance codes case * 1.9 21 Feb 2008 - Fix bug on 16-bit integer architectures [Pohland] * - Catch missing end-of-block symbol error * 2.0 25 Jul 2008 - Add #define to permit distance too far back * - Add option in TEST code for puff to write the data * - Add option in TEST code to skip input bytes * - Allow TEST code to read from piped stdin * 2.1 4 Apr 2010 - Avoid variable initialization for happier compilers * - Avoid unsigned comparisons for even happier compilers * 2.2 25 Apr 2010 - Fix bug in variable initializations [Oberhumer] * - Add const where appropriate [Oberhumer] * - Split if's and ?'s for coverage testing * - Break out test code to separate file * - Move NIL to puff.h * - Allow incomplete code only if single code length is 1 * - Add full code coverage test to Makefile * 2.3 21 Jan 2013 - Check for invalid code length codes in dynamic blocks * ?? 22 May 2021 - Convert to M2-Planet C subset for bootstrapping purposes. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include "M2libc/bootstrappable.h" /* * Maximums for allocations and loops. It is not useful to change these -- * they are fixed by the deflate format. */ #define MAXBITS 15 /* maximum bits in a code */ #define MAXLCODES 286 /* maximum number of literal/length codes */ #define MAXDCODES 30 /* maximum number of distance codes */ #define MAXCODES 316 /* maximum codes lengths to read (MAXLCODES+MAXDCODES) */ #define FIXLCODES 288 /* number of fixed literal/length codes */ /* input and output state */ struct state { /* output state */ char *out; /* output buffer */ size_t outlen; /* available space at out */ size_t outcnt; /* bytes written to out so far */ /* input state */ char *in; /* input buffer */ size_t inlen; /* available input at in */ size_t incnt; /* bytes read so far */ int bitbuf; /* bit buffer */ int bitcnt; /* number of bits in bit buffer */ }; /* * Return need bits from the input stream. This always leaves less than * eight bits in the buffer. bits() works properly for need == 0. * * Format notes: * * - Bits are stored in bytes from the least significant bit to the most * significant bit. Therefore bits are dropped from the bottom of the bit * buffer, using shift right, and new bytes are appended to the top of the * bit buffer, using shift left. */ int bits(struct state *s, int need) { long val; /* bit accumulator (can use up to 20 bits) */ long hold; /* load at least need bits into val */ val = s->bitbuf; while (s->bitcnt < need) { if (s->incnt == s->inlen) { fputs("out of input\n", stderr); exit(EXIT_FAILURE); } hold = (s->in[s->incnt] & 0xFF); s->incnt = s->incnt + 1; val = val | (hold << s->bitcnt); /* load eight bits */ s->bitcnt = s->bitcnt + 8; } /* drop need bits and update buffer, always zero to seven bits left */ s->bitbuf = (val >> need); s->bitcnt = s->bitcnt - need; /* return need bits, zeroing the bits above that */ val = (val & ((1 << need) - 1)); #if defined(DEBUG) fputs(int2str(val, 16, FALSE), stderr); fputs(" : bits\n", stderr); #endif return val; } /* * Process a stored block. * * Format notes: * * - After the two-bit stored block type (00), the stored block length and * stored bytes are byte-aligned for fast copying. Therefore any leftover * bits in the byte that has the last bit of the type, as many as seven, are * discarded. The value of the discarded bits are not defined and should not * be checked against any expectation. * * - The second inverted copy of the stored block length does not have to be * checked, but it's probably a good idea to do so anyway. * * - A stored block can have zero length. This is sometimes used to byte-align * subsets of the compressed data for random access or partial recovery. */ int stored(struct state *s) { unsigned len; /* length of stored block */ /* discard leftover bits from current byte (assumes s->bitcnt < 8) */ s->bitbuf = 0; s->bitcnt = 0; /* get length and check against its one's complement */ if ((s->incnt + 4) > s->inlen) return 2; /* not enough input */ len = s->in[s->incnt]; s->incnt = s->incnt + 1; len = len | (s->in[s->incnt] << 8); s->incnt = s->incnt + 1; if(s->in[s->incnt] != (~len & 0xff)) return -2; /* didn't match complement! */ s->incnt = s->incnt + 1; if(s->in[s->incnt] != ((~len >> 8) & 0xff)) return -2; /* didn't match complement! */ s->incnt = s->incnt + 1; /* copy len bytes from in to out */ if ((s->incnt + len) > s->inlen) return 2; /* not enough input */ if (s->out != 0) { if ((s->outcnt + len) > s->outlen) return 1; /* not enough output space */ while (0 != len) { len = len - 1; s->out[s->outcnt] = s->in[s->incnt]; s->outcnt = s->outcnt + 1; s->incnt = s->incnt + 1; } } else { /* just scanning */ s->outcnt = s->outcnt + len; s->incnt = s->incnt + len; } /* done with a valid stored block */ return 0; } /* * Huffman code decoding tables. count[1..MAXBITS] is the number of symbols of * each length, which for a canonical code are stepped through in order. * symbol[] are the symbol values in canonical order, where the number of * entries is the sum of the counts in count[]. The decoding process can be * seen in the function decode() below. */ struct huffman { int *count; /* number of symbols of each length */ int *symbol; /* canonically ordered symbols */ }; /* * Decode a code from the stream s using huffman table h. Return the symbol or * a negative value if there is an error. If all of the lengths are zero, i.e. * an empty code, or if the code is incomplete and an invalid code is received, * then -10 is returned after reading MAXBITS bits. * * Format notes: * * - The codes as stored in the compressed data are bit-reversed relative to * a simple integer ordering of codes of the same lengths. Hence below the * bits are pulled from the compressed data one at a time and used to * build the code value reversed from what is in the stream in order to * permit simple integer comparisons for decoding. A table-based decoding * scheme (as used in zlib) does not need to do this reversal. * * - The first code for the shortest length is all zeros. Subsequent codes of * the same length are simply integer increments of the previous code. When * moving up a length, a zero bit is appended to the code. For a complete * code, the last code of the longest length will be all ones. * * - Incomplete codes are handled by this decoder, since they are permitted * in the deflate format. See the format notes for fixed() and dynamic(). */ int decode(struct state *s, struct huffman *h) { int len; /* current number of bits in code */ int code = 0; /* len bits being decoded */ int first = 0; /* first code of length len */ int count; /* number of codes of length len */ int index = 0; /* index of first code of length len in symbol table */ long hold; for (len = 1; len <= MAXBITS; len = len + 1) { hold = bits(s, 1); /* get next bit */ code = code | hold; count = h->count[len]; if ((code - count) < first) { hold = index + (code - first); return h->symbol[hold]; /* if length len, return symbol */ } index = index + count; /* else update for next length */ first = first + count; first = first << 1; code = code << 1; } return -10; /* ran out of codes */ } /* * Given the list of code lengths length[0..n-1] representing a canonical * Huffman code for n symbols, construct the tables required to decode those * codes. Those tables are the number of codes of each length, and the symbols * sorted by length, retaining their original order within each length. The * return value is zero for a complete code set, negative for an over- * subscribed code set, and positive for an incomplete code set. The tables * can be used if the return value is zero or positive, but they cannot be used * if the return value is negative. If the return value is zero, it is not * possible for decode() using that table to return an error--any stream of * enough bits will resolve to a symbol. If the return value is positive, then * it is possible for decode() using that table to return an error for received * codes past the end of the incomplete lengths. * * Not used by decode(), but used for error checking, h->count[0] is the number * of the n symbols not in the code. So n - h->count[0] is the number of * codes. This is useful for checking for incomplete codes that have more than * one symbol, which is an error in a dynamic block. * * Assumption: for all i in 0..n-1, 0 <= length[i] <= MAXBITS * This is assured by the construction of the length arrays in dynamic() and * fixed() and is not verified by construct(). * * Format notes: * * - Permitted and expected examples of incomplete codes are one of the fixed * codes and any code with a single symbol which in deflate is coded as one * bit instead of zero bits. See the format notes for fixed() and dynamic(). * * - Within a given code length, the symbols are kept in ascending order for * the code bits definition. */ int construct(struct huffman *h, int *length, int n) { int symbol; /* current symbol when stepping through length[] */ int len; /* current length when stepping through h->count[] */ int left; /* number of possible codes left of current length */ int* offs; /* offsets in symbol table for each length */ offs = calloc(MAXBITS+1, sizeof(int)); long hold; #if defined(DEBUG) int i; fputs(int2str(n, 16, FALSE), stderr); fputs(" : construct 0\n", stderr); for(i = 0; i < n; i = i + 1) { fputs(int2str(length[i], 16, FALSE), stderr); fputs(" : construct 2\n", stderr); } #endif /* count number of codes of each length */ for (len = 0; len <= MAXBITS; len = len + 1) { h->count[len] = 0; } for (symbol = 0; symbol < n; symbol = symbol + 1) { hold = length[symbol]; h->count[hold] = h->count[hold] + 1; /* assumes lengths are within bounds */ } if (h->count[0] == n) return 0; /* no codes! complete, but decode() will fail */ /* check for an over-subscribed or incomplete set of lengths */ left = 1; /* one possible code of zero length */ for (len = 1; len <= MAXBITS; len = len + 1) { left = left << 1; /* one more bit, double codes left */ left = left - h->count[len]; /* deduct count from possible codes */ if (left < 0) return left; /* over-subscribed--return negative */ } /* left > 0 means incomplete */ /* generate offsets into symbol table for each length for sorting */ offs[1] = 0; for (len = 1; len < MAXBITS; len = len + 1) { offs[len + 1] = offs[len] + h->count[len]; } /* * put symbols in table sorted by length, by symbol order within each * length */ for (symbol = 0; symbol < n; symbol = symbol + 1) { if (length[symbol] != 0) { hold = length[symbol]; hold = offs[hold]; h->symbol[hold] = symbol; hold = length[symbol]; offs[hold] = offs[hold] + 1; } } /* return zero for complete set, positive for incomplete set */ return left; } /* * Decode literal/length and distance codes until an end-of-block code. * * Format notes: * * - Compressed data that is after the block type if fixed or after the code * description if dynamic is a combination of literals and length/distance * pairs terminated by and end-of-block code. Literals are simply Huffman * coded bytes. A length/distance pair is a coded length followed by a * coded distance to represent a string that occurs earlier in the * uncompressed data that occurs again at the current location. * * - Literals, lengths, and the end-of-block code are combined into a single * code of up to 286 symbols. They are 256 literals (0..255), 29 length * symbols (257..285), and the end-of-block symbol (256). * * - There are 256 possible lengths (3..258), and so 29 symbols are not enough * to represent all of those. Lengths 3..10 and 258 are in fact represented * by just a length symbol. Lengths 11..257 are represented as a symbol and * some number of extra bits that are added as an integer to the base length * of the length symbol. The number of extra bits is determined by the base * length symbol. These are in the static arrays below, lens[] for the base * lengths and lext[] for the corresponding number of extra bits. * * - The reason that 258 gets its own symbol is that the longest length is used * often in highly redundant files. Note that 258 can also be coded as the * base value 227 plus the maximum extra value of 31. While a good deflate * should never do this, it is not an error, and should be decoded properly. * * - If a length is decoded, including its extra bits if any, then it is * followed a distance code. There are up to 30 distance symbols. Again * there are many more possible distances (1..32768), so extra bits are added * to a base value represented by the symbol. The distances 1..4 get their * own symbol, but the rest require extra bits. The base distances and * corresponding number of extra bits are below in the static arrays dist[] * and dext[]. * * - Literal bytes are simply written to the output. A length/distance pair is * an instruction to copy previously uncompressed bytes to the output. The * copy is from distance bytes back in the output stream, copying for length * bytes. * * - Distances pointing before the beginning of the output data are not * permitted. * * - Overlapped copies, where the length is greater than the distance, are * allowed and common. For example, a distance of one and a length of 258 * simply copies the last byte 258 times. A distance of four and a length of * twelve copies the last four bytes three times. A simple forward copy * ignoring whether the length is greater than the distance or not implements * this correctly. You should not use memcpy() since its behavior is not * defined for overlapped arrays. You should not use memmove() or bcopy() * since though their behavior -is- defined for overlapping arrays, it is * defined to do the wrong thing in this case. */ int* codes_lens() { /* Size base for length codes 257..285 */ int* r = calloc(30, sizeof(int)); r[0] = 3; r[1] = 4; r[2] = 5; r[3] = 6; r[4] = 7; r[5] = 8; r[6] = 9; r[7] = 10; r[8] = 11; r[9] = 13; r[10] = 15; r[11] = 17; r[12] = 19; r[13] = 23; r[14] = 27; r[15] = 31; r[16] = 35; r[17] = 43; r[18] = 51; r[19] = 59; r[20] = 67; r[21] = 83; r[22] = 99; r[23] = 115; r[24] = 131; r[25] = 163; r[26] = 195; r[27] = 227; r[28] = 258; return r; } int* codes_lext() { /* Extra bits for length codes 257..285 */ int* r = calloc(30, sizeof(int)); r[0] = 0; r[1] = 0; r[2] = 0; r[3] = 0; r[4] = 0; r[5] = 0; r[6] = 0; r[7] = 0; r[8] = 1; r[9] = 1; r[10] = 1; r[11] = 1; r[12] = 2; r[13] = 2; r[14] = 2; r[15] = 2; r[16] = 3; r[17] = 3; r[18] = 3; r[19] = 3; r[20] = 4; r[21] = 4; r[22] = 4; r[23] = 4; r[24] = 5; r[25] = 5; r[26] = 5; r[27] = 5; r[28] = 0; return r; } int* codes_dists() { /* Offset base for distance codes 0..29 */ int* r = calloc(31, sizeof(int)); r[0] = 1; r[1] = 2; r[2] = 3; r[3] = 4; r[4] = 5; r[5] = 7; r[6] = 9; r[7] = 13; r[8] = 17; r[9] = 25; r[10] = 33; r[11] = 49; r[12] = 65; r[13] = 97; r[14] = 129; r[15] = 193; r[16] = 257; r[17] = 385; r[18] = 513; r[19] = 769; r[20] = 1025; r[21] = 1537; r[22] = 2049; r[23] = 3073; r[24] = 4097; r[25] = 6145; r[26] = 8193; r[27] = 12289; r[28] = 16385; r[29] = 24577; return r; } int* codes_dext() { /* Extra bits for distance codes 0..29 */ int* r = calloc(31, sizeof(int)); r[0] = 0; r[1] = 0; r[2] = 0; r[3] = 0; r[4] = 1; r[5] = 1; r[6] = 2; r[7] = 2; r[8] = 3; r[9] = 3; r[10] = 4; r[11] = 4; r[12] = 5; r[13] = 5; r[14] = 6; r[15] = 6; r[16] = 7; r[17] = 7; r[18] = 8; r[19] = 8; r[20] = 9; r[21] = 9; r[22] = 10; r[23] = 10; r[24] = 11; r[25] = 11; r[26] = 12; r[27] = 12; r[28] = 13; r[29] = 13; return r; } int codes(struct state *s, struct huffman *lencode, struct huffman *distcode) { int symbol; /* decoded symbol */ int len; /* length for copy */ unsigned dist; /* distance for copy */ int* lens = codes_lens(); int* lext = codes_lext(); int* dists = codes_dists(); int* dext = codes_dext(); /* decode literals and length/distance pairs */ do { symbol = decode(s, lencode); if (symbol < 0) return symbol; /* invalid symbol */ if (symbol < 256) /* literal: symbol is the byte */ { /* write out the literal */ if (s->out != 0) { if (s->outcnt == s->outlen) return 1; s->out[s->outcnt] = symbol; } s->outcnt = s->outcnt + 1; } else if (symbol > 256) /* length */ { /* get and compute length */ symbol = symbol - 257; if (symbol >= 29) return -10; /* invalid fixed code */ len = lens[symbol] + bits(s, lext[symbol]); /* get and check distance */ symbol = decode(s, distcode); if (symbol < 0) return symbol; /* invalid symbol */ dist = dists[symbol] + bits(s, dext[symbol]); if (dist > s->outcnt) return -11; /* distance too far back */ /* copy length bytes from distance bytes back */ if (s->out != 0) { if (s->outcnt + len > s->outlen) return 1; while (0 != len) { len = len - 1; if(dist > s->outcnt) s->out[s->outcnt] = 0; else s->out[s->outcnt] = s->out[s->outcnt - dist]; s->outcnt = s->outcnt + 1; } } else s->outcnt = s->outcnt + len; } } while (symbol != 256); /* end of block symbol */ /* done with a valid fixed or dynamic block */ return 0; } /* * Process a fixed codes block. * * Format notes: * * - This block type can be useful for compressing small amounts of data for * which the size of the code descriptions in a dynamic block exceeds the * benefit of custom codes for that block. For fixed codes, no bits are * spent on code descriptions. Instead the code lengths for literal/length * codes and distance codes are fixed. The specific lengths for each symbol * can be seen in the "for" loops below. * * - The literal/length code is complete, but has two symbols that are invalid * and should result in an error if received. This cannot be implemented * simply as an incomplete code since those two symbols are in the "middle" * of the code. They are eight bits long and the longest literal/length\ * code is nine bits. Therefore the code must be constructed with those * symbols, and the invalid symbols must be detected after decoding. * * - The fixed distance codes also have two invalid symbols that should result * in an error if received. Since all of the distance codes are the same * length, this can be implemented as an incomplete code. Then the invalid * codes are detected while decoding. */ int fixed(struct state *s) { int* lencnt = calloc((MAXBITS + 1), sizeof(int)); int* lensym = calloc(FIXLCODES, sizeof(int)); int* distcnt = calloc((MAXBITS + 1), sizeof(int)); int* distsym = calloc(MAXDCODES, sizeof(int)); struct huffman* lencode = calloc(1, sizeof(struct huffman)); struct huffman* distcode = calloc(1, sizeof(struct huffman)); int hold; /* build fixed huffman tables if first call (may not be thread safe) */ int symbol; int* lengths = calloc(FIXLCODES, sizeof(int)); /* construct lencode and distcode */ lencode->count = lencnt; lencode->symbol = lensym; distcode->count = distcnt; distcode->symbol = distsym; /* literal/length table */ for (symbol = 0; symbol < 144; symbol = symbol + 1) { lengths[symbol] = 8; } while(symbol < 256) { lengths[symbol] = 9; symbol = symbol + 1; } while(symbol < 280) { lengths[symbol] = 7; symbol = symbol + 1; } while(symbol < FIXLCODES) { lengths[symbol] = 8; symbol = symbol + 1; } construct(lencode, lengths, FIXLCODES); /* distance table */ for (symbol = 0; symbol < MAXDCODES; symbol = symbol + 1) { lengths[symbol] = 5; } construct(distcode, lengths, MAXDCODES); /* decode data until end-of-block code */ hold = codes(s, lencode, distcode); return hold; } /* * Process a dynamic codes block. * * Format notes: * * - A dynamic block starts with a description of the literal/length and * distance codes for that block. New dynamic blocks allow the compressor to * rapidly adapt to changing data with new codes optimized for that data. * * - The codes used by the deflate format are "canonical", which means that * the actual bits of the codes are generated in an unambiguous way simply * from the number of bits in each code. Therefore the code descriptions * are simply a list of code lengths for each symbol. * * - The code lengths are stored in order for the symbols, so lengths are * provided for each of the literal/length symbols, and for each of the * distance symbols. * * - If a symbol is not used in the block, this is represented by a zero as * as the code length. This does not mean a zero-length code, but rather * that no code should be created for this symbol. There is no way in the * deflate format to represent a zero-length code. * * - The maximum number of bits in a code is 15, so the possible lengths for * any code are 1..15. * * - The fact that a length of zero is not permitted for a code has an * interesting consequence. Normally if only one symbol is used for a given * code, then in fact that code could be represented with zero bits. However * in deflate, that code has to be at least one bit. So for example, if * only a single distance base symbol appears in a block, then it will be * represented by a single code of length one, in particular one 0 bit. This * is an incomplete code, since if a 1 bit is received, it has no meaning, * and should result in an error. So incomplete distance codes of one symbol * should be permitted, and the receipt of invalid codes should be handled. * * - It is also possible to have a single literal/length code, but that code * must be the end-of-block code, since every dynamic block has one. This * is not the most efficient way to create an empty block (an empty fixed * block is fewer bits), but it is allowed by the format. So incomplete * literal/length codes of one symbol should also be permitted. * * - If there are only literal codes and no lengths, then there are no distance * codes. This is represented by one distance code with zero bits. * * - The list of up to 286 length/literal lengths and up to 30 distance lengths * are themselves compressed using Huffman codes and run-length encoding. In * the list of code lengths, a 0 symbol means no code, a 1..15 symbol means * that length, and the symbols 16, 17, and 18 are run-length instructions. * Each of 16, 17, and 18 are follwed by extra bits to define the length of * the run. 16 copies the last length 3 to 6 times. 17 represents 3 to 10 * zero lengths, and 18 represents 11 to 138 zero lengths. Unused symbols * are common, hence the special coding for zero lengths. * * - The symbols for 0..18 are Huffman coded, and so that code must be * described first. This is simply a sequence of up to 19 three-bit values * representing no code (0) or the code length for that symbol (1..7). * * - A dynamic block starts with three fixed-size counts from which is computed * the number of literal/length code lengths, the number of distance code * lengths, and the number of code length code lengths (ok, you come up with * a better name!) in the code descriptions. For the literal/length and * distance codes, lengths after those provided are considered zero, i.e. no * code. The code length code lengths are received in a permuted order (see * the order[] array below) to make a short code length code length list more * likely. As it turns out, very short and very long codes are less likely * to be seen in a dynamic code description, hence what may appear initially * to be a peculiar ordering. * * - Given the number of literal/length code lengths (nlen) and distance code * lengths (ndist), then they are treated as one long list of nlen + ndist * code lengths. Therefore run-length coding can and often does cross the * boundary between the two sets of lengths. * * - So to summarize, the code description at the start of a dynamic block is * three counts for the number of code lengths for the literal/length codes, * the distance codes, and the code length codes. This is followed by the * code length code lengths, three bits each. This is used to construct the * code length code which is used to read the remainder of the lengths. Then * the literal/length code lengths and distance lengths are read as a single * set of lengths using the code length codes. Codes are constructed from * the resulting two sets of lengths, and then finally you can start * decoding actual compressed data in the block. * * - For reference, a "typical" size for the code description in a dynamic * block is around 80 bytes. */ int* dynamic_order() { /* permutation of code length codes */ int* r = calloc(20, sizeof(int)); r[0] = 16; r[1] = 17; r[2] = 18; r[3] = 0; r[4] = 8; r[5] = 7; r[6] = 9; r[7] = 6; r[8] = 10; r[9] = 5; r[10] = 11; r[11] = 4; r[12] = 12; r[13] = 3; r[14] = 13; r[15] = 2; r[16] = 14; r[17] = 1; r[18] = 15; return r; } int dynamic(struct state *s) { #if defined(__M2__) int array = sizeof(int); #else int array = 1; #endif int nlen; int ndist; int ncode; /* number of lengths in descriptor */ int index; /* index of lengths[] */ int err; /* construct() return value */ int* lengths = calloc(MAXCODES, sizeof(int)); /* descriptor code lengths */ int* lencnt = calloc((MAXBITS + 1), sizeof(int)); int* lensym = calloc(MAXLCODES, sizeof(int)); /* lencode memory */ int* distcnt = calloc((MAXBITS + 1), sizeof(int)); int* distsym = calloc(MAXDCODES, sizeof(int)); /* distcode memory */ struct huffman* lencode = calloc(1, sizeof(struct huffman)); struct huffman* distcode = calloc(1, sizeof(struct huffman)); int* order = dynamic_order(); long hold; int* set; /* construct lencode and distcode */ lencode->count = lencnt; lencode->symbol = lensym; distcode->count = distcnt; distcode->symbol = distsym; /* get number of lengths in each table, check lengths */ nlen = bits(s, 5) + 257; ndist = bits(s, 5) + 1; ncode = bits(s, 4) + 4; if (nlen > MAXLCODES) return -3; /* bad counts */ if(ndist > MAXDCODES) return -3; /* bad counts */ /* read code length code lengths (really), missing lengths are zero */ for (index = 0; index < ncode; index = index + 1) { hold = order[index]; lengths[hold] = bits(s, 3); } while(index < 19) { hold = order[index]; lengths[hold] = 0; index = index + 1; } /* build huffman table for code lengths codes (use lencode temporarily) */ err = construct(lencode, lengths, 19); if (err != 0) return -4; /* require complete code set here */ /* read length/literal and distance code length tables */ index = 0; int symbol; /* decoded value */ int len; /* last length to repeat */ while (index < (nlen + ndist)) { symbol = decode(s, lencode); if (symbol < 0) return symbol; /* invalid symbol */ if (symbol < 16) /* length in 0..15 */ { lengths[index] = symbol; index = index + 1; } else /* repeat instruction */ { len = 0; /* assume repeating zeros */ if (symbol == 16) /* repeat last length 3..6 times */ { if (index == 0) return -5; /* no last length! */ len = lengths[index - 1]; /* last length */ symbol = 3 + bits(s, 2); } else if (symbol == 17) symbol = 3 + bits(s, 3); /* repeat zero 3..10 times */ else symbol = 11 + bits(s, 7); /* == 18, repeat zero 11..138 times */ if ((index + symbol) > (nlen + ndist)) return -6; /* too many lengths! */ while(0 != symbol) /* repeat last or zero symbol times */ { lengths[index] = len; index = index + 1; symbol = symbol - 1; } } } /* check for end-of-block code -- there better be one! */ if (lengths[256] == 0) return -9; /* build huffman table for literal/length codes */ err = construct(lencode, lengths, nlen); /* incomplete code ok only for single length 1 code */ if (err < 0) return -7; if((0 != err) && (nlen != (lencode->count[0] + lencode->count[1]))) return -7; /* build huffman table for distance codes */ set = lengths + (nlen * array); err = construct(distcode, set, ndist); /* incomplete code ok only for single length 1 code */ if (err < 0) return -8; if((0 != err) && (ndist != (distcode->count[0] + distcode->count[1]))) return -8; /* decode data until end-of-block code */ hold = codes(s, lencode, distcode); return hold; } /* * Inflate source to dest. On return, destlen and sourcelen are updated to the * size of the uncompressed data and the size of the deflate data respectively. * On success, the return value of puff() is zero. If there is an error in the * source data, i.e. it is not in the deflate format, then a negative value is * returned. If there is not enough input available or there is not enough * output space, then a positive error is returned. In that case, destlen and * sourcelen are not updated to facilitate retrying from the beginning with the * provision of more input data or more output space. In the case of invalid * inflate data (a negative error), the dest and source pointers are updated to * facilitate the debugging of deflators. * * puff() also has a mode to determine the size of the uncompressed output with * no output written. For this dest must be (unsigned char *)0. In this case, * the input value of *destlen is ignored, and on return *destlen is set to the * size of the uncompressed output. * * The return codes are: * * 2: available inflate data did not terminate * 1: output space exhausted before completing inflate * 0: successful inflate * -1: invalid block type (type == 3) * -2: stored block length did not match one's complement * -3: dynamic block code description: too many length or distance codes * -4: dynamic block code description: code lengths codes incomplete * -5: dynamic block code description: repeat lengths with no first length * -6: dynamic block code description: repeat more than specified lengths * -7: dynamic block code description: invalid literal/length code lengths * -8: dynamic block code description: invalid distance code lengths * -9: dynamic block code description: missing end-of-block code * -10: invalid literal/length or distance code in fixed or dynamic block * -11: distance is too far back in fixed or dynamic block * * Format notes: * * - Three bits are read for each block to determine the kind of block and * whether or not it is the last block. Then the block is decoded and the * process repeated if it was not the last block. * * - The leftover bits in the last byte of the deflate data after the last * block (if it was a fixed or dynamic block) are undefined and have no * expected values to check. */ struct puffer { int error; size_t destlen; size_t sourcelen; }; struct puffer* puff(char* dest, size_t destlen, char* source, size_t sourcelen) { struct state* s = calloc(1, sizeof(struct state)); /* input/output state */ int last; int type; /* block information */ int err; /* return value */ /* initialize output state */ s->out = dest; s->outlen = destlen; /* ignored if dest is NIL */ s->outcnt = 0; /* initialize input state */ s->in = source; s->inlen = sourcelen; s->incnt = 0; s->bitbuf = 0; s->bitcnt = 0; /* process blocks until last block or error */ do { last = bits(s, 1); /* one if last block */ type = bits(s, 2); /* block type 0..3 */ if(0 == type) { err = stored(s); } else if(1 == type) { err = fixed(s); } else if(2 == type) { err = dynamic(s); } else err = -1; if (err != 0) break; /* return with error */ } while (!last); /* update the lengths and return */ struct puffer* r = calloc(1, sizeof(struct puffer)); r->error = err; r->destlen = s->outcnt; r->sourcelen = s->incnt; return r; } void write_blob(char* s, int start, int len, FILE* f) { char* table = "0123456789ABCDEF"; if(start > len) return; int i = s[start] & 0xFF; fputc(table[(i >> 4)], f); fputc(table[(i & 0xF)], f); fputc(' ', f); if(start == len) fputc('\n', f); else fputc(' ', f); write_blob(s, start + 1, len, f); } #define FTEXT 0x01 #define FHCRC 0x02 #define FEXTRA 0x04 #define FNAME 0x08 #define FCOMMENT 0x10 struct gz { char* HEADER; int ID; int CM; int FLG; int MTIME; int XFL; int OS; int XLEN; char* FLG_FEXTRA; char* FLG_FNAME; char* FLG_FCOMMENT; int CRC16; char* FLG_FHCRC; char* block; int CRC32; size_t ISIZE; size_t file_size; }; /* Read the input file *name, or stdin if name is NULL, into allocated memory. Reallocate to larger buffers until the entire file is read in. Return a pointer to the allocated data, or NULL if there was a memory allocation failure. *len is the number of bytes of data read from the input file (even if load() returns NULL). If the input file was empty or could not be opened or read, *len is zero. */ struct gz* load(char* name) { struct gz* r = calloc(1, sizeof(struct gz)); char* scratch = calloc(5, sizeof(char)); FILE* f = fopen(name, "r"); int count; int ID1; int ID2; int count1; int count2; int count3; int count4; int c; int i; char* s = calloc(11, sizeof(char)); if(NULL == f) { fputs("unable to open file: ", stderr); fputs(name, stderr); fputs("\nfor reading\n", stderr); return NULL; } fseek(f, 0, SEEK_END); r->file_size = ftell(f); fseek(f, 0, SEEK_SET); count = fread(s, sizeof(char), 10, f); if(10 != count) { fputs("incomplete gzip header\n", stderr); return NULL; } /* Verify header */ r->HEADER = s; #if defined(DEBUG) write_blob(s, 0, 10, stderr); #endif ID1 = (s[0] & 0xFF); ID2 = (s[1] & 0xFF); r->ID = ((ID1 << 8) | ID2); if(0x1f8b != r->ID) { fputs("bad header\n", stderr); return NULL; } /* Verify Compression */ r->CM = (r->HEADER[2] & 0xFF); if(8 != r->CM) { fputs("NOT DEFLATE COMPRESSION\n", stderr); return NULL; } /* Get specials specified in flag bits */ r->FLG = (r->HEADER[3] & 0xFF); if(0 != (FEXTRA & r->FLG)) { count = fread(scratch, sizeof(char), 4, f); count1 = (scratch[0] & 0xFF); count2 = (scratch[1] & 0xFF); count3 = (scratch[2] & 0xFF); count4 = (scratch[3] & 0xFF); count = (count1 << 24) | (count2 << 16) | (count3 << 8) | count4; require(0 < count, "FEXTRA field needs to be a positive number of bytes in size\n"); require(100000000 > count, "we don't support FEXTRA fields greater than 100MB in size\n"); r->FLG_FEXTRA = calloc(count + 1, sizeof(char)); fread(r->FLG_FEXTRA, sizeof(char), count, f); } if(0 != (FNAME & r->FLG)) { r->FLG_FNAME = calloc(r->file_size, sizeof(char)); i = 0; do { c = fgetc(f); require(0 <= c, "received a non-null terminated filename in the file\n"); r->FLG_FNAME[i] = c; i = i + 1; } while(0 != c); } if(0 != (FCOMMENT & r->FLG)) { r->FLG_FCOMMENT = calloc(r->file_size, sizeof(char)); i = 0; do { c = fgetc(f); require(0 <= c, "received a non-null terminated comment in the file\n"); r->FLG_FCOMMENT[i] = c; i = i + 1; } while(0 != c); } if(0 != (FHCRC & r->FLG)) { /* Not implemented */ fputs("FHCRC is not implemented at this time\n", stderr); return NULL; } if(NULL == r->FLG_FNAME) { count = strlen(name) - 3; r->FLG_FNAME = calloc(count + 4, sizeof(char)); i = 0; while(i < count) { r->FLG_FNAME[i] = name[i]; i = i + 1; } } r->block = calloc(r->file_size, sizeof(char)); count = fread(r->block, sizeof(char), r->file_size, f); r->ISIZE = count; fclose(f); return r; } int main(int argc, char **argv) { struct puffer* ret; char* name; char* buffer; char *dest; struct gz* in; FILE* out; int FUZZING = FALSE; /* process arguments */ int i = 1; while (i < argc) { if(NULL == argv[i]) { i = i + 1; } else if(match(argv[i], "-f") || match(argv[i], "--file")) { name = argv[i+1]; require(NULL != name, "the --file option requires a filename to be given\n"); i = i + 2; } else if(match(argv[i], "-o") || match(argv[i], "--output")) { dest = argv[i+1]; require(NULL != dest, "the --output option requires a filename to be given\n"); i = i + 2; } else if(match(argv[i], "--chaos") || match(argv[i], "--fuzz-mode") || match(argv[i], "--fuzzing")) { FUZZING = TRUE; fputs("fuzz-mode enabled, preparing for chaos\n", stderr); i = i + 1; } else if(match(argv[i], "-h") || match(argv[i], "--help")) { fputs("Usage: ", stderr); fputs(argv[0], stderr); fputs(" --file $input.gz", stderr); fputs(" [--output $output] (or it'll use the internal filename)\n", stderr); fputs("--help to get this message\n", stderr); fputs("--fuzz-mode if you wish to fuzz this application safely\n", stderr); exit(EXIT_SUCCESS); } else { fputs("Unknown option:", stderr); fputs(argv[i], stderr); fputs("\nAborting to avoid problems\n", stderr); exit(EXIT_FAILURE); } } in = load(name); if (in == NULL) { fputs("memory allocation failure\nDidn't read file\n", stderr); exit(1); } ret = puff(0, 0, in->block, in->ISIZE); if(NULL == dest) { dest = in->FLG_FNAME; } fputs(name, stderr); fputs(" => ", stderr); fputs(dest, stderr); if (0 != ret->error) { fputs("puff() failed with return code ", stderr); fputs(int2str(ret->error, 10, TRUE), stderr); fputc('\n', stderr); exit(3); } else { fputs(": succeeded uncompressing ", stderr); fputs(int2str(ret->destlen, 10, FALSE), stderr); fputs(" bytes\n", stderr); } buffer = malloc(ret->destlen); if (buffer == NULL) { fputs("memory allocation failure\n", stderr); return 4; } ret = puff(buffer, ret->destlen, in->block, in->ISIZE); if(!FUZZING) { out = fopen(dest, "w"); fwrite(buffer, 1, ret->destlen, out); } else { fputs("skipped write to file due to --fuzz-mode flag\n", stderr); } free(buffer); /* clean up */ return 0; }
/* Copyright (C) 2003, 2007 Rob Landley <rob@landley.net> * Copyright (C) 2022 Paul Dersey <pdersey@gmail.com> * This file is part of mescc-tools-extra * * mescc-tools-extra is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools-extra is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools-extra. If not, see <http://www.gnu.org/licenses/>. */ /* bzcat.c - bzip2 decompression * * Copyright 2003, 2007 Rob Landley <rob@landley.net> * * Based on a close reading (but not the actual code) of the original bzip2 * decompression code by Julian R Seward (jseward@acm.org), which also * acknowledges contributions by Mike Burrows, David Wheeler, Peter Fenwick, * Alistair Moffat, Radford Neal, Ian H. Witten, Robert Sedgewick, and * Jon L. Bentley. * * No standard. */ /* unbz2.c is a bz2 file decompression utility based on bzcat.c with * modifications to enable being built by M2-Planet with M2libc. */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include "M2libc/bootstrappable.h" // Constants for huffman coding #define MAX_GROUPS 6 #define GROUP_SIZE 50 /* 64 would have been more efficient */ #define MAX_HUFCODE_BITS 20 /* Longest huffman code allowed */ #define MAX_SYMBOLS 258 /* 256 literals + RUNA + RUNB */ #define SYMBOL_RUNA 0 #define SYMBOL_RUNB 1 // Other housekeeping constants #define IOBUF_SIZE 4096 // Status return values #define RETVAL_LAST_BLOCK (-100) #define RETVAL_NOT_BZIP_DATA (-1) #define RETVAL_DATA_ERROR (-2) #define RETVAL_OBSOLETE_INPUT (-3) #define INT_MAX 2147483647 // This is what we know about each huffman coding group struct group_data { int *limit; int *base; int *permute; char minLen; char maxLen; }; // Data for burrows wheeler transform struct bwdata { unsigned origPtr; int *byteCount; // State saved when interrupting output int writePos; int writeRun; int writeCount; int writeCurrent; unsigned dataCRC; unsigned headerCRC; unsigned *dbuf; }; // Structure holding all the housekeeping data, including IO buffers and // memory that persists between calls to bunzip struct bunzip_data { // Input stream, input buffer, input bit buffer int in_fd; int inbufCount; int inbufPos; char *inbuf; unsigned inbufBitCount; unsigned inbufBits; // Output buffer char *outbuf; int outbufPos; unsigned totalCRC; // First pass decompression data (Huffman and MTF decoding) char *selectors; // nSelectors=15 bits struct group_data *groups; // huffman coding tables int symTotal; int groupCount; int nSelectors; unsigned *symToByte; unsigned *mtfSymbol; // The CRC values stored in the block header and calculated from the data unsigned *crc32Table; // Second pass decompression data (burrows-wheeler transform) unsigned dbufSize; struct bwdata* bwdata; }; int FUZZING; void crc_init(unsigned *crc_table, int little_endian) { unsigned i; unsigned j; unsigned c; // Init the CRC32 table (big endian) for(i = 0; i < 256; i += 1) { if(little_endian) { c = i; } else { c = i << 24; } for(j = 8; j > 0; j -= 1) { if(little_endian) { if(c & 1) { c = (c >> 1) ^ 0xEDB88320; } else { c = c >> 1; } } else { if(c & 0x80000000) { c = (c << 1) ^ 0x04C11DB7; #if defined(__M2__) // & 0xFFFFFFFF not working if(sizeof(unsigned) == 8) { c <<= 32; c >>= 32; } #endif } else { c = c << 1; } } } crc_table[i] = c; } } // Return the next nnn bits of input. All reads from the compressed input // are done through this function. All reads are big endian. unsigned get_bits(struct bunzip_data *bd, char bits_wanted) { unsigned bits = 0; // If we need to get more data from the byte buffer, do so. (Loop getting // one byte at a time to enforce endianness and avoid unaligned access.) while(bd->inbufBitCount < bits_wanted) { // If we need to read more data from file into byte buffer, do so if(bd->inbufPos == bd->inbufCount) { if(0 >= (bd->inbufCount = read(bd->in_fd, bd->inbuf, IOBUF_SIZE))) { exit(1); } bd->inbufPos = 0; } // Avoid 32-bit overflow (dump bit buffer to top of output) if(bd->inbufBitCount >= 24) { bits = bd->inbufBits & ((1 << bd->inbufBitCount) - 1); bits_wanted = bits_wanted - bd->inbufBitCount; bits = bits << bits_wanted; bd->inbufBitCount = 0; } // Grab next 8 bits of input from buffer. bd->inbufBits = (bd->inbufBits << 8) | (bd->inbuf[bd->inbufPos] & 0xFF); bd->inbufPos = bd->inbufPos + 1; bd->inbufBitCount = bd->inbufBitCount + 8; } // Calculate result bd->inbufBitCount = bd->inbufBitCount - bits_wanted; bits = bits | ((bd->inbufBits >> bd->inbufBitCount) & ((1 << bits_wanted) - 1)); return bits; } /* Read block header at start of a new compressed data block. Consists of: * * 48 bits : Block signature, either pi (data block) or e (EOF block). * 32 bits : bw->headerCRC * 1 bit : obsolete feature flag. * 24 bits : origPtr (Burrows-wheeler unwind index, only 20 bits ever used) * 16 bits : Mapping table index. *[16 bits]: symToByte[symTotal] (Mapping table. For each bit set in mapping * table index above, read another 16 bits of mapping table data. * If correspondig bit is unset, all bits in that mapping table * section are 0.) * 3 bits : groupCount (how many huffman tables used to encode, anywhere * from 2 to MAX_GROUPS) * variable: hufGroup[groupCount] (MTF encoded huffman table data.) */ int read_block_header(struct bunzip_data *bd, struct bwdata *bw) { struct group_data *hufGroup; int hh; int ii; int jj; int kk; int symCount; int *base; int *limit; unsigned uc; unsigned *length = calloc(MAX_SYMBOLS, sizeof(unsigned)); unsigned *temp = calloc(MAX_HUFCODE_BITS + 1, sizeof(unsigned)); size_t minLen; size_t maxLen; int pp; #if defined(__M2__) int int_array = sizeof(int); int group_data_array = sizeof(struct group_data); #else int int_array = 1; int group_data_array = 1; #endif size_t hold; // Read in header signature and CRC (which is stored big endian) ii = get_bits(bd, 24); jj = get_bits(bd, 24); bw->headerCRC = get_bits(bd, 32); // Is this the EOF block with CRC for whole file? (Constant is "e") if(ii == 0x177245 && jj == 0x385090) { free(length); free(temp); return RETVAL_LAST_BLOCK; } // Is this a valid data block? (Constant is "pi".) if(ii != 0x314159 || jj != 0x265359) { return RETVAL_NOT_BZIP_DATA; } // We can add support for blockRandomised if anybody complains. if(get_bits(bd, 1)) { return RETVAL_OBSOLETE_INPUT; } if((bw->origPtr = get_bits(bd, 24)) > bd->dbufSize) { return RETVAL_DATA_ERROR; } // mapping table: if some byte values are never used (encoding things // like ascii text), the compression code removes the gaps to have fewer // symbols to deal with, and writes a sparse bitfield indicating which // values were present. We make a translation table to convert the symbols // back to the corresponding bytes. hh = get_bits(bd, 16); bd->symTotal = 0; for(ii = 0; ii < 16; ii += 1) { if(hh & (1 << (15 - ii))) { kk = get_bits(bd, 16); for(jj = 0; jj < 16; jj += 1) { if(kk & (1 << (15 - jj))) { bd->symToByte[bd->symTotal] = (16 * ii) + jj; bd->symTotal += 1; } } } } // How many different huffman coding groups does this block use? bd->groupCount = get_bits(bd, 3); if(bd->groupCount < 2 || bd->groupCount > MAX_GROUPS) { return RETVAL_DATA_ERROR; } // nSelectors: Every GROUP_SIZE many symbols we switch huffman coding // tables. Each group has a selector, which is an index into the huffman // coding table arrays. // // Read in the group selector array, which is stored as MTF encoded // bit runs. (MTF = Move To Front. Every time a symbol occurs its moved // to the front of the table, so it has a shorter encoding next time.) if(!(bd->nSelectors = get_bits(bd, 15))) { return RETVAL_DATA_ERROR; } for(ii = 0; ii < bd->groupCount; ii += 1) { bd->mtfSymbol[ii] = ii; } for(ii = 0; ii < bd->nSelectors; ii += 1) { // Get next value for(jj = 0; get_bits(bd, 1); jj += 1) if(jj >= bd->groupCount) { return RETVAL_DATA_ERROR; } // Decode MTF to get the next selector, and move it to the front. uc = bd->mtfSymbol[jj]; while(jj) { jj = jj - 1; bd->mtfSymbol[jj + 1] = bd->mtfSymbol[jj]; } bd->mtfSymbol[0] = bd->selectors[ii] = uc; } // Read the huffman coding tables for each group, which code for symTotal // literal symbols, plus two run symbols (RUNA, RUNB) symCount = bd->symTotal + 2; for(jj = 0; jj < bd->groupCount; jj += 1) { // Read lengths hh = get_bits(bd, 5); for(ii = 0; ii < symCount; ii += 1) { while(TRUE) { // !hh || hh > MAX_HUFCODE_BITS in one test. if(MAX_HUFCODE_BITS - 1 < hh - 1) { return RETVAL_DATA_ERROR; } // Grab 2 bits instead of 1 (slightly smaller/faster). Stop if // first bit is 0, otherwise second bit says whether to // increment or decrement. kk = get_bits(bd, 2); if(kk & 2) { hh += (1 - ((kk & 1) << 1)); } else { bd->inbufBitCount += 1; break; } } length[ii] = hh; } // Find largest and smallest lengths in this group minLen = maxLen = length[0]; for(ii = 1; ii < symCount; ii += 1) { hold = length[ii]; if(hold > maxLen) { maxLen = hold; } else if(hold < minLen) { minLen = hold; } } /* Calculate permute[], base[], and limit[] tables from length[]. * * permute[] is the lookup table for converting huffman coded symbols * into decoded symbols. It contains symbol values sorted by length. * * base[] is the amount to subtract from the value of a huffman symbol * of a given length when using permute[]. * * limit[] indicates the largest numerical value a symbol with a given * number of bits can have. It lets us know when to stop reading. * * To use these, keep reading bits until value <= limit[bitcount] or * youve read over 20 bits (error). Then the decoded symbol * equals permute[hufcode_value - base[hufcode_bitcount]]. */ hufGroup = bd->groups + (group_data_array * jj); require(minLen > 0, "hufGroup minLen can't have negative values\n"); require(minLen <= MAX_HUFCODE_BITS, "hufGroup minLen can't exceed MAX_HUFCODE_BITS\n"); hufGroup->minLen = minLen; require(maxLen > 0, "hufGroup maxLen can't have negative values\n"); require(maxLen <= MAX_HUFCODE_BITS, "hufGroup maxLen can't exceed MAX_HUFCODE_BITS\n"); hufGroup->maxLen = maxLen; // Note that minLen cant be smaller than 1, so we adjust the base // and limit array pointers so were not always wasting the first // entry. We do this again when using them (during symbol decoding). base = hufGroup->base - (int_array * 1); require(0 <= base, "can't have a negative hufGroup->base\n"); limit = hufGroup->limit - (int_array * 1); // zero temp[] and limit[], and calculate permute[] pp = 0; for(ii = minLen; ii <= maxLen; ii += 1) { require(MAX_HUFCODE_BITS >= ii, "Invalid HUFCODE_BITS length\n"); temp[ii] = 0; limit[ii] = 0; for(hh = 0; hh < symCount; hh += 1) { if(length[hh] == ii) { require(MAX_SYMBOLS >= pp, "pp exceeded MAX_SYMBOLS\n"); hufGroup->permute[pp] = hh; pp += 1; } } } // Count symbols coded for at each bit length for(ii = 0; ii < symCount; ii += 1) { hold = length[ii]; require(MAX_HUFCODE_BITS >= hold, "Invalid HUFCODE_BITS length\n"); temp[hold] += 1; } /* Calculate limit[] (the largest symbol-coding value at each bit * length, which is (previous limit<<1)+symbols at this level), and * base[] (number of symbols to ignore at each bit length, which is * limit minus the cumulative count of symbols coded for already). */ pp = hh = 0; for(ii = minLen; ii < maxLen; ii += 1) { pp += temp[ii]; limit[ii] = pp - 1; pp = pp << 1; hh += temp[ii]; base[ii + 1] = pp - hh; } limit[maxLen] = pp + temp[maxLen] - 1; limit[maxLen + 1] = INT_MAX; base[minLen] = 0; } free(length); free(temp); return 0; } /* First pass, read blocks symbols into dbuf[dbufCount]. * * This undoes three types of compression: huffman coding, run length encoding, * and move to front encoding. We have to undo all those to know when weve * read enough input. */ int read_huffman_data(struct bunzip_data *bd, struct bwdata *bw) { struct group_data *hufGroup; int ii; int jj; int kk; int runPos; int dbufCount; int symCount; int selector; int nextSym; int *byteCount; int *base; int *limit; unsigned hh; unsigned *dbuf = bw->dbuf; unsigned uc; #if defined(__M2__) int int_array = sizeof(int); int group_data_array = sizeof(struct group_data); #else int int_array = 1; int group_data_array = 1; #endif // Weve finished reading and digesting the block header. Now read this // blocks huffman coded symbols from the file and undo the huffman coding // and run length encoding, saving the result into dbuf[dbufCount++] = uc // Initialize symbol occurrence counters and symbol mtf table byteCount = bw->byteCount; for(ii = 0; ii < 256; ii += 1) { byteCount[ii] = 0; bd->mtfSymbol[ii] = ii; } // Loop through compressed symbols. This is the first "tight inner loop" // that needs to be micro-optimized for speed. (This one fills out dbuf[] // linearly, staying in cache more, so isnt as limited by DRAM access.) runPos = 0; dbufCount = 0; symCount = 0; selector = 0; // Some unnecessary initializations to shut gcc up. base = 0; limit = 0; hufGroup = 0; hh = 0; while(TRUE) { // Have we reached the end of this huffman group? if(!(symCount)) { // Determine which huffman coding group to use. symCount = GROUP_SIZE - 1; if(selector >= bd->nSelectors) { return RETVAL_DATA_ERROR; } hufGroup = bd->groups + (group_data_array * bd->selectors[selector]); selector += 1; base = hufGroup->base - (int_array * 1); require(0 <= base, "can't have negative hufGroup->base\n"); limit = hufGroup->limit - (int_array * 1); } else { symCount -= 1; } // Read next huffman-coded symbol (into jj). ii = hufGroup->minLen; jj = get_bits(bd, ii); while(jj > limit[ii]) { // if (ii > hufGroup->maxLen) return RETVAL_DATA_ERROR; ii += 1; // Unroll get_bits() to avoid a function call when the datas in // the buffer already. if(bd->inbufBitCount) { bd->inbufBitCount -= 1; kk = (bd->inbufBits >> bd->inbufBitCount) & 1; } else { kk = get_bits(bd, 1); } jj = (jj << 1) | kk; } // Huffman decode jj into nextSym (with bounds checking) jj -= base[ii]; if(ii > hufGroup->maxLen || jj >= MAX_SYMBOLS) { return RETVAL_DATA_ERROR; } nextSym = hufGroup->permute[jj]; // If this is a repeated run, loop collecting data if(nextSym <= SYMBOL_RUNB) { // If this is the start of a new run, zero out counter if(!runPos) { runPos = 1; hh = 0; } /* Neat trick that saves 1 symbol: instead of or-ing 0 or 1 at each bit position, add 1 or 2 instead. For example, 1011 is 1<<0 + 1<<1 + 2<<2. 1010 is 2<<0 + 2<<1 + 1<<2. You can make any bit pattern that way using 1 less symbol than the basic or 0/1 method (except all bits 0, which would use no symbols, but a run of length 0 doesnt mean anything in this context). Thus space is saved. */ hh += (runPos << nextSym); // +runPos if RUNA; +2*runPos if RUNB runPos = runPos << 1; continue; } /* When we hit the first non-run symbol after a run, we now know how many times to repeat the last literal, so append that many copies to our buffer of decoded symbols (dbuf) now. (The last literal used is the one at the head of the mtfSymbol array.) */ if(runPos) { runPos = 0; // Check for integer overflow if(hh > bd->dbufSize || dbufCount + hh > bd->dbufSize) { return RETVAL_DATA_ERROR; } uc = bd->symToByte[bd->mtfSymbol[0]]; byteCount[uc] += hh; while(hh) { hh -= 1; dbuf[dbufCount] = uc; dbufCount += 1; } } // Is this the terminating symbol? if(nextSym > bd->symTotal) { break; } /* At this point, the symbol we just decoded indicates a new literal character. Subtract one to get the position in the MTF array at which this literal is currently to be found. (Note that the result cant be -1 or 0, because 0 and 1 are RUNA and RUNB. Another instance of the first symbol in the mtf array, position 0, would have been handled as part of a run.) */ if(dbufCount >= bd->dbufSize) { return RETVAL_DATA_ERROR; } ii = nextSym - 1; uc = bd->mtfSymbol[ii]; // On my laptop, unrolling this memmove() into a loop shaves 3.5% off // the total running time. while(ii) { ii -= 1; bd->mtfSymbol[ii + 1] = bd->mtfSymbol[ii]; } bd->mtfSymbol[0] = uc; uc = bd->symToByte[uc]; // We have our literal byte. Save it into dbuf. byteCount[uc] += 1; dbuf[dbufCount] = uc; dbufCount += 1; } // Now we know what dbufCount is, do a better sanity check on origPtr. if(bw->origPtr >= (bw->writeCount = dbufCount)) { return RETVAL_DATA_ERROR; } return 0; } // Flush output buffer to disk void flush_bunzip_outbuf(struct bunzip_data *bd, int out_fd) { if(bd->outbufPos) { if(write(out_fd, bd->outbuf, bd->outbufPos) != bd->outbufPos) { exit(1); } bd->outbufPos = 0; } } void burrows_wheeler_prep(struct bunzip_data *bd, struct bwdata *bw) { int ii; int jj; int kk; unsigned *dbuf = bw->dbuf; int *byteCount = bw->byteCount; unsigned uc; // Turn byteCount into cumulative occurrence counts of 0 to n-1. jj = 0; for(ii = 0; ii < 256; ii += 1) { kk = jj + byteCount[ii]; byteCount[ii] = jj; jj = kk; } // Use occurrence counts to quickly figure out what order dbuf would be in // if we sorted it. for(ii = 0; ii < bw->writeCount; ii += 1) { uc = dbuf[ii] & 0xFF; dbuf[byteCount[uc]] = dbuf[byteCount[uc]] | (ii << 8); byteCount[uc] += 1; } // blockRandomised support would go here. // Using ii as position, jj as previous character, hh as current character, // and uc as run count. bw->dataCRC = 0xffffffff; /* Decode first byte by hand to initialize "previous" byte. Note that it doesnt get output, and if the first three characters are identical it doesnt qualify as a run (hence uc=255, which will either wrap to 1 or get reset). */ if(bw->writeCount) { bw->writePos = dbuf[bw->origPtr]; bw->writeCurrent = bw->writePos; bw->writePos = bw->writePos >> 8; bw->writeRun = -1; } } // Decompress a block of text to intermediate buffer int read_bunzip_data(struct bunzip_data *bd) { int rc = read_block_header(bd, bd->bwdata); if(!rc) { rc = read_huffman_data(bd, bd->bwdata); } // First thing that can be done by a background thread. burrows_wheeler_prep(bd, bd->bwdata); return rc; } // Undo burrows-wheeler transform on intermediate buffer to produce output. // If !len, write up to len bytes of data to buf. Otherwise write to out_fd. // Returns len ? bytes written : 0. Notice all errors are negative #s. // // Burrows-wheeler transform is described at: // http://dogma.net/markn/articles/bwt/bwt.htm // http://marknelson.us/1996/09/01/bwt/ int write_bunzip_data(struct bunzip_data *bd, struct bwdata *bw, int out_fd, char *outbuf, int len) { unsigned *dbuf = bw->dbuf; int count; int pos; int current; int run; int copies; int outbyte; int previous; int gotcount = 0; int i; int crc_index; while(TRUE) { // If last read was short due to end of file, return last block now if(bw->writeCount < 0) { return bw->writeCount; } // If we need to refill dbuf, do it. if(!bw->writeCount) { i = read_bunzip_data(bd); if(i) { if(i == RETVAL_LAST_BLOCK) { bw->writeCount = i; return gotcount; } else { return i; } } } // loop generating output count = bw->writeCount; pos = bw->writePos; current = bw->writeCurrent; run = bw->writeRun; while(count) { // If somebody (like tar) wants a certain number of bytes of // data from memory instead of written to a file, humor them. if(len && bd->outbufPos >= len) { goto dataus_interruptus; } count -= 1; // Follow sequence vector to undo Burrows-Wheeler transform. previous = current; pos = dbuf[pos]; current = pos & 0xff; pos = pos >> 8; // Whenever we see 3 consecutive copies of the same byte, // the 4th is a repeat count if(run == 3) { run += 1; copies = current; outbyte = previous; current = -1; } else { run += 1; copies = 1; outbyte = current; } // Output bytes to buffer, flushing to file if necessary while(copies) { copies -= 1; if(bd->outbufPos == IOBUF_SIZE) { flush_bunzip_outbuf(bd, out_fd); } bd->outbuf[bd->outbufPos] = outbyte; bd->outbufPos += 1; crc_index = ((bw->dataCRC >> 24) ^ outbyte) & 0xFF; bw->dataCRC = (bw->dataCRC << 8) ^ bd->crc32Table[crc_index]; } if(current != previous) { run = 0; } } // decompression of this block completed successfully bw->dataCRC = ~(bw->dataCRC); #if defined(__M2__) // & 0xFFFFFFFF not working if(sizeof(unsigned) == 8) { bw->dataCRC <<= 32; bw->dataCRC >>= 32; } #endif bd->totalCRC = ((bd->totalCRC << 1) | (bd->totalCRC >> 31)) ^ bw->dataCRC; // if this block had a crc error, force file level crc error. if(bw->dataCRC != bw->headerCRC) { bd->totalCRC = bw->headerCRC + 1; return RETVAL_LAST_BLOCK; } dataus_interruptus: bw->writeCount = count; if(len) { gotcount += bd->outbufPos; memcpy(outbuf, bd->outbuf, len); // If we got enough data, checkpoint loop state and return len -= bd->outbufPos; if(len < 1) { bd->outbufPos -= len; if(bd->outbufPos) { memmove(bd->outbuf, bd->outbuf + len, bd->outbufPos); } bw->writePos = pos; bw->writeCurrent = current; bw->writeRun = run; return gotcount; } } } } // Allocate the structure, read file header. If !len, src_fd contains // filehandle to read from. Else inbuf contains data. int start_bunzip(struct bunzip_data **bdp, int src_fd) { struct bunzip_data *bd; unsigned i; // Figure out how much data to allocate. i = sizeof(struct bunzip_data); // Allocate bunzip_data. Most fields initialize to zero. *bdp = malloc(i); bd = *bdp; memset(bd, 0, i); bd->inbuf = calloc(IOBUF_SIZE, sizeof(char)); bd->outbuf = calloc(IOBUF_SIZE, sizeof(char)); bd->selectors = calloc(32768, sizeof(char)); bd->groups = calloc(MAX_GROUPS, sizeof(struct group_data)); for(i = 0; i < MAX_GROUPS; i += 1) { bd->groups[i].limit = calloc(MAX_HUFCODE_BITS + 1, sizeof(int)); bd->groups[i].base = calloc(MAX_HUFCODE_BITS, sizeof(int)); bd->groups[i].permute = calloc(MAX_SYMBOLS, sizeof(int)); } bd->symToByte = calloc(256, sizeof(unsigned)); bd->mtfSymbol = calloc(256, sizeof(unsigned)); bd->crc32Table = calloc(256, sizeof(unsigned)); bd->bwdata = calloc(1, sizeof(struct bwdata)); bd->bwdata->byteCount = calloc(256, sizeof(int)); unsigned *crc32Table; bd->in_fd = src_fd; crc_init(bd->crc32Table, 0); // Ensure that file starts with "BZh". char *header = "BZh"; for(i = 0; i < 3; i += 1) if(get_bits(bd, 8) != header[i]) { return RETVAL_NOT_BZIP_DATA; } // Next byte ascii 1-9, indicates block size in units of 100k of // uncompressed data. Allocate intermediate buffer for block. i = get_bits(bd, 8); if(i < 49 || i > 57) { return RETVAL_NOT_BZIP_DATA; } bd->dbufSize = 100000 * (i - 48); bd->bwdata[0].dbuf = malloc(bd->dbufSize * sizeof(int)); return 0; } // Example usage: decompress src_fd to dst_fd. (Stops at end of bzip data, // not end of file.) int bunzipStream(int src_fd, int dst_fd) { struct bunzip_data *bd; int i; int j; if(!(i = start_bunzip(&bd, src_fd))) { i = write_bunzip_data(bd, bd->bwdata, dst_fd, 0, 0); if(i == RETVAL_LAST_BLOCK) { if(bd->bwdata[0].headerCRC == bd->totalCRC) { i = 0; } else { i = RETVAL_DATA_ERROR; } } } flush_bunzip_outbuf(bd, dst_fd); free(bd->bwdata[0].dbuf); free(bd->inbuf); free(bd->outbuf); free(bd->selectors); for(j = 0; j < MAX_GROUPS; j += 1) { free(bd->groups[j].limit); free(bd->groups[j].base); free(bd->groups[j].permute); } free(bd->groups); free(bd->symToByte); free(bd->mtfSymbol); free(bd->crc32Table); free(bd->bwdata->byteCount); free(bd->bwdata); free(bd); return -i; } void do_bunzip2(int in_fd, int out_fd) { int err = bunzipStream(in_fd, out_fd); if(err) { exit(1); } } int main(int argc, char **argv) { char *name = NULL; char *dest = NULL; FUZZING = FALSE; /* process arguments */ int i = 1; while(i < argc) { if(NULL == argv[i]) { i += 1; } else if(match(argv[i], "-f") || match(argv[i], "--file")) { name = argv[i + 1]; require(NULL != name, "the --file option requires a filename to be given\n"); i += 2; } else if(match(argv[i], "-o") || match(argv[i], "--output")) { dest = argv[i + 1]; require(NULL != dest, "the --output option requires a filename to be given\n"); i += 2; } else if(match(argv[i], "--fuzzing-mode")) { FUZZING = TRUE; i += 1; } else if(match(argv[i], "-h") || match(argv[i], "--help")) { fputs("Usage: ", stderr); fputs(argv[0], stderr); fputs(" --file $input.bz2", stderr); fputs(" --output $output\n", stderr); fputs("--help to get this message\n", stderr); exit(EXIT_SUCCESS); } else { fputs("Unknown option:", stderr); fputs(argv[i], stderr); fputs("\nAborting to avoid problems\n", stderr); exit(EXIT_FAILURE); } } /* Deal with no input */ if(NULL == name) { fputs("an input file (--file $name) must be provided\n", stderr); exit(EXIT_FAILURE); } int in_fd = open(name, 0, 0); if(in_fd < 0) { fputs("Unable to open input file\n", stderr); exit(EXIT_FAILURE); } /* If an output name isn't provided */ if(NULL == dest) { int length = strlen(name); require(length > 4, "file name length not sufficient, please provide output name with --output $filename\n"); } int out_fd; if(FUZZING) { /* Dump to /dev/null the garbage data produced during fuzzing */ out_fd = open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0600); } else { out_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, 0600); } if(out_fd < 0) { fputs("Unable to open output file for writing\n", stderr); exit(EXIT_FAILURE); } do_bunzip2(in_fd, out_fd); close(in_fd); close(out_fd); exit(0); }
/* Copyright (C) 2019 Jeremiah Orians * This file is part of mescc-tools * * mescc-tools is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> // CONSTANT BUFFER_SIZE 4096 #define BUFFER_SIZE 4096 int main(int argc, char** argv) { if(2 > argc) { fputs("catm requires 2 or more arguments\n", stderr); exit(EXIT_FAILURE); } int output = open(argv[1], 577 , 384); if(-1 == output) { fputs("The file: ", stderr); fputs(argv[1], stderr); fputs(" is not a valid output file name\n", stderr); exit(EXIT_FAILURE); } int i; int bytes; char* buffer = calloc(BUFFER_SIZE + 1, sizeof(char)); int input; for(i = 2; i < argc ; i = i + 1) { input = open(argv[i], 0, 0); if(-1 == input) { fputs("The file: ", stderr); fputs(argv[i], stderr); fputs(" is not a valid input file name\n", stderr); exit(EXIT_FAILURE); } keep: bytes = read(input, buffer, BUFFER_SIZE); write(output, buffer, bytes); if(BUFFER_SIZE == bytes) goto keep; } free(buffer); return EXIT_SUCCESS; }
/* Copyright (C) 2020 fosslinux * This file is part of mescc-tools * * mescc-tools is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include "M2libc/bootstrappable.h" /* Define all of the constants */ // CONSTANT FALSE 0 #define FALSE 0 // CONSTANT TRUE 1 #define TRUE 1 // CONSTANT MAX_STRING 4096 #define MAX_STRING 4096 // CONSTANT MAX_ARRAY 256 #define MAX_ARRAY 256 /* Prototypes for external funcs */ /* Globals */ int verbose; /* UTILITY FUNCTIONS */ /* Function to find a character's position in a string (last match) */ int find_last_char_pos(char* string, char a) { int i = strlen(string) - 1; if(i < 0) return i; while(i >= 0) { /* * This conditional should be in the while conditional but we are * running into the M2-Planet short-circuit bug. */ if(a == string[i]) break; i = i - 1; } return i; } /* Function to find the length of a char**; an array of strings */ int array_length(char** array) { int length = 0; while(array[length] != NULL) { length = length + 1; } return length; } /* PROCESSING FUNCTIONS */ char* directory_dest(char* dest, char* source, int require_directory) { /* * First, check if it is a directory to copy to. * We have two ways of knowing this: * - If the destination ends in a slash, the user has explicitly said * it is a directory. * - Normally we would use stat() but we don't want to force support for * that syscall onto the kernel, so we just attempt to chdir() into it * and if it works then it must be a directory. A bit hacky, bit it * works. */ int isdirectory = FALSE; if(dest[strlen(dest) - 1] == '/') { isdirectory = TRUE; } if(!isdirectory) { /* Use the other testing method */ /* * Get the current path so that we can chdir back to it if it does * chdir successfully. */ char* current_path = calloc(MAX_STRING, sizeof(char)); require(current_path != NULL, "Memory initialization of current_path in directory_dest failed\n"); getcwd(current_path, MAX_STRING); require(!match("", current_path), "getcwd() failed\n"); /* * chdir expects an absolute path. * If the first character is / then it is already absolute, otherwise * it is relative and needs to be changed (by appending current_path * to the dest path). */ char* chdir_dest = calloc(MAX_STRING, sizeof(char)); require(chdir_dest != NULL, "Memory initialization of chdir_dest in directory_dest failed\n"); if(dest[0] != '/') { /* The path is relative, append current_path */ strcat(chdir_dest, current_path); strcat(chdir_dest, "/"); strcat(chdir_dest, dest); } else { /* The path is absolute */ strcpy(chdir_dest, dest); } if(0 <= chdir(chdir_dest)) { /* chdir returned successfully */ /* * But because of M2-Planet, that dosen't mean anything actually * happened, check that before we go any further. */ char* new_path = calloc(MAX_STRING, sizeof(char)); require(new_path != NULL, "Memory initialization of new_path in directory_dest failed\n"); getcwd(new_path, MAX_STRING); if(!match(current_path, new_path)) { isdirectory = TRUE; chdir(current_path); } } free(chdir_dest); free(current_path); } /* * If it isn't a directory, and we require one, error out. * Otherwise, just return what we were given, we're done here. */ if(require_directory) require(isdirectory, "Provide a directory destination for multiple source files\n"); if(!isdirectory) return dest; /* If it is, we need to make dest a full path */ /* 1. Get the basename of source */ char* basename = calloc(MAX_STRING, sizeof(char)); require(basename != NULL, "Memory initialization of basename in directory_dest failed\n"); int last_slash_pos = find_last_char_pos(source, '/'); if(last_slash_pos >= 0) { /* Yes, there is a slash in it, copy over everything after that pos */ unsigned spos; /* source pos */ unsigned bpos = 0; /* basename pos */ /* Do the actual copy */ for(spos = last_slash_pos + 1; spos < strlen(source); spos = spos + 1) { basename[bpos] = source[spos]; bpos = bpos + 1; } } else { /* No, there is no slash in it, hence the basename is just the source */ strcpy(basename, source); } /* 2. Ensure our dest (which is a directory) has a trailing slash */ if(dest[strlen(dest) - 1] != '/') { strcat(dest, "/"); } /* 3. Add the basename to the end of the directory */ strcat(dest, basename); free(basename); /* Now we have a returnable path! */ return dest; } void copy_file(char* source, char* dest) { if(verbose) { /* Output message */ /* Of the form 'source' -> 'dest' */ fputs("'", stdout); fputs(source, stdout); fputs("' -> '", stdout); fputs(dest, stdout); fputs("'\n", stdout); } /* Open source and dest as FILE*s */ FILE* fsource = fopen(source, "r"); if(fsource == NULL) { fputs("Error opening source file ", stderr); fputs(source, stderr); fputc('\n', stderr); exit(EXIT_FAILURE); } FILE* fdest = fopen(dest, "w"); if(fdest < 0) { fputs("Error opening destination file", stderr); fputs(dest, stderr); fputc('\n', stderr); exit(EXIT_FAILURE); } /* * The following loop reads a character from the source and writes it to the * dest file. This is all M2-Planet supports. */ int c = fgetc(fsource); while(c != EOF) { fputc(c, fdest); c = fgetc(fsource); } /* Cleanup */ fclose(fsource); fclose(fdest); } int main(int argc, char** argv) { /* Initialize variables */ char** sources = calloc(MAX_ARRAY, sizeof(char*)); require(sources != NULL, "Memory initialization of sources failed\n"); int sources_index = 0; char* dest = NULL; /* Set defaults */ verbose = FALSE; int i = 1; int j; int args_found; /* Loop arguments */ while(i <= argc) { if(NULL == argv[i]) { /* Ignore and continue */ i = i + 1; } else if(match(argv[i], "-h") || match(argv[i], "--help")) { fputs("Usage: ", stdout); fputs(argv[0], stdout); fputs(" [-h | --help] [-V | --version] [-v | --verbose] source1 source2 sourcen destination\n", stdout); exit(EXIT_SUCCESS); } else if(match(argv[i], "-V") || match(argv[i], "--version")) { /* Output version */ fputs("cp version 1.2.1\n", stdout); exit(EXIT_SUCCESS); } else if(match(argv[i], "-v") || match(argv[i], "--verbose")) { verbose = TRUE; i = i + 1; } else if(argv[i][0] != '-') { /* It is not an option */ /* * We can tell if this is the source file or the destination file * through looking *ahead*. If it is the last of this type of argument then * it must be the destination. (1 destination, many sources). */ j = i + 1; args_found = 0; while(j < array_length(argv)) { if(argv[j][0] != '-') { /* It's one of these type of arguments */ args_found = args_found + 1; } j = j + 1; } if(args_found == 0) { /* We are setting the destination (there are no more left after this) */ dest = calloc(MAX_STRING, sizeof(char)); require(dest != NULL, "Memory initialization of dest failed\n"); strcpy(dest, argv[i]); } else { /* We are setting a source */ require(sources_index < MAX_ARRAY, "Too many files\n"); sources[sources_index] = calloc(MAX_STRING, sizeof(char)); require(sources[sources_index] != NULL, "Memory initialization of sources[source_index] failed\n"); strcpy(sources[sources_index], argv[i]); sources_index = sources_index + 1; } i = i + 1; } else { /* Unknown argument */ fputs("UNKNOWN_ARGUMENT\n", stderr); exit(EXIT_FAILURE); } } /* Sanitize values */ /* Ensure the two values have values */ /* Another workaround for short-circuit bug */ int error = FALSE; if(sources[0] == NULL) error = TRUE; if(error == FALSE) if(match(sources[0], "")) error = TRUE; require(!error, "Provide a source file\n"); error = FALSE; if(dest == NULL) error = TRUE; if(error == FALSE) if(match(dest, "")) error = TRUE; require(!error, "Provide a destination file\n"); /* Loop through all of the sources, copying each one */ char* this_dest; for(i = 0; i < array_length(sources); i = i + 1) { /* Convert the dest variable to a full path if it's a directory copying to */ /* * Also, if there is more than one source, we have to be copying to * a directory destination... */ if(array_length(sources) == 1) { dest = directory_dest(dest, sources[i], FALSE); copy_file(sources[i], dest); } else { this_dest = calloc(MAX_STRING, sizeof(char)); require(this_dest != NULL, "Memory initalization of this_dest failed\n"); this_dest = directory_dest(dest, sources[i], TRUE); copy_file(sources[i], this_dest); } /* Perform the actual copy */ free(sources[i]); } free(sources); free(dest); return EXIT_SUCCESS; }
/* Copyright (C) 2020 fosslinux * This file is part of mescc-tools * * mescc-tools is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include "M2libc/bootstrappable.h" /* Define all of the constants */ // CONSTANT FALSE 0 #define FALSE 0 // CONSTANT TRUE 1 #define TRUE 1 // CONSTANT MAX_STRING 4096 #define MAX_STRING 4096 // CONSTANT MAX_ARRAY 256 #define MAX_ARRAY 256 struct files { char* name; struct files* next; }; /* Globals */ int verbose; /* PROCESSING FUNCTIONS */ int main(int argc, char** argv) { /* Initialize variables */ char* mode = NULL; struct files* f = NULL; struct files* n; int ok; /* Set defaults */ verbose = FALSE; int i = 1; /* Loop arguments */ while(i <= argc) { if(NULL == argv[i]) { /* Ignore and continue */ i = i + 1; } else if(match(argv[i], "-h") || match(argv[i], "--help")) { fputs("Usage: ", stdout); fputs(argv[0], stdout); fputs(" [-h | --help] [-V | --version] [-v | --verbose]\n", stdout); exit(EXIT_SUCCESS); } else if(match(argv[i], "-V") || match(argv[i], "--version")) { /* Output version */ fputs("chmod version 1.2.1\n", stdout); exit(EXIT_SUCCESS); } else if(match(argv[i], "-v") || match(argv[i], "--verbose")) { verbose = TRUE; i = i + 1; } else { /* It must be the file or the mode */ if(mode == NULL) { /* Mode always comes first */ mode = calloc(MAX_STRING, sizeof(char)); require(mode != NULL, "Memory initialization of mode failed\n"); /* We need to indicate it is octal */ strcat(mode, "0"); strcat(mode, argv[i]); } else { /* It's a file, as the mode is already done */ n = calloc(1, sizeof(struct files)); require(n != NULL, "Memory initialization of files failed\n"); n->next = f; f = n; f->name = argv[i]; } i = i + 1; } } /* Ensure the two values have values */ require(mode != NULL, "Provide a mode\n"); require(f != NULL, "Provide a file\n"); /* Convert the mode str into octal */ int omode = strtoint(mode); /* Loop over files to be operated on */ while(NULL != f) { /* Make sure the file can be opened */ ok = access(f->name, 0); if(ok != 0) { fputs("The file: ", stderr); fputs(f->name, stderr); fputs(" does not exist\n", stderr); exit(EXIT_FAILURE); } /* Verbose message */ if(verbose) { fputs("mode of '", stdout); fputs(f->name, stdout); fputs("' changed to ", stdout); fputs(mode, stdout); fputs("\n", stdout); } /* Perform the chmod */ chmod(f->name, omode); f = f->next; } }
/* Copyright (C) 2021 Jeremiah Orians * This file is part of mescc-tools-extra * * mescc-tools-extra is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools-extra is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools-extra. If not, see <http://www.gnu.org/licenses/>. */ /* * "rm" can be used to delete files. It can also delete * parent directories. * * Usage: rm <dir1>/<file1> <file2> * * These are all highly standard and portable headers. */ #include <stdio.h> #include <string.h> /* This is for unlink() ; this may need to be changed for some platforms. */ #include <unistd.h> /* For unlink() */ #include <stdlib.h> #include "M2libc/bootstrappable.h" void delete_dir(char* name) { int r = unlink(name); if(0 != r) { fputs("unable to delete file: ", stderr); fputs(name, stderr); fputs(" !!!\n", stderr); } } int main(int argc, char **argv) { int i; for(i = 1; argc > i; i = i + 1) { delete_dir(argv[i]); } return 0; }
/* Copyright (C) 2019 Jeremiah Orians * This file is part of mescc-tools * * mescc-tools is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * mescc-tools is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mescc-tools. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "M2libc/bootstrappable.h" char* input_name; FILE* input; char* output_name; FILE* output; char* pattern; size_t pattern_length; char* replacement; char* buffer; size_t buffer_index; char* hold; void read_next_byte() { int c= hold[0]; size_t i = 0; while(i < pattern_length) { hold[i] = hold[i+1]; i = i + 1; } hold[pattern_length-1] = buffer[buffer_index]; buffer_index = buffer_index + 1; /* NEVER WRITE NULLS!!! */ if(0 != c) fputc(c, output); } void clear_hold() { /* FILL hold with NULLS */ size_t i = 0; while(i < pattern_length) { hold[i] = 0; i = i + 1; } } void check_match() { /* Do the actual replacing */ if(match(pattern, hold)) { fputs(replacement, output); clear_hold(); } } int main(int argc, char** argv) { output_name = "/dev/stdout"; pattern = NULL; replacement = NULL; buffer_index = 0; int i = 1; while (i < argc) { if(NULL == argv[i]) { i = i + 1; } else if(match(argv[i], "-f") || match(argv[i], "--file")) { input_name = argv[i+1]; require(NULL != input_name, "the --file option requires a filename to be given\n"); i = i + 2; } else if(match(argv[i], "-o") || match(argv[i], "--output")) { output_name = argv[i+1]; require(NULL != output_name, "the --output option requires a filename to be given\n"); i = i + 2; } else if(match(argv[i], "-m") || match(argv[i], "--match-on")) { pattern = argv[i+1]; require(NULL != pattern, "the --match-on option requires a string to be given\n"); i = i + 2; } else if(match(argv[i], "-r") || match(argv[i], "--replace-with")) { replacement = argv[i+1]; require(NULL != replacement, "the --replace-with option requires a string to be given\n"); i = i + 2; } else if(match(argv[i], "-h") || match(argv[i], "--help")) { fputs("Usage: ", stderr); fputs(argv[0], stderr); fputs(" --file $input", stderr); fputs(" --match-on $string", stderr); fputs(" --replace-with $string", stderr); fputs(" [--output $output] (or it'll dump to stdout)\n", stderr); fputs("--help to get this message\n", stderr); exit(EXIT_SUCCESS); } else { fputs("Unknown option:", stderr); fputs(argv[i], stderr); fputs("\nAborting to avoid problems\n", stderr); exit(EXIT_FAILURE); } } /* Sanity check that we got everything we need */ require(NULL != input_name, "You need to pass an input file with --file\n"); require(NULL != output_name, "You need to pass an output file with --output\n"); require(NULL != pattern, "You can't do a replacement without something to match on\n"); require(NULL != replacement, "You can't do a replacement without something to replace it with\n"); input = fopen(input_name, "r"); require(NULL != input, "unable to open requested input file!\n"); /* Get enough buffer to read it all */ fseek(input, 0, SEEK_END); size_t size = ftell(input); buffer = malloc((size + 8) * sizeof(char)); /* Save ourself work if the input file is too small */ pattern_length = strlen(pattern); require(pattern_length < size, "input file is to small for pattern\n"); /* Now read it all into buffer */ fseek(input, 0, SEEK_SET); size_t r = fread(buffer,sizeof(char), size, input); require(r == size, "incomplete read of input\n"); fclose(input); /* Now we can safely open the output (which could have been the same as the input */ output = fopen(output_name, "w"); require(NULL != input, "unable to open requested output file!\n"); /* build our match buffer */ hold = calloc(pattern_length + 4, sizeof(char)); require(NULL != hold, "temp memory allocation failed\n"); /* Replace it all */ while((size + pattern_length + 4) >= buffer_index) { read_next_byte(); check_match(); } fclose(output); }
275d135eea485d900553def672a53a1d3c627010660da3e5834658154bc9b8e6 x86/bin/blood-elf dee788ac575fbaea8695c5f2f79fda5593fa23b5aae14420b74df0a6fa15be45 x86/bin/catm 7a2bd22b80a88d28d88df2193d71d506dbd09713b412287a71449ef8f727d90a x86/bin/chmod 7b4e27df73125e37b2ca6fac8fec5ad14a6ffe3d9efdebbddeb675c59175ca25 x86/bin/cp 941863a4dc7188c44a38cf312b7259f4cd32dfe1b75775db108dd09ccc1595ca x86/bin/get_machine 92b2414feba8744bc41ad4b165004ce42a4c47208094868d0b60e9ac6cdf229a x86/bin/hex2 967caba7eb0f3223d685f67341e0df072c9700fed3dd61e892a3cce1f4404cb4 x86/bin/kaem b7e7e05714748e919e2ec8f985afd4e5c8a08dca6384f3179721a2ca5845a57a x86/bin/M1 f8c09c42c203fb21c3bb8ff6537df7864b85007a72380bd06cd1bfb5acd92588 x86/bin/M2-Mesoplanet e33eb9c487d1e34bcca70e0d55c158cc24ed5b671fba72db8102182daa0d35a8 x86/bin/M2-Planet 481ad71972d5fece4de35dd2c357910d3740fbd5d18bcab1ae0353fcde45d846 x86/bin/match 1e3f266c93d56e4ee05fec8422f92e9261c674a09b22efa31b4198172be07b6f x86/bin/mkdir 47bcca1d7f9a07df1256c6372fbcd14afb98b0e53f4cd3a42eb73bcef1468aa2 x86/bin/replace 91d2a629a99f9b23c66acdc5f156a21bf1b26306031ca2ad3da4869e142d29d8 x86/bin/rm 130cdb574ea4e78b703182387d610d27bff3fb5bc9f19c8f6a22d3b88e47a796 x86/bin/sha256sum c43dc403413cdd11e5d10159219a6984c3571fd3bc025f23123257b6d03641b5 x86/bin/ungz caf4adc1d50b35147f16a03cbca4b45339b11d3aec260d13eb21050185438d3a x86/bin/unbz2 b8339525443c02be31b0b6d55cbc0cdebb7921ce641eff1596800a1be1a874e5 x86/bin/untar
/* * SPDX-FileCopyrightText: 2022 fosslinux <fosslinux@aussies.space> * * SPDX-License-Identifier: GPL-3.0-or-later */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <bootstrappable.h> #include <unistd.h> #define MAX_STRING 4096 #define MAX_TOKENS 3 char *get_distfiles(char **envp) { char *envvar = "distfiles="; int i = 0; while (envp[i] != NULL && strncmp(envp[i], envvar, strlen(envvar)) != 0) i += 1; // Now we have distfiles= - get just the part we want. require(envp[i] != NULL, "Unable to find distfiles environment variable"); return envp[i] + strlen(envvar); } int main(int argc, char **argv, char **envp) { // Random file things require(argc == 2, "Usage: checksum-transcriber FILENAME"); char *input = argv[1]; FILE *in = fopen(input, "r"); require(in != NULL, "File does not exist"); char *output = calloc(MAX_STRING, sizeof(char)); require(strcpy(output, input) != NULL, "Failed copying string"); require(strcat(output, ".SHA256SUM") != NULL, "Failed concating string"); FILE *out = fopen(output, "w+"); require(out != NULL, "Failed opening output file"); char *orig_line; char *line = calloc(MAX_STRING, sizeof(char)); require(line != NULL, "Failed allocating string"); char **tokens; char *new_line; char *checksum; char *filename; int i; fgets(line, MAX_STRING, in); while (strlen(line) != 0) { // Split each line into tokens orig_line = line; tokens = calloc(MAX_TOKENS, sizeof(char*)); i = 0; while (i < MAX_TOKENS) { tokens[i] = line; new_line = strchr(line, ' '); // Occurs when there are only two tokens if (new_line == NULL) break; line = new_line; line[0] = '\0'; line += 1; i += 1; } line = strchr(line, '\n'); line[0] = '\0'; // Get checksum and filename checksum = tokens[1]; if (tokens[2] != NULL) { filename = tokens[2]; } else { filename = strrchr(tokens[0], '/'); filename += 1; } // Put it all together fputs(checksum, out); fputs(" ", out); fputs(get_distfiles(envp), out); fputc('/', out); fputs(filename, out); fputc('\n', out); // Cleanup i = 0; free(orig_line); free(tokens); line = calloc(MAX_STRING, sizeof(char)); require(line != NULL, "Failed allocating string"); fgets(line, MAX_STRING, in); } // Clean up fclose(in); fclose(out); }
216fe50dd8e70928f382d40ced1d9a2c167f7a5aafd555901194c5559af8c526 /usr/bin/checksum-transcriber
#include <string.h> #include <stdio.h> #include <stdlib.h> #include "M2libc/bootstrappable.h" /* SPDX-FileCopyrightText: 2023 Richard Masters <grick23@gmail.com> SPDX-License-Identifier: MIT Simple Patch program. This program is written in a subset of C called M2, which is from the stage0-posix bootstrap project. Example usage: ./simple-patch file_to_patch before_pattern_file after_pattern_file */ // function prototypes void read_file_or_die(char *file_name, char **buffer, int *file_size); void patch_buffer_or_die(char *patch_file_before_buffer, int patch_file_before_size, char *before_pattern_buffer, int before_pattern_size, char *after_pattern_buffer, int after_pattern_size, char *patch_file_after_buffer); void writestr_fd(int fd, char *str); int memsame(char *search_buffer, int search_size, char *pattern_buffer, int pattern_size); int main(int argc, char **argv) { char *patch_file_before_buffer; int patch_file_before_size; char *before_pattern_buffer; int before_pattern_size; char *after_pattern_buffer; int after_pattern_size; int patch_file_after_size; char *patch_file_after_buffer; int patch_file_fd; read_file_or_die(argv[1], &patch_file_before_buffer, &patch_file_before_size); read_file_or_die(argv[2], &before_pattern_buffer, &before_pattern_size); read_file_or_die(argv[3], &after_pattern_buffer, &after_pattern_size); patch_file_after_size = patch_file_before_size - before_pattern_size + after_pattern_size; patch_file_after_buffer = calloc(patch_file_after_size, sizeof(char)); patch_buffer_or_die(patch_file_before_buffer, patch_file_before_size, before_pattern_buffer, before_pattern_size, after_pattern_buffer, after_pattern_size, patch_file_after_buffer); patch_file_fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0); write(patch_file_fd, patch_file_after_buffer, patch_file_after_size); close(patch_file_fd); return EXIT_SUCCESS; } void read_file_or_die(char *file_name, char **buffer, int *file_size) { int file_fd; int num_bytes_read; file_fd = open(file_name, O_RDONLY, 0); if (file_fd == -1) { writestr_fd(2, "Could not open file: "); writestr_fd(2, file_name); writestr_fd(2, "\n"); exit(1); } // determine file size *file_size = lseek(file_fd, 0, SEEK_END); // go back to beginning of file lseek(file_fd, 0, SEEK_SET); // alloc a buffer to read the entire file *buffer = calloc(*file_size, sizeof(char)); // read the entire patch file num_bytes_read = read(file_fd, *buffer, *file_size); if (num_bytes_read != *file_size) { writestr_fd(2, "Could not read file: "); writestr_fd(2, file_name); writestr_fd(2, "\n"); exit(1); } close(file_fd); } void patch_buffer_or_die(char *patch_file_before_buffer, int patch_file_before_size, char *before_pattern_buffer, int before_pattern_size, char *after_pattern_buffer, int after_pattern_size, char *patch_file_after_buffer) { char *pos = patch_file_before_buffer; int prefix_len = 0; // look for the pattern at every offset while (prefix_len < patch_file_before_size) { // if we find the pattern, replace it and return if (memsame(pos, patch_file_before_size - prefix_len, before_pattern_buffer, before_pattern_size)) { memcpy(patch_file_after_buffer, patch_file_before_buffer, prefix_len); memcpy(patch_file_after_buffer + prefix_len, after_pattern_buffer, after_pattern_size); memcpy(patch_file_after_buffer + prefix_len + after_pattern_size, patch_file_before_buffer + prefix_len + before_pattern_size, patch_file_before_size - (prefix_len + before_pattern_size)); return; } pos = pos + 1; prefix_len = prefix_len + 1; } /* if we don't find the pattern, something is wrong, so exit with error */ exit(1); } /* Write the string to the given file descriptor. */ void writestr_fd(int fd, char *str) { write(fd, str, strlen(str)); } /* Is the pattern located at the start of the search buffer (and not exceeding the length of the search buffer)? */ int memsame(char *search_buffer, int search_size, char *pattern_buffer, int pattern_size) { int check_offset = 0; if (pattern_size > search_size) { return FALSE; } while (check_offset < pattern_size) { if (search_buffer[check_offset] != pattern_buffer[check_offset]) { return FALSE; } check_offset = check_offset + 1; } return TRUE; }
https://mirrors.kernel.org/gnu/mes/mes-0.24.2.tar.gz 7ddae0854e46ebfa18c13ab37e64839a7b86ea88aeed366a8d017efd11dae86e https://download.savannah.gnu.org/releases/nyacc/nyacc-1.00.2.tar.gz f36e4fb7dd524dc3f4b354d3d5313f69e7ce5a6ae93711e8cf6d51eaa8d2b318
// SPDX-FileCopyrightText: 2020 fosslinux <fosslinux@aussies.space> // SPDX-FileCopyrightText: 2021 Andrius Å tikonas <andrius@stikonas.eu> // // SPDX-License-Identifier: GPL-3.0-or-later #undef SYSTEM_LIBC #define MES_VERSION "0.24.2"
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_LINUX_X86_SYSCALL_H #define __MES_LINUX_X86_SYSCALL_H 1 /* libc-mini */ #ifndef SYS_exit // CONSTANT SYS_exit 0x01 #define SYS_exit 0x01 #endif #ifndef SYS_write // CONSTANT SYS_write 0x04 #define SYS_write 0x04 #endif /* libc */ // CONSTANT SYS_fork 0x02 #define SYS_fork 0x02 // CONSTANT SYS_read 0x03 #define SYS_read 0x03 // CONSTANT SYS_open 0x05 #define SYS_open 0x05 // CONSTANT SYS_waitpid 0x07 #define SYS_waitpid 0x07 // CONSTANT SYS_wait4 0x72 #define SYS_wait4 0x72 // CONSTANT SYS_execve 0x0b #define SYS_execve 0x0b // CONSTANT SYS_chmod 0x0f #define SYS_chmod 0x0f // CONSTANT SYS_access 0x21 #define SYS_access 0x21 // CONSTANT SYS_brk 0x2d #define SYS_brk 0x2d // CONSTANT SYS_ioctl 0x36 #define SYS_ioctl 0x36 // CONSTANT SYS_fsync 0x76 #define SYS_fsync 0x76 // CONSTANT SYS_getcwd 0xb7 #define SYS_getcwd 0xb7 // CONSTANT SYS_dup 0x29 #define SYS_dup 0x29 // CONSTANT SYS_dup2 0x3f #define SYS_dup2 0x3f // CONSTANT SYS_unlink 0x0a #define SYS_unlink 0x0a // CONSTANT SYS_gettimeofday 0x4e #define SYS_gettimeofday 0x4e // CONSTANT SYS_clock_gettime 0x109 #define SYS_clock_gettime 0x109 // CONSTANT SYS_time 0x0d #define SYS_time 0x0d /* libc+tcc */ #define SYS_close 0x06 #define SYS_lseek 0x13 #define SYS_rmdir 0x28 #define SYS_stat 0x6a /* libc+gnu */ #define SYS_chdir 0x0c #define SYS_link 0x09 #define SYS_getpid 0x14 #define SYS_getuid 0x18 #define SYS_kill 0x25 #define SYS_rename 0x26 #define SYS_mkdir 0x27 #define SYS_pipe 0x2a #define SYS_getgid 0x2f #define SYS_signal 0x30 #define SYS_sigaction 0x43 #define SYS_rt_sigaction 0xae #define SYS_signal 0x30 #define SYS_fcntl 0x37 #define SYS_getrusage 0x4d #define SYS_lstat 0x6b #define SYS_setitimer 0x68 #define SYS_fstat 0x6c #define SYS_nanosleep 0xa2 #define SYS_getdents 0x8d /* bash */ #define SYS_setuid 0x17 #define SYS_geteuid 0x31 #define SYS_getegid 0x32 #define SYS_setgid 0x3e #define SYS_getppid 0x40 /* make+POSIX */ #define SYS_sigprocmask 0x7e /* tar */ #define SYS_symlink 0x53 #define SYS_readlink 0x55 #define SYS_mknod 0x0e #if __SIZEOF_LONG_LONG__ == 8 #define SYS_stat64 0xc3 #define SYS_lstat64 0xc4 #define SYS_fstat64 0xc5 #define SYS_fcntl64 0xdd #define SYS_getdents64 0xdc #undef SYS_stat #define SYS_stat SYS_stat64 #undef SYS_lstat #define SYS_lstat SYS_lstat64 #undef SYS_fstat #define SYS_fstat SYS_fstat64 #endif // __SIZEOF_LONG_LONG__ == 8 #endif /* __MES_LINUX_X86_SYSCALL_H */
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_LINUX_X86_KERNEL_STAT_H #define __MES_LINUX_X86_KERNEL_STAT_H 1 // https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/stat.h #include <arch/syscall.h> #if __SIZEOF_LONG_LONG__ != 8 // *INDENT-OFF* struct stat { unsigned long st_dev; unsigned long st_ino; unsigned short st_mode; unsigned short st_nlink; unsigned short st_uid; unsigned short st_gid; unsigned long st_rdev; unsigned long st_size; unsigned long st_blksize; unsigned long st_blocks; unsigned long st_atime; unsigned long st_atime_usec; unsigned long st_mtime; unsigned long st_mtime_usec; unsigned long st_ctime; unsigned long st_ctime_usec; unsigned long __pad0; unsigned long __pad1; }; #else // __SIZEOF_LONG_LONG__ == 8 struct stat { unsigned long long st_dev; unsigned char __pad0[4]; unsigned long __st_ino; unsigned int st_mode; unsigned int st_nlink; unsigned long st_uid; unsigned long st_gid; unsigned long long st_rdev; unsigned char __pad3[4]; long long st_size; unsigned long st_blksize; unsigned long long st_blocks; unsigned long st_atime; unsigned long st_atime_nsec; unsigned long st_mtime; unsigned int st_mtime_nsec; unsigned long st_ctime; unsigned long st_ctime_nsec; unsigned long long st_ino; }; #endif // __SIZEOF_LONG_LONG__ == 8 #endif // __MES_LINUX_X86_KERNEL_STAT_H
;;; -*-scheme-*- ;;; GNU Mes --- Maxwell Equations of Software ;;; Copyright (C) 2016, 2017, 2018 Free Software Foundation, Inc. ;;; ;;; This file is part of GNU Mes. ;;; ;;; GNU Mes is free software; you can redistribute it and/or modify it ;;; under the terms of the GNU General Public License as published by ;;; the Free Software Foundation; either version 3 of the License, or (at ;;; your option) any later version. ;;; ;;; GNU Mes is distributed in the hope that it will be useful, but ;;; WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. ;;; Commentary: ;;; This file is generated from psyntax.ss. ;;; Code: (letrec ((syntmp-lambda-var-list-167 (lambda (syntmp-vars-552) (let syntmp-lvl-553 ((syntmp-vars-554 syntmp-vars-552) (syntmp-ls-555 (quote ())) (syntmp-w-556 (quote (())))) (cond ((pair? syntmp-vars-554) (syntmp-lvl-553 (cdr syntmp-vars-554) (cons (syntmp-wrap-146 (car syntmp-vars-554) syntmp-w-556) syntmp-ls-555) syntmp-w-556)) ((syntmp-id?-118 syntmp-vars-554) (cons (syntmp-wrap-146 syntmp-vars-554 syntmp-w-556) syntmp-ls-555)) ((null? syntmp-vars-554) syntmp-ls-555) ((syntmp-syntax-object?-104 syntmp-vars-554) (syntmp-lvl-553 (syntmp-syntax-object-expression-105 syntmp-vars-554) syntmp-ls-555 (syntmp-join-wraps-137 syntmp-w-556 (syntmp-syntax-object-wrap-106 syntmp-vars-554)))) ((syntmp-annotation?-92 syntmp-vars-554) (syntmp-lvl-553 (annotation-expression syntmp-vars-554) syntmp-ls-555 syntmp-w-556)) (else (cons syntmp-vars-554 syntmp-ls-555)))))) (syntmp-gen-var-166 (lambda (syntmp-id-557) (let ((syntmp-id-558 (if (syntmp-syntax-object?-104 syntmp-id-557) (syntmp-syntax-object-expression-105 syntmp-id-557) syntmp-id-557))) (if (syntmp-annotation?-92 syntmp-id-558) (gensym (symbol->string (annotation-expression syntmp-id-558))) (gensym (symbol->string syntmp-id-558)))))) (syntmp-strip-165 (lambda (syntmp-x-559 syntmp-w-560) (if (memq 'top (syntmp-wrap-marks-121 syntmp-w-560)) (if (or (syntmp-annotation?-92 syntmp-x-559) (and (pair? syntmp-x-559) (syntmp-annotation?-92 (car syntmp-x-559)))) (syntmp-strip-annotation-164 syntmp-x-559 #f) syntmp-x-559) (let syntmp-f-561 ((syntmp-x-562 syntmp-x-559)) (cond ((syntmp-syntax-object?-104 syntmp-x-562) (syntmp-strip-165 (syntmp-syntax-object-expression-105 syntmp-x-562) (syntmp-syntax-object-wrap-106 syntmp-x-562))) ((pair? syntmp-x-562) (let ((syntmp-a-563 (syntmp-f-561 (car syntmp-x-562))) (syntmp-d-564 (syntmp-f-561 (cdr syntmp-x-562)))) (if (and (eq? syntmp-a-563 (car syntmp-x-562)) (eq? syntmp-d-564 (cdr syntmp-x-562))) syntmp-x-562 (cons syntmp-a-563 syntmp-d-564)))) ((vector? syntmp-x-562) (let ((syntmp-old-565 (vector->list syntmp-x-562))) (let ((syntmp-new-566 (map syntmp-f-561 syntmp-old-565))) (if (andmap eq? syntmp-old-565 syntmp-new-566) syntmp-x-562 (list->vector syntmp-new-566))))) (else syntmp-x-562)))))) (syntmp-strip-annotation-164 (lambda (syntmp-x-567 syntmp-parent-568) (cond ((pair? syntmp-x-567) (let ((syntmp-new-569 (cons #f #f))) (begin (when syntmp-parent-568 (set-annotation-stripped! syntmp-parent-568 syntmp-new-569)) (set-car! syntmp-new-569 (syntmp-strip-annotation-164 (car syntmp-x-567) #f)) (set-cdr! syntmp-new-569 (syntmp-strip-annotation-164 (cdr syntmp-x-567) #f)) syntmp-new-569))) ((syntmp-annotation?-92 syntmp-x-567) (or (annotation-stripped syntmp-x-567) (syntmp-strip-annotation-164 (annotation-expression syntmp-x-567) syntmp-x-567))) ((vector? syntmp-x-567) (let ((syntmp-new-570 (make-vector (vector-length syntmp-x-567)))) (begin (when syntmp-parent-568 (set-annotation-stripped! syntmp-parent-568 syntmp-new-570)) (let syntmp-loop-571 ((syntmp-i-572 (- (vector-length syntmp-x-567) 1))) (unless (syntmp-fx<-91 syntmp-i-572 0) (vector-set! syntmp-new-570 syntmp-i-572 (syntmp-strip-annotation-164 (vector-ref syntmp-x-567 syntmp-i-572) #f)) (syntmp-loop-571 (syntmp-fx--89 syntmp-i-572 1)))) syntmp-new-570))) (else syntmp-x-567)))) (syntmp-ellipsis?-163 (lambda (syntmp-e-573 syntmp-r-574) (and (syntmp-nonsymbol-id?-117 syntmp-e-573) (let ((syntmp-id-575 (syntmp-make-syntax-object-103 '$sc-ellipsis (syntmp-syntax-object-wrap-106 syntmp-e-573)))) (let ((syntmp-n-576 (syntmp-id-var-name-140 syntmp-id-575 '(())))) (let ((syntmp-b-577 (syntmp-lookup-115 syntmp-n-576 syntmp-r-574))) (if (eq? (syntmp-binding-type-110 syntmp-b-577) 'ellipsis) (syntmp-bound-id=?-142 syntmp-e-573 (syntmp-binding-value-111 syntmp-b-577)) (syntmp-free-id=?-141 syntmp-e-573 '#(syntax-object ... ((top) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(b) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(n) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(id) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(e r) #((top) (top)) #("i" "i")) #(ribcage (lambda-var-list gen-var strip strip-annotation ellipsis? chi-void eval-local-transformer chi-local-syntax chi-lambda-clause chi-body chi-macro chi-application chi-expr chi chi-top syntax-type chi-when-list chi-install-global chi-top-sequence chi-sequence source-wrap wrap bound-id-member? distinct-bound-ids? valid-bound-ids? bound-id=? free-id=? id-var-name same-marks? join-marks join-wraps smart-append make-binding-wrap extend-ribcage! make-empty-ribcage new-mark anti-mark the-anti-mark top-marked? top-wrap empty-wrap set-ribcage-labels! set-ribcage-marks! set-ribcage-symnames! ribcage-labels ribcage-marks ribcage-symnames ribcage? make-ribcage gen-labels gen-label make-rename rename-marks rename-new rename-old subst-rename? wrap-subst wrap-marks make-wrap id-sym-name&marks id-sym-name id? nonsymbol-id? global-extend lookup macros-only-env extend-var-env extend-env null-env binding-value binding-type make-binding arg-check source-annotation no-source unannotate set-syntax-object-wrap! set-syntax-object-expression! syntax-object-wrap syntax-object-expression syntax-object? make-syntax-object build-lexical-var build-letrec build-named-let build-let build-sequence build-data build-primref build-lambda build-global-definition build-global-assignment build-global-reference build-lexical-assignment build-lexical-reference build-conditional build-application get-global-definition-hook put-global-definition-hook gensym-hook error-hook local-eval-hook top-level-eval-hook annotation? fx< fx= fx- fx+ noexpand) ((top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top)) ("i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i")) #(ribcage (define-structure) ((top)) ("i")))))))))))) (syntmp-chi-void-162 (lambda () (list (quote void)))) (syntmp-eval-local-transformer-161 (lambda (syntmp-expanded-578) (let ((syntmp-p-579 (syntmp-local-eval-hook-94 syntmp-expanded-578))) (if (procedure? syntmp-p-579) syntmp-p-579 (syntax-error syntmp-p-579 "nonprocedure transformer"))))) (syntmp-chi-local-syntax-160 (lambda (syntmp-rec?-580 syntmp-e-581 syntmp-r-582 syntmp-w-583 syntmp-s-584 syntmp-k-585) ((lambda (syntmp-tmp-586) ((lambda (syntmp-tmp-587) (if syntmp-tmp-587 (apply (lambda (syntmp-_-588 syntmp-id-589 syntmp-val-590 syntmp-e1-591 syntmp-e2-592) (let ((syntmp-ids-593 syntmp-id-589)) (if (not (syntmp-valid-bound-ids?-143 syntmp-ids-593)) (syntax-error syntmp-e-581 "duplicate bound keyword in") (let ((syntmp-labels-595 (syntmp-gen-labels-124 syntmp-ids-593))) (let ((syntmp-new-w-596 (syntmp-make-binding-wrap-135 syntmp-ids-593 syntmp-labels-595 syntmp-w-583))) (syntmp-k-585 (cons syntmp-e1-591 syntmp-e2-592) (syntmp-extend-env-112 syntmp-labels-595 (let ((syntmp-w-598 (if syntmp-rec?-580 syntmp-new-w-596 syntmp-w-583)) (syntmp-trans-r-599 (syntmp-macros-only-env-114 syntmp-r-582))) (map (lambda (syntmp-x-600) (cons 'macro (syntmp-eval-local-transformer-161 (syntmp-chi-154 syntmp-x-600 syntmp-trans-r-599 syntmp-w-598)))) syntmp-val-590)) syntmp-r-582) syntmp-new-w-596 syntmp-s-584)))))) syntmp-tmp-587) ((lambda (syntmp-_-602) (syntax-error (syntmp-source-wrap-147 syntmp-e-581 syntmp-w-583 syntmp-s-584))) syntmp-tmp-586))) (syntax-dispatch syntmp-tmp-586 '(any #(each (any any)) any . each-any)))) syntmp-e-581))) (syntmp-chi-lambda-clause-159 (lambda (syntmp-e-603 syntmp-c-604 syntmp-r-605 syntmp-w-606 syntmp-k-607) ((lambda (syntmp-tmp-608) ((lambda (syntmp-tmp-609) (if syntmp-tmp-609 (apply (lambda (syntmp-id-610 syntmp-e1-611 syntmp-e2-612) (let ((syntmp-ids-613 syntmp-id-610)) (if (not (syntmp-valid-bound-ids?-143 syntmp-ids-613)) (syntax-error syntmp-e-603 "invalid parameter list in") (let ((syntmp-labels-615 (syntmp-gen-labels-124 syntmp-ids-613)) (syntmp-new-vars-616 (map syntmp-gen-var-166 syntmp-ids-613))) (syntmp-k-607 syntmp-new-vars-616 (syntmp-chi-body-158 (cons syntmp-e1-611 syntmp-e2-612) syntmp-e-603 (syntmp-extend-var-env-113 syntmp-labels-615 syntmp-new-vars-616 syntmp-r-605) (syntmp-make-binding-wrap-135 syntmp-ids-613 syntmp-labels-615 syntmp-w-606))))))) syntmp-tmp-609) ((lambda (syntmp-tmp-618) (if syntmp-tmp-618 (apply (lambda (syntmp-ids-619 syntmp-e1-620 syntmp-e2-621) (let ((syntmp-old-ids-622 (syntmp-lambda-var-list-167 syntmp-ids-619))) (if (not (syntmp-valid-bound-ids?-143 syntmp-old-ids-622)) (syntax-error syntmp-e-603 "invalid parameter list in") (let ((syntmp-labels-623 (syntmp-gen-labels-124 syntmp-old-ids-622)) (syntmp-new-vars-624 (map syntmp-gen-var-166 syntmp-old-ids-622))) (syntmp-k-607 (let syntmp-f-625 ((syntmp-ls1-626 (cdr syntmp-new-vars-624)) (syntmp-ls2-627 (car syntmp-new-vars-624))) (if (null? syntmp-ls1-626) syntmp-ls2-627 (syntmp-f-625 (cdr syntmp-ls1-626) (cons (car syntmp-ls1-626) syntmp-ls2-627)))) (syntmp-chi-body-158 (cons syntmp-e1-620 syntmp-e2-621) syntmp-e-603 (syntmp-extend-var-env-113 syntmp-labels-623 syntmp-new-vars-624 syntmp-r-605) (syntmp-make-binding-wrap-135 syntmp-old-ids-622 syntmp-labels-623 syntmp-w-606))))))) syntmp-tmp-618) ((lambda (syntmp-_-629) (syntax-error syntmp-e-603)) syntmp-tmp-608))) (syntax-dispatch syntmp-tmp-608 '(any any . each-any))))) (syntax-dispatch syntmp-tmp-608 '(each-any any . each-any)))) syntmp-c-604))) (syntmp-chi-body-158 (lambda (syntmp-body-630 syntmp-outer-form-631 syntmp-r-632 syntmp-w-633) (let ((syntmp-r-634 (cons '("placeholder" placeholder) syntmp-r-632))) (let ((syntmp-ribcage-635 (syntmp-make-ribcage-125 '() '() '()))) (let ((syntmp-w-636 (syntmp-make-wrap-120 (syntmp-wrap-marks-121 syntmp-w-633) (cons syntmp-ribcage-635 (syntmp-wrap-subst-122 syntmp-w-633))))) (let syntmp-parse-637 ((syntmp-body-638 (map (lambda (syntmp-x-644) (cons syntmp-r-634 (syntmp-wrap-146 syntmp-x-644 syntmp-w-636))) syntmp-body-630)) (syntmp-ids-639 (quote ())) (syntmp-labels-640 (quote ())) (syntmp-vars-641 (quote ())) (syntmp-vals-642 (quote ())) (syntmp-bindings-643 (quote ()))) (if (null? syntmp-body-638) (syntax-error syntmp-outer-form-631 "no expressions in body") (let ((syntmp-e-645 (cdar syntmp-body-638)) (syntmp-er-646 (caar syntmp-body-638))) (call-with-values (lambda () (syntmp-syntax-type-152 syntmp-e-645 syntmp-er-646 '(()) #f syntmp-ribcage-635)) (lambda (syntmp-type-647 syntmp-value-648 syntmp-e-649 syntmp-w-650 syntmp-s-651) (let ((syntmp-t-652 syntmp-type-647)) (if (memv syntmp-t-652 (quote (define-form))) (let ((syntmp-id-653 (syntmp-wrap-146 syntmp-value-648 syntmp-w-650)) (syntmp-label-654 (syntmp-gen-label-123))) (let ((syntmp-var-655 (syntmp-gen-var-166 syntmp-id-653))) (begin (syntmp-extend-ribcage!-134 syntmp-ribcage-635 syntmp-id-653 syntmp-label-654) (syntmp-parse-637 (cdr syntmp-body-638) (cons syntmp-id-653 syntmp-ids-639) (cons syntmp-label-654 syntmp-labels-640) (cons syntmp-var-655 syntmp-vars-641) (cons (cons syntmp-er-646 (syntmp-wrap-146 syntmp-e-649 syntmp-w-650)) syntmp-vals-642) (cons (cons 'lexical syntmp-var-655) syntmp-bindings-643))))) (if (memv syntmp-t-652 '(define-syntax-form)) (let ((syntmp-id-656 (syntmp-wrap-146 syntmp-value-648 syntmp-w-650)) (syntmp-label-657 (syntmp-gen-label-123))) (begin (syntmp-extend-ribcage!-134 syntmp-ribcage-635 syntmp-id-656 syntmp-label-657) (syntmp-parse-637 (cdr syntmp-body-638) (cons syntmp-id-656 syntmp-ids-639) (cons syntmp-label-657 syntmp-labels-640) syntmp-vars-641 syntmp-vals-642 (cons (cons 'macro (cons syntmp-er-646 (syntmp-wrap-146 syntmp-e-649 syntmp-w-650))) syntmp-bindings-643)))) (if (memv syntmp-t-652 (quote (begin-form))) ((lambda (syntmp-tmp-658) ((lambda (syntmp-tmp-659) (if syntmp-tmp-659 (apply (lambda (syntmp-_-660 syntmp-e1-661) (syntmp-parse-637 (let syntmp-f-662 ((syntmp-forms-663 syntmp-e1-661)) (if (null? syntmp-forms-663) (cdr syntmp-body-638) (cons (cons syntmp-er-646 (syntmp-wrap-146 (car syntmp-forms-663) syntmp-w-650)) (syntmp-f-662 (cdr syntmp-forms-663))))) syntmp-ids-639 syntmp-labels-640 syntmp-vars-641 syntmp-vals-642 syntmp-bindings-643)) syntmp-tmp-659) (syntax-error syntmp-tmp-658))) (syntax-dispatch syntmp-tmp-658 '(any . each-any)))) syntmp-e-649) (if (memv syntmp-t-652 '(local-syntax-form)) (syntmp-chi-local-syntax-160 syntmp-value-648 syntmp-e-649 syntmp-er-646 syntmp-w-650 syntmp-s-651 (lambda (syntmp-forms-665 syntmp-er-666 syntmp-w-667 syntmp-s-668) (syntmp-parse-637 (let syntmp-f-669 ((syntmp-forms-670 syntmp-forms-665)) (if (null? syntmp-forms-670) (cdr syntmp-body-638) (cons (cons syntmp-er-666 (syntmp-wrap-146 (car syntmp-forms-670) syntmp-w-667)) (syntmp-f-669 (cdr syntmp-forms-670))))) syntmp-ids-639 syntmp-labels-640 syntmp-vars-641 syntmp-vals-642 syntmp-bindings-643))) (if (null? syntmp-ids-639) (syntmp-build-sequence-99 #f (map (lambda (syntmp-x-671) (syntmp-chi-154 (cdr syntmp-x-671) (car syntmp-x-671) '(()))) (cons (cons syntmp-er-646 (syntmp-source-wrap-147 syntmp-e-649 syntmp-w-650 syntmp-s-651)) (cdr syntmp-body-638)))) (begin (if (not (syntmp-valid-bound-ids?-143 syntmp-ids-639)) (syntax-error syntmp-outer-form-631 "invalid or duplicate identifier in definition")) (let syntmp-loop-672 ((syntmp-bs-673 syntmp-bindings-643) (syntmp-er-cache-674 #f) (syntmp-r-cache-675 #f)) (if (not (null? syntmp-bs-673)) (let ((syntmp-b-676 (car syntmp-bs-673))) (if (eq? (car syntmp-b-676) 'macro) (let ((syntmp-er-677 (cadr syntmp-b-676))) (let ((syntmp-r-cache-678 (if (eq? syntmp-er-677 syntmp-er-cache-674) syntmp-r-cache-675 (syntmp-macros-only-env-114 syntmp-er-677)))) (begin (set-cdr! syntmp-b-676 (syntmp-eval-local-transformer-161 (syntmp-chi-154 (cddr syntmp-b-676) syntmp-r-cache-678 '(())))) (syntmp-loop-672 (cdr syntmp-bs-673) syntmp-er-677 syntmp-r-cache-678)))) (syntmp-loop-672 (cdr syntmp-bs-673) syntmp-er-cache-674 syntmp-r-cache-675))))) (set-cdr! syntmp-r-634 (syntmp-extend-env-112 syntmp-labels-640 syntmp-bindings-643 (cdr syntmp-r-634))) (syntmp-build-letrec-102 #f syntmp-vars-641 (map (lambda (syntmp-x-679) (syntmp-chi-154 (cdr syntmp-x-679) (car syntmp-x-679) '(()))) syntmp-vals-642) (syntmp-build-sequence-99 #f (map (lambda (syntmp-x-680) (syntmp-chi-154 (cdr syntmp-x-680) (car syntmp-x-680) '(()))) (cons (cons syntmp-er-646 (syntmp-source-wrap-147 syntmp-e-649 syntmp-w-650 syntmp-s-651)) (cdr syntmp-body-638)))))))))))))))))))))) (syntmp-chi-macro-157 (lambda (syntmp-p-681 syntmp-e-682 syntmp-r-683 syntmp-w-684 syntmp-rib-685) (letrec ((syntmp-rebuild-macro-output-686 (lambda (syntmp-x-687 syntmp-m-688) (cond ((pair? syntmp-x-687) (cons (syntmp-rebuild-macro-output-686 (car syntmp-x-687) syntmp-m-688) (syntmp-rebuild-macro-output-686 (cdr syntmp-x-687) syntmp-m-688))) ((syntmp-syntax-object?-104 syntmp-x-687) (let ((syntmp-w-689 (syntmp-syntax-object-wrap-106 syntmp-x-687))) (let ((syntmp-ms-690 (syntmp-wrap-marks-121 syntmp-w-689)) (syntmp-s-691 (syntmp-wrap-subst-122 syntmp-w-689))) (syntmp-make-syntax-object-103 (syntmp-syntax-object-expression-105 syntmp-x-687) (if (and (pair? syntmp-ms-690) (eq? (car syntmp-ms-690) #f)) (syntmp-make-wrap-120 (cdr syntmp-ms-690) (if syntmp-rib-685 (cons syntmp-rib-685 (cdr syntmp-s-691)) (cdr syntmp-s-691))) (syntmp-make-wrap-120 (cons syntmp-m-688 syntmp-ms-690) (if syntmp-rib-685 (cons syntmp-rib-685 (cons 'shift syntmp-s-691)) (cons 'shift syntmp-s-691)))))))) ((vector? syntmp-x-687) (let ((syntmp-n-692 (vector-length syntmp-x-687))) (let ((syntmp-v-693 (make-vector syntmp-n-692))) (let syntmp-doloop-694 ((syntmp-i-695 0)) (if (syntmp-fx=-90 syntmp-i-695 syntmp-n-692) syntmp-v-693 (begin (vector-set! syntmp-v-693 syntmp-i-695 (syntmp-rebuild-macro-output-686 (vector-ref syntmp-x-687 syntmp-i-695) syntmp-m-688)) (syntmp-doloop-694 (syntmp-fx+-88 syntmp-i-695 1)))))))) ((symbol? syntmp-x-687) (syntax-error syntmp-x-687 "encountered raw symbol in macro output")) (else syntmp-x-687))))) (syntmp-rebuild-macro-output-686 (syntmp-p-681 (syntmp-wrap-146 syntmp-e-682 (syntmp-anti-mark-133 syntmp-w-684))) (string #\m))))) (syntmp-chi-application-156 (lambda (syntmp-x-696 syntmp-e-697 syntmp-r-698 syntmp-w-699 syntmp-s-700) ((lambda (syntmp-tmp-701) ((lambda (syntmp-tmp-702) (if syntmp-tmp-702 (apply (lambda (syntmp-e0-703 syntmp-e1-704) (cons syntmp-x-696 (map (lambda (syntmp-e-705) (syntmp-chi-154 syntmp-e-705 syntmp-r-698 syntmp-w-699)) syntmp-e1-704))) syntmp-tmp-702) (syntax-error syntmp-tmp-701))) (syntax-dispatch syntmp-tmp-701 '(any . each-any)))) syntmp-e-697))) (syntmp-chi-expr-155 (lambda (syntmp-type-707 syntmp-value-708 syntmp-e-709 syntmp-r-710 syntmp-w-711 syntmp-s-712) (let ((syntmp-t-713 syntmp-type-707)) (if (memv syntmp-t-713 (quote (lexical))) syntmp-value-708 (if (memv syntmp-t-713 (quote (core external-macro))) (syntmp-value-708 syntmp-e-709 syntmp-r-710 syntmp-w-711 syntmp-s-712) (if (memv syntmp-t-713 (quote (lexical-call))) (syntmp-chi-application-156 syntmp-value-708 syntmp-e-709 syntmp-r-710 syntmp-w-711 syntmp-s-712) (if (memv syntmp-t-713 (quote (global-call))) (syntmp-chi-application-156 syntmp-value-708 syntmp-e-709 syntmp-r-710 syntmp-w-711 syntmp-s-712) (if (memv syntmp-t-713 (quote (constant))) (syntmp-build-data-98 syntmp-s-712 (syntmp-strip-165 (syntmp-source-wrap-147 syntmp-e-709 syntmp-w-711 syntmp-s-712) '(()))) (if (memv syntmp-t-713 (quote (global))) syntmp-value-708 (if (memv syntmp-t-713 (quote (call))) (syntmp-chi-application-156 (syntmp-chi-154 (car syntmp-e-709) syntmp-r-710 syntmp-w-711) syntmp-e-709 syntmp-r-710 syntmp-w-711 syntmp-s-712) (if (memv syntmp-t-713 (quote (begin-form))) ((lambda (syntmp-tmp-714) ((lambda (syntmp-tmp-715) (if syntmp-tmp-715 (apply (lambda (syntmp-_-716 syntmp-e1-717 syntmp-e2-718) (syntmp-chi-sequence-148 (cons syntmp-e1-717 syntmp-e2-718) syntmp-r-710 syntmp-w-711 syntmp-s-712)) syntmp-tmp-715) (syntax-error syntmp-tmp-714))) (syntax-dispatch syntmp-tmp-714 '(any any . each-any)))) syntmp-e-709) (if (memv syntmp-t-713 '(local-syntax-form)) (syntmp-chi-local-syntax-160 syntmp-value-708 syntmp-e-709 syntmp-r-710 syntmp-w-711 syntmp-s-712 syntmp-chi-sequence-148) (if (memv syntmp-t-713 '(eval-when-form)) ((lambda (syntmp-tmp-720) ((lambda (syntmp-tmp-721) (if syntmp-tmp-721 (apply (lambda (syntmp-_-722 syntmp-x-723 syntmp-e1-724 syntmp-e2-725) (let ((syntmp-when-list-726 (syntmp-chi-when-list-151 syntmp-e-709 syntmp-x-723 syntmp-w-711))) (if (memq 'eval syntmp-when-list-726) (syntmp-chi-sequence-148 (cons syntmp-e1-724 syntmp-e2-725) syntmp-r-710 syntmp-w-711 syntmp-s-712) (syntmp-chi-void-162)))) syntmp-tmp-721) (syntax-error syntmp-tmp-720))) (syntax-dispatch syntmp-tmp-720 '(any each-any any . each-any)))) syntmp-e-709) (if (memv syntmp-t-713 '(define-form define-syntax-form)) (syntax-error (syntmp-wrap-146 syntmp-value-708 syntmp-w-711) "invalid context for definition of") (if (memv syntmp-t-713 (quote (syntax))) (syntax-error (syntmp-source-wrap-147 syntmp-e-709 syntmp-w-711 syntmp-s-712) "reference to pattern variable outside syntax form") (if (memv syntmp-t-713 '(displaced-lexical)) (syntax-error (syntmp-source-wrap-147 syntmp-e-709 syntmp-w-711 syntmp-s-712) "reference to identifier outside its scope") (syntax-error (syntmp-source-wrap-147 syntmp-e-709 syntmp-w-711 syntmp-s-712)))))))))))))))))) (syntmp-chi-154 (lambda (syntmp-e-729 syntmp-r-730 syntmp-w-731) (call-with-values (lambda () (syntmp-syntax-type-152 syntmp-e-729 syntmp-r-730 syntmp-w-731 #f #f)) (lambda (syntmp-type-732 syntmp-value-733 syntmp-e-734 syntmp-w-735 syntmp-s-736) (syntmp-chi-expr-155 syntmp-type-732 syntmp-value-733 syntmp-e-734 syntmp-r-730 syntmp-w-735 syntmp-s-736))))) (syntmp-chi-top-153 (lambda (syntmp-e-737 syntmp-r-738 syntmp-w-739 syntmp-m-740 syntmp-esew-741) (call-with-values (lambda () (syntmp-syntax-type-152 syntmp-e-737 syntmp-r-738 syntmp-w-739 #f #f)) (lambda (syntmp-type-754 syntmp-value-755 syntmp-e-756 syntmp-w-757 syntmp-s-758) (let ((syntmp-t-759 syntmp-type-754)) (if (memv syntmp-t-759 (quote (begin-form))) ((lambda (syntmp-tmp-760) ((lambda (syntmp-tmp-761) (if syntmp-tmp-761 (apply (lambda (syntmp-_-762) (syntmp-chi-void-162)) syntmp-tmp-761) ((lambda (syntmp-tmp-763) (if syntmp-tmp-763 (apply (lambda (syntmp-_-764 syntmp-e1-765 syntmp-e2-766) (syntmp-chi-top-sequence-149 (cons syntmp-e1-765 syntmp-e2-766) syntmp-r-738 syntmp-w-757 syntmp-s-758 syntmp-m-740 syntmp-esew-741)) syntmp-tmp-763) (syntax-error syntmp-tmp-760))) (syntax-dispatch syntmp-tmp-760 '(any any . each-any))))) (syntax-dispatch syntmp-tmp-760 (quote (any))))) syntmp-e-756) (if (memv syntmp-t-759 (quote (local-syntax-form))) (syntmp-chi-local-syntax-160 syntmp-value-755 syntmp-e-756 syntmp-r-738 syntmp-w-757 syntmp-s-758 (lambda (syntmp-body-768 syntmp-r-769 syntmp-w-770 syntmp-s-771) (syntmp-chi-top-sequence-149 syntmp-body-768 syntmp-r-769 syntmp-w-770 syntmp-s-771 syntmp-m-740 syntmp-esew-741))) (if (memv syntmp-t-759 (quote (eval-when-form))) ((lambda (syntmp-tmp-772) ((lambda (syntmp-tmp-773) (if syntmp-tmp-773 (apply (lambda (syntmp-_-774 syntmp-x-775 syntmp-e1-776 syntmp-e2-777) (let ((syntmp-when-list-778 (syntmp-chi-when-list-151 syntmp-e-756 syntmp-x-775 syntmp-w-757)) (syntmp-body-779 (cons syntmp-e1-776 syntmp-e2-777))) (cond ((eq? syntmp-m-740 (quote e)) (if (memq 'eval syntmp-when-list-778) (syntmp-chi-top-sequence-149 syntmp-body-779 syntmp-r-738 syntmp-w-757 syntmp-s-758 'e '(eval)) (syntmp-chi-void-162))) ((memq 'load syntmp-when-list-778) (if (or (memq 'compile syntmp-when-list-778) (and (eq? syntmp-m-740 'c&e) (memq 'eval syntmp-when-list-778))) (syntmp-chi-top-sequence-149 syntmp-body-779 syntmp-r-738 syntmp-w-757 syntmp-s-758 'c&e '(compile load)) (if (memq syntmp-m-740 '(c c&e)) (syntmp-chi-top-sequence-149 syntmp-body-779 syntmp-r-738 syntmp-w-757 syntmp-s-758 'c '(load)) (syntmp-chi-void-162)))) ((or (memq 'compile syntmp-when-list-778) (and (eq? syntmp-m-740 'c&e) (memq 'eval syntmp-when-list-778))) (syntmp-top-level-eval-hook-93 (syntmp-chi-top-sequence-149 syntmp-body-779 syntmp-r-738 syntmp-w-757 syntmp-s-758 'e '(eval))) (syntmp-chi-void-162)) (else (syntmp-chi-void-162))))) syntmp-tmp-773) (syntax-error syntmp-tmp-772))) (syntax-dispatch syntmp-tmp-772 '(any each-any any . each-any)))) syntmp-e-756) (if (memv syntmp-t-759 (quote (define-syntax-form))) (let ((syntmp-n-782 (syntmp-id-var-name-140 syntmp-value-755 syntmp-w-757)) (syntmp-r-783 (syntmp-macros-only-env-114 syntmp-r-738))) (let ((syntmp-t-784 syntmp-m-740)) (if (memv syntmp-t-784 (quote (c))) (if (memq (quote compile) syntmp-esew-741) (let ((syntmp-e-785 (syntmp-chi-install-global-150 syntmp-n-782 (syntmp-chi-154 syntmp-e-756 syntmp-r-783 syntmp-w-757)))) (begin (syntmp-top-level-eval-hook-93 syntmp-e-785) (if (memq (quote load) syntmp-esew-741) syntmp-e-785 (syntmp-chi-void-162)))) (if (memq (quote load) syntmp-esew-741) (syntmp-chi-install-global-150 syntmp-n-782 (syntmp-chi-154 syntmp-e-756 syntmp-r-783 syntmp-w-757)) (syntmp-chi-void-162))) (if (memv syntmp-t-784 (quote (c&e))) (let ((syntmp-e-786 (syntmp-chi-install-global-150 syntmp-n-782 (syntmp-chi-154 syntmp-e-756 syntmp-r-783 syntmp-w-757)))) (begin (syntmp-top-level-eval-hook-93 syntmp-e-786) syntmp-e-786)) (begin (if (memq (quote eval) syntmp-esew-741) (syntmp-top-level-eval-hook-93 (syntmp-chi-install-global-150 syntmp-n-782 (syntmp-chi-154 syntmp-e-756 syntmp-r-783 syntmp-w-757)))) (syntmp-chi-void-162)))))) (if (memv syntmp-t-759 (quote (define-form))) (let ((syntmp-n-787 (syntmp-id-var-name-140 syntmp-value-755 syntmp-w-757))) (let ((syntmp-type-788 (syntmp-binding-type-110 (syntmp-lookup-115 syntmp-n-787 syntmp-r-738)))) (let ((syntmp-t-789 syntmp-type-788)) (if (memv syntmp-t-789 (quote (global))) (let ((syntmp-x-790 (list 'define syntmp-n-787 (syntmp-chi-154 syntmp-e-756 syntmp-r-738 syntmp-w-757)))) (begin (if (eq? syntmp-m-740 (quote c&e)) (syntmp-top-level-eval-hook-93 syntmp-x-790)) syntmp-x-790)) (if (memv syntmp-t-789 '(displaced-lexical)) (syntax-error (syntmp-wrap-146 syntmp-value-755 syntmp-w-757) "identifier out of context") (if (eq? syntmp-type-788 'external-macro) (let ((syntmp-x-791 (list 'define syntmp-n-787 (syntmp-chi-154 syntmp-e-756 syntmp-r-738 syntmp-w-757)))) (begin (if (eq? syntmp-m-740 (quote c&e)) (syntmp-top-level-eval-hook-93 syntmp-x-791)) syntmp-x-791)) (syntax-error (syntmp-wrap-146 syntmp-value-755 syntmp-w-757) "cannot define keyword at top level"))))))) (let ((syntmp-x-792 (syntmp-chi-expr-155 syntmp-type-754 syntmp-value-755 syntmp-e-756 syntmp-r-738 syntmp-w-757 syntmp-s-758))) (begin (if (eq? syntmp-m-740 (quote c&e)) (syntmp-top-level-eval-hook-93 syntmp-x-792)) syntmp-x-792)))))))))))) (syntmp-syntax-type-152 (lambda (syntmp-e-793 syntmp-r-794 syntmp-w-795 syntmp-s-796 syntmp-rib-797) (cond ((symbol? syntmp-e-793) (let ((syntmp-n-798 (syntmp-id-var-name-140 syntmp-e-793 syntmp-w-795))) (let ((syntmp-b-799 (syntmp-lookup-115 syntmp-n-798 syntmp-r-794))) (let ((syntmp-type-800 (syntmp-binding-type-110 syntmp-b-799))) (let ((syntmp-t-801 syntmp-type-800)) (if (memv syntmp-t-801 (quote (lexical))) (values syntmp-type-800 (syntmp-binding-value-111 syntmp-b-799) syntmp-e-793 syntmp-w-795 syntmp-s-796) (if (memv syntmp-t-801 (quote (global))) (values syntmp-type-800 syntmp-n-798 syntmp-e-793 syntmp-w-795 syntmp-s-796) (if (memv syntmp-t-801 (quote (macro))) (syntmp-syntax-type-152 (syntmp-chi-macro-157 (syntmp-binding-value-111 syntmp-b-799) syntmp-e-793 syntmp-r-794 syntmp-w-795 syntmp-rib-797) syntmp-r-794 '(()) syntmp-s-796 syntmp-rib-797) (values syntmp-type-800 (syntmp-binding-value-111 syntmp-b-799) syntmp-e-793 syntmp-w-795 syntmp-s-796))))))))) ((pair? syntmp-e-793) (let ((syntmp-first-802 (car syntmp-e-793))) (if (syntmp-id?-118 syntmp-first-802) (let ((syntmp-n-803 (syntmp-id-var-name-140 syntmp-first-802 syntmp-w-795))) (let ((syntmp-b-804 (syntmp-lookup-115 syntmp-n-803 syntmp-r-794))) (let ((syntmp-type-805 (syntmp-binding-type-110 syntmp-b-804))) (let ((syntmp-t-806 syntmp-type-805)) (if (memv syntmp-t-806 (quote (lexical))) (values 'lexical-call (syntmp-binding-value-111 syntmp-b-804) syntmp-e-793 syntmp-w-795 syntmp-s-796) (if (memv syntmp-t-806 (quote (global))) (values 'global-call syntmp-n-803 syntmp-e-793 syntmp-w-795 syntmp-s-796) (if (memv syntmp-t-806 (quote (macro))) (syntmp-syntax-type-152 (syntmp-chi-macro-157 (syntmp-binding-value-111 syntmp-b-804) syntmp-e-793 syntmp-r-794 syntmp-w-795 syntmp-rib-797) syntmp-r-794 '(()) syntmp-s-796 syntmp-rib-797) (if (memv syntmp-t-806 '(core external-macro)) (values syntmp-type-805 (syntmp-binding-value-111 syntmp-b-804) syntmp-e-793 syntmp-w-795 syntmp-s-796) (if (memv syntmp-t-806 '(local-syntax)) (values 'local-syntax-form (syntmp-binding-value-111 syntmp-b-804) syntmp-e-793 syntmp-w-795 syntmp-s-796) (if (memv syntmp-t-806 '(begin)) (values 'begin-form #f syntmp-e-793 syntmp-w-795 syntmp-s-796) (if (memv syntmp-t-806 '(eval-when)) (values 'eval-when-form #f syntmp-e-793 syntmp-w-795 syntmp-s-796) (if (memv syntmp-t-806 '(define)) ((lambda (syntmp-tmp-807) ((lambda (syntmp-tmp-808) (if (if syntmp-tmp-808 (apply (lambda (syntmp-_-809 syntmp-name-810 syntmp-val-811) (syntmp-id?-118 syntmp-name-810)) syntmp-tmp-808) #f) (apply (lambda (syntmp-_-812 syntmp-name-813 syntmp-val-814) (values 'define-form syntmp-name-813 syntmp-val-814 syntmp-w-795 syntmp-s-796)) syntmp-tmp-808) ((lambda (syntmp-tmp-815) (if (if syntmp-tmp-815 (apply (lambda (syntmp-_-816 syntmp-name-817 syntmp-args-818 syntmp-e1-819 syntmp-e2-820) (and (syntmp-id?-118 syntmp-name-817) (syntmp-valid-bound-ids?-143 (syntmp-lambda-var-list-167 syntmp-args-818)))) syntmp-tmp-815) #f) (apply (lambda (syntmp-_-821 syntmp-name-822 syntmp-args-823 syntmp-e1-824 syntmp-e2-825) (values 'define-form (syntmp-wrap-146 syntmp-name-822 syntmp-w-795) (cons '#(syntax-object lambda ((top) #(ribcage #(_ name args e1 e2) #((top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(t) #(("m" top)) #("i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(type) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(b) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(n) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(first) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(e r w s rib) #((top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i")) #(ribcage (lambda-var-list gen-var strip strip-annotation ellipsis? chi-void eval-local-transformer chi-local-syntax chi-lambda-clause chi-body chi-macro chi-application chi-expr chi chi-top syntax-type chi-when-list chi-install-global chi-top-sequence chi-sequence source-wrap wrap bound-id-member? distinct-bound-ids? valid-bound-ids? bound-id=? free-id=? id-var-name same-marks? join-marks join-wraps smart-append make-binding-wrap extend-ribcage! make-empty-ribcage new-mark anti-mark the-anti-mark top-marked? top-wrap empty-wrap set-ribcage-labels! set-ribcage-marks! set-ribcage-symnames! ribcage-labels ribcage-marks ribcage-symnames ribcage? make-ribcage gen-labels gen-label make-rename rename-marks rename-new rename-old subst-rename? wrap-subst wrap-marks make-wrap id-sym-name&marks id-sym-name id? nonsymbol-id? global-extend lookup macros-only-env extend-var-env extend-env null-env binding-value binding-type make-binding arg-check source-annotation no-source unannotate set-syntax-object-wrap! set-syntax-object-expression! syntax-object-wrap syntax-object-expression syntax-object? make-syntax-object build-lexical-var build-letrec build-named-let build-let build-sequence build-data build-primref build-lambda build-global-definition build-global-assignment build-global-reference build-lexical-assignment build-lexical-reference build-conditional build-application get-global-definition-hook put-global-definition-hook gensym-hook error-hook local-eval-hook top-level-eval-hook annotation? fx< fx= fx- fx+ noexpand) ((top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top)) ("i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i")) #(ribcage (define-structure) ((top)) ("i")))) (syntmp-wrap-146 (cons syntmp-args-823 (cons syntmp-e1-824 syntmp-e2-825)) syntmp-w-795)) '(()) syntmp-s-796)) syntmp-tmp-815) ((lambda (syntmp-tmp-827) (if (if syntmp-tmp-827 (apply (lambda (syntmp-_-828 syntmp-name-829) (syntmp-id?-118 syntmp-name-829)) syntmp-tmp-827) #f) (apply (lambda (syntmp-_-830 syntmp-name-831) (values 'define-form (syntmp-wrap-146 syntmp-name-831 syntmp-w-795) '(#(syntax-object void ((top) #(ribcage #(_ name) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(t) #(("m" top)) #("i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(type) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(b) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(n) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(first) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(e r w s rib) #((top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i")) #(ribcage (lambda-var-list gen-var strip strip-annotation ellipsis? chi-void eval-local-transformer chi-local-syntax chi-lambda-clause chi-body chi-macro chi-application chi-expr chi chi-top syntax-type chi-when-list chi-install-global chi-top-sequence chi-sequence source-wrap wrap bound-id-member? distinct-bound-ids? valid-bound-ids? bound-id=? free-id=? id-var-name same-marks? join-marks join-wraps smart-append make-binding-wrap extend-ribcage! make-empty-ribcage new-mark anti-mark the-anti-mark top-marked? top-wrap empty-wrap set-ribcage-labels! set-ribcage-marks! set-ribcage-symnames! ribcage-labels ribcage-marks ribcage-symnames ribcage? make-ribcage gen-labels gen-label make-rename rename-marks rename-new rename-old subst-rename? wrap-subst wrap-marks make-wrap id-sym-name&marks id-sym-name id? nonsymbol-id? global-extend lookup macros-only-env extend-var-env extend-env null-env binding-value binding-type make-binding arg-check source-annotation no-source unannotate set-syntax-object-wrap! set-syntax-object-expression! syntax-object-wrap syntax-object-expression syntax-object? make-syntax-object build-lexical-var build-letrec build-named-let build-let build-sequence build-data build-primref build-lambda build-global-definition build-global-assignment build-global-reference build-lexical-assignment build-lexical-reference build-conditional build-application get-global-definition-hook put-global-definition-hook gensym-hook error-hook local-eval-hook top-level-eval-hook annotation? fx< fx= fx- fx+ noexpand) ((top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top)) ("i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i")) #(ribcage (define-structure) ((top)) ("i"))))) '(()) syntmp-s-796)) syntmp-tmp-827) (syntax-error syntmp-tmp-807))) (syntax-dispatch syntmp-tmp-807 '(any any))))) (syntax-dispatch syntmp-tmp-807 '(any (any . any) any . each-any))))) (syntax-dispatch syntmp-tmp-807 '(any any any)))) syntmp-e-793) (if (memv syntmp-t-806 '(define-syntax)) ((lambda (syntmp-tmp-832) ((lambda (syntmp-tmp-833) (if (if syntmp-tmp-833 (apply (lambda (syntmp-_-834 syntmp-name-835 syntmp-val-836) (syntmp-id?-118 syntmp-name-835)) syntmp-tmp-833) #f) (apply (lambda (syntmp-_-837 syntmp-name-838 syntmp-val-839) (values 'define-syntax-form syntmp-name-838 syntmp-val-839 syntmp-w-795 syntmp-s-796)) syntmp-tmp-833) (syntax-error syntmp-tmp-832))) (syntax-dispatch syntmp-tmp-832 '(any any any)))) syntmp-e-793) (if (memv syntmp-t-806 '(ellipsis)) (values 'ellipsis (syntmp-make-syntax-object-103 (syntmp-syntax-object-expression-105 value) (syntmp-anti-mark-133 (syntmp-syntax-object-wrap-106 value)))) (values 'call #f syntmp-e-793 syntmp-w-795 syntmp-s-796))))))))))))))) (values 'call #f syntmp-e-793 syntmp-w-795 syntmp-s-796)))) ((syntmp-syntax-object?-104 syntmp-e-793) (syntmp-syntax-type-152 (syntmp-syntax-object-expression-105 syntmp-e-793) syntmp-r-794 (syntmp-join-wraps-137 syntmp-w-795 (syntmp-syntax-object-wrap-106 syntmp-e-793)) #f syntmp-rib-797)) ((syntmp-annotation?-92 syntmp-e-793) (syntmp-syntax-type-152 (annotation-expression syntmp-e-793) syntmp-r-794 syntmp-w-795 (annotation-source syntmp-e-793) syntmp-rib-797)) ((self-evaluating? syntmp-e-793) (values 'constant #f syntmp-e-793 syntmp-w-795 syntmp-s-796)) (else (values 'other #f syntmp-e-793 syntmp-w-795 syntmp-s-796))))) (syntmp-chi-when-list-151 (lambda (syntmp-e-840 syntmp-when-list-841 syntmp-w-842) (let syntmp-f-843 ((syntmp-when-list-844 syntmp-when-list-841) (syntmp-situations-845 (quote ()))) (if (null? syntmp-when-list-844) syntmp-situations-845 (syntmp-f-843 (cdr syntmp-when-list-844) (cons (let ((syntmp-x-846 (car syntmp-when-list-844))) (cond ((syntmp-free-id=?-141 syntmp-x-846 '#(syntax-object compile ((top) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(f when-list situations) #((top) (top) (top)) #("i" "i" "i")) #(ribcage () () ()) #(ribcage #(e when-list w) #((top) (top) (top)) #("i" "i" "i")) #(ribcage (lambda-var-list gen-var strip strip-annotation ellipsis? chi-void eval-local-transformer chi-local-syntax chi-lambda-clause chi-body chi-macro chi-application chi-expr chi chi-top syntax-type chi-when-list chi-install-global chi-top-sequence chi-sequence source-wrap wrap bound-id-member? distinct-bound-ids? valid-bound-ids? bound-id=? free-id=? id-var-name same-marks? join-marks join-wraps smart-append make-binding-wrap extend-ribcage! make-empty-ribcage new-mark anti-mark the-anti-mark top-marked? top-wrap empty-wrap set-ribcage-labels! set-ribcage-marks! set-ribcage-symnames! ribcage-labels ribcage-marks ribcage-symnames ribcage? make-ribcage gen-labels gen-label make-rename rename-marks rename-new rename-old subst-rename? wrap-subst wrap-marks make-wrap id-sym-name&marks id-sym-name id? nonsymbol-id? global-extend lookup macros-only-env extend-var-env extend-env null-env binding-value binding-type make-binding arg-check source-annotation no-source unannotate set-syntax-object-wrap! set-syntax-object-expression! syntax-object-wrap syntax-object-expression syntax-object? make-syntax-object build-lexical-var build-letrec build-named-let build-let build-sequence build-data build-primref build-lambda build-global-definition build-global-assignment build-global-reference build-lexical-assignment build-lexical-reference build-conditional build-application get-global-definition-hook put-global-definition-hook gensym-hook error-hook local-eval-hook top-level-eval-hook annotation? fx< fx= fx- fx+ noexpand) ((top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top)) ("i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i")) #(ribcage (define-structure) ((top)) ("i"))))) 'compile) ((syntmp-free-id=?-141 syntmp-x-846 '#(syntax-object load ((top) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(f when-list situations) #((top) (top) (top)) #("i" "i" "i")) #(ribcage () () ()) #(ribcage #(e when-list w) #((top) (top) (top)) #("i" "i" "i")) #(ribcage (lambda-var-list gen-var strip strip-annotation ellipsis? chi-void eval-local-transformer chi-local-syntax chi-lambda-clause chi-body chi-macro chi-application chi-expr chi chi-top syntax-type chi-when-list chi-install-global chi-top-sequence chi-sequence source-wrap wrap bound-id-member? distinct-bound-ids? valid-bound-ids? bound-id=? free-id=? id-var-name same-marks? join-marks join-wraps smart-append make-binding-wrap extend-ribcage! make-empty-ribcage new-mark anti-mark the-anti-mark top-marked? top-wrap empty-wrap set-ribcage-labels! set-ribcage-marks! set-ribcage-symnames! ribcage-labels ribcage-marks ribcage-symnames ribcage? make-ribcage gen-labels gen-label make-rename rename-marks rename-new rename-old subst-rename? wrap-subst wrap-marks make-wrap id-sym-name&marks id-sym-name id? nonsymbol-id? global-extend lookup macros-only-env extend-var-env extend-env null-env binding-value binding-type make-binding arg-check source-annotation no-source unannotate set-syntax-object-wrap! set-syntax-object-expression! syntax-object-wrap syntax-object-expression syntax-object? make-syntax-object build-lexical-var build-letrec build-named-let build-let build-sequence build-data build-primref build-lambda build-global-definition build-global-assignment build-global-reference build-lexical-assignment build-lexical-reference build-conditional build-application get-global-definition-hook put-global-definition-hook gensym-hook error-hook local-eval-hook top-level-eval-hook annotation? fx< fx= fx- fx+ noexpand) ((top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top)) ("i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i")) #(ribcage (define-structure) ((top)) ("i"))))) 'load) ((syntmp-free-id=?-141 syntmp-x-846 '#(syntax-object eval ((top) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(f when-list situations) #((top) (top) (top)) #("i" "i" "i")) #(ribcage () () ()) #(ribcage #(e when-list w) #((top) (top) (top)) #("i" "i" "i")) #(ribcage (lambda-var-list gen-var strip strip-annotation ellipsis? chi-void eval-local-transformer chi-local-syntax chi-lambda-clause chi-body chi-macro chi-application chi-expr chi chi-top syntax-type chi-when-list chi-install-global chi-top-sequence chi-sequence source-wrap wrap bound-id-member? distinct-bound-ids? valid-bound-ids? bound-id=? free-id=? id-var-name same-marks? join-marks join-wraps smart-append make-binding-wrap extend-ribcage! make-empty-ribcage new-mark anti-mark the-anti-mark top-marked? top-wrap empty-wrap set-ribcage-labels! set-ribcage-marks! set-ribcage-symnames! ribcage-labels ribcage-marks ribcage-symnames ribcage? make-ribcage gen-labels gen-label make-rename rename-marks rename-new rename-old subst-rename? wrap-subst wrap-marks make-wrap id-sym-name&marks id-sym-name id? nonsymbol-id? global-extend lookup macros-only-env extend-var-env extend-env null-env binding-value binding-type make-binding arg-check source-annotation no-source unannotate set-syntax-object-wrap! set-syntax-object-expression! syntax-object-wrap syntax-object-expression syntax-object? make-syntax-object build-lexical-var build-letrec build-named-let build-let build-sequence build-data build-primref build-lambda build-global-definition build-global-assignment build-global-reference build-lexical-assignment build-lexical-reference build-conditional build-application get-global-definition-hook put-global-definition-hook gensym-hook error-hook local-eval-hook top-level-eval-hook annotation? fx< fx= fx- fx+ noexpand) ((top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top)) ("i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i")) #(ribcage (define-structure) ((top)) ("i"))))) 'eval) (else (syntax-error (syntmp-wrap-146 syntmp-x-846 syntmp-w-842) "invalid eval-when situation")))) syntmp-situations-845)))))) (syntmp-chi-install-global-150 (lambda (syntmp-name-847 syntmp-e-848) (list 'install-global-transformer (syntmp-build-data-98 #f syntmp-name-847) syntmp-e-848))) (syntmp-chi-top-sequence-149 (lambda (syntmp-body-849 syntmp-r-850 syntmp-w-851 syntmp-s-852 syntmp-m-853 syntmp-esew-854) (syntmp-build-sequence-99 syntmp-s-852 (let syntmp-dobody-855 ((syntmp-body-856 syntmp-body-849) (syntmp-r-857 syntmp-r-850) (syntmp-w-858 syntmp-w-851) (syntmp-m-859 syntmp-m-853) (syntmp-esew-860 syntmp-esew-854)) (if (null? syntmp-body-856) '() (let ((syntmp-first-861 (syntmp-chi-top-153 (car syntmp-body-856) syntmp-r-857 syntmp-w-858 syntmp-m-859 syntmp-esew-860))) (cons syntmp-first-861 (syntmp-dobody-855 (cdr syntmp-body-856) syntmp-r-857 syntmp-w-858 syntmp-m-859 syntmp-esew-860)))))))) (syntmp-chi-sequence-148 (lambda (syntmp-body-862 syntmp-r-863 syntmp-w-864 syntmp-s-865) (syntmp-build-sequence-99 syntmp-s-865 (let syntmp-dobody-866 ((syntmp-body-867 syntmp-body-862) (syntmp-r-868 syntmp-r-863) (syntmp-w-869 syntmp-w-864)) (if (null? syntmp-body-867) '() (let ((syntmp-first-870 (syntmp-chi-154 (car syntmp-body-867) syntmp-r-868 syntmp-w-869))) (cons syntmp-first-870 (syntmp-dobody-866 (cdr syntmp-body-867) syntmp-r-868 syntmp-w-869)))))))) (syntmp-source-wrap-147 (lambda (syntmp-x-871 syntmp-w-872 syntmp-s-873) (syntmp-wrap-146 (if syntmp-s-873 (make-annotation syntmp-x-871 syntmp-s-873 #f) syntmp-x-871) syntmp-w-872))) (syntmp-wrap-146 (lambda (syntmp-x-874 syntmp-w-875) (cond ((and (null? (syntmp-wrap-marks-121 syntmp-w-875)) (null? (syntmp-wrap-subst-122 syntmp-w-875))) syntmp-x-874) ((syntmp-syntax-object?-104 syntmp-x-874) (syntmp-make-syntax-object-103 (syntmp-syntax-object-expression-105 syntmp-x-874) (syntmp-join-wraps-137 syntmp-w-875 (syntmp-syntax-object-wrap-106 syntmp-x-874)))) ((null? syntmp-x-874) syntmp-x-874) (else (syntmp-make-syntax-object-103 syntmp-x-874 syntmp-w-875))))) (syntmp-bound-id-member?-145 (lambda (syntmp-x-876 syntmp-list-877) (and (not (null? syntmp-list-877)) (or (syntmp-bound-id=?-142 syntmp-x-876 (car syntmp-list-877)) (syntmp-bound-id-member?-145 syntmp-x-876 (cdr syntmp-list-877)))))) (syntmp-distinct-bound-ids?-144 (lambda (syntmp-ids-878) (let syntmp-distinct?-879 ((syntmp-ids-880 syntmp-ids-878)) (or (null? syntmp-ids-880) (and (not (syntmp-bound-id-member?-145 (car syntmp-ids-880) (cdr syntmp-ids-880))) (syntmp-distinct?-879 (cdr syntmp-ids-880))))))) (syntmp-valid-bound-ids?-143 (lambda (syntmp-ids-881) (and (let syntmp-all-ids?-882 ((syntmp-ids-883 syntmp-ids-881)) (or (null? syntmp-ids-883) (and (syntmp-id?-118 (car syntmp-ids-883)) (syntmp-all-ids?-882 (cdr syntmp-ids-883))))) (syntmp-distinct-bound-ids?-144 syntmp-ids-881)))) (syntmp-bound-id=?-142 (lambda (syntmp-i-884 syntmp-j-885) (if (and (syntmp-syntax-object?-104 syntmp-i-884) (syntmp-syntax-object?-104 syntmp-j-885)) (and (eq? (let ((syntmp-e-886 (syntmp-syntax-object-expression-105 syntmp-i-884))) (if (syntmp-annotation?-92 syntmp-e-886) (annotation-expression syntmp-e-886) syntmp-e-886)) (let ((syntmp-e-887 (syntmp-syntax-object-expression-105 syntmp-j-885))) (if (syntmp-annotation?-92 syntmp-e-887) (annotation-expression syntmp-e-887) syntmp-e-887))) (syntmp-same-marks?-139 (syntmp-wrap-marks-121 (syntmp-syntax-object-wrap-106 syntmp-i-884)) (syntmp-wrap-marks-121 (syntmp-syntax-object-wrap-106 syntmp-j-885)))) (eq? (let ((syntmp-e-888 syntmp-i-884)) (if (syntmp-annotation?-92 syntmp-e-888) (annotation-expression syntmp-e-888) syntmp-e-888)) (let ((syntmp-e-889 syntmp-j-885)) (if (syntmp-annotation?-92 syntmp-e-889) (annotation-expression syntmp-e-889) syntmp-e-889)))))) (syntmp-free-id=?-141 (lambda (syntmp-i-890 syntmp-j-891) (and (eq? (let ((syntmp-x-892 syntmp-i-890)) (let ((syntmp-e-893 (if (syntmp-syntax-object?-104 syntmp-x-892) (syntmp-syntax-object-expression-105 syntmp-x-892) syntmp-x-892))) (if (syntmp-annotation?-92 syntmp-e-893) (annotation-expression syntmp-e-893) syntmp-e-893))) (let ((syntmp-x-894 syntmp-j-891)) (let ((syntmp-e-895 (if (syntmp-syntax-object?-104 syntmp-x-894) (syntmp-syntax-object-expression-105 syntmp-x-894) syntmp-x-894))) (if (syntmp-annotation?-92 syntmp-e-895) (annotation-expression syntmp-e-895) syntmp-e-895)))) (eq? (syntmp-id-var-name-140 syntmp-i-890 '(())) (syntmp-id-var-name-140 syntmp-j-891 '(())))))) (syntmp-id-var-name-140 (lambda (syntmp-id-896 syntmp-w-897) (letrec ((syntmp-search-vector-rib-900 (lambda (syntmp-sym-911 syntmp-subst-912 syntmp-marks-913 syntmp-symnames-914 syntmp-ribcage-915) (let ((syntmp-n-916 (vector-length syntmp-symnames-914))) (let syntmp-f-917 ((syntmp-i-918 0)) (cond ((syntmp-fx=-90 syntmp-i-918 syntmp-n-916) (syntmp-search-898 syntmp-sym-911 (cdr syntmp-subst-912) syntmp-marks-913)) ((and (eq? (vector-ref syntmp-symnames-914 syntmp-i-918) syntmp-sym-911) (syntmp-same-marks?-139 syntmp-marks-913 (vector-ref (syntmp-ribcage-marks-128 syntmp-ribcage-915) syntmp-i-918))) (values (vector-ref (syntmp-ribcage-labels-129 syntmp-ribcage-915) syntmp-i-918) syntmp-marks-913)) (else (syntmp-f-917 (syntmp-fx+-88 syntmp-i-918 1)))))))) (syntmp-search-list-rib-899 (lambda (syntmp-sym-919 syntmp-subst-920 syntmp-marks-921 syntmp-symnames-922 syntmp-ribcage-923) (let syntmp-f-924 ((syntmp-symnames-925 syntmp-symnames-922) (syntmp-i-926 0)) (cond ((null? syntmp-symnames-925) (syntmp-search-898 syntmp-sym-919 (cdr syntmp-subst-920) syntmp-marks-921)) ((and (eq? (car syntmp-symnames-925) syntmp-sym-919) (syntmp-same-marks?-139 syntmp-marks-921 (list-ref (syntmp-ribcage-marks-128 syntmp-ribcage-923) syntmp-i-926))) (values (list-ref (syntmp-ribcage-labels-129 syntmp-ribcage-923) syntmp-i-926) syntmp-marks-921)) (else (syntmp-f-924 (cdr syntmp-symnames-925) (syntmp-fx+-88 syntmp-i-926 1))))))) (syntmp-search-898 (lambda (syntmp-sym-927 syntmp-subst-928 syntmp-marks-929) (if (null? syntmp-subst-928) (values #f syntmp-marks-929) (let ((syntmp-fst-930 (car syntmp-subst-928))) (if (eq? syntmp-fst-930 (quote shift)) (syntmp-search-898 syntmp-sym-927 (cdr syntmp-subst-928) (cdr syntmp-marks-929)) (let ((syntmp-symnames-931 (syntmp-ribcage-symnames-127 syntmp-fst-930))) (if (vector? syntmp-symnames-931) (syntmp-search-vector-rib-900 syntmp-sym-927 syntmp-subst-928 syntmp-marks-929 syntmp-symnames-931 syntmp-fst-930) (syntmp-search-list-rib-899 syntmp-sym-927 syntmp-subst-928 syntmp-marks-929 syntmp-symnames-931 syntmp-fst-930))))))))) (cond ((symbol? syntmp-id-896) (or (call-with-values (lambda () (syntmp-search-898 syntmp-id-896 (syntmp-wrap-subst-122 syntmp-w-897) (syntmp-wrap-marks-121 syntmp-w-897))) (lambda (syntmp-x-933 . syntmp-ignore-932) syntmp-x-933)) syntmp-id-896)) ((syntmp-syntax-object?-104 syntmp-id-896) (let ((syntmp-id-934 (let ((syntmp-e-936 (syntmp-syntax-object-expression-105 syntmp-id-896))) (if (syntmp-annotation?-92 syntmp-e-936) (annotation-expression syntmp-e-936) syntmp-e-936))) (syntmp-w1-935 (syntmp-syntax-object-wrap-106 syntmp-id-896))) (let ((syntmp-marks-937 (syntmp-join-marks-138 (syntmp-wrap-marks-121 syntmp-w-897) (syntmp-wrap-marks-121 syntmp-w1-935)))) (call-with-values (lambda () (syntmp-search-898 syntmp-id-934 (syntmp-wrap-subst-122 syntmp-w-897) syntmp-marks-937)) (lambda (syntmp-new-id-938 syntmp-marks-939) (or syntmp-new-id-938 (call-with-values (lambda () (syntmp-search-898 syntmp-id-934 (syntmp-wrap-subst-122 syntmp-w1-935) syntmp-marks-939)) (lambda (syntmp-x-941 . syntmp-ignore-940) syntmp-x-941)) syntmp-id-934)))))) ((syntmp-annotation?-92 syntmp-id-896) (let ((syntmp-id-942 (let ((syntmp-e-943 syntmp-id-896)) (if (syntmp-annotation?-92 syntmp-e-943) (annotation-expression syntmp-e-943) syntmp-e-943)))) (or (call-with-values (lambda () (syntmp-search-898 syntmp-id-942 (syntmp-wrap-subst-122 syntmp-w-897) (syntmp-wrap-marks-121 syntmp-w-897))) (lambda (syntmp-x-945 . syntmp-ignore-944) syntmp-x-945)) syntmp-id-942))) (else (syntmp-error-hook-95 'id-var-name "invalid id" syntmp-id-896)))))) (syntmp-same-marks?-139 (lambda (syntmp-x-946 syntmp-y-947) (or (eq? syntmp-x-946 syntmp-y-947) (and (not (null? syntmp-x-946)) (not (null? syntmp-y-947)) (eq? (car syntmp-x-946) (car syntmp-y-947)) (syntmp-same-marks?-139 (cdr syntmp-x-946) (cdr syntmp-y-947)))))) (syntmp-join-marks-138 (lambda (syntmp-m1-948 syntmp-m2-949) (syntmp-smart-append-136 syntmp-m1-948 syntmp-m2-949))) (syntmp-join-wraps-137 (lambda (syntmp-w1-950 syntmp-w2-951) (let ((syntmp-m1-952 (syntmp-wrap-marks-121 syntmp-w1-950)) (syntmp-s1-953 (syntmp-wrap-subst-122 syntmp-w1-950))) (if (null? syntmp-m1-952) (if (null? syntmp-s1-953) syntmp-w2-951 (syntmp-make-wrap-120 (syntmp-wrap-marks-121 syntmp-w2-951) (syntmp-smart-append-136 syntmp-s1-953 (syntmp-wrap-subst-122 syntmp-w2-951)))) (syntmp-make-wrap-120 (syntmp-smart-append-136 syntmp-m1-952 (syntmp-wrap-marks-121 syntmp-w2-951)) (syntmp-smart-append-136 syntmp-s1-953 (syntmp-wrap-subst-122 syntmp-w2-951))))))) (syntmp-smart-append-136 (lambda (syntmp-m1-954 syntmp-m2-955) (if (null? syntmp-m2-955) syntmp-m1-954 (append syntmp-m1-954 syntmp-m2-955)))) (syntmp-make-binding-wrap-135 (lambda (syntmp-ids-956 syntmp-labels-957 syntmp-w-958) (if (null? syntmp-ids-956) syntmp-w-958 (syntmp-make-wrap-120 (syntmp-wrap-marks-121 syntmp-w-958) (cons (let ((syntmp-labelvec-959 (list->vector syntmp-labels-957))) (let ((syntmp-n-960 (vector-length syntmp-labelvec-959))) (let ((syntmp-symnamevec-961 (make-vector syntmp-n-960)) (syntmp-marksvec-962 (make-vector syntmp-n-960))) (begin (let syntmp-f-963 ((syntmp-ids-964 syntmp-ids-956) (syntmp-i-965 0)) (if (not (null? syntmp-ids-964)) (call-with-values (lambda () (syntmp-id-sym-name&marks-119 (car syntmp-ids-964) syntmp-w-958)) (lambda (syntmp-symname-966 syntmp-marks-967) (begin (vector-set! syntmp-symnamevec-961 syntmp-i-965 syntmp-symname-966) (vector-set! syntmp-marksvec-962 syntmp-i-965 syntmp-marks-967) (syntmp-f-963 (cdr syntmp-ids-964) (syntmp-fx+-88 syntmp-i-965 1))))))) (syntmp-make-ribcage-125 syntmp-symnamevec-961 syntmp-marksvec-962 syntmp-labelvec-959))))) (syntmp-wrap-subst-122 syntmp-w-958)))))) (syntmp-extend-ribcage!-134 (lambda (syntmp-ribcage-968 syntmp-id-969 syntmp-label-970) (begin (syntmp-set-ribcage-symnames!-130 syntmp-ribcage-968 (cons (let ((syntmp-e-971 (syntmp-syntax-object-expression-105 syntmp-id-969))) (if (syntmp-annotation?-92 syntmp-e-971) (annotation-expression syntmp-e-971) syntmp-e-971)) (syntmp-ribcage-symnames-127 syntmp-ribcage-968))) (syntmp-set-ribcage-marks!-131 syntmp-ribcage-968 (cons (syntmp-wrap-marks-121 (syntmp-syntax-object-wrap-106 syntmp-id-969)) (syntmp-ribcage-marks-128 syntmp-ribcage-968))) (syntmp-set-ribcage-labels!-132 syntmp-ribcage-968 (cons syntmp-label-970 (syntmp-ribcage-labels-129 syntmp-ribcage-968)))))) (syntmp-anti-mark-133 (lambda (syntmp-w-972) (syntmp-make-wrap-120 (cons #f (syntmp-wrap-marks-121 syntmp-w-972)) (cons 'shift (syntmp-wrap-subst-122 syntmp-w-972))))) (syntmp-set-ribcage-labels!-132 (lambda (syntmp-x-973 syntmp-update-974) (vector-set! syntmp-x-973 3 syntmp-update-974))) (syntmp-set-ribcage-marks!-131 (lambda (syntmp-x-975 syntmp-update-976) (vector-set! syntmp-x-975 2 syntmp-update-976))) (syntmp-set-ribcage-symnames!-130 (lambda (syntmp-x-977 syntmp-update-978) (vector-set! syntmp-x-977 1 syntmp-update-978))) (syntmp-ribcage-labels-129 (lambda (syntmp-x-979) (vector-ref syntmp-x-979 3))) (syntmp-ribcage-marks-128 (lambda (syntmp-x-980) (vector-ref syntmp-x-980 2))) (syntmp-ribcage-symnames-127 (lambda (syntmp-x-981) (vector-ref syntmp-x-981 1))) (syntmp-ribcage?-126 (lambda (syntmp-x-982) (and (vector? syntmp-x-982) (= (vector-length syntmp-x-982) 4) (eq? (vector-ref syntmp-x-982 0) (quote ribcage))))) (syntmp-make-ribcage-125 (lambda (syntmp-symnames-983 syntmp-marks-984 syntmp-labels-985) (vector 'ribcage syntmp-symnames-983 syntmp-marks-984 syntmp-labels-985))) (syntmp-gen-labels-124 (lambda (syntmp-ls-986) (if (null? syntmp-ls-986) '() (cons (syntmp-gen-label-123) (syntmp-gen-labels-124 (cdr syntmp-ls-986)))))) (syntmp-gen-label-123 (lambda () (string #\i))) (syntmp-wrap-subst-122 cdr) (syntmp-wrap-marks-121 car) (syntmp-make-wrap-120 cons) (syntmp-id-sym-name&marks-119 (lambda (syntmp-x-987 syntmp-w-988) (if (syntmp-syntax-object?-104 syntmp-x-987) (values (let ((syntmp-e-989 (syntmp-syntax-object-expression-105 syntmp-x-987))) (if (syntmp-annotation?-92 syntmp-e-989) (annotation-expression syntmp-e-989) syntmp-e-989)) (syntmp-join-marks-138 (syntmp-wrap-marks-121 syntmp-w-988) (syntmp-wrap-marks-121 (syntmp-syntax-object-wrap-106 syntmp-x-987)))) (values (let ((syntmp-e-990 syntmp-x-987)) (if (syntmp-annotation?-92 syntmp-e-990) (annotation-expression syntmp-e-990) syntmp-e-990)) (syntmp-wrap-marks-121 syntmp-w-988))))) (syntmp-id?-118 (lambda (syntmp-x-991) (cond ((symbol? syntmp-x-991) #t) ((syntmp-syntax-object?-104 syntmp-x-991) (symbol? (let ((syntmp-e-992 (syntmp-syntax-object-expression-105 syntmp-x-991))) (if (syntmp-annotation?-92 syntmp-e-992) (annotation-expression syntmp-e-992) syntmp-e-992)))) ((syntmp-annotation?-92 syntmp-x-991) (symbol? (annotation-expression syntmp-x-991))) (else #f)))) (syntmp-nonsymbol-id?-117 (lambda (syntmp-x-993) (and (syntmp-syntax-object?-104 syntmp-x-993) (symbol? (let ((syntmp-e-994 (syntmp-syntax-object-expression-105 syntmp-x-993))) (if (syntmp-annotation?-92 syntmp-e-994) (annotation-expression syntmp-e-994) syntmp-e-994)))))) (syntmp-global-extend-116 (lambda (syntmp-type-995 syntmp-sym-996 syntmp-val-997) (syntmp-put-global-definition-hook-96 syntmp-sym-996 (cons syntmp-type-995 syntmp-val-997)))) (syntmp-lookup-115 (lambda (syntmp-x-998 syntmp-r-999) (cond ((assq syntmp-x-998 syntmp-r-999) => cdr) ((symbol? syntmp-x-998) (or (syntmp-get-global-definition-hook-97 syntmp-x-998) '(global))) (else (quote (displaced-lexical)))))) (syntmp-macros-only-env-114 (lambda (syntmp-r-1000) (if (null? syntmp-r-1000) '() (let ((syntmp-a-1001 (car syntmp-r-1000))) (if (memq (cadr syntmp-a-1001) '(macro ellipsis)) (cons syntmp-a-1001 (syntmp-macros-only-env-114 (cdr syntmp-r-1000))) (syntmp-macros-only-env-114 (cdr syntmp-r-1000))))))) (syntmp-extend-var-env-113 (lambda (syntmp-labels-1002 syntmp-vars-1003 syntmp-r-1004) (if (null? syntmp-labels-1002) syntmp-r-1004 (syntmp-extend-var-env-113 (cdr syntmp-labels-1002) (cdr syntmp-vars-1003) (cons (cons (car syntmp-labels-1002) (cons (quote lexical) (car syntmp-vars-1003))) syntmp-r-1004))))) (syntmp-extend-env-112 (lambda (syntmp-labels-1005 syntmp-bindings-1006 syntmp-r-1007) (if (null? syntmp-labels-1005) syntmp-r-1007 (syntmp-extend-env-112 (cdr syntmp-labels-1005) (cdr syntmp-bindings-1006) (cons (cons (car syntmp-labels-1005) (car syntmp-bindings-1006)) syntmp-r-1007))))) (syntmp-binding-value-111 cdr) (syntmp-binding-type-110 car) (syntmp-source-annotation-109 (lambda (syntmp-x-1008) (cond ((syntmp-annotation?-92 syntmp-x-1008) (annotation-source syntmp-x-1008)) ((syntmp-syntax-object?-104 syntmp-x-1008) (syntmp-source-annotation-109 (syntmp-syntax-object-expression-105 syntmp-x-1008))) (else #f)))) (syntmp-set-syntax-object-wrap!-108 (lambda (syntmp-x-1009 syntmp-update-1010) (vector-set! syntmp-x-1009 2 syntmp-update-1010))) (syntmp-set-syntax-object-expression!-107 (lambda (syntmp-x-1011 syntmp-update-1012) (vector-set! syntmp-x-1011 1 syntmp-update-1012))) (syntmp-syntax-object-wrap-106 (lambda (syntmp-x-1013) (vector-ref syntmp-x-1013 2))) (syntmp-syntax-object-expression-105 (lambda (syntmp-x-1014) (vector-ref syntmp-x-1014 1))) (syntmp-syntax-object?-104 (lambda (syntmp-x-1015) (and (vector? syntmp-x-1015) (= (vector-length syntmp-x-1015) 3) (eq? (vector-ref syntmp-x-1015 0) 'syntax-object)))) (syntmp-make-syntax-object-103 (lambda (syntmp-expression-1016 syntmp-wrap-1017) (vector 'syntax-object syntmp-expression-1016 syntmp-wrap-1017))) (syntmp-build-letrec-102 (lambda (syntmp-src-1018 syntmp-vars-1019 syntmp-val-exps-1020 syntmp-body-exp-1021) (if (null? syntmp-vars-1019) syntmp-body-exp-1021 (list 'letrec (map list syntmp-vars-1019 syntmp-val-exps-1020) syntmp-body-exp-1021)))) (syntmp-build-named-let-101 (lambda (syntmp-src-1022 syntmp-vars-1023 syntmp-val-exps-1024 syntmp-body-exp-1025) (if (null? syntmp-vars-1023) syntmp-body-exp-1025 (list 'let (car syntmp-vars-1023) (map list (cdr syntmp-vars-1023) syntmp-val-exps-1024) syntmp-body-exp-1025)))) (syntmp-build-let-100 (lambda (syntmp-src-1026 syntmp-vars-1027 syntmp-val-exps-1028 syntmp-body-exp-1029) (if (null? syntmp-vars-1027) syntmp-body-exp-1029 (list 'let (map list syntmp-vars-1027 syntmp-val-exps-1028) syntmp-body-exp-1029)))) (syntmp-build-sequence-99 (lambda (syntmp-src-1030 syntmp-exps-1031) (if (null? (cdr syntmp-exps-1031)) (car syntmp-exps-1031) (cons (quote begin) syntmp-exps-1031)))) (syntmp-build-data-98 (lambda (syntmp-src-1032 syntmp-exp-1033) (if (and (self-evaluating? syntmp-exp-1033) (not (vector? syntmp-exp-1033))) syntmp-exp-1033 (list (quote quote) syntmp-exp-1033)))) (syntmp-get-global-definition-hook-97 (lambda (syntmp-symbol-1034) (getprop syntmp-symbol-1034 '*sc-expander*))) (syntmp-put-global-definition-hook-96 (lambda (syntmp-symbol-1035 syntmp-binding-1036) (putprop syntmp-symbol-1035 '*sc-expander* syntmp-binding-1036))) (syntmp-error-hook-95 (lambda (syntmp-who-1037 syntmp-why-1038 syntmp-what-1039) (error syntmp-who-1037 "~a ~s" syntmp-why-1038 syntmp-what-1039))) (syntmp-local-eval-hook-94 (lambda (syntmp-x-1040) (eval (list syntmp-noexpand-87 syntmp-x-1040) (interaction-environment)))) (syntmp-top-level-eval-hook-93 (lambda (syntmp-x-1041) (eval (list syntmp-noexpand-87 syntmp-x-1041) (interaction-environment)))) (syntmp-annotation?-92 (lambda (syntmp-x-1042) #f)) (syntmp-fx<-91 <) (syntmp-fx=-90 =) (syntmp-fx--89 -) (syntmp-fx+-88 +) (syntmp-noexpand-87 "noexpand")) (begin (syntmp-global-extend-116 'local-syntax 'letrec-syntax #t) (syntmp-global-extend-116 'local-syntax 'let-syntax #f) (syntmp-global-extend-116 'core 'fluid-let-syntax (lambda (syntmp-e-1043 syntmp-r-1044 syntmp-w-1045 syntmp-s-1046) ((lambda (syntmp-tmp-1047) ((lambda (syntmp-tmp-1048) (if (if syntmp-tmp-1048 (apply (lambda (syntmp-_-1049 syntmp-var-1050 syntmp-val-1051 syntmp-e1-1052 syntmp-e2-1053) (syntmp-valid-bound-ids?-143 syntmp-var-1050)) syntmp-tmp-1048) #f) (apply (lambda (syntmp-_-1055 syntmp-var-1056 syntmp-val-1057 syntmp-e1-1058 syntmp-e2-1059) (let ((syntmp-names-1060 (map (lambda (syntmp-x-1061) (syntmp-id-var-name-140 syntmp-x-1061 syntmp-w-1045)) syntmp-var-1056))) (begin (for-each (lambda (syntmp-id-1063 syntmp-n-1064) (let ((syntmp-t-1065 (syntmp-binding-type-110 (syntmp-lookup-115 syntmp-n-1064 syntmp-r-1044)))) (if (memv syntmp-t-1065 '(displaced-lexical)) (syntax-error (syntmp-source-wrap-147 syntmp-id-1063 syntmp-w-1045 syntmp-s-1046) "identifier out of context")))) syntmp-var-1056 syntmp-names-1060) (syntmp-chi-body-158 (cons syntmp-e1-1058 syntmp-e2-1059) (syntmp-source-wrap-147 syntmp-e-1043 syntmp-w-1045 syntmp-s-1046) (syntmp-extend-env-112 syntmp-names-1060 (let ((syntmp-trans-r-1068 (syntmp-macros-only-env-114 syntmp-r-1044))) (map (lambda (syntmp-x-1069) (cons 'macro (syntmp-eval-local-transformer-161 (syntmp-chi-154 syntmp-x-1069 syntmp-trans-r-1068 syntmp-w-1045)))) syntmp-val-1057)) syntmp-r-1044) syntmp-w-1045)))) syntmp-tmp-1048) ((lambda (syntmp-_-1071) (syntax-error (syntmp-source-wrap-147 syntmp-e-1043 syntmp-w-1045 syntmp-s-1046))) syntmp-tmp-1047))) (syntax-dispatch syntmp-tmp-1047 '(any #(each (any any)) any . each-any)))) syntmp-e-1043))) (syntmp-global-extend-116 'core 'quote (lambda (syntmp-e-1072 syntmp-r-1073 syntmp-w-1074 syntmp-s-1075) ((lambda (syntmp-tmp-1076) ((lambda (syntmp-tmp-1077) (if syntmp-tmp-1077 (apply (lambda (syntmp-_-1078 syntmp-e-1079) (syntmp-build-data-98 syntmp-s-1075 (syntmp-strip-165 syntmp-e-1079 syntmp-w-1074))) syntmp-tmp-1077) ((lambda (syntmp-_-1080) (syntax-error (syntmp-source-wrap-147 syntmp-e-1072 syntmp-w-1074 syntmp-s-1075))) syntmp-tmp-1076))) (syntax-dispatch syntmp-tmp-1076 '(any any)))) syntmp-e-1072))) (syntmp-global-extend-116 'core 'syntax (letrec ((syntmp-regen-1088 (lambda (syntmp-x-1089) (let ((syntmp-t-1090 (car syntmp-x-1089))) (if (memv syntmp-t-1090 (quote (ref))) (cadr syntmp-x-1089) (if (memv syntmp-t-1090 (quote (primitive))) (cadr syntmp-x-1089) (if (memv syntmp-t-1090 (quote (quote))) (syntmp-build-data-98 #f (cadr syntmp-x-1089)) (if (memv syntmp-t-1090 (quote (lambda))) (list 'lambda (cadr syntmp-x-1089) (syntmp-regen-1088 (caddr syntmp-x-1089))) (if (memv syntmp-t-1090 (quote (map))) (let ((syntmp-ls-1091 (map syntmp-regen-1088 (cdr syntmp-x-1089)))) (cons (if (syntmp-fx=-90 (length syntmp-ls-1091) 2) 'map 'map) syntmp-ls-1091)) (cons (car syntmp-x-1089) (map syntmp-regen-1088 (cdr syntmp-x-1089))))))))))) (syntmp-gen-vector-1087 (lambda (syntmp-x-1092) (cond ((eq? (car syntmp-x-1092) (quote list)) (cons (quote vector) (cdr syntmp-x-1092))) ((eq? (car syntmp-x-1092) (quote quote)) (list 'quote (list->vector (cadr syntmp-x-1092)))) (else (list (quote list->vector) syntmp-x-1092))))) (syntmp-gen-append-1086 (lambda (syntmp-x-1093 syntmp-y-1094) (if (equal? syntmp-y-1094 (quote (quote ()))) syntmp-x-1093 (list (quote append) syntmp-x-1093 syntmp-y-1094)))) (syntmp-gen-cons-1085 (lambda (syntmp-x-1095 syntmp-y-1096) (let ((syntmp-t-1097 (car syntmp-y-1096))) (if (memv syntmp-t-1097 (quote (quote))) (if (eq? (car syntmp-x-1095) (quote quote)) (list 'quote (cons (cadr syntmp-x-1095) (cadr syntmp-y-1096))) (if (eq? (cadr syntmp-y-1096) (quote ())) (list (quote list) syntmp-x-1095) (list (quote cons) syntmp-x-1095 syntmp-y-1096))) (if (memv syntmp-t-1097 (quote (list))) (cons 'list (cons syntmp-x-1095 (cdr syntmp-y-1096))) (list (quote cons) syntmp-x-1095 syntmp-y-1096)))))) (syntmp-gen-map-1084 (lambda (syntmp-e-1098 syntmp-map-env-1099) (let ((syntmp-formals-1100 (map cdr syntmp-map-env-1099)) (syntmp-actuals-1101 (map (lambda (syntmp-x-1102) (list (quote ref) (car syntmp-x-1102))) syntmp-map-env-1099))) (cond ((eq? (car syntmp-e-1098) (quote ref)) (car syntmp-actuals-1101)) ((andmap (lambda (syntmp-x-1103) (and (eq? (car syntmp-x-1103) (quote ref)) (memq (cadr syntmp-x-1103) syntmp-formals-1100))) (cdr syntmp-e-1098)) (cons 'map (cons (list 'primitive (car syntmp-e-1098)) (map (let ((syntmp-r-1104 (map cons syntmp-formals-1100 syntmp-actuals-1101))) (lambda (syntmp-x-1105) (cdr (assq (cadr syntmp-x-1105) syntmp-r-1104)))) (cdr syntmp-e-1098))))) (else (cons 'map (cons (list 'lambda syntmp-formals-1100 syntmp-e-1098) syntmp-actuals-1101))))))) (syntmp-gen-mappend-1083 (lambda (syntmp-e-1106 syntmp-map-env-1107) (list 'apply '(primitive append) (syntmp-gen-map-1084 syntmp-e-1106 syntmp-map-env-1107)))) (syntmp-gen-ref-1082 (lambda (syntmp-src-1108 syntmp-var-1109 syntmp-level-1110 syntmp-maps-1111) (if (syntmp-fx=-90 syntmp-level-1110 0) (values syntmp-var-1109 syntmp-maps-1111) (if (null? syntmp-maps-1111) (syntax-error syntmp-src-1108 "missing ellipsis in syntax form") (call-with-values (lambda () (syntmp-gen-ref-1082 syntmp-src-1108 syntmp-var-1109 (syntmp-fx--89 syntmp-level-1110 1) (cdr syntmp-maps-1111))) (lambda (syntmp-outer-var-1112 syntmp-outer-maps-1113) (let ((syntmp-b-1114 (assq syntmp-outer-var-1112 (car syntmp-maps-1111)))) (if syntmp-b-1114 (values (cdr syntmp-b-1114) syntmp-maps-1111) (let ((syntmp-inner-var-1115 (syntmp-gen-var-166 (quote tmp)))) (values syntmp-inner-var-1115 (cons (cons (cons syntmp-outer-var-1112 syntmp-inner-var-1115) (car syntmp-maps-1111)) syntmp-outer-maps-1113))))))))))) (syntmp-gen-syntax-1081 (lambda (syntmp-src-1116 syntmp-e-1117 syntmp-r-1118 syntmp-maps-1119 syntmp-ellipsis?-1120) (if (syntmp-id?-118 syntmp-e-1117) (let ((syntmp-label-1121 (syntmp-id-var-name-140 syntmp-e-1117 '(())))) (let ((syntmp-b-1122 (syntmp-lookup-115 syntmp-label-1121 syntmp-r-1118))) (if (eq? (syntmp-binding-type-110 syntmp-b-1122) 'syntax) (call-with-values (lambda () (let ((syntmp-var.lev-1123 (syntmp-binding-value-111 syntmp-b-1122))) (syntmp-gen-ref-1082 syntmp-src-1116 (car syntmp-var.lev-1123) (cdr syntmp-var.lev-1123) syntmp-maps-1119))) (lambda (syntmp-var-1124 syntmp-maps-1125) (values (list (quote ref) syntmp-var-1124) syntmp-maps-1125))) (if (syntmp-ellipsis?-1120 syntmp-e-1117 syntmp-r-1118) (syntax-error syntmp-src-1116 "misplaced ellipsis in syntax form") (values (list (quote quote) syntmp-e-1117) syntmp-maps-1119))))) ((lambda (syntmp-tmp-1126) ((lambda (syntmp-tmp-1127) (if (if syntmp-tmp-1127 (apply (lambda (syntmp-dots-1128 syntmp-e-1129) (syntmp-ellipsis?-1120 syntmp-dots-1128 syntmp-r-1118)) syntmp-tmp-1127) #f) (apply (lambda (syntmp-dots-1130 syntmp-e-1131) (syntmp-gen-syntax-1081 syntmp-src-1116 syntmp-e-1131 syntmp-r-1118 syntmp-maps-1119 (lambda (syntmp-e-1132 syntmp-r-1133) #f))) syntmp-tmp-1127) ((lambda (syntmp-tmp-1134) (if (if syntmp-tmp-1134 (apply (lambda (syntmp-x-1135 syntmp-dots-1136 syntmp-y-1137) (syntmp-ellipsis?-1120 syntmp-dots-1136 syntmp-r-1118)) syntmp-tmp-1134) #f) (apply (lambda (syntmp-x-1138 syntmp-dots-1139 syntmp-y-1140) (let syntmp-f-1141 ((syntmp-y-1142 syntmp-y-1140) (syntmp-k-1143 (lambda (syntmp-maps-1144) (call-with-values (lambda () (syntmp-gen-syntax-1081 syntmp-src-1116 syntmp-x-1138 syntmp-r-1118 (cons '() syntmp-maps-1144) syntmp-ellipsis?-1120)) (lambda (syntmp-x-1145 syntmp-maps-1146) (if (null? (car syntmp-maps-1146)) (syntax-error syntmp-src-1116 "extra ellipsis in syntax form") (values (syntmp-gen-map-1084 syntmp-x-1145 (car syntmp-maps-1146)) (cdr syntmp-maps-1146)))))))) ((lambda (syntmp-tmp-1147) ((lambda (syntmp-tmp-1148) (if (if syntmp-tmp-1148 (apply (lambda (syntmp-dots-1149 syntmp-y-1150) (syntmp-ellipsis?-1120 syntmp-dots-1149 syntmp-r-1118)) syntmp-tmp-1148) #f) (apply (lambda (syntmp-dots-1151 syntmp-y-1152) (syntmp-f-1141 syntmp-y-1152 (lambda (syntmp-maps-1153) (call-with-values (lambda () (syntmp-k-1143 (cons '() syntmp-maps-1153))) (lambda (syntmp-x-1154 syntmp-maps-1155) (if (null? (car syntmp-maps-1155)) (syntax-error syntmp-src-1116 "extra ellipsis in syntax form") (values (syntmp-gen-mappend-1083 syntmp-x-1154 (car syntmp-maps-1155)) (cdr syntmp-maps-1155)))))))) syntmp-tmp-1148) ((lambda (syntmp-_-1156) (call-with-values (lambda () (syntmp-gen-syntax-1081 syntmp-src-1116 syntmp-y-1142 syntmp-r-1118 syntmp-maps-1119 syntmp-ellipsis?-1120)) (lambda (syntmp-y-1157 syntmp-maps-1158) (call-with-values (lambda () (syntmp-k-1143 syntmp-maps-1158)) (lambda (syntmp-x-1159 syntmp-maps-1160) (values (syntmp-gen-append-1086 syntmp-x-1159 syntmp-y-1157) syntmp-maps-1160)))))) syntmp-tmp-1147))) (syntax-dispatch syntmp-tmp-1147 '(any . any)))) syntmp-y-1142))) syntmp-tmp-1134) ((lambda (syntmp-tmp-1161) (if syntmp-tmp-1161 (apply (lambda (syntmp-x-1162 syntmp-y-1163) (call-with-values (lambda () (syntmp-gen-syntax-1081 syntmp-src-1116 syntmp-x-1162 syntmp-r-1118 syntmp-maps-1119 syntmp-ellipsis?-1120)) (lambda (syntmp-x-1164 syntmp-maps-1165) (call-with-values (lambda () (syntmp-gen-syntax-1081 syntmp-src-1116 syntmp-y-1163 syntmp-r-1118 syntmp-maps-1165 syntmp-ellipsis?-1120)) (lambda (syntmp-y-1166 syntmp-maps-1167) (values (syntmp-gen-cons-1085 syntmp-x-1164 syntmp-y-1166) syntmp-maps-1167)))))) syntmp-tmp-1161) ((lambda (syntmp-tmp-1168) (if syntmp-tmp-1168 (apply (lambda (syntmp-e1-1169 syntmp-e2-1170) (call-with-values (lambda () (syntmp-gen-syntax-1081 syntmp-src-1116 (cons syntmp-e1-1169 syntmp-e2-1170) syntmp-r-1118 syntmp-maps-1119 syntmp-ellipsis?-1120)) (lambda (syntmp-e-1172 syntmp-maps-1173) (values (syntmp-gen-vector-1087 syntmp-e-1172) syntmp-maps-1173)))) syntmp-tmp-1168) ((lambda (syntmp-_-1174) (values (list 'quote syntmp-e-1117) syntmp-maps-1119)) syntmp-tmp-1126))) (syntax-dispatch syntmp-tmp-1126 '#(vector (any . each-any)))))) (syntax-dispatch syntmp-tmp-1126 '(any . any))))) (syntax-dispatch syntmp-tmp-1126 '(any any . any))))) (syntax-dispatch syntmp-tmp-1126 '(any any)))) syntmp-e-1117))))) (lambda (syntmp-e-1175 syntmp-r-1176 syntmp-w-1177 syntmp-s-1178) (let ((syntmp-e-1179 (syntmp-source-wrap-147 syntmp-e-1175 syntmp-w-1177 syntmp-s-1178))) ((lambda (syntmp-tmp-1180) ((lambda (syntmp-tmp-1181) (if syntmp-tmp-1181 (apply (lambda (syntmp-_-1182 syntmp-x-1183) (call-with-values (lambda () (syntmp-gen-syntax-1081 syntmp-e-1179 syntmp-x-1183 syntmp-r-1176 '() syntmp-ellipsis?-163)) (lambda (syntmp-e-1184 syntmp-maps-1185) (syntmp-regen-1088 syntmp-e-1184)))) syntmp-tmp-1181) ((lambda (syntmp-_-1186) (syntax-error syntmp-e-1179)) syntmp-tmp-1180))) (syntax-dispatch syntmp-tmp-1180 '(any any)))) syntmp-e-1179))))) (syntmp-global-extend-116 'core 'lambda (lambda (syntmp-e-1187 syntmp-r-1188 syntmp-w-1189 syntmp-s-1190) ((lambda (syntmp-tmp-1191) ((lambda (syntmp-tmp-1192) (if syntmp-tmp-1192 (apply (lambda (syntmp-_-1193 syntmp-c-1194) (syntmp-chi-lambda-clause-159 (syntmp-source-wrap-147 syntmp-e-1187 syntmp-w-1189 syntmp-s-1190) syntmp-c-1194 syntmp-r-1188 syntmp-w-1189 (lambda (syntmp-vars-1195 syntmp-body-1196) (list 'lambda syntmp-vars-1195 syntmp-body-1196)))) syntmp-tmp-1192) (syntax-error syntmp-tmp-1191))) (syntax-dispatch syntmp-tmp-1191 '(any . any)))) syntmp-e-1187))) (syntmp-global-extend-116 'core 'with-ellipsis (lambda (syntmp-e-1197 syntmp-r-1198 syntmp-w-1199 syntmp-s-1200) (let ((syntmp-tmp-1201 syntmp-e-1197)) (let ((syntmp-tmp-1202 (syntax-dispatch syntmp-tmp-1201 '(_ any any . each-any)))) (if (and syntmp-tmp-1202 (apply (lambda (syntmp-dots-1203 syntmp-e1-1204 syntmp-e2-1205) (syntmp-id?-118 syntmp-dots-1203)) syntmp-tmp-1202)) (apply (lambda (syntmp-dots-1206 syntmp-e1-1207 syntmp-e2-1208) (let ((syntmp-id-1209 (if (symbol? syntmp-dots-1206) '$sc-ellipsis (syntmp-make-syntax-object-103 '$sc-ellipsis (syntmp-syntax-object-wrap-106 syntmp-dots-1206))))) (let ((syntmp-ids-1210 (list syntmp-id-1209)) (syntmp-labels-1211 (list (syntmp-gen-label-123))) (syntmp-bindings-1212 (list (cons 'ellipsis (syntmp-source-wrap-147 syntmp-dots-1206 syntmp-w-1199 syntmp-s-1200))))) (let ((syntmp-nw-1213 (syntmp-make-binding-wrap-135 syntmp-ids-1210 syntmp-labels-1211 syntmp-w-1199)) (syntmp-nr-1214 (syntmp-extend-env-112 syntmp-labels-1211 syntmp-bindings-1212 syntmp-r-1198))) (syntmp-chi-body-158 (cons syntmp-e1-1207 syntmp-e2-1208) (syntmp-source-wrap-147 syntmp-e-1197 syntmp-nw-1213 syntmp-s-1200) syntmp-nr-1214 syntmp-nw-1213))))) syntmp-tmp-1202) (syntax-error (quote with-ellipsis) "bad syntax")))))) (syntmp-global-extend-116 'core 'let (letrec ((syntmp-chi-let-1215 (lambda (syntmp-e-1216 syntmp-r-1217 syntmp-w-1218 syntmp-s-1219 syntmp-constructor-1220 syntmp-ids-1221 syntmp-vals-1222 syntmp-exps-1223) (if (not (syntmp-valid-bound-ids?-143 syntmp-ids-1221)) (syntax-error syntmp-e-1216 "duplicate bound variable in") (let ((syntmp-labels-1224 (syntmp-gen-labels-124 syntmp-ids-1221)) (syntmp-new-vars-1225 (map syntmp-gen-var-166 syntmp-ids-1221))) (let ((syntmp-nw-1226 (syntmp-make-binding-wrap-135 syntmp-ids-1221 syntmp-labels-1224 syntmp-w-1218)) (syntmp-nr-1227 (syntmp-extend-var-env-113 syntmp-labels-1224 syntmp-new-vars-1225 syntmp-r-1217))) (syntmp-constructor-1220 syntmp-s-1219 syntmp-new-vars-1225 (map (lambda (syntmp-x-1228) (syntmp-chi-154 syntmp-x-1228 syntmp-r-1217 syntmp-w-1218)) syntmp-vals-1222) (syntmp-chi-body-158 syntmp-exps-1223 (syntmp-source-wrap-147 syntmp-e-1216 syntmp-nw-1226 syntmp-s-1219) syntmp-nr-1227 syntmp-nw-1226)))))))) (lambda (syntmp-e-1229 syntmp-r-1230 syntmp-w-1231 syntmp-s-1232) ((lambda (syntmp-tmp-1233) ((lambda (syntmp-tmp-1234) (if syntmp-tmp-1234 (apply (lambda (syntmp-_-1235 syntmp-id-1236 syntmp-val-1237 syntmp-e1-1238 syntmp-e2-1239) (syntmp-chi-let-1215 syntmp-e-1229 syntmp-r-1230 syntmp-w-1231 syntmp-s-1232 syntmp-build-let-100 syntmp-id-1236 syntmp-val-1237 (cons syntmp-e1-1238 syntmp-e2-1239))) syntmp-tmp-1234) ((lambda (syntmp-tmp-1243) (if (if syntmp-tmp-1243 (apply (lambda (syntmp-_-1244 syntmp-f-1245 syntmp-id-1246 syntmp-val-1247 syntmp-e1-1248 syntmp-e2-1249) (syntmp-id?-118 syntmp-f-1245)) syntmp-tmp-1243) #f) (apply (lambda (syntmp-_-1250 syntmp-f-1251 syntmp-id-1252 syntmp-val-1253 syntmp-e1-1254 syntmp-e2-1255) (syntmp-chi-let-1215 syntmp-e-1229 syntmp-r-1230 syntmp-w-1231 syntmp-s-1232 syntmp-build-named-let-101 (cons syntmp-f-1251 syntmp-id-1252) syntmp-val-1253 (cons syntmp-e1-1254 syntmp-e2-1255))) syntmp-tmp-1243) ((lambda (syntmp-_-1259) (syntax-error (syntmp-source-wrap-147 syntmp-e-1229 syntmp-w-1231 syntmp-s-1232))) syntmp-tmp-1233))) (syntax-dispatch syntmp-tmp-1233 '(any any #(each (any any)) any . each-any))))) (syntax-dispatch syntmp-tmp-1233 '(any #(each (any any)) any . each-any)))) syntmp-e-1229)))) (syntmp-global-extend-116 'core 'letrec (lambda (syntmp-e-1260 syntmp-r-1261 syntmp-w-1262 syntmp-s-1263) ((lambda (syntmp-tmp-1264) ((lambda (syntmp-tmp-1265) (if syntmp-tmp-1265 (apply (lambda (syntmp-_-1266 syntmp-id-1267 syntmp-val-1268 syntmp-e1-1269 syntmp-e2-1270) (let ((syntmp-ids-1271 syntmp-id-1267)) (if (not (syntmp-valid-bound-ids?-143 syntmp-ids-1271)) (syntax-error syntmp-e-1260 "duplicate bound variable in") (let ((syntmp-labels-1273 (syntmp-gen-labels-124 syntmp-ids-1271)) (syntmp-new-vars-1274 (map syntmp-gen-var-166 syntmp-ids-1271))) (let ((syntmp-w-1275 (syntmp-make-binding-wrap-135 syntmp-ids-1271 syntmp-labels-1273 syntmp-w-1262)) (syntmp-r-1276 (syntmp-extend-var-env-113 syntmp-labels-1273 syntmp-new-vars-1274 syntmp-r-1261))) (syntmp-build-letrec-102 syntmp-s-1263 syntmp-new-vars-1274 (map (lambda (syntmp-x-1277) (syntmp-chi-154 syntmp-x-1277 syntmp-r-1276 syntmp-w-1275)) syntmp-val-1268) (syntmp-chi-body-158 (cons syntmp-e1-1269 syntmp-e2-1270) (syntmp-source-wrap-147 syntmp-e-1260 syntmp-w-1275 syntmp-s-1263) syntmp-r-1276 syntmp-w-1275))))))) syntmp-tmp-1265) ((lambda (syntmp-_-1280) (syntax-error (syntmp-source-wrap-147 syntmp-e-1260 syntmp-w-1262 syntmp-s-1263))) syntmp-tmp-1264))) (syntax-dispatch syntmp-tmp-1264 '(any #(each (any any)) any . each-any)))) syntmp-e-1260))) (syntmp-global-extend-116 'core 'set! (lambda (syntmp-e-1281 syntmp-r-1282 syntmp-w-1283 syntmp-s-1284) ((lambda (syntmp-tmp-1285) ((lambda (syntmp-tmp-1286) (if (if syntmp-tmp-1286 (apply (lambda (syntmp-_-1287 syntmp-id-1288 syntmp-val-1289) (syntmp-id?-118 syntmp-id-1288)) syntmp-tmp-1286) #f) (apply (lambda (syntmp-_-1290 syntmp-id-1291 syntmp-val-1292) (let ((syntmp-val-1293 (syntmp-chi-154 syntmp-val-1292 syntmp-r-1282 syntmp-w-1283)) (syntmp-n-1294 (syntmp-id-var-name-140 syntmp-id-1291 syntmp-w-1283))) (let ((syntmp-b-1295 (syntmp-lookup-115 syntmp-n-1294 syntmp-r-1282))) (let ((syntmp-t-1296 (syntmp-binding-type-110 syntmp-b-1295))) (if (memv syntmp-t-1296 (quote (lexical))) (list 'set! (syntmp-binding-value-111 syntmp-b-1295) syntmp-val-1293) (if (memv syntmp-t-1296 (quote (global))) (list 'set! syntmp-n-1294 syntmp-val-1293) (if (memv syntmp-t-1296 '(displaced-lexical)) (syntax-error (syntmp-wrap-146 syntmp-id-1291 syntmp-w-1283) "identifier out of context") (syntax-error (syntmp-source-wrap-147 syntmp-e-1281 syntmp-w-1283 syntmp-s-1284))))))))) syntmp-tmp-1286) ((lambda (syntmp-tmp-1297) (if syntmp-tmp-1297 (apply (lambda (syntmp-_-1298 syntmp-getter-1299 syntmp-arg-1300 syntmp-val-1301) (cons (syntmp-chi-154 (list '#(syntax-object setter ((top) #(ribcage #(_ getter arg val) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(e r w s) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage (lambda-var-list gen-var strip strip-annotation ellipsis? chi-void eval-local-transformer chi-local-syntax chi-lambda-clause chi-body chi-macro chi-application chi-expr chi chi-top syntax-type chi-when-list chi-install-global chi-top-sequence chi-sequence source-wrap wrap bound-id-member? distinct-bound-ids? valid-bound-ids? bound-id=? free-id=? id-var-name same-marks? join-marks join-wraps smart-append make-binding-wrap extend-ribcage! make-empty-ribcage new-mark anti-mark the-anti-mark top-marked? top-wrap empty-wrap set-ribcage-labels! set-ribcage-marks! set-ribcage-symnames! ribcage-labels ribcage-marks ribcage-symnames ribcage? make-ribcage gen-labels gen-label make-rename rename-marks rename-new rename-old subst-rename? wrap-subst wrap-marks make-wrap id-sym-name&marks id-sym-name id? nonsymbol-id? global-extend lookup macros-only-env extend-var-env extend-env null-env binding-value binding-type make-binding arg-check source-annotation no-source unannotate set-syntax-object-wrap! set-syntax-object-expression! syntax-object-wrap syntax-object-expression syntax-object? make-syntax-object build-lexical-var build-letrec build-named-let build-let build-sequence build-data build-primref build-lambda build-global-definition build-global-assignment build-global-reference build-lexical-assignment build-lexical-reference build-conditional build-application get-global-definition-hook put-global-definition-hook gensym-hook error-hook local-eval-hook top-level-eval-hook annotation? fx< fx= fx- fx+ noexpand) ((top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top)) ("i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i")) #(ribcage (define-structure) ((top)) ("i")))) syntmp-getter-1299) syntmp-r-1282 syntmp-w-1283) (map (lambda (syntmp-e-1302) (syntmp-chi-154 syntmp-e-1302 syntmp-r-1282 syntmp-w-1283)) (append syntmp-arg-1300 (list syntmp-val-1301))))) syntmp-tmp-1297) ((lambda (syntmp-_-1304) (syntax-error (syntmp-source-wrap-147 syntmp-e-1281 syntmp-w-1283 syntmp-s-1284))) syntmp-tmp-1285))) (syntax-dispatch syntmp-tmp-1285 '(any (any . each-any) any))))) (syntax-dispatch syntmp-tmp-1285 '(any any any)))) syntmp-e-1281))) (syntmp-global-extend-116 'begin 'begin '()) (syntmp-global-extend-116 'define 'define '()) (syntmp-global-extend-116 'define-syntax 'define-syntax '()) (syntmp-global-extend-116 'eval-when 'eval-when '()) (syntmp-global-extend-116 'core 'syntax-case (letrec ((syntmp-gen-syntax-case-1308 (lambda (syntmp-x-1309 syntmp-keys-1310 syntmp-clauses-1311 syntmp-r-1312) (if (null? syntmp-clauses-1311) (list (quote syntax-error) syntmp-x-1309) ((lambda (syntmp-tmp-1313) ((lambda (syntmp-tmp-1314) (if syntmp-tmp-1314 (apply (lambda (syntmp-pat-1315 syntmp-exp-1316) (if (and (syntmp-id?-118 syntmp-pat-1315) (andmap (lambda (syntmp-x-1317) (not (syntmp-free-id=?-141 syntmp-pat-1315 syntmp-x-1317))) (cons '#(syntax-object ... ((top) #(ribcage #(pat exp) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(x keys clauses r) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage (gen-syntax-case gen-clause build-dispatch-call convert-pattern) ((top) (top) (top) (top)) ("i" "i" "i" "i")) #(ribcage (lambda-var-list gen-var strip strip-annotation ellipsis? chi-void eval-local-transformer chi-local-syntax chi-lambda-clause chi-body chi-macro chi-application chi-expr chi chi-top syntax-type chi-when-list chi-install-global chi-top-sequence chi-sequence source-wrap wrap bound-id-member? distinct-bound-ids? valid-bound-ids? bound-id=? free-id=? id-var-name same-marks? join-marks join-wraps smart-append make-binding-wrap extend-ribcage! make-empty-ribcage new-mark anti-mark the-anti-mark top-marked? top-wrap empty-wrap set-ribcage-labels! set-ribcage-marks! set-ribcage-symnames! ribcage-labels ribcage-marks ribcage-symnames ribcage? make-ribcage gen-labels gen-label make-rename rename-marks rename-new rename-old subst-rename? wrap-subst wrap-marks make-wrap id-sym-name&marks id-sym-name id? nonsymbol-id? global-extend lookup macros-only-env extend-var-env extend-env null-env binding-value binding-type make-binding arg-check source-annotation no-source unannotate set-syntax-object-wrap! set-syntax-object-expression! syntax-object-wrap syntax-object-expression syntax-object? make-syntax-object build-lexical-var build-letrec build-named-let build-let build-sequence build-data build-primref build-lambda build-global-definition build-global-assignment build-global-reference build-lexical-assignment build-lexical-reference build-conditional build-application get-global-definition-hook put-global-definition-hook gensym-hook error-hook local-eval-hook top-level-eval-hook annotation? fx< fx= fx- fx+ noexpand) ((top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top) (top)) ("i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i" "i")) #(ribcage (define-structure) ((top)) ("i")))) syntmp-keys-1310))) (let ((syntmp-labels-1318 (list (syntmp-gen-label-123))) (syntmp-var-1319 (syntmp-gen-var-166 syntmp-pat-1315))) (list (list 'lambda (list syntmp-var-1319) (syntmp-chi-154 syntmp-exp-1316 (syntmp-extend-env-112 syntmp-labels-1318 (list (cons 'syntax (cons syntmp-var-1319 0))) syntmp-r-1312) (syntmp-make-binding-wrap-135 (list syntmp-pat-1315) syntmp-labels-1318 '(())))) syntmp-x-1309)) (syntmp-gen-clause-1307 syntmp-x-1309 syntmp-keys-1310 (cdr syntmp-clauses-1311) syntmp-r-1312 syntmp-pat-1315 #t syntmp-exp-1316))) syntmp-tmp-1314) ((lambda (syntmp-tmp-1320) (if syntmp-tmp-1320 (apply (lambda (syntmp-pat-1321 syntmp-fender-1322 syntmp-exp-1323) (syntmp-gen-clause-1307 syntmp-x-1309 syntmp-keys-1310 (cdr syntmp-clauses-1311) syntmp-r-1312 syntmp-pat-1321 syntmp-fender-1322 syntmp-exp-1323)) syntmp-tmp-1320) ((lambda (syntmp-_-1324) (syntax-error (car syntmp-clauses-1311) "invalid syntax-case clause")) syntmp-tmp-1313))) (syntax-dispatch syntmp-tmp-1313 '(any any any))))) (syntax-dispatch syntmp-tmp-1313 '(any any)))) (car syntmp-clauses-1311))))) (syntmp-gen-clause-1307 (lambda (syntmp-x-1325 syntmp-keys-1326 syntmp-clauses-1327 syntmp-r-1328 syntmp-pat-1329 syntmp-fender-1330 syntmp-exp-1331) (call-with-values (lambda () (syntmp-convert-pattern-1305 syntmp-pat-1329 syntmp-keys-1326 (lambda (syntmp-e-1332) (syntmp-ellipsis?-163 syntmp-e-1332 syntmp-r-1328)))) (lambda (syntmp-p-1333 syntmp-pvars-1334) (cond ((not (syntmp-distinct-bound-ids?-144 (map car syntmp-pvars-1334))) (syntax-error syntmp-pat-1329 "duplicate pattern variable in syntax-case pattern")) ((not (andmap (lambda (syntmp-x-1335) (not (syntmp-ellipsis?-163 (car syntmp-x-1335) syntmp-r-1328))) syntmp-pvars-1334)) (syntax-error syntmp-pat-1329 "misplaced ellipsis in syntax-case pattern")) (else (let ((syntmp-y-1336 (syntmp-gen-var-166 (quote tmp)))) (list (list 'lambda (list syntmp-y-1336) (let ((syntmp-y-1337 syntmp-y-1336)) (list 'if ((lambda (syntmp-tmp-1338) ((lambda (syntmp-tmp-1339) (if syntmp-tmp-1339 (apply (lambda () syntmp-y-1337) syntmp-tmp-1339) ((lambda (syntmp-_-1340) (list 'if syntmp-y-1337 (syntmp-build-dispatch-call-1306 syntmp-pvars-1334 syntmp-fender-1330 syntmp-y-1337 syntmp-r-1328) (syntmp-build-data-98 #f #f))) syntmp-tmp-1338))) (syntax-dispatch syntmp-tmp-1338 '#(atom #t)))) syntmp-fender-1330) (syntmp-build-dispatch-call-1306 syntmp-pvars-1334 syntmp-exp-1331 syntmp-y-1337 syntmp-r-1328) (syntmp-gen-syntax-case-1308 syntmp-x-1325 syntmp-keys-1326 syntmp-clauses-1327 syntmp-r-1328)))) (if (eq? syntmp-p-1333 (quote any)) (list (quote list) syntmp-x-1325) (list 'syntax-dispatch syntmp-x-1325 (syntmp-build-data-98 #f syntmp-p-1333))))))))))) (syntmp-build-dispatch-call-1306 (lambda (syntmp-pvars-1341 syntmp-exp-1342 syntmp-y-1343 syntmp-r-1344) (let ((syntmp-ids-1345 (map car syntmp-pvars-1341)) (syntmp-levels-1346 (map cdr syntmp-pvars-1341))) (let ((syntmp-labels-1347 (syntmp-gen-labels-124 syntmp-ids-1345)) (syntmp-new-vars-1348 (map syntmp-gen-var-166 syntmp-ids-1345))) (list 'apply (list 'lambda syntmp-new-vars-1348 (syntmp-chi-154 syntmp-exp-1342 (syntmp-extend-env-112 syntmp-labels-1347 (map (lambda (syntmp-var-1349 syntmp-level-1350) (cons 'syntax (cons syntmp-var-1349 syntmp-level-1350))) syntmp-new-vars-1348 (map cdr syntmp-pvars-1341)) syntmp-r-1344) (syntmp-make-binding-wrap-135 syntmp-ids-1345 syntmp-labels-1347 '(())))) syntmp-y-1343))))) (syntmp-convert-pattern-1305 (lambda (syntmp-pattern-1351 syntmp-keys-1352 syntmp-ellipsis?-1353) (let syntmp-cvt-1354 ((syntmp-p-1355 syntmp-pattern-1351) (syntmp-n-1356 0) (syntmp-ids-1357 (quote ()))) (if (syntmp-id?-118 syntmp-p-1355) (if (syntmp-bound-id-member?-145 syntmp-p-1355 syntmp-keys-1352) (values (vector (quote free-id) syntmp-p-1355) syntmp-ids-1357) (values 'any (cons (cons syntmp-p-1355 syntmp-n-1356) syntmp-ids-1357))) ((lambda (syntmp-tmp-1358) ((lambda (syntmp-tmp-1359) (if (if syntmp-tmp-1359 (apply (lambda (syntmp-x-1360 syntmp-dots-1361) (syntmp-ellipsis?-1353 syntmp-dots-1361)) syntmp-tmp-1359) #f) (apply (lambda (syntmp-x-1362 syntmp-dots-1363) (call-with-values (lambda () (syntmp-cvt-1354 syntmp-x-1362 (syntmp-fx+-88 syntmp-n-1356 1) syntmp-ids-1357)) (lambda (syntmp-p-1364 syntmp-ids-1365) (values (if (eq? syntmp-p-1364 'any) 'each-any (vector 'each syntmp-p-1364)) syntmp-ids-1365)))) syntmp-tmp-1359) ((lambda (syntmp-tmp-1366) (if syntmp-tmp-1366 (apply (lambda (syntmp-x-1367 syntmp-y-1368) (call-with-values (lambda () (syntmp-cvt-1354 syntmp-y-1368 syntmp-n-1356 syntmp-ids-1357)) (lambda (syntmp-y-1369 syntmp-ids-1370) (call-with-values (lambda () (syntmp-cvt-1354 syntmp-x-1367 syntmp-n-1356 syntmp-ids-1370)) (lambda (syntmp-x-1371 syntmp-ids-1372) (values (cons syntmp-x-1371 syntmp-y-1369) syntmp-ids-1372)))))) syntmp-tmp-1366) ((lambda (syntmp-tmp-1373) (if syntmp-tmp-1373 (apply (lambda () (values '() syntmp-ids-1357)) syntmp-tmp-1373) ((lambda (syntmp-tmp-1374) (if syntmp-tmp-1374 (apply (lambda (syntmp-x-1375) (call-with-values (lambda () (syntmp-cvt-1354 syntmp-x-1375 syntmp-n-1356 syntmp-ids-1357)) (lambda (syntmp-p-1377 syntmp-ids-1378) (values (vector 'vector syntmp-p-1377) syntmp-ids-1378)))) syntmp-tmp-1374) ((lambda (syntmp-x-1379) (values (vector 'atom (syntmp-strip-165 syntmp-p-1355 '(()))) syntmp-ids-1357)) syntmp-tmp-1358))) (syntax-dispatch syntmp-tmp-1358 '#(vector each-any))))) (syntax-dispatch syntmp-tmp-1358 '())))) (syntax-dispatch syntmp-tmp-1358 '(any . any))))) (syntax-dispatch syntmp-tmp-1358 '(any any)))) syntmp-p-1355)))))) (lambda (syntmp-e-1380 syntmp-r-1381 syntmp-w-1382 syntmp-s-1383) (let ((syntmp-e-1384 (syntmp-source-wrap-147 syntmp-e-1380 syntmp-w-1382 syntmp-s-1383))) ((lambda (syntmp-tmp-1385) ((lambda (syntmp-tmp-1386) (if syntmp-tmp-1386 (apply (lambda (syntmp-_-1387 syntmp-val-1388 syntmp-key-1389 syntmp-m-1390) (if (andmap (lambda (syntmp-x-1391) (and (syntmp-id?-118 syntmp-x-1391) (not (syntmp-ellipsis?-163 syntmp-x-1391 syntmp-r-1381)))) syntmp-key-1389) (let ((syntmp-x-1393 (syntmp-gen-var-166 (quote tmp)))) (list (list 'lambda (list syntmp-x-1393) (syntmp-gen-syntax-case-1308 syntmp-x-1393 syntmp-key-1389 syntmp-m-1390 syntmp-r-1381)) (syntmp-chi-154 syntmp-val-1388 syntmp-r-1381 '(())))) (syntax-error syntmp-e-1384 "invalid literals list in"))) syntmp-tmp-1386) (syntax-error syntmp-tmp-1385))) (syntax-dispatch syntmp-tmp-1385 '(any any each-any . each-any)))) syntmp-e-1384))))) (set! sc-expand (let ((syntmp-m-1396 (quote e)) (syntmp-esew-1397 (quote (eval)))) (lambda (syntmp-x-1398) (if (and (pair? syntmp-x-1398) (equal? (car syntmp-x-1398) syntmp-noexpand-87)) (cadr syntmp-x-1398) (syntmp-chi-top-153 syntmp-x-1398 '() '((top)) syntmp-m-1396 syntmp-esew-1397))))) (set! sc-expand3 (let ((syntmp-m-1399 (quote e)) (syntmp-esew-1400 (quote (eval)))) (lambda (syntmp-x-1402 . syntmp-rest-1401) (if (and (pair? syntmp-x-1402) (equal? (car syntmp-x-1402) syntmp-noexpand-87)) (cadr syntmp-x-1402) (syntmp-chi-top-153 syntmp-x-1402 '() '((top)) (if (null? syntmp-rest-1401) syntmp-m-1399 (car syntmp-rest-1401)) (if (or (null? syntmp-rest-1401) (null? (cdr syntmp-rest-1401))) syntmp-esew-1400 (cadr syntmp-rest-1401))))))) (set! identifier? (lambda (syntmp-x-1403) (syntmp-nonsymbol-id?-117 syntmp-x-1403))) (set! datum->syntax-object (lambda (syntmp-id-1404 syntmp-datum-1405) (syntmp-make-syntax-object-103 syntmp-datum-1405 (syntmp-syntax-object-wrap-106 syntmp-id-1404)))) (set! syntax-object->datum (lambda (syntmp-x-1406) (syntmp-strip-165 syntmp-x-1406 (quote (()))))) (set! generate-temporaries (lambda (syntmp-ls-1407) (begin (let ((syntmp-x-1408 syntmp-ls-1407)) (if (not (list? syntmp-x-1408)) (syntmp-error-hook-95 'generate-temporaries "invalid argument" syntmp-x-1408))) (map (lambda (syntmp-x-1409) (syntmp-wrap-146 (gensym) (quote ((top))))) syntmp-ls-1407)))) (set! free-identifier=? (lambda (syntmp-x-1410 syntmp-y-1411) (begin (let ((syntmp-x-1412 syntmp-x-1410)) (if (not (syntmp-nonsymbol-id?-117 syntmp-x-1412)) (syntmp-error-hook-95 'free-identifier=? "invalid argument" syntmp-x-1412))) (let ((syntmp-x-1413 syntmp-y-1411)) (if (not (syntmp-nonsymbol-id?-117 syntmp-x-1413)) (syntmp-error-hook-95 'free-identifier=? "invalid argument" syntmp-x-1413))) (syntmp-free-id=?-141 syntmp-x-1410 syntmp-y-1411)))) (set! bound-identifier=? (lambda (syntmp-x-1414 syntmp-y-1415) (begin (let ((syntmp-x-1416 syntmp-x-1414)) (if (not (syntmp-nonsymbol-id?-117 syntmp-x-1416)) (syntmp-error-hook-95 'bound-identifier=? "invalid argument" syntmp-x-1416))) (let ((syntmp-x-1417 syntmp-y-1415)) (if (not (syntmp-nonsymbol-id?-117 syntmp-x-1417)) (syntmp-error-hook-95 'bound-identifier=? "invalid argument" syntmp-x-1417))) (syntmp-bound-id=?-142 syntmp-x-1414 syntmp-y-1415)))) (set! syntax-error (lambda (syntmp-object-1419 . syntmp-messages-1418) (begin (for-each (lambda (syntmp-x-1420) (let ((syntmp-x-1421 syntmp-x-1420)) (if (not (string? syntmp-x-1421)) (syntmp-error-hook-95 'syntax-error "invalid argument" syntmp-x-1421)))) syntmp-messages-1418) (let ((syntmp-message-1422 (if (null? syntmp-messages-1418) "invalid syntax" (apply string-append syntmp-messages-1418)))) (syntmp-error-hook-95 #f syntmp-message-1422 (syntmp-strip-165 syntmp-object-1419 '(()))))))) (set! install-global-transformer (lambda (syntmp-sym-1423 syntmp-v-1424) (begin (let ((syntmp-x-1425 syntmp-sym-1423)) (if (not (symbol? syntmp-x-1425)) (syntmp-error-hook-95 'define-syntax "invalid argument" syntmp-x-1425))) (let ((syntmp-x-1426 syntmp-v-1424)) (if (not (procedure? syntmp-x-1426)) (syntmp-error-hook-95 'define-syntax "invalid argument" syntmp-x-1426))) (syntmp-global-extend-116 'macro syntmp-sym-1423 syntmp-v-1424)))) (letrec ((syntmp-match-1431 (lambda (syntmp-e-1432 syntmp-p-1433 syntmp-w-1434 syntmp-r-1435) (cond ((not syntmp-r-1435) #f) ((eq? syntmp-p-1433 (quote _)) syntmp-r-1435) ((eq? syntmp-p-1433 (quote any)) (cons (syntmp-wrap-146 syntmp-e-1432 syntmp-w-1434) syntmp-r-1435)) ((syntmp-syntax-object?-104 syntmp-e-1432) (syntmp-match*-1430 (let ((syntmp-e-1436 (syntmp-syntax-object-expression-105 syntmp-e-1432))) (if (syntmp-annotation?-92 syntmp-e-1436) (annotation-expression syntmp-e-1436) syntmp-e-1436)) syntmp-p-1433 (syntmp-join-wraps-137 syntmp-w-1434 (syntmp-syntax-object-wrap-106 syntmp-e-1432)) syntmp-r-1435)) (else (syntmp-match*-1430 (let ((syntmp-e-1437 syntmp-e-1432)) (if (syntmp-annotation?-92 syntmp-e-1437) (annotation-expression syntmp-e-1437) syntmp-e-1437)) syntmp-p-1433 syntmp-w-1434 syntmp-r-1435))))) (syntmp-match*-1430 (lambda (syntmp-e-1438 syntmp-p-1439 syntmp-w-1440 syntmp-r-1441) (cond ((null? syntmp-p-1439) (and (null? syntmp-e-1438) syntmp-r-1441)) ((pair? syntmp-p-1439) (and (pair? syntmp-e-1438) (syntmp-match-1431 (car syntmp-e-1438) (car syntmp-p-1439) syntmp-w-1440 (syntmp-match-1431 (cdr syntmp-e-1438) (cdr syntmp-p-1439) syntmp-w-1440 syntmp-r-1441)))) ((eq? syntmp-p-1439 (quote each-any)) (let ((syntmp-l-1442 (syntmp-match-each-any-1428 syntmp-e-1438 syntmp-w-1440))) (and syntmp-l-1442 (cons syntmp-l-1442 syntmp-r-1441)))) (else (let ((syntmp-t-1443 (vector-ref syntmp-p-1439 0))) (if (memv syntmp-t-1443 (quote (each))) (if (null? syntmp-e-1438) (syntmp-match-empty-1429 (vector-ref syntmp-p-1439 1) syntmp-r-1441) (let ((syntmp-l-1444 (syntmp-match-each-1427 syntmp-e-1438 (vector-ref syntmp-p-1439 1) syntmp-w-1440))) (and syntmp-l-1444 (let syntmp-collect-1445 ((syntmp-l-1446 syntmp-l-1444)) (if (null? (car syntmp-l-1446)) syntmp-r-1441 (cons (map car syntmp-l-1446) (syntmp-collect-1445 (map cdr syntmp-l-1446)))))))) (if (memv syntmp-t-1443 (quote (free-id))) (and (syntmp-id?-118 syntmp-e-1438) (syntmp-free-id=?-141 (syntmp-wrap-146 syntmp-e-1438 syntmp-w-1440) (vector-ref syntmp-p-1439 1)) syntmp-r-1441) (if (memv syntmp-t-1443 (quote (atom))) (and (equal? (vector-ref syntmp-p-1439 1) (syntmp-strip-165 syntmp-e-1438 syntmp-w-1440)) syntmp-r-1441) (if (memv syntmp-t-1443 (quote (vector))) (and (vector? syntmp-e-1438) (syntmp-match-1431 (vector->list syntmp-e-1438) (vector-ref syntmp-p-1439 1) syntmp-w-1440 syntmp-r-1441))))))))))) (syntmp-match-empty-1429 (lambda (syntmp-p-1447 syntmp-r-1448) (cond ((null? syntmp-p-1447) syntmp-r-1448) ((eq? syntmp-p-1447 (quote _)) syntmp-r-1448) ((eq? syntmp-p-1447 (quote any)) (cons (quote ()) syntmp-r-1448)) ((pair? syntmp-p-1447) (syntmp-match-empty-1429 (car syntmp-p-1447) (syntmp-match-empty-1429 (cdr syntmp-p-1447) syntmp-r-1448))) ((eq? syntmp-p-1447 (quote each-any)) (cons (quote ()) syntmp-r-1448)) (else (let ((syntmp-t-1449 (vector-ref syntmp-p-1447 0))) (if (memv syntmp-t-1449 (quote (each))) (syntmp-match-empty-1429 (vector-ref syntmp-p-1447 1) syntmp-r-1448) (if (memv syntmp-t-1449 (quote (free-id atom))) syntmp-r-1448 (if (memv syntmp-t-1449 (quote (vector))) (syntmp-match-empty-1429 (vector-ref syntmp-p-1447 1) syntmp-r-1448))))))))) (syntmp-match-each-any-1428 (lambda (syntmp-e-1450 syntmp-w-1451) (cond ((syntmp-annotation?-92 syntmp-e-1450) (syntmp-match-each-any-1428 (annotation-expression syntmp-e-1450) syntmp-w-1451)) ((pair? syntmp-e-1450) (let ((syntmp-l-1452 (syntmp-match-each-any-1428 (cdr syntmp-e-1450) syntmp-w-1451))) (and syntmp-l-1452 (cons (syntmp-wrap-146 (car syntmp-e-1450) syntmp-w-1451) syntmp-l-1452)))) ((null? syntmp-e-1450) (quote ())) ((syntmp-syntax-object?-104 syntmp-e-1450) (syntmp-match-each-any-1428 (syntmp-syntax-object-expression-105 syntmp-e-1450) (syntmp-join-wraps-137 syntmp-w-1451 (syntmp-syntax-object-wrap-106 syntmp-e-1450)))) (else #f)))) (syntmp-match-each-1427 (lambda (syntmp-e-1453 syntmp-p-1454 syntmp-w-1455) (cond ((syntmp-annotation?-92 syntmp-e-1453) (syntmp-match-each-1427 (annotation-expression syntmp-e-1453) syntmp-p-1454 syntmp-w-1455)) ((pair? syntmp-e-1453) (let ((syntmp-first-1456 (syntmp-match-1431 (car syntmp-e-1453) syntmp-p-1454 syntmp-w-1455 '()))) (and syntmp-first-1456 (let ((syntmp-rest-1457 (syntmp-match-each-1427 (cdr syntmp-e-1453) syntmp-p-1454 syntmp-w-1455))) (and syntmp-rest-1457 (cons syntmp-first-1456 syntmp-rest-1457)))))) ((null? syntmp-e-1453) (quote ())) ((syntmp-syntax-object?-104 syntmp-e-1453) (syntmp-match-each-1427 (syntmp-syntax-object-expression-105 syntmp-e-1453) syntmp-p-1454 (syntmp-join-wraps-137 syntmp-w-1455 (syntmp-syntax-object-wrap-106 syntmp-e-1453)))) (else #f))))) (begin (set! syntax-dispatch (lambda (syntmp-e-1458 syntmp-p-1459) (cond ((eq? syntmp-p-1459 (quote any)) (list syntmp-e-1458)) ((eq? syntmp-p-1459 (quote _)) (quote ())) ((syntmp-syntax-object?-104 syntmp-e-1458) (syntmp-match*-1430 (let ((syntmp-e-1460 (syntmp-syntax-object-expression-105 syntmp-e-1458))) (if (syntmp-annotation?-92 syntmp-e-1460) (annotation-expression syntmp-e-1460) syntmp-e-1460)) syntmp-p-1459 (syntmp-syntax-object-wrap-106 syntmp-e-1458) '())) (else (syntmp-match*-1430 (let ((syntmp-e-1461 syntmp-e-1458)) (if (syntmp-annotation?-92 syntmp-e-1461) (annotation-expression syntmp-e-1461) syntmp-e-1461)) syntmp-p-1459 '(()) '()))))) (set! sc-chi syntmp-chi-154))))) (install-global-transformer 'with-syntax (lambda (syntmp-x-1462) ((lambda (syntmp-tmp-1463) ((lambda (syntmp-tmp-1464) (if syntmp-tmp-1464 (apply (lambda (syntmp-_-1465 syntmp-e1-1466 syntmp-e2-1467) (cons '#(syntax-object let ((top) #(ribcage #(_ e1 e2) #((top) (top) (top)) #("i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) (cons '() (cons syntmp-e1-1466 syntmp-e2-1467)))) syntmp-tmp-1464) ((lambda (syntmp-tmp-1469) (if syntmp-tmp-1469 (apply (lambda (syntmp-_-1470 syntmp-out-1471 syntmp-in-1472 syntmp-e1-1473 syntmp-e2-1474) (list '#(syntax-object syntax-case ((top) #(ribcage #(_ out in e1 e2) #((top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) syntmp-in-1472 '() (list syntmp-out-1471 (cons '#(syntax-object let ((top) #(ribcage #(_ out in e1 e2) #((top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) (cons '() (cons syntmp-e1-1473 syntmp-e2-1474)))))) syntmp-tmp-1469) ((lambda (syntmp-tmp-1476) (if syntmp-tmp-1476 (apply (lambda (syntmp-_-1477 syntmp-out-1478 syntmp-in-1479 syntmp-e1-1480 syntmp-e2-1481) (list '#(syntax-object syntax-case ((top) #(ribcage #(_ out in e1 e2) #((top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) (cons '#(syntax-object list ((top) #(ribcage #(_ out in e1 e2) #((top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) syntmp-in-1479) '() (list syntmp-out-1478 (cons '#(syntax-object let ((top) #(ribcage #(_ out in e1 e2) #((top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) (cons '() (cons syntmp-e1-1480 syntmp-e2-1481)))))) syntmp-tmp-1476) (syntax-error syntmp-tmp-1463))) (syntax-dispatch syntmp-tmp-1463 '(any #(each (any any)) any . each-any))))) (syntax-dispatch syntmp-tmp-1463 '(any ((any any)) any . each-any))))) (syntax-dispatch syntmp-tmp-1463 '(any () any . each-any)))) syntmp-x-1462))) (install-global-transformer 'syntax-rules (lambda (syntmp-xx-1503) (letrec ((syntmp-expand-syntax-rules-1504 (lambda (syntmp-dots-1505 syntmp-keys-1506 syntmp-docstrings-1507 syntmp-clauses-1508) ((lambda (syntmp-tmp-1509) ((lambda (syntmp-tmp-1510) (if syntmp-tmp-1510 (apply (lambda (syntmp-k-1511 syntmp-docstring-1512 syntmp-keyword-1513 syntmp-pattern-1514 syntmp-template-1515) ((lambda (syntmp-tmp-1516) ((lambda (syntmp-form-1517) (if syntmp-dots-1505 ((lambda (syntmp-tmp-1518) ((lambda (syntmp-dots-1519) (list '#(syntax-object with-ellipsis ((top) #(ribcage () () ()) #(ribcage #(dots) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(form) #((top)) #("i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(k docstring keyword pattern template) #((top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(dots keys docstrings clauses) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage (expand-syntax-rules) ((top)) ("i")) #(ribcage #(xx) #((top)) #("i")))) syntmp-dots-1519 syntmp-form-1517)) syntmp-tmp-1518)) syntmp-dots-1505) syntmp-form-1517)) syntmp-tmp-1516)) (cons '#(syntax-object lambda ((top) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(k docstring keyword pattern template) #((top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(dots keys docstrings clauses) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage (expand-syntax-rules) ((top)) ("i")) #(ribcage #(xx) #((top)) #("i")))) (cons '(#(syntax-object x ((top) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(k docstring keyword pattern template) #((top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(dots keys docstrings clauses) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage (expand-syntax-rules) ((top)) ("i")) #(ribcage #(xx) #((top)) #("i"))))) (append syntmp-docstring-1512 (list (cons '#(syntax-object syntax-case ((top) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(k docstring keyword pattern template) #((top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(dots keys docstrings clauses) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage (expand-syntax-rules) ((top)) ("i")) #(ribcage #(xx) #((top)) #("i")))) (cons '#(syntax-object x ((top) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(k docstring keyword pattern template) #((top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(dots keys docstrings clauses) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage (expand-syntax-rules) ((top)) ("i")) #(ribcage #(xx) #((top)) #("i")))) (cons syntmp-k-1511 (map (lambda (syntmp-tmp-1522 syntmp-tmp-1521) (list (cons '#(syntax-object dummy ((top) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(k docstring keyword pattern template) #((top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(dots keys docstrings clauses) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage (expand-syntax-rules) ((top)) ("i")) #(ribcage #(xx) #((top)) #("i")))) syntmp-tmp-1521) (list '#(syntax-object syntax ((top) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(k docstring keyword pattern template) #((top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(dots keys docstrings clauses) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage (expand-syntax-rules) ((top)) ("i")) #(ribcage #(xx) #((top)) #("i")))) syntmp-tmp-1522))) syntmp-template-1515 syntmp-pattern-1514)))))))))) syntmp-tmp-1510) (syntax-error syntmp-tmp-1509))) (syntax-dispatch syntmp-tmp-1509 '(each-any each-any #(each ((any . any) any)))))) (list syntmp-keys-1506 syntmp-docstrings-1507 syntmp-clauses-1508))))) ((lambda (syntmp-tmp-1524) ((lambda (syntmp-tmp-1525) (if syntmp-tmp-1525 (apply (lambda (syntmp-_-1526 syntmp-k-1527 syntmp-keyword-1528 syntmp-pattern-1529 syntmp-template-1530) (syntmp-expand-syntax-rules-1504 #f syntmp-k-1527 '() (map (lambda (syntmp-tmp-1534 syntmp-tmp-1533 syntmp-tmp-1532) (list (cons syntmp-tmp-1532 syntmp-tmp-1533) syntmp-tmp-1534)) syntmp-template-1530 syntmp-pattern-1529 syntmp-keyword-1528))) syntmp-tmp-1525) ((lambda (syntmp-tmp-1535) (if (if syntmp-tmp-1535 (apply (lambda (syntmp-_-1536 syntmp-k-1537 syntmp-docstring-1538 syntmp-keyword-1539 syntmp-pattern-1540 syntmp-template-1541) (string? (syntax-object->datum syntmp-docstring-1538))) syntmp-tmp-1535) #f) (apply (lambda (syntmp-_-1542 syntmp-k-1543 syntmp-docstring-1544 syntmp-keyword-1545 syntmp-pattern-1546 syntmp-template-1547) (syntmp-expand-syntax-rules-1504 #f syntmp-k-1543 (list syntmp-docstring-1544) (map (lambda (syntmp-tmp-1551 syntmp-tmp-1550 syntmp-tmp-1549) (list (cons syntmp-tmp-1549 syntmp-tmp-1550) syntmp-tmp-1551)) syntmp-template-1547 syntmp-pattern-1546 syntmp-keyword-1545))) syntmp-tmp-1535) ((lambda (syntmp-tmp-1552) (if (if syntmp-tmp-1552 (apply (lambda (syntmp-_-1553 syntmp-dots-1554 syntmp-k-1555 syntmp-keyword-1556 syntmp-pattern-1557 syntmp-template-1558) (identifier? syntmp-dots-1554)) syntmp-tmp-1552) #f) (apply (lambda (syntmp-_-1559 syntmp-dots-1560 syntmp-k-1561 syntmp-keyword-1562 syntmp-pattern-1563 syntmp-template-1564) (syntmp-expand-syntax-rules-1504 syntmp-dots-1560 syntmp-k-1561 '() (map (lambda (syntmp-tmp-1568 syntmp-tmp-1567 syntmp-tmp-1566) (list (cons syntmp-tmp-1566 syntmp-tmp-1567) syntmp-tmp-1568)) syntmp-template-1564 syntmp-pattern-1563 syntmp-keyword-1562))) syntmp-tmp-1552) ((lambda (syntmp-tmp-1569) (if (if syntmp-tmp-1569 (apply (lambda (syntmp-_-1570 syntmp-dots-1571 syntmp-k-1572 syntmp-docstring-1573 syntmp-keyword-1574 syntmp-pattern-1575 syntmp-template-1576) (and (identifier? syntmp-dots-1571) (string? (syntax-object->datum syntmp-docstring-1573)))) syntmp-tmp-1569) #f) (apply (lambda (syntmp-_-1577 syntmp-dots-1578 syntmp-k-1579 syntmp-docstring-1580 syntmp-keyword-1581 syntmp-pattern-1582 syntmp-template-1583) (syntmp-expand-syntax-rules-1504 syntmp-dots-1578 syntmp-k-1579 (list syntmp-docstring-1580) (map (lambda (syntmp-tmp-1587 syntmp-tmp-1586 syntmp-tmp-1585) (list (cons syntmp-tmp-1585 syntmp-tmp-1586) syntmp-tmp-1587)) syntmp-template-1583 syntmp-pattern-1582 syntmp-keyword-1581))) syntmp-tmp-1569) (syntax-error syntmp-tmp-1524))) (syntax-dispatch syntmp-tmp-1524 '(any any each-any any . #(each ((any . any) any))))))) (syntax-dispatch syntmp-tmp-1524 '(any any each-any . #(each ((any . any) any))))))) (syntax-dispatch syntmp-tmp-1524 '(any each-any any . #(each ((any . any) any))))))) (syntax-dispatch syntmp-tmp-1524 '(any each-any . #(each ((any . any) any)))))) syntmp-xx-1503)))) (install-global-transformer 'define-syntax-rule (lambda (syntmp-x-1667) ((lambda (syntmp-tmp-1668) ((lambda (syntmp-tmp-1669) (if syntmp-tmp-1669 (apply (lambda (syntmp-_-1670 syntmp-name-1671 syntmp-pattern-1672 syntmp-template-1673) (list '#(syntax-object define-syntax ((top) #(ribcage #(_ name pattern template) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) syntmp-name-1671 (list '#(syntax-object syntax-rules ((top) #(ribcage #(_ name pattern template) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) '() (list (cons syntmp-_-1670 syntmp-pattern-1672) syntmp-template-1673)))) syntmp-tmp-1669) ((lambda (syntmp-tmp-1674) (if (if syntmp-tmp-1674 (apply (lambda (syntmp-_-1675 syntmp-name-1676 syntmp-pattern-1677 syntmp-docstring-1678 syntmp-template-1679) (string? (syntax-object->datum syntmp-docstring-1678))) syntmp-tmp-1674) #f) (apply (lambda (syntmp-_-1680 syntmp-name-1681 syntmp-pattern-1682 syntmp-docstring-1683 syntmp-template-1684) (list '#(syntax-object define-syntax ((top) #(ribcage #(_ name pattern docstring template) #((top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) syntmp-name-1681 (list '#(syntax-object syntax-rules ((top) #(ribcage #(_ name pattern docstring template) #((top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) '() syntmp-docstring-1683 (list (cons syntmp-_-1680 syntmp-pattern-1682) syntmp-template-1684)))) syntmp-tmp-1674) (syntax-error syntmp-tmp-1668))) (syntax-dispatch syntmp-tmp-1668 '(any (any . any) any any))))) (syntax-dispatch syntmp-tmp-1668 '(any (any . any) any)))) syntmp-x-1667))) (install-global-transformer 'let* (lambda (syntmp-x-1703) ((lambda (syntmp-tmp-1704) ((lambda (syntmp-tmp-1705) (if (if syntmp-tmp-1705 (apply (lambda (syntmp-let*-1706 syntmp-x-1707 syntmp-v-1708 syntmp-e1-1709 syntmp-e2-1710) (andmap identifier? syntmp-x-1707)) syntmp-tmp-1705) #f) (apply (lambda (syntmp-let*-1712 syntmp-x-1713 syntmp-v-1714 syntmp-e1-1715 syntmp-e2-1716) (let syntmp-f-1717 ((syntmp-bindings-1718 (map list syntmp-x-1713 syntmp-v-1714))) (if (null? syntmp-bindings-1718) (cons '#(syntax-object let ((top) #(ribcage () () ()) #(ribcage #(f bindings) #((top) (top)) #("i" "i")) #(ribcage #(let* x v e1 e2) #((top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) (cons '() (cons syntmp-e1-1715 syntmp-e2-1716))) ((lambda (syntmp-tmp-1722) ((lambda (syntmp-tmp-1723) (if syntmp-tmp-1723 (apply (lambda (syntmp-body-1724 syntmp-binding-1725) (list '#(syntax-object let ((top) #(ribcage () () ()) #(ribcage #(body binding) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(f bindings) #((top) (top)) #("i" "i")) #(ribcage #(let* x v e1 e2) #((top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) (list syntmp-binding-1725) syntmp-body-1724)) syntmp-tmp-1723) (syntax-error syntmp-tmp-1722))) (syntax-dispatch syntmp-tmp-1722 '(any any)))) (list (syntmp-f-1717 (cdr syntmp-bindings-1718)) (car syntmp-bindings-1718)))))) syntmp-tmp-1705) (syntax-error syntmp-tmp-1704))) (syntax-dispatch syntmp-tmp-1704 '(any #(each (any any)) any . each-any)))) syntmp-x-1703))) (install-global-transformer 'do (lambda (syntmp-orig-x-1745) ((lambda (syntmp-tmp-1746) ((lambda (syntmp-tmp-1747) (if syntmp-tmp-1747 (apply (lambda (syntmp-_-1748 syntmp-var-1749 syntmp-init-1750 syntmp-step-1751 syntmp-e0-1752 syntmp-e1-1753 syntmp-c-1754) ((lambda (syntmp-tmp-1755) ((lambda (syntmp-tmp-1756) (if syntmp-tmp-1756 (apply (lambda (syntmp-step-1757) ((lambda (syntmp-tmp-1758) ((lambda (syntmp-tmp-1759) (if syntmp-tmp-1759 (apply (lambda () (list '#(syntax-object let ((top) #(ribcage () () ()) #(ribcage #(step) #((top)) #("i")) #(ribcage #(_ var init step e0 e1 c) #((top) (top) (top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(orig-x) #((top)) #("i")))) '#(syntax-object doloop ((top) #(ribcage () () ()) #(ribcage #(step) #((top)) #("i")) #(ribcage #(_ var init step e0 e1 c) #((top) (top) (top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(orig-x) #((top)) #("i")))) (map list syntmp-var-1749 syntmp-init-1750) (list '#(syntax-object if ((top) #(ribcage () () ()) #(ribcage #(step) #((top)) #("i")) #(ribcage #(_ var init step e0 e1 c) #((top) (top) (top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(orig-x) #((top)) #("i")))) (list '#(syntax-object not ((top) #(ribcage () () ()) #(ribcage #(step) #((top)) #("i")) #(ribcage #(_ var init step e0 e1 c) #((top) (top) (top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(orig-x) #((top)) #("i")))) syntmp-e0-1752) (cons '#(syntax-object begin ((top) #(ribcage () () ()) #(ribcage #(step) #((top)) #("i")) #(ribcage #(_ var init step e0 e1 c) #((top) (top) (top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(orig-x) #((top)) #("i")))) (append syntmp-c-1754 (list (cons '#(syntax-object doloop ((top) #(ribcage () () ()) #(ribcage #(step) #((top)) #("i")) #(ribcage #(_ var init step e0 e1 c) #((top) (top) (top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(orig-x) #((top)) #("i")))) syntmp-step-1757))))))) syntmp-tmp-1759) ((lambda (syntmp-tmp-1764) (if syntmp-tmp-1764 (apply (lambda (syntmp-e1-1765 syntmp-e2-1766) (list '#(syntax-object let ((top) #(ribcage #(e1 e2) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(step) #((top)) #("i")) #(ribcage #(_ var init step e0 e1 c) #((top) (top) (top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(orig-x) #((top)) #("i")))) '#(syntax-object doloop ((top) #(ribcage #(e1 e2) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(step) #((top)) #("i")) #(ribcage #(_ var init step e0 e1 c) #((top) (top) (top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(orig-x) #((top)) #("i")))) (map list syntmp-var-1749 syntmp-init-1750) (list '#(syntax-object if ((top) #(ribcage #(e1 e2) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(step) #((top)) #("i")) #(ribcage #(_ var init step e0 e1 c) #((top) (top) (top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(orig-x) #((top)) #("i")))) syntmp-e0-1752 (cons '#(syntax-object begin ((top) #(ribcage #(e1 e2) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(step) #((top)) #("i")) #(ribcage #(_ var init step e0 e1 c) #((top) (top) (top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(orig-x) #((top)) #("i")))) (cons syntmp-e1-1765 syntmp-e2-1766)) (cons '#(syntax-object begin ((top) #(ribcage #(e1 e2) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(step) #((top)) #("i")) #(ribcage #(_ var init step e0 e1 c) #((top) (top) (top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(orig-x) #((top)) #("i")))) (append syntmp-c-1754 (list (cons '#(syntax-object doloop ((top) #(ribcage #(e1 e2) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(step) #((top)) #("i")) #(ribcage #(_ var init step e0 e1 c) #((top) (top) (top) (top) (top) (top) (top)) #("i" "i" "i" "i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(orig-x) #((top)) #("i")))) syntmp-step-1757))))))) syntmp-tmp-1764) (syntax-error syntmp-tmp-1758))) (syntax-dispatch syntmp-tmp-1758 '(any . each-any))))) (syntax-dispatch syntmp-tmp-1758 '()))) syntmp-e1-1753)) syntmp-tmp-1756) (syntax-error syntmp-tmp-1755))) (syntax-dispatch syntmp-tmp-1755 'each-any))) (map (lambda (syntmp-v-1773 syntmp-s-1774) ((lambda (syntmp-tmp-1775) ((lambda (syntmp-tmp-1776) (if syntmp-tmp-1776 (apply (lambda () syntmp-v-1773) syntmp-tmp-1776) ((lambda (syntmp-tmp-1777) (if syntmp-tmp-1777 (apply (lambda (syntmp-e-1778) syntmp-e-1778) syntmp-tmp-1777) ((lambda (syntmp-_-1779) (syntax-error syntmp-orig-x-1745)) syntmp-tmp-1775))) (syntax-dispatch syntmp-tmp-1775 '(any))))) (syntax-dispatch syntmp-tmp-1775 (quote ())))) syntmp-s-1774)) syntmp-var-1749 syntmp-step-1751))) syntmp-tmp-1747) (syntax-error syntmp-tmp-1746))) (syntax-dispatch syntmp-tmp-1746 '(any #(each (any any . any)) (any . each-any) . each-any)))) syntmp-orig-x-1745))) (install-global-transformer 'quasiquote (letrec ((syntmp-quasicons-1807 (lambda (syntmp-x-1811 syntmp-y-1812) ((lambda (syntmp-tmp-1813) ((lambda (syntmp-tmp-1814) (if syntmp-tmp-1814 (apply (lambda (syntmp-x-1815 syntmp-y-1816) ((lambda (syntmp-tmp-1817) ((lambda (syntmp-tmp-1818) (if syntmp-tmp-1818 (apply (lambda (syntmp-dy-1819) ((lambda (syntmp-tmp-1820) ((lambda (syntmp-tmp-1821) (if syntmp-tmp-1821 (apply (lambda (syntmp-dx-1822) (list '#(syntax-object quote ((top) #(ribcage #(dx) #((top)) #("i")) #(ribcage #(dy) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(x y) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(x y) #((top) (top)) #("i" "i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i")))) (cons syntmp-dx-1822 syntmp-dy-1819))) syntmp-tmp-1821) ((lambda (syntmp-_-1823) (if (null? syntmp-dy-1819) (list '#(syntax-object list ((top) #(ribcage #(_) #((top)) #("i")) #(ribcage #(dy) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(x y) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(x y) #((top) (top)) #("i" "i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i")))) syntmp-x-1815) (list '#(syntax-object cons ((top) #(ribcage #(_) #((top)) #("i")) #(ribcage #(dy) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(x y) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(x y) #((top) (top)) #("i" "i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i")))) syntmp-x-1815 syntmp-y-1816))) syntmp-tmp-1820))) (syntax-dispatch syntmp-tmp-1820 '(#(free-id #(syntax-object quote ((top) #(ribcage #(dy) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(x y) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(x y) #((top) (top)) #("i" "i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i"))))) any)))) syntmp-x-1815)) syntmp-tmp-1818) ((lambda (syntmp-tmp-1824) (if syntmp-tmp-1824 (apply (lambda (syntmp-stuff-1825) (cons '#(syntax-object list ((top) #(ribcage #(stuff) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(x y) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(x y) #((top) (top)) #("i" "i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i")))) (cons syntmp-x-1815 syntmp-stuff-1825))) syntmp-tmp-1824) ((lambda (syntmp-else-1826) (list '#(syntax-object cons ((top) #(ribcage #(else) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(x y) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(x y) #((top) (top)) #("i" "i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i")))) syntmp-x-1815 syntmp-y-1816)) syntmp-tmp-1817))) (syntax-dispatch syntmp-tmp-1817 '(#(free-id #(syntax-object list ((top) #(ribcage () () ()) #(ribcage #(x y) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(x y) #((top) (top)) #("i" "i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i"))))) . any))))) (syntax-dispatch syntmp-tmp-1817 '(#(free-id #(syntax-object quote ((top) #(ribcage () () ()) #(ribcage #(x y) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(x y) #((top) (top)) #("i" "i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i"))))) any)))) syntmp-y-1816)) syntmp-tmp-1814) (syntax-error syntmp-tmp-1813))) (syntax-dispatch syntmp-tmp-1813 '(any any)))) (list syntmp-x-1811 syntmp-y-1812)))) (syntmp-quasiappend-1808 (lambda (syntmp-x-1827 syntmp-y-1828) ((lambda (syntmp-tmp-1829) ((lambda (syntmp-tmp-1830) (if syntmp-tmp-1830 (apply (lambda (syntmp-x-1831 syntmp-y-1832) ((lambda (syntmp-tmp-1833) ((lambda (syntmp-tmp-1834) (if syntmp-tmp-1834 (apply (lambda () syntmp-x-1831) syntmp-tmp-1834) ((lambda (syntmp-_-1835) (list '#(syntax-object append ((top) #(ribcage #(_) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(x y) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(x y) #((top) (top)) #("i" "i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i")))) syntmp-x-1831 syntmp-y-1832)) syntmp-tmp-1833))) (syntax-dispatch syntmp-tmp-1833 '(#(free-id #(syntax-object quote ((top) #(ribcage () () ()) #(ribcage #(x y) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(x y) #((top) (top)) #("i" "i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i"))))) ())))) syntmp-y-1832)) syntmp-tmp-1830) (syntax-error syntmp-tmp-1829))) (syntax-dispatch syntmp-tmp-1829 '(any any)))) (list syntmp-x-1827 syntmp-y-1828)))) (syntmp-quasivector-1809 (lambda (syntmp-x-1836) ((lambda (syntmp-tmp-1837) ((lambda (syntmp-x-1838) ((lambda (syntmp-tmp-1839) ((lambda (syntmp-tmp-1840) (if syntmp-tmp-1840 (apply (lambda (syntmp-x-1841) (list '#(syntax-object quote ((top) #(ribcage #(x) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i")))) (list->vector syntmp-x-1841))) syntmp-tmp-1840) ((lambda (syntmp-tmp-1843) (if syntmp-tmp-1843 (apply (lambda (syntmp-x-1844) (cons '#(syntax-object vector ((top) #(ribcage #(x) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i")))) syntmp-x-1844)) syntmp-tmp-1843) ((lambda (syntmp-_-1846) (list '#(syntax-object list->vector ((top) #(ribcage #(_) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i")))) syntmp-x-1838)) syntmp-tmp-1839))) (syntax-dispatch syntmp-tmp-1839 '(#(free-id #(syntax-object list ((top) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i"))))) . each-any))))) (syntax-dispatch syntmp-tmp-1839 '(#(free-id #(syntax-object quote ((top) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i"))))) each-any)))) syntmp-x-1838)) syntmp-tmp-1837)) syntmp-x-1836))) (syntmp-quasi-1810 (lambda (syntmp-p-1847 syntmp-lev-1848) ((lambda (syntmp-tmp-1849) ((lambda (syntmp-tmp-1850) (if syntmp-tmp-1850 (apply (lambda (syntmp-p-1851) (if (= syntmp-lev-1848 0) syntmp-p-1851 (syntmp-quasicons-1807 '(#(syntax-object quote ((top) #(ribcage #(p) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(p lev) #((top) (top)) #("i" "i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i")))) #(syntax-object unquote ((top) #(ribcage #(p) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(p lev) #((top) (top)) #("i" "i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i"))))) (syntmp-quasi-1810 (list syntmp-p-1851) (- syntmp-lev-1848 1))))) syntmp-tmp-1850) ((lambda (syntmp-tmp-1852) (if syntmp-tmp-1852 (apply (lambda (syntmp-p-1853 syntmp-q-1854) (if (= syntmp-lev-1848 0) (syntmp-quasiappend-1808 syntmp-p-1853 (syntmp-quasi-1810 syntmp-q-1854 syntmp-lev-1848)) (syntmp-quasicons-1807 (syntmp-quasicons-1807 '(#(syntax-object quote ((top) #(ribcage #(p q) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(p lev) #((top) (top)) #("i" "i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i")))) #(syntax-object unquote-splicing ((top) #(ribcage #(p q) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(p lev) #((top) (top)) #("i" "i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i"))))) (syntmp-quasi-1810 (list syntmp-p-1853) (- syntmp-lev-1848 1))) (syntmp-quasi-1810 syntmp-q-1854 syntmp-lev-1848)))) syntmp-tmp-1852) ((lambda (syntmp-tmp-1855) (if syntmp-tmp-1855 (apply (lambda (syntmp-p-1856) (syntmp-quasicons-1807 '(#(syntax-object quote ((top) #(ribcage #(p) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(p lev) #((top) (top)) #("i" "i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i")))) #(syntax-object quasiquote ((top) #(ribcage #(p) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(p lev) #((top) (top)) #("i" "i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i"))))) (syntmp-quasi-1810 (list syntmp-p-1856) (+ syntmp-lev-1848 1)))) syntmp-tmp-1855) ((lambda (syntmp-tmp-1857) (if syntmp-tmp-1857 (apply (lambda (syntmp-p-1858 syntmp-q-1859) (syntmp-quasicons-1807 (syntmp-quasi-1810 syntmp-p-1858 syntmp-lev-1848) (syntmp-quasi-1810 syntmp-q-1859 syntmp-lev-1848))) syntmp-tmp-1857) ((lambda (syntmp-tmp-1860) (if syntmp-tmp-1860 (apply (lambda (syntmp-x-1861) (syntmp-quasivector-1809 (syntmp-quasi-1810 syntmp-x-1861 syntmp-lev-1848))) syntmp-tmp-1860) ((lambda (syntmp-p-1863) (list '#(syntax-object quote ((top) #(ribcage #(p) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(p lev) #((top) (top)) #("i" "i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i")))) syntmp-p-1863)) syntmp-tmp-1849))) (syntax-dispatch syntmp-tmp-1849 '#(vector each-any))))) (syntax-dispatch syntmp-tmp-1849 '(any . any))))) (syntax-dispatch syntmp-tmp-1849 '(#(free-id #(syntax-object quasiquote ((top) #(ribcage () () ()) #(ribcage #(p lev) #((top) (top)) #("i" "i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i"))))) any))))) (syntax-dispatch syntmp-tmp-1849 '((#(free-id #(syntax-object unquote-splicing ((top) #(ribcage () () ()) #(ribcage #(p lev) #((top) (top)) #("i" "i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i"))))) any) . any))))) (syntax-dispatch syntmp-tmp-1849 '(#(free-id #(syntax-object unquote ((top) #(ribcage () () ()) #(ribcage #(p lev) #((top) (top)) #("i" "i")) #(ribcage #(quasicons quasiappend quasivector quasi) #((top) (top) (top) (top)) #("i" "i" "i" "i"))))) any)))) syntmp-p-1847)))) (lambda (syntmp-x-1864) ((lambda (syntmp-tmp-1865) ((lambda (syntmp-tmp-1866) (if syntmp-tmp-1866 (apply (lambda (syntmp-_-1867 syntmp-e-1868) (syntmp-quasi-1810 syntmp-e-1868 0)) syntmp-tmp-1866) (syntax-error syntmp-tmp-1865))) (syntax-dispatch syntmp-tmp-1865 '(any any)))) syntmp-x-1864)))) (install-global-transformer 'include (lambda (syntmp-x-1928) (letrec ((syntmp-read-file-1929 (lambda (syntmp-fn-1930 syntmp-k-1931) (let ((syntmp-p-1932 (open-input-file syntmp-fn-1930))) (let syntmp-f-1933 ((syntmp-x-1934 (read syntmp-p-1932))) (if (eof-object? syntmp-x-1934) (begin (close-input-port syntmp-p-1932) '()) (cons (datum->syntax-object syntmp-k-1931 syntmp-x-1934) (syntmp-f-1933 (read syntmp-p-1932))))))))) ((lambda (syntmp-tmp-1935) ((lambda (syntmp-tmp-1936) (if syntmp-tmp-1936 (apply (lambda (syntmp-k-1937 syntmp-filename-1938) (let ((syntmp-fn-1939 (syntax-object->datum syntmp-filename-1938))) ((lambda (syntmp-tmp-1940) ((lambda (syntmp-tmp-1941) (if syntmp-tmp-1941 (apply (lambda (syntmp-exp-1942) (cons '#(syntax-object begin ((top) #(ribcage () () ()) #(ribcage #(exp) #((top)) #("i")) #(ribcage () () ()) #(ribcage () () ()) #(ribcage #(fn) #((top)) #("i")) #(ribcage #(k filename) #((top) (top)) #("i" "i")) #(ribcage (read-file) ((top)) ("i")) #(ribcage #(x) #((top)) #("i")))) syntmp-exp-1942)) syntmp-tmp-1941) (syntax-error syntmp-tmp-1940))) (syntax-dispatch syntmp-tmp-1940 'each-any))) (syntmp-read-file-1929 syntmp-fn-1939 syntmp-k-1937)))) syntmp-tmp-1936) (syntax-error syntmp-tmp-1935))) (syntax-dispatch syntmp-tmp-1935 '(any any)))) syntmp-x-1928)))) (install-global-transformer 'unquote (lambda (syntmp-x-1959) ((lambda (syntmp-tmp-1960) ((lambda (syntmp-tmp-1961) (if syntmp-tmp-1961 (apply (lambda (syntmp-_-1962 syntmp-e-1963) (error 'unquote "expression ,~s not valid outside of quasiquote" (syntax-object->datum syntmp-e-1963))) syntmp-tmp-1961) (syntax-error syntmp-tmp-1960))) (syntax-dispatch syntmp-tmp-1960 '(any any)))) syntmp-x-1959))) (install-global-transformer 'unquote-splicing (lambda (syntmp-x-1969) ((lambda (syntmp-tmp-1970) ((lambda (syntmp-tmp-1971) (if syntmp-tmp-1971 (apply (lambda (syntmp-_-1972 syntmp-e-1973) (error 'unquote-splicing "expression ,@~s not valid outside of quasiquote" (syntax-object->datum syntmp-e-1973))) syntmp-tmp-1971) (syntax-error syntmp-tmp-1970))) (syntax-dispatch syntmp-tmp-1970 '(any any)))) syntmp-x-1969))) (install-global-transformer 'case (lambda (syntmp-x-1979) ((lambda (syntmp-tmp-1980) ((lambda (syntmp-tmp-1981) (if syntmp-tmp-1981 (apply (lambda (syntmp-_-1982 syntmp-e-1983 syntmp-m1-1984 syntmp-m2-1985) ((lambda (syntmp-tmp-1986) ((lambda (syntmp-body-1987) (list '#(syntax-object let ((top) #(ribcage () () ()) #(ribcage #(body) #((top)) #("i")) #(ribcage #(_ e m1 m2) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) (list (list '#(syntax-object t ((top) #(ribcage () () ()) #(ribcage #(body) #((top)) #("i")) #(ribcage #(_ e m1 m2) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) syntmp-e-1983)) syntmp-body-1987)) syntmp-tmp-1986)) (let syntmp-f-1988 ((syntmp-clause-1989 syntmp-m1-1984) (syntmp-clauses-1990 syntmp-m2-1985)) (if (null? syntmp-clauses-1990) ((lambda (syntmp-tmp-1992) ((lambda (syntmp-tmp-1993) (if syntmp-tmp-1993 (apply (lambda (syntmp-e1-1994 syntmp-e2-1995) (cons '#(syntax-object begin ((top) #(ribcage #(e1 e2) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(f clause clauses) #((top) (top) (top)) #("i" "i" "i")) #(ribcage #(_ e m1 m2) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) (cons syntmp-e1-1994 syntmp-e2-1995))) syntmp-tmp-1993) ((lambda (syntmp-tmp-1997) (if syntmp-tmp-1997 (apply (lambda (syntmp-k-1998 syntmp-e1-1999 syntmp-e2-2000) (list '#(syntax-object if ((top) #(ribcage #(k e1 e2) #((top) (top) (top)) #("i" "i" "i")) #(ribcage () () ()) #(ribcage #(f clause clauses) #((top) (top) (top)) #("i" "i" "i")) #(ribcage #(_ e m1 m2) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) (list '#(syntax-object memv ((top) #(ribcage #(k e1 e2) #((top) (top) (top)) #("i" "i" "i")) #(ribcage () () ()) #(ribcage #(f clause clauses) #((top) (top) (top)) #("i" "i" "i")) #(ribcage #(_ e m1 m2) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) '#(syntax-object t ((top) #(ribcage #(k e1 e2) #((top) (top) (top)) #("i" "i" "i")) #(ribcage () () ()) #(ribcage #(f clause clauses) #((top) (top) (top)) #("i" "i" "i")) #(ribcage #(_ e m1 m2) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) (list '#(syntax-object quote ((top) #(ribcage #(k e1 e2) #((top) (top) (top)) #("i" "i" "i")) #(ribcage () () ()) #(ribcage #(f clause clauses) #((top) (top) (top)) #("i" "i" "i")) #(ribcage #(_ e m1 m2) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) syntmp-k-1998)) (cons '#(syntax-object begin ((top) #(ribcage #(k e1 e2) #((top) (top) (top)) #("i" "i" "i")) #(ribcage () () ()) #(ribcage #(f clause clauses) #((top) (top) (top)) #("i" "i" "i")) #(ribcage #(_ e m1 m2) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) (cons syntmp-e1-1999 syntmp-e2-2000)))) syntmp-tmp-1997) ((lambda (syntmp-_-2003) (syntax-error syntmp-x-1979)) syntmp-tmp-1992))) (syntax-dispatch syntmp-tmp-1992 '(each-any any . each-any))))) (syntax-dispatch syntmp-tmp-1992 '(#(free-id #(syntax-object else ((top) #(ribcage () () ()) #(ribcage #(f clause clauses) #((top) (top) (top)) #("i" "i" "i")) #(ribcage #(_ e m1 m2) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i"))))) any . each-any)))) syntmp-clause-1989) ((lambda (syntmp-tmp-2004) ((lambda (syntmp-rest-2005) ((lambda (syntmp-tmp-2006) ((lambda (syntmp-tmp-2007) (if syntmp-tmp-2007 (apply (lambda (syntmp-k-2008 syntmp-e1-2009 syntmp-e2-2010) (list '#(syntax-object if ((top) #(ribcage #(k e1 e2) #((top) (top) (top)) #("i" "i" "i")) #(ribcage () () ()) #(ribcage #(rest) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(f clause clauses) #((top) (top) (top)) #("i" "i" "i")) #(ribcage #(_ e m1 m2) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) (list '#(syntax-object memv ((top) #(ribcage #(k e1 e2) #((top) (top) (top)) #("i" "i" "i")) #(ribcage () () ()) #(ribcage #(rest) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(f clause clauses) #((top) (top) (top)) #("i" "i" "i")) #(ribcage #(_ e m1 m2) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) '#(syntax-object t ((top) #(ribcage #(k e1 e2) #((top) (top) (top)) #("i" "i" "i")) #(ribcage () () ()) #(ribcage #(rest) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(f clause clauses) #((top) (top) (top)) #("i" "i" "i")) #(ribcage #(_ e m1 m2) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) (list '#(syntax-object quote ((top) #(ribcage #(k e1 e2) #((top) (top) (top)) #("i" "i" "i")) #(ribcage () () ()) #(ribcage #(rest) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(f clause clauses) #((top) (top) (top)) #("i" "i" "i")) #(ribcage #(_ e m1 m2) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) syntmp-k-2008)) (cons '#(syntax-object begin ((top) #(ribcage #(k e1 e2) #((top) (top) (top)) #("i" "i" "i")) #(ribcage () () ()) #(ribcage #(rest) #((top)) #("i")) #(ribcage () () ()) #(ribcage #(f clause clauses) #((top) (top) (top)) #("i" "i" "i")) #(ribcage #(_ e m1 m2) #((top) (top) (top) (top)) #("i" "i" "i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) (cons syntmp-e1-2009 syntmp-e2-2010)) syntmp-rest-2005)) syntmp-tmp-2007) ((lambda (syntmp-_-2013) (syntax-error syntmp-x-1979)) syntmp-tmp-2006))) (syntax-dispatch syntmp-tmp-2006 '(each-any any . each-any)))) syntmp-clause-1989)) syntmp-tmp-2004)) (syntmp-f-1988 (car syntmp-clauses-1990) (cdr syntmp-clauses-1990))))))) syntmp-tmp-1981) (syntax-error syntmp-tmp-1980))) (syntax-dispatch syntmp-tmp-1980 '(any any any . each-any)))) syntmp-x-1979))) (install-global-transformer 'identifier-syntax (lambda (syntmp-x-2043) ((lambda (syntmp-tmp-2044) ((lambda (syntmp-tmp-2045) (if syntmp-tmp-2045 (apply (lambda (syntmp-_-2046 syntmp-e-2047) (list '#(syntax-object lambda ((top) #(ribcage #(_ e) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) '(#(syntax-object x ((top) #(ribcage #(_ e) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i"))))) (list '#(syntax-object syntax-case ((top) #(ribcage #(_ e) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) '#(syntax-object x ((top) #(ribcage #(_ e) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) '() (list '#(syntax-object id ((top) #(ribcage #(_ e) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) '(#(syntax-object identifier? ((top) #(ribcage #(_ e) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) (#(syntax-object syntax ((top) #(ribcage #(_ e) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) #(syntax-object id ((top) #(ribcage #(_ e) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))))) (list '#(syntax-object syntax ((top) #(ribcage #(_ e) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) syntmp-e-2047)) (list (cons syntmp-_-2046 '(#(syntax-object x ((top) #(ribcage #(_ e) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) #(syntax-object ... ((top) #(ribcage #(_ e) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))))) (list '#(syntax-object syntax ((top) #(ribcage #(_ e) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) (cons syntmp-e-2047 '(#(syntax-object x ((top) #(ribcage #(_ e) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i")))) #(syntax-object ... ((top) #(ribcage #(_ e) #((top) (top)) #("i" "i")) #(ribcage () () ()) #(ribcage #(x) #((top)) #("i"))))))))))) syntmp-tmp-2045) (syntax-error syntmp-tmp-2044))) (syntax-dispatch syntmp-tmp-2044 '(any any)))) syntmp-x-2043)))
;;; -*-scheme-*- ;;; GNU Mes --- Maxwell Equations of Software ;;; Copyright (C) 2016, 2017, 2018 Free Software Foundation, Inc. ;;; ;;; This file is part of GNU Mes. ;;; ;;; GNU Mes is free software; you can redistribute it and/or modify it ;;; under the terms of the GNU General Public License as published by ;;; the Free Software Foundation; either version 3 of the License, or (at ;;; your option) any later version. ;;; ;;; GNU Mes is distributed in the hope that it will be useful, but ;;; WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. ;;; Commentary: ;;; This file is generated from psyntax.ss. ;;; Code:
;;; -*-scheme-*- ;;; GNU Mes --- Maxwell Equations of Software ;;; Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> ;;; ;;; This file is part of GNU Mes. ;;; ;;; GNU Mes is free software; you can redistribute it and/or modify it ;;; under the terms of the GNU General Public License as published by ;;; the Free Software Foundation; either version 3 of the License, or (at ;;; your option) any later version. ;;; ;;; GNU Mes is distributed in the hope that it will be useful, but ;;; WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. ;;; Commentary: ;;; srfi-9.mes - records, based on struct. (define-macro (define-record-type name constructor+params predicate . fields) (let ((type (make-record-type name (map car fields)))) `(begin (define ,name ,type) (define ,(car constructor+params) ,(record-constructor type name (cdr constructor+params))) (define ,predicate ,(record-predicate type)) (define-record-accessors ,type ,@fields)))) (define (make-record-type type fields . printer) (let ((printer (if (pair? printer) (car printer)))) (make-struct '<record-type> (cons type (list fields)) printer))) (define (record-type? o) (eq? (struct-vtable o) '<record-type>)) (define (struct-vtable o) (struct-ref o 0)) (define (record-type o) (struct-ref o 2)) (define (record-predicate type) (lambda (o) (and (record? o) (eq? (record-type o) (record-type type))))) (define (record? o) (and (struct? o) (record-type? (struct-vtable o)))) (define (record-constructor type name params) (let ((fields (record-fields type)) (record-type (record-type type))) (lambda (. o) (if (not (= (length o) (length params))) (error "wrong number of arguments for record-constructor") (let ((rest (make-list (- (length fields) (length params))))) (make-struct type (cons name (append o rest)) record-printer)))))) (define record-printer *unspecified*) ; TODO (define (record-printer o) (display "#<") (display (record-type o)) (let* ((vtable (struct-vtable o)) (fields (record-fields vtable))) (for-each (lambda (field) (display " ") (display field) (display ": ") (display ((record-getter vtable field) o))) fields)) (display ">")) (define (record-fields o) (struct-ref o 3)) (define-macro (define-record-accessors type . fields) `(begin ,@(map (lambda (field) `(define-record-accessor ,type ,field)) fields))) (define-macro (define-record-accessor type field) `(begin (define ,(cadr field) ,(record-getter type (car field))) (if ,(pair? (cddr field)) (define ,(if (pair? (cddr field)) (caddr field)) ,(record-setter type (car field)))))) (define (record-getter type field) (let ((i (record-field-index type field))) (lambda (o . field?) (if (not (eq? (record-type o) (record-type type))) (error "record getter: record expected" type o) (if (pair? field?) field (struct-ref o i)))))) (define (record-setter type field) (let ((i (record-field-index type field))) (lambda (o v) (if (not (eq? (record-type o) (record-type type))) (error "record setter: record expected" type o) (struct-set! o i v))))) (define (record-field-index type field) (+ 3 (or (lst-index (record-fields type) field) (error "no such field" type field)))) (define (lst-index lst o) (let loop ((lst lst) (i 0)) (and (pair? lst) (if (eq? o (car lst)) i (loop (cdr lst) (1+ i)))))) ;; (define-record-type <employee> ;; (make-employee name age salary) ;; employee? ;; (name employe-name) ;; (age employee-age set-employee-age!) ;; (salary employee-salary)) ;; (display <employee>) ;; (newline) ;; (display make-employee) ;; (newline) ;; (display "employee-age ") ;; (display employee-age) ;; (newline) ;; (display "set-employee-age! ") ;; (display set-employee-age!) ;; (newline) ;; (define janneke (make-employee "janneke" 49 42)) ;; (display janneke) ;; (newline) ;; (display (employee-age janneke)) ;; (newline) ;; (display (set-employee-age! janneke 33)) ;; (newline) ;; (display (employee-age janneke)) ;; (newline)
;;; -*-scheme-*- ;;; GNU Mes --- Maxwell Equations of Software ;;; Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> ;;; ;;; This file is part of GNU Mes. ;;; ;;; GNU Mes is free software; you can redistribute it and/or modify it ;;; under the terms of the GNU General Public License as published by ;;; the Free Software Foundation; either version 3 of the License, or (at ;;; your option) any later version. ;;; ;;; GNU Mes is distributed in the hope that it will be useful, but ;;; WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. ;;; Commentary: ;;; srfi-9.mes - GNU immutable records. (define-macro (define-immutable-record-type type constructor+params predicate . fields) `(define-record-type ,type ,constructor+params ,predicate ,@(map (lambda (f) (list-head f 2)) fields))) (define-macro (set-field o getters value) `(let ((getter ,(car getters))) (let* ((type (struct-vtable ,o)) (name (record-type ,o)) (set (getter ,o #t))) (define (field->value field) (if (eq? set field) ,value ((record-getter type field) ,o))) (let* ((fields (record-fields type)) (values (map field->value fields))) (apply (record-constructor type name fields) values)))))
/* * GNU Mes --- Maxwell Equations of Software * Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __M2_LIB_H #define __M2_LIB_H char **environ; int __stdin; int __stdout; int __stderr; int errno; // CONSTANT EOF 0xffffffff // CONSTANT __FILEDES_MAX 512 char* cast_intp_to_charp (int *i); char* cast_long_to_charp (long i); long cast_charp_to_long (char const *); long cast_int_to_long (int i); long cast_voidp_to_long (void const *); char *itoa (int number); char *ltoa (long number); int __ungetc_p (int filedes); int eputs (char *s); int oputs (char *s); int puts (char *s); size_t strlen (char *s); ssize_t _write (); ssize_t write (int filedes, void *buffer, size_t size); void __ungetc_clear (int filedes); void __ungetc_init (); void __ungetc_set (int filedes, int c); struct timezone { int tz_minuteswest; int tz_dsttime; }; struct timespec { long tv_sec; long tv_nsec; }; struct timeval { long tv_sec; long tv_usec; }; #endif /* __M2_LIB_H */
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib-mini.h" int __stdin; int __stdout; int __stderr; char **environ; int main (int argc, char **argv, char **envp); /* FIXME: this is going to be called `FUNCTION__start' */ //#int //#_start () //#{ //# .. //#}
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ void _exit () { asm ("mov____$i32,%eax SYS_exit"); asm ("mov____0x8(%ebp),%ebx !-4"); asm ("int____$0x80"); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ void _write (int filedes, void *buffer, int size) { asm ("mov____$i32,%eax SYS_write"); asm ("mov____0x8(%ebp),%ebx !-4"); asm ("mov____0x8(%ebp),%ecx !-8"); asm ("mov____0x8(%ebp),%edx !-12"); asm ("int____$0x80"); }
/* * GNU Mes --- Maxwell Equations of Software * Copyright © 2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib-mini.h> int errno; char **environ; int __stdin; int __stdout; int __stderr;
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> char* cast_intp_to_charp (int const *i) { return i; } char* cast_long_to_charp (long i) { return i; } long cast_charp_to_long (char const *i) { return i; } long cast_int_to_long (int i) { return i; } long cast_voidp_to_long (void const *i) { return i; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> void _exit (int code); void exit (int code) { _exit (code); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> ssize_t write (int filedes, void const *buffer, size_t size) { int r = _write (filedes, buffer, size); if (r < 0) { errno = -r; r = -1; } else errno = 0; return r; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <errno.h> #include <linux/x86/syscall.h> int errno; int __sys_call (int sys_call) { asm ("mov____0x8(%ebp),%eax !-4"); asm ("int____$0x80"); } int __sys_call1 (int sys_call, int one) { asm ("mov____0x8(%ebp),%eax !-4"); asm ("mov____0x8(%ebp),%ebx !-8"); asm ("int____$0x80"); } int __sys_call2 (int sys_call, int one, int two) { asm ("mov____0x8(%ebp),%eax !-4"); asm ("mov____0x8(%ebp),%ebx !-8"); asm ("mov____0x8(%ebp),%ecx !-12"); asm ("int____$0x80"); } int __sys_call3 (int sys_call, int one, int two, int three) { asm ("mov____0x8(%ebp),%eax !-4"); asm ("mov____0x8(%ebp),%ebx !-8"); asm ("mov____0x8(%ebp),%ecx !-12"); asm ("mov____0x8(%ebp),%edx !-16"); asm ("int____$0x80"); } int __sys_call4 (int sys_call, int one, int two, int three, int four) { asm ("mov____0x8(%ebp),%eax !-4"); asm ("mov____0x8(%ebp),%ebx !-8"); asm ("mov____0x8(%ebp),%ecx !-12"); asm ("mov____0x8(%ebp),%edx !-16"); asm ("mov____0x8(%ebp),%esi !-20"); asm ("int____$0x80"); } int _sys_call (int sys_call) { int r = __sys_call (sys_call); if (r < 0) { errno = -r; r = -1; } else errno = 0; return r; } int _sys_call1 (int sys_call, int one) { int r = __sys_call1 (sys_call, one); if (r < 0) { errno = -r; r = -1; } else errno = 0; return r; } int _sys_call2 (int sys_call, int one, int two) { int r = __sys_call2 (sys_call, one, two); if (r < 0) { errno = -r; r = -1; } else errno = 0; return r; } int _sys_call3 (int sys_call, int one, int two, int three) { int r = __sys_call3 (sys_call, one, two, three); if (r < 0) { errno = -r; r = -1; } else errno = 0; return r; } int _sys_call4 (int sys_call, int one, int two, int three, int four) { int r = __sys_call4 (sys_call, one, two, three, four); if (r < 0) { errno = -r; r = -1; } else errno = 0; return r; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ // CONSTANT SIGABRT 0 int __raise (int signum) { return -1; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <linux/syscall.h> #include <arch/syscall.h> long brk (void *addr) { long long_addr = cast_voidp_to_long (addr); return _sys_call1 (SYS_brk, long_addr); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2021 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <string.h> char *__brk = 0; void * malloc (size_t size) { if (!__brk) __brk = cast_long_to_charp (brk (0)); if (brk (__brk + size) == -1) return 0; char *p = __brk; __brk = __brk + size; return p; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> char * _memset (char *s, int c, size_t n) { char *p = s; while (n != 0) { n = n - 1; s[0] = c; s = s + 1; } return p; } void * memset (void *s, int c, size_t n) { return _memset (s, c, n); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <syscall.h> #include <mes/lib.h> #include <fcntl.h> long read (int filedes, void *buffer, long size) { long bytes = _sys_call3 (SYS_read, filedes, buffer, size); return bytes; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <limits.h> #include <stdlib.h> #include <string.h> #include <sys/resource.h> #include <unistd.h> int *__ungetc_buf; int __ungetc_p (int filedes) { if (__ungetc_buf == 0) __ungetc_init (); return __ungetc_buf[filedes] >= 0; } void __ungetc_init () { if (__ungetc_buf == 0) { int save_errno = errno; __ungetc_buf = malloc ((__FILEDES_MAX + 1) * sizeof (int)); errno = save_errno; memset (__ungetc_buf, -1, (__FILEDES_MAX + 1) * sizeof (int)); } } void __ungetc_clear (int filedes) { if (__ungetc_buf == 0) __ungetc_init (); __ungetc_buf[filedes] = -1; } void __ungetc_set (int filedes, int c) { if (__ungetc_buf == 0) __ungetc_init (); __ungetc_buf[filedes] = c; } int fdgetc (int fd) { if (__ungetc_buf == 0) __ungetc_init (); char c; int i = __ungetc_buf[fd]; if (i >= 0) __ungetc_buf[fd] = -1; else { int r = read (fd, &c, 1); if (r < 1) return -1; i = c; } if (i < 0) i = i + 256; return i; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> int getchar () { return fdgetc (__stdin); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdio.h> int putchar (int c) { char *p = cast_intp_to_charp (&c); write (__stdout, p, 1); return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <mes/lib.h> #include <fcntl.h> #include <stdarg.h> int open (char *file_name, int flags, int mask) { int r = _sys_call3 (SYS_open, file_name, flags, mask); if (r > 2) __ungetc_clear (r); return r; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ int mes_open (char *file_name, int flags, int mask) { int filedes = open (file_name, flags, mask); if (filedes > 2) __ungetc_clear (filedes); return filedes; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> size_t strlen (char const *s) { int i = 0; while (s[i] != 0) i = i + 1; return i; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib-mini.h> #include <string.h> int eputs (char const *s) { int i = strlen (s); write (__stderr, s, i); return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> int fdputc (int c, int fd) { char *p = cast_intp_to_charp (&c); write (fd, p, 1); return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> int eputc (int c) { return fdputc (c, __stderr); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2022,2023 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __M2_TYPES_H #define __M2_TYPES_H 1 /* #ifndef __MES_CLOCK_T #define __MES_CLOCK_T #undef clock_t typedef long clock_t; #endif */ #ifndef __MES_DEV_T #define __MES_DEV_T #undef dev_t typedef long dev_t; #endif /* #if !defined (__MES_FILE_T) && ! defined (_FILE_T) #define __MES_FILE_T #define _FILE_T typedef long FILE; #endif */ #ifndef __MES_GID_T #define __MES_GID_T #undef gid_t typedef unsigned gid_t; #endif #ifndef __MES_INO_T #define __MES_INO_T #undef ino_t typedef unsigned ino_t; #endif #if __SIZEOF_LONG_LONG__ == 8 #ifndef __MES_INO64_T #define __MES_INO64_T #undef ino64_t typedef unsigned ino64_t; #endif #endif // __SIZEOF_LONG_LONG__ == 8 #if !defined (__MES_INTPTR_T) && !defined (__intptr_t_defined) #define __MES_INTPTR_T #define __intptr_t_defined #undef intptr_t typedef long intptr_t; #undef uintptr_t typedef unsigned uintptr_t; #endif #ifndef __MES_OFF_T #define __MES_OFF_T #undef off_t typedef long off_t; #endif #if __SIZEOF_LONG_LONG__ == 8 #ifndef __MES_OFF64_T #define __MES_OFF64_T #undef off64_t typedef unsigned off64_t; #endif #endif // __SIZEOF_LONG_LONG__ == 8 #ifndef __MES_PID_T #define __MES_PID_T #undef pid_t typedef int pid_t; #endif #ifndef __PTRDIFF_T #define __PTRDIFF_T #ifndef __MES_PTRDIFF_T #define __MES_PTRDIFF_T #undef ptrdiff_t typedef long ptrdiff_t; #endif #endif #ifndef __MES_SIGVAL_T #define __MES_SIGVAL_T #undef clock_t typedef long sigval_t; #endif #ifndef __SIZE_T #define __SIZE_T #ifndef __MES_SIZE_T #define __MES_SIZE_T typedef unsigned size_t; #endif #endif #ifndef __MES_SSIZE_T #define __MES_SSIZE_T #undef ssize_t typedef long ssize_t; #endif #ifndef __MES_UID_T #define __MES_UID_T #undef uid_t typedef unsigned uid_t; #endif #ifndef __WCHAR_T #define __WCHAR_T #ifndef __MES_WCHAR_T #define __MES_WCHAR_T #undef wchar_t typedef int wchar_t; #endif #endif #endif // __M2_TYPES_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020,2021 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_MES_H #define __MES_MES_H #include <sys/types.h> #include "mes/cc.h" struct scm { long type; union { struct scm *car; long car_value; char *bytes; long length; struct scm *ref; struct scm *variable; struct scm *macro; long port; }; union { struct scm *cdr; long cdr_value; struct scm *closure; struct scm *continuation; char *name; struct scm *string; struct scm *structure; long value; FUNCTION function; struct scm *vector; }; }; /* mes */ extern char *g_datadir; extern int g_debug; extern char *g_buf; extern int g_continuations; extern struct scm *g_symbols; extern struct scm *g_symbol_max; extern int g_mini; /* a/env */ extern struct scm *R0; /* param 1 */ extern struct scm *R1; /* save 2 */ extern struct scm *R2; /* continuation */ extern struct scm *R3; /* current-module */ extern struct scm *M0; /* macro */ extern struct scm *g_macros; extern struct scm *g_ports; /* gc */ extern size_t ARENA_SIZE; extern size_t MAX_ARENA_SIZE; extern size_t STACK_SIZE; extern size_t JAM_SIZE; extern size_t GC_SAFETY; extern size_t MAX_STRING; extern char *g_arena; extern struct scm *cell_arena; extern struct scm *cell_zero; extern struct scm *g_free; extern struct scm *g_symbol; extern struct scm **g_stack_array; extern struct scm *g_cells; extern struct scm *g_news; extern long g_stack; extern size_t gc_count; extern struct timespec *gc_start_time; extern struct timespec *gc_end_time; extern size_t gc_time; extern char **__execl_c_argv; extern char *__open_boot_buf; extern char *__open_boot_file_name; extern char *__setenv_buf; extern char *__reader_read_char_buf; extern struct timespec *g_start_time; extern struct timeval *__gettimeofday_time; extern struct timespec *__get_internal_run_time_ts; struct scm *cast_charp_to_scmp (char const *i); struct scm **cast_charp_to_scmpp (char const *i); char *cast_voidp_to_charp (void const *i); long cast_scmp_to_long (struct scm *i); char *cast_scmp_to_charp (struct scm *i); struct scm *alloc (long n); struct scm *apply (struct scm *f, struct scm *x, struct scm *a); struct scm *apply_builtin (struct scm *fn, struct scm *x); struct scm *apply_builtin0 (struct scm *fn); struct scm *apply_builtin1 (struct scm *fn, struct scm *x); struct scm *apply_builtin2 (struct scm *fn, struct scm *x, struct scm *y); struct scm *apply_builtin3 (struct scm *fn, struct scm *x, struct scm *y, struct scm *z); struct scm *builtin_name (struct scm *builtin); struct scm *cstring_to_list (char const *s); struct scm *cstring_to_symbol (char const *s); struct scm *cell_ref (struct scm *cell, long index); struct scm *fdisplay_ (struct scm *, int, int); struct scm *init_symbols (); struct scm *init_time (struct scm *a); struct scm *make_builtin_type (); struct scm *make_bytes (char const *s, size_t length); struct scm *make_cell (long type, struct scm *car, struct scm *cdr); struct scm *make_pointer_cell (long type, long car, void *cdr); struct scm *make_value_cell (long type, long car, long cdr); struct scm *make_char (int n); struct scm *make_continuation (long n); struct scm *make_hash_table_ (long size); struct scm *make_hashq_type (); struct scm *make_initial_module (struct scm *a); struct scm *make_macro (struct scm *name, struct scm *x); struct scm *make_number (long n); struct scm *make_ref (struct scm *x); struct scm *make_string (char const *s, size_t length); struct scm *make_string0 (char const *s); struct scm *make_string_port (struct scm *x); struct scm *make_vector_ (long k, struct scm *e); struct scm *mes_builtins (struct scm *a); struct scm *push_cc (struct scm *p1, struct scm *p2, struct scm *a, struct scm *c); struct scm *struct_ref_ (struct scm *x, long i); struct scm *struct_set_x_ (struct scm *x, long i, struct scm *e); struct scm *vector_ref_ (struct scm *x, long i); struct scm *vector_set_x_ (struct scm *x, long i, struct scm *e); FUNCTION builtin_function (struct scm *builtin); char *cell_bytes (struct scm *x); int peekchar (); int readchar (); int unreadchar (); long gc_free (); long length__ (struct scm *x); size_t bytes_cells (size_t length); void assert_max_string (size_t i, char const *msg, char const *string); void assert_msg (int check, char *msg); void assert_number (char const *name, struct scm *x); void copy_cell (struct scm *to, struct scm *from); void gc_ (); void gc_dump_arena (struct scm *cells, long size); void gc_init (); void gc_peek_frame (); void gc_pop_frame (); void gc_push_frame (); void gc_stats_ (char const* where); void init_symbols_ (); long seconds_and_nanoseconds_to_long (long s, long ns); #include "mes/builtins.h" #include "mes/constants.h" #include "mes/symbols.h" #endif /* __MES_MES_H */
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_BUILTINS_H #define __MES_BUILTINS_H /* src/builtins.c */ struct scm *make_builtin (struct scm *builtin_type, struct scm *name, struct scm *arity, struct scm *function); struct scm *builtin_name (struct scm *builtin); struct scm *builtin_arity (struct scm *builtin); struct scm *builtin_p (struct scm *x); struct scm *builtin_printer (struct scm *builtin); /* src/core.c */ struct scm *car (struct scm *x); struct scm *cdr (struct scm *x); struct scm *list (struct scm *x); struct scm *null_p (struct scm *x); struct scm *eq_p (struct scm *x, struct scm *y); struct scm *values (struct scm *x); struct scm *acons (struct scm *key, struct scm *value, struct scm *alist); struct scm *length (struct scm *x); struct scm *error (struct scm *key, struct scm *x); struct scm *append2 (struct scm *x, struct scm *y); struct scm *append_reverse (struct scm *x, struct scm *y); struct scm *reverse_x_ (struct scm *x, struct scm *t); struct scm *assq (struct scm *x, struct scm *a); struct scm *assoc (struct scm *x, struct scm *a); /* src/display.c */ struct scm *display_ (struct scm *x); struct scm *display_error_ (struct scm *x); struct scm *display_port_ (struct scm *x, struct scm *p); struct scm *write_ (struct scm *x); struct scm *write_error_ (struct scm *x); struct scm *write_port_ (struct scm *x, struct scm *p); /* src/eval-apply.c */ struct scm *pairlis (struct scm *x, struct scm *y, struct scm *a); struct scm *set_car_x (struct scm *x, struct scm *e); struct scm *set_cdr_x (struct scm *x, struct scm *e); struct scm *set_env_x (struct scm *x, struct scm *e, struct scm *a); struct scm *add_formals (struct scm *formals, struct scm *x); struct scm *eval_apply (); /* src/gc.c */ struct scm *gc_stats (); struct scm *cons (struct scm *x, struct scm *y); struct scm *gc_check (); struct scm *gc (); /* src/hash.c */ struct scm *hashq (struct scm *x, struct scm *size); struct scm *hash (struct scm *x, struct scm *size); struct scm *hashq_get_handle (struct scm *table, struct scm *key, struct scm *dflt); struct scm *hashq_ref (struct scm *table, struct scm *key, struct scm *dflt); struct scm *hash_ref (struct scm *table, struct scm *key, struct scm *dflt); struct scm *hashq_set_x (struct scm *table, struct scm *key, struct scm *value); struct scm *hash_set_x (struct scm *table, struct scm *key, struct scm *value); struct scm *hash_table_printer (struct scm *table); struct scm *make_hash_table (struct scm *x); /* src/lib.c */ struct scm *type_ (struct scm *x); struct scm *car_ (struct scm *x); struct scm *cdr_ (struct scm *x); struct scm *xassq (struct scm *x, struct scm *a); struct scm *memq (struct scm *x, struct scm *a); struct scm *equal2_p (struct scm *a, struct scm *b); struct scm *last_pair (struct scm *x); struct scm *pair_p (struct scm *x); struct scm *char_to_integer (struct scm *x); struct scm *integer_to_char (struct scm *x); /* src/math.c */ struct scm *greater_p (struct scm *x); struct scm *less_p (struct scm *x); struct scm *is_p (struct scm *x); struct scm *minus (struct scm *x); struct scm *plus (struct scm *x); struct scm *divide (struct scm *x); struct scm *modulo (struct scm *a, struct scm *b); struct scm *multiply (struct scm *x); struct scm *logand (struct scm *x); struct scm *logior (struct scm *x); struct scm *lognot (struct scm *x); struct scm *logxor (struct scm *x); struct scm *ash (struct scm *n, struct scm *count); /* src/module.c */ struct scm *make_module_type (); struct scm *module_printer (struct scm *module); struct scm *module_variable (struct scm *module, struct scm *name); struct scm *module_ref (struct scm *module, struct scm *name); struct scm *module_define_x (struct scm *module, struct scm *name, struct scm *value); /* src/posix.c */ struct scm *abort_ (); struct scm *exit_ (struct scm *x); struct scm *peek_byte (); struct scm *read_byte (); struct scm *unread_byte (struct scm *i); struct scm *peek_char (); struct scm *read_char (struct scm *port); struct scm *unread_char (struct scm *i); struct scm *write_char (struct scm *i); struct scm *write_byte (struct scm *x); struct scm *getenv_ (struct scm *s); struct scm *setenv_ (struct scm *s, struct scm *v); struct scm *access_p (struct scm *file_name, struct scm *mode); struct scm *current_input_port (); struct scm *open_input_file (struct scm *file_name); struct scm *open_input_string (struct scm *string); struct scm *set_current_input_port (struct scm *port); struct scm *current_output_port (); struct scm *current_error_port (); struct scm *open_output_file (struct scm *x); struct scm *set_current_output_port (struct scm *port); struct scm *set_current_error_port (struct scm *port); struct scm *chmod_ (struct scm *file_name, struct scm *mode); struct scm *isatty_p (struct scm *port); struct scm *primitive_fork (); struct scm *execl_ (struct scm *file_name, struct scm *args); struct scm *waitpid_ (struct scm *pid, struct scm *options); struct scm *current_time (); struct scm *gettimeofday_ (); struct scm *get_internal_run_time (); struct scm *getcwd_ (); struct scm *dup_ (struct scm *port); struct scm *dup2_ (struct scm *old, struct scm *new); struct scm *delete_file (struct scm *file_name); /* src/reader.c */ struct scm *read_input_file_env_ (struct scm *e, struct scm *a); struct scm *read_input_file_env (struct scm *a); struct scm *read_env (struct scm *a); struct scm *reader_read_sexp (struct scm *c, struct scm *s, struct scm *a); struct scm *reader_read_character (); struct scm *reader_read_binary (); struct scm *reader_read_octal (); struct scm *reader_read_hex (); struct scm *reader_read_string (); /* src/stack.c */ struct scm *frame_printer (struct scm *frame); struct scm *make_stack (struct scm *stack); struct scm *stack_length (struct scm *stack); struct scm *stack_ref (struct scm *stack, struct scm *index); /* src/string.c */ struct scm *string_equal_p (struct scm *a, struct scm *b); struct scm *symbol_to_string (struct scm *symbol); struct scm *symbol_to_keyword (struct scm *symbol); struct scm *keyword_to_string (struct scm *keyword); struct scm *string_to_symbol (struct scm *string); struct scm *make_symbol (struct scm *string); struct scm *string_to_list (struct scm *string); struct scm *list_to_string (struct scm *list); struct scm *read_string (struct scm *port); struct scm *string_append (struct scm *x); struct scm *string_length (struct scm *string); struct scm *string_ref (struct scm *str, struct scm *k); /* src/struct.c */ struct scm *make_struct (struct scm *type, struct scm *fields, struct scm *printer); struct scm *struct_length (struct scm *x); struct scm *struct_ref (struct scm *x, struct scm *i); struct scm *struct_set_x (struct scm *x, struct scm *i, struct scm *e); /* src/vector.c */ struct scm *make_vector (struct scm *x); struct scm *vector_length (struct scm *x); struct scm *vector_ref (struct scm *x, struct scm *i); struct scm *vector_entry (struct scm *x); struct scm *vector_set_x (struct scm *x, struct scm *i, struct scm *e); struct scm *list_to_vector (struct scm *x); struct scm *vector_to_list (struct scm *v); #endif /* __MES_BUILTINS_H */
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_CONSTANTS_H #define __MES_CONSTANTS_H /* Cell types */ // CONSTANT TCHAR 0 #define TCHAR 0 // CONSTANT TBYTES 1 #define TBYTES 1 // CONSTANT TCLOSURE 2 #define TCLOSURE 2 // CONSTANT TCONTINUATION 3 #define TCONTINUATION 3 // CONSTANT TKEYWORD 4 #define TKEYWORD 4 // CONSTANT TMACRO 5 #define TMACRO 5 // CONSTANT TNUMBER 6 #define TNUMBER 6 // CONSTANT TPAIR 7 #define TPAIR 7 // CONSTANT TPORT 8 #define TPORT 8 // CONSTANT TREF 9 #define TREF 9 // CONSTANT TSPECIAL 10 #define TSPECIAL 10 // CONSTANT TSTRING 11 #define TSTRING 11 // CONSTANT TSTRUCT 12 #define TSTRUCT 12 // CONSTANT TSYMBOL 13 #define TSYMBOL 13 // CONSTANT TVALUES 14 #define TVALUES 14 // CONSTANT TVARIABLE 15 #define TVARIABLE 15 // CONSTANT TVECTOR 16 #define TVECTOR 16 // CONSTANT TBROKEN_HEART 17 #define TBROKEN_HEART 17 /* Struct types */ // CONSTANT STRUCT_TYPE 0 #define STRUCT_TYPE 0 // CONSTANT STRUCT_PRINTER 1 #define STRUCT_PRINTER 1 // CONSTANT GC_FRAME_SIZE 5 #define GC_FRAME_SIZE 5 // CONSTANT GC_FRAME_PROCEDURE 4 #define GC_FRAME_PROCEDURE 4 // CONSTANT STDIN 0 // CONSTANT STDOUT 1 // CONSTANT STDERR 2 /* Unknown type 1 // CONSTANT EOF -1 */ // CONSTANT O_RDONLY 0 // CONSTANT O_WRONLY 1 // CONSTANT O_CREAT 0x40 // CONSTANT O_TRUNC 0x200 // CONSTANT PATH_MAX 1024 // CONSTANT __FILEDES_MAX 512 // CONSTANT S_IRUSR 00400 // CONSTANT S_IWUSR 00200 // CONSTANT CLOCK_PROCESS_CPUTIME_ID 2 #endif /* __MES_CONSTANTS_H */
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2021 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_SYMBOLS_H #define __MES_SYMBOLS_H extern struct scm *cell_nil; extern struct scm *cell_f; extern struct scm *cell_t; extern struct scm *cell_dot; extern struct scm *cell_arrow; extern struct scm *cell_undefined; extern struct scm *cell_unspecified; extern struct scm *cell_closure; extern struct scm *cell_circular; extern struct scm *cell_vm_apply; extern struct scm *cell_vm_apply2; extern struct scm *cell_vm_begin; extern struct scm *cell_vm_begin_eval; extern struct scm *cell_vm_begin_expand; extern struct scm *cell_vm_begin_expand_eval; extern struct scm *cell_vm_begin_expand_macro; extern struct scm *cell_vm_begin_expand_primitive_load; extern struct scm *cell_vm_begin_primitive_load; extern struct scm *cell_vm_begin_read_input_file; extern struct scm *cell_vm_call_with_current_continuation2; extern struct scm *cell_vm_call_with_values2; extern struct scm *cell_vm_eval; extern struct scm *cell_vm_eval2; extern struct scm *cell_vm_eval_check_func; extern struct scm *cell_vm_eval_define; extern struct scm *cell_vm_eval_macro_expand_eval; extern struct scm *cell_vm_eval_macro_expand_expand; extern struct scm *cell_vm_eval_pmatch_car; extern struct scm *cell_vm_eval_pmatch_cdr; extern struct scm *cell_vm_eval_set_x; extern struct scm *cell_vm_evlis; extern struct scm *cell_vm_evlis2; extern struct scm *cell_vm_evlis3; extern struct scm *cell_vm_if; extern struct scm *cell_vm_if_expr; extern struct scm *cell_vm_macro_expand; extern struct scm *cell_vm_macro_expand_car; extern struct scm *cell_vm_macro_expand_cdr; extern struct scm *cell_vm_macro_expand_define; extern struct scm *cell_vm_macro_expand_define_macro; extern struct scm *cell_vm_macro_expand_lambda; extern struct scm *cell_vm_macro_expand_set_x; extern struct scm *cell_vm_return; extern struct scm *cell_symbol_lambda; extern struct scm *cell_symbol_begin; extern struct scm *cell_symbol_if; extern struct scm *cell_symbol_quote; extern struct scm *cell_symbol_define; extern struct scm *cell_symbol_define_macro; extern struct scm *cell_symbol_quasiquote; extern struct scm *cell_symbol_unquote; extern struct scm *cell_symbol_unquote_splicing; extern struct scm *cell_symbol_syntax; extern struct scm *cell_symbol_quasisyntax; extern struct scm *cell_symbol_unsyntax; extern struct scm *cell_symbol_unsyntax_splicing; extern struct scm *cell_symbol_set_x; extern struct scm *cell_symbol_sc_expand; extern struct scm *cell_symbol_macro_expand; extern struct scm *cell_symbol_portable_macro_expand; extern struct scm *cell_symbol_sc_expander_alist; extern struct scm *cell_symbol_call_with_values; extern struct scm *cell_symbol_call_with_current_continuation; extern struct scm *cell_symbol_boot_module; extern struct scm *cell_symbol_current_module; extern struct scm *cell_symbol_primitive_load; extern struct scm *cell_symbol_car; extern struct scm *cell_symbol_cdr; extern struct scm *cell_symbol_not_a_number; extern struct scm *cell_symbol_not_a_pair; extern struct scm *cell_symbol_system_error; extern struct scm *cell_symbol_throw; extern struct scm *cell_symbol_unbound_variable; extern struct scm *cell_symbol_wrong_number_of_args; extern struct scm *cell_symbol_wrong_type_arg; extern struct scm *cell_symbol_buckets; extern struct scm *cell_symbol_builtin; extern struct scm *cell_symbol_frame; extern struct scm *cell_symbol_hashq_table; extern struct scm *cell_symbol_module; extern struct scm *cell_symbol_procedure; extern struct scm *cell_symbol_record_type; extern struct scm *cell_symbol_size; extern struct scm *cell_symbol_stack; extern struct scm *cell_symbol_argv; extern struct scm *cell_symbol_mes_datadir; extern struct scm *cell_symbol_mes_version; extern struct scm *cell_symbol_internal_time_units_per_second; extern struct scm *cell_symbol_compiler; extern struct scm *cell_symbol_arch; extern struct scm *cell_symbol_pmatch_car; extern struct scm *cell_symbol_pmatch_cdr; extern struct scm *cell_type_bytes; extern struct scm *cell_type_char; extern struct scm *cell_type_closure; extern struct scm *cell_type_continuation; extern struct scm *cell_type_function; extern struct scm *cell_type_keyword; extern struct scm *cell_type_macro; extern struct scm *cell_type_number; extern struct scm *cell_type_pair; extern struct scm *cell_type_port; extern struct scm *cell_type_ref; extern struct scm *cell_type_special; extern struct scm *cell_type_string; extern struct scm *cell_type_struct; extern struct scm *cell_type_symbol; extern struct scm *cell_type_values; extern struct scm *cell_type_variable; extern struct scm *cell_type_vector; extern struct scm *cell_type_broken_heart; extern struct scm *cell_symbol_program; extern struct scm *cell_symbol_test; // CONSTANT SYMBOL_MAX 114 #define SYMBOL_MAX 114 // CONSTANT CELL_UNSPECIFIED 7 #define CELL_UNSPECIFIED 7 // CONSTANT CELL_SYMBOL_RECORD_TYPE 82 #define CELL_SYMBOL_RECORD_TYPE 82 #endif /* __MES_SYMBOLS_H */
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <assert.h> void __assert_fail (char const *msg, char const *file, unsigned line, char const *function) { if (file && file[0]) { eputs (file); eputs (":"); } if (line) { eputs (itoa (line)); eputs (":"); } if (function && function[0]) { eputs (function); eputs (":"); } eputs ("assert fail: "); eputs (msg); eputs ("\n"); char *fail = 0; fail[0] = 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2019 Jeremiah Orians <jeremiah@pdp10.guru> * Copyright © 2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <assert.h> #include <stdlib.h> void assert_msg (int bool, char *msg) { if (bool == 0) __assert_fail (msg, 0, 0, 0); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> int strncmp (char const *a, char const *b, size_t size) { if (size == 0) return 0; while (a[0] != 0 && b[0] != 0 && a[0] == b[0] && size > 1) { size = size - 1; a = a + 1; b = b + 1; } return a[0] - b[0]; }
/* * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <string.h> #include <stdlib.h> // CONSTANT M2_PTR_SIZE 4 #define M2_PTR_SIZE 1 char * getenv (char const *s) { /* eputs ("\ngetenv s="); eputs (s); eputs ("\n"); */ char **p = environ; char *q; int length = strlen (s); while (p[0] != 0) { /* eputs ("getenv p[0]="); eputs (p[0]); eputs ("\n"); */ if (strncmp (s, p[0], length) == 0) { /* eputs ("found!\n"); */ q = p[0] + length; if (q[0] == '=') return q + 1; } /* else */ /* eputs ("not found!\n"); */ p = p + M2_PTR_SIZE; } return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <string.h> int fdputs (char const *s, int fd) { int i = strlen (s); write (fd, s, i); return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <assert.h> #include <stdlib.h> #include <string.h> #if __M2_PLANET__ || (!(__MESC__ && __arm__) && !(__TINYC__ && __arm__ && BOOTSTRAP)) size_t __mesabi_uldiv (size_t a, size_t b, size_t *remainder) { remainder[0] = a % b; return a / b; } #endif char *__itoa_buf; char * ntoab (long x, unsigned base, int signed_p) { if (__itoa_buf == 0) __itoa_buf = malloc (20); char *p = __itoa_buf + 11; p[0] = 0; p = p - 1; assert_msg (base > 0, "base > 0"); int sign_p = 0; size_t i; size_t u; size_t b = base; if (signed_p != 0 && x < 0) { sign_p = 1; /* Avoid LONG_MIN */ u = (-(x + 1)); u = u + 1; } else u = x; do { u = __mesabi_uldiv (u, b, &i); if (i > 9) p[0] = 'a' + i - 10; else p[0] = '0' + i; p = p - 1; } while (u != 0); if (sign_p && p[1] != '0') { p[0] = '-'; p = p - 1; } return p + 1; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * Copyright © 2023 Rick Masters <grick23@gmail.com> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> int isdigit (int c) { return c >= '0' && c <= '9'; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <ctype.h> int isxdigit (int c) { return isdigit (c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> int isspace (int c) { return (c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r' || c == ' '); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <ctype.h> int isnumber (int c, int base) { if (base == 2) return (c >= '0') && (c <= '1'); if (base == 8) return (c >= '0') && (c <= '7'); if (base == 10) return isdigit (c); if (base == 16) return isxdigit (c); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * Copyright © 2022,2023 Rick Masters <grick23@gmail.com> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <ctype.h> long abtol (char const **p, int base) { char const *s = p[0]; int i = 0; int sign_p = 0; int m = '0'; if (base == 0) base = 10; while (isspace (s[0]) != 0) s = s + 1; if (s[0] != 0 && s[0] == '+') s = s + 1; if (s[0] != 0 && s[0] == '-') { sign_p = 1; s = s + 1; } while (isnumber (s[0], base) != 0) { i = i * base; if (s[0] >= 'a') m = 'a' - 10; else { if (s[0] >= 'A') m = 'A' - 10; else m = '0'; } i = i + s[0] - m; s = s + 1; } p[0] = s; if (sign_p != 0) return -i; return i; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> int atoi (char const *string) { char const *p = string; return abtol (&p, 0); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <string.h> char * _memcpy (char *dest, char const *src, size_t n) { char *p = dest; while (n != 0) { n = n - 1; dest[0] = src[0]; dest = dest + 1; src = src + 1; } return p; } void * memcpy (void *dest, void const *src, size_t n) { return _memcpy (dest, src, n); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> void free (void *ptr) { }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> #include <string.h> void * realloc (void *ptr, size_t size) { void *new = malloc (size); if (ptr != 0 && new != 0) { memcpy (new, ptr, size); free (ptr); } return new; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> char * strcpy (char *dest, char const *src) { char *p = dest; while (src[0] != 0) { p[0] = src[0]; p = p + 1; src = src + 1; } p[0] = 0; return dest; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> char * itoa (int x) { return ntoab (x, 10, 1); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> char * ltoa (long x) { return ntoab (x, 10, 1); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> int fdungetc (int c, int fd) { if (c == -1) return c; else if (__ungetc_p (fd)) { eputs (" ***MES C LIB*** fdungetc ungetc buffer overflow fd="); eputs (itoa (fd)); eputs ("\n"); assert_msg (0, "0"); } __ungetc_set (fd, c); return c; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <string.h> #include <stdlib.h> // CONSTANT M2_PTR_SIZE 4 #define M2_PTR_SIZE 1 int setenv (char const *s, char const *v, int overwrite_p) { char **p = environ; int length = strlen (s); char* q; while (p[0] != 0) { if (strncmp (s, p[0], length) == 0) { q = p[0] + length; if (q[0] == '=') break; } p = p + M2_PTR_SIZE; } char *entry = malloc (length + strlen (v) + 2); int end_p = p[0] == 0; p[0] = entry; strcpy (entry, s); strcpy (entry + length, "="); strcpy (entry + length + 1, v); entry[length + strlen (v) + 2] = 0; if (end_p != 0) p[1] = 0; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <linux/syscall.h> #include <arch/syscall.h> int access (char const *file_name, int how) { long long_file_name = cast_charp_to_long (file_name); long long_how = cast_int_to_long (how); return _sys_call2 (SYS_access, long_file_name, long_how); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __LINUX_M2_KERNEL_STAT_H #define __LINUX_M2_KERNEL_STAT_H /* https://github.com/torvalds/linux/blob/master/arch/x86/include/uapi/asm/stat.h */ /* *INDENT-OFF* */ struct stat { unsigned st_dev; unsigned st_ino; char st_mode[2]; char st_nlink[2]; char st_uid[2]; char st_gid[2]; unsigned st_rdev; unsigned st_size; unsigned st_blksize; unsigned st_blocks; unsigned st_atime; unsigned st_atime_usec; unsigned st_mtime; unsigned st_mtime_usec; unsigned st_ctime; unsigned st_ctime_usec; unsigned __pad0; unsigned __pad1; }; #endif /* __LINUX_M2_KERNEL_STAT_H */
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * Copyright © 2022 Dor Askayo <dor.askayo@gmail.com> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_SYS_STAT_H #define __MES_SYS_STAT_H 1 #if SYSTEM_LIBC #undef __MES_SYS_STAT_H #include_next <sys/stat.h> #else // ! SYSTEM_LIBC #include <time.h> #include <sys/types.h> #include <arch/kernel-stat.h> #ifndef __MES_MODE_T #define __MES_MODE_T typedef int mode_t; #endif int chmod (char const *file_name, mode_t mode); int fstat (int filedes, struct stat *buf); int mkdir (char const *file_name, mode_t mode); int mknod (char const *file_name, mode_t mode, dev_t dev); int chown (char const *file_name, uid_t owner, gid_t group); int rmdir (char const *file_name); int stat (char const *file_name, struct stat *buf); #define S_IFIFO 0010000 #define S_IFCHR 0020000 #define S_IFDIR 0040000 #define S_IFBLK 0060000 #define S_IFREG 0100000 #define S_IFLNK 0120000 #define S_IFMT 0170000 #define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #define S_IRWXU 00700 #define S_IXUSR 00100 #define S_IWUSR 00200 #define S_IRUSR 00400 #define S_ISUID 04000 #define S_ISGID 02000 #define S_IXGRP 00010 #define S_IXOTH 00001 #define S_IRGRP 00040 #define S_IROTH 00004 #define S_IWGRP 00020 #define S_IWOTH 00002 #define S_IRWXG 00070 #define S_IRWXO 00007 #endif // ! SYSTEM_LIBC #endif // __MES_SYS_STAT_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <syscall.h> #include <sys/stat.h> int chmod (char const *file_name, int mask) { long long_file_name = file_name; long long_mask = mask; return _sys_call2 (SYS_chmod, long_file_name, long_mask); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <mes/lib.h> int ioctl3 (int filedes, size_t command, long data) { long long_filedes = cast_int_to_long (filedes); int r = _sys_call3 (SYS_ioctl, long_filedes, command, data); return r; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <sys/ioctl.h> #include <stdlib.h> #include <string.h> #include <termio.h> // CONSTANT TCGETS 0x5401 #define TCGETS 0x5401 struct ktermios { unsigned c_iflag; unsigned c_oflag; unsigned c_cflag; unsigned c_lflag; char c_line; char c_cc[19]; unsigned c_ispeed; unsigned c_ospeed; }; struct ktermios *__isatty_kernel_termios; int isatty (int filedes) { if (__isatty_kernel_termios == 0) __isatty_kernel_termios = malloc (sizeof (struct ktermios)); int r = ioctl3 (filedes, TCGETS, __isatty_kernel_termios); return r == 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> int fork () { return _sys_call (SYS_fork); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> int execve (char const *file_name, char **argv, char **env) { long long_file_name = file_name; long long_argv = argv; long long_env = env; return _sys_call3 (SYS_execve, file_name, argv, env); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ int execv (char *file_name, char **argv) { return execve (file_name, argv, environ); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <linux/syscall.h> #include <arch/syscall.h> #include <sys/types.h> int waitpid (int pid, int *status_ptr, int options) { long long_pid = pid; long long_status_ptr = cast_voidp_to_long (status_ptr); long long_options = options; //##if __i386__ //# return _sys_call3 (SYS_waitpid, long_pid, long_status_ptr, long_options); //##elif __x86_64__ || __arm__ return _sys_call4 (SYS_wait4, long_pid, long_status_ptr, long_options, 0); //##else //##error arch not supported //##endif }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <linux/syscall.h> #include <arch/syscall.h> #include <sys/time.h> int gettimeofday (struct timeval *tv, struct timezone *tz) { long long_tv = cast_voidp_to_long (tv); long long_tz = cast_voidp_to_long (tz); return _sys_call2 (SYS_gettimeofday, long_tv, long_tz); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <syscall.h> #include <time.h> int clock_gettime (long clk_id, struct timespec *tp) { long long_tp = tp; return _sys_call2 (SYS_clock_gettime, clk_id, tp); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022,2023 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <time.h> char *__tv; long time (long* result) { int r; if (__tv == 0) __tv = malloc (sizeof (struct timeval)); struct timeval *tv = __tv; r = _sys_call2 (SYS_gettimeofday, tv, 0); if (r != 0) return -1; if (result != 0) result[0] = tv->tv_sec; return tv->tv_sec; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <linux/syscall.h> #include <arch/syscall.h> char * _getcwd (char *buffer, size_t size) { long long_buffer = cast_charp_to_long (buffer); int r = _sys_call2 (SYS_getcwd, long_buffer, size); if (r >= 0) return buffer; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <mes/mes.h> #include <limits.h> #include <stdlib.h> #include <sys/types.h> // CONSTANT PATH_MAX 1024 char *__getcwd_buf; char * getcwd (char *buffer, int size) { if (buffer == 0) buffer = __getcwd_buf; if (buffer == 0) { __getcwd_buf = malloc (PATH_MAX); buffer = __getcwd_buf; } return _getcwd (buffer, size); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> int dup (int old) { long long_old = old; return _sys_call1 (SYS_dup, long_old); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> int dup2 (int old, int new) { long long_old = old; long long_new = new; return _sys_call2 (SYS_dup2, long_old, long_new); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> int strcmp (char const *a, char const *b) { while (a[0] != 0 && b[0] != 0 && a[0] == b[0]) { a = a + 1; b = b + 1; } return a[0] - b[0]; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> int memcmp (void const *s1, void const *s2, size_t size) { if (size == 0) return 0; char const *a = s1; char const *b = s2; while (a[0] == b[0] && size > 1) { size = size - 1; a = a + 1; b = b + 1; } return a[0] - b[0]; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <linux/syscall.h> #include <arch/syscall.h> int unlink (char const *file_name) { long long_file_name = cast_charp_to_long (file_name); return _sys_call1 (SYS_unlink, long_file_name); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib.h" #include "mes/mes.h" struct scm * make_builtin_type () /*:((internal)) */ { struct scm *record_type = cell_symbol_record_type; struct scm *fields = cell_nil; fields = cons (cstring_to_symbol ("address"), fields); fields = cons (cstring_to_symbol ("arity"), fields); fields = cons (cstring_to_symbol ("name"), fields); fields = cons (fields, cell_nil); fields = cons (cell_symbol_builtin, fields); return make_struct (record_type, fields, cell_unspecified); } struct scm * make_builtin (struct scm *builtin_type, struct scm *name, struct scm *arity, struct scm *function) { struct scm *values = cell_nil; values = cons (function, values); values = cons (arity, values); values = cons (name, values); values = cons (cell_symbol_builtin, values); return make_struct (builtin_type, values, cstring_to_symbol ("builtin-printer")); } struct scm * builtin_name (struct scm *builtin) { return struct_ref_ (builtin, 3); } struct scm * builtin_arity (struct scm *builtin) { return struct_ref_ (builtin, 4); } FUNCTION builtin_function (struct scm *builtin) { struct scm *x = struct_ref_ (builtin, 5); return x->function; } struct scm * builtin_p (struct scm *x) { if (x->type == TSTRUCT) if (struct_ref_ (x, 2) == cell_symbol_builtin) return cell_t; return cell_f; } struct scm * builtin_printer (struct scm *builtin) { fdputs ("#<procedure ", __stdout); display_ (builtin_name (builtin)); fdputc (' ', __stdout); struct scm *x = builtin_arity (builtin); int arity = x->value; if (arity == -1) fdputc ('_', __stdout); else { fdputc ('(', __stdout); int i; for (i = 0; i < arity; i = i + 1) { if (i != 0) fdputc (' ', __stdout); fdputc ('_', __stdout); } } fdputc ('>', __stdout); } struct scm * init_builtin (struct scm *builtin_type, char const *name, int arity, void* function, struct scm *a) { struct scm *s = cstring_to_symbol (name); long n = cast_voidp_to_long (function); return acons (s, make_builtin (builtin_type, symbol_to_string (s), make_number (arity), make_number (n)), a); } struct scm * mes_builtins (struct scm *a) /*:((internal)) */ { struct scm *builtin_type = make_builtin_type (); if (g_mini != 0) { a = init_builtin (builtin_type, "cons", 2, &cons, a); a = init_builtin (builtin_type, "car", 1, &car, a); a = init_builtin (builtin_type, "list", -1, &list, a); a = init_builtin (builtin_type, "eq?", 2, &eq_p, a); a = init_builtin (builtin_type, "-", -1, &minus, a); a = init_builtin (builtin_type, "+", -1, &plus, a); a = init_builtin (builtin_type, "core:display", 1, &display_, a); a = init_builtin (builtin_type, "core:write", 1, &write_, a); a = init_builtin (builtin_type, "core:display-error", 1, &display_error_, a); a = init_builtin (builtin_type, "getenv", 1, &getenv_, a); a = init_builtin (builtin_type, "gc", 0, &gc, a); a = init_builtin (builtin_type, ">", -1, &greater_p, a); a = init_builtin (builtin_type, "<", -1, &less_p, a); a = init_builtin (builtin_type, "make-vector", -1, &make_vector, a); return a; } /* src/builtins.c */ a = init_builtin (builtin_type, "make-builtin", 4, &make_builtin, a); a = init_builtin (builtin_type, "builtin-name", 1, &builtin_name, a); a = init_builtin (builtin_type, "builtin-arity", 1, &builtin_arity, a); a = init_builtin (builtin_type, "builtin?", 1, &builtin_p, a); a = init_builtin (builtin_type, "builtin-printer", 1, &builtin_printer, a); /* src/core.c */ a = init_builtin (builtin_type, "car", 1, &car, a); a = init_builtin (builtin_type, "cdr", 1, &cdr, a); a = init_builtin (builtin_type, "list", -1, &list, a); a = init_builtin (builtin_type, "null?", 1, &null_p, a); a = init_builtin (builtin_type, "eq?", 2, &eq_p, a); a = init_builtin (builtin_type, "values", -1, &values, a); a = init_builtin (builtin_type, "acons", 3, &acons, a); a = init_builtin (builtin_type, "length", 1, &length, a); a = init_builtin (builtin_type, "error", 2, &error, a); a = init_builtin (builtin_type, "append2", 2, &append2, a); a = init_builtin (builtin_type, "append-reverse", 2, &append_reverse, a); a = init_builtin (builtin_type, "core:reverse!", 2, &reverse_x_, a); a = init_builtin (builtin_type, "assq", 2, &assq, a); a = init_builtin (builtin_type, "assoc", 2, &assoc, a); /* src/display.c */ a = init_builtin (builtin_type, "core:display", 1, &display_, a); a = init_builtin (builtin_type, "core:display-error", 1, &display_error_, a); a = init_builtin (builtin_type, "core:display-port", 2, &display_port_, a); a = init_builtin (builtin_type, "core:write", 1, &write_, a); a = init_builtin (builtin_type, "core:write-error", 1, &write_error_, a); a = init_builtin (builtin_type, "core:write-port", 2, &write_port_, a); /* src/eval-apply.c */ a = init_builtin (builtin_type, "pairlis", 3, &pairlis, a); a = init_builtin (builtin_type, "set-car!", 2, &set_car_x, a); a = init_builtin (builtin_type, "set-cdr!", 2, &set_cdr_x, a); a = init_builtin (builtin_type, "set-env!", 3, &set_env_x, a); a = init_builtin (builtin_type, "add-formals", 2, &add_formals, a); a = init_builtin (builtin_type, "eval-apply", 0, &eval_apply, a); /* src/gc.c */ a = init_builtin (builtin_type, "gc-stats", 0, &gc_stats, a); a = init_builtin (builtin_type, "cons", 2, &cons, a); a = init_builtin (builtin_type, "gc-check", 0, &gc_check, a); a = init_builtin (builtin_type, "gc", 0, &gc, a); /* src/hash.c */ a = init_builtin (builtin_type, "hashq", 2, &hashq, a); a = init_builtin (builtin_type, "hash", 2, &hash, a); a = init_builtin (builtin_type, "hashq-get-handle", 3, &hashq_get_handle, a); a = init_builtin (builtin_type, "hashq-ref", 3, &hashq_ref, a); a = init_builtin (builtin_type, "hash-ref", 3, &hash_ref, a); a = init_builtin (builtin_type, "hashq-set!", 3, &hashq_set_x, a); a = init_builtin (builtin_type, "hash-set!", 3, &hash_set_x, a); a = init_builtin (builtin_type, "hash-table-printer", 1, &hash_table_printer, a); a = init_builtin (builtin_type, "make-hash-table", 1, &make_hash_table, a); /* src/lib.c */ a = init_builtin (builtin_type, "core:type", 1, &type_, a); a = init_builtin (builtin_type, "core:car", 1, &car_, a); a = init_builtin (builtin_type, "core:cdr", 1, &cdr_, a); a = init_builtin (builtin_type, "xassq", 2, &xassq, a); a = init_builtin (builtin_type, "memq", 2, &memq, a); a = init_builtin (builtin_type, "equal2?", 2, &equal2_p, a); a = init_builtin (builtin_type, "last-pair", 1, &last_pair, a); a = init_builtin (builtin_type, "pair?", 1, &pair_p, a); a = init_builtin (builtin_type, "char->integer", 1, &char_to_integer, a); a = init_builtin (builtin_type, "integer->char", 1, &integer_to_char, a); /* src/math.c */ a = init_builtin (builtin_type, ">", -1, &greater_p, a); a = init_builtin (builtin_type, "<", -1, &less_p, a); a = init_builtin (builtin_type, "=", -1, &is_p, a); a = init_builtin (builtin_type, "-", -1, &minus, a); a = init_builtin (builtin_type, "+", -1, &plus, a); a = init_builtin (builtin_type, "/", -1, ÷, a); a = init_builtin (builtin_type, "modulo", 2, &modulo, a); a = init_builtin (builtin_type, "*", -1, &multiply, a); a = init_builtin (builtin_type, "logand", -1, &logand, a); a = init_builtin (builtin_type, "logior", -1, &logior, a); a = init_builtin (builtin_type, "lognot", 1, &lognot, a); a = init_builtin (builtin_type, "logxor", -1, &logxor, a); a = init_builtin (builtin_type, "ash", 2, &ash, a); /* src/module.c */ a = init_builtin (builtin_type, "make-module-type", 0, &make_module_type, a); a = init_builtin (builtin_type, "module-printer", 1, &module_printer, a); a = init_builtin (builtin_type, "module-variable", 2, &module_variable, a); a = init_builtin (builtin_type, "module-ref", 2, &module_ref, a); a = init_builtin (builtin_type, "module-define!", 3, &module_define_x, a); /* src/posix.c */ a = init_builtin (builtin_type, "abort", 0, &abort_, a); a = init_builtin (builtin_type, "exit", 1, &exit_, a); a = init_builtin (builtin_type, "peek-byte", 0, &peek_byte, a); a = init_builtin (builtin_type, "read-byte", 0, &read_byte, a); a = init_builtin (builtin_type, "unread-byte", 1, &unread_byte, a); a = init_builtin (builtin_type, "peek-char", 0, &peek_char, a); a = init_builtin (builtin_type, "read-char", -1, &read_char, a); a = init_builtin (builtin_type, "unread-char", 1, &unread_char, a); a = init_builtin (builtin_type, "write-char", -1, &write_char, a); a = init_builtin (builtin_type, "write-byte", -1, &write_byte, a); a = init_builtin (builtin_type, "getenv", 1, &getenv_, a); a = init_builtin (builtin_type, "setenv", 2, &setenv_, a); a = init_builtin (builtin_type, "access?", 2, &access_p, a); a = init_builtin (builtin_type, "current-input-port", 0, ¤t_input_port, a); a = init_builtin (builtin_type, "open-input-file", 1, &open_input_file, a); a = init_builtin (builtin_type, "open-input-string", 1, &open_input_string, a); a = init_builtin (builtin_type, "set-current-input-port", 1, &set_current_input_port, a); a = init_builtin (builtin_type, "current-output-port", 0, ¤t_output_port, a); a = init_builtin (builtin_type, "current-error-port", 0, ¤t_error_port, a); a = init_builtin (builtin_type, "open-output-file", -1, &open_output_file, a); a = init_builtin (builtin_type, "set-current-output-port", 1, &set_current_output_port, a); a = init_builtin (builtin_type, "set-current-error-port", 1, &set_current_error_port, a); a = init_builtin (builtin_type, "chmod", 2, &chmod_, a); a = init_builtin (builtin_type, "isatty?", 1, &isatty_p, a); a = init_builtin (builtin_type, "primitive-fork", 0, &primitive_fork, a); a = init_builtin (builtin_type, "execl", 2, &execl_, a); a = init_builtin (builtin_type, "core:waitpid", 2, &waitpid_, a); a = init_builtin (builtin_type, "current-time", 0, ¤t_time, a); a = init_builtin (builtin_type, "gettimeofday", 0, &gettimeofday_, a); a = init_builtin (builtin_type, "get-internal-run-time", 0, &get_internal_run_time, a); a = init_builtin (builtin_type, "getcwd", 0, &getcwd_, a); a = init_builtin (builtin_type, "dup", 1, &dup_, a); a = init_builtin (builtin_type, "dup2", 2, &dup2_, a); a = init_builtin (builtin_type, "delete-file", 1, &delete_file, a); /* src/reader.c */ a = init_builtin (builtin_type, "core:read-input-file-env", 2, &read_input_file_env_, a); a = init_builtin (builtin_type, "read-input-file-env", 1, &read_input_file_env, a); a = init_builtin (builtin_type, "read-env", 1, &read_env, a); a = init_builtin (builtin_type, "reader-read-sexp", 3, &reader_read_sexp, a); a = init_builtin (builtin_type, "reader-read-character", 0, &reader_read_character, a); a = init_builtin (builtin_type, "reader-read-binary", 0, &reader_read_binary, a); a = init_builtin (builtin_type, "reader-read-octal", 0, &reader_read_octal, a); a = init_builtin (builtin_type, "reader-read-hex", 0, &reader_read_hex, a); a = init_builtin (builtin_type, "reader-read-string", 0, &reader_read_string, a); /* src/stack.c */ a = init_builtin (builtin_type, "frame-printer", 1, &frame_printer, a); a = init_builtin (builtin_type, "make-stack", -1, &make_stack, a); a = init_builtin (builtin_type, "stack-length", 1, &stack_length, a); a = init_builtin (builtin_type, "stack-ref", 2, &stack_ref, a); /* src/string.c */ a = init_builtin (builtin_type, "string=?", 2, &string_equal_p, a); a = init_builtin (builtin_type, "symbol->string", 1, &symbol_to_string, a); a = init_builtin (builtin_type, "symbol->keyword", 1, &symbol_to_keyword, a); a = init_builtin (builtin_type, "keyword->string", 1, &keyword_to_string, a); a = init_builtin (builtin_type, "string->symbol", 1, &string_to_symbol, a); a = init_builtin (builtin_type, "make-symbol", 1, &make_symbol, a); a = init_builtin (builtin_type, "string->list", 1, &string_to_list, a); a = init_builtin (builtin_type, "list->string", 1, &list_to_string, a); a = init_builtin (builtin_type, "read-string", -1, &read_string, a); a = init_builtin (builtin_type, "string-append", -1, &string_append, a); a = init_builtin (builtin_type, "string-length", 1, &string_length, a); a = init_builtin (builtin_type, "string-ref", 2, &string_ref, a); /* src/struct.c */ a = init_builtin (builtin_type, "make-struct", 3, &make_struct, a); a = init_builtin (builtin_type, "struct-length", 1, &struct_length, a); a = init_builtin (builtin_type, "struct-ref", 2, &struct_ref, a); a = init_builtin (builtin_type, "struct-set!", 3, &struct_set_x, a); /* src/vector.c */ a = init_builtin (builtin_type, "make-vector", -1, &make_vector, a); a = init_builtin (builtin_type, "vector-length", 1, &vector_length, a); a = init_builtin (builtin_type, "vector-ref", 2, &vector_ref, a); a = init_builtin (builtin_type, "vector-entry", 1, &vector_entry, a); a = init_builtin (builtin_type, "vector-set!", 3, &vector_set_x, a); a = init_builtin (builtin_type, "list->vector", 1, &list_to_vector, a); a = init_builtin (builtin_type, "vector->list", 1, &vector_to_list, a); return a; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ /** Commentary: Essential functions, used by the eval/apply core. */ /** Code: */ #include "mes/lib.h" #include "mes/mes.h" #include <stdlib.h> struct scm * assoc_string (struct scm *x, struct scm *a) /*:((internal)) */ { struct scm *b; while (a != cell_nil) { b = a->car; if (b->car->type == TSTRING) if (string_equal_p (x, b->car) == cell_t) return b; a = a->cdr; } if (a != cell_nil) return a->car; return cell_f; } struct scm * car (struct scm *x) { #if !__MESC_MES__ if (x->type != TPAIR) error (cell_symbol_not_a_pair, cons (x, cell_symbol_car)); #endif return x->car; } struct scm * cdr (struct scm *x) { #if !__MESC_MES__ if (x->type != TPAIR) error (cell_symbol_not_a_pair, cons (x, cell_symbol_cdr)); #endif return x->cdr; } struct scm * list (struct scm *x) /*:((arity . n)) */ { return x; } struct scm * null_p (struct scm *x) { if (x == cell_nil) return cell_t; return cell_f; } struct scm * eq_p (struct scm *x, struct scm *y) { if (x == y) return cell_t; int t = x->type; if (t == TKEYWORD) { if (y->type == TKEYWORD) return string_equal_p (x, y); return cell_f; } if (t == TCHAR) { if (y->type != TCHAR) return cell_f; if (x->value == y->value) return cell_t; return cell_f; } if (t == TNUMBER) { if (y->type != TNUMBER) return cell_f; if (x->value == y->value) return cell_t; return cell_f; } return cell_f; } struct scm * values (struct scm *x) /*:((arity . n)) */ { struct scm *v = cons (0, x); v->type = TVALUES; return v; } struct scm * acons (struct scm *key, struct scm *value, struct scm *alist) { return cons (cons (key, value), alist); } long length__ (struct scm *x) /*:((internal)) */ { long n = 0; while (x != cell_nil) { n = n + 1; if (x->type != TPAIR) return -1; x = x->cdr; } return n; } struct scm * length (struct scm *x) { return make_number (length__ (x)); } struct scm * error (struct scm *key, struct scm *x) { #if !__MESC_MES__ && !__M2_PLANET__ struct scm *throw = module_ref (R0, cell_symbol_throw); if (throw != cell_undefined) return apply (throw, cons (key, cons (x, cell_nil)), R0); #endif display_error_ (key); eputs (": "); write_error_ (x); eputs ("\n"); assert_msg (0, "ERROR"); exit (1); } struct scm * append2 (struct scm *x, struct scm *y) { if (x == cell_nil) return y; if (x->type != TPAIR) error (cell_symbol_not_a_pair, cons (x, cstring_to_symbol ("append2"))); struct scm *r = cell_nil; while (x != cell_nil) { r = cons (x->car, r); x = x->cdr; } return reverse_x_ (r, y); } struct scm * append_reverse (struct scm *x, struct scm *y) { if (x == cell_nil) return y; if (x->type != TPAIR) error (cell_symbol_not_a_pair, cons (x, cstring_to_symbol ("append-reverse"))); while (x != cell_nil) { y = cons (x->car, y); x = x->cdr; } return y; } struct scm * reverse_x_ (struct scm *x, struct scm *t) { if (x != cell_nil && x->type != TPAIR) error (cell_symbol_not_a_pair, cons (x, cstring_to_symbol ("core:reverse!"))); struct scm *r = t; while (x != cell_nil) { t = x->cdr; x->cdr = r; r = x; x = t; } return r; } struct scm * assq (struct scm *x, struct scm *a) { if (a->type != TPAIR) return cell_f; int t = x->type; if (t == TSYMBOL || t == TSPECIAL) while (a != cell_nil) { if (x == a->car->car) return a->car; a = a->cdr; } else if (t == TCHAR || t == TNUMBER) { long v = x->value; while (a != cell_nil) { if (v == a->car->car->value) return a->car; a = a->cdr; } } else if (t == TKEYWORD) { while (a != cell_nil) { if (string_equal_p (x, a->car->car) == cell_t) return a->car; a = a->cdr; } } else /* pointer equality, e.g. on strings. */ while (a != cell_nil) { if (x == a->car->car) return a->car; a = a->cdr; } return cell_f; } struct scm * assoc (struct scm *x, struct scm *a) { if (x->type == TSTRING) return assoc_string (x, a); while (a != cell_nil) { if (equal2_p (x, a->car->car) == cell_t) return a->car; a = a->cdr; } return cell_f; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib.h" #include "mes/mes.h" #include <stdlib.h> int g_depth; void fdwrite_char (char v, int fd) { if (v == '\0') fdputs ("\\nul", fd); else if (v == '\a') fdputs ("\\alarm", fd); else if (v == '\b') fdputs ("\\backspace", fd); else if (v == '\t') fdputs ("\\tab", fd); else if (v == '\n') fdputs ("\\newline", fd); else if (v == '\v') fdputs ("\\vtab", fd); else if (v == '\f') fdputs ("\\page", fd); /* Nyacc bug else if (v == '\r') fdputs ("return", fd); */ else if (v == 13) fdputs ("\\return", fd); else if (v == ' ') fdputs ("\\space", fd); else { if (v >= 32 && v <= 127) fdputc ('\\', fd); fdputc (v, fd); } } void fdwrite_string_char (char v, int fd) { if (v == '\0') fdputs ("\\0", fd); else if (v == '\a') fdputs ("\\a", fd); else if (v == '\b') fdputs ("\\b", fd); else if (v == '\t') fdputs ("\\t", fd); else if (v == '\v') fdputs ("\\v", fd); else if (v == '\n') fdputs ("\\n", fd); else if (v == '\f') fdputs ("\\f", fd); /* Nyacc bug else if (v == '\r') fdputs ("\\r", fd); else if (v == '\e') fdputs ("\\e", fd); */ else if (v == 13) fdputs ("\\r", fd); else if (v == 27) fdputs ("\\e", fd); else if (v == '\\') fdputs ("\\\\", fd); else if (v == '"') fdputs ("\\\"", fd); else fdputc (v, fd); } void fdwrite_string (char *s, int length, int fd) { int i; for (i = 0; i < length; i = i + 1) fdwrite_string_char (s[i], fd); } struct scm *display_helper (struct scm *x, int cont, char *sep, int fd, int write_p); struct scm * display_helper (struct scm *x, int cont, char *sep, int fd, int write_p) { fdputs (sep, fd); if (g_depth == 0) return cell_unspecified; g_depth = g_depth - 1; int t = x->type; if (t == TCHAR) { if (write_p == 0) fdputc (x->value, fd); else { fdputs ("#", fd); fdwrite_char (x->value, fd); } } else if (t == TCLOSURE) { fdputs ("#<closure ", fd); struct scm *circ = x->cdr->car; struct scm *name = circ->cdr->car; struct scm *args = x->cdr->cdr->car; display_helper (name->car, 0, "", fd, 0); fdputc (' ', fd); display_helper (args, 0, "", fd, 0); fdputs (">", fd); } else if (t == TMACRO) { fdputs ("#<macro ", fd); display_helper (x->cdr, cont, "", fd, 0); fdputs (">", fd); } else if (t == TVARIABLE) { fdputs ("#<variable ", fd); display_helper (x->variable->car, cont, "", fd, 0); fdputs (">", fd); } else if (t == TNUMBER) { fdputs (itoa (x->value), fd); } else if (t == TPAIR) { if (cont == 0) fdputs ("(", fd); if (x->car == cell_circular && x->cdr->car != cell_closure) { fdputs ("(*circ* . ", fd); int i = 0; x = x->cdr; while (x != cell_nil && i < 10) { i = i + 1; fdisplay_ (x->car->car, fd, write_p); fdputs (" ", fd); x = x->cdr; } fdputs (" ...)", fd); } else { if (x != 0 && x != cell_nil) fdisplay_ (x->car, fd, write_p); if (x->cdr != 0 && x->cdr->type == TPAIR) display_helper (x->cdr, 1, " ", fd, write_p); else if (x->cdr != 0 && x->cdr != cell_nil) { if (x->cdr->type != TPAIR) fdputs (" . ", fd); fdisplay_ (x->cdr, fd, write_p); } } if (cont == 0) fdputs (")", fd); } else if (t == TPORT) { fdputs ("#<port ", fd); fdputs (itoa (x->port), fd); fdputs (" ", fd); x = x->string; fdputc ('"', fd); fdwrite_string (cell_bytes (x->string), x->length, fd); fdputc ('"', fd); fdputs (">", fd); } else if (t == TKEYWORD) { fdputs ("#:", fd); fdwrite_string (cell_bytes (x->string), x->length, fd); } else if (t == TSTRING) { if (write_p == 1) { fdputc ('"', fd); fdwrite_string (cell_bytes (x->string), x->length, fd); fdputc ('"', fd); } else fdputs (cell_bytes (x->string), fd); } else if (t == TSPECIAL || t == TSYMBOL) fdwrite_string (cell_bytes (x->string), x->length, fd); else if (t == TREF) fdisplay_ (x->ref, fd, write_p); else if (t == TSTRUCT) { struct scm *printer = struct_ref_ (x, STRUCT_PRINTER); if (printer->type == TREF) printer = printer->ref; if (printer->type == TCLOSURE || builtin_p (printer) == cell_t) apply (printer, cons (x, cell_nil), R0); else { fdputs ("#<", fd); fdisplay_ (x->structure, fd, write_p); struct scm *t = x->car; long size = x->length; long i; for (i = 2; i < size; i = i + 1) { fdputc (' ', fd); fdisplay_ (cell_ref (x->structure, i), fd, write_p); } fdputc ('>', fd); } } else if (t == TVECTOR) { fdputs ("#(", fd); struct scm *t = x->car; long i; for (i = 0; i < x->length; i = i + 1) { if (i != 0) fdputc (' ', fd); fdisplay_ (cell_ref (x->vector, i), fd, write_p); } fdputc (')', fd); } else { fdputs ("<", fd); fdputs (itoa (t), fd); fdputs (":", fd); fdputs (ltoa (cast_voidp_to_long (x)), fd); fdputs (">", fd); } return cell_unspecified; } struct scm * display_ (struct scm *x) { g_depth = 5; return display_helper (x, 0, "", __stdout, 0); } struct scm * display_error_ (struct scm *x) { g_depth = 5; return display_helper (x, 0, "", __stderr, 0); } struct scm * display_port_ (struct scm *x, struct scm *p) { assert_msg (p->type == TNUMBER, "p->type == TNUMBER"); return fdisplay_ (x, p->value, 0); } struct scm * write_ (struct scm *x) { g_depth = 5; return display_helper (x, 0, "", __stdout, 1); } struct scm * write_error_ (struct scm *x) { g_depth = 5; return display_helper (x, 0, "", __stderr, 1); } struct scm * write_port_ (struct scm *x, struct scm *p) { assert_msg (p->type == TNUMBER, "p->type == TNUMBER"); return fdisplay_ (x, p->value, 1); } struct scm * fdisplay_ (struct scm *x, int fd, int write_p) /*:((internal)) */ { g_depth = 5; return display_helper (x, 0, "", fd, write_p); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib.h" #include "mes/mes.h" #include <string.h> struct scm * assert_defined (struct scm *x, struct scm *e) /*:((internal)) */ { if (e == cell_undefined) return error (cell_symbol_unbound_variable, x); return e; } struct scm * check_formals (struct scm *f, struct scm *formals, struct scm *args) /*:((internal)) */ { long flen; if (formals->type == TNUMBER) flen = formals->value; else flen = length__ (formals); long alen = length__ (args); if (alen != flen && alen != -1 && flen != -1) { char *s = "apply: wrong number of arguments; expected: "; eputs (s); eputs (itoa (flen)); eputs (", got: "); eputs (itoa (alen)); eputs ("\n"); write_error_ (f); struct scm *e = make_string0 (s); return error (cell_symbol_wrong_number_of_args, cons (e, f)); } return cell_unspecified; } struct scm * check_apply (struct scm *f, struct scm *e) /*:((internal)) */ { char *type = 0; if (f == cell_f || f == cell_t) type = "bool"; if (f == cell_nil) type = "nil"; if (f == cell_unspecified) type = "*unspecified*"; if (f == cell_undefined) type = "*undefined*"; if (f->type == TCHAR) type = "char"; if (f->type == TNUMBER) type = "number"; if (f->type == TSTRING) type = "string"; if (f->type == TSTRUCT && builtin_p (f) == cell_f) type = "#<...>"; if (f->type == TBROKEN_HEART) type = "<3"; if (type != 0) { char *s = "cannot apply: "; eputs (s); eputs (type); eputs ("["); write_error_ (e); eputs ("]\n"); struct scm *e = make_string0 (s); return error (cell_symbol_wrong_type_arg, cons (e, f)); } return cell_unspecified; } struct scm * pairlis (struct scm *x, struct scm *y, struct scm *a) { if (x == cell_nil) return a; if (x->type != TPAIR) return cons (cons (x, y), a); return cons (cons (car (x), car (y)), pairlis (cdr (x), cdr (y), a)); } struct scm * set_car_x (struct scm *x, struct scm *e) { if (x->type != TPAIR) error (cell_symbol_not_a_pair, cons (x, cstring_to_symbol ("set-car!"))); x->car = e; return cell_unspecified; } struct scm * set_cdr_x (struct scm *x, struct scm *e) { if (x->type != TPAIR) error (cell_symbol_not_a_pair, cons (x, cstring_to_symbol ("set-cdr!"))); x->cdr = e; return cell_unspecified; } struct scm * set_env_x (struct scm *x, struct scm *e, struct scm *a) { struct scm *p; if (x->type == TVARIABLE) p = x->variable; else p = assert_defined (x, module_variable (a, x)); if (p->type != TPAIR) error (cell_symbol_not_a_pair, cons (p, x)); return set_cdr_x (p, e); } struct scm * call_lambda (struct scm *e, struct scm *x, struct scm *aa, struct scm *a) /*:((internal)) */ { struct scm *cl = cons (cons (cell_closure, x), x); R1 = e; R0 = cl; return cell_unspecified; } struct scm * make_closure_ (struct scm *args, struct scm *body, struct scm *a) /*:((internal)) */ { return make_cell (TCLOSURE, cell_f, cons (cons (cell_circular, a), cons (args, body))); } struct scm * make_variable_ (struct scm *var) /*:((internal)) */ { return make_cell (TVARIABLE, var, 0); } struct scm * macro_get_handle (struct scm *name) /*:((internal)) */ { if (name->type == TSYMBOL) return hashq_get_handle (g_macros, name, cell_nil); return cell_f; } struct scm * get_macro (struct scm *name) /*:((internal)) */ { struct scm *m = macro_get_handle (name); if (m != cell_f) { struct scm *d = m->cdr; return d->macro; } return cell_f; } struct scm * macro_set_x (struct scm *name, struct scm *value) /*:((internal)) */ { return hashq_set_x (g_macros, name, value); } struct scm * push_cc (struct scm *p1, struct scm *p2, struct scm *a, struct scm *c) /*:((internal)) */ { struct scm *x = R3; R3 = c; R2 = p2; gc_push_frame (); R1 = p1; R0 = a; R3 = x; return cell_unspecified; } struct scm * add_formals (struct scm *formals, struct scm *x) { while (x->type == TPAIR) { formals = cons (x->car, formals); x = x->cdr; } if (x->type == TSYMBOL) formals = cons (x, formals); return formals; } int formal_p (struct scm *x, struct scm *formals) /*:((internal)) */ { if (formals->type == TSYMBOL) { if (x == formals) return 1; else return 0; } while (formals->type == TPAIR) { if (formals->car == x) break; formals = formals->cdr; } if (formals->type == TSYMBOL) return formals == x; return formals->type == TPAIR; } struct scm * expand_variable_ (struct scm *x, struct scm *formals, int top_p) /*:((internal)) */ { struct scm *a; struct scm *f; struct scm *v; while (x->type == TPAIR) { a = x->car; if (a->type == TPAIR) { if (a->car == cell_symbol_lambda) { f = a->cdr->car; formals = add_formals (formals, f); } else if (a->car == cell_symbol_define || a->car == cell_symbol_define_macro) { f = a->cdr->car; formals = add_formals (formals, f); } if (a->car != cell_symbol_quote) expand_variable_ (a, formals, 0); } else { if (a == cell_symbol_lambda) { f = x->cdr->car; formals = add_formals (formals, f); x = x->cdr; } else if (a == cell_symbol_define || a == cell_symbol_define_macro) { f = x->cdr->car; if (top_p != 0 && f->type == TPAIR) f = f->cdr; formals = add_formals (formals, f); x = x->cdr; } else if (a == cell_symbol_quote) return cell_unspecified; else if (a->type == TSYMBOL && a != cell_symbol_boot_module && a != cell_symbol_current_module && a != cell_symbol_primitive_load && formal_p (x->car, formals) == 0) { v = module_variable (R0, a); if (v != cell_f) x->car = make_variable_ (v); } } x = x->cdr; top_p = 0; } return cell_unspecified; } struct scm * expand_variable (struct scm *x, struct scm *formals) /*:((internal)) */ { return expand_variable_ (x, formals, 1); } struct scm * apply_builtin (struct scm *fn, struct scm *x) /*:((internal)) */ { struct scm *a = builtin_arity (fn); struct scm *d; int arity = a->value; if ((arity > 0 || arity == -1) && x != cell_nil) { a = x->car; if (a->type == TVALUES) x = cons (a->cdr->car, x->cdr); } if ((arity > 1 || arity == -1) && x != cell_nil) { a = x->car; d = x->cdr; if (d->type == TPAIR) if (d->car->type == TVALUES) x = cons (a, cons (d->car->cdr->car, d)); } if (arity == 0) return apply_builtin0 (fn); if (arity == 1) return apply_builtin1 (fn, x->car); else if (arity == 2) return apply_builtin2 (fn, x->car, x->cdr->car); else if (arity == 3) return apply_builtin3 (fn, x->car, x->cdr->car, x->cdr->cdr->car); else if (arity == -1) return apply_builtin1 (fn, x); return cell_unspecified; } struct scm * eval_apply () { struct scm *aa; struct scm *args; struct scm *body; struct scm *cl; struct scm *entry; struct scm *expanders; struct scm *formals; struct scm *input; struct scm *name; struct scm *macro; struct scm *p; struct scm *program; struct scm *sc_expand; struct scm *v; struct scm *x; int global_p; int macro_p; struct scm *a; struct scm *c; struct scm *d; int t; long i; eval_apply: if (R3 == cell_vm_evlis2) goto evlis2; else if (R3 == cell_vm_evlis3) goto evlis3; else if (R3 == cell_vm_eval_check_func) goto eval_check_func; else if (R3 == cell_vm_eval2) goto eval2; else if (R3 == cell_vm_apply2) goto apply2; else if (R3 == cell_vm_if_expr) goto if_expr; else if (R3 == cell_vm_begin_eval) goto begin_eval; else if (R3 == cell_vm_eval_set_x) goto eval_set_x; else if (R3 == cell_vm_macro_expand_car) goto macro_expand_car; else if (R3 == cell_vm_return) goto vm_return; else if (R3 == cell_vm_macro_expand_cdr) goto macro_expand_cdr; else if (R3 == cell_vm_eval_define) goto eval_define; else if (R3 == cell_vm_macro_expand) goto macro_expand; else if (R3 == cell_vm_macro_expand_lambda) goto macro_expand_lambda; else if (R3 == cell_vm_eval_pmatch_car) goto eval_pmatch_car; else if (R3 == cell_vm_begin_expand_macro) goto begin_expand_macro; else if (R3 == cell_vm_macro_expand_define) goto macro_expand_define; else if (R3 == cell_vm_begin_expand_eval) goto begin_expand_eval; else if (R3 == cell_vm_call_with_current_continuation2) goto call_with_current_continuation2; else if (R3 == cell_vm_macro_expand_set_x) goto macro_expand_set_x; else if (R3 == cell_vm_eval_pmatch_cdr) goto eval_pmatch_cdr; else if (R3 == cell_vm_macro_expand_define_macro) goto macro_expand_define_macro; else if (R3 == cell_vm_begin_primitive_load) goto begin_primitive_load; else if (R3 == cell_vm_evlis) goto evlis; else if (R3 == cell_vm_apply) goto apply; else if (R3 == cell_vm_eval) goto eval; else if (R3 == cell_vm_eval_macro_expand_eval) goto eval_macro_expand_eval; else if (R3 == cell_vm_eval_macro_expand_expand) goto eval_macro_expand_expand; else if (R3 == cell_vm_begin) goto begin; else if (R3 == cell_vm_begin_expand) goto begin_expand; else if (R3 == cell_vm_begin_expand_primitive_load) goto begin_expand_primitive_load; else if (R3 == cell_vm_if) goto vm_if; else if (R3 == cell_vm_call_with_values2) goto call_with_values2; else if (R3 == cell_unspecified) return R1; else assert_msg (0, "eval/apply unknown continuation"); evlis: if (R1 == cell_nil) goto vm_return; if (R1->type != TPAIR) goto eval; push_cc (R1->car, R1, R0, cell_vm_evlis2); goto eval; evlis2: push_cc (R2->cdr, R1, R0, cell_vm_evlis3); goto evlis; evlis3: R1 = cons (R2, R1); goto vm_return; apply: g_stack_array[g_stack + GC_FRAME_PROCEDURE] = R1->car; a = R1->car; t = a->type; if (t == TSTRUCT && builtin_p (R1->car) == cell_t) { check_formals (R1->car, builtin_arity (R1->car), R1->cdr); R1 = apply_builtin (R1->car, R1->cdr); goto vm_return; } else if (t == TCLOSURE) { cl = R1->car->closure; body = cl->cdr->cdr; formals = cl->cdr->car; args = R1->cdr; aa = cl->car->cdr; aa = aa->cdr; check_formals (R1->car, formals, R1->cdr); p = pairlis (formals, args, aa); call_lambda (body, p, aa, R0); goto begin; } else if (t == TCONTINUATION) { a = R1->car; v = a->continuation; if (v->length != 0) { for (i = 0; i < v->length; i = i + 1) g_stack_array[STACK_SIZE - v->length + i] = vector_ref_ (v, i); g_stack = STACK_SIZE - v->length; } x = R1; gc_pop_frame (); R1 = x->cdr->car; goto eval_apply; } else if (t == TSPECIAL) { c = R1->car; if (c == cell_vm_apply) { push_cc (cons (R1->cdr->car, R1->cdr->cdr->car), R1, R0, cell_vm_return); goto apply; } else if (c == cell_vm_eval) { push_cc (R1->cdr->car, R1, R1->cdr->cdr->car, cell_vm_return); goto eval; } else if (c == cell_vm_begin_expand) { push_cc (cons (R1->cdr->car, cell_nil), R1, R1->cdr->cdr->car, cell_vm_return); goto begin_expand; } else check_apply (cell_f, R1->car); } else if (t == TSYMBOL) { c = R1->car; if (c == cell_symbol_call_with_current_continuation) { R1 = R1->cdr; goto call_with_current_continuation; } if (c == cell_symbol_call_with_values) { R1 = R1->cdr; goto call_with_values; } if (c == cell_symbol_current_module) { R1 = R0; goto vm_return; } if (c == cell_symbol_boot_module) { R1 = M0; goto vm_return; } } else if (t == TPAIR) { if (R1->car->car == cell_symbol_lambda) { formals = R1->car->cdr->car; args = R1->cdr; body = R1->car->cdr->cdr; p = pairlis (formals, R1->cdr, R0); check_formals (R1, formals, args); call_lambda (body, p, p, R0); goto begin; } } push_cc (R1->car, R1, R0, cell_vm_apply2); goto eval; apply2: check_apply (R1, R2->car); R1 = cons (R1, R2->cdr); goto apply; eval: t = R1->type; if (t == TPAIR) { c = R1->car; if (c == cell_symbol_pmatch_car) { push_cc (R1->cdr->car, R1, R0, cell_vm_eval_pmatch_car); goto eval; eval_pmatch_car: x = R1; gc_pop_frame (); R1 = x->car; goto eval_apply; } else if (c == cell_symbol_pmatch_cdr) { push_cc (R1->cdr->car, R1, R0, cell_vm_eval_pmatch_cdr); goto eval; eval_pmatch_cdr: x = R1; gc_pop_frame (); R1 = x->cdr; goto eval_apply; } else if (c == cell_symbol_quote) { x = R1; gc_pop_frame (); R1 = x->cdr->car; goto eval_apply; } else if (c == cell_symbol_begin) goto begin; else if (c == cell_symbol_lambda) { R1 = make_closure_ (R1->cdr->car, R1->cdr->cdr, R0); goto vm_return; } else if (c == cell_symbol_if) { R1 = R1->cdr; goto vm_if; } else if (c == cell_symbol_set_x) { push_cc (R1->cdr->cdr->car, R1, R0, cell_vm_eval_set_x); goto eval; eval_set_x: R1 = set_env_x (R2->cdr->car, R1, R0); goto vm_return; } else if (c == cell_vm_macro_expand) { push_cc (R1->cdr->car, R1, R0, cell_vm_eval_macro_expand_eval); goto eval; eval_macro_expand_eval: push_cc (R1, R2, R0, cell_vm_eval_macro_expand_expand); goto macro_expand; eval_macro_expand_expand: goto vm_return; } else { if (R1->type == TPAIR) if (R1->car == cell_symbol_define || R1->car == cell_symbol_define_macro) { global_p = 0; if (R0->car->car != cell_closure) global_p = 1; macro_p = 0; if (R1->car == cell_symbol_define_macro) macro_p = 1; if (global_p != 0) { name = R1->cdr->car; aa = R1->cdr->car; if (aa->type == TPAIR) name = name->car; if (macro_p != 0) { entry = assq (name, g_macros); if (entry == cell_f) macro_set_x (name, cell_f); } else { entry = module_variable (R0, name); if (entry == cell_f) module_define_x (M0, name, cell_f); } } R2 = R1; aa = R1->cdr->car; if (aa->type != TPAIR) { push_cc (R1->cdr->cdr->car, R2, cons (cons (R1->cdr->car, R1->cdr->car), R0), cell_vm_eval_define); goto eval; } else { p = pairlis (R1->cdr->car, R1->cdr->car, R0); formals = R1->cdr->car->cdr; body = R1->cdr->cdr; if (macro_p != 0 || global_p != 0) expand_variable (body, formals); R1 = cons (cell_symbol_lambda, cons (formals, body)); push_cc (R1, R2, p, cell_vm_eval_define); goto eval; } eval_define: name = R2->cdr->car; aa = R2->cdr->car; if (aa->type == TPAIR) name = name->car; if (macro_p != 0) { entry = macro_get_handle (name); R1 = make_macro (name, R1); set_cdr_x (entry, R1); } else if (global_p != 0) { entry = module_variable (R0, name); set_cdr_x (entry, R1); } else { entry = cons (name, R1); aa = cons (entry, cell_nil); set_cdr_x (aa, cdr (R0)); set_cdr_x (R0, aa); cl = module_variable (R0, cell_closure); set_cdr_x (cl, aa); } R1 = cell_unspecified; goto vm_return; } push_cc (R1->car, R1, R0, cell_vm_eval_check_func); gc_check (); goto eval; eval_check_func: push_cc (R2->cdr, R2, R0, cell_vm_eval2); goto evlis; eval2: R1 = cons (R2->car, R1); goto apply; } } else if (t == TSYMBOL) { if (R1 == cell_symbol_boot_module) goto vm_return; if (R1 == cell_symbol_current_module) goto vm_return; if (R1 == cell_symbol_begin) goto vm_return; if (R1 == cell_symbol_call_with_current_continuation) goto vm_return; R1 = assert_defined (R1, module_ref (R0, R1)); goto vm_return; } else if (t == TVARIABLE) { x = R1->variable; R1 = x->cdr; goto vm_return; } else if (t == TBROKEN_HEART) error (cell_symbol_system_error, R1); else goto vm_return; macro_expand: if (R1->type != TPAIR || R1->car == cell_symbol_quote) goto vm_return; if (R1->car == cell_symbol_lambda) { push_cc (R1->cdr->cdr, R1, R0, cell_vm_macro_expand_lambda); goto macro_expand; macro_expand_lambda: R2->cdr->cdr = R1; R1 = R2; goto vm_return; } if (R1->type == TPAIR) { macro = get_macro (R1->car); if (macro != cell_f) { R1 = cons (macro, R1->cdr); push_cc (R1, cell_nil, R0, cell_vm_macro_expand); goto apply; } } if (R1->car == cell_symbol_define || R1->car == cell_symbol_define_macro) { push_cc (R1->cdr->cdr, R1, R0, cell_vm_macro_expand_define); goto macro_expand; macro_expand_define: R2->cdr->cdr = R1; R1 = R2; if (R1->car == cell_symbol_define_macro) { push_cc (R1, R1, R0, cell_vm_macro_expand_define_macro); goto eval; macro_expand_define_macro: R1 = R2; } goto vm_return; } if (R1->car == cell_symbol_set_x) { push_cc (R1->cdr->cdr, R1, R0, cell_vm_macro_expand_set_x); goto macro_expand; macro_expand_set_x: R2->cdr->cdr = R1; R1 = R2; goto vm_return; } if (R1->type == TPAIR) { a = R1->car; if (a->type == TSYMBOL && a != cell_symbol_begin) { macro = macro_get_handle (cell_symbol_portable_macro_expand); if (macro != cell_f) { expanders = module_ref (R0, cell_symbol_sc_expander_alist); if (expanders != cell_undefined) { macro = assq (R1->car, expanders); if (macro != cell_f) { sc_expand = module_ref (R0, cell_symbol_macro_expand); R2 = R1; if (sc_expand != cell_undefined && sc_expand != cell_f) { R1 = cons (sc_expand, cons (R1, cell_nil)); goto apply; } } } } } } push_cc (R1->car, R1, R0, cell_vm_macro_expand_car); goto macro_expand; macro_expand_car: R2->car = R1; R1 = R2; if (R1->cdr == cell_nil) goto vm_return; push_cc (R1->cdr, R1, R0, cell_vm_macro_expand_cdr); goto macro_expand; macro_expand_cdr: R2->cdr = R1; R1 = R2; goto vm_return; begin: x = cell_unspecified; while (R1 != cell_nil) { gc_check (); if (R1->type == TPAIR) { if (R1->car->car == cell_symbol_primitive_load) { program = cons (R1->car, cell_nil); push_cc (program, R1, R0, cell_vm_begin_primitive_load); goto begin_expand; begin_primitive_load: R2->car = R1; R1 = R2; } } if (R1->type == TPAIR) { a = R1->car; if (a->type == TPAIR) { if (a->car == cell_symbol_begin) R1 = append2 (a->cdr, R1->cdr); } } if (R1->cdr == cell_nil) { R1 = R1->car; goto eval; } push_cc (R1->car, R1, R0, cell_vm_begin_eval); goto eval; begin_eval: x = R1; R1 = R2->cdr; } R1 = x; goto vm_return; begin_expand: x = cell_unspecified; while (R1 != cell_nil) { begin_expand_while: gc_check (); if (R1->type == TPAIR) { a = R1->car; if (a->type == TPAIR) if (R1->car->car == cell_symbol_begin) R1 = append2 (R1->car->cdr, R1->cdr); if (R1->car->car == cell_symbol_primitive_load) { push_cc (R1->car->cdr->car, R1, R0, cell_vm_begin_expand_primitive_load); goto eval; begin_expand_primitive_load: if ((R1->type == TNUMBER) && R1->value == 0) 0; else if (R1->type == TSTRING) input = set_current_input_port (open_input_file (R1)); else if (R1->type == TPORT) input = set_current_input_port (R1); else { eputs ("begin_expand failed, R1="); display_error_ (R1); assert_msg (0, "begin-expand-boom 0"); } push_cc (input, R2, R0, cell_vm_return); x = read_input_file_env (R0); if (g_debug > 5) module_printer (M0); gc_pop_frame (); input = R1; R1 = x; set_current_input_port (input); R1 = cons (cell_symbol_begin, R1); R2->car = R1; R1 = R2; goto begin_expand_while; continue; /* FIXME: M2-PLanet */ } } push_cc (R1->car, R1, R0, cell_vm_begin_expand_macro); goto macro_expand; begin_expand_macro: if (R1 != R2->car) { R2->car = R1; R1 = R2; goto begin_expand_while; continue; /* FIXME: M2-PLanet */ } R1 = R2; expand_variable (R1->car, cell_nil); push_cc (R1->car, R1, R0, cell_vm_begin_expand_eval); goto eval; begin_expand_eval: x = R1; R1 = R2->cdr; } R1 = x; goto vm_return; vm_if: push_cc (R1->car, R1, R0, cell_vm_if_expr); goto eval; if_expr: x = R1; R1 = R2; if (x != cell_f) { R1 = R1->cdr->car; goto eval; } if (R1->cdr->cdr != cell_nil) { R1 = R1->cdr->cdr->car; goto eval; } R1 = cell_unspecified; goto vm_return; call_with_current_continuation: gc_push_frame (); x = make_continuation (g_continuations); g_continuations = g_continuations + 1; v = make_vector_ (STACK_SIZE - g_stack, cell_unspecified); for (i = g_stack; i < STACK_SIZE; i = i + 1) vector_set_x_ (v, i - g_stack, g_stack_array[i]); x->continuation = v; gc_pop_frame (); push_cc (cons (R1->car, cons (x, cell_nil)), x, R0, cell_vm_call_with_current_continuation2); goto apply; call_with_current_continuation2: v = make_vector_ (STACK_SIZE - g_stack, cell_unspecified); for (i = g_stack; i < STACK_SIZE; i = i + 1) vector_set_x_ (v, i - g_stack, g_stack_array[i]); R2->continuation = v; goto vm_return; call_with_values: push_cc (cons (R1->car, cell_nil), R1, R0, cell_vm_call_with_values2); goto apply; call_with_values2: if (R1->type == TVALUES) R1 = R1->cdr; R1 = cons (R2->cdr->car, R1); goto apply; vm_return: x = R1; gc_pop_frame (); R1 = x; goto eval_apply; } struct scm * apply (struct scm *f, struct scm *x, struct scm *a) /*:((internal)) */ { push_cc (cons (f, x), cell_unspecified, R0, cell_unspecified); R3 = cell_vm_apply; return eval_apply (); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020,2021 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * Copyright © 2022 Gabriel Wicki <gabriel@erlikon.ch> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib.h" #include "mes/mes.h" #include <errno.h> #include <fcntl.h> #include <string.h> #include <stdlib.h> #include <sys/time.h> #include <time.h> int g_dump_filedes; #define M2_CELL_SIZE 1U // CONSTANT M2_CELL_SIZE 12 char * cell_bytes (struct scm *x) { char *p = cast_voidp_to_charp (x); return p + (2 * sizeof (long)); } #define U10 10U // CONSTANT U10 10 #define U100 100U // CONSTANT U100 100 void gc_init () { #if SYSTEM_LIBC ARENA_SIZE = 100000000; /* 2.3GiB */ #elif ! __M2_PLANET__ ARENA_SIZE = 300000; /* 32b: 3MiB, 64b: 6 MiB */ #else ARENA_SIZE = 20000000; #endif STACK_SIZE = 20000; JAM_SIZE = 10; MAX_ARENA_SIZE = 10000000; GC_SAFETY = 2000; MAX_STRING = 524288; char *p; p = getenv ("MES_MAX_ARENA"); if (p != 0) MAX_ARENA_SIZE = atoi (p); p = getenv ("MES_ARENA"); if (p != 0) ARENA_SIZE = atoi (p); JAM_SIZE = ARENA_SIZE / U10; p = getenv ("MES_JAM"); if (p != 0) JAM_SIZE = atoi (p); GC_SAFETY = ARENA_SIZE / U100; p = getenv ("MES_SAFETY"); if (p != 0) GC_SAFETY = atoi (p); p = getenv ("MES_STACK"); if (p != 0) STACK_SIZE = atoi (p); p = getenv ("MES_MAX_STRING"); if (p != 0) MAX_STRING = atoi (p); long arena_bytes = (ARENA_SIZE + JAM_SIZE) * sizeof (struct scm); long alloc_bytes = arena_bytes + (STACK_SIZE * sizeof (struct scm)); g_arena = malloc (alloc_bytes); g_cells = cast_charp_to_scmp (g_arena); g_stack_array = cast_charp_to_scmpp (g_arena + arena_bytes); /* The vector that holds the arenea. */ cell_arena = g_cells; cell_zero = cell_arena + M2_CELL_SIZE; g_cells = g_cells + M2_CELL_SIZE; /* Hmm? */ cell_arena->type = TVECTOR; cell_arena->length = 1000; cell_arena->vector = cell_zero; cell_zero->type = TCHAR; cell_zero->value = 'c'; g_free = g_cells + M2_CELL_SIZE; /* FIXME: remove MES_MAX_STRING, grow dynamically. */ g_buf = malloc (MAX_STRING); } long gc_free () { return (g_free - g_cells) / M2_CELL_SIZE; } void gc_stats_ (char const* where) { size_t i = g_free - g_cells; i = i / M2_CELL_SIZE; if (where) { eputs (where); eputs (": "); } eputs ("["); eputs (ltoa (i)); eputs ("]\n"); } struct scm * gc_stats () { gc_stats_ (0); size_t arena_used = g_free - g_cells; arena_used = arena_used / M2_CELL_SIZE; size_t arena_free = ARENA_SIZE - arena_used; struct scm *r = cell_nil; r = acons (cstring_to_symbol ("gc-count"), make_number (gc_count), r); r = acons (cstring_to_symbol ("gc-time"), make_number (gc_time), r); r = acons (cstring_to_symbol ("arena-free"), make_number (arena_free), r); r = acons (cstring_to_symbol ("arena-size"), make_number (ARENA_SIZE), r); return r; } struct scm * alloc (long n) { struct scm *x = g_free; g_free = g_free + (n * M2_CELL_SIZE); long i = g_free - g_cells; i = i / M2_CELL_SIZE; if (i > ARENA_SIZE) assert_msg (0, "alloc: out of memory"); return x; } struct scm * make_cell (long type, struct scm *car, struct scm *cdr) { struct scm *x = g_free; g_free = g_free + M2_CELL_SIZE; long i = g_free - g_cells; i = i / M2_CELL_SIZE; if (i > ARENA_SIZE) assert_msg (0, "make_cell: out of memory"); x->type = type; x->car = car; x->cdr = cdr; return x; } struct scm * make_pointer_cell (long type, long car, void *cdr) { struct scm *x = g_free; g_free = g_free + M2_CELL_SIZE; long i = g_free - g_cells; i = i / M2_CELL_SIZE; if (i > ARENA_SIZE) assert_msg (0, "make_pointer_cell: out of memory"); x->type = type; x->length = car; x->cdr = cdr; return x; } struct scm * make_value_cell (long type, long car, long cdr) { struct scm *x = g_free; g_free = g_free + M2_CELL_SIZE; long i = g_free - g_cells; i = i / M2_CELL_SIZE; if (i > ARENA_SIZE) assert_msg (0, "make_value_cell: out of memory"); x->type = type; x->length = car; x->value = cdr; return x; } void copy_cell (struct scm *to, struct scm *from) { to->type = from->type; to->car = from->car; to->cdr = from->cdr; } void copy_stack (long index, struct scm *from) { g_stack_array[index] = from; } struct scm * cell_ref (struct scm *cell, long index) { return cell + (index * M2_CELL_SIZE); } struct scm * cons (struct scm *x, struct scm *y) { return make_cell (TPAIR, x, y); } size_t bytes_cells (size_t length) { return (sizeof (long) + sizeof (long) + length - 1 + sizeof (struct scm *)) / sizeof (struct scm *); } struct scm * make_bytes (char const *s, size_t length) { size_t size = bytes_cells (length); struct scm *x = alloc (size); x->type = TBYTES; x->length = length; char *p = cell_bytes (x); if (length == 0) p[0] = 0; else memcpy (p, s, length); return x; } struct scm * make_char (int n) { return make_value_cell (TCHAR, 0, n); } struct scm * make_continuation (long n) { return make_value_cell (TCONTINUATION, n, g_stack); } struct scm * make_macro (struct scm *name, struct scm *x) /*:((internal)) */ { return make_cell (TMACRO, x, name->string); } struct scm * make_number (long n) { return make_value_cell (TNUMBER, 0, n); } struct scm * make_ref (struct scm *x) /*:((internal)) */ { return make_cell (TREF, x, 0); } struct scm * make_string (char const *s, size_t length) { if (length > MAX_STRING) assert_max_string (length, "make_string", s); struct scm *x = make_pointer_cell (TSTRING, length, 0); struct scm *v = make_bytes (s, length + 1); x->cdr = v; return x; } struct scm * make_string0 (char const *s) { return make_string (s, strlen (s)); } struct scm * make_string_port (struct scm *x) /*:((internal)) */ { return make_pointer_cell (TPORT, -length__ (g_ports) - 2, x); } void gc_init_news () { g_news = g_free; struct scm *ncell_arena = g_news; struct scm *ncell_zero = ncell_arena + M2_CELL_SIZE; g_news = g_news + M2_CELL_SIZE; ncell_arena->type = TVECTOR; ncell_arena->length = cell_arena->length; ncell_arena->vector = g_news; ncell_zero->type = TCHAR; ncell_zero->value = 'n'; } void gc_up_arena () { long old_arena_bytes = (ARENA_SIZE + JAM_SIZE) * sizeof (struct scm); if (ARENA_SIZE / 2 < MAX_ARENA_SIZE / 4) { ARENA_SIZE = ARENA_SIZE * 2; JAM_SIZE = JAM_SIZE * 2; GC_SAFETY = GC_SAFETY * 2; } else ARENA_SIZE = MAX_ARENA_SIZE - JAM_SIZE; long arena_bytes = (ARENA_SIZE + JAM_SIZE) * sizeof (struct scm); long stack_offset = (arena_bytes * 2); long realloc_bytes = (arena_bytes * 2) + (STACK_SIZE * sizeof (struct scm)); void *p = realloc (g_cells - M2_CELL_SIZE, realloc_bytes); if (p == 0) { eputs ("realloc failed, g_free="); eputs (ltoa (cast_voidp_to_long (g_free))); eputs (":"); long i = g_free - g_cells; i = i / M2_CELL_SIZE; eputs (ltoa (ARENA_SIZE - i)); eputs ("\n"); assert_msg (0, "0"); exit (1); } g_cells = p; memcpy (p + stack_offset, p + old_arena_bytes, STACK_SIZE * sizeof (struct scm *)); g_cells = g_cells + M2_CELL_SIZE; } /* A pointer relocating memcpy for pointer cells to avoid using only half of the allocated cells. For number based cells a simply memcpy could be used, as number references are relative. A simple stop and copy (SICP 5.3) garbage collector allocates twice the cell arena size only for the garbage collector. The garbage collector switches back and forth between cells and news, thus utilizing only half the allocated memory. */ void gc_cellcpy (struct scm *dest, struct scm *src, size_t n) { void *p = src; void *q = dest; long dist = p - q; long t; long a; long d; int i; int c; while (n != 0) { t = src->type; a = src->car_value; d = src->cdr_value; dest->type = t; if (t == TBROKEN_HEART) assert_msg (0, "gc_cellcpy: broken heart"); if (t == TMACRO || t == TPAIR || t == TREF || t == TVARIABLE) dest->car_value = a - dist; else dest->car_value = a; if (t == TBYTES || t == TCLOSURE || t == TCONTINUATION || t == TKEYWORD || t == TMACRO || t == TPAIR || t == TPORT || t == TSPECIAL || t == TSTRING || t == TSTRUCT || t == TSYMBOL || t == TVALUES || t == TVECTOR) dest->cdr_value = d - dist; else dest->cdr_value = d; if (t == TBYTES) { if (g_debug > 5) { eputs ("copying bytes["); eputs (ntoab (cast_voidp_to_long (cell_bytes (src)), 16, 0)); eputs (", "); eputs (ntoab (a, 10, 0)); eputs ("]: "); eputs (cell_bytes (src)); eputs ("\n to ["); eputs (ntoab (cast_voidp_to_long (cell_bytes (dest)), 16, 0)); } memcpy (cell_bytes (dest), cell_bytes (src), a); if (g_debug > 5) { eputs ("]: "); eputs (cell_bytes (dest)); eputs ("\n"); } i = bytes_cells (a); n = n - i; c = i * M2_CELL_SIZE; dest = dest + c; src = src + c; } else { n = n - 1; dest = dest + M2_CELL_SIZE; src = src + M2_CELL_SIZE; } } } /* We do not actually flip cells and news, instead we move news back to cells. */ void gc_flip () { if (g_free - g_news > JAM_SIZE) JAM_SIZE = ((g_free - g_news) * 3) / 2; cell_arena = g_cells - M2_CELL_SIZE; /* For debugging. */ gc_cellcpy (g_cells, g_news, (g_free - g_news) / M2_CELL_SIZE); long dist = g_news - g_cells; g_free = g_free - dist; g_symbols = g_symbols - dist; g_macros = g_macros - dist; g_ports = g_ports - dist; M0 = M0 - dist; long i; for (i = g_stack; i < STACK_SIZE; i = i + 1) g_stack_array[i] = g_stack_array[i] - dist; if (g_debug > 2) gc_stats_ (";;; => jam"); } struct scm * gc_copy (struct scm *old) /*:((internal)) */ { if (old->type == TBROKEN_HEART) return old->car; struct scm *new = g_free; g_free = g_free + M2_CELL_SIZE; copy_cell (new, old); if (new->type == TSTRUCT || new->type == TVECTOR) { new->vector = g_free; long i; for (i = 0; i < old->length; i = i + 1) { copy_cell (g_free, cell_ref (old->vector, i)); g_free = g_free + M2_CELL_SIZE; } } else if (new->type == TBYTES) { char const *src = cell_bytes (old); char *dest = cell_bytes (new); size_t length = new->length; memcpy (dest, src, length); g_free = g_free + ((bytes_cells (length) - 1) * M2_CELL_SIZE); if (g_debug > 4) { eputs ("gc copy bytes: "); eputs (src); eputs ("\n"); eputs (" length: "); eputs (ltoa (old->length)); eputs ("\n"); eputs (" nlength: "); eputs (ltoa (new->length)); eputs ("\n"); eputs (" ==> "); eputs (dest); eputs ("\n"); } } old->type = TBROKEN_HEART; old->car = new; return new; } struct scm * gc_relocate_car (struct scm *new, struct scm *car) /*:((internal)) */ { new->car = car; return cell_unspecified; } struct scm * gc_relocate_cdr (struct scm *new, struct scm *cdr) /*:((internal)) */ { new->cdr = cdr; return cell_unspecified; } void gc_loop (struct scm *scan) { struct scm *car; struct scm *cdr; long t; while (scan < g_free) { t = scan->type; if (t == TBROKEN_HEART) assert_msg (0, "gc_loop: broken heart"); /* *INDENT-OFF* */ if (t == TMACRO || t == TPAIR || t == TREF || t == TVARIABLE) /* *INDENT-ON* */ { car = gc_copy (scan->car); gc_relocate_car (scan, car); } /* *INDENT-OFF* */ if (t == TCLOSURE || t == TCONTINUATION || t == TKEYWORD || t == TMACRO || t == TPAIR || t == TPORT || t == TSPECIAL || t == TSTRING /*|| t == TSTRUCT handled by gc_copy */ || t == TSYMBOL || t == TVALUES /*|| t == TVECTOR handled by gc_copy */ ) /* *INDENT-ON* */ { cdr = gc_copy (scan->cdr); gc_relocate_cdr (scan, cdr); } if (t == TBYTES) scan = scan + (bytes_cells (scan->length) * M2_CELL_SIZE); else scan = scan + M2_CELL_SIZE; } gc_flip (); } struct scm * gc_check () { long used = ((g_free - g_cells) / M2_CELL_SIZE) + GC_SAFETY; if (used >= ARENA_SIZE) return gc (); return cell_unspecified; } void gc_ () { gc_init_news (); if (g_debug == 2) eputs ("."); if (g_debug > 2) { gc_stats_ (";;; gc"); eputs (";;; free: ["); eputs (ltoa (ARENA_SIZE - gc_free ())); eputs ("]..."); } g_free = g_news + M2_CELL_SIZE; if (ARENA_SIZE < MAX_ARENA_SIZE && cast_voidp_to_charp (g_cells) == g_arena + M2_CELL_SIZE) { if (g_debug == 2) eputs ("+"); if (g_debug > 2) { eputs (" up["); eputs (ltoa (cast_voidp_to_long (g_cells))); eputs (","); eputs (ltoa (cast_voidp_to_long (g_news))); eputs (":"); eputs (ltoa (ARENA_SIZE)); eputs (","); eputs (ltoa (MAX_ARENA_SIZE)); eputs ("]..."); } gc_up_arena (); } struct scm *new_cell_nil = g_free; struct scm *s; for (s = cell_nil; s < g_symbol_max; s = s + M2_CELL_SIZE) gc_copy (s); g_symbols = gc_copy (g_symbols); g_macros = gc_copy (g_macros); g_ports = gc_copy (g_ports); M0 = gc_copy (M0); long i; for (i = g_stack; i < STACK_SIZE; i = i + 1) copy_stack (i, gc_copy (g_stack_array[i])); gc_loop (new_cell_nil); } struct scm * gc () { if (getenv ("MES_DUMP") != 0) gc_dump_arena (g_cells, gc_free ()); if (g_debug > 5) { eputs ("symbols: "); write_error_ (g_symbols); eputs ("\n"); eputs ("R0: "); write_error_ (R0); eputs ("\n"); } clock_gettime (CLOCK_PROCESS_CPUTIME_ID, gc_start_time); gc_push_frame (); gc_ (); gc_pop_frame (); clock_gettime (CLOCK_PROCESS_CPUTIME_ID, gc_end_time); long time = seconds_and_nanoseconds_to_long (gc_end_time->tv_sec - gc_start_time->tv_sec, gc_end_time->tv_nsec - gc_start_time->tv_nsec); gc_time = gc_time + time; gc_count = gc_count + 1; if (g_debug > 5) { eputs ("symbols: "); write_error_ (g_symbols); eputs ("\n"); eputs ("R0: "); write_error_ (R0); eputs ("\n"); } if (getenv ("MES_DUMP") != 0) gc_dump_arena (g_cells, gc_free ()); return cell_unspecified; } void gc_push_frame () { if (g_stack < GC_FRAME_SIZE) assert_msg (0, "STACK FULL"); g_stack_array[g_stack - 1] = cell_f; g_stack_array[g_stack - 2] = R0; g_stack_array[g_stack - 3] = R1; g_stack_array[g_stack - 4] = R2; g_stack_array[g_stack - 5] = R3; g_stack = g_stack - GC_FRAME_SIZE; } void gc_peek_frame () { R3 = g_stack_array[g_stack]; R2 = g_stack_array[g_stack + 1]; R1 = g_stack_array[g_stack + 2]; R0 = g_stack_array[g_stack + 3]; g_stack_array[g_stack + GC_FRAME_PROCEDURE]; } void gc_pop_frame () { gc_peek_frame (); g_stack = g_stack + GC_FRAME_SIZE; } void dumpc (char c) { fdputc (c, g_dump_filedes); } void dumps (char const *s) { fdputs (s, g_dump_filedes); } void gc_dump_register (char const* n, struct scm *r) { dumps (n); dumps (": "); long i = cast_scmp_to_long (r); long a = cast_charp_to_long (g_arena); i = i - a; i = i / M2_CELL_SIZE; dumps (ltoa (i)); dumps ("\n"); } void gc_dump_state () { gc_dump_register ("R0", R0); gc_dump_register ("R1", R1); gc_dump_register ("R2", R2); gc_dump_register ("R3", R3); gc_dump_register ("M0", M0); gc_dump_register ("g_symbols", g_symbols); gc_dump_register ("g_symbol_max", g_symbol_max); gc_dump_register ("g_macros", g_macros); gc_dump_register ("g_ports", g_ports); gc_dump_register ("cell_zero", cell_zero); gc_dump_register ("cell_nil", cell_nil); } void gc_dump_stack () { long i = g_stack; while (i < STACK_SIZE) { gc_dump_register (itoa (i), g_stack_array[i]); i = i + 1; } } void gc_dump_arena (struct scm *cells, long size) { struct scm *end = g_cells + (size * M2_CELL_SIZE); struct scm *dist = cells; int i; long t; long a; long d; int c; char* p; if (g_dump_filedes == 0) g_dump_filedes = mes_open ("dump.mo", O_CREAT|O_WRONLY, 0644); dumps ("stack="); dumps (ltoa (g_stack)); dumpc ('\n'); dumps ("size="); dumps (ltoa (size)); dumpc ('\n'); gc_dump_state (); gc_dump_stack (); while (end->type == 0 && end->car == 0 && end->cdr == 0) { end = end - M2_CELL_SIZE; size = size - 1; } while (size > 0) { for (i=0; i < 16; i = i + 1) { t = cells->type; a = cells->car_value; d = cells->cdr_value; if (size == 0) dumps ("0 0 0"); else { dumps (ltoa (t)); dumpc (' '); if (t == TMACRO || t == TPAIR || t == TREF || t == TVARIABLE) { dumps (ltoa ((cells->car - dist) / M2_CELL_SIZE)); /* dumps ("["); dumps (ltoa (a)); dumps ("]"); */ } else dumps (ltoa (a)); dumpc (' '); if (t != TBYTES) { if (t == TCLOSURE || t == TCONTINUATION || t == TKEYWORD || t == TMACRO || t == TPAIR || t == TPORT || t == TSPECIAL || t == TSTRING || t == TSTRUCT || t == TSYMBOL || t == TVALUES || t == TVECTOR) { dumps (ltoa ((cells->cdr - dist) / M2_CELL_SIZE)); /* dumps ("["); dumps (ltoa (d)); dumps ("]"); */ } else if (t == TNUMBER && d > 1000) dumps (ltoa (1001)); else dumps (ltoa (d)); } if (t == TBYTES) { c = bytes_cells (a); p = cell_bytes (cells); size = size - c; dumpc ('"'); while (a > 0) { if (p[0] != 0) dumpc (p[0]); p = p + 1; a = a - 1; } dumpc ('"'); cells = cells + c * M2_CELL_SIZE; size = size - c; } else { cells = cells + M2_CELL_SIZE; size = size - 1; } } if (i != 15) dumps (" "); else dumpc ('\n'); } dumpc ('\n'); } }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib.h" #include "mes/mes.h" #include <string.h> int hash_cstring (char const *s, long size) { int hash = s[0] * 37; if (s[0] != 0) if (s[1] != 0) hash = hash + (s[1] * 43); assert_msg (size != 0, "size"); hash = hash % size; return hash; } int hashq_ (struct scm *x, long size) { if (x->type == TSPECIAL || x->type == TSYMBOL) return hash_cstring (cell_bytes (x->string), size); /* FIXME: hash x directly. */ error (cell_symbol_system_error, cons (make_string0 ("hashq_: not a symbol"), x)); } int hash_ (struct scm *x, long size) { if (x->type != TSTRING) { eputs ("hash_ failed, not a string:"); display_error_ (x); assert_msg (0, "0"); } return hash_cstring (cell_bytes (x->string), size); } struct scm * hashq (struct scm *x, struct scm *size) { eputs ("hashq not supporteed\n"); assert_msg (0, "0"); } struct scm * hash (struct scm *x, struct scm *size) { eputs ("hash not supporteed\n"); assert_msg (0, "0"); } struct scm * hashq_get_handle (struct scm *table, struct scm *key, struct scm *dflt) { struct scm *s = struct_ref_ (table, 3); long size = s->value; unsigned hash = hashq_ (key, size); struct scm *buckets = struct_ref_ (table, 4); struct scm *bucket = vector_ref_ (buckets, hash); struct scm *x = cell_f; if (dflt->type == TPAIR) x = dflt->car; if (bucket->type == TPAIR) x = assq (key, bucket); return x; } struct scm * hashq_ref (struct scm *table, struct scm *key, struct scm *dflt) { struct scm *x = hashq_get_handle (table, key, dflt); if (x != cell_f) x = x->cdr; return x; } struct scm * hash_ref (struct scm *table, struct scm *key, struct scm *dflt) { struct scm *s = struct_ref_ (table, 3); long size = s->value; unsigned hash = hash_ (key, size); struct scm *buckets = struct_ref_ (table, 4); struct scm *bucket = vector_ref_ (buckets, hash); struct scm *x = cell_f; if (dflt->type == TPAIR) x = dflt->car; if (bucket->type == TPAIR) { x = assoc (key, bucket); if (x != cell_f) x = x->cdr; } return x; } struct scm * hash_set_x_ (struct scm *table, unsigned hash, struct scm *key, struct scm *value) { struct scm *buckets = struct_ref_ (table, 4); struct scm *bucket = vector_ref_ (buckets, hash); if (bucket->type != TPAIR) bucket = cell_nil; bucket = acons (key, value, bucket); vector_set_x_ (buckets, hash, bucket); return value; } struct scm * hashq_set_x (struct scm *table, struct scm *key, struct scm *value) { struct scm *s = struct_ref_ (table, 3); long size = s->value; unsigned hash = hashq_ (key, size); return hash_set_x_ (table, hash, key, value); } struct scm * hash_set_x (struct scm *table, struct scm *key, struct scm *value) { struct scm *s = struct_ref_ (table, 3); long size = s->value; unsigned hash = hash_ (key, size); return hash_set_x_ (table, hash, key, value); } struct scm * hash_table_printer (struct scm *table) { fdputs ("#<", __stdout); display_ (struct_ref_ (table, 2)); fdputc (' ', __stdout); fdputs ("size: ", __stdout); display_ (struct_ref_ (table, 3)); fdputc (' ', __stdout); struct scm *buckets = struct_ref_ (table, 4); fdputs ("buckets: ", __stdout); int i; struct scm *e; for (i = 0; i < buckets->length; i = i + 1) { e = vector_ref_ (buckets, i); if (e != cell_unspecified) { fdputc ('[', __stdout); while (e->type == TPAIR) { write_ (e->car->car); e = e->cdr; if (e->type == TPAIR) fdputc (' ', __stdout); } fdputs ("]\n ", __stdout); } } fdputc ('>', __stdout); } struct scm * make_hashq_type () /*:((internal)) */ { struct scm *fields = cell_nil; fields = cons (cell_symbol_buckets, fields); fields = cons (cell_symbol_size, fields); fields = cons (fields, cell_nil); fields = cons (cell_symbol_hashq_table, fields); return make_struct (cell_symbol_record_type, fields, cell_unspecified); } struct scm * make_hash_table_ (long size) { if (size == 0) size = 100; struct scm *hashq_type = make_hashq_type (); struct scm *buckets = make_vector_ (size, cell_unspecified); struct scm *values = cell_nil; values = cons (buckets, values); values = cons (make_number (size), values); values = cons (cell_symbol_hashq_table, values); /*FIXME: symbol/printer return make_struct (hashq_type, values, cstring_to_symbol ("hash-table-printer");*/ return make_struct (hashq_type, values, cell_unspecified); } struct scm * make_hash_table (struct scm *x) { long size = 0; if (x->type == TPAIR) { assert_msg (x->type == TNUMBER, "x->type == TNUMBER"); size = x->value; } return make_hash_table_ (size); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ /** Commentary: Scheme library functions not used by the eval/apply core. */ /** Code: */ #include "mes/lib.h" #include "mes/mes.h" #include <stdlib.h> struct scm * type_ (struct scm *x) { return make_number (x->type); } struct scm * car_ (struct scm *x) { struct scm *a = x->car; if (x->type == TPAIR) return a; return make_number (cast_scmp_to_long (a)); } struct scm * cdr_ (struct scm *x) { struct scm *d = x->cdr; if (x->type == TPAIR || x->type == TCLOSURE) return d; return make_number (cast_scmp_to_long (d)); } struct scm * xassq (struct scm *x, struct scm *a) /* For speed in core. */ { while (a != cell_nil) { if (x == a->car->cdr) return a->car; a = a->cdr; } return cell_f; } struct scm * memq (struct scm *x, struct scm *a) { int t = x->type; if (t == TCHAR || t == TNUMBER) { long v = x->value; while (a != cell_nil) { if (v == a->car->value) return a; a = a->cdr; } return cell_f; } if (t == TKEYWORD) { while (a != cell_nil) { if (a->car->type == TKEYWORD) if (string_equal_p (x, a->car) == cell_t) return a; a = a->cdr; } return cell_f; } while (a != cell_nil) { if (x == a->car) return a; a = a->cdr; } return cell_f; } struct scm * equal2_p (struct scm *a, struct scm *b) { long i; struct scm *ai; struct scm *bi; equal2: if (a == b) return cell_t; if (a->type == TPAIR && b->type == TPAIR) { if (equal2_p (a->car, b->car) == cell_t) { a = a->cdr; b = b->cdr; goto equal2; } return cell_f; } if (a->type == TSTRING && b->type == TSTRING) return string_equal_p (a, b); if (a->type == TVECTOR && b->type == TVECTOR) { if (a->length != b->length) return cell_f; for (i = 0; i < a->length; i = i + 1) { ai = cell_ref (a->vector, i); bi = cell_ref (b->vector, i); if (ai->type == TREF) ai = ai->ref; if (bi->type == TREF) bi = bi->ref; if (equal2_p (ai, bi) == cell_f) return cell_f; } return cell_t; } return eq_p (a, b); } struct scm * last_pair (struct scm *x) { while (x != cell_nil) { if (x->cdr == cell_nil) return x; x = x->cdr; } return x; } struct scm * pair_p (struct scm *x) { if (x->type == TPAIR) return cell_t; return cell_f; } struct scm * char_to_integer (struct scm *x) { return make_number (x->value); } struct scm * integer_to_char (struct scm *x) { return make_char (x->value); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib.h" #include "mes/mes.h" struct scm * apply_builtin0 (struct scm *fn) { FUNCTION fp = builtin_function (fn); return fp (); } struct scm * apply_builtin1 (struct scm *fn, struct scm *x) { FUNCTION fp = builtin_function (fn); return fp (x); } struct scm * apply_builtin2 (struct scm *fn, struct scm *x, struct scm *y) { FUNCTION fp = builtin_function (fn); return fp (x, y); } struct scm * apply_builtin3 (struct scm *fn, struct scm *x, struct scm *y, struct scm *z) { FUNCTION fp = builtin_function (fn); return fp (x, y, z); } #undef cast_charp_to_scmp #undef cast_charp_to_scmpp #undef cast_voidp_to_charp #undef cast_scmp_to_long #undef cast_scmp_to_charp struct scm * cast_charp_to_scmp (char const *i) { return i; } struct scm ** cast_charp_to_scmpp (char const *i) { return i; } char* cast_voidp_to_charp (void const *i) { return i; } long cast_scmp_to_long (struct scm *i) { return i; } char* cast_scmp_to_charp (struct scm *i) { return i; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020,2021 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * Copyright © 2021 W. J. van der Laan <laanwj@protonmail.com> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib.h" #include "mes/mes.h" #include <ctype.h> #include <limits.h> #include <stdio.h> #include <string.h> void assert_number (char const *name, struct scm *x) { if (x->type != TNUMBER) { eputs (name); error (cell_symbol_not_a_number, x); } } struct scm * greater_p (struct scm *x) /*:((name . ">") (arity . n)) */ { if (x == cell_nil) return cell_t; assert_number ("greater_p", x->car); long n = x->car->value; x = x->cdr; struct scm *i; long v; while (x != cell_nil) { assert_number ("greater_p", x->car); i = car (x); v = i->value; if (v >= n) return cell_f; n = v; x = cdr (x); } return cell_t; } struct scm * less_p (struct scm *x) /*:((name . "<") (arity . n)) */ { if (x == cell_nil) return cell_t; assert_number ("less_p", x->car); long n = x->car->value; x = x->cdr; struct scm *i; long v; while (x != cell_nil) { assert_number ("less_p", x->car); i = car (x); v = i->value; if (v <= n) return cell_f; n = v; x = cdr (x); } return cell_t; } struct scm * is_p (struct scm *x) /*:((name . "=") (arity . n)) */ { if (x == cell_nil) return cell_t; assert_number ("is_p", x->car); long n = x->car->value; x = cdr (x); struct scm *i; long v; while (x != cell_nil) { i = car (x); v = i->value; if (v != n) return cell_f; x = cdr (x); } return cell_t; } struct scm * minus (struct scm *x) /*:((name . "-") (arity . n)) */ { assert_number ("minus", x->car); long n = x->car->value; x = cdr (x); if (x == cell_nil) n = -n; struct scm *i; long v; while (x != cell_nil) { i = car (x); assert_number ("minus", i); v = i->value; n = n - v; x = cdr (x); } return make_number (n); } struct scm * plus (struct scm *x) /*:((name . "+") (arity . n)) */ { long n = 0; struct scm *i; long v; while (x != cell_nil) { i = car (x); assert_number ("plus", i); v = i->value; n = n + v; x = cdr (x); } return make_number (n); } struct scm * divide (struct scm *x) /*:((name . "/") (arity . n)) */ { long n = 1; struct scm *i; long v; if (x != cell_nil) { i = car (x); assert_number ("divide", i); v = i->value; n = v; x = cdr (x); } int sign_p = 0; size_t u = n; if (n < 0) { sign_p = 1; u = -n; } size_t w; while (x != cell_nil) { i = car (x); assert_number ("divide", i); v = i->value; sign_p = sign_p && v > 0 || !sign_p && v < 0; w = v; if (v == 0) error (cstring_to_symbol ("divide-by-zero"), x); if (u == 0) break; if (w != 1) u = u / w; x = cdr (x); } n = u; if (sign_p) n = -n; return make_number (n); } struct scm * modulo (struct scm *a, struct scm *b) { assert_number ("modulo", a); assert_number ("modulo", b); long n = a->value; long v = b->value; if (v == 0) error (cstring_to_symbol ("divide-by-zero"), a); int sign_p = 0; size_t w = v; if (v < 0) { sign_p = 1; w = -v; } while (n < 0) n = n + w; size_t u = n; if (u != 0) u = u % w; n = u; if (sign_p) n = -n; return make_number (n); } struct scm * multiply (struct scm *x) /*:((name . "*") (arity . n)) */ { long n = 1; struct scm *i; long v; while (x != cell_nil) { i = car (x); assert_number ("multiply", i); v = i->value; n = n * v; x = cdr (x); } return make_number (n); } struct scm * logand (struct scm *x) /*:((arity . n)) */ { long n = -1; struct scm *i; long v; while (x != cell_nil) { i = car (x); assert_number ("multiply", i); v = i->value; n = n & v; x = cdr (x); } return make_number (n); } struct scm * logior (struct scm *x) /*:((arity . n)) */ { long n = 0; struct scm *i; long v; while (x != cell_nil) { i = car (x); assert_number ("logior", i); v = i->value; n = n | v; x = cdr (x); } return make_number (n); } struct scm * lognot (struct scm *x) { assert_number ("lognot", x); long n = ~x->value; return make_number (n); } struct scm * logxor (struct scm *x) /*:((arity . n)) */ { long n = 0; struct scm *i; long v; while (x != cell_nil) { i = car (x); assert_number ("logxor", i); v = i->value; n = n ^ v; x = cdr (x); } return make_number (n); } struct scm * ash (struct scm *n, struct scm *count) { assert_number ("ash", n); assert_number ("ash", count); long cn = n->value; long ccount = count->value; long result; if (ccount < 0) result = cn >> -ccount; else result = cn << ccount; return make_number (result); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020,2021 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib.h" #include "mes/mes.h" #include <fcntl.h> #include <limits.h> #include <stdlib.h> #include <string.h> #include <sys/time.h> #include <time.h> struct scm * mes_g_stack (struct scm *a) /*:((internal)) */ { g_stack = STACK_SIZE; R0 = a; R1 = make_char (0); R2 = make_char (0); R3 = make_char (0); return R0; } struct scm * mes_environment (int argc, char **argv) { struct scm *a = init_symbols (); char *compiler = "gnuc"; #if __MESC__ compiler = "mesc"; #elif __TINYC__ compiler = "tcc"; #endif a = acons (cell_symbol_compiler, make_string0 (compiler), a); char *arch; #if __i386__ arch = "x86"; #elif __arm__ arch = "arm"; #elif __x86_64__ arch = "x86_64"; #else #error arch not supported #endif a = acons (cell_symbol_arch, make_string0 (arch), a); #if !MES_MINI struct scm *lst = cell_nil; int i; for (i = argc - 1; i >= 0; i = i - 1) lst = cons (make_string0 (argv[i]), lst); a = acons (cell_symbol_argv, lst, a); #endif return mes_g_stack (a); } int try_open_boot (char *file_name, char const *boot, char const *location) { strcpy (file_name + strlen (file_name), boot); if (g_debug > 1) { eputs ("mes: reading boot-0 ["); eputs (location); eputs ("]: "); eputs (file_name); eputs ("\n"); } int fd = mes_open (file_name, O_RDONLY, 0); if (g_debug != 0 && fd > 0) { eputs ("mes: read boot-0: "); eputs (file_name); eputs ("\n"); } return fd; } void open_boot () { __stdin = -1; char *boot = __open_boot_buf; char *file_name = __open_boot_file_name; strcpy (g_datadir, "."); if (getenv ("MES_BOOT") != 0) strcpy (boot, getenv ("MES_BOOT")); else strcpy (boot, "boot-0.scm"); if (getenv ("MES_PREFIX") != 0) { strcpy (g_datadir, getenv ("MES_PREFIX")); strcpy (g_datadir + strlen (g_datadir), "/mes"); strcpy (file_name, g_datadir); strcpy (file_name + strlen (file_name), "/module/mes/"); __stdin = try_open_boot (file_name, boot, "MES_PREFIX"); if (__stdin < 0) { strcpy (g_datadir, getenv ("MES_PREFIX")); strcpy (g_datadir + strlen (g_datadir), "/share/mes"); strcpy (file_name, g_datadir); strcpy (file_name + strlen (file_name), "/module/mes/"); __stdin = try_open_boot (file_name, boot, "MES_PREFIX/share/mes"); } } if (__stdin < 0) { g_datadir[0] = 0; if (getenv ("srcdest") != 0) strcpy (g_datadir, getenv ("srcdest")); strcpy (g_datadir + strlen (g_datadir), "mes"); strcpy (file_name, g_datadir); strcpy (file_name + strlen (file_name), "/module/mes/"); __stdin = try_open_boot (file_name, boot, "${srcdest}mes"); } if (__stdin < 0) { file_name[0] = 0; __stdin = try_open_boot (file_name, boot, "<boot>"); } if (__stdin < 0) { eputs ("mes: boot failed: no such file: "); eputs (boot); eputs ("\n"); exit (1); } } struct scm * read_boot () /*:((internal)) */ { R2 = read_input_file_env (R0); __stdin = STDIN; return R2; } void init (char **envp) { environ = envp; __execl_c_argv = malloc (1024 * sizeof (char *)); /* POSIX minimum: 4096 */ __gettimeofday_time = malloc (sizeof (struct timeval)); __get_internal_run_time_ts = malloc (sizeof (struct timespec)); __open_boot_buf = malloc (PATH_MAX); __open_boot_file_name = malloc (PATH_MAX); __reader_read_char_buf = malloc (10); __setenv_buf = malloc (1024); g_datadir = malloc (1024); g_start_time = malloc (sizeof (struct timespec)); memset (g_start_time, 0, sizeof (struct timespec)); gc_start_time = malloc (sizeof (struct timespec)); gc_end_time = malloc (sizeof (struct timespec)); char *p = getenv ("MES_DEBUG"); if (p != 0) g_debug = atoi (p); g_mini = cast_charp_to_long (getenv ("MES_MINI")); open_boot (); gc_init (); } int main (int argc, char **argv, char **envp) { init (envp); struct scm *a = mes_environment (argc, argv); a = mes_builtins (a); a = init_time (a); M0 = make_initial_module (a); g_macros = make_hash_table_ (0); if (g_debug > 5) module_printer (M0); struct scm *program = read_boot (); R0 = acons (cell_symbol_program, program, R0); push_cc (R2, cell_unspecified, R0, cell_unspecified); if (g_debug > 2) gc_stats_ ("\n gc boot"); if (g_debug > 3) { eputs ("program: "); write_error_ (R1); eputs ("\n"); } R3 = cell_vm_begin_expand; R1 = eval_apply (); if (g_debug != 0) { write_error_ (R1); eputs ("\n"); } if (g_debug != 0) { if (g_debug > 5) module_printer (M0); if (g_debug < 3) gc_stats_ ("\ngc run"); MAX_ARENA_SIZE = 0; gc (g_stack); if (g_debug < 3) gc_stats_ (" => "); if (g_debug > 5) { eputs ("\nports:"); write_error_ (g_ports); eputs ("\n"); } eputs ("\n"); } return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib.h" #include "mes/mes.h" struct scm * make_module_type () /*:(internal)) */ { struct scm *fields = cell_nil; fields = cons (cstring_to_symbol ("globals"), fields); fields = cons (cstring_to_symbol ("locals"), fields); fields = cons (cstring_to_symbol ("name"), fields); fields = cons (fields, cell_nil); fields = cons (cell_symbol_module, fields); return make_struct (cell_symbol_record_type, fields, cell_unspecified); } struct scm * make_initial_module (struct scm *a) /*:((internal)) */ { struct scm *module_type = make_module_type (); a = acons (cell_symbol_module, module_type, a); struct scm *hashq_type = make_hashq_type (); a = acons (cell_symbol_hashq_table, hashq_type, a); struct scm *name = cons (cstring_to_symbol ("boot"), cell_nil); struct scm *globals = make_hash_table_ (0); struct scm *locals = cell_nil; struct scm *values = cell_nil; values = cons (globals, values); values = cons (locals, values); values = cons (name, values); values = cons (cell_symbol_module, values); struct scm *module = make_struct (module_type, values, cstring_to_symbol ("module-printer")); R0 = cell_nil; R0 = cons (a->cdr->car, R0); R0 = cons (a->car, R0); M0 = module; while (a->type == TPAIR) { module_define_x (module, a->car->car, a->car->cdr); a = a->cdr; } return module; } struct scm * module_printer (struct scm *module) { fdputs ("#<", __stdout); display_ (struct_ref_ (module, 2)); fdputc (' ', __stdout); fdputs ("name: ", __stdout); display_ (struct_ref_ (module, 3)); fdputc (' ', __stdout); fdputs ("locals: ", __stdout); display_ (struct_ref_ (module, 4)); fdputc (' ', __stdout); struct scm *table = struct_ref_ (module, 5); fdputs ("globals:\n ", __stdout); display_ (table); fdputc ('>', __stdout); } struct scm * module_variable (struct scm *module, struct scm *name) { /*struct scm *locals = struct_ref_ (module, 3);*/ struct scm *locals = module; struct scm *x = assq (name, locals); if (x == cell_f) { module = M0; struct scm *globals = struct_ref_ (module, 5); x = hashq_get_handle (globals, name, cell_f); } return x; } struct scm * module_ref (struct scm *module, struct scm *name) { struct scm *x = module_variable (module, name); if (x == cell_f) return cell_undefined; return x->cdr; } struct scm * module_define_x (struct scm *module, struct scm *name, struct scm *value) { module = M0; struct scm *globals = struct_ref_ (module, 5); return hashq_set_x (globals, name, value); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020,2021 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib.h" #include "mes/mes.h" #include <fcntl.h> #include <limits.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <time.h> #include <sys/time.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #if SYSTEM_LIBC #define __raise(x) -1 #endif struct scm * abort_ () /*:((name . "abort")) */ { if (g_debug > 0) eputs ("abort!\n"); if (__raise (SIGABRT) < 0) /* could not raise SIGABRT */ { /* Fail in any way possible */ char* x = 0; x[0] = 2; } return cell_unspecified; } struct scm * exit_ (struct scm *x) /*:((name . "exit")) */ { assert_msg (x->type == TNUMBER, "x->type == TNUMBER"); exit (x->value); } int peekchar () { if (__stdin >= 0) { int c = readchar (); unreadchar (c); return c; } struct scm *port = current_input_port (); struct scm *string = port->string; size_t length = string->length; if (length == 0) return -1; char const *p = cell_bytes (string->string); return p[0]; } int readchar () { if (__stdin >= 0) return fdgetc (__stdin); struct scm *port = current_input_port (); struct scm *string = port->string; size_t length = string->length; if (length == 0) return -1; char const *p = cell_bytes (string->string); int c = p[0]; p = p + 1; port->string = make_string (p, length - 1); return c; } int unreadchar (int c) { if (__stdin >= 0) return fdungetc (c, __stdin); if (c == EOF) /* can't unread EOF */ return c; struct scm *port = current_input_port (); struct scm *string = port->string; size_t length = string->length; char *p = cell_bytes (string->string); p = p - 1; string = make_string (p, length + 1); p = cell_bytes (string->string); p[0] = c; port->string = string; return c; } struct scm * peek_byte () { return make_number (peekchar ()); } struct scm * read_byte () { return make_number (readchar ()); } struct scm * unread_byte (struct scm *i) { unreadchar (i->value); return i; } struct scm * peek_char () { return make_char (peekchar ()); } struct scm * read_char (struct scm *port) /*:((arity . n)) */ { int fd = __stdin; if (port->type == TPAIR) if (port->car->type == TNUMBER) __stdin = port->car->value; struct scm *c = make_char (readchar ()); __stdin = fd; return c; } struct scm * unread_char (struct scm *i) { unreadchar (i->value); return i; } struct scm * write_char (struct scm *i) /*:((arity . n)) */ { write_byte (i); return i; } struct scm * write_byte (struct scm *x) /*:((arity . n)) */ { struct scm *c = car (x); struct scm *p = cdr (x); int fd = __stdout; if (p->type == TPAIR) { struct scm *f = p->car; if (f->type == TNUMBER) { long v = f->value; if (v != 1) fd = v; if (v == 2) fd = __stderr; } } char cc = c->value; write (fd, &cc, 1); #if !__MESC__ assert_msg (c->type == TNUMBER || c->type == TCHAR, "c->type == TNUMBER || c->type == TCHAR"); #endif return c; } struct scm * getenv_ (struct scm *s) /*:((name . "getenv")) */ { char *p; p = getenv (cell_bytes (s->string)); if (p != 0) return make_string0 (p); return cell_f; } struct scm * setenv_ (struct scm *s, struct scm *v) /*:((name . "setenv")) */ { char *buf = __setenv_buf; strcpy (buf, cell_bytes (s->string)); setenv (buf, cell_bytes (v->string), 1); return cell_unspecified; } struct scm * access_p (struct scm *file_name, struct scm *mode) { int result = access (cell_bytes (file_name->string), mode->value); if (result == 0) return cell_t; return cell_f; } struct scm * current_input_port () { if (__stdin >= 0) return make_number (__stdin); struct scm *x = g_ports; struct scm *a; while (x != 0) { a = x->car; if (a->port == __stdin) return a; x = x->cdr; } return x->car; } struct scm * open_input_file (struct scm *file_name) { int filedes = mes_open (cell_bytes (file_name->string), O_RDONLY, 0); if (filedes == -1) error (cell_symbol_system_error, cons (make_string0 ("No such file or directory"), file_name)); return make_number (filedes); } struct scm * open_input_string (struct scm *string) { struct scm *port = make_string_port (string); g_ports = cons (port, g_ports); return port; } struct scm * set_current_input_port (struct scm *port) { struct scm *prev = current_input_port (); if (port->type == TNUMBER) { int p = port->value; if (p != 0) __stdin = p; else __stdin = STDIN; } else if (port->type == TPORT) __stdin = port->port; return prev; } struct scm * current_output_port () { return make_number (__stdout); } struct scm * current_error_port () { return make_number (__stderr); } struct scm * open_output_file (struct scm *x) /*:((arity . n)) */ { struct scm *file_name = car (x); x = cdr (x); int mode = S_IRUSR | S_IWUSR; if (x->type == TPAIR) { struct scm *i = car (x); if (i->type == TNUMBER) mode = i->value; } return make_number (mes_open (cell_bytes (file_name->string), O_WRONLY | O_CREAT | O_TRUNC, mode)); } struct scm * set_current_output_port (struct scm *port) { if (port->value != 0) __stdout = port->value; else __stdout = STDOUT; return current_output_port (); } struct scm * set_current_error_port (struct scm *port) { if (port->value != 0) __stderr = port->value; else __stderr = STDERR; return current_error_port (); } struct scm * chmod_ (struct scm *file_name, struct scm *mode) /*:((name . "chmod")) */ { chmod (cell_bytes (file_name->string), mode->value); return cell_unspecified; } struct scm * isatty_p (struct scm *port) { if (isatty (port->value) != 0) return cell_t; return cell_f; } struct scm * primitive_fork () { return make_number (fork ()); } struct scm * execl_ (struct scm *file_name, struct scm *args) /*:((name . "execl")) */ { char **c_argv = __execl_c_argv; int i = 0; if (length__ (args) > 1000) error (cell_symbol_system_error, cons (file_name, cons (make_string0 ("too many arguments"), cons (file_name, args)))); c_argv[i] = cell_bytes (file_name->string); i = i + 1; struct scm *arg; while (args != cell_nil) { assert_msg (args->car->type == TSTRING, "args->car->type == TSTRING"); arg = args->car; c_argv[i] = cell_bytes (arg->string); i = i + 1; args = args->cdr; if (g_debug > 2) { eputs ("arg["); eputs (itoa (i)); eputs ("]: "); eputs (c_argv[i - 1]); eputs ("\n"); } } c_argv[i] = 0; return make_number (execv (c_argv[0], c_argv)); } struct scm * waitpid_ (struct scm *pid, struct scm *options) { int status; int child = waitpid (pid->value, &status, options->value); return cons (make_number (child), make_number (status)); } #if __x86_64__ /* Nanoseconds on 64-bit systems with POSIX timers. */ // CONSTANT TIME_UNITS_PER_SECOND 1000000000 #define TIME_UNITS_PER_SECOND 1000000000U #else /* Milliseconds for everyone else. */ // CONSTANT TIME_UNITS_PER_SECOND 1000 #define TIME_UNITS_PER_SECOND 1000U #endif struct scm * init_time (struct scm *a) /*:((internal)) */ { clock_gettime (CLOCK_PROCESS_CPUTIME_ID, g_start_time); a = acons (cell_symbol_internal_time_units_per_second, make_number (TIME_UNITS_PER_SECOND), a); } struct scm * current_time () { return make_number (time (0)); } struct scm * gettimeofday_ () /*:((name . "gettimeofday")) */ { struct timeval *time = __gettimeofday_time; gettimeofday (time, 0); return cons (make_number (time->tv_sec), make_number (time->tv_usec)); } #define UL1000000000 1000000000UL // CONSTANT UL1000000000 1000000000 long seconds_and_nanoseconds_to_long (long s, long ns) { size_t uns = ns; if (ns < 0) { uns = - ns; return s * TIME_UNITS_PER_SECOND - uns / (UL1000000000 / TIME_UNITS_PER_SECOND); } return s * TIME_UNITS_PER_SECOND + uns / (UL1000000000 / TIME_UNITS_PER_SECOND); } struct scm * get_internal_run_time () { struct timespec *ts = __get_internal_run_time_ts; clock_gettime (CLOCK_PROCESS_CPUTIME_ID, ts); long time = seconds_and_nanoseconds_to_long (ts->tv_sec - g_start_time->tv_sec, ts->tv_nsec - g_start_time->tv_nsec); return make_number (time); } struct scm * getcwd_ () /*:((name . "getcwd")) */ { return make_string0 (getcwd (0, PATH_MAX)); } struct scm * dup_ (struct scm *port) /*:((name . "dup")) */ { return make_number (dup (port->value)); } struct scm * dup2_ (struct scm *old, struct scm *new) /*:((name . "dup2")) */ { dup2 (old->value, new->value); return cell_unspecified; } struct scm * delete_file (struct scm *file_name) { unlink (cell_bytes (file_name->string)); return cell_unspecified; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * Copyright © 2018 Jeremiah Orians <jeremiah@pdp10.guru> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib.h" #include "mes/mes.h" #include <ctype.h> #include <stdio.h> #include <string.h> struct scm * read_input_file_env_ (struct scm *e, struct scm *a) { if (e == cell_nil) return e; return cons (e, read_input_file_env_ (read_env (a), a)); } struct scm * read_input_file_env (struct scm *a) { return read_input_file_env_ (read_env (cell_nil), cell_nil); } int reader_read_line_comment (int c) { while (c != EOF) { if (c == '\n') return c; c = readchar (); } error (cell_symbol_system_error, make_string0 ("reader_read_line_comment")); } struct scm *reader_read_block_comment (int s, int c); struct scm *reader_read_hash (int c, struct scm *a); struct scm *reader_read_list (int c, struct scm *a); int reader_identifier_p (int c) { return (c > ' ' && c <= '~' && c != '"' && c != ';' && c != '(' && c != ')' && c != EOF); } int reader_end_of_word_p (int c) { return (c == '"' || c == ';' || c == '(' || c == ')' || isspace (c) || c == EOF); } struct scm * reader_read_identifier_or_number (int c) { int i = 0; long n = 0; int negative_p = 0; if (c == '+') if (isdigit (peekchar ()) != 0) c = readchar (); if (c == '-') if (isdigit (peekchar ()) != 0) { negative_p = 1; c = readchar (); } while (isdigit (c) != 0) { g_buf[i] = c; i = i + 1; n = n * 10; n = n + c - '0'; c = readchar (); } if (reader_end_of_word_p (c) != 0) { unreadchar (c); if (negative_p != 0) n = 0 - n; return make_number (n); } /* Fallthrough: Note that `4a', `+1b' are identifiers */ while (reader_end_of_word_p (c) == 0) { g_buf[i] = c; i = i + 1; c = readchar (); } unreadchar (c); g_buf[i] = 0; return cstring_to_symbol (g_buf); } struct scm * reader_read_sexp_ (int c, struct scm *a) { reset_reader: if (c == EOF) return cell_nil; if (c == ';') { c = reader_read_line_comment (c); goto reset_reader; } if ((c == ' ') || (c == '\t') || (c == '\n') || (c == '\f')) { c = readchar (); goto reset_reader; } if (c == '(') return reader_read_list (readchar (), a); if (c == ')') return cell_nil; if (c == '#') return reader_read_hash (readchar (), a); if (c == '`') return cons (cell_symbol_quasiquote, cons (reader_read_sexp_ (readchar (), a), cell_nil)); if (c == ',') { if (peekchar () == '@') { readchar (); return cons (cell_symbol_unquote_splicing, cons (reader_read_sexp_ (readchar (), a), cell_nil)); } return cons (cell_symbol_unquote, cons (reader_read_sexp_ (readchar (), a), cell_nil)); } if (c == '\'') return cons (cell_symbol_quote, cons (reader_read_sexp_ (readchar (), a), cell_nil)); if (c == '"') return reader_read_string (); if (c == '.') if (reader_identifier_p (peekchar ()) == 0) return cell_dot; return reader_read_identifier_or_number (c); } int reader_eat_whitespace (int c) { while (isspace (c) != 0) c = readchar (); if (c == ';') return reader_eat_whitespace (reader_read_line_comment (c)); if (c == '#') { int p = peekchar (); if (p == '!' || p == '|') { c = readchar (); reader_read_block_comment (c, readchar ()); return reader_eat_whitespace (readchar ()); } } return c; } struct scm * reader_read_list (int c, struct scm *a) { c = reader_eat_whitespace (c); if (c == ')') return cell_nil; if (c == EOF) error (cell_symbol_not_a_pair, make_string0 ("EOF in list")); struct scm *s = reader_read_sexp_ (c, a); if (s == cell_dot) { s = reader_read_list (readchar (), a); return s->car; } return cons (s, reader_read_list (readchar (), a)); } struct scm * read_env (struct scm *a) { return reader_read_sexp_ (readchar (), a); } struct scm * reader_read_block_comment (int s, int c) { if (c == s) if (peekchar () == '#') { readchar (); return cell_unspecified; } return reader_read_block_comment (s, readchar ()); } struct scm * reader_read_hash (int c, struct scm *a) { if (c == '!') { reader_read_block_comment (c, readchar ()); return reader_read_sexp_ (readchar (), a); } if (c == '|') { reader_read_block_comment (c, readchar ()); return reader_read_sexp_ (readchar (), a); } if (c == 'f') return cell_f; if (c == 't') return cell_t; if (c == ',') { if (peekchar () == '@') { readchar (); return cons (cell_symbol_unsyntax_splicing, cons (reader_read_sexp_ (readchar (), a), cell_nil)); } return cons (cell_symbol_unsyntax, cons (reader_read_sexp_ (readchar (), a), cell_nil)); } if (c == '\'') return cons (cell_symbol_syntax, cons (reader_read_sexp_ (readchar (), a), cell_nil)); if (c == '`') return cons (cell_symbol_quasisyntax, cons (reader_read_sexp_ (readchar (), a), cell_nil)); if (c == ':') { struct scm *x = reader_read_identifier_or_number (readchar ()); struct scm *msg = make_string0 ("keyword perifx ':' not followed by a symbol: "); if (x->type == TNUMBER) error (cell_symbol_system_error, cons (msg, x)); return symbol_to_keyword (x); } if (c == 'b') return reader_read_binary (); if (c == 'o') return reader_read_octal (); if (c == 'x') return reader_read_hex (); if (c == '\\') return reader_read_character (); if (c == '(') return list_to_vector (reader_read_list (readchar (), a)); if (c == ';') { reader_read_sexp_ (readchar (), a); return reader_read_sexp_ (readchar (), a); } return reader_read_sexp_ (readchar (), a); } struct scm * reader_read_sexp (struct scm *c, struct scm *s, struct scm *a) { return reader_read_sexp_ (c->value, a); } struct scm * reader_read_character () { int c = readchar (); int p = peekchar (); int i = 0; if (c >= '0' && c <= '7' && p >= '0' && p <= '7') { c = c - '0'; while (p >= '0' && p <= '7') { c = c << 3; c = c + readchar () - '0'; p = peekchar (); } } else if (c == 'x' && ((p >= '0' && p <= '9') || (p >= 'a' && p <= 'f') || (p >= 'F' && p <= 'F'))) { struct scm *n = reader_read_hex (); c = n->value; eputs ("reading hex c="); eputs (itoa (c)); eputs ("\n"); } else if (((c >= 'a' && c <= 'z') || c == '*') && ((p >= 'a' && p <= 'z') || p == '*')) { char *buf = __reader_read_char_buf; buf[i] = c; i = i + 1; while ((p >= 'a' && p <= 'z') || p == '*') { buf[i] = readchar (); i = i + 1; p = peekchar (); } buf[i] = 0; if (strcmp (buf, "*eof*") == 0) c = EOF; else if (strcmp (buf, "nul") == 0) c = '\0'; else if (strcmp (buf, "alarm") == 0) c = '\a'; else if (strcmp (buf, "backspace") == 0) c = '\b'; else if (strcmp (buf, "tab") == 0) c = '\t'; else if (strcmp (buf, "linefeed") == 0) c = '\n'; else if (strcmp (buf, "newline") == 0) c = '\n'; else if (strcmp (buf, "vtab") == 0) c = '\v'; else if (strcmp (buf, "page") == 0) c = '\f'; else if (strcmp (buf, "return") == 0) /* Nyacc bug c = '\r'; */ c = 13; else if (strcmp (buf, "esc") == 0) c = 27; else if (strcmp (buf, "space") == 0) c = ' '; /* Nyacc uses old abbrevs */ else if (strcmp (buf, "bel") == 0) c = '\a'; else if (strcmp (buf, "bs") == 0) c = '\b'; else if (strcmp (buf, "ht") == 0) c = '\t'; else if (strcmp (buf, "vt") == 0) c = '\v'; else if (strcmp (buf, "cr") == 0) /* Nyacc bug c = '\r'; */ c = 13; else { eputs ("char not supported: "); eputs (buf); eputs ("\n"); error (cell_symbol_system_error, make_string0 ("char not supported")); } } return make_char (c); } struct scm * reader_read_binary () { long n = 0; int c = peekchar (); int negative_p = 0; if (c == '-') { negative_p = 1; readchar (); c = peekchar (); } while (c == '0' || c == '1') { n = n << 1; n = n + c - '0'; readchar (); c = peekchar (); } if (negative_p != 0) n = 0 - n; return make_number (n); } struct scm * reader_read_octal () { long n = 0; int c = peekchar (); int negative_p = 0; if (c == '-') { negative_p = 1; readchar (); c = peekchar (); } while (c >= '0' && c <= '7') { n = n << 3; n = n + c - '0'; readchar (); c = peekchar (); } if (negative_p != 0) n = 0 - n; return make_number (n); } struct scm * reader_read_hex () { long n = 0; int c = peekchar (); int negative_p = 0; if (c == '-') { negative_p = 1; readchar (); c = peekchar (); } while ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) { n = n << 4; if (c >= 'a') n = n + c - 'a' + 10; else if (c >= 'A') n = n + c - 'A' + 10; else n = n + c - '0'; readchar (); c = peekchar (); } if (negative_p != 0) n = 0 - n; return make_number (n); } struct scm * reader_read_string () { size_t i = 0; int c; struct scm *n; do { if (i > MAX_STRING) assert_max_string (i, "reader_read_string", g_buf); c = readchar (); if (c == '"') break; if (c == '\\') { c = readchar (); if (c == '\\' || c == '"') 0; else if (c == '0') c = '\0'; else if (c == 'a') c = '\a'; else if (c == 'b') c = '\b'; else if (c == 't') c = '\t'; else if (c == 'n') c = '\n'; else if (c == 'v') c = '\v'; else if (c == 'f') c = '\f'; else if (c == 'r') /* Nyacc bug c = '\r'; */ c = 13; else if (c == 'e') /* Nyacc bug c = '\e'; */ c = 27; else if (c == 'x') { n = reader_read_hex (); c = n->value; } } g_buf[i] = c; i = i + 1; } while (1); g_buf[i] = 0; return make_string (g_buf, i); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * Copyright © 2021 W. J. van der Laan <laanwj@protonmail.com> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib.h" #include "mes/mes.h" #include <stdlib.h> struct scm * frame_printer (struct scm *frame) { fdputs ("#<", __stdout); display_ (struct_ref_ (frame, 2)); fdputc (' ', __stdout); fdputs ("procedure: ", __stdout); display_ (struct_ref_ (frame, 3)); fdputc ('>', __stdout); } struct scm * make_frame_type () /*:((internal)) */ { struct scm *fields = cell_nil; fields = cons (cell_symbol_procedure, fields); fields = cons (fields, cell_nil); fields = cons (cell_symbol_frame, fields); return make_struct (cell_symbol_record_type, fields, cell_unspecified); } struct scm * make_frame (struct scm *stack, long index) { struct scm *frame_type = make_frame_type (); long array_index = 0; struct scm *procedure = 0; if (index != 0) { array_index = (STACK_SIZE - (index * GC_FRAME_SIZE)); procedure = g_stack_array[array_index + GC_FRAME_PROCEDURE]; } if (procedure == 0) procedure = cell_f; struct scm *values = cell_nil; values = cons (procedure, values); values = cons (cell_symbol_frame, values); return make_struct (frame_type, values, cstring_to_symbol ("frame-printer")); } struct scm * make_stack_type () /*:((internal)) */ { struct scm *fields = cell_nil; fields = cons (cstring_to_symbol ("frames"), fields); fields = cons (fields, cell_nil); fields = cons (cell_symbol_stack, fields); return make_struct (cell_symbol_record_type, fields, cell_unspecified); } struct scm * make_stack (struct scm *stack) /*:((arity . n)) */ { struct scm *stack_type = make_stack_type (); long size = (STACK_SIZE - g_stack) / GC_FRAME_SIZE; struct scm *frames = make_vector_ (size, cell_unspecified); long i; struct scm* frame; for (i = 0; i < size; i = i + 1) { frame = make_frame (stack, i); vector_set_x_ (frames, i, frame); } struct scm *values = cell_nil; values = cons (frames, values); values = cons (cell_symbol_stack, values); return make_struct (stack_type, values, cell_unspecified); } struct scm * stack_length (struct scm *stack) { struct scm *frames = struct_ref_ (stack, 3); return vector_length (frames); } struct scm * stack_ref (struct scm *stack, struct scm *index) { struct scm *frames = struct_ref_ (stack, 3); return vector_ref (frames, index); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib.h" #include "mes/mes.h" #include <limits.h> #include <string.h> void assert_max_string (size_t i, char const *msg, char const *string) { if (i > MAX_STRING) { eputs (msg); eputs (":string too long["); eputs (itoa (i)); eputs ("]:"); char *p = cast_voidp_to_charp (string); p[MAX_STRING - 1] = 0; eputs (p); error (cell_symbol_system_error, cell_f); } } char const * list_to_cstring (struct scm *list, size_t *size) { size_t i = 0; char *p = g_buf; struct scm *x; while (list != cell_nil) { if (i > MAX_STRING) assert_max_string (i, "list_to_string", g_buf); x = car (list); g_buf[i] = x->value; i = i + 1; list = cdr (list); } g_buf[i] = 0; size[0] = i; return g_buf; } struct scm * string_equal_p (struct scm *a, struct scm *b) /*:((name . "string=?")) */ { if (!((a->type == TSTRING && b->type == TSTRING) || (a->type == TKEYWORD || b->type == TKEYWORD))) { eputs ("type a: "); eputs (itoa (a->type)); eputs ("\n"); eputs ("type b: "); eputs (itoa (b->type)); eputs ("\n"); eputs ("a= "); write_error_ (a); eputs ("\n"); eputs ("b= "); write_error_ (b); eputs ("\n"); assert_msg ((a->type == TSTRING && b->type == TSTRING) || (a->type == TKEYWORD || b->type == TKEYWORD), "(a->type == TSTRING && b->type == TSTRING) || (a->type == TKEYWORD || b->type == TKEYWORD)"); } if (a == b) return cell_t; if (a->string == b->string) return cell_t; if (a->length == 0 && b->length == 0) return cell_t; if (a->length == b->length) if (memcmp (cell_bytes (a->string), cell_bytes (b->string), a->length) == 0) return cell_t; return cell_f; } struct scm * symbol_to_string (struct scm *symbol) { return make_cell (TSTRING, symbol->car, symbol->cdr); } struct scm * symbol_to_keyword (struct scm *symbol) { return make_cell (TKEYWORD, symbol->car, symbol->cdr); } struct scm * keyword_to_string (struct scm *keyword) { return make_cell (TSTRING, keyword->car, keyword->cdr); } struct scm * string_to_symbol (struct scm *string) { struct scm *x = hash_ref (g_symbols, string, cell_f); if (x == cell_f) x = make_symbol (string); return x; } struct scm * make_symbol (struct scm *string) { struct scm *x = make_pointer_cell (TSYMBOL, string->length, string->string); hash_set_x (g_symbols, string, x); return x; } struct scm * bytes_to_list (char const *s, size_t i) { struct scm *p = cell_nil; int c; while (i != 0) { i = i - 1; c = (0x100 + s[i]) % 0x100; p = cons (make_char (c), p); } return p; } struct scm * cstring_to_list (char const *s) { return bytes_to_list (s, strlen (s)); } struct scm * cstring_to_symbol (char const *s) { struct scm *string = make_string0 (s); return string_to_symbol (string); } struct scm * string_to_list (struct scm *string) { return bytes_to_list (cell_bytes (string->string), string->length); } struct scm * list_to_string (struct scm *list) { size_t size; char const *s = list_to_cstring (list, &size); return make_string (s, size); } struct scm * read_string (struct scm *port) /*:((arity . n)) */ { int fd = __stdin; if (port->type == TPAIR) { struct scm *p = car (port); if (p->type == TNUMBER) __stdin = p->value; } int c = readchar (); size_t i = 0; while (c != -1) { if (i > MAX_STRING) assert_max_string (i, "read_string", g_buf); g_buf[i] = c; i = i + 1; c = readchar (); } g_buf[i] = 0; __stdin = fd; return make_string (g_buf, i); } struct scm * string_append (struct scm *x) /*:((arity . n)) */ { char *p = g_buf; g_buf[0] = 0; size_t size = 0; struct scm *string; while (x != cell_nil) { string = x->car; assert_msg (string->type == TSTRING, "string->type == TSTRING"); memcpy (p, cell_bytes (string->string), string->length + 1); p = p + string->length; size = size + string->length; if (size > MAX_STRING) assert_max_string (size, "string_append", g_buf); x = x->cdr; } return make_string (g_buf, size); } struct scm * string_length (struct scm *string) { assert_msg (string->type == TSTRING, "string->type == TSTRING"); return make_number (string->length); } struct scm * string_ref (struct scm *str, struct scm *k) { assert_msg (str->type == TSTRING, "str->type == TSTRING"); assert_msg (k->type == TNUMBER, "k->type == TNUMBER"); size_t size = str->length; size_t i = k->value; if (i > size) error (cell_symbol_system_error, cons (make_string0 ("value out of range"), k)); char const *p = cell_bytes (str->string); return make_char (p[i]); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib.h" #include "mes/mes.h" struct scm * make_struct (struct scm *type, struct scm *fields, struct scm *printer) { long size = 2 + length__ (fields); struct scm *x = alloc (1); struct scm *v = alloc (size); x->type = TSTRUCT; x->length = size; x->structure = v; copy_cell (v, vector_entry (type)); copy_cell (cell_ref (v, 1), vector_entry (printer)); long i; struct scm *e; for (i = 2; i < size; i = i + 1) { e = cell_unspecified; if (fields != cell_nil) { e = fields->car; fields = fields->cdr; } copy_cell (cell_ref (v, i), vector_entry (e)); } return x; } struct scm * struct_length (struct scm *x) { assert_msg (x->type == TSTRUCT, "x->type == TSTRUCT"); return make_number (x->length); } struct scm * struct_ref_ (struct scm *x, long i) { assert_msg (x->type == TSTRUCT, "x->type == TSTRUCT"); assert_msg (i < x->length, "i < x->length"); struct scm *e = cell_ref (x->structure, i); if (e->type == TREF) e = e->ref; if (e->type == TCHAR) e = make_char (e->value); if (e->type == TNUMBER) e = make_number (e->value); return e; } struct scm * struct_set_x_ (struct scm *x, long i, struct scm *e) { assert_msg (x->type == TSTRUCT, "x->type == TSTRUCT"); assert_msg (i < x->length, "i < x->length"); copy_cell (cell_ref (x->structure, i), vector_entry (e)); return cell_unspecified; } struct scm * struct_ref (struct scm *x, struct scm *i) { return struct_ref_ (x, i->value); } struct scm * struct_set_x (struct scm *x, struct scm *i, struct scm *e) { return struct_set_x_ (x, i->value, e); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib.h" #include "mes/mes.h" #include <string.h> // char const *MES_VERSION = "0.24.2"; #if __M2_PLANET__ #define M2_CELL_SIZE 12 // CONSTANT M2_CELL_SIZE 12 #else #define M2_CELL_SIZE 1 // CONSTANT M2_CELL_SIZE 12 #endif struct scm * init_symbol (struct scm *x, long type, char const *name) { x->type = type; if (g_symbols == 0) g_free = g_free + M2_CELL_SIZE; else { int length = strlen (name); struct scm *string = make_string (name, length); x->car_value = length; x->cdr = string->string; hash_set_x (g_symbols, string, x); } g_symbol = g_symbol + M2_CELL_SIZE; return x; } void init_symbols_ () /*:((internal)) */ { g_symbol = cell_nil; cell_nil = init_symbol (g_symbol, TSPECIAL, "()"); cell_f = init_symbol (g_symbol, TSPECIAL, "#f"); cell_t = init_symbol (g_symbol, TSPECIAL, "#t"); cell_dot = init_symbol (g_symbol, TSPECIAL, "."); cell_arrow = init_symbol (g_symbol, TSPECIAL, "=>"); cell_undefined = init_symbol (g_symbol, TSPECIAL, "*undefined*"); cell_unspecified = init_symbol (g_symbol, TSPECIAL, "*unspecified*"); cell_closure = init_symbol (g_symbol, TSPECIAL, "*closure*"); cell_circular = init_symbol (g_symbol, TSPECIAL, "*circular*"); cell_vm_apply = init_symbol (g_symbol, TSPECIAL, "core:apply"); cell_vm_apply2 = init_symbol (g_symbol, TSPECIAL, "*vm-apply2*"); cell_vm_begin = init_symbol (g_symbol, TSPECIAL, "*vm-begin*"); cell_vm_begin_eval = init_symbol (g_symbol, TSPECIAL, "*vm:begin-eval*"); cell_vm_begin_expand = init_symbol (g_symbol, TSPECIAL, "core:eval"); cell_vm_begin_expand_eval = init_symbol (g_symbol, TSPECIAL, "*vm:begin-expand-eval*"); cell_vm_begin_expand_macro = init_symbol (g_symbol, TSPECIAL, "*vm:begin-expand-macro*"); cell_vm_begin_expand_primitive_load = init_symbol (g_symbol, TSPECIAL, "*vm:core:begin-expand-primitive-load*"); cell_vm_begin_primitive_load = init_symbol (g_symbol, TSPECIAL, "*vm:core:begin-primitive-load*"); cell_vm_begin_read_input_file = init_symbol (g_symbol, TSPECIAL, "*vm-begin-read-input-file*"); cell_vm_call_with_current_continuation2 = init_symbol (g_symbol, TSPECIAL, "*vm-call-with-current-continuation2*"); cell_vm_call_with_values2 = init_symbol (g_symbol, TSPECIAL, "*vm-call-with-values2*"); cell_vm_eval = init_symbol (g_symbol, TSPECIAL, "core:eval-expanded"); cell_vm_eval2 = init_symbol (g_symbol, TSPECIAL, "*vm-eval2*"); cell_vm_eval_check_func = init_symbol (g_symbol, TSPECIAL, "*vm-eval-check-func*"); cell_vm_eval_define = init_symbol (g_symbol, TSPECIAL, "*vm-eval-define*"); cell_vm_eval_macro_expand_eval = init_symbol (g_symbol, TSPECIAL, "*vm:eval-macro-expand-eval*"); cell_vm_eval_macro_expand_expand = init_symbol (g_symbol, TSPECIAL, "*vm:eval-macro-expand-expand*"); cell_vm_eval_pmatch_car = init_symbol (g_symbol, TSPECIAL, "*vm-eval-pmatch-car*"); cell_vm_eval_pmatch_cdr = init_symbol (g_symbol, TSPECIAL, "*vm-eval-pmatch-cdr*"); cell_vm_eval_set_x = init_symbol (g_symbol, TSPECIAL, "*vm-eval-set!*"); cell_vm_evlis = init_symbol (g_symbol, TSPECIAL, "*vm-evlis*"); cell_vm_evlis2 = init_symbol (g_symbol, TSPECIAL, "*vm-evlis2*"); cell_vm_evlis3 = init_symbol (g_symbol, TSPECIAL, "*vm-evlis3*"); cell_vm_if = init_symbol (g_symbol, TSPECIAL, "*vm-if*"); cell_vm_if_expr = init_symbol (g_symbol, TSPECIAL, "*vm-if-expr*"); cell_vm_macro_expand = init_symbol (g_symbol, TSPECIAL, "core:macro-expand"); cell_vm_macro_expand_car = init_symbol (g_symbol, TSPECIAL, "*vm:core:macro-expand-car*"); cell_vm_macro_expand_cdr = init_symbol (g_symbol, TSPECIAL, "*vm:macro-expand-cdr*"); cell_vm_macro_expand_define = init_symbol (g_symbol, TSPECIAL, "*vm:core:macro-expand-define*"); cell_vm_macro_expand_define_macro = init_symbol (g_symbol, TSPECIAL, "*vm:core:macro-expand-define-macro*"); cell_vm_macro_expand_lambda = init_symbol (g_symbol, TSPECIAL, "*vm:core:macro-expand-lambda*"); cell_vm_macro_expand_set_x = init_symbol (g_symbol, TSPECIAL, "*vm:core:macro-expand-set!*"); cell_vm_return = init_symbol (g_symbol, TSPECIAL, "*vm-return*"); cell_symbol_lambda = init_symbol (g_symbol, TSYMBOL, "lambda"); cell_symbol_begin = init_symbol (g_symbol, TSYMBOL, "begin"); cell_symbol_if = init_symbol (g_symbol, TSYMBOL, "if"); cell_symbol_quote = init_symbol (g_symbol, TSYMBOL, "quote"); cell_symbol_define = init_symbol (g_symbol, TSYMBOL, "define"); cell_symbol_define_macro = init_symbol (g_symbol, TSYMBOL, "define-macro"); cell_symbol_quasiquote = init_symbol (g_symbol, TSYMBOL, "quasiquote"); cell_symbol_unquote = init_symbol (g_symbol, TSYMBOL, "unquote"); cell_symbol_unquote_splicing = init_symbol (g_symbol, TSYMBOL, "unquote-splicing"); cell_symbol_syntax = init_symbol (g_symbol, TSYMBOL, "syntax"); cell_symbol_quasisyntax = init_symbol (g_symbol, TSYMBOL, "quasisyntax"); cell_symbol_unsyntax = init_symbol (g_symbol, TSYMBOL, "unsyntax"); cell_symbol_unsyntax_splicing = init_symbol (g_symbol, TSYMBOL, "unsyntax-splicing"); cell_symbol_set_x = init_symbol (g_symbol, TSYMBOL, "set!"); cell_symbol_sc_expand = init_symbol (g_symbol, TSYMBOL, "sc-expand"); cell_symbol_macro_expand = init_symbol (g_symbol, TSYMBOL, "macro-expand"); cell_symbol_portable_macro_expand = init_symbol (g_symbol, TSYMBOL, "portable-macro-expand"); cell_symbol_sc_expander_alist = init_symbol (g_symbol, TSYMBOL, "*sc-expander-alist*"); cell_symbol_call_with_values = init_symbol (g_symbol, TSYMBOL, "call-with-values"); cell_symbol_call_with_current_continuation = init_symbol (g_symbol, TSYMBOL, "call-with-current-continuation"); cell_symbol_boot_module = init_symbol (g_symbol, TSYMBOL, "boot-module"); cell_symbol_current_module = init_symbol (g_symbol, TSYMBOL, "current-module"); cell_symbol_primitive_load = init_symbol (g_symbol, TSYMBOL, "primitive-load"); cell_symbol_car = init_symbol (g_symbol, TSYMBOL, "car"); cell_symbol_cdr = init_symbol (g_symbol, TSYMBOL, "cdr"); cell_symbol_not_a_number = init_symbol (g_symbol, TSYMBOL, "not-a-number"); cell_symbol_not_a_pair = init_symbol (g_symbol, TSYMBOL, "not-a-pair"); cell_symbol_system_error = init_symbol (g_symbol, TSYMBOL, "system-error"); cell_symbol_throw = init_symbol (g_symbol, TSYMBOL, "throw"); cell_symbol_unbound_variable = init_symbol (g_symbol, TSYMBOL, "unbound-variable"); cell_symbol_wrong_number_of_args = init_symbol (g_symbol, TSYMBOL, "wrong-number-of-args"); cell_symbol_wrong_type_arg = init_symbol (g_symbol, TSYMBOL, "wrong-type-arg"); cell_symbol_buckets = init_symbol (g_symbol, TSYMBOL, "buckets"); cell_symbol_builtin = init_symbol (g_symbol, TSYMBOL, "<builtin>"); cell_symbol_frame = init_symbol (g_symbol, TSYMBOL, "<frame>"); cell_symbol_hashq_table = init_symbol (g_symbol, TSYMBOL, "<hashq-table>"); cell_symbol_module = init_symbol (g_symbol, TSYMBOL, "<module>"); cell_symbol_procedure = init_symbol (g_symbol, TSYMBOL, "procedure"); cell_symbol_record_type = init_symbol (g_symbol, TSYMBOL, "<record-type>"); cell_symbol_size = init_symbol (g_symbol, TSYMBOL, "size"); cell_symbol_stack = init_symbol (g_symbol, TSYMBOL, "<stack>"); cell_symbol_argv = init_symbol (g_symbol, TSYMBOL, "%argv"); cell_symbol_mes_datadir = init_symbol (g_symbol, TSYMBOL, "%datadir"); cell_symbol_mes_version = init_symbol (g_symbol, TSYMBOL, "%version"); cell_symbol_internal_time_units_per_second = init_symbol (g_symbol, TSYMBOL, "internal-time-units-per-second"); cell_symbol_compiler = init_symbol (g_symbol, TSYMBOL, "%compiler"); cell_symbol_arch = init_symbol (g_symbol, TSYMBOL, "%arch"); cell_symbol_pmatch_car = init_symbol (g_symbol, TSYMBOL, "pmatch-car"); cell_symbol_pmatch_cdr = init_symbol (g_symbol, TSYMBOL, "pmatch-cdr"); cell_type_bytes = init_symbol (g_symbol, TSYMBOL, "<cell:bytes>"); cell_type_char = init_symbol (g_symbol, TSYMBOL, "<cell:char>"); cell_type_closure = init_symbol (g_symbol, TSYMBOL, "<cell:closure>"); cell_type_continuation = init_symbol (g_symbol, TSYMBOL, "<cell:continuation>"); cell_type_function = init_symbol (g_symbol, TSYMBOL, "<cell:function>"); cell_type_keyword = init_symbol (g_symbol, TSYMBOL, "<cell:keyword>"); cell_type_macro = init_symbol (g_symbol, TSYMBOL, "<cell:macro>"); cell_type_number = init_symbol (g_symbol, TSYMBOL, "<cell:number>"); cell_type_pair = init_symbol (g_symbol, TSYMBOL, "<cell:pair>"); cell_type_port = init_symbol (g_symbol, TSYMBOL, "<cell:port>"); cell_type_ref = init_symbol (g_symbol, TSYMBOL, "<cell:ref>"); cell_type_special = init_symbol (g_symbol, TSYMBOL, "<cell:special>"); cell_type_string = init_symbol (g_symbol, TSYMBOL, "<cell:string>"); cell_type_struct = init_symbol (g_symbol, TSYMBOL, "<cell:struct>"); cell_type_symbol = init_symbol (g_symbol, TSYMBOL, "<cell:symbol>"); cell_type_values = init_symbol (g_symbol, TSYMBOL, "<cell:values>"); cell_type_variable = init_symbol (g_symbol, TSYMBOL, "<cell:variable>"); cell_type_vector = init_symbol (g_symbol, TSYMBOL, "<cell:vector>"); cell_type_broken_heart = init_symbol (g_symbol, TSYMBOL, "<cell:broken-heart>"); cell_symbol_program = init_symbol (g_symbol, TSYMBOL, "%program"); cell_symbol_test = init_symbol (g_symbol, TSYMBOL, "%%test"); } struct scm * init_symbols () /*:((internal)) */ { g_free = g_cells + M2_CELL_SIZE; g_symbols = 0; cell_nil = g_free; init_symbols_ (); g_symbol_max = g_symbol; g_symbols = make_hash_table_ (500); init_symbols_ (); g_ports = cell_nil; struct scm *a = cell_nil; a = acons (cell_symbol_call_with_values, cell_symbol_call_with_values, a); a = acons (cell_symbol_boot_module, cell_symbol_boot_module, a); a = acons (cell_symbol_current_module, cell_symbol_current_module, a); a = acons (cell_symbol_mes_version, make_string0 (MES_VERSION), a); a = acons (cell_symbol_mes_datadir, make_string0 (g_datadir), a); a = acons (cell_type_bytes, make_number (TBYTES), a); a = acons (cell_type_char, make_number (TCHAR), a); a = acons (cell_type_closure, make_number (TCLOSURE), a); a = acons (cell_type_continuation, make_number (TCONTINUATION), a); a = acons (cell_type_keyword, make_number (TKEYWORD), a); a = acons (cell_type_macro, make_number (TMACRO), a); a = acons (cell_type_number, make_number (TNUMBER), a); a = acons (cell_type_pair, make_number (TPAIR), a); a = acons (cell_type_port, make_number (TPORT), a); a = acons (cell_type_ref, make_number (TREF), a); a = acons (cell_type_special, make_number (TSPECIAL), a); a = acons (cell_type_string, make_number (TSTRING), a); a = acons (cell_type_struct, make_number (TSTRUCT), a); a = acons (cell_type_symbol, make_number (TSYMBOL), a); a = acons (cell_type_values, make_number (TVALUES), a); a = acons (cell_type_variable, make_number (TVARIABLE), a); a = acons (cell_type_vector, make_number (TVECTOR), a); a = acons (cell_type_broken_heart, make_number (TBROKEN_HEART), a); a = acons (cell_closure, a, a); return a; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib.h" #include "mes/mes.h" #if __M2_PLANET__ #define M2_CELL_SIZE 12 // CONSTANT M2_CELL_SIZE 12 #else #define M2_CELL_SIZE 1 // CONSTANT M2_CELL_SIZE 12 #endif struct scm * make_vector_ (long k, struct scm *e) { struct scm *x = alloc (1); struct scm *v = alloc (k); x->type = TVECTOR; x->length = k; x->vector = v; long i; for (i = 0; i < k; i = i + 1) copy_cell (cell_ref (v, i), vector_entry (e)); return x; } struct scm * make_vector (struct scm *x) /*:((arity . n)) */ { struct scm *k = x->car; assert_number ("make-vector", k); long n = k->value; struct scm *e = cell_unspecified; if (x->cdr != cell_nil) e = x->cdr->car; return make_vector_ (n, e); } struct scm * vector_length (struct scm *x) { assert_msg (x->type == TVECTOR, "x->type == TVECTOR"); return make_number (x->length); } struct scm * vector_ref_ (struct scm *x, long i) { assert_msg (x->type == TVECTOR, "x->type == TVECTOR"); assert_msg (i < x->length, "i < x->length"); struct scm *e = cell_ref (x->vector, i); if (e->type == TREF) e = e->ref; if (e->type == TCHAR) e = make_char (e->value); if (e->type == TNUMBER) e = make_number (e->value); return e; } struct scm * vector_ref (struct scm *x, struct scm *i) { return vector_ref_ (x, i->value); } struct scm * vector_entry (struct scm *x) { if (x->type != TCHAR && x->type != TNUMBER) x = make_ref (x); return x; } struct scm * vector_set_x_ (struct scm *x, long i, struct scm *e) { assert_msg (x->type == TVECTOR, "x->type == TVECTOR"); assert_msg (i < x->length, "i < x->length"); copy_cell (cell_ref (x->vector, i), vector_entry (e)); return cell_unspecified; } struct scm * vector_set_x (struct scm *x, struct scm *i, struct scm *e) { return vector_set_x_ (x, i->value, e); } struct scm * list_to_vector (struct scm *x) { struct scm *v = make_vector_ (length__ (x), cell_unspecified); struct scm *p = v->vector; while (x != cell_nil) { copy_cell (p, vector_entry (car (x))); p = p + M2_CELL_SIZE; x = cdr (x); } return v; } struct scm * vector_to_list (struct scm *v) { struct scm *x = cell_nil; long i; struct scm *e; for (i = v->length; i; i = i - 1) { e = cell_ref (v->vector, i - 1); if (e->type == TREF) e = e->ref; x = cons (e, x); } return x; }
### GNU Mes --- Maxwell Equations of Software ### Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> ### ### This file is part of GNU Mes. ### ### Mes is free software# you can redistribute it and/or modify it ### under the terms of the GNU General Public License as published by ### the Free Software Foundation# either version 3 of the License, or (at ### your option) any later version. ### ### GNU Mes is distributed in the hope that it will be useful, but ### WITHOUT ANY WARRANTY# without even the implied warranty of ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ### GNU General Public License for more details. ### ### You should have received a copy of the GNU General Public License ### along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. # reduced instruction set: eax, ebx (some ecx for shift, edx for mul, div) # 193 instructions DEFINE add____$i32,%eax 05 DEFINE add____$i32,%ebx 81c3 DEFINE add____$i32,(%eax) 8100 DEFINE add____$i32,(%ebx) 8103 DEFINE add____$i32,0x32(%eax) 8180 DEFINE add____$i32,0x32(%ebp) 8185 DEFINE add____$i8,%eax 83c0 DEFINE add____$i8,%ebx 83c3 DEFINE add____$i8,%esp 83c4 DEFINE add____$i8,(%eax) 8300 DEFINE add____$i8,(%ebx) 8303 DEFINE add____$i8,0x32 8305 DEFINE add____$i8,0x32(%eax) 8380 DEFINE add____$i8,0x32(%ebp) 8385 DEFINE add____$i8,0x8(%eax) 8340 DEFINE add____$i8,0x8(%ebp) 8345 DEFINE add____%eax,%eax 01c0 DEFINE add____%ebp,%eax 01e8 DEFINE add____%ebx,%eax 01d8 DEFINE add____%ebx,%ebx 01db DEFINE addb___$i8,(%eax) 8000 DEFINE addb___$i8,(%ebx) 8003 DEFINE addw___$i8,(%eax) 668100 DEFINE addw___$i8,(%ebx) 668103 DEFINE and____$i32,%eax 25 DEFINE and____$i32,%ebx 81e3 DEFINE and____%ebx,%eax 21d8 DEFINE call32 e8 DEFINE call___*%eax ffd0 DEFINE call___*%ebx ffd3 DEFINE cltd 99 DEFINE cmp____$0x32,%eax 3d DEFINE cmp____$i32,%eax 3d DEFINE cmp____$i8,%eax 83f8 DEFINE cmp____$i8,%ebx 81fb DEFINE div___%ebx f7f3 DEFINE hlt f4 DEFINE idiv___%ebx f7fb DEFINE int cd DEFINE int____$0x80 cd80 DEFINE ja32 0f87 DEFINE jae32 0f83 DEFINE jb32 0f82 DEFINE jbe32 0f86 DEFINE je32 0f84 DEFINE je8 74 DEFINE jg32 0f8f DEFINE jge32 0f8d DEFINE jl32 0f8c DEFINE jle32 0f8e DEFINE jmp32 e9 DEFINE jmp____*%ebx ffe3 DEFINE jne32 0f85 DEFINE lahf 9f DEFINE lea____0x32(%ebp),%eax 8d85 DEFINE lea____0x8(%ebp),%eax 8d45 DEFINE leave c9 DEFINE mov____$i32,%eax b8 DEFINE mov____$i32,%ebx bb DEFINE mov____$i32,(%eax) c700 DEFINE mov____$i32,0x32 c705 DEFINE mov____$i32,0x8(%eax) c740 DEFINE mov____$i32,0x8(%ebp) c745 DEFINE mov____$i8,%al b0 DEFINE mov____$i8,%eax b0 DEFINE mov____%al,(%ebx) 8803 DEFINE mov____%al,0x8(%ebp) 8845 DEFINE mov____%ax,(%ebx) 668903 DEFINE mov____%ax,0x8(%ebp) 668945 DEFINE mov____%bx,%bx 6689db DEFINE mov____%cl,(%ebx) 8a08 DEFINE mov____%cx,(%ebx) 668b08 DEFINE mov____%dl,(%eax) 8810 DEFINE mov____%dl,0x8(%eax) 8850 DEFINE mov____%eax,%ebx 89c3 DEFINE mov____%eax,%ecx 89c1 DEFINE mov____%eax,(%ebx) 8903 DEFINE mov____%eax,0x32 a3 DEFINE mov____%eax,0x32(%ebp) 8985 DEFINE mov____%eax,0x8(%ebp) 8945 DEFINE mov____%ebp,%eax 89e8 DEFINE mov____%ebp,%ebx 89eb DEFINE mov____%ebx,%eax 89d8 DEFINE mov____%ebx,%ecx 89d9 DEFINE mov____%ebx,0x32 891d DEFINE mov____%ebx,0x32(%ebp) 899d DEFINE mov____%ebx,0x8(%ebp) 895d DEFINE mov____%ecx,(%ebx) 890b DEFINE mov____%edi,%ebp 89fd DEFINE mov____%edx,%eax 89d0 DEFINE mov____%edx,%ebx 89d3 DEFINE mov____%esp,%ebp 89e5 DEFINE mov____%esp,%edi 89e7 DEFINE mov____(%eax),%cl 8a08 DEFINE mov____(%eax),%cx 668b08 DEFINE mov____(%eax),%eax 8b00 DEFINE mov____(%eax),%ecx 8b08 DEFINE mov____(%ebx),%ebx 8b1b DEFINE mov____0x32(%eax),%eax 8b80 DEFINE mov____0x32(%eax),%ebx 8b98 DEFINE mov____0x32(%ebp),%eax 8b85 DEFINE mov____0x32(%ebp),%ebx 8b9d DEFINE mov____0x32,%eax a1 DEFINE mov____0x32,%ebx 8b1d DEFINE mov____0x8(%eax),%eax 8b40 DEFINE mov____0x8(%eax),%ebx 8b58 DEFINE mov____0x8(%ebp),%eax 8b45 DEFINE mov____0x8(%ebp),%ebp 8b6d DEFINE mov____0x8(%ebp),%ebx 8b5d DEFINE mov____0x8(%ebp),%ecx 8b4d DEFINE mov____0x8(%ebp),%edi 8b7d DEFINE mov____0x8(%ebp),%edx 8b55 DEFINE mov____0x8(%ebp),%esi 8b75 DEFINE mov____0x8(%ebp),%esp 8b65 DEFINE movb___%al,0x32 a2 DEFINE movb___%bl,0x32 881d DEFINE movsbl_%al,%eax 0fbec0 DEFINE movsbl_%bl,%ebx 0fbedb DEFINE movswl_%ax,%eax 0fbfc0 DEFINE movswl_%bx,%ebx 0fbfdb DEFINE movw___%ax,0x32 66a3 DEFINE movw___%bx,0x32 66891d DEFINE movzbl_%al,%eax 0fb6c0 DEFINE movzbl_%bl,%ebx 0fb6db DEFINE movzbl_(%eax),%eax 0fb600 DEFINE movzbl_(%ebx),%ebx 0fb61b DEFINE movzbl_0x32(%eax),%eax 0fb680 DEFINE movzbl_0x8(%eax),%eax 0fb640 DEFINE movzbl_0x8(%ebp),%eax 0fb645 DEFINE movzwl_%ax,%eax 0fb7c0 DEFINE movzwl_%bx,%ebx 0fb7db DEFINE movzwl_(%eax),%eax 0fb700 DEFINE movzwl_(%ebx),%ebx 0fb71b DEFINE movzwl_0x32(%eax),%eax 0fb780 DEFINE movzwl_0x32(%ebp),%eax 0fb785 DEFINE movzwl_0x8(%eax),%eax 0fb740 DEFINE mul____%ebx f7e3 DEFINE mul_____%ebx f7e3 DEFINE nop 90 DEFINE not____%eax f7d0 DEFINE not____%ebx f7d3 DEFINE or_____%ebx,%eax 09d8 DEFINE pop____%eax 58 DEFINE pop____%ebx 5b DEFINE pop____%edx 5a DEFINE push___$i32 68 DEFINE push___%eax 50 DEFINE push___%ebp 55 DEFINE push___%ebx 53 DEFINE push___%edx 52 DEFINE push___(%eax) ff30 DEFINE push___0x32(%ebp) ffb5 DEFINE push___0x8(%ebp) ff75 DEFINE ret c3 DEFINE sahf 9e DEFINE seta___%al 0f97c0 DEFINE seta___%bl 0f97c3 DEFINE setae__%al 0f93c0 DEFINE setae__%bl 0f93c3 DEFINE setb___%al 0f92c0 DEFINE setb___%bl 0f92c3 DEFINE setbe__%al 0f96c0 DEFINE setbe__%bl 0f96c3 DEFINE sete___%al 0f94c0 DEFINE sete___%bl 0f94c3 DEFINE setg___%al 0f9fc0 DEFINE setg___%bl 0f9fc3 DEFINE setge__%al 0f9dc0 DEFINE setge__%bl 0f9dc3 DEFINE setl___%al 0f9cc0 DEFINE setl___%bl 0f9cc3 DEFINE setle__%al 0f9ec0 DEFINE setle__%bl 0f9ec3 DEFINE setne__%al 0f95c0 DEFINE setne__%bl 0f95c3 DEFINE shl____$i8,%eax c1e0 DEFINE shl____$i8,%ebx c1e3 DEFINE shl____%cl,%eax d3e0 DEFINE shl____%cl,%ebx d3e3 DEFINE shr____%cl,%eax d3e8 DEFINE sub____$8,%esp 83ec DEFINE sub____$i32,%esp 81ec DEFINE sub____%al,%dl 28d0 DEFINE sub____%dl,%al 28c2 DEFINE sub____%ebx,%eax 29d8 DEFINE test___%al,%al 84c0 DEFINE test___%eax,%eax 85c0 DEFINE test___%ebx,%ebx 85db DEFINE xchg___%eax,%ebx 93 DEFINE xchg___%eax,(%esp) 870424 DEFINE xchg___%ebx,(%esp) 871c24 DEFINE xor____$i32,%eax 35 DEFINE xor____$i8,%ah 80f4 DEFINE xor____%eax,%eax 31c0 DEFINE xor____%ebx,%eax 31d8 DEFINE xor____%ebx,%ebx 31db DEFINE xor____%edx,%edx 31d2 # Enough for all of Mes + Mes C Libray when using all registers, i.e., # non-reduced instruction set #DEFINE add____$i32,%ecx 81c1 #DEFINE add____$i32,%edx 81c2 #DEFINE add____$i32,%esi 81c6 #DEFINE add____$i8,%ecx 83c1 #DEFINE add____$i8,%edx 83c2 #DEFINE add____$i8,%esi 83c6 #DEFINE add____$i8,(%ecx) 8301 #DEFINE add____$i8,(%edx) 8302 #DEFINE add____%ecx,%ebx 01cb #DEFINE add____%ecx,%ecx 01c9 #DEFINE add____%edx,%eax 01d0 #DEFINE add____%edx,%ecx 01d1 #DEFINE add____%edx,%edx 01d2 #DEFINE add____%esi,%edx 01f2 #DEFINE and____$i32,%ecx 81e1 #DEFINE and____$i32,%edx 81e2 #DEFINE and____%edx,%eax 21d0 #DEFINE and____(%edx),%eax 2302 #DEFINE cmp____%edx,%eax 39d0 #DEFINE idiv___%ecx f7f9 #DEFINE lea____0x32(%ebp),%edx 8d95 #DEFINE lea____0x8(%ebp),%edx 8d55 #DEFINE mov____$i32,%ecx b9 #DEFINE mov____$i32,%edx ba #DEFINE mov____$i32,%esi be #DEFINE mov____%al,(%edx) 8802 #DEFINE mov____%al,0x8(%edx) 8842 #DEFINE mov____%ax,(%edx) 668902 #DEFINE mov____%ax,0x32(%edx) 668982 #DEFINE mov____%ax,0x8(%edx) 668942 #DEFINE mov____%bl,(%ecx) 8819 #DEFINE mov____%eax,%edx 89c2 #DEFINE mov____%eax,%esi 89c6 #DEFINE mov____%eax,(%ecx) 8901 #DEFINE mov____%eax,(%edx) 8902 #DEFINE mov____%eax,0x32(%edx) 8982 #DEFINE mov____%eax,0x8(%edx) 8942 #DEFINE mov____%ebp,%ecx 89e9 #DEFINE mov____%ebp,%edx 89ea #DEFINE mov____%ebp,%esi 89ee #DEFINE mov____%ebx,(%ecx) 8919 #DEFINE mov____%ebx,0x8(%edx) 895a #DEFINE mov____%ecx,%eax 89c8 #DEFINE mov____%ecx,%ecx 89c9 #DEFINE mov____%ecx,%edx 89ca #DEFINE mov____%ecx,(%eax) 8908 #DEFINE mov____%ecx,(%edx) 890a #DEFINE mov____%ecx,0x32(%ebp) 898d #DEFINE mov____%ecx,0x8(%ebp) 894d #DEFINE mov____%edi,%ebx 89fb #DEFINE mov____%edx,%ecx 89d1 #DEFINE mov____%edx,(%eax) 8910 #DEFINE mov____%edx,0x32(%ebp) 8995 #DEFINE mov____%edx,0x8(%ebp) 8955 #DEFINE mov____%esi,%eax 89f0 #DEFINE mov____%esi,%ebx 89f3 #DEFINE mov____(%ecx),%ecx 8b09 #DEFINE mov____(%edx),%eax 8b02 #DEFINE mov____(%edx),%ecx 8b0a #DEFINE mov____(%edx),%edx 8b12 #DEFINE mov____0x32(%eax),%ecx 8b88 #DEFINE mov____0x32(%ebp),%ecx 8b8d #DEFINE mov____0x32(%ebp),%edx 8b95 #DEFINE mov____0x32,%ecx 8b0d #DEFINE mov____0x32,%edx 8b15 #DEFINE mov____0x8(%eax),%ecx 8b48 #DEFINE movsbl_%cl,%ecx 0fbec9 #DEFINE movsbl_%dl,%edx 0fbed2 #DEFINE movswl_%cx,%ecx 0fbfc9 #DEFINE movzbl_%cl,%ecx 0fb6c9 #DEFINE movzbl_%dl,%edx 0fb6d2 #DEFINE movzbl_(%eax),%edx 0fb610 #DEFINE movzbl_(%ecx),%ecx 0fb609 #DEFINE movzbl_(%edx),%edx 0fb612 #DEFINE movzwl_(%ecx),%ecx 0fb709 #DEFINE mul____%ecx f7e1 #DEFINE mul____%edi f7e7 #DEFINE mul____%edx f7e2 #DEFINE mul____%esi f7e6 #DEFINE or_____%ecx,%ebx 09cb #DEFINE or_____%edx,%eax 09d0 #DEFINE or_____(%edx),%eax 0b02 #DEFINE pop____%ecx 59 #DEFINE pop____%edi 5f #DEFINE push___%ecx 51 #DEFINE push___%edi 57 #DEFINE push___%esi 56 #DEFINE shl____$i8,%ecx c1e1 #DEFINE shl____$i8,%edx c1e2 #DEFINE shl____%cl,%ecx d3e1 #DEFINE sub____%eax,%edx 29c2 #DEFINE sub____%ecx,%ebx 29cb #DEFINE sub____%edx,%eax 29d0 #DEFINE sub____%edx,%ecx 29d1 #DEFINE xchg___%ebx,%ecx 87d9 #DEFINE xchg___%ecx,%edx 87ca #DEFINE xor____%ecx,%ecx 31c9 #DEFINE xor____%edx,%eax 31d0 # deprecated, remove after 0.18 DEFINE sub____%esp,$i32 81ec DEFINE sub____%esp,$i8 83ec DEFINE SYS_exit 01000000 DEFINE SYS_fork 02000000 DEFINE SYS_read 03000000 DEFINE SYS_rmdir 28000000 DEFINE SYS_write 04000000 DEFINE SYS_open 05000000 DEFINE SYS_close 06000000 DEFINE SYS_waitpid 07000000 DEFINE SYS_unlink 0a000000 DEFINE SYS_execve 0b000000 DEFINE SYS_chmod 0f000000 DEFINE SYS_lseek 13000000 DEFINE SYS_access 21000000 DEFINE SYS_brk 2d000000 DEFINE SYS_ioctl 36000000 DEFINE SYS_stat 6a000000 DEFINE SYS_fsync 76000000 DEFINE SYS_getcwd b7000000
### GNU Mes --- Maxwell Equations of Software ### Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> ### ### This file is part of GNU Mes. ### ### GNU Mes is free software; you can redistribute it and/or modify it ### under the terms of the GNU General Public License as published by ### the Free Software Foundation; either version 3 of the License, or (at ### your option) any later version. ### ### GNU Mes is distributed in the hope that it will be useful, but ### WITHOUT ANY WARRANTY; without even the implied warranty of ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ### GNU General Public License for more details. ### ### You should have received a copy of the GNU General Public License ### along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. :_start push___%ebp mov____%esp,%ebp sub____$i32,%esp %0x1054 mov____$i8,%eax !0 mov____%eax,0x32 &GLOBAL___stdin mov____$i8,%eax !1 mov____%eax,0x32 &GLOBAL___stdout mov____$i8,%eax !2 mov____%eax,0x32 &GLOBAL___stderr mov____%ebp,%eax add____$i8,%eax !4 mov____(%eax),%eax add____$i8,%eax !3 shl____$i8,%eax !0x02 add____%ebp,%eax mov____%eax,0x32 &GLOBAL_environ mov____%esp,%edi ; M2-Planet calling convention pushes forward mov____%ebp,%eax ; argc add____$i8,%eax !4 mov____(%eax),%eax push___%eax mov____%ebp,%eax ; argv add____$i8,%eax !8 push___%eax mov____0x32,%eax &GLOBAL_environ push___%eax mov____%edi,%ebp ; M2-Planet calling convention call32 %FUNCTION_main add____$i8,%esp !0x0 test___%eax,%eax mov____%eax,%ebx mov____$i32,%eax %1 int____$0x80 hlt leave ret
### Copyright (C) 2016 Jeremiah Orians ### Copyright (C) 2017 Jan Nieuwenhuizen <janneke@gnu.org> ### This file is part of M2-Planet. ### ### M2-Planet is free software: you can redistribute it and/or modify ### it under the terms of the GNU General Public License as published by ### the Free Software Foundation, either version 3 of the License, or ### (at your option) any later version. ### ### M2-Planet is distributed in the hope that it will be useful, ### but WITHOUT ANY WARRANTY; without even the implied warranty of ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ### GNU General Public License for more details. ### ### You should have received a copy of the GNU General Public License ### along with M2-Planet. If not, see <http://www.gnu.org/licenses/>. ### stage0's hex2 format ### !<label> 1 byte relative ### $<label> 2 byte address ### @<label> 2 byte relative ### &<label> 4 byte address ### %<label> 4 byte relative ### if you wish to use this header, you need to add :ELF_end to the end of your ### M1 or hex2 files. ## ELF Header :ELF_base 7F 45 4C 46 # e_ident[EI_MAG0-3] ELF's magic number 01 # e_ident[EI_CLASS] Indicating 32 bit 01 # e_ident[EI_DATA] Indicating little endianness 01 # e_ident[EI_VERSION] Indicating original elf 03 # e_ident[EI_OSABI] Set at 3 because FreeBSD is strict 00 # e_ident[EI_ABIVERSION] See above 00 00 00 00 00 00 00 # e_ident[EI_PAD] 02 00 # e_type Indicating Executable 03 00 # e_machine Indicating 386 01 00 00 00 # e_version Indicating original elf &_start # e_entry Address of the entry point %ELF_program_headers>ELF_base # e_phoff Address of program header table %ELF_section_headers>ELF_base # e_shoff Address of section header table 00 00 00 00 # e_flags 34 00 # e_ehsize Indicating our 52 Byte header 20 00 # e_phentsize size of a program header table 01 00 # e_phnum number of entries in program table 28 00 # e_shentsize size of a section header table 05 00 # e_shnum number of entries in section table 02 00 # e_shstrndx index of the section names :ELF_program_headers :ELF_program_header__text 01 00 00 00 # ph_type: PT-LOAD = 1 00 00 00 00 # ph_offset &ELF_base # ph_vaddr &ELF_base # ph_physaddr %ELF_end>ELF_base # ph_filesz %ELF_end>ELF_base # ph_memsz 07 00 00 00 # ph_flags: PF-X|PF-W|PF-R = 7 01 00 00 00 # ph_alignment :ELF_text
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2021 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib-mini.h" int main (int argc, char *argv[], char *envp[]); int _start () { asm ("mov____$i8,%eax !0"); asm ("mov____%eax,0x32 &__stdin"); asm ("mov____$i8,%eax !1"); asm ("mov____%eax,0x32 &__stdout"); asm ("mov____$i8,%eax !2"); asm ("mov____%eax,0x32 &__stderr"); asm ("mov____%ebp,%eax"); asm ("add____$i8,%eax !4"); asm ("mov____(%eax),%eax"); asm ("add____$i8,%eax !3"); asm ("shl____$i8,%eax !0x02"); asm ("add____%ebp,%eax"); asm ("mov____%eax,0x32 &environ"); asm ("push___%eax"); asm ("mov____%ebp,%eax"); asm ("add____$i8,%eax !8"); asm ("push___%eax"); asm ("mov____%ebp,%eax"); asm ("add____$i8,%eax !4"); asm ("mov____(%eax),%eax"); asm ("push___%eax"); main (); asm ("mov____%eax,%ebx"); asm ("mov____$i32,%eax %1"); asm ("int____$0x80"); asm ("hlt"); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_LIB_MINI_H #define __MES_LIB_MINI_H #if HAVE_CONFIG_H #include <mes/config.h> #endif // CONSTANT STDIN 0 #ifndef STDIN #define STDIN 0 #endif // CONSTANT STDOUT 1 #ifndef STDOUT #define STDOUT 1 #endif // CONSTANT STDERR 2 #ifndef STDERR #define STDERR 2 #endif extern char **environ; extern int __stdin; extern int __stdout; extern int __stderr; int eputs (char const *s); int puts (char const *s); int oputs (char const *s); #if SYSTEM_LIBC #include <sys/types.h> #include <unistd.h> #else //!SYSTEM_LIBC #ifndef _SIZE_T #define _SIZE_T #ifndef __SIZE_T #define __SIZE_T #ifndef __MES_SIZE_T #define __MES_SIZE_T #undef size_t typedef unsigned long size_t; #endif #endif #endif #ifndef _SSIZE_T #define _SSIZE_T #ifndef __SSIZE_T #define __SSIZE_T #ifndef __MES_SSIZE_T #define __MES_SSIZE_T #undef ssize_t #if __i386__ typedef int ssize_t; #else typedef long ssize_t; #endif #endif #endif #endif #ifndef __MES_ERRNO_T #define __MES_ERRNO_T 1 typedef int error_t; extern int errno; #endif // !__MES_ERRNO_T size_t strlen (char const *s); ssize_t _write (); ssize_t write (int filedes, void const *buffer, size_t size); #endif // !SYSTEM_LIBC #endif //__MES_LIB_MINI_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_STRING_H #define __MES_STRING_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_STRING_H #include_next <string.h> #else // ! SYSTEM_LIBC #include <sys/types.h> void *memchr (void const *block, int c, size_t size); void *memcpy (void *dest, void const *src, size_t n); void *memmove (void *dest, void const *src, size_t n); void *memset (void *s, int c, size_t n); void *memchr (void const *block, int c, size_t size); int memcmp (void const *s1, void const *s2, size_t n); void *memmem (void const *haystack, int haystack_len, void const *needle, int needle_len); char *strcat (char *dest, char const *src); char *strchr (char const *s, int c); int strcasecmp (char const *s1, char const *s2); int strcmp (char const *, char const *); char *strcpy (char *dest, char const *src); size_t strlen (char const *); char *strncpy (char *to, char const *from, size_t size); int strncmp (char const *, char const *, size_t); char *strrchr (char const *s, int c); char *strstr (char const *haystack, char const *needle); char *strlwr (char *string); char *strupr (char *string); char *strerror (int errnum); void perror (char const *message); #endif // ! SYSTEM_LIBC #endif // __MES_STRING_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib-mini.h> #include <string.h> int oputs (char const *s) { int i = strlen (s); write (__stdout, s, i); return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> void (*__call_at_exit) (void); void exit (int code) { if (__call_at_exit) (*__call_at_exit) (); _exit (code); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_LIB_H #define __MES_LIB_H #include <mes/lib-mini.h> char *cast_intp_to_charp (int const *i); char *cast_long_to_charp (long i); long cast_charp_to_long (char const *); long cast_int_to_long (int i); long cast_voidp_to_long (void const *); int __mes_debug (); void __ungetc_init (); void __ungetc_clear (int filedes); void __ungetc_set (int filedes, int c); int __ungetc_p (int filedes); double abtod (char const **p, int base); long abtol (char const **p, int base); char *dtoab (double number, int base, int signed_p); char *itoa (int number); char *ltoa (long number); char *ltoab (long x, int base); char *ntoab (long number, unsigned base, int signed_p); char *ultoa (unsigned long number); char *utoa (unsigned number); int eputc (int c); int fdgetc (int fd); char * fdgets (char *s, int count, int fd); int fdputc (int c, int fd); int fdputs (char const *s, int fd); int fdungetc (int c, int fd); char *_getcwd (char *buffer, size_t size); int ioctl3 (int filedes, size_t command, long data); int isnumber (int c, int base); int mes_open (char const *file_name, int flags, int mask); int _open2 (char const *file_name, int flags); int _open3 (char const *file_name, int flags, int mask); int oputc (int c); int oputs (char const *s); char *search_path (char const *file_name); ssize_t _read (int fd, void *buffer, size_t size); void assert_msg (int check, char *msg); extern char *__brk; extern void (*__call_at_exit) (void); #define __FILEDES_MAX 512 #if !SYSTEM_LIBC void __assert_fail (char const *s, char const *file, unsigned line, char const *function); ssize_t __buffered_read (int filedes, void *buffer, size_t size); size_t __buffered_read_clear (int filedes); void _exit (int code); long brk (void *addr); #endif // !SYSTEM_LIBC long __mesabi_imod (long a, long b); long __mesabi_idiv (long a, long b); unsigned long __mesabi_umod (unsigned long a, unsigned long b); unsigned long __mesabi_udiv (unsigned long a, unsigned long b); unsigned long __mesabi_uldiv (unsigned long a, unsigned long b, unsigned long *remainder); void *__memcpy (void *dest, void const *src, size_t n); void *__memmove (void *dest, void const *src, size_t n); void *__memset (void *s, int c, size_t n); int __raise (int signal); #endif //__MES_LIB_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ void _exit () { asm ("mov____$i32,%eax SYS_exit"); asm ("mov____0x8(%ebp),%ebx !8"); asm ("int____$0x80"); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ void _write () { asm ("mov____$i32,%eax SYS_write"); asm ("mov____0x8(%ebp),%ebx !8"); asm ("mov____0x8(%ebp),%ecx !12"); asm ("mov____0x8(%ebp),%edx !16"); asm ("int____$0x80"); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> int puts (char const *s) { oputs (s); return oputs ("\n"); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_ERRNO_H #define __MES_ERRNO_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_ERRNO_H #include_next <errno.h> #else // ! SYSTEM_LIBC #ifndef __MES_ERRNO_T #define __MES_ERRNO_T 1 typedef int error_t; #endif // !__MES_ERRNO_T extern int errno; #define ENOENT 2 #define EINTR 4 #define EIO 5 #define ENXIO 6 #define E2BIG 7 #define ENOEXEC 8 #define EBADF 9 #define ECHILD 10 #define EAGAIN 11 #define ENOMEM 12 #define EACCES 13 #define EEXIST 17 #define EXDEV 18 #define ENOTDIR 20 #define EISDIR 21 #define EINVAL 22 #define EMFILE 24 #define ENOSPC 28 #define ESPIPE 29 #define EPIPE 32 #define ERANGE 34 #define ENAMETOOLONG 36 #define ENOSYS 38 #define ELOOP 40 #if !__MESC__ //extern char const *const sys_errlist[]; extern char *sys_errlist[]; extern int sys_nerr; #endif // !__MESC__ #endif // ! SYSTEM_LIBC #endif // __MES_ERRNO_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/x86/syscall.h> static long __sys_call_internal (long sys_call) { asm ("mov____0x8(%ebp),%eax !8"); asm ("int____$0x80"); } static long __sys_call2_internal (long sys_call, long one, long two) { asm ("mov____0x8(%ebp),%eax !8"); asm ("mov____0x8(%ebp),%ebx !12"); asm ("mov____0x8(%ebp),%ecx !16"); asm ("int____$0x80"); } /* Return < 0 on error (errno-like value from kernel), or 0 on success */ int __raise (int signum) { int pid = (int) __sys_call_internal (SYS_getpid); if (pid < 0) return pid; else return (int) __sys_call2_internal (SYS_kill, pid, signum); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_CTYPE_H #define __MES_CTYPE_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_CTYPE_H #include_next <ctype.h> #else // ! SYSTEM_LIBC #include <endian.h> int isalnum (int c); int isalpha (int c); int isascii (int c); int iscntrl (int c); int isdigit (int c); int isgraph (int c); int islower (int c); int isnumber (int c, int base); int isprint (int c); int ispunct (int c); int isspace (int c); int isupper (int c); int isxdigit (int c); int tolower (int c); int toupper (int c); #endif // ! SYSTEM_LIBC #endif // __MES_CTYPE_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #undef cast_intp_to_charp #undef cast_long_to_charp #undef cast_charp_to_long #undef cast_int_to_long #undef cast_voidp_to_long char* cast_intp_to_charp (int const *i) { return (char*)i; } char* cast_long_to_charp (long i) { return (char*)i; } long cast_charp_to_long (char const *i) { return (long)i; } long cast_int_to_long (int i) { return (long)i; } long cast_voidp_to_long (void const *i) { return (long)i; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_LIMITS_H #define __MES_LIMITS_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_LIMITS_H #include_next <limits.h> #else // ! SYSTEM_LIBC #include <stdint.h> #define MB_CUR_MAX 1 #define NAME_MAX 255 #define PATH_MAX 512 #define _POSIX_OPEN_MAX 16 #endif // ! SYSTEM_LIBC #endif // __MES_LIMITS_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_STDLIB_H #define __MES_STDLIB_H 1 #ifndef __MES_COMPARISON_FN_T #define __MES_COMPARISON_FN_T typedef int (*comparison_fn_t) (void const *, void const *); #endif #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_STDLIB_H #include_next <stdlib.h> #else // ! SYSTEM_LIBC #include <sys/types.h> #include <alloca.h> void abort (void); double atof (char const *s); int atoi (char const *s); int atexit (void (*function) (void)); void *calloc (size_t nmemb, size_t size); void _exit (int status); void exit (int status); void free (void *ptr); char *getenv (char const *s); int setenv (char const *s, char const *v, int overwrite_p); void unsetenv (char const *name); void *malloc (size_t); void qsort (void *base, size_t nmemb, size_t size, int (*compar) (void const *, void const *)); int rand (void); void *realloc (void *p, size_t size); double strtod (char const *string, char **tailptr); float strtof (char const *string, char **tailptr); long double strtold (char const *string, char **tailptr); long strtol (char const *string, char **tailptr, int base); long long strtoll (char const *string, char **tailptr, int base); unsigned long strtoul (char const *string, char **tailptr, int base); unsigned long long strtoull (char const *string, char **tailptr, int base); #define EXIT_FAILURE 1 #define EXIT_SUCCESS 0 void *bsearch (void const *key, void const *array, size_t count, size_t size, comparison_fn_t compare); #endif // ! SYSTEM_LIBC #endif // __MES_STDLIB_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_SYS_RESOURCE_H #define __MES_SYS_RESOURCE_H 1 #if SYSTEM_LIBC #undef __MES_SYS_RESOURCE_H #include_next <sys/resource.h> #else // ! SYSTEM_LIBC #include <sys/time.h> struct rusage { struct timeval ru_utime; struct timeval ru_stime; long int ru_maxrss; long int ru_ixrss; long int ru_idrss; long int ru_isrss; long int ru_minflt; long int ru_majflt; long int ru_nswap; long int ru_inblock; long int ru_oublock; long int ru_msgsnd; long int ru_msgrcv; long int ru_nsignals; long int ru_nvcsw; long int ru_nivcsw; }; #define RUSAGE_SELF 0 #define RUSAGE_CHILDREN -1 #define RLIMIT_NOFILE 1024 #define OPEN_MAX RLIMIT_NOFILE int getrusage (int processes, struct rusage *rusage); #endif // ! SYSTEM_LIBC #endif // __MES_SYS_RESOURCE_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_UNISTD_H #define __MES_UNISTD_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_UNISTD_H #include_next <unistd.h> #else // ! SYSTEM_LIBC #if defined (BOOTSTRAP_WITH_POSIX) #define _POSIX_VERSION 199009L #endif #include <sys/types.h> #ifndef NULL #define NULL 0 #endif #ifndef STDIN_FILENO #define STDIN_FILENO 0 #define STDOUT_FILENO 1 #define STDERR_FILENO 2 #endif // STDIN_FILENO #ifndef STDIN_FILE_NO #define STDIN_FILE_NO 0 #define STDOUT_FILE_NO 1 #define STDERR_FILE_NO 2 #endif // STDIN_FILE_NO #ifndef R_OK #define F_OK 0 #define X_OK 1 #define W_OK 2 #define R_OK 4 #endif int access (char const *s, int mode); unsigned int alarm (unsigned int seconds); int close (int fd); int execv (char const *file_name, char *const argv[]); int execl (char const *file_name, char const *arg, ...); int execlp (char const *file_name, char const *arg, ...); int execve (char const *file, char *const argv[], char *const env[]); int execvp (char const *file, char *const argv[]); int fork (void); int fsync (int filedes); char *getcwd (char *buf, size_t size); uid_t getuid (void); gid_t getgid (void); int setgid (gid_t newgid); int setuid (uid_t newuid); uid_t geteuid (void); gid_t getegid (void); pid_t getpgrp (void); pid_t getpid (void); pid_t getppid (void); int getpgid (pid_t pid); int isatty (int fd); int link (char const *old_name, char const *new_name); off_t lseek (int fd, off_t offset, int whence); ssize_t read (int fd, void *buffer, size_t size); ssize_t readlink (char const *file_name, char *buffer, size_t size); #if __SBRK_CHAR_PTRDIFF /* xmalloc in binutils <= 2.10.1 uses this old prototype */ char *sbrk (ptrdiff_t delta); #else void *sbrk (intptr_t delta); #endif int symlink (char const *old_name, char const *new_name); int unlink (char const *file_name); ssize_t write (int filedes, void const *buffer, size_t size); #endif // ! SYSTEM_LIBC #endif // __MES_UNISTD_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> char * ltoab (long x, int base) { return ntoab (x, base, 1); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdlib.h> #if SYSTEM_LIBC #include <fcntl.h> #include <stdarg.h> // The Mes C Library defines and initializes these in crt1 int __stdin = STDIN; int __stdout = STDOUT; int __stderr = STDERR; int mes_open (char const *file_name, int flags, int mask) { int filedes = open (file_name, flags, mask); if (filedes > 2) __ungetc_clear (filedes); return filedes; } #else // !SYSTEM_LIBC int mes_open (char const *file_name, int flags, int mask) { return _open3 (file_name, flags, mask); } #endif // !SYSTEM_LIBC
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_ASSERT_H #define __MES_ASSERT_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_ASSERT_H #include_next <assert.h> #else // ! SYSTEM_LIBC #define assert(x) ((x) ? (void)0 : __assert_fail (#x, 0, 0, 0)) void __assert_fail (char const *s, char const *file, unsigned line, char const *function); #endif // ! SYSTEM_LIBC #endif // __MES_ASSERT_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> int oputc (int c) { return fdputc (c, __stdout); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> char * ultoa (unsigned long x) { return ntoab (x, 10, 1); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> char * utoa (unsigned x) { return ntoab (x, 10, 0); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <stdio.h> #include <unistd.h> ssize_t write (int filedes, void const *buffer, size_t size) { size_t skip = __buffered_read_clear (filedes); if (skip) lseek (filedes, -skip, SEEK_CUR); int r = _write (filedes, buffer, size); if (r < 0) { errno = -r; r = -1; } else errno = 0; return r; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_STDIO_H #define __MES_STDIO_H 1 #include <mes/lib.h> #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_STDIO_H #include_next <stdio.h> #else // ! SYSTEM_LIBC #ifndef _IOFBF #define _IOFBF 0 /* Fully buffered. */ #define _IOLBF 1 /* Line buffered. */ #define _IONBF 2 /* No buffering. */ #endif #ifndef BUFSIZ #define BUFSIZ 256 #endif #ifndef L_tmpnam #define L_tmpnam 100 #endif #include <sys/types.h> #define stdin (FILE*)0 #define stdout (FILE*)1 #define stderr (FILE*)2 #define SEEK_SET 0 #define SEEK_CUR 1 #define SEEK_END 2 FILE *fdopen (int fd, char const *mode); FILE *fopen (char const *file_name, char const *mode); int eputc (int c); int eputs (char const *s); int fclose (FILE * stream); int feof (FILE * stream); int ferror (FILE * stream); int fflush (FILE * stream); int fgetc (FILE * stream); char *fgets (char *s, int size, FILE * stream); int fprintf (FILE * stream, char const *format, ...); int fpurge (FILE * stream); int fputc (int c, FILE * stream); int fputs (char const *s, FILE * stream); int fscanf (FILE * stream, char const *template, ...); int fseek (FILE * stream, long offset, int whence); int getc (FILE * stream); int getchar (void); char *getlogin (void); int printf (char const *format, ...); int putc (int c, FILE * stream); int putchar (int c); int puts (char const *s); int remove (char const *file_name); int setvbuf (FILE * stream, char *buf, int mode, size_t size); int snprintf (char *str, size_t size, char const *format, ...); int sprintf (char *str, char const *format, ...); int sscanf (char const *str, char const *format, ...); int ungetc (int c, FILE * stream); long ftell (FILE * stream); size_t fread (void *ptr, size_t size, size_t count, FILE * stream); size_t freadahead (FILE * fp); size_t fwrite (void const *ptr, size_t size, size_t count, FILE * stream); #endif // ! SYSTEM_LIBC #endif // __MES_STDIO_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <linux/syscall.h> #include <arch/syscall.h> #include <stdio.h> #include <sys/types.h> #if !__MESC__ /* FIXME: We want bin/mes-mescc's x86-linux sha256sum to stay the same. */ off_t _lseek (int filedes, off_t offset, int whence) { return _sys_call3 (SYS_lseek, (int) filedes, (long) offset, (int) whence); } #endif off_t lseek (int filedes, off_t offset, int whence) { #if !__MESC__ /* FIXME: We want bin/mes-mescc's x86-linux sha256sum to stay the same. */ if (_lseek (filedes, 0, SEEK_CUR) == -1) return -1; #endif size_t skip = __buffered_read_clear (filedes); if (whence == SEEK_CUR) offset -= skip; return _sys_call3 (SYS_lseek, (int) filedes, (long) offset, (int) whence); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_SYSCALL_H #define __MES_SYSCALL_H long _sys_call (long sys_call); long _sys_call1 (long sys_call, long one); long _sys_call2 (long sys_call, long one, long two); long _sys_call3 (long sys_call, long one, long two, long three); long _sys_call4 (long sys_call, long one, long two, long three, long four); long _sys_call6 (long sys_call, long one, long two, long three, long four, long five, long six); #endif //__MES_SYSCALL_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_SYS_TYPES_H #define __MES_SYS_TYPES_H 1 #if SYSTEM_LIBC #undef __MES_SYS_TYPES_H #include_next <sys/types.h> #else // ! SYSTEM_LIBC #include <endian.h> #ifndef __MESCCLIB__ #define __MESCCLIB__ 15 #endif #ifndef EOF #define EOF -1 #endif #ifndef NULL #define NULL 0 #endif #ifndef __MES_CLOCK_T #define __MES_CLOCK_T #undef clock_t typedef long clock_t; #endif #ifndef __MES_DEV_T #define __MES_DEV_T #undef dev_t typedef long dev_t; #endif #if !defined (__MES_FILE_T) && ! defined (_FILE_T) #define __MES_FILE_T #define _FILE_T typedef long FILE; #endif #ifndef __MES_GID_T #define __MES_GID_T #undef gid_t typedef unsigned gid_t; #endif #ifndef __MES_INO_T #define __MES_INO_T #undef ino_t typedef unsigned long ino_t; #endif #if __SIZEOF_LONG_LONG__ == 8 #ifndef __MES_INO64_T #define __MES_INO64_T #undef ino64_t typedef unsigned long long ino64_t; #endif #endif // __SIZEOF_LONG_LONG__ == 8 #if !defined (__MES_INTPTR_T) && !defined (__intptr_t_defined) #define __MES_INTPTR_T #define __intptr_t_defined #undef intptr_t typedef long intptr_t; #undef uintptr_t typedef unsigned long uintptr_t; #endif #ifndef __MES_OFF_T #define __MES_OFF_T #undef off_t typedef long off_t; #endif #if __SIZEOF_LONG_LONG__ == 8 #ifndef __MES_OFF64_T #define __MES_OFF64_T #undef off64_t typedef unsigned long long off64_t; #endif #endif // __SIZEOF_LONG_LONG__ == 8 #ifndef __MES_PID_T #define __MES_PID_T #undef pid_t typedef int pid_t; #endif #ifndef __PTRDIFF_T #define __PTRDIFF_T #ifndef __MES_PTRDIFF_T #define __MES_PTRDIFF_T #undef ptrdiff_t typedef long ptrdiff_t; #endif #endif #ifndef __MES_SIGVAL_T #define __MES_SIGVAL_T #undef clock_t typedef long sigval_t; #endif #ifndef __SIZE_T #define __SIZE_T #ifndef __MES_SIZE_T #define __MES_SIZE_T #undef size_t typedef unsigned long size_t; #endif #endif #ifndef __MES_SSIZE_T #define __MES_SSIZE_T #undef ssize_t typedef long ssize_t; #endif #ifndef __MES_UID_T #define __MES_UID_T #undef uid_t typedef unsigned uid_t; #endif #ifndef __WCHAR_T #define __WCHAR_T #ifndef __MES_WCHAR_T #define __MES_WCHAR_T #undef wchar_t typedef int wchar_t; #endif #endif #endif // ! SYSTEM_LIBC #endif // __MES_SYS_TYPES_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdlib.h> #include <string.h> #if !__MESC__ #define __READ_BUFFER_MAX 128 int __read_buffer_max; #else /* FIXME: We want bin/mes-mescc's x86-linux sha256sum to stay the same. */ #define __READ_BUFFER_MAX 100 #define __read_buffer_max 100 #endif struct __read_buffer { ssize_t size; char string[__READ_BUFFER_MAX]; }; struct __read_buffer *__read_cache = 0; void __buffered_read_init (int filedes) { if (!__read_cache) { __read_cache = (struct __read_buffer *) malloc (sizeof (struct __read_buffer) * __FILEDES_MAX); #if !__MESC__ __read_buffer_max = __READ_BUFFER_MAX; char *p = getenv ("MES_READ_BUFFER"); if (p) { __read_buffer_max = atoi (p); if (__read_buffer_max < 0) __read_buffer_max = 0; if (__read_buffer_max > __READ_BUFFER_MAX) __read_buffer_max = __READ_BUFFER_MAX; } #endif } } size_t __buffered_read_clear (int filedes) { __buffered_read_init (filedes); size_t size = __read_cache[filedes].size; __read_cache[filedes].size = 0; return size; } ssize_t __buffered_read (int filedes, void *buffer, size_t size) { size_t todo = size; __buffered_read_init (filedes); struct __read_buffer *cache = &__read_cache[filedes]; char *p = buffer; if (!cache->size && size > __read_buffer_max) return _read (filedes, buffer, size); while (cache->size > 0 && todo) { todo--; *p++ = cache->string[__read_buffer_max - cache->size--]; } if (todo) { #if !__MESC__ if (todo > __read_buffer_max) return size - todo + _read (filedes, p, todo); if (__mes_debug () > 4) { eputs ("__buffered_read: "); eputs (itoa (__read_buffer_max)); eputs ("\n"); } #endif ssize_t bytes = _read (filedes, cache->string, __read_buffer_max); if (bytes < 0) return -1; if (bytes) { cache->size = bytes; if (bytes < __read_buffer_max) memmove (cache->string + __read_buffer_max - bytes, cache->string, bytes); return size - todo + __buffered_read (filedes, p, todo); } } return size - todo; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib.h" #include <stdlib.h> #define MAX(a, b) (((a) > (b)) ? (a) : (b)) int __mes_debug () { static int __mes_debug = -1; if (__mes_debug == -1) { char *p = getenv ("MES_DEBUG"); __mes_debug = p ? MAX (atoi (p), 1) : 0; } return __mes_debug; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <unistd.h> int execv (char const *file_name, char *const argv[]) { return execve (file_name, argv, environ); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020,2021 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <limits.h> #include <sys/types.h> #include <mes/lib.h> char *__getcwd_buf; char * getcwd (char *buffer, size_t size) { static char buf[PATH_MAX]; if (buffer == 0) buffer = buf; return _getcwd (buffer, size); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <sys/ioctl.h> #include <stdlib.h> #include <string.h> #include <termio.h> #if !SYSTEM_LIBC typedef unsigned char cc_t; typedef unsigned int speed_t; typedef unsigned int tcflag_t; // Use termio, termios2? #define NCCS 19 struct termios { tcflag_t c_iflag; tcflag_t c_oflag; tcflag_t c_cflag; tcflag_t c_lflag; cc_t c_line; cc_t c_cc[NCCS]; }; #endif struct ktermios { tcflag_t c_iflag; tcflag_t c_oflag; tcflag_t c_cflag; tcflag_t c_lflag; cc_t c_line; cc_t c_cc[NCCS]; speed_t c_ispeed; speed_t c_ospeed; }; int __tcgetattr (int filedes, struct termios *termios_p) { struct ktermios kernel_termios; int r = ioctl3 (filedes, TCGETS, (long) &kernel_termios); termios_p->c_iflag = kernel_termios.c_iflag; termios_p->c_oflag = kernel_termios.c_oflag; termios_p->c_cflag = kernel_termios.c_cflag; termios_p->c_lflag = kernel_termios.c_lflag; termios_p->c_line = kernel_termios.c_line; #if 0 termios_p->c_ispeed = kernel_termios.c_ispeed; termios_p->c_ospeed = kernel_termios.c_ospeed; #endif memcpy (&termios_p->c_cc[0], &kernel_termios.c_cc[0], NCCS * sizeof (cc_t)); return r; } int isatty (int filedes) { struct termios term; return __tcgetattr (filedes, &term) == 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_SYS_IOCTL_H #define __MES_SYS_IOCTL_H 1 #if SYSTEM_LIBC #undef __MES_SYS_IOCTL_H #include_next <sys/ioctl.h> #else // ! SYSTEM_LIBC #define TCGETS 0x5401 #define TCGETA 0x5405 int ioctl (int fd, unsigned long request, ...); int ioctl3 (int fd, unsigned long request, long data); #endif // ! SYSTEM_LIBC #endif // __MES_SYS_IOCTL_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_TERMIO_H #define __MES_TERMIO_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_TERMIO_H #include_next <termio.h> #else // ! SYSTEM_LIBC #define TIOCGWINSZ 0x5413 #define TCGETA 0x5405 #define TCSETAW 0x5407 #define VTIME 5 #define VMIN 6 #define ISIG 0000001 #define ICANON 0000002 #define ECHO 0000010 #define ECHOK 0000040 #define ECHONL 0000100 #define ISTRIP 0000040 #define INLCR 0000100 #define ICRNL 0000400 #define CS8 0000060 #define PARENB 0000400 struct winsize { unsigned short ws_row; unsigned short ws_col; unsigned short ws_xpixel; unsigned short ws_ypixel; }; struct termio { unsigned short c_iflag; unsigned short c_oflag; unsigned short c_cflag; unsigned short c_lflag; unsigned char c_line; unsigned char c_cc[8]; }; #endif // ! SYSTEM_LIBC #endif // __MES_TERMIO_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <fcntl.h> #include <stdarg.h> int _open2 (char const *file_name, int flags) { int mask = 0777; return _open3 (file_name, flags, mask); } int open (char const *file_name, int flags, ...) { if (flags & O_CREAT) { va_list ap; va_start (ap, flags); int mask = va_arg (ap, int); int r = _open3 (file_name, flags, mask); va_end (ap); return r; } else return _open2 (file_name, flags); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_FCNTL_H #define __MES_FCNTL_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_FCNTL_H #include_next <fcntl.h> #else // ! SYSTEM_LIBC // *INDENT-OFF* #if __linux__ #define O_RDONLY 0 #define O_WRONLY 1 #define O_RDWR 2 #define O_CREAT 0x40 #define O_EXCL 0x80 #define O_TRUNC 0x200 #define O_APPEND 0x400 #ifdef __arm__ #define O_DIRECTORY 0x4000 /*#define O_DIRECT 0x10000*/ #else #define O_DIRECTORY 0x10000 #endif #elif __GNU__ #define O_RDONLY 1 #define O_WRONLY 2 #define O_RDWR 3 #define O_CREAT 0x10 #define O_APPEND 0x100 #define O_TRUNC 0x10000 #else #error platform not supported #endif // *INDENT-ON* #define FD_CLOEXEC 1 #define F_DUPFD 0 #define F_GETFD 1 #define F_SETFD 2 #define F_GETFL 3 #define F_SETFL 4 #define creat(file_name, mode) open (file_name, O_WRONLY | O_CREAT | O_TRUNC, mode) int dup (int old); int dup2 (int old, int new); int fcntl (int filedes, int command, ...); int open (char const *s, int flags, ...); #endif // ! SYSTEM_LIBC #endif // __MES_FCNTL_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_STDARG_H #define __MES_STDARG_H 1 #if SYSTEM_LIBC #undef __MES_STDARG_H #include_next <stdarg.h> #define va_arg8(ap, type) va_arg(ap, type) #else // ! SYSTEM_LIBC #include <sys/types.h> #if __GNUC__ && __x86_64__ #define __FOO_VARARGS 1 #endif typedef char *va_list; #define va_start(ap, last) (void)((ap) = (char*)(&(last) + 1)) #define va_arg(ap, type) (type)(((long*)((ap) = ((ap) + sizeof (void*))))[-1]) #define va_align(ap, alignment) ((char*)((((unsigned long) (ap)) + (alignment) - 1) &~ ((alignment) - 1))) #define va_arg8(ap, type) (type)(((double*)((ap) = (va_align((ap), 8) + sizeof(double))))[-1]) #define va_end(ap) (void)((ap) = 0) #define va_copy(dest, src) dest = src int vexec (char const *file_name, va_list ap); int vfprintf (FILE * stream, char const *template, va_list ap); int vfscanf (FILE * stream, char const *template, va_list ap); int vprintf (char const *format, va_list ap); int vsprintf (char *str, char const *format, va_list ap); int vsnprintf (char *str, size_t size, char const *format, va_list ap); int vsscanf (char const *s, char const *template, va_list ap); #endif // ! SYSTEM_LIBC #endif // __MES_STDARG_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <fcntl.h> ssize_t read (int filedes, void *buffer, size_t size) { ssize_t bytes = __buffered_read (filedes, buffer, size); if (__mes_debug () > 4) { if (bytes == 1) { eputs ("read fd="); eputs (itoa ((int) filedes)); eputs (" c="); eputc (*(char *) buffer); eputs ("\n"); } else { eputs ("read fd="); eputs (itoa ((int) filedes)); eputs (" bytes="); eputs (itoa (bytes)); eputs ("\n"); } } return bytes; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <sys/wait.h> pid_t wait (int *status_ptr) { return waitpid (-1, status_ptr, 0); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_SYS_WAIT_H #define __MES_SYS_WAIT_H 1 #if SYSTEM_LIBC #undef __MES_SYS_WAIT_H #include_next <sys/wait.h> #else // ! SYSTEM_LIBC #ifndef __MES_PID_T #define __MES_PID_T typedef int pid_t; #endif #define WNOHANG 1 #define W_EXITCODE(status, signal) ((status) << 8 | (signal)) pid_t waitpid (pid_t pid, int *status_ptr, int options); pid_t wait (int *status_ptr); #endif // ! SYSTEM_LIBC #endif // __MES_SYS_WAIT_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> int fgetc (FILE * stream) { return fdgetc ((long) stream); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> int fputc (int c, FILE * stream) { return fdputc (c, (long) stream); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> int fputs (char const *s, FILE * stream) { return fdputs (s, (long) stream); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> int getc (FILE * stream) { return fdgetc ((long) stream); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> int putc (int c, FILE * stream) { return fdputc (c, (long) stream); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> int ungetc (int c, FILE * stream) { return fdungetc (c, (long) stream); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> void * memchr (void const *block, int c, size_t size) { char const *p = block; while (size != 0) { size = size - 1; if (c == p[0]) return (void*) p; p = p + 1; } return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> void * memmove (void *dest, void const *src, size_t n) { if (dest < src) return memcpy (dest, src, n); char *p = dest + n; char const *q = src + n; while (n--) *--p = *--q; return dest; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <signal.h> #include <unistd.h> int raise (int signum) { kill (getpid (), signum); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_SIGNAL_H #define __MES_SIGNAL_H 1 #if SYSTEM_LIBC #undef __MES_SIGNAL_H #include_next <signal.h> #else //! SYSTEM_LIBC #define _NSIG 64 #define _SIGSET_NITEMS (_NSIG / (8 * sizeof(unsigned long))) typedef struct { unsigned long items[_SIGSET_NITEMS]; } sigset_t; typedef long stack_t; #include <sys/types.h> // *INDENT-OFF* #define NSIG 30 #define SIGHUP 1 #define SIGINT 2 #define SIGQUIT 3 #define SIGILL 4 #define SIGTRAP 5 #define SIGABRT 6 #define SIGIOT 6 #define SIGBUS 7 #define SIGFPE 8 #define SIGKILL 9 #define SIGUSR1 10 #define SIGSEGV 11 #define SIGUSR2 12 #define SIGPIPE 13 #define SIGALRM 14 #define SIGTERM 15 #define SIGSTKFLT 16 #define SIGCHLD 17 #define SIGCONT 18 #define SIGSTOP 19 #define SIGTSTP 20 #define SIGTTIN 21 #define SIGTTOU 22 #define SIGURG 23 #define SIGXCPU 24 #define SIGXFSZ 25 #define SIGVTALRM 26 #define SIGPROF 27 #define SIGWINCH 28 #define SIGIO 29 #define SIGPOLL SIGIO #define FPE_INTDIV 1 #define FPE_INTOVF 2 #define FPE_FLTDIV 3 #define FPE_FLTOVF 4 #define FPE_FLTUND 5 #define FPE_FLTRES 6 #define FPE_FLTINV 7 #define FPE_FLTSUB 8 #define SA_NOCLDSTOP 0x00000001 #define SA_NOCLDWAIT 0x00000002 #define SA_SIGINFO 0x00000004 #define SA_RESTORER 0x04000000 #define SA_ONSTACK 0x08000000 #define SA_RESTART 0x10000000 #define SA_NODEFER 0x40000000 #define SA_RESETHAND 0x80000000 #define SA_NOMASK SA_NODEFER #define SA_ONESHOT SA_RESETHAND typedef struct siginfo_t { int si_signo; int si_errno; int si_code; int si_trapno; pid_t si_pid; uid_t si_uid; int si_status; clock_t si_utime; clock_t si_stime; sigval_t si_value; int si_int; void *si_ptr; int si_overrun; int si_timerid; void *si_addr; long si_band; int si_fd; short si_addr_lsb; void *si_lower; void *si_upper; int si_pkey; void *si_call_addr; int si_syscall; unsigned int si_arch; } siginfo_t; // *INDENT-ON* #if __MESC__ typedef long sighandler_t; #else typedef void (*sighandler_t) (int); #endif #if __i386__ || __x86_64__ struct sigaction { union { sighandler_t sa_handler; void (*sa_sigaction) (int signum, siginfo_t *, void *); }; unsigned long sa_flags; #if __x86_64__ long _foo0; #endif sigset_t sa_mask; #if __x86_64__ long _foo1[15]; #endif //unsigned long sa_flags; // x86? void (*sa_restorer) (void); }; #else /* uapi */ struct sigaction { union { sighandler_t sa_handler; void (*sa_sigaction) (int signum, siginfo_t *, void *); }; unsigned long sa_flags; void (*sa_restorer) (void); sigset_t sa_mask; }; #endif #define SIG_DFL ((sighandler_t)0) #define SIG_IGN ((sighandler_t)1) #define SIG_ERR ((sighandler_t)-1) #ifdef __i386__ #define EBX 0 #define ECX 1 #define EDX 2 #define ESI 3 #define EDI 4 #define EBP 5 #define EAX 6 #define DS 7 #define ES 8 #define FS 9 #define GS 10 #define ORIG_EAX 11 #define EIP 12 #define CS 13 #define EFL 14 #define UESP 15 #define SS 16 #define FRAME_SIZE 17 /* Type for general register. */ typedef int greg_t; /* Number of general registers. */ #define NGREG 19 /* Container for all general registers. */ typedef greg_t gregset_t[NGREG]; /* Definitions taken from the kernel headers. */ struct _libc_fpreg { unsigned short int significand[4]; unsigned short int exponent; }; struct _libc_fpstate { unsigned long int cw; unsigned long int sw; unsigned long int tag; unsigned long int ipoff; unsigned long int cssel; unsigned long int dataoff; unsigned long int datasel; struct _libc_fpreg _st[8]; unsigned long int status; }; /* Structure to describe FPU registers. */ typedef struct _libc_fpstate *fpregset_t; typedef struct { gregset_t gregs; /* Due to Linux's history we have to use a pointer here. The SysV/i386 ABI requires a struct with the values. */ fpregset_t fpregs; unsigned long int oldmask; unsigned long int cr2; } mcontext_t; /* Userlevel context. */ typedef struct ucontext { unsigned long int uc_flags; struct ucontext *uc_link; stack_t uc_stack; mcontext_t uc_mcontext; sigset_t uc_sigmask; struct _libc_fpstate __fpregs_mem; } ucontext_t; #endif // !__i386__ int kill (pid_t pid, int signum); int raise (int); int sigaction (int signum, struct sigaction const *act, struct sigaction *oldact); int sigaddset (sigset_t * set, int signum); #if __MESC__ void *signal (int signum, void *action); #else sighandler_t signal (int signum, sighandler_t action); #endif int sigemptyset (sigset_t * set); #ifndef SIG_BLOCK #define SIG_BLOCK 0 #define SIG_UNBLOCK 1 #define SIG_SETMASK 2 #endif int sigprocmask (int how, sigset_t const *set, sigset_t * oldset); #endif //! SYSTEM_LIBC #endif // __MES_SIGNAL_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <linux/syscall.h> #include <arch/syscall.h> #include <sys/stat.h> int chmod (char const *file_name, mode_t mask) { long long_file_name = cast_charp_to_long (file_name); long long_mask = cast_int_to_long (mask); return _sys_call2 (SYS_chmod, long_file_name, long_mask); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <linux/syscall.h> #include <arch/syscall.h> #include <time.h> int clock_gettime (clockid_t clk_id, struct timespec *tp) { long long_clk_id = cast_int_to_long (clk_id); long long_tp = cast_voidp_to_long (tp); return _sys_call2 (SYS_clock_gettime, long_clk_id, long_tp); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_TIME_H #define __MES_TIME_H 1 #if SYSTEM_LIBC #undef __MES_TIME_H #include_next <time.h> #else // ! SYSTEM_LIBC #ifndef __MES_TIME_T #define __MES_TIME_T 1 typedef long int clockid_t; typedef long int time_t; #endif struct tm { int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_year; int tm_wday; int tm_yday; int tm_isdst; }; #ifndef __MES_STRUCT_TIMESPEC #define __MES_STRUCT_TIMESPEC struct timespec { long tv_sec; long tv_nsec; }; #endif // __MES_STRUCT_TIMESPEC #define CLOCK_PROCESS_CPUTIME_ID 2 int clock_gettime (clockid_t clk_id, struct timespec *tp); struct tm *localtime (time_t const *timep); struct tm *gmtime (time_t const *time); time_t mktime (struct tm *broken_time); int nanosleep (struct timespec const *requested_time, struct timespec const *remaining); time_t time (time_t * tloc); #endif // ! SYSTEM_LIBC #endif // __MES_TIME_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> int execve (char const *file_name, char *const argv[], char *const env[]) { return _sys_call3 (SYS_execve, (long) file_name, (long) argv, (long) env); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> int fsync (int filedes) { return _sys_call1 (SYS_fsync, (int) filedes); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_SYS_TIME_H #define __MES_SYS_TIME_H 1 #if SYSTEM_LIBC #undef __MES_SYS_TIME_H #include_next <sys/time.h> #else // ! SYSTEM_LIBC struct timeval { long tv_sec; long tv_usec; }; struct timezone { int tz_minuteswest; int tz_dsttime; }; struct itimerval { struct timeval it_interval; struct timeval it_value; }; #define ITIMER_REAL 0 #define ITIMER_VIRTUAL 1 #define ITIMER_PROF 2 int gettimeofday (struct timeval *tv, struct timezone *tz); int setitimer (int which, struct itimerval const *new, struct itimerval *old); #endif // ! SYSTEM_LIBC #endif // __MES_SYS_TIME_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2021 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * Copyright © 2021 Danny Milosavljevic <dannym@scratchpost.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <string.h> #include <stddef.h> #include <stdint.h> char *__brk = 0; void * malloc (size_t size) { if (!__brk) __brk = cast_long_to_charp (brk (0)); /* align what we give back. */ __brk = (char*) (((uintptr_t) __brk + sizeof (max_align_t) - 1) & -sizeof (max_align_t)); if (brk (__brk + size) == -1) return 0; char *p = __brk; __brk = __brk + size; return p; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * Copyright © 2021 Danny Milosavljevic <dannym@scratchpost.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_STDDEF_H #define __MES_STDDEF_H 1 #if SYSTEM_LIBC #undef __MES_STDDEF_H #include_next <stddef.h> #else // ! SYSTEM_LIBC #include <sys/types.h> #include <stdint.h> #include <unistd.h> #ifndef offsetof #if __MESC__ #define offsetof(type, field) (&((type *)0)->field) #else // !__MESC__ #define offsetof(type, field) ((size_t)&((type *)0)->field) #endif // !__MESC__ #endif // offsetof /* Note: on banana gcc, max_align_t is 16 Byte big instead! */ typedef double max_align_t; #endif // ! SYSTEM_LIBC #endif // __MES_STDDEF_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * Copyright © 2018 Peter De Wachter <pdewacht@gmail.com> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_STDINT_H #define __MES_STDINT_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_STDINT_H #include_next <stdint.h> #else // ! SYSTEM_LIBC #undef unsigned #undef uint8_t #undef int8_t #undef uint16_t #undef int16_t #undef uint32_t #undef int32_t #undef uint64_t #undef int64_t #undef uintptr_t #undef intmax_t #undef intptr_t #undef uintmax_t #undef ptrdiff_t typedef unsigned char uint8_t; typedef char int8_t; typedef unsigned short uint16_t; typedef short int16_t; typedef unsigned uint32_t; typedef int int32_t; #if __SIZEOF_LONG_LONG__ == 8 typedef unsigned long long uint64_t; typedef long long int64_t; #endif // __SIZEOF_LONG_LONG__ == 8 typedef int intmax_t; typedef unsigned uintmax_t; #include <sys/types.h> #define CHAR_BIT 8 #define CHAR_MAX 255 #define UCHAR_MAX 255 #define INT8_MAX 127 #define INT8_MIN (-INT8_MAX-1) #define UINT8_MAX 255 #define INT16_MAX 32767 #define INT16_MIN (-INT16_MAX-1) #define UINT16_MAX 65535 #define INT32_MAX 2147483647 #define INT32_MIN (-INT32_MAX-1) #define UINT32_MAX 4294967295U #define INT64_MAX 9223372036854775807LL #define INT64_MIN (-INT64_MAX-1) #define UINT64_MAX 18446744073709551615ULL #define INT_MIN -2147483648 #define INT_MAX 2147483647 #if __i386__ || __arm__ #define LONG_MIN INT_MIN #define LONG_MAX INT_MAX #define UINT_MAX UINT32_MAX #define ULONG_MAX UINT32_MAX #define LLONG_MIN INT64_MIN #define LLONG_MAX INT64_MAX #define SIZE_MAX UINT32_MAX #elif __x86_64__ #define LONG_MIN INT64_MIN #define LONG_MAX INT64_MAX #define UINT_MAX UINT32_MAX #define ULONG_MAX UINT64_MAX #define LLONG_MIN INT64_MIN #define LLONG_MAX INT64_MAX #define SIZE_MAX UINT64_MAX #endif #endif // ! SYSTEM_LIBC #endif // __MES_STDINT_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <mes/lib.h> #include <fcntl.h> int _open3 (char const *file_name, int flags, int mask) { int r = _sys_call3 (SYS_open, (long) file_name, (int) flags, (int) mask); if (r > 2) { __ungetc_clear (r); __buffered_read_clear (r); } return r; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <mes/lib.h> #include <fcntl.h> ssize_t _read (int filedes, void *buffer, size_t size) { return _sys_call3 (SYS_read, (int) filedes, (long) buffer, (long) size); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <time.h> #include <sys/time.h> #include <stdlib.h> time_t time (time_t * result) { struct timeval tv; struct timezone tz; if (gettimeofday (&tv, &tz) != 0) return (time_t) - 1; if (result) *result = tv.tv_sec; return tv.tv_sec; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <sys/types.h> pid_t waitpid (pid_t pid, int *status_ptr, int options) { #if __i386__ return _sys_call3 (SYS_waitpid, (long) pid, (long) status_ptr, (int) options); #elif __x86_64__ || __arm__ return _sys_call4 (SYS_wait4, (long) pid, (long) status_ptr, (int) options, 0); #else #error arch not supported #endif }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <errno.h> #include <linux/x86/syscall.h> long __sys_call (long sys_call) { asm ("mov____0x8(%ebp),%eax !8"); asm ("int____$0x80"); } long __sys_call1 (long sys_call, long one) { asm ("mov____0x8(%ebp),%eax !8"); asm ("mov____0x8(%ebp),%ebx !12"); asm ("int____$0x80"); } long __sys_call2 (long sys_call, long one, long two) { asm ("mov____0x8(%ebp),%eax !8"); asm ("mov____0x8(%ebp),%ebx !12"); asm ("mov____0x8(%ebp),%ecx !16"); asm ("int____$0x80"); } long __sys_call3 (long sys_call, long one, long two, long three) { asm ("mov____0x8(%ebp),%eax !8"); asm ("mov____0x8(%ebp),%ebx !12"); asm ("mov____0x8(%ebp),%ecx !16"); asm ("mov____0x8(%ebp),%edx !20"); asm ("int____$0x80"); } long __sys_call4 (long sys_call, long one, long two, long three, long four) { asm ("mov____0x8(%ebp),%eax !8"); asm ("mov____0x8(%ebp),%ebx !12"); asm ("mov____0x8(%ebp),%ecx !16"); asm ("mov____0x8(%ebp),%edx !20"); asm ("mov____0x8(%ebp),%esi !24"); asm ("int____$0x80"); } long _sys_call (long sys_call) { long r = __sys_call (sys_call); if (r < 0) { errno = -r; r = -1; } else errno = 0; return r; } long _sys_call1 (long sys_call, long one) { long r = __sys_call1 (sys_call, one); if (r < 0) { errno = -r; r = -1; } else errno = 0; return r; } long _sys_call2 (long sys_call, long one, long two) { long r = __sys_call2 (sys_call, one, two); if (r < 0) { errno = -r; r = -1; } else errno = 0; return r; } long _sys_call3 (long sys_call, long one, long two, long three) { long r = __sys_call3 (sys_call, one, two, three); if (r < 0) { errno = -r; r = -1; } else errno = 0; return r; } long _sys_call4 (long sys_call, long one, long two, long three, long four) { long r = __sys_call4 (sys_call, one, two, three, four); if (r < 0) { errno = -r; r = -1; } else errno = 0; return r; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <unistd.h> pid_t getpid () { return _sys_call (SYS_getpid); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <unistd.h> int kill (pid_t pid, int signum) { return _sys_call2 (SYS_kill, (long) pid, (long) signum); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_CC_H #define __MES_CC_H typedef struct scm* SCM; #if __MESC__ typedef long FUNCTION; typedef long function0_t; typedef long function1_t; typedef long function2_t; typedef long function3_t; typedef long functionn_t; #else // !__MESC__ typedef SCM (*FUNCTION) (void); typedef SCM (*function0_t) (void); typedef SCM (*function1_t) (SCM); typedef SCM (*function2_t) (SCM, SCM); typedef SCM (*function3_t) (SCM, SCM, SCM); typedef SCM (*functionn_t) (SCM); #endif // !__MESC__ #endif //__MES_CC_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib.h" #include "mes/mes.h" struct scm * apply_builtin0 (struct scm *fn) { struct scm *(*fp) (void) = (function0_t) builtin_function (fn); return fp (); } struct scm * apply_builtin1 (struct scm *fn, struct scm *x) { struct scm *(*fp) (struct scm *) = (function1_t) builtin_function (fn); return fp (x); } struct scm * apply_builtin2 (struct scm *fn, struct scm *x, struct scm *y) { struct scm *(*fp) (struct scm *, struct scm *) = (function2_t) builtin_function (fn); return fp (x, y); } struct scm * apply_builtin3 (struct scm *fn, struct scm *x, struct scm *y, struct scm *z) { struct scm *(*fp) (struct scm *, struct scm *, struct scm *) = (function3_t) builtin_function (fn); return fp (x, y, z); } #undef cast_charp_to_scmp #undef cast_charp_to_scmpp #undef cast_voidp_to_charp #undef cast_scmp_to_long #undef cast_scmp_to_charp struct scm * cast_charp_to_scmp (char const *i) { return (struct scm *)i; } struct scm ** cast_charp_to_scmpp (char const *i) { return (struct scm **)i; } char* cast_voidp_to_charp (void const *i) { return (char*)i; } long cast_scmp_to_long (struct scm *i) { return (long)i; } char* cast_scmp_to_charp (struct scm *i) { return (char*)i; }
/* * GNU Mes --- Maxwell Equations of Software * Copyright © 2021 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib-mini.h> #define extern #include <mes/symbols.h> #include <mes/mes.h>
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <ctype.h> int islower (int c) { return c >= 'a' && c <= 'z'; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <ctype.h> int isupper (int c) { return c >= 'A' && c <= 'Z'; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <ctype.h> int tolower (int c) { if (isupper (c)) return c + ('a' - 'A'); return c; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <ctype.h> int toupper (int c) { if (islower (c)) return c - ('a' - 'A'); return c; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> double abtod (char const **p, int base) { char const *s = *p; double d = 0; int sign_p = 0; if (!base) base = 10; double dbase = base; long i = abtol (&s, base); long f = 0; long e = 0; if (*s == '.') { s++; f = abtol (&s, base); } if (*s == 'e') { s++; e = abtol (&s, base); } d = i + f / dbase; if (e < 0) while (e++) d = d / dbase; while (e--) d = d * dbase; *p = s; return sign_p ? -d : d; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <limits.h> #include <string.h> char * dtoab (double d, int base, int signed_p) { static char dtoa_buf[40]; long i = (long) d; char *p = ntoab (i, base, signed_p); strcpy (dtoa_buf, p); long f = (d - (double) i) * (double) 100000000; if (f) { if (f < 0) f = -f; strcat (dtoa_buf, "."); p = ntoab (f, base, 1); strcat (dtoa_buf, p); p = strchr (dtoa_buf, 0); p--; while (*p && *p == '0') *p-- = 0; } return dtoa_buf; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <string.h> #include <stdlib.h> #include <unistd.h> char * search_path (char const *file_name) { static char buf[256]; char *path = getenv ("PATH"); if (__mes_debug ()) { eputs ("\n search-path: "); eputs (file_name); eputs ("\n"); } while (*path) { char *end = strchr (path, ':'); if (!end) end = strchr (path, '\0'); strncpy (buf, path, end - path); buf[end - path] = 0; if (__mes_debug ()) { eputs (" dir: "); eputs (buf); eputs ("\n"); } if (buf[end - path] != '/') strcat (buf, "/"); strcat (buf, file_name); if (!access (buf, X_OK)) { if (__mes_debug ()) { eputs (" found: "); eputs (buf); eputs ("\n"); } return buf; } path = end + 1; } return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <string.h> #include <unistd.h> int execvp (char const *file_name, char *const argv[]) { if (!strchr (file_name, '/')) file_name = search_path (file_name); if (!file_name) { errno = ENOENT; return -1; } if (__mes_debug ()) { eputs (" EXEC: "); eputs (file_name); eputs ("\n"); int i = 0; while (argv[i]) { eputs (" arg["); eputs (itoa (i)); eputs ("]: "); eputs (argv[i]); eputs ("\n"); i++; } } return execve (file_name, argv, environ); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> #include <unistd.h> int fclose (FILE * stream) { int fd = (long) stream; return close (fd); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> FILE * fdopen (int fd, char const *mode) { return (FILE *) (long) fd; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> int ferror (FILE * stream) { int fd = (long) stream; if (fd == -1) return -1; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> #include <sys/stat.h> #include <unistd.h> int fflush (FILE * stream) { int filedes = (long) stream; if (filedes < 3) return 0; return fsync (filedes); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * Copyright © 2018 Jeremiah Orians <jeremiah@pdp10.guru> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <assert.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include <unistd.h> FILE * fopen (char const *file_name, char const *opentype) { if (__mes_debug ()) { eputs ("fopen "); eputs (file_name); eputs (" "); eputs (opentype); eputs ("\n"); } int fd; int mode = 0600; if ((opentype[0] == 'a' || !strcmp (opentype, "r+")) && !access (file_name, O_RDONLY)) { int flags = O_RDWR; if (opentype[0] == 'a') flags |= O_APPEND; fd = _open3 (file_name, flags, mode); } else if (opentype[0] == 'w' || opentype[0] == 'a' || !strcmp (opentype, "r+")) { char *plus_p = strchr (opentype, '+'); int flags = plus_p ? O_RDWR | O_CREAT : O_WRONLY | O_CREAT | O_TRUNC; fd = _open3 (file_name, flags, mode); } else fd = _open3 (file_name, O_RDONLY, 0); if (__mes_debug ()) { eputs (" => fd="); eputs (itoa (fd)); eputs ("\n"); } if (!fd) { eputs (" ***MES LIB C*** fopen of stdin: signal me in band\n"); assert (0); } if (fd < 0) fd = 0; return (FILE *) (long) fd; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdarg.h> #include <stdio.h> int fprintf (FILE * stream, char const *format, ...) { va_list ap; va_start (ap, format); int r = vfprintf (stream, format, ap); va_end (ap); return r; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdio.h> #include <string.h> #include <unistd.h> int __fungetc_p (FILE * stream) { return __ungetc_p ((int) (long) stream); } size_t fread (void *data, size_t size, size_t count, FILE * stream) { if (!size || !count) return 0; size_t todo = size * count; char *buf = (char *) data; int bytes = 0; while (__fungetc_p (stream) && todo-- && ++bytes) *buf++ = fgetc (stream); int filedes = (long) stream; if (todo) { int r = read (filedes, buf, todo); if (r < 0 && !bytes) bytes = r; else bytes += r; } if (__mes_debug ()) { static char debug_buf[4096]; eputs ("fread fd="); eputs (itoa (filedes)); eputs (" bytes="); eputs (itoa (bytes)); eputs ("\n"); if (bytes > 0 && bytes < sizeof (debug_buf)) { strncpy (debug_buf, data, bytes); buf[bytes] = 0; eputs ("fread buf="); eputs (debug_buf); eputs ("\n"); } } if (bytes > 0) return bytes / size; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdio.h> #include <unistd.h> int fseek (FILE * stream, long offset, int whence) { int filedes = (long) stream; off_t pos = lseek (filedes, offset, whence); if (__mes_debug ()) { eputs ("fread fd="); eputs (itoa (filedes)); eputs (" =>"); eputs (itoa (pos)); eputs ("\n"); } if (pos >= 0) return 0; return -1; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <fcntl.h> #include <stdio.h> #include <unistd.h> long ftell (FILE * stream) { return lseek ((int) (long) stream, 0, SEEK_CUR); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdio.h> #include <unistd.h> size_t fwrite (void const *data, size_t size, size_t count, FILE * stream) { if (__mes_debug () > 1) { eputs ("fwrite "); eputs (itoa ((int) (long) stream)); eputs (" "); eputs (itoa (size)); eputs ("\n"); } if (!size || !count) return 0; int filedes = (long) stream; int bytes = write (filedes, data, size * count); if (__mes_debug () > 2) { eputs (" => "); eputs (itoa (bytes)); eputs ("\n"); } if (bytes > 0) return bytes / size; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdarg.h> #include <stdio.h> int printf (char const *format, ...) { va_list ap; int r; #if __GNUC__ && __x86_64__ && !SYSTEM_LIBC #define __FUNCTION_ARGS 1 ap += (__FOO_VARARGS + (__FUNCTION_ARGS << 1)) << 3; #undef __FUNCTION_ARGS #endif va_start (ap, format); r = vprintf (format, ap); va_end (ap); return r; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> #include <sys/stat.h> #include <unistd.h> int remove (char const *file_name) { struct stat buf; if (stat (file_name, &buf) < 0) return -1; if (S_ISDIR (buf.st_mode)) return rmdir (file_name); return unlink (file_name); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdarg.h> #include <stdio.h> int snprintf (char *str, size_t size, char const *format, ...) { va_list ap; int r; #if __GNUC__ && __x86_64__ && !SYSTEM_LIBC #define __FUNCTION_ARGS 3 ap += (__FOO_VARARGS + (__FUNCTION_ARGS << 1)) << 3; #undef __FUNCTION_ARGS #endif va_start (ap, format); r = vsnprintf (str, size, format, ap); va_end (ap); return r; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdarg.h> #include <stdio.h> int sprintf (char *str, char const *format, ...) { va_list ap; int r; #if __GNUC__ && __x86_64__ && !SYSTEM_LIBC #define __FUNCTION_ARGS 2 ap += (__FOO_VARARGS + (__FUNCTION_ARGS << 1)) << 3; #undef __FUNCTION_ARGS #endif va_start (ap, format); r = vsprintf (str, format, ap); va_end (ap); return r; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdarg.h> #include <stdio.h> int sscanf (char const *str, char const *template, ...) { va_list ap; va_start (ap, template); int r = vsscanf (str, template, ap); va_end (ap); return r; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdarg.h> #include <stdio.h> #include <string.h> int vfprintf (FILE * f, char const *format, va_list ap) { int fd = (long) f; char const *p = format; int count = 0; while (*p) if (*p != '%') { count++; fputc (*p++, f); } else { p++; char c = *p; int left_p = 0; int precision = -1; int width = -1; if (c == '-') { left_p = 1; c = *++p; } char pad = ' '; if (c == ' ') { pad = c; c = *p++; } if (c == '0') { pad = c; c = *p++; } if (c >= '0' && c <= '9') { width = abtol (&p, 10); c = *p; } else if (c == '*') { width = va_arg (ap, int); c = *++p; } if (c == '.') { c = *++p; if (c >= '0' && c <= '9') { precision = abtol (&p, 10); c = *p; } else if (c == '*') { precision = va_arg (ap, int); c = *++p; } } if (c == 'l') c = *++p; if (c == 'l') { eputs ("vfprintf: skipping second: l\n"); c = *++p; } switch (c) { case '%': { fputc (*p, f); count++; break; } case 'c': { char _c; _c = va_arg (ap, long); fputc (_c, f); break; } case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': { long d = va_arg (ap, long); int base = c == 'o' ? 8 : c == 'x' || c == 'X' ? 16 : 10; char *s = ntoab (d, base, c != 'u' && c != 'x' && c != 'X'); if (c == 'X') strupr (s); int length = strlen (s); if (precision == -1) precision = length; if (!left_p) { while (width-- > precision) { fputc (pad, f); count++; } while (precision > length) { fputc ('0', f); precision--; width--; count++; } } while (*s) { if (precision-- <= 0) break; width--; fputc (*s++, f); count++; } while (width > 0) { width--; fputc (pad, f); count++; } break; } case 's': { char *s = va_arg (ap, char *); int length = strlen (s); if (precision == -1) precision = length; if (!left_p) { while (width-- > precision) { fputc (pad, f); count++; } while (precision > length) { fputc (' ', f); precision--; width--; count++; } } while (*s) { if (precision-- <= 0) break; width--; fputc (*s++, f); count++; } while (width > 0) { width--; fputc (pad, f); count++; } break; } case 'f': case 'e': case 'E': case 'g': case 'G': { double d = va_arg8 (ap, double); char *s = dtoab (d, 10, 1); if (c == 'E' || c == 'G') strupr (s); int length = strlen (s); if (precision == -1) precision = length; if (!left_p) { while (width-- > precision) { fputc (pad, f); count++; } while (precision > length) { fputc (' ', f); precision--; width--; count++; } } while (*s) { if (precision-- <= 0) break; width--; fputc (*s++, f); count++; } while (width > 0) { width--; fputc (pad, f); count++; } break; } case 'n': { int *n = va_arg (ap, int *); *n = count; break; } default: { eputs ("vfprintf: not supported: %:"); eputc (c); eputs ("\n"); p++; } } p++; } va_end (ap); return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdarg.h> #include <stdio.h> int vprintf (char const *format, va_list ap) { return vfprintf (stdout, format, ap); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <ctype.h> #include <stdarg.h> #include <string.h> int vsnprintf (char *str, size_t size, char const *format, va_list ap) { char const *p = format; int count = 0; char c; while (*p) if (*p != '%') { c = *p++; if (count < size) *str++ = c; count++; } else { p++; c = *p; int left_p = 0; int precision = -1; int width = -1; if (c == '-') { left_p = 1; c = *++p; } char pad = ' '; if (c == ' ') { pad = c; c = *p++; } if (c == '0') { pad = c; c = *p++; } if (c >= '0' && c <= '9') { width = abtol (&p, 10); c = *p; } else if (c == '*') { width = va_arg (ap, long); c = *++p; } if (c == '.') { c = *++p; if (c >= '0' && c <= '9') { precision = abtol (&p, 10); c = *p; } else if (c == '*') { precision = va_arg (ap, long); c = *++p; } } if (c == 'l') c = *++p; if (c == 'l') c = *++p; if (c == 'l') { eputs ("vsnprintf: skipping second: l\n"); c = *++p; } switch (c) { case '%': { if (count < size) *str++ = *p; count++; break; } case 'c': { c = va_arg (ap, long); if (count < size) *str++ = c; count++; break; } case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': { long d = va_arg (ap, long); int base = c == 'o' ? 8 : c == 'x' || c == 'X' ? 16 : 10; char *s = ntoab (d, base, c != 'u' && c != 'x' && c != 'X'); if (c == 'X') strupr (s); int length = strlen (s); if (precision == -1) precision = length; if (!left_p) { while (width-- > precision) { if (count < size) *str++ = pad; count++; } while (precision > length) { if (count < size) *str++ = '0'; precision--; width--; count++; } } while (*s) { if (precision-- <= 0) break; width--; c = *s++; if (count < size) *str++ = c; count++; } while (width > 0) { width--; if (count < size) *str++ = pad; count++; } break; } case 's': { char *s = va_arg (ap, char *); int length = s ? strlen (s) : 0; if (precision == -1) precision = length; if (!left_p) { while (width-- > precision) { if (count < size) *str++ = pad; count++; } while (width > length) { if (count < size) *str++ = ' '; precision--; width--; count++; } } while (s && *s) { if (precision-- <= 0) break; width--; c = *s++; if (count < size) *str++ = c; count++; } while (width > 0) { width--; if (count < size) *str++ = pad; count++; } break; } case 'f': case 'e': case 'E': case 'g': case 'G': { double d = va_arg8 (ap, double); char *s = dtoab (d, 10, 1); if (c == 'E' || c == 'G') strupr (s); int length = strlen (s); if (precision == -1) precision = length; if (!left_p) { while (width-- > precision) { if (count < size) *str++ = pad; count++; } while (precision > length) { if (count < size) *str++ = ' '; precision--; width--; count++; } } while (*s) { if (precision-- <= 0) break; width--; c = *s++; if (count < size) *str++ = c; count++; } while (width > 0) { width--; if (count < size) *str++ = pad; count++; } break; } case 'n': { int *n = va_arg (ap, int *); *n = count; break; } default: { eputs ("vsnprintf: not supported: %:"); eputc (c); eputs ("\n"); p++; } } p++; } va_end (ap); if (count < size) *str = 0; return count; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <limits.h> #include <stdarg.h> int vsprintf (char *str, char const *format, va_list ap) { return vsnprintf (str, LONG_MAX, format, ap); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <ctype.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> int vsscanf (char const *s, char const *template, va_list ap) { char *p = (char *) s; char const *t = template; int count = 0; while (*t && *p) if (*t != '%') { t++; p++; } else { t++; char c = *t; if (c == 'l') c = *++t; switch (c) { case '%': { p++; break; } case 'c': { char *c = va_arg (ap, char *); *c = *p++; count++; break; } case 'd': case 'i': case 'u': { int *d = va_arg (ap, int *); *d = abtol ((char const **)&p, 10); count++; break; } case 'e': case 'f': case 'g': case 'E': case 'G': { float *f = va_arg (ap, float *); *f = strtod (p, &p); count++; break; } default: { eputs ("vsscanf: not supported: %:"); eputc (c); eputs ("\n"); t++; p++; } } t++; } va_end (ap); return count; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> #include <string.h> void * calloc (size_t nmemb, size_t size) { size_t count = nmemb * size; void *p = malloc (count); memset (p, 0, count); return p; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * Copyright © 2021 Paul Dersey <pdersey@gmail.com> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> #include <string.h> void qswap (void *a, void *b, size_t size) { char *pa = a; char *pb = b; do { char tmp = *pa; *pa++ = *pb; *pb++ = tmp; } while (--size > 0); } size_t qpart (void *base, size_t count, size_t size, int (*compare) (void const *, void const *)) { void *p = base + count * size; size_t i = 0; for (size_t j = 0; j < count; j++) { int c = compare (base + j * size, p); if (c < 0) { #if 1 //__x86_64__ qswap (base + i * size, base + j * size, size); #else int p1 = base + i * size; int p2 = base + j * size; qswap (p1, p2, size); #endif i++; } else if (c == 0) i++; } if (compare (base + count * size, base + i * size) < 0) qswap (base + i * size, base + count * size, size); return i; } void qsort (void *base, size_t count, size_t size, int (*compare) (void const *, void const *)) { if (count > 1) { int p = qpart (base, count - 1, size, compare); qsort (base, p, size, compare); #if 1 //__x86_64__ qsort (base + p * size, count - p, size, compare); #else int p1 = base + p * size; int p2 = count - p; qsort (p1, p2, size, compare); #endif } }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdlib.h> #include <string.h> double strtod (char const *string, char **tailptr) { int base = 10; if (!strncmp (string, "0x", 2)) { string += 2; base = 16; } if (tailptr) { *tailptr = (char *) string; return abtod ((char const **) tailptr, base); } char **p = (char **) &string; return abtod ((char const **) p, base); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> float strtof (char const *string, char **tailptr) { return strtod (string, tailptr); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdlib.h> #include <string.h> long strtol (char const *string, char **tailptr, int base) { if (!strncmp (string, "0x", 2)) { string += 2; base = 16; } if (tailptr) { *tailptr = (char *) string; return abtol ((char const **) tailptr, base); } char **p = (char **) &string; return abtol ((char const **) p, base); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> long double strtold (char const *string, char **tailptr) { return strtod (string, tailptr); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> long long int strtoll (char const *string, char **tailptr, int base) { return strtol (string, tailptr, base); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> unsigned long strtoul (char const *string, char **tailptr, int base) { return strtol (string, tailptr, base); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> unsigned long long strtoull (char const *string, char **tailptr, int base) { return strtol (string, tailptr, base); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 1997--2015,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * Copyright (C) 1997--2015,2018 Han-Wen Nienhuys <hanwen@xs4all.nl> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> /** locate a substring. #memmem# finds the first occurrence of #needle# in #haystack#. This is not ANSI-C. The prototype is not in accordance with the Linux Programmer's Manual v1.15, but it is with /usr/include/string.h */ unsigned char * _memmem (unsigned char const *haystack, int haystack_len, unsigned char const *needle, int needle_len) { unsigned char const *end_haystack = haystack + haystack_len - needle_len + 1; unsigned char const *end_needle = needle + needle_len; /* Ahhh ... Some minimal lowlevel stuff. This *is* nice; Varation is the spice of life */ while (haystack < end_haystack) { unsigned char const *subneedle = needle; unsigned char const *subhaystack = haystack; while (subneedle < end_needle) if (*subneedle++ != *subhaystack++) goto next; /* Completed the needle. Gotcha. */ return (unsigned char *) haystack; next: haystack++; } return 0; } void * memmem (void const *haystack, int haystack_len, void const *needle, int needle_len) { unsigned char const *haystack_byte_c = (unsigned char const *) haystack; unsigned char const *needle_byte_c = (unsigned char const *) needle; return _memmem (haystack_byte_c, haystack_len, needle_byte_c, needle_len); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> char * strcat (char *to, char const *from) { char *p = strchr (to, '\0'); while (*from) *p++ = *from++; *p = 0; return to; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> char * strchr (char const *s, int c) { char const *p = s; while (*p || !c) { if (c == *p) return (char *) p; p++; } return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <ctype.h> #include <string.h> char * strlwr (char *string) { char *p = string; while (*p) { *p = tolower (*p); p++; } return string; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> char * strncpy (char *to, char const *from, size_t size) { if (size == 0) return to; char *p = to; while (*from && size--) *p++ = *from++; if (*from) size++; while (size--) *p++ = 0; return to; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> char * strrchr (char const *s, int c) { int n = strlen (s); if (!n) return 0; char const *p = s + n; if (!*p && !c) return (char *) p; p--; while (n-- && (*p || !c)) { if (c == *p) return (char *) p; p--; } return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> #include <string.h> #include <sys/mman.h> char * strstr (char const *haystack, char const *needle) { return memmem (haystack, strlen (haystack), needle, strlen (needle)); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_SYS_MMAN_H #define __MES_SYS_MMAN_H 1 #if SYSTEM_LIBC #undef __MES_SYS_MMAN_H #include_next <sys/mman.h> #else // ! SYSTEM_LIBC #ifndef __MES_SIZE_T #define __MES_SIZE_T typedef unsigned long size_t; #endif #define PROT_NONE 0 #define PROT_READ 1 #define PROT_WRITE 2 #define PROT_EXEC 4 int mprotect (void *addr, size_t len, int prot); #endif // ! SYSTEM_LIBC #endif // __MES_SYS_MMAN_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <ctype.h> #include <string.h> char * strupr (char *string) { char *p = string; while (*p) { *p = toupper (*p); p++; } return string; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <signal.h> int sigaction (int signum, struct sigaction const *act, struct sigaction *oldact) { return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <math.h> double ldexp (double value, int exponent) { static int stub = 0; if (__mes_debug () && !stub) eputs ("ldexp stub\n"); stub = 1; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_MATH_H #define __MES_MATH_H 1 #if SYSTEM_LIBC #undef __MES_MATH_H #include_next <math.h> #else // ! SYSTEM_LIBC double atan2 (double y, double x); double ceil (double x); double cos (double x); double exp (double x); double fabs (double number); double floor (double x); double ldexp (double value, int exponent); double log (double x); double modf (double value, double *integer_part); double pow (double base, double power); double sin (double x); double sqrt (double x); #endif // ! SYSTEM_LIBC #endif // __MES_MATH_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <string.h> int mprotect (void *addr, size_t len, int prot) { return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <time.h> #include <sys/time.h> struct tm * localtime (time_t const *timep) { static int stub = 0; if (__mes_debug () && !stub) eputs ("localtime stub\n"); stub = 1; errno = 0; static struct tm zero = { 0 }; return &zero; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <signal.h> int sigemptyset (sigset_t * set) { return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <setjmp.h> #include <stdlib.h> void longjmp (jmp_buf env, int val) { val = val == 0 ? 1 : val; ///asm ("mov____0x8(%ebp),%eax !0x0c"); // val asm ("mov____0x8(%ebp),%ebp !0x08"); // env* asm ("mov____0x8(%ebp),%ebx !0x4"); // env.__pc asm ("mov____0x8(%ebp),%esp !0x8"); // env.__sp asm ("mov____0x8(%ebp),%ebp !0x0"); // env.__bp asm ("jmp____*%ebx"); // not reached exit (42); } int setjmp (__jmp_buf * env) { long *p = (long *) &env; env[0].__bp = p[-2]; env[0].__pc = p[-1]; env[0].__sp = (long) &env; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * Copyright © 2020 Danny Milosavljevic <dannym@scratchpost.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_SETJMP_H #define __MES_SETJMP_H 1 #if SYSTEM_LIBC #undef __MES_SETJMP_H #include_next <setjmp.h> #else // ! SYSTEM_LIBC #if __arm__ #if __GNUC__ || __TINYC__ #warning "It is not supported to use mes' setjmp implementation together with GCC. Continuing with best-effort implementation." typedef struct { long __sp; long __lr; long __registers[8]; /* Note: Keep in sync with lib/arm-mes-gcc/setjmp.c */ } __jmp_buf; #else typedef struct { long __fp; long __lr; long __sp; } __jmp_buf; #endif #else typedef struct { long __bp; long __pc; long __sp; } __jmp_buf; #endif typedef __jmp_buf jmp_buf[1]; void longjmp (jmp_buf env, int val); int setjmp (jmp_buf env); #endif // ! SYSTEM_LIBC #endif // __MES_SETJMP_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <mes/lib.h> int close (int filedes) { __ungetc_clear (filedes); __buffered_read_clear (filedes); return _sys_call1 (SYS_close, (int) filedes); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> int rmdir (char const *file_name) { return _sys_call1 (SYS_rmdir, (long) file_name); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <sys/stat.h> int stat (char const *file_name, struct stat *statbuf) { return _sys_call2 (SYS_stat, (long) file_name, (long) statbuf); }
### Copyright (C) 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> ### This file is part of stage0. ### ### stage0 is free software: you can redistribute it and/or modify ### it under the terms of the GNU General Public License as published by ### the Free Software Foundation, either version 3 of the License, or ### (at your option) any later version. ### ### stage0 is distributed in the hope that it will be useful, ### but WITHOUT ANY WARRANTY; without even the implied warranty of ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ### GNU General Public License for more details. ### ### You should have received a copy of the GNU General Public License ### along with stage0. If not, see <http://www.gnu.org/licenses/>. ### stage0's hex2 format for x86 ### !<label> 1 byte relative ### $<label> 2 byte address ### @<label> 2 byte relative ### &<label> 4 byte address ### %<label> 4 byte relative ### local_<label> function-local ### string_<index> string #<index> ### elf32-footer-single-main.hex2: 32 bit elf footer in hex2 for single main # @230 :ELF_str 00 # 0 :ELF_str__start 5f 73 74 61 72 74 00 # _start :ELF_str__main 6d 61 69 6e 00 # main 00 00 00 # @240 :ELF_sym 00 00 00 00 # st-name 00 00 00 00 # st-offset: &_start - BaseAddress 00 00 00 00 # st-len : &main - _start 00 # st-info = stt-func= 2 00 # st-other 01 00 # st-shndx: 1 # _start %ELF_str__start>ELF_str # st-name &_start 10 00 00 00 # st-len : &main - _start 02 # st-info = stt-func= 2 00 # st-other 01 00 # st-shndx: 1 # main %ELF_str__main>ELF_str # st-name &main 10 00 00 00 # st-len : &ELF_data - main 02 # st-info = stt-func= 2 00 # st-other 01 00 # st-shndx: 1 :ELF_end
### Copyright (C) 2016 Jeremiah Orians ### Copyright (C) 2017,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> ### ### This file is part of GNU Mes. ### ### GNU Mes is free software; you can redistribute it and/or modify it ### under the terms of the GNU General Public License as published by ### the Free Software Foundation; either version 3 of the License, or (at ### your option) any later version. ### ### GNU Mes is distributed in the hope that it will be useful, but ### WITHOUT ANY WARRANTY; without even the implied warranty of ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ### GNU General Public License for more details. ### ### You should have received a copy of the GNU General Public License ### along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. ### Commentary: # elf32-header.hex2: 32 bit elf header in hex2, with text segment, data # segment and symbol tables. # stage0's hex2 format for x86 # !<label> 1 byte relative # $<label> 2 byte address # @<label> 2 byte relative # &<label> 4 byte address # %<label> 4 byte relative # local_<label> function-local # string_<index> string #<index> ### Code: :ELF_base 7F 45 4C 46 # e_ident[EI_MAG0-3] ELF's magic number 01 # e_ident[EI_CLASS] Indicating 32 bit 01 # e_ident[EI_DATA] Indicating little endianness 01 # e_ident[EI_VERSION] Indicating original elf 00 # e_ident[EI_OSABI] Set at 0 because none cares 00 # e_ident[EI_ABIVERSION] See above 00 00 00 00 00 00 00 # e_ident[EI_PAD] 02 00 # e_type Indicating Executable 03 00 # e_machine Indicating 32bit x86 01 00 00 00 # e_version Indicating original elf &ELF_text # e_entry Address of the entry point %ELF_program_headers>ELF_base # e_phoff Address of program header table %ELF_section_headers>ELF_base # e_shoff Address of section header table 00 00 00 00 # e_flags 34 00 # e_ehsize Indicating our 52 Byte header 20 00 # e_phentsize size of a program header table 01 00 # e_phnum number of entries in program table 28 00 # e_shentsize size of a section header table 07 00 # e_shnum number of entries in section table 04 00 # e_shstrndx index of the section names # @34 00 00 00 00 00 00 00 00 00 00 00 00 # @40 :ELF_program_headers :ELF_program_header__text 01 00 00 00 # ph_type: PT-LOAD = 1 00 00 00 00 # ph_offset &ELF_base # ph_vaddr &ELF_base # ph_physaddr %ELF_end>ELF_base # ph_filesz %ELF_end>ELF_base # ph_memsz 07 00 00 00 # ph_flags: PF-X|PF-W|PF-R = 7 01 00 00 00 # ph_align # @60 #:ELF_program_header__data # NOT USED # FIXME: linux 4.17 does not allow this overlap # Uhuuh, elf segment at 0000000001000000 # requested but the memory is mapped already 01 00 00 00 # ph_type: PT-LOAD = 1 00 00 00 00 # ph_offset &ELF_base # ph_vaddr &ELF_base # ph_physaddr %ELF_end>ELF_base # ph_filesz %ELF_end>ELF_base # ph_memsz 07 00 00 00 # ph_flags: PF-X|PF-W|PF-R = 7 01 00 00 00 # ph_align # @80 :ELF_comment 4d 45 53 00 # MES 00 00 00 00 # align 00 00 00 00 00 00 00 00 4d 45 53 00 # MES 00 00 00 00 # align 00 00 00 00 00 00 00 00 # @a0 :ELF_shstr 00 :ELF_shstr__text 2e 74 65 78 74 00 # .text :ELF_shstr__data 2e 64 61 74 61 00 # .data :ELF_shstr__comment 2e 63 6f 6d 6d 65 6e 74 00 # .comment :ELF_shstr__shstr 2e 73 68 73 74 72 74 61 62 00 # .shstrtab :ELF_shstr__sym 2e 73 79 6d 74 61 62 00 # .symtab :ELF_shstr__str 2e 73 74 72 74 61 62 00 # .strtab # @d0 :ELF_section_headers 00 00 00 00 # sh_name 00 00 00 00 # sh_type 00 00 00 00 # sh_flags 00 00 00 00 # sh_addr 00 00 00 00 # sh_offset 00 00 00 00 # sh_length 00 00 00 00 # sh_link 00 00 00 00 # sh_info 01 00 00 00 # sh_1? 00 00 00 00 # sh_entsize ## FIXME: M0 for calculations? :ELF_section_header_text %ELF_shstr__text>ELF_shstr # sh_name 01 00 00 00 # sh_type = SHT_PROGBITS = 1 06 00 00 00 # sh_flags = SHF-ALLOC|SHF-EXEC =2 | 4 = 6 &ELF_text # sh_addr %ELF_text>ELF_base # sh_offset %ELF_data>ELF_text # sh_length 00 00 00 00 # sh_link 00 00 00 00 # sh_info 01 00 00 00 # sh_1? 00 00 00 00 # sh_entsize :ELF_section_header_data %ELF_shstr__data>ELF_shstr # sh_name 01 00 00 00 # sh_type = SHT_PROGBITS = 1 03 00 00 00 # sh_flags = SHF-WRITE|SHF-ALLOC = 1 | 2 = 3 &ELF_data # sh_addr %ELF_data>ELF_base # sh_offset %ELF_sym>ELF_data # sh_length 00 00 00 00 # sh_link 00 00 00 00 # sh_info 01 00 00 00 # sh_1? 00 00 00 00 # sh_entsize :ELF_section_header_comment %ELF_shstr__comment>ELF_shstr # sh_name 01 00 00 00 # sh_type = SHT_PROGBITS = 1 00 00 00 00 # sh_flags &ELF_comment # sh_addr %ELF_comment>ELF_base # sh_offset %ELF_shstr>ELF_comment # sh_length 00 00 00 00 # sh_link 00 00 00 00 # sh_info 01 00 00 00 # sh_1? 00 00 00 00 # sh_entsize :ELF_section_header_shstr %ELF_shstr__shstr>ELF_shstr # sh_name 03 00 00 00 # sh_type: str-sht-strtab 00 00 00 00 # sh_flags &ELF_shstr # sh_addr %ELF_shstr>ELF_base # sh_offset %ELF_section_headers>ELF_shstr # sh_length 00 00 00 00 # sh_link 00 00 00 00 # sh_info 01 00 00 00 # sh_1? 00 00 00 00 # sh_entsize :ELF_section_header_sym %ELF_shstr__sym>ELF_shstr # sh_name 02 00 00 00 # sh_type: str-sht-symtab 00 00 00 00 # sh_flags &ELF_sym # sh_addr %ELF_sym>ELF_base # sh_offset %ELF_end>ELF_sym # sh_length 06 00 00 00 # sh_link:6 00 00 00 00 # sh_info 01 00 00 00 # sh_1? 10 00 00 00 # sh_entsize :ELF_section_header_str %ELF_shstr__str>ELF_shstr # sh_name 03 00 00 00 # sh_type: str-sht-strtab 00 00 00 00 # sh_flags &ELF_str # sh_addr %ELF_str>ELF_base # sh_offset %ELF_sym>ELF_str # sh_length 00 00 00 00 # sh_link 00 00 00 00 # sh_info 01 00 00 00 # sh_1? 00 00 00 00 # sh_entsize # @1e8 00 00 00 00 # align 00 00 00 00 # @1f0 00 00 00 00 # align 00 00 00 00 00 00 00 00 00 00 00 00 # @200 :ELF_text
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_ALLOCA_H #define __MES_ALLOCA_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_ALLOCA_H #include_next <alloca.h> #else // ! SYSTEM_LIBC #include <sys/types.h> #if _ALLOCA_UNSIGNED void *alloca (unsigned size); #elif _ALLOCA_CHAR char *alloca (int); #else void *alloca (size_t size); #endif #endif // ! SYSTEM_LIBC #endif // __MES_ALLOCA_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_ARGZ_H #define __MES_ARGZ_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_ARGZ_H #include_next <argz.h> #else // ! SYSTEM_LIBC #include <mes/lib-mini.h> size_t __argz_count (char const *argz, size_t len); void __argz_extract (char const *argz, size_t len, char **argv); size_t __argz_extract_count (char const *argz, size_t len, char **argv); #endif // ! SYSTEM_LIBC #endif // __MES_ARGZ_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright (C) 1996 Free Software Foundation, Inc. * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_AR_H #define __MES_AR_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_AR_H #include_next <ar.h> #else // ! SYSTEM_LIBC // Taken from GNU C Library 2.2.5 /* Archive files start with the ARMAG identifying string. Then follows a `struct ar_hdr', and as many bytes of member file data as its `ar_size' member indicates, for each member file. */ #define ARMAG "!<arch>\n" /* String that begins an archive file. */ #define SARMAG 8 /* Size of that string. */ #define ARFMAG "`\n" /* String in ar_fmag at end of each header. */ struct ar_hdr { char ar_name[16]; /* Member file name, sometimes / terminated. */ char ar_date[12]; /* File date, decimal seconds since Epoch. */ char ar_uid[6], ar_gid[6]; /* User and group IDs, in ASCII decimal. */ char ar_mode[8]; /* File mode, in ASCII octal. */ char ar_size[10]; /* File size, in ASCII decimal. */ char ar_fmag[2]; /* Always contains ARFMAG. */ }; #endif // ! SYSTEM_LIBC #endif // __MES_ARGZ_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright (C) 1991, 1992 Free Software Foundation, Inc. * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_DIRENT_H #define __MES_DIRENT_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_DIRENT_H #include_next <dirent.h> #else // ! SYSTEM_LIBC #include <dirstream.h> // Taken from GNU C Library 1.06.4, 2.2.5 /* * POSIX Standard: 5.1.2 Directory Operations <dirent.h> */ #include <stddef.h> int __getdirentries (int filedes, char *buffer, size_t nbytes, off_t * basep); struct dirent { ino_t d_ino; off_t d_off; unsigned short int d_reclen; #if 0 unsigned char d_type; #endif char d_name[256]; /* We must not include limits.h! */ }; /* Open a directory stream on NAME. Return a DIR stream on the directory, or NULL if it could not be opened. */ DIR *opendir (char const *name); /* Close the directory stream DIRP. Return 0 if successful, -1 if not. */ int closedir (DIR * dirp); /* Read a directory entry from DIRP. Return a pointer to a `struct dirent' describing the entry, or NULL for EOF or error. The storage returned may be overwritten by a later readdir call on the same DIR stream. */ struct dirent *readdir (DIR * dirp); /* Rewind DIRP to the beginning of the directory. */ extern void rewinddir (DIR * dirp); #endif // ! SYSTEM_LIBC #endif // __MES_DIRENT_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software *Copyright (C) 1993, 1995, 1996 Free Software Foundation, Inc. * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_DIRSTREAM_H #define __MES_DIRSTREAM_H 1 #if SYSTEM_LIBC && HAVE_DIRSTREAM_H #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_DIRSTREAM_H #include_next <dirstream.h> #else // ! SYSTEM_LIBC #include <sys/types.h> // Taken from GNU C Library 2.2.5 /* Directory stream type. */ struct __dirstream { int fd; /* File descriptor. */ char *data; /* Directory block. */ size_t allocation; /* Space allocated for the block. */ size_t size; /* Total valid data in the block. */ size_t offset; /* Current offset into the block. */ off_t filepos; /* Position of next entry to read. */ }; typedef struct __dirstream DIR; #endif // ! SYSTEM_LIBC #endif // __MES_DIRSTREAM_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_DLFCN_H #define __MES_DLFCN_H 1 #if SYSTEM_LIBC #undef __MES_DLFCN_H #include_next <dlfcn.h> #else // ! SYSTEM_LIBC #define RTLD_LAZY 0x00001 #define RTLD_NOW 0x00002 #define RTLD_BINDING_MASK 0x3 #define RTLD_NOLOAD 0x00004 #define RTLD_DEEPBIND 0x00008 #define RTLD_GLOBAL 0x00100 #define RTLD_LOCAL 0 #define RTLD_NODELETE 0x01000 #define RTLD_DEFAULT 0 void *dlopen (char const *filename, int flags); int dlclose (void *handle); #endif // ! SYSTEM_LIBC #endif // __MES_DLFCN_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_ENDIAN_H #define __MES_ENDIAN_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_ENDIAN_H #include_next <endian.h> #else // ! SYSTEM_LIBC #define __LITTLE_ENDIAN 1234 #define __BIG_ENDIAN 4321 #define __BYTE_ORDER __LITTLE_ENDIAN #endif // ! SYSTEM_LIBC #endif // __MES_ENDIAN_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_FEATURES_H #define __MES_FEATURES_H 1 #if SYSTEM_LIBC #undef __MES_FEATURES_H #include_next <features.h> #endif // (SYSTEM_LIBC) #endif // __MES_FEATURES_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_FLOAT_H #define __MES_FLOAT_H 1 #if SYSTEM_LIBC #undef __MES_FLOAT_H #include_next <float.h> #else // ! SYSTEM_LIBC #define MIN_EXP -1021 #define DBL_MIN_EXP -1021 #define LDBL_MIN_EXP -1021 #endif // ! SYSTEM_LIBC #endif // __MES_FLOAT_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc. * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_GETOPT_H #define __MES_GETOPT_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_GETOPT_H #include_next <getopt.h> #else // ! SYSTEM_LIBC #include <endian.h> int isdigit (int); int isxdigit (int); char *optarg; int optind; int opterr; struct option { char const *name; int has_arg; int *flag; int val; }; enum _argtype { no_argument, required_argument, optional_argument }; int getopt (int argc, char *const *argv, char const *options); int getopt_long (int argc, char *const *argv, char const *options, struct option const *long_options, int *opt_index); int getopt_long_only (int argc, char *const *argv, char const *options, struct option const *long_options, int *opt_index); #endif // ! SYSTEM_LIBC #endif // __MES_GETOPT_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_GRP_H #define __MES_GRP_H 1 #if SYSTEM_LIBC #undef __MES_GRP_H #include_next <grp.h> #else // ! SYSTEM_LIBC #include <sys/types.h> struct group { char *gr_name; gid_t gr_gid; char **gr_mem; }; struct group *getgrent (void); void endgrent (void); void setgrent (void); struct group *getgrgid (gid_t gid); struct group *getgrnam (char const *name); #endif // ! SYSTEM_LIBC #endif // __MES_GRP_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_INTTYPES_H #define __MES_INTTYPES_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_INTTYPES_H #include_next <inttypes.h> #else // ! SYSTEM_LIBC #include <stdint.h> #endif // ! SYSTEM_LIBC #endif // __MES_INTTYPES_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_LIBGEN_H #define __MES_LIBGEN_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_LIBGEN_H #include_next <libgen.h> #else // ! SYSTEM_LIBC char *dirname (char *); #endif // ! SYSTEM_LIBC #endif // __MES_LIBGEN_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_LOCALE_H #define __MES_LOCALE_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_LOCALE_H #include_next <locale.h> #else // ! SYSTEM_LIBC // *INDENT-OFF* #ifndef LC_ALL #define LC_CTYPE 0 #define LC_NUMERIC 1 #define LC_COLLATE 3 #define LC_ALL 6 #endif // *INDENT-ON* char *setlocale (int category, char const *locale); #endif // ! SYSTEM_LIBC #endif // __MES_LOCALE_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_MEMORY_H #define __MES_MEMORY_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_MEMORY_H #include_next <memory.h> #else // ! SYSTEM_LIBC #include <string.h> #endif // ! SYSTEM_LIBC #endif // __MES_MEMORY_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_PWD_H #define __MES_PWD_H 1 #if SYSTEM_LIBC #undef __MES_PWD_H #include_next <pwd.h> #else // ! SYSTEM_LIBC #include <sys/types.h> struct passwd { char *pw_name; char *pw_passwd; uid_t pw_uid; gid_t pw_gid; char *pw_gecos; char *pw_dir; char *pw_shell; }; struct passwd *getpwuid (); #endif // ! SYSTEM_LIBC #endif // __MES_PWD_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_STDBOOL_H #define __MES_STDBOOL_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_STDBOOL_H #include_next <stdbool.h> #else // ! SYSTEM_LIBC typedef int bool; #define false 0 #define true 1 #endif // ! SYSTEM_LIBC #endif // __MES_STDBOOL_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_STDNORETURN_H #define __MES_STDNORETURN_H 1 #if SYSTEM_LIBC #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #undef __MES_STDNORETURN_H #include_next <stdnoreturn.h> #else // ! SYSTEM_LIBC // whut? #endif // ! SYSTEM_LIBC #endif // __MES_STDNORETURN_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_STRINGS_H #define __MES_STRINGS_H 1 #if SYSTEM_LIBC #undef __MES_STRINGS_H #include_next <strings.h> #endif // (SYSTEM_LIBC) #endif // __MES_STRINGS_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_SYS_CDEFS_H #define __MES_SYS_CDEFS_H 1 #if SYSTEM_LIBC #undef __MES_SYS_CDEFS_H #include_next <sys/cdefs.h> #endif // (SYSTEM_LIBC) #endif // __MES_SYS_CDEFS_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_SYS_DIR_H #define __MES_SYS_DIR_H 1 #if SYSTEM_LIBC #undef __MES_SYS_DIR_H #include_next <sys/dir.h> #else // ! SYSTEM_LIBC #endif // ! SYSTEM_LIBC #endif // __MES_SYS_DIR_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_SYS_FILE_H #define __MES_SYS_FILE_H 1 #if SYSTEM_LIBC #undef __MES_SYS_FILE_H #include_next <sys/file.h> #else // ! SYSTEM_LIBC #endif // ! SYSTEM_LIBC #endif // __MES_SYS_FILE_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_SYS_PARAM_H #define __MES_SYS_PARAM_H 1 #if SYSTEM_LIBC #undef __MES_SYS_PARAM_H #include_next <sys/param.h> #else // ! SYSTEM_LIBC #endif // ! SYSTEM_LIBC #endif // __MES_SYS_PARAM_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_SYS_SELECT_H #define __MES_SYS_SELECT_H 1 #if SYSTEM_LIBC #undef __MES_SYS_SELECT_H #include_next <sys/select.h> #else //! SYSTEM_LIBC typedef int fd_set; #endif //! SYSTEM_LIBC #endif // __MES_SYS_SELECT_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_SYS_TIMEB_H #define __MES_SYS_TIMEB_H 1 #if SYSTEM_LIBC #undef __MES_SYS_TIMEB_H #include_next <sys/timeb.h> #endif // (SYSTEM_LIBC) #endif // __MES_SYS_TIMEB_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_SYS_TIMES_H #define __MES_SYS_TIMES_H 1 #if SYSTEM_LIBC #undef __MES_SYS_TIMES_H #include_next <sys/times.h> #else // ! SYSTEM_LIBC #ifndef __MES_CLOCK_T #define __MES_CLOCK_T #undef clock_t typedef long clock_t; #endif #ifndef CLOCKS_PER_SEC #define CLOCKS_PER_SEC 1000000 #endif #ifndef HZ #define HZ 100 #endif struct tms { clock_t tms_utime; clock_t tms_stime; clock_t tms_cutime; clock_t tms_cstime; }; #endif // ! SYSTEM_LIBC #endif // __MES_SYS_TIMES_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_SYS_UCONTEXT_H #define __MES_SYS_UCONTEXT_H 1 #if SYSTEM_LIBC #undef __MES_SYS_UCONTEXT_H #include_next <sys/ucontext.h> #endif // (SYSTEM_LIBC) #endif // __MES_SYS_UCONTEXT_H
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #ifndef __MES_SYS_USER_H #define __MES_SYS_USER_H 1 #if SYSTEM_LIBC #undef __MES_SYS_USER_H #include_next <sys/user.h> #else // ! SYSTEM_LIBC /* These are the 32-bit x86 structures. */ struct user_fpregs_struct { long int cwd; long int swd; long int twd; long int fip; long int fcs; long int foo; long int fos; long int st_space[20]; }; struct user_fpxregs_struct { unsigned short int cwd; unsigned short int swd; unsigned short int twd; unsigned short int fop; long int fip; long int fcs; long int foo; long int fos; long int mxcsr; long int reserved; long int st_space[32]; /* 8*16 bytes for each FP-reg = 128 bytes */ long int xmm_space[32]; /* 8*16 bytes for each XMM-reg = 128 bytes */ long int padding[56]; }; struct user_regs_struct { long int ebx; long int ecx; long int edx; long int esi; long int edi; long int ebp; long int eax; long int xds; long int xes; long int xfs; long int xgs; long int orig_eax; long int eip; long int xcs; long int eflags; long int esp; long int xss; }; // *INDENT-OFF* struct user { struct user_regs_struct regs; int u_fpvalid; struct user_fpregs_struct i387; unsigned long int u_tsize; unsigned long int u_dsize; unsigned long int u_ssize; unsigned long int start_code; unsigned long int start_stack; long int signal; int reserved; struct user_regs_struct *u_ar0; struct user_fpregs_struct *u_fpstate; unsigned long int magic; char u_comm [32]; int u_debugreg [8]; }; #define PAGE_SHIFT 12 #define PAGE_SIZE (1UL << PAGE_SHIFT) #define PAGE_MASK (~(PAGE_SIZE-1)) #define NBPG PAGE_SIZE #define UPAGES 1 #define HOST_TEXT_START_ADDR (u.start_code) #define HOST_STACK_END_ADDR (u.start_stack + u.u_ssize * NBPG) // *INDENT-ON* #endif // ! SYSTEM_LIBC #endif // __MES_SYS_USER_H
b2a48b2724a7b791df66efc2384a405a91d26579459a6797514dec89c2e76658 /usr/bin/mes 148ce096422535832802494f1128c26b6580cf3b66635e56abb33f7b3fc26043 /usr/bin/mes-m2 17c86665c2f925d88e3c1f827f312bbc7575b9e6006ef57833e895b6e73b8087 /usr/bin/mescc.scm 4feafab424611c976dc4628aa862e37b4e92b5869aebbcf7655e684c3f589041 /usr/lib/x86-mes/crt1.s 69e9ec2ee2ebf065575cdaf878629178b85330257dde0da790a428c0280d1ecb /usr/lib/x86-mes/crt1.o c9944a799d584abfa76f385c14ac0caf6f46d03b34bf2712493602b12826c6b2 /usr/lib/x86-mes/x86.M1 d8646707db6aa2a76fdc5dbb3521376439e357f9f1de1d67f02a1afeefd342ac /usr/lib/x86-mes/libmescc.s 7ec49fbcbc70b49648150b021a2ff9ab2b27e89eb8815919a7ffabe375ab1edc /usr/lib/x86-mes/libc+tcc.s b935d4c30213b49d899ab8bccce33117057ea0f3a1a8d3afc5e07fb8ce4590db /usr/lib/x86-mes/libc.s 52f697278ccdff5e457f27e10f465a91ab9858f0c6cee0683831cadb3109bbb7 /usr/lib/x86-mes/libmescc.a 840ee884db456e1982f40bce82209515a8e0b09bc404c4bc2a26d237bfa4d9ca /usr/lib/x86-mes/libc+tcc.a 32be26479096c52dbffe9c8bf14765f753d6882c3ae43760eda03406c0cf2539 /usr/lib/x86-mes/libc.a b16ab368bc4c7b8bd896d03cba565a60e97760dea4da9f5c8a1a3d2902a79df6 /usr/lib/linux/x86-mes/elf32-header.hex2 f9873d9aab12e70f24d97f8319e17a1e698ca60779ae9a6ab3ede648cd60fc61 /usr/lib/linux/x86-mes/elf32-footer-single-main.hex2
https://lilypond.org/janneke/tcc/tcc-0.9.26-1136-g5bba73cc.tar.gz 23cacd448cff2baf6ed76c2d1e2d654ff4e557046e311dfb6be7e1c631014ef8 tcc-0.9.26.tar.gz
/* -------------------------------------------------------------- */ /* * TCC - Tiny C Compiler * * tcctools.c - extra tools and and -m32/64 support * */ /* -------------------------------------------------------------- */ /* * This program is for making libtcc1.a without ar * tiny_libmaker - tiny elf lib maker * usage: tiny_libmaker [lib] files... * Copyright (c) 2007 Timppa * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _TCC_H #include "tcc.h" #endif //#define ARMAG "!<arch>\n" #define ARFMAG "`\n" typedef struct { char ar_name[16]; char ar_date[12]; char ar_uid[6]; char ar_gid[6]; char ar_mode[8]; char ar_size[10]; char ar_fmag[2]; } ArHdr; static unsigned long le2belong(unsigned long ul) { return ((ul & 0xFF0000)>>8)+((ul & 0xFF000000)>>24) + ((ul & 0xFF)<<24)+((ul & 0xFF00)<<8); } /* Returns 1 if s contains any of the chars of list, else 0 */ static int contains_any(const char *s, const char *list) { const char *l; for (; *s; s++) { for (l = list; *l; l++) { if (*s == *l) return 1; } } return 0; } static int ar_usage(int ret) { fprintf(stderr, "usage: tcc -ar [rcsv] lib file...\n"); fprintf(stderr, "create library ([abdioptxN] not supported).\n"); return ret; } ST_FUNC int tcc_tool_ar(TCCState *s1, int argc, char **argv) { static ArHdr arhdr = { "/ ", " ", "0 ", "0 ", "0 ", " ", ARFMAG }; static ArHdr arhdro = { " ", " ", "0 ", "0 ", "0 ", " ", ARFMAG }; FILE *fi, *fh = NULL, *fo = NULL; ElfW(Ehdr) *ehdr; ElfW(Shdr) *shdr; ElfW(Sym) *sym; int i, fsize, i_lib, i_obj; char *buf, *shstr, *symtab = NULL, *strtab = NULL; int symtabsize = 0;//, strtabsize = 0; char *anames = NULL; int *afpos = NULL; int istrlen, strpos = 0, fpos = 0, funccnt = 0, funcmax, hofs; char tfile[260], stmp[20]; char *file, *name; int ret = 2; const char *ops_conflict = "habdioptxN"; // unsupported but destructive if ignored. int verbose = 0; i_lib = 0; i_obj = 0; // will hold the index of the lib and first obj for (i = 1; i < argc; i++) { const char *a = argv[i]; if (*a == '-' && strstr(a, ".")) ret = 1; // -x.y is always invalid (same as gnu ar) if ((*a == '-') || (i == 1 && !strstr(a, "."))) { // options argument if (contains_any(a, ops_conflict)) ret = 1; if (strstr(a, "v")) verbose = 1; } else { // lib or obj files: don't abort - keep validating all args. if (!i_lib) // first file is the lib i_lib = i; else if (!i_obj) // second file is the first obj i_obj = i; } } if (!i_obj) // i_obj implies also i_lib. we require both. ret = 1; if (ret == 1) return ar_usage(ret); if ((fh = fopen(argv[i_lib], "wb")) == NULL) { fprintf(stderr, "tcc: ar: can't open file %s \n", argv[i_lib]); goto the_end; } sprintf(tfile, "%s.tmp", argv[i_lib]); if ((fo = fopen(tfile, "wb+")) == NULL) { fprintf(stderr, "tcc: ar: can't create temporary file %s\n", tfile); goto the_end; } funcmax = 250; afpos = tcc_realloc(NULL, funcmax * sizeof *afpos); // 250 func memcpy(&arhdro.ar_mode, "100666", 6); // i_obj = first input object file while (i_obj < argc) { if (*argv[i_obj] == '-') { // by now, all options start with '-' i_obj++; continue; } if ((fi = fopen(argv[i_obj], "rb")) == NULL) { fprintf(stderr, "tcc: ar: can't open file %s \n", argv[i_obj]); goto the_end; } if (verbose) printf("a - %s\n", argv[i_obj]); fseek(fi, 0, SEEK_END); fsize = ftell(fi); fseek(fi, 0, SEEK_SET); buf = tcc_malloc(fsize + 1); fread(buf, fsize, 1, fi); fclose(fi); // elf header ehdr = (ElfW(Ehdr) *)buf; if (ehdr->e_ident[4] != ELFCLASSW) { fprintf(stderr, "tcc: ar: Unsupported Elf Class: %s\n", argv[i_obj]); goto the_end; } shdr = (ElfW(Shdr) *) (buf + ehdr->e_shoff + ehdr->e_shstrndx * ehdr->e_shentsize); shstr = (char *)(buf + shdr->sh_offset); for (i = 0; i < ehdr->e_shnum; i++) { shdr = (ElfW(Shdr) *) (buf + ehdr->e_shoff + i * ehdr->e_shentsize); if (!shdr->sh_offset) continue; if (shdr->sh_type == SHT_SYMTAB) { symtab = (char *)(buf + shdr->sh_offset); symtabsize = shdr->sh_size; } if (shdr->sh_type == SHT_STRTAB) { if (!strcmp(shstr + shdr->sh_name, ".strtab")) { strtab = (char *)(buf + shdr->sh_offset); //strtabsize = shdr->sh_size; } } } if (symtab && symtabsize) { int nsym = symtabsize / sizeof(ElfW(Sym)); //printf("symtab: info size shndx name\n"); for (i = 1; i < nsym; i++) { sym = (ElfW(Sym) *) (symtab + i * sizeof(ElfW(Sym))); if (sym->st_shndx && (sym->st_info == 0x10 || sym->st_info == 0x11 || sym->st_info == 0x12 )) { //printf("symtab: %2Xh %4Xh %2Xh %s\n", sym->st_info, sym->st_size, sym->st_shndx, strtab + sym->st_name); istrlen = strlen(strtab + sym->st_name)+1; anames = tcc_realloc(anames, strpos+istrlen); strcpy(anames + strpos, strtab + sym->st_name); strpos += istrlen; if (++funccnt >= funcmax) { funcmax += 250; afpos = tcc_realloc(afpos, funcmax * sizeof *afpos); // 250 func more } afpos[funccnt] = fpos; } } } file = argv[i_obj]; for (name = strchr(file, 0); name > file && name[-1] != '/' && name[-1] != '\\'; --name); istrlen = strlen(name); if (istrlen >= sizeof(arhdro.ar_name)) istrlen = sizeof(arhdro.ar_name) - 1; memset(arhdro.ar_name, ' ', sizeof(arhdro.ar_name)); memcpy(arhdro.ar_name, name, istrlen); arhdro.ar_name[istrlen] = '/'; sprintf(stmp, "%-10d", fsize); memcpy(&arhdro.ar_size, stmp, 10); fwrite(&arhdro, sizeof(arhdro), 1, fo); fwrite(buf, fsize, 1, fo); tcc_free(buf); i_obj++; fpos += (fsize + sizeof(arhdro)); } hofs = 8 + sizeof(arhdr) + strpos + (funccnt+1) * sizeof(int); fpos = 0; if ((hofs & 1)) // align hofs++, fpos = 1; // write header fwrite("!<arch>\n", 8, 1, fh); sprintf(stmp, "%-10d", (int)(strpos + (funccnt+1) * sizeof(int))); memcpy(&arhdr.ar_size, stmp, 10); fwrite(&arhdr, sizeof(arhdr), 1, fh); afpos[0] = le2belong(funccnt); for (i=1; i<=funccnt; i++) afpos[i] = le2belong(afpos[i] + hofs); fwrite(afpos, (funccnt+1) * sizeof(int), 1, fh); fwrite(anames, strpos, 1, fh); if (fpos) fwrite("", 1, 1, fh); // write objects fseek(fo, 0, SEEK_END); fsize = ftell(fo); fseek(fo, 0, SEEK_SET); buf = tcc_malloc(fsize + 1); fread(buf, fsize, 1, fo); fwrite(buf, fsize, 1, fh); tcc_free(buf); ret = 0; the_end: if (anames) tcc_free(anames); if (afpos) tcc_free(afpos); if (fh) fclose(fh); if (fo) fclose(fo), remove(tfile); return ret; } /* -------------------------------------------------------------- */ /* * tiny_impdef creates an export definition file (.def) from a dll * on MS-Windows. Usage: tiny_impdef library.dll [-o outputfile]" * * Copyright (c) 2005,2007 grischka * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifdef TCC_TARGET_PE ST_FUNC int tcc_tool_impdef(TCCState *s1, int argc, char **argv) { int ret, v, i; char infile[260]; char outfile[260]; const char *file; char *p, *q; FILE *fp, *op; #ifdef _WIN32 char path[260]; #endif infile[0] = outfile[0] = 0; fp = op = NULL; ret = 1; p = NULL; v = 0; for (i = 1; i < argc; ++i) { const char *a = argv[i]; if ('-' == a[0]) { if (0 == strcmp(a, "-v")) { v = 1; } else if (0 == strcmp(a, "-o")) { if (++i == argc) goto usage; strcpy(outfile, argv[i]); } else goto usage; } else if (0 == infile[0]) strcpy(infile, a); else goto usage; } if (0 == infile[0]) { usage: fprintf(stderr, "usage: tcc -impdef library.dll [-v] [-o outputfile]\n" "create export definition file (.def) from dll\n" ); goto the_end; } if (0 == outfile[0]) { strcpy(outfile, tcc_basename(infile)); q = strrchr(outfile, '.'); if (NULL == q) q = strchr(outfile, 0); strcpy(q, ".def"); } file = infile; #ifdef _WIN32 if (SearchPath(NULL, file, ".dll", sizeof path, path, NULL)) file = path; #endif ret = tcc_get_dllexports(file, &p); if (ret || !p) { fprintf(stderr, "tcc: impdef: %s '%s'\n", ret == 32 ? "can't read symbols from 32bit" : ret == 64 ? "can't read symbols from 64bit" : ret == -1 ? "can't find file" : ret == 0 ? "no symbols found in" : "unknown file type", file); ret = 1; goto the_end; } if (v) printf("-> %s\n", file); op = fopen(outfile, "w"); if (NULL == op) { fprintf(stderr, "tcc: impdef: could not create output file: %s\n", outfile); goto the_end; } fprintf(op, "LIBRARY %s\n\nEXPORTS\n", tcc_basename(file)); for (q = p, i = 0; *q; ++i) { fprintf(op, "%s\n", q); q += strlen(q) + 1; } if (v) printf("<- %s (%d symbol%s)\n", outfile, i, &"s"[i<2]); ret = 0; the_end: /* cannot free memory received from tcc_get_dllexports if it came from a dll */ /* if (p) tcc_free(p); */ if (fp) fclose(fp); if (op) fclose(op); return ret; } #endif /* TCC_TARGET_PE */ /* -------------------------------------------------------------- */ /* * TCC - Tiny C Compiler * * Copyright (c) 2001-2004 Fabrice Bellard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* re-execute the i386/x86_64 cross-compilers with tcc -m32/-m64: */ #if !defined TCC_TARGET_I386 && !defined TCC_TARGET_X86_64 ST_FUNC void tcc_tool_cross(TCCState *s, char **argv, int option) { tcc_error("-m%d not implemented.", option); } #else #ifdef _WIN32 #include <process.h> static char *str_replace(const char *str, const char *p, const char *r) { const char *s, *s0; char *d, *d0; int sl, pl, rl; sl = strlen(str); pl = strlen(p); rl = strlen(r); for (d0 = NULL;; d0 = tcc_malloc(sl + 1)) { for (d = d0, s = str; s0 = s, s = strstr(s, p), s; s += pl) { if (d) { memcpy(d, s0, sl = s - s0), d += sl; memcpy(d, r, rl), d += rl; } else sl += rl - pl; } if (d) { strcpy(d, s0); return d0; } } } static int execvp_win32(const char *prog, char **argv) { int ret; char **p; /* replace all " by \" */ for (p = argv; *p; ++p) if (strchr(*p, '"')) *p = str_replace(*p, "\"", "\\\""); ret = _spawnvp(P_NOWAIT, prog, (const char *const*)argv); if (-1 == ret) return ret; _cwait(&ret, ret, WAIT_CHILD); exit(ret); } #define execvp execvp_win32 #endif /* _WIN32 */ ST_FUNC void tcc_tool_cross(TCCState *s, char **argv, int target) { char program[4096]; char *a0 = argv[0]; int prefix = tcc_basename(a0) - a0; snprintf(program, sizeof program, "%.*s%s" #ifdef TCC_TARGET_PE "-win32" #endif "-tcc" #ifdef _WIN32 ".exe" #endif , prefix, a0, target == 64 ? "x86_64" : "i386"); if (strcmp(a0, program)) execvp(argv[0] = program, argv); tcc_error("could not run '%s'", program); } #endif /* TCC_TARGET_I386 && TCC_TARGET_X86_64 */ /* -------------------------------------------------------------- */ /* enable commandline wildcard expansion (tcc -o x.exe *.c) */ #ifdef _WIN32 int _CRT_glob = 1; #ifndef _CRT_glob int _dowildcard = 1; #endif #endif /* -------------------------------------------------------------- */ /* generate xxx.d file */ ST_FUNC void gen_makedeps(TCCState *s, const char *target, const char *filename) { FILE *depout; char buf[1024]; int i; if (!filename) { /* compute filename automatically: dir/file.o -> dir/file.d */ snprintf(buf, sizeof buf, "%.*s.d", (int)(tcc_fileextension(target) - target), target); filename = buf; } if (s->verbose) printf("<- %s\n", filename); /* XXX return err codes instead of error() ? */ depout = fopen(filename, "w"); if (!depout) tcc_error("could not open '%s'", filename); fprintf(depout, "%s: \\\n", target); for (i=0; i<s->nb_target_deps; ++i) fprintf(depout, " %s \\\n", s->target_deps[i]); fprintf(depout, "\n"); fclose(depout); } /* -------------------------------------------------------------- */
if (ret == 1) return ar_usage(ret); if ((fh = fopen(argv[i_lib], "wb")) == NULL) { fprintf(stderr, "tcc: ar: can't open file %s \n", argv[i_lib]); goto the_end; }
if (ret == 1) return ar_usage(ret);
// write header
if ((fh = fopen(argv[i_lib], "wb")) == NULL) { fprintf(stderr, "tcc: ar: can't open file %s \n", argv[i_lib]); goto the_end; } // write header
/* * TCC - Tiny C Compiler * * Copyright (c) 2001-2004 Fabrice Bellard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef ONE_SOURCE #include "libtcc.c" #else #include "tcc.h" #endif #include "tcctools.c" static const char help[] = "Tiny C Compiler "TCC_VERSION" - Copyright (C) 2001-2006 Fabrice Bellard\n" "Usage: tcc [options...] [-o outfile] [-c] infile(s)...\n" " tcc [options...] -run infile [arguments...]\n" "General options:\n" " -c compile only - generate an object file\n" " -o outfile set output filename\n" " -run run compiled source\n" " -fflag set or reset (with 'no-' prefix) 'flag' (see tcc -hh)\n" " -Wwarning set or reset (with 'no-' prefix) 'warning' (see tcc -hh)\n" " -w disable all warnings\n" " -v -vv show version, show search paths or loaded files\n" " -h -hh show this, show more help\n" " -bench show compilation statistics\n" " - use stdin pipe as infile\n" " @listfile read arguments from listfile\n" "Preprocessor options:\n" " -Idir add include path 'dir'\n" " -Dsym[=val] define 'sym' with value 'val'\n" " -Usym undefine 'sym'\n" " -E preprocess only\n" "Linker options:\n" " -Ldir add library path 'dir'\n" " -llib link with dynamic or static library 'lib'\n" " -r generate (relocatable) object file\n" " -shared generate a shared library/dll\n" " -rdynamic export all global symbols to dynamic linker\n" " -soname set name for shared library to be used at runtime\n" " -Wl,-opt[=val] set linker option (see tcc -hh)\n" "Debugger options:\n" " -g generate runtime debug info\n" #ifdef CONFIG_TCC_BCHECK " -b compile with built-in memory and bounds checker (implies -g)\n" #endif #ifdef CONFIG_TCC_BACKTRACE " -bt N show N callers in stack traces\n" #endif "Misc. options:\n" " -x[c|a|n] specify type of the next infile\n" " -nostdinc do not use standard system include paths\n" " -nostdlib do not link with standard crt and libraries\n" " -Bdir set tcc's private include/library dir\n" " -MD generate dependency file for make\n" " -MF file specify dependency file name\n" " -m32/64 defer to i386/x86_64 cross compiler\n" "Tools:\n" " create library : tcc -ar [rcsv] lib.a files\n" #ifdef TCC_TARGET_PE " create def file : tcc -impdef lib.dll [-v] [-o lib.def]\n" #endif ; static const char help2[] = "Tiny C Compiler "TCC_VERSION" - More Options\n" "Special options:\n" " -P -P1 with -E: no/alternative #line output\n" " -dD -dM with -E: output #define directives\n" " -pthread same as -D_REENTRANT and -lpthread\n" " -On same as -D__OPTIMIZE__ for n > 0\n" " -Wp,-opt same as -opt\n" " -include file include 'file' above each input file\n" " -isystem dir add 'dir' to system include path\n" " -iwithprefix dir set tcc's private include/library subdir\n" " -static link to static libraries (not recommended)\n" " -dumpversion print version\n" " -print-search-dirs print search paths\n" "Ignored options:\n" " --param -pedantic -pipe -s -std -traditional\n" "-W... warnings:\n" " all turn on some (*) warnings\n" " error stop after first warning\n" " unsupported warn about ignored options, pragmas, etc.\n" " write-strings strings are const\n" " implicit-function-declaration warn for missing prototype (*)\n" "-f[no-]... flags:\n" " unsigned-char default char is unsigned\n" " signed-char default char is signed\n" " common use common section instead of bss\n" " leading-underscore decorate extern symbols\n" " ms-extensions allow anonymous struct in struct\n" " dollars-in-identifiers allow '$' in C symbols\n" "-m... target specific options:\n" " ms-bitfields use MSVC bitfield layout\n" #ifdef TCC_TARGET_ARM " float-abi hard/softfp on arm\n" #endif #ifdef TCC_TARGET_X86_64 " no-sse disable floats on x86_64\n" #endif "-Wl,... linker options:\n" " -nostdlib do not link with standard crt/libs\n" " -[no-]whole-archive load lib(s) fully/only as needed\n" " -export-all-symbols same as -rdynamic\n" " -image-base= -Ttext= set base address of executable\n" " -section-alignment= set section alignment in executable\n" #ifdef TCC_TARGET_PE " -file-alignment= set PE file alignment\n" " -stack= set PE stack reserve\n" " -large-address-aware set related PE option\n" " -subsystem=[console/windows] set PE subsystem\n" " -oformat=[pe-* binary] set executable output format\n" "Predefined macros:\n" " tcc -E -dM - < nul\n" #else " -rpath= set dynamic library search path\n" " -enable-new-dtags set DT_RUNPATH instead of DT_RPATH\n" " -soname= set DT_SONAME elf tag\n" " -Bsymbolic set DT_SYMBOLIC elf tag\n" " -oformat=[elf32/64-* binary] set executable output format\n" " -init= -fini= -as-needed -O (ignored)\n" "Predefined macros:\n" " tcc -E -dM - < /dev/null\n" #endif "See also the manual for more details.\n" ; static const char version[] = "tcc version "TCC_VERSION" (" #ifdef TCC_TARGET_I386 "i386" #elif defined TCC_TARGET_X86_64 "x86_64" #elif defined TCC_TARGET_C67 "C67" #elif defined TCC_TARGET_ARM "ARM" #elif defined TCC_TARGET_ARM64 "AArch64" #endif #ifdef TCC_ARM_HARDFLOAT " Hard Float" #endif #ifdef TCC_TARGET_PE " Windows" #elif defined(TCC_TARGET_MACHO) " Darwin" #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) " FreeBSD" #else " Linux" #endif ")\n" ; static void print_dirs(const char *msg, char **paths, int nb_paths) { int i; printf("%s:\n%s", msg, nb_paths ? "" : " -\n"); for(i = 0; i < nb_paths; i++) printf(" %s\n", paths[i]); } static void print_search_dirs(TCCState *s) { printf("install: %s\n", s->tcc_lib_path); /* print_dirs("programs", NULL, 0); */ print_dirs("include", s->sysinclude_paths, s->nb_sysinclude_paths); print_dirs("libraries", s->library_paths, s->nb_library_paths); #ifndef TCC_TARGET_PE print_dirs("crt", s->crt_paths, s->nb_crt_paths); printf("libtcc1:\n %s/"TCC_LIBTCC1"\n", s->tcc_lib_path); printf("elfinterp:\n %s\n", DEFAULT_ELFINTERP(s)); #endif } static void set_environment(TCCState *s) { char * path; path = getenv("C_INCLUDE_PATH"); if(path != NULL) { tcc_add_include_path(s, path); } path = getenv("CPATH"); if(path != NULL) { tcc_add_include_path(s, path); } path = getenv("LIBRARY_PATH"); if(path != NULL) { tcc_add_library_path(s, path); } } static char *default_outputfile(TCCState *s, const char *first_file) { char buf[1024]; char *ext; const char *name = "a"; if (first_file && strcmp(first_file, "-")) name = tcc_basename(first_file); snprintf(buf, sizeof(buf), "%s", name); ext = tcc_fileextension(buf); #ifdef TCC_TARGET_PE if (s->output_type == TCC_OUTPUT_DLL) strcpy(ext, ".dll"); else if (s->output_type == TCC_OUTPUT_EXE) strcpy(ext, ".exe"); else #endif if (s->output_type == TCC_OUTPUT_OBJ && !s->option_r && *ext) strcpy(ext, ".o"); else strcpy(buf, "a.out"); return tcc_strdup(buf); } static unsigned getclock_ms(void) { #ifdef _WIN32 return GetTickCount(); #else struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec*1000 + (tv.tv_usec+500)/1000; #endif } int main(int argc, char **argv) { TCCState *s; int ret, opt, n = 0; unsigned start_time = 0; const char *first_file; redo: s = tcc_new(); opt = tcc_parse_args(s, &argc, &argv, 1); if (n == 0) { if (opt == OPT_HELP) return printf(help), 1; if (opt == OPT_HELP2) return printf(help2), 1; if (opt == OPT_M32 || opt == OPT_M64) tcc_tool_cross(s, argv, opt); /* never returns */ if (s->verbose) printf(version); if (opt == OPT_AR) return tcc_tool_ar(s, argc, argv); #ifdef TCC_TARGET_PE if (opt == OPT_IMPDEF) return tcc_tool_impdef(s, argc, argv); #endif if (opt == OPT_V) return 0; if (opt == OPT_PRINT_DIRS) { /* initialize search dirs */ tcc_set_output_type(s, TCC_OUTPUT_MEMORY); print_search_dirs(s); return 0; } n = s->nb_files; if (n == 0) tcc_error("no input files\n"); if (s->output_type == TCC_OUTPUT_PREPROCESS) { if (!s->outfile) { s->ppfp = stdout; } else { s->ppfp = fopen(s->outfile, "w"); if (!s->ppfp) tcc_error("could not write '%s'", s->outfile); } } else if (s->output_type == TCC_OUTPUT_OBJ && !s->option_r) { if (s->nb_libraries) tcc_error("cannot specify libraries with -c"); if (n > 1 && s->outfile) tcc_error("cannot specify output file with -c many files"); } else { if (s->option_pthread) tcc_set_options(s, "-lpthread"); } if (s->do_bench) start_time = getclock_ms(); } set_environment(s); if (s->output_type == 0) s->output_type = TCC_OUTPUT_EXE; tcc_set_output_type(s, s->output_type); /* compile or add each files or library */ for (first_file = NULL, ret = 0;;) { struct filespec *f = s->files[s->nb_files - n]; s->filetype = f->type; s->alacarte_link = f->alacarte; if (f->type == AFF_TYPE_LIB) { if (tcc_add_library_err(s, f->name) < 0) ret = 1; } else { if (1 == s->verbose) printf("-> %s\n", f->name); if (!first_file) first_file = f->name; if (tcc_add_file(s, f->name) < 0) ret = 1; } s->filetype = 0; s->alacarte_link = 1; if (ret || --n == 0 || (s->output_type == TCC_OUTPUT_OBJ && !s->option_r)) break; } if (s->output_type == TCC_OUTPUT_PREPROCESS) { if (s->outfile) fclose(s->ppfp); } else if (0 == ret) { if (s->output_type == TCC_OUTPUT_MEMORY) { #ifdef TCC_IS_NATIVE ret = tcc_run(s, argc, argv); #endif } else { if (!s->outfile) s->outfile = default_outputfile(s, first_file); if (tcc_output_file(s, s->outfile)) ret = 1; else if (s->gen_deps) gen_makedeps(s, s->outfile, s->deps_outfile); } } if (s->do_bench && ret == 0 && n == 0) tcc_print_stats(s, getclock_ms() - start_time); tcc_delete(s); if (ret == 0 && n) goto redo; /* compile more files with -c */ return ret; }
/* * TCC - Tiny C Compiler * * Copyright (c) 2001-2004 Fabrice Bellard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _TCC_H #include "tcc.h" #endif /********************************************************/ /* global variables */ /* use GNU C extensions */ ST_DATA int gnu_ext = 1; /* use TinyCC extensions */ ST_DATA int tcc_ext = 1; /* XXX: get rid of this ASAP */ ST_DATA struct TCCState *tcc_state; static int nb_states; /********************************************************/ #ifdef ONE_SOURCE #include "tccpp.c" #include "tccgen.c" #include "tccelf.c" #include "tccrun.c" #ifdef TCC_TARGET_I386 #include "i386-gen.c" #include "i386-link.c" #include "i386-asm.c" #endif #ifdef TCC_TARGET_ARM #include "arm-gen.c" #include "arm-link.c" #include "arm-asm.c" #endif #ifdef TCC_TARGET_ARM64 #include "arm64-gen.c" #include "arm64-link.c" #endif #ifdef TCC_TARGET_C67 #include "c67-gen.c" #include "c67-link.c" #endif #ifdef TCC_TARGET_X86_64 #include "x86_64-gen.c" #include "x86_64-link.c" #include "i386-asm.c" #endif #ifdef CONFIG_TCC_ASM #include "tccasm.c" #endif #ifdef TCC_TARGET_COFF #include "tcccoff.c" #endif #ifdef TCC_TARGET_PE #include "tccpe.c" #endif #endif /* ONE_SOURCE */ /********************************************************/ #ifndef CONFIG_TCC_ASM ST_FUNC void asm_instr(void) { tcc_error("inline asm() not supported"); } ST_FUNC void asm_global_instr(void) { tcc_error("inline asm() not supported"); } #endif /********************************************************/ #ifdef _WIN32 ST_FUNC char *normalize_slashes(char *path) { char *p; for (p = path; *p; ++p) if (*p == '\\') *p = '/'; return path; } static HMODULE tcc_module; /* on win32, we suppose the lib and includes are at the location of 'tcc.exe' */ static void tcc_set_lib_path_w32(TCCState *s) { char path[1024], *p; GetModuleFileNameA(tcc_module, path, sizeof path); p = tcc_basename(normalize_slashes(strlwr(path))); if (p - 5 > path && 0 == strncmp(p - 5, "/bin/", 5)) p -= 5; else if (p > path) p--; *p = 0; tcc_set_lib_path(s, path); } #ifdef TCC_TARGET_PE static void tcc_add_systemdir(TCCState *s) { char buf[1000]; GetSystemDirectory(buf, sizeof buf); tcc_add_library_path(s, normalize_slashes(buf)); } #endif #ifdef LIBTCC_AS_DLL BOOL WINAPI DllMain (HINSTANCE hDll, DWORD dwReason, LPVOID lpReserved) { if (DLL_PROCESS_ATTACH == dwReason) tcc_module = hDll; return TRUE; } #endif #endif /********************************************************/ /* copy a string and truncate it. */ ST_FUNC char *pstrcpy(char *buf, int buf_size, const char *s) { char *q, *q_end; int c; if (buf_size > 0) { q = buf; q_end = buf + buf_size - 1; while (q < q_end) { c = *s++; if (c == '\0') break; *q++ = c; } *q = '\0'; } return buf; } /* strcat and truncate. */ ST_FUNC char *pstrcat(char *buf, int buf_size, const char *s) { int len; len = strlen(buf); if (len < buf_size) pstrcpy(buf + len, buf_size - len, s); return buf; } ST_FUNC char *pstrncpy(char *out, const char *in, size_t num) { memcpy(out, in, num); out[num] = '\0'; return out; } /* extract the basename of a file */ PUB_FUNC char *tcc_basename(const char *name) { char *p = strchr(name, 0); while (p > name && !IS_DIRSEP(p[-1])) --p; return p; } /* extract extension part of a file * * (if no extension, return pointer to end-of-string) */ PUB_FUNC char *tcc_fileextension (const char *name) { char *b = tcc_basename(name); char *e = strrchr(b, '.'); return e ? e : strchr(b, 0); } /********************************************************/ /* memory management */ #undef free #undef malloc #undef realloc #ifndef MEM_DEBUG PUB_FUNC void tcc_free(void *ptr) { free(ptr); } PUB_FUNC void *tcc_malloc(unsigned long size) { void *ptr; ptr = malloc(size); if (!ptr && size) tcc_error("memory full (malloc)"); return ptr; } PUB_FUNC void *tcc_mallocz(unsigned long size) { void *ptr; ptr = tcc_malloc(size); memset(ptr, 0, size); return ptr; } PUB_FUNC void *tcc_realloc(void *ptr, unsigned long size) { void *ptr1; ptr1 = realloc(ptr, size); if (!ptr1 && size) tcc_error("memory full (realloc)"); return ptr1; } PUB_FUNC char *tcc_strdup(const char *str) { char *ptr; ptr = tcc_malloc(strlen(str) + 1); strcpy(ptr, str); return ptr; } PUB_FUNC void tcc_memcheck(void) { } #else #define MEM_DEBUG_MAGIC1 0xFEEDDEB1 #define MEM_DEBUG_MAGIC2 0xFEEDDEB2 #define MEM_DEBUG_MAGIC3 0xFEEDDEB3 #define MEM_DEBUG_FILE_LEN 40 #define MEM_DEBUG_CHECK3(header) \ ((mem_debug_header_t*)((char*)header + header->size))->magic3 #define MEM_USER_PTR(header) \ ((char *)header + offsetof(mem_debug_header_t, magic3)) #define MEM_HEADER_PTR(ptr) \ (mem_debug_header_t *)((char*)ptr - offsetof(mem_debug_header_t, magic3)) struct mem_debug_header { unsigned magic1; unsigned size; struct mem_debug_header *prev; struct mem_debug_header *next; int line_num; char file_name[MEM_DEBUG_FILE_LEN + 1]; unsigned magic2; __attribute__((aligned(16))) unsigned magic3; }; typedef struct mem_debug_header mem_debug_header_t; static mem_debug_header_t *mem_debug_chain; static unsigned mem_cur_size; static unsigned mem_max_size; static mem_debug_header_t *malloc_check(void *ptr, const char *msg) { mem_debug_header_t * header = MEM_HEADER_PTR(ptr); if (header->magic1 != MEM_DEBUG_MAGIC1 || header->magic2 != MEM_DEBUG_MAGIC2 || MEM_DEBUG_CHECK3(header) != MEM_DEBUG_MAGIC3 || header->size == (unsigned)-1) { fprintf(stderr, "%s check failed\n", msg); if (header->magic1 == MEM_DEBUG_MAGIC1) fprintf(stderr, "%s:%u: block allocated here.\n", header->file_name, header->line_num); exit(1); } return header; } PUB_FUNC void *tcc_malloc_debug(unsigned long size, const char *file, int line) { int ofs; mem_debug_header_t *header; header = malloc(sizeof(mem_debug_header_t) + size); if (!header) tcc_error("memory full (malloc)"); header->magic1 = MEM_DEBUG_MAGIC1; header->magic2 = MEM_DEBUG_MAGIC2; header->size = size; MEM_DEBUG_CHECK3(header) = MEM_DEBUG_MAGIC3; header->line_num = line; ofs = strlen(file) - MEM_DEBUG_FILE_LEN; strncpy(header->file_name, file + (ofs > 0 ? ofs : 0), MEM_DEBUG_FILE_LEN); header->file_name[MEM_DEBUG_FILE_LEN] = 0; header->next = mem_debug_chain; header->prev = NULL; if (header->next) header->next->prev = header; mem_debug_chain = header; mem_cur_size += size; if (mem_cur_size > mem_max_size) mem_max_size = mem_cur_size; return MEM_USER_PTR(header); } PUB_FUNC void tcc_free_debug(void *ptr) { mem_debug_header_t *header; if (!ptr) return; header = malloc_check(ptr, "tcc_free"); mem_cur_size -= header->size; header->size = (unsigned)-1; if (header->next) header->next->prev = header->prev; if (header->prev) header->prev->next = header->next; if (header == mem_debug_chain) mem_debug_chain = header->next; free(header); } PUB_FUNC void *tcc_mallocz_debug(unsigned long size, const char *file, int line) { void *ptr; ptr = tcc_malloc_debug(size,file,line); memset(ptr, 0, size); return ptr; } PUB_FUNC void *tcc_realloc_debug(void *ptr, unsigned long size, const char *file, int line) { mem_debug_header_t *header; int mem_debug_chain_update = 0; if (!ptr) return tcc_malloc_debug(size, file, line); header = malloc_check(ptr, "tcc_realloc"); mem_cur_size -= header->size; mem_debug_chain_update = (header == mem_debug_chain); header = realloc(header, sizeof(mem_debug_header_t) + size); if (!header) tcc_error("memory full (realloc)"); header->size = size; MEM_DEBUG_CHECK3(header) = MEM_DEBUG_MAGIC3; if (header->next) header->next->prev = header; if (header->prev) header->prev->next = header; if (mem_debug_chain_update) mem_debug_chain = header; mem_cur_size += size; if (mem_cur_size > mem_max_size) mem_max_size = mem_cur_size; return MEM_USER_PTR(header); } PUB_FUNC char *tcc_strdup_debug(const char *str, const char *file, int line) { char *ptr; ptr = tcc_malloc_debug(strlen(str) + 1, file, line); strcpy(ptr, str); return ptr; } PUB_FUNC void tcc_memcheck(void) { if (mem_cur_size) { mem_debug_header_t *header = mem_debug_chain; fprintf(stderr, "MEM_DEBUG: mem_leak= %d bytes, mem_max_size= %d bytes\n", mem_cur_size, mem_max_size); while (header) { fprintf(stderr, "%s:%u: error: %u bytes leaked\n", header->file_name, header->line_num, header->size); header = header->next; } #if MEM_DEBUG-0 == 2 exit(2); #endif } } #endif /* MEM_DEBUG */ #define free(p) use_tcc_free(p) #define malloc(s) use_tcc_malloc(s) #define realloc(p, s) use_tcc_realloc(p, s) /********************************************************/ /* dynarrays */ ST_FUNC void dynarray_add(void *ptab, int *nb_ptr, void *data) { int nb, nb_alloc; void **pp; nb = *nb_ptr; pp = *(void ***)ptab; /* every power of two we double array size */ if ((nb & (nb - 1)) == 0) { if (!nb) nb_alloc = 1; else nb_alloc = nb * 2; pp = tcc_realloc(pp, nb_alloc * sizeof(void *)); *(void***)ptab = pp; } pp[nb++] = data; *nb_ptr = nb; } ST_FUNC void dynarray_reset(void *pp, int *n) { void **p; for (p = *(void***)pp; *n; ++p, --*n) if (*p) tcc_free(*p); tcc_free(*(void**)pp); *(void**)pp = NULL; } static void tcc_split_path(TCCState *s, void *p_ary, int *p_nb_ary, const char *in) { const char *p; do { int c; CString str; cstr_new(&str); for (p = in; c = *p, c != '\0' && c != PATHSEP; ++p) { if (c == '{' && p[1] && p[2] == '}') { c = p[1], p += 2; if (c == 'B') cstr_cat(&str, s->tcc_lib_path, -1); } else { cstr_ccat(&str, c); } } if (str.size) { cstr_ccat(&str, '\0'); dynarray_add(p_ary, p_nb_ary, tcc_strdup(str.data)); } cstr_free(&str); in = p+1; } while (*p); } /********************************************************/ static void strcat_vprintf(char *buf, int buf_size, const char *fmt, va_list ap) { int len; len = strlen(buf); vsnprintf(buf + len, buf_size - len, fmt, ap); } static void strcat_printf(char *buf, int buf_size, const char *fmt, ...) { va_list ap; va_start(ap, fmt); strcat_vprintf(buf, buf_size, fmt, ap); va_end(ap); } static void error1(TCCState *s1, int is_warning, const char *fmt, va_list ap) { char buf[2048]; BufferedFile **pf, *f; buf[0] = '\0'; /* use upper file if inline ":asm:" or token ":paste:" */ for (f = file; f && f->filename[0] == ':'; f = f->prev) ; if (f) { for(pf = s1->include_stack; pf < s1->include_stack_ptr; pf++) strcat_printf(buf, sizeof(buf), "In file included from %s:%d:\n", (*pf)->filename, (*pf)->line_num); if (f->line_num > 0) { strcat_printf(buf, sizeof(buf), "%s:%d: ", f->filename, f->line_num - !!(tok_flags & TOK_FLAG_BOL)); } else { strcat_printf(buf, sizeof(buf), "%s: ", f->filename); } } else { strcat_printf(buf, sizeof(buf), "tcc: "); } if (is_warning) strcat_printf(buf, sizeof(buf), "warning: "); else strcat_printf(buf, sizeof(buf), "error: "); strcat_vprintf(buf, sizeof(buf), fmt, ap); if (!s1->error_func) { /* default case: stderr */ if (s1->ppfp) /* print a newline during tcc -E */ fprintf(s1->ppfp, "\n"), fflush(s1->ppfp); fflush(stdout); /* flush -v output */ fprintf(stderr, "%s\n", buf); fflush(stderr); /* print error/warning now (win32) */ } else { s1->error_func(s1->error_opaque, buf); } if (!is_warning || s1->warn_error) s1->nb_errors++; } LIBTCCAPI void tcc_set_error_func(TCCState *s, void *error_opaque, void (*error_func)(void *opaque, const char *msg)) { s->error_opaque = error_opaque; s->error_func = error_func; } /* error without aborting current compilation */ PUB_FUNC void tcc_error_noabort(const char *fmt, ...) { TCCState *s1 = tcc_state; va_list ap; va_start(ap, fmt); error1(s1, 0, fmt, ap); va_end(ap); } PUB_FUNC void tcc_error(const char *fmt, ...) { TCCState *s1 = tcc_state; va_list ap; va_start(ap, fmt); error1(s1, 0, fmt, ap); va_end(ap); /* better than nothing: in some cases, we accept to handle errors */ if (s1->error_set_jmp_enabled) { longjmp(s1->error_jmp_buf, 1); } else { /* XXX: eliminate this someday */ exit(1); } } PUB_FUNC void tcc_warning(const char *fmt, ...) { TCCState *s1 = tcc_state; va_list ap; if (s1->warn_none) return; va_start(ap, fmt); error1(s1, 1, fmt, ap); va_end(ap); } /********************************************************/ /* I/O layer */ ST_FUNC void tcc_open_bf(TCCState *s1, const char *filename, int initlen) { BufferedFile *bf; int buflen = initlen ? initlen : IO_BUF_SIZE; bf = tcc_mallocz(sizeof(BufferedFile) + buflen); bf->buf_ptr = bf->buffer; bf->buf_end = bf->buffer + initlen; bf->buf_end[0] = CH_EOB; /* put eob symbol */ pstrcpy(bf->filename, sizeof(bf->filename), filename); pstrcpy(bf->filename2, sizeof(bf->filename2), filename); #ifdef _WIN32 normalize_slashes(bf->filename); #endif bf->line_num = 1; bf->ifdef_stack_ptr = s1->ifdef_stack_ptr; bf->fd = -1; bf->prev = file; file = bf; } ST_FUNC void tcc_close(void) { BufferedFile *bf = file; if (bf->fd > 0) { close(bf->fd); total_lines += bf->line_num; } file = bf->prev; tcc_free(bf); } ST_FUNC int tcc_open(TCCState *s1, const char *filename) { int fd; if (strcmp(filename, "-") == 0) fd = 0, filename = "<stdin>"; else fd = open(filename, O_RDONLY | O_BINARY); if ((s1->verbose == 2 && fd >= 0) || s1->verbose == 3) printf("%s %*s%s\n", fd < 0 ? "nf":"->", (int)(s1->include_stack_ptr - s1->include_stack), "", filename); if (fd < 0) return -1; tcc_open_bf(s1, filename, 0); file->fd = fd; return fd; } /* compile the C file opened in 'file'. Return non zero if errors. */ static int tcc_compile(TCCState *s1) { Sym *define_start; define_start = define_stack; #if HAVE_SETJMP if (setjmp(s1->error_jmp_buf) == 0) { #else if (1) { #endif s1->nb_errors = 0; s1->error_set_jmp_enabled = 1; preprocess_start(s1); tccgen_start(s1); #ifdef INC_DEBUG printf("%s: **** new file\n", file->filename); #endif ch = file->buf_ptr[0]; tok_flags = TOK_FLAG_BOL | TOK_FLAG_BOF; parse_flags = PARSE_FLAG_PREPROCESS | PARSE_FLAG_TOK_NUM | PARSE_FLAG_TOK_STR; next(); decl(VT_CONST); if (tok != TOK_EOF) expect("declaration"); /* free defines here already on behalf of of M.M.'s possibly existing experimental preprocessor implementation. The normal call below is still there to free after error-longjmp */ free_defines(define_start); tccgen_end(s1); } s1->error_set_jmp_enabled = 0; free_inline_functions(s1); /* reset define stack, but keep -D and built-ins */ free_defines(define_start); sym_pop(&global_stack, NULL, 0); sym_pop(&local_stack, NULL, 0); return s1->nb_errors != 0 ? -1 : 0; } LIBTCCAPI int tcc_compile_string(TCCState *s, const char *str) { int len, ret; len = strlen(str); tcc_open_bf(s, "<string>", len); memcpy(file->buffer, str, len); ret = tcc_compile(s); tcc_close(); return ret; } /* define a preprocessor symbol. A value can also be provided with the '=' operator */ LIBTCCAPI void tcc_define_symbol(TCCState *s1, const char *sym, const char *value) { int len1, len2; /* default value */ if (!value) value = "1"; len1 = strlen(sym); len2 = strlen(value); /* init file structure */ tcc_open_bf(s1, "<define>", len1 + len2 + 1); memcpy(file->buffer, sym, len1); file->buffer[len1] = ' '; memcpy(file->buffer + len1 + 1, value, len2); /* parse with define parser */ ch = file->buf_ptr[0]; next_nomacro(); parse_define(); tcc_close(); } /* undefine a preprocessor symbol */ LIBTCCAPI void tcc_undefine_symbol(TCCState *s1, const char *sym) { TokenSym *ts; Sym *s; ts = tok_alloc(sym, strlen(sym)); s = define_find(ts->tok); /* undefine symbol by putting an invalid name */ if (s) define_undef(s); } /* cleanup all static data used during compilation */ static void tcc_cleanup(void) { if (NULL == tcc_state) return; tccpp_delete(tcc_state); tcc_state = NULL; /* free sym_pools */ dynarray_reset(&sym_pools, &nb_sym_pools); /* reset symbol stack */ sym_free_first = NULL; } LIBTCCAPI TCCState *tcc_new(void) { TCCState *s; tcc_cleanup(); s = tcc_mallocz(sizeof(TCCState)); if (!s) return NULL; tcc_state = s; ++nb_states; #if BOOTSTRAP s->static_link = 1; #endif s->alacarte_link = 1; s->nocommon = 1; s->warn_implicit_function_declaration = 1; #ifdef CHAR_IS_UNSIGNED s->char_is_unsigned = 1; #endif #ifdef TCC_TARGET_I386 s->seg_size = 32; #endif /* enable this if you want symbols with leading underscore on windows: */ #if 0 /* def TCC_TARGET_PE */ s->leading_underscore = 1; #endif #ifdef _WIN32 tcc_set_lib_path_w32(s); #else tcc_set_lib_path(s, CONFIG_TCCDIR); #endif tccelf_new(s); tccpp_new(s); /* we add dummy defines for some special macros to speed up tests and to have working defined() */ define_push(TOK___LINE__, MACRO_OBJ, NULL, NULL); define_push(TOK___FILE__, MACRO_OBJ, NULL, NULL); define_push(TOK___DATE__, MACRO_OBJ, NULL, NULL); define_push(TOK___TIME__, MACRO_OBJ, NULL, NULL); { /* define __TINYC__ 92X */ char buffer[32]; int a,b,c; sscanf(TCC_VERSION, "%d.%d.%d", &a, &b, &c); sprintf(buffer, "%d", a*10000 + b*100 + c); tcc_define_symbol(s, "__TINYC__", buffer); } /* standard defines */ tcc_define_symbol(s, "__STDC__", NULL); tcc_define_symbol(s, "__STDC_VERSION__", "199901L"); tcc_define_symbol(s, "__STDC_HOSTED__", NULL); tcc_define_symbol(s, "__SIZEOF_LONG_LONG__", "8"); /* target defines */ #if defined(TCC_TARGET_I386) tcc_define_symbol(s, "__i386__", NULL); tcc_define_symbol(s, "__i386", NULL); tcc_define_symbol(s, "i386", NULL); #elif defined(TCC_TARGET_X86_64) tcc_define_symbol(s, "__x86_64__", NULL); #elif defined(TCC_TARGET_ARM) tcc_define_symbol(s, "__ARM_ARCH_4__", NULL); tcc_define_symbol(s, "__arm_elf__", NULL); tcc_define_symbol(s, "__arm_elf", NULL); tcc_define_symbol(s, "arm_elf", NULL); tcc_define_symbol(s, "__arm__", NULL); tcc_define_symbol(s, "__arm", NULL); tcc_define_symbol(s, "arm", NULL); tcc_define_symbol(s, "__APCS_32__", NULL); tcc_define_symbol(s, "__ARMEL__", NULL); #if defined(TCC_ARM_EABI) tcc_define_symbol(s, "__ARM_EABI__", NULL); #endif #if defined(TCC_ARM_HARDFLOAT) s->float_abi = ARM_HARD_FLOAT; tcc_define_symbol(s, "__ARM_PCS_VFP", NULL); #else s->float_abi = ARM_SOFTFP_FLOAT; #endif #elif defined(TCC_TARGET_ARM64) tcc_define_symbol(s, "__aarch64__", NULL); #endif #ifdef TCC_TARGET_PE tcc_define_symbol(s, "_WIN32", NULL); # ifdef TCC_TARGET_X86_64 tcc_define_symbol(s, "_WIN64", NULL); # endif #else tcc_define_symbol(s, "__unix__", NULL); tcc_define_symbol(s, "__unix", NULL); tcc_define_symbol(s, "unix", NULL); # if defined(__linux__) tcc_define_symbol(s, "__linux__", NULL); tcc_define_symbol(s, "__linux", NULL); # endif # if defined(__FreeBSD__) tcc_define_symbol(s, "__FreeBSD__", "__FreeBSD__"); /* No 'Thread Storage Local' on FreeBSD with tcc */ tcc_define_symbol(s, "__NO_TLS", NULL); # endif # if defined(__FreeBSD_kernel__) tcc_define_symbol(s, "__FreeBSD_kernel__", NULL); # endif #endif # if defined(__NetBSD__) tcc_define_symbol(s, "__NetBSD__", "__NetBSD__"); # endif # if defined(__OpenBSD__) tcc_define_symbol(s, "__OpenBSD__", "__OpenBSD__"); # endif /* TinyCC & gcc defines */ #if defined(TCC_TARGET_PE) && PTR_SIZE == 8 /* 64bit Windows. */ tcc_define_symbol(s, "__SIZE_TYPE__", "unsigned long long"); tcc_define_symbol(s, "__PTRDIFF_TYPE__", "long long"); tcc_define_symbol(s, "__LLP64__", NULL); #elif PTR_SIZE == 8 /* Other 64bit systems. */ tcc_define_symbol(s, "__SIZE_TYPE__", "unsigned long"); tcc_define_symbol(s, "__PTRDIFF_TYPE__", "long"); tcc_define_symbol(s, "__LP64__", NULL); #else /* Other 32bit systems. */ tcc_define_symbol(s, "__SIZE_TYPE__", "unsigned long"); tcc_define_symbol(s, "__PTRDIFF_TYPE__", "long"); tcc_define_symbol(s, "__ILP32__", NULL); #endif #if defined(TCC_MUSL) tcc_define_symbol(s, "__builtin_va_list", "void *"); #endif /* TCC_MUSL */ #ifdef TCC_TARGET_PE tcc_define_symbol(s, "__WCHAR_TYPE__", "unsigned short"); tcc_define_symbol(s, "__WINT_TYPE__", "unsigned short"); #else tcc_define_symbol(s, "__WCHAR_TYPE__", "int"); /* wint_t is unsigned int by default, but (signed) int on BSDs and unsigned short on windows. Other OSes might have still other conventions, sigh. */ # if defined(__FreeBSD__) || defined (__FreeBSD_kernel__) \ || defined(__NetBSD__) || defined(__OpenBSD__) tcc_define_symbol(s, "__WINT_TYPE__", "int"); # ifdef __FreeBSD__ /* define __GNUC__ to have some useful stuff from sys/cdefs.h that are unconditionally used in FreeBSDs other system headers :/ */ tcc_define_symbol(s, "__GNUC__", "2"); tcc_define_symbol(s, "__GNUC_MINOR__", "7"); tcc_define_symbol(s, "__builtin_alloca", "alloca"); # endif # else tcc_define_symbol(s, "__WINT_TYPE__", "unsigned int"); /* glibc defines */ tcc_define_symbol(s, "__REDIRECT(name, proto, alias)", "name proto __asm__ (#alias)"); tcc_define_symbol(s, "__REDIRECT_NTH(name, proto, alias)", "name proto __asm__ (#alias) __THROW"); # endif /* Some GCC builtins that are simple to express as macros. */ tcc_define_symbol(s, "__builtin_extract_return_addr(x)", "x"); #endif /* ndef TCC_TARGET_PE */ return s; } LIBTCCAPI void tcc_delete(TCCState *s1) { tcc_cleanup(); /* free sections */ tccelf_delete(s1); /* free library paths */ dynarray_reset(&s1->library_paths, &s1->nb_library_paths); dynarray_reset(&s1->crt_paths, &s1->nb_crt_paths); /* free include paths */ dynarray_reset(&s1->cached_includes, &s1->nb_cached_includes); dynarray_reset(&s1->include_paths, &s1->nb_include_paths); dynarray_reset(&s1->sysinclude_paths, &s1->nb_sysinclude_paths); dynarray_reset(&s1->cmd_include_files, &s1->nb_cmd_include_files); tcc_free(s1->tcc_lib_path); tcc_free(s1->soname); tcc_free(s1->rpath); tcc_free(s1->init_symbol); tcc_free(s1->fini_symbol); tcc_free(s1->outfile); tcc_free(s1->deps_outfile); dynarray_reset(&s1->files, &s1->nb_files); dynarray_reset(&s1->target_deps, &s1->nb_target_deps); dynarray_reset(&s1->pragma_libs, &s1->nb_pragma_libs); dynarray_reset(&s1->argv, &s1->argc); #ifdef TCC_IS_NATIVE /* free runtime memory */ tcc_run_free(s1); #endif tcc_free(s1); if (0 == --nb_states) tcc_memcheck(); } LIBTCCAPI int tcc_set_output_type(TCCState *s, int output_type) { s->output_type = output_type; /* always elf for objects */ if (output_type == TCC_OUTPUT_OBJ) s->output_format = TCC_OUTPUT_FORMAT_ELF; if (s->char_is_unsigned) tcc_define_symbol(s, "__CHAR_UNSIGNED__", NULL); if (!s->nostdinc) { /* default include paths */ /* -isystem paths have already been handled */ tcc_add_sysinclude_path(s, CONFIG_TCC_SYSINCLUDEPATHS); } #ifdef CONFIG_TCC_BCHECK if (s->do_bounds_check) { /* if bound checking, then add corresponding sections */ tccelf_bounds_new(s); /* define symbol */ tcc_define_symbol(s, "__BOUNDS_CHECKING_ON", NULL); } #endif if (s->do_debug) { /* add debug sections */ tccelf_stab_new(s); } tcc_add_library_path(s, CONFIG_TCC_LIBPATHS); #ifdef TCC_TARGET_PE # ifdef _WIN32 if (!s->nostdlib && output_type != TCC_OUTPUT_OBJ) tcc_add_systemdir(s); # endif #else /* paths for crt objects */ tcc_split_path(s, &s->crt_paths, &s->nb_crt_paths, CONFIG_TCC_CRTPREFIX); /* add libc crt1/crti objects */ if ((output_type == TCC_OUTPUT_EXE || output_type == TCC_OUTPUT_DLL) && !s->nostdlib) { if (output_type != TCC_OUTPUT_DLL) tcc_add_crt(s, "crt1.o"); tcc_add_crt(s, "crti.o"); } #endif return 0; } LIBTCCAPI int tcc_add_include_path(TCCState *s, const char *pathname) { tcc_split_path(s, &s->include_paths, &s->nb_include_paths, pathname); return 0; } LIBTCCAPI int tcc_add_sysinclude_path(TCCState *s, const char *pathname) { tcc_split_path(s, &s->sysinclude_paths, &s->nb_sysinclude_paths, pathname); return 0; } ST_FUNC int tcc_add_file_internal(TCCState *s1, const char *filename, int flags) { int ret, filetype; filetype = flags & 0x0F; if (filetype == 0) { /* use a file extension to detect a filetype */ const char *ext = tcc_fileextension(filename); if (ext[0]) { ext++; if (!strcmp(ext, "S")) filetype = AFF_TYPE_ASMPP; else if (!strcmp(ext, "s")) filetype = AFF_TYPE_ASM; else if (!PATHCMP(ext, "c") || !PATHCMP(ext, "i")) filetype = AFF_TYPE_C; else filetype = AFF_TYPE_BIN; } else { filetype = AFF_TYPE_C; } } /* open the file */ ret = tcc_open(s1, filename); if (ret < 0) { if (flags & AFF_PRINT_ERROR) tcc_error_noabort("file '%s' not found", filename); return ret; } /* update target deps */ dynarray_add(&s1->target_deps, &s1->nb_target_deps, tcc_strdup(filename)); parse_flags = 0; /* if .S file, define __ASSEMBLER__ like gcc does */ if (filetype == AFF_TYPE_ASM || filetype == AFF_TYPE_ASMPP) { tcc_define_symbol(s1, "__ASSEMBLER__", NULL); parse_flags = PARSE_FLAG_ASM_FILE; } if (flags & AFF_PREPROCESS) { ret = tcc_preprocess(s1); } else if (filetype == AFF_TYPE_C) { ret = tcc_compile(s1); #ifdef CONFIG_TCC_ASM } else if (filetype == AFF_TYPE_ASMPP) { /* non preprocessed assembler */ ret = tcc_assemble(s1, 1); } else if (filetype == AFF_TYPE_ASM) { /* preprocessed assembler */ ret = tcc_assemble(s1, 0); #endif } else { ElfW(Ehdr) ehdr; int fd, obj_type; fd = file->fd; obj_type = tcc_object_type(fd, &ehdr); lseek(fd, 0, SEEK_SET); /* do not display line number if error */ file->line_num = 0; #ifdef TCC_TARGET_MACHO if (0 == obj_type && 0 == strcmp(tcc_fileextension(filename), "dylib")) obj_type = AFF_BINTYPE_DYN; #endif switch (obj_type) { case AFF_BINTYPE_REL: ret = tcc_load_object_file(s1, fd, 0); break; #ifndef TCC_TARGET_PE case AFF_BINTYPE_DYN: if (s1->output_type == TCC_OUTPUT_MEMORY) { ret = 0; #ifdef TCC_IS_NATIVE if (NULL == dlopen(filename, RTLD_GLOBAL | RTLD_LAZY)) ret = -1; #endif } else { ret = tcc_load_dll(s1, fd, filename, (flags & AFF_REFERENCED_DLL) != 0); } break; #endif case AFF_BINTYPE_AR: ret = tcc_load_archive(s1, fd); break; #ifdef TCC_TARGET_COFF case AFF_BINTYPE_C67: ret = tcc_load_coff(s1, fd); break; #endif default: #ifdef TCC_TARGET_PE ret = pe_load_file(s1, filename, fd); #else /* as GNU ld, consider it is an ld script if not recognized */ ret = tcc_load_ldscript(s1); #endif if (ret < 0) tcc_error_noabort("unrecognized file type"); break; } } tcc_close(); return ret; } LIBTCCAPI int tcc_add_file(TCCState *s, const char *filename) { if (s->output_type == TCC_OUTPUT_PREPROCESS) return tcc_add_file_internal(s, filename, AFF_PRINT_ERROR | AFF_PREPROCESS | s->filetype); else return tcc_add_file_internal(s, filename, AFF_PRINT_ERROR | s->filetype); } LIBTCCAPI int tcc_add_library_path(TCCState *s, const char *pathname) { tcc_split_path(s, &s->library_paths, &s->nb_library_paths, pathname); return 0; } static int tcc_add_library_internal(TCCState *s, const char *fmt, const char *filename, int flags, char **paths, int nb_paths) { char buf[1024]; int i; for(i = 0; i < nb_paths; i++) { snprintf(buf, sizeof(buf), fmt, paths[i], filename); if (tcc_add_file_internal(s, buf, flags | AFF_TYPE_BIN) == 0) return 0; } return -1; } /* find and load a dll. Return non zero if not found */ /* XXX: add '-rpath' option support ? */ ST_FUNC int tcc_add_dll(TCCState *s, const char *filename, int flags) { return tcc_add_library_internal(s, "%s/%s", filename, flags, s->library_paths, s->nb_library_paths); } ST_FUNC int tcc_add_crt(TCCState *s, const char *filename) { if (-1 == tcc_add_library_internal(s, "%s/%s", filename, 0, s->crt_paths, s->nb_crt_paths)) tcc_error_noabort("file '%s' not found", filename); return 0; } /* the library name is the same as the argument of the '-l' option */ LIBTCCAPI int tcc_add_library(TCCState *s, const char *libraryname) { #if defined TCC_TARGET_PE const char *libs[] = { "%s/%s.def", "%s/lib%s.def", "%s/%s.dll", "%s/lib%s.dll", "%s/lib%s.a", NULL }; const char **pp = s->static_link ? libs + 4 : libs; #elif defined TCC_TARGET_MACHO const char *libs[] = { "%s/lib%s.dylib", "%s/lib%s.a", NULL }; const char **pp = s->static_link ? libs + 1 : libs; #else const char *libs[] = { "%s/lib%s.so", "%s/lib%s.a", NULL }; const char **pp = s->static_link ? libs + 1 : libs; #endif while (*pp) { if (0 == tcc_add_library_internal(s, *pp, libraryname, 0, s->library_paths, s->nb_library_paths)) return 0; ++pp; } return -1; } PUB_FUNC int tcc_add_library_err(TCCState *s, const char *libname) { int ret = tcc_add_library(s, libname); if (ret < 0) tcc_error_noabort("library '%s' not found", libname); return ret; } /* handle #pragma comment(lib,) */ ST_FUNC void tcc_add_pragma_libs(TCCState *s1) { int i; for (i = 0; i < s1->nb_pragma_libs; i++) tcc_add_library_err(s1, s1->pragma_libs[i]); } LIBTCCAPI int tcc_add_symbol(TCCState *s, const char *name, const void *val) { #ifdef TCC_TARGET_PE /* On x86_64 'val' might not be reachable with a 32bit offset. So it is handled here as if it were in a DLL. */ pe_putimport(s, 0, name, (uintptr_t)val); #else set_elf_sym(symtab_section, (uintptr_t)val, 0, ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, SHN_ABS, name); #endif return 0; } LIBTCCAPI void tcc_set_lib_path(TCCState *s, const char *path) { tcc_free(s->tcc_lib_path); s->tcc_lib_path = tcc_strdup(path); } #define WD_ALL 0x0001 /* warning is activated when using -Wall */ #define FD_INVERT 0x0002 /* invert value before storing */ typedef struct FlagDef { uint16_t offset; uint16_t flags; const char *name; } FlagDef; static int no_flag(const char **pp) { const char *p = *pp; if (*p != 'n' || *++p != 'o' || *++p != '-') return 0; *pp = p + 1; return 1; } ST_FUNC int set_flag(TCCState *s, const FlagDef *flags, const char *name) { int value, ret; const FlagDef *p; const char *r; value = 1; r = name; if (no_flag(&r)) value = 0; for (ret = -1, p = flags; p->name; ++p) { if (ret) { if (strcmp(r, p->name)) continue; } else { if (0 == (p->flags & WD_ALL)) continue; } if (p->offset) { *(int*)((char *)s + p->offset) = p->flags & FD_INVERT ? !value : value; if (ret) return 0; } else { ret = 0; } } return ret; } static int strstart(const char *val, const char **str) { const char *p, *q; p = *str; q = val; while (*q) { if (*p != *q) return 0; p++; q++; } *str = p; return 1; } /* Like strstart, but automatically takes into account that ld options can * * - start with double or single dash (e.g. '--soname' or '-soname') * - arguments can be given as separate or after '=' (e.g. '-Wl,-soname,x.so' * or '-Wl,-soname=x.so') * * you provide `val` always in 'option[=]' form (no leading -) */ static int link_option(const char *str, const char *val, const char **ptr) { const char *p, *q; int ret; /* there should be 1 or 2 dashes */ if (*str++ != '-') return 0; if (*str == '-') str++; /* then str & val should match (potentially up to '=') */ p = str; q = val; ret = 1; if (q[0] == '?') { ++q; if (no_flag(&p)) ret = -1; } while (*q != '\0' && *q != '=') { if (*p != *q) return 0; p++; q++; } /* '=' near eos means ',' or '=' is ok */ if (*q == '=') { if (*p == 0) *ptr = p; if (*p != ',' && *p != '=') return 0; p++; } else if (*p) { return 0; } *ptr = p; return ret; } static const char *skip_linker_arg(const char **str) { const char *s1 = *str; const char *s2 = strchr(s1, ','); *str = s2 ? s2++ : (s2 = s1 + strlen(s1)); return s2; } static void copy_linker_arg(char **pp, const char *s, int sep) { const char *q = s; char *p = *pp; int l = 0; if (p && sep) p[l = strlen(p)] = sep, ++l; skip_linker_arg(&q); pstrncpy(l + (*pp = tcc_realloc(p, q - s + l + 1)), s, q - s); } /* set linker options */ static int tcc_set_linker(TCCState *s, const char *option) { while (*option) { const char *p = NULL; char *end = NULL; int ignoring = 0; int ret; if (link_option(option, "Bsymbolic", &p)) { s->symbolic = 1; } else if (link_option(option, "nostdlib", &p)) { s->nostdlib = 1; } else if (link_option(option, "fini=", &p)) { copy_linker_arg(&s->fini_symbol, p, 0); ignoring = 1; } else if (link_option(option, "image-base=", &p) || link_option(option, "Ttext=", &p)) { s->text_addr = strtoull(p, &end, 16); s->has_text_addr = 1; } else if (link_option(option, "init=", &p)) { copy_linker_arg(&s->init_symbol, p, 0); ignoring = 1; } else if (link_option(option, "oformat=", &p)) { #if defined(TCC_TARGET_PE) if (strstart("pe-", &p)) { #elif PTR_SIZE == 8 if (strstart("elf64-", &p)) { #else if (strstart("elf32-", &p)) { #endif s->output_format = TCC_OUTPUT_FORMAT_ELF; } else if (!strcmp(p, "binary")) { s->output_format = TCC_OUTPUT_FORMAT_BINARY; #ifdef TCC_TARGET_COFF } else if (!strcmp(p, "coff")) { s->output_format = TCC_OUTPUT_FORMAT_COFF; #endif } else goto err; } else if (link_option(option, "as-needed", &p)) { ignoring = 1; } else if (link_option(option, "O", &p)) { ignoring = 1; } else if (link_option(option, "export-all-symbols", &p)) { s->rdynamic = 1; } else if (link_option(option, "rpath=", &p)) { copy_linker_arg(&s->rpath, p, ':'); } else if (link_option(option, "enable-new-dtags", &p)) { s->enable_new_dtags = 1; } else if (link_option(option, "section-alignment=", &p)) { s->section_align = strtoul(p, &end, 16); } else if (link_option(option, "soname=", &p)) { copy_linker_arg(&s->soname, p, 0); #ifdef TCC_TARGET_PE } else if (link_option(option, "large-address-aware", &p)) { s->pe_characteristics |= 0x20; } else if (link_option(option, "file-alignment=", &p)) { s->pe_file_align = strtoul(p, &end, 16); } else if (link_option(option, "stack=", &p)) { s->pe_stack_size = strtoul(p, &end, 10); } else if (link_option(option, "subsystem=", &p)) { #if defined(TCC_TARGET_I386) || defined(TCC_TARGET_X86_64) if (!strcmp(p, "native")) { s->pe_subsystem = 1; } else if (!strcmp(p, "console")) { s->pe_subsystem = 3; } else if (!strcmp(p, "gui") || !strcmp(p, "windows")) { s->pe_subsystem = 2; } else if (!strcmp(p, "posix")) { s->pe_subsystem = 7; } else if (!strcmp(p, "efiapp")) { s->pe_subsystem = 10; } else if (!strcmp(p, "efiboot")) { s->pe_subsystem = 11; } else if (!strcmp(p, "efiruntime")) { s->pe_subsystem = 12; } else if (!strcmp(p, "efirom")) { s->pe_subsystem = 13; #elif defined(TCC_TARGET_ARM) if (!strcmp(p, "wince")) { s->pe_subsystem = 9; #endif } else goto err; #endif } else if (ret = link_option(option, "?whole-archive", &p), ret) { s->alacarte_link = ret < 0; } else if (p) { return 0; } else { err: tcc_error("unsupported linker option '%s'", option); } if (ignoring && s->warn_unsupported) tcc_warning("unsupported linker option '%s'", option); option = skip_linker_arg(&p); } return 1; } typedef struct TCCOption { const char *name; uint16_t index; uint16_t flags; } TCCOption; enum { TCC_OPTION_HELP, TCC_OPTION_HELP2, TCC_OPTION_v, TCC_OPTION_I, TCC_OPTION_D, TCC_OPTION_U, TCC_OPTION_P, TCC_OPTION_L, TCC_OPTION_B, TCC_OPTION_l, TCC_OPTION_bench, TCC_OPTION_bt, TCC_OPTION_b, TCC_OPTION_g, TCC_OPTION_c, TCC_OPTION_dumpversion, TCC_OPTION_d, TCC_OPTION_static, TCC_OPTION_std, TCC_OPTION_shared, TCC_OPTION_soname, TCC_OPTION_o, TCC_OPTION_r, TCC_OPTION_s, TCC_OPTION_traditional, TCC_OPTION_Wl, TCC_OPTION_Wp, TCC_OPTION_W, TCC_OPTION_O, TCC_OPTION_mfloat_abi, TCC_OPTION_m, TCC_OPTION_f, TCC_OPTION_isystem, TCC_OPTION_iwithprefix, TCC_OPTION_include, TCC_OPTION_nostdinc, TCC_OPTION_nostdlib, TCC_OPTION_print_search_dirs, TCC_OPTION_rdynamic, TCC_OPTION_param, TCC_OPTION_pedantic, TCC_OPTION_pthread, TCC_OPTION_run, TCC_OPTION_w, TCC_OPTION_pipe, TCC_OPTION_E, TCC_OPTION_MD, TCC_OPTION_MF, TCC_OPTION_x, TCC_OPTION_ar, TCC_OPTION_impdef }; #define TCC_OPTION_HAS_ARG 0x0001 #define TCC_OPTION_NOSEP 0x0002 /* cannot have space before option and arg */ static const TCCOption tcc_options[] = { { "h", TCC_OPTION_HELP, 0 }, { "-help", TCC_OPTION_HELP, 0 }, { "?", TCC_OPTION_HELP, 0 }, { "hh", TCC_OPTION_HELP2, 0 }, { "v", TCC_OPTION_v, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "I", TCC_OPTION_I, TCC_OPTION_HAS_ARG }, { "D", TCC_OPTION_D, TCC_OPTION_HAS_ARG }, { "U", TCC_OPTION_U, TCC_OPTION_HAS_ARG }, { "P", TCC_OPTION_P, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "L", TCC_OPTION_L, TCC_OPTION_HAS_ARG }, { "B", TCC_OPTION_B, TCC_OPTION_HAS_ARG }, { "l", TCC_OPTION_l, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "bench", TCC_OPTION_bench, 0 }, #ifdef CONFIG_TCC_BACKTRACE { "bt", TCC_OPTION_bt, TCC_OPTION_HAS_ARG }, #endif #ifdef CONFIG_TCC_BCHECK { "b", TCC_OPTION_b, 0 }, #endif { "g", TCC_OPTION_g, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "c", TCC_OPTION_c, 0 }, { "dumpversion", TCC_OPTION_dumpversion, 0}, { "d", TCC_OPTION_d, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "static", TCC_OPTION_static, 0 }, { "std", TCC_OPTION_std, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "shared", TCC_OPTION_shared, 0 }, { "soname", TCC_OPTION_soname, TCC_OPTION_HAS_ARG }, { "o", TCC_OPTION_o, TCC_OPTION_HAS_ARG }, { "-param", TCC_OPTION_param, TCC_OPTION_HAS_ARG }, { "pedantic", TCC_OPTION_pedantic, 0}, { "pthread", TCC_OPTION_pthread, 0}, { "run", TCC_OPTION_run, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "rdynamic", TCC_OPTION_rdynamic, 0 }, { "r", TCC_OPTION_r, 0 }, { "s", TCC_OPTION_s, 0 }, { "traditional", TCC_OPTION_traditional, 0 }, { "Wl,", TCC_OPTION_Wl, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "Wp,", TCC_OPTION_Wp, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "W", TCC_OPTION_W, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "O", TCC_OPTION_O, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, #ifdef TCC_TARGET_ARM { "mfloat-abi", TCC_OPTION_mfloat_abi, TCC_OPTION_HAS_ARG }, #endif { "m", TCC_OPTION_m, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "f", TCC_OPTION_f, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "isystem", TCC_OPTION_isystem, TCC_OPTION_HAS_ARG }, { "iwithprefix", TCC_OPTION_iwithprefix, TCC_OPTION_HAS_ARG }, { "include", TCC_OPTION_include, TCC_OPTION_HAS_ARG }, { "nostdinc", TCC_OPTION_nostdinc, 0 }, { "nostdlib", TCC_OPTION_nostdlib, 0 }, { "print-search-dirs", TCC_OPTION_print_search_dirs, 0 }, { "w", TCC_OPTION_w, 0 }, { "pipe", TCC_OPTION_pipe, 0}, { "E", TCC_OPTION_E, 0}, { "MD", TCC_OPTION_MD, 0}, { "MF", TCC_OPTION_MF, TCC_OPTION_HAS_ARG }, { "x", TCC_OPTION_x, TCC_OPTION_HAS_ARG }, { "ar", TCC_OPTION_ar, 0}, #ifdef TCC_TARGET_PE { "impdef", TCC_OPTION_impdef, 0}, #endif { NULL, 0, 0 }, }; static const FlagDef options_W[] = { { 0, 0, "all" }, { offsetof(TCCState, warn_unsupported), 0, "unsupported" }, { offsetof(TCCState, warn_write_strings), 0, "write-strings" }, { offsetof(TCCState, warn_error), 0, "error" }, { offsetof(TCCState, warn_gcc_compat), 0, "gcc-compat" }, { offsetof(TCCState, warn_implicit_function_declaration), WD_ALL, "implicit-function-declaration" }, { 0, 0, NULL } }; static const FlagDef options_f[] = { { offsetof(TCCState, char_is_unsigned), 0, "unsigned-char" }, { offsetof(TCCState, char_is_unsigned), FD_INVERT, "signed-char" }, { offsetof(TCCState, nocommon), FD_INVERT, "common" }, { offsetof(TCCState, leading_underscore), 0, "leading-underscore" }, { offsetof(TCCState, ms_extensions), 0, "ms-extensions" }, { offsetof(TCCState, dollars_in_identifiers), 0, "dollars-in-identifiers" }, { 0, 0, NULL } }; static const FlagDef options_m[] = { { offsetof(TCCState, ms_bitfields), 0, "ms-bitfields" }, #ifdef TCC_TARGET_X86_64 { offsetof(TCCState, nosse), FD_INVERT, "sse" }, #endif { 0, 0, NULL } }; static void parse_option_D(TCCState *s1, const char *optarg) { char *sym = tcc_strdup(optarg); char *value = strchr(sym, '='); if (value) *value++ = '\0'; tcc_define_symbol(s1, sym, value); tcc_free(sym); } static void args_parser_add_file(TCCState *s, const char* filename, int filetype) { struct filespec *f = tcc_malloc(sizeof *f + strlen(filename)); f->type = filetype; f->alacarte = s->alacarte_link; strcpy(f->name, filename); dynarray_add(&s->files, &s->nb_files, f); } static int args_parser_make_argv(const char *r, int *argc, char ***argv) { int ret = 0, q, c; CString str; for(;;) { while (c = (unsigned char)*r, c && c <= ' ') ++r; if (c == 0) break; q = 0; cstr_new(&str); while (c = (unsigned char)*r, c) { ++r; if (c == '\\' && (*r == '"' || *r == '\\')) { c = *r++; } else if (c == '"') { q = !q; continue; } else if (q == 0 && c <= ' ') { break; } cstr_ccat(&str, c); } cstr_ccat(&str, 0); //printf("<%s>\n", str.data), fflush(stdout); dynarray_add(argv, argc, tcc_strdup(str.data)); cstr_free(&str); ++ret; } return ret; } /* read list file */ static void args_parser_listfile(TCCState *s, const char *filename, int optind, int *pargc, char ***pargv) { int fd, i; size_t len; char *p; int argc = 0; char **argv = NULL; fd = open(filename, O_RDONLY | O_BINARY); if (fd < 0) tcc_error("listfile '%s' not found", filename); len = lseek(fd, 0, SEEK_END); p = tcc_malloc(len + 1), p[len] = 0; lseek(fd, 0, SEEK_SET), read(fd, p, len), close(fd); for (i = 0; i < *pargc; ++i) if (i == optind) args_parser_make_argv(p, &argc, &argv); else dynarray_add(&argv, &argc, tcc_strdup((*pargv)[i])); tcc_free(p); dynarray_reset(&s->argv, &s->argc); *pargc = s->argc = argc, *pargv = s->argv = argv; } PUB_FUNC int tcc_parse_args(TCCState *s, int *pargc, char ***pargv, int optind) { const TCCOption *popt; const char *optarg, *r; const char *run = NULL; int last_o = -1; int x; CString linker_arg; /* collect -Wl options */ char buf[1024]; int tool = 0, arg_start = 0, noaction = optind; char **argv = *pargv; int argc = *pargc; cstr_new(&linker_arg); while (optind < argc) { r = argv[optind]; if (r[0] == '@' && r[1] != '\0') { args_parser_listfile(s, r + 1, optind, &argc, &argv); continue; } optind++; if (tool) { if (r[0] == '-' && r[1] == 'v' && r[2] == 0) ++s->verbose; continue; } reparse: if (r[0] != '-' || r[1] == '\0') { if (r[0] != '@') /* allow "tcc file(s) -run @ args ..." */ args_parser_add_file(s, r, s->filetype); if (run) { tcc_set_options(s, run); arg_start = optind - 1; break; } continue; } /* find option in table */ for(popt = tcc_options; ; ++popt) { const char *p1 = popt->name; const char *r1 = r + 1; if (p1 == NULL) tcc_error("invalid option -- '%s'", r); if (!strstart(p1, &r1)) continue; optarg = r1; if (popt->flags & TCC_OPTION_HAS_ARG) { if (*r1 == '\0' && !(popt->flags & TCC_OPTION_NOSEP)) { if (optind >= argc) arg_err: tcc_error("argument to '%s' is missing", r); optarg = argv[optind++]; } } else if (*r1 != '\0') continue; break; } switch(popt->index) { case TCC_OPTION_HELP: return OPT_HELP; case TCC_OPTION_HELP2: return OPT_HELP2; case TCC_OPTION_I: tcc_add_include_path(s, optarg); break; case TCC_OPTION_D: parse_option_D(s, optarg); break; case TCC_OPTION_U: tcc_undefine_symbol(s, optarg); break; case TCC_OPTION_L: tcc_add_library_path(s, optarg); break; case TCC_OPTION_B: /* set tcc utilities path (mainly for tcc development) */ tcc_set_lib_path(s, optarg); break; case TCC_OPTION_l: args_parser_add_file(s, optarg, AFF_TYPE_LIB); s->nb_libraries++; break; case TCC_OPTION_pthread: parse_option_D(s, "_REENTRANT"); s->option_pthread = 1; break; case TCC_OPTION_bench: s->do_bench = 1; break; #ifdef CONFIG_TCC_BACKTRACE case TCC_OPTION_bt: tcc_set_num_callers(atoi(optarg)); break; #endif #ifdef CONFIG_TCC_BCHECK case TCC_OPTION_b: s->do_bounds_check = 1; s->do_debug = 1; break; #endif case TCC_OPTION_g: s->do_debug = 1; break; case TCC_OPTION_c: x = TCC_OUTPUT_OBJ; set_output_type: if (s->output_type) tcc_warning("-%s: overriding compiler action already specified", popt->name); s->output_type = x; break; case TCC_OPTION_d: if (*optarg == 'D') s->dflag = 3; else if (*optarg == 'M') s->dflag = 7; else goto unsupported_option; break; case TCC_OPTION_static: s->static_link = 1; break; case TCC_OPTION_std: /* silently ignore, a current purpose: allow to use a tcc as a reference compiler for "make test" */ break; case TCC_OPTION_shared: x = TCC_OUTPUT_DLL; goto set_output_type; case TCC_OPTION_soname: s->soname = tcc_strdup(optarg); break; case TCC_OPTION_o: if (s->outfile) { tcc_warning("multiple -o option"); tcc_free(s->outfile); } s->outfile = tcc_strdup(optarg); break; case TCC_OPTION_r: /* generate a .o merging several output files */ s->option_r = 1; x = TCC_OUTPUT_OBJ; goto set_output_type; case TCC_OPTION_isystem: tcc_add_sysinclude_path(s, optarg); break; case TCC_OPTION_iwithprefix: snprintf(buf, sizeof buf, "{B}/%s", optarg); tcc_add_sysinclude_path(s, buf); break; case TCC_OPTION_include: dynarray_add(&s->cmd_include_files, &s->nb_cmd_include_files, tcc_strdup(optarg)); break; case TCC_OPTION_nostdinc: s->nostdinc = 1; break; case TCC_OPTION_nostdlib: s->nostdlib = 1; break; case TCC_OPTION_run: #ifndef TCC_IS_NATIVE tcc_error("-run is not available in a cross compiler"); #endif run = optarg; x = TCC_OUTPUT_MEMORY; goto set_output_type; case TCC_OPTION_v: do ++s->verbose; while (*optarg++ == 'v'); ++noaction; break; case TCC_OPTION_f: if (set_flag(s, options_f, optarg) < 0) goto unsupported_option; break; #ifdef TCC_TARGET_ARM case TCC_OPTION_mfloat_abi: /* tcc doesn't support soft float yet */ if (!strcmp(optarg, "softfp")) { s->float_abi = ARM_SOFTFP_FLOAT; tcc_undefine_symbol(s, "__ARM_PCS_VFP"); } else if (!strcmp(optarg, "hard")) s->float_abi = ARM_HARD_FLOAT; else tcc_error("unsupported float abi '%s'", optarg); break; #endif case TCC_OPTION_m: if (set_flag(s, options_m, optarg) < 0) { if (x = atoi(optarg), x != 32 && x != 64) goto unsupported_option; if (PTR_SIZE != x/8) return x; ++noaction; } break; case TCC_OPTION_W: if (set_flag(s, options_W, optarg) < 0) goto unsupported_option; break; case TCC_OPTION_w: s->warn_none = 1; break; case TCC_OPTION_rdynamic: s->rdynamic = 1; break; case TCC_OPTION_Wl: if (linker_arg.size) --linker_arg.size, cstr_ccat(&linker_arg, ','); cstr_cat(&linker_arg, optarg, 0); if (tcc_set_linker(s, linker_arg.data)) cstr_free(&linker_arg); break; case TCC_OPTION_Wp: r = optarg; goto reparse; case TCC_OPTION_E: x = TCC_OUTPUT_PREPROCESS; goto set_output_type; case TCC_OPTION_P: s->Pflag = atoi(optarg) + 1; break; case TCC_OPTION_MD: s->gen_deps = 1; break; case TCC_OPTION_MF: s->deps_outfile = tcc_strdup(optarg); break; case TCC_OPTION_dumpversion: printf ("%s\n", TCC_VERSION); exit(0); break; case TCC_OPTION_x: if (*optarg == 'c') s->filetype = AFF_TYPE_C; else if (*optarg == 'a') s->filetype = AFF_TYPE_ASMPP; else if (*optarg == 'n') s->filetype = AFF_TYPE_NONE; else tcc_warning("unsupported language '%s'", optarg); break; case TCC_OPTION_O: last_o = atoi(optarg); break; case TCC_OPTION_print_search_dirs: x = OPT_PRINT_DIRS; goto extra_action; case TCC_OPTION_impdef: x = OPT_IMPDEF; goto extra_action; case TCC_OPTION_ar: x = OPT_AR; extra_action: arg_start = optind - 1; if (arg_start != noaction) tcc_error("cannot parse %s here", r); tool = x; break; case TCC_OPTION_traditional: case TCC_OPTION_pedantic: case TCC_OPTION_pipe: case TCC_OPTION_s: /* ignored */ break; default: unsupported_option: if (s->warn_unsupported) tcc_warning("unsupported option '%s'", r); break; } } if (last_o > 0) tcc_define_symbol(s, "__OPTIMIZE__", NULL); if (linker_arg.size) { r = linker_arg.data; goto arg_err; } *pargc = argc - arg_start; *pargv = argv + arg_start; if (tool) return tool; if (optind != noaction) return 0; if (s->verbose == 2) return OPT_PRINT_DIRS; if (s->verbose) return OPT_V; return OPT_HELP; } LIBTCCAPI void tcc_set_options(TCCState *s, const char *r) { char **argv = NULL; int argc = 0; args_parser_make_argv(r, &argc, &argv); tcc_parse_args(s, &argc, &argv, 0); dynarray_reset(&argv, &argc); } PUB_FUNC void tcc_print_stats(TCCState *s, unsigned total_time) { if (total_time < 1) total_time = 1; if (total_bytes < 1) total_bytes = 1; fprintf(stderr, "* %d idents, %d lines, %d bytes\n" "* %0.3f s, %u lines/s, %0.1f MB/s\n", tok_ident - TOK_IDENT, total_lines, total_bytes, (double)total_time/1000, (unsigned)total_lines*1000/total_time, (double)total_bytes/1000/total_time); #ifdef MEM_DEBUG fprintf(stderr, "* %d bytes memory used\n", mem_max_size); #endif }
/* * TCC - Tiny C Compiler * * Copyright (c) 2001-2004 Fabrice Bellard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _TCC_H #define _TCC_H #define _GNU_SOURCE #include "config.h" #include <stdlib.h> #include <stdio.h> #include <stdarg.h> #include <string.h> #include <errno.h> #include <math.h> #include <fcntl.h> #include <setjmp.h> #include <time.h> #ifndef _WIN32 # include <unistd.h> # include <sys/time.h> # ifndef CONFIG_TCC_STATIC # include <dlfcn.h> # endif /* XXX: need to define this to use them in non ISOC99 context */ extern float strtof (const char *__nptr, char **__endptr); extern long double strtold (const char *__nptr, char **__endptr); #else /* on _WIN32: */ # include <windows.h> # include <io.h> /* open, close etc. */ # include <direct.h> /* getcwd */ # ifdef __GNUC__ # include <stdint.h> # endif # define inline __inline # define inp next_inp /* inp is an intrinsic on msvc */ # define snprintf _snprintf # define vsnprintf _vsnprintf # ifndef __GNUC__ # define strtold (long double)strtod # define strtof (float)strtod # define strtoll _strtoi64 # define strtoull _strtoui64 # endif # ifdef LIBTCC_AS_DLL # define LIBTCCAPI __declspec(dllexport) # define PUB_FUNC LIBTCCAPI # endif # ifdef _MSC_VER # pragma warning (disable : 4244) // conversion from 'uint64_t' to 'int', possible loss of data # pragma warning (disable : 4267) // conversion from 'size_t' to 'int', possible loss of data # pragma warning (disable : 4996) // The POSIX name for this item is deprecated. Instead, use the ISO C and C++ conformant name # pragma warning (disable : 4018) // signed/unsigned mismatch # pragma warning (disable : 4146) // unary minus operator applied to unsigned type, result still unsigned # define ssize_t intptr_t # define __attribute__(x) __declspec x # define aligned align # else # endif # undef CONFIG_TCC_STATIC #endif #ifndef O_BINARY # define O_BINARY 0 #endif #ifdef __GNUC__ # define NORETURN __attribute__ ((noreturn)) #elif defined _MSC_VER # define NORETURN __declspec(noreturn) #else # define NORETURN #endif #ifdef _WIN32 # define IS_DIRSEP(c) (c == '/' || c == '\\') # define IS_ABSPATH(p) (IS_DIRSEP(p[0]) || (p[0] && p[1] == ':' && IS_DIRSEP(p[2]))) # define PATHCMP stricmp #else # define IS_DIRSEP(c) (c == '/') # define IS_ABSPATH(p) IS_DIRSEP(p[0]) # define PATHCMP strcmp #endif #ifdef TCC_TARGET_PE #define PATHSEP ';' #else #define PATHSEP ':' #endif /* -------------------------------------------- */ /* parser debug */ /* #define PARSE_DEBUG */ /* preprocessor debug */ /* #define PP_DEBUG */ /* include file debug */ /* #define INC_DEBUG */ /* memory leak debug */ /* #define MEM_DEBUG */ /* assembler debug */ /* #define ASM_DEBUG */ /* target selection */ /* #define TCC_TARGET_I386 *//* i386 code generator */ /* #define TCC_TARGET_X86_64 *//* x86-64 code generator */ /* #define TCC_TARGET_ARM *//* ARMv4 code generator */ /* #define TCC_TARGET_ARM64 *//* ARMv8 code generator */ /* #define TCC_TARGET_C67 *//* TMS320C67xx code generator */ /* default target is I386 */ #if !defined(TCC_TARGET_I386) && !defined(TCC_TARGET_ARM) && \ !defined(TCC_TARGET_ARM64) && !defined(TCC_TARGET_C67) && \ !defined(TCC_TARGET_X86_64) #define TCC_TARGET_I386 #endif /* object format selection */ #if defined(TCC_TARGET_C67) #define TCC_TARGET_COFF #endif /* only native compiler supports -run */ #if defined _WIN32 == defined TCC_TARGET_PE # if (defined __i386__ || defined _X86_) && defined TCC_TARGET_I386 # define TCC_IS_NATIVE # elif (defined __x86_64__ || defined _AMD64_) && defined TCC_TARGET_X86_64 # define TCC_IS_NATIVE # elif defined __arm__ && defined TCC_TARGET_ARM # define TCC_IS_NATIVE # elif defined __aarch64__ && defined TCC_TARGET_ARM64 # define TCC_IS_NATIVE # endif #endif #if defined TCC_IS_NATIVE && !defined CONFIG_TCCBOOT # define CONFIG_TCC_BACKTRACE # if (defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64) \ && !defined TCC_UCLIBC && !defined TCC_MUSL # define CONFIG_TCC_BCHECK /* enable bound checking code */ # endif #endif /* ------------ path configuration ------------ */ #ifndef CONFIG_SYSROOT # define CONFIG_SYSROOT "" #endif #ifndef CONFIG_TCCDIR # define CONFIG_TCCDIR "." #endif #ifndef CONFIG_LDDIR # define CONFIG_LDDIR "lib" #endif #ifdef CONFIG_TRIPLET # define USE_TRIPLET(s) s "/" CONFIG_TRIPLET # define ALSO_TRIPLET(s) USE_TRIPLET(s) ":" s #else # define USE_TRIPLET(s) s # define ALSO_TRIPLET(s) s #endif /* path to find crt1.o, crti.o and crtn.o */ #ifndef CONFIG_TCC_CRTPREFIX # define CONFIG_TCC_CRTPREFIX USE_TRIPLET(CONFIG_SYSROOT "/usr/" CONFIG_LDDIR) #endif /* Below: {B} is substituted by CONFIG_TCCDIR (rsp. -B option) */ /* system include paths */ #ifndef CONFIG_TCC_SYSINCLUDEPATHS # ifdef TCC_TARGET_PE # define CONFIG_TCC_SYSINCLUDEPATHS "{B}/include;{B}/include/winapi" # else # define CONFIG_TCC_SYSINCLUDEPATHS \ "{B}/include" \ ":" ALSO_TRIPLET(CONFIG_SYSROOT "/usr/local/include") \ ":" ALSO_TRIPLET(CONFIG_SYSROOT "/usr/include") # endif #endif /* library search paths */ #ifndef CONFIG_TCC_LIBPATHS # ifdef TCC_TARGET_PE # define CONFIG_TCC_LIBPATHS "{B}/lib" # else # define CONFIG_TCC_LIBPATHS \ ALSO_TRIPLET(CONFIG_SYSROOT "/usr/" CONFIG_LDDIR) \ ":" ALSO_TRIPLET(CONFIG_SYSROOT "/" CONFIG_LDDIR) \ ":" ALSO_TRIPLET(CONFIG_SYSROOT "/usr/local/" CONFIG_LDDIR) # endif #endif /* name of ELF interpreter */ #ifndef CONFIG_TCC_ELFINTERP # if defined __FreeBSD__ # define CONFIG_TCC_ELFINTERP "/libexec/ld-elf.so.1" # elif defined __FreeBSD_kernel__ # if defined(TCC_TARGET_X86_64) # define CONFIG_TCC_ELFINTERP "/lib/ld-kfreebsd-x86-64.so.1" # else # define CONFIG_TCC_ELFINTERP "/lib/ld.so.1" # endif # elif defined __DragonFly__ # define CONFIG_TCC_ELFINTERP "/usr/libexec/ld-elf.so.2" # elif defined __NetBSD__ # define CONFIG_TCC_ELFINTERP "/usr/libexec/ld.elf_so" # elif defined __GNU__ # define CONFIG_TCC_ELFINTERP "/lib/ld.so" # elif defined(TCC_TARGET_PE) # define CONFIG_TCC_ELFINTERP "-" # elif defined(TCC_UCLIBC) # define CONFIG_TCC_ELFINTERP "/lib/ld-uClibc.so.0" /* is there a uClibc for x86_64 ? */ # elif defined TCC_TARGET_ARM64 # if defined(TCC_MUSL) # define CONFIG_TCC_ELFINTERP "/lib/ld-musl-aarch64.so.1" # else # define CONFIG_TCC_ELFINTERP "/lib/ld-linux-aarch64.so.1" # endif # elif defined(TCC_TARGET_X86_64) # if defined(TCC_MUSL) # define CONFIG_TCC_ELFINTERP "/lib/ld-musl-x86_64.so.1" # else # define CONFIG_TCC_ELFINTERP "/lib64/ld-linux-x86-64.so.2" # endif # elif !defined(TCC_ARM_EABI) # if defined(TCC_MUSL) # define CONFIG_TCC_ELFINTERP "/lib/ld-musl-arm.so.1" # else # define CONFIG_TCC_ELFINTERP "/lib/ld-linux.so.2" # endif # endif #endif /* var elf_interp dans *-gen.c */ #ifdef CONFIG_TCC_ELFINTERP # define DEFAULT_ELFINTERP(s) CONFIG_TCC_ELFINTERP #else # define DEFAULT_ELFINTERP(s) default_elfinterp(s) #endif /* (target specific) libtcc1.a */ #ifndef TCC_LIBTCC1 # define TCC_LIBTCC1 "libtcc1.a" #endif /* library to use with CONFIG_USE_LIBGCC instead of libtcc1.a */ #if defined CONFIG_USE_LIBGCC && !defined TCC_LIBGCC #define TCC_LIBGCC USE_TRIPLET(CONFIG_SYSROOT "/" CONFIG_LDDIR) "/libgcc_s.so.1" #endif /* -------------------------------------------- */ #include "libtcc.h" #include "elf.h" #include "stab.h" /* -------------------------------------------- */ #ifndef PUB_FUNC /* functions used by tcc.c but not in libtcc.h */ # define PUB_FUNC #endif #ifdef ONE_SOURCE #define ST_INLN static inline #define ST_FUNC static #define ST_DATA static #else #define ST_INLN #define ST_FUNC #define ST_DATA extern #endif #ifdef TCC_PROFILE /* profile all functions */ # define static #endif /* -------------------------------------------- */ /* include the target specific definitions */ #define TARGET_DEFS_ONLY #ifdef TCC_TARGET_I386 # include "i386-gen.c" # include "i386-link.c" #endif #ifdef TCC_TARGET_X86_64 # include "x86_64-gen.c" # include "x86_64-link.c" #endif #ifdef TCC_TARGET_ARM # include "arm-gen.c" # include "arm-link.c" # include "arm-asm.c" #endif #ifdef TCC_TARGET_ARM64 # include "arm64-gen.c" # include "arm64-link.c" #endif #ifdef TCC_TARGET_C67 # include "coff.h" # include "c67-gen.c" # include "c67-link.c" #endif #undef TARGET_DEFS_ONLY /* -------------------------------------------- */ #if PTR_SIZE == 8 # define ELFCLASSW ELFCLASS64 # define ElfW(type) Elf##64##_##type # define ELFW(type) ELF##64##_##type # define ElfW_Rel ElfW(Rela) # define SHT_RELX SHT_RELA # define REL_SECTION_FMT ".rela%s" #else # define ELFCLASSW ELFCLASS32 # define ElfW(type) Elf##32##_##type # define ELFW(type) ELF##32##_##type # define ElfW_Rel ElfW(Rel) # define SHT_RELX SHT_REL # define REL_SECTION_FMT ".rel%s" #endif /* target address type */ #define addr_t ElfW(Addr) /* -------------------------------------------- */ #define INCLUDE_STACK_SIZE 32 #define IFDEF_STACK_SIZE 64 #define VSTACK_SIZE 256 #define STRING_MAX_SIZE 1024 #define TOKSTR_MAX_SIZE 256 #define PACK_STACK_SIZE 8 #define TOK_HASH_SIZE 16384 /* must be a power of two */ #define TOK_ALLOC_INCR 512 /* must be a power of two */ #define TOK_MAX_SIZE 4 /* token max size in int unit when stored in string */ /* token symbol management */ typedef struct TokenSym { struct TokenSym *hash_next; struct Sym *sym_define; /* direct pointer to define */ struct Sym *sym_label; /* direct pointer to label */ struct Sym *sym_struct; /* direct pointer to structure */ struct Sym *sym_identifier; /* direct pointer to identifier */ int tok; /* token number */ int len; char str[1]; } TokenSym; #ifdef TCC_TARGET_PE typedef unsigned short nwchar_t; #else typedef int nwchar_t; #endif typedef struct CString { int size; /* size in bytes */ void *data; /* either 'char *' or 'nwchar_t *' */ int size_allocated; } CString; /* type definition */ typedef struct CType { int t; struct Sym *ref; } CType; /* constant value */ typedef union CValue { long double ld; double d; float f; #if HAVE_LONG_LONG_STUB || HAVE_LONG_LONG uint64_t i; #else uint32_t i; uint32_t i_padding; #endif struct { int size; const void *data; } str; #if BOOTSTRAP && __arm__ int tab[4]; #else int tab[LDOUBLE_SIZE/4]; #endif } CValue; /* value on stack */ typedef struct SValue { CType type; /* type */ unsigned short r; /* register + flags */ unsigned short r2; /* second register, used for 'long long' type. If not used, set to VT_CONST */ CValue c; /* constant, if VT_CONST */ struct Sym *sym; /* symbol, if (VT_SYM | VT_CONST), or if result of unary() for an identifier. */ } SValue; struct Attribute { #if HAVE_BITFIELD unsigned func_call : 3, /* calling convention (0..5), see below */ aligned : 5, /* alignment as log2+1 (0 == unspecified) */ packed : 1, func_export : 1, func_import : 1, func_args : 5, func_body : 1, mode : 4, weak : 1, visibility : 2, unsigned_enum : 1, fill : 7; // 7 bits left to fit well in union below #else // !HAVE_BITFIELD struct { unsigned func_call; unsigned aligned; unsigned packed; unsigned func_export; unsigned func_import; unsigned func_args; unsigned func_body; unsigned mode; unsigned weak; unsigned visibility; unsigned unsigned_enum; unsigned fill; }; #endif // !HAVE_BITFIELD }; /* GNUC attribute definition */ typedef struct AttributeDef { struct Attribute a; struct Section *section; int alias_target; /* token */ int asm_label; /* associated asm label */ } AttributeDef; /* symbol management */ typedef struct Sym { int v; /* symbol token */ union { int asm_label; /* associated asm label */ int scope; /* scope level for locals */ }; union { long r; /* associated register or VT_CONST/VT_LOCAL and LVAL type */ struct Attribute a; }; union { long c; /* associated number */ int *d; /* define token stream */ }; CType type; /* associated type */ union { struct Sym *next; /* next related symbol (for fields and anoms) */ long jnext; /* next jump label */ }; struct Sym *prev; /* prev symbol in stack */ struct Sym *prev_tok; /* previous symbol for this token */ } Sym; /* section definition */ /* XXX: use directly ELF structure for parameters ? */ /* special flag to indicate that the section should not be linked to the other ones */ #define SHF_PRIVATE 0x80000000 /* special flag, too */ #define SECTION_ABS ((void *)1) typedef struct Section { unsigned long data_offset; /* current data offset */ unsigned char *data; /* section data */ unsigned long data_allocated; /* used for realloc() handling */ int sh_name; /* elf section name (only used during output) */ int sh_num; /* elf section number */ int sh_type; /* elf section type */ int sh_flags; /* elf section flags */ int sh_info; /* elf section info */ int sh_addralign; /* elf section alignment */ int sh_entsize; /* elf entry size */ unsigned long sh_size; /* section size (only used during output) */ addr_t sh_addr; /* address at which the section is relocated */ unsigned long sh_offset; /* file offset */ int nb_hashed_syms; /* used to resize the hash table */ struct Section *link; /* link to another section */ struct Section *reloc; /* corresponding section for relocation, if any */ struct Section *hash; /* hash table for symbols */ struct Section *prev; /* previous section on section stack */ char name[1]; /* section name */ } Section; typedef struct DLLReference { int level; void *handle; char name[1]; } DLLReference; /* -------------------------------------------------- */ #define SYM_STRUCT 0x40000000 /* struct/union/enum symbol space */ #define SYM_FIELD 0x20000000 /* struct/union field symbol space */ #define SYM_FIRST_ANOM 0x10000000 /* first anonymous sym */ /* stored in 'Sym.c' field */ #define FUNC_NEW 1 /* ansi function prototype */ #define FUNC_OLD 2 /* old function prototype */ #define FUNC_ELLIPSIS 3 /* ansi function prototype with ... */ /* stored in 'Sym.r' field */ #define FUNC_CDECL 0 /* standard c call */ #define FUNC_STDCALL 1 /* pascal c call */ #define FUNC_FASTCALL1 2 /* first param in %eax */ #define FUNC_FASTCALL2 3 /* first parameters in %eax, %edx */ #define FUNC_FASTCALL3 4 /* first parameter in %eax, %edx, %ecx */ #define FUNC_FASTCALLW 5 /* first parameter in %ecx, %edx */ /* field 'Sym.t' for macros */ #define MACRO_OBJ 0 /* object like macro */ #define MACRO_FUNC 1 /* function like macro */ /* field 'Sym.r' for C labels */ #define LABEL_DEFINED 0 /* label is defined */ #define LABEL_FORWARD 1 /* label is forward defined */ #define LABEL_DECLARED 2 /* label is declared but never used */ /* type_decl() types */ #define TYPE_ABSTRACT 1 /* type without variable */ #define TYPE_DIRECT 2 /* type with variable */ #define IO_BUF_SIZE 8192 typedef struct BufferedFile { uint8_t *buf_ptr; uint8_t *buf_end; int fd; struct BufferedFile *prev; int line_num; /* current line number - here to simplify code */ int line_ref; /* tcc -E: last printed line */ int ifndef_macro; /* #ifndef macro / #endif search */ int ifndef_macro_saved; /* saved ifndef_macro */ int *ifdef_stack_ptr; /* ifdef_stack value at the start of the file */ int include_next_index; /* next search path */ char filename[1024]; /* filename */ char filename2[1024]; /* filename not modified by # line directive */ unsigned char unget[4]; unsigned char buffer[1]; /* extra size for CH_EOB char */ } BufferedFile; #define CH_EOB '\\' /* end of buffer or '\0' char in file */ #define CH_EOF (-1) /* end of file */ /* parsing state (used to save parser state to reparse part of the source several times) */ typedef struct ParseState { const int *macro_ptr; int line_num; int tok; CValue tokc; } ParseState; /* used to record tokens */ typedef struct TokenString { int *str; int len; int allocated_len; int last_line_num; /* used to chain token-strings with begin/end_macro() */ struct TokenString *prev; const int *prev_ptr; char alloc; } TokenString; /* inline functions */ typedef struct InlineFunc { TokenString *func_str; Sym *sym; char filename[1]; } InlineFunc; /* include file cache, used to find files faster and also to eliminate inclusion if the include file is protected by #ifndef ... #endif */ typedef struct CachedInclude { int ifndef_macro; int once; int hash_next; /* -1 if none */ char filename[1]; /* path specified in #include */ } CachedInclude; #define CACHED_INCLUDES_HASH_SIZE 32 #ifdef CONFIG_TCC_ASM typedef struct ExprValue { #if HAVE_LONG_LONG_STUB || HAVE_LONG_LONG uint64_t v; #else uint32_t v; uint32_t v_padding; #endif Sym *sym; int pcrel; } ExprValue; #define MAX_ASM_OPERANDS 30 typedef struct ASMOperand { int id; /* GCC 3 optional identifier (0 if number only supported */ char *constraint; char asm_str[16]; /* computed asm string for operand */ SValue *vt; /* C value of the expression */ int ref_index; /* if >= 0, gives reference to a output constraint */ int input_index; /* if >= 0, gives reference to an input constraint */ int priority; /* priority, used to assign registers */ int reg; /* if >= 0, register number used for this operand */ int is_llong; /* true if double register value */ int is_memory; /* true if memory operand */ int is_rw; /* for '+' modifier */ } ASMOperand; #endif /* extra symbol attributes (not in symbol table) */ struct sym_attr { unsigned got_offset; unsigned plt_offset; int plt_sym; int dyn_index; #ifdef TCC_TARGET_ARM unsigned char plt_thumb_stub:1; #endif }; struct TCCState { int verbose; /* if true, display some information during compilation */ int nostdinc; /* if true, no standard headers are added */ int nostdlib; /* if true, no standard libraries are added */ int nocommon; /* if true, do not use common symbols for .bss data */ int static_link; /* if true, static linking is performed */ int rdynamic; /* if true, all symbols are exported */ int symbolic; /* if true, resolve symbols in the current module first */ int alacarte_link; /* if true, only link in referenced objects from archive */ char *tcc_lib_path; /* CONFIG_TCCDIR or -B option */ char *soname; /* as specified on the command line (-soname) */ char *rpath; /* as specified on the command line (-Wl,-rpath=) */ int enable_new_dtags; /* ditto, (-Wl,--enable-new-dtags) */ /* output type, see TCC_OUTPUT_XXX */ int output_type; /* output format, see TCC_OUTPUT_FORMAT_xxx */ int output_format; /* C language options */ int char_is_unsigned; int leading_underscore; int ms_extensions; /* allow nested named struct w/o identifier behave like unnamed */ int dollars_in_identifiers; /* allows '$' char in identifiers */ int ms_bitfields; /* if true, emulate MS algorithm for aligning bitfields */ /* warning switches */ int warn_write_strings; int warn_unsupported; int warn_error; int warn_none; int warn_implicit_function_declaration; int warn_gcc_compat; /* compile with debug symbol (and use them if error during execution) */ int do_debug; #ifdef CONFIG_TCC_BCHECK /* compile with built-in memory and bounds checker */ int do_bounds_check; #endif #ifdef TCC_TARGET_ARM enum float_abi float_abi; /* float ABI of the generated code*/ #endif addr_t text_addr; /* address of text section */ int has_text_addr; unsigned section_align; /* section alignment */ char *init_symbol; /* symbols to call at load-time (not used currently) */ char *fini_symbol; /* symbols to call at unload-time (not used currently) */ #ifdef TCC_TARGET_I386 int seg_size; /* 32. Can be 16 with i386 assembler (.code16) */ #endif #ifdef TCC_TARGET_X86_64 int nosse; /* For -mno-sse support. */ #endif /* array of all loaded dlls (including those referenced by loaded dlls) */ DLLReference **loaded_dlls; int nb_loaded_dlls; /* include paths */ char **include_paths; int nb_include_paths; char **sysinclude_paths; int nb_sysinclude_paths; /* library paths */ char **library_paths; int nb_library_paths; /* crt?.o object path */ char **crt_paths; int nb_crt_paths; /* -include files */ char **cmd_include_files; int nb_cmd_include_files; /* error handling */ void *error_opaque; void (*error_func)(void *opaque, const char *msg); int error_set_jmp_enabled; jmp_buf error_jmp_buf; int nb_errors; /* output file for preprocessing (-E) */ FILE *ppfp; enum { LINE_MACRO_OUTPUT_FORMAT_GCC, LINE_MACRO_OUTPUT_FORMAT_NONE, LINE_MACRO_OUTPUT_FORMAT_STD, LINE_MACRO_OUTPUT_FORMAT_P10 = 11 } Pflag; /* -P switch */ char dflag; /* -dX value */ /* for -MD/-MF: collected dependencies for this compilation */ char **target_deps; int nb_target_deps; /* compilation */ BufferedFile *include_stack[INCLUDE_STACK_SIZE]; BufferedFile **include_stack_ptr; int ifdef_stack[IFDEF_STACK_SIZE]; int *ifdef_stack_ptr; /* included files enclosed with #ifndef MACRO */ int cached_includes_hash[CACHED_INCLUDES_HASH_SIZE]; CachedInclude **cached_includes; int nb_cached_includes; /* #pragma pack stack */ int pack_stack[PACK_STACK_SIZE]; int *pack_stack_ptr; char **pragma_libs; int nb_pragma_libs; /* inline functions are stored as token lists and compiled last only if referenced */ struct InlineFunc **inline_fns; int nb_inline_fns; /* sections */ Section **sections; int nb_sections; /* number of sections, including first dummy section */ Section **priv_sections; int nb_priv_sections; /* number of private sections */ /* got & plt handling */ Section *got; Section *plt; /* temporary dynamic symbol sections (for dll loading) */ Section *dynsymtab_section; /* exported dynamic symbol section */ Section *dynsym; /* copy of the global symtab_section variable */ Section *symtab; /* extra attributes (eg. GOT/PLT value) for symtab symbols */ struct sym_attr *sym_attrs; int nb_sym_attrs; /* tiny assembler state */ Sym *asm_labels; #ifdef TCC_TARGET_PE /* PE info */ int pe_subsystem; unsigned pe_characteristics; unsigned pe_file_align; unsigned pe_stack_size; # ifdef TCC_TARGET_X86_64 Section *uw_pdata; int uw_sym; unsigned uw_offs; # endif #endif #ifdef TCC_IS_NATIVE const char *runtime_main; void **runtime_mem; int nb_runtime_mem; #endif /* used by main and tcc_parse_args only */ struct filespec **files; /* files seen on command line */ int nb_files; /* number thereof */ int nb_libraries; /* number of libs thereof */ int filetype; char *outfile; /* output filename */ int option_r; /* option -r */ int do_bench; /* option -bench */ int gen_deps; /* option -MD */ char *deps_outfile; /* option -MF */ int option_pthread; /* -pthread option */ int argc; char **argv; }; struct filespec { char type; char alacarte; char name[1]; }; /* The current value can be: */ #define VT_VALMASK 0x003f /* mask for value location, register or: */ #define VT_CONST 0x0030 /* constant in vc (must be first non register value) */ #define VT_LLOCAL 0x0031 /* lvalue, offset on stack */ #define VT_LOCAL 0x0032 /* offset on stack */ #define VT_CMP 0x0033 /* the value is stored in processor flags (in vc) */ #define VT_JMP 0x0034 /* value is the consequence of jmp true (even) */ #define VT_JMPI 0x0035 /* value is the consequence of jmp false (odd) */ #define VT_LVAL 0x0100 /* var is an lvalue */ #define VT_SYM 0x0200 /* a symbol value is added */ #define VT_MUSTCAST 0x0400 /* value must be casted to be correct (used for char/short stored in integer registers) */ #define VT_MUSTBOUND 0x0800 /* bound checking must be done before dereferencing value */ #define VT_BOUNDED 0x8000 /* value is bounded. The address of the bounding function call point is in vc */ #define VT_LVAL_BYTE 0x1000 /* lvalue is a byte */ #define VT_LVAL_SHORT 0x2000 /* lvalue is a short */ #define VT_LVAL_UNSIGNED 0x4000 /* lvalue is unsigned */ #define VT_LVAL_TYPE (VT_LVAL_BYTE | VT_LVAL_SHORT | VT_LVAL_UNSIGNED) /* types */ #define VT_BTYPE 0x000f /* mask for basic type */ #define VT_INT 0 /* integer type */ #define VT_BYTE 1 /* signed byte type */ #define VT_SHORT 2 /* short type */ #define VT_VOID 3 /* void type */ #define VT_PTR 4 /* pointer */ #define VT_ENUM 5 /* enum definition */ #define VT_FUNC 6 /* function type */ #define VT_STRUCT 7 /* struct/union definition */ #define VT_FLOAT 8 /* IEEE float */ #define VT_DOUBLE 9 /* IEEE double */ #define VT_LDOUBLE 10 /* IEEE long double */ #define VT_BOOL 11 /* ISOC99 boolean type */ #define VT_LLONG 12 /* 64 bit integer */ #define VT_LONG 13 /* long integer (NEVER USED as type, only during parsing) */ #define VT_QLONG 14 /* 128-bit integer. Only used for x86-64 ABI */ #define VT_QFLOAT 15 /* 128-bit float. Only used for x86-64 ABI */ #define VT_UNSIGNED 0x0010 /* unsigned type */ #define VT_ARRAY 0x0020 /* array type (also has VT_PTR) */ #define VT_BITFIELD 0x0040 /* bitfield modifier */ #define VT_CONSTANT 0x0800 /* const modifier */ #define VT_VOLATILE 0x1000 /* volatile modifier */ #define VT_DEFSIGN 0x2000 /* signed type */ #define VT_VLA 0x00020000 /* VLA type (also has VT_PTR and VT_ARRAY) */ /* storage */ #define VT_EXTERN 0x00000080 /* extern definition */ #define VT_STATIC 0x00000100 /* static variable */ #define VT_TYPEDEF 0x00000200 /* typedef definition */ #define VT_INLINE 0x00000400 /* inline definition */ #define VT_IMPORT 0x00004000 /* win32: extern data imported from dll */ #define VT_EXPORT 0x00008000 /* win32: data exported from dll */ #define VT_WEAK 0x00010000 /* weak symbol */ #define VT_TLS 0x00040000 /* thread-local storage */ #define VT_VIS_SHIFT 19 /* shift for symbol visibility, overlapping bitfield values, because bitfields never have linkage and hence never have visibility. */ #define VT_VIS_SIZE 2 /* We have four visibilities. */ #define VT_VIS_MASK (((1 << VT_VIS_SIZE)-1) << VT_VIS_SHIFT) #define VT_STRUCT_SHIFT 19 /* shift for bitfield shift values (max: 32 - 2*6) */ /* type mask (except storage) */ #define VT_STORAGE (VT_EXTERN | VT_STATIC | VT_TYPEDEF | VT_INLINE | VT_IMPORT | VT_EXPORT | VT_WEAK | VT_VIS_MASK) #define VT_TYPE (~(VT_STORAGE)) /* token values */ /* warning: the following compare tokens depend on i386 asm code */ #define TOK_ULT 0x92 #define TOK_UGE 0x93 #define TOK_EQ 0x94 #define TOK_NE 0x95 #define TOK_ULE 0x96 #define TOK_UGT 0x97 #define TOK_Nset 0x98 #define TOK_Nclear 0x99 #define TOK_LT 0x9c #define TOK_GE 0x9d #define TOK_LE 0x9e #define TOK_GT 0x9f #define TOK_LAND 0xa0 #define TOK_LOR 0xa1 #define TOK_DEC 0xa2 #define TOK_MID 0xa3 /* inc/dec, to void constant */ #define TOK_INC 0xa4 #define TOK_UDIV 0xb0 /* unsigned division */ #define TOK_UMOD 0xb1 /* unsigned modulo */ #define TOK_PDIV 0xb2 /* fast division with undefined rounding for pointers */ /* tokens that carry values (in additional token string space / tokc) --> */ #define TOK_CCHAR 0xb3 /* char constant in tokc */ #define TOK_LCHAR 0xb4 #define TOK_CINT 0xb5 /* number in tokc */ #define TOK_CUINT 0xb6 /* unsigned int constant */ #define TOK_CLLONG 0xb7 /* long long constant */ #define TOK_CULLONG 0xb8 /* unsigned long long constant */ #define TOK_STR 0xb9 /* pointer to string in tokc */ #define TOK_LSTR 0xba #define TOK_CFLOAT 0xbb /* float constant */ #define TOK_CDOUBLE 0xbc /* double constant */ #define TOK_CLDOUBLE 0xbd /* long double constant */ #define TOK_PPNUM 0xbe /* preprocessor number */ #define TOK_PPSTR 0xbf /* preprocessor string */ #define TOK_LINENUM 0xc0 /* line number info */ /* <-- */ #define TOK_UMULL 0xc2 /* unsigned 32x32 -> 64 mul */ #define TOK_ADDC1 0xc3 /* add with carry generation */ #define TOK_ADDC2 0xc4 /* add with carry use */ #define TOK_SUBC1 0xc5 /* add with carry generation */ #define TOK_SUBC2 0xc6 /* add with carry use */ #define TOK_ARROW 0xc7 #define TOK_DOTS 0xc8 /* three dots */ #define TOK_SHR 0xc9 /* unsigned shift right */ #define TOK_TWOSHARPS 0xca /* ## preprocessing token */ #define TOK_PLCHLDR 0xcb /* placeholder token as defined in C99 */ #define TOK_NOSUBST 0xcc /* means following token has already been pp'd */ #define TOK_PPJOIN 0xce /* A '##' in the right position to mean pasting */ #define TOK_SHL 0x01 /* shift left */ #define TOK_SAR 0x02 /* signed shift right */ /* assignment operators : normal operator or 0x80 */ #define TOK_A_MOD 0xa5 #define TOK_A_AND 0xa6 #define TOK_A_MUL 0xaa #define TOK_A_ADD 0xab #define TOK_A_SUB 0xad #define TOK_A_DIV 0xaf #define TOK_A_XOR 0xde #define TOK_A_OR 0xfc #define TOK_A_SHL 0x81 #define TOK_A_SAR 0x82 #ifndef offsetof #define offsetof(type, field) ((size_t) &((type *)0)->field) #endif #ifndef countof #define countof(tab) (sizeof(tab) / sizeof((tab)[0])) #endif #define TOK_EOF (-1) /* end of file */ #define TOK_LINEFEED 10 /* line feed */ /* all identifiers and strings have token above that */ #define TOK_IDENT 256 #define DEF_ASM(x) DEF(TOK_ASM_ ## x, #x) #define TOK_ASM_int TOK_INT #define DEF_ASMDIR(x) DEF(TOK_ASMDIR_ ## x, "." #x) #define TOK_ASMDIR_FIRST TOK_ASMDIR_byte #define TOK_ASMDIR_LAST TOK_ASMDIR_section #if defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64 /* only used for i386 asm opcodes definitions */ #define DEF_BWL(x) \ DEF(TOK_ASM_ ## x ## b, #x "b") \ DEF(TOK_ASM_ ## x ## w, #x "w") \ DEF(TOK_ASM_ ## x ## l, #x "l") \ DEF(TOK_ASM_ ## x, #x) #define DEF_WL(x) \ DEF(TOK_ASM_ ## x ## w, #x "w") \ DEF(TOK_ASM_ ## x ## l, #x "l") \ DEF(TOK_ASM_ ## x, #x) #ifdef TCC_TARGET_X86_64 # define DEF_BWLQ(x) \ DEF(TOK_ASM_ ## x ## b, #x "b") \ DEF(TOK_ASM_ ## x ## w, #x "w") \ DEF(TOK_ASM_ ## x ## l, #x "l") \ DEF(TOK_ASM_ ## x ## q, #x "q") \ DEF(TOK_ASM_ ## x, #x) # define DEF_WLQ(x) \ DEF(TOK_ASM_ ## x ## w, #x "w") \ DEF(TOK_ASM_ ## x ## l, #x "l") \ DEF(TOK_ASM_ ## x ## q, #x "q") \ DEF(TOK_ASM_ ## x, #x) # define DEF_BWLX DEF_BWLQ # define DEF_WLX DEF_WLQ /* number of sizes + 1 */ # define NBWLX 5 #else # define DEF_BWLX DEF_BWL # define DEF_WLX DEF_WL /* number of sizes + 1 */ # define NBWLX 4 #endif #define DEF_FP1(x) \ DEF(TOK_ASM_ ## f ## x ## s, "f" #x "s") \ DEF(TOK_ASM_ ## fi ## x ## l, "fi" #x "l") \ DEF(TOK_ASM_ ## f ## x ## l, "f" #x "l") \ DEF(TOK_ASM_ ## fi ## x ## s, "fi" #x "s") #define DEF_FP(x) \ DEF(TOK_ASM_ ## f ## x, "f" #x ) \ DEF(TOK_ASM_ ## f ## x ## p, "f" #x "p") \ DEF_FP1(x) #define DEF_ASMTEST(x,suffix) \ DEF_ASM(x ## o ## suffix) \ DEF_ASM(x ## no ## suffix) \ DEF_ASM(x ## b ## suffix) \ DEF_ASM(x ## c ## suffix) \ DEF_ASM(x ## nae ## suffix) \ DEF_ASM(x ## nb ## suffix) \ DEF_ASM(x ## nc ## suffix) \ DEF_ASM(x ## ae ## suffix) \ DEF_ASM(x ## e ## suffix) \ DEF_ASM(x ## z ## suffix) \ DEF_ASM(x ## ne ## suffix) \ DEF_ASM(x ## nz ## suffix) \ DEF_ASM(x ## be ## suffix) \ DEF_ASM(x ## na ## suffix) \ DEF_ASM(x ## nbe ## suffix) \ DEF_ASM(x ## a ## suffix) \ DEF_ASM(x ## s ## suffix) \ DEF_ASM(x ## ns ## suffix) \ DEF_ASM(x ## p ## suffix) \ DEF_ASM(x ## pe ## suffix) \ DEF_ASM(x ## np ## suffix) \ DEF_ASM(x ## po ## suffix) \ DEF_ASM(x ## l ## suffix) \ DEF_ASM(x ## nge ## suffix) \ DEF_ASM(x ## nl ## suffix) \ DEF_ASM(x ## ge ## suffix) \ DEF_ASM(x ## le ## suffix) \ DEF_ASM(x ## ng ## suffix) \ DEF_ASM(x ## nle ## suffix) \ DEF_ASM(x ## g ## suffix) #endif /* defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64 */ enum tcc_token { TOK_LAST = TOK_IDENT - 1 #define DEF(id, str) ,id #include "tcctok.h" #undef DEF }; /* keywords: tok >= TOK_IDENT && tok < TOK_UIDENT */ #define TOK_UIDENT TOK_DEFINE /* ------------ libtcc.c ------------ */ /* use GNU C extensions */ ST_DATA int gnu_ext; /* use Tiny C extensions */ ST_DATA int tcc_ext; /* XXX: get rid of this ASAP */ ST_DATA struct TCCState *tcc_state; /* public functions currently used by the tcc main function */ ST_FUNC char *pstrcpy(char *buf, int buf_size, const char *s); ST_FUNC char *pstrcat(char *buf, int buf_size, const char *s); ST_FUNC char *pstrncpy(char *out, const char *in, size_t num); PUB_FUNC char *tcc_basename(const char *name); PUB_FUNC char *tcc_fileextension (const char *name); #ifndef MEM_DEBUG PUB_FUNC void tcc_free(void *ptr); PUB_FUNC void *tcc_malloc(unsigned long size); PUB_FUNC void *tcc_mallocz(unsigned long size); PUB_FUNC void *tcc_realloc(void *ptr, unsigned long size); PUB_FUNC char *tcc_strdup(const char *str); #else #define tcc_free(ptr) tcc_free_debug(ptr) #define tcc_malloc(size) tcc_malloc_debug(size, __FILE__, __LINE__) #define tcc_mallocz(size) tcc_mallocz_debug(size, __FILE__, __LINE__) #define tcc_realloc(ptr,size) tcc_realloc_debug(ptr, size, __FILE__, __LINE__) #define tcc_strdup(str) tcc_strdup_debug(str, __FILE__, __LINE__) PUB_FUNC void tcc_free_debug(void *ptr); PUB_FUNC void *tcc_malloc_debug(unsigned long size, const char *file, int line); PUB_FUNC void *tcc_mallocz_debug(unsigned long size, const char *file, int line); PUB_FUNC void *tcc_realloc_debug(void *ptr, unsigned long size, const char *file, int line); PUB_FUNC char *tcc_strdup_debug(const char *str, const char *file, int line); #endif #define free(p) use_tcc_free(p) #define malloc(s) use_tcc_malloc(s) #define realloc(p, s) use_tcc_realloc(p, s) #undef strdup #define strdup(s) use_tcc_strdup(s) PUB_FUNC void tcc_memcheck(void); PUB_FUNC void tcc_error_noabort(const char *fmt, ...); PUB_FUNC NORETURN void tcc_error(const char *fmt, ...); PUB_FUNC void tcc_warning(const char *fmt, ...); /* other utilities */ ST_FUNC void dynarray_add(void *ptab, int *nb_ptr, void *data); ST_FUNC void dynarray_reset(void *pp, int *n); ST_INLN void cstr_ccat(CString *cstr, int ch); ST_FUNC void cstr_cat(CString *cstr, const char *str, int len); ST_FUNC void cstr_wccat(CString *cstr, int ch); ST_FUNC void cstr_new(CString *cstr); ST_FUNC void cstr_free(CString *cstr); ST_FUNC void cstr_reset(CString *cstr); ST_INLN void sym_free(Sym *sym); ST_FUNC Sym *sym_push2(Sym **ps, int v, int t, long c); ST_FUNC Sym *sym_find2(Sym *s, int v); ST_FUNC Sym *sym_push(int v, CType *type, int r, long c); ST_FUNC void sym_pop(Sym **ptop, Sym *b, int keep); ST_INLN Sym *struct_find(int v); ST_INLN Sym *sym_find(int v); ST_FUNC Sym *global_identifier_push(int v, int t, long c); ST_FUNC void tcc_open_bf(TCCState *s1, const char *filename, int initlen); ST_FUNC int tcc_open(TCCState *s1, const char *filename); ST_FUNC void tcc_close(void); ST_FUNC int tcc_add_file_internal(TCCState *s1, const char *filename, int flags); /* flags: */ #define AFF_PRINT_ERROR 0x10 /* print error if file not found */ #define AFF_REFERENCED_DLL 0x20 /* load a referenced dll from another dll */ #define AFF_PREPROCESS 0x40 /* preprocess file */ /* combined with: */ #define AFF_TYPE_NONE 0 #define AFF_TYPE_C 1 #define AFF_TYPE_ASM 2 #define AFF_TYPE_ASMPP 3 #define AFF_TYPE_BIN 4 #define AFF_TYPE_LIB 5 /* values from tcc_object_type(...) */ #define AFF_BINTYPE_REL 1 #define AFF_BINTYPE_DYN 2 #define AFF_BINTYPE_AR 3 #define AFF_BINTYPE_C67 4 ST_FUNC int tcc_add_crt(TCCState *s, const char *filename); ST_FUNC int tcc_add_dll(TCCState *s, const char *filename, int flags); ST_FUNC void tcc_add_pragma_libs(TCCState *s1); PUB_FUNC int tcc_add_library_err(TCCState *s, const char *f); PUB_FUNC void tcc_print_stats(TCCState *s, unsigned total_time); PUB_FUNC int tcc_parse_args(TCCState *s, int *argc, char ***argv, int optind); #ifdef _WIN32 ST_FUNC char *normalize_slashes(char *path); #endif /* tcc_parse_args return codes: */ #define OPT_HELP 1 #define OPT_HELP2 2 #define OPT_V 3 #define OPT_PRINT_DIRS 4 #define OPT_AR 5 #define OPT_IMPDEF 6 #define OPT_M32 32 #define OPT_M64 64 /* ------------ tccpp.c ------------ */ ST_DATA struct BufferedFile *file; ST_DATA int ch, tok; ST_DATA CValue tokc; ST_DATA const int *macro_ptr; ST_DATA int parse_flags; ST_DATA int tok_flags; ST_DATA CString tokcstr; /* current parsed string, if any */ /* display benchmark infos */ ST_DATA int total_lines; ST_DATA int total_bytes; ST_DATA int tok_ident; ST_DATA TokenSym **table_ident; #define TOK_FLAG_BOL 0x0001 /* beginning of line before */ #define TOK_FLAG_BOF 0x0002 /* beginning of file before */ #define TOK_FLAG_ENDIF 0x0004 /* a endif was found matching starting #ifdef */ #define TOK_FLAG_EOF 0x0008 /* end of file */ #define PARSE_FLAG_PREPROCESS 0x0001 /* activate preprocessing */ #define PARSE_FLAG_TOK_NUM 0x0002 /* return numbers instead of TOK_PPNUM */ #define PARSE_FLAG_LINEFEED 0x0004 /* line feed is returned as a token. line feed is also returned at eof */ #define PARSE_FLAG_ASM_FILE 0x0008 /* we processing an asm file: '#' can be used for line comment, etc. */ #define PARSE_FLAG_SPACES 0x0010 /* next() returns space tokens (for -E) */ #define PARSE_FLAG_ACCEPT_STRAYS 0x0020 /* next() returns '\\' token */ #define PARSE_FLAG_TOK_STR 0x0040 /* return parsed strings instead of TOK_PPSTR */ /* isidnum_table flags: */ #define IS_SPC 1 #define IS_ID 2 #define IS_NUM 4 ST_FUNC TokenSym *tok_alloc(const char *str, int len); ST_FUNC const char *get_tok_str(int v, CValue *cv); ST_FUNC void begin_macro(TokenString *str, int alloc); ST_FUNC void end_macro(void); ST_FUNC void set_idnum(int c, int val); ST_FUNC void save_parse_state(ParseState *s); ST_FUNC void restore_parse_state(ParseState *s); ST_INLN void tok_str_new(TokenString *s); ST_FUNC TokenString *tok_str_alloc(void); ST_FUNC void tok_str_free(TokenString *s); ST_FUNC void tok_str_free_str(int *str); ST_FUNC void tok_str_add(TokenString *s, int t); ST_FUNC void tok_str_add_tok(TokenString *s); ST_INLN void define_push(int v, int macro_type, int *str, Sym *first_arg); ST_FUNC void define_undef(Sym *s); ST_INLN Sym *define_find(int v); ST_FUNC void free_defines(Sym *b); ST_FUNC Sym *label_find(int v); ST_FUNC Sym *label_push(Sym **ptop, int v, int flags); ST_FUNC void label_pop(Sym **ptop, Sym *slast); ST_FUNC void parse_define(void); ST_FUNC void preprocess(int is_bof); ST_FUNC void next_nomacro(void); ST_FUNC void next(void); ST_INLN void unget_tok(int last_tok); ST_FUNC void preprocess_start(TCCState *s1); ST_FUNC void tccpp_new(TCCState *s); ST_FUNC void tccpp_delete(TCCState *s); ST_FUNC int tcc_preprocess(TCCState *s1); ST_FUNC void skip(int c); ST_FUNC NORETURN void expect(const char *msg); /* space excluding newline */ static inline int is_space(int ch) { return ch == ' ' || ch == '\t' || ch == '\v' || ch == '\f' || ch == '\r'; } static inline int isid(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; } static inline int isnum(int c) { return c >= '0' && c <= '9'; } static inline int isoct(int c) { return c >= '0' && c <= '7'; } static inline int toup(int c) { return (c >= 'a' && c <= 'z') ? c - 'a' + 'A' : c; } /* ------------ tccgen.c ------------ */ #define SYM_POOL_NB (8192 / sizeof(Sym)) ST_DATA Sym *sym_free_first; ST_DATA void **sym_pools; ST_DATA int nb_sym_pools; ST_DATA Sym *global_stack; ST_DATA Sym *local_stack; ST_DATA Sym *local_label_stack; ST_DATA Sym *global_label_stack; ST_DATA Sym *define_stack; ST_DATA CType char_pointer_type, func_old_type, int_type, size_type; ST_DATA SValue __vstack[1+/*to make bcheck happy*/ VSTACK_SIZE], *vtop, *pvtop; #define vstack (__vstack + 1) ST_DATA int rsym, anon_sym, ind, loc; ST_DATA int const_wanted; /* true if constant wanted */ ST_DATA int nocode_wanted; /* true if no code generation wanted for an expression */ ST_DATA int global_expr; /* true if compound literals must be allocated globally (used during initializers parsing */ ST_DATA CType func_vt; /* current function return type (used by return instruction) */ ST_DATA int func_var; /* true if current function is variadic */ ST_DATA int func_vc; ST_DATA int last_line_num, last_ind, func_ind; /* debug last line number and pc */ ST_DATA const char *funcname; ST_FUNC void tcc_debug_start(TCCState *s1); ST_FUNC void tcc_debug_end(TCCState *s1); ST_FUNC void tcc_debug_funcstart(TCCState *s1, Sym *sym); ST_FUNC void tcc_debug_funcend(TCCState *s1, int size); ST_FUNC void tcc_debug_line(TCCState *s1); ST_FUNC void tccgen_start(TCCState *s1); ST_FUNC void tccgen_end(TCCState *s1); ST_FUNC void free_inline_functions(TCCState *s); ST_FUNC void check_vstack(void); ST_INLN int is_float(int t); ST_FUNC int ieee_finite(double d); ST_FUNC void test_lvalue(void); ST_FUNC void vpushi(int v); ST_FUNC Sym *external_global_sym(int v, CType *type, int r); ST_FUNC void vset(CType *type, int r, long v); ST_FUNC void vswap(void); ST_FUNC void vpush_global_sym(CType *type, int v); ST_FUNC void vrote(SValue *e, int n); ST_FUNC void vrott(int n); ST_FUNC void vrotb(int n); #ifdef TCC_TARGET_ARM ST_FUNC int get_reg_ex(int rc, int rc2); ST_FUNC void lexpand_nr(void); #endif ST_FUNC void vpushv(SValue *v); ST_FUNC void save_reg(int r); ST_FUNC void save_reg_upstack(int r, int n); ST_FUNC int get_reg(int rc); ST_FUNC void save_regs(int n); ST_FUNC void gaddrof(void); ST_FUNC int gv(int rc); ST_FUNC void gv2(int rc1, int rc2); ST_FUNC void vpop(void); ST_FUNC void gen_op(int op); ST_FUNC int type_size(CType *type, int *a); ST_FUNC void mk_pointer(CType *type); ST_FUNC void vstore(void); ST_FUNC void inc(int post, int c); ST_FUNC void parse_mult_str (CString *astr, const char *msg); ST_FUNC void parse_asm_str(CString *astr); ST_FUNC int lvalue_type(int t); ST_FUNC void indir(void); ST_FUNC void unary(void); ST_FUNC void expr_prod(void); ST_FUNC void expr_sum(void); ST_FUNC void gexpr(void); ST_FUNC int expr_const(void); ST_FUNC void decl(int l); #if defined CONFIG_TCC_BCHECK || defined TCC_TARGET_C67 ST_FUNC Sym *get_sym_ref(CType *type, Section *sec, unsigned long offset, unsigned long size); #endif #if defined TCC_TARGET_X86_64 && !defined TCC_TARGET_PE ST_FUNC int classify_x86_64_va_arg(CType *ty); #endif /* ------------ tccelf.c ------------ */ #define TCC_OUTPUT_FORMAT_ELF 0 /* default output format: ELF */ #define TCC_OUTPUT_FORMAT_BINARY 1 /* binary image output */ #define TCC_OUTPUT_FORMAT_COFF 2 /* COFF */ #define ARMAG "!<arch>\012" /* For COFF and a.out archives */ typedef struct { unsigned int n_strx; /* index into string table of name */ unsigned char n_type; /* type of symbol */ unsigned char n_other; /* misc info (usually empty) */ unsigned short n_desc; /* description field */ unsigned int n_value; /* value of symbol */ } Stab_Sym; ST_DATA Section *text_section, *data_section, *bss_section; /* predefined sections */ ST_DATA Section *common_section; ST_DATA Section *cur_text_section; /* current section where function code is generated */ #ifdef CONFIG_TCC_ASM ST_DATA Section *last_text_section; /* to handle .previous asm directive */ #endif #ifdef CONFIG_TCC_BCHECK /* bound check related sections */ ST_DATA Section *bounds_section; /* contains global data bound description */ ST_DATA Section *lbounds_section; /* contains local data bound description */ ST_FUNC void tccelf_bounds_new(TCCState *s); #endif /* symbol sections */ ST_DATA Section *symtab_section, *strtab_section; /* debug sections */ ST_DATA Section *stab_section, *stabstr_section; ST_FUNC void tccelf_new(TCCState *s); ST_FUNC void tccelf_delete(TCCState *s); ST_FUNC void tccelf_stab_new(TCCState *s); ST_FUNC Section *new_section(TCCState *s1, const char *name, int sh_type, int sh_flags); ST_FUNC void section_realloc(Section *sec, unsigned long new_size); ST_FUNC size_t section_add(Section *sec, addr_t size, int align); ST_FUNC void *section_ptr_add(Section *sec, addr_t size); ST_FUNC void section_reserve(Section *sec, unsigned long size); ST_FUNC Section *find_section(TCCState *s1, const char *name); ST_FUNC Section *new_symtab(TCCState *s1, const char *symtab_name, int sh_type, int sh_flags, const char *strtab_name, const char *hash_name, int hash_sh_flags); ST_FUNC void put_extern_sym2(Sym *sym, Section *section, addr_t value, unsigned long size, int can_add_underscore); ST_FUNC void put_extern_sym(Sym *sym, Section *section, addr_t value, unsigned long size); #if PTR_SIZE == 4 ST_FUNC void greloc(Section *s, Sym *sym, unsigned long offset, int type); #endif ST_FUNC void greloca(Section *s, Sym *sym, unsigned long offset, int type, addr_t addend); ST_FUNC int put_elf_str(Section *s, const char *sym); ST_FUNC int put_elf_sym(Section *s, addr_t value, unsigned long size, int info, int other, int shndx, const char *name); ST_FUNC int set_elf_sym(Section *s, addr_t value, unsigned long size, int info, int other, int shndx, const char *name); ST_FUNC int find_elf_sym(Section *s, const char *name); ST_FUNC void put_elf_reloc(Section *symtab, Section *s, unsigned long offset, int type, int symbol); ST_FUNC void put_elf_reloca(Section *symtab, Section *s, unsigned long offset, int type, int symbol, addr_t addend); ST_FUNC void put_stabs(const char *str, int type, int other, int desc, unsigned long value); ST_FUNC void put_stabs_r(const char *str, int type, int other, int desc, unsigned long value, Section *sec, int sym_index); ST_FUNC void put_stabn(int type, int other, int desc, int value); ST_FUNC void put_stabd(int type, int other, int desc); ST_FUNC void relocate_common_syms(void); ST_FUNC void relocate_syms(TCCState *s1, Section *symtab, int do_resolve); ST_FUNC void relocate_section(TCCState *s1, Section *s); ST_FUNC void tcc_add_linker_symbols(TCCState *s1); ST_FUNC int tcc_object_type(int fd, ElfW(Ehdr) *h); ST_FUNC int tcc_load_object_file(TCCState *s1, int fd, unsigned long file_offset); ST_FUNC int tcc_load_archive(TCCState *s1, int fd); ST_FUNC void tcc_add_bcheck(TCCState *s1); ST_FUNC void tcc_add_runtime(TCCState *s1); ST_FUNC void build_got_entries(TCCState *s1); ST_FUNC struct sym_attr *get_sym_attr(TCCState *s1, int index, int alloc); ST_FUNC void squeeze_multi_relocs(Section *sec, size_t oldrelocoffset); ST_FUNC addr_t get_elf_sym_addr(TCCState *s, const char *name, int err); #if defined TCC_IS_NATIVE || defined TCC_TARGET_PE ST_FUNC void *tcc_get_symbol_err(TCCState *s, const char *name); #endif #ifndef TCC_TARGET_PE ST_FUNC int tcc_load_dll(TCCState *s1, int fd, const char *filename, int level); ST_FUNC int tcc_load_ldscript(TCCState *s1); ST_FUNC uint8_t *parse_comment(uint8_t *p); ST_FUNC void minp(void); ST_INLN void inp(void); ST_FUNC int handle_eob(void); #endif /* ------------ xxx-link.c ------------ */ /* Wether to generate a GOT/PLT entry and when. NO_GOTPLT_ENTRY is first so that unknown relocation don't create a GOT or PLT entry */ enum gotplt_entry { NO_GOTPLT_ENTRY, /* never generate (eg. GLOB_DAT & JMP_SLOT relocs) */ BUILD_GOT_ONLY, /* only build GOT (eg. TPOFF relocs) */ AUTO_GOTPLT_ENTRY, /* generate if sym is UNDEF */ ALWAYS_GOTPLT_ENTRY /* always generate (eg. PLTOFF relocs) */ }; ST_FUNC int code_reloc (int reloc_type); ST_FUNC int gotplt_entry_type (int reloc_type); ST_FUNC unsigned create_plt_entry(TCCState *s1, unsigned got_offset, struct sym_attr *attr); ST_FUNC void relocate_init(Section *sr); ST_FUNC void relocate(TCCState *s1, ElfW_Rel *rel, int type, unsigned char *ptr, addr_t addr, addr_t val); ST_FUNC void relocate_plt(TCCState *s1); /* ------------ xxx-gen.c ------------ */ ST_DATA const int reg_classes[NB_REGS]; ST_FUNC void gsym_addr(int t, int a); ST_FUNC void gsym(int t); ST_FUNC void load(int r, SValue *sv); ST_FUNC void store(int r, SValue *v); ST_FUNC int gfunc_sret(CType *vt, int variadic, CType *ret, int *align, int *regsize); ST_FUNC void gfunc_call(int nb_args); ST_FUNC void gfunc_prolog(CType *func_type); ST_FUNC void gfunc_epilog(void); ST_FUNC int gjmp(int t); ST_FUNC void gjmp_addr(int a); ST_FUNC int gtst(int inv, int t); #if defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64 ST_FUNC void gtst_addr(int inv, int a); #else #define gtst_addr(inv, a) gsym_addr(gtst(inv, 0), a) #endif ST_FUNC void gen_opi(int op); ST_FUNC void gen_opf(int op); ST_FUNC void gen_cvt_ftoi(int t); ST_FUNC void gen_cvt_ftof(int t); ST_FUNC void ggoto(void); #ifndef TCC_TARGET_C67 ST_FUNC void o(unsigned int c); #endif #ifndef TCC_TARGET_ARM ST_FUNC void gen_cvt_itof(int t); #endif ST_FUNC void gen_vla_sp_save(int addr); ST_FUNC void gen_vla_sp_restore(int addr); ST_FUNC void gen_vla_alloc(CType *type, int align); static inline uint16_t read16le(unsigned char *p) { return p[0] | (uint16_t)p[1] << 8; } static inline void write16le(unsigned char *p, uint16_t x) { p[0] = x & 255, p[1] = x >> 8 & 255; } static inline uint32_t read32le(unsigned char *p) { return read16le(p) | (uint32_t)read16le(p + 2) << 16; } static inline void write32le(unsigned char *p, uint32_t x) { write16le(p, x), write16le(p + 2, x >> 16); } static inline void add32le(unsigned char *p, int32_t x) { write32le(p, read32le(p) + x); } #if HAVE_LONG_LONG static inline uint64_t read64le(unsigned char *p) { return read32le(p) | (uint64_t)read32le(p + 4) << 32; } static inline void write64le(unsigned char *p, uint64_t x) { write32le(p, x), write32le(p + 4, x >> 32); } static inline void add64le(unsigned char *p, int64_t x) { write64le(p, read64le(p) + x); } #endif /* ------------ i386-gen.c ------------ */ #if defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64 ST_FUNC void g(int c); ST_FUNC void gen_le16(int c); ST_FUNC void gen_le32(int c); ST_FUNC void gen_addr32(int r, Sym *sym, long c); ST_FUNC void gen_addrpc32(int r, Sym *sym, long c); #endif #ifdef CONFIG_TCC_BCHECK ST_FUNC void gen_bounded_ptr_add(void); ST_FUNC void gen_bounded_ptr_deref(void); #endif /* ------------ x86_64-gen.c ------------ */ #ifdef TCC_TARGET_X86_64 ST_FUNC void gen_addr64(int r, Sym *sym, int64_t c); ST_FUNC void gen_opl(int op); #endif /* ------------ arm-gen.c ------------ */ #ifdef TCC_TARGET_ARM #if defined(TCC_ARM_EABI) && !defined(CONFIG_TCC_ELFINTERP) PUB_FUNC const char *default_elfinterp(struct TCCState *s); #endif ST_FUNC void arm_init(struct TCCState *s); ST_FUNC void gen_cvt_itof1(int t); #endif /* ------------ arm64-gen.c ------------ */ #ifdef TCC_TARGET_ARM64 ST_FUNC void gen_cvt_sxtw(void); ST_FUNC void gen_opl(int op); ST_FUNC void gfunc_return(CType *func_type); ST_FUNC void gen_va_start(void); ST_FUNC void gen_va_arg(CType *t); ST_FUNC void gen_clear_cache(void); #endif /* ------------ c67-gen.c ------------ */ #ifdef TCC_TARGET_C67 #endif /* ------------ tcccoff.c ------------ */ #ifdef TCC_TARGET_COFF ST_FUNC int tcc_output_coff(TCCState *s1, FILE *f); ST_FUNC int tcc_load_coff(TCCState * s1, int fd); #endif /* ------------ tccasm.c ------------ */ ST_FUNC void asm_instr(void); ST_FUNC void asm_global_instr(void); #ifdef CONFIG_TCC_ASM ST_FUNC int find_constraint(ASMOperand *operands, int nb_operands, const char *name, const char **pp); ST_FUNC Sym* get_asm_sym(int name, Sym *csym); ST_FUNC void asm_expr(TCCState *s1, ExprValue *pe); ST_FUNC int asm_int_expr(TCCState *s1); ST_FUNC int tcc_assemble(TCCState *s1, int do_preprocess); /* ------------ i386-asm.c ------------ */ ST_FUNC void gen_expr32(ExprValue *pe); #ifdef TCC_TARGET_X86_64 ST_FUNC void gen_expr64(ExprValue *pe); #endif ST_FUNC void asm_opcode(TCCState *s1, int opcode); ST_FUNC int asm_parse_regvar(int t); ST_FUNC void asm_compute_constraints(ASMOperand *operands, int nb_operands, int nb_outputs, const uint8_t *clobber_regs, int *pout_reg); ST_FUNC void subst_asm_operand(CString *add_str, SValue *sv, int modifier); ST_FUNC void asm_gen_code(ASMOperand *operands, int nb_operands, int nb_outputs, int is_output, uint8_t *clobber_regs, int out_reg); ST_FUNC void asm_clobber(uint8_t *clobber_regs, const char *str); #endif /* ------------ tccpe.c -------------- */ #ifdef TCC_TARGET_PE ST_FUNC int pe_load_file(struct TCCState *s1, const char *filename, int fd); ST_FUNC int pe_output_file(TCCState * s1, const char *filename); ST_FUNC int pe_putimport(TCCState *s1, int dllindex, const char *name, addr_t value); #ifndef TCC_TARGET_ARM ST_FUNC SValue *pe_getimport(SValue *sv, SValue *v2); #endif #ifdef TCC_TARGET_X86_64 ST_FUNC void pe_add_unwind_data(unsigned start, unsigned end, unsigned stack); #endif PUB_FUNC int tcc_get_dllexports(const char *filename, char **pp); /* symbol properties stored in Elf32_Sym->st_other */ # define ST_PE_EXPORT 0x10 # define ST_PE_IMPORT 0x20 # define ST_PE_STDCALL 0x40 #endif /* ------------ tccrun.c ----------------- */ #ifdef TCC_IS_NATIVE #ifdef CONFIG_TCC_STATIC #define RTLD_LAZY 0x001 #define RTLD_NOW 0x002 #define RTLD_GLOBAL 0x100 #define RTLD_DEFAULT NULL /* dummy function for profiling */ ST_FUNC void *dlopen(const char *filename, int flag); ST_FUNC void dlclose(void *p); ST_FUNC const char *dlerror(void); ST_FUNC void *dlsym(void *handle, const char *symbol); #endif #ifdef CONFIG_TCC_BACKTRACE ST_DATA int rt_num_callers; ST_DATA const char **rt_bound_error_msg; ST_DATA void *rt_prog_main; ST_FUNC void tcc_set_num_callers(int n); #endif ST_FUNC void tcc_run_free(TCCState *s1); #endif /* ------------ tcctools.c ----------------- */ #if 0 /* included in tcc.c */ ST_FUNC int tcc_tool_ar(TCCState *s, int argc, char **argv); #ifdef TCC_TARGET_PE ST_FUNC int tcc_tool_impdef(TCCState *s, int argc, char **argv); #endif ST_FUNC void tcc_tool_cross(TCCState *s, char **argv, int option); ST_FUNC void gen_makedeps(TCCState *s, const char *target, const char *filename); #endif /********************************************************/ #undef ST_DATA #ifdef ONE_SOURCE #define ST_DATA static #else #define ST_DATA #endif /********************************************************/ #endif /* _TCC_H */
/* * TCC - Tiny C Compiler * * Copyright (c) 2001-2004 Fabrice Bellard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _TCC_H #include "tcc.h" #endif #if BOOTSTRAP && __arm__ void tccpp_ok () { } #endif /********************************************************/ /* global variables */ ST_DATA int tok_flags; ST_DATA int parse_flags; ST_DATA struct BufferedFile *file; ST_DATA int ch, tok; ST_DATA CValue tokc; ST_DATA const int *macro_ptr; ST_DATA CString tokcstr; /* current parsed string, if any */ /* display benchmark infos */ ST_DATA int total_lines; ST_DATA int total_bytes; ST_DATA int tok_ident; ST_DATA TokenSym **table_ident; /* ------------------------------------------------------------------------- */ static TokenSym *hash_ident[TOK_HASH_SIZE]; static char token_buf[STRING_MAX_SIZE + 1]; static CString cstr_buf; static CString macro_equal_buf; static TokenString tokstr_buf; static unsigned char isidnum_table[256 - CH_EOF]; static int pp_debug_tok, pp_debug_symv; static int pp_once; static void tok_print(const char *msg, const int *str); static struct TinyAlloc *toksym_alloc; static struct TinyAlloc *tokstr_alloc; static struct TinyAlloc *cstr_alloc; static TokenString *macro_stack; static const char tcc_keywords[] = #define DEF(id, str) str "\0" #include "tcctok.h" #undef DEF ; /* WARNING: the content of this string encodes token numbers */ static const unsigned char tok_two_chars[] = /* outdated -- gr "<=\236>=\235!=\225&&\240||\241++\244--\242==\224<<\1>>\2+=\253" "-=\255*=\252/=\257%=\245&=\246^=\336|=\374->\313..\250##\266"; */{ '<','=', TOK_LE, '>','=', TOK_GE, '!','=', TOK_NE, '&','&', TOK_LAND, '|','|', TOK_LOR, '+','+', TOK_INC, '-','-', TOK_DEC, '=','=', TOK_EQ, '<','<', TOK_SHL, '>','>', TOK_SAR, '+','=', TOK_A_ADD, '-','=', TOK_A_SUB, '*','=', TOK_A_MUL, '/','=', TOK_A_DIV, '%','=', TOK_A_MOD, '&','=', TOK_A_AND, '^','=', TOK_A_XOR, '|','=', TOK_A_OR, '-','>', TOK_ARROW, '.','.', 0xa8, // C++ token ? '#','#', TOK_TWOSHARPS, 0 }; static void next_nomacro_spc(void); ST_FUNC void skip(int c) { if (tok != c) tcc_error("'%c' expected (got \"%s\")", c, get_tok_str(tok, &tokc)); next(); } ST_FUNC void expect(const char *msg) { tcc_error("%s expected", msg); } /* ------------------------------------------------------------------------- */ /* Custom allocator for tiny objects */ #define USE_TAL #ifndef USE_TAL #define tal_free(al, p) tcc_free(p) #define tal_realloc(al, p, size) tcc_realloc(p, size) #define tal_new(a,b,c) #define tal_delete(a) #else #if !defined(MEM_DEBUG) #define tal_free(al, p) tal_free_impl(al, p) #define tal_realloc(al, p, size) tal_realloc_impl(&al, p, size) #define TAL_DEBUG_PARAMS #else #define TAL_DEBUG 1 //#define TAL_INFO 1 /* collect and dump allocators stats */ #define tal_free(al, p) tal_free_impl(al, p, __FILE__, __LINE__) #define tal_realloc(al, p, size) tal_realloc_impl(&al, p, size, __FILE__, __LINE__) #define TAL_DEBUG_PARAMS , const char *file, int line #define TAL_DEBUG_FILE_LEN 15 #endif #define TOKSYM_TAL_SIZE (768 * 1024) /* allocator for tiny TokenSym in table_ident */ #define TOKSTR_TAL_SIZE (768 * 1024) /* allocator for tiny TokenString instances */ #define CSTR_TAL_SIZE (256 * 1024) /* allocator for tiny CString instances */ #define TOKSYM_TAL_LIMIT 256 /* prefer unique limits to distinguish allocators debug msgs */ #define TOKSTR_TAL_LIMIT 128 /* 32 * sizeof(int) */ #define CSTR_TAL_LIMIT 1024 typedef struct TinyAlloc { unsigned limit; unsigned size; uint8_t *buffer; uint8_t *p; unsigned nb_allocs; struct TinyAlloc *next, *top; #ifdef TAL_INFO unsigned nb_peak; unsigned nb_total; unsigned nb_missed; uint8_t *peak_p; #endif } TinyAlloc; typedef struct tal_header_t { unsigned size; #ifdef TAL_DEBUG int line_num; /* negative line_num used for double free check */ char file_name[TAL_DEBUG_FILE_LEN + 1]; #endif } tal_header_t; /* ------------------------------------------------------------------------- */ static TinyAlloc *tal_new(TinyAlloc **pal, unsigned limit, unsigned size) { TinyAlloc *al = tcc_mallocz(sizeof(TinyAlloc)); al->p = al->buffer = tcc_malloc(size); al->limit = limit; al->size = size; if (pal) *pal = al; return al; } static void tal_delete(TinyAlloc *al) { TinyAlloc *next; tail_call: if (!al) return; #ifdef TAL_INFO fprintf(stderr, "limit=%5d, size=%5g MB, nb_peak=%6d, nb_total=%8d, nb_missed=%6d, usage=%5.1f%%\n", al->limit, al->size / 1024.0 / 1024.0, al->nb_peak, al->nb_total, al->nb_missed, (al->peak_p - al->buffer) * 100.0 / al->size); #endif #ifdef TAL_DEBUG if (al->nb_allocs > 0) { uint8_t *p; fprintf(stderr, "TAL_DEBUG: memory leak %d chunk(s) (limit= %d)\n", al->nb_allocs, al->limit); p = al->buffer; while (p < al->p) { tal_header_t *header = (tal_header_t *)p; if (header->line_num > 0) { fprintf(stderr, "%s:%d: chunk of %d bytes leaked\n", header->file_name, header->line_num, header->size); } p += header->size + sizeof(tal_header_t); } #if MEM_DEBUG-0 == 2 exit(2); #endif } #endif next = al->next; tcc_free(al->buffer); tcc_free(al); al = next; goto tail_call; } static void tal_free_impl(TinyAlloc *al, void *p TAL_DEBUG_PARAMS) { if (!p) return; tail_call: if (al->buffer <= (uint8_t *)p && (uint8_t *)p < al->buffer + al->size) { #ifdef TAL_DEBUG tal_header_t *header = (((tal_header_t *)p) - 1); if (header->line_num < 0) { fprintf(stderr, "%s:%d: TAL_DEBUG: double frees chunk from\n", file, line); fprintf(stderr, "%s:%d: %d bytes\n", header->file_name, (int)-header->line_num, (int)header->size); } else header->line_num = -header->line_num; #endif al->nb_allocs--; if (!al->nb_allocs) al->p = al->buffer; } else if (al->next) { al = al->next; goto tail_call; } else tcc_free(p); } static void *tal_realloc_impl(TinyAlloc **pal, void *p, unsigned size TAL_DEBUG_PARAMS) { tal_header_t *header; void *ret; int is_own; unsigned adj_size = (size + 3) & -4; TinyAlloc *al = *pal; tail_call: is_own = (al->buffer <= (uint8_t *)p && (uint8_t *)p < al->buffer + al->size); if ((!p || is_own) && size <= al->limit) { if (al->p + adj_size + sizeof(tal_header_t) < al->buffer + al->size) { header = (tal_header_t *)al->p; header->size = adj_size; #ifdef TAL_DEBUG { int ofs = strlen(file) - TAL_DEBUG_FILE_LEN; strncpy(header->file_name, file + (ofs > 0 ? ofs : 0), TAL_DEBUG_FILE_LEN); header->file_name[TAL_DEBUG_FILE_LEN] = 0; header->line_num = line; } #endif ret = al->p + sizeof(tal_header_t); al->p += adj_size + sizeof(tal_header_t); if (is_own) { header = (((tal_header_t *)p) - 1); memcpy(ret, p, header->size); #ifdef TAL_DEBUG header->line_num = -header->line_num; #endif } else { al->nb_allocs++; } #ifdef TAL_INFO if (al->nb_peak < al->nb_allocs) al->nb_peak = al->nb_allocs; if (al->peak_p < al->p) al->peak_p = al->p; al->nb_total++; #endif return ret; } else if (is_own) { al->nb_allocs--; ret = tal_realloc(*pal, 0, size); header = (((tal_header_t *)p) - 1); memcpy(ret, p, header->size); #ifdef TAL_DEBUG header->line_num = -header->line_num; #endif return ret; } if (al->next) { al = al->next; } else { TinyAlloc *bottom = al, *next = al->top ? al->top : al; al = tal_new(pal, next->limit, next->size * 2); al->next = next; bottom->top = al; } goto tail_call; } if (is_own) { al->nb_allocs--; ret = tcc_malloc(size); header = (((tal_header_t *)p) - 1); memcpy(ret, p, header->size); #ifdef TAL_DEBUG header->line_num = -header->line_num; #endif } else if (al->next) { al = al->next; goto tail_call; } else ret = tcc_realloc(p, size); #ifdef TAL_INFO al->nb_missed++; #endif return ret; } #endif /* USE_TAL */ /* ------------------------------------------------------------------------- */ /* CString handling */ static void cstr_realloc(CString *cstr, int new_size) { int size; size = cstr->size_allocated; if (size < 8) size = 8; /* no need to allocate a too small first string */ while (size < new_size) size = size * 2; cstr->data = tal_realloc(cstr_alloc, cstr->data, size); cstr->size_allocated = size; } /* add a byte */ ST_INLN void cstr_ccat(CString *cstr, int ch) { int size; size = cstr->size + 1; if (size > cstr->size_allocated) cstr_realloc(cstr, size); ((unsigned char *)cstr->data)[size - 1] = ch; cstr->size = size; } ST_FUNC void cstr_cat(CString *cstr, const char *str, int len) { int size; if (len <= 0) len = strlen(str) + 1 + len; size = cstr->size + len; if (size > cstr->size_allocated) cstr_realloc(cstr, size); memmove(((unsigned char *)cstr->data) + cstr->size, str, len); cstr->size = size; } /* add a wide char */ ST_FUNC void cstr_wccat(CString *cstr, int ch) { int size; size = cstr->size + sizeof(nwchar_t); if (size > cstr->size_allocated) cstr_realloc(cstr, size); *(nwchar_t *)(((unsigned char *)cstr->data) + size - sizeof(nwchar_t)) = ch; cstr->size = size; } ST_FUNC void cstr_new(CString *cstr) { memset(cstr, 0, sizeof(CString)); } /* free string and reset it to NULL */ ST_FUNC void cstr_free(CString *cstr) { tal_free(cstr_alloc, cstr->data); cstr_new(cstr); } /* reset string to empty */ ST_FUNC void cstr_reset(CString *cstr) { cstr->size = 0; } /* XXX: unicode ? */ static void add_char(CString *cstr, int c) { if (c == '\'' || c == '\"' || c == '\\') { /* XXX: could be more precise if char or string */ cstr_ccat(cstr, '\\'); } if (c >= 32 && c <= 126) { cstr_ccat(cstr, c); } else { cstr_ccat(cstr, '\\'); if (c == '\n') { cstr_ccat(cstr, 'n'); } else { cstr_ccat(cstr, '0' + ((c >> 6) & 7)); cstr_ccat(cstr, '0' + ((c >> 3) & 7)); cstr_ccat(cstr, '0' + (c & 7)); } } } /* ------------------------------------------------------------------------- */ /* allocate a new token */ static TokenSym *tok_alloc_new(TokenSym **pts, const char *str, int len) { TokenSym *ts, **ptable; int i; if (tok_ident >= SYM_FIRST_ANOM) tcc_error("memory full (symbols)"); /* expand token table if needed */ i = tok_ident - TOK_IDENT; if ((i % TOK_ALLOC_INCR) == 0) { ptable = tcc_realloc(table_ident, (i + TOK_ALLOC_INCR) * sizeof(TokenSym *)); table_ident = ptable; } ts = tal_realloc(toksym_alloc, 0, sizeof(TokenSym) + len); table_ident[i] = ts; ts->tok = tok_ident++; ts->sym_define = NULL; ts->sym_label = NULL; ts->sym_struct = NULL; ts->sym_identifier = NULL; ts->len = len; ts->hash_next = NULL; memcpy(ts->str, str, len); ts->str[len] = '\0'; *pts = ts; return ts; } #define TOK_HASH_INIT 1 #define TOK_HASH_FUNC(h, c) ((h) + ((h) << 5) + ((h) >> 27) + (c)) /* find a token and add it if not found */ ST_FUNC TokenSym *tok_alloc(const char *str, int len) { TokenSym *ts, **pts; int i; unsigned int h; h = TOK_HASH_INIT; for(i=0;i<len;i++) h = TOK_HASH_FUNC(h, ((unsigned char *)str)[i]); h &= (TOK_HASH_SIZE - 1); pts = &hash_ident[h]; for(;;) { ts = *pts; if (!ts) break; if (ts->len == len && !memcmp(ts->str, str, len)) return ts; pts = &(ts->hash_next); } return tok_alloc_new(pts, str, len); } /* XXX: buffer overflow */ /* XXX: float tokens */ ST_FUNC const char *get_tok_str(int v, CValue *cv) { char *p; int i, len; cstr_reset(&cstr_buf); p = cstr_buf.data; switch(v) { case TOK_CINT: case TOK_CUINT: case TOK_CLLONG: case TOK_CULLONG: /* XXX: not quite exact, but only useful for testing */ #ifdef _WIN32 sprintf(p, "%u", (unsigned)cv->i); #else sprintf(p, "%llu", (unsigned long long)cv->i); #endif break; case TOK_LCHAR: cstr_ccat(&cstr_buf, 'L'); case TOK_CCHAR: cstr_ccat(&cstr_buf, '\''); add_char(&cstr_buf, cv->i); cstr_ccat(&cstr_buf, '\''); cstr_ccat(&cstr_buf, '\0'); break; case TOK_PPNUM: case TOK_PPSTR: return (char*)cv->str.data; case TOK_LSTR: cstr_ccat(&cstr_buf, 'L'); case TOK_STR: cstr_ccat(&cstr_buf, '\"'); if (v == TOK_STR) { len = cv->str.size - 1; for(i=0;i<len;i++) add_char(&cstr_buf, ((unsigned char *)cv->str.data)[i]); } else { len = (cv->str.size / sizeof(nwchar_t)) - 1; for(i=0;i<len;i++) add_char(&cstr_buf, ((nwchar_t *)cv->str.data)[i]); } cstr_ccat(&cstr_buf, '\"'); cstr_ccat(&cstr_buf, '\0'); break; case TOK_CFLOAT: cstr_cat(&cstr_buf, "<float>", 0); break; case TOK_CDOUBLE: cstr_cat(&cstr_buf, "<double>", 0); break; case TOK_CLDOUBLE: cstr_cat(&cstr_buf, "<long double>", 0); break; case TOK_LINENUM: cstr_cat(&cstr_buf, "<linenumber>", 0); break; /* above tokens have value, the ones below don't */ case TOK_LT: v = '<'; goto addv; case TOK_GT: v = '>'; goto addv; case TOK_DOTS: return strcpy(p, "..."); case TOK_A_SHL: return strcpy(p, "<<="); case TOK_A_SAR: return strcpy(p, ">>="); default: if (v < TOK_IDENT) { /* search in two bytes table */ const unsigned char *q = tok_two_chars; while (*q) { if (q[2] == v) { *p++ = q[0]; *p++ = q[1]; *p = '\0'; return cstr_buf.data; } q += 3; } if (v >= 127) { sprintf(cstr_buf.data, "<%02x>", v); return cstr_buf.data; } addv: *p++ = v; *p = '\0'; } else if (v < tok_ident) { return table_ident[v - TOK_IDENT]->str; } else if (v >= SYM_FIRST_ANOM) { /* special name for anonymous symbol */ sprintf(p, "L.%u", v - SYM_FIRST_ANOM); } else { /* should never happen */ return NULL; } break; } return cstr_buf.data; } /* return the current character, handling end of block if necessary (but not stray) */ ST_FUNC int handle_eob(void) { BufferedFile *bf = file; int len; /* only tries to read if really end of buffer */ if (bf->buf_ptr >= bf->buf_end) { if (bf->fd != -1) { #if defined(PARSE_DEBUG) len = 1; #else len = IO_BUF_SIZE; #endif len = read(bf->fd, bf->buffer, len); if (len < 0) len = 0; } else { len = 0; } total_bytes += len; bf->buf_ptr = bf->buffer; bf->buf_end = bf->buffer + len; *bf->buf_end = CH_EOB; } if (bf->buf_ptr < bf->buf_end) { return bf->buf_ptr[0]; } else { bf->buf_ptr = bf->buf_end; return CH_EOF; } } /* read next char from current input file and handle end of input buffer */ ST_INLN void inp(void) { ch = *(++(file->buf_ptr)); /* end of buffer/file handling */ if (ch == CH_EOB) ch = handle_eob(); } /* handle '\[\r]\n' */ static int handle_stray_noerror(void) { while (ch == '\\') { inp(); if (ch == '\n') { file->line_num++; inp(); } else if (ch == '\r') { inp(); if (ch != '\n') goto fail; file->line_num++; inp(); } else { fail: return 1; } } return 0; } static void handle_stray(void) { if (handle_stray_noerror()) tcc_error("stray '\\' in program"); } /* skip the stray and handle the \\n case. Output an error if incorrect char after the stray */ static int handle_stray1(uint8_t *p) { int c; file->buf_ptr = p; if (p >= file->buf_end) { c = handle_eob(); if (c != '\\') return c; p = file->buf_ptr; } ch = *p; if (handle_stray_noerror()) { if (!(parse_flags & PARSE_FLAG_ACCEPT_STRAYS)) tcc_error("stray '\\' in program"); *--file->buf_ptr = '\\'; } p = file->buf_ptr; c = *p; return c; } /* handle just the EOB case, but not stray */ #define PEEKC_EOB(c, p)\ {\ p++;\ c = *p;\ if (c == '\\') {\ file->buf_ptr = p;\ c = handle_eob();\ p = file->buf_ptr;\ }\ } /* handle the complicated stray case */ #define PEEKC(c, p)\ {\ p++;\ c = *p;\ if (c == '\\') {\ c = handle_stray1(p);\ p = file->buf_ptr;\ }\ } /* input with '\[\r]\n' handling. Note that this function cannot handle other characters after '\', so you cannot call it inside strings or comments */ ST_FUNC void minp(void) { inp(); if (ch == '\\') handle_stray(); } /* single line C++ comments */ static uint8_t *parse_line_comment(uint8_t *p) { int c; p++; for(;;) { c = *p; redo: if (c == '\n' || c == CH_EOF) { break; } else if (c == '\\') { file->buf_ptr = p; c = handle_eob(); p = file->buf_ptr; if (c == '\\') { PEEKC_EOB(c, p); if (c == '\n') { file->line_num++; PEEKC_EOB(c, p); } else if (c == '\r') { PEEKC_EOB(c, p); if (c == '\n') { file->line_num++; PEEKC_EOB(c, p); } } } else { goto redo; } } else { p++; } } return p; } /* C comments */ ST_FUNC uint8_t *parse_comment(uint8_t *p) { int c; p++; for(;;) { /* fast skip loop */ for(;;) { c = *p; if (c == '\n' || c == '*' || c == '\\') break; p++; c = *p; if (c == '\n' || c == '*' || c == '\\') break; p++; } /* now we can handle all the cases */ if (c == '\n') { file->line_num++; p++; } else if (c == '*') { p++; for(;;) { c = *p; if (c == '*') { p++; } else if (c == '/') { goto end_of_comment; } else if (c == '\\') { file->buf_ptr = p; c = handle_eob(); p = file->buf_ptr; if (c == CH_EOF) tcc_error("unexpected end of file in comment"); if (c == '\\') { /* skip '\[\r]\n', otherwise just skip the stray */ while (c == '\\') { PEEKC_EOB(c, p); if (c == '\n') { file->line_num++; PEEKC_EOB(c, p); } else if (c == '\r') { PEEKC_EOB(c, p); if (c == '\n') { file->line_num++; PEEKC_EOB(c, p); } } else { goto after_star; } } } } else { break; } } after_star: ; } else { /* stray, eob or eof */ file->buf_ptr = p; c = handle_eob(); p = file->buf_ptr; if (c == CH_EOF) { tcc_error("unexpected end of file in comment"); } else if (c == '\\') { p++; } } } end_of_comment: p++; return p; } ST_FUNC void set_idnum(int c, int val) { isidnum_table[c - CH_EOF] = val; } #define cinp minp static inline void skip_spaces(void) { while (isidnum_table[ch - CH_EOF] & IS_SPC) cinp(); } static inline int check_space(int t, int *spc) { if (t < 256 && (isidnum_table[t - CH_EOF] & IS_SPC)) { if (*spc) return 1; *spc = 1; } else *spc = 0; return 0; } /* parse a string without interpreting escapes */ static uint8_t *parse_pp_string(uint8_t *p, int sep, CString *str) { int c; p++; for(;;) { c = *p; if (c == sep) { break; } else if (c == '\\') { file->buf_ptr = p; c = handle_eob(); p = file->buf_ptr; if (c == CH_EOF) { unterminated_string: /* XXX: indicate line number of start of string */ tcc_error("missing terminating %c character", sep); } else if (c == '\\') { /* escape : just skip \[\r]\n */ PEEKC_EOB(c, p); if (c == '\n') { file->line_num++; p++; } else if (c == '\r') { PEEKC_EOB(c, p); if (c != '\n') expect("'\n' after '\r'"); file->line_num++; p++; } else if (c == CH_EOF) { goto unterminated_string; } else { if (str) { cstr_ccat(str, '\\'); cstr_ccat(str, c); } p++; } } } else if (c == '\n') { file->line_num++; goto add_char; } else if (c == '\r') { PEEKC_EOB(c, p); if (c != '\n') { if (str) cstr_ccat(str, '\r'); } else { file->line_num++; goto add_char; } } else { add_char: if (str) cstr_ccat(str, c); p++; } } p++; return p; } /* skip block of text until #else, #elif or #endif. skip also pairs of #if/#endif */ static void preprocess_skip(void) { int a, start_of_line, c, in_warn_or_error; uint8_t *p; p = file->buf_ptr; a = 0; redo_start: start_of_line = 1; in_warn_or_error = 0; for(;;) { redo_no_start: c = *p; switch(c) { case ' ': case '\t': case '\f': case '\v': case '\r': p++; goto redo_no_start; case '\n': file->line_num++; p++; goto redo_start; case '\\': file->buf_ptr = p; c = handle_eob(); if (c == CH_EOF) { expect("#endif"); } else if (c == '\\') { ch = file->buf_ptr[0]; handle_stray_noerror(); } p = file->buf_ptr; goto redo_no_start; /* skip strings */ case '\"': case '\'': if (in_warn_or_error) goto _default; p = parse_pp_string(p, c, NULL); break; /* skip comments */ case '/': if (in_warn_or_error) goto _default; file->buf_ptr = p; ch = *p; minp(); p = file->buf_ptr; if (ch == '*') { p = parse_comment(p); } else if (ch == '/') { p = parse_line_comment(p); } break; case '#': p++; if (start_of_line) { file->buf_ptr = p; next_nomacro(); p = file->buf_ptr; if (a == 0 && (tok == TOK_ELSE || tok == TOK_ELIF || tok == TOK_ENDIF)) goto the_end; if (tok == TOK_IF || tok == TOK_IFDEF || tok == TOK_IFNDEF) a++; else if (tok == TOK_ENDIF) a--; else if( tok == TOK_ERROR || tok == TOK_WARNING) in_warn_or_error = 1; else if (tok == TOK_LINEFEED) goto redo_start; else if (parse_flags & PARSE_FLAG_ASM_FILE) p = parse_line_comment(p - 1); } else if (parse_flags & PARSE_FLAG_ASM_FILE) p = parse_line_comment(p - 1); break; _default: default: p++; break; } start_of_line = 0; } the_end: ; file->buf_ptr = p; } /* ParseState handling */ /* XXX: currently, no include file info is stored. Thus, we cannot display accurate messages if the function or data definition spans multiple files */ /* save current parse state in 's' */ ST_FUNC void save_parse_state(ParseState *s) { s->line_num = file->line_num; s->macro_ptr = macro_ptr; s->tok = tok; s->tokc = tokc; } /* restore parse state from 's' */ ST_FUNC void restore_parse_state(ParseState *s) { file->line_num = s->line_num; macro_ptr = s->macro_ptr; tok = s->tok; tokc = s->tokc; } #if 0 /* return the number of additional 'ints' necessary to store the token */ static inline int tok_size(const int *p) { switch(*p) { /* 4 bytes */ case TOK_CINT: case TOK_CUINT: case TOK_CCHAR: case TOK_LCHAR: case TOK_CFLOAT: case TOK_LINENUM: return 1 + 1; case TOK_STR: case TOK_LSTR: case TOK_PPNUM: case TOK_PPSTR: return 1 + ((sizeof(CString) + ((CString *)(p+1))->size + 3) >> 2); case TOK_CDOUBLE: case TOK_CLLONG: case TOK_CULLONG: return 1 + 2; case TOK_CLDOUBLE: return 1 + LDOUBLE_SIZE / 4; default: return 1 + 0; } } #endif /* token string handling */ ST_INLN void tok_str_new(TokenString *s) { s->str = NULL; s->len = 0; s->allocated_len = 0; s->last_line_num = -1; } ST_FUNC TokenString *tok_str_alloc(void) { TokenString *str = tal_realloc(tokstr_alloc, 0, sizeof *str); tok_str_new(str); return str; } ST_FUNC int *tok_str_dup(TokenString *s) { int *str; str = tal_realloc(tokstr_alloc, 0, s->len * sizeof(int)); memcpy(str, s->str, s->len * sizeof(int)); return str; } ST_FUNC void tok_str_free_str(int *str) { tal_free(tokstr_alloc, str); } ST_FUNC void tok_str_free(TokenString *str) { tok_str_free_str(str->str); tal_free(tokstr_alloc, str); } ST_FUNC int *tok_str_realloc(TokenString *s, int new_size) { int *str, size; size = s->allocated_len; if (size < 16) size = 16; while (size < new_size) size = size * 2; if (size > s->allocated_len) { str = tal_realloc(tokstr_alloc, s->str, size * sizeof(int)); s->allocated_len = size; s->str = str; } return s->str; } ST_FUNC void tok_str_add(TokenString *s, int t) { int len, *str; len = s->len; str = s->str; if (len >= s->allocated_len) str = tok_str_realloc(s, len + 1); str[len++] = t; s->len = len; } ST_FUNC void begin_macro(TokenString *str, int alloc) { str->alloc = alloc; str->prev = macro_stack; str->prev_ptr = macro_ptr; macro_ptr = str->str; macro_stack = str; } ST_FUNC void end_macro(void) { TokenString *str = macro_stack; macro_stack = str->prev; macro_ptr = str->prev_ptr; if (str->alloc == 2) { str->alloc = 3; /* just mark as finished */ } else { tok_str_free(str); } } static void tok_str_add2(TokenString *s, int t, CValue *cv) { int len, *str; len = s->len; str = s->str; /* allocate space for worst case */ if (len + TOK_MAX_SIZE >= s->allocated_len) str = tok_str_realloc(s, len + TOK_MAX_SIZE + 1); str[len++] = t; switch(t) { case TOK_CINT: case TOK_CUINT: case TOK_CCHAR: case TOK_LCHAR: case TOK_CFLOAT: case TOK_LINENUM: str[len++] = cv->tab[0]; break; case TOK_PPNUM: case TOK_PPSTR: case TOK_STR: case TOK_LSTR: { /* Insert the string into the int array. */ size_t nb_words = 1 + (cv->str.size + sizeof(int) - 1) / sizeof(int); if (len + nb_words >= s->allocated_len) str = tok_str_realloc(s, len + nb_words + 1); str[len] = cv->str.size; memcpy(&str[len + 1], cv->str.data, cv->str.size); len += nb_words; } break; case TOK_CDOUBLE: case TOK_CLLONG: case TOK_CULLONG: #if LDOUBLE_SIZE == 8 case TOK_CLDOUBLE: #endif str[len++] = cv->tab[0]; str[len++] = cv->tab[1]; break; #if LDOUBLE_SIZE == 12 case TOK_CLDOUBLE: str[len++] = cv->tab[0]; str[len++] = cv->tab[1]; str[len++] = cv->tab[2]; #elif LDOUBLE_SIZE == 16 case TOK_CLDOUBLE: str[len++] = cv->tab[0]; str[len++] = cv->tab[1]; str[len++] = cv->tab[2]; str[len++] = cv->tab[3]; #elif LDOUBLE_SIZE != 8 #error add long double size support #endif break; default: break; } s->len = len; } /* add the current parse token in token string 's' */ ST_FUNC void tok_str_add_tok(TokenString *s) { CValue cval; /* save line number info */ if (file->line_num != s->last_line_num) { s->last_line_num = file->line_num; cval.i = s->last_line_num; tok_str_add2(s, TOK_LINENUM, &cval); } tok_str_add2(s, tok, &tokc); } /* get a token from an integer array and increment pointer accordingly. we code it as a macro to avoid pointer aliasing. */ static inline void TOK_GET(int *t, const int **pp, CValue *cv) { const int *p = *pp; int n, *tab; tab = cv->tab; switch(*t = *p++) { case TOK_CINT: case TOK_CUINT: case TOK_CCHAR: case TOK_LCHAR: case TOK_LINENUM: tab[0] = *p++; cv->i = (*t == TOK_CUINT) ? (unsigned)cv->i : (int)cv->i; break; case TOK_CFLOAT: tab[0] = *p++; break; case TOK_STR: case TOK_LSTR: case TOK_PPNUM: case TOK_PPSTR: cv->str.size = *p++; cv->str.data = p; p += (cv->str.size + sizeof(int) - 1) / sizeof(int); break; case TOK_CDOUBLE: case TOK_CLLONG: case TOK_CULLONG: n = 2; goto copy; case TOK_CLDOUBLE: #if LDOUBLE_SIZE == 16 n = 4; #elif LDOUBLE_SIZE == 12 n = 3; #elif LDOUBLE_SIZE == 8 n = 2; #else # error add long double size support #endif copy: do *tab++ = *p++; while (--n); break; default: break; } *pp = p; } /* Calling this function is expensive, but it is not possible to read a token string backwards. */ static int tok_last(const int *str0, const int *str1) { const int *str = str0; int tok = 0; CValue cval; while (str < str1) TOK_GET(&tok, &str, &cval); return tok; } static int macro_is_equal(const int *a, const int *b) { CValue cv; int t; if (!a || !b) return 1; while (*a && *b) { /* first time preallocate macro_equal_buf, next time only reset position to start */ cstr_reset(¯o_equal_buf); TOK_GET(&t, &a, &cv); cstr_cat(¯o_equal_buf, get_tok_str(t, &cv), 0); TOK_GET(&t, &b, &cv); if (strcmp(macro_equal_buf.data, get_tok_str(t, &cv))) return 0; } return !(*a || *b); } /* defines handling */ ST_INLN void define_push(int v, int macro_type, int *str, Sym *first_arg) { Sym *s, *o; o = define_find(v); s = sym_push2(&define_stack, v, macro_type, 0); s->d = str; s->next = first_arg; table_ident[v - TOK_IDENT]->sym_define = s; if (o && !macro_is_equal(o->d, s->d)) tcc_warning("%s redefined", get_tok_str(v, NULL)); } /* undefined a define symbol. Its name is just set to zero */ ST_FUNC void define_undef(Sym *s) { int v = s->v; if (v >= TOK_IDENT && v < tok_ident) table_ident[v - TOK_IDENT]->sym_define = NULL; } ST_INLN Sym *define_find(int v) { v -= TOK_IDENT; if ((unsigned)v >= (unsigned)(tok_ident - TOK_IDENT)) return NULL; return table_ident[v]->sym_define; } /* free define stack until top reaches 'b' */ ST_FUNC void free_defines(Sym *b) { while (define_stack != b) { Sym *top = define_stack; define_stack = top->prev; tok_str_free_str(top->d); define_undef(top); sym_free(top); } /* restore remaining (-D or predefined) symbols if they were #undef'd in the file */ while (b) { int v = b->v; if (v >= TOK_IDENT && v < tok_ident) { Sym **d = &table_ident[v - TOK_IDENT]->sym_define; if (!*d) *d = b; } b = b->prev; } } /* label lookup */ ST_FUNC Sym *label_find(int v) { v -= TOK_IDENT; if ((unsigned)v >= (unsigned)(tok_ident - TOK_IDENT)) return NULL; return table_ident[v]->sym_label; } ST_FUNC Sym *label_push(Sym **ptop, int v, int flags) { Sym *s, **ps; s = sym_push2(ptop, v, 0, 0); s->r = flags; ps = &table_ident[v - TOK_IDENT]->sym_label; if (ptop == &global_label_stack) { /* modify the top most local identifier, so that sym_identifier will point to 's' when popped */ while (*ps != NULL) ps = &(*ps)->prev_tok; } s->prev_tok = *ps; *ps = s; return s; } /* pop labels until element last is reached. Look if any labels are undefined. Define symbols if '&&label' was used. */ ST_FUNC void label_pop(Sym **ptop, Sym *slast) { Sym *s, *s1; for(s = *ptop; s != slast; s = s1) { s1 = s->prev; if (s->r == LABEL_DECLARED) { tcc_warning("label '%s' declared but not used", get_tok_str(s->v, NULL)); } else if (s->r == LABEL_FORWARD) { tcc_error("label '%s' used but not defined", get_tok_str(s->v, NULL)); } else { if (s->c) { /* define corresponding symbol. A size of 1 is put. */ put_extern_sym(s, cur_text_section, s->jnext, 1); } } /* remove label */ table_ident[s->v - TOK_IDENT]->sym_label = s->prev_tok; sym_free(s); } *ptop = slast; } /* eval an expression for #if/#elif */ static int expr_preprocess(void) { int c, t; TokenString *str; str = tok_str_alloc(); while (tok != TOK_LINEFEED && tok != TOK_EOF) { next(); /* do macro subst */ if (tok == TOK_DEFINED) { next_nomacro(); t = tok; if (t == '(') next_nomacro(); c = define_find(tok) != 0; if (t == '(') next_nomacro(); tok = TOK_CINT; tokc.i = c; } else if (tok >= TOK_IDENT) { /* if undefined macro */ tok = TOK_CINT; tokc.i = 0; } tok_str_add_tok(str); } tok_str_add(str, -1); /* simulate end of file */ tok_str_add(str, 0); /* now evaluate C constant expression */ begin_macro(str, 1); next(); c = expr_const(); end_macro(); return c != 0; } /* parse after #define */ ST_FUNC void parse_define(void) { Sym *s, *first, **ps; int v, t, varg, is_vaargs, spc; int saved_parse_flags = parse_flags; v = tok; if (v < TOK_IDENT) tcc_error("invalid macro name '%s'", get_tok_str(tok, &tokc)); /* XXX: should check if same macro (ANSI) */ first = NULL; t = MACRO_OBJ; /* We have to parse the whole define as if not in asm mode, in particular no line comment with '#' must be ignored. Also for function macros the argument list must be parsed without '.' being an ID character. */ parse_flags = ((parse_flags & ~PARSE_FLAG_ASM_FILE) | PARSE_FLAG_SPACES); /* '(' must be just after macro definition for MACRO_FUNC */ next_nomacro_spc(); if (tok == '(') { set_idnum('.', 0); next_nomacro(); ps = &first; if (tok != ')') for (;;) { varg = tok; next_nomacro(); is_vaargs = 0; if (varg == TOK_DOTS) { varg = TOK___VA_ARGS__; is_vaargs = 1; } else if (tok == TOK_DOTS && gnu_ext) { is_vaargs = 1; next_nomacro(); } if (varg < TOK_IDENT) bad_list: tcc_error("bad macro parameter list"); s = sym_push2(&define_stack, varg | SYM_FIELD, is_vaargs, 0); *ps = s; ps = &s->next; if (tok == ')') break; if (tok != ',' || is_vaargs) goto bad_list; next_nomacro(); } next_nomacro_spc(); t = MACRO_FUNC; } tokstr_buf.len = 0; spc = 2; parse_flags |= PARSE_FLAG_ACCEPT_STRAYS | PARSE_FLAG_SPACES | PARSE_FLAG_LINEFEED; /* The body of a macro definition should be parsed such that identifiers are parsed like the file mode determines (i.e. with '.' being an ID character in asm mode). But '#' should be retained instead of regarded as line comment leader, so still don't set ASM_FILE in parse_flags. */ set_idnum('.', (saved_parse_flags & PARSE_FLAG_ASM_FILE) ? IS_ID : 0); while (tok != TOK_LINEFEED && tok != TOK_EOF) { /* remove spaces around ## and after '#' */ if (TOK_TWOSHARPS == tok) { if (2 == spc) goto bad_twosharp; if (1 == spc) --tokstr_buf.len; spc = 3; tok = TOK_PPJOIN; } else if ('#' == tok) { spc = 4; } else if (check_space(tok, &spc)) { goto skip; } tok_str_add2(&tokstr_buf, tok, &tokc); skip: next_nomacro_spc(); } parse_flags = saved_parse_flags; if (spc == 1) --tokstr_buf.len; /* remove trailing space */ tok_str_add(&tokstr_buf, 0); if (3 == spc) bad_twosharp: tcc_error("'##' cannot appear at either end of macro"); define_push(v, t, tok_str_dup(&tokstr_buf), first); } static CachedInclude *search_cached_include(TCCState *s1, const char *filename, int add) { const unsigned char *s; unsigned int h; CachedInclude *e; int i; h = TOK_HASH_INIT; s = (unsigned char *) filename; while (*s) { #ifdef _WIN32 h = TOK_HASH_FUNC(h, toup(*s)); #else h = TOK_HASH_FUNC(h, *s); #endif s++; } h &= (CACHED_INCLUDES_HASH_SIZE - 1); i = s1->cached_includes_hash[h]; for(;;) { if (i == 0) break; e = s1->cached_includes[i - 1]; if (0 == PATHCMP(e->filename, filename)) return e; i = e->hash_next; } if (!add) return NULL; e = tcc_malloc(sizeof(CachedInclude) + strlen(filename)); strcpy(e->filename, filename); e->ifndef_macro = e->once = 0; dynarray_add(&s1->cached_includes, &s1->nb_cached_includes, e); /* add in hash table */ e->hash_next = s1->cached_includes_hash[h]; s1->cached_includes_hash[h] = s1->nb_cached_includes; #ifdef INC_DEBUG printf("adding cached '%s'\n", filename); #endif return e; } static void pragma_parse(TCCState *s1) { next_nomacro(); if (tok == TOK_push_macro || tok == TOK_pop_macro) { int t = tok, v; Sym *s; if (next(), tok != '(') goto pragma_err; if (next(), tok != TOK_STR) goto pragma_err; v = tok_alloc(tokc.str.data, tokc.str.size - 1)->tok; if (next(), tok != ')') goto pragma_err; if (t == TOK_push_macro) { while (NULL == (s = define_find(v))) define_push(v, 0, NULL, NULL); s->type.ref = s; /* set push boundary */ } else { for (s = define_stack; s; s = s->prev) if (s->v == v && s->type.ref == s) { s->type.ref = NULL; break; } } if (s) table_ident[v - TOK_IDENT]->sym_define = s->d ? s : NULL; else tcc_warning("unbalanced #pragma pop_macro"); pp_debug_tok = t, pp_debug_symv = v; } else if (tok == TOK_once) { search_cached_include(s1, file->filename, 1)->once = pp_once; } else if (s1->ppfp) { /* tcc -E: keep pragmas below unchanged */ unget_tok(' '); unget_tok(TOK_PRAGMA); unget_tok('#'); unget_tok(TOK_LINEFEED); } else if (tok == TOK_pack) { /* This may be: #pragma pack(1) // set #pragma pack() // reset to default #pragma pack(push,1) // push & set #pragma pack(pop) // restore previous */ next(); skip('('); if (tok == TOK_ASM_pop) { next(); if (s1->pack_stack_ptr <= s1->pack_stack) { stk_error: tcc_error("out of pack stack"); } s1->pack_stack_ptr--; } else { int val = 0; if (tok != ')') { if (tok == TOK_ASM_push) { next(); if (s1->pack_stack_ptr >= s1->pack_stack + PACK_STACK_SIZE - 1) goto stk_error; s1->pack_stack_ptr++; skip(','); } if (tok != TOK_CINT) goto pragma_err; val = tokc.i; if (val < 1 || val > 16 || (val & (val - 1)) != 0) goto pragma_err; next(); } *s1->pack_stack_ptr = val; } if (tok != ')') goto pragma_err; } else if (tok == TOK_comment) { char *file; next(); skip('('); if (tok != TOK_lib) goto pragma_warn; next(); skip(','); if (tok != TOK_STR) goto pragma_err; file = tcc_strdup((char *)tokc.str.data); dynarray_add(&s1->pragma_libs, &s1->nb_pragma_libs, file); next(); if (tok != ')') goto pragma_err; } else { pragma_warn: if (s1->warn_unsupported) tcc_warning("#pragma %s is ignored", get_tok_str(tok, &tokc)); } return; pragma_err: tcc_error("malformed #pragma directive"); return; } /* is_bof is true if first non space token at beginning of file */ ST_FUNC void preprocess(int is_bof) { TCCState *s1 = tcc_state; int i, c, n, saved_parse_flags; char buf[1024], *q; Sym *s; saved_parse_flags = parse_flags; parse_flags = PARSE_FLAG_PREPROCESS | PARSE_FLAG_TOK_NUM | PARSE_FLAG_TOK_STR | PARSE_FLAG_LINEFEED | (parse_flags & PARSE_FLAG_ASM_FILE) ; next_nomacro(); redo: switch(tok) { case TOK_DEFINE: pp_debug_tok = tok; next_nomacro(); pp_debug_symv = tok; parse_define(); break; case TOK_UNDEF: pp_debug_tok = tok; next_nomacro(); pp_debug_symv = tok; s = define_find(tok); /* undefine symbol by putting an invalid name */ if (s) define_undef(s); break; case TOK_INCLUDE: case TOK_INCLUDE_NEXT: ch = file->buf_ptr[0]; /* XXX: incorrect if comments : use next_nomacro with a special mode */ skip_spaces(); if (ch == '<') { c = '>'; goto read_name; } else if (ch == '\"') { c = ch; read_name: inp(); q = buf; while (ch != c && ch != '\n' && ch != CH_EOF) { if ((q - buf) < sizeof(buf) - 1) *q++ = ch; if (ch == '\\') { if (handle_stray_noerror() == 0) --q; } else inp(); } *q = '\0'; minp(); #if 0 /* eat all spaces and comments after include */ /* XXX: slightly incorrect */ while (ch1 != '\n' && ch1 != CH_EOF) inp(); #endif } else { int len; /* computed #include : concatenate everything up to linefeed, the result must be one of the two accepted forms. Don't convert pp-tokens to tokens here. */ parse_flags = (PARSE_FLAG_PREPROCESS | PARSE_FLAG_LINEFEED | (parse_flags & PARSE_FLAG_ASM_FILE)); next(); buf[0] = '\0'; while (tok != TOK_LINEFEED) { pstrcat(buf, sizeof(buf), get_tok_str(tok, &tokc)); next(); } len = strlen(buf); /* check syntax and remove '<>|""' */ if ((len < 2 || ((buf[0] != '"' || buf[len-1] != '"') && (buf[0] != '<' || buf[len-1] != '>')))) tcc_error("'#include' expects \"FILENAME\" or <FILENAME>"); c = buf[len-1]; memmove(buf, buf + 1, len - 2); buf[len - 2] = '\0'; } if (s1->include_stack_ptr >= s1->include_stack + INCLUDE_STACK_SIZE) tcc_error("#include recursion too deep"); /* store current file in stack, but increment stack later below */ *s1->include_stack_ptr = file; i = tok == TOK_INCLUDE_NEXT ? file->include_next_index : 0; n = 2 + s1->nb_include_paths + s1->nb_sysinclude_paths; for (; i < n; ++i) { char buf1[sizeof file->filename]; CachedInclude *e; const char *path; if (i == 0) { /* check absolute include path */ if (!IS_ABSPATH(buf)) continue; buf1[0] = 0; } else if (i == 1) { /* search in file's dir if "header.h" */ if (c != '\"') continue; /* https://savannah.nongnu.org/bugs/index.php?50847 */ path = file->filename2; pstrncpy(buf1, path, tcc_basename(path) - path); } else { /* search in all the include paths */ int j = i - 2, k = j - s1->nb_include_paths; path = k < 0 ? s1->include_paths[j] : s1->sysinclude_paths[k]; pstrcpy(buf1, sizeof(buf1), path); pstrcat(buf1, sizeof(buf1), "/"); } pstrcat(buf1, sizeof(buf1), buf); e = search_cached_include(s1, buf1, 0); if (e && (define_find(e->ifndef_macro) || e->once == pp_once)) { /* no need to parse the include because the 'ifndef macro' is defined (or had #pragma once) */ #ifdef INC_DEBUG printf("%s: skipping cached %s\n", file->filename, buf1); #endif goto include_done; } if (tcc_open(s1, buf1) < 0) continue; file->include_next_index = i + 1; #ifdef INC_DEBUG printf("%s: including %s\n", file->prev->filename, file->filename); #endif /* update target deps */ dynarray_add(&s1->target_deps, &s1->nb_target_deps, tcc_strdup(buf1)); /* push current file in stack */ ++s1->include_stack_ptr; /* add include file debug info */ if (s1->do_debug) put_stabs(file->filename, N_BINCL, 0, 0, 0); tok_flags |= TOK_FLAG_BOF | TOK_FLAG_BOL; ch = file->buf_ptr[0]; goto the_end; } tcc_error("include file '%s' not found", buf); include_done: break; case TOK_IFNDEF: c = 1; goto do_ifdef; case TOK_IF: c = expr_preprocess(); goto do_if; case TOK_IFDEF: c = 0; do_ifdef: next_nomacro(); if (tok < TOK_IDENT) tcc_error("invalid argument for '#if%sdef'", c ? "n" : ""); if (is_bof) { if (c) { #ifdef INC_DEBUG printf("#ifndef %s\n", get_tok_str(tok, NULL)); #endif file->ifndef_macro = tok; } } c = (define_find(tok) != 0) ^ c; do_if: if (s1->ifdef_stack_ptr >= s1->ifdef_stack + IFDEF_STACK_SIZE) tcc_error("memory full (ifdef)"); *s1->ifdef_stack_ptr++ = c; goto test_skip; case TOK_ELSE: if (s1->ifdef_stack_ptr == s1->ifdef_stack) tcc_error("#else without matching #if"); if (s1->ifdef_stack_ptr[-1] & 2) tcc_error("#else after #else"); c = (s1->ifdef_stack_ptr[-1] ^= 3); goto test_else; case TOK_ELIF: if (s1->ifdef_stack_ptr == s1->ifdef_stack) tcc_error("#elif without matching #if"); c = s1->ifdef_stack_ptr[-1]; if (c > 1) tcc_error("#elif after #else"); /* last #if/#elif expression was true: we skip */ if (c == 1) { c = 0; } else { c = expr_preprocess(); s1->ifdef_stack_ptr[-1] = c; } test_else: if (s1->ifdef_stack_ptr == file->ifdef_stack_ptr + 1) file->ifndef_macro = 0; test_skip: if (!(c & 1)) { preprocess_skip(); is_bof = 0; goto redo; } break; case TOK_ENDIF: if (s1->ifdef_stack_ptr <= file->ifdef_stack_ptr) tcc_error("#endif without matching #if"); s1->ifdef_stack_ptr--; /* '#ifndef macro' was at the start of file. Now we check if an '#endif' is exactly at the end of file */ if (file->ifndef_macro && s1->ifdef_stack_ptr == file->ifdef_stack_ptr) { file->ifndef_macro_saved = file->ifndef_macro; /* need to set to zero to avoid false matches if another #ifndef at middle of file */ file->ifndef_macro = 0; while (tok != TOK_LINEFEED) next_nomacro(); tok_flags |= TOK_FLAG_ENDIF; goto the_end; } break; case TOK_PPNUM: n = strtoul((char*)tokc.str.data, &q, 10); goto _line_num; case TOK_LINE: next(); if (tok != TOK_CINT) _line_err: tcc_error("wrong #line format"); n = tokc.i; _line_num: next(); if (tok != TOK_LINEFEED) { if (tok == TOK_STR) pstrcpy(file->filename, sizeof(file->filename), (char *)tokc.str.data); else if (parse_flags & PARSE_FLAG_ASM_FILE) break; else goto _line_err; --n; } if (file->fd > 0) total_lines += file->line_num - n; file->line_num = n; if (s1->do_debug) put_stabs(file->filename, N_BINCL, 0, 0, 0); break; case TOK_ERROR: case TOK_WARNING: c = tok; ch = file->buf_ptr[0]; skip_spaces(); q = buf; while (ch != '\n' && ch != CH_EOF) { if ((q - buf) < sizeof(buf) - 1) *q++ = ch; if (ch == '\\') { if (handle_stray_noerror() == 0) --q; } else inp(); } *q = '\0'; if (c == TOK_ERROR) tcc_error("#error %s", buf); else tcc_warning("#warning %s", buf); break; case TOK_PRAGMA: pragma_parse(s1); break; case TOK_LINEFEED: goto the_end; default: /* ignore gas line comment in an 'S' file. */ if (saved_parse_flags & PARSE_FLAG_ASM_FILE) goto ignore; if (tok == '!' && is_bof) /* '!' is ignored at beginning to allow C scripts. */ goto ignore; tcc_warning("Ignoring unknown preprocessing directive #%s", get_tok_str(tok, &tokc)); ignore: file->buf_ptr = parse_line_comment(file->buf_ptr - 1); goto the_end; } /* ignore other preprocess commands or #! for C scripts */ while (tok != TOK_LINEFEED) next_nomacro(); the_end: parse_flags = saved_parse_flags; } /* evaluate escape codes in a string. */ static void parse_escape_string(CString *outstr, const uint8_t *buf, int is_long) { int c, n; const uint8_t *p; p = buf; for(;;) { c = *p; if (c == '\0') break; if (c == '\\') { p++; /* escape */ c = *p; switch(c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': /* at most three octal digits */ n = c - '0'; p++; c = *p; if (isoct(c)) { n = n * 8 + c - '0'; p++; c = *p; if (isoct(c)) { n = n * 8 + c - '0'; p++; } } c = n; goto add_char_nonext; case 'x': case 'u': case 'U': p++; n = 0; for(;;) { c = *p; if (c >= 'a' && c <= 'f') c = c - 'a' + 10; else if (c >= 'A' && c <= 'F') c = c - 'A' + 10; else if (isnum(c)) c = c - '0'; else break; n = n * 16 + c; p++; } c = n; goto add_char_nonext; case 'a': c = '\a'; break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; case 'e': if (!gnu_ext) goto invalid_escape; c = 27; break; case '\'': case '\"': case '\\': case '?': break; default: invalid_escape: if (c >= '!' && c <= '~') tcc_warning("unknown escape sequence: \'\\%c\'", c); else tcc_warning("unknown escape sequence: \'\\x%x\'", c); break; } } p++; add_char_nonext: if (!is_long) cstr_ccat(outstr, c); else cstr_wccat(outstr, c); } /* add a trailing '\0' */ if (!is_long) cstr_ccat(outstr, '\0'); else cstr_wccat(outstr, '\0'); } static void parse_string(const char *s, int len) { uint8_t buf[1000], *p = buf; int is_long, sep; if ((is_long = *s == 'L')) ++s, --len; sep = *s++; len -= 2; if (len >= sizeof buf) p = tcc_malloc(len + 1); memcpy(p, s, len); p[len] = 0; cstr_reset(&tokcstr); parse_escape_string(&tokcstr, p, is_long); if (p != buf) tcc_free(p); if (sep == '\'') { int char_size; /* XXX: make it portable */ if (!is_long) char_size = 1; else char_size = sizeof(nwchar_t); if (tokcstr.size <= char_size) tcc_error("empty character constant"); if (tokcstr.size > 2 * char_size) tcc_warning("multi-character character constant"); if (!is_long) { tokc.i = *(int8_t *)tokcstr.data; tok = TOK_CCHAR; } else { tokc.i = *(nwchar_t *)tokcstr.data; tok = TOK_LCHAR; } } else { tokc.str.size = tokcstr.size; tokc.str.data = tokcstr.data; if (!is_long) tok = TOK_STR; else tok = TOK_LSTR; } } /* we use 64 bit numbers */ #define BN_SIZE 2 /* bn = (bn << shift) | or_val */ static void bn_lshift(unsigned int *bn, int shift, int or_val) { int i; unsigned int v; for(i=0;i<BN_SIZE;i++) { v = bn[i]; bn[i] = (v << shift) | or_val; or_val = v >> (32 - shift); } } static void bn_zero(unsigned int *bn) { int i; for(i=0;i<BN_SIZE;i++) { bn[i] = 0; } } /* parse number in null terminated string 'p' and return it in the current token */ static void parse_number(const char *p) { int b, t, shift, frac_bits, s, exp_val, ch; char *q; unsigned int bn[BN_SIZE]; double d; /* number */ q = token_buf; ch = *p++; t = ch; ch = *p++; *q++ = t; b = 10; if (t == '.') { goto float_frac_parse; } else if (t == '0') { if (ch == 'x' || ch == 'X') { q--; ch = *p++; b = 16; } else if (tcc_ext && (ch == 'b' || ch == 'B')) { q--; ch = *p++; b = 2; } } /* parse all digits. cannot check octal numbers at this stage because of floating point constants */ while (1) { if (ch >= 'a' && ch <= 'f') t = ch - 'a' + 10; else if (ch >= 'A' && ch <= 'F') t = ch - 'A' + 10; else if (isnum(ch)) t = ch - '0'; else break; if (t >= b) break; if (q >= token_buf + STRING_MAX_SIZE) { num_too_long: tcc_error("number too long"); } *q++ = ch; ch = *p++; } if (ch == '.' || ((ch == 'e' || ch == 'E') && b == 10) || ((ch == 'p' || ch == 'P') && (b == 16 || b == 2))) { if (b != 10) { /* NOTE: strtox should support that for hexa numbers, but non ISOC99 libcs do not support it, so we prefer to do it by hand */ /* hexadecimal or binary floats */ /* XXX: handle overflows */ *q = '\0'; if (b == 16) shift = 4; else shift = 1; bn_zero(bn); q = token_buf; while (1) { t = *q++; if (t == '\0') { break; } else if (t >= 'a') { t = t - 'a' + 10; } else if (t >= 'A') { t = t - 'A' + 10; } else { t = t - '0'; } bn_lshift(bn, shift, t); } frac_bits = 0; if (ch == '.') { ch = *p++; while (1) { t = ch; if (t >= 'a' && t <= 'f') { t = t - 'a' + 10; } else if (t >= 'A' && t <= 'F') { t = t - 'A' + 10; } else if (t >= '0' && t <= '9') { t = t - '0'; } else { break; } if (t >= b) tcc_error("invalid digit"); bn_lshift(bn, shift, t); frac_bits += shift; ch = *p++; } } if (ch != 'p' && ch != 'P') expect("exponent"); ch = *p++; s = 1; exp_val = 0; if (ch == '+') { ch = *p++; } else if (ch == '-') { s = -1; ch = *p++; } if (ch < '0' || ch > '9') expect("exponent digits"); while (ch >= '0' && ch <= '9') { exp_val = exp_val * 10 + ch - '0'; ch = *p++; } exp_val = exp_val * s; /* now we can generate the number */ /* XXX: should patch directly float number */ #if HAVE_FLOAT d = (double)bn[1] * 4294967296.0 + (double)bn[0]; #endif d = ldexp(d, exp_val - frac_bits); t = toup(ch); if (t == 'F') { ch = *p++; tok = TOK_CFLOAT; /* float : should handle overflow */ tokc.f = (float)d; } else if (t == 'L') { ch = *p++; #ifdef TCC_TARGET_PE tok = TOK_CDOUBLE; tokc.d = d; #else tok = TOK_CLDOUBLE; /* XXX: not large enough */ tokc.ld = (long double)d; #endif } else { tok = TOK_CDOUBLE; tokc.d = d; } } else { /* decimal floats */ if (ch == '.') { if (q >= token_buf + STRING_MAX_SIZE) goto num_too_long; *q++ = ch; ch = *p++; float_frac_parse: while (ch >= '0' && ch <= '9') { if (q >= token_buf + STRING_MAX_SIZE) goto num_too_long; *q++ = ch; ch = *p++; } } if (ch == 'e' || ch == 'E') { if (q >= token_buf + STRING_MAX_SIZE) goto num_too_long; *q++ = ch; ch = *p++; if (ch == '-' || ch == '+') { if (q >= token_buf + STRING_MAX_SIZE) goto num_too_long; *q++ = ch; ch = *p++; } if (ch < '0' || ch > '9') expect("exponent digits"); while (ch >= '0' && ch <= '9') { if (q >= token_buf + STRING_MAX_SIZE) goto num_too_long; *q++ = ch; ch = *p++; } } *q = '\0'; t = toup(ch); errno = 0; if (t == 'F') { ch = *p++; tok = TOK_CFLOAT; tokc.f = strtof(token_buf, NULL); } else if (t == 'L') { ch = *p++; #ifdef TCC_TARGET_PE tok = TOK_CDOUBLE; tokc.d = strtod(token_buf, NULL); #else tok = TOK_CLDOUBLE; #if HAVE_FLOAT tokc.ld = strtold(token_buf, NULL); #endif #endif } else { tok = TOK_CDOUBLE; #if HAVE_FLOAT tokc.d = strtod(token_buf, NULL); #endif } } } else { unsigned long long n, n1; int lcount, ucount, must_64bit; const char *p1; /* integer number */ *q = '\0'; q = token_buf; if (b == 10 && *q == '0') { b = 8; q++; } n = 0; while(1) { t = *q++; /* no need for checks except for base 10 / 8 errors */ if (t == '\0') break; else if (t >= 'a') t = t - 'a' + 10; else if (t >= 'A') t = t - 'A' + 10; else t = t - '0'; if (t >= b) tcc_error("invalid digit"); n1 = n; n = n * b + t; #if HAVE_LONG_LONG /* detect overflow */ /* XXX: this test is not reliable */ if (n < n1) tcc_error("integer constant overflow"); #endif } /* Determine the characteristics (unsigned and/or 64bit) the type of the constant must have according to the constant suffix(es) */ lcount = ucount = must_64bit = 0; p1 = p; for(;;) { t = toup(ch); if (t == 'L') { if (lcount >= 2) tcc_error("three 'l's in integer constant"); if (lcount && *(p - 1) != ch) tcc_error("incorrect integer suffix: %s", p1); lcount++; #if !defined TCC_TARGET_X86_64 || defined TCC_TARGET_PE if (lcount == 2) #endif must_64bit = 1; ch = *p++; } else if (t == 'U') { if (ucount >= 1) tcc_error("two 'u's in integer constant"); ucount++; ch = *p++; } else { break; } } #if HAVE_LONG_LONG /* Whether 64 bits are needed to hold the constant's value */ if (n & 0xffffffff00000000LL || must_64bit) { tok = TOK_CLLONG; n1 = n >> 32; } else #endif { tok = TOK_CINT; n1 = n; } /* Whether type must be unsigned to hold the constant's value */ if (ucount || ((n1 >> 31) && (b != 10))) { if (tok == TOK_CLLONG) tok = TOK_CULLONG; else tok = TOK_CUINT; /* If decimal and no unsigned suffix, bump to 64 bits or throw error */ } else if (n1 >> 31) { if (tok == TOK_CINT) tok = TOK_CLLONG; else tcc_error("integer constant overflow"); } tokc.i = n; } if (ch) tcc_error("invalid number\n"); } #define PARSE2(c1, tok1, c2, tok2) \ case c1: \ PEEKC(c, p); \ if (c == c2) { \ p++; \ tok = tok2; \ } else { \ tok = tok1; \ } \ break; /* return next token without macro substitution */ static inline void next_nomacro1(void) { int t, c, is_long, len; TokenSym *ts; uint8_t *p, *p1; unsigned int h; p = file->buf_ptr; redo_no_start: c = *p; switch(c) { case ' ': case '\t': tok = c; p++; if (parse_flags & PARSE_FLAG_SPACES) goto keep_tok_flags; while (isidnum_table[*p - CH_EOF] & IS_SPC) ++p; goto redo_no_start; case '\f': case '\v': case '\r': p++; goto redo_no_start; case '\\': /* first look if it is in fact an end of buffer */ c = handle_stray1(p); p = file->buf_ptr; if (c == '\\') goto parse_simple; if (c != CH_EOF) goto redo_no_start; { TCCState *s1 = tcc_state; if ((parse_flags & PARSE_FLAG_LINEFEED) && !(tok_flags & TOK_FLAG_EOF)) { tok_flags |= TOK_FLAG_EOF; tok = TOK_LINEFEED; goto keep_tok_flags; } else if (!(parse_flags & PARSE_FLAG_PREPROCESS)) { tok = TOK_EOF; } else if (s1->ifdef_stack_ptr != file->ifdef_stack_ptr) { tcc_error("missing #endif"); } else if (s1->include_stack_ptr == s1->include_stack) { /* no include left : end of file. */ tok = TOK_EOF; } else { tok_flags &= ~TOK_FLAG_EOF; /* pop include file */ /* test if previous '#endif' was after a #ifdef at start of file */ if (tok_flags & TOK_FLAG_ENDIF) { #ifdef INC_DEBUG printf("#endif %s\n", get_tok_str(file->ifndef_macro_saved, NULL)); #endif search_cached_include(s1, file->filename, 1) ->ifndef_macro = file->ifndef_macro_saved; tok_flags &= ~TOK_FLAG_ENDIF; } /* add end of include file debug info */ if (tcc_state->do_debug) { put_stabd(N_EINCL, 0, 0); } /* pop include stack */ tcc_close(); s1->include_stack_ptr--; p = file->buf_ptr; goto redo_no_start; } } break; case '\n': file->line_num++; tok_flags |= TOK_FLAG_BOL; p++; maybe_newline: if (0 == (parse_flags & PARSE_FLAG_LINEFEED)) goto redo_no_start; tok = TOK_LINEFEED; goto keep_tok_flags; case '#': /* XXX: simplify */ PEEKC(c, p); if ((tok_flags & TOK_FLAG_BOL) && (parse_flags & PARSE_FLAG_PREPROCESS)) { file->buf_ptr = p; preprocess(tok_flags & TOK_FLAG_BOF); p = file->buf_ptr; goto maybe_newline; } else { if (c == '#') { p++; tok = TOK_TWOSHARPS; } else { if (parse_flags & PARSE_FLAG_ASM_FILE) { p = parse_line_comment(p - 1); goto redo_no_start; } else { tok = '#'; } } } break; /* dollar is allowed to start identifiers when not parsing asm */ case '$': if (!(isidnum_table[c - CH_EOF] & IS_ID) || (parse_flags & PARSE_FLAG_ASM_FILE)) goto parse_simple; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': parse_ident_fast: p1 = p; h = TOK_HASH_INIT; h = TOK_HASH_FUNC(h, c); while (c = *++p, isidnum_table[c - CH_EOF] & (IS_ID|IS_NUM)) h = TOK_HASH_FUNC(h, c); len = p - p1; if (c != '\\') { TokenSym **pts; /* fast case : no stray found, so we have the full token and we have already hashed it */ h &= (TOK_HASH_SIZE - 1); pts = &hash_ident[h]; for(;;) { ts = *pts; if (!ts) break; if (ts->len == len && !memcmp(ts->str, p1, len)) goto token_found; pts = &(ts->hash_next); } ts = tok_alloc_new(pts, (char *) p1, len); token_found: ; } else { /* slower case */ cstr_reset(&tokcstr); cstr_cat(&tokcstr, (char *) p1, len); p--; PEEKC(c, p); parse_ident_slow: while (isidnum_table[c - CH_EOF] & (IS_ID|IS_NUM)) { cstr_ccat(&tokcstr, c); PEEKC(c, p); } ts = tok_alloc(tokcstr.data, tokcstr.size); } tok = ts->tok; break; case 'L': t = p[1]; if (t != '\\' && t != '\'' && t != '\"') { /* fast case */ goto parse_ident_fast; } else { PEEKC(c, p); if (c == '\'' || c == '\"') { is_long = 1; goto str_const; } else { cstr_reset(&tokcstr); cstr_ccat(&tokcstr, 'L'); goto parse_ident_slow; } } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': t = c; PEEKC(c, p); /* after the first digit, accept digits, alpha, '.' or sign if prefixed by 'eEpP' */ parse_num: cstr_reset(&tokcstr); for(;;) { cstr_ccat(&tokcstr, t); if (!((isidnum_table[c - CH_EOF] & (IS_ID|IS_NUM)) || c == '.' || ((c == '+' || c == '-') && (((t == 'e' || t == 'E') && !(parse_flags & PARSE_FLAG_ASM_FILE /* 0xe+1 is 3 tokens in asm */ && ((char*)tokcstr.data)[0] == '0' && toup(((char*)tokcstr.data)[1]) == 'X')) || t == 'p' || t == 'P')))) break; t = c; PEEKC(c, p); } /* We add a trailing '\0' to ease parsing */ cstr_ccat(&tokcstr, '\0'); tokc.str.size = tokcstr.size; tokc.str.data = tokcstr.data; tok = TOK_PPNUM; break; case '.': /* special dot handling because it can also start a number */ PEEKC(c, p); if (isnum(c)) { t = '.'; goto parse_num; } else if ((isidnum_table['.' - CH_EOF] & IS_ID) && (isidnum_table[c - CH_EOF] & (IS_ID|IS_NUM))) { *--p = c = '.'; goto parse_ident_fast; } else if (c == '.') { PEEKC(c, p); if (c == '.') { p++; tok = TOK_DOTS; } else { *--p = '.'; /* may underflow into file->unget[] */ tok = '.'; } } else { tok = '.'; } break; case '\'': case '\"': is_long = 0; str_const: cstr_reset(&tokcstr); if (is_long) cstr_ccat(&tokcstr, 'L'); cstr_ccat(&tokcstr, c); p = parse_pp_string(p, c, &tokcstr); cstr_ccat(&tokcstr, c); cstr_ccat(&tokcstr, '\0'); tokc.str.size = tokcstr.size; tokc.str.data = tokcstr.data; tok = TOK_PPSTR; break; case '<': PEEKC(c, p); if (c == '=') { p++; tok = TOK_LE; } else if (c == '<') { PEEKC(c, p); if (c == '=') { p++; tok = TOK_A_SHL; } else { tok = TOK_SHL; } } else { tok = TOK_LT; } break; case '>': PEEKC(c, p); if (c == '=') { p++; tok = TOK_GE; } else if (c == '>') { PEEKC(c, p); if (c == '=') { p++; tok = TOK_A_SAR; } else { tok = TOK_SAR; } } else { tok = TOK_GT; } break; case '&': PEEKC(c, p); if (c == '&') { p++; tok = TOK_LAND; } else if (c == '=') { p++; tok = TOK_A_AND; } else { tok = '&'; } break; case '|': PEEKC(c, p); if (c == '|') { p++; tok = TOK_LOR; } else if (c == '=') { p++; tok = TOK_A_OR; } else { tok = '|'; } break; case '+': PEEKC(c, p); if (c == '+') { p++; tok = TOK_INC; } else if (c == '=') { p++; tok = TOK_A_ADD; } else { tok = '+'; } break; case '-': PEEKC(c, p); if (c == '-') { p++; tok = TOK_DEC; } else if (c == '=') { p++; tok = TOK_A_SUB; } else if (c == '>') { p++; tok = TOK_ARROW; } else { tok = '-'; } break; PARSE2('!', '!', '=', TOK_NE) PARSE2('=', '=', '=', TOK_EQ) PARSE2('*', '*', '=', TOK_A_MUL) PARSE2('%', '%', '=', TOK_A_MOD) PARSE2('^', '^', '=', TOK_A_XOR) /* comments or operator */ case '/': PEEKC(c, p); if (c == '*') { p = parse_comment(p); /* comments replaced by a blank */ tok = ' '; goto keep_tok_flags; } else if (c == '/') { p = parse_line_comment(p); tok = ' '; goto keep_tok_flags; } else if (c == '=') { p++; tok = TOK_A_DIV; } else { tok = '/'; } break; /* simple tokens */ case '(': case ')': case '[': case ']': case '{': case '}': case ',': case ';': case ':': case '?': case '~': case '@': /* only used in assembler */ parse_simple: tok = c; p++; break; default: if (c >= 0x80 && c <= 0xFF) /* utf8 identifiers */ goto parse_ident_fast; if (parse_flags & PARSE_FLAG_ASM_FILE) goto parse_simple; tcc_error("unrecognized character \\x%02x", c); break; } tok_flags = 0; keep_tok_flags: file->buf_ptr = p; #if defined(PARSE_DEBUG) printf("token = %s\n", get_tok_str(tok, &tokc)); #endif } /* return next token without macro substitution. Can read input from macro_ptr buffer */ static void next_nomacro_spc(void) { if (macro_ptr) { redo: tok = *macro_ptr; if (tok) { TOK_GET(&tok, ¯o_ptr, &tokc); if (tok == TOK_LINENUM) { file->line_num = tokc.i; goto redo; } } } else { next_nomacro1(); } } ST_FUNC void next_nomacro(void) { do { next_nomacro_spc(); } while (tok < 256 && (isidnum_table[tok - CH_EOF] & IS_SPC)); } static void macro_subst( TokenString *tok_str, Sym **nested_list, const int *macro_str, int can_read_stream ); /* substitute arguments in replacement lists in macro_str by the values in args (field d) and return allocated string */ static int *macro_arg_subst(Sym **nested_list, const int *macro_str, Sym *args) { int t, t0, t1, spc; const int *st; Sym *s; CValue cval; TokenString str; CString cstr; tok_str_new(&str); t0 = t1 = 0; while(1) { TOK_GET(&t, ¯o_str, &cval); if (!t) break; if (t == '#') { /* stringize */ TOK_GET(&t, ¯o_str, &cval); if (!t) goto bad_stringy; s = sym_find2(args, t); if (s) { cstr_new(&cstr); cstr_ccat(&cstr, '\"'); st = s->d; spc = 0; while (*st) { TOK_GET(&t, &st, &cval); if (t != TOK_PLCHLDR && t != TOK_NOSUBST && 0 == check_space(t, &spc)) { const char *s = get_tok_str(t, &cval); while (*s) { if (t == TOK_PPSTR && *s != '\'') add_char(&cstr, *s); else cstr_ccat(&cstr, *s); ++s; } } } cstr.size -= spc; cstr_ccat(&cstr, '\"'); cstr_ccat(&cstr, '\0'); #ifdef PP_DEBUG printf("\nstringize: <%s>\n", (char *)cstr.data); #endif /* add string */ cval.str.size = cstr.size; cval.str.data = cstr.data; tok_str_add2(&str, TOK_PPSTR, &cval); cstr_free(&cstr); } else { bad_stringy: expect("macro parameter after '#'"); } } else if (t >= TOK_IDENT) { s = sym_find2(args, t); if (s) { int l0 = str.len; st = s->d; /* if '##' is present before or after, no arg substitution */ if (*macro_str == TOK_PPJOIN || t1 == TOK_PPJOIN) { /* special case for var arg macros : ## eats the ',' if empty VA_ARGS variable. */ if (t1 == TOK_PPJOIN && t0 == ',' && gnu_ext && s->type.t) { if (*st == 0) { /* suppress ',' '##' */ str.len -= 2; } else { /* suppress '##' and add variable */ str.len--; goto add_var; } } else { for(;;) { int t1; TOK_GET(&t1, &st, &cval); if (!t1) break; tok_str_add2(&str, t1, &cval); } } } else { add_var: /* NOTE: the stream cannot be read when macro substituting an argument */ macro_subst(&str, nested_list, st, 0); } if (str.len == l0) /* expanded to empty string */ tok_str_add(&str, TOK_PLCHLDR); } else { tok_str_add(&str, t); } } else { tok_str_add2(&str, t, &cval); } t0 = t1, t1 = t; } tok_str_add(&str, 0); return str.str; } static char const ab_month_name[12][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; /* peek or read [ws_str == NULL] next token from function macro call, walking up macro levels up to the file if necessary */ static int next_argstream(Sym **nested_list, int can_read_stream, TokenString *ws_str) { int t; const int *p; Sym *sa; for (;;) { if (macro_ptr) { p = macro_ptr, t = *p; if (ws_str) { while (is_space(t) || TOK_LINEFEED == t || TOK_PLCHLDR == t) tok_str_add(ws_str, t), t = *++p; } if (t == 0 && can_read_stream) { end_macro(); /* also, end of scope for nested defined symbol */ sa = *nested_list; while (sa && sa->v == 0) sa = sa->prev; if (sa) sa->v = 0; continue; } } else { ch = handle_eob(); if (ws_str) { while (is_space(ch) || ch == '\n' || ch == '/') { if (ch == '/') { int c; uint8_t *p = file->buf_ptr; PEEKC(c, p); if (c == '*') { p = parse_comment(p); file->buf_ptr = p - 1; } else if (c == '/') { p = parse_line_comment(p); file->buf_ptr = p - 1; } else break; ch = ' '; } if (!(ch == '\f' || ch == '\v' || ch == '\r')) tok_str_add(ws_str, ch); cinp(); } } t = ch; } if (ws_str) return t; next_nomacro_spc(); return tok; } } /* do macro substitution of current token with macro 's' and add result to (tok_str,tok_len). 'nested_list' is the list of all macros we got inside to avoid recursing. Return non zero if no substitution needs to be done */ static int macro_subst_tok( TokenString *tok_str, Sym **nested_list, Sym *s, int can_read_stream) { Sym *args, *sa, *sa1; int parlevel, *mstr, t, t1, spc; TokenString str; char *cstrval; CValue cval; CString cstr; char buf[32]; /* if symbol is a macro, prepare substitution */ /* special macros */ if (tok == TOK___LINE__) { snprintf(buf, sizeof(buf), "%d", file->line_num); cstrval = buf; t1 = TOK_PPNUM; goto add_cstr1; } else if (tok == TOK___FILE__) { cstrval = file->filename; goto add_cstr; } else if (tok == TOK___DATE__ || tok == TOK___TIME__) { time_t ti; struct tm *tm; time(&ti); tm = localtime(&ti); if (tok == TOK___DATE__) { snprintf(buf, sizeof(buf), "%s %2d %d", ab_month_name[tm->tm_mon], tm->tm_mday, tm->tm_year + 1900); } else { snprintf(buf, sizeof(buf), "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); } cstrval = buf; add_cstr: t1 = TOK_STR; add_cstr1: cstr_new(&cstr); cstr_cat(&cstr, cstrval, 0); cval.str.size = cstr.size; cval.str.data = cstr.data; tok_str_add2(tok_str, t1, &cval); cstr_free(&cstr); } else { int saved_parse_flags = parse_flags; mstr = s->d; if (s->type.t == MACRO_FUNC) { /* whitespace between macro name and argument list */ TokenString ws_str; tok_str_new(&ws_str); spc = 0; parse_flags |= PARSE_FLAG_SPACES | PARSE_FLAG_LINEFEED | PARSE_FLAG_ACCEPT_STRAYS; /* get next token from argument stream */ t = next_argstream(nested_list, can_read_stream, &ws_str); if (t != '(') { /* not a macro substitution after all, restore the * macro token plus all whitespace we've read. * whitespace is intentionally not merged to preserve * newlines. */ parse_flags = saved_parse_flags; tok_str_add(tok_str, tok); if (parse_flags & PARSE_FLAG_SPACES) { int i; for (i = 0; i < ws_str.len; i++) tok_str_add(tok_str, ws_str.str[i]); } tok_str_free_str(ws_str.str); return 0; } else { tok_str_free_str(ws_str.str); } do { next_nomacro(); /* eat '(' */ } while (tok == TOK_PLCHLDR); /* argument macro */ args = NULL; sa = s->next; /* NOTE: empty args are allowed, except if no args */ for(;;) { do { next_argstream(nested_list, can_read_stream, NULL); } while (is_space(tok) || TOK_LINEFEED == tok); empty_arg: /* handle '()' case */ if (!args && !sa && tok == ')') break; if (!sa) tcc_error("macro '%s' used with too many args", get_tok_str(s->v, 0)); tok_str_new(&str); parlevel = spc = 0; /* NOTE: non zero sa->t indicates VA_ARGS */ while ((parlevel > 0 || (tok != ')' && (tok != ',' || sa->type.t)))) { if (tok == TOK_EOF || tok == 0) break; if (tok == '(') parlevel++; else if (tok == ')') parlevel--; if (tok == TOK_LINEFEED) tok = ' '; if (!check_space(tok, &spc)) tok_str_add2(&str, tok, &tokc); next_argstream(nested_list, can_read_stream, NULL); } if (parlevel) expect(")"); str.len -= spc; tok_str_add(&str, 0); sa1 = sym_push2(&args, sa->v & ~SYM_FIELD, sa->type.t, 0); sa1->d = str.str; sa = sa->next; if (tok == ')') { /* special case for gcc var args: add an empty var arg argument if it is omitted */ if (sa && sa->type.t && gnu_ext) goto empty_arg; break; } if (tok != ',') expect(","); } if (sa) { tcc_error("macro '%s' used with too few args", get_tok_str(s->v, 0)); } parse_flags = saved_parse_flags; /* now subst each arg */ mstr = macro_arg_subst(nested_list, mstr, args); /* free memory */ sa = args; while (sa) { sa1 = sa->prev; tok_str_free_str(sa->d); sym_free(sa); sa = sa1; } } sym_push2(nested_list, s->v, 0, 0); parse_flags = saved_parse_flags; macro_subst(tok_str, nested_list, mstr, can_read_stream | 2); /* pop nested defined symbol */ sa1 = *nested_list; *nested_list = sa1->prev; sym_free(sa1); if (mstr != s->d) tok_str_free_str(mstr); } return 0; } static int paste_tokens(int t1, CValue *v1, int t2, CValue *v2) { CString cstr; int n, ret = 1; cstr_new(&cstr); if (t1 != TOK_PLCHLDR) cstr_cat(&cstr, get_tok_str(t1, v1), -1); n = cstr.size; if (t2 != TOK_PLCHLDR) cstr_cat(&cstr, get_tok_str(t2, v2), -1); cstr_ccat(&cstr, '\0'); tcc_open_bf(tcc_state, ":paste:", cstr.size); memcpy(file->buffer, cstr.data, cstr.size); for (;;) { next_nomacro1(); if (0 == *file->buf_ptr) break; if (is_space(tok)) continue; tcc_warning("pasting \"%.*s\" and \"%s\" does not give a valid" " preprocessing token", n, cstr.data, (char*)cstr.data + n); ret = 0; break; } tcc_close(); //printf("paste <%s>\n", (char*)cstr.data); cstr_free(&cstr); return ret; } /* handle the '##' operator. Return NULL if no '##' seen. Otherwise return the resulting string (which must be freed). */ static inline int *macro_twosharps(const int *ptr0) { int t; CValue cval; TokenString macro_str1; int start_of_nosubsts = -1; const int *ptr; /* we search the first '##' */ for (ptr = ptr0;;) { TOK_GET(&t, &ptr, &cval); if (t == TOK_PPJOIN) break; if (t == 0) return NULL; } tok_str_new(¯o_str1); //tok_print(" $$$", ptr0); for (ptr = ptr0;;) { TOK_GET(&t, &ptr, &cval); if (t == 0) break; if (t == TOK_PPJOIN) continue; while (*ptr == TOK_PPJOIN) { int t1; CValue cv1; /* given 'a##b', remove nosubsts preceding 'a' */ if (start_of_nosubsts >= 0) macro_str1.len = start_of_nosubsts; /* given 'a##b', remove nosubsts preceding 'b' */ while ((t1 = *++ptr) == TOK_NOSUBST) ; if (t1 && t1 != TOK_PPJOIN) { TOK_GET(&t1, &ptr, &cv1); if (t != TOK_PLCHLDR || t1 != TOK_PLCHLDR) { if (paste_tokens(t, &cval, t1, &cv1)) { t = tok, cval = tokc; } else { tok_str_add2(¯o_str1, t, &cval); t = t1, cval = cv1; } } } } if (t == TOK_NOSUBST) { if (start_of_nosubsts < 0) start_of_nosubsts = macro_str1.len; } else { start_of_nosubsts = -1; } tok_str_add2(¯o_str1, t, &cval); } tok_str_add(¯o_str1, 0); //tok_print(" ###", macro_str1.str); return macro_str1.str; } /* do macro substitution of macro_str and add result to (tok_str,tok_len). 'nested_list' is the list of all macros we got inside to avoid recursing. */ static void macro_subst( TokenString *tok_str, Sym **nested_list, const int *macro_str, int can_read_stream ) { Sym *s; const int *ptr; int t, spc, nosubst; CValue cval; int *macro_str1 = NULL; /* first scan for '##' operator handling */ ptr = macro_str; spc = nosubst = 0; /* first scan for '##' operator handling */ if (can_read_stream) { macro_str1 = macro_twosharps(ptr); if (macro_str1) ptr = macro_str1; } while (1) { TOK_GET(&t, &ptr, &cval); if (t == 0) break; if (t >= TOK_IDENT && 0 == nosubst) { s = define_find(t); if (s == NULL) goto no_subst; /* if nested substitution, do nothing */ if (sym_find2(*nested_list, t)) { /* and mark it as TOK_NOSUBST, so it doesn't get subst'd again */ tok_str_add2(tok_str, TOK_NOSUBST, NULL); goto no_subst; } { TokenString str; str.str = (int*)ptr; begin_macro(&str, 2); tok = t; macro_subst_tok(tok_str, nested_list, s, can_read_stream); if (str.alloc == 3) { /* already finished by reading function macro arguments */ break; } ptr = macro_ptr; end_macro (); } spc = (tok_str->len && is_space(tok_last(tok_str->str, tok_str->str + tok_str->len))); } else { if (t == '\\' && !(parse_flags & PARSE_FLAG_ACCEPT_STRAYS)) tcc_error("stray '\\' in program"); no_subst: if (!check_space(t, &spc)) tok_str_add2(tok_str, t, &cval); nosubst = 0; if (t == TOK_NOSUBST) nosubst = 1; } } if (macro_str1) tok_str_free_str(macro_str1); } /* return next token with macro substitution */ ST_FUNC void next(void) { redo: if (parse_flags & PARSE_FLAG_SPACES) next_nomacro_spc(); else next_nomacro(); if (macro_ptr) { if (tok == TOK_NOSUBST || tok == TOK_PLCHLDR) { /* discard preprocessor markers */ goto redo; } else if (tok == 0) { /* end of macro or unget token string */ end_macro(); goto redo; } } else if (tok >= TOK_IDENT && (parse_flags & PARSE_FLAG_PREPROCESS)) { Sym *s; /* if reading from file, try to substitute macros */ s = define_find(tok); if (s) { Sym *nested_list = NULL; tokstr_buf.len = 0; macro_subst_tok(&tokstr_buf, &nested_list, s, 1); tok_str_add(&tokstr_buf, 0); begin_macro(&tokstr_buf, 2); goto redo; } } /* convert preprocessor tokens into C tokens */ if (tok == TOK_PPNUM) { if (parse_flags & PARSE_FLAG_TOK_NUM) parse_number((char *)tokc.str.data); } else if (tok == TOK_PPSTR) { if (parse_flags & PARSE_FLAG_TOK_STR) parse_string((char *)tokc.str.data, tokc.str.size - 1); } } /* push back current token and set current token to 'last_tok'. Only identifier case handled for labels. */ ST_INLN void unget_tok(int last_tok) { TokenString *str = tok_str_alloc(); tok_str_add2(str, tok, &tokc); tok_str_add(str, 0); begin_macro(str, 1); tok = last_tok; } ST_FUNC void preprocess_start(TCCState *s1) { char *buf; s1->include_stack_ptr = s1->include_stack; s1->ifdef_stack_ptr = s1->ifdef_stack; file->ifdef_stack_ptr = s1->ifdef_stack_ptr; pp_once++; pvtop = vtop = vstack - 1; s1->pack_stack[0] = 0; s1->pack_stack_ptr = s1->pack_stack; set_idnum('$', s1->dollars_in_identifiers ? IS_ID : 0); set_idnum('.', (parse_flags & PARSE_FLAG_ASM_FILE) ? IS_ID : 0); buf = tcc_malloc(3 + strlen(file->filename)); sprintf(buf, "\"%s\"", file->filename); tcc_define_symbol(s1, "__BASE_FILE__", buf); tcc_free(buf); if (s1->nb_cmd_include_files) { CString cstr; int i; cstr_new(&cstr); for (i = 0; i < s1->nb_cmd_include_files; i++) { cstr_cat(&cstr, "#include \"", -1); cstr_cat(&cstr, s1->cmd_include_files[i], -1); cstr_cat(&cstr, "\"\n", -1); } *s1->include_stack_ptr++ = file; tcc_open_bf(s1, "<command line>", cstr.size); memcpy(file->buffer, cstr.data, cstr.size); cstr_free(&cstr); } } ST_FUNC void tccpp_new(TCCState *s) { int i, c; const char *p, *r; /* might be used in error() before preprocess_start() */ s->include_stack_ptr = s->include_stack; /* init isid table */ for(i = CH_EOF; i<128; i++) set_idnum(i, is_space(i) ? IS_SPC : isid(i) ? IS_ID : isnum(i) ? IS_NUM : 0); for(i = 128; i<256; i++) set_idnum(i, IS_ID); /* init allocators */ tal_new(&toksym_alloc, TOKSYM_TAL_LIMIT, TOKSYM_TAL_SIZE); tal_new(&tokstr_alloc, TOKSTR_TAL_LIMIT, TOKSTR_TAL_SIZE); tal_new(&cstr_alloc, CSTR_TAL_LIMIT, CSTR_TAL_SIZE); memset(hash_ident, 0, TOK_HASH_SIZE * sizeof(TokenSym *)); cstr_new(&cstr_buf); cstr_realloc(&cstr_buf, STRING_MAX_SIZE); tok_str_new(&tokstr_buf); tok_str_realloc(&tokstr_buf, TOKSTR_MAX_SIZE); tok_ident = TOK_IDENT; p = tcc_keywords; while (*p) { r = p; for(;;) { c = *r++; if (c == '\0') break; } tok_alloc(p, r - p - 1); p = r; } } ST_FUNC void tccpp_delete(TCCState *s) { int i, n; /* free -D and compiler defines */ free_defines(NULL); /* cleanup from error/setjmp */ while (macro_stack) end_macro(); macro_ptr = NULL; while (file) tcc_close(); /* free tokens */ n = tok_ident - TOK_IDENT; for(i = 0; i < n; i++) tal_free(toksym_alloc, table_ident[i]); tcc_free(table_ident); table_ident = NULL; /* free static buffers */ cstr_free(&tokcstr); cstr_free(&cstr_buf); cstr_free(¯o_equal_buf); tok_str_free_str(tokstr_buf.str); /* free allocators */ tal_delete(toksym_alloc); toksym_alloc = NULL; tal_delete(tokstr_alloc); tokstr_alloc = NULL; tal_delete(cstr_alloc); cstr_alloc = NULL; } /* ------------------------------------------------------------------------- */ /* tcc -E [-P[1]] [-dD} support */ static void tok_print(const char *msg, const int *str) { FILE *fp; int t; CValue cval; fp = tcc_state->ppfp; if (!fp || !tcc_state->dflag) fp = stdout; fprintf(fp, "%s ", msg); while (str) { TOK_GET(&t, &str, &cval); if (!t) break; fprintf(fp,"%s", get_tok_str(t, &cval)); } fprintf(fp, "\n"); } static void pp_line(TCCState *s1, BufferedFile *f, int level) { int d = f->line_num - f->line_ref; if (s1->dflag & 4) return; if (s1->Pflag == LINE_MACRO_OUTPUT_FORMAT_NONE) { ; } else if (level == 0 && f->line_ref && d < 8) { while (d > 0) fputs("\n", s1->ppfp), --d; } else if (s1->Pflag == LINE_MACRO_OUTPUT_FORMAT_STD) { fprintf(s1->ppfp, "#line %d \"%s\"\n", f->line_num, f->filename); } else { fprintf(s1->ppfp, "# %d \"%s\"%s\n", f->line_num, f->filename, level > 0 ? " 1" : level < 0 ? " 2" : ""); } f->line_ref = f->line_num; } static void define_print(TCCState *s1, int v) { FILE *fp; Sym *s; s = define_find(v); if (NULL == s || NULL == s->d) return; fp = s1->ppfp; fprintf(fp, "#define %s", get_tok_str(v, NULL)); if (s->type.t == MACRO_FUNC) { Sym *a = s->next; fprintf(fp,"("); if (a) for (;;) { fprintf(fp,"%s", get_tok_str(a->v & ~SYM_FIELD, NULL)); if (!(a = a->next)) break; fprintf(fp,","); } fprintf(fp,")"); } tok_print("", s->d); } static void pp_debug_defines(TCCState *s1) { int v, t; const char *vs; FILE *fp; t = pp_debug_tok; if (t == 0) return; file->line_num--; pp_line(s1, file, 0); file->line_ref = ++file->line_num; fp = s1->ppfp; v = pp_debug_symv; vs = get_tok_str(v, NULL); if (t == TOK_DEFINE) { define_print(s1, v); } else if (t == TOK_UNDEF) { fprintf(fp, "#undef %s\n", vs); } else if (t == TOK_push_macro) { fprintf(fp, "#pragma push_macro(\"%s\")\n", vs); } else if (t == TOK_pop_macro) { fprintf(fp, "#pragma pop_macro(\"%s\")\n", vs); } pp_debug_tok = 0; } static void pp_debug_builtins(TCCState *s1) { int v; for (v = TOK_IDENT; v < tok_ident; ++v) define_print(s1, v); } /* Add a space between tokens a and b to avoid unwanted textual pasting */ static int pp_need_space(int a, int b) { return 'E' == a ? '+' == b || '-' == b : '+' == a ? TOK_INC == b || '+' == b : '-' == a ? TOK_DEC == b || '-' == b : a >= TOK_IDENT ? b >= TOK_IDENT : 0; } /* maybe hex like 0x1e */ static int pp_check_he0xE(int t, const char *p) { if (t == TOK_PPNUM && toup(strchr(p, 0)[-1]) == 'E') return 'E'; return t; } /* Preprocess the current file */ ST_FUNC int tcc_preprocess(TCCState *s1) { BufferedFile **iptr; int token_seen, spcs, level; const char *p; Sym *define_start; define_start = define_stack; preprocess_start(s1); ch = file->buf_ptr[0]; tok_flags = TOK_FLAG_BOL | TOK_FLAG_BOF; parse_flags = PARSE_FLAG_PREPROCESS | (parse_flags & PARSE_FLAG_ASM_FILE) | PARSE_FLAG_LINEFEED | PARSE_FLAG_SPACES | PARSE_FLAG_ACCEPT_STRAYS ; /* Credits to Fabrice Bellard's initial revision to demonstrate its capability to compile and run itself, provided all numbers are given as decimals. tcc -E -P10 will do. */ if (s1->Pflag == LINE_MACRO_OUTPUT_FORMAT_P10) parse_flags |= PARSE_FLAG_TOK_NUM, s1->Pflag = 1; #ifdef PP_BENCH /* for PP benchmarks */ do next(); while (tok != TOK_EOF); free_defines(define_start); return 0; #endif if (s1->dflag & 1) { pp_debug_builtins(s1); s1->dflag &= ~1; } token_seen = TOK_LINEFEED, spcs = 0; pp_line(s1, file, 0); for (;;) { iptr = s1->include_stack_ptr; next(); if (tok == TOK_EOF) break; level = s1->include_stack_ptr - iptr; if (level) { if (level > 0) pp_line(s1, *iptr, 0); pp_line(s1, file, level); } if (s1->dflag) { pp_debug_defines(s1); if (s1->dflag & 4) continue; } if (token_seen == TOK_LINEFEED) { if (tok == ' ') { ++spcs; continue; } if (tok == TOK_LINEFEED) { spcs = 0; continue; } pp_line(s1, file, 0); } else if (tok == TOK_LINEFEED) { ++file->line_ref; } else { spcs = pp_need_space(token_seen, tok); } while (spcs) fputs(" ", s1->ppfp), --spcs; fputs(p = get_tok_str(tok, &tokc), s1->ppfp); token_seen = pp_check_he0xE(tok, p);; } /* reset define stack, but keep -D and built-ins */ free_defines(define_start); return 0; } /* ------------------------------------------------------------------------- */
/* * TCC - Tiny C Compiler * * Copyright (c) 2001-2004 Fabrice Bellard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _TCC_H #include "tcc.h" #endif #if BOOTSTRAP && __arm__ void tccgen_ok () { } #endif /********************************************************/ /* global variables */ /* loc : local variable index ind : output code index rsym: return symbol anon_sym: anonymous symbol index */ ST_DATA int rsym, anon_sym, ind, loc; ST_DATA Sym *sym_free_first; ST_DATA void **sym_pools; ST_DATA int nb_sym_pools; ST_DATA Sym *global_stack; ST_DATA Sym *local_stack; ST_DATA Sym *define_stack; ST_DATA Sym *global_label_stack; ST_DATA Sym *local_label_stack; static int local_scope; static int in_sizeof; static int section_sym; ST_DATA int vlas_in_scope; /* number of VLAs that are currently in scope */ ST_DATA int vla_sp_root_loc; /* vla_sp_loc for SP before any VLAs were pushed */ ST_DATA int vla_sp_loc; /* Pointer to variable holding location to store stack pointer on the stack when modifying stack pointer */ ST_DATA SValue __vstack[1+VSTACK_SIZE], *vtop, *pvtop; ST_DATA int const_wanted; /* true if constant wanted */ ST_DATA int nocode_wanted; /* true if no code generation wanted for an expression */ ST_DATA int global_expr; /* true if compound literals must be allocated globally (used during initializers parsing */ ST_DATA CType func_vt; /* current function return type (used by return instruction) */ ST_DATA int func_var; /* true if current function is variadic (used by return instruction) */ ST_DATA int func_vc; ST_DATA int last_line_num, last_ind, func_ind; /* debug last line number and pc */ ST_DATA const char *funcname; ST_DATA CType char_pointer_type, func_old_type, int_type, size_type; ST_DATA struct switch_t { struct case_t { #if HAVE_LONG_LONG_STUB || HAVE_LONG_LONG int64_t v1, v2; #else int32_t v1, v1_padding, v2, v2_padding; #endif int sym; } **p; int n; /* list of case ranges */ int def_sym; /* default symbol */ } *cur_switch; /* current switch */ /* ------------------------------------------------------------------------- */ static void gen_cast(CType *type); static inline CType *pointed_type(CType *type); static int is_compatible_types(CType *type1, CType *type2); static int parse_btype(CType *type, AttributeDef *ad); static CType *type_decl(CType *type, AttributeDef *ad, int *v, int td); static void parse_expr_type(CType *type); static void init_putv(CType *type, Section *sec, unsigned long c); static void decl_initializer(CType *type, Section *sec, unsigned long c, int first, int size_only); static void block(int *bsym, int *csym, int is_expr); static void decl_initializer_alloc(CType *type, AttributeDef *ad, int r, int has_init, int v, int scope); static int decl0(int l, int is_for_loop_init, Sym *); static void expr_eq(void); static void vla_runtime_type_size(CType *type, int *a); static void vla_sp_restore(void); static void vla_sp_restore_root(void); static int is_compatible_parameter_types(CType *type1, CType *type2); #if HAVE_LONG_LONG static inline int64_t expr_const64(void); ST_FUNC void vpush64(int ty, unsigned long long v); #else static inline int32_t expr_const32(void); #endif ST_FUNC void vpush(CType *type); ST_FUNC int gvtst(int inv, int t); ST_FUNC int is_btype_size(int bt); static void gen_inline_functions(TCCState *s); ST_INLN int is_float(int t) { int bt; bt = t & VT_BTYPE; return bt == VT_LDOUBLE || bt == VT_DOUBLE || bt == VT_FLOAT || bt == VT_QFLOAT; } /* we use our own 'finite' function to avoid potential problems with non standard math libs */ /* XXX: endianness dependent */ ST_FUNC int ieee_finite(double d) { int p[4]; memcpy(p, &d, sizeof(double)); return ((unsigned)((p[1] | 0x800fffff) + 1)) >> 31; } ST_FUNC void test_lvalue(void) { if (!(vtop->r & VT_LVAL)) expect("lvalue"); } ST_FUNC void check_vstack(void) { if (pvtop != vtop) tcc_error("internal compiler error: vstack leak (%d)", vtop - pvtop); } /* ------------------------------------------------------------------------- */ /* vstack debugging aid */ #if 0 void pv (const char *lbl, int a, int b) { int i; for (i = a; i < a + b; ++i) { SValue *p = &vtop[-i]; printf("%s vtop[-%d] : type.t:%04x r:%04x r2:%04x c.i:%d\n", lbl, i, p->type.t, p->r, p->r2, (int)p->c.i); } } #endif /* ------------------------------------------------------------------------- */ /* start of translation unit info */ ST_FUNC void tcc_debug_start(TCCState *s1) { if (s1->do_debug) { char buf[512]; /* file info: full path + filename */ section_sym = put_elf_sym(symtab_section, 0, 0, ELFW(ST_INFO)(STB_LOCAL, STT_SECTION), 0, text_section->sh_num, NULL); getcwd(buf, sizeof(buf)); #ifdef _WIN32 normalize_slashes(buf); #endif pstrcat(buf, sizeof(buf), "/"); put_stabs_r(buf, N_SO, 0, 0, text_section->data_offset, text_section, section_sym); put_stabs_r(file->filename, N_SO, 0, 0, text_section->data_offset, text_section, section_sym); last_ind = 0; last_line_num = 0; } /* an elf symbol of type STT_FILE must be put so that STB_LOCAL symbols can be safely used */ put_elf_sym(symtab_section, 0, 0, ELFW(ST_INFO)(STB_LOCAL, STT_FILE), 0, SHN_ABS, file->filename); } /* put end of translation unit info */ ST_FUNC void tcc_debug_end(TCCState *s1) { if (!s1->do_debug) return; put_stabs_r(NULL, N_SO, 0, 0, text_section->data_offset, text_section, section_sym); } /* generate line number info */ ST_FUNC void tcc_debug_line(TCCState *s1) { if (!s1->do_debug) return; if ((last_line_num != file->line_num || last_ind != ind)) { put_stabn(N_SLINE, 0, file->line_num, ind - func_ind); last_ind = ind; last_line_num = file->line_num; } } /* put function symbol */ ST_FUNC void tcc_debug_funcstart(TCCState *s1, Sym *sym) { char buf[512]; if (!s1->do_debug) return; /* stabs info */ /* XXX: we put here a dummy type */ snprintf(buf, sizeof(buf), "%s:%c1", funcname, sym->type.t & VT_STATIC ? 'f' : 'F'); put_stabs_r(buf, N_FUN, 0, file->line_num, 0, cur_text_section, sym->c); /* //gr gdb wants a line at the function */ put_stabn(N_SLINE, 0, file->line_num, 0); last_ind = 0; last_line_num = 0; } /* put function size */ ST_FUNC void tcc_debug_funcend(TCCState *s1, int size) { if (!s1->do_debug) return; put_stabn(N_FUN, 0, 0, size); } /* ------------------------------------------------------------------------- */ ST_FUNC void tccgen_start(TCCState *s1) { cur_text_section = NULL; funcname = ""; anon_sym = SYM_FIRST_ANOM; section_sym = 0; const_wanted = 0; nocode_wanted = 1; /* define some often used types */ int_type.t = VT_INT; char_pointer_type.t = VT_BYTE; mk_pointer(&char_pointer_type); #if PTR_SIZE == 4 size_type.t = VT_INT; #else size_type.t = VT_LLONG; #endif func_old_type.t = VT_FUNC; func_old_type.ref = sym_push(SYM_FIELD, &int_type, FUNC_CDECL, FUNC_OLD); tcc_debug_start(s1); #ifdef TCC_TARGET_ARM arm_init(s1); #endif } ST_FUNC void tccgen_end(TCCState *s1) { gen_inline_functions(s1); check_vstack(); /* end of translation unit info */ tcc_debug_end(s1); } /* ------------------------------------------------------------------------- */ /* apply storage attributes to Elf symbol */ static void update_storage(Sym *sym) { int t; ElfW(Sym) *esym; if (0 == sym->c) return; t = sym->type.t; esym = &((ElfW(Sym) *)symtab_section->data)[sym->c]; #if !__MESC__ // Nyacc 0.80.40 cpp-bug if (t & VT_VIS_MASK) esym->st_other = (esym->st_other & ~ELFW(ST_VISIBILITY)(-1)) | ((t & VT_VIS_MASK) >> VT_VIS_SHIFT); if (t & VT_WEAK) esym->st_info = ELFW(ST_INFO)(STB_WEAK, ELFW(ST_TYPE)(esym->st_info)); #else //__MESC__ // #warning work around Nyacc 0.80.42 bug. #endif // __MESC__ #ifdef TCC_TARGET_PE if (t & VT_EXPORT) esym->st_other |= ST_PE_EXPORT; #endif } /* ------------------------------------------------------------------------- */ /* update sym->c so that it points to an external symbol in section 'section' with value 'value' */ ST_FUNC void put_extern_sym2(Sym *sym, Section *section, addr_t value, unsigned long size, int can_add_underscore) { int sym_type, sym_bind, sh_num, info, other, t; ElfW(Sym) *esym; const char *name; char buf1[256]; #ifdef CONFIG_TCC_BCHECK char buf[32]; #endif if (section == NULL) sh_num = SHN_UNDEF; else if (section == SECTION_ABS) sh_num = SHN_ABS; else sh_num = section->sh_num; if (!sym->c) { name = get_tok_str(sym->v, NULL); #ifdef CONFIG_TCC_BCHECK if (tcc_state->do_bounds_check) { /* XXX: avoid doing that for statics ? */ /* if bound checking is activated, we change some function names by adding the "__bound" prefix */ switch(sym->v) { #ifdef TCC_TARGET_PE /* XXX: we rely only on malloc hooks */ case TOK_malloc: case TOK_free: case TOK_realloc: case TOK_memalign: case TOK_calloc: #endif case TOK_memcpy: case TOK_memmove: case TOK_memset: case TOK_strlen: case TOK_strcpy: case TOK_alloca: strcpy(buf, "__bound_"); strcat(buf, name); name = buf; break; } } #endif t = sym->type.t; if ((t & VT_BTYPE) == VT_FUNC) { sym_type = STT_FUNC; } else if ((t & VT_BTYPE) == VT_VOID) { sym_type = STT_NOTYPE; } else { sym_type = STT_OBJECT; } if (t & VT_STATIC) sym_bind = STB_LOCAL; else sym_bind = STB_GLOBAL; other = 0; #ifdef TCC_TARGET_PE if (sym_type == STT_FUNC && sym->type.ref) { Sym *ref = sym->type.ref; if (ref->a.func_call == FUNC_STDCALL && can_add_underscore) { sprintf(buf1, "_%s@%d", name, ref->a.func_args * PTR_SIZE); name = buf1; other |= ST_PE_STDCALL; can_add_underscore = 0; } } if (t & VT_IMPORT) other |= ST_PE_IMPORT; #endif if (tcc_state->leading_underscore && can_add_underscore) { buf1[0] = '_'; pstrcpy(buf1 + 1, sizeof(buf1) - 1, name); name = buf1; } if (sym->asm_label) name = get_tok_str(sym->asm_label, NULL); info = ELFW(ST_INFO)(sym_bind, sym_type); sym->c = set_elf_sym(symtab_section, value, size, info, other, sh_num, name); } else { esym = &((ElfW(Sym) *)symtab_section->data)[sym->c]; esym->st_value = value; esym->st_size = size; esym->st_shndx = sh_num; } update_storage(sym); } ST_FUNC void put_extern_sym(Sym *sym, Section *section, addr_t value, unsigned long size) { put_extern_sym2(sym, section, value, size, 1); } /* add a new relocation entry to symbol 'sym' in section 's' */ ST_FUNC void greloca(Section *s, Sym *sym, unsigned long offset, int type, addr_t addend) { int c = 0; if (nocode_wanted && s == cur_text_section) return; if (sym) { if (0 == sym->c) put_extern_sym(sym, NULL, 0, 0); c = sym->c; } /* now we can add ELF relocation info */ put_elf_reloca(symtab_section, s, offset, type, c, addend); } #if PTR_SIZE == 4 ST_FUNC void greloc(Section *s, Sym *sym, unsigned long offset, int type) { greloca(s, sym, offset, type, 0); } #endif /* ------------------------------------------------------------------------- */ /* symbol allocator */ static Sym *__sym_malloc(void) { Sym *sym_pool, *sym, *last_sym; int i; sym_pool = tcc_malloc(SYM_POOL_NB * sizeof(Sym)); dynarray_add(&sym_pools, &nb_sym_pools, sym_pool); last_sym = sym_free_first; sym = sym_pool; for(i = 0; i < SYM_POOL_NB; i++) { sym->next = last_sym; last_sym = sym; sym++; } sym_free_first = last_sym; return last_sym; } static inline Sym *sym_malloc(void) { Sym *sym; #ifndef SYM_DEBUG sym = sym_free_first; if (!sym) sym = __sym_malloc(); sym_free_first = sym->next; return sym; #else sym = tcc_malloc(sizeof(Sym)); return sym; #endif } ST_INLN void sym_free(Sym *sym) { #ifndef SYM_DEBUG sym->next = sym_free_first; sym_free_first = sym; #else tcc_free(sym); #endif } /* push, without hashing */ ST_FUNC Sym *sym_push2(Sym **ps, int v, int t, long c) { Sym *s; s = sym_malloc(); s->scope = 0; s->v = v; s->type.t = t; s->type.ref = NULL; #ifdef _WIN64 s->d = NULL; #endif s->c = c; s->next = NULL; /* add in stack */ s->prev = *ps; *ps = s; return s; } /* find a symbol and return its associated structure. 's' is the top of the symbol stack */ ST_FUNC Sym *sym_find2(Sym *s, int v) { while (s) { if (s->v == v) return s; else if (s->v == -1) return NULL; s = s->prev; } return NULL; } /* structure lookup */ ST_INLN Sym *struct_find(int v) { v -= TOK_IDENT; if ((unsigned)v >= (unsigned)(tok_ident - TOK_IDENT)) return NULL; return table_ident[v]->sym_struct; } /* find an identifier */ ST_INLN Sym *sym_find(int v) { v -= TOK_IDENT; if ((unsigned)v >= (unsigned)(tok_ident - TOK_IDENT)) return NULL; return table_ident[v]->sym_identifier; } /* push a given symbol on the symbol stack */ ST_FUNC Sym *sym_push(int v, CType *type, int r, long c) { Sym *s, **ps; TokenSym *ts; if (local_stack) ps = &local_stack; else ps = &global_stack; s = sym_push2(ps, v, type->t, c); s->type.ref = type->ref; s->r = r; /* don't record fields or anonymous symbols */ /* XXX: simplify */ if (!(v & SYM_FIELD) && (v & ~SYM_STRUCT) < SYM_FIRST_ANOM) { /* record symbol in token array */ ts = table_ident[(v & ~SYM_STRUCT) - TOK_IDENT]; if (v & SYM_STRUCT) ps = &ts->sym_struct; else ps = &ts->sym_identifier; s->prev_tok = *ps; *ps = s; s->scope = local_scope; if (s->prev_tok && s->prev_tok->scope == s->scope) tcc_error("redeclaration of '%s'", get_tok_str(v & ~SYM_STRUCT, NULL)); } return s; } /* push a global identifier */ ST_FUNC Sym *global_identifier_push(int v, int t, long c) { Sym *s, **ps; s = sym_push2(&global_stack, v, t, c); /* don't record anonymous symbol */ if (v < SYM_FIRST_ANOM) { ps = &table_ident[v - TOK_IDENT]->sym_identifier; /* modify the top most local identifier, so that sym_identifier will point to 's' when popped */ while (*ps != NULL) ps = &(*ps)->prev_tok; s->prev_tok = NULL; *ps = s; } return s; } /* pop symbols until top reaches 'b'. If KEEP is non-zero don't really pop them yet from the list, but do remove them from the token array. */ ST_FUNC void sym_pop(Sym **ptop, Sym *b, int keep) { Sym *s, *ss, **ps; TokenSym *ts; int v; s = *ptop; while(s != b) { ss = s->prev; v = s->v; /* remove symbol in token array */ /* XXX: simplify */ if (!(v & SYM_FIELD) && (v & ~SYM_STRUCT) < SYM_FIRST_ANOM) { ts = table_ident[(v & ~SYM_STRUCT) - TOK_IDENT]; if (v & SYM_STRUCT) ps = &ts->sym_struct; else ps = &ts->sym_identifier; *ps = s->prev_tok; } if (!keep) sym_free(s); s = ss; } if (!keep) *ptop = b; } /* ------------------------------------------------------------------------- */ static void vsetc(CType *type, int r, CValue *vc) { int v; if (vtop >= vstack + (VSTACK_SIZE - 1)) tcc_error("memory full (vstack)"); /* cannot let cpu flags if other instruction are generated. Also avoid leaving VT_JMP anywhere except on the top of the stack because it would complicate the code generator. Don't do this when nocode_wanted. vtop might come from !nocode_wanted regions (see 88_codeopt.c) and transforming it to a register without actually generating code is wrong as their value might still be used for real. All values we push under nocode_wanted will eventually be popped again, so that the VT_CMP/VT_JMP value will be in vtop when code is unsuppressed again. Same logic below in vswap(); */ if (vtop >= vstack && !nocode_wanted) { v = vtop->r & VT_VALMASK; if (v == VT_CMP || (v & ~1) == VT_JMP) gv(RC_INT); } vtop++; vtop->type = *type; vtop->r = r; vtop->r2 = VT_CONST; vtop->c = *vc; vtop->sym = NULL; } ST_FUNC void vswap(void) { SValue tmp; /* cannot vswap cpu flags. See comment at vsetc() above */ if (vtop >= vstack && !nocode_wanted) { int v = vtop->r & VT_VALMASK; if (v == VT_CMP || (v & ~1) == VT_JMP) gv(RC_INT); } tmp = vtop[0]; vtop[0] = vtop[-1]; vtop[-1] = tmp; } /* pop stack value */ ST_FUNC void vpop(void) { int v; v = vtop->r & VT_VALMASK; #if defined(TCC_TARGET_I386) || defined(TCC_TARGET_X86_64) /* for x86, we need to pop the FP stack */ if (v == TREG_ST0) { o(0xd8dd); /* fstp %st(0) */ } else #endif if (v == VT_JMP || v == VT_JMPI) { /* need to put correct jump if && or || without test */ gsym(vtop->c.i); } vtop--; } /* push constant of type "type" with useless value */ ST_FUNC void vpush(CType *type) { CValue cval; vsetc(type, VT_CONST, &cval); } /* push integer constant */ ST_FUNC void vpushi(int v) { CValue cval; cval.i = v; vsetc(&int_type, VT_CONST, &cval); } /* push a pointer sized constant */ static void vpushs(addr_t v) { CValue cval; cval.i = v; vsetc(&size_type, VT_CONST, &cval); } /* push arbitrary 64bit constant */ ST_FUNC void vpush64(int ty, unsigned long long v) { CValue cval; CType ctype; ctype.t = ty; ctype.ref = NULL; cval.i = v; vsetc(&ctype, VT_CONST, &cval); } /* push long long constant */ static inline void vpushll(long long v) { vpush64(VT_LLONG, v); } ST_FUNC void vset(CType *type, int r, long v) { CValue cval; cval.i = v; vsetc(type, r, &cval); } static void vseti(int r, int v) { CType type; type.t = VT_INT; type.ref = 0; vset(&type, r, v); } ST_FUNC void vpushv(SValue *v) { if (vtop >= vstack + (VSTACK_SIZE - 1)) tcc_error("memory full (vstack)"); vtop++; *vtop = *v; } static void vdup(void) { vpushv(vtop); } /* rotate n first stack elements to the bottom I1 ... In -> I2 ... In I1 [top is right] */ ST_FUNC void vrotb(int n) { int i; SValue tmp; tmp = vtop[-n + 1]; for(i=-n+1;i!=0;i++) vtop[i] = vtop[i+1]; vtop[0] = tmp; } /* rotate the n elements before entry e towards the top I1 ... In ... -> In I1 ... I(n-1) ... [top is right] */ ST_FUNC void vrote(SValue *e, int n) { int i; SValue tmp; tmp = *e; for(i = 0;i < n - 1; i++) e[-i] = e[-i - 1]; e[-n + 1] = tmp; } /* rotate n first stack elements to the top I1 ... In -> In I1 ... I(n-1) [top is right] */ ST_FUNC void vrott(int n) { vrote(vtop, n); } /* push a symbol value of TYPE */ static inline void vpushsym(CType *type, Sym *sym) { CValue cval; cval.i = 0; vsetc(type, VT_CONST | VT_SYM, &cval); vtop->sym = sym; } /* Return a static symbol pointing to a section */ ST_FUNC Sym *get_sym_ref(CType *type, Section *sec, unsigned long offset, unsigned long size) { int v; Sym *sym; v = anon_sym++; sym = global_identifier_push(v, type->t | VT_STATIC, 0); sym->type.ref = type->ref; sym->r = VT_CONST | VT_SYM; put_extern_sym(sym, sec, offset, size); return sym; } /* push a reference to a section offset by adding a dummy symbol */ static void vpush_ref(CType *type, Section *sec, unsigned long offset, unsigned long size) { vpushsym(type, get_sym_ref(type, sec, offset, size)); } /* define a new external reference to a symbol 'v' of type 'u' */ ST_FUNC Sym *external_global_sym(int v, CType *type, int r) { Sym *s; s = sym_find(v); if (!s) { /* push forward reference */ s = global_identifier_push(v, type->t | VT_EXTERN, 0); s->type.ref = type->ref; s->r = r | VT_CONST | VT_SYM; } return s; } /* Merge some storage attributes. */ static void patch_storage(Sym *sym, CType *type) { int t; if (!is_compatible_types(&sym->type, type)) tcc_error("incompatible types for redefinition of '%s'", get_tok_str(sym->v, NULL)); t = type->t; #ifdef TCC_TARGET_PE if ((sym->type.t ^ t) & VT_IMPORT) tcc_error("incompatible dll linkage for redefinition of '%s'", get_tok_str(sym->v, NULL)); #endif sym->type.t |= t & (VT_EXPORT|VT_WEAK); if (t & VT_VIS_MASK) { int vis = sym->type.t & VT_VIS_MASK; int vis2 = t & VT_VIS_MASK; if (vis == (STV_DEFAULT << VT_VIS_SHIFT)) vis = vis2; else if (vis2 != (STV_DEFAULT << VT_VIS_SHIFT)) vis = (vis < vis2) ? vis : vis2; sym->type.t = (sym->type.t & ~VT_VIS_MASK) | vis; } } /* define a new external reference to a symbol 'v' */ static Sym *external_sym(int v, CType *type, int r) { Sym *s; s = sym_find(v); if (!s) { /* push forward reference */ s = sym_push(v, type, r | VT_CONST | VT_SYM, 0); s->type.t |= VT_EXTERN; } else { if (s->type.ref == func_old_type.ref) { s->type.ref = type->ref; s->r = r | VT_CONST | VT_SYM; s->type.t |= VT_EXTERN; } patch_storage(s, type); update_storage(s); } return s; } /* push a reference to global symbol v */ ST_FUNC void vpush_global_sym(CType *type, int v) { vpushsym(type, external_global_sym(v, type, 0)); } /* save registers up to (vtop - n) stack entry */ ST_FUNC void save_regs(int n) { SValue *p, *p1; for(p = vstack, p1 = vtop - n; p <= p1; p++) save_reg(p->r); } /* save r to the memory stack, and mark it as being free */ ST_FUNC void save_reg(int r) { save_reg_upstack(r, 0); } /* save r to the memory stack, and mark it as being free, if seen up to (vtop - n) stack entry */ ST_FUNC void save_reg_upstack(int r, int n) { int l, saved, size, align; SValue *p, *p1, sv; CType *type; if ((r &= VT_VALMASK) >= VT_CONST) return; if (nocode_wanted) return; /* modify all stack values */ saved = 0; l = 0; for(p = vstack, p1 = vtop - n; p <= p1; p++) { if ((p->r & VT_VALMASK) == r || ((p->type.t & VT_BTYPE) == VT_LLONG && (p->r2 & VT_VALMASK) == r)) { /* must save value on stack if not already done */ if (!saved) { /* NOTE: must reload 'r' because r might be equal to r2 */ r = p->r & VT_VALMASK; /* store register in the stack */ type = &p->type; if ((p->r & VT_LVAL) || (!is_float(type->t) && (type->t & VT_BTYPE) != VT_LLONG)) #if PTR_SIZE == 8 type = &char_pointer_type; #else type = &int_type; #endif size = type_size(type, &align); loc = (loc - size) & -align; sv.type.t = type->t; sv.r = VT_LOCAL | VT_LVAL; sv.c.i = loc; store(r, &sv); #if defined(TCC_TARGET_I386) || defined(TCC_TARGET_X86_64) /* x86 specific: need to pop fp register ST0 if saved */ if (r == TREG_ST0) { o(0xd8dd); /* fstp %st(0) */ } #endif #if PTR_SIZE == 4 /* special long long case */ if ((type->t & VT_BTYPE) == VT_LLONG) { sv.c.i += 4; store(p->r2, &sv); } #endif l = loc; saved = 1; } /* mark that stack entry as being saved on the stack */ if (p->r & VT_LVAL) { /* also clear the bounded flag because the relocation address of the function was stored in p->c.i */ p->r = (p->r & ~(VT_VALMASK | VT_BOUNDED)) | VT_LLOCAL; } else { p->r = lvalue_type(p->type.t) | VT_LOCAL; } p->r2 = VT_CONST; p->c.i = l; } } } #ifdef TCC_TARGET_ARM /* find a register of class 'rc2' with at most one reference on stack. * If none, call get_reg(rc) */ ST_FUNC int get_reg_ex(int rc, int rc2) { int r; SValue *p; for(r=0;r<NB_REGS;r++) { if (reg_classes[r] & rc2) { int n; n=0; for(p = vstack; p <= vtop; p++) { if ((p->r & VT_VALMASK) == r || (p->r2 & VT_VALMASK) == r) n++; } if (n <= 1) return r; } } return get_reg(rc); } #endif /* find a free register of class 'rc'. If none, save one register */ ST_FUNC int get_reg(int rc) { int r; SValue *p; /* find a free register */ for(r=0;r<NB_REGS;r++) { if (reg_classes[r] & rc) { if (nocode_wanted) return r; for(p=vstack;p<=vtop;p++) { if ((p->r & VT_VALMASK) == r || (p->r2 & VT_VALMASK) == r) goto notfound; } return r; } notfound: ; } /* no register left : free the first one on the stack (VERY IMPORTANT to start from the bottom to ensure that we don't spill registers used in gen_opi()) */ for(p=vstack;p<=vtop;p++) { /* look at second register (if long long) */ r = p->r2 & VT_VALMASK; if (r < VT_CONST && (reg_classes[r] & rc)) goto save_found; r = p->r & VT_VALMASK; if (r < VT_CONST && (reg_classes[r] & rc)) { save_found: save_reg(r); return r; } } /* Should never comes here */ return -1; } /* move register 's' (of type 't') to 'r', and flush previous value of r to memory if needed */ static void move_reg(int r, int s, int t) { SValue sv; if (r != s) { save_reg(r); sv.type.t = t; sv.type.ref = NULL; sv.r = s; sv.c.i = 0; load(r, &sv); } } /* get address of vtop (vtop MUST BE an lvalue) */ ST_FUNC void gaddrof(void) { vtop->r &= ~VT_LVAL; /* tricky: if saved lvalue, then we can go back to lvalue */ if ((vtop->r & VT_VALMASK) == VT_LLOCAL) vtop->r = (vtop->r & ~(VT_VALMASK | VT_LVAL_TYPE)) | VT_LOCAL | VT_LVAL; } #ifdef CONFIG_TCC_BCHECK /* generate lvalue bound code */ static void gbound(void) { int lval_type; CType type1; vtop->r &= ~VT_MUSTBOUND; /* if lvalue, then use checking code before dereferencing */ if (vtop->r & VT_LVAL) { /* if not VT_BOUNDED value, then make one */ if (!(vtop->r & VT_BOUNDED)) { lval_type = vtop->r & (VT_LVAL_TYPE | VT_LVAL); /* must save type because we must set it to int to get pointer */ type1 = vtop->type; vtop->type.t = VT_PTR; gaddrof(); vpushi(0); gen_bounded_ptr_add(); vtop->r |= lval_type; vtop->type = type1; } /* then check for dereferencing */ gen_bounded_ptr_deref(); } } #endif /* store vtop a register belonging to class 'rc'. lvalues are converted to values. Cannot be used if cannot be converted to register value (such as structures). */ ST_FUNC int gv(int rc) { int r, bit_pos, bit_size, size, align; int rc2; /* NOTE: get_reg can modify vstack[] */ if (vtop->type.t & VT_BITFIELD) { CType type; int bits = 32; bit_pos = (vtop->type.t >> VT_STRUCT_SHIFT) & 0x3f; bit_size = (vtop->type.t >> (VT_STRUCT_SHIFT + 6)) & 0x3f; /* remove bit field info to avoid loops */ vtop->type.t &= ~VT_BITFIELD & ((1 << VT_STRUCT_SHIFT) - 1); /* cast to int to propagate signedness in following ops */ if ((vtop->type.t & VT_BTYPE) == VT_LLONG) { type.t = VT_LLONG; bits = 64; } else type.t = VT_INT; if((vtop->type.t & VT_UNSIGNED) || (vtop->type.t & VT_BTYPE) == VT_BOOL || (((vtop->type.t & VT_BTYPE) == VT_ENUM) && vtop->type.ref->a.unsigned_enum)) type.t |= VT_UNSIGNED; gen_cast(&type); /* generate shifts */ vpushi(bits - (bit_pos + bit_size)); gen_op(TOK_SHL); vpushi(bits - bit_size); /* NOTE: transformed to SHR if unsigned */ gen_op(TOK_SAR); r = gv(rc); } else { if (is_float(vtop->type.t) && (vtop->r & (VT_VALMASK | VT_LVAL)) == VT_CONST) { unsigned long offset; /* CPUs usually cannot use float constants, so we store them generically in data segment */ size = type_size(&vtop->type, &align); offset = section_add(data_section, size, align); vpush_ref(&vtop->type, data_section, offset, size); vswap(); init_putv(&vtop->type, data_section, offset); vtop->r |= VT_LVAL; } #ifdef CONFIG_TCC_BCHECK if (vtop->r & VT_MUSTBOUND) gbound(); #endif r = vtop->r & VT_VALMASK; rc2 = (rc & RC_FLOAT) ? RC_FLOAT : RC_INT; #ifndef TCC_TARGET_ARM64 if (rc == RC_IRET) rc2 = RC_LRET; #ifdef TCC_TARGET_X86_64 else if (rc == RC_FRET) rc2 = RC_QRET; #endif #endif /* need to reload if: - constant - lvalue (need to dereference pointer) - already a register, but not in the right class */ if (r >= VT_CONST || (vtop->r & VT_LVAL) || !(reg_classes[r] & rc) #if PTR_SIZE == 8 || ((vtop->type.t & VT_BTYPE) == VT_QLONG && !(reg_classes[vtop->r2] & rc2)) || ((vtop->type.t & VT_BTYPE) == VT_QFLOAT && !(reg_classes[vtop->r2] & rc2)) #else || ((vtop->type.t & VT_BTYPE) == VT_LLONG && !(reg_classes[vtop->r2] & rc2)) #endif ) { r = get_reg(rc); #if PTR_SIZE == 8 if (((vtop->type.t & VT_BTYPE) == VT_QLONG) || ((vtop->type.t & VT_BTYPE) == VT_QFLOAT)) { int addr_type = VT_LLONG, load_size = 8, load_type = ((vtop->type.t & VT_BTYPE) == VT_QLONG) ? VT_LLONG : VT_DOUBLE; #else if ((vtop->type.t & VT_BTYPE) == VT_LLONG) { int addr_type = VT_INT, load_size = 4, load_type = VT_INT; unsigned long long ll; #endif int r2, original_type; original_type = vtop->type.t; /* two register type load : expand to two words temporarily */ #if PTR_SIZE == 4 if ((vtop->r & (VT_VALMASK | VT_LVAL)) == VT_CONST) { /* load constant */ ll = vtop->c.i; vtop->c.i = ll; /* first word */ load(r, vtop); vtop->r = r; /* save register value */ vpushi(ll >> 32); /* second word */ } else #endif if (vtop->r & VT_LVAL) { /* We do not want to modifier the long long pointer here, so the safest (and less efficient) is to save all the other registers in the stack. XXX: totally inefficient. */ #if 0 save_regs(1); #else /* lvalue_save: save only if used further down the stack */ save_reg_upstack(vtop->r, 1); #endif /* load from memory */ vtop->type.t = load_type; load(r, vtop); vdup(); vtop[-1].r = r; /* save register value */ /* increment pointer to get second word */ vtop->type.t = addr_type; gaddrof(); vpushi(load_size); gen_op('+'); vtop->r |= VT_LVAL; vtop->type.t = load_type; } else { /* move registers */ load(r, vtop); vdup(); vtop[-1].r = r; /* save register value */ vtop->r = vtop[-1].r2; } /* Allocate second register. Here we rely on the fact that get_reg() tries first to free r2 of an SValue. */ r2 = get_reg(rc2); load(r2, vtop); vpop(); /* write second register */ vtop->r2 = r2; vtop->type.t = original_type; } else if ((vtop->r & VT_LVAL) && !is_float(vtop->type.t)) { int t1, t; /* lvalue of scalar type : need to use lvalue type because of possible cast */ t = vtop->type.t; t1 = t; /* compute memory access type */ if (vtop->r & VT_LVAL_BYTE) t = VT_BYTE; else if (vtop->r & VT_LVAL_SHORT) t = VT_SHORT; if (vtop->r & VT_LVAL_UNSIGNED) t |= VT_UNSIGNED; vtop->type.t = t; load(r, vtop); /* restore wanted type */ vtop->type.t = t1; } else { /* one register type load */ load(r, vtop); } } vtop->r = r; #ifdef TCC_TARGET_C67 /* uses register pairs for doubles */ if ((vtop->type.t & VT_BTYPE) == VT_DOUBLE) vtop->r2 = r+1; #endif } return r; } /* generate vtop[-1] and vtop[0] in resp. classes rc1 and rc2 */ ST_FUNC void gv2(int rc1, int rc2) { int v; /* generate more generic register first. But VT_JMP or VT_CMP values must be generated first in all cases to avoid possible reload errors */ v = vtop[0].r & VT_VALMASK; if (v != VT_CMP && (v & ~1) != VT_JMP && rc1 <= rc2) { vswap(); gv(rc1); vswap(); gv(rc2); /* test if reload is needed for first register */ if ((vtop[-1].r & VT_VALMASK) >= VT_CONST) { vswap(); gv(rc1); vswap(); } } else { gv(rc2); vswap(); gv(rc1); vswap(); /* test if reload is needed for first register */ if ((vtop[0].r & VT_VALMASK) >= VT_CONST) { gv(rc2); } } } #ifndef TCC_TARGET_ARM64 /* wrapper around RC_FRET to return a register by type */ static int rc_fret(int t) { #ifdef TCC_TARGET_X86_64 if (t == VT_LDOUBLE) { return RC_ST0; } #endif return RC_FRET; } #endif /* wrapper around REG_FRET to return a register by type */ static int reg_fret(int t) { #ifdef TCC_TARGET_X86_64 if (t == VT_LDOUBLE) { return TREG_ST0; } #endif return REG_FRET; } #if PTR_SIZE == 4 /* expand 64bit on stack in two ints */ static void lexpand(void) { int u, v; u = vtop->type.t & (VT_DEFSIGN | VT_UNSIGNED); v = vtop->r & (VT_VALMASK | VT_LVAL); if (v == VT_CONST) { vdup(); #if HAVE_LONG_LONG_STUB || HAVE_LONG_LONG vtop[0].c.i >>= 32; #endif } else if (v == (VT_LVAL|VT_CONST) || v == (VT_LVAL|VT_LOCAL)) { vdup(); vtop[0].c.i += 4; } else { gv(RC_INT); vdup(); vtop[0].r = vtop[-1].r2; vtop[0].r2 = vtop[-1].r2 = VT_CONST; } vtop[0].type.t = vtop[-1].type.t = VT_INT | u; } #endif #ifdef TCC_TARGET_ARM /* expand long long on stack */ ST_FUNC void lexpand_nr(void) { int u,v; u = vtop->type.t & (VT_DEFSIGN | VT_UNSIGNED); vdup(); vtop->r2 = VT_CONST; vtop->type.t = VT_INT | u; v=vtop[-1].r & (VT_VALMASK | VT_LVAL); if (v == VT_CONST) { vtop[-1].c.i = vtop->c.i; #if HAVE_LONG_LONG_STUB || HAVE_LONG_LONG vtop->c.i = vtop->c.i >> 32; #endif vtop->r = VT_CONST; } else if (v == (VT_LVAL|VT_CONST) || v == (VT_LVAL|VT_LOCAL)) { vtop->c.i += 4; vtop->r = vtop[-1].r; } else if (v > VT_CONST) { vtop--; lexpand(); } else vtop->r = vtop[-1].r2; vtop[-1].r2 = VT_CONST; vtop[-1].type.t = VT_INT | u; } #endif #if PTR_SIZE == 4 /* build a long long from two ints */ static void lbuild(int t) { gv2(RC_INT, RC_INT); vtop[-1].r2 = vtop[0].r; vtop[-1].type.t = t; vpop(); } #endif /* convert stack entry to register and duplicate its value in another register */ static void gv_dup(void) { int rc, t, r, r1; SValue sv; t = vtop->type.t; #if PTR_SIZE == 4 if ((t & VT_BTYPE) == VT_LLONG) { lexpand(); gv_dup(); vswap(); vrotb(3); gv_dup(); vrotb(4); /* stack: H L L1 H1 */ lbuild(t); vrotb(3); vrotb(3); vswap(); lbuild(t); vswap(); } else #endif { /* duplicate value */ rc = RC_INT; sv.type.t = VT_INT; if (is_float(t)) { rc = RC_FLOAT; #ifdef TCC_TARGET_X86_64 if ((t & VT_BTYPE) == VT_LDOUBLE) { rc = RC_ST0; } #endif sv.type.t = t; } r = gv(rc); r1 = get_reg(rc); sv.r = r; sv.c.i = 0; load(r1, &sv); /* move r to r1 */ vdup(); /* duplicates value */ if (r != r1) vtop->r = r1; } } /* Generate value test * * Generate a test for any value (jump, comparison and integers) */ ST_FUNC int gvtst(int inv, int t) { int v = vtop->r & VT_VALMASK; if (v != VT_CMP && v != VT_JMP && v != VT_JMPI) { vpushi(0); gen_op(TOK_NE); } if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) { /* constant jmp optimization */ if ((vtop->c.i != 0) != inv) t = gjmp(t); vtop--; return t; } return gtst(inv, t); } #if PTR_SIZE == 4 /* generate CPU independent (unsigned) long long operations */ static void gen_opl(int op) { int t, a, b, op1, c, i; int func; unsigned short reg_iret = REG_IRET; unsigned short reg_lret = REG_LRET; SValue tmp; switch(op) { case '/': case TOK_PDIV: func = TOK___divdi3; goto gen_func; case TOK_UDIV: func = TOK___udivdi3; goto gen_func; case '%': func = TOK___moddi3; goto gen_mod_func; case TOK_UMOD: func = TOK___umoddi3; gen_mod_func: #ifdef TCC_ARM_EABI reg_iret = TREG_R2; reg_lret = TREG_R3; #endif gen_func: /* call generic long long function */ vpush_global_sym(&func_old_type, func); vrott(3); gfunc_call(2); vpushi(0); vtop->r = reg_iret; vtop->r2 = reg_lret; break; case '^': case '&': case '|': case '*': case '+': case '-': //pv("gen_opl A",0,2); t = vtop->type.t; vswap(); lexpand(); vrotb(3); lexpand(); /* stack: L1 H1 L2 H2 */ tmp = vtop[0]; vtop[0] = vtop[-3]; vtop[-3] = tmp; tmp = vtop[-2]; vtop[-2] = vtop[-3]; vtop[-3] = tmp; vswap(); /* stack: H1 H2 L1 L2 */ //pv("gen_opl B",0,4); if (op == '*') { vpushv(vtop - 1); vpushv(vtop - 1); gen_op(TOK_UMULL); lexpand(); /* stack: H1 H2 L1 L2 ML MH */ for(i=0;i<4;i++) vrotb(6); /* stack: ML MH H1 H2 L1 L2 */ tmp = vtop[0]; vtop[0] = vtop[-2]; vtop[-2] = tmp; /* stack: ML MH H1 L2 H2 L1 */ gen_op('*'); vrotb(3); vrotb(3); gen_op('*'); /* stack: ML MH M1 M2 */ gen_op('+'); gen_op('+'); } else if (op == '+' || op == '-') { /* XXX: add non carry method too (for MIPS or alpha) */ if (op == '+') op1 = TOK_ADDC1; else op1 = TOK_SUBC1; gen_op(op1); /* stack: H1 H2 (L1 op L2) */ vrotb(3); vrotb(3); gen_op(op1 + 1); /* TOK_xxxC2 */ } else { gen_op(op); /* stack: H1 H2 (L1 op L2) */ vrotb(3); vrotb(3); /* stack: (L1 op L2) H1 H2 */ gen_op(op); /* stack: (L1 op L2) (H1 op H2) */ } /* stack: L H */ lbuild(t); break; case TOK_SAR: case TOK_SHR: case TOK_SHL: if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) { t = vtop[-1].type.t; vswap(); lexpand(); vrotb(3); /* stack: L H shift */ c = (int)vtop->c.i; /* constant: simpler */ /* NOTE: all comments are for SHL. the other cases are done by swapping words */ vpop(); if (op != TOK_SHL) vswap(); if (c >= 32) { /* stack: L H */ vpop(); if (c > 32) { vpushi(c - 32); gen_op(op); } if (op != TOK_SAR) { vpushi(0); } else { gv_dup(); vpushi(31); gen_op(TOK_SAR); } vswap(); } else { vswap(); gv_dup(); /* stack: H L L */ vpushi(c); gen_op(op); vswap(); vpushi(32 - c); if (op == TOK_SHL) gen_op(TOK_SHR); else gen_op(TOK_SHL); vrotb(3); /* stack: L L H */ vpushi(c); if (op == TOK_SHL) gen_op(TOK_SHL); else gen_op(TOK_SHR); gen_op('|'); } if (op != TOK_SHL) vswap(); lbuild(t); } else { /* XXX: should provide a faster fallback on x86 ? */ switch(op) { case TOK_SAR: func = TOK___ashrdi3; goto gen_func; case TOK_SHR: func = TOK___lshrdi3; goto gen_func; case TOK_SHL: func = TOK___ashldi3; goto gen_func; } } break; default: /* compare operations */ t = vtop->type.t; vswap(); lexpand(); vrotb(3); lexpand(); /* stack: L1 H1 L2 H2 */ tmp = vtop[-1]; vtop[-1] = vtop[-2]; vtop[-2] = tmp; /* stack: L1 L2 H1 H2 */ /* compare high */ op1 = op; /* when values are equal, we need to compare low words. since the jump is inverted, we invert the test too. */ if (op1 == TOK_LT) op1 = TOK_LE; else if (op1 == TOK_GT) op1 = TOK_GE; else if (op1 == TOK_ULT) op1 = TOK_ULE; else if (op1 == TOK_UGT) op1 = TOK_UGE; a = 0; b = 0; gen_op(op1); if (op == TOK_NE) { b = gvtst(0, 0); } else { a = gvtst(1, 0); if (op != TOK_EQ) { /* generate non equal test */ vpushi(TOK_NE); vtop->r = VT_CMP; b = gvtst(0, 0); } } /* compare low. Always unsigned */ op1 = op; if (op1 == TOK_LT) op1 = TOK_ULT; else if (op1 == TOK_LE) op1 = TOK_ULE; else if (op1 == TOK_GT) op1 = TOK_UGT; else if (op1 == TOK_GE) op1 = TOK_UGE; gen_op(op1); a = gvtst(1, a); gsym(b); vseti(VT_JMPI, a); break; } } #endif #if HAVE_LONG_LONG_STUB || HAVE_LONG_LONG static uint64_t gen_opic_sdiv(uint64_t a, uint64_t b) { #if HAVE_LONG_LONG uint64_t x = (a >> 63 ? -a : a) / (b >> 63 ? -b : b); return (a ^ b) >> 63 ? -x : x; #else uint32_t x = (a >> 31 ? -a : a) / (b >> 31 ? -b : b); return (a ^ b) >> 31 ? -x : x; #endif } static int gen_opic_lt(uint64_t a, uint64_t b) { #if HAVE_LONG_LONG return (a ^ (uint64_t)1 << 63) < (b ^ (uint64_t)1 << 63); #else return (a ^ (uint32_t)1 << 31) < (b ^ (uint32_t)1 << 31); #endif } #else static uint32_t gen_opic_sdiv(uint32_t a, uint32_t b) { uint32_t x = (a >> 31 ? -a : a) / (b >> 31 ? -b : b); return (a ^ b) >> 31 ? -x : x; } static int gen_opic_lt(uint32_t a, uint32_t b) { return (a ^ (uint32_t)1 << 31) < (b ^ (uint32_t)1 << 31); } #endif /* handle integer constant optimizations and various machine independent opt */ static void gen_opic(int op) { SValue *v1 = vtop - 1; SValue *v2 = vtop; int t1 = v1->type.t & VT_BTYPE; int t2 = v2->type.t & VT_BTYPE; int c1 = (v1->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST; int c2 = (v2->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST; #if HAVE_LONG_LONG uint64_t l1 = c1 ? v1->c.i : 0; uint64_t l2 = c2 ? v2->c.i : 0; int shm = (t1 == VT_LLONG) ? 63 : 31; #else uint32_t l1 = c1 ? v1->c.i : 0; uint32_t l2 = c2 ? v2->c.i : 0; int shm = 31; #endif if (t1 != VT_LLONG && (PTR_SIZE != 8 || t1 != VT_PTR)) l1 = ((uint32_t)l1 | (v1->type.t & VT_UNSIGNED ? 0 : -(l1 & 0x80000000))); if (t2 != VT_LLONG && (PTR_SIZE != 8 || t2 != VT_PTR)) l2 = ((uint32_t)l2 | (v2->type.t & VT_UNSIGNED ? 0 : -(l2 & 0x80000000))); if (c1 && c2) { switch(op) { case '+': l1 += l2; break; case '-': l1 -= l2; break; case '&': l1 &= l2; break; case '^': l1 ^= l2; break; case '|': l1 |= l2; break; case '*': l1 *= l2; break; case TOK_PDIV: case '/': case '%': case TOK_UDIV: case TOK_UMOD: /* if division by zero, generate explicit division */ if (l2 == 0) { if (const_wanted) tcc_error("division by zero in constant"); goto general_case; } switch(op) { default: l1 = gen_opic_sdiv(l1, l2); break; case '%': l1 = l1 - l2 * gen_opic_sdiv(l1, l2); break; case TOK_UDIV: l1 = l1 / l2; break; case TOK_UMOD: l1 = l1 % l2; break; } break; case TOK_SHL: l1 <<= (l2 & shm); break; case TOK_SHR: l1 >>= (l2 & shm); break; case TOK_SAR: #if HAVE_LONG_LONG l1 = (l1 >> 63) ? ~(~l1 >> (l2 & shm)) : l1 >> (l2 & shm); #else l1 = (l1 >> 31) ? ~(~l1 >> (l2 & shm)) : l1 >> (l2 & shm); #endif break; /* tests */ case TOK_ULT: l1 = l1 < l2; break; case TOK_UGE: l1 = l1 >= l2; break; case TOK_EQ: l1 = l1 == l2; break; case TOK_NE: l1 = l1 != l2; break; case TOK_ULE: l1 = l1 <= l2; break; case TOK_UGT: l1 = l1 > l2; break; case TOK_LT: l1 = gen_opic_lt(l1, l2); break; case TOK_GE: l1 = !gen_opic_lt(l1, l2); break; case TOK_LE: l1 = !gen_opic_lt(l2, l1); break; case TOK_GT: l1 = gen_opic_lt(l2, l1); break; /* logical */ case TOK_LAND: l1 = l1 && l2; break; case TOK_LOR: l1 = l1 || l2; break; default: goto general_case; } if (t1 != VT_LLONG && (PTR_SIZE != 8 || t1 != VT_PTR)) l1 = ((uint32_t)l1 | (v1->type.t & VT_UNSIGNED ? 0 : -(l1 & 0x80000000))); v1->c.i = l1; vtop--; } else { /* if commutative ops, put c2 as constant */ if (c1 && (op == '+' || op == '&' || op == '^' || op == '|' || op == '*')) { vswap(); c2 = c1; //c = c1, c1 = c2, c2 = c; l2 = l1; //l = l1, l1 = l2, l2 = l; } if (!const_wanted && c1 && ((l1 == 0 && (op == TOK_SHL || op == TOK_SHR || op == TOK_SAR)) || (l1 == -1 && op == TOK_SAR))) { /* treat (0 << x), (0 >> x) and (-1 >> x) as constant */ vtop--; } else if (!const_wanted && c2 && ((l2 == 0 && (op == '&' || op == '*')) || (l2 == -1 && op == '|') || (l2 == 0xffffffff && t2 != VT_LLONG && op == '|') || (l2 == 1 && (op == '%' || op == TOK_UMOD)))) { /* treat (x & 0), (x * 0), (x | -1) and (x % 1) as constant */ if (l2 == 1) vtop->c.i = 0; vswap(); vtop--; } else if (c2 && (((op == '*' || op == '/' || op == TOK_UDIV || op == TOK_PDIV) && l2 == 1) || ((op == '+' || op == '-' || op == '|' || op == '^' || op == TOK_SHL || op == TOK_SHR || op == TOK_SAR) && l2 == 0) || (op == '&' && l2 == -1))) { /* filter out NOP operations like x*1, x-0, x&-1... */ vtop--; } else if (c2 && (op == '*' || op == TOK_PDIV || op == TOK_UDIV)) { /* try to use shifts instead of muls or divs */ if (l2 > 0 && (l2 & (l2 - 1)) == 0) { int n = -1; while (l2) { l2 >>= 1; n++; } vtop->c.i = n; if (op == '*') op = TOK_SHL; else if (op == TOK_PDIV) op = TOK_SAR; else op = TOK_SHR; } goto general_case; } else if (c2 && (op == '+' || op == '-') && (((vtop[-1].r & (VT_VALMASK | VT_LVAL | VT_SYM)) == (VT_CONST | VT_SYM)) || (vtop[-1].r & (VT_VALMASK | VT_LVAL)) == VT_LOCAL)) { /* symbol + constant case */ if (op == '-') l2 = -l2; l2 += vtop[-1].c.i; /* The backends can't always deal with addends to symbols larger than +-1<<31. Don't construct such. */ if ((int)l2 != l2) goto general_case; vtop--; vtop->c.i = l2; } else { general_case: /* call low level op generator */ if (t1 == VT_LLONG || t2 == VT_LLONG || (PTR_SIZE == 8 && (t1 == VT_PTR || t2 == VT_PTR))) gen_opl(op); else gen_opi(op); } } } /* generate a floating point operation with constant propagation */ static void gen_opif(int op) { int c1, c2; SValue *v1, *v2; long double f1, f2; v1 = vtop - 1; v2 = vtop; /* currently, we cannot do computations with forward symbols */ c1 = (v1->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST; c2 = (v2->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST; if (c1 && c2) { if (v1->type.t == VT_FLOAT) { f1 = v1->c.f; f2 = v2->c.f; } else if (v1->type.t == VT_DOUBLE) { f1 = v1->c.d; f2 = v2->c.d; } else { f1 = v1->c.ld; f2 = v2->c.ld; } /* NOTE: we only do constant propagation if finite number (not NaN or infinity) (ANSI spec) */ if (!ieee_finite(f1) || !ieee_finite(f2)) goto general_case; switch(op) { case '+': f1 += f2; break; case '-': f1 -= f2; break; case '*': f1 *= f2; break; case '/': #if HAVE_FLOAT if (f2 == 0.0) { if (const_wanted) tcc_error("division by zero in constant"); goto general_case; } #endif f1 /= f2; break; /* XXX: also handles tests ? */ default: goto general_case; } /* XXX: overflow test ? */ if (v1->type.t == VT_FLOAT) { v1->c.f = f1; } else if (v1->type.t == VT_DOUBLE) { v1->c.d = f1; } else { v1->c.ld = f1; } vtop--; } else { general_case: gen_opf(op); } } static int pointed_size(CType *type) { int align; return type_size(pointed_type(type), &align); } static void vla_runtime_pointed_size(CType *type) { int align; vla_runtime_type_size(pointed_type(type), &align); } static inline int is_null_pointer(SValue *p) { if ((p->r & (VT_VALMASK | VT_LVAL | VT_SYM)) != VT_CONST) return 0; return ((p->type.t & VT_BTYPE) == VT_INT && (uint32_t)p->c.i == 0) || ((p->type.t & VT_BTYPE) == VT_LLONG && p->c.i == 0) || ((p->type.t & VT_BTYPE) == VT_PTR && (PTR_SIZE == 4 ? (uint32_t)p->c.i == 0 : p->c.i == 0)); } static inline int is_integer_btype(int bt) { return (bt == VT_BYTE || bt == VT_SHORT || bt == VT_INT || bt == VT_LLONG); } /* check types for comparison or subtraction of pointers */ static void check_comparison_pointer_types(SValue *p1, SValue *p2, int op) { CType *type1, *type2, tmp_type1, tmp_type2; int bt1, bt2; /* null pointers are accepted for all comparisons as gcc */ if (is_null_pointer(p1) || is_null_pointer(p2)) return; type1 = &p1->type; type2 = &p2->type; bt1 = type1->t & VT_BTYPE; bt2 = type2->t & VT_BTYPE; /* accept comparison between pointer and integer with a warning */ if ((is_integer_btype(bt1) || is_integer_btype(bt2)) && op != '-') { if (op != TOK_LOR && op != TOK_LAND ) tcc_warning("comparison between pointer and integer"); return; } /* both must be pointers or implicit function pointers */ if (bt1 == VT_PTR) { type1 = pointed_type(type1); } else if (bt1 != VT_FUNC) goto invalid_operands; if (bt2 == VT_PTR) { type2 = pointed_type(type2); } else if (bt2 != VT_FUNC) { invalid_operands: tcc_error("invalid operands to binary %s", get_tok_str(op, NULL)); } if ((type1->t & VT_BTYPE) == VT_VOID || (type2->t & VT_BTYPE) == VT_VOID) return; tmp_type1 = *type1; tmp_type2 = *type2; tmp_type1.t &= ~(VT_DEFSIGN | VT_UNSIGNED | VT_CONSTANT | VT_VOLATILE); tmp_type2.t &= ~(VT_DEFSIGN | VT_UNSIGNED | VT_CONSTANT | VT_VOLATILE); if (!is_compatible_types(&tmp_type1, &tmp_type2)) { /* gcc-like error if '-' is used */ if (op == '-') goto invalid_operands; else tcc_warning("comparison of distinct pointer types lacks a cast"); } } /* generic gen_op: handles types problems */ ST_FUNC void gen_op(int op) { int u, t1, t2, bt1, bt2, t; CType type1; redo: t1 = vtop[-1].type.t; t2 = vtop[0].type.t; bt1 = t1 & VT_BTYPE; bt2 = t2 & VT_BTYPE; if (bt1 == VT_STRUCT || bt2 == VT_STRUCT) { tcc_error("operation on a struct"); } else if (bt1 == VT_FUNC || bt2 == VT_FUNC) { if (bt2 == VT_FUNC) { mk_pointer(&vtop->type); gaddrof(); } if (bt1 == VT_FUNC) { vswap(); mk_pointer(&vtop->type); gaddrof(); vswap(); } goto redo; } else if (bt1 == VT_PTR || bt2 == VT_PTR) { /* at least one operand is a pointer */ /* relational op: must be both pointers */ if (op >= TOK_ULT && op <= TOK_LOR) { check_comparison_pointer_types(vtop - 1, vtop, op); /* pointers are handled are unsigned */ #if PTR_SIZE == 8 t = VT_LLONG | VT_UNSIGNED; #else t = VT_INT | VT_UNSIGNED; #endif goto std_op; } /* if both pointers, then it must be the '-' op */ if (bt1 == VT_PTR && bt2 == VT_PTR) { if (op != '-') tcc_error("cannot use pointers here"); check_comparison_pointer_types(vtop - 1, vtop, op); /* XXX: check that types are compatible */ if (vtop[-1].type.t & VT_VLA) { vla_runtime_pointed_size(&vtop[-1].type); } else { vpushi(pointed_size(&vtop[-1].type)); } vrott(3); gen_opic(op); /* set to integer type */ #if PTR_SIZE == 8 vtop->type.t = VT_LLONG; #else vtop->type.t = VT_INT; #endif vswap(); gen_op(TOK_PDIV); } else { /* exactly one pointer : must be '+' or '-'. */ if (op != '-' && op != '+') tcc_error("cannot use pointers here"); /* Put pointer as first operand */ if (bt2 == VT_PTR) { vswap(); t = t1, t1 = t2, t2 = t; } #if PTR_SIZE == 4 if ((vtop[0].type.t & VT_BTYPE) == VT_LLONG) /* XXX: truncate here because gen_opl can't handle ptr + long long */ gen_cast(&int_type); #endif type1 = vtop[-1].type; type1.t &= ~VT_ARRAY; if (vtop[-1].type.t & VT_VLA) vla_runtime_pointed_size(&vtop[-1].type); else { u = pointed_size(&vtop[-1].type); if (u < 0) tcc_error("unknown array element size"); #if PTR_SIZE == 8 vpushll(u); #else /* XXX: cast to int ? (long long case) */ vpushi(u); #endif } gen_op('*'); #if 0 /* #ifdef CONFIG_TCC_BCHECK The main reason to removing this code: #include <stdio.h> int main () { int v[10]; int i = 10; int j = 9; fprintf(stderr, "v+i-j = %p\n", v+i-j); fprintf(stderr, "v+(i-j) = %p\n", v+(i-j)); } When this code is on. then the output looks like v+i-j = 0xfffffffe v+(i-j) = 0xbff84000 */ /* if evaluating constant expression, no code should be generated, so no bound check */ if (tcc_state->do_bounds_check && !const_wanted) { /* if bounded pointers, we generate a special code to test bounds */ if (op == '-') { vpushi(0); vswap(); gen_op('-'); } gen_bounded_ptr_add(); } else #endif { gen_opic(op); } /* put again type if gen_opic() swaped operands */ vtop->type = type1; } } else if (is_float(bt1) || is_float(bt2)) { /* compute bigger type and do implicit casts */ if (bt1 == VT_LDOUBLE || bt2 == VT_LDOUBLE) { t = VT_LDOUBLE; } else if (bt1 == VT_DOUBLE || bt2 == VT_DOUBLE) { t = VT_DOUBLE; } else { t = VT_FLOAT; } /* floats can only be used for a few operations */ if (op != '+' && op != '-' && op != '*' && op != '/' && (op < TOK_ULT || op > TOK_GT)) tcc_error("invalid operands for binary operation"); goto std_op; } else if (op == TOK_SHR || op == TOK_SAR || op == TOK_SHL) { t = bt1 == VT_LLONG ? VT_LLONG : VT_INT; if ((t1 & (VT_BTYPE | VT_UNSIGNED | VT_BITFIELD)) == (t | VT_UNSIGNED)) t |= VT_UNSIGNED; goto std_op; } else if (bt1 == VT_LLONG || bt2 == VT_LLONG) { /* cast to biggest op */ t = VT_LLONG; /* convert to unsigned if it does not fit in a long long */ if ((t1 & (VT_BTYPE | VT_UNSIGNED | VT_BITFIELD)) == (VT_LLONG | VT_UNSIGNED) || (t2 & (VT_BTYPE | VT_UNSIGNED | VT_BITFIELD)) == (VT_LLONG | VT_UNSIGNED)) t |= VT_UNSIGNED; goto std_op; } else { /* integer operations */ t = VT_INT; /* convert to unsigned if it does not fit in an integer */ if ((t1 & (VT_BTYPE | VT_UNSIGNED | VT_BITFIELD)) == (VT_INT | VT_UNSIGNED) || (t2 & (VT_BTYPE | VT_UNSIGNED | VT_BITFIELD)) == (VT_INT | VT_UNSIGNED)) t |= VT_UNSIGNED; std_op: /* XXX: currently, some unsigned operations are explicit, so we modify them here */ if (t & VT_UNSIGNED) { if (op == TOK_SAR) op = TOK_SHR; else if (op == '/') op = TOK_UDIV; else if (op == '%') op = TOK_UMOD; else if (op == TOK_LT) op = TOK_ULT; else if (op == TOK_GT) op = TOK_UGT; else if (op == TOK_LE) op = TOK_ULE; else if (op == TOK_GE) op = TOK_UGE; } vswap(); type1.t = t; gen_cast(&type1); vswap(); /* special case for shifts and long long: we keep the shift as an integer */ if (op == TOK_SHR || op == TOK_SAR || op == TOK_SHL) type1.t = VT_INT; gen_cast(&type1); if (is_float(t)) gen_opif(op); else gen_opic(op); if (op >= TOK_ULT && op <= TOK_GT) { /* relational op: the result is an int */ vtop->type.t = VT_INT; } else { vtop->type.t = t; } } // Make sure that we have converted to an rvalue: if (vtop->r & VT_LVAL) gv(is_float(vtop->type.t & VT_BTYPE) ? RC_FLOAT : RC_INT); } #ifndef TCC_TARGET_ARM /* generic itof for unsigned long long case */ static void gen_cvt_itof1(int t) { #ifdef TCC_TARGET_ARM64 gen_cvt_itof(t); #else if ((vtop->type.t & (VT_BTYPE | VT_UNSIGNED)) == (VT_LLONG | VT_UNSIGNED)) { if (t == VT_FLOAT) vpush_global_sym(&func_old_type, TOK___floatundisf); #if LDOUBLE_SIZE != 8 else if (t == VT_LDOUBLE) vpush_global_sym(&func_old_type, TOK___floatundixf); #endif else vpush_global_sym(&func_old_type, TOK___floatundidf); vrott(2); gfunc_call(1); vpushi(0); vtop->r = reg_fret(t); } else { gen_cvt_itof(t); } #endif } #endif /* generic ftoi for unsigned long long case */ static void gen_cvt_ftoi1(int t) { #ifdef TCC_TARGET_ARM64 gen_cvt_ftoi(t); #else int st; if (t == (VT_LLONG | VT_UNSIGNED)) { /* not handled natively */ st = vtop->type.t & VT_BTYPE; if (st == VT_FLOAT) vpush_global_sym(&func_old_type, TOK___fixunssfdi); #if LDOUBLE_SIZE != 8 else if (st == VT_LDOUBLE) vpush_global_sym(&func_old_type, TOK___fixunsxfdi); #endif else vpush_global_sym(&func_old_type, TOK___fixunsdfdi); vrott(2); gfunc_call(1); vpushi(0); vtop->r = REG_IRET; vtop->r2 = REG_LRET; } else { gen_cvt_ftoi(t); } #endif } /* force char or short cast */ static void force_charshort_cast(int t) { int bits, dbt; dbt = t & VT_BTYPE; /* XXX: add optimization if lvalue : just change type and offset */ if (dbt == VT_BYTE) bits = 8; else bits = 16; if (t & VT_UNSIGNED) { vpushi((1 << bits) - 1); gen_op('&'); } else { if ((vtop->type.t & VT_BTYPE) == VT_LLONG) bits = 64 - bits; else bits = 32 - bits; vpushi(bits); gen_op(TOK_SHL); /* result must be signed or the SAR is converted to an SHL This was not the case when "t" was a signed short and the last value on the stack was an unsigned int */ vtop->type.t &= ~VT_UNSIGNED; vpushi(bits); gen_op(TOK_SAR); } } /* cast 'vtop' to 'type'. Casting to bitfields is forbidden. */ static void gen_cast(CType *type) { int sbt, dbt, sf, df, c, p; /* special delayed cast for char/short */ /* XXX: in some cases (multiple cascaded casts), it may still be incorrect */ if (vtop->r & VT_MUSTCAST) { vtop->r &= ~VT_MUSTCAST; force_charshort_cast(vtop->type.t); } /* bitfields first get cast to ints */ if (vtop->type.t & VT_BITFIELD) { gv(RC_INT); } dbt = type->t & (VT_BTYPE | VT_UNSIGNED); sbt = vtop->type.t & (VT_BTYPE | VT_UNSIGNED); if (sbt != dbt) { sf = is_float(sbt); df = is_float(dbt); c = (vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST; p = (vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == (VT_CONST | VT_SYM); if (c) { /* constant case: we can do it now */ /* XXX: in ISOC, cannot do it if error in convert */ #if HAVE_FLOAT if (sbt == VT_FLOAT) vtop->c.ld = vtop->c.f; else if (sbt == VT_DOUBLE) vtop->c.ld = vtop->c.d; if (df) { if ((sbt & VT_BTYPE) == VT_LLONG) { if ((sbt & VT_UNSIGNED) || !(vtop->c.i >> 63)) vtop->c.ld = vtop->c.i; else vtop->c.ld = -(long double)-vtop->c.i; } else if(!sf) { if ((sbt & VT_UNSIGNED) || !(vtop->c.i >> 31)) vtop->c.ld = (uint32_t)vtop->c.i; else vtop->c.ld = -(long double)-(uint32_t)vtop->c.i; } if (dbt == VT_FLOAT) vtop->c.f = (float)vtop->c.ld; else if (dbt == VT_DOUBLE) vtop->c.d = (double)vtop->c.ld; } else if (sf && dbt == (VT_LLONG|VT_UNSIGNED)) { vtop->c.i = vtop->c.ld; } else if (sf && dbt == VT_BOOL) { vtop->c.i = (vtop->c.ld != 0); } else #endif { #if HAVE_FLOAT if(sf) vtop->c.i = vtop->c.ld; else #endif if (sbt == (VT_LLONG|VT_UNSIGNED)) ; else if (sbt & VT_UNSIGNED) vtop->c.i = (uint32_t)vtop->c.i; #if PTR_SIZE == 8 else if (sbt == VT_PTR) ; #endif else if (sbt != VT_LLONG) vtop->c.i = ((uint32_t)vtop->c.i | -(vtop->c.i & 0x80000000)); if (dbt == (VT_LLONG|VT_UNSIGNED)) ; else if (dbt == VT_BOOL) vtop->c.i = (vtop->c.i != 0); #if PTR_SIZE == 8 else if (dbt == VT_PTR) ; #endif else if (dbt != VT_LLONG) { uint32_t m = ((dbt & VT_BTYPE) == VT_BYTE ? 0xff : (dbt & VT_BTYPE) == VT_SHORT ? 0xffff : 0xffffffff); vtop->c.i &= m; if (!(dbt & VT_UNSIGNED)) vtop->c.i |= -(vtop->c.i & ((m >> 1) + 1)); } } } else if (p && dbt == VT_BOOL) { vtop->r = VT_CONST; vtop->c.i = 1; } else { /* non constant case: generate code */ if (sf && df) { /* convert from fp to fp */ gen_cvt_ftof(dbt); } else if (df) { /* convert int to fp */ gen_cvt_itof1(dbt); } else if (sf) { /* convert fp to int */ if (dbt == VT_BOOL) { vpushi(0); gen_op(TOK_NE); } else { /* we handle char/short/etc... with generic code */ if (dbt != (VT_INT | VT_UNSIGNED) && dbt != (VT_LLONG | VT_UNSIGNED) && dbt != VT_LLONG) dbt = VT_INT; gen_cvt_ftoi1(dbt); if (dbt == VT_INT && (type->t & (VT_BTYPE | VT_UNSIGNED)) != dbt) { /* additional cast for char/short... */ vtop->type.t = dbt; gen_cast(type); } } #if PTR_SIZE == 4 } else if ((dbt & VT_BTYPE) == VT_LLONG) { if ((sbt & VT_BTYPE) != VT_LLONG) { /* scalar to long long */ /* machine independent conversion */ gv(RC_INT); /* generate high word */ if (sbt == (VT_INT | VT_UNSIGNED)) { vpushi(0); gv(RC_INT); } else { if (sbt == VT_PTR) { /* cast from pointer to int before we apply shift operation, which pointers don't support*/ gen_cast(&int_type); } gv_dup(); vpushi(31); gen_op(TOK_SAR); } /* patch second register */ vtop[-1].r2 = vtop->r; vpop(); } #else } else if ((dbt & VT_BTYPE) == VT_LLONG || (dbt & VT_BTYPE) == VT_PTR || (dbt & VT_BTYPE) == VT_FUNC) { if ((sbt & VT_BTYPE) != VT_LLONG && (sbt & VT_BTYPE) != VT_PTR && (sbt & VT_BTYPE) != VT_FUNC) { /* need to convert from 32bit to 64bit */ gv(RC_INT); if (sbt != (VT_INT | VT_UNSIGNED)) { #if defined(TCC_TARGET_ARM64) gen_cvt_sxtw(); #elif defined(TCC_TARGET_X86_64) int r = gv(RC_INT); /* x86_64 specific: movslq */ o(0x6348); o(0xc0 + (REG_VALUE(r) << 3) + REG_VALUE(r)); #else #error #endif } } #endif } else if (dbt == VT_BOOL) { /* scalar to bool */ vpushi(0); gen_op(TOK_NE); } else if ((dbt & VT_BTYPE) == VT_BYTE || (dbt & VT_BTYPE) == VT_SHORT) { if (sbt == VT_PTR) { vtop->type.t = VT_INT; tcc_warning("nonportable conversion from pointer to char/short"); } force_charshort_cast(dbt); #if PTR_SIZE == 4 } else if ((dbt & VT_BTYPE) == VT_INT) { /* scalar to int */ if ((sbt & VT_BTYPE) == VT_LLONG) { /* from long long: just take low order word */ lexpand(); vpop(); } /* if lvalue and single word type, nothing to do because the lvalue already contains the real type size (see VT_LVAL_xxx constants) */ #endif } } } else if ((dbt & VT_BTYPE) == VT_PTR && !(vtop->r & VT_LVAL)) { /* if we are casting between pointer types, we must update the VT_LVAL_xxx size */ vtop->r = (vtop->r & ~VT_LVAL_TYPE) | (lvalue_type(type->ref->type.t) & VT_LVAL_TYPE); } vtop->type = *type; } /* return type size as known at compile time. Put alignment at 'a' */ ST_FUNC int type_size(CType *type, int *a) { Sym *s; int bt; bt = type->t & VT_BTYPE; if (bt == VT_STRUCT) { /* struct/union */ s = type->ref; *a = s->r; return s->c; } else if (bt == VT_PTR) { if (type->t & VT_ARRAY) { int ts; s = type->ref; ts = type_size(&s->type, a); if (ts < 0 && s->c < 0) ts = -ts; return ts * s->c; } else { *a = PTR_SIZE; return PTR_SIZE; } } else if (bt == VT_LDOUBLE) { *a = LDOUBLE_ALIGN; return LDOUBLE_SIZE; } else if (bt == VT_DOUBLE || bt == VT_LLONG) { #ifdef TCC_TARGET_I386 #ifdef TCC_TARGET_PE *a = 8; #else *a = 4; #endif #elif defined(TCC_TARGET_ARM) #ifdef TCC_ARM_EABI *a = 8; #else *a = 4; #endif #else *a = 8; #endif return 8; } else if (bt == VT_INT || bt == VT_FLOAT) { *a = 4; return 4; } else if (bt == VT_SHORT) { *a = 2; return 2; } else if (bt == VT_QLONG || bt == VT_QFLOAT) { *a = 8; return 16; } else if (bt == VT_ENUM) { *a = 4; /* Enums might be incomplete, so don't just return '4' here. */ return type->ref->c; } else { /* char, void, function, _Bool */ *a = 1; return 1; } } /* push type size as known at runtime time on top of value stack. Put alignment at 'a' */ ST_FUNC void vla_runtime_type_size(CType *type, int *a) { if (type->t & VT_VLA) { type_size(&type->ref->type, a); vset(&int_type, VT_LOCAL|VT_LVAL, type->ref->c); } else { vpushi(type_size(type, a)); } } static void vla_sp_restore(void) { if (vlas_in_scope) { gen_vla_sp_restore(vla_sp_loc); } } static void vla_sp_restore_root(void) { if (vlas_in_scope) { gen_vla_sp_restore(vla_sp_root_loc); } } /* return the pointed type of t */ static inline CType *pointed_type(CType *type) { return &type->ref->type; } /* modify type so that its it is a pointer to type. */ ST_FUNC void mk_pointer(CType *type) { Sym *s; s = sym_push(SYM_FIELD, type, 0, -1); type->t = VT_PTR | (type->t & ~VT_TYPE); type->ref = s; } /* compare function types. OLD functions match any new functions */ static int is_compatible_func(CType *type1, CType *type2) { Sym *s1, *s2; s1 = type1->ref; s2 = type2->ref; if (!is_compatible_types(&s1->type, &s2->type)) return 0; /* check func_call */ if (s1->a.func_call != s2->a.func_call) return 0; /* XXX: not complete */ if (s1->c == FUNC_OLD || s2->c == FUNC_OLD) return 1; if (s1->c != s2->c) return 0; while (s1 != NULL) { if (s2 == NULL) return 0; if (!is_compatible_parameter_types(&s1->type, &s2->type)) return 0; s1 = s1->next; s2 = s2->next; } if (s2) return 0; return 1; } /* return true if type1 and type2 are the same. If unqualified is true, qualifiers on the types are ignored. - enums are not checked as gcc __builtin_types_compatible_p () */ static int compare_types(CType *type1, CType *type2, int unqualified) { int bt1, t1, t2; t1 = type1->t & VT_TYPE; t2 = type2->t & VT_TYPE; if (unqualified) { /* strip qualifiers before comparing */ t1 &= ~(VT_CONSTANT | VT_VOLATILE); t2 &= ~(VT_CONSTANT | VT_VOLATILE); } /* Default Vs explicit signedness only matters for char */ if ((t1 & VT_BTYPE) != VT_BYTE) { t1 &= ~VT_DEFSIGN; t2 &= ~VT_DEFSIGN; } /* An enum is compatible with (unsigned) int. Ideally we would store the enums signedness in type->ref.a.<some_bit> and only accept unsigned enums with unsigned int and vice versa. But one of our callers (gen_assign_cast) always strips VT_UNSIGNED from pointer target types, so we can't add it here either. */ if ((t1 & VT_BTYPE) == VT_ENUM) { t1 = VT_INT; if (type1->ref->a.unsigned_enum) t1 |= VT_UNSIGNED; } if ((t2 & VT_BTYPE) == VT_ENUM) { t2 = VT_INT; if (type2->ref->a.unsigned_enum) t2 |= VT_UNSIGNED; } /* XXX: bitfields ? */ if (t1 != t2) return 0; /* test more complicated cases */ bt1 = t1 & VT_BTYPE; if (bt1 == VT_PTR) { type1 = pointed_type(type1); type2 = pointed_type(type2); return is_compatible_types(type1, type2); } else if (bt1 == VT_STRUCT) { return (type1->ref == type2->ref); } else if (bt1 == VT_FUNC) { return is_compatible_func(type1, type2); } else { return 1; } } /* return true if type1 and type2 are exactly the same (including qualifiers). */ static int is_compatible_types(CType *type1, CType *type2) { return compare_types(type1,type2,0); } /* return true if type1 and type2 are the same (ignoring qualifiers). */ static int is_compatible_parameter_types(CType *type1, CType *type2) { return compare_types(type1,type2,1); } /* print a type. If 'varstr' is not NULL, then the variable is also printed in the type */ /* XXX: union */ /* XXX: add array and function pointers */ static void type_to_str(char *buf, int buf_size, CType *type, const char *varstr) { int bt, v, t; Sym *s, *sa; char buf1[256]; const char *tstr; t = type->t; bt = t & VT_BTYPE; buf[0] = '\0'; if (t & VT_CONSTANT) pstrcat(buf, buf_size, "const "); if (t & VT_VOLATILE) pstrcat(buf, buf_size, "volatile "); if ((t & (VT_DEFSIGN | VT_UNSIGNED)) == (VT_DEFSIGN | VT_UNSIGNED)) pstrcat(buf, buf_size, "unsigned "); else if (t & VT_DEFSIGN) pstrcat(buf, buf_size, "signed "); if (t & VT_EXTERN) pstrcat(buf, buf_size, "extern "); if (t & VT_STATIC) pstrcat(buf, buf_size, "static "); if (t & VT_TYPEDEF) pstrcat(buf, buf_size, "typedef "); if (t & VT_INLINE) pstrcat(buf, buf_size, "inline "); buf_size -= strlen(buf); buf += strlen(buf); switch(bt) { case VT_VOID: tstr = "void"; goto add_tstr; case VT_BOOL: tstr = "_Bool"; goto add_tstr; case VT_BYTE: tstr = "char"; goto add_tstr; case VT_SHORT: tstr = "short"; goto add_tstr; case VT_INT: tstr = "int"; goto add_tstr; case VT_LONG: tstr = "long"; goto add_tstr; case VT_LLONG: tstr = "long long"; goto add_tstr; case VT_FLOAT: tstr = "float"; goto add_tstr; case VT_DOUBLE: tstr = "double"; goto add_tstr; case VT_LDOUBLE: tstr = "long double"; add_tstr: pstrcat(buf, buf_size, tstr); break; case VT_ENUM: case VT_STRUCT: if (bt == VT_STRUCT) tstr = "struct "; else tstr = "enum "; pstrcat(buf, buf_size, tstr); v = type->ref->v & ~SYM_STRUCT; if (v >= SYM_FIRST_ANOM) pstrcat(buf, buf_size, "<anonymous>"); else pstrcat(buf, buf_size, get_tok_str(v, NULL)); break; case VT_FUNC: s = type->ref; type_to_str(buf, buf_size, &s->type, varstr); pstrcat(buf, buf_size, "("); sa = s->next; while (sa != NULL) { type_to_str(buf1, sizeof(buf1), &sa->type, NULL); pstrcat(buf, buf_size, buf1); sa = sa->next; if (sa) pstrcat(buf, buf_size, ", "); } pstrcat(buf, buf_size, ")"); goto no_var; case VT_PTR: s = type->ref; if (t & VT_ARRAY) { snprintf(buf1, sizeof(buf1), "%s[%ld]", varstr ? varstr : "", s->c); type_to_str(buf, buf_size, &s->type, buf1); goto no_var; } pstrcpy(buf1, sizeof(buf1), "*"); if (t & VT_CONSTANT) pstrcat(buf1, buf_size, "const "); if (t & VT_VOLATILE) pstrcat(buf1, buf_size, "volatile "); if (varstr) pstrcat(buf1, sizeof(buf1), varstr); type_to_str(buf, buf_size, &s->type, buf1); goto no_var; } if (varstr) { pstrcat(buf, buf_size, " "); pstrcat(buf, buf_size, varstr); } no_var: ; } /* verify type compatibility to store vtop in 'dt' type, and generate casts if needed. */ static void gen_assign_cast(CType *dt) { CType *st, *type1, *type2, tmp_type1, tmp_type2; char buf1[256], buf2[256]; int dbt, sbt; st = &vtop->type; /* source type */ dbt = dt->t & VT_BTYPE; sbt = st->t & VT_BTYPE; if (sbt == VT_VOID || dbt == VT_VOID) { if (sbt == VT_VOID && dbt == VT_VOID) ; /* It is Ok if both are void A test program: void func1() {} void func2() { return func1(); } gcc accepts this program */ else tcc_error("cannot cast from/to void"); } if (dt->t & VT_CONSTANT) tcc_warning("assignment of read-only location"); switch(dbt) { case VT_PTR: /* special cases for pointers */ /* '0' can also be a pointer */ if (is_null_pointer(vtop)) goto type_ok; /* accept implicit pointer to integer cast with warning */ if (is_integer_btype(sbt)) { tcc_warning("assignment makes pointer from integer without a cast"); goto type_ok; } type1 = pointed_type(dt); /* a function is implicitly a function pointer */ if (sbt == VT_FUNC) { if ((type1->t & VT_BTYPE) != VT_VOID && !is_compatible_types(pointed_type(dt), st)) tcc_warning("assignment from incompatible pointer type"); goto type_ok; } if (sbt != VT_PTR) goto error; type2 = pointed_type(st); if ((type1->t & VT_BTYPE) == VT_VOID || (type2->t & VT_BTYPE) == VT_VOID) { /* void * can match anything */ } else { /* exact type match, except for qualifiers */ tmp_type1 = *type1; tmp_type2 = *type2; tmp_type1.t &= ~(VT_CONSTANT | VT_VOLATILE); tmp_type2.t &= ~(VT_CONSTANT | VT_VOLATILE); if (!is_compatible_types(&tmp_type1, &tmp_type2)) { /* Like GCC don't warn by default for merely changes in pointer target signedness. Do warn for different base types, though, in particular for unsigned enums and signed int targets. */ if ((tmp_type1.t & (VT_DEFSIGN | VT_UNSIGNED)) != (tmp_type2.t & (VT_DEFSIGN | VT_UNSIGNED)) && (tmp_type1.t & VT_BTYPE) == (tmp_type2.t & VT_BTYPE)) ; else tcc_warning("assignment from incompatible pointer type"); } } /* check const and volatile */ if ((!(type1->t & VT_CONSTANT) && (type2->t & VT_CONSTANT)) || (!(type1->t & VT_VOLATILE) && (type2->t & VT_VOLATILE))) tcc_warning("assignment discards qualifiers from pointer target type"); break; case VT_BYTE: case VT_SHORT: case VT_INT: case VT_LLONG: if (sbt == VT_PTR || sbt == VT_FUNC) { tcc_warning("assignment makes integer from pointer without a cast"); } else if (sbt == VT_STRUCT) { goto case_VT_STRUCT; } /* XXX: more tests */ break; case VT_STRUCT: case_VT_STRUCT: tmp_type1 = *dt; tmp_type2 = *st; tmp_type1.t &= ~(VT_CONSTANT | VT_VOLATILE); tmp_type2.t &= ~(VT_CONSTANT | VT_VOLATILE); if (!is_compatible_types(&tmp_type1, &tmp_type2)) { error: type_to_str(buf1, sizeof(buf1), st, NULL); type_to_str(buf2, sizeof(buf2), dt, NULL); tcc_error("cannot cast '%s' to '%s'", buf1, buf2); } break; } type_ok: gen_cast(dt); } /* store vtop in lvalue pushed on stack */ ST_FUNC void vstore(void) { int sbt, dbt, ft, r, t, size, align, bit_size, bit_pos, rc, delayed_cast; ft = vtop[-1].type.t; sbt = vtop->type.t & VT_BTYPE; dbt = ft & VT_BTYPE; if ((((sbt == VT_INT || sbt == VT_SHORT) && dbt == VT_BYTE) || (sbt == VT_INT && dbt == VT_SHORT)) && !(vtop->type.t & VT_BITFIELD)) { /* optimize char/short casts */ delayed_cast = VT_MUSTCAST; vtop->type.t = (ft & VT_TYPE & ~VT_BITFIELD & ((1 << VT_STRUCT_SHIFT) - 1)); /* XXX: factorize */ if (ft & VT_CONSTANT) tcc_warning("assignment of read-only location"); } else { delayed_cast = 0; if (!(ft & VT_BITFIELD)) gen_assign_cast(&vtop[-1].type); } if (sbt == VT_STRUCT) { /* if structure, only generate pointer */ /* structure assignment : generate memcpy */ /* XXX: optimize if small size */ size = type_size(&vtop->type, &align); /* destination */ vswap(); vtop->type.t = VT_PTR; gaddrof(); /* address of memcpy() */ #ifdef TCC_ARM_EABI if(!(align & 7)) vpush_global_sym(&func_old_type, TOK_memcpy8); else if(!(align & 3)) vpush_global_sym(&func_old_type, TOK_memcpy4); else #endif /* Use memmove, rather than memcpy, as dest and src may be same: */ #if BOOTSTRAP && __arm__ vpush_global_sym(&func_old_type, TOK___memmove); #else vpush_global_sym(&func_old_type, TOK_memmove); #endif vswap(); /* source */ vpushv(vtop - 2); vtop->type.t = VT_PTR; gaddrof(); /* type size */ vpushi(size); gfunc_call(3); /* leave source on stack */ } else if (ft & VT_BITFIELD) { /* bitfield store handling */ /* save lvalue as expression result (example: s.b = s.a = n;) */ vdup(), vtop[-1] = vtop[-2]; bit_pos = (ft >> VT_STRUCT_SHIFT) & 0x3f; bit_size = (ft >> (VT_STRUCT_SHIFT + 6)) & 0x3f; /* remove bit field info to avoid loops */ vtop[-1].type.t = ft & ~VT_BITFIELD & ((1 << VT_STRUCT_SHIFT) - 1); if((ft & VT_BTYPE) == VT_BOOL) { gen_cast(&vtop[-1].type); vtop[-1].type.t = (vtop[-1].type.t & ~VT_BTYPE) | (VT_BYTE | VT_UNSIGNED); } /* duplicate destination */ vdup(); vtop[-1] = vtop[-2]; /* mask and shift source */ if((ft & VT_BTYPE) != VT_BOOL) { if((ft & VT_BTYPE) == VT_LLONG) { vpushll((1ULL << bit_size) - 1ULL); } else { vpushi((1 << bit_size) - 1); } gen_op('&'); } vpushi(bit_pos); gen_op(TOK_SHL); /* load destination, mask and or with source */ vswap(); if((ft & VT_BTYPE) == VT_LLONG) { vpushll(~(((1ULL << bit_size) - 1ULL) << bit_pos)); } else { vpushi(~(((1 << bit_size) - 1) << bit_pos)); } gen_op('&'); gen_op('|'); /* store result */ vstore(); /* ... and discard */ vpop(); } else { #ifdef CONFIG_TCC_BCHECK /* bound check case */ if (vtop[-1].r & VT_MUSTBOUND) { vswap(); gbound(); vswap(); } #endif rc = RC_INT; if (is_float(ft)) { rc = RC_FLOAT; #ifdef TCC_TARGET_X86_64 if ((ft & VT_BTYPE) == VT_LDOUBLE) { rc = RC_ST0; } else if ((ft & VT_BTYPE) == VT_QFLOAT) { rc = RC_FRET; } #endif } r = gv(rc); /* generate value */ /* if lvalue was saved on stack, must read it */ if ((vtop[-1].r & VT_VALMASK) == VT_LLOCAL) { SValue sv; t = get_reg(RC_INT); #if PTR_SIZE == 8 sv.type.t = VT_PTR; #else sv.type.t = VT_INT; #endif sv.r = VT_LOCAL | VT_LVAL; sv.c.i = vtop[-1].c.i; load(t, &sv); vtop[-1].r = t | VT_LVAL; } /* two word case handling : store second register at word + 4 (or +8 for x86-64) */ #if PTR_SIZE == 8 if (((ft & VT_BTYPE) == VT_QLONG) || ((ft & VT_BTYPE) == VT_QFLOAT)) { int addr_type = VT_LLONG, load_size = 8, load_type = ((vtop->type.t & VT_BTYPE) == VT_QLONG) ? VT_LLONG : VT_DOUBLE; #else if ((ft & VT_BTYPE) == VT_LLONG) { int addr_type = VT_INT, load_size = 4, load_type = VT_INT; #endif vtop[-1].type.t = load_type; store(r, vtop - 1); vswap(); /* convert to int to increment easily */ vtop->type.t = addr_type; gaddrof(); vpushi(load_size); gen_op('+'); vtop->r |= VT_LVAL; vswap(); vtop[-1].type.t = load_type; /* XXX: it works because r2 is spilled last ! */ store(vtop->r2, vtop - 1); } else { store(r, vtop - 1); } vswap(); vtop--; /* NOT vpop() because on x86 it would flush the fp stack */ vtop->r |= delayed_cast; } } /* post defines POST/PRE add. c is the token ++ or -- */ ST_FUNC void inc(int post, int c) { test_lvalue(); vdup(); /* save lvalue */ if (post) { gv_dup(); /* duplicate value */ vrotb(3); vrotb(3); } /* add constant */ vpushi(c - TOK_MID); gen_op('+'); vstore(); /* store value */ if (post) vpop(); /* if post op, return saved value */ } ST_FUNC void parse_mult_str (CString *astr, const char *msg) { /* read the string */ if (tok != TOK_STR) expect(msg); cstr_new(astr); while (tok == TOK_STR) { /* XXX: add \0 handling too ? */ cstr_cat(astr, tokc.str.data, -1); next(); } cstr_ccat(astr, '\0'); } /* If I is >= 1 and a power of two, returns log2(i)+1. If I is 0 returns 0. */ static int exact_log2p1(int i) { int ret; if (!i) return 0; for (ret = 1; i >= 1 << 8; ret += 8) i >>= 8; if (i >= 1 << 4) ret += 4, i >>= 4; if (i >= 1 << 2) ret += 2, i >>= 2; if (i >= 1 << 1) ret++; return ret; } /* Parse GNUC __attribute__ extension. Currently, the following extensions are recognized: - aligned(n) : set data/function alignment. - packed : force data alignment to 1 - section(x) : generate data/code in this section. - unused : currently ignored, but may be used someday. - regparm(n) : pass function parameters in registers (i386 only) */ static void parse_attribute(AttributeDef *ad) { int t, n; CString astr; while (tok == TOK_ATTRIBUTE1 || tok == TOK_ATTRIBUTE2) { next(); skip('('); skip('('); while (tok != ')') { if (tok < TOK_IDENT) expect("attribute name"); t = tok; next(); switch(t) { case TOK_SECTION1: case TOK_SECTION2: skip('('); parse_mult_str(&astr, "section name"); ad->section = find_section(tcc_state, (char *)astr.data); skip(')'); cstr_free(&astr); break; case TOK_ALIAS1: case TOK_ALIAS2: skip('('); parse_mult_str(&astr, "alias(\"target\")"); ad->alias_target = /* save string as token, for later */ tok_alloc((char*)astr.data, astr.size-1)->tok; skip(')'); cstr_free(&astr); break; case TOK_VISIBILITY1: case TOK_VISIBILITY2: skip('('); parse_mult_str(&astr, "visibility(\"default|hidden|internal|protected\")"); if (!strcmp (astr.data, "default")) ad->a.visibility = STV_DEFAULT; else if (!strcmp (astr.data, "hidden")) ad->a.visibility = STV_HIDDEN; else if (!strcmp (astr.data, "internal")) ad->a.visibility = STV_INTERNAL; else if (!strcmp (astr.data, "protected")) ad->a.visibility = STV_PROTECTED; else expect("visibility(\"default|hidden|internal|protected\")"); skip(')'); cstr_free(&astr); break; case TOK_ALIGNED1: case TOK_ALIGNED2: if (tok == '(') { next(); n = expr_const(); if (n <= 0 || (n & (n - 1)) != 0) tcc_error("alignment must be a positive power of two"); skip(')'); } else { n = MAX_ALIGN; } ad->a.aligned = exact_log2p1(n); if (n != 1 << (ad->a.aligned - 1)) tcc_error("alignment of %d is larger than implemented", n); break; case TOK_PACKED1: case TOK_PACKED2: ad->a.packed = 1; break; case TOK_WEAK1: case TOK_WEAK2: ad->a.weak = 1; break; case TOK_UNUSED1: case TOK_UNUSED2: /* currently, no need to handle it because tcc does not track unused objects */ break; case TOK_NORETURN1: case TOK_NORETURN2: /* currently, no need to handle it because tcc does not track unused objects */ break; case TOK_CDECL1: case TOK_CDECL2: case TOK_CDECL3: ad->a.func_call = FUNC_CDECL; break; case TOK_STDCALL1: case TOK_STDCALL2: case TOK_STDCALL3: ad->a.func_call = FUNC_STDCALL; break; #ifdef TCC_TARGET_I386 case TOK_REGPARM1: case TOK_REGPARM2: skip('('); n = expr_const(); if (n > 3) n = 3; else if (n < 0) n = 0; if (n > 0) ad->a.func_call = FUNC_FASTCALL1 + n - 1; skip(')'); break; case TOK_FASTCALL1: case TOK_FASTCALL2: case TOK_FASTCALL3: ad->a.func_call = FUNC_FASTCALLW; break; #endif case TOK_MODE: skip('('); switch(tok) { case TOK_MODE_DI: ad->a.mode = VT_LLONG + 1; break; case TOK_MODE_QI: ad->a.mode = VT_BYTE + 1; break; case TOK_MODE_HI: ad->a.mode = VT_SHORT + 1; break; case TOK_MODE_SI: case TOK_MODE_word: ad->a.mode = VT_INT + 1; break; default: tcc_warning("__mode__(%s) not supported\n", get_tok_str(tok, NULL)); break; } next(); skip(')'); break; case TOK_DLLEXPORT: ad->a.func_export = 1; break; case TOK_DLLIMPORT: ad->a.func_import = 1; break; default: if (tcc_state->warn_unsupported) tcc_warning("'%s' attribute ignored", get_tok_str(t, NULL)); /* skip parameters */ if (tok == '(') { int parenthesis = 0; do { if (tok == '(') parenthesis++; else if (tok == ')') parenthesis--; next(); } while (parenthesis && tok != -1); } break; } if (tok != ',') break; next(); } skip(')'); skip(')'); } } static Sym * find_field (CType *type, int v) { Sym *s = type->ref; v |= SYM_FIELD; while ((s = s->next) != NULL) { if ((s->v & SYM_FIELD) && (s->type.t & VT_BTYPE) == VT_STRUCT && (s->v & ~SYM_FIELD) >= SYM_FIRST_ANOM) { Sym *ret = find_field (&s->type, v); if (ret) return ret; } if (s->v == v) break; } return s; } static void struct_add_offset (Sym *s, int offset) { while ((s = s->next) != NULL) { if ((s->v & SYM_FIELD) && (s->type.t & VT_BTYPE) == VT_STRUCT && (s->v & ~SYM_FIELD) >= SYM_FIRST_ANOM) { struct_add_offset(s->type.ref, offset); } else s->c += offset; } } static void struct_layout(CType *type, AttributeDef *ad) { int align, maxalign, offset, c, bit_pos, bt, prevbt, prev_bit_size; int pcc = !tcc_state->ms_bitfields; int packwarn = tcc_state->warn_gcc_compat; int typealign, bit_size, size; Sym *f; if (ad->a.aligned) maxalign = 1 << (ad->a.aligned - 1); else maxalign = 1; offset = 0; c = 0; bit_pos = 0; prevbt = VT_STRUCT; /* make it never match */ prev_bit_size = 0; size = 0; for (f = type->ref->next; f; f = f->next) { size = type_size(&f->type, &typealign); if (f->type.t & VT_BITFIELD) bit_size = (f->type.t >> (VT_STRUCT_SHIFT + 6)) & 0x3f; else bit_size = -1; if (bit_size == 0 && pcc) { /* Zero-width bit-fields in PCC mode aren't affected by any packing (attribute or pragma). */ align = typealign; } else if (f->r > 1) { align = f->r; } else if (ad->a.packed || f->r == 1) { align = 1; /* Packed fields or packed records don't let the base type influence the records type alignment. */ typealign = 1; } else { align = typealign; } if (type->ref->type.t != TOK_STRUCT) { if (pcc && bit_size >= 0) size = (bit_size + 7) >> 3; /* Bit position is already zero from our caller. */ offset = 0; if (size > c) c = size; } else if (bit_size < 0) { int addbytes = pcc ? (bit_pos + 7) >> 3 : 0; prevbt = VT_STRUCT; prev_bit_size = 0; c = (c + addbytes + align - 1) & -align; offset = c; if (size > 0) c += size; bit_pos = 0; } else { /* A bit-field. Layout is more complicated. There are two options TCC implements: PCC compatible and MS compatible (PCC compatible is what GCC uses for almost all targets). In PCC layout the overall size of the struct (in c) is _excluding_ the current run of bit-fields (that is, there's at least additional bit_pos bits after c). In MS layout c does include the current run of bit-fields. This matters for calculating the natural alignment buckets in PCC mode. */ /* 'align' will be used to influence records alignment, so it's the max of specified and type alignment, except in certain cases that depend on the mode. */ if (align < typealign) align = typealign; if (pcc) { /* In PCC layout a non-packed bit-field is placed adjacent to the preceding bit-fields, except if it would overflow its container (depending on base type) or it's a zero-width bit-field. Packed non-zero-width bit-fields always are placed adjacent. */ int ofs = (c * 8 + bit_pos) % (typealign * 8); int ofs2 = ofs + bit_size + (typealign * 8) - 1; if (bit_size == 0 || (typealign != 1 && (ofs2 / (typealign * 8)) > (size/typealign))) { c = (c + ((bit_pos + 7) >> 3) + typealign - 1) & -typealign; bit_pos = 0; } else if (bit_pos + bit_size > size * 8) { c += bit_pos >> 3; bit_pos &= 7; if (bit_pos + bit_size > size * 8) { c += 1, bit_pos = 0; if ((ad->a.packed || f->r) && packwarn) { tcc_warning("struct layout not compatible with GCC (internal limitation)"); packwarn = 0; } } } offset = c; /* In PCC layout named bit-fields influence the alignment of the containing struct using the base types alignment, except for packed fields (which here have correct align/typealign). */ if ((f->v & SYM_FIRST_ANOM)) align = 1; } else { bt = f->type.t & VT_BTYPE; if ((bit_pos + bit_size > size * 8) || (bit_size > 0) == (bt != prevbt)) { c = (c + typealign - 1) & -typealign; offset = c; bit_pos = 0; /* In MS bitfield mode a bit-field run always uses at least as many bits as the underlying type. To start a new run it's also required that this or the last bit-field had non-zero width. */ if (bit_size || prev_bit_size) c += size; } /* In MS layout the records alignment is normally influenced by the field, except for a zero-width field at the start of a run (but by further zero-width fields it is again). */ if (bit_size == 0 && prevbt != bt) align = 1; prevbt = bt; prev_bit_size = bit_size; } f->type.t = (f->type.t & ~(0x3f << VT_STRUCT_SHIFT)) | (bit_pos << VT_STRUCT_SHIFT); bit_pos += bit_size; if (pcc && bit_pos >= size * 8) { c += size; bit_pos -= size * 8; } } if (align > maxalign) maxalign = align; #if 0 printf("set field %s offset=%d", get_tok_str(f->v & ~SYM_FIELD, NULL), offset); if (f->type.t & VT_BITFIELD) { printf(" pos=%d size=%d", (f->type.t >> VT_STRUCT_SHIFT) & 0x3f, (f->type.t >> (VT_STRUCT_SHIFT + 6)) & 0x3f); } printf("\n"); #endif if (f->v & SYM_FIRST_ANOM && (f->type.t & VT_BTYPE) == VT_STRUCT) { Sym *ass; /* An anonymous struct/union. Adjust member offsets to reflect the real offset of our containing struct. Also set the offset of this anon member inside the outer struct to be zero. Via this it works when accessing the field offset directly (from base object), as well as when recursing members in initializer handling. */ int v2 = f->type.ref->v; if (!(v2 & SYM_FIELD) && (v2 & ~SYM_STRUCT) < SYM_FIRST_ANOM) { Sym **pps; /* This happens only with MS extensions. The anon member has a named struct type, so it potentially is shared with other references. We need to unshare members so we can modify them. */ ass = f->type.ref; f->type.ref = sym_push(anon_sym++ | SYM_FIELD, &f->type.ref->type, 0, f->type.ref->c); pps = &f->type.ref->next; while ((ass = ass->next) != NULL) { *pps = sym_push(ass->v, &ass->type, 0, ass->c); pps = &((*pps)->next); } *pps = NULL; } struct_add_offset(f->type.ref, offset); f->c = 0; } else { f->c = offset; } f->r = 0; } /* store size and alignment */ type->ref->c = (c + (pcc ? (bit_pos + 7) >> 3 : 0) + maxalign - 1) & -maxalign; type->ref->r = maxalign; if (offset + size > type->ref->c) tcc_warning("will touch memory past end of the struct (internal limitation)"); } /* enum/struct/union declaration. u is either VT_ENUM or VT_STRUCT */ static void struct_decl(CType *type, AttributeDef *ad, int u) { int a, v, size, align, flexible, alignoverride; long c; int bit_size, bsize, bt; Sym *s, *ss, **ps; AttributeDef ad1; CType type1, btype; a = tok; /* save decl type */ next(); if (tok == TOK_ATTRIBUTE1 || tok == TOK_ATTRIBUTE2) parse_attribute(ad); if (tok != '{') { v = tok; next(); /* struct already defined ? return it */ if (v < TOK_IDENT) expect("struct/union/enum name"); s = struct_find(v); if (s && (s->scope == local_scope || (tok != '{' && tok != ';'))) { if (s->type.t != a) tcc_error("redefinition of '%s'", get_tok_str(v, NULL)); goto do_decl; } } else { v = anon_sym++; } /* Record the original enum/struct/union token. */ type1.t = a; type1.ref = NULL; /* we put an undefined size for struct/union */ s = sym_push(v | SYM_STRUCT, &type1, 0, -1); s->r = 0; /* default alignment is zero as gcc */ /* put struct/union/enum name in type */ do_decl: type->t = u; type->ref = s; if (tok == '{') { next(); if (s->c != -1) tcc_error("struct/union/enum already defined"); /* cannot be empty */ c = 0; /* non empty enums are not allowed */ if (a == TOK_ENUM) { int seen_neg = 0; int seen_wide = 0; for(;;) { CType *t = &int_type; v = tok; if (v < TOK_UIDENT) expect("identifier"); ss = sym_find(v); if (ss && !local_stack) tcc_error("redefinition of enumerator '%s'", get_tok_str(v, NULL)); next(); if (tok == '=') { next(); #if PTR_SIZE == 8 c = expr_const64(); #else /* We really want to support long long enums on i386 as well, but the Sym structure only holds a 'long' for associated constants, and enlarging it would bump its size (no available padding). So punt for now. */ c = expr_const(); #endif } if (c < 0) seen_neg = 1; if (c != (int)c && (unsigned long)c != (unsigned int)c) seen_wide = 1, t = &size_type; /* enum symbols have static storage */ ss = sym_push(v, t, VT_CONST, c); ss->type.t |= VT_STATIC; if (tok != ',') break; next(); c++; /* NOTE: we accept a trailing comma */ if (tok == '}') break; } if (!seen_neg) s->a.unsigned_enum = 1; s->c = type_size(seen_wide ? &size_type : &int_type, &align); skip('}'); } else { ps = &s->next; flexible = 0; while (tok != '}') { if (!parse_btype(&btype, &ad1)) { skip(';'); continue; } while (1) { if (flexible) tcc_error("flexible array member '%s' not at the end of struct", get_tok_str(v, NULL)); bit_size = -1; v = 0; type1 = btype; if (tok != ':') { if (tok != ';') type_decl(&type1, &ad1, &v, TYPE_DIRECT); if (v == 0) { if ((type1.t & VT_BTYPE) != VT_STRUCT) expect("identifier"); else { int v = btype.ref->v; if (!(v & SYM_FIELD) && (v & ~SYM_STRUCT) < SYM_FIRST_ANOM) { if (tcc_state->ms_extensions == 0) expect("identifier"); } } } if (type_size(&type1, &align) < 0) { if ((a == TOK_STRUCT) && (type1.t & VT_ARRAY) && c) flexible = 1; else tcc_error("field '%s' has incomplete type", get_tok_str(v, NULL)); } if ((type1.t & VT_BTYPE) == VT_FUNC || (type1.t & VT_STORAGE)) tcc_error("invalid type for '%s'", get_tok_str(v, NULL)); } if (tok == ':') { next(); bit_size = expr_const(); /* XXX: handle v = 0 case for messages */ if (bit_size < 0) tcc_error("negative width in bit-field '%s'", get_tok_str(v, NULL)); if (v && bit_size == 0) tcc_error("zero width for bit-field '%s'", get_tok_str(v, NULL)); if (tok == TOK_ATTRIBUTE1 || tok == TOK_ATTRIBUTE2) parse_attribute(&ad1); } size = type_size(&type1, &align); /* Only remember non-default alignment. */ alignoverride = 0; if (ad1.a.aligned) { int speca = 1 << (ad1.a.aligned - 1); alignoverride = speca; } else if (ad1.a.packed || ad->a.packed) { alignoverride = 1; } else if (*tcc_state->pack_stack_ptr) { if (align >= *tcc_state->pack_stack_ptr) alignoverride = *tcc_state->pack_stack_ptr; } if (bit_size >= 0) { bt = type1.t & VT_BTYPE; if (bt != VT_INT && bt != VT_BYTE && bt != VT_SHORT && bt != VT_BOOL && bt != VT_ENUM && bt != VT_LLONG) tcc_error("bitfields must have scalar type"); bsize = size * 8; if (bit_size > bsize) { tcc_error("width of '%s' exceeds its type", get_tok_str(v, NULL)); } else if (bit_size == bsize) { /* no need for bit fields */ ; } else { type1.t |= VT_BITFIELD | (0 << VT_STRUCT_SHIFT) | (bit_size << (VT_STRUCT_SHIFT + 6)); } } if (v != 0 || (type1.t & VT_BTYPE) == VT_STRUCT) { /* Remember we've seen a real field to check for placement of flexible array member. */ c = 1; } /* If member is a struct or bit-field, enforce placing into the struct (as anonymous). */ if (v == 0 && ((type1.t & VT_BTYPE) == VT_STRUCT || bit_size >= 0)) { v = anon_sym++; } if (v) { ss = sym_push(v | SYM_FIELD, &type1, alignoverride, 0); *ps = ss; ps = &ss->next; } if (tok == ';' || tok == TOK_EOF) break; skip(','); } skip(';'); } skip('}'); if (tok == TOK_ATTRIBUTE1 || tok == TOK_ATTRIBUTE2) parse_attribute(ad); struct_layout(type, ad); } } } /* return 1 if basic type is a type size (short, long, long long) */ ST_FUNC int is_btype_size(int bt) { return bt == VT_SHORT || bt == VT_LONG || bt == VT_LLONG; } /* Add type qualifiers to a type. If the type is an array then the qualifiers are added to the element type, copied because it could be a typedef. */ static void parse_btype_qualify(CType *type, int qualifiers) { while (type->t & VT_ARRAY) { type->ref = sym_push(SYM_FIELD, &type->ref->type, 0, type->ref->c); type = &type->ref->type; } type->t |= qualifiers; } /* return 0 if no type declaration. otherwise, return the basic type and skip it. */ static int parse_btype(CType *type, AttributeDef *ad) { int t, u, bt_size, complete, type_found, typespec_found, g; Sym *s; CType type1; memset(ad, 0, sizeof(AttributeDef)); complete = 0; type_found = 0; typespec_found = 0; t = 0; while(1) { switch(tok) { case TOK_EXTENSION: /* currently, we really ignore extension */ next(); continue; /* basic types */ case TOK_CHAR: u = VT_BYTE; basic_type: next(); basic_type1: if (complete) tcc_error("too many basic types"); t |= u; bt_size = is_btype_size (u & VT_BTYPE); if (u == VT_INT || (!bt_size && !(t & VT_TYPEDEF))) complete = 1; typespec_found = 1; break; case TOK_VOID: u = VT_VOID; goto basic_type; case TOK_SHORT: u = VT_SHORT; goto basic_type; case TOK_INT: u = VT_INT; goto basic_type; case TOK_LONG: next(); if ((t & VT_BTYPE) == VT_DOUBLE) { #ifndef TCC_TARGET_PE t = (t & ~VT_BTYPE) | VT_LDOUBLE; #endif } else if ((t & VT_BTYPE) == VT_LONG) { t = (t & ~VT_BTYPE) | VT_LLONG; } else { u = VT_LONG; goto basic_type1; } break; #ifdef TCC_TARGET_ARM64 case TOK_UINT128: /* GCC's __uint128_t appears in some Linux header files. Make it a synonym for long double to get the size and alignment right. */ u = VT_LDOUBLE; goto basic_type; #endif case TOK_BOOL: u = VT_BOOL; goto basic_type; case TOK_FLOAT: u = VT_FLOAT; goto basic_type; case TOK_DOUBLE: next(); if ((t & VT_BTYPE) == VT_LONG) { #ifdef TCC_TARGET_PE t = (t & ~VT_BTYPE) | VT_DOUBLE; #else t = (t & ~VT_BTYPE) | VT_LDOUBLE; #endif } else { u = VT_DOUBLE; goto basic_type1; } break; case TOK_ENUM: struct_decl(&type1, ad, VT_ENUM); basic_type2: u = type1.t; type->ref = type1.ref; goto basic_type1; case TOK_STRUCT: case TOK_UNION: struct_decl(&type1, ad, VT_STRUCT); goto basic_type2; /* type modifiers */ case TOK_CONST1: case TOK_CONST2: case TOK_CONST3: type->t = t; parse_btype_qualify(type, VT_CONSTANT); t = type->t; next(); break; case TOK_VOLATILE1: case TOK_VOLATILE2: case TOK_VOLATILE3: type->t = t; parse_btype_qualify(type, VT_VOLATILE); t = type->t; next(); break; case TOK_SIGNED1: case TOK_SIGNED2: case TOK_SIGNED3: if ((t & (VT_DEFSIGN|VT_UNSIGNED)) == (VT_DEFSIGN|VT_UNSIGNED)) tcc_error("signed and unsigned modifier"); typespec_found = 1; t |= VT_DEFSIGN; next(); break; case TOK_REGISTER: case TOK_AUTO: case TOK_RESTRICT1: case TOK_RESTRICT2: case TOK_RESTRICT3: next(); break; case TOK_UNSIGNED: if ((t & (VT_DEFSIGN|VT_UNSIGNED)) == VT_DEFSIGN) tcc_error("signed and unsigned modifier"); t |= VT_DEFSIGN | VT_UNSIGNED; next(); typespec_found = 1; break; /* storage */ case TOK_EXTERN: g = VT_EXTERN; goto storage; case TOK_STATIC: g = VT_STATIC; goto storage; case TOK_TYPEDEF: g = VT_TYPEDEF; goto storage; storage: if (t & (VT_EXTERN|VT_STATIC|VT_TYPEDEF) & ~g) tcc_error("multiple storage classes"); t |= g; next(); break; case TOK_INLINE1: case TOK_INLINE2: case TOK_INLINE3: t |= VT_INLINE; next(); break; /* GNUC attribute */ case TOK_ATTRIBUTE1: case TOK_ATTRIBUTE2: parse_attribute(ad); if (ad->a.mode) { u = ad->a.mode -1; t = (t & ~VT_BTYPE) | u; } break; /* GNUC typeof */ case TOK_TYPEOF1: case TOK_TYPEOF2: case TOK_TYPEOF3: next(); parse_expr_type(&type1); /* remove all storage modifiers except typedef */ type1.t &= ~(VT_STORAGE&~VT_TYPEDEF); goto basic_type2; default: if (typespec_found) goto the_end; s = sym_find(tok); if (!s || !(s->type.t & VT_TYPEDEF)) goto the_end; type->t = ((s->type.t & ~VT_TYPEDEF) | (t & ~(VT_CONSTANT | VT_VOLATILE))); type->ref = s->type.ref; if (t & (VT_CONSTANT | VT_VOLATILE)) parse_btype_qualify(type, t & (VT_CONSTANT | VT_VOLATILE)); t = type->t; if (s->r) { /* get attributes from typedef */ if (0 == ad->a.aligned) ad->a.aligned = s->a.aligned; if (0 == ad->a.func_call) ad->a.func_call = s->a.func_call; ad->a.packed |= s->a.packed; } next(); typespec_found = 1; break; } type_found = 1; } the_end: if (tcc_state->char_is_unsigned) { if ((t & (VT_DEFSIGN|VT_BTYPE)) == VT_BYTE) t |= VT_UNSIGNED; } /* long is never used as type */ if ((t & VT_BTYPE) == VT_LONG) #if PTR_SIZE == 8 && !defined TCC_TARGET_PE t = (t & ~VT_BTYPE) | VT_LLONG; #else t = (t & ~VT_BTYPE) | VT_INT; #endif type->t = t; return type_found; } /* convert a function parameter type (array to pointer and function to function pointer) */ static inline void convert_parameter_type(CType *pt) { /* remove const and volatile qualifiers (XXX: const could be used to indicate a const function parameter */ pt->t &= ~(VT_CONSTANT | VT_VOLATILE); /* array must be transformed to pointer according to ANSI C */ pt->t &= ~VT_ARRAY; if ((pt->t & VT_BTYPE) == VT_FUNC) { mk_pointer(pt); } } ST_FUNC void parse_asm_str(CString *astr) { skip('('); parse_mult_str(astr, "string constant"); } /* Parse an asm label and return the token */ static int asm_label_instr(void) { int v; CString astr; next(); parse_asm_str(&astr); skip(')'); #ifdef ASM_DEBUG printf("asm_alias: \"%s\"\n", (char *)astr.data); #endif v = tok_alloc(astr.data, astr.size - 1)->tok; cstr_free(&astr); return v; } static int post_type(CType *type, AttributeDef *ad, int storage, int td) { int n, l, t1, arg_size, align; Sym **plast, *s, *first; AttributeDef ad1; CType pt; if (tok == '(') { /* function type, or recursive declarator (return if so) */ next(); if (td && !(td & TYPE_ABSTRACT)) return 0; if (tok == ')') l = 0; else if (parse_btype(&pt, &ad1)) l = FUNC_NEW; else if (td) return 0; else l = FUNC_OLD; first = NULL; plast = &first; arg_size = 0; if (l) { for(;;) { /* read param name and compute offset */ if (l != FUNC_OLD) { if ((pt.t & VT_BTYPE) == VT_VOID && tok == ')') break; type_decl(&pt, &ad1, &n, TYPE_DIRECT | TYPE_ABSTRACT); if ((pt.t & VT_BTYPE) == VT_VOID) tcc_error("parameter declared as void"); arg_size += (type_size(&pt, &align) + PTR_SIZE - 1) / PTR_SIZE; } else { n = tok; if (n < TOK_UIDENT) expect("identifier"); pt.t = VT_VOID; /* invalid type */ next(); } convert_parameter_type(&pt); s = sym_push(n | SYM_FIELD, &pt, 0, 0); *plast = s; plast = &s->next; if (tok == ')') break; skip(','); if (l == FUNC_NEW && tok == TOK_DOTS) { l = FUNC_ELLIPSIS; next(); break; } if (l == FUNC_NEW && !parse_btype(&pt, &ad1)) tcc_error("invalid type"); } } else /* if no parameters, then old type prototype */ l = FUNC_OLD; skip(')'); /* NOTE: const is ignored in returned type as it has a special meaning in gcc / C++ */ type->t &= ~VT_CONSTANT; /* some ancient pre-K&R C allows a function to return an array and the array brackets to be put after the arguments, such that "int c()[]" means something like "int[] c()" */ if (tok == '[') { next(); skip(']'); /* only handle simple "[]" */ mk_pointer(type); } /* we push a anonymous symbol which will contain the function prototype */ ad->a.func_args = arg_size; s = sym_push(SYM_FIELD, type, 0, l); s->a = ad->a; s->next = first; type->t = VT_FUNC; type->ref = s; } else if (tok == '[') { int saved_nocode_wanted = nocode_wanted; /* array definition */ next(); if (tok == TOK_RESTRICT1) next(); n = -1; t1 = 0; if (tok != ']') { if (!local_stack || (storage & VT_STATIC)) vpushi(expr_const()); else { /* VLAs (which can only happen with local_stack && !VT_STATIC) length must always be evaluated, even under nocode_wanted, so that its size slot is initialized (e.g. under sizeof or typeof). */ nocode_wanted = 0; gexpr(); } if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) { n = vtop->c.i; if (n < 0) tcc_error("invalid array size"); } else { if (!is_integer_btype(vtop->type.t & VT_BTYPE)) tcc_error("size of variable length array should be an integer"); t1 = VT_VLA; } } skip(']'); /* parse next post type */ post_type(type, ad, storage, 0); if (type->t == VT_FUNC) tcc_error("declaration of an array of functions"); t1 |= type->t & VT_VLA; if (t1 & VT_VLA) { loc -= type_size(&int_type, &align); loc &= -align; n = loc; vla_runtime_type_size(type, &align); gen_op('*'); vset(&int_type, VT_LOCAL|VT_LVAL, n); vswap(); vstore(); } if (n != -1) vpop(); nocode_wanted = saved_nocode_wanted; /* we push an anonymous symbol which will contain the array element type */ s = sym_push(SYM_FIELD, type, 0, n); type->t = (t1 ? VT_VLA : VT_ARRAY) | VT_PTR; type->ref = s; } return 1; } /* Parse a type declarator (except basic type), and return the type in 'type'. 'td' is a bitmask indicating which kind of type decl is expected. 'type' should contain the basic type. 'ad' is the attribute definition of the basic type. It can be modified by type_decl(). If this (possibly abstract) declarator is a pointer chain it returns the innermost pointed to type (equals *type, but is a different pointer), otherwise returns type itself, that's used for recursive calls. */ static CType *type_decl(CType *type, AttributeDef *ad, int *v, int td) { CType *post, *ret; int qualifiers, storage; /* recursive type, remove storage bits first, apply them later again */ storage = type->t & VT_STORAGE; type->t &= ~VT_STORAGE; post = ret = type; while (tok == '*') { qualifiers = 0; redo: next(); switch(tok) { case TOK_CONST1: case TOK_CONST2: case TOK_CONST3: qualifiers |= VT_CONSTANT; goto redo; case TOK_VOLATILE1: case TOK_VOLATILE2: case TOK_VOLATILE3: qualifiers |= VT_VOLATILE; goto redo; case TOK_RESTRICT1: case TOK_RESTRICT2: case TOK_RESTRICT3: goto redo; /* XXX: clarify attribute handling */ case TOK_ATTRIBUTE1: case TOK_ATTRIBUTE2: parse_attribute(ad); break; } mk_pointer(type); type->t |= qualifiers; if (ret == type) /* innermost pointed to type is the one for the first derivation */ ret = pointed_type(type); } if (tok == '(') { /* This is possibly a parameter type list for abstract declarators ('int ()'), use post_type for testing this. */ if (!post_type(type, ad, 0, td)) { /* It's not, so it's a nested declarator, and the post operations apply to the innermost pointed to type (if any). */ /* XXX: this is not correct to modify 'ad' at this point, but the syntax is not clear */ if (tok == TOK_ATTRIBUTE1 || tok == TOK_ATTRIBUTE2) parse_attribute(ad); post = type_decl(type, ad, v, td); skip(')'); } } else if (tok >= TOK_IDENT && (td & TYPE_DIRECT)) { /* type identifier */ *v = tok; next(); } else { if (!(td & TYPE_ABSTRACT)) expect("identifier"); *v = 0; } post_type(post, ad, storage, 0); if (tok == TOK_ATTRIBUTE1 || tok == TOK_ATTRIBUTE2) parse_attribute(ad); type->t |= storage; return ret; } /* compute the lvalue VT_LVAL_xxx needed to match type t. */ ST_FUNC int lvalue_type(int t) { int bt, r; r = VT_LVAL; bt = t & VT_BTYPE; if (bt == VT_BYTE || bt == VT_BOOL) r |= VT_LVAL_BYTE; else if (bt == VT_SHORT) r |= VT_LVAL_SHORT; else return r; if (t & VT_UNSIGNED) r |= VT_LVAL_UNSIGNED; return r; } /* indirection with full error checking and bound check */ ST_FUNC void indir(void) { if ((vtop->type.t & VT_BTYPE) != VT_PTR) { if ((vtop->type.t & VT_BTYPE) == VT_FUNC) return; expect("pointer"); } if (vtop->r & VT_LVAL) gv(RC_INT); vtop->type = *pointed_type(&vtop->type); /* Arrays and functions are never lvalues */ if (!(vtop->type.t & VT_ARRAY) && !(vtop->type.t & VT_VLA) && (vtop->type.t & VT_BTYPE) != VT_FUNC) { vtop->r |= lvalue_type(vtop->type.t); /* if bound checking, the referenced pointer must be checked */ #ifdef CONFIG_TCC_BCHECK if (tcc_state->do_bounds_check) vtop->r |= VT_MUSTBOUND; #endif } } /* pass a parameter to a function and do type checking and casting */ static void gfunc_param_typed(Sym *func, Sym *arg) { int func_type; CType type; func_type = func->c; if (func_type == FUNC_OLD || (func_type == FUNC_ELLIPSIS && arg == NULL)) { /* default casting : only need to convert float to double */ if ((vtop->type.t & VT_BTYPE) == VT_FLOAT) { type.t = VT_DOUBLE; gen_cast(&type); } else if (vtop->type.t & VT_BITFIELD) { type.t = vtop->type.t & (VT_BTYPE | VT_UNSIGNED); type.ref = vtop->type.ref; gen_cast(&type); } } else if (arg == NULL) { tcc_error("too many arguments to function"); } else { type = arg->type; type.t &= ~VT_CONSTANT; /* need to do that to avoid false warning */ gen_assign_cast(&type); } } /* parse an expression and return its type without any side effect. If UNRY we parse an unary expression, otherwise a full one. */ static void expr_type(CType *type, int unry) { nocode_wanted++; if (unry) unary(); else gexpr(); *type = vtop->type; vpop(); nocode_wanted--; } /* parse an expression of the form '(type)' or '(expr)' and return its type */ static void parse_expr_type(CType *type) { int n; AttributeDef ad; skip('('); if (parse_btype(type, &ad)) { type_decl(type, &ad, &n, TYPE_ABSTRACT); } else { expr_type(type, 0); } skip(')'); } static void parse_type(CType *type) { AttributeDef ad; int n; if (!parse_btype(type, &ad)) { expect("type"); } type_decl(type, &ad, &n, TYPE_ABSTRACT); } static void parse_builtin_params(int nc, const char *args) { char c, sep = '('; CType t; if (nc) nocode_wanted++; next(); while ((c = *args++)) { skip(sep); sep = ','; switch (c) { case 'e': expr_eq(); continue; case 't': parse_type(&t); vpush(&t); continue; default: tcc_error("internal error"); break; } } skip(')'); if (nc) nocode_wanted--; } ST_FUNC void unary(void) { int n, t, align, size, r, sizeof_caller; CType type; Sym *s; AttributeDef ad; sizeof_caller = in_sizeof; in_sizeof = 0; /* XXX: GCC 2.95.3 does not generate a table although it should be better here */ tok_next: switch(tok) { case TOK_EXTENSION: next(); goto tok_next; case TOK_CINT: case TOK_CCHAR: case TOK_LCHAR: t = VT_INT; push_tokc: type.t = t; type.ref = 0; vsetc(&type, VT_CONST, &tokc); next(); break; case TOK_CUINT: t = VT_INT | VT_UNSIGNED; goto push_tokc; case TOK_CLLONG: t = VT_LLONG; goto push_tokc; case TOK_CULLONG: t = VT_LLONG | VT_UNSIGNED; goto push_tokc; case TOK_CFLOAT: t = VT_FLOAT; goto push_tokc; case TOK_CDOUBLE: t = VT_DOUBLE; goto push_tokc; case TOK_CLDOUBLE: t = VT_LDOUBLE; goto push_tokc; case TOK___FUNCTION__: if (!gnu_ext) goto tok_identifier; /* fall thru */ case TOK___FUNC__: { void *ptr; int len; /* special function name identifier */ len = strlen(funcname) + 1; /* generate char[len] type */ type.t = VT_BYTE; mk_pointer(&type); type.t |= VT_ARRAY; type.ref->c = len; vpush_ref(&type, data_section, data_section->data_offset, len); ptr = section_ptr_add(data_section, len); memcpy(ptr, funcname, len); next(); } break; case TOK_LSTR: #ifdef TCC_TARGET_PE t = VT_SHORT | VT_UNSIGNED; #else t = VT_INT; #endif goto str_init; case TOK_STR: /* string parsing */ t = VT_BYTE; str_init: if (tcc_state->warn_write_strings) t |= VT_CONSTANT; type.t = t; mk_pointer(&type); type.t |= VT_ARRAY; memset(&ad, 0, sizeof(AttributeDef)); decl_initializer_alloc(&type, &ad, VT_CONST, 2, 0, 0); break; case '(': next(); /* cast ? */ if (parse_btype(&type, &ad)) { type_decl(&type, &ad, &n, TYPE_ABSTRACT); skip(')'); /* check ISOC99 compound literal */ if (tok == '{') { /* data is allocated locally by default */ if (global_expr) r = VT_CONST; else r = VT_LOCAL; /* all except arrays are lvalues */ if (!(type.t & VT_ARRAY)) r |= lvalue_type(type.t); memset(&ad, 0, sizeof(AttributeDef)); decl_initializer_alloc(&type, &ad, r, 1, 0, 0); } else { if (sizeof_caller) { vpush(&type); return; } unary(); gen_cast(&type); } } else if (tok == '{') { int saved_nocode_wanted = nocode_wanted; if (const_wanted) tcc_error("expected constant"); /* save all registers */ save_regs(0); /* statement expression : we do not accept break/continue inside as GCC does. We do retain the nocode_wanted state, as statement expressions can't ever be entered from the outside, so any reactivation of code emission (from labels or loop heads) can be disabled again after the end of it. */ block(NULL, NULL, 1); nocode_wanted = saved_nocode_wanted; skip(')'); } else { gexpr(); skip(')'); } break; case '*': next(); unary(); indir(); break; case '&': next(); unary(); /* functions names must be treated as function pointers, except for unary '&' and sizeof. Since we consider that functions are not lvalues, we only have to handle it there and in function calls. */ /* arrays can also be used although they are not lvalues */ if ((vtop->type.t & VT_BTYPE) != VT_FUNC && !(vtop->type.t & VT_ARRAY)) test_lvalue(); mk_pointer(&vtop->type); gaddrof(); break; case '!': next(); unary(); if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) { CType boolean; boolean.t = VT_BOOL; gen_cast(&boolean); vtop->c.i = !vtop->c.i; } else if ((vtop->r & VT_VALMASK) == VT_CMP) vtop->c.i ^= 1; else { save_regs(1); vseti(VT_JMP, gvtst(1, 0)); } break; case '~': next(); unary(); vpushi(-1); gen_op('^'); break; case '+': next(); unary(); if ((vtop->type.t & VT_BTYPE) == VT_PTR) tcc_error("pointer not accepted for unary plus"); /* In order to force cast, we add zero, except for floating point where we really need an noop (otherwise -0.0 will be transformed into +0.0). */ if (!is_float(vtop->type.t)) { vpushi(0); gen_op('+'); } break; case TOK_SIZEOF: case TOK_ALIGNOF1: case TOK_ALIGNOF2: t = tok; next(); in_sizeof++; expr_type(&type, 1); // Perform a in_sizeof = 0; size = type_size(&type, &align); if (t == TOK_SIZEOF) { if (!(type.t & VT_VLA)) { if (size < 0) tcc_error("sizeof applied to an incomplete type"); vpushs(size); } else { vla_runtime_type_size(&type, &align); } } else { vpushs(align); } vtop->type.t |= VT_UNSIGNED; break; case TOK_builtin_expect: /* __builtin_expect is a no-op for now */ parse_builtin_params(0, "ee"); vpop(); break; case TOK_builtin_types_compatible_p: parse_builtin_params(0, "tt"); vtop[-1].type.t &= ~(VT_CONSTANT | VT_VOLATILE); vtop[0].type.t &= ~(VT_CONSTANT | VT_VOLATILE); n = is_compatible_types(&vtop[-1].type, &vtop[0].type); vtop -= 2; vpushi(n); break; case TOK_builtin_choose_expr: { #if HAVE_LONG_LONG_STUB || HAVE_LONG_LONG int64_t c; #else int32_t c; #endif next(); skip('('); #if HAVE_LONG_LONG c = expr_const64(); #else c = expr_const32(); #endif skip(','); if (!c) { nocode_wanted++; } expr_eq(); if (!c) { vpop(); nocode_wanted--; } skip(','); if (c) { nocode_wanted++; } expr_eq(); if (c) { vpop(); nocode_wanted--; } skip(')'); } break; case TOK_builtin_constant_p: parse_builtin_params(1, "e"); n = (vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST; vtop--; vpushi(n); break; case TOK_builtin_frame_address: case TOK_builtin_return_address: { int tok1 = tok; int level; CType type; next(); skip('('); if (tok != TOK_CINT) { tcc_error("%s only takes positive integers", tok1 == TOK_builtin_return_address ? "__builtin_return_address" : "__builtin_frame_address"); } level = (uint32_t)tokc.i; next(); skip(')'); type.t = VT_VOID; mk_pointer(&type); vset(&type, VT_LOCAL, 0); /* local frame */ while (level--) { mk_pointer(&vtop->type); indir(); /* -> parent frame */ } if (tok1 == TOK_builtin_return_address) { // assume return address is just above frame pointer on stack vpushi(PTR_SIZE); gen_op('+'); mk_pointer(&vtop->type); indir(); } } break; #ifdef TCC_TARGET_X86_64 #ifdef TCC_TARGET_PE case TOK_builtin_va_start: parse_builtin_params(0, "ee"); r = vtop->r & VT_VALMASK; if (r == VT_LLOCAL) r = VT_LOCAL; if (r != VT_LOCAL) tcc_error("__builtin_va_start expects a local variable"); vtop->r = r; vtop->type = char_pointer_type; vtop->c.i += 8; vstore(); break; #else case TOK_builtin_va_arg_types: parse_builtin_params(0, "t"); vpushi(classify_x86_64_va_arg(&vtop->type)); vswap(); vpop(); break; #endif #endif #ifdef TCC_TARGET_ARM64 case TOK___va_start: { parse_builtin_params(0, "ee"); //xx check types gen_va_start(); vpushi(0); vtop->type.t = VT_VOID; break; } case TOK___va_arg: { CType type; parse_builtin_params(0, "et"); type = vtop->type; vpop(); //xx check types gen_va_arg(&type); vtop->type = type; break; } case TOK___arm64_clear_cache: { parse_builtin_params(0, "ee"); gen_clear_cache(); vpushi(0); vtop->type.t = VT_VOID; break; } #endif /* pre operations */ case TOK_INC: case TOK_DEC: t = tok; next(); unary(); inc(0, t); break; case '-': next(); unary(); t = vtop->type.t & VT_BTYPE; if (is_float(t)) { /* In IEEE negate(x) isn't subtract(0,x), but rather subtract(-0, x). */ vpush(&vtop->type); #if HAVE_FLOAT if (t == VT_FLOAT) vtop->c.f = -1.0 * 0.0; else if (t == VT_DOUBLE) vtop->c.d = -1.0 * 0.0; else vtop->c.ld = -1.0 * 0.0; #endif } else vpushi(0); vswap(); gen_op('-'); break; case TOK_LAND: if (!gnu_ext) goto tok_identifier; next(); /* allow to take the address of a label */ if (tok < TOK_UIDENT) expect("label identifier"); s = label_find(tok); if (!s) { s = label_push(&global_label_stack, tok, LABEL_FORWARD); } else { if (s->r == LABEL_DECLARED) s->r = LABEL_FORWARD; } if (!s->type.t) { s->type.t = VT_VOID; mk_pointer(&s->type); s->type.t |= VT_STATIC; } vpushsym(&s->type, s); next(); break; // special qnan , snan and infinity values case TOK___NAN__: #if HAVE_LONG_LONG vpush64(VT_DOUBLE, 0x7ff8000000000000ULL); #else vpush64(VT_DOUBLE, 0x7ff80000); vtop->c.i = vtop->c.i << 32; #endif next(); break; case TOK___SNAN__: #if HAVE_LONG_LONG vpush64(VT_DOUBLE, 0x7ff0000000000001ULL); #else vpush64(VT_DOUBLE, 0x7ff00000); vtop->c.i = vtop->c.i << 32; vtop->c.i = vtop->c.i | 1; #endif next(); break; case TOK___INF__: #if HAVE_LONG_LONG vpush64(VT_DOUBLE, 0x7ff0000000000000ULL); #else vpush64(VT_DOUBLE, 0x7ff00000); vtop->c.i = vtop->c.i << 32; #endif next(); break; default: tok_identifier: t = tok; next(); if (t < TOK_UIDENT) expect("identifier"); s = sym_find(t); if (!s) { const char *name = get_tok_str(t, NULL); if (tok != '(') tcc_error("'%s' undeclared", name); /* for simple function calls, we tolerate undeclared external reference to int() function */ if (tcc_state->warn_implicit_function_declaration #ifdef TCC_TARGET_PE /* people must be warned about using undeclared WINAPI functions (which usually start with uppercase letter) */ || (name[0] >= 'A' && name[0] <= 'Z') #endif ) tcc_warning("implicit declaration of function '%s'", name); s = external_global_sym(t, &func_old_type, 0); } r = s->r; /* A symbol that has a register is a local register variable, which starts out as VT_LOCAL value. */ if ((r & VT_VALMASK) < VT_CONST) r = (r & ~VT_VALMASK) | VT_LOCAL; vset(&s->type, r, s->c); /* Point to s as backpointer (even without r&VT_SYM). Will be used by at least the x86 inline asm parser for regvars. */ vtop->sym = s; if (vtop->r & VT_SYM) { vtop->c.i = 0; } break; } /* post operations */ while (1) { if (tok == TOK_INC || tok == TOK_DEC) { inc(1, tok); next(); } else if (tok == '.' || tok == TOK_ARROW || tok == TOK_CDOUBLE) { int qualifiers; /* field */ if (tok == TOK_ARROW) indir(); qualifiers = vtop->type.t & (VT_CONSTANT | VT_VOLATILE); test_lvalue(); gaddrof(); /* expect pointer on structure */ if ((vtop->type.t & VT_BTYPE) != VT_STRUCT) expect("struct or union"); if (tok == TOK_CDOUBLE) expect("field name"); next(); if (tok == TOK_CINT || tok == TOK_CUINT) expect("field name"); s = find_field(&vtop->type, tok); if (!s) tcc_error("field not found: %s", get_tok_str(tok & ~SYM_FIELD, &tokc)); /* add field offset to pointer */ vtop->type = char_pointer_type; /* change type to 'char *' */ vpushi(s->c); gen_op('+'); /* change type to field type, and set to lvalue */ vtop->type = s->type; vtop->type.t |= qualifiers; /* an array is never an lvalue */ if (!(vtop->type.t & VT_ARRAY)) { vtop->r |= lvalue_type(vtop->type.t); #ifdef CONFIG_TCC_BCHECK /* if bound checking, the referenced pointer must be checked */ if (tcc_state->do_bounds_check && (vtop->r & VT_VALMASK) != VT_LOCAL) vtop->r |= VT_MUSTBOUND; #endif } next(); } else if (tok == '[') { next(); gexpr(); gen_op('+'); indir(); skip(']'); } else if (tok == '(') { SValue ret; Sym *sa; int nb_args, ret_nregs, ret_align, regsize, variadic; /* function call */ if ((vtop->type.t & VT_BTYPE) != VT_FUNC) { /* pointer test (no array accepted) */ if ((vtop->type.t & (VT_BTYPE | VT_ARRAY)) == VT_PTR) { vtop->type = *pointed_type(&vtop->type); if ((vtop->type.t & VT_BTYPE) != VT_FUNC) goto error_func; } else { error_func: expect("function pointer"); } } else { vtop->r &= ~VT_LVAL; /* no lvalue */ } /* get return type */ s = vtop->type.ref; next(); sa = s->next; /* first parameter */ nb_args = regsize = 0; ret.r2 = VT_CONST; /* compute first implicit argument if a structure is returned */ if ((s->type.t & VT_BTYPE) == VT_STRUCT) { variadic = (s->c == FUNC_ELLIPSIS); ret_nregs = gfunc_sret(&s->type, variadic, &ret.type, &ret_align, ®size); if (!ret_nregs) { /* get some space for the returned structure */ size = type_size(&s->type, &align); #ifdef TCC_TARGET_ARM64 /* On arm64, a small struct is return in registers. It is much easier to write it to memory if we know that we are allowed to write some extra bytes, so round the allocated space up to a power of 2: */ if (size < 16) while (size & (size - 1)) size = (size | (size - 1)) + 1; #endif loc = (loc - size) & -align; ret.type = s->type; ret.r = VT_LOCAL | VT_LVAL; /* pass it as 'int' to avoid structure arg passing problems */ vseti(VT_LOCAL, loc); ret.c = vtop->c; nb_args++; } } else { ret_nregs = 1; ret.type = s->type; } if (ret_nregs) { /* return in register */ if (is_float(ret.type.t)) { ret.r = reg_fret(ret.type.t); #ifdef TCC_TARGET_X86_64 if ((ret.type.t & VT_BTYPE) == VT_QFLOAT) ret.r2 = REG_QRET; #endif } else { #ifndef TCC_TARGET_ARM64 #ifdef TCC_TARGET_X86_64 if ((ret.type.t & VT_BTYPE) == VT_QLONG) #else if ((ret.type.t & VT_BTYPE) == VT_LLONG) #endif ret.r2 = REG_LRET; #endif ret.r = REG_IRET; } ret.c.i = 0; } if (tok != ')') { for(;;) { expr_eq(); gfunc_param_typed(s, sa); nb_args++; if (sa) sa = sa->next; if (tok == ')') break; skip(','); } } if (sa) tcc_error("too few arguments to function"); skip(')'); gfunc_call(nb_args); /* return value */ for (r = ret.r + ret_nregs + !ret_nregs; r-- > ret.r;) { vsetc(&ret.type, r, &ret.c); vtop->r2 = ret.r2; /* Loop only happens when r2 is VT_CONST */ } /* handle packed struct return */ if (((s->type.t & VT_BTYPE) == VT_STRUCT) && ret_nregs) { int addr, offset; size = type_size(&s->type, &align); /* We're writing whole regs often, make sure there's enough space. Assume register size is power of 2. */ if (regsize > align) align = regsize; loc = (loc - size) & -align; addr = loc; offset = 0; for (;;) { vset(&ret.type, VT_LOCAL | VT_LVAL, addr + offset); vswap(); vstore(); vtop--; if (--ret_nregs == 0) break; offset += regsize; } vset(&s->type, VT_LOCAL | VT_LVAL, addr); } } else { break; } } } ST_FUNC void expr_prod(void) { int t; unary(); while (tok == '*' || tok == '/' || tok == '%') { t = tok; next(); unary(); gen_op(t); } } ST_FUNC void expr_sum(void) { int t; expr_prod(); while (tok == '+' || tok == '-') { t = tok; next(); expr_prod(); gen_op(t); } } static void expr_shift(void) { int t; expr_sum(); while (tok == TOK_SHL || tok == TOK_SAR) { t = tok; next(); expr_sum(); gen_op(t); } } static void expr_cmp(void) { int t; expr_shift(); while ((tok >= TOK_ULE && tok <= TOK_GT) || tok == TOK_ULT || tok == TOK_UGE) { t = tok; next(); expr_shift(); gen_op(t); } } static void expr_cmpeq(void) { int t; expr_cmp(); while (tok == TOK_EQ || tok == TOK_NE) { t = tok; next(); expr_cmp(); gen_op(t); } } static void expr_and(void) { expr_cmpeq(); while (tok == '&') { next(); expr_cmpeq(); gen_op('&'); } } static void expr_xor(void) { expr_and(); while (tok == '^') { next(); expr_and(); gen_op('^'); } } static void expr_or(void) { expr_xor(); while (tok == '|') { next(); expr_xor(); gen_op('|'); } } static void expr_land(void) { expr_or(); if (tok == TOK_LAND) { int t = 0; for(;;) { if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) { CType ctb; ctb.t = VT_BOOL; gen_cast(&ctb); if (vtop->c.i) { vpop(); } else { nocode_wanted++; while (tok == TOK_LAND) { next(); expr_or(); vpop(); } nocode_wanted--; if (t) gsym(t); gen_cast(&int_type); break; } } else { if (!t) save_regs(1); t = gvtst(1, t); } if (tok != TOK_LAND) { if (t) vseti(VT_JMPI, t); else vpushi(1); break; } next(); expr_or(); } } } static void expr_lor(void) { expr_land(); if (tok == TOK_LOR) { int t = 0; for(;;) { if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) { CType ctb; ctb.t = VT_BOOL; gen_cast(&ctb); if (!vtop->c.i) { vpop(); } else { nocode_wanted++; while (tok == TOK_LOR) { next(); expr_land(); vpop(); } nocode_wanted--; if (t) gsym(t); gen_cast(&int_type); break; } } else { if (!t) save_regs(1); t = gvtst(0, t); } if (tok != TOK_LOR) { if (t) vseti(VT_JMP, t); else vpushi(0); break; } next(); expr_land(); } } } /* Assuming vtop is a value used in a conditional context (i.e. compared with zero) return 0 if it's false, 1 if true and -1 if it can't be statically determined. */ static int condition_3way(void) { int c = -1; if ((vtop->r & (VT_VALMASK | VT_LVAL)) == VT_CONST && (!(vtop->r & VT_SYM) || !(vtop->sym->type.t & VT_WEAK))) { CType boolean; boolean.t = VT_BOOL; vdup(); gen_cast(&boolean); c = vtop->c.i; vpop(); } return c; } static void expr_cond(void) { int tt, u, r1, r2, rc, t1, t2, bt1, bt2, islv, c, g; SValue sv; CType type, type1, type2; expr_lor(); if (tok == '?') { next(); c = condition_3way(); g = (tok == ':' && gnu_ext); if (c < 0) { /* needed to avoid having different registers saved in each branch */ if (is_float(vtop->type.t)) { rc = RC_FLOAT; #ifdef TCC_TARGET_X86_64 if ((vtop->type.t & VT_BTYPE) == VT_LDOUBLE) { rc = RC_ST0; } #endif } else rc = RC_INT; gv(rc); save_regs(1); if (g) gv_dup(); tt = gvtst(1, 0); } else { if (!g) vpop(); tt = 0; } if (1) { if (c == 0) nocode_wanted++; if (!g) gexpr(); type1 = vtop->type; sv = *vtop; /* save value to handle it later */ vtop--; /* no vpop so that FP stack is not flushed */ skip(':'); u = 0; if (c < 0) u = gjmp(0); gsym(tt); if (c == 0) nocode_wanted--; if (c == 1) nocode_wanted++; expr_cond(); if (c == 1) nocode_wanted--; type2 = vtop->type; t1 = type1.t; bt1 = t1 & VT_BTYPE; t2 = type2.t; bt2 = t2 & VT_BTYPE; /* cast operands to correct type according to ISOC rules */ if (is_float(bt1) || is_float(bt2)) { if (bt1 == VT_LDOUBLE || bt2 == VT_LDOUBLE) { type.t = VT_LDOUBLE; } else if (bt1 == VT_DOUBLE || bt2 == VT_DOUBLE) { type.t = VT_DOUBLE; } else { type.t = VT_FLOAT; } } else if (bt1 == VT_LLONG || bt2 == VT_LLONG) { /* cast to biggest op */ type.t = VT_LLONG; /* convert to unsigned if it does not fit in a long long */ if ((t1 & (VT_BTYPE | VT_UNSIGNED | VT_BITFIELD)) == (VT_LLONG | VT_UNSIGNED) || (t2 & (VT_BTYPE | VT_UNSIGNED | VT_BITFIELD)) == (VT_LLONG | VT_UNSIGNED)) type.t |= VT_UNSIGNED; } else if (bt1 == VT_PTR || bt2 == VT_PTR) { /* If one is a null ptr constant the result type is the other. */ if (is_null_pointer (vtop)) type = type1; else if (is_null_pointer (&sv)) type = type2; /* XXX: test pointer compatibility, C99 has more elaborate rules here. */ else type = type1; } else if (bt1 == VT_FUNC || bt2 == VT_FUNC) { /* XXX: test function pointer compatibility */ type = bt1 == VT_FUNC ? type1 : type2; } else if (bt1 == VT_STRUCT || bt2 == VT_STRUCT) { /* XXX: test structure compatibility */ type = bt1 == VT_STRUCT ? type1 : type2; } else if (bt1 == VT_VOID || bt2 == VT_VOID) { /* NOTE: as an extension, we accept void on only one side */ type.t = VT_VOID; } else { /* integer operations */ type.t = VT_INT; /* convert to unsigned if it does not fit in an integer */ if ((t1 & (VT_BTYPE | VT_UNSIGNED | VT_BITFIELD)) == (VT_INT | VT_UNSIGNED) || (t2 & (VT_BTYPE | VT_UNSIGNED | VT_BITFIELD)) == (VT_INT | VT_UNSIGNED)) type.t |= VT_UNSIGNED; } /* keep structs lvalue by transforming `(expr ? a : b)` to `*(expr ? &a : &b)` so that `(expr ? a : b).mem` does not error with "lvalue expected" */ islv = (vtop->r & VT_LVAL) && (sv.r & VT_LVAL) && VT_STRUCT == (type.t & VT_BTYPE); islv &= c < 0; /* now we convert second operand */ if (c != 1) { gen_cast(&type); if (islv) { mk_pointer(&vtop->type); gaddrof(); } else if (VT_STRUCT == (vtop->type.t & VT_BTYPE)) gaddrof(); } rc = RC_INT; if (is_float(type.t)) { rc = RC_FLOAT; #ifdef TCC_TARGET_X86_64 if ((type.t & VT_BTYPE) == VT_LDOUBLE) { rc = RC_ST0; } #endif } else if ((type.t & VT_BTYPE) == VT_LLONG) { /* for long longs, we use fixed registers to avoid having to handle a complicated move */ rc = RC_IRET; } tt = r2 = 0; if (c < 0) { r2 = gv(rc); tt = gjmp(0); } gsym(u); /* this is horrible, but we must also convert first operand */ if (c != 0) { *vtop = sv; gen_cast(&type); if (islv) { mk_pointer(&vtop->type); gaddrof(); } else if (VT_STRUCT == (vtop->type.t & VT_BTYPE)) gaddrof(); } if (c < 0) { r1 = gv(rc); move_reg(r2, r1, type.t); vtop->r = r2; gsym(tt); if (islv) indir(); } } } } static void expr_eq(void) { int t; expr_cond(); if (tok == '=' || (tok >= TOK_A_MOD && tok <= TOK_A_DIV) || tok == TOK_A_XOR || tok == TOK_A_OR || tok == TOK_A_SHL || tok == TOK_A_SAR) { test_lvalue(); t = tok; next(); if (t == '=') { expr_eq(); } else { vdup(); expr_eq(); gen_op(t & 0x7f); } vstore(); } } ST_FUNC void gexpr(void) { while (1) { expr_eq(); if (tok != ',') break; vpop(); next(); } } /* parse a constant expression and return value in vtop. */ static void expr_const1(void) { const_wanted++; expr_cond(); const_wanted--; } #if HAVE_LONG_LONG /* parse an integer constant and return its value. */ static inline int64_t expr_const64(void) { int64_t c; expr_const1(); if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) != VT_CONST) expect("constant expression"); c = vtop->c.i; vpop(); return c; } #else /* parse an integer constant and return its value. */ static inline int32_t expr_const32(void) { int32_t c; expr_const1(); if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) != VT_CONST) expect("constant expression"); c = vtop->c.i; vpop(); return c; } #endif /* parse an integer constant and return its value. Complain if it doesn't fit 32bit (signed or unsigned). */ ST_FUNC int expr_const(void) { int c; #if HAVE_LONG_LONG int64_t wc = expr_const64(); #else int32_t wc = expr_const32(); #endif c = wc; if (c != wc && (unsigned)c != wc) tcc_error("constant exceeds 32 bit"); return c; } /* return the label token if current token is a label, otherwise return zero */ static int is_label(void) { int last_tok; /* fast test first */ if (tok < TOK_UIDENT) return 0; /* no need to save tokc because tok is an identifier */ last_tok = tok; next(); if (tok == ':') { return last_tok; } else { unget_tok(last_tok); return 0; } } #ifndef TCC_TARGET_ARM64 static void gfunc_return(CType *func_type) { if ((func_type->t & VT_BTYPE) == VT_STRUCT) { CType type, ret_type; int ret_align, ret_nregs, regsize; ret_nregs = gfunc_sret(func_type, func_var, &ret_type, &ret_align, ®size); if (0 == ret_nregs) { /* if returning structure, must copy it to implicit first pointer arg location */ type = *func_type; mk_pointer(&type); vset(&type, VT_LOCAL | VT_LVAL, func_vc); indir(); vswap(); /* copy structure value to pointer */ vstore(); } else { /* returning structure packed into registers */ int r, size, addr, align; size = type_size(func_type,&align); if ((vtop->r != (VT_LOCAL | VT_LVAL) || (vtop->c.i & (ret_align-1))) && (align & (ret_align-1))) { loc = (loc - size) & -ret_align; addr = loc; type = *func_type; vset(&type, VT_LOCAL | VT_LVAL, addr); vswap(); vstore(); vpop(); vset(&ret_type, VT_LOCAL | VT_LVAL, addr); } vtop->type = ret_type; if (is_float(ret_type.t)) r = rc_fret(ret_type.t); else r = RC_IRET; if (ret_nregs == 1) gv(r); else { for (;;) { vdup(); gv(r); vpop(); if (--ret_nregs == 0) break; /* We assume that when a structure is returned in multiple registers, their classes are consecutive values of the suite s(n) = 2^n */ r <<= 1; vtop->c.i += regsize; } } } } else if (is_float(func_type->t)) { gv(rc_fret(func_type->t)); } else { gv(RC_IRET); } vtop--; /* NOT vpop() because on x86 it would flush the fp stack */ } #endif static int case_cmp(const void *pa, const void *pb) { #if HAVE_LONG_LONG int64_t a = (*(struct case_t**) pa)->v1; int64_t b = (*(struct case_t**) pb)->v1; #else int32_t a = (*(struct case_t**) pa)->v1; int32_t b = (*(struct case_t**) pb)->v1; #endif return a < b ? -1 : a > b; } static void gcase(struct case_t **base, int len, int *bsym) { struct case_t *p; int e; int ll = (vtop->type.t & VT_BTYPE) == VT_LLONG; gv(RC_INT); while (len > 4) { /* binary search */ p = base[len/2]; vdup(); if (ll) vpushll(p->v2); else vpushi(p->v2); gen_op(TOK_LE); e = gtst(1, 0); vdup(); if (ll) vpushll(p->v1); else vpushi(p->v1); gen_op(TOK_GE); gtst_addr(0, p->sym); /* v1 <= x <= v2 */ /* x < v1 */ gcase(base, len/2, bsym); if (cur_switch->def_sym) gjmp_addr(cur_switch->def_sym); else *bsym = gjmp(*bsym); /* x > v2 */ gsym(e); e = len/2 + 1; base += e; len -= e; } /* linear scan */ while (len--) { p = *base++; vdup(); if (ll) vpushll(p->v2); else vpushi(p->v2); if (p->v1 == p->v2) { gen_op(TOK_EQ); gtst_addr(0, p->sym); } else { gen_op(TOK_LE); e = gtst(1, 0); vdup(); if (ll) vpushll(p->v1); else vpushi(p->v1); gen_op(TOK_GE); gtst_addr(0, p->sym); gsym(e); } } } static void block(int *bsym, int *csym, int is_expr) { int a, b, c, d, cond; Sym *s; /* generate line number info */ if (tcc_state->do_debug) tcc_debug_line(tcc_state); if (is_expr) { /* default return value is (void) */ vpushi(0); vtop->type.t = VT_VOID; } if (tok == TOK_IF) { /* if test */ int saved_nocode_wanted = nocode_wanted; next(); skip('('); gexpr(); skip(')'); cond = condition_3way(); if (cond == 1) a = 0, vpop(); else a = gvtst(1, 0); if (cond == 0) nocode_wanted |= 0x20000000; block(bsym, csym, 0); if (cond != 1) nocode_wanted = saved_nocode_wanted; c = tok; if (c == TOK_ELSE) { next(); d = gjmp(0); gsym(a); if (cond == 1) nocode_wanted |= 0x20000000; block(bsym, csym, 0); gsym(d); /* patch else jmp */ if (cond != 0) nocode_wanted = saved_nocode_wanted; } else gsym(a); } else if (tok == TOK_WHILE) { int saved_nocode_wanted; nocode_wanted &= ~0x20000000; next(); d = ind; vla_sp_restore(); skip('('); gexpr(); skip(')'); a = gvtst(1, 0); b = 0; ++local_scope; saved_nocode_wanted = nocode_wanted; block(&a, &b, 0); nocode_wanted = saved_nocode_wanted; --local_scope; gjmp_addr(d); gsym(a); gsym_addr(b, d); } else if (tok == '{') { Sym *llabel; int block_vla_sp_loc = vla_sp_loc, saved_vlas_in_scope = vlas_in_scope; next(); /* record local declaration stack position */ s = local_stack; llabel = local_label_stack; ++local_scope; /* handle local labels declarations */ if (tok == TOK_LABEL) { next(); for(;;) { if (tok < TOK_UIDENT) expect("label identifier"); label_push(&local_label_stack, tok, LABEL_DECLARED); next(); if (tok == ',') { next(); } else { skip(';'); break; } } } while (tok != '}') { if ((a = is_label())) unget_tok(a); else decl(VT_LOCAL); if (tok != '}') { if (is_expr) vpop(); block(bsym, csym, is_expr); } } /* pop locally defined labels */ label_pop(&local_label_stack, llabel); /* pop locally defined symbols */ --local_scope; /* In the is_expr case (a statement expression is finished here), vtop might refer to symbols on the local_stack. Either via the type or via vtop->sym. We can't pop those nor any that in turn might be referred to. To make it easier we don't roll back any symbols in that case; some upper level call to block() will do that. We do have to remove such symbols from the lookup tables, though. sym_pop will do that. */ sym_pop(&local_stack, s, is_expr); /* Pop VLA frames and restore stack pointer if required */ if (vlas_in_scope > saved_vlas_in_scope) { vla_sp_loc = saved_vlas_in_scope ? block_vla_sp_loc : vla_sp_root_loc; vla_sp_restore(); } vlas_in_scope = saved_vlas_in_scope; next(); } else if (tok == TOK_RETURN) { next(); if (tok != ';') { gexpr(); gen_assign_cast(&func_vt); gfunc_return(&func_vt); } skip(';'); /* jump unless last stmt in top-level block */ if (tok != '}' || local_scope != 1) rsym = gjmp(rsym); nocode_wanted |= 0x20000000; } else if (tok == TOK_BREAK) { /* compute jump */ if (!bsym) tcc_error("cannot break"); *bsym = gjmp(*bsym); next(); skip(';'); nocode_wanted |= 0x20000000; } else if (tok == TOK_CONTINUE) { /* compute jump */ if (!csym) tcc_error("cannot continue"); vla_sp_restore_root(); *csym = gjmp(*csym); next(); skip(';'); } else if (tok == TOK_FOR) { int e; int saved_nocode_wanted; nocode_wanted &= ~0x20000000; next(); skip('('); s = local_stack; ++local_scope; if (tok != ';') { /* c99 for-loop init decl? */ if (!decl0(VT_LOCAL, 1, NULL)) { /* no, regular for-loop init expr */ gexpr(); vpop(); } } skip(';'); d = ind; c = ind; vla_sp_restore(); a = 0; b = 0; if (tok != ';') { gexpr(); a = gvtst(1, 0); } skip(';'); if (tok != ')') { e = gjmp(0); c = ind; vla_sp_restore(); gexpr(); vpop(); gjmp_addr(d); gsym(e); } skip(')'); saved_nocode_wanted = nocode_wanted; block(&a, &b, 0); nocode_wanted = saved_nocode_wanted; gjmp_addr(c); gsym(a); gsym_addr(b, c); --local_scope; sym_pop(&local_stack, s, 0); } else if (tok == TOK_DO) { int saved_nocode_wanted; nocode_wanted &= ~0x20000000; next(); a = 0; b = 0; d = ind; vla_sp_restore(); saved_nocode_wanted = nocode_wanted; block(&a, &b, 0); skip(TOK_WHILE); skip('('); gsym(b); gexpr(); c = gvtst(0, 0); gsym_addr(c, d); nocode_wanted = saved_nocode_wanted; skip(')'); gsym(a); skip(';'); } else if (tok == TOK_SWITCH) { struct switch_t *saved, sw; int saved_nocode_wanted = nocode_wanted; SValue switchval; next(); skip('('); gexpr(); skip(')'); switchval = *vtop--; a = 0; b = gjmp(0); /* jump to first case */ sw.p = NULL; sw.n = 0; sw.def_sym = 0; saved = cur_switch; cur_switch = &sw; block(&a, csym, 0); nocode_wanted = saved_nocode_wanted; a = gjmp(a); /* add implicit break */ /* case lookup */ gsym(b); qsort(sw.p, sw.n, sizeof(void*), case_cmp); for (b = 1; b < sw.n; b++) if (sw.p[b - 1]->v2 >= sw.p[b]->v1) tcc_error("duplicate case value"); /* Our switch table sorting is signed, so the compared value needs to be as well when it's 64bit. */ if ((switchval.type.t & VT_BTYPE) == VT_LLONG) switchval.type.t &= ~VT_UNSIGNED; vpushv(&switchval); gcase(sw.p, sw.n, &a); vpop(); if (sw.def_sym) gjmp_addr(sw.def_sym); dynarray_reset(&sw.p, &sw.n); cur_switch = saved; /* break label */ gsym(a); } else if (tok == TOK_CASE) { struct case_t *cr = tcc_malloc(sizeof(struct case_t)); if (!cur_switch) expect("switch"); nocode_wanted &= ~0x20000000; next(); #if HAVE_LONG_LONG cr->v1 = cr->v2 = expr_const64(); #else cr->v1 = cr->v2 = expr_const32(); #endif if (gnu_ext && tok == TOK_DOTS) { next(); #if HAVE_LONG_LONG cr->v2 = expr_const64(); #else cr->v2 = expr_const32(); #endif if (cr->v2 < cr->v1) tcc_warning("empty case range"); } cr->sym = ind; dynarray_add(&cur_switch->p, &cur_switch->n, cr); skip(':'); is_expr = 0; goto block_after_label; } else if (tok == TOK_DEFAULT) { next(); skip(':'); if (!cur_switch) expect("switch"); if (cur_switch->def_sym) tcc_error("too many 'default'"); cur_switch->def_sym = ind; is_expr = 0; goto block_after_label; } else if (tok == TOK_GOTO) { next(); if (tok == '*' && gnu_ext) { /* computed goto */ next(); gexpr(); if ((vtop->type.t & VT_BTYPE) != VT_PTR) expect("pointer"); ggoto(); } else if (tok >= TOK_UIDENT) { s = label_find(tok); /* put forward definition if needed */ if (!s) { s = label_push(&global_label_stack, tok, LABEL_FORWARD); } else { if (s->r == LABEL_DECLARED) s->r = LABEL_FORWARD; } vla_sp_restore_root(); if (s->r & LABEL_FORWARD) s->jnext = gjmp(s->jnext); else gjmp_addr(s->jnext); next(); } else { expect("label identifier"); } skip(';'); } else if (tok == TOK_ASM1 || tok == TOK_ASM2 || tok == TOK_ASM3) { asm_instr(); } else { b = is_label(); if (b) { /* label case */ next(); s = label_find(b); if (s) { if (s->r == LABEL_DEFINED) tcc_error("duplicate label '%s'", get_tok_str(s->v, NULL)); gsym(s->jnext); s->r = LABEL_DEFINED; } else { s = label_push(&global_label_stack, b, LABEL_DEFINED); } s->jnext = ind; vla_sp_restore(); /* we accept this, but it is a mistake */ block_after_label: nocode_wanted &= ~0x20000000; if (tok == '}') { tcc_warning("deprecated use of label at end of compound statement"); } else { if (is_expr) vpop(); block(bsym, csym, is_expr); } } else { /* expression case */ if (tok != ';') { if (is_expr) { vpop(); gexpr(); } else { gexpr(); vpop(); } } skip(';'); } } } /* This skips over a stream of tokens containing balanced {} and () pairs, stopping at outer ',' ';' and '}'. If STR then allocates and stores the skipped tokens in *STR. This doesn't check if () and {} are nested correctly, i.e. "({)}" is accepted. */ static void skip_or_save_block(TokenString **str) { int level = 0; if (str) *str = tok_str_alloc(); while ((level > 0 || (tok != '}' && tok != ',' && tok != ';'))) { int t; if (tok == TOK_EOF) { if (str || level > 0) tcc_error("unexpected end of file"); else break; } if (str) tok_str_add_tok(*str); t = tok; next(); if (t == '{' || t == '(') { level++; } else if (t == '}' || t == ')') { level--; if (level == 0) break; } } if (str) { tok_str_add(*str, -1); tok_str_add(*str, 0); } } #define EXPR_CONST 1 #define EXPR_ANY 2 static void parse_init_elem(int expr_type) { int saved_global_expr; switch(expr_type) { case EXPR_CONST: /* compound literals must be allocated globally in this case */ saved_global_expr = global_expr; global_expr = 1; expr_const1(); global_expr = saved_global_expr; /* NOTE: symbols are accepted, as well as lvalue for anon symbols (compound literals). */ if (((vtop->r & (VT_VALMASK | VT_LVAL)) != VT_CONST && ((vtop->r & (VT_SYM|VT_LVAL)) != (VT_SYM|VT_LVAL) || vtop->sym->v < SYM_FIRST_ANOM)) #ifdef TCC_TARGET_PE || (vtop->type.t & VT_IMPORT) #endif ) tcc_error("initializer element is not constant"); break; case EXPR_ANY: expr_eq(); break; } } /* put zeros for variable based init */ static void init_putz(Section *sec, unsigned long c, int size) { if (sec) { /* nothing to do because globals are already set to zero */ } else { #if BOOTSTRAP && __arm__ vpush_global_sym(&func_old_type, TOK___memset); #else vpush_global_sym(&func_old_type, TOK_memset); #endif vseti(VT_LOCAL, c); #ifdef TCC_TARGET_ARM vpushs(size); vpushi(0); #else vpushi(0); vpushs(size); #endif gfunc_call(3); } } /* t is the array or struct type. c is the array or struct address. cur_field is the pointer to the current field, for arrays the 'c' member contains the current start index. 'size_only' is true if only size info is needed (only used in arrays). al contains the already initialized length of the current container (starting at c). This returns the new length of that. */ static int decl_designator(CType *type, Section *sec, unsigned long c, Sym **cur_field, int size_only, int al) { Sym *s, *f; int index, index_last, align, l, nb_elems, elem_size; unsigned long corig = c; elem_size = 0; nb_elems = 1; if (gnu_ext && (l = is_label()) != 0) goto struct_field; /* NOTE: we only support ranges for last designator */ while (nb_elems == 1 && (tok == '[' || tok == '.')) { if (tok == '[') { if (!(type->t & VT_ARRAY)) expect("array type"); next(); index = index_last = expr_const(); if (tok == TOK_DOTS && gnu_ext) { next(); index_last = expr_const(); } skip(']'); s = type->ref; if (index < 0 || (s->c >= 0 && index_last >= s->c) || index_last < index) tcc_error("invalid index"); if (cur_field) (*cur_field)->c = index_last; type = pointed_type(type); elem_size = type_size(type, &align); c += index * elem_size; nb_elems = index_last - index + 1; } else { next(); l = tok; struct_field: next(); if ((type->t & VT_BTYPE) != VT_STRUCT) expect("struct/union type"); f = find_field(type, l); if (!f) expect("field"); if (cur_field) *cur_field = f; type = &f->type; c += f->c; } cur_field = NULL; } if (!cur_field) { if (tok == '=') { next(); } else if (!gnu_ext) { expect("="); } } else { if (type->t & VT_ARRAY) { index = (*cur_field)->c; if (type->ref->c >= 0 && index >= type->ref->c) tcc_error("index too large"); type = pointed_type(type); c += index * type_size(type, &align); } else { f = *cur_field; while (f && (f->v & SYM_FIRST_ANOM) && (f->type.t & VT_BITFIELD)) *cur_field = f = f->next; if (!f) tcc_error("too many field init"); type = &f->type; c += f->c; } } /* must put zero in holes (note that doing it that way ensures that it even works with designators) */ if (!size_only && c - corig > al) init_putz(sec, corig + al, c - corig - al); decl_initializer(type, sec, c, 0, size_only); /* XXX: make it more general */ if (!size_only && nb_elems > 1) { unsigned long c_end; uint8_t *src, *dst; int i; if (!sec) { vset(type, VT_LOCAL|VT_LVAL, c); for (i = 1; i < nb_elems; i++) { vset(type, VT_LOCAL|VT_LVAL, c + elem_size * i); vswap(); vstore(); } vpop(); } else { c_end = c + nb_elems * elem_size; if (c_end > sec->data_allocated) section_realloc(sec, c_end); src = sec->data + c; dst = src; for(i = 1; i < nb_elems; i++) { dst += elem_size; memcpy(dst, src, elem_size); } } } c += nb_elems * type_size(type, &align); if (c - corig > al) al = c - corig; return al; } #if BOOTSTRAP && HAVE_FLOAT struct long_double { int one; int two; int three; }; #endif /* store a value or an expression directly in global data or in local array */ static void init_putv(CType *type, Section *sec, unsigned long c) { int bt, bit_pos, bit_size; void *ptr; unsigned long long bit_mask; CType dtype; dtype = *type; dtype.t &= ~VT_CONSTANT; /* need to do that to avoid false warning */ if (sec) { int size, align; /* XXX: not portable */ /* XXX: generate error if incorrect relocation */ gen_assign_cast(&dtype); bt = type->t & VT_BTYPE; size = type_size(type, &align); section_reserve(sec, c + size); ptr = sec->data + c; /* XXX: make code faster ? */ if (!(type->t & VT_BITFIELD)) { bit_pos = 0; bit_size = PTR_SIZE * 8; bit_mask = -1LL; } else { bit_pos = (vtop->type.t >> VT_STRUCT_SHIFT) & 0x3f; bit_size = (vtop->type.t >> (VT_STRUCT_SHIFT + 6)) & 0x3f; bit_mask = (1LL << bit_size) - 1; } if ((vtop->r & (VT_SYM|VT_CONST)) == (VT_SYM|VT_CONST) && vtop->sym->v >= SYM_FIRST_ANOM && /* XXX This rejects compound literals like '(void *){ptr}'. The problem is that '&sym' is represented the same way, which would be ruled out by the SYM_FIRST_ANOM check above, but also '"string"' in 'char *p = "string"' is represented the same with the type being VT_PTR and the symbol being an anonymous one. That is, there's no difference in vtop between '(void *){x}' and '&(void *){x}'. Ignore pointer typed entities here. Hopefully no real code will every use compound literals with scalar type. */ (vtop->type.t & VT_BTYPE) != VT_PTR) { /* These come from compound literals, memcpy stuff over. */ Section *ssec; ElfW(Sym) *esym; ElfW_Rel *rel; esym = &((ElfW(Sym) *)symtab_section->data)[vtop->sym->c]; ssec = tcc_state->sections[esym->st_shndx]; memmove (ptr, ssec->data + esym->st_value, size); if (ssec->reloc) { /* We need to copy over all memory contents, and that includes relocations. Use the fact that relocs are created it order, so look from the end of relocs until we hit one before the copied region. */ int num_relocs = ssec->reloc->data_offset / sizeof(*rel); rel = (ElfW_Rel*)(ssec->reloc->data + ssec->reloc->data_offset); while (num_relocs--) { rel--; if (rel->r_offset >= esym->st_value + size) continue; if (rel->r_offset < esym->st_value) break; /* Note: if the same fields are initialized multiple times (possible with designators) then we possibly add multiple relocations for the same offset here. That would lead to wrong code, the last reloc needs to win. We clean this up later after the whole initializer is parsed. */ put_elf_reloca(symtab_section, sec, c + rel->r_offset - esym->st_value, ELFW(R_TYPE)(rel->r_info), ELFW(R_SYM)(rel->r_info), #if PTR_SIZE == 8 rel->r_addend #else 0 #endif ); } } } else { if ((vtop->r & VT_SYM) && (bt == VT_BYTE || bt == VT_SHORT || bt == VT_DOUBLE || bt == VT_LDOUBLE || #if PTR_SIZE == 8 (bt == VT_LLONG && bit_size != 64) || bt == VT_INT #else bt == VT_LLONG || (bt == VT_INT && bit_size != 32) #endif )) tcc_error("initializer element is not computable at load time"); switch(bt) { /* XXX: when cross-compiling we assume that each type has the same representation on host and target, which is likely to be wrong in the case of long double */ case VT_BOOL: vtop->c.i = (vtop->c.i != 0); case VT_BYTE: *(char *)ptr |= (vtop->c.i & bit_mask) << bit_pos; break; case VT_SHORT: *(short *)ptr |= (vtop->c.i & bit_mask) << bit_pos; break; case VT_FLOAT: #if HAVE_FLOAT #if !(BOOTSTRAP && __arm__) *(float*)ptr = vtop->c.f; #else { long *lptr = ptr; *lptr = vtop->c.f; } #endif #endif break; case VT_DOUBLE: #if HAVE_FLOAT #if !(BOOTSTRAP && __arm__) *(double *)ptr = vtop->c.d; #else { long long *llptr = ptr; *llptr = vtop->c.d; } #endif #endif break; case VT_LDOUBLE: #if HAVE_FLOAT if (sizeof(long double) == LDOUBLE_SIZE) #if !(BOOTSTRAP && __arm__) *(long double *)ptr = vtop->c.ld; #else { // XXX TODO: breaks on mescc/mes-tcc based build // maybe disable with HAVE_LONG_DOUBLE? //struct long_double *ldptr = ptr; //struct long_double tmp = (struct long_double)vtop->c.ld; //*ldptr = (struct long_double)tmp; } #endif else if (sizeof(double) == LDOUBLE_SIZE) *(double *)ptr = (double)vtop->c.ld; #if (defined __i386__ || defined __x86_64__) && (defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64) else if (sizeof (long double) >= 10) memcpy(memset(ptr, 0, LDOUBLE_SIZE), &vtop->c.ld, 10); #if defined (__TINYC__) else if (sizeof (long double) == sizeof (double)) __asm__("fldl %1\nfstpt %0\n" : "=m" (memset(ptr, 0, LDOUBLE_SIZE), ptr) : "m" (vtop->c.ld)); #endif #endif else tcc_error("can't cross compile long double constants"); #endif break; #if PTR_SIZE != 8 case VT_LLONG: *(long long *)ptr |= (vtop->c.i & bit_mask) << bit_pos; break; #else case VT_LLONG: #endif case VT_PTR: { addr_t val = (vtop->c.i & bit_mask) << bit_pos; #if PTR_SIZE == 8 if (vtop->r & VT_SYM) greloca(sec, vtop->sym, c, R_DATA_PTR, val); else *(addr_t *)ptr |= val; #else if (vtop->r & VT_SYM) greloc(sec, vtop->sym, c, R_DATA_PTR); *(addr_t *)ptr |= val; #endif break; } default: { int val = (vtop->c.i & bit_mask) << bit_pos; #if PTR_SIZE == 8 if (vtop->r & VT_SYM) greloca(sec, vtop->sym, c, R_DATA_PTR, val); else *(int *)ptr |= val; #else if (vtop->r & VT_SYM) greloc(sec, vtop->sym, c, R_DATA_PTR); *(int *)ptr |= val; #endif break; } } } vtop--; } else { vset(&dtype, VT_LOCAL|VT_LVAL, c); vswap(); vstore(); vpop(); } } /* 't' contains the type and storage info. 'c' is the offset of the object in section 'sec'. If 'sec' is NULL, it means stack based allocation. 'first' is true if array '{' must be read (multi dimension implicit array init handling). 'size_only' is true if size only evaluation is wanted (only for arrays). */ static void decl_initializer(CType *type, Section *sec, unsigned long c, int first, int size_only) { int len, n, no_oblock, nb, i; int size1, align1; int have_elem; Sym *s, *f; Sym indexsym; CType *t1; /* If we currently are at an '}' or ',' we have read an initializer element in one of our callers, and not yet consumed it. */ have_elem = tok == '}' || tok == ','; if (!have_elem && tok != '{' && /* In case of strings we have special handling for arrays, so don't consume them as initializer value (which would commit them to some anonymous symbol). */ tok != TOK_LSTR && tok != TOK_STR && !size_only) { parse_init_elem(!sec ? EXPR_ANY : EXPR_CONST); have_elem = 1; } if (have_elem && !(type->t & VT_ARRAY) && /* Use i_c_parameter_t, to strip toplevel qualifiers. The source type might have VT_CONSTANT set, which is of course assignable to non-const elements. */ is_compatible_parameter_types(type, &vtop->type)) { init_putv(type, sec, c); } else if (type->t & VT_ARRAY) { s = type->ref; n = s->c; t1 = pointed_type(type); size1 = type_size(t1, &align1); no_oblock = 1; if ((first && tok != TOK_LSTR && tok != TOK_STR) || tok == '{') { if (tok != '{') tcc_error("character array initializer must be a literal," " optionally enclosed in braces"); skip('{'); no_oblock = 0; } /* only parse strings here if correct type (otherwise: handle them as ((w)char *) expressions */ if ((tok == TOK_LSTR && #ifdef TCC_TARGET_PE (t1->t & VT_BTYPE) == VT_SHORT && (t1->t & VT_UNSIGNED) #else (t1->t & VT_BTYPE) == VT_INT #endif ) || (tok == TOK_STR && (t1->t & VT_BTYPE) == VT_BYTE)) { len = 0; while (tok == TOK_STR || tok == TOK_LSTR) { int cstr_len, ch; /* compute maximum number of chars wanted */ if (tok == TOK_STR) cstr_len = tokc.str.size; else cstr_len = tokc.str.size / sizeof(nwchar_t); cstr_len--; nb = cstr_len; if (n >= 0 && nb > (n - len)) nb = n - len; if (!size_only) { if (cstr_len > nb) tcc_warning("initializer-string for array is too long"); /* in order to go faster for common case (char string in global variable, we handle it specifically */ if (sec && tok == TOK_STR && size1 == 1) { memcpy(sec->data + c + len, tokc.str.data, nb); } else { for(i=0;i<nb;i++) { if (tok == TOK_STR) ch = ((unsigned char *)tokc.str.data)[i]; else ch = ((nwchar_t *)tokc.str.data)[i]; vpushi(ch); init_putv(t1, sec, c + (len + i) * size1); } } } len += nb; next(); } /* only add trailing zero if enough storage (no warning in this case since it is standard) */ if (n < 0 || len < n) { if (!size_only) { vpushi(0); init_putv(t1, sec, c + (len * size1)); } len++; } len *= size1; } else { indexsym.c = 0; f = &indexsym; do_init_list: len = 0; while (tok != '}' || have_elem) { len = decl_designator(type, sec, c, &f, size_only, len); have_elem = 0; if (type->t & VT_ARRAY) { ++indexsym.c; /* special test for multi dimensional arrays (may not be strictly correct if designators are used at the same time) */ if (no_oblock && len >= n*size1) break; } else { if (s->type.t == TOK_UNION) f = NULL; else f = f->next; if (no_oblock && f == NULL) break; } if (tok == '}') break; skip(','); } } /* put zeros at the end */ if (!size_only && len < n*size1) init_putz(sec, c + len, n*size1 - len); if (!no_oblock) skip('}'); /* patch type size if needed, which happens only for array types */ if (n < 0) s->c = size1 == 1 ? len : ((len + size1 - 1)/size1); } else if ((type->t & VT_BTYPE) == VT_STRUCT) { size1 = 1; no_oblock = 1; if (first || tok == '{') { skip('{'); no_oblock = 0; } s = type->ref; f = s->next; n = s->c; goto do_init_list; } else if (tok == '{') { next(); decl_initializer(type, sec, c, first, size_only); skip('}'); } else if (size_only) { /* If we supported only ISO C we wouldn't have to accept calling this on anything than an array size_only==1 (and even then only on the outermost level, so no recursion would be needed), because initializing a flex array member isn't supported. But GNU C supports it, so we need to recurse even into subfields of structs and arrays when size_only is set. */ /* just skip expression */ do { skip_or_save_block(NULL); } while (tok != '}' && tok != ',' && tok != -1); } else { if (!have_elem) { /* This should happen only when we haven't parsed the init element above for fear of committing a string constant to memory too early. */ if (tok != TOK_STR && tok != TOK_LSTR) expect("string constant"); parse_init_elem(!sec ? EXPR_ANY : EXPR_CONST); } init_putv(type, sec, c); } } /* parse an initializer for type 't' if 'has_init' is non zero, and allocate space in local or global data space ('r' is either VT_LOCAL or VT_CONST). If 'v' is non zero, then an associated variable 'v' of scope 'scope' is declared before initializers are parsed. If 'v' is zero, then a reference to the new object is put in the value stack. If 'has_init' is 2, a special parsing is done to handle string constants. */ static void decl_initializer_alloc(CType *type, AttributeDef *ad, int r, int has_init, int v, int scope) { int size, align, addr; ParseState saved_parse_state = {0}; TokenString *init_str = NULL; Section *sec; Sym *flexible_array; flexible_array = NULL; if ((type->t & VT_BTYPE) == VT_STRUCT) { Sym *field = type->ref->next; if (field) { while (field->next) field = field->next; if (field->type.t & VT_ARRAY && field->type.ref->c < 0) flexible_array = field; } } size = type_size(type, &align); /* If unknown size, we must evaluate it before evaluating initializers because initializers can generate global data too (e.g. string pointers or ISOC99 compound literals). It also simplifies local initializers handling */ if (size < 0 || (flexible_array && has_init)) { if (!has_init) tcc_error("unknown type size"); /* get all init string */ if (has_init == 2) { init_str = tok_str_alloc(); /* only get strings */ while (tok == TOK_STR || tok == TOK_LSTR) { tok_str_add_tok(init_str); next(); } tok_str_add(init_str, -1); tok_str_add(init_str, 0); } else { skip_or_save_block(&init_str); } /* compute size */ save_parse_state(&saved_parse_state); begin_macro(init_str, 1); next(); decl_initializer(type, NULL, 0, 1, 1); /* prepare second initializer parsing */ macro_ptr = init_str->str; next(); /* if still unknown size, error */ size = type_size(type, &align); if (size < 0) tcc_error("unknown type size"); } /* If there's a flex member and it was used in the initializer adjust size. */ if (flexible_array && flexible_array->type.ref->c > 0) size += flexible_array->type.ref->c * pointed_size(&flexible_array->type); /* take into account specified alignment if bigger */ if (ad->a.aligned) { int speca = 1 << (ad->a.aligned - 1); if (speca > align) align = speca; } else if (ad->a.packed) { align = 1; } if ((r & VT_VALMASK) == VT_LOCAL) { sec = NULL; #ifdef CONFIG_TCC_BCHECK if (tcc_state->do_bounds_check && (type->t & VT_ARRAY)) { loc--; } #endif loc = (loc - size) & -align; addr = loc; #ifdef CONFIG_TCC_BCHECK /* handles bounds */ /* XXX: currently, since we do only one pass, we cannot track '&' operators, so we add only arrays */ if (tcc_state->do_bounds_check && (type->t & VT_ARRAY)) { addr_t *bounds_ptr; /* add padding between regions */ loc--; /* then add local bound info */ bounds_ptr = section_ptr_add(lbounds_section, 2 * sizeof(addr_t)); bounds_ptr[0] = addr; bounds_ptr[1] = size; } #endif if (v) { /* local variable */ #ifdef CONFIG_TCC_ASM if (ad->asm_label) { int reg = asm_parse_regvar(ad->asm_label); if (reg >= 0) r = (r & ~VT_VALMASK) | reg; } #endif sym_push(v, type, r, addr); } else { /* push local reference */ vset(type, r, addr); } } else { Sym *sym = NULL; if (v && scope == VT_CONST) { /* see if the symbol was already defined */ sym = sym_find(v); if (sym) { patch_storage(sym, type); if (sym->type.t & VT_EXTERN) { /* if the variable is extern, it was not allocated */ sym->type.t &= ~VT_EXTERN; /* set array size if it was omitted in extern declaration */ if ((sym->type.t & VT_ARRAY) && sym->type.ref->c < 0 && type->ref->c >= 0) sym->type.ref->c = type->ref->c; } else if (!has_init) { /* we accept several definitions of the same global variable. this is tricky, because we must play with the SHN_COMMON type of the symbol */ /* no init data, we won't add more to the symbol */ update_storage(sym); goto no_alloc; } else if (sym->c) { ElfW(Sym) *esym; esym = &((ElfW(Sym) *)symtab_section->data)[sym->c]; if (esym->st_shndx == data_section->sh_num) tcc_error("redefinition of '%s'", get_tok_str(v, NULL)); } } } /* allocate symbol in corresponding section */ sec = ad->section; if (!sec) { if (has_init) sec = data_section; else if (tcc_state->nocommon) sec = bss_section; } if (sec) { addr = section_add(sec, size, align); #ifdef CONFIG_TCC_BCHECK /* add padding if bound check */ if (tcc_state->do_bounds_check) section_add(sec, 1, 1); #endif } else { addr = align; /* SHN_COMMON is special, symbol value is align */ sec = common_section; } if (v) { if (!sym) { sym = sym_push(v, type, r | VT_SYM, 0); sym->asm_label = ad->asm_label; } /* update symbol definition */ put_extern_sym(sym, sec, addr, size); } else { /* push global reference */ sym = get_sym_ref(type, sec, addr, size); vpushsym(type, sym); vtop->r |= r; } #ifdef CONFIG_TCC_BCHECK /* handles bounds now because the symbol must be defined before for the relocation */ if (tcc_state->do_bounds_check) { addr_t *bounds_ptr; greloca(bounds_section, sym, bounds_section->data_offset, R_DATA_PTR, 0); /* then add global bound info */ bounds_ptr = section_ptr_add(bounds_section, 2 * sizeof(addr_t)); bounds_ptr[0] = 0; /* relocated */ bounds_ptr[1] = size; } #endif } if (type->t & VT_VLA) { int a; /* save current stack pointer */ if (vlas_in_scope == 0) { if (vla_sp_root_loc == -1) vla_sp_root_loc = (loc -= PTR_SIZE); gen_vla_sp_save(vla_sp_root_loc); } vla_runtime_type_size(type, &a); gen_vla_alloc(type, a); gen_vla_sp_save(addr); vla_sp_loc = addr; vlas_in_scope++; } else if (has_init) { size_t oldreloc_offset = 0; if (sec && sec->reloc) oldreloc_offset = sec->reloc->data_offset; decl_initializer(type, sec, addr, 1, 0); if (sec && sec->reloc) squeeze_multi_relocs(sec, oldreloc_offset); /* patch flexible array member size back to -1, */ /* for possible subsequent similar declarations */ if (flexible_array) flexible_array->type.ref->c = -1; } no_alloc: /* restore parse state if needed */ if (init_str) { end_macro(); restore_parse_state(&saved_parse_state); } } /* parse a function defined by symbol 'sym' and generate its code in 'cur_text_section' */ static void gen_function(Sym *sym) { nocode_wanted = 0; ind = cur_text_section->data_offset; /* NOTE: we patch the symbol size later */ put_extern_sym(sym, cur_text_section, ind, 0); funcname = get_tok_str(sym->v, NULL); func_ind = ind; /* Initialize VLA state */ vla_sp_loc = -1; vla_sp_root_loc = -1; /* put debug symbol */ tcc_debug_funcstart(tcc_state, sym); /* push a dummy symbol to enable local sym storage */ sym_push2(&local_stack, SYM_FIELD, 0, 0); local_scope = 1; /* for function parameters */ gfunc_prolog(&sym->type); local_scope = 0; rsym = 0; block(NULL, NULL, 0); nocode_wanted = 0; gsym(rsym); gfunc_epilog(); cur_text_section->data_offset = ind; label_pop(&global_label_stack, NULL); /* reset local stack */ local_scope = 0; sym_pop(&local_stack, NULL, 0); /* end of function */ /* patch symbol size */ ((ElfW(Sym) *)symtab_section->data)[sym->c].st_size = ind - func_ind; tcc_debug_funcend(tcc_state, ind - func_ind); /* It's better to crash than to generate wrong code */ cur_text_section = NULL; funcname = ""; /* for safety */ func_vt.t = VT_VOID; /* for safety */ func_var = 0; /* for safety */ ind = 0; /* for safety */ nocode_wanted = 1; check_vstack(); } static void gen_inline_functions(TCCState *s) { Sym *sym; int inline_generated, i, ln; struct InlineFunc *fn; ln = file->line_num; /* iterate while inline function are referenced */ for(;;) { inline_generated = 0; for (i = 0; i < s->nb_inline_fns; ++i) { fn = s->inline_fns[i]; sym = fn->sym; if (sym && sym->c) { /* the function was used: generate its code and convert it to a normal function */ fn->sym = NULL; if (file) pstrcpy(file->filename, sizeof file->filename, fn->filename); sym->type.t &= ~VT_INLINE; begin_macro(fn->func_str, 1); next(); cur_text_section = text_section; gen_function(sym); end_macro(); inline_generated = 1; } } if (!inline_generated) break; } file->line_num = ln; } ST_FUNC void free_inline_functions(TCCState *s) { int i; /* free tokens of unused inline functions */ for (i = 0; i < s->nb_inline_fns; ++i) { struct InlineFunc *fn = s->inline_fns[i]; if (fn->sym) tok_str_free(fn->func_str); } dynarray_reset(&s->inline_fns, &s->nb_inline_fns); } /* 'l' is VT_LOCAL or VT_CONST to define default storage type, or VT_CMP if parsing old style parameter decl list (and FUNC_SYM is set then) */ static int decl0(int l, int is_for_loop_init, Sym *func_sym) { int v, has_init, r; CType type, btype; Sym *sym; AttributeDef ad; while (1) { if (!parse_btype(&btype, &ad)) { if (is_for_loop_init) return 0; /* skip redundant ';' if not in old parameter decl scope */ if (tok == ';' && l != VT_CMP) { next(); continue; } if (l == VT_CONST && (tok == TOK_ASM1 || tok == TOK_ASM2 || tok == TOK_ASM3)) { /* global asm block */ asm_global_instr(); continue; } /* special test for old K&R protos without explicit int type. Only accepted when defining global data */ if (l != VT_CONST || tok < TOK_UIDENT) break; btype.t = VT_INT; } if (((btype.t & VT_BTYPE) == VT_ENUM || (btype.t & VT_BTYPE) == VT_STRUCT) && tok == ';') { if ((btype.t & VT_BTYPE) == VT_STRUCT) { int v = btype.ref->v; if (!(v & SYM_FIELD) && (v & ~SYM_STRUCT) >= SYM_FIRST_ANOM) tcc_warning("unnamed struct/union that defines no instances"); } next(); continue; } while (1) { /* iterate thru each declaration */ type = btype; /* If the base type itself was an array type of unspecified size (like in 'typedef int arr[]; arr x = {1};') then we will overwrite the unknown size by the real one for this decl. We need to unshare the ref symbol holding that size. */ if ((type.t & VT_ARRAY) && type.ref->c < 0) { type.ref = sym_push(SYM_FIELD, &type.ref->type, 0, type.ref->c); } type_decl(&type, &ad, &v, TYPE_DIRECT); #if 0 { char buf[500]; type_to_str(buf, sizeof(buf), &type, get_tok_str(v, NULL)); printf("type = '%s'\n", buf); } #endif if ((type.t & VT_BTYPE) == VT_FUNC) { if ((type.t & VT_STATIC) && (l == VT_LOCAL)) { tcc_error("function without file scope cannot be static"); } /* if old style function prototype, we accept a declaration list */ sym = type.ref; if (sym->c == FUNC_OLD && l == VT_CONST) decl0(VT_CMP, 0, sym); } if (gnu_ext && (tok == TOK_ASM1 || tok == TOK_ASM2 || tok == TOK_ASM3)) { ad.asm_label = asm_label_instr(); /* parse one last attribute list, after asm label */ parse_attribute(&ad); if (tok == '{') expect(";"); } if (ad.a.weak) type.t |= VT_WEAK; #ifdef TCC_TARGET_PE if (ad.a.func_import || ad.a.func_export) { if (type.t & (VT_STATIC|VT_TYPEDEF)) tcc_error("cannot have dll linkage with static or typedef"); if (ad.a.func_export) type.t |= VT_EXPORT; else if ((type.t & VT_BTYPE) != VT_FUNC) type.t |= VT_IMPORT|VT_EXTERN; } #endif type.t |= ad.a.visibility << VT_VIS_SHIFT; if (tok == '{') { if (l != VT_CONST) tcc_error("cannot use local functions"); if ((type.t & VT_BTYPE) != VT_FUNC) expect("function definition"); /* reject abstract declarators in function definition make old style params without decl have int type */ sym = type.ref; while ((sym = sym->next) != NULL) { if (!(sym->v & ~SYM_FIELD)) expect("identifier"); if (sym->type.t == VT_VOID) sym->type = int_type; } /* XXX: cannot do better now: convert extern line to static inline */ if ((type.t & (VT_EXTERN | VT_INLINE)) == (VT_EXTERN | VT_INLINE)) type.t = (type.t & ~VT_EXTERN) | VT_STATIC; sym = sym_find(v); if (sym) { Sym *ref; if ((sym->type.t & VT_BTYPE) != VT_FUNC) goto func_error1; ref = sym->type.ref; /* use func_call from prototype if not defined */ if (ref->a.func_call != FUNC_CDECL && type.ref->a.func_call == FUNC_CDECL) type.ref->a.func_call = ref->a.func_call; /* use static from prototype */ if (sym->type.t & VT_STATIC) type.t = (type.t & ~VT_EXTERN) | VT_STATIC; /* If the definition has no visibility use the one from prototype. */ if (! (type.t & VT_VIS_MASK)) type.t |= sym->type.t & VT_VIS_MASK; /* apply other storage attributes from prototype */ type.t |= sym->type.t & (VT_EXPORT|VT_WEAK); if (!is_compatible_types(&sym->type, &type)) { func_error1: tcc_error("incompatible types for redefinition of '%s'", get_tok_str(v, NULL)); } if (ref->a.func_body) tcc_error("redefinition of '%s'", get_tok_str(v, NULL)); /* if symbol is already defined, then put complete type */ sym->type = type; } else { /* put function symbol */ sym = global_identifier_push(v, type.t, 0); sym->type.ref = type.ref; } sym->type.ref->a.func_body = 1; sym->r = VT_SYM | VT_CONST; /* static inline functions are just recorded as a kind of macro. Their code will be emitted at the end of the compilation unit only if they are used */ if ((type.t & (VT_INLINE | VT_STATIC)) == (VT_INLINE | VT_STATIC)) { struct InlineFunc *fn; const char *filename; filename = file ? file->filename : ""; fn = tcc_malloc(sizeof *fn + strlen(filename)); strcpy(fn->filename, filename); fn->sym = sym; skip_or_save_block(&fn->func_str); dynarray_add(&tcc_state->inline_fns, &tcc_state->nb_inline_fns, fn); } else { /* compute text section */ cur_text_section = ad.section; if (!cur_text_section) cur_text_section = text_section; gen_function(sym); } break; } else { if (l == VT_CMP) { /* find parameter in function parameter list */ for (sym = func_sym->next; sym; sym = sym->next) if ((sym->v & ~SYM_FIELD) == v) goto found; tcc_error("declaration for parameter '%s' but no such parameter", get_tok_str(v, NULL)); found: if (type.t & VT_STORAGE) /* 'register' is okay */ tcc_error("storage class specified for '%s'", get_tok_str(v, NULL)); if (sym->type.t != VT_VOID) tcc_error("redefinition of parameter '%s'", get_tok_str(v, NULL)); convert_parameter_type(&type); sym->type = type; } else if (type.t & VT_TYPEDEF) { /* save typedefed type */ /* XXX: test storage specifiers ? */ sym = sym_find(v); if (sym && sym->scope == local_scope) { if (!is_compatible_types(&sym->type, &type) || !(sym->type.t & VT_TYPEDEF)) tcc_error("incompatible redefinition of '%s'", get_tok_str(v, NULL)); sym->type = type; } else { sym = sym_push(v, &type, 0, 0); } sym->a = ad.a; } else { r = 0; if ((type.t & VT_BTYPE) == VT_FUNC) { /* external function definition */ /* specific case for func_call attribute */ type.ref->a = ad.a; } else if (!(type.t & VT_ARRAY)) { /* not lvalue if array */ r |= lvalue_type(type.t); } has_init = (tok == '='); if (has_init && (type.t & VT_VLA)) tcc_error("variable length array cannot be initialized"); if (((type.t & VT_EXTERN) && (!has_init || l != VT_CONST)) || ((type.t & VT_BTYPE) == VT_FUNC) || ((type.t & VT_ARRAY) && (type.t & VT_STATIC) && !has_init && l == VT_CONST && type.ref->c < 0)) { /* external variable or function */ /* NOTE: as GCC, uninitialized global static arrays of null size are considered as extern */ sym = external_sym(v, &type, r); sym->asm_label = ad.asm_label; if (ad.alias_target) { Section tsec; ElfW(Sym) *esym; Sym *alias_target; alias_target = sym_find(ad.alias_target); if (!alias_target || !alias_target->c) tcc_error("unsupported forward __alias__ attribute"); esym = &((ElfW(Sym) *)symtab_section->data)[alias_target->c]; tsec.sh_num = esym->st_shndx; put_extern_sym2(sym, &tsec, esym->st_value, esym->st_size, 0); } } else { if (type.t & VT_STATIC) r |= VT_CONST; else r |= l; if (has_init) next(); decl_initializer_alloc(&type, &ad, r, has_init, v, l); } } if (tok != ',') { if (is_for_loop_init) return 1; skip(';'); break; } next(); } ad.a.aligned = 0; } } return 0; } ST_FUNC void decl(int l) { decl0(l, 0, NULL); } /* ------------------------------------------------------------------------- */
/* * ELF file handling for TCC * * Copyright (c) 2001-2004 Fabrice Bellard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _TCC_H #include "tcc.h" #endif /* Define this to get some debug output during relocation processing. */ #undef DEBUG_RELOC /********************************************************/ /* global variables */ ST_DATA Section *text_section, *data_section, *bss_section; /* predefined sections */ ST_DATA Section *common_section; ST_DATA Section *cur_text_section; /* current section where function code is generated */ #ifdef CONFIG_TCC_ASM ST_DATA Section *last_text_section; /* to handle .previous asm directive */ #endif #ifdef CONFIG_TCC_BCHECK /* bound check related sections */ ST_DATA Section *bounds_section; /* contains global data bound description */ ST_DATA Section *lbounds_section; /* contains local data bound description */ #endif /* symbol sections */ ST_DATA Section *symtab_section, *strtab_section; /* debug sections */ ST_DATA Section *stab_section, *stabstr_section; /* XXX: avoid static variable */ static int new_undef_sym = 0; /* Is there a new undefined sym since last new_undef_sym() */ /* ------------------------------------------------------------------------- */ ST_FUNC void tccelf_new(TCCState *s) { /* no section zero */ dynarray_add(&s->sections, &s->nb_sections, NULL); /* create standard sections */ text_section = new_section(s, ".text", SHT_PROGBITS, SHF_ALLOC | SHF_EXECINSTR); data_section = new_section(s, ".data", SHT_PROGBITS, SHF_ALLOC | SHF_WRITE); bss_section = new_section(s, ".bss", SHT_NOBITS, SHF_ALLOC | SHF_WRITE); common_section = new_section(s, ".common", SHT_NOBITS, SHF_PRIVATE); common_section->sh_num = SHN_COMMON; /* symbols are always generated for linking stage */ symtab_section = new_symtab(s, ".symtab", SHT_SYMTAB, 0, ".strtab", ".hashtab", SHF_PRIVATE); strtab_section = symtab_section->link; s->symtab = symtab_section; /* private symbol table for dynamic symbols */ s->dynsymtab_section = new_symtab(s, ".dynsymtab", SHT_SYMTAB, SHF_PRIVATE, ".dynstrtab", ".dynhashtab", SHF_PRIVATE); get_sym_attr(s, 0, 1); } #ifdef CONFIG_TCC_BCHECK ST_FUNC void tccelf_bounds_new(TCCState *s) { /* create bounds sections */ bounds_section = new_section(s, ".bounds", SHT_PROGBITS, SHF_ALLOC); lbounds_section = new_section(s, ".lbounds", SHT_PROGBITS, SHF_ALLOC); } #endif ST_FUNC void tccelf_stab_new(TCCState *s) { stab_section = new_section(s, ".stab", SHT_PROGBITS, 0); stab_section->sh_entsize = sizeof(Stab_Sym); stabstr_section = new_section(s, ".stabstr", SHT_STRTAB, 0); put_elf_str(stabstr_section, ""); stab_section->link = stabstr_section; /* put first entry */ put_stabs("", 0, 0, 0, 0); } static void free_section(Section *s) { tcc_free(s->data); } ST_FUNC void tccelf_delete(TCCState *s1) { int i; /* free all sections */ for(i = 1; i < s1->nb_sections; i++) free_section(s1->sections[i]); dynarray_reset(&s1->sections, &s1->nb_sections); for(i = 0; i < s1->nb_priv_sections; i++) free_section(s1->priv_sections[i]); dynarray_reset(&s1->priv_sections, &s1->nb_priv_sections); /* free any loaded DLLs */ #ifdef TCC_IS_NATIVE for ( i = 0; i < s1->nb_loaded_dlls; i++) { DLLReference *ref = s1->loaded_dlls[i]; if ( ref->handle ) # ifdef _WIN32 FreeLibrary((HMODULE)ref->handle); # else dlclose(ref->handle); # endif } #endif /* free loaded dlls array */ dynarray_reset(&s1->loaded_dlls, &s1->nb_loaded_dlls); tcc_free(s1->sym_attrs); } ST_FUNC Section *new_section(TCCState *s1, const char *name, int sh_type, int sh_flags) { Section *sec; sec = tcc_mallocz(sizeof(Section) + strlen(name)); strcpy(sec->name, name); sec->sh_type = sh_type; sec->sh_flags = sh_flags; switch(sh_type) { case SHT_HASH: case SHT_REL: case SHT_RELA: case SHT_DYNSYM: case SHT_SYMTAB: case SHT_DYNAMIC: sec->sh_addralign = 4; break; case SHT_STRTAB: sec->sh_addralign = 1; break; default: sec->sh_addralign = PTR_SIZE; /* gcc/pcc default alignment */ break; } if (sh_flags & SHF_PRIVATE) { dynarray_add(&s1->priv_sections, &s1->nb_priv_sections, sec); } else { sec->sh_num = s1->nb_sections; dynarray_add(&s1->sections, &s1->nb_sections, sec); } return sec; } ST_FUNC Section *new_symtab(TCCState *s1, const char *symtab_name, int sh_type, int sh_flags, const char *strtab_name, const char *hash_name, int hash_sh_flags) { Section *symtab, *strtab, *hash; int *ptr, nb_buckets; symtab = new_section(s1, symtab_name, sh_type, sh_flags); symtab->sh_entsize = sizeof(ElfW(Sym)); strtab = new_section(s1, strtab_name, SHT_STRTAB, sh_flags); put_elf_str(strtab, ""); symtab->link = strtab; put_elf_sym(symtab, 0, 0, 0, 0, 0, NULL); nb_buckets = 1; hash = new_section(s1, hash_name, SHT_HASH, hash_sh_flags); hash->sh_entsize = sizeof(int); symtab->hash = hash; hash->link = symtab; ptr = section_ptr_add(hash, (2 + nb_buckets + 1) * sizeof(int)); ptr[0] = nb_buckets; ptr[1] = 1; memset(ptr + 2, 0, (nb_buckets + 1) * sizeof(int)); return symtab; } /* realloc section and set its content to zero */ ST_FUNC void section_realloc(Section *sec, unsigned long new_size) { unsigned long size; unsigned char *data; size = sec->data_allocated; if (size == 0) size = 1; while (size < new_size) size = size * 2; data = tcc_realloc(sec->data, size); memset(data + sec->data_allocated, 0, size - sec->data_allocated); sec->data = data; sec->data_allocated = size; } /* reserve at least 'size' bytes aligned per 'align' in section 'sec' from current offset, and return the aligned offset */ ST_FUNC size_t section_add(Section *sec, addr_t size, int align) { size_t offset, offset1; offset = (sec->data_offset + align - 1) & -align; offset1 = offset + size; if (sec->sh_type != SHT_NOBITS && offset1 > sec->data_allocated) section_realloc(sec, offset1); sec->data_offset = offset1; if (align > sec->sh_addralign) sec->sh_addralign = align; return offset; } /* reserve at least 'size' bytes in section 'sec' from sec->data_offset. */ ST_FUNC void *section_ptr_add(Section *sec, addr_t size) { size_t offset = section_add(sec, size, 1); return sec->data + offset; } /* reserve at least 'size' bytes from section start */ ST_FUNC void section_reserve(Section *sec, unsigned long size) { if (size > sec->data_allocated) section_realloc(sec, size); if (size > sec->data_offset) sec->data_offset = size; } /* return a reference to a section, and create it if it does not exists */ ST_FUNC Section *find_section(TCCState *s1, const char *name) { Section *sec; int i; for(i = 1; i < s1->nb_sections; i++) { sec = s1->sections[i]; if (!strcmp(name, sec->name)) return sec; } /* sections are created as PROGBITS */ return new_section(s1, name, SHT_PROGBITS, SHF_ALLOC); } /* ------------------------------------------------------------------------- */ ST_FUNC int put_elf_str(Section *s, const char *sym) { int offset, len; char *ptr; len = strlen(sym) + 1; offset = s->data_offset; ptr = section_ptr_add(s, len); memcpy(ptr, sym, len); return offset; } /* elf symbol hashing function */ static unsigned long elf_hash(const unsigned char *name) { unsigned long h = 0, g; while (*name) { h = (h << 4) + *name++; g = h & 0xf0000000; if (g) h ^= g >> 24; h &= ~g; } return h; } /* rebuild hash table of section s */ /* NOTE: we do factorize the hash table code to go faster */ static void rebuild_hash(Section *s, unsigned int nb_buckets) { ElfW(Sym) *sym; int *ptr, *hash, nb_syms, sym_index, h; unsigned char *strtab; strtab = s->link->data; nb_syms = s->data_offset / sizeof(ElfW(Sym)); s->hash->data_offset = 0; ptr = section_ptr_add(s->hash, (2 + nb_buckets + nb_syms) * sizeof(int)); ptr[0] = nb_buckets; ptr[1] = nb_syms; ptr += 2; hash = ptr; memset(hash, 0, (nb_buckets + 1) * sizeof(int)); ptr += nb_buckets + 1; sym = (ElfW(Sym) *)s->data + 1; for(sym_index = 1; sym_index < nb_syms; sym_index++) { if (ELFW(ST_BIND)(sym->st_info) != STB_LOCAL) { #if !BOOTSTRAP h = elf_hash(strtab + sym->st_name) % nb_buckets; #else h = elf_hash(strtab + sym->st_name); h = h % nb_buckets; #endif *ptr = hash[h]; hash[h] = sym_index; } else { *ptr = 0; } ptr++; sym++; } } /* return the symbol number */ ST_FUNC int put_elf_sym(Section *s, addr_t value, unsigned long size, int info, int other, int shndx, const char *name) { int name_offset, sym_index; int nbuckets, h; ElfW(Sym) *sym; Section *hs; sym = section_ptr_add(s, sizeof(ElfW(Sym))); if (name) name_offset = put_elf_str(s->link, name); else name_offset = 0; /* XXX: endianness */ sym->st_name = name_offset; sym->st_value = value; sym->st_size = size; sym->st_info = info; sym->st_other = other; sym->st_shndx = shndx; sym_index = sym - (ElfW(Sym) *)s->data; hs = s->hash; if (hs) { int *ptr, *base; ptr = section_ptr_add(hs, sizeof(int)); base = (int *)hs->data; /* only add global or weak symbols */ if (ELFW(ST_BIND)(info) != STB_LOCAL) { /* add another hashing entry */ nbuckets = base[0]; #if !BOOTSTRAP h = elf_hash((unsigned char *) name) % nbuckets; #else h = elf_hash((unsigned char *) name); h = h % nbuckets; #endif *ptr = base[2 + h]; base[2 + h] = sym_index; base[1]++; /* we resize the hash table */ hs->nb_hashed_syms++; if (hs->nb_hashed_syms > 2 * nbuckets) { rebuild_hash(s, 2 * nbuckets); } } else { *ptr = 0; base[1]++; } } return sym_index; } /* find global ELF symbol 'name' and return its index. Return 0 if not found. */ ST_FUNC int find_elf_sym(Section *s, const char *name) { ElfW(Sym) *sym; Section *hs; int nbuckets, sym_index, h; const char *name1; hs = s->hash; if (!hs) return 0; nbuckets = ((int *)hs->data)[0]; #if !BOOTSTRAP h = elf_hash((unsigned char *) name) % nbuckets; #else h = elf_hash((unsigned char *) name); h = h % nbuckets; #endif sym_index = ((int *)hs->data)[2 + h]; while (sym_index != 0) { sym = &((ElfW(Sym) *)s->data)[sym_index]; name1 = (char *) s->link->data + sym->st_name; if (!strcmp(name, name1)) return sym_index; sym_index = ((int *)hs->data)[2 + nbuckets + sym_index]; } return 0; } /* return elf symbol value, signal error if 'err' is nonzero */ ST_FUNC addr_t get_elf_sym_addr(TCCState *s, const char *name, int err) { int sym_index; ElfW(Sym) *sym; sym_index = find_elf_sym(s->symtab, name); sym = &((ElfW(Sym) *)s->symtab->data)[sym_index]; if (!sym_index || sym->st_shndx == SHN_UNDEF) { if (err) tcc_error("%s not defined", name); return 0; } return sym->st_value; } #if __MESC_MES__ || !__MESC__ #if !defined (__intptr_t_defined) && !defined (__MES_INTPTR_T) typedef unsigned long uintptr_t; #endif #else // guile #undef uintptr_t #endif /* return elf symbol value */ LIBTCCAPI void *tcc_get_symbol(TCCState *s, const char *name) { return (void*)(uintptr_t)get_elf_sym_addr(s, name, 0); } #if defined TCC_IS_NATIVE || defined TCC_TARGET_PE /* return elf symbol value or error */ ST_FUNC void* tcc_get_symbol_err(TCCState *s, const char *name) { return (void*)(uintptr_t)get_elf_sym_addr(s, name, 1); } #endif /* add an elf symbol : check if it is already defined and patch it. Return symbol index. NOTE that sh_num can be SHN_UNDEF. */ ST_FUNC int set_elf_sym(Section *s, addr_t value, unsigned long size, int info, int other, int shndx, const char *name) { ElfW(Sym) *esym; int sym_bind, sym_index, sym_type, esym_bind; unsigned char sym_vis, esym_vis, new_vis; sym_bind = ELFW(ST_BIND)(info); sym_type = ELFW(ST_TYPE)(info); sym_vis = ELFW(ST_VISIBILITY)(other); sym_index = find_elf_sym(s, name); esym = &((ElfW(Sym) *)s->data)[sym_index]; if (sym_index && esym->st_value == value && esym->st_size == size && esym->st_info == info && esym->st_other == other && esym->st_shndx == shndx) return sym_index; if (sym_bind != STB_LOCAL) { /* we search global or weak symbols */ if (!sym_index) goto do_def; if (esym->st_shndx != SHN_UNDEF) { esym_bind = ELFW(ST_BIND)(esym->st_info); /* propagate the most constraining visibility */ /* STV_DEFAULT(0)<STV_PROTECTED(3)<STV_HIDDEN(2)<STV_INTERNAL(1) */ esym_vis = ELFW(ST_VISIBILITY)(esym->st_other); if (esym_vis == STV_DEFAULT) { new_vis = sym_vis; } else if (sym_vis == STV_DEFAULT) { new_vis = esym_vis; } else { new_vis = (esym_vis < sym_vis) ? esym_vis : sym_vis; } esym->st_other = (esym->st_other & ~ELFW(ST_VISIBILITY)(-1)) | new_vis; other = esym->st_other; /* in case we have to patch esym */ if (shndx == SHN_UNDEF) { /* ignore adding of undefined symbol if the corresponding symbol is already defined */ } else if (sym_bind == STB_GLOBAL && esym_bind == STB_WEAK) { /* global overrides weak, so patch */ goto do_patch; } else if (sym_bind == STB_WEAK && esym_bind == STB_GLOBAL) { /* weak is ignored if already global */ } else if (sym_bind == STB_WEAK && esym_bind == STB_WEAK) { /* keep first-found weak definition, ignore subsequents */ } else if (sym_vis == STV_HIDDEN || sym_vis == STV_INTERNAL) { /* ignore hidden symbols after */ } else if ((esym->st_shndx == SHN_COMMON || esym->st_shndx == bss_section->sh_num) && (shndx < SHN_LORESERVE && shndx != bss_section->sh_num)) { /* data symbol gets precedence over common/bss */ goto do_patch; } else if (shndx == SHN_COMMON || shndx == bss_section->sh_num) { /* data symbol keeps precedence over common/bss */ } else if (s == tcc_state->dynsymtab_section) { /* we accept that two DLL define the same symbol */ } else { #if 0 printf("new_bind=%x new_shndx=%x new_vis=%x old_bind=%x old_shndx=%x old_vis=%x\n", sym_bind, shndx, new_vis, esym_bind, esym->st_shndx, esym_vis); #endif tcc_error_noabort("'%s' defined twice", name); } } else { do_patch: esym->st_info = ELFW(ST_INFO)(sym_bind, sym_type); esym->st_shndx = shndx; new_undef_sym = 1; esym->st_value = value; esym->st_size = size; esym->st_other = other; } } else { do_def: sym_index = put_elf_sym(s, value, size, ELFW(ST_INFO)(sym_bind, sym_type), other, shndx, name); } return sym_index; } /* put relocation */ ST_FUNC void put_elf_reloca(Section *symtab, Section *s, unsigned long offset, int type, int symbol, addr_t addend) { char buf[256]; Section *sr; ElfW_Rel *rel; sr = s->reloc; if (!sr) { /* if no relocation section, create it */ snprintf(buf, sizeof(buf), REL_SECTION_FMT, s->name); /* if the symtab is allocated, then we consider the relocation are also */ sr = new_section(tcc_state, buf, SHT_RELX, symtab->sh_flags); sr->sh_entsize = sizeof(ElfW_Rel); sr->link = symtab; sr->sh_info = s->sh_num; s->reloc = sr; } rel = section_ptr_add(sr, sizeof(ElfW_Rel)); rel->r_offset = offset; rel->r_info = ELFW(R_INFO)(symbol, type); #if SHT_RELX == SHT_RELA rel->r_addend = addend; #else if (addend) tcc_error("non-zero addend on REL architecture"); #endif } ST_FUNC void put_elf_reloc(Section *symtab, Section *s, unsigned long offset, int type, int symbol) { put_elf_reloca(symtab, s, offset, type, symbol, 0); } /* Remove relocations for section S->reloc starting at oldrelocoffset that are to the same place, retaining the last of them. As side effect the relocations are sorted. Possibly reduces the number of relocs. */ ST_FUNC void squeeze_multi_relocs(Section *s, size_t oldrelocoffset) { Section *sr = s->reloc; ElfW_Rel *r, *dest; ssize_t a; ElfW(Addr) addr; if (oldrelocoffset + sizeof(*r) >= sr->data_offset) return; /* The relocs we're dealing with are the result of initializer parsing. So they will be mostly in order and there aren't many of them. Secondly we need a stable sort (which qsort isn't). We use a simple insertion sort. */ for (a = oldrelocoffset + sizeof(*r); a < sr->data_offset; a += sizeof(*r)) { ssize_t i = a - sizeof(*r); ///addr = ((ElfW_Rel*)(sr->data + a))->r_offset; ElfW_Rel* ea = (ElfW_Rel*)(sr->data + a); ElfW_Rel* ei = (ElfW_Rel*)(sr->data + i); addr = ea->r_offset; for (; i >= (ssize_t)oldrelocoffset && ei->r_offset > addr; i -= sizeof(*r)) { ei = (ElfW_Rel*)(sr->data + i); ElfW_Rel tmp = *(ElfW_Rel*)(sr->data + a); *(ElfW_Rel*)(sr->data + a) = *(ElfW_Rel*)(sr->data + i); *(ElfW_Rel*)(sr->data + i) = tmp; } } r = (ElfW_Rel*)(sr->data + oldrelocoffset); dest = r; for (; r < (ElfW_Rel*)(sr->data + sr->data_offset); r++) { if (dest->r_offset != r->r_offset) dest++; *dest = *r; } sr->data_offset = (unsigned char*)dest - sr->data + sizeof(*r); } /* put stab debug information */ ST_FUNC void put_stabs(const char *str, int type, int other, int desc, unsigned long value) { Stab_Sym *sym; sym = section_ptr_add(stab_section, sizeof(Stab_Sym)); if (str) { sym->n_strx = put_elf_str(stabstr_section, str); } else { sym->n_strx = 0; } sym->n_type = type; sym->n_other = other; sym->n_desc = desc; sym->n_value = value; } ST_FUNC void put_stabs_r(const char *str, int type, int other, int desc, unsigned long value, Section *sec, int sym_index) { put_stabs(str, type, other, desc, value); put_elf_reloc(symtab_section, stab_section, stab_section->data_offset - sizeof(unsigned int), R_DATA_32, sym_index); } ST_FUNC void put_stabn(int type, int other, int desc, int value) { put_stabs(NULL, type, other, desc, value); } ST_FUNC void put_stabd(int type, int other, int desc) { put_stabs(NULL, type, other, desc, 0); } ST_FUNC struct sym_attr *get_sym_attr(TCCState *s1, int index, int alloc) { int n; struct sym_attr *tab; if (index >= s1->nb_sym_attrs) { if (!alloc) return s1->sym_attrs; /* find immediately bigger power of 2 and reallocate array */ n = 1; while (index >= n) n *= 2; tab = tcc_realloc(s1->sym_attrs, n * sizeof(*s1->sym_attrs)); s1->sym_attrs = tab; memset(s1->sym_attrs + s1->nb_sym_attrs, 0, (n - s1->nb_sym_attrs) * sizeof(*s1->sym_attrs)); s1->nb_sym_attrs = n; } return &s1->sym_attrs[index]; } /* Browse each elem of type <type> in section <sec> starting at elem <startoff> using variable <elem> */ #define for_each_elem(sec, startoff, elem, type) \ for (elem = (type *) sec->data + startoff; \ elem < (type *) (sec->data + sec->data_offset); elem++) /* In an ELF file symbol table, the local symbols must appear below the global and weak ones. Since TCC cannot sort it while generating the code, we must do it after. All the relocation tables are also modified to take into account the symbol table sorting */ static void sort_syms(TCCState *s1, Section *s) { int *old_to_new_syms; ElfW(Sym) *new_syms; int nb_syms, i; ElfW(Sym) *p, *q; ElfW_Rel *rel; Section *sr; int type, sym_index; nb_syms = s->data_offset / sizeof(ElfW(Sym)); new_syms = tcc_malloc(nb_syms * sizeof(ElfW(Sym))); old_to_new_syms = tcc_malloc(nb_syms * sizeof(int)); /* first pass for local symbols */ p = (ElfW(Sym) *)s->data; q = new_syms; for(i = 0; i < nb_syms; i++) { if (ELFW(ST_BIND)(p->st_info) == STB_LOCAL) { old_to_new_syms[i] = q - new_syms; *q++ = *p; } p++; } /* save the number of local symbols in section header */ s->sh_info = q - new_syms; /* then second pass for non local symbols */ p = (ElfW(Sym) *)s->data; for(i = 0; i < nb_syms; i++) { if (ELFW(ST_BIND)(p->st_info) != STB_LOCAL) { old_to_new_syms[i] = q - new_syms; *q++ = *p; } p++; } /* we copy the new symbols to the old */ memcpy(s->data, new_syms, nb_syms * sizeof(ElfW(Sym))); tcc_free(new_syms); /* now we modify all the relocations */ for(i = 1; i < s1->nb_sections; i++) { sr = s1->sections[i]; if (sr->sh_type == SHT_RELX && sr->link == s) { for_each_elem(sr, 0, rel, ElfW_Rel) { sym_index = ELFW(R_SYM)(rel->r_info); type = ELFW(R_TYPE)(rel->r_info); sym_index = old_to_new_syms[sym_index]; rel->r_info = ELFW(R_INFO)(sym_index, type); } } } tcc_free(old_to_new_syms); } /* relocate common symbols in the .bss section */ ST_FUNC void relocate_common_syms(void) { ElfW(Sym) *sym; for_each_elem(symtab_section, 1, sym, ElfW(Sym)) { if (sym->st_shndx == SHN_COMMON) { /* symbol alignment is in st_value for SHN_COMMONs */ sym->st_value = section_add(bss_section, sym->st_size, sym->st_value); sym->st_shndx = bss_section->sh_num; } } } /* relocate symbol table, resolve undefined symbols if do_resolve is true and output error if undefined symbol. */ ST_FUNC void relocate_syms(TCCState *s1, Section *symtab, int do_resolve) { ElfW(Sym) *sym; int sym_bind, sh_num; const char *name; for_each_elem(symtab, 1, sym, ElfW(Sym)) { sh_num = sym->st_shndx; if (sh_num == SHN_UNDEF) { name = (char *) strtab_section->data + sym->st_name; /* Use ld.so to resolve symbol for us (for tcc -run) */ if (do_resolve) { #if defined TCC_IS_NATIVE && !defined TCC_TARGET_PE void *addr = dlsym(RTLD_DEFAULT, name); if (addr) { sym->st_value = (addr_t) addr; #ifdef DEBUG_RELOC printf ("relocate_sym: %s -> 0x%lx\n", name, sym->st_value); #endif goto found; } #endif /* if dynamic symbol exist, it will be used in relocate_section */ } else if (s1->dynsym && find_elf_sym(s1->dynsym, name)) goto found; /* XXX: _fp_hw seems to be part of the ABI, so we ignore it */ if (!strcmp(name, "_fp_hw")) goto found; /* only weak symbols are accepted to be undefined. Their value is zero */ sym_bind = ELFW(ST_BIND)(sym->st_info); if (sym_bind == STB_WEAK) sym->st_value = 0; else tcc_error_noabort("undefined symbol '%s'", name); } else if (sh_num < SHN_LORESERVE) { /* add section base */ sym->st_value += s1->sections[sym->st_shndx]->sh_addr; } found: ; } } /* relocate a given section (CPU dependent) by applying the relocations in the associated relocation section */ ST_FUNC void relocate_section(TCCState *s1, Section *s) { Section *sr = s->reloc; ElfW_Rel *rel; ElfW(Sym) *sym; int type, sym_index; unsigned char *ptr; addr_t tgt, addr; relocate_init(sr); for_each_elem(sr, 0, rel, ElfW_Rel) { ptr = s->data + rel->r_offset; sym_index = ELFW(R_SYM)(rel->r_info); sym = &((ElfW(Sym) *)symtab_section->data)[sym_index]; type = ELFW(R_TYPE)(rel->r_info); tgt = sym->st_value; #if SHT_RELX == SHT_RELA tgt += rel->r_addend; #endif addr = s->sh_addr + rel->r_offset; relocate(s1, rel, type, ptr, addr, tgt); } /* if the relocation is allocated, we change its symbol table */ if (sr->sh_flags & SHF_ALLOC) sr->link = s1->dynsym; } /* relocate relocation table in 'sr' */ static void relocate_rel(TCCState *s1, Section *sr) { Section *s; ElfW_Rel *rel; s = s1->sections[sr->sh_info]; for_each_elem(sr, 0, rel, ElfW_Rel) rel->r_offset += s->sh_addr; } /* count the number of dynamic relocations so that we can reserve their space */ static int prepare_dynamic_rel(TCCState *s1, Section *sr) { ElfW_Rel *rel; int sym_index, type, count; count = 0; for_each_elem(sr, 0, rel, ElfW_Rel) { sym_index = ELFW(R_SYM)(rel->r_info); type = ELFW(R_TYPE)(rel->r_info); switch(type) { #if defined(TCC_TARGET_I386) case R_386_32: #elif defined(TCC_TARGET_X86_64) case R_X86_64_32: case R_X86_64_32S: case R_X86_64_64: #endif count++; break; #if defined(TCC_TARGET_I386) case R_386_PC32: #elif defined(TCC_TARGET_X86_64) case R_X86_64_PC32: #endif if (get_sym_attr(s1, sym_index, 0)->dyn_index) count++; break; default: break; } } if (count) { /* allocate the section */ sr->sh_flags |= SHF_ALLOC; sr->sh_size = count * sizeof(ElfW_Rel); } return count; } static void build_got(TCCState *s1) { /* if no got, then create it */ s1->got = new_section(s1, ".got", SHT_PROGBITS, SHF_ALLOC | SHF_WRITE); s1->got->sh_entsize = 4; set_elf_sym(symtab_section, 0, 4, ELFW(ST_INFO)(STB_GLOBAL, STT_OBJECT), 0, s1->got->sh_num, "_GLOBAL_OFFSET_TABLE_"); /* keep space for _DYNAMIC pointer and two dummy got entries */ section_ptr_add(s1->got, 3 * PTR_SIZE); } /* Create a GOT and (for function call) a PLT entry corresponding to a symbol in s1->symtab. When creating the dynamic symbol table entry for the GOT relocation, use 'size' and 'info' for the corresponding symbol metadata. Returns the offset of the GOT or (if any) PLT entry. */ static struct sym_attr * put_got_entry(TCCState *s1, int dyn_reloc_type, unsigned long size, int info, int sym_index) { int need_plt_entry; const char *name; ElfW(Sym) *sym; struct sym_attr *attr; unsigned got_offset; char plt_name[100]; int len; need_plt_entry = (dyn_reloc_type == R_JMP_SLOT); attr = get_sym_attr(s1, sym_index, 1); /* In case a function is both called and its address taken 2 GOT entries are created, one for taking the address (GOT) and the other for the PLT entry (PLTGOT). */ if (need_plt_entry ? attr->plt_offset : attr->got_offset) return attr; /* create the GOT entry */ got_offset = s1->got->data_offset; section_ptr_add(s1->got, PTR_SIZE); /* Create the GOT relocation that will insert the address of the object or function of interest in the GOT entry. This is a static relocation for memory output (dlsym will give us the address of symbols) and dynamic relocation otherwise (executable and DLLs). The relocation should be done lazily for GOT entry with *_JUMP_SLOT relocation type (the one associated to a PLT entry) but is currently done at load time for an unknown reason. */ sym = &((ElfW(Sym) *) symtab_section->data)[sym_index]; name = (char *) symtab_section->link->data + sym->st_name; if (s1->dynsym) { if (ELFW(ST_BIND)(sym->st_info) == STB_LOCAL) { /* Hack alarm. We don't want to emit dynamic symbols and symbol based relocs for STB_LOCAL symbols, but rather want to resolve them directly. At this point the symbol values aren't final yet, so we must defer this. We will later have to create a RELATIVE reloc anyway, so we misuse the relocation slot to smuggle the symbol reference until fill_local_got_entries. Not that the sym_index is relative to symtab_section, not s1->dynsym! Nevertheless we use s1->dyn_sym so that if this is the first call that got->reloc is correctly created. Also note that RELATIVE relocs are not normally created for the .got, so the types serves as a marker for later (and is retained also for the final output, which is okay because then the got is just normal data). */ put_elf_reloc(s1->dynsym, s1->got, got_offset, R_RELATIVE, sym_index); } else { if (0 == attr->dyn_index) attr->dyn_index = set_elf_sym(s1->dynsym, sym->st_value, size, info, 0, sym->st_shndx, name); put_elf_reloc(s1->dynsym, s1->got, got_offset, dyn_reloc_type, attr->dyn_index); } } else { put_elf_reloc(symtab_section, s1->got, got_offset, dyn_reloc_type, sym_index); } if (need_plt_entry) { if (!s1->plt) { s1->plt = new_section(s1, ".plt", SHT_PROGBITS, SHF_ALLOC | SHF_EXECINSTR); s1->plt->sh_entsize = 4; } attr->plt_offset = create_plt_entry(s1, got_offset, attr); /* create a symbol 'sym@plt' for the PLT jump vector */ len = strlen(name); if (len > sizeof plt_name - 5) len = sizeof plt_name - 5; memcpy(plt_name, name, len); strcpy(plt_name + len, "@plt"); attr->plt_sym = put_elf_sym(s1->symtab, attr->plt_offset, sym->st_size, ELFW(ST_INFO)(STB_GLOBAL, STT_FUNC), 0, s1->plt->sh_num, plt_name); } else { attr->got_offset = got_offset; } return attr; } /* build GOT and PLT entries */ ST_FUNC void build_got_entries(TCCState *s1) { Section *s; ElfW_Rel *rel; ElfW(Sym) *sym; int i, type, gotplt_entry, reloc_type, sym_index; struct sym_attr *attr; for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; if (s->sh_type != SHT_RELX) continue; /* no need to handle got relocations */ if (s->link != symtab_section) continue; for_each_elem(s, 0, rel, ElfW_Rel) { type = ELFW(R_TYPE)(rel->r_info); gotplt_entry = gotplt_entry_type(type); sym_index = ELFW(R_SYM)(rel->r_info); sym = &((ElfW(Sym) *)symtab_section->data)[sym_index]; if (gotplt_entry == NO_GOTPLT_ENTRY) { continue; } /* Automatically create PLT/GOT [entry] if it is an undefined reference (resolved at runtime), or the symbol is absolute, probably created by tcc_add_symbol, and thus on 64-bit targets might be too far from application code. */ if (gotplt_entry == AUTO_GOTPLT_ENTRY) { if (sym->st_shndx == SHN_UNDEF) { ElfW(Sym) *esym; int dynindex; if (s1->output_type == TCC_OUTPUT_DLL && ! PCRELATIVE_DLLPLT) continue; /* Relocations for UNDEF symbols would normally need to be transferred into the executable or shared object. If that were done AUTO_GOTPLT_ENTRY wouldn't exist. But TCC doesn't do that (at least for exes), so we need to resolve all such relocs locally. And that means PLT slots for functions in DLLs and COPY relocs for data symbols. COPY relocs were generated in bind_exe_dynsyms (and the symbol adjusted to be defined), and for functions we were generated a dynamic symbol of function type. */ if (s1->dynsym) { /* dynsym isn't set for -run :-/ */ dynindex = get_sym_attr(s1, sym_index, 0)->dyn_index; esym = (ElfW(Sym) *)s1->dynsym->data + dynindex; if (dynindex && (ELFW(ST_TYPE)(esym->st_info) == STT_FUNC || (ELFW(ST_TYPE)(esym->st_info) == STT_NOTYPE && ELFW(ST_TYPE)(sym->st_info) == STT_FUNC))) goto jmp_slot; } } else if (!(sym->st_shndx == SHN_ABS #ifndef TCC_TARGET_ARM && PTR_SIZE == 8 #endif )) continue; } #ifdef TCC_TARGET_X86_64 if ((type == R_X86_64_PLT32 || type == R_X86_64_PC32) && (ELFW(ST_VISIBILITY)(sym->st_other) != STV_DEFAULT || ELFW(ST_BIND)(sym->st_info) == STB_LOCAL)) { rel->r_info = ELFW(R_INFO)(sym_index, R_X86_64_PC32); continue; } #endif if (code_reloc(type)) { jmp_slot: reloc_type = R_JMP_SLOT; } else reloc_type = R_GLOB_DAT; if (!s1->got) build_got(s1); if (gotplt_entry == BUILD_GOT_ONLY) continue; attr = put_got_entry(s1, reloc_type, sym->st_size, sym->st_info, sym_index); if (reloc_type == R_JMP_SLOT) rel->r_info = ELFW(R_INFO)(attr->plt_sym, type); } } } /* put dynamic tag */ static void put_dt(Section *dynamic, int dt, addr_t val) { ElfW(Dyn) *dyn; dyn = section_ptr_add(dynamic, sizeof(ElfW(Dyn))); dyn->d_tag = dt; dyn->d_un.d_val = val; } #ifndef TCC_TARGET_PE static void add_init_array_defines(TCCState *s1, const char *section_name) { Section *s; long end_offset; char sym_start[1024]; char sym_end[1024]; snprintf(sym_start, sizeof(sym_start), "__%s_start", section_name + 1); snprintf(sym_end, sizeof(sym_end), "__%s_end", section_name + 1); s = find_section(s1, section_name); if (!s) { end_offset = 0; s = data_section; } else { end_offset = s->data_offset; } set_elf_sym(symtab_section, 0, 0, ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, s->sh_num, sym_start); set_elf_sym(symtab_section, end_offset, 0, ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, s->sh_num, sym_end); } #endif static int tcc_add_support(TCCState *s1, const char *filename) { char buf[1024]; snprintf(buf, sizeof(buf), "%s/%s", s1->tcc_lib_path, filename); return tcc_add_file(s1, buf); } ST_FUNC void tcc_add_bcheck(TCCState *s1) { #ifdef CONFIG_TCC_BCHECK addr_t *ptr; int sym_index; if (0 == s1->do_bounds_check) return; /* XXX: add an object file to do that */ ptr = section_ptr_add(bounds_section, sizeof(*ptr)); *ptr = 0; set_elf_sym(symtab_section, 0, 0, ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, bounds_section->sh_num, "__bounds_start"); /* pull bcheck.o from libtcc1.a */ sym_index = set_elf_sym(symtab_section, 0, 0, ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, SHN_UNDEF, "__bound_init"); if (s1->output_type != TCC_OUTPUT_MEMORY) { /* add 'call __bound_init()' in .init section */ Section *init_section = find_section(s1, ".init"); unsigned char *pinit = section_ptr_add(init_section, 5); pinit[0] = 0xe8; write32le(pinit + 1, -4); put_elf_reloc(symtab_section, init_section, init_section->data_offset - 4, R_386_PC32, sym_index); /* R_386_PC32 = R_X86_64_PC32 = 2 */ } #endif } /* add tcc runtime libraries */ ST_FUNC void tcc_add_runtime(TCCState *s1) { tcc_add_bcheck(s1); tcc_add_pragma_libs(s1); /* add libc */ if (!s1->nostdlib) { tcc_add_library_err(s1, "c"); #ifdef TCC_LIBGCC if (!s1->static_link) { if (TCC_LIBGCC[0] == '/') tcc_add_file(s1, TCC_LIBGCC); else tcc_add_dll(s1, TCC_LIBGCC, 0); } #endif tcc_add_support(s1, TCC_LIBTCC1); #if CONFIG_TCC_LIBTCC1_MES tcc_add_support(s1, TCC_LIBTCC1_MES); #endif /* add crt end if not memory output */ if (s1->output_type != TCC_OUTPUT_MEMORY) tcc_add_crt(s1, "crtn.o"); } } /* add various standard linker symbols (must be done after the sections are filled (for example after allocating common symbols)) */ ST_FUNC void tcc_add_linker_symbols(TCCState *s1) { char buf[1024]; int i; Section *s; set_elf_sym(symtab_section, text_section->data_offset, 0, ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, text_section->sh_num, "_etext"); set_elf_sym(symtab_section, data_section->data_offset, 0, ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, data_section->sh_num, "_edata"); set_elf_sym(symtab_section, bss_section->data_offset, 0, ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, bss_section->sh_num, "_end"); #ifndef TCC_TARGET_PE /* horrible new standard ldscript defines */ add_init_array_defines(s1, ".preinit_array"); add_init_array_defines(s1, ".init_array"); add_init_array_defines(s1, ".fini_array"); #endif /* add start and stop symbols for sections whose name can be expressed in C */ for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; if (s->sh_type == SHT_PROGBITS && (s->sh_flags & SHF_ALLOC)) { const char *p; int ch; /* check if section name can be expressed in C */ p = s->name; for(;;) { ch = *p; if (!ch) break; if (!isid(ch) && !isnum(ch)) goto next_sec; p++; } snprintf(buf, sizeof(buf), "__start_%s", s->name); set_elf_sym(symtab_section, 0, 0, ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, s->sh_num, buf); snprintf(buf, sizeof(buf), "__stop_%s", s->name); set_elf_sym(symtab_section, s->data_offset, 0, ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, s->sh_num, buf); } next_sec: ; } } static void tcc_output_binary(TCCState *s1, FILE *f, const int *sec_order) { Section *s; int i, offset, size; offset = 0; for(i=1;i<s1->nb_sections;i++) { s = s1->sections[sec_order[i]]; if (s->sh_type != SHT_NOBITS && (s->sh_flags & SHF_ALLOC)) { while (offset < s->sh_offset) { fputc(0, f); offset++; } size = s->sh_size; fwrite(s->data, 1, size, f); offset += size; } } } #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) #define HAVE_PHDR 1 #define EXTRA_RELITEMS 14 #else #define HAVE_PHDR 1 #define EXTRA_RELITEMS 9 #endif ST_FUNC void fill_got_entry(TCCState *s1, ElfW_Rel *rel) { int sym_index = ELFW(R_SYM) (rel->r_info); ElfW(Sym) *sym = &((ElfW(Sym) *) symtab_section->data)[sym_index]; struct sym_attr *attr = get_sym_attr(s1, sym_index, 0); unsigned offset = attr->got_offset; if (0 == offset) return; section_reserve(s1->got, offset + PTR_SIZE); #ifdef TCC_TARGET_X86_64 write64le(s1->got->data + offset, sym->st_value); #else write32le(s1->got->data + offset, sym->st_value); #endif } /* Perform relocation to GOT or PLT entries */ ST_FUNC void fill_got(TCCState *s1) { Section *s; ElfW_Rel *rel; int i; for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; if (s->sh_type != SHT_RELX) continue; /* no need to handle got relocations */ if (s->link != symtab_section) continue; for_each_elem(s, 0, rel, ElfW_Rel) { switch (ELFW(R_TYPE) (rel->r_info)) { case R_X86_64_GOT32: case R_X86_64_GOTPCREL: case R_X86_64_GOTPCRELX: case R_X86_64_REX_GOTPCRELX: case R_X86_64_PLT32: fill_got_entry(s1, rel); break; } } } } /* See put_got_entry for a description. This is the second stage where GOT references to local defined symbols are rewritten. */ static void fill_local_got_entries(TCCState *s1) { ElfW_Rel *rel; for_each_elem(s1->got->reloc, 0, rel, ElfW_Rel) { if (ELFW(R_TYPE)(rel->r_info) == R_RELATIVE) { int sym_index = ELFW(R_SYM) (rel->r_info); ElfW(Sym) *sym = &((ElfW(Sym) *) symtab_section->data)[sym_index]; struct sym_attr *attr = get_sym_attr(s1, sym_index, 0); unsigned offset = attr->got_offset; if (offset != rel->r_offset - s1->got->sh_addr) tcc_error_noabort("huh"); rel->r_info = ELFW(R_INFO)(0, R_RELATIVE); #if SHT_RELX == SHT_RELA rel->r_addend = sym->st_value; #else /* All our REL architectures also happen to be 32bit LE. */ write32le(s1->got->data + offset, sym->st_value); #endif } } } /* Bind symbols of executable: resolve undefined symbols from exported symbols in shared libraries and export non local defined symbols to shared libraries if -rdynamic switch was given on command line */ static void bind_exe_dynsyms(TCCState *s1) { const char *name; int sym_index, index; ElfW(Sym) *sym, *esym; int type; /* Resolve undefined symbols from dynamic symbols. When there is a match: - if STT_FUNC or STT_GNU_IFUNC symbol -> add it in PLT - if STT_OBJECT symbol -> add it in .bss section with suitable reloc */ for_each_elem(symtab_section, 1, sym, ElfW(Sym)) { if (sym->st_shndx == SHN_UNDEF) { name = (char *) symtab_section->link->data + sym->st_name; sym_index = find_elf_sym(s1->dynsymtab_section, name); if (sym_index) { esym = &((ElfW(Sym) *)s1->dynsymtab_section->data)[sym_index]; type = ELFW(ST_TYPE)(esym->st_info); if ((type == STT_FUNC) || (type == STT_GNU_IFUNC)) { /* Indirect functions shall have STT_FUNC type in executable * dynsym section. Indeed, a dlsym call following a lazy * resolution would pick the symbol value from the * executable dynsym entry which would contain the address * of the function wanted by the caller of dlsym instead of * the address of the function that would return that * address */ int dynindex = put_elf_sym(s1->dynsym, 0, esym->st_size, ELFW(ST_INFO)(STB_GLOBAL,STT_FUNC), 0, 0, name); int index = sym - (ElfW(Sym) *) symtab_section->data; get_sym_attr(s1, index, 1)->dyn_index = dynindex; } else if (type == STT_OBJECT) { unsigned long offset; ElfW(Sym) *dynsym; offset = bss_section->data_offset; /* XXX: which alignment ? */ offset = (offset + 16 - 1) & -16; set_elf_sym (s1->symtab, offset, esym->st_size, esym->st_info, 0, bss_section->sh_num, name); index = put_elf_sym(s1->dynsym, offset, esym->st_size, esym->st_info, 0, bss_section->sh_num, name); /* Ensure R_COPY works for weak symbol aliases */ if (ELFW(ST_BIND)(esym->st_info) == STB_WEAK) { for_each_elem(s1->dynsymtab_section, 1, dynsym, ElfW(Sym)) { if ((dynsym->st_value == esym->st_value) && (ELFW(ST_BIND)(dynsym->st_info) == STB_GLOBAL)) { char *dynname = (char *) s1->dynsymtab_section->link->data + dynsym->st_name; put_elf_sym(s1->dynsym, offset, dynsym->st_size, dynsym->st_info, 0, bss_section->sh_num, dynname); break; } } } put_elf_reloc(s1->dynsym, bss_section, offset, R_COPY, index); offset += esym->st_size; bss_section->data_offset = offset; } } else { /* STB_WEAK undefined symbols are accepted */ /* XXX: _fp_hw seems to be part of the ABI, so we ignore it */ if (ELFW(ST_BIND)(sym->st_info) == STB_WEAK || !strcmp(name, "_fp_hw")) { } else { tcc_error_noabort("undefined symbol '%s'", name); } } } else if (s1->rdynamic && ELFW(ST_BIND)(sym->st_info) != STB_LOCAL) { /* if -rdynamic option, then export all non local symbols */ name = (char *) symtab_section->link->data + sym->st_name; set_elf_sym(s1->dynsym, sym->st_value, sym->st_size, sym->st_info, 0, sym->st_shndx, name); } } } /* Bind symbols of libraries: export all non local symbols of executable that are referenced by shared libraries. The reason is that the dynamic loader search symbol first in executable and then in libraries. Therefore a reference to a symbol already defined by a library can still be resolved by a symbol in the executable. */ static void bind_libs_dynsyms(TCCState *s1) { const char *name; int sym_index; ElfW(Sym) *sym, *esym; for_each_elem(s1->dynsymtab_section, 1, esym, ElfW(Sym)) { name = (char *) s1->dynsymtab_section->link->data + esym->st_name; sym_index = find_elf_sym(symtab_section, name); /* XXX: avoid adding a symbol if already present because of -rdynamic ? */ sym = &((ElfW(Sym) *)symtab_section->data)[sym_index]; if (sym_index && sym->st_shndx != SHN_UNDEF) set_elf_sym(s1->dynsym, sym->st_value, sym->st_size, sym->st_info, 0, sym->st_shndx, name); else if (esym->st_shndx == SHN_UNDEF) { /* weak symbols can stay undefined */ if (ELFW(ST_BIND)(esym->st_info) != STB_WEAK) tcc_warning("undefined dynamic symbol '%s'", name); } } } /* Export all non local symbols. This is used by shared libraries so that the non local symbols they define can resolve a reference in another shared library or in the executable. Correspondingly, it allows undefined local symbols to be resolved by other shared libraries or by the executable. */ static void export_global_syms(TCCState *s1) { int dynindex, index; const char *name; ElfW(Sym) *sym; for_each_elem(symtab_section, 1, sym, ElfW(Sym)) { if (ELFW(ST_BIND)(sym->st_info) != STB_LOCAL) { name = (char *) symtab_section->link->data + sym->st_name; dynindex = put_elf_sym(s1->dynsym, sym->st_value, sym->st_size, sym->st_info, 0, sym->st_shndx, name); index = sym - (ElfW(Sym) *) symtab_section->data; get_sym_attr(s1, index, 1)->dyn_index = dynindex; } } } /* Allocate strings for section names and decide if an unallocated section should be output. NOTE: the strsec section comes last, so its size is also correct ! */ static void alloc_sec_names(TCCState *s1, int file_type, Section *strsec) { int i; Section *s; /* Allocate strings for section names */ for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; /* when generating a DLL, we include relocations but we may patch them */ if (file_type == TCC_OUTPUT_DLL && s->sh_type == SHT_RELX && !(s->sh_flags & SHF_ALLOC)) { /* gr: avoid bogus relocs for empty (debug) sections */ if (s1->sections[s->sh_info]->sh_flags & SHF_ALLOC) prepare_dynamic_rel(s1, s); else if (s1->do_debug) s->sh_size = s->data_offset; } else if (s1->do_debug || file_type == TCC_OUTPUT_OBJ || (s->sh_flags & SHF_ALLOC) || i == (s1->nb_sections - 1)) { /* we output all sections if debug or object file */ s->sh_size = s->data_offset; } if (s->sh_size || (s->sh_flags & SHF_ALLOC)) s->sh_name = put_elf_str(strsec, s->name); } strsec->sh_size = strsec->data_offset; } /* Info to be copied in dynamic section */ struct dyn_inf { Section *dynamic; Section *dynstr; unsigned long dyn_rel_off; addr_t rel_addr; addr_t rel_size; #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) addr_t bss_addr; addr_t bss_size; #endif }; /* Assign sections to segments and decide how are sections laid out when loaded in memory. This function also fills corresponding program headers. */ static int layout_sections(TCCState *s1, ElfW(Phdr) *phdr, int phnum, Section *interp, Section* strsec, struct dyn_inf *dyninf, int *sec_order) { int i, j, k, file_type, sh_order_index, file_offset; unsigned long s_align; long long tmp; addr_t addr; ElfW(Phdr) *ph; Section *s; file_type = s1->output_type; sh_order_index = 1; file_offset = 0; if (s1->output_format == TCC_OUTPUT_FORMAT_ELF) file_offset = sizeof(ElfW(Ehdr)) + phnum * sizeof(ElfW(Phdr)); s_align = ELF_PAGE_SIZE; if (s1->section_align) s_align = s1->section_align; if (phnum > 0) { if (s1->has_text_addr) { int a_offset, p_offset; addr = s1->text_addr; /* we ensure that (addr % ELF_PAGE_SIZE) == file_offset % ELF_PAGE_SIZE */ a_offset = (int) (addr & (s_align - 1)); p_offset = file_offset & (s_align - 1); if (a_offset < p_offset) a_offset += s_align; file_offset += (a_offset - p_offset); } else { if (file_type == TCC_OUTPUT_DLL) addr = 0; else addr = ELF_START_ADDR; /* compute address after headers */ addr += (file_offset & (s_align - 1)); } ph = &phdr[0]; /* Leave one program headers for the program interpreter and one for the program header table itself if needed. These are done later as they require section layout to be done first. */ if (interp) ph += 1 + HAVE_PHDR; /* dynamic relocation table information, for .dynamic section */ dyninf->rel_addr = dyninf->rel_size = 0; #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) dyninf->bss_addr = dyninf->bss_size = 0; #endif for(j = 0; j < 2; j++) { ph->p_type = PT_LOAD; if (j == 0) ph->p_flags = PF_R | PF_X; else ph->p_flags = PF_R | PF_W; ph->p_align = s_align; /* Decide the layout of sections loaded in memory. This must be done before program headers are filled since they contain info about the layout. We do the following ordering: interp, symbol tables, relocations, progbits, nobits */ /* XXX: do faster and simpler sorting */ for(k = 0; k < 5; k++) { for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; /* compute if section should be included */ if (j == 0) { if ((s->sh_flags & (SHF_ALLOC | SHF_WRITE)) != SHF_ALLOC) continue; } else { if ((s->sh_flags & (SHF_ALLOC | SHF_WRITE)) != (SHF_ALLOC | SHF_WRITE)) continue; } if (s == interp) { if (k != 0) continue; } else if (s->sh_type == SHT_DYNSYM || s->sh_type == SHT_STRTAB || s->sh_type == SHT_HASH) { if (k != 1) continue; } else if (s->sh_type == SHT_RELX) { if (k != 2) continue; } else if (s->sh_type == SHT_NOBITS) { if (k != 4) continue; } else { if (k != 3) continue; } sec_order[sh_order_index++] = i; /* section matches: we align it and add its size */ tmp = addr; addr = (addr + s->sh_addralign - 1) & ~(s->sh_addralign - 1); file_offset += (int) ( addr - tmp ); s->sh_offset = file_offset; s->sh_addr = addr; /* update program header infos */ if (ph->p_offset == 0) { ph->p_offset = file_offset; ph->p_vaddr = addr; ph->p_paddr = ph->p_vaddr; } /* update dynamic relocation infos */ if (s->sh_type == SHT_RELX) { #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) if (!strcmp(strsec->data + s->sh_name, ".rel.got")) { dyninf->rel_addr = addr; dyninf->rel_size += s->sh_size; /* XXX only first rel. */ } if (!strcmp(strsec->data + s->sh_name, ".rel.bss")) { dyninf->bss_addr = addr; dyninf->bss_size = s->sh_size; /* XXX only first rel. */ } #else if (dyninf->rel_size == 0) dyninf->rel_addr = addr; dyninf->rel_size += s->sh_size; #endif } addr += s->sh_size; if (s->sh_type != SHT_NOBITS) file_offset += s->sh_size; } } if (j == 0) { /* Make the first PT_LOAD segment include the program headers itself (and the ELF header as well), it'll come out with same memory use but will make various tools like binutils strip work better. */ ph->p_offset &= ~(ph->p_align - 1); ph->p_vaddr &= ~(ph->p_align - 1); ph->p_paddr &= ~(ph->p_align - 1); } ph->p_filesz = file_offset - ph->p_offset; ph->p_memsz = addr - ph->p_vaddr; ph++; if (j == 0) { if (s1->output_format == TCC_OUTPUT_FORMAT_ELF) { /* if in the middle of a page, we duplicate the page in memory so that one copy is RX and the other is RW */ if ((addr & (s_align - 1)) != 0) addr += s_align; } else { addr = (addr + s_align - 1) & ~(s_align - 1); file_offset = (file_offset + s_align - 1) & ~(s_align - 1); } } } } /* all other sections come after */ for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; if (phnum > 0 && (s->sh_flags & SHF_ALLOC)) continue; sec_order[sh_order_index++] = i; file_offset = (file_offset + s->sh_addralign - 1) & ~(s->sh_addralign - 1); s->sh_offset = file_offset; if (s->sh_type != SHT_NOBITS) file_offset += s->sh_size; } return file_offset; } static void fill_unloadable_phdr(ElfW(Phdr) *phdr, int phnum, Section *interp, Section *dynamic) { ElfW(Phdr) *ph; /* if interpreter, then add corresponding program header */ if (interp) { ph = &phdr[0]; if (HAVE_PHDR) { int len = phnum * sizeof(ElfW(Phdr)); ph->p_type = PT_PHDR; ph->p_offset = sizeof(ElfW(Ehdr)); ph->p_vaddr = interp->sh_addr - len; ph->p_paddr = ph->p_vaddr; ph->p_filesz = ph->p_memsz = len; ph->p_flags = PF_R | PF_X; ph->p_align = 4; /* interp->sh_addralign; */ ph++; } ph->p_type = PT_INTERP; ph->p_offset = interp->sh_offset; ph->p_vaddr = interp->sh_addr; ph->p_paddr = ph->p_vaddr; ph->p_filesz = interp->sh_size; ph->p_memsz = interp->sh_size; ph->p_flags = PF_R; ph->p_align = interp->sh_addralign; } /* if dynamic section, then add corresponding program header */ if (dynamic) { ph = &phdr[phnum - 1]; ph->p_type = PT_DYNAMIC; ph->p_offset = dynamic->sh_offset; ph->p_vaddr = dynamic->sh_addr; ph->p_paddr = ph->p_vaddr; ph->p_filesz = dynamic->sh_size; ph->p_memsz = dynamic->sh_size; ph->p_flags = PF_R | PF_W; ph->p_align = dynamic->sh_addralign; } } /* Fill the dynamic section with tags describing the address and size of sections */ static void fill_dynamic(TCCState *s1, struct dyn_inf *dyninf) { Section *dynamic; dynamic = dyninf->dynamic; /* put dynamic section entries */ dynamic->data_offset = dyninf->dyn_rel_off; put_dt(dynamic, DT_HASH, s1->dynsym->hash->sh_addr); put_dt(dynamic, DT_STRTAB, dyninf->dynstr->sh_addr); put_dt(dynamic, DT_SYMTAB, s1->dynsym->sh_addr); put_dt(dynamic, DT_STRSZ, dyninf->dynstr->data_offset); put_dt(dynamic, DT_SYMENT, sizeof(ElfW(Sym))); #if PTR_SIZE == 8 put_dt(dynamic, DT_RELA, dyninf->rel_addr); put_dt(dynamic, DT_RELASZ, dyninf->rel_size); put_dt(dynamic, DT_RELAENT, sizeof(ElfW_Rel)); #else #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) put_dt(dynamic, DT_PLTGOT, s1->got->sh_addr); put_dt(dynamic, DT_PLTRELSZ, dyninf->rel_size); put_dt(dynamic, DT_JMPREL, dyninf->rel_addr); put_dt(dynamic, DT_PLTREL, DT_REL); put_dt(dynamic, DT_REL, dyninf->bss_addr); put_dt(dynamic, DT_RELSZ, dyninf->bss_size); #else put_dt(dynamic, DT_REL, dyninf->rel_addr); put_dt(dynamic, DT_RELSZ, dyninf->rel_size); put_dt(dynamic, DT_RELENT, sizeof(ElfW_Rel)); #endif #endif if (s1->do_debug) put_dt(dynamic, DT_DEBUG, 0); put_dt(dynamic, DT_NULL, 0); } /* Relocate remaining sections and symbols (that is those not related to dynamic linking) */ static int final_sections_reloc(TCCState *s1) { int i; Section *s; relocate_syms(s1, s1->symtab, 0); if (s1->nb_errors != 0) return -1; /* relocate sections */ /* XXX: ignore sections with allocated relocations ? */ for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; #if defined(TCC_TARGET_I386) || defined(TCC_MUSL) if (s->reloc && s != s1->got && (s->sh_flags & SHF_ALLOC)) //gr /* On X86 gdb 7.3 works in any case but gdb 6.6 will crash if SHF_ALLOC checking is removed */ #else if (s->reloc && s != s1->got) /* On X86_64 gdb 7.3 will crash if SHF_ALLOC checking is present */ #endif relocate_section(s1, s); } /* relocate relocation entries if the relocation tables are allocated in the executable */ for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; if ((s->sh_flags & SHF_ALLOC) && s->sh_type == SHT_RELX) { relocate_rel(s1, s); } } return 0; } /* Create an ELF file on disk. This function handle ELF specific layout requirements */ static void tcc_output_elf(TCCState *s1, FILE *f, int phnum, ElfW(Phdr) *phdr, int file_offset, int *sec_order) { int i, shnum, offset, size, file_type; Section *s; ElfW(Ehdr) ehdr; ElfW(Shdr) shdr, *sh; file_type = s1->output_type; shnum = s1->nb_sections; memset(&ehdr, 0, sizeof(ehdr)); if (phnum > 0) { ehdr.e_phentsize = sizeof(ElfW(Phdr)); ehdr.e_phnum = phnum; ehdr.e_phoff = sizeof(ElfW(Ehdr)); } /* align to 4 */ file_offset = (file_offset + 3) & -4; /* fill header */ ehdr.e_ident[0] = ELFMAG0; ehdr.e_ident[1] = ELFMAG1; ehdr.e_ident[2] = ELFMAG2; ehdr.e_ident[3] = ELFMAG3; ehdr.e_ident[4] = ELFCLASSW; ehdr.e_ident[5] = ELFDATA2LSB; ehdr.e_ident[6] = EV_CURRENT; #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) ehdr.e_ident[EI_OSABI] = ELFOSABI_FREEBSD; #endif #ifdef TCC_TARGET_ARM #if defined (TCC_ARM_EABI) || BOOTSTRAP ehdr.e_ident[EI_OSABI] = 0; ehdr.e_flags = EF_ARM_EABI_VER4; if (file_type == TCC_OUTPUT_EXE || file_type == TCC_OUTPUT_DLL) ehdr.e_flags |= EF_ARM_HASENTRY; if (s1->float_abi == ARM_HARD_FLOAT) ehdr.e_flags |= EF_ARM_VFP_FLOAT; else ehdr.e_flags |= EF_ARM_SOFT_FLOAT; #else ehdr.e_ident[EI_OSABI] = ELFOSABI_ARM; #endif #endif switch(file_type) { default: case TCC_OUTPUT_EXE: ehdr.e_type = ET_EXEC; ehdr.e_entry = get_elf_sym_addr(s1, "_start", 1); break; case TCC_OUTPUT_DLL: ehdr.e_type = ET_DYN; ehdr.e_entry = text_section->sh_addr; /* XXX: is it correct ? */ break; case TCC_OUTPUT_OBJ: ehdr.e_type = ET_REL; break; } ehdr.e_machine = EM_TCC_TARGET; ehdr.e_version = EV_CURRENT; ehdr.e_shoff = file_offset; ehdr.e_ehsize = sizeof(ElfW(Ehdr)); ehdr.e_shentsize = sizeof(ElfW(Shdr)); ehdr.e_shnum = shnum; ehdr.e_shstrndx = shnum - 1; fwrite(&ehdr, 1, sizeof(ElfW(Ehdr)), f); fwrite(phdr, 1, phnum * sizeof(ElfW(Phdr)), f); offset = sizeof(ElfW(Ehdr)) + phnum * sizeof(ElfW(Phdr)); sort_syms(s1, symtab_section); for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[sec_order[i]]; if (s->sh_type != SHT_NOBITS) { while (offset < s->sh_offset) { fputc(0, f); offset++; } size = s->sh_size; if (size) fwrite(s->data, 1, size, f); offset += size; } } /* output section headers */ while (offset < ehdr.e_shoff) { fputc(0, f); offset++; } for(i = 0; i < s1->nb_sections; i++) { sh = &shdr; memset(sh, 0, sizeof(ElfW(Shdr))); s = s1->sections[i]; if (s) { sh->sh_name = s->sh_name; sh->sh_type = s->sh_type; sh->sh_flags = s->sh_flags; sh->sh_entsize = s->sh_entsize; sh->sh_info = s->sh_info; if (s->link) sh->sh_link = s->link->sh_num; sh->sh_addralign = s->sh_addralign; sh->sh_addr = s->sh_addr; sh->sh_offset = s->sh_offset; sh->sh_size = s->sh_size; } fwrite(sh, 1, sizeof(ElfW(Shdr)), f); } } /* Write an elf, coff or "binary" file */ static int tcc_write_elf_file(TCCState *s1, const char *filename, int phnum, ElfW(Phdr) *phdr, int file_offset, int *sec_order) { int fd, mode, file_type; FILE *f; file_type = s1->output_type; if (file_type == TCC_OUTPUT_OBJ) mode = 0666; else mode = 0777; unlink(filename); fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, mode); if (fd < 0) { tcc_error_noabort("could not write '%s'", filename); return -1; } f = fdopen(fd, "wb"); if (s1->verbose) printf("<- %s\n", filename); #ifdef TCC_TARGET_COFF if (s1->output_format == TCC_OUTPUT_FORMAT_COFF) tcc_output_coff(s1, f); else #endif if (s1->output_format == TCC_OUTPUT_FORMAT_ELF) tcc_output_elf(s1, f, phnum, phdr, file_offset, sec_order); else tcc_output_binary(s1, f, sec_order); fclose(f); return 0; } /* Sort section headers by assigned sh_addr, remove sections that we aren't going to output. */ static void tidy_section_headers(TCCState *s1, int *sec_order) { int i, nnew, l, *backmap; Section **snew, *s; ElfW(Sym) *sym; snew = tcc_malloc(s1->nb_sections * sizeof(snew[0])); backmap = tcc_malloc(s1->nb_sections * sizeof(backmap[0])); for (i = 0, nnew = 0, l = s1->nb_sections; i < s1->nb_sections; i++) { s = s1->sections[sec_order[i]]; if (!i || s->sh_name) { backmap[sec_order[i]] = nnew; snew[nnew] = s; ++nnew; } else { backmap[sec_order[i]] = 0; snew[--l] = s; } } for (i = 0; i < nnew; i++) { s = snew[i]; if (s) { s->sh_num = i; if (s->sh_type == SHT_RELX) s->sh_info = backmap[s->sh_info]; } } for_each_elem(symtab_section, 1, sym, ElfW(Sym)) if (sym->st_shndx != SHN_UNDEF && sym->st_shndx < SHN_LORESERVE) sym->st_shndx = backmap[sym->st_shndx]; for_each_elem(s1->dynsym, 1, sym, ElfW(Sym)) if (sym->st_shndx != SHN_UNDEF && sym->st_shndx < SHN_LORESERVE) sym->st_shndx = backmap[sym->st_shndx]; for (i = 0; i < s1->nb_sections; i++) sec_order[i] = i; tcc_free(s1->sections); s1->sections = snew; s1->nb_sections = nnew; tcc_free(backmap); } /* Output an elf, coff or binary file */ /* XXX: suppress unneeded sections */ static int elf_output_file(TCCState *s1, const char *filename) { int i, ret, phnum, shnum, file_type, file_offset, *sec_order; struct dyn_inf dyninf = {0}; ElfW(Phdr) *phdr; ElfW(Sym) *sym; Section *strsec, *interp, *dynamic, *dynstr; file_type = s1->output_type; s1->nb_errors = 0; /* if linking, also link in runtime libraries (libc, libgcc, etc.) */ if (file_type != TCC_OUTPUT_OBJ) { tcc_add_runtime(s1); } phdr = NULL; sec_order = NULL; interp = dynamic = dynstr = NULL; /* avoid warning */ if (file_type != TCC_OUTPUT_OBJ) { relocate_common_syms(); tcc_add_linker_symbols(s1); if (!s1->static_link) { if (file_type == TCC_OUTPUT_EXE) { char *ptr; /* allow override the dynamic loader */ const char *elfint = getenv("LD_SO"); if (elfint == NULL) elfint = DEFAULT_ELFINTERP(s1); /* add interpreter section only if executable */ interp = new_section(s1, ".interp", SHT_PROGBITS, SHF_ALLOC); interp->sh_addralign = 1; ptr = section_ptr_add(interp, 1 + strlen(elfint)); strcpy(ptr, elfint); } /* add dynamic symbol table */ s1->dynsym = new_symtab(s1, ".dynsym", SHT_DYNSYM, SHF_ALLOC, ".dynstr", ".hash", SHF_ALLOC); dynstr = s1->dynsym->link; /* add dynamic section */ dynamic = new_section(s1, ".dynamic", SHT_DYNAMIC, SHF_ALLOC | SHF_WRITE); dynamic->link = dynstr; dynamic->sh_entsize = sizeof(ElfW(Dyn)); build_got(s1); if (file_type == TCC_OUTPUT_EXE) { bind_exe_dynsyms(s1); if (s1->nb_errors) { ret = -1; goto the_end; } bind_libs_dynsyms(s1); } else /* shared library case: simply export all global symbols */ export_global_syms(s1); build_got_entries(s1); /* add a list of needed dlls */ for(i = 0; i < s1->nb_loaded_dlls; i++) { DLLReference *dllref = s1->loaded_dlls[i]; if (dllref->level == 0) put_dt(dynamic, DT_NEEDED, put_elf_str(dynstr, dllref->name)); } if (s1->rpath) put_dt(dynamic, s1->enable_new_dtags ? DT_RUNPATH : DT_RPATH, put_elf_str(dynstr, s1->rpath)); /* XXX: currently, since we do not handle PIC code, we must relocate the readonly segments */ if (file_type == TCC_OUTPUT_DLL) { if (s1->soname) put_dt(dynamic, DT_SONAME, put_elf_str(dynstr, s1->soname)); put_dt(dynamic, DT_TEXTREL, 0); } if (s1->symbolic) put_dt(dynamic, DT_SYMBOLIC, 0); /* add necessary space for other entries */ dyninf.dyn_rel_off = dynamic->data_offset; dynamic->data_offset += sizeof(ElfW(Dyn)) * EXTRA_RELITEMS; } else { /* still need to build got entries in case of static link */ build_got_entries(s1); } } /* we add a section for symbols */ strsec = new_section(s1, ".shstrtab", SHT_STRTAB, 0); put_elf_str(strsec, ""); /* compute number of sections */ shnum = s1->nb_sections; /* this array is used to reorder sections in the output file */ sec_order = tcc_malloc(sizeof(int) * shnum); sec_order[0] = 0; /* compute number of program headers */ switch(file_type) { default: case TCC_OUTPUT_OBJ: phnum = 0; break; case TCC_OUTPUT_EXE: if (!s1->static_link) phnum = 4 + HAVE_PHDR; else phnum = 2; break; case TCC_OUTPUT_DLL: phnum = 3; break; } /* Allocate strings for section names */ alloc_sec_names(s1, file_type, strsec); /* allocate program segment headers */ phdr = tcc_mallocz(phnum * sizeof(ElfW(Phdr))); /* compute section to program header mapping */ file_offset = layout_sections(s1, phdr, phnum, interp, strsec, &dyninf, sec_order); /* Fill remaining program header and finalize relocation related to dynamic linking. */ if (phnum > 0) { fill_unloadable_phdr(phdr, phnum, interp, dynamic); if (dynamic) { dyninf.dynamic = dynamic; dyninf.dynstr = dynstr; fill_dynamic(s1, &dyninf); /* put in GOT the dynamic section address and relocate PLT */ write32le(s1->got->data, dynamic->sh_addr); if (file_type == TCC_OUTPUT_EXE || (RELOCATE_DLLPLT && file_type == TCC_OUTPUT_DLL)) relocate_plt(s1); /* relocate symbols in .dynsym now that final addresses are known */ for_each_elem(s1->dynsym, 1, sym, ElfW(Sym)) { if (sym->st_shndx != SHN_UNDEF && sym->st_shndx < SHN_LORESERVE) { /* do symbol relocation */ sym->st_value += s1->sections[sym->st_shndx]->sh_addr; } } } } /* if building executable or DLL, then relocate each section except the GOT which is already relocated */ if (file_type != TCC_OUTPUT_OBJ) { ret = final_sections_reloc(s1); if (ret) goto the_end; #if !BOOTSTRAP tidy_section_headers(s1, sec_order); #endif } /* Perform relocation to GOT or PLT entries */ if (file_type == TCC_OUTPUT_EXE && s1->static_link) fill_got(s1); else if (s1->got) fill_local_got_entries(s1); /* Create the ELF file with name 'filename' */ ret = tcc_write_elf_file(s1, filename, phnum, phdr, file_offset, sec_order); s1->nb_sections = shnum; the_end: tcc_free(sec_order); tcc_free(phdr); return ret; } LIBTCCAPI int tcc_output_file(TCCState *s, const char *filename) { int ret; #ifdef TCC_TARGET_PE if (s->output_type != TCC_OUTPUT_OBJ) { ret = pe_output_file(s, filename); } else #endif ret = elf_output_file(s, filename); return ret; } static void *load_data(int fd, unsigned long file_offset, unsigned long size) { void *data; data = tcc_malloc(size); lseek(fd, file_offset, SEEK_SET); read(fd, data, size); return data; } typedef struct SectionMergeInfo { Section *s; /* corresponding existing section */ unsigned long offset; /* offset of the new section in the existing section */ uint8_t new_section; /* true if section 's' was added */ uint8_t link_once; /* true if link once section */ } SectionMergeInfo; ST_FUNC int tcc_object_type(int fd, ElfW(Ehdr) *h) { int size = read(fd, h, sizeof *h); if (size == sizeof *h && 0 == memcmp(h, ELFMAG, 4)) { if (h->e_type == ET_REL) return AFF_BINTYPE_REL; if (h->e_type == ET_DYN) return AFF_BINTYPE_DYN; } else if (size >= 8) { if (0 == memcmp(h, ARMAG, 8)) return AFF_BINTYPE_AR; #ifdef TCC_TARGET_COFF if (((struct filehdr*)h)->f_magic == COFF_C67_MAGIC) return AFF_BINTYPE_C67; #endif } return 0; } /* load an object file and merge it with current files */ /* XXX: handle correctly stab (debug) info */ ST_FUNC int tcc_load_object_file(TCCState *s1, int fd, unsigned long file_offset) { ElfW(Ehdr) ehdr; ElfW(Shdr) *shdr, *sh; int size, i, j, offset, offseti, nb_syms, sym_index, ret, seencompressed; unsigned char *strsec, *strtab; int *old_to_new_syms; char *sh_name, *name; SectionMergeInfo *sm_table, *sm; ElfW(Sym) *sym, *symtab; ElfW_Rel *rel; Section *s; int stab_index; int stabstr_index; stab_index = stabstr_index = 0; lseek(fd, file_offset, SEEK_SET); if (tcc_object_type(fd, &ehdr) != AFF_BINTYPE_REL) goto fail1; /* test CPU specific stuff */ if (ehdr.e_ident[5] != ELFDATA2LSB || ehdr.e_machine != EM_TCC_TARGET) { fail1: tcc_error_noabort("invalid object file"); return -1; } /* read sections */ shdr = load_data(fd, file_offset + ehdr.e_shoff, sizeof(ElfW(Shdr)) * ehdr.e_shnum); sm_table = tcc_mallocz(sizeof(SectionMergeInfo) * ehdr.e_shnum); /* load section names */ sh = &shdr[ehdr.e_shstrndx]; strsec = load_data(fd, file_offset + sh->sh_offset, sh->sh_size); /* load symtab and strtab */ old_to_new_syms = NULL; symtab = NULL; strtab = NULL; nb_syms = 0; seencompressed = 0; for(i = 1; i < ehdr.e_shnum; i++) { sh = &shdr[i]; if (sh->sh_type == SHT_SYMTAB) { if (symtab) { tcc_error_noabort("object must contain only one symtab"); fail: ret = -1; goto the_end; } nb_syms = sh->sh_size / sizeof(ElfW(Sym)); symtab = load_data(fd, file_offset + sh->sh_offset, sh->sh_size); sm_table[i].s = symtab_section; /* now load strtab */ sh = &shdr[sh->sh_link]; strtab = load_data(fd, file_offset + sh->sh_offset, sh->sh_size); } if (sh->sh_flags & SHF_COMPRESSED) seencompressed = 1; } /* now examine each section and try to merge its content with the ones in memory */ for(i = 1; i < ehdr.e_shnum; i++) { /* no need to examine section name strtab */ if (i == ehdr.e_shstrndx) continue; sh = &shdr[i]; sh_name = (char *) strsec + sh->sh_name; /* ignore sections types we do not handle */ if (sh->sh_type != SHT_PROGBITS && sh->sh_type != SHT_RELX && #ifdef TCC_ARM_EABI sh->sh_type != SHT_ARM_EXIDX && #endif sh->sh_type != SHT_NOBITS && sh->sh_type != SHT_PREINIT_ARRAY && sh->sh_type != SHT_INIT_ARRAY && sh->sh_type != SHT_FINI_ARRAY && strcmp(sh_name, ".stabstr") ) continue; if (seencompressed && (!strncmp(sh_name, ".debug_", sizeof(".debug_")-1) || (sh->sh_type == SHT_RELX && !strncmp((char*)strsec + shdr[sh->sh_info].sh_name, ".debug_", sizeof(".debug_")-1)))) continue; if (sh->sh_addralign < 1) sh->sh_addralign = 1; /* find corresponding section, if any */ for(j = 1; j < s1->nb_sections;j++) { s = s1->sections[j]; if (!strcmp(s->name, sh_name)) { if (!strncmp(sh_name, ".gnu.linkonce", sizeof(".gnu.linkonce") - 1)) { /* if a 'linkonce' section is already present, we do not add it again. It is a little tricky as symbols can still be defined in it. */ sm_table[i].link_once = 1; goto next; } else { goto found; } } } /* not found: create new section */ s = new_section(s1, sh_name, sh->sh_type, sh->sh_flags & ~SHF_GROUP); /* take as much info as possible from the section. sh_link and sh_info will be updated later */ s->sh_addralign = sh->sh_addralign; s->sh_entsize = sh->sh_entsize; sm_table[i].new_section = 1; found: if (sh->sh_type != s->sh_type) { tcc_error_noabort("invalid section type"); goto fail; } /* align start of section */ offset = s->data_offset; if (0 == strcmp(sh_name, ".stab")) { stab_index = i; goto no_align; } if (0 == strcmp(sh_name, ".stabstr")) { stabstr_index = i; goto no_align; } size = sh->sh_addralign - 1; offset = (offset + size) & ~size; if (sh->sh_addralign > s->sh_addralign) s->sh_addralign = sh->sh_addralign; s->data_offset = offset; no_align: sm_table[i].offset = offset; sm_table[i].s = s; /* concatenate sections */ size = sh->sh_size; if (sh->sh_type != SHT_NOBITS) { unsigned char *ptr; lseek(fd, file_offset + sh->sh_offset, SEEK_SET); ptr = section_ptr_add(s, size); read(fd, ptr, size); } else { s->data_offset += size; } next: ; } /* gr relocate stab strings */ if (stab_index && stabstr_index) { Stab_Sym *a, *b; unsigned o; s = sm_table[stab_index].s; a = (Stab_Sym *)(s->data + sm_table[stab_index].offset); b = (Stab_Sym *)(s->data + s->data_offset); o = sm_table[stabstr_index].offset; while (a < b) a->n_strx += o, a++; } /* second short pass to update sh_link and sh_info fields of new sections */ for(i = 1; i < ehdr.e_shnum; i++) { s = sm_table[i].s; if (!s || !sm_table[i].new_section) continue; sh = &shdr[i]; if (sh->sh_link > 0) s->link = sm_table[sh->sh_link].s; if (sh->sh_type == SHT_RELX) { s->sh_info = sm_table[sh->sh_info].s->sh_num; /* update backward link */ s1->sections[s->sh_info]->reloc = s; } } sm = sm_table; /* resolve symbols */ old_to_new_syms = tcc_mallocz(nb_syms * sizeof(int)); sym = symtab + 1; for(i = 1; i < nb_syms; i++, sym++) { if (sym->st_shndx != SHN_UNDEF && sym->st_shndx < SHN_LORESERVE) { sm = &sm_table[sym->st_shndx]; if (sm->link_once) { /* if a symbol is in a link once section, we use the already defined symbol. It is very important to get correct relocations */ if (ELFW(ST_BIND)(sym->st_info) != STB_LOCAL) { name = (char *) strtab + sym->st_name; sym_index = find_elf_sym(symtab_section, name); if (sym_index) old_to_new_syms[i] = sym_index; } continue; } /* if no corresponding section added, no need to add symbol */ if (!sm->s) continue; /* convert section number */ sym->st_shndx = sm->s->sh_num; /* offset value */ sym->st_value += sm->offset; } /* add symbol */ name = (char *) strtab + sym->st_name; sym_index = set_elf_sym(symtab_section, sym->st_value, sym->st_size, sym->st_info, sym->st_other, sym->st_shndx, name); old_to_new_syms[i] = sym_index; } /* third pass to patch relocation entries */ for(i = 1; i < ehdr.e_shnum; i++) { s = sm_table[i].s; if (!s) continue; sh = &shdr[i]; offset = sm_table[i].offset; switch(s->sh_type) { case SHT_RELX: /* take relocation offset information */ offseti = sm_table[sh->sh_info].offset; for_each_elem(s, (offset / sizeof(*rel)), rel, ElfW_Rel) { int type; unsigned sym_index; /* convert symbol index */ type = ELFW(R_TYPE)(rel->r_info); sym_index = ELFW(R_SYM)(rel->r_info); /* NOTE: only one symtab assumed */ if (sym_index >= nb_syms) goto invalid_reloc; sym_index = old_to_new_syms[sym_index]; /* ignore link_once in rel section. */ if (!sym_index && !sm->link_once #ifdef TCC_TARGET_ARM && type != R_ARM_V4BX #endif ) { invalid_reloc: tcc_error_noabort("Invalid relocation entry [%2d] '%s' @ %.8x", i, strsec + sh->sh_name, rel->r_offset); goto fail; } rel->r_info = ELFW(R_INFO)(sym_index, type); /* offset the relocation offset */ rel->r_offset += offseti; #ifdef TCC_TARGET_ARM /* Jumps and branches from a Thumb code to a PLT entry need special handling since PLT entries are ARM code. Unconditional bl instructions referencing PLT entries are handled by converting these instructions into blx instructions. Other case of instructions referencing a PLT entry require to add a Thumb stub before the PLT entry to switch to ARM mode. We set bit plt_thumb_stub of the attribute of a symbol to indicate such a case. */ if (type == R_ARM_THM_JUMP24) get_sym_attr(s1, sym_index, 1)->plt_thumb_stub = 1; #endif } break; default: break; } } ret = 0; the_end: tcc_free(symtab); tcc_free(strtab); tcc_free(old_to_new_syms); tcc_free(sm_table); tcc_free(strsec); tcc_free(shdr); return ret; } typedef struct ArchiveHeader { char ar_name[16]; /* name of this member */ char ar_date[12]; /* file mtime */ char ar_uid[6]; /* owner uid; printed as decimal */ char ar_gid[6]; /* owner gid; printed as decimal */ char ar_mode[8]; /* file mode, printed as octal */ char ar_size[10]; /* file size, printed as decimal */ char ar_fmag[2]; /* should contain ARFMAG */ } ArchiveHeader; static int get_be32(const uint8_t *b) { return b[3] | (b[2] << 8) | (b[1] << 16) | (b[0] << 24); } static long get_be64(const uint8_t *b) { long long ret = get_be32(b); ret = (ret << 32) | (unsigned)get_be32(b+4); return (long)ret; } /* load only the objects which resolve undefined symbols */ static int tcc_load_alacarte(TCCState *s1, int fd, int size, int entrysize) { long i, bound, nsyms, sym_index, off, ret; uint8_t *data; const char *ar_names, *p; const uint8_t *ar_index; ElfW(Sym) *sym; data = tcc_malloc(size); if (read(fd, data, size) != size) goto fail; nsyms = entrysize == 4 ? get_be32(data) : get_be64(data); ar_index = data + entrysize; ar_names = (char *) ar_index + nsyms * entrysize; do { bound = 0; for(p = ar_names, i = 0; i < nsyms; i++, p += strlen(p)+1) { sym_index = find_elf_sym(symtab_section, p); if(sym_index) { sym = &((ElfW(Sym) *)symtab_section->data)[sym_index]; if(sym->st_shndx == SHN_UNDEF) { off = (entrysize == 4 ? get_be32(ar_index + i * 4) : get_be64(ar_index + i * 8)) + sizeof(ArchiveHeader); ++bound; if(tcc_load_object_file(s1, fd, off) < 0) { fail: ret = -1; goto the_end; } } } } } while(bound); ret = 0; the_end: tcc_free(data); return ret; } /* load a '.a' file */ ST_FUNC int tcc_load_archive(TCCState *s1, int fd) { ArchiveHeader hdr; char ar_size[11]; char ar_name[17]; char magic[8]; int size, len, i; unsigned long file_offset; /* skip magic which was already checked */ read(fd, magic, sizeof(magic)); for(;;) { len = read(fd, &hdr, sizeof(hdr)); if (len == 0) break; if (len != sizeof(hdr)) { tcc_error_noabort("invalid archive"); return -1; } memcpy(ar_size, hdr.ar_size, sizeof(hdr.ar_size)); ar_size[sizeof(hdr.ar_size)] = '\0'; size = strtol(ar_size, NULL, 0); memcpy(ar_name, hdr.ar_name, sizeof(hdr.ar_name)); for(i = sizeof(hdr.ar_name) - 1; i >= 0; i--) { if (ar_name[i] != ' ') break; } ar_name[i + 1] = '\0'; file_offset = lseek(fd, 0, SEEK_CUR); /* align to even */ size = (size + 1) & ~1; if (!strcmp(ar_name, "/")) { /* coff symbol table : we handle it */ if(s1->alacarte_link) return tcc_load_alacarte(s1, fd, size, 4); } else if (!strcmp(ar_name, "/SYM64/")) { if(s1->alacarte_link) return tcc_load_alacarte(s1, fd, size, 8); } else { ElfW(Ehdr) ehdr; if (tcc_object_type(fd, &ehdr) == AFF_BINTYPE_REL) { if (tcc_load_object_file(s1, fd, file_offset) < 0) return -1; } } lseek(fd, file_offset + size, SEEK_SET); } return 0; } #ifndef TCC_TARGET_PE /* load a DLL and all referenced DLLs. 'level = 0' means that the DLL is referenced by the user (so it should be added as DT_NEEDED in the generated ELF file) */ ST_FUNC int tcc_load_dll(TCCState *s1, int fd, const char *filename, int level) { ElfW(Ehdr) ehdr; ElfW(Shdr) *shdr, *sh, *sh1; int i, j, nb_syms, nb_dts, sym_bind, ret; ElfW(Sym) *sym, *dynsym; ElfW(Dyn) *dt, *dynamic; unsigned char *dynstr; const char *name, *soname; DLLReference *dllref; read(fd, &ehdr, sizeof(ehdr)); /* test CPU specific stuff */ if (ehdr.e_ident[5] != ELFDATA2LSB || ehdr.e_machine != EM_TCC_TARGET) { tcc_error_noabort("bad architecture"); return -1; } /* read sections */ shdr = load_data(fd, ehdr.e_shoff, sizeof(ElfW(Shdr)) * ehdr.e_shnum); /* load dynamic section and dynamic symbols */ nb_syms = 0; nb_dts = 0; dynamic = NULL; dynsym = NULL; /* avoid warning */ dynstr = NULL; /* avoid warning */ for(i = 0, sh = shdr; i < ehdr.e_shnum; i++, sh++) { switch(sh->sh_type) { case SHT_DYNAMIC: nb_dts = sh->sh_size / sizeof(ElfW(Dyn)); dynamic = load_data(fd, sh->sh_offset, sh->sh_size); break; case SHT_DYNSYM: nb_syms = sh->sh_size / sizeof(ElfW(Sym)); dynsym = load_data(fd, sh->sh_offset, sh->sh_size); sh1 = &shdr[sh->sh_link]; dynstr = load_data(fd, sh1->sh_offset, sh1->sh_size); break; default: break; } } /* compute the real library name */ soname = tcc_basename(filename); for(i = 0, dt = dynamic; i < nb_dts; i++, dt++) { if (dt->d_tag == DT_SONAME) { soname = (char *) dynstr + dt->d_un.d_val; } } /* if the dll is already loaded, do not load it */ for(i = 0; i < s1->nb_loaded_dlls; i++) { dllref = s1->loaded_dlls[i]; if (!strcmp(soname, dllref->name)) { /* but update level if needed */ if (level < dllref->level) dllref->level = level; ret = 0; goto the_end; } } /* add the dll and its level */ dllref = tcc_mallocz(sizeof(DLLReference) + strlen(soname)); dllref->level = level; strcpy(dllref->name, soname); dynarray_add(&s1->loaded_dlls, &s1->nb_loaded_dlls, dllref); /* add dynamic symbols in dynsym_section */ for(i = 1, sym = dynsym + 1; i < nb_syms; i++, sym++) { sym_bind = ELFW(ST_BIND)(sym->st_info); if (sym_bind == STB_LOCAL) continue; name = (char *) dynstr + sym->st_name; set_elf_sym(s1->dynsymtab_section, sym->st_value, sym->st_size, sym->st_info, sym->st_other, sym->st_shndx, name); } /* load all referenced DLLs */ for(i = 0, dt = dynamic; i < nb_dts; i++, dt++) { switch(dt->d_tag) { case DT_NEEDED: name = (char *) dynstr + dt->d_un.d_val; for(j = 0; j < s1->nb_loaded_dlls; j++) { dllref = s1->loaded_dlls[j]; if (!strcmp(name, dllref->name)) goto already_loaded; } if (tcc_add_dll(s1, name, AFF_REFERENCED_DLL) < 0) { tcc_error_noabort("referenced dll '%s' not found", name); ret = -1; goto the_end; } already_loaded: break; } } ret = 0; the_end: tcc_free(dynstr); tcc_free(dynsym); tcc_free(dynamic); tcc_free(shdr); return ret; } #define LD_TOK_NAME 256 #define LD_TOK_EOF (-1) /* return next ld script token */ static int ld_next(TCCState *s1, char *name, int name_size) { int c; char *q; redo: switch(ch) { case ' ': case '\t': case '\f': case '\v': case '\r': case '\n': inp(); goto redo; case '/': minp(); if (ch == '*') { file->buf_ptr = parse_comment(file->buf_ptr); ch = file->buf_ptr[0]; goto redo; } else { q = name; *q++ = '/'; goto parse_name; } break; case '\\': ch = handle_eob(); if (ch != '\\') goto redo; /* fall through */ /* case 'a' ... 'z': */ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': /* case 'A' ... 'z': */ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': case '.': case '$': case '~': q = name; parse_name: for(;;) { if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || strchr("/.-_+=$:\\,~", ch))) break; if ((q - name) < name_size - 1) { *q++ = ch; } minp(); } *q = '\0'; c = LD_TOK_NAME; break; case CH_EOF: c = LD_TOK_EOF; break; default: c = ch; inp(); break; } return c; } static int ld_add_file(TCCState *s1, const char filename[]) { if (filename[0] == '/') { if (CONFIG_SYSROOT[0] == '\0' && tcc_add_file_internal(s1, filename, AFF_TYPE_BIN) == 0) return 0; filename = tcc_basename(filename); } return tcc_add_dll(s1, filename, 0); } static inline int new_undef_syms(void) { int ret = 0; ret = new_undef_sym; new_undef_sym = 0; return ret; } static int ld_add_file_list(TCCState *s1, const char *cmd, int as_needed) { char filename[1024], libname[1024]; int t, group, nblibs = 0, ret = 0; char **libs = NULL; group = !strcmp(cmd, "GROUP"); if (!as_needed) new_undef_syms(); t = ld_next(s1, filename, sizeof(filename)); if (t != '(') expect("("); t = ld_next(s1, filename, sizeof(filename)); for(;;) { libname[0] = '\0'; if (t == LD_TOK_EOF) { tcc_error_noabort("unexpected end of file"); ret = -1; goto lib_parse_error; } else if (t == ')') { break; } else if (t == '-') { t = ld_next(s1, filename, sizeof(filename)); if ((t != LD_TOK_NAME) || (filename[0] != 'l')) { tcc_error_noabort("library name expected"); ret = -1; goto lib_parse_error; } pstrcpy(libname, sizeof libname, &filename[1]); if (s1->static_link) { snprintf(filename, sizeof filename, "lib%s.a", libname); } else { snprintf(filename, sizeof filename, "lib%s.so", libname); } } else if (t != LD_TOK_NAME) { tcc_error_noabort("filename expected"); ret = -1; goto lib_parse_error; } if (!strcmp(filename, "AS_NEEDED")) { ret = ld_add_file_list(s1, cmd, 1); if (ret) goto lib_parse_error; } else { /* TODO: Implement AS_NEEDED support. Ignore it for now */ if (!as_needed) { ret = ld_add_file(s1, filename); if (ret) goto lib_parse_error; if (group) { /* Add the filename *and* the libname to avoid future conversions */ dynarray_add(&libs, &nblibs, tcc_strdup(filename)); if (libname[0] != '\0') dynarray_add(&libs, &nblibs, tcc_strdup(libname)); } } } t = ld_next(s1, filename, sizeof(filename)); if (t == ',') { t = ld_next(s1, filename, sizeof(filename)); } } if (group && !as_needed) { while (new_undef_syms()) { int i; for (i = 0; i < nblibs; i ++) ld_add_file(s1, libs[i]); } } lib_parse_error: dynarray_reset(&libs, &nblibs); return ret; } /* interpret a subset of GNU ldscripts to handle the dummy libc.so files */ ST_FUNC int tcc_load_ldscript(TCCState *s1) { char cmd[64]; char filename[1024]; int t, ret; ch = handle_eob(); for(;;) { t = ld_next(s1, cmd, sizeof(cmd)); if (t == LD_TOK_EOF) return 0; else if (t != LD_TOK_NAME) return -1; if (!strcmp(cmd, "INPUT") || !strcmp(cmd, "GROUP")) { ret = ld_add_file_list(s1, cmd, 0); if (ret) return ret; } else if (!strcmp(cmd, "OUTPUT_FORMAT") || !strcmp(cmd, "TARGET")) { /* ignore some commands */ t = ld_next(s1, cmd, sizeof(cmd)); if (t != '(') expect("("); for(;;) { t = ld_next(s1, filename, sizeof(filename)); if (t == LD_TOK_EOF) { tcc_error_noabort("unexpected end of file"); return -1; } else if (t == ')') { break; } } } else { return -1; } } return 0; } #endif /* !TCC_TARGET_PE */
/* * TCC - Tiny C Compiler - Support for -run switch * * Copyright (c) 2001-2004 Fabrice Bellard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _TCC_H #include "tcc.h" #endif /* only native compiler supports -run */ #ifdef TCC_IS_NATIVE #ifndef _WIN32 # include <sys/mman.h> #endif #ifdef CONFIG_TCC_BACKTRACE # ifndef _WIN32 # include <signal.h> # ifndef __OpenBSD__ # include <sys/ucontext.h> # endif # else # define ucontext_t CONTEXT # endif ST_DATA int rt_num_callers = 6; ST_DATA const char **rt_bound_error_msg; ST_DATA void *rt_prog_main; static int rt_get_caller_pc(addr_t *paddr, ucontext_t *uc, int level); static void rt_error(ucontext_t *uc, const char *fmt, ...); static void set_exception_handler(void); #endif static void set_pages_executable(void *ptr, unsigned long length); static int tcc_relocate_ex(TCCState *s1, void *ptr); #ifdef _WIN64 static void *win64_add_function_table(TCCState *s1); static void win64_del_function_table(void *); #endif // #define HAVE_SELINUX /* ------------------------------------------------------------- */ /* Do all relocations (needed before using tcc_get_symbol()) Returns -1 on error. */ LIBTCCAPI int tcc_relocate(TCCState *s1, void *ptr) { int size; if (TCC_RELOCATE_AUTO != ptr) return tcc_relocate_ex(s1, ptr); size = tcc_relocate_ex(s1, NULL); if (size < 0) return -1; #ifdef HAVE_SELINUX /* Use mmap instead of malloc for Selinux. */ ptr = mmap (NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (ptr == MAP_FAILED) tcc_error("tccrun: could not map memory"); dynarray_add(&s1->runtime_mem, &s1->nb_runtime_mem, (void*)(addr_t)size); #else ptr = tcc_malloc(size); #endif tcc_relocate_ex(s1, ptr); /* no more errors expected */ dynarray_add(&s1->runtime_mem, &s1->nb_runtime_mem, ptr); return 0; } ST_FUNC void tcc_run_free(TCCState *s1) { int i; for (i = 0; i < s1->nb_runtime_mem; ++i) { #ifdef HAVE_SELINUX unsigned size = (unsigned)(addr_t)s1->runtime_mem[i++]; munmap(s1->runtime_mem[i], size); #else #ifdef _WIN64 win64_del_function_table(*(void**)s1->runtime_mem[i]); #endif tcc_free(s1->runtime_mem[i]); #endif } tcc_free(s1->runtime_mem); } /* launch the compiled program with the given arguments */ LIBTCCAPI int tcc_run(TCCState *s1, int argc, char **argv) { int (*prog_main)(int, char **); s1->runtime_main = "main"; if (tcc_relocate(s1, TCC_RELOCATE_AUTO) < 0) return -1; prog_main = tcc_get_symbol_err(s1, s1->runtime_main); #ifdef CONFIG_TCC_BACKTRACE if (s1->do_debug) { set_exception_handler(); rt_prog_main = prog_main; } #endif errno = 0; /* clean errno value */ #ifdef CONFIG_TCC_BCHECK if (s1->do_bounds_check) { void (*bound_init)(void); void (*bound_exit)(void); void (*bound_new_region)(void *p, addr_t size); int (*bound_delete_region)(void *p); int i, ret; /* set error function */ rt_bound_error_msg = tcc_get_symbol_err(s1, "__bound_error_msg"); /* XXX: use .init section so that it also work in binary ? */ bound_init = tcc_get_symbol_err(s1, "__bound_init"); bound_exit = tcc_get_symbol_err(s1, "__bound_exit"); bound_new_region = tcc_get_symbol_err(s1, "__bound_new_region"); bound_delete_region = tcc_get_symbol_err(s1, "__bound_delete_region"); bound_init(); /* mark argv area as valid */ bound_new_region(argv, argc*sizeof(argv[0])); for (i=0; i<argc; ++i) bound_new_region(argv[i], strlen(argv[i]) + 1); ret = (*prog_main)(argc, argv); /* unmark argv area */ for (i=0; i<argc; ++i) bound_delete_region(argv[i]); bound_delete_region(argv); bound_exit(); return ret; } #endif return (*prog_main)(argc, argv); } #if defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64 #define RUN_SECTION_ALIGNMENT 63 #else #define RUN_SECTION_ALIGNMENT 15 #endif /* relocate code. Return -1 on error, required size if ptr is NULL, otherwise copy code into buffer passed by the caller */ static int tcc_relocate_ex(TCCState *s1, void *ptr) { Section *s; unsigned offset, length, fill, i, k; addr_t mem; if (NULL == ptr) { s1->nb_errors = 0; #ifdef TCC_TARGET_PE pe_output_file(s1, NULL); #else tcc_add_runtime(s1); relocate_common_syms(); tcc_add_linker_symbols(s1); build_got_entries(s1); #endif if (s1->nb_errors) return -1; } offset = 0, mem = (addr_t)ptr; fill = -mem & RUN_SECTION_ALIGNMENT; #ifdef _WIN64 offset += sizeof (void*); #endif for (k = 0; k < 2; ++k) { for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; if (0 == (s->sh_flags & SHF_ALLOC)) continue; if (k != !(s->sh_flags & SHF_EXECINSTR)) continue; offset += fill; s->sh_addr = mem ? mem + offset : 0; #if 0 if (mem) printf("%-16s +%02lx %p %04x\n", s->name, fill, (void*)s->sh_addr, (unsigned)s->data_offset); #endif offset += s->data_offset; fill = -(mem + offset) & 15; } #if RUN_SECTION_ALIGNMENT > 15 /* To avoid that x86 processors would reload cached instructions each time when data is written in the near, we need to make sure that code and data do not share the same 64 byte unit */ fill = -(mem + offset) & RUN_SECTION_ALIGNMENT; #endif } /* relocate symbols */ relocate_syms(s1, s1->symtab, 1); if (s1->nb_errors) return -1; if (0 == mem) return offset + RUN_SECTION_ALIGNMENT; /* relocate each section */ for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; if (s->reloc) relocate_section(s1, s); } relocate_plt(s1); #ifdef _WIN64 *(void**)ptr = win64_add_function_table(s1); #endif for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; if (0 == (s->sh_flags & SHF_ALLOC)) continue; length = s->data_offset; ptr = (void*)s->sh_addr; if (NULL == s->data || s->sh_type == SHT_NOBITS) memset(ptr, 0, length); else memcpy(ptr, s->data, length); /* mark executable sections as executable in memory */ if (s->sh_flags & SHF_EXECINSTR) set_pages_executable(ptr, length); } return 0; } /* ------------------------------------------------------------- */ /* allow to run code in memory */ static void set_pages_executable(void *ptr, unsigned long length) { #ifdef _WIN32 unsigned long old_protect; VirtualProtect(ptr, length, PAGE_EXECUTE_READWRITE, &old_protect); #else void __clear_cache(void *beginning, void *end); addr_t start, end; #ifndef PAGESIZE # define PAGESIZE 4096 #endif start = (addr_t)ptr & ~(PAGESIZE - 1); end = (addr_t)ptr + length; end = (end + PAGESIZE - 1) & ~(PAGESIZE - 1); if (mprotect((void *)start, end - start, PROT_READ | PROT_WRITE | PROT_EXEC)) tcc_error("mprotect failed: did you mean to configure --with-selinux?"); # if defined TCC_TARGET_ARM || defined TCC_TARGET_ARM64 #if !BOOTSTRAP __clear_cache(ptr, (char *)ptr + length); #endif # endif #endif } #ifdef _WIN64 static void *win64_add_function_table(TCCState *s1) { void *p = NULL; if (s1->uw_pdata) { p = (void*)s1->uw_pdata->sh_addr; RtlAddFunctionTable( (RUNTIME_FUNCTION*)p, s1->uw_pdata->data_offset / sizeof (RUNTIME_FUNCTION), text_section->sh_addr ); s1->uw_pdata = NULL; } return p;; } static void win64_del_function_table(void *p) { if (p) { RtlDeleteFunctionTable((RUNTIME_FUNCTION*)p); } } #endif /* ------------------------------------------------------------- */ #ifdef CONFIG_TCC_BACKTRACE ST_FUNC void tcc_set_num_callers(int n) { rt_num_callers = n; } /* print the position in the source file of PC value 'pc' by reading the stabs debug information */ static addr_t rt_printline(addr_t wanted_pc, const char *msg) { char func_name[128], last_func_name[128]; addr_t func_addr, last_pc, pc; const char *incl_files[INCLUDE_STACK_SIZE]; int incl_index, len, last_line_num, i; const char *str, *p; Stab_Sym *stab_sym = NULL, *stab_sym_end, *sym; int stab_len = 0; char *stab_str = NULL; if (stab_section) { stab_len = stab_section->data_offset; stab_sym = (Stab_Sym *)stab_section->data; stab_str = (char *) stabstr_section->data; } func_name[0] = '\0'; func_addr = 0; incl_index = 0; last_func_name[0] = '\0'; last_pc = (addr_t)-1; last_line_num = 1; if (!stab_sym) goto no_stabs; stab_sym_end = (Stab_Sym*)((char*)stab_sym + stab_len); for (sym = stab_sym + 1; sym < stab_sym_end; ++sym) { switch(sym->n_type) { /* function start or end */ case N_FUN: if (sym->n_strx == 0) { /* we test if between last line and end of function */ pc = sym->n_value + func_addr; if (wanted_pc >= last_pc && wanted_pc < pc) goto found; func_name[0] = '\0'; func_addr = 0; } else { str = stab_str + sym->n_strx; p = strchr(str, ':'); if (!p) { pstrcpy(func_name, sizeof(func_name), str); } else { len = p - str; if (len > sizeof(func_name) - 1) len = sizeof(func_name) - 1; memcpy(func_name, str, len); func_name[len] = '\0'; } func_addr = sym->n_value; } break; /* line number info */ case N_SLINE: pc = sym->n_value + func_addr; if (wanted_pc >= last_pc && wanted_pc < pc) goto found; last_pc = pc; last_line_num = sym->n_desc; /* XXX: slow! */ strcpy(last_func_name, func_name); break; /* include files */ case N_BINCL: str = stab_str + sym->n_strx; add_incl: if (incl_index < INCLUDE_STACK_SIZE) { incl_files[incl_index++] = str; } break; case N_EINCL: if (incl_index > 1) incl_index--; break; case N_SO: if (sym->n_strx == 0) { incl_index = 0; /* end of translation unit */ } else { str = stab_str + sym->n_strx; /* do not add path */ len = strlen(str); if (len > 0 && str[len - 1] != '/') goto add_incl; } break; } } no_stabs: /* second pass: we try symtab symbols (no line number info) */ incl_index = 0; if (symtab_section) { ElfW(Sym) *sym, *sym_end; int type; sym_end = (ElfW(Sym) *)(symtab_section->data + symtab_section->data_offset); for(sym = (ElfW(Sym) *)symtab_section->data + 1; sym < sym_end; sym++) { type = ELFW(ST_TYPE)(sym->st_info); if (type == STT_FUNC || type == STT_GNU_IFUNC) { if (wanted_pc >= sym->st_value && wanted_pc < sym->st_value + sym->st_size) { pstrcpy(last_func_name, sizeof(last_func_name), (char *) strtab_section->data + sym->st_name); func_addr = sym->st_value; goto found; } } } } /* did not find any info: */ fprintf(stderr, "%s %p ???\n", msg, (void*)wanted_pc); fflush(stderr); return 0; found: i = incl_index; if (i > 0) fprintf(stderr, "%s:%d: ", incl_files[--i], last_line_num); fprintf(stderr, "%s %p", msg, (void*)wanted_pc); if (last_func_name[0] != '\0') fprintf(stderr, " %s()", last_func_name); if (--i >= 0) { fprintf(stderr, " (included from "); for (;;) { fprintf(stderr, "%s", incl_files[i]); if (--i < 0) break; fprintf(stderr, ", "); } fprintf(stderr, ")"); } fprintf(stderr, "\n"); fflush(stderr); return func_addr; } /* emit a run time error at position 'pc' */ static void rt_error(ucontext_t *uc, const char *fmt, ...) { va_list ap; addr_t pc; int i; fprintf(stderr, "Runtime error: "); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "\n"); for(i=0;i<rt_num_callers;i++) { if (rt_get_caller_pc(&pc, uc, i) < 0) break; pc = rt_printline(pc, i ? "by" : "at"); if (pc == (addr_t)rt_prog_main && pc) break; } } /* ------------------------------------------------------------- */ #ifndef _WIN32 /* signal handler for fatal errors */ static void sig_error(int signum, siginfo_t *siginf, void *puc) { ucontext_t *uc = puc; switch(signum) { case SIGFPE: switch(siginf->si_code) { case FPE_INTDIV: case FPE_FLTDIV: rt_error(uc, "division by zero"); break; default: rt_error(uc, "floating point exception"); break; } break; case SIGBUS: case SIGSEGV: if (rt_bound_error_msg && *rt_bound_error_msg) rt_error(uc, *rt_bound_error_msg); else rt_error(uc, "dereferencing invalid pointer"); break; case SIGILL: rt_error(uc, "illegal instruction"); break; case SIGABRT: rt_error(uc, "abort() called"); break; default: rt_error(uc, "caught signal %d", signum); break; } exit(255); } #ifndef SA_SIGINFO # define SA_SIGINFO 0x00000004u #endif /* Generate a stack backtrace when a CPU exception occurs. */ static void set_exception_handler(void) { struct sigaction sigact; /* install TCC signal handlers to print debug info on fatal runtime errors */ sigact.sa_flags = SA_SIGINFO | SA_RESETHAND; sigact.sa_sigaction = sig_error; sigemptyset(&sigact.sa_mask); sigaction(SIGFPE, &sigact, NULL); sigaction(SIGILL, &sigact, NULL); sigaction(SIGSEGV, &sigact, NULL); sigaction(SIGBUS, &sigact, NULL); sigaction(SIGABRT, &sigact, NULL); } /* ------------------------------------------------------------- */ #ifdef __i386__ /* fix for glibc 2.1 */ #ifndef REG_EIP #define REG_EIP EIP #define REG_EBP EBP #endif /* return the PC at frame level 'level'. Return negative if not found */ static int rt_get_caller_pc(addr_t *paddr, ucontext_t *uc, int level) { addr_t fp; int i; if (level == 0) { #if defined(__APPLE__) *paddr = uc->uc_mcontext->__ss.__eip; #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) *paddr = uc->uc_mcontext.mc_eip; #elif defined(__dietlibc__) *paddr = uc->uc_mcontext.eip; #elif defined(__NetBSD__) *paddr = uc->uc_mcontext.__gregs[_REG_EIP]; #elif defined(__OpenBSD__) *paddr = uc->sc_eip; #else *paddr = uc->uc_mcontext.gregs[REG_EIP]; #endif return 0; } else { #if defined(__APPLE__) fp = uc->uc_mcontext->__ss.__ebp; #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) fp = uc->uc_mcontext.mc_ebp; #elif defined(__dietlibc__) fp = uc->uc_mcontext.ebp; #elif defined(__NetBSD__) fp = uc->uc_mcontext.__gregs[_REG_EBP]; #elif defined(__OpenBSD__) *paddr = uc->sc_ebp; #else fp = uc->uc_mcontext.gregs[REG_EBP]; #endif for(i=1;i<level;i++) { /* XXX: check address validity with program info */ if (fp <= 0x1000 || fp >= 0xc0000000) return -1; fp = ((addr_t *)fp)[0]; } *paddr = ((addr_t *)fp)[1]; return 0; } } /* ------------------------------------------------------------- */ #elif defined(__x86_64__) /* return the PC at frame level 'level'. Return negative if not found */ static int rt_get_caller_pc(addr_t *paddr, ucontext_t *uc, int level) { addr_t fp; int i; if (level == 0) { /* XXX: only support linux */ #if defined(__APPLE__) *paddr = uc->uc_mcontext->__ss.__rip; #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) *paddr = uc->uc_mcontext.mc_rip; #elif defined(__NetBSD__) *paddr = uc->uc_mcontext.__gregs[_REG_RIP]; #else *paddr = uc->uc_mcontext.gregs[REG_RIP]; #endif return 0; } else { #if defined(__APPLE__) fp = uc->uc_mcontext->__ss.__rbp; #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) fp = uc->uc_mcontext.mc_rbp; #elif defined(__NetBSD__) fp = uc->uc_mcontext.__gregs[_REG_RBP]; #else fp = uc->uc_mcontext.gregs[REG_RBP]; #endif for(i=1;i<level;i++) { /* XXX: check address validity with program info */ if (fp <= 0x1000) return -1; fp = ((addr_t *)fp)[0]; } *paddr = ((addr_t *)fp)[1]; return 0; } } /* ------------------------------------------------------------- */ #elif defined(__arm__) /* return the PC at frame level 'level'. Return negative if not found */ static int rt_get_caller_pc(addr_t *paddr, ucontext_t *uc, int level) { addr_t fp, sp; int i; if (level == 0) { /* XXX: only supports linux */ #if defined(__linux__) *paddr = uc->uc_mcontext.arm_pc; #else return -1; #endif return 0; } else { #if defined(__linux__) fp = uc->uc_mcontext.arm_fp; sp = uc->uc_mcontext.arm_sp; if (sp < 0x1000) sp = 0x1000; #else return -1; #endif /* XXX: specific to tinycc stack frames */ if (fp < sp + 12 || fp & 3) return -1; for(i = 1; i < level; i++) { sp = ((addr_t *)fp)[-2]; if (sp < fp || sp - fp > 16 || sp & 3) return -1; fp = ((addr_t *)fp)[-3]; if (fp <= sp || fp - sp < 12 || fp & 3) return -1; } /* XXX: check address validity with program info */ *paddr = ((addr_t *)fp)[-1]; return 0; } } /* ------------------------------------------------------------- */ #elif defined(__aarch64__) static int rt_get_caller_pc(addr_t *paddr, ucontext_t *uc, int level) { if (level < 0) return -1; else if (level == 0) { *paddr = uc->uc_mcontext.pc; return 0; } else { addr_t *fp = (addr_t *)uc->uc_mcontext.regs[29]; int i; for (i = 1; i < level; i++) fp = (addr_t *)fp[0]; *paddr = fp[1]; return 0; } } /* ------------------------------------------------------------- */ #else #warning add arch specific rt_get_caller_pc() static int rt_get_caller_pc(addr_t *paddr, ucontext_t *uc, int level) { return -1; } #endif /* !__i386__ */ /* ------------------------------------------------------------- */ #else /* WIN32 */ static long __stdcall cpu_exception_handler(EXCEPTION_POINTERS *ex_info) { EXCEPTION_RECORD *er = ex_info->ExceptionRecord; CONTEXT *uc = ex_info->ContextRecord; switch (er->ExceptionCode) { case EXCEPTION_ACCESS_VIOLATION: if (rt_bound_error_msg && *rt_bound_error_msg) rt_error(uc, *rt_bound_error_msg); else rt_error(uc, "access violation"); break; case EXCEPTION_STACK_OVERFLOW: rt_error(uc, "stack overflow"); break; case EXCEPTION_INT_DIVIDE_BY_ZERO: rt_error(uc, "division by zero"); break; default: rt_error(uc, "exception caught"); break; } return EXCEPTION_EXECUTE_HANDLER; } /* Generate a stack backtrace when a CPU exception occurs. */ static void set_exception_handler(void) { SetUnhandledExceptionFilter(cpu_exception_handler); } /* return the PC at frame level 'level'. Return non zero if not found */ static int rt_get_caller_pc(addr_t *paddr, CONTEXT *uc, int level) { addr_t fp, pc; int i; #ifdef _WIN64 pc = uc->Rip; fp = uc->Rbp; #else pc = uc->Eip; fp = uc->Ebp; #endif if (level > 0) { for(i=1;i<level;i++) { /* XXX: check address validity with program info */ if (fp <= 0x1000 || fp >= 0xc0000000) return -1; fp = ((addr_t*)fp)[0]; } pc = ((addr_t*)fp)[1]; } *paddr = pc; return 0; } #endif /* _WIN32 */ #endif /* CONFIG_TCC_BACKTRACE */ /* ------------------------------------------------------------- */ #ifdef CONFIG_TCC_STATIC /* dummy function for profiling */ ST_FUNC void *dlopen(const char *filename, int flag) { return NULL; } ST_FUNC void dlclose(void *p) { } ST_FUNC const char *dlerror(void) { return "error"; } typedef struct TCCSyms { char *str; void *ptr; } TCCSyms; /* add the symbol you want here if no dynamic linking is done */ static TCCSyms tcc_syms[] = { #if !defined(CONFIG_TCCBOOT) #define TCCSYM(a) { #a, &a, }, TCCSYM(printf) TCCSYM(fprintf) TCCSYM(fopen) TCCSYM(fclose) #undef TCCSYM #endif { NULL, NULL }, }; ST_FUNC void *dlsym(void *handle, const char *symbol) { TCCSyms *p; p = tcc_syms; while (p->str != NULL) { if (!strcmp(p->str, symbol)) return p->ptr; p++; } return NULL; } #endif /* CONFIG_TCC_STATIC */ #endif /* TCC_IS_NATIVE */ /* ------------------------------------------------------------- */
/* * X86 code generator for TCC * * Copyright (c) 2001-2004 Fabrice Bellard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef TARGET_DEFS_ONLY /* number of available registers */ #define NB_REGS 5 #define NB_ASM_REGS 8 #define CONFIG_TCC_ASM /* a register can belong to several classes. The classes must be sorted from more general to more precise (see gv2() code which does assumptions on it). */ #define RC_INT 0x0001 /* generic integer register */ #define RC_FLOAT 0x0002 /* generic float register */ #define RC_EAX 0x0004 #define RC_ST0 0x0008 #define RC_ECX 0x0010 #define RC_EDX 0x0020 #define RC_EBX 0x0040 #define RC_IRET RC_EAX /* function return: integer register */ #define RC_LRET RC_EDX /* function return: second integer register */ #define RC_FRET RC_ST0 /* function return: float register */ /* pretty names for the registers */ enum { TREG_EAX = 0, TREG_ECX, TREG_EDX, TREG_EBX, TREG_ST0, TREG_ESP = 4 }; /* return registers for function */ #define REG_IRET TREG_EAX /* single word int return register */ #define REG_LRET TREG_EDX /* second word return register (for long long) */ #define REG_FRET TREG_ST0 /* float return register */ /* defined if function parameters must be evaluated in reverse order */ #define INVERT_FUNC_PARAMS /* defined if structures are passed as pointers. Otherwise structures are directly pushed on stack. */ /* #define FUNC_STRUCT_PARAM_AS_PTR */ /* pointer size, in bytes */ #define PTR_SIZE 4 /* long double size and alignment, in bytes */ #define LDOUBLE_SIZE 12 #define LDOUBLE_ALIGN 4 /* maximum alignment (for aligned attribute support) */ #define MAX_ALIGN 8 /******************************************************/ #else /* ! TARGET_DEFS_ONLY */ /******************************************************/ #ifndef _TCC_H #include "tcc.h" #endif /* define to 1/0 to [not] have EBX as 4th register */ #define USE_EBX 0 ST_DATA const int reg_classes[NB_REGS] = { /* eax */ RC_INT | RC_EAX, /* ecx */ RC_INT | RC_ECX, /* edx */ RC_INT | RC_EDX, /* ebx */ (RC_INT | RC_EBX) * USE_EBX, /* st0 */ RC_FLOAT | RC_ST0, }; static unsigned long func_sub_sp_offset; static int func_ret_sub; #ifdef CONFIG_TCC_BCHECK static addr_t func_bound_offset; static unsigned long func_bound_ind; #endif /* XXX: make it faster ? */ ST_FUNC void g(int c) { int ind1; if (nocode_wanted) return; ind1 = ind + 1; if (ind1 > cur_text_section->data_allocated) section_realloc(cur_text_section, ind1); cur_text_section->data[ind] = c; ind = ind1; } ST_FUNC void o(unsigned int c) { while (c) { g(c); c = c >> 8; } } ST_FUNC void gen_le16(int v) { g(v); g(v >> 8); } ST_FUNC void gen_le32(int c) { g(c); g(c >> 8); g(c >> 16); g(c >> 24); } /* output a symbol and patch all calls to it */ ST_FUNC void gsym_addr(int t, int a) { while (t) { unsigned char *ptr = cur_text_section->data + t; uint32_t n = read32le(ptr); /* next value */ write32le(ptr, a - t - 4); t = n; } } ST_FUNC void gsym(int t) { gsym_addr(t, ind); } /* instruction + 4 bytes data. Return the address of the data */ static int oad(int c, int s) { int t; if (nocode_wanted) return s; o(c); t = ind; gen_le32(s); return t; } /* generate jmp to a label */ #define gjmp2(instr,lbl) oad(instr,lbl) /* output constant with relocation if 'r & VT_SYM' is true */ ST_FUNC void gen_addr32(int r, Sym *sym, long c) { if (r & VT_SYM) greloc(cur_text_section, sym, ind, R_386_32); gen_le32(c); } ST_FUNC void gen_addrpc32(int r, Sym *sym, long c) { if (r & VT_SYM) greloc(cur_text_section, sym, ind, R_386_PC32); gen_le32(c - 4); } /* generate a modrm reference. 'op_reg' contains the addtional 3 opcode bits */ static void gen_modrm(int op_reg, int r, Sym *sym, int c) { op_reg = op_reg << 3; if ((r & VT_VALMASK) == VT_CONST) { /* constant memory reference */ o(0x05 | op_reg); gen_addr32(r, sym, c); } else if ((r & VT_VALMASK) == VT_LOCAL) { /* currently, we use only ebp as base */ if (c == (char)c) { /* short reference */ o(0x45 | op_reg); g(c); } else { oad(0x85 | op_reg, c); } } else { g(0x00 | op_reg | (r & VT_VALMASK)); } } /* load 'r' from value 'sv' */ ST_FUNC void load(int r, SValue *sv) { int v, t, ft, fc, fr; SValue v1; #ifdef TCC_TARGET_PE SValue v2; sv = pe_getimport(sv, &v2); #endif fr = sv->r; ft = sv->type.t; fc = sv->c.i; ft &= ~(VT_VOLATILE | VT_CONSTANT); v = fr & VT_VALMASK; if (fr & VT_LVAL) { if (v == VT_LLOCAL) { v1.type.t = VT_INT; v1.r = VT_LOCAL | VT_LVAL; v1.c.i = fc; fr = r; if (!(reg_classes[fr] & RC_INT)) fr = get_reg(RC_INT); load(fr, &v1); } if ((ft & VT_BTYPE) == VT_FLOAT) { o(0xd9); /* flds */ r = 0; } else if ((ft & VT_BTYPE) == VT_DOUBLE) { o(0xdd); /* fldl */ r = 0; } else if ((ft & VT_BTYPE) == VT_LDOUBLE) { o(0xdb); /* fldt */ r = 5; } else if ((ft & VT_TYPE) == VT_BYTE || (ft & VT_TYPE) == VT_BOOL) { o(0xbe0f); /* movsbl */ } else if ((ft & VT_TYPE) == (VT_BYTE | VT_UNSIGNED)) { o(0xb60f); /* movzbl */ } else if ((ft & VT_TYPE) == VT_SHORT) { o(0xbf0f); /* movswl */ } else if ((ft & VT_TYPE) == (VT_SHORT | VT_UNSIGNED)) { o(0xb70f); /* movzwl */ } else { o(0x8b); /* movl */ } gen_modrm(r, fr, sv->sym, fc); } else { if (v == VT_CONST) { o(0xb8 + r); /* mov $xx, r */ gen_addr32(fr, sv->sym, fc); } else if (v == VT_LOCAL) { if (fc) { o(0x8d); /* lea xxx(%ebp), r */ gen_modrm(r, VT_LOCAL, sv->sym, fc); } else { o(0x89); o(0xe8 + r); /* mov %ebp, r */ } } else if (v == VT_CMP) { oad(0xb8 + r, 0); /* mov $0, r */ o(0x0f); /* setxx %br */ o(fc); o(0xc0 + r); } else if (v == VT_JMP || v == VT_JMPI) { t = v & 1; oad(0xb8 + r, t); /* mov $1, r */ o(0x05eb); /* jmp after */ gsym(fc); oad(0xb8 + r, t ^ 1); /* mov $0, r */ } else if (v != r) { o(0x89); o(0xc0 + r + v * 8); /* mov v, r */ } } } /* store register 'r' in lvalue 'v' */ ST_FUNC void store(int r, SValue *v) { int fr, bt, ft, fc; #ifdef TCC_TARGET_PE SValue v2; v = pe_getimport(v, &v2); #endif ft = v->type.t; fc = v->c.i; fr = v->r & VT_VALMASK; ft &= ~(VT_VOLATILE | VT_CONSTANT); bt = ft & VT_BTYPE; /* XXX: incorrect if float reg to reg */ if (bt == VT_FLOAT) { o(0xd9); /* fsts */ r = 2; } else if (bt == VT_DOUBLE) { o(0xdd); /* fstpl */ r = 2; } else if (bt == VT_LDOUBLE) { o(0xc0d9); /* fld %st(0) */ o(0xdb); /* fstpt */ r = 7; } else { if (bt == VT_SHORT) o(0x66); if (bt == VT_BYTE || bt == VT_BOOL) o(0x88); else o(0x89); } if (fr == VT_CONST || fr == VT_LOCAL || (v->r & VT_LVAL)) { gen_modrm(r, v->r, v->sym, fc); } else if (fr != r) { o(0xc0 + fr + r * 8); /* mov r, fr */ } } static void gadd_sp(int val) { if (val == (char)val) { o(0xc483); g(val); } else { oad(0xc481, val); /* add $xxx, %esp */ } } #if defined CONFIG_TCC_BCHECK || defined TCC_TARGET_PE static void gen_static_call(int v) { Sym *sym; sym = external_global_sym(v, &func_old_type, 0); oad(0xe8, -4); greloc(cur_text_section, sym, ind-4, R_386_PC32); } #endif /* 'is_jmp' is '1' if it is a jump */ static void gcall_or_jmp(int is_jmp) { int r; if ((vtop->r & (VT_VALMASK | VT_LVAL)) == VT_CONST) { int rt; /* constant case */ if (vtop->r & VT_SYM) { /* relocation case */ greloc(cur_text_section, vtop->sym, ind + 1, R_386_PC32); } else { /* put an empty PC32 relocation */ put_elf_reloc(symtab_section, cur_text_section, ind + 1, R_386_PC32, 0); } oad(0xe8 + is_jmp, vtop->c.i - 4); /* call/jmp im */ /* extend the return value to the whole register if necessary visual studio and gcc do not always set the whole eax register when assigning the return value of a function */ rt = vtop->type.ref->type.t; switch (rt & VT_BTYPE) { case VT_BYTE: if (rt & VT_UNSIGNED) { o(0xc0b60f); /* movzx %al, %eax */ } else { o(0xc0be0f); /* movsx %al, %eax */ } break; case VT_SHORT: if (rt & VT_UNSIGNED) { o(0xc0b70f); /* movzx %ax, %eax */ } else { o(0xc0bf0f); /* movsx %ax, %eax */ } break; default: break; } } else { /* otherwise, indirect call */ r = gv(RC_INT); o(0xff); /* call/jmp *r */ o(0xd0 + r + (is_jmp << 4)); } } static uint8_t fastcall_regs[3] = { TREG_EAX, TREG_EDX, TREG_ECX }; static uint8_t fastcallw_regs[2] = { TREG_ECX, TREG_EDX }; /* Return the number of registers needed to return the struct, or 0 if returning via struct pointer. */ ST_FUNC int gfunc_sret(CType *vt, int variadic, CType *ret, int *ret_align, int *regsize) { #ifdef TCC_TARGET_PE int size, align; *ret_align = 1; // Never have to re-align return values for x86 *regsize = 4; size = type_size(vt, &align); if (size > 8 || (size & (size - 1))) return 0; if (size == 8) ret->t = VT_LLONG; else if (size == 4) ret->t = VT_INT; else if (size == 2) ret->t = VT_SHORT; else ret->t = VT_BYTE; ret->ref = NULL; return 1; #else *ret_align = 1; // Never have to re-align return values for x86 return 0; #endif } /* Generate function call. The function address is pushed first, then all the parameters in call order. This functions pops all the parameters and the function address. */ ST_FUNC void gfunc_call(int nb_args) { int size, align, r, args_size, i, func_call; Sym *func_sym; args_size = 0; for(i = 0;i < nb_args; i++) { if ((vtop->type.t & VT_BTYPE) == VT_STRUCT) { size = type_size(&vtop->type, &align); /* align to stack align size */ size = (size + 3) & ~3; /* allocate the necessary size on stack */ oad(0xec81, size); /* sub $xxx, %esp */ /* generate structure store */ r = get_reg(RC_INT); o(0x89); /* mov %esp, r */ o(0xe0 + r); vset(&vtop->type, r | VT_LVAL, 0); vswap(); vstore(); args_size += size; } else if (is_float(vtop->type.t)) { gv(RC_FLOAT); /* only one float register */ if ((vtop->type.t & VT_BTYPE) == VT_FLOAT) size = 4; else if ((vtop->type.t & VT_BTYPE) == VT_DOUBLE) size = 8; else size = 12; oad(0xec81, size); /* sub $xxx, %esp */ if (size == 12) o(0x7cdb); else o(0x5cd9 + size - 4); /* fstp[s|l] 0(%esp) */ g(0x24); g(0x00); args_size += size; } else { /* simple type (currently always same size) */ /* XXX: implicit cast ? */ r = gv(RC_INT); if ((vtop->type.t & VT_BTYPE) == VT_LLONG) { size = 8; o(0x50 + vtop->r2); /* push r */ } else { size = 4; } o(0x50 + r); /* push r */ args_size += size; } vtop--; } save_regs(0); /* save used temporary registers */ func_sym = vtop->type.ref; func_call = func_sym->a.func_call; /* fast call case */ if ((func_call >= FUNC_FASTCALL1 && func_call <= FUNC_FASTCALL3) || func_call == FUNC_FASTCALLW) { int fastcall_nb_regs; uint8_t *fastcall_regs_ptr; if (func_call == FUNC_FASTCALLW) { fastcall_regs_ptr = fastcallw_regs; fastcall_nb_regs = 2; } else { fastcall_regs_ptr = fastcall_regs; fastcall_nb_regs = func_call - FUNC_FASTCALL1 + 1; } for(i = 0;i < fastcall_nb_regs; i++) { if (args_size <= 0) break; o(0x58 + fastcall_regs_ptr[i]); /* pop r */ /* XXX: incorrect for struct/floats */ args_size -= 4; } } #ifndef TCC_TARGET_PE else if ((vtop->type.ref->type.t & VT_BTYPE) == VT_STRUCT) args_size -= 4; #endif gcall_or_jmp(0); if (args_size && func_call != FUNC_STDCALL) gadd_sp(args_size); vtop--; } #ifdef TCC_TARGET_PE #define FUNC_PROLOG_SIZE (10 + USE_EBX) #else #define FUNC_PROLOG_SIZE (9 + USE_EBX) #endif /* generate function prolog of type 't' */ ST_FUNC void gfunc_prolog(CType *func_type) { int addr, align, size, func_call, fastcall_nb_regs; int param_index, param_addr; uint8_t *fastcall_regs_ptr; Sym *sym; CType *type; sym = func_type->ref; func_call = sym->a.func_call; addr = 8; loc = 0; func_vc = 0; if (func_call >= FUNC_FASTCALL1 && func_call <= FUNC_FASTCALL3) { fastcall_nb_regs = func_call - FUNC_FASTCALL1 + 1; fastcall_regs_ptr = fastcall_regs; } else if (func_call == FUNC_FASTCALLW) { fastcall_nb_regs = 2; fastcall_regs_ptr = fastcallw_regs; } else { fastcall_nb_regs = 0; fastcall_regs_ptr = NULL; } param_index = 0; ind += FUNC_PROLOG_SIZE; func_sub_sp_offset = ind; /* if the function returns a structure, then add an implicit pointer parameter */ func_vt = sym->type; func_var = (sym->c == FUNC_ELLIPSIS); #ifdef TCC_TARGET_PE size = type_size(&func_vt,&align); if (((func_vt.t & VT_BTYPE) == VT_STRUCT) && (size > 8 || (size & (size - 1)))) { #else if ((func_vt.t & VT_BTYPE) == VT_STRUCT) { #endif /* XXX: fastcall case ? */ func_vc = addr; addr += 4; param_index++; } /* define parameters */ while ((sym = sym->next) != NULL) { type = &sym->type; size = type_size(type, &align); size = (size + 3) & ~3; #ifdef FUNC_STRUCT_PARAM_AS_PTR /* structs are passed as pointer */ if ((type->t & VT_BTYPE) == VT_STRUCT) { size = 4; } #endif if (param_index < fastcall_nb_regs) { /* save FASTCALL register */ loc -= 4; o(0x89); /* movl */ gen_modrm(fastcall_regs_ptr[param_index], VT_LOCAL, NULL, loc); param_addr = loc; } else { param_addr = addr; addr += size; } sym_push(sym->v & ~SYM_FIELD, type, VT_LOCAL | lvalue_type(type->t), param_addr); param_index++; } func_ret_sub = 0; /* pascal type call ? */ if (func_call == FUNC_STDCALL) func_ret_sub = addr - 8; #ifndef TCC_TARGET_PE else if (func_vc) func_ret_sub = 4; #endif #ifdef CONFIG_TCC_BCHECK /* leave some room for bound checking code */ if (tcc_state->do_bounds_check) { func_bound_offset = lbounds_section->data_offset; func_bound_ind = ind; oad(0xb8, 0); /* lbound section pointer */ oad(0xb8, 0); /* call to function */ } #endif } /* generate function epilog */ ST_FUNC void gfunc_epilog(void) { addr_t v, saved_ind; #ifdef CONFIG_TCC_BCHECK if (tcc_state->do_bounds_check && func_bound_offset != lbounds_section->data_offset) { addr_t saved_ind; addr_t *bounds_ptr; Sym *sym_data; /* add end of table info */ bounds_ptr = section_ptr_add(lbounds_section, sizeof(addr_t)); *bounds_ptr = 0; /* generate bound local allocation */ saved_ind = ind; ind = func_bound_ind; sym_data = get_sym_ref(&char_pointer_type, lbounds_section, func_bound_offset, lbounds_section->data_offset); greloc(cur_text_section, sym_data, ind + 1, R_386_32); oad(0xb8, 0); /* mov %eax, xxx */ gen_static_call(TOK___bound_local_new); ind = saved_ind; /* generate bound check local freeing */ o(0x5250); /* save returned value, if any */ greloc(cur_text_section, sym_data, ind + 1, R_386_32); oad(0xb8, 0); /* mov %eax, xxx */ gen_static_call(TOK___bound_local_delete); o(0x585a); /* restore returned value, if any */ } #endif /* align local size to word & save local variables */ v = (-loc + 3) & -4; #if USE_EBX o(0x8b); gen_modrm(TREG_EBX, VT_LOCAL, NULL, -(v+4)); #endif o(0xc9); /* leave */ if (func_ret_sub == 0) { o(0xc3); /* ret */ } else { o(0xc2); /* ret n */ g(func_ret_sub); g(func_ret_sub >> 8); } saved_ind = ind; ind = func_sub_sp_offset - FUNC_PROLOG_SIZE; #ifdef TCC_TARGET_PE if (v >= 4096) { oad(0xb8, v); /* mov stacksize, %eax */ gen_static_call(TOK___chkstk); /* call __chkstk, (does the stackframe too) */ } else #endif { o(0xe58955); /* push %ebp, mov %esp, %ebp */ o(0xec81); /* sub esp, stacksize */ gen_le32(v); #ifdef TCC_TARGET_PE o(0x90); /* adjust to FUNC_PROLOG_SIZE */ #endif } o(0x53 * USE_EBX); /* push ebx */ ind = saved_ind; } /* generate a jump to a label */ ST_FUNC int gjmp(int t) { return gjmp2(0xe9, t); } /* generate a jump to a fixed address */ ST_FUNC void gjmp_addr(int a) { int r; r = a - ind - 2; if (r == (char)r) { g(0xeb); g(r); } else { oad(0xe9, a - ind - 5); } } ST_FUNC void gtst_addr(int inv, int a) { int v = vtop->r & VT_VALMASK; if (v == VT_CMP) { inv ^= (vtop--)->c.i; a -= ind + 2; if (a == (char)a) { g(inv - 32); g(a); } else { g(0x0f); oad(inv - 16, a - 4); } } else if ((v & ~1) == VT_JMP) { if ((v & 1) != inv) { gjmp_addr(a); gsym(vtop->c.i); } else { gsym(vtop->c.i); o(0x05eb); gjmp_addr(a); } vtop--; } } /* generate a test. set 'inv' to invert test. Stack entry is popped */ ST_FUNC int gtst(int inv, int t) { int v = vtop->r & VT_VALMASK; if (nocode_wanted) { ; } else if (v == VT_CMP) { /* fast case : can jump directly since flags are set */ g(0x0f); t = gjmp2((vtop->c.i - 16) ^ inv, t); } else if (v == VT_JMP || v == VT_JMPI) { /* && or || optimization */ if ((v & 1) == inv) { /* insert vtop->c jump list in t */ uint32_t n1, n = vtop->c.i; if (n) { while ((n1 = read32le(cur_text_section->data + n))) n = n1; write32le(cur_text_section->data + n, t); t = vtop->c.i; } } else { t = gjmp(t); gsym(vtop->c.i); } } vtop--; return t; } /* generate an integer binary operation */ ST_FUNC void gen_opi(int op) { int r, fr, opc, c; switch(op) { case '+': case TOK_ADDC1: /* add with carry generation */ opc = 0; gen_op8: if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) { /* constant case */ vswap(); r = gv(RC_INT); vswap(); c = vtop->c.i; if (c == (char)c) { /* generate inc and dec for smaller code */ if (c==1 && opc==0 && op != TOK_ADDC1) { o (0x40 | r); // inc } else if (c==1 && opc==5 && op != TOK_SUBC1) { o (0x48 | r); // dec } else { o(0x83); o(0xc0 | (opc << 3) | r); g(c); } } else { o(0x81); oad(0xc0 | (opc << 3) | r, c); } } else { gv2(RC_INT, RC_INT); r = vtop[-1].r; fr = vtop[0].r; o((opc << 3) | 0x01); o(0xc0 + r + fr * 8); } vtop--; if (op >= TOK_ULT && op <= TOK_GT) { vtop->r = VT_CMP; vtop->c.i = op; } break; case '-': case TOK_SUBC1: /* sub with carry generation */ opc = 5; goto gen_op8; case TOK_ADDC2: /* add with carry use */ opc = 2; goto gen_op8; case TOK_SUBC2: /* sub with carry use */ opc = 3; goto gen_op8; case '&': opc = 4; goto gen_op8; case '^': opc = 6; goto gen_op8; case '|': opc = 1; goto gen_op8; case '*': gv2(RC_INT, RC_INT); r = vtop[-1].r; fr = vtop[0].r; vtop--; o(0xaf0f); /* imul fr, r */ o(0xc0 + fr + r * 8); break; case TOK_SHL: opc = 4; goto gen_shift; case TOK_SHR: opc = 5; goto gen_shift; case TOK_SAR: opc = 7; gen_shift: opc = 0xc0 | (opc << 3); if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) { /* constant case */ vswap(); r = gv(RC_INT); vswap(); c = vtop->c.i & 0x1f; o(0xc1); /* shl/shr/sar $xxx, r */ o(opc | r); g(c); } else { /* we generate the shift in ecx */ gv2(RC_INT, RC_ECX); r = vtop[-1].r; o(0xd3); /* shl/shr/sar %cl, r */ o(opc | r); } vtop--; break; case '/': case TOK_UDIV: case TOK_PDIV: case '%': case TOK_UMOD: case TOK_UMULL: /* first operand must be in eax */ /* XXX: need better constraint for second operand */ gv2(RC_EAX, RC_ECX); r = vtop[-1].r; fr = vtop[0].r; vtop--; save_reg(TREG_EDX); /* save EAX too if used otherwise */ save_reg_upstack(TREG_EAX, 1); if (op == TOK_UMULL) { o(0xf7); /* mul fr */ o(0xe0 + fr); vtop->r2 = TREG_EDX; r = TREG_EAX; } else { if (op == TOK_UDIV || op == TOK_UMOD) { o(0xf7d231); /* xor %edx, %edx, div fr, %eax */ o(0xf0 + fr); } else { o(0xf799); /* cltd, idiv fr, %eax */ o(0xf8 + fr); } if (op == '%' || op == TOK_UMOD) r = TREG_EDX; else r = TREG_EAX; } vtop->r = r; break; default: opc = 7; goto gen_op8; } } /* generate a floating point operation 'v = t1 op t2' instruction. The two operands are guaranteed to have the same floating point type */ /* XXX: need to use ST1 too */ ST_FUNC void gen_opf(int op) { int a, ft, fc, swapped, r; /* convert constants to memory references */ if ((vtop[-1].r & (VT_VALMASK | VT_LVAL)) == VT_CONST) { vswap(); gv(RC_FLOAT); vswap(); } if ((vtop[0].r & (VT_VALMASK | VT_LVAL)) == VT_CONST) gv(RC_FLOAT); /* must put at least one value in the floating point register */ if ((vtop[-1].r & VT_LVAL) && (vtop[0].r & VT_LVAL)) { vswap(); gv(RC_FLOAT); vswap(); } swapped = 0; /* swap the stack if needed so that t1 is the register and t2 is the memory reference */ if (vtop[-1].r & VT_LVAL) { vswap(); swapped = 1; } if (op >= TOK_ULT && op <= TOK_GT) { /* load on stack second operand */ load(TREG_ST0, vtop); save_reg(TREG_EAX); /* eax is used by FP comparison code */ if (op == TOK_GE || op == TOK_GT) swapped = !swapped; else if (op == TOK_EQ || op == TOK_NE) swapped = 0; if (swapped) o(0xc9d9); /* fxch %st(1) */ if (op == TOK_EQ || op == TOK_NE) o(0xe9da); /* fucompp */ else o(0xd9de); /* fcompp */ o(0xe0df); /* fnstsw %ax */ if (op == TOK_EQ) { o(0x45e480); /* and $0x45, %ah */ o(0x40fC80); /* cmp $0x40, %ah */ } else if (op == TOK_NE) { o(0x45e480); /* and $0x45, %ah */ o(0x40f480); /* xor $0x40, %ah */ op = TOK_NE; } else if (op == TOK_GE || op == TOK_LE) { o(0x05c4f6); /* test $0x05, %ah */ op = TOK_EQ; } else { o(0x45c4f6); /* test $0x45, %ah */ op = TOK_EQ; } vtop--; vtop->r = VT_CMP; vtop->c.i = op; } else { /* no memory reference possible for long double operations */ if ((vtop->type.t & VT_BTYPE) == VT_LDOUBLE) { load(TREG_ST0, vtop); swapped = !swapped; } switch(op) { default: case '+': a = 0; break; case '-': a = 4; if (swapped) a++; break; case '*': a = 1; break; case '/': a = 6; if (swapped) a++; break; } ft = vtop->type.t; fc = vtop->c.i; if ((ft & VT_BTYPE) == VT_LDOUBLE) { o(0xde); /* fxxxp %st, %st(1) */ o(0xc1 + (a << 3)); } else { /* if saved lvalue, then we must reload it */ r = vtop->r; if ((r & VT_VALMASK) == VT_LLOCAL) { SValue v1; r = get_reg(RC_INT); v1.type.t = VT_INT; v1.r = VT_LOCAL | VT_LVAL; v1.c.i = fc; load(r, &v1); fc = 0; } if ((ft & VT_BTYPE) == VT_DOUBLE) o(0xdc); else o(0xd8); gen_modrm(a, r, vtop->sym, fc); } vtop--; } } /* convert integers to fp 't' type. Must handle 'int', 'unsigned int' and 'long long' cases. */ ST_FUNC void gen_cvt_itof(int t) { save_reg(TREG_ST0); gv(RC_INT); if ((vtop->type.t & VT_BTYPE) == VT_LLONG) { /* signed long long to float/double/long double (unsigned case is handled generically) */ o(0x50 + vtop->r2); /* push r2 */ o(0x50 + (vtop->r & VT_VALMASK)); /* push r */ o(0x242cdf); /* fildll (%esp) */ o(0x08c483); /* add $8, %esp */ } else if ((vtop->type.t & (VT_BTYPE | VT_UNSIGNED)) == (VT_INT | VT_UNSIGNED)) { /* unsigned int to float/double/long double */ o(0x6a); /* push $0 */ g(0x00); o(0x50 + (vtop->r & VT_VALMASK)); /* push r */ o(0x242cdf); /* fildll (%esp) */ o(0x08c483); /* add $8, %esp */ } else { /* int to float/double/long double */ o(0x50 + (vtop->r & VT_VALMASK)); /* push r */ o(0x2404db); /* fildl (%esp) */ o(0x04c483); /* add $4, %esp */ } vtop->r = TREG_ST0; } /* convert fp to int 't' type */ ST_FUNC void gen_cvt_ftoi(int t) { #if HAVE_FLOAT int bt = vtop->type.t & VT_BTYPE; if (bt == VT_FLOAT) vpush_global_sym(&func_old_type, TOK___fixsfdi); else if (bt == VT_LDOUBLE) vpush_global_sym(&func_old_type, TOK___fixxfdi); else vpush_global_sym(&func_old_type, TOK___fixdfdi); vswap(); gfunc_call(1); vpushi(0); vtop->r = REG_IRET; vtop->r2 = REG_LRET; #endif // HAVE_FLOAT } /* convert from one floating point type to another */ ST_FUNC void gen_cvt_ftof(int t) { /* all we have to do on i386 is to put the float in a register */ gv(RC_FLOAT); } /* computed goto support */ ST_FUNC void ggoto(void) { gcall_or_jmp(1); vtop--; } /* bound check support functions */ #ifdef CONFIG_TCC_BCHECK /* generate a bounded pointer addition */ ST_FUNC void gen_bounded_ptr_add(void) { /* prepare fast i386 function call (args in eax and edx) */ gv2(RC_EAX, RC_EDX); /* save all temporary registers */ vtop -= 2; save_regs(0); /* do a fast function call */ gen_static_call(TOK___bound_ptr_add); /* returned pointer is in eax */ vtop++; vtop->r = TREG_EAX | VT_BOUNDED; /* address of bounding function call point */ vtop->c.i = (cur_text_section->reloc->data_offset - sizeof(Elf32_Rel)); } /* patch pointer addition in vtop so that pointer dereferencing is also tested */ ST_FUNC void gen_bounded_ptr_deref(void) { addr_t func; int size, align; Elf32_Rel *rel; Sym *sym; size = 0; /* XXX: put that code in generic part of tcc */ if (!is_float(vtop->type.t)) { if (vtop->r & VT_LVAL_BYTE) size = 1; else if (vtop->r & VT_LVAL_SHORT) size = 2; } if (!size) size = type_size(&vtop->type, &align); switch(size) { case 1: func = TOK___bound_ptr_indir1; break; case 2: func = TOK___bound_ptr_indir2; break; case 4: func = TOK___bound_ptr_indir4; break; case 8: func = TOK___bound_ptr_indir8; break; case 12: func = TOK___bound_ptr_indir12; break; case 16: func = TOK___bound_ptr_indir16; break; default: tcc_error("unhandled size when dereferencing bounded pointer"); func = 0; break; } /* patch relocation */ /* XXX: find a better solution ? */ rel = (Elf32_Rel *)(cur_text_section->reloc->data + vtop->c.i); sym = external_global_sym(func, &func_old_type, 0); if (!sym->c) put_extern_sym(sym, NULL, 0, 0); rel->r_info = ELF32_R_INFO(sym->c, ELF32_R_TYPE(rel->r_info)); } #endif /* Save the stack pointer onto the stack */ ST_FUNC void gen_vla_sp_save(int addr) { /* mov %esp,addr(%ebp)*/ o(0x89); gen_modrm(TREG_ESP, VT_LOCAL, NULL, addr); } /* Restore the SP from a location on the stack */ ST_FUNC void gen_vla_sp_restore(int addr) { o(0x8b); gen_modrm(TREG_ESP, VT_LOCAL, NULL, addr); } /* Subtract from the stack pointer, and push the resulting value onto the stack */ ST_FUNC void gen_vla_alloc(CType *type, int align) { #ifdef TCC_TARGET_PE /* alloca does more than just adjust %rsp on Windows */ vpush_global_sym(&func_old_type, TOK_alloca); vswap(); /* Move alloca ref past allocation size */ gfunc_call(1); #else int r; r = gv(RC_INT); /* allocation size */ /* sub r,%rsp */ o(0x2b); o(0xe0 | r); /* We align to 16 bytes rather than align */ /* and ~15, %esp */ o(0xf0e483); vpop(); #endif } /* end of X86 code generator */ /*************************************************************/ #endif /*************************************************************/
#ifdef TARGET_DEFS_ONLY #define EM_TCC_TARGET EM_386 /* relocation type for 32 bit data relocation */ #define R_DATA_32 R_386_32 #define R_DATA_PTR R_386_32 #define R_JMP_SLOT R_386_JMP_SLOT #define R_GLOB_DAT R_386_GLOB_DAT #define R_COPY R_386_COPY #define R_RELATIVE R_386_RELATIVE #define R_NUM R_386_NUM #define ELF_START_ADDR 0x08048000 #define ELF_PAGE_SIZE 0x1000 #define PCRELATIVE_DLLPLT 0 #define RELOCATE_DLLPLT 0 #else /* !TARGET_DEFS_ONLY */ #ifndef _TCC_H #include "tcc.h" #endif /* Returns 1 for a code relocation, 0 for a data relocation. For unknown relocations, returns -1. */ int code_reloc (int reloc_type) { switch (reloc_type) { case R_386_RELATIVE: case R_386_16: case R_386_32: case R_386_GOTPC: case R_386_GOTOFF: case R_386_GOT32: case R_386_GOT32X: case R_386_GLOB_DAT: case R_386_COPY: return 0; case R_386_PC16: case R_386_PC32: case R_386_PLT32: case R_386_JMP_SLOT: return 1; } tcc_error ("Unknown relocation type: %d", reloc_type); return -1; } /* Returns an enumerator to describe whether and when the relocation needs a GOT and/or PLT entry to be created. See tcc.h for a description of the different values. */ int gotplt_entry_type (int reloc_type) { switch (reloc_type) { case R_386_RELATIVE: case R_386_16: case R_386_GLOB_DAT: case R_386_JMP_SLOT: case R_386_COPY: return NO_GOTPLT_ENTRY; case R_386_32: /* This relocations shouldn't normally need GOT or PLT slots if it weren't for simplicity in the code generator. See our caller for comments. */ return AUTO_GOTPLT_ENTRY; case R_386_PC16: case R_386_PC32: return AUTO_GOTPLT_ENTRY; case R_386_GOTPC: case R_386_GOTOFF: return BUILD_GOT_ONLY; case R_386_GOT32: case R_386_GOT32X: case R_386_PLT32: return ALWAYS_GOTPLT_ENTRY; } tcc_error ("Unknown relocation type: %d", reloc_type); return -1; } ST_FUNC unsigned create_plt_entry(TCCState *s1, unsigned got_offset, struct sym_attr *attr) { Section *plt = s1->plt; uint8_t *p; int modrm; unsigned plt_offset, relofs; /* on i386 if we build a DLL, we add a %ebx offset */ if (s1->output_type == TCC_OUTPUT_DLL) modrm = 0xa3; else modrm = 0x25; /* empty PLT: create PLT0 entry that pushes the library identifier (GOT + PTR_SIZE) and jumps to ld.so resolution routine (GOT + 2 * PTR_SIZE) */ if (plt->data_offset == 0) { p = section_ptr_add(plt, 16); p[0] = 0xff; /* pushl got + PTR_SIZE */ p[1] = modrm + 0x10; write32le(p + 2, PTR_SIZE); p[6] = 0xff; /* jmp *(got + PTR_SIZE * 2) */ p[7] = modrm; write32le(p + 8, PTR_SIZE * 2); } plt_offset = plt->data_offset; /* The PLT slot refers to the relocation entry it needs via offset. The reloc entry is created below, so its offset is the current data_offset */ relofs = s1->got->reloc ? s1->got->reloc->data_offset : 0; /* Jump to GOT entry where ld.so initially put the address of ip + 4 */ p = section_ptr_add(plt, 16); p[0] = 0xff; /* jmp *(got + x) */ p[1] = modrm; write32le(p + 2, got_offset); p[6] = 0x68; /* push $xxx */ write32le(p + 7, relofs); p[11] = 0xe9; /* jmp plt_start */ write32le(p + 12, -(plt->data_offset)); return plt_offset; } /* relocate the PLT: compute addresses and offsets in the PLT now that final address for PLT and GOT are known (see fill_program_header) */ ST_FUNC void relocate_plt(TCCState *s1) { uint8_t *p, *p_end; if (!s1->plt) return; p = s1->plt->data; p_end = p + s1->plt->data_offset; if (p < p_end) { add32le(p + 2, s1->got->sh_addr); add32le(p + 8, s1->got->sh_addr); p += 16; while (p < p_end) { add32le(p + 2, s1->got->sh_addr); p += 16; } } } static ElfW_Rel *qrel; /* ptr to next reloc entry reused */ void relocate_init(Section *sr) { qrel = (ElfW_Rel *) sr->data; } void relocate(TCCState *s1, ElfW_Rel *rel, int type, unsigned char *ptr, addr_t addr, addr_t val) { int sym_index, esym_index; sym_index = ELFW(R_SYM)(rel->r_info); switch (type) { case R_386_32: if (s1->output_type == TCC_OUTPUT_DLL) { esym_index = s1->sym_attrs[sym_index].dyn_index; qrel->r_offset = rel->r_offset; if (esym_index) { qrel->r_info = ELFW(R_INFO)(esym_index, R_386_32); qrel++; return; } else { qrel->r_info = ELFW(R_INFO)(0, R_386_RELATIVE); qrel++; } } add32le(ptr, val); return; case R_386_PC32: if (s1->output_type == TCC_OUTPUT_DLL) { /* DLL relocation */ esym_index = s1->sym_attrs[sym_index].dyn_index; if (esym_index) { qrel->r_offset = rel->r_offset; qrel->r_info = ELFW(R_INFO)(esym_index, R_386_PC32); qrel++; return; } } add32le(ptr, val - addr); return; case R_386_PLT32: add32le(ptr, val - addr); return; case R_386_GLOB_DAT: case R_386_JMP_SLOT: write32le(ptr, val); return; case R_386_GOTPC: add32le(ptr, s1->got->sh_addr - addr); return; case R_386_GOTOFF: add32le(ptr, val - s1->got->sh_addr); return; case R_386_GOT32: case R_386_GOT32X: /* we load the got offset */ add32le(ptr, s1->sym_attrs[sym_index].got_offset); return; case R_386_16: if (s1->output_format != TCC_OUTPUT_FORMAT_BINARY) { output_file: tcc_error("can only produce 16-bit binary files"); } write16le(ptr, read16le(ptr) + val); return; case R_386_PC16: if (s1->output_format != TCC_OUTPUT_FORMAT_BINARY) goto output_file; write16le(ptr, read16le(ptr) + val - addr); return; case R_386_RELATIVE: /* do nothing */ return; case R_386_COPY: /* This relocation must copy initialized data from the library to the program .bss segment. Currently made like for ARM (to remove noise of default case). Is this true? */ return; default: fprintf(stderr,"FIXME: handle reloc type %d at %x [%p] to %x\n", type, (unsigned)addr, ptr, (unsigned)val); return; } } #endif /* !TARGET_DEFS_ONLY */
/* * i386 specific functions for TCC assembler * * Copyright (c) 2001, 2002 Fabrice Bellard * Copyright (c) 2009 Frédéric Feret (x86_64 support) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _TCC_H #include "tcc.h" #endif #define MAX_OPERANDS 3 #define TOK_ASM_first TOK_ASM_clc #define TOK_ASM_last TOK_ASM_emms #define TOK_ASM_alllast TOK_ASM_subps #define OPC_B 0x01 /* only used with OPC_WL */ #define OPC_WL 0x02 /* accepts w, l or no suffix */ #define OPC_BWL (OPC_B | OPC_WL) /* accepts b, w, l or no suffix */ #define OPC_REG 0x04 /* register is added to opcode */ #define OPC_MODRM 0x08 /* modrm encoding */ #define OPCT_MASK 0x70 #define OPC_FWAIT 0x10 /* add fwait opcode */ #define OPC_SHIFT 0x20 /* shift opcodes */ #define OPC_ARITH 0x30 /* arithmetic opcodes */ #define OPC_FARITH 0x40 /* FPU arithmetic opcodes */ #define OPC_TEST 0x50 /* test opcodes */ #define OPCT_IS(v,i) (((v) & OPCT_MASK) == (i)) #define OPC_0F 0x100 /* Is secondary map (0x0f prefix) */ #define OPC_48 0x200 /* Always has REX prefix */ #ifdef TCC_TARGET_X86_64 # define OPC_WLQ 0x1000 /* accepts w, l, q or no suffix */ # define OPC_BWLQ (OPC_B | OPC_WLQ) /* accepts b, w, l, q or no suffix */ # define OPC_WLX OPC_WLQ # define OPC_BWLX OPC_BWLQ #else # define OPC_WLX OPC_WL # define OPC_BWLX OPC_BWL #endif #define OPC_GROUP_SHIFT 13 /* in order to compress the operand type, we use specific operands and we or only with EA */ enum { OPT_REG8=0, /* warning: value is hardcoded from TOK_ASM_xxx */ OPT_REG16, /* warning: value is hardcoded from TOK_ASM_xxx */ OPT_REG32, /* warning: value is hardcoded from TOK_ASM_xxx */ #ifdef TCC_TARGET_X86_64 OPT_REG64, /* warning: value is hardcoded from TOK_ASM_xxx */ #endif OPT_MMX, /* warning: value is hardcoded from TOK_ASM_xxx */ OPT_SSE, /* warning: value is hardcoded from TOK_ASM_xxx */ OPT_CR, /* warning: value is hardcoded from TOK_ASM_xxx */ OPT_TR, /* warning: value is hardcoded from TOK_ASM_xxx */ OPT_DB, /* warning: value is hardcoded from TOK_ASM_xxx */ OPT_SEG, OPT_ST, #ifdef TCC_TARGET_X86_64 OPT_REG8_LOW, /* %spl,%bpl,%sil,%dil, encoded like ah,ch,dh,bh, but with REX prefix, not used in insn templates */ #endif OPT_IM8, OPT_IM8S, OPT_IM16, OPT_IM32, #ifdef TCC_TARGET_X86_64 OPT_IM64, #endif OPT_EAX, /* %al, %ax, %eax or %rax register */ OPT_ST0, /* %st(0) register */ OPT_CL, /* %cl register */ OPT_DX, /* %dx register */ OPT_ADDR, /* OP_EA with only offset */ OPT_INDIR, /* *(expr) */ /* composite types */ OPT_COMPOSITE_FIRST, OPT_IM, /* IM8 | IM16 | IM32 */ OPT_REG, /* REG8 | REG16 | REG32 | REG64 */ OPT_REGW, /* REG16 | REG32 | REG64 */ OPT_IMW, /* IM16 | IM32 */ OPT_MMXSSE, /* MMX | SSE */ OPT_DISP, /* Like OPT_ADDR, but emitted as displacement (for jumps) */ OPT_DISP8, /* Like OPT_ADDR, but only 8bit (short jumps) */ /* can be ored with any OPT_xxx */ OPT_EA = 0x80 }; #define OP_REG8 (1 << OPT_REG8) #define OP_REG16 (1 << OPT_REG16) #define OP_REG32 (1 << OPT_REG32) #define OP_MMX (1 << OPT_MMX) #define OP_SSE (1 << OPT_SSE) #define OP_CR (1 << OPT_CR) #define OP_TR (1 << OPT_TR) #define OP_DB (1 << OPT_DB) #define OP_SEG (1 << OPT_SEG) #define OP_ST (1 << OPT_ST) #define OP_IM8 (1 << OPT_IM8) #define OP_IM8S (1 << OPT_IM8S) #define OP_IM16 (1 << OPT_IM16) #define OP_IM32 (1 << OPT_IM32) #define OP_EAX (1 << OPT_EAX) #define OP_ST0 (1 << OPT_ST0) #define OP_CL (1 << OPT_CL) #define OP_DX (1 << OPT_DX) #define OP_ADDR (1 << OPT_ADDR) #define OP_INDIR (1 << OPT_INDIR) #ifdef TCC_TARGET_X86_64 # define OP_REG64 (1 << OPT_REG64) # define OP_REG8_LOW (1 << OPT_REG8_LOW) # define OP_IM64 (1 << OPT_IM64) # define OP_EA32 (OP_EA << 1) #else # define OP_REG64 0 # define OP_REG8_LOW 0 # define OP_IM64 0 # define OP_EA32 0 #endif #define OP_EA 0x40000000 #define OP_REG (OP_REG8 | OP_REG16 | OP_REG32 | OP_REG64) #ifdef TCC_TARGET_X86_64 # define TREG_XAX TREG_RAX # define TREG_XCX TREG_RCX # define TREG_XDX TREG_RDX #else # define TREG_XAX TREG_EAX # define TREG_XCX TREG_ECX # define TREG_XDX TREG_EDX #endif typedef struct ASMInstr { uint16_t sym; uint16_t opcode; uint16_t instr_type; uint8_t nb_ops; uint8_t op_type[MAX_OPERANDS]; /* see OP_xxx */ } ASMInstr; typedef struct Operand { uint32_t type; int8_t reg; /* register, -1 if none */ int8_t reg2; /* second register, -1 if none */ uint8_t shift; ExprValue e; } Operand; static const uint8_t reg_to_size[9] = { /* [OP_REG8] = 0, [OP_REG16] = 1, [OP_REG32] = 2, #ifdef TCC_TARGET_X86_64 [OP_REG64] = 3, #endif */ 0, 0, 1, 0, 2, 0, 0, 0, 3 }; #define NB_TEST_OPCODES 30 static const uint8_t test_bits[NB_TEST_OPCODES] = { 0x00, /* o */ 0x01, /* no */ 0x02, /* b */ 0x02, /* c */ 0x02, /* nae */ 0x03, /* nb */ 0x03, /* nc */ 0x03, /* ae */ 0x04, /* e */ 0x04, /* z */ 0x05, /* ne */ 0x05, /* nz */ 0x06, /* be */ 0x06, /* na */ 0x07, /* nbe */ 0x07, /* a */ 0x08, /* s */ 0x09, /* ns */ 0x0a, /* p */ 0x0a, /* pe */ 0x0b, /* np */ 0x0b, /* po */ 0x0c, /* l */ 0x0c, /* nge */ 0x0d, /* nl */ 0x0d, /* ge */ 0x0e, /* le */ 0x0e, /* ng */ 0x0f, /* nle */ 0x0f, /* g */ }; static const uint8_t segment_prefixes[] = { 0x26, /* es */ 0x2e, /* cs */ 0x36, /* ss */ 0x3e, /* ds */ 0x64, /* fs */ 0x65 /* gs */ }; static const ASMInstr asm_instrs[] = { #define ALT(x) x /* This removes a 0x0f in the second byte */ #if HAVE_LONG_LONG #define O(o) ((uint64_t) ((((o) & 0xff00) == 0x0f00) ? ((((o) >> 8) & ~0xff) | ((o) & 0xff)) : (o))) #else #define O(o) ((uint32_t) ((((o) & 0xff00) == 0x0f00) ? ((((o) >> 8) & ~0xff) | ((o) & 0xff)) : (o))) #endif /* This constructs instr_type from opcode, type and group. */ #define T(o,i,g) ((i) | ((g) << OPC_GROUP_SHIFT) | ((((o) & 0xff00) == 0x0f00) ? OPC_0F : 0)) #define DEF_ASM_OP0(name, opcode) #define DEF_ASM_OP0L(name, opcode, group, instr_type) { TOK_ASM_ ## name, O(opcode), T(opcode, instr_type, group), 0, { 0 } }, #define DEF_ASM_OP1(name, opcode, group, instr_type, op0) { TOK_ASM_ ## name, O(opcode), T(opcode, instr_type, group), 1, { op0 }}, #define DEF_ASM_OP2(name, opcode, group, instr_type, op0, op1) { TOK_ASM_ ## name, O(opcode), T(opcode, instr_type, group), 2, { op0, op1 }}, #define DEF_ASM_OP3(name, opcode, group, instr_type, op0, op1, op2) { TOK_ASM_ ## name, O(opcode), T(opcode, instr_type, group), 3, { op0, op1, op2 }}, #ifdef TCC_TARGET_X86_64 # include "x86_64-asm.h" #else # include "i386-asm.h" #endif /* last operation */ { 0, }, }; static const uint16_t op0_codes[] = { #define ALT(x) #define DEF_ASM_OP0(x, opcode) opcode, #define DEF_ASM_OP0L(name, opcode, group, instr_type) #define DEF_ASM_OP1(name, opcode, group, instr_type, op0) #define DEF_ASM_OP2(name, opcode, group, instr_type, op0, op1) #define DEF_ASM_OP3(name, opcode, group, instr_type, op0, op1, op2) #ifdef TCC_TARGET_X86_64 # include "x86_64-asm.h" #else # include "i386-asm.h" #endif }; static inline int get_reg_shift(TCCState *s1) { int shift, v; v = asm_int_expr(s1); switch(v) { case 1: shift = 0; break; case 2: shift = 1; break; case 4: shift = 2; break; case 8: shift = 3; break; default: expect("1, 2, 4 or 8 constant"); shift = 0; break; } return shift; } #ifdef TCC_TARGET_X86_64 static int asm_parse_numeric_reg(int t, unsigned int *type) { int reg = -1; if (t >= TOK_IDENT && t < tok_ident) { const char *s = table_ident[t - TOK_IDENT]->str; char c; *type = OP_REG64; if (*s == 'c') { s++; *type = OP_CR; } if (*s++ != 'r') return -1; /* Don't allow leading '0'. */ if ((c = *s++) >= '1' && c <= '9') reg = c - '0'; else return -1; if ((c = *s) >= '0' && c <= '5') s++, reg = reg * 10 + c - '0'; if (reg > 15) return -1; if ((c = *s) == 0) ; else if (*type != OP_REG64) return -1; else if (c == 'b' && !s[1]) *type = OP_REG8; else if (c == 'w' && !s[1]) *type = OP_REG16; else if (c == 'd' && !s[1]) *type = OP_REG32; else return -1; } return reg; } #endif static int asm_parse_reg(unsigned int *type) { int reg = 0; *type = 0; if (tok != '%') goto error_32; next(); if (tok >= TOK_ASM_eax && tok <= TOK_ASM_edi) { reg = tok - TOK_ASM_eax; *type = OP_REG32; #ifdef TCC_TARGET_X86_64 } else if (tok >= TOK_ASM_rax && tok <= TOK_ASM_rdi) { reg = tok - TOK_ASM_rax; *type = OP_REG64; } else if (tok == TOK_ASM_rip) { reg = -2; /* Probably should use different escape code. */ *type = OP_REG64; } else if ((reg = asm_parse_numeric_reg(tok, type)) >= 0 && (*type == OP_REG32 || *type == OP_REG64)) { ; #endif } else { error_32: expect("register"); } next(); return reg; } static void parse_operand(TCCState *s1, Operand *op) { ExprValue e; int reg, indir; const char *p; indir = 0; if (tok == '*') { next(); indir = OP_INDIR; } if (tok == '%') { next(); if (tok >= TOK_ASM_al && tok <= TOK_ASM_db7) { reg = tok - TOK_ASM_al; op->type = 1 << (reg >> 3); /* WARNING: do not change constant order */ op->reg = reg & 7; if ((op->type & OP_REG) && op->reg == TREG_XAX) op->type |= OP_EAX; else if (op->type == OP_REG8 && op->reg == TREG_XCX) op->type |= OP_CL; else if (op->type == OP_REG16 && op->reg == TREG_XDX) op->type |= OP_DX; } else if (tok >= TOK_ASM_dr0 && tok <= TOK_ASM_dr7) { op->type = OP_DB; op->reg = tok - TOK_ASM_dr0; } else if (tok >= TOK_ASM_es && tok <= TOK_ASM_gs) { op->type = OP_SEG; op->reg = tok - TOK_ASM_es; } else if (tok == TOK_ASM_st) { op->type = OP_ST; op->reg = 0; next(); if (tok == '(') { next(); if (tok != TOK_PPNUM) goto reg_error; p = tokc.str.data; reg = p[0] - '0'; if ((unsigned)reg >= 8 || p[1] != '\0') goto reg_error; op->reg = reg; next(); skip(')'); } if (op->reg == 0) op->type |= OP_ST0; goto no_skip; #ifdef TCC_TARGET_X86_64 } else if (tok >= TOK_ASM_spl && tok <= TOK_ASM_dil) { op->type = OP_REG8 | OP_REG8_LOW; op->reg = 4 + tok - TOK_ASM_spl; } else if ((op->reg = asm_parse_numeric_reg(tok, &op->type)) >= 0) { ; #endif } else { reg_error: tcc_error("unknown register %%%s", get_tok_str(tok, &tokc)); } next(); no_skip: ; } else if (tok == '$') { /* constant value */ next(); asm_expr(s1, &e); op->type = OP_IM32; op->e = e; if (!op->e.sym) { if (op->e.v == (uint8_t)op->e.v) op->type |= OP_IM8; if (op->e.v == (int8_t)op->e.v) op->type |= OP_IM8S; if (op->e.v == (uint16_t)op->e.v) op->type |= OP_IM16; #ifdef TCC_TARGET_X86_64 if (op->e.v != (int32_t)op->e.v && op->e.v != (uint32_t)op->e.v) op->type = OP_IM64; #endif } } else { /* address(reg,reg2,shift) with all variants */ op->type = OP_EA; op->reg = -1; op->reg2 = -1; op->shift = 0; if (tok != '(') { asm_expr(s1, &e); op->e = e; } else { next(); if (tok == '%') { unget_tok('('); op->e.v = 0; op->e.sym = NULL; } else { /* bracketed offset expression */ asm_expr(s1, &e); if (tok != ')') expect(")"); next(); op->e.v = e.v; op->e.sym = e.sym; } op->e.pcrel = 0; } if (tok == '(') { unsigned int type = 0; next(); if (tok != ',') { op->reg = asm_parse_reg(&type); } if (tok == ',') { next(); if (tok != ',') { op->reg2 = asm_parse_reg(&type); } if (tok == ',') { next(); op->shift = get_reg_shift(s1); } } if (type & OP_REG32) op->type |= OP_EA32; skip(')'); } if (op->reg == -1 && op->reg2 == -1) op->type |= OP_ADDR; } op->type |= indir; } /* XXX: unify with C code output ? */ ST_FUNC void gen_expr32(ExprValue *pe) { if (pe->pcrel) /* If PC-relative, always set VT_SYM, even without symbol, so as to force a relocation to be emitted. */ gen_addrpc32(VT_SYM, pe->sym, pe->v); else gen_addr32(pe->sym ? VT_SYM : 0, pe->sym, pe->v); } #ifdef TCC_TARGET_X86_64 ST_FUNC void gen_expr64(ExprValue *pe) { gen_addr64(pe->sym ? VT_SYM : 0, pe->sym, pe->v); } #endif /* XXX: unify with C code output ? */ static void gen_disp32(ExprValue *pe) { Sym *sym = pe->sym; if (sym && sym->r == cur_text_section->sh_num) { /* same section: we can output an absolute value. Note that the TCC compiler behaves differently here because it always outputs a relocation to ease (future) code elimination in the linker */ gen_le32(pe->v + sym->jnext - ind - 4); } else { if (sym && sym->type.t == VT_VOID) { sym->type.t = VT_FUNC; sym->type.ref = NULL; } gen_addrpc32(VT_SYM, sym, pe->v); } } /* generate the modrm operand */ static inline int asm_modrm(int reg, Operand *op) { int mod, reg1, reg2, sib_reg1; if (op->type & (OP_REG | OP_MMX | OP_SSE)) { g(0xc0 + (reg << 3) + op->reg); } else if (op->reg == -1 && op->reg2 == -1) { /* displacement only */ #ifdef TCC_TARGET_X86_64 g(0x04 + (reg << 3)); g(0x25); #else g(0x05 + (reg << 3)); #endif gen_expr32(&op->e); #ifdef TCC_TARGET_X86_64 } else if (op->reg == -2) { ExprValue *pe = &op->e; g(0x05 + (reg << 3)); gen_addrpc32(pe->sym ? VT_SYM : 0, pe->sym, pe->v); return ind; #endif } else { sib_reg1 = op->reg; /* fist compute displacement encoding */ if (sib_reg1 == -1) { sib_reg1 = 5; mod = 0x00; } else if (op->e.v == 0 && !op->e.sym && op->reg != 5) { mod = 0x00; } else if (op->e.v == (int8_t)op->e.v && !op->e.sym) { mod = 0x40; } else { mod = 0x80; } /* compute if sib byte needed */ reg1 = op->reg; if (op->reg2 != -1) reg1 = 4; g(mod + (reg << 3) + reg1); if (reg1 == 4) { /* add sib byte */ reg2 = op->reg2; if (reg2 == -1) reg2 = 4; /* indicate no index */ g((op->shift << 6) + (reg2 << 3) + sib_reg1); } /* add offset */ if (mod == 0x40) { g(op->e.v); } else if (mod == 0x80 || op->reg == -1) { gen_expr32(&op->e); } } return 0; } #ifdef TCC_TARGET_X86_64 #define REX_W 0x48 #define REX_R 0x44 #define REX_X 0x42 #define REX_B 0x41 static void asm_rex(int width64, Operand *ops, int nb_ops, int *op_type, int regi, int rmi) { unsigned char rex = width64 ? 0x48 : 0; int saw_high_8bit = 0; int i; if (rmi == -1) { /* No mod/rm byte, but we might have a register op nevertheless (we will add it to the opcode later). */ for(i = 0; i < nb_ops; i++) { if (op_type[i] & (OP_REG | OP_ST)) { if (ops[i].reg >= 8) { rex |= REX_B; ops[i].reg -= 8; } else if (ops[i].type & OP_REG8_LOW) rex |= 0x40; else if (ops[i].type & OP_REG8 && ops[i].reg >= 4) /* An 8 bit reg >= 4 without REG8 is ah/ch/dh/bh */ saw_high_8bit = ops[i].reg; break; } } } else { if (regi != -1) { if (ops[regi].reg >= 8) { rex |= REX_R; ops[regi].reg -= 8; } else if (ops[regi].type & OP_REG8_LOW) rex |= 0x40; else if (ops[regi].type & OP_REG8 && ops[regi].reg >= 4) /* An 8 bit reg >= 4 without REG8 is ah/ch/dh/bh */ saw_high_8bit = ops[regi].reg; } if (ops[rmi].type & (OP_REG | OP_MMX | OP_SSE | OP_CR | OP_EA)) { if (ops[rmi].reg >= 8) { rex |= REX_B; ops[rmi].reg -= 8; } else if (ops[rmi].type & OP_REG8_LOW) rex |= 0x40; else if (ops[rmi].type & OP_REG8 && ops[rmi].reg >= 4) /* An 8 bit reg >= 4 without REG8 is ah/ch/dh/bh */ saw_high_8bit = ops[rmi].reg; } if (ops[rmi].type & OP_EA && ops[rmi].reg2 >= 8) { rex |= REX_X; ops[rmi].reg2 -= 8; } } if (rex) { if (saw_high_8bit) tcc_error("can't encode register %%%ch when REX prefix is required", "acdb"[saw_high_8bit-4]); g(rex); } } #endif static void maybe_print_stats (void) { static int already = 1; if (!already) /* print stats about opcodes */ { const struct ASMInstr *pa; int freq[4]; int op_vals[500]; int nb_op_vals, i, j; already = 1; nb_op_vals = 0; memset(freq, 0, sizeof(freq)); for(pa = asm_instrs; pa->sym != 0; pa++) { freq[pa->nb_ops]++; //for(i=0;i<pa->nb_ops;i++) { for(j=0;j<nb_op_vals;j++) { //if (pa->op_type[i] == op_vals[j]) if (pa->instr_type == op_vals[j]) goto found; } //op_vals[nb_op_vals++] = pa->op_type[i]; op_vals[nb_op_vals++] = pa->instr_type; found: ; //} } for(i=0;i<nb_op_vals;i++) { int v = op_vals[i]; //if ((v & (v - 1)) != 0) printf("%3d: %08x\n", i, v); } printf("size=%d nb=%d f0=%d f1=%d f2=%d f3=%d\n", (int)sizeof(asm_instrs), (int)sizeof(asm_instrs) / (int)sizeof(ASMInstr), freq[0], freq[1], freq[2], freq[3]); } } ST_FUNC void asm_opcode(TCCState *s1, int opcode) { const ASMInstr *pa; int i, modrm_index, modreg_index, reg, v, op1, seg_prefix, pc; int nb_ops, s; Operand ops[MAX_OPERANDS], *pop; int op_type[3]; /* decoded op type */ int alltypes; /* OR of all operand types */ int autosize; int p66; #ifdef TCC_TARGET_X86_64 int rex64; #endif maybe_print_stats(); /* force synthetic ';' after prefix instruction, so we can handle */ /* one-line things like "rep stosb" instead of only "rep\nstosb" */ if (opcode >= TOK_ASM_wait && opcode <= TOK_ASM_repnz) unget_tok(';'); /* get operands */ pop = ops; nb_ops = 0; seg_prefix = 0; alltypes = 0; for(;;) { if (tok == ';' || tok == TOK_LINEFEED) break; if (nb_ops >= MAX_OPERANDS) { tcc_error("incorrect number of operands"); } parse_operand(s1, pop); if (tok == ':') { if (pop->type != OP_SEG || seg_prefix) tcc_error("incorrect prefix"); seg_prefix = segment_prefixes[pop->reg]; next(); parse_operand(s1, pop); if (!(pop->type & OP_EA)) { tcc_error("segment prefix must be followed by memory reference"); } } pop++; nb_ops++; if (tok != ',') break; next(); } s = 0; /* avoid warning */ /* optimize matching by using a lookup table (no hashing is needed !) */ for(pa = asm_instrs; pa->sym != 0; pa++) { int it = pa->instr_type & OPCT_MASK; s = 0; if (it == OPC_FARITH) { v = opcode - pa->sym; if (!((unsigned)v < 8 * 6 && (v % 6) == 0)) continue; } else if (it == OPC_ARITH) { if (!(opcode >= pa->sym && opcode < pa->sym + 8*NBWLX)) continue; s = (opcode - pa->sym) % NBWLX; if ((pa->instr_type & OPC_BWLX) == OPC_WLX) { /* We need to reject the xxxb opcodes that we accepted above. Note that pa->sym for WLX opcodes is the 'w' token, to get the 'b' token subtract one. */ if (((opcode - pa->sym + 1) % NBWLX) == 0) continue; s++; } } else if (it == OPC_SHIFT) { if (!(opcode >= pa->sym && opcode < pa->sym + 7*NBWLX)) continue; s = (opcode - pa->sym) % NBWLX; } else if (it == OPC_TEST) { if (!(opcode >= pa->sym && opcode < pa->sym + NB_TEST_OPCODES)) continue; /* cmovxx is a test opcode but accepts multiple sizes. TCC doesn't accept the suffixed mnemonic, instead we simply force size autodetection always. */ if (pa->instr_type & OPC_WLX) s = NBWLX - 1; } else if (pa->instr_type & OPC_B) { #ifdef TCC_TARGET_X86_64 /* Some instructions don't have the full size but only bwl form. insb e.g. */ if ((pa->instr_type & OPC_WLQ) != OPC_WLQ && !(opcode >= pa->sym && opcode < pa->sym + NBWLX-1)) continue; #endif if (!(opcode >= pa->sym && opcode < pa->sym + NBWLX)) continue; s = opcode - pa->sym; } else if (pa->instr_type & OPC_WLX) { if (!(opcode >= pa->sym && opcode < pa->sym + NBWLX-1)) continue; s = opcode - pa->sym + 1; } else { if (pa->sym != opcode) continue; } if (pa->nb_ops != nb_ops) continue; #ifdef TCC_TARGET_X86_64 /* Special case for moves. Selecting the IM64->REG64 form should only be done if we really have an >32bit imm64, and that is hardcoded. Ignore it here. */ if (pa->opcode == 0xb0 && ops[0].type != OP_IM64 && (ops[1].type & OP_REG) == OP_REG64 && !(pa->instr_type & OPC_0F)) continue; #endif /* now decode and check each operand */ alltypes = 0; for(i = 0; i < nb_ops; i++) { int op1, op2; op1 = pa->op_type[i]; op2 = op1 & 0x1f; switch(op2) { case OPT_IM: v = OP_IM8 | OP_IM16 | OP_IM32; break; case OPT_REG: v = OP_REG8 | OP_REG16 | OP_REG32 | OP_REG64; break; case OPT_REGW: v = OP_REG16 | OP_REG32 | OP_REG64; break; case OPT_IMW: v = OP_IM16 | OP_IM32; break; case OPT_MMXSSE: v = OP_MMX | OP_SSE; break; case OPT_DISP: case OPT_DISP8: v = OP_ADDR; break; default: v = 1 << op2; break; } if (op1 & OPT_EA) v |= OP_EA; op_type[i] = v; if ((ops[i].type & v) == 0) goto next; alltypes |= ops[i].type; } /* all is matching ! */ break; next: ; } if (pa->sym == 0) { if (opcode >= TOK_ASM_first && opcode <= TOK_ASM_last) { int b; b = op0_codes[opcode - TOK_ASM_first]; if (b & 0xff00) g(b >> 8); g(b); return; } else if (opcode <= TOK_ASM_alllast) { tcc_error("bad operand with opcode '%s'", get_tok_str(opcode, NULL)); } else { tcc_error("unknown opcode '%s'", get_tok_str(opcode, NULL)); } } /* if the size is unknown, then evaluate it (OPC_B or OPC_WL case) */ autosize = NBWLX-1; #ifdef TCC_TARGET_X86_64 /* XXX the autosize should rather be zero, to not have to adjust this all the time. */ if ((pa->instr_type & OPC_BWLQ) == OPC_B) autosize = NBWLX-2; #endif if (s == autosize) { /* Check for register operands providing hints about the size. Start from the end, i.e. destination operands. This matters only for opcodes accepting different sized registers, lar and lsl are such opcodes. */ for(i = nb_ops - 1; s == autosize && i >= 0; i--) { if ((ops[i].type & OP_REG) && !(op_type[i] & (OP_CL | OP_DX))) s = reg_to_size[ops[i].type & OP_REG]; } if (s == autosize) { if ((opcode == TOK_ASM_push || opcode == TOK_ASM_pop) && (ops[0].type & (OP_SEG | OP_IM8S | OP_IM32))) s = 2; else if ((opcode == TOK_ASM_push || opcode == TOK_ASM_pop) && (ops[0].type & OP_EA)) s = NBWLX - 2; else tcc_error("cannot infer opcode suffix"); } } #ifdef TCC_TARGET_X86_64 /* Generate addr32 prefix if needed */ for(i = 0; i < nb_ops; i++) { if (ops[i].type & OP_EA32) { g(0x67); break; } } #endif /* generate data16 prefix if needed */ p66 = 0; if (s == 1) p66 = 1; else { /* accepting mmx+sse in all operands --> needs 0x66 to switch to sse mode. Accepting only sse in an operand --> is already SSE insn and needs 0x66/f2/f3 handling. */ for (i = 0; i < nb_ops; i++) if ((op_type[i] & (OP_MMX | OP_SSE)) == (OP_MMX | OP_SSE) && ops[i].type & OP_SSE) p66 = 1; } if (p66) g(0x66); #ifdef TCC_TARGET_X86_64 rex64 = 0; if (pa->instr_type & OPC_48) rex64 = 1; else if (s == 3 || (alltypes & OP_REG64)) { /* generate REX prefix */ int default64 = 0; for(i = 0; i < nb_ops; i++) { if (op_type[i] == OP_REG64 && pa->opcode != 0xb8) { /* If only 64bit regs are accepted in one operand this is a default64 instruction without need for REX prefixes, except for movabs(0xb8). */ default64 = 1; break; } } /* XXX find better encoding for the default64 instructions. */ if (((opcode != TOK_ASM_push && opcode != TOK_ASM_pop && opcode != TOK_ASM_pushw && opcode != TOK_ASM_pushl && opcode != TOK_ASM_pushq && opcode != TOK_ASM_popw && opcode != TOK_ASM_popl && opcode != TOK_ASM_popq && opcode != TOK_ASM_call && opcode != TOK_ASM_jmp)) && !default64) rex64 = 1; } #endif /* now generates the operation */ if (OPCT_IS(pa->instr_type, OPC_FWAIT)) g(0x9b); if (seg_prefix) g(seg_prefix); v = pa->opcode; if (pa->instr_type & OPC_0F) v = ((v & ~0xff) << 8) | 0x0f00 | (v & 0xff); if ((v == 0x69 || v == 0x6b) && nb_ops == 2) { /* kludge for imul $im, %reg */ nb_ops = 3; ops[2] = ops[1]; op_type[2] = op_type[1]; } else if (v == 0xcd && ops[0].e.v == 3 && !ops[0].e.sym) { v--; /* int $3 case */ nb_ops = 0; } else if ((v == 0x06 || v == 0x07)) { if (ops[0].reg >= 4) { /* push/pop %fs or %gs */ v = 0x0fa0 + (v - 0x06) + ((ops[0].reg - 4) << 3); } else { v += ops[0].reg << 3; } nb_ops = 0; } else if (v <= 0x05) { /* arith case */ v += ((opcode - TOK_ASM_addb) / NBWLX) << 3; } else if ((pa->instr_type & (OPCT_MASK | OPC_MODRM)) == OPC_FARITH) { /* fpu arith case */ v += ((opcode - pa->sym) / 6) << 3; } /* search which operand will be used for modrm */ modrm_index = -1; modreg_index = -1; if (pa->instr_type & OPC_MODRM) { if (!nb_ops) { /* A modrm opcode without operands is a special case (e.g. mfence). It has a group and acts as if there's an register operand 0 (ax). */ i = 0; ops[i].type = OP_REG; ops[i].reg = 0; goto modrm_found; } /* first look for an ea operand */ for(i = 0;i < nb_ops; i++) { if (op_type[i] & OP_EA) goto modrm_found; } /* then if not found, a register or indirection (shift instructions) */ for(i = 0;i < nb_ops; i++) { if (op_type[i] & (OP_REG | OP_MMX | OP_SSE | OP_INDIR)) goto modrm_found; } #ifdef ASM_DEBUG tcc_error("bad op table"); #endif modrm_found: modrm_index = i; /* if a register is used in another operand then it is used instead of group */ for(i = 0;i < nb_ops; i++) { int t = op_type[i]; if (i != modrm_index && (t & (OP_REG | OP_MMX | OP_SSE | OP_CR | OP_TR | OP_DB | OP_SEG))) { modreg_index = i; break; } } } #ifdef TCC_TARGET_X86_64 asm_rex (rex64, ops, nb_ops, op_type, modreg_index, modrm_index); #endif if (pa->instr_type & OPC_REG) { /* mov $im, %reg case */ if (v == 0xb0 && s >= 1) v += 7; for(i = 0; i < nb_ops; i++) { if (op_type[i] & (OP_REG | OP_ST)) { v += ops[i].reg; break; } } } if (pa->instr_type & OPC_B) v += s >= 1; if (nb_ops == 1 && pa->op_type[0] == OPT_DISP8) { Sym *sym; int jmp_disp; /* see if we can really generate the jump with a byte offset */ sym = ops[0].e.sym; if (!sym) goto no_short_jump; if (sym->r != cur_text_section->sh_num) goto no_short_jump; jmp_disp = ops[0].e.v + sym->jnext - ind - 2 - (v >= 0xff); if (jmp_disp == (int8_t)jmp_disp) { /* OK to generate jump */ ops[0].e.sym = 0; ops[0].e.v = jmp_disp; op_type[0] = OP_IM8S; } else { no_short_jump: /* long jump will be allowed. need to modify the opcode slightly */ if (v == 0xeb) /* jmp */ v = 0xe9; else if (v == 0x70) /* jcc */ v += 0x0f10; else tcc_error("invalid displacement"); } } if (OPCT_IS(pa->instr_type, OPC_TEST)) v += test_bits[opcode - pa->sym]; op1 = v >> 16; if (op1) g(op1); op1 = (v >> 8) & 0xff; if (op1) g(op1); g(v); if (OPCT_IS(pa->instr_type, OPC_SHIFT)) { reg = (opcode - pa->sym) / NBWLX; if (reg == 6) reg = 7; } else if (OPCT_IS(pa->instr_type, OPC_ARITH)) { reg = (opcode - pa->sym) / NBWLX; } else if (OPCT_IS(pa->instr_type, OPC_FARITH)) { reg = (opcode - pa->sym) / 6; } else { reg = (pa->instr_type >> OPC_GROUP_SHIFT) & 7; } pc = 0; if (pa->instr_type & OPC_MODRM) { /* if a register is used in another operand then it is used instead of group */ if (modreg_index >= 0) reg = ops[modreg_index].reg; pc = asm_modrm(reg, &ops[modrm_index]); } /* emit constants */ #ifndef TCC_TARGET_X86_64 if (!(pa->instr_type & OPC_0F) && (pa->opcode == 0x9a || pa->opcode == 0xea)) { /* ljmp or lcall kludge */ gen_expr32(&ops[1].e); if (ops[0].e.sym) tcc_error("cannot relocate"); gen_le16(ops[0].e.v); return; } #endif for(i = 0;i < nb_ops; i++) { v = op_type[i]; if (v & (OP_IM8 | OP_IM16 | OP_IM32 | OP_IM64 | OP_IM8S | OP_ADDR)) { /* if multiple sizes are given it means we must look at the op size */ if ((v | OP_IM8 | OP_IM64) == (OP_IM8 | OP_IM16 | OP_IM32 | OP_IM64)) { if (s == 0) v = OP_IM8; else if (s == 1) v = OP_IM16; else if (s == 2 || (v & OP_IM64) == 0) v = OP_IM32; else v = OP_IM64; } if ((v & (OP_IM8 | OP_IM8S | OP_IM16)) && ops[i].e.sym) tcc_error("cannot relocate"); if (v & (OP_IM8 | OP_IM8S)) { g(ops[i].e.v); } else if (v & OP_IM16) { gen_le16(ops[i].e.v); #ifdef TCC_TARGET_X86_64 } else if (v & OP_IM64) { gen_expr64(&ops[i].e); #endif } else if (pa->op_type[i] == OPT_DISP || pa->op_type[i] == OPT_DISP8) { gen_disp32(&ops[i].e); } else { gen_expr32(&ops[i].e); } } } /* after immediate operands, adjust pc-relative address */ if (pc) add32le(cur_text_section->data + pc - 4, pc - ind); } /* return the constraint priority (we allocate first the lowest numbered constraints) */ static inline int constraint_priority(const char *str) { int priority, c, pr; /* we take the lowest priority */ priority = 0; for(;;) { c = *str; if (c == '\0') break; str++; switch(c) { case 'A': pr = 0; break; case 'a': case 'b': case 'c': case 'd': case 'S': case 'D': pr = 1; break; case 'q': pr = 2; break; case 'r': case 'R': case 'p': pr = 3; break; case 'N': case 'M': case 'I': case 'e': case 'i': case 'm': case 'g': pr = 4; break; default: tcc_error("unknown constraint '%c'", c); pr = 0; } if (pr > priority) priority = pr; } return priority; } static const char *skip_constraint_modifiers(const char *p) { while (*p == '=' || *p == '&' || *p == '+' || *p == '%') p++; return p; } /* If T (a token) is of the form "%reg" returns the register number and type, otherwise return -1. */ ST_FUNC int asm_parse_regvar (int t) { const char *s; Operand op; if (t < TOK_IDENT) return -1; s = table_ident[t - TOK_IDENT]->str; if (s[0] != '%') return -1; t = tok_alloc(s+1, strlen(s)-1)->tok; unget_tok(t); unget_tok('%'); parse_operand(tcc_state, &op); /* Accept only integer regs for now. */ if (op.type & OP_REG) return op.reg; else return -1; } #define REG_OUT_MASK 0x01 #define REG_IN_MASK 0x02 #define is_reg_allocated(reg) (regs_allocated[reg] & reg_mask) ST_FUNC void asm_compute_constraints(ASMOperand *operands, int nb_operands, int nb_outputs, const uint8_t *clobber_regs, int *pout_reg) { ASMOperand *op; int sorted_op[MAX_ASM_OPERANDS]; int i, j, k, p1, p2, tmp, reg, c, reg_mask; const char *str; uint8_t regs_allocated[NB_ASM_REGS]; /* init fields */ for(i=0;i<nb_operands;i++) { op = &operands[i]; op->input_index = -1; op->ref_index = -1; op->reg = -1; op->is_memory = 0; op->is_rw = 0; } /* compute constraint priority and evaluate references to output constraints if input constraints */ for(i=0;i<nb_operands;i++) { op = &operands[i]; str = op->constraint; str = skip_constraint_modifiers(str); if (isnum(*str) || *str == '[') { /* this is a reference to another constraint */ k = find_constraint(operands, nb_operands, str, NULL); if ((unsigned)k >= i || i < nb_outputs) tcc_error("invalid reference in constraint %d ('%s')", i, str); op->ref_index = k; if (operands[k].input_index >= 0) tcc_error("cannot reference twice the same operand"); operands[k].input_index = i; op->priority = 5; } else if ((op->vt->r & VT_VALMASK) == VT_LOCAL && op->vt->sym && (reg = op->vt->sym->r & VT_VALMASK) < VT_CONST) { op->priority = 1; op->reg = reg; } else { op->priority = constraint_priority(str); } } /* sort operands according to their priority */ for(i=0;i<nb_operands;i++) sorted_op[i] = i; for(i=0;i<nb_operands - 1;i++) { for(j=i+1;j<nb_operands;j++) { p1 = operands[sorted_op[i]].priority; p2 = operands[sorted_op[j]].priority; if (p2 < p1) { tmp = sorted_op[i]; sorted_op[i] = sorted_op[j]; sorted_op[j] = tmp; } } } for(i = 0;i < NB_ASM_REGS; i++) { if (clobber_regs[i]) regs_allocated[i] = REG_IN_MASK | REG_OUT_MASK; else regs_allocated[i] = 0; } /* esp cannot be used */ regs_allocated[4] = REG_IN_MASK | REG_OUT_MASK; /* ebp cannot be used yet */ regs_allocated[5] = REG_IN_MASK | REG_OUT_MASK; /* allocate registers and generate corresponding asm moves */ for(i=0;i<nb_operands;i++) { j = sorted_op[i]; op = &operands[j]; str = op->constraint; /* no need to allocate references */ if (op->ref_index >= 0) continue; /* select if register is used for output, input or both */ if (op->input_index >= 0) { reg_mask = REG_IN_MASK | REG_OUT_MASK; } else if (j < nb_outputs) { reg_mask = REG_OUT_MASK; } else { reg_mask = REG_IN_MASK; } if (op->reg >= 0) { if (is_reg_allocated(op->reg)) tcc_error("asm regvar requests register that's taken already"); reg = op->reg; goto reg_found; } try_next: c = *str++; switch(c) { case '=': goto try_next; case '+': op->is_rw = 1; /* FALL THRU */ case '&': if (j >= nb_outputs) tcc_error("'%c' modifier can only be applied to outputs", c); reg_mask = REG_IN_MASK | REG_OUT_MASK; goto try_next; case 'A': /* allocate both eax and edx */ if (is_reg_allocated(TREG_XAX) || is_reg_allocated(TREG_XDX)) goto try_next; op->is_llong = 1; op->reg = TREG_XAX; regs_allocated[TREG_XAX] |= reg_mask; regs_allocated[TREG_XDX] |= reg_mask; break; case 'a': reg = TREG_XAX; goto alloc_reg; case 'b': reg = 3; goto alloc_reg; case 'c': reg = TREG_XCX; goto alloc_reg; case 'd': reg = TREG_XDX; goto alloc_reg; case 'S': reg = 6; goto alloc_reg; case 'D': reg = 7; alloc_reg: if (is_reg_allocated(reg)) goto try_next; goto reg_found; case 'q': /* eax, ebx, ecx or edx */ for(reg = 0; reg < 4; reg++) { if (!is_reg_allocated(reg)) goto reg_found; } goto try_next; case 'r': case 'R': case 'p': /* A general address, for x86(64) any register is acceptable*/ /* any general register */ for(reg = 0; reg < 8; reg++) { if (!is_reg_allocated(reg)) goto reg_found; } goto try_next; reg_found: /* now we can reload in the register */ op->is_llong = 0; op->reg = reg; regs_allocated[reg] |= reg_mask; break; case 'e': case 'i': if (!((op->vt->r & (VT_VALMASK | VT_LVAL)) == VT_CONST)) goto try_next; break; case 'I': case 'N': case 'M': if (!((op->vt->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST)) goto try_next; break; case 'm': case 'g': /* nothing special to do because the operand is already in memory, except if the pointer itself is stored in a memory variable (VT_LLOCAL case) */ /* XXX: fix constant case */ /* if it is a reference to a memory zone, it must lie in a register, so we reserve the register in the input registers and a load will be generated later */ if (j < nb_outputs || c == 'm') { if ((op->vt->r & VT_VALMASK) == VT_LLOCAL) { /* any general register */ for(reg = 0; reg < 8; reg++) { if (!(regs_allocated[reg] & REG_IN_MASK)) goto reg_found1; } goto try_next; reg_found1: /* now we can reload in the register */ regs_allocated[reg] |= REG_IN_MASK; op->reg = reg; op->is_memory = 1; } } break; default: tcc_error("asm constraint %d ('%s') could not be satisfied", j, op->constraint); break; } /* if a reference is present for that operand, we assign it too */ if (op->input_index >= 0) { operands[op->input_index].reg = op->reg; operands[op->input_index].is_llong = op->is_llong; } } /* compute out_reg. It is used to store outputs registers to memory locations references by pointers (VT_LLOCAL case) */ *pout_reg = -1; for(i=0;i<nb_operands;i++) { op = &operands[i]; if (op->reg >= 0 && (op->vt->r & VT_VALMASK) == VT_LLOCAL && !op->is_memory) { for(reg = 0; reg < 8; reg++) { if (!(regs_allocated[reg] & REG_OUT_MASK)) goto reg_found2; } tcc_error("could not find free output register for reloading"); reg_found2: *pout_reg = reg; break; } } /* print sorted constraints */ #ifdef ASM_DEBUG for(i=0;i<nb_operands;i++) { j = sorted_op[i]; op = &operands[j]; printf("%%%d [%s]: \"%s\" r=0x%04x reg=%d\n", j, op->id ? get_tok_str(op->id, NULL) : "", op->constraint, op->vt->r, op->reg); } if (*pout_reg >= 0) printf("out_reg=%d\n", *pout_reg); #endif } ST_FUNC void subst_asm_operand(CString *add_str, SValue *sv, int modifier) { int r, reg, size, val; char buf[64]; r = sv->r; if ((r & VT_VALMASK) == VT_CONST) { if (!(r & VT_LVAL) && modifier != 'c' && modifier != 'n' && modifier != 'P') cstr_ccat(add_str, '$'); if (r & VT_SYM) { const char *name = get_tok_str(sv->sym->v, NULL); if (sv->sym->v >= SYM_FIRST_ANOM) { /* In case of anonymous symbols ("L.42", used for static data labels) we can't find them in the C symbol table when later looking up this name. So enter them now into the asm label list when we still know the symbol. */ get_asm_sym(tok_alloc(name, strlen(name))->tok, sv->sym); } cstr_cat(add_str, name, -1); if ((uint32_t)sv->c.i == 0) goto no_offset; cstr_ccat(add_str, '+'); } val = sv->c.i; if (modifier == 'n') val = -val; snprintf(buf, sizeof(buf), "%d", (int)sv->c.i); cstr_cat(add_str, buf, -1); no_offset:; #ifdef TCC_TARGET_X86_64 if (r & VT_LVAL) cstr_cat(add_str, "(%rip)", -1); #endif } else if ((r & VT_VALMASK) == VT_LOCAL) { #ifdef TCC_TARGET_X86_64 snprintf(buf, sizeof(buf), "%d(%%rbp)", (int)sv->c.i); #else snprintf(buf, sizeof(buf), "%d(%%ebp)", (int)sv->c.i); #endif cstr_cat(add_str, buf, -1); } else if (r & VT_LVAL) { reg = r & VT_VALMASK; if (reg >= VT_CONST) tcc_error("internal compiler error"); snprintf(buf, sizeof(buf), "(%%%s)", #ifdef TCC_TARGET_X86_64 get_tok_str(TOK_ASM_rax + reg, NULL) #else get_tok_str(TOK_ASM_eax + reg, NULL) #endif ); cstr_cat(add_str, buf, -1); } else { /* register case */ reg = r & VT_VALMASK; if (reg >= VT_CONST) tcc_error("internal compiler error"); /* choose register operand size */ if ((sv->type.t & VT_BTYPE) == VT_BYTE || (sv->type.t & VT_BTYPE) == VT_BOOL) size = 1; else if ((sv->type.t & VT_BTYPE) == VT_SHORT) size = 2; #ifdef TCC_TARGET_X86_64 else if ((sv->type.t & VT_BTYPE) == VT_LLONG || (sv->type.t & VT_BTYPE) == VT_PTR) size = 8; #endif else size = 4; if (size == 1 && reg >= 4) size = 4; if (modifier == 'b') { if (reg >= 4) tcc_error("cannot use byte register"); size = 1; } else if (modifier == 'h') { if (reg >= 4) tcc_error("cannot use byte register"); size = -1; } else if (modifier == 'w') { size = 2; } else if (modifier == 'k') { size = 4; #ifdef TCC_TARGET_X86_64 } else if (modifier == 'q') { size = 8; #endif } switch(size) { case -1: reg = TOK_ASM_ah + reg; break; case 1: reg = TOK_ASM_al + reg; break; case 2: reg = TOK_ASM_ax + reg; break; default: reg = TOK_ASM_eax + reg; break; #ifdef TCC_TARGET_X86_64 case 8: reg = TOK_ASM_rax + reg; break; #endif } snprintf(buf, sizeof(buf), "%%%s", get_tok_str(reg, NULL)); cstr_cat(add_str, buf, -1); } } /* generate prolog and epilog code for asm statement */ ST_FUNC void asm_gen_code(ASMOperand *operands, int nb_operands, int nb_outputs, int is_output, uint8_t *clobber_regs, int out_reg) { uint8_t regs_allocated[NB_ASM_REGS]; ASMOperand *op; int i, reg; /* Strictly speaking %Xbp and %Xsp should be included in the call-preserved registers, but currently it doesn't matter. */ #ifdef TCC_TARGET_X86_64 #ifdef TCC_TARGET_PE static uint8_t reg_saved[] = { 3, 6, 7, 12, 13, 14, 15 }; #else static uint8_t reg_saved[] = { 3, 12, 13, 14, 15 }; #endif #else static uint8_t reg_saved[] = { 3, 6, 7 }; #endif /* mark all used registers */ memcpy(regs_allocated, clobber_regs, sizeof(regs_allocated)); for(i = 0; i < nb_operands;i++) { op = &operands[i]; if (op->reg >= 0) regs_allocated[op->reg] = 1; } if (!is_output) { /* generate reg save code */ for(i = 0; i < sizeof(reg_saved)/sizeof(reg_saved[0]); i++) { reg = reg_saved[i]; if (regs_allocated[reg]) { if (reg >= 8) g(0x41), reg-=8; g(0x50 + reg); } } /* generate load code */ for(i = 0; i < nb_operands; i++) { op = &operands[i]; if (op->reg >= 0) { if ((op->vt->r & VT_VALMASK) == VT_LLOCAL && op->is_memory) { /* memory reference case (for both input and output cases) */ SValue sv; sv = *op->vt; sv.r = (sv.r & ~VT_VALMASK) | VT_LOCAL | VT_LVAL; sv.type.t = VT_PTR; load(op->reg, &sv); } else if (i >= nb_outputs || op->is_rw) { /* load value in register */ load(op->reg, op->vt); if (op->is_llong) { SValue sv; sv = *op->vt; sv.c.i += 4; load(TREG_XDX, &sv); } } } } } else { /* generate save code */ for(i = 0 ; i < nb_outputs; i++) { op = &operands[i]; if (op->reg >= 0) { if ((op->vt->r & VT_VALMASK) == VT_LLOCAL) { if (!op->is_memory) { SValue sv; sv = *op->vt; sv.r = (sv.r & ~VT_VALMASK) | VT_LOCAL; sv.type.t = VT_PTR; load(out_reg, &sv); sv = *op->vt; sv.r = (sv.r & ~VT_VALMASK) | out_reg; store(op->reg, &sv); } } else { store(op->reg, op->vt); if (op->is_llong) { SValue sv; sv = *op->vt; sv.c.i += 4; store(TREG_XDX, &sv); } } } } /* generate reg restore code */ for(i = sizeof(reg_saved)/sizeof(reg_saved[0]) - 1; i >= 0; i--) { reg = reg_saved[i]; if (regs_allocated[reg]) { if (reg >= 8) g(0x41), reg-=8; g(0x58 + reg); } } } } ST_FUNC void asm_clobber(uint8_t *clobber_regs, const char *str) { int reg; TokenSym *ts; #ifdef TCC_TARGET_X86_64 unsigned int type; #endif if (!strcmp(str, "memory") || !strcmp(str, "cc") || !strcmp(str, "flags")) return; ts = tok_alloc(str, strlen(str)); reg = ts->tok; if (reg >= TOK_ASM_eax && reg <= TOK_ASM_edi) { reg -= TOK_ASM_eax; } else if (reg >= TOK_ASM_ax && reg <= TOK_ASM_di) { reg -= TOK_ASM_ax; #ifdef TCC_TARGET_X86_64 } else if (reg >= TOK_ASM_rax && reg <= TOK_ASM_rdi) { reg -= TOK_ASM_rax; } else if ((reg = asm_parse_numeric_reg(reg, &type)) >= 0) { ; #endif } else { tcc_error("invalid clobber register '%s'", str); } clobber_regs[reg] = 1; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <ctype.h> int isalnum (int c) { return isdigit (c) || isalpha (c); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <ctype.h> int isalpha (int c) { return islower (c) || isupper (c); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <ctype.h> int isascii (int c) { return c >= 0 && c <= 127; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <ctype.h> int iscntrl (int c) { return c >= 0 && c < 32; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <ctype.h> int isgraph (int c) { return c > 32 && c < 127; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <ctype.h> int isprint (int c) { return c >= 32 && c < 127; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <ctype.h> int ispunct (int c) { return isprint (c) && !isspace (c) && !isalnum (c); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright (C) 1991, 1993, 1995, 1996, 1998 Free Software Foundation, Inc. * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ // Taken from GNU C Library 2.2.5 #include <errno.h> #include <stddef.h> #include <stdlib.h> #include <dirent.h> #include <unistd.h> #include <errno.h> #include <stddef.h> #include <stdlib.h> #include <dirent.h> #include <unistd.h> #include <dirstream.h> /* Close the directory stream DIRP. Return 0 if successful, -1 if not. */ int closedir (DIR * dirp) { int filedes; if (dirp == NULL) { errno = EINVAL; return -1; } filedes = dirp->fd; free (dirp); return close (filedes); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright (C) 1993 Free Software Foundation, Inc. * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ // Taken from GNU C Library 1.06.4 #include <mes/lib.h> #include <dirent.h> #include <stdio.h> #include <unistd.h> int __getdirentries (int filedes, char *buffer, size_t nbytes, off_t * basep) { if (basep) *basep = lseek (filedes, (off_t) 0, SEEK_CUR); return read (filedes, buffer, nbytes); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright (C) 1991-1996,98,2000,2001 Free Software Foundation, Inc. * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ // Taken from GNU C Library 2.2.5 #include <mes/lib.h> #include <errno.h> #include <limits.h> #include <stddef.h> #include <stdlib.h> #include <dirent.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <stdio.h> #include <dirstream.h> /* Open a directory stream on NAME. */ DIR * opendir (char const *name) { DIR *dirp; struct stat statbuf; int fd; size_t allocation; int save_errno; if (name[0] == '\0') { /* POSIX.1-1990 says an empty name gets ENOENT; but `open' might like it fine. */ errno = ENOENT; return 0; } fd = open (name, O_RDONLY | O_DIRECTORY); if (fd < 0) return 0; if (fstat (fd, &statbuf) < 0) goto lose; if (fcntl (fd, F_SETFD, FD_CLOEXEC) < 0) goto lose; allocation = statbuf.st_blksize; dirp = (DIR *) calloc (1, sizeof (DIR) + allocation); if (!dirp) lose: { save_errno = errno; close (fd); errno = save_errno; return 0; } dirp->data = (char *) (dirp + 1); dirp->allocation = allocation; dirp->fd = fd; return dirp; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright (C) 1991,92,93,94,95,96,97,99,2000 Free Software Foundation, Inc. * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ // Taken from GNU C Library 2.2.5 #include <errno.h> #include <limits.h> #include <stddef.h> #include <string.h> #include <dirent.h> #include <unistd.h> #include <sys/types.h> #include <assert.h> #include <dirstream.h> int getdents (int filedes, char *buffer, size_t nbytes); /* Read a directory entry from DIRP. */ struct dirent * readdir (DIR * dirp) { struct dirent *dp; int saved_errno = errno; do { size_t reclen; if (dirp->offset >= dirp->size) { /* We've emptied out our buffer. Refill it. */ size_t maxread; ssize_t bytes; maxread = dirp->allocation; #if 0 off_t base; bytes = __getdirentries (dirp->fd, dirp->data, maxread, &base); #else bytes = getdents (dirp->fd, dirp->data, maxread); #endif if (bytes <= 0) { /* Don't modifiy errno when reaching EOF. */ if (bytes == 0) errno = saved_errno; dp = 0; break; } dirp->size = (size_t) bytes; /* Reset the offset into the buffer. */ dirp->offset = 0; } dp = (struct dirent *) &dirp->data[dirp->offset]; reclen = dp->d_reclen; dirp->offset += reclen; dirp->filepos = dp->d_off; /* Skip deleted files. */ } while (dp->d_ino == 0); return dp; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> int chdir (char const *file_name) { return _sys_call1 (SYS_chdir, (long) file_name); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <stdarg.h> int fcntl (int filedes, int command, ...) { va_list ap; va_start (ap, command); int data = va_arg (ap, int); int r = _sys_call3 (SYS_fcntl, (int) filedes, (int) command, (int) data); va_end (ap); return r; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <sys/stat.h> int fstat (int filedes, struct stat *statbuf) { return _sys_call2 (SYS_fstat, (int) filedes, (long) statbuf); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <sys/types.h> int getdents (int filedes, char *buffer, size_t nbytes) { return _sys_call3 (SYS_getdents, (int) filedes, (long) buffer, (long) nbytes); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <unistd.h> gid_t getegid () { return _sys_call (SYS_getegid); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <unistd.h> uid_t geteuid () { return _sys_call (SYS_geteuid); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <unistd.h> gid_t getgid () { return _sys_call (SYS_getgid); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <unistd.h> pid_t getppid () { return _sys_call (SYS_getppid); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <sys/resource.h> int getrusage (int processes, struct rusage *rusage) { return _sys_call2 (SYS_getrusage, (int) processes, (long) rusage); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <unistd.h> uid_t getuid () { return _sys_call (SYS_getuid); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018,2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <stdarg.h> #include <sys/ioctl.h> int ioctl (int filedes, unsigned long command, ...) { va_list ap; va_start (ap, command); int data = va_arg (ap, int); int r = _sys_call3 (SYS_ioctl, (int) filedes, (long) command, (int) data); va_end (ap); return r; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> int link (char const *old_name, char const *new_name) { return _sys_call2 (SYS_link, (long) old_name, (long) new_name); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <sys/stat.h> int lstat (char const *file_name, struct stat *statbuf) { return _sys_call2 (SYS_lstat, (long) file_name, (long) statbuf); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <sys/stat.h> int mkdir (char const *file_name, mode_t mode) { return _sys_call2 (SYS_mkdir, (long) file_name, (long) mode); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <sys/stat.h> int mknod (char const *file_name, mode_t mode, dev_t dev) { return _sys_call3 (SYS_mknod, (long) file_name, (long) mode, (long) dev); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <time.h> #include <sys/time.h> int nanosleep (struct timespec const *requested_time, struct timespec const *remaining) { return _sys_call2 (SYS_nanosleep, (long) requested_time, (long) remaining); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <unistd.h> int pipe (int filedes[2]) { return _sys_call1 (SYS_pipe, (long) filedes); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <sys/stat.h> ssize_t readlink (char const *file_name, char *buffer, size_t size) { return _sys_call3 (SYS_readlink, (long) file_name, (long) buffer, (long) size); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <unistd.h> int rename (char const *old_name, char const *new_name) { return _sys_call2 (SYS_rename, (long) old_name, (long) new_name); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <unistd.h> int setgid (gid_t newgid) { return _sys_call1 (SYS_setgid, (long) newgid); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <sys/time.h> #include <unistd.h> int setitimer (int which, struct itimerval const *new, struct itimerval *old) { return _sys_call3 (SYS_setitimer, (long) which, (long) new, (long) old); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <unistd.h> int setuid (uid_t newuid) { return _sys_call1 (SYS_setuid, (long) newuid); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022,2023 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <unistd.h> #include <signal.h> #if __i386__ #else void _restorer_for_siginfo (void) { _sys_call (SYS_rt_sigreturn); } #endif sighandler_t signal (int signum, sighandler_t action) { #if __i386__ return (sighandler_t) _sys_call2 (SYS_signal, signum, (long) action); #else static struct sigaction setup_action = { 0 }; static struct sigaction old = { 0 }; unsigned short bitindex; unsigned short itembitcount; setup_action.sa_handler = action; /* The sa_restorer is not used. Reason: * The kernel uses its own restorer anyway by pretending we gave one on our side (no, really). glibc still has a custom restorer because they want to be able to backtrace. gdb has a second part that detects a specific instruction sequence and then fixes up the backtrace. Since we don't use that specific instruction sequence and also the non-siginfo restorer is complicated (pops foreign item off the stack--so needs inline assembly) and we don't support great debuggability in other places, I've decided to take the easy way out for now. */ /*setup_action.sa_restorer = _restorer_for_siginfo;*/ bitindex = signum - 1; itembitcount = 8 * sizeof (setup_action.sa_mask.items[0]); setup_action.sa_mask.items[bitindex / itembitcount] = 1UL << (bitindex % itembitcount); old.sa_handler = SIG_DFL; setup_action.sa_flags = SA_RESTART; #if __x86_64__ /* Tell kernel that we use a restorer, on penalty of segfault. */ setup_action.sa_flags |= SA_RESTORER; #endif int r = _sys_call4 (SYS_rt_sigaction, signum, (long) &setup_action, (long) &old, sizeof (sigset_t)); if (r) return 0; return old.sa_handler; #endif }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <signal.h> #include <unistd.h> int sigprocmask (int how, sigset_t const *set, sigset_t * oldset) { #if __i386__ return _sys_call3 (SYS_sigprocmask, (long) how, (long) set, (long) oldset); #else return _sys_call3 (SYS_rt_sigprocmask, (long) how, (long) set, (long) oldset); #endif }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019,2022 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/syscall.h> #include <arch/syscall.h> #include <unistd.h> int symlink (char const *old_name, char const *new_name) { return _sys_call2 (SYS_symlink, (long) old_name, (long) new_name); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib-mini.h" #define SYS_exit "0x01" // *INDENT-OFF* void _exit (int code) { asm ( "mov $"SYS_exit",%%eax\n\t" "mov %0,%%ebx\n\t" "int $0x80\n\t" : // no outputs "=" (r) : "rm" (code) : "eax", "ebx" ); // not reached // _exit (0); // tcc has a problem with this }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <errno.h> #include <linux/x86/syscall.h> // *INDENT-OFF* long __sys_call (long sys_call) { long r; asm ( "mov %1,%%eax\n\t" "int $0x80\n\t" "mov %%eax,%0\n\t" : "=r" (r) : "rm" (sys_call) : "eax" ); return r; } long __sys_call1 (long sys_call, long one) { long r; asm ( "mov %1,%%eax\n\t" "mov %2,%%ebx\n\t" "int $0x80\n\t" "mov %%eax,%0\n\t" : "=r" (r) : "rm" (sys_call), "rm" (one) : "eax", "ebx" ); return r; } long __sys_call2 (long sys_call, long one, long two) { long r; asm ( "mov %1,%%eax\n\t" "mov %2,%%ebx\n\t" "mov %3,%%ecx\n\t" "int $0x80\n\t" "mov %%eax,%0\n\t" : "=r" (r) : "rm" (sys_call), "rm" (one), "rm" (two) : "eax", "ebx", "ecx" ); return r; } long __sys_call3 (long sys_call, long one, long two, long three) { long r; asm ( "mov %2,%%ebx\n\t" "mov %3,%%ecx\n\t" "mov %4,%%edx\n\t" "mov %1,%%eax\n\t" "int $0x80\n\t" "mov %%eax,%0\n\t" : "=r" (r) : "rm" (sys_call), "rm" (one), "rm" (two), "rm" (three) : "eax", "ebx", "ecx", "edx" ); return r; } long __sys_call4 (long sys_call, long one, long two, long three, long four) { long r; asm ( "mov %2,%%ebx\n\t" "mov %3,%%ecx\n\t" "mov %4,%%edx\n\t" "mov %5,%%esi\n\t" "mov %1,%%eax\n\t" "int $0x80\n\t" "mov %%eax,%0\n\t" : "=r" (r) : "rm" (sys_call), "rm" (one), "rm" (two), "rm" (three), "rm" (four) : "eax", "ebx", "ecx", "edx", "esi" ); return r; } // *INDENT-ON* long _sys_call (long sys_call) { long r = __sys_call (sys_call); if (r < 0) { errno = -r; r = -1; } else errno = 0; return r; } long _sys_call1 (long sys_call, long one) { long r = __sys_call1 (sys_call, one); if (r < 0) { errno = -r; r = -1; } else errno = 0; return r; } long _sys_call2 (long sys_call, long one, long two) { long r = __sys_call2 (sys_call, one, two); if (r < 0) { errno = -r; r = -1; } else errno = 0; return r; } long _sys_call3 (long sys_call, long one, long two, long three) { long r = __sys_call3 (sys_call, one, two, three); if (r < 0) { errno = -r; r = -1; } else errno = 0; return r; } long _sys_call4 (long sys_call, long one, long two, long three, long four) { long r = __sys_call4 (sys_call, one, two, three, four); if (r < 0) { errno = -r; r = -1; } else errno = 0; return r; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2016,2017,2019,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include "mes/lib-mini.h" #define SYS_write "0x04" // *INDENT-OFF* ssize_t _write (int filedes, void const *buffer, size_t size) { long r; asm ( "mov $"SYS_write",%%eax\n\t" "mov %1,%%ebx\n\t" "mov %2,%%ecx\n\t" "mov %3,%%edx\n\t" "int $0x80\n\t" "mov %%eax,%0\n\t" : "=r" (r) : "rm" (filedes), "rm" (buffer), "rm" (size) : "eax", "ebx", "ecx", "edx" ); return r; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <math.h> double ceil (double number) { long i = number + 0.9999; number = i; return number; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <math.h> double fabs (double number) { if (number < 0) return -number; return number; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <math.h> double floor (double number) { long i = number; number = i; return number; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdio.h> char * fdgets (char *s, int count, int fd) { int c = 0; char *p = s; while (--count > 0 && c != '\n') { c = fdgetc (fd); if (c == EOF) break; *p++ = c; } if (p == s && (c == EOF || count == -1)) return 0; *p = 0; return s; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <unistd.h> #include <sys/time.h> unsigned int alarm (unsigned int seconds) { #if !__MESC__ struct itimerval old; struct itimerval new; new.it_interval.tv_usec = 0; new.it_interval.tv_sec = 0; new.it_value.tv_usec = 0; new.it_value.tv_sec = (long int) seconds; if (setitimer (ITIMER_REAL, &new, &old) < 0) return 0; return old.it_value.tv_sec; #endif }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdarg.h> #include <unistd.h> int vexec (char const *file_name, va_list ap) { char *arg = va_arg (ap, char *); char *argv[1000]; // POSIX minimum 4096 int i = 0; argv[i++] = (char *) file_name; while (arg) { argv[i++] = arg; arg = va_arg (ap, char *); if (__mes_debug () > 2) { eputs ("arg["); eputs (itoa (i)); eputs ("]: "); eputs (argv[i - 1]); eputs ("\n"); } } argv[i] = 0; int r = execv (file_name, argv); va_end (ap); return r; } int execl (char const *file_name, char const *arg, ...) { va_list ap; int r; va_start (ap, arg); if (__mes_debug () > 2) { eputs ("execl "); eputs (file_name); eputs ("\n"); } r = vexec (file_name, ap); va_end (ap); return r; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <stdarg.h> #include <string.h> int execlp (char const *file_name, char const *arg, ...) { va_list ap; int r; va_start (ap, arg); if (!strchr (file_name, '/')) file_name = search_path (file_name); if (__mes_debug () > 2) { eputs ("execlp "); eputs (file_name ? file_name : "0"); eputs ("\n"); } if (!file_name) { errno = ENOENT; return -1; } r = vexec (file_name, ap); va_end (ap); return r; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> #include <unistd.h> char * mktemp (char *template) { char *p = strchr (template, '\0'); int q = (long) template; *--p = ((unsigned char) (q >> 4)) % 26 + 'a'; *--p = ((unsigned char) (q >> 8)) % 26 + 'a'; *--p = ((unsigned char) (q >> 12)) % 26 + 'a'; *--p = ((unsigned char) (q >> 16)) % 26 + 'a'; *--p = ((unsigned char) (q >> 20)) % 26 + 'a'; *--p = ((unsigned char) (q >> 24)) % 26 + 'a'; return template; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <unistd.h> #include <stdlib.h> #if __SBRK_CHAR_PTRDIFF char * sbrk (ptrdiff_t) #else void * sbrk (intptr_t delta) #endif { if (delta >= 0) return malloc (delta); return __brk; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <sys/time.h> #include <time.h> #include <unistd.h> unsigned int sleep (unsigned int seconds) { struct timespec requested_time; struct timespec remaining; requested_time.tv_sec = seconds; requested_time.tv_nsec = 0; return nanosleep (&requested_time, &remaining); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <string.h> #include <unistd.h> void unsetenv (char const *name) { int length = strlen (name); char **p = environ; while (*p) { if (!strncmp (name, *p, length) && *(*p + length) == '=') { char **q = p; q[0] = q[1]; while (*q++) q[0] = q[1]; } p++; } }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <stdio.h> void clearerr (FILE * stream) { errno = 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> int feof (FILE * stream) { int c = fgetc (stream); if (c != EOF) ungetc (c, stream); return c == EOF; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdio.h> char * fgets (char *s, int count, FILE * stream) { return fdgets (s, count, (int) (long) stream); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> int fileno (FILE * stream) { return (int) (long) stream; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> FILE * freopen (char const *file_name, char const *opentype, FILE * stream) { fclose (stream); return fopen (file_name, opentype); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdarg.h> #include <stdio.h> int fscanf (FILE * stream, char const *template, ...) { va_list ap; va_start (ap, template); int r = vfscanf (stream, template, ap); va_end (ap); return r; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <stdio.h> #include <string.h> void perror (char const *message) { fprintf (stderr, "%s: %s\n", strerror (errno), message); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019,2023 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <ctype.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> int vfscanf (FILE * stream, char const *template, va_list ap) { char p = fgetc (stream); char const *t = template; int count = 0; while (*t && p != EOF) if (*t != '%') { t++; p = fgetc (stream); } else { t++; char c = *t; if (c == 'l') c = *++t; switch (c) { case '%': { p = fgetc (stream); break; } case 'c': { char *c = va_arg (ap, char *); *c = p; p = fgetc (stream); count++; break; } case 'd': case 'i': case 'u': { int *d = va_arg (ap, int *); char buf[20]; char *q = buf; if (p == '+' || p == '-') { *q++ = p; p = fgetc (stream); } while (isdigit (p)) { *q++ = p; p = fgetc (stream); } ungetc (p, stream); *q = 0; q = buf; *d = abtol ((char const**)&q, 10); count++; break; } case 'e': case 'f': case 'g': case 'E': case 'G': { float *f = va_arg (ap, float *); char buf[20]; char *q = buf; if (p == '+' || p == '-') { *q++ = p; p = fgetc (stream); } while (isdigit (p)) { *q++ = p; p = fgetc (stream); } ungetc (p, stream); *q = 0; q = buf; *f = strtod (q, &q); count++; break; } default: { eputs ("vsscanf: not supported: %:"); eputc (c); eputs ("\n"); t++; p = fgetc (stream); } } t++; } va_end (ap); return count; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <signal.h> void abort (void) { if (raise (SIGABRT) < 0) /* could not raise SIGABRT */ { /* Fail in any way possible */ unsigned char* x = (unsigned char*) 0; *x = 2; } }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> int abs (int x) { if (x < 0) return -x; return x; }
/* alloca.c -- allocate automatically reclaimed memory (Mostly) portable public-domain implementation -- D A Gwyn Taken from GNU binutils 2.10.1. Minor changes Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> This implementation of the PWB library alloca function, which is used to allocate space off the run-time stack so that it is automatically reclaimed upon procedure exit, was inspired by discussions with J. Q. Johnson of Cornell. There are some preprocessor constants that can be defined when compiling for your specific system, for improved efficiency; however, the defaults should be okay. The general concept of this implementation is to keep track of all alloca-allocated blocks, and reclaim any that are found to be deeper in the stack than the current invocation. This heuristic does not reclaim storage as soon as it becomes invalid, but it will do so eventually. As a special case, alloca(0) reclaims storage without allocating any. It is a good idea to use alloca(0) in your main control loop, etc. to force garbage collection. */ #include <stdlib.h> #include <unistd.h> #define ALIGN_SIZE 4 #define ADDRESS_FUNCTION(arg) &(arg) #define STACK_DIR -1 union alloca_header { char align[ALIGN_SIZE]; /* To force sizeof(union alloca_header). */ struct { union alloca_header *next; /* For chaining headers. */ char *deep; /* For stack depth measure. */ } h; }; static union alloca_header *last_alloca_header = NULL; /* -> last alloca header. */ /* Return a void * to at least SIZE bytes of storage, which will be automatically reclaimed upon exit from the procedure that called alloca. Originally, this space was supposed to be taken from the current stack frame of the caller, but that method cannot be made to work for some implementations of C, for example under Gould's UTX/32. */ void * alloca (size_t size) { char probe; /* Probes stack depth: */ char *depth = ADDRESS_FUNCTION (probe); /* Reclaim garbage, defined as all alloca'd storage that was allocated from deeper in the stack than currently. */ { union alloca_header *hp; /* Traverses linked list. */ for (hp = last_alloca_header; hp != NULL;) if ((STACK_DIR > 0 && hp->h.deep > depth) || (STACK_DIR < 0 && hp->h.deep < depth)) { union alloca_header *np = hp->h.next; free ((void *) hp); /* Collect garbage. */ hp = np; /* -> next header. */ } else break; /* Rest are not deeper. */ last_alloca_header = hp; /* -> last valid storage. */ } if (size == 0) return NULL; /* No allocation required. */ /* Allocate combined header + user data storage. */ { void *new = malloc (sizeof (union alloca_header) + size); /* Address of header. */ if (new == 0) abort (); ((union alloca_header *) new)->h.next = last_alloca_header; ((union alloca_header *) new)->h.deep = depth; last_alloca_header = (union alloca_header *) new; /* User storage begins just after header. */ return (void *) ((char *) new + sizeof (union alloca_header)); } }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdlib.h> int atexit (void (*function) (void)) { __call_at_exit = function; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> double atof (char const *string) { char const *p = string; return abtod (&p, 0); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> int atol (char const *s) { return atoi (s); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> int __exit (int status) { exit (status); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2023 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdio.h> #include <stdlib.h> #include <string.h> size_t mbstowcs (wchar_t * wstring, char const *string, size_t size) { static int stub = 0; if (__mes_debug () && !stub) eputs ("mbstowcs stub\n"); stub = 1; strcpy ((char*)wstring, string); return strlen (string); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> int bcmp (void const *s1, void const *s2, size_t size) { return memcmp (s1, s2, size); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> void bcopy (void const *src, void *dest, size_t n) { memmove (dest, src, n); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> #include <string.h> #if BZERO_INT int #else void #endif bzero (void *block, size_t size) { #if BZERO_INT return (int) (long) #endif memset (block, 0, size); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> #if INDEX_INT int #else char * #endif index (char const *s, int c) { return strchr (s, c); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> #if INDEX_INT int #else char * #endif rindex (char const *s, int c) { return strrchr (s, c); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> size_t strcspn (char const *string, char const *stopset) { char *p = (char *) string; while (*p) if (strchr (stopset, *p)) break; else p++; return p - string; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> #include <string.h> char * strdup (char const *s) { size_t length = strlen (s) + 1; char *p = (char *) malloc (length); if (p) return memcpy (p, s, length); return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <string.h> char *sys_errlist[] = { "error 00", "error 01", "error 02", "error 03", "error 04", "error 05", "error 06", "error 07", "error 08", "error 09", "error 10", "error 11", "error 12", "error 13", "error 14", "error 15", "error 16", "error 17", "error 18", "error 19", "error 20", "error 21", "error 22", "error 23", "error 24", "error 25", "error 26", "error 27", "error 28", "error 29", "error 30", "error 31", "error 32", "error 33", "error 34", "error 35", "error 36", "error 37", "error 38", "error 39", }; int sys_nerr = 39; char * strerror (int errnum) { if (__mes_debug ()) { eputs ("strerror errnum="); eputs (itoa (errnum)); eputs ("\n"); } if (errnum > 0 && errnum <= sys_nerr) return sys_errlist[errnum]; return "sterror: unknown error"; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> char * strncat (char *to, char const *from, size_t size) { if (size == 0) return to; char *p = strchr (to, '\0'); while (*from && size-- > 0) *p++ = *from++; *p = 0; return to; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> char * strpbrk (char const *string, char const *stopset) { char *p = (char *) string; while (*p) if (strchr (stopset, *p)) break; else p++; return p; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> size_t strspn (char const *string, char const *skipset) { char *p = (char *) string; while (*p) if (!strchr (skipset, *p)) break; else p++; return p - string; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <math.h> double atan2 (double x, double y) { static int stub = 0; if (__mes_debug () && !stub) eputs ("atan2 stub\n"); stub = 1; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdlib.h> void * #if __MESC__ bsearch (void const *key, void const *array, size_t count, size_t size, void (*compare) ()) #else bsearch (void const *key, void const *array, size_t count, size_t size, comparison_fn_t compare) #endif { static int stub = 0; if (__mes_debug () && !stub) eputs ("bsearch stub\n"); stub = 1; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <unistd.h> int chown (char const *file_name, uid_t owner, gid_t group) { static int stub = 0; if (__mes_debug () && !stub) eputs ("chown stub\n"); stub = 1; errno = 0; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> int __cleanup () { static int stub = 0; if (__mes_debug () && !stub) eputs ("__cleanup stub\n"); stub = 1; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <math.h> double cos (double x) { static int stub = 0; if (__mes_debug () && !stub) eputs ("cos stub\n"); stub = 1; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <time.h> char * ctime (time_t const *time) { static int stub = 0; if (__mes_debug () && !stub) eputs ("ctime stub\n"); stub = 1; errno = 0; return "now"; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <math.h> double exp (double x) { static int stub = 0; if (__mes_debug () && !stub) eputs ("exp stub\n"); stub = 1; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <stdio.h> int fpurge (FILE * stream) { static int stub = 0; if (__mes_debug () && !stub) eputs ("fpurge stub\n"); stub = 1; errno = 0; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <stdio.h> size_t freadahead (FILE * fp) { static int stub = 0; if (__mes_debug () && !stub) eputs ("freadahead stub\n"); stub = 1; errno = 0; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> double frexp (int x) { static int stub = 0; if (__mes_debug () && !stub) eputs ("frexp stub\n"); stub = 1; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <grp.h> struct group * getgrgid (gid_t gid) { static int stub = 0; if (__mes_debug () && !stub) eputs ("getgrid stub\n"); static char *groups[2] = { "root", 0 }; #if SYSTEM_LIBC static struct group root = { "root", 0, 0 }; root.gr_mem = groups; #else static struct group root = { "root", 0, groups }; #endif stub = 1; errno = 0; return &root; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <grp.h> struct group * getgrnam (char const *name) { static int stub = 0; if (__mes_debug () && !stub) eputs ("getgrid stub\n"); static char *groups[2] = { "root", 0 }; #if SYSTEM_LIBC static struct group root = { "root", 0, 0 }; root.gr_mem = groups; #else static struct group root = { "root", 0, groups }; #endif stub = 1; errno = 0; return &root; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <unistd.h> char * getlogin (void) { static int stub = 0; if (__mes_debug () && !stub) eputs ("getlogin stub\n"); stub = 1; errno = 0; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <unistd.h> int getpgid (pid_t pid) { static int stub = 0; if (__mes_debug () && !stub) eputs ("getpgid stub\n"); stub = 1; errno = 0; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <unistd.h> pid_t getpgrp (void) { static int stub = 0; if (__mes_debug () && !stub) eputs ("getpgrp stub\n"); stub = 1; errno = 0; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <pwd.h> struct passwd * getpwnam (const char *name) { static int stub = 0; if (__mes_debug () && !stub) eputs ("getpwnam stub\n"); stub = 1; errno = 0; static struct passwd root = { "root", "*", 0, 0, "", "/root", "/bin/sh" }; return &root; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <pwd.h> struct passwd * getpwuid (uid_t uid) { static int stub = 0; if (__mes_debug () && !stub) eputs ("getpwuid stub\n"); stub = 1; errno = 0; static struct passwd root = { "root", "*", 0, 0, "", "/root", "/bin/sh" }; return &root; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <time.h> #include <sys/time.h> struct tm * gmtime (time_t const *time) { static int stub = 0; if (__mes_debug () && !stub) eputs ("gmtime stub\n"); stub = 1; errno = 0; return localtime (time); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <math.h> double log (double x) { static int stub = 0; if (__mes_debug () && !stub) eputs ("log stub\n"); stub = 1; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <time.h> time_t mktime (struct tm *broken_time) { static int stub = 0; if (__mes_debug () && !stub) eputs ("mktime stub\n"); stub = 1; errno = 0; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <math.h> double modf (double value, double *integer_part) { static int stub = 0; if (__mes_debug () && !stub) eputs ("modf stub\n"); stub = 1; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> int pclose (int x) { static int stub = 0; if (__mes_debug () && !stub) eputs ("pclose stub\n"); stub = 1; errno = 0; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> int popen (int x) { static int stub = 0; if (__mes_debug () && !stub) eputs ("popen stub\n"); stub = 1; errno = 0; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <math.h> double pow (double base, double power) { static int stub = 0; if (__mes_debug () && !stub) eputs ("pow stub\n"); stub = 1; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> int rand (void) { static int stub = 0; if (__mes_debug () && !stub) eputs ("rand stub\n"); stub = 1; errno = 0; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> int rewind (int x) { static int stub = 0; if (__mes_debug () && !stub) eputs ("rewind stub\n"); stub = 1; errno = 0; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> int setbuf (int x) { static int stub = 0; if (__mes_debug () && !stub) eputs ("setbuf stub\n"); stub = 1; errno = 0; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> void setgrent (void) { static int stub = 0; if (__mes_debug () && !stub) eputs ("setgrent stub\n"); stub = 1; errno = 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <locale.h> char * setlocale (int category, char const *locale) { return "C"; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <stdio.h> int setvbuf (FILE * stream, char *buf, int mode, size_t size) { static int stub = 0; if (__mes_debug () && !stub) eputs ("setvbuf stub\n"); stub = 1; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <signal.h> int sigaddset (sigset_t * set, int signum) { return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <signal.h> int sigblock (int mask) { return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <signal.h> int sigdelset (sigset_t * set, int signum) { return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> int sigsetmask (int x) { static int stub = 0; if (__mes_debug () && !stub) eputs ("sigsetmask stub\n"); stub = 1; errno = 0; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <math.h> double sin (double x) { static int stub = 0; if (__mes_debug () && !stub) eputs ("sin stub\n"); stub = 1; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> int sys_siglist (int x) { static int stub = 0; if (__mes_debug () && !stub) eputs ("sys_siglist stub\n"); stub = 1; errno = 0; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> int system (int x) { static int stub = 0; if (__mes_debug () && !stub) eputs ("system stub\n"); stub = 1; errno = 0; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <math.h> double sqrt (double x) { static int stub = 0; if (__mes_debug () && !stub) eputs ("sqrt stub\n"); stub = 1; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <time.h> size_t strftime (char *s, size_t size, char const *template, struct tm const *brokentime) { static int stub = 0; if (__mes_debug () && !stub) eputs ("strftime stub\n"); stub = 1; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <sys/times.h> #include <sys/time.h> clock_t times (struct tms *buffer) { static int stub = 0; if (__mes_debug () && !stub) eputs ("times stub\n"); stub = 1; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> #include <unistd.h> char * ttyname (int filedes) { static int stub = 0; if (__mes_debug () && !stub) eputs ("ttyname stub\n"); stub = 1; errno = 0; if (isatty (filedes)) return "/dev/tty0"; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> int umask (int x) { static int stub = 0; if (__mes_debug () && !stub) eputs ("umask stub\n"); stub = 1; errno = 0; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #include <errno.h> int utime (int x) { static int stub = 0; if (__mes_debug () && !stub) eputs ("utime stub\n"); errno = 0; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <setjmp.h> #include <stdlib.h> void longjmp (jmp_buf env, int val) { val = val == 0 ? 1 : val; // *INDENT-OFF* asm ( "mov 0x8(%ebp),%ebp\n\t" // env* "mov 0x4(%ebp),%ebx\n\t" // env->__pc "mov 0x8(%ebp),%esp\n\t" // env->__sp "mov 0x0(%ebp),%ebp\n\t" // env->__bp "jmp *%ebx\n\t" // jmp *PC ); // *INDENT-ON* // not reached exit (42); } int setjmp (jmp_buf env) { long *p = (long *) &env; env[0].__bp = p[-2]; env[0].__pc = p[-1]; env[0].__sp = (long) &env; return 0; }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2019 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * Copyright © 2021 Paul Dersey <pdersey@gmail.com> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib-mini.h> //int main (int argc, char *argv[], char *envp[]); // *INDENT-OFF* void _start () { asm ( "mov $0,%%eax\n\t" "mov %%eax,%0\n" : "=r" (__stdin) : //no inputs "" ); asm ( "mov $1,%%eax\n\t" "mov %%eax,%0\n" : "=r" (__stdout) : //no inputs "" ); asm ( "mov $2,%%eax\n\t" "mov %%eax,%0\n" : "=r" (__stderr) : //no inputs "" ); asm ( "mov %%ebp,%%eax\n\t" "add $4,%%eax\n\t" "mov (%%eax),%%eax\n\t" "add $3,%%eax\n\t" "shl $2,%%eax\n\t" "add %%ebp,%%eax\n\t" "mov %%eax,%0\n\t" "push %%eax\n\t" : "=r" (environ) : //no inputs "" ); asm ( "mov %ebp,%eax\n\t" "add $8,%eax\n\t" "push %eax\n\t" "mov %ebp,%eax\n\t" "add $4,%eax\n\t" "mov (%eax),%eax\n\t" "push %eax\n\t" "call main\n\t" "mov %eax,%ebx\n\t" "mov $1,%eax\n\t" "int $0x80\n\t" "hlt \n\t" ); }
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */
/* -*-comment-start: "//";comment-end:""-*- * GNU Mes --- Maxwell Equations of Software * Copyright © 2017,2018,2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> * Copyright © 2021 Danny Milosavljevic <dannym@scratchpost.org> * * This file is part of GNU Mes. * * GNU Mes is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * GNU Mes is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Mes. If not, see <http://www.gnu.org/licenses/>. */ #include <mes/lib.h> #if HAVE_LONG_LONG long long __divdi3 (long long a, long long b) #else long __divdi3 (long a, long b) #endif { #if !__TINYC__ static int stub = 0; if (__mes_debug () && !stub) eputs ("__divdi3 stub\n"); stub = 1; #endif // FIXME: Actually use long long long ai = a; long bi = b; #if __arm__ && __TINYC__ return __mesabi_idiv (ai, bi); #else return ai / bi; #endif } #if HAVE_LONG_LONG long long __moddi3 (long long a, long long b) #else long __moddi3 (long a, long b) #endif { #if !__TINYC__ static int stub = 0; if (__mes_debug () && !stub) eputs ("__moddi3 stub\n"); stub = 1; #endif // FIXME: Actually use long long long ai = a; long bi = b; #if __arm__ && __TINYC__ return __mesabi_imod (ai, bi); #else return ai % bi; #endif } #if HAVE_LONG_LONG unsigned long long __udivdi3 (unsigned long long a, unsigned long long b) #else unsigned long __udivdi3 (unsigned long a, long ah, unsigned long b) #endif { #if !__TINYC__ static int stub = 0; if (__mes_debug () && !stub) eputs ("__udivdi3 stub\n"); stub = 1; #endif // FIXME: Actually use long long unsigned long ai = a; unsigned long bi = b; if (!b) return 0; #if __arm__ && __TINYC__ return __mesabi_udiv (ai, bi); #else return ai / bi; #endif } #if HAVE_LONG_LONG unsigned long long __umoddi3 (unsigned long long a, unsigned long long b) #else unsigned long __umoddi3 (unsigned long a, long ah, unsigned long b) #endif { #if !__TINYC__ static int stub = 0; if (__mes_debug () && !stub) eputs ("__umoddi3 stub\n"); stub = 1; #endif // FIXME: Actually use long long unsigned long ai = a; unsigned long bi = b; #if __arm__ && __TINYC__ return __mesabi_umod (ai, bi); #else return ai % bi; #endif } #if HAVE_LONG_LONG unsigned long long __lshrdi3 (unsigned long long a, long b) #else unsigned long __lshrdi3 (unsigned long a, long ah, long b) #endif { #if !__TINYC__ static int stub = 0; if (__mes_debug () && !stub) eputs ("__lshrdi3 stub\n"); stub = 1; return a >> b; #else // __TINYC__ for (int i = 0; i < b; i++) #if __arm__ a = __mesabi_udiv (a, 2); // I sure hope that doesn't endless recurse because of the optimizations in __mesabi_udiv that shift #else // !__arm__ a /= 2; #endif // !__arm__ return a; #endif // __TINYC__ } #if HAVE_LONG_LONG long long __ashldi3 (long long a, long b) #else long __ashldi3 (long a, long ah, long b) #endif { #if !__TINYC__ static int stub = 0; if (__mes_debug () && !stub) eputs ("__ashldi3 stub\n"); stub = 1; return a << b; #else // __TINYC__ for (int i = 0; i < b; i++) a += a; return a; #endif // __TINYC__ } #if HAVE_LONG_LONG long long __ashrdi3 (long long a, long b) #else long __ashrdi3 (long a, long ah, long b) #endif { #if !__TINYC__ static int stub = 0; if (__mes_debug () && !stub) eputs ("__ashrdi3 stub\n"); stub = 1; return a >> b; #else // __TINYC__ for (int i = 0; i < b; i++) #if __arm__ a = __mesabi_idiv (a, 2); // I sure hope that doesn't endless recurse because of the optimizations in __mesabi_udiv that shift #else // !__arm__ a /= 2; #endif // !__arm__ return a; #endif // __TINYC__ } #if HAVE_FLOAT_STUB || HAVE_FLOAT double __attribute__((weak)) #if HAVE_LONG_LONG && HAVE_FLOAT __floatundidf (unsigned long long a) #else __floatundidf (unsigned long a) #endif { #if !__TINYC__ static int stub = 0; if (__mes_debug () && !stub) eputs ("__floatundidf stub\n"); stub = 1; #endif return 0; } #if HAVE_LONG_LONG && HAVE_FLOAT long double __attribute__((weak)) __floatundixf (unsigned long long a) #else double __attribute__((weak)) __floatundixf (unsigned long a) #endif { #if !__TINYC__ static int stub = 0; if (__mes_debug () && !stub) eputs ("__floatundixf stub\n"); stub = 1; #endif return 0; } #if HAVE_LONG_LONG unsigned long long #else unsigned long #endif __attribute__((weak)) __fixunsxfdi (double a1) { #if !__TINYC__ static int stub = 0; if (__mes_debug () && !stub) eputs ("__fixunsxfdi stub\n"); stub = 1; #endif return 0; } #if __TINYC__ == 9226 long #elif __TINYC__ int #else // !__TINYCC__ long long #endif // !__TINYCC__ __attribute__((weak)) __fixdfdi (double a1) { #if !__TINYC__ static int stub = 0; if (__mes_debug () && !stub) eputs ("__fixdfdi stub\n"); stub = 1; #endif return 0; } #if HAVE_LONG_LONG unsigned long long #else unsigned long #endif __attribute__((weak)) __fixxfdi (double a1) { #if !__TINYC__ static int stub = 0; if (__mes_debug () && !stub) eputs ("__fixxfdi stub\n"); stub = 1; #endif return 0; } #if HAVE_LONG_LONG long long #else long #endif __attribute__((weak)) __fixsfdi (double a1) { #if !__TINYC__ static int stub = 0; if (__mes_debug () && !stub) eputs ("__fixsfdi stub\n"); stub = 1; #endif return 0; } double __attribute__((weak)) __fixunsdfdi (double num, double den) { #if !__TINYC__ static int stub = 0; if (__mes_debug () && !stub) eputs ("__fixunsdfdi stub\n"); stub = 1; #endif return 0; } #endif // HAVE_FLOAT_STUB || HAVE_FLOAT int __attribute__((weak)) __fixunsdfsi (int a, int b) { #if !__TINYC__ static int stub = 0; if (__mes_debug () && !stub) eputs ("__fixunsdfsi stub\n"); stub = 1; #endif return 0; } #if __arm__ int __attribute__((weak)) __floatdisf (int a, int b) { #if !__TINYC__ static int stub = 0; if (__mes_debug () && !stub) eputs ("__floatdisf stub\n"); stub = 1; #endif return 0; } int __attribute__((weak)) __floatdidf (int a, int b) { #if !__TINYC__ static int stub = 0; if (__mes_debug () && !stub) eputs ("__floatdidf stub\n"); stub = 1; #endif return 0; } int __divsi3 (int num, int den) { return __mesabi_idiv (num, den); } int __modsi3 (int num, int den) { return __mesabi_imod (num, den); } int __udivsi3 (int num, int den) { return __mesabi_udiv (num, den); } int __umodsi3 (int num, int den) { return __mesabi_umod (num, den); } #endif //__arm__
/* Getopt for GNU. Copyright (C) 1987, 88, 89, 90, 91, 1992 Free Software Foundation, Inc. Copyright (C) 2017,2018 Jan (janneke) Nieuwenhuizen <janneke@gnu.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; If not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> #include <string.h> #include <getopt.h> #if __MESC__ #define static #endif /* For communication from `getopt' to the caller. When `getopt' finds an option that takes an argument, the argument value is returned here. */ char *optarg = 0; /* Index in ARGV of the next element to be scanned. This is used for communication to and from the caller and for communication between successive calls to `getopt'. On entry to `getopt', zero means this is the first call; initialize. When `getopt' returns EOF, this is the index of the first of the non-option elements that the caller should itself scan. Otherwise, `optind' communicates from one call to the next how much of ARGV has been scanned so far. */ int optind = 0; /* The next char to be scanned in the option-element in which the last option character we returned was found. This allows us to pick up the scan where we left off. If this is zero, or a null string, it means resume the scan by advancing to the next ARGV-element. */ static char *nextchar; /* Callers store zero here to inhibit the error message for unrecognized options. */ int opterr = 1; /* Handle permutation of arguments. */ /* Describe the part of ARGV that contains non-options that have been skipped. `first_nonopt' is the index in ARGV of the first of them; `last_nonopt' is the index after the last of them. */ static int first_nonopt; static int last_nonopt; /* Scan elements of ARGV (whose length is ARGC) for option characters given in OPTSTRING. If an element of ARGV starts with '-', and is not exactly "-" or "--", then it is an option element. The characters of this element (aside from the initial '-') are option characters. If `getopt' is called repeatedly, it returns successively each of the option characters from each of the option elements. If `getopt' finds another option character, it returns that character, updating `optind' and `nextchar' so that the next call to `getopt' can resume the scan with the following option character or ARGV-element. If there are no more option characters, `getopt' returns `EOF'. Then `optind' is the index in ARGV of the first ARGV-element that is not an option. (The ARGV-elements have been permuted so that those that are not options now come last.) OPTSTRING is a string containing the legitimate option characters. If an option character is seen that is not listed in OPTSTRING, return '?' after printing an error message. If you set `opterr' to zero, the error message is suppressed but we still return '?'. If a char in OPTSTRING is followed by a colon, that means it wants an arg, so the following text in the same ARGV-element, or the text of the following ARGV-element, is returned in `optarg'. Two colons mean an option that wants an optional arg; if there is text in the current ARGV-element, it is returned in `optarg', otherwise `optarg' is set to zero. If OPTSTRING starts with `-' or `+', it requests different methods of handling the non-option ARGV-elements. Long-named options begin with `--' instead of `-'. Their names may be abbreviated as long as the abbreviation is unique or is an exact match for some defined option. If they have an argument, it follows the option name in the same ARGV-element, separated from the option name by a `=', or else the in next ARGV-element. When `getopt' finds a long-named option, it returns 0 if that option's `flag' field is nonzero, the value of the option's `val' field if the `flag' field is zero. The elements of ARGV aren't really const, because we permute them. But we pretend they're const in the prototype to be compatible with other systems. LONGOPTS is a vector of `struct option' terminated by an element containing a name which is zero. LONGIND returns the index in LONGOPT of the long-named option found. It is only valid when a long-named option has been found by the most recent call. If LONG_ONLY is nonzero, '-' as well as '--' can introduce long-named options. */ int _getopt_internal (int argc, char *const *argv, char const *optstring, struct option const *longopts, int *longind, int long_only) { int option_index; optarg = 0; /* Initialize the internal data when the first call is made. Start processing options with ARGV-element 1 (since ARGV-element 0 is the program name); the sequence of previously skipped non-option ARGV-elements is empty. */ if (optind == 0) { first_nonopt = last_nonopt = optind = 1; nextchar = NULL; } if (nextchar == NULL || *nextchar == '\0') { /* If we have done all the ARGV-elements, stop the scan and back over any non-options that we skipped and permuted. */ if (optind == argc) { /* Set the next-arg-index to point at the non-options that we previously skipped, so the caller will digest them. */ if (first_nonopt != last_nonopt) optind = first_nonopt; return EOF; } /* If we have come to a non-option and did not permute it, either stop the scan or describe it to the caller and pass it by. */ if ((argv[optind][0] != '-' || argv[optind][1] == '\0')) return EOF; /* We have found another option-ARGV-element. Start decoding its characters. */ nextchar = (argv[optind] + 1 + (longopts != NULL && argv[optind][1] == '-')); } if (longopts != NULL && ((argv[optind][0] == '-' && (argv[optind][1] == '-' || long_only)))) { const struct option *p; char *s = nextchar; int exact = 0; int ambig = 0; const struct option *pfound = NULL; int indfound; while (*s && *s != '=') s++; /* Test all options for either exact match or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp (p->name, nextchar, s - nextchar)) { if (s - nextchar == strlen (p->name)) { /* Exact match found. */ pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { /* First nonexact match found. */ pfound = p; indfound = option_index; } else /* Second nonexact match found. */ ambig = 1; } if (ambig && !exact) { if (opterr) fprintf (stderr, "%s: option `%s' is ambiguous\n", argv[0], argv[optind]); nextchar += strlen (nextchar); optind++; return '?'; } if (pfound != NULL) { option_index = indfound; optind++; if (*s) { /* Don't test has_arg with >, because some C compilers don't allow it to be used on enums. */ if (pfound->has_arg) optarg = s + 1; else { if (opterr) { if (argv[optind - 1][1] == '-') /* --option */ fprintf (stderr, "%s: option `--%s' doesn't allow an argument\n", argv[0], pfound->name); else /* +option or -option */ fprintf (stderr, "%s: option `%c%s' doesn't allow an argument\n", argv[0], argv[optind - 1][0], pfound->name); } nextchar += strlen (nextchar); return '?'; } } else if (pfound->has_arg == 1) { if (optind < argc) optarg = argv[optind++]; else { if (opterr) fprintf (stderr, "%s: option `%s' requires an argument\n", argv[0], argv[optind - 1]); nextchar += strlen (nextchar); return '?'; } } nextchar += strlen (nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } /* Can't find it as a long option. If this is not getopt_long_only, or the option starts with '--' or is not a valid short option, then it's an error. Otherwise interpret it as a short option. */ if (!long_only || argv[optind][1] == '-' || strchr (optstring, *nextchar) == NULL) { if (opterr) { if (argv[optind][1] == '-') /* --option */ fprintf (stderr, "%s: unrecognized option `--%s'\n", argv[0], nextchar); else /* +option or -option */ fprintf (stderr, "%s: unrecognized option `%c%s'\n", argv[0], argv[optind][0], nextchar); } nextchar += strlen (nextchar); optind++; return '?'; } } /* Look at and handle the next option-character. */ { char c = *nextchar++; char *temp = strchr (optstring, c); /* Increment `optind' when we start to process its last character. */ if (*nextchar == '\0') optind++; if (temp == NULL || c == ':') { if (opterr) { if (c < 040 || c >= 0177) fprintf (stderr, "%s: unrecognized option, character code 0%o\n", argv[0], c); else fprintf (stderr, "%s: unrecognized option `-%c'\n", argv[0], c); } return '?'; } if (temp[1] == ':') { if (temp[2] == ':') { /* This is an option that accepts an argument optionally. */ if (*nextchar != '\0') { optarg = nextchar; optind++; } else optarg = 0; nextchar = NULL; } else { /* This is an option that requires an argument. */ if (*nextchar != 0) { optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ optind++; } else if (optind == argc) { if (opterr) fprintf (stderr, "%s: option `-%c' requires an argument\n", argv[0], c); c = '?'; } else /* We already incremented `optind' once; increment it again when taking next ARGV-elt as argument. */ optarg = argv[optind++]; nextchar = NULL; } } return c; } } int getopt (int argc, char *const *argv, char const *options) { return _getopt_internal (argc, argv, options, (const struct option *) 0, (int *) 0, 0); } int getopt_long (int argc, char *const *argv, char const *options, struct option const *long_options, int *opt_index) { return _getopt_internal (argc, argv, options, long_options, opt_index, 0); } int getopt_long_only (int argc, char *const *argv, char const *options, struct option const *long_options, int *opt_index) { return _getopt_internal (argc, argv, options, long_options, opt_index, 1); }
/* TCC runtime library. Parts of this code are (c) 2002 Fabrice Bellard Copyright (C) 1987, 1988, 1992, 1994, 1995 Free Software Foundation, Inc. This file is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. In addition to the permissions in the GNU General Public License, the Free Software Foundation gives you unlimited permission to link the compiled version of this file into combinations with other programs, and to distribute those combinations without any restriction coming from the use of this file. (The General Public License restrictions do apply in other respects; for example, they cover modification of the file, and distribution when not linked into a combine executable.) This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #define W_TYPE_SIZE 32 #define BITS_PER_UNIT 8 typedef int Wtype; typedef unsigned int UWtype; typedef unsigned int USItype; typedef long long DWtype; typedef unsigned long long UDWtype; struct DWstruct { Wtype low, high; }; typedef union { struct DWstruct s; DWtype ll; } DWunion; typedef long double XFtype; #define WORD_SIZE (sizeof (Wtype) * BITS_PER_UNIT) #define HIGH_WORD_COEFF (((UDWtype) 1) << WORD_SIZE) /* the following deal with IEEE single-precision numbers */ #define EXCESS 126 #define SIGNBIT 0x80000000 #define HIDDEN (1 << 23) #define SIGN(fp) ((fp) & SIGNBIT) #define EXP(fp) (((fp) >> 23) & 0xFF) #define MANT(fp) (((fp) & 0x7FFFFF) | HIDDEN) #define PACK(s,e,m) ((s) | ((e) << 23) | (m)) /* the following deal with IEEE double-precision numbers */ #define EXCESSD 1022 #define HIDDEND (1 << 20) #define EXPD(fp) (((fp.l.upper) >> 20) & 0x7FF) #define SIGND(fp) ((fp.l.upper) & SIGNBIT) #define MANTD(fp) (((((fp.l.upper) & 0xFFFFF) | HIDDEND) << 10) | \ (fp.l.lower >> 22)) #define HIDDEND_LL ((long long)1 << 52) #define MANTD_LL(fp) ((fp.ll & (HIDDEND_LL-1)) | HIDDEND_LL) #define PACKD_LL(s,e,m) (((long long)((s)+((e)<<20))<<32)|(m)) /* the following deal with x86 long double-precision numbers */ #define EXCESSLD 16382 #define EXPLD(fp) (fp.l.upper & 0x7fff) #define SIGNLD(fp) ((fp.l.upper) & 0x8000) /* only for x86 */ union ldouble_long { long double ld; struct { unsigned long long lower; unsigned short upper; } l; }; union double_long { double d; #if 1 struct { unsigned int lower; int upper; } l; #else struct { int upper; unsigned int lower; } l; #endif long long ll; }; union float_long { float f; unsigned int l; }; /* XXX: we don't support several builtin supports for now */ #if !defined(TCC_TARGET_X86_64) && !defined(TCC_TARGET_ARM) /* XXX: use gcc/tcc intrinsic ? */ #if defined(TCC_TARGET_I386) #define sub_ddmmss(sh, sl, ah, al, bh, bl) \ __asm__ ("subl %5,%1\n\tsbbl %3,%0" \ : "=r" ((USItype) (sh)), \ "=&r" ((USItype) (sl)) \ : "0" ((USItype) (ah)), \ "g" ((USItype) (bh)), \ "1" ((USItype) (al)), \ "g" ((USItype) (bl))) #define umul_ppmm(w1, w0, u, v) \ __asm__ ("mull %3" \ : "=a" ((USItype) (w0)), \ "=d" ((USItype) (w1)) \ : "%0" ((USItype) (u)), \ "rm" ((USItype) (v))) #define udiv_qrnnd(q, r, n1, n0, dv) \ __asm__ ("divl %4" \ : "=a" ((USItype) (q)), \ "=d" ((USItype) (r)) \ : "0" ((USItype) (n0)), \ "1" ((USItype) (n1)), \ "rm" ((USItype) (dv))) #define count_leading_zeros(count, x) \ do { \ USItype __cbtmp; \ __asm__ ("bsrl %1,%0" \ : "=r" (__cbtmp) : "rm" ((USItype) (x))); \ (count) = __cbtmp ^ 31; \ } while (0) #else #error unsupported CPU type #endif /* most of this code is taken from libgcc2.c from gcc */ static UDWtype __udivmoddi4 (UDWtype n, UDWtype d, UDWtype *rp) { DWunion ww; DWunion nn, dd; DWunion rr; UWtype d0, d1, n0, n1, n2; UWtype q0, q1; UWtype b, bm; nn.ll = n; dd.ll = d; d0 = dd.s.low; d1 = dd.s.high; n0 = nn.s.low; n1 = nn.s.high; #if !defined(UDIV_NEEDS_NORMALIZATION) if (d1 == 0) { if (d0 > n1) { /* 0q = nn / 0D */ udiv_qrnnd (q0, n0, n1, n0, d0); q1 = 0; /* Remainder in n0. */ } else { /* qq = NN / 0d */ if (d0 == 0) d0 = 1 / d0; /* Divide intentionally by zero. */ udiv_qrnnd (q1, n1, 0, n1, d0); udiv_qrnnd (q0, n0, n1, n0, d0); /* Remainder in n0. */ } if (rp != 0) { rr.s.low = n0; rr.s.high = 0; *rp = rr.ll; } } #else /* UDIV_NEEDS_NORMALIZATION */ if (d1 == 0) { if (d0 > n1) { /* 0q = nn / 0D */ count_leading_zeros (bm, d0); if (bm != 0) { /* Normalize, i.e. make the most significant bit of the denominator set. */ d0 = d0 << bm; n1 = (n1 << bm) | (n0 >> (W_TYPE_SIZE - bm)); n0 = n0 << bm; } udiv_qrnnd (q0, n0, n1, n0, d0); q1 = 0; /* Remainder in n0 >> bm. */ } else { /* qq = NN / 0d */ if (d0 == 0) d0 = 1 / d0; /* Divide intentionally by zero. */ count_leading_zeros (bm, d0); if (bm == 0) { /* From (n1 >= d0) /\ (the most significant bit of d0 is set), conclude (the most significant bit of n1 is set) /\ (the leading quotient digit q1 = 1). This special case is necessary, not an optimization. (Shifts counts of W_TYPE_SIZE are undefined.) */ n1 -= d0; q1 = 1; } else { /* Normalize. */ b = W_TYPE_SIZE - bm; d0 = d0 << bm; n2 = n1 >> b; n1 = (n1 << bm) | (n0 >> b); n0 = n0 << bm; udiv_qrnnd (q1, n1, n2, n1, d0); } /* n1 != d0... */ udiv_qrnnd (q0, n0, n1, n0, d0); /* Remainder in n0 >> bm. */ } if (rp != 0) { rr.s.low = n0 >> bm; rr.s.high = 0; *rp = rr.ll; } } #endif /* UDIV_NEEDS_NORMALIZATION */ else { if (d1 > n1) { /* 00 = nn / DD */ q0 = 0; q1 = 0; /* Remainder in n1n0. */ if (rp != 0) { rr.s.low = n0; rr.s.high = n1; *rp = rr.ll; } } else { /* 0q = NN / dd */ count_leading_zeros (bm, d1); if (bm == 0) { /* From (n1 >= d1) /\ (the most significant bit of d1 is set), conclude (the most significant bit of n1 is set) /\ (the quotient digit q0 = 0 or 1). This special case is necessary, not an optimization. */ /* The condition on the next line takes advantage of that n1 >= d1 (true due to program flow). */ if (n1 > d1 || n0 >= d0) { q0 = 1; sub_ddmmss (n1, n0, n1, n0, d1, d0); } else q0 = 0; q1 = 0; if (rp != 0) { rr.s.low = n0; rr.s.high = n1; *rp = rr.ll; } } else { UWtype m1, m0; /* Normalize. */ b = W_TYPE_SIZE - bm; d1 = (d1 << bm) | (d0 >> b); d0 = d0 << bm; n2 = n1 >> b; n1 = (n1 << bm) | (n0 >> b); n0 = n0 << bm; udiv_qrnnd (q0, n1, n2, n1, d1); umul_ppmm (m1, m0, q0, d0); if (m1 > n1 || (m1 == n1 && m0 > n0)) { q0--; sub_ddmmss (m1, m0, m1, m0, d1, d0); } q1 = 0; /* Remainder in (n1n0 - m1m0) >> bm. */ if (rp != 0) { sub_ddmmss (n1, n0, n1, n0, m1, m0); rr.s.low = (n1 << b) | (n0 >> bm); rr.s.high = n1 >> bm; *rp = rr.ll; } } } } ww.s.low = q0; ww.s.high = q1; return ww.ll; } #define __negdi2(a) (-(a)) long long __divdi3(long long u, long long v) { int c = 0; DWunion uu, vv; DWtype w; uu.ll = u; vv.ll = v; if (uu.s.high < 0) { c = ~c; uu.ll = __negdi2 (uu.ll); } if (vv.s.high < 0) { c = ~c; vv.ll = __negdi2 (vv.ll); } w = __udivmoddi4 (uu.ll, vv.ll, (UDWtype *) 0); if (c) w = __negdi2 (w); return w; } long long __moddi3(long long u, long long v) { int c = 0; DWunion uu, vv; DWtype w; uu.ll = u; vv.ll = v; if (uu.s.high < 0) { c = ~c; uu.ll = __negdi2 (uu.ll); } if (vv.s.high < 0) vv.ll = __negdi2 (vv.ll); __udivmoddi4 (uu.ll, vv.ll, (UDWtype *) &w); if (c) w = __negdi2 (w); return w; } unsigned long long __udivdi3(unsigned long long u, unsigned long long v) { return __udivmoddi4 (u, v, (UDWtype *) 0); } unsigned long long __umoddi3(unsigned long long u, unsigned long long v) { UDWtype w; __udivmoddi4 (u, v, &w); return w; } /* XXX: fix tcc's code generator to do this instead */ long long __ashrdi3(long long a, int b) { #ifdef __TINYC__ DWunion u; u.ll = a; if (b >= 32) { u.s.low = u.s.high >> (b - 32); u.s.high = u.s.high >> 31; } else if (b != 0) { u.s.low = ((unsigned)u.s.low >> b) | (u.s.high << (32 - b)); u.s.high = u.s.high >> b; } return u.ll; #else return a >> b; #endif } /* XXX: fix tcc's code generator to do this instead */ unsigned long long __lshrdi3(unsigned long long a, int b) { #ifdef __TINYC__ DWunion u; u.ll = a; if (b >= 32) { u.s.low = (unsigned)u.s.high >> (b - 32); u.s.high = 0; } else if (b != 0) { u.s.low = ((unsigned)u.s.low >> b) | (u.s.high << (32 - b)); u.s.high = (unsigned)u.s.high >> b; } return u.ll; #else return a >> b; #endif } /* XXX: fix tcc's code generator to do this instead */ long long __ashldi3(long long a, int b) { #ifdef __TINYC__ DWunion u; u.ll = a; if (b >= 32) { u.s.high = (unsigned)u.s.low << (b - 32); u.s.low = 0; } else if (b != 0) { u.s.high = ((unsigned)u.s.high << b) | ((unsigned)u.s.low >> (32 - b)); u.s.low = (unsigned)u.s.low << b; } return u.ll; #else return a << b; #endif } #endif /* !__x86_64__ */ /* XXX: fix tcc's code generator to do this instead */ float __floatundisf(unsigned long long a) { DWunion uu; XFtype r; uu.ll = a; if (uu.s.high >= 0) { return (float)uu.ll; } else { r = (XFtype)uu.ll; r += 18446744073709551616.0; return (float)r; } } double __floatundidf(unsigned long long a) { DWunion uu; XFtype r; uu.ll = a; if (uu.s.high >= 0) { return (double)uu.ll; } else { r = (XFtype)uu.ll; r += 18446744073709551616.0; return (double)r; } } long double __floatundixf(unsigned long long a) { DWunion uu; XFtype r; uu.ll = a; if (uu.s.high >= 0) { return (long double)uu.ll; } else { r = (XFtype)uu.ll; r += 18446744073709551616.0; return (long double)r; } } unsigned long long __fixunssfdi (float a1) { register union float_long fl1; register int exp; register unsigned long l; fl1.f = a1; if (fl1.l == 0) return (0); exp = EXP (fl1.l) - EXCESS - 24; l = MANT(fl1.l); if (exp >= 41) return (unsigned long long)-1; else if (exp >= 0) return (unsigned long long)l << exp; else if (exp >= -23) return l >> -exp; else return 0; } long long __fixsfdi (float a1) { long long ret; int s; ret = __fixunssfdi((s = a1 >= 0) ? a1 : -a1); return s ? ret : -ret; } unsigned long long __fixunsdfdi (double a1) { register union double_long dl1; register int exp; register unsigned long long l; dl1.d = a1; if (dl1.ll == 0) return (0); exp = EXPD (dl1) - EXCESSD - 53; l = MANTD_LL(dl1); if (exp >= 12) return (unsigned long long)-1; else if (exp >= 0) return l << exp; else if (exp >= -52) return l >> -exp; else return 0; } long long __fixdfdi (double a1) { long long ret; int s; ret = __fixunsdfdi((s = a1 >= 0) ? a1 : -a1); return s ? ret : -ret; } #ifndef TCC_TARGET_ARM unsigned long long __fixunsxfdi (long double a1) { register union ldouble_long dl1; register int exp; register unsigned long long l; dl1.ld = a1; if (dl1.l.lower == 0 && dl1.l.upper == 0) return (0); exp = EXPLD (dl1) - EXCESSLD - 64; l = dl1.l.lower; if (exp > 0) return (unsigned long long)-1; else if (exp >= -63) return l >> -exp; else return 0; } long long __fixxfdi (long double a1) { long long ret; int s; ret = __fixunsxfdi((s = a1 >= 0) ? a1 : -a1); return s ? ret : -ret; } #endif /* !ARM */
7897c2ba182e92765856a9e3ddd2bdfcd69988b3ddc45b3ef932dc3ec041041a /usr/bin/mes-tcc 64bdb36f0048744957454b6ad186d37db4a7fcc392f67bf052b9bc7ec033b963 /usr/bin/boot0-tcc 18af79e0e3c4bfeaced2b0867561bc9fd370cbfcc78860de5a70926374ba7c53 /usr/bin/boot1-tcc d6fa0c2006f6c8fefb4f3750fade14aa884670637098857d06aef3c7a9bd02d8 /usr/bin/boot2-tcc 93b2c1c567f5938435fbe6c0115276f6073bd46a0a92791b164ca2752180caf5 /usr/bin/boot3-tcc a40305e6d2acb3dc64dbc80f64952431f4728eeff03f6e7c91c737cbf6e1bdda /usr/bin/boot4-tcc c56d99e284dfecae047e792b049c0cee022452f13262d6b3c869ec1c9935b276 /usr/bin/tcc 96f93f2d281ee6996767b25fbff4441e8335e7eeaeab060c1d4b698366b277c9 /usr/lib/mes/libc.a 12c07ae103e7e3b390150a79e5c600d88de14e9bb73a066f6342582729ef5a3f /usr/lib/mes/libgetopt.a d1168ee9b528e39f0b40e8d51fb7fa3619c4a5ee928137f7faf6d0879b0916b0 /usr/lib/mes/crt1.o 09d4f9821a2566f7e56381a19259c41bd97f3c5ed83f490705acbfd1139a7736 /usr/lib/mes/crti.o 461ca1494737fab86fe1c1d3addeaf9d0ece413e353abcdea8674db3f700cda3 /usr/lib/mes/crtn.o ac11f09698f092ed76ae40ebcd56cf3f2b903ea1333ef7537a00673dd6f73da7 /usr/lib/mes/tcc/libtcc1.a
https://download.savannah.gnu.org/releases/tinycc/tcc-0.9.27.tar.bz2 de23af78fca90ce32dff2dd45b3432b2334740bb9bb7b05bf60fdbfc396ceb9c
if (ret == 1) return ar_usage(ret); if ((fh = fopen(argv[i_lib], "wb")) == NULL) { fprintf(stderr, "tcc: ar: can't open file %s \n", argv[i_lib]); goto the_end; }
if (ret == 1) return ar_usage(ret);
// write header
if ((fh = fopen(argv[i_lib], "wb")) == NULL) { fprintf(stderr, "tcc: ar: can't open file %s \n", argv[i_lib]); goto the_end; } // write header
/* * ELF file handling for TCC * * Copyright (c) 2001-2004 Fabrice Bellard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "tcc.h" /* Define this to get some debug output during relocation processing. */ #undef DEBUG_RELOC /********************************************************/ /* global variables */ ST_DATA Section *text_section, *data_section, *bss_section; /* predefined sections */ ST_DATA Section *common_section; ST_DATA Section *cur_text_section; /* current section where function code is generated */ #ifdef CONFIG_TCC_ASM ST_DATA Section *last_text_section; /* to handle .previous asm directive */ #endif #ifdef CONFIG_TCC_BCHECK /* bound check related sections */ ST_DATA Section *bounds_section; /* contains global data bound description */ ST_DATA Section *lbounds_section; /* contains local data bound description */ #endif /* symbol sections */ ST_DATA Section *symtab_section; /* debug sections */ ST_DATA Section *stab_section, *stabstr_section; /* XXX: avoid static variable */ static int new_undef_sym = 0; /* Is there a new undefined sym since last new_undef_sym() */ /* special flag to indicate that the section should not be linked to the other ones */ #define SHF_PRIVATE 0x80000000 /* section is dynsymtab_section */ #define SHF_DYNSYM 0x40000000 /* ------------------------------------------------------------------------- */ ST_FUNC void tccelf_new(TCCState *s) { /* no section zero */ dynarray_add(&s->sections, &s->nb_sections, NULL); /* create standard sections */ text_section = new_section(s, ".text", SHT_PROGBITS, SHF_ALLOC | SHF_EXECINSTR); data_section = new_section(s, ".data", SHT_PROGBITS, SHF_ALLOC | SHF_WRITE); bss_section = new_section(s, ".bss", SHT_NOBITS, SHF_ALLOC | SHF_WRITE); common_section = new_section(s, ".common", SHT_NOBITS, SHF_PRIVATE); common_section->sh_num = SHN_COMMON; /* symbols are always generated for linking stage */ symtab_section = new_symtab(s, ".symtab", SHT_SYMTAB, 0, ".strtab", ".hashtab", SHF_PRIVATE); s->symtab = symtab_section; /* private symbol table for dynamic symbols */ s->dynsymtab_section = new_symtab(s, ".dynsymtab", SHT_SYMTAB, SHF_PRIVATE|SHF_DYNSYM, ".dynstrtab", ".dynhashtab", SHF_PRIVATE); get_sym_attr(s, 0, 1); } #ifdef CONFIG_TCC_BCHECK ST_FUNC void tccelf_bounds_new(TCCState *s) { /* create bounds sections */ bounds_section = new_section(s, ".bounds", SHT_PROGBITS, SHF_ALLOC); lbounds_section = new_section(s, ".lbounds", SHT_PROGBITS, SHF_ALLOC); } #endif ST_FUNC void tccelf_stab_new(TCCState *s) { stab_section = new_section(s, ".stab", SHT_PROGBITS, 0); stab_section->sh_entsize = sizeof(Stab_Sym); stabstr_section = new_section(s, ".stabstr", SHT_STRTAB, 0); put_elf_str(stabstr_section, ""); stab_section->link = stabstr_section; /* put first entry */ put_stabs("", 0, 0, 0, 0); } static void free_section(Section *s) { tcc_free(s->data); } ST_FUNC void tccelf_delete(TCCState *s1) { int i; /* free all sections */ for(i = 1; i < s1->nb_sections; i++) free_section(s1->sections[i]); dynarray_reset(&s1->sections, &s1->nb_sections); for(i = 0; i < s1->nb_priv_sections; i++) free_section(s1->priv_sections[i]); dynarray_reset(&s1->priv_sections, &s1->nb_priv_sections); /* free any loaded DLLs */ #ifdef TCC_IS_NATIVE for ( i = 0; i < s1->nb_loaded_dlls; i++) { DLLReference *ref = s1->loaded_dlls[i]; if ( ref->handle ) # ifdef _WIN32 FreeLibrary((HMODULE)ref->handle); # else dlclose(ref->handle); # endif } #endif /* free loaded dlls array */ dynarray_reset(&s1->loaded_dlls, &s1->nb_loaded_dlls); tcc_free(s1->sym_attrs); symtab_section = NULL; /* for tccrun.c:rt_printline() */ } /* save section data state */ ST_FUNC void tccelf_begin_file(TCCState *s1) { Section *s; int i; for (i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; s->sh_offset = s->data_offset; } /* disable symbol hashing during compilation */ s = s1->symtab, s->reloc = s->hash, s->hash = NULL; #if defined TCC_TARGET_X86_64 && defined TCC_TARGET_PE s1->uw_sym = 0; #endif } /* At the end of compilation, convert any UNDEF syms to global, and merge with previously existing symbols */ ST_FUNC void tccelf_end_file(TCCState *s1) { Section *s = s1->symtab; int first_sym, nb_syms, *tr, i; first_sym = s->sh_offset / sizeof (ElfSym); nb_syms = s->data_offset / sizeof (ElfSym) - first_sym; s->data_offset = s->sh_offset; s->link->data_offset = s->link->sh_offset; s->hash = s->reloc, s->reloc = NULL; tr = tcc_mallocz(nb_syms * sizeof *tr); for (i = 0; i < nb_syms; ++i) { ElfSym *sym = (ElfSym*)s->data + first_sym + i; if (sym->st_shndx == SHN_UNDEF && ELFW(ST_BIND)(sym->st_info) == STB_LOCAL) sym->st_info = ELFW(ST_INFO)(STB_GLOBAL, ELFW(ST_TYPE)(sym->st_info)); tr[i] = set_elf_sym(s, sym->st_value, sym->st_size, sym->st_info, sym->st_other, sym->st_shndx, s->link->data + sym->st_name); } /* now update relocations */ for (i = 1; i < s1->nb_sections; i++) { Section *sr = s1->sections[i]; if (sr->sh_type == SHT_RELX && sr->link == s) { ElfW_Rel *rel = (ElfW_Rel*)(sr->data + sr->sh_offset); ElfW_Rel *rel_end = (ElfW_Rel*)(sr->data + sr->data_offset); for (; rel < rel_end; ++rel) { int n = ELFW(R_SYM)(rel->r_info) - first_sym; //if (n < 0) tcc_error("internal: invalid symbol index in relocation"); rel->r_info = ELFW(R_INFO)(tr[n], ELFW(R_TYPE)(rel->r_info)); } } } tcc_free(tr); } ST_FUNC Section *new_section(TCCState *s1, const char *name, int sh_type, int sh_flags) { Section *sec; sec = tcc_mallocz(sizeof(Section) + strlen(name)); strcpy(sec->name, name); sec->sh_type = sh_type; sec->sh_flags = sh_flags; switch(sh_type) { case SHT_HASH: case SHT_REL: case SHT_RELA: case SHT_DYNSYM: case SHT_SYMTAB: case SHT_DYNAMIC: sec->sh_addralign = 4; break; case SHT_STRTAB: sec->sh_addralign = 1; break; default: sec->sh_addralign = PTR_SIZE; /* gcc/pcc default alignment */ break; } if (sh_flags & SHF_PRIVATE) { dynarray_add(&s1->priv_sections, &s1->nb_priv_sections, sec); } else { sec->sh_num = s1->nb_sections; dynarray_add(&s1->sections, &s1->nb_sections, sec); } return sec; } ST_FUNC Section *new_symtab(TCCState *s1, const char *symtab_name, int sh_type, int sh_flags, const char *strtab_name, const char *hash_name, int hash_sh_flags) { Section *symtab, *strtab, *hash; int *ptr, nb_buckets; symtab = new_section(s1, symtab_name, sh_type, sh_flags); symtab->sh_entsize = sizeof(ElfW(Sym)); strtab = new_section(s1, strtab_name, SHT_STRTAB, sh_flags); put_elf_str(strtab, ""); symtab->link = strtab; put_elf_sym(symtab, 0, 0, 0, 0, 0, NULL); nb_buckets = 1; hash = new_section(s1, hash_name, SHT_HASH, hash_sh_flags); hash->sh_entsize = sizeof(int); symtab->hash = hash; hash->link = symtab; ptr = section_ptr_add(hash, (2 + nb_buckets + 1) * sizeof(int)); ptr[0] = nb_buckets; ptr[1] = 1; memset(ptr + 2, 0, (nb_buckets + 1) * sizeof(int)); return symtab; } /* realloc section and set its content to zero */ ST_FUNC void section_realloc(Section *sec, unsigned long new_size) { unsigned long size; unsigned char *data; size = sec->data_allocated; if (size == 0) size = 1; while (size < new_size) size = size * 2; data = tcc_realloc(sec->data, size); memset(data + sec->data_allocated, 0, size - sec->data_allocated); sec->data = data; sec->data_allocated = size; } /* reserve at least 'size' bytes aligned per 'align' in section 'sec' from current offset, and return the aligned offset */ ST_FUNC size_t section_add(Section *sec, addr_t size, int align) { size_t offset, offset1; offset = (sec->data_offset + align - 1) & -align; offset1 = offset + size; if (sec->sh_type != SHT_NOBITS && offset1 > sec->data_allocated) section_realloc(sec, offset1); sec->data_offset = offset1; if (align > sec->sh_addralign) sec->sh_addralign = align; return offset; } /* reserve at least 'size' bytes in section 'sec' from sec->data_offset. */ ST_FUNC void *section_ptr_add(Section *sec, addr_t size) { size_t offset = section_add(sec, size, 1); return sec->data + offset; } /* reserve at least 'size' bytes from section start */ ST_FUNC void section_reserve(Section *sec, unsigned long size) { if (size > sec->data_allocated) section_realloc(sec, size); if (size > sec->data_offset) sec->data_offset = size; } /* return a reference to a section, and create it if it does not exists */ ST_FUNC Section *find_section(TCCState *s1, const char *name) { Section *sec; int i; for(i = 1; i < s1->nb_sections; i++) { sec = s1->sections[i]; if (!strcmp(name, sec->name)) return sec; } /* sections are created as PROGBITS */ return new_section(s1, name, SHT_PROGBITS, SHF_ALLOC); } /* ------------------------------------------------------------------------- */ ST_FUNC int put_elf_str(Section *s, const char *sym) { int offset, len; char *ptr; len = strlen(sym) + 1; offset = s->data_offset; ptr = section_ptr_add(s, len); memmove(ptr, sym, len); return offset; } /* elf symbol hashing function */ static unsigned long elf_hash(const unsigned char *name) { unsigned long h = 0, g; while (*name) { h = (h << 4) + *name++; g = h & 0xf0000000; if (g) h ^= g >> 24; h &= ~g; } return h; } /* rebuild hash table of section s */ /* NOTE: we do factorize the hash table code to go faster */ static void rebuild_hash(Section *s, unsigned int nb_buckets) { ElfW(Sym) *sym; int *ptr, *hash, nb_syms, sym_index, h; unsigned char *strtab; strtab = s->link->data; nb_syms = s->data_offset / sizeof(ElfW(Sym)); if (!nb_buckets) nb_buckets = ((int*)s->hash->data)[0]; s->hash->data_offset = 0; ptr = section_ptr_add(s->hash, (2 + nb_buckets + nb_syms) * sizeof(int)); ptr[0] = nb_buckets; ptr[1] = nb_syms; ptr += 2; hash = ptr; memset(hash, 0, (nb_buckets + 1) * sizeof(int)); ptr += nb_buckets + 1; sym = (ElfW(Sym) *)s->data + 1; for(sym_index = 1; sym_index < nb_syms; sym_index++) { if (ELFW(ST_BIND)(sym->st_info) != STB_LOCAL) { h = elf_hash(strtab + sym->st_name) % nb_buckets; *ptr = hash[h]; hash[h] = sym_index; } else { *ptr = 0; } ptr++; sym++; } } /* return the symbol number */ ST_FUNC int put_elf_sym(Section *s, addr_t value, unsigned long size, int info, int other, int shndx, const char *name) { int name_offset, sym_index; int nbuckets, h; ElfW(Sym) *sym; Section *hs; sym = section_ptr_add(s, sizeof(ElfW(Sym))); if (name && name[0]) name_offset = put_elf_str(s->link, name); else name_offset = 0; /* XXX: endianness */ sym->st_name = name_offset; sym->st_value = value; sym->st_size = size; sym->st_info = info; sym->st_other = other; sym->st_shndx = shndx; sym_index = sym - (ElfW(Sym) *)s->data; hs = s->hash; if (hs) { int *ptr, *base; ptr = section_ptr_add(hs, sizeof(int)); base = (int *)hs->data; /* only add global or weak symbols. */ if (ELFW(ST_BIND)(info) != STB_LOCAL) { /* add another hashing entry */ nbuckets = base[0]; h = elf_hash((unsigned char *)s->link->data + name_offset) % nbuckets; *ptr = base[2 + h]; base[2 + h] = sym_index; base[1]++; /* we resize the hash table */ hs->nb_hashed_syms++; if (hs->nb_hashed_syms > 2 * nbuckets) { rebuild_hash(s, 2 * nbuckets); } } else { *ptr = 0; base[1]++; } } return sym_index; } ST_FUNC int find_elf_sym(Section *s, const char *name) { ElfW(Sym) *sym; Section *hs; int nbuckets, sym_index, h; const char *name1; hs = s->hash; if (!hs) return 0; nbuckets = ((int *)hs->data)[0]; h = elf_hash((unsigned char *) name) % nbuckets; sym_index = ((int *)hs->data)[2 + h]; while (sym_index != 0) { sym = &((ElfW(Sym) *)s->data)[sym_index]; name1 = (char *) s->link->data + sym->st_name; if (!strcmp(name, name1)) return sym_index; sym_index = ((int *)hs->data)[2 + nbuckets + sym_index]; } return 0; } /* return elf symbol value, signal error if 'err' is nonzero */ ST_FUNC addr_t get_elf_sym_addr(TCCState *s, const char *name, int err) { int sym_index; ElfW(Sym) *sym; sym_index = find_elf_sym(s->symtab, name); sym = &((ElfW(Sym) *)s->symtab->data)[sym_index]; if (!sym_index || sym->st_shndx == SHN_UNDEF) { if (err) tcc_error("%s not defined", name); return 0; } return sym->st_value; } /* return elf symbol value */ LIBTCCAPI void *tcc_get_symbol(TCCState *s, const char *name) { return (void*)(uintptr_t)get_elf_sym_addr(s, name, 0); } #if defined TCC_IS_NATIVE || defined TCC_TARGET_PE /* return elf symbol value or error */ ST_FUNC void* tcc_get_symbol_err(TCCState *s, const char *name) { return (void*)(uintptr_t)get_elf_sym_addr(s, name, 1); } #endif /* add an elf symbol : check if it is already defined and patch it. Return symbol index. NOTE that sh_num can be SHN_UNDEF. */ ST_FUNC int set_elf_sym(Section *s, addr_t value, unsigned long size, int info, int other, int shndx, const char *name) { ElfW(Sym) *esym; int sym_bind, sym_index, sym_type, esym_bind; unsigned char sym_vis, esym_vis, new_vis; sym_bind = ELFW(ST_BIND)(info); sym_type = ELFW(ST_TYPE)(info); sym_vis = ELFW(ST_VISIBILITY)(other); if (sym_bind != STB_LOCAL) { /* we search global or weak symbols */ sym_index = find_elf_sym(s, name); if (!sym_index) goto do_def; esym = &((ElfW(Sym) *)s->data)[sym_index]; if (esym->st_value == value && esym->st_size == size && esym->st_info == info && esym->st_other == other && esym->st_shndx == shndx) return sym_index; if (esym->st_shndx != SHN_UNDEF) { esym_bind = ELFW(ST_BIND)(esym->st_info); /* propagate the most constraining visibility */ /* STV_DEFAULT(0)<STV_PROTECTED(3)<STV_HIDDEN(2)<STV_INTERNAL(1) */ esym_vis = ELFW(ST_VISIBILITY)(esym->st_other); if (esym_vis == STV_DEFAULT) { new_vis = sym_vis; } else if (sym_vis == STV_DEFAULT) { new_vis = esym_vis; } else { new_vis = (esym_vis < sym_vis) ? esym_vis : sym_vis; } esym->st_other = (esym->st_other & ~ELFW(ST_VISIBILITY)(-1)) | new_vis; other = esym->st_other; /* in case we have to patch esym */ if (shndx == SHN_UNDEF) { /* ignore adding of undefined symbol if the corresponding symbol is already defined */ } else if (sym_bind == STB_GLOBAL && esym_bind == STB_WEAK) { /* global overrides weak, so patch */ goto do_patch; } else if (sym_bind == STB_WEAK && esym_bind == STB_GLOBAL) { /* weak is ignored if already global */ } else if (sym_bind == STB_WEAK && esym_bind == STB_WEAK) { /* keep first-found weak definition, ignore subsequents */ } else if (sym_vis == STV_HIDDEN || sym_vis == STV_INTERNAL) { /* ignore hidden symbols after */ } else if ((esym->st_shndx == SHN_COMMON || esym->st_shndx == bss_section->sh_num) && (shndx < SHN_LORESERVE && shndx != bss_section->sh_num)) { /* data symbol gets precedence over common/bss */ goto do_patch; } else if (shndx == SHN_COMMON || shndx == bss_section->sh_num) { /* data symbol keeps precedence over common/bss */ } else if (s->sh_flags & SHF_DYNSYM) { /* we accept that two DLL define the same symbol */ } else if (esym->st_other & ST_ASM_SET) { /* If the existing symbol came from an asm .set we can override. */ goto do_patch; } else { #if 0 printf("new_bind=%x new_shndx=%x new_vis=%x old_bind=%x old_shndx=%x old_vis=%x\n", sym_bind, shndx, new_vis, esym_bind, esym->st_shndx, esym_vis); #endif tcc_error_noabort("'%s' defined twice", name); } } else { do_patch: esym->st_info = ELFW(ST_INFO)(sym_bind, sym_type); esym->st_shndx = shndx; new_undef_sym = 1; esym->st_value = value; esym->st_size = size; esym->st_other = other; } } else { do_def: sym_index = put_elf_sym(s, value, size, ELFW(ST_INFO)(sym_bind, sym_type), other, shndx, name); } return sym_index; } /* put relocation */ ST_FUNC void put_elf_reloca(Section *symtab, Section *s, unsigned long offset, int type, int symbol, addr_t addend) { char buf[256]; Section *sr; ElfW_Rel *rel; sr = s->reloc; if (!sr) { /* if no relocation section, create it */ snprintf(buf, sizeof(buf), REL_SECTION_FMT, s->name); /* if the symtab is allocated, then we consider the relocation are also */ sr = new_section(tcc_state, buf, SHT_RELX, symtab->sh_flags); sr->sh_entsize = sizeof(ElfW_Rel); sr->link = symtab; sr->sh_info = s->sh_num; s->reloc = sr; } rel = section_ptr_add(sr, sizeof(ElfW_Rel)); rel->r_offset = offset; rel->r_info = ELFW(R_INFO)(symbol, type); #if SHT_RELX == SHT_RELA rel->r_addend = addend; #else if (addend) tcc_error("non-zero addend on REL architecture"); #endif } ST_FUNC void put_elf_reloc(Section *symtab, Section *s, unsigned long offset, int type, int symbol) { put_elf_reloca(symtab, s, offset, type, symbol, 0); } /* Remove relocations for section S->reloc starting at oldrelocoffset that are to the same place, retaining the last of them. As side effect the relocations are sorted. Possibly reduces the number of relocs. */ ST_FUNC void squeeze_multi_relocs(Section *s, size_t oldrelocoffset) { Section *sr = s->reloc; ElfW_Rel *r, *dest; ssize_t a; ElfW(Addr) addr; if (oldrelocoffset + sizeof(*r) >= sr->data_offset) return; /* The relocs we're dealing with are the result of initializer parsing. So they will be mostly in order and there aren't many of them. Secondly we need a stable sort (which qsort isn't). We use a simple insertion sort. */ for (a = oldrelocoffset + sizeof(*r); a < sr->data_offset; a += sizeof(*r)) { ssize_t i = a - sizeof(*r); addr = ((ElfW_Rel*)(sr->data + a))->r_offset; for (; i >= (ssize_t)oldrelocoffset && ((ElfW_Rel*)(sr->data + i))->r_offset > addr; i -= sizeof(*r)) { ElfW_Rel tmp = *(ElfW_Rel*)(sr->data + a); *(ElfW_Rel*)(sr->data + a) = *(ElfW_Rel*)(sr->data + i); *(ElfW_Rel*)(sr->data + i) = tmp; } } r = (ElfW_Rel*)(sr->data + oldrelocoffset); dest = r; for (; r < (ElfW_Rel*)(sr->data + sr->data_offset); r++) { if (dest->r_offset != r->r_offset) dest++; *dest = *r; } sr->data_offset = (unsigned char*)dest - sr->data + sizeof(*r); } /* put stab debug information */ ST_FUNC void put_stabs(const char *str, int type, int other, int desc, unsigned long value) { Stab_Sym *sym; sym = section_ptr_add(stab_section, sizeof(Stab_Sym)); if (str) { sym->n_strx = put_elf_str(stabstr_section, str); } else { sym->n_strx = 0; } sym->n_type = type; sym->n_other = other; sym->n_desc = desc; sym->n_value = value; } ST_FUNC void put_stabs_r(const char *str, int type, int other, int desc, unsigned long value, Section *sec, int sym_index) { put_stabs(str, type, other, desc, value); put_elf_reloc(symtab_section, stab_section, stab_section->data_offset - sizeof(unsigned int), R_DATA_32, sym_index); } ST_FUNC void put_stabn(int type, int other, int desc, int value) { put_stabs(NULL, type, other, desc, value); } ST_FUNC void put_stabd(int type, int other, int desc) { put_stabs(NULL, type, other, desc, 0); } ST_FUNC struct sym_attr *get_sym_attr(TCCState *s1, int index, int alloc) { int n; struct sym_attr *tab; if (index >= s1->nb_sym_attrs) { if (!alloc) return s1->sym_attrs; /* find immediately bigger power of 2 and reallocate array */ n = 1; while (index >= n) n *= 2; tab = tcc_realloc(s1->sym_attrs, n * sizeof(*s1->sym_attrs)); s1->sym_attrs = tab; memset(s1->sym_attrs + s1->nb_sym_attrs, 0, (n - s1->nb_sym_attrs) * sizeof(*s1->sym_attrs)); s1->nb_sym_attrs = n; } return &s1->sym_attrs[index]; } /* Browse each elem of type <type> in section <sec> starting at elem <startoff> using variable <elem> */ #define for_each_elem(sec, startoff, elem, type) \ for (elem = (type *) sec->data + startoff; \ elem < (type *) (sec->data + sec->data_offset); elem++) /* In an ELF file symbol table, the local symbols must appear below the global and weak ones. Since TCC cannot sort it while generating the code, we must do it after. All the relocation tables are also modified to take into account the symbol table sorting */ static void sort_syms(TCCState *s1, Section *s) { int *old_to_new_syms; ElfW(Sym) *new_syms; int nb_syms, i; ElfW(Sym) *p, *q; ElfW_Rel *rel; Section *sr; int type, sym_index; nb_syms = s->data_offset / sizeof(ElfW(Sym)); new_syms = tcc_malloc(nb_syms * sizeof(ElfW(Sym))); old_to_new_syms = tcc_malloc(nb_syms * sizeof(int)); /* first pass for local symbols */ p = (ElfW(Sym) *)s->data; q = new_syms; for(i = 0; i < nb_syms; i++) { if (ELFW(ST_BIND)(p->st_info) == STB_LOCAL) { old_to_new_syms[i] = q - new_syms; *q++ = *p; } p++; } /* save the number of local symbols in section header */ if( s->sh_size ) /* this 'if' makes IDA happy */ s->sh_info = q - new_syms; /* then second pass for non local symbols */ p = (ElfW(Sym) *)s->data; for(i = 0; i < nb_syms; i++) { if (ELFW(ST_BIND)(p->st_info) != STB_LOCAL) { old_to_new_syms[i] = q - new_syms; *q++ = *p; } p++; } /* we copy the new symbols to the old */ memcpy(s->data, new_syms, nb_syms * sizeof(ElfW(Sym))); tcc_free(new_syms); /* now we modify all the relocations */ for(i = 1; i < s1->nb_sections; i++) { sr = s1->sections[i]; if (sr->sh_type == SHT_RELX && sr->link == s) { for_each_elem(sr, 0, rel, ElfW_Rel) { sym_index = ELFW(R_SYM)(rel->r_info); type = ELFW(R_TYPE)(rel->r_info); sym_index = old_to_new_syms[sym_index]; rel->r_info = ELFW(R_INFO)(sym_index, type); } } } tcc_free(old_to_new_syms); } /* relocate symbol table, resolve undefined symbols if do_resolve is true and output error if undefined symbol. */ ST_FUNC void relocate_syms(TCCState *s1, Section *symtab, int do_resolve) { ElfW(Sym) *sym; int sym_bind, sh_num; const char *name; for_each_elem(symtab, 1, sym, ElfW(Sym)) { sh_num = sym->st_shndx; if (sh_num == SHN_UNDEF) { name = (char *) s1->symtab->link->data + sym->st_name; /* Use ld.so to resolve symbol for us (for tcc -run) */ if (do_resolve) { #if defined TCC_IS_NATIVE && !defined TCC_TARGET_PE void *addr = dlsym(RTLD_DEFAULT, name); if (addr) { sym->st_value = (addr_t) addr; #ifdef DEBUG_RELOC printf ("relocate_sym: %s -> 0x%lx\n", name, sym->st_value); #endif goto found; } #endif /* if dynamic symbol exist, it will be used in relocate_section */ } else if (s1->dynsym && find_elf_sym(s1->dynsym, name)) goto found; /* XXX: _fp_hw seems to be part of the ABI, so we ignore it */ if (!strcmp(name, "_fp_hw")) goto found; /* only weak symbols are accepted to be undefined. Their value is zero */ sym_bind = ELFW(ST_BIND)(sym->st_info); if (sym_bind == STB_WEAK) sym->st_value = 0; else tcc_error_noabort("undefined symbol '%s'", name); } else if (sh_num < SHN_LORESERVE) { /* add section base */ sym->st_value += s1->sections[sym->st_shndx]->sh_addr; } found: ; } } /* relocate a given section (CPU dependent) by applying the relocations in the associated relocation section */ ST_FUNC void relocate_section(TCCState *s1, Section *s) { Section *sr = s->reloc; ElfW_Rel *rel; ElfW(Sym) *sym; int type, sym_index; unsigned char *ptr; addr_t tgt, addr; relocate_init(sr); for_each_elem(sr, 0, rel, ElfW_Rel) { ptr = s->data + rel->r_offset; sym_index = ELFW(R_SYM)(rel->r_info); sym = &((ElfW(Sym) *)symtab_section->data)[sym_index]; type = ELFW(R_TYPE)(rel->r_info); tgt = sym->st_value; #if SHT_RELX == SHT_RELA tgt += rel->r_addend; #endif addr = s->sh_addr + rel->r_offset; relocate(s1, rel, type, ptr, addr, tgt); } /* if the relocation is allocated, we change its symbol table */ if (sr->sh_flags & SHF_ALLOC) sr->link = s1->dynsym; } /* relocate relocation table in 'sr' */ static void relocate_rel(TCCState *s1, Section *sr) { Section *s; ElfW_Rel *rel; s = s1->sections[sr->sh_info]; for_each_elem(sr, 0, rel, ElfW_Rel) rel->r_offset += s->sh_addr; } /* count the number of dynamic relocations so that we can reserve their space */ static int prepare_dynamic_rel(TCCState *s1, Section *sr) { ElfW_Rel *rel; int sym_index, type, count; count = 0; for_each_elem(sr, 0, rel, ElfW_Rel) { sym_index = ELFW(R_SYM)(rel->r_info); type = ELFW(R_TYPE)(rel->r_info); switch(type) { #if defined(TCC_TARGET_I386) case R_386_32: if (!get_sym_attr(s1, sym_index, 0)->dyn_index && ((ElfW(Sym)*)symtab_section->data + sym_index)->st_shndx == SHN_UNDEF) { /* don't fixup unresolved (weak) symbols */ rel->r_info = ELFW(R_INFO)(sym_index, R_386_RELATIVE); break; } #elif defined(TCC_TARGET_X86_64) case R_X86_64_32: case R_X86_64_32S: case R_X86_64_64: #endif count++; break; #if defined(TCC_TARGET_I386) case R_386_PC32: #elif defined(TCC_TARGET_X86_64) case R_X86_64_PC32: #endif if (get_sym_attr(s1, sym_index, 0)->dyn_index) count++; break; default: break; } } if (count) { /* allocate the section */ sr->sh_flags |= SHF_ALLOC; sr->sh_size = count * sizeof(ElfW_Rel); } return count; } static void build_got(TCCState *s1) { /* if no got, then create it */ s1->got = new_section(s1, ".got", SHT_PROGBITS, SHF_ALLOC | SHF_WRITE); s1->got->sh_entsize = 4; set_elf_sym(symtab_section, 0, 4, ELFW(ST_INFO)(STB_GLOBAL, STT_OBJECT), 0, s1->got->sh_num, "_GLOBAL_OFFSET_TABLE_"); /* keep space for _DYNAMIC pointer and two dummy got entries */ section_ptr_add(s1->got, 3 * PTR_SIZE); } /* Create a GOT and (for function call) a PLT entry corresponding to a symbol in s1->symtab. When creating the dynamic symbol table entry for the GOT relocation, use 'size' and 'info' for the corresponding symbol metadata. Returns the offset of the GOT or (if any) PLT entry. */ static struct sym_attr * put_got_entry(TCCState *s1, int dyn_reloc_type, unsigned long size, int info, int sym_index) { int need_plt_entry; const char *name; ElfW(Sym) *sym; struct sym_attr *attr; unsigned got_offset; char plt_name[100]; int len; need_plt_entry = (dyn_reloc_type == R_JMP_SLOT); attr = get_sym_attr(s1, sym_index, 1); /* In case a function is both called and its address taken 2 GOT entries are created, one for taking the address (GOT) and the other for the PLT entry (PLTGOT). */ if (need_plt_entry ? attr->plt_offset : attr->got_offset) return attr; /* create the GOT entry */ got_offset = s1->got->data_offset; section_ptr_add(s1->got, PTR_SIZE); /* Create the GOT relocation that will insert the address of the object or function of interest in the GOT entry. This is a static relocation for memory output (dlsym will give us the address of symbols) and dynamic relocation otherwise (executable and DLLs). The relocation should be done lazily for GOT entry with *_JUMP_SLOT relocation type (the one associated to a PLT entry) but is currently done at load time for an unknown reason. */ sym = &((ElfW(Sym) *) symtab_section->data)[sym_index]; name = (char *) symtab_section->link->data + sym->st_name; if (s1->dynsym) { if (ELFW(ST_BIND)(sym->st_info) == STB_LOCAL) { /* Hack alarm. We don't want to emit dynamic symbols and symbol based relocs for STB_LOCAL symbols, but rather want to resolve them directly. At this point the symbol values aren't final yet, so we must defer this. We will later have to create a RELATIVE reloc anyway, so we misuse the relocation slot to smuggle the symbol reference until fill_local_got_entries. Not that the sym_index is relative to symtab_section, not s1->dynsym! Nevertheless we use s1->dyn_sym so that if this is the first call that got->reloc is correctly created. Also note that RELATIVE relocs are not normally created for the .got, so the types serves as a marker for later (and is retained also for the final output, which is okay because then the got is just normal data). */ put_elf_reloc(s1->dynsym, s1->got, got_offset, R_RELATIVE, sym_index); } else { if (0 == attr->dyn_index) attr->dyn_index = set_elf_sym(s1->dynsym, sym->st_value, size, info, 0, sym->st_shndx, name); put_elf_reloc(s1->dynsym, s1->got, got_offset, dyn_reloc_type, attr->dyn_index); } } else { put_elf_reloc(symtab_section, s1->got, got_offset, dyn_reloc_type, sym_index); } if (need_plt_entry) { if (!s1->plt) { s1->plt = new_section(s1, ".plt", SHT_PROGBITS, SHF_ALLOC | SHF_EXECINSTR); s1->plt->sh_entsize = 4; } attr->plt_offset = create_plt_entry(s1, got_offset, attr); /* create a symbol 'sym@plt' for the PLT jump vector */ len = strlen(name); if (len > sizeof plt_name - 5) len = sizeof plt_name - 5; memcpy(plt_name, name, len); strcpy(plt_name + len, "@plt"); attr->plt_sym = put_elf_sym(s1->symtab, attr->plt_offset, sym->st_size, ELFW(ST_INFO)(STB_GLOBAL, STT_FUNC), 0, s1->plt->sh_num, plt_name); } else { attr->got_offset = got_offset; } return attr; } /* build GOT and PLT entries */ ST_FUNC void build_got_entries(TCCState *s1) { Section *s; ElfW_Rel *rel; ElfW(Sym) *sym; int i, type, gotplt_entry, reloc_type, sym_index; struct sym_attr *attr; for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; if (s->sh_type != SHT_RELX) continue; /* no need to handle got relocations */ if (s->link != symtab_section) continue; for_each_elem(s, 0, rel, ElfW_Rel) { type = ELFW(R_TYPE)(rel->r_info); gotplt_entry = gotplt_entry_type(type); sym_index = ELFW(R_SYM)(rel->r_info); sym = &((ElfW(Sym) *)symtab_section->data)[sym_index]; if (gotplt_entry == NO_GOTPLT_ENTRY) { continue; } /* Automatically create PLT/GOT [entry] if it is an undefined reference (resolved at runtime), or the symbol is absolute, probably created by tcc_add_symbol, and thus on 64-bit targets might be too far from application code. */ if (gotplt_entry == AUTO_GOTPLT_ENTRY) { if (sym->st_shndx == SHN_UNDEF) { ElfW(Sym) *esym; int dynindex; if (s1->output_type == TCC_OUTPUT_DLL && ! PCRELATIVE_DLLPLT) continue; /* Relocations for UNDEF symbols would normally need to be transferred into the executable or shared object. If that were done AUTO_GOTPLT_ENTRY wouldn't exist. But TCC doesn't do that (at least for exes), so we need to resolve all such relocs locally. And that means PLT slots for functions in DLLs and COPY relocs for data symbols. COPY relocs were generated in bind_exe_dynsyms (and the symbol adjusted to be defined), and for functions we were generated a dynamic symbol of function type. */ if (s1->dynsym) { /* dynsym isn't set for -run :-/ */ dynindex = get_sym_attr(s1, sym_index, 0)->dyn_index; esym = (ElfW(Sym) *)s1->dynsym->data + dynindex; if (dynindex && (ELFW(ST_TYPE)(esym->st_info) == STT_FUNC || (ELFW(ST_TYPE)(esym->st_info) == STT_NOTYPE && ELFW(ST_TYPE)(sym->st_info) == STT_FUNC))) goto jmp_slot; } } else if (!(sym->st_shndx == SHN_ABS #ifndef TCC_TARGET_ARM && PTR_SIZE == 8 #endif )) continue; } #ifdef TCC_TARGET_X86_64 if ((type == R_X86_64_PLT32 || type == R_X86_64_PC32) && (ELFW(ST_VISIBILITY)(sym->st_other) != STV_DEFAULT || ELFW(ST_BIND)(sym->st_info) == STB_LOCAL)) { rel->r_info = ELFW(R_INFO)(sym_index, R_X86_64_PC32); continue; } #endif if (code_reloc(type)) { jmp_slot: reloc_type = R_JMP_SLOT; } else reloc_type = R_GLOB_DAT; if (!s1->got) build_got(s1); if (gotplt_entry == BUILD_GOT_ONLY) continue; attr = put_got_entry(s1, reloc_type, sym->st_size, sym->st_info, sym_index); if (reloc_type == R_JMP_SLOT) rel->r_info = ELFW(R_INFO)(attr->plt_sym, type); } } } /* put dynamic tag */ static void put_dt(Section *dynamic, int dt, addr_t val) { ElfW(Dyn) *dyn; dyn = section_ptr_add(dynamic, sizeof(ElfW(Dyn))); dyn->d_tag = dt; dyn->d_un.d_val = val; } #ifndef TCC_TARGET_PE static void add_init_array_defines(TCCState *s1, const char *section_name) { Section *s; long end_offset; char sym_start[1024]; char sym_end[1024]; snprintf(sym_start, sizeof(sym_start), "__%s_start", section_name + 1); snprintf(sym_end, sizeof(sym_end), "__%s_end", section_name + 1); s = find_section(s1, section_name); if (!s) { end_offset = 0; s = data_section; } else { end_offset = s->data_offset; } set_elf_sym(symtab_section, 0, 0, ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, s->sh_num, sym_start); set_elf_sym(symtab_section, end_offset, 0, ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, s->sh_num, sym_end); } #endif static int tcc_add_support(TCCState *s1, const char *filename) { char buf[1024]; snprintf(buf, sizeof(buf), "%s/%s", s1->tcc_lib_path, filename); return tcc_add_file(s1, buf); } ST_FUNC void tcc_add_bcheck(TCCState *s1) { #ifdef CONFIG_TCC_BCHECK addr_t *ptr; int sym_index; if (0 == s1->do_bounds_check) return; /* XXX: add an object file to do that */ ptr = section_ptr_add(bounds_section, sizeof(*ptr)); *ptr = 0; set_elf_sym(symtab_section, 0, 0, ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, bounds_section->sh_num, "__bounds_start"); /* pull bcheck.o from libtcc1.a */ sym_index = set_elf_sym(symtab_section, 0, 0, ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, SHN_UNDEF, "__bound_init"); if (s1->output_type != TCC_OUTPUT_MEMORY) { /* add 'call __bound_init()' in .init section */ Section *init_section = find_section(s1, ".init"); unsigned char *pinit = section_ptr_add(init_section, 5); pinit[0] = 0xe8; write32le(pinit + 1, -4); put_elf_reloc(symtab_section, init_section, init_section->data_offset - 4, R_386_PC32, sym_index); /* R_386_PC32 = R_X86_64_PC32 = 2 */ } #endif } /* add tcc runtime libraries */ ST_FUNC void tcc_add_runtime(TCCState *s1) { tcc_add_bcheck(s1); tcc_add_pragma_libs(s1); /* add libc */ if (!s1->nostdlib) { tcc_add_library_err(s1, "c"); #ifdef TCC_LIBGCC if (!s1->static_link) { if (TCC_LIBGCC[0] == '/') tcc_add_file(s1, TCC_LIBGCC); else tcc_add_dll(s1, TCC_LIBGCC, 0); } #endif tcc_add_support(s1, TCC_LIBTCC1); /* add crt end if not memory output */ if (s1->output_type != TCC_OUTPUT_MEMORY) tcc_add_crt(s1, "crtn.o"); } } /* add various standard linker symbols (must be done after the sections are filled (for example after allocating common symbols)) */ static void tcc_add_linker_symbols(TCCState *s1) { char buf[1024]; int i; Section *s; set_elf_sym(symtab_section, text_section->data_offset, 0, ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, text_section->sh_num, "_etext"); set_elf_sym(symtab_section, data_section->data_offset, 0, ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, data_section->sh_num, "_edata"); set_elf_sym(symtab_section, bss_section->data_offset, 0, ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, bss_section->sh_num, "_end"); #ifndef TCC_TARGET_PE /* horrible new standard ldscript defines */ add_init_array_defines(s1, ".preinit_array"); add_init_array_defines(s1, ".init_array"); add_init_array_defines(s1, ".fini_array"); #endif /* add start and stop symbols for sections whose name can be expressed in C */ for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; if (s->sh_type == SHT_PROGBITS && (s->sh_flags & SHF_ALLOC)) { const char *p; int ch; /* check if section name can be expressed in C */ p = s->name; for(;;) { ch = *p; if (!ch) break; if (!isid(ch) && !isnum(ch)) goto next_sec; p++; } snprintf(buf, sizeof(buf), "__start_%s", s->name); set_elf_sym(symtab_section, 0, 0, ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, s->sh_num, buf); snprintf(buf, sizeof(buf), "__stop_%s", s->name); set_elf_sym(symtab_section, s->data_offset, 0, ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, s->sh_num, buf); } next_sec: ; } } ST_FUNC void resolve_common_syms(TCCState *s1) { ElfW(Sym) *sym; /* Allocate common symbols in BSS. */ for_each_elem(symtab_section, 1, sym, ElfW(Sym)) { if (sym->st_shndx == SHN_COMMON) { /* symbol alignment is in st_value for SHN_COMMONs */ sym->st_value = section_add(bss_section, sym->st_size, sym->st_value); sym->st_shndx = bss_section->sh_num; } } /* Now assign linker provided symbols their value. */ tcc_add_linker_symbols(s1); } static void tcc_output_binary(TCCState *s1, FILE *f, const int *sec_order) { Section *s; int i, offset, size; offset = 0; for(i=1;i<s1->nb_sections;i++) { s = s1->sections[sec_order[i]]; if (s->sh_type != SHT_NOBITS && (s->sh_flags & SHF_ALLOC)) { while (offset < s->sh_offset) { fputc(0, f); offset++; } size = s->sh_size; fwrite(s->data, 1, size, f); offset += size; } } } ST_FUNC void fill_got_entry(TCCState *s1, ElfW_Rel *rel) { int sym_index = ELFW(R_SYM) (rel->r_info); ElfW(Sym) *sym = &((ElfW(Sym) *) symtab_section->data)[sym_index]; struct sym_attr *attr = get_sym_attr(s1, sym_index, 0); unsigned offset = attr->got_offset; if (0 == offset) return; section_reserve(s1->got, offset + PTR_SIZE); #ifdef TCC_TARGET_X86_64 write64le(s1->got->data + offset, sym->st_value); #else write32le(s1->got->data + offset, sym->st_value); #endif } /* Perform relocation to GOT or PLT entries */ ST_FUNC void fill_got(TCCState *s1) { Section *s; ElfW_Rel *rel; int i; for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; if (s->sh_type != SHT_RELX) continue; /* no need to handle got relocations */ if (s->link != symtab_section) continue; for_each_elem(s, 0, rel, ElfW_Rel) { switch (ELFW(R_TYPE) (rel->r_info)) { case R_X86_64_GOT32: case R_X86_64_GOTPCREL: case R_X86_64_GOTPCRELX: case R_X86_64_REX_GOTPCRELX: case R_X86_64_PLT32: fill_got_entry(s1, rel); break; } } } } /* See put_got_entry for a description. This is the second stage where GOT references to local defined symbols are rewritten. */ static void fill_local_got_entries(TCCState *s1) { ElfW_Rel *rel; for_each_elem(s1->got->reloc, 0, rel, ElfW_Rel) { if (ELFW(R_TYPE)(rel->r_info) == R_RELATIVE) { int sym_index = ELFW(R_SYM) (rel->r_info); ElfW(Sym) *sym = &((ElfW(Sym) *) symtab_section->data)[sym_index]; struct sym_attr *attr = get_sym_attr(s1, sym_index, 0); unsigned offset = attr->got_offset; if (offset != rel->r_offset - s1->got->sh_addr) tcc_error_noabort("huh"); rel->r_info = ELFW(R_INFO)(0, R_RELATIVE); #if SHT_RELX == SHT_RELA rel->r_addend = sym->st_value; #else /* All our REL architectures also happen to be 32bit LE. */ write32le(s1->got->data + offset, sym->st_value); #endif } } } /* Bind symbols of executable: resolve undefined symbols from exported symbols in shared libraries and export non local defined symbols to shared libraries if -rdynamic switch was given on command line */ static void bind_exe_dynsyms(TCCState *s1) { const char *name; int sym_index, index; ElfW(Sym) *sym, *esym; int type; /* Resolve undefined symbols from dynamic symbols. When there is a match: - if STT_FUNC or STT_GNU_IFUNC symbol -> add it in PLT - if STT_OBJECT symbol -> add it in .bss section with suitable reloc */ for_each_elem(symtab_section, 1, sym, ElfW(Sym)) { if (sym->st_shndx == SHN_UNDEF) { name = (char *) symtab_section->link->data + sym->st_name; sym_index = find_elf_sym(s1->dynsymtab_section, name); if (sym_index) { esym = &((ElfW(Sym) *)s1->dynsymtab_section->data)[sym_index]; type = ELFW(ST_TYPE)(esym->st_info); if ((type == STT_FUNC) || (type == STT_GNU_IFUNC)) { /* Indirect functions shall have STT_FUNC type in executable * dynsym section. Indeed, a dlsym call following a lazy * resolution would pick the symbol value from the * executable dynsym entry which would contain the address * of the function wanted by the caller of dlsym instead of * the address of the function that would return that * address */ int dynindex = put_elf_sym(s1->dynsym, 0, esym->st_size, ELFW(ST_INFO)(STB_GLOBAL,STT_FUNC), 0, 0, name); int index = sym - (ElfW(Sym) *) symtab_section->data; get_sym_attr(s1, index, 1)->dyn_index = dynindex; } else if (type == STT_OBJECT) { unsigned long offset; ElfW(Sym) *dynsym; offset = bss_section->data_offset; /* XXX: which alignment ? */ offset = (offset + 16 - 1) & -16; set_elf_sym (s1->symtab, offset, esym->st_size, esym->st_info, 0, bss_section->sh_num, name); index = put_elf_sym(s1->dynsym, offset, esym->st_size, esym->st_info, 0, bss_section->sh_num, name); /* Ensure R_COPY works for weak symbol aliases */ if (ELFW(ST_BIND)(esym->st_info) == STB_WEAK) { for_each_elem(s1->dynsymtab_section, 1, dynsym, ElfW(Sym)) { if ((dynsym->st_value == esym->st_value) && (ELFW(ST_BIND)(dynsym->st_info) == STB_GLOBAL)) { char *dynname = (char *) s1->dynsymtab_section->link->data + dynsym->st_name; put_elf_sym(s1->dynsym, offset, dynsym->st_size, dynsym->st_info, 0, bss_section->sh_num, dynname); break; } } } put_elf_reloc(s1->dynsym, bss_section, offset, R_COPY, index); offset += esym->st_size; bss_section->data_offset = offset; } } else { /* STB_WEAK undefined symbols are accepted */ /* XXX: _fp_hw seems to be part of the ABI, so we ignore it */ if (ELFW(ST_BIND)(sym->st_info) == STB_WEAK || !strcmp(name, "_fp_hw")) { } else { tcc_error_noabort("undefined symbol '%s'", name); } } } else if (s1->rdynamic && ELFW(ST_BIND)(sym->st_info) != STB_LOCAL) { /* if -rdynamic option, then export all non local symbols */ name = (char *) symtab_section->link->data + sym->st_name; set_elf_sym(s1->dynsym, sym->st_value, sym->st_size, sym->st_info, 0, sym->st_shndx, name); } } } /* Bind symbols of libraries: export all non local symbols of executable that are referenced by shared libraries. The reason is that the dynamic loader search symbol first in executable and then in libraries. Therefore a reference to a symbol already defined by a library can still be resolved by a symbol in the executable. */ static void bind_libs_dynsyms(TCCState *s1) { const char *name; int sym_index; ElfW(Sym) *sym, *esym; for_each_elem(s1->dynsymtab_section, 1, esym, ElfW(Sym)) { name = (char *) s1->dynsymtab_section->link->data + esym->st_name; sym_index = find_elf_sym(symtab_section, name); sym = &((ElfW(Sym) *)symtab_section->data)[sym_index]; if (sym_index && sym->st_shndx != SHN_UNDEF && ELFW(ST_BIND)(sym->st_info) != STB_LOCAL) { set_elf_sym(s1->dynsym, sym->st_value, sym->st_size, sym->st_info, 0, sym->st_shndx, name); } else if (esym->st_shndx == SHN_UNDEF) { /* weak symbols can stay undefined */ if (ELFW(ST_BIND)(esym->st_info) != STB_WEAK) tcc_warning("undefined dynamic symbol '%s'", name); } } } /* Export all non local symbols. This is used by shared libraries so that the non local symbols they define can resolve a reference in another shared library or in the executable. Correspondingly, it allows undefined local symbols to be resolved by other shared libraries or by the executable. */ static void export_global_syms(TCCState *s1) { int dynindex, index; const char *name; ElfW(Sym) *sym; for_each_elem(symtab_section, 1, sym, ElfW(Sym)) { if (ELFW(ST_BIND)(sym->st_info) != STB_LOCAL) { name = (char *) symtab_section->link->data + sym->st_name; dynindex = put_elf_sym(s1->dynsym, sym->st_value, sym->st_size, sym->st_info, 0, sym->st_shndx, name); index = sym - (ElfW(Sym) *) symtab_section->data; get_sym_attr(s1, index, 1)->dyn_index = dynindex; } } } /* Allocate strings for section names and decide if an unallocated section should be output. NOTE: the strsec section comes last, so its size is also correct ! */ static int alloc_sec_names(TCCState *s1, int file_type, Section *strsec) { int i; Section *s; int textrel = 0; /* Allocate strings for section names */ for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; /* when generating a DLL, we include relocations but we may patch them */ if (file_type == TCC_OUTPUT_DLL && s->sh_type == SHT_RELX && !(s->sh_flags & SHF_ALLOC) && (s1->sections[s->sh_info]->sh_flags & SHF_ALLOC) && prepare_dynamic_rel(s1, s)) { if (s1->sections[s->sh_info]->sh_flags & SHF_EXECINSTR) textrel = 1; } else if (s1->do_debug || file_type == TCC_OUTPUT_OBJ || (s->sh_flags & SHF_ALLOC) || i == (s1->nb_sections - 1)) { /* we output all sections if debug or object file */ s->sh_size = s->data_offset; } if (s->sh_size || (s->sh_flags & SHF_ALLOC)) s->sh_name = put_elf_str(strsec, s->name); } strsec->sh_size = strsec->data_offset; return textrel; } /* Info to be copied in dynamic section */ struct dyn_inf { Section *dynamic; Section *dynstr; unsigned long data_offset; addr_t rel_addr; addr_t rel_size; #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) addr_t bss_addr; addr_t bss_size; #endif }; /* Assign sections to segments and decide how are sections laid out when loaded in memory. This function also fills corresponding program headers. */ static int layout_sections(TCCState *s1, ElfW(Phdr) *phdr, int phnum, Section *interp, Section* strsec, struct dyn_inf *dyninf, int *sec_order) { int i, j, k, file_type, sh_order_index, file_offset; unsigned long s_align; long long tmp; addr_t addr; ElfW(Phdr) *ph; Section *s; file_type = s1->output_type; sh_order_index = 1; file_offset = 0; if (s1->output_format == TCC_OUTPUT_FORMAT_ELF) file_offset = sizeof(ElfW(Ehdr)) + phnum * sizeof(ElfW(Phdr)); s_align = ELF_PAGE_SIZE; if (s1->section_align) s_align = s1->section_align; if (phnum > 0) { if (s1->has_text_addr) { int a_offset, p_offset; addr = s1->text_addr; /* we ensure that (addr % ELF_PAGE_SIZE) == file_offset % ELF_PAGE_SIZE */ a_offset = (int) (addr & (s_align - 1)); p_offset = file_offset & (s_align - 1); if (a_offset < p_offset) a_offset += s_align; file_offset += (a_offset - p_offset); } else { if (file_type == TCC_OUTPUT_DLL) addr = 0; else addr = ELF_START_ADDR; /* compute address after headers */ addr += (file_offset & (s_align - 1)); } ph = &phdr[0]; /* Leave one program headers for the program interpreter and one for the program header table itself if needed. These are done later as they require section layout to be done first. */ if (interp) ph += 2; /* dynamic relocation table information, for .dynamic section */ dyninf->rel_addr = dyninf->rel_size = 0; #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) dyninf->bss_addr = dyninf->bss_size = 0; #endif for(j = 0; j < 2; j++) { ph->p_type = PT_LOAD; if (j == 0) ph->p_flags = PF_R | PF_X; else ph->p_flags = PF_R | PF_W; ph->p_align = s_align; /* Decide the layout of sections loaded in memory. This must be done before program headers are filled since they contain info about the layout. We do the following ordering: interp, symbol tables, relocations, progbits, nobits */ /* XXX: do faster and simpler sorting */ for(k = 0; k < 5; k++) { for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; /* compute if section should be included */ if (j == 0) { if ((s->sh_flags & (SHF_ALLOC | SHF_WRITE)) != SHF_ALLOC) continue; } else { if ((s->sh_flags & (SHF_ALLOC | SHF_WRITE)) != (SHF_ALLOC | SHF_WRITE)) continue; } if (s == interp) { if (k != 0) continue; } else if (s->sh_type == SHT_DYNSYM || s->sh_type == SHT_STRTAB || s->sh_type == SHT_HASH) { if (k != 1) continue; } else if (s->sh_type == SHT_RELX) { if (k != 2) continue; } else if (s->sh_type == SHT_NOBITS) { if (k != 4) continue; } else { if (k != 3) continue; } sec_order[sh_order_index++] = i; /* section matches: we align it and add its size */ tmp = addr; addr = (addr + s->sh_addralign - 1) & ~(s->sh_addralign - 1); file_offset += (int) ( addr - tmp ); s->sh_offset = file_offset; s->sh_addr = addr; /* update program header infos */ if (ph->p_offset == 0) { ph->p_offset = file_offset; ph->p_vaddr = addr; ph->p_paddr = ph->p_vaddr; } /* update dynamic relocation infos */ if (s->sh_type == SHT_RELX) { #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) if (!strcmp(strsec->data + s->sh_name, ".rel.got")) { dyninf->rel_addr = addr; dyninf->rel_size += s->sh_size; /* XXX only first rel. */ } if (!strcmp(strsec->data + s->sh_name, ".rel.bss")) { dyninf->bss_addr = addr; dyninf->bss_size = s->sh_size; /* XXX only first rel. */ } #else if (dyninf->rel_size == 0) dyninf->rel_addr = addr; dyninf->rel_size += s->sh_size; #endif } addr += s->sh_size; if (s->sh_type != SHT_NOBITS) file_offset += s->sh_size; } } if (j == 0) { /* Make the first PT_LOAD segment include the program headers itself (and the ELF header as well), it'll come out with same memory use but will make various tools like binutils strip work better. */ ph->p_offset &= ~(ph->p_align - 1); ph->p_vaddr &= ~(ph->p_align - 1); ph->p_paddr &= ~(ph->p_align - 1); } ph->p_filesz = file_offset - ph->p_offset; ph->p_memsz = addr - ph->p_vaddr; ph++; if (j == 0) { if (s1->output_format == TCC_OUTPUT_FORMAT_ELF) { /* if in the middle of a page, we duplicate the page in memory so that one copy is RX and the other is RW */ if ((addr & (s_align - 1)) != 0) addr += s_align; } else { addr = (addr + s_align - 1) & ~(s_align - 1); file_offset = (file_offset + s_align - 1) & ~(s_align - 1); } } } } /* all other sections come after */ for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; if (phnum > 0 && (s->sh_flags & SHF_ALLOC)) continue; sec_order[sh_order_index++] = i; file_offset = (file_offset + s->sh_addralign - 1) & ~(s->sh_addralign - 1); s->sh_offset = file_offset; if (s->sh_type != SHT_NOBITS) file_offset += s->sh_size; } return file_offset; } static void fill_unloadable_phdr(ElfW(Phdr) *phdr, int phnum, Section *interp, Section *dynamic) { ElfW(Phdr) *ph; /* if interpreter, then add corresponding program header */ if (interp) { ph = &phdr[0]; ph->p_type = PT_PHDR; ph->p_offset = sizeof(ElfW(Ehdr)); ph->p_filesz = ph->p_memsz = phnum * sizeof(ElfW(Phdr)); ph->p_vaddr = interp->sh_addr - ph->p_filesz; ph->p_paddr = ph->p_vaddr; ph->p_flags = PF_R | PF_X; ph->p_align = 4; /* interp->sh_addralign; */ ph++; ph->p_type = PT_INTERP; ph->p_offset = interp->sh_offset; ph->p_vaddr = interp->sh_addr; ph->p_paddr = ph->p_vaddr; ph->p_filesz = interp->sh_size; ph->p_memsz = interp->sh_size; ph->p_flags = PF_R; ph->p_align = interp->sh_addralign; } /* if dynamic section, then add corresponding program header */ if (dynamic) { ph = &phdr[phnum - 1]; ph->p_type = PT_DYNAMIC; ph->p_offset = dynamic->sh_offset; ph->p_vaddr = dynamic->sh_addr; ph->p_paddr = ph->p_vaddr; ph->p_filesz = dynamic->sh_size; ph->p_memsz = dynamic->sh_size; ph->p_flags = PF_R | PF_W; ph->p_align = dynamic->sh_addralign; } } /* Fill the dynamic section with tags describing the address and size of sections */ static void fill_dynamic(TCCState *s1, struct dyn_inf *dyninf) { Section *dynamic = dyninf->dynamic; /* put dynamic section entries */ put_dt(dynamic, DT_HASH, s1->dynsym->hash->sh_addr); put_dt(dynamic, DT_STRTAB, dyninf->dynstr->sh_addr); put_dt(dynamic, DT_SYMTAB, s1->dynsym->sh_addr); put_dt(dynamic, DT_STRSZ, dyninf->dynstr->data_offset); put_dt(dynamic, DT_SYMENT, sizeof(ElfW(Sym))); #if PTR_SIZE == 8 put_dt(dynamic, DT_RELA, dyninf->rel_addr); put_dt(dynamic, DT_RELASZ, dyninf->rel_size); put_dt(dynamic, DT_RELAENT, sizeof(ElfW_Rel)); #else #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) put_dt(dynamic, DT_PLTGOT, s1->got->sh_addr); put_dt(dynamic, DT_PLTRELSZ, dyninf->rel_size); put_dt(dynamic, DT_JMPREL, dyninf->rel_addr); put_dt(dynamic, DT_PLTREL, DT_REL); put_dt(dynamic, DT_REL, dyninf->bss_addr); put_dt(dynamic, DT_RELSZ, dyninf->bss_size); #else put_dt(dynamic, DT_REL, dyninf->rel_addr); put_dt(dynamic, DT_RELSZ, dyninf->rel_size); put_dt(dynamic, DT_RELENT, sizeof(ElfW_Rel)); #endif #endif if (s1->do_debug) put_dt(dynamic, DT_DEBUG, 0); put_dt(dynamic, DT_NULL, 0); } /* Relocate remaining sections and symbols (that is those not related to dynamic linking) */ static int final_sections_reloc(TCCState *s1) { int i; Section *s; relocate_syms(s1, s1->symtab, 0); if (s1->nb_errors != 0) return -1; /* relocate sections */ /* XXX: ignore sections with allocated relocations ? */ for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; #if defined(TCC_TARGET_I386) || defined(TCC_MUSL) if (s->reloc && s != s1->got && (s->sh_flags & SHF_ALLOC)) //gr /* On X86 gdb 7.3 works in any case but gdb 6.6 will crash if SHF_ALLOC checking is removed */ #else if (s->reloc && s != s1->got) /* On X86_64 gdb 7.3 will crash if SHF_ALLOC checking is present */ #endif relocate_section(s1, s); } /* relocate relocation entries if the relocation tables are allocated in the executable */ for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; if ((s->sh_flags & SHF_ALLOC) && s->sh_type == SHT_RELX) { relocate_rel(s1, s); } } return 0; } /* Create an ELF file on disk. This function handle ELF specific layout requirements */ static void tcc_output_elf(TCCState *s1, FILE *f, int phnum, ElfW(Phdr) *phdr, int file_offset, int *sec_order) { int i, shnum, offset, size, file_type; Section *s; ElfW(Ehdr) ehdr; ElfW(Shdr) shdr, *sh; file_type = s1->output_type; shnum = s1->nb_sections; memset(&ehdr, 0, sizeof(ehdr)); if (phnum > 0) { ehdr.e_phentsize = sizeof(ElfW(Phdr)); ehdr.e_phnum = phnum; ehdr.e_phoff = sizeof(ElfW(Ehdr)); } /* align to 4 */ file_offset = (file_offset + 3) & -4; /* fill header */ ehdr.e_ident[0] = ELFMAG0; ehdr.e_ident[1] = ELFMAG1; ehdr.e_ident[2] = ELFMAG2; ehdr.e_ident[3] = ELFMAG3; ehdr.e_ident[4] = ELFCLASSW; ehdr.e_ident[5] = ELFDATA2LSB; ehdr.e_ident[6] = EV_CURRENT; #if !defined(TCC_TARGET_PE) && (defined(__FreeBSD__) || defined(__FreeBSD_kernel__)) /* FIXME: should set only for freebsd _target_, but we exclude only PE target */ ehdr.e_ident[EI_OSABI] = ELFOSABI_FREEBSD; #endif #ifdef TCC_TARGET_ARM #ifdef TCC_ARM_EABI ehdr.e_ident[EI_OSABI] = 0; ehdr.e_flags = EF_ARM_EABI_VER4; if (file_type == TCC_OUTPUT_EXE || file_type == TCC_OUTPUT_DLL) ehdr.e_flags |= EF_ARM_HASENTRY; if (s1->float_abi == ARM_HARD_FLOAT) ehdr.e_flags |= EF_ARM_VFP_FLOAT; else ehdr.e_flags |= EF_ARM_SOFT_FLOAT; #else ehdr.e_ident[EI_OSABI] = ELFOSABI_ARM; #endif #endif switch(file_type) { default: case TCC_OUTPUT_EXE: ehdr.e_type = ET_EXEC; ehdr.e_entry = get_elf_sym_addr(s1, "_start", 1); break; case TCC_OUTPUT_DLL: ehdr.e_type = ET_DYN; ehdr.e_entry = text_section->sh_addr; /* XXX: is it correct ? */ break; case TCC_OUTPUT_OBJ: ehdr.e_type = ET_REL; break; } ehdr.e_machine = EM_TCC_TARGET; ehdr.e_version = EV_CURRENT; ehdr.e_shoff = file_offset; ehdr.e_ehsize = sizeof(ElfW(Ehdr)); ehdr.e_shentsize = sizeof(ElfW(Shdr)); ehdr.e_shnum = shnum; ehdr.e_shstrndx = shnum - 1; fwrite(&ehdr, 1, sizeof(ElfW(Ehdr)), f); fwrite(phdr, 1, phnum * sizeof(ElfW(Phdr)), f); offset = sizeof(ElfW(Ehdr)) + phnum * sizeof(ElfW(Phdr)); sort_syms(s1, symtab_section); for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[sec_order[i]]; if (s->sh_type != SHT_NOBITS) { while (offset < s->sh_offset) { fputc(0, f); offset++; } size = s->sh_size; if (size) fwrite(s->data, 1, size, f); offset += size; } } /* output section headers */ while (offset < ehdr.e_shoff) { fputc(0, f); offset++; } for(i = 0; i < s1->nb_sections; i++) { sh = &shdr; memset(sh, 0, sizeof(ElfW(Shdr))); s = s1->sections[i]; if (s) { sh->sh_name = s->sh_name; sh->sh_type = s->sh_type; sh->sh_flags = s->sh_flags; sh->sh_entsize = s->sh_entsize; sh->sh_info = s->sh_info; if (s->link) sh->sh_link = s->link->sh_num; sh->sh_addralign = s->sh_addralign; sh->sh_addr = s->sh_addr; sh->sh_offset = s->sh_offset; sh->sh_size = s->sh_size; } fwrite(sh, 1, sizeof(ElfW(Shdr)), f); } } /* Write an elf, coff or "binary" file */ static int tcc_write_elf_file(TCCState *s1, const char *filename, int phnum, ElfW(Phdr) *phdr, int file_offset, int *sec_order) { int fd, mode, file_type; FILE *f; file_type = s1->output_type; if (file_type == TCC_OUTPUT_OBJ) mode = 0666; else mode = 0777; unlink(filename); fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, mode); if (fd < 0) { tcc_error_noabort("could not write '%s'", filename); return -1; } f = fdopen(fd, "wb"); if (s1->verbose) printf("<- %s\n", filename); #ifdef TCC_TARGET_COFF if (s1->output_format == TCC_OUTPUT_FORMAT_COFF) tcc_output_coff(s1, f); else #endif if (s1->output_format == TCC_OUTPUT_FORMAT_ELF) tcc_output_elf(s1, f, phnum, phdr, file_offset, sec_order); else tcc_output_binary(s1, f, sec_order); fclose(f); return 0; } /* Sort section headers by assigned sh_addr, remove sections that we aren't going to output. */ static void tidy_section_headers(TCCState *s1, int *sec_order) { int i, nnew, l, *backmap; Section **snew, *s; ElfW(Sym) *sym; snew = tcc_malloc(s1->nb_sections * sizeof(snew[0])); backmap = tcc_malloc(s1->nb_sections * sizeof(backmap[0])); for (i = 0, nnew = 0, l = s1->nb_sections; i < s1->nb_sections; i++) { s = s1->sections[sec_order[i]]; if (!i || s->sh_name) { backmap[sec_order[i]] = nnew; snew[nnew] = s; ++nnew; } else { backmap[sec_order[i]] = 0; snew[--l] = s; } } for (i = 0; i < nnew; i++) { s = snew[i]; if (s) { s->sh_num = i; if (s->sh_type == SHT_RELX) s->sh_info = backmap[s->sh_info]; } } for_each_elem(symtab_section, 1, sym, ElfW(Sym)) if (sym->st_shndx != SHN_UNDEF && sym->st_shndx < SHN_LORESERVE) sym->st_shndx = backmap[sym->st_shndx]; if( !s1->static_link ) { for_each_elem(s1->dynsym, 1, sym, ElfW(Sym)) if (sym->st_shndx != SHN_UNDEF && sym->st_shndx < SHN_LORESERVE) sym->st_shndx = backmap[sym->st_shndx]; } for (i = 0; i < s1->nb_sections; i++) sec_order[i] = i; tcc_free(s1->sections); s1->sections = snew; s1->nb_sections = nnew; tcc_free(backmap); } /* Output an elf, coff or binary file */ /* XXX: suppress unneeded sections */ static int elf_output_file(TCCState *s1, const char *filename) { int i, ret, phnum, shnum, file_type, file_offset, *sec_order; struct dyn_inf dyninf = {0}; ElfW(Phdr) *phdr; ElfW(Sym) *sym; Section *strsec, *interp, *dynamic, *dynstr; int textrel; file_type = s1->output_type; s1->nb_errors = 0; ret = -1; phdr = NULL; sec_order = NULL; interp = dynamic = dynstr = NULL; /* avoid warning */ textrel = 0; if (file_type != TCC_OUTPUT_OBJ) { /* if linking, also link in runtime libraries (libc, libgcc, etc.) */ tcc_add_runtime(s1); resolve_common_syms(s1); if (!s1->static_link) { if (file_type == TCC_OUTPUT_EXE) { char *ptr; /* allow override the dynamic loader */ const char *elfint = getenv("LD_SO"); if (elfint == NULL) elfint = DEFAULT_ELFINTERP(s1); /* add interpreter section only if executable */ interp = new_section(s1, ".interp", SHT_PROGBITS, SHF_ALLOC); interp->sh_addralign = 1; ptr = section_ptr_add(interp, 1 + strlen(elfint)); strcpy(ptr, elfint); } /* add dynamic symbol table */ s1->dynsym = new_symtab(s1, ".dynsym", SHT_DYNSYM, SHF_ALLOC, ".dynstr", ".hash", SHF_ALLOC); dynstr = s1->dynsym->link; /* add dynamic section */ dynamic = new_section(s1, ".dynamic", SHT_DYNAMIC, SHF_ALLOC | SHF_WRITE); dynamic->link = dynstr; dynamic->sh_entsize = sizeof(ElfW(Dyn)); build_got(s1); if (file_type == TCC_OUTPUT_EXE) { bind_exe_dynsyms(s1); if (s1->nb_errors) goto the_end; bind_libs_dynsyms(s1); } else { /* shared library case: simply export all global symbols */ export_global_syms(s1); } } build_got_entries(s1); } /* we add a section for symbols */ strsec = new_section(s1, ".shstrtab", SHT_STRTAB, 0); put_elf_str(strsec, ""); /* Allocate strings for section names */ textrel = alloc_sec_names(s1, file_type, strsec); if (dynamic) { /* add a list of needed dlls */ for(i = 0; i < s1->nb_loaded_dlls; i++) { DLLReference *dllref = s1->loaded_dlls[i]; if (dllref->level == 0) put_dt(dynamic, DT_NEEDED, put_elf_str(dynstr, dllref->name)); } if (s1->rpath) put_dt(dynamic, s1->enable_new_dtags ? DT_RUNPATH : DT_RPATH, put_elf_str(dynstr, s1->rpath)); if (file_type == TCC_OUTPUT_DLL) { if (s1->soname) put_dt(dynamic, DT_SONAME, put_elf_str(dynstr, s1->soname)); /* XXX: currently, since we do not handle PIC code, we must relocate the readonly segments */ if (textrel) put_dt(dynamic, DT_TEXTREL, 0); } if (s1->symbolic) put_dt(dynamic, DT_SYMBOLIC, 0); dyninf.dynamic = dynamic; dyninf.dynstr = dynstr; /* remember offset and reserve space for 2nd call below */ dyninf.data_offset = dynamic->data_offset; fill_dynamic(s1, &dyninf); dynamic->sh_size = dynamic->data_offset; dynstr->sh_size = dynstr->data_offset; } /* compute number of program headers */ if (file_type == TCC_OUTPUT_OBJ) phnum = 0; else if (file_type == TCC_OUTPUT_DLL) phnum = 3; else if (s1->static_link) phnum = 2; else phnum = 5; /* allocate program segment headers */ phdr = tcc_mallocz(phnum * sizeof(ElfW(Phdr))); /* compute number of sections */ shnum = s1->nb_sections; /* this array is used to reorder sections in the output file */ sec_order = tcc_malloc(sizeof(int) * shnum); sec_order[0] = 0; /* compute section to program header mapping */ file_offset = layout_sections(s1, phdr, phnum, interp, strsec, &dyninf, sec_order); /* Fill remaining program header and finalize relocation related to dynamic linking. */ if (file_type != TCC_OUTPUT_OBJ) { fill_unloadable_phdr(phdr, phnum, interp, dynamic); if (dynamic) { dynamic->data_offset = dyninf.data_offset; fill_dynamic(s1, &dyninf); /* put in GOT the dynamic section address and relocate PLT */ write32le(s1->got->data, dynamic->sh_addr); if (file_type == TCC_OUTPUT_EXE || (RELOCATE_DLLPLT && file_type == TCC_OUTPUT_DLL)) relocate_plt(s1); /* relocate symbols in .dynsym now that final addresses are known */ for_each_elem(s1->dynsym, 1, sym, ElfW(Sym)) { if (sym->st_shndx != SHN_UNDEF && sym->st_shndx < SHN_LORESERVE) { /* do symbol relocation */ sym->st_value += s1->sections[sym->st_shndx]->sh_addr; } } } /* if building executable or DLL, then relocate each section except the GOT which is already relocated */ ret = final_sections_reloc(s1); if (ret) goto the_end; tidy_section_headers(s1, sec_order); /* Perform relocation to GOT or PLT entries */ if (file_type == TCC_OUTPUT_EXE && s1->static_link) fill_got(s1); else if (s1->got) fill_local_got_entries(s1); } /* Create the ELF file with name 'filename' */ ret = tcc_write_elf_file(s1, filename, phnum, phdr, file_offset, sec_order); s1->nb_sections = shnum; the_end: tcc_free(sec_order); tcc_free(phdr); return ret; } LIBTCCAPI int tcc_output_file(TCCState *s, const char *filename) { int ret; #ifdef TCC_TARGET_PE if (s->output_type != TCC_OUTPUT_OBJ) { ret = pe_output_file(s, filename); } else #endif ret = elf_output_file(s, filename); return ret; } static void *load_data(int fd, unsigned long file_offset, unsigned long size) { void *data; data = tcc_malloc(size); lseek(fd, file_offset, SEEK_SET); read(fd, data, size); return data; } typedef struct SectionMergeInfo { Section *s; /* corresponding existing section */ unsigned long offset; /* offset of the new section in the existing section */ uint8_t new_section; /* true if section 's' was added */ uint8_t link_once; /* true if link once section */ } SectionMergeInfo; ST_FUNC int tcc_object_type(int fd, ElfW(Ehdr) *h) { int size = read(fd, h, sizeof *h); if (size == sizeof *h && 0 == memcmp(h, ELFMAG, 4)) { if (h->e_type == ET_REL) return AFF_BINTYPE_REL; if (h->e_type == ET_DYN) return AFF_BINTYPE_DYN; } else if (size >= 8) { if (0 == memcmp(h, ARMAG, 8)) return AFF_BINTYPE_AR; #ifdef TCC_TARGET_COFF if (((struct filehdr*)h)->f_magic == COFF_C67_MAGIC) return AFF_BINTYPE_C67; #endif } return 0; } /* load an object file and merge it with current files */ /* XXX: handle correctly stab (debug) info */ ST_FUNC int tcc_load_object_file(TCCState *s1, int fd, unsigned long file_offset) { ElfW(Ehdr) ehdr; ElfW(Shdr) *shdr, *sh; int size, i, j, offset, offseti, nb_syms, sym_index, ret, seencompressed; unsigned char *strsec, *strtab; int *old_to_new_syms; char *sh_name, *name; SectionMergeInfo *sm_table, *sm; ElfW(Sym) *sym, *symtab; ElfW_Rel *rel; Section *s; int stab_index; int stabstr_index; stab_index = stabstr_index = 0; lseek(fd, file_offset, SEEK_SET); if (tcc_object_type(fd, &ehdr) != AFF_BINTYPE_REL) goto fail1; /* test CPU specific stuff */ if (ehdr.e_ident[5] != ELFDATA2LSB || ehdr.e_machine != EM_TCC_TARGET) { fail1: tcc_error_noabort("invalid object file"); return -1; } /* read sections */ shdr = load_data(fd, file_offset + ehdr.e_shoff, sizeof(ElfW(Shdr)) * ehdr.e_shnum); sm_table = tcc_mallocz(sizeof(SectionMergeInfo) * ehdr.e_shnum); /* load section names */ sh = &shdr[ehdr.e_shstrndx]; strsec = load_data(fd, file_offset + sh->sh_offset, sh->sh_size); /* load symtab and strtab */ old_to_new_syms = NULL; symtab = NULL; strtab = NULL; nb_syms = 0; seencompressed = 0; for(i = 1; i < ehdr.e_shnum; i++) { sh = &shdr[i]; if (sh->sh_type == SHT_SYMTAB) { if (symtab) { tcc_error_noabort("object must contain only one symtab"); fail: ret = -1; goto the_end; } nb_syms = sh->sh_size / sizeof(ElfW(Sym)); symtab = load_data(fd, file_offset + sh->sh_offset, sh->sh_size); sm_table[i].s = symtab_section; /* now load strtab */ sh = &shdr[sh->sh_link]; strtab = load_data(fd, file_offset + sh->sh_offset, sh->sh_size); } if (sh->sh_flags & SHF_COMPRESSED) seencompressed = 1; } /* now examine each section and try to merge its content with the ones in memory */ for(i = 1; i < ehdr.e_shnum; i++) { /* no need to examine section name strtab */ if (i == ehdr.e_shstrndx) continue; sh = &shdr[i]; sh_name = (char *) strsec + sh->sh_name; /* ignore sections types we do not handle */ if (sh->sh_type != SHT_PROGBITS && sh->sh_type != SHT_RELX && #ifdef TCC_ARM_EABI sh->sh_type != SHT_ARM_EXIDX && #endif sh->sh_type != SHT_NOBITS && sh->sh_type != SHT_PREINIT_ARRAY && sh->sh_type != SHT_INIT_ARRAY && sh->sh_type != SHT_FINI_ARRAY && strcmp(sh_name, ".stabstr") ) continue; if (seencompressed && (!strncmp(sh_name, ".debug_", sizeof(".debug_")-1) || (sh->sh_type == SHT_RELX && !strncmp((char*)strsec + shdr[sh->sh_info].sh_name, ".debug_", sizeof(".debug_")-1)))) continue; if (sh->sh_addralign < 1) sh->sh_addralign = 1; /* find corresponding section, if any */ for(j = 1; j < s1->nb_sections;j++) { s = s1->sections[j]; if (!strcmp(s->name, sh_name)) { if (!strncmp(sh_name, ".gnu.linkonce", sizeof(".gnu.linkonce") - 1)) { /* if a 'linkonce' section is already present, we do not add it again. It is a little tricky as symbols can still be defined in it. */ sm_table[i].link_once = 1; goto next; } else { goto found; } } } /* not found: create new section */ s = new_section(s1, sh_name, sh->sh_type, sh->sh_flags & ~SHF_GROUP); /* take as much info as possible from the section. sh_link and sh_info will be updated later */ s->sh_addralign = sh->sh_addralign; s->sh_entsize = sh->sh_entsize; sm_table[i].new_section = 1; found: if (sh->sh_type != s->sh_type) { tcc_error_noabort("invalid section type"); goto fail; } /* align start of section */ offset = s->data_offset; if (0 == strcmp(sh_name, ".stab")) { stab_index = i; goto no_align; } if (0 == strcmp(sh_name, ".stabstr")) { stabstr_index = i; goto no_align; } size = sh->sh_addralign - 1; offset = (offset + size) & ~size; if (sh->sh_addralign > s->sh_addralign) s->sh_addralign = sh->sh_addralign; s->data_offset = offset; no_align: sm_table[i].offset = offset; sm_table[i].s = s; /* concatenate sections */ size = sh->sh_size; if (sh->sh_type != SHT_NOBITS) { unsigned char *ptr; lseek(fd, file_offset + sh->sh_offset, SEEK_SET); ptr = section_ptr_add(s, size); read(fd, ptr, size); } else { s->data_offset += size; } next: ; } /* gr relocate stab strings */ if (stab_index && stabstr_index) { Stab_Sym *a, *b; unsigned o; s = sm_table[stab_index].s; a = (Stab_Sym *)(s->data + sm_table[stab_index].offset); b = (Stab_Sym *)(s->data + s->data_offset); o = sm_table[stabstr_index].offset; while (a < b) a->n_strx += o, a++; } /* second short pass to update sh_link and sh_info fields of new sections */ for(i = 1; i < ehdr.e_shnum; i++) { s = sm_table[i].s; if (!s || !sm_table[i].new_section) continue; sh = &shdr[i]; if (sh->sh_link > 0) s->link = sm_table[sh->sh_link].s; if (sh->sh_type == SHT_RELX) { s->sh_info = sm_table[sh->sh_info].s->sh_num; /* update backward link */ s1->sections[s->sh_info]->reloc = s; } } sm = sm_table; /* resolve symbols */ old_to_new_syms = tcc_mallocz(nb_syms * sizeof(int)); sym = symtab + 1; for(i = 1; i < nb_syms; i++, sym++) { if (sym->st_shndx != SHN_UNDEF && sym->st_shndx < SHN_LORESERVE) { sm = &sm_table[sym->st_shndx]; if (sm->link_once) { /* if a symbol is in a link once section, we use the already defined symbol. It is very important to get correct relocations */ if (ELFW(ST_BIND)(sym->st_info) != STB_LOCAL) { name = (char *) strtab + sym->st_name; sym_index = find_elf_sym(symtab_section, name); if (sym_index) old_to_new_syms[i] = sym_index; } continue; } /* if no corresponding section added, no need to add symbol */ if (!sm->s) continue; /* convert section number */ sym->st_shndx = sm->s->sh_num; /* offset value */ sym->st_value += sm->offset; } /* add symbol */ name = (char *) strtab + sym->st_name; sym_index = set_elf_sym(symtab_section, sym->st_value, sym->st_size, sym->st_info, sym->st_other, sym->st_shndx, name); old_to_new_syms[i] = sym_index; } /* third pass to patch relocation entries */ for(i = 1; i < ehdr.e_shnum; i++) { s = sm_table[i].s; if (!s) continue; sh = &shdr[i]; offset = sm_table[i].offset; switch(s->sh_type) { case SHT_RELX: /* take relocation offset information */ offseti = sm_table[sh->sh_info].offset; for_each_elem(s, (offset / sizeof(*rel)), rel, ElfW_Rel) { int type; unsigned sym_index; /* convert symbol index */ type = ELFW(R_TYPE)(rel->r_info); sym_index = ELFW(R_SYM)(rel->r_info); /* NOTE: only one symtab assumed */ if (sym_index >= nb_syms) goto invalid_reloc; sym_index = old_to_new_syms[sym_index]; /* ignore link_once in rel section. */ if (!sym_index && !sm->link_once #ifdef TCC_TARGET_ARM && type != R_ARM_V4BX #endif ) { invalid_reloc: tcc_error_noabort("Invalid relocation entry [%2d] '%s' @ %.8x", i, strsec + sh->sh_name, rel->r_offset); goto fail; } rel->r_info = ELFW(R_INFO)(sym_index, type); /* offset the relocation offset */ rel->r_offset += offseti; #ifdef TCC_TARGET_ARM /* Jumps and branches from a Thumb code to a PLT entry need special handling since PLT entries are ARM code. Unconditional bl instructions referencing PLT entries are handled by converting these instructions into blx instructions. Other case of instructions referencing a PLT entry require to add a Thumb stub before the PLT entry to switch to ARM mode. We set bit plt_thumb_stub of the attribute of a symbol to indicate such a case. */ if (type == R_ARM_THM_JUMP24) get_sym_attr(s1, sym_index, 1)->plt_thumb_stub = 1; #endif } break; default: break; } } ret = 0; the_end: tcc_free(symtab); tcc_free(strtab); tcc_free(old_to_new_syms); tcc_free(sm_table); tcc_free(strsec); tcc_free(shdr); return ret; } typedef struct ArchiveHeader { char ar_name[16]; /* name of this member */ char ar_date[12]; /* file mtime */ char ar_uid[6]; /* owner uid; printed as decimal */ char ar_gid[6]; /* owner gid; printed as decimal */ char ar_mode[8]; /* file mode, printed as octal */ char ar_size[10]; /* file size, printed as decimal */ char ar_fmag[2]; /* should contain ARFMAG */ } ArchiveHeader; static int get_be32(const uint8_t *b) { return b[3] | (b[2] << 8) | (b[1] << 16) | (b[0] << 24); } static long get_be64(const uint8_t *b) { long long ret = get_be32(b); ret = (ret << 32) | (unsigned)get_be32(b+4); return (long)ret; } /* load only the objects which resolve undefined symbols */ static int tcc_load_alacarte(TCCState *s1, int fd, int size, int entrysize) { long i, bound, nsyms, sym_index, off, ret; uint8_t *data; const char *ar_names, *p; const uint8_t *ar_index; ElfW(Sym) *sym; data = tcc_malloc(size); if (read(fd, data, size) != size) goto fail; nsyms = entrysize == 4 ? get_be32(data) : get_be64(data); ar_index = data + entrysize; ar_names = (char *) ar_index + nsyms * entrysize; do { bound = 0; for(p = ar_names, i = 0; i < nsyms; i++, p += strlen(p)+1) { sym_index = find_elf_sym(symtab_section, p); if(sym_index) { sym = &((ElfW(Sym) *)symtab_section->data)[sym_index]; if(sym->st_shndx == SHN_UNDEF) { off = (entrysize == 4 ? get_be32(ar_index + i * 4) : get_be64(ar_index + i * 8)) + sizeof(ArchiveHeader); ++bound; if(tcc_load_object_file(s1, fd, off) < 0) { fail: ret = -1; goto the_end; } } } } } while(bound); ret = 0; the_end: tcc_free(data); return ret; } /* load a '.a' file */ ST_FUNC int tcc_load_archive(TCCState *s1, int fd) { ArchiveHeader hdr; char ar_size[11]; char ar_name[17]; char magic[8]; int size, len, i; unsigned long file_offset; /* skip magic which was already checked */ read(fd, magic, sizeof(magic)); for(;;) { len = read(fd, &hdr, sizeof(hdr)); if (len == 0) break; if (len != sizeof(hdr)) { tcc_error_noabort("invalid archive"); return -1; } memcpy(ar_size, hdr.ar_size, sizeof(hdr.ar_size)); ar_size[sizeof(hdr.ar_size)] = '\0'; size = strtol(ar_size, NULL, 0); memcpy(ar_name, hdr.ar_name, sizeof(hdr.ar_name)); for(i = sizeof(hdr.ar_name) - 1; i >= 0; i--) { if (ar_name[i] != ' ') break; } ar_name[i + 1] = '\0'; file_offset = lseek(fd, 0, SEEK_CUR); /* align to even */ size = (size + 1) & ~1; if (!strcmp(ar_name, "/")) { /* coff symbol table : we handle it */ if(s1->alacarte_link) return tcc_load_alacarte(s1, fd, size, 4); } else if (!strcmp(ar_name, "/SYM64/")) { if(s1->alacarte_link) return tcc_load_alacarte(s1, fd, size, 8); } else { ElfW(Ehdr) ehdr; if (tcc_object_type(fd, &ehdr) == AFF_BINTYPE_REL) { if (tcc_load_object_file(s1, fd, file_offset) < 0) return -1; } } lseek(fd, file_offset + size, SEEK_SET); } return 0; } #ifndef TCC_TARGET_PE /* load a DLL and all referenced DLLs. 'level = 0' means that the DLL is referenced by the user (so it should be added as DT_NEEDED in the generated ELF file) */ ST_FUNC int tcc_load_dll(TCCState *s1, int fd, const char *filename, int level) { ElfW(Ehdr) ehdr; ElfW(Shdr) *shdr, *sh, *sh1; int i, j, nb_syms, nb_dts, sym_bind, ret; ElfW(Sym) *sym, *dynsym; ElfW(Dyn) *dt, *dynamic; unsigned char *dynstr; const char *name, *soname; DLLReference *dllref; read(fd, &ehdr, sizeof(ehdr)); /* test CPU specific stuff */ if (ehdr.e_ident[5] != ELFDATA2LSB || ehdr.e_machine != EM_TCC_TARGET) { tcc_error_noabort("bad architecture"); return -1; } /* read sections */ shdr = load_data(fd, ehdr.e_shoff, sizeof(ElfW(Shdr)) * ehdr.e_shnum); /* load dynamic section and dynamic symbols */ nb_syms = 0; nb_dts = 0; dynamic = NULL; dynsym = NULL; /* avoid warning */ dynstr = NULL; /* avoid warning */ for(i = 0, sh = shdr; i < ehdr.e_shnum; i++, sh++) { switch(sh->sh_type) { case SHT_DYNAMIC: nb_dts = sh->sh_size / sizeof(ElfW(Dyn)); dynamic = load_data(fd, sh->sh_offset, sh->sh_size); break; case SHT_DYNSYM: nb_syms = sh->sh_size / sizeof(ElfW(Sym)); dynsym = load_data(fd, sh->sh_offset, sh->sh_size); sh1 = &shdr[sh->sh_link]; dynstr = load_data(fd, sh1->sh_offset, sh1->sh_size); break; default: break; } } /* compute the real library name */ soname = tcc_basename(filename); for(i = 0, dt = dynamic; i < nb_dts; i++, dt++) { if (dt->d_tag == DT_SONAME) { soname = (char *) dynstr + dt->d_un.d_val; } } /* if the dll is already loaded, do not load it */ for(i = 0; i < s1->nb_loaded_dlls; i++) { dllref = s1->loaded_dlls[i]; if (!strcmp(soname, dllref->name)) { /* but update level if needed */ if (level < dllref->level) dllref->level = level; ret = 0; goto the_end; } } /* add the dll and its level */ dllref = tcc_mallocz(sizeof(DLLReference) + strlen(soname)); dllref->level = level; strcpy(dllref->name, soname); dynarray_add(&s1->loaded_dlls, &s1->nb_loaded_dlls, dllref); /* add dynamic symbols in dynsym_section */ for(i = 1, sym = dynsym + 1; i < nb_syms; i++, sym++) { sym_bind = ELFW(ST_BIND)(sym->st_info); if (sym_bind == STB_LOCAL) continue; name = (char *) dynstr + sym->st_name; set_elf_sym(s1->dynsymtab_section, sym->st_value, sym->st_size, sym->st_info, sym->st_other, sym->st_shndx, name); } /* load all referenced DLLs */ for(i = 0, dt = dynamic; i < nb_dts; i++, dt++) { switch(dt->d_tag) { case DT_NEEDED: name = (char *) dynstr + dt->d_un.d_val; for(j = 0; j < s1->nb_loaded_dlls; j++) { dllref = s1->loaded_dlls[j]; if (!strcmp(name, dllref->name)) goto already_loaded; } if (tcc_add_dll(s1, name, AFF_REFERENCED_DLL) < 0) { tcc_error_noabort("referenced dll '%s' not found", name); ret = -1; goto the_end; } already_loaded: break; } } ret = 0; the_end: tcc_free(dynstr); tcc_free(dynsym); tcc_free(dynamic); tcc_free(shdr); return ret; } #define LD_TOK_NAME 256 #define LD_TOK_EOF (-1) /* return next ld script token */ static int ld_next(TCCState *s1, char *name, int name_size) { int c; char *q; redo: switch(ch) { case ' ': case '\t': case '\f': case '\v': case '\r': case '\n': inp(); goto redo; case '/': minp(); if (ch == '*') { file->buf_ptr = parse_comment(file->buf_ptr); ch = file->buf_ptr[0]; goto redo; } else { q = name; *q++ = '/'; goto parse_name; } break; case '\\': ch = handle_eob(); if (ch != '\\') goto redo; /* fall through */ /* case 'a' ... 'z': */ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': /* case 'A' ... 'z': */ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': case '.': case '$': case '~': q = name; parse_name: for(;;) { if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || strchr("/.-_+=$:\\,~", ch))) break; if ((q - name) < name_size - 1) { *q++ = ch; } minp(); } *q = '\0'; c = LD_TOK_NAME; break; case CH_EOF: c = LD_TOK_EOF; break; default: c = ch; inp(); break; } return c; } static int ld_add_file(TCCState *s1, const char filename[]) { if (filename[0] == '/') { if (CONFIG_SYSROOT[0] == '\0' && tcc_add_file_internal(s1, filename, AFF_TYPE_BIN) == 0) return 0; filename = tcc_basename(filename); } return tcc_add_dll(s1, filename, 0); } static inline int new_undef_syms(void) { int ret = 0; ret = new_undef_sym; new_undef_sym = 0; return ret; } static int ld_add_file_list(TCCState *s1, const char *cmd, int as_needed) { char filename[1024], libname[1024]; int t, group, nblibs = 0, ret = 0; char **libs = NULL; group = !strcmp(cmd, "GROUP"); if (!as_needed) new_undef_syms(); t = ld_next(s1, filename, sizeof(filename)); if (t != '(') expect("("); t = ld_next(s1, filename, sizeof(filename)); for(;;) { libname[0] = '\0'; if (t == LD_TOK_EOF) { tcc_error_noabort("unexpected end of file"); ret = -1; goto lib_parse_error; } else if (t == ')') { break; } else if (t == '-') { t = ld_next(s1, filename, sizeof(filename)); if ((t != LD_TOK_NAME) || (filename[0] != 'l')) { tcc_error_noabort("library name expected"); ret = -1; goto lib_parse_error; } pstrcpy(libname, sizeof libname, &filename[1]); if (s1->static_link) { snprintf(filename, sizeof filename, "lib%s.a", libname); } else { snprintf(filename, sizeof filename, "lib%s.so", libname); } } else if (t != LD_TOK_NAME) { tcc_error_noabort("filename expected"); ret = -1; goto lib_parse_error; } if (!strcmp(filename, "AS_NEEDED")) { ret = ld_add_file_list(s1, cmd, 1); if (ret) goto lib_parse_error; } else { /* TODO: Implement AS_NEEDED support. Ignore it for now */ if (!as_needed) { ret = ld_add_file(s1, filename); if (ret) goto lib_parse_error; if (group) { /* Add the filename *and* the libname to avoid future conversions */ dynarray_add(&libs, &nblibs, tcc_strdup(filename)); if (libname[0] != '\0') dynarray_add(&libs, &nblibs, tcc_strdup(libname)); } } } t = ld_next(s1, filename, sizeof(filename)); if (t == ',') { t = ld_next(s1, filename, sizeof(filename)); } } if (group && !as_needed) { while (new_undef_syms()) { int i; for (i = 0; i < nblibs; i ++) ld_add_file(s1, libs[i]); } } lib_parse_error: dynarray_reset(&libs, &nblibs); return ret; } /* interpret a subset of GNU ldscripts to handle the dummy libc.so files */ ST_FUNC int tcc_load_ldscript(TCCState *s1) { char cmd[64]; char filename[1024]; int t, ret; ch = handle_eob(); for(;;) { t = ld_next(s1, cmd, sizeof(cmd)); if (t == LD_TOK_EOF) return 0; else if (t != LD_TOK_NAME) return -1; if (!strcmp(cmd, "INPUT") || !strcmp(cmd, "GROUP")) { ret = ld_add_file_list(s1, cmd, 0); if (ret) return ret; } else if (!strcmp(cmd, "OUTPUT_FORMAT") || !strcmp(cmd, "TARGET")) { /* ignore some commands */ t = ld_next(s1, cmd, sizeof(cmd)); if (t != '(') expect("("); for(;;) { t = ld_next(s1, filename, sizeof(filename)); if (t == LD_TOK_EOF) { tcc_error_noabort("unexpected end of file"); return -1; } else if (t == ')') { break; } } } else { return -1; } } return 0; } #endif /* !TCC_TARGET_PE */
ph->p_paddr = ph->p_vaddr;
ph->p_paddr = ph->p_vaddr; if (ph->p_paddr >= 0xC0000000) ph->p_paddr = ph->p_paddr - 0xC0000000;
static void fill_local_got_entries(TCCState *s1) { ElfW_Rel *rel; for_each_elem(s1->got->reloc, 0, rel, ElfW_Rel) {
static void fill_local_got_entries(TCCState *s1) { ElfW_Rel *rel; if (!s1->got->reloc) return; for_each_elem(s1->got->reloc, 0, rel, ElfW_Rel) {
/* * TCC - Tiny C Compiler * * Copyright (c) 2001-2004 Fabrice Bellard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "tcc.h" #if ONE_SOURCE # include "libtcc.c" #endif #include "tcctools.c" static const char help[] = "Tiny C Compiler "TCC_VERSION" - Copyright (C) 2001-2006 Fabrice Bellard\n" "Usage: tcc [options...] [-o outfile] [-c] infile(s)...\n" " tcc [options...] -run infile [arguments...]\n" "General options:\n" " -c compile only - generate an object file\n" " -o outfile set output filename\n" " -run run compiled source\n" " -fflag set or reset (with 'no-' prefix) 'flag' (see tcc -hh)\n" " -Wwarning set or reset (with 'no-' prefix) 'warning' (see tcc -hh)\n" " -w disable all warnings\n" " -v -vv show version, show search paths or loaded files\n" " -h -hh show this, show more help\n" " -bench show compilation statistics\n" " - use stdin pipe as infile\n" " @listfile read arguments from listfile\n" "Preprocessor options:\n" " -Idir add include path 'dir'\n" " -Dsym[=val] define 'sym' with value 'val'\n" " -Usym undefine 'sym'\n" " -E preprocess only\n" "Linker options:\n" " -Ldir add library path 'dir'\n" " -llib link with dynamic or static library 'lib'\n" " -r generate (relocatable) object file\n" " -shared generate a shared library/dll\n" " -rdynamic export all global symbols to dynamic linker\n" " -soname set name for shared library to be used at runtime\n" " -Wl,-opt[=val] set linker option (see tcc -hh)\n" "Debugger options:\n" " -g generate runtime debug info\n" #ifdef CONFIG_TCC_BCHECK " -b compile with built-in memory and bounds checker (implies -g)\n" #endif #ifdef CONFIG_TCC_BACKTRACE " -bt N show N callers in stack traces\n" #endif "Misc. options:\n" " -x[c|a|n] specify type of the next infile\n" " -nostdinc do not use standard system include paths\n" " -nostdlib do not link with standard crt and libraries\n" " -Bdir set tcc's private include/library dir\n" " -MD generate dependency file for make\n" " -MF file specify dependency file name\n" " -m32/64 defer to i386/x86_64 cross compiler\n" "Tools:\n" " create library : tcc -ar [rcsv] lib.a files\n" #ifdef TCC_TARGET_PE " create def file : tcc -impdef lib.dll [-v] [-o lib.def]\n" #endif ; static const char help2[] = "Tiny C Compiler "TCC_VERSION" - More Options\n" "Special options:\n" " -P -P1 with -E: no/alternative #line output\n" " -dD -dM with -E: output #define directives\n" " -pthread same as -D_REENTRANT and -lpthread\n" " -On same as -D__OPTIMIZE__ for n > 0\n" " -Wp,-opt same as -opt\n" " -include file include 'file' above each input file\n" " -isystem dir add 'dir' to system include path\n" " -static link to static libraries (not recommended)\n" " -dumpversion print version\n" " -print-search-dirs print search paths\n" " -dt with -run/-E: auto-define 'test_...' macros\n" "Ignored options:\n" " --param -pedantic -pipe -s -std -traditional\n" "-W... warnings:\n" " all turn on some (*) warnings\n" " error stop after first warning\n" " unsupported warn about ignored options, pragmas, etc.\n" " write-strings strings are const\n" " implicit-function-declaration warn for missing prototype (*)\n" "-f[no-]... flags:\n" " unsigned-char default char is unsigned\n" " signed-char default char is signed\n" " common use common section instead of bss\n" " leading-underscore decorate extern symbols\n" " ms-extensions allow anonymous struct in struct\n" " dollars-in-identifiers allow '$' in C symbols\n" "-m... target specific options:\n" " ms-bitfields use MSVC bitfield layout\n" #ifdef TCC_TARGET_ARM " float-abi hard/softfp on arm\n" #endif #ifdef TCC_TARGET_X86_64 " no-sse disable floats on x86_64\n" #endif "-Wl,... linker options:\n" " -nostdlib do not link with standard crt/libs\n" " -[no-]whole-archive load lib(s) fully/only as needed\n" " -export-all-symbols same as -rdynamic\n" " -image-base= -Ttext= set base address of executable\n" " -section-alignment= set section alignment in executable\n" #ifdef TCC_TARGET_PE " -file-alignment= set PE file alignment\n" " -stack= set PE stack reserve\n" " -large-address-aware set related PE option\n" " -subsystem=[console/windows] set PE subsystem\n" " -oformat=[pe-* binary] set executable output format\n" "Predefined macros:\n" " tcc -E -dM - < nul\n" #else " -rpath= set dynamic library search path\n" " -enable-new-dtags set DT_RUNPATH instead of DT_RPATH\n" " -soname= set DT_SONAME elf tag\n" " -Bsymbolic set DT_SYMBOLIC elf tag\n" " -oformat=[elf32/64-* binary] set executable output format\n" " -init= -fini= -as-needed -O (ignored)\n" "Predefined macros:\n" " tcc -E -dM - < /dev/null\n" #endif "See also the manual for more details.\n" ; static const char version[] = "tcc version "TCC_VERSION" (" #ifdef TCC_TARGET_I386 "i386" #elif defined TCC_TARGET_X86_64 "x86_64" #elif defined TCC_TARGET_C67 "C67" #elif defined TCC_TARGET_ARM "ARM" #elif defined TCC_TARGET_ARM64 "AArch64" #endif #ifdef TCC_ARM_HARDFLOAT " Hard Float" #endif #ifdef TCC_TARGET_PE " Windows" #elif defined(TCC_TARGET_MACHO) " Darwin" #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) " FreeBSD" #else " Linux" #endif ")\n" ; static void print_dirs(const char *msg, char **paths, int nb_paths) { int i; printf("%s:\n%s", msg, nb_paths ? "" : " -\n"); for(i = 0; i < nb_paths; i++) printf(" %s\n", paths[i]); } static void print_search_dirs(TCCState *s) { printf("install: %s\n", s->tcc_lib_path); /* print_dirs("programs", NULL, 0); */ print_dirs("include", s->sysinclude_paths, s->nb_sysinclude_paths); print_dirs("libraries", s->library_paths, s->nb_library_paths); printf("libtcc1:\n %s/"TCC_LIBTCC1"\n", s->tcc_lib_path); #ifndef TCC_TARGET_PE print_dirs("crt", s->crt_paths, s->nb_crt_paths); printf("elfinterp:\n %s\n", DEFAULT_ELFINTERP(s)); #endif } static void set_environment(TCCState *s) { char * path; path = getenv("C_INCLUDE_PATH"); if(path != NULL) { tcc_add_sysinclude_path(s, path); } path = getenv("CPATH"); if(path != NULL) { tcc_add_include_path(s, path); } path = getenv("LIBRARY_PATH"); if(path != NULL) { tcc_add_library_path(s, path); } } static char *default_outputfile(TCCState *s, const char *first_file) { char buf[1024]; char *ext; const char *name = "a"; if (first_file && strcmp(first_file, "-")) name = tcc_basename(first_file); snprintf(buf, sizeof(buf), "%s", name); ext = tcc_fileextension(buf); #ifdef TCC_TARGET_PE if (s->output_type == TCC_OUTPUT_DLL) strcpy(ext, ".dll"); else if (s->output_type == TCC_OUTPUT_EXE) strcpy(ext, ".exe"); else #endif if (s->output_type == TCC_OUTPUT_OBJ && !s->option_r && *ext) strcpy(ext, ".o"); else strcpy(buf, "a.out"); return tcc_strdup(buf); } static unsigned getclock_ms(void) { #ifdef _WIN32 return GetTickCount(); #else struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec*1000 + (tv.tv_usec+500)/1000; #endif } int main(int argc0, char **argv0) { TCCState *s; int ret, opt, n = 0, t = 0; unsigned start_time = 0; const char *first_file; int argc; char **argv; FILE *ppfp = stdout; redo: argc = argc0, argv = argv0; s = tcc_new(); opt = tcc_parse_args(s, &argc, &argv, 1); if ((n | t) == 0) { if (opt == OPT_HELP) return printf(help), 1; if (opt == OPT_HELP2) return printf(help2), 1; if (opt == OPT_M32 || opt == OPT_M64) tcc_tool_cross(s, argv, opt); /* never returns */ if (s->verbose) printf(version); if (opt == OPT_AR) return tcc_tool_ar(s, argc, argv); #ifdef TCC_TARGET_PE if (opt == OPT_IMPDEF) return tcc_tool_impdef(s, argc, argv); #endif if (opt == OPT_V) return 0; if (opt == OPT_PRINT_DIRS) { /* initialize search dirs */ set_environment(s); tcc_set_output_type(s, TCC_OUTPUT_MEMORY); print_search_dirs(s); return 0; } n = s->nb_files; if (n == 0) tcc_error("no input files\n"); if (s->output_type == TCC_OUTPUT_PREPROCESS) { if (s->outfile) { ppfp = fopen(s->outfile, "w"); if (!ppfp) tcc_error("could not write '%s'", s->outfile); } } else if (s->output_type == TCC_OUTPUT_OBJ && !s->option_r) { if (s->nb_libraries) tcc_error("cannot specify libraries with -c"); if (n > 1 && s->outfile) tcc_error("cannot specify output file with -c many files"); } else { if (s->option_pthread) tcc_set_options(s, "-lpthread"); } if (s->do_bench) start_time = getclock_ms(); } set_environment(s); if (s->output_type == 0) s->output_type = TCC_OUTPUT_EXE; tcc_set_output_type(s, s->output_type); s->ppfp = ppfp; if ((s->output_type == TCC_OUTPUT_MEMORY || s->output_type == TCC_OUTPUT_PREPROCESS) && (s->dflag & 16)) s->dflag |= t ? 32 : 0, s->run_test = ++t, n = s->nb_files; /* compile or add each files or library */ for (first_file = NULL, ret = 0;;) { struct filespec *f = s->files[s->nb_files - n]; s->filetype = f->type; s->alacarte_link = f->alacarte; if (f->type == AFF_TYPE_LIB) { if (tcc_add_library_err(s, f->name) < 0) ret = 1; } else { if (1 == s->verbose) printf("-> %s\n", f->name); if (!first_file) first_file = f->name; if (tcc_add_file(s, f->name) < 0) ret = 1; } s->filetype = 0; s->alacarte_link = 1; if (--n == 0 || ret || (s->output_type == TCC_OUTPUT_OBJ && !s->option_r)) break; } if (s->run_test) { t = 0; } else if (s->output_type == TCC_OUTPUT_PREPROCESS) { ; } else if (0 == ret) { if (s->output_type == TCC_OUTPUT_MEMORY) { #ifdef TCC_IS_NATIVE ret = tcc_run(s, argc, argv); #endif } else { if (!s->outfile) s->outfile = default_outputfile(s, first_file); if (tcc_output_file(s, s->outfile)) ret = 1; else if (s->gen_deps) gen_makedeps(s, s->outfile, s->deps_outfile); } } if (s->do_bench && (n | t | ret) == 0) tcc_print_stats(s, getclock_ms() - start_time); tcc_delete(s); if (ret == 0 && n) goto redo; /* compile more files with -c */ if (t) goto redo; /* run more tests with -dt -run */ if (ppfp && ppfp != stdout) fclose(ppfp); return ret; }
/* * TCC - Tiny C Compiler * * Copyright (c) 2001-2004 Fabrice Bellard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _TCC_H #define _TCC_H #define _GNU_SOURCE #include "config.h" #include <stdlib.h> #include <stdio.h> #include <stdarg.h> #include <string.h> #include <errno.h> #include <math.h> #include <fcntl.h> #include <setjmp.h> #include <time.h> #ifndef _WIN32 # include <unistd.h> # include <sys/time.h> # ifndef CONFIG_TCC_STATIC # include <dlfcn.h> # endif /* XXX: need to define this to use them in non ISOC99 context */ extern float strtof (const char *__nptr, char **__endptr); extern long double strtold (const char *__nptr, char **__endptr); #endif #ifdef _WIN32 # include <windows.h> # include <io.h> /* open, close etc. */ # include <direct.h> /* getcwd */ # ifdef __GNUC__ # include <stdint.h> # endif # define inline __inline # define snprintf _snprintf # define vsnprintf _vsnprintf # ifndef __GNUC__ # define strtold (long double)strtod # define strtof (float)strtod # define strtoll _strtoi64 # define strtoull _strtoui64 # endif # ifdef LIBTCC_AS_DLL # define LIBTCCAPI __declspec(dllexport) # define PUB_FUNC LIBTCCAPI # endif # define inp next_inp /* inp is an intrinsic on msvc/mingw */ # ifdef _MSC_VER # pragma warning (disable : 4244) // conversion from 'uint64_t' to 'int', possible loss of data # pragma warning (disable : 4267) // conversion from 'size_t' to 'int', possible loss of data # pragma warning (disable : 4996) // The POSIX name for this item is deprecated. Instead, use the ISO C and C++ conformant name # pragma warning (disable : 4018) // signed/unsigned mismatch # pragma warning (disable : 4146) // unary minus operator applied to unsigned type, result still unsigned # define ssize_t intptr_t # endif # undef CONFIG_TCC_STATIC #endif #ifndef O_BINARY # define O_BINARY 0 #endif #ifndef offsetof #define offsetof(type, field) ((size_t) &((type *)0)->field) #endif #ifndef countof #define countof(tab) (sizeof(tab) / sizeof((tab)[0])) #endif #ifdef _MSC_VER # define NORETURN __declspec(noreturn) # define ALIGNED(x) __declspec(align(x)) #else # define NORETURN __attribute__((noreturn)) # define ALIGNED(x) __attribute__((aligned(x))) #endif #ifdef _WIN32 # define IS_DIRSEP(c) (c == '/' || c == '\\') # define IS_ABSPATH(p) (IS_DIRSEP(p[0]) || (p[0] && p[1] == ':' && IS_DIRSEP(p[2]))) # define PATHCMP stricmp # define PATHSEP ";" #else # define IS_DIRSEP(c) (c == '/') # define IS_ABSPATH(p) IS_DIRSEP(p[0]) # define PATHCMP strcmp # define PATHSEP ":" #endif /* -------------------------------------------- */ /* parser debug */ /* #define PARSE_DEBUG */ /* preprocessor debug */ /* #define PP_DEBUG */ /* include file debug */ /* #define INC_DEBUG */ /* memory leak debug */ /* #define MEM_DEBUG */ /* assembler debug */ /* #define ASM_DEBUG */ /* target selection */ /* #define TCC_TARGET_I386 *//* i386 code generator */ /* #define TCC_TARGET_X86_64 *//* x86-64 code generator */ /* #define TCC_TARGET_ARM *//* ARMv4 code generator */ /* #define TCC_TARGET_ARM64 *//* ARMv8 code generator */ /* #define TCC_TARGET_C67 *//* TMS320C67xx code generator */ /* default target is I386 */ #if !defined(TCC_TARGET_I386) && !defined(TCC_TARGET_ARM) && \ !defined(TCC_TARGET_ARM64) && !defined(TCC_TARGET_C67) && \ !defined(TCC_TARGET_X86_64) # if defined __x86_64__ || defined _AMD64_ # define TCC_TARGET_X86_64 # elif defined __arm__ # define TCC_TARGET_ARM # define TCC_ARM_EABI # define TCC_ARM_HARDFLOAT # elif defined __aarch64__ # define TCC_TARGET_ARM64 # else # define TCC_TARGET_I386 # endif # ifdef _WIN32 # define TCC_TARGET_PE 1 # endif #endif /* only native compiler supports -run */ #if defined _WIN32 == defined TCC_TARGET_PE # if (defined __i386__ || defined _X86_) && defined TCC_TARGET_I386 # define TCC_IS_NATIVE # elif (defined __x86_64__ || defined _AMD64_) && defined TCC_TARGET_X86_64 # define TCC_IS_NATIVE # elif defined __arm__ && defined TCC_TARGET_ARM # define TCC_IS_NATIVE # elif defined __aarch64__ && defined TCC_TARGET_ARM64 # define TCC_IS_NATIVE # endif #endif #if defined TCC_IS_NATIVE && !defined CONFIG_TCCBOOT # define CONFIG_TCC_BACKTRACE # if (defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64) \ && !defined TCC_UCLIBC && !defined TCC_MUSL # define CONFIG_TCC_BCHECK /* enable bound checking code */ # endif #endif /* ------------ path configuration ------------ */ #ifndef CONFIG_SYSROOT # define CONFIG_SYSROOT "" #endif #ifndef CONFIG_TCCDIR # define CONFIG_TCCDIR "/usr/local/lib/tcc" #endif #ifndef CONFIG_LDDIR # define CONFIG_LDDIR "lib" #endif #ifdef CONFIG_TRIPLET # define USE_TRIPLET(s) s "/" CONFIG_TRIPLET # define ALSO_TRIPLET(s) USE_TRIPLET(s) ":" s #else # define USE_TRIPLET(s) s # define ALSO_TRIPLET(s) s #endif /* path to find crt1.o, crti.o and crtn.o */ #ifndef CONFIG_TCC_CRTPREFIX # define CONFIG_TCC_CRTPREFIX USE_TRIPLET(CONFIG_SYSROOT "/usr/" CONFIG_LDDIR) #endif /* Below: {B} is substituted by CONFIG_TCCDIR (rsp. -B option) */ /* system include paths */ #ifndef CONFIG_TCC_SYSINCLUDEPATHS # ifdef TCC_TARGET_PE # define CONFIG_TCC_SYSINCLUDEPATHS "{B}/include"PATHSEP"{B}/include/winapi" # else # define CONFIG_TCC_SYSINCLUDEPATHS \ "{B}/include" \ ":" ALSO_TRIPLET(CONFIG_SYSROOT "/usr/local/include") \ ":" ALSO_TRIPLET(CONFIG_SYSROOT "/usr/include") # endif #endif /* library search paths */ #ifndef CONFIG_TCC_LIBPATHS # ifdef TCC_TARGET_PE # define CONFIG_TCC_LIBPATHS "{B}/lib" # else # define CONFIG_TCC_LIBPATHS \ ALSO_TRIPLET(CONFIG_SYSROOT "/usr/" CONFIG_LDDIR) \ ":" ALSO_TRIPLET(CONFIG_SYSROOT "/" CONFIG_LDDIR) \ ":" ALSO_TRIPLET(CONFIG_SYSROOT "/usr/local/" CONFIG_LDDIR) # endif #endif /* name of ELF interpreter */ #ifndef CONFIG_TCC_ELFINTERP # if defined __FreeBSD__ # define CONFIG_TCC_ELFINTERP "/libexec/ld-elf.so.1" # elif defined __FreeBSD_kernel__ # if defined(TCC_TARGET_X86_64) # define CONFIG_TCC_ELFINTERP "/lib/ld-kfreebsd-x86-64.so.1" # else # define CONFIG_TCC_ELFINTERP "/lib/ld.so.1" # endif # elif defined __DragonFly__ # define CONFIG_TCC_ELFINTERP "/usr/libexec/ld-elf.so.2" # elif defined __NetBSD__ # define CONFIG_TCC_ELFINTERP "/usr/libexec/ld.elf_so" # elif defined __GNU__ # define CONFIG_TCC_ELFINTERP "/lib/ld.so" # elif defined(TCC_TARGET_PE) # define CONFIG_TCC_ELFINTERP "-" # elif defined(TCC_UCLIBC) # define CONFIG_TCC_ELFINTERP "/lib/ld-uClibc.so.0" /* is there a uClibc for x86_64 ? */ # elif defined TCC_TARGET_ARM64 # if defined(TCC_MUSL) # define CONFIG_TCC_ELFINTERP "/lib/ld-musl-aarch64.so.1" # else # define CONFIG_TCC_ELFINTERP "/lib/ld-linux-aarch64.so.1" # endif # elif defined(TCC_TARGET_X86_64) # if defined(TCC_MUSL) # define CONFIG_TCC_ELFINTERP "/lib/ld-musl-x86_64.so.1" # else # define CONFIG_TCC_ELFINTERP "/lib64/ld-linux-x86-64.so.2" # endif # elif !defined(TCC_ARM_EABI) # if defined(TCC_MUSL) # define CONFIG_TCC_ELFINTERP "/lib/ld-musl-arm.so.1" # else # define CONFIG_TCC_ELFINTERP "/lib/ld-linux.so.2" # endif # endif #endif /* var elf_interp dans *-gen.c */ #ifdef CONFIG_TCC_ELFINTERP # define DEFAULT_ELFINTERP(s) CONFIG_TCC_ELFINTERP #else # define DEFAULT_ELFINTERP(s) default_elfinterp(s) #endif /* (target specific) libtcc1.a */ #ifndef TCC_LIBTCC1 # define TCC_LIBTCC1 "libtcc1.a" #endif /* library to use with CONFIG_USE_LIBGCC instead of libtcc1.a */ #if defined CONFIG_USE_LIBGCC && !defined TCC_LIBGCC #define TCC_LIBGCC USE_TRIPLET(CONFIG_SYSROOT "/" CONFIG_LDDIR) "/libgcc_s.so.1" #endif /* -------------------------------------------- */ #include "libtcc.h" #include "elf.h" #include "stab.h" /* -------------------------------------------- */ #ifndef PUB_FUNC /* functions used by tcc.c but not in libtcc.h */ # define PUB_FUNC #endif #ifndef ONE_SOURCE # define ONE_SOURCE 1 #endif #if ONE_SOURCE #define ST_INLN static inline #define ST_FUNC static #define ST_DATA static #else #define ST_INLN #define ST_FUNC #define ST_DATA extern #endif #ifdef TCC_PROFILE /* profile all functions */ # define static #endif /* -------------------------------------------- */ /* include the target specific definitions */ #define TARGET_DEFS_ONLY #ifdef TCC_TARGET_I386 # include "i386-gen.c" # include "i386-link.c" #endif #ifdef TCC_TARGET_X86_64 # include "x86_64-gen.c" # include "x86_64-link.c" #endif #ifdef TCC_TARGET_ARM # include "arm-gen.c" # include "arm-link.c" # include "arm-asm.c" #endif #ifdef TCC_TARGET_ARM64 # include "arm64-gen.c" # include "arm64-link.c" #endif #ifdef TCC_TARGET_C67 # define TCC_TARGET_COFF # include "coff.h" # include "c67-gen.c" # include "c67-link.c" #endif #undef TARGET_DEFS_ONLY /* -------------------------------------------- */ #if PTR_SIZE == 8 # define ELFCLASSW ELFCLASS64 # define ElfW(type) Elf##64##_##type # define ELFW(type) ELF##64##_##type # define ElfW_Rel ElfW(Rela) # define SHT_RELX SHT_RELA # define REL_SECTION_FMT ".rela%s" #else # define ELFCLASSW ELFCLASS32 # define ElfW(type) Elf##32##_##type # define ELFW(type) ELF##32##_##type # define ElfW_Rel ElfW(Rel) # define SHT_RELX SHT_REL # define REL_SECTION_FMT ".rel%s" #endif /* target address type */ #define addr_t ElfW(Addr) #define ElfSym ElfW(Sym) #if PTR_SIZE == 8 && !defined TCC_TARGET_PE # define LONG_SIZE 8 #else # define LONG_SIZE 4 #endif /* -------------------------------------------- */ #define INCLUDE_STACK_SIZE 32 #define IFDEF_STACK_SIZE 64 #define VSTACK_SIZE 256 #define STRING_MAX_SIZE 1024 #define TOKSTR_MAX_SIZE 256 #define PACK_STACK_SIZE 8 #define TOK_HASH_SIZE 16384 /* must be a power of two */ #define TOK_ALLOC_INCR 512 /* must be a power of two */ #define TOK_MAX_SIZE 4 /* token max size in int unit when stored in string */ /* token symbol management */ typedef struct TokenSym { struct TokenSym *hash_next; struct Sym *sym_define; /* direct pointer to define */ struct Sym *sym_label; /* direct pointer to label */ struct Sym *sym_struct; /* direct pointer to structure */ struct Sym *sym_identifier; /* direct pointer to identifier */ int tok; /* token number */ int len; char str[1]; } TokenSym; #ifdef TCC_TARGET_PE typedef unsigned short nwchar_t; #else typedef int nwchar_t; #endif typedef struct CString { int size; /* size in bytes */ void *data; /* either 'char *' or 'nwchar_t *' */ int size_allocated; } CString; /* type definition */ typedef struct CType { int t; struct Sym *ref; } CType; /* constant value */ typedef union CValue { long double ld; double d; float f; uint64_t i; struct { int size; const void *data; } str; int tab[LDOUBLE_SIZE/4]; } CValue; /* value on stack */ typedef struct SValue { CType type; /* type */ unsigned short r; /* register + flags */ unsigned short r2; /* second register, used for 'long long' type. If not used, set to VT_CONST */ CValue c; /* constant, if VT_CONST */ struct Sym *sym; /* symbol, if (VT_SYM | VT_CONST), or if result of unary() for an identifier. */ } SValue; /* symbol attributes */ struct SymAttr { unsigned short aligned : 5, /* alignment as log2+1 (0 == unspecified) */ packed : 1, weak : 1, visibility : 2, dllexport : 1, dllimport : 1, unused : 5; }; /* function attributes or temporary attributes for parsing */ struct FuncAttr { unsigned func_call : 3, /* calling convention (0..5), see below */ func_type : 2, /* FUNC_OLD/NEW/ELLIPSIS */ func_args : 8; /* PE __stdcall args */ }; /* GNUC attribute definition */ typedef struct AttributeDef { struct SymAttr a; struct FuncAttr f; struct Section *section; int alias_target; /* token */ int asm_label; /* associated asm label */ char attr_mode; /* __attribute__((__mode__(...))) */ } AttributeDef; /* symbol management */ typedef struct Sym { int v; /* symbol token */ unsigned short r; /* associated register or VT_CONST/VT_LOCAL and LVAL type */ struct SymAttr a; /* symbol attributes */ union { struct { int c; /* associated number or Elf symbol index */ union { int sym_scope; /* scope level for locals */ int jnext; /* next jump label */ struct FuncAttr f; /* function attributes */ int auxtype; /* bitfield access type */ }; }; long long enum_val; /* enum constant if IS_ENUM_VAL */ int *d; /* define token stream */ }; CType type; /* associated type */ union { struct Sym *next; /* next related symbol (for fields and anoms) */ int asm_label; /* associated asm label */ }; struct Sym *prev; /* prev symbol in stack */ struct Sym *prev_tok; /* previous symbol for this token */ } Sym; /* section definition */ typedef struct Section { unsigned long data_offset; /* current data offset */ unsigned char *data; /* section data */ unsigned long data_allocated; /* used for realloc() handling */ int sh_name; /* elf section name (only used during output) */ int sh_num; /* elf section number */ int sh_type; /* elf section type */ int sh_flags; /* elf section flags */ int sh_info; /* elf section info */ int sh_addralign; /* elf section alignment */ int sh_entsize; /* elf entry size */ unsigned long sh_size; /* section size (only used during output) */ addr_t sh_addr; /* address at which the section is relocated */ unsigned long sh_offset; /* file offset */ int nb_hashed_syms; /* used to resize the hash table */ struct Section *link; /* link to another section */ struct Section *reloc; /* corresponding section for relocation, if any */ struct Section *hash; /* hash table for symbols */ struct Section *prev; /* previous section on section stack */ char name[1]; /* section name */ } Section; typedef struct DLLReference { int level; void *handle; char name[1]; } DLLReference; /* -------------------------------------------------- */ #define SYM_STRUCT 0x40000000 /* struct/union/enum symbol space */ #define SYM_FIELD 0x20000000 /* struct/union field symbol space */ #define SYM_FIRST_ANOM 0x10000000 /* first anonymous sym */ /* stored in 'Sym->f.func_type' field */ #define FUNC_NEW 1 /* ansi function prototype */ #define FUNC_OLD 2 /* old function prototype */ #define FUNC_ELLIPSIS 3 /* ansi function prototype with ... */ /* stored in 'Sym->f.func_call' field */ #define FUNC_CDECL 0 /* standard c call */ #define FUNC_STDCALL 1 /* pascal c call */ #define FUNC_FASTCALL1 2 /* first param in %eax */ #define FUNC_FASTCALL2 3 /* first parameters in %eax, %edx */ #define FUNC_FASTCALL3 4 /* first parameter in %eax, %edx, %ecx */ #define FUNC_FASTCALLW 5 /* first parameter in %ecx, %edx */ /* field 'Sym.t' for macros */ #define MACRO_OBJ 0 /* object like macro */ #define MACRO_FUNC 1 /* function like macro */ /* field 'Sym.r' for C labels */ #define LABEL_DEFINED 0 /* label is defined */ #define LABEL_FORWARD 1 /* label is forward defined */ #define LABEL_DECLARED 2 /* label is declared but never used */ /* type_decl() types */ #define TYPE_ABSTRACT 1 /* type without variable */ #define TYPE_DIRECT 2 /* type with variable */ #define IO_BUF_SIZE 8192 typedef struct BufferedFile { uint8_t *buf_ptr; uint8_t *buf_end; int fd; struct BufferedFile *prev; int line_num; /* current line number - here to simplify code */ int line_ref; /* tcc -E: last printed line */ int ifndef_macro; /* #ifndef macro / #endif search */ int ifndef_macro_saved; /* saved ifndef_macro */ int *ifdef_stack_ptr; /* ifdef_stack value at the start of the file */ int include_next_index; /* next search path */ char filename[1024]; /* filename */ char *true_filename; /* filename not modified by # line directive */ unsigned char unget[4]; unsigned char buffer[1]; /* extra size for CH_EOB char */ } BufferedFile; #define CH_EOB '\\' /* end of buffer or '\0' char in file */ #define CH_EOF (-1) /* end of file */ /* used to record tokens */ typedef struct TokenString { int *str; int len; int lastlen; int allocated_len; int last_line_num; int save_line_num; /* used to chain token-strings with begin/end_macro() */ struct TokenString *prev; const int *prev_ptr; char alloc; } TokenString; /* inline functions */ typedef struct InlineFunc { TokenString *func_str; Sym *sym; char filename[1]; } InlineFunc; /* include file cache, used to find files faster and also to eliminate inclusion if the include file is protected by #ifndef ... #endif */ typedef struct CachedInclude { int ifndef_macro; int once; int hash_next; /* -1 if none */ char filename[1]; /* path specified in #include */ } CachedInclude; #define CACHED_INCLUDES_HASH_SIZE 32 #ifdef CONFIG_TCC_ASM typedef struct ExprValue { uint64_t v; Sym *sym; int pcrel; } ExprValue; #define MAX_ASM_OPERANDS 30 typedef struct ASMOperand { int id; /* GCC 3 optional identifier (0 if number only supported */ char *constraint; char asm_str[16]; /* computed asm string for operand */ SValue *vt; /* C value of the expression */ int ref_index; /* if >= 0, gives reference to a output constraint */ int input_index; /* if >= 0, gives reference to an input constraint */ int priority; /* priority, used to assign registers */ int reg; /* if >= 0, register number used for this operand */ int is_llong; /* true if double register value */ int is_memory; /* true if memory operand */ int is_rw; /* for '+' modifier */ } ASMOperand; #endif /* extra symbol attributes (not in symbol table) */ struct sym_attr { unsigned got_offset; unsigned plt_offset; int plt_sym; int dyn_index; #ifdef TCC_TARGET_ARM unsigned char plt_thumb_stub:1; #endif }; struct TCCState { int verbose; /* if true, display some information during compilation */ int nostdinc; /* if true, no standard headers are added */ int nostdlib; /* if true, no standard libraries are added */ int nocommon; /* if true, do not use common symbols for .bss data */ int static_link; /* if true, static linking is performed */ int rdynamic; /* if true, all symbols are exported */ int symbolic; /* if true, resolve symbols in the current module first */ int alacarte_link; /* if true, only link in referenced objects from archive */ char *tcc_lib_path; /* CONFIG_TCCDIR or -B option */ char *soname; /* as specified on the command line (-soname) */ char *rpath; /* as specified on the command line (-Wl,-rpath=) */ int enable_new_dtags; /* ditto, (-Wl,--enable-new-dtags) */ /* output type, see TCC_OUTPUT_XXX */ int output_type; /* output format, see TCC_OUTPUT_FORMAT_xxx */ int output_format; /* C language options */ int char_is_unsigned; int leading_underscore; int ms_extensions; /* allow nested named struct w/o identifier behave like unnamed */ int dollars_in_identifiers; /* allows '$' char in identifiers */ int ms_bitfields; /* if true, emulate MS algorithm for aligning bitfields */ /* warning switches */ int warn_write_strings; int warn_unsupported; int warn_error; int warn_none; int warn_implicit_function_declaration; int warn_gcc_compat; /* compile with debug symbol (and use them if error during execution) */ int do_debug; #ifdef CONFIG_TCC_BCHECK /* compile with built-in memory and bounds checker */ int do_bounds_check; #endif #ifdef TCC_TARGET_ARM enum float_abi float_abi; /* float ABI of the generated code*/ #endif int run_test; /* nth test to run with -dt -run */ addr_t text_addr; /* address of text section */ int has_text_addr; unsigned section_align; /* section alignment */ char *init_symbol; /* symbols to call at load-time (not used currently) */ char *fini_symbol; /* symbols to call at unload-time (not used currently) */ #ifdef TCC_TARGET_I386 int seg_size; /* 32. Can be 16 with i386 assembler (.code16) */ #endif #ifdef TCC_TARGET_X86_64 int nosse; /* For -mno-sse support. */ #endif /* array of all loaded dlls (including those referenced by loaded dlls) */ DLLReference **loaded_dlls; int nb_loaded_dlls; /* include paths */ char **include_paths; int nb_include_paths; char **sysinclude_paths; int nb_sysinclude_paths; /* library paths */ char **library_paths; int nb_library_paths; /* crt?.o object path */ char **crt_paths; int nb_crt_paths; /* -include files */ char **cmd_include_files; int nb_cmd_include_files; /* error handling */ void *error_opaque; void (*error_func)(void *opaque, const char *msg); int error_set_jmp_enabled; jmp_buf error_jmp_buf; int nb_errors; /* output file for preprocessing (-E) */ FILE *ppfp; enum { LINE_MACRO_OUTPUT_FORMAT_GCC, LINE_MACRO_OUTPUT_FORMAT_NONE, LINE_MACRO_OUTPUT_FORMAT_STD, LINE_MACRO_OUTPUT_FORMAT_P10 = 11 } Pflag; /* -P switch */ char dflag; /* -dX value */ /* for -MD/-MF: collected dependencies for this compilation */ char **target_deps; int nb_target_deps; /* compilation */ BufferedFile *include_stack[INCLUDE_STACK_SIZE]; BufferedFile **include_stack_ptr; int ifdef_stack[IFDEF_STACK_SIZE]; int *ifdef_stack_ptr; /* included files enclosed with #ifndef MACRO */ int cached_includes_hash[CACHED_INCLUDES_HASH_SIZE]; CachedInclude **cached_includes; int nb_cached_includes; /* #pragma pack stack */ int pack_stack[PACK_STACK_SIZE]; int *pack_stack_ptr; char **pragma_libs; int nb_pragma_libs; /* inline functions are stored as token lists and compiled last only if referenced */ struct InlineFunc **inline_fns; int nb_inline_fns; /* sections */ Section **sections; int nb_sections; /* number of sections, including first dummy section */ Section **priv_sections; int nb_priv_sections; /* number of private sections */ /* got & plt handling */ Section *got; Section *plt; /* temporary dynamic symbol sections (for dll loading) */ Section *dynsymtab_section; /* exported dynamic symbol section */ Section *dynsym; /* copy of the global symtab_section variable */ Section *symtab; /* extra attributes (eg. GOT/PLT value) for symtab symbols */ struct sym_attr *sym_attrs; int nb_sym_attrs; #ifdef TCC_TARGET_PE /* PE info */ int pe_subsystem; unsigned pe_characteristics; unsigned pe_file_align; unsigned pe_stack_size; addr_t pe_imagebase; # ifdef TCC_TARGET_X86_64 Section *uw_pdata; int uw_sym; unsigned uw_offs; # endif #endif #ifdef TCC_IS_NATIVE const char *runtime_main; void **runtime_mem; int nb_runtime_mem; #endif /* used by main and tcc_parse_args only */ struct filespec **files; /* files seen on command line */ int nb_files; /* number thereof */ int nb_libraries; /* number of libs thereof */ int filetype; char *outfile; /* output filename */ int option_r; /* option -r */ int do_bench; /* option -bench */ int gen_deps; /* option -MD */ char *deps_outfile; /* option -MF */ int option_pthread; /* -pthread option */ int argc; char **argv; }; struct filespec { char type; char alacarte; char name[1]; }; /* The current value can be: */ #define VT_VALMASK 0x003f /* mask for value location, register or: */ #define VT_CONST 0x0030 /* constant in vc (must be first non register value) */ #define VT_LLOCAL 0x0031 /* lvalue, offset on stack */ #define VT_LOCAL 0x0032 /* offset on stack */ #define VT_CMP 0x0033 /* the value is stored in processor flags (in vc) */ #define VT_JMP 0x0034 /* value is the consequence of jmp true (even) */ #define VT_JMPI 0x0035 /* value is the consequence of jmp false (odd) */ #define VT_LVAL 0x0100 /* var is an lvalue */ #define VT_SYM 0x0200 /* a symbol value is added */ #define VT_MUSTCAST 0x0400 /* value must be casted to be correct (used for char/short stored in integer registers) */ #define VT_MUSTBOUND 0x0800 /* bound checking must be done before dereferencing value */ #define VT_BOUNDED 0x8000 /* value is bounded. The address of the bounding function call point is in vc */ #define VT_LVAL_BYTE 0x1000 /* lvalue is a byte */ #define VT_LVAL_SHORT 0x2000 /* lvalue is a short */ #define VT_LVAL_UNSIGNED 0x4000 /* lvalue is unsigned */ #define VT_LVAL_TYPE (VT_LVAL_BYTE | VT_LVAL_SHORT | VT_LVAL_UNSIGNED) /* types */ #define VT_BTYPE 0x000f /* mask for basic type */ #define VT_VOID 0 /* void type */ #define VT_BYTE 1 /* signed byte type */ #define VT_SHORT 2 /* short type */ #define VT_INT 3 /* integer type */ #define VT_LLONG 4 /* 64 bit integer */ #define VT_PTR 5 /* pointer */ #define VT_FUNC 6 /* function type */ #define VT_STRUCT 7 /* struct/union definition */ #define VT_FLOAT 8 /* IEEE float */ #define VT_DOUBLE 9 /* IEEE double */ #define VT_LDOUBLE 10 /* IEEE long double */ #define VT_BOOL 11 /* ISOC99 boolean type */ #define VT_QLONG 13 /* 128-bit integer. Only used for x86-64 ABI */ #define VT_QFLOAT 14 /* 128-bit float. Only used for x86-64 ABI */ #define VT_UNSIGNED 0x0010 /* unsigned type */ #define VT_DEFSIGN 0x0020 /* explicitly signed or unsigned */ #define VT_ARRAY 0x0040 /* array type (also has VT_PTR) */ #define VT_BITFIELD 0x0080 /* bitfield modifier */ #define VT_CONSTANT 0x0100 /* const modifier */ #define VT_VOLATILE 0x0200 /* volatile modifier */ #define VT_VLA 0x0400 /* VLA type (also has VT_PTR and VT_ARRAY) */ #define VT_LONG 0x0800 /* long type (also has VT_INT rsp. VT_LLONG) */ /* storage */ #define VT_EXTERN 0x00001000 /* extern definition */ #define VT_STATIC 0x00002000 /* static variable */ #define VT_TYPEDEF 0x00004000 /* typedef definition */ #define VT_INLINE 0x00008000 /* inline definition */ /* currently unused: 0x000[1248]0000 */ #define VT_STRUCT_SHIFT 20 /* shift for bitfield shift values (32 - 2*6) */ #define VT_STRUCT_MASK (((1 << (6+6)) - 1) << VT_STRUCT_SHIFT | VT_BITFIELD) #define BIT_POS(t) (((t) >> VT_STRUCT_SHIFT) & 0x3f) #define BIT_SIZE(t) (((t) >> (VT_STRUCT_SHIFT + 6)) & 0x3f) #define VT_UNION (1 << VT_STRUCT_SHIFT | VT_STRUCT) #define VT_ENUM (2 << VT_STRUCT_SHIFT) /* integral type is an enum really */ #define VT_ENUM_VAL (3 << VT_STRUCT_SHIFT) /* integral type is an enum constant really */ #define IS_ENUM(t) ((t & VT_STRUCT_MASK) == VT_ENUM) #define IS_ENUM_VAL(t) ((t & VT_STRUCT_MASK) == VT_ENUM_VAL) #define IS_UNION(t) ((t & (VT_STRUCT_MASK|VT_BTYPE)) == VT_UNION) /* type mask (except storage) */ #define VT_STORAGE (VT_EXTERN | VT_STATIC | VT_TYPEDEF | VT_INLINE) #define VT_TYPE (~(VT_STORAGE|VT_STRUCT_MASK)) /* symbol was created by tccasm.c first */ #define VT_ASM (VT_VOID | VT_UNSIGNED) #define IS_ASM_SYM(sym) (((sym)->type.t & (VT_BTYPE | VT_ASM)) == VT_ASM) /* token values */ /* warning: the following compare tokens depend on i386 asm code */ #define TOK_ULT 0x92 #define TOK_UGE 0x93 #define TOK_EQ 0x94 #define TOK_NE 0x95 #define TOK_ULE 0x96 #define TOK_UGT 0x97 #define TOK_Nset 0x98 #define TOK_Nclear 0x99 #define TOK_LT 0x9c #define TOK_GE 0x9d #define TOK_LE 0x9e #define TOK_GT 0x9f #define TOK_LAND 0xa0 #define TOK_LOR 0xa1 #define TOK_DEC 0xa2 #define TOK_MID 0xa3 /* inc/dec, to void constant */ #define TOK_INC 0xa4 #define TOK_UDIV 0xb0 /* unsigned division */ #define TOK_UMOD 0xb1 /* unsigned modulo */ #define TOK_PDIV 0xb2 /* fast division with undefined rounding for pointers */ /* tokens that carry values (in additional token string space / tokc) --> */ #define TOK_CCHAR 0xb3 /* char constant in tokc */ #define TOK_LCHAR 0xb4 #define TOK_CINT 0xb5 /* number in tokc */ #define TOK_CUINT 0xb6 /* unsigned int constant */ #define TOK_CLLONG 0xb7 /* long long constant */ #define TOK_CULLONG 0xb8 /* unsigned long long constant */ #define TOK_STR 0xb9 /* pointer to string in tokc */ #define TOK_LSTR 0xba #define TOK_CFLOAT 0xbb /* float constant */ #define TOK_CDOUBLE 0xbc /* double constant */ #define TOK_CLDOUBLE 0xbd /* long double constant */ #define TOK_PPNUM 0xbe /* preprocessor number */ #define TOK_PPSTR 0xbf /* preprocessor string */ #define TOK_LINENUM 0xc0 /* line number info */ #define TOK_TWODOTS 0xa8 /* C++ token ? */ /* <-- */ #define TOK_UMULL 0xc2 /* unsigned 32x32 -> 64 mul */ #define TOK_ADDC1 0xc3 /* add with carry generation */ #define TOK_ADDC2 0xc4 /* add with carry use */ #define TOK_SUBC1 0xc5 /* add with carry generation */ #define TOK_SUBC2 0xc6 /* add with carry use */ #define TOK_ARROW 0xc7 #define TOK_DOTS 0xc8 /* three dots */ #define TOK_SHR 0xc9 /* unsigned shift right */ #define TOK_TWOSHARPS 0xca /* ## preprocessing token */ #define TOK_PLCHLDR 0xcb /* placeholder token as defined in C99 */ #define TOK_NOSUBST 0xcc /* means following token has already been pp'd */ #define TOK_PPJOIN 0xcd /* A '##' in the right position to mean pasting */ #define TOK_CLONG 0xce /* long constant */ #define TOK_CULONG 0xcf /* unsigned long constant */ #define TOK_SHL 0x01 /* shift left */ #define TOK_SAR 0x02 /* signed shift right */ /* assignment operators : normal operator or 0x80 */ #define TOK_A_MOD 0xa5 #define TOK_A_AND 0xa6 #define TOK_A_MUL 0xaa #define TOK_A_ADD 0xab #define TOK_A_SUB 0xad #define TOK_A_DIV 0xaf #define TOK_A_XOR 0xde #define TOK_A_OR 0xfc #define TOK_A_SHL 0x81 #define TOK_A_SAR 0x82 #define TOK_EOF (-1) /* end of file */ #define TOK_LINEFEED 10 /* line feed */ /* all identifiers and strings have token above that */ #define TOK_IDENT 256 #define DEF_ASM(x) DEF(TOK_ASM_ ## x, #x) #define TOK_ASM_int TOK_INT #define DEF_ASMDIR(x) DEF(TOK_ASMDIR_ ## x, "." #x) #define TOK_ASMDIR_FIRST TOK_ASMDIR_byte #define TOK_ASMDIR_LAST TOK_ASMDIR_section #if defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64 /* only used for i386 asm opcodes definitions */ #define DEF_BWL(x) \ DEF(TOK_ASM_ ## x ## b, #x "b") \ DEF(TOK_ASM_ ## x ## w, #x "w") \ DEF(TOK_ASM_ ## x ## l, #x "l") \ DEF(TOK_ASM_ ## x, #x) #define DEF_WL(x) \ DEF(TOK_ASM_ ## x ## w, #x "w") \ DEF(TOK_ASM_ ## x ## l, #x "l") \ DEF(TOK_ASM_ ## x, #x) #ifdef TCC_TARGET_X86_64 # define DEF_BWLQ(x) \ DEF(TOK_ASM_ ## x ## b, #x "b") \ DEF(TOK_ASM_ ## x ## w, #x "w") \ DEF(TOK_ASM_ ## x ## l, #x "l") \ DEF(TOK_ASM_ ## x ## q, #x "q") \ DEF(TOK_ASM_ ## x, #x) # define DEF_WLQ(x) \ DEF(TOK_ASM_ ## x ## w, #x "w") \ DEF(TOK_ASM_ ## x ## l, #x "l") \ DEF(TOK_ASM_ ## x ## q, #x "q") \ DEF(TOK_ASM_ ## x, #x) # define DEF_BWLX DEF_BWLQ # define DEF_WLX DEF_WLQ /* number of sizes + 1 */ # define NBWLX 5 #else # define DEF_BWLX DEF_BWL # define DEF_WLX DEF_WL /* number of sizes + 1 */ # define NBWLX 4 #endif #define DEF_FP1(x) \ DEF(TOK_ASM_ ## f ## x ## s, "f" #x "s") \ DEF(TOK_ASM_ ## fi ## x ## l, "fi" #x "l") \ DEF(TOK_ASM_ ## f ## x ## l, "f" #x "l") \ DEF(TOK_ASM_ ## fi ## x ## s, "fi" #x "s") #define DEF_FP(x) \ DEF(TOK_ASM_ ## f ## x, "f" #x ) \ DEF(TOK_ASM_ ## f ## x ## p, "f" #x "p") \ DEF_FP1(x) #define DEF_ASMTEST(x,suffix) \ DEF_ASM(x ## o ## suffix) \ DEF_ASM(x ## no ## suffix) \ DEF_ASM(x ## b ## suffix) \ DEF_ASM(x ## c ## suffix) \ DEF_ASM(x ## nae ## suffix) \ DEF_ASM(x ## nb ## suffix) \ DEF_ASM(x ## nc ## suffix) \ DEF_ASM(x ## ae ## suffix) \ DEF_ASM(x ## e ## suffix) \ DEF_ASM(x ## z ## suffix) \ DEF_ASM(x ## ne ## suffix) \ DEF_ASM(x ## nz ## suffix) \ DEF_ASM(x ## be ## suffix) \ DEF_ASM(x ## na ## suffix) \ DEF_ASM(x ## nbe ## suffix) \ DEF_ASM(x ## a ## suffix) \ DEF_ASM(x ## s ## suffix) \ DEF_ASM(x ## ns ## suffix) \ DEF_ASM(x ## p ## suffix) \ DEF_ASM(x ## pe ## suffix) \ DEF_ASM(x ## np ## suffix) \ DEF_ASM(x ## po ## suffix) \ DEF_ASM(x ## l ## suffix) \ DEF_ASM(x ## nge ## suffix) \ DEF_ASM(x ## nl ## suffix) \ DEF_ASM(x ## ge ## suffix) \ DEF_ASM(x ## le ## suffix) \ DEF_ASM(x ## ng ## suffix) \ DEF_ASM(x ## nle ## suffix) \ DEF_ASM(x ## g ## suffix) #endif /* defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64 */ enum tcc_token { TOK_LAST = TOK_IDENT - 1 #define DEF(id, str) ,id #include "tcctok.h" #undef DEF }; /* keywords: tok >= TOK_IDENT && tok < TOK_UIDENT */ #define TOK_UIDENT TOK_DEFINE /* ------------ libtcc.c ------------ */ /* use GNU C extensions */ ST_DATA int gnu_ext; /* use Tiny C extensions */ ST_DATA int tcc_ext; /* XXX: get rid of this ASAP */ ST_DATA struct TCCState *tcc_state; /* public functions currently used by the tcc main function */ ST_FUNC char *pstrcpy(char *buf, int buf_size, const char *s); ST_FUNC char *pstrcat(char *buf, int buf_size, const char *s); ST_FUNC char *pstrncpy(char *out, const char *in, size_t num); PUB_FUNC char *tcc_basename(const char *name); PUB_FUNC char *tcc_fileextension (const char *name); #ifndef MEM_DEBUG PUB_FUNC void tcc_free(void *ptr); PUB_FUNC void *tcc_malloc(unsigned long size); PUB_FUNC void *tcc_mallocz(unsigned long size); PUB_FUNC void *tcc_realloc(void *ptr, unsigned long size); PUB_FUNC char *tcc_strdup(const char *str); #else #define tcc_free(ptr) tcc_free_debug(ptr) #define tcc_malloc(size) tcc_malloc_debug(size, __FILE__, __LINE__) #define tcc_mallocz(size) tcc_mallocz_debug(size, __FILE__, __LINE__) #define tcc_realloc(ptr,size) tcc_realloc_debug(ptr, size, __FILE__, __LINE__) #define tcc_strdup(str) tcc_strdup_debug(str, __FILE__, __LINE__) PUB_FUNC void tcc_free_debug(void *ptr); PUB_FUNC void *tcc_malloc_debug(unsigned long size, const char *file, int line); PUB_FUNC void *tcc_mallocz_debug(unsigned long size, const char *file, int line); PUB_FUNC void *tcc_realloc_debug(void *ptr, unsigned long size, const char *file, int line); PUB_FUNC char *tcc_strdup_debug(const char *str, const char *file, int line); #endif #define free(p) use_tcc_free(p) #define malloc(s) use_tcc_malloc(s) #define realloc(p, s) use_tcc_realloc(p, s) #undef strdup #define strdup(s) use_tcc_strdup(s) PUB_FUNC void tcc_memcheck(void); PUB_FUNC void tcc_error_noabort(const char *fmt, ...); PUB_FUNC NORETURN void tcc_error(const char *fmt, ...); PUB_FUNC void tcc_warning(const char *fmt, ...); /* other utilities */ ST_FUNC void dynarray_add(void *ptab, int *nb_ptr, void *data); ST_FUNC void dynarray_reset(void *pp, int *n); ST_INLN void cstr_ccat(CString *cstr, int ch); ST_FUNC void cstr_cat(CString *cstr, const char *str, int len); ST_FUNC void cstr_wccat(CString *cstr, int ch); ST_FUNC void cstr_new(CString *cstr); ST_FUNC void cstr_free(CString *cstr); ST_FUNC void cstr_reset(CString *cstr); ST_INLN void sym_free(Sym *sym); ST_FUNC Sym *sym_push2(Sym **ps, int v, int t, int c); ST_FUNC Sym *sym_find2(Sym *s, int v); ST_FUNC Sym *sym_push(int v, CType *type, int r, int c); ST_FUNC void sym_pop(Sym **ptop, Sym *b, int keep); ST_INLN Sym *struct_find(int v); ST_INLN Sym *sym_find(int v); ST_FUNC Sym *global_identifier_push(int v, int t, int c); ST_FUNC void tcc_open_bf(TCCState *s1, const char *filename, int initlen); ST_FUNC int tcc_open(TCCState *s1, const char *filename); ST_FUNC void tcc_close(void); ST_FUNC int tcc_add_file_internal(TCCState *s1, const char *filename, int flags); /* flags: */ #define AFF_PRINT_ERROR 0x10 /* print error if file not found */ #define AFF_REFERENCED_DLL 0x20 /* load a referenced dll from another dll */ #define AFF_TYPE_BIN 0x40 /* file to add is binary */ /* s->filetype: */ #define AFF_TYPE_NONE 0 #define AFF_TYPE_C 1 #define AFF_TYPE_ASM 2 #define AFF_TYPE_ASMPP 3 #define AFF_TYPE_LIB 4 /* values from tcc_object_type(...) */ #define AFF_BINTYPE_REL 1 #define AFF_BINTYPE_DYN 2 #define AFF_BINTYPE_AR 3 #define AFF_BINTYPE_C67 4 ST_FUNC int tcc_add_crt(TCCState *s, const char *filename); ST_FUNC int tcc_add_dll(TCCState *s, const char *filename, int flags); ST_FUNC void tcc_add_pragma_libs(TCCState *s1); PUB_FUNC int tcc_add_library_err(TCCState *s, const char *f); PUB_FUNC void tcc_print_stats(TCCState *s, unsigned total_time); PUB_FUNC int tcc_parse_args(TCCState *s, int *argc, char ***argv, int optind); #ifdef _WIN32 ST_FUNC char *normalize_slashes(char *path); #endif /* tcc_parse_args return codes: */ #define OPT_HELP 1 #define OPT_HELP2 2 #define OPT_V 3 #define OPT_PRINT_DIRS 4 #define OPT_AR 5 #define OPT_IMPDEF 6 #define OPT_M32 32 #define OPT_M64 64 /* ------------ tccpp.c ------------ */ ST_DATA struct BufferedFile *file; ST_DATA int ch, tok; ST_DATA CValue tokc; ST_DATA const int *macro_ptr; ST_DATA int parse_flags; ST_DATA int tok_flags; ST_DATA CString tokcstr; /* current parsed string, if any */ /* display benchmark infos */ ST_DATA int total_lines; ST_DATA int total_bytes; ST_DATA int tok_ident; ST_DATA TokenSym **table_ident; #define TOK_FLAG_BOL 0x0001 /* beginning of line before */ #define TOK_FLAG_BOF 0x0002 /* beginning of file before */ #define TOK_FLAG_ENDIF 0x0004 /* a endif was found matching starting #ifdef */ #define TOK_FLAG_EOF 0x0008 /* end of file */ #define PARSE_FLAG_PREPROCESS 0x0001 /* activate preprocessing */ #define PARSE_FLAG_TOK_NUM 0x0002 /* return numbers instead of TOK_PPNUM */ #define PARSE_FLAG_LINEFEED 0x0004 /* line feed is returned as a token. line feed is also returned at eof */ #define PARSE_FLAG_ASM_FILE 0x0008 /* we processing an asm file: '#' can be used for line comment, etc. */ #define PARSE_FLAG_SPACES 0x0010 /* next() returns space tokens (for -E) */ #define PARSE_FLAG_ACCEPT_STRAYS 0x0020 /* next() returns '\\' token */ #define PARSE_FLAG_TOK_STR 0x0040 /* return parsed strings instead of TOK_PPSTR */ /* isidnum_table flags: */ #define IS_SPC 1 #define IS_ID 2 #define IS_NUM 4 ST_FUNC TokenSym *tok_alloc(const char *str, int len); ST_FUNC const char *get_tok_str(int v, CValue *cv); ST_FUNC void begin_macro(TokenString *str, int alloc); ST_FUNC void end_macro(void); ST_FUNC int set_idnum(int c, int val); ST_INLN void tok_str_new(TokenString *s); ST_FUNC TokenString *tok_str_alloc(void); ST_FUNC void tok_str_free(TokenString *s); ST_FUNC void tok_str_free_str(int *str); ST_FUNC void tok_str_add(TokenString *s, int t); ST_FUNC void tok_str_add_tok(TokenString *s); ST_INLN void define_push(int v, int macro_type, int *str, Sym *first_arg); ST_FUNC void define_undef(Sym *s); ST_INLN Sym *define_find(int v); ST_FUNC void free_defines(Sym *b); ST_FUNC Sym *label_find(int v); ST_FUNC Sym *label_push(Sym **ptop, int v, int flags); ST_FUNC void label_pop(Sym **ptop, Sym *slast, int keep); ST_FUNC void parse_define(void); ST_FUNC void preprocess(int is_bof); ST_FUNC void next_nomacro(void); ST_FUNC void next(void); ST_INLN void unget_tok(int last_tok); ST_FUNC void preprocess_start(TCCState *s1, int is_asm); ST_FUNC void preprocess_end(TCCState *s1); ST_FUNC void tccpp_new(TCCState *s); ST_FUNC void tccpp_delete(TCCState *s); ST_FUNC int tcc_preprocess(TCCState *s1); ST_FUNC void skip(int c); ST_FUNC NORETURN void expect(const char *msg); /* space excluding newline */ static inline int is_space(int ch) { return ch == ' ' || ch == '\t' || ch == '\v' || ch == '\f' || ch == '\r'; } static inline int isid(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; } static inline int isnum(int c) { return c >= '0' && c <= '9'; } static inline int isoct(int c) { return c >= '0' && c <= '7'; } static inline int toup(int c) { return (c >= 'a' && c <= 'z') ? c - 'a' + 'A' : c; } /* ------------ tccgen.c ------------ */ #define SYM_POOL_NB (8192 / sizeof(Sym)) ST_DATA Sym *sym_free_first; ST_DATA void **sym_pools; ST_DATA int nb_sym_pools; ST_DATA Sym *global_stack; ST_DATA Sym *local_stack; ST_DATA Sym *local_label_stack; ST_DATA Sym *global_label_stack; ST_DATA Sym *define_stack; ST_DATA CType char_pointer_type, func_old_type, int_type, size_type; ST_DATA SValue __vstack[1+/*to make bcheck happy*/ VSTACK_SIZE], *vtop, *pvtop; #define vstack (__vstack + 1) ST_DATA int rsym, anon_sym, ind, loc; ST_DATA int const_wanted; /* true if constant wanted */ ST_DATA int nocode_wanted; /* true if no code generation wanted for an expression */ ST_DATA int global_expr; /* true if compound literals must be allocated globally (used during initializers parsing */ ST_DATA CType func_vt; /* current function return type (used by return instruction) */ ST_DATA int func_var; /* true if current function is variadic */ ST_DATA int func_vc; ST_DATA int last_line_num, last_ind, func_ind; /* debug last line number and pc */ ST_DATA const char *funcname; ST_DATA int g_debug; ST_FUNC void tcc_debug_start(TCCState *s1); ST_FUNC void tcc_debug_end(TCCState *s1); ST_FUNC void tcc_debug_funcstart(TCCState *s1, Sym *sym); ST_FUNC void tcc_debug_funcend(TCCState *s1, int size); ST_FUNC void tcc_debug_line(TCCState *s1); ST_FUNC int tccgen_compile(TCCState *s1); ST_FUNC void free_inline_functions(TCCState *s); ST_FUNC void check_vstack(void); ST_INLN int is_float(int t); ST_FUNC int ieee_finite(double d); ST_FUNC void test_lvalue(void); ST_FUNC void vpushi(int v); ST_FUNC ElfSym *elfsym(Sym *); ST_FUNC void update_storage(Sym *sym); ST_FUNC Sym *external_global_sym(int v, CType *type, int r); ST_FUNC void vset(CType *type, int r, int v); ST_FUNC void vswap(void); ST_FUNC void vpush_global_sym(CType *type, int v); ST_FUNC void vrote(SValue *e, int n); ST_FUNC void vrott(int n); ST_FUNC void vrotb(int n); #ifdef TCC_TARGET_ARM ST_FUNC int get_reg_ex(int rc, int rc2); ST_FUNC void lexpand_nr(void); #endif ST_FUNC void vpushv(SValue *v); ST_FUNC void save_reg(int r); ST_FUNC void save_reg_upstack(int r, int n); ST_FUNC int get_reg(int rc); ST_FUNC void save_regs(int n); ST_FUNC void gaddrof(void); ST_FUNC int gv(int rc); ST_FUNC void gv2(int rc1, int rc2); ST_FUNC void vpop(void); ST_FUNC void gen_op(int op); ST_FUNC int type_size(CType *type, int *a); ST_FUNC void mk_pointer(CType *type); ST_FUNC void vstore(void); ST_FUNC void inc(int post, int c); ST_FUNC void parse_mult_str (CString *astr, const char *msg); ST_FUNC void parse_asm_str(CString *astr); ST_FUNC int lvalue_type(int t); ST_FUNC void indir(void); ST_FUNC void unary(void); ST_FUNC void expr_prod(void); ST_FUNC void expr_sum(void); ST_FUNC void gexpr(void); ST_FUNC int expr_const(void); #if defined CONFIG_TCC_BCHECK || defined TCC_TARGET_C67 ST_FUNC Sym *get_sym_ref(CType *type, Section *sec, unsigned long offset, unsigned long size); #endif #if defined TCC_TARGET_X86_64 && !defined TCC_TARGET_PE ST_FUNC int classify_x86_64_va_arg(CType *ty); #endif /* ------------ tccelf.c ------------ */ #define TCC_OUTPUT_FORMAT_ELF 0 /* default output format: ELF */ #define TCC_OUTPUT_FORMAT_BINARY 1 /* binary image output */ #define TCC_OUTPUT_FORMAT_COFF 2 /* COFF */ #define ARMAG "!<arch>\012" /* For COFF and a.out archives */ typedef struct { unsigned int n_strx; /* index into string table of name */ unsigned char n_type; /* type of symbol */ unsigned char n_other; /* misc info (usually empty) */ unsigned short n_desc; /* description field */ unsigned int n_value; /* value of symbol */ } Stab_Sym; ST_DATA Section *text_section, *data_section, *bss_section; /* predefined sections */ ST_DATA Section *common_section; ST_DATA Section *cur_text_section; /* current section where function code is generated */ #ifdef CONFIG_TCC_ASM ST_DATA Section *last_text_section; /* to handle .previous asm directive */ #endif #ifdef CONFIG_TCC_BCHECK /* bound check related sections */ ST_DATA Section *bounds_section; /* contains global data bound description */ ST_DATA Section *lbounds_section; /* contains local data bound description */ ST_FUNC void tccelf_bounds_new(TCCState *s); #endif /* symbol sections */ ST_DATA Section *symtab_section; /* debug sections */ ST_DATA Section *stab_section, *stabstr_section; ST_FUNC void tccelf_new(TCCState *s); ST_FUNC void tccelf_delete(TCCState *s); ST_FUNC void tccelf_stab_new(TCCState *s); ST_FUNC void tccelf_begin_file(TCCState *s1); ST_FUNC void tccelf_end_file(TCCState *s1); ST_FUNC Section *new_section(TCCState *s1, const char *name, int sh_type, int sh_flags); ST_FUNC void section_realloc(Section *sec, unsigned long new_size); ST_FUNC size_t section_add(Section *sec, addr_t size, int align); ST_FUNC void *section_ptr_add(Section *sec, addr_t size); ST_FUNC void section_reserve(Section *sec, unsigned long size); ST_FUNC Section *find_section(TCCState *s1, const char *name); ST_FUNC Section *new_symtab(TCCState *s1, const char *symtab_name, int sh_type, int sh_flags, const char *strtab_name, const char *hash_name, int hash_sh_flags); ST_FUNC void put_extern_sym2(Sym *sym, int sh_num, addr_t value, unsigned long size, int can_add_underscore); ST_FUNC void put_extern_sym(Sym *sym, Section *section, addr_t value, unsigned long size); #if PTR_SIZE == 4 ST_FUNC void greloc(Section *s, Sym *sym, unsigned long offset, int type); #endif ST_FUNC void greloca(Section *s, Sym *sym, unsigned long offset, int type, addr_t addend); ST_FUNC int put_elf_str(Section *s, const char *sym); ST_FUNC int put_elf_sym(Section *s, addr_t value, unsigned long size, int info, int other, int shndx, const char *name); ST_FUNC int set_elf_sym(Section *s, addr_t value, unsigned long size, int info, int other, int shndx, const char *name); ST_FUNC int find_elf_sym(Section *s, const char *name); ST_FUNC void put_elf_reloc(Section *symtab, Section *s, unsigned long offset, int type, int symbol); ST_FUNC void put_elf_reloca(Section *symtab, Section *s, unsigned long offset, int type, int symbol, addr_t addend); ST_FUNC void put_stabs(const char *str, int type, int other, int desc, unsigned long value); ST_FUNC void put_stabs_r(const char *str, int type, int other, int desc, unsigned long value, Section *sec, int sym_index); ST_FUNC void put_stabn(int type, int other, int desc, int value); ST_FUNC void put_stabd(int type, int other, int desc); ST_FUNC void resolve_common_syms(TCCState *s1); ST_FUNC void relocate_syms(TCCState *s1, Section *symtab, int do_resolve); ST_FUNC void relocate_section(TCCState *s1, Section *s); ST_FUNC int tcc_object_type(int fd, ElfW(Ehdr) *h); ST_FUNC int tcc_load_object_file(TCCState *s1, int fd, unsigned long file_offset); ST_FUNC int tcc_load_archive(TCCState *s1, int fd); ST_FUNC void tcc_add_bcheck(TCCState *s1); ST_FUNC void tcc_add_runtime(TCCState *s1); ST_FUNC void build_got_entries(TCCState *s1); ST_FUNC struct sym_attr *get_sym_attr(TCCState *s1, int index, int alloc); ST_FUNC void squeeze_multi_relocs(Section *sec, size_t oldrelocoffset); ST_FUNC addr_t get_elf_sym_addr(TCCState *s, const char *name, int err); #if defined TCC_IS_NATIVE || defined TCC_TARGET_PE ST_FUNC void *tcc_get_symbol_err(TCCState *s, const char *name); #endif #ifndef TCC_TARGET_PE ST_FUNC int tcc_load_dll(TCCState *s1, int fd, const char *filename, int level); ST_FUNC int tcc_load_ldscript(TCCState *s1); ST_FUNC uint8_t *parse_comment(uint8_t *p); ST_FUNC void minp(void); ST_INLN void inp(void); ST_FUNC int handle_eob(void); #endif /* ------------ xxx-link.c ------------ */ /* Whether to generate a GOT/PLT entry and when. NO_GOTPLT_ENTRY is first so that unknown relocation don't create a GOT or PLT entry */ enum gotplt_entry { NO_GOTPLT_ENTRY, /* never generate (eg. GLOB_DAT & JMP_SLOT relocs) */ BUILD_GOT_ONLY, /* only build GOT (eg. TPOFF relocs) */ AUTO_GOTPLT_ENTRY, /* generate if sym is UNDEF */ ALWAYS_GOTPLT_ENTRY /* always generate (eg. PLTOFF relocs) */ }; ST_FUNC int code_reloc (int reloc_type); ST_FUNC int gotplt_entry_type (int reloc_type); ST_FUNC unsigned create_plt_entry(TCCState *s1, unsigned got_offset, struct sym_attr *attr); ST_FUNC void relocate_init(Section *sr); ST_FUNC void relocate(TCCState *s1, ElfW_Rel *rel, int type, unsigned char *ptr, addr_t addr, addr_t val); ST_FUNC void relocate_plt(TCCState *s1); /* ------------ xxx-gen.c ------------ */ ST_DATA const int reg_classes[NB_REGS]; ST_FUNC void gsym_addr(int t, int a); ST_FUNC void gsym(int t); ST_FUNC void load(int r, SValue *sv); ST_FUNC void store(int r, SValue *v); ST_FUNC int gfunc_sret(CType *vt, int variadic, CType *ret, int *align, int *regsize); ST_FUNC void gfunc_call(int nb_args); ST_FUNC void gfunc_prolog(CType *func_type); ST_FUNC void gfunc_epilog(void); ST_FUNC int gjmp(int t); ST_FUNC void gjmp_addr(int a); ST_FUNC int gtst(int inv, int t); #if defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64 ST_FUNC void gtst_addr(int inv, int a); #else #define gtst_addr(inv, a) gsym_addr(gtst(inv, 0), a) #endif ST_FUNC void gen_opi(int op); ST_FUNC void gen_opf(int op); ST_FUNC void gen_cvt_ftoi(int t); ST_FUNC void gen_cvt_ftof(int t); ST_FUNC void ggoto(void); #ifndef TCC_TARGET_C67 ST_FUNC void o(unsigned int c); #endif #ifndef TCC_TARGET_ARM ST_FUNC void gen_cvt_itof(int t); #endif ST_FUNC void gen_vla_sp_save(int addr); ST_FUNC void gen_vla_sp_restore(int addr); ST_FUNC void gen_vla_alloc(CType *type, int align); static inline uint16_t read16le(unsigned char *p) { return p[0] | (uint16_t)p[1] << 8; } static inline void write16le(unsigned char *p, uint16_t x) { p[0] = x & 255; p[1] = x >> 8 & 255; } static inline uint32_t read32le(unsigned char *p) { return read16le(p) | (uint32_t)read16le(p + 2) << 16; } static inline void write32le(unsigned char *p, uint32_t x) { write16le(p, x); write16le(p + 2, x >> 16); } static inline void add32le(unsigned char *p, int32_t x) { write32le(p, read32le(p) + x); } static inline uint64_t read64le(unsigned char *p) { return read32le(p) | (uint64_t)read32le(p + 4) << 32; } static inline void write64le(unsigned char *p, uint64_t x) { write32le(p, x); write32le(p + 4, x >> 32); } static inline void add64le(unsigned char *p, int64_t x) { write64le(p, read64le(p) + x); } /* ------------ i386-gen.c ------------ */ #if defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64 ST_FUNC void g(int c); ST_FUNC void gen_le16(int c); ST_FUNC void gen_le32(int c); ST_FUNC void gen_addr32(int r, Sym *sym, int c); ST_FUNC void gen_addrpc32(int r, Sym *sym, int c); #endif #ifdef CONFIG_TCC_BCHECK ST_FUNC void gen_bounded_ptr_add(void); ST_FUNC void gen_bounded_ptr_deref(void); #endif /* ------------ x86_64-gen.c ------------ */ #ifdef TCC_TARGET_X86_64 ST_FUNC void gen_addr64(int r, Sym *sym, int64_t c); ST_FUNC void gen_opl(int op); #ifdef TCC_TARGET_PE ST_FUNC void gen_vla_result(int addr); #endif #endif /* ------------ arm-gen.c ------------ */ #ifdef TCC_TARGET_ARM #if defined(TCC_ARM_EABI) && !defined(CONFIG_TCC_ELFINTERP) PUB_FUNC const char *default_elfinterp(struct TCCState *s); #endif ST_FUNC void arm_init(struct TCCState *s); ST_FUNC void gen_cvt_itof1(int t); #endif /* ------------ arm64-gen.c ------------ */ #ifdef TCC_TARGET_ARM64 ST_FUNC void gen_cvt_sxtw(void); ST_FUNC void gen_opl(int op); ST_FUNC void gfunc_return(CType *func_type); ST_FUNC void gen_va_start(void); ST_FUNC void gen_va_arg(CType *t); ST_FUNC void gen_clear_cache(void); #endif /* ------------ c67-gen.c ------------ */ #ifdef TCC_TARGET_C67 #endif /* ------------ tcccoff.c ------------ */ #ifdef TCC_TARGET_COFF ST_FUNC int tcc_output_coff(TCCState *s1, FILE *f); ST_FUNC int tcc_load_coff(TCCState * s1, int fd); #endif /* ------------ tccasm.c ------------ */ ST_FUNC void asm_instr(void); ST_FUNC void asm_global_instr(void); #ifdef CONFIG_TCC_ASM ST_FUNC int find_constraint(ASMOperand *operands, int nb_operands, const char *name, const char **pp); ST_FUNC Sym* get_asm_sym(int name, Sym *csym); ST_FUNC void asm_expr(TCCState *s1, ExprValue *pe); ST_FUNC int asm_int_expr(TCCState *s1); ST_FUNC int tcc_assemble(TCCState *s1, int do_preprocess); /* ------------ i386-asm.c ------------ */ ST_FUNC void gen_expr32(ExprValue *pe); #ifdef TCC_TARGET_X86_64 ST_FUNC void gen_expr64(ExprValue *pe); #endif ST_FUNC void asm_opcode(TCCState *s1, int opcode); ST_FUNC int asm_parse_regvar(int t); ST_FUNC void asm_compute_constraints(ASMOperand *operands, int nb_operands, int nb_outputs, const uint8_t *clobber_regs, int *pout_reg); ST_FUNC void subst_asm_operand(CString *add_str, SValue *sv, int modifier); ST_FUNC void asm_gen_code(ASMOperand *operands, int nb_operands, int nb_outputs, int is_output, uint8_t *clobber_regs, int out_reg); ST_FUNC void asm_clobber(uint8_t *clobber_regs, const char *str); #endif /* ------------ tccpe.c -------------- */ #ifdef TCC_TARGET_PE ST_FUNC int pe_load_file(struct TCCState *s1, const char *filename, int fd); ST_FUNC int pe_output_file(TCCState * s1, const char *filename); ST_FUNC int pe_putimport(TCCState *s1, int dllindex, const char *name, addr_t value); #if defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64 ST_FUNC SValue *pe_getimport(SValue *sv, SValue *v2); #endif #ifdef TCC_TARGET_X86_64 ST_FUNC void pe_add_unwind_data(unsigned start, unsigned end, unsigned stack); #endif PUB_FUNC int tcc_get_dllexports(const char *filename, char **pp); /* symbol properties stored in Elf32_Sym->st_other */ # define ST_PE_EXPORT 0x10 # define ST_PE_IMPORT 0x20 # define ST_PE_STDCALL 0x40 #endif #define ST_ASM_SET 0x04 /* ------------ tccrun.c ----------------- */ #ifdef TCC_IS_NATIVE #ifdef CONFIG_TCC_STATIC #define RTLD_LAZY 0x001 #define RTLD_NOW 0x002 #define RTLD_GLOBAL 0x100 #define RTLD_DEFAULT NULL /* dummy function for profiling */ ST_FUNC void *dlopen(const char *filename, int flag); ST_FUNC void dlclose(void *p); ST_FUNC const char *dlerror(void); ST_FUNC void *dlsym(void *handle, const char *symbol); #endif #ifdef CONFIG_TCC_BACKTRACE ST_DATA int rt_num_callers; ST_DATA const char **rt_bound_error_msg; ST_DATA void *rt_prog_main; ST_FUNC void tcc_set_num_callers(int n); #endif ST_FUNC void tcc_run_free(TCCState *s1); #endif /* ------------ tcctools.c ----------------- */ #if 0 /* included in tcc.c */ ST_FUNC int tcc_tool_ar(TCCState *s, int argc, char **argv); #ifdef TCC_TARGET_PE ST_FUNC int tcc_tool_impdef(TCCState *s, int argc, char **argv); #endif ST_FUNC void tcc_tool_cross(TCCState *s, char **argv, int option); ST_FUNC void gen_makedeps(TCCState *s, const char *target, const char *filename); #endif /********************************************************/ #undef ST_DATA #if ONE_SOURCE #define ST_DATA static #else #define ST_DATA #endif /********************************************************/ #endif /* _TCC_H */
/* * TCC - Tiny C Compiler * * Copyright (c) 2001-2004 Fabrice Bellard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "tcc.h" /********************************************************/ /* global variables */ /* use GNU C extensions */ ST_DATA int gnu_ext = 1; /* use TinyCC extensions */ ST_DATA int tcc_ext = 1; /* XXX: get rid of this ASAP */ ST_DATA struct TCCState *tcc_state; static int nb_states; /********************************************************/ #if ONE_SOURCE #include "tccpp.c" #include "tccgen.c" #include "tccelf.c" #include "tccrun.c" #ifdef TCC_TARGET_I386 #include "i386-gen.c" #include "i386-link.c" #include "i386-asm.c" #endif #ifdef TCC_TARGET_ARM #include "arm-gen.c" #include "arm-link.c" #include "arm-asm.c" #endif #ifdef TCC_TARGET_ARM64 #include "arm64-gen.c" #include "arm64-link.c" #endif #ifdef TCC_TARGET_C67 #include "c67-gen.c" #include "c67-link.c" #include "tcccoff.c" #endif #ifdef TCC_TARGET_X86_64 #include "x86_64-gen.c" #include "x86_64-link.c" #include "i386-asm.c" #endif #ifdef CONFIG_TCC_ASM #include "tccasm.c" #endif #ifdef TCC_TARGET_PE #include "tccpe.c" #endif #endif /* ONE_SOURCE */ /********************************************************/ #ifndef CONFIG_TCC_ASM ST_FUNC void asm_instr(void) { tcc_error("inline asm() not supported"); } ST_FUNC void asm_global_instr(void) { tcc_error("inline asm() not supported"); } #endif /********************************************************/ #ifdef _WIN32 ST_FUNC char *normalize_slashes(char *path) { char *p; for (p = path; *p; ++p) if (*p == '\\') *p = '/'; return path; } static HMODULE tcc_module; /* on win32, we suppose the lib and includes are at the location of 'tcc.exe' */ static void tcc_set_lib_path_w32(TCCState *s) { char path[1024], *p; GetModuleFileNameA(tcc_module, path, sizeof path); p = tcc_basename(normalize_slashes(strlwr(path))); if (p > path) --p; *p = 0; tcc_set_lib_path(s, path); } #ifdef TCC_TARGET_PE static void tcc_add_systemdir(TCCState *s) { char buf[1000]; GetSystemDirectory(buf, sizeof buf); tcc_add_library_path(s, normalize_slashes(buf)); } #endif #ifdef LIBTCC_AS_DLL BOOL WINAPI DllMain (HINSTANCE hDll, DWORD dwReason, LPVOID lpReserved) { if (DLL_PROCESS_ATTACH == dwReason) tcc_module = hDll; return TRUE; } #endif #endif /********************************************************/ /* copy a string and truncate it. */ ST_FUNC char *pstrcpy(char *buf, int buf_size, const char *s) { char *q, *q_end; int c; if (buf_size > 0) { q = buf; q_end = buf + buf_size - 1; while (q < q_end) { c = *s++; if (c == '\0') break; *q++ = c; } *q = '\0'; } return buf; } /* strcat and truncate. */ ST_FUNC char *pstrcat(char *buf, int buf_size, const char *s) { int len; len = strlen(buf); if (len < buf_size) pstrcpy(buf + len, buf_size - len, s); return buf; } ST_FUNC char *pstrncpy(char *out, const char *in, size_t num) { memcpy(out, in, num); out[num] = '\0'; return out; } /* extract the basename of a file */ PUB_FUNC char *tcc_basename(const char *name) { char *p = strchr(name, 0); while (p > name && !IS_DIRSEP(p[-1])) --p; return p; } /* extract extension part of a file * * (if no extension, return pointer to end-of-string) */ PUB_FUNC char *tcc_fileextension (const char *name) { char *b = tcc_basename(name); char *e = strrchr(b, '.'); return e ? e : strchr(b, 0); } /********************************************************/ /* memory management */ #undef free #undef malloc #undef realloc #ifndef MEM_DEBUG PUB_FUNC void tcc_free(void *ptr) { free(ptr); } PUB_FUNC void *tcc_malloc(unsigned long size) { void *ptr; ptr = malloc(size); if (!ptr && size) tcc_error("memory full (malloc)"); return ptr; } PUB_FUNC void *tcc_mallocz(unsigned long size) { void *ptr; ptr = tcc_malloc(size); memset(ptr, 0, size); return ptr; } PUB_FUNC void *tcc_realloc(void *ptr, unsigned long size) { void *ptr1; ptr1 = realloc(ptr, size); if (!ptr1 && size) tcc_error("memory full (realloc)"); return ptr1; } PUB_FUNC char *tcc_strdup(const char *str) { char *ptr; ptr = tcc_malloc(strlen(str) + 1); strcpy(ptr, str); return ptr; } PUB_FUNC void tcc_memcheck(void) { } #else #define MEM_DEBUG_MAGIC1 0xFEEDDEB1 #define MEM_DEBUG_MAGIC2 0xFEEDDEB2 #define MEM_DEBUG_MAGIC3 0xFEEDDEB3 #define MEM_DEBUG_FILE_LEN 40 #define MEM_DEBUG_CHECK3(header) \ ((mem_debug_header_t*)((char*)header + header->size))->magic3 #define MEM_USER_PTR(header) \ ((char *)header + offsetof(mem_debug_header_t, magic3)) #define MEM_HEADER_PTR(ptr) \ (mem_debug_header_t *)((char*)ptr - offsetof(mem_debug_header_t, magic3)) struct mem_debug_header { unsigned magic1; unsigned size; struct mem_debug_header *prev; struct mem_debug_header *next; int line_num; char file_name[MEM_DEBUG_FILE_LEN + 1]; unsigned magic2; ALIGNED(16) unsigned magic3; }; typedef struct mem_debug_header mem_debug_header_t; static mem_debug_header_t *mem_debug_chain; static unsigned mem_cur_size; static unsigned mem_max_size; static mem_debug_header_t *malloc_check(void *ptr, const char *msg) { mem_debug_header_t * header = MEM_HEADER_PTR(ptr); if (header->magic1 != MEM_DEBUG_MAGIC1 || header->magic2 != MEM_DEBUG_MAGIC2 || MEM_DEBUG_CHECK3(header) != MEM_DEBUG_MAGIC3 || header->size == (unsigned)-1) { fprintf(stderr, "%s check failed\n", msg); if (header->magic1 == MEM_DEBUG_MAGIC1) fprintf(stderr, "%s:%u: block allocated here.\n", header->file_name, header->line_num); exit(1); } return header; } PUB_FUNC void *tcc_malloc_debug(unsigned long size, const char *file, int line) { int ofs; mem_debug_header_t *header; header = malloc(sizeof(mem_debug_header_t) + size); if (!header) tcc_error("memory full (malloc)"); header->magic1 = MEM_DEBUG_MAGIC1; header->magic2 = MEM_DEBUG_MAGIC2; header->size = size; MEM_DEBUG_CHECK3(header) = MEM_DEBUG_MAGIC3; header->line_num = line; ofs = strlen(file) - MEM_DEBUG_FILE_LEN; strncpy(header->file_name, file + (ofs > 0 ? ofs : 0), MEM_DEBUG_FILE_LEN); header->file_name[MEM_DEBUG_FILE_LEN] = 0; header->next = mem_debug_chain; header->prev = NULL; if (header->next) header->next->prev = header; mem_debug_chain = header; mem_cur_size += size; if (mem_cur_size > mem_max_size) mem_max_size = mem_cur_size; return MEM_USER_PTR(header); } PUB_FUNC void tcc_free_debug(void *ptr) { mem_debug_header_t *header; if (!ptr) return; header = malloc_check(ptr, "tcc_free"); mem_cur_size -= header->size; header->size = (unsigned)-1; if (header->next) header->next->prev = header->prev; if (header->prev) header->prev->next = header->next; if (header == mem_debug_chain) mem_debug_chain = header->next; free(header); } PUB_FUNC void *tcc_mallocz_debug(unsigned long size, const char *file, int line) { void *ptr; ptr = tcc_malloc_debug(size,file,line); memset(ptr, 0, size); return ptr; } PUB_FUNC void *tcc_realloc_debug(void *ptr, unsigned long size, const char *file, int line) { mem_debug_header_t *header; int mem_debug_chain_update = 0; if (!ptr) return tcc_malloc_debug(size, file, line); header = malloc_check(ptr, "tcc_realloc"); mem_cur_size -= header->size; mem_debug_chain_update = (header == mem_debug_chain); header = realloc(header, sizeof(mem_debug_header_t) + size); if (!header) tcc_error("memory full (realloc)"); header->size = size; MEM_DEBUG_CHECK3(header) = MEM_DEBUG_MAGIC3; if (header->next) header->next->prev = header; if (header->prev) header->prev->next = header; if (mem_debug_chain_update) mem_debug_chain = header; mem_cur_size += size; if (mem_cur_size > mem_max_size) mem_max_size = mem_cur_size; return MEM_USER_PTR(header); } PUB_FUNC char *tcc_strdup_debug(const char *str, const char *file, int line) { char *ptr; ptr = tcc_malloc_debug(strlen(str) + 1, file, line); strcpy(ptr, str); return ptr; } PUB_FUNC void tcc_memcheck(void) { if (mem_cur_size) { mem_debug_header_t *header = mem_debug_chain; fprintf(stderr, "MEM_DEBUG: mem_leak= %d bytes, mem_max_size= %d bytes\n", mem_cur_size, mem_max_size); while (header) { fprintf(stderr, "%s:%u: error: %u bytes leaked\n", header->file_name, header->line_num, header->size); header = header->next; } #if MEM_DEBUG-0 == 2 exit(2); #endif } } #endif /* MEM_DEBUG */ #define free(p) use_tcc_free(p) #define malloc(s) use_tcc_malloc(s) #define realloc(p, s) use_tcc_realloc(p, s) /********************************************************/ /* dynarrays */ ST_FUNC void dynarray_add(void *ptab, int *nb_ptr, void *data) { int nb, nb_alloc; void **pp; nb = *nb_ptr; pp = *(void ***)ptab; /* every power of two we double array size */ if ((nb & (nb - 1)) == 0) { if (!nb) nb_alloc = 1; else nb_alloc = nb * 2; pp = tcc_realloc(pp, nb_alloc * sizeof(void *)); *(void***)ptab = pp; } pp[nb++] = data; *nb_ptr = nb; } ST_FUNC void dynarray_reset(void *pp, int *n) { void **p; for (p = *(void***)pp; *n; ++p, --*n) if (*p) tcc_free(*p); tcc_free(*(void**)pp); *(void**)pp = NULL; } static void tcc_split_path(TCCState *s, void *p_ary, int *p_nb_ary, const char *in) { const char *p; do { int c; CString str; cstr_new(&str); for (p = in; c = *p, c != '\0' && c != PATHSEP[0]; ++p) { if (c == '{' && p[1] && p[2] == '}') { c = p[1], p += 2; if (c == 'B') cstr_cat(&str, s->tcc_lib_path, -1); } else { cstr_ccat(&str, c); } } if (str.size) { cstr_ccat(&str, '\0'); dynarray_add(p_ary, p_nb_ary, tcc_strdup(str.data)); } cstr_free(&str); in = p+1; } while (*p); } /********************************************************/ static void strcat_vprintf(char *buf, int buf_size, const char *fmt, va_list ap) { int len; len = strlen(buf); vsnprintf(buf + len, buf_size - len, fmt, ap); } static void strcat_printf(char *buf, int buf_size, const char *fmt, ...) { va_list ap; va_start(ap, fmt); strcat_vprintf(buf, buf_size, fmt, ap); va_end(ap); } static void error1(TCCState *s1, int is_warning, const char *fmt, va_list ap) { char buf[2048]; BufferedFile **pf, *f; buf[0] = '\0'; /* use upper file if inline ":asm:" or token ":paste:" */ for (f = file; f && f->filename[0] == ':'; f = f->prev) ; if (f) { for(pf = s1->include_stack; pf < s1->include_stack_ptr; pf++) strcat_printf(buf, sizeof(buf), "In file included from %s:%d:\n", (*pf)->filename, (*pf)->line_num); if (s1->error_set_jmp_enabled) { strcat_printf(buf, sizeof(buf), "%s:%d: ", f->filename, f->line_num - !!(tok_flags & TOK_FLAG_BOL)); } else { strcat_printf(buf, sizeof(buf), "%s: ", f->filename); } } else { strcat_printf(buf, sizeof(buf), "tcc: "); } if (is_warning) strcat_printf(buf, sizeof(buf), "warning: "); else strcat_printf(buf, sizeof(buf), "error: "); strcat_vprintf(buf, sizeof(buf), fmt, ap); if (!s1->error_func) { /* default case: stderr */ if (s1->output_type == TCC_OUTPUT_PREPROCESS && s1->ppfp == stdout) /* print a newline during tcc -E */ printf("\n"), fflush(stdout); fflush(stdout); /* flush -v output */ fprintf(stderr, "%s\n", buf); fflush(stderr); /* print error/warning now (win32) */ } else { s1->error_func(s1->error_opaque, buf); } if (!is_warning || s1->warn_error) s1->nb_errors++; } LIBTCCAPI void tcc_set_error_func(TCCState *s, void *error_opaque, void (*error_func)(void *opaque, const char *msg)) { s->error_opaque = error_opaque; s->error_func = error_func; } /* error without aborting current compilation */ PUB_FUNC void tcc_error_noabort(const char *fmt, ...) { TCCState *s1 = tcc_state; va_list ap; va_start(ap, fmt); error1(s1, 0, fmt, ap); va_end(ap); } PUB_FUNC void tcc_error(const char *fmt, ...) { TCCState *s1 = tcc_state; va_list ap; va_start(ap, fmt); error1(s1, 0, fmt, ap); va_end(ap); /* better than nothing: in some cases, we accept to handle errors */ if (s1->error_set_jmp_enabled) { longjmp(s1->error_jmp_buf, 1); } else { /* XXX: eliminate this someday */ exit(1); } } PUB_FUNC void tcc_warning(const char *fmt, ...) { TCCState *s1 = tcc_state; va_list ap; if (s1->warn_none) return; va_start(ap, fmt); error1(s1, 1, fmt, ap); va_end(ap); } /********************************************************/ /* I/O layer */ ST_FUNC void tcc_open_bf(TCCState *s1, const char *filename, int initlen) { BufferedFile *bf; int buflen = initlen ? initlen : IO_BUF_SIZE; bf = tcc_mallocz(sizeof(BufferedFile) + buflen); bf->buf_ptr = bf->buffer; bf->buf_end = bf->buffer + initlen; bf->buf_end[0] = CH_EOB; /* put eob symbol */ pstrcpy(bf->filename, sizeof(bf->filename), filename); bf->true_filename = bf->filename; bf->line_num = 1; bf->ifdef_stack_ptr = s1->ifdef_stack_ptr; bf->fd = -1; bf->prev = file; file = bf; tok_flags = TOK_FLAG_BOL | TOK_FLAG_BOF; } ST_FUNC void tcc_close(void) { BufferedFile *bf = file; if (bf->fd > 0) { close(bf->fd); total_lines += bf->line_num; } if (bf->true_filename != bf->filename) tcc_free(bf->true_filename); file = bf->prev; tcc_free(bf); } ST_FUNC int tcc_open(TCCState *s1, const char *filename) { int fd; if (strcmp(filename, "-") == 0) fd = 0, filename = "<stdin>"; else fd = open(filename, O_RDONLY | O_BINARY); if ((s1->verbose == 2 && fd >= 0) || s1->verbose == 3) printf("%s %*s%s\n", fd < 0 ? "nf":"->", (int)(s1->include_stack_ptr - s1->include_stack), "", filename); if (fd < 0) return -1; tcc_open_bf(s1, filename, 0); #ifdef _WIN32 normalize_slashes(file->filename); #endif file->fd = fd; return fd; } /* compile the file opened in 'file'. Return non zero if errors. */ static int tcc_compile(TCCState *s1) { Sym *define_start; int filetype, is_asm; define_start = define_stack; filetype = s1->filetype; is_asm = filetype == AFF_TYPE_ASM || filetype == AFF_TYPE_ASMPP; tccelf_begin_file(s1); if (setjmp(s1->error_jmp_buf) == 0) { s1->nb_errors = 0; s1->error_set_jmp_enabled = 1; preprocess_start(s1, is_asm); if (s1->output_type == TCC_OUTPUT_PREPROCESS) { tcc_preprocess(s1); } else if (is_asm) { #ifdef CONFIG_TCC_ASM tcc_assemble(s1, filetype == AFF_TYPE_ASMPP); #else tcc_error_noabort("asm not supported"); #endif } else { tccgen_compile(s1); } } s1->error_set_jmp_enabled = 0; preprocess_end(s1); free_inline_functions(s1); /* reset define stack, but keep -D and built-ins */ free_defines(define_start); sym_pop(&global_stack, NULL, 0); sym_pop(&local_stack, NULL, 0); tccelf_end_file(s1); return s1->nb_errors != 0 ? -1 : 0; } LIBTCCAPI int tcc_compile_string(TCCState *s, const char *str) { int len, ret; len = strlen(str); tcc_open_bf(s, "<string>", len); memcpy(file->buffer, str, len); ret = tcc_compile(s); tcc_close(); return ret; } /* define a preprocessor symbol. A value can also be provided with the '=' operator */ LIBTCCAPI void tcc_define_symbol(TCCState *s1, const char *sym, const char *value) { int len1, len2; /* default value */ if (!value) value = "1"; len1 = strlen(sym); len2 = strlen(value); /* init file structure */ tcc_open_bf(s1, "<define>", len1 + len2 + 1); memcpy(file->buffer, sym, len1); file->buffer[len1] = ' '; memcpy(file->buffer + len1 + 1, value, len2); /* parse with define parser */ next_nomacro(); parse_define(); tcc_close(); } /* undefine a preprocessor symbol */ LIBTCCAPI void tcc_undefine_symbol(TCCState *s1, const char *sym) { TokenSym *ts; Sym *s; ts = tok_alloc(sym, strlen(sym)); s = define_find(ts->tok); /* undefine symbol by putting an invalid name */ if (s) define_undef(s); } /* cleanup all static data used during compilation */ static void tcc_cleanup(void) { if (NULL == tcc_state) return; while (file) tcc_close(); tccpp_delete(tcc_state); tcc_state = NULL; /* free sym_pools */ dynarray_reset(&sym_pools, &nb_sym_pools); /* reset symbol stack */ sym_free_first = NULL; } LIBTCCAPI TCCState *tcc_new(void) { TCCState *s; tcc_cleanup(); s = tcc_mallocz(sizeof(TCCState)); if (!s) return NULL; tcc_state = s; ++nb_states; s->alacarte_link = 1; s->nocommon = 1; s->warn_implicit_function_declaration = 1; s->ms_extensions = 1; #ifdef CHAR_IS_UNSIGNED s->char_is_unsigned = 1; #endif #ifdef TCC_TARGET_I386 s->seg_size = 32; #endif /* enable this if you want symbols with leading underscore on windows: */ #if 0 /* def TCC_TARGET_PE */ s->leading_underscore = 1; #endif #ifdef _WIN32 tcc_set_lib_path_w32(s); #else tcc_set_lib_path(s, CONFIG_TCCDIR); #endif tccelf_new(s); tccpp_new(s); /* we add dummy defines for some special macros to speed up tests and to have working defined() */ define_push(TOK___LINE__, MACRO_OBJ, NULL, NULL); define_push(TOK___FILE__, MACRO_OBJ, NULL, NULL); define_push(TOK___DATE__, MACRO_OBJ, NULL, NULL); define_push(TOK___TIME__, MACRO_OBJ, NULL, NULL); define_push(TOK___COUNTER__, MACRO_OBJ, NULL, NULL); { /* define __TINYC__ 92X */ char buffer[32]; int a,b,c; sscanf(TCC_VERSION, "%d.%d.%d", &a, &b, &c); sprintf(buffer, "%d", a*10000 + b*100 + c); tcc_define_symbol(s, "__TINYC__", buffer); } /* standard defines */ tcc_define_symbol(s, "__STDC__", NULL); tcc_define_symbol(s, "__STDC_VERSION__", "199901L"); tcc_define_symbol(s, "__STDC_HOSTED__", NULL); /* target defines */ #if defined(TCC_TARGET_I386) tcc_define_symbol(s, "__i386__", NULL); tcc_define_symbol(s, "__i386", NULL); tcc_define_symbol(s, "i386", NULL); #elif defined(TCC_TARGET_X86_64) tcc_define_symbol(s, "__x86_64__", NULL); #elif defined(TCC_TARGET_ARM) tcc_define_symbol(s, "__ARM_ARCH_4__", NULL); tcc_define_symbol(s, "__arm_elf__", NULL); tcc_define_symbol(s, "__arm_elf", NULL); tcc_define_symbol(s, "arm_elf", NULL); tcc_define_symbol(s, "__arm__", NULL); tcc_define_symbol(s, "__arm", NULL); tcc_define_symbol(s, "arm", NULL); tcc_define_symbol(s, "__APCS_32__", NULL); tcc_define_symbol(s, "__ARMEL__", NULL); #if defined(TCC_ARM_EABI) tcc_define_symbol(s, "__ARM_EABI__", NULL); #endif #if defined(TCC_ARM_HARDFLOAT) s->float_abi = ARM_HARD_FLOAT; tcc_define_symbol(s, "__ARM_PCS_VFP", NULL); #else s->float_abi = ARM_SOFTFP_FLOAT; #endif #elif defined(TCC_TARGET_ARM64) tcc_define_symbol(s, "__aarch64__", NULL); #elif defined TCC_TARGET_C67 tcc_define_symbol(s, "__C67__", NULL); #endif #ifdef TCC_TARGET_PE tcc_define_symbol(s, "_WIN32", NULL); # ifdef TCC_TARGET_X86_64 tcc_define_symbol(s, "_WIN64", NULL); # endif #else tcc_define_symbol(s, "__unix__", NULL); tcc_define_symbol(s, "__unix", NULL); tcc_define_symbol(s, "unix", NULL); # if defined(__linux__) tcc_define_symbol(s, "__linux__", NULL); tcc_define_symbol(s, "__linux", NULL); # endif # if defined(__FreeBSD__) tcc_define_symbol(s, "__FreeBSD__", "__FreeBSD__"); /* No 'Thread Storage Local' on FreeBSD with tcc */ tcc_define_symbol(s, "__NO_TLS", NULL); # endif # if defined(__FreeBSD_kernel__) tcc_define_symbol(s, "__FreeBSD_kernel__", NULL); # endif # if defined(__NetBSD__) tcc_define_symbol(s, "__NetBSD__", "__NetBSD__"); # endif # if defined(__OpenBSD__) tcc_define_symbol(s, "__OpenBSD__", "__OpenBSD__"); # endif #endif /* TinyCC & gcc defines */ #if PTR_SIZE == 4 /* 32bit systems. */ tcc_define_symbol(s, "__SIZE_TYPE__", "unsigned int"); tcc_define_symbol(s, "__PTRDIFF_TYPE__", "int"); tcc_define_symbol(s, "__ILP32__", NULL); #elif LONG_SIZE == 4 /* 64bit Windows. */ tcc_define_symbol(s, "__SIZE_TYPE__", "unsigned long long"); tcc_define_symbol(s, "__PTRDIFF_TYPE__", "long long"); tcc_define_symbol(s, "__LLP64__", NULL); #else /* Other 64bit systems. */ tcc_define_symbol(s, "__SIZE_TYPE__", "unsigned long"); tcc_define_symbol(s, "__PTRDIFF_TYPE__", "long"); tcc_define_symbol(s, "__LP64__", NULL); #endif #ifdef TCC_TARGET_PE tcc_define_symbol(s, "__WCHAR_TYPE__", "unsigned short"); tcc_define_symbol(s, "__WINT_TYPE__", "unsigned short"); #else tcc_define_symbol(s, "__WCHAR_TYPE__", "int"); /* wint_t is unsigned int by default, but (signed) int on BSDs and unsigned short on windows. Other OSes might have still other conventions, sigh. */ # if defined(__FreeBSD__) || defined (__FreeBSD_kernel__) \ || defined(__NetBSD__) || defined(__OpenBSD__) tcc_define_symbol(s, "__WINT_TYPE__", "int"); # ifdef __FreeBSD__ /* define __GNUC__ to have some useful stuff from sys/cdefs.h that are unconditionally used in FreeBSDs other system headers :/ */ tcc_define_symbol(s, "__GNUC__", "2"); tcc_define_symbol(s, "__GNUC_MINOR__", "7"); tcc_define_symbol(s, "__builtin_alloca", "alloca"); # endif # else tcc_define_symbol(s, "__WINT_TYPE__", "unsigned int"); /* glibc defines */ tcc_define_symbol(s, "__REDIRECT(name, proto, alias)", "name proto __asm__ (#alias)"); tcc_define_symbol(s, "__REDIRECT_NTH(name, proto, alias)", "name proto __asm__ (#alias) __THROW"); # endif # if defined(TCC_MUSL) tcc_define_symbol(s, "__DEFINED_va_list", ""); tcc_define_symbol(s, "__DEFINED___isoc_va_list", ""); tcc_define_symbol(s, "__isoc_va_list", "void *"); # endif /* TCC_MUSL */ /* Some GCC builtins that are simple to express as macros. */ tcc_define_symbol(s, "__builtin_extract_return_addr(x)", "x"); #endif /* ndef TCC_TARGET_PE */ return s; } LIBTCCAPI void tcc_delete(TCCState *s1) { tcc_cleanup(); /* free sections */ tccelf_delete(s1); /* free library paths */ dynarray_reset(&s1->library_paths, &s1->nb_library_paths); dynarray_reset(&s1->crt_paths, &s1->nb_crt_paths); /* free include paths */ dynarray_reset(&s1->cached_includes, &s1->nb_cached_includes); dynarray_reset(&s1->include_paths, &s1->nb_include_paths); dynarray_reset(&s1->sysinclude_paths, &s1->nb_sysinclude_paths); dynarray_reset(&s1->cmd_include_files, &s1->nb_cmd_include_files); tcc_free(s1->tcc_lib_path); tcc_free(s1->soname); tcc_free(s1->rpath); tcc_free(s1->init_symbol); tcc_free(s1->fini_symbol); tcc_free(s1->outfile); tcc_free(s1->deps_outfile); dynarray_reset(&s1->files, &s1->nb_files); dynarray_reset(&s1->target_deps, &s1->nb_target_deps); dynarray_reset(&s1->pragma_libs, &s1->nb_pragma_libs); dynarray_reset(&s1->argv, &s1->argc); #ifdef TCC_IS_NATIVE /* free runtime memory */ tcc_run_free(s1); #endif tcc_free(s1); if (0 == --nb_states) tcc_memcheck(); } LIBTCCAPI int tcc_set_output_type(TCCState *s, int output_type) { s->output_type = output_type; /* always elf for objects */ if (output_type == TCC_OUTPUT_OBJ) s->output_format = TCC_OUTPUT_FORMAT_ELF; if (s->char_is_unsigned) tcc_define_symbol(s, "__CHAR_UNSIGNED__", NULL); if (!s->nostdinc) { /* default include paths */ /* -isystem paths have already been handled */ tcc_add_sysinclude_path(s, CONFIG_TCC_SYSINCLUDEPATHS); } #ifdef CONFIG_TCC_BCHECK if (s->do_bounds_check) { /* if bound checking, then add corresponding sections */ tccelf_bounds_new(s); /* define symbol */ tcc_define_symbol(s, "__BOUNDS_CHECKING_ON", NULL); } #endif if (s->do_debug) { /* add debug sections */ tccelf_stab_new(s); } tcc_add_library_path(s, CONFIG_TCC_LIBPATHS); #ifdef TCC_TARGET_PE # ifdef _WIN32 if (!s->nostdlib && output_type != TCC_OUTPUT_OBJ) tcc_add_systemdir(s); # endif #else /* paths for crt objects */ tcc_split_path(s, &s->crt_paths, &s->nb_crt_paths, CONFIG_TCC_CRTPREFIX); /* add libc crt1/crti objects */ if ((output_type == TCC_OUTPUT_EXE || output_type == TCC_OUTPUT_DLL) && !s->nostdlib) { if (output_type != TCC_OUTPUT_DLL) tcc_add_crt(s, "crt1.o"); tcc_add_crt(s, "crti.o"); } #endif return 0; } LIBTCCAPI int tcc_add_include_path(TCCState *s, const char *pathname) { tcc_split_path(s, &s->include_paths, &s->nb_include_paths, pathname); return 0; } LIBTCCAPI int tcc_add_sysinclude_path(TCCState *s, const char *pathname) { tcc_split_path(s, &s->sysinclude_paths, &s->nb_sysinclude_paths, pathname); return 0; } ST_FUNC int tcc_add_file_internal(TCCState *s1, const char *filename, int flags) { int ret; /* open the file */ ret = tcc_open(s1, filename); if (ret < 0) { if (flags & AFF_PRINT_ERROR) tcc_error_noabort("file '%s' not found", filename); return ret; } /* update target deps */ dynarray_add(&s1->target_deps, &s1->nb_target_deps, tcc_strdup(filename)); if (flags & AFF_TYPE_BIN) { ElfW(Ehdr) ehdr; int fd, obj_type; fd = file->fd; obj_type = tcc_object_type(fd, &ehdr); lseek(fd, 0, SEEK_SET); #ifdef TCC_TARGET_MACHO if (0 == obj_type && 0 == strcmp(tcc_fileextension(filename), ".dylib")) obj_type = AFF_BINTYPE_DYN; #endif switch (obj_type) { case AFF_BINTYPE_REL: ret = tcc_load_object_file(s1, fd, 0); break; #ifndef TCC_TARGET_PE case AFF_BINTYPE_DYN: if (s1->output_type == TCC_OUTPUT_MEMORY) { ret = 0; #ifdef TCC_IS_NATIVE if (NULL == dlopen(filename, RTLD_GLOBAL | RTLD_LAZY)) ret = -1; #endif } else { ret = tcc_load_dll(s1, fd, filename, (flags & AFF_REFERENCED_DLL) != 0); } break; #endif case AFF_BINTYPE_AR: ret = tcc_load_archive(s1, fd); break; #ifdef TCC_TARGET_COFF case AFF_BINTYPE_C67: ret = tcc_load_coff(s1, fd); break; #endif default: #ifdef TCC_TARGET_PE ret = pe_load_file(s1, filename, fd); #else /* as GNU ld, consider it is an ld script if not recognized */ ret = tcc_load_ldscript(s1); #endif if (ret < 0) tcc_error_noabort("unrecognized file type"); break; } } else { ret = tcc_compile(s1); } tcc_close(); return ret; } LIBTCCAPI int tcc_add_file(TCCState *s, const char *filename) { int filetype = s->filetype; int flags = AFF_PRINT_ERROR; if (filetype == 0) { /* use a file extension to detect a filetype */ const char *ext = tcc_fileextension(filename); if (ext[0]) { ext++; if (!strcmp(ext, "S")) filetype = AFF_TYPE_ASMPP; else if (!strcmp(ext, "s")) filetype = AFF_TYPE_ASM; else if (!PATHCMP(ext, "c") || !PATHCMP(ext, "i")) filetype = AFF_TYPE_C; else flags |= AFF_TYPE_BIN; } else { filetype = AFF_TYPE_C; } s->filetype = filetype; } return tcc_add_file_internal(s, filename, flags); } LIBTCCAPI int tcc_add_library_path(TCCState *s, const char *pathname) { tcc_split_path(s, &s->library_paths, &s->nb_library_paths, pathname); return 0; } static int tcc_add_library_internal(TCCState *s, const char *fmt, const char *filename, int flags, char **paths, int nb_paths) { char buf[1024]; int i; for(i = 0; i < nb_paths; i++) { snprintf(buf, sizeof(buf), fmt, paths[i], filename); if (tcc_add_file_internal(s, buf, flags | AFF_TYPE_BIN) == 0) return 0; } return -1; } /* find and load a dll. Return non zero if not found */ /* XXX: add '-rpath' option support ? */ ST_FUNC int tcc_add_dll(TCCState *s, const char *filename, int flags) { return tcc_add_library_internal(s, "%s/%s", filename, flags, s->library_paths, s->nb_library_paths); } ST_FUNC int tcc_add_crt(TCCState *s, const char *filename) { if (-1 == tcc_add_library_internal(s, "%s/%s", filename, 0, s->crt_paths, s->nb_crt_paths)) tcc_error_noabort("file '%s' not found", filename); return 0; } /* the library name is the same as the argument of the '-l' option */ LIBTCCAPI int tcc_add_library(TCCState *s, const char *libraryname) { #if defined TCC_TARGET_PE const char *libs[] = { "%s/%s.def", "%s/lib%s.def", "%s/%s.dll", "%s/lib%s.dll", "%s/lib%s.a", NULL }; const char **pp = s->static_link ? libs + 4 : libs; #elif defined TCC_TARGET_MACHO const char *libs[] = { "%s/lib%s.dylib", "%s/lib%s.a", NULL }; const char **pp = s->static_link ? libs + 1 : libs; #else const char *libs[] = { "%s/lib%s.so", "%s/lib%s.a", NULL }; const char **pp = s->static_link ? libs + 1 : libs; #endif while (*pp) { if (0 == tcc_add_library_internal(s, *pp, libraryname, 0, s->library_paths, s->nb_library_paths)) return 0; ++pp; } return -1; } PUB_FUNC int tcc_add_library_err(TCCState *s, const char *libname) { int ret = tcc_add_library(s, libname); if (ret < 0) tcc_error_noabort("library '%s' not found", libname); return ret; } /* handle #pragma comment(lib,) */ ST_FUNC void tcc_add_pragma_libs(TCCState *s1) { int i; for (i = 0; i < s1->nb_pragma_libs; i++) tcc_add_library_err(s1, s1->pragma_libs[i]); } LIBTCCAPI int tcc_add_symbol(TCCState *s, const char *name, const void *val) { #ifdef TCC_TARGET_PE /* On x86_64 'val' might not be reachable with a 32bit offset. So it is handled here as if it were in a DLL. */ pe_putimport(s, 0, name, (uintptr_t)val); #else set_elf_sym(symtab_section, (uintptr_t)val, 0, ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, SHN_ABS, name); #endif return 0; } LIBTCCAPI void tcc_set_lib_path(TCCState *s, const char *path) { tcc_free(s->tcc_lib_path); s->tcc_lib_path = tcc_strdup(path); } #define WD_ALL 0x0001 /* warning is activated when using -Wall */ #define FD_INVERT 0x0002 /* invert value before storing */ typedef struct FlagDef { uint16_t offset; uint16_t flags; const char *name; } FlagDef; static int no_flag(const char **pp) { const char *p = *pp; if (*p != 'n' || *++p != 'o' || *++p != '-') return 0; *pp = p + 1; return 1; } ST_FUNC int set_flag(TCCState *s, const FlagDef *flags, const char *name) { int value, ret; const FlagDef *p; const char *r; value = 1; r = name; if (no_flag(&r)) value = 0; for (ret = -1, p = flags; p->name; ++p) { if (ret) { if (strcmp(r, p->name)) continue; } else { if (0 == (p->flags & WD_ALL)) continue; } if (p->offset) { *(int*)((char *)s + p->offset) = p->flags & FD_INVERT ? !value : value; if (ret) return 0; } else { ret = 0; } } return ret; } static int strstart(const char *val, const char **str) { const char *p, *q; p = *str; q = val; while (*q) { if (*p != *q) return 0; p++; q++; } *str = p; return 1; } /* Like strstart, but automatically takes into account that ld options can * * - start with double or single dash (e.g. '--soname' or '-soname') * - arguments can be given as separate or after '=' (e.g. '-Wl,-soname,x.so' * or '-Wl,-soname=x.so') * * you provide `val` always in 'option[=]' form (no leading -) */ static int link_option(const char *str, const char *val, const char **ptr) { const char *p, *q; int ret; /* there should be 1 or 2 dashes */ if (*str++ != '-') return 0; if (*str == '-') str++; /* then str & val should match (potentially up to '=') */ p = str; q = val; ret = 1; if (q[0] == '?') { ++q; if (no_flag(&p)) ret = -1; } while (*q != '\0' && *q != '=') { if (*p != *q) return 0; p++; q++; } /* '=' near eos means ',' or '=' is ok */ if (*q == '=') { if (*p == 0) *ptr = p; if (*p != ',' && *p != '=') return 0; p++; } else if (*p) { return 0; } *ptr = p; return ret; } static const char *skip_linker_arg(const char **str) { const char *s1 = *str; const char *s2 = strchr(s1, ','); *str = s2 ? s2++ : (s2 = s1 + strlen(s1)); return s2; } static void copy_linker_arg(char **pp, const char *s, int sep) { const char *q = s; char *p = *pp; int l = 0; if (p && sep) p[l = strlen(p)] = sep, ++l; skip_linker_arg(&q); pstrncpy(l + (*pp = tcc_realloc(p, q - s + l + 1)), s, q - s); } /* set linker options */ static int tcc_set_linker(TCCState *s, const char *option) { while (*option) { const char *p = NULL; char *end = NULL; int ignoring = 0; int ret; if (link_option(option, "Bsymbolic", &p)) { s->symbolic = 1; } else if (link_option(option, "nostdlib", &p)) { s->nostdlib = 1; } else if (link_option(option, "fini=", &p)) { copy_linker_arg(&s->fini_symbol, p, 0); ignoring = 1; } else if (link_option(option, "image-base=", &p) || link_option(option, "Ttext=", &p)) { s->text_addr = strtoull(p, &end, 16); s->has_text_addr = 1; } else if (link_option(option, "init=", &p)) { copy_linker_arg(&s->init_symbol, p, 0); ignoring = 1; } else if (link_option(option, "oformat=", &p)) { #if defined(TCC_TARGET_PE) if (strstart("pe-", &p)) { #elif PTR_SIZE == 8 if (strstart("elf64-", &p)) { #else if (strstart("elf32-", &p)) { #endif s->output_format = TCC_OUTPUT_FORMAT_ELF; } else if (!strcmp(p, "binary")) { s->output_format = TCC_OUTPUT_FORMAT_BINARY; #ifdef TCC_TARGET_COFF } else if (!strcmp(p, "coff")) { s->output_format = TCC_OUTPUT_FORMAT_COFF; #endif } else goto err; } else if (link_option(option, "as-needed", &p)) { ignoring = 1; } else if (link_option(option, "O", &p)) { ignoring = 1; } else if (link_option(option, "export-all-symbols", &p)) { s->rdynamic = 1; } else if (link_option(option, "rpath=", &p)) { copy_linker_arg(&s->rpath, p, ':'); } else if (link_option(option, "enable-new-dtags", &p)) { s->enable_new_dtags = 1; } else if (link_option(option, "section-alignment=", &p)) { s->section_align = strtoul(p, &end, 16); } else if (link_option(option, "soname=", &p)) { copy_linker_arg(&s->soname, p, 0); #ifdef TCC_TARGET_PE } else if (link_option(option, "large-address-aware", &p)) { s->pe_characteristics |= 0x20; } else if (link_option(option, "file-alignment=", &p)) { s->pe_file_align = strtoul(p, &end, 16); } else if (link_option(option, "stack=", &p)) { s->pe_stack_size = strtoul(p, &end, 10); } else if (link_option(option, "subsystem=", &p)) { #if defined(TCC_TARGET_I386) || defined(TCC_TARGET_X86_64) if (!strcmp(p, "native")) { s->pe_subsystem = 1; } else if (!strcmp(p, "console")) { s->pe_subsystem = 3; } else if (!strcmp(p, "gui") || !strcmp(p, "windows")) { s->pe_subsystem = 2; } else if (!strcmp(p, "posix")) { s->pe_subsystem = 7; } else if (!strcmp(p, "efiapp")) { s->pe_subsystem = 10; } else if (!strcmp(p, "efiboot")) { s->pe_subsystem = 11; } else if (!strcmp(p, "efiruntime")) { s->pe_subsystem = 12; } else if (!strcmp(p, "efirom")) { s->pe_subsystem = 13; #elif defined(TCC_TARGET_ARM) if (!strcmp(p, "wince")) { s->pe_subsystem = 9; #endif } else goto err; #endif } else if (ret = link_option(option, "?whole-archive", &p), ret) { s->alacarte_link = ret < 0; } else if (p) { return 0; } else { err: tcc_error("unsupported linker option '%s'", option); } if (ignoring && s->warn_unsupported) tcc_warning("unsupported linker option '%s'", option); option = skip_linker_arg(&p); } return 1; } typedef struct TCCOption { const char *name; uint16_t index; uint16_t flags; } TCCOption; enum { TCC_OPTION_HELP, TCC_OPTION_HELP2, TCC_OPTION_v, TCC_OPTION_I, TCC_OPTION_D, TCC_OPTION_U, TCC_OPTION_P, TCC_OPTION_L, TCC_OPTION_B, TCC_OPTION_l, TCC_OPTION_bench, TCC_OPTION_bt, TCC_OPTION_b, TCC_OPTION_g, TCC_OPTION_c, TCC_OPTION_dumpversion, TCC_OPTION_d, TCC_OPTION_static, TCC_OPTION_std, TCC_OPTION_shared, TCC_OPTION_soname, TCC_OPTION_o, TCC_OPTION_r, TCC_OPTION_s, TCC_OPTION_traditional, TCC_OPTION_Wl, TCC_OPTION_Wp, TCC_OPTION_W, TCC_OPTION_O, TCC_OPTION_mfloat_abi, TCC_OPTION_m, TCC_OPTION_f, TCC_OPTION_isystem, TCC_OPTION_iwithprefix, TCC_OPTION_include, TCC_OPTION_nostdinc, TCC_OPTION_nostdlib, TCC_OPTION_print_search_dirs, TCC_OPTION_rdynamic, TCC_OPTION_param, TCC_OPTION_pedantic, TCC_OPTION_pthread, TCC_OPTION_run, TCC_OPTION_w, TCC_OPTION_pipe, TCC_OPTION_E, TCC_OPTION_MD, TCC_OPTION_MF, TCC_OPTION_x, TCC_OPTION_ar, TCC_OPTION_impdef }; #define TCC_OPTION_HAS_ARG 0x0001 #define TCC_OPTION_NOSEP 0x0002 /* cannot have space before option and arg */ static const TCCOption tcc_options[] = { { "h", TCC_OPTION_HELP, 0 }, { "-help", TCC_OPTION_HELP, 0 }, { "?", TCC_OPTION_HELP, 0 }, { "hh", TCC_OPTION_HELP2, 0 }, { "v", TCC_OPTION_v, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "I", TCC_OPTION_I, TCC_OPTION_HAS_ARG }, { "D", TCC_OPTION_D, TCC_OPTION_HAS_ARG }, { "U", TCC_OPTION_U, TCC_OPTION_HAS_ARG }, { "P", TCC_OPTION_P, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "L", TCC_OPTION_L, TCC_OPTION_HAS_ARG }, { "B", TCC_OPTION_B, TCC_OPTION_HAS_ARG }, { "l", TCC_OPTION_l, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "bench", TCC_OPTION_bench, 0 }, #ifdef CONFIG_TCC_BACKTRACE { "bt", TCC_OPTION_bt, TCC_OPTION_HAS_ARG }, #endif #ifdef CONFIG_TCC_BCHECK { "b", TCC_OPTION_b, 0 }, #endif { "g", TCC_OPTION_g, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "c", TCC_OPTION_c, 0 }, { "dumpversion", TCC_OPTION_dumpversion, 0}, { "d", TCC_OPTION_d, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "static", TCC_OPTION_static, 0 }, { "std", TCC_OPTION_std, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "shared", TCC_OPTION_shared, 0 }, { "soname", TCC_OPTION_soname, TCC_OPTION_HAS_ARG }, { "o", TCC_OPTION_o, TCC_OPTION_HAS_ARG }, { "-param", TCC_OPTION_param, TCC_OPTION_HAS_ARG }, { "pedantic", TCC_OPTION_pedantic, 0}, { "pthread", TCC_OPTION_pthread, 0}, { "run", TCC_OPTION_run, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "rdynamic", TCC_OPTION_rdynamic, 0 }, { "r", TCC_OPTION_r, 0 }, { "s", TCC_OPTION_s, 0 }, { "traditional", TCC_OPTION_traditional, 0 }, { "Wl,", TCC_OPTION_Wl, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "Wp,", TCC_OPTION_Wp, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "W", TCC_OPTION_W, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "O", TCC_OPTION_O, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, #ifdef TCC_TARGET_ARM { "mfloat-abi", TCC_OPTION_mfloat_abi, TCC_OPTION_HAS_ARG }, #endif { "m", TCC_OPTION_m, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "f", TCC_OPTION_f, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP }, { "isystem", TCC_OPTION_isystem, TCC_OPTION_HAS_ARG }, { "include", TCC_OPTION_include, TCC_OPTION_HAS_ARG }, { "nostdinc", TCC_OPTION_nostdinc, 0 }, { "nostdlib", TCC_OPTION_nostdlib, 0 }, { "print-search-dirs", TCC_OPTION_print_search_dirs, 0 }, { "w", TCC_OPTION_w, 0 }, { "pipe", TCC_OPTION_pipe, 0}, { "E", TCC_OPTION_E, 0}, { "MD", TCC_OPTION_MD, 0}, { "MF", TCC_OPTION_MF, TCC_OPTION_HAS_ARG }, { "x", TCC_OPTION_x, TCC_OPTION_HAS_ARG }, { "ar", TCC_OPTION_ar, 0}, #ifdef TCC_TARGET_PE { "impdef", TCC_OPTION_impdef, 0}, #endif { NULL, 0, 0 }, }; static const FlagDef options_W[] = { { 0, 0, "all" }, { offsetof(TCCState, warn_unsupported), 0, "unsupported" }, { offsetof(TCCState, warn_write_strings), 0, "write-strings" }, { offsetof(TCCState, warn_error), 0, "error" }, { offsetof(TCCState, warn_gcc_compat), 0, "gcc-compat" }, { offsetof(TCCState, warn_implicit_function_declaration), WD_ALL, "implicit-function-declaration" }, { 0, 0, NULL } }; static const FlagDef options_f[] = { { offsetof(TCCState, char_is_unsigned), 0, "unsigned-char" }, { offsetof(TCCState, char_is_unsigned), FD_INVERT, "signed-char" }, { offsetof(TCCState, nocommon), FD_INVERT, "common" }, { offsetof(TCCState, leading_underscore), 0, "leading-underscore" }, { offsetof(TCCState, ms_extensions), 0, "ms-extensions" }, { offsetof(TCCState, dollars_in_identifiers), 0, "dollars-in-identifiers" }, { 0, 0, NULL } }; static const FlagDef options_m[] = { { offsetof(TCCState, ms_bitfields), 0, "ms-bitfields" }, #ifdef TCC_TARGET_X86_64 { offsetof(TCCState, nosse), FD_INVERT, "sse" }, #endif { 0, 0, NULL } }; static void parse_option_D(TCCState *s1, const char *optarg) { char *sym = tcc_strdup(optarg); char *value = strchr(sym, '='); if (value) *value++ = '\0'; tcc_define_symbol(s1, sym, value); tcc_free(sym); } static void args_parser_add_file(TCCState *s, const char* filename, int filetype) { struct filespec *f = tcc_malloc(sizeof *f + strlen(filename)); f->type = filetype; f->alacarte = s->alacarte_link; strcpy(f->name, filename); dynarray_add(&s->files, &s->nb_files, f); } static int args_parser_make_argv(const char *r, int *argc, char ***argv) { int ret = 0, q, c; CString str; for(;;) { while (c = (unsigned char)*r, c && c <= ' ') ++r; if (c == 0) break; q = 0; cstr_new(&str); while (c = (unsigned char)*r, c) { ++r; if (c == '\\' && (*r == '"' || *r == '\\')) { c = *r++; } else if (c == '"') { q = !q; continue; } else if (q == 0 && c <= ' ') { break; } cstr_ccat(&str, c); } cstr_ccat(&str, 0); //printf("<%s>\n", str.data), fflush(stdout); dynarray_add(argv, argc, tcc_strdup(str.data)); cstr_free(&str); ++ret; } return ret; } /* read list file */ static void args_parser_listfile(TCCState *s, const char *filename, int optind, int *pargc, char ***pargv) { int fd, i; size_t len; char *p; int argc = 0; char **argv = NULL; fd = open(filename, O_RDONLY | O_BINARY); if (fd < 0) tcc_error("listfile '%s' not found", filename); len = lseek(fd, 0, SEEK_END); p = tcc_malloc(len + 1), p[len] = 0; lseek(fd, 0, SEEK_SET), read(fd, p, len), close(fd); for (i = 0; i < *pargc; ++i) if (i == optind) args_parser_make_argv(p, &argc, &argv); else dynarray_add(&argv, &argc, tcc_strdup((*pargv)[i])); tcc_free(p); dynarray_reset(&s->argv, &s->argc); *pargc = s->argc = argc, *pargv = s->argv = argv; } PUB_FUNC int tcc_parse_args(TCCState *s, int *pargc, char ***pargv, int optind) { const TCCOption *popt; const char *optarg, *r; const char *run = NULL; int last_o = -1; int x; CString linker_arg; /* collect -Wl options */ int tool = 0, arg_start = 0, noaction = optind; char **argv = *pargv; int argc = *pargc; cstr_new(&linker_arg); while (optind < argc) { r = argv[optind]; if (r[0] == '@' && r[1] != '\0') { args_parser_listfile(s, r + 1, optind, &argc, &argv); continue; } optind++; if (tool) { if (r[0] == '-' && r[1] == 'v' && r[2] == 0) ++s->verbose; continue; } reparse: if (r[0] != '-' || r[1] == '\0') { if (r[0] != '@') /* allow "tcc file(s) -run @ args ..." */ args_parser_add_file(s, r, s->filetype); if (run) { tcc_set_options(s, run); arg_start = optind - 1; break; } continue; } /* find option in table */ for(popt = tcc_options; ; ++popt) { const char *p1 = popt->name; const char *r1 = r + 1; if (p1 == NULL) tcc_error("invalid option -- '%s'", r); if (!strstart(p1, &r1)) continue; optarg = r1; if (popt->flags & TCC_OPTION_HAS_ARG) { if (*r1 == '\0' && !(popt->flags & TCC_OPTION_NOSEP)) { if (optind >= argc) arg_err: tcc_error("argument to '%s' is missing", r); optarg = argv[optind++]; } } else if (*r1 != '\0') continue; break; } switch(popt->index) { case TCC_OPTION_HELP: return OPT_HELP; case TCC_OPTION_HELP2: return OPT_HELP2; case TCC_OPTION_I: tcc_add_include_path(s, optarg); break; case TCC_OPTION_D: parse_option_D(s, optarg); break; case TCC_OPTION_U: tcc_undefine_symbol(s, optarg); break; case TCC_OPTION_L: tcc_add_library_path(s, optarg); break; case TCC_OPTION_B: /* set tcc utilities path (mainly for tcc development) */ tcc_set_lib_path(s, optarg); break; case TCC_OPTION_l: args_parser_add_file(s, optarg, AFF_TYPE_LIB); s->nb_libraries++; break; case TCC_OPTION_pthread: parse_option_D(s, "_REENTRANT"); s->option_pthread = 1; break; case TCC_OPTION_bench: s->do_bench = 1; break; #ifdef CONFIG_TCC_BACKTRACE case TCC_OPTION_bt: tcc_set_num_callers(atoi(optarg)); break; #endif #ifdef CONFIG_TCC_BCHECK case TCC_OPTION_b: s->do_bounds_check = 1; s->do_debug = 1; break; #endif case TCC_OPTION_g: s->do_debug = 1; break; case TCC_OPTION_c: x = TCC_OUTPUT_OBJ; set_output_type: if (s->output_type) tcc_warning("-%s: overriding compiler action already specified", popt->name); s->output_type = x; break; case TCC_OPTION_d: if (*optarg == 'D') s->dflag = 3; else if (*optarg == 'M') s->dflag = 7; else if (*optarg == 't') s->dflag = 16; else if (isnum(*optarg)) g_debug = atoi(optarg); else goto unsupported_option; break; case TCC_OPTION_static: s->static_link = 1; break; case TCC_OPTION_std: /* silently ignore, a current purpose: allow to use a tcc as a reference compiler for "make test" */ break; case TCC_OPTION_shared: x = TCC_OUTPUT_DLL; goto set_output_type; case TCC_OPTION_soname: s->soname = tcc_strdup(optarg); break; case TCC_OPTION_o: if (s->outfile) { tcc_warning("multiple -o option"); tcc_free(s->outfile); } s->outfile = tcc_strdup(optarg); break; case TCC_OPTION_r: /* generate a .o merging several output files */ s->option_r = 1; x = TCC_OUTPUT_OBJ; goto set_output_type; case TCC_OPTION_isystem: tcc_add_sysinclude_path(s, optarg); break; case TCC_OPTION_include: dynarray_add(&s->cmd_include_files, &s->nb_cmd_include_files, tcc_strdup(optarg)); break; case TCC_OPTION_nostdinc: s->nostdinc = 1; break; case TCC_OPTION_nostdlib: s->nostdlib = 1; break; case TCC_OPTION_run: #ifndef TCC_IS_NATIVE tcc_error("-run is not available in a cross compiler"); #endif run = optarg; x = TCC_OUTPUT_MEMORY; goto set_output_type; case TCC_OPTION_v: do ++s->verbose; while (*optarg++ == 'v'); ++noaction; break; case TCC_OPTION_f: if (set_flag(s, options_f, optarg) < 0) goto unsupported_option; break; #ifdef TCC_TARGET_ARM case TCC_OPTION_mfloat_abi: /* tcc doesn't support soft float yet */ if (!strcmp(optarg, "softfp")) { s->float_abi = ARM_SOFTFP_FLOAT; tcc_undefine_symbol(s, "__ARM_PCS_VFP"); } else if (!strcmp(optarg, "hard")) s->float_abi = ARM_HARD_FLOAT; else tcc_error("unsupported float abi '%s'", optarg); break; #endif case TCC_OPTION_m: if (set_flag(s, options_m, optarg) < 0) { if (x = atoi(optarg), x != 32 && x != 64) goto unsupported_option; if (PTR_SIZE != x/8) return x; ++noaction; } break; case TCC_OPTION_W: if (set_flag(s, options_W, optarg) < 0) goto unsupported_option; break; case TCC_OPTION_w: s->warn_none = 1; break; case TCC_OPTION_rdynamic: s->rdynamic = 1; break; case TCC_OPTION_Wl: if (linker_arg.size) --linker_arg.size, cstr_ccat(&linker_arg, ','); cstr_cat(&linker_arg, optarg, 0); if (tcc_set_linker(s, linker_arg.data)) cstr_free(&linker_arg); break; case TCC_OPTION_Wp: r = optarg; goto reparse; case TCC_OPTION_E: x = TCC_OUTPUT_PREPROCESS; goto set_output_type; case TCC_OPTION_P: s->Pflag = atoi(optarg) + 1; break; case TCC_OPTION_MD: s->gen_deps = 1; break; case TCC_OPTION_MF: s->deps_outfile = tcc_strdup(optarg); break; case TCC_OPTION_dumpversion: printf ("%s\n", TCC_VERSION); exit(0); break; case TCC_OPTION_x: if (*optarg == 'c') s->filetype = AFF_TYPE_C; else if (*optarg == 'a') s->filetype = AFF_TYPE_ASMPP; else if (*optarg == 'n') s->filetype = AFF_TYPE_NONE; else tcc_warning("unsupported language '%s'", optarg); break; case TCC_OPTION_O: last_o = atoi(optarg); break; case TCC_OPTION_print_search_dirs: x = OPT_PRINT_DIRS; goto extra_action; case TCC_OPTION_impdef: x = OPT_IMPDEF; goto extra_action; case TCC_OPTION_ar: x = OPT_AR; extra_action: arg_start = optind - 1; if (arg_start != noaction) tcc_error("cannot parse %s here", r); tool = x; break; case TCC_OPTION_traditional: case TCC_OPTION_pedantic: case TCC_OPTION_pipe: case TCC_OPTION_s: /* ignored */ break; default: unsupported_option: if (s->warn_unsupported) tcc_warning("unsupported option '%s'", r); break; } } if (last_o > 0) tcc_define_symbol(s, "__OPTIMIZE__", NULL); if (linker_arg.size) { r = linker_arg.data; goto arg_err; } *pargc = argc - arg_start; *pargv = argv + arg_start; if (tool) return tool; if (optind != noaction) return 0; if (s->verbose == 2) return OPT_PRINT_DIRS; if (s->verbose) return OPT_V; return OPT_HELP; } LIBTCCAPI void tcc_set_options(TCCState *s, const char *r) { char **argv = NULL; int argc = 0; args_parser_make_argv(r, &argc, &argv); tcc_parse_args(s, &argc, &argv, 0); dynarray_reset(&argv, &argc); } PUB_FUNC void tcc_print_stats(TCCState *s, unsigned total_time) { if (total_time < 1) total_time = 1; if (total_bytes < 1) total_bytes = 1; fprintf(stderr, "* %d idents, %d lines, %d bytes\n" "* %0.3f s, %u lines/s, %0.1f MB/s\n", tok_ident - TOK_IDENT, total_lines, total_bytes, (double)total_time/1000, (unsigned)total_lines*1000/total_time, (double)total_bytes/1000/total_time); #ifdef MEM_DEBUG fprintf(stderr, "* %d bytes memory used\n", mem_max_size); #endif }
/* * TCC - Tiny C Compiler * * Copyright (c) 2001-2004 Fabrice Bellard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "tcc.h" /********************************************************/ /* global variables */ ST_DATA int tok_flags; ST_DATA int parse_flags; ST_DATA struct BufferedFile *file; ST_DATA int ch, tok; ST_DATA CValue tokc; ST_DATA const int *macro_ptr; ST_DATA CString tokcstr; /* current parsed string, if any */ /* display benchmark infos */ ST_DATA int total_lines; ST_DATA int total_bytes; ST_DATA int tok_ident; ST_DATA TokenSym **table_ident; /* ------------------------------------------------------------------------- */ static TokenSym *hash_ident[TOK_HASH_SIZE]; static char token_buf[STRING_MAX_SIZE + 1]; static CString cstr_buf; static CString macro_equal_buf; static TokenString tokstr_buf; static unsigned char isidnum_table[256 - CH_EOF]; static int pp_debug_tok, pp_debug_symv; static int pp_once; static int pp_expr; static int pp_counter; static void tok_print(const char *msg, const int *str); static struct TinyAlloc *toksym_alloc; static struct TinyAlloc *tokstr_alloc; static struct TinyAlloc *cstr_alloc; static TokenString *macro_stack; static const char tcc_keywords[] = #define DEF(id, str) str "\0" #include "tcctok.h" #undef DEF ; /* WARNING: the content of this string encodes token numbers */ static const unsigned char tok_two_chars[] = /* outdated -- gr "<=\236>=\235!=\225&&\240||\241++\244--\242==\224<<\1>>\2+=\253" "-=\255*=\252/=\257%=\245&=\246^=\336|=\374->\313..\250##\266"; */{ '<','=', TOK_LE, '>','=', TOK_GE, '!','=', TOK_NE, '&','&', TOK_LAND, '|','|', TOK_LOR, '+','+', TOK_INC, '-','-', TOK_DEC, '=','=', TOK_EQ, '<','<', TOK_SHL, '>','>', TOK_SAR, '+','=', TOK_A_ADD, '-','=', TOK_A_SUB, '*','=', TOK_A_MUL, '/','=', TOK_A_DIV, '%','=', TOK_A_MOD, '&','=', TOK_A_AND, '^','=', TOK_A_XOR, '|','=', TOK_A_OR, '-','>', TOK_ARROW, '.','.', TOK_TWODOTS, '#','#', TOK_TWOSHARPS, 0 }; static void next_nomacro_spc(void); ST_FUNC void skip(int c) { if (tok != c) tcc_error("'%c' expected (got \"%s\")", c, get_tok_str(tok, &tokc)); next(); } ST_FUNC void expect(const char *msg) { tcc_error("%s expected", msg); } /* ------------------------------------------------------------------------- */ /* Custom allocator for tiny objects */ #define USE_TAL #ifndef USE_TAL #define tal_free(al, p) tcc_free(p) #define tal_realloc(al, p, size) tcc_realloc(p, size) #define tal_new(a,b,c) #define tal_delete(a) #else #if !defined(MEM_DEBUG) #define tal_free(al, p) tal_free_impl(al, p) #define tal_realloc(al, p, size) tal_realloc_impl(&al, p, size) #define TAL_DEBUG_PARAMS #else #define TAL_DEBUG 1 //#define TAL_INFO 1 /* collect and dump allocators stats */ #define tal_free(al, p) tal_free_impl(al, p, __FILE__, __LINE__) #define tal_realloc(al, p, size) tal_realloc_impl(&al, p, size, __FILE__, __LINE__) #define TAL_DEBUG_PARAMS , const char *file, int line #define TAL_DEBUG_FILE_LEN 40 #endif #define TOKSYM_TAL_SIZE (768 * 1024) /* allocator for tiny TokenSym in table_ident */ #define TOKSTR_TAL_SIZE (768 * 1024) /* allocator for tiny TokenString instances */ #define CSTR_TAL_SIZE (256 * 1024) /* allocator for tiny CString instances */ #define TOKSYM_TAL_LIMIT 256 /* prefer unique limits to distinguish allocators debug msgs */ #define TOKSTR_TAL_LIMIT 128 /* 32 * sizeof(int) */ #define CSTR_TAL_LIMIT 1024 typedef struct TinyAlloc { unsigned limit; unsigned size; uint8_t *buffer; uint8_t *p; unsigned nb_allocs; struct TinyAlloc *next, *top; #ifdef TAL_INFO unsigned nb_peak; unsigned nb_total; unsigned nb_missed; uint8_t *peak_p; #endif } TinyAlloc; typedef struct tal_header_t { unsigned size; #ifdef TAL_DEBUG int line_num; /* negative line_num used for double free check */ char file_name[TAL_DEBUG_FILE_LEN + 1]; #endif } tal_header_t; /* ------------------------------------------------------------------------- */ static TinyAlloc *tal_new(TinyAlloc **pal, unsigned limit, unsigned size) { TinyAlloc *al = tcc_mallocz(sizeof(TinyAlloc)); al->p = al->buffer = tcc_malloc(size); al->limit = limit; al->size = size; if (pal) *pal = al; return al; } static void tal_delete(TinyAlloc *al) { TinyAlloc *next; tail_call: if (!al) return; #ifdef TAL_INFO fprintf(stderr, "limit=%5d, size=%5g MB, nb_peak=%6d, nb_total=%8d, nb_missed=%6d, usage=%5.1f%%\n", al->limit, al->size / 1024.0 / 1024.0, al->nb_peak, al->nb_total, al->nb_missed, (al->peak_p - al->buffer) * 100.0 / al->size); #endif #ifdef TAL_DEBUG if (al->nb_allocs > 0) { uint8_t *p; fprintf(stderr, "TAL_DEBUG: memory leak %d chunk(s) (limit= %d)\n", al->nb_allocs, al->limit); p = al->buffer; while (p < al->p) { tal_header_t *header = (tal_header_t *)p; if (header->line_num > 0) { fprintf(stderr, "%s:%d: chunk of %d bytes leaked\n", header->file_name, header->line_num, header->size); } p += header->size + sizeof(tal_header_t); } #if MEM_DEBUG-0 == 2 exit(2); #endif } #endif next = al->next; tcc_free(al->buffer); tcc_free(al); al = next; goto tail_call; } static void tal_free_impl(TinyAlloc *al, void *p TAL_DEBUG_PARAMS) { if (!p) return; tail_call: if (al->buffer <= (uint8_t *)p && (uint8_t *)p < al->buffer + al->size) { #ifdef TAL_DEBUG tal_header_t *header = (((tal_header_t *)p) - 1); if (header->line_num < 0) { fprintf(stderr, "%s:%d: TAL_DEBUG: double frees chunk from\n", file, line); fprintf(stderr, "%s:%d: %d bytes\n", header->file_name, (int)-header->line_num, (int)header->size); } else header->line_num = -header->line_num; #endif al->nb_allocs--; if (!al->nb_allocs) al->p = al->buffer; } else if (al->next) { al = al->next; goto tail_call; } else tcc_free(p); } static void *tal_realloc_impl(TinyAlloc **pal, void *p, unsigned size TAL_DEBUG_PARAMS) { tal_header_t *header; void *ret; int is_own; unsigned adj_size = (size + 3) & -4; TinyAlloc *al = *pal; tail_call: is_own = (al->buffer <= (uint8_t *)p && (uint8_t *)p < al->buffer + al->size); if ((!p || is_own) && size <= al->limit) { if (al->p + adj_size + sizeof(tal_header_t) < al->buffer + al->size) { header = (tal_header_t *)al->p; header->size = adj_size; #ifdef TAL_DEBUG { int ofs = strlen(file) - TAL_DEBUG_FILE_LEN; strncpy(header->file_name, file + (ofs > 0 ? ofs : 0), TAL_DEBUG_FILE_LEN); header->file_name[TAL_DEBUG_FILE_LEN] = 0; header->line_num = line; } #endif ret = al->p + sizeof(tal_header_t); al->p += adj_size + sizeof(tal_header_t); if (is_own) { header = (((tal_header_t *)p) - 1); memcpy(ret, p, header->size); #ifdef TAL_DEBUG header->line_num = -header->line_num; #endif } else { al->nb_allocs++; } #ifdef TAL_INFO if (al->nb_peak < al->nb_allocs) al->nb_peak = al->nb_allocs; if (al->peak_p < al->p) al->peak_p = al->p; al->nb_total++; #endif return ret; } else if (is_own) { al->nb_allocs--; ret = tal_realloc(*pal, 0, size); header = (((tal_header_t *)p) - 1); memcpy(ret, p, header->size); #ifdef TAL_DEBUG header->line_num = -header->line_num; #endif return ret; } if (al->next) { al = al->next; } else { TinyAlloc *bottom = al, *next = al->top ? al->top : al; al = tal_new(pal, next->limit, next->size * 2); al->next = next; bottom->top = al; } goto tail_call; } if (is_own) { al->nb_allocs--; ret = tcc_malloc(size); header = (((tal_header_t *)p) - 1); memcpy(ret, p, header->size); #ifdef TAL_DEBUG header->line_num = -header->line_num; #endif } else if (al->next) { al = al->next; goto tail_call; } else ret = tcc_realloc(p, size); #ifdef TAL_INFO al->nb_missed++; #endif return ret; } #endif /* USE_TAL */ /* ------------------------------------------------------------------------- */ /* CString handling */ static void cstr_realloc(CString *cstr, int new_size) { int size; size = cstr->size_allocated; if (size < 8) size = 8; /* no need to allocate a too small first string */ while (size < new_size) size = size * 2; cstr->data = tal_realloc(cstr_alloc, cstr->data, size); cstr->size_allocated = size; } /* add a byte */ ST_INLN void cstr_ccat(CString *cstr, int ch) { int size; size = cstr->size + 1; if (size > cstr->size_allocated) cstr_realloc(cstr, size); ((unsigned char *)cstr->data)[size - 1] = ch; cstr->size = size; } ST_FUNC void cstr_cat(CString *cstr, const char *str, int len) { int size; if (len <= 0) len = strlen(str) + 1 + len; size = cstr->size + len; if (size > cstr->size_allocated) cstr_realloc(cstr, size); memmove(((unsigned char *)cstr->data) + cstr->size, str, len); cstr->size = size; } /* add a wide char */ ST_FUNC void cstr_wccat(CString *cstr, int ch) { int size; size = cstr->size + sizeof(nwchar_t); if (size > cstr->size_allocated) cstr_realloc(cstr, size); *(nwchar_t *)(((unsigned char *)cstr->data) + size - sizeof(nwchar_t)) = ch; cstr->size = size; } ST_FUNC void cstr_new(CString *cstr) { memset(cstr, 0, sizeof(CString)); } /* free string and reset it to NULL */ ST_FUNC void cstr_free(CString *cstr) { tal_free(cstr_alloc, cstr->data); cstr_new(cstr); } /* reset string to empty */ ST_FUNC void cstr_reset(CString *cstr) { cstr->size = 0; } /* XXX: unicode ? */ static void add_char(CString *cstr, int c) { if (c == '\'' || c == '\"' || c == '\\') { /* XXX: could be more precise if char or string */ cstr_ccat(cstr, '\\'); } if (c >= 32 && c <= 126) { cstr_ccat(cstr, c); } else { cstr_ccat(cstr, '\\'); if (c == '\n') { cstr_ccat(cstr, 'n'); } else { cstr_ccat(cstr, '0' + ((c >> 6) & 7)); cstr_ccat(cstr, '0' + ((c >> 3) & 7)); cstr_ccat(cstr, '0' + (c & 7)); } } } /* ------------------------------------------------------------------------- */ /* allocate a new token */ static TokenSym *tok_alloc_new(TokenSym **pts, const char *str, int len) { TokenSym *ts, **ptable; int i; if (tok_ident >= SYM_FIRST_ANOM) tcc_error("memory full (symbols)"); /* expand token table if needed */ i = tok_ident - TOK_IDENT; if ((i % TOK_ALLOC_INCR) == 0) { ptable = tcc_realloc(table_ident, (i + TOK_ALLOC_INCR) * sizeof(TokenSym *)); table_ident = ptable; } ts = tal_realloc(toksym_alloc, 0, sizeof(TokenSym) + len); table_ident[i] = ts; ts->tok = tok_ident++; ts->sym_define = NULL; ts->sym_label = NULL; ts->sym_struct = NULL; ts->sym_identifier = NULL; ts->len = len; ts->hash_next = NULL; memcpy(ts->str, str, len); ts->str[len] = '\0'; *pts = ts; return ts; } #define TOK_HASH_INIT 1 #define TOK_HASH_FUNC(h, c) ((h) + ((h) << 5) + ((h) >> 27) + (c)) /* find a token and add it if not found */ ST_FUNC TokenSym *tok_alloc(const char *str, int len) { TokenSym *ts, **pts; int i; unsigned int h; h = TOK_HASH_INIT; for(i=0;i<len;i++) h = TOK_HASH_FUNC(h, ((unsigned char *)str)[i]); h &= (TOK_HASH_SIZE - 1); pts = &hash_ident[h]; for(;;) { ts = *pts; if (!ts) break; if (ts->len == len && !memcmp(ts->str, str, len)) return ts; pts = &(ts->hash_next); } return tok_alloc_new(pts, str, len); } /* XXX: buffer overflow */ /* XXX: float tokens */ ST_FUNC const char *get_tok_str(int v, CValue *cv) { char *p; int i, len; cstr_reset(&cstr_buf); p = cstr_buf.data; switch(v) { case TOK_CINT: case TOK_CUINT: case TOK_CLONG: case TOK_CULONG: case TOK_CLLONG: case TOK_CULLONG: /* XXX: not quite exact, but only useful for testing */ #ifdef _WIN32 sprintf(p, "%u", (unsigned)cv->i); #else sprintf(p, "%llu", (unsigned long long)cv->i); #endif break; case TOK_LCHAR: cstr_ccat(&cstr_buf, 'L'); case TOK_CCHAR: cstr_ccat(&cstr_buf, '\''); add_char(&cstr_buf, cv->i); cstr_ccat(&cstr_buf, '\''); cstr_ccat(&cstr_buf, '\0'); break; case TOK_PPNUM: case TOK_PPSTR: return (char*)cv->str.data; case TOK_LSTR: cstr_ccat(&cstr_buf, 'L'); case TOK_STR: cstr_ccat(&cstr_buf, '\"'); if (v == TOK_STR) { len = cv->str.size - 1; for(i=0;i<len;i++) add_char(&cstr_buf, ((unsigned char *)cv->str.data)[i]); } else { len = (cv->str.size / sizeof(nwchar_t)) - 1; for(i=0;i<len;i++) add_char(&cstr_buf, ((nwchar_t *)cv->str.data)[i]); } cstr_ccat(&cstr_buf, '\"'); cstr_ccat(&cstr_buf, '\0'); break; case TOK_CFLOAT: cstr_cat(&cstr_buf, "<float>", 0); break; case TOK_CDOUBLE: cstr_cat(&cstr_buf, "<double>", 0); break; case TOK_CLDOUBLE: cstr_cat(&cstr_buf, "<long double>", 0); break; case TOK_LINENUM: cstr_cat(&cstr_buf, "<linenumber>", 0); break; /* above tokens have value, the ones below don't */ case TOK_LT: v = '<'; goto addv; case TOK_GT: v = '>'; goto addv; case TOK_DOTS: return strcpy(p, "..."); case TOK_A_SHL: return strcpy(p, "<<="); case TOK_A_SAR: return strcpy(p, ">>="); case TOK_EOF: return strcpy(p, "<eof>"); default: if (v < TOK_IDENT) { /* search in two bytes table */ const unsigned char *q = tok_two_chars; while (*q) { if (q[2] == v) { *p++ = q[0]; *p++ = q[1]; *p = '\0'; return cstr_buf.data; } q += 3; } if (v >= 127) { sprintf(cstr_buf.data, "<%02x>", v); return cstr_buf.data; } addv: *p++ = v; *p = '\0'; } else if (v < tok_ident) { return table_ident[v - TOK_IDENT]->str; } else if (v >= SYM_FIRST_ANOM) { /* special name for anonymous symbol */ sprintf(p, "L.%u", v - SYM_FIRST_ANOM); } else { /* should never happen */ return NULL; } break; } return cstr_buf.data; } /* return the current character, handling end of block if necessary (but not stray) */ ST_FUNC int handle_eob(void) { BufferedFile *bf = file; int len; /* only tries to read if really end of buffer */ if (bf->buf_ptr >= bf->buf_end) { if (bf->fd >= 0) { #if defined(PARSE_DEBUG) len = 1; #else len = IO_BUF_SIZE; #endif len = read(bf->fd, bf->buffer, len); if (len < 0) len = 0; } else { len = 0; } total_bytes += len; bf->buf_ptr = bf->buffer; bf->buf_end = bf->buffer + len; *bf->buf_end = CH_EOB; } if (bf->buf_ptr < bf->buf_end) { return bf->buf_ptr[0]; } else { bf->buf_ptr = bf->buf_end; return CH_EOF; } } /* read next char from current input file and handle end of input buffer */ ST_INLN void inp(void) { ch = *(++(file->buf_ptr)); /* end of buffer/file handling */ if (ch == CH_EOB) ch = handle_eob(); } /* handle '\[\r]\n' */ static int handle_stray_noerror(void) { while (ch == '\\') { inp(); if (ch == '\n') { file->line_num++; inp(); } else if (ch == '\r') { inp(); if (ch != '\n') goto fail; file->line_num++; inp(); } else { fail: return 1; } } return 0; } static void handle_stray(void) { if (handle_stray_noerror()) tcc_error("stray '\\' in program"); } /* skip the stray and handle the \\n case. Output an error if incorrect char after the stray */ static int handle_stray1(uint8_t *p) { int c; file->buf_ptr = p; if (p >= file->buf_end) { c = handle_eob(); if (c != '\\') return c; p = file->buf_ptr; } ch = *p; if (handle_stray_noerror()) { if (!(parse_flags & PARSE_FLAG_ACCEPT_STRAYS)) tcc_error("stray '\\' in program"); *--file->buf_ptr = '\\'; } p = file->buf_ptr; c = *p; return c; } /* handle just the EOB case, but not stray */ #define PEEKC_EOB(c, p)\ {\ p++;\ c = *p;\ if (c == '\\') {\ file->buf_ptr = p;\ c = handle_eob();\ p = file->buf_ptr;\ }\ } /* handle the complicated stray case */ #define PEEKC(c, p)\ {\ p++;\ c = *p;\ if (c == '\\') {\ c = handle_stray1(p);\ p = file->buf_ptr;\ }\ } /* input with '\[\r]\n' handling. Note that this function cannot handle other characters after '\', so you cannot call it inside strings or comments */ ST_FUNC void minp(void) { inp(); if (ch == '\\') handle_stray(); } /* single line C++ comments */ static uint8_t *parse_line_comment(uint8_t *p) { int c; p++; for(;;) { c = *p; redo: if (c == '\n' || c == CH_EOF) { break; } else if (c == '\\') { file->buf_ptr = p; c = handle_eob(); p = file->buf_ptr; if (c == '\\') { PEEKC_EOB(c, p); if (c == '\n') { file->line_num++; PEEKC_EOB(c, p); } else if (c == '\r') { PEEKC_EOB(c, p); if (c == '\n') { file->line_num++; PEEKC_EOB(c, p); } } } else { goto redo; } } else { p++; } } return p; } /* C comments */ ST_FUNC uint8_t *parse_comment(uint8_t *p) { int c; p++; for(;;) { /* fast skip loop */ for(;;) { c = *p; if (c == '\n' || c == '*' || c == '\\') break; p++; c = *p; if (c == '\n' || c == '*' || c == '\\') break; p++; } /* now we can handle all the cases */ if (c == '\n') { file->line_num++; p++; } else if (c == '*') { p++; for(;;) { c = *p; if (c == '*') { p++; } else if (c == '/') { goto end_of_comment; } else if (c == '\\') { file->buf_ptr = p; c = handle_eob(); p = file->buf_ptr; if (c == CH_EOF) tcc_error("unexpected end of file in comment"); if (c == '\\') { /* skip '\[\r]\n', otherwise just skip the stray */ while (c == '\\') { PEEKC_EOB(c, p); if (c == '\n') { file->line_num++; PEEKC_EOB(c, p); } else if (c == '\r') { PEEKC_EOB(c, p); if (c == '\n') { file->line_num++; PEEKC_EOB(c, p); } } else { goto after_star; } } } } else { break; } } after_star: ; } else { /* stray, eob or eof */ file->buf_ptr = p; c = handle_eob(); p = file->buf_ptr; if (c == CH_EOF) { tcc_error("unexpected end of file in comment"); } else if (c == '\\') { p++; } } } end_of_comment: p++; return p; } ST_FUNC int set_idnum(int c, int val) { int prev = isidnum_table[c - CH_EOF]; isidnum_table[c - CH_EOF] = val; return prev; } #define cinp minp static inline void skip_spaces(void) { while (isidnum_table[ch - CH_EOF] & IS_SPC) cinp(); } static inline int check_space(int t, int *spc) { if (t < 256 && (isidnum_table[t - CH_EOF] & IS_SPC)) { if (*spc) return 1; *spc = 1; } else *spc = 0; return 0; } /* parse a string without interpreting escapes */ static uint8_t *parse_pp_string(uint8_t *p, int sep, CString *str) { int c; p++; for(;;) { c = *p; if (c == sep) { break; } else if (c == '\\') { file->buf_ptr = p; c = handle_eob(); p = file->buf_ptr; if (c == CH_EOF) { unterminated_string: /* XXX: indicate line number of start of string */ tcc_error("missing terminating %c character", sep); } else if (c == '\\') { /* escape : just skip \[\r]\n */ PEEKC_EOB(c, p); if (c == '\n') { file->line_num++; p++; } else if (c == '\r') { PEEKC_EOB(c, p); if (c != '\n') expect("'\n' after '\r'"); file->line_num++; p++; } else if (c == CH_EOF) { goto unterminated_string; } else { if (str) { cstr_ccat(str, '\\'); cstr_ccat(str, c); } p++; } } } else if (c == '\n') { file->line_num++; goto add_char; } else if (c == '\r') { PEEKC_EOB(c, p); if (c != '\n') { if (str) cstr_ccat(str, '\r'); } else { file->line_num++; goto add_char; } } else { add_char: if (str) cstr_ccat(str, c); p++; } } p++; return p; } /* skip block of text until #else, #elif or #endif. skip also pairs of #if/#endif */ static void preprocess_skip(void) { int a, start_of_line, c, in_warn_or_error; uint8_t *p; p = file->buf_ptr; a = 0; redo_start: start_of_line = 1; in_warn_or_error = 0; for(;;) { redo_no_start: c = *p; switch(c) { case ' ': case '\t': case '\f': case '\v': case '\r': p++; goto redo_no_start; case '\n': file->line_num++; p++; goto redo_start; case '\\': file->buf_ptr = p; c = handle_eob(); if (c == CH_EOF) { expect("#endif"); } else if (c == '\\') { ch = file->buf_ptr[0]; handle_stray_noerror(); } p = file->buf_ptr; goto redo_no_start; /* skip strings */ case '\"': case '\'': if (in_warn_or_error) goto _default; p = parse_pp_string(p, c, NULL); break; /* skip comments */ case '/': if (in_warn_or_error) goto _default; file->buf_ptr = p; ch = *p; minp(); p = file->buf_ptr; if (ch == '*') { p = parse_comment(p); } else if (ch == '/') { p = parse_line_comment(p); } break; case '#': p++; if (start_of_line) { file->buf_ptr = p; next_nomacro(); p = file->buf_ptr; if (a == 0 && (tok == TOK_ELSE || tok == TOK_ELIF || tok == TOK_ENDIF)) goto the_end; if (tok == TOK_IF || tok == TOK_IFDEF || tok == TOK_IFNDEF) a++; else if (tok == TOK_ENDIF) a--; else if( tok == TOK_ERROR || tok == TOK_WARNING) in_warn_or_error = 1; else if (tok == TOK_LINEFEED) goto redo_start; else if (parse_flags & PARSE_FLAG_ASM_FILE) p = parse_line_comment(p - 1); } else if (parse_flags & PARSE_FLAG_ASM_FILE) p = parse_line_comment(p - 1); break; _default: default: p++; break; } start_of_line = 0; } the_end: ; file->buf_ptr = p; } #if 0 /* return the number of additional 'ints' necessary to store the token */ static inline int tok_size(const int *p) { switch(*p) { /* 4 bytes */ case TOK_CINT: case TOK_CUINT: case TOK_CCHAR: case TOK_LCHAR: case TOK_CFLOAT: case TOK_LINENUM: return 1 + 1; case TOK_STR: case TOK_LSTR: case TOK_PPNUM: case TOK_PPSTR: return 1 + ((sizeof(CString) + ((CString *)(p+1))->size + 3) >> 2); case TOK_CLONG: case TOK_CULONG: return 1 + LONG_SIZE / 4; case TOK_CDOUBLE: case TOK_CLLONG: case TOK_CULLONG: return 1 + 2; case TOK_CLDOUBLE: return 1 + LDOUBLE_SIZE / 4; default: return 1 + 0; } } #endif /* token string handling */ ST_INLN void tok_str_new(TokenString *s) { s->str = NULL; s->len = s->lastlen = 0; s->allocated_len = 0; s->last_line_num = -1; } ST_FUNC TokenString *tok_str_alloc(void) { TokenString *str = tal_realloc(tokstr_alloc, 0, sizeof *str); tok_str_new(str); return str; } ST_FUNC int *tok_str_dup(TokenString *s) { int *str; str = tal_realloc(tokstr_alloc, 0, s->len * sizeof(int)); memcpy(str, s->str, s->len * sizeof(int)); return str; } ST_FUNC void tok_str_free_str(int *str) { tal_free(tokstr_alloc, str); } ST_FUNC void tok_str_free(TokenString *str) { tok_str_free_str(str->str); tal_free(tokstr_alloc, str); } ST_FUNC int *tok_str_realloc(TokenString *s, int new_size) { int *str, size; size = s->allocated_len; if (size < 16) size = 16; while (size < new_size) size = size * 2; if (size > s->allocated_len) { str = tal_realloc(tokstr_alloc, s->str, size * sizeof(int)); s->allocated_len = size; s->str = str; } return s->str; } ST_FUNC void tok_str_add(TokenString *s, int t) { int len, *str; len = s->len; str = s->str; if (len >= s->allocated_len) str = tok_str_realloc(s, len + 1); str[len++] = t; s->len = len; } ST_FUNC void begin_macro(TokenString *str, int alloc) { str->alloc = alloc; str->prev = macro_stack; str->prev_ptr = macro_ptr; str->save_line_num = file->line_num; macro_ptr = str->str; macro_stack = str; } ST_FUNC void end_macro(void) { TokenString *str = macro_stack; macro_stack = str->prev; macro_ptr = str->prev_ptr; file->line_num = str->save_line_num; if (str->alloc == 2) { str->alloc = 3; /* just mark as finished */ } else { tok_str_free(str); } } static void tok_str_add2(TokenString *s, int t, CValue *cv) { int len, *str; len = s->lastlen = s->len; str = s->str; /* allocate space for worst case */ if (len + TOK_MAX_SIZE >= s->allocated_len) str = tok_str_realloc(s, len + TOK_MAX_SIZE + 1); str[len++] = t; switch(t) { case TOK_CINT: case TOK_CUINT: case TOK_CCHAR: case TOK_LCHAR: case TOK_CFLOAT: case TOK_LINENUM: #if LONG_SIZE == 4 case TOK_CLONG: case TOK_CULONG: #endif str[len++] = cv->tab[0]; break; case TOK_PPNUM: case TOK_PPSTR: case TOK_STR: case TOK_LSTR: { /* Insert the string into the int array. */ size_t nb_words = 1 + (cv->str.size + sizeof(int) - 1) / sizeof(int); if (len + nb_words >= s->allocated_len) str = tok_str_realloc(s, len + nb_words + 1); str[len] = cv->str.size; memcpy(&str[len + 1], cv->str.data, cv->str.size); len += nb_words; } break; case TOK_CDOUBLE: case TOK_CLLONG: case TOK_CULLONG: #if LONG_SIZE == 8 case TOK_CLONG: case TOK_CULONG: #endif #if LDOUBLE_SIZE == 8 case TOK_CLDOUBLE: #endif str[len++] = cv->tab[0]; str[len++] = cv->tab[1]; break; #if LDOUBLE_SIZE == 12 case TOK_CLDOUBLE: str[len++] = cv->tab[0]; str[len++] = cv->tab[1]; str[len++] = cv->tab[2]; #elif LDOUBLE_SIZE == 16 case TOK_CLDOUBLE: str[len++] = cv->tab[0]; str[len++] = cv->tab[1]; str[len++] = cv->tab[2]; str[len++] = cv->tab[3]; #elif LDOUBLE_SIZE != 8 #error add long double size support #endif break; default: break; } s->len = len; } /* add the current parse token in token string 's' */ ST_FUNC void tok_str_add_tok(TokenString *s) { CValue cval; /* save line number info */ if (file->line_num != s->last_line_num) { s->last_line_num = file->line_num; cval.i = s->last_line_num; tok_str_add2(s, TOK_LINENUM, &cval); } tok_str_add2(s, tok, &tokc); } /* get a token from an integer array and increment pointer accordingly. we code it as a macro to avoid pointer aliasing. */ static inline void TOK_GET(int *t, const int **pp, CValue *cv) { const int *p = *pp; int n, *tab; tab = cv->tab; switch(*t = *p++) { #if LONG_SIZE == 4 case TOK_CLONG: #endif case TOK_CINT: case TOK_CCHAR: case TOK_LCHAR: case TOK_LINENUM: cv->i = *p++; break; #if LONG_SIZE == 4 case TOK_CULONG: #endif case TOK_CUINT: cv->i = (unsigned)*p++; break; case TOK_CFLOAT: tab[0] = *p++; break; case TOK_STR: case TOK_LSTR: case TOK_PPNUM: case TOK_PPSTR: cv->str.size = *p++; cv->str.data = p; p += (cv->str.size + sizeof(int) - 1) / sizeof(int); break; case TOK_CDOUBLE: case TOK_CLLONG: case TOK_CULLONG: #if LONG_SIZE == 8 case TOK_CLONG: case TOK_CULONG: #endif n = 2; goto copy; case TOK_CLDOUBLE: #if LDOUBLE_SIZE == 16 n = 4; #elif LDOUBLE_SIZE == 12 n = 3; #elif LDOUBLE_SIZE == 8 n = 2; #else # error add long double size support #endif copy: do *tab++ = *p++; while (--n); break; default: break; } *pp = p; } static int macro_is_equal(const int *a, const int *b) { CValue cv; int t; if (!a || !b) return 1; while (*a && *b) { /* first time preallocate macro_equal_buf, next time only reset position to start */ cstr_reset(¯o_equal_buf); TOK_GET(&t, &a, &cv); cstr_cat(¯o_equal_buf, get_tok_str(t, &cv), 0); TOK_GET(&t, &b, &cv); if (strcmp(macro_equal_buf.data, get_tok_str(t, &cv))) return 0; } return !(*a || *b); } /* defines handling */ ST_INLN void define_push(int v, int macro_type, int *str, Sym *first_arg) { Sym *s, *o; o = define_find(v); s = sym_push2(&define_stack, v, macro_type, 0); s->d = str; s->next = first_arg; table_ident[v - TOK_IDENT]->sym_define = s; if (o && !macro_is_equal(o->d, s->d)) tcc_warning("%s redefined", get_tok_str(v, NULL)); } /* undefined a define symbol. Its name is just set to zero */ ST_FUNC void define_undef(Sym *s) { int v = s->v; if (v >= TOK_IDENT && v < tok_ident) table_ident[v - TOK_IDENT]->sym_define = NULL; } ST_INLN Sym *define_find(int v) { v -= TOK_IDENT; if ((unsigned)v >= (unsigned)(tok_ident - TOK_IDENT)) return NULL; return table_ident[v]->sym_define; } /* free define stack until top reaches 'b' */ ST_FUNC void free_defines(Sym *b) { while (define_stack != b) { Sym *top = define_stack; define_stack = top->prev; tok_str_free_str(top->d); define_undef(top); sym_free(top); } /* restore remaining (-D or predefined) symbols if they were #undef'd in the file */ while (b) { int v = b->v; if (v >= TOK_IDENT && v < tok_ident) { Sym **d = &table_ident[v - TOK_IDENT]->sym_define; if (!*d) *d = b; } b = b->prev; } } /* label lookup */ ST_FUNC Sym *label_find(int v) { v -= TOK_IDENT; if ((unsigned)v >= (unsigned)(tok_ident - TOK_IDENT)) return NULL; return table_ident[v]->sym_label; } ST_FUNC Sym *label_push(Sym **ptop, int v, int flags) { Sym *s, **ps; s = sym_push2(ptop, v, 0, 0); s->r = flags; ps = &table_ident[v - TOK_IDENT]->sym_label; if (ptop == &global_label_stack) { /* modify the top most local identifier, so that sym_identifier will point to 's' when popped */ while (*ps != NULL) ps = &(*ps)->prev_tok; } s->prev_tok = *ps; *ps = s; return s; } /* pop labels until element last is reached. Look if any labels are undefined. Define symbols if '&&label' was used. */ ST_FUNC void label_pop(Sym **ptop, Sym *slast, int keep) { Sym *s, *s1; for(s = *ptop; s != slast; s = s1) { s1 = s->prev; if (s->r == LABEL_DECLARED) { tcc_warning("label '%s' declared but not used", get_tok_str(s->v, NULL)); } else if (s->r == LABEL_FORWARD) { tcc_error("label '%s' used but not defined", get_tok_str(s->v, NULL)); } else { if (s->c) { /* define corresponding symbol. A size of 1 is put. */ put_extern_sym(s, cur_text_section, s->jnext, 1); } } /* remove label */ table_ident[s->v - TOK_IDENT]->sym_label = s->prev_tok; if (!keep) sym_free(s); } if (!keep) *ptop = slast; } /* fake the nth "#if defined test_..." for tcc -dt -run */ static void maybe_run_test(TCCState *s) { const char *p; if (s->include_stack_ptr != s->include_stack) return; p = get_tok_str(tok, NULL); if (0 != memcmp(p, "test_", 5)) return; if (0 != --s->run_test) return; fprintf(s->ppfp, "\n[%s]\n" + !(s->dflag & 32), p), fflush(s->ppfp); define_push(tok, MACRO_OBJ, NULL, NULL); } /* eval an expression for #if/#elif */ static int expr_preprocess(void) { int c, t; TokenString *str; str = tok_str_alloc(); pp_expr = 1; while (tok != TOK_LINEFEED && tok != TOK_EOF) { next(); /* do macro subst */ if (tok == TOK_DEFINED) { next_nomacro(); t = tok; if (t == '(') next_nomacro(); if (tok < TOK_IDENT) expect("identifier"); if (tcc_state->run_test) maybe_run_test(tcc_state); c = define_find(tok) != 0; if (t == '(') { next_nomacro(); if (tok != ')') expect("')'"); } tok = TOK_CINT; tokc.i = c; } else if (tok >= TOK_IDENT) { /* if undefined macro */ tok = TOK_CINT; tokc.i = 0; } tok_str_add_tok(str); } pp_expr = 0; tok_str_add(str, -1); /* simulate end of file */ tok_str_add(str, 0); /* now evaluate C constant expression */ begin_macro(str, 1); next(); c = expr_const(); end_macro(); return c != 0; } /* parse after #define */ ST_FUNC void parse_define(void) { Sym *s, *first, **ps; int v, t, varg, is_vaargs, spc; int saved_parse_flags = parse_flags; v = tok; if (v < TOK_IDENT || v == TOK_DEFINED) tcc_error("invalid macro name '%s'", get_tok_str(tok, &tokc)); /* XXX: should check if same macro (ANSI) */ first = NULL; t = MACRO_OBJ; /* We have to parse the whole define as if not in asm mode, in particular no line comment with '#' must be ignored. Also for function macros the argument list must be parsed without '.' being an ID character. */ parse_flags = ((parse_flags & ~PARSE_FLAG_ASM_FILE) | PARSE_FLAG_SPACES); /* '(' must be just after macro definition for MACRO_FUNC */ next_nomacro_spc(); if (tok == '(') { int dotid = set_idnum('.', 0); next_nomacro(); ps = &first; if (tok != ')') for (;;) { varg = tok; next_nomacro(); is_vaargs = 0; if (varg == TOK_DOTS) { varg = TOK___VA_ARGS__; is_vaargs = 1; } else if (tok == TOK_DOTS && gnu_ext) { is_vaargs = 1; next_nomacro(); } if (varg < TOK_IDENT) bad_list: tcc_error("bad macro parameter list"); s = sym_push2(&define_stack, varg | SYM_FIELD, is_vaargs, 0); *ps = s; ps = &s->next; if (tok == ')') break; if (tok != ',' || is_vaargs) goto bad_list; next_nomacro(); } next_nomacro_spc(); t = MACRO_FUNC; set_idnum('.', dotid); } tokstr_buf.len = 0; spc = 2; parse_flags |= PARSE_FLAG_ACCEPT_STRAYS | PARSE_FLAG_SPACES | PARSE_FLAG_LINEFEED; /* The body of a macro definition should be parsed such that identifiers are parsed like the file mode determines (i.e. with '.' being an ID character in asm mode). But '#' should be retained instead of regarded as line comment leader, so still don't set ASM_FILE in parse_flags. */ while (tok != TOK_LINEFEED && tok != TOK_EOF) { /* remove spaces around ## and after '#' */ if (TOK_TWOSHARPS == tok) { if (2 == spc) goto bad_twosharp; if (1 == spc) --tokstr_buf.len; spc = 3; tok = TOK_PPJOIN; } else if ('#' == tok) { spc = 4; } else if (check_space(tok, &spc)) { goto skip; } tok_str_add2(&tokstr_buf, tok, &tokc); skip: next_nomacro_spc(); } parse_flags = saved_parse_flags; if (spc == 1) --tokstr_buf.len; /* remove trailing space */ tok_str_add(&tokstr_buf, 0); if (3 == spc) bad_twosharp: tcc_error("'##' cannot appear at either end of macro"); define_push(v, t, tok_str_dup(&tokstr_buf), first); } static CachedInclude *search_cached_include(TCCState *s1, const char *filename, int add) { const unsigned char *s; unsigned int h; CachedInclude *e; int i; h = TOK_HASH_INIT; s = (unsigned char *) filename; while (*s) { #ifdef _WIN32 h = TOK_HASH_FUNC(h, toup(*s)); #else h = TOK_HASH_FUNC(h, *s); #endif s++; } h &= (CACHED_INCLUDES_HASH_SIZE - 1); i = s1->cached_includes_hash[h]; for(;;) { if (i == 0) break; e = s1->cached_includes[i - 1]; if (0 == PATHCMP(e->filename, filename)) return e; i = e->hash_next; } if (!add) return NULL; e = tcc_malloc(sizeof(CachedInclude) + strlen(filename)); strcpy(e->filename, filename); e->ifndef_macro = e->once = 0; dynarray_add(&s1->cached_includes, &s1->nb_cached_includes, e); /* add in hash table */ e->hash_next = s1->cached_includes_hash[h]; s1->cached_includes_hash[h] = s1->nb_cached_includes; #ifdef INC_DEBUG printf("adding cached '%s'\n", filename); #endif return e; } static void pragma_parse(TCCState *s1) { next_nomacro(); if (tok == TOK_push_macro || tok == TOK_pop_macro) { int t = tok, v; Sym *s; if (next(), tok != '(') goto pragma_err; if (next(), tok != TOK_STR) goto pragma_err; v = tok_alloc(tokc.str.data, tokc.str.size - 1)->tok; if (next(), tok != ')') goto pragma_err; if (t == TOK_push_macro) { while (NULL == (s = define_find(v))) define_push(v, 0, NULL, NULL); s->type.ref = s; /* set push boundary */ } else { for (s = define_stack; s; s = s->prev) if (s->v == v && s->type.ref == s) { s->type.ref = NULL; break; } } if (s) table_ident[v - TOK_IDENT]->sym_define = s->d ? s : NULL; else tcc_warning("unbalanced #pragma pop_macro"); pp_debug_tok = t, pp_debug_symv = v; } else if (tok == TOK_once) { search_cached_include(s1, file->filename, 1)->once = pp_once; } else if (s1->output_type == TCC_OUTPUT_PREPROCESS) { /* tcc -E: keep pragmas below unchanged */ unget_tok(' '); unget_tok(TOK_PRAGMA); unget_tok('#'); unget_tok(TOK_LINEFEED); } else if (tok == TOK_pack) { /* This may be: #pragma pack(1) // set #pragma pack() // reset to default #pragma pack(push,1) // push & set #pragma pack(pop) // restore previous */ next(); skip('('); if (tok == TOK_ASM_pop) { next(); if (s1->pack_stack_ptr <= s1->pack_stack) { stk_error: tcc_error("out of pack stack"); } s1->pack_stack_ptr--; } else { int val = 0; if (tok != ')') { if (tok == TOK_ASM_push) { next(); if (s1->pack_stack_ptr >= s1->pack_stack + PACK_STACK_SIZE - 1) goto stk_error; s1->pack_stack_ptr++; skip(','); } if (tok != TOK_CINT) goto pragma_err; val = tokc.i; if (val < 1 || val > 16 || (val & (val - 1)) != 0) goto pragma_err; next(); } *s1->pack_stack_ptr = val; } if (tok != ')') goto pragma_err; } else if (tok == TOK_comment) { char *p; int t; next(); skip('('); t = tok; next(); skip(','); if (tok != TOK_STR) goto pragma_err; p = tcc_strdup((char *)tokc.str.data); next(); if (tok != ')') goto pragma_err; if (t == TOK_lib) { dynarray_add(&s1->pragma_libs, &s1->nb_pragma_libs, p); } else { if (t == TOK_option) tcc_set_options(s1, p); tcc_free(p); } } else if (s1->warn_unsupported) { tcc_warning("#pragma %s is ignored", get_tok_str(tok, &tokc)); } return; pragma_err: tcc_error("malformed #pragma directive"); return; } /* is_bof is true if first non space token at beginning of file */ ST_FUNC void preprocess(int is_bof) { TCCState *s1 = tcc_state; int i, c, n, saved_parse_flags; char buf[1024], *q; Sym *s; saved_parse_flags = parse_flags; parse_flags = PARSE_FLAG_PREPROCESS | PARSE_FLAG_TOK_NUM | PARSE_FLAG_TOK_STR | PARSE_FLAG_LINEFEED | (parse_flags & PARSE_FLAG_ASM_FILE) ; next_nomacro(); redo: switch(tok) { case TOK_DEFINE: pp_debug_tok = tok; next_nomacro(); pp_debug_symv = tok; parse_define(); break; case TOK_UNDEF: pp_debug_tok = tok; next_nomacro(); pp_debug_symv = tok; s = define_find(tok); /* undefine symbol by putting an invalid name */ if (s) define_undef(s); break; case TOK_INCLUDE: case TOK_INCLUDE_NEXT: ch = file->buf_ptr[0]; /* XXX: incorrect if comments : use next_nomacro with a special mode */ skip_spaces(); if (ch == '<') { c = '>'; goto read_name; } else if (ch == '\"') { c = ch; read_name: inp(); q = buf; while (ch != c && ch != '\n' && ch != CH_EOF) { if ((q - buf) < sizeof(buf) - 1) *q++ = ch; if (ch == '\\') { if (handle_stray_noerror() == 0) --q; } else inp(); } *q = '\0'; minp(); #if 0 /* eat all spaces and comments after include */ /* XXX: slightly incorrect */ while (ch1 != '\n' && ch1 != CH_EOF) inp(); #endif } else { int len; /* computed #include : concatenate everything up to linefeed, the result must be one of the two accepted forms. Don't convert pp-tokens to tokens here. */ parse_flags = (PARSE_FLAG_PREPROCESS | PARSE_FLAG_LINEFEED | (parse_flags & PARSE_FLAG_ASM_FILE)); next(); buf[0] = '\0'; while (tok != TOK_LINEFEED) { pstrcat(buf, sizeof(buf), get_tok_str(tok, &tokc)); next(); } len = strlen(buf); /* check syntax and remove '<>|""' */ if ((len < 2 || ((buf[0] != '"' || buf[len-1] != '"') && (buf[0] != '<' || buf[len-1] != '>')))) tcc_error("'#include' expects \"FILENAME\" or <FILENAME>"); c = buf[len-1]; memmove(buf, buf + 1, len - 2); buf[len - 2] = '\0'; } if (s1->include_stack_ptr >= s1->include_stack + INCLUDE_STACK_SIZE) tcc_error("#include recursion too deep"); /* store current file in stack, but increment stack later below */ *s1->include_stack_ptr = file; i = tok == TOK_INCLUDE_NEXT ? file->include_next_index : 0; n = 2 + s1->nb_include_paths + s1->nb_sysinclude_paths; for (; i < n; ++i) { char buf1[sizeof file->filename]; CachedInclude *e; const char *path; if (i == 0) { /* check absolute include path */ if (!IS_ABSPATH(buf)) continue; buf1[0] = 0; } else if (i == 1) { /* search in file's dir if "header.h" */ if (c != '\"') continue; /* https://savannah.nongnu.org/bugs/index.php?50847 */ path = file->true_filename; pstrncpy(buf1, path, tcc_basename(path) - path); } else { /* search in all the include paths */ int j = i - 2, k = j - s1->nb_include_paths; path = k < 0 ? s1->include_paths[j] : s1->sysinclude_paths[k]; pstrcpy(buf1, sizeof(buf1), path); pstrcat(buf1, sizeof(buf1), "/"); } pstrcat(buf1, sizeof(buf1), buf); e = search_cached_include(s1, buf1, 0); if (e && (define_find(e->ifndef_macro) || e->once == pp_once)) { /* no need to parse the include because the 'ifndef macro' is defined (or had #pragma once) */ #ifdef INC_DEBUG printf("%s: skipping cached %s\n", file->filename, buf1); #endif goto include_done; } if (tcc_open(s1, buf1) < 0) continue; file->include_next_index = i + 1; #ifdef INC_DEBUG printf("%s: including %s\n", file->prev->filename, file->filename); #endif /* update target deps */ dynarray_add(&s1->target_deps, &s1->nb_target_deps, tcc_strdup(buf1)); /* push current file in stack */ ++s1->include_stack_ptr; /* add include file debug info */ if (s1->do_debug) put_stabs(file->filename, N_BINCL, 0, 0, 0); tok_flags |= TOK_FLAG_BOF | TOK_FLAG_BOL; ch = file->buf_ptr[0]; goto the_end; } tcc_error("include file '%s' not found", buf); include_done: break; case TOK_IFNDEF: c = 1; goto do_ifdef; case TOK_IF: c = expr_preprocess(); goto do_if; case TOK_IFDEF: c = 0; do_ifdef: next_nomacro(); if (tok < TOK_IDENT) tcc_error("invalid argument for '#if%sdef'", c ? "n" : ""); if (is_bof) { if (c) { #ifdef INC_DEBUG printf("#ifndef %s\n", get_tok_str(tok, NULL)); #endif file->ifndef_macro = tok; } } c = (define_find(tok) != 0) ^ c; do_if: if (s1->ifdef_stack_ptr >= s1->ifdef_stack + IFDEF_STACK_SIZE) tcc_error("memory full (ifdef)"); *s1->ifdef_stack_ptr++ = c; goto test_skip; case TOK_ELSE: if (s1->ifdef_stack_ptr == s1->ifdef_stack) tcc_error("#else without matching #if"); if (s1->ifdef_stack_ptr[-1] & 2) tcc_error("#else after #else"); c = (s1->ifdef_stack_ptr[-1] ^= 3); goto test_else; case TOK_ELIF: if (s1->ifdef_stack_ptr == s1->ifdef_stack) tcc_error("#elif without matching #if"); c = s1->ifdef_stack_ptr[-1]; if (c > 1) tcc_error("#elif after #else"); /* last #if/#elif expression was true: we skip */ if (c == 1) { c = 0; } else { c = expr_preprocess(); s1->ifdef_stack_ptr[-1] = c; } test_else: if (s1->ifdef_stack_ptr == file->ifdef_stack_ptr + 1) file->ifndef_macro = 0; test_skip: if (!(c & 1)) { preprocess_skip(); is_bof = 0; goto redo; } break; case TOK_ENDIF: if (s1->ifdef_stack_ptr <= file->ifdef_stack_ptr) tcc_error("#endif without matching #if"); s1->ifdef_stack_ptr--; /* '#ifndef macro' was at the start of file. Now we check if an '#endif' is exactly at the end of file */ if (file->ifndef_macro && s1->ifdef_stack_ptr == file->ifdef_stack_ptr) { file->ifndef_macro_saved = file->ifndef_macro; /* need to set to zero to avoid false matches if another #ifndef at middle of file */ file->ifndef_macro = 0; while (tok != TOK_LINEFEED) next_nomacro(); tok_flags |= TOK_FLAG_ENDIF; goto the_end; } break; case TOK_PPNUM: n = strtoul((char*)tokc.str.data, &q, 10); goto _line_num; case TOK_LINE: next(); if (tok != TOK_CINT) _line_err: tcc_error("wrong #line format"); n = tokc.i; _line_num: next(); if (tok != TOK_LINEFEED) { if (tok == TOK_STR) { if (file->true_filename == file->filename) file->true_filename = tcc_strdup(file->filename); pstrcpy(file->filename, sizeof(file->filename), (char *)tokc.str.data); } else if (parse_flags & PARSE_FLAG_ASM_FILE) break; else goto _line_err; --n; } if (file->fd > 0) total_lines += file->line_num - n; file->line_num = n; if (s1->do_debug) put_stabs(file->filename, N_BINCL, 0, 0, 0); break; case TOK_ERROR: case TOK_WARNING: c = tok; ch = file->buf_ptr[0]; skip_spaces(); q = buf; while (ch != '\n' && ch != CH_EOF) { if ((q - buf) < sizeof(buf) - 1) *q++ = ch; if (ch == '\\') { if (handle_stray_noerror() == 0) --q; } else inp(); } *q = '\0'; if (c == TOK_ERROR) tcc_error("#error %s", buf); else tcc_warning("#warning %s", buf); break; case TOK_PRAGMA: pragma_parse(s1); break; case TOK_LINEFEED: goto the_end; default: /* ignore gas line comment in an 'S' file. */ if (saved_parse_flags & PARSE_FLAG_ASM_FILE) goto ignore; if (tok == '!' && is_bof) /* '!' is ignored at beginning to allow C scripts. */ goto ignore; tcc_warning("Ignoring unknown preprocessing directive #%s", get_tok_str(tok, &tokc)); ignore: file->buf_ptr = parse_line_comment(file->buf_ptr - 1); goto the_end; } /* ignore other preprocess commands or #! for C scripts */ while (tok != TOK_LINEFEED) next_nomacro(); the_end: parse_flags = saved_parse_flags; } /* evaluate escape codes in a string. */ static void parse_escape_string(CString *outstr, const uint8_t *buf, int is_long) { int c, n; const uint8_t *p; p = buf; for(;;) { c = *p; if (c == '\0') break; if (c == '\\') { p++; /* escape */ c = *p; switch(c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': /* at most three octal digits */ n = c - '0'; p++; c = *p; if (isoct(c)) { n = n * 8 + c - '0'; p++; c = *p; if (isoct(c)) { n = n * 8 + c - '0'; p++; } } c = n; goto add_char_nonext; case 'x': case 'u': case 'U': p++; n = 0; for(;;) { c = *p; if (c >= 'a' && c <= 'f') c = c - 'a' + 10; else if (c >= 'A' && c <= 'F') c = c - 'A' + 10; else if (isnum(c)) c = c - '0'; else break; n = n * 16 + c; p++; } c = n; goto add_char_nonext; case 'a': c = '\a'; break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; case 'e': if (!gnu_ext) goto invalid_escape; c = 27; break; case '\'': case '\"': case '\\': case '?': break; default: invalid_escape: if (c >= '!' && c <= '~') tcc_warning("unknown escape sequence: \'\\%c\'", c); else tcc_warning("unknown escape sequence: \'\\x%x\'", c); break; } } else if (is_long && c >= 0x80) { /* assume we are processing UTF-8 sequence */ /* reference: The Unicode Standard, Version 10.0, ch3.9 */ int cont; /* count of continuation bytes */ int skip; /* how many bytes should skip when error occurred */ int i; /* decode leading byte */ if (c < 0xC2) { skip = 1; goto invalid_utf8_sequence; } else if (c <= 0xDF) { cont = 1; n = c & 0x1f; } else if (c <= 0xEF) { cont = 2; n = c & 0xf; } else if (c <= 0xF4) { cont = 3; n = c & 0x7; } else { skip = 1; goto invalid_utf8_sequence; } /* decode continuation bytes */ for (i = 1; i <= cont; i++) { int l = 0x80, h = 0xBF; /* adjust limit for second byte */ if (i == 1) { switch (c) { case 0xE0: l = 0xA0; break; case 0xED: h = 0x9F; break; case 0xF0: l = 0x90; break; case 0xF4: h = 0x8F; break; } } if (p[i] < l || p[i] > h) { skip = i; goto invalid_utf8_sequence; } n = (n << 6) | (p[i] & 0x3f); } /* advance pointer */ p += 1 + cont; c = n; goto add_char_nonext; /* error handling */ invalid_utf8_sequence: tcc_warning("ill-formed UTF-8 subsequence starting with: \'\\x%x\'", c); c = 0xFFFD; p += skip; goto add_char_nonext; } p++; add_char_nonext: if (!is_long) cstr_ccat(outstr, c); else { #ifdef TCC_TARGET_PE /* store as UTF-16 */ if (c < 0x10000) { cstr_wccat(outstr, c); } else { c -= 0x10000; cstr_wccat(outstr, (c >> 10) + 0xD800); cstr_wccat(outstr, (c & 0x3FF) + 0xDC00); } #else cstr_wccat(outstr, c); #endif } } /* add a trailing '\0' */ if (!is_long) cstr_ccat(outstr, '\0'); else cstr_wccat(outstr, '\0'); } static void parse_string(const char *s, int len) { uint8_t buf[1000], *p = buf; int is_long, sep; if ((is_long = *s == 'L')) ++s, --len; sep = *s++; len -= 2; if (len >= sizeof buf) p = tcc_malloc(len + 1); memcpy(p, s, len); p[len] = 0; cstr_reset(&tokcstr); parse_escape_string(&tokcstr, p, is_long); if (p != buf) tcc_free(p); if (sep == '\'') { int char_size, i, n, c; /* XXX: make it portable */ if (!is_long) tok = TOK_CCHAR, char_size = 1; else tok = TOK_LCHAR, char_size = sizeof(nwchar_t); n = tokcstr.size / char_size - 1; if (n < 1) tcc_error("empty character constant"); if (n > 1) tcc_warning("multi-character character constant"); for (c = i = 0; i < n; ++i) { if (is_long) c = ((nwchar_t *)tokcstr.data)[i]; else c = (c << 8) | ((char *)tokcstr.data)[i]; } tokc.i = c; } else { tokc.str.size = tokcstr.size; tokc.str.data = tokcstr.data; if (!is_long) tok = TOK_STR; else tok = TOK_LSTR; } } /* we use 64 bit numbers */ #define BN_SIZE 2 /* bn = (bn << shift) | or_val */ static void bn_lshift(unsigned int *bn, int shift, int or_val) { int i; unsigned int v; for(i=0;i<BN_SIZE;i++) { v = bn[i]; bn[i] = (v << shift) | or_val; or_val = v >> (32 - shift); } } static void bn_zero(unsigned int *bn) { int i; for(i=0;i<BN_SIZE;i++) { bn[i] = 0; } } /* parse number in null terminated string 'p' and return it in the current token */ static void parse_number(const char *p) { int b, t, shift, frac_bits, s, exp_val, ch; char *q; unsigned int bn[BN_SIZE]; double d; /* number */ q = token_buf; ch = *p++; t = ch; ch = *p++; *q++ = t; b = 10; if (t == '.') { goto float_frac_parse; } else if (t == '0') { if (ch == 'x' || ch == 'X') { q--; ch = *p++; b = 16; } else if (tcc_ext && (ch == 'b' || ch == 'B')) { q--; ch = *p++; b = 2; } } /* parse all digits. cannot check octal numbers at this stage because of floating point constants */ while (1) { if (ch >= 'a' && ch <= 'f') t = ch - 'a' + 10; else if (ch >= 'A' && ch <= 'F') t = ch - 'A' + 10; else if (isnum(ch)) t = ch - '0'; else break; if (t >= b) break; if (q >= token_buf + STRING_MAX_SIZE) { num_too_long: tcc_error("number too long"); } *q++ = ch; ch = *p++; } if (ch == '.' || ((ch == 'e' || ch == 'E') && b == 10) || ((ch == 'p' || ch == 'P') && (b == 16 || b == 2))) { if (b != 10) { /* NOTE: strtox should support that for hexa numbers, but non ISOC99 libcs do not support it, so we prefer to do it by hand */ /* hexadecimal or binary floats */ /* XXX: handle overflows */ *q = '\0'; if (b == 16) shift = 4; else shift = 1; bn_zero(bn); q = token_buf; while (1) { t = *q++; if (t == '\0') { break; } else if (t >= 'a') { t = t - 'a' + 10; } else if (t >= 'A') { t = t - 'A' + 10; } else { t = t - '0'; } bn_lshift(bn, shift, t); } frac_bits = 0; if (ch == '.') { ch = *p++; while (1) { t = ch; if (t >= 'a' && t <= 'f') { t = t - 'a' + 10; } else if (t >= 'A' && t <= 'F') { t = t - 'A' + 10; } else if (t >= '0' && t <= '9') { t = t - '0'; } else { break; } if (t >= b) tcc_error("invalid digit"); bn_lshift(bn, shift, t); frac_bits += shift; ch = *p++; } } if (ch != 'p' && ch != 'P') expect("exponent"); ch = *p++; s = 1; exp_val = 0; if (ch == '+') { ch = *p++; } else if (ch == '-') { s = -1; ch = *p++; } if (ch < '0' || ch > '9') expect("exponent digits"); while (ch >= '0' && ch <= '9') { exp_val = exp_val * 10 + ch - '0'; ch = *p++; } exp_val = exp_val * s; /* now we can generate the number */ /* XXX: should patch directly float number */ d = (double)bn[1] * 4294967296.0 + (double)bn[0]; d = ldexp(d, exp_val - frac_bits); t = toup(ch); if (t == 'F') { ch = *p++; tok = TOK_CFLOAT; /* float : should handle overflow */ tokc.f = (float)d; } else if (t == 'L') { ch = *p++; #ifdef TCC_TARGET_PE tok = TOK_CDOUBLE; tokc.d = d; #else tok = TOK_CLDOUBLE; /* XXX: not large enough */ tokc.ld = (long double)d; #endif } else { tok = TOK_CDOUBLE; tokc.d = d; } } else { /* decimal floats */ if (ch == '.') { if (q >= token_buf + STRING_MAX_SIZE) goto num_too_long; *q++ = ch; ch = *p++; float_frac_parse: while (ch >= '0' && ch <= '9') { if (q >= token_buf + STRING_MAX_SIZE) goto num_too_long; *q++ = ch; ch = *p++; } } if (ch == 'e' || ch == 'E') { if (q >= token_buf + STRING_MAX_SIZE) goto num_too_long; *q++ = ch; ch = *p++; if (ch == '-' || ch == '+') { if (q >= token_buf + STRING_MAX_SIZE) goto num_too_long; *q++ = ch; ch = *p++; } if (ch < '0' || ch > '9') expect("exponent digits"); while (ch >= '0' && ch <= '9') { if (q >= token_buf + STRING_MAX_SIZE) goto num_too_long; *q++ = ch; ch = *p++; } } *q = '\0'; t = toup(ch); errno = 0; if (t == 'F') { ch = *p++; tok = TOK_CFLOAT; tokc.f = strtof(token_buf, NULL); } else if (t == 'L') { ch = *p++; #ifdef TCC_TARGET_PE tok = TOK_CDOUBLE; tokc.d = strtod(token_buf, NULL); #else tok = TOK_CLDOUBLE; tokc.ld = strtold(token_buf, NULL); #endif } else { tok = TOK_CDOUBLE; tokc.d = strtod(token_buf, NULL); } } } else { unsigned long long n, n1; int lcount, ucount, ov = 0; const char *p1; /* integer number */ *q = '\0'; q = token_buf; if (b == 10 && *q == '0') { b = 8; q++; } n = 0; while(1) { t = *q++; /* no need for checks except for base 10 / 8 errors */ if (t == '\0') break; else if (t >= 'a') t = t - 'a' + 10; else if (t >= 'A') t = t - 'A' + 10; else t = t - '0'; if (t >= b) tcc_error("invalid digit"); n1 = n; n = n * b + t; /* detect overflow */ if (n1 >= 0x1000000000000000ULL && n / b != n1) ov = 1; } /* Determine the characteristics (unsigned and/or 64bit) the type of the constant must have according to the constant suffix(es) */ lcount = ucount = 0; p1 = p; for(;;) { t = toup(ch); if (t == 'L') { if (lcount >= 2) tcc_error("three 'l's in integer constant"); if (lcount && *(p - 1) != ch) tcc_error("incorrect integer suffix: %s", p1); lcount++; ch = *p++; } else if (t == 'U') { if (ucount >= 1) tcc_error("two 'u's in integer constant"); ucount++; ch = *p++; } else { break; } } /* Determine if it needs 64 bits and/or unsigned in order to fit */ if (ucount == 0 && b == 10) { if (lcount <= (LONG_SIZE == 4)) { if (n >= 0x80000000U) lcount = (LONG_SIZE == 4) + 1; } if (n >= 0x8000000000000000ULL) ov = 1, ucount = 1; } else { if (lcount <= (LONG_SIZE == 4)) { if (n >= 0x100000000ULL) lcount = (LONG_SIZE == 4) + 1; else if (n >= 0x80000000U) ucount = 1; } if (n >= 0x8000000000000000ULL) ucount = 1; } if (ov) tcc_warning("integer constant overflow"); tok = TOK_CINT; if (lcount) { tok = TOK_CLONG; if (lcount == 2) tok = TOK_CLLONG; } if (ucount) ++tok; /* TOK_CU... */ tokc.i = n; } if (ch) tcc_error("invalid number\n"); } #define PARSE2(c1, tok1, c2, tok2) \ case c1: \ PEEKC(c, p); \ if (c == c2) { \ p++; \ tok = tok2; \ } else { \ tok = tok1; \ } \ break; /* return next token without macro substitution */ static inline void next_nomacro1(void) { int t, c, is_long, len; TokenSym *ts; uint8_t *p, *p1; unsigned int h; p = file->buf_ptr; redo_no_start: c = *p; switch(c) { case ' ': case '\t': tok = c; p++; if (parse_flags & PARSE_FLAG_SPACES) goto keep_tok_flags; while (isidnum_table[*p - CH_EOF] & IS_SPC) ++p; goto redo_no_start; case '\f': case '\v': case '\r': p++; goto redo_no_start; case '\\': /* first look if it is in fact an end of buffer */ c = handle_stray1(p); p = file->buf_ptr; if (c == '\\') goto parse_simple; if (c != CH_EOF) goto redo_no_start; { TCCState *s1 = tcc_state; if ((parse_flags & PARSE_FLAG_LINEFEED) && !(tok_flags & TOK_FLAG_EOF)) { tok_flags |= TOK_FLAG_EOF; tok = TOK_LINEFEED; goto keep_tok_flags; } else if (!(parse_flags & PARSE_FLAG_PREPROCESS)) { tok = TOK_EOF; } else if (s1->ifdef_stack_ptr != file->ifdef_stack_ptr) { tcc_error("missing #endif"); } else if (s1->include_stack_ptr == s1->include_stack) { /* no include left : end of file. */ tok = TOK_EOF; } else { tok_flags &= ~TOK_FLAG_EOF; /* pop include file */ /* test if previous '#endif' was after a #ifdef at start of file */ if (tok_flags & TOK_FLAG_ENDIF) { #ifdef INC_DEBUG printf("#endif %s\n", get_tok_str(file->ifndef_macro_saved, NULL)); #endif search_cached_include(s1, file->filename, 1) ->ifndef_macro = file->ifndef_macro_saved; tok_flags &= ~TOK_FLAG_ENDIF; } /* add end of include file debug info */ if (tcc_state->do_debug) { put_stabd(N_EINCL, 0, 0); } /* pop include stack */ tcc_close(); s1->include_stack_ptr--; p = file->buf_ptr; if (p == file->buffer) tok_flags = TOK_FLAG_BOF|TOK_FLAG_BOL; goto redo_no_start; } } break; case '\n': file->line_num++; tok_flags |= TOK_FLAG_BOL; p++; maybe_newline: if (0 == (parse_flags & PARSE_FLAG_LINEFEED)) goto redo_no_start; tok = TOK_LINEFEED; goto keep_tok_flags; case '#': /* XXX: simplify */ PEEKC(c, p); if ((tok_flags & TOK_FLAG_BOL) && (parse_flags & PARSE_FLAG_PREPROCESS)) { file->buf_ptr = p; preprocess(tok_flags & TOK_FLAG_BOF); p = file->buf_ptr; goto maybe_newline; } else { if (c == '#') { p++; tok = TOK_TWOSHARPS; } else { if (parse_flags & PARSE_FLAG_ASM_FILE) { p = parse_line_comment(p - 1); goto redo_no_start; } else { tok = '#'; } } } break; /* dollar is allowed to start identifiers when not parsing asm */ case '$': if (!(isidnum_table[c - CH_EOF] & IS_ID) || (parse_flags & PARSE_FLAG_ASM_FILE)) goto parse_simple; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': parse_ident_fast: p1 = p; h = TOK_HASH_INIT; h = TOK_HASH_FUNC(h, c); while (c = *++p, isidnum_table[c - CH_EOF] & (IS_ID|IS_NUM)) h = TOK_HASH_FUNC(h, c); len = p - p1; if (c != '\\') { TokenSym **pts; /* fast case : no stray found, so we have the full token and we have already hashed it */ h &= (TOK_HASH_SIZE - 1); pts = &hash_ident[h]; for(;;) { ts = *pts; if (!ts) break; if (ts->len == len && !memcmp(ts->str, p1, len)) goto token_found; pts = &(ts->hash_next); } ts = tok_alloc_new(pts, (char *) p1, len); token_found: ; } else { /* slower case */ cstr_reset(&tokcstr); cstr_cat(&tokcstr, (char *) p1, len); p--; PEEKC(c, p); parse_ident_slow: while (isidnum_table[c - CH_EOF] & (IS_ID|IS_NUM)) { cstr_ccat(&tokcstr, c); PEEKC(c, p); } ts = tok_alloc(tokcstr.data, tokcstr.size); } tok = ts->tok; break; case 'L': t = p[1]; if (t != '\\' && t != '\'' && t != '\"') { /* fast case */ goto parse_ident_fast; } else { PEEKC(c, p); if (c == '\'' || c == '\"') { is_long = 1; goto str_const; } else { cstr_reset(&tokcstr); cstr_ccat(&tokcstr, 'L'); goto parse_ident_slow; } } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': t = c; PEEKC(c, p); /* after the first digit, accept digits, alpha, '.' or sign if prefixed by 'eEpP' */ parse_num: cstr_reset(&tokcstr); for(;;) { cstr_ccat(&tokcstr, t); if (!((isidnum_table[c - CH_EOF] & (IS_ID|IS_NUM)) || c == '.' || ((c == '+' || c == '-') && (((t == 'e' || t == 'E') && !(parse_flags & PARSE_FLAG_ASM_FILE /* 0xe+1 is 3 tokens in asm */ && ((char*)tokcstr.data)[0] == '0' && toup(((char*)tokcstr.data)[1]) == 'X')) || t == 'p' || t == 'P')))) break; t = c; PEEKC(c, p); } /* We add a trailing '\0' to ease parsing */ cstr_ccat(&tokcstr, '\0'); tokc.str.size = tokcstr.size; tokc.str.data = tokcstr.data; tok = TOK_PPNUM; break; case '.': /* special dot handling because it can also start a number */ PEEKC(c, p); if (isnum(c)) { t = '.'; goto parse_num; } else if ((isidnum_table['.' - CH_EOF] & IS_ID) && (isidnum_table[c - CH_EOF] & (IS_ID|IS_NUM))) { *--p = c = '.'; goto parse_ident_fast; } else if (c == '.') { PEEKC(c, p); if (c == '.') { p++; tok = TOK_DOTS; } else { *--p = '.'; /* may underflow into file->unget[] */ tok = '.'; } } else { tok = '.'; } break; case '\'': case '\"': is_long = 0; str_const: cstr_reset(&tokcstr); if (is_long) cstr_ccat(&tokcstr, 'L'); cstr_ccat(&tokcstr, c); p = parse_pp_string(p, c, &tokcstr); cstr_ccat(&tokcstr, c); cstr_ccat(&tokcstr, '\0'); tokc.str.size = tokcstr.size; tokc.str.data = tokcstr.data; tok = TOK_PPSTR; break; case '<': PEEKC(c, p); if (c == '=') { p++; tok = TOK_LE; } else if (c == '<') { PEEKC(c, p); if (c == '=') { p++; tok = TOK_A_SHL; } else { tok = TOK_SHL; } } else { tok = TOK_LT; } break; case '>': PEEKC(c, p); if (c == '=') { p++; tok = TOK_GE; } else if (c == '>') { PEEKC(c, p); if (c == '=') { p++; tok = TOK_A_SAR; } else { tok = TOK_SAR; } } else { tok = TOK_GT; } break; case '&': PEEKC(c, p); if (c == '&') { p++; tok = TOK_LAND; } else if (c == '=') { p++; tok = TOK_A_AND; } else { tok = '&'; } break; case '|': PEEKC(c, p); if (c == '|') { p++; tok = TOK_LOR; } else if (c == '=') { p++; tok = TOK_A_OR; } else { tok = '|'; } break; case '+': PEEKC(c, p); if (c == '+') { p++; tok = TOK_INC; } else if (c == '=') { p++; tok = TOK_A_ADD; } else { tok = '+'; } break; case '-': PEEKC(c, p); if (c == '-') { p++; tok = TOK_DEC; } else if (c == '=') { p++; tok = TOK_A_SUB; } else if (c == '>') { p++; tok = TOK_ARROW; } else { tok = '-'; } break; PARSE2('!', '!', '=', TOK_NE) PARSE2('=', '=', '=', TOK_EQ) PARSE2('*', '*', '=', TOK_A_MUL) PARSE2('%', '%', '=', TOK_A_MOD) PARSE2('^', '^', '=', TOK_A_XOR) /* comments or operator */ case '/': PEEKC(c, p); if (c == '*') { p = parse_comment(p); /* comments replaced by a blank */ tok = ' '; goto keep_tok_flags; } else if (c == '/') { p = parse_line_comment(p); tok = ' '; goto keep_tok_flags; } else if (c == '=') { p++; tok = TOK_A_DIV; } else { tok = '/'; } break; /* simple tokens */ case '(': case ')': case '[': case ']': case '{': case '}': case ',': case ';': case ':': case '?': case '~': case '@': /* only used in assembler */ parse_simple: tok = c; p++; break; default: if (c >= 0x80 && c <= 0xFF) /* utf8 identifiers */ goto parse_ident_fast; if (parse_flags & PARSE_FLAG_ASM_FILE) goto parse_simple; tcc_error("unrecognized character \\x%02x", c); break; } tok_flags = 0; keep_tok_flags: file->buf_ptr = p; #if defined(PARSE_DEBUG) printf("token = %d %s\n", tok, get_tok_str(tok, &tokc)); #endif } /* return next token without macro substitution. Can read input from macro_ptr buffer */ static void next_nomacro_spc(void) { if (macro_ptr) { redo: tok = *macro_ptr; if (tok) { TOK_GET(&tok, ¯o_ptr, &tokc); if (tok == TOK_LINENUM) { file->line_num = tokc.i; goto redo; } } } else { next_nomacro1(); } //printf("token = %s\n", get_tok_str(tok, &tokc)); } ST_FUNC void next_nomacro(void) { do { next_nomacro_spc(); } while (tok < 256 && (isidnum_table[tok - CH_EOF] & IS_SPC)); } static void macro_subst( TokenString *tok_str, Sym **nested_list, const int *macro_str ); /* substitute arguments in replacement lists in macro_str by the values in args (field d) and return allocated string */ static int *macro_arg_subst(Sym **nested_list, const int *macro_str, Sym *args) { int t, t0, t1, spc; const int *st; Sym *s; CValue cval; TokenString str; CString cstr; tok_str_new(&str); t0 = t1 = 0; while(1) { TOK_GET(&t, ¯o_str, &cval); if (!t) break; if (t == '#') { /* stringize */ TOK_GET(&t, ¯o_str, &cval); if (!t) goto bad_stringy; s = sym_find2(args, t); if (s) { cstr_new(&cstr); cstr_ccat(&cstr, '\"'); st = s->d; spc = 0; while (*st >= 0) { TOK_GET(&t, &st, &cval); if (t != TOK_PLCHLDR && t != TOK_NOSUBST && 0 == check_space(t, &spc)) { const char *s = get_tok_str(t, &cval); while (*s) { if (t == TOK_PPSTR && *s != '\'') add_char(&cstr, *s); else cstr_ccat(&cstr, *s); ++s; } } } cstr.size -= spc; cstr_ccat(&cstr, '\"'); cstr_ccat(&cstr, '\0'); #ifdef PP_DEBUG printf("\nstringize: <%s>\n", (char *)cstr.data); #endif /* add string */ cval.str.size = cstr.size; cval.str.data = cstr.data; tok_str_add2(&str, TOK_PPSTR, &cval); cstr_free(&cstr); } else { bad_stringy: expect("macro parameter after '#'"); } } else if (t >= TOK_IDENT) { s = sym_find2(args, t); if (s) { int l0 = str.len; st = s->d; /* if '##' is present before or after, no arg substitution */ if (*macro_str == TOK_PPJOIN || t1 == TOK_PPJOIN) { /* special case for var arg macros : ## eats the ',' if empty VA_ARGS variable. */ if (t1 == TOK_PPJOIN && t0 == ',' && gnu_ext && s->type.t) { if (*st <= 0) { /* suppress ',' '##' */ str.len -= 2; } else { /* suppress '##' and add variable */ str.len--; goto add_var; } } } else { add_var: if (!s->next) { /* Expand arguments tokens and store them. In most cases we could also re-expand each argument if used multiple times, but not if the argument contains the __COUNTER__ macro. */ TokenString str2; sym_push2(&s->next, s->v, s->type.t, 0); tok_str_new(&str2); macro_subst(&str2, nested_list, st); tok_str_add(&str2, 0); s->next->d = str2.str; } st = s->next->d; } for(;;) { int t2; TOK_GET(&t2, &st, &cval); if (t2 <= 0) break; tok_str_add2(&str, t2, &cval); } if (str.len == l0) /* expanded to empty string */ tok_str_add(&str, TOK_PLCHLDR); } else { tok_str_add(&str, t); } } else { tok_str_add2(&str, t, &cval); } t0 = t1, t1 = t; } tok_str_add(&str, 0); return str.str; } static char const ab_month_name[12][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static int paste_tokens(int t1, CValue *v1, int t2, CValue *v2) { CString cstr; int n, ret = 1; cstr_new(&cstr); if (t1 != TOK_PLCHLDR) cstr_cat(&cstr, get_tok_str(t1, v1), -1); n = cstr.size; if (t2 != TOK_PLCHLDR) cstr_cat(&cstr, get_tok_str(t2, v2), -1); cstr_ccat(&cstr, '\0'); tcc_open_bf(tcc_state, ":paste:", cstr.size); memcpy(file->buffer, cstr.data, cstr.size); tok_flags = 0; for (;;) { next_nomacro1(); if (0 == *file->buf_ptr) break; if (is_space(tok)) continue; tcc_warning("pasting \"%.*s\" and \"%s\" does not give a valid" " preprocessing token", n, cstr.data, (char*)cstr.data + n); ret = 0; break; } tcc_close(); //printf("paste <%s>\n", (char*)cstr.data); cstr_free(&cstr); return ret; } /* handle the '##' operator. Return NULL if no '##' seen. Otherwise return the resulting string (which must be freed). */ static inline int *macro_twosharps(const int *ptr0) { int t; CValue cval; TokenString macro_str1; int start_of_nosubsts = -1; const int *ptr; /* we search the first '##' */ for (ptr = ptr0;;) { TOK_GET(&t, &ptr, &cval); if (t == TOK_PPJOIN) break; if (t == 0) return NULL; } tok_str_new(¯o_str1); //tok_print(" $$$", ptr0); for (ptr = ptr0;;) { TOK_GET(&t, &ptr, &cval); if (t == 0) break; if (t == TOK_PPJOIN) continue; while (*ptr == TOK_PPJOIN) { int t1; CValue cv1; /* given 'a##b', remove nosubsts preceding 'a' */ if (start_of_nosubsts >= 0) macro_str1.len = start_of_nosubsts; /* given 'a##b', remove nosubsts preceding 'b' */ while ((t1 = *++ptr) == TOK_NOSUBST) ; if (t1 && t1 != TOK_PPJOIN) { TOK_GET(&t1, &ptr, &cv1); if (t != TOK_PLCHLDR || t1 != TOK_PLCHLDR) { if (paste_tokens(t, &cval, t1, &cv1)) { t = tok, cval = tokc; } else { tok_str_add2(¯o_str1, t, &cval); t = t1, cval = cv1; } } } } if (t == TOK_NOSUBST) { if (start_of_nosubsts < 0) start_of_nosubsts = macro_str1.len; } else { start_of_nosubsts = -1; } tok_str_add2(¯o_str1, t, &cval); } tok_str_add(¯o_str1, 0); //tok_print(" ###", macro_str1.str); return macro_str1.str; } /* peek or read [ws_str == NULL] next token from function macro call, walking up macro levels up to the file if necessary */ static int next_argstream(Sym **nested_list, TokenString *ws_str) { int t; const int *p; Sym *sa; for (;;) { if (macro_ptr) { p = macro_ptr, t = *p; if (ws_str) { while (is_space(t) || TOK_LINEFEED == t || TOK_PLCHLDR == t) tok_str_add(ws_str, t), t = *++p; } if (t == 0) { end_macro(); /* also, end of scope for nested defined symbol */ sa = *nested_list; while (sa && sa->v == 0) sa = sa->prev; if (sa) sa->v = 0; continue; } } else { ch = handle_eob(); if (ws_str) { while (is_space(ch) || ch == '\n' || ch == '/') { if (ch == '/') { int c; uint8_t *p = file->buf_ptr; PEEKC(c, p); if (c == '*') { p = parse_comment(p); file->buf_ptr = p - 1; } else if (c == '/') { p = parse_line_comment(p); file->buf_ptr = p - 1; } else break; ch = ' '; } if (ch == '\n') file->line_num++; if (!(ch == '\f' || ch == '\v' || ch == '\r')) tok_str_add(ws_str, ch); cinp(); } } t = ch; } if (ws_str) return t; next_nomacro_spc(); return tok; } } /* do macro substitution of current token with macro 's' and add result to (tok_str,tok_len). 'nested_list' is the list of all macros we got inside to avoid recursing. Return non zero if no substitution needs to be done */ static int macro_subst_tok( TokenString *tok_str, Sym **nested_list, Sym *s) { Sym *args, *sa, *sa1; int parlevel, t, t1, spc; TokenString str; char *cstrval; CValue cval; CString cstr; char buf[32]; /* if symbol is a macro, prepare substitution */ /* special macros */ if (tok == TOK___LINE__ || tok == TOK___COUNTER__) { t = tok == TOK___LINE__ ? file->line_num : pp_counter++; snprintf(buf, sizeof(buf), "%d", t); cstrval = buf; t1 = TOK_PPNUM; goto add_cstr1; } else if (tok == TOK___FILE__) { cstrval = file->filename; goto add_cstr; } else if (tok == TOK___DATE__ || tok == TOK___TIME__) { time_t ti; struct tm *tm; time(&ti); tm = localtime(&ti); if (tok == TOK___DATE__) { snprintf(buf, sizeof(buf), "%s %2d %d", ab_month_name[tm->tm_mon], tm->tm_mday, tm->tm_year + 1900); } else { snprintf(buf, sizeof(buf), "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); } cstrval = buf; add_cstr: t1 = TOK_STR; add_cstr1: cstr_new(&cstr); cstr_cat(&cstr, cstrval, 0); cval.str.size = cstr.size; cval.str.data = cstr.data; tok_str_add2(tok_str, t1, &cval); cstr_free(&cstr); } else if (s->d) { int saved_parse_flags = parse_flags; int *joined_str = NULL; int *mstr = s->d; if (s->type.t == MACRO_FUNC) { /* whitespace between macro name and argument list */ TokenString ws_str; tok_str_new(&ws_str); spc = 0; parse_flags |= PARSE_FLAG_SPACES | PARSE_FLAG_LINEFEED | PARSE_FLAG_ACCEPT_STRAYS; /* get next token from argument stream */ t = next_argstream(nested_list, &ws_str); if (t != '(') { /* not a macro substitution after all, restore the * macro token plus all whitespace we've read. * whitespace is intentionally not merged to preserve * newlines. */ parse_flags = saved_parse_flags; tok_str_add(tok_str, tok); if (parse_flags & PARSE_FLAG_SPACES) { int i; for (i = 0; i < ws_str.len; i++) tok_str_add(tok_str, ws_str.str[i]); } tok_str_free_str(ws_str.str); return 0; } else { tok_str_free_str(ws_str.str); } do { next_nomacro(); /* eat '(' */ } while (tok == TOK_PLCHLDR); /* argument macro */ args = NULL; sa = s->next; /* NOTE: empty args are allowed, except if no args */ for(;;) { do { next_argstream(nested_list, NULL); } while (is_space(tok) || TOK_LINEFEED == tok); empty_arg: /* handle '()' case */ if (!args && !sa && tok == ')') break; if (!sa) tcc_error("macro '%s' used with too many args", get_tok_str(s->v, 0)); tok_str_new(&str); parlevel = spc = 0; /* NOTE: non zero sa->t indicates VA_ARGS */ while ((parlevel > 0 || (tok != ')' && (tok != ',' || sa->type.t)))) { if (tok == TOK_EOF || tok == 0) break; if (tok == '(') parlevel++; else if (tok == ')') parlevel--; if (tok == TOK_LINEFEED) tok = ' '; if (!check_space(tok, &spc)) tok_str_add2(&str, tok, &tokc); next_argstream(nested_list, NULL); } if (parlevel) expect(")"); str.len -= spc; tok_str_add(&str, -1); tok_str_add(&str, 0); sa1 = sym_push2(&args, sa->v & ~SYM_FIELD, sa->type.t, 0); sa1->d = str.str; sa = sa->next; if (tok == ')') { /* special case for gcc var args: add an empty var arg argument if it is omitted */ if (sa && sa->type.t && gnu_ext) goto empty_arg; break; } if (tok != ',') expect(","); } if (sa) { tcc_error("macro '%s' used with too few args", get_tok_str(s->v, 0)); } parse_flags = saved_parse_flags; /* now subst each arg */ mstr = macro_arg_subst(nested_list, mstr, args); /* free memory */ sa = args; while (sa) { sa1 = sa->prev; tok_str_free_str(sa->d); if (sa->next) { tok_str_free_str(sa->next->d); sym_free(sa->next); } sym_free(sa); sa = sa1; } } sym_push2(nested_list, s->v, 0, 0); parse_flags = saved_parse_flags; joined_str = macro_twosharps(mstr); macro_subst(tok_str, nested_list, joined_str ? joined_str : mstr); /* pop nested defined symbol */ sa1 = *nested_list; *nested_list = sa1->prev; sym_free(sa1); if (joined_str) tok_str_free_str(joined_str); if (mstr != s->d) tok_str_free_str(mstr); } return 0; } /* do macro substitution of macro_str and add result to (tok_str,tok_len). 'nested_list' is the list of all macros we got inside to avoid recursing. */ static void macro_subst( TokenString *tok_str, Sym **nested_list, const int *macro_str ) { Sym *s; int t, spc, nosubst; CValue cval; spc = nosubst = 0; while (1) { TOK_GET(&t, ¯o_str, &cval); if (t <= 0) break; if (t >= TOK_IDENT && 0 == nosubst) { s = define_find(t); if (s == NULL) goto no_subst; /* if nested substitution, do nothing */ if (sym_find2(*nested_list, t)) { /* and mark it as TOK_NOSUBST, so it doesn't get subst'd again */ tok_str_add2(tok_str, TOK_NOSUBST, NULL); goto no_subst; } { TokenString str; str.str = (int*)macro_str; begin_macro(&str, 2); tok = t; macro_subst_tok(tok_str, nested_list, s); if (str.alloc == 3) { /* already finished by reading function macro arguments */ break; } macro_str = macro_ptr; end_macro (); } if (tok_str->len) spc = is_space(t = tok_str->str[tok_str->lastlen]); } else { if (t == '\\' && !(parse_flags & PARSE_FLAG_ACCEPT_STRAYS)) tcc_error("stray '\\' in program"); no_subst: if (!check_space(t, &spc)) tok_str_add2(tok_str, t, &cval); if (nosubst) { if (nosubst > 1 && (spc || (++nosubst == 3 && t == '('))) continue; nosubst = 0; } if (t == TOK_NOSUBST) nosubst = 1; } /* GCC supports 'defined' as result of a macro substitution */ if (t == TOK_DEFINED && pp_expr) nosubst = 2; } } /* return next token with macro substitution */ ST_FUNC void next(void) { redo: if (parse_flags & PARSE_FLAG_SPACES) next_nomacro_spc(); else next_nomacro(); if (macro_ptr) { if (tok == TOK_NOSUBST || tok == TOK_PLCHLDR) { /* discard preprocessor markers */ goto redo; } else if (tok == 0) { /* end of macro or unget token string */ end_macro(); goto redo; } } else if (tok >= TOK_IDENT && (parse_flags & PARSE_FLAG_PREPROCESS)) { Sym *s; /* if reading from file, try to substitute macros */ s = define_find(tok); if (s) { Sym *nested_list = NULL; tokstr_buf.len = 0; macro_subst_tok(&tokstr_buf, &nested_list, s); tok_str_add(&tokstr_buf, 0); begin_macro(&tokstr_buf, 2); goto redo; } } /* convert preprocessor tokens into C tokens */ if (tok == TOK_PPNUM) { if (parse_flags & PARSE_FLAG_TOK_NUM) parse_number((char *)tokc.str.data); } else if (tok == TOK_PPSTR) { if (parse_flags & PARSE_FLAG_TOK_STR) parse_string((char *)tokc.str.data, tokc.str.size - 1); } } /* push back current token and set current token to 'last_tok'. Only identifier case handled for labels. */ ST_INLN void unget_tok(int last_tok) { TokenString *str = tok_str_alloc(); tok_str_add2(str, tok, &tokc); tok_str_add(str, 0); begin_macro(str, 1); tok = last_tok; } ST_FUNC void preprocess_start(TCCState *s1, int is_asm) { CString cstr; int i; s1->include_stack_ptr = s1->include_stack; s1->ifdef_stack_ptr = s1->ifdef_stack; file->ifdef_stack_ptr = s1->ifdef_stack_ptr; pp_expr = 0; pp_counter = 0; pp_debug_tok = pp_debug_symv = 0; pp_once++; pvtop = vtop = vstack - 1; s1->pack_stack[0] = 0; s1->pack_stack_ptr = s1->pack_stack; set_idnum('$', s1->dollars_in_identifiers ? IS_ID : 0); set_idnum('.', is_asm ? IS_ID : 0); cstr_new(&cstr); cstr_cat(&cstr, "\"", -1); cstr_cat(&cstr, file->filename, -1); cstr_cat(&cstr, "\"", 0); tcc_define_symbol(s1, "__BASE_FILE__", cstr.data); cstr_reset(&cstr); for (i = 0; i < s1->nb_cmd_include_files; i++) { cstr_cat(&cstr, "#include \"", -1); cstr_cat(&cstr, s1->cmd_include_files[i], -1); cstr_cat(&cstr, "\"\n", -1); } if (cstr.size) { *s1->include_stack_ptr++ = file; tcc_open_bf(s1, "<command line>", cstr.size); memcpy(file->buffer, cstr.data, cstr.size); } cstr_free(&cstr); if (is_asm) tcc_define_symbol(s1, "__ASSEMBLER__", NULL); parse_flags = is_asm ? PARSE_FLAG_ASM_FILE : 0; tok_flags = TOK_FLAG_BOL | TOK_FLAG_BOF; } /* cleanup from error/setjmp */ ST_FUNC void preprocess_end(TCCState *s1) { while (macro_stack) end_macro(); macro_ptr = NULL; } ST_FUNC void tccpp_new(TCCState *s) { int i, c; const char *p, *r; /* might be used in error() before preprocess_start() */ s->include_stack_ptr = s->include_stack; s->ppfp = stdout; /* init isid table */ for(i = CH_EOF; i<128; i++) set_idnum(i, is_space(i) ? IS_SPC : isid(i) ? IS_ID : isnum(i) ? IS_NUM : 0); for(i = 128; i<256; i++) set_idnum(i, IS_ID); /* init allocators */ tal_new(&toksym_alloc, TOKSYM_TAL_LIMIT, TOKSYM_TAL_SIZE); tal_new(&tokstr_alloc, TOKSTR_TAL_LIMIT, TOKSTR_TAL_SIZE); tal_new(&cstr_alloc, CSTR_TAL_LIMIT, CSTR_TAL_SIZE); memset(hash_ident, 0, TOK_HASH_SIZE * sizeof(TokenSym *)); cstr_new(&cstr_buf); cstr_realloc(&cstr_buf, STRING_MAX_SIZE); tok_str_new(&tokstr_buf); tok_str_realloc(&tokstr_buf, TOKSTR_MAX_SIZE); tok_ident = TOK_IDENT; p = tcc_keywords; while (*p) { r = p; for(;;) { c = *r++; if (c == '\0') break; } tok_alloc(p, r - p - 1); p = r; } } ST_FUNC void tccpp_delete(TCCState *s) { int i, n; /* free -D and compiler defines */ free_defines(NULL); /* free tokens */ n = tok_ident - TOK_IDENT; for(i = 0; i < n; i++) tal_free(toksym_alloc, table_ident[i]); tcc_free(table_ident); table_ident = NULL; /* free static buffers */ cstr_free(&tokcstr); cstr_free(&cstr_buf); cstr_free(¯o_equal_buf); tok_str_free_str(tokstr_buf.str); /* free allocators */ tal_delete(toksym_alloc); toksym_alloc = NULL; tal_delete(tokstr_alloc); tokstr_alloc = NULL; tal_delete(cstr_alloc); cstr_alloc = NULL; } /* ------------------------------------------------------------------------- */ /* tcc -E [-P[1]] [-dD} support */ static void tok_print(const char *msg, const int *str) { FILE *fp; int t, s = 0; CValue cval; fp = tcc_state->ppfp; fprintf(fp, "%s", msg); while (str) { TOK_GET(&t, &str, &cval); if (!t) break; fprintf(fp, " %s" + s, get_tok_str(t, &cval)), s = 1; } fprintf(fp, "\n"); } static void pp_line(TCCState *s1, BufferedFile *f, int level) { int d = f->line_num - f->line_ref; if (s1->dflag & 4) return; if (s1->Pflag == LINE_MACRO_OUTPUT_FORMAT_NONE) { ; } else if (level == 0 && f->line_ref && d < 8) { while (d > 0) fputs("\n", s1->ppfp), --d; } else if (s1->Pflag == LINE_MACRO_OUTPUT_FORMAT_STD) { fprintf(s1->ppfp, "#line %d \"%s\"\n", f->line_num, f->filename); } else { fprintf(s1->ppfp, "# %d \"%s\"%s\n", f->line_num, f->filename, level > 0 ? " 1" : level < 0 ? " 2" : ""); } f->line_ref = f->line_num; } static void define_print(TCCState *s1, int v) { FILE *fp; Sym *s; s = define_find(v); if (NULL == s || NULL == s->d) return; fp = s1->ppfp; fprintf(fp, "#define %s", get_tok_str(v, NULL)); if (s->type.t == MACRO_FUNC) { Sym *a = s->next; fprintf(fp,"("); if (a) for (;;) { fprintf(fp,"%s", get_tok_str(a->v & ~SYM_FIELD, NULL)); if (!(a = a->next)) break; fprintf(fp,","); } fprintf(fp,")"); } tok_print("", s->d); } static void pp_debug_defines(TCCState *s1) { int v, t; const char *vs; FILE *fp; t = pp_debug_tok; if (t == 0) return; file->line_num--; pp_line(s1, file, 0); file->line_ref = ++file->line_num; fp = s1->ppfp; v = pp_debug_symv; vs = get_tok_str(v, NULL); if (t == TOK_DEFINE) { define_print(s1, v); } else if (t == TOK_UNDEF) { fprintf(fp, "#undef %s\n", vs); } else if (t == TOK_push_macro) { fprintf(fp, "#pragma push_macro(\"%s\")\n", vs); } else if (t == TOK_pop_macro) { fprintf(fp, "#pragma pop_macro(\"%s\")\n", vs); } pp_debug_tok = 0; } static void pp_debug_builtins(TCCState *s1) { int v; for (v = TOK_IDENT; v < tok_ident; ++v) define_print(s1, v); } /* Add a space between tokens a and b to avoid unwanted textual pasting */ static int pp_need_space(int a, int b) { return 'E' == a ? '+' == b || '-' == b : '+' == a ? TOK_INC == b || '+' == b : '-' == a ? TOK_DEC == b || '-' == b : a >= TOK_IDENT ? b >= TOK_IDENT : a == TOK_PPNUM ? b >= TOK_IDENT : 0; } /* maybe hex like 0x1e */ static int pp_check_he0xE(int t, const char *p) { if (t == TOK_PPNUM && toup(strchr(p, 0)[-1]) == 'E') return 'E'; return t; } /* Preprocess the current file */ ST_FUNC int tcc_preprocess(TCCState *s1) { BufferedFile **iptr; int token_seen, spcs, level; const char *p; char white[400]; parse_flags = PARSE_FLAG_PREPROCESS | (parse_flags & PARSE_FLAG_ASM_FILE) | PARSE_FLAG_LINEFEED | PARSE_FLAG_SPACES | PARSE_FLAG_ACCEPT_STRAYS ; /* Credits to Fabrice Bellard's initial revision to demonstrate its capability to compile and run itself, provided all numbers are given as decimals. tcc -E -P10 will do. */ if (s1->Pflag == LINE_MACRO_OUTPUT_FORMAT_P10) parse_flags |= PARSE_FLAG_TOK_NUM, s1->Pflag = 1; #ifdef PP_BENCH /* for PP benchmarks */ do next(); while (tok != TOK_EOF); return 0; #endif if (s1->dflag & 1) { pp_debug_builtins(s1); s1->dflag &= ~1; } token_seen = TOK_LINEFEED, spcs = 0; pp_line(s1, file, 0); for (;;) { iptr = s1->include_stack_ptr; next(); if (tok == TOK_EOF) break; level = s1->include_stack_ptr - iptr; if (level) { if (level > 0) pp_line(s1, *iptr, 0); pp_line(s1, file, level); } if (s1->dflag & 7) { pp_debug_defines(s1); if (s1->dflag & 4) continue; } if (is_space(tok)) { if (spcs < sizeof white - 1) white[spcs++] = tok; continue; } else if (tok == TOK_LINEFEED) { spcs = 0; if (token_seen == TOK_LINEFEED) continue; ++file->line_ref; } else if (token_seen == TOK_LINEFEED) { pp_line(s1, file, 0); } else if (spcs == 0 && pp_need_space(token_seen, tok)) { white[spcs++] = ' '; } white[spcs] = 0, fputs(white, s1->ppfp), spcs = 0; fputs(p = get_tok_str(tok, &tokc), s1->ppfp); token_seen = pp_check_he0xE(tok, p); } return 0; } /* ------------------------------------------------------------------------- */
/* keywords */ DEF(TOK_INT, "int") DEF(TOK_VOID, "void") DEF(TOK_CHAR, "char") DEF(TOK_IF, "if") DEF(TOK_ELSE, "else") DEF(TOK_WHILE, "while") DEF(TOK_BREAK, "break") DEF(TOK_RETURN, "return") DEF(TOK_FOR, "for") DEF(TOK_EXTERN, "extern") DEF(TOK_STATIC, "static") DEF(TOK_UNSIGNED, "unsigned") DEF(TOK_GOTO, "goto") DEF(TOK_DO, "do") DEF(TOK_CONTINUE, "continue") DEF(TOK_SWITCH, "switch") DEF(TOK_CASE, "case") DEF(TOK_CONST1, "const") DEF(TOK_CONST2, "__const") /* gcc keyword */ DEF(TOK_CONST3, "__const__") /* gcc keyword */ DEF(TOK_VOLATILE1, "volatile") DEF(TOK_VOLATILE2, "__volatile") /* gcc keyword */ DEF(TOK_VOLATILE3, "__volatile__") /* gcc keyword */ DEF(TOK_LONG, "long") DEF(TOK_REGISTER, "register") DEF(TOK_SIGNED1, "signed") DEF(TOK_SIGNED2, "__signed") /* gcc keyword */ DEF(TOK_SIGNED3, "__signed__") /* gcc keyword */ DEF(TOK_AUTO, "auto") DEF(TOK_INLINE1, "inline") DEF(TOK_INLINE2, "__inline") /* gcc keyword */ DEF(TOK_INLINE3, "__inline__") /* gcc keyword */ DEF(TOK_RESTRICT1, "restrict") DEF(TOK_RESTRICT2, "__restrict") DEF(TOK_RESTRICT3, "__restrict__") DEF(TOK_EXTENSION, "__extension__") /* gcc keyword */ DEF(TOK_GENERIC, "_Generic") DEF(TOK_FLOAT, "float") DEF(TOK_DOUBLE, "double") DEF(TOK_BOOL, "_Bool") DEF(TOK_SHORT, "short") DEF(TOK_STRUCT, "struct") DEF(TOK_UNION, "union") DEF(TOK_TYPEDEF, "typedef") DEF(TOK_DEFAULT, "default") DEF(TOK_ENUM, "enum") DEF(TOK_SIZEOF, "sizeof") DEF(TOK_ATTRIBUTE1, "__attribute") DEF(TOK_ATTRIBUTE2, "__attribute__") DEF(TOK_ALIGNOF1, "__alignof") DEF(TOK_ALIGNOF2, "__alignof__") DEF(TOK_TYPEOF1, "typeof") DEF(TOK_TYPEOF2, "__typeof") DEF(TOK_TYPEOF3, "__typeof__") DEF(TOK_LABEL, "__label__") DEF(TOK_ASM1, "asm") DEF(TOK_ASM2, "__asm") DEF(TOK_ASM3, "__asm__") #ifdef TCC_TARGET_ARM64 DEF(TOK_UINT128, "__uint128_t") #endif /*********************************************************************/ /* the following are not keywords. They are included to ease parsing */ /* preprocessor only */ DEF(TOK_DEFINE, "define") DEF(TOK_INCLUDE, "include") DEF(TOK_INCLUDE_NEXT, "include_next") DEF(TOK_IFDEF, "ifdef") DEF(TOK_IFNDEF, "ifndef") DEF(TOK_ELIF, "elif") DEF(TOK_ENDIF, "endif") DEF(TOK_DEFINED, "defined") DEF(TOK_UNDEF, "undef") DEF(TOK_ERROR, "error") DEF(TOK_WARNING, "warning") DEF(TOK_LINE, "line") DEF(TOK_PRAGMA, "pragma") DEF(TOK___LINE__, "__LINE__") DEF(TOK___FILE__, "__FILE__") DEF(TOK___DATE__, "__DATE__") DEF(TOK___TIME__, "__TIME__") DEF(TOK___FUNCTION__, "__FUNCTION__") DEF(TOK___VA_ARGS__, "__VA_ARGS__") DEF(TOK___COUNTER__, "__COUNTER__") /* special identifiers */ DEF(TOK___FUNC__, "__func__") /* special floating point values */ DEF(TOK___NAN__, "__nan__") DEF(TOK___SNAN__, "__snan__") DEF(TOK___INF__, "__inf__") /* attribute identifiers */ /* XXX: handle all tokens generically since speed is not critical */ DEF(TOK_SECTION1, "section") DEF(TOK_SECTION2, "__section__") DEF(TOK_ALIGNED1, "aligned") DEF(TOK_ALIGNED2, "__aligned__") DEF(TOK_PACKED1, "packed") DEF(TOK_PACKED2, "__packed__") DEF(TOK_WEAK1, "weak") DEF(TOK_WEAK2, "__weak__") DEF(TOK_ALIAS1, "alias") DEF(TOK_ALIAS2, "__alias__") DEF(TOK_UNUSED1, "unused") DEF(TOK_UNUSED2, "__unused__") DEF(TOK_CDECL1, "cdecl") DEF(TOK_CDECL2, "__cdecl") DEF(TOK_CDECL3, "__cdecl__") DEF(TOK_STDCALL1, "stdcall") DEF(TOK_STDCALL2, "__stdcall") DEF(TOK_STDCALL3, "__stdcall__") DEF(TOK_FASTCALL1, "fastcall") DEF(TOK_FASTCALL2, "__fastcall") DEF(TOK_FASTCALL3, "__fastcall__") DEF(TOK_REGPARM1, "regparm") DEF(TOK_REGPARM2, "__regparm__") DEF(TOK_MODE, "__mode__") DEF(TOK_MODE_QI, "__QI__") DEF(TOK_MODE_DI, "__DI__") DEF(TOK_MODE_HI, "__HI__") DEF(TOK_MODE_SI, "__SI__") DEF(TOK_MODE_word, "__word__") DEF(TOK_DLLEXPORT, "dllexport") DEF(TOK_DLLIMPORT, "dllimport") DEF(TOK_NORETURN1, "noreturn") DEF(TOK_NORETURN2, "__noreturn__") DEF(TOK_VISIBILITY1, "visibility") DEF(TOK_VISIBILITY2, "__visibility__") DEF(TOK_builtin_types_compatible_p, "__builtin_types_compatible_p") DEF(TOK_builtin_choose_expr, "__builtin_choose_expr") DEF(TOK_builtin_constant_p, "__builtin_constant_p") DEF(TOK_builtin_frame_address, "__builtin_frame_address") DEF(TOK_builtin_return_address, "__builtin_return_address") DEF(TOK_builtin_expect, "__builtin_expect") /*DEF(TOK_builtin_va_list, "__builtin_va_list")*/ #if defined TCC_TARGET_PE && defined TCC_TARGET_X86_64 DEF(TOK_builtin_va_start, "__builtin_va_start") #elif defined TCC_TARGET_X86_64 DEF(TOK_builtin_va_arg_types, "__builtin_va_arg_types") #elif defined TCC_TARGET_ARM64 DEF(TOK___va_start, "__va_start") DEF(TOK___va_arg, "__va_arg") #endif /* pragma */ DEF(TOK_pack, "pack") #if !defined(TCC_TARGET_I386) && !defined(TCC_TARGET_X86_64) /* already defined for assembler */ DEF(TOK_ASM_push, "push") DEF(TOK_ASM_pop, "pop") #endif DEF(TOK_comment, "comment") DEF(TOK_lib, "lib") DEF(TOK_push_macro, "push_macro") DEF(TOK_pop_macro, "pop_macro") DEF(TOK_once, "once") DEF(TOK_option, "option") /* builtin functions or variables */ #ifndef TCC_ARM_EABI DEF(TOK_memcpy, "memcpy") DEF(TOK_memmove, "memmove") DEF(TOK_memset, "memset") DEF(TOK___divdi3, "__divdi3") DEF(TOK___moddi3, "__moddi3") DEF(TOK___udivdi3, "__udivdi3") DEF(TOK___umoddi3, "__umoddi3") DEF(TOK___ashrdi3, "__ashrdi3") DEF(TOK___lshrdi3, "__lshrdi3") DEF(TOK___ashldi3, "__ashldi3") DEF(TOK___floatundisf, "__floatundisf") DEF(TOK___floatundidf, "__floatundidf") # ifndef TCC_ARM_VFP DEF(TOK___floatundixf, "__floatundixf") DEF(TOK___fixunsxfdi, "__fixunsxfdi") # endif DEF(TOK___fixunssfdi, "__fixunssfdi") DEF(TOK___fixunsdfdi, "__fixunsdfdi") #endif #if defined TCC_TARGET_ARM # ifdef TCC_ARM_EABI DEF(TOK_memcpy, "__aeabi_memcpy") DEF(TOK_memcpy4, "__aeabi_memcpy4") DEF(TOK_memcpy8, "__aeabi_memcpy8") DEF(TOK_memmove, "__aeabi_memmove") DEF(TOK_memset, "__aeabi_memset") DEF(TOK___aeabi_ldivmod, "__aeabi_ldivmod") DEF(TOK___aeabi_uldivmod, "__aeabi_uldivmod") DEF(TOK___aeabi_idivmod, "__aeabi_idivmod") DEF(TOK___aeabi_uidivmod, "__aeabi_uidivmod") DEF(TOK___divsi3, "__aeabi_idiv") DEF(TOK___udivsi3, "__aeabi_uidiv") DEF(TOK___floatdisf, "__aeabi_l2f") DEF(TOK___floatdidf, "__aeabi_l2d") DEF(TOK___fixsfdi, "__aeabi_f2lz") DEF(TOK___fixdfdi, "__aeabi_d2lz") DEF(TOK___ashrdi3, "__aeabi_lasr") DEF(TOK___lshrdi3, "__aeabi_llsr") DEF(TOK___ashldi3, "__aeabi_llsl") DEF(TOK___floatundisf, "__aeabi_ul2f") DEF(TOK___floatundidf, "__aeabi_ul2d") DEF(TOK___fixunssfdi, "__aeabi_f2ulz") DEF(TOK___fixunsdfdi, "__aeabi_d2ulz") # else DEF(TOK___modsi3, "__modsi3") DEF(TOK___umodsi3, "__umodsi3") DEF(TOK___divsi3, "__divsi3") DEF(TOK___udivsi3, "__udivsi3") DEF(TOK___floatdisf, "__floatdisf") DEF(TOK___floatdidf, "__floatdidf") # ifndef TCC_ARM_VFP DEF(TOK___floatdixf, "__floatdixf") DEF(TOK___fixunssfsi, "__fixunssfsi") DEF(TOK___fixunsdfsi, "__fixunsdfsi") DEF(TOK___fixunsxfsi, "__fixunsxfsi") DEF(TOK___fixxfdi, "__fixxfdi") # endif DEF(TOK___fixsfdi, "__fixsfdi") DEF(TOK___fixdfdi, "__fixdfdi") # endif #endif #if defined TCC_TARGET_C67 DEF(TOK__divi, "_divi") DEF(TOK__divu, "_divu") DEF(TOK__divf, "_divf") DEF(TOK__divd, "_divd") DEF(TOK__remi, "_remi") DEF(TOK__remu, "_remu") #endif #if defined TCC_TARGET_I386 DEF(TOK___fixsfdi, "__fixsfdi") DEF(TOK___fixdfdi, "__fixdfdi") DEF(TOK___fixxfdi, "__fixxfdi") #endif #if defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64 DEF(TOK_alloca, "alloca") #endif #if defined TCC_TARGET_PE DEF(TOK___chkstk, "__chkstk") #endif #ifdef TCC_TARGET_ARM64 DEF(TOK___arm64_clear_cache, "__arm64_clear_cache") DEF(TOK___addtf3, "__addtf3") DEF(TOK___subtf3, "__subtf3") DEF(TOK___multf3, "__multf3") DEF(TOK___divtf3, "__divtf3") DEF(TOK___extendsftf2, "__extendsftf2") DEF(TOK___extenddftf2, "__extenddftf2") DEF(TOK___trunctfsf2, "__trunctfsf2") DEF(TOK___trunctfdf2, "__trunctfdf2") DEF(TOK___fixtfsi, "__fixtfsi") DEF(TOK___fixtfdi, "__fixtfdi") DEF(TOK___fixunstfsi, "__fixunstfsi") DEF(TOK___fixunstfdi, "__fixunstfdi") DEF(TOK___floatsitf, "__floatsitf") DEF(TOK___floatditf, "__floatditf") DEF(TOK___floatunsitf, "__floatunsitf") DEF(TOK___floatunditf, "__floatunditf") DEF(TOK___eqtf2, "__eqtf2") DEF(TOK___netf2, "__netf2") DEF(TOK___lttf2, "__lttf2") DEF(TOK___letf2, "__letf2") DEF(TOK___gttf2, "__gttf2") DEF(TOK___getf2, "__getf2") #endif /* bound checking symbols */ #ifdef CONFIG_TCC_BCHECK DEF(TOK___bound_ptr_add, "__bound_ptr_add") DEF(TOK___bound_ptr_indir1, "__bound_ptr_indir1") DEF(TOK___bound_ptr_indir2, "__bound_ptr_indir2") DEF(TOK___bound_ptr_indir4, "__bound_ptr_indir4") DEF(TOK___bound_ptr_indir8, "__bound_ptr_indir8") DEF(TOK___bound_ptr_indir12, "__bound_ptr_indir12") DEF(TOK___bound_ptr_indir16, "__bound_ptr_indir16") DEF(TOK___bound_main_arg, "__bound_main_arg") DEF(TOK___bound_local_new, "__bound_local_new") DEF(TOK___bound_local_delete, "__bound_local_delete") # ifdef TCC_TARGET_PE DEF(TOK_malloc, "malloc") DEF(TOK_free, "free") DEF(TOK_realloc, "realloc") DEF(TOK_memalign, "memalign") DEF(TOK_calloc, "calloc") # endif DEF(TOK_strlen, "strlen") DEF(TOK_strcpy, "strcpy") #endif /* Tiny Assembler */ DEF_ASMDIR(byte) /* must be first directive */ DEF_ASMDIR(word) DEF_ASMDIR(align) DEF_ASMDIR(balign) DEF_ASMDIR(p2align) DEF_ASMDIR(set) DEF_ASMDIR(skip) DEF_ASMDIR(space) DEF_ASMDIR(string) DEF_ASMDIR(asciz) DEF_ASMDIR(ascii) DEF_ASMDIR(file) DEF_ASMDIR(globl) DEF_ASMDIR(global) DEF_ASMDIR(weak) DEF_ASMDIR(hidden) DEF_ASMDIR(ident) DEF_ASMDIR(size) DEF_ASMDIR(type) DEF_ASMDIR(text) DEF_ASMDIR(data) DEF_ASMDIR(bss) DEF_ASMDIR(previous) DEF_ASMDIR(pushsection) DEF_ASMDIR(popsection) DEF_ASMDIR(fill) DEF_ASMDIR(rept) DEF_ASMDIR(endr) DEF_ASMDIR(org) DEF_ASMDIR(quad) #if defined(TCC_TARGET_I386) DEF_ASMDIR(code16) DEF_ASMDIR(code32) #elif defined(TCC_TARGET_X86_64) DEF_ASMDIR(code64) #endif DEF_ASMDIR(short) DEF_ASMDIR(long) DEF_ASMDIR(int) DEF_ASMDIR(section) /* must be last directive */ #if defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64 #include "i386-tok.h" #endif
/* * TCC - Tiny C Compiler * * Copyright (c) 2001-2004 Fabrice Bellard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "tcc.h" /********************************************************/ /* global variables */ /* loc : local variable index ind : output code index rsym: return symbol anon_sym: anonymous symbol index */ ST_DATA int rsym, anon_sym, ind, loc; ST_DATA Sym *sym_free_first; ST_DATA void **sym_pools; ST_DATA int nb_sym_pools; ST_DATA Sym *global_stack; ST_DATA Sym *local_stack; ST_DATA Sym *define_stack; ST_DATA Sym *global_label_stack; ST_DATA Sym *local_label_stack; static int local_scope; static int in_sizeof; static int section_sym; ST_DATA int vlas_in_scope; /* number of VLAs that are currently in scope */ ST_DATA int vla_sp_root_loc; /* vla_sp_loc for SP before any VLAs were pushed */ ST_DATA int vla_sp_loc; /* Pointer to variable holding location to store stack pointer on the stack when modifying stack pointer */ ST_DATA SValue __vstack[1+VSTACK_SIZE], *vtop, *pvtop; ST_DATA int const_wanted; /* true if constant wanted */ ST_DATA int nocode_wanted; /* no code generation wanted */ #define NODATA_WANTED (nocode_wanted > 0) /* no static data output wanted either */ #define STATIC_DATA_WANTED (nocode_wanted & 0xC0000000) /* only static data output */ ST_DATA int global_expr; /* true if compound literals must be allocated globally (used during initializers parsing */ ST_DATA CType func_vt; /* current function return type (used by return instruction) */ ST_DATA int func_var; /* true if current function is variadic (used by return instruction) */ ST_DATA int func_vc; ST_DATA int last_line_num, last_ind, func_ind; /* debug last line number and pc */ ST_DATA const char *funcname; ST_DATA int g_debug; ST_DATA CType char_pointer_type, func_old_type, int_type, size_type, ptrdiff_type; ST_DATA struct switch_t { struct case_t { int64_t v1, v2; int sym; } **p; int n; /* list of case ranges */ int def_sym; /* default symbol */ } *cur_switch; /* current switch */ /* ------------------------------------------------------------------------- */ static void gen_cast(CType *type); static void gen_cast_s(int t); static inline CType *pointed_type(CType *type); static int is_compatible_types(CType *type1, CType *type2); static int parse_btype(CType *type, AttributeDef *ad); static CType *type_decl(CType *type, AttributeDef *ad, int *v, int td); static void parse_expr_type(CType *type); static void init_putv(CType *type, Section *sec, unsigned long c); static void decl_initializer(CType *type, Section *sec, unsigned long c, int first, int size_only); static void block(int *bsym, int *csym, int is_expr); static void decl_initializer_alloc(CType *type, AttributeDef *ad, int r, int has_init, int v, int scope); static void decl(int l); static int decl0(int l, int is_for_loop_init, Sym *); static void expr_eq(void); static void vla_runtime_type_size(CType *type, int *a); static void vla_sp_restore(void); static void vla_sp_restore_root(void); static int is_compatible_unqualified_types(CType *type1, CType *type2); static inline int64_t expr_const64(void); static void vpush64(int ty, unsigned long long v); static void vpush(CType *type); static int gvtst(int inv, int t); static void gen_inline_functions(TCCState *s); static void skip_or_save_block(TokenString **str); static void gv_dup(void); ST_INLN int is_float(int t) { int bt; bt = t & VT_BTYPE; return bt == VT_LDOUBLE || bt == VT_DOUBLE || bt == VT_FLOAT || bt == VT_QFLOAT; } /* we use our own 'finite' function to avoid potential problems with non standard math libs */ /* XXX: endianness dependent */ ST_FUNC int ieee_finite(double d) { int p[4]; memcpy(p, &d, sizeof(double)); return ((unsigned)((p[1] | 0x800fffff) + 1)) >> 31; } /* compiling intel long double natively */ #if (defined __i386__ || defined __x86_64__) \ && (defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64) # define TCC_IS_NATIVE_387 #endif ST_FUNC void test_lvalue(void) { if (!(vtop->r & VT_LVAL)) expect("lvalue"); } ST_FUNC void check_vstack(void) { if (pvtop != vtop) tcc_error("internal compiler error: vstack leak (%d)", vtop - pvtop); } /* ------------------------------------------------------------------------- */ /* vstack debugging aid */ #if 0 void pv (const char *lbl, int a, int b) { int i; for (i = a; i < a + b; ++i) { SValue *p = &vtop[-i]; printf("%s vtop[-%d] : type.t:%04x r:%04x r2:%04x c.i:%d\n", lbl, i, p->type.t, p->r, p->r2, (int)p->c.i); } } #endif /* ------------------------------------------------------------------------- */ /* start of translation unit info */ ST_FUNC void tcc_debug_start(TCCState *s1) { if (s1->do_debug) { char buf[512]; /* file info: full path + filename */ section_sym = put_elf_sym(symtab_section, 0, 0, ELFW(ST_INFO)(STB_LOCAL, STT_SECTION), 0, text_section->sh_num, NULL); getcwd(buf, sizeof(buf)); #ifdef _WIN32 normalize_slashes(buf); #endif pstrcat(buf, sizeof(buf), "/"); put_stabs_r(buf, N_SO, 0, 0, text_section->data_offset, text_section, section_sym); put_stabs_r(file->filename, N_SO, 0, 0, text_section->data_offset, text_section, section_sym); last_ind = 0; last_line_num = 0; } /* an elf symbol of type STT_FILE must be put so that STB_LOCAL symbols can be safely used */ put_elf_sym(symtab_section, 0, 0, ELFW(ST_INFO)(STB_LOCAL, STT_FILE), 0, SHN_ABS, file->filename); } /* put end of translation unit info */ ST_FUNC void tcc_debug_end(TCCState *s1) { if (!s1->do_debug) return; put_stabs_r(NULL, N_SO, 0, 0, text_section->data_offset, text_section, section_sym); } /* generate line number info */ ST_FUNC void tcc_debug_line(TCCState *s1) { if (!s1->do_debug) return; if ((last_line_num != file->line_num || last_ind != ind)) { put_stabn(N_SLINE, 0, file->line_num, ind - func_ind); last_ind = ind; last_line_num = file->line_num; } } /* put function symbol */ ST_FUNC void tcc_debug_funcstart(TCCState *s1, Sym *sym) { char buf[512]; if (!s1->do_debug) return; /* stabs info */ /* XXX: we put here a dummy type */ snprintf(buf, sizeof(buf), "%s:%c1", funcname, sym->type.t & VT_STATIC ? 'f' : 'F'); put_stabs_r(buf, N_FUN, 0, file->line_num, 0, cur_text_section, sym->c); /* //gr gdb wants a line at the function */ put_stabn(N_SLINE, 0, file->line_num, 0); last_ind = 0; last_line_num = 0; } /* put function size */ ST_FUNC void tcc_debug_funcend(TCCState *s1, int size) { if (!s1->do_debug) return; put_stabn(N_FUN, 0, 0, size); } /* ------------------------------------------------------------------------- */ ST_FUNC int tccgen_compile(TCCState *s1) { cur_text_section = NULL; funcname = ""; anon_sym = SYM_FIRST_ANOM; section_sym = 0; const_wanted = 0; nocode_wanted = 0x80000000; /* define some often used types */ int_type.t = VT_INT; char_pointer_type.t = VT_BYTE; mk_pointer(&char_pointer_type); #if PTR_SIZE == 4 size_type.t = VT_INT | VT_UNSIGNED; ptrdiff_type.t = VT_INT; #elif LONG_SIZE == 4 size_type.t = VT_LLONG | VT_UNSIGNED; ptrdiff_type.t = VT_LLONG; #else size_type.t = VT_LONG | VT_LLONG | VT_UNSIGNED; ptrdiff_type.t = VT_LONG | VT_LLONG; #endif func_old_type.t = VT_FUNC; func_old_type.ref = sym_push(SYM_FIELD, &int_type, 0, 0); func_old_type.ref->f.func_call = FUNC_CDECL; func_old_type.ref->f.func_type = FUNC_OLD; tcc_debug_start(s1); #ifdef TCC_TARGET_ARM arm_init(s1); #endif #ifdef INC_DEBUG printf("%s: **** new file\n", file->filename); #endif parse_flags = PARSE_FLAG_PREPROCESS | PARSE_FLAG_TOK_NUM | PARSE_FLAG_TOK_STR; next(); decl(VT_CONST); gen_inline_functions(s1); check_vstack(); /* end of translation unit info */ tcc_debug_end(s1); return 0; } /* ------------------------------------------------------------------------- */ ST_FUNC ElfSym *elfsym(Sym *s) { if (!s || !s->c) return NULL; return &((ElfSym *)symtab_section->data)[s->c]; } /* apply storage attributes to Elf symbol */ ST_FUNC void update_storage(Sym *sym) { ElfSym *esym; int sym_bind, old_sym_bind; esym = elfsym(sym); if (!esym) return; if (sym->a.visibility) esym->st_other = (esym->st_other & ~ELFW(ST_VISIBILITY)(-1)) | sym->a.visibility; if (sym->type.t & VT_STATIC) sym_bind = STB_LOCAL; else if (sym->a.weak) sym_bind = STB_WEAK; else sym_bind = STB_GLOBAL; old_sym_bind = ELFW(ST_BIND)(esym->st_info); if (sym_bind != old_sym_bind) { esym->st_info = ELFW(ST_INFO)(sym_bind, ELFW(ST_TYPE)(esym->st_info)); } #ifdef TCC_TARGET_PE if (sym->a.dllimport) esym->st_other |= ST_PE_IMPORT; if (sym->a.dllexport) esym->st_other |= ST_PE_EXPORT; #endif #if 0 printf("storage %s: bind=%c vis=%d exp=%d imp=%d\n", get_tok_str(sym->v, NULL), sym_bind == STB_WEAK ? 'w' : sym_bind == STB_LOCAL ? 'l' : 'g', sym->a.visibility, sym->a.dllexport, sym->a.dllimport ); #endif } /* ------------------------------------------------------------------------- */ /* update sym->c so that it points to an external symbol in section 'section' with value 'value' */ ST_FUNC void put_extern_sym2(Sym *sym, int sh_num, addr_t value, unsigned long size, int can_add_underscore) { int sym_type, sym_bind, info, other, t; ElfSym *esym; const char *name; char buf1[256]; #ifdef CONFIG_TCC_BCHECK char buf[32]; #endif if (!sym->c) { name = get_tok_str(sym->v, NULL); #ifdef CONFIG_TCC_BCHECK if (tcc_state->do_bounds_check) { /* XXX: avoid doing that for statics ? */ /* if bound checking is activated, we change some function names by adding the "__bound" prefix */ switch(sym->v) { #ifdef TCC_TARGET_PE /* XXX: we rely only on malloc hooks */ case TOK_malloc: case TOK_free: case TOK_realloc: case TOK_memalign: case TOK_calloc: #endif case TOK_memcpy: case TOK_memmove: case TOK_memset: case TOK_strlen: case TOK_strcpy: case TOK_alloca: strcpy(buf, "__bound_"); strcat(buf, name); name = buf; break; } } #endif t = sym->type.t; if ((t & VT_BTYPE) == VT_FUNC) { sym_type = STT_FUNC; } else if ((t & VT_BTYPE) == VT_VOID) { sym_type = STT_NOTYPE; } else { sym_type = STT_OBJECT; } if (t & VT_STATIC) sym_bind = STB_LOCAL; else sym_bind = STB_GLOBAL; other = 0; #ifdef TCC_TARGET_PE if (sym_type == STT_FUNC && sym->type.ref) { Sym *ref = sym->type.ref; if (ref->f.func_call == FUNC_STDCALL && can_add_underscore) { sprintf(buf1, "_%s@%d", name, ref->f.func_args * PTR_SIZE); name = buf1; other |= ST_PE_STDCALL; can_add_underscore = 0; } } #endif if (tcc_state->leading_underscore && can_add_underscore) { buf1[0] = '_'; pstrcpy(buf1 + 1, sizeof(buf1) - 1, name); name = buf1; } if (sym->asm_label) name = get_tok_str(sym->asm_label, NULL); info = ELFW(ST_INFO)(sym_bind, sym_type); sym->c = put_elf_sym(symtab_section, value, size, info, other, sh_num, name); } else { esym = elfsym(sym); esym->st_value = value; esym->st_size = size; esym->st_shndx = sh_num; } update_storage(sym); } ST_FUNC void put_extern_sym(Sym *sym, Section *section, addr_t value, unsigned long size) { int sh_num = section ? section->sh_num : SHN_UNDEF; put_extern_sym2(sym, sh_num, value, size, 1); } /* add a new relocation entry to symbol 'sym' in section 's' */ ST_FUNC void greloca(Section *s, Sym *sym, unsigned long offset, int type, addr_t addend) { int c = 0; if (nocode_wanted && s == cur_text_section) return; if (sym) { if (0 == sym->c) put_extern_sym(sym, NULL, 0, 0); c = sym->c; } /* now we can add ELF relocation info */ put_elf_reloca(symtab_section, s, offset, type, c, addend); } #if PTR_SIZE == 4 ST_FUNC void greloc(Section *s, Sym *sym, unsigned long offset, int type) { greloca(s, sym, offset, type, 0); } #endif /* ------------------------------------------------------------------------- */ /* symbol allocator */ static Sym *__sym_malloc(void) { Sym *sym_pool, *sym, *last_sym; int i; sym_pool = tcc_malloc(SYM_POOL_NB * sizeof(Sym)); dynarray_add(&sym_pools, &nb_sym_pools, sym_pool); last_sym = sym_free_first; sym = sym_pool; for(i = 0; i < SYM_POOL_NB; i++) { sym->next = last_sym; last_sym = sym; sym++; } sym_free_first = last_sym; return last_sym; } static inline Sym *sym_malloc(void) { Sym *sym; #ifndef SYM_DEBUG sym = sym_free_first; if (!sym) sym = __sym_malloc(); sym_free_first = sym->next; return sym; #else sym = tcc_malloc(sizeof(Sym)); return sym; #endif } ST_INLN void sym_free(Sym *sym) { #ifndef SYM_DEBUG sym->next = sym_free_first; sym_free_first = sym; #else tcc_free(sym); #endif } /* push, without hashing */ ST_FUNC Sym *sym_push2(Sym **ps, int v, int t, int c) { Sym *s; s = sym_malloc(); memset(s, 0, sizeof *s); s->v = v; s->type.t = t; s->c = c; /* add in stack */ s->prev = *ps; *ps = s; return s; } /* find a symbol and return its associated structure. 's' is the top of the symbol stack */ ST_FUNC Sym *sym_find2(Sym *s, int v) { while (s) { if (s->v == v) return s; else if (s->v == -1) return NULL; s = s->prev; } return NULL; } /* structure lookup */ ST_INLN Sym *struct_find(int v) { v -= TOK_IDENT; if ((unsigned)v >= (unsigned)(tok_ident - TOK_IDENT)) return NULL; return table_ident[v]->sym_struct; } /* find an identifier */ ST_INLN Sym *sym_find(int v) { v -= TOK_IDENT; if ((unsigned)v >= (unsigned)(tok_ident - TOK_IDENT)) return NULL; return table_ident[v]->sym_identifier; } /* push a given symbol on the symbol stack */ ST_FUNC Sym *sym_push(int v, CType *type, int r, int c) { Sym *s, **ps; TokenSym *ts; if (local_stack) ps = &local_stack; else ps = &global_stack; s = sym_push2(ps, v, type->t, c); s->type.ref = type->ref; s->r = r; /* don't record fields or anonymous symbols */ /* XXX: simplify */ if (!(v & SYM_FIELD) && (v & ~SYM_STRUCT) < SYM_FIRST_ANOM) { /* record symbol in token array */ ts = table_ident[(v & ~SYM_STRUCT) - TOK_IDENT]; if (v & SYM_STRUCT) ps = &ts->sym_struct; else ps = &ts->sym_identifier; s->prev_tok = *ps; *ps = s; s->sym_scope = local_scope; if (s->prev_tok && s->prev_tok->sym_scope == s->sym_scope) tcc_error("redeclaration of '%s'", get_tok_str(v & ~SYM_STRUCT, NULL)); } return s; } /* push a global identifier */ ST_FUNC Sym *global_identifier_push(int v, int t, int c) { Sym *s, **ps; s = sym_push2(&global_stack, v, t, c); /* don't record anonymous symbol */ if (v < SYM_FIRST_ANOM) { ps = &table_ident[v - TOK_IDENT]->sym_identifier; /* modify the top most local identifier, so that sym_identifier will point to 's' when popped */ while (*ps != NULL && (*ps)->sym_scope) ps = &(*ps)->prev_tok; s->prev_tok = *ps; *ps = s; } return s; } /* pop symbols until top reaches 'b'. If KEEP is non-zero don't really pop them yet from the list, but do remove them from the token array. */ ST_FUNC void sym_pop(Sym **ptop, Sym *b, int keep) { Sym *s, *ss, **ps; TokenSym *ts; int v; s = *ptop; while(s != b) { ss = s->prev; v = s->v; /* remove symbol in token array */ /* XXX: simplify */ if (!(v & SYM_FIELD) && (v & ~SYM_STRUCT) < SYM_FIRST_ANOM) { ts = table_ident[(v & ~SYM_STRUCT) - TOK_IDENT]; if (v & SYM_STRUCT) ps = &ts->sym_struct; else ps = &ts->sym_identifier; *ps = s->prev_tok; } if (!keep) sym_free(s); s = ss; } if (!keep) *ptop = b; } /* ------------------------------------------------------------------------- */ static void vsetc(CType *type, int r, CValue *vc) { int v; if (vtop >= vstack + (VSTACK_SIZE - 1)) tcc_error("memory full (vstack)"); /* cannot let cpu flags if other instruction are generated. Also avoid leaving VT_JMP anywhere except on the top of the stack because it would complicate the code generator. Don't do this when nocode_wanted. vtop might come from !nocode_wanted regions (see 88_codeopt.c) and transforming it to a register without actually generating code is wrong as their value might still be used for real. All values we push under nocode_wanted will eventually be popped again, so that the VT_CMP/VT_JMP value will be in vtop when code is unsuppressed again. Same logic below in vswap(); */ if (vtop >= vstack && !nocode_wanted) { v = vtop->r & VT_VALMASK; if (v == VT_CMP || (v & ~1) == VT_JMP) gv(RC_INT); } vtop++; vtop->type = *type; vtop->r = r; vtop->r2 = VT_CONST; vtop->c = *vc; vtop->sym = NULL; } ST_FUNC void vswap(void) { SValue tmp; /* cannot vswap cpu flags. See comment at vsetc() above */ if (vtop >= vstack && !nocode_wanted) { int v = vtop->r & VT_VALMASK; if (v == VT_CMP || (v & ~1) == VT_JMP) gv(RC_INT); } tmp = vtop[0]; vtop[0] = vtop[-1]; vtop[-1] = tmp; } /* pop stack value */ ST_FUNC void vpop(void) { int v; v = vtop->r & VT_VALMASK; #if defined(TCC_TARGET_I386) || defined(TCC_TARGET_X86_64) /* for x86, we need to pop the FP stack */ if (v == TREG_ST0) { o(0xd8dd); /* fstp %st(0) */ } else #endif if (v == VT_JMP || v == VT_JMPI) { /* need to put correct jump if && or || without test */ gsym(vtop->c.i); } vtop--; } /* push constant of type "type" with useless value */ ST_FUNC void vpush(CType *type) { vset(type, VT_CONST, 0); } /* push integer constant */ ST_FUNC void vpushi(int v) { CValue cval; cval.i = v; vsetc(&int_type, VT_CONST, &cval); } /* push a pointer sized constant */ static void vpushs(addr_t v) { CValue cval; cval.i = v; vsetc(&size_type, VT_CONST, &cval); } /* push arbitrary 64bit constant */ ST_FUNC void vpush64(int ty, unsigned long long v) { CValue cval; CType ctype; ctype.t = ty; ctype.ref = NULL; cval.i = v; vsetc(&ctype, VT_CONST, &cval); } /* push long long constant */ static inline void vpushll(long long v) { vpush64(VT_LLONG, v); } ST_FUNC void vset(CType *type, int r, int v) { CValue cval; cval.i = v; vsetc(type, r, &cval); } static void vseti(int r, int v) { CType type; type.t = VT_INT; type.ref = NULL; vset(&type, r, v); } ST_FUNC void vpushv(SValue *v) { if (vtop >= vstack + (VSTACK_SIZE - 1)) tcc_error("memory full (vstack)"); vtop++; *vtop = *v; } static void vdup(void) { vpushv(vtop); } /* rotate n first stack elements to the bottom I1 ... In -> I2 ... In I1 [top is right] */ ST_FUNC void vrotb(int n) { int i; SValue tmp; tmp = vtop[-n + 1]; for(i=-n+1;i!=0;i++) vtop[i] = vtop[i+1]; vtop[0] = tmp; } /* rotate the n elements before entry e towards the top I1 ... In ... -> In I1 ... I(n-1) ... [top is right] */ ST_FUNC void vrote(SValue *e, int n) { int i; SValue tmp; tmp = *e; for(i = 0;i < n - 1; i++) e[-i] = e[-i - 1]; e[-n + 1] = tmp; } /* rotate n first stack elements to the top I1 ... In -> In I1 ... I(n-1) [top is right] */ ST_FUNC void vrott(int n) { vrote(vtop, n); } /* push a symbol value of TYPE */ static inline void vpushsym(CType *type, Sym *sym) { CValue cval; cval.i = 0; vsetc(type, VT_CONST | VT_SYM, &cval); vtop->sym = sym; } /* Return a static symbol pointing to a section */ ST_FUNC Sym *get_sym_ref(CType *type, Section *sec, unsigned long offset, unsigned long size) { int v; Sym *sym; v = anon_sym++; sym = global_identifier_push(v, type->t | VT_STATIC, 0); sym->type.ref = type->ref; sym->r = VT_CONST | VT_SYM; put_extern_sym(sym, sec, offset, size); return sym; } /* push a reference to a section offset by adding a dummy symbol */ static void vpush_ref(CType *type, Section *sec, unsigned long offset, unsigned long size) { vpushsym(type, get_sym_ref(type, sec, offset, size)); } /* define a new external reference to a symbol 'v' of type 'u' */ ST_FUNC Sym *external_global_sym(int v, CType *type, int r) { Sym *s; s = sym_find(v); if (!s) { /* push forward reference */ s = global_identifier_push(v, type->t | VT_EXTERN, 0); s->type.ref = type->ref; s->r = r | VT_CONST | VT_SYM; } else if (IS_ASM_SYM(s)) { s->type.t = type->t | (s->type.t & VT_EXTERN); s->type.ref = type->ref; update_storage(s); } return s; } /* Merge some type attributes. */ static void patch_type(Sym *sym, CType *type) { if (!(type->t & VT_EXTERN)) { if (!(sym->type.t & VT_EXTERN)) tcc_error("redefinition of '%s'", get_tok_str(sym->v, NULL)); sym->type.t &= ~VT_EXTERN; } if (IS_ASM_SYM(sym)) { /* stay static if both are static */ sym->type.t = type->t & (sym->type.t | ~VT_STATIC); sym->type.ref = type->ref; } if (!is_compatible_types(&sym->type, type)) { tcc_error("incompatible types for redefinition of '%s'", get_tok_str(sym->v, NULL)); } else if ((sym->type.t & VT_BTYPE) == VT_FUNC) { int static_proto = sym->type.t & VT_STATIC; /* warn if static follows non-static function declaration */ if ((type->t & VT_STATIC) && !static_proto && !(type->t & VT_INLINE)) tcc_warning("static storage ignored for redefinition of '%s'", get_tok_str(sym->v, NULL)); if (0 == (type->t & VT_EXTERN)) { /* put complete type, use static from prototype */ sym->type.t = (type->t & ~VT_STATIC) | static_proto; if (type->t & VT_INLINE) sym->type.t = type->t; sym->type.ref = type->ref; } } else { if ((sym->type.t & VT_ARRAY) && type->ref->c >= 0) { /* set array size if it was omitted in extern declaration */ if (sym->type.ref->c < 0) sym->type.ref->c = type->ref->c; else if (sym->type.ref->c != type->ref->c) tcc_error("conflicting type for '%s'", get_tok_str(sym->v, NULL)); } if ((type->t ^ sym->type.t) & VT_STATIC) tcc_warning("storage mismatch for redefinition of '%s'", get_tok_str(sym->v, NULL)); } } /* Merge some storage attributes. */ static void patch_storage(Sym *sym, AttributeDef *ad, CType *type) { if (type) patch_type(sym, type); #ifdef TCC_TARGET_PE if (sym->a.dllimport != ad->a.dllimport) tcc_error("incompatible dll linkage for redefinition of '%s'", get_tok_str(sym->v, NULL)); sym->a.dllexport |= ad->a.dllexport; #endif sym->a.weak |= ad->a.weak; if (ad->a.visibility) { int vis = sym->a.visibility; int vis2 = ad->a.visibility; if (vis == STV_DEFAULT) vis = vis2; else if (vis2 != STV_DEFAULT) vis = (vis < vis2) ? vis : vis2; sym->a.visibility = vis; } if (ad->a.aligned) sym->a.aligned = ad->a.aligned; if (ad->asm_label) sym->asm_label = ad->asm_label; update_storage(sym); } /* define a new external reference to a symbol 'v' */ static Sym *external_sym(int v, CType *type, int r, AttributeDef *ad) { Sym *s; s = sym_find(v); if (!s) { /* push forward reference */ s = sym_push(v, type, r | VT_CONST | VT_SYM, 0); s->type.t |= VT_EXTERN; s->a = ad->a; s->sym_scope = 0; } else { if (s->type.ref == func_old_type.ref) { s->type.ref = type->ref; s->r = r | VT_CONST | VT_SYM; s->type.t |= VT_EXTERN; } patch_storage(s, ad, type); } return s; } /* push a reference to global symbol v */ ST_FUNC void vpush_global_sym(CType *type, int v) { vpushsym(type, external_global_sym(v, type, 0)); } /* save registers up to (vtop - n) stack entry */ ST_FUNC void save_regs(int n) { SValue *p, *p1; for(p = vstack, p1 = vtop - n; p <= p1; p++) save_reg(p->r); } /* save r to the memory stack, and mark it as being free */ ST_FUNC void save_reg(int r) { save_reg_upstack(r, 0); } /* save r to the memory stack, and mark it as being free, if seen up to (vtop - n) stack entry */ ST_FUNC void save_reg_upstack(int r, int n) { int l, saved, size, align; SValue *p, *p1, sv; CType *type; if ((r &= VT_VALMASK) >= VT_CONST) return; if (nocode_wanted) return; /* modify all stack values */ saved = 0; l = 0; for(p = vstack, p1 = vtop - n; p <= p1; p++) { if ((p->r & VT_VALMASK) == r || ((p->type.t & VT_BTYPE) == VT_LLONG && (p->r2 & VT_VALMASK) == r)) { /* must save value on stack if not already done */ if (!saved) { /* NOTE: must reload 'r' because r might be equal to r2 */ r = p->r & VT_VALMASK; /* store register in the stack */ type = &p->type; if ((p->r & VT_LVAL) || (!is_float(type->t) && (type->t & VT_BTYPE) != VT_LLONG)) #if PTR_SIZE == 8 type = &char_pointer_type; #else type = &int_type; #endif size = type_size(type, &align); loc = (loc - size) & -align; sv.type.t = type->t; sv.r = VT_LOCAL | VT_LVAL; sv.c.i = loc; store(r, &sv); #if defined(TCC_TARGET_I386) || defined(TCC_TARGET_X86_64) /* x86 specific: need to pop fp register ST0 if saved */ if (r == TREG_ST0) { o(0xd8dd); /* fstp %st(0) */ } #endif #if PTR_SIZE == 4 /* special long long case */ if ((type->t & VT_BTYPE) == VT_LLONG) { sv.c.i += 4; store(p->r2, &sv); } #endif l = loc; saved = 1; } /* mark that stack entry as being saved on the stack */ if (p->r & VT_LVAL) { /* also clear the bounded flag because the relocation address of the function was stored in p->c.i */ p->r = (p->r & ~(VT_VALMASK | VT_BOUNDED)) | VT_LLOCAL; } else { p->r = lvalue_type(p->type.t) | VT_LOCAL; } p->r2 = VT_CONST; p->c.i = l; } } } #ifdef TCC_TARGET_ARM /* find a register of class 'rc2' with at most one reference on stack. * If none, call get_reg(rc) */ ST_FUNC int get_reg_ex(int rc, int rc2) { int r; SValue *p; for(r=0;r<NB_REGS;r++) { if (reg_classes[r] & rc2) { int n; n=0; for(p = vstack; p <= vtop; p++) { if ((p->r & VT_VALMASK) == r || (p->r2 & VT_VALMASK) == r) n++; } if (n <= 1) return r; } } return get_reg(rc); } #endif /* find a free register of class 'rc'. If none, save one register */ ST_FUNC int get_reg(int rc) { int r; SValue *p; /* find a free register */ for(r=0;r<NB_REGS;r++) { if (reg_classes[r] & rc) { if (nocode_wanted) return r; for(p=vstack;p<=vtop;p++) { if ((p->r & VT_VALMASK) == r || (p->r2 & VT_VALMASK) == r) goto notfound; } return r; } notfound: ; } /* no register left : free the first one on the stack (VERY IMPORTANT to start from the bottom to ensure that we don't spill registers used in gen_opi()) */ for(p=vstack;p<=vtop;p++) { /* look at second register (if long long) */ r = p->r2 & VT_VALMASK; if (r < VT_CONST && (reg_classes[r] & rc)) goto save_found; r = p->r & VT_VALMASK; if (r < VT_CONST && (reg_classes[r] & rc)) { save_found: save_reg(r); return r; } } /* Should never comes here */ return -1; } /* move register 's' (of type 't') to 'r', and flush previous value of r to memory if needed */ static void move_reg(int r, int s, int t) { SValue sv; if (r != s) { save_reg(r); sv.type.t = t; sv.type.ref = NULL; sv.r = s; sv.c.i = 0; load(r, &sv); } } /* get address of vtop (vtop MUST BE an lvalue) */ ST_FUNC void gaddrof(void) { vtop->r &= ~VT_LVAL; /* tricky: if saved lvalue, then we can go back to lvalue */ if ((vtop->r & VT_VALMASK) == VT_LLOCAL) vtop->r = (vtop->r & ~(VT_VALMASK | VT_LVAL_TYPE)) | VT_LOCAL | VT_LVAL; } #ifdef CONFIG_TCC_BCHECK /* generate lvalue bound code */ static void gbound(void) { int lval_type; CType type1; vtop->r &= ~VT_MUSTBOUND; /* if lvalue, then use checking code before dereferencing */ if (vtop->r & VT_LVAL) { /* if not VT_BOUNDED value, then make one */ if (!(vtop->r & VT_BOUNDED)) { lval_type = vtop->r & (VT_LVAL_TYPE | VT_LVAL); /* must save type because we must set it to int to get pointer */ type1 = vtop->type; vtop->type.t = VT_PTR; gaddrof(); vpushi(0); gen_bounded_ptr_add(); vtop->r |= lval_type; vtop->type = type1; } /* then check for dereferencing */ gen_bounded_ptr_deref(); } } #endif static void incr_bf_adr(int o) { vtop->type = char_pointer_type; gaddrof(); vpushi(o); gen_op('+'); vtop->type.t = (vtop->type.t & ~(VT_BTYPE|VT_DEFSIGN)) | (VT_BYTE|VT_UNSIGNED); vtop->r = (vtop->r & ~VT_LVAL_TYPE) | (VT_LVAL_BYTE|VT_LVAL_UNSIGNED|VT_LVAL); } /* single-byte load mode for packed or otherwise unaligned bitfields */ static void load_packed_bf(CType *type, int bit_pos, int bit_size) { int n, o, bits; save_reg_upstack(vtop->r, 1); vpush64(type->t & VT_BTYPE, 0); // B X bits = 0, o = bit_pos >> 3, bit_pos &= 7; do { vswap(); // X B incr_bf_adr(o); vdup(); // X B B n = 8 - bit_pos; if (n > bit_size) n = bit_size; if (bit_pos) vpushi(bit_pos), gen_op(TOK_SHR), bit_pos = 0; // X B Y if (n < 8) vpushi((1 << n) - 1), gen_op('&'); gen_cast(type); if (bits) vpushi(bits), gen_op(TOK_SHL); vrotb(3); // B Y X gen_op('|'); // B X bits += n, bit_size -= n, o = 1; } while (bit_size); vswap(), vpop(); if (!(type->t & VT_UNSIGNED)) { n = ((type->t & VT_BTYPE) == VT_LLONG ? 64 : 32) - bits; vpushi(n), gen_op(TOK_SHL); vpushi(n), gen_op(TOK_SAR); } } /* single-byte store mode for packed or otherwise unaligned bitfields */ static void store_packed_bf(int bit_pos, int bit_size) { int bits, n, o, m, c; c = (vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST; vswap(); // X B save_reg_upstack(vtop->r, 1); bits = 0, o = bit_pos >> 3, bit_pos &= 7; do { incr_bf_adr(o); // X B vswap(); //B X c ? vdup() : gv_dup(); // B V X vrott(3); // X B V if (bits) vpushi(bits), gen_op(TOK_SHR); if (bit_pos) vpushi(bit_pos), gen_op(TOK_SHL); n = 8 - bit_pos; if (n > bit_size) n = bit_size; if (n < 8) { m = ((1 << n) - 1) << bit_pos; vpushi(m), gen_op('&'); // X B V1 vpushv(vtop-1); // X B V1 B vpushi(m & 0x80 ? ~m & 0x7f : ~m); gen_op('&'); // X B V1 B1 gen_op('|'); // X B V2 } vdup(), vtop[-1] = vtop[-2]; // X B B V2 vstore(), vpop(); // X B bits += n, bit_size -= n, bit_pos = 0, o = 1; } while (bit_size); vpop(), vpop(); } static int adjust_bf(SValue *sv, int bit_pos, int bit_size) { int t; if (0 == sv->type.ref) return 0; t = sv->type.ref->auxtype; if (t != -1 && t != VT_STRUCT) { sv->type.t = (sv->type.t & ~VT_BTYPE) | t; sv->r = (sv->r & ~VT_LVAL_TYPE) | lvalue_type(sv->type.t); } return t; } /* store vtop a register belonging to class 'rc'. lvalues are converted to values. Cannot be used if cannot be converted to register value (such as structures). */ ST_FUNC int gv(int rc) { int r, bit_pos, bit_size, size, align, rc2; /* NOTE: get_reg can modify vstack[] */ if (vtop->type.t & VT_BITFIELD) { CType type; bit_pos = BIT_POS(vtop->type.t); bit_size = BIT_SIZE(vtop->type.t); /* remove bit field info to avoid loops */ vtop->type.t &= ~VT_STRUCT_MASK; type.ref = NULL; type.t = vtop->type.t & VT_UNSIGNED; if ((vtop->type.t & VT_BTYPE) == VT_BOOL) type.t |= VT_UNSIGNED; r = adjust_bf(vtop, bit_pos, bit_size); if ((vtop->type.t & VT_BTYPE) == VT_LLONG) type.t |= VT_LLONG; else type.t |= VT_INT; if (r == VT_STRUCT) { load_packed_bf(&type, bit_pos, bit_size); } else { int bits = (type.t & VT_BTYPE) == VT_LLONG ? 64 : 32; /* cast to int to propagate signedness in following ops */ gen_cast(&type); /* generate shifts */ vpushi(bits - (bit_pos + bit_size)); gen_op(TOK_SHL); vpushi(bits - bit_size); /* NOTE: transformed to SHR if unsigned */ gen_op(TOK_SAR); } r = gv(rc); } else { if (is_float(vtop->type.t) && (vtop->r & (VT_VALMASK | VT_LVAL)) == VT_CONST) { unsigned long offset; /* CPUs usually cannot use float constants, so we store them generically in data segment */ size = type_size(&vtop->type, &align); if (NODATA_WANTED) size = 0, align = 1; offset = section_add(data_section, size, align); vpush_ref(&vtop->type, data_section, offset, size); vswap(); init_putv(&vtop->type, data_section, offset); vtop->r |= VT_LVAL; } #ifdef CONFIG_TCC_BCHECK if (vtop->r & VT_MUSTBOUND) gbound(); #endif r = vtop->r & VT_VALMASK; rc2 = (rc & RC_FLOAT) ? RC_FLOAT : RC_INT; #ifndef TCC_TARGET_ARM64 if (rc == RC_IRET) rc2 = RC_LRET; #ifdef TCC_TARGET_X86_64 else if (rc == RC_FRET) rc2 = RC_QRET; #endif #endif /* need to reload if: - constant - lvalue (need to dereference pointer) - already a register, but not in the right class */ if (r >= VT_CONST || (vtop->r & VT_LVAL) || !(reg_classes[r] & rc) #if PTR_SIZE == 8 || ((vtop->type.t & VT_BTYPE) == VT_QLONG && !(reg_classes[vtop->r2] & rc2)) || ((vtop->type.t & VT_BTYPE) == VT_QFLOAT && !(reg_classes[vtop->r2] & rc2)) #else || ((vtop->type.t & VT_BTYPE) == VT_LLONG && !(reg_classes[vtop->r2] & rc2)) #endif ) { r = get_reg(rc); #if PTR_SIZE == 8 if (((vtop->type.t & VT_BTYPE) == VT_QLONG) || ((vtop->type.t & VT_BTYPE) == VT_QFLOAT)) { int addr_type = VT_LLONG, load_size = 8, load_type = ((vtop->type.t & VT_BTYPE) == VT_QLONG) ? VT_LLONG : VT_DOUBLE; #else if ((vtop->type.t & VT_BTYPE) == VT_LLONG) { int addr_type = VT_INT, load_size = 4, load_type = VT_INT; unsigned long long ll; #endif int r2, original_type; original_type = vtop->type.t; /* two register type load : expand to two words temporarily */ #if PTR_SIZE == 4 if ((vtop->r & (VT_VALMASK | VT_LVAL)) == VT_CONST) { /* load constant */ ll = vtop->c.i; vtop->c.i = ll; /* first word */ load(r, vtop); vtop->r = r; /* save register value */ vpushi(ll >> 32); /* second word */ } else #endif if (vtop->r & VT_LVAL) { /* We do not want to modifier the long long pointer here, so the safest (and less efficient) is to save all the other registers in the stack. XXX: totally inefficient. */ #if 0 save_regs(1); #else /* lvalue_save: save only if used further down the stack */ save_reg_upstack(vtop->r, 1); #endif /* load from memory */ vtop->type.t = load_type; load(r, vtop); vdup(); vtop[-1].r = r; /* save register value */ /* increment pointer to get second word */ vtop->type.t = addr_type; gaddrof(); vpushi(load_size); gen_op('+'); vtop->r |= VT_LVAL; vtop->type.t = load_type; } else { /* move registers */ load(r, vtop); vdup(); vtop[-1].r = r; /* save register value */ vtop->r = vtop[-1].r2; } /* Allocate second register. Here we rely on the fact that get_reg() tries first to free r2 of an SValue. */ r2 = get_reg(rc2); load(r2, vtop); vpop(); /* write second register */ vtop->r2 = r2; vtop->type.t = original_type; } else if ((vtop->r & VT_LVAL) && !is_float(vtop->type.t)) { int t1, t; /* lvalue of scalar type : need to use lvalue type because of possible cast */ t = vtop->type.t; t1 = t; /* compute memory access type */ if (vtop->r & VT_LVAL_BYTE) t = VT_BYTE; else if (vtop->r & VT_LVAL_SHORT) t = VT_SHORT; if (vtop->r & VT_LVAL_UNSIGNED) t |= VT_UNSIGNED; vtop->type.t = t; load(r, vtop); /* restore wanted type */ vtop->type.t = t1; } else { /* one register type load */ load(r, vtop); } } vtop->r = r; #ifdef TCC_TARGET_C67 /* uses register pairs for doubles */ if ((vtop->type.t & VT_BTYPE) == VT_DOUBLE) vtop->r2 = r+1; #endif } return r; } /* generate vtop[-1] and vtop[0] in resp. classes rc1 and rc2 */ ST_FUNC void gv2(int rc1, int rc2) { int v; /* generate more generic register first. But VT_JMP or VT_CMP values must be generated first in all cases to avoid possible reload errors */ v = vtop[0].r & VT_VALMASK; if (v != VT_CMP && (v & ~1) != VT_JMP && rc1 <= rc2) { vswap(); gv(rc1); vswap(); gv(rc2); /* test if reload is needed for first register */ if ((vtop[-1].r & VT_VALMASK) >= VT_CONST) { vswap(); gv(rc1); vswap(); } } else { gv(rc2); vswap(); gv(rc1); vswap(); /* test if reload is needed for first register */ if ((vtop[0].r & VT_VALMASK) >= VT_CONST) { gv(rc2); } } } #ifndef TCC_TARGET_ARM64 /* wrapper around RC_FRET to return a register by type */ static int rc_fret(int t) { #ifdef TCC_TARGET_X86_64 if (t == VT_LDOUBLE) { return RC_ST0; } #endif return RC_FRET; } #endif /* wrapper around REG_FRET to return a register by type */ static int reg_fret(int t) { #ifdef TCC_TARGET_X86_64 if (t == VT_LDOUBLE) { return TREG_ST0; } #endif return REG_FRET; } #if PTR_SIZE == 4 /* expand 64bit on stack in two ints */ static void lexpand(void) { int u, v; u = vtop->type.t & (VT_DEFSIGN | VT_UNSIGNED); v = vtop->r & (VT_VALMASK | VT_LVAL); if (v == VT_CONST) { vdup(); vtop[0].c.i >>= 32; } else if (v == (VT_LVAL|VT_CONST) || v == (VT_LVAL|VT_LOCAL)) { vdup(); vtop[0].c.i += 4; } else { gv(RC_INT); vdup(); vtop[0].r = vtop[-1].r2; vtop[0].r2 = vtop[-1].r2 = VT_CONST; } vtop[0].type.t = vtop[-1].type.t = VT_INT | u; } #endif #ifdef TCC_TARGET_ARM /* expand long long on stack */ ST_FUNC void lexpand_nr(void) { int u,v; u = vtop->type.t & (VT_DEFSIGN | VT_UNSIGNED); vdup(); vtop->r2 = VT_CONST; vtop->type.t = VT_INT | u; v=vtop[-1].r & (VT_VALMASK | VT_LVAL); if (v == VT_CONST) { vtop[-1].c.i = vtop->c.i; vtop->c.i = vtop->c.i >> 32; vtop->r = VT_CONST; } else if (v == (VT_LVAL|VT_CONST) || v == (VT_LVAL|VT_LOCAL)) { vtop->c.i += 4; vtop->r = vtop[-1].r; } else if (v > VT_CONST) { vtop--; lexpand(); } else vtop->r = vtop[-1].r2; vtop[-1].r2 = VT_CONST; vtop[-1].type.t = VT_INT | u; } #endif #if PTR_SIZE == 4 /* build a long long from two ints */ static void lbuild(int t) { gv2(RC_INT, RC_INT); vtop[-1].r2 = vtop[0].r; vtop[-1].type.t = t; vpop(); } #endif /* convert stack entry to register and duplicate its value in another register */ static void gv_dup(void) { int rc, t, r, r1; SValue sv; t = vtop->type.t; #if PTR_SIZE == 4 if ((t & VT_BTYPE) == VT_LLONG) { if (t & VT_BITFIELD) { gv(RC_INT); t = vtop->type.t; } lexpand(); gv_dup(); vswap(); vrotb(3); gv_dup(); vrotb(4); /* stack: H L L1 H1 */ lbuild(t); vrotb(3); vrotb(3); vswap(); lbuild(t); vswap(); } else #endif { /* duplicate value */ rc = RC_INT; sv.type.t = VT_INT; if (is_float(t)) { rc = RC_FLOAT; #ifdef TCC_TARGET_X86_64 if ((t & VT_BTYPE) == VT_LDOUBLE) { rc = RC_ST0; } #endif sv.type.t = t; } r = gv(rc); r1 = get_reg(rc); sv.r = r; sv.c.i = 0; load(r1, &sv); /* move r to r1 */ vdup(); /* duplicates value */ if (r != r1) vtop->r = r1; } } /* Generate value test * * Generate a test for any value (jump, comparison and integers) */ ST_FUNC int gvtst(int inv, int t) { int v = vtop->r & VT_VALMASK; if (v != VT_CMP && v != VT_JMP && v != VT_JMPI) { vpushi(0); gen_op(TOK_NE); } if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) { /* constant jmp optimization */ if ((vtop->c.i != 0) != inv) t = gjmp(t); vtop--; return t; } return gtst(inv, t); } #if PTR_SIZE == 4 /* generate CPU independent (unsigned) long long operations */ static void gen_opl(int op) { int t, a, b, op1, c, i; int func; unsigned short reg_iret = REG_IRET; unsigned short reg_lret = REG_LRET; SValue tmp; switch(op) { case '/': case TOK_PDIV: func = TOK___divdi3; goto gen_func; case TOK_UDIV: func = TOK___udivdi3; goto gen_func; case '%': func = TOK___moddi3; goto gen_mod_func; case TOK_UMOD: func = TOK___umoddi3; gen_mod_func: #ifdef TCC_ARM_EABI reg_iret = TREG_R2; reg_lret = TREG_R3; #endif gen_func: /* call generic long long function */ vpush_global_sym(&func_old_type, func); vrott(3); gfunc_call(2); vpushi(0); vtop->r = reg_iret; vtop->r2 = reg_lret; break; case '^': case '&': case '|': case '*': case '+': case '-': //pv("gen_opl A",0,2); t = vtop->type.t; vswap(); lexpand(); vrotb(3); lexpand(); /* stack: L1 H1 L2 H2 */ tmp = vtop[0]; vtop[0] = vtop[-3]; vtop[-3] = tmp; tmp = vtop[-2]; vtop[-2] = vtop[-3]; vtop[-3] = tmp; vswap(); /* stack: H1 H2 L1 L2 */ //pv("gen_opl B",0,4); if (op == '*') { vpushv(vtop - 1); vpushv(vtop - 1); gen_op(TOK_UMULL); lexpand(); /* stack: H1 H2 L1 L2 ML MH */ for(i=0;i<4;i++) vrotb(6); /* stack: ML MH H1 H2 L1 L2 */ tmp = vtop[0]; vtop[0] = vtop[-2]; vtop[-2] = tmp; /* stack: ML MH H1 L2 H2 L1 */ gen_op('*'); vrotb(3); vrotb(3); gen_op('*'); /* stack: ML MH M1 M2 */ gen_op('+'); gen_op('+'); } else if (op == '+' || op == '-') { /* XXX: add non carry method too (for MIPS or alpha) */ if (op == '+') op1 = TOK_ADDC1; else op1 = TOK_SUBC1; gen_op(op1); /* stack: H1 H2 (L1 op L2) */ vrotb(3); vrotb(3); gen_op(op1 + 1); /* TOK_xxxC2 */ } else { gen_op(op); /* stack: H1 H2 (L1 op L2) */ vrotb(3); vrotb(3); /* stack: (L1 op L2) H1 H2 */ gen_op(op); /* stack: (L1 op L2) (H1 op H2) */ } /* stack: L H */ lbuild(t); break; case TOK_SAR: case TOK_SHR: case TOK_SHL: if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) { t = vtop[-1].type.t; vswap(); lexpand(); vrotb(3); /* stack: L H shift */ c = (int)vtop->c.i; /* constant: simpler */ /* NOTE: all comments are for SHL. the other cases are done by swapping words */ vpop(); if (op != TOK_SHL) vswap(); if (c >= 32) { /* stack: L H */ vpop(); if (c > 32) { vpushi(c - 32); gen_op(op); } if (op != TOK_SAR) { vpushi(0); } else { gv_dup(); vpushi(31); gen_op(TOK_SAR); } vswap(); } else { vswap(); gv_dup(); /* stack: H L L */ vpushi(c); gen_op(op); vswap(); vpushi(32 - c); if (op == TOK_SHL) gen_op(TOK_SHR); else gen_op(TOK_SHL); vrotb(3); /* stack: L L H */ vpushi(c); if (op == TOK_SHL) gen_op(TOK_SHL); else gen_op(TOK_SHR); gen_op('|'); } if (op != TOK_SHL) vswap(); lbuild(t); } else { /* XXX: should provide a faster fallback on x86 ? */ switch(op) { case TOK_SAR: func = TOK___ashrdi3; goto gen_func; case TOK_SHR: func = TOK___lshrdi3; goto gen_func; case TOK_SHL: func = TOK___ashldi3; goto gen_func; } } break; default: /* compare operations */ t = vtop->type.t; vswap(); lexpand(); vrotb(3); lexpand(); /* stack: L1 H1 L2 H2 */ tmp = vtop[-1]; vtop[-1] = vtop[-2]; vtop[-2] = tmp; /* stack: L1 L2 H1 H2 */ /* compare high */ op1 = op; /* when values are equal, we need to compare low words. since the jump is inverted, we invert the test too. */ if (op1 == TOK_LT) op1 = TOK_LE; else if (op1 == TOK_GT) op1 = TOK_GE; else if (op1 == TOK_ULT) op1 = TOK_ULE; else if (op1 == TOK_UGT) op1 = TOK_UGE; a = 0; b = 0; gen_op(op1); if (op == TOK_NE) { b = gvtst(0, 0); } else { a = gvtst(1, 0); if (op != TOK_EQ) { /* generate non equal test */ vpushi(TOK_NE); vtop->r = VT_CMP; b = gvtst(0, 0); } } /* compare low. Always unsigned */ op1 = op; if (op1 == TOK_LT) op1 = TOK_ULT; else if (op1 == TOK_LE) op1 = TOK_ULE; else if (op1 == TOK_GT) op1 = TOK_UGT; else if (op1 == TOK_GE) op1 = TOK_UGE; gen_op(op1); a = gvtst(1, a); gsym(b); vseti(VT_JMPI, a); break; } } #endif static uint64_t gen_opic_sdiv(uint64_t a, uint64_t b) { uint64_t x = (a >> 63 ? -a : a) / (b >> 63 ? -b : b); return (a ^ b) >> 63 ? -x : x; } static int gen_opic_lt(uint64_t a, uint64_t b) { return (a ^ (uint64_t)1 << 63) < (b ^ (uint64_t)1 << 63); } /* handle integer constant optimizations and various machine independent opt */ static void gen_opic(int op) { SValue *v1 = vtop - 1; SValue *v2 = vtop; int t1 = v1->type.t & VT_BTYPE; int t2 = v2->type.t & VT_BTYPE; int c1 = (v1->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST; int c2 = (v2->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST; uint64_t l1 = c1 ? v1->c.i : 0; uint64_t l2 = c2 ? v2->c.i : 0; int shm = (t1 == VT_LLONG) ? 63 : 31; if (t1 != VT_LLONG && (PTR_SIZE != 8 || t1 != VT_PTR)) l1 = ((uint32_t)l1 | (v1->type.t & VT_UNSIGNED ? 0 : -(l1 & 0x80000000))); if (t2 != VT_LLONG && (PTR_SIZE != 8 || t2 != VT_PTR)) l2 = ((uint32_t)l2 | (v2->type.t & VT_UNSIGNED ? 0 : -(l2 & 0x80000000))); if (c1 && c2) { switch(op) { case '+': l1 += l2; break; case '-': l1 -= l2; break; case '&': l1 &= l2; break; case '^': l1 ^= l2; break; case '|': l1 |= l2; break; case '*': l1 *= l2; break; case TOK_PDIV: case '/': case '%': case TOK_UDIV: case TOK_UMOD: /* if division by zero, generate explicit division */ if (l2 == 0) { if (const_wanted) tcc_error("division by zero in constant"); goto general_case; } switch(op) { default: l1 = gen_opic_sdiv(l1, l2); break; case '%': l1 = l1 - l2 * gen_opic_sdiv(l1, l2); break; case TOK_UDIV: l1 = l1 / l2; break; case TOK_UMOD: l1 = l1 % l2; break; } break; case TOK_SHL: l1 <<= (l2 & shm); break; case TOK_SHR: l1 >>= (l2 & shm); break; case TOK_SAR: l1 = (l1 >> 63) ? ~(~l1 >> (l2 & shm)) : l1 >> (l2 & shm); break; /* tests */ case TOK_ULT: l1 = l1 < l2; break; case TOK_UGE: l1 = l1 >= l2; break; case TOK_EQ: l1 = l1 == l2; break; case TOK_NE: l1 = l1 != l2; break; case TOK_ULE: l1 = l1 <= l2; break; case TOK_UGT: l1 = l1 > l2; break; case TOK_LT: l1 = gen_opic_lt(l1, l2); break; case TOK_GE: l1 = !gen_opic_lt(l1, l2); break; case TOK_LE: l1 = !gen_opic_lt(l2, l1); break; case TOK_GT: l1 = gen_opic_lt(l2, l1); break; /* logical */ case TOK_LAND: l1 = l1 && l2; break; case TOK_LOR: l1 = l1 || l2; break; default: goto general_case; } if (t1 != VT_LLONG && (PTR_SIZE != 8 || t1 != VT_PTR)) l1 = ((uint32_t)l1 | (v1->type.t & VT_UNSIGNED ? 0 : -(l1 & 0x80000000))); v1->c.i = l1; vtop--; } else { /* if commutative ops, put c2 as constant */ if (c1 && (op == '+' || op == '&' || op == '^' || op == '|' || op == '*')) { vswap(); c2 = c1; //c = c1, c1 = c2, c2 = c; l2 = l1; //l = l1, l1 = l2, l2 = l; } if (!const_wanted && c1 && ((l1 == 0 && (op == TOK_SHL || op == TOK_SHR || op == TOK_SAR)) || (l1 == -1 && op == TOK_SAR))) { /* treat (0 << x), (0 >> x) and (-1 >> x) as constant */ vtop--; } else if (!const_wanted && c2 && ((l2 == 0 && (op == '&' || op == '*')) || (op == '|' && (l2 == -1 || (l2 == 0xFFFFFFFF && t2 != VT_LLONG))) || (l2 == 1 && (op == '%' || op == TOK_UMOD)))) { /* treat (x & 0), (x * 0), (x | -1) and (x % 1) as constant */ if (l2 == 1) vtop->c.i = 0; vswap(); vtop--; } else if (c2 && (((op == '*' || op == '/' || op == TOK_UDIV || op == TOK_PDIV) && l2 == 1) || ((op == '+' || op == '-' || op == '|' || op == '^' || op == TOK_SHL || op == TOK_SHR || op == TOK_SAR) && l2 == 0) || (op == '&' && (l2 == -1 || (l2 == 0xFFFFFFFF && t2 != VT_LLONG))))) { /* filter out NOP operations like x*1, x-0, x&-1... */ vtop--; } else if (c2 && (op == '*' || op == TOK_PDIV || op == TOK_UDIV)) { /* try to use shifts instead of muls or divs */ if (l2 > 0 && (l2 & (l2 - 1)) == 0) { int n = -1; while (l2) { l2 >>= 1; n++; } vtop->c.i = n; if (op == '*') op = TOK_SHL; else if (op == TOK_PDIV) op = TOK_SAR; else op = TOK_SHR; } goto general_case; } else if (c2 && (op == '+' || op == '-') && (((vtop[-1].r & (VT_VALMASK | VT_LVAL | VT_SYM)) == (VT_CONST | VT_SYM)) || (vtop[-1].r & (VT_VALMASK | VT_LVAL)) == VT_LOCAL)) { /* symbol + constant case */ if (op == '-') l2 = -l2; l2 += vtop[-1].c.i; /* The backends can't always deal with addends to symbols larger than +-1<<31. Don't construct such. */ if ((int)l2 != l2) goto general_case; vtop--; vtop->c.i = l2; } else { general_case: /* call low level op generator */ if (t1 == VT_LLONG || t2 == VT_LLONG || (PTR_SIZE == 8 && (t1 == VT_PTR || t2 == VT_PTR))) gen_opl(op); else gen_opi(op); } } } /* generate a floating point operation with constant propagation */ static void gen_opif(int op) { int c1, c2; SValue *v1, *v2; #if defined _MSC_VER && defined _AMD64_ /* avoid bad optimization with f1 -= f2 for f1:-0.0, f2:0.0 */ volatile #endif long double f1, f2; v1 = vtop - 1; v2 = vtop; /* currently, we cannot do computations with forward symbols */ c1 = (v1->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST; c2 = (v2->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST; if (c1 && c2) { if (v1->type.t == VT_FLOAT) { f1 = v1->c.f; f2 = v2->c.f; } else if (v1->type.t == VT_DOUBLE) { f1 = v1->c.d; f2 = v2->c.d; } else { f1 = v1->c.ld; f2 = v2->c.ld; } /* NOTE: we only do constant propagation if finite number (not NaN or infinity) (ANSI spec) */ if (!ieee_finite(f1) || !ieee_finite(f2)) goto general_case; switch(op) { case '+': f1 += f2; break; case '-': f1 -= f2; break; case '*': f1 *= f2; break; case '/': if (f2 == 0.0) { if (const_wanted) tcc_error("division by zero in constant"); goto general_case; } f1 /= f2; break; /* XXX: also handles tests ? */ default: goto general_case; } /* XXX: overflow test ? */ if (v1->type.t == VT_FLOAT) { v1->c.f = f1; } else if (v1->type.t == VT_DOUBLE) { v1->c.d = f1; } else { v1->c.ld = f1; } vtop--; } else { general_case: gen_opf(op); } } static int pointed_size(CType *type) { int align; return type_size(pointed_type(type), &align); } static void vla_runtime_pointed_size(CType *type) { int align; vla_runtime_type_size(pointed_type(type), &align); } static inline int is_null_pointer(SValue *p) { if ((p->r & (VT_VALMASK | VT_LVAL | VT_SYM)) != VT_CONST) return 0; return ((p->type.t & VT_BTYPE) == VT_INT && (uint32_t)p->c.i == 0) || ((p->type.t & VT_BTYPE) == VT_LLONG && p->c.i == 0) || ((p->type.t & VT_BTYPE) == VT_PTR && (PTR_SIZE == 4 ? (uint32_t)p->c.i == 0 : p->c.i == 0)); } static inline int is_integer_btype(int bt) { return (bt == VT_BYTE || bt == VT_SHORT || bt == VT_INT || bt == VT_LLONG); } /* check types for comparison or subtraction of pointers */ static void check_comparison_pointer_types(SValue *p1, SValue *p2, int op) { CType *type1, *type2, tmp_type1, tmp_type2; int bt1, bt2; /* null pointers are accepted for all comparisons as gcc */ if (is_null_pointer(p1) || is_null_pointer(p2)) return; type1 = &p1->type; type2 = &p2->type; bt1 = type1->t & VT_BTYPE; bt2 = type2->t & VT_BTYPE; /* accept comparison between pointer and integer with a warning */ if ((is_integer_btype(bt1) || is_integer_btype(bt2)) && op != '-') { if (op != TOK_LOR && op != TOK_LAND ) tcc_warning("comparison between pointer and integer"); return; } /* both must be pointers or implicit function pointers */ if (bt1 == VT_PTR) { type1 = pointed_type(type1); } else if (bt1 != VT_FUNC) goto invalid_operands; if (bt2 == VT_PTR) { type2 = pointed_type(type2); } else if (bt2 != VT_FUNC) { invalid_operands: tcc_error("invalid operands to binary %s", get_tok_str(op, NULL)); } if ((type1->t & VT_BTYPE) == VT_VOID || (type2->t & VT_BTYPE) == VT_VOID) return; tmp_type1 = *type1; tmp_type2 = *type2; tmp_type1.t &= ~(VT_DEFSIGN | VT_UNSIGNED | VT_CONSTANT | VT_VOLATILE); tmp_type2.t &= ~(VT_DEFSIGN | VT_UNSIGNED | VT_CONSTANT | VT_VOLATILE); if (!is_compatible_types(&tmp_type1, &tmp_type2)) { /* gcc-like error if '-' is used */ if (op == '-') goto invalid_operands; else tcc_warning("comparison of distinct pointer types lacks a cast"); } } /* generic gen_op: handles types problems */ ST_FUNC void gen_op(int op) { int u, t1, t2, bt1, bt2, t; CType type1; redo: t1 = vtop[-1].type.t; t2 = vtop[0].type.t; bt1 = t1 & VT_BTYPE; bt2 = t2 & VT_BTYPE; if (bt1 == VT_STRUCT || bt2 == VT_STRUCT) { tcc_error("operation on a struct"); } else if (bt1 == VT_FUNC || bt2 == VT_FUNC) { if (bt2 == VT_FUNC) { mk_pointer(&vtop->type); gaddrof(); } if (bt1 == VT_FUNC) { vswap(); mk_pointer(&vtop->type); gaddrof(); vswap(); } goto redo; } else if (bt1 == VT_PTR || bt2 == VT_PTR) { /* at least one operand is a pointer */ /* relational op: must be both pointers */ if (op >= TOK_ULT && op <= TOK_LOR) { check_comparison_pointer_types(vtop - 1, vtop, op); /* pointers are handled are unsigned */ #if PTR_SIZE == 8 t = VT_LLONG | VT_UNSIGNED; #else t = VT_INT | VT_UNSIGNED; #endif goto std_op; } /* if both pointers, then it must be the '-' op */ if (bt1 == VT_PTR && bt2 == VT_PTR) { if (op != '-') tcc_error("cannot use pointers here"); check_comparison_pointer_types(vtop - 1, vtop, op); /* XXX: check that types are compatible */ if (vtop[-1].type.t & VT_VLA) { vla_runtime_pointed_size(&vtop[-1].type); } else { vpushi(pointed_size(&vtop[-1].type)); } vrott(3); gen_opic(op); vtop->type.t = ptrdiff_type.t; vswap(); gen_op(TOK_PDIV); } else { /* exactly one pointer : must be '+' or '-'. */ if (op != '-' && op != '+') tcc_error("cannot use pointers here"); /* Put pointer as first operand */ if (bt2 == VT_PTR) { vswap(); t = t1, t1 = t2, t2 = t; } #if PTR_SIZE == 4 if ((vtop[0].type.t & VT_BTYPE) == VT_LLONG) /* XXX: truncate here because gen_opl can't handle ptr + long long */ gen_cast_s(VT_INT); #endif type1 = vtop[-1].type; type1.t &= ~VT_ARRAY; if (vtop[-1].type.t & VT_VLA) vla_runtime_pointed_size(&vtop[-1].type); else { u = pointed_size(&vtop[-1].type); if (u < 0) tcc_error("unknown array element size"); #if PTR_SIZE == 8 vpushll(u); #else /* XXX: cast to int ? (long long case) */ vpushi(u); #endif } gen_op('*'); #if 0 /* #ifdef CONFIG_TCC_BCHECK The main reason to removing this code: #include <stdio.h> int main () { int v[10]; int i = 10; int j = 9; fprintf(stderr, "v+i-j = %p\n", v+i-j); fprintf(stderr, "v+(i-j) = %p\n", v+(i-j)); } When this code is on. then the output looks like v+i-j = 0xfffffffe v+(i-j) = 0xbff84000 */ /* if evaluating constant expression, no code should be generated, so no bound check */ if (tcc_state->do_bounds_check && !const_wanted) { /* if bounded pointers, we generate a special code to test bounds */ if (op == '-') { vpushi(0); vswap(); gen_op('-'); } gen_bounded_ptr_add(); } else #endif { gen_opic(op); } /* put again type if gen_opic() swaped operands */ vtop->type = type1; } } else if (is_float(bt1) || is_float(bt2)) { /* compute bigger type and do implicit casts */ if (bt1 == VT_LDOUBLE || bt2 == VT_LDOUBLE) { t = VT_LDOUBLE; } else if (bt1 == VT_DOUBLE || bt2 == VT_DOUBLE) { t = VT_DOUBLE; } else { t = VT_FLOAT; } /* floats can only be used for a few operations */ if (op != '+' && op != '-' && op != '*' && op != '/' && (op < TOK_ULT || op > TOK_GT)) tcc_error("invalid operands for binary operation"); goto std_op; } else if (op == TOK_SHR || op == TOK_SAR || op == TOK_SHL) { t = bt1 == VT_LLONG ? VT_LLONG : VT_INT; if ((t1 & (VT_BTYPE | VT_UNSIGNED | VT_BITFIELD)) == (t | VT_UNSIGNED)) t |= VT_UNSIGNED; t |= (VT_LONG & t1); goto std_op; } else if (bt1 == VT_LLONG || bt2 == VT_LLONG) { /* cast to biggest op */ t = VT_LLONG | VT_LONG; if (bt1 == VT_LLONG) t &= t1; if (bt2 == VT_LLONG) t &= t2; /* convert to unsigned if it does not fit in a long long */ if ((t1 & (VT_BTYPE | VT_UNSIGNED | VT_BITFIELD)) == (VT_LLONG | VT_UNSIGNED) || (t2 & (VT_BTYPE | VT_UNSIGNED | VT_BITFIELD)) == (VT_LLONG | VT_UNSIGNED)) t |= VT_UNSIGNED; goto std_op; } else { /* integer operations */ t = VT_INT | (VT_LONG & (t1 | t2)); /* convert to unsigned if it does not fit in an integer */ if ((t1 & (VT_BTYPE | VT_UNSIGNED | VT_BITFIELD)) == (VT_INT | VT_UNSIGNED) || (t2 & (VT_BTYPE | VT_UNSIGNED | VT_BITFIELD)) == (VT_INT | VT_UNSIGNED)) t |= VT_UNSIGNED; std_op: /* XXX: currently, some unsigned operations are explicit, so we modify them here */ if (t & VT_UNSIGNED) { if (op == TOK_SAR) op = TOK_SHR; else if (op == '/') op = TOK_UDIV; else if (op == '%') op = TOK_UMOD; else if (op == TOK_LT) op = TOK_ULT; else if (op == TOK_GT) op = TOK_UGT; else if (op == TOK_LE) op = TOK_ULE; else if (op == TOK_GE) op = TOK_UGE; } vswap(); type1.t = t; type1.ref = NULL; gen_cast(&type1); vswap(); /* special case for shifts and long long: we keep the shift as an integer */ if (op == TOK_SHR || op == TOK_SAR || op == TOK_SHL) type1.t = VT_INT; gen_cast(&type1); if (is_float(t)) gen_opif(op); else gen_opic(op); if (op >= TOK_ULT && op <= TOK_GT) { /* relational op: the result is an int */ vtop->type.t = VT_INT; } else { vtop->type.t = t; } } // Make sure that we have converted to an rvalue: if (vtop->r & VT_LVAL) gv(is_float(vtop->type.t & VT_BTYPE) ? RC_FLOAT : RC_INT); } #ifndef TCC_TARGET_ARM /* generic itof for unsigned long long case */ static void gen_cvt_itof1(int t) { #ifdef TCC_TARGET_ARM64 gen_cvt_itof(t); #else if ((vtop->type.t & (VT_BTYPE | VT_UNSIGNED)) == (VT_LLONG | VT_UNSIGNED)) { if (t == VT_FLOAT) vpush_global_sym(&func_old_type, TOK___floatundisf); #if LDOUBLE_SIZE != 8 else if (t == VT_LDOUBLE) vpush_global_sym(&func_old_type, TOK___floatundixf); #endif else vpush_global_sym(&func_old_type, TOK___floatundidf); vrott(2); gfunc_call(1); vpushi(0); vtop->r = reg_fret(t); } else { gen_cvt_itof(t); } #endif } #endif /* generic ftoi for unsigned long long case */ static void gen_cvt_ftoi1(int t) { #ifdef TCC_TARGET_ARM64 gen_cvt_ftoi(t); #else int st; if (t == (VT_LLONG | VT_UNSIGNED)) { /* not handled natively */ st = vtop->type.t & VT_BTYPE; if (st == VT_FLOAT) vpush_global_sym(&func_old_type, TOK___fixunssfdi); #if LDOUBLE_SIZE != 8 else if (st == VT_LDOUBLE) vpush_global_sym(&func_old_type, TOK___fixunsxfdi); #endif else vpush_global_sym(&func_old_type, TOK___fixunsdfdi); vrott(2); gfunc_call(1); vpushi(0); vtop->r = REG_IRET; vtop->r2 = REG_LRET; } else { gen_cvt_ftoi(t); } #endif } /* force char or short cast */ static void force_charshort_cast(int t) { int bits, dbt; /* cannot cast static initializers */ if (STATIC_DATA_WANTED) return; dbt = t & VT_BTYPE; /* XXX: add optimization if lvalue : just change type and offset */ if (dbt == VT_BYTE) bits = 8; else bits = 16; if (t & VT_UNSIGNED) { vpushi((1 << bits) - 1); gen_op('&'); } else { if ((vtop->type.t & VT_BTYPE) == VT_LLONG) bits = 64 - bits; else bits = 32 - bits; vpushi(bits); gen_op(TOK_SHL); /* result must be signed or the SAR is converted to an SHL This was not the case when "t" was a signed short and the last value on the stack was an unsigned int */ vtop->type.t &= ~VT_UNSIGNED; vpushi(bits); gen_op(TOK_SAR); } } /* cast 'vtop' to 'type'. Casting to bitfields is forbidden. */ static void gen_cast_s(int t) { CType type; type.t = t; type.ref = NULL; gen_cast(&type); } static void gen_cast(CType *type) { int sbt, dbt, sf, df, c, p; /* special delayed cast for char/short */ /* XXX: in some cases (multiple cascaded casts), it may still be incorrect */ if (vtop->r & VT_MUSTCAST) { vtop->r &= ~VT_MUSTCAST; force_charshort_cast(vtop->type.t); } /* bitfields first get cast to ints */ if (vtop->type.t & VT_BITFIELD) { gv(RC_INT); } dbt = type->t & (VT_BTYPE | VT_UNSIGNED); sbt = vtop->type.t & (VT_BTYPE | VT_UNSIGNED); if (sbt != dbt) { sf = is_float(sbt); df = is_float(dbt); c = (vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST; p = (vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == (VT_CONST | VT_SYM); #if !defined TCC_IS_NATIVE && !defined TCC_IS_NATIVE_387 c &= dbt != VT_LDOUBLE; #endif if (c) { /* constant case: we can do it now */ /* XXX: in ISOC, cannot do it if error in convert */ if (sbt == VT_FLOAT) vtop->c.ld = vtop->c.f; else if (sbt == VT_DOUBLE) vtop->c.ld = vtop->c.d; if (df) { if ((sbt & VT_BTYPE) == VT_LLONG) { if ((sbt & VT_UNSIGNED) || !(vtop->c.i >> 63)) vtop->c.ld = vtop->c.i; else vtop->c.ld = -(long double)-vtop->c.i; } else if(!sf) { if ((sbt & VT_UNSIGNED) || !(vtop->c.i >> 31)) vtop->c.ld = (uint32_t)vtop->c.i; else vtop->c.ld = -(long double)-(uint32_t)vtop->c.i; } if (dbt == VT_FLOAT) vtop->c.f = (float)vtop->c.ld; else if (dbt == VT_DOUBLE) vtop->c.d = (double)vtop->c.ld; } else if (sf && dbt == (VT_LLONG|VT_UNSIGNED)) { vtop->c.i = vtop->c.ld; } else if (sf && dbt == VT_BOOL) { vtop->c.i = (vtop->c.ld != 0); } else { if(sf) vtop->c.i = vtop->c.ld; else if (sbt == (VT_LLONG|VT_UNSIGNED)) ; else if (sbt & VT_UNSIGNED) vtop->c.i = (uint32_t)vtop->c.i; #if PTR_SIZE == 8 else if (sbt == VT_PTR) ; #endif else if (sbt != VT_LLONG) vtop->c.i = ((uint32_t)vtop->c.i | -(vtop->c.i & 0x80000000)); if (dbt == (VT_LLONG|VT_UNSIGNED)) ; else if (dbt == VT_BOOL) vtop->c.i = (vtop->c.i != 0); #if PTR_SIZE == 8 else if (dbt == VT_PTR) ; #endif else if (dbt != VT_LLONG) { uint32_t m = ((dbt & VT_BTYPE) == VT_BYTE ? 0xff : (dbt & VT_BTYPE) == VT_SHORT ? 0xffff : 0xffffffff); vtop->c.i &= m; if (!(dbt & VT_UNSIGNED)) vtop->c.i |= -(vtop->c.i & ((m >> 1) + 1)); } } } else if (p && dbt == VT_BOOL) { vtop->r = VT_CONST; vtop->c.i = 1; } else { /* non constant case: generate code */ if (sf && df) { /* convert from fp to fp */ gen_cvt_ftof(dbt); } else if (df) { /* convert int to fp */ gen_cvt_itof1(dbt); } else if (sf) { /* convert fp to int */ if (dbt == VT_BOOL) { vpushi(0); gen_op(TOK_NE); } else { /* we handle char/short/etc... with generic code */ if (dbt != (VT_INT | VT_UNSIGNED) && dbt != (VT_LLONG | VT_UNSIGNED) && dbt != VT_LLONG) dbt = VT_INT; gen_cvt_ftoi1(dbt); if (dbt == VT_INT && (type->t & (VT_BTYPE | VT_UNSIGNED)) != dbt) { /* additional cast for char/short... */ vtop->type.t = dbt; gen_cast(type); } } #if PTR_SIZE == 4 } else if ((dbt & VT_BTYPE) == VT_LLONG) { if ((sbt & VT_BTYPE) != VT_LLONG) { /* scalar to long long */ /* machine independent conversion */ gv(RC_INT); /* generate high word */ if (sbt == (VT_INT | VT_UNSIGNED)) { vpushi(0); gv(RC_INT); } else { if (sbt == VT_PTR) { /* cast from pointer to int before we apply shift operation, which pointers don't support*/ gen_cast_s(VT_INT); } gv_dup(); vpushi(31); gen_op(TOK_SAR); } /* patch second register */ vtop[-1].r2 = vtop->r; vpop(); } #else } else if ((dbt & VT_BTYPE) == VT_LLONG || (dbt & VT_BTYPE) == VT_PTR || (dbt & VT_BTYPE) == VT_FUNC) { if ((sbt & VT_BTYPE) != VT_LLONG && (sbt & VT_BTYPE) != VT_PTR && (sbt & VT_BTYPE) != VT_FUNC) { /* need to convert from 32bit to 64bit */ gv(RC_INT); if (sbt != (VT_INT | VT_UNSIGNED)) { #if defined(TCC_TARGET_ARM64) gen_cvt_sxtw(); #elif defined(TCC_TARGET_X86_64) int r = gv(RC_INT); /* x86_64 specific: movslq */ o(0x6348); o(0xc0 + (REG_VALUE(r) << 3) + REG_VALUE(r)); #else #error #endif } } #endif } else if (dbt == VT_BOOL) { /* scalar to bool */ vpushi(0); gen_op(TOK_NE); } else if ((dbt & VT_BTYPE) == VT_BYTE || (dbt & VT_BTYPE) == VT_SHORT) { if (sbt == VT_PTR) { vtop->type.t = VT_INT; tcc_warning("nonportable conversion from pointer to char/short"); } force_charshort_cast(dbt); #if PTR_SIZE == 4 } else if ((dbt & VT_BTYPE) == VT_INT) { /* scalar to int */ if ((sbt & VT_BTYPE) == VT_LLONG) { /* from long long: just take low order word */ lexpand(); vpop(); } /* if lvalue and single word type, nothing to do because the lvalue already contains the real type size (see VT_LVAL_xxx constants) */ #endif } } } else if ((dbt & VT_BTYPE) == VT_PTR && !(vtop->r & VT_LVAL)) { /* if we are casting between pointer types, we must update the VT_LVAL_xxx size */ vtop->r = (vtop->r & ~VT_LVAL_TYPE) | (lvalue_type(type->ref->type.t) & VT_LVAL_TYPE); } vtop->type = *type; } /* return type size as known at compile time. Put alignment at 'a' */ ST_FUNC int type_size(CType *type, int *a) { Sym *s; int bt; bt = type->t & VT_BTYPE; if (bt == VT_STRUCT) { /* struct/union */ s = type->ref; *a = s->r; return s->c; } else if (bt == VT_PTR) { if (type->t & VT_ARRAY) { int ts; s = type->ref; ts = type_size(&s->type, a); if (ts < 0 && s->c < 0) ts = -ts; return ts * s->c; } else { *a = PTR_SIZE; return PTR_SIZE; } } else if (IS_ENUM(type->t) && type->ref->c == -1) { return -1; /* incomplete enum */ } else if (bt == VT_LDOUBLE) { *a = LDOUBLE_ALIGN; return LDOUBLE_SIZE; } else if (bt == VT_DOUBLE || bt == VT_LLONG) { #ifdef TCC_TARGET_I386 #ifdef TCC_TARGET_PE *a = 8; #else *a = 4; #endif #elif defined(TCC_TARGET_ARM) #ifdef TCC_ARM_EABI *a = 8; #else *a = 4; #endif #else *a = 8; #endif return 8; } else if (bt == VT_INT || bt == VT_FLOAT) { *a = 4; return 4; } else if (bt == VT_SHORT) { *a = 2; return 2; } else if (bt == VT_QLONG || bt == VT_QFLOAT) { *a = 8; return 16; } else { /* char, void, function, _Bool */ *a = 1; return 1; } } /* push type size as known at runtime time on top of value stack. Put alignment at 'a' */ ST_FUNC void vla_runtime_type_size(CType *type, int *a) { if (type->t & VT_VLA) { type_size(&type->ref->type, a); vset(&int_type, VT_LOCAL|VT_LVAL, type->ref->c); } else { vpushi(type_size(type, a)); } } static void vla_sp_restore(void) { if (vlas_in_scope) { gen_vla_sp_restore(vla_sp_loc); } } static void vla_sp_restore_root(void) { if (vlas_in_scope) { gen_vla_sp_restore(vla_sp_root_loc); } } /* return the pointed type of t */ static inline CType *pointed_type(CType *type) { return &type->ref->type; } /* modify type so that its it is a pointer to type. */ ST_FUNC void mk_pointer(CType *type) { Sym *s; s = sym_push(SYM_FIELD, type, 0, -1); type->t = VT_PTR | (type->t & VT_STORAGE); type->ref = s; } /* compare function types. OLD functions match any new functions */ static int is_compatible_func(CType *type1, CType *type2) { Sym *s1, *s2; s1 = type1->ref; s2 = type2->ref; if (!is_compatible_types(&s1->type, &s2->type)) return 0; /* check func_call */ if (s1->f.func_call != s2->f.func_call) return 0; /* XXX: not complete */ if (s1->f.func_type == FUNC_OLD || s2->f.func_type == FUNC_OLD) return 1; if (s1->f.func_type != s2->f.func_type) return 0; while (s1 != NULL) { if (s2 == NULL) return 0; if (!is_compatible_unqualified_types(&s1->type, &s2->type)) return 0; s1 = s1->next; s2 = s2->next; } if (s2) return 0; return 1; } /* return true if type1 and type2 are the same. If unqualified is true, qualifiers on the types are ignored. - enums are not checked as gcc __builtin_types_compatible_p () */ static int compare_types(CType *type1, CType *type2, int unqualified) { int bt1, t1, t2; t1 = type1->t & VT_TYPE; t2 = type2->t & VT_TYPE; if (unqualified) { /* strip qualifiers before comparing */ t1 &= ~(VT_CONSTANT | VT_VOLATILE); t2 &= ~(VT_CONSTANT | VT_VOLATILE); } /* Default Vs explicit signedness only matters for char */ if ((t1 & VT_BTYPE) != VT_BYTE) { t1 &= ~VT_DEFSIGN; t2 &= ~VT_DEFSIGN; } /* XXX: bitfields ? */ if (t1 != t2) return 0; /* test more complicated cases */ bt1 = t1 & VT_BTYPE; if (bt1 == VT_PTR) { type1 = pointed_type(type1); type2 = pointed_type(type2); return is_compatible_types(type1, type2); } else if (bt1 == VT_STRUCT) { return (type1->ref == type2->ref); } else if (bt1 == VT_FUNC) { return is_compatible_func(type1, type2); } else { return 1; } } /* return true if type1 and type2 are exactly the same (including qualifiers). */ static int is_compatible_types(CType *type1, CType *type2) { return compare_types(type1,type2,0); } /* return true if type1 and type2 are the same (ignoring qualifiers). */ static int is_compatible_unqualified_types(CType *type1, CType *type2) { return compare_types(type1,type2,1); } /* print a type. If 'varstr' is not NULL, then the variable is also printed in the type */ /* XXX: union */ /* XXX: add array and function pointers */ static void type_to_str(char *buf, int buf_size, CType *type, const char *varstr) { int bt, v, t; Sym *s, *sa; char buf1[256]; const char *tstr; t = type->t; bt = t & VT_BTYPE; buf[0] = '\0'; if (t & VT_EXTERN) pstrcat(buf, buf_size, "extern "); if (t & VT_STATIC) pstrcat(buf, buf_size, "static "); if (t & VT_TYPEDEF) pstrcat(buf, buf_size, "typedef "); if (t & VT_INLINE) pstrcat(buf, buf_size, "inline "); if (t & VT_VOLATILE) pstrcat(buf, buf_size, "volatile "); if (t & VT_CONSTANT) pstrcat(buf, buf_size, "const "); if (((t & VT_DEFSIGN) && bt == VT_BYTE) || ((t & VT_UNSIGNED) && (bt == VT_SHORT || bt == VT_INT || bt == VT_LLONG) && !IS_ENUM(t) )) pstrcat(buf, buf_size, (t & VT_UNSIGNED) ? "unsigned " : "signed "); buf_size -= strlen(buf); buf += strlen(buf); switch(bt) { case VT_VOID: tstr = "void"; goto add_tstr; case VT_BOOL: tstr = "_Bool"; goto add_tstr; case VT_BYTE: tstr = "char"; goto add_tstr; case VT_SHORT: tstr = "short"; goto add_tstr; case VT_INT: tstr = "int"; goto maybe_long; case VT_LLONG: tstr = "long long"; maybe_long: if (t & VT_LONG) tstr = "long"; if (!IS_ENUM(t)) goto add_tstr; tstr = "enum "; goto tstruct; case VT_FLOAT: tstr = "float"; goto add_tstr; case VT_DOUBLE: tstr = "double"; goto add_tstr; case VT_LDOUBLE: tstr = "long double"; add_tstr: pstrcat(buf, buf_size, tstr); break; case VT_STRUCT: tstr = "struct "; if (IS_UNION(t)) tstr = "union "; tstruct: pstrcat(buf, buf_size, tstr); v = type->ref->v & ~SYM_STRUCT; if (v >= SYM_FIRST_ANOM) pstrcat(buf, buf_size, "<anonymous>"); else pstrcat(buf, buf_size, get_tok_str(v, NULL)); break; case VT_FUNC: s = type->ref; type_to_str(buf, buf_size, &s->type, varstr); pstrcat(buf, buf_size, "("); sa = s->next; while (sa != NULL) { type_to_str(buf1, sizeof(buf1), &sa->type, NULL); pstrcat(buf, buf_size, buf1); sa = sa->next; if (sa) pstrcat(buf, buf_size, ", "); } pstrcat(buf, buf_size, ")"); goto no_var; case VT_PTR: s = type->ref; if (t & VT_ARRAY) { snprintf(buf1, sizeof(buf1), "%s[%d]", varstr ? varstr : "", s->c); type_to_str(buf, buf_size, &s->type, buf1); goto no_var; } pstrcpy(buf1, sizeof(buf1), "*"); if (t & VT_CONSTANT) pstrcat(buf1, buf_size, "const "); if (t & VT_VOLATILE) pstrcat(buf1, buf_size, "volatile "); if (varstr) pstrcat(buf1, sizeof(buf1), varstr); type_to_str(buf, buf_size, &s->type, buf1); goto no_var; } if (varstr) { pstrcat(buf, buf_size, " "); pstrcat(buf, buf_size, varstr); } no_var: ; } /* verify type compatibility to store vtop in 'dt' type, and generate casts if needed. */ static void gen_assign_cast(CType *dt) { CType *st, *type1, *type2; char buf1[256], buf2[256]; int dbt, sbt; st = &vtop->type; /* source type */ dbt = dt->t & VT_BTYPE; sbt = st->t & VT_BTYPE; if (sbt == VT_VOID || dbt == VT_VOID) { if (sbt == VT_VOID && dbt == VT_VOID) ; /* It is Ok if both are void A test program: void func1() {} void func2() { return func1(); } gcc accepts this program */ else tcc_error("cannot cast from/to void"); } if (dt->t & VT_CONSTANT) tcc_warning("assignment of read-only location"); switch(dbt) { case VT_PTR: /* special cases for pointers */ /* '0' can also be a pointer */ if (is_null_pointer(vtop)) goto type_ok; /* accept implicit pointer to integer cast with warning */ if (is_integer_btype(sbt)) { tcc_warning("assignment makes pointer from integer without a cast"); goto type_ok; } type1 = pointed_type(dt); /* a function is implicitly a function pointer */ if (sbt == VT_FUNC) { if ((type1->t & VT_BTYPE) != VT_VOID && !is_compatible_types(pointed_type(dt), st)) tcc_warning("assignment from incompatible pointer type"); goto type_ok; } if (sbt != VT_PTR) goto error; type2 = pointed_type(st); if ((type1->t & VT_BTYPE) == VT_VOID || (type2->t & VT_BTYPE) == VT_VOID) { /* void * can match anything */ } else { //printf("types %08x %08x\n", type1->t, type2->t); /* exact type match, except for qualifiers */ if (!is_compatible_unqualified_types(type1, type2)) { /* Like GCC don't warn by default for merely changes in pointer target signedness. Do warn for different base types, though, in particular for unsigned enums and signed int targets. */ if ((type1->t & (VT_BTYPE|VT_LONG)) != (type2->t & (VT_BTYPE|VT_LONG)) || IS_ENUM(type1->t) || IS_ENUM(type2->t) ) tcc_warning("assignment from incompatible pointer type"); } } /* check const and volatile */ if ((!(type1->t & VT_CONSTANT) && (type2->t & VT_CONSTANT)) || (!(type1->t & VT_VOLATILE) && (type2->t & VT_VOLATILE))) tcc_warning("assignment discards qualifiers from pointer target type"); break; case VT_BYTE: case VT_SHORT: case VT_INT: case VT_LLONG: if (sbt == VT_PTR || sbt == VT_FUNC) { tcc_warning("assignment makes integer from pointer without a cast"); } else if (sbt == VT_STRUCT) { goto case_VT_STRUCT; } /* XXX: more tests */ break; case VT_STRUCT: case_VT_STRUCT: if (!is_compatible_unqualified_types(dt, st)) { error: type_to_str(buf1, sizeof(buf1), st, NULL); type_to_str(buf2, sizeof(buf2), dt, NULL); tcc_error("cannot cast '%s' to '%s'", buf1, buf2); } break; } type_ok: gen_cast(dt); } /* store vtop in lvalue pushed on stack */ ST_FUNC void vstore(void) { int sbt, dbt, ft, r, t, size, align, bit_size, bit_pos, rc, delayed_cast; ft = vtop[-1].type.t; sbt = vtop->type.t & VT_BTYPE; dbt = ft & VT_BTYPE; if ((((sbt == VT_INT || sbt == VT_SHORT) && dbt == VT_BYTE) || (sbt == VT_INT && dbt == VT_SHORT)) && !(vtop->type.t & VT_BITFIELD)) { /* optimize char/short casts */ delayed_cast = VT_MUSTCAST; vtop->type.t = ft & VT_TYPE; /* XXX: factorize */ if (ft & VT_CONSTANT) tcc_warning("assignment of read-only location"); } else { delayed_cast = 0; if (!(ft & VT_BITFIELD)) gen_assign_cast(&vtop[-1].type); } if (sbt == VT_STRUCT) { /* if structure, only generate pointer */ /* structure assignment : generate memcpy */ /* XXX: optimize if small size */ size = type_size(&vtop->type, &align); /* destination */ vswap(); vtop->type.t = VT_PTR; gaddrof(); /* address of memcpy() */ #ifdef TCC_ARM_EABI if(!(align & 7)) vpush_global_sym(&func_old_type, TOK_memcpy8); else if(!(align & 3)) vpush_global_sym(&func_old_type, TOK_memcpy4); else #endif /* Use memmove, rather than memcpy, as dest and src may be same: */ vpush_global_sym(&func_old_type, TOK_memmove); vswap(); /* source */ vpushv(vtop - 2); vtop->type.t = VT_PTR; gaddrof(); /* type size */ vpushi(size); gfunc_call(3); /* leave source on stack */ } else if (ft & VT_BITFIELD) { /* bitfield store handling */ /* save lvalue as expression result (example: s.b = s.a = n;) */ vdup(), vtop[-1] = vtop[-2]; bit_pos = BIT_POS(ft); bit_size = BIT_SIZE(ft); /* remove bit field info to avoid loops */ vtop[-1].type.t = ft & ~VT_STRUCT_MASK; if ((ft & VT_BTYPE) == VT_BOOL) { gen_cast(&vtop[-1].type); vtop[-1].type.t = (vtop[-1].type.t & ~VT_BTYPE) | (VT_BYTE | VT_UNSIGNED); } r = adjust_bf(vtop - 1, bit_pos, bit_size); if (r == VT_STRUCT) { gen_cast_s((ft & VT_BTYPE) == VT_LLONG ? VT_LLONG : VT_INT); store_packed_bf(bit_pos, bit_size); } else { unsigned long long mask = (1ULL << bit_size) - 1; if ((ft & VT_BTYPE) != VT_BOOL) { /* mask source */ if ((vtop[-1].type.t & VT_BTYPE) == VT_LLONG) vpushll(mask); else vpushi((unsigned)mask); gen_op('&'); } /* shift source */ vpushi(bit_pos); gen_op(TOK_SHL); vswap(); /* duplicate destination */ vdup(); vrott(3); /* load destination, mask and or with source */ if ((vtop->type.t & VT_BTYPE) == VT_LLONG) vpushll(~(mask << bit_pos)); else vpushi(~((unsigned)mask << bit_pos)); gen_op('&'); gen_op('|'); /* store result */ vstore(); /* ... and discard */ vpop(); } } else if (dbt == VT_VOID) { --vtop; } else { #ifdef CONFIG_TCC_BCHECK /* bound check case */ if (vtop[-1].r & VT_MUSTBOUND) { vswap(); gbound(); vswap(); } #endif rc = RC_INT; if (is_float(ft)) { rc = RC_FLOAT; #ifdef TCC_TARGET_X86_64 if ((ft & VT_BTYPE) == VT_LDOUBLE) { rc = RC_ST0; } else if ((ft & VT_BTYPE) == VT_QFLOAT) { rc = RC_FRET; } #endif } r = gv(rc); /* generate value */ /* if lvalue was saved on stack, must read it */ if ((vtop[-1].r & VT_VALMASK) == VT_LLOCAL) { SValue sv; t = get_reg(RC_INT); #if PTR_SIZE == 8 sv.type.t = VT_PTR; #else sv.type.t = VT_INT; #endif sv.r = VT_LOCAL | VT_LVAL; sv.c.i = vtop[-1].c.i; load(t, &sv); vtop[-1].r = t | VT_LVAL; } /* two word case handling : store second register at word + 4 (or +8 for x86-64) */ #if PTR_SIZE == 8 if (((ft & VT_BTYPE) == VT_QLONG) || ((ft & VT_BTYPE) == VT_QFLOAT)) { int addr_type = VT_LLONG, load_size = 8, load_type = ((vtop->type.t & VT_BTYPE) == VT_QLONG) ? VT_LLONG : VT_DOUBLE; #else if ((ft & VT_BTYPE) == VT_LLONG) { int addr_type = VT_INT, load_size = 4, load_type = VT_INT; #endif vtop[-1].type.t = load_type; store(r, vtop - 1); vswap(); /* convert to int to increment easily */ vtop->type.t = addr_type; gaddrof(); vpushi(load_size); gen_op('+'); vtop->r |= VT_LVAL; vswap(); vtop[-1].type.t = load_type; /* XXX: it works because r2 is spilled last ! */ store(vtop->r2, vtop - 1); } else { store(r, vtop - 1); } vswap(); vtop--; /* NOT vpop() because on x86 it would flush the fp stack */ vtop->r |= delayed_cast; } } /* post defines POST/PRE add. c is the token ++ or -- */ ST_FUNC void inc(int post, int c) { test_lvalue(); vdup(); /* save lvalue */ if (post) { gv_dup(); /* duplicate value */ vrotb(3); vrotb(3); } /* add constant */ vpushi(c - TOK_MID); gen_op('+'); vstore(); /* store value */ if (post) vpop(); /* if post op, return saved value */ } ST_FUNC void parse_mult_str (CString *astr, const char *msg) { /* read the string */ if (tok != TOK_STR) expect(msg); cstr_new(astr); while (tok == TOK_STR) { /* XXX: add \0 handling too ? */ cstr_cat(astr, tokc.str.data, -1); next(); } cstr_ccat(astr, '\0'); } /* If I is >= 1 and a power of two, returns log2(i)+1. If I is 0 returns 0. */ static int exact_log2p1(int i) { int ret; if (!i) return 0; for (ret = 1; i >= 1 << 8; ret += 8) i >>= 8; if (i >= 1 << 4) ret += 4, i >>= 4; if (i >= 1 << 2) ret += 2, i >>= 2; if (i >= 1 << 1) ret++; return ret; } /* Parse __attribute__((...)) GNUC extension. */ static void parse_attribute(AttributeDef *ad) { int t, n; CString astr; redo: if (tok != TOK_ATTRIBUTE1 && tok != TOK_ATTRIBUTE2) return; next(); skip('('); skip('('); while (tok != ')') { if (tok < TOK_IDENT) expect("attribute name"); t = tok; next(); switch(t) { case TOK_SECTION1: case TOK_SECTION2: skip('('); parse_mult_str(&astr, "section name"); ad->section = find_section(tcc_state, (char *)astr.data); skip(')'); cstr_free(&astr); break; case TOK_ALIAS1: case TOK_ALIAS2: skip('('); parse_mult_str(&astr, "alias(\"target\")"); ad->alias_target = /* save string as token, for later */ tok_alloc((char*)astr.data, astr.size-1)->tok; skip(')'); cstr_free(&astr); break; case TOK_VISIBILITY1: case TOK_VISIBILITY2: skip('('); parse_mult_str(&astr, "visibility(\"default|hidden|internal|protected\")"); if (!strcmp (astr.data, "default")) ad->a.visibility = STV_DEFAULT; else if (!strcmp (astr.data, "hidden")) ad->a.visibility = STV_HIDDEN; else if (!strcmp (astr.data, "internal")) ad->a.visibility = STV_INTERNAL; else if (!strcmp (astr.data, "protected")) ad->a.visibility = STV_PROTECTED; else expect("visibility(\"default|hidden|internal|protected\")"); skip(')'); cstr_free(&astr); break; case TOK_ALIGNED1: case TOK_ALIGNED2: if (tok == '(') { next(); n = expr_const(); if (n <= 0 || (n & (n - 1)) != 0) tcc_error("alignment must be a positive power of two"); skip(')'); } else { n = MAX_ALIGN; } ad->a.aligned = exact_log2p1(n); if (n != 1 << (ad->a.aligned - 1)) tcc_error("alignment of %d is larger than implemented", n); break; case TOK_PACKED1: case TOK_PACKED2: ad->a.packed = 1; break; case TOK_WEAK1: case TOK_WEAK2: ad->a.weak = 1; break; case TOK_UNUSED1: case TOK_UNUSED2: /* currently, no need to handle it because tcc does not track unused objects */ break; case TOK_NORETURN1: case TOK_NORETURN2: /* currently, no need to handle it because tcc does not track unused objects */ break; case TOK_CDECL1: case TOK_CDECL2: case TOK_CDECL3: ad->f.func_call = FUNC_CDECL; break; case TOK_STDCALL1: case TOK_STDCALL2: case TOK_STDCALL3: ad->f.func_call = FUNC_STDCALL; break; #ifdef TCC_TARGET_I386 case TOK_REGPARM1: case TOK_REGPARM2: skip('('); n = expr_const(); if (n > 3) n = 3; else if (n < 0) n = 0; if (n > 0) ad->f.func_call = FUNC_FASTCALL1 + n - 1; skip(')'); break; case TOK_FASTCALL1: case TOK_FASTCALL2: case TOK_FASTCALL3: ad->f.func_call = FUNC_FASTCALLW; break; #endif case TOK_MODE: skip('('); switch(tok) { case TOK_MODE_DI: ad->attr_mode = VT_LLONG + 1; break; case TOK_MODE_QI: ad->attr_mode = VT_BYTE + 1; break; case TOK_MODE_HI: ad->attr_mode = VT_SHORT + 1; break; case TOK_MODE_SI: case TOK_MODE_word: ad->attr_mode = VT_INT + 1; break; default: tcc_warning("__mode__(%s) not supported\n", get_tok_str(tok, NULL)); break; } next(); skip(')'); break; case TOK_DLLEXPORT: ad->a.dllexport = 1; break; case TOK_DLLIMPORT: ad->a.dllimport = 1; break; default: if (tcc_state->warn_unsupported) tcc_warning("'%s' attribute ignored", get_tok_str(t, NULL)); /* skip parameters */ if (tok == '(') { int parenthesis = 0; do { if (tok == '(') parenthesis++; else if (tok == ')') parenthesis--; next(); } while (parenthesis && tok != -1); } break; } if (tok != ',') break; next(); } skip(')'); skip(')'); goto redo; } static Sym * find_field (CType *type, int v) { Sym *s = type->ref; v |= SYM_FIELD; while ((s = s->next) != NULL) { if ((s->v & SYM_FIELD) && (s->type.t & VT_BTYPE) == VT_STRUCT && (s->v & ~SYM_FIELD) >= SYM_FIRST_ANOM) { Sym *ret = find_field (&s->type, v); if (ret) return ret; } if (s->v == v) break; } return s; } static void struct_add_offset (Sym *s, int offset) { while ((s = s->next) != NULL) { if ((s->v & SYM_FIELD) && (s->type.t & VT_BTYPE) == VT_STRUCT && (s->v & ~SYM_FIELD) >= SYM_FIRST_ANOM) { struct_add_offset(s->type.ref, offset); } else s->c += offset; } } static void struct_layout(CType *type, AttributeDef *ad) { int size, align, maxalign, offset, c, bit_pos, bit_size; int packed, a, bt, prevbt, prev_bit_size; int pcc = !tcc_state->ms_bitfields; int pragma_pack = *tcc_state->pack_stack_ptr; Sym *f; maxalign = 1; offset = 0; c = 0; bit_pos = 0; prevbt = VT_STRUCT; /* make it never match */ prev_bit_size = 0; //#define BF_DEBUG for (f = type->ref->next; f; f = f->next) { if (f->type.t & VT_BITFIELD) bit_size = BIT_SIZE(f->type.t); else bit_size = -1; size = type_size(&f->type, &align); a = f->a.aligned ? 1 << (f->a.aligned - 1) : 0; packed = 0; if (pcc && bit_size == 0) { /* in pcc mode, packing does not affect zero-width bitfields */ } else { /* in pcc mode, attribute packed overrides if set. */ if (pcc && (f->a.packed || ad->a.packed)) align = packed = 1; /* pragma pack overrides align if lesser and packs bitfields always */ if (pragma_pack) { packed = 1; if (pragma_pack < align) align = pragma_pack; /* in pcc mode pragma pack also overrides individual align */ if (pcc && pragma_pack < a) a = 0; } } /* some individual align was specified */ if (a) align = a; if (type->ref->type.t == VT_UNION) { if (pcc && bit_size >= 0) size = (bit_size + 7) >> 3; offset = 0; if (size > c) c = size; } else if (bit_size < 0) { if (pcc) c += (bit_pos + 7) >> 3; c = (c + align - 1) & -align; offset = c; if (size > 0) c += size; bit_pos = 0; prevbt = VT_STRUCT; prev_bit_size = 0; } else { /* A bit-field. Layout is more complicated. There are two options: PCC (GCC) compatible and MS compatible */ if (pcc) { /* In PCC layout a bit-field is placed adjacent to the preceding bit-fields, except if: - it has zero-width - an individual alignment was given - it would overflow its base type container and there is no packing */ if (bit_size == 0) { new_field: c = (c + ((bit_pos + 7) >> 3) + align - 1) & -align; bit_pos = 0; } else if (f->a.aligned) { goto new_field; } else if (!packed) { int a8 = align * 8; int ofs = ((c * 8 + bit_pos) % a8 + bit_size + a8 - 1) / a8; if (ofs > size / align) goto new_field; } /* in pcc mode, long long bitfields have type int if they fit */ if (size == 8 && bit_size <= 32) f->type.t = (f->type.t & ~VT_BTYPE) | VT_INT, size = 4; while (bit_pos >= align * 8) c += align, bit_pos -= align * 8; offset = c; /* In PCC layout named bit-fields influence the alignment of the containing struct using the base types alignment, except for packed fields (which here have correct align). */ if (f->v & SYM_FIRST_ANOM // && bit_size // ??? gcc on ARM/rpi does that ) align = 1; } else { bt = f->type.t & VT_BTYPE; if ((bit_pos + bit_size > size * 8) || (bit_size > 0) == (bt != prevbt) ) { c = (c + align - 1) & -align; offset = c; bit_pos = 0; /* In MS bitfield mode a bit-field run always uses at least as many bits as the underlying type. To start a new run it's also required that this or the last bit-field had non-zero width. */ if (bit_size || prev_bit_size) c += size; } /* In MS layout the records alignment is normally influenced by the field, except for a zero-width field at the start of a run (but by further zero-width fields it is again). */ if (bit_size == 0 && prevbt != bt) align = 1; prevbt = bt; prev_bit_size = bit_size; } f->type.t = (f->type.t & ~(0x3f << VT_STRUCT_SHIFT)) | (bit_pos << VT_STRUCT_SHIFT); bit_pos += bit_size; } if (align > maxalign) maxalign = align; #ifdef BF_DEBUG printf("set field %s offset %-2d size %-2d align %-2d", get_tok_str(f->v & ~SYM_FIELD, NULL), offset, size, align); if (f->type.t & VT_BITFIELD) { printf(" pos %-2d bits %-2d", BIT_POS(f->type.t), BIT_SIZE(f->type.t) ); } printf("\n"); #endif if (f->v & SYM_FIRST_ANOM && (f->type.t & VT_BTYPE) == VT_STRUCT) { Sym *ass; /* An anonymous struct/union. Adjust member offsets to reflect the real offset of our containing struct. Also set the offset of this anon member inside the outer struct to be zero. Via this it works when accessing the field offset directly (from base object), as well as when recursing members in initializer handling. */ int v2 = f->type.ref->v; if (!(v2 & SYM_FIELD) && (v2 & ~SYM_STRUCT) < SYM_FIRST_ANOM) { Sym **pps; /* This happens only with MS extensions. The anon member has a named struct type, so it potentially is shared with other references. We need to unshare members so we can modify them. */ ass = f->type.ref; f->type.ref = sym_push(anon_sym++ | SYM_FIELD, &f->type.ref->type, 0, f->type.ref->c); pps = &f->type.ref->next; while ((ass = ass->next) != NULL) { *pps = sym_push(ass->v, &ass->type, 0, ass->c); pps = &((*pps)->next); } *pps = NULL; } struct_add_offset(f->type.ref, offset); f->c = 0; } else { f->c = offset; } f->r = 0; } if (pcc) c += (bit_pos + 7) >> 3; /* store size and alignment */ a = bt = ad->a.aligned ? 1 << (ad->a.aligned - 1) : 1; if (a < maxalign) a = maxalign; type->ref->r = a; if (pragma_pack && pragma_pack < maxalign && 0 == pcc) { /* can happen if individual align for some member was given. In this case MSVC ignores maxalign when aligning the size */ a = pragma_pack; if (a < bt) a = bt; } c = (c + a - 1) & -a; type->ref->c = c; #ifdef BF_DEBUG printf("struct size %-2d align %-2d\n\n", c, a), fflush(stdout); #endif /* check whether we can access bitfields by their type */ for (f = type->ref->next; f; f = f->next) { int s, px, cx, c0; CType t; if (0 == (f->type.t & VT_BITFIELD)) continue; f->type.ref = f; f->auxtype = -1; bit_size = BIT_SIZE(f->type.t); if (bit_size == 0) continue; bit_pos = BIT_POS(f->type.t); size = type_size(&f->type, &align); if (bit_pos + bit_size <= size * 8 && f->c + size <= c) continue; /* try to access the field using a different type */ c0 = -1, s = align = 1; for (;;) { px = f->c * 8 + bit_pos; cx = (px >> 3) & -align; px = px - (cx << 3); if (c0 == cx) break; s = (px + bit_size + 7) >> 3; if (s > 4) { t.t = VT_LLONG; } else if (s > 2) { t.t = VT_INT; } else if (s > 1) { t.t = VT_SHORT; } else { t.t = VT_BYTE; } s = type_size(&t, &align); c0 = cx; } if (px + bit_size <= s * 8 && cx + s <= c) { /* update offset and bit position */ f->c = cx; bit_pos = px; f->type.t = (f->type.t & ~(0x3f << VT_STRUCT_SHIFT)) | (bit_pos << VT_STRUCT_SHIFT); if (s != size) f->auxtype = t.t; #ifdef BF_DEBUG printf("FIX field %s offset %-2d size %-2d align %-2d " "pos %-2d bits %-2d\n", get_tok_str(f->v & ~SYM_FIELD, NULL), cx, s, align, px, bit_size); #endif } else { /* fall back to load/store single-byte wise */ f->auxtype = VT_STRUCT; #ifdef BF_DEBUG printf("FIX field %s : load byte-wise\n", get_tok_str(f->v & ~SYM_FIELD, NULL)); #endif } } } /* enum/struct/union declaration. u is VT_ENUM/VT_STRUCT/VT_UNION */ static void struct_decl(CType *type, int u) { int v, c, size, align, flexible; int bit_size, bsize, bt; Sym *s, *ss, **ps; AttributeDef ad, ad1; CType type1, btype; memset(&ad, 0, sizeof ad); next(); parse_attribute(&ad); if (tok != '{') { v = tok; next(); /* struct already defined ? return it */ if (v < TOK_IDENT) expect("struct/union/enum name"); s = struct_find(v); if (s && (s->sym_scope == local_scope || tok != '{')) { if (u == s->type.t) goto do_decl; if (u == VT_ENUM && IS_ENUM(s->type.t)) goto do_decl; tcc_error("redefinition of '%s'", get_tok_str(v, NULL)); } } else { v = anon_sym++; } /* Record the original enum/struct/union token. */ type1.t = u == VT_ENUM ? u | VT_INT | VT_UNSIGNED : u; type1.ref = NULL; /* we put an undefined size for struct/union */ s = sym_push(v | SYM_STRUCT, &type1, 0, -1); s->r = 0; /* default alignment is zero as gcc */ do_decl: type->t = s->type.t; type->ref = s; if (tok == '{') { next(); if (s->c != -1) tcc_error("struct/union/enum already defined"); /* cannot be empty */ /* non empty enums are not allowed */ ps = &s->next; if (u == VT_ENUM) { long long ll = 0, pl = 0, nl = 0; CType t; t.ref = s; /* enum symbols have static storage */ t.t = VT_INT|VT_STATIC|VT_ENUM_VAL; for(;;) { v = tok; if (v < TOK_UIDENT) expect("identifier"); ss = sym_find(v); if (ss && !local_stack) tcc_error("redefinition of enumerator '%s'", get_tok_str(v, NULL)); next(); if (tok == '=') { next(); ll = expr_const64(); } ss = sym_push(v, &t, VT_CONST, 0); ss->enum_val = ll; *ps = ss, ps = &ss->next; if (ll < nl) nl = ll; if (ll > pl) pl = ll; if (tok != ',') break; next(); ll++; /* NOTE: we accept a trailing comma */ if (tok == '}') break; } skip('}'); /* set integral type of the enum */ t.t = VT_INT; if (nl >= 0) { if (pl != (unsigned)pl) t.t = (LONG_SIZE==8 ? VT_LLONG|VT_LONG : VT_LLONG); t.t |= VT_UNSIGNED; } else if (pl != (int)pl || nl != (int)nl) t.t = (LONG_SIZE==8 ? VT_LLONG|VT_LONG : VT_LLONG); s->type.t = type->t = t.t | VT_ENUM; s->c = 0; /* set type for enum members */ for (ss = s->next; ss; ss = ss->next) { ll = ss->enum_val; if (ll == (int)ll) /* default is int if it fits */ continue; if (t.t & VT_UNSIGNED) { ss->type.t |= VT_UNSIGNED; if (ll == (unsigned)ll) continue; } ss->type.t = (ss->type.t & ~VT_BTYPE) | (LONG_SIZE==8 ? VT_LLONG|VT_LONG : VT_LLONG); } } else { c = 0; flexible = 0; while (tok != '}') { if (!parse_btype(&btype, &ad1)) { skip(';'); continue; } while (1) { if (flexible) tcc_error("flexible array member '%s' not at the end of struct", get_tok_str(v, NULL)); bit_size = -1; v = 0; type1 = btype; if (tok != ':') { if (tok != ';') type_decl(&type1, &ad1, &v, TYPE_DIRECT); if (v == 0) { if ((type1.t & VT_BTYPE) != VT_STRUCT) expect("identifier"); else { int v = btype.ref->v; if (!(v & SYM_FIELD) && (v & ~SYM_STRUCT) < SYM_FIRST_ANOM) { if (tcc_state->ms_extensions == 0) expect("identifier"); } } } if (type_size(&type1, &align) < 0) { if ((u == VT_STRUCT) && (type1.t & VT_ARRAY) && c) flexible = 1; else tcc_error("field '%s' has incomplete type", get_tok_str(v, NULL)); } if ((type1.t & VT_BTYPE) == VT_FUNC || (type1.t & VT_STORAGE)) tcc_error("invalid type for '%s'", get_tok_str(v, NULL)); } if (tok == ':') { next(); bit_size = expr_const(); /* XXX: handle v = 0 case for messages */ if (bit_size < 0) tcc_error("negative width in bit-field '%s'", get_tok_str(v, NULL)); if (v && bit_size == 0) tcc_error("zero width for bit-field '%s'", get_tok_str(v, NULL)); parse_attribute(&ad1); } size = type_size(&type1, &align); if (bit_size >= 0) { bt = type1.t & VT_BTYPE; if (bt != VT_INT && bt != VT_BYTE && bt != VT_SHORT && bt != VT_BOOL && bt != VT_LLONG) tcc_error("bitfields must have scalar type"); bsize = size * 8; if (bit_size > bsize) { tcc_error("width of '%s' exceeds its type", get_tok_str(v, NULL)); } else if (bit_size == bsize && !ad.a.packed && !ad1.a.packed) { /* no need for bit fields */ ; } else if (bit_size == 64) { tcc_error("field width 64 not implemented"); } else { type1.t = (type1.t & ~VT_STRUCT_MASK) | VT_BITFIELD | (bit_size << (VT_STRUCT_SHIFT + 6)); } } if (v != 0 || (type1.t & VT_BTYPE) == VT_STRUCT) { /* Remember we've seen a real field to check for placement of flexible array member. */ c = 1; } /* If member is a struct or bit-field, enforce placing into the struct (as anonymous). */ if (v == 0 && ((type1.t & VT_BTYPE) == VT_STRUCT || bit_size >= 0)) { v = anon_sym++; } if (v) { ss = sym_push(v | SYM_FIELD, &type1, 0, 0); ss->a = ad1.a; *ps = ss; ps = &ss->next; } if (tok == ';' || tok == TOK_EOF) break; skip(','); } skip(';'); } skip('}'); parse_attribute(&ad); struct_layout(type, &ad); } } } static void sym_to_attr(AttributeDef *ad, Sym *s) { if (s->a.aligned && 0 == ad->a.aligned) ad->a.aligned = s->a.aligned; if (s->f.func_call && 0 == ad->f.func_call) ad->f.func_call = s->f.func_call; if (s->f.func_type && 0 == ad->f.func_type) ad->f.func_type = s->f.func_type; if (s->a.packed) ad->a.packed = 1; } /* Add type qualifiers to a type. If the type is an array then the qualifiers are added to the element type, copied because it could be a typedef. */ static void parse_btype_qualify(CType *type, int qualifiers) { while (type->t & VT_ARRAY) { type->ref = sym_push(SYM_FIELD, &type->ref->type, 0, type->ref->c); type = &type->ref->type; } type->t |= qualifiers; } /* return 0 if no type declaration. otherwise, return the basic type and skip it. */ static int parse_btype(CType *type, AttributeDef *ad) { int t, u, bt, st, type_found, typespec_found, g; Sym *s; CType type1; memset(ad, 0, sizeof(AttributeDef)); type_found = 0; typespec_found = 0; t = VT_INT; bt = st = -1; type->ref = NULL; while(1) { switch(tok) { case TOK_EXTENSION: /* currently, we really ignore extension */ next(); continue; /* basic types */ case TOK_CHAR: u = VT_BYTE; basic_type: next(); basic_type1: if (u == VT_SHORT || u == VT_LONG) { if (st != -1 || (bt != -1 && bt != VT_INT)) tmbt: tcc_error("too many basic types"); st = u; } else { if (bt != -1 || (st != -1 && u != VT_INT)) goto tmbt; bt = u; } if (u != VT_INT) t = (t & ~(VT_BTYPE|VT_LONG)) | u; typespec_found = 1; break; case TOK_VOID: u = VT_VOID; goto basic_type; case TOK_SHORT: u = VT_SHORT; goto basic_type; case TOK_INT: u = VT_INT; goto basic_type; case TOK_LONG: if ((t & VT_BTYPE) == VT_DOUBLE) { t = (t & ~(VT_BTYPE|VT_LONG)) | VT_LDOUBLE; } else if ((t & (VT_BTYPE|VT_LONG)) == VT_LONG) { t = (t & ~(VT_BTYPE|VT_LONG)) | VT_LLONG; } else { u = VT_LONG; goto basic_type; } next(); break; #ifdef TCC_TARGET_ARM64 case TOK_UINT128: /* GCC's __uint128_t appears in some Linux header files. Make it a synonym for long double to get the size and alignment right. */ u = VT_LDOUBLE; goto basic_type; #endif case TOK_BOOL: u = VT_BOOL; goto basic_type; case TOK_FLOAT: u = VT_FLOAT; goto basic_type; case TOK_DOUBLE: if ((t & (VT_BTYPE|VT_LONG)) == VT_LONG) { t = (t & ~(VT_BTYPE|VT_LONG)) | VT_LDOUBLE; } else { u = VT_DOUBLE; goto basic_type; } next(); break; case TOK_ENUM: struct_decl(&type1, VT_ENUM); basic_type2: u = type1.t; type->ref = type1.ref; goto basic_type1; case TOK_STRUCT: struct_decl(&type1, VT_STRUCT); goto basic_type2; case TOK_UNION: struct_decl(&type1, VT_UNION); goto basic_type2; /* type modifiers */ case TOK_CONST1: case TOK_CONST2: case TOK_CONST3: type->t = t; parse_btype_qualify(type, VT_CONSTANT); t = type->t; next(); break; case TOK_VOLATILE1: case TOK_VOLATILE2: case TOK_VOLATILE3: type->t = t; parse_btype_qualify(type, VT_VOLATILE); t = type->t; next(); break; case TOK_SIGNED1: case TOK_SIGNED2: case TOK_SIGNED3: if ((t & (VT_DEFSIGN|VT_UNSIGNED)) == (VT_DEFSIGN|VT_UNSIGNED)) tcc_error("signed and unsigned modifier"); t |= VT_DEFSIGN; next(); typespec_found = 1; break; case TOK_REGISTER: case TOK_AUTO: case TOK_RESTRICT1: case TOK_RESTRICT2: case TOK_RESTRICT3: next(); break; case TOK_UNSIGNED: if ((t & (VT_DEFSIGN|VT_UNSIGNED)) == VT_DEFSIGN) tcc_error("signed and unsigned modifier"); t |= VT_DEFSIGN | VT_UNSIGNED; next(); typespec_found = 1; break; /* storage */ case TOK_EXTERN: g = VT_EXTERN; goto storage; case TOK_STATIC: g = VT_STATIC; goto storage; case TOK_TYPEDEF: g = VT_TYPEDEF; goto storage; storage: if (t & (VT_EXTERN|VT_STATIC|VT_TYPEDEF) & ~g) tcc_error("multiple storage classes"); t |= g; next(); break; case TOK_INLINE1: case TOK_INLINE2: case TOK_INLINE3: t |= VT_INLINE; next(); break; /* GNUC attribute */ case TOK_ATTRIBUTE1: case TOK_ATTRIBUTE2: parse_attribute(ad); if (ad->attr_mode) { u = ad->attr_mode -1; t = (t & ~(VT_BTYPE|VT_LONG)) | u; } break; /* GNUC typeof */ case TOK_TYPEOF1: case TOK_TYPEOF2: case TOK_TYPEOF3: next(); parse_expr_type(&type1); /* remove all storage modifiers except typedef */ type1.t &= ~(VT_STORAGE&~VT_TYPEDEF); if (type1.ref) sym_to_attr(ad, type1.ref); goto basic_type2; default: if (typespec_found) goto the_end; s = sym_find(tok); if (!s || !(s->type.t & VT_TYPEDEF)) goto the_end; t &= ~(VT_BTYPE|VT_LONG); u = t & ~(VT_CONSTANT | VT_VOLATILE), t ^= u; type->t = (s->type.t & ~VT_TYPEDEF) | u; type->ref = s->type.ref; if (t) parse_btype_qualify(type, t); t = type->t; /* get attributes from typedef */ sym_to_attr(ad, s); next(); typespec_found = 1; st = bt = -2; break; } type_found = 1; } the_end: if (tcc_state->char_is_unsigned) { if ((t & (VT_DEFSIGN|VT_BTYPE)) == VT_BYTE) t |= VT_UNSIGNED; } /* VT_LONG is used just as a modifier for VT_INT / VT_LLONG */ bt = t & (VT_BTYPE|VT_LONG); if (bt == VT_LONG) t |= LONG_SIZE == 8 ? VT_LLONG : VT_INT; #ifdef TCC_TARGET_PE if (bt == VT_LDOUBLE) t = (t & ~(VT_BTYPE|VT_LONG)) | VT_DOUBLE; #endif type->t = t; return type_found; } /* convert a function parameter type (array to pointer and function to function pointer) */ static inline void convert_parameter_type(CType *pt) { /* remove const and volatile qualifiers (XXX: const could be used to indicate a const function parameter */ pt->t &= ~(VT_CONSTANT | VT_VOLATILE); /* array must be transformed to pointer according to ANSI C */ pt->t &= ~VT_ARRAY; if ((pt->t & VT_BTYPE) == VT_FUNC) { mk_pointer(pt); } } ST_FUNC void parse_asm_str(CString *astr) { skip('('); parse_mult_str(astr, "string constant"); } /* Parse an asm label and return the token */ static int asm_label_instr(void) { int v; CString astr; next(); parse_asm_str(&astr); skip(')'); #ifdef ASM_DEBUG printf("asm_alias: \"%s\"\n", (char *)astr.data); #endif v = tok_alloc(astr.data, astr.size - 1)->tok; cstr_free(&astr); return v; } static int post_type(CType *type, AttributeDef *ad, int storage, int td) { int n, l, t1, arg_size, align; Sym **plast, *s, *first; AttributeDef ad1; CType pt; if (tok == '(') { /* function type, or recursive declarator (return if so) */ next(); if (td && !(td & TYPE_ABSTRACT)) return 0; if (tok == ')') l = 0; else if (parse_btype(&pt, &ad1)) l = FUNC_NEW; else if (td) return 0; else l = FUNC_OLD; first = NULL; plast = &first; arg_size = 0; if (l) { for(;;) { /* read param name and compute offset */ if (l != FUNC_OLD) { if ((pt.t & VT_BTYPE) == VT_VOID && tok == ')') break; type_decl(&pt, &ad1, &n, TYPE_DIRECT | TYPE_ABSTRACT); if ((pt.t & VT_BTYPE) == VT_VOID) tcc_error("parameter declared as void"); arg_size += (type_size(&pt, &align) + PTR_SIZE - 1) / PTR_SIZE; } else { n = tok; if (n < TOK_UIDENT) expect("identifier"); pt.t = VT_VOID; /* invalid type */ next(); } convert_parameter_type(&pt); s = sym_push(n | SYM_FIELD, &pt, 0, 0); *plast = s; plast = &s->next; if (tok == ')') break; skip(','); if (l == FUNC_NEW && tok == TOK_DOTS) { l = FUNC_ELLIPSIS; next(); break; } if (l == FUNC_NEW && !parse_btype(&pt, &ad1)) tcc_error("invalid type"); } } else /* if no parameters, then old type prototype */ l = FUNC_OLD; skip(')'); /* NOTE: const is ignored in returned type as it has a special meaning in gcc / C++ */ type->t &= ~VT_CONSTANT; /* some ancient pre-K&R C allows a function to return an array and the array brackets to be put after the arguments, such that "int c()[]" means something like "int[] c()" */ if (tok == '[') { next(); skip(']'); /* only handle simple "[]" */ mk_pointer(type); } /* we push a anonymous symbol which will contain the function prototype */ ad->f.func_args = arg_size; ad->f.func_type = l; s = sym_push(SYM_FIELD, type, 0, 0); s->a = ad->a; s->f = ad->f; s->next = first; type->t = VT_FUNC; type->ref = s; } else if (tok == '[') { int saved_nocode_wanted = nocode_wanted; /* array definition */ next(); if (tok == TOK_RESTRICT1) next(); n = -1; t1 = 0; if (tok != ']') { if (!local_stack || (storage & VT_STATIC)) vpushi(expr_const()); else { /* VLAs (which can only happen with local_stack && !VT_STATIC) length must always be evaluated, even under nocode_wanted, so that its size slot is initialized (e.g. under sizeof or typeof). */ nocode_wanted = 0; gexpr(); } if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) { n = vtop->c.i; if (n < 0) tcc_error("invalid array size"); } else { if (!is_integer_btype(vtop->type.t & VT_BTYPE)) tcc_error("size of variable length array should be an integer"); t1 = VT_VLA; } } skip(']'); /* parse next post type */ post_type(type, ad, storage, 0); if (type->t == VT_FUNC) tcc_error("declaration of an array of functions"); t1 |= type->t & VT_VLA; if (t1 & VT_VLA) { loc -= type_size(&int_type, &align); loc &= -align; n = loc; vla_runtime_type_size(type, &align); gen_op('*'); vset(&int_type, VT_LOCAL|VT_LVAL, n); vswap(); vstore(); } if (n != -1) vpop(); nocode_wanted = saved_nocode_wanted; /* we push an anonymous symbol which will contain the array element type */ s = sym_push(SYM_FIELD, type, 0, n); type->t = (t1 ? VT_VLA : VT_ARRAY) | VT_PTR; type->ref = s; } return 1; } /* Parse a type declarator (except basic type), and return the type in 'type'. 'td' is a bitmask indicating which kind of type decl is expected. 'type' should contain the basic type. 'ad' is the attribute definition of the basic type. It can be modified by type_decl(). If this (possibly abstract) declarator is a pointer chain it returns the innermost pointed to type (equals *type, but is a different pointer), otherwise returns type itself, that's used for recursive calls. */ static CType *type_decl(CType *type, AttributeDef *ad, int *v, int td) { CType *post, *ret; int qualifiers, storage; /* recursive type, remove storage bits first, apply them later again */ storage = type->t & VT_STORAGE; type->t &= ~VT_STORAGE; post = ret = type; while (tok == '*') { qualifiers = 0; redo: next(); switch(tok) { case TOK_CONST1: case TOK_CONST2: case TOK_CONST3: qualifiers |= VT_CONSTANT; goto redo; case TOK_VOLATILE1: case TOK_VOLATILE2: case TOK_VOLATILE3: qualifiers |= VT_VOLATILE; goto redo; case TOK_RESTRICT1: case TOK_RESTRICT2: case TOK_RESTRICT3: goto redo; /* XXX: clarify attribute handling */ case TOK_ATTRIBUTE1: case TOK_ATTRIBUTE2: parse_attribute(ad); break; } mk_pointer(type); type->t |= qualifiers; if (ret == type) /* innermost pointed to type is the one for the first derivation */ ret = pointed_type(type); } if (tok == '(') { /* This is possibly a parameter type list for abstract declarators ('int ()'), use post_type for testing this. */ if (!post_type(type, ad, 0, td)) { /* It's not, so it's a nested declarator, and the post operations apply to the innermost pointed to type (if any). */ /* XXX: this is not correct to modify 'ad' at this point, but the syntax is not clear */ parse_attribute(ad); post = type_decl(type, ad, v, td); skip(')'); } } else if (tok >= TOK_IDENT && (td & TYPE_DIRECT)) { /* type identifier */ *v = tok; next(); } else { if (!(td & TYPE_ABSTRACT)) expect("identifier"); *v = 0; } post_type(post, ad, storage, 0); parse_attribute(ad); type->t |= storage; return ret; } /* compute the lvalue VT_LVAL_xxx needed to match type t. */ ST_FUNC int lvalue_type(int t) { int bt, r; r = VT_LVAL; bt = t & VT_BTYPE; if (bt == VT_BYTE || bt == VT_BOOL) r |= VT_LVAL_BYTE; else if (bt == VT_SHORT) r |= VT_LVAL_SHORT; else return r; if (t & VT_UNSIGNED) r |= VT_LVAL_UNSIGNED; return r; } /* indirection with full error checking and bound check */ ST_FUNC void indir(void) { if ((vtop->type.t & VT_BTYPE) != VT_PTR) { if ((vtop->type.t & VT_BTYPE) == VT_FUNC) return; expect("pointer"); } if (vtop->r & VT_LVAL) gv(RC_INT); vtop->type = *pointed_type(&vtop->type); /* Arrays and functions are never lvalues */ if (!(vtop->type.t & VT_ARRAY) && !(vtop->type.t & VT_VLA) && (vtop->type.t & VT_BTYPE) != VT_FUNC) { vtop->r |= lvalue_type(vtop->type.t); /* if bound checking, the referenced pointer must be checked */ #ifdef CONFIG_TCC_BCHECK if (tcc_state->do_bounds_check) vtop->r |= VT_MUSTBOUND; #endif } } /* pass a parameter to a function and do type checking and casting */ static void gfunc_param_typed(Sym *func, Sym *arg) { int func_type; CType type; func_type = func->f.func_type; if (func_type == FUNC_OLD || (func_type == FUNC_ELLIPSIS && arg == NULL)) { /* default casting : only need to convert float to double */ if ((vtop->type.t & VT_BTYPE) == VT_FLOAT) { gen_cast_s(VT_DOUBLE); } else if (vtop->type.t & VT_BITFIELD) { type.t = vtop->type.t & (VT_BTYPE | VT_UNSIGNED); type.ref = vtop->type.ref; gen_cast(&type); } } else if (arg == NULL) { tcc_error("too many arguments to function"); } else { type = arg->type; type.t &= ~VT_CONSTANT; /* need to do that to avoid false warning */ gen_assign_cast(&type); } } /* parse an expression and return its type without any side effect. */ static void expr_type(CType *type, void (*expr_fn)(void)) { nocode_wanted++; expr_fn(); *type = vtop->type; vpop(); nocode_wanted--; } /* parse an expression of the form '(type)' or '(expr)' and return its type */ static void parse_expr_type(CType *type) { int n; AttributeDef ad; skip('('); if (parse_btype(type, &ad)) { type_decl(type, &ad, &n, TYPE_ABSTRACT); } else { expr_type(type, gexpr); } skip(')'); } static void parse_type(CType *type) { AttributeDef ad; int n; if (!parse_btype(type, &ad)) { expect("type"); } type_decl(type, &ad, &n, TYPE_ABSTRACT); } static void parse_builtin_params(int nc, const char *args) { char c, sep = '('; CType t; if (nc) nocode_wanted++; next(); while ((c = *args++)) { skip(sep); sep = ','; switch (c) { case 'e': expr_eq(); continue; case 't': parse_type(&t); vpush(&t); continue; default: tcc_error("internal error"); break; } } skip(')'); if (nc) nocode_wanted--; } ST_FUNC void unary(void) { int n, t, align, size, r, sizeof_caller; CType type; Sym *s; AttributeDef ad; sizeof_caller = in_sizeof; in_sizeof = 0; type.ref = NULL; /* XXX: GCC 2.95.3 does not generate a table although it should be better here */ tok_next: switch(tok) { case TOK_EXTENSION: next(); goto tok_next; case TOK_LCHAR: #ifdef TCC_TARGET_PE t = VT_SHORT|VT_UNSIGNED; goto push_tokc; #endif case TOK_CINT: case TOK_CCHAR: t = VT_INT; push_tokc: type.t = t; vsetc(&type, VT_CONST, &tokc); next(); break; case TOK_CUINT: t = VT_INT | VT_UNSIGNED; goto push_tokc; case TOK_CLLONG: t = VT_LLONG; goto push_tokc; case TOK_CULLONG: t = VT_LLONG | VT_UNSIGNED; goto push_tokc; case TOK_CFLOAT: t = VT_FLOAT; goto push_tokc; case TOK_CDOUBLE: t = VT_DOUBLE; goto push_tokc; case TOK_CLDOUBLE: t = VT_LDOUBLE; goto push_tokc; case TOK_CLONG: t = (LONG_SIZE == 8 ? VT_LLONG : VT_INT) | VT_LONG; goto push_tokc; case TOK_CULONG: t = (LONG_SIZE == 8 ? VT_LLONG : VT_INT) | VT_LONG | VT_UNSIGNED; goto push_tokc; case TOK___FUNCTION__: if (!gnu_ext) goto tok_identifier; /* fall thru */ case TOK___FUNC__: { void *ptr; int len; /* special function name identifier */ len = strlen(funcname) + 1; /* generate char[len] type */ type.t = VT_BYTE; mk_pointer(&type); type.t |= VT_ARRAY; type.ref->c = len; vpush_ref(&type, data_section, data_section->data_offset, len); if (!NODATA_WANTED) { ptr = section_ptr_add(data_section, len); memcpy(ptr, funcname, len); } next(); } break; case TOK_LSTR: #ifdef TCC_TARGET_PE t = VT_SHORT | VT_UNSIGNED; #else t = VT_INT; #endif goto str_init; case TOK_STR: /* string parsing */ t = VT_BYTE; if (tcc_state->char_is_unsigned) t = VT_BYTE | VT_UNSIGNED; str_init: if (tcc_state->warn_write_strings) t |= VT_CONSTANT; type.t = t; mk_pointer(&type); type.t |= VT_ARRAY; memset(&ad, 0, sizeof(AttributeDef)); decl_initializer_alloc(&type, &ad, VT_CONST, 2, 0, 0); break; case '(': next(); /* cast ? */ if (parse_btype(&type, &ad)) { type_decl(&type, &ad, &n, TYPE_ABSTRACT); skip(')'); /* check ISOC99 compound literal */ if (tok == '{') { /* data is allocated locally by default */ if (global_expr) r = VT_CONST; else r = VT_LOCAL; /* all except arrays are lvalues */ if (!(type.t & VT_ARRAY)) r |= lvalue_type(type.t); memset(&ad, 0, sizeof(AttributeDef)); decl_initializer_alloc(&type, &ad, r, 1, 0, 0); } else { if (sizeof_caller) { vpush(&type); return; } unary(); gen_cast(&type); } } else if (tok == '{') { int saved_nocode_wanted = nocode_wanted; if (const_wanted) tcc_error("expected constant"); /* save all registers */ save_regs(0); /* statement expression : we do not accept break/continue inside as GCC does. We do retain the nocode_wanted state, as statement expressions can't ever be entered from the outside, so any reactivation of code emission (from labels or loop heads) can be disabled again after the end of it. */ block(NULL, NULL, 1); nocode_wanted = saved_nocode_wanted; skip(')'); } else { gexpr(); skip(')'); } break; case '*': next(); unary(); indir(); break; case '&': next(); unary(); /* functions names must be treated as function pointers, except for unary '&' and sizeof. Since we consider that functions are not lvalues, we only have to handle it there and in function calls. */ /* arrays can also be used although they are not lvalues */ if ((vtop->type.t & VT_BTYPE) != VT_FUNC && !(vtop->type.t & VT_ARRAY)) test_lvalue(); mk_pointer(&vtop->type); gaddrof(); break; case '!': next(); unary(); if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) { gen_cast_s(VT_BOOL); vtop->c.i = !vtop->c.i; } else if ((vtop->r & VT_VALMASK) == VT_CMP) vtop->c.i ^= 1; else { save_regs(1); vseti(VT_JMP, gvtst(1, 0)); } break; case '~': next(); unary(); vpushi(-1); gen_op('^'); break; case '+': next(); unary(); if ((vtop->type.t & VT_BTYPE) == VT_PTR) tcc_error("pointer not accepted for unary plus"); /* In order to force cast, we add zero, except for floating point where we really need an noop (otherwise -0.0 will be transformed into +0.0). */ if (!is_float(vtop->type.t)) { vpushi(0); gen_op('+'); } break; case TOK_SIZEOF: case TOK_ALIGNOF1: case TOK_ALIGNOF2: t = tok; next(); in_sizeof++; expr_type(&type, unary); /* Perform a in_sizeof = 0; */ s = vtop[1].sym; /* hack: accessing previous vtop */ size = type_size(&type, &align); if (s && s->a.aligned) align = 1 << (s->a.aligned - 1); if (t == TOK_SIZEOF) { if (!(type.t & VT_VLA)) { if (size < 0) tcc_error("sizeof applied to an incomplete type"); vpushs(size); } else { vla_runtime_type_size(&type, &align); } } else { vpushs(align); } vtop->type.t |= VT_UNSIGNED; break; case TOK_builtin_expect: /* __builtin_expect is a no-op for now */ parse_builtin_params(0, "ee"); vpop(); break; case TOK_builtin_types_compatible_p: parse_builtin_params(0, "tt"); vtop[-1].type.t &= ~(VT_CONSTANT | VT_VOLATILE); vtop[0].type.t &= ~(VT_CONSTANT | VT_VOLATILE); n = is_compatible_types(&vtop[-1].type, &vtop[0].type); vtop -= 2; vpushi(n); break; case TOK_builtin_choose_expr: { int64_t c; next(); skip('('); c = expr_const64(); skip(','); if (!c) { nocode_wanted++; } expr_eq(); if (!c) { vpop(); nocode_wanted--; } skip(','); if (c) { nocode_wanted++; } expr_eq(); if (c) { vpop(); nocode_wanted--; } skip(')'); } break; case TOK_builtin_constant_p: parse_builtin_params(1, "e"); n = (vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST; vtop--; vpushi(n); break; case TOK_builtin_frame_address: case TOK_builtin_return_address: { int tok1 = tok; int level; next(); skip('('); if (tok != TOK_CINT) { tcc_error("%s only takes positive integers", tok1 == TOK_builtin_return_address ? "__builtin_return_address" : "__builtin_frame_address"); } level = (uint32_t)tokc.i; next(); skip(')'); type.t = VT_VOID; mk_pointer(&type); vset(&type, VT_LOCAL, 0); /* local frame */ while (level--) { mk_pointer(&vtop->type); indir(); /* -> parent frame */ } if (tok1 == TOK_builtin_return_address) { // assume return address is just above frame pointer on stack vpushi(PTR_SIZE); gen_op('+'); mk_pointer(&vtop->type); indir(); } } break; #ifdef TCC_TARGET_X86_64 #ifdef TCC_TARGET_PE case TOK_builtin_va_start: parse_builtin_params(0, "ee"); r = vtop->r & VT_VALMASK; if (r == VT_LLOCAL) r = VT_LOCAL; if (r != VT_LOCAL) tcc_error("__builtin_va_start expects a local variable"); vtop->r = r; vtop->type = char_pointer_type; vtop->c.i += 8; vstore(); break; #else case TOK_builtin_va_arg_types: parse_builtin_params(0, "t"); vpushi(classify_x86_64_va_arg(&vtop->type)); vswap(); vpop(); break; #endif #endif #ifdef TCC_TARGET_ARM64 case TOK___va_start: { parse_builtin_params(0, "ee"); //xx check types gen_va_start(); vpushi(0); vtop->type.t = VT_VOID; break; } case TOK___va_arg: { parse_builtin_params(0, "et"); type = vtop->type; vpop(); //xx check types gen_va_arg(&type); vtop->type = type; break; } case TOK___arm64_clear_cache: { parse_builtin_params(0, "ee"); gen_clear_cache(); vpushi(0); vtop->type.t = VT_VOID; break; } #endif /* pre operations */ case TOK_INC: case TOK_DEC: t = tok; next(); unary(); inc(0, t); break; case '-': next(); unary(); t = vtop->type.t & VT_BTYPE; if (is_float(t)) { /* In IEEE negate(x) isn't subtract(0,x), but rather subtract(-0, x). */ vpush(&vtop->type); if (t == VT_FLOAT) vtop->c.f = -1.0 * 0.0; else if (t == VT_DOUBLE) vtop->c.d = -1.0 * 0.0; else vtop->c.ld = -1.0 * 0.0; } else vpushi(0); vswap(); gen_op('-'); break; case TOK_LAND: if (!gnu_ext) goto tok_identifier; next(); /* allow to take the address of a label */ if (tok < TOK_UIDENT) expect("label identifier"); s = label_find(tok); if (!s) { s = label_push(&global_label_stack, tok, LABEL_FORWARD); } else { if (s->r == LABEL_DECLARED) s->r = LABEL_FORWARD; } if (!s->type.t) { s->type.t = VT_VOID; mk_pointer(&s->type); s->type.t |= VT_STATIC; } vpushsym(&s->type, s); next(); break; case TOK_GENERIC: { CType controlling_type; int has_default = 0; int has_match = 0; int learn = 0; TokenString *str = NULL; next(); skip('('); expr_type(&controlling_type, expr_eq); controlling_type.t &= ~(VT_CONSTANT | VT_VOLATILE | VT_ARRAY); for (;;) { learn = 0; skip(','); if (tok == TOK_DEFAULT) { if (has_default) tcc_error("too many 'default'"); has_default = 1; if (!has_match) learn = 1; next(); } else { AttributeDef ad_tmp; int itmp; CType cur_type; parse_btype(&cur_type, &ad_tmp); type_decl(&cur_type, &ad_tmp, &itmp, TYPE_ABSTRACT); if (compare_types(&controlling_type, &cur_type, 0)) { if (has_match) { tcc_error("type match twice"); } has_match = 1; learn = 1; } } skip(':'); if (learn) { if (str) tok_str_free(str); skip_or_save_block(&str); } else { skip_or_save_block(NULL); } if (tok == ')') break; } if (!str) { char buf[60]; type_to_str(buf, sizeof buf, &controlling_type, NULL); tcc_error("type '%s' does not match any association", buf); } begin_macro(str, 1); next(); expr_eq(); if (tok != TOK_EOF) expect(","); end_macro(); next(); break; } // special qnan , snan and infinity values case TOK___NAN__: vpush64(VT_DOUBLE, 0x7ff8000000000000ULL); next(); break; case TOK___SNAN__: vpush64(VT_DOUBLE, 0x7ff0000000000001ULL); next(); break; case TOK___INF__: vpush64(VT_DOUBLE, 0x7ff0000000000000ULL); next(); break; default: tok_identifier: t = tok; next(); if (t < TOK_UIDENT) expect("identifier"); s = sym_find(t); if (!s || IS_ASM_SYM(s)) { const char *name = get_tok_str(t, NULL); if (tok != '(') tcc_error("'%s' undeclared", name); /* for simple function calls, we tolerate undeclared external reference to int() function */ if (tcc_state->warn_implicit_function_declaration #ifdef TCC_TARGET_PE /* people must be warned about using undeclared WINAPI functions (which usually start with uppercase letter) */ || (name[0] >= 'A' && name[0] <= 'Z') #endif ) tcc_warning("implicit declaration of function '%s'", name); s = external_global_sym(t, &func_old_type, 0); } r = s->r; /* A symbol that has a register is a local register variable, which starts out as VT_LOCAL value. */ if ((r & VT_VALMASK) < VT_CONST) r = (r & ~VT_VALMASK) | VT_LOCAL; vset(&s->type, r, s->c); /* Point to s as backpointer (even without r&VT_SYM). Will be used by at least the x86 inline asm parser for regvars. */ vtop->sym = s; if (r & VT_SYM) { vtop->c.i = 0; } else if (r == VT_CONST && IS_ENUM_VAL(s->type.t)) { vtop->c.i = s->enum_val; } break; } /* post operations */ while (1) { if (tok == TOK_INC || tok == TOK_DEC) { inc(1, tok); next(); } else if (tok == '.' || tok == TOK_ARROW || tok == TOK_CDOUBLE) { int qualifiers; /* field */ if (tok == TOK_ARROW) indir(); qualifiers = vtop->type.t & (VT_CONSTANT | VT_VOLATILE); test_lvalue(); gaddrof(); /* expect pointer on structure */ if ((vtop->type.t & VT_BTYPE) != VT_STRUCT) expect("struct or union"); if (tok == TOK_CDOUBLE) expect("field name"); next(); if (tok == TOK_CINT || tok == TOK_CUINT) expect("field name"); s = find_field(&vtop->type, tok); if (!s) tcc_error("field not found: %s", get_tok_str(tok & ~SYM_FIELD, &tokc)); /* add field offset to pointer */ vtop->type = char_pointer_type; /* change type to 'char *' */ vpushi(s->c); gen_op('+'); /* change type to field type, and set to lvalue */ vtop->type = s->type; vtop->type.t |= qualifiers; /* an array is never an lvalue */ if (!(vtop->type.t & VT_ARRAY)) { vtop->r |= lvalue_type(vtop->type.t); #ifdef CONFIG_TCC_BCHECK /* if bound checking, the referenced pointer must be checked */ if (tcc_state->do_bounds_check && (vtop->r & VT_VALMASK) != VT_LOCAL) vtop->r |= VT_MUSTBOUND; #endif } next(); } else if (tok == '[') { next(); gexpr(); gen_op('+'); indir(); skip(']'); } else if (tok == '(') { SValue ret; Sym *sa; int nb_args, ret_nregs, ret_align, regsize, variadic; /* function call */ if ((vtop->type.t & VT_BTYPE) != VT_FUNC) { /* pointer test (no array accepted) */ if ((vtop->type.t & (VT_BTYPE | VT_ARRAY)) == VT_PTR) { vtop->type = *pointed_type(&vtop->type); if ((vtop->type.t & VT_BTYPE) != VT_FUNC) goto error_func; } else { error_func: expect("function pointer"); } } else { vtop->r &= ~VT_LVAL; /* no lvalue */ } /* get return type */ s = vtop->type.ref; next(); sa = s->next; /* first parameter */ nb_args = regsize = 0; ret.r2 = VT_CONST; /* compute first implicit argument if a structure is returned */ if ((s->type.t & VT_BTYPE) == VT_STRUCT) { variadic = (s->f.func_type == FUNC_ELLIPSIS); ret_nregs = gfunc_sret(&s->type, variadic, &ret.type, &ret_align, ®size); if (!ret_nregs) { /* get some space for the returned structure */ size = type_size(&s->type, &align); #ifdef TCC_TARGET_ARM64 /* On arm64, a small struct is return in registers. It is much easier to write it to memory if we know that we are allowed to write some extra bytes, so round the allocated space up to a power of 2: */ if (size < 16) while (size & (size - 1)) size = (size | (size - 1)) + 1; #endif loc = (loc - size) & -align; ret.type = s->type; ret.r = VT_LOCAL | VT_LVAL; /* pass it as 'int' to avoid structure arg passing problems */ vseti(VT_LOCAL, loc); ret.c = vtop->c; nb_args++; } } else { ret_nregs = 1; ret.type = s->type; } if (ret_nregs) { /* return in register */ if (is_float(ret.type.t)) { ret.r = reg_fret(ret.type.t); #ifdef TCC_TARGET_X86_64 if ((ret.type.t & VT_BTYPE) == VT_QFLOAT) ret.r2 = REG_QRET; #endif } else { #ifndef TCC_TARGET_ARM64 #ifdef TCC_TARGET_X86_64 if ((ret.type.t & VT_BTYPE) == VT_QLONG) #else if ((ret.type.t & VT_BTYPE) == VT_LLONG) #endif ret.r2 = REG_LRET; #endif ret.r = REG_IRET; } ret.c.i = 0; } if (tok != ')') { for(;;) { expr_eq(); gfunc_param_typed(s, sa); nb_args++; if (sa) sa = sa->next; if (tok == ')') break; skip(','); } } if (sa) tcc_error("too few arguments to function"); skip(')'); gfunc_call(nb_args); /* return value */ for (r = ret.r + ret_nregs + !ret_nregs; r-- > ret.r;) { vsetc(&ret.type, r, &ret.c); vtop->r2 = ret.r2; /* Loop only happens when r2 is VT_CONST */ } /* handle packed struct return */ if (((s->type.t & VT_BTYPE) == VT_STRUCT) && ret_nregs) { int addr, offset; size = type_size(&s->type, &align); /* We're writing whole regs often, make sure there's enough space. Assume register size is power of 2. */ if (regsize > align) align = regsize; loc = (loc - size) & -align; addr = loc; offset = 0; for (;;) { vset(&ret.type, VT_LOCAL | VT_LVAL, addr + offset); vswap(); vstore(); vtop--; if (--ret_nregs == 0) break; offset += regsize; } vset(&s->type, VT_LOCAL | VT_LVAL, addr); } } else { break; } } } ST_FUNC void expr_prod(void) { int t; unary(); while (tok == '*' || tok == '/' || tok == '%') { t = tok; next(); unary(); gen_op(t); } } ST_FUNC void expr_sum(void) { int t; expr_prod(); while (tok == '+' || tok == '-') { t = tok; next(); expr_prod(); gen_op(t); } } static void expr_shift(void) { int t; expr_sum(); while (tok == TOK_SHL || tok == TOK_SAR) { t = tok; next(); expr_sum(); gen_op(t); } } static void expr_cmp(void) { int t; expr_shift(); while ((tok >= TOK_ULE && tok <= TOK_GT) || tok == TOK_ULT || tok == TOK_UGE) { t = tok; next(); expr_shift(); gen_op(t); } } static void expr_cmpeq(void) { int t; expr_cmp(); while (tok == TOK_EQ || tok == TOK_NE) { t = tok; next(); expr_cmp(); gen_op(t); } } static void expr_and(void) { expr_cmpeq(); while (tok == '&') { next(); expr_cmpeq(); gen_op('&'); } } static void expr_xor(void) { expr_and(); while (tok == '^') { next(); expr_and(); gen_op('^'); } } static void expr_or(void) { expr_xor(); while (tok == '|') { next(); expr_xor(); gen_op('|'); } } static void expr_land(void) { expr_or(); if (tok == TOK_LAND) { int t = 0; for(;;) { if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) { gen_cast_s(VT_BOOL); if (vtop->c.i) { vpop(); } else { nocode_wanted++; while (tok == TOK_LAND) { next(); expr_or(); vpop(); } nocode_wanted--; if (t) gsym(t); gen_cast_s(VT_INT); break; } } else { if (!t) save_regs(1); t = gvtst(1, t); } if (tok != TOK_LAND) { if (t) vseti(VT_JMPI, t); else vpushi(1); break; } next(); expr_or(); } } } static void expr_lor(void) { expr_land(); if (tok == TOK_LOR) { int t = 0; for(;;) { if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) { gen_cast_s(VT_BOOL); if (!vtop->c.i) { vpop(); } else { nocode_wanted++; while (tok == TOK_LOR) { next(); expr_land(); vpop(); } nocode_wanted--; if (t) gsym(t); gen_cast_s(VT_INT); break; } } else { if (!t) save_regs(1); t = gvtst(0, t); } if (tok != TOK_LOR) { if (t) vseti(VT_JMP, t); else vpushi(0); break; } next(); expr_land(); } } } /* Assuming vtop is a value used in a conditional context (i.e. compared with zero) return 0 if it's false, 1 if true and -1 if it can't be statically determined. */ static int condition_3way(void) { int c = -1; if ((vtop->r & (VT_VALMASK | VT_LVAL)) == VT_CONST && (!(vtop->r & VT_SYM) || !vtop->sym->a.weak)) { vdup(); gen_cast_s(VT_BOOL); c = vtop->c.i; vpop(); } return c; } static void expr_cond(void) { int tt, u, r1, r2, rc, t1, t2, bt1, bt2, islv, c, g; SValue sv; CType type, type1, type2; expr_lor(); if (tok == '?') { next(); c = condition_3way(); g = (tok == ':' && gnu_ext); if (c < 0) { /* needed to avoid having different registers saved in each branch */ if (is_float(vtop->type.t)) { rc = RC_FLOAT; #ifdef TCC_TARGET_X86_64 if ((vtop->type.t & VT_BTYPE) == VT_LDOUBLE) { rc = RC_ST0; } #endif } else rc = RC_INT; gv(rc); save_regs(1); if (g) gv_dup(); tt = gvtst(1, 0); } else { if (!g) vpop(); tt = 0; } if (1) { if (c == 0) nocode_wanted++; if (!g) gexpr(); type1 = vtop->type; sv = *vtop; /* save value to handle it later */ vtop--; /* no vpop so that FP stack is not flushed */ skip(':'); u = 0; if (c < 0) u = gjmp(0); gsym(tt); if (c == 0) nocode_wanted--; if (c == 1) nocode_wanted++; expr_cond(); if (c == 1) nocode_wanted--; type2 = vtop->type; t1 = type1.t; bt1 = t1 & VT_BTYPE; t2 = type2.t; bt2 = t2 & VT_BTYPE; type.ref = NULL; /* cast operands to correct type according to ISOC rules */ if (is_float(bt1) || is_float(bt2)) { if (bt1 == VT_LDOUBLE || bt2 == VT_LDOUBLE) { type.t = VT_LDOUBLE; } else if (bt1 == VT_DOUBLE || bt2 == VT_DOUBLE) { type.t = VT_DOUBLE; } else { type.t = VT_FLOAT; } } else if (bt1 == VT_LLONG || bt2 == VT_LLONG) { /* cast to biggest op */ type.t = VT_LLONG | VT_LONG; if (bt1 == VT_LLONG) type.t &= t1; if (bt2 == VT_LLONG) type.t &= t2; /* convert to unsigned if it does not fit in a long long */ if ((t1 & (VT_BTYPE | VT_UNSIGNED | VT_BITFIELD)) == (VT_LLONG | VT_UNSIGNED) || (t2 & (VT_BTYPE | VT_UNSIGNED | VT_BITFIELD)) == (VT_LLONG | VT_UNSIGNED)) type.t |= VT_UNSIGNED; } else if (bt1 == VT_PTR || bt2 == VT_PTR) { /* If one is a null ptr constant the result type is the other. */ if (is_null_pointer (vtop)) type = type1; else if (is_null_pointer (&sv)) type = type2; /* XXX: test pointer compatibility, C99 has more elaborate rules here. */ else type = type1; } else if (bt1 == VT_FUNC || bt2 == VT_FUNC) { /* XXX: test function pointer compatibility */ type = bt1 == VT_FUNC ? type1 : type2; } else if (bt1 == VT_STRUCT || bt2 == VT_STRUCT) { /* XXX: test structure compatibility */ type = bt1 == VT_STRUCT ? type1 : type2; } else if (bt1 == VT_VOID || bt2 == VT_VOID) { /* NOTE: as an extension, we accept void on only one side */ type.t = VT_VOID; } else { /* integer operations */ type.t = VT_INT | (VT_LONG & (t1 | t2)); /* convert to unsigned if it does not fit in an integer */ if ((t1 & (VT_BTYPE | VT_UNSIGNED | VT_BITFIELD)) == (VT_INT | VT_UNSIGNED) || (t2 & (VT_BTYPE | VT_UNSIGNED | VT_BITFIELD)) == (VT_INT | VT_UNSIGNED)) type.t |= VT_UNSIGNED; } /* keep structs lvalue by transforming `(expr ? a : b)` to `*(expr ? &a : &b)` so that `(expr ? a : b).mem` does not error with "lvalue expected" */ islv = (vtop->r & VT_LVAL) && (sv.r & VT_LVAL) && VT_STRUCT == (type.t & VT_BTYPE); islv &= c < 0; /* now we convert second operand */ if (c != 1) { gen_cast(&type); if (islv) { mk_pointer(&vtop->type); gaddrof(); } else if (VT_STRUCT == (vtop->type.t & VT_BTYPE)) gaddrof(); } rc = RC_INT; if (is_float(type.t)) { rc = RC_FLOAT; #ifdef TCC_TARGET_X86_64 if ((type.t & VT_BTYPE) == VT_LDOUBLE) { rc = RC_ST0; } #endif } else if ((type.t & VT_BTYPE) == VT_LLONG) { /* for long longs, we use fixed registers to avoid having to handle a complicated move */ rc = RC_IRET; } tt = r2 = 0; if (c < 0) { r2 = gv(rc); tt = gjmp(0); } gsym(u); /* this is horrible, but we must also convert first operand */ if (c != 0) { *vtop = sv; gen_cast(&type); if (islv) { mk_pointer(&vtop->type); gaddrof(); } else if (VT_STRUCT == (vtop->type.t & VT_BTYPE)) gaddrof(); } if (c < 0) { r1 = gv(rc); move_reg(r2, r1, type.t); vtop->r = r2; gsym(tt); if (islv) indir(); } } } } static void expr_eq(void) { int t; expr_cond(); if (tok == '=' || (tok >= TOK_A_MOD && tok <= TOK_A_DIV) || tok == TOK_A_XOR || tok == TOK_A_OR || tok == TOK_A_SHL || tok == TOK_A_SAR) { test_lvalue(); t = tok; next(); if (t == '=') { expr_eq(); } else { vdup(); expr_eq(); gen_op(t & 0x7f); } vstore(); } } ST_FUNC void gexpr(void) { while (1) { expr_eq(); if (tok != ',') break; vpop(); next(); } } /* parse a constant expression and return value in vtop. */ static void expr_const1(void) { const_wanted++; nocode_wanted++; expr_cond(); nocode_wanted--; const_wanted--; } /* parse an integer constant and return its value. */ static inline int64_t expr_const64(void) { int64_t c; expr_const1(); if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) != VT_CONST) expect("constant expression"); c = vtop->c.i; vpop(); return c; } /* parse an integer constant and return its value. Complain if it doesn't fit 32bit (signed or unsigned). */ ST_FUNC int expr_const(void) { int c; int64_t wc = expr_const64(); c = wc; if (c != wc && (unsigned)c != wc) tcc_error("constant exceeds 32 bit"); return c; } /* return the label token if current token is a label, otherwise return zero */ static int is_label(void) { int last_tok; /* fast test first */ if (tok < TOK_UIDENT) return 0; /* no need to save tokc because tok is an identifier */ last_tok = tok; next(); if (tok == ':') { return last_tok; } else { unget_tok(last_tok); return 0; } } #ifndef TCC_TARGET_ARM64 static void gfunc_return(CType *func_type) { if ((func_type->t & VT_BTYPE) == VT_STRUCT) { CType type, ret_type; int ret_align, ret_nregs, regsize; ret_nregs = gfunc_sret(func_type, func_var, &ret_type, &ret_align, ®size); if (0 == ret_nregs) { /* if returning structure, must copy it to implicit first pointer arg location */ type = *func_type; mk_pointer(&type); vset(&type, VT_LOCAL | VT_LVAL, func_vc); indir(); vswap(); /* copy structure value to pointer */ vstore(); } else { /* returning structure packed into registers */ int r, size, addr, align; size = type_size(func_type,&align); if ((vtop->r != (VT_LOCAL | VT_LVAL) || (vtop->c.i & (ret_align-1))) && (align & (ret_align-1))) { loc = (loc - size) & -ret_align; addr = loc; type = *func_type; vset(&type, VT_LOCAL | VT_LVAL, addr); vswap(); vstore(); vpop(); vset(&ret_type, VT_LOCAL | VT_LVAL, addr); } vtop->type = ret_type; if (is_float(ret_type.t)) r = rc_fret(ret_type.t); else r = RC_IRET; if (ret_nregs == 1) gv(r); else { for (;;) { vdup(); gv(r); vpop(); if (--ret_nregs == 0) break; /* We assume that when a structure is returned in multiple registers, their classes are consecutive values of the suite s(n) = 2^n */ r <<= 1; vtop->c.i += regsize; } } } } else if (is_float(func_type->t)) { gv(rc_fret(func_type->t)); } else { gv(RC_IRET); } vtop--; /* NOT vpop() because on x86 it would flush the fp stack */ } #endif static int case_cmp(const void *pa, const void *pb) { int64_t a = (*(struct case_t**) pa)->v1; int64_t b = (*(struct case_t**) pb)->v1; return a < b ? -1 : a > b; } static void gcase(struct case_t **base, int len, int *bsym) { struct case_t *p; int e; int ll = (vtop->type.t & VT_BTYPE) == VT_LLONG; gv(RC_INT); while (len > 4) { /* binary search */ p = base[len/2]; vdup(); if (ll) vpushll(p->v2); else vpushi(p->v2); gen_op(TOK_LE); e = gtst(1, 0); vdup(); if (ll) vpushll(p->v1); else vpushi(p->v1); gen_op(TOK_GE); gtst_addr(0, p->sym); /* v1 <= x <= v2 */ /* x < v1 */ gcase(base, len/2, bsym); if (cur_switch->def_sym) gjmp_addr(cur_switch->def_sym); else *bsym = gjmp(*bsym); /* x > v2 */ gsym(e); e = len/2 + 1; base += e; len -= e; } /* linear scan */ while (len--) { p = *base++; vdup(); if (ll) vpushll(p->v2); else vpushi(p->v2); if (p->v1 == p->v2) { gen_op(TOK_EQ); gtst_addr(0, p->sym); } else { gen_op(TOK_LE); e = gtst(1, 0); vdup(); if (ll) vpushll(p->v1); else vpushi(p->v1); gen_op(TOK_GE); gtst_addr(0, p->sym); gsym(e); } } } static void block(int *bsym, int *csym, int is_expr) { int a, b, c, d, cond; Sym *s; /* generate line number info */ if (tcc_state->do_debug) tcc_debug_line(tcc_state); if (is_expr) { /* default return value is (void) */ vpushi(0); vtop->type.t = VT_VOID; } if (tok == TOK_IF) { /* if test */ int saved_nocode_wanted = nocode_wanted; next(); skip('('); gexpr(); skip(')'); cond = condition_3way(); if (cond == 1) a = 0, vpop(); else a = gvtst(1, 0); if (cond == 0) nocode_wanted |= 0x20000000; block(bsym, csym, 0); if (cond != 1) nocode_wanted = saved_nocode_wanted; c = tok; if (c == TOK_ELSE) { next(); d = gjmp(0); gsym(a); if (cond == 1) nocode_wanted |= 0x20000000; block(bsym, csym, 0); gsym(d); /* patch else jmp */ if (cond != 0) nocode_wanted = saved_nocode_wanted; } else gsym(a); } else if (tok == TOK_WHILE) { int saved_nocode_wanted; nocode_wanted &= ~0x20000000; next(); d = ind; vla_sp_restore(); skip('('); gexpr(); skip(')'); a = gvtst(1, 0); b = 0; ++local_scope; saved_nocode_wanted = nocode_wanted; block(&a, &b, 0); nocode_wanted = saved_nocode_wanted; --local_scope; gjmp_addr(d); gsym(a); gsym_addr(b, d); } else if (tok == '{') { Sym *llabel; int block_vla_sp_loc = vla_sp_loc, saved_vlas_in_scope = vlas_in_scope; next(); /* record local declaration stack position */ s = local_stack; llabel = local_label_stack; ++local_scope; /* handle local labels declarations */ if (tok == TOK_LABEL) { next(); for(;;) { if (tok < TOK_UIDENT) expect("label identifier"); label_push(&local_label_stack, tok, LABEL_DECLARED); next(); if (tok == ',') { next(); } else { skip(';'); break; } } } while (tok != '}') { if ((a = is_label())) unget_tok(a); else decl(VT_LOCAL); if (tok != '}') { if (is_expr) vpop(); block(bsym, csym, is_expr); } } /* pop locally defined labels */ label_pop(&local_label_stack, llabel, is_expr); /* pop locally defined symbols */ --local_scope; /* In the is_expr case (a statement expression is finished here), vtop might refer to symbols on the local_stack. Either via the type or via vtop->sym. We can't pop those nor any that in turn might be referred to. To make it easier we don't roll back any symbols in that case; some upper level call to block() will do that. We do have to remove such symbols from the lookup tables, though. sym_pop will do that. */ sym_pop(&local_stack, s, is_expr); /* Pop VLA frames and restore stack pointer if required */ if (vlas_in_scope > saved_vlas_in_scope) { vla_sp_loc = saved_vlas_in_scope ? block_vla_sp_loc : vla_sp_root_loc; vla_sp_restore(); } vlas_in_scope = saved_vlas_in_scope; next(); } else if (tok == TOK_RETURN) { next(); if (tok != ';') { gexpr(); gen_assign_cast(&func_vt); if ((func_vt.t & VT_BTYPE) == VT_VOID) vtop--; else gfunc_return(&func_vt); } skip(';'); /* jump unless last stmt in top-level block */ if (tok != '}' || local_scope != 1) rsym = gjmp(rsym); nocode_wanted |= 0x20000000; } else if (tok == TOK_BREAK) { /* compute jump */ if (!bsym) tcc_error("cannot break"); *bsym = gjmp(*bsym); next(); skip(';'); nocode_wanted |= 0x20000000; } else if (tok == TOK_CONTINUE) { /* compute jump */ if (!csym) tcc_error("cannot continue"); vla_sp_restore_root(); *csym = gjmp(*csym); next(); skip(';'); } else if (tok == TOK_FOR) { int e; int saved_nocode_wanted; nocode_wanted &= ~0x20000000; next(); skip('('); s = local_stack; ++local_scope; if (tok != ';') { /* c99 for-loop init decl? */ if (!decl0(VT_LOCAL, 1, NULL)) { /* no, regular for-loop init expr */ gexpr(); vpop(); } } skip(';'); d = ind; c = ind; vla_sp_restore(); a = 0; b = 0; if (tok != ';') { gexpr(); a = gvtst(1, 0); } skip(';'); if (tok != ')') { e = gjmp(0); c = ind; vla_sp_restore(); gexpr(); vpop(); gjmp_addr(d); gsym(e); } skip(')'); saved_nocode_wanted = nocode_wanted; block(&a, &b, 0); nocode_wanted = saved_nocode_wanted; gjmp_addr(c); gsym(a); gsym_addr(b, c); --local_scope; sym_pop(&local_stack, s, 0); } else if (tok == TOK_DO) { int saved_nocode_wanted; nocode_wanted &= ~0x20000000; next(); a = 0; b = 0; d = ind; vla_sp_restore(); saved_nocode_wanted = nocode_wanted; block(&a, &b, 0); skip(TOK_WHILE); skip('('); gsym(b); gexpr(); c = gvtst(0, 0); gsym_addr(c, d); nocode_wanted = saved_nocode_wanted; skip(')'); gsym(a); skip(';'); } else if (tok == TOK_SWITCH) { struct switch_t *saved, sw; int saved_nocode_wanted = nocode_wanted; SValue switchval; next(); skip('('); gexpr(); skip(')'); switchval = *vtop--; a = 0; b = gjmp(0); /* jump to first case */ sw.p = NULL; sw.n = 0; sw.def_sym = 0; saved = cur_switch; cur_switch = &sw; block(&a, csym, 0); nocode_wanted = saved_nocode_wanted; a = gjmp(a); /* add implicit break */ /* case lookup */ gsym(b); qsort(sw.p, sw.n, sizeof(void*), case_cmp); for (b = 1; b < sw.n; b++) if (sw.p[b - 1]->v2 >= sw.p[b]->v1) tcc_error("duplicate case value"); /* Our switch table sorting is signed, so the compared value needs to be as well when it's 64bit. */ if ((switchval.type.t & VT_BTYPE) == VT_LLONG) switchval.type.t &= ~VT_UNSIGNED; vpushv(&switchval); gcase(sw.p, sw.n, &a); vpop(); if (sw.def_sym) gjmp_addr(sw.def_sym); dynarray_reset(&sw.p, &sw.n); cur_switch = saved; /* break label */ gsym(a); } else if (tok == TOK_CASE) { struct case_t *cr = tcc_malloc(sizeof(struct case_t)); if (!cur_switch) expect("switch"); nocode_wanted &= ~0x20000000; next(); cr->v1 = cr->v2 = expr_const64(); if (gnu_ext && tok == TOK_DOTS) { next(); cr->v2 = expr_const64(); if (cr->v2 < cr->v1) tcc_warning("empty case range"); } cr->sym = ind; dynarray_add(&cur_switch->p, &cur_switch->n, cr); skip(':'); is_expr = 0; goto block_after_label; } else if (tok == TOK_DEFAULT) { next(); skip(':'); if (!cur_switch) expect("switch"); if (cur_switch->def_sym) tcc_error("too many 'default'"); cur_switch->def_sym = ind; is_expr = 0; goto block_after_label; } else if (tok == TOK_GOTO) { next(); if (tok == '*' && gnu_ext) { /* computed goto */ next(); gexpr(); if ((vtop->type.t & VT_BTYPE) != VT_PTR) expect("pointer"); ggoto(); } else if (tok >= TOK_UIDENT) { s = label_find(tok); /* put forward definition if needed */ if (!s) { s = label_push(&global_label_stack, tok, LABEL_FORWARD); } else { if (s->r == LABEL_DECLARED) s->r = LABEL_FORWARD; } vla_sp_restore_root(); if (s->r & LABEL_FORWARD) s->jnext = gjmp(s->jnext); else gjmp_addr(s->jnext); next(); } else { expect("label identifier"); } skip(';'); } else if (tok == TOK_ASM1 || tok == TOK_ASM2 || tok == TOK_ASM3) { asm_instr(); } else { b = is_label(); if (b) { /* label case */ next(); s = label_find(b); if (s) { if (s->r == LABEL_DEFINED) tcc_error("duplicate label '%s'", get_tok_str(s->v, NULL)); gsym(s->jnext); s->r = LABEL_DEFINED; } else { s = label_push(&global_label_stack, b, LABEL_DEFINED); } s->jnext = ind; vla_sp_restore(); /* we accept this, but it is a mistake */ block_after_label: nocode_wanted &= ~0x20000000; if (tok == '}') { tcc_warning("deprecated use of label at end of compound statement"); } else { if (is_expr) vpop(); block(bsym, csym, is_expr); } } else { /* expression case */ if (tok != ';') { if (is_expr) { vpop(); gexpr(); } else { gexpr(); vpop(); } } skip(';'); } } } /* This skips over a stream of tokens containing balanced {} and () pairs, stopping at outer ',' ';' and '}' (or matching '}' if we started with a '{'). If STR then allocates and stores the skipped tokens in *STR. This doesn't check if () and {} are nested correctly, i.e. "({)}" is accepted. */ static void skip_or_save_block(TokenString **str) { int braces = tok == '{'; int level = 0; if (str) *str = tok_str_alloc(); while ((level > 0 || (tok != '}' && tok != ',' && tok != ';' && tok != ')'))) { int t; if (tok == TOK_EOF) { if (str || level > 0) tcc_error("unexpected end of file"); else break; } if (str) tok_str_add_tok(*str); t = tok; next(); if (t == '{' || t == '(') { level++; } else if (t == '}' || t == ')') { level--; if (level == 0 && braces && t == '}') break; } } if (str) { tok_str_add(*str, -1); tok_str_add(*str, 0); } } #define EXPR_CONST 1 #define EXPR_ANY 2 static void parse_init_elem(int expr_type) { int saved_global_expr; switch(expr_type) { case EXPR_CONST: /* compound literals must be allocated globally in this case */ saved_global_expr = global_expr; global_expr = 1; expr_const1(); global_expr = saved_global_expr; /* NOTE: symbols are accepted, as well as lvalue for anon symbols (compound literals). */ if (((vtop->r & (VT_VALMASK | VT_LVAL)) != VT_CONST && ((vtop->r & (VT_SYM|VT_LVAL)) != (VT_SYM|VT_LVAL) || vtop->sym->v < SYM_FIRST_ANOM)) #ifdef TCC_TARGET_PE || ((vtop->r & VT_SYM) && vtop->sym->a.dllimport) #endif ) tcc_error("initializer element is not constant"); break; case EXPR_ANY: expr_eq(); break; } } /* put zeros for variable based init */ static void init_putz(Section *sec, unsigned long c, int size) { if (sec) { /* nothing to do because globals are already set to zero */ } else { vpush_global_sym(&func_old_type, TOK_memset); vseti(VT_LOCAL, c); #ifdef TCC_TARGET_ARM vpushs(size); vpushi(0); #else vpushi(0); vpushs(size); #endif gfunc_call(3); } } /* t is the array or struct type. c is the array or struct address. cur_field is the pointer to the current field, for arrays the 'c' member contains the current start index. 'size_only' is true if only size info is needed (only used in arrays). al contains the already initialized length of the current container (starting at c). This returns the new length of that. */ static int decl_designator(CType *type, Section *sec, unsigned long c, Sym **cur_field, int size_only, int al) { Sym *s, *f; int index, index_last, align, l, nb_elems, elem_size; unsigned long corig = c; elem_size = 0; nb_elems = 1; if (gnu_ext && (l = is_label()) != 0) goto struct_field; /* NOTE: we only support ranges for last designator */ while (nb_elems == 1 && (tok == '[' || tok == '.')) { if (tok == '[') { if (!(type->t & VT_ARRAY)) expect("array type"); next(); index = index_last = expr_const(); if (tok == TOK_DOTS && gnu_ext) { next(); index_last = expr_const(); } skip(']'); s = type->ref; if (index < 0 || (s->c >= 0 && index_last >= s->c) || index_last < index) tcc_error("invalid index"); if (cur_field) (*cur_field)->c = index_last; type = pointed_type(type); elem_size = type_size(type, &align); c += index * elem_size; nb_elems = index_last - index + 1; } else { next(); l = tok; struct_field: next(); if ((type->t & VT_BTYPE) != VT_STRUCT) expect("struct/union type"); f = find_field(type, l); if (!f) expect("field"); if (cur_field) *cur_field = f; type = &f->type; c += f->c; } cur_field = NULL; } if (!cur_field) { if (tok == '=') { next(); } else if (!gnu_ext) { expect("="); } } else { if (type->t & VT_ARRAY) { index = (*cur_field)->c; if (type->ref->c >= 0 && index >= type->ref->c) tcc_error("index too large"); type = pointed_type(type); c += index * type_size(type, &align); } else { f = *cur_field; while (f && (f->v & SYM_FIRST_ANOM) && (f->type.t & VT_BITFIELD)) *cur_field = f = f->next; if (!f) tcc_error("too many field init"); type = &f->type; c += f->c; } } /* must put zero in holes (note that doing it that way ensures that it even works with designators) */ if (!size_only && c - corig > al) init_putz(sec, corig + al, c - corig - al); decl_initializer(type, sec, c, 0, size_only); /* XXX: make it more general */ if (!size_only && nb_elems > 1) { unsigned long c_end; uint8_t *src, *dst; int i; if (!sec) { vset(type, VT_LOCAL|VT_LVAL, c); for (i = 1; i < nb_elems; i++) { vset(type, VT_LOCAL|VT_LVAL, c + elem_size * i); vswap(); vstore(); } vpop(); } else if (!NODATA_WANTED) { c_end = c + nb_elems * elem_size; if (c_end > sec->data_allocated) section_realloc(sec, c_end); src = sec->data + c; dst = src; for(i = 1; i < nb_elems; i++) { dst += elem_size; memcpy(dst, src, elem_size); } } } c += nb_elems * type_size(type, &align); if (c - corig > al) al = c - corig; return al; } /* store a value or an expression directly in global data or in local array */ static void init_putv(CType *type, Section *sec, unsigned long c) { int bt; void *ptr; CType dtype; dtype = *type; dtype.t &= ~VT_CONSTANT; /* need to do that to avoid false warning */ if (sec) { int size, align; /* XXX: not portable */ /* XXX: generate error if incorrect relocation */ gen_assign_cast(&dtype); bt = type->t & VT_BTYPE; if ((vtop->r & VT_SYM) && bt != VT_PTR && bt != VT_FUNC && (bt != (PTR_SIZE == 8 ? VT_LLONG : VT_INT) || (type->t & VT_BITFIELD)) && !((vtop->r & VT_CONST) && vtop->sym->v >= SYM_FIRST_ANOM) ) tcc_error("initializer element is not computable at load time"); if (NODATA_WANTED) { vtop--; return; } size = type_size(type, &align); section_reserve(sec, c + size); ptr = sec->data + c; /* XXX: make code faster ? */ if ((vtop->r & (VT_SYM|VT_CONST)) == (VT_SYM|VT_CONST) && vtop->sym->v >= SYM_FIRST_ANOM && /* XXX This rejects compound literals like '(void *){ptr}'. The problem is that '&sym' is represented the same way, which would be ruled out by the SYM_FIRST_ANOM check above, but also '"string"' in 'char *p = "string"' is represented the same with the type being VT_PTR and the symbol being an anonymous one. That is, there's no difference in vtop between '(void *){x}' and '&(void *){x}'. Ignore pointer typed entities here. Hopefully no real code will every use compound literals with scalar type. */ (vtop->type.t & VT_BTYPE) != VT_PTR) { /* These come from compound literals, memcpy stuff over. */ Section *ssec; ElfSym *esym; ElfW_Rel *rel; esym = elfsym(vtop->sym); ssec = tcc_state->sections[esym->st_shndx]; memmove (ptr, ssec->data + esym->st_value, size); if (ssec->reloc) { /* We need to copy over all memory contents, and that includes relocations. Use the fact that relocs are created it order, so look from the end of relocs until we hit one before the copied region. */ int num_relocs = ssec->reloc->data_offset / sizeof(*rel); rel = (ElfW_Rel*)(ssec->reloc->data + ssec->reloc->data_offset); while (num_relocs--) { rel--; if (rel->r_offset >= esym->st_value + size) continue; if (rel->r_offset < esym->st_value) break; /* Note: if the same fields are initialized multiple times (possible with designators) then we possibly add multiple relocations for the same offset here. That would lead to wrong code, the last reloc needs to win. We clean this up later after the whole initializer is parsed. */ put_elf_reloca(symtab_section, sec, c + rel->r_offset - esym->st_value, ELFW(R_TYPE)(rel->r_info), ELFW(R_SYM)(rel->r_info), #if PTR_SIZE == 8 rel->r_addend #else 0 #endif ); } } } else { if (type->t & VT_BITFIELD) { int bit_pos, bit_size, bits, n; unsigned char *p, v, m; bit_pos = BIT_POS(vtop->type.t); bit_size = BIT_SIZE(vtop->type.t); p = (unsigned char*)ptr + (bit_pos >> 3); bit_pos &= 7, bits = 0; while (bit_size) { n = 8 - bit_pos; if (n > bit_size) n = bit_size; v = vtop->c.i >> bits << bit_pos; m = ((1 << n) - 1) << bit_pos; *p = (*p & ~m) | (v & m); bits += n, bit_size -= n, bit_pos = 0, ++p; } } else switch(bt) { /* XXX: when cross-compiling we assume that each type has the same representation on host and target, which is likely to be wrong in the case of long double */ case VT_BOOL: vtop->c.i = vtop->c.i != 0; case VT_BYTE: *(char *)ptr |= vtop->c.i; break; case VT_SHORT: *(short *)ptr |= vtop->c.i; break; case VT_FLOAT: *(float*)ptr = vtop->c.f; break; case VT_DOUBLE: *(double *)ptr = vtop->c.d; break; case VT_LDOUBLE: #if defined TCC_IS_NATIVE_387 if (sizeof (long double) >= 10) /* zero pad ten-byte LD */ memcpy(ptr, &vtop->c.ld, 10); #ifdef __TINYC__ else if (sizeof (long double) == sizeof (double)) __asm__("fldl %1\nfstpt %0\n" : "=m" (*ptr) : "m" (vtop->c.ld)); #endif else if (vtop->c.ld == 0.0) ; else #endif if (sizeof(long double) == LDOUBLE_SIZE) *(long double*)ptr = vtop->c.ld; else if (sizeof(double) == LDOUBLE_SIZE) *(double *)ptr = (double)vtop->c.ld; else tcc_error("can't cross compile long double constants"); break; #if PTR_SIZE != 8 case VT_LLONG: *(long long *)ptr |= vtop->c.i; break; #else case VT_LLONG: #endif case VT_PTR: { addr_t val = vtop->c.i; #if PTR_SIZE == 8 if (vtop->r & VT_SYM) greloca(sec, vtop->sym, c, R_DATA_PTR, val); else *(addr_t *)ptr |= val; #else if (vtop->r & VT_SYM) greloc(sec, vtop->sym, c, R_DATA_PTR); *(addr_t *)ptr |= val; #endif break; } default: { int val = vtop->c.i; #if PTR_SIZE == 8 if (vtop->r & VT_SYM) greloca(sec, vtop->sym, c, R_DATA_PTR, val); else *(int *)ptr |= val; #else if (vtop->r & VT_SYM) greloc(sec, vtop->sym, c, R_DATA_PTR); *(int *)ptr |= val; #endif break; } } } vtop--; } else { vset(&dtype, VT_LOCAL|VT_LVAL, c); vswap(); vstore(); vpop(); } } /* 't' contains the type and storage info. 'c' is the offset of the object in section 'sec'. If 'sec' is NULL, it means stack based allocation. 'first' is true if array '{' must be read (multi dimension implicit array init handling). 'size_only' is true if size only evaluation is wanted (only for arrays). */ static void decl_initializer(CType *type, Section *sec, unsigned long c, int first, int size_only) { int len, n, no_oblock, nb, i; int size1, align1; int have_elem; Sym *s, *f; Sym indexsym; CType *t1; /* If we currently are at an '}' or ',' we have read an initializer element in one of our callers, and not yet consumed it. */ have_elem = tok == '}' || tok == ','; if (!have_elem && tok != '{' && /* In case of strings we have special handling for arrays, so don't consume them as initializer value (which would commit them to some anonymous symbol). */ tok != TOK_LSTR && tok != TOK_STR && !size_only) { parse_init_elem(!sec ? EXPR_ANY : EXPR_CONST); have_elem = 1; } if (have_elem && !(type->t & VT_ARRAY) && /* Use i_c_parameter_t, to strip toplevel qualifiers. The source type might have VT_CONSTANT set, which is of course assignable to non-const elements. */ is_compatible_unqualified_types(type, &vtop->type)) { init_putv(type, sec, c); } else if (type->t & VT_ARRAY) { s = type->ref; n = s->c; t1 = pointed_type(type); size1 = type_size(t1, &align1); no_oblock = 1; if ((first && tok != TOK_LSTR && tok != TOK_STR) || tok == '{') { if (tok != '{') tcc_error("character array initializer must be a literal," " optionally enclosed in braces"); skip('{'); no_oblock = 0; } /* only parse strings here if correct type (otherwise: handle them as ((w)char *) expressions */ if ((tok == TOK_LSTR && #ifdef TCC_TARGET_PE (t1->t & VT_BTYPE) == VT_SHORT && (t1->t & VT_UNSIGNED) #else (t1->t & VT_BTYPE) == VT_INT #endif ) || (tok == TOK_STR && (t1->t & VT_BTYPE) == VT_BYTE)) { len = 0; while (tok == TOK_STR || tok == TOK_LSTR) { int cstr_len, ch; /* compute maximum number of chars wanted */ if (tok == TOK_STR) cstr_len = tokc.str.size; else cstr_len = tokc.str.size / sizeof(nwchar_t); cstr_len--; nb = cstr_len; if (n >= 0 && nb > (n - len)) nb = n - len; if (!size_only) { if (cstr_len > nb) tcc_warning("initializer-string for array is too long"); /* in order to go faster for common case (char string in global variable, we handle it specifically */ if (sec && tok == TOK_STR && size1 == 1) { if (!NODATA_WANTED) memcpy(sec->data + c + len, tokc.str.data, nb); } else { for(i=0;i<nb;i++) { if (tok == TOK_STR) ch = ((unsigned char *)tokc.str.data)[i]; else ch = ((nwchar_t *)tokc.str.data)[i]; vpushi(ch); init_putv(t1, sec, c + (len + i) * size1); } } } len += nb; next(); } /* only add trailing zero if enough storage (no warning in this case since it is standard) */ if (n < 0 || len < n) { if (!size_only) { vpushi(0); init_putv(t1, sec, c + (len * size1)); } len++; } len *= size1; } else { indexsym.c = 0; f = &indexsym; do_init_list: len = 0; while (tok != '}' || have_elem) { len = decl_designator(type, sec, c, &f, size_only, len); have_elem = 0; if (type->t & VT_ARRAY) { ++indexsym.c; /* special test for multi dimensional arrays (may not be strictly correct if designators are used at the same time) */ if (no_oblock && len >= n*size1) break; } else { if (s->type.t == VT_UNION) f = NULL; else f = f->next; if (no_oblock && f == NULL) break; } if (tok == '}') break; skip(','); } } /* put zeros at the end */ if (!size_only && len < n*size1) init_putz(sec, c + len, n*size1 - len); if (!no_oblock) skip('}'); /* patch type size if needed, which happens only for array types */ if (n < 0) s->c = size1 == 1 ? len : ((len + size1 - 1)/size1); } else if ((type->t & VT_BTYPE) == VT_STRUCT) { size1 = 1; no_oblock = 1; if (first || tok == '{') { skip('{'); no_oblock = 0; } s = type->ref; f = s->next; n = s->c; goto do_init_list; } else if (tok == '{') { next(); decl_initializer(type, sec, c, first, size_only); skip('}'); } else if (size_only) { /* If we supported only ISO C we wouldn't have to accept calling this on anything than an array size_only==1 (and even then only on the outermost level, so no recursion would be needed), because initializing a flex array member isn't supported. But GNU C supports it, so we need to recurse even into subfields of structs and arrays when size_only is set. */ /* just skip expression */ skip_or_save_block(NULL); } else { if (!have_elem) { /* This should happen only when we haven't parsed the init element above for fear of committing a string constant to memory too early. */ if (tok != TOK_STR && tok != TOK_LSTR) expect("string constant"); parse_init_elem(!sec ? EXPR_ANY : EXPR_CONST); } init_putv(type, sec, c); } } /* parse an initializer for type 't' if 'has_init' is non zero, and allocate space in local or global data space ('r' is either VT_LOCAL or VT_CONST). If 'v' is non zero, then an associated variable 'v' of scope 'scope' is declared before initializers are parsed. If 'v' is zero, then a reference to the new object is put in the value stack. If 'has_init' is 2, a special parsing is done to handle string constants. */ static void decl_initializer_alloc(CType *type, AttributeDef *ad, int r, int has_init, int v, int scope) { int size, align, addr; TokenString *init_str = NULL; Section *sec; Sym *flexible_array; Sym *sym = NULL; int saved_nocode_wanted = nocode_wanted; #ifdef CONFIG_TCC_BCHECK int bcheck = tcc_state->do_bounds_check && !NODATA_WANTED; #endif if (type->t & VT_STATIC) nocode_wanted |= NODATA_WANTED ? 0x40000000 : 0x80000000; flexible_array = NULL; if ((type->t & VT_BTYPE) == VT_STRUCT) { Sym *field = type->ref->next; if (field) { while (field->next) field = field->next; if (field->type.t & VT_ARRAY && field->type.ref->c < 0) flexible_array = field; } } size = type_size(type, &align); /* If unknown size, we must evaluate it before evaluating initializers because initializers can generate global data too (e.g. string pointers or ISOC99 compound literals). It also simplifies local initializers handling */ if (size < 0 || (flexible_array && has_init)) { if (!has_init) tcc_error("unknown type size"); /* get all init string */ if (has_init == 2) { init_str = tok_str_alloc(); /* only get strings */ while (tok == TOK_STR || tok == TOK_LSTR) { tok_str_add_tok(init_str); next(); } tok_str_add(init_str, -1); tok_str_add(init_str, 0); } else { skip_or_save_block(&init_str); } unget_tok(0); /* compute size */ begin_macro(init_str, 1); next(); decl_initializer(type, NULL, 0, 1, 1); /* prepare second initializer parsing */ macro_ptr = init_str->str; next(); /* if still unknown size, error */ size = type_size(type, &align); if (size < 0) tcc_error("unknown type size"); } /* If there's a flex member and it was used in the initializer adjust size. */ if (flexible_array && flexible_array->type.ref->c > 0) size += flexible_array->type.ref->c * pointed_size(&flexible_array->type); /* take into account specified alignment if bigger */ if (ad->a.aligned) { int speca = 1 << (ad->a.aligned - 1); if (speca > align) align = speca; } else if (ad->a.packed) { align = 1; } if (NODATA_WANTED) size = 0, align = 1; if ((r & VT_VALMASK) == VT_LOCAL) { sec = NULL; #ifdef CONFIG_TCC_BCHECK if (bcheck && (type->t & VT_ARRAY)) { loc--; } #endif loc = (loc - size) & -align; addr = loc; #ifdef CONFIG_TCC_BCHECK /* handles bounds */ /* XXX: currently, since we do only one pass, we cannot track '&' operators, so we add only arrays */ if (bcheck && (type->t & VT_ARRAY)) { addr_t *bounds_ptr; /* add padding between regions */ loc--; /* then add local bound info */ bounds_ptr = section_ptr_add(lbounds_section, 2 * sizeof(addr_t)); bounds_ptr[0] = addr; bounds_ptr[1] = size; } #endif if (v) { /* local variable */ #ifdef CONFIG_TCC_ASM if (ad->asm_label) { int reg = asm_parse_regvar(ad->asm_label); if (reg >= 0) r = (r & ~VT_VALMASK) | reg; } #endif sym = sym_push(v, type, r, addr); sym->a = ad->a; } else { /* push local reference */ vset(type, r, addr); } } else { if (v && scope == VT_CONST) { /* see if the symbol was already defined */ sym = sym_find(v); if (sym) { patch_storage(sym, ad, type); /* we accept several definitions of the same global variable. */ if (!has_init && sym->c && elfsym(sym)->st_shndx != SHN_UNDEF) goto no_alloc; } } /* allocate symbol in corresponding section */ sec = ad->section; if (!sec) { if (has_init) sec = data_section; else if (tcc_state->nocommon) sec = bss_section; } if (sec) { addr = section_add(sec, size, align); #ifdef CONFIG_TCC_BCHECK /* add padding if bound check */ if (bcheck) section_add(sec, 1, 1); #endif } else { addr = align; /* SHN_COMMON is special, symbol value is align */ sec = common_section; } if (v) { if (!sym) { sym = sym_push(v, type, r | VT_SYM, 0); patch_storage(sym, ad, NULL); } /* Local statics have a scope until now (for warnings), remove it here. */ sym->sym_scope = 0; /* update symbol definition */ put_extern_sym(sym, sec, addr, size); } else { /* push global reference */ sym = get_sym_ref(type, sec, addr, size); vpushsym(type, sym); vtop->r |= r; } #ifdef CONFIG_TCC_BCHECK /* handles bounds now because the symbol must be defined before for the relocation */ if (bcheck) { addr_t *bounds_ptr; greloca(bounds_section, sym, bounds_section->data_offset, R_DATA_PTR, 0); /* then add global bound info */ bounds_ptr = section_ptr_add(bounds_section, 2 * sizeof(addr_t)); bounds_ptr[0] = 0; /* relocated */ bounds_ptr[1] = size; } #endif } if (type->t & VT_VLA) { int a; if (NODATA_WANTED) goto no_alloc; /* save current stack pointer */ if (vlas_in_scope == 0) { if (vla_sp_root_loc == -1) vla_sp_root_loc = (loc -= PTR_SIZE); gen_vla_sp_save(vla_sp_root_loc); } vla_runtime_type_size(type, &a); gen_vla_alloc(type, a); #if defined TCC_TARGET_PE && defined TCC_TARGET_X86_64 /* on _WIN64, because of the function args scratch area, the result of alloca differs from RSP and is returned in RAX. */ gen_vla_result(addr), addr = (loc -= PTR_SIZE); #endif gen_vla_sp_save(addr); vla_sp_loc = addr; vlas_in_scope++; } else if (has_init) { size_t oldreloc_offset = 0; if (sec && sec->reloc) oldreloc_offset = sec->reloc->data_offset; decl_initializer(type, sec, addr, 1, 0); if (sec && sec->reloc) squeeze_multi_relocs(sec, oldreloc_offset); /* patch flexible array member size back to -1, */ /* for possible subsequent similar declarations */ if (flexible_array) flexible_array->type.ref->c = -1; } no_alloc: /* restore parse state if needed */ if (init_str) { end_macro(); next(); } nocode_wanted = saved_nocode_wanted; } /* parse a function defined by symbol 'sym' and generate its code in 'cur_text_section' */ static void gen_function(Sym *sym) { nocode_wanted = 0; ind = cur_text_section->data_offset; /* NOTE: we patch the symbol size later */ put_extern_sym(sym, cur_text_section, ind, 0); funcname = get_tok_str(sym->v, NULL); func_ind = ind; /* Initialize VLA state */ vla_sp_loc = -1; vla_sp_root_loc = -1; /* put debug symbol */ tcc_debug_funcstart(tcc_state, sym); /* push a dummy symbol to enable local sym storage */ sym_push2(&local_stack, SYM_FIELD, 0, 0); local_scope = 1; /* for function parameters */ gfunc_prolog(&sym->type); local_scope = 0; rsym = 0; block(NULL, NULL, 0); nocode_wanted = 0; gsym(rsym); gfunc_epilog(); cur_text_section->data_offset = ind; label_pop(&global_label_stack, NULL, 0); /* reset local stack */ local_scope = 0; sym_pop(&local_stack, NULL, 0); /* end of function */ /* patch symbol size */ elfsym(sym)->st_size = ind - func_ind; tcc_debug_funcend(tcc_state, ind - func_ind); /* It's better to crash than to generate wrong code */ cur_text_section = NULL; funcname = ""; /* for safety */ func_vt.t = VT_VOID; /* for safety */ func_var = 0; /* for safety */ ind = 0; /* for safety */ nocode_wanted = 0x80000000; check_vstack(); } static void gen_inline_functions(TCCState *s) { Sym *sym; int inline_generated, i, ln; struct InlineFunc *fn; ln = file->line_num; /* iterate while inline function are referenced */ do { inline_generated = 0; for (i = 0; i < s->nb_inline_fns; ++i) { fn = s->inline_fns[i]; sym = fn->sym; if (sym && sym->c) { /* the function was used: generate its code and convert it to a normal function */ fn->sym = NULL; if (file) pstrcpy(file->filename, sizeof file->filename, fn->filename); sym->type.t &= ~VT_INLINE; begin_macro(fn->func_str, 1); next(); cur_text_section = text_section; gen_function(sym); end_macro(); inline_generated = 1; } } } while (inline_generated); file->line_num = ln; } ST_FUNC void free_inline_functions(TCCState *s) { int i; /* free tokens of unused inline functions */ for (i = 0; i < s->nb_inline_fns; ++i) { struct InlineFunc *fn = s->inline_fns[i]; if (fn->sym) tok_str_free(fn->func_str); } dynarray_reset(&s->inline_fns, &s->nb_inline_fns); } /* 'l' is VT_LOCAL or VT_CONST to define default storage type, or VT_CMP if parsing old style parameter decl list (and FUNC_SYM is set then) */ static int decl0(int l, int is_for_loop_init, Sym *func_sym) { int v, has_init, r; CType type, btype; Sym *sym; AttributeDef ad; while (1) { if (!parse_btype(&btype, &ad)) { if (is_for_loop_init) return 0; /* skip redundant ';' if not in old parameter decl scope */ if (tok == ';' && l != VT_CMP) { next(); continue; } if (l != VT_CONST) break; if (tok == TOK_ASM1 || tok == TOK_ASM2 || tok == TOK_ASM3) { /* global asm block */ asm_global_instr(); continue; } if (tok >= TOK_UIDENT) { /* special test for old K&R protos without explicit int type. Only accepted when defining global data */ btype.t = VT_INT; } else { if (tok != TOK_EOF) expect("declaration"); break; } } if (tok == ';') { if ((btype.t & VT_BTYPE) == VT_STRUCT) { int v = btype.ref->v; if (!(v & SYM_FIELD) && (v & ~SYM_STRUCT) >= SYM_FIRST_ANOM) tcc_warning("unnamed struct/union that defines no instances"); next(); continue; } if (IS_ENUM(btype.t)) { next(); continue; } } while (1) { /* iterate thru each declaration */ type = btype; /* If the base type itself was an array type of unspecified size (like in 'typedef int arr[]; arr x = {1};') then we will overwrite the unknown size by the real one for this decl. We need to unshare the ref symbol holding that size. */ if ((type.t & VT_ARRAY) && type.ref->c < 0) { type.ref = sym_push(SYM_FIELD, &type.ref->type, 0, type.ref->c); } type_decl(&type, &ad, &v, TYPE_DIRECT); #if 0 { char buf[500]; type_to_str(buf, sizeof(buf), &type, get_tok_str(v, NULL)); printf("type = '%s'\n", buf); } #endif if ((type.t & VT_BTYPE) == VT_FUNC) { if ((type.t & VT_STATIC) && (l == VT_LOCAL)) { tcc_error("function without file scope cannot be static"); } /* if old style function prototype, we accept a declaration list */ sym = type.ref; if (sym->f.func_type == FUNC_OLD && l == VT_CONST) decl0(VT_CMP, 0, sym); } if (gnu_ext && (tok == TOK_ASM1 || tok == TOK_ASM2 || tok == TOK_ASM3)) { ad.asm_label = asm_label_instr(); /* parse one last attribute list, after asm label */ parse_attribute(&ad); if (tok == '{') expect(";"); } #ifdef TCC_TARGET_PE if (ad.a.dllimport || ad.a.dllexport) { if (type.t & (VT_STATIC|VT_TYPEDEF)) tcc_error("cannot have dll linkage with static or typedef"); if (ad.a.dllimport) { if ((type.t & VT_BTYPE) == VT_FUNC) ad.a.dllimport = 0; else type.t |= VT_EXTERN; } } #endif if (tok == '{') { if (l != VT_CONST) tcc_error("cannot use local functions"); if ((type.t & VT_BTYPE) != VT_FUNC) expect("function definition"); /* reject abstract declarators in function definition make old style params without decl have int type */ sym = type.ref; while ((sym = sym->next) != NULL) { if (!(sym->v & ~SYM_FIELD)) expect("identifier"); if (sym->type.t == VT_VOID) sym->type = int_type; } /* XXX: cannot do better now: convert extern line to static inline */ if ((type.t & (VT_EXTERN | VT_INLINE)) == (VT_EXTERN | VT_INLINE)) type.t = (type.t & ~VT_EXTERN) | VT_STATIC; /* put function symbol */ sym = external_global_sym(v, &type, 0); type.t &= ~VT_EXTERN; patch_storage(sym, &ad, &type); /* static inline functions are just recorded as a kind of macro. Their code will be emitted at the end of the compilation unit only if they are used */ if ((type.t & (VT_INLINE | VT_STATIC)) == (VT_INLINE | VT_STATIC)) { struct InlineFunc *fn; const char *filename; filename = file ? file->filename : ""; fn = tcc_malloc(sizeof *fn + strlen(filename)); strcpy(fn->filename, filename); fn->sym = sym; skip_or_save_block(&fn->func_str); dynarray_add(&tcc_state->inline_fns, &tcc_state->nb_inline_fns, fn); } else { /* compute text section */ cur_text_section = ad.section; if (!cur_text_section) cur_text_section = text_section; gen_function(sym); } break; } else { if (l == VT_CMP) { /* find parameter in function parameter list */ for (sym = func_sym->next; sym; sym = sym->next) if ((sym->v & ~SYM_FIELD) == v) goto found; tcc_error("declaration for parameter '%s' but no such parameter", get_tok_str(v, NULL)); found: if (type.t & VT_STORAGE) /* 'register' is okay */ tcc_error("storage class specified for '%s'", get_tok_str(v, NULL)); if (sym->type.t != VT_VOID) tcc_error("redefinition of parameter '%s'", get_tok_str(v, NULL)); convert_parameter_type(&type); sym->type = type; } else if (type.t & VT_TYPEDEF) { /* save typedefed type */ /* XXX: test storage specifiers ? */ sym = sym_find(v); if (sym && sym->sym_scope == local_scope) { if (!is_compatible_types(&sym->type, &type) || !(sym->type.t & VT_TYPEDEF)) tcc_error("incompatible redefinition of '%s'", get_tok_str(v, NULL)); sym->type = type; } else { sym = sym_push(v, &type, 0, 0); } sym->a = ad.a; sym->f = ad.f; } else { r = 0; if ((type.t & VT_BTYPE) == VT_FUNC) { /* external function definition */ /* specific case for func_call attribute */ type.ref->f = ad.f; } else if (!(type.t & VT_ARRAY)) { /* not lvalue if array */ r |= lvalue_type(type.t); } has_init = (tok == '='); if (has_init && (type.t & VT_VLA)) tcc_error("variable length array cannot be initialized"); if (((type.t & VT_EXTERN) && (!has_init || l != VT_CONST)) || ((type.t & VT_BTYPE) == VT_FUNC) || ((type.t & VT_ARRAY) && (type.t & VT_STATIC) && !has_init && l == VT_CONST && type.ref->c < 0)) { /* external variable or function */ /* NOTE: as GCC, uninitialized global static arrays of null size are considered as extern */ type.t |= VT_EXTERN; sym = external_sym(v, &type, r, &ad); if (ad.alias_target) { ElfSym *esym; Sym *alias_target; alias_target = sym_find(ad.alias_target); esym = elfsym(alias_target); if (!esym) tcc_error("unsupported forward __alias__ attribute"); /* Local statics have a scope until now (for warnings), remove it here. */ sym->sym_scope = 0; put_extern_sym2(sym, esym->st_shndx, esym->st_value, esym->st_size, 0); } } else { if (type.t & VT_STATIC) r |= VT_CONST; else r |= l; if (has_init) next(); else if (l == VT_CONST) /* uninitialized global variables may be overridden */ type.t |= VT_EXTERN; decl_initializer_alloc(&type, &ad, r, has_init, v, l); } } if (tok != ',') { if (is_for_loop_init) return 1; skip(';'); break; } next(); } ad.a.aligned = 0; } } return 0; } static void decl(int l) { decl0(l, 0, NULL); } /* ------------------------------------------------------------------------- */
/* * TCC - Tiny C Compiler - Support for -run switch * * Copyright (c) 2001-2004 Fabrice Bellard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "tcc.h" /* only native compiler supports -run */ #ifdef TCC_IS_NATIVE #ifndef _WIN32 # include <sys/mman.h> #endif #ifdef CONFIG_TCC_BACKTRACE # ifndef _WIN32 # include <signal.h> # ifndef __OpenBSD__ # include <sys/ucontext.h> # endif # else # define ucontext_t CONTEXT # endif ST_DATA int rt_num_callers = 6; ST_DATA const char **rt_bound_error_msg; ST_DATA void *rt_prog_main; static int rt_get_caller_pc(addr_t *paddr, ucontext_t *uc, int level); static void rt_error(ucontext_t *uc, const char *fmt, ...); static void set_exception_handler(void); #endif static void set_pages_executable(void *ptr, unsigned long length); static int tcc_relocate_ex(TCCState *s1, void *ptr, addr_t ptr_diff); #ifdef _WIN64 static void *win64_add_function_table(TCCState *s1); static void win64_del_function_table(void *); #endif /* ------------------------------------------------------------- */ /* Do all relocations (needed before using tcc_get_symbol()) Returns -1 on error. */ LIBTCCAPI int tcc_relocate(TCCState *s1, void *ptr) { int size; addr_t ptr_diff = 0; if (TCC_RELOCATE_AUTO != ptr) return tcc_relocate_ex(s1, ptr, 0); size = tcc_relocate_ex(s1, NULL, 0); if (size < 0) return -1; #ifdef HAVE_SELINUX { /* Using mmap instead of malloc */ void *prx; char tmpfname[] = "/tmp/.tccrunXXXXXX"; int fd = mkstemp(tmpfname); unlink(tmpfname); ftruncate(fd, size); ptr = mmap (NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); prx = mmap (NULL, size, PROT_READ|PROT_EXEC, MAP_SHARED, fd, 0); if (ptr == MAP_FAILED || prx == MAP_FAILED) tcc_error("tccrun: could not map memory"); dynarray_add(&s1->runtime_mem, &s1->nb_runtime_mem, (void*)(addr_t)size); dynarray_add(&s1->runtime_mem, &s1->nb_runtime_mem, prx); ptr_diff = (char*)prx - (char*)ptr; } #else ptr = tcc_malloc(size); #endif tcc_relocate_ex(s1, ptr, ptr_diff); /* no more errors expected */ dynarray_add(&s1->runtime_mem, &s1->nb_runtime_mem, ptr); return 0; } ST_FUNC void tcc_run_free(TCCState *s1) { int i; for (i = 0; i < s1->nb_runtime_mem; ++i) { #ifdef HAVE_SELINUX unsigned size = (unsigned)(addr_t)s1->runtime_mem[i++]; munmap(s1->runtime_mem[i++], size); munmap(s1->runtime_mem[i], size); #else #ifdef _WIN64 win64_del_function_table(*(void**)s1->runtime_mem[i]); #endif tcc_free(s1->runtime_mem[i]); #endif } tcc_free(s1->runtime_mem); } /* launch the compiled program with the given arguments */ LIBTCCAPI int tcc_run(TCCState *s1, int argc, char **argv) { int (*prog_main)(int, char **); s1->runtime_main = "main"; if ((s1->dflag & 16) && !find_elf_sym(s1->symtab, s1->runtime_main)) return 0; if (tcc_relocate(s1, TCC_RELOCATE_AUTO) < 0) return -1; prog_main = tcc_get_symbol_err(s1, s1->runtime_main); #ifdef CONFIG_TCC_BACKTRACE if (s1->do_debug) { set_exception_handler(); rt_prog_main = prog_main; } #endif errno = 0; /* clean errno value */ #ifdef CONFIG_TCC_BCHECK if (s1->do_bounds_check) { void (*bound_init)(void); void (*bound_exit)(void); void (*bound_new_region)(void *p, addr_t size); int (*bound_delete_region)(void *p); int i, ret; /* set error function */ rt_bound_error_msg = tcc_get_symbol_err(s1, "__bound_error_msg"); /* XXX: use .init section so that it also work in binary ? */ bound_init = tcc_get_symbol_err(s1, "__bound_init"); bound_exit = tcc_get_symbol_err(s1, "__bound_exit"); bound_new_region = tcc_get_symbol_err(s1, "__bound_new_region"); bound_delete_region = tcc_get_symbol_err(s1, "__bound_delete_region"); bound_init(); /* mark argv area as valid */ bound_new_region(argv, argc*sizeof(argv[0])); for (i=0; i<argc; ++i) bound_new_region(argv[i], strlen(argv[i]) + 1); ret = (*prog_main)(argc, argv); /* unmark argv area */ for (i=0; i<argc; ++i) bound_delete_region(argv[i]); bound_delete_region(argv); bound_exit(); return ret; } #endif return (*prog_main)(argc, argv); } #if defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64 #define RUN_SECTION_ALIGNMENT 63 #else #define RUN_SECTION_ALIGNMENT 15 #endif /* relocate code. Return -1 on error, required size if ptr is NULL, otherwise copy code into buffer passed by the caller */ static int tcc_relocate_ex(TCCState *s1, void *ptr, addr_t ptr_diff) { Section *s; unsigned offset, length, fill, i, k; addr_t mem; if (NULL == ptr) { s1->nb_errors = 0; #ifdef TCC_TARGET_PE pe_output_file(s1, NULL); #else tcc_add_runtime(s1); resolve_common_syms(s1); build_got_entries(s1); #endif if (s1->nb_errors) return -1; } offset = 0, mem = (addr_t)ptr; fill = -mem & RUN_SECTION_ALIGNMENT; #ifdef _WIN64 offset += sizeof (void*); #endif for (k = 0; k < 2; ++k) { for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; if (0 == (s->sh_flags & SHF_ALLOC)) continue; if (k != !(s->sh_flags & SHF_EXECINSTR)) continue; offset += fill; if (!mem) s->sh_addr = 0; else if (s->sh_flags & SHF_EXECINSTR) s->sh_addr = mem + offset + ptr_diff; else s->sh_addr = mem + offset; #if 0 if (mem) printf("%-16s +%02lx %p %04x\n", s->name, fill, (void*)s->sh_addr, (unsigned)s->data_offset); #endif offset += s->data_offset; fill = -(mem + offset) & 15; } #if RUN_SECTION_ALIGNMENT > 15 /* To avoid that x86 processors would reload cached instructions each time when data is written in the near, we need to make sure that code and data do not share the same 64 byte unit */ fill = -(mem + offset) & RUN_SECTION_ALIGNMENT; #endif } /* relocate symbols */ relocate_syms(s1, s1->symtab, 1); if (s1->nb_errors) return -1; if (0 == mem) return offset + RUN_SECTION_ALIGNMENT; #ifdef TCC_TARGET_PE s1->pe_imagebase = mem; #endif /* relocate each section */ for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; if (s->reloc) relocate_section(s1, s); } relocate_plt(s1); for(i = 1; i < s1->nb_sections; i++) { s = s1->sections[i]; if (0 == (s->sh_flags & SHF_ALLOC)) continue; length = s->data_offset; ptr = (void*)s->sh_addr; if (s->sh_flags & SHF_EXECINSTR) ptr = (char*)ptr - ptr_diff; if (NULL == s->data || s->sh_type == SHT_NOBITS) memset(ptr, 0, length); else memcpy(ptr, s->data, length); /* mark executable sections as executable in memory */ if (s->sh_flags & SHF_EXECINSTR) set_pages_executable((char*)ptr + ptr_diff, length); } #ifdef _WIN64 *(void**)mem = win64_add_function_table(s1); #endif return 0; } /* ------------------------------------------------------------- */ /* allow to run code in memory */ static void set_pages_executable(void *ptr, unsigned long length) { #ifdef _WIN32 unsigned long old_protect; VirtualProtect(ptr, length, PAGE_EXECUTE_READWRITE, &old_protect); #else void __clear_cache(void *beginning, void *end); # ifndef HAVE_SELINUX addr_t start, end; # ifndef PAGESIZE # define PAGESIZE 4096 # endif start = (addr_t)ptr & ~(PAGESIZE - 1); end = (addr_t)ptr + length; end = (end + PAGESIZE - 1) & ~(PAGESIZE - 1); if (mprotect((void *)start, end - start, PROT_READ | PROT_WRITE | PROT_EXEC)) tcc_error("mprotect failed: did you mean to configure --with-selinux?"); # endif # if defined TCC_TARGET_ARM || defined TCC_TARGET_ARM64 __clear_cache(ptr, (char *)ptr + length); # endif #endif } #ifdef _WIN64 static void *win64_add_function_table(TCCState *s1) { void *p = NULL; if (s1->uw_pdata) { p = (void*)s1->uw_pdata->sh_addr; RtlAddFunctionTable( (RUNTIME_FUNCTION*)p, s1->uw_pdata->data_offset / sizeof (RUNTIME_FUNCTION), s1->pe_imagebase ); s1->uw_pdata = NULL; } return p; } static void win64_del_function_table(void *p) { if (p) { RtlDeleteFunctionTable((RUNTIME_FUNCTION*)p); } } #endif /* ------------------------------------------------------------- */ #ifdef CONFIG_TCC_BACKTRACE ST_FUNC void tcc_set_num_callers(int n) { rt_num_callers = n; } /* print the position in the source file of PC value 'pc' by reading the stabs debug information */ static addr_t rt_printline(addr_t wanted_pc, const char *msg) { char func_name[128], last_func_name[128]; addr_t func_addr, last_pc, pc; const char *incl_files[INCLUDE_STACK_SIZE]; int incl_index, len, last_line_num, i; const char *str, *p; Stab_Sym *stab_sym = NULL, *stab_sym_end, *sym; int stab_len = 0; char *stab_str = NULL; if (stab_section) { stab_len = stab_section->data_offset; stab_sym = (Stab_Sym *)stab_section->data; stab_str = (char *) stabstr_section->data; } func_name[0] = '\0'; func_addr = 0; incl_index = 0; last_func_name[0] = '\0'; last_pc = (addr_t)-1; last_line_num = 1; if (!stab_sym) goto no_stabs; stab_sym_end = (Stab_Sym*)((char*)stab_sym + stab_len); for (sym = stab_sym + 1; sym < stab_sym_end; ++sym) { switch(sym->n_type) { /* function start or end */ case N_FUN: if (sym->n_strx == 0) { /* we test if between last line and end of function */ pc = sym->n_value + func_addr; if (wanted_pc >= last_pc && wanted_pc < pc) goto found; func_name[0] = '\0'; func_addr = 0; } else { str = stab_str + sym->n_strx; p = strchr(str, ':'); if (!p) { pstrcpy(func_name, sizeof(func_name), str); } else { len = p - str; if (len > sizeof(func_name) - 1) len = sizeof(func_name) - 1; memcpy(func_name, str, len); func_name[len] = '\0'; } func_addr = sym->n_value; } break; /* line number info */ case N_SLINE: pc = sym->n_value + func_addr; if (wanted_pc >= last_pc && wanted_pc < pc) goto found; last_pc = pc; last_line_num = sym->n_desc; /* XXX: slow! */ strcpy(last_func_name, func_name); break; /* include files */ case N_BINCL: str = stab_str + sym->n_strx; add_incl: if (incl_index < INCLUDE_STACK_SIZE) { incl_files[incl_index++] = str; } break; case N_EINCL: if (incl_index > 1) incl_index--; break; case N_SO: if (sym->n_strx == 0) { incl_index = 0; /* end of translation unit */ } else { str = stab_str + sym->n_strx; /* do not add path */ len = strlen(str); if (len > 0 && str[len - 1] != '/') goto add_incl; } break; } } no_stabs: /* second pass: we try symtab symbols (no line number info) */ incl_index = 0; if (symtab_section) { ElfW(Sym) *sym, *sym_end; int type; sym_end = (ElfW(Sym) *)(symtab_section->data + symtab_section->data_offset); for(sym = (ElfW(Sym) *)symtab_section->data + 1; sym < sym_end; sym++) { type = ELFW(ST_TYPE)(sym->st_info); if (type == STT_FUNC || type == STT_GNU_IFUNC) { if (wanted_pc >= sym->st_value && wanted_pc < sym->st_value + sym->st_size) { pstrcpy(last_func_name, sizeof(last_func_name), (char *) symtab_section->link->data + sym->st_name); func_addr = sym->st_value; goto found; } } } } /* did not find any info: */ fprintf(stderr, "%s %p ???\n", msg, (void*)wanted_pc); fflush(stderr); return 0; found: i = incl_index; if (i > 0) fprintf(stderr, "%s:%d: ", incl_files[--i], last_line_num); fprintf(stderr, "%s %p", msg, (void*)wanted_pc); if (last_func_name[0] != '\0') fprintf(stderr, " %s()", last_func_name); if (--i >= 0) { fprintf(stderr, " (included from "); for (;;) { fprintf(stderr, "%s", incl_files[i]); if (--i < 0) break; fprintf(stderr, ", "); } fprintf(stderr, ")"); } fprintf(stderr, "\n"); fflush(stderr); return func_addr; } /* emit a run time error at position 'pc' */ static void rt_error(ucontext_t *uc, const char *fmt, ...) { va_list ap; addr_t pc; int i; fprintf(stderr, "Runtime error: "); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "\n"); for(i=0;i<rt_num_callers;i++) { if (rt_get_caller_pc(&pc, uc, i) < 0) break; pc = rt_printline(pc, i ? "by" : "at"); if (pc == (addr_t)rt_prog_main && pc) break; } } /* ------------------------------------------------------------- */ #ifndef _WIN32 /* signal handler for fatal errors */ static void sig_error(int signum, siginfo_t *siginf, void *puc) { ucontext_t *uc = puc; switch(signum) { case SIGFPE: switch(siginf->si_code) { case FPE_INTDIV: case FPE_FLTDIV: rt_error(uc, "division by zero"); break; default: rt_error(uc, "floating point exception"); break; } break; case SIGBUS: case SIGSEGV: if (rt_bound_error_msg && *rt_bound_error_msg) rt_error(uc, *rt_bound_error_msg); else rt_error(uc, "dereferencing invalid pointer"); break; case SIGILL: rt_error(uc, "illegal instruction"); break; case SIGABRT: rt_error(uc, "abort() called"); break; default: rt_error(uc, "caught signal %d", signum); break; } exit(255); } #ifndef SA_SIGINFO # define SA_SIGINFO 0x00000004u #endif /* Generate a stack backtrace when a CPU exception occurs. */ static void set_exception_handler(void) { struct sigaction sigact; /* install TCC signal handlers to print debug info on fatal runtime errors */ sigact.sa_flags = SA_SIGINFO | SA_RESETHAND; sigact.sa_sigaction = sig_error; sigemptyset(&sigact.sa_mask); sigaction(SIGFPE, &sigact, NULL); sigaction(SIGILL, &sigact, NULL); sigaction(SIGSEGV, &sigact, NULL); sigaction(SIGBUS, &sigact, NULL); sigaction(SIGABRT, &sigact, NULL); } /* ------------------------------------------------------------- */ #ifdef __i386__ /* fix for glibc 2.1 */ #ifndef REG_EIP #define REG_EIP EIP #define REG_EBP EBP #endif /* return the PC at frame level 'level'. Return negative if not found */ static int rt_get_caller_pc(addr_t *paddr, ucontext_t *uc, int level) { addr_t fp; int i; if (level == 0) { #if defined(__APPLE__) *paddr = uc->uc_mcontext->__ss.__eip; #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) *paddr = uc->uc_mcontext.mc_eip; #elif defined(__dietlibc__) *paddr = uc->uc_mcontext.eip; #elif defined(__NetBSD__) *paddr = uc->uc_mcontext.__gregs[_REG_EIP]; #elif defined(__OpenBSD__) *paddr = uc->sc_eip; #else *paddr = uc->uc_mcontext.gregs[REG_EIP]; #endif return 0; } else { #if defined(__APPLE__) fp = uc->uc_mcontext->__ss.__ebp; #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) fp = uc->uc_mcontext.mc_ebp; #elif defined(__dietlibc__) fp = uc->uc_mcontext.ebp; #elif defined(__NetBSD__) fp = uc->uc_mcontext.__gregs[_REG_EBP]; #elif defined(__OpenBSD__) *paddr = uc->sc_ebp; #else fp = uc->uc_mcontext.gregs[REG_EBP]; #endif for(i=1;i<level;i++) { /* XXX: check address validity with program info */ if (fp <= 0x1000 || fp >= 0xc0000000) return -1; fp = ((addr_t *)fp)[0]; } *paddr = ((addr_t *)fp)[1]; return 0; } } /* ------------------------------------------------------------- */ #elif defined(__x86_64__) /* return the PC at frame level 'level'. Return negative if not found */ static int rt_get_caller_pc(addr_t *paddr, ucontext_t *uc, int level) { addr_t fp; int i; if (level == 0) { /* XXX: only support linux */ #if defined(__APPLE__) *paddr = uc->uc_mcontext->__ss.__rip; #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) *paddr = uc->uc_mcontext.mc_rip; #elif defined(__NetBSD__) *paddr = uc->uc_mcontext.__gregs[_REG_RIP]; #else *paddr = uc->uc_mcontext.gregs[REG_RIP]; #endif return 0; } else { #if defined(__APPLE__) fp = uc->uc_mcontext->__ss.__rbp; #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) fp = uc->uc_mcontext.mc_rbp; #elif defined(__NetBSD__) fp = uc->uc_mcontext.__gregs[_REG_RBP]; #else fp = uc->uc_mcontext.gregs[REG_RBP]; #endif for(i=1;i<level;i++) { /* XXX: check address validity with program info */ if (fp <= 0x1000) return -1; fp = ((addr_t *)fp)[0]; } *paddr = ((addr_t *)fp)[1]; return 0; } } /* ------------------------------------------------------------- */ #elif defined(__arm__) /* return the PC at frame level 'level'. Return negative if not found */ static int rt_get_caller_pc(addr_t *paddr, ucontext_t *uc, int level) { addr_t fp, sp; int i; if (level == 0) { /* XXX: only supports linux */ #if defined(__linux__) *paddr = uc->uc_mcontext.arm_pc; #else return -1; #endif return 0; } else { #if defined(__linux__) fp = uc->uc_mcontext.arm_fp; sp = uc->uc_mcontext.arm_sp; if (sp < 0x1000) sp = 0x1000; #else return -1; #endif /* XXX: specific to tinycc stack frames */ if (fp < sp + 12 || fp & 3) return -1; for(i = 1; i < level; i++) { sp = ((addr_t *)fp)[-2]; if (sp < fp || sp - fp > 16 || sp & 3) return -1; fp = ((addr_t *)fp)[-3]; if (fp <= sp || fp - sp < 12 || fp & 3) return -1; } /* XXX: check address validity with program info */ *paddr = ((addr_t *)fp)[-1]; return 0; } } /* ------------------------------------------------------------- */ #elif defined(__aarch64__) static int rt_get_caller_pc(addr_t *paddr, ucontext_t *uc, int level) { if (level < 0) return -1; else if (level == 0) { *paddr = uc->uc_mcontext.pc; return 0; } else { addr_t *fp = (addr_t *)uc->uc_mcontext.regs[29]; int i; for (i = 1; i < level; i++) fp = (addr_t *)fp[0]; *paddr = fp[1]; return 0; } } /* ------------------------------------------------------------- */ #else #warning add arch specific rt_get_caller_pc() static int rt_get_caller_pc(addr_t *paddr, ucontext_t *uc, int level) { return -1; } #endif /* !__i386__ */ /* ------------------------------------------------------------- */ #else /* WIN32 */ static long __stdcall cpu_exception_handler(EXCEPTION_POINTERS *ex_info) { EXCEPTION_RECORD *er = ex_info->ExceptionRecord; CONTEXT *uc = ex_info->ContextRecord; switch (er->ExceptionCode) { case EXCEPTION_ACCESS_VIOLATION: if (rt_bound_error_msg && *rt_bound_error_msg) rt_error(uc, *rt_bound_error_msg); else rt_error(uc, "access violation"); break; case EXCEPTION_STACK_OVERFLOW: rt_error(uc, "stack overflow"); break; case EXCEPTION_INT_DIVIDE_BY_ZERO: rt_error(uc, "division by zero"); break; default: rt_error(uc, "exception caught"); break; } return EXCEPTION_EXECUTE_HANDLER; } /* Generate a stack backtrace when a CPU exception occurs. */ static void set_exception_handler(void) { SetUnhandledExceptionFilter(cpu_exception_handler); } /* return the PC at frame level 'level'. Return non zero if not found */ static int rt_get_caller_pc(addr_t *paddr, CONTEXT *uc, int level) { addr_t fp, pc; int i; #ifdef _WIN64 pc = uc->Rip; fp = uc->Rbp; #else pc = uc->Eip; fp = uc->Ebp; #endif if (level > 0) { for(i=1;i<level;i++) { /* XXX: check address validity with program info */ if (fp <= 0x1000 || fp >= 0xc0000000) return -1; fp = ((addr_t*)fp)[0]; } pc = ((addr_t*)fp)[1]; } *paddr = pc; return 0; } #endif /* _WIN32 */ #endif /* CONFIG_TCC_BACKTRACE */ /* ------------------------------------------------------------- */ #ifdef CONFIG_TCC_STATIC /* dummy function for profiling */ ST_FUNC void *dlopen(const char *filename, int flag) { return NULL; } ST_FUNC void dlclose(void *p) { } ST_FUNC const char *dlerror(void) { return "error"; } typedef struct TCCSyms { char *str; void *ptr; } TCCSyms; /* add the symbol you want here if no dynamic linking is done */ static TCCSyms tcc_syms[] = { #if !defined(CONFIG_TCCBOOT) #define TCCSYM(a) { #a, &a, }, TCCSYM(printf) TCCSYM(fprintf) TCCSYM(fopen) TCCSYM(fclose) #undef TCCSYM #endif { NULL, NULL }, }; ST_FUNC void *dlsym(void *handle, const char *symbol) { TCCSyms *p; p = tcc_syms; while (p->str != NULL) { if (!strcmp(p->str, symbol)) return p->ptr; p++; } return NULL; } #endif /* CONFIG_TCC_STATIC */ #endif /* TCC_IS_NATIVE */ /* ------------------------------------------------------------- */
/* * X86 code generator for TCC * * Copyright (c) 2001-2004 Fabrice Bellard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef TARGET_DEFS_ONLY /* number of available registers */ #define NB_REGS 5 #define NB_ASM_REGS 8 #define CONFIG_TCC_ASM /* a register can belong to several classes. The classes must be sorted from more general to more precise (see gv2() code which does assumptions on it). */ #define RC_INT 0x0001 /* generic integer register */ #define RC_FLOAT 0x0002 /* generic float register */ #define RC_EAX 0x0004 #define RC_ST0 0x0008 #define RC_ECX 0x0010 #define RC_EDX 0x0020 #define RC_EBX 0x0040 #define RC_IRET RC_EAX /* function return: integer register */ #define RC_LRET RC_EDX /* function return: second integer register */ #define RC_FRET RC_ST0 /* function return: float register */ /* pretty names for the registers */ enum { TREG_EAX = 0, TREG_ECX, TREG_EDX, TREG_EBX, TREG_ST0, TREG_ESP = 4 }; /* return registers for function */ #define REG_IRET TREG_EAX /* single word int return register */ #define REG_LRET TREG_EDX /* second word return register (for long long) */ #define REG_FRET TREG_ST0 /* float return register */ /* defined if function parameters must be evaluated in reverse order */ #define INVERT_FUNC_PARAMS /* defined if structures are passed as pointers. Otherwise structures are directly pushed on stack. */ /* #define FUNC_STRUCT_PARAM_AS_PTR */ /* pointer size, in bytes */ #define PTR_SIZE 4 /* long double size and alignment, in bytes */ #define LDOUBLE_SIZE 12 #define LDOUBLE_ALIGN 4 /* maximum alignment (for aligned attribute support) */ #define MAX_ALIGN 8 /******************************************************/ #else /* ! TARGET_DEFS_ONLY */ /******************************************************/ #include "tcc.h" /* define to 1/0 to [not] have EBX as 4th register */ #define USE_EBX 0 ST_DATA const int reg_classes[NB_REGS] = { /* eax */ RC_INT | RC_EAX, /* ecx */ RC_INT | RC_ECX, /* edx */ RC_INT | RC_EDX, /* ebx */ (RC_INT | RC_EBX) * USE_EBX, /* st0 */ RC_FLOAT | RC_ST0, }; static unsigned long func_sub_sp_offset; static int func_ret_sub; #ifdef CONFIG_TCC_BCHECK static addr_t func_bound_offset; static unsigned long func_bound_ind; #endif /* XXX: make it faster ? */ ST_FUNC void g(int c) { int ind1; if (nocode_wanted) return; ind1 = ind + 1; if (ind1 > cur_text_section->data_allocated) section_realloc(cur_text_section, ind1); cur_text_section->data[ind] = c; ind = ind1; } ST_FUNC void o(unsigned int c) { while (c) { g(c); c = c >> 8; } } ST_FUNC void gen_le16(int v) { g(v); g(v >> 8); } ST_FUNC void gen_le32(int c) { g(c); g(c >> 8); g(c >> 16); g(c >> 24); } /* output a symbol and patch all calls to it */ ST_FUNC void gsym_addr(int t, int a) { while (t) { unsigned char *ptr = cur_text_section->data + t; uint32_t n = read32le(ptr); /* next value */ write32le(ptr, a - t - 4); t = n; } } ST_FUNC void gsym(int t) { gsym_addr(t, ind); } /* instruction + 4 bytes data. Return the address of the data */ static int oad(int c, int s) { int t; if (nocode_wanted) return s; o(c); t = ind; gen_le32(s); return t; } /* generate jmp to a label */ #define gjmp2(instr,lbl) oad(instr,lbl) /* output constant with relocation if 'r & VT_SYM' is true */ ST_FUNC void gen_addr32(int r, Sym *sym, int c) { if (r & VT_SYM) greloc(cur_text_section, sym, ind, R_386_32); gen_le32(c); } ST_FUNC void gen_addrpc32(int r, Sym *sym, int c) { if (r & VT_SYM) greloc(cur_text_section, sym, ind, R_386_PC32); gen_le32(c - 4); } /* generate a modrm reference. 'op_reg' contains the additional 3 opcode bits */ static void gen_modrm(int op_reg, int r, Sym *sym, int c) { op_reg = op_reg << 3; if ((r & VT_VALMASK) == VT_CONST) { /* constant memory reference */ o(0x05 | op_reg); gen_addr32(r, sym, c); } else if ((r & VT_VALMASK) == VT_LOCAL) { /* currently, we use only ebp as base */ if (c == (char)c) { /* short reference */ o(0x45 | op_reg); g(c); } else { oad(0x85 | op_reg, c); } } else { g(0x00 | op_reg | (r & VT_VALMASK)); } } /* load 'r' from value 'sv' */ ST_FUNC void load(int r, SValue *sv) { int v, t, ft, fc, fr; SValue v1; #ifdef TCC_TARGET_PE SValue v2; sv = pe_getimport(sv, &v2); #endif fr = sv->r; ft = sv->type.t & ~VT_DEFSIGN; fc = sv->c.i; ft &= ~(VT_VOLATILE | VT_CONSTANT); v = fr & VT_VALMASK; if (fr & VT_LVAL) { if (v == VT_LLOCAL) { v1.type.t = VT_INT; v1.r = VT_LOCAL | VT_LVAL; v1.c.i = fc; fr = r; if (!(reg_classes[fr] & RC_INT)) fr = get_reg(RC_INT); load(fr, &v1); } if ((ft & VT_BTYPE) == VT_FLOAT) { o(0xd9); /* flds */ r = 0; } else if ((ft & VT_BTYPE) == VT_DOUBLE) { o(0xdd); /* fldl */ r = 0; } else if ((ft & VT_BTYPE) == VT_LDOUBLE) { o(0xdb); /* fldt */ r = 5; } else if ((ft & VT_TYPE) == VT_BYTE || (ft & VT_TYPE) == VT_BOOL) { o(0xbe0f); /* movsbl */ } else if ((ft & VT_TYPE) == (VT_BYTE | VT_UNSIGNED)) { o(0xb60f); /* movzbl */ } else if ((ft & VT_TYPE) == VT_SHORT) { o(0xbf0f); /* movswl */ } else if ((ft & VT_TYPE) == (VT_SHORT | VT_UNSIGNED)) { o(0xb70f); /* movzwl */ } else { o(0x8b); /* movl */ } gen_modrm(r, fr, sv->sym, fc); } else { if (v == VT_CONST) { o(0xb8 + r); /* mov $xx, r */ gen_addr32(fr, sv->sym, fc); } else if (v == VT_LOCAL) { if (fc) { o(0x8d); /* lea xxx(%ebp), r */ gen_modrm(r, VT_LOCAL, sv->sym, fc); } else { o(0x89); o(0xe8 + r); /* mov %ebp, r */ } } else if (v == VT_CMP) { oad(0xb8 + r, 0); /* mov $0, r */ o(0x0f); /* setxx %br */ o(fc); o(0xc0 + r); } else if (v == VT_JMP || v == VT_JMPI) { t = v & 1; oad(0xb8 + r, t); /* mov $1, r */ o(0x05eb); /* jmp after */ gsym(fc); oad(0xb8 + r, t ^ 1); /* mov $0, r */ } else if (v != r) { o(0x89); o(0xc0 + r + v * 8); /* mov v, r */ } } } /* store register 'r' in lvalue 'v' */ ST_FUNC void store(int r, SValue *v) { int fr, bt, ft, fc; #ifdef TCC_TARGET_PE SValue v2; v = pe_getimport(v, &v2); #endif ft = v->type.t; fc = v->c.i; fr = v->r & VT_VALMASK; ft &= ~(VT_VOLATILE | VT_CONSTANT); bt = ft & VT_BTYPE; /* XXX: incorrect if float reg to reg */ if (bt == VT_FLOAT) { o(0xd9); /* fsts */ r = 2; } else if (bt == VT_DOUBLE) { o(0xdd); /* fstpl */ r = 2; } else if (bt == VT_LDOUBLE) { o(0xc0d9); /* fld %st(0) */ o(0xdb); /* fstpt */ r = 7; } else { if (bt == VT_SHORT) o(0x66); if (bt == VT_BYTE || bt == VT_BOOL) o(0x88); else o(0x89); } if (fr == VT_CONST || fr == VT_LOCAL || (v->r & VT_LVAL)) { gen_modrm(r, v->r, v->sym, fc); } else if (fr != r) { o(0xc0 + fr + r * 8); /* mov r, fr */ } } static void gadd_sp(int val) { if (val == (char)val) { o(0xc483); g(val); } else { oad(0xc481, val); /* add $xxx, %esp */ } } #if defined CONFIG_TCC_BCHECK || defined TCC_TARGET_PE static void gen_static_call(int v) { Sym *sym; sym = external_global_sym(v, &func_old_type, 0); oad(0xe8, -4); greloc(cur_text_section, sym, ind-4, R_386_PC32); } #endif /* 'is_jmp' is '1' if it is a jump */ static void gcall_or_jmp(int is_jmp) { int r; if ((vtop->r & (VT_VALMASK | VT_LVAL)) == VT_CONST && (vtop->r & VT_SYM)) { /* constant and relocation case */ greloc(cur_text_section, vtop->sym, ind + 1, R_386_PC32); oad(0xe8 + is_jmp, vtop->c.i - 4); /* call/jmp im */ } else { /* otherwise, indirect call */ r = gv(RC_INT); o(0xff); /* call/jmp *r */ o(0xd0 + r + (is_jmp << 4)); } if (!is_jmp) { int rt; /* extend the return value to the whole register if necessary visual studio and gcc do not always set the whole eax register when assigning the return value of a function */ rt = vtop->type.ref->type.t; switch (rt & VT_BTYPE) { case VT_BYTE: if (rt & VT_UNSIGNED) { o(0xc0b60f); /* movzx %al, %eax */ } else { o(0xc0be0f); /* movsx %al, %eax */ } break; case VT_SHORT: if (rt & VT_UNSIGNED) { o(0xc0b70f); /* movzx %ax, %eax */ } else { o(0xc0bf0f); /* movsx %ax, %eax */ } break; default: break; } } } static uint8_t fastcall_regs[3] = { TREG_EAX, TREG_EDX, TREG_ECX }; static uint8_t fastcallw_regs[2] = { TREG_ECX, TREG_EDX }; /* Return the number of registers needed to return the struct, or 0 if returning via struct pointer. */ ST_FUNC int gfunc_sret(CType *vt, int variadic, CType *ret, int *ret_align, int *regsize) { #ifdef TCC_TARGET_PE int size, align; *ret_align = 1; // Never have to re-align return values for x86 *regsize = 4; size = type_size(vt, &align); if (size > 8 || (size & (size - 1))) return 0; if (size == 8) ret->t = VT_LLONG; else if (size == 4) ret->t = VT_INT; else if (size == 2) ret->t = VT_SHORT; else ret->t = VT_BYTE; ret->ref = NULL; return 1; #else *ret_align = 1; // Never have to re-align return values for x86 return 0; #endif } /* Generate function call. The function address is pushed first, then all the parameters in call order. This functions pops all the parameters and the function address. */ ST_FUNC void gfunc_call(int nb_args) { int size, align, r, args_size, i, func_call; Sym *func_sym; args_size = 0; for(i = 0;i < nb_args; i++) { if ((vtop->type.t & VT_BTYPE) == VT_STRUCT) { size = type_size(&vtop->type, &align); /* align to stack align size */ size = (size + 3) & ~3; /* allocate the necessary size on stack */ oad(0xec81, size); /* sub $xxx, %esp */ /* generate structure store */ r = get_reg(RC_INT); o(0x89); /* mov %esp, r */ o(0xe0 + r); vset(&vtop->type, r | VT_LVAL, 0); vswap(); vstore(); args_size += size; } else if (is_float(vtop->type.t)) { gv(RC_FLOAT); /* only one float register */ if ((vtop->type.t & VT_BTYPE) == VT_FLOAT) size = 4; else if ((vtop->type.t & VT_BTYPE) == VT_DOUBLE) size = 8; else size = 12; oad(0xec81, size); /* sub $xxx, %esp */ if (size == 12) o(0x7cdb); else o(0x5cd9 + size - 4); /* fstp[s|l] 0(%esp) */ g(0x24); g(0x00); args_size += size; } else { /* simple type (currently always same size) */ /* XXX: implicit cast ? */ r = gv(RC_INT); if ((vtop->type.t & VT_BTYPE) == VT_LLONG) { size = 8; o(0x50 + vtop->r2); /* push r */ } else { size = 4; } o(0x50 + r); /* push r */ args_size += size; } vtop--; } save_regs(0); /* save used temporary registers */ func_sym = vtop->type.ref; func_call = func_sym->f.func_call; /* fast call case */ if ((func_call >= FUNC_FASTCALL1 && func_call <= FUNC_FASTCALL3) || func_call == FUNC_FASTCALLW) { int fastcall_nb_regs; uint8_t *fastcall_regs_ptr; if (func_call == FUNC_FASTCALLW) { fastcall_regs_ptr = fastcallw_regs; fastcall_nb_regs = 2; } else { fastcall_regs_ptr = fastcall_regs; fastcall_nb_regs = func_call - FUNC_FASTCALL1 + 1; } for(i = 0;i < fastcall_nb_regs; i++) { if (args_size <= 0) break; o(0x58 + fastcall_regs_ptr[i]); /* pop r */ /* XXX: incorrect for struct/floats */ args_size -= 4; } } #ifndef TCC_TARGET_PE else if ((vtop->type.ref->type.t & VT_BTYPE) == VT_STRUCT) args_size -= 4; #endif gcall_or_jmp(0); if (args_size && func_call != FUNC_STDCALL && func_call != FUNC_FASTCALLW) gadd_sp(args_size); vtop--; } #ifdef TCC_TARGET_PE #define FUNC_PROLOG_SIZE (10 + USE_EBX) #else #define FUNC_PROLOG_SIZE (9 + USE_EBX) #endif /* generate function prolog of type 't' */ ST_FUNC void gfunc_prolog(CType *func_type) { int addr, align, size, func_call, fastcall_nb_regs; int param_index, param_addr; uint8_t *fastcall_regs_ptr; Sym *sym; CType *type; sym = func_type->ref; func_call = sym->f.func_call; addr = 8; loc = 0; func_vc = 0; if (func_call >= FUNC_FASTCALL1 && func_call <= FUNC_FASTCALL3) { fastcall_nb_regs = func_call - FUNC_FASTCALL1 + 1; fastcall_regs_ptr = fastcall_regs; } else if (func_call == FUNC_FASTCALLW) { fastcall_nb_regs = 2; fastcall_regs_ptr = fastcallw_regs; } else { fastcall_nb_regs = 0; fastcall_regs_ptr = NULL; } param_index = 0; ind += FUNC_PROLOG_SIZE; func_sub_sp_offset = ind; /* if the function returns a structure, then add an implicit pointer parameter */ func_vt = sym->type; func_var = (sym->f.func_type == FUNC_ELLIPSIS); #ifdef TCC_TARGET_PE size = type_size(&func_vt,&align); if (((func_vt.t & VT_BTYPE) == VT_STRUCT) && (size > 8 || (size & (size - 1)))) { #else if ((func_vt.t & VT_BTYPE) == VT_STRUCT) { #endif /* XXX: fastcall case ? */ func_vc = addr; addr += 4; param_index++; } /* define parameters */ while ((sym = sym->next) != NULL) { type = &sym->type; size = type_size(type, &align); size = (size + 3) & ~3; #ifdef FUNC_STRUCT_PARAM_AS_PTR /* structs are passed as pointer */ if ((type->t & VT_BTYPE) == VT_STRUCT) { size = 4; } #endif if (param_index < fastcall_nb_regs) { /* save FASTCALL register */ loc -= 4; o(0x89); /* movl */ gen_modrm(fastcall_regs_ptr[param_index], VT_LOCAL, NULL, loc); param_addr = loc; } else { param_addr = addr; addr += size; } sym_push(sym->v & ~SYM_FIELD, type, VT_LOCAL | lvalue_type(type->t), param_addr); param_index++; } func_ret_sub = 0; /* pascal type call or fastcall ? */ if (func_call == FUNC_STDCALL || func_call == FUNC_FASTCALLW) func_ret_sub = addr - 8; #ifndef TCC_TARGET_PE else if (func_vc) func_ret_sub = 4; #endif #ifdef CONFIG_TCC_BCHECK /* leave some room for bound checking code */ if (tcc_state->do_bounds_check) { func_bound_offset = lbounds_section->data_offset; func_bound_ind = ind; oad(0xb8, 0); /* lbound section pointer */ oad(0xb8, 0); /* call to function */ } #endif } /* generate function epilog */ ST_FUNC void gfunc_epilog(void) { addr_t v, saved_ind; #ifdef CONFIG_TCC_BCHECK if (tcc_state->do_bounds_check && func_bound_offset != lbounds_section->data_offset) { addr_t saved_ind; addr_t *bounds_ptr; Sym *sym_data; /* add end of table info */ bounds_ptr = section_ptr_add(lbounds_section, sizeof(addr_t)); *bounds_ptr = 0; /* generate bound local allocation */ saved_ind = ind; ind = func_bound_ind; sym_data = get_sym_ref(&char_pointer_type, lbounds_section, func_bound_offset, lbounds_section->data_offset); greloc(cur_text_section, sym_data, ind + 1, R_386_32); oad(0xb8, 0); /* mov %eax, xxx */ gen_static_call(TOK___bound_local_new); ind = saved_ind; /* generate bound check local freeing */ o(0x5250); /* save returned value, if any */ greloc(cur_text_section, sym_data, ind + 1, R_386_32); oad(0xb8, 0); /* mov %eax, xxx */ gen_static_call(TOK___bound_local_delete); o(0x585a); /* restore returned value, if any */ } #endif /* align local size to word & save local variables */ v = (-loc + 3) & -4; #if USE_EBX o(0x8b); gen_modrm(TREG_EBX, VT_LOCAL, NULL, -(v+4)); #endif o(0xc9); /* leave */ if (func_ret_sub == 0) { o(0xc3); /* ret */ } else { o(0xc2); /* ret n */ g(func_ret_sub); g(func_ret_sub >> 8); } saved_ind = ind; ind = func_sub_sp_offset - FUNC_PROLOG_SIZE; #ifdef TCC_TARGET_PE if (v >= 4096) { oad(0xb8, v); /* mov stacksize, %eax */ gen_static_call(TOK___chkstk); /* call __chkstk, (does the stackframe too) */ } else #endif { o(0xe58955); /* push %ebp, mov %esp, %ebp */ o(0xec81); /* sub esp, stacksize */ gen_le32(v); #ifdef TCC_TARGET_PE o(0x90); /* adjust to FUNC_PROLOG_SIZE */ #endif } o(0x53 * USE_EBX); /* push ebx */ ind = saved_ind; } /* generate a jump to a label */ ST_FUNC int gjmp(int t) { return gjmp2(0xe9, t); } /* generate a jump to a fixed address */ ST_FUNC void gjmp_addr(int a) { int r; r = a - ind - 2; if (r == (char)r) { g(0xeb); g(r); } else { oad(0xe9, a - ind - 5); } } ST_FUNC void gtst_addr(int inv, int a) { int v = vtop->r & VT_VALMASK; if (v == VT_CMP) { inv ^= (vtop--)->c.i; a -= ind + 2; if (a == (char)a) { g(inv - 32); g(a); } else { g(0x0f); oad(inv - 16, a - 4); } } else if ((v & ~1) == VT_JMP) { if ((v & 1) != inv) { gjmp_addr(a); gsym(vtop->c.i); } else { gsym(vtop->c.i); o(0x05eb); gjmp_addr(a); } vtop--; } } /* generate a test. set 'inv' to invert test. Stack entry is popped */ ST_FUNC int gtst(int inv, int t) { int v = vtop->r & VT_VALMASK; if (nocode_wanted) { ; } else if (v == VT_CMP) { /* fast case : can jump directly since flags are set */ g(0x0f); t = gjmp2((vtop->c.i - 16) ^ inv, t); } else if (v == VT_JMP || v == VT_JMPI) { /* && or || optimization */ if ((v & 1) == inv) { /* insert vtop->c jump list in t */ uint32_t n1, n = vtop->c.i; if (n) { while ((n1 = read32le(cur_text_section->data + n))) n = n1; write32le(cur_text_section->data + n, t); t = vtop->c.i; } } else { t = gjmp(t); gsym(vtop->c.i); } } vtop--; return t; } /* generate an integer binary operation */ ST_FUNC void gen_opi(int op) { int r, fr, opc, c; switch(op) { case '+': case TOK_ADDC1: /* add with carry generation */ opc = 0; gen_op8: if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) { /* constant case */ vswap(); r = gv(RC_INT); vswap(); c = vtop->c.i; if (c == (char)c) { /* generate inc and dec for smaller code */ if (c==1 && opc==0 && op != TOK_ADDC1) { o (0x40 | r); // inc } else if (c==1 && opc==5 && op != TOK_SUBC1) { o (0x48 | r); // dec } else { o(0x83); o(0xc0 | (opc << 3) | r); g(c); } } else { o(0x81); oad(0xc0 | (opc << 3) | r, c); } } else { gv2(RC_INT, RC_INT); r = vtop[-1].r; fr = vtop[0].r; o((opc << 3) | 0x01); o(0xc0 + r + fr * 8); } vtop--; if (op >= TOK_ULT && op <= TOK_GT) { vtop->r = VT_CMP; vtop->c.i = op; } break; case '-': case TOK_SUBC1: /* sub with carry generation */ opc = 5; goto gen_op8; case TOK_ADDC2: /* add with carry use */ opc = 2; goto gen_op8; case TOK_SUBC2: /* sub with carry use */ opc = 3; goto gen_op8; case '&': opc = 4; goto gen_op8; case '^': opc = 6; goto gen_op8; case '|': opc = 1; goto gen_op8; case '*': gv2(RC_INT, RC_INT); r = vtop[-1].r; fr = vtop[0].r; vtop--; o(0xaf0f); /* imul fr, r */ o(0xc0 + fr + r * 8); break; case TOK_SHL: opc = 4; goto gen_shift; case TOK_SHR: opc = 5; goto gen_shift; case TOK_SAR: opc = 7; gen_shift: opc = 0xc0 | (opc << 3); if ((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) { /* constant case */ vswap(); r = gv(RC_INT); vswap(); c = vtop->c.i & 0x1f; o(0xc1); /* shl/shr/sar $xxx, r */ o(opc | r); g(c); } else { /* we generate the shift in ecx */ gv2(RC_INT, RC_ECX); r = vtop[-1].r; o(0xd3); /* shl/shr/sar %cl, r */ o(opc | r); } vtop--; break; case '/': case TOK_UDIV: case TOK_PDIV: case '%': case TOK_UMOD: case TOK_UMULL: /* first operand must be in eax */ /* XXX: need better constraint for second operand */ gv2(RC_EAX, RC_ECX); r = vtop[-1].r; fr = vtop[0].r; vtop--; save_reg(TREG_EDX); /* save EAX too if used otherwise */ save_reg_upstack(TREG_EAX, 1); if (op == TOK_UMULL) { o(0xf7); /* mul fr */ o(0xe0 + fr); vtop->r2 = TREG_EDX; r = TREG_EAX; } else { if (op == TOK_UDIV || op == TOK_UMOD) { o(0xf7d231); /* xor %edx, %edx, div fr, %eax */ o(0xf0 + fr); } else { o(0xf799); /* cltd, idiv fr, %eax */ o(0xf8 + fr); } if (op == '%' || op == TOK_UMOD) r = TREG_EDX; else r = TREG_EAX; } vtop->r = r; break; default: opc = 7; goto gen_op8; } } /* generate a floating point operation 'v = t1 op t2' instruction. The two operands are guaranteed to have the same floating point type */ /* XXX: need to use ST1 too */ ST_FUNC void gen_opf(int op) { int a, ft, fc, swapped, r; /* convert constants to memory references */ if ((vtop[-1].r & (VT_VALMASK | VT_LVAL)) == VT_CONST) { vswap(); gv(RC_FLOAT); vswap(); } if ((vtop[0].r & (VT_VALMASK | VT_LVAL)) == VT_CONST) gv(RC_FLOAT); /* must put at least one value in the floating point register */ if ((vtop[-1].r & VT_LVAL) && (vtop[0].r & VT_LVAL)) { vswap(); gv(RC_FLOAT); vswap(); } swapped = 0; /* swap the stack if needed so that t1 is the register and t2 is the memory reference */ if (vtop[-1].r & VT_LVAL) { vswap(); swapped = 1; } if (op >= TOK_ULT && op <= TOK_GT) { /* load on stack second operand */ load(TREG_ST0, vtop); save_reg(TREG_EAX); /* eax is used by FP comparison code */ if (op == TOK_GE || op == TOK_GT) swapped = !swapped; else if (op == TOK_EQ || op == TOK_NE) swapped = 0; if (swapped) o(0xc9d9); /* fxch %st(1) */ if (op == TOK_EQ || op == TOK_NE) o(0xe9da); /* fucompp */ else o(0xd9de); /* fcompp */ o(0xe0df); /* fnstsw %ax */ if (op == TOK_EQ) { o(0x45e480); /* and $0x45, %ah */ o(0x40fC80); /* cmp $0x40, %ah */ } else if (op == TOK_NE) { o(0x45e480); /* and $0x45, %ah */ o(0x40f480); /* xor $0x40, %ah */ op = TOK_NE; } else if (op == TOK_GE || op == TOK_LE) { o(0x05c4f6); /* test $0x05, %ah */ op = TOK_EQ; } else { o(0x45c4f6); /* test $0x45, %ah */ op = TOK_EQ; } vtop--; vtop->r = VT_CMP; vtop->c.i = op; } else { /* no memory reference possible for long double operations */ if ((vtop->type.t & VT_BTYPE) == VT_LDOUBLE) { load(TREG_ST0, vtop); swapped = !swapped; } switch(op) { default: case '+': a = 0; break; case '-': a = 4; if (swapped) a++; break; case '*': a = 1; break; case '/': a = 6; if (swapped) a++; break; } ft = vtop->type.t; fc = vtop->c.i; if ((ft & VT_BTYPE) == VT_LDOUBLE) { o(0xde); /* fxxxp %st, %st(1) */ o(0xc1 + (a << 3)); } else { /* if saved lvalue, then we must reload it */ r = vtop->r; if ((r & VT_VALMASK) == VT_LLOCAL) { SValue v1; r = get_reg(RC_INT); v1.type.t = VT_INT; v1.r = VT_LOCAL | VT_LVAL; v1.c.i = fc; load(r, &v1); fc = 0; } if ((ft & VT_BTYPE) == VT_DOUBLE) o(0xdc); else o(0xd8); gen_modrm(a, r, vtop->sym, fc); } vtop--; } } /* convert integers to fp 't' type. Must handle 'int', 'unsigned int' and 'long long' cases. */ ST_FUNC void gen_cvt_itof(int t) { save_reg(TREG_ST0); gv(RC_INT); if ((vtop->type.t & VT_BTYPE) == VT_LLONG) { /* signed long long to float/double/long double (unsigned case is handled generically) */ o(0x50 + vtop->r2); /* push r2 */ o(0x50 + (vtop->r & VT_VALMASK)); /* push r */ o(0x242cdf); /* fildll (%esp) */ o(0x08c483); /* add $8, %esp */ } else if ((vtop->type.t & (VT_BTYPE | VT_UNSIGNED)) == (VT_INT | VT_UNSIGNED)) { /* unsigned int to float/double/long double */ o(0x6a); /* push $0 */ g(0x00); o(0x50 + (vtop->r & VT_VALMASK)); /* push r */ o(0x242cdf); /* fildll (%esp) */ o(0x08c483); /* add $8, %esp */ } else { /* int to float/double/long double */ o(0x50 + (vtop->r & VT_VALMASK)); /* push r */ o(0x2404db); /* fildl (%esp) */ o(0x04c483); /* add $4, %esp */ } vtop->r = TREG_ST0; } /* convert fp to int 't' type */ ST_FUNC void gen_cvt_ftoi(int t) { int bt = vtop->type.t & VT_BTYPE; if (bt == VT_FLOAT) vpush_global_sym(&func_old_type, TOK___fixsfdi); else if (bt == VT_LDOUBLE) vpush_global_sym(&func_old_type, TOK___fixxfdi); else vpush_global_sym(&func_old_type, TOK___fixdfdi); vswap(); gfunc_call(1); vpushi(0); vtop->r = REG_IRET; vtop->r2 = REG_LRET; } /* convert from one floating point type to another */ ST_FUNC void gen_cvt_ftof(int t) { /* all we have to do on i386 is to put the float in a register */ gv(RC_FLOAT); } /* computed goto support */ ST_FUNC void ggoto(void) { gcall_or_jmp(1); vtop--; } /* bound check support functions */ #ifdef CONFIG_TCC_BCHECK /* generate a bounded pointer addition */ ST_FUNC void gen_bounded_ptr_add(void) { /* prepare fast i386 function call (args in eax and edx) */ gv2(RC_EAX, RC_EDX); /* save all temporary registers */ vtop -= 2; save_regs(0); /* do a fast function call */ gen_static_call(TOK___bound_ptr_add); /* returned pointer is in eax */ vtop++; vtop->r = TREG_EAX | VT_BOUNDED; /* address of bounding function call point */ vtop->c.i = (cur_text_section->reloc->data_offset - sizeof(Elf32_Rel)); } /* patch pointer addition in vtop so that pointer dereferencing is also tested */ ST_FUNC void gen_bounded_ptr_deref(void) { addr_t func; int size, align; Elf32_Rel *rel; Sym *sym; size = 0; /* XXX: put that code in generic part of tcc */ if (!is_float(vtop->type.t)) { if (vtop->r & VT_LVAL_BYTE) size = 1; else if (vtop->r & VT_LVAL_SHORT) size = 2; } if (!size) size = type_size(&vtop->type, &align); switch(size) { case 1: func = TOK___bound_ptr_indir1; break; case 2: func = TOK___bound_ptr_indir2; break; case 4: func = TOK___bound_ptr_indir4; break; case 8: func = TOK___bound_ptr_indir8; break; case 12: func = TOK___bound_ptr_indir12; break; case 16: func = TOK___bound_ptr_indir16; break; default: tcc_error("unhandled size when dereferencing bounded pointer"); func = 0; break; } /* patch relocation */ /* XXX: find a better solution ? */ rel = (Elf32_Rel *)(cur_text_section->reloc->data + vtop->c.i); sym = external_global_sym(func, &func_old_type, 0); if (!sym->c) put_extern_sym(sym, NULL, 0, 0); rel->r_info = ELF32_R_INFO(sym->c, ELF32_R_TYPE(rel->r_info)); } #endif /* Save the stack pointer onto the stack */ ST_FUNC void gen_vla_sp_save(int addr) { /* mov %esp,addr(%ebp)*/ o(0x89); gen_modrm(TREG_ESP, VT_LOCAL, NULL, addr); } /* Restore the SP from a location on the stack */ ST_FUNC void gen_vla_sp_restore(int addr) { o(0x8b); gen_modrm(TREG_ESP, VT_LOCAL, NULL, addr); } /* Subtract from the stack pointer, and push the resulting value onto the stack */ ST_FUNC void gen_vla_alloc(CType *type, int align) { #ifdef TCC_TARGET_PE /* alloca does more than just adjust %rsp on Windows */ vpush_global_sym(&func_old_type, TOK_alloca); vswap(); /* Move alloca ref past allocation size */ gfunc_call(1); #else int r; r = gv(RC_INT); /* allocation size */ /* sub r,%rsp */ o(0x2b); o(0xe0 | r); /* We align to 16 bytes rather than align */ /* and ~15, %esp */ o(0xf0e483); vpop(); #endif } /* end of X86 code generator */ /*************************************************************/ #endif /*************************************************************/
#ifdef TARGET_DEFS_ONLY #define EM_TCC_TARGET EM_386 /* relocation type for 32 bit data relocation */ #define R_DATA_32 R_386_32 #define R_DATA_PTR R_386_32 #define R_JMP_SLOT R_386_JMP_SLOT #define R_GLOB_DAT R_386_GLOB_DAT #define R_COPY R_386_COPY #define R_RELATIVE R_386_RELATIVE #define R_NUM R_386_NUM #define ELF_START_ADDR 0x08048000 #define ELF_PAGE_SIZE 0x1000 #define PCRELATIVE_DLLPLT 0 #define RELOCATE_DLLPLT 0 #else /* !TARGET_DEFS_ONLY */ #include "tcc.h" /* Returns 1 for a code relocation, 0 for a data relocation. For unknown relocations, returns -1. */ int code_reloc (int reloc_type) { switch (reloc_type) { case R_386_RELATIVE: case R_386_16: case R_386_32: case R_386_GOTPC: case R_386_GOTOFF: case R_386_GOT32: case R_386_GOT32X: case R_386_GLOB_DAT: case R_386_COPY: return 0; case R_386_PC16: case R_386_PC32: case R_386_PLT32: case R_386_JMP_SLOT: return 1; } tcc_error ("Unknown relocation type: %d", reloc_type); return -1; } /* Returns an enumerator to describe whether and when the relocation needs a GOT and/or PLT entry to be created. See tcc.h for a description of the different values. */ int gotplt_entry_type (int reloc_type) { switch (reloc_type) { case R_386_RELATIVE: case R_386_16: case R_386_GLOB_DAT: case R_386_JMP_SLOT: case R_386_COPY: return NO_GOTPLT_ENTRY; case R_386_32: /* This relocations shouldn't normally need GOT or PLT slots if it weren't for simplicity in the code generator. See our caller for comments. */ return AUTO_GOTPLT_ENTRY; case R_386_PC16: case R_386_PC32: return AUTO_GOTPLT_ENTRY; case R_386_GOTPC: case R_386_GOTOFF: return BUILD_GOT_ONLY; case R_386_GOT32: case R_386_GOT32X: case R_386_PLT32: return ALWAYS_GOTPLT_ENTRY; } tcc_error ("Unknown relocation type: %d", reloc_type); return -1; } ST_FUNC unsigned create_plt_entry(TCCState *s1, unsigned got_offset, struct sym_attr *attr) { Section *plt = s1->plt; uint8_t *p; int modrm; unsigned plt_offset, relofs; /* on i386 if we build a DLL, we add a %ebx offset */ if (s1->output_type == TCC_OUTPUT_DLL) modrm = 0xa3; else modrm = 0x25; /* empty PLT: create PLT0 entry that pushes the library identifier (GOT + PTR_SIZE) and jumps to ld.so resolution routine (GOT + 2 * PTR_SIZE) */ if (plt->data_offset == 0) { p = section_ptr_add(plt, 16); p[0] = 0xff; /* pushl got + PTR_SIZE */ p[1] = modrm + 0x10; write32le(p + 2, PTR_SIZE); p[6] = 0xff; /* jmp *(got + PTR_SIZE * 2) */ p[7] = modrm; write32le(p + 8, PTR_SIZE * 2); } plt_offset = plt->data_offset; /* The PLT slot refers to the relocation entry it needs via offset. The reloc entry is created below, so its offset is the current data_offset */ relofs = s1->got->reloc ? s1->got->reloc->data_offset : 0; /* Jump to GOT entry where ld.so initially put the address of ip + 4 */ p = section_ptr_add(plt, 16); p[0] = 0xff; /* jmp *(got + x) */ p[1] = modrm; write32le(p + 2, got_offset); p[6] = 0x68; /* push $xxx */ write32le(p + 7, relofs); p[11] = 0xe9; /* jmp plt_start */ write32le(p + 12, -(plt->data_offset)); return plt_offset; } /* relocate the PLT: compute addresses and offsets in the PLT now that final address for PLT and GOT are known (see fill_program_header) */ ST_FUNC void relocate_plt(TCCState *s1) { uint8_t *p, *p_end; if (!s1->plt) return; p = s1->plt->data; p_end = p + s1->plt->data_offset; if (p < p_end) { add32le(p + 2, s1->got->sh_addr); add32le(p + 8, s1->got->sh_addr); p += 16; while (p < p_end) { add32le(p + 2, s1->got->sh_addr); p += 16; } } } static ElfW_Rel *qrel; /* ptr to next reloc entry reused */ void relocate_init(Section *sr) { qrel = (ElfW_Rel *) sr->data; } void relocate(TCCState *s1, ElfW_Rel *rel, int type, unsigned char *ptr, addr_t addr, addr_t val) { int sym_index, esym_index; sym_index = ELFW(R_SYM)(rel->r_info); switch (type) { case R_386_32: if (s1->output_type == TCC_OUTPUT_DLL) { esym_index = s1->sym_attrs[sym_index].dyn_index; qrel->r_offset = rel->r_offset; if (esym_index) { qrel->r_info = ELFW(R_INFO)(esym_index, R_386_32); qrel++; return; } else { qrel->r_info = ELFW(R_INFO)(0, R_386_RELATIVE); qrel++; } } add32le(ptr, val); return; case R_386_PC32: if (s1->output_type == TCC_OUTPUT_DLL) { /* DLL relocation */ esym_index = s1->sym_attrs[sym_index].dyn_index; if (esym_index) { qrel->r_offset = rel->r_offset; qrel->r_info = ELFW(R_INFO)(esym_index, R_386_PC32); qrel++; return; } } add32le(ptr, val - addr); return; case R_386_PLT32: add32le(ptr, val - addr); return; case R_386_GLOB_DAT: case R_386_JMP_SLOT: write32le(ptr, val); return; case R_386_GOTPC: add32le(ptr, s1->got->sh_addr - addr); return; case R_386_GOTOFF: add32le(ptr, val - s1->got->sh_addr); return; case R_386_GOT32: case R_386_GOT32X: /* we load the got offset */ add32le(ptr, s1->sym_attrs[sym_index].got_offset); return; case R_386_16: if (s1->output_format != TCC_OUTPUT_FORMAT_BINARY) { output_file: tcc_error("can only produce 16-bit binary files"); } write16le(ptr, read16le(ptr) + val); return; case R_386_PC16: if (s1->output_format != TCC_OUTPUT_FORMAT_BINARY) goto output_file; write16le(ptr, read16le(ptr) + val - addr); return; case R_386_RELATIVE: #ifdef TCC_TARGET_PE add32le(ptr, val - s1->pe_imagebase); #endif /* do nothing */ return; case R_386_COPY: /* This relocation must copy initialized data from the library to the program .bss segment. Currently made like for ARM (to remove noise of default case). Is this true? */ return; default: fprintf(stderr,"FIXME: handle reloc type %d at %x [%p] to %x\n", type, (unsigned)addr, ptr, (unsigned)val); return; } } #endif /* !TARGET_DEFS_ONLY */
/* * i386 specific functions for TCC assembler * * Copyright (c) 2001, 2002 Fabrice Bellard * Copyright (c) 2009 Frédéric Feret (x86_64 support) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "tcc.h" #define MAX_OPERANDS 3 #define TOK_ASM_first TOK_ASM_clc #define TOK_ASM_last TOK_ASM_emms #define TOK_ASM_alllast TOK_ASM_subps #define OPC_B 0x01 /* only used with OPC_WL */ #define OPC_WL 0x02 /* accepts w, l or no suffix */ #define OPC_BWL (OPC_B | OPC_WL) /* accepts b, w, l or no suffix */ #define OPC_REG 0x04 /* register is added to opcode */ #define OPC_MODRM 0x08 /* modrm encoding */ #define OPCT_MASK 0x70 #define OPC_FWAIT 0x10 /* add fwait opcode */ #define OPC_SHIFT 0x20 /* shift opcodes */ #define OPC_ARITH 0x30 /* arithmetic opcodes */ #define OPC_FARITH 0x40 /* FPU arithmetic opcodes */ #define OPC_TEST 0x50 /* test opcodes */ #define OPCT_IS(v,i) (((v) & OPCT_MASK) == (i)) #define OPC_0F 0x100 /* Is secondary map (0x0f prefix) */ #define OPC_48 0x200 /* Always has REX prefix */ #ifdef TCC_TARGET_X86_64 # define OPC_WLQ 0x1000 /* accepts w, l, q or no suffix */ # define OPC_BWLQ (OPC_B | OPC_WLQ) /* accepts b, w, l, q or no suffix */ # define OPC_WLX OPC_WLQ # define OPC_BWLX OPC_BWLQ #else # define OPC_WLX OPC_WL # define OPC_BWLX OPC_BWL #endif #define OPC_GROUP_SHIFT 13 /* in order to compress the operand type, we use specific operands and we or only with EA */ enum { OPT_REG8=0, /* warning: value is hardcoded from TOK_ASM_xxx */ OPT_REG16, /* warning: value is hardcoded from TOK_ASM_xxx */ OPT_REG32, /* warning: value is hardcoded from TOK_ASM_xxx */ #ifdef TCC_TARGET_X86_64 OPT_REG64, /* warning: value is hardcoded from TOK_ASM_xxx */ #endif OPT_MMX, /* warning: value is hardcoded from TOK_ASM_xxx */ OPT_SSE, /* warning: value is hardcoded from TOK_ASM_xxx */ OPT_CR, /* warning: value is hardcoded from TOK_ASM_xxx */ OPT_TR, /* warning: value is hardcoded from TOK_ASM_xxx */ OPT_DB, /* warning: value is hardcoded from TOK_ASM_xxx */ OPT_SEG, OPT_ST, #ifdef TCC_TARGET_X86_64 OPT_REG8_LOW, /* %spl,%bpl,%sil,%dil, encoded like ah,ch,dh,bh, but with REX prefix, not used in insn templates */ #endif OPT_IM8, OPT_IM8S, OPT_IM16, OPT_IM32, #ifdef TCC_TARGET_X86_64 OPT_IM64, #endif OPT_EAX, /* %al, %ax, %eax or %rax register */ OPT_ST0, /* %st(0) register */ OPT_CL, /* %cl register */ OPT_DX, /* %dx register */ OPT_ADDR, /* OP_EA with only offset */ OPT_INDIR, /* *(expr) */ /* composite types */ OPT_COMPOSITE_FIRST, OPT_IM, /* IM8 | IM16 | IM32 */ OPT_REG, /* REG8 | REG16 | REG32 | REG64 */ OPT_REGW, /* REG16 | REG32 | REG64 */ OPT_IMW, /* IM16 | IM32 */ OPT_MMXSSE, /* MMX | SSE */ OPT_DISP, /* Like OPT_ADDR, but emitted as displacement (for jumps) */ OPT_DISP8, /* Like OPT_ADDR, but only 8bit (short jumps) */ /* can be ored with any OPT_xxx */ OPT_EA = 0x80 }; #define OP_REG8 (1 << OPT_REG8) #define OP_REG16 (1 << OPT_REG16) #define OP_REG32 (1 << OPT_REG32) #define OP_MMX (1 << OPT_MMX) #define OP_SSE (1 << OPT_SSE) #define OP_CR (1 << OPT_CR) #define OP_TR (1 << OPT_TR) #define OP_DB (1 << OPT_DB) #define OP_SEG (1 << OPT_SEG) #define OP_ST (1 << OPT_ST) #define OP_IM8 (1 << OPT_IM8) #define OP_IM8S (1 << OPT_IM8S) #define OP_IM16 (1 << OPT_IM16) #define OP_IM32 (1 << OPT_IM32) #define OP_EAX (1 << OPT_EAX) #define OP_ST0 (1 << OPT_ST0) #define OP_CL (1 << OPT_CL) #define OP_DX (1 << OPT_DX) #define OP_ADDR (1 << OPT_ADDR) #define OP_INDIR (1 << OPT_INDIR) #ifdef TCC_TARGET_X86_64 # define OP_REG64 (1 << OPT_REG64) # define OP_REG8_LOW (1 << OPT_REG8_LOW) # define OP_IM64 (1 << OPT_IM64) # define OP_EA32 (OP_EA << 1) #else # define OP_REG64 0 # define OP_REG8_LOW 0 # define OP_IM64 0 # define OP_EA32 0 #endif #define OP_EA 0x40000000 #define OP_REG (OP_REG8 | OP_REG16 | OP_REG32 | OP_REG64) #ifdef TCC_TARGET_X86_64 # define TREG_XAX TREG_RAX # define TREG_XCX TREG_RCX # define TREG_XDX TREG_RDX #else # define TREG_XAX TREG_EAX # define TREG_XCX TREG_ECX # define TREG_XDX TREG_EDX #endif typedef struct ASMInstr { uint16_t sym; uint16_t opcode; uint16_t instr_type; uint8_t nb_ops; uint8_t op_type[MAX_OPERANDS]; /* see OP_xxx */ } ASMInstr; typedef struct Operand { uint32_t type; int8_t reg; /* register, -1 if none */ int8_t reg2; /* second register, -1 if none */ uint8_t shift; ExprValue e; } Operand; static const uint8_t reg_to_size[9] = { /* [OP_REG8] = 0, [OP_REG16] = 1, [OP_REG32] = 2, #ifdef TCC_TARGET_X86_64 [OP_REG64] = 3, #endif */ 0, 0, 1, 0, 2, 0, 0, 0, 3 }; #define NB_TEST_OPCODES 30 static const uint8_t test_bits[NB_TEST_OPCODES] = { 0x00, /* o */ 0x01, /* no */ 0x02, /* b */ 0x02, /* c */ 0x02, /* nae */ 0x03, /* nb */ 0x03, /* nc */ 0x03, /* ae */ 0x04, /* e */ 0x04, /* z */ 0x05, /* ne */ 0x05, /* nz */ 0x06, /* be */ 0x06, /* na */ 0x07, /* nbe */ 0x07, /* a */ 0x08, /* s */ 0x09, /* ns */ 0x0a, /* p */ 0x0a, /* pe */ 0x0b, /* np */ 0x0b, /* po */ 0x0c, /* l */ 0x0c, /* nge */ 0x0d, /* nl */ 0x0d, /* ge */ 0x0e, /* le */ 0x0e, /* ng */ 0x0f, /* nle */ 0x0f, /* g */ }; static const uint8_t segment_prefixes[] = { 0x26, /* es */ 0x2e, /* cs */ 0x36, /* ss */ 0x3e, /* ds */ 0x64, /* fs */ 0x65 /* gs */ }; static const ASMInstr asm_instrs[] = { #define ALT(x) x /* This removes a 0x0f in the second byte */ #define O(o) ((uint64_t) ((((o) & 0xff00) == 0x0f00) ? ((((o) >> 8) & ~0xff) | ((o) & 0xff)) : (o))) /* This constructs instr_type from opcode, type and group. */ #define T(o,i,g) ((i) | ((g) << OPC_GROUP_SHIFT) | ((((o) & 0xff00) == 0x0f00) ? OPC_0F : 0)) #define DEF_ASM_OP0(name, opcode) #define DEF_ASM_OP0L(name, opcode, group, instr_type) { TOK_ASM_ ## name, O(opcode), T(opcode, instr_type, group), 0, { 0 } }, #define DEF_ASM_OP1(name, opcode, group, instr_type, op0) { TOK_ASM_ ## name, O(opcode), T(opcode, instr_type, group), 1, { op0 }}, #define DEF_ASM_OP2(name, opcode, group, instr_type, op0, op1) { TOK_ASM_ ## name, O(opcode), T(opcode, instr_type, group), 2, { op0, op1 }}, #define DEF_ASM_OP3(name, opcode, group, instr_type, op0, op1, op2) { TOK_ASM_ ## name, O(opcode), T(opcode, instr_type, group), 3, { op0, op1, op2 }}, #ifdef TCC_TARGET_X86_64 # include "x86_64-asm.h" #else # include "i386-asm.h" #endif /* last operation */ { 0, }, }; static const uint16_t op0_codes[] = { #define ALT(x) #define DEF_ASM_OP0(x, opcode) opcode, #define DEF_ASM_OP0L(name, opcode, group, instr_type) #define DEF_ASM_OP1(name, opcode, group, instr_type, op0) #define DEF_ASM_OP2(name, opcode, group, instr_type, op0, op1) #define DEF_ASM_OP3(name, opcode, group, instr_type, op0, op1, op2) #ifdef TCC_TARGET_X86_64 # include "x86_64-asm.h" #else # include "i386-asm.h" #endif }; static inline int get_reg_shift(TCCState *s1) { int shift, v; v = asm_int_expr(s1); switch(v) { case 1: shift = 0; break; case 2: shift = 1; break; case 4: shift = 2; break; case 8: shift = 3; break; default: expect("1, 2, 4 or 8 constant"); shift = 0; break; } return shift; } #ifdef TCC_TARGET_X86_64 static int asm_parse_numeric_reg(int t, unsigned int *type) { int reg = -1; if (t >= TOK_IDENT && t < tok_ident) { const char *s = table_ident[t - TOK_IDENT]->str; char c; *type = OP_REG64; if (*s == 'c') { s++; *type = OP_CR; } if (*s++ != 'r') return -1; /* Don't allow leading '0'. */ if ((c = *s++) >= '1' && c <= '9') reg = c - '0'; else return -1; if ((c = *s) >= '0' && c <= '5') s++, reg = reg * 10 + c - '0'; if (reg > 15) return -1; if ((c = *s) == 0) ; else if (*type != OP_REG64) return -1; else if (c == 'b' && !s[1]) *type = OP_REG8; else if (c == 'w' && !s[1]) *type = OP_REG16; else if (c == 'd' && !s[1]) *type = OP_REG32; else return -1; } return reg; } #endif static int asm_parse_reg(unsigned int *type) { int reg = 0; *type = 0; if (tok != '%') goto error_32; next(); if (tok >= TOK_ASM_eax && tok <= TOK_ASM_edi) { reg = tok - TOK_ASM_eax; *type = OP_REG32; #ifdef TCC_TARGET_X86_64 } else if (tok >= TOK_ASM_rax && tok <= TOK_ASM_rdi) { reg = tok - TOK_ASM_rax; *type = OP_REG64; } else if (tok == TOK_ASM_rip) { reg = -2; /* Probably should use different escape code. */ *type = OP_REG64; } else if ((reg = asm_parse_numeric_reg(tok, type)) >= 0 && (*type == OP_REG32 || *type == OP_REG64)) { ; #endif } else { error_32: expect("register"); } next(); return reg; } static void parse_operand(TCCState *s1, Operand *op) { ExprValue e; int reg, indir; const char *p; indir = 0; if (tok == '*') { next(); indir = OP_INDIR; } if (tok == '%') { next(); if (tok >= TOK_ASM_al && tok <= TOK_ASM_db7) { reg = tok - TOK_ASM_al; op->type = 1 << (reg >> 3); /* WARNING: do not change constant order */ op->reg = reg & 7; if ((op->type & OP_REG) && op->reg == TREG_XAX) op->type |= OP_EAX; else if (op->type == OP_REG8 && op->reg == TREG_XCX) op->type |= OP_CL; else if (op->type == OP_REG16 && op->reg == TREG_XDX) op->type |= OP_DX; } else if (tok >= TOK_ASM_dr0 && tok <= TOK_ASM_dr7) { op->type = OP_DB; op->reg = tok - TOK_ASM_dr0; } else if (tok >= TOK_ASM_es && tok <= TOK_ASM_gs) { op->type = OP_SEG; op->reg = tok - TOK_ASM_es; } else if (tok == TOK_ASM_st) { op->type = OP_ST; op->reg = 0; next(); if (tok == '(') { next(); if (tok != TOK_PPNUM) goto reg_error; p = tokc.str.data; reg = p[0] - '0'; if ((unsigned)reg >= 8 || p[1] != '\0') goto reg_error; op->reg = reg; next(); skip(')'); } if (op->reg == 0) op->type |= OP_ST0; goto no_skip; #ifdef TCC_TARGET_X86_64 } else if (tok >= TOK_ASM_spl && tok <= TOK_ASM_dil) { op->type = OP_REG8 | OP_REG8_LOW; op->reg = 4 + tok - TOK_ASM_spl; } else if ((op->reg = asm_parse_numeric_reg(tok, &op->type)) >= 0) { ; #endif } else { reg_error: tcc_error("unknown register %%%s", get_tok_str(tok, &tokc)); } next(); no_skip: ; } else if (tok == '$') { /* constant value */ next(); asm_expr(s1, &e); op->type = OP_IM32; op->e = e; if (!op->e.sym) { if (op->e.v == (uint8_t)op->e.v) op->type |= OP_IM8; if (op->e.v == (int8_t)op->e.v) op->type |= OP_IM8S; if (op->e.v == (uint16_t)op->e.v) op->type |= OP_IM16; #ifdef TCC_TARGET_X86_64 if (op->e.v != (int32_t)op->e.v && op->e.v != (uint32_t)op->e.v) op->type = OP_IM64; #endif } } else { /* address(reg,reg2,shift) with all variants */ op->type = OP_EA; op->reg = -1; op->reg2 = -1; op->shift = 0; if (tok != '(') { asm_expr(s1, &e); op->e = e; } else { next(); if (tok == '%') { unget_tok('('); op->e.v = 0; op->e.sym = NULL; } else { /* bracketed offset expression */ asm_expr(s1, &e); if (tok != ')') expect(")"); next(); op->e.v = e.v; op->e.sym = e.sym; } op->e.pcrel = 0; } if (tok == '(') { unsigned int type = 0; next(); if (tok != ',') { op->reg = asm_parse_reg(&type); } if (tok == ',') { next(); if (tok != ',') { op->reg2 = asm_parse_reg(&type); } if (tok == ',') { next(); op->shift = get_reg_shift(s1); } } if (type & OP_REG32) op->type |= OP_EA32; skip(')'); } if (op->reg == -1 && op->reg2 == -1) op->type |= OP_ADDR; } op->type |= indir; } /* XXX: unify with C code output ? */ ST_FUNC void gen_expr32(ExprValue *pe) { if (pe->pcrel) /* If PC-relative, always set VT_SYM, even without symbol, so as to force a relocation to be emitted. */ gen_addrpc32(VT_SYM, pe->sym, pe->v); else gen_addr32(pe->sym ? VT_SYM : 0, pe->sym, pe->v); } #ifdef TCC_TARGET_X86_64 ST_FUNC void gen_expr64(ExprValue *pe) { gen_addr64(pe->sym ? VT_SYM : 0, pe->sym, pe->v); } #endif /* XXX: unify with C code output ? */ static void gen_disp32(ExprValue *pe) { Sym *sym = pe->sym; ElfSym *esym = elfsym(sym); if (esym && esym->st_shndx == cur_text_section->sh_num) { /* same section: we can output an absolute value. Note that the TCC compiler behaves differently here because it always outputs a relocation to ease (future) code elimination in the linker */ gen_le32(pe->v + esym->st_value - ind - 4); } else { if (sym && sym->type.t == VT_VOID) { sym->type.t = VT_FUNC; sym->type.ref = NULL; } gen_addrpc32(VT_SYM, sym, pe->v); } } /* generate the modrm operand */ static inline int asm_modrm(int reg, Operand *op) { int mod, reg1, reg2, sib_reg1; if (op->type & (OP_REG | OP_MMX | OP_SSE)) { g(0xc0 + (reg << 3) + op->reg); } else if (op->reg == -1 && op->reg2 == -1) { /* displacement only */ #ifdef TCC_TARGET_X86_64 g(0x04 + (reg << 3)); g(0x25); #else g(0x05 + (reg << 3)); #endif gen_expr32(&op->e); #ifdef TCC_TARGET_X86_64 } else if (op->reg == -2) { ExprValue *pe = &op->e; g(0x05 + (reg << 3)); gen_addrpc32(pe->sym ? VT_SYM : 0, pe->sym, pe->v); return ind; #endif } else { sib_reg1 = op->reg; /* fist compute displacement encoding */ if (sib_reg1 == -1) { sib_reg1 = 5; mod = 0x00; } else if (op->e.v == 0 && !op->e.sym && op->reg != 5) { mod = 0x00; } else if (op->e.v == (int8_t)op->e.v && !op->e.sym) { mod = 0x40; } else { mod = 0x80; } /* compute if sib byte needed */ reg1 = op->reg; if (op->reg2 != -1) reg1 = 4; g(mod + (reg << 3) + reg1); if (reg1 == 4) { /* add sib byte */ reg2 = op->reg2; if (reg2 == -1) reg2 = 4; /* indicate no index */ g((op->shift << 6) + (reg2 << 3) + sib_reg1); } /* add offset */ if (mod == 0x40) { g(op->e.v); } else if (mod == 0x80 || op->reg == -1) { gen_expr32(&op->e); } } return 0; } #ifdef TCC_TARGET_X86_64 #define REX_W 0x48 #define REX_R 0x44 #define REX_X 0x42 #define REX_B 0x41 static void asm_rex(int width64, Operand *ops, int nb_ops, int *op_type, int regi, int rmi) { unsigned char rex = width64 ? 0x48 : 0; int saw_high_8bit = 0; int i; if (rmi == -1) { /* No mod/rm byte, but we might have a register op nevertheless (we will add it to the opcode later). */ for(i = 0; i < nb_ops; i++) { if (op_type[i] & (OP_REG | OP_ST)) { if (ops[i].reg >= 8) { rex |= REX_B; ops[i].reg -= 8; } else if (ops[i].type & OP_REG8_LOW) rex |= 0x40; else if (ops[i].type & OP_REG8 && ops[i].reg >= 4) /* An 8 bit reg >= 4 without REG8 is ah/ch/dh/bh */ saw_high_8bit = ops[i].reg; break; } } } else { if (regi != -1) { if (ops[regi].reg >= 8) { rex |= REX_R; ops[regi].reg -= 8; } else if (ops[regi].type & OP_REG8_LOW) rex |= 0x40; else if (ops[regi].type & OP_REG8 && ops[regi].reg >= 4) /* An 8 bit reg >= 4 without REG8 is ah/ch/dh/bh */ saw_high_8bit = ops[regi].reg; } if (ops[rmi].type & (OP_REG | OP_MMX | OP_SSE | OP_CR | OP_EA)) { if (ops[rmi].reg >= 8) { rex |= REX_B; ops[rmi].reg -= 8; } else if (ops[rmi].type & OP_REG8_LOW) rex |= 0x40; else if (ops[rmi].type & OP_REG8 && ops[rmi].reg >= 4) /* An 8 bit reg >= 4 without REG8 is ah/ch/dh/bh */ saw_high_8bit = ops[rmi].reg; } if (ops[rmi].type & OP_EA && ops[rmi].reg2 >= 8) { rex |= REX_X; ops[rmi].reg2 -= 8; } } if (rex) { if (saw_high_8bit) tcc_error("can't encode register %%%ch when REX prefix is required", "acdb"[saw_high_8bit-4]); g(rex); } } #endif static void maybe_print_stats (void) { static int already = 1; if (!already) /* print stats about opcodes */ { const struct ASMInstr *pa; int freq[4]; int op_vals[500]; int nb_op_vals, i, j; already = 1; nb_op_vals = 0; memset(freq, 0, sizeof(freq)); for(pa = asm_instrs; pa->sym != 0; pa++) { freq[pa->nb_ops]++; //for(i=0;i<pa->nb_ops;i++) { for(j=0;j<nb_op_vals;j++) { //if (pa->op_type[i] == op_vals[j]) if (pa->instr_type == op_vals[j]) goto found; } //op_vals[nb_op_vals++] = pa->op_type[i]; op_vals[nb_op_vals++] = pa->instr_type; found: ; //} } for(i=0;i<nb_op_vals;i++) { int v = op_vals[i]; //if ((v & (v - 1)) != 0) printf("%3d: %08x\n", i, v); } printf("size=%d nb=%d f0=%d f1=%d f2=%d f3=%d\n", (int)sizeof(asm_instrs), (int)sizeof(asm_instrs) / (int)sizeof(ASMInstr), freq[0], freq[1], freq[2], freq[3]); } } ST_FUNC void asm_opcode(TCCState *s1, int opcode) { const ASMInstr *pa; int i, modrm_index, modreg_index, reg, v, op1, seg_prefix, pc; int nb_ops, s; Operand ops[MAX_OPERANDS], *pop; int op_type[3]; /* decoded op type */ int alltypes; /* OR of all operand types */ int autosize; int p66; #ifdef TCC_TARGET_X86_64 int rex64; #endif maybe_print_stats(); /* force synthetic ';' after prefix instruction, so we can handle */ /* one-line things like "rep stosb" instead of only "rep\nstosb" */ if (opcode >= TOK_ASM_wait && opcode <= TOK_ASM_repnz) unget_tok(';'); /* get operands */ pop = ops; nb_ops = 0; seg_prefix = 0; alltypes = 0; for(;;) { if (tok == ';' || tok == TOK_LINEFEED) break; if (nb_ops >= MAX_OPERANDS) { tcc_error("incorrect number of operands"); } parse_operand(s1, pop); if (tok == ':') { if (pop->type != OP_SEG || seg_prefix) tcc_error("incorrect prefix"); seg_prefix = segment_prefixes[pop->reg]; next(); parse_operand(s1, pop); if (!(pop->type & OP_EA)) { tcc_error("segment prefix must be followed by memory reference"); } } pop++; nb_ops++; if (tok != ',') break; next(); } s = 0; /* avoid warning */ again: /* optimize matching by using a lookup table (no hashing is needed !) */ for(pa = asm_instrs; pa->sym != 0; pa++) { int it = pa->instr_type & OPCT_MASK; s = 0; if (it == OPC_FARITH) { v = opcode - pa->sym; if (!((unsigned)v < 8 * 6 && (v % 6) == 0)) continue; } else if (it == OPC_ARITH) { if (!(opcode >= pa->sym && opcode < pa->sym + 8*NBWLX)) continue; s = (opcode - pa->sym) % NBWLX; if ((pa->instr_type & OPC_BWLX) == OPC_WLX) { /* We need to reject the xxxb opcodes that we accepted above. Note that pa->sym for WLX opcodes is the 'w' token, to get the 'b' token subtract one. */ if (((opcode - pa->sym + 1) % NBWLX) == 0) continue; s++; } } else if (it == OPC_SHIFT) { if (!(opcode >= pa->sym && opcode < pa->sym + 7*NBWLX)) continue; s = (opcode - pa->sym) % NBWLX; } else if (it == OPC_TEST) { if (!(opcode >= pa->sym && opcode < pa->sym + NB_TEST_OPCODES)) continue; /* cmovxx is a test opcode but accepts multiple sizes. The suffixes aren't encoded in the table, instead we simply force size autodetection always and deal with suffixed variants below when we don't find e.g. "cmovzl". */ if (pa->instr_type & OPC_WLX) s = NBWLX - 1; } else if (pa->instr_type & OPC_B) { #ifdef TCC_TARGET_X86_64 /* Some instructions don't have the full size but only bwl form. insb e.g. */ if ((pa->instr_type & OPC_WLQ) != OPC_WLQ && !(opcode >= pa->sym && opcode < pa->sym + NBWLX-1)) continue; #endif if (!(opcode >= pa->sym && opcode < pa->sym + NBWLX)) continue; s = opcode - pa->sym; } else if (pa->instr_type & OPC_WLX) { if (!(opcode >= pa->sym && opcode < pa->sym + NBWLX-1)) continue; s = opcode - pa->sym + 1; } else { if (pa->sym != opcode) continue; } if (pa->nb_ops != nb_ops) continue; #ifdef TCC_TARGET_X86_64 /* Special case for moves. Selecting the IM64->REG64 form should only be done if we really have an >32bit imm64, and that is hardcoded. Ignore it here. */ if (pa->opcode == 0xb0 && ops[0].type != OP_IM64 && (ops[1].type & OP_REG) == OP_REG64 && !(pa->instr_type & OPC_0F)) continue; #endif /* now decode and check each operand */ alltypes = 0; for(i = 0; i < nb_ops; i++) { int op1, op2; op1 = pa->op_type[i]; op2 = op1 & 0x1f; switch(op2) { case OPT_IM: v = OP_IM8 | OP_IM16 | OP_IM32; break; case OPT_REG: v = OP_REG8 | OP_REG16 | OP_REG32 | OP_REG64; break; case OPT_REGW: v = OP_REG16 | OP_REG32 | OP_REG64; break; case OPT_IMW: v = OP_IM16 | OP_IM32; break; case OPT_MMXSSE: v = OP_MMX | OP_SSE; break; case OPT_DISP: case OPT_DISP8: v = OP_ADDR; break; default: v = 1 << op2; break; } if (op1 & OPT_EA) v |= OP_EA; op_type[i] = v; if ((ops[i].type & v) == 0) goto next; alltypes |= ops[i].type; } /* all is matching ! */ break; next: ; } if (pa->sym == 0) { if (opcode >= TOK_ASM_first && opcode <= TOK_ASM_last) { int b; b = op0_codes[opcode - TOK_ASM_first]; if (b & 0xff00) g(b >> 8); g(b); return; } else if (opcode <= TOK_ASM_alllast) { tcc_error("bad operand with opcode '%s'", get_tok_str(opcode, NULL)); } else { /* Special case for cmovcc, we accept size suffixes but ignore them, but we don't want them to blow up our tables. */ TokenSym *ts = table_ident[opcode - TOK_IDENT]; if (ts->len >= 6 && strchr("wlq", ts->str[ts->len-1]) && !memcmp(ts->str, "cmov", 4)) { opcode = tok_alloc(ts->str, ts->len-1)->tok; goto again; } tcc_error("unknown opcode '%s'", ts->str); } } /* if the size is unknown, then evaluate it (OPC_B or OPC_WL case) */ autosize = NBWLX-1; #ifdef TCC_TARGET_X86_64 /* XXX the autosize should rather be zero, to not have to adjust this all the time. */ if ((pa->instr_type & OPC_BWLQ) == OPC_B) autosize = NBWLX-2; #endif if (s == autosize) { /* Check for register operands providing hints about the size. Start from the end, i.e. destination operands. This matters only for opcodes accepting different sized registers, lar and lsl are such opcodes. */ for(i = nb_ops - 1; s == autosize && i >= 0; i--) { if ((ops[i].type & OP_REG) && !(op_type[i] & (OP_CL | OP_DX))) s = reg_to_size[ops[i].type & OP_REG]; } if (s == autosize) { if ((opcode == TOK_ASM_push || opcode == TOK_ASM_pop) && (ops[0].type & (OP_SEG | OP_IM8S | OP_IM32))) s = 2; else if ((opcode == TOK_ASM_push || opcode == TOK_ASM_pop) && (ops[0].type & OP_EA)) s = NBWLX - 2; else tcc_error("cannot infer opcode suffix"); } } #ifdef TCC_TARGET_X86_64 /* Generate addr32 prefix if needed */ for(i = 0; i < nb_ops; i++) { if (ops[i].type & OP_EA32) { g(0x67); break; } } #endif /* generate data16 prefix if needed */ p66 = 0; if (s == 1) p66 = 1; else { /* accepting mmx+sse in all operands --> needs 0x66 to switch to sse mode. Accepting only sse in an operand --> is already SSE insn and needs 0x66/f2/f3 handling. */ for (i = 0; i < nb_ops; i++) if ((op_type[i] & (OP_MMX | OP_SSE)) == (OP_MMX | OP_SSE) && ops[i].type & OP_SSE) p66 = 1; } if (p66) g(0x66); #ifdef TCC_TARGET_X86_64 rex64 = 0; if (pa->instr_type & OPC_48) rex64 = 1; else if (s == 3 || (alltypes & OP_REG64)) { /* generate REX prefix */ int default64 = 0; for(i = 0; i < nb_ops; i++) { if (op_type[i] == OP_REG64 && pa->opcode != 0xb8) { /* If only 64bit regs are accepted in one operand this is a default64 instruction without need for REX prefixes, except for movabs(0xb8). */ default64 = 1; break; } } /* XXX find better encoding for the default64 instructions. */ if (((opcode != TOK_ASM_push && opcode != TOK_ASM_pop && opcode != TOK_ASM_pushw && opcode != TOK_ASM_pushl && opcode != TOK_ASM_pushq && opcode != TOK_ASM_popw && opcode != TOK_ASM_popl && opcode != TOK_ASM_popq && opcode != TOK_ASM_call && opcode != TOK_ASM_jmp)) && !default64) rex64 = 1; } #endif /* now generates the operation */ if (OPCT_IS(pa->instr_type, OPC_FWAIT)) g(0x9b); if (seg_prefix) g(seg_prefix); v = pa->opcode; if (pa->instr_type & OPC_0F) v = ((v & ~0xff) << 8) | 0x0f00 | (v & 0xff); if ((v == 0x69 || v == 0x6b) && nb_ops == 2) { /* kludge for imul $im, %reg */ nb_ops = 3; ops[2] = ops[1]; op_type[2] = op_type[1]; } else if (v == 0xcd && ops[0].e.v == 3 && !ops[0].e.sym) { v--; /* int $3 case */ nb_ops = 0; } else if ((v == 0x06 || v == 0x07)) { if (ops[0].reg >= 4) { /* push/pop %fs or %gs */ v = 0x0fa0 + (v - 0x06) + ((ops[0].reg - 4) << 3); } else { v += ops[0].reg << 3; } nb_ops = 0; } else if (v <= 0x05) { /* arith case */ v += ((opcode - TOK_ASM_addb) / NBWLX) << 3; } else if ((pa->instr_type & (OPCT_MASK | OPC_MODRM)) == OPC_FARITH) { /* fpu arith case */ v += ((opcode - pa->sym) / 6) << 3; } /* search which operand will be used for modrm */ modrm_index = -1; modreg_index = -1; if (pa->instr_type & OPC_MODRM) { if (!nb_ops) { /* A modrm opcode without operands is a special case (e.g. mfence). It has a group and acts as if there's an register operand 0 (ax). */ i = 0; ops[i].type = OP_REG; ops[i].reg = 0; goto modrm_found; } /* first look for an ea operand */ for(i = 0;i < nb_ops; i++) { if (op_type[i] & OP_EA) goto modrm_found; } /* then if not found, a register or indirection (shift instructions) */ for(i = 0;i < nb_ops; i++) { if (op_type[i] & (OP_REG | OP_MMX | OP_SSE | OP_INDIR)) goto modrm_found; } #ifdef ASM_DEBUG tcc_error("bad op table"); #endif modrm_found: modrm_index = i; /* if a register is used in another operand then it is used instead of group */ for(i = 0;i < nb_ops; i++) { int t = op_type[i]; if (i != modrm_index && (t & (OP_REG | OP_MMX | OP_SSE | OP_CR | OP_TR | OP_DB | OP_SEG))) { modreg_index = i; break; } } } #ifdef TCC_TARGET_X86_64 asm_rex (rex64, ops, nb_ops, op_type, modreg_index, modrm_index); #endif if (pa->instr_type & OPC_REG) { /* mov $im, %reg case */ if (v == 0xb0 && s >= 1) v += 7; for(i = 0; i < nb_ops; i++) { if (op_type[i] & (OP_REG | OP_ST)) { v += ops[i].reg; break; } } } if (pa->instr_type & OPC_B) v += s >= 1; if (nb_ops == 1 && pa->op_type[0] == OPT_DISP8) { ElfSym *esym; int jmp_disp; /* see if we can really generate the jump with a byte offset */ esym = elfsym(ops[0].e.sym); if (!esym || esym->st_shndx != cur_text_section->sh_num) goto no_short_jump; jmp_disp = ops[0].e.v + esym->st_value - ind - 2 - (v >= 0xff); if (jmp_disp == (int8_t)jmp_disp) { /* OK to generate jump */ ops[0].e.sym = 0; ops[0].e.v = jmp_disp; op_type[0] = OP_IM8S; } else { no_short_jump: /* long jump will be allowed. need to modify the opcode slightly */ if (v == 0xeb) /* jmp */ v = 0xe9; else if (v == 0x70) /* jcc */ v += 0x0f10; else tcc_error("invalid displacement"); } } if (OPCT_IS(pa->instr_type, OPC_TEST)) v += test_bits[opcode - pa->sym]; op1 = v >> 16; if (op1) g(op1); op1 = (v >> 8) & 0xff; if (op1) g(op1); g(v); if (OPCT_IS(pa->instr_type, OPC_SHIFT)) { reg = (opcode - pa->sym) / NBWLX; if (reg == 6) reg = 7; } else if (OPCT_IS(pa->instr_type, OPC_ARITH)) { reg = (opcode - pa->sym) / NBWLX; } else if (OPCT_IS(pa->instr_type, OPC_FARITH)) { reg = (opcode - pa->sym) / 6; } else { reg = (pa->instr_type >> OPC_GROUP_SHIFT) & 7; } pc = 0; if (pa->instr_type & OPC_MODRM) { /* if a register is used in another operand then it is used instead of group */ if (modreg_index >= 0) reg = ops[modreg_index].reg; pc = asm_modrm(reg, &ops[modrm_index]); } /* emit constants */ #ifndef TCC_TARGET_X86_64 if (!(pa->instr_type & OPC_0F) && (pa->opcode == 0x9a || pa->opcode == 0xea)) { /* ljmp or lcall kludge */ gen_expr32(&ops[1].e); if (ops[0].e.sym) tcc_error("cannot relocate"); gen_le16(ops[0].e.v); return; } #endif for(i = 0;i < nb_ops; i++) { v = op_type[i]; if (v & (OP_IM8 | OP_IM16 | OP_IM32 | OP_IM64 | OP_IM8S | OP_ADDR)) { /* if multiple sizes are given it means we must look at the op size */ if ((v | OP_IM8 | OP_IM64) == (OP_IM8 | OP_IM16 | OP_IM32 | OP_IM64)) { if (s == 0) v = OP_IM8; else if (s == 1) v = OP_IM16; else if (s == 2 || (v & OP_IM64) == 0) v = OP_IM32; else v = OP_IM64; } if ((v & (OP_IM8 | OP_IM8S | OP_IM16)) && ops[i].e.sym) tcc_error("cannot relocate"); if (v & (OP_IM8 | OP_IM8S)) { g(ops[i].e.v); } else if (v & OP_IM16) { gen_le16(ops[i].e.v); #ifdef TCC_TARGET_X86_64 } else if (v & OP_IM64) { gen_expr64(&ops[i].e); #endif } else if (pa->op_type[i] == OPT_DISP || pa->op_type[i] == OPT_DISP8) { gen_disp32(&ops[i].e); } else { gen_expr32(&ops[i].e); } } } /* after immediate operands, adjust pc-relative address */ if (pc) add32le(cur_text_section->data + pc - 4, pc - ind); } /* return the constraint priority (we allocate first the lowest numbered constraints) */ static inline int constraint_priority(const char *str) { int priority, c, pr; /* we take the lowest priority */ priority = 0; for(;;) { c = *str; if (c == '\0') break; str++; switch(c) { case 'A': pr = 0; break; case 'a': case 'b': case 'c': case 'd': case 'S': case 'D': pr = 1; break; case 'q': pr = 2; break; case 'r': case 'R': case 'p': pr = 3; break; case 'N': case 'M': case 'I': case 'e': case 'i': case 'm': case 'g': pr = 4; break; default: tcc_error("unknown constraint '%c'", c); pr = 0; } if (pr > priority) priority = pr; } return priority; } static const char *skip_constraint_modifiers(const char *p) { while (*p == '=' || *p == '&' || *p == '+' || *p == '%') p++; return p; } /* If T (a token) is of the form "%reg" returns the register number and type, otherwise return -1. */ ST_FUNC int asm_parse_regvar (int t) { const char *s; Operand op; if (t < TOK_IDENT) return -1; s = table_ident[t - TOK_IDENT]->str; if (s[0] != '%') return -1; t = tok_alloc(s+1, strlen(s)-1)->tok; unget_tok(t); unget_tok('%'); parse_operand(tcc_state, &op); /* Accept only integer regs for now. */ if (op.type & OP_REG) return op.reg; else return -1; } #define REG_OUT_MASK 0x01 #define REG_IN_MASK 0x02 #define is_reg_allocated(reg) (regs_allocated[reg] & reg_mask) ST_FUNC void asm_compute_constraints(ASMOperand *operands, int nb_operands, int nb_outputs, const uint8_t *clobber_regs, int *pout_reg) { ASMOperand *op; int sorted_op[MAX_ASM_OPERANDS]; int i, j, k, p1, p2, tmp, reg, c, reg_mask; const char *str; uint8_t regs_allocated[NB_ASM_REGS]; /* init fields */ for(i=0;i<nb_operands;i++) { op = &operands[i]; op->input_index = -1; op->ref_index = -1; op->reg = -1; op->is_memory = 0; op->is_rw = 0; } /* compute constraint priority and evaluate references to output constraints if input constraints */ for(i=0;i<nb_operands;i++) { op = &operands[i]; str = op->constraint; str = skip_constraint_modifiers(str); if (isnum(*str) || *str == '[') { /* this is a reference to another constraint */ k = find_constraint(operands, nb_operands, str, NULL); if ((unsigned)k >= i || i < nb_outputs) tcc_error("invalid reference in constraint %d ('%s')", i, str); op->ref_index = k; if (operands[k].input_index >= 0) tcc_error("cannot reference twice the same operand"); operands[k].input_index = i; op->priority = 5; } else if ((op->vt->r & VT_VALMASK) == VT_LOCAL && op->vt->sym && (reg = op->vt->sym->r & VT_VALMASK) < VT_CONST) { op->priority = 1; op->reg = reg; } else { op->priority = constraint_priority(str); } } /* sort operands according to their priority */ for(i=0;i<nb_operands;i++) sorted_op[i] = i; for(i=0;i<nb_operands - 1;i++) { for(j=i+1;j<nb_operands;j++) { p1 = operands[sorted_op[i]].priority; p2 = operands[sorted_op[j]].priority; if (p2 < p1) { tmp = sorted_op[i]; sorted_op[i] = sorted_op[j]; sorted_op[j] = tmp; } } } for(i = 0;i < NB_ASM_REGS; i++) { if (clobber_regs[i]) regs_allocated[i] = REG_IN_MASK | REG_OUT_MASK; else regs_allocated[i] = 0; } /* esp cannot be used */ regs_allocated[4] = REG_IN_MASK | REG_OUT_MASK; /* ebp cannot be used yet */ regs_allocated[5] = REG_IN_MASK | REG_OUT_MASK; /* allocate registers and generate corresponding asm moves */ for(i=0;i<nb_operands;i++) { j = sorted_op[i]; op = &operands[j]; str = op->constraint; /* no need to allocate references */ if (op->ref_index >= 0) continue; /* select if register is used for output, input or both */ if (op->input_index >= 0) { reg_mask = REG_IN_MASK | REG_OUT_MASK; } else if (j < nb_outputs) { reg_mask = REG_OUT_MASK; } else { reg_mask = REG_IN_MASK; } if (op->reg >= 0) { if (is_reg_allocated(op->reg)) tcc_error("asm regvar requests register that's taken already"); reg = op->reg; goto reg_found; } try_next: c = *str++; switch(c) { case '=': goto try_next; case '+': op->is_rw = 1; /* FALL THRU */ case '&': if (j >= nb_outputs) tcc_error("'%c' modifier can only be applied to outputs", c); reg_mask = REG_IN_MASK | REG_OUT_MASK; goto try_next; case 'A': /* allocate both eax and edx */ if (is_reg_allocated(TREG_XAX) || is_reg_allocated(TREG_XDX)) goto try_next; op->is_llong = 1; op->reg = TREG_XAX; regs_allocated[TREG_XAX] |= reg_mask; regs_allocated[TREG_XDX] |= reg_mask; break; case 'a': reg = TREG_XAX; goto alloc_reg; case 'b': reg = 3; goto alloc_reg; case 'c': reg = TREG_XCX; goto alloc_reg; case 'd': reg = TREG_XDX; goto alloc_reg; case 'S': reg = 6; goto alloc_reg; case 'D': reg = 7; alloc_reg: if (is_reg_allocated(reg)) goto try_next; goto reg_found; case 'q': /* eax, ebx, ecx or edx */ for(reg = 0; reg < 4; reg++) { if (!is_reg_allocated(reg)) goto reg_found; } goto try_next; case 'r': case 'R': case 'p': /* A general address, for x86(64) any register is acceptable*/ /* any general register */ for(reg = 0; reg < 8; reg++) { if (!is_reg_allocated(reg)) goto reg_found; } goto try_next; reg_found: /* now we can reload in the register */ op->is_llong = 0; op->reg = reg; regs_allocated[reg] |= reg_mask; break; case 'e': case 'i': if (!((op->vt->r & (VT_VALMASK | VT_LVAL)) == VT_CONST)) goto try_next; break; case 'I': case 'N': case 'M': if (!((op->vt->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST)) goto try_next; break; case 'm': case 'g': /* nothing special to do because the operand is already in memory, except if the pointer itself is stored in a memory variable (VT_LLOCAL case) */ /* XXX: fix constant case */ /* if it is a reference to a memory zone, it must lie in a register, so we reserve the register in the input registers and a load will be generated later */ if (j < nb_outputs || c == 'm') { if ((op->vt->r & VT_VALMASK) == VT_LLOCAL) { /* any general register */ for(reg = 0; reg < 8; reg++) { if (!(regs_allocated[reg] & REG_IN_MASK)) goto reg_found1; } goto try_next; reg_found1: /* now we can reload in the register */ regs_allocated[reg] |= REG_IN_MASK; op->reg = reg; op->is_memory = 1; } } break; default: tcc_error("asm constraint %d ('%s') could not be satisfied", j, op->constraint); break; } /* if a reference is present for that operand, we assign it too */ if (op->input_index >= 0) { operands[op->input_index].reg = op->reg; operands[op->input_index].is_llong = op->is_llong; } } /* compute out_reg. It is used to store outputs registers to memory locations references by pointers (VT_LLOCAL case) */ *pout_reg = -1; for(i=0;i<nb_operands;i++) { op = &operands[i]; if (op->reg >= 0 && (op->vt->r & VT_VALMASK) == VT_LLOCAL && !op->is_memory) { for(reg = 0; reg < 8; reg++) { if (!(regs_allocated[reg] & REG_OUT_MASK)) goto reg_found2; } tcc_error("could not find free output register for reloading"); reg_found2: *pout_reg = reg; break; } } /* print sorted constraints */ #ifdef ASM_DEBUG for(i=0;i<nb_operands;i++) { j = sorted_op[i]; op = &operands[j]; printf("%%%d [%s]: \"%s\" r=0x%04x reg=%d\n", j, op->id ? get_tok_str(op->id, NULL) : "", op->constraint, op->vt->r, op->reg); } if (*pout_reg >= 0) printf("out_reg=%d\n", *pout_reg); #endif } ST_FUNC void subst_asm_operand(CString *add_str, SValue *sv, int modifier) { int r, reg, size, val; char buf[64]; r = sv->r; if ((r & VT_VALMASK) == VT_CONST) { if (!(r & VT_LVAL) && modifier != 'c' && modifier != 'n' && modifier != 'P') cstr_ccat(add_str, '$'); if (r & VT_SYM) { const char *name = get_tok_str(sv->sym->v, NULL); if (sv->sym->v >= SYM_FIRST_ANOM) { /* In case of anonymous symbols ("L.42", used for static data labels) we can't find them in the C symbol table when later looking up this name. So enter them now into the asm label list when we still know the symbol. */ get_asm_sym(tok_alloc(name, strlen(name))->tok, sv->sym); } cstr_cat(add_str, name, -1); if ((uint32_t)sv->c.i == 0) goto no_offset; cstr_ccat(add_str, '+'); } val = sv->c.i; if (modifier == 'n') val = -val; snprintf(buf, sizeof(buf), "%d", (int)sv->c.i); cstr_cat(add_str, buf, -1); no_offset:; #ifdef TCC_TARGET_X86_64 if (r & VT_LVAL) cstr_cat(add_str, "(%rip)", -1); #endif } else if ((r & VT_VALMASK) == VT_LOCAL) { #ifdef TCC_TARGET_X86_64 snprintf(buf, sizeof(buf), "%d(%%rbp)", (int)sv->c.i); #else snprintf(buf, sizeof(buf), "%d(%%ebp)", (int)sv->c.i); #endif cstr_cat(add_str, buf, -1); } else if (r & VT_LVAL) { reg = r & VT_VALMASK; if (reg >= VT_CONST) tcc_error("internal compiler error"); snprintf(buf, sizeof(buf), "(%%%s)", #ifdef TCC_TARGET_X86_64 get_tok_str(TOK_ASM_rax + reg, NULL) #else get_tok_str(TOK_ASM_eax + reg, NULL) #endif ); cstr_cat(add_str, buf, -1); } else { /* register case */ reg = r & VT_VALMASK; if (reg >= VT_CONST) tcc_error("internal compiler error"); /* choose register operand size */ if ((sv->type.t & VT_BTYPE) == VT_BYTE || (sv->type.t & VT_BTYPE) == VT_BOOL) size = 1; else if ((sv->type.t & VT_BTYPE) == VT_SHORT) size = 2; #ifdef TCC_TARGET_X86_64 else if ((sv->type.t & VT_BTYPE) == VT_LLONG || (sv->type.t & VT_BTYPE) == VT_PTR) size = 8; #endif else size = 4; if (size == 1 && reg >= 4) size = 4; if (modifier == 'b') { if (reg >= 4) tcc_error("cannot use byte register"); size = 1; } else if (modifier == 'h') { if (reg >= 4) tcc_error("cannot use byte register"); size = -1; } else if (modifier == 'w') { size = 2; } else if (modifier == 'k') { size = 4; #ifdef TCC_TARGET_X86_64 } else if (modifier == 'q') { size = 8; #endif } switch(size) { case -1: reg = TOK_ASM_ah + reg; break; case 1: reg = TOK_ASM_al + reg; break; case 2: reg = TOK_ASM_ax + reg; break; default: reg = TOK_ASM_eax + reg; break; #ifdef TCC_TARGET_X86_64 case 8: reg = TOK_ASM_rax + reg; break; #endif } snprintf(buf, sizeof(buf), "%%%s", get_tok_str(reg, NULL)); cstr_cat(add_str, buf, -1); } } /* generate prolog and epilog code for asm statement */ ST_FUNC void asm_gen_code(ASMOperand *operands, int nb_operands, int nb_outputs, int is_output, uint8_t *clobber_regs, int out_reg) { uint8_t regs_allocated[NB_ASM_REGS]; ASMOperand *op; int i, reg; /* Strictly speaking %Xbp and %Xsp should be included in the call-preserved registers, but currently it doesn't matter. */ #ifdef TCC_TARGET_X86_64 #ifdef TCC_TARGET_PE static uint8_t reg_saved[] = { 3, 6, 7, 12, 13, 14, 15 }; #else static uint8_t reg_saved[] = { 3, 12, 13, 14, 15 }; #endif #else static uint8_t reg_saved[] = { 3, 6, 7 }; #endif /* mark all used registers */ memcpy(regs_allocated, clobber_regs, sizeof(regs_allocated)); for(i = 0; i < nb_operands;i++) { op = &operands[i]; if (op->reg >= 0) regs_allocated[op->reg] = 1; } if (!is_output) { /* generate reg save code */ for(i = 0; i < sizeof(reg_saved)/sizeof(reg_saved[0]); i++) { reg = reg_saved[i]; if (regs_allocated[reg]) { if (reg >= 8) g(0x41), reg-=8; g(0x50 + reg); } } /* generate load code */ for(i = 0; i < nb_operands; i++) { op = &operands[i]; if (op->reg >= 0) { if ((op->vt->r & VT_VALMASK) == VT_LLOCAL && op->is_memory) { /* memory reference case (for both input and output cases) */ SValue sv; sv = *op->vt; sv.r = (sv.r & ~VT_VALMASK) | VT_LOCAL | VT_LVAL; sv.type.t = VT_PTR; load(op->reg, &sv); } else if (i >= nb_outputs || op->is_rw) { /* load value in register */ load(op->reg, op->vt); if (op->is_llong) { SValue sv; sv = *op->vt; sv.c.i += 4; load(TREG_XDX, &sv); } } } } } else { /* generate save code */ for(i = 0 ; i < nb_outputs; i++) { op = &operands[i]; if (op->reg >= 0) { if ((op->vt->r & VT_VALMASK) == VT_LLOCAL) { if (!op->is_memory) { SValue sv; sv = *op->vt; sv.r = (sv.r & ~VT_VALMASK) | VT_LOCAL; sv.type.t = VT_PTR; load(out_reg, &sv); sv = *op->vt; sv.r = (sv.r & ~VT_VALMASK) | out_reg; store(op->reg, &sv); } } else { store(op->reg, op->vt); if (op->is_llong) { SValue sv; sv = *op->vt; sv.c.i += 4; store(TREG_XDX, &sv); } } } } /* generate reg restore code */ for(i = sizeof(reg_saved)/sizeof(reg_saved[0]) - 1; i >= 0; i--) { reg = reg_saved[i]; if (regs_allocated[reg]) { if (reg >= 8) g(0x41), reg-=8; g(0x58 + reg); } } } } ST_FUNC void asm_clobber(uint8_t *clobber_regs, const char *str) { int reg; TokenSym *ts; #ifdef TCC_TARGET_X86_64 unsigned int type; #endif if (!strcmp(str, "memory") || !strcmp(str, "cc") || !strcmp(str, "flags")) return; ts = tok_alloc(str, strlen(str)); reg = ts->tok; if (reg >= TOK_ASM_eax && reg <= TOK_ASM_edi) { reg -= TOK_ASM_eax; } else if (reg >= TOK_ASM_ax && reg <= TOK_ASM_di) { reg -= TOK_ASM_ax; #ifdef TCC_TARGET_X86_64 } else if (reg >= TOK_ASM_rax && reg <= TOK_ASM_rdi) { reg -= TOK_ASM_rax; } else if ((reg = asm_parse_numeric_reg(reg, &type)) >= 0) { ; #endif } else { tcc_error("invalid clobber register '%s'", str); } clobber_regs[reg] = 1; }
/* -------------------------------------------------------------- */ /* * TCC - Tiny C Compiler * * tcctools.c - extra tools and and -m32/64 support * */ /* -------------------------------------------------------------- */ /* * This program is for making libtcc1.a without ar * tiny_libmaker - tiny elf lib maker * usage: tiny_libmaker [lib] files... * Copyright (c) 2007 Timppa * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "tcc.h" //#define ARMAG "!<arch>\n" #define ARFMAG "`\n" typedef struct { char ar_name[16]; char ar_date[12]; char ar_uid[6]; char ar_gid[6]; char ar_mode[8]; char ar_size[10]; char ar_fmag[2]; } ArHdr; static unsigned long le2belong(unsigned long ul) { return ((ul & 0xFF0000)>>8)+((ul & 0xFF000000)>>24) + ((ul & 0xFF)<<24)+((ul & 0xFF00)<<8); } /* Returns 1 if s contains any of the chars of list, else 0 */ static int contains_any(const char *s, const char *list) { const char *l; for (; *s; s++) { for (l = list; *l; l++) { if (*s == *l) return 1; } } return 0; } static int ar_usage(int ret) { fprintf(stderr, "usage: tcc -ar [rcsv] lib file...\n"); fprintf(stderr, "create library ([abdioptxN] not supported).\n"); return ret; } ST_FUNC int tcc_tool_ar(TCCState *s1, int argc, char **argv) { static ArHdr arhdr = { "/ ", " ", "0 ", "0 ", "0 ", " ", ARFMAG }; static ArHdr arhdro = { " ", " ", "0 ", "0 ", "0 ", " ", ARFMAG }; FILE *fi, *fh = NULL, *fo = NULL; ElfW(Ehdr) *ehdr; ElfW(Shdr) *shdr; ElfW(Sym) *sym; int i, fsize, i_lib, i_obj; char *buf, *shstr, *symtab = NULL, *strtab = NULL; int symtabsize = 0;//, strtabsize = 0; char *anames = NULL; int *afpos = NULL; int istrlen, strpos = 0, fpos = 0, funccnt = 0, funcmax, hofs; char tfile[260], stmp[20]; char *file, *name; int ret = 2; const char *ops_conflict = "habdioptxN"; // unsupported but destructive if ignored. int verbose = 0; i_lib = 0; i_obj = 0; // will hold the index of the lib and first obj for (i = 1; i < argc; i++) { const char *a = argv[i]; if (*a == '-' && strstr(a, ".")) ret = 1; // -x.y is always invalid (same as gnu ar) if ((*a == '-') || (i == 1 && !strstr(a, "."))) { // options argument if (contains_any(a, ops_conflict)) ret = 1; if (strstr(a, "v")) verbose = 1; } else { // lib or obj files: don't abort - keep validating all args. if (!i_lib) // first file is the lib i_lib = i; else if (!i_obj) // second file is the first obj i_obj = i; } } if (!i_obj) // i_obj implies also i_lib. we require both. ret = 1; if (ret == 1) return ar_usage(ret); if ((fh = fopen(argv[i_lib], "wb")) == NULL) { fprintf(stderr, "tcc: ar: can't open file %s \n", argv[i_lib]); goto the_end; } sprintf(tfile, "%s.tmp", argv[i_lib]); if ((fo = fopen(tfile, "wb+")) == NULL) { fprintf(stderr, "tcc: ar: can't create temporary file %s\n", tfile); goto the_end; } funcmax = 250; afpos = tcc_realloc(NULL, funcmax * sizeof *afpos); // 250 func memcpy(&arhdro.ar_mode, "100666", 6); // i_obj = first input object file while (i_obj < argc) { if (*argv[i_obj] == '-') { // by now, all options start with '-' i_obj++; continue; } if ((fi = fopen(argv[i_obj], "rb")) == NULL) { fprintf(stderr, "tcc: ar: can't open file %s \n", argv[i_obj]); goto the_end; } if (verbose) printf("a - %s\n", argv[i_obj]); fseek(fi, 0, SEEK_END); fsize = ftell(fi); fseek(fi, 0, SEEK_SET); buf = tcc_malloc(fsize + 1); fread(buf, fsize, 1, fi); fclose(fi); // elf header ehdr = (ElfW(Ehdr) *)buf; if (ehdr->e_ident[4] != ELFCLASSW) { fprintf(stderr, "tcc: ar: Unsupported Elf Class: %s\n", argv[i_obj]); goto the_end; } shdr = (ElfW(Shdr) *) (buf + ehdr->e_shoff + ehdr->e_shstrndx * ehdr->e_shentsize); shstr = (char *)(buf + shdr->sh_offset); for (i = 0; i < ehdr->e_shnum; i++) { shdr = (ElfW(Shdr) *) (buf + ehdr->e_shoff + i * ehdr->e_shentsize); if (!shdr->sh_offset) continue; if (shdr->sh_type == SHT_SYMTAB) { symtab = (char *)(buf + shdr->sh_offset); symtabsize = shdr->sh_size; } if (shdr->sh_type == SHT_STRTAB) { if (!strcmp(shstr + shdr->sh_name, ".strtab")) { strtab = (char *)(buf + shdr->sh_offset); //strtabsize = shdr->sh_size; } } } if (symtab && symtabsize) { int nsym = symtabsize / sizeof(ElfW(Sym)); //printf("symtab: info size shndx name\n"); for (i = 1; i < nsym; i++) { sym = (ElfW(Sym) *) (symtab + i * sizeof(ElfW(Sym))); if (sym->st_shndx && (sym->st_info == 0x10 || sym->st_info == 0x11 || sym->st_info == 0x12 )) { //printf("symtab: %2Xh %4Xh %2Xh %s\n", sym->st_info, sym->st_size, sym->st_shndx, strtab + sym->st_name); istrlen = strlen(strtab + sym->st_name)+1; anames = tcc_realloc(anames, strpos+istrlen); strcpy(anames + strpos, strtab + sym->st_name); strpos += istrlen; if (++funccnt >= funcmax) { funcmax += 250; afpos = tcc_realloc(afpos, funcmax * sizeof *afpos); // 250 func more } afpos[funccnt] = fpos; } } } file = argv[i_obj]; for (name = strchr(file, 0); name > file && name[-1] != '/' && name[-1] != '\\'; --name); istrlen = strlen(name); if (istrlen >= sizeof(arhdro.ar_name)) istrlen = sizeof(arhdro.ar_name) - 1; memset(arhdro.ar_name, ' ', sizeof(arhdro.ar_name)); memcpy(arhdro.ar_name, name, istrlen); arhdro.ar_name[istrlen] = '/'; sprintf(stmp, "%-10d", fsize); memcpy(&arhdro.ar_size, stmp, 10); fwrite(&arhdro, sizeof(arhdro), 1, fo); fwrite(buf, fsize, 1, fo); tcc_free(buf); i_obj++; fpos += (fsize + sizeof(arhdro)); } hofs = 8 + sizeof(arhdr) + strpos + (funccnt+1) * sizeof(int); fpos = 0; if ((hofs & 1)) // align hofs++, fpos = 1; // write header fwrite("!<arch>\n", 8, 1, fh); sprintf(stmp, "%-10d", (int)(strpos + (funccnt+1) * sizeof(int))); memcpy(&arhdr.ar_size, stmp, 10); fwrite(&arhdr, sizeof(arhdr), 1, fh); afpos[0] = le2belong(funccnt); for (i=1; i<=funccnt; i++) afpos[i] = le2belong(afpos[i] + hofs); fwrite(afpos, (funccnt+1) * sizeof(int), 1, fh); fwrite(anames, strpos, 1, fh); if (fpos) fwrite("", 1, 1, fh); // write objects fseek(fo, 0, SEEK_END); fsize = ftell(fo); fseek(fo, 0, SEEK_SET); buf = tcc_malloc(fsize + 1); fread(buf, fsize, 1, fo); fwrite(buf, fsize, 1, fh); tcc_free(buf); ret = 0; the_end: if (anames) tcc_free(anames); if (afpos) tcc_free(afpos); if (fh) fclose(fh); if (fo) fclose(fo), remove(tfile); return ret; } /* -------------------------------------------------------------- */ /* * tiny_impdef creates an export definition file (.def) from a dll * on MS-Windows. Usage: tiny_impdef library.dll [-o outputfile]" * * Copyright (c) 2005,2007 grischka * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifdef TCC_TARGET_PE ST_FUNC int tcc_tool_impdef(TCCState *s1, int argc, char **argv) { int ret, v, i; char infile[260]; char outfile[260]; const char *file; char *p, *q; FILE *fp, *op; #ifdef _WIN32 char path[260]; #endif infile[0] = outfile[0] = 0; fp = op = NULL; ret = 1; p = NULL; v = 0; for (i = 1; i < argc; ++i) { const char *a = argv[i]; if ('-' == a[0]) { if (0 == strcmp(a, "-v")) { v = 1; } else if (0 == strcmp(a, "-o")) { if (++i == argc) goto usage; strcpy(outfile, argv[i]); } else goto usage; } else if (0 == infile[0]) strcpy(infile, a); else goto usage; } if (0 == infile[0]) { usage: fprintf(stderr, "usage: tcc -impdef library.dll [-v] [-o outputfile]\n" "create export definition file (.def) from dll\n" ); goto the_end; } if (0 == outfile[0]) { strcpy(outfile, tcc_basename(infile)); q = strrchr(outfile, '.'); if (NULL == q) q = strchr(outfile, 0); strcpy(q, ".def"); } file = infile; #ifdef _WIN32 if (SearchPath(NULL, file, ".dll", sizeof path, path, NULL)) file = path; #endif ret = tcc_get_dllexports(file, &p); if (ret || !p) { fprintf(stderr, "tcc: impdef: %s '%s'\n", ret == -1 ? "can't find file" : ret == 1 ? "can't read symbols" : ret == 0 ? "no symbols found in" : "unknown file type", file); ret = 1; goto the_end; } if (v) printf("-> %s\n", file); op = fopen(outfile, "wb"); if (NULL == op) { fprintf(stderr, "tcc: impdef: could not create output file: %s\n", outfile); goto the_end; } fprintf(op, "LIBRARY %s\n\nEXPORTS\n", tcc_basename(file)); for (q = p, i = 0; *q; ++i) { fprintf(op, "%s\n", q); q += strlen(q) + 1; } if (v) printf("<- %s (%d symbol%s)\n", outfile, i, &"s"[i<2]); ret = 0; the_end: /* cannot free memory received from tcc_get_dllexports if it came from a dll */ /* if (p) tcc_free(p); */ if (fp) fclose(fp); if (op) fclose(op); return ret; } #endif /* TCC_TARGET_PE */ /* -------------------------------------------------------------- */ /* * TCC - Tiny C Compiler * * Copyright (c) 2001-2004 Fabrice Bellard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* re-execute the i386/x86_64 cross-compilers with tcc -m32/-m64: */ #if !defined TCC_TARGET_I386 && !defined TCC_TARGET_X86_64 ST_FUNC void tcc_tool_cross(TCCState *s, char **argv, int option) { tcc_error("-m%d not implemented.", option); } #else #ifdef _WIN32 #include <process.h> static char *str_replace(const char *str, const char *p, const char *r) { const char *s, *s0; char *d, *d0; int sl, pl, rl; sl = strlen(str); pl = strlen(p); rl = strlen(r); for (d0 = NULL;; d0 = tcc_malloc(sl + 1)) { for (d = d0, s = str; s0 = s, s = strstr(s, p), s; s += pl) { if (d) { memcpy(d, s0, sl = s - s0), d += sl; memcpy(d, r, rl), d += rl; } else sl += rl - pl; } if (d) { strcpy(d, s0); return d0; } } } static int execvp_win32(const char *prog, char **argv) { int ret; char **p; /* replace all " by \" */ for (p = argv; *p; ++p) if (strchr(*p, '"')) *p = str_replace(*p, "\"", "\\\""); ret = _spawnvp(P_NOWAIT, prog, (const char *const*)argv); if (-1 == ret) return ret; _cwait(&ret, ret, WAIT_CHILD); exit(ret); } #define execvp execvp_win32 #endif /* _WIN32 */ ST_FUNC void tcc_tool_cross(TCCState *s, char **argv, int target) { char program[4096]; char *a0 = argv[0]; int prefix = tcc_basename(a0) - a0; snprintf(program, sizeof program, "%.*s%s" #ifdef TCC_TARGET_PE "-win32" #endif "-tcc" #ifdef _WIN32 ".exe" #endif , prefix, a0, target == 64 ? "x86_64" : "i386"); if (strcmp(a0, program)) execvp(argv[0] = program, argv); tcc_error("could not run '%s'", program); } #endif /* TCC_TARGET_I386 && TCC_TARGET_X86_64 */ /* -------------------------------------------------------------- */ /* enable commandline wildcard expansion (tcc -o x.exe *.c) */ #ifdef _WIN32 int _CRT_glob = 1; #ifndef _CRT_glob int _dowildcard = 1; #endif #endif /* -------------------------------------------------------------- */ /* generate xxx.d file */ ST_FUNC void gen_makedeps(TCCState *s, const char *target, const char *filename) { FILE *depout; char buf[1024]; int i; if (!filename) { /* compute filename automatically: dir/file.o -> dir/file.d */ snprintf(buf, sizeof buf, "%.*s.d", (int)(tcc_fileextension(target) - target), target); filename = buf; } if (s->verbose) printf("<- %s\n", filename); /* XXX return err codes instead of error() ? */ depout = fopen(filename, "w"); if (!depout) tcc_error("could not open '%s'", filename); fprintf(depout, "%s: \\\n", target); for (i=0; i<s->nb_target_deps; ++i) fprintf(depout, " %s \\\n", s->target_deps[i]); fprintf(depout, "\n"); fclose(depout); } /* -------------------------------------------------------------- */
/* TCC runtime library. Parts of this code are (c) 2002 Fabrice Bellard Copyright (C) 1987, 1988, 1992, 1994, 1995 Free Software Foundation, Inc. This file is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. In addition to the permissions in the GNU General Public License, the Free Software Foundation gives you unlimited permission to link the compiled version of this file into combinations with other programs, and to distribute those combinations without any restriction coming from the use of this file. (The General Public License restrictions do apply in other respects; for example, they cover modification of the file, and distribution when not linked into a combine executable.) This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #define W_TYPE_SIZE 32 #define BITS_PER_UNIT 8 typedef int Wtype; typedef unsigned int UWtype; typedef unsigned int USItype; typedef long long DWtype; typedef unsigned long long UDWtype; struct DWstruct { Wtype low, high; }; typedef union { struct DWstruct s; DWtype ll; } DWunion; typedef long double XFtype; #define WORD_SIZE (sizeof (Wtype) * BITS_PER_UNIT) #define HIGH_WORD_COEFF (((UDWtype) 1) << WORD_SIZE) /* the following deal with IEEE single-precision numbers */ #define EXCESS 126 #define SIGNBIT 0x80000000 #define HIDDEN (1 << 23) #define SIGN(fp) ((fp) & SIGNBIT) #define EXP(fp) (((fp) >> 23) & 0xFF) #define MANT(fp) (((fp) & 0x7FFFFF) | HIDDEN) #define PACK(s,e,m) ((s) | ((e) << 23) | (m)) /* the following deal with IEEE double-precision numbers */ #define EXCESSD 1022 #define HIDDEND (1 << 20) #define EXPD(fp) (((fp.l.upper) >> 20) & 0x7FF) #define SIGND(fp) ((fp.l.upper) & SIGNBIT) #define MANTD(fp) (((((fp.l.upper) & 0xFFFFF) | HIDDEND) << 10) | \ (fp.l.lower >> 22)) #define HIDDEND_LL ((long long)1 << 52) #define MANTD_LL(fp) ((fp.ll & (HIDDEND_LL-1)) | HIDDEND_LL) #define PACKD_LL(s,e,m) (((long long)((s)+((e)<<20))<<32)|(m)) /* the following deal with x86 long double-precision numbers */ #define EXCESSLD 16382 #define EXPLD(fp) (fp.l.upper & 0x7fff) #define SIGNLD(fp) ((fp.l.upper) & 0x8000) /* only for x86 */ union ldouble_long { long double ld; struct { unsigned long long lower; unsigned short upper; } l; }; union double_long { double d; #if 1 struct { unsigned int lower; int upper; } l; #else struct { int upper; unsigned int lower; } l; #endif long long ll; }; union float_long { float f; unsigned int l; }; /* XXX: we don't support several builtin supports for now */ #if !defined __x86_64__ && !defined __arm__ /* XXX: use gcc/tcc intrinsic ? */ #if defined __i386__ #define sub_ddmmss(sh, sl, ah, al, bh, bl) \ __asm__ ("subl %5,%1\n\tsbbl %3,%0" \ : "=r" ((USItype) (sh)), \ "=&r" ((USItype) (sl)) \ : "0" ((USItype) (ah)), \ "g" ((USItype) (bh)), \ "1" ((USItype) (al)), \ "g" ((USItype) (bl))) #define umul_ppmm(w1, w0, u, v) \ __asm__ ("mull %3" \ : "=a" ((USItype) (w0)), \ "=d" ((USItype) (w1)) \ : "%0" ((USItype) (u)), \ "rm" ((USItype) (v))) #define udiv_qrnnd(q, r, n1, n0, dv) \ __asm__ ("divl %4" \ : "=a" ((USItype) (q)), \ "=d" ((USItype) (r)) \ : "0" ((USItype) (n0)), \ "1" ((USItype) (n1)), \ "rm" ((USItype) (dv))) #define count_leading_zeros(count, x) \ do { \ USItype __cbtmp; \ __asm__ ("bsrl %1,%0" \ : "=r" (__cbtmp) : "rm" ((USItype) (x))); \ (count) = __cbtmp ^ 31; \ } while (0) #else #error unsupported CPU type #endif /* most of this code is taken from libgcc2.c from gcc */ static UDWtype __udivmoddi4 (UDWtype n, UDWtype d, UDWtype *rp) { DWunion ww; DWunion nn, dd; DWunion rr; UWtype d0, d1, n0, n1, n2; UWtype q0, q1; UWtype b, bm; nn.ll = n; dd.ll = d; d0 = dd.s.low; d1 = dd.s.high; n0 = nn.s.low; n1 = nn.s.high; #if !defined(UDIV_NEEDS_NORMALIZATION) if (d1 == 0) { if (d0 > n1) { /* 0q = nn / 0D */ udiv_qrnnd (q0, n0, n1, n0, d0); q1 = 0; /* Remainder in n0. */ } else { /* qq = NN / 0d */ if (d0 == 0) d0 = 1 / d0; /* Divide intentionally by zero. */ udiv_qrnnd (q1, n1, 0, n1, d0); udiv_qrnnd (q0, n0, n1, n0, d0); /* Remainder in n0. */ } if (rp != 0) { rr.s.low = n0; rr.s.high = 0; *rp = rr.ll; } } #else /* UDIV_NEEDS_NORMALIZATION */ if (d1 == 0) { if (d0 > n1) { /* 0q = nn / 0D */ count_leading_zeros (bm, d0); if (bm != 0) { /* Normalize, i.e. make the most significant bit of the denominator set. */ d0 = d0 << bm; n1 = (n1 << bm) | (n0 >> (W_TYPE_SIZE - bm)); n0 = n0 << bm; } udiv_qrnnd (q0, n0, n1, n0, d0); q1 = 0; /* Remainder in n0 >> bm. */ } else { /* qq = NN / 0d */ if (d0 == 0) d0 = 1 / d0; /* Divide intentionally by zero. */ count_leading_zeros (bm, d0); if (bm == 0) { /* From (n1 >= d0) /\ (the most significant bit of d0 is set), conclude (the most significant bit of n1 is set) /\ (the leading quotient digit q1 = 1). This special case is necessary, not an optimization. (Shifts counts of W_TYPE_SIZE are undefined.) */ n1 -= d0; q1 = 1; } else { /* Normalize. */ b = W_TYPE_SIZE - bm; d0 = d0 << bm; n2 = n1 >> b; n1 = (n1 << bm) | (n0 >> b); n0 = n0 << bm; udiv_qrnnd (q1, n1, n2, n1, d0); } /* n1 != d0... */ udiv_qrnnd (q0, n0, n1, n0, d0); /* Remainder in n0 >> bm. */ } if (rp != 0) { rr.s.low = n0 >> bm; rr.s.high = 0; *rp = rr.ll; } } #endif /* UDIV_NEEDS_NORMALIZATION */ else { if (d1 > n1) { /* 00 = nn / DD */ q0 = 0; q1 = 0; /* Remainder in n1n0. */ if (rp != 0) { rr.s.low = n0; rr.s.high = n1; *rp = rr.ll; } } else { /* 0q = NN / dd */ count_leading_zeros (bm, d1); if (bm == 0) { /* From (n1 >= d1) /\ (the most significant bit of d1 is set), conclude (the most significant bit of n1 is set) /\ (the quotient digit q0 = 0 or 1). This special case is necessary, not an optimization. */ /* The condition on the next line takes advantage of that n1 >= d1 (true due to program flow). */ if (n1 > d1 || n0 >= d0) { q0 = 1; sub_ddmmss (n1, n0, n1, n0, d1, d0); } else q0 = 0; q1 = 0; if (rp != 0) { rr.s.low = n0; rr.s.high = n1; *rp = rr.ll; } } else { UWtype m1, m0; /* Normalize. */ b = W_TYPE_SIZE - bm; d1 = (d1 << bm) | (d0 >> b); d0 = d0 << bm; n2 = n1 >> b; n1 = (n1 << bm) | (n0 >> b); n0 = n0 << bm; udiv_qrnnd (q0, n1, n2, n1, d1); umul_ppmm (m1, m0, q0, d0); if (m1 > n1 || (m1 == n1 && m0 > n0)) { q0--; sub_ddmmss (m1, m0, m1, m0, d1, d0); } q1 = 0; /* Remainder in (n1n0 - m1m0) >> bm. */ if (rp != 0) { sub_ddmmss (n1, n0, n1, n0, m1, m0); rr.s.low = (n1 << b) | (n0 >> bm); rr.s.high = n1 >> bm; *rp = rr.ll; } } } } ww.s.low = q0; ww.s.high = q1; return ww.ll; } #define __negdi2(a) (-(a)) long long __divdi3(long long u, long long v) { int c = 0; DWunion uu, vv; DWtype w; uu.ll = u; vv.ll = v; if (uu.s.high < 0) { c = ~c; uu.ll = __negdi2 (uu.ll); } if (vv.s.high < 0) { c = ~c; vv.ll = __negdi2 (vv.ll); } w = __udivmoddi4 (uu.ll, vv.ll, (UDWtype *) 0); if (c) w = __negdi2 (w); return w; } long long __moddi3(long long u, long long v) { int c = 0; DWunion uu, vv; DWtype w; uu.ll = u; vv.ll = v; if (uu.s.high < 0) { c = ~c; uu.ll = __negdi2 (uu.ll); } if (vv.s.high < 0) vv.ll = __negdi2 (vv.ll); __udivmoddi4 (uu.ll, vv.ll, (UDWtype *) &w); if (c) w = __negdi2 (w); return w; } unsigned long long __udivdi3(unsigned long long u, unsigned long long v) { return __udivmoddi4 (u, v, (UDWtype *) 0); } unsigned long long __umoddi3(unsigned long long u, unsigned long long v) { UDWtype w; __udivmoddi4 (u, v, &w); return w; } /* XXX: fix tcc's code generator to do this instead */ long long __ashrdi3(long long a, int b) { #ifdef __TINYC__ DWunion u; u.ll = a; if (b >= 32) { u.s.low = u.s.high >> (b - 32); u.s.high = u.s.high >> 31; } else if (b != 0) { u.s.low = ((unsigned)u.s.low >> b) | (u.s.high << (32 - b)); u.s.high = u.s.high >> b; } return u.ll; #else return a >> b; #endif } /* XXX: fix tcc's code generator to do this instead */ unsigned long long __lshrdi3(unsigned long long a, int b) { #ifdef __TINYC__ DWunion u; u.ll = a; if (b >= 32) { u.s.low = (unsigned)u.s.high >> (b - 32); u.s.high = 0; } else if (b != 0) { u.s.low = ((unsigned)u.s.low >> b) | (u.s.high << (32 - b)); u.s.high = (unsigned)u.s.high >> b; } return u.ll; #else return a >> b; #endif } /* XXX: fix tcc's code generator to do this instead */ long long __ashldi3(long long a, int b) { #ifdef __TINYC__ DWunion u; u.ll = a; if (b >= 32) { u.s.high = (unsigned)u.s.low << (b - 32); u.s.low = 0; } else if (b != 0) { u.s.high = ((unsigned)u.s.high << b) | ((unsigned)u.s.low >> (32 - b)); u.s.low = (unsigned)u.s.low << b; } return u.ll; #else return a << b; #endif } #endif /* !__x86_64__ */ /* XXX: fix tcc's code generator to do this instead */ float __floatundisf(unsigned long long a) { DWunion uu; XFtype r; uu.ll = a; if (uu.s.high >= 0) { return (float)uu.ll; } else { r = (XFtype)uu.ll; r += 18446744073709551616.0; return (float)r; } } double __floatundidf(unsigned long long a) { DWunion uu; XFtype r; uu.ll = a; if (uu.s.high >= 0) { return (double)uu.ll; } else { r = (XFtype)uu.ll; r += 18446744073709551616.0; return (double)r; } } long double __floatundixf(unsigned long long a) { DWunion uu; XFtype r; uu.ll = a; if (uu.s.high >= 0) { return (long double)uu.ll; } else { r = (XFtype)uu.ll; r += 18446744073709551616.0; return (long double)r; } } unsigned long long __fixunssfdi (float a1) { register union float_long fl1; register int exp; register unsigned long l; fl1.f = a1; if (fl1.l == 0) return (0); exp = EXP (fl1.l) - EXCESS - 24; l = MANT(fl1.l); if (exp >= 41) return (unsigned long long)-1; else if (exp >= 0) return (unsigned long long)l << exp; else if (exp >= -23) return l >> -exp; else return 0; } long long __fixsfdi (float a1) { long long ret; int s; ret = __fixunssfdi((s = a1 >= 0) ? a1 : -a1); return s ? ret : -ret; } unsigned long long __fixunsdfdi (double a1) { register union double_long dl1; register int exp; register unsigned long long l; dl1.d = a1; if (dl1.ll == 0) return (0); exp = EXPD (dl1) - EXCESSD - 53; l = MANTD_LL(dl1); if (exp >= 12) return (unsigned long long)-1; else if (exp >= 0) return l << exp; else if (exp >= -52) return l >> -exp; else return 0; } long long __fixdfdi (double a1) { long long ret; int s; ret = __fixunsdfdi((s = a1 >= 0) ? a1 : -a1); return s ? ret : -ret; } #ifndef __arm__ unsigned long long __fixunsxfdi (long double a1) { register union ldouble_long dl1; register int exp; register unsigned long long l; dl1.ld = a1; if (dl1.l.lower == 0 && dl1.l.upper == 0) return (0); exp = EXPLD (dl1) - EXCESSLD - 64; l = dl1.l.lower; if (exp > 0) return (unsigned long long)-1; else if (exp >= -63) return l >> -exp; else return 0; } long long __fixxfdi (long double a1) { long long ret; int s; ret = __fixunsxfdi((s = a1 >= 0) ? a1 : -a1); return s ? ret : -ret; } #endif /* !ARM */
e5f00ef66c5796f436089d3f6e67af001182e6fbb439d13bb65eef8cf82f5973 /usr/bin/tcc
https://mirrors.kernel.org/gnu/make/make-3.82.tar.bz2 e2c1a73f179c40c71e2fe8abf8a8a0688b8499538512984da4a76958d0402966
/* * SPDX-FileCopyrightText: 2023 Paul Dersey <pdersey@gmail.com> * * SPDX-License-Identifier: GPL-2.0-or-later */ int putenv(char *string) { return 0; }
/* Getopt for GNU. NOTE: getopt is now part of the C library, so if you don't know what "Keep this file name-space clean" means, talk to drepper@gnu.org before changing it! Copyright (C) 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. Bugs can be reported to bug-glibc@gnu.org. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>. Ditto for AIX 3.2 and <stdlib.h>. */ #ifndef _NO_PROTO # define _NO_PROTO #endif #ifdef HAVE_CONFIG_H # include <config.h> #endif #if !defined __STDC__ || !__STDC__ /* This is a separate conditional since some stdc systems reject `defined (const)'. */ # ifndef const # define const # endif #endif #include <stdio.h> /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #define GETOPT_INTERFACE_VERSION 2 #if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 # include <gnu-versions.h> # if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION # define ELIDE_CODE # endif #endif #ifndef ELIDE_CODE /* This needs to come after some library #include to get __GNU_LIBRARY__ defined. */ #ifdef __GNU_LIBRARY__ /* Don't include stdlib.h for non-GNU C libraries because some of them contain conflicting prototypes for getopt. */ # include <stdlib.h> # include <unistd.h> #endif /* GNU C library. */ #ifdef VMS # include <unixlib.h> # if HAVE_STRING_H - 0 # include <string.h> # endif #endif /* This is for other GNU distributions with internationalized messages. When compiling libc, the _ macro is predefined. */ #include "gettext.h" #define _(msgid) gettext (msgid) /* This version of `getopt' appears to the caller like standard Unix `getopt' but it behaves differently for the user, since it allows the user to intersperse the options with the other arguments. As `getopt' works, it permutes the elements of ARGV so that, when it is done, all the options precede everything else. Thus all application programs are extended to handle flexible argument order. Setting the environment variable POSIXLY_CORRECT disables permutation. Then the behavior is completely standard. GNU application programs can use a third alternative mode in which they can distinguish the relative order of options and other arguments. */ #include "getopt.h" /* For communication from `getopt' to the caller. When `getopt' finds an option that takes an argument, the argument value is returned here. Also, when `ordering' is RETURN_IN_ORDER, each non-option ARGV-element is returned here. */ char *optarg = NULL; /* Index in ARGV of the next element to be scanned. This is used for communication to and from the caller and for communication between successive calls to `getopt'. On entry to `getopt', zero means this is the first call; initialize. When `getopt' returns -1, this is the index of the first of the non-option elements that the caller should itself scan. Otherwise, `optind' communicates from one call to the next how much of ARGV has been scanned so far. */ /* 1003.2 says this must be 1 before any call. */ int optind = 1; /* Formerly, initialization of getopt depended on optind==0, which causes problems with re-calling getopt as programs generally don't know that. */ int __getopt_initialized = 0; /* The next char to be scanned in the option-element in which the last option character we returned was found. This allows us to pick up the scan where we left off. If this is zero, or a null string, it means resume the scan by advancing to the next ARGV-element. */ static char *nextchar; /* Callers store zero here to inhibit the error message for unrecognized options. */ int opterr = 1; /* Set to an option character which was unrecognized. This must be initialized on some systems to avoid linking in the system's own getopt implementation. */ int optopt = '?'; /* Describe how to deal with options that follow non-option ARGV-elements. If the caller did not specify anything, the default is REQUIRE_ORDER if the environment variable POSIXLY_CORRECT is defined, PERMUTE otherwise. REQUIRE_ORDER means don't recognize them as options; stop option processing when the first non-option is seen. This is what Unix does. This mode of operation is selected by either setting the environment variable POSIXLY_CORRECT, or using `+' as the first character of the list of option characters. PERMUTE is the default. We permute the contents of ARGV as we scan, so that eventually all the non-options are at the end. This allows options to be given in any order, even with programs that were not written to expect this. RETURN_IN_ORDER is an option available to programs that were written to expect options and other ARGV-elements in any order and that care about the ordering of the two. We describe each non-option ARGV-element as if it were the argument of an option with character code 1. Using `-' as the first character of the list of option characters selects this mode of operation. The special argument `--' forces an end of option-scanning regardless of the value of `ordering'. In the case of RETURN_IN_ORDER, only `--' can cause `getopt' to return -1 with `optind' != ARGC. */ static enum { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER } ordering; /* Value of POSIXLY_CORRECT environment variable. */ static char *posixly_correct; #ifdef __GNU_LIBRARY__ /* We want to avoid inclusion of string.h with non-GNU libraries because there are many ways it can cause trouble. On some systems, it contains special magic macros that don't work in GCC. */ # include <string.h> # define my_index strchr #else # if HAVE_STRING_H # include <string.h> # else # include <strings.h> # endif /* Avoid depending on library functions or files whose names are inconsistent. */ #ifndef getenv extern char *getenv (); #endif static char * my_index (const char *str, int chr) { while (*str) { if (*str == chr) return (char *) str; str++; } return 0; } /* If using GCC, we can safely declare strlen this way. If not using GCC, it is ok not to declare it. */ #ifdef __GNUC__ /* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h. That was relevant to code that was here before. */ # if (!defined __STDC__ || !__STDC__) && !defined strlen /* gcc with -traditional declares the built-in strlen to return int, and has done so at least since version 2.4.5. -- rms. */ extern int strlen (const char *); # endif /* not __STDC__ */ #endif /* __GNUC__ */ #endif /* not __GNU_LIBRARY__ */ /* Handle permutation of arguments. */ /* Describe the part of ARGV that contains non-options that have been skipped. `first_nonopt' is the index in ARGV of the first of them; `last_nonopt' is the index after the last of them. */ static int first_nonopt; static int last_nonopt; #ifdef _LIBC /* Bash 2.0 gives us an environment variable containing flags indicating ARGV elements that should not be considered arguments. */ /* Defined in getopt_init.c */ extern char *__getopt_nonoption_flags; static int nonoption_flags_max_len; static int nonoption_flags_len; static int original_argc; static char *const *original_argv; /* Make sure the environment variable bash 2.0 puts in the environment is valid for the getopt call we must make sure that the ARGV passed to getopt is that one passed to the process. */ static void __attribute__ ((unused)) store_args_and_env (int argc, char *const *argv) { /* XXX This is no good solution. We should rather copy the args so that we can compare them later. But we must not use malloc(3). */ original_argc = argc; original_argv = argv; } # ifdef text_set_element text_set_element (__libc_subinit, store_args_and_env); # endif /* text_set_element */ # define SWAP_FLAGS(ch1, ch2) \ if (nonoption_flags_len > 0) \ { \ char __tmp = __getopt_nonoption_flags[ch1]; \ __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ __getopt_nonoption_flags[ch2] = __tmp; \ } #else /* !_LIBC */ # define SWAP_FLAGS(ch1, ch2) #endif /* _LIBC */ /* Exchange two adjacent subsequences of ARGV. One subsequence is elements [first_nonopt,last_nonopt) which contains all the non-options that have been skipped so far. The other is elements [last_nonopt,optind), which contains all the options processed since those non-options were skipped. `first_nonopt' and `last_nonopt' are relocated so that they describe the new indices of the non-options in ARGV after they are moved. */ #if defined __STDC__ && __STDC__ static void exchange (char **); #endif static void exchange (char **argv) { int bottom = first_nonopt; int middle = last_nonopt; int top = optind; char *tem; /* Exchange the shorter segment with the far end of the longer segment. That puts the shorter segment into the right place. It leaves the longer segment in the right place overall, but it consists of two parts that need to be swapped next. */ #ifdef _LIBC /* First make sure the handling of the `__getopt_nonoption_flags' string can work normally. Our top argument must be in the range of the string. */ if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len) { /* We must extend the array. The user plays games with us and presents new arguments. */ char *new_str = malloc (top + 1); if (new_str == NULL) nonoption_flags_len = nonoption_flags_max_len = 0; else { memset (__mempcpy (new_str, __getopt_nonoption_flags, nonoption_flags_max_len), '\0', top + 1 - nonoption_flags_max_len); nonoption_flags_max_len = top + 1; __getopt_nonoption_flags = new_str; } } #endif while (top > middle && middle > bottom) { if (top - middle > middle - bottom) { /* Bottom segment is the short one. */ int len = middle - bottom; register int i; /* Swap it with the top part of the top segment. */ for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[top - (middle - bottom) + i]; argv[top - (middle - bottom) + i] = tem; SWAP_FLAGS (bottom + i, top - (middle - bottom) + i); } /* Exclude the moved bottom segment from further swapping. */ top -= len; } else { /* Top segment is the short one. */ int len = top - middle; register int i; /* Swap it with the bottom part of the bottom segment. */ for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[middle + i]; argv[middle + i] = tem; SWAP_FLAGS (bottom + i, middle + i); } /* Exclude the moved top segment from further swapping. */ bottom += len; } } /* Update records for the slots the non-options now occupy. */ first_nonopt += (optind - last_nonopt); last_nonopt = optind; } /* Initialize the internal data when the first call is made. */ #if defined __STDC__ && __STDC__ static const char *_getopt_initialize (int, char *const *, const char *); #endif static const char * _getopt_initialize (int argc, char *const *argv, const char *optstring) { /* Start processing options with ARGV-element 1 (since ARGV-element 0 is the program name); the sequence of previously skipped non-option ARGV-elements is empty. */ first_nonopt = last_nonopt = optind; nextchar = NULL; posixly_correct = getenv ("POSIXLY_CORRECT"); /* Determine how to handle the ordering of options and nonoptions. */ if (optstring[0] == '-') { ordering = RETURN_IN_ORDER; ++optstring; } else if (optstring[0] == '+') { ordering = REQUIRE_ORDER; ++optstring; } else if (posixly_correct != NULL) ordering = REQUIRE_ORDER; else ordering = PERMUTE; #ifdef _LIBC if (posixly_correct == NULL && argc == original_argc && argv == original_argv) { if (nonoption_flags_max_len == 0) { if (__getopt_nonoption_flags == NULL || __getopt_nonoption_flags[0] == '\0') nonoption_flags_max_len = -1; else { const char *orig_str = __getopt_nonoption_flags; int len = nonoption_flags_max_len = strlen (orig_str); if (nonoption_flags_max_len < argc) nonoption_flags_max_len = argc; __getopt_nonoption_flags = (char *) malloc (nonoption_flags_max_len); if (__getopt_nonoption_flags == NULL) nonoption_flags_max_len = -1; else memset (__mempcpy (__getopt_nonoption_flags, orig_str, len), '\0', nonoption_flags_max_len - len); } } nonoption_flags_len = nonoption_flags_max_len; } else nonoption_flags_len = 0; #endif return optstring; } /* Scan elements of ARGV (whose length is ARGC) for option characters given in OPTSTRING. If an element of ARGV starts with '-', and is not exactly "-" or "--", then it is an option element. The characters of this element (aside from the initial '-') are option characters. If `getopt' is called repeatedly, it returns successively each of the option characters from each of the option elements. If `getopt' finds another option character, it returns that character, updating `optind' and `nextchar' so that the next call to `getopt' can resume the scan with the following option character or ARGV-element. If there are no more option characters, `getopt' returns -1. Then `optind' is the index in ARGV of the first ARGV-element that is not an option. (The ARGV-elements have been permuted so that those that are not options now come last.) OPTSTRING is a string containing the legitimate option characters. If an option character is seen that is not listed in OPTSTRING, return '?' after printing an error message. If you set `opterr' to zero, the error message is suppressed but we still return '?'. If a char in OPTSTRING is followed by a colon, that means it wants an arg, so the following text in the same ARGV-element, or the text of the following ARGV-element, is returned in `optarg'. Two colons mean an option that wants an optional arg; if there is text in the current ARGV-element, it is returned in `optarg', otherwise `optarg' is set to zero. If OPTSTRING starts with `-' or `+', it requests different methods of handling the non-option ARGV-elements. See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. Long-named options begin with `--' instead of `-'. Their names may be abbreviated as long as the abbreviation is unique or is an exact match for some defined option. If they have an argument, it follows the option name in the same ARGV-element, separated from the option name by a `=', or else the in next ARGV-element. When `getopt' finds a long-named option, it returns 0 if that option's `flag' field is nonzero, the value of the option's `val' field if the `flag' field is zero. The elements of ARGV aren't really const, because we permute them. But we pretend they're const in the prototype to be compatible with other systems. LONGOPTS is a vector of `struct option' terminated by an element containing a name which is zero. LONGIND returns the index in LONGOPT of the long-named option found. It is only valid when a long-named option has been found by the most recent call. If LONG_ONLY is nonzero, '-' as well as '--' can introduce long-named options. */ int _getopt_internal (int argc, char *const *argv, const char *optstring, const struct option *longopts, int *longind, int long_only) { optarg = NULL; if (optind == 0 || !__getopt_initialized) { if (optind == 0) optind = 1; /* Don't scan ARGV[0], the program name. */ optstring = _getopt_initialize (argc, argv, optstring); __getopt_initialized = 1; } /* Test whether ARGV[optind] points to a non-option argument. Either it does not have option syntax, or there is an environment flag from the shell indicating it is not an option. The later information is only used when the used in the GNU libc. */ #ifdef _LIBC # define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \ || (optind < nonoption_flags_len \ && __getopt_nonoption_flags[optind] == '1')) #else # define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0') #endif if (nextchar == NULL || *nextchar == '\0') { /* Advance to the next ARGV-element. */ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been moved back by the user (who may also have changed the arguments). */ if (last_nonopt > optind) last_nonopt = optind; if (first_nonopt > optind) first_nonopt = optind; if (ordering == PERMUTE) { /* If we have just processed some options following some non-options, exchange them so that the options come first. */ if (first_nonopt != last_nonopt && last_nonopt != optind) exchange ((char **) argv); else if (last_nonopt != optind) first_nonopt = optind; /* Skip any additional non-options and extend the range of non-options previously skipped. */ while (optind < argc && NONOPTION_P) optind++; last_nonopt = optind; } /* The special ARGV-element `--' means premature end of options. Skip it like a null option, then exchange with previous non-options as if it were an option, then skip everything else like a non-option. */ if (optind != argc && !strcmp (argv[optind], "--")) { optind++; if (first_nonopt != last_nonopt && last_nonopt != optind) exchange ((char **) argv); else if (first_nonopt == last_nonopt) first_nonopt = optind; last_nonopt = argc; optind = argc; } /* If we have done all the ARGV-elements, stop the scan and back over any non-options that we skipped and permuted. */ if (optind == argc) { /* Set the next-arg-index to point at the non-options that we previously skipped, so the caller will digest them. */ if (first_nonopt != last_nonopt) optind = first_nonopt; return -1; } /* If we have come to a non-option and did not permute it, either stop the scan or describe it to the caller and pass it by. */ if (NONOPTION_P) { if (ordering == REQUIRE_ORDER) return -1; optarg = argv[optind++]; return 1; } /* We have found another option-ARGV-element. Skip the initial punctuation. */ nextchar = (argv[optind] + 1 + (longopts != NULL && argv[optind][1] == '-')); } /* Decode the current option-ARGV-element. */ /* Check whether the ARGV-element is a long option. If long_only and the ARGV-element has the form "-f", where f is a valid short option, don't consider it an abbreviated form of a long option that starts with f. Otherwise there would be no way to give the -f short option. On the other hand, if there's a long option "fubar" and the ARGV-element is "-fu", do consider that an abbreviation of the long option, just like "--fu", and not "-f" with arg "u". This distinction seems to be the most useful approach. */ if (longopts != NULL && (argv[optind][1] == '-' || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1]))))) { char *nameend; const struct option *p; const struct option *pfound = NULL; int exact = 0; int ambig = 0; int indfound = -1; int option_index; for (nameend = nextchar; *nameend && *nameend != '='; nameend++) /* Do nothing. */ ; /* Test all long options for either exact match or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp (p->name, nextchar, nameend - nextchar)) { if ((unsigned int) (nameend - nextchar) == (unsigned int) strlen (p->name)) { /* Exact match found. */ pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { /* First nonexact match found. */ pfound = p; indfound = option_index; } else /* Second or later nonexact match found. */ ambig = 1; } if (ambig && !exact) { if (opterr) fprintf (stderr, _("%s: option `%s' is ambiguous\n"), argv[0], argv[optind]); nextchar += strlen (nextchar); optind++; optopt = 0; return '?'; } if (pfound != NULL) { option_index = indfound; optind++; if (*nameend) { /* Don't test has_arg with >, because some C compilers don't allow it to be used on enums. */ if (pfound->has_arg) optarg = nameend + 1; else { if (opterr) if (argv[optind - 1][1] == '-') /* --option */ fprintf (stderr, _("%s: option `--%s' doesn't allow an argument\n"), argv[0], pfound->name); else /* +option or -option */ fprintf (stderr, _("%s: option `%c%s' doesn't allow an argument\n"), argv[0], argv[optind - 1][0], pfound->name); nextchar += strlen (nextchar); optopt = pfound->val; return '?'; } } else if (pfound->has_arg == 1) { if (optind < argc) optarg = argv[optind++]; else { if (opterr) fprintf (stderr, _("%s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]); nextchar += strlen (nextchar); optopt = pfound->val; return optstring[0] == ':' ? ':' : '?'; } } nextchar += strlen (nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } /* Can't find it as a long option. If this is not getopt_long_only, or the option starts with '--' or is not a valid short option, then it's an error. Otherwise interpret it as a short option. */ if (!long_only || argv[optind][1] == '-' || my_index (optstring, *nextchar) == NULL) { if (opterr) { if (argv[optind][1] == '-') /* --option */ fprintf (stderr, _("%s: unrecognized option `--%s'\n"), argv[0], nextchar); else /* +option or -option */ fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), argv[0], argv[optind][0], nextchar); } nextchar = (char *) ""; optind++; optopt = 0; return '?'; } } /* Look at and handle the next short option-character. */ { char c = *nextchar++; char *temp = my_index (optstring, c); /* Increment `optind' when we start to process its last character. */ if (*nextchar == '\0') ++optind; if (temp == NULL || c == ':') { if (opterr) { if (posixly_correct) /* 1003.2 specifies the format of this message. */ fprintf (stderr, _("%s: illegal option -- %c\n"), argv[0], c); else fprintf (stderr, _("%s: invalid option -- %c\n"), argv[0], c); } optopt = c; return '?'; } /* Convenience. Treat POSIX -W foo same as long option --foo */ if (temp[0] == 'W' && temp[1] == ';') { char *nameend; const struct option *p; const struct option *pfound = NULL; int exact = 0; int ambig = 0; int indfound = 0; int option_index; /* This is an option that requires an argument. */ if (*nextchar != '\0') { optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ optind++; } else if (optind == argc) { if (opterr) { /* 1003.2 specifies the format of this message. */ fprintf (stderr, _("%s: option requires an argument -- %c\n"), argv[0], c); } optopt = c; if (optstring[0] == ':') c = ':'; else c = '?'; return c; } else /* We already incremented `optind' once; increment it again when taking next ARGV-elt as argument. */ optarg = argv[optind++]; /* optarg is now the argument, see if it's in the table of longopts. */ for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++) /* Do nothing. */ ; /* Test all long options for either exact match or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp (p->name, nextchar, nameend - nextchar)) { if ((unsigned int) (nameend - nextchar) == strlen (p->name)) { /* Exact match found. */ pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { /* First nonexact match found. */ pfound = p; indfound = option_index; } else /* Second or later nonexact match found. */ ambig = 1; } if (ambig && !exact) { if (opterr) fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"), argv[0], argv[optind]); nextchar += strlen (nextchar); optind++; return '?'; } if (pfound != NULL) { option_index = indfound; if (*nameend) { /* Don't test has_arg with >, because some C compilers don't allow it to be used on enums. */ if (pfound->has_arg) optarg = nameend + 1; else { if (opterr) fprintf (stderr, _("\ %s: option `-W %s' doesn't allow an argument\n"), argv[0], pfound->name); nextchar += strlen (nextchar); return '?'; } } else if (pfound->has_arg == 1) { if (optind < argc) optarg = argv[optind++]; else { if (opterr) fprintf (stderr, _("%s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]); nextchar += strlen (nextchar); return optstring[0] == ':' ? ':' : '?'; } } nextchar += strlen (nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } nextchar = NULL; return 'W'; /* Let the application handle it. */ } if (temp[1] == ':') { if (temp[2] == ':') { /* This is an option that accepts an argument optionally. */ if (*nextchar != '\0') { optarg = nextchar; optind++; } else optarg = NULL; nextchar = NULL; } else { /* This is an option that requires an argument. */ if (*nextchar != '\0') { optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ optind++; } else if (optind == argc) { if (opterr) { /* 1003.2 specifies the format of this message. */ fprintf (stderr, _("%s: option requires an argument -- %c\n"), argv[0], c); } optopt = c; if (optstring[0] == ':') c = ':'; else c = '?'; } else /* We already incremented `optind' once; increment it again when taking next ARGV-elt as argument. */ optarg = argv[optind++]; nextchar = NULL; } } return c; } } int getopt (int argc, char *const *argv, const char *optstring) { return _getopt_internal (argc, argv, optstring, (const struct option *) 0, (int *) 0, 0); } #endif /* Not ELIDE_CODE. */ #ifdef TEST /* Compile with -DTEST to make an executable for use in testing the above definition of `getopt'. */ int main (int argc, char **argv) { int c; int digit_optind = 0; while (1) { int this_option_optind = optind ? optind : 1; c = getopt (argc, argv, "abc:d:0123456789"); if (c == -1) break; switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (digit_optind != 0 && digit_optind != this_option_optind) printf ("digits occur in two different argv-elements.\n"); digit_optind = this_option_optind; printf ("option %c\n", c); break; case 'a': printf ("option a\n"); break; case 'b': printf ("option b\n"); break; case 'c': printf ("option c with value `%s'\n", optarg); break; case '?': break; default: printf ("?? getopt returned character code 0%o ??\n", c); } } if (optind < argc) { printf ("non-option ARGV-elements: "); while (optind < argc) printf ("%s ", argv[optind++]); printf ("\n"); } exit (0); } #endif /* TEST */
/* getopt_long and getopt_long_only entry points for GNU getopt. Copyright (C) 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. Bugs can be reported to bug-glibc@gnu.org. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include "getopt.h" #if !defined __STDC__ || !__STDC__ /* This is a separate conditional since some stdc systems reject `defined (const)'. */ #ifndef const #define const #endif #endif #include <stdio.h> /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #define GETOPT_INTERFACE_VERSION 2 #if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 #include <gnu-versions.h> #if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION #define ELIDE_CODE #endif #endif #ifndef ELIDE_CODE /* This needs to come after some library #include to get __GNU_LIBRARY__ defined. */ #ifdef __GNU_LIBRARY__ #include <stdlib.h> #endif #ifndef NULL #define NULL 0 #endif int getopt_long (int argc, char *const *argv, const char *options, const struct option *long_options, int *opt_index) { return _getopt_internal (argc, argv, options, long_options, opt_index, 0); } /* Like getopt_long, but '-' as well as '--' can indicate a long option. If an option that starts with '-' (not '--') doesn't match a long option, but does match a short option, it is parsed as a short option instead. */ int getopt_long_only (int argc, char *const *argv, const char *options, const struct option *long_options, int *opt_index) { return _getopt_internal (argc, argv, options, long_options, opt_index, 1); } #endif /* Not ELIDE_CODE. */ #ifdef TEST #include <stdio.h> int main (int argc, char **argv) { int c; int digit_optind = 0; while (1) { int this_option_optind = optind ? optind : 1; int option_index = 0; static struct option long_options[] = { {"add", 1, 0, 0}, {"append", 0, 0, 0}, {"delete", 1, 0, 0}, {"verbose", 0, 0, 0}, {"create", 0, 0, 0}, {"file", 1, 0, 0}, {0, 0, 0, 0} }; c = getopt_long (argc, argv, "abc:d:0123456789", long_options, &option_index); if (c == -1) break; switch (c) { case 0: printf ("option %s", long_options[option_index].name); if (optarg) printf (" with arg %s", optarg); printf ("\n"); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (digit_optind != 0 && digit_optind != this_option_optind) printf ("digits occur in two different argv-elements.\n"); digit_optind = this_option_optind; printf ("option %c\n", c); break; case 'a': printf ("option a\n"); break; case 'b': printf ("option b\n"); break; case 'c': printf ("option c with value `%s'\n", optarg); break; case 'd': printf ("option d with value `%s'\n", optarg); break; case '?': break; default: printf ("?? getopt returned character code 0%o ??\n", c); } } if (optind < argc) { printf ("non-option ARGV-elements: "); while (optind < argc) printf ("%s ", argv[optind++]); printf ("\n"); } exit (0); } #endif /* TEST */
/* Interface to `ar' archives for GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" #ifndef NO_ARCHIVES #include "filedef.h" #include "dep.h" #include <fnmatch.h> /* Return nonzero if NAME is an archive-member reference, zero if not. An archive-member reference is a name like `lib(member)' where member is a non-empty string. If a name like `lib((entry))' is used, a fatal error is signaled at the attempt to use this unsupported feature. */ int ar_name (const char *name) { const char *p = strchr (name, '('); const char *end; if (p == 0 || p == name) return 0; end = p + strlen (p) - 1; if (*end != ')' || end == p + 1) return 0; if (p[1] == '(' && end[-1] == ')') fatal (NILF, _("attempt to use unsupported feature: `%s'"), name); return 1; } /* Parse the archive-member reference NAME into the archive and member names. Creates one allocated string containing both names, pointed to by ARNAME_P. MEMNAME_P points to the member. */ void ar_parse_name (const char *name, char **arname_p, char **memname_p) { char *p; *arname_p = xstrdup (name); p = strchr (*arname_p, '('); *(p++) = '\0'; p[strlen(p) - 1] = '\0'; *memname_p = p; } /* This function is called by `ar_scan' to find which member to look at. */ /* ARGSUSED */ static long int ar_member_date_1 (int desc UNUSED, const char *mem, int truncated, long int hdrpos UNUSED, long int datapos UNUSED, long int size UNUSED, long int date, int uid UNUSED, int gid UNUSED, int mode UNUSED, const void *name) { return ar_name_equal (name, mem, truncated) ? date : 0; } /* Return the modtime of NAME. */ time_t ar_member_date (const char *name) { char *arname; char *memname; long int val; ar_parse_name (name, &arname, &memname); /* Make sure we know the modtime of the archive itself because we are likely to be called just before commands to remake a member are run, and they will change the archive itself. But we must be careful not to enter_file the archive itself if it does not exist, because pattern_search assumes that files found in the data base exist or can be made. */ { struct file *arfile; arfile = lookup_file (arname); if (arfile == 0 && file_exists_p (arname)) arfile = enter_file (strcache_add (arname)); if (arfile != 0) (void) f_mtime (arfile, 0); } val = ar_scan (arname, ar_member_date_1, memname); free (arname); return (val <= 0 ? (time_t) -1 : (time_t) val); } /* Set the archive-member NAME's modtime to now. */ #ifdef VMS int ar_touch (const char *name) { error (NILF, _("touch archive member is not available on VMS")); return -1; } #else int ar_touch (const char *name) { char *arname, *memname; int val; ar_parse_name (name, &arname, &memname); /* Make sure we know the modtime of the archive itself before we touch the member, since this will change the archive modtime. */ { struct file *arfile; arfile = enter_file (strcache_add (arname)); f_mtime (arfile, 0); } val = 1; switch (ar_member_touch (arname, memname)) { case -1: error (NILF, _("touch: Archive `%s' does not exist"), arname); break; case -2: error (NILF, _("touch: `%s' is not a valid archive"), arname); break; case -3: perror_with_name ("touch: ", arname); break; case 1: error (NILF, _("touch: Member `%s' does not exist in `%s'"), memname, arname); break; case 0: val = 0; break; default: error (NILF, _("touch: Bad return code from ar_member_touch on `%s'"), name); } free (arname); return val; } #endif /* !VMS */ /* State of an `ar_glob' run, passed to `ar_glob_match'. */ struct ar_glob_state { const char *arname; const char *pattern; unsigned int size; struct nameseq *chain; unsigned int n; }; /* This function is called by `ar_scan' to match one archive element against the pattern in STATE. */ static long int ar_glob_match (int desc UNUSED, const char *mem, int truncated UNUSED, long int hdrpos UNUSED, long int datapos UNUSED, long int size UNUSED, long int date UNUSED, int uid UNUSED, int gid UNUSED, int mode UNUSED, const void *arg) { struct ar_glob_state *state = (struct ar_glob_state *)arg; if (fnmatch (state->pattern, mem, FNM_PATHNAME|FNM_PERIOD) == 0) { /* We have a match. Add it to the chain. */ struct nameseq *new = xcalloc (state->size); new->name = strcache_add (concat (4, state->arname, "(", mem, ")")); new->next = state->chain; state->chain = new; ++state->n; } return 0L; } /* Return nonzero if PATTERN contains any metacharacters. Metacharacters can be quoted with backslashes if QUOTE is nonzero. */ static int glob_pattern_p (const char *pattern, int quote) { const char *p; int opened = 0; for (p = pattern; *p != '\0'; ++p) switch (*p) { case '?': case '*': return 1; case '\\': if (quote) ++p; break; case '[': opened = 1; break; case ']': if (opened) return 1; break; } return 0; } /* Glob for MEMBER_PATTERN in archive ARNAME. Return a malloc'd chain of matching elements (or nil if none). */ struct nameseq * ar_glob (const char *arname, const char *member_pattern, unsigned int size) { struct ar_glob_state state; struct nameseq *n; const char **names; unsigned int i; if (! glob_pattern_p (member_pattern, 1)) return 0; /* Scan the archive for matches. ar_glob_match will accumulate them in STATE.chain. */ state.arname = arname; state.pattern = member_pattern; state.size = size; state.chain = 0; state.n = 0; ar_scan (arname, ar_glob_match, &state); if (state.chain == 0) return 0; /* Now put the names into a vector for sorting. */ names = alloca (state.n * sizeof (const char *)); i = 0; for (n = state.chain; n != 0; n = n->next) names[i++] = n->name; /* Sort them alphabetically. */ /* MSVC erroneously warns without a cast here. */ qsort ((void *)names, i, sizeof (*names), alpha_compare); /* Put them back into the chain in the sorted order. */ i = 0; for (n = state.chain; n != 0; n = n->next) n->name = names[i++]; return state.chain; } #endif /* Not NO_ARCHIVES. */
/* Miscellaneous global declarations and portability cruft for GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* We use <config.h> instead of "config.h" so that a compilation using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h (which it would do because make.h was found in $srcdir). */ #include <config.h> #undef HAVE_CONFIG_H #define HAVE_CONFIG_H 1 /* Specify we want GNU source code. This must be defined before any system headers are included. */ #define _GNU_SOURCE 1 /* AIX requires this to be the first thing in the file. */ #if HAVE_ALLOCA_H # include <alloca.h> #else # ifdef _AIX #pragma alloca # else # if !defined(__GNUC__) && !defined(WINDOWS32) # ifndef alloca /* predefined by HP cc +Olibcalls */ char *alloca (); # endif # endif # endif #endif #ifdef CRAY /* This must happen before #include <signal.h> so that the declaration therein is changed. */ # define signal bsdsignal #endif /* If we're compiling for the dmalloc debugger, turn off string inlining. */ #if defined(HAVE_DMALLOC_H) && defined(__GNUC__) # define __NO_STRING_INLINES #endif #include <sys/types.h> #include <sys/stat.h> #include <signal.h> #include <stdio.h> #include <ctype.h> #ifdef HAVE_SYS_TIMEB_H /* SCO 3.2 "devsys 4.2" has a prototype for `ftime' in <time.h> that bombs unless <sys/timeb.h> has been included first. Does every system have a <sys/timeb.h>? If any does not, configure should check for it. */ # include <sys/timeb.h> #endif #if TIME_WITH_SYS_TIME # include <sys/time.h> # include <time.h> #else # if HAVE_SYS_TIME_H # include <sys/time.h> # else # include <time.h> # endif #endif #include <errno.h> #ifndef errno extern int errno; #endif #ifndef isblank # define isblank(c) ((c) == ' ' || (c) == '\t') #endif #ifdef HAVE_UNISTD_H # include <unistd.h> /* Ultrix's unistd.h always defines _POSIX_VERSION, but you only get POSIX.1 behavior with `cc -YPOSIX', which predefines POSIX itself! */ # if defined (_POSIX_VERSION) && !defined (ultrix) && !defined (VMS) # define POSIX 1 # endif #endif /* Some systems define _POSIX_VERSION but are not really POSIX.1. */ #if (defined (butterfly) || defined (__arm) || (defined (__mips) && defined (_SYSTYPE_SVR3)) || (defined (sequent) && defined (i386))) # undef POSIX #endif #if !defined (POSIX) && defined (_AIX) && defined (_POSIX_SOURCE) # define POSIX 1 #endif #ifndef RETSIGTYPE # define RETSIGTYPE void #endif #ifndef sigmask # define sigmask(sig) (1 << ((sig) - 1)) #endif #ifndef HAVE_SA_RESTART # define SA_RESTART 0 #endif #ifdef HAVE_LIMITS_H # include <limits.h> #endif #ifdef HAVE_SYS_PARAM_H # include <sys/param.h> #endif #ifndef PATH_MAX # ifndef POSIX # define PATH_MAX MAXPATHLEN # endif #endif #ifndef MAXPATHLEN # define MAXPATHLEN 1024 #endif #ifdef PATH_MAX # define GET_PATH_MAX PATH_MAX # define PATH_VAR(var) char var[PATH_MAX] #else # define NEED_GET_PATH_MAX 1 # define GET_PATH_MAX (get_path_max ()) # define PATH_VAR(var) char *var = alloca (GET_PATH_MAX) unsigned int get_path_max (void); #endif #ifndef CHAR_BIT # define CHAR_BIT 8 #endif /* Nonzero if the integer type T is signed. */ #define INTEGER_TYPE_SIGNED(t) ((t) -1 < 0) /* The minimum and maximum values for the integer type T. Use ~ (t) 0, not -1, for portability to 1's complement hosts. */ #define INTEGER_TYPE_MINIMUM(t) \ (! INTEGER_TYPE_SIGNED (t) ? (t) 0 : ~ (t) 0 << (sizeof (t) * CHAR_BIT - 1)) #define INTEGER_TYPE_MAXIMUM(t) (~ (t) 0 - INTEGER_TYPE_MINIMUM (t)) #ifndef CHAR_MAX # define CHAR_MAX INTEGER_TYPE_MAXIMUM (char) #endif #ifdef STAT_MACROS_BROKEN # ifdef S_ISREG # undef S_ISREG # endif # ifdef S_ISDIR # undef S_ISDIR # endif #endif /* STAT_MACROS_BROKEN. */ #ifndef S_ISREG # define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) #endif #ifndef S_ISDIR # define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) #endif #ifdef VMS # include <types.h> # include <unixlib.h> # include <unixio.h> # include <perror.h> /* Needed to use alloca on VMS. */ # include <builtins.h> #endif #ifndef __attribute__ /* This feature is available in gcc versions 2.5 and later. */ # if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) || __STRICT_ANSI__ # define __attribute__(x) # endif /* The __-protected variants of `format' and `printf' attributes are accepted by gcc versions 2.6.4 (effectively 2.7) and later. */ # if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7) # define __format__ format # define __printf__ printf # endif #endif #define UNUSED __attribute__ ((unused)) #if defined (STDC_HEADERS) || defined (__GNU_LIBRARY__) # include <stdlib.h> # include <string.h> # define ANSI_STRING 1 #else /* No standard headers. */ # ifdef HAVE_STRING_H # include <string.h> # define ANSI_STRING 1 # else # include <strings.h> # endif # ifdef HAVE_MEMORY_H # include <memory.h> # endif # ifdef HAVE_STDLIB_H # include <stdlib.h> # else void *malloc (int); void *realloc (void *, int); void free (void *); void abort (void) __attribute__ ((noreturn)); void exit (int) __attribute__ ((noreturn)); # endif /* HAVE_STDLIB_H. */ #endif /* Standard headers. */ /* These should be in stdlib.h. Make sure we have them. */ #ifndef EXIT_SUCCESS # define EXIT_SUCCESS 0 #endif #ifndef EXIT_FAILURE # define EXIT_FAILURE 1 #endif #ifndef ANSI_STRING /* SCO Xenix has a buggy macro definition in <string.h>. */ #undef strerror #if !defined(__DECC) char *strerror (int errnum); #endif #endif /* !ANSI_STRING. */ #undef ANSI_STRING #if HAVE_INTTYPES_H # include <inttypes.h> #endif #define FILE_TIMESTAMP uintmax_t #if !defined(HAVE_STRSIGNAL) char *strsignal (int signum); #endif /* ISDIGIT offers the following features: - Its arg may be any int or unsigned int; it need not be an unsigned char. - It's guaranteed to evaluate its argument exactly once. NOTE! Make relies on this behavior, don't change it! - It's typically faster. POSIX 1003.2-1992 section 2.5.2.1 page 50 lines 1556-1558 says that only '0' through '9' are digits. Prefer ISDIGIT to isdigit() unless it's important to use the locale's definition of `digit' even when the host does not conform to POSIX. */ #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9) /* Test if two strings are equal. Is this worthwhile? Should be profiled. */ #define streq(a, b) \ ((a) == (b) || \ (*(a) == *(b) && (*(a) == '\0' || !strcmp ((a) + 1, (b) + 1)))) /* Test if two strings are equal, but match case-insensitively on systems which have case-insensitive filesystems. Should only be used for filenames! */ #ifdef HAVE_CASE_INSENSITIVE_FS # define patheq(a, b) \ ((a) == (b) \ || (tolower((unsigned char)*(a)) == tolower((unsigned char)*(b)) \ && (*(a) == '\0' || !strcasecmp ((a) + 1, (b) + 1)))) #else # define patheq(a, b) streq(a, b) #endif #define strneq(a, b, l) (strncmp ((a), (b), (l)) == 0) #if defined(__GNUC__) || defined(ENUM_BITFIELDS) # define ENUM_BITFIELD(bits) :bits #else # define ENUM_BITFIELD(bits) #endif /* Handle gettext and locales. */ #if HAVE_LOCALE_H # include <locale.h> #else # define setlocale(category, locale) #endif #include <gettext.h> #define _(msgid) gettext (msgid) #define N_(msgid) gettext_noop (msgid) #define S_(msg1,msg2,num) ngettext (msg1,msg2,num) /* Handle other OSs. */ #ifndef PATH_SEPARATOR_CHAR # if defined(HAVE_DOS_PATHS) # define PATH_SEPARATOR_CHAR ';' # elif defined(VMS) # define PATH_SEPARATOR_CHAR ',' # else # define PATH_SEPARATOR_CHAR ':' # endif #endif /* This is needed for getcwd() and chdir(), on some W32 systems. */ #if defined(HAVE_DIRECT_H) # include <direct.h> #endif #ifdef WINDOWS32 # include <fcntl.h> # include <malloc.h> # define pipe(_p) _pipe((_p), 512, O_BINARY) # define kill(_pid,_sig) w32_kill((_pid),(_sig)) void sync_Path_environment (void); int w32_kill (pid_t pid, int sig); char *end_of_token_w32 (const char *s, char stopchar); int find_and_set_default_shell (const char *token); /* indicates whether or not we have Bourne shell */ extern int no_default_sh_exe; /* is default_shell unixy? */ extern int unixy_shell; #endif /* WINDOWS32 */ #if defined(HAVE_SYS_RESOURCE_H) && defined(HAVE_GETRLIMIT) && defined(HAVE_SETRLIMIT) # define SET_STACK_SIZE #endif #ifdef SET_STACK_SIZE # include <sys/resource.h> struct rlimit stack_limit; #endif struct floc { const char *filenm; unsigned long lineno; }; #define NILF ((struct floc *)0) #define STRING_SIZE_TUPLE(_s) (_s), (sizeof (_s)-1) /* We have to have stdarg.h or varargs.h AND v*printf or doprnt to use variadic versions of these functions. */ #if HAVE_STDARG_H || HAVE_VARARGS_H # if HAVE_VPRINTF || HAVE_DOPRNT # define USE_VARIADIC 1 # endif #endif #if HAVE_ANSI_COMPILER && USE_VARIADIC && HAVE_STDARG_H const char *concat (unsigned int, ...); void message (int prefix, const char *fmt, ...) __attribute__ ((__format__ (__printf__, 2, 3))); void error (const struct floc *flocp, const char *fmt, ...) __attribute__ ((__format__ (__printf__, 2, 3))); void fatal (const struct floc *flocp, const char *fmt, ...) __attribute__ ((noreturn, __format__ (__printf__, 2, 3))); #else const char *concat (); void message (); void error (); void fatal (); #endif void die (int) __attribute__ ((noreturn)); void log_working_directory (int); void pfatal_with_name (const char *) __attribute__ ((noreturn)); void perror_with_name (const char *, const char *); void *xmalloc (unsigned int); void *xcalloc (unsigned int); void *xrealloc (void *, unsigned int); char *xstrdup (const char *); char *xstrndup (const char *, unsigned int); char *find_next_token (const char **, unsigned int *); char *next_token (const char *); char *end_of_token (const char *); void collapse_continuations (char *); char *lindex (const char *, const char *, int); int alpha_compare (const void *, const void *); void print_spaces (unsigned int); char *find_percent (char *); const char *find_percent_cached (const char **); FILE *open_tmpfile (char **, const char *); #ifndef NO_ARCHIVES int ar_name (const char *); void ar_parse_name (const char *, char **, char **); int ar_touch (const char *); time_t ar_member_date (const char *); typedef long int (*ar_member_func_t) (int desc, const char *mem, int truncated, long int hdrpos, long int datapos, long int size, long int date, int uid, int gid, int mode, const void *arg); long int ar_scan (const char *archive, ar_member_func_t function, const void *arg); int ar_name_equal (const char *name, const char *mem, int truncated); #ifndef VMS int ar_member_touch (const char *arname, const char *memname); #endif #endif int dir_file_exists_p (const char *, const char *); int file_exists_p (const char *); int file_impossible_p (const char *); void file_impossible (const char *); const char *dir_name (const char *); void hash_init_directories (void); void define_default_variables (void); void set_default_suffixes (void); void install_default_suffix_rules (void); void install_default_implicit_rules (void); void build_vpath_lists (void); void construct_vpath_list (char *pattern, char *dirpath); const char *vpath_search (const char *file, FILE_TIMESTAMP *mtime_ptr, unsigned int* vpath_index, unsigned int* path_index); int gpath_search (const char *file, unsigned int len); void construct_include_path (const char **arg_dirs); void user_access (void); void make_access (void); void child_access (void); void close_stdout (void); char *strip_whitespace (const char **begpp, const char **endpp); /* String caching */ void strcache_init (void); void strcache_print_stats (const char *prefix); int strcache_iscached (const char *str); const char *strcache_add (const char *str); const char *strcache_add_len (const char *str, int len); int strcache_setbufsize (int size); #ifdef HAVE_VFORK_H # include <vfork.h> #endif /* We omit these declarations on non-POSIX systems which define _POSIX_VERSION, because such systems often declare them in header files anyway. */ #if !defined (__GNU_LIBRARY__) && !defined (POSIX) && !defined (_POSIX_VERSION) && !defined(WINDOWS32) long int atol (); # ifndef VMS long int lseek (); # endif #endif /* Not GNU C library or POSIX. */ #ifdef HAVE_GETCWD # if !defined(VMS) && !defined(__DECC) char *getcwd (); # endif #else char *getwd (); # define getcwd(buf, len) getwd (buf) #endif #if !HAVE_STRCASECMP # if HAVE_STRICMP # define strcasecmp stricmp # elif HAVE_STRCMPI # define strcasecmp strcmpi # else /* Create our own, in misc.c */ int strcasecmp (const char *s1, const char *s2); # endif #endif #if !HAVE_STRNCASECMP # if HAVE_STRNICMP # define strncasecmp strnicmp # elif HAVE_STRNCMPI # define strncasecmp strncmpi # else /* Create our own, in misc.c */ int strncasecmp (const char *s1, const char *s2, int n); # endif #endif extern const struct floc *reading_file; extern const struct floc **expanding_var; extern char **environ; extern int just_print_flag, silent_flag, ignore_errors_flag, keep_going_flag; extern int print_data_base_flag, question_flag, touch_flag, always_make_flag; extern int env_overrides, no_builtin_rules_flag, no_builtin_variables_flag; extern int print_version_flag, print_directory_flag, check_symlink_flag; extern int warn_undefined_variables_flag, posix_pedantic, not_parallel; extern int second_expansion, clock_skew_detected, rebuilding_makefiles; extern int one_shell; /* can we run commands via 'sh -c xxx' or must we use batch files? */ extern int batch_mode_shell; /* Resetting the command script introduction prefix character. */ #define RECIPEPREFIX_NAME ".RECIPEPREFIX" #define RECIPEPREFIX_DEFAULT '\t' extern char cmd_prefix; extern unsigned int job_slots; extern int job_fds[2]; extern int job_rfd; #ifndef NO_FLOAT extern double max_load_average; #else extern int max_load_average; #endif extern char *program; extern char *starting_directory; extern unsigned int makelevel; extern char *version_string, *remote_description, *make_host; extern unsigned int commands_started; extern int handling_fatal_signal; #ifndef MIN #define MIN(_a,_b) ((_a)<(_b)?(_a):(_b)) #endif #ifndef MAX #define MAX(_a,_b) ((_a)>(_b)?(_a):(_b)) #endif #ifdef VMS # define MAKE_SUCCESS 1 # define MAKE_TROUBLE 2 # define MAKE_FAILURE 3 #else # define MAKE_SUCCESS 0 # define MAKE_TROUBLE 1 # define MAKE_FAILURE 2 #endif /* Set up heap debugging library dmalloc. */ #ifdef HAVE_DMALLOC_H #include <dmalloc.h> #endif #ifndef initialize_main # ifdef __EMX__ # define initialize_main(pargc, pargv) \ { _wildcard(pargc, pargv); _response(pargc, pargv); } # else # define initialize_main(pargc, pargv) # endif #endif #ifdef __EMX__ # if !defined chdir # define chdir _chdir2 # endif # if !defined getcwd # define getcwd _getcwd2 # endif /* NO_CHDIR2 causes make not to use _chdir2() and _getcwd2() instead of chdir() and getcwd(). This avoids some error messages for the make testsuite but restricts the drive letter support. */ # ifdef NO_CHDIR2 # warning NO_CHDIR2: usage of drive letters restricted # undef chdir # undef getcwd # endif #endif #ifndef initialize_main # define initialize_main(pargc, pargv) #endif /* Some systems (like Solaris, PTX, etc.) do not support the SA_RESTART flag properly according to POSIX. So, we try to wrap common system calls with checks for EINTR. Note that there are still plenty of system calls that can fail with EINTR but this, reportedly, gets the vast majority of failure cases. If you still experience failures you'll need to either get a system where SA_RESTART works, or you need to avoid -j. */ #define EINTRLOOP(_v,_c) while (((_v)=_c)==-1 && errno==EINTR) /* While system calls that return integers are pretty consistent about returning -1 on failure and setting errno in that case, functions that return pointers are not always so well behaved. Sometimes they return NULL for expected behavior: one good example is readdir() which returns NULL at the end of the directory--and _doesn't_ reset errno. So, we have to do it ourselves here. */ #define ENULLLOOP(_v,_c) do { errno = 0; (_v) = _c; } \ while((_v)==0 && errno==EINTR)
/* Definition of target file data structures for GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* Structure that represents the info on one file that the makefile says how to make. All of these are chained together through `next'. */ #include "hash.h" struct file { const char *name; const char *hname; /* Hashed filename */ const char *vpath; /* VPATH/vpath pathname */ struct dep *deps; /* all dependencies, including duplicates */ struct commands *cmds; /* Commands to execute for this target. */ int command_flags; /* Flags OR'd in for cmds; see commands.h. */ const char *stem; /* Implicit stem, if an implicit rule has been used */ struct dep *also_make; /* Targets that are made by making this. */ FILE_TIMESTAMP last_mtime; /* File's modtime, if already known. */ FILE_TIMESTAMP mtime_before_update; /* File's modtime before any updating has been performed. */ struct file *prev; /* Previous entry for same file name; used when there are multiple double-colon entries for the same file. */ struct file *last; /* Last entry for the same file name. */ /* File that this file was renamed to. After any time that a file could be renamed, call `check_renamed' (below). */ struct file *renamed; /* List of variable sets used for this file. */ struct variable_set_list *variables; /* Pattern-specific variable reference for this target, or null if there isn't one. Also see the pat_searched flag, below. */ struct variable_set_list *pat_variables; /* Immediate dependent that caused this target to be remade, or nil if there isn't one. */ struct file *parent; /* For a double-colon entry, this is the first double-colon entry for the same file. Otherwise this is null. */ struct file *double_colon; short int update_status; /* Status of the last attempt to update, or -1 if none has been made. */ enum cmd_state /* State of the commands. */ { /* Note: It is important that cs_not_started be zero. */ cs_not_started, /* Not yet started. */ cs_deps_running, /* Dep commands running. */ cs_running, /* Commands running. */ cs_finished /* Commands finished. */ } command_state ENUM_BITFIELD (2); unsigned int precious:1; /* Non-0 means don't delete file on quit */ unsigned int low_resolution_time:1; /* Nonzero if this file's time stamp has only one-second resolution. */ unsigned int tried_implicit:1; /* Nonzero if have searched for implicit rule for making this file; don't search again. */ unsigned int updating:1; /* Nonzero while updating deps of this file */ unsigned int updated:1; /* Nonzero if this file has been remade. */ unsigned int is_target:1; /* Nonzero if file is described as target. */ unsigned int cmd_target:1; /* Nonzero if file was given on cmd line. */ unsigned int phony:1; /* Nonzero if this is a phony file i.e., a prerequisite of .PHONY. */ unsigned int intermediate:1;/* Nonzero if this is an intermediate file. */ unsigned int secondary:1; /* Nonzero means remove_intermediates should not delete it. */ unsigned int dontcare:1; /* Nonzero if no complaint is to be made if this target cannot be remade. */ unsigned int ignore_vpath:1;/* Nonzero if we threw out VPATH name. */ unsigned int pat_searched:1;/* Nonzero if we already searched for pattern-specific variables. */ unsigned int considered:1; /* equal to 'considered' if file has been considered on current scan of goal chain */ unsigned int no_diag:1; /* True if the file failed to update and no diagnostics has been issued (dontcare). */ }; extern struct file *suffix_file, *default_file; struct file *lookup_file (const char *name); struct file *enter_file (const char *name); struct dep *split_prereqs (char *prereqstr); struct dep *enter_prereqs (struct dep *prereqs, const char *stem); void remove_intermediates (int sig); void snap_deps (void); void rename_file (struct file *file, const char *name); void rehash_file (struct file *file, const char *name); void set_command_state (struct file *file, enum cmd_state state); void notice_finished_file (struct file *file); void init_hash_files (void); char *build_target_list (char *old_list); void print_prereqs (const struct dep *deps); void print_file_data_base (void); #if FILE_TIMESTAMP_HI_RES # define FILE_TIMESTAMP_STAT_MODTIME(fname, st) \ file_timestamp_cons (fname, (st).st_mtime, (st).st_mtim.ST_MTIM_NSEC) #else # define FILE_TIMESTAMP_STAT_MODTIME(fname, st) \ file_timestamp_cons (fname, (st).st_mtime, 0) #endif /* If FILE_TIMESTAMP is 64 bits (or more), use nanosecond resolution. (Multiply by 2**30 instead of by 10**9 to save time at the cost of slightly decreasing the number of available timestamps.) With 64-bit FILE_TIMESTAMP, this stops working on 2514-05-30 01:53:04 UTC, but by then uintmax_t should be larger than 64 bits. */ #define FILE_TIMESTAMPS_PER_S (FILE_TIMESTAMP_HI_RES ? 1000000000 : 1) #define FILE_TIMESTAMP_LO_BITS (FILE_TIMESTAMP_HI_RES ? 30 : 0) #define FILE_TIMESTAMP_S(ts) (((ts) - ORDINARY_MTIME_MIN) \ >> FILE_TIMESTAMP_LO_BITS) #define FILE_TIMESTAMP_NS(ts) ((int) (((ts) - ORDINARY_MTIME_MIN) \ & ((1 << FILE_TIMESTAMP_LO_BITS) - 1))) /* Upper bound on length of string "YYYY-MM-DD HH:MM:SS.NNNNNNNNN" representing a file timestamp. The upper bound is not necessarily 19, since the year might be less than -999 or greater than 9999. Subtract one for the sign bit if in case file timestamps can be negative; subtract FLOOR_LOG2_SECONDS_PER_YEAR to yield an upper bound on how many file timestamp bits might affect the year; 302 / 1000 is log10 (2) rounded up; add one for integer division truncation; add one more for a minus sign if file timestamps can be negative; add 4 to allow for any 4-digit epoch year (e.g. 1970); add 25 to allow for "-MM-DD HH:MM:SS.NNNNNNNNN". */ #define FLOOR_LOG2_SECONDS_PER_YEAR 24 #define FILE_TIMESTAMP_PRINT_LEN_BOUND \ (((sizeof (FILE_TIMESTAMP) * CHAR_BIT - 1 - FLOOR_LOG2_SECONDS_PER_YEAR) \ * 302 / 1000) \ + 1 + 1 + 4 + 25) FILE_TIMESTAMP file_timestamp_cons (char const *, time_t, int); FILE_TIMESTAMP file_timestamp_now (int *); void file_timestamp_sprintf (char *p, FILE_TIMESTAMP ts); /* Return the mtime of file F (a struct file *), caching it. The value is NONEXISTENT_MTIME if the file does not exist. */ #define file_mtime(f) file_mtime_1 ((f), 1) /* Return the mtime of file F (a struct file *), caching it. Don't search using vpath for the file--if it doesn't actually exist, we don't find it. The value is NONEXISTENT_MTIME if the file does not exist. */ #define file_mtime_no_search(f) file_mtime_1 ((f), 0) FILE_TIMESTAMP f_mtime (struct file *file, int search); #define file_mtime_1(f, v) \ ((f)->last_mtime == UNKNOWN_MTIME ? f_mtime ((f), v) : (f)->last_mtime) /* Special timestamp values. */ /* The file's timestamp is not yet known. */ #define UNKNOWN_MTIME 0 /* The file does not exist. */ #define NONEXISTENT_MTIME 1 /* The file does not exist, and we assume that it is older than any actual file. */ #define OLD_MTIME 2 /* The smallest and largest ordinary timestamps. */ #define ORDINARY_MTIME_MIN (OLD_MTIME + 1) #define ORDINARY_MTIME_MAX ((FILE_TIMESTAMP_S (NEW_MTIME) \ << FILE_TIMESTAMP_LO_BITS) \ + ORDINARY_MTIME_MIN + FILE_TIMESTAMPS_PER_S - 1) /* Modtime value to use for `infinitely new'. We used to get the current time from the system and use that whenever we wanted `new'. But that causes trouble when the machine running make and the machine holding a file have different ideas about what time it is; and can also lose for `force' targets, which need to be considered newer than anything that depends on them, even if said dependents' modtimes are in the future. */ #define NEW_MTIME INTEGER_TYPE_MAXIMUM (FILE_TIMESTAMP) #define check_renamed(file) \ while ((file)->renamed != 0) (file) = (file)->renamed /* No ; here. */ /* Have we snapped deps yet? */ extern int snapped_deps;
/* hash.h -- decls for hash table Copyright (C) 1995, 1999, 2002, 2010 Free Software Foundation, Inc. Written by Greg McGary <gkm@gnu.org> <greg@mcgary.org> GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #ifndef _hash_h_ #define _hash_h_ #include <stdio.h> #include <ctype.h> #if defined __cplusplus || (defined __STDC__ && __STDC__) || defined WINDOWS32 # if !defined __GLIBC__ || !defined __P # undef __P # define __P(protos) protos # endif #else /* Not C++ or ANSI C. */ # undef __P # define __P(protos) () /* We can get away without defining `const' here only because in this file it is used only inside the prototype for `fnmatch', which is elided in non-ANSI C where `const' is problematical. */ #endif /* C++ or ANSI C. */ typedef unsigned long (*hash_func_t) __P((void const *key)); typedef int (*hash_cmp_func_t) __P((void const *x, void const *y)); typedef void (*hash_map_func_t) __P((void const *item)); typedef void (*hash_map_arg_func_t) __P((void const *item, void *arg)); struct hash_table { void **ht_vec; unsigned long ht_size; /* total number of slots (power of 2) */ unsigned long ht_capacity; /* usable slots, limited by loading-factor */ unsigned long ht_fill; /* items in table */ unsigned long ht_empty_slots; /* empty slots not including deleted slots */ unsigned long ht_collisions; /* # of failed calls to comparison function */ unsigned long ht_lookups; /* # of queries */ unsigned int ht_rehashes; /* # of times we've expanded table */ hash_func_t ht_hash_1; /* primary hash function */ hash_func_t ht_hash_2; /* secondary hash function */ hash_cmp_func_t ht_compare; /* comparison function */ }; typedef int (*qsort_cmp_t) __P((void const *, void const *)); void hash_init __P((struct hash_table *ht, unsigned long size, hash_func_t hash_1, hash_func_t hash_2, hash_cmp_func_t hash_cmp)); void hash_load __P((struct hash_table *ht, void *item_table, unsigned long cardinality, unsigned long size)); void **hash_find_slot __P((struct hash_table *ht, void const *key)); void *hash_find_item __P((struct hash_table *ht, void const *key)); void *hash_insert __P((struct hash_table *ht, const void *item)); void *hash_insert_at __P((struct hash_table *ht, const void *item, void const *slot)); void *hash_delete __P((struct hash_table *ht, void const *item)); void *hash_delete_at __P((struct hash_table *ht, void const *slot)); void hash_delete_items __P((struct hash_table *ht)); void hash_free_items __P((struct hash_table *ht)); void hash_free __P((struct hash_table *ht, int free_items)); void hash_map __P((struct hash_table *ht, hash_map_func_t map)); void hash_map_arg __P((struct hash_table *ht, hash_map_arg_func_t map, void *arg)); void hash_print_stats __P((struct hash_table *ht, FILE *out_FILE)); void **hash_dump __P((struct hash_table *ht, void **vector_0, qsort_cmp_t compare)); extern void *hash_deleted_item; #define HASH_VACANT(item) ((item) == 0 || (void *) (item) == hash_deleted_item) /* hash and comparison macros for case-sensitive string keys. */ /* Due to the strcache, it's not uncommon for the string pointers to be identical. Take advantage of that to short-circuit string compares. */ #define STRING_HASH_1(KEY, RESULT) do { \ unsigned char const *_key_ = (unsigned char const *) (KEY) - 1; \ while (*++_key_) \ (RESULT) += (*_key_ << (_key_[1] & 0xf)); \ } while (0) #define return_STRING_HASH_1(KEY) do { \ unsigned long _result_ = 0; \ STRING_HASH_1 ((KEY), _result_); \ return _result_; \ } while (0) #define STRING_HASH_2(KEY, RESULT) do { \ unsigned char const *_key_ = (unsigned char const *) (KEY) - 1; \ while (*++_key_) \ (RESULT) += (*_key_ << (_key_[1] & 0x7)); \ } while (0) #define return_STRING_HASH_2(KEY) do { \ unsigned long _result_ = 0; \ STRING_HASH_2 ((KEY), _result_); \ return _result_; \ } while (0) #define STRING_COMPARE(X, Y, RESULT) do { \ RESULT = (X) == (Y) ? 0 : strcmp ((X), (Y)); \ } while (0) #define return_STRING_COMPARE(X, Y) do { \ return (X) == (Y) ? 0 : strcmp ((X), (Y)); \ } while (0) #define STRING_N_HASH_1(KEY, N, RESULT) do { \ unsigned char const *_key_ = (unsigned char const *) (KEY) - 1; \ int _n_ = (N); \ if (_n_) \ while (--_n_ && *++_key_) \ (RESULT) += (*_key_ << (_key_[1] & 0xf)); \ (RESULT) += *++_key_; \ } while (0) #define return_STRING_N_HASH_1(KEY, N) do { \ unsigned long _result_ = 0; \ STRING_N_HASH_1 ((KEY), (N), _result_); \ return _result_; \ } while (0) #define STRING_N_HASH_2(KEY, N, RESULT) do { \ unsigned char const *_key_ = (unsigned char const *) (KEY) - 1; \ int _n_ = (N); \ if (_n_) \ while (--_n_ && *++_key_) \ (RESULT) += (*_key_ << (_key_[1] & 0x7)); \ (RESULT) += *++_key_; \ } while (0) #define return_STRING_N_HASH_2(KEY, N) do { \ unsigned long _result_ = 0; \ STRING_N_HASH_2 ((KEY), (N), _result_); \ return _result_; \ } while (0) #define STRING_N_COMPARE(X, Y, N, RESULT) do { \ RESULT = (X) == (Y) ? 0 : strncmp ((X), (Y), (N)); \ } while (0) #define return_STRING_N_COMPARE(X, Y, N) do { \ return (X) == (Y) ? 0 : strncmp ((X), (Y), (N)); \ } while (0) #ifdef HAVE_CASE_INSENSITIVE_FS /* hash and comparison macros for case-insensitive string _key_s. */ #define ISTRING_HASH_1(KEY, RESULT) do { \ unsigned char const *_key_ = (unsigned char const *) (KEY) - 1; \ while (*++_key_) \ (RESULT) += ((isupper (*_key_) ? tolower (*_key_) : *_key_) << (_key_[1] & 0xf)); \ } while (0) #define return_ISTRING_HASH_1(KEY) do { \ unsigned long _result_ = 0; \ ISTRING_HASH_1 ((KEY), _result_); \ return _result_; \ } while (0) #define ISTRING_HASH_2(KEY, RESULT) do { \ unsigned char const *_key_ = (unsigned char const *) (KEY) - 1; \ while (*++_key_) \ (RESULT) += ((isupper (*_key_) ? tolower (*_key_) : *_key_) << (_key_[1] & 0x7)); \ } while (0) #define return_ISTRING_HASH_2(KEY) do { \ unsigned long _result_ = 0; \ ISTRING_HASH_2 ((KEY), _result_); \ return _result_; \ } while (0) #define ISTRING_COMPARE(X, Y, RESULT) do { \ RESULT = (X) == (Y) ? 0 : strcasecmp ((X), (Y)); \ } while (0) #define return_ISTRING_COMPARE(X, Y) do { \ return (X) == (Y) ? 0 : strcasecmp ((X), (Y)); \ } while (0) #else #define ISTRING_HASH_1(KEY, RESULT) STRING_HASH_1 ((KEY), (RESULT)) #define return_ISTRING_HASH_1(KEY) return_STRING_HASH_1 (KEY) #define ISTRING_HASH_2(KEY, RESULT) STRING_HASH_2 ((KEY), (RESULT)) #define return_ISTRING_HASH_2(KEY) return_STRING_HASH_2 (KEY) #define ISTRING_COMPARE(X, Y, RESULT) STRING_COMPARE ((X), (Y), (RESULT)) #define return_ISTRING_COMPARE(X, Y) return_STRING_COMPARE ((X), (Y)) #endif /* hash and comparison macros for integer _key_s. */ #define INTEGER_HASH_1(KEY, RESULT) do { \ (RESULT) += ((unsigned long)(KEY)); \ } while (0) #define return_INTEGER_HASH_1(KEY) do { \ unsigned long _result_ = 0; \ INTEGER_HASH_1 ((KEY), _result_); \ return _result_; \ } while (0) #define INTEGER_HASH_2(KEY, RESULT) do { \ (RESULT) += ~((unsigned long)(KEY)); \ } while (0) #define return_INTEGER_HASH_2(KEY) do { \ unsigned long _result_ = 0; \ INTEGER_HASH_2 ((KEY), _result_); \ return _result_; \ } while (0) #define INTEGER_COMPARE(X, Y, RESULT) do { \ (RESULT) = X - Y; \ } while (0) #define return_INTEGER_COMPARE(X, Y) do { \ int _result_; \ INTEGER_COMPARE (X, Y, _result_); \ return _result_; \ } while (0) /* hash and comparison macros for address keys. */ #define ADDRESS_HASH_1(KEY, RESULT) INTEGER_HASH_1 (((unsigned long)(KEY)) >> 3, (RESULT)) #define ADDRESS_HASH_2(KEY, RESULT) INTEGER_HASH_2 (((unsigned long)(KEY)) >> 3, (RESULT)) #define ADDRESS_COMPARE(X, Y, RESULT) INTEGER_COMPARE ((X), (Y), (RESULT)) #define return_ADDRESS_HASH_1(KEY) return_INTEGER_HASH_1 (((unsigned long)(KEY)) >> 3) #define return_ADDRESS_HASH_2(KEY) return_INTEGER_HASH_2 (((unsigned long)(KEY)) >> 3) #define return_ADDRESS_COMPARE(X, Y) return_INTEGER_COMPARE ((X), (Y)) #endif /* not _hash_h_ */
/* Definitions of dependency data structures for GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* Flag bits for the second argument to `read_makefile'. These flags are saved in the `changed' field of each `struct dep' in the chain returned by `read_all_makefiles'. */ #define RM_NO_DEFAULT_GOAL (1 << 0) /* Do not set default goal. */ #define RM_INCLUDED (1 << 1) /* Search makefile search path. */ #define RM_DONTCARE (1 << 2) /* No error if it doesn't exist. */ #define RM_NO_TILDE (1 << 3) /* Don't expand ~ in file name. */ #define RM_NOFLAG 0 /* Structure representing one dependency of a file. Each struct file's `deps' points to a chain of these, chained through the `next'. `stem' is the stem for this dep line of static pattern rule or NULL. Note that the first two words of this match a struct nameseq. */ struct dep { struct dep *next; const char *name; const char *stem; struct file *file; unsigned int changed : 8; unsigned int ignore_mtime : 1; unsigned int staticpattern : 1; unsigned int need_2nd_expansion : 1; unsigned int dontcare : 1; }; /* Structure used in chains of names, for parsing and globbing. */ struct nameseq { struct nameseq *next; const char *name; }; #define PARSEFS_NONE (0x0000) #define PARSEFS_NOSTRIP (0x0001) #define PARSEFS_NOAR (0x0002) #define PARSEFS_NOGLOB (0x0004) #define PARSEFS_EXISTS (0x0008) #define PARSEFS_NOCACHE (0x0010) #define PARSE_FILE_SEQ(_s,_t,_c,_p,_f) \ (_t *)parse_file_seq ((_s),sizeof (_t),(_c),(_p),(_f)) #ifdef VMS void *parse_file_seq (); #else void *parse_file_seq (char **stringp, unsigned int size, int stopchar, const char *prefix, int flags); #endif char *tilde_expand (const char *name); #ifndef NO_ARCHIVES struct nameseq *ar_glob (const char *arname, const char *member_pattern, unsigned int size); #endif #define dep_name(d) ((d)->name == 0 ? (d)->file->name : (d)->name) #define alloc_dep() (xcalloc (sizeof (struct dep))) #define free_ns(_n) free (_n) #define free_dep(_d) free_ns (_d) struct dep *copy_dep_chain (const struct dep *d); void free_dep_chain (struct dep *d); void free_ns_chain (struct nameseq *n); struct dep *read_all_makefiles (const char **makefiles); void eval_buffer (char *buffer); int update_goal_chain (struct dep *goals);
/* Copyright (C) 1991, 1992, 1993, 1996, 1997, 1998, 1999 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef _FNMATCH_H #define _FNMATCH_H 1 #ifdef __cplusplus extern "C" { #endif #if defined __cplusplus || (defined __STDC__ && __STDC__) || defined WINDOWS32 # if !defined __GLIBC__ # undef __P # define __P(protos) protos # endif #else /* Not C++ or ANSI C. */ # undef __P # define __P(protos) () /* We can get away without defining `const' here only because in this file it is used only inside the prototype for `fnmatch', which is elided in non-ANSI C where `const' is problematical. */ #endif /* C++ or ANSI C. */ #ifndef const # if (defined __STDC__ && __STDC__) || defined __cplusplus || defined WINDOWS32 # define __const const # else # define __const # endif #endif /* We #undef these before defining them because some losing systems (HP-UX A.08.07 for example) define these in <unistd.h>. */ #undef FNM_PATHNAME #undef FNM_NOESCAPE #undef FNM_PERIOD /* Bits set in the FLAGS argument to `fnmatch'. */ #define FNM_PATHNAME (1 << 0) /* No wildcard can ever match `/'. */ #define FNM_NOESCAPE (1 << 1) /* Backslashes don't quote special chars. */ #define FNM_PERIOD (1 << 2) /* Leading `.' is matched only explicitly. */ #if !defined _POSIX_C_SOURCE || _POSIX_C_SOURCE < 2 || defined _GNU_SOURCE # define FNM_FILE_NAME FNM_PATHNAME /* Preferred GNU name. */ # define FNM_LEADING_DIR (1 << 3) /* Ignore `/...' after a match. */ # define FNM_CASEFOLD (1 << 4) /* Compare without regard to case. */ #endif /* Value returned by `fnmatch' if STRING does not match PATTERN. */ #define FNM_NOMATCH 1 /* This value is returned if the implementation does not support `fnmatch'. Since this is not the case here it will never be returned but the conformance test suites still require the symbol to be defined. */ #ifdef _XOPEN_SOURCE # define FNM_NOSYS (-1) #endif /* Match NAME against the filename pattern PATTERN, returning zero if it matches, FNM_NOMATCH if not. */ extern int fnmatch __P ((__const char *__pattern, __const char *__name, int __flags)); #ifdef __cplusplus } #endif #endif /* fnmatch.h */
/* Library function for scanning an archive file. Copyright (C) 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" #ifdef HAVE_FCNTL_H #include <fcntl.h> #else #include <sys/file.h> #endif #ifndef NO_ARCHIVES #ifdef VMS #include <lbrdef.h> #include <mhddef.h> #include <credef.h> #include <descrip.h> #include <ctype.h> #if __DECC #include <unixlib.h> #include <lbr$routines.h> #endif static void *VMS_lib_idx; static char *VMS_saved_memname; static time_t VMS_member_date; static long int (*VMS_function) (); static int VMS_get_member_info (struct dsc$descriptor_s *module, unsigned long *rfa) { int status, i; long int fnval; time_t val; static struct dsc$descriptor_s bufdesc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL }; struct mhddef *mhd; char filename[128]; bufdesc.dsc$a_pointer = filename; bufdesc.dsc$w_length = sizeof (filename); status = lbr$set_module (&VMS_lib_idx, rfa, &bufdesc, &bufdesc.dsc$w_length, 0); if (! (status & 1)) { error (NILF, _("lbr$set_module() failed to extract module info, status = %d"), status); lbr$close (&VMS_lib_idx); return 0; } mhd = (struct mhddef *) filename; #ifdef __DECC /* John Fowler <jfowler@nyx.net> writes this is needed in his environment, * but that decc$fix_time() isn't documented to work this way. Let me * know if this causes problems in other VMS environments. */ { /* Modified by M. Gehre at 11-JAN-2008 because old formula is wrong: * val = decc$fix_time (&mhd->mhd$l_datim) + timezone - daylight*3600; * a) daylight specifies, if the timezone has daylight saving enabled, not * if it is active * b) what we need is the information, if daylight saving was active, if * the library module was replaced. This information we get using the * localtime function */ struct tm *tmp; /* Conversion from VMS time to C time */ val = decc$fix_time (&mhd->mhd$l_datim); /* * Conversion from local time (stored in library) to GMT (needed for gmake) * Note: The tm_gmtoff element is a VMS extension to the ANSI standard. */ tmp = localtime (&val); val -= tmp->tm_gmtoff; } #endif for (i = 0; i < module->dsc$w_length; i++) filename[i] = _tolower ((unsigned char)module->dsc$a_pointer[i]); filename[i] = '\0'; VMS_member_date = (time_t) -1; fnval = (*VMS_function) (-1, filename, 0, 0, 0, 0, val, 0, 0, 0, VMS_saved_memname); if (fnval) { VMS_member_date = fnval; return 0; } else return 1; } /* Takes three arguments ARCHIVE, FUNCTION and ARG. Open the archive named ARCHIVE, find its members one by one, and for each one call FUNCTION with the following arguments: archive file descriptor for reading the data, member name, member name might be truncated flag, member header position in file, member data position in file, member data size, member date, member uid, member gid, member protection mode, ARG. NOTE: on VMS systems, only name, date, and arg are meaningful! The descriptor is poised to read the data of the member when FUNCTION is called. It does not matter how much data FUNCTION reads. If FUNCTION returns nonzero, we immediately return what FUNCTION returned. Returns -1 if archive does not exist, Returns -2 if archive has invalid format. Returns 0 if have scanned successfully. */ long int ar_scan (const char *archive, ar_member_func_t function, const void *arg) { char *p; static struct dsc$descriptor_s libdesc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL }; unsigned long func = LBR$C_READ; unsigned long type = LBR$C_TYP_UNK; unsigned long index = 1; int status; status = lbr$ini_control (&VMS_lib_idx, &func, &type, 0); if (! (status & 1)) { error (NILF, _("lbr$ini_control() failed with status = %d"), status); return -2; } /* there is no such descriptor with "const char *dsc$a_pointer" */ libdesc.dsc$a_pointer = (char *)archive; libdesc.dsc$w_length = strlen (archive); status = lbr$open (&VMS_lib_idx, &libdesc, 0, 0, 0, 0, 0); if (! (status & 1)) { error (NILF, _("unable to open library `%s' to lookup member `%s'"), archive, (char *)arg); return -1; } VMS_saved_memname = (char *)arg; /* For comparison, delete .obj from arg name. */ p = strrchr (VMS_saved_memname, '.'); if (p) *p = '\0'; VMS_function = function; VMS_member_date = (time_t) -1; lbr$get_index (&VMS_lib_idx, &index, VMS_get_member_info, 0); /* Undo the damage. */ if (p) *p = '.'; lbr$close (&VMS_lib_idx); return VMS_member_date > 0 ? VMS_member_date : 0; } #else /* !VMS */ /* SCO Unix's compiler defines both of these. */ #ifdef M_UNIX #undef M_XENIX #endif /* On the sun386i and in System V rel 3, ar.h defines two different archive formats depending upon whether you have defined PORTAR (normal) or PORT5AR (System V Release 1). There is no default, one or the other must be defined to have a nonzero value. */ #if (!defined (PORTAR) || PORTAR == 0) && (!defined (PORT5AR) || PORT5AR == 0) #undef PORTAR #ifdef M_XENIX /* According to Jim Sievert <jas1@rsvl.unisys.com>, for SCO XENIX defining PORTAR to 1 gets the wrong archive format, and defining it to 0 gets the right one. */ #define PORTAR 0 #else #define PORTAR 1 #endif #endif /* On AIX, define these symbols to be sure to get both archive formats. AIX 4.3 introduced the "big" archive format to support 64-bit object files, so on AIX 4.3 systems we need to support both the "normal" and "big" archive formats. An archive's format is indicated in the "fl_magic" field of the "FL_HDR" structure. For a normal archive, this field will be the string defined by the AIAMAG symbol. For a "big" archive, it will be the string defined by the AIAMAGBIG symbol (at least on AIX it works this way). Note: we'll define these symbols regardless of which AIX version we're compiling on, but this is okay since we'll use the new symbols only if they're present. */ #ifdef _AIX # define __AR_SMALL__ # define __AR_BIG__ #endif #ifndef WINDOWS32 # ifndef __BEOS__ # include <ar.h> # else /* BeOS 5 doesn't have <ar.h> but has archives in the same format * as many other Unices. This was taken from GNU binutils for BeOS. */ # define ARMAG "!<arch>\n" /* String that begins an archive file. */ # define SARMAG 8 /* Size of that string. */ # define ARFMAG "`\n" /* String in ar_fmag at end of each header. */ struct ar_hdr { char ar_name[16]; /* Member file name, sometimes / terminated. */ char ar_date[12]; /* File date, decimal seconds since Epoch. */ char ar_uid[6], ar_gid[6]; /* User and group IDs, in ASCII decimal. */ char ar_mode[8]; /* File mode, in ASCII octal. */ char ar_size[10]; /* File size, in ASCII decimal. */ char ar_fmag[2]; /* Always contains ARFMAG. */ }; # endif # define TOCHAR(_m) (_m) #else /* These should allow us to read Windows (VC++) libraries (according to Frank * Libbrecht <frankl@abzx.belgium.hp.com>) */ # include <windows.h> # include <windef.h> # include <io.h> # define ARMAG IMAGE_ARCHIVE_START # define SARMAG IMAGE_ARCHIVE_START_SIZE # define ar_hdr _IMAGE_ARCHIVE_MEMBER_HEADER # define ar_name Name # define ar_mode Mode # define ar_size Size # define ar_date Date # define ar_uid UserID # define ar_gid GroupID /* In Windows the member names have type BYTE so we must cast them. */ # define TOCHAR(_m) ((char *)(_m)) #endif /* Cray's <ar.h> apparently defines this. */ #ifndef AR_HDR_SIZE # define AR_HDR_SIZE (sizeof (struct ar_hdr)) #endif /* Takes three arguments ARCHIVE, FUNCTION and ARG. Open the archive named ARCHIVE, find its members one by one, and for each one call FUNCTION with the following arguments: archive file descriptor for reading the data, member name, member name might be truncated flag, member header position in file, member data position in file, member data size, member date, member uid, member gid, member protection mode, ARG. The descriptor is poised to read the data of the member when FUNCTION is called. It does not matter how much data FUNCTION reads. If FUNCTION returns nonzero, we immediately return what FUNCTION returned. Returns -1 if archive does not exist, Returns -2 if archive has invalid format. Returns 0 if have scanned successfully. */ long int ar_scan (const char *archive, ar_member_func_t function, const void *arg) { #ifdef AIAMAG FL_HDR fl_header; #ifdef AIAMAGBIG int big_archive = 0; FL_HDR_BIG fl_header_big; #endif #else int long_name = 0; #endif char *namemap = 0; int desc = open (archive, O_RDONLY, 0); if (desc < 0) return -1; #ifdef SARMAG { char buf[SARMAG]; register int nread = read (desc, buf, SARMAG); if (nread != SARMAG || memcmp (buf, ARMAG, SARMAG)) { (void) close (desc); return -2; } } #else #ifdef AIAMAG { register int nread = read (desc, &fl_header, FL_HSZ); if (nread != FL_HSZ) { (void) close (desc); return -2; } #ifdef AIAMAGBIG /* If this is a "big" archive, then set the flag and re-read the header into the "big" structure. */ if (!memcmp (fl_header.fl_magic, AIAMAGBIG, SAIAMAG)) { big_archive = 1; /* seek back to beginning of archive */ if (lseek (desc, 0, 0) < 0) { (void) close (desc); return -2; } /* re-read the header into the "big" structure */ nread = read (desc, &fl_header_big, FL_HSZ_BIG); if (nread != FL_HSZ_BIG) { (void) close (desc); return -2; } } else #endif /* Check to make sure this is a "normal" archive. */ if (memcmp (fl_header.fl_magic, AIAMAG, SAIAMAG)) { (void) close (desc); return -2; } } #else { #ifndef M_XENIX int buf; #else unsigned short int buf; #endif register int nread = read(desc, &buf, sizeof (buf)); if (nread != sizeof (buf) || buf != ARMAG) { (void) close (desc); return -2; } } #endif #endif /* Now find the members one by one. */ { #ifdef SARMAG register long int member_offset = SARMAG; #else #ifdef AIAMAG long int member_offset; long int last_member_offset; #ifdef AIAMAGBIG if ( big_archive ) { sscanf (fl_header_big.fl_fstmoff, "%20ld", &member_offset); sscanf (fl_header_big.fl_lstmoff, "%20ld", &last_member_offset); } else #endif { sscanf (fl_header.fl_fstmoff, "%12ld", &member_offset); sscanf (fl_header.fl_lstmoff, "%12ld", &last_member_offset); } if (member_offset == 0) { /* Empty archive. */ close (desc); return 0; } #else #ifndef M_XENIX register long int member_offset = sizeof (int); #else /* Xenix. */ register long int member_offset = sizeof (unsigned short int); #endif /* Not Xenix. */ #endif #endif while (1) { register int nread; struct ar_hdr member_header; #ifdef AIAMAGBIG struct ar_hdr_big member_header_big; #endif #ifdef AIAMAG char name[256]; int name_len; long int dateval; int uidval, gidval; long int data_offset; #else char namebuf[sizeof member_header.ar_name + 1]; char *name; int is_namemap; /* Nonzero if this entry maps long names. */ #endif long int eltsize; int eltmode; long int fnval; if (lseek (desc, member_offset, 0) < 0) { (void) close (desc); return -2; } #ifdef AIAMAG #define AR_MEMHDR_SZ(x) (sizeof(x) - sizeof (x._ar_name)) #ifdef AIAMAGBIG if (big_archive) { nread = read (desc, &member_header_big, AR_MEMHDR_SZ(member_header_big) ); if (nread != AR_MEMHDR_SZ(member_header_big)) { (void) close (desc); return -2; } sscanf (member_header_big.ar_namlen, "%4d", &name_len); nread = read (desc, name, name_len); if (nread != name_len) { (void) close (desc); return -2; } name[name_len] = 0; sscanf (member_header_big.ar_date, "%12ld", &dateval); sscanf (member_header_big.ar_uid, "%12d", &uidval); sscanf (member_header_big.ar_gid, "%12d", &gidval); sscanf (member_header_big.ar_mode, "%12o", &eltmode); sscanf (member_header_big.ar_size, "%20ld", &eltsize); data_offset = (member_offset + AR_MEMHDR_SZ(member_header_big) + name_len + 2); } else #endif { nread = read (desc, &member_header, AR_MEMHDR_SZ(member_header) ); if (nread != AR_MEMHDR_SZ(member_header)) { (void) close (desc); return -2; } sscanf (member_header.ar_namlen, "%4d", &name_len); nread = read (desc, name, name_len); if (nread != name_len) { (void) close (desc); return -2; } name[name_len] = 0; sscanf (member_header.ar_date, "%12ld", &dateval); sscanf (member_header.ar_uid, "%12d", &uidval); sscanf (member_header.ar_gid, "%12d", &gidval); sscanf (member_header.ar_mode, "%12o", &eltmode); sscanf (member_header.ar_size, "%12ld", &eltsize); data_offset = (member_offset + AR_MEMHDR_SZ(member_header) + name_len + 2); } data_offset += data_offset % 2; fnval = (*function) (desc, name, 0, member_offset, data_offset, eltsize, dateval, uidval, gidval, eltmode, arg); #else /* Not AIAMAG. */ nread = read (desc, &member_header, AR_HDR_SIZE); if (nread == 0) /* No data left means end of file; that is OK. */ break; if (nread != AR_HDR_SIZE #if defined(ARFMAG) || defined(ARFZMAG) || ( # ifdef ARFMAG memcmp (member_header.ar_fmag, ARFMAG, 2) # else 1 # endif && # ifdef ARFZMAG memcmp (member_header.ar_fmag, ARFZMAG, 2) # else 1 # endif ) #endif ) { (void) close (desc); return -2; } name = namebuf; memcpy (name, member_header.ar_name, sizeof member_header.ar_name); { register char *p = name + sizeof member_header.ar_name; do *p = '\0'; while (p > name && *--p == ' '); #ifndef AIAMAG /* If the member name is "//" or "ARFILENAMES/" this may be a list of file name mappings. The maximum file name length supported by the standard archive format is 14 characters. This member will actually always be the first or second entry in the archive, but we don't check that. */ is_namemap = (!strcmp (name, "//") || !strcmp (name, "ARFILENAMES/")); #endif /* Not AIAMAG. */ /* On some systems, there is a slash after each member name. */ if (*p == '/') *p = '\0'; #ifndef AIAMAG /* If the member name starts with a space or a slash, this is an index into the file name mappings (used by GNU ar). Otherwise if the member name looks like #1/NUMBER the real member name appears in the element data (used by 4.4BSD). */ if (! is_namemap && (name[0] == ' ' || name[0] == '/') && namemap != 0) { name = namemap + atoi (name + 1); long_name = 1; } else if (name[0] == '#' && name[1] == '1' && name[2] == '/') { int namesize = atoi (name + 3); name = alloca (namesize + 1); nread = read (desc, name, namesize); if (nread != namesize) { close (desc); return -2; } name[namesize] = '\0'; long_name = 1; } #endif /* Not AIAMAG. */ } #ifndef M_XENIX sscanf (TOCHAR (member_header.ar_mode), "%o", &eltmode); eltsize = atol (TOCHAR (member_header.ar_size)); #else /* Xenix. */ eltmode = (unsigned short int) member_header.ar_mode; eltsize = member_header.ar_size; #endif /* Not Xenix. */ fnval = (*function) (desc, name, ! long_name, member_offset, member_offset + AR_HDR_SIZE, eltsize, #ifndef M_XENIX atol (TOCHAR (member_header.ar_date)), atoi (TOCHAR (member_header.ar_uid)), atoi (TOCHAR (member_header.ar_gid)), #else /* Xenix. */ member_header.ar_date, member_header.ar_uid, member_header.ar_gid, #endif /* Not Xenix. */ eltmode, arg); #endif /* AIAMAG. */ if (fnval) { (void) close (desc); return fnval; } #ifdef AIAMAG if (member_offset == last_member_offset) /* End of the chain. */ break; #ifdef AIAMAGBIG if (big_archive) sscanf (member_header_big.ar_nxtmem, "%20ld", &member_offset); else #endif sscanf (member_header.ar_nxtmem, "%12ld", &member_offset); if (lseek (desc, member_offset, 0) != member_offset) { (void) close (desc); return -2; } #else /* If this member maps archive names, we must read it in. The name map will always precede any members whose names must be mapped. */ if (is_namemap) { char *clear; char *limit; namemap = alloca (eltsize); nread = read (desc, namemap, eltsize); if (nread != eltsize) { (void) close (desc); return -2; } /* The names are separated by newlines. Some formats have a trailing slash. Null terminate the strings for convenience. */ limit = namemap + eltsize; for (clear = namemap; clear < limit; clear++) { if (*clear == '\n') { *clear = '\0'; if (clear[-1] == '/') clear[-1] = '\0'; } } is_namemap = 0; } member_offset += AR_HDR_SIZE + eltsize; if (member_offset % 2 != 0) member_offset++; #endif } } close (desc); return 0; } #endif /* !VMS */ /* Return nonzero iff NAME matches MEM. If TRUNCATED is nonzero, MEM may be truncated to sizeof (struct ar_hdr.ar_name) - 1. */ int ar_name_equal (const char *name, const char *mem, int truncated) { const char *p; p = strrchr (name, '/'); if (p != 0) name = p + 1; #ifndef VMS if (truncated) { #ifdef AIAMAG /* TRUNCATED should never be set on this system. */ abort (); #else struct ar_hdr hdr; #if !defined (__hpux) && !defined (cray) return strneq (name, mem, sizeof(hdr.ar_name) - 1); #else return strneq (name, mem, sizeof(hdr.ar_name) - 2); #endif /* !__hpux && !cray */ #endif /* !AIAMAG */ } #endif /* !VMS */ return !strcmp (name, mem); } #ifndef VMS /* ARGSUSED */ static long int ar_member_pos (int desc UNUSED, const char *mem, int truncated, long int hdrpos, long int datapos UNUSED, long int size UNUSED, long int date UNUSED, int uid UNUSED, int gid UNUSED, int mode UNUSED, const void *name) { if (!ar_name_equal (name, mem, truncated)) return 0; return hdrpos; } /* Set date of member MEMNAME in archive ARNAME to current time. Returns 0 if successful, -1 if file ARNAME does not exist, -2 if not a valid archive, -3 if other random system call error (including file read-only), 1 if valid but member MEMNAME does not exist. */ int ar_member_touch (const char *arname, const char *memname) { long int pos = ar_scan (arname, ar_member_pos, memname); int fd; struct ar_hdr ar_hdr; int i; unsigned int ui; struct stat statbuf; if (pos < 0) return (int) pos; if (!pos) return 1; fd = open (arname, O_RDWR, 0666); if (fd < 0) return -3; /* Read in this member's header */ if (lseek (fd, pos, 0) < 0) goto lose; if (AR_HDR_SIZE != read (fd, &ar_hdr, AR_HDR_SIZE)) goto lose; /* Write back the header, thus touching the archive file. */ if (lseek (fd, pos, 0) < 0) goto lose; if (AR_HDR_SIZE != write (fd, &ar_hdr, AR_HDR_SIZE)) goto lose; /* The file's mtime is the time we we want. */ EINTRLOOP (i, fstat (fd, &statbuf)); if (i < 0) goto lose; #if defined(ARFMAG) || defined(ARFZMAG) || defined(AIAMAG) || defined(WINDOWS32) /* Advance member's time to that time */ for (ui = 0; ui < sizeof ar_hdr.ar_date; ui++) ar_hdr.ar_date[ui] = ' '; sprintf (TOCHAR (ar_hdr.ar_date), "%ld", (long int) statbuf.st_mtime); #ifdef AIAMAG ar_hdr.ar_date[strlen(ar_hdr.ar_date)] = ' '; #endif #else ar_hdr.ar_date = statbuf.st_mtime; #endif /* Write back this member's header */ if (lseek (fd, pos, 0) < 0) goto lose; if (AR_HDR_SIZE != write (fd, &ar_hdr, AR_HDR_SIZE)) goto lose; close (fd); return 0; lose: i = errno; close (fd); errno = i; return -3; } #endif #ifdef TEST long int describe_member (int desc, const char *name, int truncated, long int hdrpos, long int datapos, long int size, long int date, int uid, int gid, int mode, const void *arg) { extern char *ctime (); printf (_("Member `%s'%s: %ld bytes at %ld (%ld).\n"), name, truncated ? _(" (name might be truncated)") : "", size, hdrpos, datapos); printf (_(" Date %s"), ctime (&date)); printf (_(" uid = %d, gid = %d, mode = 0%o.\n"), uid, gid, mode); return 0; } int main (int argc, char **argv) { ar_scan (argv[1], describe_member, NULL); return 0; } #endif /* TEST. */ #endif /* NO_ARCHIVES. */
/* Command processing for GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" #include "dep.h" #include "filedef.h" #include "variable.h" #include "job.h" #include "commands.h" #ifdef WINDOWS32 #include <windows.h> #include "w32err.h" #endif #if VMS # define FILE_LIST_SEPARATOR ',' #else # define FILE_LIST_SEPARATOR ' ' #endif int remote_kill (int id, int sig); #ifndef HAVE_UNISTD_H int getpid (); #endif static unsigned long dep_hash_1 (const void *key) { const struct dep *d = key; return_STRING_HASH_1 (dep_name (d)); } static unsigned long dep_hash_2 (const void *key) { const struct dep *d = key; return_STRING_HASH_2 (dep_name (d)); } static int dep_hash_cmp (const void *x, const void *y) { const struct dep *dx = x; const struct dep *dy = y; return strcmp (dep_name (dx), dep_name (dy)); } /* Set FILE's automatic variables up. */ void set_file_variables (struct file *file) { struct dep *d; const char *at, *percent, *star, *less; #ifndef NO_ARCHIVES /* If the target is an archive member `lib(member)', then $@ is `lib' and $% is `member'. */ if (ar_name (file->name)) { unsigned int len; const char *cp; char *p; cp = strchr (file->name, '('); p = alloca (cp - file->name + 1); memcpy (p, file->name, cp - file->name); p[cp - file->name] = '\0'; at = p; len = strlen (cp + 1); p = alloca (len); memcpy (p, cp + 1, len - 1); p[len - 1] = '\0'; percent = p; } else #endif /* NO_ARCHIVES. */ { at = file->name; percent = ""; } /* $* is the stem from an implicit or static pattern rule. */ if (file->stem == 0) { /* In Unix make, $* is set to the target name with any suffix in the .SUFFIXES list stripped off for explicit rules. We store this in the `stem' member. */ const char *name; unsigned int len; #ifndef NO_ARCHIVES if (ar_name (file->name)) { name = strchr (file->name, '(') + 1; len = strlen (name) - 1; } else #endif { name = file->name; len = strlen (name); } for (d = enter_file (strcache_add (".SUFFIXES"))->deps; d ; d = d->next) { unsigned int slen = strlen (dep_name (d)); if (len > slen && strneq (dep_name (d), name + (len - slen), slen)) { file->stem = strcache_add_len (name, len - slen); break; } } if (d == 0) file->stem = ""; } star = file->stem; /* $< is the first not order-only dependency. */ less = ""; for (d = file->deps; d != 0; d = d->next) if (!d->ignore_mtime) { if (!d->need_2nd_expansion) less = dep_name (d); break; } if (file->cmds == default_file->cmds) /* This file got its commands from .DEFAULT. In this case $< is the same as $@. */ less = at; #define DEFINE_VARIABLE(name, len, value) \ (void) define_variable_for_file (name,len,value,o_automatic,0,file) /* Define the variables. */ DEFINE_VARIABLE ("<", 1, less); DEFINE_VARIABLE ("*", 1, star); DEFINE_VARIABLE ("@", 1, at); DEFINE_VARIABLE ("%", 1, percent); /* Compute the values for $^, $+, $?, and $|. */ { static char *plus_value=0, *bar_value=0, *qmark_value=0; static unsigned int plus_max=0, bar_max=0, qmark_max=0; unsigned int qmark_len, plus_len, bar_len; char *cp; char *caret_value; char *qp; char *bp; unsigned int len; struct hash_table dep_hash; void **slot; /* Compute first the value for $+, which is supposed to contain duplicate dependencies as they were listed in the makefile. */ plus_len = 0; bar_len = 0; for (d = file->deps; d != 0; d = d->next) { if (!d->need_2nd_expansion) { if (d->ignore_mtime) bar_len += strlen (dep_name (d)) + 1; else plus_len += strlen (dep_name (d)) + 1; } } if (bar_len == 0) bar_len++; if (plus_len == 0) plus_len++; if (plus_len > plus_max) plus_value = xrealloc (plus_value, plus_max = plus_len); cp = plus_value; qmark_len = plus_len + 1; /* Will be this or less. */ for (d = file->deps; d != 0; d = d->next) if (! d->ignore_mtime && ! d->need_2nd_expansion) { const char *c = dep_name (d); #ifndef NO_ARCHIVES if (ar_name (c)) { c = strchr (c, '(') + 1; len = strlen (c) - 1; } else #endif len = strlen (c); memcpy (cp, c, len); cp += len; *cp++ = FILE_LIST_SEPARATOR; if (! (d->changed || always_make_flag)) qmark_len -= len + 1; /* Don't space in $? for this one. */ } /* Kill the last space and define the variable. */ cp[cp > plus_value ? -1 : 0] = '\0'; DEFINE_VARIABLE ("+", 1, plus_value); /* Compute the values for $^, $?, and $|. */ cp = caret_value = plus_value; /* Reuse the buffer; it's big enough. */ if (qmark_len > qmark_max) qmark_value = xrealloc (qmark_value, qmark_max = qmark_len); qp = qmark_value; if (bar_len > bar_max) bar_value = xrealloc (bar_value, bar_max = bar_len); bp = bar_value; /* Make sure that no dependencies are repeated in $^, $?, and $|. It would be natural to combine the next two loops but we can't do it because of a situation where we have two dep entries, the first is order-only and the second is normal (see below). */ hash_init (&dep_hash, 500, dep_hash_1, dep_hash_2, dep_hash_cmp); for (d = file->deps; d != 0; d = d->next) { if (d->need_2nd_expansion) continue; slot = hash_find_slot (&dep_hash, d); if (HASH_VACANT (*slot)) hash_insert_at (&dep_hash, d, slot); else { /* Check if the two prerequisites have different ignore_mtime. If so then we need to "upgrade" one that is order-only. */ struct dep* hd = (struct dep*) *slot; if (d->ignore_mtime != hd->ignore_mtime) d->ignore_mtime = hd->ignore_mtime = 0; } } for (d = file->deps; d != 0; d = d->next) { const char *c; if (d->need_2nd_expansion || hash_find_item (&dep_hash, d) != d) continue; c = dep_name (d); #ifndef NO_ARCHIVES if (ar_name (c)) { c = strchr (c, '(') + 1; len = strlen (c) - 1; } else #endif len = strlen (c); if (d->ignore_mtime) { memcpy (bp, c, len); bp += len; *bp++ = FILE_LIST_SEPARATOR; } else { memcpy (cp, c, len); cp += len; *cp++ = FILE_LIST_SEPARATOR; if (d->changed || always_make_flag) { memcpy (qp, c, len); qp += len; *qp++ = FILE_LIST_SEPARATOR; } } } hash_free (&dep_hash, 0); /* Kill the last spaces and define the variables. */ cp[cp > caret_value ? -1 : 0] = '\0'; DEFINE_VARIABLE ("^", 1, caret_value); qp[qp > qmark_value ? -1 : 0] = '\0'; DEFINE_VARIABLE ("?", 1, qmark_value); bp[bp > bar_value ? -1 : 0] = '\0'; DEFINE_VARIABLE ("|", 1, bar_value); } #undef DEFINE_VARIABLE } /* Chop CMDS up into individual command lines if necessary. Also set the `lines_flags' and `any_recurse' members. */ void chop_commands (struct commands *cmds) { unsigned int nlines, idx; char **lines; /* If we don't have any commands, or we already parsed them, never mind. */ if (!cmds || cmds->command_lines != 0) return; /* Chop CMDS->commands up into lines in CMDS->command_lines. */ if (one_shell) { int l = strlen (cmds->commands); nlines = 1; lines = xmalloc (nlines * sizeof (char *)); lines[0] = xstrdup (cmds->commands); /* Strip the trailing newline. */ if (l > 0 && lines[0][l-1] == '\n') lines[0][l-1] = '\0'; } else { const char *p; nlines = 5; lines = xmalloc (nlines * sizeof (char *)); idx = 0; p = cmds->commands; while (*p != '\0') { const char *end = p; find_end:; end = strchr (end, '\n'); if (end == 0) end = p + strlen (p); else if (end > p && end[-1] == '\\') { int backslash = 1; const char *b; for (b = end - 2; b >= p && *b == '\\'; --b) backslash = !backslash; if (backslash) { ++end; goto find_end; } } if (idx == nlines) { nlines += 2; lines = xrealloc (lines, nlines * sizeof (char *)); } lines[idx++] = xstrndup (p, end - p); p = end; if (*p != '\0') ++p; } if (idx != nlines) { nlines = idx; lines = xrealloc (lines, nlines * sizeof (char *)); } } /* Finally, set the corresponding CMDS->lines_flags elements and the CMDS->any_recurse flag. */ cmds->ncommand_lines = nlines; cmds->command_lines = lines; cmds->any_recurse = 0; cmds->lines_flags = xmalloc (nlines); for (idx = 0; idx < nlines; ++idx) { int flags = 0; const char *p = lines[idx]; while (isblank (*p) || *p == '-' || *p == '@' || *p == '+') switch (*(p++)) { case '+': flags |= COMMANDS_RECURSE; break; case '@': flags |= COMMANDS_SILENT; break; case '-': flags |= COMMANDS_NOERROR; break; } /* If no explicit '+' was given, look for MAKE variable references. */ if (!(flags & COMMANDS_RECURSE) && (strstr (p, "$(MAKE)") != 0 || strstr (p, "${MAKE}") != 0)) flags |= COMMANDS_RECURSE; cmds->lines_flags[idx] = flags; cmds->any_recurse |= flags & COMMANDS_RECURSE; } } /* Execute the commands to remake FILE. If they are currently executing, return or have already finished executing, just return. Otherwise, fork off a child process to run the first command line in the sequence. */ void execute_file_commands (struct file *file) { const char *p; /* Don't go through all the preparations if the commands are nothing but whitespace. */ for (p = file->cmds->commands; *p != '\0'; ++p) if (!isspace ((unsigned char)*p) && *p != '-' && *p != '@') break; if (*p == '\0') { /* If there are no commands, assume everything worked. */ set_command_state (file, cs_running); file->update_status = 0; notice_finished_file (file); return; } /* First set the automatic variables according to this file. */ initialize_file_variables (file, 0); set_file_variables (file); /* Start the commands running. */ new_job (file); } /* This is set while we are inside fatal_error_signal, so things can avoid nonreentrant operations. */ int handling_fatal_signal = 0; /* Handle fatal signals. */ RETSIGTYPE fatal_error_signal (int sig) { #ifdef __MSDOS__ extern int dos_status, dos_command_running; if (dos_command_running) { /* That was the child who got the signal, not us. */ dos_status |= (sig << 8); return; } remove_intermediates (1); exit (EXIT_FAILURE); #else /* not __MSDOS__ */ #ifdef _AMIGA remove_intermediates (1); if (sig == SIGINT) fputs (_("*** Break.\n"), stderr); exit (10); #else /* not Amiga */ #ifdef WINDOWS32 extern HANDLE main_thread; /* Windows creates a sperate thread for handling Ctrl+C, so we need to suspend the main thread, or else we will have race conditions when both threads call reap_children. */ if (main_thread) { DWORD susp_count = SuspendThread (main_thread); if (susp_count != 0) fprintf (stderr, "SuspendThread: suspend count = %ld\n", susp_count); else if (susp_count == (DWORD)-1) { DWORD ierr = GetLastError (); fprintf (stderr, "SuspendThread: error %ld: %s\n", ierr, map_windows32_error_to_string (ierr)); } } #endif handling_fatal_signal = 1; /* Set the handling for this signal to the default. It is blocked now while we run this handler. */ signal (sig, SIG_DFL); /* A termination signal won't be sent to the entire process group, but it means we want to kill the children. */ if (sig == SIGTERM) { struct child *c; for (c = children; c != 0; c = c->next) if (!c->remote) (void) kill (c->pid, SIGTERM); } /* If we got a signal that means the user wanted to kill make, remove pending targets. */ if (sig == SIGTERM || sig == SIGINT #ifdef SIGHUP || sig == SIGHUP #endif #ifdef SIGQUIT || sig == SIGQUIT #endif ) { struct child *c; /* Remote children won't automatically get signals sent to the process group, so we must send them. */ for (c = children; c != 0; c = c->next) if (c->remote) (void) remote_kill (c->pid, sig); for (c = children; c != 0; c = c->next) delete_child_targets (c); /* Clean up the children. We don't just use the call below because we don't want to print the "Waiting for children" message. */ while (job_slots_used > 0) reap_children (1, 0); } else /* Wait for our children to die. */ while (job_slots_used > 0) reap_children (1, 1); /* Delete any non-precious intermediate files that were made. */ remove_intermediates (1); #ifdef SIGQUIT if (sig == SIGQUIT) /* We don't want to send ourselves SIGQUIT, because it will cause a core dump. Just exit instead. */ exit (EXIT_FAILURE); #endif #ifdef WINDOWS32 if (main_thread) CloseHandle (main_thread); /* Cannot call W32_kill with a pid (it needs a handle). The exit status of 130 emulates what happens in Bash. */ exit (130); #else /* Signal the same code; this time it will really be fatal. The signal will be unblocked when we return and arrive then to kill us. */ if (kill (getpid (), sig) < 0) pfatal_with_name ("kill"); #endif /* not WINDOWS32 */ #endif /* not Amiga */ #endif /* not __MSDOS__ */ } /* Delete FILE unless it's precious or not actually a file (phony), and it has changed on disk since we last stat'd it. */ static void delete_target (struct file *file, const char *on_behalf_of) { struct stat st; int e; if (file->precious || file->phony) return; #ifndef NO_ARCHIVES if (ar_name (file->name)) { time_t file_date = (file->last_mtime == NONEXISTENT_MTIME ? (time_t) -1 : (time_t) FILE_TIMESTAMP_S (file->last_mtime)); if (ar_member_date (file->name) != file_date) { if (on_behalf_of) error (NILF, _("*** [%s] Archive member `%s' may be bogus; not deleted"), on_behalf_of, file->name); else error (NILF, _("*** Archive member `%s' may be bogus; not deleted"), file->name); } return; } #endif EINTRLOOP (e, stat (file->name, &st)); if (e == 0 && S_ISREG (st.st_mode) && FILE_TIMESTAMP_STAT_MODTIME (file->name, st) != file->last_mtime) { if (on_behalf_of) error (NILF, _("*** [%s] Deleting file `%s'"), on_behalf_of, file->name); else error (NILF, _("*** Deleting file `%s'"), file->name); if (unlink (file->name) < 0 && errno != ENOENT) /* It disappeared; so what. */ perror_with_name ("unlink: ", file->name); } } /* Delete all non-precious targets of CHILD unless they were already deleted. Set the flag in CHILD to say they've been deleted. */ void delete_child_targets (struct child *child) { struct dep *d; if (child->deleted) return; /* Delete the target file if it changed. */ delete_target (child->file, NULL); /* Also remove any non-precious targets listed in the `also_make' member. */ for (d = child->file->also_make; d != 0; d = d->next) delete_target (d->file, child->file->name); child->deleted = 1; } /* Print out the commands in CMDS. */ void print_commands (const struct commands *cmds) { const char *s; fputs (_("# recipe to execute"), stdout); if (cmds->fileinfo.filenm == 0) puts (_(" (built-in):")); else printf (_(" (from `%s', line %lu):\n"), cmds->fileinfo.filenm, cmds->fileinfo.lineno); s = cmds->commands; while (*s != '\0') { const char *end; end = strchr (s, '\n'); if (end == 0) end = s + strlen (s); printf ("%c%.*s\n", cmd_prefix, (int) (end - s), s); s = end + (end[0] == '\n'); } }
/* Definitions for using variables in GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "hash.h" /* Codes in a variable definition saying where the definition came from. Increasing numeric values signify less-overridable definitions. */ enum variable_origin { o_default, /* Variable from the default set. */ o_env, /* Variable from environment. */ o_file, /* Variable given in a makefile. */ o_env_override, /* Variable from environment, if -e. */ o_command, /* Variable given by user. */ o_override, /* Variable from an `override' directive. */ o_automatic, /* Automatic variable -- cannot be set. */ o_invalid /* Core dump time. */ }; enum variable_flavor { f_bogus, /* Bogus (error) */ f_simple, /* Simple definition (:=) */ f_recursive, /* Recursive definition (=) */ f_append, /* Appending definition (+=) */ f_conditional /* Conditional definition (?=) */ }; /* Structure that represents one variable definition. Each bucket of the hash table is a chain of these, chained through `next'. */ #define EXP_COUNT_BITS 15 /* This gets all the bitfields into 32 bits */ #define EXP_COUNT_MAX ((1<<EXP_COUNT_BITS)-1) struct variable { char *name; /* Variable name. */ int length; /* strlen (name) */ char *value; /* Variable value. */ struct floc fileinfo; /* Where the variable was defined. */ unsigned int recursive:1; /* Gets recursively re-evaluated. */ unsigned int append:1; /* Nonzero if an appending target-specific variable. */ unsigned int conditional:1; /* Nonzero if set with a ?=. */ unsigned int per_target:1; /* Nonzero if a target-specific variable. */ unsigned int special:1; /* Nonzero if this is a special variable. */ unsigned int exportable:1; /* Nonzero if the variable _could_ be exported. */ unsigned int expanding:1; /* Nonzero if currently being expanded. */ unsigned int private_var:1; /* Nonzero avoids inheritance of this target-specific variable. */ unsigned int exp_count:EXP_COUNT_BITS; /* If >1, allow this many self-referential expansions. */ enum variable_flavor flavor ENUM_BITFIELD (3); /* Variable flavor. */ enum variable_origin origin ENUM_BITFIELD (3); /* Variable origin. */ enum variable_export { v_export, /* Export this variable. */ v_noexport, /* Don't export this variable. */ v_ifset, /* Export it if it has a non-default value. */ v_default /* Decide in target_environment. */ } export ENUM_BITFIELD (2); }; /* Structure that represents a variable set. */ struct variable_set { struct hash_table table; /* Hash table of variables. */ }; /* Structure that represents a list of variable sets. */ struct variable_set_list { struct variable_set_list *next; /* Link in the chain. */ struct variable_set *set; /* Variable set. */ int next_is_parent; /* True if next is a parent target. */ }; /* Structure used for pattern-specific variables. */ struct pattern_var { struct pattern_var *next; const char *suffix; const char *target; unsigned int len; struct variable variable; }; extern char *variable_buffer; extern struct variable_set_list *current_variable_set_list; extern struct variable *default_goal_var; /* expand.c */ char *variable_buffer_output (char *ptr, const char *string, unsigned int length); char *variable_expand (const char *line); char *variable_expand_for_file (const char *line, struct file *file); char *allocated_variable_expand_for_file (const char *line, struct file *file); #define allocated_variable_expand(line) \ allocated_variable_expand_for_file (line, (struct file *) 0) char *expand_argument (const char *str, const char *end); char *variable_expand_string (char *line, const char *string, long length); void install_variable_buffer (char **bufp, unsigned int *lenp); void restore_variable_buffer (char *buf, unsigned int len); /* function.c */ int handle_function (char **op, const char **stringp); int pattern_matches (const char *pattern, const char *percent, const char *str); char *subst_expand (char *o, const char *text, const char *subst, const char *replace, unsigned int slen, unsigned int rlen, int by_word); char *patsubst_expand_pat (char *o, const char *text, const char *pattern, const char *replace, const char *pattern_percent, const char *replace_percent); char *patsubst_expand (char *o, const char *text, char *pattern, char *replace); /* expand.c */ char *recursively_expand_for_file (struct variable *v, struct file *file); #define recursively_expand(v) recursively_expand_for_file (v, NULL) /* variable.c */ struct variable_set_list *create_new_variable_set (void); void free_variable_set (struct variable_set_list *); struct variable_set_list *push_new_variable_scope (void); void pop_variable_scope (void); void define_automatic_variables (void); void initialize_file_variables (struct file *file, int reading); void print_file_variables (const struct file *file); void print_variable_set (struct variable_set *set, char *prefix); void merge_variable_set_lists (struct variable_set_list **to_list, struct variable_set_list *from_list); struct variable *do_variable_definition (const struct floc *flocp, const char *name, const char *value, enum variable_origin origin, enum variable_flavor flavor, int target_var); char *parse_variable_definition (const char *line, enum variable_flavor *flavor); struct variable *assign_variable_definition (struct variable *v, char *line); struct variable *try_variable_definition (const struct floc *flocp, char *line, enum variable_origin origin, int target_var); void init_hash_global_variable_set (void); void hash_init_function_table (void); struct variable *lookup_variable (const char *name, unsigned int length); struct variable *lookup_variable_in_set (const char *name, unsigned int length, const struct variable_set *set); struct variable *define_variable_in_set (const char *name, unsigned int length, const char *value, enum variable_origin origin, int recursive, struct variable_set *set, const struct floc *flocp); /* Define a variable in the current variable set. */ #define define_variable(n,l,v,o,r) \ define_variable_in_set((n),(l),(v),(o),(r),\ current_variable_set_list->set,NILF) /* Define a variable with a constant name in the current variable set. */ #define define_variable_cname(n,v,o,r) \ define_variable_in_set((n),(sizeof (n) - 1),(v),(o),(r),\ current_variable_set_list->set,NILF) /* Define a variable with a location in the current variable set. */ #define define_variable_loc(n,l,v,o,r,f) \ define_variable_in_set((n),(l),(v),(o),(r),\ current_variable_set_list->set,(f)) /* Define a variable with a location in the global variable set. */ #define define_variable_global(n,l,v,o,r,f) \ define_variable_in_set((n),(l),(v),(o),(r),NULL,(f)) /* Define a variable in FILE's variable set. */ #define define_variable_for_file(n,l,v,o,r,f) \ define_variable_in_set((n),(l),(v),(o),(r),(f)->variables->set,NILF) void undefine_variable_in_set (const char *name, unsigned int length, enum variable_origin origin, struct variable_set *set); /* Remove variable from the current variable set. */ #define undefine_variable_global(n,l,o) \ undefine_variable_in_set((n),(l),(o),NULL) /* Warn that NAME is an undefined variable. */ #define warn_undefined(n,l) do{\ if (warn_undefined_variables_flag) \ error (reading_file, \ _("warning: undefined variable `%.*s'"), \ (int)(l), (n)); \ }while(0) char **target_environment (struct file *file); struct pattern_var *create_pattern_var (const char *target, const char *suffix); extern int export_all_variables; #define MAKELEVEL_NAME "MAKELEVEL" #define MAKELEVEL_LENGTH (sizeof (MAKELEVEL_NAME) - 1)
/* Definitions for managing subprocesses in GNU Make. Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #ifndef SEEN_JOB_H #define SEEN_JOB_H #ifdef HAVE_FCNTL_H # include <fcntl.h> #else # include <sys/file.h> #endif /* How to set close-on-exec for a file descriptor. */ #if !defined F_SETFD # define CLOSE_ON_EXEC(_d) #else # ifndef FD_CLOEXEC # define FD_CLOEXEC 1 # endif # define CLOSE_ON_EXEC(_d) (void) fcntl ((_d), F_SETFD, FD_CLOEXEC) #endif /* Structure describing a running or dead child process. */ struct child { struct child *next; /* Link in the chain. */ struct file *file; /* File being remade. */ char **environment; /* Environment for commands. */ char **command_lines; /* Array of variable-expanded cmd lines. */ unsigned int command_line; /* Index into above. */ char *command_ptr; /* Ptr into command_lines[command_line]. */ pid_t pid; /* Child process's ID number. */ #ifdef VMS int efn; /* Completion event flag number */ int cstatus; /* Completion status */ char *comname; /* Temporary command file name */ #endif char *sh_batch_file; /* Script file for shell commands */ unsigned int remote:1; /* Nonzero if executing remotely. */ unsigned int noerror:1; /* Nonzero if commands contained a `-'. */ unsigned int good_stdin:1; /* Nonzero if this child has a good stdin. */ unsigned int deleted:1; /* Nonzero if targets have been deleted. */ unsigned int dontcare:1; /* Saved dontcare flag. */ }; extern struct child *children; int is_bourne_compatible_shell(const char *path); void new_job (struct file *file); void reap_children (int block, int err); void start_waiting_jobs (void); char **construct_command_argv (char *line, char **restp, struct file *file, int cmd_flags, char** batch_file); #ifdef VMS int child_execute_job (char *argv, struct child *child); #elif defined(__EMX__) int child_execute_job (int stdin_fd, int stdout_fd, char **argv, char **envp); #else void child_execute_job (int stdin_fd, int stdout_fd, char **argv, char **envp); #endif #ifdef _AMIGA void exec_command (char **argv); #elif defined(__EMX__) int exec_command (char **argv, char **envp); #else void exec_command (char **argv, char **envp); #endif extern unsigned int job_slots_used; void block_sigs (void); #ifdef POSIX void unblock_sigs (void); #else #ifdef HAVE_SIGSETMASK extern int fatal_signal_mask; #define unblock_sigs() sigsetmask (0) #else #define unblock_sigs() #endif #endif extern unsigned int jobserver_tokens; #endif /* SEEN_JOB_H */
/* Definition of data structures describing shell commands for GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* Structure that gives the commands to make a file and information about where these commands came from. */ struct commands { struct floc fileinfo; /* Where commands were defined. */ char *commands; /* Commands text. */ unsigned int ncommand_lines;/* Number of command lines. */ char **command_lines; /* Commands chopped up into lines. */ char *lines_flags; /* One set of flag bits for each line. */ int any_recurse; /* Nonzero if any `lines_recurse' elt has */ /* the COMMANDS_RECURSE bit set. */ }; /* Bits in `lines_flags'. */ #define COMMANDS_RECURSE 1 /* Recurses: + or $(MAKE). */ #define COMMANDS_SILENT 2 /* Silent: @. */ #define COMMANDS_NOERROR 4 /* No errors: -. */ void execute_file_commands (struct file *file); void print_commands (const struct commands *cmds); void delete_child_targets (struct child *child); void chop_commands (struct commands *cmds); void set_file_variables (struct file *file);
/* Data base of default implicit rules for GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" #include "filedef.h" #include "variable.h" #include "rule.h" #include "dep.h" #include "job.h" #include "commands.h" /* Define GCC_IS_NATIVE if gcc is the native development environment on your system (gcc/bison/flex vs cc/yacc/lex). */ #if defined(__MSDOS__) || defined(__EMX__) # define GCC_IS_NATIVE #endif /* This is the default list of suffixes for suffix rules. `.s' must come last, so that a `.o' file will be made from a `.c' or `.p' or ... file rather than from a .s file. */ static char default_suffixes[] #ifdef VMS = ".exe .olb .ln .obj .c .cxx .cc .pas .p .for .f .r .y .l .mar \ .s .ss .i .ii .mod .sym .def .h .info .dvi .tex .texinfo .texi .txinfo \ .w .ch .cweb .web .com .sh .elc .el"; #elif defined(__EMX__) = ".out .a .ln .o .c .cc .C .cpp .p .f .F .m .r .y .l .ym .yl .s .S \ .mod .sym .def .h .info .dvi .tex .texinfo .texi .txinfo \ .w .ch .web .sh .elc .el .obj .exe .dll .lib"; #else = ".out .a .ln .o .c .cc .C .cpp .p .f .F .m .r .y .l .ym .yl .s .S \ .mod .sym .def .h .info .dvi .tex .texinfo .texi .txinfo \ .w .ch .web .sh .elc .el"; #endif static struct pspec default_pattern_rules[] = { { "(%)", "%", "$(AR) $(ARFLAGS) $@ $<" }, /* The X.out rules are only in BSD's default set because BSD Make has no null-suffix rules, so `foo.out' and `foo' are the same thing. */ #ifdef VMS { "%.exe", "%", "copy $< $@" }, #else { "%.out", "%", "@rm -f $@ \n cp $< $@" }, #endif /* Syntax is "ctangle foo.w foo.ch foo.c". */ { "%.c", "%.w %.ch", "$(CTANGLE) $^ $@" }, { "%.tex", "%.w %.ch", "$(CWEAVE) $^ $@" }, { 0, 0, 0 } }; static struct pspec default_terminal_rules[] = { #ifdef VMS /* RCS. */ { "%", "%$$5lv", /* Multinet style */ "if f$$search($@) .nes. \"\" then +$(CHECKOUT,v)" }, { "%", "[.$$rcs]%$$5lv", /* Multinet style */ "if f$$search($@) .nes. \"\" then +$(CHECKOUT,v)" }, { "%", "%_v", /* Normal style */ "if f$$search($@) .nes. \"\" then +$(CHECKOUT,v)" }, { "%", "[.rcs]%_v", /* Normal style */ "if f$$search($@) .nes. \"\" then +$(CHECKOUT,v)" }, /* SCCS. */ /* ain't no SCCS on vms */ #else /* RCS. */ { "%", "%,v", "$(CHECKOUT,v)" }, { "%", "RCS/%,v", "$(CHECKOUT,v)" }, { "%", "RCS/%", "$(CHECKOUT,v)" }, /* SCCS. */ { "%", "s.%", "$(GET) $(GFLAGS) $(SCCS_OUTPUT_OPTION) $<" }, { "%", "SCCS/s.%", "$(GET) $(GFLAGS) $(SCCS_OUTPUT_OPTION) $<" }, #endif /* !VMS */ { 0, 0, 0 } }; static char *default_suffix_rules[] = { #ifdef VMS ".obj.exe", "$(LINK.obj) $^ $(LOADLIBES) $(LDLIBS) $(CRT0) /exe=$@", ".mar.exe", "$(COMPILE.mar) $^ \n $(LINK.obj) $(subst .mar,.obj,$^) $(LOADLIBES) $(LDLIBS) $(CRT0) /exe=$@", ".s.exe", "$(COMPILE.s) $^ \n $(LINK.obj) $(subst .s,.obj,$^) $(LOADLIBES) $(LDLIBS) $(CRT0) /exe=$@", ".c.exe", "$(COMPILE.c) $^ \n $(LINK.obj) $(subst .c,.obj,$^) $(LOADLIBES) $(LDLIBS) $(CRT0) /exe=$@", ".cc.exe", #ifdef GCC_IS_NATIVE "$(COMPILE.cc) $^ \n $(LINK.obj) $(CXXSTARTUP),sys$$disk:[]$(subst .cc,.obj,$^) $(LOADLIBES) $(LXLIBS) $(LDLIBS) $(CXXRT0) /exe=$@", #else "$(COMPILE.cc) $^ \n $(CXXLINK.obj) $(subst .cc,.obj,$^) $(LOADLIBES) $(LXLIBS) $(LDLIBS) $(CXXRT0) /exe=$@", ".cxx.exe", "$(COMPILE.cxx) $^ \n $(CXXLINK.obj) $(subst .cxx,.obj,$^) $(LOADLIBES) $(LXLIBS) $(LDLIBS) $(CXXRT0) /exe=$@", #endif ".for.exe", "$(COMPILE.for) $^ \n $(LINK.obj) $(subst .for,.obj,$^) $(LOADLIBES) $(LDLIBS) /exe=$@", ".pas.exe", "$(COMPILE.pas) $^ \n $(LINK.obj) $(subst .pas,.obj,$^) $(LOADLIBES) $(LDLIBS) /exe=$@", ".com", "copy $< >$@", ".mar.obj", "$(COMPILE.mar) /obj=$@ $<", ".s.obj", "$(COMPILE.s) /obj=$@ $<", ".ss.obj", "$(COMPILE.s) /obj=$@ $<", ".c.i", "$(COMPILE.c)/prep /list=$@ $<", ".c.s", "$(COMPILE.c)/noobj/machine /list=$@ $<", ".i.s", "$(COMPILE.c)/noprep/noobj/machine /list=$@ $<", ".c.obj", "$(COMPILE.c) /obj=$@ $<", ".cc.ii", "$(COMPILE.cc)/prep /list=$@ $<", ".cc.ss", "$(COMPILE.cc)/noobj/machine /list=$@ $<", ".ii.ss", "$(COMPILE.cc)/noprep/noobj/machine /list=$@ $<", ".cc.obj", "$(COMPILE.cc) /obj=$@ $<", ".cxx.obj", "$(COMPILE.cxx) /obj=$@ $<", ".for.obj", "$(COMPILE.for) /obj=$@ $<", ".pas.obj", "$(COMPILE.pas) /obj=$@ $<", ".y.c", "$(YACC.y) $< \n rename y_tab.c $@", ".l.c", "$(LEX.l) $< \n rename lexyy.c $@", ".texinfo.info", "$(MAKEINFO) $<", ".tex.dvi", "$(TEX) $<", #else /* ! VMS */ ".o", "$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@", ".s", "$(LINK.s) $^ $(LOADLIBES) $(LDLIBS) -o $@", ".S", "$(LINK.S) $^ $(LOADLIBES) $(LDLIBS) -o $@", ".c", "$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@", ".cc", "$(LINK.cc) $^ $(LOADLIBES) $(LDLIBS) -o $@", ".C", "$(LINK.C) $^ $(LOADLIBES) $(LDLIBS) -o $@", ".cpp", "$(LINK.cpp) $^ $(LOADLIBES) $(LDLIBS) -o $@", ".f", "$(LINK.f) $^ $(LOADLIBES) $(LDLIBS) -o $@", ".m", "$(LINK.m) $^ $(LOADLIBES) $(LDLIBS) -o $@", ".p", "$(LINK.p) $^ $(LOADLIBES) $(LDLIBS) -o $@", ".F", "$(LINK.F) $^ $(LOADLIBES) $(LDLIBS) -o $@", ".r", "$(LINK.r) $^ $(LOADLIBES) $(LDLIBS) -o $@", ".mod", "$(COMPILE.mod) -o $@ -e $@ $^", ".def.sym", "$(COMPILE.def) -o $@ $<", ".sh", "cat $< >$@ \n chmod a+x $@", ".s.o", "$(COMPILE.s) -o $@ $<", ".S.o", "$(COMPILE.S) -o $@ $<", ".c.o", "$(COMPILE.c) $(OUTPUT_OPTION) $<", ".cc.o", "$(COMPILE.cc) $(OUTPUT_OPTION) $<", ".C.o", "$(COMPILE.C) $(OUTPUT_OPTION) $<", ".cpp.o", "$(COMPILE.cpp) $(OUTPUT_OPTION) $<", ".f.o", "$(COMPILE.f) $(OUTPUT_OPTION) $<", ".m.o", "$(COMPILE.m) $(OUTPUT_OPTION) $<", ".p.o", "$(COMPILE.p) $(OUTPUT_OPTION) $<", ".F.o", "$(COMPILE.F) $(OUTPUT_OPTION) $<", ".r.o", "$(COMPILE.r) $(OUTPUT_OPTION) $<", ".mod.o", "$(COMPILE.mod) -o $@ $<", ".c.ln", "$(LINT.c) -C$* $<", ".y.ln", #ifndef __MSDOS__ "$(YACC.y) $< \n $(LINT.c) -C$* y.tab.c \n $(RM) y.tab.c", #else "$(YACC.y) $< \n $(LINT.c) -C$* y_tab.c \n $(RM) y_tab.c", #endif ".l.ln", "@$(RM) $*.c\n $(LEX.l) $< > $*.c\n$(LINT.c) -i $*.c -o $@\n $(RM) $*.c", ".y.c", #ifndef __MSDOS__ "$(YACC.y) $< \n mv -f y.tab.c $@", #else "$(YACC.y) $< \n mv -f y_tab.c $@", #endif ".l.c", "@$(RM) $@ \n $(LEX.l) $< > $@", ".ym.m", "$(YACC.m) $< \n mv -f y.tab.c $@", ".lm.m", "@$(RM) $@ \n $(LEX.m) $< > $@", ".F.f", "$(PREPROCESS.F) $(OUTPUT_OPTION) $<", ".r.f", "$(PREPROCESS.r) $(OUTPUT_OPTION) $<", /* This might actually make lex.yy.c if there's no %R% directive in $*.l, but in that case why were you trying to make $*.r anyway? */ ".l.r", "$(LEX.l) $< > $@ \n mv -f lex.yy.r $@", ".S.s", "$(PREPROCESS.S) $< > $@", ".texinfo.info", "$(MAKEINFO) $(MAKEINFO_FLAGS) $< -o $@", ".texi.info", "$(MAKEINFO) $(MAKEINFO_FLAGS) $< -o $@", ".txinfo.info", "$(MAKEINFO) $(MAKEINFO_FLAGS) $< -o $@", ".tex.dvi", "$(TEX) $<", ".texinfo.dvi", "$(TEXI2DVI) $(TEXI2DVI_FLAGS) $<", ".texi.dvi", "$(TEXI2DVI) $(TEXI2DVI_FLAGS) $<", ".txinfo.dvi", "$(TEXI2DVI) $(TEXI2DVI_FLAGS) $<", ".w.c", "$(CTANGLE) $< - $@", /* The `-' says there is no `.ch' file. */ ".web.p", "$(TANGLE) $<", ".w.tex", "$(CWEAVE) $< - $@", /* The `-' says there is no `.ch' file. */ ".web.tex", "$(WEAVE) $<", #endif /* !VMS */ 0, 0, }; static const char *default_variables[] = { #ifdef VMS #ifdef __ALPHA "ARCH", "ALPHA", #endif #ifdef __ia64 "ARCH", "IA64", #endif #ifdef __VAX "ARCH", "VAX", #endif "AR", "library/obj", "ARFLAGS", "/replace", "AS", "macro", "MACRO", "macro", #ifdef GCC_IS_NATIVE "CC", "gcc", #else "CC", "cc", #endif "CD", "builtin_cd", "MAKE", "make", "ECHO", "write sys$$output \"", #ifdef GCC_IS_NATIVE "C++", "gcc/plus", "CXX", "gcc/plus", #else "C++", "cxx", "CXX", "cxx", "CXXLD", "cxxlink", #endif "CO", "co", "CPP", "$(CC) /preprocess_only", "FC", "fortran", /* System V uses these, so explicit rules using them should work. However, there is no way to make implicit rules use them and FC. */ "F77", "$(FC)", "F77FLAGS", "$(FFLAGS)", "LD", "link", "LEX", "lex", "PC", "pascal", "YACC", "bison/yacc", "YFLAGS", "/Define/Verbose", "BISON", "bison", "MAKEINFO", "makeinfo", "TEX", "tex", "TEXINDEX", "texindex", "RM", "delete/nolog", "CSTARTUP", "", #ifdef GCC_IS_NATIVE "CRT0", ",sys$$library:vaxcrtl.olb/lib,gnu_cc_library:crt0.obj", "CXXSTARTUP", "gnu_cc_library:crtbegin.obj", "CXXRT0", ",sys$$library:vaxcrtl.olb/lib,gnu_cc_library:crtend.obj,gnu_cc_library:gxx_main.obj", "LXLIBS", ",gnu_cc_library:libstdcxx.olb/lib,gnu_cc_library:libgccplus.olb/lib", "LDLIBS", ",gnu_cc_library:libgcc.olb/lib", #else "CRT0", "", "CXXSTARTUP", "", "CXXRT0", "", "LXLIBS", "", "LDLIBS", "", #endif "LINK.obj", "$(LD) $(LDFLAGS)", #ifndef GCC_IS_NATIVE "CXXLINK.obj", "$(CXXLD) $(LDFLAGS)", "COMPILE.cxx", "$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH)", #endif "COMPILE.c", "$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH)", "COMPILE.cc", "$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH)", "YACC.y", "$(YACC) $(YFLAGS)", "LEX.l", "$(LEX) $(LFLAGS)", "COMPILE.for", "$(FC) $(FFLAGS) $(TARGET_ARCH)", "COMPILE.pas", "$(PC) $(PFLAGS) $(CPPFLAGS) $(TARGET_ARCH)", "COMPILE.mar", "$(MACRO) $(MACROFLAGS)", "COMPILE.s", "$(AS) $(ASFLAGS) $(TARGET_MACH)", "LINT.c", "$(LINT) $(LINTFLAGS) $(CPPFLAGS) $(TARGET_ARCH)", "MV", "rename/new_version", "CP", "copy", #else /* !VMS */ "AR", "ar", "ARFLAGS", "rv", "AS", "as", #ifdef GCC_IS_NATIVE "CC", "gcc", # ifdef __MSDOS__ "CXX", "gpp", /* g++ is an invalid name on MSDOS */ # else "CXX", "gcc", # endif /* __MSDOS__ */ "OBJC", "gcc", #else "CC", "cc", "CXX", "g++", "OBJC", "cc", #endif /* This expands to $(CO) $(COFLAGS) $< $@ if $@ does not exist, and to the empty string if $@ does exist. */ "CHECKOUT,v", "+$(if $(wildcard $@),,$(CO) $(COFLAGS) $< $@)", "CO", "co", "COFLAGS", "", "CPP", "$(CC) -E", #ifdef CRAY "CF77PPFLAGS", "-P", "CF77PP", "/lib/cpp", "CFT", "cft77", "CF", "cf77", "FC", "$(CF)", #else /* Not CRAY. */ #ifdef _IBMR2 "FC", "xlf", #else #ifdef __convex__ "FC", "fc", #else "FC", "f77", #endif /* __convex__ */ #endif /* _IBMR2 */ /* System V uses these, so explicit rules using them should work. However, there is no way to make implicit rules use them and FC. */ "F77", "$(FC)", "F77FLAGS", "$(FFLAGS)", #endif /* Cray. */ "GET", SCCS_GET, "LD", "ld", #ifdef GCC_IS_NATIVE "LEX", "flex", #else "LEX", "lex", #endif "LINT", "lint", "M2C", "m2c", #ifdef pyr "PC", "pascal", #else #ifdef CRAY "PC", "PASCAL", "SEGLDR", "segldr", #else "PC", "pc", #endif /* CRAY. */ #endif /* pyr. */ #ifdef GCC_IS_NATIVE "YACC", "bison -y", #else "YACC", "yacc", /* Or "bison -y" */ #endif "MAKEINFO", "makeinfo", "TEX", "tex", "TEXI2DVI", "texi2dvi", "WEAVE", "weave", "CWEAVE", "cweave", "TANGLE", "tangle", "CTANGLE", "ctangle", "RM", "rm -f", "LINK.o", "$(CC) $(LDFLAGS) $(TARGET_ARCH)", "COMPILE.c", "$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c", "LINK.c", "$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)", "COMPILE.m", "$(OBJC) $(OBJCFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c", "LINK.m", "$(OBJC) $(OBJCFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)", "COMPILE.cc", "$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c", "COMPILE.C", "$(COMPILE.cc)", "COMPILE.cpp", "$(COMPILE.cc)", "LINK.cc", "$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)", "LINK.C", "$(LINK.cc)", "LINK.cpp", "$(LINK.cc)", "YACC.y", "$(YACC) $(YFLAGS)", "LEX.l", "$(LEX) $(LFLAGS) -t", "YACC.m", "$(YACC) $(YFLAGS)", "LEX.m", "$(LEX) $(LFLAGS) -t", "COMPILE.f", "$(FC) $(FFLAGS) $(TARGET_ARCH) -c", "LINK.f", "$(FC) $(FFLAGS) $(LDFLAGS) $(TARGET_ARCH)", "COMPILE.F", "$(FC) $(FFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c", "LINK.F", "$(FC) $(FFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)", "COMPILE.r", "$(FC) $(FFLAGS) $(RFLAGS) $(TARGET_ARCH) -c", "LINK.r", "$(FC) $(FFLAGS) $(RFLAGS) $(LDFLAGS) $(TARGET_ARCH)", "COMPILE.def", "$(M2C) $(M2FLAGS) $(DEFFLAGS) $(TARGET_ARCH)", "COMPILE.mod", "$(M2C) $(M2FLAGS) $(MODFLAGS) $(TARGET_ARCH)", "COMPILE.p", "$(PC) $(PFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c", "LINK.p", "$(PC) $(PFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)", "LINK.s", "$(CC) $(ASFLAGS) $(LDFLAGS) $(TARGET_MACH)", "COMPILE.s", "$(AS) $(ASFLAGS) $(TARGET_MACH)", "LINK.S", "$(CC) $(ASFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_MACH)", "COMPILE.S", "$(CC) $(ASFLAGS) $(CPPFLAGS) $(TARGET_MACH) -c", "PREPROCESS.S", "$(CC) -E $(CPPFLAGS)", "PREPROCESS.F", "$(FC) $(FFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -F", "PREPROCESS.r", "$(FC) $(FFLAGS) $(RFLAGS) $(TARGET_ARCH) -F", "LINT.c", "$(LINT) $(LINTFLAGS) $(CPPFLAGS) $(TARGET_ARCH)", #ifndef NO_MINUS_C_MINUS_O "OUTPUT_OPTION", "-o $@", #endif #ifdef SCCS_GET_MINUS_G "SCCS_OUTPUT_OPTION", "-G$@", #endif #ifdef _AMIGA ".LIBPATTERNS", "%.lib", #else #ifdef __MSDOS__ ".LIBPATTERNS", "lib%.a $(DJDIR)/lib/lib%.a", #else ".LIBPATTERNS", "lib%.so lib%.a", #endif #endif #endif /* !VMS */ 0, 0 }; /* Set up the default .SUFFIXES list. */ void set_default_suffixes (void) { suffix_file = enter_file (strcache_add (".SUFFIXES")); if (no_builtin_rules_flag) define_variable_cname ("SUFFIXES", "", o_default, 0); else { char *p = default_suffixes; suffix_file->deps = enter_prereqs(PARSE_FILE_SEQ (&p, struct dep, '\0', NULL, 0), NULL); define_variable_cname ("SUFFIXES", default_suffixes, o_default, 0); } } /* Enter the default suffix rules as file rules. This used to be done in install_default_implicit_rules, but that loses because we want the suffix rules installed before reading makefiles, and the pattern rules installed after. */ void install_default_suffix_rules (void) { char **s; if (no_builtin_rules_flag) return; for (s = default_suffix_rules; *s != 0; s += 2) { struct file *f = enter_file (strcache_add (s[0])); /* Don't clobber cmds given in a makefile if there were any. */ if (f->cmds == 0) { f->cmds = xmalloc (sizeof (struct commands)); f->cmds->fileinfo.filenm = 0; f->cmds->commands = s[1]; f->cmds->command_lines = 0; } } } /* Install the default pattern rules. */ void install_default_implicit_rules (void) { struct pspec *p; if (no_builtin_rules_flag) return; for (p = default_pattern_rules; p->target != 0; ++p) install_pattern_rule (p, 0); for (p = default_terminal_rules; p->target != 0; ++p) install_pattern_rule (p, 1); } void define_default_variables (void) { const char **s; if (no_builtin_variables_flag) return; for (s = default_variables; *s != 0; s += 2) define_variable (s[0], strlen (s[0]), s[1], o_default, 1); }
/* Definitions for using pattern rules in GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* Structure used for pattern (implicit) rules. */ struct rule { struct rule *next; const char **targets; /* Targets of the rule. */ unsigned int *lens; /* Lengths of each target. */ const char **suffixes; /* Suffixes (after `%') of each target. */ struct dep *deps; /* Dependencies of the rule. */ struct commands *cmds; /* Commands to execute. */ unsigned short num; /* Number of targets. */ char terminal; /* If terminal (double-colon). */ char in_use; /* If in use by a parent pattern_search. */ }; /* For calling install_pattern_rule. */ struct pspec { char *target, *dep, *commands; }; extern struct rule *pattern_rules; extern struct rule *last_pattern_rule; extern unsigned int num_pattern_rules; extern unsigned int max_pattern_deps; extern unsigned int max_pattern_targets; extern unsigned int max_pattern_dep_length; extern struct file *suffix_file; extern unsigned int maxsuffix; void count_implicit_rule_limits (void); void convert_to_pattern (void); void install_pattern_rule (struct pspec *p, int terminal); void create_pattern_rule (const char **targets, const char **target_percents, unsigned int num, int terminal, struct dep *deps, struct commands *commands, int override);
/* Directory hashing for GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" #include "hash.h" #ifdef HAVE_DIRENT_H # include <dirent.h> # define NAMLEN(dirent) strlen((dirent)->d_name) # ifdef VMS /* its prototype is in vmsdir.h, which is not needed for HAVE_DIRENT_H */ const char *vmsify (const char *name, int type); # endif #else # define dirent direct # define NAMLEN(dirent) (dirent)->d_namlen # ifdef HAVE_SYS_NDIR_H # include <sys/ndir.h> # endif # ifdef HAVE_SYS_DIR_H # include <sys/dir.h> # endif # ifdef HAVE_NDIR_H # include <ndir.h> # endif # ifdef HAVE_VMSDIR_H # include "vmsdir.h" # endif /* HAVE_VMSDIR_H */ #endif /* In GNU systems, <dirent.h> defines this macro for us. */ #ifdef _D_NAMLEN # undef NAMLEN # define NAMLEN(d) _D_NAMLEN(d) #endif #if (defined (POSIX) || defined (VMS) || defined (WINDOWS32)) && !defined (__GNU_LIBRARY__) /* Posix does not require that the d_ino field be present, and some systems do not provide it. */ # define REAL_DIR_ENTRY(dp) 1 # define FAKE_DIR_ENTRY(dp) #else # define REAL_DIR_ENTRY(dp) (dp->d_ino != 0) # define FAKE_DIR_ENTRY(dp) (dp->d_ino = 1) #endif /* POSIX */ #ifdef __MSDOS__ #include <ctype.h> #include <fcntl.h> /* If it's MSDOS that doesn't have _USE_LFN, disable LFN support. */ #ifndef _USE_LFN #define _USE_LFN 0 #endif static const char * dosify (const char *filename) { static char dos_filename[14]; char *df; int i; if (filename == 0 || _USE_LFN) return filename; /* FIXME: what about filenames which violate 8+3 constraints, like "config.h.in", or ".emacs"? */ if (strpbrk (filename, "\"*+,;<=>?[\\]|") != 0) return filename; df = dos_filename; /* First, transform the name part. */ for (i = 0; *filename != '\0' && i < 8 && *filename != '.'; ++i) *df++ = tolower ((unsigned char)*filename++); /* Now skip to the next dot. */ while (*filename != '\0' && *filename != '.') ++filename; if (*filename != '\0') { *df++ = *filename++; for (i = 0; *filename != '\0' && i < 3 && *filename != '.'; ++i) *df++ = tolower ((unsigned char)*filename++); } /* Look for more dots. */ while (*filename != '\0' && *filename != '.') ++filename; if (*filename == '.') return filename; *df = 0; return dos_filename; } #endif /* __MSDOS__ */ #ifdef WINDOWS32 #include "pathstuff.h" #endif #ifdef _AMIGA #include <ctype.h> #endif #ifdef HAVE_CASE_INSENSITIVE_FS static const char * downcase (const char *filename) { static PATH_VAR (new_filename); char *df; if (filename == 0) return 0; df = new_filename; while (*filename != '\0') { *df++ = tolower ((unsigned char)*filename); ++filename; } *df = 0; return new_filename; } #endif /* HAVE_CASE_INSENSITIVE_FS */ #ifdef VMS static int vms_hash (const char *name) { int h = 0; int g; while (*name) { unsigned char uc = *name; #ifdef HAVE_CASE_INSENSITIVE_FS h = (h << 4) + (isupper (uc) ? tolower (uc) : uc); #else h = (h << 4) + uc; #endif name++; g = h & 0xf0000000; if (g) { h = h ^ (g >> 24); h = h ^ g; } } return h; } /* fake stat entry for a directory */ static int vmsstat_dir (const char *name, struct stat *st) { char *s; int h; DIR *dir; dir = opendir (name); if (dir == 0) return -1; closedir (dir); s = strchr (name, ':'); /* find device */ if (s) { /* to keep the compiler happy we said "const char *name", now we cheat */ *s++ = 0; st->st_dev = (char *)vms_hash (name); h = vms_hash (s); *(s-1) = ':'; } else { st->st_dev = 0; h = vms_hash (name); } st->st_ino[0] = h & 0xff; st->st_ino[1] = h & 0xff00; st->st_ino[2] = h >> 16; return 0; } #endif /* VMS */ /* Hash table of directories. */ #ifndef DIRECTORY_BUCKETS #define DIRECTORY_BUCKETS 199 #endif struct directory_contents { dev_t dev; /* Device and inode numbers of this dir. */ #ifdef WINDOWS32 /* Inode means nothing on WINDOWS32. Even file key information is * unreliable because it is random per file open and undefined for remote * filesystems. The most unique attribute I can come up with is the fully * qualified name of the directory. Beware though, this is also * unreliable. I'm open to suggestion on a better way to emulate inode. */ char *path_key; int ctime; int mtime; /* controls check for stale directory cache */ int fs_flags; /* FS_FAT, FS_NTFS, ... */ # define FS_FAT 0x1 # define FS_NTFS 0x2 # define FS_UNKNOWN 0x4 #else # ifdef VMS ino_t ino[3]; # else ino_t ino; # endif #endif /* WINDOWS32 */ struct hash_table dirfiles; /* Files in this directory. */ DIR *dirstream; /* Stream reading this directory. */ }; static unsigned long directory_contents_hash_1 (const void *key_0) { const struct directory_contents *key = key_0; unsigned long hash; #ifdef WINDOWS32 hash = 0; ISTRING_HASH_1 (key->path_key, hash); hash ^= ((unsigned int) key->dev << 4) ^ (unsigned int) key->ctime; #else # ifdef VMS hash = (((unsigned int) key->dev << 4) ^ ((unsigned int) key->ino[0] + (unsigned int) key->ino[1] + (unsigned int) key->ino[2])); # else hash = ((unsigned int) key->dev << 4) ^ (unsigned int) key->ino; # endif #endif /* WINDOWS32 */ return hash; } static unsigned long directory_contents_hash_2 (const void *key_0) { const struct directory_contents *key = key_0; unsigned long hash; #ifdef WINDOWS32 hash = 0; ISTRING_HASH_2 (key->path_key, hash); hash ^= ((unsigned int) key->dev << 4) ^ (unsigned int) ~key->ctime; #else # ifdef VMS hash = (((unsigned int) key->dev << 4) ^ ~((unsigned int) key->ino[0] + (unsigned int) key->ino[1] + (unsigned int) key->ino[2])); # else hash = ((unsigned int) key->dev << 4) ^ (unsigned int) ~key->ino; # endif #endif /* WINDOWS32 */ return hash; } /* Sometimes it's OK to use subtraction to get this value: result = X - Y; But, if we're not sure of the type of X and Y they may be too large for an int (on a 64-bit system for example). So, use ?: instead. See Savannah bug #15534. NOTE! This macro has side-effects! */ #define MAKECMP(_x,_y) ((_x)<(_y)?-1:((_x)==(_y)?0:1)) static int directory_contents_hash_cmp (const void *xv, const void *yv) { const struct directory_contents *x = xv; const struct directory_contents *y = yv; int result; #ifdef WINDOWS32 ISTRING_COMPARE (x->path_key, y->path_key, result); if (result) return result; result = MAKECMP(x->ctime, y->ctime); if (result) return result; #else # ifdef VMS result = MAKECMP(x->ino[0], y->ino[0]); if (result) return result; result = MAKECMP(x->ino[1], y->ino[1]); if (result) return result; result = MAKECMP(x->ino[2], y->ino[2]); if (result) return result; # else result = MAKECMP(x->ino, y->ino); if (result) return result; # endif #endif /* WINDOWS32 */ return MAKECMP(x->dev, y->dev); } /* Table of directory contents hashed by device and inode number. */ static struct hash_table directory_contents; struct directory { const char *name; /* Name of the directory. */ /* The directory's contents. This data may be shared by several entries in the hash table, which refer to the same directory (identified uniquely by `dev' and `ino') under different names. */ struct directory_contents *contents; }; static unsigned long directory_hash_1 (const void *key) { return_ISTRING_HASH_1 (((const struct directory *) key)->name); } static unsigned long directory_hash_2 (const void *key) { return_ISTRING_HASH_2 (((const struct directory *) key)->name); } static int directory_hash_cmp (const void *x, const void *y) { return_ISTRING_COMPARE (((const struct directory *) x)->name, ((const struct directory *) y)->name); } /* Table of directories hashed by name. */ static struct hash_table directories; /* Never have more than this many directories open at once. */ #define MAX_OPEN_DIRECTORIES 10 static unsigned int open_directories = 0; /* Hash table of files in each directory. */ struct dirfile { const char *name; /* Name of the file. */ short length; short impossible; /* This file is impossible. */ }; static unsigned long dirfile_hash_1 (const void *key) { return_ISTRING_HASH_1 (((struct dirfile const *) key)->name); } static unsigned long dirfile_hash_2 (const void *key) { return_ISTRING_HASH_2 (((struct dirfile const *) key)->name); } static int dirfile_hash_cmp (const void *xv, const void *yv) { const struct dirfile *x = xv; const struct dirfile *y = yv; int result = x->length - y->length; if (result) return result; return_ISTRING_COMPARE (x->name, y->name); } #ifndef DIRFILE_BUCKETS #define DIRFILE_BUCKETS 107 #endif static int dir_contents_file_exists_p (struct directory_contents *dir, const char *filename); static struct directory *find_directory (const char *name); /* Find the directory named NAME and return its `struct directory'. */ static struct directory * find_directory (const char *name) { const char *p; struct directory *dir; struct directory **dir_slot; struct directory dir_key; int r; #ifdef WINDOWS32 char* w32_path; char fs_label[BUFSIZ]; char fs_type[BUFSIZ]; unsigned long fs_serno; unsigned long fs_flags; unsigned long fs_len; #endif #ifdef VMS if ((*name == '.') && (*(name+1) == 0)) name = "[]"; else name = vmsify (name,1); #endif dir_key.name = name; dir_slot = (struct directory **) hash_find_slot (&directories, &dir_key); dir = *dir_slot; if (HASH_VACANT (dir)) { struct stat st; /* The directory was not found. Create a new entry for it. */ p = name + strlen (name); dir = xmalloc (sizeof (struct directory)); #if defined(HAVE_CASE_INSENSITIVE_FS) && defined(VMS) dir->name = strcache_add_len (downcase(name), p - name); #else dir->name = strcache_add_len (name, p - name); #endif hash_insert_at (&directories, dir, dir_slot); /* The directory is not in the name hash table. Find its device and inode numbers, and look it up by them. */ #ifdef VMS r = vmsstat_dir (name, &st); #elif defined(WINDOWS32) { char tem[MAXPATHLEN], *tstart, *tend; /* Remove any trailing slashes. Windows32 stat fails even on valid directories if they end in a slash. */ memcpy (tem, name, p - name + 1); tstart = tem; if (tstart[1] == ':') tstart += 2; for (tend = tem + (p - name - 1); tend > tstart && (*tend == '/' || *tend == '\\'); tend--) *tend = '\0'; r = stat (tem, &st); } #else EINTRLOOP (r, stat (name, &st)); #endif if (r < 0) { /* Couldn't stat the directory. Mark this by setting the `contents' member to a nil pointer. */ dir->contents = 0; } else { /* Search the contents hash table; device and inode are the key. */ struct directory_contents *dc; struct directory_contents **dc_slot; struct directory_contents dc_key; dc_key.dev = st.st_dev; #ifdef WINDOWS32 dc_key.path_key = w32_path = w32ify (name, 1); dc_key.ctime = st.st_ctime; #else # ifdef VMS dc_key.ino[0] = st.st_ino[0]; dc_key.ino[1] = st.st_ino[1]; dc_key.ino[2] = st.st_ino[2]; # else dc_key.ino = st.st_ino; # endif #endif dc_slot = (struct directory_contents **) hash_find_slot (&directory_contents, &dc_key); dc = *dc_slot; if (HASH_VACANT (dc)) { /* Nope; this really is a directory we haven't seen before. */ dc = (struct directory_contents *) xmalloc (sizeof (struct directory_contents)); /* Enter it in the contents hash table. */ dc->dev = st.st_dev; #ifdef WINDOWS32 dc->path_key = xstrdup (w32_path); dc->ctime = st.st_ctime; dc->mtime = st.st_mtime; /* * NTFS is the only WINDOWS32 filesystem that bumps mtime * on a directory when files are added/deleted from * a directory. */ w32_path[3] = '\0'; if (GetVolumeInformation(w32_path, fs_label, sizeof (fs_label), &fs_serno, &fs_len, &fs_flags, fs_type, sizeof (fs_type)) == FALSE) dc->fs_flags = FS_UNKNOWN; else if (!strcmp(fs_type, "FAT")) dc->fs_flags = FS_FAT; else if (!strcmp(fs_type, "NTFS")) dc->fs_flags = FS_NTFS; else dc->fs_flags = FS_UNKNOWN; #else # ifdef VMS dc->ino[0] = st.st_ino[0]; dc->ino[1] = st.st_ino[1]; dc->ino[2] = st.st_ino[2]; # else dc->ino = st.st_ino; # endif #endif /* WINDOWS32 */ hash_insert_at (&directory_contents, dc, dc_slot); ENULLLOOP (dc->dirstream, opendir (name)); if (dc->dirstream == 0) /* Couldn't open the directory. Mark this by setting the `files' member to a nil pointer. */ dc->dirfiles.ht_vec = 0; else { hash_init (&dc->dirfiles, DIRFILE_BUCKETS, dirfile_hash_1, dirfile_hash_2, dirfile_hash_cmp); /* Keep track of how many directories are open. */ ++open_directories; if (open_directories == MAX_OPEN_DIRECTORIES) /* We have too many directories open already. Read the entire directory and then close it. */ dir_contents_file_exists_p (dc, 0); } } /* Point the name-hashed entry for DIR at its contents data. */ dir->contents = dc; } } return dir; } /* Return 1 if the name FILENAME is entered in DIR's hash table. FILENAME must contain no slashes. */ static int dir_contents_file_exists_p (struct directory_contents *dir, const char *filename) { unsigned int hash; struct dirfile *df; struct dirent *d; #ifdef WINDOWS32 struct stat st; int rehash = 0; #endif if (dir == 0 || dir->dirfiles.ht_vec == 0) /* The directory could not be stat'd or opened. */ return 0; #ifdef __MSDOS__ filename = dosify (filename); #endif #ifdef HAVE_CASE_INSENSITIVE_FS filename = downcase (filename); #endif #ifdef __EMX__ if (filename != 0) _fnlwr (filename); /* lower case for FAT drives */ #endif #ifdef VMS filename = vmsify (filename,0); #endif hash = 0; if (filename != 0) { struct dirfile dirfile_key; if (*filename == '\0') { /* Checking if the directory exists. */ return 1; } dirfile_key.name = filename; dirfile_key.length = strlen (filename); df = hash_find_item (&dir->dirfiles, &dirfile_key); if (df) return !df->impossible; } /* The file was not found in the hashed list. Try to read the directory further. */ if (dir->dirstream == 0) { #ifdef WINDOWS32 /* * Check to see if directory has changed since last read. FAT * filesystems force a rehash always as mtime does not change * on directories (ugh!). */ if (dir->path_key) { if ((dir->fs_flags & FS_FAT) != 0) { dir->mtime = time ((time_t *) 0); rehash = 1; } else if (stat (dir->path_key, &st) == 0 && st.st_mtime > dir->mtime) { /* reset date stamp to show most recent re-process. */ dir->mtime = st.st_mtime; rehash = 1; } /* If it has been already read in, all done. */ if (!rehash) return 0; /* make sure directory can still be opened; if not return. */ dir->dirstream = opendir (dir->path_key); if (!dir->dirstream) return 0; } else #endif /* The directory has been all read in. */ return 0; } while (1) { /* Enter the file in the hash table. */ unsigned int len; struct dirfile dirfile_key; struct dirfile **dirfile_slot; ENULLLOOP (d, readdir (dir->dirstream)); if (d == 0) { if (errno) fatal (NILF, "INTERNAL: readdir: %s\n", strerror (errno)); break; } #if defined(VMS) && defined(HAVE_DIRENT_H) /* In VMS we get file versions too, which have to be stripped off */ { char *p = strrchr (d->d_name, ';'); if (p) *p = '\0'; } #endif if (!REAL_DIR_ENTRY (d)) continue; len = NAMLEN (d); dirfile_key.name = d->d_name; dirfile_key.length = len; dirfile_slot = (struct dirfile **) hash_find_slot (&dir->dirfiles, &dirfile_key); #ifdef WINDOWS32 /* * If re-reading a directory, don't cache files that have * already been discovered. */ if (! rehash || HASH_VACANT (*dirfile_slot)) #endif { df = xmalloc (sizeof (struct dirfile)); #if defined(HAVE_CASE_INSENSITIVE_FS) && defined(VMS) df->name = strcache_add_len (downcase(d->d_name), len); #else df->name = strcache_add_len (d->d_name, len); #endif df->length = len; df->impossible = 0; hash_insert_at (&dir->dirfiles, df, dirfile_slot); } /* Check if the name matches the one we're searching for. */ if (filename != 0 && patheq (d->d_name, filename)) return 1; } /* If the directory has been completely read in, close the stream and reset the pointer to nil. */ if (d == 0) { --open_directories; closedir (dir->dirstream); dir->dirstream = 0; } return 0; } /* Return 1 if the name FILENAME in directory DIRNAME is entered in the dir hash table. FILENAME must contain no slashes. */ int dir_file_exists_p (const char *dirname, const char *filename) { return dir_contents_file_exists_p (find_directory (dirname)->contents, filename); } /* Return 1 if the file named NAME exists. */ int file_exists_p (const char *name) { const char *dirend; const char *dirname; const char *slash; #ifndef NO_ARCHIVES if (ar_name (name)) return ar_member_date (name) != (time_t) -1; #endif #ifdef VMS dirend = strrchr (name, ']'); if (dirend == 0) dirend = strrchr (name, ':'); if (dirend == 0) return dir_file_exists_p ("[]", name); #else /* !VMS */ dirend = strrchr (name, '/'); #ifdef HAVE_DOS_PATHS /* Forward and backslashes might be mixed. We need the rightmost one. */ { const char *bslash = strrchr(name, '\\'); if (!dirend || bslash > dirend) dirend = bslash; /* The case of "d:file". */ if (!dirend && name[0] && name[1] == ':') dirend = name + 1; } #endif /* HAVE_DOS_PATHS */ if (dirend == 0) #ifndef _AMIGA return dir_file_exists_p (".", name); #else /* !VMS && !AMIGA */ return dir_file_exists_p ("", name); #endif /* AMIGA */ #endif /* VMS */ slash = dirend; if (dirend == name) dirname = "/"; else { char *p; #ifdef HAVE_DOS_PATHS /* d:/ and d: are *very* different... */ if (dirend < name + 3 && name[1] == ':' && (*dirend == '/' || *dirend == '\\' || *dirend == ':')) dirend++; #endif p = alloca (dirend - name + 1); memcpy (p, name, dirend - name); p[dirend - name] = '\0'; dirname = p; } return dir_file_exists_p (dirname, slash + 1); } /* Mark FILENAME as `impossible' for `file_impossible_p'. This means an attempt has been made to search for FILENAME as an intermediate file, and it has failed. */ void file_impossible (const char *filename) { const char *dirend; const char *p = filename; struct directory *dir; struct dirfile *new; #ifdef VMS dirend = strrchr (p, ']'); if (dirend == 0) dirend = strrchr (p, ':'); dirend++; if (dirend == (char *)1) dir = find_directory ("[]"); #else dirend = strrchr (p, '/'); # ifdef HAVE_DOS_PATHS /* Forward and backslashes might be mixed. We need the rightmost one. */ { const char *bslash = strrchr(p, '\\'); if (!dirend || bslash > dirend) dirend = bslash; /* The case of "d:file". */ if (!dirend && p[0] && p[1] == ':') dirend = p + 1; } # endif /* HAVE_DOS_PATHS */ if (dirend == 0) # ifdef _AMIGA dir = find_directory (""); # else /* !VMS && !AMIGA */ dir = find_directory ("."); # endif /* AMIGA */ #endif /* VMS */ else { const char *dirname; const char *slash = dirend; if (dirend == p) dirname = "/"; else { char *cp; #ifdef HAVE_DOS_PATHS /* d:/ and d: are *very* different... */ if (dirend < p + 3 && p[1] == ':' && (*dirend == '/' || *dirend == '\\' || *dirend == ':')) dirend++; #endif cp = alloca (dirend - p + 1); memcpy (cp, p, dirend - p); cp[dirend - p] = '\0'; dirname = cp; } dir = find_directory (dirname); filename = p = slash + 1; } if (dir->contents == 0) /* The directory could not be stat'd. We allocate a contents structure for it, but leave it out of the contents hash table. */ dir->contents = xcalloc (sizeof (struct directory_contents)); if (dir->contents->dirfiles.ht_vec == 0) { hash_init (&dir->contents->dirfiles, DIRFILE_BUCKETS, dirfile_hash_1, dirfile_hash_2, dirfile_hash_cmp); } /* Make a new entry and put it in the table. */ new = xmalloc (sizeof (struct dirfile)); new->length = strlen (filename); #if defined(HAVE_CASE_INSENSITIVE_FS) && defined(VMS) new->name = strcache_add_len (downcase(filename), new->length); #else new->name = strcache_add_len (filename, new->length); #endif new->impossible = 1; hash_insert (&dir->contents->dirfiles, new); } /* Return nonzero if FILENAME has been marked impossible. */ int file_impossible_p (const char *filename) { const char *dirend; const char *p = filename; struct directory_contents *dir; struct dirfile *dirfile; struct dirfile dirfile_key; #ifdef VMS dirend = strrchr (filename, ']'); if (dirend == 0) dir = find_directory ("[]")->contents; #else dirend = strrchr (filename, '/'); #ifdef HAVE_DOS_PATHS /* Forward and backslashes might be mixed. We need the rightmost one. */ { const char *bslash = strrchr(filename, '\\'); if (!dirend || bslash > dirend) dirend = bslash; /* The case of "d:file". */ if (!dirend && filename[0] && filename[1] == ':') dirend = filename + 1; } #endif /* HAVE_DOS_PATHS */ if (dirend == 0) #ifdef _AMIGA dir = find_directory ("")->contents; #else /* !VMS && !AMIGA */ dir = find_directory (".")->contents; #endif /* AMIGA */ #endif /* VMS */ else { const char *dirname; const char *slash = dirend; if (dirend == filename) dirname = "/"; else { char *cp; #ifdef HAVE_DOS_PATHS /* d:/ and d: are *very* different... */ if (dirend < filename + 3 && filename[1] == ':' && (*dirend == '/' || *dirend == '\\' || *dirend == ':')) dirend++; #endif cp = alloca (dirend - filename + 1); memcpy (cp, p, dirend - p); cp[dirend - p] = '\0'; dirname = cp; } dir = find_directory (dirname)->contents; p = filename = slash + 1; } if (dir == 0 || dir->dirfiles.ht_vec == 0) /* There are no files entered for this directory. */ return 0; #ifdef __MSDOS__ filename = dosify (p); #endif #ifdef HAVE_CASE_INSENSITIVE_FS filename = downcase (p); #endif #ifdef VMS filename = vmsify (p, 1); #endif dirfile_key.name = filename; dirfile_key.length = strlen (filename); dirfile = hash_find_item (&dir->dirfiles, &dirfile_key); if (dirfile) return dirfile->impossible; return 0; } /* Return the already allocated name in the directory hash table that matches DIR. */ const char * dir_name (const char *dir) { return find_directory (dir)->name; } /* Print the data base of directories. */ void print_dir_data_base (void) { unsigned int files; unsigned int impossible; struct directory **dir_slot; struct directory **dir_end; puts (_("\n# Directories\n")); files = impossible = 0; dir_slot = (struct directory **) directories.ht_vec; dir_end = dir_slot + directories.ht_size; for ( ; dir_slot < dir_end; dir_slot++) { struct directory *dir = *dir_slot; if (! HASH_VACANT (dir)) { if (dir->contents == 0) printf (_("# %s: could not be stat'd.\n"), dir->name); else if (dir->contents->dirfiles.ht_vec == 0) { #ifdef WINDOWS32 printf (_("# %s (key %s, mtime %d): could not be opened.\n"), dir->name, dir->contents->path_key,dir->contents->mtime); #else /* WINDOWS32 */ #ifdef VMS printf (_("# %s (device %d, inode [%d,%d,%d]): could not be opened.\n"), dir->name, dir->contents->dev, dir->contents->ino[0], dir->contents->ino[1], dir->contents->ino[2]); #else printf (_("# %s (device %ld, inode %ld): could not be opened.\n"), dir->name, (long int) dir->contents->dev, (long int) dir->contents->ino); #endif #endif /* WINDOWS32 */ } else { unsigned int f = 0; unsigned int im = 0; struct dirfile **files_slot; struct dirfile **files_end; files_slot = (struct dirfile **) dir->contents->dirfiles.ht_vec; files_end = files_slot + dir->contents->dirfiles.ht_size; for ( ; files_slot < files_end; files_slot++) { struct dirfile *df = *files_slot; if (! HASH_VACANT (df)) { if (df->impossible) ++im; else ++f; } } #ifdef WINDOWS32 printf (_("# %s (key %s, mtime %d): "), dir->name, dir->contents->path_key, dir->contents->mtime); #else /* WINDOWS32 */ #ifdef VMS printf (_("# %s (device %d, inode [%d,%d,%d]): "), dir->name, dir->contents->dev, dir->contents->ino[0], dir->contents->ino[1], dir->contents->ino[2]); #else printf (_("# %s (device %ld, inode %ld): "), dir->name, (long)dir->contents->dev, (long)dir->contents->ino); #endif #endif /* WINDOWS32 */ if (f == 0) fputs (_("No"), stdout); else printf ("%u", f); fputs (_(" files, "), stdout); if (im == 0) fputs (_("no"), stdout); else printf ("%u", im); fputs (_(" impossibilities"), stdout); if (dir->contents->dirstream == 0) puts ("."); else puts (_(" so far.")); files += f; impossible += im; } } } fputs ("\n# ", stdout); if (files == 0) fputs (_("No"), stdout); else printf ("%u", files); fputs (_(" files, "), stdout); if (impossible == 0) fputs (_("no"), stdout); else printf ("%u", impossible); printf (_(" impossibilities in %lu directories.\n"), directories.ht_fill); } /* Hooks for globbing. */ #include <glob.h> /* Structure describing state of iterating through a directory hash table. */ struct dirstream { struct directory_contents *contents; /* The directory being read. */ struct dirfile **dirfile_slot; /* Current slot in table. */ }; /* Forward declarations. */ static __ptr_t open_dirstream (const char *); static struct dirent *read_dirstream (__ptr_t); static __ptr_t open_dirstream (const char *directory) { struct dirstream *new; struct directory *dir = find_directory (directory); if (dir->contents == 0 || dir->contents->dirfiles.ht_vec == 0) /* DIR->contents is nil if the directory could not be stat'd. DIR->contents->dirfiles is nil if it could not be opened. */ return 0; /* Read all the contents of the directory now. There is no benefit in being lazy, since glob will want to see every file anyway. */ dir_contents_file_exists_p (dir->contents, 0); new = xmalloc (sizeof (struct dirstream)); new->contents = dir->contents; new->dirfile_slot = (struct dirfile **) new->contents->dirfiles.ht_vec; return (__ptr_t) new; } static struct dirent * read_dirstream (__ptr_t stream) { static char *buf; static unsigned int bufsz; struct dirstream *const ds = (struct dirstream *) stream; struct directory_contents *dc = ds->contents; struct dirfile **dirfile_end = (struct dirfile **) dc->dirfiles.ht_vec + dc->dirfiles.ht_size; while (ds->dirfile_slot < dirfile_end) { struct dirfile *df = *ds->dirfile_slot++; if (! HASH_VACANT (df) && !df->impossible) { /* The glob interface wants a `struct dirent', so mock one up. */ struct dirent *d; unsigned int len = df->length + 1; unsigned int sz = sizeof (*d) - sizeof (d->d_name) + len; if (sz > bufsz) { bufsz *= 2; if (sz > bufsz) bufsz = sz; buf = xrealloc (buf, bufsz); } d = (struct dirent *) buf; #ifdef __MINGW32__ # if __MINGW32_MAJOR_VERSION < 3 || (__MINGW32_MAJOR_VERSION == 3 && \ __MINGW32_MINOR_VERSION == 0) d->d_name = xmalloc(len); # endif #endif FAKE_DIR_ENTRY (d); #ifdef _DIRENT_HAVE_D_NAMLEN d->d_namlen = len - 1; #endif #ifdef _DIRENT_HAVE_D_TYPE d->d_type = DT_UNKNOWN; #endif memcpy (d->d_name, df->name, len); return d; } } return 0; } static void ansi_free (void *p) { if (p) free(p); } /* On 64 bit ReliantUNIX (5.44 and above) in LFS mode, stat() is actually a * macro for stat64(). If stat is a macro, make a local wrapper function to * invoke it. */ #ifndef stat # ifndef VMS int stat (const char *path, struct stat *sbuf); # endif # define local_stat stat #else static int local_stat (const char *path, struct stat *buf) { int e; EINTRLOOP (e, stat (path, buf)); return e; } #endif void dir_setup_glob (glob_t *gl) { gl->gl_opendir = open_dirstream; gl->gl_readdir = read_dirstream; gl->gl_closedir = ansi_free; gl->gl_stat = local_stat; /* We don't bother setting gl_lstat, since glob never calls it. The slot is only there for compatibility with 4.4 BSD. */ } void hash_init_directories (void) { hash_init (&directories, DIRECTORY_BUCKETS, directory_hash_1, directory_hash_2, directory_hash_cmp); hash_init (&directory_contents, DIRECTORY_BUCKETS, directory_contents_hash_1, directory_contents_hash_2, directory_contents_hash_cmp); }
/* Variable expansion functions for GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" #include <assert.h> #include "filedef.h" #include "job.h" #include "commands.h" #include "variable.h" #include "rule.h" /* Initially, any errors reported when expanding strings will be reported against the file where the error appears. */ const struct floc **expanding_var = &reading_file; /* The next two describe the variable output buffer. This buffer is used to hold the variable-expansion of a line of the makefile. It is made bigger with realloc whenever it is too small. variable_buffer_length is the size currently allocated. variable_buffer is the address of the buffer. For efficiency, it's guaranteed that the buffer will always have VARIABLE_BUFFER_ZONE extra bytes allocated. This allows you to add a few extra chars without having to call a function. Note you should never use these bytes unless you're _sure_ you have room (you know when the buffer length was last checked. */ #define VARIABLE_BUFFER_ZONE 5 static unsigned int variable_buffer_length; char *variable_buffer; /* Subroutine of variable_expand and friends: The text to add is LENGTH chars starting at STRING to the variable_buffer. The text is added to the buffer at PTR, and the updated pointer into the buffer is returned as the value. Thus, the value returned by each call to variable_buffer_output should be the first argument to the following call. */ char * variable_buffer_output (char *ptr, const char *string, unsigned int length) { register unsigned int newlen = length + (ptr - variable_buffer); if ((newlen + VARIABLE_BUFFER_ZONE) > variable_buffer_length) { unsigned int offset = ptr - variable_buffer; variable_buffer_length = (newlen + 100 > 2 * variable_buffer_length ? newlen + 100 : 2 * variable_buffer_length); variable_buffer = xrealloc (variable_buffer, variable_buffer_length); ptr = variable_buffer + offset; } memcpy (ptr, string, length); return ptr + length; } /* Return a pointer to the beginning of the variable buffer. */ static char * initialize_variable_output (void) { /* If we don't have a variable output buffer yet, get one. */ if (variable_buffer == 0) { variable_buffer_length = 200; variable_buffer = xmalloc (variable_buffer_length); variable_buffer[0] = '\0'; } return variable_buffer; } /* Recursively expand V. The returned string is malloc'd. */ static char *allocated_variable_append (const struct variable *v); char * recursively_expand_for_file (struct variable *v, struct file *file) { char *value; const struct floc *this_var; const struct floc **saved_varp; struct variable_set_list *save = 0; int set_reading = 0; /* Don't install a new location if this location is empty. This can happen for command-line variables, builtin variables, etc. */ saved_varp = expanding_var; if (v->fileinfo.filenm) { this_var = &v->fileinfo; expanding_var = &this_var; } /* If we have no other file-reading context, use the variable's context. */ if (!reading_file) { set_reading = 1; reading_file = &v->fileinfo; } if (v->expanding) { if (!v->exp_count) /* Expanding V causes infinite recursion. Lose. */ fatal (*expanding_var, _("Recursive variable `%s' references itself (eventually)"), v->name); --v->exp_count; } if (file) { save = current_variable_set_list; current_variable_set_list = file->variables; } v->expanding = 1; if (v->append) value = allocated_variable_append (v); else value = allocated_variable_expand (v->value); v->expanding = 0; if (set_reading) reading_file = 0; if (file) current_variable_set_list = save; expanding_var = saved_varp; return value; } /* Expand a simple reference to variable NAME, which is LENGTH chars long. */ #ifdef __GNUC__ __inline #endif static char * reference_variable (char *o, const char *name, unsigned int length) { struct variable *v; char *value; v = lookup_variable (name, length); if (v == 0) warn_undefined (name, length); /* If there's no variable by that name or it has no value, stop now. */ if (v == 0 || (*v->value == '\0' && !v->append)) return o; value = (v->recursive ? recursively_expand (v) : v->value); o = variable_buffer_output (o, value, strlen (value)); if (v->recursive) free (value); return o; } /* Scan STRING for variable references and expansion-function calls. Only LENGTH bytes of STRING are actually scanned. If LENGTH is -1, scan until a null byte is found. Write the results to LINE, which must point into `variable_buffer'. If LINE is NULL, start at the beginning of the buffer. Return a pointer to LINE, or to the beginning of the buffer if LINE is NULL. */ char * variable_expand_string (char *line, const char *string, long length) { struct variable *v; const char *p, *p1; char *abuf = NULL; char *o; unsigned int line_offset; if (!line) line = initialize_variable_output(); o = line; line_offset = line - variable_buffer; if (length == 0) { variable_buffer_output (o, "", 1); return (variable_buffer); } /* If we want a subset of the string, allocate a temporary buffer for it. Most of the functions we use here don't work with length limits. */ if (length > 0 && string[length] != '\0') { abuf = xmalloc(length+1); memcpy(abuf, string, length); abuf[length] = '\0'; string = abuf; } p = string; while (1) { /* Copy all following uninteresting chars all at once to the variable output buffer, and skip them. Uninteresting chars end at the next $ or the end of the input. */ p1 = strchr (p, '$'); o = variable_buffer_output (o, p, p1 != 0 ? (unsigned int)(p1 - p) : strlen (p) + 1); if (p1 == 0) break; p = p1 + 1; /* Dispatch on the char that follows the $. */ switch (*p) { case '$': /* $$ seen means output one $ to the variable output buffer. */ o = variable_buffer_output (o, p, 1); break; case '(': case '{': /* $(...) or ${...} is the general case of substitution. */ { char openparen = *p; char closeparen = (openparen == '(') ? ')' : '}'; const char *begp; const char *beg = p + 1; char *op; char *abeg = NULL; const char *end, *colon; op = o; begp = p; if (handle_function (&op, &begp)) { o = op; p = begp; break; } /* Is there a variable reference inside the parens or braces? If so, expand it before expanding the entire reference. */ end = strchr (beg, closeparen); if (end == 0) /* Unterminated variable reference. */ fatal (*expanding_var, _("unterminated variable reference")); p1 = lindex (beg, end, '$'); if (p1 != 0) { /* BEG now points past the opening paren or brace. Count parens or braces until it is matched. */ int count = 0; for (p = beg; *p != '\0'; ++p) { if (*p == openparen) ++count; else if (*p == closeparen && --count < 0) break; } /* If COUNT is >= 0, there were unmatched opening parens or braces, so we go to the simple case of a variable name such as `$($(a)'. */ if (count < 0) { abeg = expand_argument (beg, p); /* Expand the name. */ beg = abeg; end = strchr (beg, '\0'); } } else /* Advance P to the end of this reference. After we are finished expanding this one, P will be incremented to continue the scan. */ p = end; /* This is not a reference to a built-in function and any variable references inside are now expanded. Is the resultant text a substitution reference? */ colon = lindex (beg, end, ':'); if (colon) { /* This looks like a substitution reference: $(FOO:A=B). */ const char *subst_beg, *subst_end, *replace_beg, *replace_end; subst_beg = colon + 1; subst_end = lindex (subst_beg, end, '='); if (subst_end == 0) /* There is no = in sight. Punt on the substitution reference and treat this as a variable name containing a colon, in the code below. */ colon = 0; else { replace_beg = subst_end + 1; replace_end = end; /* Extract the variable name before the colon and look up that variable. */ v = lookup_variable (beg, colon - beg); if (v == 0) warn_undefined (beg, colon - beg); /* If the variable is not empty, perform the substitution. */ if (v != 0 && *v->value != '\0') { char *pattern, *replace, *ppercent, *rpercent; char *value = (v->recursive ? recursively_expand (v) : v->value); /* Copy the pattern and the replacement. Add in an extra % at the beginning to use in case there isn't one in the pattern. */ pattern = alloca (subst_end - subst_beg + 2); *(pattern++) = '%'; memcpy (pattern, subst_beg, subst_end - subst_beg); pattern[subst_end - subst_beg] = '\0'; replace = alloca (replace_end - replace_beg + 2); *(replace++) = '%'; memcpy (replace, replace_beg, replace_end - replace_beg); replace[replace_end - replace_beg] = '\0'; /* Look for %. Set the percent pointers properly based on whether we find one or not. */ ppercent = find_percent (pattern); if (ppercent) { ++ppercent; rpercent = find_percent (replace); if (rpercent) ++rpercent; } else { ppercent = pattern; rpercent = replace; --pattern; --replace; } o = patsubst_expand_pat (o, value, pattern, replace, ppercent, rpercent); if (v->recursive) free (value); } } } if (colon == 0) /* This is an ordinary variable reference. Look up the value of the variable. */ o = reference_variable (o, beg, end - beg); if (abeg) free (abeg); } break; case '\0': break; default: if (isblank ((unsigned char)p[-1])) break; /* A $ followed by a random char is a variable reference: $a is equivalent to $(a). */ o = reference_variable (o, p, 1); break; } if (*p == '\0') break; ++p; } if (abuf) free (abuf); variable_buffer_output (o, "", 1); return (variable_buffer + line_offset); } /* Scan LINE for variable references and expansion-function calls. Build in `variable_buffer' the result of expanding the references and calls. Return the address of the resulting string, which is null-terminated and is valid only until the next time this function is called. */ char * variable_expand (const char *line) { return variable_expand_string(NULL, line, (long)-1); } /* Expand an argument for an expansion function. The text starting at STR and ending at END is variable-expanded into a null-terminated string that is returned as the value. This is done without clobbering `variable_buffer' or the current variable-expansion that is in progress. */ char * expand_argument (const char *str, const char *end) { char *tmp, *alloc = NULL; char *r; if (str == end) return xstrdup(""); if (!end || *end == '\0') return allocated_variable_expand (str); if (end - str + 1 > 1000) tmp = alloc = xmalloc (end - str + 1); else tmp = alloca (end - str + 1); memcpy (tmp, str, end - str); tmp[end - str] = '\0'; r = allocated_variable_expand (tmp); if (alloc) free (alloc); return r; } /* Expand LINE for FILE. Error messages refer to the file and line where FILE's commands were found. Expansion uses FILE's variable set list. */ char * variable_expand_for_file (const char *line, struct file *file) { char *result; struct variable_set_list *savev; const struct floc *savef; if (file == 0) return variable_expand (line); savev = current_variable_set_list; current_variable_set_list = file->variables; savef = reading_file; if (file->cmds && file->cmds->fileinfo.filenm) reading_file = &file->cmds->fileinfo; else reading_file = 0; result = variable_expand (line); current_variable_set_list = savev; reading_file = savef; return result; } /* Like allocated_variable_expand, but for += target-specific variables. First recursively construct the variable value from its appended parts in any upper variable sets. Then expand the resulting value. */ static char * variable_append (const char *name, unsigned int length, const struct variable_set_list *set) { const struct variable *v; char *buf = 0; /* If there's nothing left to check, return the empty buffer. */ if (!set) return initialize_variable_output (); /* Try to find the variable in this variable set. */ v = lookup_variable_in_set (name, length, set->set); /* If there isn't one, look to see if there's one in a set above us. */ if (!v) return variable_append (name, length, set->next); /* If this variable type is append, first get any upper values. If not, initialize the buffer. */ if (v->append) buf = variable_append (name, length, set->next); else buf = initialize_variable_output (); /* Append this value to the buffer, and return it. If we already have a value, first add a space. */ if (buf > variable_buffer) buf = variable_buffer_output (buf, " ", 1); /* Either expand it or copy it, depending. */ if (! v->recursive) return variable_buffer_output (buf, v->value, strlen (v->value)); buf = variable_expand_string (buf, v->value, strlen (v->value)); return (buf + strlen (buf)); } static char * allocated_variable_append (const struct variable *v) { char *val; /* Construct the appended variable value. */ char *obuf = variable_buffer; unsigned int olen = variable_buffer_length; variable_buffer = 0; val = variable_append (v->name, strlen (v->name), current_variable_set_list); variable_buffer_output (val, "", 1); val = variable_buffer; variable_buffer = obuf; variable_buffer_length = olen; return val; } /* Like variable_expand_for_file, but the returned string is malloc'd. This function is called a lot. It wants to be efficient. */ char * allocated_variable_expand_for_file (const char *line, struct file *file) { char *value; char *obuf = variable_buffer; unsigned int olen = variable_buffer_length; variable_buffer = 0; value = variable_expand_for_file (line, file); variable_buffer = obuf; variable_buffer_length = olen; return value; } /* Install a new variable_buffer context, returning the current one for safe-keeping. */ void install_variable_buffer (char **bufp, unsigned int *lenp) { *bufp = variable_buffer; *lenp = variable_buffer_length; variable_buffer = 0; initialize_variable_output (); } /* Restore a previously-saved variable_buffer setting (free the current one). */ void restore_variable_buffer (char *buf, unsigned int len) { free (variable_buffer); variable_buffer = buf; variable_buffer_length = len; }
/* Target file management for GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" #include <assert.h> #include "dep.h" #include "filedef.h" #include "job.h" #include "commands.h" #include "variable.h" #include "debug.h" #include "hash.h" /* Remember whether snap_deps has been invoked: we need this to be sure we don't add new rules (via $(eval ...)) afterwards. In the future it would be nice to support this, but it means we'd need to re-run snap_deps() or at least its functionality... it might mean changing snap_deps() to be run per-file, so we can invoke it after the eval... or remembering which files in the hash have been snapped (a new boolean flag?) and having snap_deps() only work on files which have not yet been snapped. */ int snapped_deps = 0; /* Hash table of files the makefile knows how to make. */ static unsigned long file_hash_1 (const void *key) { return_ISTRING_HASH_1 (((struct file const *) key)->hname); } static unsigned long file_hash_2 (const void *key) { return_ISTRING_HASH_2 (((struct file const *) key)->hname); } static int file_hash_cmp (const void *x, const void *y) { return_ISTRING_COMPARE (((struct file const *) x)->hname, ((struct file const *) y)->hname); } #ifndef FILE_BUCKETS #define FILE_BUCKETS 1007 #endif static struct hash_table files; /* Whether or not .SECONDARY with no prerequisites was given. */ static int all_secondary = 0; /* Access the hash table of all file records. lookup_file given a name, return the struct file * for that name, or nil if there is none. */ struct file * lookup_file (const char *name) { struct file *f; struct file file_key; #if defined(VMS) && !defined(WANT_CASE_SENSITIVE_TARGETS) char *lname; #endif assert (*name != '\0'); /* This is also done in parse_file_seq, so this is redundant for names read from makefiles. It is here for names passed on the command line. */ #ifdef VMS # ifndef WANT_CASE_SENSITIVE_TARGETS if (*name != '.') { const char *n; char *ln; lname = xstrdup (name); for (n = name, ln = lname; *n != '\0'; ++n, ++ln) *ln = isupper ((unsigned char)*n) ? tolower ((unsigned char)*n) : *n; *ln = '\0'; name = lname; } # endif while (name[0] == '[' && name[1] == ']' && name[2] != '\0') name += 2; #endif while (name[0] == '.' #ifdef HAVE_DOS_PATHS && (name[1] == '/' || name[1] == '\\') #else && name[1] == '/' #endif && name[2] != '\0') { name += 2; while (*name == '/' #ifdef HAVE_DOS_PATHS || *name == '\\' #endif ) /* Skip following slashes: ".//foo" is "foo", not "/foo". */ ++name; } if (*name == '\0') /* It was all slashes after a dot. */ #if defined(VMS) name = "[]"; #elif defined(_AMIGA) name = ""; #else name = "./"; #endif file_key.hname = name; f = hash_find_item (&files, &file_key); #if defined(VMS) && !defined(WANT_CASE_SENSITIVE_TARGETS) if (*name != '.') free (lname); #endif return f; } /* Look up a file record for file NAME and return it. Create a new record if one doesn't exist. NAME will be stored in the new record so it should be constant or in the strcache etc. */ struct file * enter_file (const char *name) { struct file *f; struct file *new; struct file **file_slot; struct file file_key; assert (*name != '\0'); assert (strcache_iscached (name)); #if defined(VMS) && !defined(WANT_CASE_SENSITIVE_TARGETS) if (*name != '.') { const char *n; char *lname, *ln; lname = xstrdup (name); for (n = name, ln = lname; *n != '\0'; ++n, ++ln) if (isupper ((unsigned char)*n)) *ln = tolower ((unsigned char)*n); else *ln = *n; *ln = '\0'; name = strcache_add (lname); free (lname); } #endif file_key.hname = name; file_slot = (struct file **) hash_find_slot (&files, &file_key); f = *file_slot; if (! HASH_VACANT (f) && !f->double_colon) return f; new = xcalloc (sizeof (struct file)); new->name = new->hname = name; new->update_status = -1; if (HASH_VACANT (f)) { new->last = new; hash_insert_at (&files, new, file_slot); } else { /* There is already a double-colon entry for this file. */ new->double_colon = f; f->last->prev = new; f->last = new; } return new; } /* Rehash FILE to NAME. This is not as simple as resetting the `hname' member, since it must be put in a new hash bucket, and possibly merged with an existing file called NAME. */ void rehash_file (struct file *from_file, const char *to_hname) { struct file file_key; struct file **file_slot; struct file *to_file; struct file *deleted_file; struct file *f; /* If it's already that name, we're done. */ file_key.hname = to_hname; if (! file_hash_cmp (from_file, &file_key)) return; /* Find the end of the renamed list for the "from" file. */ file_key.hname = from_file->hname; while (from_file->renamed != 0) from_file = from_file->renamed; if (file_hash_cmp (from_file, &file_key)) /* hname changed unexpectedly!! */ abort (); /* Remove the "from" file from the hash. */ deleted_file = hash_delete (&files, from_file); if (deleted_file != from_file) /* from_file isn't the one stored in files */ abort (); /* Find where the newly renamed file will go in the hash. */ file_key.hname = to_hname; file_slot = (struct file **) hash_find_slot (&files, &file_key); to_file = *file_slot; /* Change the hash name for this file. */ from_file->hname = to_hname; for (f = from_file->double_colon; f != 0; f = f->prev) f->hname = to_hname; /* If the new name doesn't exist yet just set it to the renamed file. */ if (HASH_VACANT (to_file)) { hash_insert_at (&files, from_file, file_slot); return; } /* TO_FILE already exists under TO_HNAME. We must retain TO_FILE and merge FROM_FILE into it. */ if (from_file->cmds != 0) { if (to_file->cmds == 0) to_file->cmds = from_file->cmds; else if (from_file->cmds != to_file->cmds) { /* We have two sets of commands. We will go with the one given in the rule explicitly mentioning this name, but give a message to let the user know what's going on. */ if (to_file->cmds->fileinfo.filenm != 0) error (&from_file->cmds->fileinfo, _("Recipe was specified for file `%s' at %s:%lu,"), from_file->name, to_file->cmds->fileinfo.filenm, to_file->cmds->fileinfo.lineno); else error (&from_file->cmds->fileinfo, _("Recipe for file `%s' was found by implicit rule search,"), from_file->name); error (&from_file->cmds->fileinfo, _("but `%s' is now considered the same file as `%s'."), from_file->name, to_hname); error (&from_file->cmds->fileinfo, _("Recipe for `%s' will be ignored in favor of the one for `%s'."), to_hname, from_file->name); } } /* Merge the dependencies of the two files. */ if (to_file->deps == 0) to_file->deps = from_file->deps; else { struct dep *deps = to_file->deps; while (deps->next != 0) deps = deps->next; deps->next = from_file->deps; } merge_variable_set_lists (&to_file->variables, from_file->variables); if (to_file->double_colon && from_file->is_target && !from_file->double_colon) fatal (NILF, _("can't rename single-colon `%s' to double-colon `%s'"), from_file->name, to_hname); if (!to_file->double_colon && from_file->double_colon) { if (to_file->is_target) fatal (NILF, _("can't rename double-colon `%s' to single-colon `%s'"), from_file->name, to_hname); else to_file->double_colon = from_file->double_colon; } if (from_file->last_mtime > to_file->last_mtime) /* %%% Kludge so -W wins on a file that gets vpathized. */ to_file->last_mtime = from_file->last_mtime; to_file->mtime_before_update = from_file->mtime_before_update; #define MERGE(field) to_file->field |= from_file->field MERGE (precious); MERGE (tried_implicit); MERGE (updating); MERGE (updated); MERGE (is_target); MERGE (cmd_target); MERGE (phony); MERGE (ignore_vpath); #undef MERGE from_file->renamed = to_file; } /* Rename FILE to NAME. This is not as simple as resetting the `name' member, since it must be put in a new hash bucket, and possibly merged with an existing file called NAME. */ void rename_file (struct file *from_file, const char *to_hname) { rehash_file (from_file, to_hname); while (from_file) { from_file->name = from_file->hname; from_file = from_file->prev; } } /* Remove all nonprecious intermediate files. If SIG is nonzero, this was caused by a fatal signal, meaning that a different message will be printed, and the message will go to stderr rather than stdout. */ void remove_intermediates (int sig) { struct file **file_slot; struct file **file_end; int doneany = 0; /* If there's no way we will ever remove anything anyway, punt early. */ if (question_flag || touch_flag || all_secondary) return; if (sig && just_print_flag) return; file_slot = (struct file **) files.ht_vec; file_end = file_slot + files.ht_size; for ( ; file_slot < file_end; file_slot++) if (! HASH_VACANT (*file_slot)) { struct file *f = *file_slot; /* Is this file eligible for automatic deletion? Yes, IFF: it's marked intermediate, it's not secondary, it wasn't given on the command line, and it's either a -include makefile or it's not precious. */ if (f->intermediate && (f->dontcare || !f->precious) && !f->secondary && !f->cmd_target) { int status; if (f->update_status == -1) /* If nothing would have created this file yet, don't print an "rm" command for it. */ continue; if (just_print_flag) status = 0; else { status = unlink (f->name); if (status < 0 && errno == ENOENT) continue; } if (!f->dontcare) { if (sig) error (NILF, _("*** Deleting intermediate file `%s'"), f->name); else { if (! doneany) DB (DB_BASIC, (_("Removing intermediate files...\n"))); if (!silent_flag) { if (! doneany) { fputs ("rm ", stdout); doneany = 1; } else putchar (' '); fputs (f->name, stdout); fflush (stdout); } } if (status < 0) perror_with_name ("unlink: ", f->name); } } } if (doneany && !sig) { putchar ('\n'); fflush (stdout); } } /* Given a string containing prerequisites (fully expanded), break it up into a struct dep list. Enter each of these prereqs into the file database. */ struct dep * split_prereqs (char *p) { struct dep *new = PARSE_FILE_SEQ (&p, struct dep, '|', NULL, 0); if (*p) { /* Files that follow '|' are "order-only" prerequisites that satisfy the dependency by existing: their modification times are irrelevant. */ struct dep *ood; ++p; ood = PARSE_FILE_SEQ (&p, struct dep, '\0', NULL, 0); if (! new) new = ood; else { struct dep *dp; for (dp = new; dp->next != NULL; dp = dp->next) ; dp->next = ood; } for (; ood != NULL; ood = ood->next) ood->ignore_mtime = 1; } return new; } /* Given a list of prerequisites, enter them into the file database. If STEM is set then first expand patterns using STEM. */ struct dep * enter_prereqs (struct dep *deps, const char *stem) { struct dep *d1; if (deps == 0) return 0; /* If we have a stem, expand the %'s. We use patsubst_expand to translate the prerequisites' patterns into plain prerequisite names. */ if (stem) { const char *pattern = "%"; char *buffer = variable_expand (""); struct dep *dp = deps, *dl = 0; while (dp != 0) { char *percent; int nl = strlen (dp->name) + 1; char *nm = alloca (nl); memcpy (nm, dp->name, nl); percent = find_percent (nm); if (percent) { char *o; /* We have to handle empty stems specially, because that would be equivalent to $(patsubst %,dp->name,) which will always be empty. */ if (stem[0] == '\0') { memmove (percent, percent+1, strlen (percent)); o = variable_buffer_output (buffer, nm, strlen (nm) + 1); } else o = patsubst_expand_pat (buffer, stem, pattern, nm, pattern+1, percent+1); /* If the name expanded to the empty string, ignore it. */ if (buffer[0] == '\0') { struct dep *df = dp; if (dp == deps) dp = deps = deps->next; else dp = dl->next = dp->next; free_dep (df); continue; } /* Save the name. */ dp->name = strcache_add_len (buffer, o - buffer); } dp->stem = stem; dp->staticpattern = 1; dl = dp; dp = dp->next; } } /* Enter them as files, unless they need a 2nd expansion. */ for (d1 = deps; d1 != 0; d1 = d1->next) { if (d1->need_2nd_expansion) continue; d1->file = lookup_file (d1->name); if (d1->file == 0) d1->file = enter_file (d1->name); d1->staticpattern = 0; d1->name = 0; } return deps; } /* Set the intermediate flag. */ static void set_intermediate (const void *item) { struct file *f = (struct file *) item; f->intermediate = 1; } /* Expand and parse each dependency line. */ static void expand_deps (struct file *f) { struct dep *d; struct dep **dp; const char *file_stem = f->stem; int initialized = 0; f->updating = 0; /* Walk through the dependencies. For any dependency that needs 2nd expansion, expand it then insert the result into the list. */ dp = &f->deps; d = f->deps; while (d != 0) { char *p; struct dep *new, *next; char *name = (char *)d->name; if (! d->name || ! d->need_2nd_expansion) { /* This one is all set already. */ dp = &d->next; d = d->next; continue; } /* If it's from a static pattern rule, convert the patterns into "$*" so they'll expand properly. */ if (d->staticpattern) { char *o; d->name = o = variable_expand (""); o = subst_expand (o, name, "%", "$*", 1, 2, 0); *o = '\0'; free (name); d->name = name = xstrdup (d->name); d->staticpattern = 0; } /* We're going to do second expansion so initialize file variables for the file. Since the stem for static pattern rules comes from individual dep lines, we will temporarily set f->stem to d->stem. */ if (!initialized) { initialize_file_variables (f, 0); initialized = 1; } if (d->stem != 0) f->stem = d->stem; set_file_variables (f); p = variable_expand_for_file (d->name, f); if (d->stem != 0) f->stem = file_stem; /* At this point we don't need the name anymore: free it. */ free (name); /* Parse the prerequisites and enter them into the file database. */ new = enter_prereqs (split_prereqs (p), d->stem); /* If there were no prereqs here (blank!) then throw this one out. */ if (new == 0) { *dp = d->next; free_dep (d); d = *dp; continue; } /* Add newly parsed prerequisites. */ next = d->next; *dp = new; for (dp = &new->next, d = new->next; d != 0; dp = &d->next, d = d->next) ; *dp = next; d = *dp; } } /* Reset the updating flag. */ static void reset_updating (const void *item) { struct file *f = (struct file *) item; f->updating = 0; } /* For each dependency of each file, make the `struct dep' point at the appropriate `struct file' (which may have to be created). Also mark the files depended on by .PRECIOUS, .PHONY, .SILENT, and various other special targets. */ void snap_deps (void) { struct file *f; struct file *f2; struct dep *d; /* Remember that we've done this. Once we start snapping deps we can no longer define new targets. */ snapped_deps = 1; /* Perform second expansion and enter each dependency name as a file. We must use hash_dump() here because within these loops we likely add new files to the table, possibly causing an in-situ table expansion. We only need to do this if second_expansion has been defined; if it hasn't then all deps were expanded as the makefile was read in. If we ever change make to be able to unset .SECONDARY_EXPANSION this will have to change. */ if (second_expansion) { struct file **file_slot_0 = (struct file **) hash_dump (&files, 0, 0); struct file **file_end = file_slot_0 + files.ht_fill; struct file **file_slot; const char *suffixes; /* Expand .SUFFIXES: its prerequisites are used for $$* calc. */ f = lookup_file (".SUFFIXES"); suffixes = f ? f->name : 0; for (; f != 0; f = f->prev) expand_deps (f); /* For every target that's not .SUFFIXES, expand its prerequisites. */ for (file_slot = file_slot_0; file_slot < file_end; file_slot++) for (f = *file_slot; f != 0; f = f->prev) if (f->name != suffixes) expand_deps (f); free (file_slot_0); } else /* We're not doing second expansion, so reset updating. */ hash_map (&files, reset_updating); /* Now manage all the special targets. */ for (f = lookup_file (".PRECIOUS"); f != 0; f = f->prev) for (d = f->deps; d != 0; d = d->next) for (f2 = d->file; f2 != 0; f2 = f2->prev) f2->precious = 1; for (f = lookup_file (".LOW_RESOLUTION_TIME"); f != 0; f = f->prev) for (d = f->deps; d != 0; d = d->next) for (f2 = d->file; f2 != 0; f2 = f2->prev) f2->low_resolution_time = 1; for (f = lookup_file (".PHONY"); f != 0; f = f->prev) for (d = f->deps; d != 0; d = d->next) for (f2 = d->file; f2 != 0; f2 = f2->prev) { /* Mark this file as phony nonexistent target. */ f2->phony = 1; f2->is_target = 1; f2->last_mtime = NONEXISTENT_MTIME; f2->mtime_before_update = NONEXISTENT_MTIME; } for (f = lookup_file (".INTERMEDIATE"); f != 0; f = f->prev) /* Mark .INTERMEDIATE deps as intermediate files. */ for (d = f->deps; d != 0; d = d->next) for (f2 = d->file; f2 != 0; f2 = f2->prev) f2->intermediate = 1; /* .INTERMEDIATE with no deps does nothing. Marking all files as intermediates is useless since the goal targets would be deleted after they are built. */ for (f = lookup_file (".SECONDARY"); f != 0; f = f->prev) /* Mark .SECONDARY deps as both intermediate and secondary. */ if (f->deps) for (d = f->deps; d != 0; d = d->next) for (f2 = d->file; f2 != 0; f2 = f2->prev) f2->intermediate = f2->secondary = 1; /* .SECONDARY with no deps listed marks *all* files that way. */ else { all_secondary = 1; hash_map (&files, set_intermediate); } f = lookup_file (".EXPORT_ALL_VARIABLES"); if (f != 0 && f->is_target) export_all_variables = 1; f = lookup_file (".IGNORE"); if (f != 0 && f->is_target) { if (f->deps == 0) ignore_errors_flag = 1; else for (d = f->deps; d != 0; d = d->next) for (f2 = d->file; f2 != 0; f2 = f2->prev) f2->command_flags |= COMMANDS_NOERROR; } f = lookup_file (".SILENT"); if (f != 0 && f->is_target) { if (f->deps == 0) silent_flag = 1; else for (d = f->deps; d != 0; d = d->next) for (f2 = d->file; f2 != 0; f2 = f2->prev) f2->command_flags |= COMMANDS_SILENT; } f = lookup_file (".NOTPARALLEL"); if (f != 0 && f->is_target) not_parallel = 1; #ifndef NO_MINUS_C_MINUS_O /* If .POSIX was defined, remove OUTPUT_OPTION to comply. */ /* This needs more work: what if the user sets this in the makefile? if (posix_pedantic) define_variable_cname ("OUTPUT_OPTION", "", o_default, 1); */ #endif } /* Set the `command_state' member of FILE and all its `also_make's. */ void set_command_state (struct file *file, enum cmd_state state) { struct dep *d; file->command_state = state; for (d = file->also_make; d != 0; d = d->next) d->file->command_state = state; } /* Convert an external file timestamp to internal form. */ FILE_TIMESTAMP file_timestamp_cons (const char *fname, time_t s, int ns) { int offset = ORDINARY_MTIME_MIN + (FILE_TIMESTAMP_HI_RES ? ns : 0); FILE_TIMESTAMP product = (FILE_TIMESTAMP) s << FILE_TIMESTAMP_LO_BITS; FILE_TIMESTAMP ts = product + offset; if (! (s <= FILE_TIMESTAMP_S (ORDINARY_MTIME_MAX) && product <= ts && ts <= ORDINARY_MTIME_MAX)) { char buf[FILE_TIMESTAMP_PRINT_LEN_BOUND + 1]; ts = s <= OLD_MTIME ? ORDINARY_MTIME_MIN : ORDINARY_MTIME_MAX; file_timestamp_sprintf (buf, ts); error (NILF, _("%s: Timestamp out of range; substituting %s"), fname ? fname : _("Current time"), buf); } return ts; } /* Return the current time as a file timestamp, setting *RESOLUTION to its resolution. */ FILE_TIMESTAMP file_timestamp_now (int *resolution) { int r; time_t s; int ns; /* Don't bother with high-resolution clocks if file timestamps have only one-second resolution. The code below should work, but it's not worth the hassle of debugging it on hosts where it fails. */ #if FILE_TIMESTAMP_HI_RES # if HAVE_CLOCK_GETTIME && defined CLOCK_REALTIME { struct timespec timespec; if (clock_gettime (CLOCK_REALTIME, ×pec) == 0) { r = 1; s = timespec.tv_sec; ns = timespec.tv_nsec; goto got_time; } } # endif # if HAVE_GETTIMEOFDAY { struct timeval timeval; if (gettimeofday (&timeval, 0) == 0) { r = 1000; s = timeval.tv_sec; ns = timeval.tv_usec * 1000; goto got_time; } } # endif #endif r = 1000000000; s = time ((time_t *) 0); ns = 0; #if FILE_TIMESTAMP_HI_RES got_time: #endif *resolution = r; return file_timestamp_cons (0, s, ns); } /* Place into the buffer P a printable representation of the file timestamp TS. */ void file_timestamp_sprintf (char *p, FILE_TIMESTAMP ts) { time_t t = FILE_TIMESTAMP_S (ts); struct tm *tm = localtime (&t); if (tm) sprintf (p, "%04d-%02d-%02d %02d:%02d:%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); else if (t < 0) sprintf (p, "%ld", (long) t); else sprintf (p, "%lu", (unsigned long) t); p += strlen (p); /* Append nanoseconds as a fraction, but remove trailing zeros. We don't know the actual timestamp resolution, since clock_getres applies only to local times, whereas this timestamp might come from a remote filesystem. So removing trailing zeros is the best guess that we can do. */ sprintf (p, ".%09d", FILE_TIMESTAMP_NS (ts)); p += strlen (p) - 1; while (*p == '0') p--; p += *p != '.'; *p = '\0'; } /* Print the data base of files. */ void print_prereqs (const struct dep *deps) { const struct dep *ood = 0; /* Print all normal dependencies; note any order-only deps. */ for (; deps != 0; deps = deps->next) if (! deps->ignore_mtime) printf (" %s", dep_name (deps)); else if (! ood) ood = deps; /* Print order-only deps, if we have any. */ if (ood) { printf (" | %s", dep_name (ood)); for (ood = ood->next; ood != 0; ood = ood->next) if (ood->ignore_mtime) printf (" %s", dep_name (ood)); } putchar ('\n'); } static void print_file (const void *item) { const struct file *f = item; putchar ('\n'); if (!f->is_target) puts (_("# Not a target:")); printf ("%s:%s", f->name, f->double_colon ? ":" : ""); print_prereqs (f->deps); if (f->precious) puts (_("# Precious file (prerequisite of .PRECIOUS).")); if (f->phony) puts (_("# Phony target (prerequisite of .PHONY).")); if (f->cmd_target) puts (_("# Command line target.")); if (f->dontcare) puts (_("# A default, MAKEFILES, or -include/sinclude makefile.")); puts (f->tried_implicit ? _("# Implicit rule search has been done.") : _("# Implicit rule search has not been done.")); if (f->stem != 0) printf (_("# Implicit/static pattern stem: `%s'\n"), f->stem); if (f->intermediate) puts (_("# File is an intermediate prerequisite.")); if (f->also_make != 0) { const struct dep *d; fputs (_("# Also makes:"), stdout); for (d = f->also_make; d != 0; d = d->next) printf (" %s", dep_name (d)); putchar ('\n'); } if (f->last_mtime == UNKNOWN_MTIME) puts (_("# Modification time never checked.")); else if (f->last_mtime == NONEXISTENT_MTIME) puts (_("# File does not exist.")); else if (f->last_mtime == OLD_MTIME) puts (_("# File is very old.")); else { char buf[FILE_TIMESTAMP_PRINT_LEN_BOUND + 1]; file_timestamp_sprintf (buf, f->last_mtime); printf (_("# Last modified %s\n"), buf); } puts (f->updated ? _("# File has been updated.") : _("# File has not been updated.")); switch (f->command_state) { case cs_running: puts (_("# Recipe currently running (THIS IS A BUG).")); break; case cs_deps_running: puts (_("# Dependencies recipe running (THIS IS A BUG).")); break; case cs_not_started: case cs_finished: switch (f->update_status) { case -1: break; case 0: puts (_("# Successfully updated.")); break; case 1: assert (question_flag); puts (_("# Needs to be updated (-q is set).")); break; case 2: puts (_("# Failed to be updated.")); break; default: puts (_("# Invalid value in `update_status' member!")); fflush (stdout); fflush (stderr); abort (); } break; default: puts (_("# Invalid value in `command_state' member!")); fflush (stdout); fflush (stderr); abort (); } if (f->variables != 0) print_file_variables (f); if (f->cmds != 0) print_commands (f->cmds); if (f->prev) print_file ((const void *) f->prev); } void print_file_data_base (void) { puts (_("\n# Files")); hash_map (&files, print_file); fputs (_("\n# files hash-table stats:\n# "), stdout); hash_print_stats (&files, stdout); } /* Verify the integrity of the data base of files. */ #define VERIFY_CACHED(_p,_n) \ do{\ if (_p->_n && _p->_n[0] && !strcache_iscached (_p->_n)) \ error (NULL, "%s: Field '%s' not cached: %s\n", _p->name, # _n, _p->_n); \ }while(0) static void verify_file (const void *item) { const struct file *f = item; const struct dep *d; VERIFY_CACHED (f, name); VERIFY_CACHED (f, hname); VERIFY_CACHED (f, vpath); VERIFY_CACHED (f, stem); /* Check the deps. */ for (d = f->deps; d != 0; d = d->next) { if (! d->need_2nd_expansion) VERIFY_CACHED (d, name); VERIFY_CACHED (d, stem); } } void verify_file_data_base (void) { hash_map (&files, verify_file); } #define EXPANSION_INCREMENT(_l) ((((_l) / 500) + 1) * 500) char * build_target_list (char *value) { static unsigned long last_targ_count = 0; if (files.ht_fill != last_targ_count) { unsigned long max = EXPANSION_INCREMENT (strlen (value)); unsigned long len; char *p; struct file **fp = (struct file **) files.ht_vec; struct file **end = &fp[files.ht_size]; /* Make sure we have at least MAX bytes in the allocated buffer. */ value = xrealloc (value, max); p = value; len = 0; for (; fp < end; ++fp) if (!HASH_VACANT (*fp) && (*fp)->is_target) { struct file *f = *fp; int l = strlen (f->name); len += l + 1; if (len > max) { unsigned long off = p - value; max += EXPANSION_INCREMENT (l + 1); value = xrealloc (value, max); p = &value[off]; } memcpy (p, f->name, l); p += l; *(p++) = ' '; } *(p-1) = '\0'; last_targ_count = files.ht_fill; } return value; } void init_hash_files (void) { hash_init (&files, 1000, file_hash_1, file_hash_2, file_hash_cmp); } /* EOF */
/* Debugging macros and interface. Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #define DB_NONE (0x000) #define DB_BASIC (0x001) #define DB_VERBOSE (0x002) #define DB_JOBS (0x004) #define DB_IMPLICIT (0x008) #define DB_MAKEFILES (0x100) #define DB_ALL (0xfff) extern int db_level; #define ISDB(_l) ((_l)&db_level) #define DBS(_l,_x) do{ if(ISDB(_l)) {print_spaces (depth); \ printf _x; fflush (stdout);} }while(0) #define DBF(_l,_x) do{ if(ISDB(_l)) {print_spaces (depth); \ printf (_x, file->name); \ fflush (stdout);} }while(0) #define DB(_l,_x) do{ if(ISDB(_l)) {printf _x; fflush (stdout);} }while(0)
/* Builtin function expansion for GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" #include "filedef.h" #include "variable.h" #include "dep.h" #include "job.h" #include "commands.h" #include "debug.h" #ifdef _AMIGA #include "amiga.h" #endif struct function_table_entry { const char *name; unsigned char len; unsigned char minimum_args; unsigned char maximum_args; char expand_args; char *(*func_ptr) (char *output, char **argv, const char *fname); }; static unsigned long function_table_entry_hash_1 (const void *keyv) { const struct function_table_entry *key = keyv; return_STRING_N_HASH_1 (key->name, key->len); } static unsigned long function_table_entry_hash_2 (const void *keyv) { const struct function_table_entry *key = keyv; return_STRING_N_HASH_2 (key->name, key->len); } static int function_table_entry_hash_cmp (const void *xv, const void *yv) { const struct function_table_entry *x = xv; const struct function_table_entry *y = yv; int result = x->len - y->len; if (result) return result; return_STRING_N_COMPARE (x->name, y->name, x->len); } static struct hash_table function_table; /* Store into VARIABLE_BUFFER at O the result of scanning TEXT and replacing each occurrence of SUBST with REPLACE. TEXT is null-terminated. SLEN is the length of SUBST and RLEN is the length of REPLACE. If BY_WORD is nonzero, substitutions are done only on matches which are complete whitespace-delimited words. */ char * subst_expand (char *o, const char *text, const char *subst, const char *replace, unsigned int slen, unsigned int rlen, int by_word) { const char *t = text; const char *p; if (slen == 0 && !by_word) { /* The first occurrence of "" in any string is its end. */ o = variable_buffer_output (o, t, strlen (t)); if (rlen > 0) o = variable_buffer_output (o, replace, rlen); return o; } do { if (by_word && slen == 0) /* When matching by words, the empty string should match the end of each word, rather than the end of the whole text. */ p = end_of_token (next_token (t)); else { p = strstr (t, subst); if (p == 0) { /* No more matches. Output everything left on the end. */ o = variable_buffer_output (o, t, strlen (t)); return o; } } /* Output everything before this occurrence of the string to replace. */ if (p > t) o = variable_buffer_output (o, t, p - t); /* If we're substituting only by fully matched words, or only at the ends of words, check that this case qualifies. */ if (by_word && ((p > text && !isblank ((unsigned char)p[-1])) || (p[slen] != '\0' && !isblank ((unsigned char)p[slen])))) /* Struck out. Output the rest of the string that is no longer to be replaced. */ o = variable_buffer_output (o, subst, slen); else if (rlen > 0) /* Output the replacement string. */ o = variable_buffer_output (o, replace, rlen); /* Advance T past the string to be replaced. */ t = p + slen; } while (*t != '\0'); return o; } /* Store into VARIABLE_BUFFER at O the result of scanning TEXT and replacing strings matching PATTERN with REPLACE. If PATTERN_PERCENT is not nil, PATTERN has already been run through find_percent, and PATTERN_PERCENT is the result. If REPLACE_PERCENT is not nil, REPLACE has already been run through find_percent, and REPLACE_PERCENT is the result. Note that we expect PATTERN_PERCENT and REPLACE_PERCENT to point to the character _AFTER_ the %, not to the % itself. */ char * patsubst_expand_pat (char *o, const char *text, const char *pattern, const char *replace, const char *pattern_percent, const char *replace_percent) { unsigned int pattern_prepercent_len, pattern_postpercent_len; unsigned int replace_prepercent_len, replace_postpercent_len; const char *t; unsigned int len; int doneany = 0; /* Record the length of REPLACE before and after the % so we don't have to compute these lengths more than once. */ if (replace_percent) { replace_prepercent_len = replace_percent - replace - 1; replace_postpercent_len = strlen (replace_percent); } else { replace_prepercent_len = strlen (replace); replace_postpercent_len = 0; } if (!pattern_percent) /* With no % in the pattern, this is just a simple substitution. */ return subst_expand (o, text, pattern, replace, strlen (pattern), strlen (replace), 1); /* Record the length of PATTERN before and after the % so we don't have to compute it more than once. */ pattern_prepercent_len = pattern_percent - pattern - 1; pattern_postpercent_len = strlen (pattern_percent); while ((t = find_next_token (&text, &len)) != 0) { int fail = 0; /* Is it big enough to match? */ if (len < pattern_prepercent_len + pattern_postpercent_len) fail = 1; /* Does the prefix match? */ if (!fail && pattern_prepercent_len > 0 && (*t != *pattern || t[pattern_prepercent_len - 1] != pattern_percent[-2] || !strneq (t + 1, pattern + 1, pattern_prepercent_len - 1))) fail = 1; /* Does the suffix match? */ if (!fail && pattern_postpercent_len > 0 && (t[len - 1] != pattern_percent[pattern_postpercent_len - 1] || t[len - pattern_postpercent_len] != *pattern_percent || !strneq (&t[len - pattern_postpercent_len], pattern_percent, pattern_postpercent_len - 1))) fail = 1; if (fail) /* It didn't match. Output the string. */ o = variable_buffer_output (o, t, len); else { /* It matched. Output the replacement. */ /* Output the part of the replacement before the %. */ o = variable_buffer_output (o, replace, replace_prepercent_len); if (replace_percent != 0) { /* Output the part of the matched string that matched the % in the pattern. */ o = variable_buffer_output (o, t + pattern_prepercent_len, len - (pattern_prepercent_len + pattern_postpercent_len)); /* Output the part of the replacement after the %. */ o = variable_buffer_output (o, replace_percent, replace_postpercent_len); } } /* Output a space, but not if the replacement is "". */ if (fail || replace_prepercent_len > 0 || (replace_percent != 0 && len + replace_postpercent_len > 0)) { o = variable_buffer_output (o, " ", 1); doneany = 1; } } if (doneany) /* Kill the last space. */ --o; return o; } /* Store into VARIABLE_BUFFER at O the result of scanning TEXT and replacing strings matching PATTERN with REPLACE. If PATTERN_PERCENT is not nil, PATTERN has already been run through find_percent, and PATTERN_PERCENT is the result. If REPLACE_PERCENT is not nil, REPLACE has already been run through find_percent, and REPLACE_PERCENT is the result. Note that we expect PATTERN_PERCENT and REPLACE_PERCENT to point to the character _AFTER_ the %, not to the % itself. */ char * patsubst_expand (char *o, const char *text, char *pattern, char *replace) { const char *pattern_percent = find_percent (pattern); const char *replace_percent = find_percent (replace); /* If there's a percent in the pattern or replacement skip it. */ if (replace_percent) ++replace_percent; if (pattern_percent) ++pattern_percent; return patsubst_expand_pat (o, text, pattern, replace, pattern_percent, replace_percent); } /* Look up a function by name. */ static const struct function_table_entry * lookup_function (const char *s) { const char *e = s; while (*e && ( (*e >= 'a' && *e <= 'z') || *e == '-')) e++; if (*e == '\0' || isblank ((unsigned char) *e)) { struct function_table_entry function_table_entry_key; function_table_entry_key.name = s; function_table_entry_key.len = e - s; return hash_find_item (&function_table, &function_table_entry_key); } return 0; } /* Return 1 if PATTERN matches STR, 0 if not. */ int pattern_matches (const char *pattern, const char *percent, const char *str) { unsigned int sfxlen, strlength; if (percent == 0) { unsigned int len = strlen (pattern) + 1; char *new_chars = alloca (len); memcpy (new_chars, pattern, len); percent = find_percent (new_chars); if (percent == 0) return streq (new_chars, str); pattern = new_chars; } sfxlen = strlen (percent + 1); strlength = strlen (str); if (strlength < (percent - pattern) + sfxlen || !strneq (pattern, str, percent - pattern)) return 0; return !strcmp (percent + 1, str + (strlength - sfxlen)); } /* Find the next comma or ENDPAREN (counting nested STARTPAREN and ENDPARENtheses), starting at PTR before END. Return a pointer to next character. If no next argument is found, return NULL. */ static char * find_next_argument (char startparen, char endparen, const char *ptr, const char *end) { int count = 0; for (; ptr < end; ++ptr) if (*ptr == startparen) ++count; else if (*ptr == endparen) { --count; if (count < 0) return NULL; } else if (*ptr == ',' && !count) return (char *)ptr; /* We didn't find anything. */ return NULL; } /* Glob-expand LINE. The returned pointer is only good until the next call to string_glob. */ static char * string_glob (char *line) { static char *result = 0; static unsigned int length; struct nameseq *chain; unsigned int idx; chain = PARSE_FILE_SEQ (&line, struct nameseq, '\0', NULL, /* We do not want parse_file_seq to strip `./'s. That would break examples like: $(patsubst ./%.c,obj/%.o,$(wildcard ./?*.c)). */ PARSEFS_NOSTRIP|PARSEFS_NOCACHE|PARSEFS_EXISTS); if (result == 0) { length = 100; result = xmalloc (100); } idx = 0; while (chain != 0) { struct nameseq *next = chain->next; unsigned int len = strlen (chain->name); if (idx + len + 1 > length) { length += (len + 1) * 2; result = xrealloc (result, length); } memcpy (&result[idx], chain->name, len); idx += len; result[idx++] = ' '; /* Because we used PARSEFS_NOCACHE above, we have to free() NAME. */ free ((char *)chain->name); free (chain); chain = next; } /* Kill the last space and terminate the string. */ if (idx == 0) result[0] = '\0'; else result[idx - 1] = '\0'; return result; } /* Builtin functions */ static char * func_patsubst (char *o, char **argv, const char *funcname UNUSED) { o = patsubst_expand (o, argv[2], argv[0], argv[1]); return o; } static char * func_join (char *o, char **argv, const char *funcname UNUSED) { int doneany = 0; /* Write each word of the first argument directly followed by the corresponding word of the second argument. If the two arguments have a different number of words, the excess words are just output separated by blanks. */ const char *tp; const char *pp; const char *list1_iterator = argv[0]; const char *list2_iterator = argv[1]; do { unsigned int len1, len2; tp = find_next_token (&list1_iterator, &len1); if (tp != 0) o = variable_buffer_output (o, tp, len1); pp = find_next_token (&list2_iterator, &len2); if (pp != 0) o = variable_buffer_output (o, pp, len2); if (tp != 0 || pp != 0) { o = variable_buffer_output (o, " ", 1); doneany = 1; } } while (tp != 0 || pp != 0); if (doneany) /* Kill the last blank. */ --o; return o; } static char * func_origin (char *o, char **argv, const char *funcname UNUSED) { /* Expand the argument. */ struct variable *v = lookup_variable (argv[0], strlen (argv[0])); if (v == 0) o = variable_buffer_output (o, "undefined", 9); else switch (v->origin) { default: case o_invalid: abort (); break; case o_default: o = variable_buffer_output (o, "default", 7); break; case o_env: o = variable_buffer_output (o, "environment", 11); break; case o_file: o = variable_buffer_output (o, "file", 4); break; case o_env_override: o = variable_buffer_output (o, "environment override", 20); break; case o_command: o = variable_buffer_output (o, "command line", 12); break; case o_override: o = variable_buffer_output (o, "override", 8); break; case o_automatic: o = variable_buffer_output (o, "automatic", 9); break; } return o; } static char * func_flavor (char *o, char **argv, const char *funcname UNUSED) { struct variable *v = lookup_variable (argv[0], strlen (argv[0])); if (v == 0) o = variable_buffer_output (o, "undefined", 9); else if (v->recursive) o = variable_buffer_output (o, "recursive", 9); else o = variable_buffer_output (o, "simple", 6); return o; } #ifdef VMS # define IS_PATHSEP(c) ((c) == ']') #else # ifdef HAVE_DOS_PATHS # define IS_PATHSEP(c) ((c) == '/' || (c) == '\\') # else # define IS_PATHSEP(c) ((c) == '/') # endif #endif static char * func_notdir_suffix (char *o, char **argv, const char *funcname) { /* Expand the argument. */ const char *list_iterator = argv[0]; const char *p2; int doneany =0; unsigned int len=0; int is_suffix = streq (funcname, "suffix"); int is_notdir = !is_suffix; while ((p2 = find_next_token (&list_iterator, &len)) != 0) { const char *p = p2 + len; while (p >= p2 && (!is_suffix || *p != '.')) { if (IS_PATHSEP (*p)) break; --p; } if (p >= p2) { if (is_notdir) ++p; else if (*p != '.') continue; o = variable_buffer_output (o, p, len - (p - p2)); } #ifdef HAVE_DOS_PATHS /* Handle the case of "d:foo/bar". */ else if (streq (funcname, "notdir") && p2[0] && p2[1] == ':') { p = p2 + 2; o = variable_buffer_output (o, p, len - (p - p2)); } #endif else if (is_notdir) o = variable_buffer_output (o, p2, len); if (is_notdir || p >= p2) { o = variable_buffer_output (o, " ", 1); doneany = 1; } } if (doneany) /* Kill last space. */ --o; return o; } static char * func_basename_dir (char *o, char **argv, const char *funcname) { /* Expand the argument. */ const char *p3 = argv[0]; const char *p2; int doneany=0; unsigned int len=0; int is_basename= streq (funcname, "basename"); int is_dir= !is_basename; while ((p2 = find_next_token (&p3, &len)) != 0) { const char *p = p2 + len; while (p >= p2 && (!is_basename || *p != '.')) { if (IS_PATHSEP (*p)) break; --p; } if (p >= p2 && (is_dir)) o = variable_buffer_output (o, p2, ++p - p2); else if (p >= p2 && (*p == '.')) o = variable_buffer_output (o, p2, p - p2); #ifdef HAVE_DOS_PATHS /* Handle the "d:foobar" case */ else if (p2[0] && p2[1] == ':' && is_dir) o = variable_buffer_output (o, p2, 2); #endif else if (is_dir) #ifdef VMS o = variable_buffer_output (o, "[]", 2); #else #ifndef _AMIGA o = variable_buffer_output (o, "./", 2); #else ; /* Just a nop... */ #endif /* AMIGA */ #endif /* !VMS */ else /* The entire name is the basename. */ o = variable_buffer_output (o, p2, len); o = variable_buffer_output (o, " ", 1); doneany = 1; } if (doneany) /* Kill last space. */ --o; return o; } static char * func_addsuffix_addprefix (char *o, char **argv, const char *funcname) { int fixlen = strlen (argv[0]); const char *list_iterator = argv[1]; int is_addprefix = streq (funcname, "addprefix"); int is_addsuffix = !is_addprefix; int doneany = 0; const char *p; unsigned int len; while ((p = find_next_token (&list_iterator, &len)) != 0) { if (is_addprefix) o = variable_buffer_output (o, argv[0], fixlen); o = variable_buffer_output (o, p, len); if (is_addsuffix) o = variable_buffer_output (o, argv[0], fixlen); o = variable_buffer_output (o, " ", 1); doneany = 1; } if (doneany) /* Kill last space. */ --o; return o; } static char * func_subst (char *o, char **argv, const char *funcname UNUSED) { o = subst_expand (o, argv[2], argv[0], argv[1], strlen (argv[0]), strlen (argv[1]), 0); return o; } static char * func_firstword (char *o, char **argv, const char *funcname UNUSED) { unsigned int i; const char *words = argv[0]; /* Use a temp variable for find_next_token */ const char *p = find_next_token (&words, &i); if (p != 0) o = variable_buffer_output (o, p, i); return o; } static char * func_lastword (char *o, char **argv, const char *funcname UNUSED) { unsigned int i; const char *words = argv[0]; /* Use a temp variable for find_next_token */ const char *p = NULL; const char *t; while ((t = find_next_token (&words, &i))) p = t; if (p != 0) o = variable_buffer_output (o, p, i); return o; } static char * func_words (char *o, char **argv, const char *funcname UNUSED) { int i = 0; const char *word_iterator = argv[0]; char buf[20]; while (find_next_token (&word_iterator, (unsigned int *) 0) != 0) ++i; sprintf (buf, "%d", i); o = variable_buffer_output (o, buf, strlen (buf)); return o; } /* Set begpp to point to the first non-whitespace character of the string, * and endpp to point to the last non-whitespace character of the string. * If the string is empty or contains nothing but whitespace, endpp will be * begpp-1. */ char * strip_whitespace (const char **begpp, const char **endpp) { while (*begpp <= *endpp && isspace ((unsigned char)**begpp)) (*begpp) ++; while (*endpp >= *begpp && isspace ((unsigned char)**endpp)) (*endpp) --; return (char *)*begpp; } static void check_numeric (const char *s, const char *msg) { const char *end = s + strlen (s) - 1; const char *beg = s; strip_whitespace (&s, &end); for (; s <= end; ++s) if (!ISDIGIT (*s)) /* ISDIGIT only evals its arg once: see make.h. */ break; if (s <= end || end - beg < 0) fatal (*expanding_var, "%s: '%s'", msg, beg); } static char * func_word (char *o, char **argv, const char *funcname UNUSED) { const char *end_p; const char *p; int i; /* Check the first argument. */ check_numeric (argv[0], _("non-numeric first argument to `word' function")); i = atoi (argv[0]); if (i == 0) fatal (*expanding_var, _("first argument to `word' function must be greater than 0")); end_p = argv[1]; while ((p = find_next_token (&end_p, 0)) != 0) if (--i == 0) break; if (i == 0) o = variable_buffer_output (o, p, end_p - p); return o; } static char * func_wordlist (char *o, char **argv, const char *funcname UNUSED) { int start, count; /* Check the arguments. */ check_numeric (argv[0], _("non-numeric first argument to `wordlist' function")); check_numeric (argv[1], _("non-numeric second argument to `wordlist' function")); start = atoi (argv[0]); if (start < 1) fatal (*expanding_var, "invalid first argument to `wordlist' function: `%d'", start); count = atoi (argv[1]) - start + 1; if (count > 0) { const char *p; const char *end_p = argv[2]; /* Find the beginning of the "start"th word. */ while (((p = find_next_token (&end_p, 0)) != 0) && --start) ; if (p) { /* Find the end of the "count"th word from start. */ while (--count && (find_next_token (&end_p, 0) != 0)) ; /* Return the stuff in the middle. */ o = variable_buffer_output (o, p, end_p - p); } } return o; } static char * func_findstring (char *o, char **argv, const char *funcname UNUSED) { /* Find the first occurrence of the first string in the second. */ if (strstr (argv[1], argv[0]) != 0) o = variable_buffer_output (o, argv[0], strlen (argv[0])); return o; } static char * func_foreach (char *o, char **argv, const char *funcname UNUSED) { /* expand only the first two. */ char *varname = expand_argument (argv[0], NULL); char *list = expand_argument (argv[1], NULL); const char *body = argv[2]; int doneany = 0; const char *list_iterator = list; const char *p; unsigned int len; struct variable *var; push_new_variable_scope (); var = define_variable (varname, strlen (varname), "", o_automatic, 0); /* loop through LIST, put the value in VAR and expand BODY */ while ((p = find_next_token (&list_iterator, &len)) != 0) { char *result = 0; free (var->value); var->value = xstrndup (p, len); result = allocated_variable_expand (body); o = variable_buffer_output (o, result, strlen (result)); o = variable_buffer_output (o, " ", 1); doneany = 1; free (result); } if (doneany) /* Kill the last space. */ --o; pop_variable_scope (); free (varname); free (list); return o; } struct a_word { struct a_word *next; struct a_word *chain; char *str; int length; int matched; }; static unsigned long a_word_hash_1 (const void *key) { return_STRING_HASH_1 (((struct a_word const *) key)->str); } static unsigned long a_word_hash_2 (const void *key) { return_STRING_HASH_2 (((struct a_word const *) key)->str); } static int a_word_hash_cmp (const void *x, const void *y) { int result = ((struct a_word const *) x)->length - ((struct a_word const *) y)->length; if (result) return result; return_STRING_COMPARE (((struct a_word const *) x)->str, ((struct a_word const *) y)->str); } struct a_pattern { struct a_pattern *next; char *str; char *percent; int length; int save_c; }; static char * func_filter_filterout (char *o, char **argv, const char *funcname) { struct a_word *wordhead; struct a_word **wordtail; struct a_word *wp; struct a_pattern *pathead; struct a_pattern **pattail; struct a_pattern *pp; struct hash_table a_word_table; int is_filter = streq (funcname, "filter"); const char *pat_iterator = argv[0]; const char *word_iterator = argv[1]; int literals = 0; int words = 0; int hashing = 0; char *p; unsigned int len; /* Chop ARGV[0] up into patterns to match against the words. */ pattail = &pathead; while ((p = find_next_token (&pat_iterator, &len)) != 0) { struct a_pattern *pat = alloca (sizeof (struct a_pattern)); *pattail = pat; pattail = &pat->next; if (*pat_iterator != '\0') ++pat_iterator; pat->str = p; pat->length = len; pat->save_c = p[len]; p[len] = '\0'; pat->percent = find_percent (p); if (pat->percent == 0) literals++; } *pattail = 0; /* Chop ARGV[1] up into words to match against the patterns. */ wordtail = &wordhead; while ((p = find_next_token (&word_iterator, &len)) != 0) { struct a_word *word = alloca (sizeof (struct a_word)); *wordtail = word; wordtail = &word->next; if (*word_iterator != '\0') ++word_iterator; p[len] = '\0'; word->str = p; word->length = len; word->matched = 0; word->chain = 0; words++; } *wordtail = 0; /* Only use a hash table if arg list lengths justifies the cost. */ hashing = (literals >= 2 && (literals * words) >= 10); if (hashing) { hash_init (&a_word_table, words, a_word_hash_1, a_word_hash_2, a_word_hash_cmp); for (wp = wordhead; wp != 0; wp = wp->next) { struct a_word *owp = hash_insert (&a_word_table, wp); if (owp) wp->chain = owp; } } if (words) { int doneany = 0; /* Run each pattern through the words, killing words. */ for (pp = pathead; pp != 0; pp = pp->next) { if (pp->percent) for (wp = wordhead; wp != 0; wp = wp->next) wp->matched |= pattern_matches (pp->str, pp->percent, wp->str); else if (hashing) { struct a_word a_word_key; a_word_key.str = pp->str; a_word_key.length = pp->length; wp = hash_find_item (&a_word_table, &a_word_key); while (wp) { wp->matched |= 1; wp = wp->chain; } } else for (wp = wordhead; wp != 0; wp = wp->next) wp->matched |= (wp->length == pp->length && strneq (pp->str, wp->str, wp->length)); } /* Output the words that matched (or didn't, for filter-out). */ for (wp = wordhead; wp != 0; wp = wp->next) if (is_filter ? wp->matched : !wp->matched) { o = variable_buffer_output (o, wp->str, strlen (wp->str)); o = variable_buffer_output (o, " ", 1); doneany = 1; } if (doneany) /* Kill the last space. */ --o; } for (pp = pathead; pp != 0; pp = pp->next) pp->str[pp->length] = pp->save_c; if (hashing) hash_free (&a_word_table, 0); return o; } static char * func_strip (char *o, char **argv, const char *funcname UNUSED) { const char *p = argv[0]; int doneany = 0; while (*p != '\0') { int i=0; const char *word_start; while (isspace ((unsigned char)*p)) ++p; word_start = p; for (i=0; *p != '\0' && !isspace ((unsigned char)*p); ++p, ++i) {} if (!i) break; o = variable_buffer_output (o, word_start, i); o = variable_buffer_output (o, " ", 1); doneany = 1; } if (doneany) /* Kill the last space. */ --o; return o; } /* Print a warning or fatal message. */ static char * func_error (char *o, char **argv, const char *funcname) { char **argvp; char *msg, *p; int len; /* The arguments will be broken on commas. Rather than create yet another special case where function arguments aren't broken up, just create a format string that puts them back together. */ for (len=0, argvp=argv; *argvp != 0; ++argvp) len += strlen (*argvp) + 2; p = msg = alloca (len + 1); for (argvp=argv; argvp[1] != 0; ++argvp) { strcpy (p, *argvp); p += strlen (*argvp); *(p++) = ','; *(p++) = ' '; } strcpy (p, *argvp); switch (*funcname) { case 'e': fatal (reading_file, "%s", msg); case 'w': error (reading_file, "%s", msg); break; case 'i': printf ("%s\n", msg); fflush(stdout); break; default: fatal (*expanding_var, "Internal error: func_error: '%s'", funcname); } /* The warning function expands to the empty string. */ return o; } /* chop argv[0] into words, and sort them. */ static char * func_sort (char *o, char **argv, const char *funcname UNUSED) { const char *t; char **words; int wordi; char *p; unsigned int len; int i; /* Find the maximum number of words we'll have. */ t = argv[0]; wordi = 1; while (*t != '\0') { char c = *(t++); if (! isspace ((unsigned char)c)) continue; ++wordi; while (isspace ((unsigned char)*t)) ++t; } words = xmalloc (wordi * sizeof (char *)); /* Now assign pointers to each string in the array. */ t = argv[0]; wordi = 0; while ((p = find_next_token (&t, &len)) != 0) { ++t; p[len] = '\0'; words[wordi++] = p; } if (wordi) { /* Now sort the list of words. */ qsort (words, wordi, sizeof (char *), alpha_compare); /* Now write the sorted list, uniquified. */ for (i = 0; i < wordi; ++i) { len = strlen (words[i]); if (i == wordi - 1 || strlen (words[i + 1]) != len || strcmp (words[i], words[i + 1])) { o = variable_buffer_output (o, words[i], len); o = variable_buffer_output (o, " ", 1); } } /* Kill the last space. */ --o; } free (words); return o; } /* $(if condition,true-part[,false-part]) CONDITION is false iff it evaluates to an empty string. White space before and after condition are stripped before evaluation. If CONDITION is true, then TRUE-PART is evaluated, otherwise FALSE-PART is evaluated (if it exists). Because only one of the two PARTs is evaluated, you can use $(if ...) to create side-effects (with $(shell ...), for example). */ static char * func_if (char *o, char **argv, const char *funcname UNUSED) { const char *begp = argv[0]; const char *endp = begp + strlen (argv[0]) - 1; int result = 0; /* Find the result of the condition: if we have a value, and it's not empty, the condition is true. If we don't have a value, or it's the empty string, then it's false. */ strip_whitespace (&begp, &endp); if (begp <= endp) { char *expansion = expand_argument (begp, endp+1); result = strlen (expansion); free (expansion); } /* If the result is true (1) we want to eval the first argument, and if it's false (0) we want to eval the second. If the argument doesn't exist we do nothing, otherwise expand it and add to the buffer. */ argv += 1 + !result; if (*argv) { char *expansion = expand_argument (*argv, NULL); o = variable_buffer_output (o, expansion, strlen (expansion)); free (expansion); } return o; } /* $(or condition1[,condition2[,condition3[...]]]) A CONDITION is false iff it evaluates to an empty string. White space before and after CONDITION are stripped before evaluation. CONDITION1 is evaluated. If it's true, then this is the result of expansion. If it's false, CONDITION2 is evaluated, and so on. If none of the conditions are true, the expansion is the empty string. Once a CONDITION is true no further conditions are evaluated (short-circuiting). */ static char * func_or (char *o, char **argv, const char *funcname UNUSED) { for ( ; *argv ; ++argv) { const char *begp = *argv; const char *endp = begp + strlen (*argv) - 1; char *expansion; int result = 0; /* Find the result of the condition: if it's false keep going. */ strip_whitespace (&begp, &endp); if (begp > endp) continue; expansion = expand_argument (begp, endp+1); result = strlen (expansion); /* If the result is false keep going. */ if (!result) { free (expansion); continue; } /* It's true! Keep this result and return. */ o = variable_buffer_output (o, expansion, result); free (expansion); break; } return o; } /* $(and condition1[,condition2[,condition3[...]]]) A CONDITION is false iff it evaluates to an empty string. White space before and after CONDITION are stripped before evaluation. CONDITION1 is evaluated. If it's false, then this is the result of expansion. If it's true, CONDITION2 is evaluated, and so on. If all of the conditions are true, the expansion is the result of the last condition. Once a CONDITION is false no further conditions are evaluated (short-circuiting). */ static char * func_and (char *o, char **argv, const char *funcname UNUSED) { char *expansion; int result; while (1) { const char *begp = *argv; const char *endp = begp + strlen (*argv) - 1; /* An empty condition is always false. */ strip_whitespace (&begp, &endp); if (begp > endp) return o; expansion = expand_argument (begp, endp+1); result = strlen (expansion); /* If the result is false, stop here: we're done. */ if (!result) break; /* Otherwise the result is true. If this is the last one, keep this result and quit. Otherwise go on to the next one! */ if (*(++argv)) free (expansion); else { o = variable_buffer_output (o, expansion, result); break; } } free (expansion); return o; } static char * func_wildcard (char *o, char **argv, const char *funcname UNUSED) { #ifdef _AMIGA o = wildcard_expansion (argv[0], o); #else char *p = string_glob (argv[0]); o = variable_buffer_output (o, p, strlen (p)); #endif return o; } /* $(eval <makefile string>) Always resolves to the empty string. Treat the arguments as a segment of makefile, and parse them. */ static char * func_eval (char *o, char **argv, const char *funcname UNUSED) { char *buf; unsigned int len; /* Eval the buffer. Pop the current variable buffer setting so that the eval'd code can use its own without conflicting. */ install_variable_buffer (&buf, &len); eval_buffer (argv[0]); restore_variable_buffer (buf, len); return o; } static char * func_value (char *o, char **argv, const char *funcname UNUSED) { /* Look up the variable. */ struct variable *v = lookup_variable (argv[0], strlen (argv[0])); /* Copy its value into the output buffer without expanding it. */ if (v) o = variable_buffer_output (o, v->value, strlen(v->value)); return o; } /* \r is replaced on UNIX as well. Is this desirable? */ static void fold_newlines (char *buffer, unsigned int *length) { char *dst = buffer; char *src = buffer; char *last_nonnl = buffer -1; src[*length] = 0; for (; *src != '\0'; ++src) { if (src[0] == '\r' && src[1] == '\n') continue; if (*src == '\n') { *dst++ = ' '; } else { last_nonnl = dst; *dst++ = *src; } } *(++last_nonnl) = '\0'; *length = last_nonnl - buffer; } int shell_function_pid = 0, shell_function_completed; #ifdef WINDOWS32 /*untested*/ #include <windows.h> #include <io.h> #include "sub_proc.h" void windows32_openpipe (int *pipedes, pid_t *pid_p, char **command_argv, char **envp) { SECURITY_ATTRIBUTES saAttr; HANDLE hIn; HANDLE hErr; HANDLE hChildOutRd; HANDLE hChildOutWr; HANDLE hProcess; saAttr.nLength = sizeof (SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; if (DuplicateHandle (GetCurrentProcess(), GetStdHandle(STD_INPUT_HANDLE), GetCurrentProcess(), &hIn, 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE) { fatal (NILF, _("windows32_openpipe(): DuplicateHandle(In) failed (e=%ld)\n"), GetLastError()); } if (DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_ERROR_HANDLE), GetCurrentProcess(), &hErr, 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE) { fatal (NILF, _("windows32_open_pipe(): DuplicateHandle(Err) failed (e=%ld)\n"), GetLastError()); } if (!CreatePipe(&hChildOutRd, &hChildOutWr, &saAttr, 0)) fatal (NILF, _("CreatePipe() failed (e=%ld)\n"), GetLastError()); hProcess = process_init_fd(hIn, hChildOutWr, hErr); if (!hProcess) fatal (NILF, _("windows32_openpipe(): process_init_fd() failed\n")); /* make sure that CreateProcess() has Path it needs */ sync_Path_environment(); /* `sync_Path_environment' may realloc `environ', so take note of the new value. */ envp = environ; if (!process_begin(hProcess, command_argv, envp, command_argv[0], NULL)) { /* register process for wait */ process_register(hProcess); /* set the pid for returning to caller */ *pid_p = (pid_t) hProcess; /* set up to read data from child */ pipedes[0] = _open_osfhandle((intptr_t) hChildOutRd, O_RDONLY); /* this will be closed almost right away */ pipedes[1] = _open_osfhandle((intptr_t) hChildOutWr, O_APPEND); } else { /* reap/cleanup the failed process */ process_cleanup(hProcess); /* close handles which were duplicated, they weren't used */ CloseHandle(hIn); CloseHandle(hErr); /* close pipe handles, they won't be used */ CloseHandle(hChildOutRd); CloseHandle(hChildOutWr); /* set status for return */ pipedes[0] = pipedes[1] = -1; *pid_p = (pid_t)-1; } } #endif #ifdef __MSDOS__ FILE * msdos_openpipe (int* pipedes, int *pidp, char *text) { FILE *fpipe=0; /* MSDOS can't fork, but it has `popen'. */ struct variable *sh = lookup_variable ("SHELL", 5); int e; extern int dos_command_running, dos_status; /* Make sure not to bother processing an empty line. */ while (isblank ((unsigned char)*text)) ++text; if (*text == '\0') return 0; if (sh) { char buf[PATH_MAX + 7]; /* This makes sure $SHELL value is used by $(shell), even though the target environment is not passed to it. */ sprintf (buf, "SHELL=%s", sh->value); putenv (buf); } e = errno; errno = 0; dos_command_running = 1; dos_status = 0; /* If dos_status becomes non-zero, it means the child process was interrupted by a signal, like SIGINT or SIGQUIT. See fatal_error_signal in commands.c. */ fpipe = popen (text, "rt"); dos_command_running = 0; if (!fpipe || dos_status) { pipedes[0] = -1; *pidp = -1; if (dos_status) errno = EINTR; else if (errno == 0) errno = ENOMEM; shell_function_completed = -1; } else { pipedes[0] = fileno (fpipe); *pidp = 42; /* Yes, the Meaning of Life, the Universe, and Everything! */ errno = e; shell_function_completed = 1; } return fpipe; } #endif /* Do shell spawning, with the naughty bits for different OSes. */ #ifdef VMS /* VMS can't do $(shell ...) */ #define func_shell 0 #else #ifndef _AMIGA static char * func_shell (char *o, char **argv, const char *funcname UNUSED) { char *batch_filename = NULL; #ifdef __MSDOS__ FILE *fpipe; #endif char **command_argv; const char *error_prefix; char **envp; int pipedes[2]; pid_t pid; #ifndef __MSDOS__ /* Construct the argument list. */ command_argv = construct_command_argv (argv[0], NULL, NULL, 0, &batch_filename); if (command_argv == 0) return o; #endif /* Using a target environment for `shell' loses in cases like: export var = $(shell echo foobie) because target_environment hits a loop trying to expand $(var) to put it in the environment. This is even more confusing when var was not explicitly exported, but just appeared in the calling environment. See Savannah bug #10593. envp = target_environment (NILF); */ envp = environ; /* For error messages. */ if (reading_file && reading_file->filenm) { char *p = alloca (strlen (reading_file->filenm)+11+4); sprintf (p, "%s:%lu: ", reading_file->filenm, reading_file->lineno); error_prefix = p; } else error_prefix = ""; #if defined(__MSDOS__) fpipe = msdos_openpipe (pipedes, &pid, argv[0]); if (pipedes[0] < 0) { perror_with_name (error_prefix, "pipe"); return o; } #elif defined(WINDOWS32) windows32_openpipe (pipedes, &pid, command_argv, envp); if (pipedes[0] < 0) { /* open of the pipe failed, mark as failed execution */ shell_function_completed = -1; return o; } else #else if (pipe (pipedes) < 0) { perror_with_name (error_prefix, "pipe"); return o; } # ifdef __EMX__ /* close some handles that are unnecessary for the child process */ CLOSE_ON_EXEC(pipedes[1]); CLOSE_ON_EXEC(pipedes[0]); /* Never use fork()/exec() here! Use spawn() instead in exec_command() */ pid = child_execute_job (0, pipedes[1], command_argv, envp); if (pid < 0) perror_with_name (error_prefix, "spawn"); # else /* ! __EMX__ */ pid = vfork (); if (pid < 0) perror_with_name (error_prefix, "fork"); else if (pid == 0) child_execute_job (0, pipedes[1], command_argv, envp); else # endif #endif { /* We are the parent. */ char *buffer; unsigned int maxlen, i; int cc; /* Record the PID for reap_children. */ shell_function_pid = pid; #ifndef __MSDOS__ shell_function_completed = 0; /* Free the storage only the child needed. */ free (command_argv[0]); free (command_argv); /* Close the write side of the pipe. We test for -1, since pipedes[1] is -1 on MS-Windows, and some versions of MS libraries barf when `close' is called with -1. */ if (pipedes[1] >= 0) close (pipedes[1]); #endif /* Set up and read from the pipe. */ maxlen = 200; buffer = xmalloc (maxlen + 1); /* Read from the pipe until it gets EOF. */ for (i = 0; ; i += cc) { if (i == maxlen) { maxlen += 512; buffer = xrealloc (buffer, maxlen + 1); } EINTRLOOP (cc, read (pipedes[0], &buffer[i], maxlen - i)); if (cc <= 0) break; } buffer[i] = '\0'; /* Close the read side of the pipe. */ #ifdef __MSDOS__ if (fpipe) (void) pclose (fpipe); #else (void) close (pipedes[0]); #endif /* Loop until child_handler or reap_children() sets shell_function_completed to the status of our child shell. */ while (shell_function_completed == 0) reap_children (1, 0); if (batch_filename) { DB (DB_VERBOSE, (_("Cleaning up temporary batch file %s\n"), batch_filename)); remove (batch_filename); free (batch_filename); } shell_function_pid = 0; /* The child_handler function will set shell_function_completed to 1 when the child dies normally, or to -1 if it dies with status 127, which is most likely an exec fail. */ if (shell_function_completed == -1) { /* This likely means that the execvp failed, so we should just write the error message in the pipe from the child. */ fputs (buffer, stderr); fflush (stderr); } else { /* The child finished normally. Replace all newlines in its output with spaces, and put that in the variable output buffer. */ fold_newlines (buffer, &i); o = variable_buffer_output (o, buffer, i); } free (buffer); } return o; } #else /* _AMIGA */ /* Do the Amiga version of func_shell. */ static char * func_shell (char *o, char **argv, const char *funcname) { /* Amiga can't fork nor spawn, but I can start a program with redirection of my choice. However, this means that we don't have an opportunity to reopen stdout to trap it. Thus, we save our own stdout onto a new descriptor and dup a temp file's descriptor onto our stdout temporarily. After we spawn the shell program, we dup our own stdout back to the stdout descriptor. The buffer reading is the same as above, except that we're now reading from a file. */ #include <dos/dos.h> #include <proto/dos.h> BPTR child_stdout; char tmp_output[FILENAME_MAX]; unsigned int maxlen = 200, i; int cc; char * buffer, * ptr; char ** aptr; int len = 0; char* batch_filename = NULL; /* Construct the argument list. */ command_argv = construct_command_argv (argv[0], NULL, NULL, 0, &batch_filename); if (command_argv == 0) return o; /* Note the mktemp() is a security hole, but this only runs on Amiga. Ideally we would use main.c:open_tmpfile(), but this uses a special Open(), not fopen(), and I'm not familiar enough with the code to mess with it. */ strcpy (tmp_output, "t:MakeshXXXXXXXX"); mktemp (tmp_output); child_stdout = Open (tmp_output, MODE_NEWFILE); for (aptr=command_argv; *aptr; aptr++) len += strlen (*aptr) + 1; buffer = xmalloc (len + 1); ptr = buffer; for (aptr=command_argv; *aptr; aptr++) { strcpy (ptr, *aptr); ptr += strlen (ptr) + 1; *ptr ++ = ' '; *ptr = 0; } ptr[-1] = '\n'; Execute (buffer, NULL, child_stdout); free (buffer); Close (child_stdout); child_stdout = Open (tmp_output, MODE_OLDFILE); buffer = xmalloc (maxlen); i = 0; do { if (i == maxlen) { maxlen += 512; buffer = xrealloc (buffer, maxlen + 1); } cc = Read (child_stdout, &buffer[i], maxlen - i); if (cc > 0) i += cc; } while (cc > 0); Close (child_stdout); fold_newlines (buffer, &i); o = variable_buffer_output (o, buffer, i); free (buffer); return o; } #endif /* _AMIGA */ #endif /* !VMS */ #ifdef EXPERIMENTAL /* equality. Return is string-boolean, ie, the empty string is false. */ static char * func_eq (char *o, char **argv, char *funcname) { int result = ! strcmp (argv[0], argv[1]); o = variable_buffer_output (o, result ? "1" : "", result); return o; } /* string-boolean not operator. */ static char * func_not (char *o, char **argv, char *funcname) { const char *s = argv[0]; int result = 0; while (isspace ((unsigned char)*s)) s++; result = ! (*s); o = variable_buffer_output (o, result ? "1" : "", result); return o; } #endif #ifdef HAVE_DOS_PATHS #define IS_ABSOLUTE(n) (n[0] && n[1] == ':') #define ROOT_LEN 3 #else #define IS_ABSOLUTE(n) (n[0] == '/') #define ROOT_LEN 1 #endif /* Return the absolute name of file NAME which does not contain any `.', `..' components nor any repeated path separators ('/'). */ static char * abspath (const char *name, char *apath) { char *dest; const char *start, *end, *apath_limit; unsigned long root_len = ROOT_LEN; if (name[0] == '\0' || apath == NULL) return NULL; apath_limit = apath + GET_PATH_MAX; if (!IS_ABSOLUTE(name)) { /* It is unlikely we would make it until here but just to make sure. */ if (!starting_directory) return NULL; strcpy (apath, starting_directory); #ifdef HAVE_DOS_PATHS if (IS_PATHSEP(name[0])) { if (IS_PATHSEP(name[1])) { /* A UNC. Don't prepend a drive letter. */ apath[0] = name[0]; apath[1] = name[1]; root_len = 2; } /* We have /foo, an absolute file name except for the drive letter. Assume the missing drive letter is the current drive, which we can get if we remove from starting_directory everything past the root directory. */ apath[root_len] = '\0'; } #endif dest = strchr (apath, '\0'); } else { strncpy (apath, name, root_len); apath[root_len] = '\0'; dest = apath + root_len; /* Get past the root, since we already copied it. */ name += root_len; #ifdef HAVE_DOS_PATHS if (!IS_PATHSEP(apath[2])) { /* Convert d:foo into d:./foo and increase root_len. */ apath[2] = '.'; apath[3] = '/'; dest++; root_len++; /* strncpy above copied one character too many. */ name--; } else apath[2] = '/'; /* make sure it's a forward slash */ #endif } for (start = end = name; *start != '\0'; start = end) { unsigned long len; /* Skip sequence of multiple path-separators. */ while (IS_PATHSEP(*start)) ++start; /* Find end of path component. */ for (end = start; *end != '\0' && !IS_PATHSEP(*end); ++end) ; len = end - start; if (len == 0) break; else if (len == 1 && start[0] == '.') /* nothing */; else if (len == 2 && start[0] == '.' && start[1] == '.') { /* Back up to previous component, ignore if at root already. */ if (dest > apath + root_len) for (--dest; !IS_PATHSEP(dest[-1]); --dest); } else { if (!IS_PATHSEP(dest[-1])) *dest++ = '/'; if (dest + len >= apath_limit) return NULL; dest = memcpy (dest, start, len); dest += len; *dest = '\0'; } } /* Unless it is root strip trailing separator. */ if (dest > apath + root_len && IS_PATHSEP(dest[-1])) --dest; *dest = '\0'; return apath; } static char * func_realpath (char *o, char **argv, const char *funcname UNUSED) { /* Expand the argument. */ const char *p = argv[0]; const char *path = 0; int doneany = 0; unsigned int len = 0; #ifndef HAVE_REALPATH struct stat st; #endif PATH_VAR (in); PATH_VAR (out); while ((path = find_next_token (&p, &len)) != 0) { if (len < GET_PATH_MAX) { strncpy (in, path, len); in[len] = '\0'; if ( #ifdef HAVE_REALPATH realpath (in, out) #else abspath (in, out) && stat (out, &st) == 0 #endif ) { o = variable_buffer_output (o, out, strlen (out)); o = variable_buffer_output (o, " ", 1); doneany = 1; } } } /* Kill last space. */ if (doneany) --o; return o; } static char * func_abspath (char *o, char **argv, const char *funcname UNUSED) { /* Expand the argument. */ const char *p = argv[0]; const char *path = 0; int doneany = 0; unsigned int len = 0; PATH_VAR (in); PATH_VAR (out); while ((path = find_next_token (&p, &len)) != 0) { if (len < GET_PATH_MAX) { strncpy (in, path, len); in[len] = '\0'; if (abspath (in, out)) { o = variable_buffer_output (o, out, strlen (out)); o = variable_buffer_output (o, " ", 1); doneany = 1; } } } /* Kill last space. */ if (doneany) --o; return o; } /* Lookup table for builtin functions. This doesn't have to be sorted; we use a straight lookup. We might gain some efficiency by moving most often used functions to the start of the table. If MAXIMUM_ARGS is 0, that means there is no maximum and all comma-separated values are treated as arguments. EXPAND_ARGS means that all arguments should be expanded before invocation. Functions that do namespace tricks (foreach) don't automatically expand. */ static char *func_call (char *o, char **argv, const char *funcname); static struct function_table_entry function_table_init[] = { /* Name/size */ /* MIN MAX EXP? Function */ { STRING_SIZE_TUPLE("abspath"), 0, 1, 1, func_abspath}, { STRING_SIZE_TUPLE("addprefix"), 2, 2, 1, func_addsuffix_addprefix}, { STRING_SIZE_TUPLE("addsuffix"), 2, 2, 1, func_addsuffix_addprefix}, { STRING_SIZE_TUPLE("basename"), 0, 1, 1, func_basename_dir}, { STRING_SIZE_TUPLE("dir"), 0, 1, 1, func_basename_dir}, { STRING_SIZE_TUPLE("notdir"), 0, 1, 1, func_notdir_suffix}, { STRING_SIZE_TUPLE("subst"), 3, 3, 1, func_subst}, { STRING_SIZE_TUPLE("suffix"), 0, 1, 1, func_notdir_suffix}, { STRING_SIZE_TUPLE("filter"), 2, 2, 1, func_filter_filterout}, { STRING_SIZE_TUPLE("filter-out"), 2, 2, 1, func_filter_filterout}, { STRING_SIZE_TUPLE("findstring"), 2, 2, 1, func_findstring}, { STRING_SIZE_TUPLE("firstword"), 0, 1, 1, func_firstword}, { STRING_SIZE_TUPLE("flavor"), 0, 1, 1, func_flavor}, { STRING_SIZE_TUPLE("join"), 2, 2, 1, func_join}, { STRING_SIZE_TUPLE("lastword"), 0, 1, 1, func_lastword}, { STRING_SIZE_TUPLE("patsubst"), 3, 3, 1, func_patsubst}, { STRING_SIZE_TUPLE("realpath"), 0, 1, 1, func_realpath}, { STRING_SIZE_TUPLE("shell"), 0, 1, 1, func_shell}, { STRING_SIZE_TUPLE("sort"), 0, 1, 1, func_sort}, { STRING_SIZE_TUPLE("strip"), 0, 1, 1, func_strip}, { STRING_SIZE_TUPLE("wildcard"), 0, 1, 1, func_wildcard}, { STRING_SIZE_TUPLE("word"), 2, 2, 1, func_word}, { STRING_SIZE_TUPLE("wordlist"), 3, 3, 1, func_wordlist}, { STRING_SIZE_TUPLE("words"), 0, 1, 1, func_words}, { STRING_SIZE_TUPLE("origin"), 0, 1, 1, func_origin}, { STRING_SIZE_TUPLE("foreach"), 3, 3, 0, func_foreach}, { STRING_SIZE_TUPLE("call"), 1, 0, 1, func_call}, { STRING_SIZE_TUPLE("info"), 0, 1, 1, func_error}, { STRING_SIZE_TUPLE("error"), 0, 1, 1, func_error}, { STRING_SIZE_TUPLE("warning"), 0, 1, 1, func_error}, { STRING_SIZE_TUPLE("if"), 2, 3, 0, func_if}, { STRING_SIZE_TUPLE("or"), 1, 0, 0, func_or}, { STRING_SIZE_TUPLE("and"), 1, 0, 0, func_and}, { STRING_SIZE_TUPLE("value"), 0, 1, 1, func_value}, { STRING_SIZE_TUPLE("eval"), 0, 1, 1, func_eval}, #ifdef EXPERIMENTAL { STRING_SIZE_TUPLE("eq"), 2, 2, 1, func_eq}, { STRING_SIZE_TUPLE("not"), 0, 1, 1, func_not}, #endif }; #define FUNCTION_TABLE_ENTRIES (sizeof (function_table_init) / sizeof (struct function_table_entry)) /* These must come after the definition of function_table. */ static char * expand_builtin_function (char *o, int argc, char **argv, const struct function_table_entry *entry_p) { if (argc < (int)entry_p->minimum_args) fatal (*expanding_var, _("insufficient number of arguments (%d) to function `%s'"), argc, entry_p->name); /* I suppose technically some function could do something with no arguments, but so far none do, so just test it for all functions here rather than in each one. We can change it later if necessary. */ if (!argc) return o; if (!entry_p->func_ptr) fatal (*expanding_var, _("unimplemented on this platform: function `%s'"), entry_p->name); return entry_p->func_ptr (o, argv, entry_p->name); } /* Check for a function invocation in *STRINGP. *STRINGP points at the opening ( or { and is not null-terminated. If a function invocation is found, expand it into the buffer at *OP, updating *OP, incrementing *STRINGP past the reference and returning nonzero. If not, return zero. */ int handle_function (char **op, const char **stringp) { const struct function_table_entry *entry_p; char openparen = (*stringp)[0]; char closeparen = openparen == '(' ? ')' : '}'; const char *beg; const char *end; int count = 0; char *abeg = NULL; char **argv, **argvp; int nargs; beg = *stringp + 1; entry_p = lookup_function (beg); if (!entry_p) return 0; /* We found a builtin function. Find the beginning of its arguments (skip whitespace after the name). */ beg = next_token (beg + entry_p->len); /* Find the end of the function invocation, counting nested use of whichever kind of parens we use. Since we're looking, count commas to get a rough estimate of how many arguments we might have. The count might be high, but it'll never be low. */ for (nargs=1, end=beg; *end != '\0'; ++end) if (*end == ',') ++nargs; else if (*end == openparen) ++count; else if (*end == closeparen && --count < 0) break; if (count >= 0) fatal (*expanding_var, _("unterminated call to function `%s': missing `%c'"), entry_p->name, closeparen); *stringp = end; /* Get some memory to store the arg pointers. */ argvp = argv = alloca (sizeof (char *) * (nargs + 2)); /* Chop the string into arguments, then a nul. As soon as we hit MAXIMUM_ARGS (if it's >0) assume the rest of the string is part of the last argument. If we're expanding, store pointers to the expansion of each one. If not, make a duplicate of the string and point into that, nul-terminating each argument. */ if (entry_p->expand_args) { const char *p; for (p=beg, nargs=0; p <= end; ++argvp) { const char *next; ++nargs; if (nargs == entry_p->maximum_args || (! (next = find_next_argument (openparen, closeparen, p, end)))) next = end; *argvp = expand_argument (p, next); p = next + 1; } } else { int len = end - beg; char *p, *aend; abeg = xmalloc (len+1); memcpy (abeg, beg, len); abeg[len] = '\0'; aend = abeg + len; for (p=abeg, nargs=0; p <= aend; ++argvp) { char *next; ++nargs; if (nargs == entry_p->maximum_args || (! (next = find_next_argument (openparen, closeparen, p, aend)))) next = aend; *argvp = p; *next = '\0'; p = next + 1; } } *argvp = NULL; /* Finally! Run the function... */ *op = expand_builtin_function (*op, nargs, argv, entry_p); /* Free memory. */ if (entry_p->expand_args) for (argvp=argv; *argvp != 0; ++argvp) free (*argvp); if (abeg) free (abeg); return 1; } /* User-defined functions. Expand the first argument as either a builtin function or a make variable, in the context of the rest of the arguments assigned to $1, $2, ... $N. $0 is the name of the function. */ static char * func_call (char *o, char **argv, const char *funcname UNUSED) { static int max_args = 0; char *fname; char *cp; char *body; int flen; int i; int saved_args; const struct function_table_entry *entry_p; struct variable *v; /* There is no way to define a variable with a space in the name, so strip leading and trailing whitespace as a favor to the user. */ fname = argv[0]; while (*fname != '\0' && isspace ((unsigned char)*fname)) ++fname; cp = fname + strlen (fname) - 1; while (cp > fname && isspace ((unsigned char)*cp)) --cp; cp[1] = '\0'; /* Calling nothing is a no-op */ if (*fname == '\0') return o; /* Are we invoking a builtin function? */ entry_p = lookup_function (fname); if (entry_p) { /* How many arguments do we have? */ for (i=0; argv[i+1]; ++i) ; return expand_builtin_function (o, i, argv+1, entry_p); } /* Not a builtin, so the first argument is the name of a variable to be expanded and interpreted as a function. Find it. */ flen = strlen (fname); v = lookup_variable (fname, flen); if (v == 0) warn_undefined (fname, flen); if (v == 0 || *v->value == '\0') return o; body = alloca (flen + 4); body[0] = '$'; body[1] = '('; memcpy (body + 2, fname, flen); body[flen+2] = ')'; body[flen+3] = '\0'; /* Set up arguments $(1) .. $(N). $(0) is the function name. */ push_new_variable_scope (); for (i=0; *argv; ++i, ++argv) { char num[11]; sprintf (num, "%d", i); define_variable (num, strlen (num), *argv, o_automatic, 0); } /* If the number of arguments we have is < max_args, it means we're inside a recursive invocation of $(call ...). Fill in the remaining arguments in the new scope with the empty value, to hide them from this invocation. */ for (; i < max_args; ++i) { char num[11]; sprintf (num, "%d", i); define_variable (num, strlen (num), "", o_automatic, 0); } /* Expand the body in the context of the arguments, adding the result to the variable buffer. */ v->exp_count = EXP_COUNT_MAX; saved_args = max_args; max_args = i; o = variable_expand_string (o, body, flen+3); max_args = saved_args; v->exp_count = 0; pop_variable_scope (); return o + strlen (o); } void hash_init_function_table (void) { hash_init (&function_table, FUNCTION_TABLE_ENTRIES * 2, function_table_entry_hash_1, function_table_entry_hash_2, function_table_entry_hash_cmp); hash_load (&function_table, function_table_init, FUNCTION_TABLE_ENTRIES, sizeof (struct function_table_entry)); }
/* Implicit rule searching for GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" #include "filedef.h" #include "rule.h" #include "dep.h" #include "debug.h" #include "variable.h" #include "job.h" /* struct child, used inside commands.h */ #include "commands.h" /* set_file_variables */ static int pattern_search (struct file *file, int archive, unsigned int depth, unsigned int recursions); /* For a FILE which has no commands specified, try to figure out some from the implicit pattern rules. Returns 1 if a suitable implicit rule was found, after modifying FILE to contain the appropriate commands and deps, or returns 0 if no implicit rule was found. */ int try_implicit_rule (struct file *file, unsigned int depth) { DBF (DB_IMPLICIT, _("Looking for an implicit rule for `%s'.\n")); /* The order of these searches was previously reversed. My logic now is that since the non-archive search uses more information in the target (the archive search omits the archive name), it is more specific and should come first. */ if (pattern_search (file, 0, depth, 0)) return 1; #ifndef NO_ARCHIVES /* If this is an archive member reference, use just the archive member name to search for implicit rules. */ if (ar_name (file->name)) { DBF (DB_IMPLICIT, _("Looking for archive-member implicit rule for `%s'.\n")); if (pattern_search (file, 1, depth, 0)) return 1; } #endif return 0; } /* Scans the BUFFER for the next word with whitespace as a separator. Returns the pointer to the beginning of the word. LENGTH hold the length of the word. */ static const char * get_next_word (const char *buffer, unsigned int *length) { const char *p = buffer, *beg; char c; /* Skip any leading whitespace. */ while (isblank ((unsigned char)*p)) ++p; beg = p; c = *(p++); if (c == '\0') return 0; /* We already found the first value of "c", above. */ while (1) { char closeparen; int count; switch (c) { case '\0': case ' ': case '\t': goto done_word; case '$': c = *(p++); if (c == '$') break; /* This is a variable reference, so read it to the matching close paren. */ if (c == '(') closeparen = ')'; else if (c == '{') closeparen = '}'; else /* This is a single-letter variable reference. */ break; for (count = 0; *p != '\0'; ++p) { if (*p == c) ++count; else if (*p == closeparen && --count < 0) { ++p; break; } } break; case '|': goto done; default: break; } c = *(p++); } done_word: --p; done: if (length) *length = p - beg; return beg; } /* This structure stores information about the expanded prerequisites for a pattern rule. NAME is always set to the strcache'd name of the prereq. FILE and PATTERN will be set for intermediate files only. IGNORE_MTIME is copied from the prerequisite we expanded. */ struct patdeps { const char *name; const char *pattern; struct file *file; unsigned int ignore_mtime : 1; }; /* This structure stores information about pattern rules that we need to try. */ struct tryrule { struct rule *rule; /* Index of the target in this rule that matched the file. */ unsigned int matches; /* Stem length for this match. */ unsigned int stemlen; /* Definition order of this rule. Used to implement stable sort.*/ unsigned int order; /* Nonzero if the LASTSLASH logic was used in matching this rule. */ char checked_lastslash; }; int stemlen_compare (const void *v1, const void *v2) { const struct tryrule *r1 = v1; const struct tryrule *r2 = v2; int r = r1->stemlen - r2->stemlen; return r != 0 ? r : (int)(r1->order - r2->order); } /* Search the pattern rules for a rule with an existing dependency to make FILE. If a rule is found, the appropriate commands and deps are put in FILE and 1 is returned. If not, 0 is returned. If ARCHIVE is nonzero, FILE->name is of the form "LIB(MEMBER)". A rule for "(MEMBER)" will be searched for, and "(MEMBER)" will not be chopped up into directory and filename parts. If an intermediate file is found by pattern search, the intermediate file is set up as a target by the recursive call and is also made a dependency of FILE. DEPTH is used for debugging messages. */ static int pattern_search (struct file *file, int archive, unsigned int depth, unsigned int recursions) { /* Filename we are searching for a rule for. */ const char *filename = archive ? strchr (file->name, '(') : file->name; /* Length of FILENAME. */ unsigned int namelen = strlen (filename); /* The last slash in FILENAME (or nil if there is none). */ const char *lastslash; /* This is a file-object used as an argument in recursive calls. It never contains any data except during a recursive call. */ struct file *int_file = 0; /* List of dependencies found recursively. */ struct patdeps *deplist = xmalloc (max_pattern_deps * sizeof (struct patdeps)); struct patdeps *pat = deplist; /* All the prerequisites actually found for a rule, after expansion. */ struct dep *deps; /* Names of possible dependencies are constructed in this buffer. */ char *depname = alloca (namelen + max_pattern_dep_length); /* The start and length of the stem of FILENAME for the current rule. */ const char *stem = 0; unsigned int stemlen = 0; unsigned int fullstemlen = 0; /* Buffer in which we store all the rules that are possibly applicable. */ struct tryrule *tryrules = xmalloc (num_pattern_rules * max_pattern_targets * sizeof (struct tryrule)); /* Number of valid elements in TRYRULES. */ unsigned int nrules; /* The index in TRYRULES of the rule we found. */ unsigned int foundrule; /* Nonzero if should consider intermediate files as dependencies. */ int intermed_ok; /* Nonzero if we have initialized file variables for this target. */ int file_vars_initialized = 0; /* Nonzero if we have matched a pattern-rule target that is not just `%'. */ int specific_rule_matched = 0; struct dep dep_simple; unsigned int ri; /* uninit checks OK */ struct rule *rule; char *pathdir = NULL; unsigned long pathlen; PATH_VAR (stem_str); /* @@ Need to get rid of stem, stemlen, etc. */ #ifndef NO_ARCHIVES if (archive || ar_name (filename)) lastslash = 0; else #endif { /* Set LASTSLASH to point at the last slash in FILENAME but not counting any slash at the end. (foo/bar/ counts as bar/ in directory foo/, not empty in directory foo/bar/.) */ #ifdef VMS lastslash = strrchr (filename, ']'); if (lastslash == 0) lastslash = strrchr (filename, ':'); #else lastslash = strrchr (filename, '/'); #ifdef HAVE_DOS_PATHS /* Handle backslashes (possibly mixed with forward slashes) and the case of "d:file". */ { char *bslash = strrchr (filename, '\\'); if (lastslash == 0 || bslash > lastslash) lastslash = bslash; if (lastslash == 0 && filename[0] && filename[1] == ':') lastslash = filename + 1; } #endif #endif if (lastslash != 0 && lastslash[1] == '\0') lastslash = 0; } pathlen = lastslash - filename + 1; /* First see which pattern rules match this target and may be considered. Put them in TRYRULES. */ nrules = 0; for (rule = pattern_rules; rule != 0; rule = rule->next) { unsigned int ti; /* If the pattern rule has deps but no commands, ignore it. Users cancel built-in rules by redefining them without commands. */ if (rule->deps != 0 && rule->cmds == 0) continue; /* If this rule is in use by a parent pattern_search, don't use it here. */ if (rule->in_use) { DBS (DB_IMPLICIT, (_("Avoiding implicit rule recursion.\n"))); continue; } for (ti = 0; ti < rule->num; ++ti) { const char *target = rule->targets[ti]; const char *suffix = rule->suffixes[ti]; int check_lastslash; /* Rules that can match any filename and are not terminal are ignored if we're recursing, so that they cannot be intermediate files. */ if (recursions > 0 && target[1] == '\0' && !rule->terminal) continue; if (rule->lens[ti] > namelen) /* It can't possibly match. */ continue; /* From the lengths of the filename and the pattern parts, find the stem: the part of the filename that matches the %. */ stem = filename + (suffix - target - 1); stemlen = namelen - rule->lens[ti] + 1; /* Set CHECK_LASTSLASH if FILENAME contains a directory prefix and the target pattern does not contain a slash. */ check_lastslash = 0; if (lastslash) { #ifdef VMS check_lastslash = (strchr (target, ']') == 0 && strchr (target, ':') == 0); #else check_lastslash = strchr (target, '/') == 0; #ifdef HAVE_DOS_PATHS /* Didn't find it yet: check for DOS-type directories. */ if (check_lastslash) { char *b = strchr (target, '\\'); check_lastslash = !(b || (target[0] && target[1] == ':')); } #endif #endif } if (check_lastslash) { /* If so, don't include the directory prefix in STEM here. */ if (pathlen > stemlen) continue; stemlen -= pathlen; stem += pathlen; } /* Check that the rule pattern matches the text before the stem. */ if (check_lastslash) { if (stem > (lastslash + 1) && !strneq (target, lastslash + 1, stem - lastslash - 1)) continue; } else if (stem > filename && !strneq (target, filename, stem - filename)) continue; /* Check that the rule pattern matches the text after the stem. We could test simply use streq, but this way we compare the first two characters immediately. This saves time in the very common case where the first character matches because it is a period. */ if (*suffix != stem[stemlen] || (*suffix != '\0' && !streq (&suffix[1], &stem[stemlen + 1]))) continue; /* Record if we match a rule that not all filenames will match. */ if (target[1] != '\0') specific_rule_matched = 1; /* A rule with no dependencies and no commands exists solely to set specific_rule_matched when it matches. Don't try to use it. */ if (rule->deps == 0 && rule->cmds == 0) continue; /* Record this rule in TRYRULES and the index of the matching target in MATCHES. If several targets of the same rule match, that rule will be in TRYRULES more than once. */ tryrules[nrules].rule = rule; tryrules[nrules].matches = ti; tryrules[nrules].stemlen = stemlen + (check_lastslash ? pathlen : 0); tryrules[nrules].order = nrules; tryrules[nrules].checked_lastslash = check_lastslash; ++nrules; } } /* Bail out early if we haven't found any rules. */ if (nrules == 0) goto done; /* Sort the rules to place matches with the shortest stem first. This way the most specific rules will be tried first. */ if (nrules > 1) qsort (tryrules, nrules, sizeof (struct tryrule), stemlen_compare); /* If we have found a matching rule that won't match all filenames, retroactively reject any non-"terminal" rules that do always match. */ if (specific_rule_matched) for (ri = 0; ri < nrules; ++ri) if (!tryrules[ri].rule->terminal) { unsigned int j; for (j = 0; j < tryrules[ri].rule->num; ++j) if (tryrules[ri].rule->targets[j][1] == '\0') { tryrules[ri].rule = 0; break; } } /* Try each rule once without intermediate files, then once with them. */ for (intermed_ok = 0; intermed_ok < 2; ++intermed_ok) { pat = deplist; /* Try each pattern rule till we find one that applies. If it does, expand its dependencies (as substituted) and chain them in DEPS. */ for (ri = 0; ri < nrules; ri++) { struct dep *dep; int check_lastslash; unsigned int failed = 0; int file_variables_set = 0; unsigned int deps_found = 0; /* NPTR points to the part of the prereq we haven't processed. */ const char *nptr = 0; const char *dir = NULL; int order_only = 0; unsigned int matches; rule = tryrules[ri].rule; /* RULE is nil when we discover that a rule, already placed in TRYRULES, should not be applied. */ if (rule == 0) continue; /* Reject any terminal rules if we're looking to make intermediate files. */ if (intermed_ok && rule->terminal) continue; /* From the lengths of the filename and the matching pattern parts, find the stem: the part of the filename that matches the %. */ matches = tryrules[ri].matches; stem = filename + (rule->suffixes[matches] - rule->targets[matches]) - 1; stemlen = (namelen - rule->lens[matches]) + 1; check_lastslash = tryrules[ri].checked_lastslash; if (check_lastslash) { stem += pathlen; stemlen -= pathlen; /* We need to add the directory prefix, so set it up. */ if (! pathdir) { pathdir = alloca (pathlen + 1); memcpy (pathdir, filename, pathlen); pathdir[pathlen] = '\0'; } dir = pathdir; } DBS (DB_IMPLICIT, (_("Trying pattern rule with stem `%.*s'.\n"), (int) stemlen, stem)); strncpy (stem_str, stem, stemlen); stem_str[stemlen] = '\0'; /* If there are no prerequisites, then this rule matches. */ if (rule->deps == 0) break; /* Temporary assign STEM to file->stem (needed to set file variables below). */ file->stem = stem_str; /* Mark this rule as in use so a recursive pattern_search won't try to use it. */ rule->in_use = 1; /* Try each prerequisite; see if it exists or can be created. We'll build a list of prereq info in DEPLIST. Due to 2nd expansion we may have to process multiple prereqs for a single dep entry. */ pat = deplist; dep = rule->deps; nptr = dep_name (dep); while (1) { struct dep *dl, *d; char *p; /* If we're out of name to parse, start the next prereq. */ if (! nptr) { dep = dep->next; if (dep == 0) break; nptr = dep_name (dep); } /* If we don't need a second expansion, just replace the %. */ if (! dep->need_2nd_expansion) { dep_simple = *dep; dep_simple.next = 0; p = strchr (nptr, '%'); if (p == 0) dep_simple.name = nptr; else { char *o = depname; if (check_lastslash) { memcpy (o, filename, pathlen); o += pathlen; } memcpy (o, nptr, p - nptr); o += p - nptr; memcpy (o, stem_str, stemlen); o += stemlen; strcpy (o, p + 1); dep_simple.name = strcache_add (depname); } dl = &dep_simple; /* We've used up this dep, so next time get a new one. */ nptr = 0; ++deps_found; } /* We have to perform second expansion on this prereq. In an ideal world we would take the dependency line, substitute the stem, re-expand the whole line and chop it into individual prerequisites. Unfortunately this won't work because of the "check_lastslash" twist. Instead, we will have to go word by word, taking $()'s into account. For each word we will substitute the stem, re-expand, chop it up, and, if check_lastslash != 0, add the directory part to each resulting prerequisite. */ else { int add_dir = 0; unsigned int len; nptr = get_next_word (nptr, &len); if (nptr == 0) continue; /* See this is a transition to order-only prereqs. */ if (! order_only && len == 1 && nptr[0] == '|') { order_only = 1; nptr += len; continue; } /* If the dependency name has %, substitute the stem. If we just replace % with the stem value then later, when we do the 2nd expansion, we will re-expand this stem value again. This is not good if you have certain characters in your stem (like $). Instead, we will replace % with $* and allow the second expansion to take care of it for us. This way (since $* is a simple variable) there won't be additional re-expansion of the stem. */ p = lindex (nptr, nptr + len, '%'); if (p == 0) { memcpy (depname, nptr, len); depname[len] = '\0'; } else { unsigned int i = p - nptr; memcpy (depname, nptr, i); memcpy (depname + i, "$*", 2); memcpy (depname + i + 2, p + 1, len - i - 1); depname[len + 2 - 1] = '\0'; if (check_lastslash) add_dir = 1; } /* Initialize and set file variables if we haven't already done so. */ if (!file_vars_initialized) { initialize_file_variables (file, 0); set_file_variables (file); file_vars_initialized = 1; } /* Update the stem value in $* for this rule. */ else if (!file_variables_set) { define_variable_for_file ( "*", 1, file->stem, o_automatic, 0, file); file_variables_set = 1; } /* Perform the 2nd expansion. */ p = variable_expand_for_file (depname, file); /* Parse the expanded string. */ dl = PARSE_FILE_SEQ (&p, struct dep, order_only ? '\0' : '|', add_dir ? dir : NULL, 0); for (d = dl; d != NULL; d = d->next) { ++deps_found; if (order_only) d->ignore_mtime = 1; } /* Set up for the next word. */ nptr += len; } /* If there are more than max_pattern_deps prerequisites (due to 2nd expansion), reset it and realloc the arrays. */ if (deps_found > max_pattern_deps) { unsigned int l = pat - deplist; deplist = xrealloc (deplist, deps_found * sizeof (struct patdeps)); pat = deplist + l; max_pattern_deps = deps_found; } /* Go through the nameseq and handle each as a prereq name. */ for (d = dl; d != 0; d = d->next) { struct dep *expl_d; int is_rule = d->name == dep_name (dep); if (file_impossible_p (d->name)) { /* If this prereq has already been ruled "impossible", then the rule fails. Don't bother trying it on the second pass either since we know that will fail. */ DBS (DB_IMPLICIT, (is_rule ? _("Rejecting impossible rule prerequisite `%s'.\n") : _("Rejecting impossible implicit prerequisite `%s'.\n"), d->name)); tryrules[ri].rule = 0; failed = 1; break; } memset (pat, '\0', sizeof (struct patdeps)); pat->ignore_mtime = d->ignore_mtime; DBS (DB_IMPLICIT, (is_rule ? _("Trying rule prerequisite `%s'.\n") : _("Trying implicit prerequisite `%s'.\n"), d->name)); /* If this prereq is also explicitly mentioned for FILE, skip all tests below since it must be built no matter which implicit rule we choose. */ for (expl_d = file->deps; expl_d != 0; expl_d = expl_d->next) if (streq (dep_name (expl_d), d->name)) break; if (expl_d != 0) { (pat++)->name = d->name; continue; } /* The DEP->changed flag says that this dependency resides in a nonexistent directory. So we normally can skip looking for the file. However, if CHECK_LASTSLASH is set, then the dependency file we are actually looking for is in a different directory (the one gotten by prepending FILENAME's directory), so it might actually exist. */ /* @@ dep->changed check is disabled. */ if (lookup_file (d->name) != 0 /*|| ((!dep->changed || check_lastslash) && */ || file_exists_p (d->name)) { (pat++)->name = d->name; continue; } /* This code, given FILENAME = "lib/foo.o", dependency name "lib/foo.c", and VPATH=src, searches for "src/lib/foo.c". */ { const char *vname = vpath_search (d->name, 0, NULL, NULL); if (vname) { DBS (DB_IMPLICIT, (_("Found prerequisite `%s' as VPATH `%s'\n"), d->name, vname)); (pat++)->name = d->name; continue; } } /* We could not find the file in any place we should look. Try to make this dependency as an intermediate file, but only on the second pass. */ if (intermed_ok) { DBS (DB_IMPLICIT, (_("Looking for a rule with intermediate file `%s'.\n"), d->name)); if (int_file == 0) int_file = alloca (sizeof (struct file)); memset (int_file, '\0', sizeof (struct file)); int_file->name = d->name; if (pattern_search (int_file, 0, depth + 1, recursions + 1)) { pat->pattern = int_file->name; int_file->name = d->name; pat->file = int_file; (pat++)->name = d->name; int_file = 0; continue; } /* If we have tried to find P as an intermediate file and failed, mark that name as impossible so we won't go through the search again later. */ if (int_file->variables) free_variable_set (int_file->variables); if (int_file->pat_variables) free_variable_set (int_file->pat_variables); file_impossible (d->name); } /* A dependency of this rule does not exist. Therefore, this rule fails. */ failed = 1; break; } /* Free the ns chain. */ if (dl != &dep_simple) free_dep_chain (dl); if (failed) break; } /* Reset the stem in FILE. */ file->stem = 0; /* This rule is no longer `in use' for recursive searches. */ rule->in_use = 0; if (! failed) /* This pattern rule does apply. Stop looking for one. */ break; /* This pattern rule does not apply. If some of its dependencies succeeded, free the data structure describing them. */ /* free_idep_chain (deps); */ deps = 0; } /* If we found an applicable rule without intermediate files, don't try with them. */ if (ri < nrules) break; rule = 0; } /* RULE is nil if the loop went through the list but everything failed. */ if (rule == 0) goto done; foundrule = ri; /* If we are recursing, store the pattern that matched FILENAME in FILE->name for use in upper levels. */ if (recursions > 0) /* Kludge-o-matic */ file->name = rule->targets[tryrules[foundrule].matches]; /* DEPLIST lists the prerequisites for the rule we found. This includes the intermediate files, if any. Convert them into entries on the deps-chain of FILE. */ while (pat-- > deplist) { struct dep *dep; const char *s; if (pat->file != 0) { /* If we need to use an intermediate file, make sure it is entered as a target, with the info that was found for it in the recursive pattern_search call. We know that the intermediate file did not already exist as a target; therefore we can assume that the deps and cmds of F below are null before we change them. */ struct file *imf = pat->file; struct file *f = lookup_file (imf->name); /* We don't want to delete an intermediate file that happened to be a prerequisite of some (other) target. Mark it as precious. */ if (f != 0) f->precious = 1; else f = enter_file (imf->name); f->deps = imf->deps; f->cmds = imf->cmds; f->stem = imf->stem; f->variables = imf->variables; f->pat_variables = imf->pat_variables; f->pat_searched = imf->pat_searched; f->also_make = imf->also_make; f->is_target = 1; f->intermediate = 1; f->tried_implicit = 1; imf = lookup_file (pat->pattern); if (imf != 0 && imf->precious) f->precious = 1; for (dep = f->deps; dep != 0; dep = dep->next) { dep->file = enter_file (dep->name); dep->name = 0; dep->file->tried_implicit |= dep->changed; } } dep = alloc_dep (); dep->ignore_mtime = pat->ignore_mtime; s = strcache_add (pat->name); if (recursions) dep->name = s; else { dep->file = lookup_file (s); if (dep->file == 0) dep->file = enter_file (s); } if (pat->file == 0 && tryrules[foundrule].rule->terminal) { /* If the file actually existed (was not an intermediate file), and the rule that found it was a terminal one, then we want to mark the found file so that it will not have implicit rule search done for it. If we are not entering a `struct file' for it now, we indicate this with the `changed' flag. */ if (dep->file == 0) dep->changed = 1; else dep->file->tried_implicit = 1; } dep->next = file->deps; file->deps = dep; } if (!tryrules[foundrule].checked_lastslash) { /* Always allocate new storage, since STEM might be on the stack for an intermediate file. */ file->stem = strcache_add_len (stem, stemlen); fullstemlen = stemlen; } else { int dirlen = (lastslash + 1) - filename; char *sp; /* We want to prepend the directory from the original FILENAME onto the stem. */ fullstemlen = dirlen + stemlen; sp = alloca (fullstemlen + 1); memcpy (sp, filename, dirlen); memcpy (sp + dirlen, stem, stemlen); sp[fullstemlen] = '\0'; file->stem = strcache_add (sp); } file->cmds = rule->cmds; file->is_target = 1; /* Set precious flag. */ { struct file *f = lookup_file (rule->targets[tryrules[foundrule].matches]); if (f && f->precious) file->precious = 1; } /* If this rule builds other targets, too, put the others into FILE's `also_make' member. */ if (rule->num > 1) for (ri = 0; ri < rule->num; ++ri) if (ri != tryrules[foundrule].matches) { char *nm = alloca (rule->lens[ri] + fullstemlen + 1); char *p = nm; struct file *f; struct dep *new = alloc_dep (); /* GKM FIMXE: handle '|' here too */ memcpy (p, rule->targets[ri], rule->suffixes[ri] - rule->targets[ri] - 1); p += rule->suffixes[ri] - rule->targets[ri] - 1; memcpy (p, file->stem, fullstemlen); p += fullstemlen; memcpy (p, rule->suffixes[ri], rule->lens[ri] - (rule->suffixes[ri] - rule->targets[ri])+1); new->name = strcache_add (nm); new->file = enter_file (new->name); new->next = file->also_make; /* Set precious flag. */ f = lookup_file (rule->targets[ri]); if (f && f->precious) new->file->precious = 1; /* Set the is_target flag so that this file is not treated as intermediate by the pattern rule search algorithm and file_exists_p cannot pick it up yet. */ new->file->is_target = 1; file->also_make = new; } done: free (tryrules); free (deplist); return rule != 0; }
/* Job execution and handling for GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" #include <assert.h> #include "job.h" #include "debug.h" #include "filedef.h" #include "commands.h" #include "variable.h" #include "debug.h" #include <string.h> /* Default shell to use. */ #ifdef WINDOWS32 #include <windows.h> char *default_shell = "sh.exe"; int no_default_sh_exe = 1; int batch_mode_shell = 1; HANDLE main_thread; #elif defined (_AMIGA) char default_shell[] = ""; extern int MyExecute (char **); int batch_mode_shell = 0; #elif defined (__MSDOS__) /* The default shell is a pointer so we can change it if Makefile says so. It is without an explicit path so we get a chance to search the $PATH for it (since MSDOS doesn't have standard directories we could trust). */ char *default_shell = "command.com"; int batch_mode_shell = 0; #elif defined (__EMX__) char *default_shell = "/bin/sh"; int batch_mode_shell = 0; #elif defined (VMS) # include <descrip.h> char default_shell[] = ""; int batch_mode_shell = 0; #elif defined (__riscos__) char default_shell[] = ""; int batch_mode_shell = 0; #else char default_shell[] = "/bin/sh"; int batch_mode_shell = 0; #endif #ifdef __MSDOS__ # include <process.h> static int execute_by_shell; static int dos_pid = 123; int dos_status; int dos_command_running; #endif /* __MSDOS__ */ #ifdef _AMIGA # include <proto/dos.h> static int amiga_pid = 123; static int amiga_status; static char amiga_bname[32]; static int amiga_batch_file; #endif /* Amiga. */ #ifdef VMS # ifndef __GNUC__ # include <processes.h> # endif # include <starlet.h> # include <lib$routines.h> static void vmsWaitForChildren (int *); #endif #ifdef WINDOWS32 # include <windows.h> # include <io.h> # include <process.h> # include "sub_proc.h" # include "w32err.h" # include "pathstuff.h" #endif /* WINDOWS32 */ #ifdef __EMX__ # include <process.h> #endif #if defined (HAVE_SYS_WAIT_H) || defined (HAVE_UNION_WAIT) # include <sys/wait.h> #endif #ifdef HAVE_WAITPID # define WAIT_NOHANG(status) waitpid (-1, (status), WNOHANG) #else /* Don't have waitpid. */ # ifdef HAVE_WAIT3 # ifndef wait3 extern int wait3 (); # endif # define WAIT_NOHANG(status) wait3 ((status), WNOHANG, (struct rusage *) 0) # endif /* Have wait3. */ #endif /* Have waitpid. */ #if !defined (wait) && !defined (POSIX) int wait (); #endif #ifndef HAVE_UNION_WAIT # define WAIT_T int # ifndef WTERMSIG # define WTERMSIG(x) ((x) & 0x7f) # endif # ifndef WCOREDUMP # define WCOREDUMP(x) ((x) & 0x80) # endif # ifndef WEXITSTATUS # define WEXITSTATUS(x) (((x) >> 8) & 0xff) # endif # ifndef WIFSIGNALED # define WIFSIGNALED(x) (WTERMSIG (x) != 0) # endif # ifndef WIFEXITED # define WIFEXITED(x) (WTERMSIG (x) == 0) # endif #else /* Have `union wait'. */ # define WAIT_T union wait # ifndef WTERMSIG # define WTERMSIG(x) ((x).w_termsig) # endif # ifndef WCOREDUMP # define WCOREDUMP(x) ((x).w_coredump) # endif # ifndef WEXITSTATUS # define WEXITSTATUS(x) ((x).w_retcode) # endif # ifndef WIFSIGNALED # define WIFSIGNALED(x) (WTERMSIG(x) != 0) # endif # ifndef WIFEXITED # define WIFEXITED(x) (WTERMSIG(x) == 0) # endif #endif /* Don't have `union wait'. */ #if !defined(HAVE_UNISTD_H) && !defined(WINDOWS32) int dup2 (); int execve (); void _exit (); # ifndef VMS int geteuid (); int getegid (); int setgid (); int getgid (); # endif #endif /* Different systems have different requirements for pid_t. Plus we have to support gettext string translation... Argh. */ static const char * pid2str (pid_t pid) { static char pidstring[100]; #if defined(WINDOWS32) && (__GNUC__ > 3 || _MSC_VER > 1300) /* %Id is only needed for 64-builds, which were not supported by older versions of Windows compilers. */ sprintf (pidstring, "%Id", pid); #else sprintf (pidstring, "%lu", (unsigned long) pid); #endif return pidstring; } int getloadavg (double loadavg[], int nelem); int start_remote_job (char **argv, char **envp, int stdin_fd, int *is_remote, int *id_ptr, int *used_stdin); int start_remote_job_p (int); int remote_status (int *exit_code_ptr, int *signal_ptr, int *coredump_ptr, int block); RETSIGTYPE child_handler (int); static void free_child (struct child *); static void start_job_command (struct child *child); static int load_too_high (void); static int job_next_command (struct child *); static int start_waiting_job (struct child *); /* Chain of all live (or recently deceased) children. */ struct child *children = 0; /* Number of children currently running. */ unsigned int job_slots_used = 0; /* Nonzero if the `good' standard input is in use. */ static int good_stdin_used = 0; /* Chain of children waiting to run until the load average goes down. */ static struct child *waiting_jobs = 0; /* Non-zero if we use a *real* shell (always so on Unix). */ int unixy_shell = 1; /* Number of jobs started in the current second. */ unsigned long job_counter = 0; /* Number of jobserver tokens this instance is currently using. */ unsigned int jobserver_tokens = 0; #ifdef WINDOWS32 /* * The macro which references this function is defined in make.h. */ int w32_kill(pid_t pid, int sig) { return ((process_kill((HANDLE)pid, sig) == TRUE) ? 0 : -1); } /* This function creates a temporary file name with an extension specified * by the unixy arg. * Return an xmalloc'ed string of a newly created temp file and its * file descriptor, or die. */ static char * create_batch_file (char const *base, int unixy, int *fd) { const char *const ext = unixy ? "sh" : "bat"; const char *error_string = NULL; char temp_path[MAXPATHLEN]; /* need to know its length */ unsigned path_size = GetTempPath(sizeof temp_path, temp_path); int path_is_dot = 0; unsigned uniq = 1; const unsigned sizemax = strlen (base) + strlen (ext) + 10; if (path_size == 0) { path_size = GetCurrentDirectory (sizeof temp_path, temp_path); path_is_dot = 1; } while (path_size > 0 && path_size + sizemax < sizeof temp_path && uniq < 0x10000) { unsigned size = sprintf (temp_path + path_size, "%s%s-%x.%s", temp_path[path_size - 1] == '\\' ? "" : "\\", base, uniq, ext); HANDLE h = CreateFile (temp_path, /* file name */ GENERIC_READ | GENERIC_WRITE, /* desired access */ 0, /* no share mode */ NULL, /* default security attributes */ CREATE_NEW, /* creation disposition */ FILE_ATTRIBUTE_NORMAL | /* flags and attributes */ FILE_ATTRIBUTE_TEMPORARY, /* we'll delete it */ NULL); /* no template file */ if (h == INVALID_HANDLE_VALUE) { const DWORD er = GetLastError(); if (er == ERROR_FILE_EXISTS || er == ERROR_ALREADY_EXISTS) ++uniq; /* the temporary path is not guaranteed to exist */ else if (path_is_dot == 0) { path_size = GetCurrentDirectory (sizeof temp_path, temp_path); path_is_dot = 1; } else { error_string = map_windows32_error_to_string (er); break; } } else { const unsigned final_size = path_size + size + 1; char *const path = xmalloc (final_size); memcpy (path, temp_path, final_size); *fd = _open_osfhandle ((intptr_t)h, 0); if (unixy) { char *p; int ch; for (p = path; (ch = *p) != 0; ++p) if (ch == '\\') *p = '/'; } return path; /* good return */ } } *fd = -1; if (error_string == NULL) error_string = _("Cannot create a temporary file\n"); fatal (NILF, error_string); /* not reached */ return NULL; } #endif /* WINDOWS32 */ #ifdef __EMX__ /* returns whether path is assumed to be a unix like shell. */ int _is_unixy_shell (const char *path) { /* list of non unix shells */ const char *known_os2shells[] = { "cmd.exe", "cmd", "4os2.exe", "4os2", "4dos.exe", "4dos", "command.com", "command", NULL }; /* find the rightmost '/' or '\\' */ const char *name = strrchr (path, '/'); const char *p = strrchr (path, '\\'); unsigned i; if (name && p) /* take the max */ name = (name > p) ? name : p; else if (p) /* name must be 0 */ name = p; else if (!name) /* name and p must be 0 */ name = path; if (*name == '/' || *name == '\\') name++; i = 0; while (known_os2shells[i] != NULL) { if (strcasecmp (name, known_os2shells[i]) == 0) return 0; /* not a unix shell */ i++; } /* in doubt assume a unix like shell */ return 1; } #endif /* __EMX__ */ /* determines whether path looks to be a Bourne-like shell. */ int is_bourne_compatible_shell (const char *path) { /* list of known unix (Bourne-like) shells */ const char *unix_shells[] = { "sh", "bash", "ksh", "rksh", "zsh", "ash", "dash", NULL }; unsigned i, len; /* find the rightmost '/' or '\\' */ const char *name = strrchr (path, '/'); char *p = strrchr (path, '\\'); if (name && p) /* take the max */ name = (name > p) ? name : p; else if (p) /* name must be 0 */ name = p; else if (!name) /* name and p must be 0 */ name = path; if (*name == '/' || *name == '\\') name++; /* this should be able to deal with extensions on Windows-like systems */ for (i = 0; unix_shells[i] != NULL; i++) { len = strlen(unix_shells[i]); #if defined(WINDOWS32) || defined(__MSDOS__) if ((strncasecmp (name, unix_shells[i], len) == 0) && (strlen(name) >= len && (name[len] == '\0' || name[len] == '.'))) #else if ((strncmp (name, unix_shells[i], len) == 0) && (strlen(name) >= len && name[len] == '\0')) #endif return 1; /* a known unix-style shell */ } /* if not on the list, assume it's not a Bourne-like shell */ return 0; } /* Write an error message describing the exit status given in EXIT_CODE, EXIT_SIG, and COREDUMP, for the target TARGET_NAME. Append "(ignored)" if IGNORED is nonzero. */ static void child_error (const char *target_name, int exit_code, int exit_sig, int coredump, int ignored) { if (ignored && silent_flag) return; #ifdef VMS if (!(exit_code & 1)) error (NILF, (ignored ? _("*** [%s] Error 0x%x (ignored)") : _("*** [%s] Error 0x%x")), target_name, exit_code); #else if (exit_sig == 0) error (NILF, ignored ? _("[%s] Error %d (ignored)") : _("*** [%s] Error %d"), target_name, exit_code); else error (NILF, "*** [%s] %s%s", target_name, strsignal (exit_sig), coredump ? _(" (core dumped)") : ""); #endif /* VMS */ } /* Handle a dead child. This handler may or may not ever be installed. If we're using the jobserver feature, we need it. First, installing it ensures the read will interrupt on SIGCHLD. Second, we close the dup'd read FD to ensure we don't enter another blocking read without reaping all the dead children. In this case we don't need the dead_children count. If we don't have either waitpid or wait3, then make is unreliable, but we use the dead_children count to reap children as best we can. */ static unsigned int dead_children = 0; RETSIGTYPE child_handler (int sig UNUSED) { ++dead_children; if (job_rfd >= 0) { close (job_rfd); job_rfd = -1; } #ifdef __EMX__ /* The signal handler must called only once! */ signal (SIGCHLD, SIG_DFL); #endif /* This causes problems if the SIGCHLD interrupts a printf(). DB (DB_JOBS, (_("Got a SIGCHLD; %u unreaped children.\n"), dead_children)); */ } extern int shell_function_pid, shell_function_completed; /* Reap all dead children, storing the returned status and the new command state (`cs_finished') in the `file' member of the `struct child' for the dead child, and removing the child from the chain. In addition, if BLOCK nonzero, we block in this function until we've reaped at least one complete child, waiting for it to die if necessary. If ERR is nonzero, print an error message first. */ void reap_children (int block, int err) { #ifndef WINDOWS32 WAIT_T status; /* Initially, assume we have some. */ int reap_more = 1; #endif #ifdef WAIT_NOHANG # define REAP_MORE reap_more #else # define REAP_MORE dead_children #endif /* As long as: We have at least one child outstanding OR a shell function in progress, AND We're blocking for a complete child OR there are more children to reap we'll keep reaping children. */ while ((children != 0 || shell_function_pid != 0) && (block || REAP_MORE)) { int remote = 0; pid_t pid; int exit_code, exit_sig, coredump; register struct child *lastc, *c; int child_failed; int any_remote, any_local; int dontcare; if (err && block) { static int printed = 0; /* We might block for a while, so let the user know why. Only print this message once no matter how many jobs are left. */ fflush (stdout); if (!printed) error (NILF, _("*** Waiting for unfinished jobs....")); printed = 1; } /* We have one less dead child to reap. As noted in child_handler() above, this count is completely unimportant for all modern, POSIX-y systems that support wait3() or waitpid(). The rest of this comment below applies only to early, broken pre-POSIX systems. We keep the count only because... it's there... The test and decrement are not atomic; if it is compiled into: register = dead_children - 1; dead_children = register; a SIGCHLD could come between the two instructions. child_handler increments dead_children. The second instruction here would lose that increment. But the only effect of dead_children being wrong is that we might wait longer than necessary to reap a child, and lose some parallelism; and we might print the "Waiting for unfinished jobs" message above when not necessary. */ if (dead_children > 0) --dead_children; any_remote = 0; any_local = shell_function_pid != 0; for (c = children; c != 0; c = c->next) { any_remote |= c->remote; any_local |= ! c->remote; DB (DB_JOBS, (_("Live child %p (%s) PID %s %s\n"), c, c->file->name, pid2str (c->pid), c->remote ? _(" (remote)") : "")); #ifdef VMS break; #endif } /* First, check for remote children. */ if (any_remote) pid = remote_status (&exit_code, &exit_sig, &coredump, 0); else pid = 0; if (pid > 0) /* We got a remote child. */ remote = 1; else if (pid < 0) { /* A remote status command failed miserably. Punt. */ remote_status_lose: pfatal_with_name ("remote_status"); } else { /* No remote children. Check for local children. */ #if !defined(__MSDOS__) && !defined(_AMIGA) && !defined(WINDOWS32) if (any_local) { #ifdef VMS vmsWaitForChildren (&status); pid = c->pid; #else #ifdef WAIT_NOHANG if (!block) pid = WAIT_NOHANG (&status); else #endif EINTRLOOP(pid, wait (&status)); #endif /* !VMS */ } else pid = 0; if (pid < 0) { /* The wait*() failed miserably. Punt. */ pfatal_with_name ("wait"); } else if (pid > 0) { /* We got a child exit; chop the status word up. */ exit_code = WEXITSTATUS (status); exit_sig = WIFSIGNALED (status) ? WTERMSIG (status) : 0; coredump = WCOREDUMP (status); /* If we have started jobs in this second, remove one. */ if (job_counter) --job_counter; } else { /* No local children are dead. */ reap_more = 0; if (!block || !any_remote) break; /* Now try a blocking wait for a remote child. */ pid = remote_status (&exit_code, &exit_sig, &coredump, 1); if (pid < 0) goto remote_status_lose; else if (pid == 0) /* No remote children either. Finally give up. */ break; /* We got a remote child. */ remote = 1; } #endif /* !__MSDOS__, !Amiga, !WINDOWS32. */ #ifdef __MSDOS__ /* Life is very different on MSDOS. */ pid = dos_pid - 1; status = dos_status; exit_code = WEXITSTATUS (status); if (exit_code == 0xff) exit_code = -1; exit_sig = WIFSIGNALED (status) ? WTERMSIG (status) : 0; coredump = 0; #endif /* __MSDOS__ */ #ifdef _AMIGA /* Same on Amiga */ pid = amiga_pid - 1; status = amiga_status; exit_code = amiga_status; exit_sig = 0; coredump = 0; #endif /* _AMIGA */ #ifdef WINDOWS32 { HANDLE hPID; int werr; HANDLE hcTID, hcPID; exit_code = 0; exit_sig = 0; coredump = 0; /* Record the thread ID of the main process, so that we could suspend it in the signal handler. */ if (!main_thread) { hcTID = GetCurrentThread (); hcPID = GetCurrentProcess (); if (!DuplicateHandle (hcPID, hcTID, hcPID, &main_thread, 0, FALSE, DUPLICATE_SAME_ACCESS)) { DWORD e = GetLastError (); fprintf (stderr, "Determine main thread ID (Error %ld: %s)\n", e, map_windows32_error_to_string(e)); } else DB (DB_VERBOSE, ("Main thread handle = %p\n", main_thread)); } /* wait for anything to finish */ hPID = process_wait_for_any(); if (hPID) { /* was an error found on this process? */ werr = process_last_err(hPID); /* get exit data */ exit_code = process_exit_code(hPID); if (werr) fprintf(stderr, "make (e=%d): %s", exit_code, map_windows32_error_to_string(exit_code)); /* signal */ exit_sig = process_signal(hPID); /* cleanup process */ process_cleanup(hPID); coredump = 0; } pid = (pid_t) hPID; } #endif /* WINDOWS32 */ } /* Check if this is the child of the `shell' function. */ if (!remote && pid == shell_function_pid) { /* It is. Leave an indicator for the `shell' function. */ if (exit_sig == 0 && exit_code == 127) shell_function_completed = -1; else shell_function_completed = 1; break; } child_failed = exit_sig != 0 || exit_code != 0; /* Search for a child matching the deceased one. */ lastc = 0; for (c = children; c != 0; lastc = c, c = c->next) if (c->remote == remote && c->pid == pid) break; if (c == 0) /* An unknown child died. Ignore it; it was inherited from our invoker. */ continue; DB (DB_JOBS, (child_failed ? _("Reaping losing child %p PID %s %s\n") : _("Reaping winning child %p PID %s %s\n"), c, pid2str (c->pid), c->remote ? _(" (remote)") : "")); if (c->sh_batch_file) { DB (DB_JOBS, (_("Cleaning up temp batch file %s\n"), c->sh_batch_file)); /* just try and remove, don't care if this fails */ remove (c->sh_batch_file); /* all done with memory */ free (c->sh_batch_file); c->sh_batch_file = NULL; } /* If this child had the good stdin, say it is now free. */ if (c->good_stdin) good_stdin_used = 0; dontcare = c->dontcare; if (child_failed && !c->noerror && !ignore_errors_flag) { /* The commands failed. Write an error message, delete non-precious targets, and abort. */ static int delete_on_error = -1; if (!dontcare) child_error (c->file->name, exit_code, exit_sig, coredump, 0); c->file->update_status = 2; if (delete_on_error == -1) { struct file *f = lookup_file (".DELETE_ON_ERROR"); delete_on_error = f != 0 && f->is_target; } if (exit_sig != 0 || delete_on_error) delete_child_targets (c); } else { if (child_failed) { /* The commands failed, but we don't care. */ child_error (c->file->name, exit_code, exit_sig, coredump, 1); child_failed = 0; } /* If there are more commands to run, try to start them. */ if (job_next_command (c)) { if (handling_fatal_signal) { /* Never start new commands while we are dying. Since there are more commands that wanted to be run, the target was not completely remade. So we treat this as if a command had failed. */ c->file->update_status = 2; } else { /* Check again whether to start remotely. Whether or not we want to changes over time. Also, start_remote_job may need state set up by start_remote_job_p. */ c->remote = start_remote_job_p (0); start_job_command (c); /* Fatal signals are left blocked in case we were about to put that child on the chain. But it is already there, so it is safe for a fatal signal to arrive now; it will clean up this child's targets. */ unblock_sigs (); if (c->file->command_state == cs_running) /* We successfully started the new command. Loop to reap more children. */ continue; } if (c->file->update_status != 0) /* We failed to start the commands. */ delete_child_targets (c); } else /* There are no more commands. We got through them all without an unignored error. Now the target has been successfully updated. */ c->file->update_status = 0; } /* When we get here, all the commands for C->file are finished (or aborted) and C->file->update_status contains 0 or 2. But C->file->command_state is still cs_running if all the commands ran; notice_finish_file looks for cs_running to tell it that it's interesting to check the file's modtime again now. */ if (! handling_fatal_signal) /* Notice if the target of the commands has been changed. This also propagates its values for command_state and update_status to its also_make files. */ notice_finished_file (c->file); DB (DB_JOBS, (_("Removing child %p PID %s%s from chain.\n"), c, pid2str (c->pid), c->remote ? _(" (remote)") : "")); /* Block fatal signals while frobnicating the list, so that children and job_slots_used are always consistent. Otherwise a fatal signal arriving after the child is off the chain and before job_slots_used is decremented would believe a child was live and call reap_children again. */ block_sigs (); /* There is now another slot open. */ if (job_slots_used > 0) --job_slots_used; /* Remove the child from the chain and free it. */ if (lastc == 0) children = c->next; else lastc->next = c->next; free_child (c); unblock_sigs (); /* If the job failed, and the -k flag was not given, die, unless we are already in the process of dying. */ if (!err && child_failed && !dontcare && !keep_going_flag && /* fatal_error_signal will die with the right signal. */ !handling_fatal_signal) die (2); /* Only block for one child. */ block = 0; } return; } /* Free the storage allocated for CHILD. */ static void free_child (struct child *child) { if (!jobserver_tokens) fatal (NILF, "INTERNAL: Freeing child %p (%s) but no tokens left!\n", child, child->file->name); /* If we're using the jobserver and this child is not the only outstanding job, put a token back into the pipe for it. */ if (job_fds[1] >= 0 && jobserver_tokens > 1) { char token = '+'; int r; /* Write a job token back to the pipe. */ EINTRLOOP (r, write (job_fds[1], &token, 1)); if (r != 1) pfatal_with_name (_("write jobserver")); DB (DB_JOBS, (_("Released token for child %p (%s).\n"), child, child->file->name)); } --jobserver_tokens; if (handling_fatal_signal) /* Don't bother free'ing if about to die. */ return; if (child->command_lines != 0) { register unsigned int i; for (i = 0; i < child->file->cmds->ncommand_lines; ++i) free (child->command_lines[i]); free (child->command_lines); } if (child->environment != 0) { register char **ep = child->environment; while (*ep != 0) free (*ep++); free (child->environment); } free (child); } #ifdef POSIX extern sigset_t fatal_signal_set; #endif void block_sigs (void) { #ifdef POSIX (void) sigprocmask (SIG_BLOCK, &fatal_signal_set, (sigset_t *) 0); #else # ifdef HAVE_SIGSETMASK (void) sigblock (fatal_signal_mask); # endif #endif } #ifdef POSIX void unblock_sigs (void) { sigset_t empty; sigemptyset (&empty); sigprocmask (SIG_SETMASK, &empty, (sigset_t *) 0); } #endif #ifdef MAKE_JOBSERVER RETSIGTYPE job_noop (int sig UNUSED) { } /* Set the child handler action flags to FLAGS. */ static void set_child_handler_action_flags (int set_handler, int set_alarm) { struct sigaction sa; #ifdef __EMX__ /* The child handler must be turned off here. */ signal (SIGCHLD, SIG_DFL); #endif memset (&sa, '\0', sizeof sa); sa.sa_handler = child_handler; sa.sa_flags = set_handler ? 0 : SA_RESTART; #if defined SIGCHLD sigaction (SIGCHLD, &sa, NULL); #endif #if defined SIGCLD && SIGCLD != SIGCHLD sigaction (SIGCLD, &sa, NULL); #endif #if defined SIGALRM if (set_alarm) { /* If we're about to enter the read(), set an alarm to wake up in a second so we can check if the load has dropped and we can start more work. On the way out, turn off the alarm and set SIG_DFL. */ alarm (set_handler ? 1 : 0); sa.sa_handler = set_handler ? job_noop : SIG_DFL; sa.sa_flags = 0; sigaction (SIGALRM, &sa, NULL); } #endif } #endif /* Start a job to run the commands specified in CHILD. CHILD is updated to reflect the commands and ID of the child process. NOTE: On return fatal signals are blocked! The caller is responsible for calling `unblock_sigs', once the new child is safely on the chain so it can be cleaned up in the event of a fatal signal. */ static void start_job_command (struct child *child) { #if !defined(_AMIGA) && !defined(WINDOWS32) static int bad_stdin = -1; #endif char *p; /* Must be volatile to silence bogus GCC warning about longjmp/vfork. */ volatile int flags; #ifdef VMS char *argv; #else char **argv; #endif /* If we have a completely empty commandset, stop now. */ if (!child->command_ptr) goto next_command; /* Combine the flags parsed for the line itself with the flags specified globally for this target. */ flags = (child->file->command_flags | child->file->cmds->lines_flags[child->command_line - 1]); p = child->command_ptr; child->noerror = ((flags & COMMANDS_NOERROR) != 0); while (*p != '\0') { if (*p == '@') flags |= COMMANDS_SILENT; else if (*p == '+') flags |= COMMANDS_RECURSE; else if (*p == '-') child->noerror = 1; else if (!isblank ((unsigned char)*p)) break; ++p; } /* Update the file's command flags with any new ones we found. We only keep the COMMANDS_RECURSE setting. Even this isn't 100% correct; we are now marking more commands recursive than should be in the case of multiline define/endef scripts where only one line is marked "+". In order to really fix this, we'll have to keep a lines_flags for every actual line, after expansion. */ child->file->cmds->lines_flags[child->command_line - 1] |= flags & COMMANDS_RECURSE; /* Figure out an argument list from this command line. */ { char *end = 0; #ifdef VMS argv = p; #else argv = construct_command_argv (p, &end, child->file, child->file->cmds->lines_flags[child->command_line - 1], &child->sh_batch_file); #endif if (end == NULL) child->command_ptr = NULL; else { *end++ = '\0'; child->command_ptr = end; } } /* If -q was given, say that updating `failed' if there was any text on the command line, or `succeeded' otherwise. The exit status of 1 tells the user that -q is saying `something to do'; the exit status for a random error is 2. */ if (argv != 0 && question_flag && !(flags & COMMANDS_RECURSE)) { #ifndef VMS free (argv[0]); free (argv); #endif child->file->update_status = 1; notice_finished_file (child->file); return; } if (touch_flag && !(flags & COMMANDS_RECURSE)) { /* Go on to the next command. It might be the recursive one. We construct ARGV only to find the end of the command line. */ #ifndef VMS if (argv) { free (argv[0]); free (argv); } #endif argv = 0; } if (argv == 0) { next_command: #ifdef __MSDOS__ execute_by_shell = 0; /* in case construct_command_argv sets it */ #endif /* This line has no commands. Go to the next. */ if (job_next_command (child)) start_job_command (child); else { /* No more commands. Make sure we're "running"; we might not be if (e.g.) all commands were skipped due to -n. */ set_command_state (child->file, cs_running); child->file->update_status = 0; notice_finished_file (child->file); } return; } /* Print out the command. If silent, we call `message' with null so it can log the working directory before the command's own error messages appear. */ message (0, (just_print_flag || (!(flags & COMMANDS_SILENT) && !silent_flag)) ? "%s" : (char *) 0, p); /* Tell update_goal_chain that a command has been started on behalf of this target. It is important that this happens here and not in reap_children (where we used to do it), because reap_children might be reaping children from a different target. We want this increment to guaranteedly indicate that a command was started for the dependency chain (i.e., update_file recursion chain) we are processing. */ ++commands_started; /* Optimize an empty command. People use this for timestamp rules, so avoid forking a useless shell. Do this after we increment commands_started so make still treats this special case as if it performed some action (makes a difference as to what messages are printed, etc. */ #if !defined(VMS) && !defined(_AMIGA) if ( #if defined __MSDOS__ || defined (__EMX__) unixy_shell /* the test is complicated and we already did it */ #else (argv[0] && is_bourne_compatible_shell(argv[0])) #endif && (argv[1] && argv[1][0] == '-' && ((argv[1][1] == 'c' && argv[1][2] == '\0') || (argv[1][1] == 'e' && argv[1][2] == 'c' && argv[1][3] == '\0'))) && (argv[2] && argv[2][0] == ':' && argv[2][1] == '\0') && argv[3] == NULL) { free (argv[0]); free (argv); goto next_command; } #endif /* !VMS && !_AMIGA */ /* If -n was given, recurse to get the next line in the sequence. */ if (just_print_flag && !(flags & COMMANDS_RECURSE)) { #ifndef VMS free (argv[0]); free (argv); #endif goto next_command; } /* Flush the output streams so they won't have things written twice. */ fflush (stdout); fflush (stderr); #ifndef VMS #if !defined(WINDOWS32) && !defined(_AMIGA) && !defined(__MSDOS__) /* Set up a bad standard input that reads from a broken pipe. */ if (bad_stdin == -1) { /* Make a file descriptor that is the read end of a broken pipe. This will be used for some children's standard inputs. */ int pd[2]; if (pipe (pd) == 0) { /* Close the write side. */ (void) close (pd[1]); /* Save the read side. */ bad_stdin = pd[0]; /* Set the descriptor to close on exec, so it does not litter any child's descriptor table. When it is dup2'd onto descriptor 0, that descriptor will not close on exec. */ CLOSE_ON_EXEC (bad_stdin); } } #endif /* !WINDOWS32 && !_AMIGA && !__MSDOS__ */ /* Decide whether to give this child the `good' standard input (one that points to the terminal or whatever), or the `bad' one that points to the read side of a broken pipe. */ child->good_stdin = !good_stdin_used; if (child->good_stdin) good_stdin_used = 1; #endif /* !VMS */ child->deleted = 0; #ifndef _AMIGA /* Set up the environment for the child. */ if (child->environment == 0) child->environment = target_environment (child->file); #endif #if !defined(__MSDOS__) && !defined(_AMIGA) && !defined(WINDOWS32) #ifndef VMS /* start_waiting_job has set CHILD->remote if we can start a remote job. */ if (child->remote) { int is_remote, id, used_stdin; if (start_remote_job (argv, child->environment, child->good_stdin ? 0 : bad_stdin, &is_remote, &id, &used_stdin)) /* Don't give up; remote execution may fail for various reasons. If so, simply run the job locally. */ goto run_local; else { if (child->good_stdin && !used_stdin) { child->good_stdin = 0; good_stdin_used = 0; } child->remote = is_remote; child->pid = id; } } else #endif /* !VMS */ { /* Fork the child process. */ char **parent_environ; run_local: block_sigs (); child->remote = 0; #ifdef VMS if (!child_execute_job (argv, child)) { /* Fork failed! */ perror_with_name ("vfork", ""); goto error; } #else parent_environ = environ; # ifdef __EMX__ /* If we aren't running a recursive command and we have a jobserver pipe, close it before exec'ing. */ if (!(flags & COMMANDS_RECURSE) && job_fds[0] >= 0) { CLOSE_ON_EXEC (job_fds[0]); CLOSE_ON_EXEC (job_fds[1]); } if (job_rfd >= 0) CLOSE_ON_EXEC (job_rfd); /* Never use fork()/exec() here! Use spawn() instead in exec_command() */ child->pid = child_execute_job (child->good_stdin ? 0 : bad_stdin, 1, argv, child->environment); if (child->pid < 0) { /* spawn failed! */ unblock_sigs (); perror_with_name ("spawn", ""); goto error; } /* undo CLOSE_ON_EXEC() after the child process has been started */ if (!(flags & COMMANDS_RECURSE) && job_fds[0] >= 0) { fcntl (job_fds[0], F_SETFD, 0); fcntl (job_fds[1], F_SETFD, 0); } if (job_rfd >= 0) fcntl (job_rfd, F_SETFD, 0); #else /* !__EMX__ */ child->pid = vfork (); environ = parent_environ; /* Restore value child may have clobbered. */ if (child->pid == 0) { /* We are the child side. */ unblock_sigs (); /* If we aren't running a recursive command and we have a jobserver pipe, close it before exec'ing. */ if (!(flags & COMMANDS_RECURSE) && job_fds[0] >= 0) { close (job_fds[0]); close (job_fds[1]); } if (job_rfd >= 0) close (job_rfd); #ifdef SET_STACK_SIZE /* Reset limits, if necessary. */ if (stack_limit.rlim_cur) setrlimit (RLIMIT_STACK, &stack_limit); #endif child_execute_job (child->good_stdin ? 0 : bad_stdin, 1, argv, child->environment); } else if (child->pid < 0) { /* Fork failed! */ unblock_sigs (); perror_with_name ("vfork", ""); goto error; } # endif /* !__EMX__ */ #endif /* !VMS */ } #else /* __MSDOS__ or Amiga or WINDOWS32 */ #ifdef __MSDOS__ { int proc_return; block_sigs (); dos_status = 0; /* We call `system' to do the job of the SHELL, since stock DOS shell is too dumb. Our `system' knows how to handle long command lines even if pipes/redirection is needed; it will only call COMMAND.COM when its internal commands are used. */ if (execute_by_shell) { char *cmdline = argv[0]; /* We don't have a way to pass environment to `system', so we need to save and restore ours, sigh... */ char **parent_environ = environ; environ = child->environment; /* If we have a *real* shell, tell `system' to call it to do everything for us. */ if (unixy_shell) { /* A *real* shell on MSDOS may not support long command lines the DJGPP way, so we must use `system'. */ cmdline = argv[2]; /* get past "shell -c" */ } dos_command_running = 1; proc_return = system (cmdline); environ = parent_environ; execute_by_shell = 0; /* for the next time */ } else { dos_command_running = 1; proc_return = spawnvpe (P_WAIT, argv[0], argv, child->environment); } /* Need to unblock signals before turning off dos_command_running, so that child's signals will be treated as such (see fatal_error_signal). */ unblock_sigs (); dos_command_running = 0; /* If the child got a signal, dos_status has its high 8 bits set, so be careful not to alter them. */ if (proc_return == -1) dos_status |= 0xff; else dos_status |= (proc_return & 0xff); ++dead_children; child->pid = dos_pid++; } #endif /* __MSDOS__ */ #ifdef _AMIGA amiga_status = MyExecute (argv); ++dead_children; child->pid = amiga_pid++; if (amiga_batch_file) { amiga_batch_file = 0; DeleteFile (amiga_bname); /* Ignore errors. */ } #endif /* Amiga */ #ifdef WINDOWS32 { HANDLE hPID; char* arg0; /* make UNC paths safe for CreateProcess -- backslash format */ arg0 = argv[0]; if (arg0 && arg0[0] == '/' && arg0[1] == '/') for ( ; arg0 && *arg0; arg0++) if (*arg0 == '/') *arg0 = '\\'; /* make sure CreateProcess() has Path it needs */ sync_Path_environment(); hPID = process_easy(argv, child->environment); if (hPID != INVALID_HANDLE_VALUE) child->pid = (pid_t) hPID; else { int i; unblock_sigs(); fprintf(stderr, _("process_easy() failed to launch process (e=%ld)\n"), process_last_err(hPID)); for (i = 0; argv[i]; i++) fprintf(stderr, "%s ", argv[i]); fprintf(stderr, _("\nCounted %d args in failed launch\n"), i); goto error; } } #endif /* WINDOWS32 */ #endif /* __MSDOS__ or Amiga or WINDOWS32 */ /* Bump the number of jobs started in this second. */ ++job_counter; /* We are the parent side. Set the state to say the commands are running and return. */ set_command_state (child->file, cs_running); /* Free the storage used by the child's argument list. */ #ifndef VMS free (argv[0]); free (argv); #endif return; error: child->file->update_status = 2; notice_finished_file (child->file); return; } /* Try to start a child running. Returns nonzero if the child was started (and maybe finished), or zero if the load was too high and the child was put on the `waiting_jobs' chain. */ static int start_waiting_job (struct child *c) { struct file *f = c->file; /* If we can start a job remotely, we always want to, and don't care about the local load average. We record that the job should be started remotely in C->remote for start_job_command to test. */ c->remote = start_remote_job_p (1); /* If we are running at least one job already and the load average is too high, make this one wait. */ if (!c->remote && ((job_slots_used > 0 && load_too_high ()) #ifdef WINDOWS32 || (process_used_slots () >= MAXIMUM_WAIT_OBJECTS) #endif )) { /* Put this child on the chain of children waiting for the load average to go down. */ set_command_state (f, cs_running); c->next = waiting_jobs; waiting_jobs = c; return 0; } /* Start the first command; reap_children will run later command lines. */ start_job_command (c); switch (f->command_state) { case cs_running: c->next = children; DB (DB_JOBS, (_("Putting child %p (%s) PID %s%s on the chain.\n"), c, c->file->name, pid2str (c->pid), c->remote ? _(" (remote)") : "")); children = c; /* One more job slot is in use. */ ++job_slots_used; unblock_sigs (); break; case cs_not_started: /* All the command lines turned out to be empty. */ f->update_status = 0; /* FALLTHROUGH */ case cs_finished: notice_finished_file (f); free_child (c); break; default: assert (f->command_state == cs_finished); break; } return 1; } /* Create a `struct child' for FILE and start its commands running. */ void new_job (struct file *file) { struct commands *cmds = file->cmds; struct child *c; char **lines; unsigned int i; /* Let any previously decided-upon jobs that are waiting for the load to go down start before this new one. */ start_waiting_jobs (); /* Reap any children that might have finished recently. */ reap_children (0, 0); /* Chop the commands up into lines if they aren't already. */ chop_commands (cmds); /* Expand the command lines and store the results in LINES. */ lines = xmalloc (cmds->ncommand_lines * sizeof (char *)); for (i = 0; i < cmds->ncommand_lines; ++i) { /* Collapse backslash-newline combinations that are inside variable or function references. These are left alone by the parser so that they will appear in the echoing of commands (where they look nice); and collapsed by construct_command_argv when it tokenizes. But letting them survive inside function invocations loses because we don't want the functions to see them as part of the text. */ char *in, *out, *ref; /* IN points to where in the line we are scanning. OUT points to where in the line we are writing. When we collapse a backslash-newline combination, IN gets ahead of OUT. */ in = out = cmds->command_lines[i]; while ((ref = strchr (in, '$')) != 0) { ++ref; /* Move past the $. */ if (out != in) /* Copy the text between the end of the last chunk we processed (where IN points) and the new chunk we are about to process (where REF points). */ memmove (out, in, ref - in); /* Move both pointers past the boring stuff. */ out += ref - in; in = ref; if (*ref == '(' || *ref == '{') { char openparen = *ref; char closeparen = openparen == '(' ? ')' : '}'; int count; char *p; *out++ = *in++; /* Copy OPENPAREN. */ /* IN now points past the opening paren or brace. Count parens or braces until it is matched. */ count = 0; while (*in != '\0') { if (*in == closeparen && --count < 0) break; else if (*in == '\\' && in[1] == '\n') { /* We have found a backslash-newline inside a variable or function reference. Eat it and any following whitespace. */ int quoted = 0; for (p = in - 1; p > ref && *p == '\\'; --p) quoted = !quoted; if (quoted) /* There were two or more backslashes, so this is not really a continuation line. We don't collapse the quoting backslashes here as is done in collapse_continuations, because the line will be collapsed again after expansion. */ *out++ = *in++; else { /* Skip the backslash, newline and any following whitespace. */ in = next_token (in + 2); /* Discard any preceding whitespace that has already been written to the output. */ while (out > ref && isblank ((unsigned char)out[-1])) --out; /* Replace it all with a single space. */ *out++ = ' '; } } else { if (*in == openparen) ++count; *out++ = *in++; } } } } /* There are no more references in this line to worry about. Copy the remaining uninteresting text to the output. */ if (out != in) memmove (out, in, strlen (in) + 1); /* Finally, expand the line. */ lines[i] = allocated_variable_expand_for_file (cmds->command_lines[i], file); } /* Start the command sequence, record it in a new `struct child', and add that to the chain. */ c = xcalloc (sizeof (struct child)); c->file = file; c->command_lines = lines; c->sh_batch_file = NULL; /* Cache dontcare flag because file->dontcare can be changed once we return. Check dontcare inheritance mechanism for details. */ c->dontcare = file->dontcare; /* Fetch the first command line to be run. */ job_next_command (c); /* Wait for a job slot to be freed up. If we allow an infinite number don't bother; also job_slots will == 0 if we're using the jobserver. */ if (job_slots != 0) while (job_slots_used == job_slots) reap_children (1, 0); #ifdef MAKE_JOBSERVER /* If we are controlling multiple jobs make sure we have a token before starting the child. */ /* This can be inefficient. There's a decent chance that this job won't actually have to run any subprocesses: the command script may be empty or otherwise optimized away. It would be nice if we could defer obtaining a token until just before we need it, in start_job_command. To do that we'd need to keep track of whether we'd already obtained a token (since start_job_command is called for each line of the job, not just once). Also more thought needs to go into the entire algorithm; this is where the old parallel job code waits, so... */ else if (job_fds[0] >= 0) while (1) { char token; int got_token; int saved_errno; DB (DB_JOBS, ("Need a job token; we %shave children\n", children ? "" : "don't ")); /* If we don't already have a job started, use our "free" token. */ if (!jobserver_tokens) break; /* Read a token. As long as there's no token available we'll block. We enable interruptible system calls before the read(2) so that if we get a SIGCHLD while we're waiting, we'll return with EINTR and we can process the death(s) and return tokens to the free pool. Once we return from the read, we immediately reinstate restartable system calls. This allows us to not worry about checking for EINTR on all the other system calls in the program. There is one other twist: there is a span between the time reap_children() does its last check for dead children and the time the read(2) call is entered, below, where if a child dies we won't notice. This is extremely serious as it could cause us to deadlock, given the right set of events. To avoid this, we do the following: before we reap_children(), we dup(2) the read FD on the jobserver pipe. The read(2) call below uses that new FD. In the signal handler, we close that FD. That way, if a child dies during the section mentioned above, the read(2) will be invoked with an invalid FD and will return immediately with EBADF. */ /* Make sure we have a dup'd FD. */ if (job_rfd < 0) { DB (DB_JOBS, ("Duplicate the job FD\n")); job_rfd = dup (job_fds[0]); } /* Reap anything that's currently waiting. */ reap_children (0, 0); /* Kick off any jobs we have waiting for an opportunity that can run now (ie waiting for load). */ start_waiting_jobs (); /* If our "free" slot has become available, use it; we don't need an actual token. */ if (!jobserver_tokens) break; /* There must be at least one child already, or we have no business waiting for a token. */ if (!children) fatal (NILF, "INTERNAL: no children as we go to sleep on read\n"); /* Set interruptible system calls, and read() for a job token. */ set_child_handler_action_flags (1, waiting_jobs != NULL); got_token = read (job_rfd, &token, 1); saved_errno = errno; set_child_handler_action_flags (0, waiting_jobs != NULL); /* If we got one, we're done here. */ if (got_token == 1) { DB (DB_JOBS, (_("Obtained token for child %p (%s).\n"), c, c->file->name)); break; } /* If the error _wasn't_ expected (EINTR or EBADF), punt. Otherwise, go back and reap_children(), and try again. */ errno = saved_errno; if (errno != EINTR && errno != EBADF) pfatal_with_name (_("read jobs pipe")); if (errno == EBADF) DB (DB_JOBS, ("Read returned EBADF.\n")); } #endif ++jobserver_tokens; /* The job is now primed. Start it running. (This will notice if there is in fact no recipe.) */ if (cmds->fileinfo.filenm) DB (DB_BASIC, (_("Invoking recipe from %s:%lu to update target `%s'.\n"), cmds->fileinfo.filenm, cmds->fileinfo.lineno, c->file->name)); else DB (DB_BASIC, (_("Invoking builtin recipe to update target `%s'.\n"), c->file->name)); start_waiting_job (c); if (job_slots == 1 || not_parallel) /* Since there is only one job slot, make things run linearly. Wait for the child to die, setting the state to `cs_finished'. */ while (file->command_state == cs_running) reap_children (1, 0); return; } /* Move CHILD's pointers to the next command for it to execute. Returns nonzero if there is another command. */ static int job_next_command (struct child *child) { while (child->command_ptr == 0 || *child->command_ptr == '\0') { /* There are no more lines in the expansion of this line. */ if (child->command_line == child->file->cmds->ncommand_lines) { /* There are no more lines to be expanded. */ child->command_ptr = 0; return 0; } else /* Get the next line to run. */ child->command_ptr = child->command_lines[child->command_line++]; } return 1; } /* Determine if the load average on the system is too high to start a new job. The real system load average is only recomputed once a second. However, a very parallel make can easily start tens or even hundreds of jobs in a second, which brings the system to its knees for a while until that first batch of jobs clears out. To avoid this we use a weighted algorithm to try to account for jobs which have been started since the last second, and guess what the load average would be now if it were computed. This algorithm was provided by Thomas Riedl <thomas.riedl@siemens.com>, who writes: ! calculate something load-oid and add to the observed sys.load, ! so that latter can catch up: ! - every job started increases jobctr; ! - every dying job decreases a positive jobctr; ! - the jobctr value gets zeroed every change of seconds, ! after its value*weight_b is stored into the 'backlog' value last_sec ! - weight_a times the sum of jobctr and last_sec gets ! added to the observed sys.load. ! ! The two weights have been tried out on 24 and 48 proc. Sun Solaris-9 ! machines, using a several-thousand-jobs-mix of cpp, cc, cxx and smallish ! sub-shelled commands (rm, echo, sed...) for tests. ! lowering the 'direct influence' factor weight_a (e.g. to 0.1) ! resulted in significant excession of the load limit, raising it ! (e.g. to 0.5) took bad to small, fast-executing jobs and didn't ! reach the limit in most test cases. ! ! lowering the 'history influence' weight_b (e.g. to 0.1) resulted in ! exceeding the limit for longer-running stuff (compile jobs in ! the .5 to 1.5 sec. range),raising it (e.g. to 0.5) overrepresented ! small jobs' effects. */ #define LOAD_WEIGHT_A 0.25 #define LOAD_WEIGHT_B 0.25 static int load_too_high (void) { #if defined(__MSDOS__) || defined(VMS) || defined(_AMIGA) || defined(__riscos__) return 1; #else static double last_sec; static time_t last_now; double load, guess; time_t now; #ifdef WINDOWS32 /* sub_proc.c cannot wait for more than MAXIMUM_WAIT_OBJECTS children */ if (process_used_slots () >= MAXIMUM_WAIT_OBJECTS) return 1; #endif if (max_load_average < 0) return 0; /* Find the real system load average. */ make_access (); if (getloadavg (&load, 1) != 1) { static int lossage = -1; /* Complain only once for the same error. */ if (lossage == -1 || errno != lossage) { if (errno == 0) /* An errno value of zero means getloadavg is just unsupported. */ error (NILF, _("cannot enforce load limits on this operating system")); else perror_with_name (_("cannot enforce load limit: "), "getloadavg"); } lossage = errno; load = 0; } user_access (); /* If we're in a new second zero the counter and correct the backlog value. Only keep the backlog for one extra second; after that it's 0. */ now = time (NULL); if (last_now < now) { if (last_now == now - 1) last_sec = LOAD_WEIGHT_B * job_counter; else last_sec = 0.0; job_counter = 0; last_now = now; } /* Try to guess what the load would be right now. */ guess = load + (LOAD_WEIGHT_A * (job_counter + last_sec)); DB (DB_JOBS, ("Estimated system load = %f (actual = %f) (max requested = %f)\n", guess, load, max_load_average)); return guess >= max_load_average; #endif } /* Start jobs that are waiting for the load to be lower. */ void start_waiting_jobs (void) { struct child *job; if (waiting_jobs == 0) return; do { /* Check for recently deceased descendants. */ reap_children (0, 0); /* Take a job off the waiting list. */ job = waiting_jobs; waiting_jobs = job->next; /* Try to start that job. We break out of the loop as soon as start_waiting_job puts one back on the waiting list. */ } while (start_waiting_job (job) && waiting_jobs != 0); return; } #ifndef WINDOWS32 /* EMX: Start a child process. This function returns the new pid. */ # if defined __EMX__ int child_execute_job (int stdin_fd, int stdout_fd, char **argv, char **envp) { int pid; /* stdin_fd == 0 means: nothing to do for stdin; stdout_fd == 1 means: nothing to do for stdout */ int save_stdin = (stdin_fd != 0) ? dup (0) : 0; int save_stdout = (stdout_fd != 1) ? dup (1): 1; /* < 0 only if dup() failed */ if (save_stdin < 0) fatal (NILF, _("no more file handles: could not duplicate stdin\n")); if (save_stdout < 0) fatal (NILF, _("no more file handles: could not duplicate stdout\n")); /* Close unnecessary file handles for the child. */ if (save_stdin != 0) CLOSE_ON_EXEC (save_stdin); if (save_stdout != 1) CLOSE_ON_EXEC (save_stdout); /* Connect the pipes to the child process. */ if (stdin_fd != 0) (void) dup2 (stdin_fd, 0); if (stdout_fd != 1) (void) dup2 (stdout_fd, 1); /* stdin_fd and stdout_fd must be closed on exit because we are still in the parent process */ if (stdin_fd != 0) CLOSE_ON_EXEC (stdin_fd); if (stdout_fd != 1) CLOSE_ON_EXEC (stdout_fd); /* Run the command. */ pid = exec_command (argv, envp); /* Restore stdout/stdin of the parent and close temporary FDs. */ if (stdin_fd != 0) { if (dup2 (save_stdin, 0) != 0) fatal (NILF, _("Could not restore stdin\n")); else close (save_stdin); } if (stdout_fd != 1) { if (dup2 (save_stdout, 1) != 1) fatal (NILF, _("Could not restore stdout\n")); else close (save_stdout); } return pid; } #elif !defined (_AMIGA) && !defined (__MSDOS__) && !defined (VMS) /* UNIX: Replace the current process with one executing the command in ARGV. STDIN_FD and STDOUT_FD are used as the process's stdin and stdout; ENVP is the environment of the new program. This function does not return. */ void child_execute_job (int stdin_fd, int stdout_fd, char **argv, char **envp) { if (stdin_fd != 0) (void) dup2 (stdin_fd, 0); if (stdout_fd != 1) (void) dup2 (stdout_fd, 1); if (stdin_fd != 0) (void) close (stdin_fd); if (stdout_fd != 1) (void) close (stdout_fd); /* Run the command. */ exec_command (argv, envp); } #endif /* !AMIGA && !__MSDOS__ && !VMS */ #endif /* !WINDOWS32 */ #ifndef _AMIGA /* Replace the current process with one running the command in ARGV, with environment ENVP. This function does not return. */ /* EMX: This function returns the pid of the child process. */ # ifdef __EMX__ int # else void # endif exec_command (char **argv, char **envp) { #ifdef VMS /* to work around a problem with signals and execve: ignore them */ #ifdef SIGCHLD signal (SIGCHLD,SIG_IGN); #endif /* Run the program. */ execve (argv[0], argv, envp); perror_with_name ("execve: ", argv[0]); _exit (EXIT_FAILURE); #else #ifdef WINDOWS32 HANDLE hPID; HANDLE hWaitPID; int err = 0; int exit_code = EXIT_FAILURE; /* make sure CreateProcess() has Path it needs */ sync_Path_environment(); /* launch command */ hPID = process_easy(argv, envp); /* make sure launch ok */ if (hPID == INVALID_HANDLE_VALUE) { int i; fprintf(stderr, _("process_easy() failed to launch process (e=%ld)\n"), process_last_err(hPID)); for (i = 0; argv[i]; i++) fprintf(stderr, "%s ", argv[i]); fprintf(stderr, _("\nCounted %d args in failed launch\n"), i); exit(EXIT_FAILURE); } /* wait and reap last child */ hWaitPID = process_wait_for_any(); while (hWaitPID) { /* was an error found on this process? */ err = process_last_err(hWaitPID); /* get exit data */ exit_code = process_exit_code(hWaitPID); if (err) fprintf(stderr, "make (e=%d, rc=%d): %s", err, exit_code, map_windows32_error_to_string(err)); /* cleanup process */ process_cleanup(hWaitPID); /* expect to find only last pid, warn about other pids reaped */ if (hWaitPID == hPID) break; else { char *pidstr = xstrdup (pid2str ((pid_t)hWaitPID)); fprintf(stderr, _("make reaped child pid %s, still waiting for pid %s\n"), pidstr, pid2str ((pid_t)hPID)); free (pidstr); } } /* return child's exit code as our exit code */ exit(exit_code); #else /* !WINDOWS32 */ # ifdef __EMX__ int pid; # endif /* Be the user, permanently. */ child_access (); # ifdef __EMX__ /* Run the program. */ pid = spawnvpe (P_NOWAIT, argv[0], argv, envp); if (pid >= 0) return pid; /* the file might have a strange shell extension */ if (errno == ENOENT) errno = ENOEXEC; # else /* Run the program. */ environ = envp; execvp (argv[0], argv); # endif /* !__EMX__ */ switch (errno) { case ENOENT: error (NILF, _("%s: Command not found"), argv[0]); break; case ENOEXEC: { /* The file is not executable. Try it as a shell script. */ extern char *getenv (); char *shell; char **new_argv; int argc; int i=1; # ifdef __EMX__ /* Do not use $SHELL from the environment */ struct variable *p = lookup_variable ("SHELL", 5); if (p) shell = p->value; else shell = 0; # else shell = getenv ("SHELL"); # endif if (shell == 0) shell = default_shell; argc = 1; while (argv[argc] != 0) ++argc; # ifdef __EMX__ if (!unixy_shell) ++argc; # endif new_argv = alloca ((1 + argc + 1) * sizeof (char *)); new_argv[0] = shell; # ifdef __EMX__ if (!unixy_shell) { new_argv[1] = "/c"; ++i; --argc; } # endif new_argv[i] = argv[0]; while (argc > 0) { new_argv[i + argc] = argv[argc]; --argc; } # ifdef __EMX__ pid = spawnvpe (P_NOWAIT, shell, new_argv, envp); if (pid >= 0) break; # else execvp (shell, new_argv); # endif if (errno == ENOENT) error (NILF, _("%s: Shell program not found"), shell); else perror_with_name ("execvp: ", shell); break; } # ifdef __EMX__ case EINVAL: /* this nasty error was driving me nuts :-( */ error (NILF, _("spawnvpe: environment space might be exhausted")); /* FALLTHROUGH */ # endif default: perror_with_name ("execvp: ", argv[0]); break; } # ifdef __EMX__ return pid; # else _exit (127); # endif #endif /* !WINDOWS32 */ #endif /* !VMS */ } #else /* On Amiga */ void exec_command (char **argv) { MyExecute (argv); } void clean_tmp (void) { DeleteFile (amiga_bname); } #endif /* On Amiga */ #ifndef VMS /* Figure out the argument list necessary to run LINE as a command. Try to avoid using a shell. This routine handles only ' quoting, and " quoting when no backslash, $ or ` characters are seen in the quotes. Starting quotes may be escaped with a backslash. If any of the characters in sh_chars[] is seen, or any of the builtin commands listed in sh_cmds[] is the first word of a line, the shell is used. If RESTP is not NULL, *RESTP is set to point to the first newline in LINE. If *RESTP is NULL, newlines will be ignored. SHELL is the shell to use, or nil to use the default shell. IFS is the value of $IFS, or nil (meaning the default). FLAGS is the value of lines_flags for this command line. It is used in the WINDOWS32 port to check whether + or $(MAKE) were found in this command line, in which case the effect of just_print_flag is overridden. */ static char ** construct_command_argv_internal (char *line, char **restp, char *shell, char *shellflags, char *ifs, int flags, char **batch_filename_ptr) { #ifdef __MSDOS__ /* MSDOS supports both the stock DOS shell and ports of Unixy shells. We call `system' for anything that requires ``slow'' processing, because DOS shells are too dumb. When $SHELL points to a real (unix-style) shell, `system' just calls it to do everything. When $SHELL points to a DOS shell, `system' does most of the work internally, calling the shell only for its internal commands. However, it looks on the $PATH first, so you can e.g. have an external command named `mkdir'. Since we call `system', certain characters and commands below are actually not specific to COMMAND.COM, but to the DJGPP implementation of `system'. In particular: The shell wildcard characters are in DOS_CHARS because they will not be expanded if we call the child via `spawnXX'. The `;' is in DOS_CHARS, because our `system' knows how to run multiple commands on a single line. DOS_CHARS also include characters special to 4DOS/NDOS, so we won't have to tell one from another and have one more set of commands and special characters. */ static char sh_chars_dos[] = "*?[];|<>%^&()"; static char *sh_cmds_dos[] = { "break", "call", "cd", "chcp", "chdir", "cls", "copy", "ctty", "date", "del", "dir", "echo", "erase", "exit", "for", "goto", "if", "md", "mkdir", "path", "pause", "prompt", "rd", "rmdir", "rem", "ren", "rename", "set", "shift", "time", "type", "ver", "verify", "vol", ":", 0 }; static char sh_chars_sh[] = "#;\"*?[]&|<>(){}$`^"; static char *sh_cmds_sh[] = { "cd", "echo", "eval", "exec", "exit", "login", "logout", "set", "umask", "wait", "while", "for", "case", "if", ":", ".", "break", "continue", "export", "read", "readonly", "shift", "times", "trap", "switch", "unset", "ulimit", 0 }; char *sh_chars; char **sh_cmds; #elif defined (__EMX__) static char sh_chars_dos[] = "*?[];|<>%^&()"; static char *sh_cmds_dos[] = { "break", "call", "cd", "chcp", "chdir", "cls", "copy", "ctty", "date", "del", "dir", "echo", "erase", "exit", "for", "goto", "if", "md", "mkdir", "path", "pause", "prompt", "rd", "rmdir", "rem", "ren", "rename", "set", "shift", "time", "type", "ver", "verify", "vol", ":", 0 }; static char sh_chars_os2[] = "*?[];|<>%^()\"'&"; static char *sh_cmds_os2[] = { "call", "cd", "chcp", "chdir", "cls", "copy", "date", "del", "detach", "dir", "echo", "endlocal", "erase", "exit", "for", "goto", "if", "keys", "md", "mkdir", "move", "path", "pause", "prompt", "rd", "rem", "ren", "rename", "rmdir", "set", "setlocal", "shift", "start", "time", "type", "ver", "verify", "vol", ":", 0 }; static char sh_chars_sh[] = "#;\"*?[]&|<>(){}$`^~'"; static char *sh_cmds_sh[] = { "echo", "cd", "eval", "exec", "exit", "login", "logout", "set", "umask", "wait", "while", "for", "case", "if", ":", ".", "break", "continue", "export", "read", "readonly", "shift", "times", "trap", "switch", "unset", 0 }; char *sh_chars; char **sh_cmds; #elif defined (_AMIGA) static char sh_chars[] = "#;\"|<>()?*$`"; static char *sh_cmds[] = { "cd", "eval", "if", "delete", "echo", "copy", "rename", "set", "setenv", "date", "makedir", "skip", "else", "endif", "path", "prompt", "unset", "unsetenv", "version", 0 }; #elif defined (WINDOWS32) static char sh_chars_dos[] = "\"|&<>"; static char *sh_cmds_dos[] = { "assoc", "break", "call", "cd", "chcp", "chdir", "cls", "color", "copy", "ctty", "date", "del", "dir", "echo", "echo.", "endlocal", "erase", "exit", "for", "ftype", "goto", "if", "if", "md", "mkdir", "path", "pause", "prompt", "rd", "rem", "ren", "rename", "rmdir", "set", "setlocal", "shift", "time", "title", "type", "ver", "verify", "vol", ":", 0 }; static char sh_chars_sh[] = "#;\"*?[]&|<>(){}$`^"; static char *sh_cmds_sh[] = { "cd", "eval", "exec", "exit", "login", "logout", "set", "umask", "wait", "while", "for", "case", "if", ":", ".", "break", "continue", "export", "read", "readonly", "shift", "times", "trap", "switch", "test", #ifdef BATCH_MODE_ONLY_SHELL "echo", #endif 0 }; char* sh_chars; char** sh_cmds; #elif defined(__riscos__) static char sh_chars[] = ""; static char *sh_cmds[] = { 0 }; #else /* must be UNIX-ish */ static char sh_chars[] = "#;\"*?[]&|<>(){}$`^~!"; static char *sh_cmds[] = { ".", ":", "break", "case", "cd", "continue", "eval", "exec", "exit", "export", "for", "if", "login", "logout", "read", "readonly", "set", "shift", "switch", "test", "times", "trap", "ulimit", "umask", "unset", "wait", "while", 0 }; # ifdef HAVE_DOS_PATHS /* This is required if the MSYS/Cygwin ports (which do not define WINDOWS32) are compiled with HAVE_DOS_PATHS defined, which uses sh_chars_sh[] directly (see below). */ static char *sh_chars_sh = sh_chars; # endif /* HAVE_DOS_PATHS */ #endif int i; char *p; char *ap; char *end; int instring, word_has_equals, seen_nonequals, last_argument_was_empty; char **new_argv = 0; char *argstr = 0; #ifdef WINDOWS32 int slow_flag = 0; if (!unixy_shell) { sh_cmds = sh_cmds_dos; sh_chars = sh_chars_dos; } else { sh_cmds = sh_cmds_sh; sh_chars = sh_chars_sh; } #endif /* WINDOWS32 */ if (restp != NULL) *restp = NULL; /* Make sure not to bother processing an empty line. */ while (isblank ((unsigned char)*line)) ++line; if (*line == '\0') return 0; /* See if it is safe to parse commands internally. */ if (shell == 0) shell = default_shell; #ifdef WINDOWS32 else if (strcmp (shell, default_shell)) { char *s1 = _fullpath (NULL, shell, 0); char *s2 = _fullpath (NULL, default_shell, 0); slow_flag = strcmp ((s1 ? s1 : ""), (s2 ? s2 : "")); if (s1) free (s1); if (s2) free (s2); } if (slow_flag) goto slow; #else /* not WINDOWS32 */ #if defined (__MSDOS__) || defined (__EMX__) else if (strcasecmp (shell, default_shell)) { extern int _is_unixy_shell (const char *_path); DB (DB_BASIC, (_("$SHELL changed (was `%s', now `%s')\n"), default_shell, shell)); unixy_shell = _is_unixy_shell (shell); /* we must allocate a copy of shell: construct_command_argv() will free * shell after this function returns. */ default_shell = xstrdup (shell); } if (unixy_shell) { sh_chars = sh_chars_sh; sh_cmds = sh_cmds_sh; } else { sh_chars = sh_chars_dos; sh_cmds = sh_cmds_dos; # ifdef __EMX__ if (_osmode == OS2_MODE) { sh_chars = sh_chars_os2; sh_cmds = sh_cmds_os2; } # endif } #else /* !__MSDOS__ */ else if (strcmp (shell, default_shell)) goto slow; #endif /* !__MSDOS__ && !__EMX__ */ #endif /* not WINDOWS32 */ if (ifs != 0) for (ap = ifs; *ap != '\0'; ++ap) if (*ap != ' ' && *ap != '\t' && *ap != '\n') goto slow; if (shellflags != 0) if (shellflags[0] != '-' || ((shellflags[1] != 'c' || shellflags[2] != '\0') && (shellflags[1] != 'e' || shellflags[2] != 'c' || shellflags[3] != '\0'))) goto slow; i = strlen (line) + 1; /* More than 1 arg per character is impossible. */ new_argv = xmalloc (i * sizeof (char *)); /* All the args can fit in a buffer as big as LINE is. */ ap = new_argv[0] = argstr = xmalloc (i); end = ap + i; /* I is how many complete arguments have been found. */ i = 0; instring = word_has_equals = seen_nonequals = last_argument_was_empty = 0; for (p = line; *p != '\0'; ++p) { assert (ap <= end); if (instring) { /* Inside a string, just copy any char except a closing quote or a backslash-newline combination. */ if (*p == instring) { instring = 0; if (ap == new_argv[0] || *(ap-1) == '\0') last_argument_was_empty = 1; } else if (*p == '\\' && p[1] == '\n') { /* Backslash-newline is handled differently depending on what kind of string we're in: inside single-quoted strings you keep them; in double-quoted strings they disappear. For DOS/Windows/OS2, if we don't have a POSIX shell, we keep the pre-POSIX behavior of removing the backslash-newline. */ if (instring == '"' #if defined (__MSDOS__) || defined (__EMX__) || defined (WINDOWS32) || !unixy_shell #endif ) ++p; else { *(ap++) = *(p++); *(ap++) = *p; } } else if (*p == '\n' && restp != NULL) { /* End of the command line. */ *restp = p; goto end_of_line; } /* Backslash, $, and ` are special inside double quotes. If we see any of those, punt. But on MSDOS, if we use COMMAND.COM, double and single quotes have the same effect. */ else if (instring == '"' && strchr ("\\$`", *p) != 0 && unixy_shell) goto slow; else *ap++ = *p; } else if (strchr (sh_chars, *p) != 0) /* Not inside a string, but it's a special char. */ goto slow; else if (one_shell && *p == '\n') /* In .ONESHELL mode \n is a separator like ; or && */ goto slow; #ifdef __MSDOS__ else if (*p == '.' && p[1] == '.' && p[2] == '.' && p[3] != '.') /* `...' is a wildcard in DJGPP. */ goto slow; #endif else /* Not a special char. */ switch (*p) { case '=': /* Equals is a special character in leading words before the first word with no equals sign in it. This is not the case with sh -k, but we never get here when using nonstandard shell flags. */ if (! seen_nonequals && unixy_shell) goto slow; word_has_equals = 1; *ap++ = '='; break; case '\\': /* Backslash-newline has special case handling, ref POSIX. We're in the fastpath, so emulate what the shell would do. */ if (p[1] == '\n') { /* Throw out the backslash and newline. */ ++p; /* If there's nothing in this argument yet, skip any whitespace before the start of the next word. */ if (ap == new_argv[i]) p = next_token (p + 1) - 1; } else if (p[1] != '\0') { #ifdef HAVE_DOS_PATHS /* Only remove backslashes before characters special to Unixy shells. All other backslashes are copied verbatim, since they are probably DOS-style directory separators. This still leaves a small window for problems, but at least it should work for the vast majority of naive users. */ #ifdef __MSDOS__ /* A dot is only special as part of the "..." wildcard. */ if (strneq (p + 1, ".\\.\\.", 5)) { *ap++ = '.'; *ap++ = '.'; p += 4; } else #endif if (p[1] != '\\' && p[1] != '\'' && !isspace ((unsigned char)p[1]) && strchr (sh_chars_sh, p[1]) == 0) /* back up one notch, to copy the backslash */ --p; #endif /* HAVE_DOS_PATHS */ /* Copy and skip the following char. */ *ap++ = *++p; } break; case '\'': case '"': instring = *p; break; case '\n': if (restp != NULL) { /* End of the command line. */ *restp = p; goto end_of_line; } else /* Newlines are not special. */ *ap++ = '\n'; break; case ' ': case '\t': /* We have the end of an argument. Terminate the text of the argument. */ *ap++ = '\0'; new_argv[++i] = ap; last_argument_was_empty = 0; /* Update SEEN_NONEQUALS, which tells us if every word heretofore has contained an `='. */ seen_nonequals |= ! word_has_equals; if (word_has_equals && ! seen_nonequals) /* An `=' in a word before the first word without one is magical. */ goto slow; word_has_equals = 0; /* Prepare for the next word. */ /* If this argument is the command name, see if it is a built-in shell command. If so, have the shell handle it. */ if (i == 1) { register int j; for (j = 0; sh_cmds[j] != 0; ++j) { if (streq (sh_cmds[j], new_argv[0])) goto slow; # ifdef __EMX__ /* Non-Unix shells are case insensitive. */ if (!unixy_shell && strcasecmp (sh_cmds[j], new_argv[0]) == 0) goto slow; # endif } } /* Ignore multiple whitespace chars. */ p = next_token (p) - 1; break; default: *ap++ = *p; break; } } end_of_line: if (instring) /* Let the shell deal with an unterminated quote. */ goto slow; /* Terminate the last argument and the argument list. */ *ap = '\0'; if (new_argv[i][0] != '\0' || last_argument_was_empty) ++i; new_argv[i] = 0; if (i == 1) { register int j; for (j = 0; sh_cmds[j] != 0; ++j) if (streq (sh_cmds[j], new_argv[0])) goto slow; } if (new_argv[0] == 0) { /* Line was empty. */ free (argstr); free (new_argv); return 0; } return new_argv; slow:; /* We must use the shell. */ if (new_argv != 0) { /* Free the old argument list we were working on. */ free (argstr); free (new_argv); } #ifdef __MSDOS__ execute_by_shell = 1; /* actually, call `system' if shell isn't unixy */ #endif #ifdef _AMIGA { char *ptr; char *buffer; char *dptr; buffer = xmalloc (strlen (line)+1); ptr = line; for (dptr=buffer; *ptr; ) { if (*ptr == '\\' && ptr[1] == '\n') ptr += 2; else if (*ptr == '@') /* Kludge: multiline commands */ { ptr += 2; *dptr++ = '\n'; } else *dptr++ = *ptr++; } *dptr = 0; new_argv = xmalloc (2 * sizeof (char *)); new_argv[0] = buffer; new_argv[1] = 0; } #else /* Not Amiga */ #ifdef WINDOWS32 /* * Not eating this whitespace caused things like * * sh -c "\n" * * which gave the shell fits. I think we have to eat * whitespace here, but this code should be considered * suspicious if things start failing.... */ /* Make sure not to bother processing an empty line. */ while (isspace ((unsigned char)*line)) ++line; if (*line == '\0') return 0; #endif /* WINDOWS32 */ { /* SHELL may be a multi-word command. Construct a command line "$(SHELL) $(.SHELLFLAGS) LINE", with all special chars in LINE escaped. Then recurse, expanding this command line to get the final argument list. */ unsigned int shell_len = strlen (shell); unsigned int line_len = strlen (line); unsigned int sflags_len = strlen (shellflags); char *command_ptr = NULL; /* used for batch_mode_shell mode */ char *new_line; # ifdef __EMX__ /* is this necessary? */ if (!unixy_shell) shellflags[0] = '/'; /* "/c" */ # endif /* In .ONESHELL mode we are allowed to throw the entire current recipe string at a single shell and trust that the user has configured the shell and shell flags, and formatted the string, appropriately. */ if (one_shell) { /* If the shell is Bourne compatible, we must remove and ignore interior special chars [@+-] because they're meaningless to the shell itself. If, however, we're in .ONESHELL mode and have changed SHELL to something non-standard, we should leave those alone because they could be part of the script. In this case we must also leave in place any leading [@+-] for the same reason. */ /* Remove and ignore interior prefix chars [@+-] because they're meaningless given a single shell. */ #if defined __MSDOS__ || defined (__EMX__) if (unixy_shell) /* the test is complicated and we already did it */ #else if (is_bourne_compatible_shell(shell)) #endif { const char *f = line; char *t = line; /* Copy the recipe, removing and ignoring interior prefix chars [@+-]: they're meaningless in .ONESHELL mode. */ while (f[0] != '\0') { int esc = 0; /* This is the start of a new recipe line. Skip whitespace and prefix characters. */ while (isblank (*f) || *f == '-' || *f == '@' || *f == '+') ++f; /* Copy until we get to the next logical recipe line. */ while (*f != '\0') { *(t++) = *(f++); if (f[-1] == '\\') esc = !esc; else { /* On unescaped newline, we're done with this line. */ if (f[-1] == '\n' && ! esc) break; /* Something else: reset the escape sequence. */ esc = 0; } } } *t = '\0'; } new_argv = xmalloc (4 * sizeof (char *)); new_argv[0] = xstrdup(shell); new_argv[1] = xstrdup(shellflags); new_argv[2] = line; new_argv[3] = NULL; return new_argv; } new_line = alloca (shell_len + 1 + sflags_len + 1 + (line_len*2) + 1); ap = new_line; memcpy (ap, shell, shell_len); ap += shell_len; *(ap++) = ' '; memcpy (ap, shellflags, sflags_len); ap += sflags_len; *(ap++) = ' '; command_ptr = ap; for (p = line; *p != '\0'; ++p) { if (restp != NULL && *p == '\n') { *restp = p; break; } else if (*p == '\\' && p[1] == '\n') { /* POSIX says we keep the backslash-newline. If we don't have a POSIX shell on DOS/Windows/OS2, mimic the pre-POSIX behavior and remove the backslash/newline. */ #if defined (__MSDOS__) || defined (__EMX__) || defined (WINDOWS32) # define PRESERVE_BSNL unixy_shell #else # define PRESERVE_BSNL 1 #endif if (PRESERVE_BSNL) { *(ap++) = '\\'; /* Only non-batch execution needs another backslash, because it will be passed through a recursive invocation of this function. */ if (!batch_mode_shell) *(ap++) = '\\'; *(ap++) = '\n'; } ++p; continue; } /* DOS shells don't know about backslash-escaping. */ if (unixy_shell && !batch_mode_shell && (*p == '\\' || *p == '\'' || *p == '"' || isspace ((unsigned char)*p) || strchr (sh_chars, *p) != 0)) *ap++ = '\\'; #ifdef __MSDOS__ else if (unixy_shell && strneq (p, "...", 3)) { /* The case of `...' wildcard again. */ strcpy (ap, "\\.\\.\\"); ap += 5; p += 2; } #endif *ap++ = *p; } if (ap == new_line + shell_len + sflags_len + 2) /* Line was empty. */ return 0; *ap = '\0'; #ifdef WINDOWS32 /* Some shells do not work well when invoked as 'sh -c xxx' to run a command line (e.g. Cygnus GNUWIN32 sh.exe on WIN32 systems). In these cases, run commands via a script file. */ if (just_print_flag && !(flags & COMMANDS_RECURSE)) { /* Need to allocate new_argv, although it's unused, because start_job_command will want to free it and its 0'th element. */ new_argv = xmalloc(2 * sizeof (char *)); new_argv[0] = xstrdup (""); new_argv[1] = NULL; } else if ((no_default_sh_exe || batch_mode_shell) && batch_filename_ptr) { int temp_fd; FILE* batch = NULL; int id = GetCurrentProcessId(); PATH_VAR(fbuf); /* create a file name */ sprintf(fbuf, "make%d", id); *batch_filename_ptr = create_batch_file (fbuf, unixy_shell, &temp_fd); DB (DB_JOBS, (_("Creating temporary batch file %s\n"), *batch_filename_ptr)); /* Create a FILE object for the batch file, and write to it the commands to be executed. Put the batch file in TEXT mode. */ _setmode (temp_fd, _O_TEXT); batch = _fdopen (temp_fd, "wt"); if (!unixy_shell) fputs ("@echo off\n", batch); fputs (command_ptr, batch); fputc ('\n', batch); fclose (batch); DB (DB_JOBS, (_("Batch file contents:%s\n\t%s\n"), !unixy_shell ? "\n\t@echo off" : "", command_ptr)); /* create argv */ new_argv = xmalloc(3 * sizeof (char *)); if (unixy_shell) { new_argv[0] = xstrdup (shell); new_argv[1] = *batch_filename_ptr; /* only argv[0] gets freed later */ } else { new_argv[0] = xstrdup (*batch_filename_ptr); new_argv[1] = NULL; } new_argv[2] = NULL; } else #endif /* WINDOWS32 */ if (unixy_shell) new_argv = construct_command_argv_internal (new_line, 0, 0, 0, 0, flags, 0); #ifdef __EMX__ else if (!unixy_shell) { /* new_line is local, must not be freed therefore We use line here instead of new_line because we run the shell manually. */ size_t line_len = strlen (line); char *p = new_line; char *q = new_line; memcpy (new_line, line, line_len + 1); /* Replace all backslash-newline combination and also following tabs. Important: stop at the first '\n' because that's what the loop above did. The next line starting at restp[0] will be executed during the next call of this function. */ while (*q != '\0' && *q != '\n') { if (q[0] == '\\' && q[1] == '\n') q += 2; /* remove '\\' and '\n' */ else *p++ = *q++; } *p = '\0'; # ifndef NO_CMD_DEFAULT if (strnicmp (new_line, "echo", 4) == 0 && (new_line[4] == ' ' || new_line[4] == '\t')) { /* the builtin echo command: handle it separately */ size_t echo_len = line_len - 5; char *echo_line = new_line + 5; /* special case: echo 'x="y"' cmd works this way: a string is printed as is, i.e., no quotes are removed. But autoconf uses a command like echo 'x="y"' to determine whether make works. autoconf expects the output x="y" so we will do exactly that. Note: if we do not allow cmd to be the default shell we do not need this kind of voodoo */ if (echo_line[0] == '\'' && echo_line[echo_len - 1] == '\'' && strncmp (echo_line + 1, "ac_maketemp=", strlen ("ac_maketemp=")) == 0) { /* remove the enclosing quotes */ memmove (echo_line, echo_line + 1, echo_len - 2); echo_line[echo_len - 2] = '\0'; } } # endif { /* Let the shell decide what to do. Put the command line into the 2nd command line argument and hope for the best ;-) */ size_t sh_len = strlen (shell); /* exactly 3 arguments + NULL */ new_argv = xmalloc (4 * sizeof (char *)); /* Exactly strlen(shell) + strlen("/c") + strlen(line) + 3 times the trailing '\0' */ new_argv[0] = xmalloc (sh_len + line_len + 5); memcpy (new_argv[0], shell, sh_len + 1); new_argv[1] = new_argv[0] + sh_len + 1; memcpy (new_argv[1], "/c", 3); new_argv[2] = new_argv[1] + 3; memcpy (new_argv[2], new_line, line_len + 1); new_argv[3] = NULL; } } #elif defined(__MSDOS__) else { /* With MSDOS shells, we must construct the command line here instead of recursively calling ourselves, because we cannot backslash-escape the special characters (see above). */ new_argv = xmalloc (sizeof (char *)); line_len = strlen (new_line) - shell_len - sflags_len - 2; new_argv[0] = xmalloc (line_len + 1); strncpy (new_argv[0], new_line + shell_len + sflags_len + 2, line_len); new_argv[0][line_len] = '\0'; } #else else fatal (NILF, _("%s (line %d) Bad shell context (!unixy && !batch_mode_shell)\n"), __FILE__, __LINE__); #endif } #endif /* ! AMIGA */ return new_argv; } #endif /* !VMS */ /* Figure out the argument list necessary to run LINE as a command. Try to avoid using a shell. This routine handles only ' quoting, and " quoting when no backslash, $ or ` characters are seen in the quotes. Starting quotes may be escaped with a backslash. If any of the characters in sh_chars[] is seen, or any of the builtin commands listed in sh_cmds[] is the first word of a line, the shell is used. If RESTP is not NULL, *RESTP is set to point to the first newline in LINE. If *RESTP is NULL, newlines will be ignored. FILE is the target whose commands these are. It is used for variable expansion for $(SHELL) and $(IFS). */ char ** construct_command_argv (char *line, char **restp, struct file *file, int cmd_flags, char **batch_filename_ptr) { char *shell, *ifs, *shellflags; char **argv; #ifdef VMS char *cptr; int argc; argc = 0; cptr = line; for (;;) { while ((*cptr != 0) && (isspace ((unsigned char)*cptr))) cptr++; if (*cptr == 0) break; while ((*cptr != 0) && (!isspace((unsigned char)*cptr))) cptr++; argc++; } argv = xmalloc (argc * sizeof (char *)); if (argv == 0) abort (); cptr = line; argc = 0; for (;;) { while ((*cptr != 0) && (isspace ((unsigned char)*cptr))) cptr++; if (*cptr == 0) break; DB (DB_JOBS, ("argv[%d] = [%s]\n", argc, cptr)); argv[argc++] = cptr; while ((*cptr != 0) && (!isspace((unsigned char)*cptr))) cptr++; if (*cptr != 0) *cptr++ = 0; } #else { /* Turn off --warn-undefined-variables while we expand SHELL and IFS. */ int save = warn_undefined_variables_flag; warn_undefined_variables_flag = 0; shell = allocated_variable_expand_for_file ("$(SHELL)", file); #ifdef WINDOWS32 /* * Convert to forward slashes so that construct_command_argv_internal() * is not confused. */ if (shell) { char *p = w32ify (shell, 0); strcpy (shell, p); } #endif #ifdef __EMX__ { static const char *unixroot = NULL; static const char *last_shell = ""; static int init = 0; if (init == 0) { unixroot = getenv ("UNIXROOT"); /* unixroot must be NULL or not empty */ if (unixroot && unixroot[0] == '\0') unixroot = NULL; init = 1; } /* if we have an unixroot drive and if shell is not default_shell (which means it's either cmd.exe or the test has already been performed) and if shell is an absolute path without drive letter, try whether it exists e.g.: if "/bin/sh" does not exist use "$UNIXROOT/bin/sh" instead. */ if (unixroot && shell && strcmp (shell, last_shell) != 0 && (shell[0] == '/' || shell[0] == '\\')) { /* trying a new shell, check whether it exists */ size_t size = strlen (shell); char *buf = xmalloc (size + 7); memcpy (buf, shell, size); memcpy (buf + size, ".exe", 5); /* including the trailing '\0' */ if (access (shell, F_OK) != 0 && access (buf, F_OK) != 0) { /* try the same for the unixroot drive */ memmove (buf + 2, buf, size + 5); buf[0] = unixroot[0]; buf[1] = unixroot[1]; if (access (buf, F_OK) == 0) /* we have found a shell! */ /* free(shell); */ shell = buf; else free (buf); } else free (buf); } } #endif /* __EMX__ */ shellflags = allocated_variable_expand_for_file ("$(.SHELLFLAGS)", file); ifs = allocated_variable_expand_for_file ("$(IFS)", file); warn_undefined_variables_flag = save; } argv = construct_command_argv_internal (line, restp, shell, shellflags, ifs, cmd_flags, batch_filename_ptr); free (shell); free (shellflags); free (ifs); #endif /* !VMS */ return argv; } #if !defined(HAVE_DUP2) && !defined(_AMIGA) int dup2 (int old, int new) { int fd; (void) close (new); fd = dup (old); if (fd != new) { (void) close (fd); errno = EMFILE; return -1; } return fd; } #endif /* !HAVE_DUP2 && !_AMIGA */ /* On VMS systems, include special VMS functions. */ #ifdef VMS #include "vmsjobs.c" #endif
/* Argument parsing and main program of GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" #include "dep.h" #include "filedef.h" #include "variable.h" #include "job.h" #include "commands.h" #include "rule.h" #include "debug.h" #include "getopt.h" #include <assert.h> #ifdef _AMIGA # include <dos/dos.h> # include <proto/dos.h> #endif #ifdef WINDOWS32 #include <windows.h> #include <io.h> #include "pathstuff.h" #endif #ifdef __EMX__ # include <sys/types.h> # include <sys/wait.h> #endif #ifdef HAVE_FCNTL_H # include <fcntl.h> #endif #ifdef _AMIGA int __stack = 20000; /* Make sure we have 20K of stack space */ #endif void init_dir (void); void remote_setup (void); void remote_cleanup (void); RETSIGTYPE fatal_error_signal (int sig); void print_variable_data_base (void); void print_dir_data_base (void); void print_rule_data_base (void); void print_vpath_data_base (void); void verify_file_data_base (void); #if defined HAVE_WAITPID || defined HAVE_WAIT3 # define HAVE_WAIT_NOHANG #endif #ifndef HAVE_UNISTD_H int chdir (); #endif #ifndef STDC_HEADERS # ifndef sun /* Sun has an incorrect decl in a header. */ void exit (int) __attribute__ ((noreturn)); # endif double atof (); #endif static void clean_jobserver (int status); static void print_data_base (void); static void print_version (void); static void decode_switches (int argc, char **argv, int env); static void decode_env_switches (char *envar, unsigned int len); static const char *define_makeflags (int all, int makefile); static char *quote_for_env (char *out, const char *in); static void initialize_global_hash_tables (void); /* The structure that describes an accepted command switch. */ struct command_switch { int c; /* The switch character. */ enum /* Type of the value. */ { flag, /* Turn int flag on. */ flag_off, /* Turn int flag off. */ string, /* One string per switch. */ filename, /* A string containing a file name. */ positive_int, /* A positive integer. */ floating, /* A floating-point number (double). */ ignore /* Ignored. */ } type; void *value_ptr; /* Pointer to the value-holding variable. */ unsigned int env:1; /* Can come from MAKEFLAGS. */ unsigned int toenv:1; /* Should be put in MAKEFLAGS. */ unsigned int no_makefile:1; /* Don't propagate when remaking makefiles. */ const void *noarg_value; /* Pointer to value used if no arg given. */ const void *default_value; /* Pointer to default value. */ char *long_name; /* Long option name. */ }; /* True if C is a switch value that corresponds to a short option. */ #define short_option(c) ((c) <= CHAR_MAX) /* The structure used to hold the list of strings given in command switches of a type that takes string arguments. */ struct stringlist { const char **list; /* Nil-terminated list of strings. */ unsigned int idx; /* Index into above. */ unsigned int max; /* Number of pointers allocated. */ }; /* The recognized command switches. */ /* Nonzero means do not print commands to be executed (-s). */ int silent_flag; /* Nonzero means just touch the files that would appear to need remaking (-t) */ int touch_flag; /* Nonzero means just print what commands would need to be executed, don't actually execute them (-n). */ int just_print_flag; /* Print debugging info (--debug). */ static struct stringlist *db_flags; static int debug_flag = 0; int db_level = 0; /* Output level (--verbosity). */ static struct stringlist *verbosity_flags; #ifdef WINDOWS32 /* Suspend make in main for a short time to allow debugger to attach */ int suspend_flag = 0; #endif /* Environment variables override makefile definitions. */ int env_overrides = 0; /* Nonzero means ignore status codes returned by commands executed to remake files. Just treat them all as successful (-i). */ int ignore_errors_flag = 0; /* Nonzero means don't remake anything, just print the data base that results from reading the makefile (-p). */ int print_data_base_flag = 0; /* Nonzero means don't remake anything; just return a nonzero status if the specified targets are not up to date (-q). */ int question_flag = 0; /* Nonzero means do not use any of the builtin rules (-r) / variables (-R). */ int no_builtin_rules_flag = 0; int no_builtin_variables_flag = 0; /* Nonzero means keep going even if remaking some file fails (-k). */ int keep_going_flag; int default_keep_going_flag = 0; /* Nonzero means check symlink mtimes. */ int check_symlink_flag = 0; /* Nonzero means print directory before starting and when done (-w). */ int print_directory_flag = 0; /* Nonzero means ignore print_directory_flag and never print the directory. This is necessary because print_directory_flag is set implicitly. */ int inhibit_print_directory_flag = 0; /* Nonzero means print version information. */ int print_version_flag = 0; /* List of makefiles given with -f switches. */ static struct stringlist *makefiles = 0; /* Size of the stack when we started. */ #ifdef SET_STACK_SIZE struct rlimit stack_limit; #endif /* Number of job slots (commands that can be run at once). */ unsigned int job_slots = 1; unsigned int default_job_slots = 1; static unsigned int master_job_slots = 0; /* Value of job_slots that means no limit. */ static unsigned int inf_jobs = 0; /* File descriptors for the jobs pipe. */ static struct stringlist *jobserver_fds = 0; int job_fds[2] = { -1, -1 }; int job_rfd = -1; /* Maximum load average at which multiple jobs will be run. Negative values mean unlimited, while zero means limit to zero load (which could be useful to start infinite jobs remotely but one at a time locally). */ #ifndef NO_FLOAT double max_load_average = -1.0; double default_load_average = -1.0; #else int max_load_average = -1; int default_load_average = -1; #endif /* List of directories given with -C switches. */ static struct stringlist *directories = 0; /* List of include directories given with -I switches. */ static struct stringlist *include_directories = 0; /* List of files given with -o switches. */ static struct stringlist *old_files = 0; /* List of files given with -W switches. */ static struct stringlist *new_files = 0; /* List of strings to be eval'd. */ static struct stringlist *eval_strings = 0; /* If nonzero, we should just print usage and exit. */ static int print_usage_flag = 0; /* If nonzero, we should print a warning message for each reference to an undefined variable. */ int warn_undefined_variables_flag; /* If nonzero, always build all targets, regardless of whether they appear out of date or not. */ static int always_make_set = 0; int always_make_flag = 0; /* If nonzero, we're in the "try to rebuild makefiles" phase. */ int rebuilding_makefiles = 0; /* Remember the original value of the SHELL variable, from the environment. */ struct variable shell_var; /* This character introduces a command: it's the first char on the line. */ char cmd_prefix = '\t'; /* The usage output. We write it this way to make life easier for the translators, especially those trying to translate to right-to-left languages like Hebrew. */ static const char *const usage[] = { N_("Options:\n"), N_("\ -b, -m Ignored for compatibility.\n"), N_("\ -B, --always-make Unconditionally make all targets.\n"), N_("\ -C DIRECTORY, --directory=DIRECTORY\n\ Change to DIRECTORY before doing anything.\n"), N_("\ -d Print lots of debugging information.\n"), N_("\ --debug[=FLAGS] Print various types of debugging information.\n"), N_("\ -e, --environment-overrides\n\ Environment variables override makefiles.\n"), N_("\ --eval=STRING Evaluate STRING as a makefile statement.\n"), N_("\ -f FILE, --file=FILE, --makefile=FILE\n\ Read FILE as a makefile.\n"), N_("\ -h, --help Print this message and exit.\n"), N_("\ -i, --ignore-errors Ignore errors from recipes.\n"), N_("\ -I DIRECTORY, --include-dir=DIRECTORY\n\ Search DIRECTORY for included makefiles.\n"), N_("\ -j [N], --jobs[=N] Allow N jobs at once; infinite jobs with no arg.\n"), N_("\ -k, --keep-going Keep going when some targets can't be made.\n"), N_("\ -l [N], --load-average[=N], --max-load[=N]\n\ Don't start multiple jobs unless load is below N.\n"), N_("\ -L, --check-symlink-times Use the latest mtime between symlinks and target.\n"), N_("\ -n, --just-print, --dry-run, --recon\n\ Don't actually run any recipe; just print them.\n"), N_("\ -o FILE, --old-file=FILE, --assume-old=FILE\n\ Consider FILE to be very old and don't remake it.\n"), N_("\ -p, --print-data-base Print make's internal database.\n"), N_("\ -q, --question Run no recipe; exit status says if up to date.\n"), N_("\ -r, --no-builtin-rules Disable the built-in implicit rules.\n"), N_("\ -R, --no-builtin-variables Disable the built-in variable settings.\n"), N_("\ -s, --silent, --quiet Don't echo recipes.\n"), N_("\ -S, --no-keep-going, --stop\n\ Turns off -k.\n"), N_("\ -t, --touch Touch targets instead of remaking them.\n"), N_("\ -v, --version Print the version number of make and exit.\n"), N_("\ -w, --print-directory Print the current directory.\n"), N_("\ --no-print-directory Turn off -w, even if it was turned on implicitly.\n"), N_("\ -W FILE, --what-if=FILE, --new-file=FILE, --assume-new=FILE\n\ Consider FILE to be infinitely new.\n"), N_("\ --warn-undefined-variables Warn when an undefined variable is referenced.\n"), NULL }; /* The table of command switches. */ static const struct command_switch switches[] = { { 'b', ignore, 0, 0, 0, 0, 0, 0, 0 }, { 'B', flag, &always_make_set, 1, 1, 0, 0, 0, "always-make" }, { 'C', filename, &directories, 0, 0, 0, 0, 0, "directory" }, { 'd', flag, &debug_flag, 1, 1, 0, 0, 0, 0 }, { CHAR_MAX+1, string, &db_flags, 1, 1, 0, "basic", 0, "debug" }, #ifdef WINDOWS32 { 'D', flag, &suspend_flag, 1, 1, 0, 0, 0, "suspend-for-debug" }, #endif { 'e', flag, &env_overrides, 1, 1, 0, 0, 0, "environment-overrides", }, { 'f', filename, &makefiles, 0, 0, 0, 0, 0, "file" }, { 'h', flag, &print_usage_flag, 0, 0, 0, 0, 0, "help" }, { 'i', flag, &ignore_errors_flag, 1, 1, 0, 0, 0, "ignore-errors" }, { 'I', filename, &include_directories, 1, 1, 0, 0, 0, "include-dir" }, { 'j', positive_int, &job_slots, 1, 1, 0, &inf_jobs, &default_job_slots, "jobs" }, { CHAR_MAX+2, string, &jobserver_fds, 1, 1, 0, 0, 0, "jobserver-fds" }, { 'k', flag, &keep_going_flag, 1, 1, 0, 0, &default_keep_going_flag, "keep-going" }, #ifndef NO_FLOAT { 'l', floating, &max_load_average, 1, 1, 0, &default_load_average, &default_load_average, "load-average" }, #else { 'l', positive_int, &max_load_average, 1, 1, 0, &default_load_average, &default_load_average, "load-average" }, #endif { 'L', flag, &check_symlink_flag, 1, 1, 0, 0, 0, "check-symlink-times" }, { 'm', ignore, 0, 0, 0, 0, 0, 0, 0 }, { 'n', flag, &just_print_flag, 1, 1, 1, 0, 0, "just-print" }, { 'o', filename, &old_files, 0, 0, 0, 0, 0, "old-file" }, { 'p', flag, &print_data_base_flag, 1, 1, 0, 0, 0, "print-data-base" }, { 'q', flag, &question_flag, 1, 1, 1, 0, 0, "question" }, { 'r', flag, &no_builtin_rules_flag, 1, 1, 0, 0, 0, "no-builtin-rules" }, { 'R', flag, &no_builtin_variables_flag, 1, 1, 0, 0, 0, "no-builtin-variables" }, { 's', flag, &silent_flag, 1, 1, 0, 0, 0, "silent" }, { 'S', flag_off, &keep_going_flag, 1, 1, 0, 0, &default_keep_going_flag, "no-keep-going" }, { 't', flag, &touch_flag, 1, 1, 1, 0, 0, "touch" }, { 'v', flag, &print_version_flag, 1, 1, 0, 0, 0, "version" }, { CHAR_MAX+3, string, &verbosity_flags, 1, 1, 0, 0, 0, "verbosity" }, { 'w', flag, &print_directory_flag, 1, 1, 0, 0, 0, "print-directory" }, { CHAR_MAX+4, flag, &inhibit_print_directory_flag, 1, 1, 0, 0, 0, "no-print-directory" }, { 'W', filename, &new_files, 0, 0, 0, 0, 0, "what-if" }, { CHAR_MAX+5, flag, &warn_undefined_variables_flag, 1, 1, 0, 0, 0, "warn-undefined-variables" }, { CHAR_MAX+6, string, &eval_strings, 1, 0, 0, 0, 0, "eval" }, { 0, 0, 0, 0, 0, 0, 0, 0, 0 } }; /* Secondary long names for options. */ static struct option long_option_aliases[] = { { "quiet", no_argument, 0, 's' }, { "stop", no_argument, 0, 'S' }, { "new-file", required_argument, 0, 'W' }, { "assume-new", required_argument, 0, 'W' }, { "assume-old", required_argument, 0, 'o' }, { "max-load", optional_argument, 0, 'l' }, { "dry-run", no_argument, 0, 'n' }, { "recon", no_argument, 0, 'n' }, { "makefile", required_argument, 0, 'f' }, }; /* List of goal targets. */ static struct dep *goals, *lastgoal; /* List of variables which were defined on the command line (or, equivalently, in MAKEFLAGS). */ struct command_variable { struct command_variable *next; struct variable *variable; }; static struct command_variable *command_variables; /* The name we were invoked with. */ char *program; /* Our current directory before processing any -C options. */ char *directory_before_chdir; /* Our current directory after processing all -C options. */ char *starting_directory; /* Value of the MAKELEVEL variable at startup (or 0). */ unsigned int makelevel; /* Pointer to the value of the .DEFAULT_GOAL special variable. The value will be the name of the goal to remake if the command line does not override it. It can be set by the makefile, or else it's the first target defined in the makefile whose name does not start with '.'. */ struct variable * default_goal_var; /* Pointer to structure for the file .DEFAULT whose commands are used for any file that has none of its own. This is zero if the makefiles do not define .DEFAULT. */ struct file *default_file; /* Nonzero if we have seen the magic `.POSIX' target. This turns on pedantic compliance with POSIX.2. */ int posix_pedantic; /* Nonzero if we have seen the '.SECONDEXPANSION' target. This turns on secondary expansion of prerequisites. */ int second_expansion; /* Nonzero if we have seen the '.ONESHELL' target. This causes the entire recipe to be handed to SHELL as a single string, potentially containing newlines. */ int one_shell; /* Nonzero if we have seen the `.NOTPARALLEL' target. This turns off parallel builds for this invocation of make. */ int not_parallel; /* Nonzero if some rule detected clock skew; we keep track so (a) we only print one warning about it during the run, and (b) we can print a final warning at the end of the run. */ int clock_skew_detected; /* Mask of signals that are being caught with fatal_error_signal. */ #ifdef POSIX sigset_t fatal_signal_set; #else # ifdef HAVE_SIGSETMASK int fatal_signal_mask; # endif #endif #if !HAVE_DECL_BSD_SIGNAL && !defined bsd_signal # if !defined HAVE_SIGACTION # define bsd_signal signal # else typedef RETSIGTYPE (*bsd_signal_ret_t) (int); static bsd_signal_ret_t bsd_signal (int sig, bsd_signal_ret_t func) { struct sigaction act, oact; act.sa_handler = func; act.sa_flags = SA_RESTART; sigemptyset (&act.sa_mask); sigaddset (&act.sa_mask, sig); if (sigaction (sig, &act, &oact) != 0) return SIG_ERR; return oact.sa_handler; } # endif #endif static void initialize_global_hash_tables (void) { init_hash_global_variable_set (); strcache_init (); init_hash_files (); hash_init_directories (); hash_init_function_table (); } static const char * expand_command_line_file (char *name) { const char *cp; char *expanded = 0; if (name[0] == '\0') fatal (NILF, _("empty string invalid as file name")); if (name[0] == '~') { expanded = tilde_expand (name); if (expanded != 0) name = expanded; } /* This is also done in parse_file_seq, so this is redundant for names read from makefiles. It is here for names passed on the command line. */ while (name[0] == '.' && name[1] == '/' && name[2] != '\0') { name += 2; while (*name == '/') /* Skip following slashes: ".//foo" is "foo", not "/foo". */ ++name; } if (*name == '\0') { /* It was all slashes! Move back to the dot and truncate it after the first slash, so it becomes just "./". */ do --name; while (name[0] != '.'); name[2] = '\0'; } cp = strcache_add (name); if (expanded) free (expanded); return cp; } /* Toggle -d on receipt of SIGUSR1. */ #ifdef SIGUSR1 static RETSIGTYPE debug_signal_handler (int sig UNUSED) { db_level = db_level ? DB_NONE : DB_BASIC; } #endif static void decode_debug_flags (void) { const char **pp; if (debug_flag) db_level = DB_ALL; if (!db_flags) return; for (pp=db_flags->list; *pp; ++pp) { const char *p = *pp; while (1) { switch (tolower (p[0])) { case 'a': db_level |= DB_ALL; break; case 'b': db_level |= DB_BASIC; break; case 'i': db_level |= DB_BASIC | DB_IMPLICIT; break; case 'j': db_level |= DB_JOBS; break; case 'm': db_level |= DB_BASIC | DB_MAKEFILES; break; case 'v': db_level |= DB_BASIC | DB_VERBOSE; break; default: fatal (NILF, _("unknown debug level specification `%s'"), p); } while (*(++p) != '\0') if (*p == ',' || *p == ' ') break; if (*p == '\0') break; ++p; } } } #ifdef WINDOWS32 /* * HANDLE runtime exceptions by avoiding a requestor on the GUI. Capture * exception and print it to stderr instead. * * If ! DB_VERBOSE, just print a simple message and exit. * If DB_VERBOSE, print a more verbose message. * If compiled for DEBUG, let exception pass through to GUI so that * debuggers can attach. */ LONG WINAPI handle_runtime_exceptions( struct _EXCEPTION_POINTERS *exinfo ) { PEXCEPTION_RECORD exrec = exinfo->ExceptionRecord; LPSTR cmdline = GetCommandLine(); LPSTR prg = strtok(cmdline, " "); CHAR errmsg[1024]; #ifdef USE_EVENT_LOG HANDLE hEventSource; LPTSTR lpszStrings[1]; #endif if (! ISDB (DB_VERBOSE)) { sprintf(errmsg, _("%s: Interrupt/Exception caught (code = 0x%lx, addr = 0x%p)\n"), prg, exrec->ExceptionCode, exrec->ExceptionAddress); fprintf(stderr, errmsg); exit(255); } sprintf(errmsg, _("\nUnhandled exception filter called from program %s\nExceptionCode = %lx\nExceptionFlags = %lx\nExceptionAddress = 0x%p\n"), prg, exrec->ExceptionCode, exrec->ExceptionFlags, exrec->ExceptionAddress); if (exrec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && exrec->NumberParameters >= 2) sprintf(&errmsg[strlen(errmsg)], (exrec->ExceptionInformation[0] ? _("Access violation: write operation at address 0x%p\n") : _("Access violation: read operation at address 0x%p\n")), (PVOID)exrec->ExceptionInformation[1]); /* turn this on if we want to put stuff in the event log too */ #ifdef USE_EVENT_LOG hEventSource = RegisterEventSource(NULL, "GNU Make"); lpszStrings[0] = errmsg; if (hEventSource != NULL) { ReportEvent(hEventSource, /* handle of event source */ EVENTLOG_ERROR_TYPE, /* event type */ 0, /* event category */ 0, /* event ID */ NULL, /* current user's SID */ 1, /* strings in lpszStrings */ 0, /* no bytes of raw data */ lpszStrings, /* array of error strings */ NULL); /* no raw data */ (VOID) DeregisterEventSource(hEventSource); } #endif /* Write the error to stderr too */ fprintf(stderr, errmsg); #ifdef DEBUG return EXCEPTION_CONTINUE_SEARCH; #else exit(255); return (255); /* not reached */ #endif } /* * On WIN32 systems we don't have the luxury of a /bin directory that * is mapped globally to every drive mounted to the system. Since make could * be invoked from any drive, and we don't want to propogate /bin/sh * to every single drive. Allow ourselves a chance to search for * a value for default shell here (if the default path does not exist). */ int find_and_set_default_shell (const char *token) { int sh_found = 0; char *atoken = 0; char *search_token; char *tokend; PATH_VAR(sh_path); extern char *default_shell; if (!token) search_token = default_shell; else atoken = search_token = xstrdup (token); /* If the user explicitly requests the DOS cmd shell, obey that request. However, make sure that's what they really want by requiring the value of SHELL either equal, or have a final path element of, "cmd" or "cmd.exe" case-insensitive. */ tokend = search_token + strlen (search_token) - 3; if (((tokend == search_token || (tokend > search_token && (tokend[-1] == '/' || tokend[-1] == '\\'))) && !strcasecmp (tokend, "cmd")) || ((tokend - 4 == search_token || (tokend - 4 > search_token && (tokend[-5] == '/' || tokend[-5] == '\\'))) && !strcasecmp (tokend - 4, "cmd.exe"))) { batch_mode_shell = 1; unixy_shell = 0; sprintf (sh_path, "%s", search_token); default_shell = xstrdup (w32ify (sh_path, 0)); DB (DB_VERBOSE, (_("find_and_set_shell() setting default_shell = %s\n"), default_shell)); sh_found = 1; } else if (!no_default_sh_exe && (token == NULL || !strcmp (search_token, default_shell))) { /* no new information, path already set or known */ sh_found = 1; } else if (file_exists_p (search_token)) { /* search token path was found */ sprintf (sh_path, "%s", search_token); default_shell = xstrdup (w32ify (sh_path, 0)); DB (DB_VERBOSE, (_("find_and_set_shell() setting default_shell = %s\n"), default_shell)); sh_found = 1; } else { char *p; struct variable *v = lookup_variable (STRING_SIZE_TUPLE ("PATH")); /* Search Path for shell */ if (v && v->value) { char *ep; p = v->value; ep = strchr (p, PATH_SEPARATOR_CHAR); while (ep && *ep) { *ep = '\0'; if (dir_file_exists_p (p, search_token)) { sprintf (sh_path, "%s/%s", p, search_token); default_shell = xstrdup (w32ify (sh_path, 0)); sh_found = 1; *ep = PATH_SEPARATOR_CHAR; /* terminate loop */ p += strlen (p); } else { *ep = PATH_SEPARATOR_CHAR; p = ++ep; } ep = strchr (p, PATH_SEPARATOR_CHAR); } /* be sure to check last element of Path */ if (p && *p && dir_file_exists_p (p, search_token)) { sprintf (sh_path, "%s/%s", p, search_token); default_shell = xstrdup (w32ify (sh_path, 0)); sh_found = 1; } if (sh_found) DB (DB_VERBOSE, (_("find_and_set_shell() path search set default_shell = %s\n"), default_shell)); } } /* naive test */ if (!unixy_shell && sh_found && (strstr (default_shell, "sh") || strstr (default_shell, "SH"))) { unixy_shell = 1; batch_mode_shell = 0; } #ifdef BATCH_MODE_ONLY_SHELL batch_mode_shell = 1; #endif if (atoken) free (atoken); return (sh_found); } #endif /* WINDOWS32 */ #ifdef __MSDOS__ static void msdos_return_to_initial_directory (void) { if (directory_before_chdir) chdir (directory_before_chdir); } #endif /* __MSDOS__ */ char *mktemp (char *template); int mkstemp (char *template); FILE * open_tmpfile(char **name, const char *template) { #ifdef HAVE_FDOPEN int fd; #endif #if defined HAVE_MKSTEMP || defined HAVE_MKTEMP # define TEMPLATE_LEN strlen (template) #else # define TEMPLATE_LEN L_tmpnam #endif *name = xmalloc (TEMPLATE_LEN + 1); strcpy (*name, template); #if defined HAVE_MKSTEMP && defined HAVE_FDOPEN /* It's safest to use mkstemp(), if we can. */ fd = mkstemp (*name); if (fd == -1) return 0; return fdopen (fd, "w"); #else # ifdef HAVE_MKTEMP (void) mktemp (*name); # else (void) tmpnam (*name); # endif # ifdef HAVE_FDOPEN /* Can't use mkstemp(), but guard against a race condition. */ fd = open (*name, O_CREAT|O_EXCL|O_WRONLY, 0600); if (fd == -1) return 0; return fdopen (fd, "w"); # else /* Not secure, but what can we do? */ return fopen (*name, "w"); # endif #endif } #ifdef _AMIGA int main (int argc, char **argv) #else int main (int argc, char **argv, char **envp) #endif { static char *stdin_nm = 0; int makefile_status = MAKE_SUCCESS; struct dep *read_makefiles; PATH_VAR (current_directory); unsigned int restarts = 0; #ifdef WINDOWS32 char *unix_path = NULL; char *windows32_path = NULL; SetUnhandledExceptionFilter(handle_runtime_exceptions); /* start off assuming we have no shell */ unixy_shell = 0; no_default_sh_exe = 1; #endif #ifdef SET_STACK_SIZE /* Get rid of any avoidable limit on stack size. */ { struct rlimit rlim; /* Set the stack limit huge so that alloca does not fail. */ if (getrlimit (RLIMIT_STACK, &rlim) == 0 && rlim.rlim_cur > 0 && rlim.rlim_cur < rlim.rlim_max) { stack_limit = rlim; rlim.rlim_cur = rlim.rlim_max; setrlimit (RLIMIT_STACK, &rlim); } else stack_limit.rlim_cur = 0; } #endif #ifdef HAVE_ATEXIT atexit (close_stdout); #endif /* Needed for OS/2 */ initialize_main(&argc, &argv); reading_file = 0; #if defined (__MSDOS__) && !defined (_POSIX_SOURCE) /* Request the most powerful version of `system', to make up for the dumb default shell. */ __system_flags = (__system_redirect | __system_use_shell | __system_allow_multiple_cmds | __system_allow_long_cmds | __system_handle_null_commands | __system_emulate_chdir); #endif /* Set up gettext/internationalization support. */ setlocale (LC_ALL, ""); /* The cast to void shuts up compiler warnings on systems that disable NLS. */ (void)bindtextdomain (PACKAGE, LOCALEDIR); (void)textdomain (PACKAGE); #ifdef POSIX sigemptyset (&fatal_signal_set); #define ADD_SIG(sig) sigaddset (&fatal_signal_set, sig) #else #ifdef HAVE_SIGSETMASK fatal_signal_mask = 0; #define ADD_SIG(sig) fatal_signal_mask |= sigmask (sig) #else #define ADD_SIG(sig) (void)sig /* Needed to avoid warnings in MSVC. */ #endif #endif #define FATAL_SIG(sig) \ if (bsd_signal (sig, fatal_error_signal) == SIG_IGN) \ bsd_signal (sig, SIG_IGN); \ else \ ADD_SIG (sig); #ifdef SIGHUP FATAL_SIG (SIGHUP); #endif #ifdef SIGQUIT FATAL_SIG (SIGQUIT); #endif FATAL_SIG (SIGINT); FATAL_SIG (SIGTERM); #ifdef __MSDOS__ /* Windows 9X delivers FP exceptions in child programs to their parent! We don't want Make to die when a child divides by zero, so we work around that lossage by catching SIGFPE. */ FATAL_SIG (SIGFPE); #endif #ifdef SIGDANGER FATAL_SIG (SIGDANGER); #endif #ifdef SIGXCPU FATAL_SIG (SIGXCPU); #endif #ifdef SIGXFSZ FATAL_SIG (SIGXFSZ); #endif #undef FATAL_SIG /* Do not ignore the child-death signal. This must be done before any children could possibly be created; otherwise, the wait functions won't work on systems with the SVR4 ECHILD brain damage, if our invoker is ignoring this signal. */ #ifdef HAVE_WAIT_NOHANG # if defined SIGCHLD (void) bsd_signal (SIGCHLD, SIG_DFL); # endif # if defined SIGCLD && SIGCLD != SIGCHLD (void) bsd_signal (SIGCLD, SIG_DFL); # endif #endif /* Make sure stdout is line-buffered. */ #ifdef HAVE_SETVBUF # ifdef SETVBUF_REVERSED setvbuf (stdout, _IOLBF, xmalloc (BUFSIZ), BUFSIZ); # else /* setvbuf not reversed. */ /* Some buggy systems lose if we pass 0 instead of allocating ourselves. */ setvbuf (stdout, 0, _IOLBF, BUFSIZ); # endif /* setvbuf reversed. */ #elif HAVE_SETLINEBUF setlinebuf (stdout); #endif /* setlinebuf missing. */ /* Figure out where this program lives. */ if (argv[0] == 0) argv[0] = ""; if (argv[0][0] == '\0') program = "make"; else { #ifdef VMS program = strrchr (argv[0], ']'); #else program = strrchr (argv[0], '/'); #endif #if defined(__MSDOS__) || defined(__EMX__) if (program == 0) program = strrchr (argv[0], '\\'); else { /* Some weird environments might pass us argv[0] with both kinds of slashes; we must find the rightmost. */ char *p = strrchr (argv[0], '\\'); if (p && p > program) program = p; } if (program == 0 && argv[0][1] == ':') program = argv[0] + 1; #endif #ifdef WINDOWS32 if (program == 0) { /* Extract program from full path */ int argv0_len; program = strrchr (argv[0], '\\'); if (program) { argv0_len = strlen(program); if (argv0_len > 4 && streq (&program[argv0_len - 4], ".exe")) /* Remove .exe extension */ program[argv0_len - 4] = '\0'; } } #endif if (program == 0) program = argv[0]; else ++program; } /* Set up to access user data (files). */ user_access (); initialize_global_hash_tables (); /* Figure out where we are. */ #ifdef WINDOWS32 if (getcwd_fs (current_directory, GET_PATH_MAX) == 0) #else if (getcwd (current_directory, GET_PATH_MAX) == 0) #endif { #ifdef HAVE_GETCWD perror_with_name ("getcwd", ""); #else error (NILF, "getwd: %s", current_directory); #endif current_directory[0] = '\0'; directory_before_chdir = 0; } else directory_before_chdir = xstrdup (current_directory); #ifdef __MSDOS__ /* Make sure we will return to the initial directory, come what may. */ atexit (msdos_return_to_initial_directory); #endif /* Initialize the special variables. */ define_variable_cname (".VARIABLES", "", o_default, 0)->special = 1; /* define_variable_cname (".TARGETS", "", o_default, 0)->special = 1; */ define_variable_cname (".RECIPEPREFIX", "", o_default, 0)->special = 1; define_variable_cname (".SHELLFLAGS", "-c", o_default, 0); /* Set up .FEATURES We must do this in multiple calls because define_variable_cname() is a macro and some compilers (MSVC) don't like conditionals in macros. */ { const char *features = "target-specific order-only second-expansion" " else-if shortest-stem undefine" #ifndef NO_ARCHIVES " archives" #endif #ifdef MAKE_JOBSERVER " jobserver" #endif #ifdef MAKE_SYMLINKS " check-symlink" #endif ; define_variable_cname (".FEATURES", features, o_default, 0); } /* Read in variables from the environment. It is important that this be done before $(MAKE) is figured out so its definitions will not be from the environment. */ #ifndef _AMIGA { unsigned int i; for (i = 0; envp[i] != 0; ++i) { int do_not_define = 0; char *ep = envp[i]; while (*ep != '\0' && *ep != '=') ++ep; #ifdef WINDOWS32 if (!unix_path && strneq(envp[i], "PATH=", 5)) unix_path = ep+1; else if (!strnicmp(envp[i], "Path=", 5)) { do_not_define = 1; /* it gets defined after loop exits */ if (!windows32_path) windows32_path = ep+1; } #endif /* The result of pointer arithmetic is cast to unsigned int for machines where ptrdiff_t is a different size that doesn't widen the same. */ if (!do_not_define) { struct variable *v; v = define_variable (envp[i], (unsigned int) (ep - envp[i]), ep + 1, o_env, 1); /* Force exportation of every variable culled from the environment. We used to rely on target_environment's v_default code to do this. But that does not work for the case where an environment variable is redefined in a makefile with `override'; it should then still be exported, because it was originally in the environment. */ v->export = v_export; /* Another wrinkle is that POSIX says the value of SHELL set in the makefile won't change the value of SHELL given to subprocesses. */ if (streq (v->name, "SHELL")) { #ifndef __MSDOS__ v->export = v_noexport; #endif shell_var.name = "SHELL"; shell_var.length = 5; shell_var.value = xstrdup (ep + 1); } /* If MAKE_RESTARTS is set, remember it but don't export it. */ if (streq (v->name, "MAKE_RESTARTS")) { v->export = v_noexport; restarts = (unsigned int) atoi (ep + 1); } } } } #ifdef WINDOWS32 /* If we didn't find a correctly spelled PATH we define PATH as * either the first mispelled value or an empty string */ if (!unix_path) define_variable_cname ("PATH", windows32_path ? windows32_path : "", o_env, 1)->export = v_export; #endif #else /* For Amiga, read the ENV: device, ignoring all dirs */ { BPTR env, file, old; char buffer[1024]; int len; __aligned struct FileInfoBlock fib; env = Lock ("ENV:", ACCESS_READ); if (env) { old = CurrentDir (DupLock(env)); Examine (env, &fib); while (ExNext (env, &fib)) { if (fib.fib_DirEntryType < 0) /* File */ { /* Define an empty variable. It will be filled in variable_lookup(). Makes startup quite a bit faster. */ define_variable (fib.fib_FileName, strlen (fib.fib_FileName), "", o_env, 1)->export = v_export; } } UnLock (env); UnLock(CurrentDir(old)); } } #endif /* Decode the switches. */ decode_env_switches (STRING_SIZE_TUPLE ("MAKEFLAGS")); #if 0 /* People write things like: MFLAGS="CC=gcc -pipe" "CFLAGS=-g" and we set the -p, -i and -e switches. Doesn't seem quite right. */ decode_env_switches (STRING_SIZE_TUPLE ("MFLAGS")); #endif decode_switches (argc, argv, 0); #ifdef WINDOWS32 if (suspend_flag) { fprintf(stderr, "%s (pid = %ld)\n", argv[0], GetCurrentProcessId()); fprintf(stderr, _("%s is suspending for 30 seconds..."), argv[0]); Sleep(30 * 1000); fprintf(stderr, _("done sleep(30). Continuing.\n")); } #endif decode_debug_flags (); /* Set always_make_flag if -B was given and we've not restarted already. */ always_make_flag = always_make_set && (restarts == 0); /* Print version information. */ if (print_version_flag || print_data_base_flag || db_level) { print_version (); /* `make --version' is supposed to just print the version and exit. */ if (print_version_flag) die (0); } #ifndef VMS /* Set the "MAKE_COMMAND" variable to the name we were invoked with. (If it is a relative pathname with a slash, prepend our directory name so the result will run the same program regardless of the current dir. If it is a name with no slash, we can only hope that PATH did not find it in the current directory.) */ #ifdef WINDOWS32 /* * Convert from backslashes to forward slashes for * programs like sh which don't like them. Shouldn't * matter if the path is one way or the other for * CreateProcess(). */ if (strpbrk(argv[0], "/:\\") || strstr(argv[0], "..") || strneq(argv[0], "//", 2)) argv[0] = xstrdup(w32ify(argv[0],1)); #else /* WINDOWS32 */ #if defined (__MSDOS__) || defined (__EMX__) if (strchr (argv[0], '\\')) { char *p; argv[0] = xstrdup (argv[0]); for (p = argv[0]; *p; p++) if (*p == '\\') *p = '/'; } /* If argv[0] is not in absolute form, prepend the current directory. This can happen when Make is invoked by another DJGPP program that uses a non-absolute name. */ if (current_directory[0] != '\0' && argv[0] != 0 && (argv[0][0] != '/' && (argv[0][0] == '\0' || argv[0][1] != ':')) # ifdef __EMX__ /* do not prepend cwd if argv[0] contains no '/', e.g. "make" */ && (strchr (argv[0], '/') != 0 || strchr (argv[0], '\\') != 0) # endif ) argv[0] = xstrdup (concat (3, current_directory, "/", argv[0])); #else /* !__MSDOS__ */ if (current_directory[0] != '\0' && argv[0] != 0 && argv[0][0] != '/' && strchr (argv[0], '/') != 0 #ifdef HAVE_DOS_PATHS && (argv[0][0] != '\\' && (!argv[0][0] || argv[0][1] != ':')) && strchr (argv[0], '\\') != 0 #endif ) argv[0] = xstrdup (concat (3, current_directory, "/", argv[0])); #endif /* !__MSDOS__ */ #endif /* WINDOWS32 */ #endif /* The extra indirection through $(MAKE_COMMAND) is done for hysterical raisins. */ define_variable_cname ("MAKE_COMMAND", argv[0], o_default, 0); define_variable_cname ("MAKE", "$(MAKE_COMMAND)", o_default, 1); if (command_variables != 0) { struct command_variable *cv; struct variable *v; unsigned int len = 0; char *value, *p; /* Figure out how much space will be taken up by the command-line variable definitions. */ for (cv = command_variables; cv != 0; cv = cv->next) { v = cv->variable; len += 2 * strlen (v->name); if (! v->recursive) ++len; ++len; len += 2 * strlen (v->value); ++len; } /* Now allocate a buffer big enough and fill it. */ p = value = alloca (len); for (cv = command_variables; cv != 0; cv = cv->next) { v = cv->variable; p = quote_for_env (p, v->name); if (! v->recursive) *p++ = ':'; *p++ = '='; p = quote_for_env (p, v->value); *p++ = ' '; } p[-1] = '\0'; /* Kill the final space and terminate. */ /* Define an unchangeable variable with a name that no POSIX.2 makefile could validly use for its own variable. */ define_variable_cname ("-*-command-variables-*-", value, o_automatic, 0); /* Define the variable; this will not override any user definition. Normally a reference to this variable is written into the value of MAKEFLAGS, allowing the user to override this value to affect the exported value of MAKEFLAGS. In POSIX-pedantic mode, we cannot allow the user's setting of MAKEOVERRIDES to affect MAKEFLAGS, so a reference to this hidden variable is written instead. */ define_variable_cname ("MAKEOVERRIDES", "${-*-command-variables-*-}", o_env, 1); } /* If there were -C flags, move ourselves about. */ if (directories != 0) { unsigned int i; for (i = 0; directories->list[i] != 0; ++i) { const char *dir = directories->list[i]; #ifdef WINDOWS32 /* WINDOWS32 chdir() doesn't work if the directory has a trailing '/' But allow -C/ just in case someone wants that. */ { char *p = (char *)dir + strlen (dir) - 1; while (p > dir && (p[0] == '/' || p[0] == '\\')) --p; p[1] = '\0'; } #endif if (chdir (dir) < 0) pfatal_with_name (dir); } } #ifdef WINDOWS32 /* * THIS BLOCK OF CODE MUST COME AFTER chdir() CALL ABOVE IN ORDER * TO NOT CONFUSE THE DEPENDENCY CHECKING CODE IN implicit.c. * * The functions in dir.c can incorrectly cache information for "." * before we have changed directory and this can cause file * lookups to fail because the current directory (.) was pointing * at the wrong place when it was first evaluated. */ no_default_sh_exe = !find_and_set_default_shell(NULL); #endif /* WINDOWS32 */ /* Figure out the level of recursion. */ { struct variable *v = lookup_variable (STRING_SIZE_TUPLE (MAKELEVEL_NAME)); if (v != 0 && v->value[0] != '\0' && v->value[0] != '-') makelevel = (unsigned int) atoi (v->value); else makelevel = 0; } /* Except under -s, always do -w in sub-makes and under -C. */ if (!silent_flag && (directories != 0 || makelevel > 0)) print_directory_flag = 1; /* Let the user disable that with --no-print-directory. */ if (inhibit_print_directory_flag) print_directory_flag = 0; /* If -R was given, set -r too (doesn't make sense otherwise!) */ if (no_builtin_variables_flag) no_builtin_rules_flag = 1; /* Construct the list of include directories to search. */ construct_include_path (include_directories == 0 ? 0 : include_directories->list); /* Figure out where we are now, after chdir'ing. */ if (directories == 0) /* We didn't move, so we're still in the same place. */ starting_directory = current_directory; else { #ifdef WINDOWS32 if (getcwd_fs (current_directory, GET_PATH_MAX) == 0) #else if (getcwd (current_directory, GET_PATH_MAX) == 0) #endif { #ifdef HAVE_GETCWD perror_with_name ("getcwd", ""); #else error (NILF, "getwd: %s", current_directory); #endif starting_directory = 0; } else starting_directory = current_directory; } define_variable_cname ("CURDIR", current_directory, o_file, 0); /* Read any stdin makefiles into temporary files. */ if (makefiles != 0) { unsigned int i; for (i = 0; i < makefiles->idx; ++i) if (makefiles->list[i][0] == '-' && makefiles->list[i][1] == '\0') { /* This makefile is standard input. Since we may re-exec and thus re-read the makefiles, we read standard input into a temporary file and read from that. */ FILE *outfile; char *template, *tmpdir; if (stdin_nm) fatal (NILF, _("Makefile from standard input specified twice.")); #ifdef VMS # define DEFAULT_TMPDIR "sys$scratch:" #else # ifdef P_tmpdir # define DEFAULT_TMPDIR P_tmpdir # else # define DEFAULT_TMPDIR "/tmp" # endif #endif #define DEFAULT_TMPFILE "GmXXXXXX" if (((tmpdir = getenv ("TMPDIR")) == NULL || *tmpdir == '\0') #if defined (__MSDOS__) || defined (WINDOWS32) || defined (__EMX__) /* These are also used commonly on these platforms. */ && ((tmpdir = getenv ("TEMP")) == NULL || *tmpdir == '\0') && ((tmpdir = getenv ("TMP")) == NULL || *tmpdir == '\0') #endif ) tmpdir = DEFAULT_TMPDIR; template = alloca (strlen (tmpdir) + sizeof (DEFAULT_TMPFILE) + 1); strcpy (template, tmpdir); #ifdef HAVE_DOS_PATHS if (strchr ("/\\", template[strlen (template) - 1]) == NULL) strcat (template, "/"); #else # ifndef VMS if (template[strlen (template) - 1] != '/') strcat (template, "/"); # endif /* !VMS */ #endif /* !HAVE_DOS_PATHS */ strcat (template, DEFAULT_TMPFILE); outfile = open_tmpfile (&stdin_nm, template); if (outfile == 0) pfatal_with_name (_("fopen (temporary file)")); while (!feof (stdin) && ! ferror (stdin)) { char buf[2048]; unsigned int n = fread (buf, 1, sizeof (buf), stdin); if (n > 0 && fwrite (buf, 1, n, outfile) != n) pfatal_with_name (_("fwrite (temporary file)")); } fclose (outfile); /* Replace the name that read_all_makefiles will see with the name of the temporary file. */ makefiles->list[i] = strcache_add (stdin_nm); /* Make sure the temporary file will not be remade. */ { struct file *f = enter_file (strcache_add (stdin_nm)); f->updated = 1; f->update_status = 0; f->command_state = cs_finished; /* Can't be intermediate, or it'll be removed too early for make re-exec. */ f->intermediate = 0; f->dontcare = 0; } } } #ifndef __EMX__ /* Don't use a SIGCHLD handler for OS/2 */ #if defined(MAKE_JOBSERVER) || !defined(HAVE_WAIT_NOHANG) /* Set up to handle children dying. This must be done before reading in the makefiles so that `shell' function calls will work. If we don't have a hanging wait we have to fall back to old, broken functionality here and rely on the signal handler and counting children. If we're using the jobs pipe we need a signal handler so that SIGCHLD is not ignored; we need it to interrupt the read(2) of the jobserver pipe in job.c if we're waiting for a token. If none of these are true, we don't need a signal handler at all. */ { RETSIGTYPE child_handler (int sig); # if defined SIGCHLD bsd_signal (SIGCHLD, child_handler); # endif # if defined SIGCLD && SIGCLD != SIGCHLD bsd_signal (SIGCLD, child_handler); # endif } #endif #endif /* Let the user send us SIGUSR1 to toggle the -d flag during the run. */ #ifdef SIGUSR1 bsd_signal (SIGUSR1, debug_signal_handler); #endif /* Define the initial list of suffixes for old-style rules. */ set_default_suffixes (); /* Define the file rules for the built-in suffix rules. These will later be converted into pattern rules. We used to do this in install_default_implicit_rules, but since that happens after reading makefiles, it results in the built-in pattern rules taking precedence over makefile-specified suffix rules, which is wrong. */ install_default_suffix_rules (); /* Define some internal and special variables. */ define_automatic_variables (); /* Set up the MAKEFLAGS and MFLAGS variables so makefiles can look at them. */ define_makeflags (0, 0); /* Define the default variables. */ define_default_variables (); default_file = enter_file (strcache_add (".DEFAULT")); default_goal_var = define_variable_cname (".DEFAULT_GOAL", "", o_file, 0); /* Evaluate all strings provided with --eval. Also set up the $(-*-eval-flags-*-) variable. */ if (eval_strings) { char *p, *value; unsigned int i; unsigned int len = sizeof ("--eval=") * eval_strings->idx; for (i = 0; i < eval_strings->idx; ++i) { p = xstrdup (eval_strings->list[i]); len += 2 * strlen (p); eval_buffer (p); free (p); } p = value = alloca (len); for (i = 0; i < eval_strings->idx; ++i) { strcpy (p, "--eval="); p += strlen (p); p = quote_for_env (p, eval_strings->list[i]); *(p++) = ' '; } p[-1] = '\0'; define_variable_cname ("-*-eval-flags-*-", value, o_automatic, 0); } /* Read all the makefiles. */ read_makefiles = read_all_makefiles (makefiles == 0 ? 0 : makefiles->list); #ifdef WINDOWS32 /* look one last time after reading all Makefiles */ if (no_default_sh_exe) no_default_sh_exe = !find_and_set_default_shell(NULL); #endif /* WINDOWS32 */ #if defined (__MSDOS__) || defined (__EMX__) /* We need to know what kind of shell we will be using. */ { extern int _is_unixy_shell (const char *_path); struct variable *shv = lookup_variable (STRING_SIZE_TUPLE ("SHELL")); extern int unixy_shell; extern char *default_shell; if (shv && *shv->value) { char *shell_path = recursively_expand(shv); if (shell_path && _is_unixy_shell (shell_path)) unixy_shell = 1; else unixy_shell = 0; if (shell_path) default_shell = shell_path; } } #endif /* __MSDOS__ || __EMX__ */ /* Decode switches again, in case the variables were set by the makefile. */ decode_env_switches (STRING_SIZE_TUPLE ("MAKEFLAGS")); #if 0 decode_env_switches (STRING_SIZE_TUPLE ("MFLAGS")); #endif #if defined (__MSDOS__) || defined (__EMX__) if (job_slots != 1 # ifdef __EMX__ && _osmode != OS2_MODE /* turn off -j if we are in DOS mode */ # endif ) { error (NILF, _("Parallel jobs (-j) are not supported on this platform.")); error (NILF, _("Resetting to single job (-j1) mode.")); job_slots = 1; } #endif #ifdef MAKE_JOBSERVER /* If the jobserver-fds option is seen, make sure that -j is reasonable. */ if (jobserver_fds) { const char *cp; unsigned int ui; for (ui=1; ui < jobserver_fds->idx; ++ui) if (!streq (jobserver_fds->list[0], jobserver_fds->list[ui])) fatal (NILF, _("internal error: multiple --jobserver-fds options")); /* Now parse the fds string and make sure it has the proper format. */ cp = jobserver_fds->list[0]; if (sscanf (cp, "%d,%d", &job_fds[0], &job_fds[1]) != 2) fatal (NILF, _("internal error: invalid --jobserver-fds string `%s'"), cp); DB (DB_JOBS, (_("Jobserver client (fds %d,%d)\n"), job_fds[0], job_fds[1])); /* The combination of a pipe + !job_slots means we're using the jobserver. If !job_slots and we don't have a pipe, we can start infinite jobs. If we see both a pipe and job_slots >0 that means the user set -j explicitly. This is broken; in this case obey the user (ignore the jobserver pipe for this make) but print a message. */ if (job_slots > 0) error (NILF, _("warning: -jN forced in submake: disabling jobserver mode.")); /* Create a duplicate pipe, that will be closed in the SIGCHLD handler. If this fails with EBADF, the parent has closed the pipe on us because it didn't think we were a submake. If so, print a warning then default to -j1. */ else if ((job_rfd = dup (job_fds[0])) < 0) { if (errno != EBADF) pfatal_with_name (_("dup jobserver")); error (NILF, _("warning: jobserver unavailable: using -j1. Add `+' to parent make rule.")); job_slots = 1; } if (job_slots > 0) { close (job_fds[0]); close (job_fds[1]); job_fds[0] = job_fds[1] = -1; free (jobserver_fds->list); free (jobserver_fds); jobserver_fds = 0; } } /* If we have >1 slot but no jobserver-fds, then we're a top-level make. Set up the pipe and install the fds option for our children. */ if (job_slots > 1) { char *cp; char c = '+'; if (pipe (job_fds) < 0 || (job_rfd = dup (job_fds[0])) < 0) pfatal_with_name (_("creating jobs pipe")); /* Every make assumes that it always has one job it can run. For the submakes it's the token they were given by their parent. For the top make, we just subtract one from the number the user wants. We want job_slots to be 0 to indicate we're using the jobserver. */ master_job_slots = job_slots; while (--job_slots) { int r; EINTRLOOP (r, write (job_fds[1], &c, 1)); if (r != 1) pfatal_with_name (_("init jobserver pipe")); } /* Fill in the jobserver_fds struct for our children. */ cp = xmalloc ((sizeof ("1024")*2)+1); sprintf (cp, "%d,%d", job_fds[0], job_fds[1]); jobserver_fds = (struct stringlist *) xmalloc (sizeof (struct stringlist)); jobserver_fds->list = xmalloc (sizeof (char *)); jobserver_fds->list[0] = cp; jobserver_fds->idx = 1; jobserver_fds->max = 1; } #endif #ifndef MAKE_SYMLINKS if (check_symlink_flag) { error (NILF, _("Symbolic links not supported: disabling -L.")); check_symlink_flag = 0; } #endif /* Set up MAKEFLAGS and MFLAGS again, so they will be right. */ define_makeflags (1, 0); /* Make each `struct dep' point at the `struct file' for the file depended on. Also do magic for special targets. */ snap_deps (); /* Convert old-style suffix rules to pattern rules. It is important to do this before installing the built-in pattern rules below, so that makefile-specified suffix rules take precedence over built-in pattern rules. */ convert_to_pattern (); /* Install the default implicit pattern rules. This used to be done before reading the makefiles. But in that case, built-in pattern rules were in the chain before user-defined ones, so they matched first. */ install_default_implicit_rules (); /* Compute implicit rule limits. */ count_implicit_rule_limits (); /* Construct the listings of directories in VPATH lists. */ build_vpath_lists (); /* Mark files given with -o flags as very old and as having been updated already, and files given with -W flags as brand new (time-stamp as far as possible into the future). If restarts is set we'll do -W later. */ if (old_files != 0) { const char **p; for (p = old_files->list; *p != 0; ++p) { struct file *f = enter_file (*p); f->last_mtime = f->mtime_before_update = OLD_MTIME; f->updated = 1; f->update_status = 0; f->command_state = cs_finished; } } if (!restarts && new_files != 0) { const char **p; for (p = new_files->list; *p != 0; ++p) { struct file *f = enter_file (*p); f->last_mtime = f->mtime_before_update = NEW_MTIME; } } /* Initialize the remote job module. */ remote_setup (); if (read_makefiles != 0) { /* Update any makefiles if necessary. */ FILE_TIMESTAMP *makefile_mtimes = 0; unsigned int mm_idx = 0; char **nargv; int nargc; int orig_db_level = db_level; int status; if (! ISDB (DB_MAKEFILES)) db_level = DB_NONE; DB (DB_BASIC, (_("Updating makefiles....\n"))); /* Remove any makefiles we don't want to try to update. Also record the current modtimes so we can compare them later. */ { register struct dep *d, *last; last = 0; d = read_makefiles; while (d != 0) { struct file *f = d->file; if (f->double_colon) for (f = f->double_colon; f != NULL; f = f->prev) { if (f->deps == 0 && f->cmds != 0) { /* This makefile is a :: target with commands, but no dependencies. So, it will always be remade. This might well cause an infinite loop, so don't try to remake it. (This will only happen if your makefiles are written exceptionally stupidly; but if you work for Athena, that's how you write your makefiles.) */ DB (DB_VERBOSE, (_("Makefile `%s' might loop; not remaking it.\n"), f->name)); if (last == 0) read_makefiles = d->next; else last->next = d->next; /* Free the storage. */ free_dep (d); d = last == 0 ? read_makefiles : last->next; break; } } if (f == NULL || !f->double_colon) { makefile_mtimes = xrealloc (makefile_mtimes, (mm_idx+1) * sizeof (FILE_TIMESTAMP)); makefile_mtimes[mm_idx++] = file_mtime_no_search (d->file); last = d; d = d->next; } } } /* Set up `MAKEFLAGS' specially while remaking makefiles. */ define_makeflags (1, 1); rebuilding_makefiles = 1; status = update_goal_chain (read_makefiles); rebuilding_makefiles = 0; switch (status) { case 1: /* The only way this can happen is if the user specified -q and asked * for one of the makefiles to be remade as a target on the command * line. Since we're not actually updating anything with -q we can * treat this as "did nothing". */ case -1: /* Did nothing. */ break; case 2: /* Failed to update. Figure out if we care. */ { /* Nonzero if any makefile was successfully remade. */ int any_remade = 0; /* Nonzero if any makefile we care about failed in updating or could not be found at all. */ int any_failed = 0; unsigned int i; struct dep *d; for (i = 0, d = read_makefiles; d != 0; ++i, d = d->next) { /* Reset the considered flag; we may need to look at the file again to print an error. */ d->file->considered = 0; if (d->file->updated) { /* This makefile was updated. */ if (d->file->update_status == 0) { /* It was successfully updated. */ any_remade |= (file_mtime_no_search (d->file) != makefile_mtimes[i]); } else if (! (d->changed & RM_DONTCARE)) { FILE_TIMESTAMP mtime; /* The update failed and this makefile was not from the MAKEFILES variable, so we care. */ error (NILF, _("Failed to remake makefile `%s'."), d->file->name); mtime = file_mtime_no_search (d->file); any_remade |= (mtime != NONEXISTENT_MTIME && mtime != makefile_mtimes[i]); makefile_status = MAKE_FAILURE; } } else /* This makefile was not found at all. */ if (! (d->changed & RM_DONTCARE)) { /* This is a makefile we care about. See how much. */ if (d->changed & RM_INCLUDED) /* An included makefile. We don't need to die, but we do want to complain. */ error (NILF, _("Included makefile `%s' was not found."), dep_name (d)); else { /* A normal makefile. We must die later. */ error (NILF, _("Makefile `%s' was not found"), dep_name (d)); any_failed = 1; } } } /* Reset this to empty so we get the right error message below. */ read_makefiles = 0; if (any_remade) goto re_exec; if (any_failed) die (2); break; } case 0: re_exec: /* Updated successfully. Re-exec ourselves. */ remove_intermediates (0); if (print_data_base_flag) print_data_base (); log_working_directory (0); clean_jobserver (0); if (makefiles != 0) { /* These names might have changed. */ int i, j = 0; for (i = 1; i < argc; ++i) if (strneq (argv[i], "-f", 2)) /* XXX */ { if (argv[i][2] == '\0') /* This cast is OK since we never modify argv. */ argv[++i] = (char *) makefiles->list[j]; else argv[i] = xstrdup (concat (2, "-f", makefiles->list[j])); ++j; } } /* Add -o option for the stdin temporary file, if necessary. */ nargc = argc; if (stdin_nm) { nargv = xmalloc ((nargc + 2) * sizeof (char *)); memcpy (nargv, argv, argc * sizeof (char *)); nargv[nargc++] = xstrdup (concat (2, "-o", stdin_nm)); nargv[nargc] = 0; } else nargv = argv; if (directories != 0 && directories->idx > 0) { int bad = 1; if (directory_before_chdir != 0) { if (chdir (directory_before_chdir) < 0) perror_with_name ("chdir", ""); else bad = 0; } if (bad) fatal (NILF, _("Couldn't change back to original directory.")); } ++restarts; /* Reset makeflags in case they were changed. */ { const char *pv = define_makeflags (1, 1); char *p = alloca (sizeof ("MAKEFLAGS=") + strlen (pv) + 1); sprintf (p, "MAKEFLAGS=%s", pv); putenv (p); } if (ISDB (DB_BASIC)) { char **p; printf (_("Re-executing[%u]:"), restarts); for (p = nargv; *p != 0; ++p) printf (" %s", *p); putchar ('\n'); } #ifndef _AMIGA { char **p; for (p = environ; *p != 0; ++p) { if (strneq (*p, MAKELEVEL_NAME, MAKELEVEL_LENGTH) && (*p)[MAKELEVEL_LENGTH] == '=') { *p = alloca (40); sprintf (*p, "%s=%u", MAKELEVEL_NAME, makelevel); } if (strneq (*p, "MAKE_RESTARTS=", 14)) { *p = alloca (40); sprintf (*p, "MAKE_RESTARTS=%u", restarts); restarts = 0; } } } #else /* AMIGA */ { char buffer[256]; sprintf (buffer, "%u", makelevel); SetVar (MAKELEVEL_NAME, buffer, -1, GVF_GLOBAL_ONLY); sprintf (buffer, "%u", restarts); SetVar ("MAKE_RESTARTS", buffer, -1, GVF_GLOBAL_ONLY); restarts = 0; } #endif /* If we didn't set the restarts variable yet, add it. */ if (restarts) { char *b = alloca (40); sprintf (b, "MAKE_RESTARTS=%u", restarts); putenv (b); } fflush (stdout); fflush (stderr); /* Close the dup'd jobserver pipe if we opened one. */ if (job_rfd >= 0) close (job_rfd); #ifdef _AMIGA exec_command (nargv); exit (0); #elif defined (__EMX__) { /* It is not possible to use execve() here because this would cause the parent process to be terminated with exit code 0 before the child process has been terminated. Therefore it may be the best solution simply to spawn the child process including all file handles and to wait for its termination. */ int pid; int status; pid = child_execute_job (0, 1, nargv, environ); /* is this loop really necessary? */ do { pid = wait (&status); } while (pid <= 0); /* use the exit code of the child process */ exit (WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE); } #else exec_command (nargv, environ); #endif /* NOTREACHED */ default: #define BOGUS_UPDATE_STATUS 0 assert (BOGUS_UPDATE_STATUS); break; } db_level = orig_db_level; /* Free the makefile mtimes (if we allocated any). */ if (makefile_mtimes) free (makefile_mtimes); } /* Set up `MAKEFLAGS' again for the normal targets. */ define_makeflags (1, 0); /* Set always_make_flag if -B was given. */ always_make_flag = always_make_set; /* If restarts is set we haven't set up -W files yet, so do that now. */ if (restarts && new_files != 0) { const char **p; for (p = new_files->list; *p != 0; ++p) { struct file *f = enter_file (*p); f->last_mtime = f->mtime_before_update = NEW_MTIME; } } /* If there is a temp file from reading a makefile from stdin, get rid of it now. */ if (stdin_nm && unlink (stdin_nm) < 0 && errno != ENOENT) perror_with_name (_("unlink (temporary file): "), stdin_nm); /* If there were no command-line goals, use the default. */ if (goals == 0) { char *p; if (default_goal_var->recursive) p = variable_expand (default_goal_var->value); else { p = variable_buffer_output (variable_buffer, default_goal_var->value, strlen (default_goal_var->value)); *p = '\0'; p = variable_buffer; } if (*p != '\0') { struct file *f = lookup_file (p); /* If .DEFAULT_GOAL is a non-existent target, enter it into the table and let the standard logic sort it out. */ if (f == 0) { struct nameseq *ns; ns = PARSE_FILE_SEQ (&p, struct nameseq, '\0', NULL, 0); if (ns) { /* .DEFAULT_GOAL should contain one target. */ if (ns->next != 0) fatal (NILF, _(".DEFAULT_GOAL contains more than one target")); f = enter_file (strcache_add (ns->name)); ns->name = 0; /* It was reused by enter_file(). */ free_ns_chain (ns); } } if (f) { goals = alloc_dep (); goals->file = f; } } } else lastgoal->next = 0; if (!goals) { if (read_makefiles == 0) fatal (NILF, _("No targets specified and no makefile found")); fatal (NILF, _("No targets")); } /* Update the goals. */ DB (DB_BASIC, (_("Updating goal targets....\n"))); { int status; switch (update_goal_chain (goals)) { case -1: /* Nothing happened. */ case 0: /* Updated successfully. */ status = makefile_status; break; case 1: /* We are under -q and would run some commands. */ status = MAKE_TROUBLE; break; case 2: /* Updating failed. POSIX.2 specifies exit status >1 for this; but in VMS, there is only success and failure. */ status = MAKE_FAILURE; break; default: abort (); } /* If we detected some clock skew, generate one last warning */ if (clock_skew_detected) error (NILF, _("warning: Clock skew detected. Your build may be incomplete.")); /* Exit. */ die (status); } /* NOTREACHED */ return 0; } /* Parsing of arguments, decoding of switches. */ static char options[1 + sizeof (switches) / sizeof (switches[0]) * 3]; static struct option long_options[(sizeof (switches) / sizeof (switches[0])) + (sizeof (long_option_aliases) / sizeof (long_option_aliases[0]))]; /* Fill in the string and vector for getopt. */ static void init_switches (void) { char *p; unsigned int c; unsigned int i; if (options[0] != '\0') /* Already done. */ return; p = options; /* Return switch and non-switch args in order, regardless of POSIXLY_CORRECT. Non-switch args are returned as option 1. */ *p++ = '-'; for (i = 0; switches[i].c != '\0'; ++i) { long_options[i].name = (switches[i].long_name == 0 ? "" : switches[i].long_name); long_options[i].flag = 0; long_options[i].val = switches[i].c; if (short_option (switches[i].c)) *p++ = switches[i].c; switch (switches[i].type) { case flag: case flag_off: case ignore: long_options[i].has_arg = no_argument; break; case string: case filename: case positive_int: case floating: if (short_option (switches[i].c)) *p++ = ':'; if (switches[i].noarg_value != 0) { if (short_option (switches[i].c)) *p++ = ':'; long_options[i].has_arg = optional_argument; } else long_options[i].has_arg = required_argument; break; } } *p = '\0'; for (c = 0; c < (sizeof (long_option_aliases) / sizeof (long_option_aliases[0])); ++c) long_options[i++] = long_option_aliases[c]; long_options[i].name = 0; } static void handle_non_switch_argument (char *arg, int env) { /* Non-option argument. It might be a variable definition. */ struct variable *v; if (arg[0] == '-' && arg[1] == '\0') /* Ignore plain `-' for compatibility. */ return; v = try_variable_definition (0, arg, o_command, 0); if (v != 0) { /* It is indeed a variable definition. If we don't already have this one, record a pointer to the variable for later use in define_makeflags. */ struct command_variable *cv; for (cv = command_variables; cv != 0; cv = cv->next) if (cv->variable == v) break; if (! cv) { cv = xmalloc (sizeof (*cv)); cv->variable = v; cv->next = command_variables; command_variables = cv; } } else if (! env) { /* Not an option or variable definition; it must be a goal target! Enter it as a file and add it to the dep chain of goals. */ struct file *f = enter_file (strcache_add (expand_command_line_file (arg))); f->cmd_target = 1; if (goals == 0) { goals = alloc_dep (); lastgoal = goals; } else { lastgoal->next = alloc_dep (); lastgoal = lastgoal->next; } lastgoal->file = f; { /* Add this target name to the MAKECMDGOALS variable. */ struct variable *gv; const char *value; gv = lookup_variable (STRING_SIZE_TUPLE ("MAKECMDGOALS")); if (gv == 0) value = f->name; else { /* Paste the old and new values together */ unsigned int oldlen, newlen; char *vp; oldlen = strlen (gv->value); newlen = strlen (f->name); vp = alloca (oldlen + 1 + newlen + 1); memcpy (vp, gv->value, oldlen); vp[oldlen] = ' '; memcpy (&vp[oldlen + 1], f->name, newlen + 1); value = vp; } define_variable_cname ("MAKECMDGOALS", value, o_default, 0); } } } /* Print a nice usage method. */ static void print_usage (int bad) { const char *const *cpp; FILE *usageto; if (print_version_flag) print_version (); usageto = bad ? stderr : stdout; fprintf (usageto, _("Usage: %s [options] [target] ...\n"), program); for (cpp = usage; *cpp; ++cpp) fputs (_(*cpp), usageto); if (!remote_description || *remote_description == '\0') fprintf (usageto, _("\nThis program built for %s\n"), make_host); else fprintf (usageto, _("\nThis program built for %s (%s)\n"), make_host, remote_description); fprintf (usageto, _("Report bugs to <bug-make@gnu.org>\n")); } /* Decode switches from ARGC and ARGV. They came from the environment if ENV is nonzero. */ static void decode_switches (int argc, char **argv, int env) { int bad = 0; register const struct command_switch *cs; register struct stringlist *sl; register int c; /* getopt does most of the parsing for us. First, get its vectors set up. */ init_switches (); /* Let getopt produce error messages for the command line, but not for options from the environment. */ opterr = !env; /* Reset getopt's state. */ optind = 0; while (optind < argc) { /* Parse the next argument. */ c = getopt_long (argc, argv, options, long_options, (int *) 0); if (c == EOF) /* End of arguments, or "--" marker seen. */ break; else if (c == 1) /* An argument not starting with a dash. */ handle_non_switch_argument (optarg, env); else if (c == '?') /* Bad option. We will print a usage message and die later. But continue to parse the other options so the user can see all he did wrong. */ bad = 1; else for (cs = switches; cs->c != '\0'; ++cs) if (cs->c == c) { /* Whether or not we will actually do anything with this switch. We test this individually inside the switch below rather than just once outside it, so that options which are to be ignored still consume args. */ int doit = !env || cs->env; switch (cs->type) { default: abort (); case ignore: break; case flag: case flag_off: if (doit) *(int *) cs->value_ptr = cs->type == flag; break; case string: case filename: if (!doit) break; if (optarg == 0) optarg = xstrdup (cs->noarg_value); else if (*optarg == '\0') { char opt[2] = "c"; const char *op = opt; if (short_option (cs->c)) opt[0] = cs->c; else op = cs->long_name; error (NILF, _("the `%s%s' option requires a non-empty string argument"), short_option (cs->c) ? "-" : "--", op); bad = 1; } sl = *(struct stringlist **) cs->value_ptr; if (sl == 0) { sl = (struct stringlist *) xmalloc (sizeof (struct stringlist)); sl->max = 5; sl->idx = 0; sl->list = xmalloc (5 * sizeof (char *)); *(struct stringlist **) cs->value_ptr = sl; } else if (sl->idx == sl->max - 1) { sl->max += 5; /* MSVC erroneously warns without a cast here. */ sl->list = xrealloc ((void *)sl->list, sl->max * sizeof (char *)); } if (cs->type == filename) sl->list[sl->idx++] = expand_command_line_file (optarg); else sl->list[sl->idx++] = optarg; sl->list[sl->idx] = 0; break; case positive_int: /* See if we have an option argument; if we do require that it's all digits, not something like "10foo". */ if (optarg == 0 && argc > optind) { const char *cp; for (cp=argv[optind]; ISDIGIT (cp[0]); ++cp) ; if (cp[0] == '\0') optarg = argv[optind++]; } if (!doit) break; if (optarg != 0) { int i = atoi (optarg); const char *cp; /* Yes, I realize we're repeating this in some cases. */ for (cp = optarg; ISDIGIT (cp[0]); ++cp) ; if (i < 1 || cp[0] != '\0') { error (NILF, _("the `-%c' option requires a positive integral argument"), cs->c); bad = 1; } else *(unsigned int *) cs->value_ptr = i; } else *(unsigned int *) cs->value_ptr = *(unsigned int *) cs->noarg_value; break; #ifndef NO_FLOAT case floating: if (optarg == 0 && optind < argc && (ISDIGIT (argv[optind][0]) || argv[optind][0] == '.')) optarg = argv[optind++]; if (doit) *(double *) cs->value_ptr = (optarg != 0 ? atof (optarg) : *(double *) cs->noarg_value); break; #endif } /* We've found the switch. Stop looking. */ break; } } /* There are no more options according to getting getopt, but there may be some arguments left. Since we have asked for non-option arguments to be returned in order, this only happens when there is a "--" argument to prevent later arguments from being options. */ while (optind < argc) handle_non_switch_argument (argv[optind++], env); if (!env && (bad || print_usage_flag)) { print_usage (bad); die (bad ? 2 : 0); } } /* Decode switches from environment variable ENVAR (which is LEN chars long). We do this by chopping the value into a vector of words, prepending a dash to the first word if it lacks one, and passing the vector to decode_switches. */ static void decode_env_switches (char *envar, unsigned int len) { char *varref = alloca (2 + len + 2); char *value, *p; int argc; char **argv; /* Get the variable's value. */ varref[0] = '$'; varref[1] = '('; memcpy (&varref[2], envar, len); varref[2 + len] = ')'; varref[2 + len + 1] = '\0'; value = variable_expand (varref); /* Skip whitespace, and check for an empty value. */ value = next_token (value); len = strlen (value); if (len == 0) return; /* Allocate a vector that is definitely big enough. */ argv = alloca ((1 + len + 1) * sizeof (char *)); /* Allocate a buffer to copy the value into while we split it into words and unquote it. We must use permanent storage for this because decode_switches may store pointers into the passed argument words. */ p = xmalloc (2 * len); /* getopt will look at the arguments starting at ARGV[1]. Prepend a spacer word. */ argv[0] = 0; argc = 1; argv[argc] = p; while (*value != '\0') { if (*value == '\\' && value[1] != '\0') ++value; /* Skip the backslash. */ else if (isblank ((unsigned char)*value)) { /* End of the word. */ *p++ = '\0'; argv[++argc] = p; do ++value; while (isblank ((unsigned char)*value)); continue; } *p++ = *value++; } *p = '\0'; argv[++argc] = 0; if (argv[1][0] != '-' && strchr (argv[1], '=') == 0) /* The first word doesn't start with a dash and isn't a variable definition. Add a dash and pass it along to decode_switches. We need permanent storage for this in case decode_switches saves pointers into the value. */ argv[1] = xstrdup (concat (2, "-", argv[1])); /* Parse those words. */ decode_switches (argc, argv, 1); } /* Quote the string IN so that it will be interpreted as a single word with no magic by decode_env_switches; also double dollar signs to avoid variable expansion in make itself. Write the result into OUT, returning the address of the next character to be written. Allocating space for OUT twice the length of IN is always sufficient. */ static char * quote_for_env (char *out, const char *in) { while (*in != '\0') { if (*in == '$') *out++ = '$'; else if (isblank ((unsigned char)*in) || *in == '\\') *out++ = '\\'; *out++ = *in++; } return out; } /* Define the MAKEFLAGS and MFLAGS variables to reflect the settings of the command switches. Include options with args if ALL is nonzero. Don't include options with the `no_makefile' flag set if MAKEFILE. */ static const char * define_makeflags (int all, int makefile) { const char ref[] = "$(MAKEOVERRIDES)"; const char posixref[] = "$(-*-command-variables-*-)"; const char evalref[] = "$(-*-eval-flags-*-)"; const struct command_switch *cs; char *flagstring; register char *p; unsigned int words; struct variable *v; /* We will construct a linked list of `struct flag's describing all the flags which need to go in MAKEFLAGS. Then, once we know how many there are and their lengths, we can put them all together in a string. */ struct flag { struct flag *next; const struct command_switch *cs; const char *arg; }; struct flag *flags = 0; unsigned int flagslen = 0; #define ADD_FLAG(ARG, LEN) \ do { \ struct flag *new = alloca (sizeof (struct flag)); \ new->cs = cs; \ new->arg = (ARG); \ new->next = flags; \ flags = new; \ if (new->arg == 0) \ ++flagslen; /* Just a single flag letter. */ \ else \ /* " -x foo", plus space to expand "foo". */ \ flagslen += 1 + 1 + 1 + 1 + (3 * (LEN)); \ if (!short_option (cs->c)) \ /* This switch has no single-letter version, so we use the long. */ \ flagslen += 2 + strlen (cs->long_name); \ } while (0) for (cs = switches; cs->c != '\0'; ++cs) if (cs->toenv && (!makefile || !cs->no_makefile)) switch (cs->type) { case ignore: break; case flag: case flag_off: if (!*(int *) cs->value_ptr == (cs->type == flag_off) && (cs->default_value == 0 || *(int *) cs->value_ptr != *(int *) cs->default_value)) ADD_FLAG (0, 0); break; case positive_int: if (all) { if ((cs->default_value != 0 && (*(unsigned int *) cs->value_ptr == *(unsigned int *) cs->default_value))) break; else if (cs->noarg_value != 0 && (*(unsigned int *) cs->value_ptr == *(unsigned int *) cs->noarg_value)) ADD_FLAG ("", 0); /* Optional value omitted; see below. */ else if (cs->c == 'j') /* Special case for `-j'. */ ADD_FLAG ("1", 1); else { char *buf = alloca (30); sprintf (buf, "%u", *(unsigned int *) cs->value_ptr); ADD_FLAG (buf, strlen (buf)); } } break; #ifndef NO_FLOAT case floating: if (all) { if (cs->default_value != 0 && (*(double *) cs->value_ptr == *(double *) cs->default_value)) break; else if (cs->noarg_value != 0 && (*(double *) cs->value_ptr == *(double *) cs->noarg_value)) ADD_FLAG ("", 0); /* Optional value omitted; see below. */ else { char *buf = alloca (100); sprintf (buf, "%g", *(double *) cs->value_ptr); ADD_FLAG (buf, strlen (buf)); } } break; #endif case filename: case string: if (all) { struct stringlist *sl = *(struct stringlist **) cs->value_ptr; if (sl != 0) { /* Add the elements in reverse order, because all the flags get reversed below; and the order matters for some switches (like -I). */ unsigned int i = sl->idx; while (i-- > 0) ADD_FLAG (sl->list[i], strlen (sl->list[i])); } } break; default: abort (); } /* Four more for the possible " -- ". */ flagslen += 4 + sizeof (posixref) + sizeof (evalref); #undef ADD_FLAG /* Construct the value in FLAGSTRING. We allocate enough space for a preceding dash and trailing null. */ flagstring = alloca (1 + flagslen + 1); memset (flagstring, '\0', 1 + flagslen + 1); p = flagstring; words = 1; *p++ = '-'; while (flags != 0) { /* Add the flag letter or name to the string. */ if (short_option (flags->cs->c)) *p++ = flags->cs->c; else { if (*p != '-') { *p++ = ' '; *p++ = '-'; } *p++ = '-'; strcpy (p, flags->cs->long_name); p += strlen (p); } if (flags->arg != 0) { /* A flag that takes an optional argument which in this case is omitted is specified by ARG being "". We must distinguish because a following flag appended without an intervening " -" is considered the arg for the first. */ if (flags->arg[0] != '\0') { /* Add its argument too. */ *p++ = !short_option (flags->cs->c) ? '=' : ' '; p = quote_for_env (p, flags->arg); } ++words; /* Write a following space and dash, for the next flag. */ *p++ = ' '; *p++ = '-'; } else if (!short_option (flags->cs->c)) { ++words; /* Long options must each go in their own word, so we write the following space and dash. */ *p++ = ' '; *p++ = '-'; } flags = flags->next; } /* Define MFLAGS before appending variable definitions. */ if (p == &flagstring[1]) /* No flags. */ flagstring[0] = '\0'; else if (p[-1] == '-') { /* Kill the final space and dash. */ p -= 2; *p = '\0'; } else /* Terminate the string. */ *p = '\0'; /* Since MFLAGS is not parsed for flags, there is no reason to override any makefile redefinition. */ define_variable_cname ("MFLAGS", flagstring, o_env, 1); /* Write a reference to -*-eval-flags-*-, which contains all the --eval flag options. */ if (eval_strings) { if (p == &flagstring[1]) /* No flags written, so elide the leading dash already written. */ p = flagstring; else *p++ = ' '; memcpy (p, evalref, sizeof (evalref) - 1); p += sizeof (evalref) - 1; } if (all && command_variables != 0) { /* Now write a reference to $(MAKEOVERRIDES), which contains all the command-line variable definitions. */ if (p == &flagstring[1]) /* No flags written, so elide the leading dash already written. */ p = flagstring; else { /* Separate the variables from the switches with a "--" arg. */ if (p[-1] != '-') { /* We did not already write a trailing " -". */ *p++ = ' '; *p++ = '-'; } /* There is a trailing " -"; fill it out to " -- ". */ *p++ = '-'; *p++ = ' '; } /* Copy in the string. */ if (posix_pedantic) { memcpy (p, posixref, sizeof (posixref) - 1); p += sizeof (posixref) - 1; } else { memcpy (p, ref, sizeof (ref) - 1); p += sizeof (ref) - 1; } } else if (p == &flagstring[1]) { words = 0; --p; } else if (p[-1] == '-') /* Kill the final space and dash. */ p -= 2; /* Terminate the string. */ *p = '\0'; /* If there are switches, omit the leading dash unless it is a single long option with two leading dashes. */ if (flagstring[0] == '-' && flagstring[1] != '-') ++flagstring; v = define_variable_cname ("MAKEFLAGS", flagstring, /* This used to use o_env, but that lost when a makefile defined MAKEFLAGS. Makefiles set MAKEFLAGS to add switches, but we still want to redefine its value with the full set of switches. Of course, an override or command definition will still take precedence. */ o_file, 1); if (! all) /* The first time we are called, set MAKEFLAGS to always be exported. We should not do this again on the second call, because that is after reading makefiles which might have done `unexport MAKEFLAGS'. */ v->export = v_export; return v->value; } /* Print version information. */ static void print_version (void) { static int printed_version = 0; char *precede = print_data_base_flag ? "# " : ""; if (printed_version) /* Do it only once. */ return; printf ("%sGNU Make %s\n", precede, version_string); if (!remote_description || *remote_description == '\0') printf (_("%sBuilt for %s\n"), precede, make_host); else printf (_("%sBuilt for %s (%s)\n"), precede, make_host, remote_description); /* Print this untranslated. The coding standards recommend translating the (C) to the copyright symbol, but this string is going to change every year, and none of the rest of it should be translated (including the word "Copyright", so it hardly seems worth it. */ printf ("%sCopyright (C) 2010 Free Software Foundation, Inc.\n", precede); printf (_("%sLicense GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\ %sThis is free software: you are free to change and redistribute it.\n\ %sThere is NO WARRANTY, to the extent permitted by law.\n"), precede, precede, precede); printed_version = 1; /* Flush stdout so the user doesn't have to wait to see the version information while things are thought about. */ fflush (stdout); } /* Print a bunch of information about this and that. */ static void print_data_base () { time_t when; when = time ((time_t *) 0); printf (_("\n# Make data base, printed on %s"), ctime (&when)); print_variable_data_base (); print_dir_data_base (); print_rule_data_base (); print_file_data_base (); print_vpath_data_base (); strcache_print_stats ("#"); when = time ((time_t *) 0); printf (_("\n# Finished Make data base on %s\n"), ctime (&when)); } static void clean_jobserver (int status) { char token = '+'; /* Sanity: have we written all our jobserver tokens back? If our exit status is 2 that means some kind of syntax error; we might not have written all our tokens so do that now. If tokens are left after any other error code, that's bad. */ if (job_fds[0] != -1 && jobserver_tokens) { if (status != 2) error (NILF, "INTERNAL: Exiting with %u jobserver tokens (should be 0)!", jobserver_tokens); else while (jobserver_tokens--) { int r; EINTRLOOP (r, write (job_fds[1], &token, 1)); if (r != 1) perror_with_name ("write", ""); } } /* Sanity: If we're the master, were all the tokens written back? */ if (master_job_slots) { /* We didn't write one for ourself, so start at 1. */ unsigned int tcnt = 1; /* Close the write side, so the read() won't hang. */ close (job_fds[1]); while (read (job_fds[0], &token, 1) == 1) ++tcnt; if (tcnt != master_job_slots) error (NILF, "INTERNAL: Exiting with %u jobserver tokens available; should be %u!", tcnt, master_job_slots); close (job_fds[0]); /* Clean out jobserver_fds so we don't pass this information to any sub-makes. Also reset job_slots since it will be put on the command line, not in MAKEFLAGS. */ job_slots = default_job_slots; if (jobserver_fds) { /* MSVC erroneously warns without a cast here. */ free ((void *)jobserver_fds->list); free (jobserver_fds); jobserver_fds = 0; } } } /* Exit with STATUS, cleaning up as necessary. */ void die (int status) { static char dying = 0; if (!dying) { int err; dying = 1; if (print_version_flag) print_version (); /* Wait for children to die. */ err = (status != 0); while (job_slots_used > 0) reap_children (1, err); /* Let the remote job module clean up its state. */ remote_cleanup (); /* Remove the intermediate files. */ remove_intermediates (0); if (print_data_base_flag) print_data_base (); verify_file_data_base (); clean_jobserver (status); /* Try to move back to the original directory. This is essential on MS-DOS (where there is really only one process), and on Unix it puts core files in the original directory instead of the -C directory. Must wait until after remove_intermediates(), or unlinks of relative pathnames fail. */ if (directory_before_chdir != 0) { /* If it fails we don't care: shut up GCC. */ int _x; _x = chdir (directory_before_chdir); } log_working_directory (0); } exit (status); } /* Write a message indicating that we've just entered or left (according to ENTERING) the current directory. */ void log_working_directory (int entering) { static int entered = 0; /* Print nothing without the flag. Don't print the entering message again if we already have. Don't print the leaving message if we haven't printed the entering message. */ if (! print_directory_flag || entering == entered) return; entered = entering; if (print_data_base_flag) fputs ("# ", stdout); /* Use entire sentences to give the translators a fighting chance. */ if (makelevel == 0) if (starting_directory == 0) if (entering) printf (_("%s: Entering an unknown directory\n"), program); else printf (_("%s: Leaving an unknown directory\n"), program); else if (entering) printf (_("%s: Entering directory `%s'\n"), program, starting_directory); else printf (_("%s: Leaving directory `%s'\n"), program, starting_directory); else if (starting_directory == 0) if (entering) printf (_("%s[%u]: Entering an unknown directory\n"), program, makelevel); else printf (_("%s[%u]: Leaving an unknown directory\n"), program, makelevel); else if (entering) printf (_("%s[%u]: Entering directory `%s'\n"), program, makelevel, starting_directory); else printf (_("%s[%u]: Leaving directory `%s'\n"), program, makelevel, starting_directory); /* Flush stdout to be sure this comes before any stderr output. */ fflush (stdout); }
/* Declarations for getopt. Copyright (C) 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. Bugs can be reported to bug-glibc@gnu.org. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #ifndef _GETOPT_H #define _GETOPT_H 1 #ifdef __cplusplus extern "C" { #endif /* For communication from `getopt' to the caller. When `getopt' finds an option that takes an argument, the argument value is returned here. Also, when `ordering' is RETURN_IN_ORDER, each non-option ARGV-element is returned here. */ extern char *optarg; /* Index in ARGV of the next element to be scanned. This is used for communication to and from the caller and for communication between successive calls to `getopt'. On entry to `getopt', zero means this is the first call; initialize. When `getopt' returns -1, this is the index of the first of the non-option elements that the caller should itself scan. Otherwise, `optind' communicates from one call to the next how much of ARGV has been scanned so far. */ extern int optind; /* Callers store zero here to inhibit the error message `getopt' prints for unrecognized options. */ extern int opterr; /* Set to an option character which was unrecognized. */ extern int optopt; /* Describe the long-named options requested by the application. The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector of `struct option' terminated by an element containing a name which is zero. The field `has_arg' is: no_argument (or 0) if the option does not take an argument, required_argument (or 1) if the option requires an argument, optional_argument (or 2) if the option takes an optional argument. If the field `flag' is not NULL, it points to a variable that is set to the value given in the field `val' when the option is found, but left unchanged if the option is not found. To have a long-named option do something other than set an `int' to a compiled-in constant, such as set a value from `optarg', set the option's `flag' field to zero and its `val' field to a nonzero value (the equivalent single-letter option character, if there is one). For long options that have a zero `flag' field, `getopt' returns the contents of the `val' field. */ struct option { #if defined (__STDC__) && __STDC__ const char *name; #else char *name; #endif /* has_arg can't be an enum because some compilers complain about type mismatches in all the code that assumes it is an int. */ int has_arg; int *flag; int val; }; /* Names for the values of the `has_arg' field of `struct option'. */ #define no_argument 0 #define required_argument 1 #define optional_argument 2 #if defined (__STDC__) && __STDC__ #ifdef __GNU_LIBRARY__ /* Many other libraries have conflicting prototypes for getopt, with differences in the consts, in stdlib.h. To avoid compilation errors, only prototype getopt for the GNU C library. */ extern int getopt (int argc, char *const *argv, const char *shortopts); #else /* not __GNU_LIBRARY__ */ extern int getopt (); #endif /* __GNU_LIBRARY__ */ extern int getopt_long (int argc, char *const *argv, const char *shortopts, const struct option *longopts, int *longind); extern int getopt_long_only (int argc, char *const *argv, const char *shortopts, const struct option *longopts, int *longind); /* Internal only. Users should not call this directly. */ extern int _getopt_internal (int argc, char *const *argv, const char *shortopts, const struct option *longopts, int *longind, int long_only); #else /* not __STDC__ */ extern int getopt (); extern int getopt_long (); extern int getopt_long_only (); extern int _getopt_internal (); #endif /* __STDC__ */ #ifdef __cplusplus } #endif #endif /* getopt.h */
/* Miscellaneous generic support functions for GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" #include "dep.h" #include "debug.h" /* Variadic functions. We go through contortions to allow proper function prototypes for both ANSI and pre-ANSI C compilers, and also for those which support stdarg.h vs. varargs.h, and finally those which have vfprintf(), etc. and those who have _doprnt... or nothing. This fancy stuff all came from GNU fileutils, except for the VA_PRINTF and VA_END macros used here since we have multiple print functions. */ #if USE_VARIADIC # if HAVE_STDARG_H # include <stdarg.h> # define VA_START(args, lastarg) va_start(args, lastarg) # else # include <varargs.h> # define VA_START(args, lastarg) va_start(args) # endif # if HAVE_VPRINTF # define VA_PRINTF(fp, lastarg, args) vfprintf((fp), (lastarg), (args)) # else # define VA_PRINTF(fp, lastarg, args) _doprnt((lastarg), (args), (fp)) # endif # define VA_END(args) va_end(args) #else /* We can't use any variadic interface! */ # define va_alist a1, a2, a3, a4, a5, a6, a7, a8 # define va_dcl char *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8; # define VA_START(args, lastarg) # define VA_PRINTF(fp, lastarg, args) fprintf((fp), (lastarg), va_alist) # define VA_END(args) #endif /* Compare strings *S1 and *S2. Return negative if the first is less, positive if it is greater, zero if they are equal. */ int alpha_compare (const void *v1, const void *v2) { const char *s1 = *((char **)v1); const char *s2 = *((char **)v2); if (*s1 != *s2) return *s1 - *s2; return strcmp (s1, s2); } /* Discard each backslash-newline combination from LINE. Backslash-backslash-newline combinations become backslash-newlines. This is done by copying the text at LINE into itself. */ void collapse_continuations (char *line) { register char *in, *out, *p; register int backslash; register unsigned int bs_write; in = strchr (line, '\n'); if (in == 0) return; out = in; while (out > line && out[-1] == '\\') --out; while (*in != '\0') { /* BS_WRITE gets the number of quoted backslashes at the end just before IN, and BACKSLASH gets nonzero if the next character is quoted. */ backslash = 0; bs_write = 0; for (p = in - 1; p >= line && *p == '\\'; --p) { if (backslash) ++bs_write; backslash = !backslash; /* It should be impossible to go back this far without exiting, but if we do, we can't get the right answer. */ if (in == out - 1) abort (); } /* Output the appropriate number of backslashes. */ while (bs_write-- > 0) *out++ = '\\'; /* Skip the newline. */ ++in; /* If the newline is escaped, discard following whitespace leaving just one space. POSIX requires that each backslash/newline/following whitespace sequence be reduced to a single space. */ if (backslash) { in = next_token (in); /* Removing this loop will fix Savannah bug #16670: do we want to? */ while (out > line && isblank ((unsigned char)out[-1])) --out; *out++ = ' '; } else /* If the newline isn't quoted, put it in the output. */ *out++ = '\n'; /* Now copy the following line to the output. Stop when we find backslashes followed by a newline. */ while (*in != '\0') if (*in == '\\') { p = in + 1; while (*p == '\\') ++p; if (*p == '\n') { in = p; break; } while (in < p) *out++ = *in++; } else *out++ = *in++; } *out = '\0'; } /* Print N spaces (used in debug for target-depth). */ void print_spaces (unsigned int n) { while (n-- > 0) putchar (' '); } /* Return a string whose contents concatenate the NUM strings provided This string lives in static, re-used memory. */ const char * #if HAVE_ANSI_COMPILER && USE_VARIADIC && HAVE_STDARG_H concat (unsigned int num, ...) #else concat (num, va_alist) unsigned int num; va_dcl #endif { static unsigned int rlen = 0; static char *result = NULL; int ri = 0; #if USE_VARIADIC va_list args; #endif VA_START (args, num); while (num-- > 0) { const char *s = va_arg (args, const char *); unsigned int l = s ? strlen (s) : 0; if (l == 0) continue; if (ri + l > rlen) { rlen = ((rlen ? rlen : 60) + l) * 2; result = xrealloc (result, rlen); } memcpy (result + ri, s, l); ri += l; } VA_END (args); /* Get some more memory if we don't have enough space for the terminating '\0'. */ if (ri == rlen) { rlen = (rlen ? rlen : 60) * 2; result = xrealloc (result, rlen); } result[ri] = '\0'; return result; } /* Print a message on stdout. */ void #if HAVE_ANSI_COMPILER && USE_VARIADIC && HAVE_STDARG_H message (int prefix, const char *fmt, ...) #else message (prefix, fmt, va_alist) int prefix; const char *fmt; va_dcl #endif { #if USE_VARIADIC va_list args; #endif log_working_directory (1); if (fmt != 0) { if (prefix) { if (makelevel == 0) printf ("%s: ", program); else printf ("%s[%u]: ", program, makelevel); } VA_START (args, fmt); VA_PRINTF (stdout, fmt, args); VA_END (args); putchar ('\n'); } fflush (stdout); } /* Print an error message. */ void #if HAVE_ANSI_COMPILER && USE_VARIADIC && HAVE_STDARG_H error (const struct floc *flocp, const char *fmt, ...) #else error (flocp, fmt, va_alist) const struct floc *flocp; const char *fmt; va_dcl #endif { #if USE_VARIADIC va_list args; #endif log_working_directory (1); if (flocp && flocp->filenm) fprintf (stderr, "%s:%lu: ", flocp->filenm, flocp->lineno); else if (makelevel == 0) fprintf (stderr, "%s: ", program); else fprintf (stderr, "%s[%u]: ", program, makelevel); VA_START(args, fmt); VA_PRINTF (stderr, fmt, args); VA_END (args); putc ('\n', stderr); fflush (stderr); } /* Print an error message and exit. */ void #if HAVE_ANSI_COMPILER && USE_VARIADIC && HAVE_STDARG_H fatal (const struct floc *flocp, const char *fmt, ...) #else fatal (flocp, fmt, va_alist) const struct floc *flocp; const char *fmt; va_dcl #endif { #if USE_VARIADIC va_list args; #endif log_working_directory (1); if (flocp && flocp->filenm) fprintf (stderr, "%s:%lu: *** ", flocp->filenm, flocp->lineno); else if (makelevel == 0) fprintf (stderr, "%s: *** ", program); else fprintf (stderr, "%s[%u]: *** ", program, makelevel); VA_START(args, fmt); VA_PRINTF (stderr, fmt, args); VA_END (args); fputs (_(". Stop.\n"), stderr); die (2); } #ifndef HAVE_STRERROR #undef strerror char * strerror (int errnum) { extern int errno, sys_nerr; #ifndef __DECC extern char *sys_errlist[]; #endif static char buf[] = "Unknown error 12345678901234567890"; if (errno < sys_nerr) return sys_errlist[errnum]; sprintf (buf, _("Unknown error %d"), errnum); return buf; } #endif /* Print an error message from errno. */ void perror_with_name (const char *str, const char *name) { error (NILF, _("%s%s: %s"), str, name, strerror (errno)); } /* Print an error message from errno and exit. */ void pfatal_with_name (const char *name) { fatal (NILF, _("%s: %s"), name, strerror (errno)); /* NOTREACHED */ } /* Like malloc but get fatal error if memory is exhausted. */ /* Don't bother if we're using dmalloc; it provides these for us. */ #ifndef HAVE_DMALLOC_H #undef xmalloc #undef xcalloc #undef xrealloc #undef xstrdup void * xmalloc (unsigned int size) { /* Make sure we don't allocate 0, for pre-ISO implementations. */ void *result = malloc (size ? size : 1); if (result == 0) fatal (NILF, _("virtual memory exhausted")); return result; } void * xcalloc (unsigned int size) { /* Make sure we don't allocate 0, for pre-ISO implementations. */ void *result = calloc (size ? size : 1, 1); if (result == 0) fatal (NILF, _("virtual memory exhausted")); return result; } void * xrealloc (void *ptr, unsigned int size) { void *result; /* Some older implementations of realloc() don't conform to ISO. */ if (! size) size = 1; result = ptr ? realloc (ptr, size) : malloc (size); if (result == 0) fatal (NILF, _("virtual memory exhausted")); return result; } char * xstrdup (const char *ptr) { char *result; #ifdef HAVE_STRDUP result = strdup (ptr); #else result = malloc (strlen (ptr) + 1); #endif if (result == 0) fatal (NILF, _("virtual memory exhausted")); #ifdef HAVE_STRDUP return result; #else return strcpy (result, ptr); #endif } #endif /* HAVE_DMALLOC_H */ char * xstrndup (const char *str, unsigned int length) { char *result; #ifdef HAVE_STRNDUP result = strndup (str, length); if (result == 0) fatal (NILF, _("virtual memory exhausted")); #else result = xmalloc (length + 1); if (length > 0) strncpy (result, str, length); result[length] = '\0'; #endif return result; } /* Limited INDEX: Search through the string STRING, which ends at LIMIT, for the character C. Returns a pointer to the first occurrence, or nil if none is found. Like INDEX except that the string searched ends where specified instead of at the first null. */ char * lindex (const char *s, const char *limit, int c) { while (s < limit) if (*s++ == c) return (char *)(s - 1); return 0; } /* Return the address of the first whitespace or null in the string S. */ char * end_of_token (const char *s) { while (*s != '\0' && !isblank ((unsigned char)*s)) ++s; return (char *)s; } #ifdef WINDOWS32 /* * Same as end_of_token, but take into account a stop character */ char * end_of_token_w32 (const char *s, char stopchar) { const char *p = s; int backslash = 0; while (*p != '\0' && *p != stopchar && (backslash || !isblank ((unsigned char)*p))) { if (*p++ == '\\') { backslash = !backslash; while (*p == '\\') { backslash = !backslash; ++p; } } else backslash = 0; } return (char *)p; } #endif /* Return the address of the first nonwhitespace or null in the string S. */ char * next_token (const char *s) { while (isblank ((unsigned char)*s)) ++s; return (char *)s; } /* Find the next token in PTR; return the address of it, and store the length of the token into *LENGTHPTR if LENGTHPTR is not nil. Set *PTR to the end of the token, so this function can be called repeatedly in a loop. */ char * find_next_token (const char **ptr, unsigned int *lengthptr) { const char *p = next_token (*ptr); if (*p == '\0') return 0; *ptr = end_of_token (p); if (lengthptr != 0) *lengthptr = *ptr - p; return (char *)p; } /* Copy a chain of `struct dep'. For 2nd expansion deps, dup the name. */ struct dep * copy_dep_chain (const struct dep *d) { struct dep *firstnew = 0; struct dep *lastnew = 0; while (d != 0) { struct dep *c = xmalloc (sizeof (struct dep)); memcpy (c, d, sizeof (struct dep)); if (c->need_2nd_expansion) c->name = xstrdup (c->name); c->next = 0; if (firstnew == 0) firstnew = lastnew = c; else lastnew = lastnew->next = c; d = d->next; } return firstnew; } /* Free a chain of 'struct dep'. */ void free_dep_chain (struct dep *d) { while (d != 0) { struct dep *df = d; d = d->next; free_dep (df); } } /* Free a chain of struct nameseq. For struct dep chains use free_dep_chain. */ void free_ns_chain (struct nameseq *ns) { while (ns != 0) { struct nameseq *t = ns; ns = ns->next; free (t); } } #if !HAVE_STRCASECMP && !HAVE_STRICMP && !HAVE_STRCMPI /* If we don't have strcasecmp() (from POSIX), or anything that can substitute for it, define our own version. */ int strcasecmp (const char *s1, const char *s2) { while (1) { int c1 = (int) *(s1++); int c2 = (int) *(s2++); if (isalpha (c1)) c1 = tolower (c1); if (isalpha (c2)) c2 = tolower (c2); if (c1 != '\0' && c1 == c2) continue; return (c1 - c2); } } #endif #if !HAVE_STRNCASECMP && !HAVE_STRNICMP && !HAVE_STRNCMPI /* If we don't have strncasecmp() (from POSIX), or anything that can substitute for it, define our own version. */ int strncasecmp (const char *s1, const char *s2, int n) { while (n-- > 0) { int c1 = (int) *(s1++); int c2 = (int) *(s2++); if (isalpha (c1)) c1 = tolower (c1); if (isalpha (c2)) c2 = tolower (c2); if (c1 != '\0' && c1 == c2) continue; return (c1 - c2); } return 0; } #endif #ifdef GETLOADAVG_PRIVILEGED #ifdef POSIX /* Hopefully if a system says it's POSIX.1 and has the setuid and setgid functions, they work as POSIX.1 says. Some systems (Alpha OSF/1 1.2, for example) which claim to be POSIX.1 also have the BSD setreuid and setregid functions, but they don't work as in BSD and only the POSIX.1 way works. */ #undef HAVE_SETREUID #undef HAVE_SETREGID #else /* Not POSIX. */ /* Some POSIX.1 systems have the seteuid and setegid functions. In a POSIX-like system, they are the best thing to use. However, some non-POSIX systems have them too but they do not work in the POSIX style and we must use setreuid and setregid instead. */ #undef HAVE_SETEUID #undef HAVE_SETEGID #endif /* POSIX. */ #ifndef HAVE_UNISTD_H extern int getuid (), getgid (), geteuid (), getegid (); extern int setuid (), setgid (); #ifdef HAVE_SETEUID extern int seteuid (); #else #ifdef HAVE_SETREUID extern int setreuid (); #endif /* Have setreuid. */ #endif /* Have seteuid. */ #ifdef HAVE_SETEGID extern int setegid (); #else #ifdef HAVE_SETREGID extern int setregid (); #endif /* Have setregid. */ #endif /* Have setegid. */ #endif /* No <unistd.h>. */ /* Keep track of the user and group IDs for user- and make- access. */ static int user_uid = -1, user_gid = -1, make_uid = -1, make_gid = -1; #define access_inited (user_uid != -1) static enum { make, user } current_access; /* Under -d, write a message describing the current IDs. */ static void log_access (const char *flavor) { if (! ISDB (DB_JOBS)) return; /* All the other debugging messages go to stdout, but we write this one to stderr because it might be run in a child fork whose stdout is piped. */ fprintf (stderr, _("%s: user %lu (real %lu), group %lu (real %lu)\n"), flavor, (unsigned long) geteuid (), (unsigned long) getuid (), (unsigned long) getegid (), (unsigned long) getgid ()); fflush (stderr); } static void init_access (void) { #ifndef VMS user_uid = getuid (); user_gid = getgid (); make_uid = geteuid (); make_gid = getegid (); /* Do these ever fail? */ if (user_uid == -1 || user_gid == -1 || make_uid == -1 || make_gid == -1) pfatal_with_name ("get{e}[gu]id"); log_access (_("Initialized access")); current_access = make; #endif } #endif /* GETLOADAVG_PRIVILEGED */ /* Give the process appropriate permissions for access to user data (i.e., to stat files, or to spawn a child process). */ void user_access (void) { #ifdef GETLOADAVG_PRIVILEGED if (!access_inited) init_access (); if (current_access == user) return; /* We are in "make access" mode. This means that the effective user and group IDs are those of make (if it was installed setuid or setgid). We now want to set the effective user and group IDs to the real IDs, which are the IDs of the process that exec'd make. */ #ifdef HAVE_SETEUID /* Modern systems have the seteuid/setegid calls which set only the effective IDs, which is ideal. */ if (seteuid (user_uid) < 0) pfatal_with_name ("user_access: seteuid"); #else /* Not HAVE_SETEUID. */ #ifndef HAVE_SETREUID /* System V has only the setuid/setgid calls to set user/group IDs. There is an effective ID, which can be set by setuid/setgid. It can be set (unless you are root) only to either what it already is (returned by geteuid/getegid, now in make_uid/make_gid), the real ID (return by getuid/getgid, now in user_uid/user_gid), or the saved set ID (what the effective ID was before this set-ID executable (make) was exec'd). */ if (setuid (user_uid) < 0) pfatal_with_name ("user_access: setuid"); #else /* HAVE_SETREUID. */ /* In 4BSD, the setreuid/setregid calls set both the real and effective IDs. They may be set to themselves or each other. So you have two alternatives at any one time. If you use setuid/setgid, the effective will be set to the real, leaving only one alternative. Using setreuid/setregid, however, you can toggle between your two alternatives by swapping the values in a single setreuid or setregid call. */ if (setreuid (make_uid, user_uid) < 0) pfatal_with_name ("user_access: setreuid"); #endif /* Not HAVE_SETREUID. */ #endif /* HAVE_SETEUID. */ #ifdef HAVE_SETEGID if (setegid (user_gid) < 0) pfatal_with_name ("user_access: setegid"); #else #ifndef HAVE_SETREGID if (setgid (user_gid) < 0) pfatal_with_name ("user_access: setgid"); #else if (setregid (make_gid, user_gid) < 0) pfatal_with_name ("user_access: setregid"); #endif #endif current_access = user; log_access (_("User access")); #endif /* GETLOADAVG_PRIVILEGED */ } /* Give the process appropriate permissions for access to make data (i.e., the load average). */ void make_access (void) { #ifdef GETLOADAVG_PRIVILEGED if (!access_inited) init_access (); if (current_access == make) return; /* See comments in user_access, above. */ #ifdef HAVE_SETEUID if (seteuid (make_uid) < 0) pfatal_with_name ("make_access: seteuid"); #else #ifndef HAVE_SETREUID if (setuid (make_uid) < 0) pfatal_with_name ("make_access: setuid"); #else if (setreuid (user_uid, make_uid) < 0) pfatal_with_name ("make_access: setreuid"); #endif #endif #ifdef HAVE_SETEGID if (setegid (make_gid) < 0) pfatal_with_name ("make_access: setegid"); #else #ifndef HAVE_SETREGID if (setgid (make_gid) < 0) pfatal_with_name ("make_access: setgid"); #else if (setregid (user_gid, make_gid) < 0) pfatal_with_name ("make_access: setregid"); #endif #endif current_access = make; log_access (_("Make access")); #endif /* GETLOADAVG_PRIVILEGED */ } /* Give the process appropriate permissions for a child process. This is like user_access, but you can't get back to make_access. */ void child_access (void) { #ifdef GETLOADAVG_PRIVILEGED if (!access_inited) abort (); /* Set both the real and effective UID and GID to the user's. They cannot be changed back to make's. */ #ifndef HAVE_SETREUID if (setuid (user_uid) < 0) pfatal_with_name ("child_access: setuid"); #else if (setreuid (user_uid, user_uid) < 0) pfatal_with_name ("child_access: setreuid"); #endif #ifndef HAVE_SETREGID if (setgid (user_gid) < 0) pfatal_with_name ("child_access: setgid"); #else if (setregid (user_gid, user_gid) < 0) pfatal_with_name ("child_access: setregid"); #endif log_access (_("Child access")); #endif /* GETLOADAVG_PRIVILEGED */ } #ifdef NEED_GET_PATH_MAX unsigned int get_path_max (void) { static unsigned int value; if (value == 0) { long int x = pathconf ("/", _PC_PATH_MAX); if (x > 0) value = x; else return MAXPATHLEN; } return value; } #endif /* This code is stolen from gnulib. If/when we abandon the requirement to work with K&R compilers, we can remove this (and perhaps other parts of GNU make!) and migrate to using gnulib directly. This is called only through atexit(), which means die() has already been invoked. So, call exit() here directly. Apparently that works...? */ /* Close standard output, exiting with status 'exit_failure' on failure. If a program writes *anything* to stdout, that program should close stdout and make sure that it succeeds before exiting. Otherwise, suppose that you go to the extreme of checking the return status of every function that does an explicit write to stdout. The last printf can succeed in writing to the internal stream buffer, and yet the fclose(stdout) could still fail (due e.g., to a disk full error) when it tries to write out that buffered data. Thus, you would be left with an incomplete output file and the offending program would exit successfully. Even calling fflush is not always sufficient, since some file systems (NFS and CODA) buffer written/flushed data until an actual close call. Besides, it's wasteful to check the return value from every call that writes to stdout -- just let the internal stream state record the failure. That's what the ferror test is checking below. It's important to detect such failures and exit nonzero because many tools (most notably `make' and other build-management systems) depend on being able to detect failure in other tools via their exit status. */ void close_stdout (void) { int prev_fail = ferror (stdout); int fclose_fail = fclose (stdout); if (prev_fail || fclose_fail) { if (fclose_fail) error (NILF, _("write error: %s"), strerror (errno)); else error (NILF, _("write error")); exit (EXIT_FAILURE); } }
/* Reading and parsing of makefiles for GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" #include <assert.h> #include <glob.h> #include "dep.h" #include "filedef.h" #include "job.h" #include "commands.h" #include "variable.h" #include "rule.h" #include "debug.h" #include "hash.h" #ifndef WINDOWS32 #ifndef _AMIGA #ifndef VMS #include <pwd.h> #else struct passwd *getpwnam (char *name); #endif #endif #endif /* !WINDOWS32 */ /* A 'struct ebuffer' controls the origin of the makefile we are currently eval'ing. */ struct ebuffer { char *buffer; /* Start of the current line in the buffer. */ char *bufnext; /* Start of the next line in the buffer. */ char *bufstart; /* Start of the entire buffer. */ unsigned int size; /* Malloc'd size of buffer. */ FILE *fp; /* File, or NULL if this is an internal buffer. */ struct floc floc; /* Info on the file in fp (if any). */ }; /* Track the modifiers we can have on variable assignments */ struct vmodifiers { unsigned int assign_v:1; unsigned int define_v:1; unsigned int undefine_v:1; unsigned int export_v:1; unsigned int override_v:1; unsigned int private_v:1; }; /* Types of "words" that can be read in a makefile. */ enum make_word_type { w_bogus, w_eol, w_static, w_variable, w_colon, w_dcolon, w_semicolon, w_varassign }; /* A `struct conditionals' contains the information describing all the active conditionals in a makefile. The global variable `conditionals' contains the conditionals information for the current makefile. It is initialized from the static structure `toplevel_conditionals' and is later changed to new structures for included makefiles. */ struct conditionals { unsigned int if_cmds; /* Depth of conditional nesting. */ unsigned int allocated; /* Elts allocated in following arrays. */ char *ignoring; /* Are we ignoring or interpreting? 0=interpreting, 1=not yet interpreted, 2=already interpreted */ char *seen_else; /* Have we already seen an `else'? */ }; static struct conditionals toplevel_conditionals; static struct conditionals *conditionals = &toplevel_conditionals; /* Default directories to search for include files in */ static const char *default_include_directories[] = { #if defined(WINDOWS32) && !defined(INCLUDEDIR) /* This completely up to the user when they install MSVC or other packages. This is defined as a placeholder. */ # define INCLUDEDIR "." #endif INCLUDEDIR, #ifndef _AMIGA "/usr/gnu/include", "/usr/local/include", "/usr/include", #endif 0 }; /* List of directories to search for include files in */ static const char **include_directories; /* Maximum length of an element of the above. */ static unsigned int max_incl_len; /* The filename and pointer to line number of the makefile currently being read in. */ const struct floc *reading_file = 0; /* The chain of makefiles read by read_makefile. */ static struct dep *read_makefiles = 0; static int eval_makefile (const char *filename, int flags); static void eval (struct ebuffer *buffer, int flags); static long readline (struct ebuffer *ebuf); static void do_undefine (char *name, enum variable_origin origin, struct ebuffer *ebuf); static struct variable *do_define (char *name, enum variable_origin origin, struct ebuffer *ebuf); static int conditional_line (char *line, int len, const struct floc *flocp); static void record_files (struct nameseq *filenames, const char *pattern, const char *pattern_percent, char *depstr, unsigned int cmds_started, char *commands, unsigned int commands_idx, int two_colon, const struct floc *flocp); static void record_target_var (struct nameseq *filenames, char *defn, enum variable_origin origin, struct vmodifiers *vmod, const struct floc *flocp); static enum make_word_type get_next_mword (char *buffer, char *delim, char **startp, unsigned int *length); static void remove_comments (char *line); static char *find_char_unquote (char *string, int stop1, int stop2, int blank, int ignorevars); /* Compare a word, both length and contents. P must point to the word to be tested, and WLEN must be the length. */ #define word1eq(s) (wlen == sizeof(s)-1 && strneq (s, p, sizeof(s)-1)) /* Read in all the makefiles and return the chain of their names. */ struct dep * read_all_makefiles (const char **makefiles) { unsigned int num_makefiles = 0; /* Create *_LIST variables, to hold the makefiles, targets, and variables we will be reading. */ define_variable_cname ("MAKEFILE_LIST", "", o_file, 0); DB (DB_BASIC, (_("Reading makefiles...\n"))); /* If there's a non-null variable MAKEFILES, its value is a list of files to read first thing. But don't let it prevent reading the default makefiles and don't let the default goal come from there. */ { char *value; char *name, *p; unsigned int length; { /* Turn off --warn-undefined-variables while we expand MAKEFILES. */ int save = warn_undefined_variables_flag; warn_undefined_variables_flag = 0; value = allocated_variable_expand ("$(MAKEFILES)"); warn_undefined_variables_flag = save; } /* Set NAME to the start of next token and LENGTH to its length. MAKEFILES is updated for finding remaining tokens. */ p = value; while ((name = find_next_token ((const char **)&p, &length)) != 0) { if (*p != '\0') *p++ = '\0'; eval_makefile (name, RM_NO_DEFAULT_GOAL|RM_INCLUDED|RM_DONTCARE); } free (value); } /* Read makefiles specified with -f switches. */ if (makefiles != 0) while (*makefiles != 0) { struct dep *tail = read_makefiles; register struct dep *d; if (! eval_makefile (*makefiles, 0)) perror_with_name ("", *makefiles); /* Find the right element of read_makefiles. */ d = read_makefiles; while (d->next != tail) d = d->next; /* Use the storage read_makefile allocates. */ *makefiles = dep_name (d); ++num_makefiles; ++makefiles; } /* If there were no -f switches, try the default names. */ if (num_makefiles == 0) { static char *default_makefiles[] = #ifdef VMS /* all lower case since readdir() (the vms version) 'lowercasifies' */ { "makefile.vms", "gnumakefile.", "makefile.", 0 }; #else #ifdef _AMIGA { "GNUmakefile", "Makefile", "SMakefile", 0 }; #else /* !Amiga && !VMS */ { "GNUmakefile", "makefile", "Makefile", 0 }; #endif /* AMIGA */ #endif /* VMS */ register char **p = default_makefiles; while (*p != 0 && !file_exists_p (*p)) ++p; if (*p != 0) { if (! eval_makefile (*p, 0)) perror_with_name ("", *p); } else { /* No default makefile was found. Add the default makefiles to the `read_makefiles' chain so they will be updated if possible. */ struct dep *tail = read_makefiles; /* Add them to the tail, after any MAKEFILES variable makefiles. */ while (tail != 0 && tail->next != 0) tail = tail->next; for (p = default_makefiles; *p != 0; ++p) { struct dep *d = alloc_dep (); d->file = enter_file (strcache_add (*p)); d->dontcare = 1; /* Tell update_goal_chain to bail out as soon as this file is made, and main not to die if we can't make this file. */ d->changed = RM_DONTCARE; if (tail == 0) read_makefiles = d; else tail->next = d; tail = d; } if (tail != 0) tail->next = 0; } } return read_makefiles; } /* Install a new conditional and return the previous one. */ static struct conditionals * install_conditionals (struct conditionals *new) { struct conditionals *save = conditionals; memset (new, '\0', sizeof (*new)); conditionals = new; return save; } /* Free the current conditionals and reinstate a saved one. */ static void restore_conditionals (struct conditionals *saved) { /* Free any space allocated by conditional_line. */ if (conditionals->ignoring) free (conditionals->ignoring); if (conditionals->seen_else) free (conditionals->seen_else); /* Restore state. */ conditionals = saved; } static int eval_makefile (const char *filename, int flags) { struct dep *deps; struct ebuffer ebuf; const struct floc *curfile; char *expanded = 0; int makefile_errno; filename = strcache_add (filename); ebuf.floc.filenm = filename; ebuf.floc.lineno = 1; if (ISDB (DB_VERBOSE)) { printf (_("Reading makefile `%s'"), filename); if (flags & RM_NO_DEFAULT_GOAL) printf (_(" (no default goal)")); if (flags & RM_INCLUDED) printf (_(" (search path)")); if (flags & RM_DONTCARE) printf (_(" (don't care)")); if (flags & RM_NO_TILDE) printf (_(" (no ~ expansion)")); puts ("..."); } /* First, get a stream to read. */ /* Expand ~ in FILENAME unless it came from `include', in which case it was already done. */ if (!(flags & RM_NO_TILDE) && filename[0] == '~') { expanded = tilde_expand (filename); if (expanded != 0) filename = expanded; } ebuf.fp = fopen (filename, "r"); /* Save the error code so we print the right message later. */ makefile_errno = errno; /* If the makefile wasn't found and it's either a makefile from the `MAKEFILES' variable or an included makefile, search the included makefile search path for this makefile. */ if (ebuf.fp == 0 && (flags & RM_INCLUDED) && *filename != '/') { unsigned int i; for (i = 0; include_directories[i] != 0; ++i) { const char *included = concat (3, include_directories[i], "/", filename); ebuf.fp = fopen (included, "r"); if (ebuf.fp) { filename = strcache_add (included); break; } } } /* Add FILENAME to the chain of read makefiles. */ deps = alloc_dep (); deps->next = read_makefiles; read_makefiles = deps; deps->file = lookup_file (filename); if (deps->file == 0) deps->file = enter_file (filename); filename = deps->file->name; deps->changed = flags; if (flags & RM_DONTCARE) deps->dontcare = 1; if (expanded) free (expanded); /* If the makefile can't be found at all, give up entirely. */ if (ebuf.fp == 0) { /* If we did some searching, errno has the error from the last attempt, rather from FILENAME itself. Restore it in case the caller wants to use it in a message. */ errno = makefile_errno; return 0; } /* Set close-on-exec to avoid leaking the makefile to children, such as $(shell ...). */ #ifdef HAVE_FILENO CLOSE_ON_EXEC (fileno (ebuf.fp)); #endif /* Add this makefile to the list. */ do_variable_definition (&ebuf.floc, "MAKEFILE_LIST", filename, o_file, f_append, 0); /* Evaluate the makefile */ ebuf.size = 200; ebuf.buffer = ebuf.bufnext = ebuf.bufstart = xmalloc (ebuf.size); curfile = reading_file; reading_file = &ebuf.floc; eval (&ebuf, !(flags & RM_NO_DEFAULT_GOAL)); reading_file = curfile; fclose (ebuf.fp); free (ebuf.bufstart); alloca (0); return 1; } void eval_buffer (char *buffer) { struct ebuffer ebuf; struct conditionals *saved; struct conditionals new; const struct floc *curfile; /* Evaluate the buffer */ ebuf.size = strlen (buffer); ebuf.buffer = ebuf.bufnext = ebuf.bufstart = buffer; ebuf.fp = NULL; if (reading_file) ebuf.floc = *reading_file; else ebuf.floc.filenm = NULL; curfile = reading_file; reading_file = &ebuf.floc; saved = install_conditionals (&new); eval (&ebuf, 1); restore_conditionals (saved); reading_file = curfile; alloca (0); } /* Check LINE to see if it's a variable assignment or undefine. It might use one of the modifiers "export", "override", "private", or it might be one of the conditional tokens like "ifdef", "include", etc. If it's not a variable assignment or undefine, VMOD.V_ASSIGN is 0. Returns LINE. Returns a pointer to the first non-modifier character, and sets VMOD based on the modifiers found if any, plus V_ASSIGN is 1. */ static char * parse_var_assignment (const char *line, struct vmodifiers *vmod) { const char *p; memset (vmod, '\0', sizeof (*vmod)); /* Find the start of the next token. If there isn't one we're done. */ line = next_token (line); if (*line == '\0') return (char *)line; p = line; while (1) { int wlen; const char *p2; enum variable_flavor flavor; p2 = parse_variable_definition (p, &flavor); /* If this is a variable assignment, we're done. */ if (p2) break; /* It's not a variable; see if it's a modifier. */ p2 = end_of_token (p); wlen = p2 - p; if (word1eq ("export")) vmod->export_v = 1; else if (word1eq ("override")) vmod->override_v = 1; else if (word1eq ("private")) vmod->private_v = 1; else if (word1eq ("define")) { /* We can't have modifiers after 'define' */ vmod->define_v = 1; p = next_token (p2); break; } else if (word1eq ("undefine")) { /* We can't have modifiers after 'undefine' */ vmod->undefine_v = 1; p = next_token (p2); break; } else /* Not a variable or modifier: this is not a variable assignment. */ return (char *)line; /* It was a modifier. Try the next word. */ p = next_token (p2); if (*p == '\0') return (char *)line; } /* Found a variable assignment or undefine. */ vmod->assign_v = 1; return (char *)p; } /* Read file FILENAME as a makefile and add its contents to the data base. SET_DEFAULT is true if we are allowed to set the default goal. */ static void eval (struct ebuffer *ebuf, int set_default) { char *collapsed = 0; unsigned int collapsed_length = 0; unsigned int commands_len = 200; char *commands; unsigned int commands_idx = 0; unsigned int cmds_started, tgts_started; int ignoring = 0, in_ignored_define = 0; int no_targets = 0; /* Set when reading a rule without targets. */ struct nameseq *filenames = 0; char *depstr = 0; long nlines = 0; int two_colon = 0; const char *pattern = 0; const char *pattern_percent; struct floc *fstart; struct floc fi; #define record_waiting_files() \ do \ { \ if (filenames != 0) \ { \ fi.lineno = tgts_started; \ record_files (filenames, pattern, pattern_percent, depstr, \ cmds_started, commands, commands_idx, two_colon, \ &fi); \ filenames = 0; \ } \ commands_idx = 0; \ no_targets = 0; \ pattern = 0; \ } while (0) pattern_percent = 0; cmds_started = tgts_started = 1; fstart = &ebuf->floc; fi.filenm = ebuf->floc.filenm; /* Loop over lines in the file. The strategy is to accumulate target names in FILENAMES, dependencies in DEPS and commands in COMMANDS. These are used to define a rule when the start of the next rule (or eof) is encountered. When you see a "continue" in the loop below, that means we are moving on to the next line _without_ ending any rule that we happen to be working with at the moment. If you see a "goto rule_complete", then the statement we just parsed also finishes the previous rule. */ commands = xmalloc (200); while (1) { unsigned int linelen; char *line; unsigned int wlen; char *p; char *p2; struct vmodifiers vmod; /* At the top of this loop, we are starting a brand new line. */ /* Grab the next line to be evaluated */ ebuf->floc.lineno += nlines; nlines = readline (ebuf); /* If there is nothing left to eval, we're done. */ if (nlines < 0) break; /* If this line is empty, skip it. */ line = ebuf->buffer; if (line[0] == '\0') continue; linelen = strlen (line); /* Check for a shell command line first. If it is not one, we can stop treating tab specially. */ if (line[0] == cmd_prefix) { if (no_targets) /* Ignore the commands in a rule with no targets. */ continue; /* If there is no preceding rule line, don't treat this line as a command, even though it begins with a recipe prefix. SunOS 4 make appears to behave this way. */ if (filenames != 0) { if (ignoring) /* Yep, this is a shell command, and we don't care. */ continue; /* Append this command line to the line being accumulated. Strip command prefix chars that appear after newlines. */ if (commands_idx == 0) cmds_started = ebuf->floc.lineno; if (linelen + commands_idx > commands_len) { commands_len = (linelen + commands_idx) * 2; commands = xrealloc (commands, commands_len); } p = &commands[commands_idx]; p2 = line + 1; while (--linelen) { ++commands_idx; *(p++) = *p2; if (p2[0] == '\n' && p2[1] == cmd_prefix) { ++p2; --linelen; } ++p2; } *p = '\n'; ++commands_idx; continue; } } /* This line is not a shell command line. Don't worry about whitespace. Get more space if we need it; we don't need to preserve the current contents of the buffer. */ if (collapsed_length < linelen+1) { collapsed_length = linelen+1; if (collapsed) free (collapsed); /* Don't need xrealloc: we don't need to preserve the content. */ collapsed = xmalloc (collapsed_length); } strcpy (collapsed, line); /* Collapse continuation lines. */ collapse_continuations (collapsed); remove_comments (collapsed); /* Get rid if starting space (including formfeed, vtab, etc.) */ p = collapsed; while (isspace ((unsigned char)*p)) ++p; /* See if this is a variable assignment. We need to do this early, to allow variables with names like 'ifdef', 'export', 'private', etc. */ p = parse_var_assignment(p, &vmod); if (vmod.assign_v) { struct variable *v; enum variable_origin origin = vmod.override_v ? o_override : o_file; /* If we're ignoring then we're done now. */ if (ignoring) { if (vmod.define_v) in_ignored_define = 1; continue; } if (vmod.undefine_v) { do_undefine (p, origin, ebuf); /* This line has been dealt with. */ goto rule_complete; } else if (vmod.define_v) v = do_define (p, origin, ebuf); else v = try_variable_definition (fstart, p, origin, 0); assert (v != NULL); if (vmod.export_v) v->export = v_export; if (vmod.private_v) v->private_var = 1; /* This line has been dealt with. */ goto rule_complete; } /* If this line is completely empty, ignore it. */ if (*p == '\0') continue; p2 = end_of_token (p); wlen = p2 - p; p2 = next_token (p2); /* If we're in an ignored define, skip this line (but maybe get out). */ if (in_ignored_define) { /* See if this is an endef line (plus optional comment). */ if (word1eq ("endef") && (*p2 == '\0' || *p2 == '#')) in_ignored_define = 0; continue; } /* Check for conditional state changes. */ { int i = conditional_line (p, wlen, fstart); if (i != -2) { if (i == -1) fatal (fstart, _("invalid syntax in conditional")); ignoring = i; continue; } } /* Nothing to see here... move along. */ if (ignoring) continue; /* Manage the "export" keyword used outside of variable assignment as well as "unexport". */ if (word1eq ("export") || word1eq ("unexport")) { int exporting = *p == 'u' ? 0 : 1; /* (un)export by itself causes everything to be (un)exported. */ if (*p2 == '\0') export_all_variables = exporting; else { unsigned int l; const char *cp; char *ap; /* Expand the line so we can use indirect and constructed variable names in an (un)export command. */ cp = ap = allocated_variable_expand (p2); for (p = find_next_token (&cp, &l); p != 0; p = find_next_token (&cp, &l)) { struct variable *v = lookup_variable (p, l); if (v == 0) v = define_variable_loc (p, l, "", o_file, 0, fstart); v->export = exporting ? v_export : v_noexport; } free (ap); } goto rule_complete; } /* Handle the special syntax for vpath. */ if (word1eq ("vpath")) { const char *cp; char *vpat; unsigned int l; cp = variable_expand (p2); p = find_next_token (&cp, &l); if (p != 0) { vpat = xstrndup (p, l); p = find_next_token (&cp, &l); /* No searchpath means remove all previous selective VPATH's with the same pattern. */ } else /* No pattern means remove all previous selective VPATH's. */ vpat = 0; construct_vpath_list (vpat, p); if (vpat != 0) free (vpat); goto rule_complete; } /* Handle include and variants. */ if (word1eq ("include") || word1eq ("-include") || word1eq ("sinclude")) { /* We have found an `include' line specifying a nested makefile to be read at this point. */ struct conditionals *save; struct conditionals new_conditionals; struct nameseq *files; /* "-include" (vs "include") says no error if the file does not exist. "sinclude" is an alias for this from SGI. */ int noerror = (p[0] != 'i'); p = allocated_variable_expand (p2); /* If no filenames, it's a no-op. */ if (*p == '\0') { free (p); continue; } /* Parse the list of file names. Don't expand archive references! */ p2 = p; files = PARSE_FILE_SEQ (&p2, struct nameseq, '\0', NULL, PARSEFS_NOAR); free (p); /* Save the state of conditionals and start the included makefile with a clean slate. */ save = install_conditionals (&new_conditionals); /* Record the rules that are waiting so they will determine the default goal before those in the included makefile. */ record_waiting_files (); /* Read each included makefile. */ while (files != 0) { struct nameseq *next = files->next; const char *name = files->name; int r; free_ns (files); files = next; r = eval_makefile (name, (RM_INCLUDED | RM_NO_TILDE | (noerror ? RM_DONTCARE : 0) | (set_default ? 0 : RM_NO_DEFAULT_GOAL))); if (!r && !noerror) error (fstart, "%s: %s", name, strerror (errno)); } /* Restore conditional state. */ restore_conditionals (save); goto rule_complete; } /* This line starts with a tab but was not caught above because there was no preceding target, and the line might have been usable as a variable definition. But now we know it is definitely lossage. */ if (line[0] == cmd_prefix) fatal(fstart, _("recipe commences before first target")); /* This line describes some target files. This is complicated by the existence of target-specific variables, because we can't expand the entire line until we know if we have one or not. So we expand the line word by word until we find the first `:', then check to see if it's a target-specific variable. In this algorithm, `lb_next' will point to the beginning of the unexpanded parts of the input buffer, while `p2' points to the parts of the expanded buffer we haven't searched yet. */ { enum make_word_type wtype; char *cmdleft, *semip, *lb_next; unsigned int plen = 0; char *colonp; const char *end, *beg; /* Helpers for whitespace stripping. */ /* Record the previous rule. */ record_waiting_files (); tgts_started = fstart->lineno; /* Search the line for an unquoted ; that is not after an unquoted #. */ cmdleft = find_char_unquote (line, ';', '#', 0, 1); if (cmdleft != 0 && *cmdleft == '#') { /* We found a comment before a semicolon. */ *cmdleft = '\0'; cmdleft = 0; } else if (cmdleft != 0) /* Found one. Cut the line short there before expanding it. */ *(cmdleft++) = '\0'; semip = cmdleft; collapse_continuations (line); /* We can't expand the entire line, since if it's a per-target variable we don't want to expand it. So, walk from the beginning, expanding as we go, and looking for "interesting" chars. The first word is always expandable. */ wtype = get_next_mword(line, NULL, &lb_next, &wlen); switch (wtype) { case w_eol: if (cmdleft != 0) fatal(fstart, _("missing rule before recipe")); /* This line contained something but turned out to be nothing but whitespace (a comment?). */ continue; case w_colon: case w_dcolon: /* We accept and ignore rules without targets for compatibility with SunOS 4 make. */ no_targets = 1; continue; default: break; } p2 = variable_expand_string(NULL, lb_next, wlen); while (1) { lb_next += wlen; if (cmdleft == 0) { /* Look for a semicolon in the expanded line. */ cmdleft = find_char_unquote (p2, ';', 0, 0, 0); if (cmdleft != 0) { unsigned long p2_off = p2 - variable_buffer; unsigned long cmd_off = cmdleft - variable_buffer; char *pend = p2 + strlen(p2); /* Append any remnants of lb, then cut the line short at the semicolon. */ *cmdleft = '\0'; /* One school of thought says that you shouldn't expand here, but merely copy, since now you're beyond a ";" and into a command script. However, the old parser expanded the whole line, so we continue that for backwards-compatiblity. Also, it wouldn't be entirely consistent, since we do an unconditional expand below once we know we don't have a target-specific variable. */ (void)variable_expand_string(pend, lb_next, (long)-1); lb_next += strlen(lb_next); p2 = variable_buffer + p2_off; cmdleft = variable_buffer + cmd_off + 1; } } colonp = find_char_unquote(p2, ':', 0, 0, 0); #ifdef HAVE_DOS_PATHS /* The drive spec brain-damage strikes again... */ /* Note that the only separators of targets in this context are whitespace and a left paren. If others are possible, they should be added to the string in the call to index. */ while (colonp && (colonp[1] == '/' || colonp[1] == '\\') && colonp > p2 && isalpha ((unsigned char)colonp[-1]) && (colonp == p2 + 1 || strchr (" \t(", colonp[-2]) != 0)) colonp = find_char_unquote(colonp + 1, ':', 0, 0, 0); #endif if (colonp != 0) break; wtype = get_next_mword(lb_next, NULL, &lb_next, &wlen); if (wtype == w_eol) break; p2 += strlen(p2); *(p2++) = ' '; p2 = variable_expand_string(p2, lb_next, wlen); /* We don't need to worry about cmdleft here, because if it was found in the variable_buffer the entire buffer has already been expanded... we'll never get here. */ } p2 = next_token (variable_buffer); /* If the word we're looking at is EOL, see if there's _anything_ on the line. If not, a variable expanded to nothing, so ignore it. If so, we can't parse this line so punt. */ if (wtype == w_eol) { if (*p2 != '\0') /* There's no need to be ivory-tower about this: check for one of the most common bugs found in makefiles... */ fatal (fstart, _("missing separator%s"), (cmd_prefix == '\t' && !strneq(line, " ", 8)) ? "" : _(" (did you mean TAB instead of 8 spaces?)")); continue; } /* Make the colon the end-of-string so we know where to stop looking for targets. */ *colonp = '\0'; filenames = PARSE_FILE_SEQ (&p2, struct nameseq, '\0', NULL, 0); *p2 = ':'; if (!filenames) { /* We accept and ignore rules without targets for compatibility with SunOS 4 make. */ no_targets = 1; continue; } /* This should never be possible; we handled it above. */ assert (*p2 != '\0'); ++p2; /* Is this a one-colon or two-colon entry? */ two_colon = *p2 == ':'; if (two_colon) p2++; /* Test to see if it's a target-specific variable. Copy the rest of the buffer over, possibly temporarily (we'll expand it later if it's not a target-specific variable). PLEN saves the length of the unparsed section of p2, for later. */ if (*lb_next != '\0') { unsigned int l = p2 - variable_buffer; plen = strlen (p2); variable_buffer_output (p2+plen, lb_next, strlen (lb_next)+1); p2 = variable_buffer + l; } p2 = parse_var_assignment (p2, &vmod); if (vmod.assign_v) { /* If there was a semicolon found, add it back, plus anything after it. */ if (semip) { unsigned int l = p - variable_buffer; *(--semip) = ';'; collapse_continuations (semip); variable_buffer_output (p2 + strlen (p2), semip, strlen (semip)+1); p = variable_buffer + l; } record_target_var (filenames, p2, vmod.override_v ? o_override : o_file, &vmod, fstart); filenames = 0; continue; } /* This is a normal target, _not_ a target-specific variable. Unquote any = in the dependency list. */ find_char_unquote (lb_next, '=', 0, 0, 0); /* We have some targets, so don't ignore the following commands. */ no_targets = 0; /* Expand the dependencies, etc. */ if (*lb_next != '\0') { unsigned int l = p2 - variable_buffer; (void) variable_expand_string (p2 + plen, lb_next, (long)-1); p2 = variable_buffer + l; /* Look for a semicolon in the expanded line. */ if (cmdleft == 0) { cmdleft = find_char_unquote (p2, ';', 0, 0, 0); if (cmdleft != 0) *(cmdleft++) = '\0'; } } /* Is this a static pattern rule: `target: %targ: %dep; ...'? */ p = strchr (p2, ':'); while (p != 0 && p[-1] == '\\') { char *q = &p[-1]; int backslash = 0; while (*q-- == '\\') backslash = !backslash; if (backslash) p = strchr (p + 1, ':'); else break; } #ifdef _AMIGA /* Here, the situation is quite complicated. Let's have a look at a couple of targets: install: dev:make dev:make: make dev:make:: xyz The rule is that it's only a target, if there are TWO :'s OR a space around the :. */ if (p && !(isspace ((unsigned char)p[1]) || !p[1] || isspace ((unsigned char)p[-1]))) p = 0; #endif #ifdef HAVE_DOS_PATHS { int check_again; do { check_again = 0; /* For DOS-style paths, skip a "C:\..." or a "C:/..." */ if (p != 0 && (p[1] == '\\' || p[1] == '/') && isalpha ((unsigned char)p[-1]) && (p == p2 + 1 || strchr (" \t:(", p[-2]) != 0)) { p = strchr (p + 1, ':'); check_again = 1; } } while (check_again); } #endif if (p != 0) { struct nameseq *target; target = PARSE_FILE_SEQ (&p2, struct nameseq, ':', NULL, PARSEFS_NOGLOB); ++p2; if (target == 0) fatal (fstart, _("missing target pattern")); else if (target->next != 0) fatal (fstart, _("multiple target patterns")); pattern_percent = find_percent_cached (&target->name); pattern = target->name; if (pattern_percent == 0) fatal (fstart, _("target pattern contains no `%%'")); free_ns (target); } else pattern = 0; /* Strip leading and trailing whitespaces. */ beg = p2; end = beg + strlen (beg) - 1; strip_whitespace (&beg, &end); /* Put all the prerequisites here; they'll be parsed later. */ if (beg <= end && *beg != '\0') depstr = xstrndup (beg, end - beg + 1); else depstr = 0; commands_idx = 0; if (cmdleft != 0) { /* Semicolon means rest of line is a command. */ unsigned int l = strlen (cmdleft); cmds_started = fstart->lineno; /* Add this command line to the buffer. */ if (l + 2 > commands_len) { commands_len = (l + 2) * 2; commands = xrealloc (commands, commands_len); } memcpy (commands, cmdleft, l); commands_idx += l; commands[commands_idx++] = '\n'; } /* Determine if this target should be made default. We used to do this in record_files() but because of the delayed target recording and because preprocessor directives are legal in target's commands it is too late. Consider this fragment for example: foo: ifeq ($(.DEFAULT_GOAL),foo) ... endif Because the target is not recorded until after ifeq directive is evaluated the .DEFAULT_GOAL does not contain foo yet as one would expect. Because of this we have to move the logic here. */ if (set_default && default_goal_var->value[0] == '\0') { const char *name; struct dep *d; struct nameseq *t = filenames; for (; t != 0; t = t->next) { int reject = 0; name = t->name; /* We have nothing to do if this is an implicit rule. */ if (strchr (name, '%') != 0) break; /* See if this target's name does not start with a `.', unless it contains a slash. */ if (*name == '.' && strchr (name, '/') == 0 #ifdef HAVE_DOS_PATHS && strchr (name, '\\') == 0 #endif ) continue; /* If this file is a suffix, don't let it be the default goal file. */ for (d = suffix_file->deps; d != 0; d = d->next) { register struct dep *d2; if (*dep_name (d) != '.' && streq (name, dep_name (d))) { reject = 1; break; } for (d2 = suffix_file->deps; d2 != 0; d2 = d2->next) { unsigned int l = strlen (dep_name (d2)); if (!strneq (name, dep_name (d2), l)) continue; if (streq (name + l, dep_name (d))) { reject = 1; break; } } if (reject) break; } if (!reject) { define_variable_global (".DEFAULT_GOAL", 13, t->name, o_file, 0, NILF); break; } } } continue; } /* We get here except in the case that we just read a rule line. Record now the last rule we read, so following spurious commands are properly diagnosed. */ rule_complete: record_waiting_files (); } #undef word1eq if (conditionals->if_cmds) fatal (fstart, _("missing `endif'")); /* At eof, record the last rule. */ record_waiting_files (); if (collapsed) free (collapsed); free (commands); } /* Remove comments from LINE. This is done by copying the text at LINE onto itself. */ static void remove_comments (char *line) { char *comment; comment = find_char_unquote (line, '#', 0, 0, 0); if (comment != 0) /* Cut off the line at the #. */ *comment = '\0'; } /* Execute a `undefine' directive. The undefine line has already been read, and NAME is the name of the variable to be undefined. */ static void do_undefine (char *name, enum variable_origin origin, struct ebuffer *ebuf) { char *p, *var; /* Expand the variable name and find the beginning (NAME) and end. */ var = allocated_variable_expand (name); name = next_token (var); if (*name == '\0') fatal (&ebuf->floc, _("empty variable name")); p = name + strlen (name) - 1; while (p > name && isblank ((unsigned char)*p)) --p; p[1] = '\0'; undefine_variable_global (name, p - name + 1, origin); free (var); } /* Execute a `define' directive. The first line has already been read, and NAME is the name of the variable to be defined. The following lines remain to be read. */ static struct variable * do_define (char *name, enum variable_origin origin, struct ebuffer *ebuf) { struct variable *v; enum variable_flavor flavor; struct floc defstart; int nlevels = 1; unsigned int length = 100; char *definition = xmalloc (length); unsigned int idx = 0; char *p, *var; defstart = ebuf->floc; p = parse_variable_definition (name, &flavor); if (p == NULL) /* No assignment token, so assume recursive. */ flavor = f_recursive; else { if (*(next_token (p)) != '\0') error (&defstart, _("extraneous text after `define' directive")); /* Chop the string before the assignment token to get the name. */ p[flavor == f_recursive ? -1 : -2] = '\0'; } /* Expand the variable name and find the beginning (NAME) and end. */ var = allocated_variable_expand (name); name = next_token (var); if (*name == '\0') fatal (&defstart, _("empty variable name")); p = name + strlen (name) - 1; while (p > name && isblank ((unsigned char)*p)) --p; p[1] = '\0'; /* Now read the value of the variable. */ while (1) { unsigned int len; char *line; long nlines = readline (ebuf); /* If there is nothing left to be eval'd, there's no 'endef'!! */ if (nlines < 0) fatal (&defstart, _("missing `endef', unterminated `define'")); ebuf->floc.lineno += nlines; line = ebuf->buffer; collapse_continuations (line); /* If the line doesn't begin with a tab, test to see if it introduces another define, or ends one. Stop if we find an 'endef' */ if (line[0] != cmd_prefix) { p = next_token (line); len = strlen (p); /* If this is another 'define', increment the level count. */ if ((len == 6 || (len > 6 && isblank ((unsigned char)p[6]))) && strneq (p, "define", 6)) ++nlevels; /* If this is an 'endef', decrement the count. If it's now 0, we've found the last one. */ else if ((len == 5 || (len > 5 && isblank ((unsigned char)p[5]))) && strneq (p, "endef", 5)) { p += 5; remove_comments (p); if (*(next_token (p)) != '\0') error (&ebuf->floc, _("extraneous text after `endef' directive")); if (--nlevels == 0) break; } } /* Add this line to the variable definition. */ len = strlen (line); if (idx + len + 1 > length) { length = (idx + len) * 2; definition = xrealloc (definition, length + 1); } memcpy (&definition[idx], line, len); idx += len; /* Separate lines with a newline. */ definition[idx++] = '\n'; } /* We've got what we need; define the variable. */ if (idx == 0) definition[0] = '\0'; else definition[idx - 1] = '\0'; v = do_variable_definition (&defstart, name, definition, origin, flavor, 0); free (definition); free (var); return (v); } /* Interpret conditional commands "ifdef", "ifndef", "ifeq", "ifneq", "else" and "endif". LINE is the input line, with the command as its first word. FILENAME and LINENO are the filename and line number in the current makefile. They are used for error messages. Value is -2 if the line is not a conditional at all, -1 if the line is an invalid conditional, 0 if following text should be interpreted, 1 if following text should be ignored. */ static int conditional_line (char *line, int len, const struct floc *flocp) { char *cmdname; enum { c_ifdef, c_ifndef, c_ifeq, c_ifneq, c_else, c_endif } cmdtype; unsigned int i; unsigned int o; /* Compare a word, both length and contents. */ #define word1eq(s) (len == sizeof(s)-1 && strneq (s, line, sizeof(s)-1)) #define chkword(s, t) if (word1eq (s)) { cmdtype = (t); cmdname = (s); } /* Make sure this line is a conditional. */ chkword ("ifdef", c_ifdef) else chkword ("ifndef", c_ifndef) else chkword ("ifeq", c_ifeq) else chkword ("ifneq", c_ifneq) else chkword ("else", c_else) else chkword ("endif", c_endif) else return -2; /* Found one: skip past it and any whitespace after it. */ line = next_token (line + len); #define EXTRANEOUS() error (flocp, _("Extraneous text after `%s' directive"), cmdname) /* An 'endif' cannot contain extra text, and reduces the if-depth by 1 */ if (cmdtype == c_endif) { if (*line != '\0') EXTRANEOUS (); if (!conditionals->if_cmds) fatal (flocp, _("extraneous `%s'"), cmdname); --conditionals->if_cmds; goto DONE; } /* An 'else' statement can either be simple, or it can have another conditional after it. */ if (cmdtype == c_else) { const char *p; if (!conditionals->if_cmds) fatal (flocp, _("extraneous `%s'"), cmdname); o = conditionals->if_cmds - 1; if (conditionals->seen_else[o]) fatal (flocp, _("only one `else' per conditional")); /* Change the state of ignorance. */ switch (conditionals->ignoring[o]) { case 0: /* We've just been interpreting. Never do it again. */ conditionals->ignoring[o] = 2; break; case 1: /* We've never interpreted yet. Maybe this time! */ conditionals->ignoring[o] = 0; break; } /* It's a simple 'else'. */ if (*line == '\0') { conditionals->seen_else[o] = 1; goto DONE; } /* The 'else' has extra text. That text must be another conditional and cannot be an 'else' or 'endif'. */ /* Find the length of the next word. */ for (p = line+1; *p != '\0' && !isspace ((unsigned char)*p); ++p) ; len = p - line; /* If it's 'else' or 'endif' or an illegal conditional, fail. */ if (word1eq("else") || word1eq("endif") || conditional_line (line, len, flocp) < 0) EXTRANEOUS (); else { /* conditional_line() created a new level of conditional. Raise it back to this level. */ if (conditionals->ignoring[o] < 2) conditionals->ignoring[o] = conditionals->ignoring[o+1]; --conditionals->if_cmds; } goto DONE; } if (conditionals->allocated == 0) { conditionals->allocated = 5; conditionals->ignoring = xmalloc (conditionals->allocated); conditionals->seen_else = xmalloc (conditionals->allocated); } o = conditionals->if_cmds++; if (conditionals->if_cmds > conditionals->allocated) { conditionals->allocated += 5; conditionals->ignoring = xrealloc (conditionals->ignoring, conditionals->allocated); conditionals->seen_else = xrealloc (conditionals->seen_else, conditionals->allocated); } /* Record that we have seen an `if...' but no `else' so far. */ conditionals->seen_else[o] = 0; /* Search through the stack to see if we're already ignoring. */ for (i = 0; i < o; ++i) if (conditionals->ignoring[i]) { /* We are already ignoring, so just push a level to match the next "else" or "endif", and keep ignoring. We don't want to expand variables in the condition. */ conditionals->ignoring[o] = 1; return 1; } if (cmdtype == c_ifdef || cmdtype == c_ifndef) { char *var; struct variable *v; char *p; /* Expand the thing we're looking up, so we can use indirect and constructed variable names. */ var = allocated_variable_expand (line); /* Make sure there's only one variable name to test. */ p = end_of_token (var); i = p - var; p = next_token (p); if (*p != '\0') return -1; var[i] = '\0'; v = lookup_variable (var, i); conditionals->ignoring[o] = ((v != 0 && *v->value != '\0') == (cmdtype == c_ifndef)); free (var); } else { /* "ifeq" or "ifneq". */ char *s1, *s2; unsigned int l; char termin = *line == '(' ? ',' : *line; if (termin != ',' && termin != '"' && termin != '\'') return -1; s1 = ++line; /* Find the end of the first string. */ if (termin == ',') { int count = 0; for (; *line != '\0'; ++line) if (*line == '(') ++count; else if (*line == ')') --count; else if (*line == ',' && count <= 0) break; } else while (*line != '\0' && *line != termin) ++line; if (*line == '\0') return -1; if (termin == ',') { /* Strip blanks after the first string. */ char *p = line++; while (isblank ((unsigned char)p[-1])) --p; *p = '\0'; } else *line++ = '\0'; s2 = variable_expand (s1); /* We must allocate a new copy of the expanded string because variable_expand re-uses the same buffer. */ l = strlen (s2); s1 = alloca (l + 1); memcpy (s1, s2, l + 1); if (termin != ',') /* Find the start of the second string. */ line = next_token (line); termin = termin == ',' ? ')' : *line; if (termin != ')' && termin != '"' && termin != '\'') return -1; /* Find the end of the second string. */ if (termin == ')') { int count = 0; s2 = next_token (line); for (line = s2; *line != '\0'; ++line) { if (*line == '(') ++count; else if (*line == ')') { if (count <= 0) break; else --count; } } } else { ++line; s2 = line; while (*line != '\0' && *line != termin) ++line; } if (*line == '\0') return -1; *line = '\0'; line = next_token (++line); if (*line != '\0') EXTRANEOUS (); s2 = variable_expand (s2); conditionals->ignoring[o] = (streq (s1, s2) == (cmdtype == c_ifneq)); } DONE: /* Search through the stack to see if we're ignoring. */ for (i = 0; i < conditionals->if_cmds; ++i) if (conditionals->ignoring[i]) return 1; return 0; } /* Record target-specific variable values for files FILENAMES. TWO_COLON is nonzero if a double colon was used. The links of FILENAMES are freed, and so are any names in it that are not incorporated into other data structures. If the target is a pattern, add the variable to the pattern-specific variable value list. */ static void record_target_var (struct nameseq *filenames, char *defn, enum variable_origin origin, struct vmodifiers *vmod, const struct floc *flocp) { struct nameseq *nextf; struct variable_set_list *global; global = current_variable_set_list; /* If the variable is an append version, store that but treat it as a normal recursive variable. */ for (; filenames != 0; filenames = nextf) { struct variable *v; const char *name = filenames->name; const char *fname; const char *percent; struct pattern_var *p; nextf = filenames->next; free_ns (filenames); /* If it's a pattern target, then add it to the pattern-specific variable list. */ percent = find_percent_cached (&name); if (percent) { /* Get a reference for this pattern-specific variable struct. */ p = create_pattern_var (name, percent); p->variable.fileinfo = *flocp; /* I don't think this can fail since we already determined it was a variable definition. */ v = assign_variable_definition (&p->variable, defn); assert (v != 0); v->origin = origin; if (v->flavor == f_simple) v->value = allocated_variable_expand (v->value); else v->value = xstrdup (v->value); fname = p->target; } else { struct file *f; /* Get a file reference for this file, and initialize it. We don't want to just call enter_file() because that allocates a new entry if the file is a double-colon, which we don't want in this situation. */ f = lookup_file (name); if (!f) f = enter_file (strcache_add (name)); else if (f->double_colon) f = f->double_colon; initialize_file_variables (f, 1); fname = f->name; current_variable_set_list = f->variables; v = try_variable_definition (flocp, defn, origin, 1); if (!v) fatal (flocp, _("Malformed target-specific variable definition")); current_variable_set_list = global; } /* Set up the variable to be *-specific. */ v->per_target = 1; v->private_var = vmod->private_v; v->export = vmod->export_v ? v_export : v_default; /* If it's not an override, check to see if there was a command-line setting. If so, reset the value. */ if (v->origin != o_override) { struct variable *gv; int len = strlen(v->name); gv = lookup_variable (v->name, len); if (gv && (gv->origin == o_env_override || gv->origin == o_command)) { if (v->value != 0) free (v->value); v->value = xstrdup (gv->value); v->origin = gv->origin; v->recursive = gv->recursive; v->append = 0; } } } } /* Record a description line for files FILENAMES, with dependencies DEPS, commands to execute described by COMMANDS and COMMANDS_IDX, coming from FILENAME:COMMANDS_STARTED. TWO_COLON is nonzero if a double colon was used. If not nil, PATTERN is the `%' pattern to make this a static pattern rule, and PATTERN_PERCENT is a pointer to the `%' within it. The links of FILENAMES are freed, and so are any names in it that are not incorporated into other data structures. */ static void record_files (struct nameseq *filenames, const char *pattern, const char *pattern_percent, char *depstr, unsigned int cmds_started, char *commands, unsigned int commands_idx, int two_colon, const struct floc *flocp) { struct commands *cmds; struct dep *deps; const char *implicit_percent; const char *name; /* If we've already snapped deps, that means we're in an eval being resolved after the makefiles have been read in. We can't add more rules at this time, since they won't get snapped and we'll get core dumps. See Savannah bug # 12124. */ if (snapped_deps) fatal (flocp, _("prerequisites cannot be defined in recipes")); /* Determine if this is a pattern rule or not. */ name = filenames->name; implicit_percent = find_percent_cached (&name); /* If there's a recipe, set up a struct for it. */ if (commands_idx > 0) { cmds = xmalloc (sizeof (struct commands)); cmds->fileinfo.filenm = flocp->filenm; cmds->fileinfo.lineno = cmds_started; cmds->commands = xstrndup (commands, commands_idx); cmds->command_lines = 0; } else cmds = 0; /* If there's a prereq string then parse it--unless it's eligible for 2nd expansion: if so, snap_deps() will do it. */ if (depstr == 0) deps = 0; else if (second_expansion && strchr (depstr, '$')) { deps = alloc_dep (); deps->name = depstr; deps->need_2nd_expansion = 1; deps->staticpattern = pattern != 0; } else { deps = split_prereqs (depstr); free (depstr); /* We'll enter static pattern prereqs later when we have the stem. We don't want to enter pattern rules at all so that we don't think that they ought to exist (make manual "Implicit Rule Search Algorithm", item 5c). */ if (! pattern && ! implicit_percent) deps = enter_prereqs (deps, NULL); } /* For implicit rules, _all_ the targets must have a pattern. That means we can test the first one to see if we're working with an implicit rule; if so we handle it specially. */ if (implicit_percent) { struct nameseq *nextf; const char **targets, **target_pats; unsigned int c; if (pattern != 0) fatal (flocp, _("mixed implicit and static pattern rules")); /* Count the targets to create an array of target names. We already have the first one. */ nextf = filenames->next; free_ns (filenames); filenames = nextf; for (c = 1; nextf; ++c, nextf = nextf->next) ; targets = xmalloc (c * sizeof (const char *)); target_pats = xmalloc (c * sizeof (const char *)); targets[0] = name; target_pats[0] = implicit_percent; c = 1; while (filenames) { name = filenames->name; implicit_percent = find_percent_cached (&name); if (implicit_percent == 0) fatal (flocp, _("mixed implicit and normal rules")); targets[c] = name; target_pats[c] = implicit_percent; ++c; nextf = filenames->next; free_ns (filenames); filenames = nextf; } create_pattern_rule (targets, target_pats, c, two_colon, deps, cmds, 1); return; } /* Walk through each target and create it in the database. We already set up the first target, above. */ while (1) { struct nameseq *nextf = filenames->next; struct file *f; struct dep *this = 0; free_ns (filenames); /* Check for special targets. Do it here instead of, say, snap_deps() so that we can immediately use the value. */ if (streq (name, ".POSIX")) { posix_pedantic = 1; define_variable_cname (".SHELLFLAGS", "-ec", o_default, 0); } else if (streq (name, ".SECONDEXPANSION")) second_expansion = 1; #if !defined(WINDOWS32) && !defined (__MSDOS__) && !defined (__EMX__) else if (streq (name, ".ONESHELL")) one_shell = 1; #endif /* If this is a static pattern rule: `targets: target%pattern: prereq%pattern; recipe', make sure the pattern matches this target name. */ if (pattern && !pattern_matches (pattern, pattern_percent, name)) error (flocp, _("target `%s' doesn't match the target pattern"), name); else if (deps) /* If there are multiple targets, copy the chain DEPS for all but the last one. It is not safe for the same deps to go in more than one place in the database. */ this = nextf != 0 ? copy_dep_chain (deps) : deps; /* Find or create an entry in the file database for this target. */ if (!two_colon) { /* Single-colon. Combine this rule with the file's existing record, if any. */ f = enter_file (strcache_add (name)); if (f->double_colon) fatal (flocp, _("target file `%s' has both : and :: entries"), f->name); /* If CMDS == F->CMDS, this target was listed in this rule more than once. Just give a warning since this is harmless. */ if (cmds != 0 && cmds == f->cmds) error (flocp, _("target `%s' given more than once in the same rule."), f->name); /* Check for two single-colon entries both with commands. Check is_target so that we don't lose on files such as .c.o whose commands were preinitialized. */ else if (cmds != 0 && f->cmds != 0 && f->is_target) { error (&cmds->fileinfo, _("warning: overriding recipe for target `%s'"), f->name); error (&f->cmds->fileinfo, _("warning: ignoring old recipe for target `%s'"), f->name); } /* Defining .DEFAULT with no deps or cmds clears it. */ if (f == default_file && this == 0 && cmds == 0) f->cmds = 0; if (cmds != 0) f->cmds = cmds; /* Defining .SUFFIXES with no dependencies clears out the list of suffixes. */ if (f == suffix_file && this == 0) { free_dep_chain (f->deps); f->deps = 0; } } else { /* Double-colon. Make a new record even if there already is one. */ f = lookup_file (name); /* Check for both : and :: rules. Check is_target so we don't lose on default suffix rules or makefiles. */ if (f != 0 && f->is_target && !f->double_colon) fatal (flocp, _("target file `%s' has both : and :: entries"), f->name); f = enter_file (strcache_add (name)); /* If there was an existing entry and it was a double-colon entry, enter_file will have returned a new one, making it the prev pointer of the old one, and setting its double_colon pointer to the first one. */ if (f->double_colon == 0) /* This is the first entry for this name, so we must set its double_colon pointer to itself. */ f->double_colon = f; f->cmds = cmds; } f->is_target = 1; /* If this is a static pattern rule, set the stem to the part of its name that matched the `%' in the pattern, so you can use $* in the commands. If we didn't do it before, enter the prereqs now. */ if (pattern) { static const char *percent = "%"; char *buffer = variable_expand (""); char *o = patsubst_expand_pat (buffer, name, pattern, percent, pattern_percent+1, percent+1); f->stem = strcache_add_len (buffer, o - buffer); if (this) { if (! this->need_2nd_expansion) this = enter_prereqs (this, f->stem); else this->stem = f->stem; } } /* Add the dependencies to this file entry. */ if (this != 0) { /* Add the file's old deps and the new ones in THIS together. */ if (f->deps == 0) f->deps = this; else if (cmds != 0) { struct dep *d = this; /* If this rule has commands, put these deps first. */ while (d->next != 0) d = d->next; d->next = f->deps; f->deps = this; } else { struct dep *d = f->deps; /* A rule without commands: put its prereqs at the end. */ while (d->next != 0) d = d->next; d->next = this; } } name = f->name; /* All done! Set up for the next one. */ if (nextf == 0) break; filenames = nextf; /* Reduce escaped percents. If there are any unescaped it's an error */ name = filenames->name; if (find_percent_cached (&name)) fatal (flocp, _("mixed implicit and normal rules")); } } /* Search STRING for an unquoted STOPCHAR or blank (if BLANK is nonzero). Backslashes quote STOPCHAR, blanks if BLANK is nonzero, and backslash. Quoting backslashes are removed from STRING by compacting it into itself. Returns a pointer to the first unquoted STOPCHAR if there is one, or nil if there are none. STOPCHARs inside variable references are ignored if IGNOREVARS is true. STOPCHAR _cannot_ be '$' if IGNOREVARS is true. */ static char * find_char_unquote (char *string, int stop1, int stop2, int blank, int ignorevars) { unsigned int string_len = 0; char *p = string; if (ignorevars) ignorevars = '$'; while (1) { if (stop2 && blank) while (*p != '\0' && *p != ignorevars && *p != stop1 && *p != stop2 && ! isblank ((unsigned char) *p)) ++p; else if (stop2) while (*p != '\0' && *p != ignorevars && *p != stop1 && *p != stop2) ++p; else if (blank) while (*p != '\0' && *p != ignorevars && *p != stop1 && ! isblank ((unsigned char) *p)) ++p; else while (*p != '\0' && *p != ignorevars && *p != stop1) ++p; if (*p == '\0') break; /* If we stopped due to a variable reference, skip over its contents. */ if (*p == ignorevars) { char openparen = p[1]; p += 2; /* Skip the contents of a non-quoted, multi-char variable ref. */ if (openparen == '(' || openparen == '{') { unsigned int pcount = 1; char closeparen = (openparen == '(' ? ')' : '}'); while (*p) { if (*p == openparen) ++pcount; else if (*p == closeparen) if (--pcount == 0) { ++p; break; } ++p; } } /* Skipped the variable reference: look for STOPCHARS again. */ continue; } if (p > string && p[-1] == '\\') { /* Search for more backslashes. */ int i = -2; while (&p[i] >= string && p[i] == '\\') --i; ++i; /* Only compute the length if really needed. */ if (string_len == 0) string_len = strlen (string); /* The number of backslashes is now -I. Copy P over itself to swallow half of them. */ memmove (&p[i], &p[i/2], (string_len - (p - string)) - (i/2) + 1); p += i/2; if (i % 2 == 0) /* All the backslashes quoted each other; the STOPCHAR was unquoted. */ return p; /* The STOPCHAR was quoted by a backslash. Look for another. */ } else /* No backslash in sight. */ return p; } /* Never hit a STOPCHAR or blank (with BLANK nonzero). */ return 0; } /* Search PATTERN for an unquoted % and handle quoting. */ char * find_percent (char *pattern) { return find_char_unquote (pattern, '%', 0, 0, 0); } /* Search STRING for an unquoted % and handle quoting. Returns a pointer to the % or NULL if no % was found. This version is used with strings in the string cache: if there's a need to modify the string a new version will be added to the string cache and *STRING will be set to that. */ const char * find_percent_cached (const char **string) { const char *p = *string; char *new = 0; int slen = 0; /* If the first char is a % return now. This lets us avoid extra tests inside the loop. */ if (*p == '%') return p; while (1) { while (*p != '\0' && *p != '%') ++p; if (*p == '\0') break; /* See if this % is escaped with a backslash; if not we're done. */ if (p[-1] != '\\') break; { /* Search for more backslashes. */ char *pv; int i = -2; while (&p[i] >= *string && p[i] == '\\') --i; ++i; /* At this point we know we'll need to allocate a new string. Make a copy if we haven't yet done so. */ if (! new) { slen = strlen (*string); new = alloca (slen + 1); memcpy (new, *string, slen + 1); p = new + (p - *string); *string = new; } /* At this point *string, p, and new all point into the same string. Get a non-const version of p so we can modify new. */ pv = new + (p - *string); /* The number of backslashes is now -I. Copy P over itself to swallow half of them. */ memmove (&pv[i], &pv[i/2], (slen - (pv - new)) - (i/2) + 1); p += i/2; /* If the backslashes quoted each other; the % was unquoted. */ if (i % 2 == 0) break; } } /* If we had to change STRING, add it to the strcache. */ if (new) { *string = strcache_add (*string); p = *string + (p - new); } /* If we didn't find a %, return NULL. Otherwise return a ptr to it. */ return (*p == '\0') ? NULL : p; } /* Find the next line of text in an eval buffer, combining continuation lines into one line. Return the number of actual lines read (> 1 if continuation lines). Returns -1 if there's nothing left in the buffer. After this function, ebuf->buffer points to the first character of the line we just found. */ /* Read a line of text from a STRING. Since we aren't really reading from a file, don't bother with linenumbers. */ static unsigned long readstring (struct ebuffer *ebuf) { char *eol; /* If there is nothing left in this buffer, return 0. */ if (ebuf->bufnext >= ebuf->bufstart + ebuf->size) return -1; /* Set up a new starting point for the buffer, and find the end of the next logical line (taking into account backslash/newline pairs). */ eol = ebuf->buffer = ebuf->bufnext; while (1) { int backslash = 0; const char *bol = eol; const char *p; /* Find the next newline. At EOS, stop. */ p = eol = strchr (eol , '\n'); if (!eol) { ebuf->bufnext = ebuf->bufstart + ebuf->size + 1; return 0; } /* Found a newline; if it's escaped continue; else we're done. */ while (p > bol && *(--p) == '\\') backslash = !backslash; if (!backslash) break; ++eol; } /* Overwrite the newline char. */ *eol = '\0'; ebuf->bufnext = eol+1; return 0; } static long readline (struct ebuffer *ebuf) { char *p; char *end; char *start; long nlines = 0; /* The behaviors between string and stream buffers are different enough to warrant different functions. Do the Right Thing. */ if (!ebuf->fp) return readstring (ebuf); /* When reading from a file, we always start over at the beginning of the buffer for each new line. */ p = start = ebuf->bufstart; end = p + ebuf->size; *p = '\0'; while (fgets (p, end - p, ebuf->fp) != 0) { char *p2; unsigned long len; int backslash; len = strlen (p); if (len == 0) { /* This only happens when the first thing on the line is a '\0'. It is a pretty hopeless case, but (wonder of wonders) Athena lossage strikes again! (xmkmf puts NULs in its makefiles.) There is nothing really to be done; we synthesize a newline so the following line doesn't appear to be part of this line. */ error (&ebuf->floc, _("warning: NUL character seen; rest of line ignored")); p[0] = '\n'; len = 1; } /* Jump past the text we just read. */ p += len; /* If the last char isn't a newline, the whole line didn't fit into the buffer. Get some more buffer and try again. */ if (p[-1] != '\n') goto more_buffer; /* We got a newline, so add one to the count of lines. */ ++nlines; #if !defined(WINDOWS32) && !defined(__MSDOS__) && !defined(__EMX__) /* Check to see if the line was really ended with CRLF; if so ignore the CR. */ if ((p - start) > 1 && p[-2] == '\r') { --p; p[-1] = '\n'; } #endif backslash = 0; for (p2 = p - 2; p2 >= start; --p2) { if (*p2 != '\\') break; backslash = !backslash; } if (!backslash) { p[-1] = '\0'; break; } /* It was a backslash/newline combo. If we have more space, read another line. */ if (end - p >= 80) continue; /* We need more space at the end of our buffer, so realloc it. Make sure to preserve the current offset of p. */ more_buffer: { unsigned long off = p - start; ebuf->size *= 2; start = ebuf->buffer = ebuf->bufstart = xrealloc (start, ebuf->size); p = start + off; end = start + ebuf->size; *p = '\0'; } } if (ferror (ebuf->fp)) pfatal_with_name (ebuf->floc.filenm); /* If we found some lines, return how many. If we didn't, but we did find _something_, that indicates we read the last line of a file with no final newline; return 1. If we read nothing, we're at EOF; return -1. */ return nlines ? nlines : p == ebuf->bufstart ? -1 : 1; } /* Parse the next "makefile word" from the input buffer, and return info about it. A "makefile word" is one of: w_bogus Should never happen w_eol End of input w_static A static word; cannot be expanded w_variable A word containing one or more variables/functions w_colon A colon w_dcolon A double-colon w_semicolon A semicolon w_varassign A variable assignment operator (=, :=, +=, or ?=) Note that this function is only used when reading certain parts of the makefile. Don't use it where special rules hold sway (RHS of a variable, in a command list, etc.) */ static enum make_word_type get_next_mword (char *buffer, char *delim, char **startp, unsigned int *length) { enum make_word_type wtype = w_bogus; char *p = buffer, *beg; char c; /* Skip any leading whitespace. */ while (isblank ((unsigned char)*p)) ++p; beg = p; c = *(p++); switch (c) { case '\0': wtype = w_eol; break; case ';': wtype = w_semicolon; break; case '=': wtype = w_varassign; break; case ':': wtype = w_colon; switch (*p) { case ':': ++p; wtype = w_dcolon; break; case '=': ++p; wtype = w_varassign; break; } break; case '+': case '?': if (*p == '=') { ++p; wtype = w_varassign; break; } default: if (delim && strchr (delim, c)) wtype = w_static; break; } /* Did we find something? If so, return now. */ if (wtype != w_bogus) goto done; /* This is some non-operator word. A word consists of the longest string of characters that doesn't contain whitespace, one of [:=#], or [?+]=, or one of the chars in the DELIM string. */ /* We start out assuming a static word; if we see a variable we'll adjust our assumptions then. */ wtype = w_static; /* We already found the first value of "c", above. */ while (1) { char closeparen; int count; switch (c) { case '\0': case ' ': case '\t': case '=': goto done_word; case ':': #ifdef HAVE_DOS_PATHS /* A word CAN include a colon in its drive spec. The drive spec is allowed either at the beginning of a word, or as part of the archive member name, like in "libfoo.a(d:/foo/bar.o)". */ if (!(p - beg >= 2 && (*p == '/' || *p == '\\') && isalpha ((unsigned char)p[-2]) && (p - beg == 2 || p[-3] == '('))) #endif goto done_word; case '$': c = *(p++); if (c == '$') break; /* This is a variable reference, so note that it's expandable. Then read it to the matching close paren. */ wtype = w_variable; if (c == '(') closeparen = ')'; else if (c == '{') closeparen = '}'; else /* This is a single-letter variable reference. */ break; for (count=0; *p != '\0'; ++p) { if (*p == c) ++count; else if (*p == closeparen && --count < 0) { ++p; break; } } break; case '?': case '+': if (*p == '=') goto done_word; break; case '\\': switch (*p) { case ':': case ';': case '=': case '\\': ++p; break; } break; default: if (delim && strchr (delim, c)) goto done_word; break; } c = *(p++); } done_word: --p; done: if (startp) *startp = beg; if (length) *length = p - beg; return wtype; } /* Construct the list of include directories from the arguments and the default list. */ void construct_include_path (const char **arg_dirs) { #ifdef VAXC /* just don't ask ... */ stat_t stbuf; #else struct stat stbuf; #endif const char **dirs; const char **cpp; unsigned int idx; /* Compute the number of pointers we need in the table. */ idx = sizeof (default_include_directories) / sizeof (const char *); if (arg_dirs) for (cpp = arg_dirs; *cpp != 0; ++cpp) ++idx; #ifdef __MSDOS__ /* Add one for $DJDIR. */ ++idx; #endif dirs = xmalloc (idx * sizeof (const char *)); idx = 0; max_incl_len = 0; /* First consider any dirs specified with -I switches. Ignore any that don't exist. Remember the maximum string length. */ if (arg_dirs) while (*arg_dirs != 0) { const char *dir = *(arg_dirs++); char *expanded = 0; int e; if (dir[0] == '~') { expanded = tilde_expand (dir); if (expanded != 0) dir = expanded; } EINTRLOOP (e, stat (dir, &stbuf)); if (e == 0 && S_ISDIR (stbuf.st_mode)) { unsigned int len = strlen (dir); /* If dir name is written with trailing slashes, discard them. */ while (len > 1 && dir[len - 1] == '/') --len; if (len > max_incl_len) max_incl_len = len; dirs[idx++] = strcache_add_len (dir, len); } if (expanded) free (expanded); } /* Now add the standard default dirs at the end. */ #ifdef __MSDOS__ { /* The environment variable $DJDIR holds the root of the DJGPP directory tree; add ${DJDIR}/include. */ struct variable *djdir = lookup_variable ("DJDIR", 5); if (djdir) { unsigned int len = strlen (djdir->value) + 8; char *defdir = alloca (len + 1); strcat (strcpy (defdir, djdir->value), "/include"); dirs[idx++] = strcache_add (defdir); if (len > max_incl_len) max_incl_len = len; } } #endif for (cpp = default_include_directories; *cpp != 0; ++cpp) { int e; EINTRLOOP (e, stat (*cpp, &stbuf)); if (e == 0 && S_ISDIR (stbuf.st_mode)) { unsigned int len = strlen (*cpp); /* If dir name is written with trailing slashes, discard them. */ while (len > 1 && (*cpp)[len - 1] == '/') --len; if (len > max_incl_len) max_incl_len = len; dirs[idx++] = strcache_add_len (*cpp, len); } } dirs[idx] = 0; /* Now add each dir to the .INCLUDE_DIRS variable. */ for (cpp = dirs; *cpp != 0; ++cpp) do_variable_definition (NILF, ".INCLUDE_DIRS", *cpp, o_default, f_append, 0); include_directories = dirs; } /* Expand ~ or ~USER at the beginning of NAME. Return a newly malloc'd string or 0. */ char * tilde_expand (const char *name) { #ifndef VMS if (name[1] == '/' || name[1] == '\0') { extern char *getenv (); char *home_dir; int is_variable; { /* Turn off --warn-undefined-variables while we expand HOME. */ int save = warn_undefined_variables_flag; warn_undefined_variables_flag = 0; home_dir = allocated_variable_expand ("$(HOME)"); warn_undefined_variables_flag = save; } is_variable = home_dir[0] != '\0'; if (!is_variable) { free (home_dir); home_dir = getenv ("HOME"); } # if !defined(_AMIGA) && !defined(WINDOWS32) if (home_dir == 0 || home_dir[0] == '\0') { extern char *getlogin (); char *logname = getlogin (); home_dir = 0; if (logname != 0) { struct passwd *p = getpwnam (logname); if (p != 0) home_dir = p->pw_dir; } } # endif /* !AMIGA && !WINDOWS32 */ if (home_dir != 0) { char *new = xstrdup (concat (2, home_dir, name + 1)); if (is_variable) free (home_dir); return new; } } # if !defined(_AMIGA) && !defined(WINDOWS32) else { struct passwd *pwent; char *userend = strchr (name + 1, '/'); if (userend != 0) *userend = '\0'; pwent = getpwnam (name + 1); if (pwent != 0) { if (userend == 0) return xstrdup (pwent->pw_dir); else return xstrdup (concat (3, pwent->pw_dir, "/", userend + 1)); } else if (userend != 0) *userend = '/'; } # endif /* !AMIGA && !WINDOWS32 */ #endif /* !VMS */ return 0; } /* Parse a string into a sequence of filenames represented as a chain of struct nameseq's and return that chain. Optionally expand the strings via glob(). The string is passed as STRINGP, the address of a string pointer. The string pointer is updated to point at the first character not parsed, which either is a null char or equals STOPCHAR. SIZE is how big to construct chain elements. This is useful if we want them actually to be other structures that have room for additional info. PREFIX, if non-null, is added to the beginning of each filename. FLAGS allows one or more of the following bitflags to be set: PARSEFS_NOSTRIP - Do no strip './'s off the beginning PARSEFS_NOAR - Do not check filenames for archive references PARSEFS_NOGLOB - Do not expand globbing characters PARSEFS_EXISTS - Only return globbed files that actually exist (cannot also set NOGLOB) PARSEFS_NOCACHE - Do not add filenames to the strcache (caller frees) */ void * parse_file_seq (char **stringp, unsigned int size, int stopchar, const char *prefix, int flags) { extern void dir_setup_glob (glob_t *glob); /* tmp points to tmpbuf after the prefix, if any. tp is the end of the buffer. */ static char *tmpbuf = NULL; static int tmpbuf_len = 0; int cachep = (! (flags & PARSEFS_NOCACHE)); struct nameseq *new = 0; struct nameseq **newp = &new; #define NEWELT(_n) do { \ const char *__n = (_n); \ *newp = xcalloc (size); \ (*newp)->name = (cachep ? strcache_add (__n) : xstrdup (__n)); \ newp = &(*newp)->next; \ } while(0) char *p; glob_t gl; char *tp; #ifdef VMS # define VMS_COMMA ',' #else # define VMS_COMMA 0 #endif if (size < sizeof (struct nameseq)) size = sizeof (struct nameseq); if (! (flags & PARSEFS_NOGLOB)) dir_setup_glob (&gl); /* Get enough temporary space to construct the largest possible target. */ { int l = strlen (*stringp) + 1; if (l > tmpbuf_len) { tmpbuf = xrealloc (tmpbuf, l); tmpbuf_len = l; } } tp = tmpbuf; /* Parse STRING. P will always point to the end of the parsed content. */ p = *stringp; while (1) { const char *name; const char **nlist = 0; char *tildep = 0; #ifndef NO_ARCHIVES char *arname = 0; char *memname = 0; #endif char *s; int nlen; int i; /* Skip whitespace; at the end of the string or STOPCHAR we're done. */ p = next_token (p); if (*p == '\0' || *p == stopchar) break; /* There are names left, so find the end of the next name. Throughout this iteration S points to the start. */ s = p; p = find_char_unquote (p, stopchar, VMS_COMMA, 1, 0); #ifdef VMS /* convert comma separated list to space separated */ if (p && *p == ',') *p =' '; #endif #ifdef _AMIGA if (stopchar == ':' && p && *p == ':' && !(isspace ((unsigned char)p[1]) || !p[1] || isspace ((unsigned char)p[-1]))) p = find_char_unquote (p+1, stopchar, VMS_COMMA, 1, 0); #endif #ifdef HAVE_DOS_PATHS /* For DOS paths, skip a "C:\..." or a "C:/..." until we find the first colon which isn't followed by a slash or a backslash. Note that tokens separated by spaces should be treated as separate tokens since make doesn't allow path names with spaces */ if (stopchar == ':') while (p != 0 && !isspace ((unsigned char)*p) && (p[1] == '\\' || p[1] == '/') && isalpha ((unsigned char)p[-1])) p = find_char_unquote (p + 1, stopchar, VMS_COMMA, 1, 0); #endif if (p == 0) p = s + strlen (s); /* Strip leading "this directory" references. */ if (! (flags & PARSEFS_NOSTRIP)) #ifdef VMS /* Skip leading `[]'s. */ while (p - s > 2 && s[0] == '[' && s[1] == ']') #else /* Skip leading `./'s. */ while (p - s > 2 && s[0] == '.' && s[1] == '/') #endif { /* Skip "./" and all following slashes. */ s += 2; while (*s == '/') ++s; } /* Extract the filename just found, and skip it. Set NAME to the string, and NLEN to its length. */ if (s == p) { /* The name was stripped to empty ("./"). */ #if defined(VMS) continue; #elif defined(_AMIGA) /* PDS-- This cannot be right!! */ tp[0] = '\0'; nlen = 0; #else tp[0] = '.'; tp[1] = '/'; tp[2] = '\0'; nlen = 2; #endif } else { #ifdef VMS /* VMS filenames can have a ':' in them but they have to be '\'ed but we need * to remove this '\' before we can use the filename. * xstrdup called because S may be read-only string constant. */ char *n = tp; while (s < p) { if (s[0] == '\\' && s[1] == ':') ++s; *(n++) = *(s++); } n[0] = '\0'; nlen = strlen (tp); #else nlen = p - s; memcpy (tp, s, nlen); tp[nlen] = '\0'; #endif } /* At this point, TP points to the element and NLEN is its length. */ #ifndef NO_ARCHIVES /* If this is the start of an archive group that isn't complete, set up to add the archive prefix for future files. A file list like: "libf.a(x.o y.o z.o)" needs to be expanded as: "libf.a(x.o) libf.a(y.o) libf.a(z.o)" TP == TMP means we're not already in an archive group. Ignore something starting with `(', as that cannot actually be an archive-member reference (and treating it as such results in an empty file name, which causes much lossage). Also if it ends in ")" then it's a complete reference so we don't need to treat it specially. Finally, note that archive groups must end with ')' as the last character, so ensure there's some word ending like that before considering this an archive group. */ if (! (flags & PARSEFS_NOAR) && tp == tmpbuf && tp[0] != '(' && tp[nlen-1] != ')') { char *n = strchr (tp, '('); if (n) { /* This looks like the first element in an open archive group. A valid group MUST have ')' as the last character. */ const char *e = p + nlen; do { e = next_token (e); /* Find the end of this word. We don't want to unquote and we don't care about quoting since we're looking for the last char in the word. */ while (*e != '\0' && *e != stopchar && *e != VMS_COMMA && ! isblank ((unsigned char) *e)) ++e; if (e[-1] == ')') { /* Found the end, so this is the first element in an open archive group. It looks like "lib(mem". Reset TP past the open paren. */ nlen -= (n + 1) - tp; tp = n + 1; /* If we have just "lib(", part of something like "lib( a b)", go to the next item. */ if (! nlen) continue; /* We can stop looking now. */ break; } } while (*e != '\0'); } } /* If we are inside an archive group, make sure it has an end. */ if (tp > tmpbuf) { if (tp[nlen-1] == ')') { /* This is the natural end; reset TP. */ tp = tmpbuf; /* This is just ")", something like "lib(a b )": skip it. */ if (nlen == 1) continue; } else { /* Not the end, so add a "fake" end. */ tp[nlen++] = ')'; tp[nlen] = '\0'; } } #endif /* If we're not globbing we're done: add it to the end of the chain. Go to the next item in the string. */ if (flags & PARSEFS_NOGLOB) { NEWELT (concat (2, prefix, tp)); continue; } /* If we get here we know we're doing glob expansion. TP is a string in tmpbuf. NLEN is no longer used. We may need to do more work: after this NAME will be set. */ name = tp; /* Expand tilde if applicable. */ if (tp[0] == '~') { tildep = tilde_expand (tp); if (tildep != 0) name = tildep; } #ifndef NO_ARCHIVES /* If NAME is an archive member reference replace it with the archive file name, and save the member name in MEMNAME. We will glob on the archive name and then reattach MEMNAME later. */ if (! (flags & PARSEFS_NOAR) && ar_name (name)) { ar_parse_name (name, &arname, &memname); name = arname; } #endif /* !NO_ARCHIVES */ switch (glob (name, GLOB_NOSORT|GLOB_ALTDIRFUNC, NULL, &gl)) { case GLOB_NOSPACE: fatal (NILF, _("virtual memory exhausted")); case 0: /* Success. */ i = gl.gl_pathc; nlist = (const char **)gl.gl_pathv; break; case GLOB_NOMATCH: /* If we want only existing items, skip this one. */ if (flags & PARSEFS_EXISTS) { i = 0; break; } /* FALLTHROUGH */ default: /* By default keep this name. */ i = 1; nlist = &name; break; } /* For each matched element, add it to the list. */ while (i-- > 0) #ifndef NO_ARCHIVES if (memname != 0) { /* Try to glob on MEMNAME within the archive. */ struct nameseq *found = ar_glob (nlist[i], memname, size); if (! found) /* No matches. Use MEMNAME as-is. */ NEWELT (concat (5, prefix, nlist[i], "(", memname, ")")); else { /* We got a chain of items. Attach them. */ (*newp)->next = found; /* Find and set the new end. Massage names if necessary. */ while (1) { if (! cachep) found->name = xstrdup (concat (2, prefix, name)); else if (prefix) found->name = strcache_add (concat (2, prefix, name)); if (found->next == 0) break; found = found->next; } newp = &found->next; } } else #endif /* !NO_ARCHIVES */ NEWELT (concat (2, prefix, nlist[i])); globfree (&gl); #ifndef NO_ARCHIVES if (arname) free (arname); #endif if (tildep) free (tildep); } *stringp = p; return new; }
/* Copyright (C) 1991, 1992, 1995, 1996, 1997, 1998 Free Software Foundation, Inc. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef _GLOB_H #define _GLOB_H 1 #ifdef __cplusplus extern "C" { #endif #undef __ptr_t #if defined __cplusplus || (defined __STDC__ && __STDC__) || defined WINDOWS32 # if !defined __GLIBC__ # undef __P # undef __PMT # define __P(protos) protos # define __PMT(protos) protos # if !defined __GNUC__ || __GNUC__ < 2 # undef __const # define __const const # endif # endif # define __ptr_t void * #else /* Not C++ or ANSI C. */ # undef __P # undef __PMT # define __P(protos) () # define __PMT(protos) () # undef __const # define __const # define __ptr_t char * #endif /* C++ or ANSI C. */ /* We need `size_t' for the following definitions. */ #ifndef __size_t # if defined __FreeBSD__ # define __size_t size_t # else # if defined __GNUC__ && __GNUC__ >= 2 typedef __SIZE_TYPE__ __size_t; # else /* This is a guess. */ /*hb * Conflicts with DECCs aready defined type __size_t. * Defining an own type with a name beginning with '__' is no good. * Anyway if DECC is used and __SIZE_T is defined then __size_t is * already defined (and I hope it's exactly the one we need here). */ # if !(defined __DECC && defined __SIZE_T) typedef unsigned long int __size_t; # endif # endif # endif #else /* The GNU CC stddef.h version defines __size_t as empty. We need a real definition. */ # undef __size_t # define __size_t size_t #endif /* Bits set in the FLAGS argument to `glob'. */ #define GLOB_ERR (1 << 0)/* Return on read errors. */ #define GLOB_MARK (1 << 1)/* Append a slash to each name. */ #define GLOB_NOSORT (1 << 2)/* Don't sort the names. */ #define GLOB_DOOFFS (1 << 3)/* Insert PGLOB->gl_offs NULLs. */ #define GLOB_NOCHECK (1 << 4)/* If nothing matches, return the pattern. */ #define GLOB_APPEND (1 << 5)/* Append to results of a previous call. */ #define GLOB_NOESCAPE (1 << 6)/* Backslashes don't quote metacharacters. */ #define GLOB_PERIOD (1 << 7)/* Leading `.' can be matched by metachars. */ #if (!defined _POSIX_C_SOURCE || _POSIX_C_SOURCE < 2 || defined _BSD_SOURCE \ || defined _GNU_SOURCE) # define GLOB_MAGCHAR (1 << 8)/* Set in gl_flags if any metachars seen. */ # define GLOB_ALTDIRFUNC (1 << 9)/* Use gl_opendir et al functions. */ # define GLOB_BRACE (1 << 10)/* Expand "{a,b}" to "a" "b". */ # define GLOB_NOMAGIC (1 << 11)/* If no magic chars, return the pattern. */ # define GLOB_TILDE (1 << 12)/* Expand ~user and ~ to home directories. */ # define GLOB_ONLYDIR (1 << 13)/* Match only directories. */ # define GLOB_TILDE_CHECK (1 << 14)/* Like GLOB_TILDE but return an error if the user name is not available. */ # define __GLOB_FLAGS (GLOB_ERR|GLOB_MARK|GLOB_NOSORT|GLOB_DOOFFS| \ GLOB_NOESCAPE|GLOB_NOCHECK|GLOB_APPEND| \ GLOB_PERIOD|GLOB_ALTDIRFUNC|GLOB_BRACE| \ GLOB_NOMAGIC|GLOB_TILDE|GLOB_ONLYDIR|GLOB_TILDE_CHECK) #else # define __GLOB_FLAGS (GLOB_ERR|GLOB_MARK|GLOB_NOSORT|GLOB_DOOFFS| \ GLOB_NOESCAPE|GLOB_NOCHECK|GLOB_APPEND| \ GLOB_PERIOD) #endif /* Error returns from `glob'. */ #define GLOB_NOSPACE 1 /* Ran out of memory. */ #define GLOB_ABORTED 2 /* Read error. */ #define GLOB_NOMATCH 3 /* No matches found. */ #define GLOB_NOSYS 4 /* Not implemented. */ #ifdef _GNU_SOURCE /* Previous versions of this file defined GLOB_ABEND instead of GLOB_ABORTED. Provide a compatibility definition here. */ # define GLOB_ABEND GLOB_ABORTED #endif /* Structure describing a globbing run. */ #if !defined _AMIGA && !defined VMS /* Buggy compiler. */ struct stat; #endif typedef struct { __size_t gl_pathc; /* Count of paths matched by the pattern. */ char **gl_pathv; /* List of matched pathnames. */ __size_t gl_offs; /* Slots to reserve in `gl_pathv'. */ int gl_flags; /* Set to FLAGS, maybe | GLOB_MAGCHAR. */ /* If the GLOB_ALTDIRFUNC flag is set, the following functions are used instead of the normal file access functions. */ void (*gl_closedir) __PMT ((void *)); struct dirent *(*gl_readdir) __PMT ((void *)); __ptr_t (*gl_opendir) __PMT ((__const char *)); int (*gl_lstat) __PMT ((__const char *, struct stat *)); #if defined(VMS) && defined(__DECC) && !defined(_POSIX_C_SOURCE) int (*gl_stat) __PMT ((__const char *, struct stat *, ...)); #else int (*gl_stat) __PMT ((__const char *, struct stat *)); #endif } glob_t; #ifdef _LARGEFILE64_SOURCE struct stat64; typedef struct { __size_t gl_pathc; char **gl_pathv; __size_t gl_offs; int gl_flags; /* If the GLOB_ALTDIRFUNC flag is set, the following functions are used instead of the normal file access functions. */ void (*gl_closedir) __PMT ((void *)); struct dirent64 *(*gl_readdir) __PMT ((void *)); __ptr_t (*gl_opendir) __PMT ((__const char *)); int (*gl_lstat) __PMT ((__const char *, struct stat64 *)); int (*gl_stat) __PMT ((__const char *, struct stat64 *)); } glob64_t; #endif #if _FILE_OFFSET_BITS == 64 && __GNUC__ < 2 # define glob glob64 # define globfree globfree64 #else # ifdef _LARGEFILE64_SOURCE extern int glob64 __P ((__const char *__pattern, int __flags, int (*__errfunc) (__const char *, int), glob64_t *__pglob)); extern void globfree64 __P ((glob64_t *__pglob)); # endif #endif /* Do glob searching for PATTERN, placing results in PGLOB. The bits defined above may be set in FLAGS. If a directory cannot be opened or read and ERRFUNC is not nil, it is called with the pathname that caused the error, and the `errno' value from the failing call; if it returns non-zero `glob' returns GLOB_ABEND; if it returns zero, the error is ignored. If memory cannot be allocated for PGLOB, GLOB_NOSPACE is returned. Otherwise, `glob' returns zero. */ #if _FILE_OFFSET_BITS != 64 || __GNUC__ < 2 extern int glob __P ((__const char *__pattern, int __flags, int (*__errfunc) (__const char *, int), glob_t *__pglob)); /* Free storage allocated in PGLOB by a previous `glob' call. */ extern void globfree __P ((glob_t *__pglob)); #else extern int glob __P ((__const char *__pattern, int __flags, int (*__errfunc) (__const char *, int), glob_t *__pglob)) __asm__ ("glob64"); extern void globfree __P ((glob_t *__pglob)) __asm__ ("globfree64"); #endif #ifdef _GNU_SOURCE /* Return nonzero if PATTERN contains any metacharacters. Metacharacters can be quoted with backslashes if QUOTE is nonzero. This function is not part of the interface specified by POSIX.2 but several programs want to use it. */ extern int glob_pattern_p __P ((__const char *__pattern, int __quote)); #endif #ifdef __cplusplus } #endif #endif /* glob.h */
/* Basic dependency engine for GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" #include "filedef.h" #include "job.h" #include "commands.h" #include "dep.h" #include "variable.h" #include "debug.h" #include <assert.h> #ifdef HAVE_FCNTL_H #include <fcntl.h> #else #include <sys/file.h> #endif #ifdef VMS #include <starlet.h> #endif #ifdef WINDOWS32 #include <io.h> #endif extern int try_implicit_rule (struct file *file, unsigned int depth); /* The test for circular dependencies is based on the 'updating' bit in `struct file'. However, double colon targets have seperate `struct file's; make sure we always use the base of the double colon chain. */ #define start_updating(_f) (((_f)->double_colon ? (_f)->double_colon : (_f))\ ->updating = 1) #define finish_updating(_f) (((_f)->double_colon ? (_f)->double_colon : (_f))\ ->updating = 0) #define is_updating(_f) (((_f)->double_colon ? (_f)->double_colon : (_f))\ ->updating) /* Incremented when a command is started (under -n, when one would be). */ unsigned int commands_started = 0; /* Current value for pruning the scan of the goal chain (toggle 0/1). */ static unsigned int considered; static int update_file (struct file *file, unsigned int depth); static int update_file_1 (struct file *file, unsigned int depth); static int check_dep (struct file *file, unsigned int depth, FILE_TIMESTAMP this_mtime, int *must_make_ptr); static int touch_file (struct file *file); static void remake_file (struct file *file); static FILE_TIMESTAMP name_mtime (const char *name); static const char *library_search (const char *lib, FILE_TIMESTAMP *mtime_ptr); /* Remake all the goals in the `struct dep' chain GOALS. Return -1 if nothing was done, 0 if all goals were updated successfully, or 1 if a goal failed. If rebuilding_makefiles is nonzero, these goals are makefiles, so -t, -q, and -n should be disabled for them unless they were also command-line targets, and we should only make one goal at a time and return as soon as one goal whose `changed' member is nonzero is successfully made. */ int update_goal_chain (struct dep *goals) { int t = touch_flag, q = question_flag, n = just_print_flag; int status = -1; #define MTIME(file) (rebuilding_makefiles ? file_mtime_no_search (file) \ : file_mtime (file)) /* Duplicate the chain so we can remove things from it. */ goals = copy_dep_chain (goals); { /* Clear the `changed' flag of each goal in the chain. We will use the flag below to notice when any commands have actually been run for a target. When no commands have been run, we give an "up to date" diagnostic. */ struct dep *g; for (g = goals; g != 0; g = g->next) g->changed = 0; } /* All files start with the considered bit 0, so the global value is 1. */ considered = 1; /* Update all the goals until they are all finished. */ while (goals != 0) { register struct dep *g, *lastgoal; /* Start jobs that are waiting for the load to go down. */ start_waiting_jobs (); /* Wait for a child to die. */ reap_children (1, 0); lastgoal = 0; g = goals; while (g != 0) { /* Iterate over all double-colon entries for this file. */ struct file *file; int stop = 0, any_not_updated = 0; for (file = g->file->double_colon ? g->file->double_colon : g->file; file != NULL; file = file->prev) { unsigned int ocommands_started; int x; file->dontcare = g->dontcare; check_renamed (file); if (rebuilding_makefiles) { if (file->cmd_target) { touch_flag = t; question_flag = q; just_print_flag = n; } else touch_flag = question_flag = just_print_flag = 0; } /* Save the old value of `commands_started' so we can compare later. It will be incremented when any commands are actually run. */ ocommands_started = commands_started; x = update_file (file, rebuilding_makefiles ? 1 : 0); check_renamed (file); /* Set the goal's `changed' flag if any commands were started by calling update_file above. We check this flag below to decide when to give an "up to date" diagnostic. */ if (commands_started > ocommands_started) g->changed = 1; /* If we updated a file and STATUS was not already 1, set it to 1 if updating failed, or to 0 if updating succeeded. Leave STATUS as it is if no updating was done. */ stop = 0; if ((x != 0 || file->updated) && status < 1) { if (file->update_status != 0) { /* Updating failed, or -q triggered. The STATUS value tells our caller which. */ status = file->update_status; /* If -q just triggered, stop immediately. It doesn't matter how much more we run, since we already know the answer to return. */ stop = (question_flag && !keep_going_flag && !rebuilding_makefiles); } else { FILE_TIMESTAMP mtime = MTIME (file); check_renamed (file); if (file->updated && g->changed && mtime != file->mtime_before_update) { /* Updating was done. If this is a makefile and just_print_flag or question_flag is set (meaning -n or -q was given and this file was specified as a command-line target), don't change STATUS. If STATUS is changed, we will get re-exec'd, and enter an infinite loop. */ if (!rebuilding_makefiles || (!just_print_flag && !question_flag)) status = 0; if (rebuilding_makefiles && file->dontcare) /* This is a default makefile; stop remaking. */ stop = 1; } } } /* Keep track if any double-colon entry is not finished. When they are all finished, the goal is finished. */ any_not_updated |= !file->updated; file->dontcare = 0; if (stop) break; } /* Reset FILE since it is null at the end of the loop. */ file = g->file; if (stop || !any_not_updated) { /* If we have found nothing whatever to do for the goal, print a message saying nothing needs doing. */ if (!rebuilding_makefiles /* If the update_status is zero, we updated successfully or not at all. G->changed will have been set above if any commands were actually started for this goal. */ && file->update_status == 0 && !g->changed /* Never give a message under -s or -q. */ && !silent_flag && !question_flag) message (1, ((file->phony || file->cmds == 0) ? _("Nothing to be done for `%s'.") : _("`%s' is up to date.")), file->name); /* This goal is finished. Remove it from the chain. */ if (lastgoal == 0) goals = g->next; else lastgoal->next = g->next; /* Free the storage. */ free (g); g = lastgoal == 0 ? goals : lastgoal->next; if (stop) break; } else { lastgoal = g; g = g->next; } } /* If we reached the end of the dependency graph toggle the considered flag for the next pass. */ if (g == 0) considered = !considered; } if (rebuilding_makefiles) { touch_flag = t; question_flag = q; just_print_flag = n; } return status; } /* If FILE is not up to date, execute the commands for it. Return 0 if successful, 1 if unsuccessful; but with some flag settings, just call `exit' if unsuccessful. DEPTH is the depth in recursions of this function. We increment it during the consideration of our dependencies, then decrement it again after finding out whether this file is out of date. If there are multiple double-colon entries for FILE, each is considered in turn. */ static int update_file (struct file *file, unsigned int depth) { register int status = 0; register struct file *f; f = file->double_colon ? file->double_colon : file; /* Prune the dependency graph: if we've already been here on _this_ pass through the dependency graph, we don't have to go any further. We won't reap_children until we start the next pass, so no state change is possible below here until then. */ if (f->considered == considered) { /* Check for the case where a target has been tried and failed but the diagnostics hasn't been issued. If we need the diagnostics then we will have to continue. */ if (!(f->updated && f->update_status > 0 && !f->dontcare && f->no_diag)) { DBF (DB_VERBOSE, _("Pruning file `%s'.\n")); return f->command_state == cs_finished ? f->update_status : 0; } } /* This loop runs until we start commands for a double colon rule, or until the chain is exhausted. */ for (; f != 0; f = f->prev) { f->considered = considered; status |= update_file_1 (f, depth); check_renamed (f); /* Clean up any alloca() used during the update. */ alloca (0); /* If we got an error, don't bother with double_colon etc. */ if (status != 0 && !keep_going_flag) return status; if (f->command_state == cs_running || f->command_state == cs_deps_running) { /* Don't run the other :: rules for this file until this rule is finished. */ status = 0; break; } } /* Process the remaining rules in the double colon chain so they're marked considered. Start their prerequisites, too. */ if (file->double_colon) for (; f != 0 ; f = f->prev) { struct dep *d; f->considered = considered; for (d = f->deps; d != 0; d = d->next) status |= update_file (d->file, depth + 1); } return status; } /* Show a message stating the target failed to build. */ static void complain (struct file *file) { const char *msg_noparent = _("%sNo rule to make target `%s'%s"); const char *msg_parent = _("%sNo rule to make target `%s', needed by `%s'%s"); /* If this file has no_diag set then it means we tried to update it before in the dontcare mode and failed. The target that actually failed is not necessarily this file but could be one of its direct or indirect dependencies. So traverse this file's dependencies and find the one that actually caused the failure. */ struct dep *d; for (d = file->deps; d != 0; d = d->next) { if (d->file->updated && d->file->update_status > 0 && file->no_diag) { complain (d->file); break; } } if (d == 0) { /* Didn't find any dependencies to complain about. */ if (!keep_going_flag) { if (file->parent == 0) fatal (NILF, msg_noparent, "", file->name, ""); fatal (NILF, msg_parent, "", file->name, file->parent->name, ""); } if (file->parent == 0) error (NILF, msg_noparent, "*** ", file->name, "."); else error (NILF, msg_parent, "*** ", file->name, file->parent->name, "."); file->no_diag = 0; } } /* Consider a single `struct file' and update it as appropriate. */ static int update_file_1 (struct file *file, unsigned int depth) { FILE_TIMESTAMP this_mtime; int noexist, must_make, deps_changed; int dep_status = 0; struct file *ofile; struct dep *d, *ad; struct dep amake; int running = 0; DBF (DB_VERBOSE, _("Considering target file `%s'.\n")); if (file->updated) { if (file->update_status > 0) { DBF (DB_VERBOSE, _("Recently tried and failed to update file `%s'.\n")); /* If the file we tried to make is marked no_diag then no message was printed about it when it failed during the makefile rebuild. If we're trying to build it again in the normal rebuild, print a message now. */ if (file->no_diag && !file->dontcare) complain (file); return file->update_status; } DBF (DB_VERBOSE, _("File `%s' was considered already.\n")); return 0; } switch (file->command_state) { case cs_not_started: case cs_deps_running: break; case cs_running: DBF (DB_VERBOSE, _("Still updating file `%s'.\n")); return 0; case cs_finished: DBF (DB_VERBOSE, _("Finished updating file `%s'.\n")); return file->update_status; default: abort (); } /* Determine whether the diagnostics will be issued should this update fail. */ file->no_diag = file->dontcare; ++depth; /* Notice recursive update of the same file. */ start_updating (file); /* We might change file if we find a different one via vpath; remember this one to turn off updating. */ ofile = file; /* Looking at the file's modtime beforehand allows the possibility that its name may be changed by a VPATH search, and thus it may not need an implicit rule. If this were not done, the file might get implicit commands that apply to its initial name, only to have that name replaced with another found by VPATH search. */ this_mtime = file_mtime (file); check_renamed (file); noexist = this_mtime == NONEXISTENT_MTIME; if (noexist) DBF (DB_BASIC, _("File `%s' does not exist.\n")); else if (ORDINARY_MTIME_MIN <= this_mtime && this_mtime <= ORDINARY_MTIME_MAX && file->low_resolution_time) { /* Avoid spurious rebuilds due to low resolution time stamps. */ int ns = FILE_TIMESTAMP_NS (this_mtime); if (ns != 0) error (NILF, _("*** Warning: .LOW_RESOLUTION_TIME file `%s' has a high resolution time stamp"), file->name); this_mtime += FILE_TIMESTAMPS_PER_S - 1 - ns; } must_make = noexist; /* If file was specified as a target with no commands, come up with some default commands. */ if (!file->phony && file->cmds == 0 && !file->tried_implicit) { if (try_implicit_rule (file, depth)) DBF (DB_IMPLICIT, _("Found an implicit rule for `%s'.\n")); else DBF (DB_IMPLICIT, _("No implicit rule found for `%s'.\n")); file->tried_implicit = 1; } if (file->cmds == 0 && !file->is_target && default_file != 0 && default_file->cmds != 0) { DBF (DB_IMPLICIT, _("Using default recipe for `%s'.\n")); file->cmds = default_file->cmds; } /* Update all non-intermediate files we depend on, if necessary, and see whether any of them is more recent than this file. We need to walk our deps, AND the deps of any also_make targets to ensure everything happens in the correct order. */ amake.file = file; amake.next = file->also_make; ad = &amake; while (ad) { struct dep *lastd = 0; /* Find the deps we're scanning */ d = ad->file->deps; ad = ad->next; while (d) { FILE_TIMESTAMP mtime; int maybe_make; int dontcare = 0; check_renamed (d->file); mtime = file_mtime (d->file); check_renamed (d->file); if (is_updating (d->file)) { error (NILF, _("Circular %s <- %s dependency dropped."), file->name, d->file->name); /* We cannot free D here because our the caller will still have a reference to it when we were called recursively via check_dep below. */ if (lastd == 0) file->deps = d->next; else lastd->next = d->next; d = d->next; continue; } d->file->parent = file; maybe_make = must_make; /* Inherit dontcare flag from our parent. */ if (rebuilding_makefiles) { dontcare = d->file->dontcare; d->file->dontcare = file->dontcare; } dep_status |= check_dep (d->file, depth, this_mtime, &maybe_make); /* Restore original dontcare flag. */ if (rebuilding_makefiles) d->file->dontcare = dontcare; if (! d->ignore_mtime) must_make = maybe_make; check_renamed (d->file); { register struct file *f = d->file; if (f->double_colon) f = f->double_colon; do { running |= (f->command_state == cs_running || f->command_state == cs_deps_running); f = f->prev; } while (f != 0); } if (dep_status != 0 && !keep_going_flag) break; if (!running) /* The prereq is considered changed if the timestamp has changed while it was built, OR it doesn't exist. */ d->changed = ((file_mtime (d->file) != mtime) || (mtime == NONEXISTENT_MTIME)); lastd = d; d = d->next; } } /* Now we know whether this target needs updating. If it does, update all the intermediate files we depend on. */ if (must_make || always_make_flag) { for (d = file->deps; d != 0; d = d->next) if (d->file->intermediate) { int dontcare = 0; FILE_TIMESTAMP mtime = file_mtime (d->file); check_renamed (d->file); d->file->parent = file; /* Inherit dontcare flag from our parent. */ if (rebuilding_makefiles) { dontcare = d->file->dontcare; d->file->dontcare = file->dontcare; } dep_status |= update_file (d->file, depth); /* Restore original dontcare flag. */ if (rebuilding_makefiles) d->file->dontcare = dontcare; check_renamed (d->file); { register struct file *f = d->file; if (f->double_colon) f = f->double_colon; do { running |= (f->command_state == cs_running || f->command_state == cs_deps_running); f = f->prev; } while (f != 0); } if (dep_status != 0 && !keep_going_flag) break; if (!running) d->changed = ((file->phony && file->cmds != 0) || file_mtime (d->file) != mtime); } } finish_updating (file); finish_updating (ofile); DBF (DB_VERBOSE, _("Finished prerequisites of target file `%s'.\n")); if (running) { set_command_state (file, cs_deps_running); --depth; DBF (DB_VERBOSE, _("The prerequisites of `%s' are being made.\n")); return 0; } /* If any dependency failed, give up now. */ if (dep_status != 0) { file->update_status = dep_status; notice_finished_file (file); --depth; DBF (DB_VERBOSE, _("Giving up on target file `%s'.\n")); if (depth == 0 && keep_going_flag && !just_print_flag && !question_flag) error (NILF, _("Target `%s' not remade because of errors."), file->name); return dep_status; } if (file->command_state == cs_deps_running) /* The commands for some deps were running on the last iteration, but they have finished now. Reset the command_state to not_started to simplify later bookkeeping. It is important that we do this only when the prior state was cs_deps_running, because that prior state was definitely propagated to FILE's also_make's by set_command_state (called above), but in another state an also_make may have independently changed to finished state, and we would confuse that file's bookkeeping (updated, but not_started is bogus state). */ set_command_state (file, cs_not_started); /* Now record which prerequisites are more recent than this file, so we can define $?. */ deps_changed = 0; for (d = file->deps; d != 0; d = d->next) { FILE_TIMESTAMP d_mtime = file_mtime (d->file); check_renamed (d->file); if (! d->ignore_mtime) { #if 1 /* %%% In version 4, remove this code completely to implement not remaking deps if their deps are newer than their parents. */ if (d_mtime == NONEXISTENT_MTIME && !d->file->intermediate) /* We must remake if this dep does not exist and is not intermediate. */ must_make = 1; #endif /* Set DEPS_CHANGED if this dep actually changed. */ deps_changed |= d->changed; } /* Set D->changed if either this dep actually changed, or its dependent, FILE, is older or does not exist. */ d->changed |= noexist || d_mtime > this_mtime; if (!noexist && ISDB (DB_BASIC|DB_VERBOSE)) { const char *fmt = 0; if (d->ignore_mtime) { if (ISDB (DB_VERBOSE)) fmt = _("Prerequisite `%s' is order-only for target `%s'.\n"); } else if (d_mtime == NONEXISTENT_MTIME) { if (ISDB (DB_BASIC)) fmt = _("Prerequisite `%s' of target `%s' does not exist.\n"); } else if (d->changed) { if (ISDB (DB_BASIC)) fmt = _("Prerequisite `%s' is newer than target `%s'.\n"); } else if (ISDB (DB_VERBOSE)) fmt = _("Prerequisite `%s' is older than target `%s'.\n"); if (fmt) { print_spaces (depth); printf (fmt, dep_name (d), file->name); fflush (stdout); } } } /* Here depth returns to the value it had when we were called. */ depth--; if (file->double_colon && file->deps == 0) { must_make = 1; DBF (DB_BASIC, _("Target `%s' is double-colon and has no prerequisites.\n")); } else if (!noexist && file->is_target && !deps_changed && file->cmds == 0 && !always_make_flag) { must_make = 0; DBF (DB_VERBOSE, _("No recipe for `%s' and no prerequisites actually changed.\n")); } else if (!must_make && file->cmds != 0 && always_make_flag) { must_make = 1; DBF (DB_VERBOSE, _("Making `%s' due to always-make flag.\n")); } if (!must_make) { if (ISDB (DB_VERBOSE)) { print_spaces (depth); printf (_("No need to remake target `%s'"), file->name); if (!streq (file->name, file->hname)) printf (_("; using VPATH name `%s'"), file->hname); puts ("."); fflush (stdout); } notice_finished_file (file); /* Since we don't need to remake the file, convert it to use the VPATH filename if we found one. hfile will be either the local name if no VPATH or the VPATH name if one was found. */ while (file) { file->name = file->hname; file = file->prev; } return 0; } DBF (DB_BASIC, _("Must remake target `%s'.\n")); /* It needs to be remade. If it's VPATH and not reset via GPATH, toss the VPATH. */ if (!streq(file->name, file->hname)) { DB (DB_BASIC, (_(" Ignoring VPATH name `%s'.\n"), file->hname)); file->ignore_vpath = 1; } /* Now, take appropriate actions to remake the file. */ remake_file (file); if (file->command_state != cs_finished) { DBF (DB_VERBOSE, _("Recipe of `%s' is being run.\n")); return 0; } switch (file->update_status) { case 2: DBF (DB_BASIC, _("Failed to remake target file `%s'.\n")); break; case 0: DBF (DB_BASIC, _("Successfully remade target file `%s'.\n")); break; case 1: DBF (DB_BASIC, _("Target file `%s' needs remade under -q.\n")); break; default: assert (file->update_status >= 0 && file->update_status <= 2); break; } file->updated = 1; return file->update_status; } /* Set FILE's `updated' flag and re-check its mtime and the mtime's of all files listed in its `also_make' member. Under -t, this function also touches FILE. On return, FILE->update_status will no longer be -1 if it was. */ void notice_finished_file (struct file *file) { struct dep *d; int ran = file->command_state == cs_running; int touched = 0; file->command_state = cs_finished; file->updated = 1; if (touch_flag /* The update status will be: -1 if this target was not remade; 0 if 0 or more commands (+ or ${MAKE}) were run and won; 1 if some commands were run and lost. We touch the target if it has commands which either were not run or won when they ran (i.e. status is 0). */ && file->update_status == 0) { if (file->cmds != 0 && file->cmds->any_recurse) { /* If all the command lines were recursive, we don't want to do the touching. */ unsigned int i; for (i = 0; i < file->cmds->ncommand_lines; ++i) if (!(file->cmds->lines_flags[i] & COMMANDS_RECURSE)) goto have_nonrecursing; } else { have_nonrecursing: if (file->phony) file->update_status = 0; /* According to POSIX, -t doesn't affect targets with no cmds. */ else if (file->cmds != 0) { /* Should set file's modification date and do nothing else. */ file->update_status = touch_file (file); /* Pretend we ran a real touch command, to suppress the "`foo' is up to date" message. */ commands_started++; /* Request for the timestamp to be updated (and distributed to the double-colon entries). Simply setting ran=1 would almost have done the trick, but messes up with the also_make updating logic below. */ touched = 1; } } } if (file->mtime_before_update == UNKNOWN_MTIME) file->mtime_before_update = file->last_mtime; if ((ran && !file->phony) || touched) { int i = 0; /* If -n, -t, or -q and all the commands are recursive, we ran them so really check the target's mtime again. Otherwise, assume the target would have been updated. */ if ((question_flag || just_print_flag || touch_flag) && file->cmds) { for (i = file->cmds->ncommand_lines; i > 0; --i) if (! (file->cmds->lines_flags[i-1] & COMMANDS_RECURSE)) break; } /* If there were no commands at all, it's always new. */ else if (file->is_target && file->cmds == 0) i = 1; file->last_mtime = i == 0 ? UNKNOWN_MTIME : NEW_MTIME; } if (file->double_colon) { /* If this is a double colon rule and it is the last one to be updated, propagate the change of modification time to all the double-colon entries for this file. We do it on the last update because it is important to handle individual entries as separate rules with separate timestamps while they are treated as targets and then as one rule with the unified timestamp when they are considered as a prerequisite of some target. */ struct file *f; FILE_TIMESTAMP max_mtime = file->last_mtime; /* Check that all rules were updated and at the same time find the max timestamp. We assume UNKNOWN_MTIME is newer then any other value. */ for (f = file->double_colon; f != 0 && f->updated; f = f->prev) if (max_mtime != UNKNOWN_MTIME && (f->last_mtime == UNKNOWN_MTIME || f->last_mtime > max_mtime)) max_mtime = f->last_mtime; if (f == 0) for (f = file->double_colon; f != 0; f = f->prev) f->last_mtime = max_mtime; } if (ran && file->update_status != -1) /* We actually tried to update FILE, which has updated its also_make's as well (if it worked). If it didn't work, it wouldn't work again for them. So mark them as updated with the same status. */ for (d = file->also_make; d != 0; d = d->next) { d->file->command_state = cs_finished; d->file->updated = 1; d->file->update_status = file->update_status; if (ran && !d->file->phony) /* Fetch the new modification time. We do this instead of just invalidating the cached time so that a vpath_search can happen. Otherwise, it would never be done because the target is already updated. */ f_mtime (d->file, 0); } else if (file->update_status == -1) /* Nothing was done for FILE, but it needed nothing done. So mark it now as "succeeded". */ file->update_status = 0; } /* Check whether another file (whose mtime is THIS_MTIME) needs updating on account of a dependency which is file FILE. If it does, store 1 in *MUST_MAKE_PTR. In the process, update any non-intermediate files that FILE depends on (including FILE itself). Return nonzero if any updating failed. */ static int check_dep (struct file *file, unsigned int depth, FILE_TIMESTAMP this_mtime, int *must_make_ptr) { struct file *ofile; struct dep *d; int dep_status = 0; ++depth; start_updating (file); /* We might change file if we find a different one via vpath; remember this one to turn off updating. */ ofile = file; if (file->phony || !file->intermediate) { /* If this is a non-intermediate file, update it and record whether it is newer than THIS_MTIME. */ FILE_TIMESTAMP mtime; dep_status = update_file (file, depth); check_renamed (file); mtime = file_mtime (file); check_renamed (file); if (mtime == NONEXISTENT_MTIME || mtime > this_mtime) *must_make_ptr = 1; } else { /* FILE is an intermediate file. */ FILE_TIMESTAMP mtime; if (!file->phony && file->cmds == 0 && !file->tried_implicit) { if (try_implicit_rule (file, depth)) DBF (DB_IMPLICIT, _("Found an implicit rule for `%s'.\n")); else DBF (DB_IMPLICIT, _("No implicit rule found for `%s'.\n")); file->tried_implicit = 1; } if (file->cmds == 0 && !file->is_target && default_file != 0 && default_file->cmds != 0) { DBF (DB_IMPLICIT, _("Using default commands for `%s'.\n")); file->cmds = default_file->cmds; } check_renamed (file); mtime = file_mtime (file); check_renamed (file); if (mtime != NONEXISTENT_MTIME && mtime > this_mtime) /* If the intermediate file actually exists and is newer, then we should remake from it. */ *must_make_ptr = 1; else { /* Otherwise, update all non-intermediate files we depend on, if necessary, and see whether any of them is more recent than the file on whose behalf we are checking. */ struct dep *ld; int deps_running = 0; /* If this target is not running, set it's state so that we check it fresh. It could be it was checked as part of an order-only prerequisite and so wasn't rebuilt then, but should be now. */ if (file->command_state != cs_running) set_command_state (file, cs_not_started); ld = 0; d = file->deps; while (d != 0) { int maybe_make; if (is_updating (d->file)) { error (NILF, _("Circular %s <- %s dependency dropped."), file->name, d->file->name); if (ld == 0) { file->deps = d->next; free_dep (d); d = file->deps; } else { ld->next = d->next; free_dep (d); d = ld->next; } continue; } d->file->parent = file; maybe_make = *must_make_ptr; dep_status |= check_dep (d->file, depth, this_mtime, &maybe_make); if (! d->ignore_mtime) *must_make_ptr = maybe_make; check_renamed (d->file); if (dep_status != 0 && !keep_going_flag) break; if (d->file->command_state == cs_running || d->file->command_state == cs_deps_running) deps_running = 1; ld = d; d = d->next; } if (deps_running) /* Record that some of FILE's deps are still being made. This tells the upper levels to wait on processing it until the commands are finished. */ set_command_state (file, cs_deps_running); } } finish_updating (file); finish_updating (ofile); return dep_status; } /* Touch FILE. Return zero if successful, one if not. */ #define TOUCH_ERROR(call) return (perror_with_name (call, file->name), 1) static int touch_file (struct file *file) { if (!silent_flag) message (0, "touch %s", file->name); #ifndef NO_ARCHIVES if (ar_name (file->name)) return ar_touch (file->name); else #endif { int fd = open (file->name, O_RDWR | O_CREAT, 0666); if (fd < 0) TOUCH_ERROR ("touch: open: "); else { struct stat statbuf; char buf = 'x'; int e; EINTRLOOP (e, fstat (fd, &statbuf)); if (e < 0) TOUCH_ERROR ("touch: fstat: "); /* Rewrite character 0 same as it already is. */ if (read (fd, &buf, 1) < 0) TOUCH_ERROR ("touch: read: "); if (lseek (fd, 0L, 0) < 0L) TOUCH_ERROR ("touch: lseek: "); if (write (fd, &buf, 1) < 0) TOUCH_ERROR ("touch: write: "); /* If file length was 0, we just changed it, so change it back. */ if (statbuf.st_size == 0) { (void) close (fd); fd = open (file->name, O_RDWR | O_TRUNC, 0666); if (fd < 0) TOUCH_ERROR ("touch: open: "); } (void) close (fd); } } return 0; } /* Having checked and updated the dependencies of FILE, do whatever is appropriate to remake FILE itself. Return the status from executing FILE's commands. */ static void remake_file (struct file *file) { if (file->cmds == 0) { if (file->phony) /* Phony target. Pretend it succeeded. */ file->update_status = 0; else if (file->is_target) /* This is a nonexistent target file we cannot make. Pretend it was successfully remade. */ file->update_status = 0; else { /* This is a dependency file we cannot remake. Fail. */ if (!rebuilding_makefiles || !file->dontcare) complain (file); file->update_status = 2; } } else { chop_commands (file->cmds); /* The normal case: start some commands. */ if (!touch_flag || file->cmds->any_recurse) { execute_file_commands (file); return; } /* This tells notice_finished_file it is ok to touch the file. */ file->update_status = 0; } /* This does the touching under -t. */ notice_finished_file (file); } /* Return the mtime of a file, given a `struct file'. Caches the time in the struct file to avoid excess stat calls. If the file is not found, and SEARCH is nonzero, VPATH searching and replacement is done. If that fails, a library (-lLIBNAME) is tried and the library's actual name (/lib/libLIBNAME.a, etc.) is substituted into FILE. */ FILE_TIMESTAMP f_mtime (struct file *file, int search) { FILE_TIMESTAMP mtime; /* File's mtime is not known; must get it from the system. */ #ifndef NO_ARCHIVES if (ar_name (file->name)) { /* This file is an archive-member reference. */ char *arname, *memname; struct file *arfile; time_t member_date; /* Find the archive's name. */ ar_parse_name (file->name, &arname, &memname); /* Find the modification time of the archive itself. Also allow for its name to be changed via VPATH search. */ arfile = lookup_file (arname); if (arfile == 0) arfile = enter_file (strcache_add (arname)); mtime = f_mtime (arfile, search); check_renamed (arfile); if (search && strcmp (arfile->hname, arname)) { /* The archive's name has changed. Change the archive-member reference accordingly. */ char *name; unsigned int arlen, memlen; arlen = strlen (arfile->hname); memlen = strlen (memname); name = xmalloc (arlen + 1 + memlen + 2); memcpy (name, arfile->hname, arlen); name[arlen] = '('; memcpy (name + arlen + 1, memname, memlen); name[arlen + 1 + memlen] = ')'; name[arlen + 1 + memlen + 1] = '\0'; /* If the archive was found with GPATH, make the change permanent; otherwise defer it until later. */ if (arfile->name == arfile->hname) rename_file (file, name); else rehash_file (file, name); check_renamed (file); } free (arname); file->low_resolution_time = 1; if (mtime == NONEXISTENT_MTIME) /* The archive doesn't exist, so its members don't exist either. */ return NONEXISTENT_MTIME; member_date = ar_member_date (file->hname); mtime = (member_date == (time_t) -1 ? NONEXISTENT_MTIME : file_timestamp_cons (file->hname, member_date, 0)); } else #endif { mtime = name_mtime (file->name); if (mtime == NONEXISTENT_MTIME && search && !file->ignore_vpath) { /* If name_mtime failed, search VPATH. */ const char *name = vpath_search (file->name, &mtime, NULL, NULL); if (name /* Last resort, is it a library (-lxxx)? */ || (file->name[0] == '-' && file->name[1] == 'l' && (name = library_search (file->name, &mtime)) != 0)) { if (mtime != UNKNOWN_MTIME) /* vpath_search and library_search store UNKNOWN_MTIME if they didn't need to do a stat call for their work. */ file->last_mtime = mtime; /* If we found it in VPATH, see if it's in GPATH too; if so, change the name right now; if not, defer until after the dependencies are updated. */ if (gpath_search (name, strlen(name) - strlen(file->name) - 1)) { rename_file (file, name); check_renamed (file); return file_mtime (file); } rehash_file (file, name); check_renamed (file); /* If the result of a vpath search is -o or -W, preserve it. Otherwise, find the mtime of the resulting file. */ if (mtime != OLD_MTIME && mtime != NEW_MTIME) mtime = name_mtime (name); } } } /* Files can have bogus timestamps that nothing newly made will be "newer" than. Updating their dependents could just result in loops. So notify the user of the anomaly with a warning. We only need to do this once, for now. */ if (!clock_skew_detected && mtime != NONEXISTENT_MTIME && mtime != NEW_MTIME && !file->updated) { static FILE_TIMESTAMP adjusted_now; FILE_TIMESTAMP adjusted_mtime = mtime; #if defined(WINDOWS32) || defined(__MSDOS__) /* Experimentation has shown that FAT filesystems can set file times up to 3 seconds into the future! Play it safe. */ #define FAT_ADJ_OFFSET (FILE_TIMESTAMP) 3 FILE_TIMESTAMP adjustment = FAT_ADJ_OFFSET << FILE_TIMESTAMP_LO_BITS; if (ORDINARY_MTIME_MIN + adjustment <= adjusted_mtime) adjusted_mtime -= adjustment; #elif defined(__EMX__) /* FAT filesystems round time to the nearest even second! Allow for any file (NTFS or FAT) to perhaps suffer from this brain damage. */ FILE_TIMESTAMP adjustment = (((FILE_TIMESTAMP_S (adjusted_mtime) & 1) == 0 && FILE_TIMESTAMP_NS (adjusted_mtime) == 0) ? (FILE_TIMESTAMP) 1 << FILE_TIMESTAMP_LO_BITS : 0); #endif /* If the file's time appears to be in the future, update our concept of the present and try once more. */ if (adjusted_now < adjusted_mtime) { int resolution; FILE_TIMESTAMP now = file_timestamp_now (&resolution); adjusted_now = now + (resolution - 1); if (adjusted_now < adjusted_mtime) { #ifdef NO_FLOAT error (NILF, _("Warning: File `%s' has modification time in the future"), file->name); #else double from_now = (FILE_TIMESTAMP_S (mtime) - FILE_TIMESTAMP_S (now) + ((FILE_TIMESTAMP_NS (mtime) - FILE_TIMESTAMP_NS (now)) / 1e9)); char from_now_string[100]; if (from_now >= 99 && from_now <= ULONG_MAX) sprintf (from_now_string, "%lu", (unsigned long) from_now); else sprintf (from_now_string, "%.2g", from_now); error (NILF, _("Warning: File `%s' has modification time %s s in the future"), file->name, from_now_string); #endif clock_skew_detected = 1; } } } /* Store the mtime into all the entries for this file. */ if (file->double_colon) file = file->double_colon; do { /* If this file is not implicit but it is intermediate then it was made so by the .INTERMEDIATE target. If this file has never been built by us but was found now, it existed before make started. So, turn off the intermediate bit so make doesn't delete it, since it didn't create it. */ if (mtime != NONEXISTENT_MTIME && file->command_state == cs_not_started && file->command_state == cs_not_started && !file->tried_implicit && file->intermediate) file->intermediate = 0; file->last_mtime = mtime; file = file->prev; } while (file != 0); return mtime; } /* Return the mtime of the file or archive-member reference NAME. */ /* First, we check with stat(). If the file does not exist, then we return NONEXISTENT_MTIME. If it does, and the symlink check flag is set, then examine each indirection of the symlink and find the newest mtime. This causes one duplicate stat() when -L is being used, but the code is much cleaner. */ static FILE_TIMESTAMP name_mtime (const char *name) { FILE_TIMESTAMP mtime; struct stat st; int e; EINTRLOOP (e, stat (name, &st)); if (e == 0) mtime = FILE_TIMESTAMP_STAT_MODTIME (name, st); else if (errno == ENOENT || errno == ENOTDIR) mtime = NONEXISTENT_MTIME; else { perror_with_name ("stat: ", name); return NONEXISTENT_MTIME; } /* If we get here we either found it, or it doesn't exist. If it doesn't exist see if we can use a symlink mtime instead. */ #ifdef MAKE_SYMLINKS #ifndef S_ISLNK # define S_ISLNK(_m) (((_m)&S_IFMT)==S_IFLNK) #endif if (check_symlink_flag) { PATH_VAR (lpath); /* Check each symbolic link segment (if any). Find the latest mtime amongst all of them (and the target file of course). Note that we have already successfully dereferenced all the links above. So, if we run into any error trying to lstat(), or readlink(), or whatever, something bizarre-o happened. Just give up and use whatever mtime we've already computed at that point. */ strcpy (lpath, name); while (1) { FILE_TIMESTAMP ltime; PATH_VAR (lbuf); long llen; char *p; EINTRLOOP (e, lstat (lpath, &st)); if (e) { /* Just take what we have so far. */ if (errno != ENOENT && errno != ENOTDIR) perror_with_name ("lstat: ", lpath); break; } /* If this is not a symlink, we're done (we started with the real file's mtime so we don't need to test it again). */ if (!S_ISLNK (st.st_mode)) break; /* If this mtime is newer than what we had, keep the new one. */ ltime = FILE_TIMESTAMP_STAT_MODTIME (lpath, st); if (ltime > mtime) mtime = ltime; /* Set up to check the file pointed to by this link. */ EINTRLOOP (llen, readlink (lpath, lbuf, GET_PATH_MAX)); if (llen < 0) { /* Eh? Just take what we have. */ perror_with_name ("readlink: ", lpath); break; } lbuf[llen] = '\0'; /* If the target is fully-qualified or the source is just a filename, then the new path is the target. Otherwise it's the source directory plus the target. */ if (lbuf[0] == '/' || (p = strrchr (lpath, '/')) == NULL) strcpy (lpath, lbuf); else if ((p - lpath) + llen + 2 > GET_PATH_MAX) /* Eh? Path too long! Again, just go with what we have. */ break; else /* Create the next step in the symlink chain. */ strcpy (p+1, lbuf); } } #endif return mtime; } /* Search for a library file specified as -lLIBNAME, searching for a suitable library file in the system library directories and the VPATH directories. */ static const char * library_search (const char *lib, FILE_TIMESTAMP *mtime_ptr) { static char *dirs[] = { #ifndef _AMIGA "/lib", "/usr/lib", #endif #if defined(WINDOWS32) && !defined(LIBDIR) /* * This is completely up to the user at product install time. Just define * a placeholder. */ #define LIBDIR "." #endif LIBDIR, /* Defined by configuration. */ 0 }; const char *file = 0; char *libpatterns; FILE_TIMESTAMP mtime; /* Loop variables for the libpatterns value. */ char *p; const char *p2; unsigned int len; unsigned int liblen; /* Information about the earliest (in the vpath sequence) match. */ unsigned int best_vpath, best_path; unsigned int std_dirs = 0; char **dp; libpatterns = xstrdup (variable_expand ("$(.LIBPATTERNS)")); /* Skip the '-l'. */ lib += 2; liblen = strlen (lib); /* Loop through all the patterns in .LIBPATTERNS, and search on each one. To implement the linker-compatible behavior we have to search through all entries in .LIBPATTERNS and choose the "earliest" one. */ p2 = libpatterns; while ((p = find_next_token (&p2, &len)) != 0) { static char *buf = NULL; static unsigned int buflen = 0; static int libdir_maxlen = -1; char *libbuf = variable_expand (""); /* Expand the pattern using LIB as a replacement. */ { char c = p[len]; char *p3, *p4; p[len] = '\0'; p3 = find_percent (p); if (!p3) { /* Give a warning if there is no pattern. */ error (NILF, _(".LIBPATTERNS element `%s' is not a pattern"), p); p[len] = c; continue; } p4 = variable_buffer_output (libbuf, p, p3-p); p4 = variable_buffer_output (p4, lib, liblen); p4 = variable_buffer_output (p4, p3+1, len - (p3-p)); p[len] = c; } /* Look first for `libNAME.a' in the current directory. */ mtime = name_mtime (libbuf); if (mtime != NONEXISTENT_MTIME) { if (mtime_ptr != 0) *mtime_ptr = mtime; file = strcache_add (libbuf); /* This by definition will have the best index, so stop now. */ break; } /* Now try VPATH search on that. */ { unsigned int vpath_index, path_index; const char* f = vpath_search (libbuf, mtime_ptr ? &mtime : NULL, &vpath_index, &path_index); if (f) { /* If we have a better match, record it. */ if (file == 0 || vpath_index < best_vpath || (vpath_index == best_vpath && path_index < best_path)) { file = f; best_vpath = vpath_index; best_path = path_index; if (mtime_ptr != 0) *mtime_ptr = mtime; } } } /* Now try the standard set of directories. */ if (!buflen) { for (dp = dirs; *dp != 0; ++dp) { int l = strlen (*dp); if (l > libdir_maxlen) libdir_maxlen = l; std_dirs++; } buflen = strlen (libbuf); buf = xmalloc(libdir_maxlen + buflen + 2); } else if (buflen < strlen (libbuf)) { buflen = strlen (libbuf); buf = xrealloc (buf, libdir_maxlen + buflen + 2); } { /* Use the last std_dirs index for standard directories. This was it will always be greater than the VPATH index. */ unsigned int vpath_index = ~((unsigned int)0) - std_dirs; for (dp = dirs; *dp != 0; ++dp) { sprintf (buf, "%s/%s", *dp, libbuf); mtime = name_mtime (buf); if (mtime != NONEXISTENT_MTIME) { if (file == 0 || vpath_index < best_vpath) { file = strcache_add (buf); best_vpath = vpath_index; if (mtime_ptr != 0) *mtime_ptr = mtime; } } vpath_index++; } } } free (libpatterns); return file; }
/* Pattern and suffix rule internals for GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" #include <assert.h> #include "dep.h" #include "filedef.h" #include "job.h" #include "commands.h" #include "variable.h" #include "rule.h" static void freerule (struct rule *rule, struct rule *lastrule); /* Chain of all pattern rules. */ struct rule *pattern_rules; /* Pointer to last rule in the chain, so we can add onto the end. */ struct rule *last_pattern_rule; /* Number of rules in the chain. */ unsigned int num_pattern_rules; /* Maximum number of target patterns of any pattern rule. */ unsigned int max_pattern_targets; /* Maximum number of dependencies of any pattern rule. */ unsigned int max_pattern_deps; /* Maximum length of the name of a dependencies of any pattern rule. */ unsigned int max_pattern_dep_length; /* Pointer to structure for the file .SUFFIXES whose dependencies are the suffixes to be searched. */ struct file *suffix_file; /* Maximum length of a suffix. */ unsigned int maxsuffix; /* Compute the maximum dependency length and maximum number of dependencies of all implicit rules. Also sets the subdir flag for a rule when appropriate, possibly removing the rule completely when appropriate. */ void count_implicit_rule_limits (void) { char *name; int namelen; struct rule *rule, *lastrule; num_pattern_rules = max_pattern_targets = max_pattern_deps = 0; max_pattern_dep_length = 0; name = 0; namelen = 0; rule = pattern_rules; lastrule = 0; while (rule != 0) { unsigned int ndeps = 0; struct dep *dep; struct rule *next = rule->next; ++num_pattern_rules; if (rule->num > max_pattern_targets) max_pattern_targets = rule->num; for (dep = rule->deps; dep != 0; dep = dep->next) { const char *dname = dep_name (dep); unsigned int len = strlen (dname); #ifdef VMS const char *p = strrchr (dname, ']'); const char *p2; if (p == 0) p = strrchr (dname, ':'); p2 = p != 0 ? strchr (dname, '%') : 0; #else const char *p = strrchr (dname, '/'); const char *p2 = p != 0 ? strchr (dname, '%') : 0; #endif ndeps++; if (len > max_pattern_dep_length) max_pattern_dep_length = len; if (p != 0 && p2 > p) { /* There is a slash before the % in the dep name. Extract the directory name. */ if (p == dname) ++p; if (p - dname > namelen) { namelen = p - dname; name = xrealloc (name, namelen + 1); } memcpy (name, dname, p - dname); name[p - dname] = '\0'; /* In the deps of an implicit rule the `changed' flag actually indicates that the dependency is in a nonexistent subdirectory. */ dep->changed = !dir_file_exists_p (name, ""); } else /* This dependency does not reside in a subdirectory. */ dep->changed = 0; } if (ndeps > max_pattern_deps) max_pattern_deps = ndeps; lastrule = rule; rule = next; } if (name != 0) free (name); } /* Create a pattern rule from a suffix rule. TARGET is the target suffix; SOURCE is the source suffix. CMDS are the commands. If TARGET is nil, it means the target pattern should be `(%.o)'. If SOURCE is nil, it means there should be no deps. */ static void convert_suffix_rule (const char *target, const char *source, struct commands *cmds) { const char **names, **percents; struct dep *deps; names = xmalloc (sizeof (const char *)); percents = xmalloc (sizeof (const char *)); if (target == 0) { /* Special case: TARGET being nil means we are defining a `.X.a' suffix rule; the target pattern is always `(%.o)'. */ #ifdef VMS *names = strcache_add_len ("(%.obj)", 7); #else *names = strcache_add_len ("(%.o)", 5); #endif *percents = *names + 1; } else { /* Construct the target name. */ unsigned int len = strlen (target); char *p = alloca (1 + len + 1); p[0] = '%'; memcpy (p + 1, target, len + 1); *names = strcache_add_len (p, len + 1); *percents = *names; } if (source == 0) deps = 0; else { /* Construct the dependency name. */ unsigned int len = strlen (source); char *p = alloca (1 + len + 1); p[0] = '%'; memcpy (p + 1, source, len + 1); deps = alloc_dep (); deps->name = strcache_add_len (p, len + 1); } create_pattern_rule (names, percents, 1, 0, deps, cmds, 0); } /* Convert old-style suffix rules to pattern rules. All rules for the suffixes on the .SUFFIXES list are converted and added to the chain of pattern rules. */ void convert_to_pattern (void) { struct dep *d, *d2; char *rulename; /* We will compute every potential suffix rule (.x.y) from the list of suffixes in the .SUFFIXES target's dependencies and see if it exists. First find the longest of the suffixes. */ maxsuffix = 0; for (d = suffix_file->deps; d != 0; d = d->next) { unsigned int l = strlen (dep_name (d)); if (l > maxsuffix) maxsuffix = l; } /* Space to construct the suffix rule target name. */ rulename = alloca ((maxsuffix * 2) + 1); for (d = suffix_file->deps; d != 0; d = d->next) { unsigned int slen; /* Make a rule that is just the suffix, with no deps or commands. This rule exists solely to disqualify match-anything rules. */ convert_suffix_rule (dep_name (d), 0, 0); if (d->file->cmds != 0) /* Record a pattern for this suffix's null-suffix rule. */ convert_suffix_rule ("", dep_name (d), d->file->cmds); /* Add every other suffix to this one and see if it exists as a two-suffix rule. */ slen = strlen (dep_name (d)); memcpy (rulename, dep_name (d), slen); for (d2 = suffix_file->deps; d2 != 0; d2 = d2->next) { struct file *f; unsigned int s2len; s2len = strlen (dep_name (d2)); /* Can't build something from itself. */ if (slen == s2len && streq (dep_name (d), dep_name (d2))) continue; memcpy (rulename + slen, dep_name (d2), s2len + 1); f = lookup_file (rulename); if (f == 0 || f->cmds == 0) continue; if (s2len == 2 && rulename[slen] == '.' && rulename[slen + 1] == 'a') /* A suffix rule `.X.a:' generates the pattern rule `(%.o): %.X'. It also generates a normal `%.a: %.X' rule below. */ convert_suffix_rule (NULL, /* Indicates `(%.o)'. */ dep_name (d), f->cmds); /* The suffix rule `.X.Y:' is converted to the pattern rule `%.Y: %.X'. */ convert_suffix_rule (dep_name (d2), dep_name (d), f->cmds); } } } /* Install the pattern rule RULE (whose fields have been filled in) at the end of the list (so that any rules previously defined will take precedence). If this rule duplicates a previous one (identical target and dependencies), the old one is replaced if OVERRIDE is nonzero, otherwise this new one is thrown out. When an old rule is replaced, the new one is put at the end of the list. Return nonzero if RULE is used; zero if not. */ static int new_pattern_rule (struct rule *rule, int override) { struct rule *r, *lastrule; unsigned int i, j; rule->in_use = 0; rule->terminal = 0; rule->next = 0; /* Search for an identical rule. */ lastrule = 0; for (r = pattern_rules; r != 0; lastrule = r, r = r->next) for (i = 0; i < rule->num; ++i) { for (j = 0; j < r->num; ++j) if (!streq (rule->targets[i], r->targets[j])) break; /* If all the targets matched... */ if (j == r->num) { struct dep *d, *d2; for (d = rule->deps, d2 = r->deps; d != 0 && d2 != 0; d = d->next, d2 = d2->next) if (!streq (dep_name (d), dep_name (d2))) break; if (d == 0 && d2 == 0) { /* All the dependencies matched. */ if (override) { /* Remove the old rule. */ freerule (r, lastrule); /* Install the new one. */ if (pattern_rules == 0) pattern_rules = rule; else last_pattern_rule->next = rule; last_pattern_rule = rule; /* We got one. Stop looking. */ goto matched; } else { /* The old rule stays intact. Destroy the new one. */ freerule (rule, (struct rule *) 0); return 0; } } } } matched:; if (r == 0) { /* There was no rule to replace. */ if (pattern_rules == 0) pattern_rules = rule; else last_pattern_rule->next = rule; last_pattern_rule = rule; } return 1; } /* Install an implicit pattern rule based on the three text strings in the structure P points to. These strings come from one of the arrays of default implicit pattern rules. TERMINAL specifies what the `terminal' field of the rule should be. */ void install_pattern_rule (struct pspec *p, int terminal) { struct rule *r; char *ptr; r = xmalloc (sizeof (struct rule)); r->num = 1; r->targets = xmalloc (sizeof (const char *)); r->suffixes = xmalloc (sizeof (const char *)); r->lens = xmalloc (sizeof (unsigned int)); r->lens[0] = strlen (p->target); r->targets[0] = p->target; r->suffixes[0] = find_percent_cached (&r->targets[0]); assert (r->suffixes[0] != NULL); ++r->suffixes[0]; ptr = p->dep; r->deps = PARSE_FILE_SEQ (&ptr, struct dep, '\0', NULL, 0); if (new_pattern_rule (r, 0)) { r->terminal = terminal; r->cmds = xmalloc (sizeof (struct commands)); r->cmds->fileinfo.filenm = 0; r->cmds->fileinfo.lineno = 0; /* These will all be string literals, but we malloc space for them anyway because somebody might want to free them later. */ r->cmds->commands = xstrdup (p->commands); r->cmds->command_lines = 0; } } /* Free all the storage used in RULE and take it out of the pattern_rules chain. LASTRULE is the rule whose next pointer points to RULE. */ static void freerule (struct rule *rule, struct rule *lastrule) { struct rule *next = rule->next; free_dep_chain (rule->deps); /* MSVC erroneously warns without a cast here. */ free ((void *)rule->targets); free ((void *)rule->suffixes); free (rule->lens); /* We can't free the storage for the commands because there are ways that they could be in more than one place: * If the commands came from a suffix rule, they could also be in the `struct file's for other suffix rules or plain targets given on the same makefile line. * If two suffixes that together make a two-suffix rule were each given twice in the .SUFFIXES list, and in the proper order, two identical pattern rules would be created and the second one would be discarded here, but both would contain the same `struct commands' pointer from the `struct file' for the suffix rule. */ free (rule); if (pattern_rules == rule) if (lastrule != 0) abort (); else pattern_rules = next; else if (lastrule != 0) lastrule->next = next; if (last_pattern_rule == rule) last_pattern_rule = lastrule; } /* Create a new pattern rule with the targets in the nil-terminated array TARGETS. TARGET_PERCENTS is an array of pointers to the % in each element of TARGETS. N is the number of items in the array (not counting the nil element). The new rule has dependencies DEPS and commands from COMMANDS. It is a terminal rule if TERMINAL is nonzero. This rule overrides identical rules with different commands if OVERRIDE is nonzero. The storage for TARGETS and its elements and TARGET_PERCENTS is used and must not be freed until the rule is destroyed. */ void create_pattern_rule (const char **targets, const char **target_percents, unsigned int n, int terminal, struct dep *deps, struct commands *commands, int override) { unsigned int i; struct rule *r = xmalloc (sizeof (struct rule)); r->num = n; r->cmds = commands; r->deps = deps; r->targets = targets; r->suffixes = target_percents; r->lens = xmalloc (n * sizeof (unsigned int)); for (i = 0; i < n; ++i) { r->lens[i] = strlen (targets[i]); assert (r->suffixes[i] != NULL); ++r->suffixes[i]; } if (new_pattern_rule (r, override)) r->terminal = terminal; } /* Print the data base of rules. */ static void /* Useful to call from gdb. */ print_rule (struct rule *r) { unsigned int i; for (i = 0; i < r->num; ++i) { fputs (r->targets[i], stdout); putchar ((i + 1 == r->num) ? ':' : ' '); } if (r->terminal) putchar (':'); print_prereqs (r->deps); if (r->cmds != 0) print_commands (r->cmds); } void print_rule_data_base (void) { unsigned int rules, terminal; struct rule *r; puts (_("\n# Implicit Rules")); rules = terminal = 0; for (r = pattern_rules; r != 0; r = r->next) { ++rules; putchar ('\n'); print_rule (r); if (r->terminal) ++terminal; } if (rules == 0) puts (_("\n# No implicit rules.")); else { printf (_("\n# %u implicit rules, %u"), rules, terminal); #ifndef NO_FLOAT printf (" (%.1f%%)", (double) terminal / (double) rules * 100.0); #else { int f = (terminal * 1000 + 5) / rules; printf (" (%d.%d%%)", f/10, f%10); } #endif puts (_(" terminal.")); } if (num_pattern_rules != rules) { /* This can happen if a fatal error was detected while reading the makefiles and thus count_implicit_rule_limits wasn't called yet. */ if (num_pattern_rules != 0) fatal (NILF, _("BUG: num_pattern_rules is wrong! %u != %u"), num_pattern_rules, rules); } }
/* Convert between signal names and numbers. Copyright (C) 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" /* If the system provides strsignal, we don't need it. */ #if !HAVE_STRSIGNAL /* If the system provides sys_siglist, we'll use that. Otherwise create our own. */ #if !HAVE_DECL_SYS_SIGLIST /* Some systems do not define NSIG in <signal.h>. */ #ifndef NSIG #ifdef _NSIG #define NSIG _NSIG #else #define NSIG 32 #endif #endif /* There is too much variation in Sys V signal numbers and names, so we must initialize them at runtime. */ static const char *undoc; static const char *sys_siglist[NSIG]; /* Table of abbreviations for signals. Note: A given number can appear more than once with different abbreviations. */ #define SIG_TABLE_SIZE (NSIG*2) typedef struct { int number; const char *abbrev; } num_abbrev; static num_abbrev sig_table[SIG_TABLE_SIZE]; /* Number of elements of sig_table used. */ static int sig_table_nelts = 0; /* Enter signal number NUMBER into the tables with ABBREV and NAME. */ static void init_sig (int number, const char *abbrev, const char *name) { /* If this value is ever greater than NSIG it seems like it'd be a bug in the system headers, but... better safe than sorry. We know, for example, that this isn't always true on VMS. */ if (number >= 0 && number < NSIG) sys_siglist[number] = name; if (sig_table_nelts < SIG_TABLE_SIZE) { sig_table[sig_table_nelts].number = number; sig_table[sig_table_nelts++].abbrev = abbrev; } } static int signame_init (void) { int i; undoc = xstrdup (_("unknown signal")); /* Initialize signal names. */ for (i = 0; i < NSIG; i++) sys_siglist[i] = undoc; /* Initialize signal names. */ #if defined (SIGHUP) init_sig (SIGHUP, "HUP", _("Hangup")); #endif #if defined (SIGINT) init_sig (SIGINT, "INT", _("Interrupt")); #endif #if defined (SIGQUIT) init_sig (SIGQUIT, "QUIT", _("Quit")); #endif #if defined (SIGILL) init_sig (SIGILL, "ILL", _("Illegal Instruction")); #endif #if defined (SIGTRAP) init_sig (SIGTRAP, "TRAP", _("Trace/breakpoint trap")); #endif /* If SIGIOT == SIGABRT, we want to print it as SIGABRT because SIGABRT is in ANSI and POSIX.1 and SIGIOT isn't. */ #if defined (SIGABRT) init_sig (SIGABRT, "ABRT", _("Aborted")); #endif #if defined (SIGIOT) init_sig (SIGIOT, "IOT", _("IOT trap")); #endif #if defined (SIGEMT) init_sig (SIGEMT, "EMT", _("EMT trap")); #endif #if defined (SIGFPE) init_sig (SIGFPE, "FPE", _("Floating point exception")); #endif #if defined (SIGKILL) init_sig (SIGKILL, "KILL", _("Killed")); #endif #if defined (SIGBUS) init_sig (SIGBUS, "BUS", _("Bus error")); #endif #if defined (SIGSEGV) init_sig (SIGSEGV, "SEGV", _("Segmentation fault")); #endif #if defined (SIGSYS) init_sig (SIGSYS, "SYS", _("Bad system call")); #endif #if defined (SIGPIPE) init_sig (SIGPIPE, "PIPE", _("Broken pipe")); #endif #if defined (SIGALRM) init_sig (SIGALRM, "ALRM", _("Alarm clock")); #endif #if defined (SIGTERM) init_sig (SIGTERM, "TERM", _("Terminated")); #endif #if defined (SIGUSR1) init_sig (SIGUSR1, "USR1", _("User defined signal 1")); #endif #if defined (SIGUSR2) init_sig (SIGUSR2, "USR2", _("User defined signal 2")); #endif /* If SIGCLD == SIGCHLD, we want to print it as SIGCHLD because that is what is in POSIX.1. */ #if defined (SIGCHLD) init_sig (SIGCHLD, "CHLD", _("Child exited")); #endif #if defined (SIGCLD) init_sig (SIGCLD, "CLD", _("Child exited")); #endif #if defined (SIGPWR) init_sig (SIGPWR, "PWR", _("Power failure")); #endif #if defined (SIGTSTP) init_sig (SIGTSTP, "TSTP", _("Stopped")); #endif #if defined (SIGTTIN) init_sig (SIGTTIN, "TTIN", _("Stopped (tty input)")); #endif #if defined (SIGTTOU) init_sig (SIGTTOU, "TTOU", _("Stopped (tty output)")); #endif #if defined (SIGSTOP) init_sig (SIGSTOP, "STOP", _("Stopped (signal)")); #endif #if defined (SIGXCPU) init_sig (SIGXCPU, "XCPU", _("CPU time limit exceeded")); #endif #if defined (SIGXFSZ) init_sig (SIGXFSZ, "XFSZ", _("File size limit exceeded")); #endif #if defined (SIGVTALRM) init_sig (SIGVTALRM, "VTALRM", _("Virtual timer expired")); #endif #if defined (SIGPROF) init_sig (SIGPROF, "PROF", _("Profiling timer expired")); #endif #if defined (SIGWINCH) /* "Window size changed" might be more accurate, but even if that is all that it means now, perhaps in the future it will be extended to cover other kinds of window changes. */ init_sig (SIGWINCH, "WINCH", _("Window changed")); #endif #if defined (SIGCONT) init_sig (SIGCONT, "CONT", _("Continued")); #endif #if defined (SIGURG) init_sig (SIGURG, "URG", _("Urgent I/O condition")); #endif #if defined (SIGIO) /* "I/O pending" has also been suggested. A disadvantage is that signal only happens when the process has asked for it, not everytime I/O is pending. Another disadvantage is the confusion from giving it a different name than under Unix. */ init_sig (SIGIO, "IO", _("I/O possible")); #endif #if defined (SIGWIND) init_sig (SIGWIND, "WIND", _("SIGWIND")); #endif #if defined (SIGPHONE) init_sig (SIGPHONE, "PHONE", _("SIGPHONE")); #endif #if defined (SIGPOLL) init_sig (SIGPOLL, "POLL", _("I/O possible")); #endif #if defined (SIGLOST) init_sig (SIGLOST, "LOST", _("Resource lost")); #endif #if defined (SIGDANGER) init_sig (SIGDANGER, "DANGER", _("Danger signal")); #endif #if defined (SIGINFO) init_sig (SIGINFO, "INFO", _("Information request")); #endif #if defined (SIGNOFP) init_sig (SIGNOFP, "NOFP", _("Floating point co-processor not available")); #endif return 1; } #endif /* HAVE_DECL_SYS_SIGLIST */ char * strsignal (int sig) { static char buf[] = "Signal 12345678901234567890"; #if ! HAVE_DECL_SYS_SIGLIST # if HAVE_DECL__SYS_SIGLIST # define sys_siglist _sys_siglist # elif HAVE_DECL___SYS_SIGLIST # define sys_siglist __sys_siglist # else static char sig_initted = 0; if (!sig_initted) sig_initted = signame_init (); # endif #endif if (sig > 0 || sig < NSIG) return (char *) sys_siglist[sig]; sprintf (buf, "Signal %d", sig); return buf; } #endif /* HAVE_STRSIGNAL */
/* Constant string caching for GNU Make. Copyright (C) 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" #include <assert.h> #include "hash.h" /* The size (in bytes) of each cache buffer. Try to pick something that will map well into the heap. */ #define CACHE_BUFFER_SIZE (8192 - 16) /* A string cached here will never be freed, so we don't need to worry about reference counting. We just store the string, and then remember it in a hash so it can be looked up again. */ struct strcache { struct strcache *next; /* The next block of strings. */ char *end; /* Pointer to the beginning of the free space. */ int count; /* # of strings in this buffer (for stats). */ int bytesfree; /* The amount of the buffer that is free. */ char buffer[1]; /* The buffer comes after this. */ }; static int bufsize = CACHE_BUFFER_SIZE; static struct strcache *strcache = NULL; /* Add a new buffer to the cache. Add it at the front to reduce search time. This can also increase the overhead, since it's less likely that older buffers will be filled in. However, GNU make has so many smaller strings that this doesn't seem to be much of an issue in practice. */ static struct strcache * new_cache() { struct strcache *new; new = xmalloc (sizeof (*new) + bufsize); new->end = new->buffer; new->count = 0; new->bytesfree = bufsize; new->next = strcache; strcache = new; return new; } static const char * add_string(const char *str, int len) { struct strcache *best = NULL; struct strcache *sp; const char *res; /* If the string we want is too large to fit into a single buffer, then we're screwed; nothing will ever fit! Change the maximum size of the cache to be big enough. */ if (len > bufsize) bufsize = len * 2; /* First, find a cache with enough free space. We always look through all the blocks and choose the one with the best fit (the one that leaves the least amount of space free). */ for (sp = strcache; sp != NULL; sp = sp->next) if (sp->bytesfree > len && (!best || best->bytesfree > sp->bytesfree)) best = sp; /* If nothing is big enough, make a new cache. */ if (!best) best = new_cache(); assert (best->bytesfree > len); /* Add the string to the best cache. */ res = best->end; memcpy (best->end, str, len); best->end += len; *(best->end++) = '\0'; best->bytesfree -= len + 1; ++best->count; return res; } /* Hash table of strings in the cache. */ static unsigned long str_hash_1 (const void *key) { return_ISTRING_HASH_1 ((const char *) key); } static unsigned long str_hash_2 (const void *key) { return_ISTRING_HASH_2 ((const char *) key); } static int str_hash_cmp (const void *x, const void *y) { return_ISTRING_COMPARE ((const char *) x, (const char *) y); } static struct hash_table strings; static unsigned long total_adds = 0; static const char * add_hash (const char *str, int len) { /* Look up the string in the hash. If it's there, return it. */ char *const *slot = (char *const *) hash_find_slot (&strings, str); const char *key = *slot; /* Count the total number of adds we performed. */ ++total_adds; if (!HASH_VACANT (key)) return key; /* Not there yet so add it to a buffer, then into the hash table. */ key = add_string (str, len); hash_insert_at (&strings, key, slot); return key; } /* Returns true if the string is in the cache; false if not. */ int strcache_iscached (const char *str) { struct strcache *sp; for (sp = strcache; sp != 0; sp = sp->next) if (str >= sp->buffer && str < sp->end) return 1; return 0; } /* If the string is already in the cache, return a pointer to the cached version. If not, add it then return a pointer to the cached version. Note we do NOT take control of the string passed in. */ const char * strcache_add (const char *str) { return add_hash (str, strlen (str)); } const char * strcache_add_len (const char *str, int len) { /* If we're not given a nul-terminated string we have to create one, because the hashing functions expect it. */ if (str[len] != '\0') { char *key = alloca (len + 1); memcpy (key, str, len); key[len] = '\0'; str = key; } return add_hash (str, len); } int strcache_setbufsize(int size) { if (size > bufsize) bufsize = size; return bufsize; } void strcache_init (void) { hash_init (&strings, 8000, str_hash_1, str_hash_2, str_hash_cmp); } /* Generate some stats output. */ void strcache_print_stats (const char *prefix) { int numbuffs = 0, numstrs = 0; int totsize = 0, avgsize, maxsize = 0, minsize = bufsize; int totfree = 0, avgfree, maxfree = 0, minfree = bufsize; int lastused = 0, lastfree = 0; if (strcache) { const struct strcache *sp; /* Count the first buffer separately since it's not full. */ lastused = strcache->end - strcache->buffer; lastfree = strcache->bytesfree; for (sp = strcache->next; sp != NULL; sp = sp->next) { int bf = sp->bytesfree; int sz = sp->end - sp->buffer; ++numbuffs; numstrs += sp->count; totsize += sz; maxsize = (sz > maxsize ? sz : maxsize); minsize = (sz < minsize ? sz : minsize); totfree += bf; maxfree = (bf > maxfree ? bf : maxfree); minfree = (bf < minfree ? bf : minfree); } } avgsize = numbuffs ? (int)(totsize / numbuffs) : 0; avgfree = numbuffs ? (int)(totfree / numbuffs) : 0; printf (_("\n%s # of strings in strcache: %d / lookups = %lu / hits = %lu\n"), prefix, numstrs, total_adds, (total_adds - numstrs)); printf (_("%s # of strcache buffers: %d (* %d B/buffer = %d B)\n"), prefix, (numbuffs + 1), bufsize, ((numbuffs + 1) * bufsize)); printf (_("%s strcache used: total = %d (%d) / max = %d / min = %d / avg = %d\n"), prefix, totsize, lastused, maxsize, minsize, avgsize); printf (_("%s strcache free: total = %d (%d) / max = %d / min = %d / avg = %d\n"), prefix, totfree, lastfree, maxfree, minfree, avgfree); fputs (_("\n# strcache hash-table stats:\n# "), stdout); hash_print_stats (&strings, stdout); }
/* Internals of variables for GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" #include <assert.h> #include "dep.h" #include "filedef.h" #include "job.h" #include "commands.h" #include "variable.h" #include "rule.h" #ifdef WINDOWS32 #include "pathstuff.h" #endif #include "hash.h" /* Chain of all pattern-specific variables. */ static struct pattern_var *pattern_vars; /* Pointer to the last struct in the pack of a specific size, from 1 to 255.*/ static struct pattern_var *last_pattern_vars[256]; /* Create a new pattern-specific variable struct. The new variable is inserted into the PATTERN_VARS list in the shortest patterns first order to support the shortest stem matching (the variables are matched in the reverse order so the ones with the longest pattern will be considered first). Variables with the same pattern length are inserted in the definition order. */ struct pattern_var * create_pattern_var (const char *target, const char *suffix) { register unsigned int len = strlen (target); register struct pattern_var *p = xmalloc (sizeof (struct pattern_var)); if (pattern_vars != 0) { if (len < 256 && last_pattern_vars[len] != 0) { p->next = last_pattern_vars[len]->next; last_pattern_vars[len]->next = p; } else { /* Find the position where we can insert this variable. */ register struct pattern_var **v; for (v = &pattern_vars; ; v = &(*v)->next) { /* Insert at the end of the pack so that patterns with the same length appear in the order they were defined .*/ if (*v == 0 || (*v)->len > len) { p->next = *v; *v = p; break; } } } } else { pattern_vars = p; p->next = 0; } p->target = target; p->len = len; p->suffix = suffix + 1; if (len < 256) last_pattern_vars[len] = p; return p; } /* Look up a target in the pattern-specific variable list. */ static struct pattern_var * lookup_pattern_var (struct pattern_var *start, const char *target) { struct pattern_var *p; unsigned int targlen = strlen(target); for (p = start ? start->next : pattern_vars; p != 0; p = p->next) { const char *stem; unsigned int stemlen; if (p->len > targlen) /* It can't possibly match. */ continue; /* From the lengths of the filename and the pattern parts, find the stem: the part of the filename that matches the %. */ stem = target + (p->suffix - p->target - 1); stemlen = targlen - p->len + 1; /* Compare the text in the pattern before the stem, if any. */ if (stem > target && !strneq (p->target, target, stem - target)) continue; /* Compare the text in the pattern after the stem, if any. We could test simply using streq, but this way we compare the first two characters immediately. This saves time in the very common case where the first character matches because it is a period. */ if (*p->suffix == stem[stemlen] && (*p->suffix == '\0' || streq (&p->suffix[1], &stem[stemlen+1]))) break; } return p; } /* Hash table of all global variable definitions. */ static unsigned long variable_hash_1 (const void *keyv) { struct variable const *key = (struct variable const *) keyv; return_STRING_N_HASH_1 (key->name, key->length); } static unsigned long variable_hash_2 (const void *keyv) { struct variable const *key = (struct variable const *) keyv; return_STRING_N_HASH_2 (key->name, key->length); } static int variable_hash_cmp (const void *xv, const void *yv) { struct variable const *x = (struct variable const *) xv; struct variable const *y = (struct variable const *) yv; int result = x->length - y->length; if (result) return result; return_STRING_N_COMPARE (x->name, y->name, x->length); } #ifndef VARIABLE_BUCKETS #define VARIABLE_BUCKETS 523 #endif #ifndef PERFILE_VARIABLE_BUCKETS #define PERFILE_VARIABLE_BUCKETS 23 #endif #ifndef SMALL_SCOPE_VARIABLE_BUCKETS #define SMALL_SCOPE_VARIABLE_BUCKETS 13 #endif static struct variable_set global_variable_set; static struct variable_set_list global_setlist = { 0, &global_variable_set, 0 }; struct variable_set_list *current_variable_set_list = &global_setlist; /* Implement variables. */ void init_hash_global_variable_set (void) { hash_init (&global_variable_set.table, VARIABLE_BUCKETS, variable_hash_1, variable_hash_2, variable_hash_cmp); } /* Define variable named NAME with value VALUE in SET. VALUE is copied. LENGTH is the length of NAME, which does not need to be null-terminated. ORIGIN specifies the origin of the variable (makefile, command line or environment). If RECURSIVE is nonzero a flag is set in the variable saying that it should be recursively re-expanded. */ struct variable * define_variable_in_set (const char *name, unsigned int length, const char *value, enum variable_origin origin, int recursive, struct variable_set *set, const struct floc *flocp) { struct variable *v; struct variable **var_slot; struct variable var_key; if (set == NULL) set = &global_variable_set; var_key.name = (char *) name; var_key.length = length; var_slot = (struct variable **) hash_find_slot (&set->table, &var_key); if (env_overrides && origin == o_env) origin = o_env_override; v = *var_slot; if (! HASH_VACANT (v)) { if (env_overrides && v->origin == o_env) /* V came from in the environment. Since it was defined before the switches were parsed, it wasn't affected by -e. */ v->origin = o_env_override; /* A variable of this name is already defined. If the old definition is from a stronger source than this one, don't redefine it. */ if ((int) origin >= (int) v->origin) { if (v->value != 0) free (v->value); v->value = xstrdup (value); if (flocp != 0) v->fileinfo = *flocp; else v->fileinfo.filenm = 0; v->origin = origin; v->recursive = recursive; } return v; } /* Create a new variable definition and add it to the hash table. */ v = xmalloc (sizeof (struct variable)); v->name = xstrndup (name, length); v->length = length; hash_insert_at (&set->table, v, var_slot); v->value = xstrdup (value); if (flocp != 0) v->fileinfo = *flocp; else v->fileinfo.filenm = 0; v->origin = origin; v->recursive = recursive; v->special = 0; v->expanding = 0; v->exp_count = 0; v->per_target = 0; v->append = 0; v->private_var = 0; v->export = v_default; v->exportable = 1; if (*name != '_' && (*name < 'A' || *name > 'Z') && (*name < 'a' || *name > 'z')) v->exportable = 0; else { for (++name; *name != '\0'; ++name) if (*name != '_' && (*name < 'a' || *name > 'z') && (*name < 'A' || *name > 'Z') && !ISDIGIT(*name)) break; if (*name != '\0') v->exportable = 0; } return v; } /* Undefine variable named NAME in SET. LENGTH is the length of NAME, which does not need to be null-terminated. ORIGIN specifies the origin of the variable (makefile, command line or environment). */ static void free_variable_name_and_value (const void *item); void undefine_variable_in_set (const char *name, unsigned int length, enum variable_origin origin, struct variable_set *set) { struct variable *v; struct variable **var_slot; struct variable var_key; if (set == NULL) set = &global_variable_set; var_key.name = (char *) name; var_key.length = length; var_slot = (struct variable **) hash_find_slot (&set->table, &var_key); if (env_overrides && origin == o_env) origin = o_env_override; v = *var_slot; if (! HASH_VACANT (v)) { if (env_overrides && v->origin == o_env) /* V came from in the environment. Since it was defined before the switches were parsed, it wasn't affected by -e. */ v->origin = o_env_override; /* If the definition is from a stronger source than this one, don't undefine it. */ if ((int) origin >= (int) v->origin) { hash_delete_at (&set->table, var_slot); free_variable_name_and_value (v); } } } /* If the variable passed in is "special", handle its special nature. Currently there are two such variables, both used for introspection: .VARIABLES expands to a list of all the variables defined in this instance of make. .TARGETS expands to a list of all the targets defined in this instance of make. Returns the variable reference passed in. */ #define EXPANSION_INCREMENT(_l) ((((_l) / 500) + 1) * 500) static struct variable * lookup_special_var (struct variable *var) { static unsigned long last_var_count = 0; /* This one actually turns out to be very hard, due to the way the parser records targets. The way it works is that target information is collected internally until make knows the target is completely specified. It unitl it sees that some new construct (a new target or variable) is defined that it knows the previous one is done. In short, this means that if you do this: all: TARGS := $(.TARGETS) then $(TARGS) won't contain "all", because it's not until after the variable is created that the previous target is completed. Changing this would be a major pain. I think a less complex way to do it would be to pre-define the target files as soon as the first line is parsed, then come back and do the rest of the definition as now. That would allow $(.TARGETS) to be correct without a major change to the way the parser works. if (streq (var->name, ".TARGETS")) var->value = build_target_list (var->value); else */ if (streq (var->name, ".VARIABLES") && global_variable_set.table.ht_fill != last_var_count) { unsigned long max = EXPANSION_INCREMENT (strlen (var->value)); unsigned long len; char *p; struct variable **vp = (struct variable **) global_variable_set.table.ht_vec; struct variable **end = &vp[global_variable_set.table.ht_size]; /* Make sure we have at least MAX bytes in the allocated buffer. */ var->value = xrealloc (var->value, max); /* Walk through the hash of variables, constructing a list of names. */ p = var->value; len = 0; for (; vp < end; ++vp) if (!HASH_VACANT (*vp)) { struct variable *v = *vp; int l = v->length; len += l + 1; if (len > max) { unsigned long off = p - var->value; max += EXPANSION_INCREMENT (l + 1); var->value = xrealloc (var->value, max); p = &var->value[off]; } memcpy (p, v->name, l); p += l; *(p++) = ' '; } *(p-1) = '\0'; /* Remember how many variables are in our current count. Since we never remove variables from the list, this is a reliable way to know whether the list is up to date or needs to be recomputed. */ last_var_count = global_variable_set.table.ht_fill; } return var; } /* Lookup a variable whose name is a string starting at NAME and with LENGTH chars. NAME need not be null-terminated. Returns address of the `struct variable' containing all info on the variable, or nil if no such variable is defined. */ struct variable * lookup_variable (const char *name, unsigned int length) { const struct variable_set_list *setlist; struct variable var_key; int is_parent = 0; var_key.name = (char *) name; var_key.length = length; for (setlist = current_variable_set_list; setlist != 0; setlist = setlist->next) { const struct variable_set *set = setlist->set; struct variable *v; v = (struct variable *) hash_find_item ((struct hash_table *) &set->table, &var_key); if (v && (!is_parent || !v->private_var)) return v->special ? lookup_special_var (v) : v; is_parent |= setlist->next_is_parent; } #ifdef VMS /* since we don't read envp[] on startup, try to get the variable via getenv() here. */ { char *vname = alloca (length + 1); char *value; strncpy (vname, name, length); vname[length] = 0; value = getenv (vname); if (value != 0) { char *sptr; int scnt; sptr = value; scnt = 0; while ((sptr = strchr (sptr, '$'))) { scnt++; sptr++; } if (scnt > 0) { char *nvalue; char *nptr; nvalue = alloca (strlen (value) + scnt + 1); sptr = value; nptr = nvalue; while (*sptr) { if (*sptr == '$') { *nptr++ = '$'; *nptr++ = '$'; } else { *nptr++ = *sptr; } sptr++; } *nptr = '\0'; return define_variable (vname, length, nvalue, o_env, 1); } return define_variable (vname, length, value, o_env, 1); } } #endif /* VMS */ return 0; } /* Lookup a variable whose name is a string starting at NAME and with LENGTH chars in set SET. NAME need not be null-terminated. Returns address of the `struct variable' containing all info on the variable, or nil if no such variable is defined. */ struct variable * lookup_variable_in_set (const char *name, unsigned int length, const struct variable_set *set) { struct variable var_key; var_key.name = (char *) name; var_key.length = length; return (struct variable *) hash_find_item ((struct hash_table *) &set->table, &var_key); } /* Initialize FILE's variable set list. If FILE already has a variable set list, the topmost variable set is left intact, but the the rest of the chain is replaced with FILE->parent's setlist. If FILE is a double-colon rule, then we will use the "root" double-colon target's variable set as the parent of FILE's variable set. If we're READING a makefile, don't do the pattern variable search now, since the pattern variable might not have been defined yet. */ void initialize_file_variables (struct file *file, int reading) { struct variable_set_list *l = file->variables; if (l == 0) { l = (struct variable_set_list *) xmalloc (sizeof (struct variable_set_list)); l->set = xmalloc (sizeof (struct variable_set)); hash_init (&l->set->table, PERFILE_VARIABLE_BUCKETS, variable_hash_1, variable_hash_2, variable_hash_cmp); file->variables = l; } /* If this is a double-colon, then our "parent" is the "root" target for this double-colon rule. Since that rule has the same name, parent, etc. we can just use its variables as the "next" for ours. */ if (file->double_colon && file->double_colon != file) { initialize_file_variables (file->double_colon, reading); l->next = file->double_colon->variables; l->next_is_parent = 0; return; } if (file->parent == 0) l->next = &global_setlist; else { initialize_file_variables (file->parent, reading); l->next = file->parent->variables; } l->next_is_parent = 1; /* If we're not reading makefiles and we haven't looked yet, see if we can find pattern variables for this target. */ if (!reading && !file->pat_searched) { struct pattern_var *p; p = lookup_pattern_var (0, file->name); if (p != 0) { struct variable_set_list *global = current_variable_set_list; /* We found at least one. Set up a new variable set to accumulate all the pattern variables that match this target. */ file->pat_variables = create_new_variable_set (); current_variable_set_list = file->pat_variables; do { /* We found one, so insert it into the set. */ struct variable *v; if (p->variable.flavor == f_simple) { v = define_variable_loc ( p->variable.name, strlen (p->variable.name), p->variable.value, p->variable.origin, 0, &p->variable.fileinfo); v->flavor = f_simple; } else { v = do_variable_definition ( &p->variable.fileinfo, p->variable.name, p->variable.value, p->variable.origin, p->variable.flavor, 1); } /* Also mark it as a per-target and copy export status. */ v->per_target = p->variable.per_target; v->export = p->variable.export; v->private_var = p->variable.private_var; } while ((p = lookup_pattern_var (p, file->name)) != 0); current_variable_set_list = global; } file->pat_searched = 1; } /* If we have a pattern variable match, set it up. */ if (file->pat_variables != 0) { file->pat_variables->next = l->next; file->pat_variables->next_is_parent = l->next_is_parent; l->next = file->pat_variables; l->next_is_parent = 0; } } /* Pop the top set off the current variable set list, and free all its storage. */ struct variable_set_list * create_new_variable_set (void) { register struct variable_set_list *setlist; register struct variable_set *set; set = xmalloc (sizeof (struct variable_set)); hash_init (&set->table, SMALL_SCOPE_VARIABLE_BUCKETS, variable_hash_1, variable_hash_2, variable_hash_cmp); setlist = (struct variable_set_list *) xmalloc (sizeof (struct variable_set_list)); setlist->set = set; setlist->next = current_variable_set_list; setlist->next_is_parent = 0; return setlist; } static void free_variable_name_and_value (const void *item) { struct variable *v = (struct variable *) item; free (v->name); free (v->value); } void free_variable_set (struct variable_set_list *list) { hash_map (&list->set->table, free_variable_name_and_value); hash_free (&list->set->table, 1); free (list->set); free (list); } /* Create a new variable set and push it on the current setlist. If we're pushing a global scope (that is, the current scope is the global scope) then we need to "push" it the other way: file variable sets point directly to the global_setlist so we need to replace that with the new one. */ struct variable_set_list * push_new_variable_scope (void) { current_variable_set_list = create_new_variable_set(); if (current_variable_set_list->next == &global_setlist) { /* It was the global, so instead of new -> &global we want to replace &global with the new one and have &global -> new, with current still pointing to &global */ struct variable_set *set = current_variable_set_list->set; current_variable_set_list->set = global_setlist.set; global_setlist.set = set; current_variable_set_list->next = global_setlist.next; global_setlist.next = current_variable_set_list; current_variable_set_list = &global_setlist; } return (current_variable_set_list); } void pop_variable_scope (void) { struct variable_set_list *setlist; struct variable_set *set; /* Can't call this if there's no scope to pop! */ assert(current_variable_set_list->next != NULL); if (current_variable_set_list != &global_setlist) { /* We're not pointing to the global setlist, so pop this one. */ setlist = current_variable_set_list; set = setlist->set; current_variable_set_list = setlist->next; } else { /* This set is the one in the global_setlist, but there is another global set beyond that. We want to copy that set to global_setlist, then delete what used to be in global_setlist. */ setlist = global_setlist.next; set = global_setlist.set; global_setlist.set = setlist->set; global_setlist.next = setlist->next; global_setlist.next_is_parent = setlist->next_is_parent; } /* Free the one we no longer need. */ free (setlist); hash_map (&set->table, free_variable_name_and_value); hash_free (&set->table, 1); free (set); } /* Merge FROM_SET into TO_SET, freeing unused storage in FROM_SET. */ static void merge_variable_sets (struct variable_set *to_set, struct variable_set *from_set) { struct variable **from_var_slot = (struct variable **) from_set->table.ht_vec; struct variable **from_var_end = from_var_slot + from_set->table.ht_size; for ( ; from_var_slot < from_var_end; from_var_slot++) if (! HASH_VACANT (*from_var_slot)) { struct variable *from_var = *from_var_slot; struct variable **to_var_slot = (struct variable **) hash_find_slot (&to_set->table, *from_var_slot); if (HASH_VACANT (*to_var_slot)) hash_insert_at (&to_set->table, from_var, to_var_slot); else { /* GKM FIXME: delete in from_set->table */ free (from_var->value); free (from_var); } } } /* Merge SETLIST1 into SETLIST0, freeing unused storage in SETLIST1. */ void merge_variable_set_lists (struct variable_set_list **setlist0, struct variable_set_list *setlist1) { struct variable_set_list *to = *setlist0; struct variable_set_list *last0 = 0; /* If there's nothing to merge, stop now. */ if (!setlist1) return; /* This loop relies on the fact that all setlists terminate with the global setlist (before NULL). If that's not true, arguably we SHOULD die. */ if (to) while (setlist1 != &global_setlist && to != &global_setlist) { struct variable_set_list *from = setlist1; setlist1 = setlist1->next; merge_variable_sets (to->set, from->set); last0 = to; to = to->next; } if (setlist1 != &global_setlist) { if (last0 == 0) *setlist0 = setlist1; else last0->next = setlist1; } } /* Define the automatic variables, and record the addresses of their structures so we can change their values quickly. */ void define_automatic_variables (void) { #if defined(WINDOWS32) || defined(__EMX__) extern char* default_shell; #else extern char default_shell[]; #endif register struct variable *v; char buf[200]; sprintf (buf, "%u", makelevel); define_variable_cname (MAKELEVEL_NAME, buf, o_env, 0); sprintf (buf, "%s%s%s", version_string, (remote_description == 0 || remote_description[0] == '\0') ? "" : "-", (remote_description == 0 || remote_description[0] == '\0') ? "" : remote_description); define_variable_cname ("MAKE_VERSION", buf, o_default, 0); #ifdef __MSDOS__ /* Allow to specify a special shell just for Make, and use $COMSPEC as the default $SHELL when appropriate. */ { static char shell_str[] = "SHELL"; const int shlen = sizeof (shell_str) - 1; struct variable *mshp = lookup_variable ("MAKESHELL", 9); struct variable *comp = lookup_variable ("COMSPEC", 7); /* $(MAKESHELL) overrides $(SHELL) even if -e is in effect. */ if (mshp) (void) define_variable (shell_str, shlen, mshp->value, o_env_override, 0); else if (comp) { /* $(COMSPEC) shouldn't override $(SHELL). */ struct variable *shp = lookup_variable (shell_str, shlen); if (!shp) (void) define_variable (shell_str, shlen, comp->value, o_env, 0); } } #elif defined(__EMX__) { static char shell_str[] = "SHELL"; const int shlen = sizeof (shell_str) - 1; struct variable *shell = lookup_variable (shell_str, shlen); struct variable *replace = lookup_variable ("MAKESHELL", 9); /* if $MAKESHELL is defined in the environment assume o_env_override */ if (replace && *replace->value && replace->origin == o_env) replace->origin = o_env_override; /* if $MAKESHELL is not defined use $SHELL but only if the variable did not come from the environment */ if (!replace || !*replace->value) if (shell && *shell->value && (shell->origin == o_env || shell->origin == o_env_override)) { /* overwrite whatever we got from the environment */ free(shell->value); shell->value = xstrdup (default_shell); shell->origin = o_default; } /* Some people do not like cmd to be used as the default if $SHELL is not defined in the Makefile. With -DNO_CMD_DEFAULT you can turn off this behaviour */ # ifndef NO_CMD_DEFAULT /* otherwise use $COMSPEC */ if (!replace || !*replace->value) replace = lookup_variable ("COMSPEC", 7); /* otherwise use $OS2_SHELL */ if (!replace || !*replace->value) replace = lookup_variable ("OS2_SHELL", 9); # else # warning NO_CMD_DEFAULT: GNU make will not use CMD.EXE as default shell # endif if (replace && *replace->value) /* overwrite $SHELL */ (void) define_variable (shell_str, shlen, replace->value, replace->origin, 0); else /* provide a definition if there is none */ (void) define_variable (shell_str, shlen, default_shell, o_default, 0); } #endif /* This won't override any definition, but it will provide one if there isn't one there. */ v = define_variable_cname ("SHELL", default_shell, o_default, 0); #ifdef __MSDOS__ v->export = v_export; /* Export always SHELL. */ #endif /* On MSDOS we do use SHELL from environment, since it isn't a standard environment variable on MSDOS, so whoever sets it, does that on purpose. On OS/2 we do not use SHELL from environment but we have already handled that problem above. */ #if !defined(__MSDOS__) && !defined(__EMX__) /* Don't let SHELL come from the environment. */ if (*v->value == '\0' || v->origin == o_env || v->origin == o_env_override) { free (v->value); v->origin = o_file; v->value = xstrdup (default_shell); } #endif /* Make sure MAKEFILES gets exported if it is set. */ v = define_variable_cname ("MAKEFILES", "", o_default, 0); v->export = v_ifset; /* Define the magic D and F variables in terms of the automatic variables they are variations of. */ #ifdef VMS define_variable_cname ("@D", "$(dir $@)", o_automatic, 1); define_variable_cname ("%D", "$(dir $%)", o_automatic, 1); define_variable_cname ("*D", "$(dir $*)", o_automatic, 1); define_variable_cname ("<D", "$(dir $<)", o_automatic, 1); define_variable_cname ("?D", "$(dir $?)", o_automatic, 1); define_variable_cname ("^D", "$(dir $^)", o_automatic, 1); define_variable_cname ("+D", "$(dir $+)", o_automatic, 1); #else define_variable_cname ("@D", "$(patsubst %/,%,$(dir $@))", o_automatic, 1); define_variable_cname ("%D", "$(patsubst %/,%,$(dir $%))", o_automatic, 1); define_variable_cname ("*D", "$(patsubst %/,%,$(dir $*))", o_automatic, 1); define_variable_cname ("<D", "$(patsubst %/,%,$(dir $<))", o_automatic, 1); define_variable_cname ("?D", "$(patsubst %/,%,$(dir $?))", o_automatic, 1); define_variable_cname ("^D", "$(patsubst %/,%,$(dir $^))", o_automatic, 1); define_variable_cname ("+D", "$(patsubst %/,%,$(dir $+))", o_automatic, 1); #endif define_variable_cname ("@F", "$(notdir $@)", o_automatic, 1); define_variable_cname ("%F", "$(notdir $%)", o_automatic, 1); define_variable_cname ("*F", "$(notdir $*)", o_automatic, 1); define_variable_cname ("<F", "$(notdir $<)", o_automatic, 1); define_variable_cname ("?F", "$(notdir $?)", o_automatic, 1); define_variable_cname ("^F", "$(notdir $^)", o_automatic, 1); define_variable_cname ("+F", "$(notdir $+)", o_automatic, 1); } int export_all_variables; /* Create a new environment for FILE's commands. If FILE is nil, this is for the `shell' function. The child's MAKELEVEL variable is incremented. */ char ** target_environment (struct file *file) { struct variable_set_list *set_list; register struct variable_set_list *s; struct hash_table table; struct variable **v_slot; struct variable **v_end; struct variable makelevel_key; char **result_0; char **result; if (file == 0) set_list = current_variable_set_list; else set_list = file->variables; hash_init (&table, VARIABLE_BUCKETS, variable_hash_1, variable_hash_2, variable_hash_cmp); /* Run through all the variable sets in the list, accumulating variables in TABLE. */ for (s = set_list; s != 0; s = s->next) { struct variable_set *set = s->set; v_slot = (struct variable **) set->table.ht_vec; v_end = v_slot + set->table.ht_size; for ( ; v_slot < v_end; v_slot++) if (! HASH_VACANT (*v_slot)) { struct variable **new_slot; struct variable *v = *v_slot; /* If this is a per-target variable and it hasn't been touched already then look up the global version and take its export value. */ if (v->per_target && v->export == v_default) { struct variable *gv; gv = lookup_variable_in_set (v->name, strlen(v->name), &global_variable_set); if (gv) v->export = gv->export; } switch (v->export) { case v_default: if (v->origin == o_default || v->origin == o_automatic) /* Only export default variables by explicit request. */ continue; /* The variable doesn't have a name that can be exported. */ if (! v->exportable) continue; if (! export_all_variables && v->origin != o_command && v->origin != o_env && v->origin != o_env_override) continue; break; case v_export: break; case v_noexport: { /* If this is the SHELL variable and it's not exported, then add the value from our original environment, if the original environment defined a value for SHELL. */ extern struct variable shell_var; if (streq (v->name, "SHELL") && shell_var.value) { v = &shell_var; break; } continue; } case v_ifset: if (v->origin == o_default) continue; break; } new_slot = (struct variable **) hash_find_slot (&table, v); if (HASH_VACANT (*new_slot)) hash_insert_at (&table, v, new_slot); } } makelevel_key.name = MAKELEVEL_NAME; makelevel_key.length = MAKELEVEL_LENGTH; hash_delete (&table, &makelevel_key); result = result_0 = xmalloc ((table.ht_fill + 2) * sizeof (char *)); v_slot = (struct variable **) table.ht_vec; v_end = v_slot + table.ht_size; for ( ; v_slot < v_end; v_slot++) if (! HASH_VACANT (*v_slot)) { struct variable *v = *v_slot; /* If V is recursively expanded and didn't come from the environment, expand its value. If it came from the environment, it should go back into the environment unchanged. */ if (v->recursive && v->origin != o_env && v->origin != o_env_override) { char *value = recursively_expand_for_file (v, file); #ifdef WINDOWS32 if (strcmp(v->name, "Path") == 0 || strcmp(v->name, "PATH") == 0) convert_Path_to_windows32(value, ';'); #endif *result++ = xstrdup (concat (3, v->name, "=", value)); free (value); } else { #ifdef WINDOWS32 if (strcmp(v->name, "Path") == 0 || strcmp(v->name, "PATH") == 0) convert_Path_to_windows32(v->value, ';'); #endif *result++ = xstrdup (concat (3, v->name, "=", v->value)); } } *result = xmalloc (100); sprintf (*result, "%s=%u", MAKELEVEL_NAME, makelevel + 1); *++result = 0; hash_free (&table, 0); return result_0; } static struct variable * set_special_var (struct variable *var) { if (streq (var->name, RECIPEPREFIX_NAME)) { /* The user is resetting the command introduction prefix. This has to happen immediately, so that subsequent rules are interpreted properly. */ cmd_prefix = var->value[0]=='\0' ? RECIPEPREFIX_DEFAULT : var->value[0]; } return var; } /* Given a variable, a value, and a flavor, define the variable. See the try_variable_definition() function for details on the parameters. */ struct variable * do_variable_definition (const struct floc *flocp, const char *varname, const char *value, enum variable_origin origin, enum variable_flavor flavor, int target_var) { const char *p; char *alloc_value = NULL; struct variable *v; int append = 0; int conditional = 0; /* Calculate the variable's new value in VALUE. */ switch (flavor) { default: case f_bogus: /* Should not be possible. */ abort (); case f_simple: /* A simple variable definition "var := value". Expand the value. We have to allocate memory since otherwise it'll clobber the variable buffer, and we may still need that if we're looking at a target-specific variable. */ p = alloc_value = allocated_variable_expand (value); break; case f_conditional: /* A conditional variable definition "var ?= value". The value is set IFF the variable is not defined yet. */ v = lookup_variable (varname, strlen (varname)); if (v) return v->special ? set_special_var (v) : v; conditional = 1; flavor = f_recursive; /* FALLTHROUGH */ case f_recursive: /* A recursive variable definition "var = value". The value is used verbatim. */ p = value; break; case f_append: { /* If we have += but we're in a target variable context, we want to append only with other variables in the context of this target. */ if (target_var) { append = 1; v = lookup_variable_in_set (varname, strlen (varname), current_variable_set_list->set); /* Don't append from the global set if a previous non-appending target-specific variable definition exists. */ if (v && !v->append) append = 0; } else v = lookup_variable (varname, strlen (varname)); if (v == 0) { /* There was no old value. This becomes a normal recursive definition. */ p = value; flavor = f_recursive; } else { /* Paste the old and new values together in VALUE. */ unsigned int oldlen, vallen; const char *val; char *tp = NULL; val = value; if (v->recursive) /* The previous definition of the variable was recursive. The new value is the unexpanded old and new values. */ flavor = f_recursive; else /* The previous definition of the variable was simple. The new value comes from the old value, which was expanded when it was set; and from the expanded new value. Allocate memory for the expansion as we may still need the rest of the buffer if we're looking at a target-specific variable. */ val = tp = allocated_variable_expand (val); oldlen = strlen (v->value); vallen = strlen (val); p = alloc_value = xmalloc (oldlen + 1 + vallen + 1); memcpy (alloc_value, v->value, oldlen); alloc_value[oldlen] = ' '; memcpy (&alloc_value[oldlen + 1], val, vallen + 1); if (tp) free (tp); } } } #ifdef __MSDOS__ /* Many Unix Makefiles include a line saying "SHELL=/bin/sh", but non-Unix systems don't conform to this default configuration (in fact, most of them don't even have `/bin'). On the other hand, $SHELL in the environment, if set, points to the real pathname of the shell. Therefore, we generally won't let lines like "SHELL=/bin/sh" from the Makefile override $SHELL from the environment. But first, we look for the basename of the shell in the directory where SHELL= points, and along the $PATH; if it is found in any of these places, we define $SHELL to be the actual pathname of the shell. Thus, if you have bash.exe installed as d:/unix/bash.exe, and d:/unix is on your $PATH, then SHELL=/usr/local/bin/bash will have the effect of defining SHELL to be "d:/unix/bash.exe". */ if ((origin == o_file || origin == o_override) && strcmp (varname, "SHELL") == 0) { PATH_VAR (shellpath); extern char * __dosexec_find_on_path (const char *, char *[], char *); /* See if we can find "/bin/sh.exe", "/bin/sh.com", etc. */ if (__dosexec_find_on_path (p, NULL, shellpath)) { char *tp; for (tp = shellpath; *tp; tp++) if (*tp == '\\') *tp = '/'; v = define_variable_loc (varname, strlen (varname), shellpath, origin, flavor == f_recursive, flocp); } else { const char *shellbase, *bslash; struct variable *pathv = lookup_variable ("PATH", 4); char *path_string; char *fake_env[2]; size_t pathlen = 0; shellbase = strrchr (p, '/'); bslash = strrchr (p, '\\'); if (!shellbase || bslash > shellbase) shellbase = bslash; if (!shellbase && p[1] == ':') shellbase = p + 1; if (shellbase) shellbase++; else shellbase = p; /* Search for the basename of the shell (with standard executable extensions) along the $PATH. */ if (pathv) pathlen = strlen (pathv->value); path_string = xmalloc (5 + pathlen + 2 + 1); /* On MSDOS, current directory is considered as part of $PATH. */ sprintf (path_string, "PATH=.;%s", pathv ? pathv->value : ""); fake_env[0] = path_string; fake_env[1] = 0; if (__dosexec_find_on_path (shellbase, fake_env, shellpath)) { char *tp; for (tp = shellpath; *tp; tp++) if (*tp == '\\') *tp = '/'; v = define_variable_loc (varname, strlen (varname), shellpath, origin, flavor == f_recursive, flocp); } else v = lookup_variable (varname, strlen (varname)); free (path_string); } } else #endif /* __MSDOS__ */ #ifdef WINDOWS32 if ((origin == o_file || origin == o_override || origin == o_command) && streq (varname, "SHELL")) { extern char *default_shell; /* Call shell locator function. If it returns TRUE, then set no_default_sh_exe to indicate sh was found and set new value for SHELL variable. */ if (find_and_set_default_shell (p)) { v = define_variable_in_set (varname, strlen (varname), default_shell, origin, flavor == f_recursive, (target_var ? current_variable_set_list->set : NULL), flocp); no_default_sh_exe = 0; } else { char *tp = alloc_value; alloc_value = allocated_variable_expand (p); if (find_and_set_default_shell (alloc_value)) { v = define_variable_in_set (varname, strlen (varname), p, origin, flavor == f_recursive, (target_var ? current_variable_set_list->set : NULL), flocp); no_default_sh_exe = 0; } else v = lookup_variable (varname, strlen (varname)); if (tp) free (tp); } } else #endif /* If we are defining variables inside an $(eval ...), we might have a different variable context pushed, not the global context (maybe we're inside a $(call ...) or something. Since this function is only ever invoked in places where we want to define globally visible variables, make sure we define this variable in the global set. */ v = define_variable_in_set (varname, strlen (varname), p, origin, flavor == f_recursive, (target_var ? current_variable_set_list->set : NULL), flocp); v->append = append; v->conditional = conditional; if (alloc_value) free (alloc_value); return v->special ? set_special_var (v) : v; } /* Parse P (a null-terminated string) as a variable definition. If it is not a variable definition, return NULL. If it is a variable definition, return a pointer to the char after the assignment token and set *FLAVOR to the type of variable assignment. */ char * parse_variable_definition (const char *p, enum variable_flavor *flavor) { int wspace = 0; p = next_token (p); while (1) { int c = *p++; /* If we find a comment or EOS, it's not a variable definition. */ if (c == '\0' || c == '#') return NULL; if (c == '$') { /* This begins a variable expansion reference. Make sure we don't treat chars inside the reference as assignment tokens. */ char closeparen; int count; c = *p++; if (c == '(') closeparen = ')'; else if (c == '{') closeparen = '}'; else /* '$$' or '$X'. Either way, nothing special to do here. */ continue; /* P now points past the opening paren or brace. Count parens or braces until it is matched. */ count = 0; for (; *p != '\0'; ++p) { if (*p == c) ++count; else if (*p == closeparen && --count < 0) { ++p; break; } } continue; } /* If we find whitespace skip it, and remember we found it. */ if (isblank ((unsigned char)c)) { wspace = 1; p = next_token (p); c = *p; if (c == '\0') return NULL; ++p; } if (c == '=') { *flavor = f_recursive; return (char *)p; } /* Match assignment variants (:=, +=, ?=) */ if (*p == '=') { switch (c) { case ':': *flavor = f_simple; break; case '+': *flavor = f_append; break; case '?': *flavor = f_conditional; break; default: /* If we skipped whitespace, non-assignments means no var. */ if (wspace) return NULL; /* Might be assignment, or might be $= or #=. Check. */ continue; } return (char *)++p; } else if (c == ':') /* A colon other than := is a rule line, not a variable defn. */ return NULL; /* If we skipped whitespace, non-assignments means no var. */ if (wspace) return NULL; } return (char *)p; } /* Try to interpret LINE (a null-terminated string) as a variable definition. If LINE was recognized as a variable definition, a pointer to its `struct variable' is returned. If LINE is not a variable definition, NULL is returned. */ struct variable * assign_variable_definition (struct variable *v, char *line) { char *beg; char *end; enum variable_flavor flavor; char *name; beg = next_token (line); line = parse_variable_definition (beg, &flavor); if (!line) return NULL; end = line - (flavor == f_recursive ? 1 : 2); while (end > beg && isblank ((unsigned char)end[-1])) --end; line = next_token (line); v->value = line; v->flavor = flavor; /* Expand the name, so "$(foo)bar = baz" works. */ name = alloca (end - beg + 1); memcpy (name, beg, end - beg); name[end - beg] = '\0'; v->name = allocated_variable_expand (name); if (v->name[0] == '\0') fatal (&v->fileinfo, _("empty variable name")); return v; } /* Try to interpret LINE (a null-terminated string) as a variable definition. ORIGIN may be o_file, o_override, o_env, o_env_override, or o_command specifying that the variable definition comes from a makefile, an override directive, the environment with or without the -e switch, or the command line. See the comments for assign_variable_definition(). If LINE was recognized as a variable definition, a pointer to its `struct variable' is returned. If LINE is not a variable definition, NULL is returned. */ struct variable * try_variable_definition (const struct floc *flocp, char *line, enum variable_origin origin, int target_var) { struct variable v; struct variable *vp; if (flocp != 0) v.fileinfo = *flocp; else v.fileinfo.filenm = 0; if (!assign_variable_definition (&v, line)) return 0; vp = do_variable_definition (flocp, v.name, v.value, origin, v.flavor, target_var); free (v.name); return vp; } /* Print information for variable V, prefixing it with PREFIX. */ static void print_variable (const void *item, void *arg) { const struct variable *v = item; const char *prefix = arg; const char *origin; switch (v->origin) { case o_default: origin = _("default"); break; case o_env: origin = _("environment"); break; case o_file: origin = _("makefile"); break; case o_env_override: origin = _("environment under -e"); break; case o_command: origin = _("command line"); break; case o_override: origin = _("`override' directive"); break; case o_automatic: origin = _("automatic"); break; case o_invalid: default: abort (); } fputs ("# ", stdout); fputs (origin, stdout); if (v->private_var) fputs (" private", stdout); if (v->fileinfo.filenm) printf (_(" (from `%s', line %lu)"), v->fileinfo.filenm, v->fileinfo.lineno); putchar ('\n'); fputs (prefix, stdout); /* Is this a `define'? */ if (v->recursive && strchr (v->value, '\n') != 0) printf ("define %s\n%s\nendef\n", v->name, v->value); else { char *p; printf ("%s %s= ", v->name, v->recursive ? v->append ? "+" : "" : ":"); /* Check if the value is just whitespace. */ p = next_token (v->value); if (p != v->value && *p == '\0') /* All whitespace. */ printf ("$(subst ,,%s)", v->value); else if (v->recursive) fputs (v->value, stdout); else /* Double up dollar signs. */ for (p = v->value; *p != '\0'; ++p) { if (*p == '$') putchar ('$'); putchar (*p); } putchar ('\n'); } } /* Print all the variables in SET. PREFIX is printed before the actual variable definitions (everything else is comments). */ void print_variable_set (struct variable_set *set, char *prefix) { hash_map_arg (&set->table, print_variable, prefix); fputs (_("# variable set hash-table stats:\n"), stdout); fputs ("# ", stdout); hash_print_stats (&set->table, stdout); putc ('\n', stdout); } /* Print the data base of variables. */ void print_variable_data_base (void) { puts (_("\n# Variables\n")); print_variable_set (&global_variable_set, ""); puts (_("\n# Pattern-specific Variable Values")); { struct pattern_var *p; int rules = 0; for (p = pattern_vars; p != 0; p = p->next) { ++rules; printf ("\n%s :\n", p->target); print_variable (&p->variable, "# "); } if (rules == 0) puts (_("\n# No pattern-specific variable values.")); else printf (_("\n# %u pattern-specific variable values"), rules); } } /* Print all the local variables of FILE. */ void print_file_variables (const struct file *file) { if (file->variables != 0) print_variable_set (file->variables->set, "# "); } #ifdef WINDOWS32 void sync_Path_environment (void) { char *path = allocated_variable_expand ("$(PATH)"); static char *environ_path = NULL; if (!path) return; /* * If done this before, don't leak memory unnecessarily. * Free the previous entry before allocating new one. */ if (environ_path) free (environ_path); /* * Create something WINDOWS32 world can grok */ convert_Path_to_windows32 (path, ';'); environ_path = xstrdup (concat (3, "PATH", "=", path)); putenv (environ_path); free (path); } #endif
/* Record version and build host architecture for GNU make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* We use <config.h> instead of "config.h" so that a compilation using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h (which it would do because make.h was found in $srcdir). */ #include <config.h> #ifndef MAKE_HOST # define MAKE_HOST "unknown" #endif char *version_string = VERSION; char *make_host = MAKE_HOST; /* Local variables: version-control: never End: */
/* Implementation of pattern-matching file search paths for GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" #include "filedef.h" #include "variable.h" #ifdef WINDOWS32 #include "pathstuff.h" #endif /* Structure used to represent a selective VPATH searchpath. */ struct vpath { struct vpath *next; /* Pointer to next struct in the linked list. */ const char *pattern;/* The pattern to match. */ const char *percent;/* Pointer into `pattern' where the `%' is. */ unsigned int patlen;/* Length of the pattern. */ const char **searchpath; /* Null-terminated list of directories. */ unsigned int maxlen;/* Maximum length of any entry in the list. */ }; /* Linked-list of all selective VPATHs. */ static struct vpath *vpaths; /* Structure for the general VPATH given in the variable. */ static struct vpath *general_vpath; /* Structure for GPATH given in the variable. */ static struct vpath *gpaths; /* Reverse the chain of selective VPATH lists so they will be searched in the order given in the makefiles and construct the list from the VPATH variable. */ void build_vpath_lists () { register struct vpath *new = 0; register struct vpath *old, *nexto; register char *p; /* Reverse the chain. */ for (old = vpaths; old != 0; old = nexto) { nexto = old->next; old->next = new; new = old; } vpaths = new; /* If there is a VPATH variable with a nonnull value, construct the general VPATH list from it. We use variable_expand rather than just calling lookup_variable so that it will be recursively expanded. */ { /* Turn off --warn-undefined-variables while we expand SHELL and IFS. */ int save = warn_undefined_variables_flag; warn_undefined_variables_flag = 0; p = variable_expand ("$(strip $(VPATH))"); warn_undefined_variables_flag = save; } if (*p != '\0') { /* Save the list of vpaths. */ struct vpath *save_vpaths = vpaths; char gp[] = "%"; /* Empty `vpaths' so the new one will have no next, and `vpaths' will still be nil if P contains no existing directories. */ vpaths = 0; /* Parse P. */ construct_vpath_list (gp, p); /* Store the created path as the general path, and restore the old list of vpaths. */ general_vpath = vpaths; vpaths = save_vpaths; } /* If there is a GPATH variable with a nonnull value, construct the GPATH list from it. We use variable_expand rather than just calling lookup_variable so that it will be recursively expanded. */ { /* Turn off --warn-undefined-variables while we expand SHELL and IFS. */ int save = warn_undefined_variables_flag; warn_undefined_variables_flag = 0; p = variable_expand ("$(strip $(GPATH))"); warn_undefined_variables_flag = save; } if (*p != '\0') { /* Save the list of vpaths. */ struct vpath *save_vpaths = vpaths; char gp[] = "%"; /* Empty `vpaths' so the new one will have no next, and `vpaths' will still be nil if P contains no existing directories. */ vpaths = 0; /* Parse P. */ construct_vpath_list (gp, p); /* Store the created path as the GPATH, and restore the old list of vpaths. */ gpaths = vpaths; vpaths = save_vpaths; } } /* Construct the VPATH listing for the PATTERN and DIRPATH given. This function is called to generate selective VPATH lists and also for the general VPATH list (which is in fact just a selective VPATH that is applied to everything). The returned pointer is either put in the linked list of all selective VPATH lists or in the GENERAL_VPATH variable. If DIRPATH is nil, remove all previous listings with the same pattern. If PATTERN is nil, remove all VPATH listings. Existing and readable directories that are not "." given in the DIRPATH separated by the path element separator (defined in make.h) are loaded into the directory hash table if they are not there already and put in the VPATH searchpath for the given pattern with trailing slashes stripped off if present (and if the directory is not the root, "/"). The length of the longest entry in the list is put in the structure as well. The new entry will be at the head of the VPATHS chain. */ void construct_vpath_list (char *pattern, char *dirpath) { unsigned int elem; char *p; const char **vpath; unsigned int maxvpath; unsigned int maxelem; const char *percent = NULL; if (pattern != 0) percent = find_percent (pattern); if (dirpath == 0) { /* Remove matching listings. */ struct vpath *path, *lastpath; lastpath = 0; path = vpaths; while (path != 0) { struct vpath *next = path->next; if (pattern == 0 || (((percent == 0 && path->percent == 0) || (percent - pattern == path->percent - path->pattern)) && streq (pattern, path->pattern))) { /* Remove it from the linked list. */ if (lastpath == 0) vpaths = path->next; else lastpath->next = next; /* Free its unused storage. */ /* MSVC erroneously warns without a cast here. */ free ((void *)path->searchpath); free (path); } else lastpath = path; path = next; } return; } #ifdef WINDOWS32 convert_vpath_to_windows32(dirpath, ';'); #endif /* Skip over any initial separators and blanks. */ while (*dirpath == PATH_SEPARATOR_CHAR || isblank ((unsigned char)*dirpath)) ++dirpath; /* Figure out the maximum number of VPATH entries and put it in MAXELEM. We start with 2, one before the first separator and one nil (the list terminator) and increment our estimated number for each separator or blank we find. */ maxelem = 2; p = dirpath; while (*p != '\0') if (*p++ == PATH_SEPARATOR_CHAR || isblank ((unsigned char)*p)) ++maxelem; vpath = xmalloc (maxelem * sizeof (const char *)); maxvpath = 0; elem = 0; p = dirpath; while (*p != '\0') { char *v; unsigned int len; /* Find the end of this entry. */ v = p; while (*p != '\0' #if defined(HAVE_DOS_PATHS) && (PATH_SEPARATOR_CHAR == ':') /* Platforms whose PATH_SEPARATOR_CHAR is ':' and which also define HAVE_DOS_PATHS would like us to recognize colons after the drive letter in the likes of "D:/foo/bar:C:/xyzzy". */ && (*p != PATH_SEPARATOR_CHAR || (p == v + 1 && (p[1] == '/' || p[1] == '\\'))) #else && *p != PATH_SEPARATOR_CHAR #endif && !isblank ((unsigned char)*p)) ++p; len = p - v; /* Make sure there's no trailing slash, but still allow "/" as a directory. */ #if defined(__MSDOS__) || defined(__EMX__) || defined(HAVE_DOS_PATHS) /* We need also to leave alone a trailing slash in "d:/". */ if (len > 3 || (len > 1 && v[1] != ':')) #endif if (len > 1 && p[-1] == '/') --len; /* Put the directory on the vpath list. */ if (len > 1 || *v != '.') { vpath[elem++] = dir_name (strcache_add_len (v, len)); if (len > maxvpath) maxvpath = len; } /* Skip over separators and blanks between entries. */ while (*p == PATH_SEPARATOR_CHAR || isblank ((unsigned char)*p)) ++p; } if (elem > 0) { struct vpath *path; /* ELEM is now incremented one element past the last entry, to where the nil-pointer terminator goes. Usually this is maxelem - 1. If not, shrink down. */ if (elem < (maxelem - 1)) vpath = xrealloc (vpath, (elem+1) * sizeof (const char *)); /* Put the nil-pointer terminator on the end of the VPATH list. */ vpath[elem] = NULL; /* Construct the vpath structure and put it into the linked list. */ path = xmalloc (sizeof (struct vpath)); path->searchpath = vpath; path->maxlen = maxvpath; path->next = vpaths; vpaths = path; /* Set up the members. */ path->pattern = strcache_add (pattern); path->patlen = strlen (pattern); path->percent = percent ? path->pattern + (percent - pattern) : 0; } else /* There were no entries, so free whatever space we allocated. */ /* MSVC erroneously warns without a cast here. */ free ((void *)vpath); } /* Search the GPATH list for a pathname string that matches the one passed in. If it is found, return 1. Otherwise we return 0. */ int gpath_search (const char *file, unsigned int len) { const char **gp; if (gpaths && (len <= gpaths->maxlen)) for (gp = gpaths->searchpath; *gp != NULL; ++gp) if (strneq (*gp, file, len) && (*gp)[len] == '\0') return 1; return 0; } /* Search the given VPATH list for a directory where the name pointed to by FILE exists. If it is found, we return a cached name of the existing file and set *MTIME_PTR (if MTIME_PTR is not NULL) to its modtime (or zero if no stat call was done). Also set the matching directory index in PATH_INDEX if it is not NULL. Otherwise we return NULL. */ static const char * selective_vpath_search (struct vpath *path, const char *file, FILE_TIMESTAMP *mtime_ptr, unsigned int* path_index) { int not_target; char *name; const char *n; const char *filename; const char **vpath = path->searchpath; unsigned int maxvpath = path->maxlen; unsigned int i; unsigned int flen, vlen, name_dplen; int exists = 0; /* Find out if *FILE is a target. If and only if it is NOT a target, we will accept prospective files that don't exist but are mentioned in a makefile. */ { struct file *f = lookup_file (file); not_target = f == 0 || !f->is_target; } flen = strlen (file); /* Split *FILE into a directory prefix and a name-within-directory. NAME_DPLEN gets the length of the prefix; FILENAME gets the pointer to the name-within-directory and FLEN is its length. */ n = strrchr (file, '/'); #ifdef HAVE_DOS_PATHS /* We need the rightmost slash or backslash. */ { const char *bslash = strrchr(file, '\\'); if (!n || bslash > n) n = bslash; } #endif name_dplen = n != 0 ? n - file : 0; filename = name_dplen > 0 ? n + 1 : file; if (name_dplen > 0) flen -= name_dplen + 1; /* Get enough space for the biggest VPATH entry, a slash, the directory prefix that came with FILE, another slash (although this one may not always be necessary), the filename, and a null terminator. */ name = alloca (maxvpath + 1 + name_dplen + 1 + flen + 1); /* Try each VPATH entry. */ for (i = 0; vpath[i] != 0; ++i) { int exists_in_cache = 0; char *p; p = name; /* Put the next VPATH entry into NAME at P and increment P past it. */ vlen = strlen (vpath[i]); memcpy (p, vpath[i], vlen); p += vlen; /* Add the directory prefix already in *FILE. */ if (name_dplen > 0) { #ifndef VMS *p++ = '/'; #endif memcpy (p, file, name_dplen); p += name_dplen; } #ifdef HAVE_DOS_PATHS /* Cause the next if to treat backslash and slash alike. */ if (p != name && p[-1] == '\\' ) p[-1] = '/'; #endif /* Now add the name-within-directory at the end of NAME. */ #ifndef VMS if (p != name && p[-1] != '/') { *p = '/'; memcpy (p + 1, filename, flen + 1); } else #endif memcpy (p, filename, flen + 1); /* Check if the file is mentioned in a makefile. If *FILE is not a target, that is enough for us to decide this file exists. If *FILE is a target, then the file must be mentioned in the makefile also as a target to be chosen. The restriction that *FILE must not be a target for a makefile-mentioned file to be chosen was added by an inadequately commented change in July 1990; I am not sure off hand what problem it fixes. In December 1993 I loosened this restriction to allow a file to be chosen if it is mentioned as a target in a makefile. This seem logical. Special handling for -W / -o: make sure we preserve the special values here. Actually this whole thing is a little bogus: I think we should ditch the name/hname thing and look into the renamed capability that already exists for files: that is, have a new struct file* entry for the VPATH-found file, and set the renamed field if we use it. */ { struct file *f = lookup_file (name); if (f != 0) { exists = not_target || f->is_target; if (exists && mtime_ptr && (f->last_mtime == OLD_MTIME || f->last_mtime == NEW_MTIME)) { *mtime_ptr = f->last_mtime; mtime_ptr = 0; } } } if (!exists) { /* That file wasn't mentioned in the makefile. See if it actually exists. */ #ifdef VMS exists_in_cache = exists = dir_file_exists_p (vpath[i], filename); #else /* Clobber a null into the name at the last slash. Now NAME is the name of the directory to look in. */ *p = '\0'; /* We know the directory is in the hash table now because either construct_vpath_list or the code just above put it there. Does the file we seek exist in it? */ exists_in_cache = exists = dir_file_exists_p (name, filename); #endif } if (exists) { /* The file is in the directory cache. Now check that it actually exists in the filesystem. The cache may be out of date. When vpath thinks a file exists, but stat fails for it, confusion results in the higher levels. */ struct stat st; #ifndef VMS /* Put the slash back in NAME. */ *p = '/'; #endif if (exists_in_cache) /* Makefile-mentioned file need not exist. */ { int e; EINTRLOOP (e, stat (name, &st)); /* Does it really exist? */ if (e != 0) { exists = 0; continue; } /* Store the modtime into *MTIME_PTR for the caller. */ if (mtime_ptr != 0) { *mtime_ptr = FILE_TIMESTAMP_STAT_MODTIME (name, st); mtime_ptr = 0; } } /* We have found a file. If we get here and mtime_ptr hasn't been set, record UNKNOWN_MTIME to indicate this. */ if (mtime_ptr != 0) *mtime_ptr = UNKNOWN_MTIME; /* Store the name we found and return it. */ if (path_index) *path_index = i; return strcache_add_len (name, (p + 1 - name) + flen); } } return 0; } /* Search the VPATH list whose pattern matches FILE for a directory where FILE exists. If it is found, return the cached name of an existing file, and set *MTIME_PTR (if MTIME_PTR is not NULL) to its modtime (or zero if no stat call was done). Also set the matching directory index in VPATH_INDEX and PATH_INDEX if they are not NULL. Otherwise we return 0. */ const char * vpath_search (const char *file, FILE_TIMESTAMP *mtime_ptr, unsigned int* vpath_index, unsigned int* path_index) { struct vpath *v; /* If there are no VPATH entries or FILENAME starts at the root, there is nothing we can do. */ if (file[0] == '/' #ifdef HAVE_DOS_PATHS || file[0] == '\\' || file[1] == ':' #endif || (vpaths == 0 && general_vpath == 0)) return 0; if (vpath_index) { *vpath_index = 0; *path_index = 0; } for (v = vpaths; v != 0; v = v->next) { if (pattern_matches (v->pattern, v->percent, file)) { const char *p = selective_vpath_search ( v, file, mtime_ptr, path_index); if (p) return p; } if (vpath_index) ++*vpath_index; } if (general_vpath != 0) { const char *p = selective_vpath_search ( general_vpath, file, mtime_ptr, path_index); if (p) return p; } return 0; } /* Print the data base of VPATH search paths. */ void print_vpath_data_base (void) { unsigned int nvpaths; struct vpath *v; puts (_("\n# VPATH Search Paths\n")); nvpaths = 0; for (v = vpaths; v != 0; v = v->next) { register unsigned int i; ++nvpaths; printf ("vpath %s ", v->pattern); for (i = 0; v->searchpath[i] != 0; ++i) printf ("%s%c", v->searchpath[i], v->searchpath[i + 1] == 0 ? '\n' : PATH_SEPARATOR_CHAR); } if (vpaths == 0) puts (_("# No `vpath' search paths.")); else printf (_("\n# %u `vpath' search paths.\n"), nvpaths); if (general_vpath == 0) puts (_("\n# No general (`VPATH' variable) search path.")); else { const char **path = general_vpath->searchpath; unsigned int i; fputs (_("\n# General (`VPATH' variable) search path:\n# "), stdout); for (i = 0; path[i] != 0; ++i) printf ("%s%c", path[i], path[i + 1] == 0 ? '\n' : PATH_SEPARATOR_CHAR); } }
/* hash.c -- hash table maintenance Copyright (C) 1995, 1999, 2002, 2010 Free Software Foundation, Inc. Written by Greg McGary <gkm@gnu.org> <greg@mcgary.org> GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" #include "hash.h" #define CALLOC(t, n) ((t *) calloc (sizeof (t), (n))) #define MALLOC(t, n) ((t *) xmalloc (sizeof (t) * (n))) #define REALLOC(o, t, n) ((t *) xrealloc ((o), sizeof (t) * (n))) #define CLONE(o, t, n) ((t *) memcpy (MALLOC (t, (n)), (o), sizeof (t) * (n))) static void hash_rehash __P((struct hash_table* ht)); static unsigned long round_up_2 __P((unsigned long rough)); /* Implement double hashing with open addressing. The table size is always a power of two. The secondary (`increment') hash function is forced to return an odd-value, in order to be relatively prime to the table size. This guarantees that the increment can potentially hit every slot in the table during collision resolution. */ void *hash_deleted_item = &hash_deleted_item; /* Force the table size to be a power of two, possibly rounding up the given size. */ void hash_init (struct hash_table *ht, unsigned long size, hash_func_t hash_1, hash_func_t hash_2, hash_cmp_func_t hash_cmp) { ht->ht_size = round_up_2 (size); ht->ht_empty_slots = ht->ht_size; ht->ht_vec = (void**) CALLOC (struct token *, ht->ht_size); if (ht->ht_vec == 0) { fprintf (stderr, _("can't allocate %lu bytes for hash table: memory exhausted"), ht->ht_size * (unsigned long) sizeof (struct token *)); exit (1); } ht->ht_capacity = ht->ht_size - (ht->ht_size / 16); /* 93.75% loading factor */ ht->ht_fill = 0; ht->ht_collisions = 0; ht->ht_lookups = 0; ht->ht_rehashes = 0; ht->ht_hash_1 = hash_1; ht->ht_hash_2 = hash_2; ht->ht_compare = hash_cmp; } /* Load an array of items into `ht'. */ void hash_load (struct hash_table *ht, void *item_table, unsigned long cardinality, unsigned long size) { char *items = (char *) item_table; while (cardinality--) { hash_insert (ht, items); items += size; } } /* Returns the address of the table slot matching `key'. If `key' is not found, return the address of an empty slot suitable for inserting `key'. The caller is responsible for incrementing ht_fill on insertion. */ void ** hash_find_slot (struct hash_table *ht, const void *key) { void **slot; void **deleted_slot = 0; unsigned int hash_2 = 0; unsigned int hash_1 = (*ht->ht_hash_1) (key); ht->ht_lookups++; for (;;) { hash_1 &= (ht->ht_size - 1); slot = &ht->ht_vec[hash_1]; if (*slot == 0) return (deleted_slot ? deleted_slot : slot); if (*slot == hash_deleted_item) { if (deleted_slot == 0) deleted_slot = slot; } else { if (key == *slot) return slot; if ((*ht->ht_compare) (key, *slot) == 0) return slot; ht->ht_collisions++; } if (!hash_2) hash_2 = (*ht->ht_hash_2) (key) | 1; hash_1 += hash_2; } } void * hash_find_item (struct hash_table *ht, const void *key) { void **slot = hash_find_slot (ht, key); return ((HASH_VACANT (*slot)) ? 0 : *slot); } void * hash_insert (struct hash_table *ht, const void *item) { void **slot = hash_find_slot (ht, item); const void *old_item = *slot; hash_insert_at (ht, item, slot); return (void *)((HASH_VACANT (old_item)) ? 0 : old_item); } void * hash_insert_at (struct hash_table *ht, const void *item, const void *slot) { const void *old_item = *(void **) slot; if (HASH_VACANT (old_item)) { ht->ht_fill++; if (old_item == 0) ht->ht_empty_slots--; old_item = item; } *(void const **) slot = item; if (ht->ht_empty_slots < ht->ht_size - ht->ht_capacity) { hash_rehash (ht); return (void *) hash_find_slot (ht, item); } else return (void *) slot; } void * hash_delete (struct hash_table *ht, const void *item) { void **slot = hash_find_slot (ht, item); return hash_delete_at (ht, slot); } void * hash_delete_at (struct hash_table *ht, const void *slot) { void *item = *(void **) slot; if (!HASH_VACANT (item)) { *(void const **) slot = hash_deleted_item; ht->ht_fill--; return item; } else return 0; } void hash_free_items (struct hash_table *ht) { void **vec = ht->ht_vec; void **end = &vec[ht->ht_size]; for (; vec < end; vec++) { void *item = *vec; if (!HASH_VACANT (item)) free (item); *vec = 0; } ht->ht_fill = 0; ht->ht_empty_slots = ht->ht_size; } void hash_delete_items (struct hash_table *ht) { void **vec = ht->ht_vec; void **end = &vec[ht->ht_size]; for (; vec < end; vec++) *vec = 0; ht->ht_fill = 0; ht->ht_collisions = 0; ht->ht_lookups = 0; ht->ht_rehashes = 0; ht->ht_empty_slots = ht->ht_size; } void hash_free (struct hash_table *ht, int free_items) { if (free_items) hash_free_items (ht); else { ht->ht_fill = 0; ht->ht_empty_slots = ht->ht_size; } free (ht->ht_vec); ht->ht_vec = 0; ht->ht_capacity = 0; } void hash_map (struct hash_table *ht, hash_map_func_t map) { void **slot; void **end = &ht->ht_vec[ht->ht_size]; for (slot = ht->ht_vec; slot < end; slot++) { if (!HASH_VACANT (*slot)) (*map) (*slot); } } void hash_map_arg (struct hash_table *ht, hash_map_arg_func_t map, void *arg) { void **slot; void **end = &ht->ht_vec[ht->ht_size]; for (slot = ht->ht_vec; slot < end; slot++) { if (!HASH_VACANT (*slot)) (*map) (*slot, arg); } } /* Double the size of the hash table in the event of overflow... */ static void hash_rehash (struct hash_table *ht) { unsigned long old_ht_size = ht->ht_size; void **old_vec = ht->ht_vec; void **ovp; if (ht->ht_fill >= ht->ht_capacity) { ht->ht_size *= 2; ht->ht_capacity = ht->ht_size - (ht->ht_size >> 4); } ht->ht_rehashes++; ht->ht_vec = (void **) CALLOC (struct token *, ht->ht_size); for (ovp = old_vec; ovp < &old_vec[old_ht_size]; ovp++) { if (! HASH_VACANT (*ovp)) { void **slot = hash_find_slot (ht, *ovp); *slot = *ovp; } } ht->ht_empty_slots = ht->ht_size - ht->ht_fill; free (old_vec); } void hash_print_stats (struct hash_table *ht, FILE *out_FILE) { /* GKM FIXME: honor NO_FLOAT */ fprintf (out_FILE, _("Load=%ld/%ld=%.0f%%, "), ht->ht_fill, ht->ht_size, 100.0 * (double) ht->ht_fill / (double) ht->ht_size); fprintf (out_FILE, _("Rehash=%d, "), ht->ht_rehashes); fprintf (out_FILE, _("Collisions=%ld/%ld=%.0f%%"), ht->ht_collisions, ht->ht_lookups, (ht->ht_lookups ? (100.0 * (double) ht->ht_collisions / (double) ht->ht_lookups) : 0)); } /* Dump all items into a NULL-terminated vector. Use the user-supplied vector, or malloc one. */ void ** hash_dump (struct hash_table *ht, void **vector_0, qsort_cmp_t compare) { void **vector; void **slot; void **end = &ht->ht_vec[ht->ht_size]; if (vector_0 == 0) vector_0 = MALLOC (void *, ht->ht_fill + 1); vector = vector_0; for (slot = ht->ht_vec; slot < end; slot++) if (!HASH_VACANT (*slot)) *vector++ = *slot; *vector = 0; if (compare) qsort (vector_0, ht->ht_fill, sizeof (void *), compare); return vector_0; } /* Round a given number up to the nearest power of 2. */ static unsigned long round_up_2 (unsigned long n) { n |= (n >> 1); n |= (n >> 2); n |= (n >> 4); n |= (n >> 8); n |= (n >> 16); #if !defined(HAVE_LIMITS_H) || ULONG_MAX > 4294967295 /* We only need this on systems where unsigned long is >32 bits. */ n |= (n >> 32); #endif return n + 1; }
/* Template for the remote job exportation interface to GNU Make. Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This file is part of GNU Make. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "make.h" #include "filedef.h" #include "job.h" #include "commands.h" char *remote_description = 0; /* Call once at startup even if no commands are run. */ void remote_setup (void) { } /* Called before exit. */ void remote_cleanup (void) { } /* Return nonzero if the next job should be done remotely. */ int start_remote_job_p (int first_p UNUSED) { return 0; } /* Start a remote job running the command in ARGV, with environment from ENVP. It gets standard input from STDIN_FD. On failure, return nonzero. On success, return zero, and set *USED_STDIN to nonzero if it will actually use STDIN_FD, zero if not, set *ID_PTR to a unique identification, and set *IS_REMOTE to zero if the job is local, nonzero if it is remote (meaning *ID_PTR is a process ID). */ int start_remote_job (char **argv UNUSED, char **envp UNUSED, int stdin_fd UNUSED, int *is_remote UNUSED, int *id_ptr UNUSED, int *used_stdin UNUSED) { return -1; } /* Get the status of a dead remote child. Block waiting for one to die if BLOCK is nonzero. Set *EXIT_CODE_PTR to the exit status, *SIGNAL_PTR to the termination signal or zero if it exited normally, and *COREDUMP_PTR nonzero if it dumped core. Return the ID of the child that died, 0 if we would have to block and !BLOCK, or < 0 if there were none. */ int remote_status (int *exit_code_ptr UNUSED, int *signal_ptr UNUSED, int *coredump_ptr UNUSED, int block UNUSED) { errno = ECHILD; return -1; } /* Block asynchronous notification of remote child death. If this notification is done by raising the child termination signal, do not block that signal. */ void block_remote_children (void) { return; } /* Restore asynchronous notification of remote child death. If this is done by raising the child termination signal, do not unblock that signal. */ void unblock_remote_children (void) { return; } /* Send signal SIG to child ID. Return 0 if successful, -1 if not. */ int remote_kill (int id UNUSED, int sig UNUSED) { return -1; }
/* Get the system load averages. Copyright (C) 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. GNU Make is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* Compile-time symbols that this file uses: HAVE_PSTAT_GETDYNAMIC Define this if your system has the pstat_getdynamic function. I think it is unique to HPUX9. The best way to get the definition is through the AC_FUNC_GETLOADAVG macro that comes with autoconf 2.13 or newer. If that isn't an option, then just put AC_CHECK_FUNCS(pstat_getdynamic) in your configure.in file. FIXUP_KERNEL_SYMBOL_ADDR() Adjust address in returned struct nlist. KERNEL_FILE Pathname of the kernel to nlist. LDAV_CVT() Scale the load average from the kernel. Returns a double. LDAV_SYMBOL Name of kernel symbol giving load average. LOAD_AVE_TYPE Type of the load average array in the kernel. Must be defined unless one of apollo, DGUX, NeXT, or UMAX is defined; or we have libkstat; otherwise, no load average is available. NLIST_STRUCT Include nlist.h, not a.out.h, and the nlist n_name element is a pointer, not an array. HAVE_STRUCT_NLIST_N_UN_N_NAME struct nlist has an n_un member, not n_name. LINUX_LDAV_FILE [__linux__]: File containing load averages. Specific system predefines this file uses, aside from setting default values if not emacs: apollo BSD Real BSD, not just BSD-like. convex DGUX eunice UNIX emulator under VMS. hpux __MSDOS__ No-op for MSDOS. NeXT sgi sequent Sequent Dynix 3.x.x (BSD) _SEQUENT_ Sequent DYNIX/ptx 1.x.x (SYSV) sony_news NEWS-OS (works at least for 4.1C) UMAX UMAX4_3 VMS WINDOWS32 No-op for Windows95/NT. __linux__ Linux: assumes /proc filesystem mounted. Support from Michael K. Johnson. __NetBSD__ NetBSD: assumes /kern filesystem mounted. In addition, to avoid nesting many #ifdefs, we internally set LDAV_DONE to indicate that the load average has been computed. We also #define LDAV_PRIVILEGED if a program will require special installation to be able to call getloadavg. */ /* This should always be first. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <sys/types.h> /* Both the Emacs and non-Emacs sections want this. Some configuration files' definitions for the LOAD_AVE_CVT macro (like sparc.h's) use macros like FSCALE, defined here. */ #if defined (unix) || defined (__unix) # include <sys/param.h> #endif /* Exclude all the code except the test program at the end if the system has its own `getloadavg' function. The declaration of `errno' is needed by the test program as well as the function itself, so it comes first. */ #include <errno.h> #ifndef errno extern int errno; #endif #if HAVE_LOCALE_H # include <locale.h> #endif #if !HAVE_SETLOCALE # define setlocale(Category, Locale) /* empty */ #endif #ifndef HAVE_GETLOADAVG /* The existing Emacs configuration files define a macro called LOAD_AVE_CVT, which accepts a value of type LOAD_AVE_TYPE, and returns the load average multiplied by 100. What we actually want is a macro called LDAV_CVT, which returns the load average as an unmultiplied double. For backwards compatibility, we'll define LDAV_CVT in terms of LOAD_AVE_CVT, but future machine config files should just define LDAV_CVT directly. */ # if !defined(LDAV_CVT) && defined(LOAD_AVE_CVT) # define LDAV_CVT(n) (LOAD_AVE_CVT (n) / 100.0) # endif # if !defined (BSD) && defined (ultrix) /* Ultrix behaves like BSD on Vaxen. */ # define BSD # endif # ifdef NeXT /* NeXT in the 2.{0,1,2} releases defines BSD in <sys/param.h>, which conflicts with the definition understood in this file, that this really is BSD. */ # undef BSD /* NeXT defines FSCALE in <sys/param.h>. However, we take FSCALE being defined to mean that the nlist method should be used, which is not true. */ # undef FSCALE # endif /* Same issues as for NeXT apply to the HURD-based GNU system. */ # ifdef __GNU__ # undef BSD # undef FSCALE # endif /* __GNU__ */ /* Set values that are different from the defaults, which are set a little farther down with #ifndef. */ /* Some shorthands. */ # if defined (HPUX) && !defined (hpux) # define hpux # endif # if defined (__hpux) && !defined (hpux) # define hpux # endif # if defined (__sun) && !defined (sun) # define sun # endif # if defined(hp300) && !defined(hpux) # define MORE_BSD # endif # if defined(ultrix) && defined(mips) # define decstation # endif # if defined (__SVR4) && !defined (SVR4) # define SVR4 # endif # if (defined(sun) && defined(SVR4)) || defined (SOLARIS2) # define SUNOS_5 # endif # if defined (__osf__) && (defined (__alpha) || defined (__alpha__)) # define OSF_ALPHA # include <sys/mbuf.h> # include <sys/socket.h> # include <net/route.h> # include <sys/table.h> # endif # if defined (__osf__) && (defined (mips) || defined (__mips__)) # define OSF_MIPS # include <sys/table.h> # endif /* UTek's /bin/cc on the 4300 has no architecture specific cpp define by default, but _MACH_IND_SYS_TYPES is defined in <sys/types.h>. Combine that with a couple of other things and we'll have a unique match. */ # if !defined (tek4300) && defined (unix) && defined (m68k) && defined (mc68000) && defined (mc68020) && defined (_MACH_IND_SYS_TYPES) # define tek4300 /* Define by emacs, but not by other users. */ # endif /* AC_FUNC_GETLOADAVG thinks QNX is SVR4, but it isn't. */ # if defined(__QNX__) # undef SVR4 # endif /* VAX C can't handle multi-line #ifs, or lines longer than 256 chars. */ # ifndef LOAD_AVE_TYPE # ifdef MORE_BSD # define LOAD_AVE_TYPE long # endif # ifdef sun # define LOAD_AVE_TYPE long # endif # ifdef decstation # define LOAD_AVE_TYPE long # endif # ifdef _SEQUENT_ # define LOAD_AVE_TYPE long # endif # ifdef sgi # define LOAD_AVE_TYPE long # endif # ifdef SVR4 # define LOAD_AVE_TYPE long # endif # ifdef sony_news # define LOAD_AVE_TYPE long # endif # ifdef sequent # define LOAD_AVE_TYPE long # endif # ifdef OSF_ALPHA # define LOAD_AVE_TYPE long # endif # if defined (ardent) && defined (titan) # define LOAD_AVE_TYPE long # endif # ifdef tek4300 # define LOAD_AVE_TYPE long # endif # if defined(alliant) && defined(i860) /* Alliant FX/2800 */ # define LOAD_AVE_TYPE long # endif # ifdef _AIX # define LOAD_AVE_TYPE long # endif # ifdef convex # define LOAD_AVE_TYPE double # ifndef LDAV_CVT # define LDAV_CVT(n) (n) # endif # endif # endif /* No LOAD_AVE_TYPE. */ # ifdef OSF_ALPHA /* <sys/param.h> defines an incorrect value for FSCALE on Alpha OSF/1, according to ghazi@noc.rutgers.edu. */ # undef FSCALE # define FSCALE 1024.0 # endif # if defined(alliant) && defined(i860) /* Alliant FX/2800 */ /* <sys/param.h> defines an incorrect value for FSCALE on an Alliant FX/2800 Concentrix 2.2, according to ghazi@noc.rutgers.edu. */ # undef FSCALE # define FSCALE 100.0 # endif # ifndef FSCALE /* SunOS and some others define FSCALE in sys/param.h. */ # ifdef MORE_BSD # define FSCALE 2048.0 # endif # if defined(MIPS) || defined(SVR4) || defined(decstation) # define FSCALE 256 # endif # if defined (sgi) || defined (sequent) /* Sometimes both MIPS and sgi are defined, so FSCALE was just defined above under #ifdef MIPS. But we want the sgi value. */ # undef FSCALE # define FSCALE 1000.0 # endif # if defined (ardent) && defined (titan) # define FSCALE 65536.0 # endif # ifdef tek4300 # define FSCALE 100.0 # endif # ifdef _AIX # define FSCALE 65536.0 # endif # endif /* Not FSCALE. */ # if !defined (LDAV_CVT) && defined (FSCALE) # define LDAV_CVT(n) (((double) (n)) / FSCALE) # endif # if defined(sgi) || (defined(mips) && !defined(BSD)) # define FIXUP_KERNEL_SYMBOL_ADDR(nl) ((nl)[0].n_value &= ~(1 << 31)) # endif # if !defined (KERNEL_FILE) && defined (sequent) # define KERNEL_FILE "/dynix" # endif # if !defined (KERNEL_FILE) && defined (hpux) # define KERNEL_FILE "/hp-ux" # endif # if !defined(KERNEL_FILE) && (defined(_SEQUENT_) || defined(MIPS) || defined(SVR4) || defined(ISC) || defined (sgi) || (defined (ardent) && defined (titan))) # define KERNEL_FILE "/unix" # endif # if !defined (LDAV_SYMBOL) && defined (alliant) # define LDAV_SYMBOL "_Loadavg" # endif # if !defined(LDAV_SYMBOL) && ((defined(hpux) && !defined(hp9000s300)) || defined(_SEQUENT_) || defined(SVR4) || defined(ISC) || defined(sgi) || (defined (ardent) && defined (titan)) || defined (_AIX)) # define LDAV_SYMBOL "avenrun" # endif # ifdef HAVE_UNISTD_H # include <unistd.h> # endif # include <stdio.h> /* LOAD_AVE_TYPE should only get defined if we're going to use the nlist method. */ # if !defined(LOAD_AVE_TYPE) && (defined(BSD) || defined(LDAV_CVT) || defined(KERNEL_FILE) || defined(LDAV_SYMBOL)) && !defined(__riscos__) # define LOAD_AVE_TYPE double # endif # ifdef LOAD_AVE_TYPE # ifndef VMS # ifndef __linux__ # ifdef HAVE_NLIST_H # include <nlist.h> # else # include <a.out.h> # endif # ifdef SUNOS_5 # include <fcntl.h> # include <kvm.h> # include <kstat.h> # endif # if defined (hpux) && defined (HAVE_PSTAT_GETDYNAMIC) # include <sys/pstat.h> # endif # ifndef KERNEL_FILE # define KERNEL_FILE "/vmunix" # endif /* KERNEL_FILE */ # ifndef LDAV_SYMBOL # define LDAV_SYMBOL "_avenrun" # endif /* LDAV_SYMBOL */ # endif /* __linux__ */ # else /* VMS */ # ifndef eunice # include <iodef.h> # include <descrip.h> # else /* eunice */ # include <vms/iodef.h> # endif /* eunice */ # endif /* VMS */ # ifndef LDAV_CVT # define LDAV_CVT(n) ((double) (n)) # endif /* !LDAV_CVT */ # endif /* LOAD_AVE_TYPE */ # if defined(__GNU__) && !defined (NeXT) /* Note that NeXT Openstep defines __GNU__ even though it should not. */ /* GNU system acts much like NeXT, for load average purposes, but not exactly. */ # define NeXT # define host_self mach_host_self # endif # ifdef NeXT # ifdef HAVE_MACH_MACH_H # include <mach/mach.h> # else # include <mach.h> # endif # endif /* NeXT */ # ifdef sgi # include <sys/sysmp.h> # endif /* sgi */ # ifdef UMAX # include <stdio.h> # include <signal.h> # include <sys/time.h> # include <sys/wait.h> # include <sys/syscall.h> # ifdef UMAX_43 # include <machine/cpu.h> # include <inq_stats/statistics.h> # include <inq_stats/sysstats.h> # include <inq_stats/cpustats.h> # include <inq_stats/procstats.h> # else /* Not UMAX_43. */ # include <sys/sysdefs.h> # include <sys/statistics.h> # include <sys/sysstats.h> # include <sys/cpudefs.h> # include <sys/cpustats.h> # include <sys/procstats.h> # endif /* Not UMAX_43. */ # endif /* UMAX */ # ifdef DGUX # include <sys/dg_sys_info.h> # endif # if defined(HAVE_FCNTL_H) || defined(_POSIX_VERSION) # include <fcntl.h> # else # include <sys/file.h> # endif /* Avoid static vars inside a function since in HPUX they dump as pure. */ # ifdef NeXT static processor_set_t default_set; static int getloadavg_initialized; # endif /* NeXT */ # ifdef UMAX static unsigned int cpus = 0; static unsigned int samples; # endif /* UMAX */ # ifdef DGUX static struct dg_sys_info_load_info load_info; /* what-a-mouthful! */ # endif /* DGUX */ #if !defined(HAVE_LIBKSTAT) && defined(LOAD_AVE_TYPE) /* File descriptor open to /dev/kmem or VMS load ave driver. */ static int channel; /* Nonzero iff channel is valid. */ static int getloadavg_initialized; /* Offset in kmem to seek to read load average, or 0 means invalid. */ static long offset; #if !defined(VMS) && !defined(sgi) && !defined(__linux__) static struct nlist nl[2]; #endif /* Not VMS or sgi */ #ifdef SUNOS_5 static kvm_t *kd; #endif /* SUNOS_5 */ #endif /* LOAD_AVE_TYPE && !HAVE_LIBKSTAT */ /* Put the 1 minute, 5 minute and 15 minute load averages into the first NELEM elements of LOADAVG. Return the number written (never more than 3, but may be less than NELEM), or -1 if an error occurred. */ int getloadavg (double loadavg[], int nelem) { int elem = 0; /* Return value. */ # ifdef NO_GET_LOAD_AVG # define LDAV_DONE /* Set errno to zero to indicate that there was no particular error; this function just can't work at all on this system. */ errno = 0; elem = -1; # endif # if !defined (LDAV_DONE) && defined (HAVE_LIBKSTAT) /* Use libkstat because we don't have to be root. */ # define LDAV_DONE kstat_ctl_t *kc; kstat_t *ksp; kstat_named_t *kn; kc = kstat_open (); if (kc == 0) return -1; ksp = kstat_lookup (kc, "unix", 0, "system_misc"); if (ksp == 0 ) return -1; if (kstat_read (kc, ksp, 0) == -1) return -1; kn = kstat_data_lookup (ksp, "avenrun_1min"); if (kn == 0) { /* Return -1 if no load average information is available. */ nelem = 0; elem = -1; } if (nelem >= 1) loadavg[elem++] = (double) kn->value.ul/FSCALE; if (nelem >= 2) { kn = kstat_data_lookup (ksp, "avenrun_5min"); if (kn != 0) { loadavg[elem++] = (double) kn->value.ul/FSCALE; if (nelem >= 3) { kn = kstat_data_lookup (ksp, "avenrun_15min"); if (kn != 0) loadavg[elem++] = (double) kn->value.ul/FSCALE; } } } kstat_close (kc); # endif /* HAVE_LIBKSTAT */ # if !defined (LDAV_DONE) && defined (hpux) && defined (HAVE_PSTAT_GETDYNAMIC) /* Use pstat_getdynamic() because we don't have to be root. */ # define LDAV_DONE # undef LOAD_AVE_TYPE struct pst_dynamic dyn_info; if (pstat_getdynamic (&dyn_info, sizeof (dyn_info), 0, 0) < 0) return -1; if (nelem > 0) loadavg[elem++] = dyn_info.psd_avg_1_min; if (nelem > 1) loadavg[elem++] = dyn_info.psd_avg_5_min; if (nelem > 2) loadavg[elem++] = dyn_info.psd_avg_15_min; # endif /* hpux && HAVE_PSTAT_GETDYNAMIC */ # if !defined (LDAV_DONE) && defined (__linux__) # define LDAV_DONE # undef LOAD_AVE_TYPE # ifndef LINUX_LDAV_FILE # define LINUX_LDAV_FILE "/proc/loadavg" # endif char ldavgbuf[40]; double load_ave[3]; int fd, count; fd = open (LINUX_LDAV_FILE, O_RDONLY); if (fd == -1) return -1; count = read (fd, ldavgbuf, 40); (void) close (fd); if (count <= 0) return -1; /* The following sscanf must use the C locale. */ setlocale (LC_NUMERIC, "C"); count = sscanf (ldavgbuf, "%lf %lf %lf", &load_ave[0], &load_ave[1], &load_ave[2]); setlocale (LC_NUMERIC, ""); if (count < 1) return -1; for (elem = 0; elem < nelem && elem < count; elem++) loadavg[elem] = load_ave[elem]; return elem; # endif /* __linux__ */ # if !defined (LDAV_DONE) && defined (__NetBSD__) # define LDAV_DONE # undef LOAD_AVE_TYPE # ifndef NETBSD_LDAV_FILE # define NETBSD_LDAV_FILE "/kern/loadavg" # endif unsigned long int load_ave[3], scale; int count; FILE *fp; fp = fopen (NETBSD_LDAV_FILE, "r"); if (fp == NULL) return -1; count = fscanf (fp, "%lu %lu %lu %lu\n", &load_ave[0], &load_ave[1], &load_ave[2], &scale); (void) fclose (fp); if (count != 4) return -1; for (elem = 0; elem < nelem; elem++) loadavg[elem] = (double) load_ave[elem] / (double) scale; return elem; # endif /* __NetBSD__ */ # if !defined (LDAV_DONE) && defined (NeXT) # define LDAV_DONE /* The NeXT code was adapted from iscreen 3.2. */ host_t host; struct processor_set_basic_info info; unsigned info_count; /* We only know how to get the 1-minute average for this system, so even if the caller asks for more than 1, we only return 1. */ if (!getloadavg_initialized) { if (processor_set_default (host_self (), &default_set) == KERN_SUCCESS) getloadavg_initialized = 1; } if (getloadavg_initialized) { info_count = PROCESSOR_SET_BASIC_INFO_COUNT; if (processor_set_info (default_set, PROCESSOR_SET_BASIC_INFO, &host, (processor_set_info_t) &info, &info_count) != KERN_SUCCESS) getloadavg_initialized = 0; else { if (nelem > 0) loadavg[elem++] = (double) info.load_average / LOAD_SCALE; } } if (!getloadavg_initialized) return -1; # endif /* NeXT */ # if !defined (LDAV_DONE) && defined (UMAX) # define LDAV_DONE /* UMAX 4.2, which runs on the Encore Multimax multiprocessor, does not have a /dev/kmem. Information about the workings of the running kernel can be gathered with inq_stats system calls. We only know how to get the 1-minute average for this system. */ struct proc_summary proc_sum_data; struct stat_descr proc_info; double load; register unsigned int i, j; if (cpus == 0) { register unsigned int c, i; struct cpu_config conf; struct stat_descr desc; desc.sd_next = 0; desc.sd_subsys = SUBSYS_CPU; desc.sd_type = CPUTYPE_CONFIG; desc.sd_addr = (char *) &conf; desc.sd_size = sizeof conf; if (inq_stats (1, &desc)) return -1; c = 0; for (i = 0; i < conf.config_maxclass; ++i) { struct class_stats stats; memset (&stats, '\0', sizeof stats); desc.sd_type = CPUTYPE_CLASS; desc.sd_objid = i; desc.sd_addr = (char *) &stats; desc.sd_size = sizeof stats; if (inq_stats (1, &desc)) return -1; c += stats.class_numcpus; } cpus = c; samples = cpus < 2 ? 3 : (2 * cpus / 3); } proc_info.sd_next = 0; proc_info.sd_subsys = SUBSYS_PROC; proc_info.sd_type = PROCTYPE_SUMMARY; proc_info.sd_addr = (char *) &proc_sum_data; proc_info.sd_size = sizeof (struct proc_summary); proc_info.sd_sizeused = 0; if (inq_stats (1, &proc_info) != 0) return -1; load = proc_sum_data.ps_nrunnable; j = 0; for (i = samples - 1; i > 0; --i) { load += proc_sum_data.ps_nrun[j]; if (j++ == PS_NRUNSIZE) j = 0; } if (nelem > 0) loadavg[elem++] = load / samples / cpus; # endif /* UMAX */ # if !defined (LDAV_DONE) && defined (DGUX) # define LDAV_DONE /* This call can return -1 for an error, but with good args it's not supposed to fail. The first argument is for no apparent reason of type `long int *'. */ dg_sys_info ((long int *) &load_info, DG_SYS_INFO_LOAD_INFO_TYPE, DG_SYS_INFO_LOAD_VERSION_0); if (nelem > 0) loadavg[elem++] = load_info.one_minute; if (nelem > 1) loadavg[elem++] = load_info.five_minute; if (nelem > 2) loadavg[elem++] = load_info.fifteen_minute; # endif /* DGUX */ # if !defined (LDAV_DONE) && defined (apollo) # define LDAV_DONE /* Apollo code from lisch@mentorg.com (Ray Lischner). This system call is not documented. The load average is obtained as three long integers, for the load average over the past minute, five minutes, and fifteen minutes. Each value is a scaled integer, with 16 bits of integer part and 16 bits of fraction part. I'm not sure which operating system first supported this system call, but I know that SR10.2 supports it. */ extern void proc1_$get_loadav (); unsigned long load_ave[3]; proc1_$get_loadav (load_ave); if (nelem > 0) loadavg[elem++] = load_ave[0] / 65536.0; if (nelem > 1) loadavg[elem++] = load_ave[1] / 65536.0; if (nelem > 2) loadavg[elem++] = load_ave[2] / 65536.0; # endif /* apollo */ # if !defined (LDAV_DONE) && defined (OSF_MIPS) # define LDAV_DONE struct tbl_loadavg load_ave; table (TBL_LOADAVG, 0, &load_ave, 1, sizeof (load_ave)); loadavg[elem++] = (load_ave.tl_lscale == 0 ? load_ave.tl_avenrun.d[0] : (load_ave.tl_avenrun.l[0] / (double) load_ave.tl_lscale)); # endif /* OSF_MIPS */ # if !defined (LDAV_DONE) && (defined (__MSDOS__) || defined (WINDOWS32)) # define LDAV_DONE /* A faithful emulation is going to have to be saved for a rainy day. */ for ( ; elem < nelem; elem++) { loadavg[elem] = 0.0; } # endif /* __MSDOS__ || WINDOWS32 */ # if !defined (LDAV_DONE) && defined (OSF_ALPHA) # define LDAV_DONE struct tbl_loadavg load_ave; table (TBL_LOADAVG, 0, &load_ave, 1, sizeof (load_ave)); for (elem = 0; elem < nelem; elem++) loadavg[elem] = (load_ave.tl_lscale == 0 ? load_ave.tl_avenrun.d[elem] : (load_ave.tl_avenrun.l[elem] / (double) load_ave.tl_lscale)); # endif /* OSF_ALPHA */ # if !defined (LDAV_DONE) && defined (VMS) /* VMS specific code -- read from the Load Ave driver. */ LOAD_AVE_TYPE load_ave[3]; static int getloadavg_initialized = 0; # ifdef eunice struct { int dsc$w_length; char *dsc$a_pointer; } descriptor; # endif /* Ensure that there is a channel open to the load ave device. */ if (!getloadavg_initialized) { /* Attempt to open the channel. */ # ifdef eunice descriptor.dsc$w_length = 18; descriptor.dsc$a_pointer = "$$VMS_LOAD_AVERAGE"; # else $DESCRIPTOR (descriptor, "LAV0:"); # endif if (sys$assign (&descriptor, &channel, 0, 0) & 1) getloadavg_initialized = 1; } /* Read the load average vector. */ if (getloadavg_initialized && !(sys$qiow (0, channel, IO$_READVBLK, 0, 0, 0, load_ave, 12, 0, 0, 0, 0) & 1)) { sys$dassgn (channel); getloadavg_initialized = 0; } if (!getloadavg_initialized) return -1; # endif /* VMS */ # if !defined (LDAV_DONE) && defined(LOAD_AVE_TYPE) && !defined(VMS) /* UNIX-specific code -- read the average from /dev/kmem. */ # define LDAV_PRIVILEGED /* This code requires special installation. */ LOAD_AVE_TYPE load_ave[3]; /* Get the address of LDAV_SYMBOL. */ if (offset == 0) { # ifndef sgi # ifndef NLIST_STRUCT strcpy (nl[0].n_name, LDAV_SYMBOL); strcpy (nl[1].n_name, ""); # else /* NLIST_STRUCT */ # ifdef HAVE_STRUCT_NLIST_N_UN_N_NAME nl[0].n_un.n_name = LDAV_SYMBOL; nl[1].n_un.n_name = 0; # else /* not HAVE_STRUCT_NLIST_N_UN_N_NAME */ nl[0].n_name = LDAV_SYMBOL; nl[1].n_name = 0; # endif /* not HAVE_STRUCT_NLIST_N_UN_N_NAME */ # endif /* NLIST_STRUCT */ # ifndef SUNOS_5 if ( # if !(defined (_AIX) && !defined (ps2)) nlist (KERNEL_FILE, nl) # else /* _AIX */ knlist (nl, 1, sizeof (nl[0])) # endif >= 0) /* Omit "&& nl[0].n_type != 0 " -- it breaks on Sun386i. */ { # ifdef FIXUP_KERNEL_SYMBOL_ADDR FIXUP_KERNEL_SYMBOL_ADDR (nl); # endif offset = nl[0].n_value; } # endif /* !SUNOS_5 */ # else /* sgi */ int ldav_off; ldav_off = sysmp (MP_KERNADDR, MPKA_AVENRUN); if (ldav_off != -1) offset = (long) ldav_off & 0x7fffffff; # endif /* sgi */ } /* Make sure we have /dev/kmem open. */ if (!getloadavg_initialized) { # ifndef SUNOS_5 channel = open ("/dev/kmem", 0); if (channel >= 0) { /* Set the channel to close on exec, so it does not litter any child's descriptor table. */ # ifdef F_SETFD # ifndef FD_CLOEXEC # define FD_CLOEXEC 1 # endif (void) fcntl (channel, F_SETFD, FD_CLOEXEC); # endif getloadavg_initialized = 1; } # else /* SUNOS_5 */ /* We pass 0 for the kernel, corefile, and swapfile names to use the currently running kernel. */ kd = kvm_open (0, 0, 0, O_RDONLY, 0); if (kd != 0) { /* nlist the currently running kernel. */ kvm_nlist (kd, nl); offset = nl[0].n_value; getloadavg_initialized = 1; } # endif /* SUNOS_5 */ } /* If we can, get the load average values. */ if (offset && getloadavg_initialized) { /* Try to read the load. */ # ifndef SUNOS_5 if (lseek (channel, offset, 0) == -1L || read (channel, (char *) load_ave, sizeof (load_ave)) != sizeof (load_ave)) { close (channel); getloadavg_initialized = 0; } # else /* SUNOS_5 */ if (kvm_read (kd, offset, (char *) load_ave, sizeof (load_ave)) != sizeof (load_ave)) { kvm_close (kd); getloadavg_initialized = 0; } # endif /* SUNOS_5 */ } if (offset == 0 || !getloadavg_initialized) return -1; # endif /* LOAD_AVE_TYPE and not VMS */ # if !defined (LDAV_DONE) && defined (LOAD_AVE_TYPE) /* Including VMS. */ if (nelem > 0) loadavg[elem++] = LDAV_CVT (load_ave[0]); if (nelem > 1) loadavg[elem++] = LDAV_CVT (load_ave[1]); if (nelem > 2) loadavg[elem++] = LDAV_CVT (load_ave[2]); # define LDAV_DONE # endif /* !LDAV_DONE && LOAD_AVE_TYPE */ # ifdef LDAV_DONE return elem; # else /* Set errno to zero to indicate that there was no particular error; this function just can't work at all on this system. */ errno = 0; return -1; # endif } #endif /* ! HAVE_GETLOADAVG */ #ifdef TEST #include "make.h" int main (int argc, char **argv) { int naptime = 0; if (argc > 1) naptime = atoi (argv[1]); while (1) { double avg[3]; int loads; errno = 0; /* Don't be misled if it doesn't set errno. */ loads = getloadavg (avg, 3); if (loads == -1) { perror ("Error getting load average"); exit (1); } if (loads > 0) printf ("1-minute: %f ", avg[0]); if (loads > 1) printf ("5-minute: %f ", avg[1]); if (loads > 2) printf ("15-minute: %f ", avg[2]); if (loads > 0) putchar ('\n'); if (naptime == 0) break; sleep (naptime); } exit (0); } #endif /* TEST */
/* Copyright (C) 1991, 1992, 1993, 1996, 1997, 1998, 1999 Free Software Foundation, Inc. This file is part of the GNU C Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ #if HAVE_CONFIG_H # include <config.h> #endif /* Enable GNU extensions in fnmatch.h. */ #ifndef _GNU_SOURCE # define _GNU_SOURCE 1 #endif #include <errno.h> #include <fnmatch.h> #include <ctype.h> #if HAVE_STRING_H || defined _LIBC # include <string.h> #else # include <strings.h> #endif #if defined STDC_HEADERS || defined _LIBC # include <stdlib.h> #endif /* For platform which support the ISO C amendement 1 functionality we support user defined character classes. */ #if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H) /* Solaris 2.5 has a bug: <wchar.h> must be included before <wctype.h>. */ # include <wchar.h> # include <wctype.h> #endif /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #if defined _LIBC || !defined __GNU_LIBRARY__ # if defined STDC_HEADERS || !defined isascii # define ISASCII(c) 1 # else # define ISASCII(c) isascii(c) # endif # ifdef isblank # define ISBLANK(c) (ISASCII (c) && isblank (c)) # else # define ISBLANK(c) ((c) == ' ' || (c) == '\t') # endif # ifdef isgraph # define ISGRAPH(c) (ISASCII (c) && isgraph (c)) # else # define ISGRAPH(c) (ISASCII (c) && isprint (c) && !isspace (c)) # endif # define ISPRINT(c) (ISASCII (c) && isprint (c)) # define ISDIGIT(c) (ISASCII (c) && isdigit (c)) # define ISALNUM(c) (ISASCII (c) && isalnum (c)) # define ISALPHA(c) (ISASCII (c) && isalpha (c)) # define ISCNTRL(c) (ISASCII (c) && iscntrl (c)) # define ISLOWER(c) (ISASCII (c) && islower (c)) # define ISPUNCT(c) (ISASCII (c) && ispunct (c)) # define ISSPACE(c) (ISASCII (c) && isspace (c)) # define ISUPPER(c) (ISASCII (c) && isupper (c)) # define ISXDIGIT(c) (ISASCII (c) && isxdigit (c)) # define STREQ(s1, s2) ((strcmp (s1, s2) == 0)) # if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H) /* The GNU C library provides support for user-defined character classes and the functions from ISO C amendement 1. */ # ifdef CHARCLASS_NAME_MAX # define CHAR_CLASS_MAX_LENGTH CHARCLASS_NAME_MAX # else /* This shouldn't happen but some implementation might still have this problem. Use a reasonable default value. */ # define CHAR_CLASS_MAX_LENGTH 256 # endif # ifdef _LIBC # define IS_CHAR_CLASS(string) __wctype (string) # else # define IS_CHAR_CLASS(string) wctype (string) # endif # else # define CHAR_CLASS_MAX_LENGTH 6 /* Namely, `xdigit'. */ # define IS_CHAR_CLASS(string) \ (STREQ (string, "alpha") || STREQ (string, "upper") \ || STREQ (string, "lower") || STREQ (string, "digit") \ || STREQ (string, "alnum") || STREQ (string, "xdigit") \ || STREQ (string, "space") || STREQ (string, "print") \ || STREQ (string, "punct") || STREQ (string, "graph") \ || STREQ (string, "cntrl") || STREQ (string, "blank")) # endif /* Avoid depending on library functions or files whose names are inconsistent. */ # if !defined _LIBC && !defined getenv extern char *getenv (); # endif # ifndef errno extern int errno; # endif /* This function doesn't exist on most systems. */ # if !defined HAVE___STRCHRNUL && !defined _LIBC static char * __strchrnul (s, c) const char *s; int c; { char *result = strchr (s, c); if (result == NULL) result = strchr (s, '\0'); return result; } # endif # ifndef internal_function /* Inside GNU libc we mark some function in a special way. In other environments simply ignore the marking. */ # define internal_function # endif /* Match STRING against the filename pattern PATTERN, returning zero if it matches, nonzero if not. */ static int internal_fnmatch __P ((const char *pattern, const char *string, int no_leading_period, int flags)) internal_function; static int internal_function internal_fnmatch (pattern, string, no_leading_period, flags) const char *pattern; const char *string; int no_leading_period; int flags; { register const char *p = pattern, *n = string; register unsigned char c; /* Note that this evaluates C many times. */ # ifdef _LIBC # define FOLD(c) ((flags & FNM_CASEFOLD) ? tolower (c) : (c)) # else # define FOLD(c) ((flags & FNM_CASEFOLD) && ISUPPER (c) ? tolower (c) : (c)) # endif while ((c = *p++) != '\0') { c = FOLD (c); switch (c) { case '?': if (*n == '\0') return FNM_NOMATCH; else if (*n == '/' && (flags & FNM_FILE_NAME)) return FNM_NOMATCH; else if (*n == '.' && no_leading_period && (n == string || (n[-1] == '/' && (flags & FNM_FILE_NAME)))) return FNM_NOMATCH; break; case '\\': if (!(flags & FNM_NOESCAPE)) { c = *p++; if (c == '\0') /* Trailing \ loses. */ return FNM_NOMATCH; c = FOLD (c); } if (FOLD ((unsigned char) *n) != c) return FNM_NOMATCH; break; case '*': if (*n == '.' && no_leading_period && (n == string || (n[-1] == '/' && (flags & FNM_FILE_NAME)))) return FNM_NOMATCH; for (c = *p++; c == '?' || c == '*'; c = *p++) { if (*n == '/' && (flags & FNM_FILE_NAME)) /* A slash does not match a wildcard under FNM_FILE_NAME. */ return FNM_NOMATCH; else if (c == '?') { /* A ? needs to match one character. */ if (*n == '\0') /* There isn't another character; no match. */ return FNM_NOMATCH; else /* One character of the string is consumed in matching this ? wildcard, so *??? won't match if there are less than three characters. */ ++n; } } if (c == '\0') /* The wildcard(s) is/are the last element of the pattern. If the name is a file name and contains another slash this does mean it cannot match. */ return ((flags & FNM_FILE_NAME) && strchr (n, '/') != NULL ? FNM_NOMATCH : 0); else { const char *endp; endp = __strchrnul (n, (flags & FNM_FILE_NAME) ? '/' : '\0'); if (c == '[') { int flags2 = ((flags & FNM_FILE_NAME) ? flags : (flags & ~FNM_PERIOD)); for (--p; n < endp; ++n) if (internal_fnmatch (p, n, (no_leading_period && (n == string || (n[-1] == '/' && (flags & FNM_FILE_NAME)))), flags2) == 0) return 0; } else if (c == '/' && (flags & FNM_FILE_NAME)) { while (*n != '\0' && *n != '/') ++n; if (*n == '/' && (internal_fnmatch (p, n + 1, flags & FNM_PERIOD, flags) == 0)) return 0; } else { int flags2 = ((flags & FNM_FILE_NAME) ? flags : (flags & ~FNM_PERIOD)); if (c == '\\' && !(flags & FNM_NOESCAPE)) c = *p; c = FOLD (c); for (--p; n < endp; ++n) if (FOLD ((unsigned char) *n) == c && (internal_fnmatch (p, n, (no_leading_period && (n == string || (n[-1] == '/' && (flags & FNM_FILE_NAME)))), flags2) == 0)) return 0; } } /* If we come here no match is possible with the wildcard. */ return FNM_NOMATCH; case '[': { /* Nonzero if the sense of the character class is inverted. */ static int posixly_correct; register int not; char cold; if (posixly_correct == 0) posixly_correct = getenv ("POSIXLY_CORRECT") != NULL ? 1 : -1; if (*n == '\0') return FNM_NOMATCH; if (*n == '.' && no_leading_period && (n == string || (n[-1] == '/' && (flags & FNM_FILE_NAME)))) return FNM_NOMATCH; if (*n == '/' && (flags & FNM_FILE_NAME)) /* `/' cannot be matched. */ return FNM_NOMATCH; not = (*p == '!' || (posixly_correct < 0 && *p == '^')); if (not) ++p; c = *p++; for (;;) { unsigned char fn = FOLD ((unsigned char) *n); if (!(flags & FNM_NOESCAPE) && c == '\\') { if (*p == '\0') return FNM_NOMATCH; c = FOLD ((unsigned char) *p); ++p; if (c == fn) goto matched; } else if (c == '[' && *p == ':') { /* Leave room for the null. */ char str[CHAR_CLASS_MAX_LENGTH + 1]; size_t c1 = 0; # if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H) wctype_t wt; # endif const char *startp = p; for (;;) { if (c1 == CHAR_CLASS_MAX_LENGTH) /* The name is too long and therefore the pattern is ill-formed. */ return FNM_NOMATCH; c = *++p; if (c == ':' && p[1] == ']') { p += 2; break; } if (c < 'a' || c >= 'z') { /* This cannot possibly be a character class name. Match it as a normal range. */ p = startp; c = '['; goto normal_bracket; } str[c1++] = c; } str[c1] = '\0'; # if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H) wt = IS_CHAR_CLASS (str); if (wt == 0) /* Invalid character class name. */ return FNM_NOMATCH; if (__iswctype (__btowc ((unsigned char) *n), wt)) goto matched; # else if ((STREQ (str, "alnum") && ISALNUM ((unsigned char) *n)) || (STREQ (str, "alpha") && ISALPHA ((unsigned char) *n)) || (STREQ (str, "blank") && ISBLANK ((unsigned char) *n)) || (STREQ (str, "cntrl") && ISCNTRL ((unsigned char) *n)) || (STREQ (str, "digit") && ISDIGIT ((unsigned char) *n)) || (STREQ (str, "graph") && ISGRAPH ((unsigned char) *n)) || (STREQ (str, "lower") && ISLOWER ((unsigned char) *n)) || (STREQ (str, "print") && ISPRINT ((unsigned char) *n)) || (STREQ (str, "punct") && ISPUNCT ((unsigned char) *n)) || (STREQ (str, "space") && ISSPACE ((unsigned char) *n)) || (STREQ (str, "upper") && ISUPPER ((unsigned char) *n)) || (STREQ (str, "xdigit") && ISXDIGIT ((unsigned char) *n))) goto matched; # endif } else if (c == '\0') /* [ (unterminated) loses. */ return FNM_NOMATCH; else { normal_bracket: if (FOLD (c) == fn) goto matched; cold = c; c = *p++; if (c == '-' && *p != ']') { /* It is a range. */ unsigned char cend = *p++; if (!(flags & FNM_NOESCAPE) && cend == '\\') cend = *p++; if (cend == '\0') return FNM_NOMATCH; if (cold <= fn && fn <= FOLD (cend)) goto matched; c = *p++; } } if (c == ']') break; } if (!not) return FNM_NOMATCH; break; matched: /* Skip the rest of the [...] that already matched. */ while (c != ']') { if (c == '\0') /* [... (unterminated) loses. */ return FNM_NOMATCH; c = *p++; if (!(flags & FNM_NOESCAPE) && c == '\\') { if (*p == '\0') return FNM_NOMATCH; /* XXX 1003.2d11 is unclear if this is right. */ ++p; } else if (c == '[' && *p == ':') { do if (*++p == '\0') return FNM_NOMATCH; while (*p != ':' || p[1] == ']'); p += 2; c = *p; } } if (not) return FNM_NOMATCH; } break; default: if (c != FOLD ((unsigned char) *n)) return FNM_NOMATCH; } ++n; } if (*n == '\0') return 0; if ((flags & FNM_LEADING_DIR) && *n == '/') /* The FNM_LEADING_DIR flag says that "foo*" matches "foobar/frobozz". */ return 0; return FNM_NOMATCH; # undef FOLD } int fnmatch (pattern, string, flags) const char *pattern; const char *string; int flags; { return internal_fnmatch (pattern, string, flags & FNM_PERIOD, flags); } #endif /* _LIBC or not __GNU_LIBRARY__. */
/* Copyright (C) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999 Free Software Foundation, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ /* AIX requires this to be the first thing in the file. */ #if defined _AIX && !defined __GNUC__ #pragma alloca #endif #ifdef HAVE_CONFIG_H # include <config.h> #endif /* Enable GNU extensions in glob.h. */ #ifndef _GNU_SOURCE # define _GNU_SOURCE 1 #endif #include <errno.h> #include <sys/types.h> #include <sys/stat.h> /* Outcomment the following line for production quality code. */ /* #define NDEBUG 1 */ #include <assert.h> #include <stdio.h> /* Needed on stupid SunOS for assert. */ /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #define GLOB_INTERFACE_VERSION 1 #if !defined _LIBC && defined __GNU_LIBRARY__ && __GNU_LIBRARY__ > 1 # include <gnu-versions.h> # if _GNU_GLOB_INTERFACE_VERSION == GLOB_INTERFACE_VERSION # define ELIDE_CODE # endif #endif #ifndef ELIDE_CODE #if defined STDC_HEADERS || defined __GNU_LIBRARY__ # include <stddef.h> #endif #if defined HAVE_UNISTD_H || defined _LIBC # include <unistd.h> # ifndef POSIX # ifdef _POSIX_VERSION # define POSIX # endif # endif #endif #if !defined _AMIGA && !defined VMS && !defined WINDOWS32 # include <pwd.h> #endif #if !defined __GNU_LIBRARY__ && !defined STDC_HEADERS extern int errno; #endif #ifndef __set_errno # define __set_errno(val) errno = (val) #endif #ifndef NULL # define NULL 0 #endif #if defined HAVE_DIRENT_H || defined __GNU_LIBRARY__ # include <dirent.h> # define NAMLEN(dirent) strlen((dirent)->d_name) #else # define dirent direct # define NAMLEN(dirent) (dirent)->d_namlen # ifdef HAVE_SYS_NDIR_H # include <sys/ndir.h> # endif # ifdef HAVE_SYS_DIR_H # include <sys/dir.h> # endif # ifdef HAVE_NDIR_H # include <ndir.h> # endif # ifdef HAVE_VMSDIR_H # include "vmsdir.h" # endif /* HAVE_VMSDIR_H */ #endif /* In GNU systems, <dirent.h> defines this macro for us. */ #ifdef _D_NAMLEN # undef NAMLEN # define NAMLEN(d) _D_NAMLEN(d) #endif /* When used in the GNU libc the symbol _DIRENT_HAVE_D_TYPE is available if the `d_type' member for `struct dirent' is available. */ #ifdef _DIRENT_HAVE_D_TYPE # define HAVE_D_TYPE 1 #endif #if (defined POSIX || defined WINDOWS32) && !defined __GNU_LIBRARY__ /* Posix does not require that the d_ino field be present, and some systems do not provide it. */ # define REAL_DIR_ENTRY(dp) 1 #else # define REAL_DIR_ENTRY(dp) (dp->d_ino != 0) #endif /* POSIX */ #if defined STDC_HEADERS || defined __GNU_LIBRARY__ # include <stdlib.h> # include <string.h> # define ANSI_STRING #else /* No standard headers. */ extern char *getenv (); # ifdef HAVE_STRING_H # include <string.h> # define ANSI_STRING # else # include <strings.h> # endif # ifdef HAVE_MEMORY_H # include <memory.h> # endif extern char *malloc (), *realloc (); extern void free (); extern void qsort (); extern void abort (), exit (); #endif /* Standard headers. */ #ifndef ANSI_STRING # ifndef bzero extern void bzero (); # endif # ifndef bcopy extern void bcopy (); # endif # define memcpy(d, s, n) bcopy ((s), (d), (n)) # define strrchr rindex /* memset is only used for zero here, but let's be paranoid. */ # define memset(s, better_be_zero, n) \ ((void) ((better_be_zero) == 0 ? (bzero((s), (n)), 0) : (abort(), 0))) #endif /* Not ANSI_STRING. */ #if !defined HAVE_STRCOLL && !defined _LIBC # define strcoll strcmp #endif #if !defined HAVE_MEMPCPY && __GLIBC__ - 0 == 2 && __GLIBC_MINOR__ >= 1 # define HAVE_MEMPCPY 1 # undef mempcpy # define mempcpy(Dest, Src, Len) __mempcpy (Dest, Src, Len) #endif #if !defined __GNU_LIBRARY__ && !defined __DJGPP__ # ifdef __GNUC__ __inline # endif # ifndef __SASC # ifdef WINDOWS32 static void * my_realloc (void *p, unsigned int n) # else static char * my_realloc (p, n) char *p; unsigned int n; # endif { /* These casts are the for sake of the broken Ultrix compiler, which warns of illegal pointer combinations otherwise. */ if (p == NULL) return (char *) malloc (n); return (char *) realloc (p, n); } # define realloc my_realloc # endif /* __SASC */ #endif /* __GNU_LIBRARY__ || __DJGPP__ */ #if !defined __alloca && !defined __GNU_LIBRARY__ # ifdef __GNUC__ # undef alloca # define alloca(n) __builtin_alloca (n) # else /* Not GCC. */ # ifdef HAVE_ALLOCA_H # include <alloca.h> # else /* Not HAVE_ALLOCA_H. */ # ifndef _AIX # ifdef WINDOWS32 # include <malloc.h> # else extern char *alloca (); # endif /* WINDOWS32 */ # endif /* Not _AIX. */ # endif /* sparc or HAVE_ALLOCA_H. */ # endif /* GCC. */ # define __alloca alloca #endif #ifndef __GNU_LIBRARY__ # define __stat stat # ifdef STAT_MACROS_BROKEN # undef S_ISDIR # endif # ifndef S_ISDIR # define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) # endif #endif #ifdef _LIBC # undef strdup # define strdup(str) __strdup (str) # define sysconf(id) __sysconf (id) # define closedir(dir) __closedir (dir) # define opendir(name) __opendir (name) # define readdir(str) __readdir (str) # define getpwnam_r(name, bufp, buf, len, res) \ __getpwnam_r (name, bufp, buf, len, res) # ifndef __stat # define __stat(fname, buf) __xstat (_STAT_VER, fname, buf) # endif #endif #if !(defined STDC_HEADERS || defined __GNU_LIBRARY__) # undef size_t # define size_t unsigned int #endif /* Some system header files erroneously define these. We want our own definitions from <fnmatch.h> to take precedence. */ #ifndef __GNU_LIBRARY__ # undef FNM_PATHNAME # undef FNM_NOESCAPE # undef FNM_PERIOD #endif #include <fnmatch.h> /* Some system header files erroneously define these. We want our own definitions from <glob.h> to take precedence. */ #ifndef __GNU_LIBRARY__ # undef GLOB_ERR # undef GLOB_MARK # undef GLOB_NOSORT # undef GLOB_DOOFFS # undef GLOB_NOCHECK # undef GLOB_APPEND # undef GLOB_NOESCAPE # undef GLOB_PERIOD #endif #include <glob.h> #ifdef HAVE_GETLOGIN_R extern int getlogin_r __P ((char *, size_t)); #else extern char *getlogin __P ((void)); #endif static #if __GNUC__ - 0 >= 2 inline #endif const char *next_brace_sub __P ((const char *begin)); static int glob_in_dir __P ((const char *pattern, const char *directory, int flags, int (*errfunc) (const char *, int), glob_t *pglob)); static int prefix_array __P ((const char *prefix, char **array, size_t n)); static int collated_compare __P ((const __ptr_t, const __ptr_t)); #if !defined _LIBC || !defined NO_GLOB_PATTERN_P int __glob_pattern_p __P ((const char *pattern, int quote)); #endif /* Find the end of the sub-pattern in a brace expression. We define this as an inline function if the compiler permits. */ static #if __GNUC__ - 0 >= 2 inline #endif const char * next_brace_sub (begin) const char *begin; { unsigned int depth = 0; const char *cp = begin; while (1) { if (depth == 0) { if (*cp != ',' && *cp != '}' && *cp != '\0') { if (*cp == '{') ++depth; ++cp; continue; } } else { while (*cp != '\0' && (*cp != '}' || depth > 0)) { if (*cp == '}') --depth; ++cp; } if (*cp == '\0') /* An incorrectly terminated brace expression. */ return NULL; continue; } break; } return cp; } /* Do glob searching for PATTERN, placing results in PGLOB. The bits defined above may be set in FLAGS. If a directory cannot be opened or read and ERRFUNC is not nil, it is called with the pathname that caused the error, and the `errno' value from the failing call; if it returns non-zero `glob' returns GLOB_ABORTED; if it returns zero, the error is ignored. If memory cannot be allocated for PGLOB, GLOB_NOSPACE is returned. Otherwise, `glob' returns zero. */ int glob (pattern, flags, errfunc, pglob) const char *pattern; int flags; int (*errfunc) __P ((const char *, int)); glob_t *pglob; { const char *filename; const char *dirname; size_t dirlen; int status; int oldcount; if (pattern == NULL || pglob == NULL || (flags & ~__GLOB_FLAGS) != 0) { __set_errno (EINVAL); return -1; } if (flags & GLOB_BRACE) { const char *begin = strchr (pattern, '{'); if (begin != NULL) { /* Allocate working buffer large enough for our work. Note that we have at least an opening and closing brace. */ int firstc; char *alt_start; const char *p; const char *next; const char *rest; size_t rest_len; #ifdef __GNUC__ char onealt[strlen (pattern) - 1]; #else char *onealt = (char *) malloc (strlen (pattern) - 1); if (onealt == NULL) { if (!(flags & GLOB_APPEND)) globfree (pglob); return GLOB_NOSPACE; } #endif /* We know the prefix for all sub-patterns. */ #ifdef HAVE_MEMPCPY alt_start = mempcpy (onealt, pattern, begin - pattern); #else memcpy (onealt, pattern, begin - pattern); alt_start = &onealt[begin - pattern]; #endif /* Find the first sub-pattern and at the same time find the rest after the closing brace. */ next = next_brace_sub (begin + 1); if (next == NULL) { /* It is an illegal expression. */ #ifndef __GNUC__ free (onealt); #endif return glob (pattern, flags & ~GLOB_BRACE, errfunc, pglob); } /* Now find the end of the whole brace expression. */ rest = next; while (*rest != '}') { rest = next_brace_sub (rest + 1); if (rest == NULL) { /* It is an illegal expression. */ #ifndef __GNUC__ free (onealt); #endif return glob (pattern, flags & ~GLOB_BRACE, errfunc, pglob); } } /* Please note that we now can be sure the brace expression is well-formed. */ rest_len = strlen (++rest) + 1; /* We have a brace expression. BEGIN points to the opening {, NEXT points past the terminator of the first element, and END points past the final }. We will accumulate result names from recursive runs for each brace alternative in the buffer using GLOB_APPEND. */ if (!(flags & GLOB_APPEND)) { /* This call is to set a new vector, so clear out the vector so we can append to it. */ pglob->gl_pathc = 0; pglob->gl_pathv = NULL; } firstc = pglob->gl_pathc; p = begin + 1; while (1) { int result; /* Construct the new glob expression. */ #ifdef HAVE_MEMPCPY mempcpy (mempcpy (alt_start, p, next - p), rest, rest_len); #else memcpy (alt_start, p, next - p); memcpy (&alt_start[next - p], rest, rest_len); #endif result = glob (onealt, ((flags & ~(GLOB_NOCHECK|GLOB_NOMAGIC)) | GLOB_APPEND), errfunc, pglob); /* If we got an error, return it. */ if (result && result != GLOB_NOMATCH) { #ifndef __GNUC__ free (onealt); #endif if (!(flags & GLOB_APPEND)) globfree (pglob); return result; } if (*next == '}') /* We saw the last entry. */ break; p = next + 1; next = next_brace_sub (p); assert (next != NULL); } #ifndef __GNUC__ free (onealt); #endif if (pglob->gl_pathc != firstc) /* We found some entries. */ return 0; else if (!(flags & (GLOB_NOCHECK|GLOB_NOMAGIC))) return GLOB_NOMATCH; } } /* Find the filename. */ filename = strrchr (pattern, '/'); #if defined __MSDOS__ || defined WINDOWS32 /* The case of "d:pattern". Since `:' is not allowed in file names, we can safely assume that wherever it happens in pattern, it signals the filename part. This is so we could some day support patterns like "[a-z]:foo". */ if (filename == NULL) filename = strchr (pattern, ':'); #endif /* __MSDOS__ || WINDOWS32 */ if (filename == NULL) { /* This can mean two things: a simple name or "~name". The later case is nothing but a notation for a directory. */ if ((flags & (GLOB_TILDE|GLOB_TILDE_CHECK)) && pattern[0] == '~') { dirname = pattern; dirlen = strlen (pattern); /* Set FILENAME to NULL as a special flag. This is ugly but other solutions would require much more code. We test for this special case below. */ filename = NULL; } else { filename = pattern; #ifdef _AMIGA dirname = ""; #else dirname = "."; #endif dirlen = 0; } } else if (filename == pattern) { /* "/pattern". */ dirname = "/"; dirlen = 1; ++filename; } else { char *newp; dirlen = filename - pattern; #if defined __MSDOS__ || defined WINDOWS32 if (*filename == ':' || (filename > pattern + 1 && filename[-1] == ':')) { char *drive_spec; ++dirlen; drive_spec = (char *) __alloca (dirlen + 1); #ifdef HAVE_MEMPCPY *((char *) mempcpy (drive_spec, pattern, dirlen)) = '\0'; #else memcpy (drive_spec, pattern, dirlen); drive_spec[dirlen] = '\0'; #endif /* For now, disallow wildcards in the drive spec, to prevent infinite recursion in glob. */ if (__glob_pattern_p (drive_spec, !(flags & GLOB_NOESCAPE))) return GLOB_NOMATCH; /* If this is "d:pattern", we need to copy `:' to DIRNAME as well. If it's "d:/pattern", don't remove the slash from "d:/", since "d:" and "d:/" are not the same.*/ } #endif newp = (char *) __alloca (dirlen + 1); #ifdef HAVE_MEMPCPY *((char *) mempcpy (newp, pattern, dirlen)) = '\0'; #else memcpy (newp, pattern, dirlen); newp[dirlen] = '\0'; #endif dirname = newp; ++filename; if (filename[0] == '\0' #if defined __MSDOS__ || defined WINDOWS32 && dirname[dirlen - 1] != ':' && (dirlen < 3 || dirname[dirlen - 2] != ':' || dirname[dirlen - 1] != '/') #endif && dirlen > 1) /* "pattern/". Expand "pattern", appending slashes. */ { int val = glob (dirname, flags | GLOB_MARK, errfunc, pglob); if (val == 0) pglob->gl_flags = ((pglob->gl_flags & ~GLOB_MARK) | (flags & GLOB_MARK)); return val; } } if (!(flags & GLOB_APPEND)) { pglob->gl_pathc = 0; pglob->gl_pathv = NULL; } oldcount = pglob->gl_pathc; #ifndef VMS if ((flags & (GLOB_TILDE|GLOB_TILDE_CHECK)) && dirname[0] == '~') { if (dirname[1] == '\0' || dirname[1] == '/') { /* Look up home directory. */ #ifdef VMS /* This isn't obvious, RTLs of DECC and VAXC know about "HOME" */ const char *home_dir = getenv ("SYS$LOGIN"); #else const char *home_dir = getenv ("HOME"); #endif # ifdef _AMIGA if (home_dir == NULL || home_dir[0] == '\0') home_dir = "SYS:"; # else # ifdef WINDOWS32 if (home_dir == NULL || home_dir[0] == '\0') home_dir = "c:/users/default"; /* poor default */ # else # ifdef VMS /* Again, this isn't obvious, if "HOME" isn't known "SYS$LOGIN" should be set */ if (home_dir == NULL || home_dir[0] == '\0') home_dir = "SYS$DISK:[]"; # else if (home_dir == NULL || home_dir[0] == '\0') { int success; char *name; # if defined HAVE_GETLOGIN_R || defined _LIBC size_t buflen = sysconf (_SC_LOGIN_NAME_MAX) + 1; if (buflen == 0) /* `sysconf' does not support _SC_LOGIN_NAME_MAX. Try a moderate value. */ buflen = 20; name = (char *) __alloca (buflen); success = getlogin_r (name, buflen) >= 0; # else success = (name = getlogin ()) != NULL; # endif if (success) { struct passwd *p; # if defined HAVE_GETPWNAM_R || defined _LIBC size_t pwbuflen = sysconf (_SC_GETPW_R_SIZE_MAX); char *pwtmpbuf; struct passwd pwbuf; int save = errno; if (pwbuflen == -1) /* `sysconf' does not support _SC_GETPW_R_SIZE_MAX. Try a moderate value. */ pwbuflen = 1024; pwtmpbuf = (char *) __alloca (pwbuflen); while (getpwnam_r (name, &pwbuf, pwtmpbuf, pwbuflen, &p) != 0) { if (errno != ERANGE) { p = NULL; break; } pwbuflen *= 2; pwtmpbuf = (char *) __alloca (pwbuflen); __set_errno (save); } # else p = getpwnam (name); # endif if (p != NULL) home_dir = p->pw_dir; } } if (home_dir == NULL || home_dir[0] == '\0') { if (flags & GLOB_TILDE_CHECK) return GLOB_NOMATCH; else home_dir = "~"; /* No luck. */ } # endif /* VMS */ # endif /* WINDOWS32 */ # endif /* Now construct the full directory. */ if (dirname[1] == '\0') dirname = home_dir; else { char *newp; size_t home_len = strlen (home_dir); newp = (char *) __alloca (home_len + dirlen); # ifdef HAVE_MEMPCPY mempcpy (mempcpy (newp, home_dir, home_len), &dirname[1], dirlen); # else memcpy (newp, home_dir, home_len); memcpy (&newp[home_len], &dirname[1], dirlen); # endif dirname = newp; } } # if !defined _AMIGA && !defined WINDOWS32 && !defined VMS else { char *end_name = strchr (dirname, '/'); const char *user_name; const char *home_dir; if (end_name == NULL) user_name = dirname + 1; else { char *newp; newp = (char *) __alloca (end_name - dirname); # ifdef HAVE_MEMPCPY *((char *) mempcpy (newp, dirname + 1, end_name - dirname)) = '\0'; # else memcpy (newp, dirname + 1, end_name - dirname); newp[end_name - dirname - 1] = '\0'; # endif user_name = newp; } /* Look up specific user's home directory. */ { struct passwd *p; # if defined HAVE_GETPWNAM_R || defined _LIBC size_t buflen = sysconf (_SC_GETPW_R_SIZE_MAX); char *pwtmpbuf; struct passwd pwbuf; int save = errno; if (buflen == -1) /* `sysconf' does not support _SC_GETPW_R_SIZE_MAX. Try a moderate value. */ buflen = 1024; pwtmpbuf = (char *) __alloca (buflen); while (getpwnam_r (user_name, &pwbuf, pwtmpbuf, buflen, &p) != 0) { if (errno != ERANGE) { p = NULL; break; } buflen *= 2; pwtmpbuf = __alloca (buflen); __set_errno (save); } # else p = getpwnam (user_name); # endif if (p != NULL) home_dir = p->pw_dir; else home_dir = NULL; } /* If we found a home directory use this. */ if (home_dir != NULL) { char *newp; size_t home_len = strlen (home_dir); size_t rest_len = end_name == NULL ? 0 : strlen (end_name); newp = (char *) __alloca (home_len + rest_len + 1); # ifdef HAVE_MEMPCPY *((char *) mempcpy (mempcpy (newp, home_dir, home_len), end_name, rest_len)) = '\0'; # else memcpy (newp, home_dir, home_len); memcpy (&newp[home_len], end_name, rest_len); newp[home_len + rest_len] = '\0'; # endif dirname = newp; } else if (flags & GLOB_TILDE_CHECK) /* We have to regard it as an error if we cannot find the home directory. */ return GLOB_NOMATCH; } # endif /* Not Amiga && not WINDOWS32 && not VMS. */ } #endif /* Not VMS. */ /* Now test whether we looked for "~" or "~NAME". In this case we can give the answer now. */ if (filename == NULL) { struct stat st; /* Return the directory if we don't check for error or if it exists. */ if ((flags & GLOB_NOCHECK) || (((flags & GLOB_ALTDIRFUNC) ? (*pglob->gl_stat) (dirname, &st) : __stat (dirname, &st)) == 0 && S_ISDIR (st.st_mode))) { pglob->gl_pathv = (char **) realloc (pglob->gl_pathv, (pglob->gl_pathc + ((flags & GLOB_DOOFFS) ? pglob->gl_offs : 0) + 1 + 1) * sizeof (char *)); if (pglob->gl_pathv == NULL) return GLOB_NOSPACE; if (flags & GLOB_DOOFFS) while (pglob->gl_pathc < pglob->gl_offs) pglob->gl_pathv[pglob->gl_pathc++] = NULL; #if defined HAVE_STRDUP || defined _LIBC pglob->gl_pathv[pglob->gl_pathc] = strdup (dirname); #else { size_t len = strlen (dirname) + 1; char *dircopy = malloc (len); if (dircopy != NULL) pglob->gl_pathv[pglob->gl_pathc] = memcpy (dircopy, dirname, len); } #endif if (pglob->gl_pathv[pglob->gl_pathc] == NULL) { free (pglob->gl_pathv); return GLOB_NOSPACE; } pglob->gl_pathv[++pglob->gl_pathc] = NULL; pglob->gl_flags = flags; return 0; } /* Not found. */ return GLOB_NOMATCH; } if (__glob_pattern_p (dirname, !(flags & GLOB_NOESCAPE))) { /* The directory name contains metacharacters, so we have to glob for the directory, and then glob for the pattern in each directory found. */ glob_t dirs; register int i; status = glob (dirname, ((flags & (GLOB_ERR | GLOB_NOCHECK | GLOB_NOESCAPE)) | GLOB_NOSORT | GLOB_ONLYDIR), errfunc, &dirs); if (status != 0) return status; /* We have successfully globbed the preceding directory name. For each name we found, call glob_in_dir on it and FILENAME, appending the results to PGLOB. */ for (i = 0; i < dirs.gl_pathc; ++i) { int old_pathc; #ifdef SHELL { /* Make globbing interruptible in the bash shell. */ extern int interrupt_state; if (interrupt_state) { globfree (&dirs); globfree (&files); return GLOB_ABORTED; } } #endif /* SHELL. */ old_pathc = pglob->gl_pathc; status = glob_in_dir (filename, dirs.gl_pathv[i], ((flags | GLOB_APPEND) & ~(GLOB_NOCHECK | GLOB_ERR)), errfunc, pglob); if (status == GLOB_NOMATCH) /* No matches in this directory. Try the next. */ continue; if (status != 0) { globfree (&dirs); globfree (pglob); return status; } /* Stick the directory on the front of each name. */ if (prefix_array (dirs.gl_pathv[i], &pglob->gl_pathv[old_pathc], pglob->gl_pathc - old_pathc)) { globfree (&dirs); globfree (pglob); return GLOB_NOSPACE; } } flags |= GLOB_MAGCHAR; /* We have ignored the GLOB_NOCHECK flag in the `glob_in_dir' calls. But if we have not found any matching entry and thie GLOB_NOCHECK flag was set we must return the list consisting of the disrectory names followed by the filename. */ if (pglob->gl_pathc == oldcount) { /* No matches. */ if (flags & GLOB_NOCHECK) { size_t filename_len = strlen (filename) + 1; char **new_pathv; struct stat st; /* This is an pessimistic guess about the size. */ pglob->gl_pathv = (char **) realloc (pglob->gl_pathv, (pglob->gl_pathc + ((flags & GLOB_DOOFFS) ? pglob->gl_offs : 0) + dirs.gl_pathc + 1) * sizeof (char *)); if (pglob->gl_pathv == NULL) { globfree (&dirs); return GLOB_NOSPACE; } if (flags & GLOB_DOOFFS) while (pglob->gl_pathc < pglob->gl_offs) pglob->gl_pathv[pglob->gl_pathc++] = NULL; for (i = 0; i < dirs.gl_pathc; ++i) { const char *dir = dirs.gl_pathv[i]; size_t dir_len = strlen (dir); /* First check whether this really is a directory. */ if (((flags & GLOB_ALTDIRFUNC) ? (*pglob->gl_stat) (dir, &st) : __stat (dir, &st)) != 0 || !S_ISDIR (st.st_mode)) /* No directory, ignore this entry. */ continue; pglob->gl_pathv[pglob->gl_pathc] = malloc (dir_len + 1 + filename_len); if (pglob->gl_pathv[pglob->gl_pathc] == NULL) { globfree (&dirs); globfree (pglob); return GLOB_NOSPACE; } #ifdef HAVE_MEMPCPY mempcpy (mempcpy (mempcpy (pglob->gl_pathv[pglob->gl_pathc], dir, dir_len), "/", 1), filename, filename_len); #else memcpy (pglob->gl_pathv[pglob->gl_pathc], dir, dir_len); pglob->gl_pathv[pglob->gl_pathc][dir_len] = '/'; memcpy (&pglob->gl_pathv[pglob->gl_pathc][dir_len + 1], filename, filename_len); #endif ++pglob->gl_pathc; } pglob->gl_pathv[pglob->gl_pathc] = NULL; pglob->gl_flags = flags; /* Now we know how large the gl_pathv vector must be. */ new_pathv = (char **) realloc (pglob->gl_pathv, ((pglob->gl_pathc + 1) * sizeof (char *))); if (new_pathv != NULL) pglob->gl_pathv = new_pathv; } else return GLOB_NOMATCH; } globfree (&dirs); } else { status = glob_in_dir (filename, dirname, flags, errfunc, pglob); if (status != 0) return status; if (dirlen > 0) { /* Stick the directory on the front of each name. */ int ignore = oldcount; if ((flags & GLOB_DOOFFS) && ignore < pglob->gl_offs) ignore = pglob->gl_offs; if (prefix_array (dirname, &pglob->gl_pathv[ignore], pglob->gl_pathc - ignore)) { globfree (pglob); return GLOB_NOSPACE; } } } if (flags & GLOB_MARK) { /* Append slashes to directory names. */ int i; struct stat st; for (i = oldcount; i < pglob->gl_pathc; ++i) if (((flags & GLOB_ALTDIRFUNC) ? (*pglob->gl_stat) (pglob->gl_pathv[i], &st) : __stat (pglob->gl_pathv[i], &st)) == 0 && S_ISDIR (st.st_mode)) { size_t len = strlen (pglob->gl_pathv[i]) + 2; char *new = realloc (pglob->gl_pathv[i], len); if (new == NULL) { globfree (pglob); return GLOB_NOSPACE; } strcpy (&new[len - 2], "/"); pglob->gl_pathv[i] = new; } } if (!(flags & GLOB_NOSORT)) { /* Sort the vector. */ int non_sort = oldcount; if ((flags & GLOB_DOOFFS) && pglob->gl_offs > oldcount) non_sort = pglob->gl_offs; qsort ((__ptr_t) &pglob->gl_pathv[non_sort], pglob->gl_pathc - non_sort, sizeof (char *), collated_compare); } return 0; } /* Free storage allocated in PGLOB by a previous `glob' call. */ void globfree (pglob) register glob_t *pglob; { if (pglob->gl_pathv != NULL) { register int i; for (i = 0; i < pglob->gl_pathc; ++i) if (pglob->gl_pathv[i] != NULL) free ((__ptr_t) pglob->gl_pathv[i]); free ((__ptr_t) pglob->gl_pathv); } } /* Do a collated comparison of A and B. */ static int collated_compare (a, b) const __ptr_t a; const __ptr_t b; { const char *const s1 = *(const char *const * const) a; const char *const s2 = *(const char *const * const) b; if (s1 == s2) return 0; if (s1 == NULL) return 1; if (s2 == NULL) return -1; return strcoll (s1, s2); } /* Prepend DIRNAME to each of N members of ARRAY, replacing ARRAY's elements in place. Return nonzero if out of memory, zero if successful. A slash is inserted between DIRNAME and each elt of ARRAY, unless DIRNAME is just "/". Each old element of ARRAY is freed. */ static int prefix_array (dirname, array, n) const char *dirname; char **array; size_t n; { register size_t i; size_t dirlen = strlen (dirname); #if defined __MSDOS__ || defined WINDOWS32 int sep_char = '/'; # define DIRSEP_CHAR sep_char #else # define DIRSEP_CHAR '/' #endif if (dirlen == 1 && dirname[0] == '/') /* DIRNAME is just "/", so normal prepending would get us "//foo". We want "/foo" instead, so don't prepend any chars from DIRNAME. */ dirlen = 0; #if defined __MSDOS__ || defined WINDOWS32 else if (dirlen > 1) { if (dirname[dirlen - 1] == '/' && dirname[dirlen - 2] == ':') /* DIRNAME is "d:/". Don't prepend the slash from DIRNAME. */ --dirlen; else if (dirname[dirlen - 1] == ':') { /* DIRNAME is "d:". Use `:' instead of `/'. */ --dirlen; sep_char = ':'; } } #endif for (i = 0; i < n; ++i) { size_t eltlen = strlen (array[i]) + 1; char *new = (char *) malloc (dirlen + 1 + eltlen); if (new == NULL) { while (i > 0) free ((__ptr_t) array[--i]); return 1; } #ifdef HAVE_MEMPCPY { char *endp = (char *) mempcpy (new, dirname, dirlen); *endp++ = DIRSEP_CHAR; mempcpy (endp, array[i], eltlen); } #else memcpy (new, dirname, dirlen); new[dirlen] = DIRSEP_CHAR; memcpy (&new[dirlen + 1], array[i], eltlen); #endif free ((__ptr_t) array[i]); array[i] = new; } return 0; } /* We must not compile this function twice. */ #if !defined _LIBC || !defined NO_GLOB_PATTERN_P /* Return nonzero if PATTERN contains any metacharacters. Metacharacters can be quoted with backslashes if QUOTE is nonzero. */ int __glob_pattern_p (pattern, quote) const char *pattern; int quote; { register const char *p; int open = 0; for (p = pattern; *p != '\0'; ++p) switch (*p) { case '?': case '*': return 1; case '\\': if (quote && p[1] != '\0') ++p; break; case '[': open = 1; break; case ']': if (open) return 1; break; } return 0; } # ifdef _LIBC weak_alias (__glob_pattern_p, glob_pattern_p) # endif #endif /* Like `glob', but PATTERN is a final pathname component, and matches are searched for in DIRECTORY. The GLOB_NOSORT bit in FLAGS is ignored. No sorting is ever done. The GLOB_APPEND flag is assumed to be set (always appends). */ static int glob_in_dir (pattern, directory, flags, errfunc, pglob) const char *pattern; const char *directory; int flags; int (*errfunc) __P ((const char *, int)); glob_t *pglob; { __ptr_t stream = NULL; struct globlink { struct globlink *next; char *name; }; struct globlink *names = NULL; size_t nfound; int meta; int save; #ifdef VMS if (*directory == 0) directory = "[]"; #endif meta = __glob_pattern_p (pattern, !(flags & GLOB_NOESCAPE)); if (meta == 0) { if (flags & (GLOB_NOCHECK|GLOB_NOMAGIC)) /* We need not do any tests. The PATTERN contains no meta characters and we must not return an error therefore the result will always contain exactly one name. */ flags |= GLOB_NOCHECK; else { /* Since we use the normal file functions we can also use stat() to verify the file is there. */ struct stat st; size_t patlen = strlen (pattern); size_t dirlen = strlen (directory); char *fullname = (char *) __alloca (dirlen + 1 + patlen + 1); # ifdef HAVE_MEMPCPY mempcpy (mempcpy (mempcpy (fullname, directory, dirlen), "/", 1), pattern, patlen + 1); # else memcpy (fullname, directory, dirlen); fullname[dirlen] = '/'; memcpy (&fullname[dirlen + 1], pattern, patlen + 1); # endif if (((flags & GLOB_ALTDIRFUNC) ? (*pglob->gl_stat) (fullname, &st) : __stat (fullname, &st)) == 0) /* We found this file to be existing. Now tell the rest of the function to copy this name into the result. */ flags |= GLOB_NOCHECK; } nfound = 0; } else { if (pattern[0] == '\0') { /* This is a special case for matching directories like in "*a/". */ names = (struct globlink *) __alloca (sizeof (struct globlink)); names->name = (char *) malloc (1); if (names->name == NULL) goto memory_error; names->name[0] = '\0'; names->next = NULL; nfound = 1; meta = 0; } else { stream = ((flags & GLOB_ALTDIRFUNC) ? (*pglob->gl_opendir) (directory) : (__ptr_t) opendir (directory)); if (stream == NULL) { if (errno != ENOTDIR && ((errfunc != NULL && (*errfunc) (directory, errno)) || (flags & GLOB_ERR))) return GLOB_ABORTED; nfound = 0; meta = 0; } else { int fnm_flags = ((!(flags & GLOB_PERIOD) ? FNM_PERIOD : 0) | ((flags & GLOB_NOESCAPE) ? FNM_NOESCAPE : 0) #if defined HAVE_CASE_INSENSITIVE_FS | FNM_CASEFOLD #endif ); nfound = 0; flags |= GLOB_MAGCHAR; while (1) { const char *name; size_t len; struct dirent *d = ((flags & GLOB_ALTDIRFUNC) ? (*pglob->gl_readdir) (stream) : readdir ((DIR *) stream)); if (d == NULL) break; if (! REAL_DIR_ENTRY (d)) continue; #ifdef HAVE_D_TYPE /* If we shall match only directories use the information provided by the dirent call if possible. */ if ((flags & GLOB_ONLYDIR) && d->d_type != DT_UNKNOWN && d->d_type != DT_DIR) continue; #endif name = d->d_name; if (fnmatch (pattern, name, fnm_flags) == 0) { struct globlink *new = (struct globlink *) __alloca (sizeof (struct globlink)); len = NAMLEN (d); new->name = (char *) malloc (len + 1); if (new->name == NULL) goto memory_error; #ifdef HAVE_MEMPCPY *((char *) mempcpy ((__ptr_t) new->name, name, len)) = '\0'; #else memcpy ((__ptr_t) new->name, name, len); new->name[len] = '\0'; #endif new->next = names; names = new; ++nfound; } } } } } if (nfound == 0 && (flags & GLOB_NOCHECK)) { size_t len = strlen (pattern); nfound = 1; names = (struct globlink *) __alloca (sizeof (struct globlink)); names->next = NULL; names->name = (char *) malloc (len + 1); if (names->name == NULL) goto memory_error; #ifdef HAVE_MEMPCPY *((char *) mempcpy (names->name, pattern, len)) = '\0'; #else memcpy (names->name, pattern, len); names->name[len] = '\0'; #endif } if (nfound != 0) { pglob->gl_pathv = (char **) realloc (pglob->gl_pathv, (pglob->gl_pathc + ((flags & GLOB_DOOFFS) ? pglob->gl_offs : 0) + nfound + 1) * sizeof (char *)); if (pglob->gl_pathv == NULL) goto memory_error; if (flags & GLOB_DOOFFS) while (pglob->gl_pathc < pglob->gl_offs) pglob->gl_pathv[pglob->gl_pathc++] = NULL; for (; names != NULL; names = names->next) pglob->gl_pathv[pglob->gl_pathc++] = names->name; pglob->gl_pathv[pglob->gl_pathc] = NULL; pglob->gl_flags = flags; } save = errno; if (stream != NULL) { if (flags & GLOB_ALTDIRFUNC) (*pglob->gl_closedir) (stream); else closedir ((DIR *) stream); } __set_errno (save); return nfound == 0 ? GLOB_NOMATCH : 0; memory_error: { int save = errno; if (flags & GLOB_ALTDIRFUNC) (*pglob->gl_closedir) (stream); else closedir ((DIR *) stream); __set_errno (save); } while (names != NULL) { if (names->name != NULL) free ((__ptr_t) names->name); names = names->next; } return GLOB_NOSPACE; } #endif /* Not ELIDE_CODE. */
aa57178c15d44020d30cc893a5cf75278a38a801361f0373cf17600cf534c73c /usr/bin/make
https://mirrors.kernel.org/gnu/patch/patch-2.5.9.tar.gz ecb5c6469d732bcf01d6ec1afe9e64f1668caba5bfdb103c28d7f537ba3cdb8a
# SPDX-FileCopyrightText: 2022 Andrius Å tikonas <andrius@stikonas.eu> # # SPDX-License-Identifier: GPL-3.0-or-later CC = tcc CFLAGS = -I . CPPFLAGS = -DHAVE_DECL_GETENV -DHAVE_DECL_MALLOC -DHAVE_DIRENT_H -DHAVE_LIMITS_H -DHAVE_GETEUID -DHAVE_MKTEMP -DPACKAGE_BUGREPORT= -Ded_PROGRAM=\"/nullop\" -Dmbstate_t=void -DRETSIGTYPE=int -DHAVE_MKDIR -DHAVE_RMDIR -DHAVE_FCNTL_H -DPACKAGE_NAME=\"patch\" -DPACKAGE_VERSION=\"2.5.9\" -DHAVE_MALLOC -DHAVE_REALLOC -DSTDC_HEADERS -DHAVE_STRING_H -DHAVE_STDLIB_H LDFLAGS = -static .PHONY: all all: patch patch: error.o getopt.o getopt1.o addext.o argmatch.o backupfile.o basename.o dirname.o inp.o maketime.o partime.o patch.o pch.o quote.o quotearg.o quotesys.o util.o version.o xmalloc.o $(CC) $^ $(LDFLAGS) -o $@
/* Error handler for noninteractive utilities Copyright (C) 1990-1998, 2000, 2001, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@gnu.ai.mit.edu>. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #ifdef _LIBC # include <libintl.h> #else # include "gettext.h" #endif #ifdef _LIBC # include <wchar.h> # define mbsrtowcs __mbsrtowcs #endif #if HAVE_VPRINTF || HAVE_DOPRNT || _LIBC # if __STDC__ # include <stdarg.h> # define VA_START(args, lastarg) va_start(args, lastarg) # else # include <varargs.h> # define VA_START(args, lastarg) va_start(args) # endif #else # define va_alist a1, a2, a3, a4, a5, a6, a7, a8 # define va_dcl char *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8; #endif #if STDC_HEADERS || _LIBC # include <stdlib.h> # include <string.h> #else void exit (); #endif #include "error.h" #if !_LIBC # include "unlocked-io.h" #endif #ifndef _ # define _(String) String #endif /* If NULL, error will flush stdout, then print on stderr the program name, a colon and a space. Otherwise, error will call this function without parameters instead. */ void (*error_print_progname) ( #if __STDC__ - 0 void #endif ); /* This variable is incremented each time `error' is called. */ unsigned int error_message_count; #ifdef _LIBC /* In the GNU C library, there is a predefined variable for this. */ # define program_name program_invocation_name # include <errno.h> # include <libio/libioP.h> /* In GNU libc we want do not want to use the common name `error' directly. Instead make it a weak alias. */ extern void __error (int status, int errnum, const char *message, ...) __attribute__ ((__format__ (__printf__, 3, 4))); extern void __error_at_line (int status, int errnum, const char *file_name, unsigned int line_number, const char *message, ...) __attribute__ ((__format__ (__printf__, 5, 6)));; # define error __error # define error_at_line __error_at_line # ifdef USE_IN_LIBIO # include <libio/iolibio.h> # define fflush(s) INTUSE(_IO_fflush) (s) # undef putc # define putc(c, fp) INTUSE(_IO_putc) (c, fp) # endif #else /* not _LIBC */ # if !HAVE_DECL_STRERROR_R && STRERROR_R_CHAR_P # ifndef HAVE_DECL_STRERROR_R "this configure-time declaration test was not run" # endif char *strerror_r (); # endif /* The calling program should define program_name and set it to the name of the executing program. */ extern char *program_name; # if HAVE_STRERROR_R || defined strerror_r # define __strerror_r strerror_r # else # if HAVE_STRERROR # ifndef HAVE_DECL_STRERROR "this configure-time declaration test was not run" # endif # if !HAVE_DECL_STRERROR char *strerror (); # endif # else static char * private_strerror (int errnum) { extern char *sys_errlist[]; extern int sys_nerr; if (errnum > 0 && errnum <= sys_nerr) return _(sys_errlist[errnum]); return _("Unknown system error"); } # define strerror private_strerror # endif /* HAVE_STRERROR */ # endif /* HAVE_STRERROR_R || defined strerror_r */ #endif /* not _LIBC */ static void print_errno_message (int errnum) { char const *s; #if defined HAVE_STRERROR_R || _LIBC char errbuf[1024]; # if STRERROR_R_CHAR_P || _LIBC s = __strerror_r (errnum, errbuf, sizeof errbuf); # else if (__strerror_r (errnum, errbuf, sizeof errbuf) == 0) s = errbuf; else s = 0; # endif #else s = strerror (errnum); #endif #if !_LIBC if (! s) s = _("Unknown system error"); #endif #if _LIBC && USE_IN_LIBIO if (_IO_fwide (stderr, 0) > 0) { __fwprintf (stderr, L": %s", s); return; } #endif fprintf (stderr, ": %s", s); } #ifdef VA_START static void error_tail (int status, int errnum, const char *message, va_list args) { # if HAVE_VPRINTF || _LIBC # if _LIBC && USE_IN_LIBIO if (_IO_fwide (stderr, 0) > 0) { # define ALLOCA_LIMIT 2000 size_t len = strlen (message) + 1; wchar_t *wmessage = NULL; mbstate_t st; size_t res; const char *tmp; do { if (len < ALLOCA_LIMIT) wmessage = (wchar_t *) alloca (len * sizeof (wchar_t)); else { if (wmessage != NULL && len / 2 < ALLOCA_LIMIT) wmessage = NULL; wmessage = (wchar_t *) realloc (wmessage, len * sizeof (wchar_t)); if (wmessage == NULL) { fputws_unlocked (L"out of memory\n", stderr); return; } } memset (&st, '\0', sizeof (st)); tmp =message; } while ((res = mbsrtowcs (wmessage, &tmp, len, &st)) == len); if (res == (size_t) -1) /* The string cannot be converted. */ wmessage = (wchar_t *) L"???"; __vfwprintf (stderr, wmessage, args); } else # endif vfprintf (stderr, message, args); # else _doprnt (message, args, stderr); # endif va_end (args); ++error_message_count; if (errnum) print_errno_message (errnum); # if _LIBC && USE_IN_LIBIO if (_IO_fwide (stderr, 0) > 0) putwc (L'\n', stderr); else # endif putc ('\n', stderr); fflush (stderr); if (status) exit (status); } #endif /* Print the program name and error message MESSAGE, which is a printf-style format string with optional args. If ERRNUM is nonzero, print its corresponding system error message. Exit with status STATUS if it is nonzero. */ /* VARARGS */ void #if defined VA_START && __STDC__ error (int status, int errnum, const char *message, ...) #else error (status, errnum, message, va_alist) int status; int errnum; char *message; va_dcl #endif { #ifdef VA_START va_list args; #endif fflush (stdout); #ifdef _LIBC # ifdef USE_IN_LIBIO _IO_flockfile (stderr); # else __flockfile (stderr); # endif #endif if (error_print_progname) (*error_print_progname) (); else { #if _LIBC && USE_IN_LIBIO if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s: ", program_name); else #endif fprintf (stderr, "%s: ", program_name); } #ifdef VA_START VA_START (args, message); error_tail (status, errnum, message, args); #else fprintf (stderr, message, a1, a2, a3, a4, a5, a6, a7, a8); ++error_message_count; if (errnum) print_errno_message (errnum); putc ('\n', stderr); fflush (stderr); if (status) exit (status); #endif #ifdef _LIBC # ifdef USE_IN_LIBIO _IO_funlockfile (stderr); # else __funlockfile (stderr); # endif #endif } /* Sometimes we want to have at most one error per line. This variable controls whether this mode is selected or not. */ int error_one_per_line; void #if defined VA_START && __STDC__ error_at_line (int status, int errnum, const char *file_name, unsigned int line_number, const char *message, ...) #else error_at_line (status, errnum, file_name, line_number, message, va_alist) int status; int errnum; const char *file_name; unsigned int line_number; char *message; va_dcl #endif { #ifdef VA_START va_list args; #endif if (error_one_per_line) { static const char *old_file_name; static unsigned int old_line_number; if (old_line_number == line_number && (file_name == old_file_name || strcmp (old_file_name, file_name) == 0)) /* Simply return and print nothing. */ return; old_file_name = file_name; old_line_number = line_number; } fflush (stdout); #ifdef _LIBC # ifdef USE_IN_LIBIO _IO_flockfile (stderr); # else __flockfile (stderr); # endif #endif if (error_print_progname) (*error_print_progname) (); else { #if _LIBC && USE_IN_LIBIO if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s: ", program_name); else #endif fprintf (stderr, "%s:", program_name); } if (file_name != NULL) { #if _LIBC && USE_IN_LIBIO if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s:%d: ", file_name, line_number); else #endif fprintf (stderr, "%s:%d: ", file_name, line_number); } #ifdef VA_START VA_START (args, message); error_tail (status, errnum, message, args); #else fprintf (stderr, message, a1, a2, a3, a4, a5, a6, a7, a8); ++error_message_count; if (errnum) print_errno_message (errnum); putc ('\n', stderr); fflush (stderr); if (status) exit (status); #endif #ifdef _LIBC # ifdef USE_IN_LIBIO _IO_funlockfile (stderr); # else __funlockfile (stderr); # endif #endif } #ifdef _LIBC /* Make the weak alias. */ # undef error # undef error_at_line weak_alias (__error, error) weak_alias (__error_at_line, error_at_line) #endif
/* Getopt for GNU. NOTE: getopt is now part of the C library, so if you don't know what "Keep this file name-space clean" means, talk to drepper@gnu.org before changing it! Copyright (C) 1987,88,89,90,91,92,93,94,95,96,98,99,2000,2001,2002 Free Software Foundation, Inc. This file is part of the GNU C Library. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>. Ditto for AIX 3.2 and <stdlib.h>. */ #ifndef _NO_PROTO # define _NO_PROTO #endif #ifdef HAVE_CONFIG_H # include <config.h> #endif #if !defined __STDC__ || !__STDC__ /* This is a separate conditional since some stdc systems reject `defined (const)'. */ # ifndef const # define const # endif #endif #include <stdio.h> /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #define GETOPT_INTERFACE_VERSION 2 #if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 # include <gnu-versions.h> # if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION # define ELIDE_CODE # endif #endif #ifndef ELIDE_CODE /* This needs to come after some library #include to get __GNU_LIBRARY__ defined. */ #ifdef __GNU_LIBRARY__ /* Don't include stdlib.h for non-GNU C libraries because some of them contain conflicting prototypes for getopt. */ # include <stdlib.h> # include <unistd.h> #endif /* GNU C library. */ #ifdef VMS # include <unixlib.h> # if HAVE_STRING_H - 0 # include <string.h> # endif #endif #ifdef _LIBC # include <libintl.h> #else /* This is for other GNU distributions with internationalized messages. */ # include "gettext.h" #endif #define _(msgid) gettext (msgid) #if defined _LIBC && defined USE_IN_LIBIO # include <wchar.h> #endif #ifndef attribute_hidden # define attribute_hidden #endif /* This version of `getopt' appears to the caller like standard Unix `getopt' but it behaves differently for the user, since it allows the user to intersperse the options with the other arguments. As `getopt' works, it permutes the elements of ARGV so that, when it is done, all the options precede everything else. Thus all application programs are extended to handle flexible argument order. Setting the environment variable POSIXLY_CORRECT disables permutation. Then the behavior is completely standard. GNU application programs can use a third alternative mode in which they can distinguish the relative order of options and other arguments. */ #include "getopt.h" /* For communication from `getopt' to the caller. When `getopt' finds an option that takes an argument, the argument value is returned here. Also, when `ordering' is RETURN_IN_ORDER, each non-option ARGV-element is returned here. */ char *optarg; /* Index in ARGV of the next element to be scanned. This is used for communication to and from the caller and for communication between successive calls to `getopt'. On entry to `getopt', zero means this is the first call; initialize. When `getopt' returns -1, this is the index of the first of the non-option elements that the caller should itself scan. Otherwise, `optind' communicates from one call to the next how much of ARGV has been scanned so far. */ /* 1003.2 says this must be 1 before any call. */ int optind = 1; /* Formerly, initialization of getopt depended on optind==0, which causes problems with re-calling getopt as programs generally don't know that. */ int __getopt_initialized attribute_hidden; /* The next char to be scanned in the option-element in which the last option character we returned was found. This allows us to pick up the scan where we left off. If this is zero, or a null string, it means resume the scan by advancing to the next ARGV-element. */ static char *nextchar; /* Callers store zero here to inhibit the error message for unrecognized options. */ int opterr = 1; /* Set to an option character which was unrecognized. This must be initialized on some systems to avoid linking in the system's own getopt implementation. */ int optopt = '?'; /* Describe how to deal with options that follow non-option ARGV-elements. If the caller did not specify anything, the default is REQUIRE_ORDER if the environment variable POSIXLY_CORRECT is defined, PERMUTE otherwise. REQUIRE_ORDER means don't recognize them as options; stop option processing when the first non-option is seen. This is what Unix does. This mode of operation is selected by either setting the environment variable POSIXLY_CORRECT, or using `+' as the first character of the list of option characters. PERMUTE is the default. We permute the contents of ARGV as we scan, so that eventually all the non-options are at the end. This allows options to be given in any order, even with programs that were not written to expect this. RETURN_IN_ORDER is an option available to programs that were written to expect options and other ARGV-elements in any order and that care about the ordering of the two. We describe each non-option ARGV-element as if it were the argument of an option with character code 1. Using `-' as the first character of the list of option characters selects this mode of operation. The special argument `--' forces an end of option-scanning regardless of the value of `ordering'. In the case of RETURN_IN_ORDER, only `--' can cause `getopt' to return -1 with `optind' != ARGC. */ static enum { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER } ordering; /* Value of POSIXLY_CORRECT environment variable. */ static char *posixly_correct; #ifdef __GNU_LIBRARY__ /* We want to avoid inclusion of string.h with non-GNU libraries because there are many ways it can cause trouble. On some systems, it contains special magic macros that don't work in GCC. */ # include <string.h> # define my_index strchr #else # if HAVE_STRING_H # include <string.h> # else # include <strings.h> # endif /* Avoid depending on library functions or files whose names are inconsistent. */ #ifndef getenv extern char *getenv (); #endif static char * my_index (str, chr) const char *str; int chr; { while (*str) { if (*str == chr) return (char *) str; str++; } return 0; } /* If using GCC, we can safely declare strlen this way. If not using GCC, it is ok not to declare it. */ #ifdef __GNUC__ /* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h. That was relevant to code that was here before. */ # if (!defined __STDC__ || !__STDC__) && !defined strlen /* gcc with -traditional declares the built-in strlen to return int, and has done so at least since version 2.4.5. -- rms. */ extern int strlen (const char *); # endif /* not __STDC__ */ #endif /* __GNUC__ */ #endif /* not __GNU_LIBRARY__ */ /* Handle permutation of arguments. */ /* Describe the part of ARGV that contains non-options that have been skipped. `first_nonopt' is the index in ARGV of the first of them; `last_nonopt' is the index after the last of them. */ static int first_nonopt; static int last_nonopt; #ifdef _LIBC /* Stored original parameters. XXX This is no good solution. We should rather copy the args so that we can compare them later. But we must not use malloc(3). */ extern int __libc_argc; extern char **__libc_argv; /* Bash 2.0 gives us an environment variable containing flags indicating ARGV elements that should not be considered arguments. */ # ifdef USE_NONOPTION_FLAGS /* Defined in getopt_init.c */ extern char *__getopt_nonoption_flags; static int nonoption_flags_max_len; static int nonoption_flags_len; # endif # ifdef USE_NONOPTION_FLAGS # define SWAP_FLAGS(ch1, ch2) \ if (nonoption_flags_len > 0) \ { \ char __tmp = __getopt_nonoption_flags[ch1]; \ __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ __getopt_nonoption_flags[ch2] = __tmp; \ } # else # define SWAP_FLAGS(ch1, ch2) # endif #else /* !_LIBC */ # define SWAP_FLAGS(ch1, ch2) #endif /* _LIBC */ /* Exchange two adjacent subsequences of ARGV. One subsequence is elements [first_nonopt,last_nonopt) which contains all the non-options that have been skipped so far. The other is elements [last_nonopt,optind), which contains all the options processed since those non-options were skipped. `first_nonopt' and `last_nonopt' are relocated so that they describe the new indices of the non-options in ARGV after they are moved. */ #if defined __STDC__ && __STDC__ static void exchange (char **); #endif static void exchange (argv) char **argv; { int bottom = first_nonopt; int middle = last_nonopt; int top = optind; char *tem; /* Exchange the shorter segment with the far end of the longer segment. That puts the shorter segment into the right place. It leaves the longer segment in the right place overall, but it consists of two parts that need to be swapped next. */ #if defined _LIBC && defined USE_NONOPTION_FLAGS /* First make sure the handling of the `__getopt_nonoption_flags' string can work normally. Our top argument must be in the range of the string. */ if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len) { /* We must extend the array. The user plays games with us and presents new arguments. */ char *new_str = malloc (top + 1); if (new_str == NULL) nonoption_flags_len = nonoption_flags_max_len = 0; else { memset (__mempcpy (new_str, __getopt_nonoption_flags, nonoption_flags_max_len), '\0', top + 1 - nonoption_flags_max_len); nonoption_flags_max_len = top + 1; __getopt_nonoption_flags = new_str; } } #endif while (top > middle && middle > bottom) { if (top - middle > middle - bottom) { /* Bottom segment is the short one. */ int len = middle - bottom; register int i; /* Swap it with the top part of the top segment. */ for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[top - (middle - bottom) + i]; argv[top - (middle - bottom) + i] = tem; SWAP_FLAGS (bottom + i, top - (middle - bottom) + i); } /* Exclude the moved bottom segment from further swapping. */ top -= len; } else { /* Top segment is the short one. */ int len = top - middle; register int i; /* Swap it with the bottom part of the bottom segment. */ for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[middle + i]; argv[middle + i] = tem; SWAP_FLAGS (bottom + i, middle + i); } /* Exclude the moved top segment from further swapping. */ bottom += len; } } /* Update records for the slots the non-options now occupy. */ first_nonopt += (optind - last_nonopt); last_nonopt = optind; } /* Initialize the internal data when the first call is made. */ #if defined __STDC__ && __STDC__ static const char *_getopt_initialize (int, char *const *, const char *); #endif static const char * _getopt_initialize (argc, argv, optstring) int argc; char *const *argv; const char *optstring; { /* Start processing options with ARGV-element 1 (since ARGV-element 0 is the program name); the sequence of previously skipped non-option ARGV-elements is empty. */ first_nonopt = last_nonopt = optind; nextchar = NULL; posixly_correct = getenv ("POSIXLY_CORRECT"); /* Determine how to handle the ordering of options and nonoptions. */ if (optstring[0] == '-') { ordering = RETURN_IN_ORDER; ++optstring; } else if (optstring[0] == '+') { ordering = REQUIRE_ORDER; ++optstring; } else if (posixly_correct != NULL) ordering = REQUIRE_ORDER; else ordering = PERMUTE; #if defined _LIBC && defined USE_NONOPTION_FLAGS if (posixly_correct == NULL && argc == __libc_argc && argv == __libc_argv) { if (nonoption_flags_max_len == 0) { if (__getopt_nonoption_flags == NULL || __getopt_nonoption_flags[0] == '\0') nonoption_flags_max_len = -1; else { const char *orig_str = __getopt_nonoption_flags; int len = nonoption_flags_max_len = strlen (orig_str); if (nonoption_flags_max_len < argc) nonoption_flags_max_len = argc; __getopt_nonoption_flags = (char *) malloc (nonoption_flags_max_len); if (__getopt_nonoption_flags == NULL) nonoption_flags_max_len = -1; else memset (__mempcpy (__getopt_nonoption_flags, orig_str, len), '\0', nonoption_flags_max_len - len); } } nonoption_flags_len = nonoption_flags_max_len; } else nonoption_flags_len = 0; #endif return optstring; } /* Scan elements of ARGV (whose length is ARGC) for option characters given in OPTSTRING. If an element of ARGV starts with '-', and is not exactly "-" or "--", then it is an option element. The characters of this element (aside from the initial '-') are option characters. If `getopt' is called repeatedly, it returns successively each of the option characters from each of the option elements. If `getopt' finds another option character, it returns that character, updating `optind' and `nextchar' so that the next call to `getopt' can resume the scan with the following option character or ARGV-element. If there are no more option characters, `getopt' returns -1. Then `optind' is the index in ARGV of the first ARGV-element that is not an option. (The ARGV-elements have been permuted so that those that are not options now come last.) OPTSTRING is a string containing the legitimate option characters. If an option character is seen that is not listed in OPTSTRING, return '?' after printing an error message. If you set `opterr' to zero, the error message is suppressed but we still return '?'. If a char in OPTSTRING is followed by a colon, that means it wants an arg, so the following text in the same ARGV-element, or the text of the following ARGV-element, is returned in `optarg'. Two colons mean an option that wants an optional arg; if there is text in the current ARGV-element, it is returned in `optarg', otherwise `optarg' is set to zero. If OPTSTRING starts with `-' or `+', it requests different methods of handling the non-option ARGV-elements. See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. Long-named options begin with `--' instead of `-'. Their names may be abbreviated as long as the abbreviation is unique or is an exact match for some defined option. If they have an argument, it follows the option name in the same ARGV-element, separated from the option name by a `=', or else the in next ARGV-element. When `getopt' finds a long-named option, it returns 0 if that option's `flag' field is nonzero, the value of the option's `val' field if the `flag' field is zero. The elements of ARGV aren't really const, because we permute them. But we pretend they're const in the prototype to be compatible with other systems. LONGOPTS is a vector of `struct option' terminated by an element containing a name which is zero. LONGIND returns the index in LONGOPT of the long-named option found. It is only valid when a long-named option has been found by the most recent call. If LONG_ONLY is nonzero, '-' as well as '--' can introduce long-named options. */ int _getopt_internal (argc, argv, optstring, longopts, longind, long_only) int argc; char *const *argv; const char *optstring; const struct option *longopts; int *longind; int long_only; { int print_errors = opterr; if (optstring[0] == ':') print_errors = 0; if (argc < 1) return -1; optarg = NULL; if (optind == 0 || !__getopt_initialized) { if (optind == 0) optind = 1; /* Don't scan ARGV[0], the program name. */ optstring = _getopt_initialize (argc, argv, optstring); __getopt_initialized = 1; } /* Test whether ARGV[optind] points to a non-option argument. Either it does not have option syntax, or there is an environment flag from the shell indicating it is not an option. The later information is only used when the used in the GNU libc. */ #if defined _LIBC && defined USE_NONOPTION_FLAGS # define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \ || (optind < nonoption_flags_len \ && __getopt_nonoption_flags[optind] == '1')) #else # define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0') #endif if (nextchar == NULL || *nextchar == '\0') { /* Advance to the next ARGV-element. */ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been moved back by the user (who may also have changed the arguments). */ if (last_nonopt > optind) last_nonopt = optind; if (first_nonopt > optind) first_nonopt = optind; if (ordering == PERMUTE) { /* If we have just processed some options following some non-options, exchange them so that the options come first. */ if (first_nonopt != last_nonopt && last_nonopt != optind) exchange ((char **) argv); else if (last_nonopt != optind) first_nonopt = optind; /* Skip any additional non-options and extend the range of non-options previously skipped. */ while (optind < argc && NONOPTION_P) optind++; last_nonopt = optind; } /* The special ARGV-element `--' means premature end of options. Skip it like a null option, then exchange with previous non-options as if it were an option, then skip everything else like a non-option. */ if (optind != argc && !strcmp (argv[optind], "--")) { optind++; if (first_nonopt != last_nonopt && last_nonopt != optind) exchange ((char **) argv); else if (first_nonopt == last_nonopt) first_nonopt = optind; last_nonopt = argc; optind = argc; } /* If we have done all the ARGV-elements, stop the scan and back over any non-options that we skipped and permuted. */ if (optind == argc) { /* Set the next-arg-index to point at the non-options that we previously skipped, so the caller will digest them. */ if (first_nonopt != last_nonopt) optind = first_nonopt; return -1; } /* If we have come to a non-option and did not permute it, either stop the scan or describe it to the caller and pass it by. */ if (NONOPTION_P) { if (ordering == REQUIRE_ORDER) return -1; optarg = argv[optind++]; return 1; } /* We have found another option-ARGV-element. Skip the initial punctuation. */ nextchar = (argv[optind] + 1 + (longopts != NULL && argv[optind][1] == '-')); } /* Decode the current option-ARGV-element. */ /* Check whether the ARGV-element is a long option. If long_only and the ARGV-element has the form "-f", where f is a valid short option, don't consider it an abbreviated form of a long option that starts with f. Otherwise there would be no way to give the -f short option. On the other hand, if there's a long option "fubar" and the ARGV-element is "-fu", do consider that an abbreviation of the long option, just like "--fu", and not "-f" with arg "u". This distinction seems to be the most useful approach. */ if (longopts != NULL && (argv[optind][1] == '-' || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1]))))) { char *nameend; const struct option *p; const struct option *pfound = NULL; int exact = 0; int ambig = 0; int indfound = -1; int option_index; for (nameend = nextchar; *nameend && *nameend != '='; nameend++) /* Do nothing. */ ; /* Test all long options for either exact match or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp (p->name, nextchar, nameend - nextchar)) { if ((unsigned int) (nameend - nextchar) == (unsigned int) strlen (p->name)) { /* Exact match found. */ pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { /* First nonexact match found. */ pfound = p; indfound = option_index; } else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val) /* Second or later nonexact match found. */ ambig = 1; } if (ambig && !exact) { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("%s: option `%s' is ambiguous\n"), argv[0], argv[optind]) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("%s: option `%s' is ambiguous\n"), argv[0], argv[optind]); #endif } nextchar += strlen (nextchar); optind++; optopt = 0; return '?'; } if (pfound != NULL) { option_index = indfound; optind++; if (*nameend) { /* Don't test has_arg with >, because some C compilers don't allow it to be used on enums. */ if (pfound->has_arg) optarg = nameend + 1; else { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; int n; #endif if (argv[optind - 1][1] == '-') { /* --option */ #if defined _LIBC && defined USE_IN_LIBIO n = __asprintf (&buf, _("\ %s: option `--%s' doesn't allow an argument\n"), argv[0], pfound->name); #else fprintf (stderr, _("\ %s: option `--%s' doesn't allow an argument\n"), argv[0], pfound->name); #endif } else { /* +option or -option */ #if defined _LIBC && defined USE_IN_LIBIO n = __asprintf (&buf, _("\ %s: option `%c%s' doesn't allow an argument\n"), argv[0], argv[optind - 1][0], pfound->name); #else fprintf (stderr, _("\ %s: option `%c%s' doesn't allow an argument\n"), argv[0], argv[optind - 1][0], pfound->name); #endif } #if defined _LIBC && defined USE_IN_LIBIO if (n >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #endif } nextchar += strlen (nextchar); optopt = pfound->val; return '?'; } } else if (pfound->has_arg == 1) { if (optind < argc) optarg = argv[optind++]; else { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("\ %s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("%s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]); #endif } nextchar += strlen (nextchar); optopt = pfound->val; return optstring[0] == ':' ? ':' : '?'; } } nextchar += strlen (nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } /* Can't find it as a long option. If this is not getopt_long_only, or the option starts with '--' or is not a valid short option, then it's an error. Otherwise interpret it as a short option. */ if (!long_only || argv[optind][1] == '-' || my_index (optstring, *nextchar) == NULL) { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; int n; #endif if (argv[optind][1] == '-') { /* --option */ #if defined _LIBC && defined USE_IN_LIBIO n = __asprintf (&buf, _("%s: unrecognized option `--%s'\n"), argv[0], nextchar); #else fprintf (stderr, _("%s: unrecognized option `--%s'\n"), argv[0], nextchar); #endif } else { /* +option or -option */ #if defined _LIBC && defined USE_IN_LIBIO n = __asprintf (&buf, _("%s: unrecognized option `%c%s'\n"), argv[0], argv[optind][0], nextchar); #else fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), argv[0], argv[optind][0], nextchar); #endif } #if defined _LIBC && defined USE_IN_LIBIO if (n >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #endif } nextchar = (char *) ""; optind++; optopt = 0; return '?'; } } /* Look at and handle the next short option-character. */ { char c = *nextchar++; char *temp = my_index (optstring, c); /* Increment `optind' when we start to process its last character. */ if (*nextchar == '\0') ++optind; if (temp == NULL || c == ':') { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; int n; #endif if (posixly_correct) { /* 1003.2 specifies the format of this message. */ #if defined _LIBC && defined USE_IN_LIBIO n = __asprintf (&buf, _("%s: illegal option -- %c\n"), argv[0], c); #else fprintf (stderr, _("%s: illegal option -- %c\n"), argv[0], c); #endif } else { #if defined _LIBC && defined USE_IN_LIBIO n = __asprintf (&buf, _("%s: invalid option -- %c\n"), argv[0], c); #else fprintf (stderr, _("%s: invalid option -- %c\n"), argv[0], c); #endif } #if defined _LIBC && defined USE_IN_LIBIO if (n >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #endif } optopt = c; return '?'; } /* Convenience. Treat POSIX -W foo same as long option --foo */ if (temp[0] == 'W' && temp[1] == ';') { char *nameend; const struct option *p; const struct option *pfound = NULL; int exact = 0; int ambig = 0; int indfound = 0; int option_index; /* This is an option that requires an argument. */ if (*nextchar != '\0') { optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ optind++; } else if (optind == argc) { if (print_errors) { /* 1003.2 specifies the format of this message. */ #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("%s: option requires an argument -- %c\n"), argv[0], c) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("%s: option requires an argument -- %c\n"), argv[0], c); #endif } optopt = c; if (optstring[0] == ':') c = ':'; else c = '?'; return c; } else /* We already incremented `optind' once; increment it again when taking next ARGV-elt as argument. */ optarg = argv[optind++]; /* optarg is now the argument, see if it's in the table of longopts. */ for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++) /* Do nothing. */ ; /* Test all long options for either exact match or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp (p->name, nextchar, nameend - nextchar)) { if ((unsigned int) (nameend - nextchar) == strlen (p->name)) { /* Exact match found. */ pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { /* First nonexact match found. */ pfound = p; indfound = option_index; } else /* Second or later nonexact match found. */ ambig = 1; } if (ambig && !exact) { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("%s: option `-W %s' is ambiguous\n"), argv[0], argv[optind]) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"), argv[0], argv[optind]); #endif } nextchar += strlen (nextchar); optind++; return '?'; } if (pfound != NULL) { option_index = indfound; if (*nameend) { /* Don't test has_arg with >, because some C compilers don't allow it to be used on enums. */ if (pfound->has_arg) optarg = nameend + 1; else { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("\ %s: option `-W %s' doesn't allow an argument\n"), argv[0], pfound->name) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("\ %s: option `-W %s' doesn't allow an argument\n"), argv[0], pfound->name); #endif } nextchar += strlen (nextchar); return '?'; } } else if (pfound->has_arg == 1) { if (optind < argc) optarg = argv[optind++]; else { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("\ %s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("%s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]); #endif } nextchar += strlen (nextchar); return optstring[0] == ':' ? ':' : '?'; } } nextchar += strlen (nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } nextchar = NULL; return 'W'; /* Let the application handle it. */ } if (temp[1] == ':') { if (temp[2] == ':') { /* This is an option that accepts an argument optionally. */ if (*nextchar != '\0') { optarg = nextchar; optind++; } else optarg = NULL; nextchar = NULL; } else { /* This is an option that requires an argument. */ if (*nextchar != '\0') { optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ optind++; } else if (optind == argc) { if (print_errors) { /* 1003.2 specifies the format of this message. */ #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("\ %s: option requires an argument -- %c\n"), argv[0], c) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("%s: option requires an argument -- %c\n"), argv[0], c); #endif } optopt = c; if (optstring[0] == ':') c = ':'; else c = '?'; } else /* We already incremented `optind' once; increment it again when taking next ARGV-elt as argument. */ optarg = argv[optind++]; nextchar = NULL; } } return c; } } int getopt (argc, argv, optstring) int argc; char *const *argv; const char *optstring; { return _getopt_internal (argc, argv, optstring, (const struct option *) 0, (int *) 0, 0); } #endif /* Not ELIDE_CODE. */ #ifdef TEST /* Compile with -DTEST to make an executable for use in testing the above definition of `getopt'. */ int main (argc, argv) int argc; char **argv; { int c; int digit_optind = 0; while (1) { int this_option_optind = optind ? optind : 1; c = getopt (argc, argv, "abc:d:0123456789"); if (c == -1) break; switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (digit_optind != 0 && digit_optind != this_option_optind) printf ("digits occur in two different argv-elements.\n"); digit_optind = this_option_optind; printf ("option %c\n", c); break; case 'a': printf ("option a\n"); break; case 'b': printf ("option b\n"); break; case 'c': printf ("option c with value `%s'\n", optarg); break; case '?': break; default: printf ("?? getopt returned character code 0%o ??\n", c); } } if (optind < argc) { printf ("non-option ARGV-elements: "); while (optind < argc) printf ("%s ", argv[optind++]); printf ("\n"); } exit (0); } #endif /* TEST */
/* getopt_long and getopt_long_only entry points for GNU getopt. Copyright (C) 1987,88,89,90,91,92,93,94,96,97,98 Free Software Foundation, Inc. This file is part of the GNU C Library. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #ifdef _LIBC # include <getopt.h> #else # include "getopt.h" #endif #if !defined __STDC__ || !__STDC__ /* This is a separate conditional since some stdc systems reject `defined (const)'. */ #ifndef const #define const #endif #endif #include <stdio.h> /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #define GETOPT_INTERFACE_VERSION 2 #if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 #include <gnu-versions.h> #if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION #define ELIDE_CODE #endif #endif #ifndef ELIDE_CODE /* This needs to come after some library #include to get __GNU_LIBRARY__ defined. */ #ifdef __GNU_LIBRARY__ #include <stdlib.h> #endif #ifndef NULL #define NULL 0 #endif int getopt_long (argc, argv, options, long_options, opt_index) int argc; char *const *argv; const char *options; const struct option *long_options; int *opt_index; { return _getopt_internal (argc, argv, options, long_options, opt_index, 0); } /* Like getopt_long, but '-' as well as '--' can indicate a long option. If an option that starts with '-' (not '--') doesn't match a long option, but does match a short option, it is parsed as a short option instead. */ int getopt_long_only (argc, argv, options, long_options, opt_index) int argc; char *const *argv; const char *options; const struct option *long_options; int *opt_index; { return _getopt_internal (argc, argv, options, long_options, opt_index, 1); } # ifdef _LIBC libc_hidden_def (getopt_long) libc_hidden_def (getopt_long_only) # endif #endif /* Not ELIDE_CODE. */ #ifdef TEST #include <stdio.h> int main (argc, argv) int argc; char **argv; { int c; int digit_optind = 0; while (1) { int this_option_optind = optind ? optind : 1; int option_index = 0; static struct option long_options[] = { {"add", 1, 0, 0}, {"append", 0, 0, 0}, {"delete", 1, 0, 0}, {"verbose", 0, 0, 0}, {"create", 0, 0, 0}, {"file", 1, 0, 0}, {0, 0, 0, 0} }; c = getopt_long (argc, argv, "abc:d:0123456789", long_options, &option_index); if (c == -1) break; switch (c) { case 0: printf ("option %s", long_options[option_index].name); if (optarg) printf (" with arg %s", optarg); printf ("\n"); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (digit_optind != 0 && digit_optind != this_option_optind) printf ("digits occur in two different argv-elements.\n"); digit_optind = this_option_optind; printf ("option %c\n", c); break; case 'a': printf ("option a\n"); break; case 'b': printf ("option b\n"); break; case 'c': printf ("option c with value `%s'\n", optarg); break; case 'd': printf ("option d with value `%s'\n", optarg); break; case '?': break; default: printf ("?? getopt returned character code 0%o ??\n", c); } } if (optind < argc) { printf ("non-option ARGV-elements: "); while (optind < argc) printf ("%s ", argv[optind++]); printf ("\n"); } exit (0); } #endif /* TEST */
/* addext.c -- add an extension to a file name Copyright (C) 1990, 1997, 1998, 1999, 2001, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@gnu.ai.mit.edu> and Paul Eggert */ #if HAVE_CONFIG_H # include <config.h> #endif #ifndef HAVE_DOS_FILE_NAMES # define HAVE_DOS_FILE_NAMES 0 #endif #ifndef HAVE_LONG_FILE_NAMES # define HAVE_LONG_FILE_NAMES 0 #endif #if HAVE_LIMITS_H # include <limits.h> #endif #ifndef _POSIX_NAME_MAX # define _POSIX_NAME_MAX 14 #endif #include <sys/types.h> #if HAVE_STRING_H # include <string.h> #else # include <strings.h> #endif #if HAVE_UNISTD_H # include <unistd.h> #endif #include <errno.h> #ifndef errno extern int errno; #endif #include "backupfile.h" #include "dirname.h" /* Append to FILENAME the extension EXT, unless the result would be too long, in which case just append the character E. */ void addext (char *filename, char const *ext, int e) { char *s = base_name (filename); size_t slen = base_len (s); size_t extlen = strlen (ext); size_t slen_max = HAVE_LONG_FILE_NAMES ? 255 : _POSIX_NAME_MAX; #if HAVE_PATHCONF && defined _PC_NAME_MAX if (_POSIX_NAME_MAX < slen + extlen || HAVE_DOS_FILE_NAMES) { /* The new base name is long enough to require a pathconf check. */ long name_max; errno = 0; if (s == filename) name_max = pathconf (".", _PC_NAME_MAX); else { char c = *s; if (! ISSLASH (c)) *s = 0; name_max = pathconf (filename, _PC_NAME_MAX); *s = c; } if (0 <= name_max || errno == 0) { long size = slen_max = name_max; if (name_max != size) slen_max = -1; } } #endif if (HAVE_DOS_FILE_NAMES && slen_max <= 12) { /* Live within DOS's 8.3 limit. */ char *dot = strchr (s, '.'); if (dot) { slen -= dot + 1 - s; s = dot + 1; slen_max = 3; } else slen_max = 8; extlen = 9; /* Don't use EXT. */ } if (slen + extlen <= slen_max) strcpy (s + slen, ext); else { if (slen_max <= slen) slen = slen_max - 1; s[slen] = e; s[slen + 1] = 0; } }
/* argmatch.c -- find a match for a string in an array Copyright (C) 1990, 1998, 1999, 2001, 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@ai.mit.edu> Modified by Akim Demaille <demaille@inf.enst.fr> */ #if HAVE_CONFIG_H # include <config.h> #endif /* Specification. */ #include "argmatch.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include "gettext.h" #define _(msgid) gettext (msgid) #include "error.h" #include "quotearg.h" #include "quote.h" #include "unlocked-io.h" /* When reporting an invalid argument, show nonprinting characters by using the quoting style ARGMATCH_QUOTING_STYLE. Do not use literal_quoting_style. */ #ifndef ARGMATCH_QUOTING_STYLE # define ARGMATCH_QUOTING_STYLE locale_quoting_style #endif #ifndef EXIT_FAILURE # define EXIT_FAILURE 1 #endif /* Non failing version of argmatch call this function after failing. */ #ifndef ARGMATCH_DIE # define ARGMATCH_DIE exit (EXIT_FAILURE) #endif #ifdef ARGMATCH_DIE_DECL ARGMATCH_DIE_DECL; #endif static void __argmatch_die (void) { ARGMATCH_DIE; } /* Used by XARGMATCH and XARGCASEMATCH. See description in argmatch.h. Default to __argmatch_die, but allow caller to change this at run-time. */ argmatch_exit_fn argmatch_die = __argmatch_die; /* If ARG is an unambiguous match for an element of the null-terminated array ARGLIST, return the index in ARGLIST of the matched element, else -1 if it does not match any element or -2 if it is ambiguous (is a prefix of more than one element). If VALLIST is none null, use it to resolve ambiguities limited to synonyms, i.e., for "yes", "yop" -> 0 "no", "nope" -> 1 "y" is a valid argument, for `0', and "n" for `1'. */ int argmatch (const char *arg, const char *const *arglist, const char *vallist, size_t valsize) { int i; /* Temporary index in ARGLIST. */ size_t arglen; /* Length of ARG. */ int matchind = -1; /* Index of first nonexact match. */ int ambiguous = 0; /* If nonzero, multiple nonexact match(es). */ arglen = strlen (arg); /* Test all elements for either exact match or abbreviated matches. */ for (i = 0; arglist[i]; i++) { if (!strncmp (arglist[i], arg, arglen)) { if (strlen (arglist[i]) == arglen) /* Exact match found. */ return i; else if (matchind == -1) /* First nonexact match found. */ matchind = i; else { /* Second nonexact match found. */ if (vallist == NULL || memcmp (vallist + valsize * matchind, vallist + valsize * i, valsize)) { /* There is a real ambiguity, or we could not disambiguate. */ ambiguous = 1; } } } } if (ambiguous) return -2; else return matchind; } /* Error reporting for argmatch. CONTEXT is a description of the type of entity that was being matched. VALUE is the invalid value that was given. PROBLEM is the return value from argmatch. */ void argmatch_invalid (const char *context, const char *value, int problem) { char const *format = (problem == -1 ? _("invalid argument %s for %s") : _("ambiguous argument %s for %s")); error (0, 0, format, quotearg_n_style (0, ARGMATCH_QUOTING_STYLE, value), quote_n (1, context)); } /* List the valid arguments for argmatch. ARGLIST is the same as in argmatch. VALLIST is a pointer to an array of values. VALSIZE is the size of the elements of VALLIST */ void argmatch_valid (const char *const *arglist, const char *vallist, size_t valsize) { int i; const char *last_val = NULL; /* We try to put synonyms on the same line. The assumption is that synonyms follow each other */ fprintf (stderr, _("Valid arguments are:")); for (i = 0; arglist[i]; i++) if ((i == 0) || memcmp (last_val, vallist + valsize * i, valsize)) { fprintf (stderr, "\n - `%s'", arglist[i]); last_val = vallist + valsize * i; } else { fprintf (stderr, ", `%s'", arglist[i]); } putc ('\n', stderr); } /* Never failing versions of the previous functions. CONTEXT is the context for which argmatch is called (e.g., "--version-control", or "$VERSION_CONTROL" etc.). Upon failure, calls the (supposed never to return) function EXIT_FN. */ int __xargmatch_internal (const char *context, const char *arg, const char *const *arglist, const char *vallist, size_t valsize, argmatch_exit_fn exit_fn) { int res = argmatch (arg, arglist, vallist, valsize); if (res >= 0) /* Success. */ return res; /* We failed. Explain why. */ argmatch_invalid (context, arg, res); argmatch_valid (arglist, vallist, valsize); (*exit_fn) (); return -1; /* To please the compilers. */ } /* Look for VALUE in VALLIST, an array of objects of size VALSIZE and return the first corresponding argument in ARGLIST */ const char * argmatch_to_argument (const char *value, const char *const *arglist, const char *vallist, size_t valsize) { int i; for (i = 0; arglist[i]; i++) if (!memcmp (value, vallist + valsize * i, valsize)) return arglist[i]; return NULL; } #ifdef TEST /* * Based on "getversion.c" by David MacKenzie <djm@gnu.ai.mit.edu> */ char *program_name; extern const char *getenv (); /* When to make backup files. */ enum backup_type { /* Never make backups. */ none, /* Make simple backups of every file. */ simple, /* Make numbered backups of files that already have numbered backups, and simple backups of the others. */ numbered_existing, /* Make numbered backups of every file. */ numbered }; /* Two tables describing arguments (keys) and their corresponding values */ static const char *const backup_args[] = { "no", "none", "off", "simple", "never", "existing", "nil", "numbered", "t", 0 }; static const enum backup_type backup_vals[] = { none, none, none, simple, simple, numbered_existing, numbered_existing, numbered, numbered }; int main (int argc, const char *const *argv) { const char *cp; enum backup_type backup_type = none; program_name = (char *) argv[0]; if (argc > 2) { fprintf (stderr, "Usage: %s [VERSION_CONTROL]\n", program_name); exit (1); } if ((cp = getenv ("VERSION_CONTROL"))) backup_type = XARGMATCH ("$VERSION_CONTROL", cp, backup_args, backup_vals); if (argc == 2) backup_type = XARGMATCH (program_name, argv[1], backup_args, backup_vals); printf ("The version control is `%s'\n", ARGMATCH_TO_ARGUMENT (backup_type, backup_args, backup_vals)); return 0; } #endif
/* backupfile.c -- make Emacs style backup file names Copyright (C) 1990,91,92,93,94,95,96,97,98,99,2000, 2001, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@gnu.ai.mit.edu>. Some algorithms adapted from GNU Emacs. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #include <sys/types.h> #if HAVE_STRING_H # include <string.h> #else # include <strings.h> #endif #if HAVE_DIRENT_H # include <dirent.h> # define NLENGTH(direct) strlen ((direct)->d_name) #else # define dirent direct # define NLENGTH(direct) ((size_t) (direct)->d_namlen) # if HAVE_SYS_NDIR_H # include <sys/ndir.h> # endif # if HAVE_SYS_DIR_H # include <sys/dir.h> # endif # if HAVE_NDIR_H # include <ndir.h> # endif #endif #if CLOSEDIR_VOID /* Fake a return value. */ # define CLOSEDIR(d) (closedir (d), 0) #else # define CLOSEDIR(d) closedir (d) #endif #if HAVE_STDLIB_H # include <stdlib.h> #endif #ifndef HAVE_DECL_GETENV "this configure-time declaration test was not run" #endif #if !HAVE_DECL_GETENV char *getenv (); #endif #ifndef HAVE_DECL_MALLOC "this configure-time declaration test was not run" #endif #if !HAVE_DECL_MALLOC char *malloc (); #endif #if HAVE_DIRENT_H || HAVE_NDIR_H || HAVE_SYS_DIR_H || HAVE_SYS_NDIR_H # define HAVE_DIR 1 #else # define HAVE_DIR 0 #endif #if HAVE_LIMITS_H # include <limits.h> #endif #ifndef CHAR_BIT # define CHAR_BIT 8 #endif /* Upper bound on the string length of an integer converted to string. 302 / 1000 is ceil (log10 (2.0)). Subtract 1 for the sign bit; add 1 for integer division truncation; add 1 more for a minus sign. */ #define INT_STRLEN_BOUND(t) ((sizeof (t) * CHAR_BIT - 1) * 302 / 1000 + 2) /* ISDIGIT differs from isdigit, as follows: - Its arg may be any int or unsigned int; it need not be an unsigned char. - It's guaranteed to evaluate its argument exactly once. - It's typically faster. POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to ISDIGIT_LOCALE unless it's important to use the locale's definition of `digit' even when the host does not conform to POSIX. */ #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9) #if D_INO_IN_DIRENT # define REAL_DIR_ENTRY(dp) ((dp)->d_ino != 0) #else # define REAL_DIR_ENTRY(dp) 1 #endif #include "argmatch.h" #include "backupfile.h" #include "dirname.h" /* The extension added to file names to produce a simple (as opposed to numbered) backup file name. */ const char *simple_backup_suffix = "~"; static int max_backup_version PARAMS ((const char *, const char *)); static int version_number PARAMS ((const char *, const char *, size_t)); /* Return the name of the new backup file for file FILE, allocated with malloc. Return 0 if out of memory. FILE must not end with a '/' unless it is the root directory. Do not call this function if backup_type == none. */ char * find_backup_file_name (const char *file, enum backup_type backup_type) { size_t backup_suffix_size_max; size_t file_len = strlen (file); size_t numbered_suffix_size_max = INT_STRLEN_BOUND (int) + 4; char *s; const char *suffix = simple_backup_suffix; /* Allow room for simple or `.~N~' backups. */ backup_suffix_size_max = strlen (simple_backup_suffix) + 1; if (HAVE_DIR && backup_suffix_size_max < numbered_suffix_size_max) backup_suffix_size_max = numbered_suffix_size_max; s = malloc (file_len + 1 + backup_suffix_size_max + numbered_suffix_size_max); if (s) { #if HAVE_DIR if (backup_type != simple) { int highest_backup; size_t dirlen = dir_len (file); memcpy (s, file, dirlen); if (dirlen == FILESYSTEM_PREFIX_LEN (file)) s[dirlen++] = '.'; s[dirlen] = '\0'; highest_backup = max_backup_version (base_name (file), s); if (! (backup_type == numbered_existing && highest_backup == 0)) { char *numbered_suffix = s + (file_len + backup_suffix_size_max); sprintf (numbered_suffix, ".~%d~", highest_backup + 1); suffix = numbered_suffix; } } #endif /* HAVE_DIR */ strcpy (s, file); addext (s, suffix, '~'); } return s; } #if HAVE_DIR /* Return the number of the highest-numbered backup file for file FILE in directory DIR. If there are no numbered backups of FILE in DIR, or an error occurs reading DIR, return 0. */ static int max_backup_version (const char *file, const char *dir) { DIR *dirp; struct dirent *dp; int highest_version; int this_version; size_t file_name_length; dirp = opendir (dir); if (!dirp) return 0; highest_version = 0; file_name_length = base_len (file); while ((dp = readdir (dirp)) != 0) { if (!REAL_DIR_ENTRY (dp) || NLENGTH (dp) < file_name_length + 4) continue; this_version = version_number (file, dp->d_name, file_name_length); if (this_version > highest_version) highest_version = this_version; } if (CLOSEDIR (dirp)) return 0; return highest_version; } /* If BACKUP is a numbered backup of BASE, return its version number; otherwise return 0. BASE_LENGTH is the length of BASE. */ static int version_number (const char *base, const char *backup, size_t base_length) { int version; const char *p; version = 0; if (strncmp (base, backup, base_length) == 0 && backup[base_length] == '.' && backup[base_length + 1] == '~') { for (p = &backup[base_length + 2]; ISDIGIT (*p); ++p) version = version * 10 + *p - '0'; if (p[0] != '~' || p[1]) version = 0; } return version; } #endif /* HAVE_DIR */ static const char * const backup_args[] = { /* In a series of synonyms, present the most meaning full first, so that argmatch_valid be more readable. */ "none", "off", "simple", "never", "existing", "nil", "numbered", "t", 0 }; static const enum backup_type backup_types[] = { none, none, simple, simple, numbered_existing, numbered_existing, numbered, numbered }; /* Return the type of backup specified by VERSION. If VERSION is NULL or the empty string, return numbered_existing. If VERSION is invalid or ambiguous, fail with a diagnostic appropriate for the specified CONTEXT. Unambiguous abbreviations are accepted. */ enum backup_type get_version (const char *context, const char *version) { if (version == 0 || *version == 0) return numbered_existing; else return XARGMATCH (context, version, backup_args, backup_types); } /* Return the type of backup specified by VERSION. If VERSION is NULL, use the value of the envvar VERSION_CONTROL. If the specified string is invalid or ambiguous, fail with a diagnostic appropriate for the specified CONTEXT. Unambiguous abbreviations are accepted. */ enum backup_type xget_version (const char *context, const char *version) { if (version && *version) return get_version (context, version); else return get_version ("$VERSION_CONTROL", getenv ("VERSION_CONTROL")); }
/* basename.c -- return the last element in a path Copyright (C) 1990, 1998, 1999, 2000, 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #if STDC_HEADERS || HAVE_STRING_H # include <string.h> #endif #include "dirname.h" /* In general, we can't use the builtin `basename' function if available, since it has different meanings in different environments. In some environments the builtin `basename' modifies its argument. Return the address of the last file name component of NAME. If NAME has no file name components because it is all slashes, return NAME if it is empty, the address of its last slash otherwise. */ char * base_name (char const *name) { char const *base = name + FILESYSTEM_PREFIX_LEN (name); char const *p; for (p = base; *p; p++) { if (ISSLASH (*p)) { /* Treat multiple adjacent slashes like a single slash. */ do p++; while (ISSLASH (*p)); /* If the file name ends in slash, use the trailing slash as the basename if no non-slashes have been found. */ if (! *p) { if (ISSLASH (*base)) base = p - 1; break; } /* *P is a non-slash preceded by a slash. */ base = p; } } return (char *) base; } /* Return the length of of the basename NAME. Typically NAME is the value returned by base_name. Act like strlen (NAME), except omit redundant trailing slashes. */ size_t base_len (char const *name) { size_t len; for (len = strlen (name); 1 < len && ISSLASH (name[len - 1]); len--) continue; return len; }
/* dirname.c -- return all but the last element in a path Copyright 1990, 1998, 2000, 2001, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #if STDC_HEADERS || HAVE_STRING_H # include <string.h> #endif #include "dirname.h" #include "xalloc.h" /* Return the length of `dirname (PATH)', or zero if PATH is in the working directory. Works properly even if there are trailing slashes (by effectively ignoring them). */ size_t dir_len (char const *path) { size_t prefix_length = FILESYSTEM_PREFIX_LEN (path); size_t length; /* Strip the basename and any redundant slashes before it. */ for (length = base_name (path) - path; prefix_length < length; length--) if (! ISSLASH (path[length - 1])) return length; /* But don't strip the only slash from "/". */ return prefix_length + ISSLASH (path[prefix_length]); } /* Return the leading directories part of PATH, allocated with xmalloc. Works properly even if there are trailing slashes (by effectively ignoring them). */ char * dir_name (char const *path) { size_t length = dir_len (path); int append_dot = (length == FILESYSTEM_PREFIX_LEN (path)); char *newpath = xmalloc (length + append_dot + 1); memcpy (newpath, path, length); if (append_dot) newpath[length++] = '.'; newpath[length] = 0; return newpath; } #ifdef TEST_DIRNAME /* Run the test like this (expect no output): gcc -DHAVE_CONFIG_H -DTEST_DIRNAME -I.. -O -Wall \ basename.c dirname.c xmalloc.c error.c sed -n '/^BEGIN-DATA$/,/^END-DATA$/p' dirname.c|grep -v DATA|./a.out If it's been built on a DOS or Windows platforms, run another test like this (again, expect no output): sed -n '/^BEGIN-DOS-DATA$/,/^END-DOS-DATA$/p' dirname.c|grep -v DATA|./a.out BEGIN-DATA foo//// . bar/foo//// bar foo/ . / / . . a . END-DATA BEGIN-DOS-DATA c:///// c:/ c:/ c:/ c:/. c:/ c:foo c:. c:foo/bar c:foo END-DOS-DATA */ # define MAX_BUFF_LEN 1024 # include <stdio.h> char *program_name; int main (int argc, char *argv[]) { char buff[MAX_BUFF_LEN + 1]; program_name = argv[0]; buff[MAX_BUFF_LEN] = 0; while (fgets (buff, MAX_BUFF_LEN, stdin) && buff[0]) { char path[MAX_BUFF_LEN]; char expected_result[MAX_BUFF_LEN]; char const *result; sscanf (buff, "%s %s", path, expected_result); result = dir_name (path); if (strcmp (result, expected_result)) printf ("%s: got %s, expected %s\n", path, result, expected_result); } return 0; } #endif
/* inputting files to be patched */ /* $Id: inp.c,v 1.25 2003/05/20 13:58:02 eggert Exp $ */ /* Copyright (C) 1986, 1988 Larry Wall Copyright (C) 1991, 1992, 1993, 1997, 1998, 1999, 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #define XTERN extern #include <common.h> #include <backupfile.h> #include <pch.h> #include <quotearg.h> #include <util.h> #include <xalloc.h> #undef XTERN #define XTERN #include <inp.h> /* Input-file-with-indexable-lines abstract type */ static char *i_buffer; /* plan A buffer */ static char const **i_ptr; /* pointers to lines in plan A buffer */ static size_t tibufsize; /* size of plan b buffers */ #ifndef TIBUFSIZE_MINIMUM #define TIBUFSIZE_MINIMUM (8 * 1024) /* minimum value for tibufsize */ #endif static int tifd = -1; /* plan b virtual string array */ static char *tibuf[2]; /* plan b buffers */ static LINENUM tiline[2] = {-1, -1}; /* 1st line in each buffer */ static LINENUM lines_per_buf; /* how many lines per buffer */ static size_t tireclen; /* length of records in tmp file */ static size_t last_line_size; /* size of last input line */ static bool plan_a (char const *); /* yield false if memory runs out */ static void plan_b (char const *); static void report_revision (bool); static void too_many_lines (char const *) __attribute__((noreturn)); /* New patch--prepare to edit another file. */ void re_input (void) { if (using_plan_a) { if (i_buffer) { free (i_buffer); i_buffer = 0; free (i_ptr); } } else { close (tifd); tifd = -1; if (tibuf[0]) { free (tibuf[0]); tibuf[0] = 0; } tiline[0] = tiline[1] = -1; tireclen = 0; } } /* Construct the line index, somehow or other. */ void scan_input (char *filename) { using_plan_a = ! (debug & 16) && plan_a (filename); if (!using_plan_a) plan_b(filename); if (verbosity != SILENT) { filename = quotearg (filename); if (verbosity == VERBOSE) say ("Patching file %s using Plan %s...\n", filename, using_plan_a ? "A" : "B"); else say ("patching file %s\n", filename); } } /* Report whether a desired revision was found. */ static void report_revision (bool found_revision) { char const *rev = quotearg (revision); if (found_revision) { if (verbosity == VERBOSE) say ("Good. This file appears to be the %s version.\n", rev); } else if (force) { if (verbosity != SILENT) say ("Warning: this file doesn't appear to be the %s version -- patching anyway.\n", rev); } else if (batch) fatal ("This file doesn't appear to be the %s version -- aborting.", rev); else { ask ("This file doesn't appear to be the %s version -- patch anyway? [n] ", rev); if (*buf != 'y') fatal ("aborted"); } } static void too_many_lines (char const *filename) { fatal ("File %s has too many lines", quotearg (filename)); } void get_input_file (char const *filename, char const *outname) { bool elsewhere = strcmp (filename, outname) != 0; char const *cs; char *diffbuf; char *getbuf; if (inerrno == -1) inerrno = stat (inname, &instat) == 0 ? 0 : errno; /* Perhaps look for RCS or SCCS versions. */ if (patch_get && invc != 0 && (inerrno || (! elsewhere && (/* No one can write to it. */ (instat.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH)) == 0 /* Only the owner (who's not me) can write to it. */ || ((instat.st_mode & (S_IWGRP|S_IWOTH)) == 0 && instat.st_uid != geteuid ())))) && (invc = !! (cs = (version_controller (filename, elsewhere, inerrno ? (struct stat *) 0 : &instat, &getbuf, &diffbuf))))) { if (!inerrno) { if (!elsewhere && (instat.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH)) != 0) /* Somebody can write to it. */ fatal ("File %s seems to be locked by somebody else under %s", quotearg (filename), cs); if (diffbuf) { /* It might be checked out unlocked. See if it's safe to check out the default version locked. */ if (verbosity == VERBOSE) say ("Comparing file %s to default %s version...\n", quotearg (filename), cs); if (systemic (diffbuf) != 0) { say ("warning: Patching file %s, which does not match default %s version\n", quotearg (filename), cs); cs = 0; } } } if (cs && version_get (filename, cs, ! inerrno, elsewhere, getbuf, &instat)) inerrno = 0; free (getbuf); if (diffbuf) free (diffbuf); } else if (inerrno && !pch_says_nonexistent (reverse)) { errno = inerrno; pfatal ("Can't find file %s", quotearg (filename)); } if (inerrno) { instat.st_mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH; instat.st_size = 0; } else if (! S_ISREG (instat.st_mode)) fatal ("File %s is not a regular file -- can't patch", quotearg (filename)); } /* Try keeping everything in memory. */ static bool plan_a (char const *filename) { register char const *s; register char const *lim; register char const **ptr; register char *buffer; register LINENUM iline; size_t size = instat.st_size; /* Fail if the file size doesn't fit in a size_t, or if storage isn't available. */ if (! (size == instat.st_size && (buffer = malloc (size ? size : (size_t) 1)))) return false; /* Read the input file, but don't bother reading it if it's empty. When creating files, the files do not actually exist. */ if (size) { int ifd = open (filename, O_RDONLY|binary_transput); size_t buffered = 0, n; if (ifd < 0) pfatal ("can't open file %s", quotearg (filename)); while (size - buffered != 0) { n = read (ifd, buffer + buffered, size - buffered); if (n == 0) { /* Some non-POSIX hosts exaggerate st_size in text mode; or the file may have shrunk! */ size = buffered; break; } if (n == (size_t) -1) { /* Perhaps size is too large for this host. */ close (ifd); free (buffer); return false; } buffered += n; } if (close (ifd) != 0) read_fatal (); } /* Scan the buffer and build array of pointers to lines. */ lim = buffer + size; iline = 3; /* 1 unused, 1 for SOF, 1 for EOF if last line is incomplete */ for (s = buffer; (s = (char *) memchr (s, '\n', lim - s)); s++) if (++iline < 0) too_many_lines (filename); if (! (iline == (size_t) iline && (size_t) iline * sizeof *ptr / sizeof *ptr == (size_t) iline && (ptr = (char const **) malloc ((size_t) iline * sizeof *ptr)))) { free (buffer); return false; } iline = 0; for (s = buffer; ; s++) { ptr[++iline] = s; if (! (s = (char *) memchr (s, '\n', lim - s))) break; } if (size && lim[-1] != '\n') ptr[++iline] = lim; input_lines = iline - 1; if (revision) { char const *rev = revision; int rev0 = rev[0]; bool found_revision = false; size_t revlen = strlen (rev); if (revlen <= size) { char const *limrev = lim - revlen; for (s = buffer; (s = (char *) memchr (s, rev0, limrev - s)); s++) if (memcmp (s, rev, revlen) == 0 && (s == buffer || ISSPACE ((unsigned char) s[-1])) && (s + 1 == limrev || ISSPACE ((unsigned char) s[revlen]))) { found_revision = true; break; } } report_revision (found_revision); } /* Plan A will work. */ i_buffer = buffer; i_ptr = ptr; return true; } /* Keep (virtually) nothing in memory. */ static void plan_b (char const *filename) { register FILE *ifp; register int c; register size_t len; register size_t maxlen; register bool found_revision; register size_t i; register char const *rev; register size_t revlen; register LINENUM line = 1; int exclusive; if (instat.st_size == 0) filename = NULL_DEVICE; if (! (ifp = fopen (filename, binary_transput ? "rb" : "r"))) pfatal ("Can't open file %s", quotearg (filename)); exclusive = TMPINNAME_needs_removal ? 0 : O_EXCL; TMPINNAME_needs_removal = 1; tifd = create_file (TMPINNAME, O_RDWR | O_BINARY | exclusive, (mode_t) 0); i = 0; len = 0; maxlen = 1; rev = revision; found_revision = !rev; revlen = rev ? strlen (rev) : 0; while ((c = getc (ifp)) != EOF) { len++; if (c == '\n') { if (++line < 0) too_many_lines (filename); if (maxlen < len) maxlen = len; len = 0; } if (!found_revision) { if (i == revlen) { found_revision = ISSPACE ((unsigned char) c); i = (size_t) -1; } else if (i != (size_t) -1) i = rev[i]==c ? i + 1 : (size_t) -1; if (i == (size_t) -1 && ISSPACE ((unsigned char) c)) i = 0; } } if (revision) report_revision (found_revision); Fseek (ifp, (off_t) 0, SEEK_SET); /* rewind file */ for (tibufsize = TIBUFSIZE_MINIMUM; tibufsize < maxlen; tibufsize <<= 1) continue; lines_per_buf = tibufsize / maxlen; tireclen = maxlen; tibuf[0] = xmalloc (2 * tibufsize); tibuf[1] = tibuf[0] + tibufsize; for (line = 1; ; line++) { char *p = tibuf[0] + maxlen * (line % lines_per_buf); char const *p0 = p; if (! (line % lines_per_buf)) /* new block */ if (write (tifd, tibuf[0], tibufsize) != tibufsize) write_fatal (); if ((c = getc (ifp)) == EOF) break; for (;;) { *p++ = c; if (c == '\n') { last_line_size = p - p0; break; } if ((c = getc (ifp)) == EOF) { last_line_size = p - p0; line++; goto EOF_reached; } } } EOF_reached: if (ferror (ifp) || fclose (ifp) != 0) read_fatal (); if (line % lines_per_buf != 0) if (write (tifd, tibuf[0], tibufsize) != tibufsize) write_fatal (); input_lines = line - 1; } /* Fetch a line from the input file. WHICHBUF is ignored when the file is in memory. */ char const * ifetch (LINENUM line, bool whichbuf, size_t *psize) { register char const *q; register char const *p; if (line < 1 || line > input_lines) { *psize = 0; return ""; } if (using_plan_a) { p = i_ptr[line]; *psize = i_ptr[line + 1] - p; return p; } else { LINENUM offline = line % lines_per_buf; LINENUM baseline = line - offline; if (tiline[0] == baseline) whichbuf = false; else if (tiline[1] == baseline) whichbuf = true; else { tiline[whichbuf] = baseline; if (lseek (tifd, (off_t) (baseline/lines_per_buf * tibufsize), SEEK_SET) == -1 || read (tifd, tibuf[whichbuf], tibufsize) < 0) read_fatal (); } p = tibuf[whichbuf] + (tireclen*offline); if (line == input_lines) *psize = last_line_size; else { for (q = p; *q++ != '\n'; ) continue; *psize = q - p; } return p; } }
/* common definitions for `patch' */ /* $Id: common.h,v 1.34 2003/05/19 06:57:36 eggert Exp $ */ /* Copyright (C) 1986, 1988 Larry Wall Copyright (C) 1990, 1991, 1992, 1993, 1997, 1998, 1999, 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef DEBUGGING #define DEBUGGING 1 #endif #include <config.h> #include <assert.h> #include <stdbool.h> #include <stdio.h> #include <sys/types.h> #include <time.h> #include <sys/stat.h> #if ! defined S_ISDIR && defined S_IFDIR # define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif #if ! defined S_ISREG && defined S_IFREG # define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #endif #ifndef S_IXOTH #define S_IXOTH 1 #endif #ifndef S_IWOTH #define S_IWOTH 2 #endif #ifndef S_IROTH #define S_IROTH 4 #endif #ifndef S_IXGRP #define S_IXGRP (S_IXOTH << 3) #endif #ifndef S_IWGRP #define S_IWGRP (S_IWOTH << 3) #endif #ifndef S_IRGRP #define S_IRGRP (S_IROTH << 3) #endif #ifndef S_IXUSR #define S_IXUSR (S_IXOTH << 6) #endif #ifndef S_IWUSR #define S_IWUSR (S_IWOTH << 6) #endif #ifndef S_IRUSR #define S_IRUSR (S_IROTH << 6) #endif #ifdef MKDIR_TAKES_ONE_ARG # define mkdir(name, mode) ((mkdir) (name)) #endif #if HAVE_LIMITS_H # include <limits.h> #endif #ifndef CHAR_BIT #define CHAR_BIT 8 #endif /* The extra casts work around common compiler bugs, e.g. Cray C 5.0.3.0 time_t. */ #define TYPE_SIGNED(t) ((t) -1 < (t) 0) #define TYPE_MINIMUM(t) ((t) (TYPE_SIGNED (t) \ ? (t) (~ (t) 0 << (sizeof (t) * CHAR_BIT - 1)) \ : (t) 0)) #define TYPE_MAXIMUM(t) ((t) ((t) ~ (t) 0 - TYPE_MINIMUM (t))) #ifndef CHAR_MAX #define CHAR_MAX TYPE_MAXIMUM (char) #endif #ifndef INT_MAX #define INT_MAX TYPE_MAXIMUM (int) #endif #ifndef LONG_MIN #define LONG_MIN TYPE_MINIMUM (long) #endif #if HAVE_INTTYPES_H # include <inttypes.h> #endif #ifndef SIZE_MAX /* On some nonstandard hosts, size_t is signed, so SIZE_MAX != (size_t) -1. */ #define SIZE_MAX TYPE_MAXIMUM (size_t) #endif #include <ctype.h> /* CTYPE_DOMAIN (C) is nonzero if the unsigned char C can safely be given as an argument to <ctype.h> macros like `isspace'. */ #if STDC_HEADERS #define CTYPE_DOMAIN(c) 1 #else #define CTYPE_DOMAIN(c) ((unsigned) (c) <= 0177) #endif #ifndef ISSPACE #define ISSPACE(c) (CTYPE_DOMAIN (c) && isspace (c)) #endif #ifndef ISDIGIT #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9) #endif /* handy definitions */ #define strEQ(s1,s2) (!strcmp(s1, s2)) #define strnEQ(s1,s2,l) (!strncmp(s1, s2, l)) /* typedefs */ typedef off_t LINENUM; /* must be signed */ /* globals */ XTERN char *program_name; /* The name this program was run with. */ XTERN char *buf; /* general purpose buffer */ XTERN size_t bufsize; /* allocated size of buf */ XTERN bool using_plan_a; /* try to keep everything in memory */ XTERN char *inname; XTERN char *outfile; XTERN int inerrno; XTERN int invc; XTERN struct stat instat; XTERN bool dry_run; XTERN bool posixly_correct; XTERN char const *origprae; XTERN char const *origbase; XTERN char const * volatile TMPINNAME; XTERN char const * volatile TMPOUTNAME; XTERN char const * volatile TMPPATNAME; XTERN int volatile TMPINNAME_needs_removal; XTERN int volatile TMPOUTNAME_needs_removal; XTERN int volatile TMPPATNAME_needs_removal; #ifdef DEBUGGING XTERN int debug; #else # define debug 0 #endif XTERN bool force; XTERN bool batch; XTERN bool noreverse; XTERN bool reverse; XTERN enum { DEFAULT_VERBOSITY, SILENT, VERBOSE } verbosity; XTERN bool skip_rest_of_patch; XTERN int strippath; XTERN bool canonicalize; XTERN int patch_get; XTERN bool set_time; XTERN bool set_utc; enum diff { NO_DIFF, CONTEXT_DIFF, NORMAL_DIFF, ED_DIFF, NEW_CONTEXT_DIFF, UNI_DIFF }; XTERN enum diff diff_type; XTERN char *revision; /* prerequisite revision, if any */ #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 6) || __STRICT_ANSI__ # define __attribute__(x) #endif void fatal_exit (int) __attribute__ ((noreturn)); #include <errno.h> #if !STDC_HEADERS && !defined errno extern int errno; #endif #if STDC_HEADERS || HAVE_STRING_H # include <string.h> #else # if !HAVE_MEMCHR # define memcmp(s1, s2, n) bcmp (s1, s2, n) # define memcpy(d, s, n) bcopy (s, d, n) void *memchr (); # endif #endif #if STDC_HEADERS # include <stdlib.h> #else char *getenv (); void *malloc (); void *realloc (); #endif #if HAVE_UNISTD_H # include <unistd.h> #else # ifndef lseek off_t lseek (); # endif #endif #ifndef SEEK_SET #define SEEK_SET 0 #endif #ifndef STDIN_FILENO #define STDIN_FILENO 0 #endif #ifndef STDOUT_FILENO #define STDOUT_FILENO 1 #endif #ifndef STDERR_FILENO #define STDERR_FILENO 2 #endif #if HAVE_FSEEKO typedef off_t file_offset; # define file_seek fseeko # define file_tell ftello #else typedef long file_offset; # define file_seek fseek # define file_tell ftell #endif #if ! (HAVE_GETEUID || defined geteuid) # if ! (HAVE_GETUID || defined getuid) # define geteuid() (-1) # else # define geteuid() getuid () # endif #endif #if HAVE_FCNTL_H # include <fcntl.h> #endif #ifndef O_RDONLY #define O_RDONLY 0 #endif #ifndef O_WRONLY #define O_WRONLY 1 #endif #ifndef O_RDWR #define O_RDWR 2 #endif #ifndef _O_BINARY #define _O_BINARY 0 #endif #ifndef O_BINARY #define O_BINARY _O_BINARY #endif #ifndef O_CREAT #define O_CREAT 0 #endif #ifndef O_EXCL #define O_EXCL 0 #endif #ifndef O_TRUNC #define O_TRUNC 0 #endif #if HAVE_SETMODE_DOS XTERN int binary_transput; /* O_BINARY if binary i/o is desired */ #else # define binary_transput 0 #endif #ifndef NULL_DEVICE #define NULL_DEVICE "/dev/null" #endif #ifndef TTY_DEVICE #define TTY_DEVICE "/dev/tty" #endif
/* backupfile.h -- declarations for making Emacs style backup file names Copyright (C) 1990-1992, 1997-1999 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef BACKUPFILE_H_ # define BACKUPFILE_H_ /* When to make backup files. */ enum backup_type { /* Never make backups. */ none, /* Make simple backups of every file. */ simple, /* Make numbered backups of files that already have numbered backups, and simple backups of the others. */ numbered_existing, /* Make numbered backups of every file. */ numbered }; # define VALID_BACKUP_TYPE(Type) \ ((Type) == none \ || (Type) == simple \ || (Type) == numbered_existing \ || (Type) == numbered) extern char const *simple_backup_suffix; # ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif # endif char *find_backup_file_name PARAMS ((char const *, enum backup_type)); enum backup_type get_version PARAMS ((char const *context, char const *arg)); enum backup_type xget_version PARAMS ((char const *context, char const *arg)); void addext PARAMS ((char *, char const *, int)); #endif /* ! BACKUPFILE_H_ */
/* reading patches */ /* $Id: pch.h,v 1.11 2003/05/20 13:56:03 eggert Exp $ */ /* Copyright (C) 1986, 1987, 1988 Larry Wall Copyright (C) 1990, 1991, 1992, 1993, 1997, 1998, 1999, 2000, 2001, 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ LINENUM pch_end (void); LINENUM pch_first (void); LINENUM pch_hunk_beg (void); LINENUM pch_newfirst (void); LINENUM pch_prefix_context (void); LINENUM pch_ptrn_lines (void); LINENUM pch_repl_lines (void); LINENUM pch_suffix_context (void); bool pch_swap (void); bool pch_write_line (LINENUM, FILE *); bool there_is_another_patch (void); char *pfetch (LINENUM); char pch_char (LINENUM); int another_hunk (enum diff, bool); int pch_says_nonexistent (bool); size_t pch_line_len (LINENUM); time_t pch_timestamp (bool); void do_ed_script (FILE *); void open_patch_file (char const *); void re_patch (void); void set_hunkmax (void);
/* quotearg.h - quote arguments for output Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert <eggert@twinsun.com> */ #ifndef QUOTEARG_H_ # define QUOTEARG_H_ 1 # include <stddef.h> /* Basic quoting styles. */ enum quoting_style { literal_quoting_style, /* --quoting-style=literal */ shell_quoting_style, /* --quoting-style=shell */ shell_always_quoting_style, /* --quoting-style=shell-always */ c_quoting_style, /* --quoting-style=c */ escape_quoting_style, /* --quoting-style=escape */ locale_quoting_style, /* --quoting-style=locale */ clocale_quoting_style /* --quoting-style=clocale */ }; /* For now, --quoting-style=literal is the default, but this may change. */ # ifndef DEFAULT_QUOTING_STYLE # define DEFAULT_QUOTING_STYLE literal_quoting_style # endif /* Names of quoting styles and their corresponding values. */ extern char const *const quoting_style_args[]; extern enum quoting_style const quoting_style_vals[]; struct quoting_options; /* The functions listed below set and use a hidden variable that contains the default quoting style options. */ /* Allocate a new set of quoting options, with contents initially identical to O if O is not null, or to the default if O is null. It is the caller's responsibility to free the result. */ struct quoting_options *clone_quoting_options (struct quoting_options *o); /* Get the value of O's quoting style. If O is null, use the default. */ enum quoting_style get_quoting_style (struct quoting_options *o); /* In O (or in the default if O is null), set the value of the quoting style to S. */ void set_quoting_style (struct quoting_options *o, enum quoting_style s); /* In O (or in the default if O is null), set the value of the quoting options for character C to I. Return the old value. Currently, the only values defined for I are 0 (the default) and 1 (which means to quote the character even if it would not otherwise be quoted). */ int set_char_quoting (struct quoting_options *o, char c, int i); /* Place into buffer BUFFER (of size BUFFERSIZE) a quoted version of argument ARG (of size ARGSIZE), using O to control quoting. If O is null, use the default. Terminate the output with a null character, and return the written size of the output, not counting the terminating null. If BUFFERSIZE is too small to store the output string, return the value that would have been returned had BUFFERSIZE been large enough. If ARGSIZE is -1, use the string length of the argument for ARGSIZE. */ size_t quotearg_buffer (char *buffer, size_t buffersize, char const *arg, size_t argsize, struct quoting_options const *o); /* Use storage slot N to return a quoted version of the string ARG. Use the default quoting options. The returned value points to static storage that can be reused by the next call to this function with the same value of N. N must be nonnegative. */ char *quotearg_n (int n, char const *arg); /* Equivalent to quotearg_n (0, ARG). */ char *quotearg (char const *arg); /* Use style S and storage slot N to return a quoted version of the string ARG. This is like quotearg_n (N, ARG), except that it uses S with no other options to specify the quoting method. */ char *quotearg_n_style (int n, enum quoting_style s, char const *arg); /* Use style S and storage slot N to return a quoted version of the argument ARG of size ARGSIZE. This is like quotearg_n_style (N, S, ARG), except it can quote null bytes. */ char *quotearg_n_style_mem (int n, enum quoting_style s, char const *arg, size_t argsize); /* Equivalent to quotearg_n_style (0, S, ARG). */ char *quotearg_style (enum quoting_style s, char const *arg); /* Like quotearg (ARG), except also quote any instances of CH. */ char *quotearg_char (char const *arg, char ch); /* Equivalent to quotearg_char (ARG, ':'). */ char *quotearg_colon (char const *arg); #endif /* !QUOTEARG_H_ */
/* utility functions for `patch' */ /* $Id: util.h,v 1.20 2003/05/20 13:56:48 eggert Exp $ */ /* Copyright (C) 1986 Larry Wall Copyright (C) 1992, 1993, 1997, 1998, 1999, 2001, 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* An upper bound on the print length of a signed decimal line number. Add one for the sign. */ #define LINENUM_LENGTH_BOUND (sizeof (LINENUM) * CHAR_BIT / 3 + 1) XTERN enum backup_type backup_type; bool ok_to_reverse (char const *, ...) __attribute__ ((format (printf, 1, 2))); void ask (char const *, ...) __attribute__ ((format (printf, 1, 2))); void say (char const *, ...) __attribute__ ((format (printf, 1, 2))); void fatal (char const *, ...) __attribute__ ((noreturn, format (printf, 1, 2))); void pfatal (char const *, ...) __attribute__ ((noreturn, format (printf, 1, 2))); char *fetchname (char *, int, time_t *); char *savebuf (char const *, size_t); char *savestr (char const *); char const *version_controller (char const *, bool, struct stat const *, char **, char **); bool version_get (char const *, char const *, bool, bool, char const *, struct stat *); int create_file (char const *, int, mode_t); int systemic (char const *); char *format_linenum (char[LINENUM_LENGTH_BOUND + 1], LINENUM); void Fseek (FILE *, file_offset, int); void copy_file (char const *, char const *, int, mode_t); void exit_with_signal (int) __attribute__ ((noreturn)); void ignore_signals (void); void init_time (void); void memory_fatal (void) __attribute__ ((noreturn)); void move_file (char const *, int volatile *, char *, mode_t, bool); void read_fatal (void) __attribute__ ((noreturn)); void remove_prefix (char *, size_t); void removedirs (char *); void set_signals (bool); void write_fatal (void) __attribute__ ((noreturn));
/* xalloc.h -- malloc with out-of-memory checking Copyright (C) 1990-1998, 1999, 2000 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef XALLOC_H_ # define XALLOC_H_ # ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif # endif # ifndef __attribute__ # if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__ # define __attribute__(x) # endif # endif # ifndef ATTRIBUTE_NORETURN # define ATTRIBUTE_NORETURN __attribute__ ((__noreturn__)) # endif /* Exit value when the requested amount of memory is not available. It is initialized to EXIT_FAILURE, but the caller may set it to some other value. */ extern int xalloc_exit_failure; /* If this pointer is non-zero, run the specified function upon each allocation failure. It is initialized to zero. */ extern void (*xalloc_fail_func) PARAMS ((void)); /* If XALLOC_FAIL_FUNC is undefined or a function that returns, this message is output. It is translated via gettext. Its value is "memory exhausted". */ extern char const xalloc_msg_memory_exhausted[]; /* This function is always triggered when memory is exhausted. It is in charge of honoring the three previous items. This is the function to call when one wants the program to die because of a memory allocation failure. */ extern void xalloc_die PARAMS ((void)) ATTRIBUTE_NORETURN; void *xmalloc PARAMS ((size_t n)); void *xcalloc PARAMS ((size_t n, size_t s)); void *xrealloc PARAMS ((void *p, size_t n)); char *xstrdup PARAMS ((const char *str)); # define XMALLOC(Type, N_items) ((Type *) xmalloc (sizeof (Type) * (N_items))) # define XCALLOC(Type, N_items) ((Type *) xcalloc (sizeof (Type), (N_items))) # define XREALLOC(Ptr, Type, N_items) \ ((Type *) xrealloc ((void *) (Ptr), sizeof (Type) * (N_items))) /* Declare and alloc memory for VAR of type TYPE. */ # define NEW(Type, Var) Type *(Var) = XMALLOC (Type, 1) /* Free VAR only if non NULL. */ # define XFREE(Var) \ do { \ if (Var) \ free (Var); \ } while (0) /* Return a pointer to a malloc'ed copy of the array SRC of NUM elements. */ # define CCLONE(Src, Num) \ (memcpy (xmalloc (sizeof (*Src) * (Num)), (Src), sizeof (*Src) * (Num))) /* Return a malloc'ed copy of SRC. */ # define CLONE(Src) CCLONE (Src, 1) #endif /* !XALLOC_H_ */
/* inputting files to be patched */ /* $Id: inp.h,v 1.7 2003/05/20 14:05:22 eggert Exp $ */ /* Copyright (C) 1986, 1988 Larry Wall Copyright (C) 1991, 1992, 1993, 1997, 1998, 1999, 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ XTERN LINENUM input_lines; /* how long is input file in lines */ char const *ifetch (LINENUM, bool, size_t *); void get_input_file (char const *, char const *); void re_input (void); void scan_input (char *);
/* Convert struct partime into time_t. */ /* Copyright 1992, 1993, 1994, 1995, 1997 Paul Eggert Distributed under license by the Free Software Foundation, Inc. This file is part of RCS. RCS is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. RCS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with RCS; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Report problems and direct all questions to: rcs-bugs@cs.purdue.edu */ /* For maximum portability, use only localtime and gmtime. Make no assumptions about the time_t epoch or the range of time_t values. Avoid mktime because it's not universal and because there's no easy, portable way for mktime to yield the inverse of gmtime. */ #if has_conf_h # include <conf.h> #else # if HAVE_CONFIG_H # include <config.h> # else # ifndef __STDC__ # define const # endif # endif /* MIPS RISCOS4.52 defines time_t in <sys/types.h> not <time.h>. */ # include <sys/types.h> # if HAVE_LIMITS_H # include <limits.h> # endif # ifndef LONG_MIN # define LONG_MIN (-1-2147483647L) # endif # if STDC_HEADERS # include <stdlib.h> # endif # include <time.h> # ifdef __STDC__ # define P(x) x # else # define P(x) () # endif #endif #include <partime.h> #include <maketime.h> char const maket_id[] = "$Id: maketime.c,v 5.17 1999/08/29 11:12:37 eggert Exp $"; static int isleap P ((int)); static int month_days P ((struct tm const *)); static time_t maketime P ((struct partime const *, time_t)); /* Suppose A1 + B1 = SUM1, using 2's complement arithmetic ignoring overflow. Suppose A, B and SUM have the same respective signs as A1, B1, and SUM1. Then this yields nonzero if overflow occurred during the addition. Overflow occurs if A and B have the same sign, but A and SUM differ in sign. Use `^' to test whether signs differ, and `< 0' to isolate the sign. */ #define overflow_sum_sign(a, b, sum) ((~((a) ^ (b)) & ((a) ^ (sum))) < 0) /* Quotient and remainder when dividing A by B, truncating towards minus infinity, where B is positive. */ #define DIV(a, b) ((a) / (b) - ((a) % (b) < 0)) #define MOD(a, b) ((a) % (b) + (b) * ((a) % (b) < 0)) /* Number of days in 400 consecutive Gregorian years. */ #define Y400_DAYS (365 * 400L + 100 - 4 + 1) /* Number of years to add to tm_year to get Gregorian year. */ #define TM_YEAR_ORIGIN 1900 static int isleap (y) int y; { return (y & 3) == 0 && (y % 100 != 0 || y % 400 == 0); } /* days in year before start of months 0-12 */ static int const month_yday[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }; /* Yield the number of days in TM's month. */ static int month_days (tm) struct tm const *tm; { int m = tm->tm_mon; return (month_yday[m + 1] - month_yday[m] + (m == 1 && isleap (tm->tm_year + TM_YEAR_ORIGIN))); } /* Convert UNIXTIME to struct tm form. Use gmtime if available and if !LOCALZONE, localtime otherwise. */ struct tm * time2tm (unixtime, localzone) time_t unixtime; int localzone; { struct tm *tm; #ifdef TZ_is_unset static char const *TZ; if (!TZ && !(TZ = getenv ("TZ"))) TZ_is_unset ("The TZ environment variable is not set; please set it to your timezone"); #endif if (localzone || !(tm = gmtime (&unixtime))) tm = localtime (&unixtime); return tm; } /* Yield A - B, measured in seconds. */ time_t difftm (a, b) struct tm const *a; struct tm const *b; { int ay = a->tm_year + (TM_YEAR_ORIGIN - 1); int by = b->tm_year + (TM_YEAR_ORIGIN - 1); int ac = DIV (ay, 100); int bc = DIV (by, 100); int difference_in_day_of_year = a->tm_yday - b->tm_yday; int intervening_leap_days = (((ay >> 2) - (by >> 2)) - (ac - bc) + ((ac >> 2) - (bc >> 2))); time_t difference_in_years = ay - by; time_t difference_in_days = (difference_in_years * 365 + (intervening_leap_days + difference_in_day_of_year)); return (((((difference_in_days * 24 + (a->tm_hour - b->tm_hour)) * 60) + (a->tm_min - b->tm_min)) * 60) + (a->tm_sec - b->tm_sec)); } /* Adjust time T by adding SECONDS. The absolute value of SECONDS cannot exceed 59 * INT_MAX, and also cannot exceed one month's worth of seconds; this is enough to handle any POSIX or real-life daylight-saving offset. Adjust only T's year, mon, mday, hour, min and sec members; plus adjust wday if it is defined. */ void adjzone (t, seconds) register struct tm *t; long seconds; { int days = 0; /* This code can be off by a second if SECONDS is not a multiple of 60, if T is local time, and if a leap second happens during this minute. But this bug has never occurred, and most likely will not ever occur. Liberia, the last country for which SECONDS % 60 was nonzero, switched to UTC in May 1972; the first leap second was in June 1972. */ int leap_second = t->tm_sec == 60; long sec = seconds + (t->tm_sec - leap_second); if (sec < 0) { if ((t->tm_min -= (59 - sec) / 60) < 0 && (t->tm_hour -= (59 - t->tm_min) / 60) < 0) { days = - ((23 - t->tm_hour) / 24); if ((t->tm_mday += days) <= 0) { if (--t->tm_mon < 0) { --t->tm_year; t->tm_mon = 11; } t->tm_mday += month_days (t); } } } else { if (60 <= (t->tm_min += sec / 60) && (24 <= (t->tm_hour += t->tm_min / 60))) { days = t->tm_hour / 24; if (month_days (t) < (t->tm_mday += days)) { if (11 < ++t->tm_mon) { ++t->tm_year; t->tm_mon = 0; } t->tm_mday = 1; } } } if (TM_DEFINED (t->tm_wday)) t->tm_wday = MOD (t->tm_wday + days, 7); t->tm_hour = MOD (t->tm_hour, 24); t->tm_min = MOD (t->tm_min, 60); t->tm_sec = (int) MOD (sec, 60) + leap_second; } /* Convert TM to time_t, using localtime if LOCALZONE and gmtime otherwise. Use only TM's year, mon, mday, hour, min, and sec members. Ignore TM's old tm_yday and tm_wday, but fill in their correct values. Yield -1 on failure (e.g. a member out of range). POSIX 1003.1 doesn't allow leap seconds, but some implementations have them anyway, so allow them if localtime/gmtime does. */ time_t tm2time (tm, localzone) struct tm *tm; int localzone; { /* Cache the most recent t,tm pairs; 1 for gmtime, 1 for localtime. */ static time_t t_cache[2]; static struct tm tm_cache[2]; time_t d, gt; struct tm const *gtm; /* The maximum number of iterations should be enough to handle any combinations of leap seconds, time zone rule changes, and solar time. 4 is probably enough; we use a bigger number just to be safe. */ int remaining_tries = 8; /* Avoid subscript errors. */ if (12 <= (unsigned) tm->tm_mon) return -1; tm->tm_yday = month_yday[tm->tm_mon] + tm->tm_mday - (tm->tm_mon < 2 || !isleap (tm->tm_year + TM_YEAR_ORIGIN)); /* Make a first guess. */ gt = t_cache[localzone]; gtm = gt ? &tm_cache[localzone] : time2tm (gt, localzone); /* Repeatedly use the error from the guess to improve the guess. */ while ((d = difftm (tm, gtm)) != 0) { if (--remaining_tries == 0) return -1; gt += d; gtm = time2tm (gt, localzone); } /* Check that the guess actually matches; overflow can cause difftm to yield 0 even on differing times, or tm may have members out of range (e.g. bad leap seconds). */ #define TM_DIFFER(a,b) \ ( \ ((a)->tm_year ^ (b)->tm_year) | \ ((a)->tm_mon ^ (b)->tm_mon) | \ ((a)->tm_mday ^ (b)->tm_mday) | \ ((a)->tm_hour ^ (b)->tm_hour) | \ ((a)->tm_min ^ (b)->tm_min) | \ ((a)->tm_sec ^ (b)->tm_sec) \ ) if (TM_DIFFER (tm, gtm)) { /* If gt is a leap second, try gt+1; if it is one greater than a leap second, try gt-1; otherwise, it doesn't matter. Leap seconds always fall at month end. */ int yd = tm->tm_year - gtm->tm_year; gt += yd + (yd ? 0 : tm->tm_mon - gtm->tm_mon); gtm = time2tm (gt, localzone); if (TM_DIFFER (tm, gtm)) return -1; } t_cache[localzone] = gt; tm_cache[localzone] = *gtm; tm->tm_wday = gtm->tm_wday; return gt; } /* Check *PT and convert it to time_t. If it is incompletely specified, use DEFAULT_TIME to fill it out. Use localtime if PT->zone is the special value TM_LOCAL_ZONE. Yield -1 on failure. ISO 8601 day-of-year and week numbers are not yet supported. */ static time_t maketime (pt, default_time) struct partime const *pt; time_t default_time; { int localzone, wday, year; struct tm tm; struct tm *tm0 = 0; time_t r; int use_ordinal_day; tm0 = 0; /* Keep gcc -Wall happy. */ localzone = pt->zone == TM_LOCAL_ZONE; tm = pt->tm; year = tm.tm_year; wday = tm.tm_wday; use_ordinal_day = (!TM_DEFINED (tm.tm_mday) && TM_DEFINED (wday) && TM_DEFINED (pt->wday_ordinal)); if (use_ordinal_day || TM_DEFINED (pt->ymodulus) || !TM_DEFINED (year)) { /* Get tm corresponding to default time. */ tm0 = time2tm (default_time, localzone); if (!localzone) adjzone (tm0, pt->zone); } if (use_ordinal_day) tm.tm_mday = (tm0->tm_mday + ((wday - tm0->tm_wday + 7) % 7 + 7 * (pt->wday_ordinal - (pt->wday_ordinal != 0)))); if (TM_DEFINED (pt->ymodulus)) { /* Yield a year closest to the default that has the given modulus. */ int year0 = tm0->tm_year + TM_YEAR_ORIGIN; int y0 = MOD (year0, pt->ymodulus); int d = 2 * (year - y0); year += (((year0 - y0) / pt->ymodulus + (pt->ymodulus < d ? -1 : d < -pt->ymodulus)) * pt->ymodulus); } else if (!TM_DEFINED (year)) { /* Set default year, month, day from current time. */ year = tm0->tm_year + TM_YEAR_ORIGIN; if (!TM_DEFINED (tm.tm_mon)) { tm.tm_mon = tm0->tm_mon; if (!TM_DEFINED (tm.tm_mday)) tm.tm_mday = tm0->tm_mday; } } /* Set remaining default fields to be their minimum values. */ if (!TM_DEFINED (tm.tm_mon)) tm.tm_mon = 0; if (!TM_DEFINED (tm.tm_mday)) tm.tm_mday = 1; if (!TM_DEFINED (tm.tm_hour)) tm.tm_hour = 0; if (!TM_DEFINED (tm.tm_min)) tm.tm_min = 0; if (!TM_DEFINED (tm.tm_sec)) tm.tm_sec = 0; tm.tm_year = year - TM_YEAR_ORIGIN; if ((year < tm.tm_year) != (TM_YEAR_ORIGIN < 0)) return -1; if (!localzone) { adjzone (&tm, -pt->zone); wday = tm.tm_wday; } /* Convert and fill in the rest of the tm. */ r = tm2time (&tm, localzone); if (r == -1) return r; /* Check weekday. */ if (TM_DEFINED (wday) && wday != tm.tm_wday) return -1; /* Add relative time, except for seconds. We handle seconds separately, at the end, so that leap seconds are handled properly. */ if (pt->tmr.tm_year | pt->tmr.tm_mon | pt->tmr.tm_mday | pt->tmr.tm_hour | pt->tmr.tm_min) { int years = tm.tm_year + pt->tmr.tm_year; int mons = tm.tm_mon + pt->tmr.tm_mon; int mdays = tm.tm_mday + pt->tmr.tm_mday; int hours = tm.tm_hour + pt->tmr.tm_hour; int mins = tm.tm_min + pt->tmr.tm_min; int carried_hours = DIV (mins, 60); int hours1 = hours + carried_hours; int carried_days = DIV (hours1, 24); int mdays1 = mdays + carried_days; int mon0 = MOD (mons, 12); int carried_years0 = DIV (mons, 12); int year0 = years + carried_years0; int yday0 = (month_yday[mon0] - (mon0 < 2 || !isleap (year0 + TM_YEAR_ORIGIN))); int yday1 = yday0 + mdays1; int carried_years1 = DIV (yday1, Y400_DAYS) * 400; int year1 = year0 + carried_years1; int yday2 = MOD (yday1, Y400_DAYS); int leap; if (overflow_sum_sign (tm.tm_year, pt->tmr.tm_year, years) | overflow_sum_sign (tm.tm_mon, pt->tmr.tm_mon, mons) | overflow_sum_sign (tm.tm_mday, pt->tmr.tm_mday, mdays) | overflow_sum_sign (tm.tm_hour, pt->tmr.tm_hour, hours) | overflow_sum_sign (tm.tm_min, pt->tmr.tm_min, mins) | overflow_sum_sign (hours, carried_hours, hours1) | overflow_sum_sign (mdays, carried_days, mdays1) | overflow_sum_sign (years, carried_years0, year0) | overflow_sum_sign (yday0, mdays1, yday1) | overflow_sum_sign (year0, carried_years1, year1)) return -1; for (;;) { int days_per_year = 365 + (leap = isleap (year1 + TM_YEAR_ORIGIN)); if (yday2 < days_per_year) break; yday2 -= days_per_year; year1++; } tm.tm_year = year1; { int mon; for (mon = 11; (tm.tm_mday = (yday2 - month_yday[mon] + (mon < 2 || !leap))) <= 0; mon--) continue; tm.tm_mon = mon; } tm.tm_hour = MOD (hours1, 24); tm.tm_min = MOD (mins, 60); r = tm2time (&tm, localzone); if (r == -1) return r; } /* Add the seconds' part of relative time. */ { time_t rs = r + pt->tmr.tm_sec; if ((pt->tmr.tm_sec < 0) != (rs < r)) return -1; return rs; } } /* Parse a free-format date in *SOURCE, yielding a Unix format time. Update *SOURCE to point to the first character after the date. If *SOURCE is missing some information, take defaults from DEFAULT_TIME and DEFAULT_ZONE. *SOURCE may even be the empty string or an immediately invalid string, in which case the default time and zone is used. Return (time_t) -1 if the time is invalid or cannot be represented. */ time_t str2time (source, default_time, default_zone) char const **source; time_t default_time; long default_zone; { struct partime pt; *source = partime (*source, &pt); if (pt.zone == TM_UNDEFINED_ZONE) pt.zone = default_zone; return maketime (&pt, default_time); } #ifdef TEST #include <stdio.h> int main (argc, argv) int argc; char **argv; { time_t default_time = time ((time_t *) 0); long default_zone = argv[1] ? atol (argv[1]) : TM_LOCAL_ZONE; char buf[1000]; while (fgets (buf, sizeof (buf), stdin)) { char const *p = buf; time_t t = str2time (&p, default_time, default_zone); printf ("`%.*s' -> %s", (int) (p - buf - (p[0] == '\0' && p[-1] == '\n')), buf, asctime ((argv[1] ? gmtime : localtime) (&t))); } return 0; } #endif
/* Parse a string, yielding a struct partime that describes it. */ /* Copyright (C) 1993, 1994, 1995, 1997, 2002 Paul Eggert Distributed under license by the Free Software Foundation, Inc. This file is part of RCS. RCS is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. RCS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with RCS; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Report problems and direct all questions to: rcs-bugs@cs.purdue.edu */ #if has_conf_h # include <conf.h> #else # if HAVE_CONFIG_H # include <config.h> # else # ifndef __STDC__ # define const # endif # endif # if HAVE_LIMITS_H # include <limits.h> # endif # ifndef LONG_MIN # define LONG_MIN (-1-2147483647L) # endif # if HAVE_STDDEF_H # include <stddef.h> # endif # if STDC_HEADERS # include <stdlib.h> # endif # include <time.h> # ifdef __STDC__ # define P(x) x # else # define P(x) () # endif #endif #ifndef offsetof #define offsetof(aggregate, member) ((size_t) &((aggregate *) 0)->member) #endif #include <ctype.h> #if STDC_HEADERS # define CTYPE_DOMAIN(c) 1 #else # define CTYPE_DOMAIN(c) ((unsigned) (c) <= 0177) #endif #define ISALNUM(c) (CTYPE_DOMAIN (c) && isalnum (c)) #define ISALPHA(c) (CTYPE_DOMAIN (c) && isalpha (c)) #define ISSPACE(c) (CTYPE_DOMAIN (c) && isspace (c)) #define ISUPPER(c) (CTYPE_DOMAIN (c) && isupper (c)) #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9) #include <partime.h> char const partime_id[] = "$Id: partime.c,v 1.2 2002/02/18 07:42:58 eggert Exp $"; /* Lookup tables for names of months, weekdays, time zones. */ #define NAME_LENGTH_MAXIMUM 4 struct name_val { char name[NAME_LENGTH_MAXIMUM]; int val; }; static char const *parse_decimal P ((char const *, int, int, int, int, int *, int *)); static char const *parse_fixed P ((char const *, int, int *)); static char const *parse_pattern_letter P ((char const *, int, struct partime *)); static char const *parse_prefix P ((char const *, char const **, struct partime *)); static char const *parse_ranged P ((char const *, int, int, int, int *)); static char const *parse_varying P ((char const *, int *)); static int lookup P ((char const *, struct name_val const[])); static int merge_partime P ((struct partime *, struct partime const *)); static void undefine P ((struct partime *)); static struct name_val const month_names[] = { {"jan", 0}, {"feb", 1}, {"mar", 2}, {"apr", 3}, {"may", 4}, {"jun", 5}, {"jul", 6}, {"aug", 7}, {"sep", 8}, {"oct", 9}, {"nov", 10}, {"dec", 11}, {"", TM_UNDEFINED} }; static struct name_val const weekday_names[] = { {"sun", 0}, {"mon", 1}, {"tue", 2}, {"wed", 3}, {"thu", 4}, {"fri", 5}, {"sat", 6}, {"", TM_UNDEFINED} }; #define RELATIVE_CONS(member, multiplier) \ (offsetof (struct tm, member) + (multiplier) * sizeof (struct tm)) #define RELATIVE_OFFSET(c) ((c) % sizeof (struct tm)) #define RELATIVE_MULTIPLIER(c) ((c) / sizeof (struct tm)) static struct name_val const relative_units[] = { {"year", RELATIVE_CONS (tm_year, 1) }, {"mont", RELATIVE_CONS (tm_mon , 1) }, {"fort", RELATIVE_CONS (tm_mday, 14) }, {"week", RELATIVE_CONS (tm_mday, 7) }, {"day" , RELATIVE_CONS (tm_mday, 1) }, {"hour", RELATIVE_CONS (tm_hour, 1) }, {"min" , RELATIVE_CONS (tm_min , 1) }, {"sec" , RELATIVE_CONS (tm_sec , 1) }, {"", TM_UNDEFINED} }; static struct name_val const ago[] = { {"ago", 0}, {"", TM_UNDEFINED} }; static struct name_val const dst_names[] = { {"dst", 1}, {"", 0} }; #define hr60nonnegative(t) ((t)/100 * 60 + (t)%100) #define hr60(t) ((t) < 0 ? - hr60nonnegative (-(t)) : hr60nonnegative (t)) #define zs(t, s) {s, hr60 (t)} #define zd(t, s, d) zs (t, s), zs ((t) + 100, d) static struct name_val const zone_names[] = { zs (-1000, "hst"), /* Hawaii */ zd (-1000, "hast", "hadt"), /* Hawaii-Aleutian */ zd (- 900, "akst", "akdt"), /* Alaska */ zd (- 800, "pst" , "pdt" ), /* Pacific */ zd (- 700, "mst" , "mdt" ), /* Mountain */ zd (- 600, "cst" , "cdt" ), /* Central */ zd (- 500, "est" , "edt" ), /* Eastern */ zd (- 400, "ast" , "adt" ), /* Atlantic */ zd (- 330, "nst" , "ndt" ), /* Newfoundland */ zs ( 000, "utc" ), /* Coordinated Universal */ zs ( 000, "uct" ), /* " */ zs ( 000, "cut" ), /* " */ zs ( 000, "ut"), /* Universal */ zs ( 000, "z"), /* Zulu (required by ISO 8601) */ zd ( 000, "gmt" , "bst" ), /* Greenwich Mean, British Summer */ zd ( 000, "wet" , "west"), /* Western European */ zd ( 100, "cet" , "cest"), /* Central European */ zd ( 100, "met" , "mest"), /* Middle European (bug in old tz versions) */ zd ( 100, "mez" , "mesz"), /* Mittel-Europaeische Zeit */ zd ( 200, "eet" , "eest"), /* Eastern European */ zs ( 530, "ist" ), /* India */ zd ( 900, "jst" , "jdt" ), /* Japan */ zd ( 900, "kst" , "kdt" ), /* Korea */ zd ( 1200, "nzst", "nzdt"), /* New Zealand */ {"lt", 1}, #if 0 /* The following names are duplicates or are not well attested. It's not worth keeping a complete list, since alphabetic time zone names are deprecated and there are lots more where these came from. */ zs (-1100, "sst" ), /* Samoan */ zd (- 900, "yst" , "ydt" ), /* Yukon - name is no longer used */ zd (- 500, "ast" , "adt" ), /* Acre */ zd (- 400, "wst" , "wdt" ), /* Western Brazil */ zd (- 400, "cst" , "cdt" ), /* Chile */ zd (- 200, "fst" , "fdt" ), /* Fernando de Noronha */ zs ( 000, "wat" ), /* West African */ zs ( 100, "cat" ), /* Central African */ zs ( 200, "sat" ), /* South African */ zd ( 200, "ist" , "idt" ), /* Israel */ zs ( 300, "eat" ), /* East African */ zd ( 300, "msk" , "msd" ), /* Moscow */ zd ( 330, "ist" , "idt" ), /* Iran */ zs ( 800, "hkt" ), /* Hong Kong */ zs ( 800, "sgt" ), /* Singapore */ zd ( 800, "cst" , "cdt" ), /* China */ zd ( 800, "wst" , "wst" ), /* Western Australia */ zd ( 930, "cst" , "cst" ), /* Central Australia */ zs ( 1000, "gst" ), /* Guam */ zd ( 1000, "est" , "est" ), /* Eastern Australia */ #endif {"", -1} }; /* Look for a prefix of S in TABLE, returning val for first matching entry. */ static int lookup (s, table) char const *s; struct name_val const table[]; { int j; char buf[NAME_LENGTH_MAXIMUM]; for (j = 0; j < NAME_LENGTH_MAXIMUM; j++) { unsigned char c = *s; if (! ISALPHA (c)) { buf[j] = '\0'; break; } buf[j] = ISUPPER (c) ? tolower (c) : c; s++; s += *s == '.'; } for (;; table++) for (j = 0; ; j++) if (j == NAME_LENGTH_MAXIMUM || ! table[0].name[j]) return table[0].val; else if (buf[j] != table[0].name[j]) break; } /* Set *T to ``undefined'' values. */ static void undefine (t) struct partime *t; { t->tm.tm_sec = t->tm.tm_min = t->tm.tm_hour = t->tm.tm_mday = t->tm.tm_mon = t->tm.tm_year = t->tm.tm_wday = t->tm.tm_yday = t->wday_ordinal = t->ymodulus = t->yweek = TM_UNDEFINED; t->tmr.tm_sec = t->tmr.tm_min = t->tmr.tm_hour = t->tmr.tm_mday = t->tmr.tm_mon = t->tmr.tm_year = 0; t->zone = TM_UNDEFINED_ZONE; } /* Patterns to look for in a time string. Order is important: we look for the first matching pattern whose values do not contradict values that we already know about. See `parse_pattern_letter' below for the meaning of the pattern codes. */ static char const time_patterns[] = { /* Traditional patterns come first, to prevent an ISO 8601 format from misinterpreting their prefixes. */ /* RFC 822, extended */ 'E', '_', 'N', '_', 'y', '$', 0, 'x', 0, /* traditional */ '4', '_', 'M', '_', 'D', '_', 'h', '_', 'm', '_', 's', '$', 0, 'R', '_', 'M', '_', 'D', '_', 'h', '_', 'm', '_', 's', '$', 0, 'E', '_', 'N', 0, 'N', '_', 'E', '_', 'y', ';', 0, 'N', '_', 'E', ';', 0, 'N', 0, 't', ':', 'm', ':', 's', '_', 'A', 0, 't', ':', 'm', '_', 'A', 0, 't', '_', 'A', 0, /* traditional get_date */ 'i', '_', 'x', 0, 'Y', '/', 'n', '/', 'E', ';', 0, 'n', '/', 'E', '/', 'y', ';', 0, 'n', '/', 'E', ';', 0, 'u', 0, /* ISO 8601:1988 formats, generalized a bit. */ 'y', '-', 'M', '-', 'D', '$', 0, '4', 'M', 'D', '$', 0, 'Y', '-', 'M', '$', 0, 'R', 'M', 'D', '$', 0, '-', 'R', '=', 'M', '$', 0, '-', 'R', '$', 0, '-', '-', 'M', '=', 'D', '$', 0, 'M', '=', 'D', 'T', 0, '-', '-', 'M', '$', 0, '-', '-', '-', 'D', '$', 0, 'D', 'T', 0, 'Y', '-', 'd', '$', 0, '4', 'd', '$', 0, 'R', '=', 'd', '$', 0, '-', 'd', '$', 0, 'd', 'T', 0, 'y', '-', 'W', '-', 'X', 0, 'y', 'W', 'X', 0, 'y', '=', 'W', 0, '-', 'r', '-', 'W', '-', 'X', 0, 'r', '-', 'W', '-', 'X', 'T', 0, '-', 'r', 'W', 'X', 0, 'r', 'W', 'X', 'T', 0, '-', 'W', '=', 'X', 0, 'W', '=', 'X', 'T', 0, '-', 'W', 0, '-', 'w', '-', 'X', 0, 'w', '-', 'X', 'T', 0, '-', '-', '-', 'X', '$', 0, 'X', 'T', 0, '4', '$', 0, 'T', 0, 'h', ':', 'm', ':', 's', '$', 0, 'h', 'm', 's', '$', 0, 'h', ':', 'L', '$', 0, 'h', 'L', '$', 0, 'H', '$', 0, '-', 'm', ':', 's', '$', 0, '-', 'm', 's', '$', 0, '-', 'L', '$', 0, '-', '-', 's', '$', 0, 'Y', 0, 'Z', 0, 0 }; /* Parse an initial prefix of STR according to *PATTERNS, setting *T. Return the first character after the prefix, or 0 if it couldn't be parsed. *PATTERNS is a character array containing one pattern string after another; it is terminated by an empty string. If success, set *PATTERNS to the next pattern to try. Set *PATTERNS to 0 if we know there are no more patterns to try; if *PATTERNS is initially 0, give up immediately. */ static char const * parse_prefix (str, patterns, t) char const *str; char const **patterns; struct partime *t; { char const *pat = *patterns; unsigned char c; if (! pat) return 0; /* Remove initial noise. */ while (! ISALNUM (c = *str) && c != '-' && c != '+') { if (! c) { undefine (t); *patterns = 0; return str; } str++; } /* Try a pattern until one succeeds. */ while (*pat) { char const *s = str; undefine (t); do { if (! (c = *pat++)) { *patterns = pat; return s; } } while ((s = parse_pattern_letter (s, c, t)) != 0); while (*pat++) continue; } return 0; } /* Parse an initial prefix of S of length DIGITS; it must be a number. Store the parsed number into *RES. Return the first character after the prefix, or 0 if it wasn't parsed. */ static char const * parse_fixed (s, digits, res) char const *s; int digits, *res; { int n = 0; char const *lim = s + digits; while (s < lim) { unsigned d = *s++ - '0'; if (9 < d) return 0; n = 10 * n + d; } *res = n; return s; } /* Parse a possibly empty initial prefix of S. Store the parsed number into *RES. Return the first character after the prefix. */ static char const * parse_varying (s, res) char const *s; int *res; { int n = 0; for (;;) { unsigned d = *s - '0'; if (9 < d) break; s++; n = 10 * n + d; } *res = n; return s; } /* Parse an initial prefix of S of length DIGITS; it must be a number in the range LO through HI. Store the parsed number into *RES. Return the first character after the prefix, or 0 if it wasn't parsed. */ static char const * parse_ranged (s, digits, lo, hi, res) char const *s; int digits, lo, hi, *res; { s = parse_fixed (s, digits, res); return s && lo <= *res && *res <= hi ? s : 0; } /* Parse an initial prefix of S of length DIGITS; it must be a number in the range LO through HI and it may be followed by a fraction to be computed using RESOLUTION. Store the parsed number into *RES; store the fraction times RESOLUTION, rounded to the nearest integer, into *FRES. Return the first character after the prefix, or 0 if it wasn't parsed. */ static char const * parse_decimal (s, digits, lo, hi, resolution, res, fres) char const *s; int digits, lo, hi, resolution, *res, *fres; { s = parse_fixed (s, digits, res); if (s && lo <= *res && *res <= hi) { int f = 0; if ((s[0] == ',' || s[0] == '.') && ISDIGIT (s[1])) { char const *s1 = ++s; int num10 = 0, denom10 = 10, product; while (ISDIGIT (*++s)) { int d = denom10 * 10; if (d / 10 != denom10) return 0; /* overflow */ denom10 = d; } s = parse_fixed (s1, (int) (s - s1), &num10); product = num10 * resolution; f = (product + (denom10 >> 1)) / denom10; f -= f & (product % denom10 == denom10 >> 1); /* round to even */ if (f < 0 || product/resolution != num10) return 0; /* overflow */ } *fres = f; return s; } return 0; } /* Parse an initial prefix of S; it must denote a time zone. Set *ZONE to the number of seconds east of GMT, or to TM_LOCAL_ZONE if it is the local time zone. Return the first character after the prefix, or 0 if it wasn't parsed. */ char * parzone (s, zone) char const *s; long *zone; { char const *s1; char sign; int hh, mm, ss; int minutes_east_of_UTC; int trailing_DST; long offset, z; /* The formats are LT, n, n DST, nDST, no, o where n is a time zone name and o is a time zone offset of the form [-+]hh[:mm[:ss]]. */ switch (*s) { case '-': case '+': z = 0; break; default: minutes_east_of_UTC = lookup (s, zone_names); if (minutes_east_of_UTC == -1) return 0; /* Don't bother to check rest of spelling, but look for an embedded "DST". */ trailing_DST = 0; while (ISALPHA ((unsigned char) *s)) { if ((*s == 'D' || *s == 'd') && lookup (s, dst_names)) trailing_DST = 1; s++; s += *s == '.'; } /* Don't modify LT. */ if (minutes_east_of_UTC == 1) { *zone = TM_LOCAL_ZONE; return (char *) s; } z = minutes_east_of_UTC * 60L; s1 = s; /* Look for trailing "DST" or " DST". */ while (ISSPACE ((unsigned char) *s)) s++; if (lookup (s, dst_names)) { while (ISALPHA ((unsigned char) *s)) { s++; s += *s == '.'; } trailing_DST = 1; } if (trailing_DST) { *zone = z + 60*60; return (char *) s; } s = s1; switch (*s) { case '-': case '+': break; default: *zone = z; return (char *) s; } break; } sign = *s++; if (! (s = parse_ranged (s, 2, 0, 23, &hh))) return 0; mm = ss = 0; if (*s == ':') s++; if (ISDIGIT (*s)) { if (! (s = parse_ranged (s, 2, 0, 59, &mm))) return 0; if (*s == ':' && s[-3] == ':' && ISDIGIT (s[1]) && ! (s = parse_ranged (s + 1, 2, 0, 59, &ss))) return 0; } if (ISDIGIT (*s)) return 0; offset = (hh * 60 + mm) * 60L + ss; *zone = z + (sign == '-' ? -offset : offset); /* ?? Are fractions allowed here? If so, they're not implemented. */ return (char *) s; } /* Parse an initial prefix of S, matching the pattern whose code is C. Set *T accordingly. Return the first character after the prefix, or 0 if it wasn't parsed. */ static char const * parse_pattern_letter (s, c, t) char const *s; int c; struct partime *t; { char const *s0 = s; switch (c) { case '$': /* The next character must be a non-digit. */ if (ISDIGIT (*s)) return 0; break; case '-': case '/': case ':': /* These characters stand for themselves. */ if (*s++ != c) return 0; break; case '4': /* 4-digit year */ s = parse_fixed (s, 4, &t->tm.tm_year); break; case ';': /* The next character must be a non-digit, and cannot be ':'. */ if (ISDIGIT (*s) || *s == ':') return 0; break; case '=': /* optional '-' */ s += *s == '-'; break; case 'A': /* AM or PM */ /* This matches the regular expression [AaPp]\.?([Mm]\.?)?. It must not be followed by a letter or digit; otherwise it would match prefixes of strings like "PST". */ switch (*s) { case 'A': case 'a': if (t->tm.tm_hour == 12) t->tm.tm_hour = 0; break; case 'P': case 'p': if (t->tm.tm_hour != 12) t->tm.tm_hour += 12; break; default: return 0; } s++; s += *s == '.'; switch (*s) { case 'M': case 'm': s++; s += *s == '.'; break; } if (ISALNUM ((unsigned char) *s)) return 0; break; case 'D': /* day of month [01-31] */ s = parse_ranged (s, 2, 1, 31, &t->tm.tm_mday); break; case 'd': /* day of year [001-366] */ s = parse_ranged (s, 3, 1, 366, &t->tm.tm_yday); t->tm.tm_yday--; break; case 'E': /* traditional day of month [1-9, 01-31] */ s = parse_ranged (s, (ISDIGIT (s[0]) && ISDIGIT (s[1])) + 1, 1, 31, &t->tm.tm_mday); break; case 'h': /* hour [00-23] */ s = parse_ranged (s, 2, 0, 23, &t->tm.tm_hour); break; case 'H': /* hour [00-23 followed by optional fraction] */ { int frac; s = parse_decimal (s, 2, 0, 23, 60 * 60, &t->tm.tm_hour, &frac); t->tm.tm_min = frac / 60; t->tm.tm_sec = frac % 60; } break; case 'i': /* ordinal day number, e.g. "3rd" */ s = parse_varying (s, &t->wday_ordinal); if (s == s0) return 0; while (ISALPHA ((unsigned char) *s)) s++; break; case 'L': /* minute [00-59 followed by optional fraction] */ s = parse_decimal (s, 2, 0, 59, 60, &t->tm.tm_min, &t->tm.tm_sec); break; case 'm': /* minute [00-59] */ s = parse_ranged (s, 2, 0, 59, &t->tm.tm_min); break; case 'M': /* month [01-12] */ s = parse_ranged (s, 2, 1, 12, &t->tm.tm_mon); t->tm.tm_mon--; break; case 'n': /* traditional month [1-9, 01-12] */ s = parse_ranged (s, (ISDIGIT (s[0]) && ISDIGIT (s[1])) + 1, 1, 12, &t->tm.tm_mon); t->tm.tm_mon--; break; case 'N': /* month name [e.g. "Jan"] */ if (! TM_DEFINED (t->tm.tm_mon = lookup (s, month_names))) return 0; /* Don't bother to check rest of spelling. */ while (ISALPHA ((unsigned char) *s)) s++; break; case 'r': /* year % 10 (remainder in origin-0 decade) [0-9] */ s = parse_fixed (s, 1, &t->tm.tm_year); t->ymodulus = 10; break; case_R: case 'R': /* year % 100 (remainder in origin-0 century) [00-99] */ s = parse_fixed (s, 2, &t->tm.tm_year); t->ymodulus = 100; break; case 's': /* second [00-60 followed by optional fraction] */ { int frac; s = parse_decimal (s, 2, 0, 60, 1, &t->tm.tm_sec, &frac); t->tm.tm_sec += frac; } break; case 'T': /* 'T' or 't' */ switch (*s++) { case 'T': case 't': break; default: return 0; } break; case 't': /* traditional hour [1-9 or 01-12] */ s = parse_ranged (s, (ISDIGIT (s[0]) && ISDIGIT (s[1])) + 1, 1, 12, &t->tm.tm_hour); break; case 'u': /* relative unit */ { int i; int n; int negative = 0; switch (*s) { case '-': negative = 1; /* Fall through. */ case '+': s++; } if (ISDIGIT (*s)) s = parse_varying (s, &n); else if (s == s0) n = 1; else return 0; if (negative) n = -n; while (! ISALNUM ((unsigned char) *s) && *s) s++; i = lookup (s, relative_units); if (!TM_DEFINED (i)) return 0; * (int *) ((char *) &t->tmr + RELATIVE_OFFSET (i)) += n * RELATIVE_MULTIPLIER (i); while (ISALPHA ((unsigned char) *s)) s++; while (! ISALNUM ((unsigned char) *s) && *s) s++; if (TM_DEFINED (lookup (s, ago))) { t->tmr.tm_sec = - t->tmr.tm_sec; t->tmr.tm_min = - t->tmr.tm_min; t->tmr.tm_hour = - t->tmr.tm_hour; t->tmr.tm_mday = - t->tmr.tm_mday; t->tmr.tm_mon = - t->tmr.tm_mon; t->tmr.tm_year = - t->tmr.tm_year; while (ISALPHA ((unsigned char) *s)) s++; } break; } case 'w': /* 'W' or 'w' only (stands for current week) */ switch (*s++) { case 'W': case 'w': break; default: return 0; } break; case 'W': /* 'W' or 'w', followed by a week of year [00-53] */ switch (*s++) { case 'W': case 'w': break; default: return 0; } s = parse_ranged (s, 2, 0, 53, &t->yweek); break; case 'X': /* weekday (1=Mon ... 7=Sun) [1-7] */ s = parse_ranged (s, 1, 1, 7, &t->tm.tm_wday); t->tm.tm_wday--; break; case 'x': /* weekday name [e.g. "Sun"] */ if (! TM_DEFINED (t->tm.tm_wday = lookup (s, weekday_names))) return 0; /* Don't bother to check rest of spelling. */ while (ISALPHA ((unsigned char) *s)) s++; break; case 'y': /* either R or Y */ if (ISDIGIT (s[0]) && ISDIGIT (s[1]) && ! ISDIGIT (s[2])) goto case_R; /* fall into */ case 'Y': /* year in full [4 or more digits] */ s = parse_varying (s, &t->tm.tm_year); if (s - s0 < 4) return 0; break; case 'Z': /* time zone */ s = parzone (s, &t->zone); break; case '_': /* possibly empty sequence of non-alphanumerics */ while (! ISALNUM ((unsigned char) *s) && *s) s++; break; default: /* bad pattern */ return 0; } return s; } /* If there is no conflict, merge into *T the additional information in *U and return 0. Otherwise do nothing and return -1. */ static int merge_partime (t, u) struct partime *t; struct partime const *u; { # define conflict(a,b) ((a) != (b) && TM_DEFINED (a) && TM_DEFINED (b)) if (conflict (t->tm.tm_sec, u->tm.tm_sec) || conflict (t->tm.tm_min, u->tm.tm_min) || conflict (t->tm.tm_hour, u->tm.tm_hour) || conflict (t->tm.tm_mday, u->tm.tm_mday) || conflict (t->tm.tm_mon, u->tm.tm_mon) || conflict (t->tm.tm_year, u->tm.tm_year) || conflict (t->tm.tm_wday, u->tm.tm_wday) || conflict (t->tm.tm_yday, u->tm.tm_yday) || conflict (t->ymodulus, u->ymodulus) || conflict (t->yweek, u->yweek) || (t->zone != u->zone && t->zone != TM_UNDEFINED_ZONE && u->zone != TM_UNDEFINED_ZONE)) return -1; # undef conflict # define merge_(a,b) if (TM_DEFINED (b)) (a) = (b); merge_ (t->tm.tm_sec, u->tm.tm_sec) merge_ (t->tm.tm_min, u->tm.tm_min) merge_ (t->tm.tm_hour, u->tm.tm_hour) merge_ (t->tm.tm_mday, u->tm.tm_mday) merge_ (t->tm.tm_mon, u->tm.tm_mon) merge_ (t->tm.tm_year, u->tm.tm_year) merge_ (t->tm.tm_wday, u->tm.tm_wday) merge_ (t->tm.tm_yday, u->tm.tm_yday) merge_ (t->ymodulus, u->ymodulus) merge_ (t->yweek, u->yweek) # undef merge_ t->tmr.tm_sec += u->tmr.tm_sec; t->tmr.tm_min += u->tmr.tm_min; t->tmr.tm_hour += u->tmr.tm_hour; t->tmr.tm_mday += u->tmr.tm_mday; t->tmr.tm_mon += u->tmr.tm_mon; t->tmr.tm_year += u->tmr.tm_year; if (u->zone != TM_UNDEFINED_ZONE) t->zone = u->zone; return 0; } /* Parse a date/time prefix of S, putting the parsed result into *T. Return the first character after the prefix. The prefix may contain no useful information; in that case, *T will contain only undefined values. */ char * partime (s, t) char const *s; struct partime *t; { struct partime p; undefine (t); while (*s) { char const *patterns = time_patterns; char const *s1; do { if (! (s1 = parse_prefix (s, &patterns, &p))) return (char *) s; } while (merge_partime (t, &p) != 0); s = s1; } return (char *) s; }
/* patch - a program to apply diffs to original files */ /* $Id: patch.c,v 1.44 2003/05/20 13:55:03 eggert Exp $ */ /* Copyright (C) 1984, 1985, 1986, 1987, 1988 Larry Wall Copyright (C) 1989, 1990, 1991, 1992, 1993, 1997, 1998, 1999, 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #define XTERN #include <common.h> #undef XTERN #define XTERN extern #include <argmatch.h> #include <backupfile.h> #include <getopt.h> #include <inp.h> #include <pch.h> #include <quotearg.h> #include <util.h> #include <version.h> #include <xalloc.h> #if HAVE_UTIME_H # include <utime.h> #endif /* Some nonstandard hosts don't declare this structure even in <utime.h>. */ #if ! HAVE_STRUCT_UTIMBUF struct utimbuf { time_t actime; time_t modtime; }; #endif /* Output stream state. */ struct outstate { FILE *ofp; bool after_newline; bool zero_output; }; /* procedures */ static FILE *create_output_file (char const *, int); static LINENUM locate_hunk (LINENUM); static bool apply_hunk (struct outstate *, LINENUM); static bool copy_till (struct outstate *, LINENUM); static bool patch_match (LINENUM, LINENUM, LINENUM, LINENUM); static bool similar (char const *, size_t, char const *, size_t); static bool spew_output (struct outstate *); static char const *make_temp (char); static int numeric_string (char const *, bool, char const *); static void abort_hunk (void); static void cleanup (void); static void get_some_switches (void); static void init_output (char const *, int, struct outstate *); static void init_reject (void); static void reinitialize_almost_everything (void); static void remove_if_needed (char const *, int volatile *); static void usage (FILE *, int) __attribute__((noreturn)); static bool make_backups; static bool backup_if_mismatch; static char const *version_control; static char const *version_control_context; static bool remove_empty_files; /* true if -R was specified on command line. */ static bool reverse_flag_specified; /* how many input lines have been irretractably output */ static LINENUM last_frozen_line; static char const *do_defines; /* symbol to patch using ifdef, ifndef, etc. */ static char const if_defined[] = "\n#ifdef %s\n"; static char const not_defined[] = "\n#ifndef %s\n"; static char const else_defined[] = "\n#else\n"; static char const end_defined[] = "\n#endif\n"; static int Argc; static char * const *Argv; static FILE *rejfp; /* reject file pointer */ static char const *patchname; static char *rejname; static char const * volatile TMPREJNAME; static int volatile TMPREJNAME_needs_removal; static LINENUM last_offset; static LINENUM maxfuzz = 2; static char serrbuf[BUFSIZ]; /* Apply a set of diffs as appropriate. */ int main (int argc, char **argv) { char const *val; bool somefailed = false; struct outstate outstate; char numbuf[LINENUM_LENGTH_BOUND + 1]; xalloc_exit_failure = 2; program_name = argv[0]; init_time (); setbuf(stderr, serrbuf); xalloc_fail_func = memory_fatal; bufsize = 8 * 1024; buf = xmalloc (bufsize); strippath = -1; val = getenv ("QUOTING_STYLE"); { int i = val ? argmatch (val, quoting_style_args, 0, 0) : -1; set_quoting_style ((struct quoting_options *) 0, i < 0 ? shell_quoting_style : (enum quoting_style) i); } posixly_correct = getenv ("POSIXLY_CORRECT") != 0; backup_if_mismatch = ! posixly_correct; patch_get = ((val = getenv ("PATCH_GET")) ? numeric_string (val, true, "PATCH_GET value") : posixly_correct - 1); val = getenv ("SIMPLE_BACKUP_SUFFIX"); simple_backup_suffix = val && *val ? val : ".orig"; if ((version_control = getenv ("PATCH_VERSION_CONTROL"))) version_control_context = "$PATCH_VERSION_CONTROL"; else if ((version_control = getenv ("VERSION_CONTROL"))) version_control_context = "$VERSION_CONTROL"; /* Cons up the names of the global temporary files. Do this before `cleanup' can possibly be called (e.g. by `pfatal'). */ TMPOUTNAME = make_temp ('o'); TMPINNAME = make_temp ('i'); TMPREJNAME = make_temp ('r'); TMPPATNAME = make_temp ('p'); /* parse switches */ Argc = argc; Argv = argv; get_some_switches(); if (make_backups | backup_if_mismatch) backup_type = get_version (version_control_context, version_control); init_output (outfile, 0, &outstate); /* Make sure we clean up in case of disaster. */ set_signals (false); for ( open_patch_file (patchname); there_is_another_patch(); reinitialize_almost_everything() ) { /* for each patch in patch file */ int hunk = 0; int failed = 0; bool mismatch = false; char *outname = outfile ? outfile : inname; if (!skip_rest_of_patch) get_input_file (inname, outname); if (diff_type == ED_DIFF) { outstate.zero_output = false; somefailed |= skip_rest_of_patch; do_ed_script (outstate.ofp); if (! dry_run && ! outfile && ! skip_rest_of_patch) { struct stat statbuf; if (stat (TMPOUTNAME, &statbuf) != 0) pfatal ("%s", TMPOUTNAME); outstate.zero_output = statbuf.st_size == 0; } } else { int got_hunk; bool apply_anyway = false; /* initialize the patched file */ if (! skip_rest_of_patch && ! outfile) { int exclusive = TMPOUTNAME_needs_removal ? 0 : O_EXCL; TMPOUTNAME_needs_removal = 1; init_output (TMPOUTNAME, exclusive, &outstate); } /* initialize reject file */ init_reject (); /* find out where all the lines are */ if (!skip_rest_of_patch) scan_input (inname); /* from here on, open no standard i/o files, because malloc */ /* might misfire and we can't catch it easily */ /* apply each hunk of patch */ while (0 < (got_hunk = another_hunk (diff_type, reverse))) { LINENUM where = 0; /* Pacify `gcc -Wall'. */ LINENUM newwhere; LINENUM fuzz = 0; LINENUM prefix_context = pch_prefix_context (); LINENUM suffix_context = pch_suffix_context (); LINENUM context = (prefix_context < suffix_context ? suffix_context : prefix_context); LINENUM mymaxfuzz = (maxfuzz < context ? maxfuzz : context); hunk++; if (!skip_rest_of_patch) { do { where = locate_hunk(fuzz); if (! where || fuzz || last_offset) mismatch = true; if (hunk == 1 && ! where && ! (force | apply_anyway) && reverse == reverse_flag_specified) { /* dwim for reversed patch? */ if (!pch_swap()) { say ( "Not enough memory to try swapped hunk! Assuming unswapped.\n"); continue; } /* Try again. */ where = locate_hunk (fuzz); if (where && (ok_to_reverse ("%s patch detected!", (reverse ? "Unreversed" : "Reversed (or previously applied)")))) reverse = ! reverse; else { /* Put it back to normal. */ if (! pch_swap ()) fatal ("lost hunk on alloc error!"); if (where) { apply_anyway = true; fuzz--; /* Undo `++fuzz' below. */ where = 0; } } } } while (!skip_rest_of_patch && !where && ++fuzz <= mymaxfuzz); if (skip_rest_of_patch) { /* just got decided */ if (outstate.ofp && ! outfile) { fclose (outstate.ofp); outstate.ofp = 0; } } } newwhere = pch_newfirst() + last_offset; if (skip_rest_of_patch) { abort_hunk(); failed++; if (verbosity == VERBOSE) say ("Hunk #%d ignored at %s.\n", hunk, format_linenum (numbuf, newwhere)); } else if (!where || (where == 1 && pch_says_nonexistent (reverse) == 2 && instat.st_size)) { if (where) say ("Patch attempted to create file %s, which already exists.\n", quotearg (inname)); abort_hunk(); failed++; if (verbosity != SILENT) say ("Hunk #%d FAILED at %s.\n", hunk, format_linenum (numbuf, newwhere)); } else if (! apply_hunk (&outstate, where)) { abort_hunk (); failed++; if (verbosity != SILENT) say ("Hunk #%d FAILED at %s.\n", hunk, format_linenum (numbuf, newwhere)); } else { if (verbosity == VERBOSE || (verbosity != SILENT && (fuzz || last_offset))) { say ("Hunk #%d succeeded at %s", hunk, format_linenum (numbuf, newwhere)); if (fuzz) say (" with fuzz %s", format_linenum (numbuf, fuzz)); if (last_offset) say (" (offset %s line%s)", format_linenum (numbuf, last_offset), "s" + (last_offset == 1)); say (".\n"); } } } if (!skip_rest_of_patch) { if (got_hunk < 0 && using_plan_a) { if (outfile) fatal ("out of memory using Plan A"); say ("\n\nRan out of memory using Plan A -- trying again...\n\n"); if (outstate.ofp) { fclose (outstate.ofp); outstate.ofp = 0; } fclose (rejfp); continue; } /* Finish spewing out the new file. */ assert (hunk); if (! spew_output (&outstate)) { say ("Skipping patch.\n"); skip_rest_of_patch = true; } } } /* and put the output where desired */ ignore_signals (); if (! skip_rest_of_patch && ! outfile) { if (outstate.zero_output && (remove_empty_files || (pch_says_nonexistent (! reverse) == 2 && ! posixly_correct))) { if (verbosity == VERBOSE) say ("Removing file %s%s\n", quotearg (outname), dry_run ? " and any empty ancestor directories" : ""); if (! dry_run) { move_file ((char *) 0, (int *) 0, outname, (mode_t) 0, (make_backups || (backup_if_mismatch && (mismatch | failed)))); removedirs (outname); } } else { if (! outstate.zero_output && pch_says_nonexistent (! reverse)) { mismatch = true; if (verbosity != SILENT) say ("File %s is not empty after patch, as expected\n", quotearg (outname)); } if (! dry_run) { time_t t; move_file (TMPOUTNAME, &TMPOUTNAME_needs_removal, outname, instat.st_mode, (make_backups || (backup_if_mismatch && (mismatch | failed)))); if ((set_time | set_utc) && (t = pch_timestamp (! reverse)) != (time_t) -1) { struct utimbuf utimbuf; utimbuf.actime = utimbuf.modtime = t; if (! force && ! inerrno && pch_says_nonexistent (reverse) != 2 && (t = pch_timestamp (reverse)) != (time_t) -1 && t != instat.st_mtime) say ("Not setting time of file %s (time mismatch)\n", quotearg (outname)); else if (! force && (mismatch | failed)) say ("Not setting time of file %s (contents mismatch)\n", quotearg (outname)); else if (utime (outname, &utimbuf) != 0) pfatal ("Can't set timestamp on file %s", quotearg (outname)); } if (! inerrno && chmod (outname, instat.st_mode) != 0) pfatal ("Can't set permissions on file %s", quotearg (outname)); } } } if (diff_type != ED_DIFF) { if (fclose (rejfp) != 0) write_fatal (); if (failed) { somefailed = true; say ("%d out of %d hunk%s %s", failed, hunk, "s" + (hunk == 1), skip_rest_of_patch ? "ignored" : "FAILED"); if (outname) { char *rej = rejname; if (!rejname) { rej = xmalloc (strlen (outname) + 5); strcpy (rej, outname); addext (rej, ".rej", '#'); } say (" -- saving rejects to file %s", quotearg (rej)); if (! dry_run) { move_file (TMPREJNAME, &TMPREJNAME_needs_removal, rej, instat.st_mode, false); if (! inerrno && (chmod (rej, (instat.st_mode & ~(S_IXUSR|S_IXGRP|S_IXOTH))) != 0)) pfatal ("can't set permissions on file %s", quotearg (rej)); } if (!rejname) free (rej); } say ("\n"); } } set_signals (true); } if (outstate.ofp && (ferror (outstate.ofp) || fclose (outstate.ofp) != 0)) write_fatal (); cleanup (); if (somefailed) exit (1); return 0; } /* Prepare to find the next patch to do in the patch file. */ static void reinitialize_almost_everything (void) { re_patch(); re_input(); input_lines = 0; last_frozen_line = 0; if (inname) { free (inname); inname = 0; } last_offset = 0; diff_type = NO_DIFF; if (revision) { free(revision); revision = 0; } reverse = reverse_flag_specified; skip_rest_of_patch = false; } static char const shortopts[] = "bB:cd:D:eEfF:g:i:lnNo:p:r:RstTuvV:x:Y:z:Z"; static struct option const longopts[] = { {"backup", no_argument, NULL, 'b'}, {"prefix", required_argument, NULL, 'B'}, {"context", no_argument, NULL, 'c'}, {"directory", required_argument, NULL, 'd'}, {"ifdef", required_argument, NULL, 'D'}, {"ed", no_argument, NULL, 'e'}, {"remove-empty-files", no_argument, NULL, 'E'}, {"force", no_argument, NULL, 'f'}, {"fuzz", required_argument, NULL, 'F'}, {"get", no_argument, NULL, 'g'}, {"input", required_argument, NULL, 'i'}, {"ignore-whitespace", no_argument, NULL, 'l'}, {"normal", no_argument, NULL, 'n'}, {"forward", no_argument, NULL, 'N'}, {"output", required_argument, NULL, 'o'}, {"strip", required_argument, NULL, 'p'}, {"reject-file", required_argument, NULL, 'r'}, {"reverse", no_argument, NULL, 'R'}, {"quiet", no_argument, NULL, 's'}, {"silent", no_argument, NULL, 's'}, {"batch", no_argument, NULL, 't'}, {"set-time", no_argument, NULL, 'T'}, {"unified", no_argument, NULL, 'u'}, {"version", no_argument, NULL, 'v'}, {"version-control", required_argument, NULL, 'V'}, {"debug", required_argument, NULL, 'x'}, {"basename-prefix", required_argument, NULL, 'Y'}, {"suffix", required_argument, NULL, 'z'}, {"set-utc", no_argument, NULL, 'Z'}, {"dry-run", no_argument, NULL, CHAR_MAX + 1}, {"verbose", no_argument, NULL, CHAR_MAX + 2}, {"binary", no_argument, NULL, CHAR_MAX + 3}, {"help", no_argument, NULL, CHAR_MAX + 4}, {"backup-if-mismatch", no_argument, NULL, CHAR_MAX + 5}, {"no-backup-if-mismatch", no_argument, NULL, CHAR_MAX + 6}, {"posix", no_argument, NULL, CHAR_MAX + 7}, {"quoting-style", required_argument, NULL, CHAR_MAX + 8}, {NULL, no_argument, NULL, 0} }; static char const *const option_help[] = { "Input options:", "", " -p NUM --strip=NUM Strip NUM leading components from file names.", " -F LINES --fuzz LINES Set the fuzz factor to LINES for inexact matching.", " -l --ignore-whitespace Ignore white space changes between patch and input.", "", " -c --context Interpret the patch as a context difference.", " -e --ed Interpret the patch as an ed script.", " -n --normal Interpret the patch as a normal difference.", " -u --unified Interpret the patch as a unified difference.", "", " -N --forward Ignore patches that appear to be reversed or already applied.", " -R --reverse Assume patches were created with old and new files swapped.", "", " -i PATCHFILE --input=PATCHFILE Read patch from PATCHFILE instead of stdin.", "", "Output options:", "", " -o FILE --output=FILE Output patched files to FILE.", " -r FILE --reject-file=FILE Output rejects to FILE.", "", " -D NAME --ifdef=NAME Make merged if-then-else output using NAME.", " -E --remove-empty-files Remove output files that are empty after patching.", "", " -Z --set-utc Set times of patched files, assuming diff uses UTC (GMT).", " -T --set-time Likewise, assuming local time.", "", " --quoting-style=WORD output file names using quoting style WORD.", " Valid WORDs are: literal, shell, shell-always, c, escape.", " Default is taken from QUOTING_STYLE env variable, or 'shell' if unset.", "", "Backup and version control options:", "", " -b --backup Back up the original contents of each file.", " --backup-if-mismatch Back up if the patch does not match exactly.", " --no-backup-if-mismatch Back up mismatches only if otherwise requested.", "", " -V STYLE --version-control=STYLE Use STYLE version control.", " STYLE is either 'simple', 'numbered', or 'existing'.", " -B PREFIX --prefix=PREFIX Prepend PREFIX to backup file names.", " -Y PREFIX --basename-prefix=PREFIX Prepend PREFIX to backup file basenames.", " -z SUFFIX --suffix=SUFFIX Append SUFFIX to backup file names.", "", " -g NUM --get=NUM Get files from RCS etc. if positive; ask if negative.", "", "Miscellaneous options:", "", " -t --batch Ask no questions; skip bad-Prereq patches; assume reversed.", " -f --force Like -t, but ignore bad-Prereq patches, and assume unreversed.", " -s --quiet --silent Work silently unless an error occurs.", " --verbose Output extra information about the work being done.", " --dry-run Do not actually change any files; just print what would happen.", " --posix Conform to the POSIX standard.", "", " -d DIR --directory=DIR Change the working directory to DIR first.", #if HAVE_SETMODE_DOS " --binary Read and write data in binary mode.", #else " --binary Read and write data in binary mode (no effect on this platform).", #endif "", " -v --version Output version info.", " --help Output this help.", "", "Report bugs to <" PACKAGE_BUGREPORT ">.", 0 }; static void usage (FILE *stream, int status) { char const * const *p; if (status != 0) { fprintf (stream, "%s: Try `%s --help' for more information.\n", program_name, Argv[0]); } else { fprintf (stream, "Usage: %s [OPTION]... [ORIGFILE [PATCHFILE]]\n\n", Argv[0]); for (p = option_help; *p; p++) fprintf (stream, "%s\n", *p); } exit (status); } /* Process switches and filenames. */ static void get_some_switches (void) { register int optc; if (rejname) free (rejname); rejname = 0; if (optind == Argc) return; while ((optc = getopt_long (Argc, Argv, shortopts, longopts, (int *) 0)) != -1) { switch (optc) { case 'b': make_backups = true; /* Special hack for backward compatibility with CVS 1.9. If the last 4 args are `-b SUFFIX ORIGFILE PATCHFILE', treat `-b' as if it were `-b -z'. */ if (Argc - optind == 3 && strcmp (Argv[optind - 1], "-b") == 0 && ! (Argv[optind + 0][0] == '-' && Argv[optind + 0][1]) && ! (Argv[optind + 1][0] == '-' && Argv[optind + 1][1]) && ! (Argv[optind + 2][0] == '-' && Argv[optind + 2][1])) { optarg = Argv[optind++]; if (verbosity != SILENT) say ("warning: the `-b %s' option is obsolete; use `-b -z %s' instead\n", optarg, optarg); goto case_z; } break; case 'B': if (!*optarg) fatal ("backup prefix is empty"); origprae = savestr (optarg); break; case 'c': diff_type = CONTEXT_DIFF; break; case 'd': if (chdir(optarg) < 0) pfatal ("Can't change to directory %s", quotearg (optarg)); break; case 'D': do_defines = savestr (optarg); break; case 'e': diff_type = ED_DIFF; break; case 'E': remove_empty_files = true; break; case 'f': force = true; break; case 'F': maxfuzz = numeric_string (optarg, false, "fuzz factor"); break; case 'g': patch_get = numeric_string (optarg, true, "get option value"); break; case 'i': patchname = savestr (optarg); break; case 'l': canonicalize = true; break; case 'n': diff_type = NORMAL_DIFF; break; case 'N': noreverse = true; break; case 'o': if (strcmp (optarg, "-") == 0) fatal ("can't output patches to standard output"); outfile = savestr (optarg); break; case 'p': strippath = numeric_string (optarg, false, "strip count"); break; case 'r': rejname = savestr (optarg); break; case 'R': reverse = true; reverse_flag_specified = true; break; case 's': verbosity = SILENT; break; case 't': batch = true; break; case 'T': set_time = true; break; case 'u': diff_type = UNI_DIFF; break; case 'v': version(); exit (0); break; case 'V': version_control = optarg; version_control_context = "--version-control or -V option"; break; #if DEBUGGING case 'x': debug = numeric_string (optarg, true, "debugging option"); break; #endif case 'Y': if (!*optarg) fatal ("backup basename prefix is empty"); origbase = savestr (optarg); break; case 'z': case_z: if (!*optarg) fatal ("backup suffix is empty"); simple_backup_suffix = savestr (optarg); break; case 'Z': set_utc = true; break; case CHAR_MAX + 1: dry_run = true; break; case CHAR_MAX + 2: verbosity = VERBOSE; break; case CHAR_MAX + 3: #if HAVE_SETMODE_DOS binary_transput = O_BINARY; #endif break; case CHAR_MAX + 4: usage (stdout, 0); case CHAR_MAX + 5: backup_if_mismatch = true; break; case CHAR_MAX + 6: backup_if_mismatch = false; break; case CHAR_MAX + 7: posixly_correct = true; break; case CHAR_MAX + 8: { int i = argmatch (optarg, quoting_style_args, 0, 0); if (i < 0) { invalid_arg ("quoting style", optarg, i); usage (stderr, 2); } set_quoting_style ((struct quoting_options *) 0, (enum quoting_style) i); } break; default: usage (stderr, 2); } } /* Process any filename args. */ if (optind < Argc) { inname = savestr (Argv[optind++]); invc = -1; if (optind < Argc) { patchname = savestr (Argv[optind++]); if (optind < Argc) { fprintf (stderr, "%s: %s: extra operand\n", program_name, quotearg (Argv[optind])); usage (stderr, 2); } } } } /* Handle STRING (possibly negative if NEGATIVE_ALLOWED is nonzero) of type ARGTYPE_MSGID by converting it to an integer, returning the result. */ static int numeric_string (char const *string, bool negative_allowed, char const *argtype_msgid) { int value = 0; char const *p = string; int sign = *p == '-' ? -1 : 1; p += *p == '-' || *p == '+'; do { int v10 = value * 10; int digit = *p - '0'; int signed_digit = sign * digit; int next_value = v10 + signed_digit; if (9 < (unsigned) digit) fatal ("%s %s is not a number", argtype_msgid, quotearg (string)); if (v10 / 10 != value || (next_value < v10) != (signed_digit < 0)) fatal ("%s %s is too large", argtype_msgid, quotearg (string)); value = next_value; } while (*++p); if (value < 0 && ! negative_allowed) fatal ("%s %s is negative", argtype_msgid, quotearg (string)); return value; } /* Attempt to find the right place to apply this hunk of patch. */ static LINENUM locate_hunk (LINENUM fuzz) { register LINENUM first_guess = pch_first () + last_offset; register LINENUM offset; LINENUM pat_lines = pch_ptrn_lines(); LINENUM prefix_context = pch_prefix_context (); LINENUM suffix_context = pch_suffix_context (); LINENUM context = (prefix_context < suffix_context ? suffix_context : prefix_context); LINENUM prefix_fuzz = fuzz + prefix_context - context; LINENUM suffix_fuzz = fuzz + suffix_context - context; LINENUM max_where = input_lines - (pat_lines - suffix_fuzz) + 1; LINENUM min_where = last_frozen_line + 1 - (prefix_context - prefix_fuzz); LINENUM max_pos_offset = max_where - first_guess; LINENUM max_neg_offset = first_guess - min_where; LINENUM max_offset = (max_pos_offset < max_neg_offset ? max_neg_offset : max_pos_offset); if (!pat_lines) /* null range matches always */ return first_guess; /* Do not try lines <= 0. */ if (first_guess <= max_neg_offset) max_neg_offset = first_guess - 1; if (prefix_fuzz < 0) { /* Can only match start of file. */ if (suffix_fuzz < 0) /* Can only match entire file. */ if (pat_lines != input_lines || prefix_context < last_frozen_line) return 0; offset = 1 - first_guess; if (last_frozen_line <= prefix_context && offset <= max_pos_offset && patch_match (first_guess, offset, (LINENUM) 0, suffix_fuzz)) { last_offset += offset; return first_guess + offset; } else return 0; } if (suffix_fuzz < 0) { /* Can only match end of file. */ offset = first_guess - (input_lines - pat_lines + 1); if (offset <= max_neg_offset && patch_match (first_guess, -offset, prefix_fuzz, (LINENUM) 0)) { last_offset -= offset; return first_guess - offset; } else return 0; } for (offset = 0; offset <= max_offset; offset++) { char numbuf0[LINENUM_LENGTH_BOUND + 1]; char numbuf1[LINENUM_LENGTH_BOUND + 1]; if (offset <= max_pos_offset && patch_match (first_guess, offset, prefix_fuzz, suffix_fuzz)) { if (debug & 1) say ("Offset changing from %s to %s\n", format_linenum (numbuf0, last_offset), format_linenum (numbuf1, last_offset + offset)); last_offset += offset; return first_guess+offset; } if (0 < offset && offset <= max_neg_offset && patch_match (first_guess, -offset, prefix_fuzz, suffix_fuzz)) { if (debug & 1) say ("Offset changing from %s to %s\n", format_linenum (numbuf0, last_offset), format_linenum (numbuf1, last_offset - offset)); last_offset -= offset; return first_guess-offset; } } return 0; } /* We did not find the pattern, dump out the hunk so they can handle it. */ static void abort_hunk (void) { register LINENUM i; register LINENUM pat_end = pch_end (); /* add in last_offset to guess the same as the previous successful hunk */ LINENUM oldfirst = pch_first() + last_offset; LINENUM newfirst = pch_newfirst() + last_offset; LINENUM oldlast = oldfirst + pch_ptrn_lines() - 1; LINENUM newlast = newfirst + pch_repl_lines() - 1; char const *stars = (int) NEW_CONTEXT_DIFF <= (int) diff_type ? " ****" : ""; char const *minuses = (int) NEW_CONTEXT_DIFF <= (int) diff_type ? " ----" : " -----"; fprintf(rejfp, "***************\n"); for (i=0; i<=pat_end; i++) { char numbuf0[LINENUM_LENGTH_BOUND + 1]; char numbuf1[LINENUM_LENGTH_BOUND + 1]; switch (pch_char(i)) { case '*': if (oldlast < oldfirst) fprintf(rejfp, "*** 0%s\n", stars); else if (oldlast == oldfirst) fprintf (rejfp, "*** %s%s\n", format_linenum (numbuf0, oldfirst), stars); else fprintf (rejfp, "*** %s,%s%s\n", format_linenum (numbuf0, oldfirst), format_linenum (numbuf1, oldlast), stars); break; case '=': if (newlast < newfirst) fprintf(rejfp, "--- 0%s\n", minuses); else if (newlast == newfirst) fprintf (rejfp, "--- %s%s\n", format_linenum (numbuf0, newfirst), minuses); else fprintf (rejfp, "--- %s,%s%s\n", format_linenum (numbuf0, newfirst), format_linenum (numbuf1, newlast), minuses); break; case ' ': case '-': case '+': case '!': fprintf (rejfp, "%c ", pch_char (i)); /* fall into */ case '\n': pch_write_line (i, rejfp); break; default: fatal ("fatal internal error in abort_hunk"); } if (ferror (rejfp)) write_fatal (); } } /* We found where to apply it (we hope), so do it. */ static bool apply_hunk (struct outstate *outstate, LINENUM where) { register LINENUM old = 1; register LINENUM lastline = pch_ptrn_lines (); register LINENUM new = lastline+1; register enum {OUTSIDE, IN_IFNDEF, IN_IFDEF, IN_ELSE} def_state = OUTSIDE; register char const *R_do_defines = do_defines; register LINENUM pat_end = pch_end (); register FILE *fp = outstate->ofp; where--; while (pch_char(new) == '=' || pch_char(new) == '\n') new++; while (old <= lastline) { if (pch_char(old) == '-') { assert (outstate->after_newline); if (! copy_till (outstate, where + old - 1)) return false; if (R_do_defines) { if (def_state == OUTSIDE) { fprintf (fp, outstate->after_newline + not_defined, R_do_defines); def_state = IN_IFNDEF; } else if (def_state == IN_IFDEF) { fprintf (fp, outstate->after_newline + else_defined); def_state = IN_ELSE; } if (ferror (fp)) write_fatal (); outstate->after_newline = pch_write_line (old, fp); outstate->zero_output = false; } last_frozen_line++; old++; } else if (new > pat_end) { break; } else if (pch_char(new) == '+') { if (! copy_till (outstate, where + old - 1)) return false; if (R_do_defines) { if (def_state == IN_IFNDEF) { fprintf (fp, outstate->after_newline + else_defined); def_state = IN_ELSE; } else if (def_state == OUTSIDE) { fprintf (fp, outstate->after_newline + if_defined, R_do_defines); def_state = IN_IFDEF; } if (ferror (fp)) write_fatal (); } outstate->after_newline = pch_write_line (new, fp); outstate->zero_output = false; new++; } else if (pch_char(new) != pch_char(old)) { char numbuf0[LINENUM_LENGTH_BOUND + 1]; char numbuf1[LINENUM_LENGTH_BOUND + 1]; if (debug & 1) say ("oldchar = '%c', newchar = '%c'\n", pch_char (old), pch_char (new)); fatal ("Out-of-sync patch, lines %s,%s -- mangled text or line numbers, maybe?", format_linenum (numbuf0, pch_hunk_beg() + old), format_linenum (numbuf1, pch_hunk_beg() + new)); } else if (pch_char(new) == '!') { assert (outstate->after_newline); if (! copy_till (outstate, where + old - 1)) return false; assert (outstate->after_newline); if (R_do_defines) { fprintf (fp, 1 + not_defined, R_do_defines); if (ferror (fp)) write_fatal (); def_state = IN_IFNDEF; } do { if (R_do_defines) { outstate->after_newline = pch_write_line (old, fp); } last_frozen_line++; old++; } while (pch_char (old) == '!'); if (R_do_defines) { fprintf (fp, outstate->after_newline + else_defined); if (ferror (fp)) write_fatal (); def_state = IN_ELSE; } do { outstate->after_newline = pch_write_line (new, fp); new++; } while (pch_char (new) == '!'); outstate->zero_output = false; } else { assert(pch_char(new) == ' '); old++; new++; if (R_do_defines && def_state != OUTSIDE) { fprintf (fp, outstate->after_newline + end_defined); if (ferror (fp)) write_fatal (); outstate->after_newline = true; def_state = OUTSIDE; } } } if (new <= pat_end && pch_char(new) == '+') { if (! copy_till (outstate, where + old - 1)) return false; if (R_do_defines) { if (def_state == OUTSIDE) { fprintf (fp, outstate->after_newline + if_defined, R_do_defines); def_state = IN_IFDEF; } else if (def_state == IN_IFNDEF) { fprintf (fp, outstate->after_newline + else_defined); def_state = IN_ELSE; } if (ferror (fp)) write_fatal (); outstate->zero_output = false; } do { if (! outstate->after_newline && putc ('\n', fp) == EOF) write_fatal (); outstate->after_newline = pch_write_line (new, fp); outstate->zero_output = false; new++; } while (new <= pat_end && pch_char (new) == '+'); } if (R_do_defines && def_state != OUTSIDE) { fprintf (fp, outstate->after_newline + end_defined); if (ferror (fp)) write_fatal (); outstate->after_newline = true; } return true; } /* Create an output file. */ static FILE * create_output_file (char const *name, int open_flags) { int fd = create_file (name, O_WRONLY | binary_transput | open_flags, instat.st_mode); FILE *f = fdopen (fd, binary_transput ? "wb" : "w"); if (! f) pfatal ("Can't create file %s", quotearg (name)); return f; } /* Open the new file. */ static void init_output (char const *name, int open_flags, struct outstate *outstate) { outstate->ofp = name ? create_output_file (name, open_flags) : (FILE *) 0; outstate->after_newline = true; outstate->zero_output = true; } /* Open a file to put hunks we can't locate. */ static void init_reject (void) { int exclusive = TMPREJNAME_needs_removal ? 0 : O_EXCL; TMPREJNAME_needs_removal = 1; rejfp = create_output_file (TMPREJNAME, exclusive); } /* Copy input file to output, up to wherever hunk is to be applied. */ static bool copy_till (register struct outstate *outstate, register LINENUM lastline) { register LINENUM R_last_frozen_line = last_frozen_line; register FILE *fp = outstate->ofp; register char const *s; size_t size; if (R_last_frozen_line > lastline) { say ("misordered hunks! output would be garbled\n"); return false; } while (R_last_frozen_line < lastline) { s = ifetch (++R_last_frozen_line, false, &size); if (size) { if ((! outstate->after_newline && putc ('\n', fp) == EOF) || ! fwrite (s, sizeof *s, size, fp)) write_fatal (); outstate->after_newline = s[size - 1] == '\n'; outstate->zero_output = false; } } last_frozen_line = R_last_frozen_line; return true; } /* Finish copying the input file to the output file. */ static bool spew_output (struct outstate *outstate) { if (debug & 256) { char numbuf0[LINENUM_LENGTH_BOUND + 1]; char numbuf1[LINENUM_LENGTH_BOUND + 1]; say ("il=%s lfl=%s\n", format_linenum (numbuf0, input_lines), format_linenum (numbuf1, last_frozen_line)); } if (last_frozen_line < input_lines) if (! copy_till (outstate, input_lines)) return false; if (outstate->ofp && ! outfile) { if (fclose (outstate->ofp) != 0) write_fatal (); outstate->ofp = 0; } return true; } /* Does the patch pattern match at line base+offset? */ static bool patch_match (LINENUM base, LINENUM offset, LINENUM prefix_fuzz, LINENUM suffix_fuzz) { register LINENUM pline = 1 + prefix_fuzz; register LINENUM iline; register LINENUM pat_lines = pch_ptrn_lines () - suffix_fuzz; size_t size; register char const *p; for (iline=base+offset+prefix_fuzz; pline <= pat_lines; pline++,iline++) { p = ifetch (iline, offset >= 0, &size); if (canonicalize) { if (!similar(p, size, pfetch(pline), pch_line_len(pline) )) return false; } else if (size != pch_line_len (pline) || memcmp (p, pfetch (pline), size) != 0) return false; } return true; } /* Do two lines match with canonicalized white space? */ static bool similar (register char const *a, register size_t alen, register char const *b, register size_t blen) { /* Ignore presence or absence of trailing newlines. */ alen -= alen && a[alen - 1] == '\n'; blen -= blen && b[blen - 1] == '\n'; for (;;) { if (!blen || (*b == ' ' || *b == '\t')) { while (blen && (*b == ' ' || *b == '\t')) b++, blen--; if (alen) { if (!(*a == ' ' || *a == '\t')) return false; do a++, alen--; while (alen && (*a == ' ' || *a == '\t')); } if (!alen || !blen) return alen == blen; } else if (!alen || *a++ != *b++) return false; else alen--, blen--; } } /* Make a temporary file. */ #if HAVE_MKTEMP && ! HAVE_DECL_MKTEMP && ! defined mktemp char *mktemp (char *); #endif #ifndef TMPDIR #define TMPDIR "/tmp" #endif static char const * make_temp (char letter) { char *r; #if HAVE_MKTEMP char const *tmpdir = getenv ("TMPDIR"); /* Unix tradition */ if (!tmpdir) tmpdir = getenv ("TMP"); /* DOS tradition */ if (!tmpdir) tmpdir = getenv ("TEMP"); /* another DOS tradition */ if (!tmpdir) tmpdir = TMPDIR; r = xmalloc (strlen (tmpdir) + 10); sprintf (r, "%s/p%cXXXXXX", tmpdir, letter); /* It is OK to use mktemp here, since the rest of the code always opens temp files with O_EXCL. It might be better to use mkstemp to avoid some DoS problems, but simply substituting mkstemp for mktemp here will not fix the DoS problems; a more extensive change would be needed. */ mktemp (r); if (!*r) pfatal ("mktemp"); #else r = xmalloc (L_tmpnam); if (! (tmpnam (r) == r && *r)) pfatal ("tmpnam"); #endif return r; } /* Fatal exit with cleanup. */ void fatal_exit (int sig) { cleanup (); if (sig) exit_with_signal (sig); exit (2); } static void remove_if_needed (char const *name, int volatile *needs_removal) { if (*needs_removal) { unlink (name); *needs_removal = 0; } } static void cleanup (void) { remove_if_needed (TMPINNAME, &TMPINNAME_needs_removal); remove_if_needed (TMPOUTNAME, &TMPOUTNAME_needs_removal); remove_if_needed (TMPPATNAME, &TMPPATNAME_needs_removal); remove_if_needed (TMPREJNAME, &TMPREJNAME_needs_removal); }
/* argmatch.h -- definitions and prototypes for argmatch.c Copyright (C) 1990, 1998, 1999, 2001, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@ai.mit.edu> Modified by Akim Demaille <demaille@inf.enst.fr> */ #ifndef ARGMATCH_H_ # define ARGMATCH_H_ 1 # include <stddef.h> # define ARRAY_CARDINALITY(Array) (sizeof (Array) / sizeof *(Array)) # define ARGMATCH_CONSTRAINT(Arglist, Vallist) \ (ARRAY_CARDINALITY (Arglist) == ARRAY_CARDINALITY (Vallist) + 1) /* Assert there are as many real arguments as there are values (argument list ends with a NULL guard). ARGMATCH_VERIFY is preferred, since it is guaranteed to be checked at compile-time. ARGMATCH_ASSERT is for backward compatibility only. */ # define ARGMATCH_VERIFY(Arglist, Vallist) \ struct argmatch_verify \ { \ char argmatch_verify[ARGMATCH_CONSTRAINT(Arglist, Vallist) ? 1 : -1]; \ } # define ARGMATCH_ASSERT(Arglist, Vallist) \ assert (ARGMATCH_CONSTRAINT (Arglist, Vallist)) /* Return the index of the element of ARGLIST (NULL terminated) that matches with ARG. If VALLIST is not NULL, then use it to resolve false ambiguities (i.e., different matches of ARG but corresponding to the same values in VALLIST). */ int argmatch (char const *arg, char const *const *arglist, char const *vallist, size_t valsize); # define ARGMATCH(Arg, Arglist, Vallist) \ argmatch (Arg, Arglist, (char const *) (Vallist), sizeof *(Vallist)) /* xargmatch calls this function when it fails. This function should not return. By default, this is a function that calls ARGMATCH_DIE which in turn defaults to `exit (EXIT_FAILURE)'. */ typedef void (*argmatch_exit_fn) (void); extern argmatch_exit_fn argmatch_die; /* Report on stderr why argmatch failed. Report correct values. */ void argmatch_invalid (char const *context, char const *value, int problem); /* Left for compatibility with the old name invalid_arg */ # define invalid_arg(Context, Value, Problem) \ argmatch_invalid (Context, Value, Problem) /* Report on stderr the list of possible arguments. */ void argmatch_valid (char const *const *arglist, char const *vallist, size_t valsize); # define ARGMATCH_VALID(Arglist, Vallist) \ argmatch_valid (Arglist, (char const *) (Vallist), sizeof *(Vallist)) /* Same as argmatch, but upon failure, reports a explanation on the failure, and exits using the function EXIT_FN. */ int __xargmatch_internal (char const *context, char const *arg, char const *const *arglist, char const *vallist, size_t valsize, argmatch_exit_fn exit_fn); /* Programmer friendly interface to __xargmatch_internal. */ # define XARGMATCH(Context, Arg, Arglist, Vallist) \ ((Vallist) [__xargmatch_internal (Context, Arg, Arglist, \ (char const *) (Vallist), \ sizeof *(Vallist), \ argmatch_die)]) /* Convert a value into a corresponding argument. */ char const *argmatch_to_argument (char const *value, char const *const *arglist, char const *vallist, size_t valsize); # define ARGMATCH_TO_ARGUMENT(Value, Arglist, Vallist) \ argmatch_to_argument (Value, Arglist, \ (char const *) (Vallist), sizeof *(Vallist)) #endif /* ARGMATCH_H_ */
/* Declarations for getopt. Copyright (C) 1989-1994, 1996-1999, 2001 Free Software Foundation, Inc. This file is part of the GNU C Library. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef _GETOPT_H #ifndef __need_getopt # define _GETOPT_H 1 #endif /* If __GNU_LIBRARY__ is not already defined, either we are being used standalone, or this is the first header included in the source file. If we are being used with glibc, we need to include <features.h>, but that does not exist if we are standalone. So: if __GNU_LIBRARY__ is not defined, include <ctype.h>, which will pull in <features.h> for us if it's from glibc. (Why ctype.h? It's guaranteed to exist and it doesn't flood the namespace with stuff the way some other headers do.) */ #if !defined __GNU_LIBRARY__ # include <ctype.h> #endif #ifdef __cplusplus extern "C" { #endif /* For communication from `getopt' to the caller. When `getopt' finds an option that takes an argument, the argument value is returned here. Also, when `ordering' is RETURN_IN_ORDER, each non-option ARGV-element is returned here. */ extern char *optarg; /* Index in ARGV of the next element to be scanned. This is used for communication to and from the caller and for communication between successive calls to `getopt'. On entry to `getopt', zero means this is the first call; initialize. When `getopt' returns -1, this is the index of the first of the non-option elements that the caller should itself scan. Otherwise, `optind' communicates from one call to the next how much of ARGV has been scanned so far. */ extern int optind; /* Callers store zero here to inhibit the error message `getopt' prints for unrecognized options. */ extern int opterr; /* Set to an option character which was unrecognized. */ extern int optopt; #ifndef __need_getopt /* Describe the long-named options requested by the application. The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector of `struct option' terminated by an element containing a name which is zero. The field `has_arg' is: no_argument (or 0) if the option does not take an argument, required_argument (or 1) if the option requires an argument, optional_argument (or 2) if the option takes an optional argument. If the field `flag' is not NULL, it points to a variable that is set to the value given in the field `val' when the option is found, but left unchanged if the option is not found. To have a long-named option do something other than set an `int' to a compiled-in constant, such as set a value from `optarg', set the option's `flag' field to zero and its `val' field to a nonzero value (the equivalent single-letter option character, if there is one). For long options that have a zero `flag' field, `getopt' returns the contents of the `val' field. */ struct option { # if (defined __STDC__ && __STDC__) || defined __cplusplus const char *name; # else char *name; # endif /* has_arg can't be an enum because some compilers complain about type mismatches in all the code that assumes it is an int. */ int has_arg; int *flag; int val; }; /* Names for the values of the `has_arg' field of `struct option'. */ # define no_argument 0 # define required_argument 1 # define optional_argument 2 #endif /* need getopt */ /* Get definitions and prototypes for functions to process the arguments in ARGV (ARGC of them, minus the program name) for options given in OPTS. Return the option character from OPTS just read. Return -1 when there are no more options. For unrecognized options, or options missing arguments, `optopt' is set to the option letter, and '?' is returned. The OPTS string is a list of characters which are recognized option letters, optionally followed by colons, specifying that that letter takes an argument, to be placed in `optarg'. If a letter in OPTS is followed by two colons, its argument is optional. This behavior is specific to the GNU `getopt'. The argument `--' causes premature termination of argument scanning, explicitly telling `getopt' that there are no more options. If OPTS begins with `--', then non-option arguments are treated as arguments to the option '\0'. This behavior is specific to the GNU `getopt'. */ #if (defined __STDC__ && __STDC__) || defined __cplusplus # ifdef __GNU_LIBRARY__ /* Many other libraries have conflicting prototypes for getopt, with differences in the consts, in stdlib.h. To avoid compilation errors, only prototype getopt for the GNU C library. */ extern int getopt (int ___argc, char *const *___argv, const char *__shortopts); # else /* not __GNU_LIBRARY__ */ extern int getopt (); # endif /* __GNU_LIBRARY__ */ # ifndef __need_getopt extern int getopt_long (int ___argc, char *const *___argv, const char *__shortopts, const struct option *__longopts, int *__longind); extern int getopt_long_only (int ___argc, char *const *___argv, const char *__shortopts, const struct option *__longopts, int *__longind); /* Internal only. Users should not call this directly. */ extern int _getopt_internal (int ___argc, char *const *___argv, const char *__shortopts, const struct option *__longopts, int *__longind, int __long_only); # endif #else /* not __STDC__ */ extern int getopt (); # ifndef __need_getopt extern int getopt_long (); extern int getopt_long_only (); extern int _getopt_internal (); # endif #endif /* __STDC__ */ #ifdef __cplusplus } #endif /* Make sure we later can get all the definitions and declarations. */ #undef __need_getopt #endif /* getopt.h */
/* Print the version number. */ /* $Id: version.h,v 1.5 2002/05/28 07:24:05 eggert Exp $ */ void version (void);
/* reading patches */ /* $Id: pch.c,v 1.44 2003/05/20 14:03:17 eggert Exp $ */ /* Copyright (C) 1986, 1987, 1988 Larry Wall Copyright (C) 1990, 1991, 1992, 1993, 1997, 1998, 1999, 2000, 2001, 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #define XTERN extern #include <common.h> #include <backupfile.h> #include <dirname.h> #include <inp.h> #include <quotearg.h> #include <util.h> #undef XTERN #define XTERN #include <pch.h> #define INITHUNKMAX 125 /* initial dynamic allocation size */ /* Patch (diff listing) abstract type. */ static FILE *pfp; /* patch file pointer */ static int p_says_nonexistent[2]; /* [0] for old file, [1] for new: 0 for existent and nonempty, 1 for existent and probably (but not necessarily) empty, 2 for nonexistent */ static int p_rfc934_nesting; /* RFC 934 nesting level */ static time_t p_timestamp[2]; /* timestamps in patch headers */ static off_t p_filesize; /* size of the patch file */ static LINENUM p_first; /* 1st line number */ static LINENUM p_newfirst; /* 1st line number of replacement */ static LINENUM p_ptrn_lines; /* # lines in pattern */ static LINENUM p_repl_lines; /* # lines in replacement text */ static LINENUM p_end = -1; /* last line in hunk */ static LINENUM p_max; /* max allowed value of p_end */ static LINENUM p_prefix_context; /* # of prefix context lines */ static LINENUM p_suffix_context; /* # of suffix context lines */ static LINENUM p_input_line; /* current line # from patch file */ static char **p_line; /* the text of the hunk */ static size_t *p_len; /* line length including \n if any */ static char *p_Char; /* +, -, and ! */ static LINENUM hunkmax = INITHUNKMAX; /* size of above arrays */ static int p_indent; /* indent to patch */ static bool p_strip_trailing_cr; /* true if stripping trailing \r */ static bool p_pass_comments_through; /* true if not ignoring # lines */ static file_offset p_base; /* where to intuit this time */ static LINENUM p_bline; /* line # of p_base */ static file_offset p_start; /* where intuit found a patch */ static LINENUM p_sline; /* and the line number for it */ static LINENUM p_hunk_beg; /* line number of current hunk */ static LINENUM p_efake = -1; /* end of faked up lines--don't free */ static LINENUM p_bfake = -1; /* beg of faked up lines */ enum nametype { OLD, NEW, INDEX, NONE }; static char *scan_linenum (char *, LINENUM *); static enum diff intuit_diff_type (void); static enum nametype best_name (char * const *, int const *); static int prefix_components (char *, bool); static size_t pget_line (int, int, bool, bool); static size_t get_line (void); static bool incomplete_line (void); static bool grow_hunkmax (void); static void malformed (void) __attribute__ ((noreturn)); static void next_intuit_at (file_offset, LINENUM); static void skip_to (file_offset, LINENUM); static char get_ed_command_letter (char const *); /* Prepare to look for the next patch in the patch file. */ void re_patch (void) { p_first = 0; p_newfirst = 0; p_ptrn_lines = 0; p_repl_lines = 0; p_end = -1; p_max = 0; p_indent = 0; p_strip_trailing_cr = false; } /* Open the patch file at the beginning of time. */ void open_patch_file (char const *filename) { file_offset file_pos = 0; struct stat st; if (!filename || !*filename || strEQ (filename, "-")) { file_offset stdin_pos; #if HAVE_SETMODE_DOS if (binary_transput) { if (isatty (STDIN_FILENO)) fatal ("cannot read binary data from tty on this platform"); setmode (STDIN_FILENO, O_BINARY); } #endif if (fstat (STDIN_FILENO, &st) != 0) pfatal ("fstat"); if (S_ISREG (st.st_mode) && (stdin_pos = file_tell (stdin)) != -1) { pfp = stdin; file_pos = stdin_pos; } else { size_t charsread; int exclusive = TMPPATNAME_needs_removal ? 0 : O_EXCL; TMPPATNAME_needs_removal = 1; pfp = fdopen (create_file (TMPPATNAME, O_RDWR | O_BINARY | exclusive, (mode_t) 0), "w+b"); if (!pfp) pfatal ("Can't open stream for file %s", quotearg (TMPPATNAME)); for (st.st_size = 0; (charsread = fread (buf, 1, bufsize, stdin)) != 0; st.st_size += charsread) if (fwrite (buf, 1, charsread, pfp) != charsread) write_fatal (); if (ferror (stdin) || fclose (stdin) != 0) read_fatal (); if (fflush (pfp) != 0 || file_seek (pfp, (file_offset) 0, SEEK_SET) != 0) write_fatal (); } } else { pfp = fopen (filename, binary_transput ? "rb" : "r"); if (!pfp) pfatal ("Can't open patch file %s", quotearg (filename)); if (fstat (fileno (pfp), &st) != 0) pfatal ("fstat"); } p_filesize = st.st_size; if (p_filesize != (file_offset) p_filesize) fatal ("patch file is too long"); next_intuit_at (file_pos, (LINENUM) 1); set_hunkmax(); } /* Make sure our dynamically realloced tables are malloced to begin with. */ void set_hunkmax (void) { if (!p_line) p_line = (char **) malloc (hunkmax * sizeof *p_line); if (!p_len) p_len = (size_t *) malloc (hunkmax * sizeof *p_len); if (!p_Char) p_Char = malloc (hunkmax * sizeof *p_Char); } /* Enlarge the arrays containing the current hunk of patch. */ static bool grow_hunkmax (void) { hunkmax *= 2; assert (p_line && p_len && p_Char); if ((p_line = (char **) realloc (p_line, hunkmax * sizeof (*p_line))) && (p_len = (size_t *) realloc (p_len, hunkmax * sizeof (*p_len))) && (p_Char = realloc (p_Char, hunkmax * sizeof (*p_Char)))) return true; if (!using_plan_a) memory_fatal (); /* Don't free previous values of p_line etc., since some broken implementations free them for us. Whatever is null will be allocated again from within plan_a (), of all places. */ return false; } /* True if the remainder of the patch file contains a diff of some sort. */ bool there_is_another_patch (void) { if (p_base != 0 && p_base >= p_filesize) { if (verbosity == VERBOSE) say ("done\n"); return false; } if (verbosity == VERBOSE) say ("Hmm..."); diff_type = intuit_diff_type(); if (diff_type == NO_DIFF) { if (verbosity == VERBOSE) say (p_base ? " Ignoring the trailing garbage.\ndone\n" : " I can't seem to find a patch in there anywhere.\n"); if (! p_base && p_filesize) fatal ("Only garbage was found in the patch input."); return false; } if (skip_rest_of_patch) { Fseek (pfp, p_start, SEEK_SET); p_input_line = p_sline - 1; return true; } if (verbosity == VERBOSE) say (" %sooks like %s to me...\n", (p_base == 0 ? "L" : "The next patch l"), diff_type == UNI_DIFF ? "a unified diff" : diff_type == CONTEXT_DIFF ? "a context diff" : diff_type == NEW_CONTEXT_DIFF ? "a new-style context diff" : diff_type == NORMAL_DIFF ? "a normal diff" : "an ed script" ); if (verbosity != SILENT) { if (p_indent) say ("(Patch is indented %d space%s.)\n", p_indent, p_indent==1?"":"s"); if (p_strip_trailing_cr) say ("(Stripping trailing CRs from patch.)\n"); if (! inname) { char numbuf[LINENUM_LENGTH_BOUND + 1]; say ("can't find file to patch at input line %s\n", format_linenum (numbuf, p_sline)); if (diff_type != ED_DIFF) say (strippath == -1 ? "Perhaps you should have used the -p or --strip option?\n" : "Perhaps you used the wrong -p or --strip option?\n"); } } skip_to(p_start,p_sline); while (!inname) { if (force | batch) { say ("No file to patch. Skipping patch.\n"); skip_rest_of_patch = true; return true; } ask ("File to patch: "); inname = fetchname (buf, 0, (time_t *) 0); if (inname) { if (stat (inname, &instat) == 0) { inerrno = 0; invc = -1; } else { perror (inname); fflush (stderr); free (inname); inname = 0; } } if (!inname) { ask ("Skip this patch? [y] "); if (*buf != 'n') { if (verbosity != SILENT) say ("Skipping patch.\n"); skip_rest_of_patch = true; return true; } } } return true; } /* Determine what kind of diff is in the remaining part of the patch file. */ static enum diff intuit_diff_type (void) { register file_offset this_line = 0; register file_offset first_command_line = -1; char first_ed_command_letter = 0; LINENUM fcl_line = 0; /* Pacify `gcc -W'. */ register bool this_is_a_command = false; register bool stars_this_line = false; enum nametype i; char *name[3]; struct stat st[3]; int stat_errno[3]; int version_controlled[3]; register enum diff retval; name[OLD] = name[NEW] = name[INDEX] = 0; version_controlled[OLD] = -1; version_controlled[NEW] = -1; version_controlled[INDEX] = -1; p_rfc934_nesting = 0; p_timestamp[OLD] = p_timestamp[NEW] = (time_t) -1; p_says_nonexistent[OLD] = p_says_nonexistent[NEW] = 0; Fseek (pfp, p_base, SEEK_SET); p_input_line = p_bline - 1; for (;;) { register char *s; register char *t; register file_offset previous_line = this_line; register bool last_line_was_command = this_is_a_command; register bool stars_last_line = stars_this_line; register int indent = 0; char ed_command_letter; bool strip_trailing_cr; size_t chars_read; this_line = file_tell (pfp); chars_read = pget_line (0, 0, false, false); if (chars_read == (size_t) -1) memory_fatal (); if (! chars_read) { if (first_ed_command_letter) { /* nothing but deletes!? */ p_start = first_command_line; p_sline = fcl_line; retval = ED_DIFF; goto scan_exit; } else { p_start = this_line; p_sline = p_input_line; return NO_DIFF; } } strip_trailing_cr = 2 <= chars_read && buf[chars_read - 2] == '\r'; for (s = buf; *s == ' ' || *s == '\t' || *s == 'X'; s++) { if (*s == '\t') indent = (indent + 8) & ~7; else indent++; } for (t = s; ISDIGIT (*t) || *t == ','; t++) continue; this_is_a_command = (ISDIGIT (*s) && (*t == 'd' || *t == 'c' || *t == 'a') ); if (first_command_line < 0 && ((ed_command_letter = get_ed_command_letter (s)) || this_is_a_command)) { first_command_line = this_line; first_ed_command_letter = ed_command_letter; fcl_line = p_input_line; p_indent = indent; /* assume this for now */ p_strip_trailing_cr = strip_trailing_cr; } if (!stars_last_line && strnEQ(s, "*** ", 4)) name[OLD] = fetchname (s+4, strippath, &p_timestamp[OLD]); else if (strnEQ(s, "+++ ", 4)) /* Swap with NEW below. */ name[OLD] = fetchname (s+4, strippath, &p_timestamp[OLD]); else if (strnEQ(s, "Index:", 6)) name[INDEX] = fetchname (s+6, strippath, (time_t *) 0); else if (strnEQ(s, "Prereq:", 7)) { for (t = s + 7; ISSPACE ((unsigned char) *t); t++) continue; revision = t; for (t = revision; *t; t++) if (ISSPACE ((unsigned char) *t)) { char const *u; for (u = t + 1; ISSPACE ((unsigned char) *u); u++) continue; if (*u) { char numbuf[LINENUM_LENGTH_BOUND + 1]; say ("Prereq: with multiple words at line %s of patch\n", format_linenum (numbuf, this_line)); } break; } if (t == revision) revision = 0; else { char oldc = *t; *t = '\0'; revision = savestr (revision); *t = oldc; } } else { for (t = s; t[0] == '-' && t[1] == ' '; t += 2) continue; if (strnEQ(t, "--- ", 4)) { time_t timestamp = (time_t) -1; name[NEW] = fetchname (t+4, strippath, ×tamp); if (timestamp != (time_t) -1) { p_timestamp[NEW] = timestamp; p_rfc934_nesting = (t - s) >> 1; } } } if ((diff_type == NO_DIFF || diff_type == ED_DIFF) && first_command_line >= 0 && strEQ(s, ".\n") ) { p_start = first_command_line; p_sline = fcl_line; retval = ED_DIFF; goto scan_exit; } if ((diff_type == NO_DIFF || diff_type == UNI_DIFF) && strnEQ(s, "@@ -", 4)) { /* `name' and `p_timestamp' are backwards; swap them. */ time_t ti = p_timestamp[OLD]; p_timestamp[OLD] = p_timestamp[NEW]; p_timestamp[NEW] = ti; t = name[OLD]; name[OLD] = name[NEW]; name[NEW] = t; s += 4; if (s[0] == '0' && !ISDIGIT (s[1])) p_says_nonexistent[OLD] = 1 + ! p_timestamp[OLD]; while (*s != ' ' && *s != '\n') s++; while (*s == ' ') s++; if (s[0] == '+' && s[1] == '0' && !ISDIGIT (s[2])) p_says_nonexistent[NEW] = 1 + ! p_timestamp[NEW]; p_indent = indent; p_start = this_line; p_sline = p_input_line; retval = UNI_DIFF; if (! ((name[OLD] || ! p_timestamp[OLD]) && (name[NEW] || ! p_timestamp[NEW])) && ! name[INDEX]) { char numbuf[LINENUM_LENGTH_BOUND + 1]; say ("missing header for unified diff at line %s of patch\n", format_linenum (numbuf, p_sline)); } goto scan_exit; } stars_this_line = strnEQ(s, "********", 8); if ((diff_type == NO_DIFF || diff_type == CONTEXT_DIFF || diff_type == NEW_CONTEXT_DIFF) && stars_last_line && strnEQ (s, "*** ", 4)) { s += 4; if (s[0] == '0' && !ISDIGIT (s[1])) p_says_nonexistent[OLD] = 1 + ! p_timestamp[OLD]; /* if this is a new context diff the character just before */ /* the newline is a '*'. */ while (*s != '\n') s++; p_indent = indent; p_strip_trailing_cr = strip_trailing_cr; p_start = previous_line; p_sline = p_input_line - 1; retval = (*(s-1) == '*' ? NEW_CONTEXT_DIFF : CONTEXT_DIFF); { /* Scan the first hunk to see whether the file contents appear to have been deleted. */ file_offset saved_p_base = p_base; LINENUM saved_p_bline = p_bline; Fseek (pfp, previous_line, SEEK_SET); p_input_line -= 2; if (another_hunk (retval, false) && ! p_repl_lines && p_newfirst == 1) p_says_nonexistent[NEW] = 1 + ! p_timestamp[NEW]; next_intuit_at (saved_p_base, saved_p_bline); } if (! ((name[OLD] || ! p_timestamp[OLD]) && (name[NEW] || ! p_timestamp[NEW])) && ! name[INDEX]) { char numbuf[LINENUM_LENGTH_BOUND + 1]; say ("missing header for context diff at line %s of patch\n", format_linenum (numbuf, p_sline)); } goto scan_exit; } if ((diff_type == NO_DIFF || diff_type == NORMAL_DIFF) && last_line_was_command && (strnEQ(s, "< ", 2) || strnEQ(s, "> ", 2)) ) { p_start = previous_line; p_sline = p_input_line - 1; p_indent = indent; p_strip_trailing_cr = strip_trailing_cr; retval = NORMAL_DIFF; goto scan_exit; } } scan_exit: /* To intuit `inname', the name of the file to patch, use the algorithm specified by POSIX 1003.1-2001 XCU lines 25680-26599 (with some modifications if posixly_correct is zero): - Take the old and new names from the context header if present, and take the index name from the `Index:' line if present and if either the old and new names are both absent or posixly_correct is nonzero. Consider the file names to be in the order (old, new, index). - If some named files exist, use the first one if posixly_correct is nonzero, the best one otherwise. - If patch_get is nonzero, and no named files exist, but an RCS or SCCS master file exists, use the first named file with an RCS or SCCS master. - If no named files exist, no RCS or SCCS master was found, some names are given, posixly_correct is zero, and the patch appears to create a file, then use the best name requiring the creation of the fewest directories. - Otherwise, report failure by setting `inname' to 0; this causes our invoker to ask the user for a file name. */ i = NONE; if (!inname) { enum nametype i0 = NONE; if (! posixly_correct && (name[OLD] || name[NEW]) && name[INDEX]) { free (name[INDEX]); name[INDEX] = 0; } for (i = OLD; i <= INDEX; i++) if (name[i]) { if (i0 != NONE && strcmp (name[i0], name[i]) == 0) { /* It's the same name as before; reuse stat results. */ stat_errno[i] = stat_errno[i0]; if (! stat_errno[i]) st[i] = st[i0]; } else if (stat (name[i], &st[i]) != 0) stat_errno[i] = errno; else { stat_errno[i] = 0; if (posixly_correct) break; } i0 = i; } if (! posixly_correct) { bool is_empty; i = best_name (name, stat_errno); if (i == NONE && patch_get) { enum nametype nope = NONE; for (i = OLD; i <= INDEX; i++) if (name[i]) { char const *cs; char *getbuf; char *diffbuf; bool readonly = (outfile && strcmp (outfile, name[i]) != 0); if (nope == NONE || strcmp (name[nope], name[i]) != 0) { cs = (version_controller (name[i], readonly, (struct stat *) 0, &getbuf, &diffbuf)); version_controlled[i] = !! cs; if (cs) { if (version_get (name[i], cs, false, readonly, getbuf, &st[i])) stat_errno[i] = 0; else version_controlled[i] = 0; free (getbuf); if (diffbuf) free (diffbuf); if (! stat_errno[i]) break; } } nope = i; } } is_empty = i == NONE || st[i].st_size == 0; if ((! is_empty) < p_says_nonexistent[reverse ^ is_empty]) { assert (i0 != NONE); reverse ^= ok_to_reverse ("The next patch%s would %s the file %s,\nwhich %s!", reverse ? ", when reversed," : "", (i == NONE ? "delete" : st[i].st_size == 0 ? "empty out" : "create"), quotearg (name[i == NONE || st[i].st_size == 0 ? i0 : i]), (i == NONE ? "does not exist" : st[i].st_size == 0 ? "is already empty" : "already exists")); } if (i == NONE && p_says_nonexistent[reverse]) { int newdirs[3]; int newdirs_min = INT_MAX; int distance_from_minimum[3]; for (i = OLD; i <= INDEX; i++) if (name[i]) { newdirs[i] = (prefix_components (name[i], false) - prefix_components (name[i], true)); if (newdirs[i] < newdirs_min) newdirs_min = newdirs[i]; } for (i = OLD; i <= INDEX; i++) if (name[i]) distance_from_minimum[i] = newdirs[i] - newdirs_min; i = best_name (name, distance_from_minimum); } } } if (i == NONE) inerrno = -1; else { inname = name[i]; name[i] = 0; inerrno = stat_errno[i]; invc = version_controlled[i]; instat = st[i]; } for (i = OLD; i <= INDEX; i++) if (name[i]) free (name[i]); return retval; } /* Count the path name components in FILENAME's prefix. If CHECKDIRS is true, count only existing directories. */ static int prefix_components (char *filename, bool checkdirs) { int count = 0; struct stat stat_buf; int stat_result; char *f = filename + FILESYSTEM_PREFIX_LEN (filename); if (*f) while (*++f) if (ISSLASH (f[0]) && ! ISSLASH (f[-1])) { if (checkdirs) { *f = '\0'; stat_result = stat (filename, &stat_buf); *f = '/'; if (! (stat_result == 0 && S_ISDIR (stat_buf.st_mode))) break; } count++; } return count; } /* Return the index of the best of NAME[OLD], NAME[NEW], and NAME[INDEX]. Ignore null names, and ignore NAME[i] if IGNORE[i] is nonzero. Return NONE if all names are ignored. */ static enum nametype best_name (char *const *name, int const *ignore) { enum nametype i; int components[3]; int components_min = INT_MAX; size_t basename_len[3]; size_t basename_len_min = SIZE_MAX; size_t len[3]; size_t len_min = SIZE_MAX; for (i = OLD; i <= INDEX; i++) if (name[i] && !ignore[i]) { /* Take the names with the fewest prefix components. */ components[i] = prefix_components (name[i], false); if (components_min < components[i]) continue; components_min = components[i]; /* Of those, take the names with the shortest basename. */ basename_len[i] = strlen (base_name (name[i])); if (basename_len_min < basename_len[i]) continue; basename_len_min = basename_len[i]; /* Of those, take the shortest names. */ len[i] = strlen (name[i]); if (len_min < len[i]) continue; len_min = len[i]; } /* Of those, take the first name. */ for (i = OLD; i <= INDEX; i++) if (name[i] && !ignore[i] && components[i] == components_min && basename_len[i] == basename_len_min && len[i] == len_min) break; return i; } /* Remember where this patch ends so we know where to start up again. */ static void next_intuit_at (file_offset file_pos, LINENUM file_line) { p_base = file_pos; p_bline = file_line; } /* Basically a verbose fseek() to the actual diff listing. */ static void skip_to (file_offset file_pos, LINENUM file_line) { register FILE *i = pfp; register FILE *o = stdout; register int c; assert(p_base <= file_pos); if ((verbosity == VERBOSE || !inname) && p_base < file_pos) { Fseek (i, p_base, SEEK_SET); say ("The text leading up to this was:\n--------------------------\n"); while (file_tell (i) < file_pos) { putc ('|', o); do { if ((c = getc (i)) == EOF) read_fatal (); putc (c, o); } while (c != '\n'); } say ("--------------------------\n"); } else Fseek (i, file_pos, SEEK_SET); p_input_line = file_line - 1; } /* Make this a function for better debugging. */ static void malformed (void) { char numbuf[LINENUM_LENGTH_BOUND + 1]; fatal ("malformed patch at line %s: %s", format_linenum (numbuf, p_input_line), buf); /* about as informative as "Syntax error" in C */ } /* Parse a line number from a string. Return the address of the first char after the number. */ static char * scan_linenum (char *s0, LINENUM *linenum) { char *s; LINENUM n = 0; bool overflow = false; char numbuf[LINENUM_LENGTH_BOUND + 1]; for (s = s0; ISDIGIT (*s); s++) { LINENUM new_n = 10 * n + (*s - '0'); overflow |= new_n / 10 != n; n = new_n; } if (s == s0) fatal ("missing line number at line %s: %s", format_linenum (numbuf, p_input_line), buf); if (overflow) fatal ("line number %.*s is too large at line %s: %s", (int) (s - s0), s0, format_linenum (numbuf, p_input_line), buf); *linenum = n; return s; } /* 1 if there is more of the current diff listing to process; 0 if not; -1 if ran out of memory. */ int another_hunk (enum diff difftype, bool rev) { register char *s; register LINENUM context = 0; register size_t chars_read; char numbuf0[LINENUM_LENGTH_BOUND + 1]; char numbuf1[LINENUM_LENGTH_BOUND + 1]; char numbuf2[LINENUM_LENGTH_BOUND + 1]; char numbuf3[LINENUM_LENGTH_BOUND + 1]; while (p_end >= 0) { if (p_end == p_efake) p_end = p_bfake; /* don't free twice */ else free(p_line[p_end]); p_end--; } assert(p_end == -1); p_efake = -1; p_max = hunkmax; /* gets reduced when --- found */ if (difftype == CONTEXT_DIFF || difftype == NEW_CONTEXT_DIFF) { file_offset line_beginning = file_tell (pfp); /* file pos of the current line */ LINENUM repl_beginning = 0; /* index of --- line */ register LINENUM fillcnt = 0; /* #lines of missing ptrn or repl */ register LINENUM fillsrc; /* index of first line to copy */ register LINENUM filldst; /* index of first missing line */ bool ptrn_spaces_eaten = false; /* ptrn was slightly misformed */ bool some_context = false; /* (perhaps internal) context seen */ register bool repl_could_be_missing = true; bool ptrn_missing = false; /* The pattern was missing. */ bool repl_missing = false; /* Likewise for replacement. */ file_offset repl_backtrack_position = 0; /* file pos of first repl line */ LINENUM repl_patch_line; /* input line number for same */ LINENUM repl_context; /* context for same */ LINENUM ptrn_prefix_context = -1; /* lines in pattern prefix context */ LINENUM ptrn_suffix_context = -1; /* lines in pattern suffix context */ LINENUM repl_prefix_context = -1; /* lines in replac. prefix context */ LINENUM ptrn_copiable = 0; /* # of copiable lines in ptrn */ LINENUM repl_copiable = 0; /* Likewise for replacement. */ /* Pacify `gcc -Wall'. */ fillsrc = filldst = repl_patch_line = repl_context = 0; chars_read = get_line (); if (chars_read == (size_t) -1 || chars_read <= 8 || strncmp (buf, "********", 8) != 0) { next_intuit_at(line_beginning,p_input_line); return chars_read == (size_t) -1 ? -1 : 0; } p_hunk_beg = p_input_line + 1; while (p_end < p_max) { chars_read = get_line (); if (chars_read == (size_t) -1) return -1; if (!chars_read) { if (repl_beginning && repl_could_be_missing) { repl_missing = true; goto hunk_done; } if (p_max - p_end < 4) { strcpy (buf, " \n"); /* assume blank lines got chopped */ chars_read = 3; } else { fatal ("unexpected end of file in patch"); } } p_end++; if (p_end == hunkmax) fatal ("unterminated hunk starting at line %s; giving up at line %s: %s", format_linenum (numbuf0, pch_hunk_beg ()), format_linenum (numbuf1, p_input_line), buf); assert(p_end < hunkmax); p_Char[p_end] = *buf; p_len[p_end] = 0; p_line[p_end] = 0; switch (*buf) { case '*': if (strnEQ(buf, "********", 8)) { if (repl_beginning && repl_could_be_missing) { repl_missing = true; goto hunk_done; } else fatal ("unexpected end of hunk at line %s", format_linenum (numbuf0, p_input_line)); } if (p_end != 0) { if (repl_beginning && repl_could_be_missing) { repl_missing = true; goto hunk_done; } fatal ("unexpected `***' at line %s: %s", format_linenum (numbuf0, p_input_line), buf); } context = 0; p_len[p_end] = strlen (buf); if (! (p_line[p_end] = savestr (buf))) { p_end--; return -1; } for (s = buf; *s && !ISDIGIT (*s); s++) continue; if (strnEQ(s,"0,0",3)) remove_prefix (s, 2); s = scan_linenum (s, &p_first); if (*s == ',') { while (*s && !ISDIGIT (*s)) s++; scan_linenum (s, &p_ptrn_lines); p_ptrn_lines += 1 - p_first; } else if (p_first) p_ptrn_lines = 1; else { p_ptrn_lines = 0; p_first = 1; } p_max = p_ptrn_lines + 6; /* we need this much at least */ while (p_max >= hunkmax) if (! grow_hunkmax ()) return -1; p_max = hunkmax; break; case '-': if (buf[1] != '-') goto change_line; if (ptrn_prefix_context == -1) ptrn_prefix_context = context; ptrn_suffix_context = context; if (repl_beginning || (p_end != p_ptrn_lines + 1 + (p_Char[p_end - 1] == '\n'))) { if (p_end == 1) { /* `Old' lines were omitted. Set up to fill them in from `new' context lines. */ ptrn_missing = true; p_end = p_ptrn_lines + 1; ptrn_prefix_context = ptrn_suffix_context = -1; fillsrc = p_end + 1; filldst = 1; fillcnt = p_ptrn_lines; } else if (! repl_beginning) fatal ("%s `---' at line %s; check line numbers at line %s", (p_end <= p_ptrn_lines ? "Premature" : "Overdue"), format_linenum (numbuf0, p_input_line), format_linenum (numbuf1, p_hunk_beg)); else if (! repl_could_be_missing) fatal ("duplicate `---' at line %s; check line numbers at line %s", format_linenum (numbuf0, p_input_line), format_linenum (numbuf1, p_hunk_beg + repl_beginning)); else { repl_missing = true; goto hunk_done; } } repl_beginning = p_end; repl_backtrack_position = file_tell (pfp); repl_patch_line = p_input_line; repl_context = context; p_len[p_end] = strlen (buf); if (! (p_line[p_end] = savestr (buf))) { p_end--; return -1; } p_Char[p_end] = '='; for (s = buf; *s && ! ISDIGIT (*s); s++) continue; s = scan_linenum (s, &p_newfirst); if (*s == ',') { do { if (!*++s) malformed (); } while (! ISDIGIT (*s)); scan_linenum (s, &p_repl_lines); p_repl_lines += 1 - p_newfirst; } else if (p_newfirst) p_repl_lines = 1; else { p_repl_lines = 0; p_newfirst = 1; } p_max = p_repl_lines + p_end; while (p_max >= hunkmax) if (! grow_hunkmax ()) return -1; if (p_repl_lines != ptrn_copiable && (p_prefix_context != 0 || context != 0 || p_repl_lines != 1)) repl_could_be_missing = false; context = 0; break; case '+': case '!': repl_could_be_missing = false; change_line: s = buf + 1; chars_read--; if (*s == '\n' && canonicalize) { strcpy (s, " \n"); chars_read = 2; } if (*s == ' ' || *s == '\t') { s++; chars_read--; } else if (repl_beginning && repl_could_be_missing) { repl_missing = true; goto hunk_done; } if (! repl_beginning) { if (ptrn_prefix_context == -1) ptrn_prefix_context = context; } else { if (repl_prefix_context == -1) repl_prefix_context = context; } chars_read -= (1 < chars_read && p_end == (repl_beginning ? p_max : p_ptrn_lines) && incomplete_line ()); p_len[p_end] = chars_read; if (! (p_line[p_end] = savebuf (s, chars_read))) { p_end--; return -1; } context = 0; break; case '\t': case '\n': /* assume spaces got eaten */ s = buf; if (*buf == '\t') { s++; chars_read--; } if (repl_beginning && repl_could_be_missing && (!ptrn_spaces_eaten || difftype == NEW_CONTEXT_DIFF) ) { repl_missing = true; goto hunk_done; } chars_read -= (1 < chars_read && p_end == (repl_beginning ? p_max : p_ptrn_lines) && incomplete_line ()); p_len[p_end] = chars_read; if (! (p_line[p_end] = savebuf (buf, chars_read))) { p_end--; return -1; } if (p_end != p_ptrn_lines + 1) { ptrn_spaces_eaten |= (repl_beginning != 0); some_context = true; context++; if (repl_beginning) repl_copiable++; else ptrn_copiable++; p_Char[p_end] = ' '; } break; case ' ': s = buf + 1; chars_read--; if (*s == '\n' && canonicalize) { strcpy (s, "\n"); chars_read = 2; } if (*s == ' ' || *s == '\t') { s++; chars_read--; } else if (repl_beginning && repl_could_be_missing) { repl_missing = true; goto hunk_done; } some_context = true; context++; if (repl_beginning) repl_copiable++; else ptrn_copiable++; chars_read -= (1 < chars_read && p_end == (repl_beginning ? p_max : p_ptrn_lines) && incomplete_line ()); p_len[p_end] = chars_read; if (! (p_line[p_end] = savebuf (buf + 2, chars_read))) { p_end--; return -1; } break; default: if (repl_beginning && repl_could_be_missing) { repl_missing = true; goto hunk_done; } malformed (); } } hunk_done: if (p_end >=0 && !repl_beginning) fatal ("no `---' found in patch at line %s", format_linenum (numbuf0, pch_hunk_beg ())); if (repl_missing) { /* reset state back to just after --- */ p_input_line = repl_patch_line; context = repl_context; for (p_end--; p_end > repl_beginning; p_end--) free(p_line[p_end]); Fseek (pfp, repl_backtrack_position, SEEK_SET); /* redundant 'new' context lines were omitted - set */ /* up to fill them in from the old file context */ fillsrc = 1; filldst = repl_beginning+1; fillcnt = p_repl_lines; p_end = p_max; } else if (! ptrn_missing && ptrn_copiable != repl_copiable) fatal ("context mangled in hunk at line %s", format_linenum (numbuf0, p_hunk_beg)); else if (!some_context && fillcnt == 1) { /* the first hunk was a null hunk with no context */ /* and we were expecting one line -- fix it up. */ while (filldst < p_end) { p_line[filldst] = p_line[filldst+1]; p_Char[filldst] = p_Char[filldst+1]; p_len[filldst] = p_len[filldst+1]; filldst++; } #if 0 repl_beginning--; /* this doesn't need to be fixed */ #endif p_end--; p_first++; /* do append rather than insert */ fillcnt = 0; p_ptrn_lines = 0; } p_prefix_context = ((repl_prefix_context == -1 || (ptrn_prefix_context != -1 && ptrn_prefix_context < repl_prefix_context)) ? ptrn_prefix_context : repl_prefix_context); p_suffix_context = ((ptrn_suffix_context != -1 && ptrn_suffix_context < context) ? ptrn_suffix_context : context); assert (p_prefix_context != -1 && p_suffix_context != -1); if (difftype == CONTEXT_DIFF && (fillcnt || (p_first > 1 && p_prefix_context + p_suffix_context < ptrn_copiable))) { if (verbosity == VERBOSE) say ("%s\n%s\n%s\n", "(Fascinating -- this is really a new-style context diff but without", "the telltale extra asterisks on the *** line that usually indicate", "the new style...)"); diff_type = difftype = NEW_CONTEXT_DIFF; } /* if there were omitted context lines, fill them in now */ if (fillcnt) { p_bfake = filldst; /* remember where not to free() */ p_efake = filldst + fillcnt - 1; while (fillcnt-- > 0) { while (fillsrc <= p_end && fillsrc != repl_beginning && p_Char[fillsrc] != ' ') fillsrc++; if (p_end < fillsrc || fillsrc == repl_beginning) { fatal ("replacement text or line numbers mangled in hunk at line %s", format_linenum (numbuf0, p_hunk_beg)); } p_line[filldst] = p_line[fillsrc]; p_Char[filldst] = p_Char[fillsrc]; p_len[filldst] = p_len[fillsrc]; fillsrc++; filldst++; } while (fillsrc <= p_end && fillsrc != repl_beginning) { if (p_Char[fillsrc] == ' ') fatal ("replacement text or line numbers mangled in hunk at line %s", format_linenum (numbuf0, p_hunk_beg)); fillsrc++; } if (debug & 64) printf ("fillsrc %s, filldst %s, rb %s, e+1 %s\n", format_linenum (numbuf0, fillsrc), format_linenum (numbuf1, filldst), format_linenum (numbuf2, repl_beginning), format_linenum (numbuf3, p_end + 1)); assert(fillsrc==p_end+1 || fillsrc==repl_beginning); assert(filldst==p_end+1 || filldst==repl_beginning); } } else if (difftype == UNI_DIFF) { file_offset line_beginning = file_tell (pfp); /* file pos of the current line */ register LINENUM fillsrc; /* index of old lines */ register LINENUM filldst; /* index of new lines */ char ch = '\0'; chars_read = get_line (); if (chars_read == (size_t) -1 || chars_read <= 4 || strncmp (buf, "@@ -", 4) != 0) { next_intuit_at(line_beginning,p_input_line); return chars_read == (size_t) -1 ? -1 : 0; } s = scan_linenum (buf + 4, &p_first); if (*s == ',') s = scan_linenum (s + 1, &p_ptrn_lines); else p_ptrn_lines = 1; if (*s == ' ') s++; if (*s != '+') malformed (); s = scan_linenum (s + 1, &p_newfirst); if (*s == ',') s = scan_linenum (s + 1, &p_repl_lines); else p_repl_lines = 1; if (*s == ' ') s++; if (*s != '@') malformed (); if (!p_ptrn_lines) p_first++; /* do append rather than insert */ if (!p_repl_lines) p_newfirst++; p_max = p_ptrn_lines + p_repl_lines + 1; while (p_max >= hunkmax) if (! grow_hunkmax ()) return -1; fillsrc = 1; filldst = fillsrc + p_ptrn_lines; p_end = filldst + p_repl_lines; sprintf (buf, "*** %s,%s ****\n", format_linenum (numbuf0, p_first), format_linenum (numbuf1, p_first + p_ptrn_lines - 1)); p_len[0] = strlen (buf); if (! (p_line[0] = savestr (buf))) { p_end = -1; return -1; } p_Char[0] = '*'; sprintf (buf, "--- %s,%s ----\n", format_linenum (numbuf0, p_newfirst), format_linenum (numbuf1, p_newfirst + p_repl_lines - 1)); p_len[filldst] = strlen (buf); if (! (p_line[filldst] = savestr (buf))) { p_end = 0; return -1; } p_Char[filldst++] = '='; p_prefix_context = -1; p_hunk_beg = p_input_line + 1; while (fillsrc <= p_ptrn_lines || filldst <= p_end) { chars_read = get_line (); if (!chars_read) { if (p_max - filldst < 3) { strcpy (buf, " \n"); /* assume blank lines got chopped */ chars_read = 2; } else { fatal ("unexpected end of file in patch"); } } if (chars_read == (size_t) -1) s = 0; else if (*buf == '\t' || *buf == '\n') { ch = ' '; /* assume the space got eaten */ s = savebuf (buf, chars_read); } else { ch = *buf; s = savebuf (buf+1, --chars_read); } if (!s) { while (--filldst > p_ptrn_lines) free(p_line[filldst]); p_end = fillsrc-1; return -1; } switch (ch) { case '-': if (fillsrc > p_ptrn_lines) { free(s); p_end = filldst-1; malformed (); } chars_read -= fillsrc == p_ptrn_lines && incomplete_line (); p_Char[fillsrc] = ch; p_line[fillsrc] = s; p_len[fillsrc++] = chars_read; break; case '=': ch = ' '; /* FALL THROUGH */ case ' ': if (fillsrc > p_ptrn_lines) { free(s); while (--filldst > p_ptrn_lines) free(p_line[filldst]); p_end = fillsrc-1; malformed (); } context++; chars_read -= fillsrc == p_ptrn_lines && incomplete_line (); p_Char[fillsrc] = ch; p_line[fillsrc] = s; p_len[fillsrc++] = chars_read; s = savebuf (s, chars_read); if (!s) { while (--filldst > p_ptrn_lines) free(p_line[filldst]); p_end = fillsrc-1; return -1; } /* FALL THROUGH */ case '+': if (filldst > p_end) { free(s); while (--filldst > p_ptrn_lines) free(p_line[filldst]); p_end = fillsrc-1; malformed (); } chars_read -= filldst == p_end && incomplete_line (); p_Char[filldst] = ch; p_line[filldst] = s; p_len[filldst++] = chars_read; break; default: p_end = filldst; malformed (); } if (ch != ' ') { if (p_prefix_context == -1) p_prefix_context = context; context = 0; } }/* while */ if (p_prefix_context == -1) malformed (); p_suffix_context = context; } else { /* normal diff--fake it up */ char hunk_type; register int i; LINENUM min, max; file_offset line_beginning = file_tell (pfp); p_prefix_context = p_suffix_context = 0; chars_read = get_line (); if (chars_read == (size_t) -1 || !chars_read || !ISDIGIT (*buf)) { next_intuit_at(line_beginning,p_input_line); return chars_read == (size_t) -1 ? -1 : 0; } s = scan_linenum (buf, &p_first); if (*s == ',') { s = scan_linenum (s + 1, &p_ptrn_lines); p_ptrn_lines += 1 - p_first; } else p_ptrn_lines = (*s != 'a'); hunk_type = *s; if (hunk_type == 'a') p_first++; /* do append rather than insert */ s = scan_linenum (s + 1, &min); if (*s == ',') scan_linenum (s + 1, &max); else max = min; if (hunk_type == 'd') min++; p_end = p_ptrn_lines + 1 + max - min + 1; while (p_end >= hunkmax) if (! grow_hunkmax ()) { p_end = -1; return -1; } p_newfirst = min; p_repl_lines = max - min + 1; sprintf (buf, "*** %s,%s\n", format_linenum (numbuf0, p_first), format_linenum (numbuf1, p_first + p_ptrn_lines - 1)); p_len[0] = strlen (buf); if (! (p_line[0] = savestr (buf))) { p_end = -1; return -1; } p_Char[0] = '*'; for (i=1; i<=p_ptrn_lines; i++) { chars_read = get_line (); if (chars_read == (size_t) -1) { p_end = i - 1; return -1; } if (!chars_read) fatal ("unexpected end of file in patch at line %s", format_linenum (numbuf0, p_input_line)); if (buf[0] != '<' || (buf[1] != ' ' && buf[1] != '\t')) fatal ("`<' expected at line %s of patch", format_linenum (numbuf0, p_input_line)); chars_read -= 2 + (i == p_ptrn_lines && incomplete_line ()); p_len[i] = chars_read; if (! (p_line[i] = savebuf (buf + 2, chars_read))) { p_end = i-1; return -1; } p_Char[i] = '-'; } if (hunk_type == 'c') { chars_read = get_line (); if (chars_read == (size_t) -1) { p_end = i - 1; return -1; } if (! chars_read) fatal ("unexpected end of file in patch at line %s", format_linenum (numbuf0, p_input_line)); if (*buf != '-') fatal ("`---' expected at line %s of patch", format_linenum (numbuf0, p_input_line)); } sprintf (buf, "--- %s,%s\n", format_linenum (numbuf0, min), format_linenum (numbuf1, max)); p_len[i] = strlen (buf); if (! (p_line[i] = savestr (buf))) { p_end = i-1; return -1; } p_Char[i] = '='; for (i++; i<=p_end; i++) { chars_read = get_line (); if (chars_read == (size_t) -1) { p_end = i - 1; return -1; } if (!chars_read) fatal ("unexpected end of file in patch at line %s", format_linenum (numbuf0, p_input_line)); if (buf[0] != '>' || (buf[1] != ' ' && buf[1] != '\t')) fatal ("`>' expected at line %s of patch", format_linenum (numbuf0, p_input_line)); chars_read -= 2 + (i == p_end && incomplete_line ()); p_len[i] = chars_read; if (! (p_line[i] = savebuf (buf + 2, chars_read))) { p_end = i-1; return -1; } p_Char[i] = '+'; } } if (rev) /* backwards patch? */ if (!pch_swap()) say ("Not enough memory to swap next hunk!\n"); if (debug & 2) { LINENUM i; char special; for (i=0; i <= p_end; i++) { if (i == p_ptrn_lines) special = '^'; else special = ' '; fprintf (stderr, "%s %c %c ", format_linenum (numbuf0, i), p_Char[i], special); pch_write_line (i, stderr); fflush (stderr); } } if (p_end+1 < hunkmax) /* paranoia reigns supreme... */ p_Char[p_end+1] = '^'; /* add a stopper for apply_hunk */ return 1; } static size_t get_line (void) { return pget_line (p_indent, p_rfc934_nesting, p_strip_trailing_cr, p_pass_comments_through); } /* Input a line from the patch file, worrying about indentation. Strip up to INDENT characters' worth of leading indentation. Then remove up to RFC934_NESTING instances of leading "- ". If STRIP_TRAILING_CR is true, remove any trailing carriage-return. Unless PASS_COMMENTS_THROUGH is true, ignore any resulting lines that begin with '#'; they're comments. Ignore any partial lines at end of input, but warn about them. Succeed if a line was read; it is terminated by "\n\0" for convenience. Return the number of characters read, including '\n' but not '\0'. Return -1 if we ran out of memory. */ static size_t pget_line (int indent, int rfc934_nesting, bool strip_trailing_cr, bool pass_comments_through) { register FILE *fp = pfp; register int c; register int i; register char *b; register size_t s; do { i = 0; for (;;) { c = getc (fp); if (c == EOF) { if (ferror (fp)) read_fatal (); return 0; } if (indent <= i) break; if (c == ' ' || c == 'X') i++; else if (c == '\t') i = (i + 8) & ~7; else break; } i = 0; b = buf; while (c == '-' && 0 <= --rfc934_nesting) { c = getc (fp); if (c == EOF) goto patch_ends_in_middle_of_line; if (c != ' ') { i = 1; b[0] = '-'; break; } c = getc (fp); if (c == EOF) goto patch_ends_in_middle_of_line; } s = bufsize; for (;;) { if (i == s - 1) { s *= 2; b = realloc (b, s); if (!b) { if (!using_plan_a) memory_fatal (); return (size_t) -1; } buf = b; bufsize = s; } b[i++] = c; if (c == '\n') break; c = getc (fp); if (c == EOF) goto patch_ends_in_middle_of_line; } p_input_line++; } while (*b == '#' && !pass_comments_through); if (strip_trailing_cr && 2 <= i && b[i - 2] == '\r') b[i-- - 2] = '\n'; b[i] = '\0'; return i; patch_ends_in_middle_of_line: if (ferror (fp)) read_fatal (); say ("patch unexpectedly ends in middle of line\n"); return 0; } static bool incomplete_line (void) { register FILE *fp = pfp; register int c; register file_offset line_beginning = file_tell (fp); if (getc (fp) == '\\') { while ((c = getc (fp)) != '\n' && c != EOF) continue; return true; } else { /* We don't trust ungetc. */ Fseek (pfp, line_beginning, SEEK_SET); return false; } } /* Reverse the old and new portions of the current hunk. */ bool pch_swap (void) { char **tp_line; /* the text of the hunk */ size_t *tp_len; /* length of each line */ char *tp_char; /* +, -, and ! */ register LINENUM i; register LINENUM n; bool blankline = false; register char *s; i = p_first; p_first = p_newfirst; p_newfirst = i; /* make a scratch copy */ tp_line = p_line; tp_len = p_len; tp_char = p_Char; p_line = 0; /* force set_hunkmax to allocate again */ p_len = 0; p_Char = 0; set_hunkmax(); if (!p_line || !p_len || !p_Char) { if (p_line) free (p_line); p_line = tp_line; if (p_len) free (p_len); p_len = tp_len; if (p_Char) free (p_Char); p_Char = tp_char; return false; /* not enough memory to swap hunk! */ } /* now turn the new into the old */ i = p_ptrn_lines + 1; if (tp_char[i] == '\n') { /* account for possible blank line */ blankline = true; i++; } if (p_efake >= 0) { /* fix non-freeable ptr range */ if (p_efake <= i) n = p_end - i + 1; else n = -i; p_efake += n; p_bfake += n; } for (n=0; i <= p_end; i++,n++) { p_line[n] = tp_line[i]; p_Char[n] = tp_char[i]; if (p_Char[n] == '+') p_Char[n] = '-'; p_len[n] = tp_len[i]; } if (blankline) { i = p_ptrn_lines + 1; p_line[n] = tp_line[i]; p_Char[n] = tp_char[i]; p_len[n] = tp_len[i]; n++; } assert(p_Char[0] == '='); p_Char[0] = '*'; for (s=p_line[0]; *s; s++) if (*s == '-') *s = '*'; /* now turn the old into the new */ assert(tp_char[0] == '*'); tp_char[0] = '='; for (s=tp_line[0]; *s; s++) if (*s == '*') *s = '-'; for (i=0; n <= p_end; i++,n++) { p_line[n] = tp_line[i]; p_Char[n] = tp_char[i]; if (p_Char[n] == '-') p_Char[n] = '+'; p_len[n] = tp_len[i]; } assert(i == p_ptrn_lines + 1); i = p_ptrn_lines; p_ptrn_lines = p_repl_lines; p_repl_lines = i; if (tp_line) free (tp_line); if (tp_len) free (tp_len); if (tp_char) free (tp_char); return true; } /* Return whether file WHICH (false = old, true = new) appears to nonexistent. Return 1 for empty, 2 for nonexistent. */ int pch_says_nonexistent (bool which) { return p_says_nonexistent[which]; } /* Return timestamp of patch header for file WHICH (false = old, true = new), or -1 if there was no timestamp or an error in the timestamp. */ time_t pch_timestamp (bool which) { return p_timestamp[which]; } /* Return the specified line position in the old file of the old context. */ LINENUM pch_first (void) { return p_first; } /* Return the number of lines of old context. */ LINENUM pch_ptrn_lines (void) { return p_ptrn_lines; } /* Return the probable line position in the new file of the first line. */ LINENUM pch_newfirst (void) { return p_newfirst; } /* Return the number of lines in the replacement text including context. */ LINENUM pch_repl_lines (void) { return p_repl_lines; } /* Return the number of lines in the whole hunk. */ LINENUM pch_end (void) { return p_end; } /* Return the number of context lines before the first changed line. */ LINENUM pch_prefix_context (void) { return p_prefix_context; } /* Return the number of context lines after the last changed line. */ LINENUM pch_suffix_context (void) { return p_suffix_context; } /* Return the length of a particular patch line. */ size_t pch_line_len (LINENUM line) { return p_len[line]; } /* Return the control character (+, -, *, !, etc) for a patch line. */ char pch_char (LINENUM line) { return p_Char[line]; } /* Return a pointer to a particular patch line. */ char * pfetch (LINENUM line) { return p_line[line]; } /* Output a patch line. */ bool pch_write_line (LINENUM line, FILE *file) { bool after_newline = p_line[line][p_len[line] - 1] == '\n'; if (! fwrite (p_line[line], sizeof (*p_line[line]), p_len[line], file)) write_fatal (); return after_newline; } /* Return where in the patch file this hunk began, for error messages. */ LINENUM pch_hunk_beg (void) { return p_hunk_beg; } /* Is the newline-terminated line a valid `ed' command for patch input? If so, return the command character; if not, return 0. This accepts accepts just a subset of the valid commands, but it's good enough in practice. */ static char get_ed_command_letter (char const *line) { char const *p = line; char letter; bool pair = false; if (! ISDIGIT (*p)) return 0; while (ISDIGIT (*++p)) continue; if (*p == ',') { if (! ISDIGIT (*++p)) return 0; while (ISDIGIT (*++p)) continue; pair = true; } letter = *p++; switch (letter) { case 'a': case 'i': if (pair) return 0; break; case 'c': case 'd': break; case 's': if (strncmp (p, "/.//", 4) != 0) return 0; p += 4; break; default: return 0; } while (*p == ' ' || *p == '\t') p++; if (*p == '\n') return letter; return 0; } /* Apply an ed script by feeding ed itself. */ void do_ed_script (FILE *ofp) { static char const ed_program[] = ed_PROGRAM; register file_offset beginning_of_this_line; register FILE *pipefp = 0; register size_t chars_read; if (! dry_run && ! skip_rest_of_patch) { int exclusive = TMPOUTNAME_needs_removal ? 0 : O_EXCL; assert (! inerrno); TMPOUTNAME_needs_removal = 1; copy_file (inname, TMPOUTNAME, exclusive, instat.st_mode); sprintf (buf, "%s %s%s", ed_program, verbosity == VERBOSE ? "" : "- ", TMPOUTNAME); fflush (stdout); pipefp = popen(buf, binary_transput ? "wb" : "w"); if (!pipefp) pfatal ("Can't open pipe to %s", quotearg (buf)); } for (;;) { char ed_command_letter; beginning_of_this_line = file_tell (pfp); chars_read = get_line (); if (! chars_read) { next_intuit_at(beginning_of_this_line,p_input_line); break; } ed_command_letter = get_ed_command_letter (buf); if (ed_command_letter) { if (pipefp) if (! fwrite (buf, sizeof *buf, chars_read, pipefp)) write_fatal (); if (ed_command_letter != 'd' && ed_command_letter != 's') { p_pass_comments_through = true; while ((chars_read = get_line ()) != 0) { if (pipefp) if (! fwrite (buf, sizeof *buf, chars_read, pipefp)) write_fatal (); if (chars_read == 2 && strEQ (buf, ".\n")) break; } p_pass_comments_through = false; } } else { next_intuit_at(beginning_of_this_line,p_input_line); break; } } if (!pipefp) return; if (fwrite ("w\nq\n", sizeof (char), (size_t) 4, pipefp) == 0 || fflush (pipefp) != 0) write_fatal (); if (pclose (pipefp) != 0) fatal ("%s FAILED", ed_program); if (ofp) { FILE *ifp = fopen (TMPOUTNAME, binary_transput ? "rb" : "r"); int c; if (!ifp) pfatal ("can't open `%s'", TMPOUTNAME); while ((c = getc (ifp)) != EOF) if (putc (c, ofp) == EOF) write_fatal (); if (ferror (ifp) || fclose (ifp) != 0) read_fatal (); } }
/* Copyright (C) 1998, 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef DIRNAME_H_ # define DIRNAME_H_ 1 # ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif # endif # ifndef DIRECTORY_SEPARATOR # define DIRECTORY_SEPARATOR '/' # endif # ifndef ISSLASH # define ISSLASH(C) ((C) == DIRECTORY_SEPARATOR) # endif # ifndef FILESYSTEM_PREFIX_LEN # define FILESYSTEM_PREFIX_LEN(Filename) 0 # endif char *base_name PARAMS ((char const *path)); char *dir_name PARAMS ((char const *path)); size_t base_len PARAMS ((char const *path)); size_t dir_len PARAMS ((char const *path)); int strip_trailing_slashes PARAMS ((char *path)); #endif /* not DIRNAME_H_ */
/* quote.c - quote arguments for output Copyright (C) 1998, 1999, 2000, 2001, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert <eggert@twinsun.com> */ #if HAVE_CONFIG_H # include <config.h> #endif #if HAVE_STDDEF_H # include <stddef.h> /* For the definition of size_t on windows w/MSVC. */ #endif #include <sys/types.h> #include "quotearg.h" #include "quote.h" /* Return an unambiguous printable representation of NAME, allocated in slot N, suitable for diagnostics. */ char const * quote_n (int n, char const *name) { return quotearg_n_style (n, locale_quoting_style, name); } /* Return an unambiguous printable representation of NAME, suitable for diagnostics. */ char const * quote (char const *name) { return quote_n (0, name); }
/* quotearg.c - quote arguments for output Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert <eggert@twinsun.com> */ #if HAVE_CONFIG_H # include <config.h> #endif #include "quotearg.h" #include "xalloc.h" #include <ctype.h> #include <errno.h> #include <limits.h> #include <stdlib.h> #include <string.h> #include "gettext.h" #define _(msgid) gettext (msgid) #define N_(msgid) msgid #if HAVE_WCHAR_H /* BSD/OS 4.1 wchar.h requires FILE and struct tm to be declared. */ # include <stdio.h> # include <time.h> # include <wchar.h> #endif #if !HAVE_MBRTOWC /* Disable multibyte processing entirely. Since MB_CUR_MAX is 1, the other macros are defined only for documentation and to satisfy C syntax. */ # undef MB_CUR_MAX # define MB_CUR_MAX 1 # define mbrtowc(pwc, s, n, ps) ((*(pwc) = *(s)) != 0) # define iswprint(wc) isprint ((unsigned char) (wc)) # undef HAVE_MBSINIT #endif #if !defined mbsinit && !HAVE_MBSINIT # define mbsinit(ps) 1 #endif #ifndef iswprint # if HAVE_WCTYPE_H # include <wctype.h> # endif # if !defined iswprint && !HAVE_ISWPRINT # define iswprint(wc) 1 # endif #endif #ifndef SIZE_MAX # define SIZE_MAX ((size_t) -1) #endif #define INT_BITS (sizeof (int) * CHAR_BIT) struct quoting_options { /* Basic quoting style. */ enum quoting_style style; /* Quote the characters indicated by this bit vector even if the quoting style would not normally require them to be quoted. */ int quote_these_too[(UCHAR_MAX / INT_BITS) + 1]; }; /* Names of quoting styles. */ char const *const quoting_style_args[] = { "literal", "shell", "shell-always", "c", "escape", "locale", "clocale", 0 }; /* Correspondences to quoting style names. */ enum quoting_style const quoting_style_vals[] = { literal_quoting_style, shell_quoting_style, shell_always_quoting_style, c_quoting_style, escape_quoting_style, locale_quoting_style, clocale_quoting_style }; /* The default quoting options. */ static struct quoting_options default_quoting_options; /* Allocate a new set of quoting options, with contents initially identical to O if O is not null, or to the default if O is null. It is the caller's responsibility to free the result. */ struct quoting_options * clone_quoting_options (struct quoting_options *o) { int e = errno; struct quoting_options *p = xmalloc (sizeof *p); *p = *(o ? o : &default_quoting_options); errno = e; return p; } /* Get the value of O's quoting style. If O is null, use the default. */ enum quoting_style get_quoting_style (struct quoting_options *o) { return (o ? o : &default_quoting_options)->style; } /* In O (or in the default if O is null), set the value of the quoting style to S. */ void set_quoting_style (struct quoting_options *o, enum quoting_style s) { (o ? o : &default_quoting_options)->style = s; } /* In O (or in the default if O is null), set the value of the quoting options for character C to I. Return the old value. Currently, the only values defined for I are 0 (the default) and 1 (which means to quote the character even if it would not otherwise be quoted). */ int set_char_quoting (struct quoting_options *o, char c, int i) { unsigned char uc = c; int *p = (o ? o : &default_quoting_options)->quote_these_too + uc / INT_BITS; int shift = uc % INT_BITS; int r = (*p >> shift) & 1; *p ^= ((i & 1) ^ r) << shift; return r; } /* MSGID approximates a quotation mark. Return its translation if it has one; otherwise, return either it or "\"", depending on S. */ static char const * gettext_quote (char const *msgid, enum quoting_style s) { char const *translation = _(msgid); if (translation == msgid && s == clocale_quoting_style) translation = "\""; return translation; } /* Place into buffer BUFFER (of size BUFFERSIZE) a quoted version of argument ARG (of size ARGSIZE), using QUOTING_STYLE and the non-quoting-style part of O to control quoting. Terminate the output with a null character, and return the written size of the output, not counting the terminating null. If BUFFERSIZE is too small to store the output string, return the value that would have been returned had BUFFERSIZE been large enough. If ARGSIZE is -1, use the string length of the argument for ARGSIZE. This function acts like quotearg_buffer (BUFFER, BUFFERSIZE, ARG, ARGSIZE, O), except it uses QUOTING_STYLE instead of the quoting style specified by O, and O may not be null. */ static size_t quotearg_buffer_restyled (char *buffer, size_t buffersize, char const *arg, size_t argsize, enum quoting_style quoting_style, struct quoting_options const *o) { size_t i; size_t len = 0; char const *quote_string = 0; size_t quote_string_len = 0; int backslash_escapes = 0; int unibyte_locale = MB_CUR_MAX == 1; #define STORE(c) \ do \ { \ if (len < buffersize) \ buffer[len] = (c); \ len++; \ } \ while (0) switch (quoting_style) { case c_quoting_style: STORE ('"'); backslash_escapes = 1; quote_string = "\""; quote_string_len = 1; break; case escape_quoting_style: backslash_escapes = 1; break; case locale_quoting_style: case clocale_quoting_style: { /* Get translations for open and closing quotation marks. The message catalog should translate "`" to a left quotation mark suitable for the locale, and similarly for "'". If the catalog has no translation, locale_quoting_style quotes `like this', and clocale_quoting_style quotes "like this". For example, an American English Unicode locale should translate "`" to U+201C (LEFT DOUBLE QUOTATION MARK), and should translate "'" to U+201D (RIGHT DOUBLE QUOTATION MARK). A British English Unicode locale should instead translate these to U+2018 (LEFT SINGLE QUOTATION MARK) and U+2019 (RIGHT SINGLE QUOTATION MARK), respectively. */ char const *left = gettext_quote (N_("`"), quoting_style); char const *right = gettext_quote (N_("'"), quoting_style); for (quote_string = left; *quote_string; quote_string++) STORE (*quote_string); backslash_escapes = 1; quote_string = right; quote_string_len = strlen (quote_string); } break; case shell_always_quoting_style: STORE ('\''); quote_string = "'"; quote_string_len = 1; break; default: break; } for (i = 0; ! (argsize == SIZE_MAX ? arg[i] == '\0' : i == argsize); i++) { unsigned char c; unsigned char esc; if (backslash_escapes && quote_string_len && i + quote_string_len <= argsize && memcmp (arg + i, quote_string, quote_string_len) == 0) STORE ('\\'); c = arg[i]; switch (c) { case '\0': if (backslash_escapes) { STORE ('\\'); STORE ('0'); STORE ('0'); c = '0'; } break; case '?': switch (quoting_style) { case shell_quoting_style: goto use_shell_always_quoting_style; case c_quoting_style: if (i + 2 < argsize && arg[i + 1] == '?') switch (arg[i + 2]) { case '!': case '\'': case '(': case ')': case '-': case '/': case '<': case '=': case '>': /* Escape the second '?' in what would otherwise be a trigraph. */ c = arg[i + 2]; i += 2; STORE ('?'); STORE ('\\'); STORE ('?'); break; } break; default: break; } break; case '\a': esc = 'a'; goto c_escape; case '\b': esc = 'b'; goto c_escape; case '\f': esc = 'f'; goto c_escape; case '\n': esc = 'n'; goto c_and_shell_escape; case '\r': esc = 'r'; goto c_and_shell_escape; case '\t': esc = 't'; goto c_and_shell_escape; case '\v': esc = 'v'; goto c_escape; case '\\': esc = c; goto c_and_shell_escape; c_and_shell_escape: if (quoting_style == shell_quoting_style) goto use_shell_always_quoting_style; c_escape: if (backslash_escapes) { c = esc; goto store_escape; } break; case '#': case '~': if (i != 0) break; /* Fall through. */ case ' ': case '!': /* special in bash */ case '"': case '$': case '&': case '(': case ')': case '*': case ';': case '<': case '>': case '[': case '^': /* special in old /bin/sh, e.g. SunOS 4.1.4 */ case '`': case '|': /* A shell special character. In theory, '$' and '`' could be the first bytes of multibyte characters, which means we should check them with mbrtowc, but in practice this doesn't happen so it's not worth worrying about. */ if (quoting_style == shell_quoting_style) goto use_shell_always_quoting_style; break; case '\'': switch (quoting_style) { case shell_quoting_style: goto use_shell_always_quoting_style; case shell_always_quoting_style: STORE ('\''); STORE ('\\'); STORE ('\''); break; default: break; } break; case '%': case '+': case ',': case '-': case '.': case '/': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case ':': case '=': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case ']': case '_': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '{': case '}': /* These characters don't cause problems, no matter what the quoting style is. They cannot start multibyte sequences. */ break; default: /* If we have a multibyte sequence, copy it until we reach its end, find an error, or come back to the initial shift state. For C-like styles, if the sequence has unprintable characters, escape the whole sequence, since we can't easily escape single characters within it. */ { /* Length of multibyte sequence found so far. */ size_t m; int printable; if (unibyte_locale) { m = 1; printable = isprint (c); } else { mbstate_t mbstate; memset (&mbstate, 0, sizeof mbstate); m = 0; printable = 1; if (argsize == SIZE_MAX) argsize = strlen (arg); do { wchar_t w; size_t bytes = mbrtowc (&w, &arg[i + m], argsize - (i + m), &mbstate); if (bytes == 0) break; else if (bytes == (size_t) -1) { printable = 0; break; } else if (bytes == (size_t) -2) { printable = 0; while (i + m < argsize && arg[i + m]) m++; break; } else { if (! iswprint (w)) printable = 0; m += bytes; } } while (! mbsinit (&mbstate)); } if (1 < m || (backslash_escapes && ! printable)) { /* Output a multibyte sequence, or an escaped unprintable unibyte character. */ size_t ilim = i + m; for (;;) { if (backslash_escapes && ! printable) { STORE ('\\'); STORE ('0' + (c >> 6)); STORE ('0' + ((c >> 3) & 7)); c = '0' + (c & 7); } if (ilim <= i + 1) break; STORE (c); c = arg[++i]; } goto store_c; } } } if (! (backslash_escapes && o->quote_these_too[c / INT_BITS] & (1 << (c % INT_BITS)))) goto store_c; store_escape: STORE ('\\'); store_c: STORE (c); } if (quote_string) for (; *quote_string; quote_string++) STORE (*quote_string); if (len < buffersize) buffer[len] = '\0'; return len; use_shell_always_quoting_style: return quotearg_buffer_restyled (buffer, buffersize, arg, argsize, shell_always_quoting_style, o); } /* Place into buffer BUFFER (of size BUFFERSIZE) a quoted version of argument ARG (of size ARGSIZE), using O to control quoting. If O is null, use the default. Terminate the output with a null character, and return the written size of the output, not counting the terminating null. If BUFFERSIZE is too small to store the output string, return the value that would have been returned had BUFFERSIZE been large enough. If ARGSIZE is -1, use the string length of the argument for ARGSIZE. */ size_t quotearg_buffer (char *buffer, size_t buffersize, char const *arg, size_t argsize, struct quoting_options const *o) { struct quoting_options const *p = o ? o : &default_quoting_options; int e = errno; size_t r = quotearg_buffer_restyled (buffer, buffersize, arg, argsize, p->style, p); errno = e; return r; } /* Use storage slot N to return a quoted version of argument ARG. ARG is of size ARGSIZE, but if that is -1, ARG is a null-terminated string. OPTIONS specifies the quoting options. The returned value points to static storage that can be reused by the next call to this function with the same value of N. N must be nonnegative. N is deliberately declared with type "int" to allow for future extensions (using negative values). */ static char * quotearg_n_options (int n, char const *arg, size_t argsize, struct quoting_options const *options) { int e = errno; /* Preallocate a slot 0 buffer, so that the caller can always quote one small component of a "memory exhausted" message in slot 0. */ static char slot0[256]; static unsigned int nslots = 1; unsigned int n0 = n; struct slotvec { size_t size; char *val; }; static struct slotvec slotvec0 = {sizeof slot0, slot0}; static struct slotvec *slotvec = &slotvec0; if (n < 0) abort (); if (nslots <= n0) { unsigned int n1 = n0 + 1; size_t s = n1 * sizeof *slotvec; if (SIZE_MAX / UINT_MAX <= sizeof *slotvec && n1 != s / sizeof *slotvec) xalloc_die (); if (slotvec == &slotvec0) { slotvec = xmalloc (sizeof *slotvec); *slotvec = slotvec0; } slotvec = xrealloc (slotvec, s); memset (slotvec + nslots, 0, (n1 - nslots) * sizeof *slotvec); nslots = n1; } { size_t size = slotvec[n].size; char *val = slotvec[n].val; size_t qsize = quotearg_buffer (val, size, arg, argsize, options); if (size <= qsize) { slotvec[n].size = size = qsize + 1; slotvec[n].val = val = xrealloc (val == slot0 ? 0 : val, size); quotearg_buffer (val, size, arg, argsize, options); } errno = e; return val; } } char * quotearg_n (int n, char const *arg) { return quotearg_n_options (n, arg, SIZE_MAX, &default_quoting_options); } char * quotearg (char const *arg) { return quotearg_n (0, arg); } /* Return quoting options for STYLE, with no extra quoting. */ static struct quoting_options quoting_options_from_style (enum quoting_style style) { struct quoting_options o; o.style = style; memset (o.quote_these_too, 0, sizeof o.quote_these_too); return o; } char * quotearg_n_style (int n, enum quoting_style s, char const *arg) { struct quoting_options const o = quoting_options_from_style (s); return quotearg_n_options (n, arg, SIZE_MAX, &o); } char * quotearg_n_style_mem (int n, enum quoting_style s, char const *arg, size_t argsize) { struct quoting_options const o = quoting_options_from_style (s); return quotearg_n_options (n, arg, argsize, &o); } char * quotearg_style (enum quoting_style s, char const *arg) { return quotearg_n_style (0, s, arg); } char * quotearg_char (char const *arg, char ch) { struct quoting_options options; options = default_quoting_options; set_char_quoting (&options, ch, 1); return quotearg_n_options (0, arg, SIZE_MAX, &options); } char * quotearg_colon (char const *arg) { return quotearg_char (arg, ':'); }
/* Shell command argument quoting. Copyright (C) 1994, 1995, 1997 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert <eggert@twinsun.com> */ #if HAVE_CONFIG_H # include <config.h> #endif #include <sys/types.h> #include <quotesys.h> /* Place into QUOTED a quoted version of ARG suitable for `system'. Return the length of the resulting string (which is not null-terminated). If QUOTED is null, return the length without any side effects. */ size_t quote_system_arg (quoted, arg) char *quoted; char const *arg; { char const *a; size_t len = 0; /* Scan ARG, copying it to QUOTED if QUOTED is not null, looking for shell metacharacters. */ for (a = arg; ; a++) { char c = *a; switch (c) { case 0: /* ARG has no shell metacharacters. */ return len; case '=': if (*arg == '-') break; /* Fall through. */ case '\t': case '\n': case ' ': case '!': case '"': case '#': case '$': case '%': case '&': case '\'': case '(': case ')': case '*': case ';': case '<': case '>': case '?': case '[': case '\\': case '^': case '`': case '|': case '~': { /* ARG has a shell metacharacter. Start over, quoting it this time. */ len = 0; c = *arg++; /* If ARG is an option, quote just its argument. This is not necessary, but it looks nicer. */ if (c == '-' && arg < a) { c = *arg++; if (quoted) { quoted[len] = '-'; quoted[len + 1] = c; } len += 2; if (c == '-') while (arg < a) { c = *arg++; if (quoted) quoted[len] = c; len++; if (c == '=') break; } c = *arg++; } if (quoted) quoted[len] = '\''; len++; for (; c; c = *arg++) { if (c == '\'') { if (quoted) { quoted[len] = '\''; quoted[len + 1] = '\\'; quoted[len + 2] = '\''; } len += 3; } if (quoted) quoted[len] = c; len++; } if (quoted) quoted[len] = '\''; return len + 1; } } if (quoted) quoted[len] = c; len++; } }
/* utility functions for `patch' */ /* $Id: util.c,v 1.36 2003/05/20 14:04:53 eggert Exp $ */ /* Copyright (C) 1986 Larry Wall Copyright (C) 1992, 1993, 1997, 1998, 1999, 2001, 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #define XTERN extern #include <common.h> #include <backupfile.h> #include <dirname.h> #include <quotearg.h> #include <quotesys.h> #include <version.h> #undef XTERN #define XTERN #include <util.h> #include <xalloc.h> #include <maketime.h> #include <partime.h> #include <signal.h> #if !defined SIGCHLD && defined SIGCLD #define SIGCHLD SIGCLD #endif #if ! HAVE_RAISE && ! defined raise # define raise(sig) kill (getpid (), sig) #endif #include <stdarg.h> static void makedirs (char *); /* Move a file FROM (where *FROM_NEEDS_REMOVAL is nonzero if FROM needs removal when cleaning up at the end of execution) to TO, renaming it if possible and copying it if necessary. If we must create TO, use MODE to create it. If FROM is null, remove TO (ignoring FROMSTAT). FROM_NEEDS_REMOVAL must be nonnull if FROM is nonnull. Back up TO if BACKUP is true. */ void move_file (char const *from, int volatile *from_needs_removal, char *to, mode_t mode, bool backup) { struct stat to_st; int to_errno = ! backup ? -1 : stat (to, &to_st) == 0 ? 0 : errno; if (backup) { int try_makedirs_errno = 0; char *bakname; if (origprae || origbase) { char const *p = origprae ? origprae : ""; char const *b = origbase ? origbase : ""; char const *o = base_name (to); size_t plen = strlen (p); size_t tlen = o - to; size_t blen = strlen (b); size_t osize = strlen (o) + 1; bakname = xmalloc (plen + tlen + blen + osize); memcpy (bakname, p, plen); memcpy (bakname + plen, to, tlen); memcpy (bakname + plen + tlen, b, blen); memcpy (bakname + plen + tlen + blen, o, osize); for (p += FILESYSTEM_PREFIX_LEN (p); *p; p++) if (ISSLASH (*p)) { try_makedirs_errno = ENOENT; break; } } else { bakname = find_backup_file_name (to, backup_type); if (!bakname) memory_fatal (); } if (to_errno) { int fd; if (debug & 4) say ("Creating empty unreadable file %s\n", quotearg (bakname)); try_makedirs_errno = ENOENT; unlink (bakname); while ((fd = creat (bakname, 0)) < 0) { if (errno != try_makedirs_errno) pfatal ("Can't create file %s", quotearg (bakname)); makedirs (bakname); try_makedirs_errno = 0; } if (close (fd) != 0) pfatal ("Can't close file %s", quotearg (bakname)); } else { if (debug & 4) say ("Renaming file %s to %s\n", quotearg_n (0, to), quotearg_n (1, bakname)); while (rename (to, bakname) != 0) { if (errno != try_makedirs_errno) pfatal ("Can't rename file %s to %s", quotearg_n (0, to), quotearg_n (1, bakname)); makedirs (bakname); try_makedirs_errno = 0; } } free (bakname); } if (from) { if (debug & 4) say ("Renaming file %s to %s\n", quotearg_n (0, from), quotearg_n (1, to)); if (rename (from, to) != 0) { bool to_dir_known_to_exist = false; if (errno == ENOENT && (to_errno == -1 || to_errno == ENOENT)) { makedirs (to); to_dir_known_to_exist = 1; if (rename (from, to) == 0) goto rename_succeeded; } if (errno == EXDEV) { if (! backup) { if (unlink (to) == 0) to_dir_known_to_exist = true; else if (errno != ENOENT) pfatal ("Can't remove file %s", quotearg (to)); } if (! to_dir_known_to_exist) makedirs (to); copy_file (from, to, 0, mode); return; } pfatal ("Can't rename file %s to %s", quotearg_n (0, from), quotearg_n (1, to)); } rename_succeeded: /* Do not clear *FROM_NEEDS_REMOVAL if it's possible that the rename returned zero because FROM and TO are hard links to the same file. */ if (0 < to_errno || (to_errno == 0 && to_st.st_nlink <= 1)) *from_needs_removal = 0; } else if (! backup) { if (debug & 4) say ("Removing file %s\n", quotearg (to)); if (unlink (to) != 0) pfatal ("Can't remove file %s", quotearg (to)); } } /* Create FILE with OPEN_FLAGS, and with MODE adjusted so that we can read and write the file and that the file is not executable. Return the file descriptor. */ int create_file (char const *file, int open_flags, mode_t mode) { int fd; mode |= S_IRUSR | S_IWUSR; mode &= ~ (S_IXUSR | S_IXGRP | S_IXOTH); if (! (O_CREAT && O_TRUNC)) close (creat (file, mode)); fd = open (file, O_CREAT | O_TRUNC | open_flags, mode); if (fd < 0) pfatal ("Can't create file %s", quotearg (file)); return fd; } /* Copy a file. */ void copy_file (char const *from, char const *to, int to_flags, mode_t mode) { int tofd; int fromfd; size_t i; if ((fromfd = open (from, O_RDONLY | O_BINARY)) < 0) pfatal ("Can't reopen file %s", quotearg (from)); tofd = create_file (to, O_WRONLY | O_BINARY | to_flags, mode); while ((i = read (fromfd, buf, bufsize)) != 0) { if (i == (size_t) -1) read_fatal (); if (write (tofd, buf, i) != i) write_fatal (); } if (close (fromfd) != 0) read_fatal (); if (close (tofd) != 0) write_fatal (); } static char const DEV_NULL[] = NULL_DEVICE; static char const RCSSUFFIX[] = ",v"; static char const CHECKOUT[] = "co %s"; static char const CHECKOUT_LOCKED[] = "co -l %s"; static char const RCSDIFF1[] = "rcsdiff %s"; static char const SCCSPREFIX[] = "s."; static char const GET[] = "get "; static char const GET_LOCKED[] = "get -e "; static char const SCCSDIFF1[] = "get -p "; static char const SCCSDIFF2[] = "|diff - %s"; static char const CLEARTOOL_CO[] = "cleartool co -unr -nc "; static char const PERFORCE_CO[] = "p4 edit "; /* Return "RCS" if FILENAME is controlled by RCS, "SCCS" if it is controlled by SCCS, "ClearCase" if it is controlled by Clearcase, "Perforce" if it is controlled by Perforce, and 0 otherwise. READONLY is true if we desire only readonly access to FILENAME. FILESTAT describes FILENAME's status or is 0 if FILENAME does not exist. If successful and if GETBUF is nonzero, set *GETBUF to a command that gets the file; similarly for DIFFBUF and a command to diff the file (but set *DIFFBUF to 0 if the diff operation is meaningless). *GETBUF and *DIFFBUF must be freed by the caller. */ char const * version_controller (char const *filename, bool readonly, struct stat const *filestat, char **getbuf, char **diffbuf) { struct stat cstat; char const *filebase = base_name (filename); char const *dotslash = *filename == '-' ? "./" : ""; size_t dirlen = filebase - filename; size_t filenamelen = strlen (filename); size_t maxfixlen = sizeof "SCCS/" - 1 + sizeof SCCSPREFIX - 1; size_t maxtrysize = filenamelen + maxfixlen + 1; size_t quotelen = quote_system_arg (0, filename); size_t maxgetsize = sizeof CLEARTOOL_CO + quotelen + maxfixlen; size_t maxdiffsize = (sizeof SCCSDIFF1 + sizeof SCCSDIFF2 + sizeof DEV_NULL - 1 + 2 * quotelen + maxfixlen); char *trybuf = xmalloc (maxtrysize); char const *r = 0; strcpy (trybuf, filename); #define try1(f,a1) (sprintf (trybuf + dirlen, f, a1), stat (trybuf, &cstat) == 0) #define try2(f,a1,a2) (sprintf (trybuf + dirlen, f, a1,a2), stat (trybuf, &cstat) == 0) /* Check that RCS file is not working file. Some hosts don't report file name length errors. */ if ((try2 ("RCS/%s%s", filebase, RCSSUFFIX) || try1 ("RCS/%s", filebase) || try2 ("%s%s", filebase, RCSSUFFIX)) && ! (filestat && filestat->st_dev == cstat.st_dev && filestat->st_ino == cstat.st_ino)) { if (getbuf) { char *p = *getbuf = xmalloc (maxgetsize); sprintf (p, readonly ? CHECKOUT : CHECKOUT_LOCKED, dotslash); p += strlen (p); p += quote_system_arg (p, filename); *p = '\0'; } if (diffbuf) { char *p = *diffbuf = xmalloc (maxdiffsize); sprintf (p, RCSDIFF1, dotslash); p += strlen (p); p += quote_system_arg (p, filename); *p++ = '>'; strcpy (p, DEV_NULL); } r = "RCS"; } else if (try2 ("SCCS/%s%s", SCCSPREFIX, filebase) || try2 ("%s%s", SCCSPREFIX, filebase)) { if (getbuf) { char *p = *getbuf = xmalloc (maxgetsize); sprintf (p, readonly ? GET : GET_LOCKED); p += strlen (p); p += quote_system_arg (p, trybuf); *p = '\0'; } if (diffbuf) { char *p = *diffbuf = xmalloc (maxdiffsize); strcpy (p, SCCSDIFF1); p += sizeof SCCSDIFF1 - 1; p += quote_system_arg (p, trybuf); sprintf (p, SCCSDIFF2, dotslash); p += strlen (p); p += quote_system_arg (p, filename); *p++ = '>'; strcpy (p, DEV_NULL); } r = "SCCS"; } else if (!readonly && filestat && try1 ("%s@@", filebase) && S_ISDIR (cstat.st_mode)) { if (getbuf) { char *p = *getbuf = xmalloc (maxgetsize); strcpy (p, CLEARTOOL_CO); p += sizeof CLEARTOOL_CO - 1; p += quote_system_arg (p, filename); *p = '\0'; } if (diffbuf) *diffbuf = 0; r = "ClearCase"; } else if (!readonly && filestat && (getenv("P4PORT") || getenv("P4USER") || getenv("P4CONFIG"))) { if (getbuf) { char *p = *getbuf = xmalloc (maxgetsize); strcpy (p, PERFORCE_CO); p += sizeof PERFORCE_CO - 1; p += quote_system_arg (p, filename); *p = '\0'; } if (diffbuf) *diffbuf = 0; r = "Perforce"; } free (trybuf); return r; } /* Get FILENAME from version control system CS. The file already exists if EXISTS. Only readonly access is needed if READONLY. Use the command GETBUF to actually get the named file. Store the resulting file status into *FILESTAT. Return true if successful. */ bool version_get (char const *filename, char const *cs, bool exists, bool readonly, char const *getbuf, struct stat *filestat) { if (patch_get < 0) { ask ("Get file %s from %s%s? [y] ", quotearg (filename), cs, readonly ? "" : " with lock"); if (*buf == 'n') return 0; } if (dry_run) { if (! exists) fatal ("can't do dry run on nonexistent version-controlled file %s; invoke `%s' and try again", quotearg (filename), getbuf); } else { if (verbosity == VERBOSE) say ("Getting file %s from %s%s...\n", quotearg (filename), cs, readonly ? "" : " with lock"); if (systemic (getbuf) != 0) fatal ("Can't get file %s from %s", quotearg (filename), cs); if (stat (filename, filestat) != 0) pfatal ("%s", quotearg (filename)); } return 1; } /* Allocate a unique area for a string. */ char * savebuf (register char const *s, register size_t size) { register char *rv; assert (s && size); rv = malloc (size); if (! rv) { if (! using_plan_a) memory_fatal (); } else memcpy (rv, s, size); return rv; } char * savestr (char const *s) { return savebuf (s, strlen (s) + 1); } void remove_prefix (char *p, size_t prefixlen) { char const *s = p + prefixlen; while ((*p++ = *s++)) continue; } char * format_linenum (char numbuf[LINENUM_LENGTH_BOUND + 1], LINENUM n) { char *p = numbuf + LINENUM_LENGTH_BOUND; *p = '\0'; if (n < 0) { do *--p = '0' - (int) (n % 10); while ((n /= 10) != 0); *--p = '-'; } else { do *--p = '0' + (int) (n % 10); while ((n /= 10) != 0); } return p; } #if !HAVE_VPRINTF #define vfprintf my_vfprintf static int vfprintf (FILE *stream, char const *format, va_list args) { #if !HAVE_DOPRNT && HAVE__DOPRINTF # define _doprnt _doprintf #endif #if HAVE_DOPRNT || HAVE__DOPRINTF _doprnt (format, args, stream); return ferror (stream) ? -1 : 0; #else int *a = (int *) args; return fprintf (stream, format, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9]); #endif } #endif /* !HAVE_VPRINTF */ /* Terminal output, pun intended. */ void fatal (char const *format, ...) { va_list args; fprintf (stderr, "%s: **** ", program_name); va_start (args, format); vfprintf (stderr, format, args); va_end (args); putc ('\n', stderr); fflush (stderr); fatal_exit (0); } void memory_fatal (void) { fatal ("out of memory"); } void read_fatal (void) { pfatal ("read error"); } void write_fatal (void) { pfatal ("write error"); } /* Say something from patch, something from the system, then silence . . . */ void pfatal (char const *format, ...) { int errnum = errno; va_list args; fprintf (stderr, "%s: **** ", program_name); va_start (args, format); vfprintf (stderr, format, args); va_end (args); fflush (stderr); /* perror bypasses stdio on some hosts. */ errno = errnum; perror (" "); fflush (stderr); fatal_exit (0); } /* Tell the user something. */ void say (char const *format, ...) { va_list args; va_start (args, format); vfprintf (stdout, format, args); va_end (args); fflush (stdout); } /* Get a response from the user, somehow or other. */ void ask (char const *format, ...) { static int ttyfd = -2; int r; va_list args; va_start (args, format); vfprintf (stdout, format, args); va_end (args); fflush (stdout); if (ttyfd == -2) { /* If standard output is not a tty, don't bother opening /dev/tty, since it's unlikely that stdout will be seen by the tty user. The isatty test also works around a bug in GNU Emacs 19.34 under Linux which makes a call-process `patch' hang when it reads from /dev/tty. POSIX.1-2001 XCU line 26599 requires that we read /dev/tty, though. */ ttyfd = (posixly_correct || isatty (STDOUT_FILENO) ? open (TTY_DEVICE, O_RDONLY) : -1); } if (ttyfd < 0) { /* No terminal at all -- default it. */ printf ("\n"); buf[0] = '\n'; buf[1] = '\0'; } else { size_t s = 0; while ((r = read (ttyfd, buf + s, bufsize - 1 - s)) == bufsize - 1 - s && buf[bufsize - 2] != '\n') { s = bufsize - 1; bufsize *= 2; buf = realloc (buf, bufsize); if (!buf) memory_fatal (); } if (r == 0) printf ("EOF\n"); else if (r < 0) { perror ("tty read"); fflush (stderr); close (ttyfd); ttyfd = -1; r = 0; } buf[s + r] = '\0'; } } /* Return nonzero if it OK to reverse a patch. */ bool ok_to_reverse (char const *format, ...) { bool r = false; if (noreverse || ! (force && verbosity == SILENT)) { va_list args; va_start (args, format); vfprintf (stdout, format, args); va_end (args); } if (noreverse) { printf (" Skipping patch.\n"); skip_rest_of_patch = true; } else if (force) { if (verbosity != SILENT) printf (" Applying it anyway.\n"); } else if (batch) { say (reverse ? " Ignoring -R.\n" : " Assuming -R.\n"); r = true; } else { ask (reverse ? " Ignore -R? [n] " : " Assume -R? [n] "); r = *buf == 'y'; if (! r) { ask ("Apply anyway? [n] "); if (*buf != 'y') { if (verbosity != SILENT) say ("Skipping patch.\n"); skip_rest_of_patch = true; } } } return r; } /* How to handle certain events when not in a critical region. */ #define NUM_SIGS (sizeof (sigs) / sizeof (*sigs)) static int const sigs[] = { #ifdef SIGHUP SIGHUP, #endif #ifdef SIGPIPE SIGPIPE, #endif #ifdef SIGTERM SIGTERM, #endif #ifdef SIGXCPU SIGXCPU, #endif #ifdef SIGXFSZ SIGXFSZ, #endif SIGINT }; #if !HAVE_SIGPROCMASK #define sigset_t int #define sigemptyset(s) (*(s) = 0) #ifndef sigmask #define sigmask(sig) (1 << ((sig) - 1)) #endif #define sigaddset(s, sig) (*(s) |= sigmask (sig)) #define sigismember(s, sig) ((*(s) & sigmask (sig)) != 0) #ifndef SIG_BLOCK #define SIG_BLOCK 0 #endif #ifndef SIG_UNBLOCK #define SIG_UNBLOCK (SIG_BLOCK + 1) #endif #ifndef SIG_SETMASK #define SIG_SETMASK (SIG_BLOCK + 2) #endif #define sigprocmask(how, n, o) \ ((how) == SIG_BLOCK \ ? ((o) ? *(o) = sigblock (*(n)) : sigblock (*(n))) \ : (how) == SIG_UNBLOCK \ ? sigsetmask (((o) ? *(o) = sigblock (0) : sigblock (0)) & ~*(n)) \ : (o ? *(o) = sigsetmask (*(n)) : sigsetmask (*(n)))) #if !HAVE_SIGSETMASK #define sigblock(mask) 0 #define sigsetmask(mask) 0 #endif #endif static sigset_t initial_signal_mask; static sigset_t signals_to_block; #if ! HAVE_SIGACTION static RETSIGTYPE fatal_exit_handler (int) __attribute__ ((noreturn)); static RETSIGTYPE fatal_exit_handler (int sig) { signal (sig, SIG_IGN); fatal_exit (sig); } #endif void set_signals (bool reset) { int i; #if HAVE_SIGACTION struct sigaction initial_act, fatal_act; fatal_act.sa_handler = fatal_exit; sigemptyset (&fatal_act.sa_mask); fatal_act.sa_flags = 0; #define setup_handler(sig) sigaction (sig, &fatal_act, (struct sigaction *) 0) #else #define setup_handler(sig) signal (sig, fatal_exit_handler) #endif if (!reset) { #ifdef SIGCHLD /* System V fork+wait does not work if SIGCHLD is ignored. */ signal (SIGCHLD, SIG_DFL); #endif sigemptyset (&signals_to_block); for (i = 0; i < NUM_SIGS; i++) { bool ignoring_signal; #if HAVE_SIGACTION if (sigaction (sigs[i], (struct sigaction *) 0, &initial_act) != 0) continue; ignoring_signal = initial_act.sa_handler == SIG_IGN; #else ignoring_signal = signal (sigs[i], SIG_IGN) == SIG_IGN; #endif if (! ignoring_signal) { sigaddset (&signals_to_block, sigs[i]); setup_handler (sigs[i]); } } } else { /* Undo the effect of ignore_signals. */ #if HAVE_SIGPROCMASK || HAVE_SIGSETMASK sigprocmask (SIG_SETMASK, &initial_signal_mask, (sigset_t *) 0); #else for (i = 0; i < NUM_SIGS; i++) if (sigismember (&signals_to_block, sigs[i])) setup_handler (sigs[i]); #endif } } /* How to handle certain events when in a critical region. */ void ignore_signals (void) { #if HAVE_SIGPROCMASK || HAVE_SIGSETMASK sigprocmask (SIG_BLOCK, &signals_to_block, &initial_signal_mask); #else int i; for (i = 0; i < NUM_SIGS; i++) if (sigismember (&signals_to_block, sigs[i])) signal (sigs[i], SIG_IGN); #endif } void exit_with_signal (int sig) { sigset_t s; signal (sig, SIG_DFL); sigemptyset (&s); sigaddset (&s, sig); sigprocmask (SIG_UNBLOCK, &s, (sigset_t *) 0); raise (sig); exit (2); } int systemic (char const *command) { if (debug & 8) say ("+ %s\n", command); fflush (stdout); return system (command); } /* Replace '/' with '\0' in FILENAME if it marks a place that needs testing for the existence of directory. Return the address of the last location replaced, or 0 if none were replaced. */ static char * replace_slashes (char *filename) { char *f; char *last_location_replaced = 0; char const *component_start; for (f = filename + FILESYSTEM_PREFIX_LEN (filename); ISSLASH (*f); f++) continue; component_start = f; for (; *f; f++) if (ISSLASH (*f)) { char *slash = f; /* Treat multiple slashes as if they were one slash. */ while (ISSLASH (f[1])) f++; /* Ignore slashes at the end of the path. */ if (! f[1]) break; /* "." and ".." need not be tested. */ if (! (slash - component_start <= 2 && component_start[0] == '.' && slash[-1] == '.')) { *slash = '\0'; last_location_replaced = slash; } component_start = f + 1; } return last_location_replaced; } /* Make sure we'll have the directories to create a file. Ignore the last element of `filename'. */ static void makedirs (register char *filename) { register char *f; register char *flim = replace_slashes (filename); if (flim) { /* Create any missing directories, replacing NULs by '/'s. Ignore errors. We may have to keep going even after an EEXIST, since the path may contain ".."s; and when there is an EEXIST failure the system may return some other error number. Any problems will eventually be reported when we create the file. */ for (f = filename; f <= flim; f++) if (!*f) { mkdir (filename, S_IRUSR|S_IWUSR|S_IXUSR |S_IRGRP|S_IWGRP|S_IXGRP |S_IROTH|S_IWOTH|S_IXOTH); *f = '/'; } } } /* Remove empty ancestor directories of FILENAME. Ignore errors, since the path may contain ".."s, and when there is an EEXIST failure the system may return some other error number. */ void removedirs (char *filename) { size_t i; for (i = strlen (filename); i != 0; i--) if (ISSLASH (filename[i]) && ! (ISSLASH (filename[i - 1]) || (filename[i - 1] == '.' && (i == 1 || ISSLASH (filename[i - 2]) || (filename[i - 2] == '.' && (i == 2 || ISSLASH (filename[i - 3]))))))) { filename[i] = '\0'; if (rmdir (filename) == 0 && verbosity == VERBOSE) say ("Removed empty directory %s\n", quotearg (filename)); filename[i] = '/'; } } static time_t initial_time; void init_time (void) { time (&initial_time); } /* Make filenames more reasonable. */ char * fetchname (char *at, int strip_leading, time_t *pstamp) { char *name; register char *t; int sleading = strip_leading; time_t stamp = (time_t) -1; while (ISSPACE ((unsigned char) *at)) at++; if (debug & 128) say ("fetchname %s %d\n", at, strip_leading); name = at; /* Strip up to `strip_leading' leading slashes and null terminate. If `strip_leading' is negative, strip all leading slashes. */ for (t = at; *t; t++) { if (ISSLASH (*t)) { while (ISSLASH (t[1])) t++; if (strip_leading < 0 || --sleading >= 0) name = t+1; } else if (ISSPACE ((unsigned char) *t)) { /* Allow file names with internal spaces, but only if a tab separates the file name from the date. */ char const *u = t; while (*u != '\t' && ISSPACE ((unsigned char) u[1])) u++; if (*u != '\t' && strchr (u + 1, '\t')) continue; if (set_time | set_utc) stamp = str2time (&u, initial_time, set_utc ? 0L : TM_LOCAL_ZONE); else { /* The head says the file is nonexistent if the timestamp is the epoch; but the listed time is local time, not UTC, and POSIX.1 allows local time offset anywhere in the range -25:00 < offset < +26:00. Match any time in that range by assuming local time is -25:00 and then matching any ``local'' time T in the range 0 < T < 25+26 hours. */ stamp = str2time (&u, initial_time, -25L * 60 * 60); if (0 < stamp && stamp < (25 + 26) * 60L * 60) stamp = 0; } if (*u && ! ISSPACE ((unsigned char) *u)) stamp = (time_t) -1; *t = '\0'; break; } } if (!*name) return 0; /* If the name is "/dev/null", ignore the name and mark the file as being nonexistent. The name "/dev/null" appears in patches regardless of how NULL_DEVICE is spelled. */ if (strcmp (at, "/dev/null") == 0) { if (pstamp) *pstamp = 0; return 0; } /* Ignore the name if it doesn't have enough slashes to strip off. */ if (0 < sleading) return 0; if (pstamp) *pstamp = stamp; return savestr (name); } void Fseek (FILE *stream, file_offset offset, int ptrname) { if (file_seek (stream, offset, ptrname) != 0) pfatal ("fseek"); }
/* quotesys.h -- declarations for quoting system arguments */ #if defined __STDC__ || __GNUC__ # define __QUOTESYS_P(args) args #else # define __QUOTESYS_P(args) () #endif size_t quote_system_arg __QUOTESYS_P ((char *, char const *));
/* Yield time_t from struct partime yielded by partime. */ /* Copyright 1993, 1994, 1995 Paul Eggert Distributed under license by the Free Software Foundation, Inc. This file is part of RCS. RCS is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. RCS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with RCS; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Report problems and direct all questions to: rcs-bugs@cs.purdue.edu */ #if defined __STDC__ || has_prototypes # define __MAKETIME_P(x) x #else # define __MAKETIME_P(x) () #endif struct tm *time2tm __MAKETIME_P ((time_t, int)); time_t difftm __MAKETIME_P ((struct tm const *, struct tm const *)); time_t str2time __MAKETIME_P ((char const **, time_t, long)); time_t tm2time __MAKETIME_P ((struct tm *, int)); void adjzone __MAKETIME_P ((struct tm *, long));
/* Parse a string, yielding a struct partime that describes it. */ /* Copyright 1993, 1994, 1995, 1997 Paul Eggert Distributed under license by the Free Software Foundation, Inc. This file is part of RCS. RCS is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. RCS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with RCS; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Report problems and direct all questions to: rcs-bugs@cs.purdue.edu */ #define TM_UNDEFINED (-1) #define TM_DEFINED(x) (0 <= (x)) /* #include <limits.h> if you want to use these symbols. */ #define TM_LOCAL_ZONE LONG_MIN #define TM_UNDEFINED_ZONE (LONG_MIN + 1) struct partime { /* This structure describes the parsed time. Only the following tm_* members are used: sec, min, hour, mday, mon, year, wday, yday. If ! TM_DEFINED (value), the parser never found the value. The tm_year field is the actual year, not the year - 1900; but see ymodulus below. */ struct tm tm; /* Like tm, but values are relative to the value in tm, and values are initialized to 0 rather than to TM_UNDEFINED. Only the following tm_* members are used: sec, min, hour, mday, mon, year. */ struct tm tmr; /* If TM_DEFINED (wday_ordinal), then day number (e.g. 3 in "3rd Sunday"). */ int wday_ordinal; /* If TM_DEFINED (ymodulus), then tm.tm_year is actually modulo ymodulus. */ int ymodulus; /* Week of year, ISO 8601 style. If ! TM_DEFINED (yweek), the parser never found yweek. Weeks start on Mondays. Week 1 includes Jan 4. */ int yweek; /* Seconds east of UTC; or TM_LOCAL_ZONE or TM_UNDEFINED_ZONE. */ long zone; }; #if defined __STDC__ || has_prototypes # define __PARTIME_P(x) x #else # define __PARTIME_P(x) () #endif char *partime __PARTIME_P ((char const *, struct partime *)); char *parzone __PARTIME_P ((char const *, long *));
/* Print the version number. */ /* $Id: version.c,v 1.13 2003/05/18 08:25:17 eggert Exp $ */ #define XTERN extern #include <common.h> #undef XTERN #define XTERN #include <version.h> static char const copyright_string[] = "\ Copyright (C) 1988 Larry Wall\n\ Copyright (C) 2003 Free Software Foundation, Inc."; static char const free_software_msgid[] = "\ This program comes with NO WARRANTY, to the extent permitted by law.\n\ You may redistribute copies of this program\n\ under the terms of the GNU General Public License.\n\ For more information about these matters, see the file named COPYING."; static char const authorship_msgid[] = "\ written by Larry Wall and Paul Eggert"; void version (void) { printf ("%s %s\n%s\n\n%s\n\n%s\n", PACKAGE_NAME, PACKAGE_VERSION, copyright_string, free_software_msgid, authorship_msgid); }
/* xmalloc.c -- malloc with out of memory checking Copyright (C) 1990-1999, 2000, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <sys/types.h> #if STDC_HEADERS # include <stdlib.h> #else void *calloc (); void *malloc (); void *realloc (); void free (); #endif #include "gettext.h" #define _(msgid) gettext (msgid) #define N_(msgid) msgid #include "error.h" #include "xalloc.h" #ifndef EXIT_FAILURE # define EXIT_FAILURE 1 #endif /* The following tests require AC_PREREQ(2.54). */ #ifndef HAVE_MALLOC "you must run the autoconf test for a GNU libc compatible malloc" #endif #ifndef HAVE_REALLOC "you must run the autoconf test for a GNU libc compatible realloc" #endif /* Exit value when the requested amount of memory is not available. The caller may set it to some other value. */ int xalloc_exit_failure = EXIT_FAILURE; /* If non NULL, call this function when memory is exhausted. */ void (*xalloc_fail_func) PARAMS ((void)) = 0; /* If XALLOC_FAIL_FUNC is NULL, or does return, display this message before exiting when memory is exhausted. Goes through gettext. */ char const xalloc_msg_memory_exhausted[] = N_("memory exhausted"); void xalloc_die (void) { if (xalloc_fail_func) (*xalloc_fail_func) (); error (xalloc_exit_failure, 0, "%s", _(xalloc_msg_memory_exhausted)); /* The `noreturn' cannot be given to error, since it may return if its first argument is 0. To help compilers understand the xalloc_die does terminate, call exit. */ exit (EXIT_FAILURE); } /* Allocate N bytes of memory dynamically, with error checking. */ void * xmalloc (size_t n) { void *p; p = malloc (n); if (p == 0) xalloc_die (); return p; } /* Change the size of an allocated block of memory P to N bytes, with error checking. */ void * xrealloc (void *p, size_t n) { p = realloc (p, n); if (p == 0) xalloc_die (); return p; } /* Allocate memory for N elements of S bytes, with error checking. */ void * xcalloc (size_t n, size_t s) { void *p; p = calloc (n, s); if (p == 0) xalloc_die (); return p; }
0ad4338d5aaf61d126984508172e731da3162fea3d46b9fb34b2dc71beb96a1c /usr/bin/patch
https://mirrors.kernel.org/gnu/gzip/gzip-1.2.4.tar.gz 1ca41818a23c9c59ef1d5e1d00c0d5eaa2285d931c0fb059637d7c0cc02ad967
# SPDX-FileCopyrightText: 2022 Andrius Å tikonas <andrius@stikonas.eu> # # SPDX-License-Identifier: GPL-3.0-or-later CC = tcc AR = tcc -ar CPPFLAGS = -DNO_UTIME \ -Dstrlwr=unused CFLAGS = -I . LDFLAGS = -static .PHONY: all GZIP_SRC = gzip bits crypt deflate getopt inflate lzw trees unlzh unlzw unpack unzip util zip GZIP_OBJ = $(addsuffix .o, $(GZIP_SRC)) all: gzip gzip: $(GZIP_OBJ) $(CC) $(LDFLAGS) $^ -o $@
/* * SPDX-FileCopyrightText: 2022 fosslinux <fosslinux@aussies.space> * * SPDX-License-Identifier: GPL-2.0-or-later */ #include <sys/stat.h> #include <linux/syscall.h> #include <linux/x86/syscall.h> int _stat(const char *path, struct stat *buf) { int rc = stat(path, buf); if (rc == 0) { buf->st_atime = 0; buf->st_mtime = 0; } return rc; } int _lstat(const char *path, struct stat *buf) { int rc = lstat(path, buf); if (rc == 0) { buf->st_atime = 0; buf->st_mtime = 0; } return rc; } #define stat(a,b) _stat(a,b) #define lstat(a,b) _lstat(a,b)
SPDX-FileCopyrightText: 2023 Paul Dersey <pdersey@gmail.com> SPDX-License-Identifier: GPL-2.0-or-later Remove generated CRC table --- util.c +++ util.c @@ -406,57 +406,3 @@ voidp xmalloc (size) /* ======================================================================== * Table of CRC-32's of all single-byte values (made by makecrc.c) */ -ulg crc_32_tab[] = { - 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, - 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, - 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, - 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, - 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, - 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, - 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, - 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, - 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, - 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, - 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, - 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, - 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, - 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, - 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, - 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, - 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, - 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, - 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, - 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, - 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, - 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, - 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, - 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, - 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, - 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, - 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, - 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, - 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, - 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, - 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, - 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, - 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, - 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, - 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, - 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, - 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, - 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, - 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, - 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, - 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, - 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, - 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, - 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, - 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, - 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, - 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, - 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, - 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, - 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, - 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, - 0x2d02ef8dL -};
SPDX-FileCopyrightText: 2023 Paul Dersey <pdersey@gmail.com> SPDX-License-Identifier: GPL-2.0-or-later Modify makecrc so that it outputs C code to a file that can be appended to util.c --- sample/makecrc.c +++ sample/makecrc.c @@ -47,7 +47,9 @@ main() e |= 1L << (31 - p[i]); /* Compute and print table of CRC's, five per line */ - printf(" 0x00000000L"); + FILE *output = fopen("crc.c", "w"); + fprintf(output, "ulg crc_32_tab[] = {\n"); + fprintf(output," 0x00000000L"); for (i = 1; i < 256; i++) { c = i; @@ -56,8 +58,8 @@ main() */ for (k = 8; k; k--) c = c & 1 ? (c >> 1) ^ e : c >> 1; - printf(i % 5 ? ", 0x%08lxL" : ",\n 0x%08lxL", c); + fprintf(output, i % 5 ? ", 0x%08lxL" : ",\n 0x%08lxL", c); } - putchar('\n'); + fprintf(output,"\n};\n"); return 0; }
/* Not copyrighted 1990 Mark Adler */ #ifndef lint static char rcsid[] = "$Id: makecrc.c,v 0.6 1993/05/28 07:42:59 jloup Exp $"; #endif #include <stdio.h> main() /* Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. Polynomials over GF(2) are represented in binary, one bit per coefficient, with the lowest powers in the most significant bit. Then adding polynomials is just exclusive-or, and multiplying a polynomial by x is a right shift by one. If we call the above polynomial p, and represent a byte as the polynomial q, also with the lowest power in the most significant bit (so the byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, where a mod b means the remainder after dividing a by b. This calculation is done using the shift-register method of multiplying and taking the remainder. The register is initialized to zero, and for each incoming bit, x^32 is added mod p to the register if the bit is a one (where x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by x (which is shifting right by one and adding x^32 mod p if the bit shifted out is a one). We start with the highest power (least significant bit) of q and repeat for all eight bits of q. The table is simply the CRC of all possible eight bit values. This is all the information needed to generate CRC's on data a byte at a time for all combinations of CRC register values and incoming bytes. The table is written to stdout as 256 long hexadecimal values in C language format. */ { unsigned long c; /* crc shift register */ unsigned long e; /* polynomial exclusive-or pattern */ int i; /* counter for all possible eight bit values */ int k; /* byte being shifted into crc apparatus */ /* terms of polynomial defining this crc (except x^32): */ static int p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26}; /* Make exclusive-or pattern from polynomial (0xedb88320) */ e = 0; for (i = 0; i < sizeof(p)/sizeof(int); i++) e |= 1L << (31 - p[i]); /* Compute and print table of CRC's, five per line */ printf(" 0x00000000L"); for (i = 1; i < 256; i++) { c = i; /* The idea to initialize the register with the byte instead of * zero was stolen from Haruhiko Okumura's ar002 */ for (k = 8; k; k--) c = c & 1 ? (c >> 1) ^ e : c >> 1; printf(i % 5 ? ", 0x%08lxL" : ",\n 0x%08lxL", c); } putchar('\n'); return 0; }
/* bits.c -- output variable-length bit strings * Copyright (C) 1992-1993 Jean-loup Gailly * This is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License, see the file COPYING. */ /* * PURPOSE * * Output variable-length bit strings. Compression can be done * to a file or to memory. (The latter is not supported in this version.) * * DISCUSSION * * The PKZIP "deflate" file format interprets compressed file data * as a sequence of bits. Multi-bit strings in the file may cross * byte boundaries without restriction. * * The first bit of each byte is the low-order bit. * * The routines in this file allow a variable-length bit value to * be output right-to-left (useful for literal values). For * left-to-right output (useful for code strings from the tree routines), * the bits must have been reversed first with bi_reverse(). * * For in-memory compression, the compressed bit stream goes directly * into the requested output buffer. The input data is read in blocks * by the mem_read() function. The buffer is limited to 64K on 16 bit * machines. * * INTERFACE * * void bi_init (FILE *zipfile) * Initialize the bit string routines. * * void send_bits (int value, int length) * Write out a bit string, taking the source bits right to * left. * * int bi_reverse (int value, int length) * Reverse the bits of a bit string, taking the source bits left to * right and emitting them right to left. * * void bi_windup (void) * Write out any remaining bits in an incomplete byte. * * void copy_block(char *buf, unsigned len, int header) * Copy a stored block to the zip file, storing first the length and * its one's complement if requested. * */ #include "tailor.h" #include "gzip.h" #include "crypt.h" #ifdef DEBUG # include <stdio.h> #endif #ifdef RCSID static char rcsid[] = "$Id: bits.c,v 0.9 1993/06/11 10:16:58 jloup Exp $"; #endif /* =========================================================================== * Local data used by the "bit string" routines. */ local file_t zfile; /* output gzip file */ local unsigned short bi_buf; /* Output buffer. bits are inserted starting at the bottom (least significant * bits). */ #define Buf_size (8 * 2*sizeof(char)) /* Number of bits used within bi_buf. (bi_buf might be implemented on * more than 16 bits on some systems.) */ local int bi_valid; /* Number of valid bits in bi_buf. All bits above the last valid bit * are always zero. */ int (*read_buf) OF((char *buf, unsigned size)); /* Current input function. Set to mem_read for in-memory compression */ #ifdef DEBUG ulg bits_sent; /* bit length of the compressed data */ #endif /* =========================================================================== * Initialize the bit string routines. */ void bi_init (zipfile) file_t zipfile; /* output zip file, NO_FILE for in-memory compression */ { zfile = zipfile; bi_buf = 0; bi_valid = 0; #ifdef DEBUG bits_sent = 0L; #endif /* Set the defaults for file compression. They are set by memcompress * for in-memory compression. */ if (zfile != NO_FILE) { read_buf = file_read; } } /* =========================================================================== * Send a value on a given number of bits. * IN assertion: length <= 16 and value fits in length bits. */ void send_bits(value, length) int value; /* value to send */ int length; /* number of bits */ { #ifdef DEBUG Tracev((stderr," l %2d v %4x ", length, value)); Assert(length > 0 && length <= 15, "invalid length"); bits_sent += (ulg)length; #endif /* If not enough room in bi_buf, use (valid) bits from bi_buf and * (16 - bi_valid) bits from value, leaving (width - (16-bi_valid)) * unused bits in value. */ if (bi_valid > (int)Buf_size - length) { bi_buf |= (value << bi_valid); put_short(bi_buf); bi_buf = (ush)value >> (Buf_size - bi_valid); bi_valid += length - Buf_size; } else { bi_buf |= value << bi_valid; bi_valid += length; } } /* =========================================================================== * Reverse the first len bits of a code, using straightforward code (a faster * method would use a table) * IN assertion: 1 <= len <= 15 */ unsigned bi_reverse(code, len) unsigned code; /* the value to invert */ int len; /* its bit length */ { register unsigned res = 0; do { res |= code & 1; code >>= 1, res <<= 1; } while (--len > 0); return res >> 1; } /* =========================================================================== * Write out any remaining bits in an incomplete byte. */ void bi_windup() { if (bi_valid > 8) { put_short(bi_buf); } else if (bi_valid > 0) { put_byte(bi_buf); } bi_buf = 0; bi_valid = 0; #ifdef DEBUG bits_sent = (bits_sent+7) & ~7; #endif } /* =========================================================================== * Copy a stored block to the zip file, storing first the length and its * one's complement if requested. */ void copy_block(buf, len, header) char *buf; /* the input data */ unsigned len; /* its length */ int header; /* true if block header must be written */ { bi_windup(); /* align on byte boundary */ if (header) { put_short((ush)len); put_short((ush)~len); #ifdef DEBUG bits_sent += 2*16; #endif } #ifdef DEBUG bits_sent += (ulg)len<<3; #endif while (len--) { #ifdef CRYPT int t; if (key) zencode(*buf, t); #endif put_byte(*buf++); } }
/* tailor.h -- target dependent definitions * Copyright (C) 1992-1993 Jean-loup Gailly. * This is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License, see the file COPYING. */ /* The target dependent definitions should be defined here only. * The target dependent functions should be defined in tailor.c. */ /* $Id: tailor.h,v 0.18 1993/06/14 19:32:20 jloup Exp $ */ #if defined(__MSDOS__) && !defined(MSDOS) # define MSDOS #endif #if defined(__OS2__) && !defined(OS2) # define OS2 #endif #if defined(OS2) && defined(MSDOS) /* MS C under OS/2 */ # undef MSDOS #endif #ifdef MSDOS # ifdef __GNUC__ /* DJGPP version 1.09+ on MS-DOS. * The DJGPP 1.09 stat() function must be upgraded before gzip will * fully work. * No need for DIRENT, since <unistd.h> defines POSIX_SOURCE which * implies DIRENT. */ # define near # else # define MAXSEG_64K # ifdef __TURBOC__ # define NO_OFF_T # ifdef __BORLANDC__ # define DIRENT # else # define NO_UTIME # endif # else /* MSC */ # define HAVE_SYS_UTIME_H # define NO_UTIME_H # endif # endif # define PATH_SEP2 '\\' # define PATH_SEP3 ':' # define MAX_PATH_LEN 128 # define NO_MULTIPLE_DOTS # define MAX_EXT_CHARS 3 # define Z_SUFFIX "z" # define NO_CHOWN # define PROTO # define STDC_HEADERS # define NO_SIZE_CHECK # define casemap(c) tolow(c) /* Force file names to lower case */ # include <io.h> # define OS_CODE 0x00 # define SET_BINARY_MODE(fd) setmode(fd, O_BINARY) # if !defined(NO_ASM) && !defined(ASMV) # define ASMV # endif #else # define near #endif #ifdef OS2 # define PATH_SEP2 '\\' # define PATH_SEP3 ':' # define MAX_PATH_LEN 260 # ifdef OS2FAT # define NO_MULTIPLE_DOTS # define MAX_EXT_CHARS 3 # define Z_SUFFIX "z" # define casemap(c) tolow(c) # endif # define NO_CHOWN # define PROTO # define STDC_HEADERS # include <io.h> # define OS_CODE 0x06 # define SET_BINARY_MODE(fd) setmode(fd, O_BINARY) # ifdef _MSC_VER # define HAVE_SYS_UTIME_H # define NO_UTIME_H # define MAXSEG_64K # undef near # define near _near # endif # ifdef __EMX__ # define HAVE_SYS_UTIME_H # define NO_UTIME_H # define DIRENT # define EXPAND(argc,argv) \ {_response(&argc, &argv); _wildcard(&argc, &argv);} # endif # ifdef __BORLANDC__ # define DIRENT # endif # ifdef __ZTC__ # define NO_DIR # define NO_UTIME_H # include <dos.h> # define EXPAND(argc,argv) \ {response_expand(&argc, &argv);} # endif #endif #ifdef WIN32 /* Windows NT */ # define HAVE_SYS_UTIME_H # define NO_UTIME_H # define PATH_SEP2 '\\' # define PATH_SEP3 ':' # define MAX_PATH_LEN 260 # define NO_CHOWN # define PROTO # define STDC_HEADERS # define SET_BINARY_MODE(fd) setmode(fd, O_BINARY) # include <io.h> # include <malloc.h> # ifdef NTFAT # define NO_MULTIPLE_DOTS # define MAX_EXT_CHARS 3 # define Z_SUFFIX "z" # define casemap(c) tolow(c) /* Force file names to lower case */ # endif # define OS_CODE 0x0b #endif #ifdef MSDOS # ifdef __TURBOC__ # include <alloc.h> # define DYN_ALLOC /* Turbo C 2.0 does not accept static allocations of large arrays */ void * fcalloc (unsigned items, unsigned size); void fcfree (void *ptr); # else /* MSC */ # include <malloc.h> # define fcalloc(nitems,itemsize) halloc((long)(nitems),(itemsize)) # define fcfree(ptr) hfree(ptr) # endif #else # ifdef MAXSEG_64K # define fcalloc(items,size) calloc((items),(size)) # else # define fcalloc(items,size) malloc((size_t)(items)*(size_t)(size)) # endif # define fcfree(ptr) free(ptr) #endif #if defined(VAXC) || defined(VMS) # define PATH_SEP ']' # define PATH_SEP2 ':' # define SUFFIX_SEP ';' # define NO_MULTIPLE_DOTS # define Z_SUFFIX "-gz" # define RECORD_IO 1 # define casemap(c) tolow(c) # define OS_CODE 0x02 # define OPTIONS_VAR "GZIP_OPT" # define STDC_HEADERS # define NO_UTIME # define EXPAND(argc,argv) vms_expand_args(&argc,&argv); # include <file.h> # define unlink delete # ifdef VAXC # define NO_FCNTL_H # include <unixio.h> # endif #endif #ifdef AMIGA # define PATH_SEP2 ':' # define STDC_HEADERS # define OS_CODE 0x01 # define ASMV # ifdef __GNUC__ # define DIRENT # define HAVE_UNISTD_H # else /* SASC */ # define NO_STDIN_FSTAT # define SYSDIR # define NO_SYMLINK # define NO_CHOWN # define NO_FCNTL_H # include <fcntl.h> /* for read() and write() */ # define direct dirent extern void _expand_args(int *argc, char ***argv); # define EXPAND(argc,argv) _expand_args(&argc,&argv); # undef O_BINARY /* disable useless --ascii option */ # endif #endif #if defined(ATARI) || defined(atarist) # ifndef STDC_HEADERS # define STDC_HEADERS # define HAVE_UNISTD_H # define DIRENT # endif # define ASMV # define OS_CODE 0x05 # ifdef TOSFS # define PATH_SEP2 '\\' # define PATH_SEP3 ':' # define MAX_PATH_LEN 128 # define NO_MULTIPLE_DOTS # define MAX_EXT_CHARS 3 # define Z_SUFFIX "z" # define NO_CHOWN # define casemap(c) tolow(c) /* Force file names to lower case */ # define NO_SYMLINK # endif #endif #ifdef MACOS # define PATH_SEP ':' # define DYN_ALLOC # define PROTO # define NO_STDIN_FSTAT # define NO_CHOWN # define NO_UTIME # define chmod(file, mode) (0) # define OPEN(name, flags, mode) open(name, flags) # define OS_CODE 0x07 # ifdef MPW # define isatty(fd) ((fd) <= 2) # endif #endif #ifdef __50SERIES /* Prime/PRIMOS */ # define PATH_SEP '>' # define STDC_HEADERS # define NO_MEMORY_H # define NO_UTIME_H # define NO_UTIME # define NO_CHOWN # define NO_STDIN_FSTAT # define NO_SIZE_CHECK # define NO_SYMLINK # define RECORD_IO 1 # define casemap(c) tolow(c) /* Force file names to lower case */ # define put_char(c) put_byte((c) & 0x7F) # define get_char(c) ascii2pascii(get_byte()) # define OS_CODE 0x0F /* temporary, subject to change */ # ifdef SIGTERM # undef SIGTERM /* We don't want a signal handler for SIGTERM */ # endif #endif #if defined(pyr) && !defined(NOMEMCPY) /* Pyramid */ # define NOMEMCPY /* problem with overlapping copies */ #endif #ifdef TOPS20 # define OS_CODE 0x0a #endif #ifndef unix # define NO_ST_INO /* don't rely on inode numbers */ #endif /* Common defaults */ #ifndef OS_CODE # define OS_CODE 0x03 /* assume Unix */ #endif #ifndef PATH_SEP # define PATH_SEP '/' #endif #ifndef casemap # define casemap(c) (c) #endif #ifndef OPTIONS_VAR # define OPTIONS_VAR "GZIP" #endif #ifndef Z_SUFFIX # define Z_SUFFIX ".gz" #endif #ifdef MAX_EXT_CHARS # define MAX_SUFFIX MAX_EXT_CHARS #else # define MAX_SUFFIX 30 #endif #ifndef MAKE_LEGAL_NAME # ifdef NO_MULTIPLE_DOTS # define MAKE_LEGAL_NAME(name) make_simple_name(name) # else # define MAKE_LEGAL_NAME(name) # endif #endif #ifndef MIN_PART # define MIN_PART 3 /* keep at least MIN_PART chars between dots in a file name. */ #endif #ifndef EXPAND # define EXPAND(argc,argv) #endif #ifndef RECORD_IO # define RECORD_IO 0 #endif #ifndef SET_BINARY_MODE # define SET_BINARY_MODE(fd) #endif #ifndef OPEN # define OPEN(name, flags, mode) open(name, flags, mode) #endif #ifndef get_char # define get_char() get_byte() #endif #ifndef put_char # define put_char(c) put_byte(c) #endif
/* gzip.h -- common declarations for all gzip modules * Copyright (C) 1992-1993 Jean-loup Gailly. * This is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License, see the file COPYING. */ #if defined(__STDC__) || defined(PROTO) # define OF(args) args #else # define OF(args) () #endif #ifdef __STDC__ typedef void *voidp; #else typedef char *voidp; #endif /* I don't like nested includes, but the string and io functions are used * too often */ #include <stdio.h> #if !defined(NO_STRING_H) || defined(STDC_HEADERS) # include <string.h> # if !defined(STDC_HEADERS) && !defined(NO_MEMORY_H) && !defined(__GNUC__) # include <memory.h> # endif # define memzero(s, n) memset ((voidp)(s), 0, (n)) #else # include <strings.h> # define strchr index # define strrchr rindex # define memcpy(d, s, n) bcopy((s), (d), (n)) # define memcmp(s1, s2, n) bcmp((s1), (s2), (n)) # define memzero(s, n) bzero((s), (n)) #endif #ifndef RETSIGTYPE # define RETSIGTYPE void #endif #define local static typedef unsigned char uch; typedef unsigned short ush; typedef unsigned long ulg; /* Return codes from gzip */ #define OK 0 #define ERROR 1 #define WARNING 2 /* Compression methods (see algorithm.doc) */ #define STORED 0 #define COMPRESSED 1 #define PACKED 2 #define LZHED 3 /* methods 4 to 7 reserved */ #define DEFLATED 8 #define MAX_METHODS 9 extern int method; /* compression method */ /* To save memory for 16 bit systems, some arrays are overlaid between * the various modules: * deflate: prev+head window d_buf l_buf outbuf * unlzw: tab_prefix tab_suffix stack inbuf outbuf * inflate: window inbuf * unpack: window inbuf prefix_len * unlzh: left+right window c_table inbuf c_len * For compression, input is done in window[]. For decompression, output * is done in window except for unlzw. */ #ifndef INBUFSIZ # ifdef SMALL_MEM # define INBUFSIZ 0x2000 /* input buffer size */ # else # define INBUFSIZ 0x8000 /* input buffer size */ # endif #endif #define INBUF_EXTRA 64 /* required by unlzw() */ #ifndef OUTBUFSIZ # ifdef SMALL_MEM # define OUTBUFSIZ 8192 /* output buffer size */ # else # define OUTBUFSIZ 16384 /* output buffer size */ # endif #endif #define OUTBUF_EXTRA 2048 /* required by unlzw() */ #ifndef DIST_BUFSIZE # ifdef SMALL_MEM # define DIST_BUFSIZE 0x2000 /* buffer for distances, see trees.c */ # else # define DIST_BUFSIZE 0x8000 /* buffer for distances, see trees.c */ # endif #endif #ifdef DYN_ALLOC # define EXTERN(type, array) extern type * near array # define DECLARE(type, array, size) type * near array # define ALLOC(type, array, size) { \ array = (type*)fcalloc((size_t)(((size)+1L)/2), 2*sizeof(type)); \ if (array == NULL) error("insufficient memory"); \ } # define FREE(array) {if (array != NULL) fcfree(array), array=NULL;} #else # define EXTERN(type, array) extern type array[] # define DECLARE(type, array, size) type array[size] # define ALLOC(type, array, size) # define FREE(array) #endif EXTERN(uch, inbuf); /* input buffer */ EXTERN(uch, outbuf); /* output buffer */ EXTERN(ush, d_buf); /* buffer for distances, see trees.c */ EXTERN(uch, window); /* Sliding window and suffix table (unlzw) */ #define tab_suffix window #ifndef MAXSEG_64K # define tab_prefix prev /* hash link (see deflate.c) */ # define head (prev+WSIZE) /* hash head (see deflate.c) */ EXTERN(ush, tab_prefix); /* prefix code (see unlzw.c) */ #else # define tab_prefix0 prev # define head tab_prefix1 EXTERN(ush, tab_prefix0); /* prefix for even codes */ EXTERN(ush, tab_prefix1); /* prefix for odd codes */ #endif extern unsigned insize; /* valid bytes in inbuf */ extern unsigned inptr; /* index of next byte to be processed in inbuf */ extern unsigned outcnt; /* bytes in output buffer */ extern long bytes_in; /* number of input bytes */ extern long bytes_out; /* number of output bytes */ extern long header_bytes;/* number of bytes in gzip header */ #define isize bytes_in /* for compatibility with old zip sources (to be cleaned) */ extern int ifd; /* input file descriptor */ extern int ofd; /* output file descriptor */ extern char ifname[]; /* input file name or "stdin" */ extern char ofname[]; /* output file name or "stdout" */ extern char *progname; /* program name */ extern long time_stamp; /* original time stamp (modification time) */ extern long ifile_size; /* input file size, -1 for devices (debug only) */ typedef int file_t; /* Do not use stdio */ #define NO_FILE (-1) /* in memory compression */ #define PACK_MAGIC "\037\036" /* Magic header for packed files */ #define GZIP_MAGIC "\037\213" /* Magic header for gzip files, 1F 8B */ #define OLD_GZIP_MAGIC "\037\236" /* Magic header for gzip 0.5 = freeze 1.x */ #define LZH_MAGIC "\037\240" /* Magic header for SCO LZH Compress files*/ #define PKZIP_MAGIC "\120\113\003\004" /* Magic header for pkzip files */ /* gzip flag byte */ #define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */ #define CONTINUATION 0x02 /* bit 1 set: continuation of multi-part gzip file */ #define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ #define ORIG_NAME 0x08 /* bit 3 set: original file name present */ #define COMMENT 0x10 /* bit 4 set: file comment present */ #define ENCRYPTED 0x20 /* bit 5 set: file is encrypted */ #define RESERVED 0xC0 /* bit 6,7: reserved */ /* internal file attribute */ #define UNKNOWN 0xffff #define BINARY 0 #define ASCII 1 #ifndef WSIZE # define WSIZE 0x8000 /* window size--must be a power of two, and */ #endif /* at least 32K for zip's deflate method */ #define MIN_MATCH 3 #define MAX_MATCH 258 /* The minimum and maximum match lengths */ #define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1) /* Minimum amount of lookahead, except at the end of the input file. * See deflate.c for comments about the MIN_MATCH+1. */ #define MAX_DIST (WSIZE-MIN_LOOKAHEAD) /* In order to simplify the code, particularly on 16 bit machines, match * distances are limited to MAX_DIST instead of WSIZE. */ extern int decrypt; /* flag to turn on decryption */ extern int exit_code; /* program exit code */ extern int verbose; /* be verbose (-v) */ extern int quiet; /* be quiet (-q) */ extern int level; /* compression level */ extern int test; /* check .z file integrity */ extern int to_stdout; /* output to stdout (-c) */ extern int save_orig_name; /* set if original name must be saved */ #define get_byte() (inptr < insize ? inbuf[inptr++] : fill_inbuf(0)) #define try_byte() (inptr < insize ? inbuf[inptr++] : fill_inbuf(1)) /* put_byte is used for the compressed output, put_ubyte for the * uncompressed output. However unlzw() uses window for its * suffix table instead of its output buffer, so it does not use put_ubyte * (to be cleaned up). */ #define put_byte(c) {outbuf[outcnt++]=(uch)(c); if (outcnt==OUTBUFSIZ)\ flush_outbuf();} #define put_ubyte(c) {window[outcnt++]=(uch)(c); if (outcnt==WSIZE)\ flush_window();} /* Output a 16 bit value, lsb first */ #define put_short(w) \ { if (outcnt < OUTBUFSIZ-2) { \ outbuf[outcnt++] = (uch) ((w) & 0xff); \ outbuf[outcnt++] = (uch) ((ush)(w) >> 8); \ } else { \ put_byte((uch)((w) & 0xff)); \ put_byte((uch)((ush)(w) >> 8)); \ } \ } /* Output a 32 bit value to the bit stream, lsb first */ #define put_long(n) { \ put_short((n) & 0xffff); \ put_short(((ulg)(n)) >> 16); \ } #define seekable() 0 /* force sequential output */ #define translate_eol 0 /* no option -a yet */ #define tolow(c) (isupper(c) ? (c)-'A'+'a' : (c)) /* force to lower case */ /* Macros for getting two-byte and four-byte header values */ #define SH(p) ((ush)(uch)((p)[0]) | ((ush)(uch)((p)[1]) << 8)) #define LG(p) ((ulg)(SH(p)) | ((ulg)(SH((p)+2)) << 16)) /* Diagnostic functions */ #ifdef DEBUG # define Assert(cond,msg) {if(!(cond)) error(msg);} # define Trace(x) fprintf x # define Tracev(x) {if (verbose) fprintf x ;} # define Tracevv(x) {if (verbose>1) fprintf x ;} # define Tracec(c,x) {if (verbose && (c)) fprintf x ;} # define Tracecv(c,x) {if (verbose>1 && (c)) fprintf x ;} #else # define Assert(cond,msg) # define Trace(x) # define Tracev(x) # define Tracevv(x) # define Tracec(c,x) # define Tracecv(c,x) #endif #define WARN(msg) {if (!quiet) fprintf msg ; \ if (exit_code == OK) exit_code = WARNING;} /* in zip.c: */ extern int zip OF((int in, int out)); extern int file_read OF((char *buf, unsigned size)); /* in unzip.c */ extern int unzip OF((int in, int out)); extern int check_zipfile OF((int in)); /* in unpack.c */ extern int unpack OF((int in, int out)); /* in unlzh.c */ extern int unlzh OF((int in, int out)); /* in gzip.c */ RETSIGTYPE abort_gzip OF((void)); /* in deflate.c */ void lm_init OF((int pack_level, ush *flags)); ulg deflate OF((void)); /* in trees.c */ void ct_init OF((ush *attr, int *method)); int ct_tally OF((int dist, int lc)); ulg flush_block OF((char *buf, ulg stored_len, int eof)); /* in bits.c */ void bi_init OF((file_t zipfile)); void send_bits OF((int value, int length)); unsigned bi_reverse OF((unsigned value, int length)); void bi_windup OF((void)); void copy_block OF((char *buf, unsigned len, int header)); extern int (*read_buf) OF((char *buf, unsigned size)); /* in util.c: */ extern int copy OF((int in, int out)); extern ulg updcrc OF((uch *s, unsigned n)); extern void clear_bufs OF((void)); extern int fill_inbuf OF((int eof_ok)); extern void flush_outbuf OF((void)); extern void flush_window OF((void)); extern void write_buf OF((int fd, voidp buf, unsigned cnt)); extern char *strlwr OF((char *s)); extern char *basename OF((char *fname)); extern void make_simple_name OF((char *name)); extern char *add_envopt OF((int *argcp, char ***argvp, char *env)); extern void error OF((char *m)); extern void warn OF((char *a, char *b)); extern void read_error OF((void)); extern void write_error OF((void)); extern void display_ratio OF((long num, long den, FILE *file)); extern voidp xmalloc OF((unsigned int size)); /* in inflate.c */ extern int inflate OF((void));
/* crypt.h (dummy version) -- do not perform encryption * Hardly worth copyrighting :-) */ #ifdef CRYPT # undef CRYPT /* dummy version */ #endif #define RAND_HEAD_LEN 12 /* length of encryption random header */ #define zencode #define zdecode
/* crypt.c (dummy version) -- do not perform encryption * Hardly worth copyrighting :-) */ #ifdef RCSID static char rcsid[] = "$Id: crypt.c,v 0.6 1993/03/22 09:48:47 jloup Exp $"; #endif
/* deflate.c -- compress data using the deflation algorithm * Copyright (C) 1992-1993 Jean-loup Gailly * This is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License, see the file COPYING. */ /* * PURPOSE * * Identify new text as repetitions of old text within a fixed- * length sliding window trailing behind the new text. * * DISCUSSION * * The "deflation" process depends on being able to identify portions * of the input text which are identical to earlier input (within a * sliding window trailing behind the input currently being processed). * * The most straightforward technique turns out to be the fastest for * most input files: try all possible matches and select the longest. * The key feature of this algorithm is that insertions into the string * dictionary are very simple and thus fast, and deletions are avoided * completely. Insertions are performed at each input character, whereas * string matches are performed only when the previous match ends. So it * is preferable to spend more time in matches to allow very fast string * insertions and avoid deletions. The matching algorithm for small * strings is inspired from that of Rabin & Karp. A brute force approach * is used to find longer strings when a small match has been found. * A similar algorithm is used in comic (by Jan-Mark Wams) and freeze * (by Leonid Broukhis). * A previous version of this file used a more sophisticated algorithm * (by Fiala and Greene) which is guaranteed to run in linear amortized * time, but has a larger average cost, uses more memory and is patented. * However the F&G algorithm may be faster for some highly redundant * files if the parameter max_chain_length (described below) is too large. * * ACKNOWLEDGEMENTS * * The idea of lazy evaluation of matches is due to Jan-Mark Wams, and * I found it in 'freeze' written by Leonid Broukhis. * Thanks to many info-zippers for bug reports and testing. * * REFERENCES * * APPNOTE.TXT documentation file in PKZIP 1.93a distribution. * * A description of the Rabin and Karp algorithm is given in the book * "Algorithms" by R. Sedgewick, Addison-Wesley, p252. * * Fiala,E.R., and Greene,D.H. * Data Compression with Finite Windows, Comm.ACM, 32,4 (1989) 490-595 * * INTERFACE * * void lm_init (int pack_level, ush *flags) * Initialize the "longest match" routines for a new file * * ulg deflate (void) * Processes a new input file and return its compressed length. Sets * the compressed length, crc, deflate flags and internal file * attributes. */ #include <stdio.h> #include "tailor.h" #include "gzip.h" #include "lzw.h" /* just for consistency checking */ #ifdef RCSID static char rcsid[] = "$Id: deflate.c,v 0.15 1993/06/24 10:53:53 jloup Exp $"; #endif /* =========================================================================== * Configuration parameters */ /* Compile with MEDIUM_MEM to reduce the memory requirements or * with SMALL_MEM to use as little memory as possible. Use BIG_MEM if the * entire input file can be held in memory (not possible on 16 bit systems). * Warning: defining these symbols affects HASH_BITS (see below) and thus * affects the compression ratio. The compressed output * is still correct, and might even be smaller in some cases. */ #ifdef SMALL_MEM # define HASH_BITS 13 /* Number of bits used to hash strings */ #endif #ifdef MEDIUM_MEM # define HASH_BITS 14 #endif #ifndef HASH_BITS # define HASH_BITS 15 /* For portability to 16 bit machines, do not use values above 15. */ #endif /* To save space (see unlzw.c), we overlay prev+head with tab_prefix and * window with tab_suffix. Check that we can do this: */ #if (WSIZE<<1) > (1<<BITS) error: cannot overlay window with tab_suffix and prev with tab_prefix0 #endif #if HASH_BITS > BITS-1 error: cannot overlay head with tab_prefix1 #endif #define HASH_SIZE (unsigned)(1<<HASH_BITS) #define HASH_MASK (HASH_SIZE-1) #define WMASK (WSIZE-1) /* HASH_SIZE and WSIZE must be powers of two */ #define NIL 0 /* Tail of hash chains */ #define FAST 4 #define SLOW 2 /* speed options for the general purpose bit flag */ #ifndef TOO_FAR # define TOO_FAR 4096 #endif /* Matches of length 3 are discarded if their distance exceeds TOO_FAR */ /* =========================================================================== * Local data used by the "longest match" routines. */ typedef ush Pos; typedef unsigned IPos; /* A Pos is an index in the character window. We use short instead of int to * save space in the various tables. IPos is used only for parameter passing. */ /* DECLARE(uch, window, 2L*WSIZE); */ /* Sliding window. Input bytes are read into the second half of the window, * and move to the first half later to keep a dictionary of at least WSIZE * bytes. With this organization, matches are limited to a distance of * WSIZE-MAX_MATCH bytes, but this ensures that IO is always * performed with a length multiple of the block size. Also, it limits * the window size to 64K, which is quite useful on MSDOS. * To do: limit the window size to WSIZE+BSZ if SMALL_MEM (the code would * be less efficient). */ /* DECLARE(Pos, prev, WSIZE); */ /* Link to older string with same hash index. To limit the size of this * array to 64K, this link is maintained only for the last 32K strings. * An index in this array is thus a window index modulo 32K. */ /* DECLARE(Pos, head, 1<<HASH_BITS); */ /* Heads of the hash chains or NIL. */ ulg window_size = (ulg)2*WSIZE; /* window size, 2*WSIZE except for MMAP or BIG_MEM, where it is the * input file length plus MIN_LOOKAHEAD. */ long block_start; /* window position at the beginning of the current output block. Gets * negative when the window is moved backwards. */ local unsigned ins_h; /* hash index of string to be inserted */ #define H_SHIFT ((HASH_BITS+MIN_MATCH-1)/MIN_MATCH) /* Number of bits by which ins_h and del_h must be shifted at each * input step. It must be such that after MIN_MATCH steps, the oldest * byte no longer takes part in the hash key, that is: * H_SHIFT * MIN_MATCH >= HASH_BITS */ unsigned int near prev_length; /* Length of the best match at previous step. Matches not greater than this * are discarded. This is used in the lazy match evaluation. */ unsigned near strstart; /* start of string to insert */ unsigned near match_start; /* start of matching string */ local int eofile; /* flag set at end of input file */ local unsigned lookahead; /* number of valid bytes ahead in window */ unsigned near max_chain_length; /* To speed up deflation, hash chains are never searched beyond this length. * A higher limit improves compression ratio but degrades the speed. */ local unsigned int max_lazy_match; /* Attempt to find a better match only when the current match is strictly * smaller than this value. This mechanism is used only for compression * levels >= 4. */ #define max_insert_length max_lazy_match /* Insert new strings in the hash table only if the match length * is not greater than this length. This saves time but degrades compression. * max_insert_length is used only for compression levels <= 3. */ local int compr_level; /* compression level (1..9) */ unsigned near good_match; /* Use a faster search when the previous match is longer than this */ /* Values for max_lazy_match, good_match and max_chain_length, depending on * the desired pack level (0..9). The values given below have been tuned to * exclude worst case performance for pathological files. Better values may be * found for specific files. */ typedef struct config { ush good_length; /* reduce lazy search above this match length */ ush max_lazy; /* do not perform lazy search above this match length */ ush nice_length; /* quit search above this match length */ ush max_chain; } config; #ifdef FULL_SEARCH # define nice_match MAX_MATCH #else int near nice_match; /* Stop searching when current match exceeds this */ #endif local config configuration_table[10] = { /* good lazy nice chain */ /* 0 */ {0, 0, 0, 0}, /* store only */ /* 1 */ {4, 4, 8, 4}, /* maximum speed, no lazy matches */ /* 2 */ {4, 5, 16, 8}, /* 3 */ {4, 6, 32, 32}, /* 4 */ {4, 4, 16, 16}, /* lazy matches */ /* 5 */ {8, 16, 32, 32}, /* 6 */ {8, 16, 128, 128}, /* 7 */ {8, 32, 128, 256}, /* 8 */ {32, 128, 258, 1024}, /* 9 */ {32, 258, 258, 4096}}; /* maximum compression */ /* Note: the deflate() code requires max_lazy >= MIN_MATCH and max_chain >= 4 * For deflate_fast() (levels <= 3) good is ignored and lazy has a different * meaning. */ #define EQUAL 0 /* result of memcmp for equal strings */ /* =========================================================================== * Prototypes for local functions. */ local void fill_window OF((void)); local ulg deflate_fast OF((void)); int longest_match OF((IPos cur_match)); #ifdef ASMV void match_init OF((void)); /* asm code initialization */ #endif #ifdef DEBUG local void check_match OF((IPos start, IPos match, int length)); #endif /* =========================================================================== * Update a hash value with the given input byte * IN assertion: all calls to to UPDATE_HASH are made with consecutive * input characters, so that a running hash key can be computed from the * previous key instead of complete recalculation each time. */ #define UPDATE_HASH(h,c) (h = (((h)<<H_SHIFT) ^ (c)) & HASH_MASK) /* =========================================================================== * Insert string s in the dictionary and set match_head to the previous head * of the hash chain (the most recent string with same hash key). Return * the previous length of the hash chain. * IN assertion: all calls to to INSERT_STRING are made with consecutive * input characters and the first MIN_MATCH bytes of s are valid * (except for the last MIN_MATCH-1 bytes of the input file). */ #define INSERT_STRING(s, match_head) \ (UPDATE_HASH(ins_h, window[(s) + MIN_MATCH-1]), \ prev[(s) & WMASK] = match_head = head[ins_h], \ head[ins_h] = (s)) /* =========================================================================== * Initialize the "longest match" routines for a new file */ void lm_init (pack_level, flags) int pack_level; /* 0: store, 1: best speed, 9: best compression */ ush *flags; /* general purpose bit flag */ { register unsigned j; if (pack_level < 1 || pack_level > 9) error("bad pack level"); compr_level = pack_level; /* Initialize the hash table. */ #if defined(MAXSEG_64K) && HASH_BITS == 15 for (j = 0; j < HASH_SIZE; j++) head[j] = NIL; #else memzero((char*)head, HASH_SIZE*sizeof(*head)); #endif /* prev will be initialized on the fly */ /* Set the default configuration parameters: */ max_lazy_match = configuration_table[pack_level].max_lazy; good_match = configuration_table[pack_level].good_length; #ifndef FULL_SEARCH nice_match = configuration_table[pack_level].nice_length; #endif max_chain_length = configuration_table[pack_level].max_chain; if (pack_level == 1) { *flags |= FAST; } else if (pack_level == 9) { *flags |= SLOW; } /* ??? reduce max_chain_length for binary files */ strstart = 0; block_start = 0L; #ifdef ASMV match_init(); /* initialize the asm code */ #endif lookahead = read_buf((char*)window, sizeof(int) <= 2 ? (unsigned)WSIZE : 2*WSIZE); if (lookahead == 0 || lookahead == (unsigned)EOF) { eofile = 1, lookahead = 0; return; } eofile = 0; /* Make sure that we always have enough lookahead. This is important * if input comes from a device such as a tty. */ while (lookahead < MIN_LOOKAHEAD && !eofile) fill_window(); ins_h = 0; for (j=0; j<MIN_MATCH-1; j++) UPDATE_HASH(ins_h, window[j]); /* If lookahead < MIN_MATCH, ins_h is garbage, but this is * not important since only literal bytes will be emitted. */ } /* =========================================================================== * Set match_start to the longest match starting at the given string and * return its length. Matches shorter or equal to prev_length are discarded, * in which case the result is equal to prev_length and match_start is * garbage. * IN assertions: cur_match is the head of the hash chain for the current * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 */ #ifndef ASMV /* For MSDOS, OS/2 and 386 Unix, an optimized version is in match.asm or * match.s. The code is functionally equivalent, so you can use the C version * if desired. */ int longest_match(cur_match) IPos cur_match; /* current match */ { unsigned chain_length = max_chain_length; /* max hash chain length */ register uch *scan = window + strstart; /* current string */ register uch *match; /* matched string */ register int len; /* length of current match */ int best_len = prev_length; /* best match length so far */ IPos limit = strstart > (IPos)MAX_DIST ? strstart - (IPos)MAX_DIST : NIL; /* Stop when cur_match becomes <= limit. To simplify the code, * we prevent matches with the string of window index 0. */ /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. * It is easy to get rid of this optimization if necessary. */ #if HASH_BITS < 8 || MAX_MATCH != 258 error: Code too clever #endif #ifdef UNALIGNED_OK /* Compare two bytes at a time. Note: this is not always beneficial. * Try with and without -DUNALIGNED_OK to check. */ register uch *strend = window + strstart + MAX_MATCH - 1; register ush scan_start = *(ush*)scan; register ush scan_end = *(ush*)(scan+best_len-1); #else register uch *strend = window + strstart + MAX_MATCH; register uch scan_end1 = scan[best_len-1]; register uch scan_end = scan[best_len]; #endif /* Do not waste too much time if we already have a good match: */ if (prev_length >= good_match) { chain_length >>= 2; } Assert(strstart <= window_size-MIN_LOOKAHEAD, "insufficient lookahead"); do { Assert(cur_match < strstart, "no future"); match = window + cur_match; /* Skip to next match if the match length cannot increase * or if the match length is less than 2: */ #if (defined(UNALIGNED_OK) && MAX_MATCH == 258) /* This code assumes sizeof(unsigned short) == 2. Do not use * UNALIGNED_OK if your compiler uses a different size. */ if (*(ush*)(match+best_len-1) != scan_end || *(ush*)match != scan_start) continue; /* It is not necessary to compare scan[2] and match[2] since they are * always equal when the other bytes match, given that the hash keys * are equal and that HASH_BITS >= 8. Compare 2 bytes at a time at * strstart+3, +5, ... up to strstart+257. We check for insufficient * lookahead only every 4th comparison; the 128th check will be made * at strstart+257. If MAX_MATCH-2 is not a multiple of 8, it is * necessary to put more guard bytes at the end of the window, or * to check more often for insufficient lookahead. */ scan++, match++; do { } while (*(ush*)(scan+=2) == *(ush*)(match+=2) && *(ush*)(scan+=2) == *(ush*)(match+=2) && *(ush*)(scan+=2) == *(ush*)(match+=2) && *(ush*)(scan+=2) == *(ush*)(match+=2) && scan < strend); /* The funny "do {}" generates better code on most compilers */ /* Here, scan <= window+strstart+257 */ Assert(scan <= window+(unsigned)(window_size-1), "wild scan"); if (*scan == *match) scan++; len = (MAX_MATCH - 1) - (int)(strend-scan); scan = strend - (MAX_MATCH-1); #else /* UNALIGNED_OK */ if (match[best_len] != scan_end || match[best_len-1] != scan_end1 || *match != *scan || *++match != scan[1]) continue; /* The check at best_len-1 can be removed because it will be made * again later. (This heuristic is not always a win.) * It is not necessary to compare scan[2] and match[2] since they * are always equal when the other bytes match, given that * the hash keys are equal and that HASH_BITS >= 8. */ scan += 2, match++; /* We check for insufficient lookahead only every 8th comparison; * the 256th check will be made at strstart+258. */ do { } while (*++scan == *++match && *++scan == *++match && *++scan == *++match && *++scan == *++match && *++scan == *++match && *++scan == *++match && *++scan == *++match && *++scan == *++match && scan < strend); len = MAX_MATCH - (int)(strend - scan); scan = strend - MAX_MATCH; #endif /* UNALIGNED_OK */ if (len > best_len) { match_start = cur_match; best_len = len; if (len >= nice_match) break; #ifdef UNALIGNED_OK scan_end = *(ush*)(scan+best_len-1); #else scan_end1 = scan[best_len-1]; scan_end = scan[best_len]; #endif } } while ((cur_match = prev[cur_match & WMASK]) > limit && --chain_length != 0); return best_len; } #endif /* ASMV */ #ifdef DEBUG /* =========================================================================== * Check that the match at match_start is indeed a match. */ local void check_match(start, match, length) IPos start, match; int length; { /* check that the match is indeed a match */ if (memcmp((char*)window + match, (char*)window + start, length) != EQUAL) { fprintf(stderr, " start %d, match %d, length %d\n", start, match, length); error("invalid match"); } if (verbose > 1) { fprintf(stderr,"\\[%d,%d]", start-match, length); do { putc(window[start++], stderr); } while (--length != 0); } } #else # define check_match(start, match, length) #endif /* =========================================================================== * Fill the window when the lookahead becomes insufficient. * Updates strstart and lookahead, and sets eofile if end of input file. * IN assertion: lookahead < MIN_LOOKAHEAD && strstart + lookahead > 0 * OUT assertions: at least one byte has been read, or eofile is set; * file reads are performed for at least two bytes (required for the * translate_eol option). */ local void fill_window() { register unsigned n, m; unsigned more = (unsigned)(window_size - (ulg)lookahead - (ulg)strstart); /* Amount of free space at the end of the window. */ /* If the window is almost full and there is insufficient lookahead, * move the upper half to the lower one to make room in the upper half. */ if (more == (unsigned)EOF) { /* Very unlikely, but possible on 16 bit machine if strstart == 0 * and lookahead == 1 (input done one byte at time) */ more--; } else if (strstart >= WSIZE+MAX_DIST) { /* By the IN assertion, the window is not empty so we can't confuse * more == 0 with more == 64K on a 16 bit machine. */ Assert(window_size == (ulg)2*WSIZE, "no sliding with BIG_MEM"); memcpy((char*)window, (char*)window+WSIZE, (unsigned)WSIZE); match_start -= WSIZE; strstart -= WSIZE; /* we now have strstart >= MAX_DIST: */ block_start -= (long) WSIZE; for (n = 0; n < HASH_SIZE; n++) { m = head[n]; head[n] = (Pos)(m >= WSIZE ? m-WSIZE : NIL); } for (n = 0; n < WSIZE; n++) { m = prev[n]; prev[n] = (Pos)(m >= WSIZE ? m-WSIZE : NIL); /* If n is not on any hash chain, prev[n] is garbage but * its value will never be used. */ } more += WSIZE; } /* At this point, more >= 2 */ if (!eofile) { n = read_buf((char*)window+strstart+lookahead, more); if (n == 0 || n == (unsigned)EOF) { eofile = 1; } else { lookahead += n; } } } /* =========================================================================== * Flush the current block, with given end-of-file flag. * IN assertion: strstart is set to the end of the current match. */ #define FLUSH_BLOCK(eof) \ flush_block(block_start >= 0L ? (char*)&window[(unsigned)block_start] : \ (char*)NULL, (long)strstart - block_start, (eof)) /* =========================================================================== * Processes a new input file and return its compressed length. This * function does not perform lazy evaluationof matches and inserts * new strings in the dictionary only for unmatched strings or for short * matches. It is used only for the fast compression options. */ local ulg deflate_fast() { IPos hash_head; /* head of the hash chain */ int flush; /* set if current block must be flushed */ unsigned match_length = 0; /* length of best match */ prev_length = MIN_MATCH-1; while (lookahead != 0) { /* Insert the string window[strstart .. strstart+2] in the * dictionary, and set hash_head to the head of the hash chain: */ INSERT_STRING(strstart, hash_head); /* Find the longest match, discarding those <= prev_length. * At this point we have always match_length < MIN_MATCH */ if (hash_head != NIL && strstart - hash_head <= MAX_DIST) { /* To simplify the code, we prevent matches with the string * of window index 0 (in particular we have to avoid a match * of the string with itself at the start of the input file). */ match_length = longest_match (hash_head); /* longest_match() sets match_start */ if (match_length > lookahead) match_length = lookahead; } if (match_length >= MIN_MATCH) { check_match(strstart, match_start, match_length); flush = ct_tally(strstart-match_start, match_length - MIN_MATCH); lookahead -= match_length; /* Insert new strings in the hash table only if the match length * is not too large. This saves time but degrades compression. */ if (match_length <= max_insert_length) { match_length--; /* string at strstart already in hash table */ do { strstart++; INSERT_STRING(strstart, hash_head); /* strstart never exceeds WSIZE-MAX_MATCH, so there are * always MIN_MATCH bytes ahead. If lookahead < MIN_MATCH * these bytes are garbage, but it does not matter since * the next lookahead bytes will be emitted as literals. */ } while (--match_length != 0); strstart++; } else { strstart += match_length; match_length = 0; ins_h = window[strstart]; UPDATE_HASH(ins_h, window[strstart+1]); #if MIN_MATCH != 3 Call UPDATE_HASH() MIN_MATCH-3 more times #endif } } else { /* No match, output a literal byte */ Tracevv((stderr,"%c",window[strstart])); flush = ct_tally (0, window[strstart]); lookahead--; strstart++; } if (flush) FLUSH_BLOCK(0), block_start = strstart; /* Make sure that we always have enough lookahead, except * at the end of the input file. We need MAX_MATCH bytes * for the next match, plus MIN_MATCH bytes to insert the * string following the next match. */ while (lookahead < MIN_LOOKAHEAD && !eofile) fill_window(); } return FLUSH_BLOCK(1); /* eof */ } /* =========================================================================== * Same as above, but achieves better compression. We use a lazy * evaluation for matches: a match is finally adopted only if there is * no better match at the next window position. */ ulg deflate() { IPos hash_head; /* head of hash chain */ IPos prev_match; /* previous match */ int flush; /* set if current block must be flushed */ int match_available = 0; /* set if previous match exists */ register unsigned match_length = MIN_MATCH-1; /* length of best match */ #ifdef DEBUG extern long isize; /* byte length of input file, for debug only */ #endif if (compr_level <= 3) return deflate_fast(); /* optimized for speed */ /* Process the input block. */ while (lookahead != 0) { /* Insert the string window[strstart .. strstart+2] in the * dictionary, and set hash_head to the head of the hash chain: */ INSERT_STRING(strstart, hash_head); /* Find the longest match, discarding those <= prev_length. */ prev_length = match_length, prev_match = match_start; match_length = MIN_MATCH-1; if (hash_head != NIL && prev_length < max_lazy_match && strstart - hash_head <= MAX_DIST) { /* To simplify the code, we prevent matches with the string * of window index 0 (in particular we have to avoid a match * of the string with itself at the start of the input file). */ match_length = longest_match (hash_head); /* longest_match() sets match_start */ if (match_length > lookahead) match_length = lookahead; /* Ignore a length 3 match if it is too distant: */ if (match_length == MIN_MATCH && strstart-match_start > TOO_FAR){ /* If prev_match is also MIN_MATCH, match_start is garbage * but we will ignore the current match anyway. */ match_length--; } } /* If there was a match at the previous step and the current * match is not better, output the previous match: */ if (prev_length >= MIN_MATCH && match_length <= prev_length) { check_match(strstart-1, prev_match, prev_length); flush = ct_tally(strstart-1-prev_match, prev_length - MIN_MATCH); /* Insert in hash table all strings up to the end of the match. * strstart-1 and strstart are already inserted. */ lookahead -= prev_length-1; prev_length -= 2; do { strstart++; INSERT_STRING(strstart, hash_head); /* strstart never exceeds WSIZE-MAX_MATCH, so there are * always MIN_MATCH bytes ahead. If lookahead < MIN_MATCH * these bytes are garbage, but it does not matter since the * next lookahead bytes will always be emitted as literals. */ } while (--prev_length != 0); match_available = 0; match_length = MIN_MATCH-1; strstart++; if (flush) FLUSH_BLOCK(0), block_start = strstart; } else if (match_available) { /* If there was no match at the previous position, output a * single literal. If there was a match but the current match * is longer, truncate the previous match to a single literal. */ Tracevv((stderr,"%c",window[strstart-1])); if (ct_tally (0, window[strstart-1])) { FLUSH_BLOCK(0), block_start = strstart; } strstart++; lookahead--; } else { /* There is no previous match to compare with, wait for * the next step to decide. */ match_available = 1; strstart++; lookahead--; } Assert (strstart <= isize && lookahead <= isize, "a bit too far"); /* Make sure that we always have enough lookahead, except * at the end of the input file. We need MAX_MATCH bytes * for the next match, plus MIN_MATCH bytes to insert the * string following the next match. */ while (lookahead < MIN_LOOKAHEAD && !eofile) fill_window(); } if (match_available) ct_tally (0, window[strstart-1]); return FLUSH_BLOCK(1); /* eof */ }
/* lzw.h -- define the lzw functions. * Copyright (C) 1992-1993 Jean-loup Gailly. * This is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License, see the file COPYING. */ #if !defined(OF) && defined(lint) # include "gzip.h" #endif #ifndef BITS # define BITS 16 #endif #define INIT_BITS 9 /* Initial number of bits per code */ #define LZW_MAGIC "\037\235" /* Magic header for lzw files, 1F 9D */ #define BIT_MASK 0x1f /* Mask for 'number of compression bits' */ /* Mask 0x20 is reserved to mean a fourth header byte, and 0x40 is free. * It's a pity that old uncompress does not check bit 0x20. That makes * extension of the format actually undesirable because old compress * would just crash on the new format instead of giving a meaningful * error message. It does check the number of bits, but it's more * helpful to say "unsupported format, get a new version" than * "can only handle 16 bits". */ #define BLOCK_MODE 0x80 /* Block compression: if table is full and compression rate is dropping, * clear the dictionary. */ #define LZW_RESERVED 0x60 /* reserved bits */ #define CLEAR 256 /* flush the dictionary */ #define FIRST (CLEAR+1) /* first free entry */ extern int maxbits; /* max bits per code for LZW */ extern int block_mode; /* block compress mode -C compatible with 2.0 */ extern int lzw OF((int in, int out)); extern int unlzw OF((int in, int out));
/* Getopt for GNU. NOTE: getopt is now part of the C library, so if you don't know what "Keep this file name-space clean" means, talk to roland@gnu.ai.mit.edu before changing it! Copyright (C) 1987, 88, 89, 90, 91, 92, 1993 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifndef __STDC__ # ifndef const # define const # endif #endif /* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>. */ #ifndef _NO_PROTO #define _NO_PROTO #endif #include <stdio.h> #include "tailor.h" /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #if defined (_LIBC) || !defined (__GNU_LIBRARY__) /* This needs to come after some library #include to get __GNU_LIBRARY__ defined. */ #ifdef __GNU_LIBRARY__ /* Don't include stdlib.h for non-GNU C libraries because some of them contain conflicting prototypes for getopt. */ #include <stdlib.h> #endif /* GNU C library. */ /* If GETOPT_COMPAT is defined, `+' as well as `--' can introduce a long-named option. Because this is not POSIX.2 compliant, it is being phased out. */ /* #define GETOPT_COMPAT */ /* This version of `getopt' appears to the caller like standard Unix `getopt' but it behaves differently for the user, since it allows the user to intersperse the options with the other arguments. As `getopt' works, it permutes the elements of ARGV so that, when it is done, all the options precede everything else. Thus all application programs are extended to handle flexible argument order. Setting the environment variable POSIXLY_CORRECT disables permutation. Then the behavior is completely standard. GNU application programs can use a third alternative mode in which they can distinguish the relative order of options and other arguments. */ #include "getopt.h" /* For communication from `getopt' to the caller. When `getopt' finds an option that takes an argument, the argument value is returned here. Also, when `ordering' is RETURN_IN_ORDER, each non-option ARGV-element is returned here. */ char *optarg = 0; /* Index in ARGV of the next element to be scanned. This is used for communication to and from the caller and for communication between successive calls to `getopt'. On entry to `getopt', zero means this is the first call; initialize. When `getopt' returns EOF, this is the index of the first of the non-option elements that the caller should itself scan. Otherwise, `optind' communicates from one call to the next how much of ARGV has been scanned so far. */ /* XXX 1003.2 says this must be 1 before any call. */ int optind = 0; /* The next char to be scanned in the option-element in which the last option character we returned was found. This allows us to pick up the scan where we left off. If this is zero, or a null string, it means resume the scan by advancing to the next ARGV-element. */ static char *nextchar; /* Callers store zero here to inhibit the error message for unrecognized options. */ int opterr = 1; /* Set to an option character which was unrecognized. This must be initialized on some systems to avoid linking in the system's own getopt implementation. */ #define BAD_OPTION '\0' int optopt = BAD_OPTION; /* Describe how to deal with options that follow non-option ARGV-elements. If the caller did not specify anything, the default is REQUIRE_ORDER if the environment variable POSIXLY_CORRECT is defined, PERMUTE otherwise. REQUIRE_ORDER means don't recognize them as options; stop option processing when the first non-option is seen. This is what Unix does. This mode of operation is selected by either setting the environment variable POSIXLY_CORRECT, or using `+' as the first character of the list of option characters. PERMUTE is the default. We permute the contents of ARGV as we scan, so that eventually all the non-options are at the end. This allows options to be given in any order, even with programs that were not written to expect this. RETURN_IN_ORDER is an option available to programs that were written to expect options and other ARGV-elements in any order and that care about the ordering of the two. We describe each non-option ARGV-element as if it were the argument of an option with character code 1. Using `-' as the first character of the list of option characters selects this mode of operation. The special argument `--' forces an end of option-scanning regardless of the value of `ordering'. In the case of RETURN_IN_ORDER, only `--' can cause `getopt' to return EOF with `optind' != ARGC. */ static enum { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER } ordering; #ifdef __GNU_LIBRARY__ /* We want to avoid inclusion of string.h with non-GNU libraries because there are many ways it can cause trouble. On some systems, it contains special magic macros that don't work in GCC. */ #include <string.h> #define my_index strchr #define my_strlen strlen #else /* Avoid depending on library functions or files whose names are inconsistent. */ #if __STDC__ || defined(PROTO) extern char *getenv(const char *name); extern int strcmp (const char *s1, const char *s2); extern int strncmp(const char *s1, const char *s2, int n); static int my_strlen(const char *s); static char *my_index (const char *str, int chr); #else extern char *getenv (); #endif static int my_strlen (str) const char *str; { int n = 0; while (*str++) n++; return n; } static char * my_index (str, chr) const char *str; int chr; { while (*str) { if (*str == chr) return (char *) str; str++; } return 0; } #endif /* GNU C library. */ /* Handle permutation of arguments. */ /* Describe the part of ARGV that contains non-options that have been skipped. `first_nonopt' is the index in ARGV of the first of them; `last_nonopt' is the index after the last of them. */ static int first_nonopt; static int last_nonopt; /* Exchange two adjacent subsequences of ARGV. One subsequence is elements [first_nonopt,last_nonopt) which contains all the non-options that have been skipped so far. The other is elements [last_nonopt,optind), which contains all the options processed since those non-options were skipped. `first_nonopt' and `last_nonopt' are relocated so that they describe the new indices of the non-options in ARGV after they are moved. To perform the swap, we first reverse the order of all elements. So all options now come before all non options, but they are in the wrong order. So we put back the options and non options in original order by reversing them again. For example: original input: a b c -x -y reverse all: -y -x c b a reverse options: -x -y c b a reverse non options: -x -y a b c */ #if __STDC__ || defined(PROTO) static void exchange (char **argv); #endif static void exchange (argv) char **argv; { char *temp, **first, **last; /* Reverse all the elements [first_nonopt, optind) */ first = &argv[first_nonopt]; last = &argv[optind-1]; while (first < last) { temp = *first; *first = *last; *last = temp; first++; last--; } /* Put back the options in order */ first = &argv[first_nonopt]; first_nonopt += (optind - last_nonopt); last = &argv[first_nonopt - 1]; while (first < last) { temp = *first; *first = *last; *last = temp; first++; last--; } /* Put back the non options in order */ first = &argv[first_nonopt]; last_nonopt = optind; last = &argv[last_nonopt-1]; while (first < last) { temp = *first; *first = *last; *last = temp; first++; last--; } } /* Scan elements of ARGV (whose length is ARGC) for option characters given in OPTSTRING. If an element of ARGV starts with '-', and is not exactly "-" or "--", then it is an option element. The characters of this element (aside from the initial '-') are option characters. If `getopt' is called repeatedly, it returns successively each of the option characters from each of the option elements. If `getopt' finds another option character, it returns that character, updating `optind' and `nextchar' so that the next call to `getopt' can resume the scan with the following option character or ARGV-element. If there are no more option characters, `getopt' returns `EOF'. Then `optind' is the index in ARGV of the first ARGV-element that is not an option. (The ARGV-elements have been permuted so that those that are not options now come last.) OPTSTRING is a string containing the legitimate option characters. If an option character is seen that is not listed in OPTSTRING, return BAD_OPTION after printing an error message. If you set `opterr' to zero, the error message is suppressed but we still return BAD_OPTION. If a char in OPTSTRING is followed by a colon, that means it wants an arg, so the following text in the same ARGV-element, or the text of the following ARGV-element, is returned in `optarg'. Two colons mean an option that wants an optional arg; if there is text in the current ARGV-element, it is returned in `optarg', otherwise `optarg' is set to zero. If OPTSTRING starts with `-' or `+', it requests different methods of handling the non-option ARGV-elements. See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. Long-named options begin with `--' instead of `-'. Their names may be abbreviated as long as the abbreviation is unique or is an exact match for some defined option. If they have an argument, it follows the option name in the same ARGV-element, separated from the option name by a `=', or else the in next ARGV-element. When `getopt' finds a long-named option, it returns 0 if that option's `flag' field is nonzero, the value of the option's `val' field if the `flag' field is zero. The elements of ARGV aren't really const, because we permute them. But we pretend they're const in the prototype to be compatible with other systems. LONGOPTS is a vector of `struct option' terminated by an element containing a name which is zero. LONGIND returns the index in LONGOPT of the long-named option found. It is only valid when a long-named option has been found by the most recent call. If LONG_ONLY is nonzero, '-' as well as '--' can introduce long-named options. */ int _getopt_internal (argc, argv, optstring, longopts, longind, long_only) int argc; char *const *argv; const char *optstring; const struct option *longopts; int *longind; int long_only; { int option_index; optarg = 0; /* Initialize the internal data when the first call is made. Start processing options with ARGV-element 1 (since ARGV-element 0 is the program name); the sequence of previously skipped non-option ARGV-elements is empty. */ if (optind == 0) { first_nonopt = last_nonopt = optind = 1; nextchar = NULL; /* Determine how to handle the ordering of options and nonoptions. */ if (optstring[0] == '-') { ordering = RETURN_IN_ORDER; ++optstring; } else if (optstring[0] == '+') { ordering = REQUIRE_ORDER; ++optstring; } else if (getenv ("POSIXLY_CORRECT") != NULL) ordering = REQUIRE_ORDER; else ordering = PERMUTE; } if (nextchar == NULL || *nextchar == '\0') { if (ordering == PERMUTE) { /* If we have just processed some options following some non-options, exchange them so that the options come first. */ if (first_nonopt != last_nonopt && last_nonopt != optind) exchange ((char **) argv); else if (last_nonopt != optind) first_nonopt = optind; /* Now skip any additional non-options and extend the range of non-options previously skipped. */ while (optind < argc && (argv[optind][0] != '-' || argv[optind][1] == '\0') #ifdef GETOPT_COMPAT && (longopts == NULL || argv[optind][0] != '+' || argv[optind][1] == '\0') #endif /* GETOPT_COMPAT */ ) optind++; last_nonopt = optind; } /* Special ARGV-element `--' means premature end of options. Skip it like a null option, then exchange with previous non-options as if it were an option, then skip everything else like a non-option. */ if (optind != argc && !strcmp (argv[optind], "--")) { optind++; if (first_nonopt != last_nonopt && last_nonopt != optind) exchange ((char **) argv); else if (first_nonopt == last_nonopt) first_nonopt = optind; last_nonopt = argc; optind = argc; } /* If we have done all the ARGV-elements, stop the scan and back over any non-options that we skipped and permuted. */ if (optind == argc) { /* Set the next-arg-index to point at the non-options that we previously skipped, so the caller will digest them. */ if (first_nonopt != last_nonopt) optind = first_nonopt; return EOF; } /* If we have come to a non-option and did not permute it, either stop the scan or describe it to the caller and pass it by. */ if ((argv[optind][0] != '-' || argv[optind][1] == '\0') #ifdef GETOPT_COMPAT && (longopts == NULL || argv[optind][0] != '+' || argv[optind][1] == '\0') #endif /* GETOPT_COMPAT */ ) { if (ordering == REQUIRE_ORDER) return EOF; optarg = argv[optind++]; return 1; } /* We have found another option-ARGV-element. Start decoding its characters. */ nextchar = (argv[optind] + 1 + (longopts != NULL && argv[optind][1] == '-')); } if (longopts != NULL && ((argv[optind][0] == '-' && (argv[optind][1] == '-' || long_only)) #ifdef GETOPT_COMPAT || argv[optind][0] == '+' #endif /* GETOPT_COMPAT */ )) { const struct option *p; char *s = nextchar; int exact = 0; int ambig = 0; const struct option *pfound = NULL; int indfound = 0; while (*s && *s != '=') s++; /* Test all options for either exact match or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp (p->name, nextchar, s - nextchar)) { if (s - nextchar == my_strlen (p->name)) { /* Exact match found. */ pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { /* First nonexact match found. */ pfound = p; indfound = option_index; } else /* Second nonexact match found. */ ambig = 1; } if (ambig && !exact) { if (opterr) fprintf (stderr, "%s: option `%s' is ambiguous\n", argv[0], argv[optind]); nextchar += my_strlen (nextchar); optind++; return BAD_OPTION; } if (pfound != NULL) { option_index = indfound; optind++; if (*s) { /* Don't test has_arg with >, because some C compilers don't allow it to be used on enums. */ if (pfound->has_arg) optarg = s + 1; else { if (opterr) { if (argv[optind - 1][1] == '-') /* --option */ fprintf (stderr, "%s: option `--%s' doesn't allow an argument\n", argv[0], pfound->name); else /* +option or -option */ fprintf (stderr, "%s: option `%c%s' doesn't allow an argument\n", argv[0], argv[optind - 1][0], pfound->name); } nextchar += my_strlen (nextchar); return BAD_OPTION; } } else if (pfound->has_arg == 1) { if (optind < argc) optarg = argv[optind++]; else { if (opterr) fprintf (stderr, "%s: option `%s' requires an argument\n", argv[0], argv[optind - 1]); nextchar += my_strlen (nextchar); return optstring[0] == ':' ? ':' : BAD_OPTION; } } nextchar += my_strlen (nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } /* Can't find it as a long option. If this is not getopt_long_only, or the option starts with '--' or is not a valid short option, then it's an error. Otherwise interpret it as a short option. */ if (!long_only || argv[optind][1] == '-' #ifdef GETOPT_COMPAT || argv[optind][0] == '+' #endif /* GETOPT_COMPAT */ || my_index (optstring, *nextchar) == NULL) { if (opterr) { if (argv[optind][1] == '-') /* --option */ fprintf (stderr, "%s: unrecognized option `--%s'\n", argv[0], nextchar); else /* +option or -option */ fprintf (stderr, "%s: unrecognized option `%c%s'\n", argv[0], argv[optind][0], nextchar); } nextchar = (char *) ""; optind++; return BAD_OPTION; } } /* Look at and handle the next option-character. */ { char c = *nextchar++; char *temp = my_index (optstring, c); /* Increment `optind' when we start to process its last character. */ if (*nextchar == '\0') ++optind; if (temp == NULL || c == ':') { if (opterr) { #if 0 if (c < 040 || c >= 0177) fprintf (stderr, "%s: unrecognized option, character code 0%o\n", argv[0], c); else fprintf (stderr, "%s: unrecognized option `-%c'\n", argv[0], c); #else /* 1003.2 specifies the format of this message. */ fprintf (stderr, "%s: illegal option -- %c\n", argv[0], c); #endif } optopt = c; return BAD_OPTION; } if (temp[1] == ':') { if (temp[2] == ':') { /* This is an option that accepts an argument optionally. */ if (*nextchar != '\0') { optarg = nextchar; optind++; } else optarg = 0; nextchar = NULL; } else { /* This is an option that requires an argument. */ if (*nextchar != '\0') { optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ optind++; } else if (optind == argc) { if (opterr) { #if 0 fprintf (stderr, "%s: option `-%c' requires an argument\n", argv[0], c); #else /* 1003.2 specifies the format of this message. */ fprintf (stderr, "%s: option requires an argument -- %c\n", argv[0], c); #endif } optopt = c; if (optstring[0] == ':') c = ':'; else c = BAD_OPTION; } else /* We already incremented `optind' once; increment it again when taking next ARGV-elt as argument. */ optarg = argv[optind++]; nextchar = NULL; } } return c; } } int getopt (argc, argv, optstring) int argc; char *const *argv; const char *optstring; { return _getopt_internal (argc, argv, optstring, (const struct option *) 0, (int *) 0, 0); } int getopt_long (argc, argv, options, long_options, opt_index) int argc; char *const *argv; const char *options; const struct option *long_options; int *opt_index; { return _getopt_internal (argc, argv, options, long_options, opt_index, 0); } #endif /* _LIBC or not __GNU_LIBRARY__. */ #ifdef TEST /* Compile with -DTEST to make an executable for use in testing the above definition of `getopt'. */ int main (argc, argv) int argc; char **argv; { int c; int digit_optind = 0; while (1) { int this_option_optind = optind ? optind : 1; c = getopt (argc, argv, "abc:d:0123456789"); if (c == EOF) break; switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (digit_optind != 0 && digit_optind != this_option_optind) printf ("digits occur in two different argv-elements.\n"); digit_optind = this_option_optind; printf ("option %c\n", c); break; case 'a': printf ("option a\n"); break; case 'b': printf ("option b\n"); break; case 'c': printf ("option c with value `%s'\n", optarg); break; case BAD_OPTION: break; default: printf ("?? getopt returned character code 0%o ??\n", c); } } if (optind < argc) { printf ("non-option ARGV-elements: "); while (optind < argc) printf ("%s ", argv[optind++]); printf ("\n"); } exit (0); } #endif /* TEST */
/* inflate.c -- Not copyrighted 1992 by Mark Adler version c10p1, 10 January 1993 */ /* You can do whatever you like with this source file, though I would prefer that if you modify it and redistribute it that you include comments to that effect with your name and the date. Thank you. [The history has been moved to the file ChangeLog.] */ /* Inflate deflated (PKZIP's method 8 compressed) data. The compression method searches for as much of the current string of bytes (up to a length of 258) in the previous 32K bytes. If it doesn't find any matches (of at least length 3), it codes the next byte. Otherwise, it codes the length of the matched string and its distance backwards from the current position. There is a single Huffman code that codes both single bytes (called "literals") and match lengths. A second Huffman code codes the distance information, which follows a length code. Each length or distance code actually represents a base value and a number of "extra" (sometimes zero) bits to get to add to the base value. At the end of each deflated block is a special end-of-block (EOB) literal/ length code. The decoding process is basically: get a literal/length code; if EOB then done; if a literal, emit the decoded byte; if a length then get the distance and emit the referred-to bytes from the sliding window of previously emitted data. There are (currently) three kinds of inflate blocks: stored, fixed, and dynamic. The compressor deals with some chunk of data at a time, and decides which method to use on a chunk-by-chunk basis. A chunk might typically be 32K or 64K. If the chunk is uncompressible, then the "stored" method is used. In this case, the bytes are simply stored as is, eight bits per byte, with none of the above coding. The bytes are preceded by a count, since there is no longer an EOB code. If the data is compressible, then either the fixed or dynamic methods are used. In the dynamic method, the compressed data is preceded by an encoding of the literal/length and distance Huffman codes that are to be used to decode this block. The representation is itself Huffman coded, and so is preceded by a description of that code. These code descriptions take up a little space, and so for small blocks, there is a predefined set of codes, called the fixed codes. The fixed method is used if the block codes up smaller that way (usually for quite small chunks), otherwise the dynamic method is used. In the latter case, the codes are customized to the probabilities in the current block, and so can code it much better than the pre-determined fixed codes. The Huffman codes themselves are decoded using a mutli-level table lookup, in order to maximize the speed of decoding plus the speed of building the decoding tables. See the comments below that precede the lbits and dbits tuning parameters. */ /* Notes beyond the 1.93a appnote.txt: 1. Distance pointers never point before the beginning of the output stream. 2. Distance pointers can point back across blocks, up to 32k away. 3. There is an implied maximum of 7 bits for the bit length table and 15 bits for the actual data. 4. If only one code exists, then it is encoded using one bit. (Zero would be more efficient, but perhaps a little confusing.) If two codes exist, they are coded using one bit each (0 and 1). 5. There is no way of sending zero distance codes--a dummy must be sent if there are none. (History: a pre 2.0 version of PKZIP would store blocks with no distance codes, but this was discovered to be too harsh a criterion.) Valid only for 1.93a. 2.04c does allow zero distance codes, which is sent as one code of zero bits in length. 6. There are up to 286 literal/length codes. Code 256 represents the end-of-block. Note however that the static length tree defines 288 codes just to fill out the Huffman codes. Codes 286 and 287 cannot be used though, since there is no length base or extra bits defined for them. Similarly, there are up to 30 distance codes. However, static trees define 32 codes (all 5 bits) to fill out the Huffman codes, but the last two had better not show up in the data. 7. Unzip can check dynamic Huffman blocks for complete code sets. The exception is that a single code would not be complete (see #4). 8. The five bits following the block type is really the number of literal codes sent minus 257. 9. Length codes 8,16,16 are interpreted as 13 length codes of 8 bits (1+6+6). Therefore, to output three times the length, you output three codes (1+1+1), whereas to output four times the same length, you only need two codes (1+3). Hmm. 10. In the tree reconstruction algorithm, Code = Code + Increment only if BitLength(i) is not zero. (Pretty obvious.) 11. Correction: 4 Bits: # of Bit Length codes - 4 (4 - 19) 12. Note: length code 284 can represent 227-258, but length code 285 really is 258. The last length deserves its own, short code since it gets used a lot in very redundant files. The length 258 is special since 258 - 3 (the min match length) is 255. 13. The literal/length and distance code bit lengths are read as a single stream of lengths. It is possible (and advantageous) for a repeat code (16, 17, or 18) to go across the boundary between the two sets of lengths. */ #ifdef RCSID static char rcsid[] = "$Id: inflate.c,v 0.14 1993/06/10 13:27:04 jloup Exp $"; #endif #include <sys/types.h> #include "tailor.h" #if defined(STDC_HEADERS) || !defined(NO_STDLIB_H) # include <stdlib.h> #endif #include "gzip.h" #define slide window /* Huffman code lookup table entry--this entry is four bytes for machines that have 16-bit pointers (e.g. PC's in the small or medium model). Valid extra bits are 0..13. e == 15 is EOB (end of block), e == 16 means that v is a literal, 16 < e < 32 means that v is a pointer to the next table, which codes e - 16 bits, and lastly e == 99 indicates an unused code. If a code with e == 99 is looked up, this implies an error in the data. */ struct huft { uch e; /* number of extra bits or operation */ uch b; /* number of bits in this code or subcode */ union { ush n; /* literal, length base, or distance base */ struct huft *t; /* pointer to next level of table */ } v; }; /* Function prototypes */ int huft_build OF((unsigned *, unsigned, unsigned, ush *, ush *, struct huft **, int *)); int huft_free OF((struct huft *)); int inflate_codes OF((struct huft *, struct huft *, int, int)); int inflate_stored OF((void)); int inflate_fixed OF((void)); int inflate_dynamic OF((void)); int inflate_block OF((int *)); int inflate OF((void)); /* The inflate algorithm uses a sliding 32K byte window on the uncompressed stream to find repeated byte strings. This is implemented here as a circular buffer. The index is updated simply by incrementing and then and'ing with 0x7fff (32K-1). */ /* It is left to other modules to supply the 32K area. It is assumed to be usable as if it were declared "uch slide[32768];" or as just "uch *slide;" and then malloc'ed in the latter case. The definition must be in unzip.h, included above. */ /* unsigned wp; current position in slide */ #define wp outcnt #define flush_output(w) (wp=(w),flush_window()) /* Tables for deflate from PKZIP's appnote.txt. */ static unsigned border[] = { /* Order of the bit length code lengths */ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; static ush cplens[] = { /* Copy lengths for literal codes 257..285 */ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; /* note: see note #13 above about the 258 in this list. */ static ush cplext[] = { /* Extra bits for literal codes 257..285 */ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 99, 99}; /* 99==invalid */ static ush cpdist[] = { /* Copy offsets for distance codes 0..29 */ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577}; static ush cpdext[] = { /* Extra bits for distance codes */ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; /* Macros for inflate() bit peeking and grabbing. The usage is: NEEDBITS(j) x = b & mask_bits[j]; DUMPBITS(j) where NEEDBITS makes sure that b has at least j bits in it, and DUMPBITS removes the bits from b. The macros use the variable k for the number of bits in b. Normally, b and k are register variables for speed, and are initialized at the beginning of a routine that uses these macros from a global bit buffer and count. If we assume that EOB will be the longest code, then we will never ask for bits with NEEDBITS that are beyond the end of the stream. So, NEEDBITS should not read any more bytes than are needed to meet the request. Then no bytes need to be "returned" to the buffer at the end of the last block. However, this assumption is not true for fixed blocks--the EOB code is 7 bits, but the other literal/length codes can be 8 or 9 bits. (The EOB code is shorter than other codes because fixed blocks are generally short. So, while a block always has an EOB, many other literal/length codes have a significantly lower probability of showing up at all.) However, by making the first table have a lookup of seven bits, the EOB code will be found in that first lookup, and so will not require that too many bits be pulled from the stream. */ ulg bb; /* bit buffer */ unsigned bk; /* bits in bit buffer */ ush mask_bits[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff }; #ifdef CRYPT uch cc; # define NEXTBYTE() \ (decrypt ? (cc = get_byte(), zdecode(cc), cc) : get_byte()) #else # define NEXTBYTE() (uch)get_byte() #endif #define NEEDBITS(n) {while(k<(n)){b|=((ulg)NEXTBYTE())<<k;k+=8;}} #define DUMPBITS(n) {b>>=(n);k-=(n);} /* Huffman code decoding is performed using a multi-level table lookup. The fastest way to decode is to simply build a lookup table whose size is determined by the longest code. However, the time it takes to build this table can also be a factor if the data being decoded is not very long. The most common codes are necessarily the shortest codes, so those codes dominate the decoding time, and hence the speed. The idea is you can have a shorter table that decodes the shorter, more probable codes, and then point to subsidiary tables for the longer codes. The time it costs to decode the longer codes is then traded against the time it takes to make longer tables. This results of this trade are in the variables lbits and dbits below. lbits is the number of bits the first level table for literal/ length codes can decode in one step, and dbits is the same thing for the distance codes. Subsequent tables are also less than or equal to those sizes. These values may be adjusted either when all of the codes are shorter than that, in which case the longest code length in bits is used, or when the shortest code is *longer* than the requested table size, in which case the length of the shortest code in bits is used. There are two different values for the two tables, since they code a different number of possibilities each. The literal/length table codes 286 possible values, or in a flat code, a little over eight bits. The distance table codes 30 possible values, or a little less than five bits, flat. The optimum values for speed end up being about one bit more than those, so lbits is 8+1 and dbits is 5+1. The optimum values may differ though from machine to machine, and possibly even between compilers. Your mileage may vary. */ int lbits = 9; /* bits in base literal/length lookup table */ int dbits = 6; /* bits in base distance lookup table */ /* If BMAX needs to be larger than 16, then h and x[] should be ulg. */ #define BMAX 16 /* maximum bit length of any code (16 for explode) */ #define N_MAX 288 /* maximum number of codes in any set */ unsigned hufts; /* track memory usage */ int huft_build(b, n, s, d, e, t, m) unsigned *b; /* code lengths in bits (all assumed <= BMAX) */ unsigned n; /* number of codes (assumed <= N_MAX) */ unsigned s; /* number of simple-valued codes (0..s-1) */ ush *d; /* list of base values for non-simple codes */ ush *e; /* list of extra bits for non-simple codes */ struct huft **t; /* result: starting table */ int *m; /* maximum lookup bits, returns actual */ /* Given a list of code lengths and a maximum table size, make a set of tables to decode that set of codes. Return zero on success, one if the given code set is incomplete (the tables are still built in this case), two if the input is invalid (all zero length codes or an oversubscribed set of lengths), and three if not enough memory. */ { unsigned a; /* counter for codes of length k */ unsigned c[BMAX+1]; /* bit length count table */ unsigned f; /* i repeats in table every f entries */ int g; /* maximum code length */ int h; /* table level */ register unsigned i; /* counter, current code */ register unsigned j; /* counter */ register int k; /* number of bits in current code */ int l; /* bits per table (returned in m) */ register unsigned *p; /* pointer into c[], b[], or v[] */ register struct huft *q; /* points to current table */ struct huft r; /* table entry for structure assignment */ struct huft *u[BMAX]; /* table stack */ unsigned v[N_MAX]; /* values in order of bit length */ register int w; /* bits before this table == (l * h) */ unsigned x[BMAX+1]; /* bit offsets, then code stack */ unsigned *xp; /* pointer into x */ int y; /* number of dummy codes added */ unsigned z; /* number of entries in current table */ /* Generate counts for each bit length */ memzero(c, sizeof(c)); p = b; i = n; do { Tracecv(*p, (stderr, (n-i >= ' ' && n-i <= '~' ? "%c %d\n" : "0x%x %d\n"), n-i, *p)); c[*p]++; /* assume all entries <= BMAX */ p++; /* Can't combine with above line (Solaris bug) */ } while (--i); if (c[0] == n) /* null input--all zero length codes */ { *t = (struct huft *)NULL; *m = 0; return 0; } /* Find minimum and maximum length, bound *m by those */ l = *m; for (j = 1; j <= BMAX; j++) if (c[j]) break; k = j; /* minimum code length */ if ((unsigned)l < j) l = j; for (i = BMAX; i; i--) if (c[i]) break; g = i; /* maximum code length */ if ((unsigned)l > i) l = i; *m = l; /* Adjust last length count to fill out codes, if needed */ for (y = 1 << j; j < i; j++, y <<= 1) if ((y -= c[j]) < 0) return 2; /* bad input: more codes than bits */ if ((y -= c[i]) < 0) return 2; c[i] += y; /* Generate starting offsets into the value table for each length */ x[1] = j = 0; p = c + 1; xp = x + 2; while (--i) { /* note that i == g from above */ *xp++ = (j += *p++); } /* Make a table of values in order of bit lengths */ p = b; i = 0; do { if ((j = *p++) != 0) v[x[j]++] = i; } while (++i < n); /* Generate the Huffman codes and for each, make the table entries */ x[0] = i = 0; /* first Huffman code is zero */ p = v; /* grab values in bit order */ h = -1; /* no tables yet--level -1 */ w = -l; /* bits decoded == (l * h) */ u[0] = (struct huft *)NULL; /* just to keep compilers happy */ q = (struct huft *)NULL; /* ditto */ z = 0; /* ditto */ /* go through the bit lengths (k already is bits in shortest code) */ for (; k <= g; k++) { a = c[k]; while (a--) { /* here i is the Huffman code of length k bits for value *p */ /* make tables up to required level */ while (k > w + l) { h++; w += l; /* previous table always l bits */ /* compute minimum size table less than or equal to l bits */ z = (z = g - w) > (unsigned)l ? l : z; /* upper limit on table size */ if ((f = 1 << (j = k - w)) > a + 1) /* try a k-w bit table */ { /* too few codes for k-w bit table */ f -= a + 1; /* deduct codes from patterns left */ xp = c + k; while (++j < z) /* try smaller tables up to z bits */ { if ((f <<= 1) <= *++xp) break; /* enough codes to use up j bits */ f -= *xp; /* else deduct codes from patterns */ } } z = 1 << j; /* table entries for j-bit table */ /* allocate and link in new table */ if ((q = (struct huft *)malloc((z + 1)*sizeof(struct huft))) == (struct huft *)NULL) { if (h) huft_free(u[0]); return 3; /* not enough memory */ } hufts += z + 1; /* track memory usage */ *t = q + 1; /* link to list for huft_free() */ *(t = &(q->v.t)) = (struct huft *)NULL; u[h] = ++q; /* table starts after link */ /* connect to last table, if there is one */ if (h) { x[h] = i; /* save pattern for backing up */ r.b = (uch)l; /* bits to dump before this table */ r.e = (uch)(16 + j); /* bits in this table */ r.v.t = q; /* pointer to this table */ j = i >> (w - l); /* (get around Turbo C bug) */ u[h-1][j] = r; /* connect to last table */ } } /* set up table entry in r */ r.b = (uch)(k - w); if (p >= v + n) r.e = 99; /* out of values--invalid code */ else if (*p < s) { r.e = (uch)(*p < 256 ? 16 : 15); /* 256 is end-of-block code */ r.v.n = (ush)(*p); /* simple code is just the value */ p++; /* one compiler does not like *p++ */ } else { r.e = (uch)e[*p - s]; /* non-simple--look up in lists */ r.v.n = d[*p++ - s]; } /* fill code-like entries with r */ f = 1 << (k - w); for (j = i >> w; j < z; j += f) q[j] = r; /* backwards increment the k-bit code i */ for (j = 1 << (k - 1); i & j; j >>= 1) i ^= j; i ^= j; /* backup over finished tables */ while ((i & ((1 << w) - 1)) != x[h]) { h--; /* don't need to update q */ w -= l; } } } /* Return true (1) if we were given an incomplete table */ return y != 0 && g != 1; } int huft_free(t) struct huft *t; /* table to free */ /* Free the malloc'ed tables built by huft_build(), which makes a linked list of the tables it made, with the links in a dummy first entry of each table. */ { register struct huft *p, *q; /* Go through linked list, freeing from the malloced (t[-1]) address. */ p = t; while (p != (struct huft *)NULL) { q = (--p)->v.t; free((char*)p); p = q; } return 0; } int inflate_codes(tl, td, bl, bd) struct huft *tl, *td; /* literal/length and distance decoder tables */ int bl, bd; /* number of bits decoded by tl[] and td[] */ /* inflate (decompress) the codes in a deflated (compressed) block. Return an error code or zero if it all goes ok. */ { register unsigned e; /* table entry flag/number of extra bits */ unsigned n, d; /* length and index for copy */ unsigned w; /* current window position */ struct huft *t; /* pointer to table entry */ unsigned ml, md; /* masks for bl and bd bits */ register ulg b; /* bit buffer */ register unsigned k; /* number of bits in bit buffer */ /* make local copies of globals */ b = bb; /* initialize bit buffer */ k = bk; w = wp; /* initialize window position */ /* inflate the coded data */ ml = mask_bits[bl]; /* precompute masks for speed */ md = mask_bits[bd]; for (;;) /* do until end of block */ { NEEDBITS((unsigned)bl) if ((e = (t = tl + ((unsigned)b & ml))->e) > 16) do { if (e == 99) return 1; DUMPBITS(t->b) e -= 16; NEEDBITS(e) } while ((e = (t = t->v.t + ((unsigned)b & mask_bits[e]))->e) > 16); DUMPBITS(t->b) if (e == 16) /* then it's a literal */ { slide[w++] = (uch)t->v.n; Tracevv((stderr, "%c", slide[w-1])); if (w == WSIZE) { flush_output(w); w = 0; } } else /* it's an EOB or a length */ { /* exit if end of block */ if (e == 15) break; /* get length of block to copy */ NEEDBITS(e) n = t->v.n + ((unsigned)b & mask_bits[e]); DUMPBITS(e); /* decode distance of block to copy */ NEEDBITS((unsigned)bd) if ((e = (t = td + ((unsigned)b & md))->e) > 16) do { if (e == 99) return 1; DUMPBITS(t->b) e -= 16; NEEDBITS(e) } while ((e = (t = t->v.t + ((unsigned)b & mask_bits[e]))->e) > 16); DUMPBITS(t->b) NEEDBITS(e) d = w - t->v.n - ((unsigned)b & mask_bits[e]); DUMPBITS(e) Tracevv((stderr,"\\[%d,%d]", w-d, n)); /* do the copy */ do { n -= (e = (e = WSIZE - ((d &= WSIZE-1) > w ? d : w)) > n ? n : e); #if !defined(NOMEMCPY) && !defined(DEBUG) if (w - d >= e) /* (this test assumes unsigned comparison) */ { memcpy(slide + w, slide + d, e); w += e; d += e; } else /* do it slow to avoid memcpy() overlap */ #endif /* !NOMEMCPY */ do { slide[w++] = slide[d++]; Tracevv((stderr, "%c", slide[w-1])); } while (--e); if (w == WSIZE) { flush_output(w); w = 0; } } while (n); } } /* restore the globals from the locals */ wp = w; /* restore global window pointer */ bb = b; /* restore global bit buffer */ bk = k; /* done */ return 0; } int inflate_stored() /* "decompress" an inflated type 0 (stored) block. */ { unsigned n; /* number of bytes in block */ unsigned w; /* current window position */ register ulg b; /* bit buffer */ register unsigned k; /* number of bits in bit buffer */ /* make local copies of globals */ b = bb; /* initialize bit buffer */ k = bk; w = wp; /* initialize window position */ /* go to byte boundary */ n = k & 7; DUMPBITS(n); /* get the length and its complement */ NEEDBITS(16) n = ((unsigned)b & 0xffff); DUMPBITS(16) NEEDBITS(16) if (n != (unsigned)((~b) & 0xffff)) return 1; /* error in compressed data */ DUMPBITS(16) /* read and output the compressed data */ while (n--) { NEEDBITS(8) slide[w++] = (uch)b; if (w == WSIZE) { flush_output(w); w = 0; } DUMPBITS(8) } /* restore the globals from the locals */ wp = w; /* restore global window pointer */ bb = b; /* restore global bit buffer */ bk = k; return 0; } int inflate_fixed() /* decompress an inflated type 1 (fixed Huffman codes) block. We should either replace this with a custom decoder, or at least precompute the Huffman tables. */ { int i; /* temporary variable */ struct huft *tl; /* literal/length code table */ struct huft *td; /* distance code table */ int bl; /* lookup bits for tl */ int bd; /* lookup bits for td */ unsigned l[288]; /* length list for huft_build */ /* set up literal table */ for (i = 0; i < 144; i++) l[i] = 8; for (; i < 256; i++) l[i] = 9; for (; i < 280; i++) l[i] = 7; for (; i < 288; i++) /* make a complete, but wrong code set */ l[i] = 8; bl = 7; if ((i = huft_build(l, 288, 257, cplens, cplext, &tl, &bl)) != 0) return i; /* set up distance table */ for (i = 0; i < 30; i++) /* make an incomplete code set */ l[i] = 5; bd = 5; if ((i = huft_build(l, 30, 0, cpdist, cpdext, &td, &bd)) > 1) { huft_free(tl); return i; } /* decompress until an end-of-block code */ if (inflate_codes(tl, td, bl, bd)) return 1; /* free the decoding tables, return */ huft_free(tl); huft_free(td); return 0; } int inflate_dynamic() /* decompress an inflated type 2 (dynamic Huffman codes) block. */ { int i; /* temporary variables */ unsigned j; unsigned l; /* last length */ unsigned m; /* mask for bit lengths table */ unsigned n; /* number of lengths to get */ struct huft *tl; /* literal/length code table */ struct huft *td; /* distance code table */ int bl; /* lookup bits for tl */ int bd; /* lookup bits for td */ unsigned nb; /* number of bit length codes */ unsigned nl; /* number of literal/length codes */ unsigned nd; /* number of distance codes */ #ifdef PKZIP_BUG_WORKAROUND unsigned ll[288+32]; /* literal/length and distance code lengths */ #else unsigned ll[286+30]; /* literal/length and distance code lengths */ #endif register ulg b; /* bit buffer */ register unsigned k; /* number of bits in bit buffer */ /* make local bit buffer */ b = bb; k = bk; /* read in table lengths */ NEEDBITS(5) nl = 257 + ((unsigned)b & 0x1f); /* number of literal/length codes */ DUMPBITS(5) NEEDBITS(5) nd = 1 + ((unsigned)b & 0x1f); /* number of distance codes */ DUMPBITS(5) NEEDBITS(4) nb = 4 + ((unsigned)b & 0xf); /* number of bit length codes */ DUMPBITS(4) #ifdef PKZIP_BUG_WORKAROUND if (nl > 288 || nd > 32) #else if (nl > 286 || nd > 30) #endif return 1; /* bad lengths */ /* read in bit-length-code lengths */ for (j = 0; j < nb; j++) { NEEDBITS(3) ll[border[j]] = (unsigned)b & 7; DUMPBITS(3) } for (; j < 19; j++) ll[border[j]] = 0; /* build decoding table for trees--single level, 7 bit lookup */ bl = 7; if ((i = huft_build(ll, 19, 19, NULL, NULL, &tl, &bl)) != 0) { if (i == 1) huft_free(tl); return i; /* incomplete code set */ } /* read in literal and distance code lengths */ n = nl + nd; m = mask_bits[bl]; i = l = 0; while ((unsigned)i < n) { NEEDBITS((unsigned)bl) j = (td = tl + ((unsigned)b & m))->b; DUMPBITS(j) j = td->v.n; if (j < 16) /* length of code in bits (0..15) */ ll[i++] = l = j; /* save last length in l */ else if (j == 16) /* repeat last length 3 to 6 times */ { NEEDBITS(2) j = 3 + ((unsigned)b & 3); DUMPBITS(2) if ((unsigned)i + j > n) return 1; while (j--) ll[i++] = l; } else if (j == 17) /* 3 to 10 zero length codes */ { NEEDBITS(3) j = 3 + ((unsigned)b & 7); DUMPBITS(3) if ((unsigned)i + j > n) return 1; while (j--) ll[i++] = 0; l = 0; } else /* j == 18: 11 to 138 zero length codes */ { NEEDBITS(7) j = 11 + ((unsigned)b & 0x7f); DUMPBITS(7) if ((unsigned)i + j > n) return 1; while (j--) ll[i++] = 0; l = 0; } } /* free decoding table for trees */ huft_free(tl); /* restore the global bit buffer */ bb = b; bk = k; /* build the decoding tables for literal/length and distance codes */ bl = lbits; if ((i = huft_build(ll, nl, 257, cplens, cplext, &tl, &bl)) != 0) { if (i == 1) { fprintf(stderr, " incomplete literal tree\n"); huft_free(tl); } return i; /* incomplete code set */ } bd = dbits; if ((i = huft_build(ll + nl, nd, 0, cpdist, cpdext, &td, &bd)) != 0) { if (i == 1) { fprintf(stderr, " incomplete distance tree\n"); #ifdef PKZIP_BUG_WORKAROUND i = 0; } #else huft_free(td); } huft_free(tl); return i; /* incomplete code set */ #endif } /* decompress until an end-of-block code */ if (inflate_codes(tl, td, bl, bd)) return 1; /* free the decoding tables, return */ huft_free(tl); huft_free(td); return 0; } int inflate_block(e) int *e; /* last block flag */ /* decompress an inflated block */ { unsigned t; /* block type */ register ulg b; /* bit buffer */ register unsigned k; /* number of bits in bit buffer */ /* make local bit buffer */ b = bb; k = bk; /* read in last block bit */ NEEDBITS(1) *e = (int)b & 1; DUMPBITS(1) /* read in block type */ NEEDBITS(2) t = (unsigned)b & 3; DUMPBITS(2) /* restore the global bit buffer */ bb = b; bk = k; /* inflate that block type */ if (t == 2) return inflate_dynamic(); if (t == 0) return inflate_stored(); if (t == 1) return inflate_fixed(); /* bad block type */ return 2; } int inflate() /* decompress an inflated entry */ { int e; /* last block flag */ int r; /* result code */ unsigned h; /* maximum struct huft's malloc'ed */ /* initialize window, bit buffer */ wp = 0; bk = 0; bb = 0; /* decompress until the last block */ h = 0; do { hufts = 0; if ((r = inflate_block(&e)) != 0) return r; if (hufts > h) h = hufts; } while (!e); /* Undo too much lookahead. The next read will be byte aligned so we * can discard unused bits in the last meaningful byte. */ while (bk >= 8) { bk -= 8; inptr--; } /* flush out slide */ flush_output(wp); /* return success */ #ifdef DEBUG fprintf(stderr, "<%u> ", h); #endif /* DEBUG */ return 0; }
/* lzw.c -- compress files in LZW format. * This is a dummy version avoiding patent problems. */ #ifdef RCSID static char rcsid[] = "$Id: lzw.c,v 0.9 1993/06/10 13:27:31 jloup Exp $"; #endif #include "tailor.h" #include "gzip.h" #include "lzw.h" static int msg_done = 0; /* Compress in to out with lzw method. */ int lzw(in, out) int in, out; { if (msg_done) return ERROR; msg_done = 1; fprintf(stderr,"output in compress .Z format not supported\n"); if (in != out) { /* avoid warnings on unused variables */ exit_code = ERROR; } return ERROR; }
/* trees.c -- output deflated data using Huffman coding * Copyright (C) 1992-1993 Jean-loup Gailly * This is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License, see the file COPYING. */ /* * PURPOSE * * Encode various sets of source values using variable-length * binary code trees. * * DISCUSSION * * The PKZIP "deflation" process uses several Huffman trees. The more * common source values are represented by shorter bit sequences. * * Each code tree is stored in the ZIP file in a compressed form * which is itself a Huffman encoding of the lengths of * all the code strings (in ascending order by source values). * The actual code strings are reconstructed from the lengths in * the UNZIP process, as described in the "application note" * (APPNOTE.TXT) distributed as part of PKWARE's PKZIP program. * * REFERENCES * * Lynch, Thomas J. * Data Compression: Techniques and Applications, pp. 53-55. * Lifetime Learning Publications, 1985. ISBN 0-534-03418-7. * * Storer, James A. * Data Compression: Methods and Theory, pp. 49-50. * Computer Science Press, 1988. ISBN 0-7167-8156-5. * * Sedgewick, R. * Algorithms, p290. * Addison-Wesley, 1983. ISBN 0-201-06672-6. * * INTERFACE * * void ct_init (ush *attr, int *methodp) * Allocate the match buffer, initialize the various tables and save * the location of the internal file attribute (ascii/binary) and * method (DEFLATE/STORE) * * void ct_tally (int dist, int lc); * Save the match info and tally the frequency counts. * * long flush_block (char *buf, ulg stored_len, int eof) * Determine the best encoding for the current block: dynamic trees, * static trees or store, and output the encoded block to the zip * file. Returns the total compressed length for the file so far. * */ #include <ctype.h> #include "tailor.h" #include "gzip.h" #ifdef RCSID static char rcsid[] = "$Id: trees.c,v 0.12 1993/06/10 13:27:54 jloup Exp $"; #endif /* =========================================================================== * Constants */ #define MAX_BITS 15 /* All codes must not exceed MAX_BITS bits */ #define MAX_BL_BITS 7 /* Bit length codes must not exceed MAX_BL_BITS bits */ #define LENGTH_CODES 29 /* number of length codes, not counting the special END_BLOCK code */ #define LITERALS 256 /* number of literal bytes 0..255 */ #define END_BLOCK 256 /* end of block literal code */ #define L_CODES (LITERALS+1+LENGTH_CODES) /* number of Literal or Length codes, including the END_BLOCK code */ #define D_CODES 30 /* number of distance codes */ #define BL_CODES 19 /* number of codes used to transfer the bit lengths */ local int near extra_lbits[LENGTH_CODES] /* extra bits for each length code */ = {0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0}; local int near extra_dbits[D_CODES] /* extra bits for each distance code */ = {0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; local int near extra_blbits[BL_CODES]/* extra bits for each bit length code */ = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7}; #define STORED_BLOCK 0 #define STATIC_TREES 1 #define DYN_TREES 2 /* The three kinds of block type */ #ifndef LIT_BUFSIZE # ifdef SMALL_MEM # define LIT_BUFSIZE 0x2000 # else # ifdef MEDIUM_MEM # define LIT_BUFSIZE 0x4000 # else # define LIT_BUFSIZE 0x8000 # endif # endif #endif #ifndef DIST_BUFSIZE # define DIST_BUFSIZE LIT_BUFSIZE #endif /* Sizes of match buffers for literals/lengths and distances. There are * 4 reasons for limiting LIT_BUFSIZE to 64K: * - frequencies can be kept in 16 bit counters * - if compression is not successful for the first block, all input data is * still in the window so we can still emit a stored block even when input * comes from standard input. (This can also be done for all blocks if * LIT_BUFSIZE is not greater than 32K.) * - if compression is not successful for a file smaller than 64K, we can * even emit a stored file instead of a stored block (saving 5 bytes). * - creating new Huffman trees less frequently may not provide fast * adaptation to changes in the input data statistics. (Take for * example a binary file with poorly compressible code followed by * a highly compressible string table.) Smaller buffer sizes give * fast adaptation but have of course the overhead of transmitting trees * more frequently. * - I can't count above 4 * The current code is general and allows DIST_BUFSIZE < LIT_BUFSIZE (to save * memory at the expense of compression). Some optimizations would be possible * if we rely on DIST_BUFSIZE == LIT_BUFSIZE. */ #if LIT_BUFSIZE > INBUFSIZ error cannot overlay l_buf and inbuf #endif #define REP_3_6 16 /* repeat previous bit length 3-6 times (2 bits of repeat count) */ #define REPZ_3_10 17 /* repeat a zero length 3-10 times (3 bits of repeat count) */ #define REPZ_11_138 18 /* repeat a zero length 11-138 times (7 bits of repeat count) */ /* =========================================================================== * Local data */ /* Data structure describing a single value and its code string. */ typedef struct ct_data { union { ush freq; /* frequency count */ ush code; /* bit string */ } fc; union { ush dad; /* father node in Huffman tree */ ush len; /* length of bit string */ } dl; } ct_data; #define Freq fc.freq #define Code fc.code #define Dad dl.dad #define Len dl.len #define HEAP_SIZE (2*L_CODES+1) /* maximum heap size */ local ct_data near dyn_ltree[HEAP_SIZE]; /* literal and length tree */ local ct_data near dyn_dtree[2*D_CODES+1]; /* distance tree */ local ct_data near static_ltree[L_CODES+2]; /* The static literal tree. Since the bit lengths are imposed, there is no * need for the L_CODES extra codes used during heap construction. However * The codes 286 and 287 are needed to build a canonical tree (see ct_init * below). */ local ct_data near static_dtree[D_CODES]; /* The static distance tree. (Actually a trivial tree since all codes use * 5 bits.) */ local ct_data near bl_tree[2*BL_CODES+1]; /* Huffman tree for the bit lengths */ typedef struct tree_desc { ct_data near *dyn_tree; /* the dynamic tree */ ct_data near *static_tree; /* corresponding static tree or NULL */ int near *extra_bits; /* extra bits for each code or NULL */ int extra_base; /* base index for extra_bits */ int elems; /* max number of elements in the tree */ int max_length; /* max bit length for the codes */ int max_code; /* largest code with non zero frequency */ } tree_desc; local tree_desc near l_desc = {dyn_ltree, static_ltree, extra_lbits, LITERALS+1, L_CODES, MAX_BITS, 0}; local tree_desc near d_desc = {dyn_dtree, static_dtree, extra_dbits, 0, D_CODES, MAX_BITS, 0}; local tree_desc near bl_desc = {bl_tree, (ct_data near *)0, extra_blbits, 0, BL_CODES, MAX_BL_BITS, 0}; local ush near bl_count[MAX_BITS+1]; /* number of codes at each bit length for an optimal tree */ local uch near bl_order[BL_CODES] = {16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15}; /* The lengths of the bit length codes are sent in order of decreasing * probability, to avoid transmitting the lengths for unused bit length codes. */ local int near heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ local int heap_len; /* number of elements in the heap */ local int heap_max; /* element of largest frequency */ /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. * The same heap array is used to build all trees. */ local uch near depth[2*L_CODES+1]; /* Depth of each subtree used as tie breaker for trees of equal frequency */ local uch length_code[MAX_MATCH-MIN_MATCH+1]; /* length code for each normalized match length (0 == MIN_MATCH) */ local uch dist_code[512]; /* distance codes. The first 256 values correspond to the distances * 3 .. 258, the last 256 values correspond to the top 8 bits of * the 15 bit distances. */ local int near base_length[LENGTH_CODES]; /* First normalized length for each code (0 = MIN_MATCH) */ local int near base_dist[D_CODES]; /* First normalized distance for each code (0 = distance of 1) */ #define l_buf inbuf /* DECLARE(uch, l_buf, LIT_BUFSIZE); buffer for literals or lengths */ /* DECLARE(ush, d_buf, DIST_BUFSIZE); buffer for distances */ local uch near flag_buf[(LIT_BUFSIZE/8)]; /* flag_buf is a bit array distinguishing literals from lengths in * l_buf, thus indicating the presence or absence of a distance. */ local unsigned last_lit; /* running index in l_buf */ local unsigned last_dist; /* running index in d_buf */ local unsigned last_flags; /* running index in flag_buf */ local uch flags; /* current flags not yet saved in flag_buf */ local uch flag_bit; /* current bit used in flags */ /* bits are filled in flags starting at bit 0 (least significant). * Note: these flags are overkill in the current code since we don't * take advantage of DIST_BUFSIZE == LIT_BUFSIZE. */ local ulg opt_len; /* bit length of current block with optimal trees */ local ulg static_len; /* bit length of current block with static trees */ local ulg compressed_len; /* total bit length of compressed file */ local ulg input_len; /* total byte length of input file */ /* input_len is for debugging only since we can get it by other means. */ ush *file_type; /* pointer to UNKNOWN, BINARY or ASCII */ int *file_method; /* pointer to DEFLATE or STORE */ #ifdef DEBUG extern ulg bits_sent; /* bit length of the compressed data */ extern long isize; /* byte length of input file */ #endif extern long block_start; /* window offset of current block */ extern unsigned near strstart; /* window offset of current string */ /* =========================================================================== * Local (static) routines in this file. */ local void init_block OF((void)); local void pqdownheap OF((ct_data near *tree, int k)); local void gen_bitlen OF((tree_desc near *desc)); local void gen_codes OF((ct_data near *tree, int max_code)); local void build_tree OF((tree_desc near *desc)); local void scan_tree OF((ct_data near *tree, int max_code)); local void send_tree OF((ct_data near *tree, int max_code)); local int build_bl_tree OF((void)); local void send_all_trees OF((int lcodes, int dcodes, int blcodes)); local void compress_block OF((ct_data near *ltree, ct_data near *dtree)); local void set_file_type OF((void)); #ifndef DEBUG # define send_code(c, tree) send_bits(tree[c].Code, tree[c].Len) /* Send a code of the given tree. c and tree must not have side effects */ #else /* DEBUG */ # define send_code(c, tree) \ { if (verbose>1) fprintf(stderr,"\ncd %3d ",(c)); \ send_bits(tree[c].Code, tree[c].Len); } #endif #define d_code(dist) \ ((dist) < 256 ? dist_code[dist] : dist_code[256+((dist)>>7)]) /* Mapping from a distance to a distance code. dist is the distance - 1 and * must not have side effects. dist_code[256] and dist_code[257] are never * used. */ #define MAX(a,b) (a >= b ? a : b) /* the arguments must not have side effects */ /* =========================================================================== * Allocate the match buffer, initialize the various tables and save the * location of the internal file attribute (ascii/binary) and method * (DEFLATE/STORE). */ void ct_init(attr, methodp) ush *attr; /* pointer to internal file attribute */ int *methodp; /* pointer to compression method */ { int n; /* iterates over tree elements */ int bits; /* bit counter */ int length; /* length value */ int code; /* code value */ int dist; /* distance index */ file_type = attr; file_method = methodp; compressed_len = input_len = 0L; if (static_dtree[0].Len != 0) return; /* ct_init already called */ /* Initialize the mapping length (0..255) -> length code (0..28) */ length = 0; for (code = 0; code < LENGTH_CODES-1; code++) { base_length[code] = length; for (n = 0; n < (1<<extra_lbits[code]); n++) { length_code[length++] = (uch)code; } } Assert (length == 256, "ct_init: length != 256"); /* Note that the length 255 (match length 258) can be represented * in two different ways: code 284 + 5 bits or code 285, so we * overwrite length_code[255] to use the best encoding: */ length_code[length-1] = (uch)code; /* Initialize the mapping dist (0..32K) -> dist code (0..29) */ dist = 0; for (code = 0 ; code < 16; code++) { base_dist[code] = dist; for (n = 0; n < (1<<extra_dbits[code]); n++) { dist_code[dist++] = (uch)code; } } Assert (dist == 256, "ct_init: dist != 256"); dist >>= 7; /* from now on, all distances are divided by 128 */ for ( ; code < D_CODES; code++) { base_dist[code] = dist << 7; for (n = 0; n < (1<<(extra_dbits[code]-7)); n++) { dist_code[256 + dist++] = (uch)code; } } Assert (dist == 256, "ct_init: 256+dist != 512"); /* Construct the codes of the static literal tree */ for (bits = 0; bits <= MAX_BITS; bits++) bl_count[bits] = 0; n = 0; while (n <= 143) static_ltree[n++].Len = 8, bl_count[8]++; while (n <= 255) static_ltree[n++].Len = 9, bl_count[9]++; while (n <= 279) static_ltree[n++].Len = 7, bl_count[7]++; while (n <= 287) static_ltree[n++].Len = 8, bl_count[8]++; /* Codes 286 and 287 do not exist, but we must include them in the * tree construction to get a canonical Huffman tree (longest code * all ones) */ gen_codes((ct_data near *)static_ltree, L_CODES+1); /* The static distance tree is trivial: */ for (n = 0; n < D_CODES; n++) { static_dtree[n].Len = 5; static_dtree[n].Code = bi_reverse(n, 5); } /* Initialize the first block of the first file: */ init_block(); } /* =========================================================================== * Initialize a new block. */ local void init_block() { int n; /* iterates over tree elements */ /* Initialize the trees. */ for (n = 0; n < L_CODES; n++) dyn_ltree[n].Freq = 0; for (n = 0; n < D_CODES; n++) dyn_dtree[n].Freq = 0; for (n = 0; n < BL_CODES; n++) bl_tree[n].Freq = 0; dyn_ltree[END_BLOCK].Freq = 1; opt_len = static_len = 0L; last_lit = last_dist = last_flags = 0; flags = 0; flag_bit = 1; } #define SMALLEST 1 /* Index within the heap array of least frequent node in the Huffman tree */ /* =========================================================================== * Remove the smallest element from the heap and recreate the heap with * one less element. Updates heap and heap_len. */ #define pqremove(tree, top) \ {\ top = heap[SMALLEST]; \ heap[SMALLEST] = heap[heap_len--]; \ pqdownheap(tree, SMALLEST); \ } /* =========================================================================== * Compares to subtrees, using the tree depth as tie breaker when * the subtrees have equal frequency. This minimizes the worst case length. */ #define smaller(tree, n, m) \ (tree[n].Freq < tree[m].Freq || \ (tree[n].Freq == tree[m].Freq && depth[n] <= depth[m])) /* =========================================================================== * Restore the heap property by moving down the tree starting at node k, * exchanging a node with the smallest of its two sons if necessary, stopping * when the heap property is re-established (each father smaller than its * two sons). */ local void pqdownheap(tree, k) ct_data near *tree; /* the tree to restore */ int k; /* node to move down */ { int v = heap[k]; int j = k << 1; /* left son of k */ while (j <= heap_len) { /* Set j to the smallest of the two sons: */ if (j < heap_len && smaller(tree, heap[j+1], heap[j])) j++; /* Exit if v is smaller than both sons */ if (smaller(tree, v, heap[j])) break; /* Exchange v with the smallest son */ heap[k] = heap[j]; k = j; /* And continue down the tree, setting j to the left son of k */ j <<= 1; } heap[k] = v; } /* =========================================================================== * Compute the optimal bit lengths for a tree and update the total bit length * for the current block. * IN assertion: the fields freq and dad are set, heap[heap_max] and * above are the tree nodes sorted by increasing frequency. * OUT assertions: the field len is set to the optimal bit length, the * array bl_count contains the frequencies for each bit length. * The length opt_len is updated; static_len is also updated if stree is * not null. */ local void gen_bitlen(desc) tree_desc near *desc; /* the tree descriptor */ { ct_data near *tree = desc->dyn_tree; int near *extra = desc->extra_bits; int base = desc->extra_base; int max_code = desc->max_code; int max_length = desc->max_length; ct_data near *stree = desc->static_tree; int h; /* heap index */ int n, m; /* iterate over the tree elements */ int bits; /* bit length */ int xbits; /* extra bits */ ush f; /* frequency */ int overflow = 0; /* number of elements with bit length too large */ for (bits = 0; bits <= MAX_BITS; bits++) bl_count[bits] = 0; /* In a first pass, compute the optimal bit lengths (which may * overflow in the case of the bit length tree). */ tree[heap[heap_max]].Len = 0; /* root of the heap */ for (h = heap_max+1; h < HEAP_SIZE; h++) { n = heap[h]; bits = tree[tree[n].Dad].Len + 1; if (bits > max_length) bits = max_length, overflow++; tree[n].Len = (ush)bits; /* We overwrite tree[n].Dad which is no longer needed */ if (n > max_code) continue; /* not a leaf node */ bl_count[bits]++; xbits = 0; if (n >= base) xbits = extra[n-base]; f = tree[n].Freq; opt_len += (ulg)f * (bits + xbits); if (stree) static_len += (ulg)f * (stree[n].Len + xbits); } if (overflow == 0) return; Trace((stderr,"\nbit length overflow\n")); /* This happens for example on obj2 and pic of the Calgary corpus */ /* Find the first bit length which could increase: */ do { bits = max_length-1; while (bl_count[bits] == 0) bits--; bl_count[bits]--; /* move one leaf down the tree */ bl_count[bits+1] += 2; /* move one overflow item as its brother */ bl_count[max_length]--; /* The brother of the overflow item also moves one step up, * but this does not affect bl_count[max_length] */ overflow -= 2; } while (overflow > 0); /* Now recompute all bit lengths, scanning in increasing frequency. * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all * lengths instead of fixing only the wrong ones. This idea is taken * from 'ar' written by Haruhiko Okumura.) */ for (bits = max_length; bits != 0; bits--) { n = bl_count[bits]; while (n != 0) { m = heap[--h]; if (m > max_code) continue; if (tree[m].Len != (unsigned) bits) { Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); opt_len += ((long)bits-(long)tree[m].Len)*(long)tree[m].Freq; tree[m].Len = (ush)bits; } n--; } } } /* =========================================================================== * Generate the codes for a given tree and bit counts (which need not be * optimal). * IN assertion: the array bl_count contains the bit length statistics for * the given tree and the field len is set for all tree elements. * OUT assertion: the field code is set for all tree elements of non * zero code length. */ local void gen_codes (tree, max_code) ct_data near *tree; /* the tree to decorate */ int max_code; /* largest code with non zero frequency */ { ush next_code[MAX_BITS+1]; /* next code value for each bit length */ ush code = 0; /* running code value */ int bits; /* bit index */ int n; /* code index */ /* The distribution counts are first used to generate the code values * without bit reversal. */ for (bits = 1; bits <= MAX_BITS; bits++) { next_code[bits] = code = (code + bl_count[bits-1]) << 1; } /* Check that the bit counts in bl_count are consistent. The last code * must be all ones. */ Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1, "inconsistent bit counts"); Tracev((stderr,"\ngen_codes: max_code %d ", max_code)); for (n = 0; n <= max_code; n++) { int len = tree[n].Len; if (len == 0) continue; /* Now reverse the bits */ tree[n].Code = bi_reverse(next_code[len]++, len); Tracec(tree != static_ltree, (stderr,"\nn %3d %c l %2d c %4x (%x) ", n, (isgraph(n) ? n : ' '), len, tree[n].Code, next_code[len]-1)); } } /* =========================================================================== * Construct one Huffman tree and assigns the code bit strings and lengths. * Update the total bit length for the current block. * IN assertion: the field freq is set for all tree elements. * OUT assertions: the fields len and code are set to the optimal bit length * and corresponding code. The length opt_len is updated; static_len is * also updated if stree is not null. The field max_code is set. */ local void build_tree(desc) tree_desc near *desc; /* the tree descriptor */ { ct_data near *tree = desc->dyn_tree; ct_data near *stree = desc->static_tree; int elems = desc->elems; int n, m; /* iterate over heap elements */ int max_code = -1; /* largest code with non zero frequency */ int node = elems; /* next internal node of the tree */ /* Construct the initial heap, with least frequent element in * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. * heap[0] is not used. */ heap_len = 0, heap_max = HEAP_SIZE; for (n = 0; n < elems; n++) { if (tree[n].Freq != 0) { heap[++heap_len] = max_code = n; depth[n] = 0; } else { tree[n].Len = 0; } } /* The pkzip format requires that at least one distance code exists, * and that at least one bit should be sent even if there is only one * possible code. So to avoid special checks later on we force at least * two codes of non zero frequency. */ while (heap_len < 2) { int new = heap[++heap_len] = (max_code < 2 ? ++max_code : 0); tree[new].Freq = 1; depth[new] = 0; opt_len--; if (stree) static_len -= stree[new].Len; /* new is 0 or 1 so it does not have extra bits */ } desc->max_code = max_code; /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, * establish sub-heaps of increasing lengths: */ for (n = heap_len/2; n >= 1; n--) pqdownheap(tree, n); /* Construct the Huffman tree by repeatedly combining the least two * frequent nodes. */ do { pqremove(tree, n); /* n = node of least frequency */ m = heap[SMALLEST]; /* m = node of next least frequency */ heap[--heap_max] = n; /* keep the nodes sorted by frequency */ heap[--heap_max] = m; /* Create a new node father of n and m */ tree[node].Freq = tree[n].Freq + tree[m].Freq; depth[node] = (uch) (MAX(depth[n], depth[m]) + 1); tree[n].Dad = tree[m].Dad = (ush)node; #ifdef DUMP_BL_TREE if (tree == bl_tree) { fprintf(stderr,"\nnode %d(%d), sons %d(%d) %d(%d)", node, tree[node].Freq, n, tree[n].Freq, m, tree[m].Freq); } #endif /* and insert the new node in the heap */ heap[SMALLEST] = node++; pqdownheap(tree, SMALLEST); } while (heap_len >= 2); heap[--heap_max] = heap[SMALLEST]; /* At this point, the fields freq and dad are set. We can now * generate the bit lengths. */ gen_bitlen((tree_desc near *)desc); /* The field len is now set, we can generate the bit codes */ gen_codes ((ct_data near *)tree, max_code); } /* =========================================================================== * Scan a literal or distance tree to determine the frequencies of the codes * in the bit length tree. Updates opt_len to take into account the repeat * counts. (The contribution of the bit length codes will be added later * during the construction of bl_tree.) */ local void scan_tree (tree, max_code) ct_data near *tree; /* the tree to be scanned */ int max_code; /* and its largest code of non zero frequency */ { int n; /* iterates over all tree elements */ int prevlen = -1; /* last emitted length */ int curlen; /* length of current code */ int nextlen = tree[0].Len; /* length of next code */ int count = 0; /* repeat count of the current code */ int max_count = 7; /* max repeat count */ int min_count = 4; /* min repeat count */ if (nextlen == 0) max_count = 138, min_count = 3; tree[max_code+1].Len = (ush)0xffff; /* guard */ for (n = 0; n <= max_code; n++) { curlen = nextlen; nextlen = tree[n+1].Len; if (++count < max_count && curlen == nextlen) { continue; } else if (count < min_count) { bl_tree[curlen].Freq += count; } else if (curlen != 0) { if (curlen != prevlen) bl_tree[curlen].Freq++; bl_tree[REP_3_6].Freq++; } else if (count <= 10) { bl_tree[REPZ_3_10].Freq++; } else { bl_tree[REPZ_11_138].Freq++; } count = 0; prevlen = curlen; if (nextlen == 0) { max_count = 138, min_count = 3; } else if (curlen == nextlen) { max_count = 6, min_count = 3; } else { max_count = 7, min_count = 4; } } } /* =========================================================================== * Send a literal or distance tree in compressed form, using the codes in * bl_tree. */ local void send_tree (tree, max_code) ct_data near *tree; /* the tree to be scanned */ int max_code; /* and its largest code of non zero frequency */ { int n; /* iterates over all tree elements */ int prevlen = -1; /* last emitted length */ int curlen; /* length of current code */ int nextlen = tree[0].Len; /* length of next code */ int count = 0; /* repeat count of the current code */ int max_count = 7; /* max repeat count */ int min_count = 4; /* min repeat count */ /* tree[max_code+1].Len = -1; */ /* guard already set */ if (nextlen == 0) max_count = 138, min_count = 3; for (n = 0; n <= max_code; n++) { curlen = nextlen; nextlen = tree[n+1].Len; if (++count < max_count && curlen == nextlen) { continue; } else if (count < min_count) { do { send_code(curlen, bl_tree); } while (--count != 0); } else if (curlen != 0) { if (curlen != prevlen) { send_code(curlen, bl_tree); count--; } Assert(count >= 3 && count <= 6, " 3_6?"); send_code(REP_3_6, bl_tree); send_bits(count-3, 2); } else if (count <= 10) { send_code(REPZ_3_10, bl_tree); send_bits(count-3, 3); } else { send_code(REPZ_11_138, bl_tree); send_bits(count-11, 7); } count = 0; prevlen = curlen; if (nextlen == 0) { max_count = 138, min_count = 3; } else if (curlen == nextlen) { max_count = 6, min_count = 3; } else { max_count = 7, min_count = 4; } } } /* =========================================================================== * Construct the Huffman tree for the bit lengths and return the index in * bl_order of the last bit length code to send. */ local int build_bl_tree() { int max_blindex; /* index of last bit length code of non zero freq */ /* Determine the bit length frequencies for literal and distance trees */ scan_tree((ct_data near *)dyn_ltree, l_desc.max_code); scan_tree((ct_data near *)dyn_dtree, d_desc.max_code); /* Build the bit length tree: */ build_tree((tree_desc near *)(&bl_desc)); /* opt_len now includes the length of the tree representations, except * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. */ /* Determine the number of bit length codes to send. The pkzip format * requires that at least 4 bit length codes be sent. (appnote.txt says * 3 but the actual value used is 4.) */ for (max_blindex = BL_CODES-1; max_blindex >= 3; max_blindex--) { if (bl_tree[bl_order[max_blindex]].Len != 0) break; } /* Update opt_len to include the bit length tree and counts */ opt_len += 3*(max_blindex+1) + 5+5+4; Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", opt_len, static_len)); return max_blindex; } /* =========================================================================== * Send the header for a block using dynamic Huffman trees: the counts, the * lengths of the bit length codes, the literal tree and the distance tree. * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. */ local void send_all_trees(lcodes, dcodes, blcodes) int lcodes, dcodes, blcodes; /* number of codes for each tree */ { int rank; /* index in bl_order */ Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, "too many codes"); Tracev((stderr, "\nbl counts: ")); send_bits(lcodes-257, 5); /* not +255 as stated in appnote.txt */ send_bits(dcodes-1, 5); send_bits(blcodes-4, 4); /* not -3 as stated in appnote.txt */ for (rank = 0; rank < blcodes; rank++) { Tracev((stderr, "\nbl code %2d ", bl_order[rank])); send_bits(bl_tree[bl_order[rank]].Len, 3); } Tracev((stderr, "\nbl tree: sent %ld", bits_sent)); send_tree((ct_data near *)dyn_ltree, lcodes-1); /* send the literal tree */ Tracev((stderr, "\nlit tree: sent %ld", bits_sent)); send_tree((ct_data near *)dyn_dtree, dcodes-1); /* send the distance tree */ Tracev((stderr, "\ndist tree: sent %ld", bits_sent)); } /* =========================================================================== * Determine the best encoding for the current block: dynamic trees, static * trees or store, and output the encoded block to the zip file. This function * returns the total compressed length for the file so far. */ ulg flush_block(buf, stored_len, eof) char *buf; /* input block, or NULL if too old */ ulg stored_len; /* length of input block */ int eof; /* true if this is the last block for a file */ { ulg opt_lenb, static_lenb; /* opt_len and static_len in bytes */ int max_blindex; /* index of last bit length code of non zero freq */ flag_buf[last_flags] = flags; /* Save the flags for the last 8 items */ /* Check if the file is ascii or binary */ if (*file_type == (ush)UNKNOWN) set_file_type(); /* Construct the literal and distance trees */ build_tree((tree_desc near *)(&l_desc)); Tracev((stderr, "\nlit data: dyn %ld, stat %ld", opt_len, static_len)); build_tree((tree_desc near *)(&d_desc)); Tracev((stderr, "\ndist data: dyn %ld, stat %ld", opt_len, static_len)); /* At this point, opt_len and static_len are the total bit lengths of * the compressed block data, excluding the tree representations. */ /* Build the bit length tree for the above two trees, and get the index * in bl_order of the last bit length code to send. */ max_blindex = build_bl_tree(); /* Determine the best encoding. Compute first the block length in bytes */ opt_lenb = (opt_len+3+7)>>3; static_lenb = (static_len+3+7)>>3; input_len += stored_len; /* for debugging only */ Trace((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u dist %u ", opt_lenb, opt_len, static_lenb, static_len, stored_len, last_lit, last_dist)); if (static_lenb <= opt_lenb) opt_lenb = static_lenb; /* If compression failed and this is the first and last block, * and if the zip file can be seeked (to rewrite the local header), * the whole file is transformed into a stored file: */ #ifdef FORCE_METHOD if (level == 1 && eof && compressed_len == 0L) { /* force stored file */ #else if (stored_len <= opt_lenb && eof && compressed_len == 0L && seekable()) { #endif /* Since LIT_BUFSIZE <= 2*WSIZE, the input data must be there: */ if (buf == (char*)0) error ("block vanished"); copy_block(buf, (unsigned)stored_len, 0); /* without header */ compressed_len = stored_len << 3; *file_method = STORED; #ifdef FORCE_METHOD } else if (level == 2 && buf != (char*)0) { /* force stored block */ #else } else if (stored_len+4 <= opt_lenb && buf != (char*)0) { /* 4: two words for the lengths */ #endif /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. * Otherwise we can't have processed more than WSIZE input bytes since * the last block flush, because compression would have been * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to * transform a block into a stored block. */ send_bits((STORED_BLOCK<<1)+eof, 3); /* send block type */ compressed_len = (compressed_len + 3 + 7) & ~7L; compressed_len += (stored_len + 4) << 3; copy_block(buf, (unsigned)stored_len, 1); /* with header */ #ifdef FORCE_METHOD } else if (level == 3) { /* force static trees */ #else } else if (static_lenb == opt_lenb) { #endif send_bits((STATIC_TREES<<1)+eof, 3); compress_block((ct_data near *)static_ltree, (ct_data near *)static_dtree); compressed_len += 3 + static_len; } else { send_bits((DYN_TREES<<1)+eof, 3); send_all_trees(l_desc.max_code+1, d_desc.max_code+1, max_blindex+1); compress_block((ct_data near *)dyn_ltree, (ct_data near *)dyn_dtree); compressed_len += 3 + opt_len; } Assert (compressed_len == bits_sent, "bad compressed size"); init_block(); if (eof) { Assert (input_len == isize, "bad input size"); bi_windup(); compressed_len += 7; /* align on byte boundary */ } Tracev((stderr,"\ncomprlen %lu(%lu) ", compressed_len>>3, compressed_len-7*eof)); return compressed_len >> 3; } /* =========================================================================== * Save the match info and tally the frequency counts. Return true if * the current block must be flushed. */ int ct_tally (dist, lc) int dist; /* distance of matched string */ int lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */ { l_buf[last_lit++] = (uch)lc; if (dist == 0) { /* lc is the unmatched char */ dyn_ltree[lc].Freq++; } else { /* Here, lc is the match length - MIN_MATCH */ dist--; /* dist = match distance - 1 */ Assert((ush)dist < (ush)MAX_DIST && (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && (ush)d_code(dist) < (ush)D_CODES, "ct_tally: bad match"); dyn_ltree[length_code[lc]+LITERALS+1].Freq++; dyn_dtree[d_code(dist)].Freq++; d_buf[last_dist++] = (ush)dist; flags |= flag_bit; } flag_bit <<= 1; /* Output the flags if they fill a byte: */ if ((last_lit & 7) == 0) { flag_buf[last_flags++] = flags; flags = 0, flag_bit = 1; } /* Try to guess if it is profitable to stop the current block here */ if (level > 2 && (last_lit & 0xfff) == 0) { /* Compute an upper bound for the compressed length */ ulg out_length = (ulg)last_lit*8L; ulg in_length = (ulg)strstart-block_start; int dcode; for (dcode = 0; dcode < D_CODES; dcode++) { out_length += (ulg)dyn_dtree[dcode].Freq*(5L+extra_dbits[dcode]); } out_length >>= 3; Trace((stderr,"\nlast_lit %u, last_dist %u, in %ld, out ~%ld(%ld%%) ", last_lit, last_dist, in_length, out_length, 100L - out_length*100L/in_length)); if (last_dist < last_lit/2 && out_length < in_length/2) return 1; } return (last_lit == LIT_BUFSIZE-1 || last_dist == DIST_BUFSIZE); /* We avoid equality with LIT_BUFSIZE because of wraparound at 64K * on 16 bit machines and because stored blocks are restricted to * 64K-1 bytes. */ } /* =========================================================================== * Send the block data compressed using the given Huffman trees */ local void compress_block(ltree, dtree) ct_data near *ltree; /* literal tree */ ct_data near *dtree; /* distance tree */ { unsigned dist; /* distance of matched string */ int lc; /* match length or unmatched char (if dist == 0) */ unsigned lx = 0; /* running index in l_buf */ unsigned dx = 0; /* running index in d_buf */ unsigned fx = 0; /* running index in flag_buf */ uch flag = 0; /* current flags */ unsigned code; /* the code to send */ int extra; /* number of extra bits to send */ if (last_lit != 0) do { if ((lx & 7) == 0) flag = flag_buf[fx++]; lc = l_buf[lx++]; if ((flag & 1) == 0) { send_code(lc, ltree); /* send a literal byte */ Tracecv(isgraph(lc), (stderr," '%c' ", lc)); } else { /* Here, lc is the match length - MIN_MATCH */ code = length_code[lc]; send_code(code+LITERALS+1, ltree); /* send the length code */ extra = extra_lbits[code]; if (extra != 0) { lc -= base_length[code]; send_bits(lc, extra); /* send the extra length bits */ } dist = d_buf[dx++]; /* Here, dist is the match distance - 1 */ code = d_code(dist); Assert (code < D_CODES, "bad d_code"); send_code(code, dtree); /* send the distance code */ extra = extra_dbits[code]; if (extra != 0) { dist -= base_dist[code]; send_bits(dist, extra); /* send the extra distance bits */ } } /* literal or match pair ? */ flag >>= 1; } while (lx < last_lit); send_code(END_BLOCK, ltree); } /* =========================================================================== * Set the file type to ASCII or BINARY, using a crude approximation: * binary if more than 20% of the bytes are <= 6 or >= 128, ascii otherwise. * IN assertion: the fields freq of dyn_ltree are set and the total of all * frequencies does not exceed 64K (to fit in an int on 16 bit machines). */ local void set_file_type() { int n = 0; unsigned ascii_freq = 0; unsigned bin_freq = 0; while (n < 7) bin_freq += dyn_ltree[n++].Freq; while (n < 128) ascii_freq += dyn_ltree[n++].Freq; while (n < LITERALS) bin_freq += dyn_ltree[n++].Freq; *file_type = bin_freq > (ascii_freq >> 2) ? BINARY : ASCII; if (*file_type == BINARY && translate_eol) { warn("-l used on binary file", ""); } }
/* unlzh.c -- decompress files in SCO compress -H (LZH) format. * The code in this file is directly derived from the public domain 'ar002' * written by Haruhiko Okumura. */ #ifdef RCSID static char rcsid[] = "$Id: unlzh.c,v 1.2 1993/06/24 10:59:01 jloup Exp $"; #endif #include <stdio.h> #include "tailor.h" #include "gzip.h" #include "lzw.h" /* just for consistency checking */ /* decode.c */ local unsigned decode OF((unsigned count, uch buffer[])); local void decode_start OF((void)); /* huf.c */ local void huf_decode_start OF((void)); local unsigned decode_c OF((void)); local unsigned decode_p OF((void)); local void read_pt_len OF((int nn, int nbit, int i_special)); local void read_c_len OF((void)); /* io.c */ local void fillbuf OF((int n)); local unsigned getbits OF((int n)); local void init_getbits OF((void)); /* maketbl.c */ local void make_table OF((int nchar, uch bitlen[], int tablebits, ush table[])); #define DICBIT 13 /* 12(-lh4-) or 13(-lh5-) */ #define DICSIZ ((unsigned) 1 << DICBIT) #ifndef CHAR_BIT # define CHAR_BIT 8 #endif #ifndef UCHAR_MAX # define UCHAR_MAX 255 #endif #define BITBUFSIZ (CHAR_BIT * 2 * sizeof(char)) /* Do not use CHAR_BIT * sizeof(bitbuf), does not work on machines * for which short is not on 16 bits (Cray). */ /* encode.c and decode.c */ #define MAXMATCH 256 /* formerly F (not more than UCHAR_MAX + 1) */ #define THRESHOLD 3 /* choose optimal value */ /* huf.c */ #define NC (UCHAR_MAX + MAXMATCH + 2 - THRESHOLD) /* alphabet = {0, 1, 2, ..., NC - 1} */ #define CBIT 9 /* $\lfloor \log_2 NC \rfloor + 1$ */ #define CODE_BIT 16 /* codeword length */ #define NP (DICBIT + 1) #define NT (CODE_BIT + 3) #define PBIT 4 /* smallest integer such that (1U << PBIT) > NP */ #define TBIT 5 /* smallest integer such that (1U << TBIT) > NT */ #if NT > NP # define NPT NT #else # define NPT NP #endif /* local ush left[2 * NC - 1]; */ /* local ush right[2 * NC - 1]; */ #define left prev #define right head #if NC > (1<<(BITS-2)) error cannot overlay left+right and prev #endif /* local uch c_len[NC]; */ #define c_len outbuf #if NC > OUTBUFSIZ error cannot overlay c_len and outbuf #endif local uch pt_len[NPT]; local unsigned blocksize; local ush pt_table[256]; /* local ush c_table[4096]; */ #define c_table d_buf #if (DIST_BUFSIZE-1) < 4095 error cannot overlay c_table and d_buf #endif /*********************************************************** io.c -- input/output ***********************************************************/ local ush bitbuf; local unsigned subbitbuf; local int bitcount; local void fillbuf(n) /* Shift bitbuf n bits left, read n bits */ int n; { bitbuf <<= n; while (n > bitcount) { bitbuf |= subbitbuf << (n -= bitcount); subbitbuf = (unsigned)try_byte(); if ((int)subbitbuf == EOF) subbitbuf = 0; bitcount = CHAR_BIT; } bitbuf |= subbitbuf >> (bitcount -= n); } local unsigned getbits(n) int n; { unsigned x; x = bitbuf >> (BITBUFSIZ - n); fillbuf(n); return x; } local void init_getbits() { bitbuf = 0; subbitbuf = 0; bitcount = 0; fillbuf(BITBUFSIZ); } /*********************************************************** maketbl.c -- make table for decoding ***********************************************************/ local void make_table(nchar, bitlen, tablebits, table) int nchar; uch bitlen[]; int tablebits; ush table[]; { ush count[17], weight[17], start[18], *p; unsigned i, k, len, ch, jutbits, avail, nextcode, mask; for (i = 1; i <= 16; i++) count[i] = 0; for (i = 0; i < (unsigned)nchar; i++) count[bitlen[i]]++; start[1] = 0; for (i = 1; i <= 16; i++) start[i + 1] = start[i] + (count[i] << (16 - i)); if ((start[17] & 0xffff) != 0) error("Bad table\n"); jutbits = 16 - tablebits; for (i = 1; i <= (unsigned)tablebits; i++) { start[i] >>= jutbits; weight[i] = (unsigned) 1 << (tablebits - i); } while (i <= 16) { weight[i] = (unsigned) 1 << (16 - i); i++; } i = start[tablebits + 1] >> jutbits; if (i != 0) { k = 1 << tablebits; while (i != k) table[i++] = 0; } avail = nchar; mask = (unsigned) 1 << (15 - tablebits); for (ch = 0; ch < (unsigned)nchar; ch++) { if ((len = bitlen[ch]) == 0) continue; nextcode = start[len] + weight[len]; if (len <= (unsigned)tablebits) { for (i = start[len]; i < nextcode; i++) table[i] = ch; } else { k = start[len]; p = &table[k >> jutbits]; i = len - tablebits; while (i != 0) { if (*p == 0) { right[avail] = left[avail] = 0; *p = avail++; } if (k & mask) p = &right[*p]; else p = &left[*p]; k <<= 1; i--; } *p = ch; } start[len] = nextcode; } } /*********************************************************** huf.c -- static Huffman ***********************************************************/ local void read_pt_len(nn, nbit, i_special) int nn; int nbit; int i_special; { int i, c, n; unsigned mask; n = getbits(nbit); if (n == 0) { c = getbits(nbit); for (i = 0; i < nn; i++) pt_len[i] = 0; for (i = 0; i < 256; i++) pt_table[i] = c; } else { i = 0; while (i < n) { c = bitbuf >> (BITBUFSIZ - 3); if (c == 7) { mask = (unsigned) 1 << (BITBUFSIZ - 1 - 3); while (mask & bitbuf) { mask >>= 1; c++; } } fillbuf((c < 7) ? 3 : c - 3); pt_len[i++] = c; if (i == i_special) { c = getbits(2); while (--c >= 0) pt_len[i++] = 0; } } while (i < nn) pt_len[i++] = 0; make_table(nn, pt_len, 8, pt_table); } } local void read_c_len() { int i, c, n; unsigned mask; n = getbits(CBIT); if (n == 0) { c = getbits(CBIT); for (i = 0; i < NC; i++) c_len[i] = 0; for (i = 0; i < 4096; i++) c_table[i] = c; } else { i = 0; while (i < n) { c = pt_table[bitbuf >> (BITBUFSIZ - 8)]; if (c >= NT) { mask = (unsigned) 1 << (BITBUFSIZ - 1 - 8); do { if (bitbuf & mask) c = right[c]; else c = left [c]; mask >>= 1; } while (c >= NT); } fillbuf((int) pt_len[c]); if (c <= 2) { if (c == 0) c = 1; else if (c == 1) c = getbits(4) + 3; else c = getbits(CBIT) + 20; while (--c >= 0) c_len[i++] = 0; } else c_len[i++] = c - 2; } while (i < NC) c_len[i++] = 0; make_table(NC, c_len, 12, c_table); } } local unsigned decode_c() { unsigned j, mask; if (blocksize == 0) { blocksize = getbits(16); if (blocksize == 0) { return NC; /* end of file */ } read_pt_len(NT, TBIT, 3); read_c_len(); read_pt_len(NP, PBIT, -1); } blocksize--; j = c_table[bitbuf >> (BITBUFSIZ - 12)]; if (j >= NC) { mask = (unsigned) 1 << (BITBUFSIZ - 1 - 12); do { if (bitbuf & mask) j = right[j]; else j = left [j]; mask >>= 1; } while (j >= NC); } fillbuf((int) c_len[j]); return j; } local unsigned decode_p() { unsigned j, mask; j = pt_table[bitbuf >> (BITBUFSIZ - 8)]; if (j >= NP) { mask = (unsigned) 1 << (BITBUFSIZ - 1 - 8); do { if (bitbuf & mask) j = right[j]; else j = left [j]; mask >>= 1; } while (j >= NP); } fillbuf((int) pt_len[j]); if (j != 0) j = ((unsigned) 1 << (j - 1)) + getbits((int) (j - 1)); return j; } local void huf_decode_start() { init_getbits(); blocksize = 0; } /*********************************************************** decode.c ***********************************************************/ local int j; /* remaining bytes to copy */ local int done; /* set at end of input */ local void decode_start() { huf_decode_start(); j = 0; done = 0; } /* Decode the input and return the number of decoded bytes put in buffer */ local unsigned decode(count, buffer) unsigned count; uch buffer[]; /* The calling function must keep the number of bytes to be processed. This function decodes either 'count' bytes or 'DICSIZ' bytes, whichever is smaller, into the array 'buffer[]' of size 'DICSIZ' or more. Call decode_start() once for each new file before calling this function. */ { local unsigned i; unsigned r, c; r = 0; while (--j >= 0) { buffer[r] = buffer[i]; i = (i + 1) & (DICSIZ - 1); if (++r == count) return r; } for ( ; ; ) { c = decode_c(); if (c == NC) { done = 1; return r; } if (c <= UCHAR_MAX) { buffer[r] = c; if (++r == count) return r; } else { j = c - (UCHAR_MAX + 1 - THRESHOLD); i = (r - decode_p() - 1) & (DICSIZ - 1); while (--j >= 0) { buffer[r] = buffer[i]; i = (i + 1) & (DICSIZ - 1); if (++r == count) return r; } } } } /* =========================================================================== * Unlzh in to out. Return OK or ERROR. */ int unlzh(in, out) int in; int out; { unsigned n; ifd = in; ofd = out; decode_start(); while (!done) { n = decode((unsigned) DICSIZ, window); if (!test && n > 0) { write_buf(out, (char*)window, n); } } return OK; }
/* unlzw.c -- decompress files in LZW format. * The code in this file is directly derived from the public domain 'compress' * written by Spencer Thomas, Joe Orost, James Woods, Jim McKie, Steve Davies, * Ken Turkowski, Dave Mack and Peter Jannesen. * * This is a temporary version which will be rewritten in some future version * to accommodate in-memory decompression. */ #ifdef RCSID static char rcsid[] = "$Id: unlzw.c,v 0.15 1993/06/10 13:28:35 jloup Exp $"; #endif #include <sys/types.h> #include "tailor.h" #ifdef HAVE_UNISTD_H # include <unistd.h> #endif #ifndef NO_FCNTL_H # include <fcntl.h> #endif #include "gzip.h" #include "lzw.h" typedef unsigned char char_type; typedef long code_int; typedef unsigned long count_int; typedef unsigned short count_short; typedef unsigned long cmp_code_int; #define MAXCODE(n) (1L << (n)) #ifndef REGISTERS # define REGISTERS 2 #endif #define REG1 #define REG2 #define REG3 #define REG4 #define REG5 #define REG6 #define REG7 #define REG8 #define REG9 #define REG10 #define REG11 #define REG12 #define REG13 #define REG14 #define REG15 #define REG16 #if REGISTERS >= 1 # undef REG1 # define REG1 register #endif #if REGISTERS >= 2 # undef REG2 # define REG2 register #endif #if REGISTERS >= 3 # undef REG3 # define REG3 register #endif #if REGISTERS >= 4 # undef REG4 # define REG4 register #endif #if REGISTERS >= 5 # undef REG5 # define REG5 register #endif #if REGISTERS >= 6 # undef REG6 # define REG6 register #endif #if REGISTERS >= 7 # undef REG7 # define REG7 register #endif #if REGISTERS >= 8 # undef REG8 # define REG8 register #endif #if REGISTERS >= 9 # undef REG9 # define REG9 register #endif #if REGISTERS >= 10 # undef REG10 # define REG10 register #endif #if REGISTERS >= 11 # undef REG11 # define REG11 register #endif #if REGISTERS >= 12 # undef REG12 # define REG12 register #endif #if REGISTERS >= 13 # undef REG13 # define REG13 register #endif #if REGISTERS >= 14 # undef REG14 # define REG14 register #endif #if REGISTERS >= 15 # undef REG15 # define REG15 register #endif #if REGISTERS >= 16 # undef REG16 # define REG16 register #endif #ifndef BYTEORDER # define BYTEORDER 0000 #endif #ifndef NOALLIGN # define NOALLIGN 0 #endif union bytes { long word; struct { #if BYTEORDER == 4321 char_type b1; char_type b2; char_type b3; char_type b4; #else #if BYTEORDER == 1234 char_type b4; char_type b3; char_type b2; char_type b1; #else # undef BYTEORDER int dummy; #endif #endif } bytes; }; #if BYTEORDER == 4321 && NOALLIGN == 1 # define input(b,o,c,n,m){ \ (c) = (*(long *)(&(b)[(o)>>3])>>((o)&0x7))&(m); \ (o) += (n); \ } #else # define input(b,o,c,n,m){ \ REG1 char_type *p = &(b)[(o)>>3]; \ (c) = ((((long)(p[0]))|((long)(p[1])<<8)| \ ((long)(p[2])<<16))>>((o)&0x7))&(m); \ (o) += (n); \ } #endif #ifndef MAXSEG_64K /* DECLARE(ush, tab_prefix, (1<<BITS)); -- prefix code */ # define tab_prefixof(i) tab_prefix[i] # define clear_tab_prefixof() memzero(tab_prefix, 256); #else /* DECLARE(ush, tab_prefix0, (1<<(BITS-1)); -- prefix for even codes */ /* DECLARE(ush, tab_prefix1, (1<<(BITS-1)); -- prefix for odd codes */ ush *tab_prefix[2]; # define tab_prefixof(i) tab_prefix[(i)&1][(i)>>1] # define clear_tab_prefixof() \ memzero(tab_prefix0, 128), \ memzero(tab_prefix1, 128); #endif #define de_stack ((char_type *)(&d_buf[DIST_BUFSIZE-1])) #define tab_suffixof(i) tab_suffix[i] int block_mode = BLOCK_MODE; /* block compress mode -C compatible with 2.0 */ /* ============================================================================ * Decompress in to out. This routine adapts to the codes in the * file building the "string" table on-the-fly; requiring no table to * be stored in the compressed file. * IN assertions: the buffer inbuf contains already the beginning of * the compressed data, from offsets iptr to insize-1 included. * The magic header has already been checked and skipped. * bytes_in and bytes_out have been initialized. */ int unlzw(in, out) int in, out; /* input and output file descriptors */ { REG2 char_type *stackp; REG3 code_int code; REG4 int finchar; REG5 code_int oldcode; REG6 code_int incode; REG7 long inbits; REG8 long posbits; REG9 int outpos; /* REG10 int insize; (global) */ REG11 unsigned bitmask; REG12 code_int free_ent; REG13 code_int maxcode; REG14 code_int maxmaxcode; REG15 int n_bits; REG16 int rsize; #ifdef MAXSEG_64K tab_prefix[0] = tab_prefix0; tab_prefix[1] = tab_prefix1; #endif maxbits = get_byte(); block_mode = maxbits & BLOCK_MODE; if ((maxbits & LZW_RESERVED) != 0) { WARN((stderr, "\n%s: %s: warning, unknown flags 0x%x\n", progname, ifname, maxbits & LZW_RESERVED)); } maxbits &= BIT_MASK; maxmaxcode = MAXCODE(maxbits); if (maxbits > BITS) { fprintf(stderr, "\n%s: %s: compressed with %d bits, can only handle %d bits\n", progname, ifname, maxbits, BITS); exit_code = ERROR; return ERROR; } rsize = insize; maxcode = MAXCODE(n_bits = INIT_BITS)-1; bitmask = (1<<n_bits)-1; oldcode = -1; finchar = 0; outpos = 0; posbits = inptr<<3; free_ent = ((block_mode) ? FIRST : 256); clear_tab_prefixof(); /* Initialize the first 256 entries in the table. */ for (code = 255 ; code >= 0 ; --code) { tab_suffixof(code) = (char_type)code; } do { REG1 int i; int e; int o; resetbuf: e = insize-(o = (posbits>>3)); for (i = 0 ; i < e ; ++i) { inbuf[i] = inbuf[i+o]; } insize = e; posbits = 0; if (insize < INBUF_EXTRA) { if ((rsize = read(in, (char*)inbuf+insize, INBUFSIZ)) == EOF) { read_error(); } insize += rsize; bytes_in += (ulg)rsize; } inbits = ((rsize != 0) ? ((long)insize - insize%n_bits)<<3 : ((long)insize<<3)-(n_bits-1)); while (inbits > posbits) { if (free_ent > maxcode) { posbits = ((posbits-1) + ((n_bits<<3)-(posbits-1+(n_bits<<3))%(n_bits<<3))); ++n_bits; if (n_bits == maxbits) { maxcode = maxmaxcode; } else { maxcode = MAXCODE(n_bits)-1; } bitmask = (1<<n_bits)-1; goto resetbuf; } input(inbuf,posbits,code,n_bits,bitmask); Tracev((stderr, "%d ", code)); if (oldcode == -1) { if (code >= 256) error("corrupt input."); outbuf[outpos++] = (char_type)(finchar = (int)(oldcode=code)); continue; } if (code == CLEAR && block_mode) { clear_tab_prefixof(); free_ent = FIRST - 1; posbits = ((posbits-1) + ((n_bits<<3)-(posbits-1+(n_bits<<3))%(n_bits<<3))); maxcode = MAXCODE(n_bits = INIT_BITS)-1; bitmask = (1<<n_bits)-1; goto resetbuf; } incode = code; stackp = de_stack; if (code >= free_ent) { /* Special case for KwKwK string. */ if (code > free_ent) { #ifdef DEBUG char_type *p; posbits -= n_bits; p = &inbuf[posbits>>3]; fprintf(stderr, "code:%ld free_ent:%ld n_bits:%d insize:%u\n", code, free_ent, n_bits, insize); fprintf(stderr, "posbits:%ld inbuf:%02X %02X %02X %02X %02X\n", posbits, p[-1],p[0],p[1],p[2],p[3]); #endif if (!test && outpos > 0) { write_buf(out, (char*)outbuf, outpos); bytes_out += (ulg)outpos; } error(to_stdout ? "corrupt input." : "corrupt input. Use zcat to recover some data."); } *--stackp = (char_type)finchar; code = oldcode; } while ((cmp_code_int)code >= (cmp_code_int)256) { /* Generate output characters in reverse order */ *--stackp = tab_suffixof(code); code = tab_prefixof(code); } *--stackp = (char_type)(finchar = tab_suffixof(code)); /* And put them out in forward order */ { REG1 int i; if (outpos+(i = (de_stack-stackp)) >= OUTBUFSIZ) { do { if (i > OUTBUFSIZ-outpos) i = OUTBUFSIZ-outpos; if (i > 0) { memcpy(outbuf+outpos, stackp, i); outpos += i; } if (outpos >= OUTBUFSIZ) { if (!test) { write_buf(out, (char*)outbuf, outpos); bytes_out += (ulg)outpos; } outpos = 0; } stackp+= i; } while ((i = (de_stack-stackp)) > 0); } else { memcpy(outbuf+outpos, stackp, i); outpos += i; } } if ((code = free_ent) < maxmaxcode) { /* Generate the new entry. */ tab_prefixof(code) = (unsigned short)oldcode; tab_suffixof(code) = (char_type)finchar; free_ent = code+1; } oldcode = incode; /* Remember previous code. */ } } while (rsize != 0); if (!test && outpos > 0) { write_buf(out, (char*)outbuf, outpos); bytes_out += (ulg)outpos; } return OK; }
/* unpack.c -- decompress files in pack format. * Copyright (C) 1992-1993 Jean-loup Gailly * This is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License, see the file COPYING. */ #ifdef RCSID static char rcsid[] = "$Id: unpack.c,v 1.4 1993/06/11 19:25:36 jloup Exp $"; #endif #include "tailor.h" #include "gzip.h" #include "crypt.h" #define MIN(a,b) ((a) <= (b) ? (a) : (b)) /* The arguments must not have side effects. */ #define MAX_BITLEN 25 /* Maximum length of Huffman codes. (Minor modifications to the code * would be needed to support 32 bits codes, but pack never generates * more than 24 bits anyway.) */ #define LITERALS 256 /* Number of literals, excluding the End of Block (EOB) code */ #define MAX_PEEK 12 /* Maximum number of 'peek' bits used to optimize traversal of the * Huffman tree. */ local ulg orig_len; /* original uncompressed length */ local int max_len; /* maximum bit length of Huffman codes */ local uch literal[LITERALS]; /* The literal bytes present in the Huffman tree. The EOB code is not * represented. */ local int lit_base[MAX_BITLEN+1]; /* All literals of a given bit length are contiguous in literal[] and * have contiguous codes. literal[code+lit_base[len]] is the literal * for a code of len bits. */ local int leaves [MAX_BITLEN+1]; /* Number of leaves for each bit length */ local int parents[MAX_BITLEN+1]; /* Number of parents for each bit length */ local int peek_bits; /* Number of peek bits currently used */ /* local uch prefix_len[1 << MAX_PEEK]; */ #define prefix_len outbuf /* For each bit pattern b of peek_bits bits, prefix_len[b] is the length * of the Huffman code starting with a prefix of b (upper bits), or 0 * if all codes of prefix b have more than peek_bits bits. It is not * necessary to have a huge table (large MAX_PEEK) because most of the * codes encountered in the input stream are short codes (by construction). * So for most codes a single lookup will be necessary. */ #if (1<<MAX_PEEK) > OUTBUFSIZ error cannot overlay prefix_len and outbuf #endif local ulg bitbuf; /* Bits are added on the low part of bitbuf and read from the high part. */ local int valid; /* number of valid bits in bitbuf */ /* all bits above the last valid bit are always zero */ /* Set code to the next 'bits' input bits without skipping them. code * must be the name of a simple variable and bits must not have side effects. * IN assertions: bits <= 25 (so that we still have room for an extra byte * when valid is only 24), and mask = (1<<bits)-1. */ #define look_bits(code,bits,mask) \ { \ while (valid < (bits)) bitbuf = (bitbuf<<8) | (ulg)get_byte(), valid += 8; \ code = (bitbuf >> (valid-(bits))) & (mask); \ } /* Skip the given number of bits (after having peeked at them): */ #define skip_bits(bits) (valid -= (bits)) #define clear_bitbuf() (valid = 0, bitbuf = 0) /* Local functions */ local void read_tree OF((void)); local void build_tree OF((void)); /* =========================================================================== * Read the Huffman tree. */ local void read_tree() { int len; /* bit length */ int base; /* base offset for a sequence of leaves */ int n; /* Read the original input size, MSB first */ orig_len = 0; for (n = 1; n <= 4; n++) orig_len = (orig_len << 8) | (ulg)get_byte(); max_len = (int)get_byte(); /* maximum bit length of Huffman codes */ if (max_len > MAX_BITLEN) { error("invalid compressed data -- Huffman code > 32 bits"); } /* Get the number of leaves at each bit length */ n = 0; for (len = 1; len <= max_len; len++) { leaves[len] = (int)get_byte(); n += leaves[len]; } if (n > LITERALS) { error("too many leaves in Huffman tree"); } Trace((stderr, "orig_len %ld, max_len %d, leaves %d\n", orig_len, max_len, n)); /* There are at least 2 and at most 256 leaves of length max_len. * (Pack arbitrarily rejects empty files and files consisting of * a single byte even repeated.) To fit the last leaf count in a * byte, it is offset by 2. However, the last literal is the EOB * code, and is not transmitted explicitly in the tree, so we must * adjust here by one only. */ leaves[max_len]++; /* Now read the leaves themselves */ base = 0; for (len = 1; len <= max_len; len++) { /* Remember where the literals of this length start in literal[] : */ lit_base[len] = base; /* And read the literals: */ for (n = leaves[len]; n > 0; n--) { literal[base++] = (uch)get_byte(); } } leaves[max_len]++; /* Now include the EOB code in the Huffman tree */ } /* =========================================================================== * Build the Huffman tree and the prefix table. */ local void build_tree() { int nodes = 0; /* number of nodes (parents+leaves) at current bit length */ int len; /* current bit length */ uch *prefixp; /* pointer in prefix_len */ for (len = max_len; len >= 1; len--) { /* The number of parent nodes at this level is half the total * number of nodes at parent level: */ nodes >>= 1; parents[len] = nodes; /* Update lit_base by the appropriate bias to skip the parent nodes * (which are not represented in the literal array): */ lit_base[len] -= nodes; /* Restore nodes to be parents+leaves: */ nodes += leaves[len]; } /* Construct the prefix table, from shortest leaves to longest ones. * The shortest code is all ones, so we start at the end of the table. */ peek_bits = MIN(max_len, MAX_PEEK); prefixp = &prefix_len[1<<peek_bits]; for (len = 1; len <= peek_bits; len++) { int prefixes = leaves[len] << (peek_bits-len); /* may be 0 */ while (prefixes--) *--prefixp = (uch)len; } /* The length of all other codes is unknown: */ while (prefixp > prefix_len) *--prefixp = 0; } /* =========================================================================== * Unpack in to out. This routine does not support the old pack format * with magic header \037\037. * * IN assertions: the buffer inbuf contains already the beginning of * the compressed data, from offsets inptr to insize-1 included. * The magic header has already been checked. The output buffer is cleared. */ int unpack(in, out) int in, out; /* input and output file descriptors */ { int len; /* Bit length of current code */ unsigned eob; /* End Of Block code */ register unsigned peek; /* lookahead bits */ unsigned peek_mask; /* Mask for peek_bits bits */ ifd = in; ofd = out; read_tree(); /* Read the Huffman tree */ build_tree(); /* Build the prefix table */ clear_bitbuf(); /* Initialize bit input */ peek_mask = (1<<peek_bits)-1; /* The eob code is the largest code among all leaves of maximal length: */ eob = leaves[max_len]-1; Trace((stderr, "eob %d %x\n", max_len, eob)); /* Decode the input data: */ for (;;) { /* Since eob is the longest code and not shorter than max_len, * we can peek at max_len bits without having the risk of reading * beyond the end of file. */ look_bits(peek, peek_bits, peek_mask); len = prefix_len[peek]; if (len > 0) { peek >>= peek_bits - len; /* discard the extra bits */ } else { /* Code of more than peek_bits bits, we must traverse the tree */ ulg mask = peek_mask; len = peek_bits; do { len++, mask = (mask<<1)+1; look_bits(peek, len, mask); } while (peek < (unsigned)parents[len]); /* loop as long as peek is a parent node */ } /* At this point, peek is the next complete code, of len bits */ if (peek == eob && len == max_len) break; /* end of file? */ put_ubyte(literal[peek+lit_base[len]]); Tracev((stderr,"%02d %04x %c\n", len, peek, literal[peek+lit_base[len]])); skip_bits(len); } /* for (;;) */ flush_window(); Trace((stderr, "bytes_out %ld\n", bytes_out)); if (orig_len != (ulg)bytes_out) { error("invalid compressed data--length error"); } return OK; }
/* unzip.c -- decompress files in gzip or pkzip format. * Copyright (C) 1992-1993 Jean-loup Gailly * This is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License, see the file COPYING. * * The code in this file is derived from the file funzip.c written * and put in the public domain by Mark Adler. */ /* This version can extract files in gzip or pkzip format. For the latter, only the first entry is extracted, and it has to be either deflated or stored. */ #ifdef RCSID static char rcsid[] = "$Id: unzip.c,v 0.13 1993/06/10 13:29:00 jloup Exp $"; #endif #include "tailor.h" #include "gzip.h" #include "crypt.h" /* PKZIP header definitions */ #define LOCSIG 0x04034b50L /* four-byte lead-in (lsb first) */ #define LOCFLG 6 /* offset of bit flag */ #define CRPFLG 1 /* bit for encrypted entry */ #define EXTFLG 8 /* bit for extended local header */ #define LOCHOW 8 /* offset of compression method */ #define LOCTIM 10 /* file mod time (for decryption) */ #define LOCCRC 14 /* offset of crc */ #define LOCSIZ 18 /* offset of compressed size */ #define LOCLEN 22 /* offset of uncompressed length */ #define LOCFIL 26 /* offset of file name field length */ #define LOCEXT 28 /* offset of extra field length */ #define LOCHDR 30 /* size of local header, including sig */ #define EXTHDR 16 /* size of extended local header, inc sig */ /* Globals */ int decrypt; /* flag to turn on decryption */ char *key; /* not used--needed to link crypt.c */ int pkzip = 0; /* set for a pkzip file */ int ext_header = 0; /* set if extended local header */ /* =========================================================================== * Check zip file and advance inptr to the start of the compressed data. * Get ofname from the local header if necessary. */ int check_zipfile(in) int in; /* input file descriptors */ { uch *h = inbuf + inptr; /* first local header */ ifd = in; /* Check validity of local header, and skip name and extra fields */ inptr += LOCHDR + SH(h + LOCFIL) + SH(h + LOCEXT); if (inptr > insize || LG(h) != LOCSIG) { fprintf(stderr, "\n%s: %s: not a valid zip file\n", progname, ifname); exit_code = ERROR; return ERROR; } method = h[LOCHOW]; if (method != STORED && method != DEFLATED) { fprintf(stderr, "\n%s: %s: first entry not deflated or stored -- use unzip\n", progname, ifname); exit_code = ERROR; return ERROR; } /* If entry encrypted, decrypt and validate encryption header */ if ((decrypt = h[LOCFLG] & CRPFLG) != 0) { fprintf(stderr, "\n%s: %s: encrypted file -- use unzip\n", progname, ifname); exit_code = ERROR; return ERROR; } /* Save flags for unzip() */ ext_header = (h[LOCFLG] & EXTFLG) != 0; pkzip = 1; /* Get ofname and time stamp from local header (to be done) */ return OK; } /* =========================================================================== * Unzip in to out. This routine works on both gzip and pkzip files. * * IN assertions: the buffer inbuf contains already the beginning of * the compressed data, from offsets inptr to insize-1 included. * The magic header has already been checked. The output buffer is cleared. */ int unzip(in, out) int in, out; /* input and output file descriptors */ { ulg orig_crc = 0; /* original crc */ ulg orig_len = 0; /* original uncompressed length */ int n; uch buf[EXTHDR]; /* extended local header */ ifd = in; ofd = out; updcrc(NULL, 0); /* initialize crc */ if (pkzip && !ext_header) { /* crc and length at the end otherwise */ orig_crc = LG(inbuf + LOCCRC); orig_len = LG(inbuf + LOCLEN); } /* Decompress */ if (method == DEFLATED) { int res = inflate(); if (res == 3) { error("out of memory"); } else if (res != 0) { error("invalid compressed data--format violated"); } } else if (pkzip && method == STORED) { register ulg n = LG(inbuf + LOCLEN); if (n != LG(inbuf + LOCSIZ) - (decrypt ? RAND_HEAD_LEN : 0)) { fprintf(stderr, "len %ld, siz %ld\n", n, LG(inbuf + LOCSIZ)); error("invalid compressed data--length mismatch"); } while (n--) { uch c = (uch)get_byte(); #ifdef CRYPT if (decrypt) zdecode(c); #endif put_ubyte(c); } flush_window(); } else { error("internal error, invalid method"); } /* Get the crc and original length */ if (!pkzip) { /* crc32 (see algorithm.doc) * uncompressed input size modulo 2^32 */ for (n = 0; n < 8; n++) { buf[n] = (uch)get_byte(); /* may cause an error if EOF */ } orig_crc = LG(buf); orig_len = LG(buf+4); } else if (ext_header) { /* If extended header, check it */ /* signature - 4bytes: 0x50 0x4b 0x07 0x08 * CRC-32 value * compressed size 4-bytes * uncompressed size 4-bytes */ for (n = 0; n < EXTHDR; n++) { buf[n] = (uch)get_byte(); /* may cause an error if EOF */ } orig_crc = LG(buf+4); orig_len = LG(buf+12); } /* Validate decompression */ if (orig_crc != updcrc(outbuf, 0)) { error("invalid compressed data--crc error"); } if (orig_len != (ulg)bytes_out) { error("invalid compressed data--length error"); } /* Check if there are more entries in a pkzip file */ if (pkzip && inptr + 4 < insize && LG(inbuf+inptr) == LOCSIG) { if (to_stdout) { WARN((stderr, "%s: %s has more than one entry--rest ignored\n", progname, ifname)); } else { /* Don't destroy the input zip file */ fprintf(stderr, "%s: %s has more than one entry -- unchanged\n", progname, ifname); exit_code = ERROR; ext_header = pkzip = 0; return ERROR; } } ext_header = pkzip = 0; /* for next file */ return OK; }
/* zip.c -- compress files to the gzip or pkzip format * Copyright (C) 1992-1993 Jean-loup Gailly * This is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License, see the file COPYING. */ #ifdef RCSID static char rcsid[] = "$Id: zip.c,v 0.17 1993/06/10 13:29:25 jloup Exp $"; #endif #include <ctype.h> #include <sys/types.h> #include "tailor.h" #include "gzip.h" #include "crypt.h" #ifdef HAVE_UNISTD_H # include <unistd.h> #endif #ifndef NO_FCNTL_H # include <fcntl.h> #endif local ulg crc; /* crc on uncompressed file data */ long header_bytes; /* number of bytes in gzip header */ /* =========================================================================== * Deflate in to out. * IN assertions: the input and output buffers are cleared. * The variables time_stamp and save_orig_name are initialized. */ int zip(in, out) int in, out; /* input and output file descriptors */ { uch flags = 0; /* general purpose bit flags */ ush attr = 0; /* ascii/binary flag */ ush deflate_flags = 0; /* pkzip -es, -en or -ex equivalent */ ifd = in; ofd = out; outcnt = 0; /* Write the header to the gzip file. See algorithm.doc for the format */ method = DEFLATED; put_byte(GZIP_MAGIC[0]); /* magic header */ put_byte(GZIP_MAGIC[1]); put_byte(DEFLATED); /* compression method */ if (save_orig_name) { flags |= ORIG_NAME; } put_byte(flags); /* general flags */ put_long(time_stamp); /* Write deflated file to zip file */ crc = updcrc(0, 0); bi_init(out); ct_init(&attr, &method); lm_init(level, &deflate_flags); put_byte((uch)deflate_flags); /* extra flags */ put_byte(OS_CODE); /* OS identifier */ if (save_orig_name) { char *p = basename(ifname); /* Don't save the directory part. */ do { put_char(*p); } while (*p++); } header_bytes = (long)outcnt; (void)deflate(); #if !defined(NO_SIZE_CHECK) && !defined(RECORD_IO) /* Check input size (but not in VMS -- variable record lengths mess it up) * and not on MSDOS -- diet in TSR mode reports an incorrect file size) */ if (ifile_size != -1L && isize != (ulg)ifile_size) { Trace((stderr, " actual=%ld, read=%ld ", ifile_size, isize)); fprintf(stderr, "%s: %s: file size changed while zipping\n", progname, ifname); } #endif /* Write the crc and uncompressed size */ put_long(crc); put_long(isize); header_bytes += 2*sizeof(long); flush_outbuf(); return OK; } /* =========================================================================== * Read a new buffer from the current input file, perform end-of-line * translation, and update the crc and input file size. * IN assertion: size >= 2 (for end-of-line translation) */ int file_read(buf, size) char *buf; unsigned size; { unsigned len; Assert(insize == 0, "inbuf not empty"); len = read(ifd, buf, size); if (len == (unsigned)(-1) || len == 0) return (int)len; crc = updcrc((uch*)buf, len); isize += (ulg)len; return (int)len; }
6608aee8c4a8fdb0945e8b7b0a6f9fe3ca5336394bbcbc564879971e0129a6f1 /usr/bin/gzip
https://mirrors.kernel.org/gnu/tar/tar-1.12.tar.gz c6c37e888b136ccefab903c51149f4b7bd659d69d4aea21245f61053a57aa60a
# SPDX-FileCopyrightText: 2022 Andrius Å tikonas <andrius@stikonas.eu> # # SPDX-License-Identifier: GPL-3.0-or-later CC = tcc AR = tcc -ar # -DSIZEOF_UNSIGNED_LONG=4 forces use of simulated arithmetic # This is to avoid running configure test to determine sizeof(long long) CPPFLAGS = -DHAVE_FCNTL_H \ -DHAVE_DIRENT_H \ -DHAVE_GETCWD_H \ -DHAVE_GETCWD \ -DSIZEOF_UNSIGNED_LONG=4 \ -DVERSION=\"1.12\" \ -DPACKAGE=\"tar\" CFLAGS = -I . -I lib LDFLAGS = -L . -ltar -static .PHONY: all LIB_SRC = argmatch backupfile error fnmatch ftruncate getdate_stub getopt getopt1 getversion modechange msleep xgetcwd xmalloc xstrdup LIB_OBJ = $(addprefix lib/, $(addsuffix .o, $(LIB_SRC))) TAR_SRC = arith buffer compare create delete extract incremen list mangle misc names open3 rtapelib tar update TAR_OBJ = $(addprefix src/, $(addsuffix .o, $(TAR_SRC))) all: tar libtar.a: $(LIB_OBJ) $(AR) cr $@ $^ tar: libtar.a $(TAR_OBJ) $(CC) $^ $(LDFLAGS) -o $@
/* * SPDX-FileCopyrightText: 2021 Paul Dersey <pdersey@gmail.com> * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "getdate.h" time_t get_date (const char *p, const time_t *now) { return 0; }
/* * SPDX-FileCopyrightText: 2022 fosslinux <fosslinux@aussies.space> * SPDX-FileCopyrightText: 2022 Andrius Å tikonas <andrius@stikonas.eu> * * SPDX-License-Identifier: GPL-2.0-or-later */ #include <sys/stat.h> #include <linux/syscall.h> #include <linux/x86/syscall.h> int _lstat(const char *path, struct stat *buf) { int rc = lstat(path, buf); if (rc == 0) { buf->st_atime = 0; buf->st_mtime = 0; } return rc; } /* stat is deliberately hacked to be lstat. In src/system.h tar already defines lstat to be stat since S_ISLNK is not defined in mes C library Hence, we can't use something like #define lstat(a,b) _lstat(a,b) to have separate stat and lstat functions. Thus here we break tar with --dereference option but we don't use this option in live-bootstrap. */ #define stat(a,b) _lstat(a,b)
/* argmatch.c -- find a match for a string in an array Copyright (C) 1990 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@ai.mit.edu> */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <sys/types.h> #include <stdio.h> #ifdef STDC_HEADERS # include <string.h> #endif extern char *program_name; /* If ARG is an unambiguous match for an element of the null-terminated array OPTLIST, return the index in OPTLIST of the matched element, else -1 if it does not match any element or -2 if it is ambiguous (is a prefix of more than one element). */ int argmatch (arg, optlist) const char *arg; const char *const *optlist; { int i; /* Temporary index in OPTLIST. */ size_t arglen; /* Length of ARG. */ int matchind = -1; /* Index of first nonexact match. */ int ambiguous = 0; /* If nonzero, multiple nonexact match(es). */ arglen = strlen (arg); /* Test all elements for either exact match or abbreviated matches. */ for (i = 0; optlist[i]; i++) { if (!strncmp (optlist[i], arg, arglen)) { if (strlen (optlist[i]) == arglen) /* Exact match found. */ return i; else if (matchind == -1) /* First nonexact match found. */ matchind = i; else /* Second nonexact match found. */ ambiguous = 1; } } if (ambiguous) return -2; else return matchind; } /* Error reporting for argmatch. KIND is a description of the type of entity that was being matched. VALUE is the invalid value that was given. PROBLEM is the return value from argmatch. */ void invalid_arg (kind, value, problem) const char *kind; const char *value; int problem; { fprintf (stderr, "%s: ", program_name); if (problem == -1) fprintf (stderr, "invalid"); else /* Assume -2. */ fprintf (stderr, "ambiguous"); fprintf (stderr, " %s `%s'\n", kind, value); }
/* backupfile.c -- make Emacs style backup file names Copyright (C) 1990, 1997 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* David MacKenzie <djm@gnu.ai.mit.edu>. Some algorithms adapted from GNU Emacs. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #include <ctype.h> #include <sys/types.h> #include "backupfile.h" #if HAVE_STRING_H # include <string.h> #else # include <strings.h> # ifndef strrchr # define strrchr rindex # endif #endif #if HAVE_DIRENT_H # include <dirent.h> # define NLENGTH(Direct) (strlen((Direct)->d_name)) #else # define dirent direct # define NLENGTH(Direct) ((Direct)->d_namlen) # if HAVE_SYS_NDIR_H # include <sys/ndir.h> # endif # if HAVE_SYS_DIR_H # include <sys/dir.h> # endif # if HAVE_NDIR_H # include <ndir.h> # endif #endif #if CLOSEDIR_VOID /* Fake a return value. */ # define CLOSEDIR(d) (closedir (d), 0) #else # define CLOSEDIR(d) closedir (d) #endif #if STDC_HEADERS # include <stdlib.h> #else char *malloc (); #endif #if defined (STDC_HEADERS) || (!defined (isascii) && !defined (HAVE_ISASCII)) # define IN_CTYPE_DOMAIN(Char) 1 #else # define IN_CTYPE_DOMAIN(Char) isascii(Char) #endif #define ISDIGIT(Char) (IN_CTYPE_DOMAIN ((unsigned char) (Char)) \ && isdigit ((unsigned char) (Char))) #if HAVE_UNISTD_H # include <unistd.h> #endif #ifdef _POSIX_VERSION /* POSIX does not require that the d_ino field be present, and some systems do not provide it. */ # define REAL_DIR_ENTRY(Dp) 1 #else # define REAL_DIR_ENTRY(Dp) ((Dp)->d_ino != 0) #endif /* Which type of backup file names are generated. */ enum backup_type backup_type = none; /* The extension added to file names to produce a simple (as opposed to numbered) backup file name. */ char *simple_backup_suffix = "~"; static char *concat (); char *find_backup_file_name (); static char *make_version_name (); static int max_backup_version (); static int version_number (); /* Return the name of the new backup file for file FILE, allocated with malloc. Return 0 if out of memory. FILE must not end with a '/' unless it is the root directory. Do not call this function if backup_type == none. */ char * find_backup_file_name (file) const char *file; { char *copy; char *base; const char *dir; char *base_versions; int highest_backup; if (backup_type == simple) return concat (file, simple_backup_suffix); copy = malloc (strlen (file) + 1); if (copy == 0) return 0; strcpy (copy, file); base = strrchr (copy, '/'); if (base == 0) { base = copy; dir = "."; } else { *base++ = '\0'; dir = copy; } base_versions = concat (base, ".~"); if (base_versions == 0) { free (copy); return 0; } highest_backup = max_backup_version (base_versions, dir); free (copy); free (base_versions); if (backup_type == numbered_existing && highest_backup == 0) return concat (file, simple_backup_suffix); return make_version_name (file, highest_backup + 1); } /* Return the number of the highest-numbered backup file for file FILE in directory DIR. If there are no numbered backups of FILE in DIR, or an error occurs reading DIR, return 0. FILE should already have ".~" appended to it. */ static int max_backup_version (file, dir) const char *file; const char *dir; { DIR *dirp; struct dirent *dp; int highest_version; int this_version; size_t file_name_length; dirp = opendir (dir); if (!dirp) return 0; highest_version = 0; file_name_length = strlen (file); while ((dp = readdir (dirp)) != 0) { if (!REAL_DIR_ENTRY (dp) || NLENGTH (dp) <= file_name_length) continue; this_version = version_number (file, dp->d_name, file_name_length); if (this_version > highest_version) highest_version = this_version; } if (CLOSEDIR (dirp)) return 0; return highest_version; } /* Return a string, allocated with malloc, containing "FILE.~VERSION~". Return 0 if out of memory. */ static char * make_version_name (file, version) const char *file; int version; { char *backup_name; backup_name = malloc (strlen (file) + 16); if (backup_name == 0) return 0; sprintf (backup_name, "%s.~%d~", file, version); return backup_name; } /* If BACKUP is a numbered backup of BASE, return its version number; otherwise return 0. BASE_LENGTH is the length of BASE. BASE should already have ".~" appended to it. */ static int version_number (base, backup, base_length) const char *base; const char *backup; int base_length; { int version; const char *p; version = 0; if (!strncmp (base, backup, base_length) && ISDIGIT (backup[base_length])) { for (p = &backup[base_length]; ISDIGIT (*p); ++p) version = version * 10 + *p - '0'; if (p[0] != '~' || p[1]) version = 0; } return version; } /* Return the newly-allocated concatenation of STR1 and STR2. If out of memory, return 0. */ static char * concat (str1, str2) const char *str1; const char *str2; { char *newstr; int str1_length = strlen (str1); newstr = malloc (str1_length + strlen (str2) + 1); if (newstr == 0) return 0; strcpy (newstr, str1); strcpy (newstr + str1_length, str2); return newstr; }
/* error.c -- error handler for noninteractive utilities Copyright (C) 1990, 91, 92, 93, 94, 95, 96 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@gnu.ai.mit.edu>. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #if HAVE_VPRINTF || HAVE_DOPRNT || _LIBC # if __STDC__ # include <stdarg.h> # define VA_START(args, lastarg) va_start(args, lastarg) # else # include <varargs.h> # define VA_START(args, lastarg) va_start(args) # endif #else # define va_alist a1, a2, a3, a4, a5, a6, a7, a8 # define va_dcl char *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8; #endif #if STDC_HEADERS || _LIBC # include <stdlib.h> # include <string.h> #else void exit (); #endif #ifndef _ # define _(String) String #endif /* Get prototypes for the functions defined here. */ #include <error.h> /* If NULL, error will flush stdout, then print on stderr the program name, a colon and a space. Otherwise, error will call this function without parameters instead. */ void (*error_print_progname) ( #if __STDC__ - 0 void #endif ); /* This variable is incremented each time `error' is called. */ unsigned int error_message_count; #ifdef _LIBC /* In the GNU C library, there is a predefined variable for this. */ # define program_name program_invocation_name # include <errno.h> #else /* not _LIBC */ /* The calling program should define program_name and set it to the name of the executing program. */ extern char *program_name; # if HAVE_STRERROR # ifndef strerror /* On some systems, strerror is a macro */ char *strerror (); # endif # else static char * private_strerror (errnum) int errnum; { extern char *sys_errlist[]; extern int sys_nerr; if (errnum > 0 && errnum <= sys_nerr) return sys_errlist[errnum]; return _("Unknown system error"); } # define strerror private_strerror # endif /* HAVE_STRERROR */ #endif /* not _LIBC */ /* Print the program name and error message MESSAGE, which is a printf-style format string with optional args. If ERRNUM is nonzero, print its corresponding system error message. Exit with status STATUS if it is nonzero. */ /* VARARGS */ void #if defined(VA_START) && __STDC__ error (int status, int errnum, const char *message, ...) #else error (status, errnum, message, va_alist) int status; int errnum; char *message; va_dcl #endif { #ifdef VA_START va_list args; #endif if (error_print_progname) (*error_print_progname) (); else { fflush (stdout); fprintf (stderr, "%s: ", program_name); } #ifdef VA_START VA_START (args, message); # if HAVE_VPRINTF || _LIBC vfprintf (stderr, message, args); # else _doprnt (message, args, stderr); # endif va_end (args); #else fprintf (stderr, message, a1, a2, a3, a4, a5, a6, a7, a8); #endif ++error_message_count; if (errnum) fprintf (stderr, ": %s", strerror (errnum)); putc ('\n', stderr); fflush (stderr); if (status) exit (status); } /* Sometimes we want to have at most one error per line. This variable controls whether this mode is selected or not. */ int error_one_per_line; void #if defined(VA_START) && __STDC__ error_at_line (int status, int errnum, const char *file_name, unsigned int line_number, const char *message, ...) #else error_at_line (status, errnum, file_name, line_number, message, va_alist) int status; int errnum; const char *file_name; unsigned int line_number; char *message; va_dcl #endif { #ifdef VA_START va_list args; #endif if (error_one_per_line) { static const char *old_file_name; static unsigned int old_line_number; if (old_line_number == line_number && (file_name == old_file_name || !strcmp (old_file_name, file_name))) /* Simply return and print nothing. */ return; old_file_name = file_name; old_line_number = line_number; } if (error_print_progname) (*error_print_progname) (); else { fflush (stdout); fprintf (stderr, "%s:", program_name); } if (file_name != NULL) fprintf (stderr, "%s:%d: ", file_name, line_number); #ifdef VA_START VA_START (args, message); # if HAVE_VPRINTF || _LIBC vfprintf (stderr, message, args); # else _doprnt (message, args, stderr); # endif va_end (args); #else fprintf (stderr, message, a1, a2, a3, a4, a5, a6, a7, a8); #endif ++error_message_count; if (errnum) fprintf (stderr, ": %s", strerror (errnum)); putc ('\n', stderr); fflush (stderr); if (status) exit (status); }
/* Copyright (C) 1991, 1992, 1993, 1996, 1997 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif /* Enable GNU extensions in fnmatch.h. */ #ifndef _GNU_SOURCE # define _GNU_SOURCE 1 #endif #include <errno.h> #include <fnmatch.h> #include <ctype.h> /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #if defined _LIBC || !defined __GNU_LIBRARY__ # if defined STDC_HEADERS || !defined isascii # define ISASCII(c) 1 # else # define ISASCII(c) isascii(c) # endif # define ISUPPER(c) (ISASCII (c) && isupper (c)) # ifndef errno extern int errno; # endif /* Match STRING against the filename pattern PATTERN, returning zero if it matches, nonzero if not. */ int fnmatch (pattern, string, flags) const char *pattern; const char *string; int flags; { register const char *p = pattern, *n = string; register char c; /* Note that this evaluates C many times. */ # define FOLD(c) ((flags & FNM_CASEFOLD) && ISUPPER (c) ? tolower (c) : (c)) while ((c = *p++) != '\0') { c = FOLD (c); switch (c) { case '?': if (*n == '\0') return FNM_NOMATCH; else if ((flags & FNM_FILE_NAME) && *n == '/') return FNM_NOMATCH; else if ((flags & FNM_PERIOD) && *n == '.' && (n == string || ((flags & FNM_FILE_NAME) && n[-1] == '/'))) return FNM_NOMATCH; break; case '\\': if (!(flags & FNM_NOESCAPE)) { c = *p++; if (c == '\0') /* Trailing \ loses. */ return FNM_NOMATCH; c = FOLD (c); } if (FOLD (*n) != c) return FNM_NOMATCH; break; case '*': if ((flags & FNM_PERIOD) && *n == '.' && (n == string || ((flags & FNM_FILE_NAME) && n[-1] == '/'))) return FNM_NOMATCH; for (c = *p++; c == '?' || c == '*'; c = *p++) { if ((flags & FNM_FILE_NAME) && *n == '/') /* A slash does not match a wildcard under FNM_FILE_NAME. */ return FNM_NOMATCH; else if (c == '?') { /* A ? needs to match one character. */ if (*n == '\0') /* There isn't another character; no match. */ return FNM_NOMATCH; else /* One character of the string is consumed in matching this ? wildcard, so *??? won't match if there are less than three characters. */ ++n; } } if (c == '\0') return 0; { char c1 = (!(flags & FNM_NOESCAPE) && c == '\\') ? *p : c; c1 = FOLD (c1); for (--p; *n != '\0'; ++n) if ((c == '[' || FOLD (*n) == c1) && fnmatch (p, n, flags & ~FNM_PERIOD) == 0) return 0; return FNM_NOMATCH; } case '[': { /* Nonzero if the sense of the character class is inverted. */ register int not; if (*n == '\0') return FNM_NOMATCH; if ((flags & FNM_PERIOD) && *n == '.' && (n == string || ((flags & FNM_FILE_NAME) && n[-1] == '/'))) return FNM_NOMATCH; not = (*p == '!' || *p == '^'); if (not) ++p; c = *p++; for (;;) { register char cstart = c, cend = c; if (!(flags & FNM_NOESCAPE) && c == '\\') { if (*p == '\0') return FNM_NOMATCH; cstart = cend = *p++; } cstart = cend = FOLD (cstart); if (c == '\0') /* [ (unterminated) loses. */ return FNM_NOMATCH; c = *p++; c = FOLD (c); if ((flags & FNM_FILE_NAME) && c == '/') /* [/] can never match. */ return FNM_NOMATCH; if (c == '-' && *p != ']') { cend = *p++; if (!(flags & FNM_NOESCAPE) && cend == '\\') cend = *p++; if (cend == '\0') return FNM_NOMATCH; cend = FOLD (cend); c = *p++; } if (FOLD (*n) >= cstart && FOLD (*n) <= cend) goto matched; if (c == ']') break; } if (!not) return FNM_NOMATCH; break; matched:; /* Skip the rest of the [...] that already matched. */ while (c != ']') { if (c == '\0') /* [... (unterminated) loses. */ return FNM_NOMATCH; c = *p++; if (!(flags & FNM_NOESCAPE) && c == '\\') { if (*p == '\0') return FNM_NOMATCH; /* XXX 1003.2d11 is unclear if this is right. */ ++p; } } if (not) return FNM_NOMATCH; } break; default: if (c != FOLD (*n)) return FNM_NOMATCH; } ++n; } if (*n == '\0') return 0; if ((flags & FNM_LEADING_DIR) && *n == '/') /* The FNM_LEADING_DIR flag says that "foo*" matches "foobar/frobozz". */ return 0; return FNM_NOMATCH; # undef FOLD } #endif /* _LIBC or not __GNU_LIBRARY__. */
/* ftruncate emulations that work on some System V's. This file is in the public domain. */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <sys/types.h> #include <fcntl.h> #ifdef F_CHSIZE int ftruncate (fd, length) int fd; off_t length; { return fcntl (fd, F_CHSIZE, length); } #else /* not F_CHSIZE */ #ifdef F_FREESP /* By William Kucharski <kucharsk@netcom.com>. */ #include <sys/stat.h> #include <errno.h> #ifdef HAVE_UNISTD_H #include <unistd.h> #endif int ftruncate (fd, length) int fd; off_t length; { struct flock fl; struct stat filebuf; if (fstat (fd, &filebuf) < 0) return -1; if (filebuf.st_size < length) { /* Extend file length. */ if (lseek (fd, (length - 1), SEEK_SET) < 0) return -1; /* Write a "0" byte. */ if (write (fd, "", 1) != 1) return -1; } else { /* Truncate length. */ fl.l_whence = 0; fl.l_len = 0; fl.l_start = length; fl.l_type = F_WRLCK; /* write lock on file space */ /* This relies on the *undocumented* F_FREESP argument to fcntl, which truncates the file so that it ends at the position indicated by fl.l_start. Will minor miracles never cease? */ if (fcntl (fd, F_FREESP, &fl) < 0) return -1; } return 0; } #else /* not F_CHSIZE nor F_FREESP */ #ifdef HAVE_CHSIZE int ftruncate (fd, length) int fd; off_t length; { return chsize (fd, length); } #else /* not F_CHSIZE nor F_FREESP nor HAVE_CHSIZE */ #include <errno.h> #ifndef errno extern int errno; #endif int ftruncate (fd, length) int fd; off_t length; { errno = EIO; return -1; } #endif /* not HAVE_CHSIZE */ #endif /* not F_FREESP */ #endif /* not F_CHSIZE */
/* Copyright (C) 1995 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif # ifndef PARAMS # if defined (__GNUC__) || __STDC__ # define PARAMS(args) args # else # define PARAMS(args) () # endif # endif #if defined (vms) # include <types.h> # include <time.h> #else # include <sys/types.h> # ifdef TIME_WITH_SYS_TIME # include <sys/time.h> # include <time.h> # else # ifdef HAVE_SYS_TIME_H # include <sys/time.h> # else # include <time.h> # endif # endif #endif /* defined (vms) */ time_t get_date PARAMS ((const char *p, const time_t *now));
/* Getopt for GNU. NOTE: getopt is now part of the C library, so if you don't know what "Keep this file name-space clean" means, talk to roland@gnu.ai.mit.edu before changing it! Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97 Free Software Foundation, Inc. This file is part of the GNU C Library. Its master source is NOT part of the C library, however. The master source lives in /gd/gnu/lib. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>. Ditto for AIX 3.2 and <stdlib.h>. */ #ifndef _NO_PROTO #define _NO_PROTO #endif #ifdef HAVE_CONFIG_H #include <config.h> #endif #if !defined (__STDC__) || !__STDC__ /* This is a separate conditional since some stdc systems reject `defined (const)'. */ #ifndef const #define const #endif #endif #include <stdio.h> /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #define GETOPT_INTERFACE_VERSION 2 #if !defined (_LIBC) && defined (__GLIBC__) && __GLIBC__ >= 2 #include <gnu-versions.h> #if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION #define ELIDE_CODE #endif #endif #ifndef ELIDE_CODE /* This needs to come after some library #include to get __GNU_LIBRARY__ defined. */ #ifdef __GNU_LIBRARY__ /* Don't include stdlib.h for non-GNU C libraries because some of them contain conflicting prototypes for getopt. */ #include <stdlib.h> #include <unistd.h> #endif /* GNU C library. */ #ifdef VMS #include <unixlib.h> #if HAVE_STRING_H - 0 #include <string.h> #endif #endif #if defined (WIN32) && !defined (__CYGWIN32__) /* It's not Unix, really. See? Capital letters. */ #include <windows.h> #define getpid() GetCurrentProcessId() #endif #ifndef _ /* This is for other GNU distributions with internationalized messages. When compiling libc, the _ macro is predefined. */ #ifdef HAVE_LIBINTL_H # include <libintl.h> # define _(msgid) gettext (msgid) #else # define _(msgid) (msgid) #endif #endif /* This version of `getopt' appears to the caller like standard Unix `getopt' but it behaves differently for the user, since it allows the user to intersperse the options with the other arguments. As `getopt' works, it permutes the elements of ARGV so that, when it is done, all the options precede everything else. Thus all application programs are extended to handle flexible argument order. Setting the environment variable POSIXLY_CORRECT disables permutation. Then the behavior is completely standard. GNU application programs can use a third alternative mode in which they can distinguish the relative order of options and other arguments. */ #include "getopt.h" /* For communication from `getopt' to the caller. When `getopt' finds an option that takes an argument, the argument value is returned here. Also, when `ordering' is RETURN_IN_ORDER, each non-option ARGV-element is returned here. */ char *optarg = NULL; /* Index in ARGV of the next element to be scanned. This is used for communication to and from the caller and for communication between successive calls to `getopt'. On entry to `getopt', zero means this is the first call; initialize. When `getopt' returns -1, this is the index of the first of the non-option elements that the caller should itself scan. Otherwise, `optind' communicates from one call to the next how much of ARGV has been scanned so far. */ /* 1003.2 says this must be 1 before any call. */ int optind = 1; /* Formerly, initialization of getopt depended on optind==0, which causes problems with re-calling getopt as programs generally don't know that. */ int __getopt_initialized = 0; /* The next char to be scanned in the option-element in which the last option character we returned was found. This allows us to pick up the scan where we left off. If this is zero, or a null string, it means resume the scan by advancing to the next ARGV-element. */ static char *nextchar; /* Callers store zero here to inhibit the error message for unrecognized options. */ int opterr = 1; /* Set to an option character which was unrecognized. This must be initialized on some systems to avoid linking in the system's own getopt implementation. */ int optopt = '?'; /* Describe how to deal with options that follow non-option ARGV-elements. If the caller did not specify anything, the default is REQUIRE_ORDER if the environment variable POSIXLY_CORRECT is defined, PERMUTE otherwise. REQUIRE_ORDER means don't recognize them as options; stop option processing when the first non-option is seen. This is what Unix does. This mode of operation is selected by either setting the environment variable POSIXLY_CORRECT, or using `+' as the first character of the list of option characters. PERMUTE is the default. We permute the contents of ARGV as we scan, so that eventually all the non-options are at the end. This allows options to be given in any order, even with programs that were not written to expect this. RETURN_IN_ORDER is an option available to programs that were written to expect options and other ARGV-elements in any order and that care about the ordering of the two. We describe each non-option ARGV-element as if it were the argument of an option with character code 1. Using `-' as the first character of the list of option characters selects this mode of operation. The special argument `--' forces an end of option-scanning regardless of the value of `ordering'. In the case of RETURN_IN_ORDER, only `--' can cause `getopt' to return -1 with `optind' != ARGC. */ static enum { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER } ordering; /* Value of POSIXLY_CORRECT environment variable. */ static char *posixly_correct; #ifdef __GNU_LIBRARY__ /* We want to avoid inclusion of string.h with non-GNU libraries because there are many ways it can cause trouble. On some systems, it contains special magic macros that don't work in GCC. */ #include <string.h> #define my_index strchr #else /* Avoid depending on library functions or files whose names are inconsistent. */ char *getenv (); static char * my_index (str, chr) const char *str; int chr; { while (*str) { if (*str == chr) return (char *) str; str++; } return 0; } /* If using GCC, we can safely declare strlen this way. If not using GCC, it is ok not to declare it. */ #ifdef __GNUC__ /* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h. That was relevant to code that was here before. */ #if !defined (__STDC__) || !__STDC__ /* gcc with -traditional declares the built-in strlen to return int, and has done so at least since version 2.4.5. -- rms. */ extern int strlen (const char *); #endif /* not __STDC__ */ #endif /* __GNUC__ */ #endif /* not __GNU_LIBRARY__ */ /* Handle permutation of arguments. */ /* Describe the part of ARGV that contains non-options that have been skipped. `first_nonopt' is the index in ARGV of the first of them; `last_nonopt' is the index after the last of them. */ static int first_nonopt; static int last_nonopt; #ifdef _LIBC /* Bash 2.0 gives us an environment variable containing flags indicating ARGV elements that should not be considered arguments. */ static const char *nonoption_flags; static int nonoption_flags_len; static int original_argc; static char *const *original_argv; /* Make sure the environment variable bash 2.0 puts in the environment is valid for the getopt call we must make sure that the ARGV passed to getopt is that one passed to the process. */ static void store_args (int argc, char *const *argv) __attribute__ ((unused)); static void store_args (int argc, char *const *argv) { /* XXX This is no good solution. We should rather copy the args so that we can compare them later. But we must not use malloc(3). */ original_argc = argc; original_argv = argv; } text_set_element (__libc_subinit, store_args); #endif /* Exchange two adjacent subsequences of ARGV. One subsequence is elements [first_nonopt,last_nonopt) which contains all the non-options that have been skipped so far. The other is elements [last_nonopt,optind), which contains all the options processed since those non-options were skipped. `first_nonopt' and `last_nonopt' are relocated so that they describe the new indices of the non-options in ARGV after they are moved. */ #if defined (__STDC__) && __STDC__ static void exchange (char **); #endif static void exchange (argv) char **argv; { int bottom = first_nonopt; int middle = last_nonopt; int top = optind; char *tem; /* Exchange the shorter segment with the far end of the longer segment. That puts the shorter segment into the right place. It leaves the longer segment in the right place overall, but it consists of two parts that need to be swapped next. */ while (top > middle && middle > bottom) { if (top - middle > middle - bottom) { /* Bottom segment is the short one. */ int len = middle - bottom; register int i; /* Swap it with the top part of the top segment. */ for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[top - (middle - bottom) + i]; argv[top - (middle - bottom) + i] = tem; } /* Exclude the moved bottom segment from further swapping. */ top -= len; } else { /* Top segment is the short one. */ int len = top - middle; register int i; /* Swap it with the bottom part of the bottom segment. */ for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[middle + i]; argv[middle + i] = tem; } /* Exclude the moved top segment from further swapping. */ bottom += len; } } /* Update records for the slots the non-options now occupy. */ first_nonopt += (optind - last_nonopt); last_nonopt = optind; } /* Initialize the internal data when the first call is made. */ #if defined (__STDC__) && __STDC__ static const char *_getopt_initialize (int, char *const *, const char *); #endif static const char * _getopt_initialize (argc, argv, optstring) int argc; char *const *argv; const char *optstring; { /* Start processing options with ARGV-element 1 (since ARGV-element 0 is the program name); the sequence of previously skipped non-option ARGV-elements is empty. */ first_nonopt = last_nonopt = optind = 1; nextchar = NULL; posixly_correct = getenv ("POSIXLY_CORRECT"); /* Determine how to handle the ordering of options and nonoptions. */ if (optstring[0] == '-') { ordering = RETURN_IN_ORDER; ++optstring; } else if (optstring[0] == '+') { ordering = REQUIRE_ORDER; ++optstring; } else if (posixly_correct != NULL) ordering = REQUIRE_ORDER; else ordering = PERMUTE; #ifdef _LIBC if (posixly_correct == NULL && argc == original_argc && argv == original_argv) { /* Bash 2.0 puts a special variable in the environment for each command it runs, specifying which ARGV elements are the results of file name wildcard expansion and therefore should not be considered as options. */ char var[100]; sprintf (var, "_%d_GNU_nonoption_argv_flags_", getpid ()); nonoption_flags = getenv (var); if (nonoption_flags == NULL) nonoption_flags_len = 0; else nonoption_flags_len = strlen (nonoption_flags); } else nonoption_flags_len = 0; #endif return optstring; } /* Scan elements of ARGV (whose length is ARGC) for option characters given in OPTSTRING. If an element of ARGV starts with '-', and is not exactly "-" or "--", then it is an option element. The characters of this element (aside from the initial '-') are option characters. If `getopt' is called repeatedly, it returns successively each of the option characters from each of the option elements. If `getopt' finds another option character, it returns that character, updating `optind' and `nextchar' so that the next call to `getopt' can resume the scan with the following option character or ARGV-element. If there are no more option characters, `getopt' returns -1. Then `optind' is the index in ARGV of the first ARGV-element that is not an option. (The ARGV-elements have been permuted so that those that are not options now come last.) OPTSTRING is a string containing the legitimate option characters. If an option character is seen that is not listed in OPTSTRING, return '?' after printing an error message. If you set `opterr' to zero, the error message is suppressed but we still return '?'. If a char in OPTSTRING is followed by a colon, that means it wants an arg, so the following text in the same ARGV-element, or the text of the following ARGV-element, is returned in `optarg'. Two colons mean an option that wants an optional arg; if there is text in the current ARGV-element, it is returned in `optarg', otherwise `optarg' is set to zero. If OPTSTRING starts with `-' or `+', it requests different methods of handling the non-option ARGV-elements. See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. Long-named options begin with `--' instead of `-'. Their names may be abbreviated as long as the abbreviation is unique or is an exact match for some defined option. If they have an argument, it follows the option name in the same ARGV-element, separated from the option name by a `=', or else the in next ARGV-element. When `getopt' finds a long-named option, it returns 0 if that option's `flag' field is nonzero, the value of the option's `val' field if the `flag' field is zero. The elements of ARGV aren't really const, because we permute them. But we pretend they're const in the prototype to be compatible with other systems. LONGOPTS is a vector of `struct option' terminated by an element containing a name which is zero. LONGIND returns the index in LONGOPT of the long-named option found. It is only valid when a long-named option has been found by the most recent call. If LONG_ONLY is nonzero, '-' as well as '--' can introduce long-named options. */ int _getopt_internal (argc, argv, optstring, longopts, longind, long_only) int argc; char *const *argv; const char *optstring; const struct option *longopts; int *longind; int long_only; { optarg = NULL; if (!__getopt_initialized || optind == 0) { optstring = _getopt_initialize (argc, argv, optstring); optind = 1; /* Don't scan ARGV[0], the program name. */ __getopt_initialized = 1; } /* Test whether ARGV[optind] points to a non-option argument. Either it does not have option syntax, or there is an environment flag from the shell indicating it is not an option. The later information is only used when the used in the GNU libc. */ #ifdef _LIBC #define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \ || (optind < nonoption_flags_len \ && nonoption_flags[optind] == '1')) #else #define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0') #endif if (nextchar == NULL || *nextchar == '\0') { /* Advance to the next ARGV-element. */ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been moved back by the user (who may also have changed the arguments). */ if (last_nonopt > optind) last_nonopt = optind; if (first_nonopt > optind) first_nonopt = optind; if (ordering == PERMUTE) { /* If we have just processed some options following some non-options, exchange them so that the options come first. */ if (first_nonopt != last_nonopt && last_nonopt != optind) exchange ((char **) argv); else if (last_nonopt != optind) first_nonopt = optind; /* Skip any additional non-options and extend the range of non-options previously skipped. */ while (optind < argc && NONOPTION_P) optind++; last_nonopt = optind; } /* The special ARGV-element `--' means premature end of options. Skip it like a null option, then exchange with previous non-options as if it were an option, then skip everything else like a non-option. */ if (optind != argc && !strcmp (argv[optind], "--")) { optind++; if (first_nonopt != last_nonopt && last_nonopt != optind) exchange ((char **) argv); else if (first_nonopt == last_nonopt) first_nonopt = optind; last_nonopt = argc; optind = argc; } /* If we have done all the ARGV-elements, stop the scan and back over any non-options that we skipped and permuted. */ if (optind == argc) { /* Set the next-arg-index to point at the non-options that we previously skipped, so the caller will digest them. */ if (first_nonopt != last_nonopt) optind = first_nonopt; return -1; } /* If we have come to a non-option and did not permute it, either stop the scan or describe it to the caller and pass it by. */ if (NONOPTION_P) { if (ordering == REQUIRE_ORDER) return -1; optarg = argv[optind++]; return 1; } /* We have found another option-ARGV-element. Skip the initial punctuation. */ nextchar = (argv[optind] + 1 + (longopts != NULL && argv[optind][1] == '-')); } /* Decode the current option-ARGV-element. */ /* Check whether the ARGV-element is a long option. If long_only and the ARGV-element has the form "-f", where f is a valid short option, don't consider it an abbreviated form of a long option that starts with f. Otherwise there would be no way to give the -f short option. On the other hand, if there's a long option "fubar" and the ARGV-element is "-fu", do consider that an abbreviation of the long option, just like "--fu", and not "-f" with arg "u". This distinction seems to be the most useful approach. */ if (longopts != NULL && (argv[optind][1] == '-' || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1]))))) { char *nameend; const struct option *p; const struct option *pfound = NULL; int exact = 0; int ambig = 0; int indfound = -1; int option_index; for (nameend = nextchar; *nameend && *nameend != '='; nameend++) /* Do nothing. */ ; /* Test all long options for either exact match or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp (p->name, nextchar, nameend - nextchar)) { if ((unsigned int) (nameend - nextchar) == (unsigned int) strlen (p->name)) { /* Exact match found. */ pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { /* First nonexact match found. */ pfound = p; indfound = option_index; } else /* Second or later nonexact match found. */ ambig = 1; } if (ambig && !exact) { if (opterr) fprintf (stderr, _("%s: option `%s' is ambiguous\n"), argv[0], argv[optind]); nextchar += strlen (nextchar); optind++; optopt = 0; return '?'; } if (pfound != NULL) { option_index = indfound; optind++; if (*nameend) { /* Don't test has_arg with >, because some C compilers don't allow it to be used on enums. */ if (pfound->has_arg) optarg = nameend + 1; else { if (opterr) if (argv[optind - 1][1] == '-') /* --option */ fprintf (stderr, _("%s: option `--%s' doesn't allow an argument\n"), argv[0], pfound->name); else /* +option or -option */ fprintf (stderr, _("%s: option `%c%s' doesn't allow an argument\n"), argv[0], argv[optind - 1][0], pfound->name); nextchar += strlen (nextchar); optopt = pfound->val; return '?'; } } else if (pfound->has_arg == 1) { if (optind < argc) optarg = argv[optind++]; else { if (opterr) fprintf (stderr, _("%s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]); nextchar += strlen (nextchar); optopt = pfound->val; return optstring[0] == ':' ? ':' : '?'; } } nextchar += strlen (nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } /* Can't find it as a long option. If this is not getopt_long_only, or the option starts with '--' or is not a valid short option, then it's an error. Otherwise interpret it as a short option. */ if (!long_only || argv[optind][1] == '-' || my_index (optstring, *nextchar) == NULL) { if (opterr) { if (argv[optind][1] == '-') /* --option */ fprintf (stderr, _("%s: unrecognized option `--%s'\n"), argv[0], nextchar); else /* +option or -option */ fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), argv[0], argv[optind][0], nextchar); } nextchar = (char *) ""; optind++; optopt = 0; return '?'; } } /* Look at and handle the next short option-character. */ { char c = *nextchar++; char *temp = my_index (optstring, c); /* Increment `optind' when we start to process its last character. */ if (*nextchar == '\0') ++optind; if (temp == NULL || c == ':') { if (opterr) { if (posixly_correct) /* 1003.2 specifies the format of this message. */ fprintf (stderr, _("%s: illegal option -- %c\n"), argv[0], c); else fprintf (stderr, _("%s: invalid option -- %c\n"), argv[0], c); } optopt = c; return '?'; } /* Convenience. Treat POSIX -W foo same as long option --foo */ if (temp[0] == 'W' && temp[1] == ';') { char *nameend; const struct option *p; const struct option *pfound = NULL; int exact = 0; int ambig = 0; int indfound = 0; int option_index; /* This is an option that requires an argument. */ if (*nextchar != '\0') { optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ optind++; } else if (optind == argc) { if (opterr) { /* 1003.2 specifies the format of this message. */ fprintf (stderr, _("%s: option requires an argument -- %c\n"), argv[0], c); } optopt = c; if (optstring[0] == ':') c = ':'; else c = '?'; return c; } else /* We already incremented `optind' once; increment it again when taking next ARGV-elt as argument. */ optarg = argv[optind++]; /* optarg is now the argument, see if it's in the table of longopts. */ for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++) /* Do nothing. */ ; /* Test all long options for either exact match or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp (p->name, nextchar, nameend - nextchar)) { if ((unsigned int) (nameend - nextchar) == strlen (p->name)) { /* Exact match found. */ pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { /* First nonexact match found. */ pfound = p; indfound = option_index; } else /* Second or later nonexact match found. */ ambig = 1; } if (ambig && !exact) { if (opterr) fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"), argv[0], argv[optind]); nextchar += strlen (nextchar); optind++; return '?'; } if (pfound != NULL) { option_index = indfound; if (*nameend) { /* Don't test has_arg with >, because some C compilers don't allow it to be used on enums. */ if (pfound->has_arg) optarg = nameend + 1; else { if (opterr) fprintf (stderr, _("\ %s: option `-W %s' doesn't allow an argument\n"), argv[0], pfound->name); nextchar += strlen (nextchar); return '?'; } } else if (pfound->has_arg == 1) { if (optind < argc) optarg = argv[optind++]; else { if (opterr) fprintf (stderr, _("%s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]); nextchar += strlen (nextchar); return optstring[0] == ':' ? ':' : '?'; } } nextchar += strlen (nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } nextchar = NULL; return 'W'; /* Let the application handle it. */ } if (temp[1] == ':') { if (temp[2] == ':') { /* This is an option that accepts an argument optionally. */ if (*nextchar != '\0') { optarg = nextchar; optind++; } else optarg = NULL; nextchar = NULL; } else { /* This is an option that requires an argument. */ if (*nextchar != '\0') { optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ optind++; } else if (optind == argc) { if (opterr) { /* 1003.2 specifies the format of this message. */ fprintf (stderr, _("%s: option requires an argument -- %c\n"), argv[0], c); } optopt = c; if (optstring[0] == ':') c = ':'; else c = '?'; } else /* We already incremented `optind' once; increment it again when taking next ARGV-elt as argument. */ optarg = argv[optind++]; nextchar = NULL; } } return c; } } int getopt (argc, argv, optstring) int argc; char *const *argv; const char *optstring; { return _getopt_internal (argc, argv, optstring, (const struct option *) 0, (int *) 0, 0); } #endif /* Not ELIDE_CODE. */ #ifdef TEST /* Compile with -DTEST to make an executable for use in testing the above definition of `getopt'. */ int main (argc, argv) int argc; char **argv; { int c; int digit_optind = 0; while (1) { int this_option_optind = optind ? optind : 1; c = getopt (argc, argv, "abc:d:0123456789"); if (c == -1) break; switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (digit_optind != 0 && digit_optind != this_option_optind) printf ("digits occur in two different argv-elements.\n"); digit_optind = this_option_optind; printf ("option %c\n", c); break; case 'a': printf ("option a\n"); break; case 'b': printf ("option b\n"); break; case 'c': printf ("option c with value `%s'\n", optarg); break; case '?': break; default: printf ("?? getopt returned character code 0%o ??\n", c); } } if (optind < argc) { printf ("non-option ARGV-elements: "); while (optind < argc) printf ("%s ", argv[optind++]); printf ("\n"); } exit (0); } #endif /* TEST */
/* getopt_long and getopt_long_only entry points for GNU getopt. Copyright (C) 1987,88,89,90,91,92,93,94,96,97 Free Software Foundation, Inc. This file is part of the GNU C Library. Its master source is NOT part of the C library, however. The master source lives in /gd/gnu/lib. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include "getopt.h" #if !defined (__STDC__) || !__STDC__ /* This is a separate conditional since some stdc systems reject `defined (const)'. */ #ifndef const #define const #endif #endif #include <stdio.h> /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #define GETOPT_INTERFACE_VERSION 2 #if !defined (_LIBC) && defined (__GLIBC__) && __GLIBC__ >= 2 #include <gnu-versions.h> #if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION #define ELIDE_CODE #endif #endif #ifndef ELIDE_CODE /* This needs to come after some library #include to get __GNU_LIBRARY__ defined. */ #ifdef __GNU_LIBRARY__ #include <stdlib.h> #endif #ifndef NULL #define NULL 0 #endif int getopt_long (argc, argv, options, long_options, opt_index) int argc; char *const *argv; const char *options; const struct option *long_options; int *opt_index; { return _getopt_internal (argc, argv, options, long_options, opt_index, 0); } /* Like getopt_long, but '-' as well as '--' can indicate a long option. If an option that starts with '-' (not '--') doesn't match a long option, but does match a short option, it is parsed as a short option instead. */ int getopt_long_only (argc, argv, options, long_options, opt_index) int argc; char *const *argv; const char *options; const struct option *long_options; int *opt_index; { return _getopt_internal (argc, argv, options, long_options, opt_index, 1); } #endif /* Not ELIDE_CODE. */ #ifdef TEST #include <stdio.h> int main (argc, argv) int argc; char **argv; { int c; int digit_optind = 0; while (1) { int this_option_optind = optind ? optind : 1; int option_index = 0; static struct option long_options[] = { {"add", 1, 0, 0}, {"append", 0, 0, 0}, {"delete", 1, 0, 0}, {"verbose", 0, 0, 0}, {"create", 0, 0, 0}, {"file", 1, 0, 0}, {0, 0, 0, 0} }; c = getopt_long (argc, argv, "abc:d:0123456789", long_options, &option_index); if (c == -1) break; switch (c) { case 0: printf ("option %s", long_options[option_index].name); if (optarg) printf (" with arg %s", optarg); printf ("\n"); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (digit_optind != 0 && digit_optind != this_option_optind) printf ("digits occur in two different argv-elements.\n"); digit_optind = this_option_optind; printf ("option %c\n", c); break; case 'a': printf ("option a\n"); break; case 'b': printf ("option b\n"); break; case 'c': printf ("option c with value `%s'\n", optarg); break; case 'd': printf ("option d with value `%s'\n", optarg); break; case '?': break; default: printf ("?? getopt returned character code 0%o ??\n", c); } } if (optind < argc) { printf ("non-option ARGV-elements: "); while (optind < argc) printf ("%s ", argv[optind++]); printf ("\n"); } exit (0); } #endif /* TEST */
/* getversion.c -- select backup filename type Copyright (C) 1990 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@gnu.ai.mit.edu> */ #if HAVE_CONFIG_H # include <config.h> #endif #include "backupfile.h" #if STDC_HEADERS # include <stdlib.h> #endif int argmatch (); void invalid_arg (); extern char *program_name; static const char *const backup_args[] = { "never", "simple", "nil", "existing", "t", "numbered", 0 }; static const enum backup_type backup_types[] = { simple, simple, numbered_existing, numbered_existing, numbered, numbered }; /* Return the type of backup indicated by VERSION. Unique abbreviations are accepted. */ enum backup_type get_version (version) char *version; { int i; if (version == 0 || *version == 0) return numbered_existing; i = argmatch (version, backup_args); if (i >= 0) return backup_types[i]; invalid_arg ("version control type", version, i); exit (1); }
/* modechange.c -- file mode manipulation Copyright (C) 1989, 1990 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@ai.mit.edu> */ /* The ASCII mode string is compiled into a linked list of `struct modechange', which can then be applied to each file to be changed. We do this instead of re-parsing the ASCII string for each file because the compiled form requires less computation to use; when changing the mode of many files, this probably results in a performance gain. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <sys/types.h> #include <sys/stat.h> #include "modechange.h" #if STDC_HEADERS # include <stdlib.h> #else char *malloc (); #endif #ifndef NULL # define NULL 0 #endif #if STAT_MACROS_BROKEN # undef S_ISDIR #endif #if !defined(S_ISDIR) && defined(S_IFDIR) # define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif /* Return newly allocated memory to hold one element of type TYPE. */ #define talloc(type) ((type *) malloc (sizeof (type))) #define isodigit(c) ((c) >= '0' && (c) <= '7') static int oatoi (); /* Return a linked list of file mode change operations created from MODE_STRING, an ASCII string that contains either an octal number specifying an absolute mode, or symbolic mode change operations with the form: [ugoa...][[+-=][rwxXstugo...]...][,...] MASKED_OPS is a bitmask indicating which symbolic mode operators (=+-) should not affect bits set in the umask when no users are given. Operators not selected in MASKED_OPS ignore the umask. Return MODE_INVALID if `mode_string' does not contain a valid representation of file mode change operations; return MODE_MEMORY_EXHAUSTED if there is insufficient memory. */ struct mode_change * mode_compile (mode_string, masked_ops) const char *mode_string; unsigned masked_ops; { struct mode_change *head; /* First element of the linked list. */ struct mode_change *change; /* An element of the linked list. */ int i; /* General purpose temporary. */ int umask_value; /* The umask value (surprise). */ unsigned short affected_bits; /* Which bits in the mode are operated on. */ unsigned short affected_masked; /* `affected_bits' modified by umask. */ unsigned ops_to_mask; /* Operators to actually use umask on. */ i = oatoi (mode_string); if (i >= 0) { if (i > 07777) return MODE_INVALID; head = talloc (struct mode_change); if (head == NULL) return MODE_MEMORY_EXHAUSTED; head->next = NULL; head->op = '='; head->flags = 0; head->value = i; head->affected = 07777; /* Affect all permissions. */ return head; } umask_value = umask (0); umask (umask_value); /* Restore the old value. */ head = NULL; #ifdef lint change = NULL; #endif --mode_string; /* One loop iteration for each "ugoa...=+-rwxXstugo...[=+-rwxXstugo...]". */ do { affected_bits = 0; ops_to_mask = 0; /* Turn on all the bits in `affected_bits' for each group given. */ for (++mode_string;; ++mode_string) switch (*mode_string) { case 'u': affected_bits |= 04700; break; case 'g': affected_bits |= 02070; break; case 'o': affected_bits |= 01007; break; case 'a': affected_bits |= 07777; break; default: goto no_more_affected; } no_more_affected: /* If none specified, affect all bits, except perhaps those set in the umask. */ if (affected_bits == 0) { affected_bits = 07777; ops_to_mask = masked_ops; } while (*mode_string == '=' || *mode_string == '+' || *mode_string == '-') { /* Add the element to the tail of the list, so the operations are performed in the correct order. */ if (head == NULL) { head = talloc (struct mode_change); if (head == NULL) return MODE_MEMORY_EXHAUSTED; change = head; } else { change->next = talloc (struct mode_change); if (change->next == NULL) { mode_free (change); return MODE_MEMORY_EXHAUSTED; } change = change->next; } change->next = NULL; change->op = *mode_string; /* One of "=+-". */ affected_masked = affected_bits; if (ops_to_mask & (*mode_string == '=' ? MODE_MASK_EQUALS : *mode_string == '+' ? MODE_MASK_PLUS : MODE_MASK_MINUS)) affected_masked &= ~umask_value; change->affected = affected_masked; change->value = 0; change->flags = 0; /* Set `value' according to the bits set in `affected_masked'. */ for (++mode_string;; ++mode_string) switch (*mode_string) { case 'r': change->value |= 00444 & affected_masked; break; case 'w': change->value |= 00222 & affected_masked; break; case 'X': change->flags |= MODE_X_IF_ANY_X; /* Fall through. */ case 'x': change->value |= 00111 & affected_masked; break; case 's': /* Set the setuid/gid bits if `u' or `g' is selected. */ change->value |= 06000 & affected_masked; break; case 't': /* Set the "save text image" bit if `o' is selected. */ change->value |= 01000 & affected_masked; break; case 'u': /* Set the affected bits to the value of the `u' bits on the same file. */ if (change->value) goto invalid; change->value = 00700; change->flags |= MODE_COPY_EXISTING; break; case 'g': /* Set the affected bits to the value of the `g' bits on the same file. */ if (change->value) goto invalid; change->value = 00070; change->flags |= MODE_COPY_EXISTING; break; case 'o': /* Set the affected bits to the value of the `o' bits on the same file. */ if (change->value) goto invalid; change->value = 00007; change->flags |= MODE_COPY_EXISTING; break; default: goto no_more_values; } no_more_values:; } } while (*mode_string == ','); if (*mode_string == 0) return head; invalid: mode_free (head); return MODE_INVALID; } /* Return file mode OLDMODE, adjusted as indicated by the list of change operations CHANGES. If OLDMODE is a directory, the type `X' change affects it even if no execute bits were set in OLDMODE. The returned value has the S_IFMT bits cleared. */ unsigned short mode_adjust (oldmode, changes) unsigned oldmode; const struct mode_change *changes; { unsigned short newmode; /* The adjusted mode and one operand. */ unsigned short value; /* The other operand. */ newmode = oldmode & 07777; for (; changes; changes = changes->next) { if (changes->flags & MODE_COPY_EXISTING) { /* Isolate in `value' the bits in `newmode' to copy, given in the mask `changes->value'. */ value = newmode & changes->value; if (changes->value & 00700) /* Copy `u' permissions onto `g' and `o'. */ value |= (value >> 3) | (value >> 6); else if (changes->value & 00070) /* Copy `g' permissions onto `u' and `o'. */ value |= (value << 3) | (value >> 3); else /* Copy `o' permissions onto `u' and `g'. */ value |= (value << 3) | (value << 6); /* In order to change only `u', `g', or `o' permissions, or some combination thereof, clear unselected bits. This can not be done in mode_compile because the value to which the `changes->affected' mask is applied depends on the old mode of each file. */ value &= changes->affected; } else { value = changes->value; /* If `X', do not affect the execute bits if the file is not a directory and no execute bits are already set. */ if ((changes->flags & MODE_X_IF_ANY_X) && !S_ISDIR (oldmode) && (newmode & 00111) == 0) value &= ~00111; /* Clear the execute bits. */ } switch (changes->op) { case '=': /* Preserve the previous values in `newmode' of bits that are not affected by this change operation. */ newmode = (newmode & ~changes->affected) | value; break; case '+': newmode |= value; break; case '-': newmode &= ~value; break; } } return newmode; } /* Free the memory used by the list of file mode change operations CHANGES. */ void mode_free (changes) register struct mode_change *changes; { register struct mode_change *next; while (changes) { next = changes->next; free (changes); changes = next; } } /* Return a positive integer containing the value of the ASCII octal number S. If S is not an octal number, return -1. */ static int oatoi (s) char *s; { register int i; if (*s == 0) return -1; for (i = 0; isodigit (*s); ++s) i = i * 8 + *s - '0'; if (*s) return -1; return i; }
/* Sleep a given number of milliseconds. Copyright (C) 1992, 1993, 1994, 1997 Free Software Foundation, Inc. François Pinard <pinard@iro.umontreal.ca>, 1992. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif /* This code is heavily borrowed from Taylor UUCP 1.03. Ian picks one of usleep, nap, napms, poll, select and sleep, in decreasing order of preference. The sleep function is always available. */ /* In many cases, we will sleep if the wanted number of milliseconds is higher than this value. */ #define THRESHOLD_FOR_SLEEP 30000 /* Include some header files. */ #if HAVE_UNISTD_H # include <unistd.h> #endif #if HAVE_POLL # if HAVE_STROPTS_H # include <stropts.h> # endif # if HAVE_POLL_H # include <sys/types.h> # include <poll.h> # endif # if !HAVE_STROPTS_H && !HAVE_POLL_H /* We need a definition for struct pollfd, although it doesn't matter what it contains. */ struct pollfd { int idummy; }; # endif #else # if HAVE_SELECT # include <sys/time.h> # endif #endif /*---------------------------------------. | Sleep a given number of milliseconds. | `---------------------------------------*/ void msleep (milliseconds) int milliseconds; { #if HAVE_USLEEP if (milliseconds > 0) usleep (milliseconds * (long) 1000); #else # if HAVE_NAP if (milliseconds > 0) nap ((long) milliseconds); # else # if HAVE_NAPMS if (milliseconds >= THRESHOLD_FOR_SLEEP) { sleep (milliseconds / 1000); milliseconds %= 1000; } if (milliseconds > 0) napms (milliseconds); # else # if HAVE_POLL struct pollfd sdummy; /* poll(2) checks this address */ if (milliseconds >= THRESHOLD_FOR_SLEEP) { sleep (milliseconds / 1000); milliseconds %= 1000; } if (milliseconds > 0) poll (&sdummy, 0, milliseconds); # else # if HAVE_SELECT struct timeval s; if (milliseconds >= THRESHOLD_FOR_SLEEP) { sleep (milliseconds / 1000); milliseconds %= 1000; } if (milliseconds > 0) { s.tv_sec = milliseconds / 1000; s.tv_usec = (milliseconds % 1000) * (long) 1000; select (0, NULL, NULL, NULL, &s); } # else /* Round the time up to the next full second. */ if (milliseconds > 0) sleep ((milliseconds + 999) / 1000); # endif # endif # endif # endif #endif }
/* xgetcwd.c -- return current directory with unlimited length Copyright (C) 1992, 1996 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@gnu.ai.mit.edu>. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #include <errno.h> #ifndef errno extern int errno; #endif #include <sys/types.h> #include "pathmax.h" #if HAVE_GETCWD char *getcwd (); #else char *getwd (); # define getcwd(Buf, Max) getwd (Buf) #endif /* Amount to increase buffer size by in each try. */ #define PATH_INCR 32 char *xmalloc (); char *xrealloc (); void free (); /* Return the current directory, newly allocated, arbitrarily long. Return NULL and set errno on error. */ char * xgetcwd () { char *cwd; char *ret; unsigned path_max; errno = 0; path_max = (unsigned) PATH_MAX; path_max += 2; /* The getcwd docs say to do this. */ cwd = xmalloc (path_max); errno = 0; while ((ret = getcwd (cwd, path_max)) == NULL && errno == ERANGE) { path_max += PATH_INCR; cwd = xrealloc (cwd, path_max); errno = 0; } if (ret == NULL) { int save_errno = errno; free (cwd); errno = save_errno; return NULL; } return cwd; }
/* xmalloc.c -- malloc with out of memory checking Copyright (C) 1990, 91, 92, 93, 94, 95, 96 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #if __STDC__ # define VOID void #else # define VOID char #endif #include <sys/types.h> #if STDC_HEADERS # include <stdlib.h> #else VOID *calloc (); VOID *malloc (); VOID *realloc (); void free (); #endif #if ENABLE_NLS # include <libintl.h> # define _(Text) gettext (Text) #else # define textdomain(Domain) # define _(Text) Text #endif #include "error.h" #ifndef EXIT_FAILURE # define EXIT_FAILURE 1 #endif /* Prototypes for functions defined here. */ #if defined (__STDC__) && __STDC__ static VOID *fixup_null_alloc (size_t n); VOID *xmalloc (size_t n); VOID *xcalloc (size_t n, size_t s); VOID *xrealloc (VOID *p, size_t n); #endif /* Exit value when the requested amount of memory is not available. The caller may set it to some other value. */ int xmalloc_exit_failure = EXIT_FAILURE; #if __STDC__ && (HAVE_VPRINTF || HAVE_DOPRNT) void error (int, int, const char *, ...); #else void error (); #endif static VOID * fixup_null_alloc (n) size_t n; { VOID *p; p = 0; if (n == 0) p = malloc ((size_t) 1); if (p == 0) error (xmalloc_exit_failure, 0, _("Memory exhausted")); return p; } /* Allocate N bytes of memory dynamically, with error checking. */ VOID * xmalloc (n) size_t n; { VOID *p; p = malloc (n); if (p == 0) p = fixup_null_alloc (n); return p; } /* Allocate memory for N elements of S bytes, with error checking. */ VOID * xcalloc (n, s) size_t n, s; { VOID *p; p = calloc (n, s); if (p == 0) p = fixup_null_alloc (n); return p; } /* Change the size of an allocated block of memory P to N bytes, with error checking. If P is NULL, run xmalloc. */ VOID * xrealloc (p, n) VOID *p; size_t n; { if (p == 0) return xmalloc (n); p = realloc (p, n); if (p == 0) p = fixup_null_alloc (n); return p; }
/* xstrdup.c -- copy a string with out of memory checking Copyright (C) 1990, 1996 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #if defined(STDC_HEADERS) || defined(HAVE_STRING_H) # include <string.h> #else # include <strings.h> #endif #if defined (__STDC__) && __STDC__ char *xmalloc (size_t); char *xstrdup (char *string); #else char *xmalloc (); #endif /* Return a newly allocated copy of STRING. */ char * xstrdup (string) char *string; { return strcpy (xmalloc (strlen (string) + 1), string); }
/* Arithmetic for numbers greater than a long int, for GNU tar. Copyright (C) 1996, 1997 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "system.h" /* common.h is needed to define FATAL_ERROR. It also includes arith.h. */ #include "common.h" /* GNU tar needs handling numbers exceeding 32 bits, which is the size of unsigned long ints for many C compilers. This module should provide machinery for handling at least BITS_PER_TARLONG bits per number. If `long long' ints are available and are sufficient for the task, they will be used preferrably. Russell Cattelan reports 165 Gb single tapes (digital video D2 tapes on Ampex drives), so requiring 38 bits for the tape length in bytes. He also reports breaking the terabyte limit with a single file (using SGI xFS file system over 37 28GB disk arrays attached to a Power Challenge XL; check out http://www.lcse.umn.edu/ for a picture), so requiring a little more than 40 bits for the file size in bytes. The POSIX header structure allows for 12 octal digits to represent file lengths, that is, up to 36 bits for the byte size of files. If `long long' is not supported by the compiler, SIZEOF_LONG_LONG will be set to zero by configure. In this case, or if `long long' ints does not have enough bits, then huge numbers are rather represented by an array of longs, with the least significant super-digit at position 0. For making multiplication and decimal input/output easy, the base of a super-digit is an exact exponent of 10, and is such that base*base fits in a long. */ #if SUPERDIGIT /*-------------------------------. | Check if ACCUMULATOR is zero. | `-------------------------------*/ int zerop_tarlong_helper (unsigned long *accumulator) { int counter; for (counter = LONGS_PER_TARLONG - 1; counter >= 0; counter--) if (accumulator[counter]) return 0; return 1; } /*----------------------------------------------. | Check if FIRST is strictly less than SECOND. | `----------------------------------------------*/ int lessp_tarlong_helper (unsigned long *first, unsigned long *second) { int counter; for (counter = LONGS_PER_TARLONG - 1; counter >= 0; counter--) if (first[counter] != second[counter]) return first[counter] < second[counter]; return 0; } /*----------------------------. | Reset ACCUMULATOR to zero. | `----------------------------*/ void clear_tarlong_helper (unsigned long *accumulator) { int counter; for (counter = 0; counter < LONGS_PER_TARLONG; counter++) accumulator[counter] = 0; } /*----------------------------. | To ACCUMULATOR, add VALUE. | `----------------------------*/ void add_to_tarlong_helper (unsigned long *accumulator, int value) { int counter; if (value < 0) for (counter = 0; counter < LONGS_PER_TARLONG; counter++) { if (accumulator[counter] >= -value) { accumulator[counter] += value; return; } accumulator[counter] += value + SUPERDIGIT; value = -1; } else for (counter = 0; counter < LONGS_PER_TARLONG; counter++) { if (accumulator[counter] + value < SUPERDIGIT) { accumulator[counter] += value; return; } accumulator[counter] += value - SUPERDIGIT; value = 1; } FATAL_ERROR ((0, 0, _("Arithmetic overflow"))); } /*--------------------------------. | Multiply ACCUMULATOR by VALUE. | `--------------------------------*/ void mult_tarlong_helper (unsigned long *accumulator, int value) { int carry = 0; int counter; for (counter = 0; counter < LONGS_PER_TARLONG; counter++) { carry += accumulator[counter] * value; accumulator[counter] = carry % SUPERDIGIT; carry /= SUPERDIGIT; } if (carry) FATAL_ERROR ((0, 0, _("Arithmetic overflow"))); } /*----------------------------------------------------------. | Print the decimal representation of ACCUMULATOR on FILE. | `----------------------------------------------------------*/ void print_tarlong_helper (unsigned long *accumulator, FILE *file) { int counter = LONGS_PER_TARLONG - 1; while (counter > 0 && accumulator[counter] == 0) counter--; fprintf (file, "%uld", accumulator[counter]); while (counter > 0) fprintf (file, TARLONG_FORMAT, accumulator[--counter]); } #endif /* SUPERDIGIT */
/* System dependent definitions for GNU tar. Copyright (C) 1994, 1995, 1996, 1997 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif /* Declare alloca. AIX requires this to be the first thing in the file. */ #if __GNUC__ # define alloca __builtin_alloca #else # if HAVE_ALLOCA_H # include <alloca.h> # else # ifdef _AIX #pragma alloca # else # ifndef alloca char *alloca (); # endif # endif # endif #endif #include <sys/types.h> /* Declare a generic pointer type. */ #if __STDC__ || defined(__TURBOC__) # define voidstar void * #else # define voidstar char * #endif /* Declare ISASCII. */ #include <ctype.h> #if STDC_HEADERS # define ISASCII(Char) 1 #else # ifdef isascii # define ISASCII(Char) isascii (Char) # else # if HAVE_ISASCII # define ISASCII(Char) isascii (Char) # else # define ISASCII(Char) 1 # endif # endif #endif /* Declare string and memory handling routines. Take care that an ANSI string.h and pre-ANSI memory.h might conflict, and that memory.h and strings.h conflict on some systems. */ #if STDC_HEADERS || HAVE_STRING_H # include <string.h> # if !STDC_HEADERS && HAVE_MEMORY_H # include <memory.h> # endif #else # include <strings.h> # ifndef strchr # define strchr index # endif # ifndef strrchr # define strrchr rindex # endif # ifndef memcpy # define memcpy(Dst, Src, Num) bcopy (Src, Dst, Num) # endif # ifndef memcmp # define memcmp(Src1, Src2, Num) bcmp (Src1, Src2, Num) # endif #endif /* Declare errno. */ #include <errno.h> #ifndef errno extern int errno; #endif /* Declare open parameters. */ #if HAVE_FCNTL_H # include <fcntl.h> #else # include <sys/file.h> #endif /* Pick only one of the next three: */ #ifndef O_RDONLY # define O_RDONLY 0 /* only allow read */ #endif #ifndef O_WRONLY # define O_WRONLY 1 /* only allow write */ #endif #ifndef O_RDWR # define O_RDWR 2 /* both are allowed */ #endif /* The rest can be OR-ed in to the above: */ #ifndef O_NDELAY # define O_NDELAY 4 /* don't block on opening devices */ #endif #ifndef O_CREAT # define O_CREAT 8 /* create file if needed */ #endif #ifndef O_EXCL # define O_EXCL 16 /* file cannot already exist */ #endif #ifndef O_TRUNC # define O_TRUNC 32 /* truncate file on open */ #endif #ifndef O_APPEND # define O_APPEND 64 /* always write at end of file */ #endif /* MS-DOG forever, with my love! */ #ifndef O_BINARY # define O_BINARY 0 #endif /* Emulate System V 3-argument open call */ #if EMUL_OPEN3 # define open open3 #endif /* Declare file status routines and bits. */ #include <sys/stat.h> #ifndef S_ISLNK # define lstat stat #endif #if STAT_MACROS_BROKEN # undef S_ISBLK # undef S_ISCHR # undef S_ISDIR # undef S_ISFIFO # undef S_ISLNK # undef S_ISMPB # undef S_ISMPC # undef S_ISNWK # undef S_ISREG # undef S_ISSOCK #endif /* On MSDOS, there are missing things from <sys/stat.h>. */ #if MSDOS # define S_ISUID 0 # define S_ISGID 0 # define S_ISVTX 0 #endif #ifndef S_ISREG /* POSIX.1 stat stuff missing */ # define mode_t unsigned short #endif #if !defined(S_ISBLK) && defined(S_IFBLK) # define S_ISBLK(Mode) (((Mode) & S_IFMT) == S_IFBLK) #endif #if !defined(S_ISCHR) && defined(S_IFCHR) # define S_ISCHR(Mode) (((Mode) & S_IFMT) == S_IFCHR) #endif #if !defined(S_ISDIR) && defined(S_IFDIR) # define S_ISDIR(Mode) (((Mode) & S_IFMT) == S_IFDIR) #endif #if !defined(S_ISREG) && defined(S_IFREG) # define S_ISREG(Mode) (((Mode) & S_IFMT) == S_IFREG) #endif #if !defined(S_ISFIFO) && defined(S_IFIFO) # define S_ISFIFO(Mode) (((Mode) & S_IFMT) == S_IFIFO) #endif #if !defined(S_ISLNK) && defined(S_IFLNK) # define S_ISLNK(Mode) (((Mode) & S_IFMT) == S_IFLNK) #endif #if !defined(S_ISSOCK) && defined(S_IFSOCK) # define S_ISSOCK(Mode) (((Mode) & S_IFMT) == S_IFSOCK) #endif #if !defined(S_ISMPB) && defined(S_IFMPB) /* V7 */ # define S_ISMPB(Mode) (((Mode) & S_IFMT) == S_IFMPB) # define S_ISMPC(Mode) (((Mode) & S_IFMT) == S_IFMPC) #endif #if !defined(S_ISNWK) && defined(S_IFNWK) /* HP/UX */ # define S_ISNWK(Mode) (((Mode) & S_IFMT) == S_IFNWK) #endif #if !HAVE_MKFIFO # define mkfifo(Path, Mode) (mknod (Path, (Mode) | S_IFIFO, 0)) #endif #if !defined(S_ISCTG) && defined(S_IFCTG) /* contiguous file */ # define S_ISCTG(Mode) (((Mode) & S_IFMT) == S_IFCTG) #endif #if !defined(S_ISVTX) # define S_ISVTX 0001000 #endif #ifndef _POSIX_SOURCE # include <sys/param.h> #endif /* Include <unistd.h> before any preprocessor test of _POSIX_VERSION. */ #if HAVE_UNISTD_H # include <unistd.h> #endif /* Declare make device, major and minor. Since major is a function on SVR4, we have to resort to GOT_MAJOR instead of just testing if major is #define'd. */ #if MAJOR_IN_MKDEV # include <sys/mkdev.h> # define GOT_MAJOR #endif #if MAJOR_IN_SYSMACROS # include <sys/sysmacros.h> # define GOT_MAJOR #endif /* Some <sys/types.h> defines the macros. */ #ifdef major # define GOT_MAJOR #endif #ifndef GOT_MAJOR # if MSDOS # define major(Device) (Device) # define minor(Device) (Device) # define makedev(Major, Minor) (((Major) << 8) | (Minor)) # define GOT_MAJOR # endif #endif /* For HP-UX before HP-UX 8, major/minor are not in <sys/sysmacros.h>. */ #ifndef GOT_MAJOR # if defined(hpux) || defined(__hpux__) || defined(__hpux) # include <sys/mknod.h> # define GOT_MAJOR # endif #endif #ifndef GOT_MAJOR # define major(Device) (((Device) >> 8) & 0xff) # define minor(Device) ((Device) & 0xff) # define makedev(Major, Minor) (((Major) << 8) | (Minor)) #endif #undef GOT_MAJOR /* Declare directory reading routines and structures. */ #if __TURBOC__ # include "msd_dir.h" # define NAMLEN(dirent) ((dirent)->d_namlen) #else # if HAVE_DIRENT_H # include <dirent.h> # define NAMLEN(dirent) (strlen((dirent)->d_name)) # else # define dirent direct # define NAMLEN(dirent) ((dirent)->d_namlen) # if HAVE_SYS_NDIR_H # include <sys/ndir.h> # endif # if HAVE_SYS_DIR_H # include <sys/dir.h> # endif # if HAVE_NDIR_H # include <ndir.h> # endif # endif #endif /* Declare wait status. */ #if HAVE_SYS_WAIT_H # include <sys/wait.h> #endif #if HAVE_UNION_WAIT # define WAIT_T union wait # ifndef WTERMSIG # define WTERMSIG(Status) ((Status).w_termsig) # endif # ifndef WCOREDUMP # define WCOREDUMP(Status) ((Status).w_coredump) # endif # ifndef WEXITSTATUS # define WEXITSTATUS(Status) ((Status).w_retcode) # endif #else # define WAIT_T int # ifndef WTERMSIG # define WTERMSIG(Status) ((Status) & 0x7f) # endif # ifndef WCOREDUMP # define WCOREDUMP(Status) ((Status) & 0x80) # endif # ifndef WEXITSTATUS # define WEXITSTATUS(Status) (((Status) >> 8) & 0xff) # endif #endif #ifndef WIFSTOPPED # define WIFSTOPPED(Status) (WTERMSIG(Status) == 0x7f) #endif #ifndef WIFSIGNALED # define WIFSIGNALED(Status) (WTERMSIG(Status) != 0) #endif #ifndef WIFEXITED # define WIFEXITED(Status) (WTERMSIG(Status) == 0) #endif /* FIXME: It is wrong to use BLOCKSIZE for buffers when the logical block size is greater than 512 bytes; so ST_BLKSIZE code below, in preparation for some cleanup in this area, later. */ /* Get or fake the disk device blocksize. Usually defined by sys/param.h (if at all). */ #if !defined(DEV_BSIZE) && defined(BSIZE) # define DEV_BSIZE BSIZE #endif #if !defined(DEV_BSIZE) && defined(BBSIZE) /* SGI */ # define DEV_BSIZE BBSIZE #endif #ifndef DEV_BSIZE # define DEV_BSIZE 4096 #endif /* Extract or fake data from a `struct stat'. ST_BLKSIZE gives the optimal I/O blocksize for the file, in bytes. Some systems, like Sequents, return st_blksize of 0 on pipes. */ #if !HAVE_ST_BLKSIZE # define ST_BLKSIZE(Statbuf) DEV_BSIZE #else # define ST_BLKSIZE(Statbuf) \ ((Statbuf).st_blksize > 0 ? (Statbuf).st_blksize : DEV_BSIZE) #endif /* Extract or fake data from a `struct stat'. ST_NBLOCKS gives the number of 512-byte blocks in the file (including indirect blocks). fileblocks.c uses BSIZE. HP-UX counts st_blocks in 1024-byte units, this loses when mixing HP-UX and BSD filesystems with NFS. AIX PS/2 counts st_blocks in 4K units. */ #if !HAVE_ST_BLOCKS # if defined(_POSIX_SOURCE) || !defined(BSIZE) # define ST_NBLOCKS(Statbuf) (((Statbuf).st_size + 512 - 1) / 512) # else # define ST_NBLOCKS(Statbuf) (st_blocks ((Statbuf).st_size)) # endif #else # if defined(hpux) || defined(__hpux__) || defined(__hpux) # define ST_NBLOCKS(Statbuf) ((Statbuf).st_blocks * 2) # else # if defined(_AIX) && defined(_I386) # define ST_NBLOCKS(Statbuf) ((Statbuf).st_blocks * 8) # else # define ST_NBLOCKS(Statbuf) ((Statbuf).st_blocks) # endif # endif #endif /* This is a real challenge to properly get MTIO* symbols :-(. ISC uses <sys/gentape.h>. SCO and BSDi uses <sys/tape.h>; BSDi also requires <sys/tprintf.h> and <sys/device.h> for defining tp_dev and tpr_t. It seems that the rest use <sys/mtio.h>, which itself requires other files, depending on systems. Pyramid defines _IOW in <sgtty.h>, for example. */ #if HAVE_SYS_GENTAPE_H # include <sys/gentape.h> #else # if HAVE_SYS_TAPE_H # if HAVE_SYS_DEVICE_H # include <sys/device.h> # endif # if HAVE_SYS_BUF_H # include <sys/buf.h> # endif # if HAVE_SYS_TPRINTF_H # include <sys/tprintf.h> # endif # include <sys/tape.h> # else # if HAVE_SYS_MTIO_H # include <sys/ioctl.h> # if HAVE_SGTTY_H # include <sgtty.h> # endif # if HAVE_SYS_IO_TRIOCTL_H # include <sys/io/trioctl.h> # endif # include <sys/mtio.h> # endif # endif #endif /* Declare standard functions. */ #if STDC_HEADERS # include <stdlib.h> #else voidstar malloc (); voidstar realloc (); # if HAVE_GETCWD char *getcwd (); # endif char *getenv (); #endif #include <stdio.h> #ifndef _POSIX_VERSION # if MSDOS # include <io.h> # else off_t lseek (); # endif #endif #include <pathmax.h> #if WITH_DMALLOC # undef HAVE_VALLOC # define DMALLOC_FUNC_CHECK # include <dmalloc.h> #endif /* Prototypes for external functions. */ #ifndef PARAMS # if PROTOTYPES # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif #endif #if HAVE_LOCALE_H # include <locale.h> #endif #if !HAVE_SETLOCALE # define setlocale(Category, Locale) #endif #if ENABLE_NLS # include <libintl.h> # define _(Text) gettext (Text) #else # define bindtextdomain(Domain, Directory) # define textdomain(Domain) # define _(Text) Text #endif #define N_(Text) Text /* Library modules. */ #include "error.h" #if !HAVE_STRSTR char *strstr PARAMS ((const char *, const char *)); #endif #if HAVE_VALLOC # ifndef valloc voidstar valloc PARAMS ((size_t)); # endif #else # define valloc(Size) malloc (Size) #endif voidstar xmalloc PARAMS ((size_t)); voidstar xrealloc PARAMS ((voidstar, size_t)); char *xstrdup PARAMS ((const char *));
/* Common declarations for the tar program. Copyright (C) 1988, 92, 93, 94, 96, 97 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Declare the GNU tar archive format. */ #include "tar.h" /* The checksum field is filled with this while the checksum is computed. */ #define CHKBLANKS " " /* 8 blanks, no null */ /* Some constants from POSIX are given names. */ #define NAME_FIELD_SIZE 100 #define PREFIX_FIELD_SIZE 155 #define UNAME_FIELD_SIZE 32 #define GNAME_FIELD_SIZE 32 /* POSIX specified symbols currently unused are undefined here. */ #undef TSUID #undef TSGID #undef TSVTX #undef TUREAD #undef TUWRITE #undef TUEXEC #undef TGREAD #undef TGWRITE #undef TGEXEC #undef TOREAD #undef TOWRITE #undef TOEXEC /* Some various global definitions. */ /* Name of file to use for interacting with user. */ #if MSDOS # define TTY_NAME "con" #else # define TTY_NAME "/dev/tty" #endif /* GLOBAL is defined to empty in `tar.c' only, and left alone in other `*.c' modules. Here, we merely set it to "extern" if it is not already set. GNU tar does depend on the system loader to preset all GLOBAL variables to neutral (or zero) values, explicit initialisation is usually not done. */ #ifndef GLOBAL # define GLOBAL extern #endif /* Exit status for GNU tar. Let's try to keep this list as simple as possible. -d option strongly invites a status different for unequal comparison and other errors. */ GLOBAL int exit_status; #define TAREXIT_SUCCESS 0 #define TAREXIT_DIFFERS 1 #define TAREXIT_FAILURE 2 /* Both WARN and ERROR write a message on stderr and continue processing, however ERROR manages so tar will exit unsuccessfully. FATAL_ERROR writes a message on stderr and aborts immediately, with another message line telling so. USAGE_ERROR works like FATAL_ERROR except that the other message line suggests trying --help. All four macros accept a single argument of the form ((0, errno, _("FORMAT"), Args...)). `errno' is `0' when the error is not being detected by the system. */ #define WARN(Args) \ error Args #define ERROR(Args) \ (error Args, exit_status = TAREXIT_FAILURE) #define FATAL_ERROR(Args) \ (error Args, error (TAREXIT_FAILURE, 0, \ _("Error is not recoverable: exiting now")), 0) #define USAGE_ERROR(Args) \ (error Args, usage (TAREXIT_FAILURE), 0) /* Information gleaned from the command line. */ #include "arith.h" #include "modechange.h" /* Name of this program. */ GLOBAL const char *program_name; /* Main command option. */ enum subcommand { UNKNOWN_SUBCOMMAND, /* none of the following */ APPEND_SUBCOMMAND, /* -r */ CAT_SUBCOMMAND, /* -A */ CREATE_SUBCOMMAND, /* -c */ DELETE_SUBCOMMAND, /* -D */ DIFF_SUBCOMMAND, /* -d */ EXTRACT_SUBCOMMAND, /* -x */ LIST_SUBCOMMAND, /* -t */ UPDATE_SUBCOMMAND /* -u */ }; GLOBAL enum subcommand subcommand_option; /* Selected format for output archive. */ GLOBAL enum archive_format archive_format; /* Either NL or NUL, as decided by the --null option. */ GLOBAL char filename_terminator; /* Size of each record, once in blocks, once in bytes. Those two variables are always related, the second being BLOCKSIZE times the first. They do not have _option in their name, even if their values is derived from option decoding, as these are especially important in tar. */ GLOBAL int blocking_factor; GLOBAL int record_size; /* Boolean value. */ GLOBAL int absolute_names_option; /* This variable tells how to interpret newer_mtime_option, below. If zero, files get archived if their mtime is not less than newer_mtime_option. If nonzero, files get archived if *either* their ctime or mtime is not less than newer_mtime_option. */ GLOBAL int after_date_option; /* Boolean value. */ GLOBAL int atime_preserve_option; /* Boolean value. */ GLOBAL int backup_option; /* Boolean value. */ GLOBAL int block_number_option; /* Boolean value. */ GLOBAL int checkpoint_option; /* Specified name of compression program, or "gzip" as implied by -z. */ GLOBAL const char *use_compress_program_option; /* Boolean value. */ GLOBAL int dereference_option; /* Boolean value. */ GLOBAL int exclude_option; /* Specified file containing names to work on. */ GLOBAL const char *files_from_option; /* Boolean value. */ GLOBAL int force_local_option; /* Specified value to be put into tar file in place of stat () results, or just -1 if such an override should not take place. */ GLOBAL gid_t group_option; /* Boolean value. */ GLOBAL int ignore_failed_read_option; /* Boolean value. */ GLOBAL int ignore_zeros_option; /* Boolean value. */ GLOBAL int incremental_option; /* Specified name of script to run at end of each tape change. */ GLOBAL const char *info_script_option; /* Boolean value. */ GLOBAL int interactive_option; /* Boolean value. */ GLOBAL int keep_old_files_option; /* Specified file name for incremental list. */ GLOBAL const char *listed_incremental_option; /* Specified mode change string. */ GLOBAL struct mode_change *mode_option; /* Boolean value. */ GLOBAL int multi_volume_option; /* The same variable hold the time, whether mtime or ctime. Just fake a non-existing option, for making the code clearer, elsewhere. */ #define newer_ctime_option newer_mtime_option /* Specified threshold date and time. Files having a more recent timestamp get archived (also see after_date_option above). If left to zero, it may be interpreted as very low threshold, just usable as such. */ GLOBAL time_t newer_mtime_option; /* Boolean value. */ GLOBAL int no_recurse_option; /* Boolean value. */ GLOBAL int numeric_owner_option; /* Boolean value. */ GLOBAL int one_file_system_option; /* Specified value to be put into tar file in place of stat () results, or just -1 if such an override should not take place. */ GLOBAL uid_t owner_option; /* Boolean value. */ GLOBAL int recursive_unlink_option; /* Boolean value. */ GLOBAL int read_full_records_option; /* Boolean value. */ GLOBAL int remove_files_option; /* Specified remote shell command. */ GLOBAL const char *rsh_command_option; /* Boolean value. */ GLOBAL int same_order_option; /* Boolean value. */ GLOBAL int same_owner_option; /* Boolean value. */ GLOBAL int same_permissions_option; /* Boolean value. */ GLOBAL int show_omitted_dirs_option; /* Boolean value. */ GLOBAL int sparse_option; /* Boolean value. */ GLOBAL int starting_file_option; /* Specified maximum byte length of each tape volume (multiple of 1024). */ GLOBAL tarlong tape_length_option; /* Boolean value. */ GLOBAL int to_stdout_option; /* Boolean value. */ GLOBAL int totals_option; /* Boolean value. */ GLOBAL int touch_option; /* Boolean value. */ GLOBAL int unlink_first_option; /* Count how many times the option has been set, multiple setting yields more verbose behavior. Value 0 means no verbosity, 1 means file name only, 2 means file name and all attributes. More than 2 is just like 2. */ GLOBAL int verbose_option; /* Boolean value. */ GLOBAL int verify_option; /* Specified name of file containing the volume number. */ GLOBAL const char *volno_file_option; /* Specified value or pattern. */ GLOBAL const char *volume_label_option; /* Other global variables. */ /* File descriptor for archive file. */ GLOBAL int archive; /* Nonzero when outputting to /dev/null. */ GLOBAL int dev_null_output; /* Name of file for the current archive entry. */ GLOBAL char *current_file_name; /* Name of link for the current archive entry. */ GLOBAL char *current_link_name; /* List of tape drive names, number of such tape drives, allocated number, and current cursor in list. */ GLOBAL const char **archive_name_array; GLOBAL int archive_names; GLOBAL int allocated_archive_names; GLOBAL const char **archive_name_cursor; /* Structure for keeping track of filenames and lists thereof. */ struct name { struct name *next; short length; /* cached strlen(name) */ char found; /* a matching file has been found */ char firstch; /* first char is literally matched */ char regexp; /* this name is a regexp, not literal */ char *change_dir; /* set with the -C option */ char *dir_contents; /* for incremental_option */ char fake; /* dummy entry */ char name[1]; }; GLOBAL struct name *namelist; /* points to first name in list */ GLOBAL struct name *namelast; /* points to last name in list */ /* Pointer to the start of the scratch space. */ struct sp_array { off_t offset; int numbytes; }; GLOBAL struct sp_array *sparsearray; /* Initial size of the sparsearray. */ GLOBAL int sp_array_size; /* Declarations for each module. */ /* FIXME: compare.c should not directly handle the following variable, instead, this should be done in buffer.c only. */ enum access_mode { ACCESS_READ, ACCESS_WRITE, ACCESS_UPDATE }; extern enum access_mode access_mode; /* Module buffer.c. */ extern FILE *stdlis; extern char *save_name; extern long save_sizeleft; extern long save_totsize; extern int write_archive_to_stdout; int available_space_after PARAMS ((union block *)); long current_block_ordinal PARAMS ((void)); void close_archive PARAMS ((void)); void closeout_volume_number PARAMS ((void)); union block *find_next_block PARAMS ((void)); void flush_read PARAMS ((void)); void flush_write PARAMS ((void)); void flush_archive PARAMS ((void)); void init_total_written PARAMS ((void)); void init_volume_number PARAMS ((void)); void open_archive PARAMS ((enum access_mode)); void print_total_written PARAMS ((void)); void reset_eof PARAMS ((void)); void set_next_block_after PARAMS ((union block *)); /* Module create.c. */ void create_archive PARAMS ((void)); void dump_file PARAMS ((char *, int, int)); void finish_header PARAMS ((union block *)); void to_oct PARAMS ((long, int, char *)); void write_eot PARAMS ((void)); /* Module diffarch.c. */ extern int now_verifying; void diff_archive PARAMS ((void)); void diff_init PARAMS ((void)); void verify_volume PARAMS ((void)); /* Module extract.c. */ void extr_init PARAMS ((void)); void extract_archive PARAMS ((void)); void apply_delayed_set_stat PARAMS ((void)); /* Module delete.c. */ void delete_archive_members PARAMS ((void)); /* Module incremen.c. */ void collect_and_sort_names PARAMS ((void)); char *get_directory_contents PARAMS ((char *, int)); void write_dir_file PARAMS ((void)); void gnu_restore PARAMS ((int)); void write_directory_file PARAMS ((void)); /* Module list.c. */ enum read_header { HEADER_STILL_UNREAD, /* for when read_header has not been called */ HEADER_SUCCESS, /* header successfully read and checksummed */ HEADER_ZERO_BLOCK, /* zero block where header expected */ HEADER_END_OF_FILE, /* true end of file while header expected */ HEADER_FAILURE /* ill-formed header, or bad checksum */ }; extern union block *current_header; extern struct stat current_stat; extern enum archive_format current_format; void decode_header PARAMS ((union block *, struct stat *, enum archive_format *, int)); long from_oct PARAMS ((int, char *)); void list_archive PARAMS ((void)); void print_for_mkdir PARAMS ((char *, int, int)); void print_header PARAMS ((void)); void read_and PARAMS ((void (*do_) ())); enum read_header read_header PARAMS ((void)); void skip_extended_headers PARAMS ((void)); void skip_file PARAMS ((long)); /* Module mangle.c. */ void extract_mangle PARAMS ((void)); /* Module misc.c. */ void assign_string PARAMS ((char **, const char *)); char *quote_copy_string PARAMS ((const char *)); int unquote_string PARAMS ((char *)); char *merge_sort PARAMS ((char *, int, int, int (*) (char *, char *))); int is_dot_or_dotdot PARAMS ((const char *)); int remove_any_file PARAMS ((const char *, int)); int maybe_backup_file PARAMS ((const char *, int)); void undo_last_backup PARAMS ((void)); /* Module names.c. */ void gid_to_gname PARAMS ((gid_t, char gname[GNAME_FIELD_SIZE])); int gname_to_gid PARAMS ((char gname[GNAME_FIELD_SIZE], gid_t *)); void uid_to_uname PARAMS ((uid_t, char uname[UNAME_FIELD_SIZE])); int uname_to_uid PARAMS ((char uname[UNAME_FIELD_SIZE], uid_t *)); void init_names PARAMS ((void)); void name_add PARAMS ((const char *)); void name_init PARAMS ((int, char *const *)); void name_term PARAMS ((void)); char *name_next PARAMS ((int change_)); void name_close PARAMS ((void)); void name_gather PARAMS ((void)); void addname PARAMS ((const char *)); int name_match PARAMS ((const char *)); void names_notfound PARAMS ((void)); void name_expand PARAMS ((void)); struct name *name_scan PARAMS ((const char *)); char *name_from_list PARAMS ((void)); void blank_name_list PARAMS ((void)); char *new_name PARAMS ((const char *, const char *)); void add_exclude PARAMS ((char *)); void add_exclude_file PARAMS ((const char *)); int check_exclude PARAMS ((const char *)); /* Module tar.c. */ int confirm PARAMS ((const char *, const char *)); void request_stdin PARAMS ((const char *)); /* Module update.c. */ extern char *output_start; void update_archive PARAMS ((void));
/* Format of tar archives. Copyright (C) 1988, 92, 93, 94, 96, 97 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* GNU tar Archive Format description. */ /* If OLDGNU_COMPATIBILITY is not zero, tar produces archives which, by default, are readable by older versions of GNU tar. This can be overriden by using --posix; in this case, POSIXLY_CORRECT in environment may be set for enforcing stricter conformance. If OLDGNU_COMPATIBILITY is zero or undefined, tar will eventually produces archives which, by default, POSIX compatible; then either using --posix or defining POSIXLY_CORRECT enforces stricter conformance. This #define will disappear in a few years. FP, June 1995. */ #define OLDGNU_COMPATIBILITY 1 /*---------------------------------------------. | `tar' Header Block, from POSIX 1003.1-1990. | `---------------------------------------------*/ /* POSIX header. */ struct posix_header { /* byte offset */ char name[100]; /* 0 */ char mode[8]; /* 100 */ char uid[8]; /* 108 */ char gid[8]; /* 116 */ char size[12]; /* 124 */ char mtime[12]; /* 136 */ char chksum[8]; /* 148 */ char typeflag; /* 156 */ char linkname[100]; /* 157 */ char magic[6]; /* 257 */ char version[2]; /* 263 */ char uname[32]; /* 265 */ char gname[32]; /* 297 */ char devmajor[8]; /* 329 */ char devminor[8]; /* 337 */ char prefix[155]; /* 345 */ /* 500 */ }; #define TMAGIC "ustar" /* ustar and a null */ #define TMAGLEN 6 #define TVERSION "00" /* 00 and no null */ #define TVERSLEN 2 /* Values used in typeflag field. */ #define REGTYPE '0' /* regular file */ #define AREGTYPE '\0' /* regular file */ #define LNKTYPE '1' /* link */ #define SYMTYPE '2' /* reserved */ #define CHRTYPE '3' /* character special */ #define BLKTYPE '4' /* block special */ #define DIRTYPE '5' /* directory */ #define FIFOTYPE '6' /* FIFO special */ #define CONTTYPE '7' /* reserved */ /* Bits used in the mode field, values in octal. */ #define TSUID 04000 /* set UID on execution */ #define TSGID 02000 /* set GID on execution */ #define TSVTX 01000 /* reserved */ /* file permissions */ #define TUREAD 00400 /* read by owner */ #define TUWRITE 00200 /* write by owner */ #define TUEXEC 00100 /* execute/search by owner */ #define TGREAD 00040 /* read by group */ #define TGWRITE 00020 /* write by group */ #define TGEXEC 00010 /* execute/search by group */ #define TOREAD 00004 /* read by other */ #define TOWRITE 00002 /* write by other */ #define TOEXEC 00001 /* execute/search by other */ /*-------------------------------------. | `tar' Header Block, GNU extensions. | `-------------------------------------*/ /* In GNU tar, SYMTYPE is for to symbolic links, and CONTTYPE is for contiguous files, so maybe disobeying the `reserved' comment in POSIX header description. I suspect these were meant to be used this way, and should not have really been `reserved' in the published standards. */ /* *BEWARE* *BEWARE* *BEWARE* that the following information is still boiling, and may change. Even if the OLDGNU format description should be accurate, the so-called GNU format is not yet fully decided. It is surely meant to use only extensions allowed by POSIX, but the sketch below repeats some ugliness from the OLDGNU format, which should rather go away. Sparse files should be saved in such a way that they do *not* require two passes at archive creation time. Huge files get some POSIX fields to overflow, alternate solutions have to be sought for this. */ /* Descriptor for a single file hole. */ struct sparse { /* byte offset */ char offset[12]; /* 0 */ char numbytes[12]; /* 12 */ /* 24 */ }; /* Sparse files are not supported in POSIX ustar format. For sparse files with a POSIX header, a GNU extra header is provided which holds overall sparse information and a few sparse descriptors. When an old GNU header replaces both the POSIX header and the GNU extra header, it holds some sparse descriptors too. Whether POSIX or not, if more sparse descriptors are still needed, they are put into as many successive sparse headers as necessary. The following constants tell how many sparse descriptors fit in each kind of header able to hold them. */ #define SPARSES_IN_EXTRA_HEADER 16 #define SPARSES_IN_OLDGNU_HEADER 4 #define SPARSES_IN_SPARSE_HEADER 21 /* The GNU extra header contains some information GNU tar needs, but not foreseen in POSIX header format. It is only used after a POSIX header (and never with old GNU headers), and immediately follows this POSIX header, when typeflag is a letter rather than a digit, so signaling a GNU extension. */ struct extra_header { /* byte offset */ char atime[12]; /* 0 */ char ctime[12]; /* 12 */ char offset[12]; /* 24 */ char realsize[12]; /* 36 */ char longnames[4]; /* 48 */ char unused_pad1[68]; /* 52 */ struct sparse sp[SPARSES_IN_EXTRA_HEADER]; /* 120 */ char isextended; /* 504 */ /* 505 */ }; /* Extension header for sparse files, used immediately after the GNU extra header, and used only if all sparse information cannot fit into that extra header. There might even be many such extension headers, one after the other, until all sparse information has been recorded. */ struct sparse_header { /* byte offset */ struct sparse sp[SPARSES_IN_SPARSE_HEADER]; /* 0 */ char isextended; /* 504 */ /* 505 */ }; /* The old GNU format header conflicts with POSIX format in such a way that POSIX archives may fool old GNU tar's, and POSIX tar's might well be fooled by old GNU tar archives. An old GNU format header uses the space used by the prefix field in a POSIX header, and cumulates information normally found in a GNU extra header. With an old GNU tar header, we never see any POSIX header nor GNU extra header. Supplementary sparse headers are allowed, however. */ struct oldgnu_header { /* byte offset */ char unused_pad1[345]; /* 0 */ char atime[12]; /* 345 */ char ctime[12]; /* 357 */ char offset[12]; /* 369 */ char longnames[4]; /* 381 */ char unused_pad2; /* 385 */ struct sparse sp[SPARSES_IN_OLDGNU_HEADER]; /* 386 */ char isextended; /* 482 */ char realsize[12]; /* 483 */ /* 495 */ }; /* OLDGNU_MAGIC uses both magic and version fields, which are contiguous. Found in an archive, it indicates an old GNU header format, which will be hopefully become obsolescent. With OLDGNU_MAGIC, uname and gname are valid, though the header is not truly POSIX conforming. */ #define OLDGNU_MAGIC "ustar " /* 7 chars and a null */ /* The standards committee allows only capital A through capital Z for user-defined expansion. */ /* This is a dir entry that contains the names of files that were in the dir at the time the dump was made. */ #define GNUTYPE_DUMPDIR 'D' /* Identifies the *next* file on the tape as having a long linkname. */ #define GNUTYPE_LONGLINK 'K' /* Identifies the *next* file on the tape as having a long name. */ #define GNUTYPE_LONGNAME 'L' /* This is the continuation of a file that began on another volume. */ #define GNUTYPE_MULTIVOL 'M' /* For storing filenames that do not fit into the main header. */ #define GNUTYPE_NAMES 'N' /* This is for sparse files. */ #define GNUTYPE_SPARSE 'S' /* This file is a tape/volume header. Ignore it on extraction. */ #define GNUTYPE_VOLHDR 'V' /*--------------------------------------. | tar Header Block, overall structure. | `--------------------------------------*/ /* tar files are made in basic blocks of this size. */ #define BLOCKSIZE 512 enum archive_format { DEFAULT_FORMAT, /* format to be decided later */ V7_FORMAT, /* old V7 tar format */ OLDGNU_FORMAT, /* GNU format as per before tar 1.12 */ POSIX_FORMAT, /* restricted, pure POSIX format */ GNU_FORMAT /* POSIX format with GNU extensions */ }; union block { char buffer[BLOCKSIZE]; struct posix_header header; struct extra_header extra_header; struct oldgnu_header oldgnu_header; struct sparse_header sparse_header; }; /* End of Format description. */
/* Buffer management for tar. Copyright (C) 1988, 92, 93, 94, 96, 97 Free Software Foundation, Inc. Written by John Gilmore, on 1985-08-25. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "system.h" #include <signal.h> #include <time.h> time_t time (); #if MSDOS # include <process.h> #endif #if XENIX # include <sys/inode.h> #endif #ifndef FNM_LEADING_DIR # include <fnmatch.h> #endif #include "common.h" #include "rmt.h" #define DEBUG_FORK 0 /* if nonzero, childs are born stopped */ #define STDIN 0 /* standard input file descriptor */ #define STDOUT 1 /* standard output file descriptor */ #define PREAD 0 /* read file descriptor from pipe() */ #define PWRITE 1 /* write file descriptor from pipe() */ /* Number of retries before giving up on read. */ #define READ_ERROR_MAX 10 /* Globbing pattern to append to volume label if initial match failed. */ #define VOLUME_LABEL_APPEND " Volume [1-9]*" /* Variables. */ static tarlong total_written; /* bytes written on all volumes */ static tarlong bytes_written; /* bytes written on this volume */ /* FIXME: The following four variables should ideally be static to this module. However, this cannot be done yet, as update.c uses the first three a lot, and compare.c uses the fourth. The cleanup continues! */ union block *record_start; /* start of record of archive */ union block *record_end; /* last+1 block of archive record */ union block *current_block; /* current block of archive */ enum access_mode access_mode; /* how do we handle the archive */ static struct stat archive_stat; /* stat block for archive file */ static long record_start_block; /* block ordinal at record_start */ /* Where we write list messages (not errors, not interactions) to. Stdout unless we're writing a pipe, in which case stderr. */ FILE *stdlis; static void backspace_output PARAMS ((void)); static int new_volume PARAMS ((enum access_mode)); static void write_error PARAMS ((int)); static void read_error PARAMS ((void)); #if !MSDOS /* Obnoxious test to see if dimwit is trying to dump the archive. */ dev_t ar_dev; ino_t ar_ino; #endif /* PID of child program, if compress_option or remote archive access. */ static int child_pid = 0; /* Error recovery stuff */ static int read_error_count; /* Have we hit EOF yet? */ static int hit_eof; /* Checkpointing counter */ static int checkpoint; /* We're reading, but we just read the last block and its time to update. */ /* As least EXTERN like this one as possible. FIXME! */ extern int time_to_start_writing; int file_to_switch_to = -1; /* if remote update, close archive, and use this descriptor to write to */ static int volno = 1; /* which volume of a multi-volume tape we're on */ static int global_volno = 1; /* volume number to print in external messages */ /* The pointer save_name, which is set in function dump_file() of module create.c, points to the original long filename instead of the new, shorter mangled name that is set in start_header() of module create.c. The pointer save_name is only used in multi-volume mode when the file being processed is non-sparse; if a file is split between volumes, the save_name is used in generating the LF_MULTIVOL record on the second volume. (From Pierce Cantrell, 1991-08-13.) */ char *save_name; /* name of the file we are currently writing */ long save_totsize; /* total size of file we are writing, only valid if save_name is non NULL */ long save_sizeleft; /* where we are in the file we are writing, only valid if save_name is nonzero */ int write_archive_to_stdout = 0; /* Used by flush_read and flush_write to store the real info about saved names. */ static char *real_s_name = NULL; static long real_s_totsize; static long real_s_sizeleft; /* Functions. */ #if DEBUG_FORK static pid_t myfork (void) { pid_t result = fork(); if (result == 0) kill (getpid (), SIGSTOP); return result; } # define fork myfork #endif /* DEBUG FORK */ void init_total_written (void) { clear_tarlong (total_written); clear_tarlong (bytes_written); } void print_total_written (void) { fprintf (stderr, _("Total bytes written: ")); print_tarlong (total_written, stderr); fprintf (stderr, "\n"); } /*--------------------------------------------------------. | Compute and return the block ordinal at current_block. | `--------------------------------------------------------*/ long current_block_ordinal (void) { return record_start_block + (current_block - record_start); } /*------------------------------------------------------------------. | If the EOF flag is set, reset it, as well as current_block, etc. | `------------------------------------------------------------------*/ void reset_eof (void) { if (hit_eof) { hit_eof = 0; current_block = record_start; record_end = record_start + blocking_factor; access_mode = ACCESS_WRITE; } } /*-------------------------------------------------------------------------. | Return the location of the next available input or output block. | | Return NULL for EOF. Once we have returned NULL, we just keep returning | | it, to avoid accidentally going on to the next file on the tape. | `-------------------------------------------------------------------------*/ union block * find_next_block (void) { if (current_block == record_end) { if (hit_eof) return NULL; flush_archive (); if (current_block == record_end) { hit_eof = 1; return NULL; } } return current_block; } /*------------------------------------------------------. | Indicate that we have used all blocks up thru BLOCK. | | | | FIXME: should the arg have an off-by-1? | `------------------------------------------------------*/ void set_next_block_after (union block *block) { while (block >= current_block) current_block++; /* Do *not* flush the archive here. If we do, the same argument to set_next_block_after could mean the next block (if the input record is exactly one block long), which is not what is intended. */ if (current_block > record_end) abort (); } /*------------------------------------------------------------------------. | Return the number of bytes comprising the space between POINTER through | | the end of the current buffer of blocks. This space is available for | | filling with data, or taking data from. POINTER is usually (but not | | always) the result previous find_next_block call. | `------------------------------------------------------------------------*/ int available_space_after (union block *pointer) { return (int) (record_end->buffer - pointer->buffer); } /*------------------------------------------------------------------. | Close file having descriptor FD, and abort if close unsucessful. | `------------------------------------------------------------------*/ static void xclose (int fd) { if (close (fd) < 0) FATAL_ERROR ((0, errno, _("Cannot close file #%d"), fd)); } /*-----------------------------------------------------------------------. | Duplicate file descriptor FROM into becoming INTO, or else, issue | | MESSAGE. INTO is closed first and has to be the next available slot. | `-----------------------------------------------------------------------*/ static void xdup2 (int from, int into, const char *message) { if (from != into) { int status = close (into); if (status < 0 && errno != EBADF) FATAL_ERROR ((0, errno, _("Cannot close descriptor %d"), into)); status = dup (from); if (status != into) FATAL_ERROR ((0, errno, _("Cannot properly duplicate %s"), message)); xclose (from); } } #if MSDOS /*-------------------------------------------------------. | Set ARCHIVE for writing, then compressing an archive. | `-------------------------------------------------------*/ static void child_open_for_compress (void) { FATAL_ERROR ((0, 0, _("Cannot use compressed or remote archives"))); } /*---------------------------------------------------------. | Set ARCHIVE for uncompressing, then reading an archive. | `---------------------------------------------------------*/ static void child_open_for_uncompress (void) { FATAL_ERROR ((0, 0, _("Cannot use compressed or remote archives"))); } #else /* not MSDOS */ /*---------------------------------------------------------------------. | Return nonzero if NAME is the name of a regular file, or if the file | | does not exist (so it would be created as a regular file). | `---------------------------------------------------------------------*/ static int is_regular_file (const char *name) { struct stat stbuf; if (stat (name, &stbuf) < 0) return 1; if (S_ISREG (stbuf.st_mode)) return 1; return 0; } /*-------------------------------------------------------. | Set ARCHIVE for writing, then compressing an archive. | `-------------------------------------------------------*/ static void child_open_for_compress (void) { int parent_pipe[2]; int child_pipe[2]; int grandchild_pid; if (pipe (parent_pipe) < 0) FATAL_ERROR ((0, errno, _("Cannot open pipe"))); child_pid = fork (); if (child_pid < 0) FATAL_ERROR ((0, errno, _("Cannot fork"))); if (child_pid > 0) { /* The parent tar is still here! Just clean up. */ archive = parent_pipe[PWRITE]; xclose (parent_pipe[PREAD]); return; } /* The new born child tar is here! */ program_name = _("tar (child)"); xdup2 (parent_pipe[PREAD], STDIN, _("(child) Pipe to stdin")); xclose (parent_pipe[PWRITE]); /* Check if we need a grandchild tar. This happens only if either: a) we are writing stdout: to force reblocking; b) the file is to be accessed by rmt: compressor doesn't know how; c) the file is not a plain file. */ if (strcmp (archive_name_array[0], "-") != 0 && !_remdev (archive_name_array[0]) && is_regular_file (archive_name_array[0])) { if (backup_option) maybe_backup_file (archive_name_array[0], 1); /* We don't need a grandchild tar. Open the archive and launch the compressor. */ archive = creat (archive_name_array[0], 0666); if (archive < 0) { int saved_errno = errno; if (backup_option) undo_last_backup (); FATAL_ERROR ((0, saved_errno, _("Cannot open archive %s"), archive_name_array[0])); } xdup2 (archive, STDOUT, _("Archive to stdout")); execlp (use_compress_program_option, use_compress_program_option, (char *) 0); FATAL_ERROR ((0, errno, _("Cannot exec %s"), use_compress_program_option)); } /* We do need a grandchild tar. */ if (pipe (child_pipe) < 0) FATAL_ERROR ((0, errno, _("Cannot open pipe"))); grandchild_pid = fork (); if (grandchild_pid < 0) FATAL_ERROR ((0, errno, _("Child cannot fork"))); if (grandchild_pid > 0) { /* The child tar is still here! Launch the compressor. */ xdup2 (child_pipe[PWRITE], STDOUT, _("((child)) Pipe to stdout")); xclose (child_pipe[PREAD]); execlp (use_compress_program_option, use_compress_program_option, (char *) 0); FATAL_ERROR ((0, errno, _("Cannot exec %s"), use_compress_program_option)); } /* The new born grandchild tar is here! */ program_name = _("tar (grandchild)"); /* Prepare for reblocking the data from the compressor into the archive. */ xdup2 (child_pipe[PREAD], STDIN, _("(grandchild) Pipe to stdin")); xclose (child_pipe[PWRITE]); if (strcmp (archive_name_array[0], "-") == 0) archive = STDOUT; else archive = rmtcreat (archive_name_array[0], 0666, rsh_command_option); if (archive < 0) FATAL_ERROR ((0, errno, _("Cannot open archive %s"), archive_name_array[0])); /* Let's read out of the stdin pipe and write an archive. */ while (1) { int status = 0; char *cursor; int length; /* Assemble a record. */ for (length = 0, cursor = record_start->buffer; length < record_size; length += status, cursor += status) { int size = record_size - length; if (size < BLOCKSIZE) size = BLOCKSIZE; status = read (STDIN, cursor, (size_t) size); if (status <= 0) break; } if (status < 0) FATAL_ERROR ((0, errno, _("Cannot read from compression program"))); /* Copy the record. */ if (status == 0) { /* We hit the end of the file. Write last record at full length, as the only role of the grandchild is doing proper reblocking. */ if (length > 0) { memset (record_start->buffer + length, 0, (size_t) record_size - length); status = rmtwrite (archive, record_start->buffer, (unsigned int) record_size); if (status != record_size) write_error (status); } /* There is nothing else to read, break out. */ break; } status = rmtwrite (archive, record_start->buffer, (unsigned int) record_size); if (status != record_size) write_error (status); } #if 0 close_archive (); #endif exit (exit_status); } /*---------------------------------------------------------. | Set ARCHIVE for uncompressing, then reading an archive. | `---------------------------------------------------------*/ static void child_open_for_uncompress (void) { int parent_pipe[2]; int child_pipe[2]; int grandchild_pid; if (pipe (parent_pipe) < 0) FATAL_ERROR ((0, errno, _("Cannot open pipe"))); child_pid = fork (); if (child_pid < 0) FATAL_ERROR ((0, errno, _("Cannot fork"))); if (child_pid > 0) { /* The parent tar is still here! Just clean up. */ read_full_records_option = 1; archive = parent_pipe[PREAD]; xclose (parent_pipe[PWRITE]); return; } /* The new born child tar is here! */ program_name = _("tar (child)"); xdup2 (parent_pipe[PWRITE], STDOUT, _("(child) Pipe to stdout")); xclose (parent_pipe[PREAD]); /* Check if we need a grandchild tar. This happens only if either: a) we're reading stdin: to force unblocking; b) the file is to be accessed by rmt: compressor doesn't know how; c) the file is not a plain file. */ if (strcmp (archive_name_array[0], "-") != 0 && !_remdev (archive_name_array[0]) && is_regular_file (archive_name_array[0])) { /* We don't need a grandchild tar. Open the archive and lauch the uncompressor. */ archive = open (archive_name_array[0], O_RDONLY | O_BINARY, 0666); if (archive < 0) FATAL_ERROR ((0, errno, _("Cannot open archive %s"), archive_name_array[0])); xdup2 (archive, STDIN, _("Archive to stdin")); execlp (use_compress_program_option, use_compress_program_option, "-d", (char *) 0); FATAL_ERROR ((0, errno, _("Cannot exec %s"), use_compress_program_option)); } /* We do need a grandchild tar. */ if (pipe (child_pipe) < 0) FATAL_ERROR ((0, errno, _("Cannot open pipe"))); grandchild_pid = fork (); if (grandchild_pid < 0) FATAL_ERROR ((0, errno, _("Child cannot fork"))); if (grandchild_pid > 0) { /* The child tar is still here! Launch the uncompressor. */ xdup2 (child_pipe[PREAD], STDIN, _("((child)) Pipe to stdin")); xclose (child_pipe[PWRITE]); execlp (use_compress_program_option, use_compress_program_option, "-d", (char *) 0); FATAL_ERROR ((0, errno, _("Cannot exec %s"), use_compress_program_option)); } /* The new born grandchild tar is here! */ program_name = _("tar (grandchild)"); /* Prepare for unblocking the data from the archive into the uncompressor. */ xdup2 (child_pipe[PWRITE], STDOUT, _("(grandchild) Pipe to stdout")); xclose (child_pipe[PREAD]); if (strcmp (archive_name_array[0], "-") == 0) archive = STDIN; else archive = rmtopen (archive_name_array[0], O_RDONLY | O_BINARY, 0666, rsh_command_option); if (archive < 0) FATAL_ERROR ((0, errno, _("Cannot open archive %s"), archive_name_array[0])); /* Let's read the archive and pipe it into stdout. */ while (1) { char *cursor; int maximum; int count; int status; read_error_count = 0; error_loop: status = rmtread (archive, record_start->buffer, (unsigned int) (record_size)); if (status < 0) { read_error (); goto error_loop; } if (status == 0) break; cursor = record_start->buffer; maximum = status; while (maximum) { count = maximum < BLOCKSIZE ? maximum : BLOCKSIZE; status = write (STDOUT, cursor, (size_t) count); if (status < 0) FATAL_ERROR ((0, errno, _("\ Cannot write to compression program"))); if (status != count) { ERROR ((0, 0, _("\ Write to compression program short %d bytes"), count - status)); count = status; } cursor += count; maximum -= count; } } #if 0 close_archive (); #endif exit (exit_status); } #endif /* not MSDOS */ /*--------------------------------------------------------------------------. | Check the LABEL block against the volume label, seen as a globbing | | pattern. Return true if the pattern matches. In case of failure, retry | | matching a volume sequence number before giving up in multi-volume mode. | `--------------------------------------------------------------------------*/ static int check_label_pattern (union block *label) { char *string; int result; if (fnmatch (volume_label_option, label->header.name, 0) == 0) return 1; if (!multi_volume_option) return 0; string = xmalloc (strlen (volume_label_option) + sizeof VOLUME_LABEL_APPEND + 1); strcpy (string, volume_label_option); strcat (string, VOLUME_LABEL_APPEND); result = fnmatch (string, label->header.name, 0) == 0; free (string); return result; } /*------------------------------------------------------------------------. | Open an archive file. The argument specifies whether we are reading or | | writing, or both. | `------------------------------------------------------------------------*/ void open_archive (enum access_mode access) { int backed_up_flag = 0; stdlis = to_stdout_option ? stderr : stdout; if (record_size == 0) FATAL_ERROR ((0, 0, _("Invalid value for record_size"))); if (archive_names == 0) FATAL_ERROR ((0, 0, _("No archive name given"))); current_file_name = NULL; current_link_name = NULL; /* FIXME: According to POSIX.1, PATH_MAX may well not be a compile-time constant, and the value from sysconf (_SC_PATH_MAX) may well not be any size that is reasonable to allocate a buffer. In the GNU system, there is no fixed limit. The only correct thing to do is to use dynamic allocation. (Roland McGrath) */ if (!real_s_name) real_s_name = (char *) xmalloc (PATH_MAX); /* FIXME: real_s_name is never freed. */ save_name = NULL; if (multi_volume_option) { record_start = (union block *) valloc ((unsigned) (record_size + (2 * BLOCKSIZE))); if (record_start) record_start += 2; } else record_start = (union block *) valloc ((unsigned) record_size); if (!record_start) FATAL_ERROR ((0, 0, _("Could not allocate memory for blocking factor %d"), blocking_factor)); current_block = record_start; record_end = record_start + blocking_factor; /* When updating the archive, we start with reading. */ access_mode = access == ACCESS_UPDATE ? ACCESS_READ : access; if (multi_volume_option && verify_option) FATAL_ERROR ((0, 0, _("Cannot verify multi-volume archives"))); if (use_compress_program_option) { if (multi_volume_option) FATAL_ERROR ((0, 0, _("Cannot use multi-volume compressed archives"))); if (verify_option) FATAL_ERROR ((0, 0, _("Cannot verify compressed archives"))); switch (access) { case ACCESS_READ: child_open_for_uncompress (); break; case ACCESS_WRITE: child_open_for_compress (); break; case ACCESS_UPDATE: FATAL_ERROR ((0, 0, _("Cannot update compressed archives"))); break; } if (access == ACCESS_WRITE && strcmp (archive_name_array[0], "-") == 0) stdlis = stderr; } else if (strcmp (archive_name_array[0], "-") == 0) { read_full_records_option = 1; /* could be a pipe, be safe */ if (verify_option) FATAL_ERROR ((0, 0, _("Cannot verify stdin/stdout archive"))); switch (access) { case ACCESS_READ: archive = STDIN; break; case ACCESS_WRITE: archive = STDOUT; stdlis = stderr; break; case ACCESS_UPDATE: archive = STDIN; stdlis = stderr; write_archive_to_stdout = 1; break; } } else if (verify_option) archive = rmtopen (archive_name_array[0], O_RDWR | O_CREAT | O_BINARY, 0666, rsh_command_option); else switch (access) { case ACCESS_READ: archive = rmtopen (archive_name_array[0], O_RDONLY | O_BINARY, 0666, rsh_command_option); break; case ACCESS_WRITE: if (backup_option) { maybe_backup_file (archive_name_array[0], 1); backed_up_flag = 1; } archive = rmtcreat (archive_name_array[0], 0666, rsh_command_option); break; case ACCESS_UPDATE: archive = rmtopen (archive_name_array[0], O_RDWR | O_CREAT | O_BINARY, 0666, rsh_command_option); break; } if (archive < 0) { int saved_errno = errno; if (backed_up_flag) undo_last_backup (); FATAL_ERROR ((0, saved_errno, _("Cannot open %s"), archive_name_array[0])); } #if !MSDOS fstat (archive, &archive_stat); /* Detect if outputting to "/dev/null". */ { struct stat dev_null_stat; stat ("/dev/null", &dev_null_stat); dev_null_output = (S_ISCHR (archive_stat.st_mode) && archive_stat.st_rdev == dev_null_stat.st_rdev); } if (!_isrmt (archive) && S_ISREG (archive_stat.st_mode)) { ar_dev = archive_stat.st_dev; ar_ino = archive_stat.st_ino; } #endif /* not MSDOS */ #if MSDOS setmode (archive, O_BINARY); #endif switch (access) { case ACCESS_READ: case ACCESS_UPDATE: record_end = record_start; /* set up for 1st record = # 0 */ find_next_block (); /* read it in, check for EOF */ if (volume_label_option) { union block *label = find_next_block (); if (!label) FATAL_ERROR ((0, 0, _("Archive not labelled to match `%s'"), volume_label_option)); if (!check_label_pattern (label)) FATAL_ERROR ((0, 0, _("Volume `%s' does not match `%s'"), label->header.name, volume_label_option)); } break; case ACCESS_WRITE: if (volume_label_option) { memset ((void *) record_start, 0, BLOCKSIZE); if (multi_volume_option) sprintf (record_start->header.name, "%s Volume 1", volume_label_option); else strcpy (record_start->header.name, volume_label_option); assign_string (¤t_file_name, record_start->header.name); record_start->header.typeflag = GNUTYPE_VOLHDR; to_oct (time (0), 1 + 12, record_start->header.mtime); finish_header (record_start); #if 0 current_block++; #endif } break; } } /*--------------------------------------. | Perform a write to flush the buffer. | `--------------------------------------*/ void flush_write (void) { int copy_back; int status; if (checkpoint_option && !(++checkpoint % 10)) WARN ((0, 0, _("Write checkpoint %d"), checkpoint)); if (!zerop_tarlong (tape_length_option) && !lessp_tarlong (bytes_written, tape_length_option)) { errno = ENOSPC; /* FIXME: errno should be read-only */ status = 0; } else if (dev_null_output) status = record_size; else status = rmtwrite (archive, record_start->buffer, (unsigned int) record_size); if (status != record_size && !multi_volume_option) write_error (status); else if (totals_option) add_to_tarlong (total_written, record_size); if (status > 0) add_to_tarlong (bytes_written, status); if (status == record_size) { if (multi_volume_option) { char *cursor; if (!save_name) { real_s_name[0] = '\0'; real_s_totsize = 0; real_s_sizeleft = 0; return; } cursor = save_name; #if MSDOS if (cursor[1] == ':') cursor += 2; #endif while (*cursor == '/') cursor++; strcpy (real_s_name, cursor); real_s_totsize = save_totsize; real_s_sizeleft = save_sizeleft; } return; } /* We're multivol. Panic if we didn't get the right kind of response. */ /* ENXIO is for the UNIX PC. */ if (status < 0 && errno != ENOSPC && errno != EIO && errno != ENXIO) write_error (status); /* If error indicates a short write, we just move to the next tape. */ if (!new_volume (ACCESS_WRITE)) return; clear_tarlong (bytes_written); if (volume_label_option && real_s_name[0]) { copy_back = 2; record_start -= 2; } else if (volume_label_option || real_s_name[0]) { copy_back = 1; record_start--; } else copy_back = 0; if (volume_label_option) { memset ((void *) record_start, 0, BLOCKSIZE); sprintf (record_start->header.name, "%s Volume %d", volume_label_option, volno); to_oct (time (0), 1 + 12, record_start->header.mtime); record_start->header.typeflag = GNUTYPE_VOLHDR; finish_header (record_start); } if (real_s_name[0]) { int tmp; if (volume_label_option) record_start++; memset ((void *) record_start, 0, BLOCKSIZE); /* FIXME: Michael P Urban writes: [a long name file] is being written when a new volume rolls around [...] Looks like the wrong value is being preserved in real_s_name, though. */ strcpy (record_start->header.name, real_s_name); record_start->header.typeflag = GNUTYPE_MULTIVOL; to_oct ((long) real_s_sizeleft, 1 + 12, record_start->header.size); to_oct ((long) real_s_totsize - real_s_sizeleft, 1 + 12, record_start->oldgnu_header.offset); tmp = verbose_option; verbose_option = 0; finish_header (record_start); verbose_option = tmp; if (volume_label_option) record_start--; } status = rmtwrite (archive, record_start->buffer, (unsigned int) record_size); if (status != record_size) write_error (status); else if (totals_option) add_to_tarlong (total_written, record_size); add_to_tarlong (bytes_written, record_size); if (copy_back) { record_start += copy_back; memcpy ((void *) current_block, (void *) (record_start + blocking_factor - copy_back), (size_t) (copy_back * BLOCKSIZE)); current_block += copy_back; if (real_s_sizeleft >= copy_back * BLOCKSIZE) real_s_sizeleft -= copy_back * BLOCKSIZE; else if ((real_s_sizeleft + BLOCKSIZE - 1) / BLOCKSIZE <= copy_back) real_s_name[0] = '\0'; else { char *cursor = save_name; #if MSDOS if (cursor[1] == ':') cursor += 2; #endif while (*cursor == '/') cursor++; strcpy (real_s_name, cursor); real_s_sizeleft = save_sizeleft; real_s_totsize = save_totsize; } copy_back = 0; } } /*---------------------------------------------------------------------. | Handle write errors on the archive. Write errors are always fatal. | | Hitting the end of a volume does not cause a write error unless the | | write was the first record of the volume. | `---------------------------------------------------------------------*/ static void write_error (int status) { int saved_errno = errno; /* It might be useful to know how much was written before the error occured. Beware that mere printing maybe change errno value. */ if (totals_option) print_total_written (); if (status < 0) FATAL_ERROR ((0, saved_errno, _("Cannot write to %s"), *archive_name_cursor)); else FATAL_ERROR ((0, 0, _("Only wrote %u of %u bytes to %s"), status, record_size, *archive_name_cursor)); } /*-------------------------------------------------------------------. | Handle read errors on the archive. If the read should be retried, | | returns to the caller. | `-------------------------------------------------------------------*/ static void read_error (void) { WARN ((0, errno, _("Read error on %s"), *archive_name_cursor)); if (record_start_block == 0) FATAL_ERROR ((0, 0, _("At beginning of tape, quitting now"))); /* Read error in mid archive. We retry up to READ_ERROR_MAX times and then give up on reading the archive. */ if (read_error_count++ > READ_ERROR_MAX) FATAL_ERROR ((0, 0, _("Too many errors, quitting"))); return; } /*-------------------------------------. | Perform a read to flush the buffer. | `-------------------------------------*/ void flush_read (void) { int status; /* result from system call */ int left; /* bytes left */ char *more; /* pointer to next byte to read */ if (checkpoint_option && !(++checkpoint % 10)) WARN ((0, 0, _("Read checkpoint %d"), checkpoint)); /* Clear the count of errors. This only applies to a single call to flush_read. */ read_error_count = 0; /* clear error count */ if (write_archive_to_stdout && record_start_block != 0) { status = rmtwrite (1, record_start->buffer, (unsigned int) record_size); if (status != record_size) write_error (status); } if (multi_volume_option) if (save_name) { char *cursor = save_name; #if MSDOS if (cursor[1] == ':') cursor += 2; #endif while (*cursor == '/') cursor++; strcpy (real_s_name, cursor); real_s_sizeleft = save_sizeleft; real_s_totsize = save_totsize; } else { real_s_name[0] = '\0'; real_s_totsize = 0; real_s_sizeleft = 0; } error_loop: status = rmtread (archive, record_start->buffer, (unsigned int) record_size); if (status == record_size) return; if ((status == 0 || (status < 0 && errno == ENOSPC) || (status > 0 && !read_full_records_option)) && multi_volume_option) { union block *cursor; try_volume: switch (subcommand_option) { case APPEND_SUBCOMMAND: case CAT_SUBCOMMAND: case UPDATE_SUBCOMMAND: if (!new_volume (ACCESS_UPDATE)) return; break; default: if (!new_volume (ACCESS_READ)) return; break; } vol_error: status = rmtread (archive, record_start->buffer, (unsigned int) record_size); if (status < 0) { read_error (); goto vol_error; } if (status != record_size) goto short_read; cursor = record_start; if (cursor->header.typeflag == GNUTYPE_VOLHDR) { if (volume_label_option) { if (!check_label_pattern (cursor)) { WARN ((0, 0, _("Volume `%s' does not match `%s'"), cursor->header.name, volume_label_option)); volno--; global_volno--; goto try_volume; } } if (verbose_option) fprintf (stdlis, _("Reading %s\n"), cursor->header.name); cursor++; } else if (volume_label_option) WARN ((0, 0, _("WARNING: No volume header"))); if (real_s_name[0]) { if (cursor->header.typeflag != GNUTYPE_MULTIVOL || strcmp (cursor->header.name, real_s_name)) { WARN ((0, 0, _("%s is not continued on this volume"), real_s_name)); volno--; global_volno--; goto try_volume; } if (real_s_totsize != (from_oct (1 + 12, cursor->header.size) + from_oct (1 + 12, cursor->oldgnu_header.offset))) { WARN ((0, 0, _("%s is the wrong size (%ld != %ld + %ld)"), cursor->header.name, save_totsize, from_oct (1 + 12, cursor->header.size), from_oct (1 + 12, cursor->oldgnu_header.offset))); volno--; global_volno--; goto try_volume; } if (real_s_totsize - real_s_sizeleft != from_oct (1 + 12, cursor->oldgnu_header.offset)) { WARN ((0, 0, _("This volume is out of sequence"))); volno--; global_volno--; goto try_volume; } cursor++; } current_block = cursor; return; } else if (status < 0) { read_error (); goto error_loop; /* try again */ } short_read: more = record_start->buffer + status; left = record_size - status; again: if ((unsigned) left % BLOCKSIZE == 0) { /* FIXME: for size=0, multi-volume support. On the first record, warn about the problem. */ if (!read_full_records_option && verbose_option && record_start_block == 0 && status > 0) WARN ((0, 0, _("Record size = %d blocks"), status / BLOCKSIZE)); record_end = record_start + ((unsigned) (record_size - left)) / BLOCKSIZE; return; } if (read_full_records_option) { /* User warned us about this. Fix up. */ if (left > 0) { error2loop: status = rmtread (archive, more, (unsigned int) left); if (status < 0) { read_error (); goto error2loop; /* try again */ } if (status == 0) FATAL_ERROR ((0, 0, _("Archive %s EOF not on block boundary"), *archive_name_cursor)); left -= status; more += status; goto again; } } else FATAL_ERROR ((0, 0, _("Only read %d bytes from archive %s"), status, *archive_name_cursor)); } /*-----------------------------------------------. | Flush the current buffer to/from the archive. | `-----------------------------------------------*/ void flush_archive (void) { record_start_block += record_end - record_start; current_block = record_start; record_end = record_start + blocking_factor; if (access_mode == ACCESS_READ && time_to_start_writing) { access_mode = ACCESS_WRITE; time_to_start_writing = 0; if (file_to_switch_to >= 0) { int status = rmtclose (archive); if (status < 0) WARN ((0, errno, _("WARNING: Cannot close %s (%d, %d)"), *archive_name_cursor, archive, status)); archive = file_to_switch_to; } else backspace_output (); } switch (access_mode) { case ACCESS_READ: flush_read (); break; case ACCESS_WRITE: flush_write (); break; case ACCESS_UPDATE: abort (); } } /*-------------------------------------------------------------------------. | Backspace the archive descriptor by one record worth. If its a tape, | | MTIOCTOP will work. If its something else, we try to seek on it. If we | | can't seek, we lose! | `-------------------------------------------------------------------------*/ static void backspace_output (void) { #ifdef MTIOCTOP { struct mtop operation; operation.mt_op = MTBSR; operation.mt_count = 1; if (rmtioctl (archive, MTIOCTOP, (char *) &operation) >= 0) return; if (errno == EIO && rmtioctl (archive, MTIOCTOP, (char *) &operation) >= 0) return; } #endif { off_t position = rmtlseek (archive, 0L, 1); /* Seek back to the beginning of this record and start writing there. */ position -= record_size; if (rmtlseek (archive, position, 0) != position) { /* Lseek failed. Try a different method. */ WARN ((0, 0, _("\ Could not backspace archive file; it may be unreadable without -i"))); /* Replace the first part of the record with NULs. */ if (record_start->buffer != output_start) memset (record_start->buffer, 0, (size_t) (output_start - record_start->buffer)); } } } /*-------------------------. | Close the archive file. | `-------------------------*/ void close_archive (void) { if (time_to_start_writing || access_mode == ACCESS_WRITE) flush_archive (); #if !MSDOS /* Manage to fully drain a pipe we might be reading, so to not break it on the producer after the EOF block. FIXME: one of these days, GNU tar might become clever enough to just stop working, once there is no more work to do, we might have to revise this area in such time. */ if (access_mode == ACCESS_READ && S_ISFIFO (archive_stat.st_mode)) while (rmtread (archive, record_start->buffer, (unsigned int) record_size) > 0) continue; #endif if (subcommand_option == DELETE_SUBCOMMAND) { off_t pos; pos = rmtlseek (archive, 0L, 1); #if MSDOS rmtwrite (archive, "", 0); #else ftruncate (archive, (size_t) pos); #endif } if (verify_option) verify_volume (); { int status = rmtclose (archive); if (status < 0) WARN ((0, errno, _("WARNING: Cannot close %s (%d, %d)"), *archive_name_cursor, archive, status)); } #if !MSDOS if (child_pid) { WAIT_T wait_status; int child; /* Loop waiting for the right child to die, or for no more kids. */ while ((child = wait (&wait_status), child != child_pid) && child != -1) continue; if (child != -1) if (WIFSIGNALED (wait_status) #if 0 && !WIFSTOPPED (wait_status) #endif ) { /* SIGPIPE is OK, everything else is a problem. */ if (WTERMSIG (wait_status) != SIGPIPE) ERROR ((0, 0, _("Child died with signal %d%s"), WTERMSIG (wait_status), WCOREDUMP (wait_status) ? _(" (core dumped)") : "")); } else { /* Child voluntarily terminated -- but why? /bin/sh returns SIGPIPE + 128 if its child, then do nothing. */ if (WEXITSTATUS (wait_status) != (SIGPIPE + 128) && WEXITSTATUS (wait_status)) ERROR ((0, 0, _("Child returned status %d"), WEXITSTATUS (wait_status))); } } #endif /* !MSDOS */ if (current_file_name) free (current_file_name); if (current_link_name) free (current_link_name); if (save_name) free (save_name); free (multi_volume_option ? record_start - 2 : record_start); } /*------------------------------------------------. | Called to initialize the global volume number. | `------------------------------------------------*/ void init_volume_number (void) { FILE *file = fopen (volno_file_option, "r"); if (file) { fscanf (file, "%d", &global_volno); if (fclose (file) == EOF) ERROR ((0, errno, "%s", volno_file_option)); } else if (errno != ENOENT) ERROR ((0, errno, "%s", volno_file_option)); } /*-------------------------------------------------------. | Called to write out the closing global volume number. | `-------------------------------------------------------*/ void closeout_volume_number (void) { FILE *file = fopen (volno_file_option, "w"); if (file) { fprintf (file, "%d\n", global_volno); if (fclose (file) == EOF) ERROR ((0, errno, "%s", volno_file_option)); } else ERROR ((0, errno, "%s", volno_file_option)); } /*-----------------------------------------------------------------------. | We've hit the end of the old volume. Close it and open the next one. | | Return nonzero on success. | `-----------------------------------------------------------------------*/ static int new_volume (enum access_mode access) { static FILE *read_file = NULL; static int looped = 0; int status; if (!read_file && !info_script_option) /* FIXME: if fopen is used, it will never be closed. */ read_file = archive == STDIN ? fopen (TTY_NAME, "r") : stdin; if (now_verifying) return 0; if (verify_option) verify_volume (); if (status = rmtclose (archive), status < 0) WARN ((0, errno, _("WARNING: Cannot close %s (%d, %d)"), *archive_name_cursor, archive, status)); global_volno++; volno++; archive_name_cursor++; if (archive_name_cursor == archive_name_array + archive_names) { archive_name_cursor = archive_name_array; looped = 1; } tryagain: if (looped) { /* We have to prompt from now on. */ if (info_script_option) { if (volno_file_option) closeout_volume_number (); system (info_script_option); } else while (1) { char input_buffer[80]; fprintf (stderr, _("\007Prepare volume #%d for %s and hit return: "), global_volno, *archive_name_cursor); fflush (stderr); if (fgets (input_buffer, sizeof (input_buffer), read_file) == 0) { fprintf (stderr, _("EOF where user reply was expected")); if (subcommand_option != EXTRACT_SUBCOMMAND && subcommand_option != LIST_SUBCOMMAND && subcommand_option != DIFF_SUBCOMMAND) WARN ((0, 0, _("WARNING: Archive is incomplete"))); exit (TAREXIT_FAILURE); } if (input_buffer[0] == '\n' || input_buffer[0] == 'y' || input_buffer[0] == 'Y') break; switch (input_buffer[0]) { case '?': { fprintf (stderr, _("\ n [name] Give a new file name for the next (and subsequent) volume(s)\n\ q Abort tar\n\ ! Spawn a subshell\n\ ? Print this list\n")); } break; case 'q': /* Quit. */ fprintf (stdlis, _("No new volume; exiting.\n")); if (subcommand_option != EXTRACT_SUBCOMMAND && subcommand_option != LIST_SUBCOMMAND && subcommand_option != DIFF_SUBCOMMAND) WARN ((0, 0, _("WARNING: Archive is incomplete"))); exit (TAREXIT_FAILURE); case 'n': /* Get new file name. */ { char *name = &input_buffer[1]; char *cursor; while (*name == ' ' || *name == '\t') name++; cursor = name; while (*cursor && *cursor != '\n') cursor++; *cursor = '\0'; /* FIXME: the following allocation is never reclaimed. */ *archive_name_cursor = xstrdup (name); } break; case '!': #if MSDOS spawnl (P_WAIT, getenv ("COMSPEC"), "-", 0); #else /* not MSDOS */ switch (fork ()) { case -1: WARN ((0, errno, _("Cannot fork!"))); break; case 0: { const char *shell = getenv ("SHELL"); if (shell == NULL) shell = "/bin/sh"; execlp (shell, "-sh", "-i", 0); FATAL_ERROR ((0, errno, _("Cannot exec a shell %s"), shell)); } default: { WAIT_T wait_status; wait (&wait_status); } break; } /* FIXME: I'm not sure if that's all that has to be done here. (jk) */ #endif /* not MSDOS */ break; } } } if (verify_option) archive = rmtopen (*archive_name_cursor, O_RDWR | O_CREAT, 0666, rsh_command_option); else switch (access) { case ACCESS_READ: archive = rmtopen (*archive_name_cursor, O_RDONLY, 0666, rsh_command_option); break; case ACCESS_WRITE: if (backup_option) maybe_backup_file (*archive_name_cursor, 1); archive = rmtcreat (*archive_name_cursor, 0666, rsh_command_option); break; case ACCESS_UPDATE: archive = rmtopen (*archive_name_cursor, O_RDWR | O_CREAT, 0666, rsh_command_option); break; } if (archive < 0) { WARN ((0, errno, _("Cannot open %s"), *archive_name_cursor)); if (!verify_option && access == ACCESS_WRITE && backup_option) undo_last_backup (); goto tryagain; } #if MSDOS setmode (archive, O_BINARY); #endif return 1; }
/* Diff files from a tar archive. Copyright (C) 1988, 92, 93, 94, 96, 97 Free Software Foundation, Inc. Written by John Gilmore, on 1987-04-30. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "system.h" #if HAVE_LINUX_FD_H # include <linux/fd.h> #endif #include "common.h" #include "rmt.h" /* Spare space for messages, hopefully safe even after gettext. */ #define MESSAGE_BUFFER_SIZE 100 /* Nonzero if we are verifying at the moment. */ int now_verifying = 0; /* File descriptor for the file we are diffing. */ static int diff_handle; /* Area for reading file contents into. */ static char *diff_buffer = NULL; /*--------------------------------. | Initialize for a diff operation | `--------------------------------*/ void diff_init (void) { diff_buffer = (char *) valloc ((unsigned) record_size); if (!diff_buffer) FATAL_ERROR ((0, 0, _("Could not allocate memory for diff buffer of %d bytes"), record_size)); } /*------------------------------------------------------------------------. | Sigh about something that differs by writing a MESSAGE to stdlis, given | | MESSAGE is not NULL. Also set the exit status if not already. | `------------------------------------------------------------------------*/ static void report_difference (const char *message) { if (message) fprintf (stdlis, "%s: %s\n", current_file_name, message); if (exit_status == TAREXIT_SUCCESS) exit_status = TAREXIT_DIFFERS; } /*-----------------------------------------------------------------------. | Takes a buffer returned by read_and_process and does nothing with it. | `-----------------------------------------------------------------------*/ /* Yes, I know. SIZE and DATA are unused in this function. Some compilers may even report it. That's OK, just relax! */ static int process_noop (long size, char *data) { return 1; } /*---. | ? | `---*/ static int process_rawdata (long bytes, char *buffer) { int status = read (diff_handle, diff_buffer, (size_t) bytes); char message[MESSAGE_BUFFER_SIZE]; if (status != bytes) { if (status < 0) { WARN ((0, errno, _("Cannot read %s"), current_file_name)); report_difference (NULL); } else { sprintf (message, _("Could only read %d of %ld bytes"), status, bytes); report_difference (message); } return 0; } if (memcmp (buffer, diff_buffer, (size_t) bytes)) { report_difference (_("Data differs")); return 0; } return 1; } /*---. | ? | `---*/ /* Directory contents, only for GNUTYPE_DUMPDIR. */ static char *dumpdir_cursor; static int process_dumpdir (long bytes, char *buffer) { if (memcmp (buffer, dumpdir_cursor, (size_t) bytes)) { report_difference (_("Data differs")); return 0; } dumpdir_cursor += bytes; return 1; } /*------------------------------------------------------------------------. | Some other routine wants SIZE bytes in the archive. For each chunk of | | the archive, call PROCESSOR with the size of the chunk, and the address | | of the chunk it can work with. The PROCESSOR should return nonzero for | | success. It it return error once, continue skipping without calling | | PROCESSOR anymore. | `------------------------------------------------------------------------*/ static void read_and_process (long size, int (*processor) (long, char *)) { union block *data_block; long data_size; if (multi_volume_option) save_sizeleft = size; while (size) { data_block = find_next_block (); if (data_block == NULL) { ERROR ((0, 0, _("Unexpected EOF on archive file"))); return; } data_size = available_space_after (data_block); if (data_size > size) data_size = size; if (!(*processor) (data_size, data_block->buffer)) processor = process_noop; set_next_block_after ((union block *) (data_block->buffer + data_size - 1)); size -= data_size; if (multi_volume_option) save_sizeleft -= data_size; } } /*---. | ? | `---*/ /* JK This routine should be used more often than it is ... look into that. Anyhow, what it does is translate the sparse information on the header, and in any subsequent extended headers, into an array of structures with true numbers, as opposed to character strings. It simply makes our life much easier, doing so many comparisong and such. */ static void fill_in_sparse_array (void) { int counter; /* Allocate space for our scratch space; it's initially 10 elements long, but can change in this routine if necessary. */ sp_array_size = 10; sparsearray = (struct sp_array *) xmalloc (sp_array_size * sizeof (struct sp_array)); /* There are at most five of these structures in the header itself; read these in first. */ for (counter = 0; counter < SPARSES_IN_OLDGNU_HEADER; counter++) { /* Compare to 0, or use !(int)..., for Pyramid's dumb compiler. */ if (current_header->oldgnu_header.sp[counter].numbytes == 0) break; sparsearray[counter].offset = from_oct (1 + 12, current_header->oldgnu_header.sp[counter].offset); sparsearray[counter].numbytes = from_oct (1 + 12, current_header->oldgnu_header.sp[counter].numbytes); } /* If the header's extended, we gotta read in exhdr's till we're done. */ if (current_header->oldgnu_header.isextended) { /* How far into the sparsearray we are `so far'. */ static int so_far_ind = SPARSES_IN_OLDGNU_HEADER; union block *exhdr; while (1) { exhdr = find_next_block (); for (counter = 0; counter < SPARSES_IN_SPARSE_HEADER; counter++) { if (counter + so_far_ind > sp_array_size - 1) { /* We just ran out of room in our scratch area - realloc it. */ sp_array_size *= 2; sparsearray = (struct sp_array *) xrealloc (sparsearray, sp_array_size * sizeof (struct sp_array)); } /* Convert the character strings into longs. */ sparsearray[counter + so_far_ind].offset = from_oct (1 + 12, exhdr->sparse_header.sp[counter].offset); sparsearray[counter + so_far_ind].numbytes = from_oct (1 + 12, exhdr->sparse_header.sp[counter].numbytes); } /* If this is the last extended header for this file, we can stop. */ if (!exhdr->sparse_header.isextended) break; so_far_ind += SPARSES_IN_SPARSE_HEADER; set_next_block_after (exhdr); } /* Be sure to skip past the last one. */ set_next_block_after (exhdr); } } /*---. | ? | `---*/ /* JK Diff'ing a sparse file with its counterpart on the tar file is a bit of a different story than a normal file. First, we must know what areas of the file to skip through, i.e., we need to contruct a sparsearray, which will hold all the information we need. We must compare small amounts of data at a time as we find it. */ /* FIXME: This does not look very solid to me, at first glance. Zero areas are not checked, spurious sparse entries seemingly goes undetected, and I'm not sure overall identical sparsity is verified. */ static void diff_sparse_files (int size_of_file) { int remaining_size = size_of_file; char *buffer = (char *) xmalloc (BLOCKSIZE * sizeof (char)); int buffer_size = BLOCKSIZE; union block *data_block = NULL; int counter = 0; int different = 0; fill_in_sparse_array (); while (remaining_size > 0) { int status; long chunk_size; #if 0 int amount_read = 0; #endif data_block = find_next_block (); chunk_size = sparsearray[counter].numbytes; if (!chunk_size) break; lseek (diff_handle, sparsearray[counter].offset, 0); /* Take care to not run out of room in our buffer. */ while (buffer_size < chunk_size) { buffer_size *= 2; buffer = (char *) xrealloc (buffer, buffer_size * sizeof (char)); } while (chunk_size > BLOCKSIZE) { if (status = read (diff_handle, buffer, BLOCKSIZE), status != BLOCKSIZE) { if (status < 0) { WARN ((0, errno, _("Cannot read %s"), current_file_name)); report_difference (NULL); } else { char message[MESSAGE_BUFFER_SIZE]; sprintf (message, _("Could only read %d of %ld bytes"), status, chunk_size); report_difference (message); } break; } if (memcmp (buffer, data_block->buffer, BLOCKSIZE)) { different = 1; break; } chunk_size -= status; remaining_size -= status; set_next_block_after (data_block); data_block = find_next_block (); } if (status = read (diff_handle, buffer, (size_t) chunk_size), status != chunk_size) { if (status < 0) { WARN ((0, errno, _("Cannot read %s"), current_file_name)); report_difference (NULL); } else { char message[MESSAGE_BUFFER_SIZE]; sprintf (message, _("Could only read %d of %ld bytes"), status, chunk_size); report_difference (message); } break; } if (memcmp (buffer, data_block->buffer, (size_t) chunk_size)) { different = 1; break; } #if 0 amount_read += chunk_size; if (amount_read >= BLOCKSIZE) { amount_read = 0; set_next_block_after (data_block); data_block = find_next_block (); } #endif set_next_block_after (data_block); counter++; remaining_size -= chunk_size; } #if 0 /* If the number of bytes read isn't the number of bytes supposedly in the file, they're different. */ if (amount_read != size_of_file) different = 1; #endif set_next_block_after (data_block); free (sparsearray); if (different) report_difference (_("Data differs")); } /*---------------------------------------------------------------------. | Call either stat or lstat over STAT_DATA, depending on --dereference | | (-h), for a file which should exist. Diagnose any problem. Return | | nonzero for success, zero otherwise. | `---------------------------------------------------------------------*/ static int get_stat_data (struct stat *stat_data) { int status = (dereference_option ? stat (current_file_name, stat_data) : lstat (current_file_name, stat_data)); if (status < 0) { if (errno == ENOENT) report_difference (_("File does not exist")); else { ERROR ((0, errno, _("Cannot stat file %s"), current_file_name)); report_difference (NULL); } #if 0 skip_file ((long) current_stat.st_size); #endif return 0; } return 1; } /*----------------------------------. | Diff a file against the archive. | `----------------------------------*/ void diff_archive (void) { struct stat stat_data; int name_length; int status; errno = EPIPE; /* FIXME: errno should be read-only */ /* FIXME: remove perrors */ set_next_block_after (current_header); decode_header (current_header, ¤t_stat, ¤t_format, 1); /* Print the block from `current_header' and `current_stat'. */ if (verbose_option) { if (now_verifying) fprintf (stdlis, _("Verify ")); print_header (); } switch (current_header->header.typeflag) { default: WARN ((0, 0, _("Unknown file type '%c' for %s, diffed as normal file"), current_header->header.typeflag, current_file_name)); /* Fall through. */ case AREGTYPE: case REGTYPE: case GNUTYPE_SPARSE: case CONTTYPE: /* Appears to be a file. See if it's really a directory. */ name_length = strlen (current_file_name) - 1; if (current_file_name[name_length] == '/') goto really_dir; if (!get_stat_data (&stat_data)) { if (current_header->oldgnu_header.isextended) skip_extended_headers (); skip_file ((long) current_stat.st_size); goto quit; } if (!S_ISREG (stat_data.st_mode)) { report_difference (_("Not a regular file")); skip_file ((long) current_stat.st_size); goto quit; } stat_data.st_mode &= 07777; if (stat_data.st_mode != current_stat.st_mode) report_difference (_("Mode differs")); #if !MSDOS /* stat() in djgpp's C library gives a constant number of 42 as the uid and gid of a file. So, comparing an FTP'ed archive just after unpack would fail on MSDOS. */ if (stat_data.st_uid != current_stat.st_uid) report_difference (_("Uid differs")); if (stat_data.st_gid != current_stat.st_gid) report_difference (_("Gid differs")); #endif if (stat_data.st_mtime != current_stat.st_mtime) report_difference (_("Mod time differs")); if (current_header->header.typeflag != GNUTYPE_SPARSE && stat_data.st_size != current_stat.st_size) { report_difference (_("Size differs")); skip_file ((long) current_stat.st_size); goto quit; } diff_handle = open (current_file_name, O_NDELAY | O_RDONLY | O_BINARY); if (diff_handle < 0 && !absolute_names_option) { char *tmpbuf = xmalloc (strlen (current_file_name) + 2); *tmpbuf = '/'; strcpy (tmpbuf + 1, current_file_name); diff_handle = open (tmpbuf, O_NDELAY | O_RDONLY); free (tmpbuf); } if (diff_handle < 0) { ERROR ((0, errno, _("Cannot open %s"), current_file_name)); if (current_header->oldgnu_header.isextended) skip_extended_headers (); skip_file ((long) current_stat.st_size); report_difference (NULL); goto quit; } /* Need to treat sparse files completely differently here. */ if (current_header->header.typeflag == GNUTYPE_SPARSE) diff_sparse_files (current_stat.st_size); else { if (multi_volume_option) { assign_string (&save_name, current_file_name); save_totsize = current_stat.st_size; /* save_sizeleft is set in read_and_process. */ } read_and_process ((long) (current_stat.st_size), process_rawdata); if (multi_volume_option) assign_string (&save_name, NULL); } status = close (diff_handle); if (status < 0) ERROR ((0, errno, _("Error while closing %s"), current_file_name)); quit: break; #if !MSDOS case LNKTYPE: { dev_t dev; ino_t ino; if (!get_stat_data (&stat_data)) break; dev = stat_data.st_dev; ino = stat_data.st_ino; status = stat (current_link_name, &stat_data); if (status < 0) { if (errno == ENOENT) report_difference (_("Does not exist")); else { WARN ((0, errno, _("Cannot stat file %s"), current_file_name)); report_difference (NULL); } break; } if (stat_data.st_dev != dev || stat_data.st_ino != ino) { char *message = (char *) xmalloc (MESSAGE_BUFFER_SIZE + strlen (current_link_name)); sprintf (message, _("Not linked to %s"), current_link_name); report_difference (message); free (message); break; } break; } #endif /* not MSDOS */ #ifdef S_ISLNK case SYMTYPE: { char linkbuf[NAME_FIELD_SIZE + 3]; /* FIXME: may be too short. */ status = readlink (current_file_name, linkbuf, (sizeof linkbuf) - 1); if (status < 0) { if (errno == ENOENT) report_difference (_("No such file or directory")); else { WARN ((0, errno, _("Cannot read link %s"), current_file_name)); report_difference (NULL); } break; } linkbuf[status] = '\0'; /* null-terminate it */ if (strncmp (current_link_name, linkbuf, (size_t) status) != 0) report_difference (_("Symlink differs")); break; } #endif /* not S_ISLNK */ #ifdef S_IFCHR case CHRTYPE: current_stat.st_mode |= S_IFCHR; goto check_node; #endif /* not S_IFCHR */ #ifdef S_IFBLK /* If local system doesn't support block devices, use default case. */ case BLKTYPE: current_stat.st_mode |= S_IFBLK; goto check_node; #endif /* not S_IFBLK */ #ifdef S_ISFIFO /* If local system doesn't support FIFOs, use default case. */ case FIFOTYPE: # ifdef S_IFIFO current_stat.st_mode |= S_IFIFO; # endif current_stat.st_rdev = 0; /* FIXME: do we need this? */ goto check_node; #endif /* S_ISFIFO */ check_node: /* FIXME: deal with umask. */ if (!get_stat_data (&stat_data)) break; if (current_stat.st_rdev != stat_data.st_rdev) { report_difference (_("Device numbers changed")); break; } if ( #ifdef S_IFMT current_stat.st_mode != stat_data.st_mode #else /* POSIX lossage. */ (current_stat.st_mode & 07777) != (stat_data.st_mode & 07777) #endif ) { report_difference (_("Mode or device-type changed")); break; } break; case GNUTYPE_DUMPDIR: { char *dumpdir_buffer = get_directory_contents (current_file_name, 0); if (multi_volume_option) { assign_string (&save_name, current_file_name); save_totsize = current_stat.st_size; /* save_sizeleft is set in read_and_process. */ } if (dumpdir_buffer) { dumpdir_cursor = dumpdir_buffer; read_and_process ((long) (current_stat.st_size), process_dumpdir); free (dumpdir_buffer); } else read_and_process ((long) (current_stat.st_size), process_noop); if (multi_volume_option) assign_string (&save_name, NULL); /* Fall through. */ } case DIRTYPE: /* Check for trailing /. */ name_length = strlen (current_file_name) - 1; really_dir: while (name_length && current_file_name[name_length] == '/') current_file_name[name_length--] = '\0'; /* zap / */ if (!get_stat_data (&stat_data)) break; if (!S_ISDIR (stat_data.st_mode)) { report_difference (_("No longer a directory")); break; } if ((stat_data.st_mode & 07777) != (current_stat.st_mode & 07777)) report_difference (_("Mode differs")); break; case GNUTYPE_VOLHDR: break; case GNUTYPE_MULTIVOL: { off_t offset; name_length = strlen (current_file_name) - 1; if (current_file_name[name_length] == '/') goto really_dir; if (!get_stat_data (&stat_data)) break; if (!S_ISREG (stat_data.st_mode)) { report_difference (_("Not a regular file")); skip_file ((long) current_stat.st_size); break; } stat_data.st_mode &= 07777; offset = from_oct (1 + 12, current_header->oldgnu_header.offset); if (stat_data.st_size != current_stat.st_size + offset) { report_difference (_("Size differs")); skip_file ((long) current_stat.st_size); break; } diff_handle = open (current_file_name, O_NDELAY | O_RDONLY | O_BINARY); if (diff_handle < 0) { WARN ((0, errno, _("Cannot open file %s"), current_file_name)); report_difference (NULL); skip_file ((long) current_stat.st_size); break; } status = lseek (diff_handle, offset, 0); if (status != offset) { WARN ((0, errno, _("Cannot seek to %ld in file %s"), offset, current_file_name)); report_difference (NULL); break; } if (multi_volume_option) { assign_string (&save_name, current_file_name); save_totsize = stat_data.st_size; /* save_sizeleft is set in read_and_process. */ } read_and_process ((long) (current_stat.st_size), process_rawdata); if (multi_volume_option) assign_string (&save_name, NULL); status = close (diff_handle); if (status < 0) ERROR ((0, errno, _("Error while closing %s"), current_file_name)); break; } } } /*---. | ? | `---*/ void verify_volume (void) { if (!diff_buffer) diff_init (); /* Verifying an archive is meant to check if the physical media got it correctly, so try to defeat clever in-memory buffering pertaining to this particular media. On Linux, for example, the floppy drive would not even be accessed for the whole verification. The code was using fsync only when the ioctl is unavailable, but Marty Leisner says that the ioctl does not work when not preceded by fsync. So, until we know better, or maybe to please Marty, let's do it the unbelievable way :-). */ #if HAVE_FSYNC fsync (archive); #endif #ifdef FDFLUSH ioctl (archive, FDFLUSH); #endif #ifdef MTIOCTOP { struct mtop operation; int status; operation.mt_op = MTBSF; operation.mt_count = 1; if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0) { if (errno != EIO || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)) { #endif if (rmtlseek (archive, 0L, 0) != 0) { /* Lseek failed. Try a different method. */ WARN ((0, errno, _("Could not rewind archive file for verify"))); return; } #ifdef MTIOCTOP } } } #endif access_mode = ACCESS_READ; now_verifying = 1; flush_read (); while (1) { enum read_header status = read_header (); if (status == HEADER_FAILURE) { int counter = 0; while (status == HEADER_FAILURE); { counter++; status = read_header (); } ERROR ((0, 0, _("VERIFY FAILURE: %d invalid header(s) detected"), counter)); } if (status == HEADER_ZERO_BLOCK || status == HEADER_END_OF_FILE) break; diff_archive (); } access_mode = ACCESS_WRITE; now_verifying = 0; }
/* Delete entries from a tar archive. Copyright (C) 1988, 1992, 1994, 1996, 1997 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "system.h" #define STDIN 0 #define STDOUT 1 #include "common.h" #include "rmt.h" static union block *new_record = NULL; static union block *save_record = NULL; static int records_read = 0; static int new_blocks = 0; static int blocks_needed = 0; /* FIXME: This module should not directly handle the following three variables, instead, this should be done in buffer.c only. */ extern union block *record_start; extern union block *record_end; extern union block *current_block; /*-------------------------------------------------------------------------. | Move archive descriptor by COUNT records worth. If COUNT is positive we | | move forward, else we move negative. If its a tape, MTIOCTOP had better | | work. If its something else, we try to seek on it. If we can't seek, | | we loose! | `-------------------------------------------------------------------------*/ static void move_archive (int count) { #ifdef MTIOCTOP { struct mtop operation; int status; if (count > 0) { operation.mt_op = MTFSR; operation.mt_count = count; } else { operation.mt_op = MTBSR; operation.mt_count = -count; } if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status >= 0) return; if (errno == EIO) if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status >= 0) return; } #endif /* MTIOCTOP */ { off_t position = rmtlseek (archive, 0L, 1); position += record_size * count; if (rmtlseek (archive, position, 0) != position) FATAL_ERROR ((0, 0, _("Could not re-position archive file"))); return; } } /*----------------------------------------------------------------. | Write out the record which has been filled. If MOVE_BACK_FLAG, | | backspace to where we started. | `----------------------------------------------------------------*/ static void write_record (int move_back_flag) { save_record = record_start; record_start = new_record; if (archive == STDIN) { archive = STDOUT; flush_write (); archive = STDIN; } else { move_archive (-(records_read + 1)); flush_write (); } record_start = save_record; if (move_back_flag) { /* Move the tape head back to where we were. */ if (archive != STDIN) move_archive (records_read); records_read--; } blocks_needed = blocking_factor; new_blocks = 0; } /*---. | ? | `---*/ void delete_archive_members (void) { enum read_header logical_status = HEADER_STILL_UNREAD; enum read_header previous_status = HEADER_STILL_UNREAD; /* FIXME: Should clean the routine before cleaning these variables :-( */ struct name *name; int blocks_to_skip = 0; int blocks_to_keep = 0; int kept_blocks_in_record; name_gather (); open_archive (ACCESS_UPDATE); while (logical_status == HEADER_STILL_UNREAD) { enum read_header status = read_header (); switch (status) { case HEADER_STILL_UNREAD: abort (); case HEADER_SUCCESS: if (name = name_scan (current_file_name), !name) { set_next_block_after (current_header); if (current_header->oldgnu_header.isextended) skip_extended_headers (); skip_file ((long) (current_stat.st_size)); break; } name->found = 1; logical_status = HEADER_SUCCESS; break; case HEADER_ZERO_BLOCK: case HEADER_END_OF_FILE: logical_status = HEADER_END_OF_FILE; break; case HEADER_FAILURE: set_next_block_after (current_header); switch (previous_status) { case HEADER_STILL_UNREAD: WARN ((0, 0, _("This does not look like a tar archive"))); /* Fall through. */ case HEADER_SUCCESS: case HEADER_ZERO_BLOCK: ERROR ((0, 0, _("Skipping to next header"))); /* Fall through. */ case HEADER_FAILURE: break; case HEADER_END_OF_FILE: abort (); } break; } previous_status = status; } if (logical_status != HEADER_SUCCESS) { write_eot (); close_archive (); names_notfound (); return; } write_archive_to_stdout = 0; new_record = (union block *) xmalloc ((size_t) record_size); /* Save away blocks before this one in this record. */ new_blocks = current_block - record_start; blocks_needed = blocking_factor - new_blocks; if (new_blocks) memcpy ((void *) new_record, (void *) record_start, (size_t) (new_blocks * BLOCKSIZE)); #if 0 /* FIXME: Old code, before the goto was inserted. To be redesigned. */ set_next_block_after (current_header); if (current_header->oldgnu_header.isextended) skip_extended_headers (); skip_file ((long) (current_stat.st_size)); #endif logical_status = HEADER_STILL_UNREAD; goto flush_file; /* FIXME: Solaris 2.4 Sun cc (the ANSI one, not the old K&R) says: "delete.c", line 223: warning: loop not entered at top Reported by Bruno Haible. */ while (1) { enum read_header status; /* Fill in a record. */ if (current_block == record_end) { flush_archive (); records_read++; } status = read_header (); if (status == HEADER_ZERO_BLOCK && ignore_zeros_option) { set_next_block_after (current_header); continue; } if (status == HEADER_END_OF_FILE || status == HEADER_ZERO_BLOCK) { logical_status = HEADER_END_OF_FILE; memset (new_record[new_blocks].buffer, 0, (size_t) (BLOCKSIZE * blocks_needed)); new_blocks += blocks_needed; blocks_needed = 0; write_record (0); break; } if (status == HEADER_FAILURE) { ERROR ((0, 0, _("Deleting non-header from archive"))); set_next_block_after (current_header); continue; } /* Found another header. */ if (name = name_scan (current_file_name), name) { name->found = 1; flush_file: set_next_block_after (current_header); blocks_to_skip = (current_stat.st_size + BLOCKSIZE - 1) / BLOCKSIZE; while (record_end - current_block <= blocks_to_skip) { blocks_to_skip -= (record_end - current_block); flush_archive (); records_read++; } current_block += blocks_to_skip; blocks_to_skip = 0; continue; } /* Copy header. */ new_record[new_blocks] = *current_header; new_blocks++; blocks_needed--; blocks_to_keep = (current_stat.st_size + BLOCKSIZE - 1) / BLOCKSIZE; set_next_block_after (current_header); if (blocks_needed == 0) write_record (1); /* Copy data. */ kept_blocks_in_record = record_end - current_block; if (kept_blocks_in_record > blocks_to_keep) kept_blocks_in_record = blocks_to_keep; while (blocks_to_keep) { int count; if (current_block == record_end) { flush_read (); records_read++; current_block = record_start; kept_blocks_in_record = blocking_factor; if (kept_blocks_in_record > blocks_to_keep) kept_blocks_in_record = blocks_to_keep; } count = kept_blocks_in_record; if (count > blocks_needed) count = blocks_needed; memcpy ((void *) (new_record + new_blocks), (void *) current_block, (size_t) (count * BLOCKSIZE)); new_blocks += count; blocks_needed -= count; current_block += count; blocks_to_keep -= count; kept_blocks_in_record -= count; if (blocks_needed == 0) write_record (1); } } write_eot (); close_archive (); names_notfound (); }
/* Definitions for communicating with a remote tape drive. Copyright (C) 1988, 1992, 1996, 1997 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ extern char *rmt_path__; int rmt_open__ PARAMS ((const char *, int, int, const char *)); int rmt_close__ PARAMS ((int)); int rmt_read__ PARAMS ((int, char *, unsigned int)); int rmt_write__ PARAMS ((int, char *, unsigned int)); long rmt_lseek__ PARAMS ((int, off_t, int)); int rmt_ioctl__ PARAMS ((int, int, char *)); /* A filename is remote if it contains a colon not preceeded by a slash, to take care of `/:/' which is a shorthand for `/.../<CELL-NAME>/fs' on machines running OSF's Distributing Computing Environment (DCE) and Distributed File System (DFS). However, when --force-local, a filename is never remote. */ #define _remdev(Path) \ (!force_local_option && (rmt_path__ = strchr (Path, ':')) \ && rmt_path__ > (Path) && rmt_path__[-1] != '/') #define _isrmt(Fd) \ ((Fd) >= __REM_BIAS) #define __REM_BIAS 128 #ifndef O_CREAT # define O_CREAT 01000 #endif #define rmtopen(Path, Oflag, Mode, Command) \ (_remdev (Path) ? rmt_open__ (Path, Oflag, __REM_BIAS, Command) \ : open (Path, Oflag, Mode)) #define rmtaccess(Path, Amode) \ (_remdev (Path) ? 0 : access (Path, Amode)) #define rmtstat(Path, Buffer) \ (_remdev (Path) ? (errno = EOPNOTSUPP), -1 : stat (Path, Buffer)) /* FIXME: errno should be read-only */ #define rmtcreat(Path, Mode, Command) \ (_remdev (Path) \ ? rmt_open__ (Path, 1 | O_CREAT, __REM_BIAS, Command) \ : creat (Path, Mode)) #define rmtlstat(Path, Buffer) \ (_remdev (Path) ? (errno = EOPNOTSUPP), -1 : lstat (Path, Buffer)) /* FIXME: errno should be read-only */ #define rmtread(Fd, Buffer, Length) \ (_isrmt (Fd) ? rmt_read__ (Fd - __REM_BIAS, Buffer, Length) \ : read (Fd, Buffer, Length)) #define rmtwrite(Fd, Buffer, Length) \ (_isrmt (Fd) ? rmt_write__ (Fd - __REM_BIAS, Buffer, Length) \ : write (Fd, Buffer, Length)) #define rmtlseek(Fd, Offset, Where) \ (_isrmt (Fd) ? rmt_lseek__ (Fd - __REM_BIAS, Offset, Where) \ : lseek (Fd, Offset, Where)) #define rmtclose(Fd) \ (_isrmt (Fd) ? rmt_close__ (Fd - __REM_BIAS) : close (Fd)) #define rmtioctl(Fd, Request, Argument) \ (_isrmt (Fd) ? rmt_ioctl__ (Fd - __REM_BIAS, Request, Argument) \ : ioctl (Fd, Request, Argument)) #define rmtdup(Fd) \ (_isrmt (Fd) ? (errno = EOPNOTSUPP), -1 : dup (Fd)) /* FIXME: errno should be read-only */ #define rmtfstat(Fd, Buffer) \ (_isrmt (Fd) ? (errno = EOPNOTSUPP), -1 : fstat (Fd, Buffer)) /* FIXME: errno should be read-only */ #define rmtfcntl(Fd, Command, Argument) \ (_isrmt (Fd) ? (errno = EOPNOTSUPP), -1 : fcntl (Fd, Command, Argument)) /* FIXME: errno should be read-only */ #define rmtisatty(Fd) \ (_isrmt (Fd) ? 0 : isatty (Fd))
/* Extract files from a tar archive. Copyright (C) 1988, 92, 93, 94, 96, 97 Free Software Foundation, Inc. Written by John Gilmore, on 1985-11-19. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "system.h" #include <time.h> time_t time (); #if HAVE_UTIME_H # include <utime.h> #else struct utimbuf { long actime; long modtime; }; #endif #include "common.h" static time_t now; /* current time */ static int we_are_root; /* true if our effective uid == 0 */ static int newdir_umask; /* umask when creating new directories */ static int current_umask; /* current umask (which is set to 0 if -p) */ #if 0 /* "Scratch" space to store the information about a sparse file before writing the info into the header or extended header. */ struct sp_array *sparsearray; /* Number of elts storable in the sparsearray. */ int sp_array_size = 10; #endif struct delayed_set_stat { struct delayed_set_stat *next; char *file_name; struct stat stat_info; }; static struct delayed_set_stat *delayed_set_stat_head; /*--------------------------. | Set up to extract files. | `--------------------------*/ void extr_init (void) { now = time ((time_t *) 0); we_are_root = geteuid () == 0; /* Option -p clears the kernel umask, so it does not affect proper restoration of file permissions. New intermediate directories will comply with umask at start of program. */ newdir_umask = umask (0); if (same_permissions_option) current_umask = 0; else { umask (newdir_umask); /* restore the kernel umask */ current_umask = newdir_umask; } /* FIXME: Just make sure we can add files in directories we create. Maybe should we later remove permissions we are adding, here? */ newdir_umask &= ~0300; } /*------------------------------------------------------------------. | Restore mode for FILE_NAME, from information given in STAT_INFO. | `------------------------------------------------------------------*/ static void set_mode (char *file_name, struct stat *stat_info) { /* We ought to force permission when -k is not selected, because if the file already existed, open or creat would save the permission bits from the previously created file, ignoring the ones we specified. But with -k selected, we know *we* created this file, so the mode bits were set by our open. If the file has abnormal mode bits, we must chmod since writing or chown has probably reset them. If the file is normal, we merely skip the chmod. This works because we did umask (0) when -p, so umask will have left the specified mode alone. */ if (!keep_old_files_option || (stat_info->st_mode & (S_ISUID | S_ISGID | S_ISVTX))) if (chmod (file_name, ~current_umask & (int) stat_info->st_mode) < 0) ERROR ((0, errno, _("%s: Cannot change mode to %0.4o"), file_name, ~current_umask & (int) stat_info->st_mode)); } /*----------------------------------------------------------------------. | Restore stat attributes (owner, group, mode and times) for FILE_NAME, | | using information given in STAT_INFO. SYMLINK_FLAG is non-zero for a | | freshly restored symbolic link. | `----------------------------------------------------------------------*/ /* FIXME: About proper restoration of symbolic link attributes, we still do not have it right. Pretesters' reports tell us we need further study and probably more configuration. For now, just use lchown if it exists, and punt for the rest. Sigh! */ static void set_stat (char *file_name, struct stat *stat_info, int symlink_flag) { struct utimbuf utimbuf; if (!symlink_flag) { /* We do the utime before the chmod because some versions of utime are broken and trash the modes of the file. */ if (!touch_option) { /* We set the accessed time to `now', which is really the time we started extracting files, unless incremental_option is used, in which case .st_atime is used. */ /* FIXME: incremental_option should set ctime too, but how? */ if (incremental_option) utimbuf.actime = stat_info->st_atime; else utimbuf.actime = now; utimbuf.modtime = stat_info->st_mtime; if (utime (file_name, &utimbuf) < 0) ERROR ((0, errno, _("%s: Could not change access and modification times"), file_name)); } /* Some systems allow non-root users to give files away. Once this done, it is not possible anymore to change file permissions, so we have to set permissions prior to possibly giving files away. */ set_mode (file_name, stat_info); } /* If we are root, set the owner and group of the extracted file, so we extract as the original owner. Or else, if we are running as a user, leave the owner and group as they are, so we extract as that user. */ if (we_are_root || same_owner_option) { #if HAVE_LCHOWN /* When lchown exists, it should be used to change the attributes of the symbolic link itself. In this case, a mere chown would change the attributes of the file the symbolic link is pointing to, and should be avoided. */ if (symlink_flag) { if (lchown (file_name, stat_info->st_uid, stat_info->st_gid) < 0) ERROR ((0, errno, _("%s: Cannot lchown to uid %d gid %d"), file_name, stat_info->st_uid, stat_info->st_gid)); } else { if (chown (file_name, stat_info->st_uid, stat_info->st_gid) < 0) ERROR ((0, errno, _("%s: Cannot chown to uid %d gid %d"), file_name, stat_info->st_uid, stat_info->st_gid)); } #else /* not HAVE_LCHOWN */ if (!symlink_flag) if (chown (file_name, stat_info->st_uid, stat_info->st_gid) < 0) ERROR ((0, errno, _("%s: Cannot chown to uid %d gid %d"), file_name, stat_info->st_uid, stat_info->st_gid)); #endif/* not HAVE_LCHOWN */ if (!symlink_flag) /* On a few systems, and in particular, those allowing to give files away, changing the owner or group destroys the suid or sgid bits. So, when root, let's attempt setting these bits once more. */ if (we_are_root && (stat_info->st_mode & (S_ISUID | S_ISGID | S_ISVTX))) set_mode (file_name, stat_info); } } /*-----------------------------------------------------------------------. | After a file/link/symlink/directory creation has failed, see if it's | | because some required directory was not present, and if so, create all | | required directories. Return non-zero if a directory was created. | `-----------------------------------------------------------------------*/ static int make_directories (char *file_name) { char *cursor; /* points into path */ int did_something = 0; /* did we do anything yet? */ int saved_errno = errno; /* remember caller's errno */ int status; for (cursor = strchr (file_name, '/'); cursor != NULL; cursor = strchr (cursor + 1, '/')) { /* Avoid mkdir of empty string, if leading or double '/'. */ if (cursor == file_name || cursor[-1] == '/') continue; /* Avoid mkdir where last part of path is '.'. */ if (cursor[-1] == '.' && (cursor == file_name + 1 || cursor[-2] == '/')) continue; *cursor = '\0'; /* truncate the path there */ status = mkdir (file_name, ~newdir_umask & 0777); if (status == 0) { /* Fix ownership. */ if (we_are_root) if (chown (file_name, current_stat.st_uid, current_stat.st_gid) < 0) ERROR ((0, errno, _("%s: Cannot change owner to uid %d, gid %d"), file_name, current_stat.st_uid, current_stat.st_gid)); print_for_mkdir (file_name, cursor - file_name, ~newdir_umask & 0777); did_something = 1; *cursor = '/'; continue; } *cursor = '/'; if (errno == EEXIST #if MSDOS /* Turbo C mkdir gives a funny errno. */ || errno == EACCES #endif ) /* Directory already exists. */ continue; /* Some other error in the mkdir. We return to the caller. */ break; } errno = saved_errno; /* FIXME: errno should be read-only */ return did_something; /* tell them to retry if we made one */ } /*--------------------------------------------------------------------. | Attempt repairing what went wrong with the extraction. Delete an | | already existing file or create missing intermediate directories. | | Return nonzero if we somewhat increased our chances at a successful | | extraction. errno is properly restored on zero return. | `--------------------------------------------------------------------*/ static int maybe_recoverable (char *file_name) { switch (errno) { case EEXIST: /* Attempt deleting an existing file. However, with -k, just stay quiet. */ if (keep_old_files_option) return 0; return remove_any_file (file_name, 0); case ENOENT: /* Attempt creating missing intermediate directories. */ return make_directories (file_name); default: /* Just say we can't do anything about it... */ return 0; } } /*---. | ? | `---*/ static void extract_sparse_file (int fd, long *sizeleft, long totalsize, char *name) { union block *data_block; int sparse_ind = 0; int written, count; /* FIXME: `data_block' might be used uninitialized in this function. Reported by Bruno Haible. */ /* assuming sizeleft is initially totalsize */ while (*sizeleft > 0) { data_block = find_next_block (); if (data_block == NULL) { ERROR ((0, 0, _("Unexpected EOF on archive file"))); return; } lseek (fd, sparsearray[sparse_ind].offset, 0); written = sparsearray[sparse_ind++].numbytes; while (written > BLOCKSIZE) { count = write (fd, data_block->buffer, BLOCKSIZE); if (count < 0) ERROR ((0, errno, _("%s: Could not write to file"), name)); written -= count; *sizeleft -= count; set_next_block_after (data_block); data_block = find_next_block (); } count = write (fd, data_block->buffer, (size_t) written); if (count < 0) ERROR ((0, errno, _("%s: Could not write to file"), name)); else if (count != written) { ERROR ((0, 0, _("%s: Could only write %d of %d bytes"), name, count, totalsize)); skip_file ((long) (*sizeleft)); } written -= count; *sizeleft -= count; set_next_block_after (data_block); } free (sparsearray); set_next_block_after (data_block); } /*----------------------------------. | Extract a file from the archive. | `----------------------------------*/ void extract_archive (void) { union block *data_block; int fd; int status; int name_length; int written; int openflag; long size; int skipcrud; int counter; #if 0 int sparse_ind = 0; #endif union block *exhdr; struct delayed_set_stat *data; #define CURRENT_FILE_NAME (skipcrud + current_file_name) set_next_block_after (current_header); decode_header (current_header, ¤t_stat, ¤t_format, 1); if (interactive_option && !confirm ("extract", current_file_name)) { if (current_header->oldgnu_header.isextended) skip_extended_headers (); skip_file ((long) current_stat.st_size); return; } /* Print the block from `current_header' and `current_stat'. */ if (verbose_option) print_header (); /* Check for fully specified file names and other atrocities. */ skipcrud = 0; while (!absolute_names_option && CURRENT_FILE_NAME[0] == '/') { static int warned_once = 0; skipcrud++; /* force relative path */ if (!warned_once) { warned_once = 1; WARN ((0, 0, _("\ Removing leading `/' from absolute path names in the archive"))); } } /* Take a safety backup of a previously existing file. */ if (backup_option && !to_stdout_option) if (!maybe_backup_file (CURRENT_FILE_NAME, 0)) { ERROR ((0, errno, _("%s: Was unable to backup this file"), CURRENT_FILE_NAME)); if (current_header->oldgnu_header.isextended) skip_extended_headers (); skip_file ((long) current_stat.st_size); return; } /* Extract the archive entry according to its type. */ switch (current_header->header.typeflag) { /* JK - What we want to do if the file is sparse is loop through the array of sparse structures in the header and read in and translate the character strings representing 1) the offset at which to write and 2) how many bytes to write into numbers, which we store into the scratch array, "sparsearray". This array makes our life easier the same way it did in creating the tar file that had to deal with a sparse file. After we read in the first five (at most) sparse structures, we check to see if the file has an extended header, i.e., if more sparse structures are needed to describe the contents of the new file. If so, we read in the extended headers and continue to store their contents into the sparsearray. */ case GNUTYPE_SPARSE: sp_array_size = 10; sparsearray = (struct sp_array *) xmalloc (sp_array_size * sizeof (struct sp_array)); for (counter = 0; counter < SPARSES_IN_OLDGNU_HEADER; counter++) { sparsearray[counter].offset = from_oct (1 + 12, current_header->oldgnu_header.sp[counter].offset); sparsearray[counter].numbytes = from_oct (1 + 12, current_header->oldgnu_header.sp[counter].numbytes); if (!sparsearray[counter].numbytes) break; } if (current_header->oldgnu_header.isextended) { /* Read in the list of extended headers and translate them into the sparsearray as before. */ /* static */ int ind = SPARSES_IN_OLDGNU_HEADER; while (1) { exhdr = find_next_block (); for (counter = 0; counter < SPARSES_IN_SPARSE_HEADER; counter++) { if (counter + ind > sp_array_size - 1) { /* Realloc the scratch area since we've run out of room. */ sp_array_size *= 2; sparsearray = (struct sp_array *) xrealloc (sparsearray, sp_array_size * (sizeof (struct sp_array))); } /* Compare to 0, or use !(int)..., for Pyramid's dumb compiler. */ if (exhdr->sparse_header.sp[counter].numbytes == 0) break; sparsearray[counter + ind].offset = from_oct (1 + 12, exhdr->sparse_header.sp[counter].offset); sparsearray[counter + ind].numbytes = from_oct (1 + 12, exhdr->sparse_header.sp[counter].numbytes); } if (!exhdr->sparse_header.isextended) break; else { ind += SPARSES_IN_SPARSE_HEADER; set_next_block_after (exhdr); } } set_next_block_after (exhdr); } /* Fall through. */ case AREGTYPE: case REGTYPE: case CONTTYPE: /* Appears to be a file. But BSD tar uses the convention that a slash suffix means a directory. */ name_length = strlen (CURRENT_FILE_NAME) - 1; if (CURRENT_FILE_NAME[name_length] == '/') goto really_dir; /* FIXME: deal with protection issues. */ again_file: openflag = (keep_old_files_option ? O_BINARY | O_NDELAY | O_WRONLY | O_CREAT | O_EXCL : O_BINARY | O_NDELAY | O_WRONLY | O_CREAT | O_TRUNC) | ((current_header->header.typeflag == GNUTYPE_SPARSE) ? 0 : O_APPEND); /* JK - The last | is a kludge to solve the problem the O_APPEND flag causes with files we are trying to make sparse: when a file is opened with O_APPEND, it writes to the last place that something was written, thereby ignoring any lseeks that we have done. We add this extra condition to make it able to lseek when a file is sparse, i.e., we don't open the new file with this flag. (Grump -- this bug caused me to waste a good deal of time, I might add) */ if (to_stdout_option) { fd = 1; goto extract_file; } if (unlink_first_option) remove_any_file (CURRENT_FILE_NAME, recursive_unlink_option); #if O_CTG /* Contiguous files (on the Masscomp) have to specify the size in the open call that creates them. */ if (current_header->header.typeflag == CONTTYPE) fd = open (CURRENT_FILE_NAME, openflag | O_CTG, current_stat.st_mode, current_stat.st_size); else fd = open (CURRENT_FILE_NAME, openflag, current_stat.st_mode); #else /* not O_CTG */ if (current_header->header.typeflag == CONTTYPE) { static int conttype_diagnosed = 0; if (!conttype_diagnosed) { conttype_diagnosed = 1; WARN ((0, 0, _("Extracting contiguous files as regular files"))); } } fd = open (CURRENT_FILE_NAME, openflag, current_stat.st_mode); #endif /* not O_CTG */ if (fd < 0) { if (maybe_recoverable (CURRENT_FILE_NAME)) goto again_file; ERROR ((0, errno, _("%s: Could not create file"), CURRENT_FILE_NAME)); if (current_header->oldgnu_header.isextended) skip_extended_headers (); skip_file ((long) current_stat.st_size); if (backup_option) undo_last_backup (); break; } extract_file: if (current_header->header.typeflag == GNUTYPE_SPARSE) { char *name; int name_length_bis; /* Kludge alert. NAME is assigned to header.name because during the extraction, the space that contains the header will get scribbled on, and the name will get munged, so any error messages that happen to contain the filename will look REAL interesting unless we do this. */ name_length_bis = strlen (CURRENT_FILE_NAME) + 1; name = (char *) xmalloc ((sizeof (char)) * name_length_bis); memcpy (name, CURRENT_FILE_NAME, (size_t) name_length_bis); size = current_stat.st_size; extract_sparse_file (fd, &size, current_stat.st_size, name); } else for (size = current_stat.st_size; size > 0; size -= written) { #if 0 long offset, numbytes; #endif if (multi_volume_option) { assign_string (&save_name, current_file_name); save_totsize = current_stat.st_size; save_sizeleft = size; } /* Locate data, determine max length writeable, write it, block that we have used the data, then check if the write worked. */ data_block = find_next_block (); if (data_block == NULL) { ERROR ((0, 0, _("Unexpected EOF on archive file"))); break; /* FIXME: What happens, then? */ } /* If the file is sparse, use the sparsearray that we created before to lseek into the new file the proper amount, and to see how many bytes we want to write at that position. */ #if 0 if (current_header->header.typeflag == GNUTYPE_SPARSE) { off_t pos; pos = lseek (fd, (off_t) sparsearray[sparse_ind].offset, 0); fprintf (msg_file, _("%d at %d\n"), (int) pos, sparse_ind); written = sparsearray[sparse_ind++].numbytes; } else #endif written = available_space_after (data_block); if (written > size) written = size; errno = 0; /* FIXME: errno should be read-only */ status = write (fd, data_block->buffer, (size_t) written); set_next_block_after ((union block *) (data_block->buffer + written - 1)); if (status == written) continue; /* Error in writing to file. Print it, skip to next file in archive. */ if (status < 0) ERROR ((0, errno, _("%s: Could not write to file"), CURRENT_FILE_NAME)); else ERROR ((0, 0, _("%s: Could only write %d of %d bytes"), CURRENT_FILE_NAME, status, written)); skip_file ((long) (size - written)); break; /* still do the close, mod time, chmod, etc */ } if (multi_volume_option) assign_string (&save_name, NULL); /* If writing to stdout, don't try to do anything to the filename; it doesn't exist, or we don't want to touch it anyway. */ if (to_stdout_option) break; #if 0 if (current_header->header.isextended) { union block *exhdr; int counter; for (counter = 0; counter < 21; counter++) { off_t offset; if (!exhdr->sparse_header.sp[counter].numbytes) break; offset = from_oct (1 + 12, exhdr->sparse_header.sp[counter].offset); written = from_oct (1 + 12, exhdr->sparse_header.sp[counter].numbytes); lseek (fd, offset, 0); status = write (fd, data_block->buffer, written); if (status == written) continue; } } #endif status = close (fd); if (status < 0) { ERROR ((0, errno, _("%s: Error while closing"), CURRENT_FILE_NAME)); if (backup_option) undo_last_backup (); } set_stat (CURRENT_FILE_NAME, ¤t_stat, 0); break; case SYMTYPE: if (to_stdout_option) break; #ifdef S_ISLNK if (unlink_first_option) remove_any_file (CURRENT_FILE_NAME, recursive_unlink_option); while (status = symlink (current_link_name, CURRENT_FILE_NAME), status != 0) if (!maybe_recoverable (CURRENT_FILE_NAME)) break; if (status == 0) /* Setting the attributes of symbolic links might, on some systems, change the pointed to file, instead of the symbolic link itself. At least some of these systems have a lchown call, and the set_stat routine knows about this. */ set_stat (CURRENT_FILE_NAME, ¤t_stat, 1); else { ERROR ((0, errno, _("%s: Could not create symlink to `%s'"), CURRENT_FILE_NAME, current_link_name)); if (backup_option) undo_last_backup (); } break; #else /* not S_ISLNK */ { static int warned_once = 0; if (!warned_once) { warned_once = 1; WARN ((0, 0, _("\ Attempting extraction of symbolic links as hard links"))); } } /* Fall through. */ #endif /* not S_ISLNK */ case LNKTYPE: if (to_stdout_option) break; if (unlink_first_option) remove_any_file (CURRENT_FILE_NAME, recursive_unlink_option); again_link: { struct stat st1, st2; /* MSDOS does not implement links. However, djgpp's link() actually copies the file. */ status = link (current_link_name, CURRENT_FILE_NAME); if (status == 0) break; if (maybe_recoverable (CURRENT_FILE_NAME)) goto again_link; if (incremental_option && errno == EEXIST) break; if (stat (current_link_name, &st1) == 0 && stat (CURRENT_FILE_NAME, &st2) == 0 && st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino) break; ERROR ((0, errno, _("%s: Could not link to `%s'"), CURRENT_FILE_NAME, current_link_name)); if (backup_option) undo_last_backup (); } break; #if S_IFCHR case CHRTYPE: current_stat.st_mode |= S_IFCHR; goto make_node; #endif #if S_IFBLK case BLKTYPE: current_stat.st_mode |= S_IFBLK; #endif #if defined(S_IFCHR) || defined(S_IFBLK) make_node: if (to_stdout_option) break; if (unlink_first_option) remove_any_file (CURRENT_FILE_NAME, recursive_unlink_option); status = mknod (CURRENT_FILE_NAME, (int) current_stat.st_mode, (int) current_stat.st_rdev); if (status != 0) { if (maybe_recoverable (CURRENT_FILE_NAME)) goto make_node; ERROR ((0, errno, _("%s: Could not make node"), CURRENT_FILE_NAME)); if (backup_option) undo_last_backup (); break; }; set_stat (CURRENT_FILE_NAME, ¤t_stat, 0); break; #endif #ifdef S_ISFIFO case FIFOTYPE: if (to_stdout_option) break; if (unlink_first_option) remove_any_file (CURRENT_FILE_NAME, recursive_unlink_option); while (status = mkfifo (CURRENT_FILE_NAME, (int) current_stat.st_mode), status != 0) if (!maybe_recoverable (CURRENT_FILE_NAME)) break; if (status == 0) set_stat (CURRENT_FILE_NAME, ¤t_stat, 0); else { ERROR ((0, errno, _("%s: Could not make fifo"), CURRENT_FILE_NAME)); if (backup_option) undo_last_backup (); } break; #endif case DIRTYPE: case GNUTYPE_DUMPDIR: name_length = strlen (CURRENT_FILE_NAME) - 1; really_dir: /* Check for trailing /, and zap as many as we find. */ while (name_length && CURRENT_FILE_NAME[name_length] == '/') CURRENT_FILE_NAME[name_length--] = '\0'; if (incremental_option) { /* Read the entry and delete files that aren't listed in the archive. */ gnu_restore (skipcrud); } else if (current_header->header.typeflag == GNUTYPE_DUMPDIR) skip_file ((long) (current_stat.st_size)); if (to_stdout_option) break; again_dir: status = mkdir (CURRENT_FILE_NAME, (we_are_root ? 0 : 0300) | (int) current_stat.st_mode); if (status != 0) { /* If the directory creation fails, let's consider immediately the case where the directory already exists. We have three good reasons for clearing out this case before attempting recovery. 1) It would not be efficient recovering the error by deleting the directory in maybe_recoverable, then recreating it right away. We only hope we will be able to adjust its permissions adequately, later. 2) Removing the directory might fail if it is not empty. By exception, this real error is traditionally not reported. 3) Let's suppose `DIR' already exists and we are about to extract `DIR/../DIR'. This would fail because the directory already exists, and maybe_recoverable would react by removing `DIR'. This then would fail again because there are missing intermediate directories, and maybe_recoverable would react by creating `DIR'. We would then have an extraction loop. */ if (errno == EEXIST) { struct stat st1; int saved_errno = errno; if (stat (CURRENT_FILE_NAME, &st1) == 0 && S_ISDIR (st1.st_mode)) goto check_perms; errno = saved_errno; /* FIXME: errno should be read-only */ } if (maybe_recoverable (CURRENT_FILE_NAME)) goto again_dir; /* If we're trying to create '.', let it be. */ /* FIXME: Strange style... */ if (CURRENT_FILE_NAME[name_length] == '.' && (name_length == 0 || CURRENT_FILE_NAME[name_length - 1] == '/')) goto check_perms; ERROR ((0, errno, _("%s: Could not create directory"), CURRENT_FILE_NAME)); if (backup_option) undo_last_backup (); break; } check_perms: if (!we_are_root && 0300 != (0300 & (int) current_stat.st_mode)) { current_stat.st_mode |= 0300; WARN ((0, 0, _("Added write and execute permission to directory %s"), CURRENT_FILE_NAME)); } #if !MSDOS /* MSDOS does not associate timestamps with directories. In this case, no need to try delaying their restoration. */ if (touch_option) /* FIXME: I do not believe in this. Ignoring time stamps does not alleviate the need of delaying the restoration of directories' mode. Let's ponder this for a little while. */ set_mode (CURRENT_FILE_NAME, ¤t_stat); else { data = ((struct delayed_set_stat *) xmalloc (sizeof (struct delayed_set_stat))); data->file_name = xstrdup (CURRENT_FILE_NAME); data->stat_info = current_stat; data->next = delayed_set_stat_head; delayed_set_stat_head = data; } #endif /* !MSDOS */ break; case GNUTYPE_VOLHDR: if (verbose_option) fprintf (stdlis, _("Reading %s\n"), current_file_name); break; case GNUTYPE_NAMES: extract_mangle (); break; case GNUTYPE_MULTIVOL: ERROR ((0, 0, _("\ Cannot extract `%s' -- file is continued from another volume"), current_file_name)); skip_file ((long) current_stat.st_size); if (backup_option) undo_last_backup (); break; case GNUTYPE_LONGNAME: case GNUTYPE_LONGLINK: ERROR ((0, 0, _("Visible long name error"))); skip_file ((long) current_stat.st_size); if (backup_option) undo_last_backup (); break; default: WARN ((0, 0, _("Unknown file type '%c' for %s, extracted as normal file"), current_header->header.typeflag, CURRENT_FILE_NAME)); goto again_file; } #undef CURRENT_FILE_NAME } /*----------------------------------------------------------------. | Set back the utime and mode for all the extracted directories. | `----------------------------------------------------------------*/ void apply_delayed_set_stat (void) { struct delayed_set_stat *data; while (delayed_set_stat_head != NULL) { data = delayed_set_stat_head; delayed_set_stat_head = delayed_set_stat_head->next; set_stat (data->file_name, &data->stat_info, 0); free (data->file_name); free (data); } }
/* GNU dump extensions to tar. Copyright (C) 1988, 92, 93, 94, 96, 97 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "system.h" #include <time.h> time_t time (); #define ISDIGIT(Char) (ISASCII (Char) && isdigit (Char)) #define ISSPACE(Char) (ISASCII (Char) && isspace (Char)) #include "common.h" /* Variable sized generic character buffers. */ struct accumulator { int allocated; int length; char *pointer; }; /* Amount of space guaranteed just after a reallocation. */ #define ACCUMULATOR_SLACK 50 /*---------------------------------------------------------. | Return the accumulated data from an ACCUMULATOR buffer. | `---------------------------------------------------------*/ static char * get_accumulator (struct accumulator *accumulator) { return accumulator->pointer; } /*-----------------------------------------------. | Allocate and return a new accumulator buffer. | `-----------------------------------------------*/ static struct accumulator * new_accumulator (void) { struct accumulator *accumulator = (struct accumulator *) xmalloc (sizeof (struct accumulator)); accumulator->allocated = ACCUMULATOR_SLACK; accumulator->pointer = (char *) xmalloc (ACCUMULATOR_SLACK); accumulator->length = 0; return accumulator; } /*-----------------------------------. | Deallocate an ACCUMULATOR buffer. | `-----------------------------------*/ static void delete_accumulator (struct accumulator *accumulator) { free (accumulator->pointer); free (accumulator); } /*----------------------------------------------------------------------. | At the end of an ACCUMULATOR buffer, add a DATA block of SIZE bytes. | `----------------------------------------------------------------------*/ static void add_to_accumulator (struct accumulator *accumulator, const char *data, int size) { if (accumulator->length + size > accumulator->allocated) { accumulator->allocated = accumulator->length + size + ACCUMULATOR_SLACK; accumulator->pointer = (char *) xrealloc (accumulator->pointer, (size_t) accumulator->allocated); } memcpy (accumulator->pointer + accumulator->length, data, (size_t) size); accumulator->length += size; } /* Incremental dump specialities. */ /* Current time. */ static time_t time_now; /* List of directory names. */ struct directory { struct directory *next; /* next entry in list */ const char *name; /* path name of directory */ int device_number; /* device number for directory */ int inode_number; /* inode number for directory */ int allnew; const char *dir_text; }; static struct directory *directory_list = NULL; /*-------------------------------------------------------------------. | Create and link a new directory entry for directory NAME, having a | | DEVICE_NUMBER and a INODE_NUMBER, with some TEXT. | `-------------------------------------------------------------------*/ static void note_directory (char *name, dev_t device_number, ino_t inode_number, const char *text) { struct directory *directory = (struct directory *) xmalloc (sizeof (struct directory)); directory->next = directory_list; directory_list = directory; directory->device_number = device_number; directory->inode_number = inode_number; directory->name = xstrdup (name); directory->dir_text = text; directory->allnew = 0; } /*------------------------------------------------------------------------. | Return a directory entry for a given path NAME, or NULL if none found. | `------------------------------------------------------------------------*/ static struct directory * find_directory (char *name) { struct directory *directory; for (directory = directory_list; directory; directory = directory->next) { if (!strcmp (directory->name, name)) return directory; } return NULL; } /*---. | ? | `---*/ static int compare_dirents (const voidstar first, const voidstar second) { return strcmp ((*(char *const *) first) + 1, (*(char *const *) second) + 1); } /*---. | ? | `---*/ char * get_directory_contents (char *path, int device) { struct accumulator *accumulator; /* Recursively scan the given PATH. */ { DIR *dirp = opendir (path); /* for scanning directory */ struct dirent *entry; /* directory entry being scanned */ char *name_buffer; /* directory, `/', and directory member */ int name_buffer_size; /* allocated size of name_buffer, minus 2 */ int name_length; /* used length in name_buffer */ struct directory *directory; /* for checking if already already seen */ int all_children; if (dirp == NULL) { ERROR ((0, errno, _("Cannot open directory %s"), path)); return NULL; } errno = 0; /* FIXME: errno should be read-only */ name_buffer_size = strlen (path) + NAME_FIELD_SIZE; name_buffer = xmalloc ((size_t) (name_buffer_size + 2)); strcpy (name_buffer, path); if (path[strlen (path) - 1] != '/') strcat (name_buffer, "/"); name_length = strlen (name_buffer); directory = find_directory (path); all_children = directory ? directory->allnew : 0; accumulator = new_accumulator (); while (entry = readdir (dirp), entry) { struct stat stat_data; /* Skip `.' and `..'. */ if (is_dot_or_dotdot (entry->d_name)) continue; if ((int) NAMLEN (entry) + name_length >= name_buffer_size) { while ((int) NAMLEN (entry) + name_length >= name_buffer_size) name_buffer_size += NAME_FIELD_SIZE; name_buffer = (char *) xrealloc (name_buffer, (size_t) (name_buffer_size + 2)); } strcpy (name_buffer + name_length, entry->d_name); if (dereference_option #ifdef AIX ? statx (name_buffer, &stat_data, STATSIZE, STX_HIDDEN) : statx (name_buffer, &stat_data, STATSIZE, STX_HIDDEN | STX_LINK) #else ? stat (name_buffer, &stat_data) : lstat (name_buffer, &stat_data) #endif ) { ERROR ((0, errno, _("Cannot stat %s"), name_buffer)); continue; } if ((one_file_system_option && device != stat_data.st_dev) || (exclude_option && check_exclude (name_buffer))) add_to_accumulator (accumulator, "N", 1); #ifdef AIX else if (S_ISHIDDEN (stat_data.st_mode)) { add_to_accumulator (accumulator, "D", 1); strcat (entry->d_name, "A"); entry->d_namlen++; } #endif else if (S_ISDIR (stat_data.st_mode)) { if (directory = find_directory (name_buffer), directory) { /* Devices having the high bit set are NFS devices, which are attributed somewhat randomly in automounting situations. For avoiding spurious incremental redumping of directories, we have to plainly consider all NFS devices as equal, relying on the i-node only to establish differences. */ /* FIXME: Göran Uddeborg <goeran@uddeborg.pp.se> says, on 1996-09-20, that SunOS 5/Solaris 2 uses unsigned long for the device number type. */ if ((((short) directory->device_number >= 0 || (short) stat_data.st_dev >= 0) && directory->device_number != stat_data.st_dev) || directory->inode_number != stat_data.st_ino) { if (verbose_option) WARN ((0, 0, _("Directory %s has been renamed"), name_buffer)); directory->allnew = 1; directory->device_number = stat_data.st_dev; directory->inode_number = stat_data.st_ino; } directory->dir_text = ""; } else { if (verbose_option) WARN ((0, 0, _("Directory %s is new"), name_buffer)); note_directory (name_buffer, stat_data.st_dev, stat_data.st_ino, ""); directory = find_directory (name_buffer); directory->allnew = 1; } if (all_children && directory) directory->allnew = 1; add_to_accumulator (accumulator, "D", 1); } else if (!all_children && stat_data.st_mtime < newer_mtime_option && (!after_date_option || stat_data.st_ctime < newer_ctime_option)) add_to_accumulator (accumulator, "N", 1); else add_to_accumulator (accumulator, "Y", 1); add_to_accumulator (accumulator, entry->d_name, (int) (NAMLEN (entry) + 1)); } add_to_accumulator (accumulator, "\000\000", 2); free (name_buffer); closedir (dirp); } /* Sort the contents of the directory, now that we have it all. */ { char *pointer = get_accumulator (accumulator); size_t counter; char *cursor; char *buffer; char **array; char **array_cursor; counter = 0; for (cursor = pointer; *cursor; cursor += strlen (cursor) + 1) counter++; if (counter == 0) { delete_accumulator (accumulator); return NULL; } array = (char **) xmalloc (sizeof (char *) * (counter + 1)); array_cursor = array; for (cursor = pointer; *cursor; cursor += strlen (cursor) + 1) *array_cursor++ = cursor; *array_cursor = NULL; qsort ((voidstar) array, counter, sizeof (char *), compare_dirents); buffer = (char *) xmalloc ((size_t) (cursor - pointer + 2)); cursor = buffer; for (array_cursor = array; *array_cursor; array_cursor++) { char *string = *array_cursor; while ((*cursor++ = *string++)) continue; } *cursor = '\0'; delete_accumulator (accumulator); free (array); return buffer; } } /*----------------------------------------------------------------------. | Add all the files in PATH, which is a directory, to the namelist. If | | any of the files is a directory, recurse on the subdirectory. | `----------------------------------------------------------------------*/ static void add_hierarchy_to_namelist (char *path, int device) { char *buffer = get_directory_contents (path, device); { struct name *name; for (name = namelist; name; name = name->next) if (strcmp (name->name, path) == 0) break; if (name) name->dir_contents = buffer ? buffer : "\0\0\0\0"; } if (buffer) { int name_length = strlen (path); int allocated_length = (name_length >= NAME_FIELD_SIZE ? name_length + NAME_FIELD_SIZE : NAME_FIELD_SIZE); char *name_buffer = xmalloc ((size_t) (allocated_length + 1)); /* FIXME: + 2 above? */ char *string; int string_length; strcpy (name_buffer, path); if (name_buffer[name_length - 1] != '/') { name_buffer[name_length++] = '/'; name_buffer[name_length] = '\0'; } for (string = buffer; *string; string += string_length + 1) { string_length = strlen (string); if (*string == 'D') { if (name_length + string_length >= allocated_length) { while (name_length + string_length >= allocated_length) allocated_length += NAME_FIELD_SIZE; name_buffer = (char *) xrealloc (name_buffer, (size_t) (allocated_length + 1)); } strcpy (name_buffer + name_length, string + 1); addname (name_buffer); add_hierarchy_to_namelist (name_buffer, device); } } free (name_buffer); } } /*---. | ? | `---*/ static void read_directory_file (void) { dev_t device_number; ino_t inode_number; char *strp; FILE *fp; char buf[512]; static char *path = NULL; if (path == NULL) path = xmalloc (PATH_MAX); time (&time_now); if (listed_incremental_option[0] != '/') { #if HAVE_GETCWD if (!getcwd (path, PATH_MAX)) FATAL_ERROR ((0, 0, _("Could not get current directory"))); #else char *getwd (); if (!getwd (path)) FATAL_ERROR ((0, 0, _("Could not get current directory: %s"), path)); #endif if (strlen (path) + 1 + strlen (listed_incremental_option) + 1 > PATH_MAX) ERROR ((TAREXIT_FAILURE, 0, _("File name %s/%s too long"), path, listed_incremental_option)); strcat (path, "/"); strcat (path, listed_incremental_option); listed_incremental_option = path; } fp = fopen (listed_incremental_option, "r"); if (fp == 0 && errno != ENOENT) { ERROR ((0, errno, _("Cannot open %s"), listed_incremental_option)); return; } if (!fp) return; fgets (buf, sizeof (buf), fp); /* FIXME: Using after_date_option as a first time flag looks fairly dubious to me! So, using -N with incremental might be buggy just because of the next few lines. I saw a few unexplained, almost harsh advices, from other GNU people, about *not* using -N with incremental dumps, and here might lie (part of) the reason. */ if (!after_date_option) { newer_mtime_option = atol (buf); after_date_option = 1; } while (fgets (buf, sizeof (buf), fp)) { strp = &buf[strlen (buf)]; if (strp[-1] == '\n') strp[-1] = '\0'; /* FIXME: For files ending with an incomplete line, maybe a NUL might be missing, here... */ strp = buf; device_number = atol (strp); while (ISDIGIT (*strp)) strp++; inode_number = atol (strp); while (ISSPACE (*strp)) strp++; while (ISDIGIT (*strp)) strp++; strp++; unquote_string (strp); note_directory (strp, device_number, inode_number, NULL); } if (fclose (fp) == EOF) ERROR ((0, errno, "%s", listed_incremental_option)); } /*---. | ? | `---*/ void write_dir_file (void) { FILE *fp; struct directory *directory; char *str; fp = fopen (listed_incremental_option, "w"); if (fp == 0) { ERROR ((0, errno, _("Cannot write to %s"), listed_incremental_option)); return; } fprintf (fp, "%lu\n", time_now); for (directory = directory_list; directory; directory = directory->next) { if (!directory->dir_text) continue; str = quote_copy_string (directory->name); if (str) { fprintf (fp, "%u %u %s\n", directory->device_number, directory->inode_number, str); free (str); } else fprintf (fp, "%u %u %s\n", directory->device_number, directory->inode_number, directory->name); } if (fclose (fp) == EOF) ERROR ((0, errno, "%s", listed_incremental_option)); } /*---. | ? | `---*/ static int compare_names (char *param1, char *param2) { struct name *n1 = (struct name *) param1; struct name *n2 = (struct name *) param2; if (n1->found) return n2->found ? strcmp (n1->name, n2->name) : -1; if (n2->found) return 1; return strcmp (n1->name, n2->name); } /*-------------------------------------------------------------------------. | Collect all the names from argv[] (or whatever), then expand them into a | | directory tree, and put all the directories at the beginning. | `-------------------------------------------------------------------------*/ void collect_and_sort_names (void) { struct name *name; struct name *next_name; int num_names; struct stat statbuf; name_gather (); if (listed_incremental_option) read_directory_file (); if (!namelist) addname ("."); for (name = namelist; name; name = next_name) { next_name = name->next; if (name->found || name->dir_contents) continue; if (name->regexp) /* FIXME: just skip regexps for now */ continue; if (name->change_dir) if (chdir (name->change_dir) < 0) { ERROR ((0, errno, _("Cannot chdir to %s"), name->change_dir)); continue; } if ( #ifdef AIX statx (name->name, &statbuf, STATSIZE, STX_HIDDEN | STX_LINK) #else lstat (name->name, &statbuf) < 0 #endif ) { ERROR ((0, errno, _("Cannot stat %s"), name->name)); continue; } if (S_ISDIR (statbuf.st_mode)) { name->found = 1; add_hierarchy_to_namelist (name->name, statbuf.st_dev); } } num_names = 0; for (name = namelist; name; name = name->next) num_names++; namelist = (struct name *) merge_sort ((voidstar) namelist, num_names, (char *) (&(namelist->next)) - (char *) namelist, compare_names); for (name = namelist; name; name = name->next) name->found = 0; if (listed_incremental_option) write_dir_file (); } /* Restoration of incremental dumps. */ /*---. | ? | `---*/ void gnu_restore (int skipcrud) { char *current_dir; char *archive_dir; struct accumulator *accumulator; char *p; DIR *dirp; struct dirent *d; char *cur, *arc; long size, copied; union block *data_block; char *to; #define CURRENT_FILE_NAME (skipcrud + current_file_name) dirp = opendir (CURRENT_FILE_NAME); if (!dirp) { /* The directory doesn't exist now. It'll be created. In any case, we don't have to delete any files out of it. */ skip_file ((long) current_stat.st_size); return; } accumulator = new_accumulator (); while (d = readdir (dirp), d) { if (is_dot_or_dotdot (d->d_name)) continue; add_to_accumulator (accumulator, d->d_name, (int) (NAMLEN (d) + 1)); } closedir (dirp); add_to_accumulator (accumulator, "", 1); current_dir = get_accumulator (accumulator); archive_dir = (char *) xmalloc ((size_t) current_stat.st_size); to = archive_dir; for (size = current_stat.st_size; size > 0; size -= copied) { data_block = find_next_block (); if (!data_block) { ERROR ((0, 0, _("Unexpected EOF in archive"))); break; /* FIXME: What happens then? */ } copied = available_space_after (data_block); if (copied > size) copied = size; memcpy (to, data_block->buffer, (size_t) copied); to += copied; set_next_block_after ((union block *) (data_block->buffer + copied - 1)); } for (cur = current_dir; *cur; cur += strlen (cur) + 1) { for (arc = archive_dir; *arc; arc += strlen (arc) + 1) { arc++; if (!strcmp (arc, cur)) break; } if (*arc == '\0') { p = new_name (CURRENT_FILE_NAME, cur); if (interactive_option && !confirm ("delete", p)) { free (p); continue; } if (verbose_option) fprintf (stdlis, _("%s: Deleting %s\n"), program_name, p); if (!remove_any_file (p, 1)) ERROR ((0, errno, _("Error while deleting %s"), p)); free (p); } } delete_accumulator (accumulator); free (archive_dir); #undef CURRENT_FILE_NAME }
/* List a tar archive, with support routines for reading a tar archive. Copyright (C) 1988, 92, 93, 94, 96, 97 Free Software Foundation, Inc. Written by John Gilmore, on 1985-08-26. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Define to non-zero for forcing old ctime() instead of isotime(). */ #undef USE_OLD_CTIME #include "system.h" #include <ctype.h> #include <time.h> #define ISODIGIT(Char) \ ((unsigned char) (Char) >= '0' && (unsigned char) (Char) <= '7') #define ISSPACE(Char) (ISASCII (Char) && isspace (Char)) #include "common.h" union block *current_header; /* points to current archive header */ struct stat current_stat; /* stat struct corresponding */ enum archive_format current_format; /* recognized format */ /*-----------------------------------. | Main loop for reading an archive. | `-----------------------------------*/ void read_and (void (*do_something) ()) { enum read_header status = HEADER_STILL_UNREAD; enum read_header prev_status; char save_typeflag; name_gather (); open_archive (ACCESS_READ); while (1) { prev_status = status; status = read_header (); switch (status) { case HEADER_STILL_UNREAD: abort (); case HEADER_SUCCESS: /* Valid header. We should decode next field (mode) first. Ensure incoming names are null terminated. */ /* FIXME: This is a quick kludge before 1.12 goes out. */ current_stat.st_mtime = from_oct (1 + 12, current_header->header.mtime); if (!name_match (current_file_name) || current_stat.st_mtime < newer_mtime_option || (exclude_option && check_exclude (current_file_name))) { int isextended = 0; if (current_header->header.typeflag == GNUTYPE_VOLHDR || current_header->header.typeflag == GNUTYPE_MULTIVOL || current_header->header.typeflag == GNUTYPE_NAMES) { (*do_something) (); continue; } if (show_omitted_dirs_option && current_header->header.typeflag == DIRTYPE) WARN ((0, 0, _("Omitting %s"), current_file_name)); /* Skip past it in the archive. */ if (current_header->oldgnu_header.isextended) isextended = 1; save_typeflag = current_header->header.typeflag; set_next_block_after (current_header); if (isextended) { #if 0 union block *exhdr; while (1) { exhdr = find_next_block (); if (!exhdr->sparse_header.isextended) { set_next_block_after (exhdr); break; } } set_next_block_after (exhdr); #endif skip_extended_headers (); } /* Skip to the next header on the archive. */ if (save_typeflag != DIRTYPE) skip_file ((long) current_stat.st_size); continue; } (*do_something) (); continue; case HEADER_ZERO_BLOCK: if (block_number_option) fprintf (stdlis, _("block %10ld: ** Block of NULs **\n"), current_block_ordinal ()); set_next_block_after (current_header); status = prev_status; if (ignore_zeros_option) continue; break; case HEADER_END_OF_FILE: if (block_number_option) fprintf (stdlis, _("block %10ld: ** End of File **\n"), current_block_ordinal ()); break; case HEADER_FAILURE: /* If the previous header was good, tell them that we are skipping bad ones. */ set_next_block_after (current_header); switch (prev_status) { case HEADER_STILL_UNREAD: WARN ((0, 0, _("Hmm, this doesn't look like a tar archive"))); /* Fall through. */ case HEADER_ZERO_BLOCK: case HEADER_SUCCESS: WARN ((0, 0, _("Skipping to next file header"))); break; case HEADER_END_OF_FILE: case HEADER_FAILURE: /* We are in the middle of a cascade of errors. */ break; } continue; } break; } apply_delayed_set_stat (); close_archive (); names_notfound (); /* print names not found */ } /*---------------------------------------------. | Print a header block, based on tar options. | `---------------------------------------------*/ void list_archive (void) { int isextended = 0; /* to remember if current_header is extended */ /* Print the header block. */ if (verbose_option) { if (verbose_option > 1) decode_header (current_header, ¤t_stat, ¤t_format, 0); print_header (); } if (incremental_option && current_header->header.typeflag == GNUTYPE_DUMPDIR) { size_t size, written, check; union block *data_block; set_next_block_after (current_header); if (multi_volume_option) { assign_string (&save_name, current_file_name); save_totsize = current_stat.st_size; } for (size = current_stat.st_size; size > 0; size -= written) { if (multi_volume_option) save_sizeleft = size; data_block = find_next_block (); if (!data_block) { ERROR ((0, 0, _("EOF in archive file"))); break; /* FIXME: What happens, then? */ } written = available_space_after (data_block); if (written > size) written = size; errno = 0; /* FIXME: errno should be read-only */ check = fwrite (data_block->buffer, sizeof (char), written, stdlis); set_next_block_after ((union block *) (data_block->buffer + written - 1)); if (check != written) { ERROR ((0, errno, _("Only wrote %ld of %ld bytes to file %s"), check, written, current_file_name)); skip_file ((long) (size - written)); break; } } if (multi_volume_option) assign_string (&save_name, NULL); fputc ('\n', stdlis); fflush (stdlis); return; } /* Check to see if we have an extended header to skip over also. */ if (current_header->oldgnu_header.isextended) isextended = 1; /* Skip past the header in the archive. */ set_next_block_after (current_header); /* If we needed to skip any extended headers, do so now, by reading extended headers and skipping past them in the archive. */ if (isextended) { #if 0 union block *exhdr; while (1) { exhdr = find_next_block (); if (!exhdr->sparse_header.isextended) { set_next_block_after (exhdr); break; } set_next_block_after (exhdr); } #endif skip_extended_headers (); } if (multi_volume_option) assign_string (&save_name, current_file_name); /* Skip to the next header on the archive. */ skip_file ((long) current_stat.st_size); if (multi_volume_option) assign_string (&save_name, NULL); } /*-----------------------------------------------------------------------. | Read a block that's supposed to be a header block. Return its address | | in "current_header", and if it is good, the file's size in | | current_stat.st_size. | | | | Return 1 for success, 0 if the checksum is bad, EOF on eof, 2 for a | | block full of zeros (EOF marker). | | | | You must always set_next_block_after(current_header) to skip past the | | header which this routine reads. | `-----------------------------------------------------------------------*/ /* The standard BSD tar sources create the checksum by adding up the bytes in the header as type char. I think the type char was unsigned on the PDP-11, but it's signed on the Next and Sun. It looks like the sources to BSD tar were never changed to compute the checksum currectly, so both the Sun and Next add the bytes of the header as signed chars. This doesn't cause a problem until you get a file with a name containing characters with the high bit set. So read_header computes two checksums -- signed and unsigned. */ /* FIXME: The signed checksum computation is broken on machines where char's are unsigned. It's uneasy to handle all cases correctly... */ enum read_header read_header (void) { int i; long unsigned_sum; /* the POSIX one :-) */ long signed_sum; /* the Sun one :-( */ long recorded_sum; char *p; union block *header; char **longp; char *bp; union block *data_block; int size, written; static char *next_long_name, *next_long_link; while (1) { header = find_next_block (); current_header = header; if (!header) return HEADER_END_OF_FILE; recorded_sum = from_oct (sizeof header->header.chksum, header->header.chksum); unsigned_sum = 0; signed_sum = 0; p = header->buffer; for (i = sizeof (*header); --i >= 0;) { /* We can't use unsigned char here because of old compilers, e.g. V7. */ unsigned_sum += 0xFF & *p; signed_sum += *p++; } /* Adjust checksum to count the "chksum" field as blanks. */ for (i = sizeof (header->header.chksum); --i >= 0;) { unsigned_sum -= 0xFF & header->header.chksum[i]; signed_sum -= header->header.chksum[i]; } unsigned_sum += ' ' * sizeof header->header.chksum; signed_sum += ' ' * sizeof header->header.chksum; if (unsigned_sum == sizeof header->header.chksum * ' ') { /* This is a zeroed block...whole block is 0's except for the blanks we faked for the checksum field. */ return HEADER_ZERO_BLOCK; } if (unsigned_sum != recorded_sum && signed_sum != recorded_sum) return HEADER_FAILURE; /* Good block. Decode file size and return. */ if (header->header.typeflag == LNKTYPE) current_stat.st_size = 0; /* links 0 size on tape */ else current_stat.st_size = from_oct (1 + 12, header->header.size); header->header.name[NAME_FIELD_SIZE - 1] = '\0'; if (header->header.typeflag == GNUTYPE_LONGNAME || header->header.typeflag == GNUTYPE_LONGLINK) { longp = ((header->header.typeflag == GNUTYPE_LONGNAME) ? &next_long_name : &next_long_link); set_next_block_after (header); if (*longp) free (*longp); bp = *longp = (char *) xmalloc ((size_t) current_stat.st_size); for (size = current_stat.st_size; size > 0; size -= written) { data_block = find_next_block (); if (data_block == NULL) { ERROR ((0, 0, _("Unexpected EOF on archive file"))); break; } written = available_space_after (data_block); if (written > size) written = size; memcpy (bp, data_block->buffer, (size_t) written); bp += written; set_next_block_after ((union block *) (data_block->buffer + written - 1)); } /* Loop! */ } else { assign_string (¤t_file_name, (next_long_name ? next_long_name : current_header->header.name)); assign_string (¤t_link_name, (next_long_link ? next_long_link : current_header->header.linkname)); next_long_link = next_long_name = 0; return HEADER_SUCCESS; } } } /*-------------------------------------------------------------------------. | Decode things from a file HEADER block into STAT_INFO, also setting | | *FORMAT_POINTER depending on the header block format. If DO_USER_GROUP, | | decode the user/group information (this is useful for extraction, but | | waste time when merely listing). | | | | read_header() has already decoded the checksum and length, so we don't. | | | | This routine should *not* be called twice for the same block, since the | | two calls might use different DO_USER_GROUP values and thus might end up | | with different uid/gid for the two calls. If anybody wants the uid/gid | | they should decode it first, and other callers should decode it without | | uid/gid before calling a routine, e.g. print_header, that assumes | | decoded data. | `-------------------------------------------------------------------------*/ void decode_header (union block *header, struct stat *stat_info, enum archive_format *format_pointer, int do_user_group) { enum archive_format format; if (strcmp (header->header.magic, TMAGIC) == 0) format = POSIX_FORMAT; else if (strcmp (header->header.magic, OLDGNU_MAGIC) == 0) format = OLDGNU_FORMAT; else format = V7_FORMAT; *format_pointer = format; stat_info->st_mode = from_oct (8, header->header.mode); stat_info->st_mode &= 07777; stat_info->st_mtime = from_oct (1 + 12, header->header.mtime); if (format == OLDGNU_FORMAT && incremental_option) { stat_info->st_atime = from_oct (1 + 12, header->oldgnu_header.atime); stat_info->st_ctime = from_oct (1 + 12, header->oldgnu_header.ctime); } if (format == V7_FORMAT) { stat_info->st_uid = from_oct (8, header->header.uid); stat_info->st_gid = from_oct (8, header->header.gid); stat_info->st_rdev = 0; } else { if (do_user_group) { /* FIXME: Decide if this should somewhat depend on -p. */ if (numeric_owner_option || !*header->header.uname || !uname_to_uid (header->header.uname, &stat_info->st_uid)) stat_info->st_uid = from_oct (8, header->header.uid); if (numeric_owner_option || !*header->header.gname || !gname_to_gid (header->header.gname, &stat_info->st_gid)) stat_info->st_gid = from_oct (8, header->header.gid); } switch (header->header.typeflag) { #ifdef S_IFBLK case BLKTYPE: stat_info->st_rdev = makedev (from_oct (8, header->header.devmajor), from_oct (8, header->header.devminor)); break; #endif #ifdef S_IFCHR case CHRTYPE: stat_info->st_rdev = makedev (from_oct (8, header->header.devmajor), from_oct (8, header->header.devminor)); break; #endif default: stat_info->st_rdev = 0; } } } /*------------------------------------------------------------------------. | Quick and dirty octal conversion. Result is -1 if the field is invalid | | (all blank, or nonoctal). | `------------------------------------------------------------------------*/ long from_oct (int digs, char *where) { long value; while (ISSPACE (*where)) { /* skip spaces */ where++; if (--digs <= 0) return -1; /* all blank field */ } value = 0; while (digs > 0 && ISODIGIT (*where)) { /* Scan til nonoctal. */ value = (value << 3) | (*where++ - '0'); --digs; } if (digs > 0 && *where && !ISSPACE (*where)) return -1; /* ended on non-space/nul */ return value; } #if !USE_OLD_CTIME /*-------------------------------------------. | Return the time formatted along ISO 8601. | `-------------------------------------------*/ /* Also, see http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html. */ static char * isotime (const time_t *time) { static char buffer[21]; struct tm *tm; tm = localtime (time); sprintf (buffer, "%4d-%02d-%02d %02d:%02d:%02d\n", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); return buffer; } #endif /* not USE_OLD_CTIME */ /*-------------------------------------------------------------------------. | Decode MODE from its binary form in a stat structure, and encode it into | | a 9 characters string STRING, terminated with a NUL. | `-------------------------------------------------------------------------*/ static void decode_mode (unsigned mode, char *string) { unsigned mask; const char *rwx = "rwxrwxrwx"; for (mask = 0400; mask != 0; mask >>= 1) if (mode & mask) *string++ = *rwx++; else { *string++ = '-'; rwx++; } if (mode & S_ISUID) string[-7] = string[-7] == 'x' ? 's' : 'S'; if (mode & S_ISGID) string[-4] = string[-4] == 'x' ? 's' : 'S'; if (mode & S_ISVTX) string[-1] = string[-1] == 'x' ? 't' : 'T'; *string = '\0'; } /*-------------------------------------------------------------------------. | Actually print it. | | | | Plain and fancy file header block logging. Non-verbose just prints the | | name, e.g. for "tar t" or "tar x". This should just contain file names, | | so it can be fed back into tar with xargs or the "-T" option. The | | verbose option can give a bunch of info, one line per file. I doubt | | anybody tries to parse its format, or if they do, they shouldn't. Unix | | tar is pretty random here anyway. | `-------------------------------------------------------------------------*/ /* FIXME: Note that print_header uses the globals HEAD, HSTAT, and HEAD_STANDARD, which must be set up in advance. Not very clean... */ /* UGSWIDTH starts with 18, so with user and group names <= 8 chars, the columns never shift during the listing. */ #define UGSWIDTH 18 static int ugswidth = UGSWIDTH; /* maximum width encountered so far */ /* DATEWIDTH is the number of columns taken by the date and time fields. */ #if USE_OLD_CDATE # define DATEWIDTH 19 #else # define DATEWIDTH 18 #endif void print_header (void) { char modes[11]; char *timestamp; char uform[11], gform[11]; /* these hold formatted ints */ char *user, *group; char size[24]; /* holds a formatted long or maj, min */ time_t longie; /* to make ctime() call portable */ int pad; char *name; if (block_number_option) fprintf (stdlis, _("block %10ld: "), current_block_ordinal ()); if (verbose_option <= 1) { /* Just the fax, mam. */ char *quoted_name = quote_copy_string (current_file_name); if (quoted_name) { fprintf (stdlis, "%s\n", quoted_name); free (quoted_name); } else fprintf (stdlis, "%s\n", current_file_name); } else { /* File type and modes. */ modes[0] = '?'; switch (current_header->header.typeflag) { case GNUTYPE_VOLHDR: modes[0] = 'V'; break; case GNUTYPE_MULTIVOL: modes[0] = 'M'; break; case GNUTYPE_NAMES: modes[0] = 'N'; break; case GNUTYPE_LONGNAME: case GNUTYPE_LONGLINK: ERROR ((0, 0, _("Visible longname error"))); break; case GNUTYPE_SPARSE: case REGTYPE: case AREGTYPE: case LNKTYPE: modes[0] = '-'; if (current_file_name[strlen (current_file_name) - 1] == '/') modes[0] = 'd'; break; case GNUTYPE_DUMPDIR: modes[0] = 'd'; break; case DIRTYPE: modes[0] = 'd'; break; case SYMTYPE: modes[0] = 'l'; break; case BLKTYPE: modes[0] = 'b'; break; case CHRTYPE: modes[0] = 'c'; break; case FIFOTYPE: modes[0] = 'p'; break; case CONTTYPE: modes[0] = 'C'; break; } decode_mode ((unsigned) current_stat.st_mode, modes + 1); /* Timestamp. */ longie = current_stat.st_mtime; #if USE_OLD_CTIME timestamp = ctime (&longie); timestamp[16] = '\0'; timestamp[24] = '\0'; #else timestamp = isotime (&longie); timestamp[16] = '\0'; #endif /* User and group names. */ if (*current_header->header.uname && current_format != V7_FORMAT) user = current_header->header.uname; else { user = uform; sprintf (uform, "%ld", from_oct (8, current_header->header.uid)); } if (*current_header->header.gname && current_format != V7_FORMAT) group = current_header->header.gname; else { group = gform; sprintf (gform, "%ld", from_oct (8, current_header->header.gid)); } /* Format the file size or major/minor device numbers. */ switch (current_header->header.typeflag) { #if defined(S_IFBLK) || defined(S_IFCHR) case CHRTYPE: case BLKTYPE: sprintf (size, "%d,%d", major (current_stat.st_rdev), minor (current_stat.st_rdev)); break; #endif case GNUTYPE_SPARSE: sprintf (size, "%ld", from_oct (1 + 12, current_header->oldgnu_header.realsize)); break; default: sprintf (size, "%ld", (long) current_stat.st_size); } /* Figure out padding and print the whole line. */ pad = strlen (user) + strlen (group) + strlen (size) + 1; if (pad > ugswidth) ugswidth = pad; #if USE_OLD_CTIME fprintf (stdlis, "%s %s/%s %*s%s %s %s", modes, user, group, ugswidth - pad, "", size, timestamp + 4, timestamp + 20); #else fprintf (stdlis, "%s %s/%s %*s%s %s", modes, user, group, ugswidth - pad, "", size, timestamp); #endif name = quote_copy_string (current_file_name); if (name) { fprintf (stdlis, " %s", name); free (name); } else fprintf (stdlis, " %s", current_file_name); switch (current_header->header.typeflag) { case SYMTYPE: name = quote_copy_string (current_link_name); if (name) { fprintf (stdlis, " -> %s\n", name); free (name); } else fprintf (stdlis, " -> %s\n", current_link_name); break; case LNKTYPE: name = quote_copy_string (current_link_name); if (name) { fprintf (stdlis, _(" link to %s\n"), name); free (name); } else fprintf (stdlis, _(" link to %s\n"), current_link_name); break; default: fprintf (stdlis, _(" unknown file type `%c'\n"), current_header->header.typeflag); break; case AREGTYPE: case REGTYPE: case GNUTYPE_SPARSE: case CHRTYPE: case BLKTYPE: case DIRTYPE: case FIFOTYPE: case CONTTYPE: case GNUTYPE_DUMPDIR: putc ('\n', stdlis); break; case GNUTYPE_VOLHDR: fprintf (stdlis, _("--Volume Header--\n")); break; case GNUTYPE_MULTIVOL: fprintf (stdlis, _("--Continued at byte %ld--\n"), from_oct (1 + 12, current_header->oldgnu_header.offset)); break; case GNUTYPE_NAMES: fprintf (stdlis, _("--Mangled file names--\n")); break; } } fflush (stdlis); } /*--------------------------------------------------------------. | Print a similar line when we make a directory automatically. | `--------------------------------------------------------------*/ void print_for_mkdir (char *pathname, int length, int mode) { char modes[11]; char *name; if (verbose_option > 1) { /* File type and modes. */ modes[0] = 'd'; decode_mode ((unsigned) mode, modes + 1); if (block_number_option) fprintf (stdlis, _("block %10ld: "), current_block_ordinal ()); name = quote_copy_string (pathname); if (name) { fprintf (stdlis, "%s %*s %.*s\n", modes, ugswidth + DATEWIDTH, _("Creating directory:"), length, name); free (name); } else fprintf (stdlis, "%s %*s %.*s\n", modes, ugswidth + DATEWIDTH, _("Creating directory:"), length, pathname); } } /*--------------------------------------------------------. | Skip over SIZE bytes of data in blocks in the archive. | `--------------------------------------------------------*/ void skip_file (long size) { union block *x; if (multi_volume_option) { save_totsize = size; save_sizeleft = size; } while (size > 0) { x = find_next_block (); if (x == NULL) FATAL_ERROR ((0, 0, _("Unexpected EOF on archive file"))); set_next_block_after (x); size -= BLOCKSIZE; if (multi_volume_option) save_sizeleft -= BLOCKSIZE; } } /*---. | ? | `---*/ void skip_extended_headers (void) { union block *exhdr; while (1) { exhdr = find_next_block (); if (!exhdr->sparse_header.isextended) { set_next_block_after (exhdr); break; } set_next_block_after (exhdr); } }
/* Encode long filenames for GNU tar. Copyright (C) 1988, 1992, 1994, 1996, 1997 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "system.h" #include <time.h> time_t time (); #include "common.h" struct mangled { struct mangled *next; int type; char mangled[NAME_FIELD_SIZE]; char *linked_to; char normal[1]; }; /* Should use a hash table, etc. . */ struct mangled *first_mangle; int mangled_num = 0; /*---------------------------------------------------------------------. | Extract a GNUTYPE_NAMES record contents. It seems that such are not | | produced anymore by GNU tar, but we leave the reading code around | | nevertheless, for salvaging old tapes. | `---------------------------------------------------------------------*/ void extract_mangle (void) { int size = current_stat.st_size; char *buffer = xmalloc ((size_t) (size + 1)); char *copy = buffer; char *cursor = buffer; buffer[size] = '\0'; while (size > 0) { union block *block = find_next_block (); int available; if (!block) { ERROR ((0, 0, _("Unexpected EOF in mangled names"))); return; } available = available_space_after (block); if (available > size) available = size; memcpy (copy, block->buffer, (size_t) available); copy += available; size -= available; set_next_block_after ((union block *) (block->buffer + available - 1)); } while (*cursor) { char *next_cursor; char *name; char *name_end; next_cursor = strchr (cursor, '\n'); *next_cursor++ = '\0'; if (!strncmp (cursor, "Rename ", 7)) { name = cursor + 7; name_end = strchr (name, ' '); while (strncmp (name_end, " to ", 4)) { name_end++; name_end = strchr (name_end, ' '); } *name_end = '\0'; if (next_cursor[-2] == '/') next_cursor[-2] = '\0'; unquote_string (name_end + 4); if (rename (name, name_end + 4)) ERROR ((0, errno, _("Cannot rename %s to %s"), name, name_end + 4)); else if (verbose_option) WARN ((0, 0, _("Renamed %s to %s"), name, name_end + 4)); } #ifdef S_ISLNK else if (!strncmp (cursor, "Symlink ", 8)) { name = cursor + 8; name_end = strchr (name, ' '); while (strncmp (name_end, " to ", 4)) { name_end++; name_end = strchr (name_end, ' '); } *name_end = '\0'; unquote_string (name); unquote_string (name_end + 4); if (symlink (name, name_end + 4) && (unlink (name_end + 4) || symlink (name, name_end + 4))) ERROR ((0, errno, _("Cannot symlink %s to %s"), name, name_end + 4)); else if (verbose_option) WARN ((0, 0, _("Symlinked %s to %s"), name, name_end + 4)); } #endif else ERROR ((0, 0, _("Unknown demangling command %s"), cursor)); cursor = next_cursor; } }
/* Miscellaneous functions, not really specific to GNU tar. Copyright (C) 1988, 92, 94, 95, 96, 97 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "system.h" #include "backupfile.h" #include "rmt.h" /* The following inclusion for crosschecking prototypes, only. */ #include "common.h" /* Handling strings. */ #define ISPRINT(Char) (ISASCII (Char) && isprint (Char)) /*-------------------------------------------------------------------------. | Assign STRING to a copy of VALUE if not NULL, or to NULL. If STRING was | | not NULL, it is freed first. | `-------------------------------------------------------------------------*/ void assign_string (char **string, const char *value) { if (*string) free (*string); *string = value ? xstrdup (value) : NULL; } /*------------------------------------------------------------------------. | Allocate a copy of the string quoted as in C, and returns that. If the | | string does not have to be quoted, it returns the NULL string. The | | allocated copy should normally be freed with free() after the caller is | | done with it. | | | | This is used in two contexts only: either listing a tar file for the | | --list (-t) option, or generating the directory file in incremental | | dumps. | `------------------------------------------------------------------------*/ char * quote_copy_string (const char *string) { const char *source = string; char *destination = NULL; char *buffer = NULL; int copying = 0; while (*source) { int character = (unsigned char) *source++; if (character == '\\') { if (!copying) { int length = (source - string) - 1; copying = 1; buffer = (char *) xmalloc (length + 5 + strlen (source) * 4); memcpy (buffer, string, (size_t) length); destination = buffer + length; } *destination++ = '\\'; *destination++ = '\\'; } else if (ISPRINT (character)) { if (copying) *destination++ = character; } else { if (!copying) { int length = (source - string) - 1; copying = 1; buffer = (char *) xmalloc (length + 5 + strlen (source) * 4); memcpy (buffer, string, (size_t) length); destination = buffer + length; } *destination++ = '\\'; switch (character) { case '\n': *destination++ = 'n'; break; case '\t': *destination++ = 't'; break; case '\f': *destination++ = 'f'; break; case '\b': *destination++ = 'b'; break; case '\r': *destination++ = 'r'; break; case '\177': *destination++ = '?'; break; default: *destination++ = (character >> 6) + '0'; *destination++ = ((character >> 3) & 07) + '0'; *destination++ = (character & 07) + '0'; break; } } } if (copying) { *destination = '\0'; return buffer; } return NULL; } /*-------------------------------------------------------------------------. | Takes a quoted C string (like those produced by quote_copy_string) and | | turns it back into the un-quoted original. This is done in place. | | Returns 0 only if the string was not properly quoted, but completes the | | unquoting anyway. | | | | This is used for reading the saved directory file in incremental dumps. | | It is used for decoding old `N' records (demangling names). But also, | | it is used for decoding file arguments, would they come from the shell | | or a -T file, and for decoding the --exclude argument. | `-------------------------------------------------------------------------*/ int unquote_string (char *string) { int result = 1; char *source = string; char *destination = string; while (*source) if (*source == '\\') switch (*++source) { case '\\': *destination++ = '\\'; source++; break; case 'n': *destination++ = '\n'; source++; break; case 't': *destination++ = '\t'; source++; break; case 'f': *destination++ = '\f'; source++; break; case 'b': *destination++ = '\b'; source++; break; case 'r': *destination++ = '\r'; source++; break; case '?': *destination++ = 0177; source++; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': { int value = *source++ - '0'; if (*source < '0' || *source > '7') { *destination++ = value; break; } value = value * 8 + *source++ - '0'; if (*source < '0' || *source > '7') { *destination++ = value; break; } value = value * 8 + *source++ - '0'; *destination++ = value; break; } default: result = 0; *destination++ = '\\'; if (*source) *destination++ = *source++; break; } else if (source != destination) *destination++ = *source++; else source++, destination++; if (source != destination) *destination = '\0'; return result; } /* Sorting lists. */ /*---. | ? | `---*/ char * merge_sort (char *list, int length, int offset, int (*compare) (char *, char *)) { char *first_list; char *second_list; int first_length; int second_length; char *result; char **merge_point; char *cursor; int counter; #define SUCCESSOR(Pointer) \ (*((char **) (((char *) (Pointer)) + offset))) if (length == 1) return list; if (length == 2) { if ((*compare) (list, SUCCESSOR (list)) > 0) { result = SUCCESSOR (list); SUCCESSOR (result) = list; SUCCESSOR (list) = NULL; return result; } return list; } first_list = list; first_length = (length + 1) / 2; second_length = length / 2; for (cursor = list, counter = first_length - 1; counter; cursor = SUCCESSOR (cursor), counter--) continue; second_list = SUCCESSOR (cursor); SUCCESSOR (cursor) = NULL; first_list = merge_sort (first_list, first_length, offset, compare); second_list = merge_sort (second_list, second_length, offset, compare); merge_point = &result; while (first_list && second_list) if ((*compare) (first_list, second_list) < 0) { cursor = SUCCESSOR (first_list); *merge_point = first_list; merge_point = &SUCCESSOR (first_list); first_list = cursor; } else { cursor = SUCCESSOR (second_list); *merge_point = second_list; merge_point = &SUCCESSOR (second_list); second_list = cursor; } if (first_list) *merge_point = first_list; else *merge_point = second_list; return result; #undef SUCCESSOR } /* File handling. */ /* Saved names in case backup needs to be undone. */ static char *before_backup_name = NULL; static char *after_backup_name = NULL; /*------------------------------------------------------------------------. | Returns nonzero if p is `.' or `..'. This could be a macro for speed. | `------------------------------------------------------------------------*/ /* Early Solaris 2.4 readdir may return d->d_name as `' in NFS-mounted directories. The workaround here skips `' just like `.'. Without it, GNU tar would then treat `' much like `.' and loop endlessly. */ int is_dot_or_dotdot (const char *p) { return (p[0] == '\0' || (p[0] == '.' && (p[1] == '\0' || (p[1] == '.' && p[2] == '\0')))); } /*-------------------------------------------------------------------------. | Delete PATH, whatever it might be. If RECURSE, first recursively delete | | the contents of PATH when it is a directory. Return zero on any error, | | with errno set. As a special case, if we fail to delete a directory | | when not RECURSE, do not set errno (just be tolerant to this error). | `-------------------------------------------------------------------------*/ int remove_any_file (const char *path, int recurse) { struct stat stat_buffer; if (lstat (path, &stat_buffer) < 0) return 0; if (S_ISDIR (stat_buffer.st_mode)) if (recurse) { DIR *dirp = opendir (path); struct dirent *dp; if (dirp == NULL) return 0; while (dp = readdir (dirp), dp && !is_dot_or_dotdot (dp->d_name)) { char *path_buffer = new_name (path, dp->d_name); if (!remove_any_file (path_buffer, 1)) { int saved_errno = errno; free (path_buffer); closedir (dirp); errno = saved_errno; /* FIXME: errno should be read-only */ return 0; } free (path_buffer); } closedir (dirp); return rmdir (path) >= 0; } else { /* FIXME: Saving errno might not be needed anymore, now that extract_archive tests for the special case before recovery. */ int saved_errno = errno; if (rmdir (path) >= 0) return 1; errno = saved_errno; /* FIXME: errno should be read-only */ return 0; } return unlink (path) >= 0; } /*-------------------------------------------------------------------------. | Check if PATH already exists and make a backup of it right now. Return | | success (nonzero) only if the backup in either unneeded, or successful. | | | | For now, directories are considered to never need backup. If ARCHIVE is | | nonzero, this is the archive and so, we do not have to backup block or | | character devices, nor remote entities. | `-------------------------------------------------------------------------*/ int maybe_backup_file (const char *path, int archive) { struct stat file_stat; /* Check if we really need to backup the file. */ if (archive && _remdev (path)) return 1; if (stat (path, &file_stat)) { if (errno == ENOENT) return 1; ERROR ((0, errno, "%s", path)); return 0; } if (S_ISDIR (file_stat.st_mode)) return 1; #ifdef S_ISBLK if (archive && S_ISBLK (file_stat.st_mode)) return 1; #endif #ifdef S_ISCHR if (archive && S_ISCHR (file_stat.st_mode)) return 1; #endif assign_string (&before_backup_name, path); /* A run situation may exist between Emacs or other GNU programs trying to make a backup for the same file simultaneously. If theoretically possible, real problems are unlikely. Doing any better would require a convention, GNU-wide, for all programs doing backups. */ assign_string (&after_backup_name, NULL); after_backup_name = find_backup_file_name (path); if (after_backup_name == NULL) FATAL_ERROR ((0, 0, "Virtual memory exhausted")); if (rename (before_backup_name, after_backup_name) == 0) { if (verbose_option) fprintf (stdlis, _("Renaming previous `%s' to `%s'\n"), before_backup_name, after_backup_name); return 1; } /* The backup operation failed. */ ERROR ((0, errno, _("%s: Cannot rename for backup"), before_backup_name)); assign_string (&after_backup_name, NULL); return 0; } /*-----------------------------------------------------------------------. | Try to restore the recently backed up file to its original name. This | | is usually only needed after a failed extraction. | `-----------------------------------------------------------------------*/ void undo_last_backup (void) { if (after_backup_name) { if (rename (after_backup_name, before_backup_name) != 0) ERROR ((0, errno, _("%s: Cannot rename from backup"), before_backup_name)); if (verbose_option) fprintf (stdlis, _("Renaming `%s' back to `%s'\n"), after_backup_name, before_backup_name); assign_string (&after_backup_name, NULL); } }
/* backupfile.h -- declarations for making Emacs style backup file names Copyright (C) 1990 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* When to make backup files. */ enum backup_type { /* Never make backups. */ none, /* Make simple backups of every file. */ simple, /* Make numbered backups of files that already have numbered backups, and simple backups of the others. */ numbered_existing, /* Make numbered backups of every file. */ numbered }; extern enum backup_type backup_type; extern char *simple_backup_suffix; #ifdef __STDC__ char *find_backup_file_name (const char *file); #else char *find_backup_file_name (); #endif
/* Various processing of names. Copyright (C) 1988, 1992, 1994, 1996, 1997 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "system.h" #include <pwd.h> #include <grp.h> #ifndef FNM_LEADING_DIR # include <fnmatch.h> #endif #include "common.h" /* User and group names. */ extern struct group *getgrnam (); extern struct passwd *getpwnam (); #if !HAVE_GETPWUID extern struct passwd *getpwuid (); #endif #if !HAVE_GETGRGID extern struct group *getgrgid (); #endif /* Make sure you link with the proper libraries if you are running the Yellow Peril (thanks for the good laugh, Ian J.!), or, euh... NIS. This code should also be modified for non-UNIX systems to do something reasonable. */ static char cached_uname[UNAME_FIELD_SIZE] = ""; static char cached_gname[GNAME_FIELD_SIZE] = ""; static uid_t cached_uid; /* valid only if cached_uname is not empty */ static gid_t cached_gid; /* valid only if cached_gname is not empty */ /*------------------------------------------. | Given UID, find the corresponding UNAME. | `------------------------------------------*/ void uid_to_uname (uid_t uid, char uname[UNAME_FIELD_SIZE]) { struct passwd *passwd; if (!cached_uname[0] || uid != cached_uid) { passwd = getpwuid (uid); if (passwd) { cached_uid = uid; strncpy (cached_uname, passwd->pw_name, UNAME_FIELD_SIZE); } else *uname = '\0'; } strncpy (uname, cached_uname, UNAME_FIELD_SIZE); } /*------------------------------------------. | Given GID, find the corresponding GNAME. | `------------------------------------------*/ void gid_to_gname (gid_t gid, char gname[GNAME_FIELD_SIZE]) { struct group *group; if (!cached_gname[0] || gid != cached_gid) { setgrent (); /* FIXME: why?! */ group = getgrgid (gid); if (group) { cached_gid = gid; strncpy (cached_gname, group->gr_name, GNAME_FIELD_SIZE); } else *gname = '\0'; } strncpy (gname, cached_gname, GNAME_FIELD_SIZE); } /*-------------------------------------------------------------------------. | Given UNAME, set the corresponding UID and return 1, or else, return 0. | `-------------------------------------------------------------------------*/ int uname_to_uid (char uname[UNAME_FIELD_SIZE], uid_t *uidp) { struct passwd *passwd; if (!cached_uname[0] || uname[0] != cached_uname[0] || strncmp (uname, cached_uname, UNAME_FIELD_SIZE) != 0) { passwd = getpwnam (uname); if (passwd) { cached_uid = passwd->pw_uid; strncpy (cached_uname, uname, UNAME_FIELD_SIZE); } else return 0; } *uidp = cached_uid; return 1; } /*-------------------------------------------------------------------------. | Given GNAME, set the corresponding GID and return 1, or else, return 0. | `-------------------------------------------------------------------------*/ int gname_to_gid (char gname[GNAME_FIELD_SIZE], gid_t *gidp) { struct group *group; if (!cached_gname[0] || gname[0] != cached_gname[0] || strncmp (gname, cached_gname, GNAME_FIELD_SIZE) != 0) { group = getgrnam (gname); if (group) { cached_gid = group->gr_gid; strncpy (cached_gname, gname, GNAME_FIELD_SIZE); } else return 0; } *gidp = cached_gid; return 1; } /* Names from the command call. */ static const char **name_array; /* store an array of names */ static int allocated_names; /* how big is the array? */ static int names; /* how many entries does it have? */ static int name_index = 0; /* how many of the entries have we scanned? */ /*------------------------. | Initialize structures. | `------------------------*/ void init_names (void) { allocated_names = 10; name_array = (const char **) xmalloc (sizeof (const char *) * allocated_names); names = 0; } /*--------------------------------------------------------------. | Add NAME at end of name_array, reallocating it as necessary. | `--------------------------------------------------------------*/ void name_add (const char *name) { if (names == allocated_names) { allocated_names *= 2; name_array = (const char **) xrealloc (name_array, sizeof (const char *) * allocated_names); } name_array[names++] = name; } /* Names from external name file. */ static FILE *name_file; /* file to read names from */ static char *name_buffer; /* buffer to hold the current file name */ static size_t name_buffer_length; /* allocated length of name_buffer */ /*---. | ? | `---*/ /* FIXME: I should better check more closely. It seems at first glance that is_pattern is only used when reading a file, and ignored for all command line arguments. */ static inline int is_pattern (const char *string) { return strchr (string, '*') || strchr (string, '[') || strchr (string, '?'); } /*-----------------------------------------------------------------------. | Set up to gather file names for tar. They can either come from a file | | or were saved from decoding arguments. | `-----------------------------------------------------------------------*/ void name_init (int argc, char *const *argv) { name_buffer = xmalloc (NAME_FIELD_SIZE + 2); name_buffer_length = NAME_FIELD_SIZE; if (files_from_option) { if (!strcmp (files_from_option, "-")) { request_stdin ("-T"); name_file = stdin; } else if (name_file = fopen (files_from_option, "r"), !name_file) FATAL_ERROR ((0, errno, _("Cannot open file %s"), name_file)); } } /*---. | ? | `---*/ void name_term (void) { free (name_buffer); free (name_array); } /*---------------------------------------------------------------------. | Read the next filename from name_file and null-terminate it. Put it | | into name_buffer, reallocating and adjusting name_buffer_length if | | necessary. Return 0 at end of file, 1 otherwise. | `---------------------------------------------------------------------*/ static int read_name_from_file (void) { int character; int counter = 0; /* FIXME: getc may be called even if character was EOF the last time here. */ /* FIXME: This + 2 allocation might serve no purpose. */ while (character = getc (name_file), character != EOF && character != filename_terminator) { if (counter == name_buffer_length) { name_buffer_length += NAME_FIELD_SIZE; name_buffer = xrealloc (name_buffer, name_buffer_length + 2); } name_buffer[counter++] = character; } if (counter == 0 && character == EOF) return 0; if (counter == name_buffer_length) { name_buffer_length += NAME_FIELD_SIZE; name_buffer = xrealloc (name_buffer, name_buffer_length + 2); } name_buffer[counter] = '\0'; return 1; } /*------------------------------------------------------------------------. | Get the next name from ARGV or the file of names. Result is in static | | storage and can't be relied upon across two calls. | | | | If CHANGE_DIRS is true, treat a filename of the form "-C" as meaning | | that the next filename is the name of a directory to change to. If | | `filename_terminator' is NUL, CHANGE_DIRS is effectively always false. | `------------------------------------------------------------------------*/ char * name_next (int change_dirs) { const char *source; char *cursor; int chdir_flag = 0; if (filename_terminator == '\0') change_dirs = 0; while (1) { /* Get a name, either from file or from saved arguments. */ if (name_file) { if (!read_name_from_file ()) break; } else { if (name_index == names) break; source = name_array[name_index++]; if (strlen (source) > name_buffer_length) { free (name_buffer); name_buffer_length = strlen (source); name_buffer = xmalloc (name_buffer_length + 2); } strcpy (name_buffer, source); } /* Zap trailing slashes. */ cursor = name_buffer + strlen (name_buffer) - 1; while (cursor > name_buffer && *cursor == '/') *cursor-- = '\0'; if (chdir_flag) { if (chdir (name_buffer) < 0) FATAL_ERROR ((0, errno, _("Cannot change to directory %s"), name_buffer)); chdir_flag = 0; } else if (change_dirs && strcmp (name_buffer, "-C") == 0) chdir_flag = 1; else #if 0 if (!exclude_option || !check_exclude (name_buffer)) #endif { unquote_string (name_buffer); return name_buffer; } } /* No more names in file. */ if (name_file && chdir_flag) FATAL_ERROR ((0, 0, _("Missing file name after -C"))); return NULL; } /*------------------------------. | Close the name file, if any. | `------------------------------*/ void name_close (void) { if (name_file != NULL && name_file != stdin) if (fclose (name_file) == EOF) ERROR ((0, errno, "%s", name_buffer)); } /*-------------------------------------------------------------------------. | Gather names in a list for scanning. Could hash them later if we really | | care. | | | | If the names are already sorted to match the archive, we just read them | | one by one. name_gather reads the first one, and it is called by | | name_match as appropriate to read the next ones. At EOF, the last name | | read is just left in the buffer. This option lets users of small | | machines extract an arbitrary number of files by doing "tar t" and | | editing down the list of files. | `-------------------------------------------------------------------------*/ void name_gather (void) { /* Buffer able to hold a single name. */ static struct name *buffer; static int allocated_length = 0; char *name; if (same_order_option) { if (allocated_length == 0) { allocated_length = sizeof (struct name) + NAME_FIELD_SIZE; buffer = (struct name *) xmalloc (allocated_length); /* FIXME: This memset is overkill, and ugly... */ memset (buffer, 0, allocated_length); } name = name_next (0); if (name) { if (strcmp (name, "-C") == 0) { char *copy = xstrdup (name_next (0)); name = name_next (0); if (!name) FATAL_ERROR ((0, 0, _("Missing file name after -C"))); buffer->change_dir = copy; } buffer->length = strlen (name); if (sizeof (struct name) + buffer->length >= allocated_length) { allocated_length = sizeof (struct name) + buffer->length; buffer = (struct name *) xrealloc (buffer, allocated_length); } strncpy (buffer->name, name, (size_t) buffer->length); buffer->name[buffer->length] = 0; buffer->next = NULL; buffer->found = 0; /* FIXME: Poorly named globals, indeed... */ namelist = buffer; namelast = namelist; } return; } /* Non sorted names -- read them all in. */ while (name = name_next (0), name) addname (name); } /*-----------------------------. | Add a name to the namelist. | `-----------------------------*/ void addname (const char *string) { /* FIXME: This is ugly. How is memory managed? */ static char *chdir_name = NULL; struct name *name; int length; if (strcmp (string, "-C") == 0) { chdir_name = xstrdup (name_next (0)); string = name_next (0); if (!chdir_name) FATAL_ERROR ((0, 0, _("Missing file name after -C"))); if (chdir_name[0] != '/') { char *path = xmalloc (PATH_MAX); /* FIXME: Shouldn't we use xgetcwd? */ #if HAVE_GETCWD if (!getcwd (path, PATH_MAX)) FATAL_ERROR ((0, 0, _("Could not get current directory"))); #else char *getwd (); if (!getwd (path)) FATAL_ERROR ((0, 0, _("Could not get current directory: %s"), path)); #endif chdir_name = new_name (path, chdir_name); free (path); } } length = string ? strlen (string) : 0; name = (struct name *) xmalloc ((size_t) (sizeof (struct name) + length)); memset (name, 0, sizeof (struct name) + length); name->next = NULL; if (string) { name->fake = 0; name->length = length; /* FIXME: Possibly truncating a string, here? Tss, tss, tss! */ strncpy (name->name, string, (size_t) length); name->name[length] = '\0'; } else name->fake = 1; name->found = 0; name->regexp = 0; /* assume not a regular expression */ name->firstch = 1; /* assume first char is literal */ name->change_dir = chdir_name; name->dir_contents = 0; if (string && is_pattern (string)) { name->regexp = 1; if (string[0] == '*' || string[0] == '[' || string[0] == '?') name->firstch = 0; } if (namelast) namelast->next = name; namelast = name; if (!namelist) namelist = name; } /*------------------------------------------------------------------------. | Return true if and only if name PATH (from an archive) matches any name | | from the namelist. | `------------------------------------------------------------------------*/ int name_match (const char *path) { int length = strlen (path); while (1) { struct name *cursor = namelist; if (!cursor) return 1; /* empty namelist is easy */ if (cursor->fake) { if (cursor->change_dir && chdir (cursor->change_dir)) FATAL_ERROR ((0, errno, _("Cannot change to directory %s"), cursor->change_dir)); namelist = 0; return 1; } for (; cursor; cursor = cursor->next) { /* If first chars don't match, quick skip. */ if (cursor->firstch && cursor->name[0] != path[0]) continue; /* Regular expressions (shell globbing, actually). */ if (cursor->regexp) { if (fnmatch (cursor->name, path, FNM_LEADING_DIR) == 0) { cursor->found = 1; /* remember it matched */ if (starting_file_option) { free (namelist); namelist = NULL; } if (cursor->change_dir && chdir (cursor->change_dir)) FATAL_ERROR ((0, errno, _("Cannot change to directory %s"), cursor->change_dir)); /* We got a match. */ return 1; } continue; } /* Plain Old Strings. */ if (cursor->length <= length /* archive length >= specified */ && (path[cursor->length] == '\0' || path[cursor->length] == '/') /* full match on file/dirname */ && strncmp (path, cursor->name, (size_t) cursor->length) == 0) /* name compare */ { cursor->found = 1; /* remember it matched */ if (starting_file_option) { free ((void *) namelist); namelist = 0; } if (cursor->change_dir && chdir (cursor->change_dir)) FATAL_ERROR ((0, errno, _("Cannot change to directory %s"), cursor->change_dir)); /* We got a match. */ return 1; } } /* Filename from archive not found in namelist. If we have the whole namelist here, just return 0. Otherwise, read the next name in and compare it. If this was the last name, namelist->found will remain on. If not, we loop to compare the newly read name. */ if (same_order_option && namelist->found) { name_gather (); /* read one more */ if (namelist->found) return 0; } else return 0; } } /*------------------------------------------------------------------. | Print the names of things in the namelist that were not matched. | `------------------------------------------------------------------*/ void names_notfound (void) { struct name *cursor; struct name *next; for (cursor = namelist; cursor; cursor = next) { next = cursor->next; if (!cursor->found && !cursor->fake) ERROR ((0, 0, _("%s: Not found in archive"), cursor->name)); /* We could free the list, but the process is about to die anyway, so save some CPU time. Amigas and other similarly broken software will need to waste the time, though. */ #ifdef amiga if (!same_order_option) free (cursor); #endif } namelist = (struct name *) NULL; namelast = (struct name *) NULL; if (same_order_option) { char *name; while (name = name_next (1), name) ERROR ((0, 0, _("%s: Not found in archive"), name)); } } /*---. | ? | `---*/ void name_expand (void) { } /*-------------------------------------------------------------------------. | This is like name_match, except that it returns a pointer to the name it | | matched, and doesn't set FOUND in structure. The caller will have to do | | that if it wants to. Oh, and if the namelist is empty, it returns NULL, | | unlike name_match, which returns TRUE. | `-------------------------------------------------------------------------*/ struct name * name_scan (const char *path) { int length = strlen (path); while (1) { struct name *cursor = namelist; if (!cursor) return NULL; /* empty namelist is easy */ for (; cursor; cursor = cursor->next) { /* If first chars don't match, quick skip. */ if (cursor->firstch && cursor->name[0] != path[0]) continue; /* Regular expressions. */ if (cursor->regexp) { if (fnmatch (cursor->name, path, FNM_LEADING_DIR) == 0) return cursor; /* we got a match */ continue; } /* Plain Old Strings. */ if (cursor->length <= length /* archive length >= specified */ && (path[cursor->length] == '\0' || path[cursor->length] == '/') /* full match on file/dirname */ && strncmp (path, cursor->name, (size_t) cursor->length) == 0) /* name compare */ return cursor; /* we got a match */ } /* Filename from archive not found in namelist. If we have the whole namelist here, just return 0. Otherwise, read the next name in and compare it. If this was the last name, namelist->found will remain on. If not, we loop to compare the newly read name. */ if (same_order_option && namelist->found) { name_gather (); /* read one more */ if (namelist->found) return NULL; } else return NULL; } } /*-----------------------------------------------------------------------. | This returns a name from the namelist which doesn't have ->found set. | | It sets ->found before returning, so successive calls will find and | | return all the non-found names in the namelist | `-----------------------------------------------------------------------*/ struct name *gnu_list_name = NULL; char * name_from_list (void) { if (!gnu_list_name) gnu_list_name = namelist; while (gnu_list_name && gnu_list_name->found) gnu_list_name = gnu_list_name->next; if (gnu_list_name) { gnu_list_name->found = 1; if (gnu_list_name->change_dir) if (chdir (gnu_list_name->change_dir) < 0) FATAL_ERROR ((0, errno, _("Cannot change to directory %s"), gnu_list_name->change_dir)); return gnu_list_name->name; } return NULL; } /*---. | ? | `---*/ void blank_name_list (void) { struct name *name; gnu_list_name = 0; for (name = namelist; name; name = name->next) name->found = 0; } /*---. | ? | `---*/ char * new_name (const char *path, const char *name) { char *buffer = (char *) xmalloc (strlen (path) + strlen (name) + 2); sprintf (buffer, "%s/%s", path, name); return buffer; } /* Excludes names. */ static char *exclude_pool = NULL; static int exclude_pool_size = 0; static int allocated_exclude_pool_size = 0; static char **simple_exclude_array = NULL; static int simple_excludes = 0; static int allocated_simple_excludes = 0; static char **pattern_exclude_array = NULL; static int pattern_excludes = 0; static int allocated_pattern_excludes = 0; /*---. | ? | `---*/ void add_exclude (char *name) { int name_size; unquote_string (name); /* FIXME: unquote in all cases? If ever? */ name_size = strlen (name) + 1; if (exclude_pool_size + name_size > allocated_exclude_pool_size) { char *previous_exclude_pool = exclude_pool; char **cursor; allocated_exclude_pool_size = exclude_pool_size + name_size + 1024; exclude_pool = (char *) xrealloc (exclude_pool, (size_t) allocated_exclude_pool_size); for (cursor = simple_exclude_array; cursor < simple_exclude_array + simple_excludes; cursor++) *cursor = exclude_pool + (*cursor - previous_exclude_pool); for (cursor = pattern_exclude_array; cursor < pattern_exclude_array + pattern_excludes; cursor++) *cursor = exclude_pool + (*cursor - previous_exclude_pool); } if (is_pattern (name)) { if (pattern_excludes == allocated_pattern_excludes) { allocated_pattern_excludes += 32; pattern_exclude_array = (char **) xrealloc (pattern_exclude_array, allocated_pattern_excludes * sizeof (char *)); } pattern_exclude_array[pattern_excludes++] = exclude_pool + exclude_pool_size; } else { if (simple_excludes == allocated_simple_excludes) { allocated_simple_excludes += 32; simple_exclude_array = (char **) xrealloc (simple_exclude_array, allocated_simple_excludes * sizeof (char *)); } simple_exclude_array[simple_excludes++] = exclude_pool + exclude_pool_size; } strcpy (exclude_pool + exclude_pool_size, name); exclude_pool_size += name_size; } /*---. | ? | `---*/ void add_exclude_file (const char *name) { FILE *file; char buffer[1024]; if (strcmp (name, "-")) file = fopen (name, "r"); else { request_stdin ("-X"); file = stdin; } if (!file) FATAL_ERROR ((0, errno, _("Cannot open %s"), name)); while (fgets (buffer, 1024, file)) { char *end_of_line = strrchr (buffer, '\n'); if (end_of_line) *end_of_line = '\0'; add_exclude (buffer); } if (fclose (file) == EOF) ERROR ((0, errno, "%s", name)); } /*------------------------------------------------------------------. | Returns true if the file NAME should not be added nor extracted. | `------------------------------------------------------------------*/ int check_exclude (const char *name) { int counter; for (counter = 0; counter < pattern_excludes; counter++) if (fnmatch (pattern_exclude_array[counter], name, FNM_LEADING_DIR) == 0) return 1; for (counter = 0; counter < simple_excludes; counter++) { /* Accept the output from strstr only if it is the last part of the string. FIXME: Find a faster way to do this. */ char *string = strstr (name, simple_exclude_array[counter]); if (string && (string == name || string[-1] == '/') && string[strlen (simple_exclude_array[counter])] == '\0') return 1; } return 0; }
/* Copyright (C) 1991, 1992, 1993, 1996 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef _FNMATCH_H #define _FNMATCH_H 1 #ifdef __cplusplus extern "C" { #endif #if (defined (__cplusplus) || (defined (__STDC__) && __STDC__) \ || defined (WIN32)) #undef __P #define __P(protos) protos #else /* Not C++ or ANSI C. */ #undef __P #define __P(protos) () /* We can get away without defining `const' here only because in this file it is used only inside the prototype for `fnmatch', which is elided in non-ANSI C where `const' is problematical. */ #endif /* C++ or ANSI C. */ /* We #undef these before defining them because some losing systems (HP-UX A.08.07 for example) define these in <unistd.h>. */ #undef FNM_PATHNAME #undef FNM_NOESCAPE #undef FNM_PERIOD /* Bits set in the FLAGS argument to `fnmatch'. */ #define FNM_PATHNAME (1 << 0) /* No wildcard can ever match `/'. */ #define FNM_NOESCAPE (1 << 1) /* Backslashes don't quote special chars. */ #define FNM_PERIOD (1 << 2) /* Leading `.' is matched only explicitly. */ #if !defined (_POSIX_C_SOURCE) || _POSIX_C_SOURCE < 2 || defined (_GNU_SOURCE) #define FNM_FILE_NAME FNM_PATHNAME /* Preferred GNU name. */ #define FNM_LEADING_DIR (1 << 3) /* Ignore `/...' after a match. */ #define FNM_CASEFOLD (1 << 4) /* Compare without regard to case. */ #endif /* Value returned by `fnmatch' if STRING does not match PATTERN. */ #define FNM_NOMATCH 1 /* Match STRING against the filename pattern PATTERN, returning zero if it matches, FNM_NOMATCH if not. */ extern int fnmatch __P ((const char *__pattern, const char *__string, int __flags)); #ifdef __cplusplus } #endif #endif /* fnmatch.h */
/* Defines for Sys V style 3-argument open call. Copyright (C) 1988, 1994, 1995, 1996 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "system.h" #if EMUL_OPEN3 /* open3.h -- #defines for the various flags for the Sys V style 3-argument open() call. On BSD or System 5, the system already has this in an include file. This file is needed for V7 and MINIX systems for the benefit of open3() in port.c, a routine that emulates the 3-argument call using system calls available on V7/MINIX. Written 1987-06-10 by Richard Todd. The names have been changed by John Gilmore, 1987-07-31, since Richard called it "bsdopen", and really this change was introduced in AT&T Unix systems before BSD picked it up. */ /*-----------------------------------------------------------------------. | open3 -- routine to emulate the 3-argument open system. | | | | open3 (path, flag, mode); | | | | Attempts to open the file specified by the given pathname. The | | following flag bits specify options to the routine. Needless to say, | | you should only specify one of the first three. Function returns file | | descriptor if successful, -1 and errno if not. | `-----------------------------------------------------------------------*/ /* The routine obeys the following mode arguments: O_RDONLY file open for read only O_WRONLY file open for write only O_RDWR file open for both read & write O_CREAT file is created with specified mode if it needs to be O_TRUNC if file exists, it is truncated to 0 bytes O_EXCL used with O_CREAT--routine returns error if file exists */ /* Call that if present in most modern Unix systems. This version attempts to support all the flag bits except for O_NDELAY and O_APPEND, which are silently ignored. The emulation is not as efficient as the real thing (at worst, 4 system calls instead of one), but there's not much I can do about that. */ /* Array to give arguments to access for various modes FIXME, this table depends on the specific integer values of O_*, and also contains integers (args to 'access') that should be #define's. */ static int modes[] = { 04, /* O_RDONLY */ 02, /* O_WRONLY */ 06, /* O_RDWR */ 06, /* invalid, just cope: O_WRONLY+O_RDWR */ }; /* Shut off the automatic emulation of open(), we'll need it. */ #undef open int open3 (char *path, int flags, int mode) { int exists = 1; int call_creat = 0; /* We actually do the work by calling the open() or creat() system call, depending on the flags. Call_creat is true if we will use creat(), false if we will use open(). */ /* See if the file exists and is accessible in the requested mode. Strictly speaking we shouldn't be using access, since access checks against real uid, and the open call should check against euid. Most cases real uid == euid, so it won't matter. FIXME. FIXME, the construction "flags & 3" and the modes table depends on the specific integer values of the O_* #define's. Foo! */ if (access (path, modes[flags & 3]) < 0) { if (errno == ENOENT) { /* The file does not exist. */ exists = 0; } else { /* Probably permission violation. */ if (flags & O_EXCL) { /* Oops, the file exists, we didn't want it. No matter what the error, claim EEXIST. */ errno = EEXIST; /* FIXME: errno should be read-only */ } return -1; } } /* If we have the O_CREAT bit set, check for O_EXCL. */ if (flags & O_CREAT) { if ((flags & O_EXCL) && exists) { /* Oops, the file exists and we didn't want it to. */ errno = EEXIST; /* FIXME: errno should be read-only */ return -1; } /* If the file doesn't exist, be sure to call creat() so that it will be created with the proper mode. */ if (!exists) call_creat = 1; } else { /* If O_CREAT isn't set and the file doesn't exist, error. */ if (!exists) { errno = ENOENT; /* FIXME: errno should be read-only */ return -1; } } /* If the O_TRUNC flag is set and the file exists, we want to call creat() anyway, since creat() guarantees that the file will be truncated and open()-for-writing doesn't. (If the file doesn't exist, we're calling creat() anyway and the file will be created with zero length.) */ if ((flags & O_TRUNC) && exists) call_creat = 1; /* Actually do the call. */ if (call_creat) /* Call creat. May have to close and reopen the file if we want O_RDONLY or O_RDWR access -- creat() only gives O_WRONLY. */ { int fd = creat (path, mode); if (fd < 0 || (flags & O_WRONLY)) return fd; if (close (fd) < 0) return -1; /* Fall out to reopen the file we've created. */ } /* Calling old open, we strip most of the new flags just in case. */ return open (path, flags & (O_RDONLY | O_WRONLY | O_RDWR | O_BINARY)); } #endif /* EMUL_OPEN3 */
/* Functions for communicating with a remote tape drive. Copyright (C) 1988, 1992, 1994, 1996 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* The man page rmt(8) for /etc/rmt documents the remote mag tape protocol which rdump and rrestore use. Unfortunately, the man page is *WRONG*. The author of the routines I'm including originally wrote his code just based on the man page, and it didn't work, so he went to the rdump source to figure out why. The only thing he had to change was to check for the 'F' return code in addition to the 'E', and to separate the various arguments with \n instead of a space. I personally don't think that this is much of a problem, but I wanted to point it out. -- Arnold Robbins Originally written by Jeff Lee, modified some by Arnold Robbins. Redone as a library that can replace open, read, write, etc., by Fred Fish, with some additional work by Arnold Robbins. Modified to make all rmt* calls into macros for speed by Jay Fenlason. Use -DHAVE_NETDB_H for rexec code, courtesy of Dan Kegel. */ #include "system.h" /* Try hard to get EOPNOTSUPP defined. 486/ISC has it in net/errno.h, 3B2/SVR3 has it in sys/inet.h. Otherwise, like on MSDOS, use EINVAL. */ #ifndef EOPNOTSUPP # if HAVE_NET_ERRNO_H # include <net/errno.h> # endif # if HAVE_SYS_INET_H # include <sys/inet.h> # endif # ifndef EOPNOTSUPP # define EOPNOTSUPP EINVAL # endif #endif #include <signal.h> #if HAVE_NETDB_H # include <netdb.h> #endif #include "rmt.h" /* FIXME: Just to shut up -Wall. */ int rexec (); /* Exit status if exec errors. */ #define EXIT_ON_EXEC_ERROR 128 /* FIXME: Size of buffers for reading and writing commands to rmt. */ #define COMMAND_BUFFER_SIZE 64 #ifndef RETSIGTYPE # define RETSIGTYPE void #endif /* FIXME: Maximum number of simultaneous remote tape connections. */ #define MAXUNIT 4 #define PREAD 0 /* read file descriptor from pipe() */ #define PWRITE 1 /* write file descriptor from pipe() */ /* Return the parent's read side of remote tape connection Fd. */ #define READ_SIDE(Fd) (from_remote[Fd][PREAD]) /* Return the parent's write side of remote tape connection Fd. */ #define WRITE_SIDE(Fd) (to_remote[Fd][PWRITE]) /* The pipes for receiving data from remote tape drives. */ static int from_remote[MAXUNIT][2] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}}; /* The pipes for sending data to remote tape drives. */ static int to_remote[MAXUNIT][2] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}}; /* Temporary variable used by macros in rmt.h. */ char *rmt_path__; /*----------------------------------------------------------------------. | Close remote tape connection HANDLE, and reset errno to ERRNO_VALUE. | `----------------------------------------------------------------------*/ static void _rmt_shutdown (int handle, int errno_value) { close (READ_SIDE (handle)); close (WRITE_SIDE (handle)); READ_SIDE (handle) = -1; WRITE_SIDE (handle) = -1; errno = errno_value; /* FIXME: errno should be read-only */ } /*-------------------------------------------------------------------------. | Attempt to perform the remote tape command specified in BUFFER on remote | | tape connection HANDLE. Return 0 if successful, -1 on error. | `-------------------------------------------------------------------------*/ static int do_command (int handle, const char *buffer) { int length; RETSIGTYPE (*pipe_handler) (); /* Save the current pipe handler and try to make the request. */ pipe_handler = signal (SIGPIPE, SIG_IGN); length = strlen (buffer); if (write (WRITE_SIDE (handle), buffer, (size_t) length) == length) { signal (SIGPIPE, pipe_handler); return 0; } /* Something went wrong. Close down and go home. */ signal (SIGPIPE, pipe_handler); _rmt_shutdown (handle, EIO); return -1; } /*----------------------------------------------------------------------. | Read and return the status from remote tape connection HANDLE. If an | | error occurred, return -1 and set errno. | `----------------------------------------------------------------------*/ static int get_status (int handle) { char command_buffer[COMMAND_BUFFER_SIZE]; char *cursor; int counter; /* Read the reply command line. */ for (counter = 0, cursor = command_buffer; counter < COMMAND_BUFFER_SIZE; counter++, cursor++) { if (read (READ_SIDE (handle), cursor, 1) != 1) { _rmt_shutdown (handle, EIO); return -1; } if (*cursor == '\n') { *cursor = '\0'; break; } } if (counter == COMMAND_BUFFER_SIZE) { _rmt_shutdown (handle, EIO); return -1; } /* Check the return status. */ for (cursor = command_buffer; *cursor; cursor++) if (*cursor != ' ') break; if (*cursor == 'E' || *cursor == 'F') { errno = atoi (cursor + 1); /* FIXME: errno should be read-only */ /* Skip the error message line. */ /* FIXME: there is better to do than merely ignoring error messages coming from the remote end. Translate them, too... */ { char character; while (read (READ_SIDE (handle), &character, 1) == 1) if (character == '\n') break; } if (*cursor == 'F') _rmt_shutdown (handle, errno); return -1; } /* Check for mis-synced pipes. */ if (*cursor != 'A') { _rmt_shutdown (handle, EIO); return -1; } /* Got an `A' (success) response. */ return atoi (cursor + 1); } #if HAVE_NETDB_H /*-------------------------------------------------------------------------. | Execute /etc/rmt as user USER on remote system HOST using rexec. Return | | a file descriptor of a bidirectional socket for stdin and stdout. If | | USER is NULL, use the current username. | | | | By default, this code is not used, since it requires that the user have | | a .netrc file in his/her home directory, or that the application | | designer be willing to have rexec prompt for login and password info. | | This may be unacceptable, and .rhosts files for use with rsh are much | | more common on BSD systems. | `-------------------------------------------------------------------------*/ static int _rmt_rexec (char *host, char *user) { int saved_stdin = dup (fileno (stdin)); int saved_stdout = dup (fileno (stdout)); struct servent *rexecserv; int result; /* When using cpio -o < filename, stdin is no longer the tty. But the rexec subroutine reads the login and the passwd on stdin, to allow remote execution of the command. So, reopen stdin and stdout on /dev/tty before the rexec and give them back their original value after. */ if (freopen ("/dev/tty", "r", stdin) == NULL) freopen ("/dev/null", "r", stdin); if (freopen ("/dev/tty", "w", stdout) == NULL) freopen ("/dev/null", "w", stdout); if (rexecserv = getservbyname ("exec", "tcp"), !rexecserv) error (EXIT_ON_EXEC_ERROR, 0, _("exec/tcp: Service not available")); result = rexec (&host, rexecserv->s_port, user, NULL, "/etc/rmt", (int *) NULL); if (fclose (stdin) == EOF) error (0, errno, _("stdin")); fdopen (saved_stdin, "r"); if (fclose (stdout) == EOF) error (0, errno, _("stdout")); fdopen (saved_stdout, "w"); return result; } #endif /* HAVE_NETDB_H */ /*------------------------------------------------------------------------. | Open a file (a magnetic tape device?) on the system specified in PATH, | | as the given user. PATH has the form `[USER@]HOST:FILE'. OPEN_MODE is | | O_RDONLY, O_WRONLY, etc. If successful, return the remote pipe number | | plus BIAS. REMOTE_SHELL may be overriden. On error, return -1. | `------------------------------------------------------------------------*/ int rmt_open__ (const char *path, int open_mode, int bias, const char *remote_shell) { int remote_pipe_number; /* pseudo, biased file descriptor */ char *path_copy ; /* copy of path string */ char *remote_host; /* remote host name */ char *remote_file; /* remote file name (often a device) */ char *remote_user; /* remote user name */ /* Find an unused pair of file descriptors. */ for (remote_pipe_number = 0; remote_pipe_number < MAXUNIT; remote_pipe_number++) if (READ_SIDE (remote_pipe_number) == -1 && WRITE_SIDE (remote_pipe_number) == -1) break; if (remote_pipe_number == MAXUNIT) { errno = EMFILE; /* FIXME: errno should be read-only */ return -1; } /* Pull apart the system and device, and optional user. */ { char *cursor; path_copy = xstrdup (path); remote_host = path_copy; remote_user = NULL; remote_file = NULL; for (cursor = path_copy; *cursor; cursor++) switch (*cursor) { default: break; case '@': if (!remote_user) { remote_user = remote_host; *cursor = '\0'; remote_host = cursor + 1; } break; case ':': if (!remote_file) { *cursor = '\0'; remote_file = cursor + 1; } break; } } /* FIXME: Should somewhat validate the decoding, here. */ if (remote_user && *remote_user == '\0') remote_user = NULL; #if HAVE_NETDB_H /* Execute the remote command using rexec. */ READ_SIDE (remote_pipe_number) = _rmt_rexec (remote_host, remote_user); if (READ_SIDE (remote_pipe_number) < 0) { free (path_copy); return -1; } WRITE_SIDE (remote_pipe_number) = READ_SIDE (remote_pipe_number); #else /* not HAVE_NETDB_H */ { const char *remote_shell_basename; int status; /* Identify the remote command to be executed. */ if (!remote_shell) { #ifdef REMOTE_SHELL remote_shell = REMOTE_SHELL; #else errno = EIO; /* FIXME: errno should be read-only */ free (path_copy); return -1; #endif } remote_shell_basename = strrchr (remote_shell, '/'); if (remote_shell_basename) remote_shell_basename++; else remote_shell_basename = remote_shell; /* Set up the pipes for the `rsh' command, and fork. */ if (pipe (to_remote[remote_pipe_number]) == -1 || pipe (from_remote[remote_pipe_number]) == -1) { free (path_copy); return -1; } status = fork (); if (status == -1) { free (path_copy); return -1; } if (status == 0) { /* Child. */ close (0); dup (to_remote[remote_pipe_number][PREAD]); close (to_remote[remote_pipe_number][PREAD]); close (to_remote[remote_pipe_number][PWRITE]); close (1); dup (from_remote[remote_pipe_number][PWRITE]); close (from_remote[remote_pipe_number][PREAD]); close (from_remote[remote_pipe_number][PWRITE]); #if !MSDOS setuid (getuid ()); setgid (getgid ()); #endif if (remote_user) execl (remote_shell, remote_shell_basename, remote_host, "-l", remote_user, "/etc/rmt", (char *) 0); else execl (remote_shell, remote_shell_basename, remote_host, "/etc/rmt", (char *) 0); /* Bad problems if we get here. */ /* In a previous version, _exit was used here instead of exit. */ error (EXIT_ON_EXEC_ERROR, errno, _("Cannot execute remote shell")); } /* Parent. */ close (from_remote[remote_pipe_number][PWRITE]); close (to_remote[remote_pipe_number][PREAD]); } #endif /* not HAVE_NETDB_H */ /* Attempt to open the tape device. */ { char command_buffer[COMMAND_BUFFER_SIZE]; sprintf (command_buffer, "O%s\n%d\n", remote_file, open_mode); if (do_command (remote_pipe_number, command_buffer) == -1 || get_status (remote_pipe_number) == -1) { _rmt_shutdown (remote_pipe_number, errno); free (path_copy); return -1; } } free (path_copy); return remote_pipe_number + bias; } /*----------------------------------------------------------------. | Close remote tape connection HANDLE and shut down. Return 0 if | | successful, -1 on error. | `----------------------------------------------------------------*/ int rmt_close__ (int handle) { int status; if (do_command (handle, "C\n") == -1) return -1; status = get_status (handle); _rmt_shutdown (handle, errno); return status; } /*-------------------------------------------------------------------------. | Read up to LENGTH bytes into BUFFER from remote tape connection HANDLE. | | Return the number of bytes read on success, -1 on error. | `-------------------------------------------------------------------------*/ int rmt_read__ (int handle, char *buffer, unsigned int length) { char command_buffer[COMMAND_BUFFER_SIZE]; int status; int counter; sprintf (command_buffer, "R%d\n", length); if (do_command (handle, command_buffer) == -1 || (status = get_status (handle)) == -1) return -1; for (counter = 0; counter < status; counter += length, buffer += length) { length = read (READ_SIDE (handle), buffer, (size_t) (status - counter)); if (length <= 0) { _rmt_shutdown (handle, EIO); return -1; } } return status; } /*-------------------------------------------------------------------------. | Write LENGTH bytes from BUFFER to remote tape connection HANDLE. Return | | the number of bytes written on success, -1 on error. | `-------------------------------------------------------------------------*/ int rmt_write__ (int handle, char *buffer, unsigned int length) { char command_buffer[COMMAND_BUFFER_SIZE]; RETSIGTYPE (*pipe_handler) (); sprintf (command_buffer, "W%d\n", length); if (do_command (handle, command_buffer) == -1) return -1; pipe_handler = signal (SIGPIPE, SIG_IGN); if (write (WRITE_SIDE (handle), buffer, length) == length) { signal (SIGPIPE, pipe_handler); return get_status (handle); } /* Write error. */ signal (SIGPIPE, pipe_handler); _rmt_shutdown (handle, EIO); return -1; } /*------------------------------------------------------------------------. | Perform an imitation lseek operation on remote tape connection HANDLE. | | Return the new file offset if successful, -1 if on error. | `------------------------------------------------------------------------*/ long rmt_lseek__ (int handle, off_t offset, int whence) { char command_buffer[COMMAND_BUFFER_SIZE]; sprintf (command_buffer, "L%ld\n%d\n", offset, whence); if (do_command (handle, command_buffer) == -1) return -1; return get_status (handle); } /*-----------------------------------------------------------------------. | Perform a raw tape operation on remote tape connection HANDLE. Return | | the results of the ioctl, or -1 on error. | `-----------------------------------------------------------------------*/ int rmt_ioctl__ (int handle, int operation, char *argument) { switch (operation) { default: errno = EOPNOTSUPP; /* FIXME: errno should be read-only */ return -1; #ifdef MTIOCTOP case MTIOCTOP: { char command_buffer[COMMAND_BUFFER_SIZE]; /* MTIOCTOP is the easy one. Nothing is transfered in binary. */ sprintf (command_buffer, "I%d\n%d\n", ((struct mtop *) argument)->mt_op, ((struct mtop *) argument)->mt_count); if (do_command (handle, command_buffer) == -1) return -1; /* Return the count. */ return get_status (handle); } #endif /* MTIOCTOP */ #ifdef MTIOCGET case MTIOCGET: { int status; int counter; /* Grab the status and read it directly into the structure. This assumes that the status buffer is not padded and that 2 shorts fit in a long without any word alignment problems; i.e., the whole struct is contiguous. NOTE - this is probably NOT a good assumption. */ if (do_command (handle, "S") == -1 || (status = get_status (handle), status == -1)) return -1; for (; status > 0; status -= counter, argument += counter) { counter = read (READ_SIDE (handle), argument, (size_t) status); if (counter <= 0) { _rmt_shutdown (handle, EIO); return -1; } } /* Check for byte position. mt_type (or mt_model) is a small integer field (normally) so we will check its magnitude. If it is larger than 256, we will assume that the bytes are swapped and go through and reverse all the bytes. */ if (((struct mtget *) argument)->MTIO_CHECK_FIELD < 256) return 0; for (counter = 0; counter < status; counter += 2) { char copy = argument[counter]; argument[counter] = argument[counter + 1]; argument[counter + 1] = copy; } return 0; } #endif /* MTIOCGET */ } }
/* A tar (tape archiver) program. Copyright (C) 1988, 92, 93, 94, 95, 96, 97 Free Software Foundation, Inc. Written by John Gilmore, starting 1985-08-25. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "system.h" #include <getopt.h> /* The following causes "common.h" to produce definitions of all the global variables, rather than just "extern" declarations of them. GNU tar does depend on the system loader to preset all GLOBAL variables to neutral (or zero) values, explicit initialisation is usually not done. */ #define GLOBAL #include "common.h" #include "backupfile.h" enum backup_type get_version (); /* FIXME: We should use a conversion routine that does reasonable error checking -- atoi doesn't. For now, punt. */ #define intconv atoi time_t get_date (); /* Local declarations. */ #ifndef DEFAULT_ARCHIVE # define DEFAULT_ARCHIVE "tar.out" #endif #ifndef DEFAULT_BLOCKING # define DEFAULT_BLOCKING 20 #endif static void usage PARAMS ((int)); /* Miscellaneous. */ /*------------------------------------------------------------------------. | Check if STRING is the decimal representation of number, and return its | | value. If not a decimal number, return -1. | `------------------------------------------------------------------------*/ static int check_decimal (const char *string) { int value = -1; while (*string) switch (*string) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': value = value < 0 ? *string - '0' : 10 * value + *string - '0'; string++; break; default: return -1; } return value; } /*----------------------------------------------. | Doesn't return if stdin already requested. | `----------------------------------------------*/ /* Name of option using stdin. */ static const char *stdin_used_by = NULL; void request_stdin (const char *option) { if (stdin_used_by) USAGE_ERROR ((0, 0, _("Options `-%s' and `-%s' both want standard input"), stdin_used_by, option)); stdin_used_by = option; } /*--------------------------------------------------------. | Returns true if and only if the user typed 'y' or 'Y'. | `--------------------------------------------------------*/ int confirm (const char *message_action, const char *message_name) { static FILE *confirm_file = NULL; if (!confirm_file) { if (archive == 0 || stdin_used_by) confirm_file = fopen (TTY_NAME, "r"); else { request_stdin ("-w"); confirm_file = stdin; } if (!confirm_file) FATAL_ERROR ((0, 0, _("Cannot read confirmation from user"))); } fprintf (stdlis, "%s %s?", message_action, message_name); fflush (stdlis); { int reply = getc (confirm_file); int character; for (character = reply; character != '\n' && character != EOF; character = getc (confirm_file)) continue; return reply == 'y' || reply == 'Y'; } } /* Options. */ /* For long options that unconditionally set a single flag, we have getopt do it. For the others, we share the code for the equivalent short named option, the name of which is stored in the otherwise-unused `val' field of the `struct option'; for long options that have no equivalent short option, we use nongraphic characters as pseudo short option characters, starting at 2 and going upwards. */ #define BACKUP_OPTION 2 #define DELETE_OPTION 3 #define EXCLUDE_OPTION 4 #define GROUP_OPTION 5 #define MODE_OPTION 6 #define NEWER_MTIME_OPTION 7 #define NO_RECURSE_OPTION 8 #define NULL_OPTION 9 #define OWNER_OPTION 10 #define POSIX_OPTION 11 #define PRESERVE_OPTION 12 #define RECORD_SIZE_OPTION 13 #define RSH_COMMAND_OPTION 14 #define SUFFIX_OPTION 15 #define USE_COMPRESS_PROGRAM_OPTION 16 #define VOLNO_FILE_OPTION 17 /* Some cleanup is being made in GNU tar long options. Using old names is allowed for a while, but will also send a warning to stderr. Take old names out in 1.14, or in summer 1997, whichever happens last. We use nongraphic characters as pseudo short option characters, starting at 31 and going downwards. */ #define OBSOLETE_ABSOLUTE_NAMES 31 #define OBSOLETE_BLOCK_COMPRESS 30 #define OBSOLETE_BLOCKING_FACTOR 29 #define OBSOLETE_BLOCK_NUMBER 28 #define OBSOLETE_READ_FULL_RECORDS 27 #define OBSOLETE_TOUCH 26 #define OBSOLETE_VERSION_CONTROL 25 /* If nonzero, display usage information and exit. */ static int show_help = 0; /* If nonzero, print the version on standard output and exit. */ static int show_version = 0; struct option long_options[] = { {"absolute-names", no_argument, NULL, 'P'}, {"absolute-paths", no_argument, NULL, OBSOLETE_ABSOLUTE_NAMES}, {"after-date", required_argument, NULL, 'N'}, {"append", no_argument, NULL, 'r'}, {"atime-preserve", no_argument, &atime_preserve_option, 1}, {"backup", optional_argument, NULL, BACKUP_OPTION}, {"block-compress", no_argument, NULL, OBSOLETE_BLOCK_COMPRESS}, {"block-number", no_argument, NULL, 'R'}, {"block-size", required_argument, NULL, OBSOLETE_BLOCKING_FACTOR}, {"blocking-factor", required_argument, NULL, 'b'}, {"catenate", no_argument, NULL, 'A'}, {"checkpoint", no_argument, &checkpoint_option, 1}, {"compare", no_argument, NULL, 'd'}, {"compress", no_argument, NULL, 'Z'}, {"concatenate", no_argument, NULL, 'A'}, {"confirmation", no_argument, NULL, 'w'}, /* FIXME: --selective as a synonym for --confirmation? */ {"create", no_argument, NULL, 'c'}, {"delete", no_argument, NULL, DELETE_OPTION}, {"dereference", no_argument, NULL, 'h'}, {"diff", no_argument, NULL, 'd'}, {"directory", required_argument, NULL, 'C'}, {"exclude", required_argument, NULL, EXCLUDE_OPTION}, {"exclude-from", required_argument, NULL, 'X'}, {"extract", no_argument, NULL, 'x'}, {"file", required_argument, NULL, 'f'}, {"files-from", required_argument, NULL, 'T'}, {"force-local", no_argument, &force_local_option, 1}, {"get", no_argument, NULL, 'x'}, {"group", required_argument, NULL, GROUP_OPTION}, {"gunzip", no_argument, NULL, 'z'}, {"gzip", no_argument, NULL, 'z'}, {"help", no_argument, &show_help, 1}, {"ignore-failed-read", no_argument, &ignore_failed_read_option, 1}, {"ignore-zeros", no_argument, NULL, 'i'}, /* FIXME: --ignore-end as a new name for --ignore-zeros? */ {"incremental", no_argument, NULL, 'G'}, {"info-script", required_argument, NULL, 'F'}, {"interactive", no_argument, NULL, 'w'}, {"keep-old-files", no_argument, NULL, 'k'}, {"label", required_argument, NULL, 'V'}, {"list", no_argument, NULL, 't'}, {"listed-incremental", required_argument, NULL, 'g'}, {"mode", required_argument, NULL, MODE_OPTION}, {"modification-time", no_argument, NULL, OBSOLETE_TOUCH}, {"multi-volume", no_argument, NULL, 'M'}, {"new-volume-script", required_argument, NULL, 'F'}, {"newer", required_argument, NULL, 'N'}, {"newer-mtime", required_argument, NULL, NEWER_MTIME_OPTION}, {"null", no_argument, NULL, NULL_OPTION}, {"no-recursion", no_argument, NULL, NO_RECURSE_OPTION}, {"numeric-owner", no_argument, &numeric_owner_option, 1}, {"old-archive", no_argument, NULL, 'o'}, {"one-file-system", no_argument, NULL, 'l'}, {"owner", required_argument, NULL, OWNER_OPTION}, {"portability", no_argument, NULL, 'o'}, {"posix", no_argument, NULL, POSIX_OPTION}, {"preserve", no_argument, NULL, PRESERVE_OPTION}, {"preserve-order", no_argument, NULL, 's'}, {"preserve-permissions", no_argument, NULL, 'p'}, {"recursive-unlink", no_argument, &recursive_unlink_option, 1}, {"read-full-blocks", no_argument, NULL, OBSOLETE_READ_FULL_RECORDS}, {"read-full-records", no_argument, NULL, 'B'}, /* FIXME: --partial-blocks might be a synonym for --read-full-records? */ {"record-number", no_argument, NULL, OBSOLETE_BLOCK_NUMBER}, {"record-size", required_argument, NULL, RECORD_SIZE_OPTION}, {"remove-files", no_argument, &remove_files_option, 1}, {"rsh-command", required_argument, NULL, RSH_COMMAND_OPTION}, {"same-order", no_argument, NULL, 's'}, {"same-owner", no_argument, &same_owner_option, 1}, {"same-permissions", no_argument, NULL, 'p'}, {"show-omitted-dirs", no_argument, &show_omitted_dirs_option, 1}, {"sparse", no_argument, NULL, 'S'}, {"starting-file", required_argument, NULL, 'K'}, {"suffix", required_argument, NULL, SUFFIX_OPTION}, {"tape-length", required_argument, NULL, 'L'}, {"to-stdout", no_argument, NULL, 'O'}, {"totals", no_argument, &totals_option, 1}, {"touch", no_argument, NULL, 'm'}, {"uncompress", no_argument, NULL, 'Z'}, {"ungzip", no_argument, NULL, 'z'}, {"unlink-first", no_argument, NULL, 'U'}, {"update", no_argument, NULL, 'u'}, {"use-compress-program", required_argument, NULL, USE_COMPRESS_PROGRAM_OPTION}, {"verbose", no_argument, NULL, 'v'}, {"verify", no_argument, NULL, 'W'}, {"version", no_argument, &show_version, 1}, {"version-control", required_argument, NULL, OBSOLETE_VERSION_CONTROL}, {"volno-file", required_argument, NULL, VOLNO_FILE_OPTION}, {0, 0, 0, 0} }; /*---------------------------------------------. | Print a usage message and exit with STATUS. | `---------------------------------------------*/ static void usage (int status) { if (status != TAREXIT_SUCCESS) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { fputs (_("\ GNU `tar' saves many files together into a single tape or disk archive, and\n\ can restore individual files from the archive.\n"), stdout); printf (_("\nUsage: %s [OPTION]... [FILE]...\n"), program_name); fputs (_("\ \n\ If a long option shows an argument as mandatory, then it is mandatory\n\ for the equivalent short option also. Similarly for optional arguments.\n"), stdout); fputs(_("\ \n\ Main operation mode:\n\ -t, --list list the contents of an archive\n\ -x, --extract, --get extract files from an archive\n\ -c, --create create a new archive\n\ -d, --diff, --compare find differences between archive and file system\n\ -r, --append append files to the end of an archive\n\ -u, --update only append files newer than copy in archive\n\ -A, --catenate append tar files to an archive\n\ --concatenate same as -A\n\ --delete delete from the archive (not on mag tapes!)\n"), stdout); fputs (_("\ \n\ Operation modifiers:\n\ -W, --verify attempt to verify the archive after writing it\n\ --remove-files remove files after adding them to the archive\n\ -k, --keep-old-files don't overwrite existing files when extracting\n\ -U, --unlink-first remove each file prior to extracting over it\n\ --recursive-unlink empty hierarchies prior to extracting directory\n\ -S, --sparse handle sparse files efficiently\n\ -O, --to-stdout extract files to standard output\n\ -G, --incremental handle old GNU-format incremental backup\n\ -g, --listed-incremental handle new GNU-format incremental backup\n\ --ignore-failed-read do not exit with nonzero on unreadable files\n"), stdout); fputs (_("\ \n\ Handling of file attributes:\n\ --owner=NAME force NAME as owner for added files\n\ --group=NAME force NAME as group for added files\n\ --mode=CHANGES force (symbolic) mode CHANGES for added files\n\ --atime-preserve don't change access times on dumped files\n\ -m, --modification-time don't extract file modified time\n\ --same-owner try extracting files with the same ownership\n\ --numeric-owner always use numbers for user/group names\n\ -p, --same-permissions extract all protection information\n\ --preserve-permissions same as -p\n\ -s, --same-order sort names to extract to match archive\n\ --preserve-order same as -s\n\ --preserve same as both -p and -s\n"), stdout); fputs (_("\ \n\ Device selection and switching:\n\ -f, --file=ARCHIVE use archive file or device ARCHIVE\n\ --force-local archive file is local even if has a colon\n\ --rsh-command=COMMAND use remote COMMAND instead of rsh\n\ -[0-7][lmh] specify drive and density\n\ -M, --multi-volume create/list/extract multi-volume archive\n\ -L, --tape-length=NUM change tape after writing NUM x 1024 bytes\n\ -F, --info-script=FILE run script at end of each tape (implies -M)\n\ --new-volume-script=FILE same as -F FILE\n\ --volno-file=FILE use/update the volume number in FILE\n"), stdout); fputs (_("\ \n\ Device blocking:\n\ -b, --blocking-factor=BLOCKS BLOCKS x 512 bytes per record\n\ --record-size=SIZE SIZE bytes per record, multiple of 512\n\ -i, --ignore-zeros ignore zeroed blocks in archive (means EOF)\n\ -B, --read-full-records reblock as we read (for 4.2BSD pipes)\n"), stdout); fputs (_("\ \n\ Archive format selection:\n\ -V, --label=NAME create archive with volume name NAME\n\ PATTERN at list/extract time, a globbing PATTERN\n\ -o, --old-archive, --portability write a V7 format archive\n\ --posix write a POSIX conformant archive\n\ -z, --gzip, --ungzip filter the archive through gzip\n\ -Z, --compress, --uncompress filter the archive through compress\n\ --use-compress-program=PROG filter through PROG (must accept -d)\n"), stdout); fputs (_("\ \n\ Local file selection:\n\ -C, --directory=DIR change to directory DIR\n\ -T, --files-from=NAME get names to extract or create from file NAME\n\ --null -T reads null-terminated names, disable -C\n\ --exclude=PATTERN exclude files, given as a globbing PATTERN\n\ -X, --exclude-from=FILE exclude globbing patterns listed in FILE\n\ -P, --absolute-names don't strip leading `/'s from file names\n\ -h, --dereference dump instead the files symlinks point to\n\ --no-recursion avoid descending automatically in directories\n\ -l, --one-file-system stay in local file system when creating archive\n\ -K, --starting-file=NAME begin at file NAME in the archive\n"), stdout); #if !MSDOS fputs (_("\ -N, --newer=DATE only store files newer than DATE\n\ --newer-mtime compare date and time when data changed only\n\ --after-date=DATE same as -N\n"), stdout); #endif fputs (_("\ --backup[=CONTROL] backup before removal, choose version control\n\ --suffix=SUFFIX backup before removel, override usual suffix\n"), stdout); fputs (_("\ \n\ Informative output:\n\ --help print this help, then exit\n\ --version print tar program version number, then exit\n\ -v, --verbose verbosely list files processed\n\ --checkpoint print directory names while reading the archive\n\ --totals print total bytes written while creating archive\n\ -R, --block-number show block number within archive with each message\n\ -w, --interactive ask for confirmation for every action\n\ --confirmation same as -w\n"), stdout); fputs (_("\ \n\ The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ The version control may be set with --backup or VERSION_CONTROL, values are:\n\ \n\ t, numbered make numbered backups\n\ nil, existing numbered if numbered backups exist, simple otherwise\n\ never, simple always make simple backups\n"), stdout); printf (_("\ \n\ GNU tar cannot read nor produce `--posix' archives. If POSIXLY_CORRECT\n\ is set in the environment, GNU extensions are disallowed with `--posix'.\n\ Support for POSIX is only partially implemented, don't count on it yet.\n\ ARCHIVE may be FILE, HOST:FILE or USER@HOST:FILE; and FILE may be a file\n\ or a device. *This* `tar' defaults to `-f%s -b%d'.\n"), DEFAULT_ARCHIVE, DEFAULT_BLOCKING); fputs (_("\ \n\ Report bugs to <tar-bugs@gnu.ai.mit.edu>.\n"), stdout); } exit (status); } /*----------------------------. | Parse the options for tar. | `----------------------------*/ /* Available option letters are DEHIJQY and aejnqy. Some are reserved: y per-file gzip compression Y per-block gzip compression */ #define OPTION_STRING \ "-01234567ABC:F:GK:L:MN:OPRST:UV:WX:Zb:cdf:g:hiklmoprstuvwxz" static void set_subcommand_option (enum subcommand subcommand) { if (subcommand_option != UNKNOWN_SUBCOMMAND && subcommand_option != subcommand) USAGE_ERROR ((0, 0, _("You may not specify more than one `-Acdtrux' option"))); subcommand_option = subcommand; } static void set_use_compress_program_option (const char *string) { if (use_compress_program_option && strcmp (use_compress_program_option, string) != 0) USAGE_ERROR ((0, 0, _("Conflicting compression options"))); use_compress_program_option = string; } static void decode_options (int argc, char *const *argv) { int optchar; /* option letter */ int input_files; /* number of input files */ const char *backup_suffix_string; const char *version_control_string; /* Set some default option values. */ subcommand_option = UNKNOWN_SUBCOMMAND; archive_format = DEFAULT_FORMAT; blocking_factor = DEFAULT_BLOCKING; record_size = DEFAULT_BLOCKING * BLOCKSIZE; owner_option = -1; group_option = -1; backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX"); version_control_string = getenv ("VERSION_CONTROL"); /* Convert old-style tar call by exploding option element and rearranging options accordingly. */ if (argc > 1 && argv[1][0] != '-') { int new_argc; /* argc value for rearranged arguments */ char **new_argv; /* argv value for rearranged arguments */ char *const *in; /* cursor into original argv */ char **out; /* cursor into rearranged argv */ const char *letter; /* cursor into old option letters */ char buffer[3]; /* constructed option buffer */ const char *cursor; /* cursor in OPTION_STRING */ /* Initialize a constructed option. */ buffer[0] = '-'; buffer[2] = '\0'; /* Allocate a new argument array, and copy program name in it. */ new_argc = argc - 1 + strlen (argv[1]); new_argv = (char **) xmalloc (new_argc * sizeof (char *)); in = argv; out = new_argv; *out++ = *in++; /* Copy each old letter option as a separate option, and have the corresponding argument moved next to it. */ for (letter = *in++; *letter; letter++) { buffer[1] = *letter; *out++ = xstrdup (buffer); cursor = strchr (OPTION_STRING, *letter); if (cursor && cursor[1] == ':') if (in < argv + argc) *out++ = *in++; else USAGE_ERROR ((0, 0, _("Old option `%c' requires an argument."), *letter)); } /* Copy all remaining options. */ while (in < argv + argc) *out++ = *in++; /* Replace the old option list by the new one. */ argc = new_argc; argv = new_argv; } /* Parse all options and non-options as they appear. */ input_files = 0; while (optchar = getopt_long (argc, argv, OPTION_STRING, long_options, NULL), optchar != EOF) switch (optchar) { case '?': usage (TAREXIT_FAILURE); case 0: break; case 1: /* File name or non-parsed option, because of RETURN_IN_ORDER ordering triggerred by the leading dash in OPTION_STRING. */ name_add (optarg); input_files++; break; case 'A': set_subcommand_option (CAT_SUBCOMMAND); break; case OBSOLETE_BLOCK_COMPRESS: WARN ((0, 0, _("Obsolete option, now implied by --blocking-factor"))); break; case OBSOLETE_BLOCKING_FACTOR: WARN ((0, 0, _("Obsolete option name replaced by --blocking-factor"))); /* Fall through. */ case 'b': blocking_factor = intconv (optarg); record_size = blocking_factor * BLOCKSIZE; break; case OBSOLETE_READ_FULL_RECORDS: WARN ((0, 0, _("Obsolete option name replaced by --read-full-records"))); /* Fall through. */ case 'B': /* Try to reblock input records. For reading 4.2BSD pipes. */ /* It would surely make sense to exchange -B and -R, but it seems that -B has been used for a long while in Sun tar ans most BSD-derived systems. This is a consequence of the block/record terminology confusion. */ read_full_records_option = 1; break; case 'c': set_subcommand_option (CREATE_SUBCOMMAND); break; case 'C': name_add ("-C"); name_add (optarg); break; case 'd': set_subcommand_option (DIFF_SUBCOMMAND); break; case 'f': if (archive_names == allocated_archive_names) { allocated_archive_names *= 2; archive_name_array = (const char **) xrealloc (archive_name_array, sizeof (const char *) * allocated_archive_names); } archive_name_array[archive_names++] = optarg; break; case 'F': /* Since -F is only useful with -M, make it implied. Run this script at the end of each tape. */ info_script_option = optarg; multi_volume_option = 1; break; case 'g': listed_incremental_option = optarg; /* Fall through. */ case 'G': /* We are making an incremental dump (FIXME: are we?); save directories at the beginning of the archive, and include in each directory its contents. */ incremental_option = 1; break; case 'h': /* Follow symbolic links. */ dereference_option = 1; break; case 'i': /* Ignore zero blocks (eofs). This can't be the default, because Unix tar writes two blocks of zeros, then pads out the record with garbage. */ ignore_zeros_option = 1; break; case 'k': /* Don't overwrite existing files. */ keep_old_files_option = 1; break; case 'K': starting_file_option = 1; addname (optarg); break; case 'l': /* When dumping directories, don't dump files/subdirectories that are on other filesystems. */ one_file_system_option = 1; break; case 'L': clear_tarlong (tape_length_option); add_to_tarlong (tape_length_option, intconv (optarg)); mult_tarlong (tape_length_option, 1024); multi_volume_option = 1; break; case OBSOLETE_TOUCH: WARN ((0, 0, _("Obsolete option name replaced by --touch"))); /* Fall through. */ case 'm': touch_option = 1; break; case 'M': /* Make multivolume archive: when we can't write any more into the archive, re-open it, and continue writing. */ multi_volume_option = 1; break; #if !MSDOS case 'N': after_date_option = 1; /* Fall through. */ case NEWER_MTIME_OPTION: if (newer_mtime_option) USAGE_ERROR ((0, 0, _("More than one threshold date"))); newer_mtime_option = get_date (optarg, (voidstar) 0); if (newer_mtime_option == (time_t) -1) USAGE_ERROR ((0, 0, _("Invalid date format `%s'"), optarg)); break; #endif /* not MSDOS */ case 'o': if (archive_format == DEFAULT_FORMAT) archive_format = V7_FORMAT; else if (archive_format != V7_FORMAT) USAGE_ERROR ((0, 0, _("Conflicting archive format options"))); break; case 'O': to_stdout_option = 1; break; case 'p': same_permissions_option = 1; break; case OBSOLETE_ABSOLUTE_NAMES: WARN ((0, 0, _("Obsolete option name replaced by --absolute-names"))); /* Fall through. */ case 'P': absolute_names_option = 1; break; case 'r': set_subcommand_option (APPEND_SUBCOMMAND); break; case OBSOLETE_BLOCK_NUMBER: WARN ((0, 0, _("Obsolete option name replaced by --block-number"))); /* Fall through. */ case 'R': /* Print block numbers for debugging bad tar archives. */ /* It would surely make sense to exchange -B and -R, but it seems that -B has been used for a long while in Sun tar ans most BSD-derived systems. This is a consequence of the block/record terminology confusion. */ block_number_option = 1; break; case 's': /* Names to extr are sorted. */ same_order_option = 1; break; case 'S': sparse_option = 1; break; case 't': set_subcommand_option (LIST_SUBCOMMAND); verbose_option++; break; case 'T': files_from_option = optarg; break; case 'u': set_subcommand_option (UPDATE_SUBCOMMAND); break; case 'U': unlink_first_option = 1; break; case 'v': verbose_option++; break; case 'V': volume_label_option = optarg; break; case 'w': interactive_option = 1; break; case 'W': verify_option = 1; break; case 'x': set_subcommand_option (EXTRACT_SUBCOMMAND); break; case 'X': exclude_option = 1; add_exclude_file (optarg); break; case 'z': set_use_compress_program_option ("gzip"); break; case 'Z': set_use_compress_program_option ("compress"); break; case OBSOLETE_VERSION_CONTROL: WARN ((0, 0, _("Obsolete option name replaced by --backup"))); /* Fall through. */ case BACKUP_OPTION: backup_option = 1; if (optarg) version_control_string = optarg; break; case DELETE_OPTION: set_subcommand_option (DELETE_SUBCOMMAND); break; case EXCLUDE_OPTION: exclude_option = 1; add_exclude (optarg); break; case GROUP_OPTION: if (!gname_to_gid (optarg, &group_option)) if (!check_decimal (optarg) >= 0) ERROR ((TAREXIT_FAILURE, 0, _("Invalid group given on option"))); else group_option = check_decimal (optarg); break; case MODE_OPTION: mode_option = mode_compile (optarg, MODE_MASK_EQUALS | MODE_MASK_PLUS | MODE_MASK_MINUS); if (mode_option == MODE_INVALID) ERROR ((TAREXIT_FAILURE, 0, _("Invalid mode given on option"))); if (mode_option == MODE_MEMORY_EXHAUSTED) ERROR ((TAREXIT_FAILURE, 0, _("Memory exhausted"))); break; case NO_RECURSE_OPTION: no_recurse_option = 1; break; case NULL_OPTION: filename_terminator = '\0'; break; case OWNER_OPTION: if (!uname_to_uid (optarg, &owner_option)) if (!check_decimal (optarg) >= 0) ERROR ((TAREXIT_FAILURE, 0, _("Invalid owner given on option"))); else owner_option = check_decimal (optarg); break; case POSIX_OPTION: #if OLDGNU_COMPATIBILITY if (archive_format == DEFAULT_FORMAT) archive_format = GNU_FORMAT; else if (archive_format != GNU_FORMAT) USAGE_ERROR ((0, 0, _("Conflicting archive format options"))); #else if (archive_format == DEFAULT_FORMAT) archive_format = POSIX_FORMAT; else if (archive_format != POSIX_FORMAT) USAGE_ERROR ((0, 0, _("Conflicting archive format options"))); #endif break; case PRESERVE_OPTION: same_permissions_option = 1; same_order_option = 1; break; case RECORD_SIZE_OPTION: record_size = intconv (optarg); if (record_size % BLOCKSIZE != 0) USAGE_ERROR ((0, 0, _("Record size must be a multiple of %d."), BLOCKSIZE)); blocking_factor = record_size / BLOCKSIZE; break; case RSH_COMMAND_OPTION: rsh_command_option = optarg; break; case SUFFIX_OPTION: backup_option = 1; backup_suffix_string = optarg; break; case VOLNO_FILE_OPTION: volno_file_option = optarg; break; case USE_COMPRESS_PROGRAM_OPTION: set_use_compress_program_option (optarg); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': #ifdef DEVICE_PREFIX { int device = optchar - '0'; int density; static char buf[sizeof DEVICE_PREFIX + 10]; char *cursor; density = getopt_long (argc, argv, "lmh", NULL, NULL); strcpy (buf, DEVICE_PREFIX); cursor = buf + strlen (buf); #ifdef DENSITY_LETTER sprintf (cursor, "%d%c", device, density); #else /* not DENSITY_LETTER */ switch (density) { case 'l': #ifdef LOW_NUM device += LOW_NUM; #endif break; case 'm': #ifdef MID_NUM device += MID_NUM; #else device += 8; #endif break; case 'h': #ifdef HGH_NUM device += HGH_NUM; #else device += 16; #endif break; default: usage (TAREXIT_FAILURE); } sprintf (cursor, "%d", device); #endif /* not DENSITY_LETTER */ if (archive_names == allocated_archive_names) { allocated_archive_names *= 2; archive_name_array = (const char **) xrealloc (archive_name_array, sizeof (const char *) * allocated_archive_names); } archive_name_array[archive_names++] = buf; /* FIXME: How comes this works for many archives when buf is not xstrdup'ed? */ } break; #else /* not DEVICE_PREFIX */ USAGE_ERROR ((0, 0, _("Options `-[0-7][lmh]' not supported by *this* tar"))); #endif /* not DEVICE_PREFIX */ } /* Process trivial options. */ if (show_version) { printf ("tar (GNU %s) %s\n", PACKAGE, VERSION); fputs (_("\ \n\ Copyright (C) 1988, 92, 93, 94, 95, 96, 97 Free Software Foundation, Inc.\n"), stdout); fputs (_("\ This is free software; see the source for copying conditions. There is NO\n\ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"), stdout); fputs (_("\ \n\ Written by John Gilmore and Jay Fenlason.\n"), stdout); exit (TAREXIT_SUCCESS); } if (show_help) usage (TAREXIT_SUCCESS); /* Derive option values and check option consistency. */ if (archive_format == DEFAULT_FORMAT) { #if OLDGNU_COMPATIBILITY archive_format = OLDGNU_FORMAT; #else archive_format = GNU_FORMAT; #endif } if (archive_format == GNU_FORMAT && getenv ("POSIXLY_CORRECT")) archive_format = POSIX_FORMAT; if ((volume_label_option != NULL || incremental_option || multi_volume_option || sparse_option) && archive_format != OLDGNU_FORMAT && archive_format != GNU_FORMAT) USAGE_ERROR ((0, 0, _("GNU features wanted on incompatible archive format"))); if (archive_names == 0) { /* If no archive file name given, try TAPE from the environment, or else, DEFAULT_ARCHIVE from the configuration process. */ archive_names = 1; archive_name_array[0] = getenv ("TAPE"); if (archive_name_array[0] == NULL) archive_name_array[0] = DEFAULT_ARCHIVE; } /* Allow multiple archives only with `-M'. */ if (archive_names > 1 && !multi_volume_option) USAGE_ERROR ((0, 0, _("Multiple archive files requires `-M' option"))); /* If ready to unlink hierarchies, so we are for simpler files. */ if (recursive_unlink_option) unlink_first_option = 1; /* Forbid using -c with no input files whatsoever. Check that `-f -', explicit or implied, is used correctly. */ switch (subcommand_option) { case CREATE_SUBCOMMAND: if (input_files == 0 && !files_from_option) USAGE_ERROR ((0, 0, _("Cowardly refusing to create an empty archive"))); break; case EXTRACT_SUBCOMMAND: case LIST_SUBCOMMAND: case DIFF_SUBCOMMAND: for (archive_name_cursor = archive_name_array; archive_name_cursor < archive_name_array + archive_names; archive_name_cursor++) if (!strcmp (*archive_name_cursor, "-")) request_stdin ("-f"); break; case CAT_SUBCOMMAND: case UPDATE_SUBCOMMAND: case APPEND_SUBCOMMAND: for (archive_name_cursor = archive_name_array; archive_name_cursor < archive_name_array + archive_names; archive_name_cursor++) if (!strcmp (*archive_name_cursor, "-")) USAGE_ERROR ((0, 0, _("Options `-Aru' are incompatible with `-f -'"))); default: break; } archive_name_cursor = archive_name_array; /* Prepare for generating backup names. */ if (backup_suffix_string) simple_backup_suffix = xstrdup (backup_suffix_string); if (backup_option) backup_type = get_version (version_control_string); } /* Tar proper. */ /*-----------------------. | Main routine for tar. | `-----------------------*/ int main (int argc, char *const *argv) { program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); exit_status = TAREXIT_SUCCESS; filename_terminator = '\n'; /* Pre-allocate a few structures. */ allocated_archive_names = 10; archive_name_array = (const char **) xmalloc (sizeof (const char *) * allocated_archive_names); archive_names = 0; init_names (); /* Decode options. */ decode_options (argc, argv); name_init (argc, argv); /* Main command execution. */ if (volno_file_option) init_volume_number (); switch (subcommand_option) { case UNKNOWN_SUBCOMMAND: USAGE_ERROR ((0, 0, _("You must specify one of the `-Acdtrux' options"))); case CAT_SUBCOMMAND: case UPDATE_SUBCOMMAND: case APPEND_SUBCOMMAND: update_archive (); break; case DELETE_SUBCOMMAND: delete_archive_members (); break; case CREATE_SUBCOMMAND: if (totals_option) init_total_written (); create_archive (); name_close (); if (totals_option) print_total_written (); break; case EXTRACT_SUBCOMMAND: extr_init (); read_and (extract_archive); break; case LIST_SUBCOMMAND: read_and (list_archive); break; case DIFF_SUBCOMMAND: diff_init (); read_and (diff_archive); break; } if (volno_file_option) closeout_volume_number (); /* Dispose of allocated memory, and return. */ free (archive_name_array); name_term (); if (exit_status == TAREXIT_FAILURE) error (0, 0, _("Error exit delayed from previous errors")); exit (exit_status); }
/* Declarations for getopt. Copyright (C) 1989,90,91,92,93,94,96,97 Free Software Foundation, Inc. This file is part of the GNU C Library. Its master source is NOT part of the C library, however. The master source lives in /gd/gnu/lib. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef _GETOPT_H #define _GETOPT_H 1 #ifdef __cplusplus extern "C" { #endif /* For communication from `getopt' to the caller. When `getopt' finds an option that takes an argument, the argument value is returned here. Also, when `ordering' is RETURN_IN_ORDER, each non-option ARGV-element is returned here. */ extern char *optarg; /* Index in ARGV of the next element to be scanned. This is used for communication to and from the caller and for communication between successive calls to `getopt'. On entry to `getopt', zero means this is the first call; initialize. When `getopt' returns -1, this is the index of the first of the non-option elements that the caller should itself scan. Otherwise, `optind' communicates from one call to the next how much of ARGV has been scanned so far. */ extern int optind; /* Callers store zero here to inhibit the error message `getopt' prints for unrecognized options. */ extern int opterr; /* Set to an option character which was unrecognized. */ extern int optopt; /* Describe the long-named options requested by the application. The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector of `struct option' terminated by an element containing a name which is zero. The field `has_arg' is: no_argument (or 0) if the option does not take an argument, required_argument (or 1) if the option requires an argument, optional_argument (or 2) if the option takes an optional argument. If the field `flag' is not NULL, it points to a variable that is set to the value given in the field `val' when the option is found, but left unchanged if the option is not found. To have a long-named option do something other than set an `int' to a compiled-in constant, such as set a value from `optarg', set the option's `flag' field to zero and its `val' field to a nonzero value (the equivalent single-letter option character, if there is one). For long options that have a zero `flag' field, `getopt' returns the contents of the `val' field. */ struct option { #if defined (__STDC__) && __STDC__ const char *name; #else char *name; #endif /* has_arg can't be an enum because some compilers complain about type mismatches in all the code that assumes it is an int. */ int has_arg; int *flag; int val; }; /* Names for the values of the `has_arg' field of `struct option'. */ #define no_argument 0 #define required_argument 1 #define optional_argument 2 #if defined (__STDC__) && __STDC__ #ifdef __GNU_LIBRARY__ /* Many other libraries have conflicting prototypes for getopt, with differences in the consts, in stdlib.h. To avoid compilation errors, only prototype getopt for the GNU C library. */ extern int getopt (int argc, char *const *argv, const char *shortopts); #else /* not __GNU_LIBRARY__ */ extern int getopt (); #endif /* __GNU_LIBRARY__ */ extern int getopt_long (int argc, char *const *argv, const char *shortopts, const struct option *longopts, int *longind); extern int getopt_long_only (int argc, char *const *argv, const char *shortopts, const struct option *longopts, int *longind); /* Internal only. Users should not call this directly. */ extern int _getopt_internal (int argc, char *const *argv, const char *shortopts, const struct option *longopts, int *longind, int long_only); #else /* not __STDC__ */ extern int getopt (); extern int getopt_long (); extern int getopt_long_only (); extern int _getopt_internal (); #endif /* __STDC__ */ #ifdef __cplusplus } #endif #endif /* _GETOPT_H */
/* Update a tar archive. Copyright (C) 1988, 1992, 1994, 1996, 1997 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Implement the 'r', 'u' and 'A' options for tar. 'A' means that the file names are tar files, and they should simply be appended to the end of the archive. No attempt is made to record the reads from the args; if they're on raw tape or something like that, it'll probably lose... */ #include "system.h" #include "common.h" /* FIXME: This module should not directly handle the following variable, instead, this should be done in buffer.c only. */ extern union block *current_block; /* We've hit the end of the old stuff, and its time to start writing new stuff to the tape. This involves seeking back one record and re-writing the current record (which has been changed). */ int time_to_start_writing = 0; /* Pointer to where we started to write in the first record we write out. This is used if we can't backspace the output and have to null out the first part of the record. */ char *output_start; /*------------------------------------------------------------------------. | Catenate file PATH to the archive without creating a header for it. It | | had better be a tar file or the archive is screwed. | `------------------------------------------------------------------------*/ static void append_file (char *path) { int handle; struct stat stat_data; long bytes_left; if (stat (path, &stat_data) != 0 || (handle = open (path, O_RDONLY | O_BINARY), handle < 0)) { ERROR ((0, errno, _("Cannot open file %s"), path)); return; } bytes_left = stat_data.st_size; while (bytes_left > 0) { union block *start = find_next_block (); long buffer_size = available_space_after (start); int status; if (bytes_left < buffer_size) { buffer_size = bytes_left; status = buffer_size % BLOCKSIZE; if (status) memset (start->buffer + bytes_left, 0, (size_t) (BLOCKSIZE - status)); } status = read (handle, start->buffer, (size_t) buffer_size); if (status < 0) FATAL_ERROR ((0, errno, _("Read error at byte %ld reading %d bytes in file %s"), stat_data.st_size - bytes_left, buffer_size, path)); bytes_left -= status; set_next_block_after (start + (status - 1) / BLOCKSIZE); if (status != buffer_size) FATAL_ERROR ((0, 0, _("%s: File shrunk by %d bytes, (yark!)"), path, bytes_left)); } close (handle); } /*-----------------------------------------------------------------------. | Implement the 'r' (add files to end of archive), and 'u' (add files to | | end of archive if they arent there, or are more up to date than the | | version in the archive.) commands. | `-----------------------------------------------------------------------*/ void update_archive (void) { enum read_header previous_status = HEADER_STILL_UNREAD; int found_end = 0; name_gather (); if (subcommand_option == UPDATE_SUBCOMMAND) name_expand (); open_archive (ACCESS_UPDATE); while (!found_end) { enum read_header status = read_header (); switch (status) { case HEADER_STILL_UNREAD: abort (); case HEADER_SUCCESS: { struct name *name; if (subcommand_option == UPDATE_SUBCOMMAND && (name = name_scan (current_file_name), name)) { struct stat stat_data; enum archive_format unused; decode_header (current_header, ¤t_stat, &unused, 0); if (stat (current_file_name, &stat_data) < 0) ERROR ((0, errno, _("Cannot stat %s"), current_file_name)); else if (current_stat.st_mtime >= stat_data.st_mtime) name->found = 1; } set_next_block_after (current_header); if (current_header->oldgnu_header.isextended) skip_extended_headers (); skip_file ((long) current_stat.st_size); break; } case HEADER_ZERO_BLOCK: current_block = current_header; found_end = 1; break; case HEADER_END_OF_FILE: found_end = 1; break; case HEADER_FAILURE: set_next_block_after (current_header); switch (previous_status) { case HEADER_STILL_UNREAD: WARN ((0, 0, _("This does not look like a tar archive"))); /* Fall through. */ case HEADER_SUCCESS: case HEADER_ZERO_BLOCK: ERROR ((0, 0, _("Skipping to next header"))); /* Fall through. */ case HEADER_FAILURE: break; case HEADER_END_OF_FILE: abort (); } break; } previous_status = status; } reset_eof (); time_to_start_writing = 1; output_start = current_block->buffer; { char *path; while (path = name_from_list (), path) { if (interactive_option && !confirm ("add", path)) continue; if (subcommand_option == CAT_SUBCOMMAND) append_file (path); else dump_file (path, -1, 1); } } write_eot (); close_archive (); names_notfound (); }
2186b01ab8d953222f15cab0d257f80215f4a17c53b30a5a7467b80e49c2cc34 /usr/bin/tar
https://mirrors.kernel.org/gnu/sed/sed-4.0.9.tar.gz c365874794187f8444e5d22998cd5888ffa47f36def4b77517a808dec27c0600
# SPDX-FileCopyrightText: 2021 Andrius Å tikonas <andrius@stikonas.eu> # SPDX-FileCopyrightText: 2022 fosslinux <fosslinux@aussies.space> # # SPDX-License-Identifier: GPL-3.0-or-later CC = tcc AR = tcc -ar CPPFLAGS = -DENABLE_NLS=0 \ -DHAVE_FCNTL_H \ -DHAVE_ALLOCA_H \ -DSED_FEATURE_VERSION=\"4.0\" \ -DVERSION=\"4.0.9\" \ -DPACKAGE=\"sed\" CFLAGS = -I . -I lib LDFLAGS = -L . -lsed -static .PHONY: all ifeq ($(LIBC),mes) LIB_SRC = getline else LIB_SRC = alloca endif LIB_SRC += getopt1 getopt utils regex obstack strverscmp mkstemp LIB_OBJ = $(addprefix lib/, $(addsuffix .o, $(LIB_SRC))) SED_SRC = compile execute regexp fmt sed SED_OBJ = $(addprefix sed/, $(addsuffix .o, $(SED_SRC))) all: sed/sed lib/regex.h: lib/regex_.h cp $< $@ lib/regex.o: lib/regex.h libsed.a: $(LIB_OBJ) $(AR) cr $@ $^ sed/sed: libsed.a $(SED_OBJ) $(CC) $^ $(LDFLAGS) -o $@ install: install -D sed/sed $(DESTDIR)$(PREFIX)/bin/sed
/* alloca.c -- allocate automatically reclaimed memory (Mostly) portable public-domain implementation -- D A Gwyn This implementation of the PWB library alloca function, which is used to allocate space off the run-time stack so that it is automatically reclaimed upon procedure exit, was inspired by discussions with J. Q. Johnson of Cornell. J.Otto Tennant <jot@cray.com> contributed the Cray support. There are some preprocessor constants that can be defined when compiling for your specific system, for improved efficiency; however, the defaults should be okay. The general concept of this implementation is to keep track of all alloca-allocated blocks, and reclaim any that are found to be deeper in the stack than the current invocation. This heuristic does not reclaim storage as soon as it becomes invalid, but it will do so eventually. As a special case, alloca(0) reclaims storage without allocating any. It is a good idea to use alloca(0) in your main control loop, etc. to force garbage collection. */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #ifdef HAVE_STRING_H #include <string.h> #endif #ifdef HAVE_STDLIB_H #include <stdlib.h> #endif #ifdef emacs #include "blockinput.h" #endif /* If compiling with GCC 2, this file's not needed. */ #if !defined (__GNUC__) || __GNUC__ < 2 /* If someone has defined alloca as a macro, there must be some other way alloca is supposed to work. */ #ifndef alloca #ifdef emacs #ifdef static /* actually, only want this if static is defined as "" -- this is for usg, in which emacs must undefine static in order to make unexec workable */ #ifndef STACK_DIRECTION you lose -- must know STACK_DIRECTION at compile-time #endif /* STACK_DIRECTION undefined */ #endif /* static */ #endif /* emacs */ /* If your stack is a linked list of frames, you have to provide an "address metric" ADDRESS_FUNCTION macro. */ #if defined (CRAY) && defined (CRAY_STACKSEG_END) long i00afunc (); #define ADDRESS_FUNCTION(arg) (char *) i00afunc (&(arg)) #else #define ADDRESS_FUNCTION(arg) &(arg) #endif #if __STDC__ typedef void *pointer; #else typedef char *pointer; #endif #ifndef NULL #define NULL 0 #endif /* Different portions of Emacs need to call different versions of malloc. The Emacs executable needs alloca to call xmalloc, because ordinary malloc isn't protected from input signals. On the other hand, the utilities in lib-src need alloca to call malloc; some of them are very simple, and don't have an xmalloc routine. Non-Emacs programs expect this to call xmalloc. Callers below should use malloc. */ #ifndef emacs #define malloc xmalloc #endif extern pointer malloc (); /* Define STACK_DIRECTION if you know the direction of stack growth for your system; otherwise it will be automatically deduced at run-time. STACK_DIRECTION > 0 => grows toward higher addresses STACK_DIRECTION < 0 => grows toward lower addresses STACK_DIRECTION = 0 => direction of growth unknown */ #ifndef STACK_DIRECTION #define STACK_DIRECTION 0 /* Direction unknown. */ #endif #if STACK_DIRECTION != 0 #define STACK_DIR STACK_DIRECTION /* Known at compile-time. */ #else /* STACK_DIRECTION == 0; need run-time code. */ static int stack_dir; /* 1 or -1 once known. */ #define STACK_DIR stack_dir static void find_stack_direction () { static char *addr = NULL; /* Address of first `dummy', once known. */ auto char dummy; /* To get stack address. */ if (addr == NULL) { /* Initial entry. */ addr = ADDRESS_FUNCTION (dummy); find_stack_direction (); /* Recurse once. */ } else { /* Second entry. */ if (ADDRESS_FUNCTION (dummy) > addr) stack_dir = 1; /* Stack grew upward. */ else stack_dir = -1; /* Stack grew downward. */ } } #endif /* STACK_DIRECTION == 0 */ /* An "alloca header" is used to: (a) chain together all alloca'ed blocks; (b) keep track of stack depth. It is very important that sizeof(header) agree with malloc alignment chunk size. The following default should work okay. */ #ifndef ALIGN_SIZE #define ALIGN_SIZE sizeof(double) #endif typedef union hdr { char align[ALIGN_SIZE]; /* To force sizeof(header). */ struct { union hdr *next; /* For chaining headers. */ char *deep; /* For stack depth measure. */ } h; } header; static header *last_alloca_header = NULL; /* -> last alloca header. */ /* Return a pointer to at least SIZE bytes of storage, which will be automatically reclaimed upon exit from the procedure that called alloca. Originally, this space was supposed to be taken from the current stack frame of the caller, but that method cannot be made to work for some implementations of C, for example under Gould's UTX/32. */ pointer alloca (size) unsigned size; { auto char probe; /* Probes stack depth: */ register char *depth = ADDRESS_FUNCTION (probe); #if STACK_DIRECTION == 0 if (STACK_DIR == 0) /* Unknown growth direction. */ find_stack_direction (); #endif /* Reclaim garbage, defined as all alloca'd storage that was allocated from deeper in the stack than currently. */ { register header *hp; /* Traverses linked list. */ #ifdef emacs BLOCK_INPUT; #endif for (hp = last_alloca_header; hp != NULL;) if ((STACK_DIR > 0 && hp->h.deep > depth) || (STACK_DIR < 0 && hp->h.deep < depth)) { register header *np = hp->h.next; free ((pointer) hp); /* Collect garbage. */ hp = np; /* -> next header. */ } else break; /* Rest are not deeper. */ last_alloca_header = hp; /* -> last valid storage. */ #ifdef emacs UNBLOCK_INPUT; #endif } if (size == 0) return NULL; /* No allocation required. */ /* Allocate combined header + user data storage. */ { register pointer new = malloc (sizeof (header) + size); /* Address of header. */ if (new == 0) abort(); ((header *) new)->h.next = last_alloca_header; ((header *) new)->h.deep = depth; last_alloca_header = (header *) new; /* User storage begins just after header. */ return (pointer) ((char *) new + sizeof (header)); } } #if defined (CRAY) && defined (CRAY_STACKSEG_END) #ifdef DEBUG_I00AFUNC #include <stdio.h> #endif #ifndef CRAY_STACK #define CRAY_STACK #ifndef CRAY2 /* Stack structures for CRAY-1, CRAY X-MP, and CRAY Y-MP */ struct stack_control_header { long shgrow:32; /* Number of times stack has grown. */ long shaseg:32; /* Size of increments to stack. */ long shhwm:32; /* High water mark of stack. */ long shsize:32; /* Current size of stack (all segments). */ }; /* The stack segment linkage control information occurs at the high-address end of a stack segment. (The stack grows from low addresses to high addresses.) The initial part of the stack segment linkage control information is 0200 (octal) words. This provides for register storage for the routine which overflows the stack. */ struct stack_segment_linkage { long ss[0200]; /* 0200 overflow words. */ long sssize:32; /* Number of words in this segment. */ long ssbase:32; /* Offset to stack base. */ long:32; long sspseg:32; /* Offset to linkage control of previous segment of stack. */ long:32; long sstcpt:32; /* Pointer to task common address block. */ long sscsnm; /* Private control structure number for microtasking. */ long ssusr1; /* Reserved for user. */ long ssusr2; /* Reserved for user. */ long sstpid; /* Process ID for pid based multi-tasking. */ long ssgvup; /* Pointer to multitasking thread giveup. */ long sscray[7]; /* Reserved for Cray Research. */ long ssa0; long ssa1; long ssa2; long ssa3; long ssa4; long ssa5; long ssa6; long ssa7; long sss0; long sss1; long sss2; long sss3; long sss4; long sss5; long sss6; long sss7; }; #else /* CRAY2 */ /* The following structure defines the vector of words returned by the STKSTAT library routine. */ struct stk_stat { long now; /* Current total stack size. */ long maxc; /* Amount of contiguous space which would be required to satisfy the maximum stack demand to date. */ long high_water; /* Stack high-water mark. */ long overflows; /* Number of stack overflow ($STKOFEN) calls. */ long hits; /* Number of internal buffer hits. */ long extends; /* Number of block extensions. */ long stko_mallocs; /* Block allocations by $STKOFEN. */ long underflows; /* Number of stack underflow calls ($STKRETN). */ long stko_free; /* Number of deallocations by $STKRETN. */ long stkm_free; /* Number of deallocations by $STKMRET. */ long segments; /* Current number of stack segments. */ long maxs; /* Maximum number of stack segments so far. */ long pad_size; /* Stack pad size. */ long current_address; /* Current stack segment address. */ long current_size; /* Current stack segment size. This number is actually corrupted by STKSTAT to include the fifteen word trailer area. */ long initial_address; /* Address of initial segment. */ long initial_size; /* Size of initial segment. */ }; /* The following structure describes the data structure which trails any stack segment. I think that the description in 'asdef' is out of date. I only describe the parts that I am sure about. */ struct stk_trailer { long this_address; /* Address of this block. */ long this_size; /* Size of this block (does not include this trailer). */ long unknown2; long unknown3; long link; /* Address of trailer block of previous segment. */ long unknown5; long unknown6; long unknown7; long unknown8; long unknown9; long unknown10; long unknown11; long unknown12; long unknown13; long unknown14; }; #endif /* CRAY2 */ #endif /* not CRAY_STACK */ #ifdef CRAY2 /* Determine a "stack measure" for an arbitrary ADDRESS. I doubt that "lint" will like this much. */ static long i00afunc (long *address) { struct stk_stat status; struct stk_trailer *trailer; long *block, size; long result = 0; /* We want to iterate through all of the segments. The first step is to get the stack status structure. We could do this more quickly and more directly, perhaps, by referencing the $LM00 common block, but I know that this works. */ STKSTAT (&status); /* Set up the iteration. */ trailer = (struct stk_trailer *) (status.current_address + status.current_size - 15); /* There must be at least one stack segment. Therefore it is a fatal error if "trailer" is null. */ if (trailer == 0) abort (); /* Discard segments that do not contain our argument address. */ while (trailer != 0) { block = (long *) trailer->this_address; size = trailer->this_size; if (block == 0 || size == 0) abort (); trailer = (struct stk_trailer *) trailer->link; if ((block <= address) && (address < (block + size))) break; } /* Set the result to the offset in this segment and add the sizes of all predecessor segments. */ result = address - block; if (trailer == 0) { return result; } do { if (trailer->this_size <= 0) abort (); result += trailer->this_size; trailer = (struct stk_trailer *) trailer->link; } while (trailer != 0); /* We are done. Note that if you present a bogus address (one not in any segment), you will get a different number back, formed from subtracting the address of the first block. This is probably not what you want. */ return (result); } #else /* not CRAY2 */ /* Stack address function for a CRAY-1, CRAY X-MP, or CRAY Y-MP. Determine the number of the cell within the stack, given the address of the cell. The purpose of this routine is to linearize, in some sense, stack addresses for alloca. */ static long i00afunc (long address) { long stkl = 0; long size, pseg, this_segment, stack; long result = 0; struct stack_segment_linkage *ssptr; /* Register B67 contains the address of the end of the current stack segment. If you (as a subprogram) store your registers on the stack and find that you are past the contents of B67, you have overflowed the segment. B67 also points to the stack segment linkage control area, which is what we are really interested in. */ stkl = CRAY_STACKSEG_END (); ssptr = (struct stack_segment_linkage *) stkl; /* If one subtracts 'size' from the end of the segment, one has the address of the first word of the segment. If this is not the first segment, 'pseg' will be nonzero. */ pseg = ssptr->sspseg; size = ssptr->sssize; this_segment = stkl - size; /* It is possible that calling this routine itself caused a stack overflow. Discard stack segments which do not contain the target address. */ while (!(this_segment <= address && address <= stkl)) { #ifdef DEBUG_I00AFUNC fprintf (stderr, "%011o %011o %011o\n", this_segment, address, stkl); #endif if (pseg == 0) break; stkl = stkl - pseg; ssptr = (struct stack_segment_linkage *) stkl; size = ssptr->sssize; pseg = ssptr->sspseg; this_segment = stkl - size; } result = address - this_segment; /* If you subtract pseg from the current end of the stack, you get the address of the previous stack segment's end. This seems a little convoluted to me, but I'll bet you save a cycle somewhere. */ while (pseg != 0) { #ifdef DEBUG_I00AFUNC fprintf (stderr, "%011o %011o\n", pseg, size); #endif stkl = stkl - pseg; ssptr = (struct stack_segment_linkage *) stkl; size = ssptr->sssize; pseg = ssptr->sspseg; result += size; } return (result); } #endif /* not CRAY2 */ #endif /* CRAY */ #endif /* no alloca */ #endif /* not GCC version 2 */
/* getopt_long and getopt_long_only entry points for GNU getopt. Copyright (C) 1987,88,89,90,91,92,93,94,96,97,98 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. Bugs can be reported to bug-glibc@gnu.org. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include "getopt.h" #if !defined __STDC__ || !__STDC__ /* This is a separate conditional since some stdc systems reject `defined (const)'. */ #ifndef const #define const #endif #endif #include <stdio.h> /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #define GETOPT_INTERFACE_VERSION 2 #if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 #include <gnu-versions.h> #if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION #define ELIDE_CODE #endif #endif #ifndef ELIDE_CODE /* This needs to come after some library #include to get __GNU_LIBRARY__ defined. */ #ifdef __GNU_LIBRARY__ #include <stdlib.h> #endif #ifndef NULL #define NULL 0 #endif int getopt_long (argc, argv, options, long_options, opt_index) int argc; char *const *argv; const char *options; const struct option *long_options; int *opt_index; { return _getopt_internal (argc, argv, options, long_options, opt_index, 0); } /* Like getopt_long, but '-' as well as '--' can indicate a long option. If an option that starts with '-' (not '--') doesn't match a long option, but does match a short option, it is parsed as a short option instead. */ int getopt_long_only (argc, argv, options, long_options, opt_index) int argc; char *const *argv; const char *options; const struct option *long_options; int *opt_index; { return _getopt_internal (argc, argv, options, long_options, opt_index, 1); } #endif /* Not ELIDE_CODE. */ #ifdef TEST #include <stdio.h> int main (argc, argv) int argc; char **argv; { int c; int digit_optind = 0; while (1) { int this_option_optind = optind ? optind : 1; int option_index = 0; static struct option long_options[] = { {"add", 1, 0, 0}, {"append", 0, 0, 0}, {"delete", 1, 0, 0}, {"verbose", 0, 0, 0}, {"create", 0, 0, 0}, {"file", 1, 0, 0}, {0, 0, 0, 0} }; c = getopt_long (argc, argv, "abc:d:0123456789", long_options, &option_index); if (c == -1) break; switch (c) { case 0: printf ("option %s", long_options[option_index].name); if (optarg) printf (" with arg %s", optarg); printf ("\n"); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (digit_optind != 0 && digit_optind != this_option_optind) printf ("digits occur in two different argv-elements.\n"); digit_optind = this_option_optind; printf ("option %c\n", c); break; case 'a': printf ("option a\n"); break; case 'b': printf ("option b\n"); break; case 'c': printf ("option c with value `%s'\n", optarg); break; case 'd': printf ("option d with value `%s'\n", optarg); break; case '?': break; default: printf ("?? getopt returned character code 0%o ??\n", c); } } if (optind < argc) { printf ("non-option ARGV-elements: "); while (optind < argc) printf ("%s ", argv[optind++]); printf ("\n"); } exit (0); } #endif /* TEST */
/* Getopt for GNU. NOTE: getopt is now part of the C library, so if you don't know what "Keep this file name-space clean" means, talk to drepper@gnu.org before changing it! Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. Bugs can be reported to bug-glibc@gnu.org. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>. Ditto for AIX 3.2 and <stdlib.h>. */ #ifndef _NO_PROTO # define _NO_PROTO #endif #ifdef HAVE_CONFIG_H # include <config.h> #endif #if !defined __STDC__ || !__STDC__ /* This is a separate conditional since some stdc systems reject `defined (const)'. */ # ifndef const # define const # endif #endif #include <stdio.h> /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #define GETOPT_INTERFACE_VERSION 2 #if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 # include <gnu-versions.h> # if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION # define ELIDE_CODE # endif #endif #ifndef ELIDE_CODE /* This needs to come after some library #include to get __GNU_LIBRARY__ defined. */ #ifdef __GNU_LIBRARY__ /* Don't include stdlib.h for non-GNU C libraries because some of them contain conflicting prototypes for getopt. */ # include <stdlib.h> # include <unistd.h> #endif /* GNU C library. */ #ifdef VMS # include <unixlib.h> # if HAVE_STRING_H - 0 # include <string.h> # endif #endif #ifndef _ /* This is for other GNU distributions with internationalized messages. When compiling libc, the _ macro is predefined. */ # ifdef HAVE_LIBINTL_H # include <libintl.h> # define _(msgid) gettext (msgid) # else # define _(msgid) (msgid) # endif #endif /* This version of `getopt' appears to the caller like standard Unix `getopt' but it behaves differently for the user, since it allows the user to intersperse the options with the other arguments. As `getopt' works, it permutes the elements of ARGV so that, when it is done, all the options precede everything else. Thus all application programs are extended to handle flexible argument order. Setting the environment variable POSIXLY_CORRECT disables permutation. Then the behavior is completely standard. GNU application programs can use a third alternative mode in which they can distinguish the relative order of options and other arguments. */ #include "getopt.h" /* For communication from `getopt' to the caller. When `getopt' finds an option that takes an argument, the argument value is returned here. Also, when `ordering' is RETURN_IN_ORDER, each non-option ARGV-element is returned here. */ char *optarg = NULL; /* Index in ARGV of the next element to be scanned. This is used for communication to and from the caller and for communication between successive calls to `getopt'. On entry to `getopt', zero means this is the first call; initialize. When `getopt' returns -1, this is the index of the first of the non-option elements that the caller should itself scan. Otherwise, `optind' communicates from one call to the next how much of ARGV has been scanned so far. */ /* 1003.2 says this must be 1 before any call. */ int optind = 1; /* Formerly, initialization of getopt depended on optind==0, which causes problems with re-calling getopt as programs generally don't know that. */ int __getopt_initialized = 0; /* The next char to be scanned in the option-element in which the last option character we returned was found. This allows us to pick up the scan where we left off. If this is zero, or a null string, it means resume the scan by advancing to the next ARGV-element. */ static char *nextchar; /* Callers store zero here to inhibit the error message for unrecognized options. */ int opterr = 1; /* Set to an option character which was unrecognized. This must be initialized on some systems to avoid linking in the system's own getopt implementation. */ int optopt = '?'; /* Describe how to deal with options that follow non-option ARGV-elements. If the caller did not specify anything, the default is REQUIRE_ORDER if the environment variable POSIXLY_CORRECT is defined, PERMUTE otherwise. REQUIRE_ORDER means don't recognize them as options; stop option processing when the first non-option is seen. This is what Unix does. This mode of operation is selected by either setting the environment variable POSIXLY_CORRECT, or using `+' as the first character of the list of option characters. PERMUTE is the default. We permute the contents of ARGV as we scan, so that eventually all the non-options are at the end. This allows options to be given in any order, even with programs that were not written to expect this. RETURN_IN_ORDER is an option available to programs that were written to expect options and other ARGV-elements in any order and that care about the ordering of the two. We describe each non-option ARGV-element as if it were the argument of an option with character code 1. Using `-' as the first character of the list of option characters selects this mode of operation. The special argument `--' forces an end of option-scanning regardless of the value of `ordering'. In the case of RETURN_IN_ORDER, only `--' can cause `getopt' to return -1 with `optind' != ARGC. */ static enum { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER } ordering; /* Value of POSIXLY_CORRECT environment variable. */ static char *posixly_correct; #ifdef __GNU_LIBRARY__ /* We want to avoid inclusion of string.h with non-GNU libraries because there are many ways it can cause trouble. On some systems, it contains special magic macros that don't work in GCC. */ # include <string.h> # define my_index strchr #else /* Avoid depending on library functions or files whose names are inconsistent. */ #ifndef getenv extern char *getenv (); #endif #ifndef strncmp extern int strncmp (); #endif static char * my_index (str, chr) const char *str; int chr; { while (*str) { if (*str == chr) return (char *) str; str++; } return 0; } /* If using GCC, we can safely declare strlen this way. If not using GCC, it is ok not to declare it. */ #ifdef __GNUC__ /* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h. That was relevant to code that was here before. */ # if (!defined __STDC__ || !__STDC__) && !defined strlen /* gcc with -traditional declares the built-in strlen to return int, and has done so at least since version 2.4.5. -- rms. */ extern int strlen (const char *); # endif /* not __STDC__ */ #endif /* __GNUC__ */ #endif /* not __GNU_LIBRARY__ */ /* Handle permutation of arguments. */ /* Describe the part of ARGV that contains non-options that have been skipped. `first_nonopt' is the index in ARGV of the first of them; `last_nonopt' is the index after the last of them. */ static int first_nonopt; static int last_nonopt; #ifdef _LIBC /* Bash 2.0 gives us an environment variable containing flags indicating ARGV elements that should not be considered arguments. */ /* Defined in getopt_init.c */ extern char *__getopt_nonoption_flags; static int nonoption_flags_max_len; static int nonoption_flags_len; static int original_argc; static char *const *original_argv; /* Make sure the environment variable bash 2.0 puts in the environment is valid for the getopt call we must make sure that the ARGV passed to getopt is that one passed to the process. */ static void __attribute__ ((unused)) store_args_and_env (int argc, char *const *argv) { /* XXX This is no good solution. We should rather copy the args so that we can compare them later. But we must not use malloc(3). */ original_argc = argc; original_argv = argv; } # ifdef text_set_element text_set_element (__libc_subinit, store_args_and_env); # endif /* text_set_element */ # define SWAP_FLAGS(ch1, ch2) \ if (nonoption_flags_len > 0) \ { \ char __tmp = __getopt_nonoption_flags[ch1]; \ __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ __getopt_nonoption_flags[ch2] = __tmp; \ } #else /* !_LIBC */ # define SWAP_FLAGS(ch1, ch2) #endif /* _LIBC */ /* Exchange two adjacent subsequences of ARGV. One subsequence is elements [first_nonopt,last_nonopt) which contains all the non-options that have been skipped so far. The other is elements [last_nonopt,optind), which contains all the options processed since those non-options were skipped. `first_nonopt' and `last_nonopt' are relocated so that they describe the new indices of the non-options in ARGV after they are moved. */ #if defined __STDC__ && __STDC__ static void exchange (char **); #endif static void exchange (argv) char **argv; { int bottom = first_nonopt; int middle = last_nonopt; int top = optind; char *tem; /* Exchange the shorter segment with the far end of the longer segment. That puts the shorter segment into the right place. It leaves the longer segment in the right place overall, but it consists of two parts that need to be swapped next. */ #ifdef _LIBC /* First make sure the handling of the `__getopt_nonoption_flags' string can work normally. Our top argument must be in the range of the string. */ if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len) { /* We must extend the array. The user plays games with us and presents new arguments. */ char *new_str = malloc (top + 1); if (new_str == NULL) nonoption_flags_len = nonoption_flags_max_len = 0; else { memset (__mempcpy (new_str, __getopt_nonoption_flags, nonoption_flags_max_len), '\0', top + 1 - nonoption_flags_max_len); nonoption_flags_max_len = top + 1; __getopt_nonoption_flags = new_str; } } #endif while (top > middle && middle > bottom) { if (top - middle > middle - bottom) { /* Bottom segment is the short one. */ int len = middle - bottom; register int i; /* Swap it with the top part of the top segment. */ for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[top - (middle - bottom) + i]; argv[top - (middle - bottom) + i] = tem; SWAP_FLAGS (bottom + i, top - (middle - bottom) + i); } /* Exclude the moved bottom segment from further swapping. */ top -= len; } else { /* Top segment is the short one. */ int len = top - middle; register int i; /* Swap it with the bottom part of the bottom segment. */ for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[middle + i]; argv[middle + i] = tem; SWAP_FLAGS (bottom + i, middle + i); } /* Exclude the moved top segment from further swapping. */ bottom += len; } } /* Update records for the slots the non-options now occupy. */ first_nonopt += (optind - last_nonopt); last_nonopt = optind; } /* Initialize the internal data when the first call is made. */ #if defined __STDC__ && __STDC__ static const char *_getopt_initialize (int, char *const *, const char *); #endif static const char * _getopt_initialize (argc, argv, optstring) int argc; char *const *argv; const char *optstring; { /* Start processing options with ARGV-element 1 (since ARGV-element 0 is the program name); the sequence of previously skipped non-option ARGV-elements is empty. */ first_nonopt = last_nonopt = optind; nextchar = NULL; posixly_correct = getenv ("POSIXLY_CORRECT"); /* Determine how to handle the ordering of options and nonoptions. */ if (optstring[0] == '-') { ordering = RETURN_IN_ORDER; ++optstring; } else if (optstring[0] == '+') { ordering = REQUIRE_ORDER; ++optstring; } else if (posixly_correct != NULL) ordering = REQUIRE_ORDER; else ordering = PERMUTE; #ifdef _LIBC if (posixly_correct == NULL && argc == original_argc && argv == original_argv) { if (nonoption_flags_max_len == 0) { if (__getopt_nonoption_flags == NULL || __getopt_nonoption_flags[0] == '\0') nonoption_flags_max_len = -1; else { const char *orig_str = __getopt_nonoption_flags; int len = nonoption_flags_max_len = strlen (orig_str); if (nonoption_flags_max_len < argc) nonoption_flags_max_len = argc; __getopt_nonoption_flags = (char *) malloc (nonoption_flags_max_len); if (__getopt_nonoption_flags == NULL) nonoption_flags_max_len = -1; else memset (__mempcpy (__getopt_nonoption_flags, orig_str, len), '\0', nonoption_flags_max_len - len); } } nonoption_flags_len = nonoption_flags_max_len; } else nonoption_flags_len = 0; #endif return optstring; } /* Scan elements of ARGV (whose length is ARGC) for option characters given in OPTSTRING. If an element of ARGV starts with '-', and is not exactly "-" or "--", then it is an option element. The characters of this element (aside from the initial '-') are option characters. If `getopt' is called repeatedly, it returns successively each of the option characters from each of the option elements. If `getopt' finds another option character, it returns that character, updating `optind' and `nextchar' so that the next call to `getopt' can resume the scan with the following option character or ARGV-element. If there are no more option characters, `getopt' returns -1. Then `optind' is the index in ARGV of the first ARGV-element that is not an option. (The ARGV-elements have been permuted so that those that are not options now come last.) OPTSTRING is a string containing the legitimate option characters. If an option character is seen that is not listed in OPTSTRING, return '?' after printing an error message. If you set `opterr' to zero, the error message is suppressed but we still return '?'. If a char in OPTSTRING is followed by a colon, that means it wants an arg, so the following text in the same ARGV-element, or the text of the following ARGV-element, is returned in `optarg'. Two colons mean an option that wants an optional arg; if there is text in the current ARGV-element, it is returned in `optarg', otherwise `optarg' is set to zero. If OPTSTRING starts with `-' or `+', it requests different methods of handling the non-option ARGV-elements. See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. Long-named options begin with `--' instead of `-'. Their names may be abbreviated as long as the abbreviation is unique or is an exact match for some defined option. If they have an argument, it follows the option name in the same ARGV-element, separated from the option name by a `=', or else the in next ARGV-element. When `getopt' finds a long-named option, it returns 0 if that option's `flag' field is nonzero, the value of the option's `val' field if the `flag' field is zero. The elements of ARGV aren't really const, because we permute them. But we pretend they're const in the prototype to be compatible with other systems. LONGOPTS is a vector of `struct option' terminated by an element containing a name which is zero. LONGIND returns the index in LONGOPT of the long-named option found. It is only valid when a long-named option has been found by the most recent call. If LONG_ONLY is nonzero, '-' as well as '--' can introduce long-named options. */ int _getopt_internal (argc, argv, optstring, longopts, longind, long_only) int argc; char *const *argv; const char *optstring; const struct option *longopts; int *longind; int long_only; { optarg = NULL; if (optind == 0 || !__getopt_initialized) { if (optind == 0) optind = 1; /* Don't scan ARGV[0], the program name. */ optstring = _getopt_initialize (argc, argv, optstring); __getopt_initialized = 1; } /* Test whether ARGV[optind] points to a non-option argument. Either it does not have option syntax, or there is an environment flag from the shell indicating it is not an option. The later information is only used when the used in the GNU libc. */ #ifdef _LIBC # define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \ || (optind < nonoption_flags_len \ && __getopt_nonoption_flags[optind] == '1')) #else # define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0') #endif if (nextchar == NULL || *nextchar == '\0') { /* Advance to the next ARGV-element. */ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been moved back by the user (who may also have changed the arguments). */ if (last_nonopt > optind) last_nonopt = optind; if (first_nonopt > optind) first_nonopt = optind; if (ordering == PERMUTE) { /* If we have just processed some options following some non-options, exchange them so that the options come first. */ if (first_nonopt != last_nonopt && last_nonopt != optind) exchange ((char **) argv); else if (last_nonopt != optind) first_nonopt = optind; /* Skip any additional non-options and extend the range of non-options previously skipped. */ while (optind < argc && NONOPTION_P) optind++; last_nonopt = optind; } /* The special ARGV-element `--' means premature end of options. Skip it like a null option, then exchange with previous non-options as if it were an option, then skip everything else like a non-option. */ if (optind != argc && !strcmp (argv[optind], "--")) { optind++; if (first_nonopt != last_nonopt && last_nonopt != optind) exchange ((char **) argv); else if (first_nonopt == last_nonopt) first_nonopt = optind; last_nonopt = argc; optind = argc; } /* If we have done all the ARGV-elements, stop the scan and back over any non-options that we skipped and permuted. */ if (optind == argc) { /* Set the next-arg-index to point at the non-options that we previously skipped, so the caller will digest them. */ if (first_nonopt != last_nonopt) optind = first_nonopt; return -1; } /* If we have come to a non-option and did not permute it, either stop the scan or describe it to the caller and pass it by. */ if (NONOPTION_P) { if (ordering == REQUIRE_ORDER) return -1; optarg = argv[optind++]; return 1; } /* We have found another option-ARGV-element. Skip the initial punctuation. */ nextchar = (argv[optind] + 1 + (longopts != NULL && argv[optind][1] == '-')); } /* Decode the current option-ARGV-element. */ /* Check whether the ARGV-element is a long option. If long_only and the ARGV-element has the form "-f", where f is a valid short option, don't consider it an abbreviated form of a long option that starts with f. Otherwise there would be no way to give the -f short option. On the other hand, if there's a long option "fubar" and the ARGV-element is "-fu", do consider that an abbreviation of the long option, just like "--fu", and not "-f" with arg "u". This distinction seems to be the most useful approach. */ if (longopts != NULL && (argv[optind][1] == '-' || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1]))))) { char *nameend; const struct option *p; const struct option *pfound = NULL; int exact = 0; int ambig = 0; int indfound = -1; int option_index; for (nameend = nextchar; *nameend && *nameend != '='; nameend++) /* Do nothing. */ ; /* Test all long options for either exact match or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp (p->name, nextchar, nameend - nextchar)) { if ((unsigned int) (nameend - nextchar) == (unsigned int) strlen (p->name)) { /* Exact match found. */ pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { /* First nonexact match found. */ pfound = p; indfound = option_index; } else /* Second or later nonexact match found. */ ambig = 1; } if (ambig && !exact) { if (opterr) fprintf (stderr, _("%s: option `%s' is ambiguous\n"), argv[0], argv[optind]); nextchar += strlen (nextchar); optind++; optopt = 0; return '?'; } if (pfound != NULL) { option_index = indfound; optind++; if (*nameend) { /* Don't test has_arg with >, because some C compilers don't allow it to be used on enums. */ if (pfound->has_arg) optarg = nameend + 1; else { if (opterr) if (argv[optind - 1][1] == '-') /* --option */ fprintf (stderr, _("%s: option `--%s' doesn't allow an argument\n"), argv[0], pfound->name); else /* +option or -option */ fprintf (stderr, _("%s: option `%c%s' doesn't allow an argument\n"), argv[0], argv[optind - 1][0], pfound->name); nextchar += strlen (nextchar); optopt = pfound->val; return '?'; } } else if (pfound->has_arg == 1) { if (optind < argc) optarg = argv[optind++]; else { if (opterr) fprintf (stderr, _("%s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]); nextchar += strlen (nextchar); optopt = pfound->val; return optstring[0] == ':' ? ':' : '?'; } } nextchar += strlen (nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } /* Can't find it as a long option. If this is not getopt_long_only, or the option starts with '--' or is not a valid short option, then it's an error. Otherwise interpret it as a short option. */ if (!long_only || argv[optind][1] == '-' || my_index (optstring, *nextchar) == NULL) { if (opterr) { if (argv[optind][1] == '-') /* --option */ fprintf (stderr, _("%s: unrecognized option `--%s'\n"), argv[0], nextchar); else /* +option or -option */ fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), argv[0], argv[optind][0], nextchar); } nextchar = (char *) ""; optind++; optopt = 0; return '?'; } } /* Look at and handle the next short option-character. */ { char c = *nextchar++; char *temp = my_index (optstring, c); /* Increment `optind' when we start to process its last character. */ if (*nextchar == '\0') ++optind; if (temp == NULL || c == ':') { if (opterr) { if (posixly_correct) /* 1003.2 specifies the format of this message. */ fprintf (stderr, _("%s: illegal option -- %c\n"), argv[0], c); else fprintf (stderr, _("%s: invalid option -- %c\n"), argv[0], c); } optopt = c; return '?'; } /* Convenience. Treat POSIX -W foo same as long option --foo */ if (temp[0] == 'W' && temp[1] == ';') { char *nameend; const struct option *p; const struct option *pfound = NULL; int exact = 0; int ambig = 0; int indfound = 0; int option_index; /* This is an option that requires an argument. */ if (*nextchar != '\0') { optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ optind++; } else if (optind == argc) { if (opterr) { /* 1003.2 specifies the format of this message. */ fprintf (stderr, _("%s: option requires an argument -- %c\n"), argv[0], c); } optopt = c; if (optstring[0] == ':') c = ':'; else c = '?'; return c; } else /* We already incremented `optind' once; increment it again when taking next ARGV-elt as argument. */ optarg = argv[optind++]; /* optarg is now the argument, see if it's in the table of longopts. */ for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++) /* Do nothing. */ ; /* Test all long options for either exact match or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp (p->name, nextchar, nameend - nextchar)) { if ((unsigned int) (nameend - nextchar) == strlen (p->name)) { /* Exact match found. */ pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { /* First nonexact match found. */ pfound = p; indfound = option_index; } else /* Second or later nonexact match found. */ ambig = 1; } if (ambig && !exact) { if (opterr) fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"), argv[0], argv[optind]); nextchar += strlen (nextchar); optind++; return '?'; } if (pfound != NULL) { option_index = indfound; if (*nameend) { /* Don't test has_arg with >, because some C compilers don't allow it to be used on enums. */ if (pfound->has_arg) optarg = nameend + 1; else { if (opterr) fprintf (stderr, _("\ %s: option `-W %s' doesn't allow an argument\n"), argv[0], pfound->name); nextchar += strlen (nextchar); return '?'; } } else if (pfound->has_arg == 1) { if (optind < argc) optarg = argv[optind++]; else { if (opterr) fprintf (stderr, _("%s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]); nextchar += strlen (nextchar); return optstring[0] == ':' ? ':' : '?'; } } nextchar += strlen (nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } nextchar = NULL; return 'W'; /* Let the application handle it. */ } if (temp[1] == ':') { if (temp[2] == ':') { /* This is an option that accepts an argument optionally. */ if (*nextchar != '\0') { optarg = nextchar; optind++; } else optarg = NULL; nextchar = NULL; } else { /* This is an option that requires an argument. */ if (*nextchar != '\0') { optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ optind++; } else if (optind == argc) { if (opterr) { /* 1003.2 specifies the format of this message. */ fprintf (stderr, _("%s: option requires an argument -- %c\n"), argv[0], c); } optopt = c; if (optstring[0] == ':') c = ':'; else c = '?'; } else /* We already incremented `optind' once; increment it again when taking next ARGV-elt as argument. */ optarg = argv[optind++]; nextchar = NULL; } } return c; } } int getopt (argc, argv, optstring) int argc; char *const *argv; const char *optstring; { return _getopt_internal (argc, argv, optstring, (const struct option *) 0, (int *) 0, 0); } #endif /* Not ELIDE_CODE. */ #ifdef TEST /* Compile with -DTEST to make an executable for use in testing the above definition of `getopt'. */ int main (argc, argv) int argc; char **argv; { int c; int digit_optind = 0; while (1) { int this_option_optind = optind ? optind : 1; c = getopt (argc, argv, "abc:d:0123456789"); if (c == -1) break; switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (digit_optind != 0 && digit_optind != this_option_optind) printf ("digits occur in two different argv-elements.\n"); digit_optind = this_option_optind; printf ("option %c\n", c); break; case 'a': printf ("option a\n"); break; case 'b': printf ("option b\n"); break; case 'c': printf ("option c with value `%s'\n", optarg); break; case '?': break; default: printf ("?? getopt returned character code 0%o ??\n", c); } } if (optind < argc) { printf ("non-option ARGV-elements: "); while (optind < argc) printf ("%s ", argv[optind++]); printf ("\n"); } exit (0); } #endif /* TEST */
/* Functions from hack's utils library. Copyright (C) 1989, 1990, 1991, 1998, 1999, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "config.h" #include <stdio.h> #include <errno.h> #ifndef errno extern int errno; #endif #ifdef HAVE_STRINGS_H # include <strings.h> #else # include <string.h> #endif /* HAVE_STRINGS_H */ #ifdef HAVE_STDLIB_H # include <stdlib.h> #endif /* HAVE_STDLIB_H */ #include "utils.h" const char *myname; void do_ck_fclose P_((FILE *stream)); /* Print an error message and exit */ #if !defined __STDC__ || !(__STDC__-0) # include <varargs.h> # define VSTART(l,a) va_start(l) void panic(str, va_alist) char *str; va_dcl #else /*__STDC__*/ # include <stdarg.h> # define VSTART(l,a) va_start(l, a) void panic(const char *str, ...) #endif /* __STDC__ */ { va_list iggy; fprintf(stderr, "%s: ", myname); VSTART(iggy, str); #ifndef HAVE_VPRINTF # ifndef HAVE_DOPRNT fputs(str, stderr); /* not great, but perhaps better than nothing... */ # else /* HAVE_DOPRNT */ _doprnt(str, &iggy, stderr); # endif /* HAVE_DOPRNT */ #else /* HAVE_VFPRINTF */ vfprintf(stderr, str, iggy); #endif /* HAVE_VFPRINTF */ va_end(iggy); putc('\n', stderr); exit(4); } /* Store information about files opened with ck_fopen so that error messages from ck_fread, ck_fwrite, etc. can print the name of the file that had the error */ struct id { FILE *fp; char *name; struct id *link; }; static struct id *utils_id_s = NULL; /* Internal routine to get a filename from utils_id_s */ static const char *utils_fp_name P_((FILE *fp)); static const char * utils_fp_name(fp) FILE *fp; { struct id *p; for (p=utils_id_s; p; p=p->link) if (p->fp == fp) return p->name; if (fp == stdin) return "stdin"; else if (fp == stdout) return "stdout"; else if (fp == stderr) return "stderr"; return "<unknown>"; } /* Panic on failing fopen */ FILE * ck_fopen(name, mode, fail) const char *name; const char *mode; flagT fail; { FILE *fp; struct id *p; fp = fopen (name, mode); if (!fp) { if (fail) panic(_("Couldn't open file %s: %s"), name, strerror(errno)); return NULL; } for (p=utils_id_s; p; p=p->link) { if (fp == p->fp) { FREE(p->name); break; } } if (!p) { p = MALLOC(1, struct id); p->link = utils_id_s; utils_id_s = p; } p->name = ck_strdup(name); p->fp = fp; return fp; } /* Panic on failing fwrite */ void ck_fwrite(ptr, size, nmemb, stream) const VOID *ptr; size_t size; size_t nmemb; FILE *stream; { clearerr(stream); if (size && fwrite(ptr, size, nmemb, stream) != nmemb) panic(ngettext("couldn't write %d item to %s: %s", "couldn't write %d items to %s: %s", nmemb), nmemb, utils_fp_name(stream), strerror(errno)); } /* Panic on failing fread */ size_t ck_fread(ptr, size, nmemb, stream) VOID *ptr; size_t size; size_t nmemb; FILE *stream; { clearerr(stream); if (size && (nmemb=fread(ptr, size, nmemb, stream)) <= 0 && ferror(stream)) panic(_("read error on %s: %s"), utils_fp_name(stream), strerror(errno)); return nmemb; } /* Panic on failing fflush */ void ck_fflush(stream) FILE *stream; { clearerr(stream); if (fflush(stream) == EOF && errno != EBADF) panic("Couldn't flush %s: %s", utils_fp_name(stream), strerror(errno)); } /* Panic on failing fclose */ void ck_fclose(stream) FILE *stream; { struct id r; struct id *prev; struct id *cur; /* a NULL stream means to close all files */ r.link = utils_id_s; prev = &r; while ( (cur = prev->link) ) { if (!stream || stream == cur->fp) { do_ck_fclose (cur->fp); prev->link = cur->link; FREE(cur->name); FREE(cur); } else prev = cur; } utils_id_s = r.link; /* Also care about stdout, because if it is redirected the last output operations might fail and it is important to signal this as an error (perhaps to make). */ if (!stream) { do_ck_fclose (stdout); do_ck_fclose (stderr); } } /* Close a single file and update a count of closed files. */ void do_ck_fclose(stream) FILE *stream; { ck_fflush(stream); clearerr(stream); if (fclose(stream) == EOF) panic("Couldn't close %s: %s", utils_fp_name(stream), strerror(errno)); } char * temp_file_template(tmpdir, program) const char *tmpdir; char *program; { char *template; if (tmpdir == NULL) tmpdir = getenv("TMPDIR"); if (tmpdir == NULL) { tmpdir = getenv("TMP"); if (tmpdir == NULL) #ifdef P_tmpdir tmpdir = P_tmpdir; #else tmpdir = "/tmp"; #endif } template = xmalloc (strlen (tmpdir) + strlen (program) + 8); sprintf (template, "%s/%sXXXXXX", tmpdir, program); return (template); } /* Panic on failing malloc */ VOID * ck_malloc(size) size_t size; { VOID *ret = calloc(1, size ? size : 1); if (!ret) panic("Couldn't allocate memory"); return ret; } /* Panic on failing malloc */ VOID * xmalloc(size) size_t size; { return ck_malloc(size); } /* Panic on failing realloc */ VOID * ck_realloc(ptr, size) VOID *ptr; size_t size; { VOID *ret; if (size == 0) { FREE(ptr); return NULL; } if (!ptr) return ck_malloc(size); ret = realloc(ptr, size); if (!ret) panic("Couldn't re-allocate memory"); return ret; } /* Return a malloc()'d copy of a string */ char * ck_strdup(str) const char *str; { char *ret = MALLOC(strlen(str)+1, char); return strcpy(ret, str); } /* Return a malloc()'d copy of a block of memory */ VOID * ck_memdup(buf, len) const VOID *buf; size_t len; { VOID *ret = ck_malloc(len); return memcpy(ret, buf, len); } /* Release a malloc'd block of memory */ void ck_free(ptr) VOID *ptr; { if (ptr) free(ptr); } /* Implement a variable sized buffer of `stuff'. We don't know what it is, nor do we care, as long as it doesn't mind being aligned by malloc. */ struct buffer { size_t allocated; size_t length; char *b; }; #define MIN_ALLOCATE 50 struct buffer * init_buffer() { struct buffer *b = MALLOC(1, struct buffer); b->b = MALLOC(MIN_ALLOCATE, char); b->allocated = MIN_ALLOCATE; b->length = 0; return b; } char * get_buffer(b) struct buffer *b; { return b->b; } size_t size_buffer(b) struct buffer *b; { return b->length; } static void resize_buffer P_((struct buffer *b, size_t newlen)); static void resize_buffer(b, newlen) struct buffer *b; size_t newlen; { char *try = NULL; size_t alen = b->allocated; if (newlen <= alen) return; alen *= 2; if (newlen < alen) try = realloc(b->b, alen); /* Note: *not* the REALLOC() macro! */ if (!try) { alen = newlen; try = REALLOC(b->b, alen, char); } b->allocated = alen; b->b = try; } void add_buffer(b, p, n) struct buffer *b; const char *p; size_t n; { if (b->allocated - b->length < n) resize_buffer(b, b->length+n); memcpy(b->b + b->length, p, n); b->length += n; } void add1_buffer(b, c) struct buffer *b; int c; { /* This special case should be kept cheap; * don't make it just a mere convenience * wrapper for add_buffer() -- even "builtin" * versions of memcpy(a, b, 1) can become * expensive when called too often. */ if (c != EOF) { if (b->allocated - b->length < 1) resize_buffer(b, b->length+1); b->b[b->length++] = c; } } void free_buffer(b) struct buffer *b; { if (b) FREE(b->b); FREE(b); }
/* Definitions for data structures and routines for the regular expression library. Copyright (C) 1985,1989-93,1995-98,2000,2001,2002,2003 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #ifndef _REGEX_H #define _REGEX_H 1 #include <sys/types.h> /* Allow the use in C++ code. */ #ifdef __cplusplus extern "C" { #endif /* POSIX says that <sys/types.h> must be included (by the caller) before <regex.h>. */ #if !defined _POSIX_C_SOURCE && !defined _POSIX_SOURCE && defined VMS /* VMS doesn't have `size_t' in <sys/types.h>, even though POSIX says it should be there. */ # include <stddef.h> #endif /* The following two types have to be signed and unsigned integer type wide enough to hold a value of a pointer. For most ANSI compilers ptrdiff_t and size_t should be likely OK. Still size of these two types is 2 for Microsoft C. Ugh... */ typedef long int s_reg_t; typedef unsigned long int active_reg_t; /* The following bits are used to determine the regexp syntax we recognize. The set/not-set meanings are chosen so that Emacs syntax remains the value 0. The bits are given in alphabetical order, and the definitions shifted by one from the previous bit; thus, when we add or remove a bit, only one other definition need change. */ typedef unsigned long int reg_syntax_t; /* If this bit is not set, then \ inside a bracket expression is literal. If set, then such a \ quotes the following character. */ #define RE_BACKSLASH_ESCAPE_IN_LISTS ((unsigned long int) 1) /* If this bit is not set, then + and ? are operators, and \+ and \? are literals. If set, then \+ and \? are operators and + and ? are literals. */ #define RE_BK_PLUS_QM (RE_BACKSLASH_ESCAPE_IN_LISTS << 1) /* If this bit is set, then character classes are supported. They are: [:alpha:], [:upper:], [:lower:], [:digit:], [:alnum:], [:xdigit:], [:space:], [:print:], [:punct:], [:graph:], and [:cntrl:]. If not set, then character classes are not supported. */ #define RE_CHAR_CLASSES (RE_BK_PLUS_QM << 1) /* If this bit is set, then ^ and $ are always anchors (outside bracket expressions, of course). If this bit is not set, then it depends: ^ is an anchor if it is at the beginning of a regular expression or after an open-group or an alternation operator; $ is an anchor if it is at the end of a regular expression, or before a close-group or an alternation operator. This bit could be (re)combined with RE_CONTEXT_INDEP_OPS, because POSIX draft 11.2 says that * etc. in leading positions is undefined. We already implemented a previous draft which made those constructs invalid, though, so we haven't changed the code back. */ #define RE_CONTEXT_INDEP_ANCHORS (RE_CHAR_CLASSES << 1) /* If this bit is set, then special characters are always special regardless of where they are in the pattern. If this bit is not set, then special characters are special only in some contexts; otherwise they are ordinary. Specifically, * + ? and intervals are only special when not after the beginning, open-group, or alternation operator. */ #define RE_CONTEXT_INDEP_OPS (RE_CONTEXT_INDEP_ANCHORS << 1) /* If this bit is set, then *, +, ?, and { cannot be first in an re or immediately after an alternation or begin-group operator. */ #define RE_CONTEXT_INVALID_OPS (RE_CONTEXT_INDEP_OPS << 1) /* If this bit is set, then . matches newline. If not set, then it doesn't. */ #define RE_DOT_NEWLINE (RE_CONTEXT_INVALID_OPS << 1) /* If this bit is set, then . doesn't match NUL. If not set, then it does. */ #define RE_DOT_NOT_NULL (RE_DOT_NEWLINE << 1) /* If this bit is set, nonmatching lists [^...] do not match newline. If not set, they do. */ #define RE_HAT_LISTS_NOT_NEWLINE (RE_DOT_NOT_NULL << 1) /* If this bit is set, either \{...\} or {...} defines an interval, depending on RE_NO_BK_BRACES. If not set, \{, \}, {, and } are literals. */ #define RE_INTERVALS (RE_HAT_LISTS_NOT_NEWLINE << 1) /* If this bit is set, +, ? and | aren't recognized as operators. If not set, they are. */ #define RE_LIMITED_OPS (RE_INTERVALS << 1) /* If this bit is set, newline is an alternation operator. If not set, newline is literal. */ #define RE_NEWLINE_ALT (RE_LIMITED_OPS << 1) /* If this bit is set, then `{...}' defines an interval, and \{ and \} are literals. If not set, then `\{...\}' defines an interval. */ #define RE_NO_BK_BRACES (RE_NEWLINE_ALT << 1) /* If this bit is set, (...) defines a group, and \( and \) are literals. If not set, \(...\) defines a group, and ( and ) are literals. */ #define RE_NO_BK_PARENS (RE_NO_BK_BRACES << 1) /* If this bit is set, then \<digit> matches <digit>. If not set, then \<digit> is a back-reference. */ #define RE_NO_BK_REFS (RE_NO_BK_PARENS << 1) /* If this bit is set, then | is an alternation operator, and \| is literal. If not set, then \| is an alternation operator, and | is literal. */ #define RE_NO_BK_VBAR (RE_NO_BK_REFS << 1) /* If this bit is set, then an ending range point collating higher than the starting range point, as in [z-a], is invalid. If not set, then when ending range point collates higher than the starting range point, the range is ignored. */ #define RE_NO_EMPTY_RANGES (RE_NO_BK_VBAR << 1) /* If this bit is set, then an unmatched ) is ordinary. If not set, then an unmatched ) is invalid. */ #define RE_UNMATCHED_RIGHT_PAREN_ORD (RE_NO_EMPTY_RANGES << 1) /* If this bit is set, succeed as soon as we match the whole pattern, without further backtracking. */ #define RE_NO_POSIX_BACKTRACKING (RE_UNMATCHED_RIGHT_PAREN_ORD << 1) /* If this bit is set, do not process the GNU regex operators. If not set, then the GNU regex operators are recognized. */ #define RE_NO_GNU_OPS (RE_NO_POSIX_BACKTRACKING << 1) /* If this bit is set, turn on internal regex debugging. If not set, and debugging was on, turn it off. This only works if regex.c is compiled -DDEBUG. We define this bit always, so that all that's needed to turn on debugging is to recompile regex.c; the calling code can always have this bit set, and it won't affect anything in the normal case. */ #define RE_DEBUG (RE_NO_GNU_OPS << 1) /* If this bit is set, a syntactically invalid interval is treated as a string of ordinary characters. For example, the ERE 'a{1' is treated as 'a\{1'. */ #define RE_INVALID_INTERVAL_ORD (RE_DEBUG << 1) /* If this bit is set, then ignore case when matching. If not set, then case is significant. */ #define RE_ICASE (RE_INVALID_INTERVAL_ORD << 1) /* This bit is used internally like RE_CONTEXT_INDEP_ANCHORS but only for ^, because it is difficult to scan the regex backwards to find whether ^ should be special. */ #define RE_CARET_ANCHORS_HERE (RE_ICASE << 1) /* If this bit is set, then \{ cannot be first in an bre or immediately after an alternation or begin-group operator. */ #define RE_CONTEXT_INVALID_DUP (RE_CARET_ANCHORS_HERE << 1) /* This global variable defines the particular regexp syntax to use (for some interfaces). When a regexp is compiled, the syntax used is stored in the pattern buffer, so changing this does not affect already-compiled regexps. */ extern reg_syntax_t re_syntax_options; /* Define combinations of the above bits for the standard possibilities. (The [[[ comments delimit what gets put into the Texinfo file, so don't delete them!) */ /* [[[begin syntaxes]]] */ #define RE_SYNTAX_EMACS 0 #define RE_SYNTAX_AWK \ (RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DOT_NOT_NULL \ | RE_NO_BK_PARENS | RE_NO_BK_REFS \ | RE_NO_BK_VBAR | RE_NO_EMPTY_RANGES \ | RE_DOT_NEWLINE | RE_CONTEXT_INDEP_ANCHORS \ | RE_UNMATCHED_RIGHT_PAREN_ORD | RE_NO_GNU_OPS) #define RE_SYNTAX_GNU_AWK \ ((RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DEBUG) \ & ~(RE_DOT_NOT_NULL | RE_INTERVALS | RE_CONTEXT_INDEP_OPS \ | RE_CONTEXT_INVALID_OPS )) #define RE_SYNTAX_POSIX_AWK \ (RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS \ | RE_INTERVALS | RE_NO_GNU_OPS) #define RE_SYNTAX_GREP \ (RE_BK_PLUS_QM | RE_CHAR_CLASSES \ | RE_HAT_LISTS_NOT_NEWLINE | RE_INTERVALS \ | RE_NEWLINE_ALT) #define RE_SYNTAX_EGREP \ (RE_CHAR_CLASSES | RE_CONTEXT_INDEP_ANCHORS \ | RE_CONTEXT_INDEP_OPS | RE_HAT_LISTS_NOT_NEWLINE \ | RE_NEWLINE_ALT | RE_NO_BK_PARENS \ | RE_NO_BK_VBAR) #define RE_SYNTAX_POSIX_EGREP \ (RE_SYNTAX_EGREP | RE_INTERVALS | RE_NO_BK_BRACES \ | RE_INVALID_INTERVAL_ORD) /* P1003.2/D11.2, section 4.20.7.1, lines 5078ff. */ #define RE_SYNTAX_ED RE_SYNTAX_POSIX_BASIC #define RE_SYNTAX_SED RE_SYNTAX_POSIX_BASIC /* Syntax bits common to both basic and extended POSIX regex syntax. */ #define _RE_SYNTAX_POSIX_COMMON \ (RE_CHAR_CLASSES | RE_DOT_NEWLINE | RE_DOT_NOT_NULL \ | RE_INTERVALS | RE_NO_EMPTY_RANGES) #define RE_SYNTAX_POSIX_BASIC \ (_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM | RE_CONTEXT_INVALID_DUP) /* Differs from ..._POSIX_BASIC only in that RE_BK_PLUS_QM becomes RE_LIMITED_OPS, i.e., \? \+ \| are not recognized. Actually, this isn't minimal, since other operators, such as \`, aren't disabled. */ #define RE_SYNTAX_POSIX_MINIMAL_BASIC \ (_RE_SYNTAX_POSIX_COMMON | RE_LIMITED_OPS) #define RE_SYNTAX_POSIX_EXTENDED \ (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ | RE_CONTEXT_INDEP_OPS | RE_NO_BK_BRACES \ | RE_NO_BK_PARENS | RE_NO_BK_VBAR \ | RE_CONTEXT_INVALID_OPS | RE_UNMATCHED_RIGHT_PAREN_ORD) /* Differs from ..._POSIX_EXTENDED in that RE_CONTEXT_INDEP_OPS is removed and RE_NO_BK_REFS is added. */ #define RE_SYNTAX_POSIX_MINIMAL_EXTENDED \ (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ | RE_CONTEXT_INVALID_OPS | RE_NO_BK_BRACES \ | RE_NO_BK_PARENS | RE_NO_BK_REFS \ | RE_NO_BK_VBAR | RE_UNMATCHED_RIGHT_PAREN_ORD) /* [[[end syntaxes]]] */ /* Maximum number of duplicates an interval can allow. Some systems (erroneously) define this in other header files, but we want our value, so remove any previous define. */ #ifdef RE_DUP_MAX # undef RE_DUP_MAX #endif /* If sizeof(int) == 2, then ((1 << 15) - 1) overflows. */ #define RE_DUP_MAX (0x7fff) /* POSIX `cflags' bits (i.e., information for `regcomp'). */ /* If this bit is set, then use extended regular expression syntax. If not set, then use basic regular expression syntax. */ #define REG_EXTENDED 1 /* If this bit is set, then ignore case when matching. If not set, then case is significant. */ #define REG_ICASE (REG_EXTENDED << 1) /* If this bit is set, then anchors do not match at newline characters in the string. If not set, then anchors do match at newlines. */ #define REG_NEWLINE (REG_ICASE << 1) /* If this bit is set, then report only success or fail in regexec. If not set, then returns differ between not matching and errors. */ #define REG_NOSUB (REG_NEWLINE << 1) /* POSIX `eflags' bits (i.e., information for regexec). */ /* If this bit is set, then the beginning-of-line operator doesn't match the beginning of the string (presumably because it's not the beginning of a line). If not set, then the beginning-of-line operator does match the beginning of the string. */ #define REG_NOTBOL 1 /* Like REG_NOTBOL, except for the end-of-line. */ #define REG_NOTEOL (1 << 1) /* If any error codes are removed, changed, or added, update the `re_error_msg' table in regex.c. */ typedef enum { #ifdef _XOPEN_SOURCE REG_ENOSYS = -1, /* This will never happen for this implementation. */ #endif REG_NOERROR = 0, /* Success. */ REG_NOMATCH, /* Didn't find a match (for regexec). */ /* POSIX regcomp return error codes. (In the order listed in the standard.) */ REG_BADPAT, /* Invalid pattern. */ REG_ECOLLATE, /* Not implemented. */ REG_ECTYPE, /* Invalid character class name. */ REG_EESCAPE, /* Trailing backslash. */ REG_ESUBREG, /* Invalid back reference. */ REG_EBRACK, /* Unmatched left bracket. */ REG_EPAREN, /* Parenthesis imbalance. */ REG_EBRACE, /* Unmatched \{. */ REG_BADBR, /* Invalid contents of \{\}. */ REG_ERANGE, /* Invalid range end. */ REG_ESPACE, /* Ran out of memory. */ REG_BADRPT, /* No preceding re for repetition op. */ /* Error codes we've added. */ REG_EEND, /* Premature end. */ REG_ESIZE, /* Compiled pattern bigger than 2^16 bytes. */ REG_ERPAREN /* Unmatched ) or \); not returned from regcomp. */ } reg_errcode_t; /* This data structure represents a compiled pattern. Before calling the pattern compiler, the fields `buffer', `allocated', `fastmap', `translate', and `no_sub' can be set. After the pattern has been compiled, the `re_nsub' field is available. All other fields are private to the regex routines. */ #ifndef RE_TRANSLATE_TYPE # define RE_TRANSLATE_TYPE char * #endif struct re_pattern_buffer { /* [[[begin pattern_buffer]]] */ /* Space that holds the compiled pattern. It is declared as `unsigned char *' because its elements are sometimes used as array indexes. */ unsigned char *buffer; /* Number of bytes to which `buffer' points. */ unsigned long int allocated; /* Number of bytes actually used in `buffer'. */ unsigned long int used; /* Syntax setting with which the pattern was compiled. */ reg_syntax_t syntax; /* Pointer to a fastmap, if any, otherwise zero. re_search uses the fastmap, if there is one, to skip over impossible starting points for matches. */ char *fastmap; /* Either a translate table to apply to all characters before comparing them, or zero for no translation. The translation is applied to a pattern when it is compiled and to a string when it is matched. */ RE_TRANSLATE_TYPE translate; /* Number of subexpressions found by the compiler. */ size_t re_nsub; /* Zero if this pattern cannot match the empty string, one else. Well, in truth it's used only in `re_search_2', to see whether or not we should use the fastmap, so we don't set this absolutely perfectly; see `re_compile_fastmap' (the `duplicate' case). */ unsigned can_be_null : 1; /* If REGS_UNALLOCATED, allocate space in the `regs' structure for `max (RE_NREGS, re_nsub + 1)' groups. If REGS_REALLOCATE, reallocate space if necessary. If REGS_FIXED, use what's there. */ #define REGS_UNALLOCATED 0 #define REGS_REALLOCATE 1 #define REGS_FIXED 2 unsigned regs_allocated : 2; /* Set to zero when `regex_compile' compiles a pattern; set to one by `re_compile_fastmap' if it updates the fastmap. */ unsigned fastmap_accurate : 1; /* If set, `re_match_2' does not return information about subexpressions. */ unsigned no_sub : 1; /* If set, a beginning-of-line anchor doesn't match at the beginning of the string. */ unsigned not_bol : 1; /* Similarly for an end-of-line anchor. */ unsigned not_eol : 1; /* If true, an anchor at a newline matches. */ unsigned newline_anchor : 1; /* [[[end pattern_buffer]]] */ }; typedef struct re_pattern_buffer regex_t; /* Type for byte offsets within the string. POSIX mandates this. */ typedef int regoff_t; /* This is the structure we store register match data in. See regex.texinfo for a full description of what registers match. */ struct re_registers { unsigned num_regs; regoff_t *start; regoff_t *end; }; /* If `regs_allocated' is REGS_UNALLOCATED in the pattern buffer, `re_match_2' returns information about at least this many registers the first time a `regs' structure is passed. */ #ifndef RE_NREGS # define RE_NREGS 30 #endif /* POSIX specification for registers. Aside from the different names than `re_registers', POSIX uses an array of structures, instead of a structure of arrays. */ typedef struct { regoff_t rm_so; /* Byte offset from string's start to substring's start. */ regoff_t rm_eo; /* Byte offset from string's start to substring's end. */ } regmatch_t; /* Declarations for routines. */ /* To avoid duplicating every routine declaration -- once with a prototype (if we are ANSI), and once without (if we aren't) -- we use the following macro to declare argument types. This unfortunately clutters up the declarations a bit, but I think it's worth it. */ #if __STDC__ # define _RE_ARGS(args) args #else /* not __STDC__ */ # define _RE_ARGS(args) () #endif /* not __STDC__ */ /* Sets the current default syntax to SYNTAX, and return the old syntax. You can also simply assign to the `re_syntax_options' variable. */ extern reg_syntax_t re_set_syntax _RE_ARGS ((reg_syntax_t syntax)); /* Compile the regular expression PATTERN, with length LENGTH and syntax given by the global `re_syntax_options', into the buffer BUFFER. Return NULL if successful, and an error string if not. */ extern const char *re_compile_pattern _RE_ARGS ((const char *pattern, size_t length, struct re_pattern_buffer *buffer)); /* Compile a fastmap for the compiled pattern in BUFFER; used to accelerate searches. Return 0 if successful and -2 if was an internal error. */ extern int re_compile_fastmap _RE_ARGS ((struct re_pattern_buffer *buffer)); /* Search in the string STRING (with length LENGTH) for the pattern compiled into BUFFER. Start searching at position START, for RANGE characters. Return the starting position of the match, -1 for no match, or -2 for an internal error. Also return register information in REGS (if REGS and BUFFER->no_sub are nonzero). */ extern int re_search _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string, int length, int start, int range, struct re_registers *regs)); /* Like `re_search', but search in the concatenation of STRING1 and STRING2. Also, stop searching at index START + STOP. */ extern int re_search_2 _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1, int length1, const char *string2, int length2, int start, int range, struct re_registers *regs, int stop)); /* Like `re_search', but return how many characters in STRING the regexp in BUFFER matched, starting at position START. */ extern int re_match _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string, int length, int start, struct re_registers *regs)); /* Relates to `re_match' as `re_search_2' relates to `re_search'. */ extern int re_match_2 _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1, int length1, const char *string2, int length2, int start, struct re_registers *regs, int stop)); /* Set REGS to hold NUM_REGS registers, storing them in STARTS and ENDS. Subsequent matches using BUFFER and REGS will use this memory for recording register information. STARTS and ENDS must be allocated with malloc, and must each be at least `NUM_REGS * sizeof (regoff_t)' bytes long. If NUM_REGS == 0, then subsequent matches should allocate their own register data. Unless this function is called, the first search or match using PATTERN_BUFFER will allocate its own register data, without freeing the old data. */ extern void re_set_registers _RE_ARGS ((struct re_pattern_buffer *buffer, struct re_registers *regs, unsigned num_regs, regoff_t *starts, regoff_t *ends)); #if defined _REGEX_RE_COMP || defined _LIBC # ifndef _CRAY /* 4.2 bsd compatibility. */ extern char *re_comp _RE_ARGS ((const char *)); extern int re_exec _RE_ARGS ((const char *)); # endif #endif /* GCC 2.95 and later have "__restrict"; C99 compilers have "restrict", and "configure" may have defined "restrict". */ #ifndef __restrict # if ! (2 < __GNUC__ || (2 == __GNUC__ && 95 <= __GNUC_MINOR__)) # if defined restrict || 199901L <= __STDC_VERSION__ # define __restrict restrict # else # define __restrict # endif # endif #endif /* gcc 3.1 and up support the [restrict] syntax. */ #ifndef __restrict_arr # if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1) # define __restrict_arr __restrict # else # define __restrict_arr # endif #endif /* POSIX compatibility. */ extern int regcomp _RE_ARGS ((regex_t *__restrict __preg, const char *__restrict __pattern, int __cflags)); extern int regexec _RE_ARGS ((const regex_t *__restrict __preg, const char *__restrict __string, size_t __nmatch, regmatch_t __pmatch[__restrict_arr], int __eflags)); extern size_t regerror _RE_ARGS ((int __errcode, const regex_t *__preg, char *__errbuf, size_t __errbuf_size)); extern void regfree _RE_ARGS ((regex_t *__preg)); #ifdef __cplusplus } #endif /* C++ */ #endif /* regex.h */ /* Local variables: make-backup-files: t version-control: t trim-versions-without-asking: nil End: */
/* Extended regular expression matching and search library. Copyright (C) 2002, 2003 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Isamu Hasegawa <isamu@yamato.ibm.com>. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #if defined(_AIX) #pragma alloca #else # if !defined(alloca) /* predefined by HP cc +Olibcalls */ # ifdef __GNUC__ # define alloca(size) __builtin_alloca(size) # else # if HAVE_ALLOCA_H # include <alloca.h> # else # if defined(__hpux) void *alloca (); # else # if !defined(__OS2__) && !defined(WIN32) char *alloca (); # else # include <malloc.h> /* OS/2 defines alloca in here */ # endif # endif # endif # endif # endif #endif #ifdef _LIBC /* We have to keep the namespace clean. */ # define regfree(preg) __regfree (preg) # define regexec(pr, st, nm, pm, ef) __regexec (pr, st, nm, pm, ef) # define regcomp(preg, pattern, cflags) __regcomp (preg, pattern, cflags) # define regerror(errcode, preg, errbuf, errbuf_size) \ __regerror(errcode, preg, errbuf, errbuf_size) # define re_set_registers(bu, re, nu, st, en) \ __re_set_registers (bu, re, nu, st, en) # define re_match_2(bufp, string1, size1, string2, size2, pos, regs, stop) \ __re_match_2 (bufp, string1, size1, string2, size2, pos, regs, stop) # define re_match(bufp, string, size, pos, regs) \ __re_match (bufp, string, size, pos, regs) # define re_search(bufp, string, size, startpos, range, regs) \ __re_search (bufp, string, size, startpos, range, regs) # define re_compile_pattern(pattern, length, bufp) \ __re_compile_pattern (pattern, length, bufp) # define re_set_syntax(syntax) __re_set_syntax (syntax) # define re_search_2(bufp, st1, s1, st2, s2, startpos, range, regs, stop) \ __re_search_2 (bufp, st1, s1, st2, s2, startpos, range, regs, stop) # define re_compile_fastmap(bufp) __re_compile_fastmap (bufp) # include "../locale/localeinfo.h" #endif /* POSIX says that <sys/types.h> must be included (by the caller) before <regex.h>. */ #include <sys/types.h> #include <regex.h> #include "regex_internal.h" #include "regex_internal.c" #include "regcomp.c" #include "regexec.c" /* Binary backward compatibility. */ #if _LIBC # include <shlib-compat.h> # if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_3) link_warning (re_max_failures, "the 're_max_failures' variable is obsolete and will go away.") int re_max_failures = 2000; # endif #endif
/* obstack.c - subroutines used implicitly by object stack macros -*- C -*- Copyright (C) 1988,89,90,91,92,93,94,96,97 Free Software Foundation, Inc. This file is part of the GNU C Library. Its master source is NOT part of the C library, however. The master source lives in /gd/gnu/lib. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "obstack.h" /* NOTE BEFORE MODIFYING THIS FILE: This version number must be incremented whenever callers compiled using an old obstack.h can no longer properly call the functions in this obstack.c. */ #define OBSTACK_INTERFACE_VERSION 1 /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself, and the installed library supports the same library interface we do. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #include <stdio.h> /* Random thing to get __GNU_LIBRARY__. */ #if !defined (_LIBC) && defined (__GNU_LIBRARY__) && __GNU_LIBRARY__ > 1 #include <gnu-versions.h> #if _GNU_OBSTACK_INTERFACE_VERSION == OBSTACK_INTERFACE_VERSION #define ELIDE_CODE #endif #endif #ifndef ELIDE_CODE #if defined (__STDC__) && __STDC__ #define POINTER void * #else #define POINTER char * #endif /* Determine default alignment. */ struct fooalign {char x; double d;}; #define DEFAULT_ALIGNMENT \ ((PTR_INT_TYPE) ((char *) &((struct fooalign *) 0)->d - (char *) 0)) /* If malloc were really smart, it would round addresses to DEFAULT_ALIGNMENT. But in fact it might be less smart and round addresses to as much as DEFAULT_ROUNDING. So we prepare for it to do that. */ union fooround {long x; double d;}; #define DEFAULT_ROUNDING (sizeof (union fooround)) #ifdef original_glibc_code /**//* When we copy a long block of data, this is the unit to do it with. */ /**//* On some machines, copying successive ints does not work; */ /**//* in such a case, redefine COPYING_UNIT to `long' (if that works) */ /**//* or `char' as a last resort. */ /**/#ifndef COPYING_UNIT /**/#define COPYING_UNIT int /**/#endif #endif /* The functions allocating more room by calling `obstack_chunk_alloc' jump to the handler pointed to by `obstack_alloc_failed_handler'. This variable by default points to the internal function `print_and_abort'. */ #if defined (__STDC__) && __STDC__ static void print_and_abort (void); void (*obstack_alloc_failed_handler) (void) = print_and_abort; #else static void print_and_abort (); void (*obstack_alloc_failed_handler) () = print_and_abort; #endif /* Exit value used when `print_and_abort' is used. */ #if defined __GNU_LIBRARY__ || defined HAVE_STDLIB_H #include <stdlib.h> #endif #ifndef EXIT_FAILURE #define EXIT_FAILURE 1 #endif int obstack_exit_failure = EXIT_FAILURE; /* The non-GNU-C macros copy the obstack into this global variable to avoid multiple evaluation. */ struct obstack *_obstack; /* Define a macro that either calls functions with the traditional malloc/free calling interface, or calls functions with the mmalloc/mfree interface (that adds an extra first argument), based on the state of use_extra_arg. For free, do not use ?:, since some compilers, like the MIPS compilers, do not allow (expr) ? void : void. */ #if defined (__STDC__) && __STDC__ #define CALL_CHUNKFUN(h, size) \ (((h) -> use_extra_arg) \ ? (*(h)->chunkfun) ((h)->extra_arg, (size)) \ : (*(struct _obstack_chunk *(*) (long)) (h)->chunkfun) ((size))) #define CALL_FREEFUN(h, old_chunk) \ do { \ if ((h) -> use_extra_arg) \ (*(h)->freefun) ((h)->extra_arg, (old_chunk)); \ else \ (*(void (*) (void *)) (h)->freefun) ((old_chunk)); \ } while (0) #else #define CALL_CHUNKFUN(h, size) \ (((h) -> use_extra_arg) \ ? (*(h)->chunkfun) ((h)->extra_arg, (size)) \ : (*(struct _obstack_chunk *(*) ()) (h)->chunkfun) ((size))) #define CALL_FREEFUN(h, old_chunk) \ do { \ if ((h) -> use_extra_arg) \ (*(h)->freefun) ((h)->extra_arg, (old_chunk)); \ else \ (*(void (*) ()) (h)->freefun) ((old_chunk)); \ } while (0) #endif /* Initialize an obstack H for use. Specify chunk size SIZE (0 means default). Objects start on multiples of ALIGNMENT (0 means use default). CHUNKFUN is the function to use to allocate chunks, and FREEFUN the function to free them. Return nonzero if successful, zero if out of memory. To recover from an out of memory error, free up some memory, then call this again. */ int _obstack_begin (h, size, alignment, chunkfun, freefun) struct obstack *h; int size; int alignment; #if defined (__STDC__) && __STDC__ POINTER (*chunkfun) (long); void (*freefun) (void *); #else POINTER (*chunkfun) (); void (*freefun) (); #endif { register struct _obstack_chunk *chunk; /* points to new chunk */ if (alignment == 0) alignment = DEFAULT_ALIGNMENT; if (size == 0) /* Default size is what GNU malloc can fit in a 4096-byte block. */ { /* 12 is sizeof (mhead) and 4 is EXTRA from GNU malloc. Use the values for range checking, because if range checking is off, the extra bytes won't be missed terribly, but if range checking is on and we used a larger request, a whole extra 4096 bytes would be allocated. These number are irrelevant to the new GNU malloc. I suspect it is less sensitive to the size of the request. */ int extra = ((((12 + DEFAULT_ROUNDING - 1) & ~(DEFAULT_ROUNDING - 1)) + 4 + DEFAULT_ROUNDING - 1) & ~(DEFAULT_ROUNDING - 1)); size = 4096 - extra; } #if defined (__STDC__) && __STDC__ h->chunkfun = (struct _obstack_chunk * (*)(void *, long)) chunkfun; h->freefun = (void (*) (void *, struct _obstack_chunk *)) freefun; #else h->chunkfun = (struct _obstack_chunk * (*)()) chunkfun; h->freefun = freefun; #endif h->chunk_size = size; h->alignment_mask = alignment - 1; h->use_extra_arg = 0; chunk = h->chunk = CALL_CHUNKFUN (h, h -> chunk_size); if (!chunk) (*obstack_alloc_failed_handler) (); h->next_free = h->object_base = chunk->contents; h->chunk_limit = chunk->limit = (char *) chunk + h->chunk_size; chunk->prev = 0; /* The initial chunk now contains no empty object. */ h->maybe_empty_object = 0; h->alloc_failed = 0; return 1; } int _obstack_begin_1 (h, size, alignment, chunkfun, freefun, arg) struct obstack *h; int size; int alignment; #if defined (__STDC__) && __STDC__ POINTER (*chunkfun) (POINTER, long); void (*freefun) (POINTER, POINTER); #else POINTER (*chunkfun) (); void (*freefun) (); #endif POINTER arg; { register struct _obstack_chunk *chunk; /* points to new chunk */ if (alignment == 0) alignment = DEFAULT_ALIGNMENT; if (size == 0) /* Default size is what GNU malloc can fit in a 4096-byte block. */ { /* 12 is sizeof (mhead) and 4 is EXTRA from GNU malloc. Use the values for range checking, because if range checking is off, the extra bytes won't be missed terribly, but if range checking is on and we used a larger request, a whole extra 4096 bytes would be allocated. These number are irrelevant to the new GNU malloc. I suspect it is less sensitive to the size of the request. */ int extra = ((((12 + DEFAULT_ROUNDING - 1) & ~(DEFAULT_ROUNDING - 1)) + 4 + DEFAULT_ROUNDING - 1) & ~(DEFAULT_ROUNDING - 1)); size = 4096 - extra; } #if defined(__STDC__) && __STDC__ h->chunkfun = (struct _obstack_chunk * (*)(void *,long)) chunkfun; h->freefun = (void (*) (void *, struct _obstack_chunk *)) freefun; #else h->chunkfun = (struct _obstack_chunk * (*)()) chunkfun; h->freefun = freefun; #endif h->chunk_size = size; h->alignment_mask = alignment - 1; h->extra_arg = arg; h->use_extra_arg = 1; chunk = h->chunk = CALL_CHUNKFUN (h, h -> chunk_size); if (!chunk) (*obstack_alloc_failed_handler) (); h->next_free = h->object_base = chunk->contents; h->chunk_limit = chunk->limit = (char *) chunk + h->chunk_size; chunk->prev = 0; /* The initial chunk now contains no empty object. */ h->maybe_empty_object = 0; h->alloc_failed = 0; return 1; } /* Allocate a new current chunk for the obstack *H on the assumption that LENGTH bytes need to be added to the current object, or a new object of length LENGTH allocated. Copies any partial object from the end of the old chunk to the beginning of the new one. */ void _obstack_newchunk (h, length) struct obstack *h; int length; { register struct _obstack_chunk *old_chunk = h->chunk; register struct _obstack_chunk *new_chunk; register long new_size; register int obj_size = h->next_free - h->object_base; /* Compute size for new chunk. */ new_size = (obj_size + length) + (obj_size >> 3) + 100; if (new_size < h->chunk_size) new_size = h->chunk_size; /* Allocate and initialize the new chunk. */ new_chunk = CALL_CHUNKFUN (h, new_size); if (!new_chunk) (*obstack_alloc_failed_handler) (); h->chunk = new_chunk; new_chunk->prev = old_chunk; new_chunk->limit = h->chunk_limit = (char *) new_chunk + new_size; _obstack_memcpy(new_chunk->contents, h->object_base, obj_size); /* If the object just copied was the only data in OLD_CHUNK, */ /* free that chunk and remove it from the chain. */ /* But not if that chunk might contain an empty object. */ if (h->object_base == old_chunk->contents && ! h->maybe_empty_object) { new_chunk->prev = old_chunk->prev; CALL_FREEFUN (h, old_chunk); } h->object_base = new_chunk->contents; h->next_free = h->object_base + obj_size; /* The new chunk certainly contains no empty object yet. */ h->maybe_empty_object = 0; } /* Return nonzero if object OBJ has been allocated from obstack H. This is here for debugging. If you use it in a program, you are probably losing. */ #if defined (__STDC__) && __STDC__ /* Suppress -Wmissing-prototypes warning. We don't want to declare this in obstack.h because it is just for debugging. */ int _obstack_allocated_p (struct obstack *h, POINTER obj); #endif int _obstack_allocated_p (h, obj) struct obstack *h; POINTER obj; { register struct _obstack_chunk *lp; /* below addr of any objects in this chunk */ register struct _obstack_chunk *plp; /* point to previous chunk if any */ lp = (h)->chunk; /* We use >= rather than > since the object cannot be exactly at the beginning of the chunk but might be an empty object exactly at the end of an adjacent chunk. */ while (lp != 0 && ((POINTER) lp >= obj || (POINTER) (lp)->limit < obj)) { plp = lp->prev; lp = plp; } return lp != 0; } /* Free objects in obstack H, including OBJ and everything allocate more recently than OBJ. If OBJ is zero, free everything in H. */ #undef obstack_free /* This function has two names with identical definitions. This is the first one, called from non-ANSI code. */ void _obstack_free (h, obj) struct obstack *h; POINTER obj; { register struct _obstack_chunk *lp; /* below addr of any objects in this chunk */ register struct _obstack_chunk *plp; /* point to previous chunk if any */ lp = h->chunk; /* We use >= because there cannot be an object at the beginning of a chunk. But there can be an empty object at that address at the end of another chunk. */ while (lp != 0 && ((POINTER) lp >= obj || (POINTER) (lp)->limit < obj)) { plp = lp->prev; CALL_FREEFUN (h, lp); lp = plp; /* If we switch chunks, we can't tell whether the new current chunk contains an empty object, so assume that it may. */ h->maybe_empty_object = 1; } if (lp) { h->object_base = h->next_free = (char *) (obj); h->chunk_limit = lp->limit; h->chunk = lp; } else if (obj != 0) /* obj is not in any of the chunks! */ abort (); } /* This function is used from ANSI code. */ void obstack_free (h, obj) struct obstack *h; POINTER obj; { register struct _obstack_chunk *lp; /* below addr of any objects in this chunk */ register struct _obstack_chunk *plp; /* point to previous chunk if any */ lp = h->chunk; /* We use >= because there cannot be an object at the beginning of a chunk. But there can be an empty object at that address at the end of another chunk. */ while (lp != 0 && ((POINTER) lp >= obj || (POINTER) (lp)->limit < obj)) { plp = lp->prev; CALL_FREEFUN (h, lp); lp = plp; /* If we switch chunks, we can't tell whether the new current chunk contains an empty object, so assume that it may. */ h->maybe_empty_object = 1; } if (lp) { h->object_base = h->next_free = (char *) (obj); h->chunk_limit = lp->limit; h->chunk = lp; } else if (obj != 0) /* obj is not in any of the chunks! */ abort (); } int _obstack_memory_used (h) struct obstack *h; { register struct _obstack_chunk* lp; register int nbytes = 0; for (lp = h->chunk; lp != 0; lp = lp->prev) { nbytes += lp->limit - (char *) lp; } return nbytes; } /* Define the error handler. */ #ifndef _ # ifdef HAVE_LIBINTL_H # include <libintl.h> # ifndef _ # define _(Str) gettext (Str) # endif # else # define _(Str) (Str) # endif #endif static void print_and_abort () { fputs (_("memory exhausted\n"), stderr); exit (obstack_exit_failure); } #if 0 /* These are now turned off because the applications do not use it and it uses bcopy via obstack_grow, which causes trouble on sysV. */ /* Now define the functional versions of the obstack macros. Define them to simply use the corresponding macros to do the job. */ #if defined (__STDC__) && __STDC__ /* These function definitions do not work with non-ANSI preprocessors; they won't pass through the macro names in parentheses. */ /* The function names appear in parentheses in order to prevent the macro-definitions of the names from being expanded there. */ POINTER (obstack_base) (obstack) struct obstack *obstack; { return obstack_base (obstack); } POINTER (obstack_next_free) (obstack) struct obstack *obstack; { return obstack_next_free (obstack); } int (obstack_object_size) (obstack) struct obstack *obstack; { return obstack_object_size (obstack); } int (obstack_room) (obstack) struct obstack *obstack; { return obstack_room (obstack); } int (obstack_make_room) (obstack, length) struct obstack *obstack; int length; { return obstack_make_room (obstack, length); } void (obstack_grow) (obstack, pointer, length) struct obstack *obstack; POINTER pointer; int length; { obstack_grow (obstack, pointer, length); } void (obstack_grow0) (obstack, pointer, length) struct obstack *obstack; POINTER pointer; int length; { obstack_grow0 (obstack, pointer, length); } void (obstack_1grow) (obstack, character) struct obstack *obstack; int character; { obstack_1grow (obstack, character); } void (obstack_blank) (obstack, length) struct obstack *obstack; int length; { obstack_blank (obstack, length); } void (obstack_1grow_fast) (obstack, character) struct obstack *obstack; int character; { obstack_1grow_fast (obstack, character); } void (obstack_blank_fast) (obstack, length) struct obstack *obstack; int length; { obstack_blank_fast (obstack, length); } POINTER (obstack_finish) (obstack) struct obstack *obstack; { return obstack_finish (obstack); } POINTER (obstack_alloc) (obstack, length) struct obstack *obstack; int length; { return obstack_alloc (obstack, length); } POINTER (obstack_copy) (obstack, pointer, length) struct obstack *obstack; POINTER pointer; int length; { return obstack_copy (obstack, pointer, length); } POINTER (obstack_copy0) (obstack, pointer, length) struct obstack *obstack; POINTER pointer; int length; { return obstack_copy0 (obstack, pointer, length); } #endif /* __STDC__ */ #endif /* 0 */ #endif /* !ELIDE_CODE */
/* obstack.h - object stack macros Copyright (C) 1988,89,90,91,92,93,94,96,97,98,99 Free Software Foundation, Inc. This file is part of the GNU C Library. Its master source is NOT part of the C library, however. The master source lives in /gd/gnu/lib. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Summary: All the apparent functions defined here are macros. The idea is that you would use these pre-tested macros to solve a very specific set of problems, and they would run fast. Caution: no side-effects in arguments please!! They may be evaluated MANY times!! These macros operate a stack of objects. Each object starts life small, and may grow to maturity. (Consider building a word syllable by syllable.) An object can move while it is growing. Once it has been "finished" it never changes address again. So the "top of the stack" is typically an immature growing object, while the rest of the stack is of mature, fixed size and fixed address objects. These routines grab large chunks of memory, using a function you supply, called `obstack_chunk_alloc'. On occasion, they free chunks, by calling `obstack_chunk_free'. You must define them and declare them before using any obstack macros. Each independent stack is represented by a `struct obstack'. Each of the obstack macros expects a pointer to such a structure as the first argument. One motivation for this package is the problem of growing char strings in symbol tables. Unless you are "fascist pig with a read-only mind" --Gosper's immortal quote from HAKMEM item 154, out of context--you would not like to put any arbitrary upper limit on the length of your symbols. In practice this often means you will build many short symbols and a few long symbols. At the time you are reading a symbol you don't know how long it is. One traditional method is to read a symbol into a buffer, realloc()ating the buffer every time you try to read a symbol that is longer than the buffer. This is beaut, but you still will want to copy the symbol from the buffer to a more permanent symbol-table entry say about half the time. With obstacks, you can work differently. Use one obstack for all symbol names. As you read a symbol, grow the name in the obstack gradually. When the name is complete, finalize it. Then, if the symbol exists already, free the newly read name. The way we do this is to take a large chunk, allocating memory from low addresses. When you want to build a symbol in the chunk you just add chars above the current "high water mark" in the chunk. When you have finished adding chars, because you got to the end of the symbol, you know how long the chars are, and you can create a new object. Mostly the chars will not burst over the highest address of the chunk, because you would typically expect a chunk to be (say) 100 times as long as an average object. In case that isn't clear, when we have enough chars to make up the object, THEY ARE ALREADY CONTIGUOUS IN THE CHUNK (guaranteed) so we just point to it where it lies. No moving of chars is needed and this is the second win: potentially long strings need never be explicitly shuffled. Once an object is formed, it does not change its address during its lifetime. When the chars burst over a chunk boundary, we allocate a larger chunk, and then copy the partly formed object from the end of the old chunk to the beginning of the new larger chunk. We then carry on accreting characters to the end of the object as we normally would. A special macro is provided to add a single char at a time to a growing object. This allows the use of register variables, which break the ordinary 'growth' macro. Summary: We allocate large chunks. We carve out one object at a time from the current chunk. Once carved, an object never moves. We are free to append data of any size to the currently growing object. Exactly one object is growing in an obstack at any one time. You can run one obstack per control block. You may have as many control blocks as you dare. Because of the way we do it, you can `unwind' an obstack back to a previous state. (You may remove objects much as you would with a stack.) */ /* Don't do the contents of this file more than once. */ #ifndef _OBSTACK_H #define _OBSTACK_H 1 #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef __cplusplus extern "C" { #endif /* We use subtraction of (char *) 0 instead of casting to int because on word-addressable machines a simple cast to int may ignore the byte-within-word field of the pointer. */ #ifndef __PTR_TO_INT # define __PTR_TO_INT(P) ((P) - (char *) 0) #endif #ifndef __INT_TO_PTR # define __INT_TO_PTR(P) ((P) + (char *) 0) #endif /* We need the type of the resulting object. If __PTRDIFF_TYPE__ is defined, as with GNU C, use that; that way we don't pollute the namespace with <stddef.h>'s symbols. Otherwise, if <stddef.h> is available, include it and use ptrdiff_t. In traditional C, long is the best that we can do. */ #ifdef __PTRDIFF_TYPE__ # define PTR_INT_TYPE __PTRDIFF_TYPE__ #else # ifdef HAVE_STDDEF_H # include <stddef.h> # define PTR_INT_TYPE ptrdiff_t # else # define PTR_INT_TYPE long # endif #endif #if defined _LIBC || defined HAVE_STRING_H || defined HAVE_STRINGS_H # ifdef HAVE_STRINGS_H # include <strings.h> # else # include <string.h> # endif # define _obstack_memcpy(To, From, N) memcpy ((To), (From), (N)) #else # ifdef memcpy # define _obstack_memcpy(To, From, N) memcpy ((To), (From), (N)) # else # define _obstack_memcpy(To, From, N) bcopy ((From), (To), (N)) # endif #endif struct _obstack_chunk /* Lives at front of each chunk. */ { char *limit; /* 1 past end of this chunk */ struct _obstack_chunk *prev; /* address of prior chunk or NULL */ char contents[4]; /* objects begin here */ }; struct obstack /* control current object in current chunk */ { long chunk_size; /* preferred size to allocate chunks in */ struct _obstack_chunk *chunk; /* address of current struct obstack_chunk */ char *object_base; /* address of object we are building */ char *next_free; /* where to add next char to current object */ char *chunk_limit; /* address of char after current chunk */ PTR_INT_TYPE temp; /* Temporary for some macros. */ int alignment_mask; /* Mask of alignment for each object. */ #if defined __STDC__ && __STDC__ /* These prototypes vary based on `use_extra_arg', and we use casts to the prototypeless function type in all assignments, but having prototypes here quiets -Wstrict-prototypes. */ struct _obstack_chunk *(*chunkfun) (void *, long); void (*freefun) (void *, struct _obstack_chunk *); void *extra_arg; /* first arg for chunk alloc/dealloc funcs */ #else struct _obstack_chunk *(*chunkfun) (); /* User's fcn to allocate a chunk. */ void (*freefun) (); /* User's function to free a chunk. */ char *extra_arg; /* first arg for chunk alloc/dealloc funcs */ #endif unsigned use_extra_arg:1; /* chunk alloc/dealloc funcs take extra arg */ unsigned maybe_empty_object:1;/* There is a possibility that the current chunk contains a zero-length object. This prevents freeing the chunk if we allocate a bigger chunk to replace it. */ unsigned alloc_failed:1; /* No longer used, as we now call the failed handler on error, but retained for binary compatibility. */ }; /* Declare the external functions we use; they are in obstack.c. */ #if defined __STDC__ && __STDC__ extern void _obstack_newchunk (struct obstack *, int); extern void _obstack_free (struct obstack *, void *); extern int _obstack_begin (struct obstack *, int, int, void *(*) (long), void (*) (void *)); extern int _obstack_begin_1 (struct obstack *, int, int, void *(*) (void *, long), void (*) (void *, void *), void *); extern int _obstack_memory_used (struct obstack *); #else extern void _obstack_newchunk (); extern void _obstack_free (); extern int _obstack_begin (); extern int _obstack_begin_1 (); extern int _obstack_memory_used (); #endif #if defined __STDC__ && __STDC__ /* Do the function-declarations after the structs but before defining the macros. */ void obstack_init (struct obstack *obstack); void * obstack_alloc (struct obstack *obstack, int size); void * obstack_copy (struct obstack *obstack, const void *address, int size); void * obstack_copy0 (struct obstack *obstack, const void *address, int size); void obstack_free (struct obstack *obstack, void *block); void obstack_blank (struct obstack *obstack, int size); void obstack_grow (struct obstack *obstack, const void *data, int size); void obstack_grow0 (struct obstack *obstack, const void *data, int size); void obstack_1grow (struct obstack *obstack, int data_char); void obstack_ptr_grow (struct obstack *obstack, const void *data); void obstack_int_grow (struct obstack *obstack, int data); void * obstack_finish (struct obstack *obstack); int obstack_object_size (struct obstack *obstack); int obstack_room (struct obstack *obstack); void obstack_make_room (struct obstack *obstack, int size); void obstack_1grow_fast (struct obstack *obstack, int data_char); void obstack_ptr_grow_fast (struct obstack *obstack, const void *data); void obstack_int_grow_fast (struct obstack *obstack, int data); void obstack_blank_fast (struct obstack *obstack, int size); void * obstack_base (struct obstack *obstack); void * obstack_next_free (struct obstack *obstack); int obstack_alignment_mask (struct obstack *obstack); int obstack_chunk_size (struct obstack *obstack); int obstack_memory_used (struct obstack *obstack); #endif /* __STDC__ */ /* Non-ANSI C cannot really support alternative functions for these macros, so we do not declare them. */ /* Error handler called when `obstack_chunk_alloc' failed to allocate more memory. This can be set to a user defined function which should either abort gracefully or use longjump - but shouldn't return. The default action is to print a message and abort. */ #if defined __STDC__ && __STDC__ extern void (*obstack_alloc_failed_handler) (void); #else extern void (*obstack_alloc_failed_handler) (); #endif /* Exit value used when `print_and_abort' is used. */ extern int obstack_exit_failure; /* Pointer to beginning of object being allocated or to be allocated next. Note that this might not be the final address of the object because a new chunk might be needed to hold the final size. */ #define obstack_base(h) ((h)->object_base) /* Size for allocating ordinary chunks. */ #define obstack_chunk_size(h) ((h)->chunk_size) /* Pointer to next byte not yet allocated in current chunk. */ #define obstack_next_free(h) ((h)->next_free) /* Mask specifying low bits that should be clear in address of an object. */ #define obstack_alignment_mask(h) ((h)->alignment_mask) /* To prevent prototype warnings provide complete argument list in standard C version. */ #if defined __STDC__ && __STDC__ # define obstack_init(h) \ _obstack_begin ((h), 0, 0, \ (void *(*) (long)) obstack_chunk_alloc, \ (void (*) (void *)) obstack_chunk_free) # define obstack_begin(h, size) \ _obstack_begin ((h), (size), 0, \ (void *(*) (long)) obstack_chunk_alloc, \ (void (*) (void *)) obstack_chunk_free) # define obstack_specify_allocation(h, size, alignment, chunkfun, freefun) \ _obstack_begin ((h), (size), (alignment), \ (void *(*) (long)) (chunkfun), \ (void (*) (void *)) (freefun)) # define obstack_specify_allocation_with_arg(h, size, alignment, chunkfun, freefun, arg) \ _obstack_begin_1 ((h), (size), (alignment), \ (void *(*) (void *, long)) (chunkfun), \ (void (*) (void *, void *)) (freefun), (arg)) # define obstack_chunkfun(h, newchunkfun) \ ((h) -> chunkfun = (struct _obstack_chunk *(*)(void *, long)) (newchunkfun)) # define obstack_freefun(h, newfreefun) \ ((h) -> freefun = (void (*)(void *, struct _obstack_chunk *)) (newfreefun)) #else # define obstack_init(h) \ _obstack_begin ((h), 0, 0, \ (void *(*) ()) obstack_chunk_alloc, \ (void (*) ()) obstack_chunk_free) # define obstack_begin(h, size) \ _obstack_begin ((h), (size), 0, \ (void *(*) ()) obstack_chunk_alloc, \ (void (*) ()) obstack_chunk_free) # define obstack_specify_allocation(h, size, alignment, chunkfun, freefun) \ _obstack_begin ((h), (size), (alignment), \ (void *(*) ()) (chunkfun), \ (void (*) ()) (freefun)) # define obstack_specify_allocation_with_arg(h, size, alignment, chunkfun, freefun, arg) \ _obstack_begin_1 ((h), (size), (alignment), \ (void *(*) ()) (chunkfun), \ (void (*) ()) (freefun), (arg)) # define obstack_chunkfun(h, newchunkfun) \ ((h) -> chunkfun = (struct _obstack_chunk *(*)()) (newchunkfun)) # define obstack_freefun(h, newfreefun) \ ((h) -> freefun = (void (*)()) (newfreefun)) #endif #define obstack_1grow_fast(h,achar) (*((h)->next_free)++ = achar) #define obstack_blank_fast(h,n) ((h)->next_free += (n)) #define obstack_memory_used(h) _obstack_memory_used (h) #if defined __GNUC__ && defined __STDC__ && __STDC__ /* NextStep 2.0 cc is really gcc 1.93 but it defines __GNUC__ = 2 and does not implement __extension__. But that compiler doesn't define __GNUC_MINOR__. */ # if __GNUC__ < 2 || (__NeXT__ && !__GNUC_MINOR__) # define __extension__ # endif /* For GNU C, if not -traditional, we can define these macros to compute all args only once without using a global variable. Also, we can avoid using the `temp' slot, to make faster code. */ # define obstack_object_size(OBSTACK) \ __extension__ \ ({ struct obstack *__o = (OBSTACK); \ (unsigned) (__o->next_free - __o->object_base); }) # define obstack_room(OBSTACK) \ __extension__ \ ({ struct obstack *__o = (OBSTACK); \ (unsigned) (__o->chunk_limit - __o->next_free); }) # define obstack_make_room(OBSTACK,length) \ __extension__ \ ({ struct obstack *__o = (OBSTACK); \ int __len = (length); \ if (__o->chunk_limit - __o->next_free < __len) \ _obstack_newchunk (__o, __len); \ (void) 0; }) # define obstack_empty_p(OBSTACK) \ __extension__ \ ({ struct obstack *__o = (OBSTACK); \ (__o->chunk->prev == 0 && __o->next_free - __o->chunk->contents == 0); }) # define obstack_grow(OBSTACK,where,length) \ __extension__ \ ({ struct obstack *__o = (OBSTACK); \ int __len = (length); \ if (__o->next_free + __len > __o->chunk_limit) \ _obstack_newchunk (__o, __len); \ _obstack_memcpy (__o->next_free, (where), __len); \ __o->next_free += __len; \ (void) 0; }) # define obstack_grow0(OBSTACK,where,length) \ __extension__ \ ({ struct obstack *__o = (OBSTACK); \ int __len = (length); \ if (__o->next_free + __len + 1 > __o->chunk_limit) \ _obstack_newchunk (__o, __len + 1); \ _obstack_memcpy (__o->next_free, (where), __len); \ __o->next_free += __len; \ *(__o->next_free)++ = 0; \ (void) 0; }) # define obstack_1grow(OBSTACK,datum) \ __extension__ \ ({ struct obstack *__o = (OBSTACK); \ if (__o->next_free + 1 > __o->chunk_limit) \ _obstack_newchunk (__o, 1); \ *(__o->next_free)++ = (datum); \ (void) 0; }) /* These assume that the obstack alignment is good enough for pointers or ints, and that the data added so far to the current object shares that much alignment. */ # define obstack_ptr_grow(OBSTACK,datum) \ __extension__ \ ({ struct obstack *__o = (OBSTACK); \ if (__o->next_free + sizeof (void *) > __o->chunk_limit) \ _obstack_newchunk (__o, sizeof (void *)); \ *((void **)__o->next_free)++ = (datum); \ (void) 0; }) # define obstack_int_grow(OBSTACK,datum) \ __extension__ \ ({ struct obstack *__o = (OBSTACK); \ if (__o->next_free + sizeof (int) > __o->chunk_limit) \ _obstack_newchunk (__o, sizeof (int)); \ *((int *)__o->next_free)++ = (datum); \ (void) 0; }) # define obstack_ptr_grow_fast(h,aptr) \ (*((void **) (h)->next_free)++ = (aptr)) # define obstack_int_grow_fast(h,aint) \ (*((int *) (h)->next_free)++ = (aint)) # define obstack_blank(OBSTACK,length) \ __extension__ \ ({ struct obstack *__o = (OBSTACK); \ int __len = (length); \ if (__o->chunk_limit - __o->next_free < __len) \ _obstack_newchunk (__o, __len); \ __o->next_free += __len; \ (void) 0; }) # define obstack_alloc(OBSTACK,length) \ __extension__ \ ({ struct obstack *__h = (OBSTACK); \ obstack_blank (__h, (length)); \ obstack_finish (__h); }) # define obstack_copy(OBSTACK,where,length) \ __extension__ \ ({ struct obstack *__h = (OBSTACK); \ obstack_grow (__h, (where), (length)); \ obstack_finish (__h); }) # define obstack_copy0(OBSTACK,where,length) \ __extension__ \ ({ struct obstack *__h = (OBSTACK); \ obstack_grow0 (__h, (where), (length)); \ obstack_finish (__h); }) /* The local variable is named __o1 to avoid a name conflict when obstack_blank is called. */ # define obstack_finish(OBSTACK) \ __extension__ \ ({ struct obstack *__o1 = (OBSTACK); \ void *value; \ value = (void *) __o1->object_base; \ if (__o1->next_free == value) \ __o1->maybe_empty_object = 1; \ __o1->next_free \ = __INT_TO_PTR ((__PTR_TO_INT (__o1->next_free)+__o1->alignment_mask)\ & ~ (__o1->alignment_mask)); \ if (__o1->next_free - (char *)__o1->chunk \ > __o1->chunk_limit - (char *)__o1->chunk) \ __o1->next_free = __o1->chunk_limit; \ __o1->object_base = __o1->next_free; \ value; }) # define obstack_free(OBSTACK, OBJ) \ __extension__ \ ({ struct obstack *__o = (OBSTACK); \ void *__obj = (OBJ); \ if (__obj > (void *)__o->chunk && __obj < (void *)__o->chunk_limit) \ __o->next_free = __o->object_base = (char *)__obj; \ else (obstack_free) (__o, __obj); }) #else /* not __GNUC__ or not __STDC__ */ # define obstack_object_size(h) \ (unsigned) ((h)->next_free - (h)->object_base) # define obstack_room(h) \ (unsigned) ((h)->chunk_limit - (h)->next_free) # define obstack_empty_p(h) \ ((h)->chunk->prev == 0 && (h)->next_free - (h)->chunk->contents == 0) /* Note that the call to _obstack_newchunk is enclosed in (..., 0) so that we can avoid having void expressions in the arms of the conditional expression. Casting the third operand to void was tried before, but some compilers won't accept it. */ # define obstack_make_room(h,length) \ ( (h)->temp = (length), \ (((h)->next_free + (h)->temp > (h)->chunk_limit) \ ? (_obstack_newchunk ((h), (h)->temp), 0) : 0)) # define obstack_grow(h,where,length) \ ( (h)->temp = (length), \ (((h)->next_free + (h)->temp > (h)->chunk_limit) \ ? (_obstack_newchunk ((h), (h)->temp), 0) : 0), \ _obstack_memcpy ((h)->next_free, (where), (h)->temp), \ (h)->next_free += (h)->temp) # define obstack_grow0(h,where,length) \ ( (h)->temp = (length), \ (((h)->next_free + (h)->temp + 1 > (h)->chunk_limit) \ ? (_obstack_newchunk ((h), (h)->temp + 1), 0) : 0), \ _obstack_memcpy ((h)->next_free, (where), (h)->temp), \ (h)->next_free += (h)->temp, \ *((h)->next_free)++ = 0) # define obstack_1grow(h,datum) \ ( (((h)->next_free + 1 > (h)->chunk_limit) \ ? (_obstack_newchunk ((h), 1), 0) : 0), \ (*((h)->next_free)++ = (datum))) # define obstack_ptr_grow(h,datum) \ ( (((h)->next_free + sizeof (char *) > (h)->chunk_limit) \ ? (_obstack_newchunk ((h), sizeof (char *)), 0) : 0), \ (*((const char **) (((h)->next_free+=sizeof(char *))-sizeof(char *))) = (datum))) # define obstack_int_grow(h,datum) \ ( (((h)->next_free + sizeof (int) > (h)->chunk_limit) \ ? (_obstack_newchunk ((h), sizeof (int)), 0) : 0), \ (*((int *) (((h)->next_free+=sizeof(int))-sizeof(int))) = (datum))) # define obstack_ptr_grow_fast(h,aptr) \ (*((const char **) (h)->next_free)++ = (aptr)) # define obstack_int_grow_fast(h,aint) \ (*((int *) (h)->next_free)++ = (aint)) # define obstack_blank(h,length) \ ( (h)->temp = (length), \ (((h)->chunk_limit - (h)->next_free < (h)->temp) \ ? (_obstack_newchunk ((h), (h)->temp), 0) : 0), \ ((h)->next_free += (h)->temp)) # define obstack_alloc(h,length) \ (obstack_blank ((h), (length)), obstack_finish ((h))) # define obstack_copy(h,where,length) \ (obstack_grow ((h), (where), (length)), obstack_finish ((h))) # define obstack_copy0(h,where,length) \ (obstack_grow0 ((h), (where), (length)), obstack_finish ((h))) # define obstack_finish(h) \ ( ((h)->next_free == (h)->object_base \ ? (((h)->maybe_empty_object = 1), 0) \ : 0), \ (h)->temp = __PTR_TO_INT ((h)->object_base), \ (h)->next_free \ = __INT_TO_PTR ((__PTR_TO_INT ((h)->next_free)+(h)->alignment_mask) \ & ~ ((h)->alignment_mask)), \ (((h)->next_free - (char *) (h)->chunk \ > (h)->chunk_limit - (char *) (h)->chunk) \ ? ((h)->next_free = (h)->chunk_limit) : 0), \ (h)->object_base = (h)->next_free, \ __INT_TO_PTR ((h)->temp)) # if defined __STDC__ && __STDC__ # define obstack_free(h,obj) \ ( (h)->temp = (char *) (obj) - (char *) (h)->chunk, \ (((h)->temp > 0 && (h)->temp < (h)->chunk_limit - (char *) (h)->chunk)\ ? (int) ((h)->next_free = (h)->object_base \ = (h)->temp + (char *) (h)->chunk) \ : (((obstack_free) ((h), (h)->temp + (char *) (h)->chunk), 0), 0))) # else # define obstack_free(h,obj) \ ( (h)->temp = (char *) (obj) - (char *) (h)->chunk, \ (((h)->temp > 0 && (h)->temp < (h)->chunk_limit - (char *) (h)->chunk)\ ? (int) ((h)->next_free = (h)->object_base \ = (h)->temp + (char *) (h)->chunk) \ : (_obstack_free ((h), (h)->temp + (char *) (h)->chunk), 0))) # endif #endif /* not __GNUC__ or not __STDC__ */ #ifdef __cplusplus } /* C++ */ #endif #endif /* obstack.h */
/* Compare strings while treating digits characters numerically. Copyright (C) 1997, 2000, 2002 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Jean-François Bignolles <bignolle@ecoledoc.ibp.fr>, 1997. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <string.h> #include <ctype.h> /* states: S_N: normal, S_I: comparing integral part, S_F: comparing fractional parts, S_Z: idem but with leading Zeroes only */ #define S_N 0x0 #define S_I 0x4 #define S_F 0x8 #define S_Z 0xC /* result_type: CMP: return diff; LEN: compare using len_diff/diff */ #define CMP 2 #define LEN 3 /* ISDIGIT differs from isdigit, as follows: - Its arg may be any int or unsigned int; it need not be an unsigned char. - It's guaranteed to evaluate its argument exactly once. - It's typically faster. POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to ISDIGIT_LOCALE unless it's important to use the locale's definition of `digit' even when the host does not conform to POSIX. */ #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9) #undef __strverscmp #undef strverscmp #ifndef weak_alias # define __strverscmp strverscmp #endif /* Compare S1 and S2 as strings holding indices/version numbers, returning less than, equal to or greater than zero if S1 is less than, equal to or greater than S2 (for more info, see the texinfo doc). */ int __strverscmp (const char *s1, const char *s2) { const unsigned char *p1 = (const unsigned char *) s1; const unsigned char *p2 = (const unsigned char *) s2; unsigned char c1, c2; int state; int diff; /* Symbol(s) 0 [1-9] others (padding) Transition (10) 0 (01) d (00) x (11) - */ static const unsigned int next_state[] = { /* state x d 0 - */ /* S_N */ S_N, S_I, S_Z, S_N, /* S_I */ S_N, S_I, S_I, S_I, /* S_F */ S_N, S_F, S_F, S_F, /* S_Z */ S_N, S_F, S_Z, S_Z }; static const int result_type[] = { /* state x/x x/d x/0 x/- d/x d/d d/0 d/- 0/x 0/d 0/0 0/- -/x -/d -/0 -/- */ /* S_N */ CMP, CMP, CMP, CMP, CMP, LEN, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, /* S_I */ CMP, -1, -1, CMP, 1, LEN, LEN, CMP, 1, LEN, LEN, CMP, CMP, CMP, CMP, CMP, /* S_F */ CMP, CMP, CMP, CMP, CMP, LEN, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, /* S_Z */ CMP, 1, 1, CMP, -1, CMP, CMP, CMP, -1, CMP, CMP, CMP }; if (p1 == p2) return 0; c1 = *p1++; c2 = *p2++; /* Hint: '0' is a digit too. */ state = S_N | ((c1 == '0') + (ISDIGIT (c1) != 0)); while ((diff = c1 - c2) == 0 && c1 != '\0') { state = next_state[state]; c1 = *p1++; c2 = *p2++; state |= (c1 == '0') + (ISDIGIT (c1) != 0); } state = result_type[state << 2 | ((c2 == '0') + (ISDIGIT (c2) != 0))]; switch (state) { case CMP: return diff; case LEN: while (ISDIGIT (*p1++)) if (!ISDIGIT (*p2++)) return 1; return ISDIGIT (*p2) ? -1 : diff; default: return state; } } #ifdef weak_alias weak_alias (__strverscmp, strverscmp) #endif
#ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_STRINGS_H # include <strings.h> #else # include <string.h> #endif /* HAVE_STRINGS_H */ #ifdef HAVE_STDLIB_H # include <stdlib.h> #endif /* HAVE_STDLIB_H */ #ifdef HAVE_SYS_FILE_H # include <sys/file.h> #endif /* HAVE_SYS_FILE_H */ #ifdef HAVE_IO_H # include <io.h> #endif /* HAVE_IO_H */ #ifdef HAVE_UNISTD_H # include <unistd.h> #endif /* HAVE_UNISTD_H */ #ifdef HAVE_FCNTL_H #include <fcntl.h> #endif /* HAVE_FCNTL_H */ #include <limits.h> #include <errno.h> /* Generate a unique temporary file name from template. The last six characters of template must be XXXXXX and these are replaced with a string that makes the filename unique. */ int mkstemp (template) char *template; { int i, j, n, fd; char *data = template + strlen(template) - 6; if (data < template) { errno = EINVAL; return -1; } for (n = 0; n <= 5; n++) if (data[n] != 'X') { errno = EINVAL; return -1; } for (i = 0; i < INT_MAX; i++) { j = i ^ 827714841; /* Base 36 DOSSUX :-) */ for (n = 5; n >= 0; n--) { data[n] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" [j % 36]; j /= 36; } fd = open (template, O_CREAT|O_EXCL|O_RDWR, 0600); if (fd != -1) return fd; } errno = EEXIST; return -1; }
/* GNU SED, a batch stream editor. Copyright (C) 1989,90,91,92,93,94,95,98,99,2002,2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* compile.c: translate sed source into internal form */ #include "sed.h" #include "strverscmp.h" #include <stdio.h> #include <ctype.h> #ifdef HAVE_STRINGS_H # include <strings.h> # ifdef HAVE_MEMORY_H # include <memory.h> # endif #else # include <string.h> #endif /* HAVE_STRINGS_H */ #ifdef HAVE_STDLIB_H # include <stdlib.h> #endif #ifndef EXIT_FAILURE # define EXIT_FAILURE 1 #endif #ifdef HAVE_SYS_TYPES_H # include <sys/types.h> #endif #include <obstack.h> extern flagT no_default_output; extern flagT POSIXLY_CORRECT; #define YMAP_LENGTH 256 /*XXX shouldn't this be (UCHAR_MAX+1)?*/ #define VECTOR_ALLOC_INCREMENT 40 /* let's not confuse text editors that have only dumb bracket-matching... */ #define OPEN_BRACKET '[' #define CLOSE_BRACKET ']' #define OPEN_BRACE '{' #define CLOSE_BRACE '}' struct prog_info { /* When we're reading a script command from a string, `prog.base' points to the first character in the string, 'prog.cur' points to the current character in the string, and 'prog.end' points to the end of the string. This allows us to compile script strings that contain nulls. */ const unsigned char *base; const unsigned char *cur; const unsigned char *end; /* This is the current script file. If it is NULL, we are reading from a string stored at `prog.cur' instead. If both `prog.file' and `prog.cur' are NULL, we're in trouble! */ FILE *file; }; /* Information used to give out useful and informative error messages. */ struct error_info { /* This is the name of the current script file. */ const char *name; /* This is the number of the current script line that we're compiling. */ countT line; /* This is the index of the "-e" expressions on the command line. */ countT string_expr_count; }; /* Label structure used to resolve GOTO's, labels, and block beginnings. */ struct sed_label { countT v_index; /* index of vector element being referenced */ char *name; /* NUL-terminated name of the label */ struct error_info err_info; /* track where `{}' blocks start */ struct sed_label *next; /* linked list (stack) */ }; struct special_files { char *name; FILE **pfp; }; FILE *my_stdin, *my_stdout, *my_stderr; struct special_files special_files[] = { { "/dev/stdin", &my_stdin }, { "/dev/stdout", &my_stdout }, { "/dev/stderr", &my_stderr }, { NULL, NULL } }; /* This structure tracks files opened by the `w' and `s///w' commands so that they may all be closed cleanly at normal program termination. Those marked as `special' are not closed. */ struct fp_list { char *name; int special; FILE *fp; struct fp_list *link; }; /* Where we are in the processing of the input. */ static struct prog_info prog; static struct error_info cur_input; /* Information about labels and jumps-to-labels. This is used to do the required backpatching after we have compiled all the scripts. */ static struct sed_label *jumps = NULL; static struct sed_label *labels = NULL; /* We wish to detect #n magic only in the first input argument; this flag tracks when we have consumed the first file of input. */ static flagT first_script = TRUE; /* Allow for scripts like "sed -e 'i\' -e foo": */ static struct buffer *pending_text = NULL; static struct text_buf *old_text_buf = NULL; /* Information about block start positions. This is used to backpatch block end positions. */ static struct sed_label *blocks = NULL; /* Use an obstack for compilation. */ static struct obstack obs; /* Various error messages we may want to print */ static const char errors[] = "Multiple `!'s\0" "Unexpected `,'\0" "Cannot use +N or ~N as first address\0" "Unmatched `{'\0" "Unexpected `}'\0" "Extra characters after command\0" "Expected \\ after `a', `c' or `i'\0" "`}' doesn't want any addresses\0" ": doesn't want any addresses\0" "Comments don't accept any addresses\0" "Missing command\0" "Command only uses one address\0" "Unterminated address regex\0" "Unterminated `s' command\0" "Unterminated `y' command\0" "Unknown option to `s'\0" "multiple `p' options to `s' command\0" "multiple `g' options to `s' command\0" "multiple number options to `s' command\0" "number option to `s' command may not be zero\0" "strings for y command are different lengths\0" "expected newer version of sed"; #define BAD_BANG (errors) #define BAD_COMMA (BAD_BANG + sizeof(N_("Multiple `!'s"))) #define BAD_PLUS (BAD_COMMA + sizeof(N_("Unexpected `,'"))) #define EXCESS_OPEN_BRACE (BAD_PLUS + sizeof(N_("Cannot use +N or ~N as first address"))) #define EXCESS_CLOSE_BRACE (EXCESS_OPEN_BRACE + sizeof(N_("Unmatched `{'"))) #define EXCESS_JUNK (EXCESS_CLOSE_BRACE + sizeof(N_("Unexpected `}'"))) #define EXPECTED_SLASH (EXCESS_JUNK + sizeof(N_("Extra characters after command"))) #define NO_CLOSE_BRACE_ADDR (EXPECTED_SLASH + sizeof(N_("Expected \\ after `a', `c' or `i'"))) #define NO_COLON_ADDR (NO_CLOSE_BRACE_ADDR + sizeof(N_("`}' doesn't want any addresses"))) #define NO_SHARP_ADDR (NO_COLON_ADDR + sizeof(N_(": doesn't want any addresses"))) #define NO_COMMAND (NO_SHARP_ADDR + sizeof(N_("Comments don't accept any addresses"))) #define ONE_ADDR (NO_COMMAND + sizeof(N_("Missing command"))) #define UNTERM_ADDR_RE (ONE_ADDR + sizeof(N_("Command only uses one address"))) #define UNTERM_S_CMD (UNTERM_ADDR_RE + sizeof(N_("Unterminated address regex"))) #define UNTERM_Y_CMD (UNTERM_S_CMD + sizeof(N_("Unterminated `s' command"))) #define UNKNOWN_S_OPT (UNTERM_Y_CMD + sizeof(N_("Unterminated `y' command"))) #define EXCESS_P_OPT (UNKNOWN_S_OPT + sizeof(N_("Unknown option to `s'"))) #define EXCESS_G_OPT (EXCESS_P_OPT + sizeof(N_("multiple `p' options to `s' command"))) #define EXCESS_N_OPT (EXCESS_G_OPT + sizeof(N_("multiple `g' options to `s' command"))) #define ZERO_N_OPT (EXCESS_N_OPT + sizeof(N_("multiple number options to `s' command"))) #define Y_CMD_LEN (ZERO_N_OPT + sizeof(N_("number option to `s' command may not be zero"))) #define ANCIENT_VERSION (Y_CMD_LEN + sizeof(N_("strings for y command are different lengths"))) #define END_ERRORS (ANCIENT_VERSION + sizeof(N_("expected newer version of sed"))) static struct fp_list *file_read = NULL; static struct fp_list *file_write = NULL; /* Read the next character from the program. Return EOF if there isn't anything to read. Keep cur_input.line up to date, so error messages can be meaningful. */ static int inchar P_((void)); static int inchar() { int ch = EOF; if (prog.cur) { if (prog.cur < prog.end) ch = *prog.cur++; } else if (prog.file) { if (!feof(prog.file)) ch = getc(prog.file); } if (ch == '\n') ++cur_input.line; return ch; } /* unget `ch' so the next call to inchar will return it. */ static void savchar P_((int ch)); static void savchar(ch) int ch; { if (ch == EOF) return; if (ch == '\n' && cur_input.line > 0) --cur_input.line; if (prog.cur) { if (prog.cur <= prog.base || *--prog.cur != ch) panic(_("Called savchar() with unexpected pushback (%x)"), CAST(unsigned char)ch); } else ungetc(ch, prog.file); } /* Read the next non-blank character from the program. */ static int in_nonblank P_((void)); static int in_nonblank() { int ch; do ch = inchar(); while (ISBLANK(ch)); return ch; } /* Read an integer value from the program. */ static countT in_integer P_((int ch)); static countT in_integer(ch) int ch; { countT num = 0; while (ISDIGIT(ch)) { num = num * 10 + ch - '0'; ch = inchar(); } savchar(ch); return num; } static int add_then_next P_((struct buffer *b, int ch)); static int add_then_next(b, ch) struct buffer *b; int ch; { add1_buffer(b, ch); return inchar(); } static char * convert_number P_((char *, char *, const char *, int, int, int)); static char * convert_number(result, buf, bufend, base, maxdigits, default_char) char *result; char *buf; const char *bufend; int base; int maxdigits; int default_char; { int n = 0; char *p; for (p=buf; p < bufend && maxdigits-- > 0; ++p) { int d = -1; switch (*p) { case '0': d = 0x0; break; case '1': d = 0x1; break; case '2': d = 0x2; break; case '3': d = 0x3; break; case '4': d = 0x4; break; case '5': d = 0x5; break; case '6': d = 0x6; break; case '7': d = 0x7; break; case '8': d = 0x8; break; case '9': d = 0x9; break; case 'A': case 'a': d = 0xa; break; case 'B': case 'b': d = 0xb; break; case 'C': case 'c': d = 0xc; break; case 'D': case 'd': d = 0xd; break; case 'E': case 'e': d = 0xe; break; case 'F': case 'f': d = 0xf; break; } if (d < 0 || base <= d) break; n = n * base + d; } if (p == buf) *result = default_char; else *result = n; return p; } /* Read in a filename for a `r', `w', or `s///w' command. */ static struct buffer *read_filename P_((void)); static struct buffer * read_filename() { struct buffer *b; int ch; b = init_buffer(); ch = in_nonblank(); while (ch != EOF && ch != '\n') { #if 0 /*XXX ZZZ 1998-09-12 kpp: added, then had second thoughts*/ if (!POSIXLY_CORRECT) if (ch == ';' || ch == CLOSE_BRACE || ch == '#') { savchar(ch); break; } #endif add1_buffer(b, ch); ch = inchar(); } add1_buffer(b, '\0'); return b; } static FILE *get_openfile P_((struct fp_list **file_ptrs, char *mode, flagT fail)); static FILE * get_openfile(file_ptrs, mode, fail) struct fp_list **file_ptrs; char *mode; flagT fail; { struct buffer *b; char *file_name; struct fp_list *p; int is_stderr; b = read_filename(); file_name = get_buffer(b); for (p=*file_ptrs; p; p=p->link) if (strcmp(p->name, file_name) == 0) break; if (!p) { FILE *fp = NULL; if (!POSIXLY_CORRECT) { /* Check whether it is a special file (stdin, stdout or stderr) */ struct special_files *special = special_files; /* std* sometimes are not constants, so they cannot be used in the initializer for special_files */ my_stdin = stdin; my_stdout = stdout; my_stderr = stderr; for (special = special_files; special->name; special++) if (strcmp(special->name, file_name) == 0) { fp = *special->pfp; break; } } p = OB_MALLOC(&obs, 1, struct fp_list); p->name = ck_strdup(file_name); p->special = fp != NULL; if (!fp) fp = ck_fopen(p->name, mode, fail); p->fp = fp; p->link = *file_ptrs; *file_ptrs = p; } free_buffer(b); return p->fp; } static struct sed_cmd *next_cmd_entry P_((struct vector **vectorp)); static struct sed_cmd * next_cmd_entry(vectorp) struct vector **vectorp; { struct sed_cmd *cmd; struct vector *v; v = *vectorp; if (v->v_length == v->v_allocated) { v->v_allocated += VECTOR_ALLOC_INCREMENT; v->v = REALLOC(v->v, v->v_allocated, struct sed_cmd); } cmd = v->v + v->v_length; cmd->a1 = NULL; cmd->a2 = NULL; cmd->a1_matched = FALSE; cmd->addr_bang = FALSE; cmd->cmd = '\0'; /* something invalid, to catch bugs early */ *vectorp = v; return cmd; } static int snarf_char_class P_((struct buffer *b)); static int snarf_char_class(b) struct buffer *b; { int ch; ch = inchar(); if (ch == '^') ch = add_then_next(b, ch); if (ch == CLOSE_BRACKET) ch = add_then_next(b, ch); while (ch != EOF && ch != '\n' && ch != CLOSE_BRACKET) { if (ch == OPEN_BRACKET) { int prev; int delim = ch = add_then_next(b, ch); if (delim != '.' && delim != ':' && delim != '=') continue; /* bypass the add_then_next() call at bottom of loop */ for (prev=ch=add_then_next(b, ch); !(ch==CLOSE_BRACKET && prev==delim); ch=add_then_next(b, ch)) { if (ch == EOF || ch == '\n') return ch; prev = ch; } } #ifndef REG_PERL else if (ch == '\\') { ch = inchar(); if (ch == EOF) break; if (ch != 'n' && ch != '\n') { add1_buffer(b, '\\'); continue; /* bypass the add_then_next() call at bottom of loop */ } ch = '\n'; } #endif ch = add_then_next(b, ch); } return ch; } static struct buffer *match_slash P_((int slash, flagT regex, flagT keep_back)); static struct buffer * match_slash(slash, regex, keep_backwhack) int slash; flagT regex; flagT keep_backwhack; { struct buffer *b; int ch; b = init_buffer(); while ((ch = inchar()) != EOF && ch != '\n' && ch != slash) { if (ch == '\\') { ch = inchar(); if (ch == EOF) break; #ifndef REG_PERL else if (ch == 'n' && regex) ch = '\n'; #endif else if (ch != '\n' && (ch != slash || keep_backwhack)) add1_buffer(b, '\\'); } else if (ch == OPEN_BRACKET && regex) { add1_buffer(b, ch); ch = snarf_char_class(b); if (ch != CLOSE_BRACKET) break; } add1_buffer(b, ch); } if (ch == slash) return b; if (ch == '\n') savchar(ch); /* for proper line number in error report */ free_buffer(b); return NULL; } static flagT mark_subst_opts P_((struct subst *cmd)); static flagT mark_subst_opts(cmd) struct subst *cmd; { int flags = 0; int ch; cmd->global = FALSE; cmd->print = FALSE; cmd->eval = FALSE; cmd->numb = 0; cmd->fp = NULL; for (;;) switch ( (ch = in_nonblank()) ) { case 'i': /* GNU extension */ case 'I': /* GNU extension */ flags |= REG_ICASE; break; #ifdef REG_PERL case 's': /* GNU extension */ case 'S': /* GNU extension */ if (extended_regexp_flags & REG_PERL) flags |= REG_DOTALL; break; case 'x': /* GNU extension */ case 'X': /* GNU extension */ if (extended_regexp_flags & REG_PERL) flags |= REG_EXTENDED; break; #endif case 'm': /* GNU extension */ case 'M': /* GNU extension */ flags |= REG_NEWLINE; break; case 'e': cmd->eval = TRUE; break; case 'p': if (cmd->print) bad_prog(_(EXCESS_P_OPT)); cmd->print |= (TRUE << cmd->eval); /* 1=before eval, 2=after */ break; case 'g': if (cmd->global) bad_prog(_(EXCESS_G_OPT)); cmd->global = TRUE; break; case 'w': cmd->fp = get_openfile(&file_write, "w", TRUE); return flags; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (cmd->numb) bad_prog(_(EXCESS_N_OPT)); cmd->numb = in_integer(ch); if (!cmd->numb) bad_prog(_(ZERO_N_OPT)); break; case CLOSE_BRACE: case '#': savchar(ch); /* Fall Through */ case EOF: case '\n': case ';': return flags; case '\r': if (inchar() == '\n') return flags; /* FALLTHROUGH */ default: bad_prog(_(UNKNOWN_S_OPT)); /*NOTREACHED*/ } } /* read in a label for a `:', `b', or `t' command */ static char *read_label P_((void)); static char * read_label() { struct buffer *b; int ch; char *ret; b = init_buffer(); ch = in_nonblank(); while (ch != EOF && ch != '\n' && !ISBLANK(ch) && ch != ';' && ch != CLOSE_BRACE && ch != '#') { add1_buffer(b, ch); ch = inchar(); } savchar(ch); add1_buffer(b, '\0'); ret = ck_strdup(get_buffer(b)); free_buffer(b); return ret; } /* Store a label (or label reference) created by a `:', `b', or `t' command so that the jump to/from the label can be backpatched after compilation is complete, or a reference created by a `{' to be backpatched when the corresponding `}' is found. */ static struct sed_label *setup_label P_((struct sed_label *, countT, char *, const struct error_info *)); static struct sed_label * setup_label(list, idx, name, err_info) struct sed_label *list; countT idx; char *name; const struct error_info *err_info; { struct sed_label *ret = OB_MALLOC(&obs, 1, struct sed_label); ret->v_index = idx; ret->name = name; if (err_info) MEMCPY(&ret->err_info, err_info, sizeof ret->err_info); ret->next = list; return ret; } static struct sed_label *release_label P_((struct sed_label *list_head)); static struct sed_label * release_label(list_head) struct sed_label *list_head; { struct sed_label *ret; if (!list_head) return NULL; ret = list_head->next; FREE(list_head->name); #if 0 /* We use obstacks */ FREE(list_head); #endif return ret; } static struct replacement *new_replacement P_((char *, size_t, enum replacement_types)); static struct replacement * new_replacement(text, length, type) char *text; size_t length; enum replacement_types type; { struct replacement *r = OB_MALLOC(&obs, 1, struct replacement); r->prefix = text; r->prefix_length = length; r->subst_id = -1; r->repl_type = type; /* r-> next = NULL; */ return r; } static void setup_replacement P_((struct subst *, const char *, size_t)); static void setup_replacement(sub, text, length) struct subst *sub; const char *text; size_t length; { char *base; char *p; char *text_end; enum replacement_types repl_type = repl_asis, save_type = repl_asis; struct replacement root; struct replacement *tail; sub->max_id = 0; base = MEMDUP(text, length, char); length = normalize_text(base, length); text_end = base + length; tail = &root; for (p=base; p<text_end; ++p) { if (*p == '\\') { /* Preceding the backslash may be some literal text: */ tail = tail->next = new_replacement(base, CAST(size_t)(p - base), repl_type); repl_type = save_type; /* Skip the backslash and look for a numeric back-reference: */ ++p; if (p<text_end) switch (*p) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': tail->subst_id = *p - '0'; if (sub->max_id < tail->subst_id) sub->max_id = tail->subst_id; break; case 'L': repl_type = repl_lowercase; save_type = repl_lowercase; break; case 'U': repl_type = repl_uppercase; save_type = repl_uppercase; break; case 'E': repl_type = repl_asis; save_type = repl_asis; break; case 'l': save_type = repl_type; repl_type |= repl_lowercase_first; break; case 'u': save_type = repl_type; repl_type |= repl_uppercase_first; break; default: p[-1] = *p; ++tail->prefix_length; } base = p + 1; } else if (*p == '&') { /* Preceding the ampersand may be some literal text: */ tail = tail->next = new_replacement(base, CAST(size_t)(p - base), repl_type); repl_type = save_type; tail->subst_id = 0; base = p + 1; } } /* There may be some trailing literal text: */ if (base < text_end) tail = tail->next = new_replacement(base, CAST(size_t)(text_end - base), repl_type); tail->next = NULL; sub->replacement = root.next; } static void read_text P_((struct text_buf *buf, int leadin_ch)); static void read_text(buf, leadin_ch) struct text_buf *buf; int leadin_ch; { int ch; /* Should we start afresh (as opposed to continue a partial text)? */ if (buf) { if (pending_text) free_buffer(pending_text); pending_text = init_buffer(); buf->text = NULL; buf->text_length = 0; old_text_buf = buf; } /* assert(old_text_buf != NULL); */ if (leadin_ch == EOF) return; #ifndef NO_INPUT_INDENT if (leadin_ch != '\n') add1_buffer(pending_text, leadin_ch); ch = inchar(); #else /*NO_INPUT_INDENT*/ if (leadin_ch == '\n') ch = in_nonblank(); else { add1_buffer(pending_text, leadin_ch); ch = inchar(); } #endif /*NO_INPUT_INDENT*/ while (ch!=EOF && ch!='\n') { if (ch == '\\') ch = inchar(); if (ch == EOF) { add1_buffer(pending_text, '\n'); return; } add1_buffer(pending_text, ch); #ifdef NO_INPUT_INDENT if (ch == '\n') ch = in_nonblank(); else #endif /*NO_INPUT_INDENT*/ ch = inchar(); } add1_buffer(pending_text, '\n'); if (!buf) buf = old_text_buf; buf->text_length = size_buffer(pending_text); buf->text = MEMDUP(get_buffer(pending_text), buf->text_length, char); free_buffer(pending_text); pending_text = NULL; } /* Try to read an address for a sed command. If it succeeds, return non-zero and store the resulting address in `*addr'. If the input doesn't look like an address read nothing and return zero. */ static flagT compile_address P_((struct addr *addr, int ch)); static flagT compile_address(addr, ch) struct addr *addr; int ch; { addr->addr_type = addr_is_null; addr->addr_step = 0; addr->addr_number = ~(countT)0; /* extremely unlikely to ever match */ addr->addr_regex = NULL; if (ch == '/' || ch == '\\') { int flags = 0; struct buffer *b; addr->addr_type = addr_is_regex; if (ch == '\\') ch = inchar(); if ( !(b = match_slash(ch, TRUE, TRUE)) ) bad_prog(_(UNTERM_ADDR_RE)); for(;;) { ch = in_nonblank(); switch(ch) { case 'I': /* GNU extension */ flags |= REG_ICASE; break; #ifdef REG_PERL case 'S': /* GNU extension */ if (extended_regexp_flags & REG_PERL) flags |= REG_DOTALL; break; case 'X': /* GNU extension */ if (extended_regexp_flags & REG_PERL) flags |= REG_EXTENDED; break; #endif case 'M': /* GNU extension */ flags |= REG_NEWLINE; break; default: savchar (ch); addr->addr_regex = compile_regex (b, flags, 0); free_buffer(b); return TRUE; } } } else if (ISDIGIT(ch)) { addr->addr_number = in_integer(ch); addr->addr_type = addr_is_num; ch = in_nonblank(); if (ch != '~') { savchar(ch); } else { countT step = in_integer(in_nonblank()); if (step > 0) { addr->addr_step = step; addr->addr_type = addr_is_num_mod; } } } else if (ch == '+' || ch == '~') { addr->addr_step = in_integer(in_nonblank()); if (addr->addr_step==0) ; /* default to addr_is_null; forces matching to stop on next line */ else if (ch == '+') addr->addr_type = addr_is_step; else addr->addr_type = addr_is_step_mod; } else if (ch == '$') { addr->addr_type = addr_is_last; } else return FALSE; return TRUE; } /* Read a program (or a subprogram within `{' `}' pairs) in and store the compiled form in `*vector'. Return a pointer to the new vector. */ static struct vector *compile_program P_((struct vector *)); static struct vector * compile_program(vector) struct vector *vector; { struct sed_cmd *cur_cmd; struct buffer *b; int ch; if (!vector) { vector = MALLOC(1, struct vector); vector->v = NULL; vector->v_allocated = 0; vector->v_length = 0; obstack_init (&obs); } if (pending_text) read_text(NULL, '\n'); for (;;) { struct addr a; while ((ch=inchar()) == ';' || ISSPACE(ch)) ; if (ch == EOF) break; cur_cmd = next_cmd_entry(&vector); if (compile_address(&a, ch)) { if (a.addr_type == addr_is_step || a.addr_type == addr_is_step_mod) bad_prog(_(BAD_PLUS)); cur_cmd->a1 = MEMDUP(&a, 1, struct addr); ch = in_nonblank(); if (ch == ',') { if (!compile_address(&a, in_nonblank())) bad_prog(_(BAD_COMMA)); if (a.addr_type == addr_is_num) a.addr_type = addr_is_num2; cur_cmd->a2 = MEMDUP(&a, 1, struct addr); ch = in_nonblank(); if ((cur_cmd->a1->addr_type == addr_is_num || cur_cmd->a1->addr_type == addr_is_num_mod) && cur_cmd->a1->addr_number == 0) cur_cmd->a1_matched = TRUE; } } if (ch == '!') { cur_cmd->addr_bang = TRUE; ch = in_nonblank(); if (ch == '!') bad_prog(_(BAD_BANG)); } cur_cmd->cmd = ch; switch (ch) { case '#': if (cur_cmd->a1) bad_prog(_(NO_SHARP_ADDR)); ch = inchar(); if (ch=='n' && first_script && cur_input.line < 2) if ( (prog.base && prog.cur==2+prog.base) || (prog.file && !prog.base && 2==ftell(prog.file))) no_default_output = TRUE; while (ch != EOF && ch != '\n') ch = inchar(); continue; /* restart the for (;;) loop */ case 'v': /* This is an extension. Programs needing GNU sed might start * with a `v' command so that other seds will stop. * We compare the version ignore it. */ { char *version = read_label (); char *compared_version; compared_version = (*version == '\0') ? "4.0" : version; if (strverscmp (compared_version, SED_FEATURE_VERSION) > 0) bad_prog(_(ANCIENT_VERSION)); free (version); POSIXLY_CORRECT = 0; } continue; case '{': blocks = setup_label(blocks, vector->v_length, NULL, &cur_input); cur_cmd->addr_bang = !cur_cmd->addr_bang; break; case '}': if (!blocks) bad_prog(_(EXCESS_CLOSE_BRACE)); if (cur_cmd->a1) bad_prog(_(NO_CLOSE_BRACE_ADDR)); ch = in_nonblank(); if (ch == CLOSE_BRACE || ch == '#') savchar(ch); else if (ch != EOF && ch != '\n' && ch != ';') bad_prog(_(EXCESS_JUNK)); vector->v[blocks->v_index].x.jump_index = vector->v_length; blocks = release_label(blocks); /* done with this entry */ break; case 'e': ch = in_nonblank(); if (ch == EOF || ch == '\n') { cur_cmd->x.cmd_txt.text_length = 0; break; } else goto read_text_to_slash; case 'a': case 'i': if (POSIXLY_CORRECT && cur_cmd->a2) bad_prog(_(ONE_ADDR)); /* Fall Through */ case 'c': ch = in_nonblank(); read_text_to_slash: if (ch == EOF) bad_prog(_(EXPECTED_SLASH)); if (ch == '\\') ch = inchar(); else { savchar(ch); ch = '\n'; } read_text(&cur_cmd->x.cmd_txt, ch); break; case ':': if (cur_cmd->a1) bad_prog(_(NO_COLON_ADDR)); labels = setup_label(labels, vector->v_length, read_label(), NULL); break; case 'b': case 't': case 'T': jumps = setup_label(jumps, vector->v_length, read_label(), NULL); break; case 'q': case 'Q': if (cur_cmd->a2) bad_prog(_(ONE_ADDR)); /* Fall through */ case 'l': case 'L': if (POSIXLY_CORRECT && cur_cmd->a2) bad_prog(_(ONE_ADDR)); ch = in_nonblank(); if (ISDIGIT(ch)) { cur_cmd->x.int_arg = in_integer(ch); ch = in_nonblank(); } else cur_cmd->x.int_arg = -1; if (ch == CLOSE_BRACE || ch == '#') savchar(ch); else if (ch != EOF && ch != '\n' && ch != ';') bad_prog(_(EXCESS_JUNK)); break; case '=': if (POSIXLY_CORRECT && cur_cmd->a2) bad_prog(_(ONE_ADDR)); /* Fall Through */ case 'd': case 'D': case 'g': case 'G': case 'h': case 'H': case 'n': case 'N': case 'p': case 'P': case 'x': ch = in_nonblank(); if (ch == CLOSE_BRACE || ch == '#') savchar(ch); else if (ch != EOF && ch != '\n' && ch != ';') bad_prog(_(EXCESS_JUNK)); break; case 'r': if (POSIXLY_CORRECT && cur_cmd->a2) bad_prog(_(ONE_ADDR)); b = read_filename(); cur_cmd->x.fname = ck_strdup(get_buffer(b)); free_buffer(b); break; case 'R': cur_cmd->x.fp = get_openfile(&file_read, "r", FALSE); break; case 'w': case 'W': cur_cmd->x.fp = get_openfile(&file_write, "w", TRUE); break; case 's': { struct buffer *b2; int flags; int slash; slash = inchar(); if ( !(b = match_slash(slash, TRUE, TRUE)) ) bad_prog(_(UNTERM_S_CMD)); if ( !(b2 = match_slash(slash, FALSE, TRUE)) ) bad_prog(_(UNTERM_S_CMD)); cur_cmd->x.cmd_subst = OB_MALLOC(&obs, 1, struct subst); setup_replacement(cur_cmd->x.cmd_subst, get_buffer(b2), size_buffer(b2)); free_buffer(b2); flags = mark_subst_opts(cur_cmd->x.cmd_subst); cur_cmd->x.cmd_subst->regx = compile_regex(b, flags, cur_cmd->x.cmd_subst->max_id); free_buffer(b); } break; case 'y': { unsigned char *ustring; size_t len; int slash; #if defined HAVE_MBRTOWC && !defined (REG_PERL) if (MB_CUR_MAX == 1) #endif { ustring = OB_MALLOC(&obs, YMAP_LENGTH, unsigned char); for (len = 0; len < YMAP_LENGTH; len++) ustring[len] = len; cur_cmd->x.translate = ustring; } slash = inchar(); if ( !(b = match_slash(slash, FALSE, FALSE)) ) bad_prog(_(UNTERM_Y_CMD)); #if defined HAVE_MBRTOWC && !defined (REG_PERL) if (MB_CUR_MAX > 1) { int i, j, idx, src_char_num, len = size_buffer(b); size_t *src_lens = MALLOC(len, size_t); char *src_buf, *dest_buf, **trans_pairs; size_t mbclen; mbstate_t cur_stat; struct buffer *b2; /* Enumerate how many character the source buffer has. */ memset(&cur_stat, 0, sizeof(mbstate_t)); src_buf = get_buffer(b); for (i = 0, j = 0; i < len;) { mbclen = mbrlen(src_buf + i, len - i, &cur_stat); /* An invalid sequence, or a truncated multibyte character. We treat it as a singlebyte character. */ if (mbclen == (size_t) -1 || mbclen == (size_t) -2 || mbclen == 0) mbclen = 1; src_lens[j++] = mbclen; i += mbclen; } src_char_num = j; memset(&cur_stat, 0, sizeof(mbstate_t)); if ( !(b2 = match_slash(slash, FALSE, FALSE)) ) bad_prog(_(UNTERM_Y_CMD)); dest_buf = get_buffer(b2); idx = 0; len = size_buffer(b2); /* trans_pairs = {src(0), dest(0), src(1), dest(1), ..., NULL} src(i) : pointer to i-th source character. dest(i) : pointer to i-th destination character. NULL : terminator */ trans_pairs = MALLOC(2 * src_char_num + 1, char*); cur_cmd->x.translatemb = trans_pairs; for (i = 0; i < src_char_num; i++) { if (idx >= len) bad_prog(_(Y_CMD_LEN)); /* Set the i-th source character. */ trans_pairs[2 * i] = MALLOC(src_lens[i] + 1, char); strncpy(trans_pairs[2 * i], src_buf, src_lens[i]); trans_pairs[2 * i][src_lens[i]] = '\0'; src_buf += src_lens[i]; /* Forward to next character. */ /* Fetch the i-th destination character. */ mbclen = mbrlen(dest_buf + idx, len - idx, &cur_stat); /* An invalid sequence, or a truncated multibyte character. We treat it as a singlebyte character. */ if (mbclen == (size_t) -1 || mbclen == (size_t) -2 || mbclen == 0) mbclen = 1; /* Set the i-th destination character. */ trans_pairs[2 * i + 1] = MALLOC(mbclen + 1, char); strncpy(trans_pairs[2 * i + 1], dest_buf + idx, mbclen); trans_pairs[2 * i + 1][mbclen] = '\0'; idx += mbclen; /* Forward to next character. */ } trans_pairs[2 * i] = NULL; if (idx != len) bad_prog(_(Y_CMD_LEN)); free_buffer(b); free_buffer(b2); } else #endif { ustring = CAST(unsigned char *)get_buffer(b); for (len = size_buffer(b); len; --len) { ch = inchar(); if (ch == slash) bad_prog(_(Y_CMD_LEN)); if (ch == '\n') bad_prog(UNTERM_Y_CMD); if (ch == '\\') ch = inchar(); if (ch == EOF) bad_prog(UNTERM_Y_CMD); cur_cmd->x.translate[*ustring++] = ch; } free_buffer(b); if (inchar() != slash) bad_prog(_(Y_CMD_LEN)); else if ((ch = in_nonblank()) != EOF && ch != '\n' && ch != ';') bad_prog(_(EXCESS_JUNK)); } } break; case EOF: bad_prog(_(NO_COMMAND)); /*NOTREACHED*/ default: { const char *msg = _("Unknown command:"); char *unknown_cmd = xmalloc(strlen(msg) + 5); sprintf(unknown_cmd, "%s `%c'", msg, ch); bad_prog(unknown_cmd); /*NOTREACHED*/ } } /* this is buried down here so that "continue" statements will miss it */ ++vector->v_length; } return vector; } /* Complain about a programming error and exit. */ void bad_prog(why) const char *why; { if (cur_input.name) fprintf(stderr, _("%s: file %s line %lu: %s\n"), myname, cur_input.name, CAST(unsigned long)cur_input.line, why); else fprintf(stderr, _("%s: -e expression #%lu, char %lu: %s\n"), myname, CAST(unsigned long)cur_input.string_expr_count, CAST(unsigned long)(prog.cur-prog.base), why); exit(EXIT_FAILURE); } /* deal with \X escapes */ size_t normalize_text(buf, len) char *buf; size_t len; { const char *bufend = buf + len; char *p = buf; char *q = buf; /* I'm not certain whether POSIX.2 allows these escapes. Play it safe for now... */ if (POSIXLY_CORRECT && !(extended_regexp_flags)) return len; while (p < bufend) { int c; *q = *p++; if (*q == '\\' && p < bufend) switch ( (c = *p++) ) { #if defined __STDC__ && __STDC__-0 case 'a': *q = '\a'; break; #else /* Not STDC; we'll just assume ASCII */ case 'a': *q = '\007'; break; #endif /* case 'b': *q = '\b'; break; --- conflicts with \b RE */ case 'f': *q = '\f'; break; case '\n': /*fall through */ case 'n': *q = '\n'; break; case 'r': *q = '\r'; break; case 't': *q = '\t'; break; case 'v': *q = '\v'; break; case 'd': /* decimal byte */ p = convert_number(q, p, bufend, 10, 3, 'd'); break; case 'x': /* hexadecimal byte */ p = convert_number(q, p, bufend, 16, 2, 'x'); break; #ifdef REG_PERL case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': if ((extended_regexp_flags & REG_PERL) && p < bufend && isdigit(*p)) { p--; p = convert_number(q, p, bufend, 8, 3, *p); } else /* we just pass the \ up one level for interpretation */ *++q = p[-1]; break; case 'o': /* octal byte */ if (!(extended_regexp_flags & REG_PERL)) p = convert_number(q, p, bufend, 8, 3, 'o'); else /* we just pass the \ up one level for interpretation */ *++q = p[-1]; break; #else case 'o': /* octal byte */ p = convert_number(q, p, bufend, 8, 3, 'o'); break; #endif case 'c': if (p < bufend) { *q = toupper(*p) ^ 0x40; p++; break; } /* else FALLTHROUGH */ default: /* we just pass the \ up one level for interpretation */ *++q = p[-1]; break; } ++q; } return (size_t)(q - buf); } /* `str' is a string (from the command line) that contains a sed command. Compile the command, and add it to the end of `cur_program'. */ struct vector * compile_string(cur_program, str, len) struct vector *cur_program; char *str; size_t len; { static countT string_expr_count = 0; struct vector *ret; prog.file = NULL; prog.base = CAST(unsigned char *)str; prog.cur = prog.base; prog.end = prog.cur + len; cur_input.line = 0; cur_input.name = NULL; cur_input.string_expr_count = ++string_expr_count; ret = compile_program(cur_program); prog.base = NULL; prog.cur = NULL; prog.end = NULL; first_script = FALSE; return ret; } /* `cmdfile' is the name of a file containing sed commands. Read them in and add them to the end of `cur_program'. */ struct vector * compile_file(cur_program, cmdfile) struct vector *cur_program; const char *cmdfile; { size_t len; struct vector *ret; prog.file = stdin; if (cmdfile[0] != '-' || cmdfile[1] != '\0') prog.file = ck_fopen(cmdfile, "rt", TRUE); cur_input.line = 1; cur_input.name = cmdfile; cur_input.string_expr_count = 0; ret = compile_program(cur_program); if (prog.file != stdin) ck_fclose(prog.file); prog.file = NULL; first_script = FALSE; return ret; } /* Make any checks which require the whole program to have been read. In particular: this backpatches the jump targets. Any cleanup which can be done after these checks is done here also. */ void check_final_program(program) struct vector *program; { struct sed_label *go; struct sed_label *lbl; /* do all "{"s have a corresponding "}"? */ if (blocks) { /* update info for error reporting: */ MEMCPY(&cur_input, &blocks->err_info, sizeof cur_input); bad_prog(_(EXCESS_OPEN_BRACE)); } /* was the final command an unterminated a/c/i command? */ if (pending_text) { old_text_buf->text_length = size_buffer(pending_text); old_text_buf->text = MEMDUP(get_buffer(pending_text), old_text_buf->text_length, char); free_buffer(pending_text); pending_text = NULL; } for (go = jumps; go; go = release_label(go)) { for (lbl = labels; lbl; lbl = lbl->next) if (strcmp(lbl->name, go->name) == 0) break; if (lbl) { program->v[go->v_index].x.jump_index = lbl->v_index; } else { if (*go->name) panic(_("Can't find label for jump to `%s'"), go->name); program->v[go->v_index].x.jump_index = program->v_length; } } jumps = NULL; for (lbl = labels; lbl; lbl = release_label(lbl)) ; labels = NULL; /* There is no longer a need to track file names: */ { struct fp_list *p; for (p=file_read; p; p=p->link) if (p->name) { FREE(p->name); p->name = NULL; } for (p=file_write; p; p=p->link) if (p->name) { FREE(p->name); p->name = NULL; } } } /* Release all resources which were allocated in this module. */ void rewind_read_files() { struct fp_list *p, *q; for (p=file_read; p; p=p->link) if (p->fp && !p->special) rewind(p->fp); } /* Release all resources which were allocated in this module. */ void finish_program(program) struct vector *program; { /* close all files... */ { struct fp_list *p, *q; for (p=file_read; p; p=q) { if (p->fp && !p->special) ck_fclose(p->fp); q = p->link; #if 0 /* We use obstacks. */ FREE(p); #endif } for (p=file_write; p; p=q) { if (p->fp && !p->special) ck_fclose(p->fp); q = p->link; #if 0 /* We use obstacks. */ FREE(p); #endif } file_read = file_write = NULL; } #ifdef DEBUG_LEAKS obstack_free (&obs, NULL); #endif /*DEBUG_LEAKS*/ }
/* GNU SED, a batch stream editor. Copyright (C) 1989,90,91,92,93,94,95,98,99,2002,2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #if defined(_AIX) #pragma alloca #else # if !defined(alloca) /* predefined by HP cc +Olibcalls */ # ifdef __GNUC__ # define alloca(size) __builtin_alloca(size) # else # if HAVE_ALLOCA_H # include <alloca.h> # else # if defined(__hpux) void *alloca (); # else # if !defined(__OS2__) && !defined(WIN32) char *alloca (); # else # include <malloc.h> /* OS/2 defines alloca in here */ # endif # endif # endif # endif # endif #endif #ifndef BOOTSTRAP #include <stdio.h> #endif #include "basicdefs.h" #include "regex.h" #include "utils.h" #ifdef HAVE_WCHAR_H # include <wchar.h> #endif #ifdef HAVE_WCTYPE_H # include <wctype.h> #endif /* Struct vector is used to describe a compiled sed program. */ struct vector { struct sed_cmd *v; /* a dynamically allocated array */ size_t v_allocated; /* ... number slots allocated */ size_t v_length; /* ... number of slots in use */ }; struct text_buf { char *text; size_t text_length; }; enum replacement_types { repl_asis = 0, repl_uppercase = 1, repl_lowercase = 2, repl_uppercase_first = 4, repl_lowercase_first = 8, repl_modifiers = repl_uppercase_first | repl_lowercase_first, /* These are given to aid in debugging */ repl_uppercase_uppercase = repl_uppercase_first | repl_uppercase, repl_uppercase_lowercase = repl_uppercase_first | repl_lowercase, repl_lowercase_uppercase = repl_lowercase_first | repl_uppercase, repl_lowercase_lowercase = repl_lowercase_first | repl_lowercase }; enum addr_types { addr_is_null, /* null address */ addr_is_regex, /* a.addr_regex is valid */ addr_is_num, /* a.addr_number is valid */ addr_is_num_mod, /* a.addr_number is valid, addr_step is modulo */ addr_is_num2, /* a.addr_number is valid (only valid for addr2) */ addr_is_step, /* address is +N (only valid for addr2) */ addr_is_step_mod, /* address is ~N (only valid for addr2) */ addr_is_last /* address is $ */ }; struct addr { enum addr_types addr_type; countT addr_number; countT addr_step; regex_t *addr_regex; }; struct replacement { char *prefix; size_t prefix_length; int subst_id; enum replacement_types repl_type; struct replacement *next; }; struct subst { regex_t *regx; struct replacement *replacement; countT numb; /* if >0, only substitute for match number "numb" */ FILE *fp; /* 'w' option given */ unsigned global : 1; /* 'g' option given */ unsigned print : 2; /* 'p' option given (before/after eval) */ unsigned eval : 1; /* 'e' option given */ unsigned max_id : 4; /* maximum backreference on the RHS */ }; #ifdef REG_PERL /* This is the structure we store register match data in. See regex.texinfo for a full description of what registers match. */ struct re_registers { unsigned num_regs; regoff_t *start; regoff_t *end; }; #endif struct sed_cmd { struct addr *a1; /* save space: usually is NULL */ struct addr *a2; /* non-zero if a1 has been matched; apply command until a2 matches */ char a1_matched; /* non-zero: only apply command to non-matches */ char addr_bang; /* the actual command */ char cmd; /* auxiliary data for various commands */ union { /* This structure is used for a, i, and c commands */ struct text_buf cmd_txt; /* This is used for the l, q and Q commands */ int int_arg; /* This is used for {}, b, and t commands */ countT jump_index; /* This for r command */ char *fname; /* This for the hairy s command */ struct subst *cmd_subst; /* This for R and w command */ FILE *fp; /* This for the y command */ unsigned char *translate; char **translatemb; } x; }; void bad_prog P_((const char *why)); size_t normalize_text P_((char *, size_t)); struct vector *compile_string P_((struct vector *, char *str, size_t len)); struct vector *compile_file P_((struct vector *, const char *cmdfile)); void check_final_program P_((struct vector *)); void rewind_read_files P_((void)); void finish_program P_((struct vector *)); regex_t *compile_regex P_((struct buffer *b, int flags, int needed_sub)); int match_regex P_((regex_t *regex, char *buf, size_t buflen, size_t buf_start_offset, struct re_registers *regarray, int regsize)); #ifdef DEBUG_LEAKS void release_regex P_((regex_t *)); #endif int process_files P_((struct vector *, char **argv)); int main P_((int, char **)); extern void fmt P_ ((char *line, char *line_end, int max_length, FILE *output_file)); extern int extended_regexp_flags; /* If set, fflush(stdout) on every line output. */ extern flagT unbuffered_output; /* If set, don't write out the line unless explicitly told to. */ extern flagT no_default_output; /* If set, reset line counts on every new file. */ extern flagT separate_files; /* Do we need to be pedantically POSIX compliant? */ extern flagT POSIXLY_CORRECT; /* How long should the `l' command's output line be? */ extern countT lcmd_out_line_len; /* How do we edit files in-place? (we don't if NULL) */ extern char *in_place_extension;
/* strverscmp.h -- compare strings holding indices/version numbers */ #ifndef STRVERSCMP_H_ # define STRVERSCMP_H_ # if HAVE_CONFIG_H # include <config.h> # endif # ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif # endif int strverscmp PARAMS ((const char*, const char*)); #endif /* not STRVERSCMP_H_ */
/* GNU SED, a batch stream editor. Copyright (C) 1989,90,91,92,93,94,95,98,99,2002,2003,2004 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #undef EXPERIMENTAL_DASH_N_OPTIMIZATION /*don't use -- is very buggy*/ #define INITIAL_BUFFER_SIZE 50 #define FREAD_BUFFER_SIZE 8192 #include "sed.h" #include <stdio.h> #include <ctype.h> #include <errno.h> #ifndef errno extern int errno; #endif #ifdef HAVE_UNISTD_H # include <unistd.h> #endif #ifdef __GNUC__ # if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__-0 >= 7) /* silence warning about unused parameter even for "gcc -W -Wunused" */ # define UNUSED __attribute__((unused)) # endif #endif #ifndef UNUSED # define UNUSED #endif #ifdef HAVE_STRINGS_H # include <strings.h> #else # include <string.h> #endif /*HAVE_STRINGS_H*/ #ifdef HAVE_MEMORY_H # include <memory.h> #endif #ifndef HAVE_STRCHR # define strchr index # define strrchr rindex #endif #ifdef HAVE_STDLIB_H # include <stdlib.h> #endif #ifndef EXIT_SUCCESS # define EXIT_SUCCESS 0 #endif #ifdef HAVE_SYS_TYPES_H # include <sys/types.h> #endif #include <sys/stat.h> /* Sed operates a line at a time. */ struct line { char *text; /* Pointer to line allocated by malloc. */ char *active; /* Pointer to non-consumed part of text. */ size_t length; /* Length of text (or active, if used). */ size_t alloc; /* Allocated space for active. */ flagT chomped; /* Was a trailing newline dropped? */ }; /* A queue of text to write out at the end of a cycle (filled by the "a", "r" and "R" commands.) */ struct append_queue { const char *fname; char *text; size_t textlen; struct append_queue *next; flagT free; }; /* State information for the input stream. */ struct input { char **file_list; /* The list of yet-to-be-opened files. It is invalid for file_list to be NULL. When *file_list is NULL we are currently processing the last file. */ countT bad_count; /* count of files we failed to open */ countT line_number; /* current input line number (over all files) */ flagT (*read_fn) P_((struct input *)); /* read one line */ /* If fp is NULL, read_fn better not be one which uses fp; in particular, read_always_fail() is recommended. */ char *out_file_name; const char *in_file_name; FILE *fp; /* if NULL, none of the following are valid */ flagT no_buffering; }; /* Have we done any replacements lately? This is used by the `t' command. */ static flagT replaced = FALSE; /* The current output file (stdout if -i is not being used. */ static FILE *output_file; /* The `current' input line. */ static struct line line; /* An input line used to accumulate the result of the s and e commands. */ static struct line s_accum; /* An input line that's been stored by later use by the program */ static struct line hold; /* The buffered input look-ahead. The only field that should be used outside of read_mem_line() or line_init() is buffer.length. */ static struct line buffer; static struct append_queue *append_head = NULL; static struct append_queue *append_tail = NULL; #ifdef BOOTSTRAP /* We can't be sure that the system we're boostrapping on has memchr(), and ../lib/memchr.c requires configuration knowledge about how many bits are in a `long'. This implementation is far from ideal, but it should get us up-and-limping well enough to run the configure script, which is all that matters. */ # ifdef memchr # undef memchr # endif # define memchr bootstrap_memchr static VOID *bootstrap_memchr P_((const VOID *s, int c, size_t n)); static VOID * bootstrap_memchr(s, c, n) const VOID *s; int c; size_t n; { char *p; for (p=(char *)s; n-- > 0; ++p) if (*p == c) return p; return CAST(VOID *)0; } #endif /*BOOTSTRAP*/ /* increase a struct line's length, making some attempt at keeping realloc() calls under control by padding for future growth. */ static void resize_line P_((struct line *, size_t)); static void resize_line(lb, len) struct line *lb; size_t len; { int inactive; inactive = lb->active - lb->text; /* If the inactive part has got to more than two thirds of the buffer, * remove it. */ if (inactive > lb->alloc * 2) { MEMMOVE(lb->text, lb->active, lb->length); lb->alloc += lb->active - lb->text; lb->active = lb->text; inactive = 0; if (lb->alloc > len) return; } lb->alloc *= 2; if (lb->alloc < len) lb->alloc = len; if (lb->alloc < INITIAL_BUFFER_SIZE) lb->alloc = INITIAL_BUFFER_SIZE; lb->text = REALLOC(lb->text, inactive + lb->alloc, char); lb->active = lb->text + inactive; } /* Append `length' bytes from `string' to the line `to'. */ static void str_append P_((struct line *, const char *, size_t)); static void str_append(to, string, length) struct line *to; const char *string; size_t length; { size_t new_length = to->length + length; if (to->alloc < new_length) resize_line(to, new_length); MEMCPY(to->active + to->length, string, length); to->length = new_length; } static void str_append_modified P_((struct line *, const char *, size_t, enum replacement_types)); static void str_append_modified(to, string, length, type) struct line *to; const char *string; size_t length; enum replacement_types type; { size_t old_length = to->length; char *start, *end; if (length == 0) return; str_append(to, string, length); start = to->active + old_length; end = start + length; /* Now do the required modifications. First \[lu]... */ if (type & repl_uppercase_first) { *start = toupper(*start); start++; type &= ~repl_uppercase_first; } else if (type & repl_lowercase_first) { *start = tolower(*start); start++; type &= ~repl_lowercase_first; } if (type == repl_asis) return; /* ...and then \[LU] */ if (type == repl_uppercase) for (; start != end; start++) *start = toupper(*start); else for (; start != end; start++) *start = tolower(*start); } /* initialize a "struct line" buffer */ static void line_init P_((struct line *, size_t initial_size)); static void line_init(buf, initial_size) struct line *buf; size_t initial_size; { buf->text = MALLOC(initial_size, char); buf->active = buf->text; buf->alloc = initial_size; buf->length = 0; buf->chomped = TRUE; } /* Copy the contents of the line `from' into the line `to'. This destroys the old contents of `to'. */ static void line_copy P_((struct line *from, struct line *to)); static void line_copy(from, to) struct line *from; struct line *to; { /* Remove the inactive portion in the destination buffer. */ to->alloc += to->active - to->text; if (to->alloc < from->length) { to->alloc *= 2; if (to->alloc < from->length) to->alloc = from->length; if (to->alloc < INITIAL_BUFFER_SIZE) to->alloc = INITIAL_BUFFER_SIZE; /* Use FREE()+MALLOC() instead of REALLOC() to avoid unnecessary copying of old text. */ FREE(to->text); to->text = MALLOC(to->alloc, char); } to->active = to->text; to->length = from->length; to->chomped = from->chomped; MEMCPY(to->active, from->active, from->length); } /* Append the contents of the line `from' to the line `to'. */ static void line_append P_((struct line *from, struct line *to)); static void line_append(from, to) struct line *from; struct line *to; { str_append(to, "\n", 1); str_append(to, from->active, from->length); to->chomped = from->chomped; } /* Exchange the contents of two "struct line" buffers. */ static void line_exchange P_((struct line *, struct line *)); static void line_exchange(a, b) struct line *a; struct line *b; { struct line t; MEMCPY(&t, a, sizeof(struct line)); MEMCPY( a, b, sizeof(struct line)); MEMCPY( b, &t, sizeof(struct line)); } /* dummy function to simplify read_pattern_space() */ static flagT read_always_fail P_((struct input *)); static flagT read_always_fail(input) struct input *input UNUSED; { return FALSE; } static flagT read_file_line P_((struct input *)); static flagT read_file_line(input) struct input *input; { static char *b; static size_t blen; long result = getline (&b, &blen, input->fp); /* Remove the trailing new-line that is left by getline. */ if (result > 0 && b[result - 1] == '\n') --result; else { /* No trailing new line found. */ if (!*input->file_list && !POSIXLY_CORRECT) line.chomped = FALSE; if (result <= 0) return FALSE; } str_append(&line, b, result); return TRUE; } static void output_line P_((const char *, size_t, flagT, FILE *)); static void output_line(text, length, nl, fp) const char *text; size_t length; flagT nl; FILE *fp; { if (length) ck_fwrite(text, 1, length, fp); if (nl) ck_fwrite("\n", 1, 1, fp); if (fp != stdout || unbuffered_output) ck_fflush(fp); } static struct append_queue *next_append_slot P_((void)); static struct append_queue * next_append_slot() { struct append_queue *n = MALLOC(1, struct append_queue); n->fname = NULL; n->text = NULL; n->textlen = 0; n->next = NULL; n->free = FALSE; if (append_tail) append_tail->next = n; else append_head = n; return append_tail = n; } static void release_append_queue P_((void)); static void release_append_queue() { struct append_queue *p, *q; for (p=append_head; p; p=q) { if (p->free) FREE(p->text); q = p->next; FREE(p); } append_head = append_tail = NULL; } static void dump_append_queue P_((void)); static void dump_append_queue() { struct append_queue *p; for (p=append_head; p; p=p->next) { if (p->text) output_line(p->text, p->textlen, FALSE, output_file); if (p->fname) { char buf[FREAD_BUFFER_SIZE]; size_t cnt; FILE *fp; /* "If _fname_ does not exist or cannot be read, it shall be treated as if it were an empty file, causing no error condition." IEEE Std 1003.2-1992 So, don't fail. */ fp = ck_fopen(p->fname, "r", FALSE); if (fp) { while ((cnt = ck_fread(buf, 1, sizeof buf, fp)) > 0) ck_fwrite(buf, 1, cnt, output_file); ck_fclose(fp); } } } release_append_queue(); } /* Compute the name of the backup file for in-place editing */ static char *get_backup_file_name P_((const char *)); static char * get_backup_file_name(name) const char *name; { char *old_asterisk, *asterisk, *backup, *p; int name_length = strlen(name), backup_length = strlen(in_place_extension); /* Compute the length of the backup file */ for (asterisk = in_place_extension - 1, old_asterisk = asterisk + 1; asterisk = strchr(old_asterisk, '*'); old_asterisk = asterisk + 1) backup_length += name_length - 1; p = backup = xmalloc(backup_length + 1); /* Each iteration gobbles up to an asterisk */ for (asterisk = in_place_extension - 1, old_asterisk = asterisk + 1; asterisk = strchr(old_asterisk, '*'); old_asterisk = asterisk + 1) { memcpy (p, old_asterisk, asterisk - old_asterisk); p += asterisk - old_asterisk; strcpy (p, name); p += name_length; } /* Tack on what's after the last asterisk */ strcpy (p, old_asterisk); return backup; } /* Initialize a struct input for the named file. */ static void open_next_file P_((const char *name, struct input *)); static void open_next_file(name, input) const char *name; struct input *input; { buffer.length = 0; if (name[0] == '-' && name[1] == '\0') { clearerr(stdin); /* clear any stale EOF indication */ input->fp = stdin; } else if ( ! (input->fp = ck_fopen(name, "r", FALSE)) ) { const char *ptr = strerror(errno); fprintf(stderr, _("%s: can't read %s: %s\n"), myname, name, ptr); input->read_fn = read_always_fail; /* a redundancy */ ++input->bad_count; return; } input->read_fn = read_file_line; if (in_place_extension) { int output_fd; char *tmpdir = ck_strdup(name), *p; /* get the base name */ if (p = strrchr(tmpdir, '/')) *(p + 1) = 0; else strcpy(tmpdir, "."); input->in_file_name = name; input->out_file_name = temp_file_template (tmpdir, "sed"); output_fd = mkstemp (input->out_file_name); free (tmpdir); if (output_fd == -1) panic(_("Couldn't open temporary file %s: %s"), input->out_file_name, strerror(errno)); #ifdef HAVE_FCHMOD { struct stat st; fstat (fileno (input->fp), &st); fchmod (output_fd, st.st_mode); } #endif output_file = fdopen (output_fd, "w"); if (!output_file) panic(_("Couldn't open temporary file %s: %s"), input->out_file_name, strerror(errno)); } else output_file = stdout; } /* Clean up an input stream that we are done with. */ static void closedown P_((struct input *)); static void closedown(input) struct input *input; { input->read_fn = read_always_fail; if (!input->fp) return; if (input->fp != stdin) /* stdin can be reused on tty and tape devices */ ck_fclose(input->fp); if (in_place_extension && output_file != NULL) { int fd = fileno (output_file); if (strcmp(in_place_extension, "*") != 0) { char *backup_file_name = get_backup_file_name(input->in_file_name); rename (input->in_file_name, backup_file_name); free (backup_file_name); } ck_fclose (output_file); close (fd); rename (input->out_file_name, input->in_file_name); free (input->out_file_name); } input->fp = NULL; if (separate_files) rewind_read_files (); } /* Reset range commands so that they are marked as non-matching */ static void reset_addresses P_((struct vector *)); static void reset_addresses(vec) struct vector *vec; { struct sed_cmd *cur_cmd; int n; for (cur_cmd = vec->v, n = vec->v_length; n--; cur_cmd++) if (cur_cmd->a1) { enum addr_types type = cur_cmd->a1->addr_type; cur_cmd->a1_matched = (type == addr_is_num || type == addr_is_num_mod) && cur_cmd->a1->addr_number == 0; } else cur_cmd->a1_matched = FALSE; } /* Read in the next line of input, and store it in the pattern space. Return zero if there is nothing left to input. */ static flagT read_pattern_space P_((struct input *, struct vector *, flagT)); static flagT read_pattern_space(input, the_program, append) struct input *input; struct vector *the_program; flagT append; { if (append_head) /* redundant test to optimize for common case */ dump_append_queue(); replaced = FALSE; if (!append) line.length = 0; line.chomped = TRUE; /* default, until proved otherwise */ while ( ! (*input->read_fn)(input) ) { closedown(input); if (!*input->file_list) { line.chomped = FALSE; return FALSE; } if (separate_files) { input->line_number = 0; reset_addresses (the_program); } open_next_file (*input->file_list++, input); } ++input->line_number; return TRUE; } static flagT last_file_with_data_p P_((struct input *)); static flagT last_file_with_data_p(input) struct input *input; { for (;;) { int ch; closedown(input); if (!*input->file_list) return TRUE; open_next_file(*input->file_list++, input); if (input->fp) { if ((ch = getc(input->fp)) != EOF) { ungetc(ch, input->fp); return FALSE; } } } } /* Determine if we match the `$' address. */ static flagT test_eof P_((struct input *)); static flagT test_eof(input) struct input *input; { int ch; if (buffer.length) return FALSE; if (!input->fp) return separate_files || last_file_with_data_p(input); if (feof(input->fp)) return separate_files || last_file_with_data_p(input); if ((ch = getc(input->fp)) == EOF) return separate_files || last_file_with_data_p(input); ungetc(ch, input->fp); return FALSE; } /* Return non-zero if the current line matches the address pointed to by `addr'. */ static flagT match_an_address_p P_((struct addr *, struct input *)); static flagT match_an_address_p(addr, input) struct addr *addr; struct input *input; { switch (addr->addr_type) { case addr_is_null: return TRUE; case addr_is_regex: return match_regex(addr->addr_regex, line.active, line.length, 0, NULL, 0); case addr_is_num: return (addr->addr_number == input->line_number); case addr_is_num_mod: if (addr->addr_number < addr->addr_step) return (addr->addr_number == input->line_number%addr->addr_step); /* addr_number >= step implies we have an extra initial skip */ if (input->line_number < addr->addr_number) return FALSE; /* normalize */ addr->addr_number %= addr->addr_step; return (addr->addr_number == 0); case addr_is_num2: case addr_is_step: case addr_is_step_mod: /* reminder: these are only meaningful for a2 addresses */ /* a2->addr_number needs to be recomputed each time a1 address matches for the step and step_mod types */ return (addr->addr_number <= input->line_number); case addr_is_last: return test_eof(input); default: panic(_("INTERNAL ERROR: bad address type")); } /*NOTREACHED*/ return FALSE; } /* return non-zero if current address is valid for cmd */ static flagT match_address_p P_((struct sed_cmd *, struct input *)); static flagT match_address_p(cmd, input) struct sed_cmd *cmd; struct input *input; { flagT addr_matched = cmd->a1_matched; if (addr_matched) { if (match_an_address_p(cmd->a2, input)) cmd->a1_matched = FALSE; } else if (!cmd->a1 || match_an_address_p(cmd->a1, input)) { addr_matched = TRUE; if (cmd->a2) { cmd->a1_matched = TRUE; switch (cmd->a2->addr_type) { case addr_is_regex: break; case addr_is_step: cmd->a2->addr_number = input->line_number + cmd->a2->addr_step; break; case addr_is_step_mod: cmd->a2->addr_number = input->line_number + cmd->a2->addr_step - (input->line_number%cmd->a2->addr_step); break; default: if (match_an_address_p(cmd->a2, input)) cmd->a1_matched = FALSE; break; } } } if (cmd->addr_bang) return !addr_matched; return addr_matched; } static void do_list P_((int line_len)); static void do_list(line_len) int line_len; { unsigned char *p = CAST(unsigned char *)line.active; countT len = line.length; countT width = 0; char obuf[180]; /* just in case we encounter a 512-bit char (;-) */ char *o; size_t olen; for (; len--; ++p) { o = obuf; /* Some locales define 8-bit characters as printable. This makes the testsuite fail at 8to7.sed because the `l' command in fact will not convert the 8-bit characters. */ #if defined isascii || defined HAVE_ISASCII if (isascii(*p) && ISPRINT(*p)) { #else if (ISPRINT(*p)) { #endif *o++ = *p; if (*p == '\\') *o++ = '\\'; } else { *o++ = '\\'; switch (*p) { #if defined __STDC__ && __STDC__-0 case '\a': *o++ = 'a'; break; #else /* Not STDC; we'll just assume ASCII */ case 007: *o++ = 'a'; break; #endif case '\b': *o++ = 'b'; break; case '\f': *o++ = 'f'; break; case '\n': *o++ = 'n'; break; case '\r': *o++ = 'r'; break; case '\t': *o++ = 't'; break; case '\v': *o++ = 'v'; break; default: sprintf(o, "%03o", *p); o += strlen(o); break; } } olen = o - obuf; if (width+olen >= line_len && line_len > 0) { ck_fwrite("\\\n", 1, 2, output_file); width = 0; } ck_fwrite(obuf, 1, olen, output_file); width += olen; } ck_fwrite("$\n", 1, 2, output_file); } static enum replacement_types append_replacement P_((struct line *, struct replacement *, struct re_registers *, enum replacement_types)); static enum replacement_types append_replacement (buf, p, regs, repl_mod) struct line *buf; struct replacement *p; struct re_registers *regs; enum replacement_types repl_mod; { for (; p; p=p->next) { int i = p->subst_id; enum replacement_types curr_type; /* Apply a \[lu] modifier that was given earlier, but which we have not had yet the occasion to apply. But don't do it if this replacement has a modifier of its own. */ curr_type = (p->repl_type & repl_modifiers) ? p->repl_type : p->repl_type | repl_mod; repl_mod = 0; if (p->prefix_length) { str_append_modified(buf, p->prefix, p->prefix_length, curr_type); curr_type &= ~repl_modifiers; } if (0 <= i) if (regs->end[i] == regs->start[i] && p->repl_type & repl_modifiers) /* Save this modifier, we shall apply it later. e.g. in s/()([a-z])/\u\1\2/ the \u modifier is applied to \2, not \1 */ repl_mod = curr_type & repl_modifiers; else str_append_modified(buf, line.active + regs->start[i], CAST(size_t)(regs->end[i] - regs->start[i]), curr_type); } return repl_mod; } static void do_subst P_((struct subst *)); static void do_subst(sub) struct subst *sub; { size_t start = 0; /* where to start scan for (next) match in LINE */ size_t last_end = 0; /* where did the last successful match end in LINE */ countT count = 0; /* number of matches found */ flagT again = TRUE; #define MAX_BACKREFERENCES 10 static struct re_registers regs; if (s_accum.alloc == 0) line_init(&s_accum, INITIAL_BUFFER_SIZE); s_accum.length = 0; /* The first part of the loop optimizes s/xxx// when xxx is at the start, and s/xxx$// */ if (!match_regex(sub->regx, line.active, line.length, start, ®s, MAX_BACKREFERENCES)) return; if (!sub->replacement && sub->numb <= 1) if (regs.start[0] == 0 && !sub->global) { /* We found a match, set the `replaced' flag. */ replaced = TRUE; line.active += regs.end[0]; line.length -= regs.end[0]; line.alloc -= regs.end[0]; goto post_subst; } else if (regs.end[0] == line.length) { /* We found a match, set the `replaced' flag. */ replaced = TRUE; line.length = regs.start[0]; goto post_subst; } do { enum replacement_types repl_mod = 0; size_t offset = regs.start[0]; size_t matched = regs.end[0] - regs.start[0]; /* Copy stuff to the left of this match into the output string. */ if (start < offset) str_append(&s_accum, line.active + start, offset - start); /* If we're counting up to the Nth match, are we there yet? And even if we are there, there is another case we have to skip: are we matching an empty string immediately following another match? This latter case avoids that baaaac, when passed through s,a*,x,g, gives `xbxxcx' instead of xbxcx. This behavior is unacceptable because it is not consistently applied (for example, `baaaa' gives `xbx', not `xbxx'). */ if ((matched > 0 || count == 0 || offset > last_end) && (last_end = regs.end[0], ++count >= sub->numb)) { /* We found a match, set the `replaced' flag. */ replaced = TRUE; /* Now expand the replacement string into the output string. */ repl_mod = append_replacement (&s_accum, sub->replacement, ®s, repl_mod); again = sub->global; } else { /* The match was not replaced. Copy the text until its end; if it was vacuous, skip over one character and add that character to the output. */ if (matched == 0) { if (start < line.length) matched = 1; else break; } str_append(&s_accum, line.active + offset, matched); } /* Start after the match. */ start = offset + matched; } while (again && start <= line.length && match_regex(sub->regx, line.active, line.length, start, ®s, MAX_BACKREFERENCES)); /* Copy stuff to the right of the last match into the output string. */ if (start < line.length) str_append(&s_accum, line.active + start, line.length-start); s_accum.chomped = line.chomped; /* Exchange line and s_accum. This can be much cheaper than copying s_accum.active into line.text (for huge lines). */ line_exchange(&line, &s_accum); /* Finish up. */ if (count < sub->numb) return; post_subst: if (sub->print & 1) output_line(line.active, line.length, line.chomped, output_file); if (sub->eval) { #ifdef HAVE_POPEN FILE *pipe; s_accum.length = 0; str_append (&line, "", 1); pipe = popen(line.active, "r"); if (pipe != NULL) { while (!feof (pipe)) { char buf[4096]; int n = fread (buf, sizeof(char), 4096, pipe); if (n > 0) str_append(&s_accum, buf, n); } pclose (pipe); line_exchange(&line, &s_accum); if (line.length && line.active[line.length - 1] == '\n') line.length--; } else panic(_("error in subprocess")); #else panic(_("option `e' not supported")); #endif } if (sub->print & 2) output_line(line.active, line.length, line.chomped, output_file); if (sub->fp) output_line(line.active, line.length, line.chomped, sub->fp); } #ifdef EXPERIMENTAL_DASH_N_OPTIMIZATION /* Used to attempt a simple-minded optimization. */ static countT branches; static countT count_branches P_((struct vector *)); static countT count_branches(program) struct vector *program; { struct sed_cmd *cur_cmd = program->v; countT isn_cnt = program->v_length; countT cnt = 0; while (isn_cnt-- > 0) { switch (cur_cmd->cmd) { case 'b': case 't': case 'T': case '{': ++cnt; } } return cnt; } static struct sed_cmd *shrink_program P_((struct vector *, struct sed_cmd *)); static struct sed_cmd * shrink_program(vec, cur_cmd) struct vector *vec; struct sed_cmd *cur_cmd; { struct sed_cmd *v = vec->v; struct sed_cmd *last_cmd = v + vec->v_length; struct sed_cmd *p; countT cmd_cnt; for (p=v; p < cur_cmd; ++p) if (p->cmd != ':') MEMCPY(v++, p, sizeof *v); cmd_cnt = v - vec->v; for (; p < last_cmd; ++p) if (p->cmd != ':') MEMCPY(v++, p, sizeof *v); vec->v_length = v - vec->v; return (0 < vec->v_length) ? (vec->v + cmd_cnt) : CAST(struct sed_cmd *)0; } #endif /*EXPERIMENTAL_DASH_N_OPTIMIZATION*/ /* Execute the program `vec' on the current input line. Return exit status if caller should quit, -1 otherwise. */ static int execute_program P_((struct vector *, struct input *)); static int execute_program(vec, input) struct vector *vec; struct input *input; { struct sed_cmd *cur_cmd; struct sed_cmd *end_cmd; cur_cmd = vec->v; end_cmd = vec->v + vec->v_length; while (cur_cmd < end_cmd) { if (match_address_p(cur_cmd, input)) { switch (cur_cmd->cmd) { case 'a': { struct append_queue *aq = next_append_slot(); aq->text = cur_cmd->x.cmd_txt.text; aq->textlen = cur_cmd->x.cmd_txt.text_length; } break; case '{': case 'b': cur_cmd = vec->v + cur_cmd->x.jump_index; continue; case '}': case ':': /* Executing labels and block-ends are easy. */ break; case 'c': if (!cur_cmd->a1_matched) output_line(cur_cmd->x.cmd_txt.text, cur_cmd->x.cmd_txt.text_length, FALSE, output_file); /* POSIX.2 is silent about c starting a new cycle, but it seems to be expected (and make sense). */ /* Fall Through */ case 'd': return -1; case 'D': { char *p = memchr(line.active, '\n', line.length); if (!p) { line.length = 0; line.chomped = FALSE; return -1; } ++p; line.alloc -= p - line.active; line.length -= p - line.active; line.active += p - line.active; /* reset to start next cycle without reading a new line: */ cur_cmd = vec->v; continue; } case 'e': { #ifdef HAVE_POPEN FILE *pipe; int cmd_length = cur_cmd->x.cmd_txt.text_length; if (s_accum.alloc == 0) line_init(&s_accum, INITIAL_BUFFER_SIZE); s_accum.length = 0; if (!cmd_length) { str_append (&line, "", 1); pipe = popen(line.active, "r"); } else { cur_cmd->x.cmd_txt.text[cmd_length - 1] = 0; pipe = popen(cur_cmd->x.cmd_txt.text, "r"); } if (pipe != NULL) { while (!feof (pipe)) { char buf[4096]; int n = fread (buf, sizeof(char), 4096, pipe); if (n > 0) if (!cmd_length) str_append(&s_accum, buf, n); else output_line(buf, n, FALSE, output_file); } pclose (pipe); if (!cmd_length) { /* Store into pattern space for plain `e' commands */ if (s_accum.length && s_accum.active[s_accum.length - 1] == '\n') s_accum.length--; /* Exchange line and s_accum. This can be much cheaper than copying s_accum.active into line.text (for huge lines). */ line_exchange(&line, &s_accum); } } else panic(_("error in subprocess")); #else panic(_("`e' command not supported")); #endif break; } case 'g': line_copy(&hold, &line); break; case 'G': line_append(&hold, &line); break; case 'h': line_copy(&line, &hold); break; case 'H': line_append(&line, &hold); break; case 'i': output_line(cur_cmd->x.cmd_txt.text, cur_cmd->x.cmd_txt.text_length, FALSE, output_file); break; case 'l': do_list(cur_cmd->x.int_arg == -1 ? lcmd_out_line_len : cur_cmd->x.int_arg); break; case 'L': fmt(line.active, line.active + line.length, cur_cmd->x.int_arg == -1 ? lcmd_out_line_len : cur_cmd->x.int_arg, output_file); break; case 'n': if (!no_default_output) output_line(line.active, line.length, line.chomped, output_file); if (test_eof(input) || !read_pattern_space(input, vec, FALSE)) return -1; break; case 'N': str_append(&line, "\n", 1); if (test_eof(input) || !read_pattern_space(input, vec, TRUE)) { line.length--; if (!POSIXLY_CORRECT && !no_default_output) output_line(line.active, line.length, line.chomped, output_file); return -1; } break; case 'p': output_line(line.active, line.length, line.chomped, output_file); break; case 'P': { char *p = memchr(line.active, '\n', line.length); output_line(line.active, p ? p - line.active : line.length, p ? 1 : line.chomped, output_file); } break; case 'q': if (!no_default_output) output_line(line.active, line.length, line.chomped, output_file); case 'Q': return cur_cmd->x.int_arg == -1 ? 0 : cur_cmd->x.int_arg; case 'r': if (cur_cmd->x.fname) { struct append_queue *aq = next_append_slot(); aq->fname = cur_cmd->x.fname; } break; case 'R': if (cur_cmd->x.fp && !feof (cur_cmd->x.fp)) { struct append_queue *aq; size_t buflen; char *text = NULL; int result; result = getline (&text, &buflen, cur_cmd->x.fp); if (result != EOF) { aq = next_append_slot(); aq->free = TRUE; aq->text = text; aq->textlen = result; } } break; case 's': do_subst(cur_cmd->x.cmd_subst); break; case 't': if (replaced) { replaced = FALSE; cur_cmd = vec->v + cur_cmd->x.jump_index; continue; } break; case 'T': if (!replaced) { cur_cmd = vec->v + cur_cmd->x.jump_index; continue; } else replaced = FALSE; break; case 'w': if (cur_cmd->x.fp) output_line(line.active, line.length, line.chomped, cur_cmd->x.fp); break; case 'W': if (cur_cmd->x.fp) { char *p = memchr(line.active, '\n', line.length); output_line(line.active, p ? p - line.active : line.length, p ? 1 : line.chomped, cur_cmd->x.fp); } break; case 'x': line_exchange(&line, &hold); break; case 'y': { #if defined HAVE_MBRTOWC && !defined REG_PERL if (MB_CUR_MAX > 1) { int idx, prev_idx; /* index in the input line. */ char **trans; mbstate_t cur_stat; memset(&cur_stat, 0, sizeof(mbstate_t)); for (idx = 0; idx < line.length;) { int mbclen, i; mbclen = mbrlen(line.active + idx, line.length - idx, &cur_stat); /* An invalid sequence, or a truncated multibyte character. We treat it as a singlebyte character. */ if (mbclen == (size_t) -1 || mbclen == (size_t) -2 || mbclen == 0) mbclen = 1; trans = cur_cmd->x.translatemb; /* `i' indicate i-th translate pair. */ for (i = 0; trans[2*i] != NULL; i++) { if (strncmp(line.active + idx, trans[2*i], mbclen) == 0) { flagT move_remain_buffer = FALSE; int trans_len = strlen(trans[2*i+1]); if (mbclen < trans_len) { int new_len; new_len = line.length + 1 + trans_len - mbclen; /* We must extend the line buffer. */ if (line.alloc < new_len) { /* And we must resize the buffer. */ resize_line(&line, new_len); } move_remain_buffer = TRUE; } else if (mbclen > trans_len) { /* We must truncate the line buffer. */ move_remain_buffer = TRUE; } prev_idx = idx; if (move_remain_buffer) { int move_len, move_offset; char *move_from, *move_to; /* Move the remaining with \0. */ move_from = line.active + idx + mbclen; move_to = line.active + idx + trans_len; move_len = line.length + 1 - idx - mbclen; move_offset = trans_len - mbclen; memmove(move_to, move_from, move_len); line.length += move_offset; idx += move_offset; } strncpy(line.active + prev_idx, trans[2*i+1], trans_len); break; } } idx += mbclen; } } else #endif /* HAVE_MBRTOWC */ { unsigned char *p, *e; p = CAST(unsigned char *)line.active; for (e=p+line.length; p<e; ++p) *p = cur_cmd->x.translate[*p]; } } break; case '=': fprintf(output_file, "%lu\n", CAST(unsigned long)input->line_number); break; default: panic(_("INTERNAL ERROR: Bad cmd %c"), cur_cmd->cmd); } } #ifdef EXPERIMENTAL_DASH_N_OPTIMIZATION /* If our top-level program consists solely of commands with * addr_is_num addresses then once we past the last mentioned * line we should be able to quit if no_default_output is true, * or otherwise quickly copy input to output. Now whether this * optimization is a win or not depends on how cheaply we can * implement this for the cases where it doesn't help, as * compared against how much time is saved. One semantic * difference (which I think is an improvement) is that *this* * version will terminate after printing line two in the script * "yes | sed -n 2p". * * Don't use this when in-place editing is active, because line * numbers restart each time then. */ else if (output_file == stdout) { /* can we ever match again? */ if (cur_cmd->a1->addr_type == addr_is_num && ((input->line_number < cur_cmd->a1->addr_number) != !cur_cmd->addr_bang)) { /* skip all this next time */ cur_cmd->a1->addr_type = addr_is_null; cur_cmd->addr_bang = TRUE; /* can we make an optimization? */ if (cur_cmd->cmd == 'b' || cur_cmd->cmd == 't' || cur_cmd->cmd == '{') --branches; cur_cmd->cmd = ':'; /* replace with no-op */ if (branches == 0) { /* whew! all that just so that we can get to here! */ cur_cmd = shrink_program(vec, cur_cmd); if (!cur_cmd && no_default_output) return 0; end_cmd = vec->v + vec->v_length; if (!cur_cmd) cur_cmd = end_cmd; continue; } } } #endif /*EXPERIMENTAL_DASH_N_OPTIMIZATION*/ /* this is buried down here so that a "continue" statement can skip it */ ++cur_cmd; } if (!no_default_output) output_line(line.active, line.length, line.chomped, output_file); return -1; } /* Apply the compiled script to all the named files. */ int process_files(the_program, argv) struct vector *the_program; char **argv; { static char dash[] = "-"; static char *stdin_argv[2] = { dash, NULL }; struct input input; int status; line_init(&line, INITIAL_BUFFER_SIZE); line_init(&hold, 0); line_init(&buffer, 0); #ifdef EXPERIMENTAL_DASH_N_OPTIMIZATION branches = count_branches(the_program); #endif /*EXPERIMENTAL_DASH_N_OPTIMIZATION*/ input.file_list = stdin_argv; if (argv && *argv) input.file_list = argv; input.bad_count = 0; input.line_number = 0; input.read_fn = read_always_fail; input.fp = NULL; status = EXIT_SUCCESS; while (read_pattern_space(&input, the_program, FALSE)) { status = execute_program(the_program, &input); if (status == -1) status = EXIT_SUCCESS; else break; } closedown(&input); #ifdef DEBUG_LEAKS /* We're about to exit, so these free()s are redundant. But if we're running under a memory-leak detecting implementation of malloc(), we want to explicitly deallocate in order to avoid extraneous noise from the allocator. */ release_append_queue(); FREE(buffer.text); FREE(hold.text); FREE(line.text); FREE(s_accum.text); #endif /*DEBUG_LEAKS*/ if (input.bad_count) status = 2; return status; }
/* GNU SED, a batch stream editor. Copyright (C) 1999, 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "sed.h" #include <ctype.h> #include <stdio.h> #ifdef HAVE_STDLIB_H # include <stdlib.h> #endif #ifdef gettext_noop # define N_(String) gettext_noop(String) #else # define N_(String) (String) #endif extern flagT use_extended_syntax_p; static const char errors[] = "No previous regular expression\0" "Cannot specify modifiers on empty regexp"; #define NO_REGEX (errors) #define BAD_MODIF (NO_REGEX + sizeof(N_("No previous regular expression"))) #define END_ERRORS (BAD_MODIF + sizeof(N_("Cannot specify modifiers on empty regexp"))) regex_t * compile_regex(b, flags, needed_sub) struct buffer *b; int flags; int needed_sub; { regex_t *new_regex; char *last_re = NULL; size_t last_re_len; /* // matches the last RE */ if (size_buffer(b) == 0) { if (flags > 0) bad_prog(_(BAD_MODIF)); return NULL; } last_re_len = size_buffer(b); last_re = ck_memdup(get_buffer(b), last_re_len); new_regex = MALLOC(1, regex_t); #ifdef REG_PERL { int errcode; errcode = regncomp(new_regex, last_re, last_re_len, (needed_sub ? 0 : REG_NOSUB) | flags | extended_regexp_flags); if (errcode) { char errorbuf[200]; regerror(errcode, NULL, errorbuf, 200); bad_prog(gettext(errorbuf)); } } #else new_regex->fastmap = malloc (1 << (sizeof (char) * 8)); { const char *error; int syntax = ((extended_regexp_flags & REG_EXTENDED) ? RE_SYNTAX_POSIX_EXTENDED : RE_SYNTAX_POSIX_BASIC) & ~RE_UNMATCHED_RIGHT_PAREN_ORD; syntax |= RE_NO_POSIX_BACKTRACKING; #ifdef RE_ICASE syntax |= (flags & REG_ICASE) ? RE_ICASE : 0; #endif /* If REG_NEWLINE is set, newlines are treated differently. */ if (flags & REG_NEWLINE) { /* REG_NEWLINE implies neither . nor [^...] match newline. */ syntax &= ~RE_DOT_NEWLINE; syntax |= RE_HAT_LISTS_NOT_NEWLINE; } /* GNU regex does not process \t & co. */ last_re_len = normalize_text(last_re, last_re_len); re_set_syntax (syntax); error = re_compile_pattern (last_re, last_re_len, new_regex); new_regex->newline_anchor = (flags & REG_NEWLINE) != 0; new_regex->translate = NULL; #ifndef RE_ICASE if (flags & REG_ICASE) { static char translate[1 << (sizeof(char) * 8)]; int i; for (i = 0; i < sizeof(translate) / sizeof(char); i++) translate[i] = tolower (i); new_regex->translate = translate; } #endif if (error) bad_prog(error); } #endif FREE(last_re); /* Just to be sure, I mark this as not POSIXLY_CORRECT behavior */ if (new_regex->re_nsub < needed_sub && !POSIXLY_CORRECT) { char buf[200]; sprintf(buf, _("Invalid reference \\%d on `s' command's RHS"), needed_sub); bad_prog(buf); } return new_regex; } #ifdef REG_PERL static void copy_regs (regs, pmatch, nregs) struct re_registers *regs; regmatch_t *pmatch; int nregs; { int i; int need_regs = nregs + 1; /* We need one extra element beyond `num_regs' for the `-1' marker GNU code uses. */ /* Have the register data arrays been allocated? */ if (!regs->start) { /* No. So allocate them with malloc. */ regs->start = MALLOC (need_regs, regoff_t); regs->end = MALLOC (need_regs, regoff_t); regs->num_regs = need_regs; } else if (need_regs > regs->num_regs) { /* Yes. We also need more elements than were already allocated, so reallocate them. */ regs->start = REALLOC (regs->start, need_regs, regoff_t); regs->end = REALLOC (regs->end, need_regs, regoff_t); regs->num_regs = need_regs; } /* Copy the regs. */ for (i = 0; i < nregs; ++i) { regs->start[i] = pmatch[i].rm_so; regs->end[i] = pmatch[i].rm_eo; } for ( ; i < regs->num_regs; ++i) regs->start[i] = regs->end[i] = -1; } #endif int match_regex(regex, buf, buflen, buf_start_offset, regarray, regsize) regex_t *regex; char *buf; size_t buflen; size_t buf_start_offset; struct re_registers *regarray; int regsize; { int ret; static regex_t *regex_last; #ifdef REG_PERL regmatch_t *regmatch; if (regsize) regmatch = (regmatch_t *) alloca (sizeof (regmatch_t) * regsize); else regmatch = NULL; #endif /* printf ("Matching from %d/%d\n", buf_start_offset, buflen); */ /* Keep track of the last regexp matched. */ if (!regex) { regex = regex_last; if (!regex_last) bad_prog(_(NO_REGEX)); } else regex_last = regex; #ifdef REG_PERL ret = regexec2(regex, buf, CAST(int)buflen, CAST(int)buf_start_offset, regsize, regmatch, 0); if (regsize) copy_regs (regarray, regmatch, regsize); return (ret == 0); #else regex->regs_allocated = REGS_REALLOCATE; ret = re_search(regex, buf, buflen, buf_start_offset, buflen - buf_start_offset, regsize ? regarray : NULL); return (ret > -1); #endif } #ifdef DEBUG_LEAKS void release_regex(regex) regex_t *regex; { regfree(regex); FREE(regex); } #endif /*DEBUG_LEAKS*/
/* `L' command implementation for GNU sed, based on GNU fmt 1.22. Copyright (C) 1994, 1995, 1996, 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* GNU fmt was written by Ross Paterson <rap@doc.ic.ac.uk>. */ #include "sed.h" #include <stdio.h> #include <ctype.h> #include <sys/types.h> #if HAVE_LIMITS_H # include <limits.h> #endif #ifndef UINT_MAX # define UINT_MAX ((unsigned int) ~(unsigned int) 0) #endif #ifndef INT_MAX # define INT_MAX ((int) (UINT_MAX >> 1)) #endif /* The following parameters represent the program's idea of what is "best". Adjust to taste, subject to the caveats given. */ /* Prefer lines to be LEEWAY % shorter than the maximum width, giving room for optimization. */ #define LEEWAY 7 /* Costs and bonuses are expressed as the equivalent departure from the optimal line length, multiplied by 10. e.g. assigning something a cost of 50 means that it is as bad as a line 5 characters too short or too long. The definition of SHORT_COST(n) should not be changed. However, EQUIV(n) may need tuning. */ typedef long COST; #define MAXCOST (~(((unsigned long) 1) << (8 * sizeof (COST) -1))) #define SQR(n) ((n) * (n)) #define EQUIV(n) SQR ((COST) (n)) /* Cost of a filled line n chars longer or shorter than best_width. */ #define SHORT_COST(n) EQUIV ((n) * 10) /* Cost of the difference between adjacent filled lines. */ #define RAGGED_COST(n) (SHORT_COST (n) / 2) /* Basic cost per line. */ #define LINE_COST EQUIV (70) /* Cost of breaking a line after the first word of a sentence, where the length of the word is N. */ #define WIDOW_COST(n) (EQUIV (200) / ((n) + 2)) /* Cost of breaking a line before the last word of a sentence, where the length of the word is N. */ #define ORPHAN_COST(n) (EQUIV (150) / ((n) + 2)) /* Bonus for breaking a line at the end of a sentence. */ #define SENTENCE_BONUS EQUIV (50) /* Cost of breaking a line after a period not marking end of a sentence. With the definition of sentence we are using (borrowed from emacs, see get_line()) such a break would then look like a sentence break. Hence we assign a very high cost -- it should be avoided unless things are really bad. */ #define NOBREAK_COST EQUIV (600) /* Bonus for breaking a line before open parenthesis. */ #define PAREN_BONUS EQUIV (40) /* Bonus for breaking a line after other punctuation. */ #define PUNCT_BONUS EQUIV(40) /* Credit for breaking a long paragraph one line later. */ #define LINE_CREDIT EQUIV(3) /* Size of paragraph buffer in words. Longer paragraphs are handled neatly (cf. flush_paragraph()), so there's little to gain by making these larger. */ #define MAXWORDS 1000 #define GETC() (parabuf == end_of_parabuf ? EOF : *parabuf++) /* Extra ctype(3)-style macros. */ #define isopen(c) (strchr ("([`'\"", (c)) != NULL) #define isclose(c) (strchr (")]'\"", (c)) != NULL) #define isperiod(c) (strchr (".?!", (c)) != NULL) /* Size of a tab stop, for expansion on input and re-introduction on output. */ #define TABWIDTH 8 /* Word descriptor structure. */ typedef struct Word WORD; struct Word { /* Static attributes determined during input. */ const char *text; /* the text of the word */ short length; /* length of this word */ short space; /* the size of the following space */ unsigned paren:1; /* starts with open paren */ unsigned period:1; /* ends in [.?!])* */ unsigned punct:1; /* ends in punctuation */ unsigned final:1; /* end of sentence */ /* The remaining fields are computed during the optimization. */ short line_length; /* length of the best line starting here */ COST best_cost; /* cost of best paragraph starting here */ WORD *next_break; /* break which achieves best_cost */ }; /* Forward declarations. */ extern void fmt P_ ((char *line, char *line_end, int max_length, FILE *output_file)); static flagT get_paragraph P_ ((void)); static int get_line P_ ((int c)); static int get_space P_ ((int c)); static int copy_rest P_ ((int c)); static flagT same_para P_ ((int c)); static void flush_paragraph P_ ((void)); static void fmt_paragraph P_ ((void)); static void check_punctuation P_ ((WORD *w)); static COST base_cost P_ ((WORD *this)); static COST line_cost P_ ((WORD *next, int len)); static void put_paragraph P_ ((WORD *finish)); static void put_line P_ ((WORD *w, int indent)); static void put_word P_ ((WORD *w)); static void put_space P_ ((int space)); /* Option values. */ /* User-supplied maximum line width (default WIDTH). The only output lines longer than this will each comprise a single word. */ static int max_width; /* Space for the paragraph text. */ static char *parabuf; /* End of space for the paragraph text. */ static char *end_of_parabuf; /* The file on which we output */ static FILE *outfile; /* Values derived from the option values. */ /* The preferred width of text lines, set to LEEWAY % less than max_width. */ static int best_width; /* Dynamic variables. */ /* Start column of the character most recently read from the input file. */ static int in_column; /* Start column of the next character to be written to stdout. */ static int out_column; /* The words of a paragraph -- longer paragraphs are handled neatly (cf. flush_paragraph()). */ static WORD words[MAXWORDS]; /* A pointer into the above word array, indicating the first position after the last complete word. Sometimes it will point at an incomplete word. */ static WORD *word_limit; /* Indentation of the first line of the current paragraph. */ static int first_indent; /* Indentation of other lines of the current paragraph */ static int other_indent; /* The last character read from the input file. */ static int next_char; /* If nonzero, the length of the last line output in the current paragraph, used to charge for raggedness at the split point for long paragraphs chosen by fmt_paragraph(). */ static int last_line_length; /* read file F and send formatted output to stdout. */ void fmt (char *line, char *line_end, int max_length, FILE *output_file) { parabuf = line; end_of_parabuf = line_end; outfile = output_file; max_width = max_length; best_width = max_width * (201 - 2 * LEEWAY) / 200; in_column = 0; other_indent = 0; next_char = GETC(); while (get_paragraph ()) { fmt_paragraph (); put_paragraph (word_limit); } } /* Read a paragraph from input file F. A paragraph consists of a maximal number of non-blank (excluding any prefix) lines with the same indent. Return FALSE if end-of-file was encountered before the start of a paragraph, else TRUE. */ static flagT get_paragraph () { register int c; last_line_length = 0; c = next_char; /* Scan (and copy) blank lines, and lines not introduced by the prefix. */ while (c == '\n' || c == EOF) { c = copy_rest (c); if (c == EOF) { next_char = EOF; return FALSE; } putc ('\n', outfile); c = GETC(); } /* Got a suitable first line for a paragraph. */ first_indent = in_column; word_limit = words; c = get_line (c); /* Read rest of paragraph. */ other_indent = in_column; while (same_para (c) && in_column == other_indent) c = get_line (c); (word_limit - 1)->period = (word_limit - 1)->final = TRUE; next_char = c; return TRUE; } /* Copy to the output a blank line. In the latter, C is \n or EOF. Return the character (\n or EOF) ending the line. */ static int copy_rest (register int c) { out_column = 0; while (c != '\n' && c != EOF) { putc (c, outfile); c = GETC(); } return c; } /* Return TRUE if a line whose first non-blank character after the prefix (if any) is C could belong to the current paragraph, otherwise FALSE. */ static flagT same_para (register int c) { return (c != '\n' && c != EOF); } /* Read a line from the input data given first non-blank character C after the prefix, and the following indent, and break it into words. A word is a maximal non-empty string of non-white characters. A word ending in [.?!]["')\]]* and followed by end-of-line or at least two spaces ends a sentence, as in emacs. Return the first non-blank character of the next line. */ static int get_line (register int c) { int start; register WORD *end_of_word; end_of_word = &words[MAXWORDS - 2]; do { /* for each word in a line */ /* Scan word. */ word_limit->text = parabuf - 1; do c = GETC(); while (c != EOF && !ISSPACE (c)); word_limit->length = parabuf - word_limit->text - (c != EOF); in_column += word_limit->length; check_punctuation (word_limit); /* Scan inter-word space. */ start = in_column; c = get_space (c); word_limit->space = in_column - start; word_limit->final = (c == EOF || (word_limit->period && (c == '\n' || word_limit->space > 1))); if (c == '\n' || c == EOF) word_limit->space = word_limit->final ? 2 : 1; if (word_limit == end_of_word) flush_paragraph (); word_limit++; if (c == EOF) { in_column = first_indent; return EOF; } } while (c != '\n'); in_column = 0; c = GETC(); return get_space (c); } /* Read blank characters from the input data, starting with C, and keeping in_column up-to-date. Return first non-blank character. */ static int get_space (register int c) { for (;;) { if (c == ' ') in_column++; else if (c == '\t') in_column = (in_column / TABWIDTH + 1) * TABWIDTH; else return c; c = GETC(); } } /* Set extra fields in word W describing any attached punctuation. */ static void check_punctuation (register WORD *w) { register const char *start, *finish; start = w->text; finish = start + (w->length - 1); w->paren = isopen (*start); w->punct = ISPUNCT (*finish); while (isclose (*finish) && finish > start) finish--; w->period = isperiod (*finish); } /* Flush part of the paragraph to make room. This function is called on hitting the limit on the number of words or characters. */ static void flush_paragraph (void) { WORD *split_point; register WORD *w; COST best_break; /* - format what you have so far as a paragraph, - find a low-cost line break near the end, - output to there, - make that the start of the paragraph. */ fmt_paragraph (); /* Choose a good split point. */ split_point = word_limit; best_break = MAXCOST; for (w = words->next_break; w != word_limit; w = w->next_break) { if (w->best_cost - w->next_break->best_cost < best_break) { split_point = w; best_break = w->best_cost - w->next_break->best_cost; } if (best_break <= MAXCOST - LINE_CREDIT) best_break += LINE_CREDIT; } put_paragraph (split_point); /* Copy words from split_point down to word -- we use memmove because the source and target may overlap. */ memmove ((char *) words, (char *) split_point, (word_limit - split_point + 1) * sizeof (WORD)); word_limit -= split_point - words; } /* Compute the optimal formatting for the whole paragraph by computing and remembering the optimal formatting for each suffix from the empty one to the whole paragraph. */ static void fmt_paragraph (void) { register WORD *start, *w; register int len; register COST wcost, best; int saved_length; word_limit->best_cost = 0; saved_length = word_limit->length; word_limit->length = max_width; /* sentinel */ for (start = word_limit - 1; start >= words; start--) { best = MAXCOST; len = start == words ? first_indent : other_indent; /* At least one word, however long, in the line. */ w = start; len += w->length; do { w++; /* Consider breaking before w. */ wcost = line_cost (w, len) + w->best_cost; if (start == words && last_line_length > 0) wcost += RAGGED_COST (len - last_line_length); if (wcost < best) { best = wcost; start->next_break = w; start->line_length = len; } len += (w - 1)->space + w->length; /* w > start >= words */ } while (len < max_width); start->best_cost = best + base_cost (start); } word_limit->length = saved_length; } /* Return the constant component of the cost of breaking before the word THIS. */ static COST base_cost (register WORD *this) { register COST cost; cost = LINE_COST; if (this > words) { if ((this - 1)->period) { if ((this - 1)->final) cost -= SENTENCE_BONUS; else cost += NOBREAK_COST; } else if ((this - 1)->punct) cost -= PUNCT_BONUS; else if (this > words + 1 && (this - 2)->final) cost += WIDOW_COST ((this - 1)->length); } if (this->paren) cost -= PAREN_BONUS; else if (this->final) cost += ORPHAN_COST (this->length); return cost; } /* Return the component of the cost of breaking before word NEXT that depends on LEN, the length of the line beginning there. */ static COST line_cost (register WORD *next, register int len) { register int n; register COST cost; if (next == word_limit) return 0; n = best_width - len; cost = SHORT_COST (n); if (next->next_break != word_limit) { n = len - next->line_length; cost += RAGGED_COST (n); } return cost; } /* Output to stdout a paragraph from word up to (but not including) FINISH, which must be in the next_break chain from word. */ static void put_paragraph (register WORD *finish) { register WORD *w; put_line (words, first_indent); for (w = words->next_break; w != finish; w = w->next_break) put_line (w, other_indent); } /* Output to stdout the line beginning with word W, beginning in column INDENT, including the prefix (if any). */ static void put_line (register WORD *w, int indent) { register WORD *endline; out_column = 0; put_space (indent); endline = w->next_break - 1; for (; w != endline; w++) { put_word (w); put_space (w->space); } put_word (w); last_line_length = out_column; putc ('\n', outfile); } /* Output to stdout the word W. */ static void put_word (register WORD *w) { register const char *s; register int n; s = w->text; for (n = w->length; n != 0; n--) putc (*s++, outfile); out_column += w->length; } /* Output to stdout SPACE spaces, or equivalent tabs. */ static void put_space (int space) { out_column += space; while (space--) putc (' ', outfile); }
#define COPYRIGHT_NOTICE "Copyright (C) 2003 Free Software Foundation, Inc." #define BUG_ADDRESS "bonzini@gnu.org" /* GNU SED, a batch stream editor. Copyright (C) 1989,90,91,92,93,94,95,98,99,2002,2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "sed.h" #include <stdio.h> #ifdef HAVE_STRINGS_H # include <strings.h> #else # include <string.h> #endif /*HAVE_STRINGS_H*/ #ifdef HAVE_MEMORY_H # include <memory.h> #endif #ifndef HAVE_STRCHR # define strchr index # define strrchr rindex #endif #ifdef HAVE_STDLIB_H # include <stdlib.h> #endif #ifdef HAVE_SYS_TYPES_H # include <sys/types.h> #endif #ifdef HAVE_LOCALE_H # include <locale.h> #endif #include "getopt.h" #ifndef BOOTSTRAP #ifndef HAVE_STDLIB_H extern char *getenv P_((const char *)); #endif #endif #ifndef HAVE_STRTOUL # define ATOI(x) atoi(x) #else # define ATOI(x) strtoul(x, NULL, 0) #endif int extended_regexp_flags = 0; /* If set, fflush(stdout) on every line output. */ flagT unbuffered_output = FALSE; /* If set, don't write out the line unless explicitly told to */ flagT no_default_output = FALSE; /* If set, reset line counts on every new file. */ flagT separate_files = FALSE; /* How do we edit files in-place? (we don't if NULL) */ char *in_place_extension = NULL; /* Do we need to be pedantically POSIX compliant? */ flagT POSIXLY_CORRECT; /* How long should the `l' command's output line be? */ countT lcmd_out_line_len = 70; /* The complete compiled SED program that we are going to run: */ static struct vector *the_program = NULL; static void usage P_((int)); static void usage(status) int status; { FILE *out = status ? stderr : stdout; #ifdef REG_PERL #define PERL_HELP _(" -R, --regexp-perl\n use Perl 5's regular expressions syntax in the script.\n") #else #define PERL_HELP "" #endif fprintf(out, _("\ Usage: %s [OPTION]... {script-only-if-no-other-script} [input-file]...\n\ \n\ -n, --quiet, --silent\n\ suppress automatic printing of pattern space\n\ -e script, --expression=script\n\ add the script to the commands to be executed\n\ -f script-file, --file=script-file\n\ add the contents of script-file to the commands to be executed\n\ -i[suffix], --in-place[=suffix]\n\ edit files in place (makes backup if extension supplied)\n\ -l N, --line-length=N\n\ specify the desired line-wrap length for the `l' command\n\ -r, --regexp-extended\n\ use extended regular expressions in the script.\n%s\ -s, --separate\n\ consider files as separate rather than as a single continuous\n\ long stream.\n\ -u, --unbuffered\n\ load minimal amounts of data from the input files and flush\n\ the output buffers more often\n\ --help display this help and exit\n\ -V, --version output version information and exit\n\ \n\ If no -e, --expression, -f, or --file option is given, then the first\n\ non-option argument is taken as the sed script to interpret. All\n\ remaining arguments are names of input files; if no input files are\n\ specified, then the standard input is read.\n\ \n"), myname, PERL_HELP); fprintf(out, _("E-mail bug reports to: %s .\n\ Be sure to include the word ``%s'' somewhere in the ``Subject:'' field.\n"), BUG_ADDRESS, PACKAGE); ck_fclose (NULL); exit (status); } int main(argc, argv) int argc; char **argv; { #ifdef REG_PERL #define SHORTOPTS "shnrRuVe:f:l:i::" #else #define SHORTOPTS "shnruVe:f:l:i::" #endif static struct option longopts[] = { {"regexp-extended", 0, NULL, 'r'}, #ifdef REG_PERL {"regexp-perl", 0, NULL, 'R'}, #endif {"expression", 1, NULL, 'e'}, {"file", 1, NULL, 'f'}, {"in-place", 2, NULL, 'i'}, {"line-length", 1, NULL, 'l'}, {"quiet", 0, NULL, 'n'}, {"silent", 0, NULL, 'n'}, {"separate", 0, NULL, 's'}, {"unbuffered", 0, NULL, 'u'}, {"version", 0, NULL, 'V'}, {"help", 0, NULL, 'h'}, {NULL, 0, NULL, 0} }; int opt; int return_code; initialize_main(&argc, &argv); #if ENABLE_NLS #if HAVE_SETLOCALE /* Set locale according to user's wishes. */ setlocale (LC_ALL, ""); #endif /* Tell program which translations to use and where to find. */ bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); #endif POSIXLY_CORRECT = (getenv("POSIXLY_CORRECT") != NULL); /* If environment variable `COLS' is set, use its value for the baseline setting of `lcmd_out_line_len'. The "-1" is to avoid gratuitous auto-line-wrap on ttys. */ { const char *p = getenv("COLS"); if (p) { countT t = ATOI(p); if (1 < t) lcmd_out_line_len = t-1; } } myname = *argv; while ((opt = getopt_long(argc, argv, SHORTOPTS, longopts, NULL)) != EOF) { switch (opt) { case 'n': no_default_output = TRUE; break; case 'e': the_program = compile_string(the_program, optarg, strlen(optarg)); break; case 'f': the_program = compile_file(the_program, optarg); break; case 'i': separate_files = TRUE; if (optarg == NULL) in_place_extension = "*"; else if (strchr(optarg, '*') == NULL) { in_place_extension = xmalloc (strlen(optarg) + 2); in_place_extension[0] = '*'; strcpy (in_place_extension + 1, optarg); } else in_place_extension = ck_strdup(optarg); break; case 'l': lcmd_out_line_len = ATOI(optarg); break; case 'r': if (extended_regexp_flags) usage(4); extended_regexp_flags = REG_EXTENDED; break; #ifdef REG_PERL case 'R': if (extended_regexp_flags) usage(4); extended_regexp_flags = REG_PERL; break; #endif case 's': separate_files = TRUE; break; case 'u': unbuffered_output = TRUE; break; case 'V': #ifdef REG_PERL fprintf(stdout, _("super-sed version %s\n"), VERSION); fprintf(stdout, _("based on GNU sed version 3.02.80\n\n")); #else fprintf(stdout, _("GNU sed version %s\n"), VERSION); #endif fprintf(stdout, _("%s\n\ This is free software; see the source for copying conditions. There is NO\n\ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE,\n\ to the extent permitted by law.\n\ "), COPYRIGHT_NOTICE); ck_fclose (NULL); exit (0); case 'h': usage(0); default: usage(4); } } if (!the_program) { if (optind < argc) { char *arg = argv[optind++]; the_program = compile_string(the_program, arg, strlen(arg)); } else usage(4); } check_final_program(the_program); return_code = process_files(the_program, argv+optind); finish_program(the_program); ck_fclose(NULL); return return_code; }
bf0d4ef807c706a2b201c7d132c70314ad96aa24353d444bcaed4eca6c95a528 /usr/bin/sed
https://sourceware.org/pub/bzip2/bzip2-1.0.8.tar.gz ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269
SPDX-FileCopyrightText: 2021 Andrius Å tikonas <andrius@stikonas.eu> SPDX-FileCopyrightText: 2021 fosslinux <fosslinux@aussies.space> SPDX-License-Identifier: bzip2-1.0.6 mes libc has no time support, so we remove that. It also does not have fch{own,mod}, which we don't care about in the bootstrap anyway, so we can null-op those calls. diff -r -N -U3 bzip2.c bzip2.c --- bzip2.c 2019-07-13 18:50:05.000000000 +0100 +++ bzip2.c 2021-01-14 14:11:40.160213521 +0000 @@ -1051,12 +1051,9 @@ { # if BZ_UNIX IntNative retVal; - struct utimbuf uTimBuf; - uTimBuf.actime = fileMetaInfo.st_atime; - uTimBuf.modtime = fileMetaInfo.st_mtime; - retVal = utime ( dstName, &uTimBuf ); + retVal = 0; ERROR_IF_NOT_ZERO ( retVal ); # endif } diff -r -N -U3 utime.h utime.h --- utime.h 1970-01-01 01:00:00.000000000 +0100 +++ utime.h 2021-01-14 18:11:11.253825037 +0000 @@ -0,0 +1,2 @@ +#define fchown(filedes, owner, group) 0 +#define fchmod(filedes, mode) 0
/*-----------------------------------------------------------*/ /*--- A block-sorting, lossless compressor bzip2.c ---*/ /*-----------------------------------------------------------*/ /* ------------------------------------------------------------------ This file is part of bzip2/libbzip2, a program and library for lossless, block-sorting data compression. bzip2/libbzip2 version 1.0.8 of 13 July 2019 Copyright (C) 1996-2019 Julian Seward <jseward@acm.org> Please read the WARNING, DISCLAIMER and PATENTS sections in the README file. This program is released under the terms of the license contained in the file LICENSE. ------------------------------------------------------------------ */ /* Place a 1 beside your platform, and 0 elsewhere. Generic 32-bit Unix. Also works on 64-bit Unix boxes. This is the default. */ #define BZ_UNIX 1 /*-- Win32, as seen by Jacob Navia's excellent port of (Chris Fraser & David Hanson)'s excellent lcc compiler. Or with MS Visual C. This is selected automatically if compiled by a compiler which defines _WIN32, not including the Cygwin GCC. --*/ #define BZ_LCCWIN32 0 #if defined(_WIN32) && !defined(__CYGWIN__) #undef BZ_LCCWIN32 #define BZ_LCCWIN32 1 #undef BZ_UNIX #define BZ_UNIX 0 #endif /*---------------------------------------------*/ /*-- Some stuff for all platforms. --*/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <math.h> #include <errno.h> #include <ctype.h> #include "bzlib.h" #define ERROR_IF_EOF(i) { if ((i) == EOF) ioError(); } #define ERROR_IF_NOT_ZERO(i) { if ((i) != 0) ioError(); } #define ERROR_IF_MINUS_ONE(i) { if ((i) == (-1)) ioError(); } /*---------------------------------------------*/ /*-- Platform-specific stuff. --*/ #if BZ_UNIX # include <fcntl.h> # include <sys/types.h> # include <utime.h> # include <unistd.h> # include <sys/stat.h> # include <sys/times.h> # define PATH_SEP '/' # define MY_LSTAT lstat # define MY_STAT stat # define MY_S_ISREG S_ISREG # define MY_S_ISDIR S_ISDIR # define APPEND_FILESPEC(root, name) \ root=snocString((root), (name)) # define APPEND_FLAG(root, name) \ root=snocString((root), (name)) # define SET_BINARY_MODE(fd) /**/ # ifdef __GNUC__ # define NORETURN __attribute__ ((noreturn)) # else # define NORETURN /**/ # endif # ifdef __DJGPP__ # include <io.h> # include <fcntl.h> # undef MY_LSTAT # undef MY_STAT # define MY_LSTAT stat # define MY_STAT stat # undef SET_BINARY_MODE # define SET_BINARY_MODE(fd) \ do { \ int retVal = setmode ( fileno ( fd ), \ O_BINARY ); \ ERROR_IF_MINUS_ONE ( retVal ); \ } while ( 0 ) # endif # ifdef __CYGWIN__ # include <io.h> # include <fcntl.h> # undef SET_BINARY_MODE # define SET_BINARY_MODE(fd) \ do { \ int retVal = setmode ( fileno ( fd ), \ O_BINARY ); \ ERROR_IF_MINUS_ONE ( retVal ); \ } while ( 0 ) # endif #endif /* BZ_UNIX */ #if BZ_LCCWIN32 # include <io.h> # include <fcntl.h> # include <sys/stat.h> # define NORETURN /**/ # define PATH_SEP '\\' # define MY_LSTAT _stati64 # define MY_STAT _stati64 # define MY_S_ISREG(x) ((x) & _S_IFREG) # define MY_S_ISDIR(x) ((x) & _S_IFDIR) # define APPEND_FLAG(root, name) \ root=snocString((root), (name)) # define APPEND_FILESPEC(root, name) \ root = snocString ((root), (name)) # define SET_BINARY_MODE(fd) \ do { \ int retVal = setmode ( fileno ( fd ), \ O_BINARY ); \ ERROR_IF_MINUS_ONE ( retVal ); \ } while ( 0 ) #endif /* BZ_LCCWIN32 */ /*---------------------------------------------*/ /*-- Some more stuff for all platforms :-) --*/ typedef char Char; typedef unsigned char Bool; typedef unsigned char UChar; typedef int Int32; typedef unsigned int UInt32; typedef short Int16; typedef unsigned short UInt16; #define True ((Bool)1) #define False ((Bool)0) /*-- IntNative is your platform's `native' int size. Only here to avoid probs with 64-bit platforms. --*/ typedef int IntNative; /*---------------------------------------------------*/ /*--- Misc (file handling) data decls ---*/ /*---------------------------------------------------*/ Int32 verbosity; Bool keepInputFiles, smallMode, deleteOutputOnInterrupt; Bool forceOverwrite, testFailsExist, unzFailsExist, noisy; Int32 numFileNames, numFilesProcessed, blockSize100k; Int32 exitValue; /*-- source modes; F==file, I==stdin, O==stdout --*/ #define SM_I2O 1 #define SM_F2O 2 #define SM_F2F 3 /*-- operation modes --*/ #define OM_Z 1 #define OM_UNZ 2 #define OM_TEST 3 Int32 opMode; Int32 srcMode; #define FILE_NAME_LEN 1034 Int32 longestFileName; Char inName [FILE_NAME_LEN]; Char outName[FILE_NAME_LEN]; Char tmpName[FILE_NAME_LEN]; Char *progName; Char progNameReally[FILE_NAME_LEN]; FILE *outputHandleJustInCase; Int32 workFactor; static void panic ( const Char* ) NORETURN; static void ioError ( void ) NORETURN; static void outOfMemory ( void ) NORETURN; static void configError ( void ) NORETURN; static void crcError ( void ) NORETURN; static void cleanUpAndFail ( Int32 ) NORETURN; static void compressedStreamEOF ( void ) NORETURN; static void copyFileName ( Char*, Char* ); static void* myMalloc ( Int32 ); static void applySavedFileAttrToOutputFile ( IntNative fd ); /*---------------------------------------------------*/ /*--- An implementation of 64-bit ints. Sigh. ---*/ /*--- Roll on widespread deployment of ANSI C9X ! ---*/ /*---------------------------------------------------*/ typedef struct { UChar b[8]; } UInt64; static void uInt64_from_UInt32s ( UInt64* n, UInt32 lo32, UInt32 hi32 ) { n->b[7] = (UChar)((hi32 >> 24) & 0xFF); n->b[6] = (UChar)((hi32 >> 16) & 0xFF); n->b[5] = (UChar)((hi32 >> 8) & 0xFF); n->b[4] = (UChar) (hi32 & 0xFF); n->b[3] = (UChar)((lo32 >> 24) & 0xFF); n->b[2] = (UChar)((lo32 >> 16) & 0xFF); n->b[1] = (UChar)((lo32 >> 8) & 0xFF); n->b[0] = (UChar) (lo32 & 0xFF); } static double uInt64_to_double ( UInt64* n ) { Int32 i; double base = 1.0; double sum = 0.0; for (i = 0; i < 8; i++) { sum += base * (double)(n->b[i]); base *= 256.0; } return sum; } static Bool uInt64_isZero ( UInt64* n ) { Int32 i; for (i = 0; i < 8; i++) if (n->b[i] != 0) return 0; return 1; } /* Divide *n by 10, and return the remainder. */ static Int32 uInt64_qrm10 ( UInt64* n ) { UInt32 rem, tmp; Int32 i; rem = 0; for (i = 7; i >= 0; i--) { tmp = rem * 256 + n->b[i]; n->b[i] = tmp / 10; rem = tmp % 10; } return rem; } /* ... and the Whole Entire Point of all this UInt64 stuff is so that we can supply the following function. */ static void uInt64_toAscii ( char* outbuf, UInt64* n ) { Int32 i, q; UChar buf[32]; Int32 nBuf = 0; UInt64 n_copy = *n; do { q = uInt64_qrm10 ( &n_copy ); buf[nBuf] = q + '0'; nBuf++; } while (!uInt64_isZero(&n_copy)); outbuf[nBuf] = 0; for (i = 0; i < nBuf; i++) outbuf[i] = buf[nBuf-i-1]; } /*---------------------------------------------------*/ /*--- Processing of complete files and streams ---*/ /*---------------------------------------------------*/ /*---------------------------------------------*/ static Bool myfeof ( FILE* f ) { Int32 c = fgetc ( f ); if (c == EOF) return True; ungetc ( c, f ); return False; } /*---------------------------------------------*/ static void compressStream ( FILE *stream, FILE *zStream ) { BZFILE* bzf = NULL; UChar ibuf[5000]; Int32 nIbuf; UInt32 nbytes_in_lo32, nbytes_in_hi32; UInt32 nbytes_out_lo32, nbytes_out_hi32; Int32 bzerr, bzerr_dummy, ret; SET_BINARY_MODE(stream); SET_BINARY_MODE(zStream); if (ferror(stream)) goto errhandler_io; if (ferror(zStream)) goto errhandler_io; bzf = BZ2_bzWriteOpen ( &bzerr, zStream, blockSize100k, verbosity, workFactor ); if (bzerr != BZ_OK) goto errhandler; if (verbosity >= 2) fprintf ( stderr, "\n" ); while (True) { if (myfeof(stream)) break; nIbuf = fread ( ibuf, sizeof(UChar), 5000, stream ); if (ferror(stream)) goto errhandler_io; if (nIbuf > 0) BZ2_bzWrite ( &bzerr, bzf, (void*)ibuf, nIbuf ); if (bzerr != BZ_OK) goto errhandler; } BZ2_bzWriteClose64 ( &bzerr, bzf, 0, &nbytes_in_lo32, &nbytes_in_hi32, &nbytes_out_lo32, &nbytes_out_hi32 ); if (bzerr != BZ_OK) goto errhandler; if (ferror(zStream)) goto errhandler_io; ret = fflush ( zStream ); if (ret == EOF) goto errhandler_io; if (zStream != stdout) { Int32 fd = fileno ( zStream ); if (fd < 0) goto errhandler_io; applySavedFileAttrToOutputFile ( fd ); ret = fclose ( zStream ); outputHandleJustInCase = NULL; if (ret == EOF) goto errhandler_io; } outputHandleJustInCase = NULL; if (ferror(stream)) goto errhandler_io; ret = fclose ( stream ); if (ret == EOF) goto errhandler_io; if (verbosity >= 1) { if (nbytes_in_lo32 == 0 && nbytes_in_hi32 == 0) { fprintf ( stderr, " no data compressed.\n"); } else { Char buf_nin[32], buf_nout[32]; UInt64 nbytes_in, nbytes_out; double nbytes_in_d, nbytes_out_d; uInt64_from_UInt32s ( &nbytes_in, nbytes_in_lo32, nbytes_in_hi32 ); uInt64_from_UInt32s ( &nbytes_out, nbytes_out_lo32, nbytes_out_hi32 ); nbytes_in_d = uInt64_to_double ( &nbytes_in ); nbytes_out_d = uInt64_to_double ( &nbytes_out ); uInt64_toAscii ( buf_nin, &nbytes_in ); uInt64_toAscii ( buf_nout, &nbytes_out ); fprintf ( stderr, "%6.3f:1, %6.3f bits/byte, " "%5.2f%% saved, %s in, %s out.\n", nbytes_in_d / nbytes_out_d, (8.0 * nbytes_out_d) / nbytes_in_d, 100.0 * (1.0 - nbytes_out_d / nbytes_in_d), buf_nin, buf_nout ); } } return; errhandler: BZ2_bzWriteClose64 ( &bzerr_dummy, bzf, 1, &nbytes_in_lo32, &nbytes_in_hi32, &nbytes_out_lo32, &nbytes_out_hi32 ); switch (bzerr) { case BZ_CONFIG_ERROR: configError(); break; case BZ_MEM_ERROR: outOfMemory (); break; case BZ_IO_ERROR: errhandler_io: ioError(); break; default: panic ( "compress:unexpected error" ); } panic ( "compress:end" ); /*notreached*/ } /*---------------------------------------------*/ static Bool uncompressStream ( FILE *zStream, FILE *stream ) { BZFILE* bzf = NULL; Int32 bzerr, bzerr_dummy, ret, nread, streamNo, i; UChar obuf[5000]; UChar unused[BZ_MAX_UNUSED]; Int32 nUnused; void* unusedTmpV; UChar* unusedTmp; nUnused = 0; streamNo = 0; SET_BINARY_MODE(stream); SET_BINARY_MODE(zStream); if (ferror(stream)) goto errhandler_io; if (ferror(zStream)) goto errhandler_io; while (True) { bzf = BZ2_bzReadOpen ( &bzerr, zStream, verbosity, (int)smallMode, unused, nUnused ); if (bzf == NULL || bzerr != BZ_OK) goto errhandler; streamNo++; while (bzerr == BZ_OK) { nread = BZ2_bzRead ( &bzerr, bzf, obuf, 5000 ); if (bzerr == BZ_DATA_ERROR_MAGIC) goto trycat; if ((bzerr == BZ_OK || bzerr == BZ_STREAM_END) && nread > 0) fwrite ( obuf, sizeof(UChar), nread, stream ); if (ferror(stream)) goto errhandler_io; } if (bzerr != BZ_STREAM_END) goto errhandler; BZ2_bzReadGetUnused ( &bzerr, bzf, &unusedTmpV, &nUnused ); if (bzerr != BZ_OK) panic ( "decompress:bzReadGetUnused" ); unusedTmp = (UChar*)unusedTmpV; for (i = 0; i < nUnused; i++) unused[i] = unusedTmp[i]; BZ2_bzReadClose ( &bzerr, bzf ); if (bzerr != BZ_OK) panic ( "decompress:bzReadGetUnused" ); if (nUnused == 0 && myfeof(zStream)) break; } closeok: if (ferror(zStream)) goto errhandler_io; if (stream != stdout) { Int32 fd = fileno ( stream ); if (fd < 0) goto errhandler_io; applySavedFileAttrToOutputFile ( fd ); } ret = fclose ( zStream ); if (ret == EOF) goto errhandler_io; if (ferror(stream)) goto errhandler_io; ret = fflush ( stream ); if (ret != 0) goto errhandler_io; if (stream != stdout) { ret = fclose ( stream ); outputHandleJustInCase = NULL; if (ret == EOF) goto errhandler_io; } outputHandleJustInCase = NULL; if (verbosity >= 2) fprintf ( stderr, "\n " ); return True; trycat: if (forceOverwrite) { rewind(zStream); while (True) { if (myfeof(zStream)) break; nread = fread ( obuf, sizeof(UChar), 5000, zStream ); if (ferror(zStream)) goto errhandler_io; if (nread > 0) fwrite ( obuf, sizeof(UChar), nread, stream ); if (ferror(stream)) goto errhandler_io; } goto closeok; } errhandler: BZ2_bzReadClose ( &bzerr_dummy, bzf ); switch (bzerr) { case BZ_CONFIG_ERROR: configError(); break; case BZ_IO_ERROR: errhandler_io: ioError(); break; case BZ_DATA_ERROR: crcError(); case BZ_MEM_ERROR: outOfMemory(); case BZ_UNEXPECTED_EOF: compressedStreamEOF(); case BZ_DATA_ERROR_MAGIC: if (zStream != stdin) fclose(zStream); if (stream != stdout) fclose(stream); if (streamNo == 1) { return False; } else { if (noisy) fprintf ( stderr, "\n%s: %s: trailing garbage after EOF ignored\n", progName, inName ); return True; } default: panic ( "decompress:unexpected error" ); } panic ( "decompress:end" ); return True; /*notreached*/ } /*---------------------------------------------*/ static Bool testStream ( FILE *zStream ) { BZFILE* bzf = NULL; Int32 bzerr, bzerr_dummy, ret, streamNo, i; UChar obuf[5000]; UChar unused[BZ_MAX_UNUSED]; Int32 nUnused; void* unusedTmpV; UChar* unusedTmp; nUnused = 0; streamNo = 0; SET_BINARY_MODE(zStream); if (ferror(zStream)) goto errhandler_io; while (True) { bzf = BZ2_bzReadOpen ( &bzerr, zStream, verbosity, (int)smallMode, unused, nUnused ); if (bzf == NULL || bzerr != BZ_OK) goto errhandler; streamNo++; while (bzerr == BZ_OK) { BZ2_bzRead ( &bzerr, bzf, obuf, 5000 ); if (bzerr == BZ_DATA_ERROR_MAGIC) goto errhandler; } if (bzerr != BZ_STREAM_END) goto errhandler; BZ2_bzReadGetUnused ( &bzerr, bzf, &unusedTmpV, &nUnused ); if (bzerr != BZ_OK) panic ( "test:bzReadGetUnused" ); unusedTmp = (UChar*)unusedTmpV; for (i = 0; i < nUnused; i++) unused[i] = unusedTmp[i]; BZ2_bzReadClose ( &bzerr, bzf ); if (bzerr != BZ_OK) panic ( "test:bzReadGetUnused" ); if (nUnused == 0 && myfeof(zStream)) break; } if (ferror(zStream)) goto errhandler_io; ret = fclose ( zStream ); if (ret == EOF) goto errhandler_io; if (verbosity >= 2) fprintf ( stderr, "\n " ); return True; errhandler: BZ2_bzReadClose ( &bzerr_dummy, bzf ); if (verbosity == 0) fprintf ( stderr, "%s: %s: ", progName, inName ); switch (bzerr) { case BZ_CONFIG_ERROR: configError(); break; case BZ_IO_ERROR: errhandler_io: ioError(); break; case BZ_DATA_ERROR: fprintf ( stderr, "data integrity (CRC) error in data\n" ); return False; case BZ_MEM_ERROR: outOfMemory(); case BZ_UNEXPECTED_EOF: fprintf ( stderr, "file ends unexpectedly\n" ); return False; case BZ_DATA_ERROR_MAGIC: if (zStream != stdin) fclose(zStream); if (streamNo == 1) { fprintf ( stderr, "bad magic number (file not created by bzip2)\n" ); return False; } else { if (noisy) fprintf ( stderr, "trailing garbage after EOF ignored\n" ); return True; } default: panic ( "test:unexpected error" ); } panic ( "test:end" ); return True; /*notreached*/ } /*---------------------------------------------------*/ /*--- Error [non-] handling grunge ---*/ /*---------------------------------------------------*/ /*---------------------------------------------*/ static void setExit ( Int32 v ) { if (v > exitValue) exitValue = v; } /*---------------------------------------------*/ static void cadvise ( void ) { if (noisy) fprintf ( stderr, "\nIt is possible that the compressed file(s) have become corrupted.\n" "You can use the -tvv option to test integrity of such files.\n\n" "You can use the `bzip2recover' program to attempt to recover\n" "data from undamaged sections of corrupted files.\n\n" ); } /*---------------------------------------------*/ static void showFileNames ( void ) { if (noisy) fprintf ( stderr, "\tInput file = %s, output file = %s\n", inName, outName ); } /*---------------------------------------------*/ static void cleanUpAndFail ( Int32 ec ) { IntNative retVal; struct MY_STAT statBuf; if ( srcMode == SM_F2F && opMode != OM_TEST && deleteOutputOnInterrupt ) { /* Check whether input file still exists. Delete output file only if input exists to avoid loss of data. Joerg Prante, 5 January 2002. (JRS 06-Jan-2002: other changes in 1.0.2 mean this is less likely to happen. But to be ultra-paranoid, we do the check anyway.) */ retVal = MY_STAT ( inName, &statBuf ); if (retVal == 0) { if (noisy) fprintf ( stderr, "%s: Deleting output file %s, if it exists.\n", progName, outName ); if (outputHandleJustInCase != NULL) fclose ( outputHandleJustInCase ); retVal = remove ( outName ); if (retVal != 0) fprintf ( stderr, "%s: WARNING: deletion of output file " "(apparently) failed.\n", progName ); } else { fprintf ( stderr, "%s: WARNING: deletion of output file suppressed\n", progName ); fprintf ( stderr, "%s: since input file no longer exists. Output file\n", progName ); fprintf ( stderr, "%s: `%s' may be incomplete.\n", progName, outName ); fprintf ( stderr, "%s: I suggest doing an integrity test (bzip2 -tv)" " of it.\n", progName ); } } if (noisy && numFileNames > 0 && numFilesProcessed < numFileNames) { fprintf ( stderr, "%s: WARNING: some files have not been processed:\n" "%s: %d specified on command line, %d not processed yet.\n\n", progName, progName, numFileNames, numFileNames - numFilesProcessed ); } setExit(ec); exit(exitValue); } /*---------------------------------------------*/ static void panic ( const Char* s ) { fprintf ( stderr, "\n%s: PANIC -- internal consistency error:\n" "\t%s\n" "\tThis is a BUG. Please report it to:\n" "\tbzip2-devel@sourceware.org\n", progName, s ); showFileNames(); cleanUpAndFail( 3 ); } /*---------------------------------------------*/ static void crcError ( void ) { fprintf ( stderr, "\n%s: Data integrity error when decompressing.\n", progName ); showFileNames(); cadvise(); cleanUpAndFail( 2 ); } /*---------------------------------------------*/ static void compressedStreamEOF ( void ) { if (noisy) { fprintf ( stderr, "\n%s: Compressed file ends unexpectedly;\n\t" "perhaps it is corrupted? *Possible* reason follows.\n", progName ); perror ( progName ); showFileNames(); cadvise(); } cleanUpAndFail( 2 ); } /*---------------------------------------------*/ static void ioError ( void ) { fprintf ( stderr, "\n%s: I/O or other error, bailing out. " "Possible reason follows.\n", progName ); perror ( progName ); showFileNames(); cleanUpAndFail( 1 ); } /*---------------------------------------------*/ static void mySignalCatcher ( IntNative n ) { fprintf ( stderr, "\n%s: Control-C or similar caught, quitting.\n", progName ); cleanUpAndFail(1); } /*---------------------------------------------*/ static void mySIGSEGVorSIGBUScatcher ( IntNative n ) { if (opMode == OM_Z) fprintf ( stderr, "\n%s: Caught a SIGSEGV or SIGBUS whilst compressing.\n" "\n" " Possible causes are (most likely first):\n" " (1) This computer has unreliable memory or cache hardware\n" " (a surprisingly common problem; try a different machine.)\n" " (2) A bug in the compiler used to create this executable\n" " (unlikely, if you didn't compile bzip2 yourself.)\n" " (3) A real bug in bzip2 -- I hope this should never be the case.\n" " The user's manual, Section 4.3, has more info on (1) and (2).\n" " \n" " If you suspect this is a bug in bzip2, or are unsure about (1)\n" " or (2), feel free to report it to: bzip2-devel@sourceware.org.\n" " Section 4.3 of the user's manual describes the info a useful\n" " bug report should have. If the manual is available on your\n" " system, please try and read it before mailing me. If you don't\n" " have the manual or can't be bothered to read it, mail me anyway.\n" "\n", progName ); else fprintf ( stderr, "\n%s: Caught a SIGSEGV or SIGBUS whilst decompressing.\n" "\n" " Possible causes are (most likely first):\n" " (1) The compressed data is corrupted, and bzip2's usual checks\n" " failed to detect this. Try bzip2 -tvv my_file.bz2.\n" " (2) This computer has unreliable memory or cache hardware\n" " (a surprisingly common problem; try a different machine.)\n" " (3) A bug in the compiler used to create this executable\n" " (unlikely, if you didn't compile bzip2 yourself.)\n" " (4) A real bug in bzip2 -- I hope this should never be the case.\n" " The user's manual, Section 4.3, has more info on (2) and (3).\n" " \n" " If you suspect this is a bug in bzip2, or are unsure about (2)\n" " or (3), feel free to report it to: bzip2-devel@sourceware.org.\n" " Section 4.3 of the user's manual describes the info a useful\n" " bug report should have. If the manual is available on your\n" " system, please try and read it before mailing me. If you don't\n" " have the manual or can't be bothered to read it, mail me anyway.\n" "\n", progName ); showFileNames(); if (opMode == OM_Z) cleanUpAndFail( 3 ); else { cadvise(); cleanUpAndFail( 2 ); } } /*---------------------------------------------*/ static void outOfMemory ( void ) { fprintf ( stderr, "\n%s: couldn't allocate enough memory\n", progName ); showFileNames(); cleanUpAndFail(1); } /*---------------------------------------------*/ static void configError ( void ) { fprintf ( stderr, "bzip2: I'm not configured correctly for this platform!\n" "\tI require Int32, Int16 and Char to have sizes\n" "\tof 4, 2 and 1 bytes to run properly, and they don't.\n" "\tProbably you can fix this by defining them correctly,\n" "\tand recompiling. Bye!\n" ); setExit(3); exit(exitValue); } /*---------------------------------------------------*/ /*--- The main driver machinery ---*/ /*---------------------------------------------------*/ /* All rather crufty. The main problem is that input files are stat()d multiple times before use. This should be cleaned up. */ /*---------------------------------------------*/ static void pad ( Char *s ) { Int32 i; if ( (Int32)strlen(s) >= longestFileName ) return; for (i = 1; i <= longestFileName - (Int32)strlen(s); i++) fprintf ( stderr, " " ); } /*---------------------------------------------*/ static void copyFileName ( Char* to, Char* from ) { if ( strlen(from) > FILE_NAME_LEN-10 ) { fprintf ( stderr, "bzip2: file name\n`%s'\n" "is suspiciously (more than %d chars) long.\n" "Try using a reasonable file name instead. Sorry! :-)\n", from, FILE_NAME_LEN-10 ); setExit(1); exit(exitValue); } strncpy(to,from,FILE_NAME_LEN-10); to[FILE_NAME_LEN-10]='\0'; } /*---------------------------------------------*/ static Bool fileExists ( Char* name ) { FILE *tmp = fopen ( name, "rb" ); Bool exists = (tmp != NULL); if (tmp != NULL) fclose ( tmp ); return exists; } /*---------------------------------------------*/ /* Open an output file safely with O_EXCL and good permissions. This avoids a race condition in versions < 1.0.2, in which the file was first opened and then had its interim permissions set safely. We instead use open() to create the file with the interim permissions required. (--- --- rw-). For non-Unix platforms, if we are not worrying about security issues, simple this simply behaves like fopen. */ static FILE* fopen_output_safely ( Char* name, const char* mode ) { # if BZ_UNIX FILE* fp; IntNative fh; fh = open(name, O_WRONLY|O_CREAT|O_EXCL, S_IWUSR|S_IRUSR); if (fh == -1) return NULL; fp = fdopen(fh, mode); if (fp == NULL) close(fh); return fp; # else return fopen(name, mode); # endif } /*---------------------------------------------*/ /*-- if in doubt, return True --*/ static Bool notAStandardFile ( Char* name ) { IntNative i; struct MY_STAT statBuf; i = MY_LSTAT ( name, &statBuf ); if (i != 0) return True; if (MY_S_ISREG(statBuf.st_mode)) return False; return True; } /*---------------------------------------------*/ /*-- rac 11/21/98 see if file has hard links to it --*/ static Int32 countHardLinks ( Char* name ) { IntNative i; struct MY_STAT statBuf; i = MY_LSTAT ( name, &statBuf ); if (i != 0) return 0; return (statBuf.st_nlink - 1); } /*---------------------------------------------*/ /* Copy modification date, access date, permissions and owner from the source to destination file. We have to copy this meta-info off into fileMetaInfo before starting to compress / decompress it, because doing it afterwards means we get the wrong access time. To complicate matters, in compress() and decompress() below, the sequence of tests preceding the call to saveInputFileMetaInfo() involves calling fileExists(), which in turn establishes its result by attempting to fopen() the file, and if successful, immediately fclose()ing it again. So we have to assume that the fopen() call does not cause the access time field to be updated. Reading of the man page for stat() (man 2 stat) on RedHat 7.2 seems to imply that merely doing open() will not affect the access time. Therefore we merely need to hope that the C library only does open() as a result of fopen(), and not any kind of read()-ahead cleverness. It sounds pretty fragile to me. Whether this carries across robustly to arbitrary Unix-like platforms (or even works robustly on this one, RedHat 7.2) is unknown to me. Nevertheless ... */ #if BZ_UNIX static struct MY_STAT fileMetaInfo; #endif static void saveInputFileMetaInfo ( Char *srcName ) { # if BZ_UNIX IntNative retVal; /* Note use of stat here, not lstat. */ retVal = MY_STAT( srcName, &fileMetaInfo ); ERROR_IF_NOT_ZERO ( retVal ); # endif } static void applySavedTimeInfoToOutputFile ( Char *dstName ) { # if BZ_UNIX IntNative retVal; struct utimbuf uTimBuf; uTimBuf.actime = fileMetaInfo.st_atime; uTimBuf.modtime = fileMetaInfo.st_mtime; retVal = utime ( dstName, &uTimBuf ); ERROR_IF_NOT_ZERO ( retVal ); # endif } static void applySavedFileAttrToOutputFile ( IntNative fd ) { # if BZ_UNIX IntNative retVal; retVal = fchmod ( fd, fileMetaInfo.st_mode ); ERROR_IF_NOT_ZERO ( retVal ); (void) fchown ( fd, fileMetaInfo.st_uid, fileMetaInfo.st_gid ); /* chown() will in many cases return with EPERM, which can be safely ignored. */ # endif } /*---------------------------------------------*/ static Bool containsDubiousChars ( Char* name ) { # if BZ_UNIX /* On unix, files can contain any characters and the file expansion * is performed by the shell. */ return False; # else /* ! BZ_UNIX */ /* On non-unix (Win* platforms), wildcard characters are not allowed in * filenames. */ for (; *name != '\0'; name++) if (*name == '?' || *name == '*') return True; return False; # endif /* BZ_UNIX */ } /*---------------------------------------------*/ #define BZ_N_SUFFIX_PAIRS 4 const Char* zSuffix[BZ_N_SUFFIX_PAIRS] = { ".bz2", ".bz", ".tbz2", ".tbz" }; const Char* unzSuffix[BZ_N_SUFFIX_PAIRS] = { "", "", ".tar", ".tar" }; static Bool hasSuffix ( Char* s, const Char* suffix ) { Int32 ns = strlen(s); Int32 nx = strlen(suffix); if (ns < nx) return False; if (strcmp(s + ns - nx, suffix) == 0) return True; return False; } static Bool mapSuffix ( Char* name, const Char* oldSuffix, const Char* newSuffix ) { if (!hasSuffix(name,oldSuffix)) return False; name[strlen(name)-strlen(oldSuffix)] = 0; strcat ( name, newSuffix ); return True; } /*---------------------------------------------*/ static void compress ( Char *name ) { FILE *inStr; FILE *outStr; Int32 n, i; struct MY_STAT statBuf; deleteOutputOnInterrupt = False; if (name == NULL && srcMode != SM_I2O) panic ( "compress: bad modes\n" ); switch (srcMode) { case SM_I2O: copyFileName ( inName, (Char*)"(stdin)" ); copyFileName ( outName, (Char*)"(stdout)" ); break; case SM_F2F: copyFileName ( inName, name ); copyFileName ( outName, name ); strcat ( outName, ".bz2" ); break; case SM_F2O: copyFileName ( inName, name ); copyFileName ( outName, (Char*)"(stdout)" ); break; } if ( srcMode != SM_I2O && containsDubiousChars ( inName ) ) { if (noisy) fprintf ( stderr, "%s: There are no files matching `%s'.\n", progName, inName ); setExit(1); return; } if ( srcMode != SM_I2O && !fileExists ( inName ) ) { fprintf ( stderr, "%s: Can't open input file %s: %s.\n", progName, inName, strerror(errno) ); setExit(1); return; } for (i = 0; i < BZ_N_SUFFIX_PAIRS; i++) { if (hasSuffix(inName, zSuffix[i])) { if (noisy) fprintf ( stderr, "%s: Input file %s already has %s suffix.\n", progName, inName, zSuffix[i] ); setExit(1); return; } } if ( srcMode == SM_F2F || srcMode == SM_F2O ) { MY_STAT(inName, &statBuf); if ( MY_S_ISDIR(statBuf.st_mode) ) { fprintf( stderr, "%s: Input file %s is a directory.\n", progName,inName); setExit(1); return; } } if ( srcMode == SM_F2F && !forceOverwrite && notAStandardFile ( inName )) { if (noisy) fprintf ( stderr, "%s: Input file %s is not a normal file.\n", progName, inName ); setExit(1); return; } if ( srcMode == SM_F2F && fileExists ( outName ) ) { if (forceOverwrite) { remove(outName); } else { fprintf ( stderr, "%s: Output file %s already exists.\n", progName, outName ); setExit(1); return; } } if ( srcMode == SM_F2F && !forceOverwrite && (n=countHardLinks ( inName )) > 0) { fprintf ( stderr, "%s: Input file %s has %d other link%s.\n", progName, inName, n, n > 1 ? "s" : "" ); setExit(1); return; } if ( srcMode == SM_F2F ) { /* Save the file's meta-info before we open it. Doing it later means we mess up the access times. */ saveInputFileMetaInfo ( inName ); } switch ( srcMode ) { case SM_I2O: inStr = stdin; outStr = stdout; if ( isatty ( fileno ( stdout ) ) ) { fprintf ( stderr, "%s: I won't write compressed data to a terminal.\n", progName ); fprintf ( stderr, "%s: For help, type: `%s --help'.\n", progName, progName ); setExit(1); return; }; break; case SM_F2O: inStr = fopen ( inName, "rb" ); outStr = stdout; if ( isatty ( fileno ( stdout ) ) ) { fprintf ( stderr, "%s: I won't write compressed data to a terminal.\n", progName ); fprintf ( stderr, "%s: For help, type: `%s --help'.\n", progName, progName ); if ( inStr != NULL ) fclose ( inStr ); setExit(1); return; }; if ( inStr == NULL ) { fprintf ( stderr, "%s: Can't open input file %s: %s.\n", progName, inName, strerror(errno) ); setExit(1); return; }; break; case SM_F2F: inStr = fopen ( inName, "rb" ); outStr = fopen_output_safely ( outName, "wb" ); if ( outStr == NULL) { fprintf ( stderr, "%s: Can't create output file %s: %s.\n", progName, outName, strerror(errno) ); if ( inStr != NULL ) fclose ( inStr ); setExit(1); return; } if ( inStr == NULL ) { fprintf ( stderr, "%s: Can't open input file %s: %s.\n", progName, inName, strerror(errno) ); if ( outStr != NULL ) fclose ( outStr ); setExit(1); return; }; break; default: panic ( "compress: bad srcMode" ); break; } if (verbosity >= 1) { fprintf ( stderr, " %s: ", inName ); pad ( inName ); fflush ( stderr ); } /*--- Now the input and output handles are sane. Do the Biz. ---*/ outputHandleJustInCase = outStr; deleteOutputOnInterrupt = True; compressStream ( inStr, outStr ); outputHandleJustInCase = NULL; /*--- If there was an I/O error, we won't get here. ---*/ if ( srcMode == SM_F2F ) { applySavedTimeInfoToOutputFile ( outName ); deleteOutputOnInterrupt = False; if ( !keepInputFiles ) { IntNative retVal = remove ( inName ); ERROR_IF_NOT_ZERO ( retVal ); } } deleteOutputOnInterrupt = False; } /*---------------------------------------------*/ static void uncompress ( Char *name ) { FILE *inStr; FILE *outStr; Int32 n, i; Bool magicNumberOK; Bool cantGuess; struct MY_STAT statBuf; deleteOutputOnInterrupt = False; if (name == NULL && srcMode != SM_I2O) panic ( "uncompress: bad modes\n" ); cantGuess = False; switch (srcMode) { case SM_I2O: copyFileName ( inName, (Char*)"(stdin)" ); copyFileName ( outName, (Char*)"(stdout)" ); break; case SM_F2F: copyFileName ( inName, name ); copyFileName ( outName, name ); for (i = 0; i < BZ_N_SUFFIX_PAIRS; i++) if (mapSuffix(outName,zSuffix[i],unzSuffix[i])) goto zzz; cantGuess = True; strcat ( outName, ".out" ); break; case SM_F2O: copyFileName ( inName, name ); copyFileName ( outName, (Char*)"(stdout)" ); break; } zzz: if ( srcMode != SM_I2O && containsDubiousChars ( inName ) ) { if (noisy) fprintf ( stderr, "%s: There are no files matching `%s'.\n", progName, inName ); setExit(1); return; } if ( srcMode != SM_I2O && !fileExists ( inName ) ) { fprintf ( stderr, "%s: Can't open input file %s: %s.\n", progName, inName, strerror(errno) ); setExit(1); return; } if ( srcMode == SM_F2F || srcMode == SM_F2O ) { MY_STAT(inName, &statBuf); if ( MY_S_ISDIR(statBuf.st_mode) ) { fprintf( stderr, "%s: Input file %s is a directory.\n", progName,inName); setExit(1); return; } } if ( srcMode == SM_F2F && !forceOverwrite && notAStandardFile ( inName )) { if (noisy) fprintf ( stderr, "%s: Input file %s is not a normal file.\n", progName, inName ); setExit(1); return; } if ( /* srcMode == SM_F2F implied && */ cantGuess ) { if (noisy) fprintf ( stderr, "%s: Can't guess original name for %s -- using %s\n", progName, inName, outName ); /* just a warning, no return */ } if ( srcMode == SM_F2F && fileExists ( outName ) ) { if (forceOverwrite) { remove(outName); } else { fprintf ( stderr, "%s: Output file %s already exists.\n", progName, outName ); setExit(1); return; } } if ( srcMode == SM_F2F && !forceOverwrite && (n=countHardLinks ( inName ) ) > 0) { fprintf ( stderr, "%s: Input file %s has %d other link%s.\n", progName, inName, n, n > 1 ? "s" : "" ); setExit(1); return; } if ( srcMode == SM_F2F ) { /* Save the file's meta-info before we open it. Doing it later means we mess up the access times. */ saveInputFileMetaInfo ( inName ); } switch ( srcMode ) { case SM_I2O: inStr = stdin; outStr = stdout; if ( isatty ( fileno ( stdin ) ) ) { fprintf ( stderr, "%s: I won't read compressed data from a terminal.\n", progName ); fprintf ( stderr, "%s: For help, type: `%s --help'.\n", progName, progName ); setExit(1); return; }; break; case SM_F2O: inStr = fopen ( inName, "rb" ); outStr = stdout; if ( inStr == NULL ) { fprintf ( stderr, "%s: Can't open input file %s:%s.\n", progName, inName, strerror(errno) ); if ( inStr != NULL ) fclose ( inStr ); setExit(1); return; }; break; case SM_F2F: inStr = fopen ( inName, "rb" ); outStr = fopen_output_safely ( outName, "wb" ); if ( outStr == NULL) { fprintf ( stderr, "%s: Can't create output file %s: %s.\n", progName, outName, strerror(errno) ); if ( inStr != NULL ) fclose ( inStr ); setExit(1); return; } if ( inStr == NULL ) { fprintf ( stderr, "%s: Can't open input file %s: %s.\n", progName, inName, strerror(errno) ); if ( outStr != NULL ) fclose ( outStr ); setExit(1); return; }; break; default: panic ( "uncompress: bad srcMode" ); break; } if (verbosity >= 1) { fprintf ( stderr, " %s: ", inName ); pad ( inName ); fflush ( stderr ); } /*--- Now the input and output handles are sane. Do the Biz. ---*/ outputHandleJustInCase = outStr; deleteOutputOnInterrupt = True; magicNumberOK = uncompressStream ( inStr, outStr ); outputHandleJustInCase = NULL; /*--- If there was an I/O error, we won't get here. ---*/ if ( magicNumberOK ) { if ( srcMode == SM_F2F ) { applySavedTimeInfoToOutputFile ( outName ); deleteOutputOnInterrupt = False; if ( !keepInputFiles ) { IntNative retVal = remove ( inName ); ERROR_IF_NOT_ZERO ( retVal ); } } } else { unzFailsExist = True; deleteOutputOnInterrupt = False; if ( srcMode == SM_F2F ) { IntNative retVal = remove ( outName ); ERROR_IF_NOT_ZERO ( retVal ); } } deleteOutputOnInterrupt = False; if ( magicNumberOK ) { if (verbosity >= 1) fprintf ( stderr, "done\n" ); } else { setExit(2); if (verbosity >= 1) fprintf ( stderr, "not a bzip2 file.\n" ); else fprintf ( stderr, "%s: %s is not a bzip2 file.\n", progName, inName ); } } /*---------------------------------------------*/ static void testf ( Char *name ) { FILE *inStr; Bool allOK; struct MY_STAT statBuf; deleteOutputOnInterrupt = False; if (name == NULL && srcMode != SM_I2O) panic ( "testf: bad modes\n" ); copyFileName ( outName, (Char*)"(none)" ); switch (srcMode) { case SM_I2O: copyFileName ( inName, (Char*)"(stdin)" ); break; case SM_F2F: copyFileName ( inName, name ); break; case SM_F2O: copyFileName ( inName, name ); break; } if ( srcMode != SM_I2O && containsDubiousChars ( inName ) ) { if (noisy) fprintf ( stderr, "%s: There are no files matching `%s'.\n", progName, inName ); setExit(1); return; } if ( srcMode != SM_I2O && !fileExists ( inName ) ) { fprintf ( stderr, "%s: Can't open input %s: %s.\n", progName, inName, strerror(errno) ); setExit(1); return; } if ( srcMode != SM_I2O ) { MY_STAT(inName, &statBuf); if ( MY_S_ISDIR(statBuf.st_mode) ) { fprintf( stderr, "%s: Input file %s is a directory.\n", progName,inName); setExit(1); return; } } switch ( srcMode ) { case SM_I2O: if ( isatty ( fileno ( stdin ) ) ) { fprintf ( stderr, "%s: I won't read compressed data from a terminal.\n", progName ); fprintf ( stderr, "%s: For help, type: `%s --help'.\n", progName, progName ); setExit(1); return; }; inStr = stdin; break; case SM_F2O: case SM_F2F: inStr = fopen ( inName, "rb" ); if ( inStr == NULL ) { fprintf ( stderr, "%s: Can't open input file %s:%s.\n", progName, inName, strerror(errno) ); setExit(1); return; }; break; default: panic ( "testf: bad srcMode" ); break; } if (verbosity >= 1) { fprintf ( stderr, " %s: ", inName ); pad ( inName ); fflush ( stderr ); } /*--- Now the input handle is sane. Do the Biz. ---*/ outputHandleJustInCase = NULL; allOK = testStream ( inStr ); if (allOK && verbosity >= 1) fprintf ( stderr, "ok\n" ); if (!allOK) testFailsExist = True; } /*---------------------------------------------*/ static void license ( void ) { fprintf ( stderr, "bzip2, a block-sorting file compressor. " "Version %s.\n" " \n" " Copyright (C) 1996-2019 by Julian Seward.\n" " \n" " This program is free software; you can redistribute it and/or modify\n" " it under the terms set out in the LICENSE file, which is included\n" " in the bzip2 source distribution.\n" " \n" " This program is distributed in the hope that it will be useful,\n" " but WITHOUT ANY WARRANTY; without even the implied warranty of\n" " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" " LICENSE file for more details.\n" " \n", BZ2_bzlibVersion() ); } /*---------------------------------------------*/ static void usage ( Char *fullProgName ) { fprintf ( stderr, "bzip2, a block-sorting file compressor. " "Version %s.\n" "\n usage: %s [flags and input files in any order]\n" "\n" " -h --help print this message\n" " -d --decompress force decompression\n" " -z --compress force compression\n" " -k --keep keep (don't delete) input files\n" " -f --force overwrite existing output files\n" " -t --test test compressed file integrity\n" " -c --stdout output to standard out\n" " -q --quiet suppress noncritical error messages\n" " -v --verbose be verbose (a 2nd -v gives more)\n" " -L --license display software version & license\n" " -V --version display software version & license\n" " -s --small use less memory (at most 2500k)\n" " -1 .. -9 set block size to 100k .. 900k\n" " --fast alias for -1\n" " --best alias for -9\n" "\n" " If invoked as `bzip2', default action is to compress.\n" " as `bunzip2', default action is to decompress.\n" " as `bzcat', default action is to decompress to stdout.\n" "\n" " If no file names are given, bzip2 compresses or decompresses\n" " from standard input to standard output. You can combine\n" " short flags, so `-v -4' means the same as -v4 or -4v, &c.\n" # if BZ_UNIX "\n" # endif , BZ2_bzlibVersion(), fullProgName ); } /*---------------------------------------------*/ static void redundant ( Char* flag ) { fprintf ( stderr, "%s: %s is redundant in versions 0.9.5 and above\n", progName, flag ); } /*---------------------------------------------*/ /*-- All the garbage from here to main() is purely to implement a linked list of command-line arguments, into which main() copies argv[1 .. argc-1]. The purpose of this exercise is to facilitate the expansion of wildcard characters * and ? in filenames for OSs which don't know how to do it themselves, like MSDOS, Windows 95 and NT. The actual Dirty Work is done by the platform- specific macro APPEND_FILESPEC. --*/ typedef struct zzzz { Char *name; struct zzzz *link; } Cell; /*---------------------------------------------*/ static void *myMalloc ( Int32 n ) { void* p; p = malloc ( (size_t)n ); if (p == NULL) outOfMemory (); return p; } /*---------------------------------------------*/ static Cell *mkCell ( void ) { Cell *c; c = (Cell*) myMalloc ( sizeof ( Cell ) ); c->name = NULL; c->link = NULL; return c; } /*---------------------------------------------*/ static Cell *snocString ( Cell *root, Char *name ) { if (root == NULL) { Cell *tmp = mkCell(); tmp->name = (Char*) myMalloc ( 5 + strlen(name) ); strcpy ( tmp->name, name ); return tmp; } else { Cell *tmp = root; while (tmp->link != NULL) tmp = tmp->link; tmp->link = snocString ( tmp->link, name ); return root; } } /*---------------------------------------------*/ static void addFlagsFromEnvVar ( Cell** argList, Char* varName ) { Int32 i, j, k; Char *envbase, *p; envbase = getenv(varName); if (envbase != NULL) { p = envbase; i = 0; while (True) { if (p[i] == 0) break; p += i; i = 0; while (isspace((Int32)(p[0]))) p++; while (p[i] != 0 && !isspace((Int32)(p[i]))) i++; if (i > 0) { k = i; if (k > FILE_NAME_LEN-10) k = FILE_NAME_LEN-10; for (j = 0; j < k; j++) tmpName[j] = p[j]; tmpName[k] = 0; APPEND_FLAG(*argList, tmpName); } } } } /*---------------------------------------------*/ #define ISFLAG(s) (strcmp(aa->name, (s))==0) IntNative main ( IntNative argc, Char *argv[] ) { Int32 i, j; Char *tmp; Cell *argList; Cell *aa; Bool decode; /*-- Be really really really paranoid :-) --*/ if (sizeof(Int32) != 4 || sizeof(UInt32) != 4 || sizeof(Int16) != 2 || sizeof(UInt16) != 2 || sizeof(Char) != 1 || sizeof(UChar) != 1) configError(); /*-- Initialise --*/ outputHandleJustInCase = NULL; smallMode = False; keepInputFiles = False; forceOverwrite = False; noisy = True; verbosity = 0; blockSize100k = 9; testFailsExist = False; unzFailsExist = False; numFileNames = 0; numFilesProcessed = 0; workFactor = 30; deleteOutputOnInterrupt = False; exitValue = 0; i = j = 0; /* avoid bogus warning from egcs-1.1.X */ /*-- Set up signal handlers for mem access errors --*/ signal (SIGSEGV, mySIGSEGVorSIGBUScatcher); # if BZ_UNIX # ifndef __DJGPP__ signal (SIGBUS, mySIGSEGVorSIGBUScatcher); # endif # endif copyFileName ( inName, (Char*)"(none)" ); copyFileName ( outName, (Char*)"(none)" ); copyFileName ( progNameReally, argv[0] ); progName = &progNameReally[0]; for (tmp = &progNameReally[0]; *tmp != '\0'; tmp++) if (*tmp == PATH_SEP) progName = tmp + 1; /*-- Copy flags from env var BZIP2, and expand filename wildcards in arg list. --*/ argList = NULL; addFlagsFromEnvVar ( &argList, (Char*)"BZIP2" ); addFlagsFromEnvVar ( &argList, (Char*)"BZIP" ); for (i = 1; i <= argc-1; i++) APPEND_FILESPEC(argList, argv[i]); /*-- Find the length of the longest filename --*/ longestFileName = 7; numFileNames = 0; decode = True; for (aa = argList; aa != NULL; aa = aa->link) { if (ISFLAG("--")) { decode = False; continue; } if (aa->name[0] == '-' && decode) continue; numFileNames++; if (longestFileName < (Int32)strlen(aa->name) ) longestFileName = (Int32)strlen(aa->name); } /*-- Determine source modes; flag handling may change this too. --*/ if (numFileNames == 0) srcMode = SM_I2O; else srcMode = SM_F2F; /*-- Determine what to do (compress/uncompress/test/cat). --*/ /*-- Note that subsequent flag handling may change this. --*/ opMode = OM_Z; if ( (strstr ( progName, "unzip" ) != 0) || (strstr ( progName, "UNZIP" ) != 0) ) opMode = OM_UNZ; if ( (strstr ( progName, "z2cat" ) != 0) || (strstr ( progName, "Z2CAT" ) != 0) || (strstr ( progName, "zcat" ) != 0) || (strstr ( progName, "ZCAT" ) != 0) ) { opMode = OM_UNZ; srcMode = (numFileNames == 0) ? SM_I2O : SM_F2O; } /*-- Look at the flags. --*/ for (aa = argList; aa != NULL; aa = aa->link) { if (ISFLAG("--")) break; if (aa->name[0] == '-' && aa->name[1] != '-') { for (j = 1; aa->name[j] != '\0'; j++) { switch (aa->name[j]) { case 'c': srcMode = SM_F2O; break; case 'd': opMode = OM_UNZ; break; case 'z': opMode = OM_Z; break; case 'f': forceOverwrite = True; break; case 't': opMode = OM_TEST; break; case 'k': keepInputFiles = True; break; case 's': smallMode = True; break; case 'q': noisy = False; break; case '1': blockSize100k = 1; break; case '2': blockSize100k = 2; break; case '3': blockSize100k = 3; break; case '4': blockSize100k = 4; break; case '5': blockSize100k = 5; break; case '6': blockSize100k = 6; break; case '7': blockSize100k = 7; break; case '8': blockSize100k = 8; break; case '9': blockSize100k = 9; break; case 'V': case 'L': license(); break; case 'v': verbosity++; break; case 'h': usage ( progName ); exit ( 0 ); break; default: fprintf ( stderr, "%s: Bad flag `%s'\n", progName, aa->name ); usage ( progName ); exit ( 1 ); break; } } } } /*-- And again ... --*/ for (aa = argList; aa != NULL; aa = aa->link) { if (ISFLAG("--")) break; if (ISFLAG("--stdout")) srcMode = SM_F2O; else if (ISFLAG("--decompress")) opMode = OM_UNZ; else if (ISFLAG("--compress")) opMode = OM_Z; else if (ISFLAG("--force")) forceOverwrite = True; else if (ISFLAG("--test")) opMode = OM_TEST; else if (ISFLAG("--keep")) keepInputFiles = True; else if (ISFLAG("--small")) smallMode = True; else if (ISFLAG("--quiet")) noisy = False; else if (ISFLAG("--version")) license(); else if (ISFLAG("--license")) license(); else if (ISFLAG("--exponential")) workFactor = 1; else if (ISFLAG("--repetitive-best")) redundant(aa->name); else if (ISFLAG("--repetitive-fast")) redundant(aa->name); else if (ISFLAG("--fast")) blockSize100k = 1; else if (ISFLAG("--best")) blockSize100k = 9; else if (ISFLAG("--verbose")) verbosity++; else if (ISFLAG("--help")) { usage ( progName ); exit ( 0 ); } else if (strncmp ( aa->name, "--", 2) == 0) { fprintf ( stderr, "%s: Bad flag `%s'\n", progName, aa->name ); usage ( progName ); exit ( 1 ); } } if (verbosity > 4) verbosity = 4; if (opMode == OM_Z && smallMode && blockSize100k > 2) blockSize100k = 2; if (opMode == OM_TEST && srcMode == SM_F2O) { fprintf ( stderr, "%s: -c and -t cannot be used together.\n", progName ); exit ( 1 ); } if (srcMode == SM_F2O && numFileNames == 0) srcMode = SM_I2O; if (opMode != OM_Z) blockSize100k = 0; if (srcMode == SM_F2F) { signal (SIGINT, mySignalCatcher); signal (SIGTERM, mySignalCatcher); # if BZ_UNIX signal (SIGHUP, mySignalCatcher); # endif } if (opMode == OM_Z) { if (srcMode == SM_I2O) { compress ( NULL ); } else { decode = True; for (aa = argList; aa != NULL; aa = aa->link) { if (ISFLAG("--")) { decode = False; continue; } if (aa->name[0] == '-' && decode) continue; numFilesProcessed++; compress ( aa->name ); } } } else if (opMode == OM_UNZ) { unzFailsExist = False; if (srcMode == SM_I2O) { uncompress ( NULL ); } else { decode = True; for (aa = argList; aa != NULL; aa = aa->link) { if (ISFLAG("--")) { decode = False; continue; } if (aa->name[0] == '-' && decode) continue; numFilesProcessed++; uncompress ( aa->name ); } } if (unzFailsExist) { setExit(2); exit(exitValue); } } else { testFailsExist = False; if (srcMode == SM_I2O) { testf ( NULL ); } else { decode = True; for (aa = argList; aa != NULL; aa = aa->link) { if (ISFLAG("--")) { decode = False; continue; } if (aa->name[0] == '-' && decode) continue; numFilesProcessed++; testf ( aa->name ); } } if (testFailsExist) { if (noisy) { fprintf ( stderr, "\n" "You can use the `bzip2recover' program to attempt to recover\n" "data from undamaged sections of corrupted files.\n\n" ); } setExit(2); exit(exitValue); } } /* Free the argument list memory to mollify leak detectors (eg) Purify, Checker. Serves no other useful purpose. */ aa = argList; while (aa != NULL) { Cell* aa2 = aa->link; if (aa->name != NULL) free(aa->name); free(aa); aa = aa2; } return exitValue; } /*-----------------------------------------------------------*/ /*--- end bzip2.c ---*/ /*-----------------------------------------------------------*/
SPDX-FileCopyrightText: 2021 Andrius Å tikonas <andrius@stikonas.eu> SPDX-FileCopyrightText: 2021 fosslinux <fosslinux@aussies.space> SPDX-License-Identifier: bzip2-1.0.6 Disable all coreutils calls in the Makefile which we do not have at this point of the bootstrap. ranlib can be unconditionally disabled as we do not have it either. diff -r -N -U3 Makefile Makefile --- Makefile 2019-07-13 18:50:05.000000000 +0100 +++ Makefile 2021-01-14 15:45:17.558516299 +0000 @@ -21,7 +21,7 @@ LDFLAGS= BIGFILES=-D_FILE_OFFSET_BITS=64 -CFLAGS=-Wall -Winline -O2 -g $(BIGFILES) +CFLAGS=-Wall -Winline -O2 -g $(BIGFILES) -I . # Where you want it installed when you do 'make install' PREFIX=/usr/local @@ -44,11 +44,6 @@ libbz2.a: $(OBJS) $(AR) cq libbz2.a $(OBJS) - @if ( test -f $(RANLIB) -o -f /usr/bin/ranlib -o \ - -f /bin/ranlib -o -f /usr/ccs/bin/ranlib ) ; then \ - echo $(RANLIB) libbz2.a ; \ - $(RANLIB) libbz2.a ; \ - fi check: test test: bzip2 @@ -114,7 +108,6 @@ sample1.tst sample2.tst sample3.tst blocksort.o: blocksort.c - @cat words0 $(CC) $(CFLAGS) -c blocksort.c huffman.o: huffman.c $(CC) $(CFLAGS) -c huffman.c
# ------------------------------------------------------------------ # This file is part of bzip2/libbzip2, a program and library for # lossless, block-sorting data compression. # # bzip2/libbzip2 version 1.0.8 of 13 July 2019 # Copyright (C) 1996-2019 Julian Seward <jseward@acm.org> # # Please read the WARNING, DISCLAIMER and PATENTS sections in the # README file. # # This program is released under the terms of the license contained # in the file LICENSE. # ------------------------------------------------------------------ SHELL=/bin/sh # To assist in cross-compiling CC=gcc AR=ar RANLIB=ranlib LDFLAGS= BIGFILES=-D_FILE_OFFSET_BITS=64 CFLAGS=-Wall -Winline -O2 -g $(BIGFILES) # Where you want it installed when you do 'make install' PREFIX=/usr/local OBJS= blocksort.o \ huffman.o \ crctable.o \ randtable.o \ compress.o \ decompress.o \ bzlib.o all: libbz2.a bzip2 bzip2recover test bzip2: libbz2.a bzip2.o $(CC) $(CFLAGS) $(LDFLAGS) -o bzip2 bzip2.o -L. -lbz2 bzip2recover: bzip2recover.o $(CC) $(CFLAGS) $(LDFLAGS) -o bzip2recover bzip2recover.o libbz2.a: $(OBJS) rm -f libbz2.a $(AR) cq libbz2.a $(OBJS) @if ( test -f $(RANLIB) -o -f /usr/bin/ranlib -o \ -f /bin/ranlib -o -f /usr/ccs/bin/ranlib ) ; then \ echo $(RANLIB) libbz2.a ; \ $(RANLIB) libbz2.a ; \ fi check: test test: bzip2 @cat words1 ./bzip2 -1 < sample1.ref > sample1.rb2 ./bzip2 -2 < sample2.ref > sample2.rb2 ./bzip2 -3 < sample3.ref > sample3.rb2 ./bzip2 -d < sample1.bz2 > sample1.tst ./bzip2 -d < sample2.bz2 > sample2.tst ./bzip2 -ds < sample3.bz2 > sample3.tst cmp sample1.bz2 sample1.rb2 cmp sample2.bz2 sample2.rb2 cmp sample3.bz2 sample3.rb2 cmp sample1.tst sample1.ref cmp sample2.tst sample2.ref cmp sample3.tst sample3.ref @cat words3 install: bzip2 bzip2recover if ( test ! -d $(PREFIX)/bin ) ; then mkdir -p $(PREFIX)/bin ; fi if ( test ! -d $(PREFIX)/lib ) ; then mkdir -p $(PREFIX)/lib ; fi if ( test ! -d $(PREFIX)/man ) ; then mkdir -p $(PREFIX)/man ; fi if ( test ! -d $(PREFIX)/man/man1 ) ; then mkdir -p $(PREFIX)/man/man1 ; fi if ( test ! -d $(PREFIX)/include ) ; then mkdir -p $(PREFIX)/include ; fi cp -f bzip2 $(PREFIX)/bin/bzip2 cp -f bzip2 $(PREFIX)/bin/bunzip2 cp -f bzip2 $(PREFIX)/bin/bzcat cp -f bzip2recover $(PREFIX)/bin/bzip2recover chmod a+x $(PREFIX)/bin/bzip2 chmod a+x $(PREFIX)/bin/bunzip2 chmod a+x $(PREFIX)/bin/bzcat chmod a+x $(PREFIX)/bin/bzip2recover cp -f bzip2.1 $(PREFIX)/man/man1 chmod a+r $(PREFIX)/man/man1/bzip2.1 cp -f bzlib.h $(PREFIX)/include chmod a+r $(PREFIX)/include/bzlib.h cp -f libbz2.a $(PREFIX)/lib chmod a+r $(PREFIX)/lib/libbz2.a cp -f bzgrep $(PREFIX)/bin/bzgrep ln -s -f $(PREFIX)/bin/bzgrep $(PREFIX)/bin/bzegrep ln -s -f $(PREFIX)/bin/bzgrep $(PREFIX)/bin/bzfgrep chmod a+x $(PREFIX)/bin/bzgrep cp -f bzmore $(PREFIX)/bin/bzmore ln -s -f $(PREFIX)/bin/bzmore $(PREFIX)/bin/bzless chmod a+x $(PREFIX)/bin/bzmore cp -f bzdiff $(PREFIX)/bin/bzdiff ln -s -f $(PREFIX)/bin/bzdiff $(PREFIX)/bin/bzcmp chmod a+x $(PREFIX)/bin/bzdiff cp -f bzgrep.1 bzmore.1 bzdiff.1 $(PREFIX)/man/man1 chmod a+r $(PREFIX)/man/man1/bzgrep.1 chmod a+r $(PREFIX)/man/man1/bzmore.1 chmod a+r $(PREFIX)/man/man1/bzdiff.1 echo ".so man1/bzgrep.1" > $(PREFIX)/man/man1/bzegrep.1 echo ".so man1/bzgrep.1" > $(PREFIX)/man/man1/bzfgrep.1 echo ".so man1/bzmore.1" > $(PREFIX)/man/man1/bzless.1 echo ".so man1/bzdiff.1" > $(PREFIX)/man/man1/bzcmp.1 clean: rm -f *.o libbz2.a bzip2 bzip2recover \ sample1.rb2 sample2.rb2 sample3.rb2 \ sample1.tst sample2.tst sample3.tst blocksort.o: blocksort.c @cat words0 $(CC) $(CFLAGS) -c blocksort.c huffman.o: huffman.c $(CC) $(CFLAGS) -c huffman.c crctable.o: crctable.c $(CC) $(CFLAGS) -c crctable.c randtable.o: randtable.c $(CC) $(CFLAGS) -c randtable.c compress.o: compress.c $(CC) $(CFLAGS) -c compress.c decompress.o: decompress.c $(CC) $(CFLAGS) -c decompress.c bzlib.o: bzlib.c $(CC) $(CFLAGS) -c bzlib.c bzip2.o: bzip2.c $(CC) $(CFLAGS) -c bzip2.c bzip2recover.o: bzip2recover.c $(CC) $(CFLAGS) -c bzip2recover.c distclean: clean rm -f manual.ps manual.html manual.pdf DISTNAME=bzip2-1.0.8 dist: check manual rm -f $(DISTNAME) ln -s -f . $(DISTNAME) tar cvf $(DISTNAME).tar \ $(DISTNAME)/blocksort.c \ $(DISTNAME)/huffman.c \ $(DISTNAME)/crctable.c \ $(DISTNAME)/randtable.c \ $(DISTNAME)/compress.c \ $(DISTNAME)/decompress.c \ $(DISTNAME)/bzlib.c \ $(DISTNAME)/bzip2.c \ $(DISTNAME)/bzip2recover.c \ $(DISTNAME)/bzlib.h \ $(DISTNAME)/bzlib_private.h \ $(DISTNAME)/Makefile \ $(DISTNAME)/LICENSE \ $(DISTNAME)/bzip2.1 \ $(DISTNAME)/bzip2.1.preformatted \ $(DISTNAME)/bzip2.txt \ $(DISTNAME)/words0 \ $(DISTNAME)/words1 \ $(DISTNAME)/words2 \ $(DISTNAME)/words3 \ $(DISTNAME)/sample1.ref \ $(DISTNAME)/sample2.ref \ $(DISTNAME)/sample3.ref \ $(DISTNAME)/sample1.bz2 \ $(DISTNAME)/sample2.bz2 \ $(DISTNAME)/sample3.bz2 \ $(DISTNAME)/dlltest.c \ $(DISTNAME)/manual.html \ $(DISTNAME)/manual.pdf \ $(DISTNAME)/manual.ps \ $(DISTNAME)/README \ $(DISTNAME)/README.COMPILATION.PROBLEMS \ $(DISTNAME)/README.XML.STUFF \ $(DISTNAME)/CHANGES \ $(DISTNAME)/libbz2.def \ $(DISTNAME)/libbz2.dsp \ $(DISTNAME)/dlltest.dsp \ $(DISTNAME)/makefile.msc \ $(DISTNAME)/unzcrash.c \ $(DISTNAME)/spewG.c \ $(DISTNAME)/mk251.c \ $(DISTNAME)/bzdiff \ $(DISTNAME)/bzdiff.1 \ $(DISTNAME)/bzmore \ $(DISTNAME)/bzmore.1 \ $(DISTNAME)/bzgrep \ $(DISTNAME)/bzgrep.1 \ $(DISTNAME)/Makefile-libbz2_so \ $(DISTNAME)/bz-common.xsl \ $(DISTNAME)/bz-fo.xsl \ $(DISTNAME)/bz-html.xsl \ $(DISTNAME)/bzip.css \ $(DISTNAME)/entities.xml \ $(DISTNAME)/manual.xml \ $(DISTNAME)/format.pl \ $(DISTNAME)/xmlproc.sh gzip -v $(DISTNAME).tar # For rebuilding the manual from sources on my SuSE 9.1 box MANUAL_SRCS= bz-common.xsl bz-fo.xsl bz-html.xsl bzip.css \ entities.xml manual.xml manual: manual.html manual.ps manual.pdf manual.ps: $(MANUAL_SRCS) ./xmlproc.sh -ps manual.xml manual.pdf: $(MANUAL_SRCS) ./xmlproc.sh -pdf manual.xml manual.html: $(MANUAL_SRCS) ./xmlproc.sh -html manual.xml
/*-------------------------------------------------------------*/ /*--- Block sorting machinery ---*/ /*--- blocksort.c ---*/ /*-------------------------------------------------------------*/ /* ------------------------------------------------------------------ This file is part of bzip2/libbzip2, a program and library for lossless, block-sorting data compression. bzip2/libbzip2 version 1.0.8 of 13 July 2019 Copyright (C) 1996-2019 Julian Seward <jseward@acm.org> Please read the WARNING, DISCLAIMER and PATENTS sections in the README file. This program is released under the terms of the license contained in the file LICENSE. ------------------------------------------------------------------ */ #include "bzlib_private.h" /*---------------------------------------------*/ /*--- Fallback O(N log(N)^2) sorting ---*/ /*--- algorithm, for repetitive blocks ---*/ /*---------------------------------------------*/ /*---------------------------------------------*/ static __inline__ void fallbackSimpleSort ( UInt32* fmap, UInt32* eclass, Int32 lo, Int32 hi ) { Int32 i, j, tmp; UInt32 ec_tmp; if (lo == hi) return; if (hi - lo > 3) { for ( i = hi-4; i >= lo; i-- ) { tmp = fmap[i]; ec_tmp = eclass[tmp]; for ( j = i+4; j <= hi && ec_tmp > eclass[fmap[j]]; j += 4 ) fmap[j-4] = fmap[j]; fmap[j-4] = tmp; } } for ( i = hi-1; i >= lo; i-- ) { tmp = fmap[i]; ec_tmp = eclass[tmp]; for ( j = i+1; j <= hi && ec_tmp > eclass[fmap[j]]; j++ ) fmap[j-1] = fmap[j]; fmap[j-1] = tmp; } } /*---------------------------------------------*/ #define fswap(zz1, zz2) \ { Int32 zztmp = zz1; zz1 = zz2; zz2 = zztmp; } #define fvswap(zzp1, zzp2, zzn) \ { \ Int32 yyp1 = (zzp1); \ Int32 yyp2 = (zzp2); \ Int32 yyn = (zzn); \ while (yyn > 0) { \ fswap(fmap[yyp1], fmap[yyp2]); \ yyp1++; yyp2++; yyn--; \ } \ } #define fmin(a,b) ((a) < (b)) ? (a) : (b) #define fpush(lz,hz) { stackLo[sp] = lz; \ stackHi[sp] = hz; \ sp++; } #define fpop(lz,hz) { sp--; \ lz = stackLo[sp]; \ hz = stackHi[sp]; } #define FALLBACK_QSORT_SMALL_THRESH 10 #define FALLBACK_QSORT_STACK_SIZE 100 static void fallbackQSort3 ( UInt32* fmap, UInt32* eclass, Int32 loSt, Int32 hiSt ) { Int32 unLo, unHi, ltLo, gtHi, n, m; Int32 sp, lo, hi; UInt32 med, r, r3; Int32 stackLo[FALLBACK_QSORT_STACK_SIZE]; Int32 stackHi[FALLBACK_QSORT_STACK_SIZE]; r = 0; sp = 0; fpush ( loSt, hiSt ); while (sp > 0) { AssertH ( sp < FALLBACK_QSORT_STACK_SIZE - 1, 1004 ); fpop ( lo, hi ); if (hi - lo < FALLBACK_QSORT_SMALL_THRESH) { fallbackSimpleSort ( fmap, eclass, lo, hi ); continue; } /* Random partitioning. Median of 3 sometimes fails to avoid bad cases. Median of 9 seems to help but looks rather expensive. This too seems to work but is cheaper. Guidance for the magic constants 7621 and 32768 is taken from Sedgewick's algorithms book, chapter 35. */ r = ((r * 7621) + 1) % 32768; r3 = r % 3; if (r3 == 0) med = eclass[fmap[lo]]; else if (r3 == 1) med = eclass[fmap[(lo+hi)>>1]]; else med = eclass[fmap[hi]]; unLo = ltLo = lo; unHi = gtHi = hi; while (1) { while (1) { if (unLo > unHi) break; n = (Int32)eclass[fmap[unLo]] - (Int32)med; if (n == 0) { fswap(fmap[unLo], fmap[ltLo]); ltLo++; unLo++; continue; }; if (n > 0) break; unLo++; } while (1) { if (unLo > unHi) break; n = (Int32)eclass[fmap[unHi]] - (Int32)med; if (n == 0) { fswap(fmap[unHi], fmap[gtHi]); gtHi--; unHi--; continue; }; if (n < 0) break; unHi--; } if (unLo > unHi) break; fswap(fmap[unLo], fmap[unHi]); unLo++; unHi--; } AssertD ( unHi == unLo-1, "fallbackQSort3(2)" ); if (gtHi < ltLo) continue; n = fmin(ltLo-lo, unLo-ltLo); fvswap(lo, unLo-n, n); m = fmin(hi-gtHi, gtHi-unHi); fvswap(unLo, hi-m+1, m); n = lo + unLo - ltLo - 1; m = hi - (gtHi - unHi) + 1; if (n - lo > hi - m) { fpush ( lo, n ); fpush ( m, hi ); } else { fpush ( m, hi ); fpush ( lo, n ); } } } #undef fmin #undef fpush #undef fpop #undef fswap #undef fvswap #undef FALLBACK_QSORT_SMALL_THRESH #undef FALLBACK_QSORT_STACK_SIZE /*---------------------------------------------*/ /* Pre: nblock > 0 eclass exists for [0 .. nblock-1] ((UChar*)eclass) [0 .. nblock-1] holds block ptr exists for [0 .. nblock-1] Post: ((UChar*)eclass) [0 .. nblock-1] holds block All other areas of eclass destroyed fmap [0 .. nblock-1] holds sorted order bhtab [ 0 .. 2+(nblock/32) ] destroyed */ #define SET_BH(zz) bhtab[(zz) >> 5] |= ((UInt32)1 << ((zz) & 31)) #define CLEAR_BH(zz) bhtab[(zz) >> 5] &= ~((UInt32)1 << ((zz) & 31)) #define ISSET_BH(zz) (bhtab[(zz) >> 5] & ((UInt32)1 << ((zz) & 31))) #define WORD_BH(zz) bhtab[(zz) >> 5] #define UNALIGNED_BH(zz) ((zz) & 0x01f) static void fallbackSort ( UInt32* fmap, UInt32* eclass, UInt32* bhtab, Int32 nblock, Int32 verb ) { Int32 ftab[257]; Int32 ftabCopy[256]; Int32 H, i, j, k, l, r, cc, cc1; Int32 nNotDone; Int32 nBhtab; UChar* eclass8 = (UChar*)eclass; /*-- Initial 1-char radix sort to generate initial fmap and initial BH bits. --*/ if (verb >= 4) VPrintf0 ( " bucket sorting ...\n" ); for (i = 0; i < 257; i++) ftab[i] = 0; for (i = 0; i < nblock; i++) ftab[eclass8[i]]++; for (i = 0; i < 256; i++) ftabCopy[i] = ftab[i]; for (i = 1; i < 257; i++) ftab[i] += ftab[i-1]; for (i = 0; i < nblock; i++) { j = eclass8[i]; k = ftab[j] - 1; ftab[j] = k; fmap[k] = i; } nBhtab = 2 + (nblock / 32); for (i = 0; i < nBhtab; i++) bhtab[i] = 0; for (i = 0; i < 256; i++) SET_BH(ftab[i]); /*-- Inductively refine the buckets. Kind-of an "exponential radix sort" (!), inspired by the Manber-Myers suffix array construction algorithm. --*/ /*-- set sentinel bits for block-end detection --*/ for (i = 0; i < 32; i++) { SET_BH(nblock + 2*i); CLEAR_BH(nblock + 2*i + 1); } /*-- the log(N) loop --*/ H = 1; while (1) { if (verb >= 4) VPrintf1 ( " depth %6d has ", H ); j = 0; for (i = 0; i < nblock; i++) { if (ISSET_BH(i)) j = i; k = fmap[i] - H; if (k < 0) k += nblock; eclass[k] = j; } nNotDone = 0; r = -1; while (1) { /*-- find the next non-singleton bucket --*/ k = r + 1; while (ISSET_BH(k) && UNALIGNED_BH(k)) k++; if (ISSET_BH(k)) { while (WORD_BH(k) == 0xffffffff) k += 32; while (ISSET_BH(k)) k++; } l = k - 1; if (l >= nblock) break; while (!ISSET_BH(k) && UNALIGNED_BH(k)) k++; if (!ISSET_BH(k)) { while (WORD_BH(k) == 0x00000000) k += 32; while (!ISSET_BH(k)) k++; } r = k - 1; if (r >= nblock) break; /*-- now [l, r] bracket current bucket --*/ if (r > l) { nNotDone += (r - l + 1); fallbackQSort3 ( fmap, eclass, l, r ); /*-- scan bucket and generate header bits-- */ cc = -1; for (i = l; i <= r; i++) { cc1 = eclass[fmap[i]]; if (cc != cc1) { SET_BH(i); cc = cc1; }; } } } if (verb >= 4) VPrintf1 ( "%6d unresolved strings\n", nNotDone ); H *= 2; if (H > nblock || nNotDone == 0) break; } /*-- Reconstruct the original block in eclass8 [0 .. nblock-1], since the previous phase destroyed it. --*/ if (verb >= 4) VPrintf0 ( " reconstructing block ...\n" ); j = 0; for (i = 0; i < nblock; i++) { while (ftabCopy[j] == 0) j++; ftabCopy[j]--; eclass8[fmap[i]] = (UChar)j; } AssertH ( j < 256, 1005 ); } #undef SET_BH #undef CLEAR_BH #undef ISSET_BH #undef WORD_BH #undef UNALIGNED_BH /*---------------------------------------------*/ /*--- The main, O(N^2 log(N)) sorting ---*/ /*--- algorithm. Faster for "normal" ---*/ /*--- non-repetitive blocks. ---*/ /*---------------------------------------------*/ /*---------------------------------------------*/ static __inline__ Bool mainGtU ( UInt32 i1, UInt32 i2, UChar* block, UInt16* quadrant, UInt32 nblock, Int32* budget ) { Int32 k; UChar c1, c2; UInt16 s1, s2; AssertD ( i1 != i2, "mainGtU" ); /* 1 */ c1 = block[i1]; c2 = block[i2]; if (c1 != c2) return (c1 > c2); i1++; i2++; /* 2 */ c1 = block[i1]; c2 = block[i2]; if (c1 != c2) return (c1 > c2); i1++; i2++; /* 3 */ c1 = block[i1]; c2 = block[i2]; if (c1 != c2) return (c1 > c2); i1++; i2++; /* 4 */ c1 = block[i1]; c2 = block[i2]; if (c1 != c2) return (c1 > c2); i1++; i2++; /* 5 */ c1 = block[i1]; c2 = block[i2]; if (c1 != c2) return (c1 > c2); i1++; i2++; /* 6 */ c1 = block[i1]; c2 = block[i2]; if (c1 != c2) return (c1 > c2); i1++; i2++; /* 7 */ c1 = block[i1]; c2 = block[i2]; if (c1 != c2) return (c1 > c2); i1++; i2++; /* 8 */ c1 = block[i1]; c2 = block[i2]; if (c1 != c2) return (c1 > c2); i1++; i2++; /* 9 */ c1 = block[i1]; c2 = block[i2]; if (c1 != c2) return (c1 > c2); i1++; i2++; /* 10 */ c1 = block[i1]; c2 = block[i2]; if (c1 != c2) return (c1 > c2); i1++; i2++; /* 11 */ c1 = block[i1]; c2 = block[i2]; if (c1 != c2) return (c1 > c2); i1++; i2++; /* 12 */ c1 = block[i1]; c2 = block[i2]; if (c1 != c2) return (c1 > c2); i1++; i2++; k = nblock + 8; do { /* 1 */ c1 = block[i1]; c2 = block[i2]; if (c1 != c2) return (c1 > c2); s1 = quadrant[i1]; s2 = quadrant[i2]; if (s1 != s2) return (s1 > s2); i1++; i2++; /* 2 */ c1 = block[i1]; c2 = block[i2]; if (c1 != c2) return (c1 > c2); s1 = quadrant[i1]; s2 = quadrant[i2]; if (s1 != s2) return (s1 > s2); i1++; i2++; /* 3 */ c1 = block[i1]; c2 = block[i2]; if (c1 != c2) return (c1 > c2); s1 = quadrant[i1]; s2 = quadrant[i2]; if (s1 != s2) return (s1 > s2); i1++; i2++; /* 4 */ c1 = block[i1]; c2 = block[i2]; if (c1 != c2) return (c1 > c2); s1 = quadrant[i1]; s2 = quadrant[i2]; if (s1 != s2) return (s1 > s2); i1++; i2++; /* 5 */ c1 = block[i1]; c2 = block[i2]; if (c1 != c2) return (c1 > c2); s1 = quadrant[i1]; s2 = quadrant[i2]; if (s1 != s2) return (s1 > s2); i1++; i2++; /* 6 */ c1 = block[i1]; c2 = block[i2]; if (c1 != c2) return (c1 > c2); s1 = quadrant[i1]; s2 = quadrant[i2]; if (s1 != s2) return (s1 > s2); i1++; i2++; /* 7 */ c1 = block[i1]; c2 = block[i2]; if (c1 != c2) return (c1 > c2); s1 = quadrant[i1]; s2 = quadrant[i2]; if (s1 != s2) return (s1 > s2); i1++; i2++; /* 8 */ c1 = block[i1]; c2 = block[i2]; if (c1 != c2) return (c1 > c2); s1 = quadrant[i1]; s2 = quadrant[i2]; if (s1 != s2) return (s1 > s2); i1++; i2++; if (i1 >= nblock) i1 -= nblock; if (i2 >= nblock) i2 -= nblock; k -= 8; (*budget)--; } while (k >= 0); return False; } /*---------------------------------------------*/ /*-- Knuth's increments seem to work better than Incerpi-Sedgewick here. Possibly because the number of elems to sort is usually small, typically <= 20. --*/ static Int32 incs[14] = { 1, 4, 13, 40, 121, 364, 1093, 3280, 9841, 29524, 88573, 265720, 797161, 2391484 }; static void mainSimpleSort ( UInt32* ptr, UChar* block, UInt16* quadrant, Int32 nblock, Int32 lo, Int32 hi, Int32 d, Int32* budget ) { Int32 i, j, h, bigN, hp; UInt32 v; bigN = hi - lo + 1; if (bigN < 2) return; hp = 0; while (incs[hp] < bigN) hp++; hp--; for (; hp >= 0; hp--) { h = incs[hp]; i = lo + h; while (True) { /*-- copy 1 --*/ if (i > hi) break; v = ptr[i]; j = i; while ( mainGtU ( ptr[j-h]+d, v+d, block, quadrant, nblock, budget ) ) { ptr[j] = ptr[j-h]; j = j - h; if (j <= (lo + h - 1)) break; } ptr[j] = v; i++; /*-- copy 2 --*/ if (i > hi) break; v = ptr[i]; j = i; while ( mainGtU ( ptr[j-h]+d, v+d, block, quadrant, nblock, budget ) ) { ptr[j] = ptr[j-h]; j = j - h; if (j <= (lo + h - 1)) break; } ptr[j] = v; i++; /*-- copy 3 --*/ if (i > hi) break; v = ptr[i]; j = i; while ( mainGtU ( ptr[j-h]+d, v+d, block, quadrant, nblock, budget ) ) { ptr[j] = ptr[j-h]; j = j - h; if (j <= (lo + h - 1)) break; } ptr[j] = v; i++; if (*budget < 0) return; } } } /*---------------------------------------------*/ /*-- The following is an implementation of an elegant 3-way quicksort for strings, described in a paper "Fast Algorithms for Sorting and Searching Strings", by Robert Sedgewick and Jon L. Bentley. --*/ #define mswap(zz1, zz2) \ { Int32 zztmp = zz1; zz1 = zz2; zz2 = zztmp; } #define mvswap(zzp1, zzp2, zzn) \ { \ Int32 yyp1 = (zzp1); \ Int32 yyp2 = (zzp2); \ Int32 yyn = (zzn); \ while (yyn > 0) { \ mswap(ptr[yyp1], ptr[yyp2]); \ yyp1++; yyp2++; yyn--; \ } \ } static __inline__ UChar mmed3 ( UChar a, UChar b, UChar c ) { UChar t; if (a > b) { t = a; a = b; b = t; }; if (b > c) { b = c; if (a > b) b = a; } return b; } #define mmin(a,b) ((a) < (b)) ? (a) : (b) #define mpush(lz,hz,dz) { stackLo[sp] = lz; \ stackHi[sp] = hz; \ stackD [sp] = dz; \ sp++; } #define mpop(lz,hz,dz) { sp--; \ lz = stackLo[sp]; \ hz = stackHi[sp]; \ dz = stackD [sp]; } #define mnextsize(az) (nextHi[az]-nextLo[az]) #define mnextswap(az,bz) \ { Int32 tz; \ tz = nextLo[az]; nextLo[az] = nextLo[bz]; nextLo[bz] = tz; \ tz = nextHi[az]; nextHi[az] = nextHi[bz]; nextHi[bz] = tz; \ tz = nextD [az]; nextD [az] = nextD [bz]; nextD [bz] = tz; } #define MAIN_QSORT_SMALL_THRESH 20 #define MAIN_QSORT_DEPTH_THRESH (BZ_N_RADIX + BZ_N_QSORT) #define MAIN_QSORT_STACK_SIZE 100 static void mainQSort3 ( UInt32* ptr, UChar* block, UInt16* quadrant, Int32 nblock, Int32 loSt, Int32 hiSt, Int32 dSt, Int32* budget ) { Int32 unLo, unHi, ltLo, gtHi, n, m, med; Int32 sp, lo, hi, d; Int32 stackLo[MAIN_QSORT_STACK_SIZE]; Int32 stackHi[MAIN_QSORT_STACK_SIZE]; Int32 stackD [MAIN_QSORT_STACK_SIZE]; Int32 nextLo[3]; Int32 nextHi[3]; Int32 nextD [3]; sp = 0; mpush ( loSt, hiSt, dSt ); while (sp > 0) { AssertH ( sp < MAIN_QSORT_STACK_SIZE - 2, 1001 ); mpop ( lo, hi, d ); if (hi - lo < MAIN_QSORT_SMALL_THRESH || d > MAIN_QSORT_DEPTH_THRESH) { mainSimpleSort ( ptr, block, quadrant, nblock, lo, hi, d, budget ); if (*budget < 0) return; continue; } med = (Int32) mmed3 ( block[ptr[ lo ]+d], block[ptr[ hi ]+d], block[ptr[ (lo+hi)>>1 ]+d] ); unLo = ltLo = lo; unHi = gtHi = hi; while (True) { while (True) { if (unLo > unHi) break; n = ((Int32)block[ptr[unLo]+d]) - med; if (n == 0) { mswap(ptr[unLo], ptr[ltLo]); ltLo++; unLo++; continue; }; if (n > 0) break; unLo++; } while (True) { if (unLo > unHi) break; n = ((Int32)block[ptr[unHi]+d]) - med; if (n == 0) { mswap(ptr[unHi], ptr[gtHi]); gtHi--; unHi--; continue; }; if (n < 0) break; unHi--; } if (unLo > unHi) break; mswap(ptr[unLo], ptr[unHi]); unLo++; unHi--; } AssertD ( unHi == unLo-1, "mainQSort3(2)" ); if (gtHi < ltLo) { mpush(lo, hi, d+1 ); continue; } n = mmin(ltLo-lo, unLo-ltLo); mvswap(lo, unLo-n, n); m = mmin(hi-gtHi, gtHi-unHi); mvswap(unLo, hi-m+1, m); n = lo + unLo - ltLo - 1; m = hi - (gtHi - unHi) + 1; nextLo[0] = lo; nextHi[0] = n; nextD[0] = d; nextLo[1] = m; nextHi[1] = hi; nextD[1] = d; nextLo[2] = n+1; nextHi[2] = m-1; nextD[2] = d+1; if (mnextsize(0) < mnextsize(1)) mnextswap(0,1); if (mnextsize(1) < mnextsize(2)) mnextswap(1,2); if (mnextsize(0) < mnextsize(1)) mnextswap(0,1); AssertD (mnextsize(0) >= mnextsize(1), "mainQSort3(8)" ); AssertD (mnextsize(1) >= mnextsize(2), "mainQSort3(9)" ); mpush (nextLo[0], nextHi[0], nextD[0]); mpush (nextLo[1], nextHi[1], nextD[1]); mpush (nextLo[2], nextHi[2], nextD[2]); } } #undef mswap #undef mvswap #undef mpush #undef mpop #undef mmin #undef mnextsize #undef mnextswap #undef MAIN_QSORT_SMALL_THRESH #undef MAIN_QSORT_DEPTH_THRESH #undef MAIN_QSORT_STACK_SIZE /*---------------------------------------------*/ /* Pre: nblock > N_OVERSHOOT block32 exists for [0 .. nblock-1 +N_OVERSHOOT] ((UChar*)block32) [0 .. nblock-1] holds block ptr exists for [0 .. nblock-1] Post: ((UChar*)block32) [0 .. nblock-1] holds block All other areas of block32 destroyed ftab [0 .. 65536 ] destroyed ptr [0 .. nblock-1] holds sorted order if (*budget < 0), sorting was abandoned */ #define BIGFREQ(b) (ftab[((b)+1) << 8] - ftab[(b) << 8]) #define SETMASK (1 << 21) #define CLEARMASK (~(SETMASK)) static void mainSort ( UInt32* ptr, UChar* block, UInt16* quadrant, UInt32* ftab, Int32 nblock, Int32 verb, Int32* budget ) { Int32 i, j, k, ss, sb; Int32 runningOrder[256]; Bool bigDone[256]; Int32 copyStart[256]; Int32 copyEnd [256]; UChar c1; Int32 numQSorted; UInt16 s; if (verb >= 4) VPrintf0 ( " main sort initialise ...\n" ); /*-- set up the 2-byte frequency table --*/ for (i = 65536; i >= 0; i--) ftab[i] = 0; j = block[0] << 8; i = nblock-1; for (; i >= 3; i -= 4) { quadrant[i] = 0; j = (j >> 8) | ( ((UInt16)block[i]) << 8); ftab[j]++; quadrant[i-1] = 0; j = (j >> 8) | ( ((UInt16)block[i-1]) << 8); ftab[j]++; quadrant[i-2] = 0; j = (j >> 8) | ( ((UInt16)block[i-2]) << 8); ftab[j]++; quadrant[i-3] = 0; j = (j >> 8) | ( ((UInt16)block[i-3]) << 8); ftab[j]++; } for (; i >= 0; i--) { quadrant[i] = 0; j = (j >> 8) | ( ((UInt16)block[i]) << 8); ftab[j]++; } /*-- (emphasises close relationship of block & quadrant) --*/ for (i = 0; i < BZ_N_OVERSHOOT; i++) { block [nblock+i] = block[i]; quadrant[nblock+i] = 0; } if (verb >= 4) VPrintf0 ( " bucket sorting ...\n" ); /*-- Complete the initial radix sort --*/ for (i = 1; i <= 65536; i++) ftab[i] += ftab[i-1]; s = block[0] << 8; i = nblock-1; for (; i >= 3; i -= 4) { s = (s >> 8) | (block[i] << 8); j = ftab[s] -1; ftab[s] = j; ptr[j] = i; s = (s >> 8) | (block[i-1] << 8); j = ftab[s] -1; ftab[s] = j; ptr[j] = i-1; s = (s >> 8) | (block[i-2] << 8); j = ftab[s] -1; ftab[s] = j; ptr[j] = i-2; s = (s >> 8) | (block[i-3] << 8); j = ftab[s] -1; ftab[s] = j; ptr[j] = i-3; } for (; i >= 0; i--) { s = (s >> 8) | (block[i] << 8); j = ftab[s] -1; ftab[s] = j; ptr[j] = i; } /*-- Now ftab contains the first loc of every small bucket. Calculate the running order, from smallest to largest big bucket. --*/ for (i = 0; i <= 255; i++) { bigDone [i] = False; runningOrder[i] = i; } { Int32 vv; Int32 h = 1; do h = 3 * h + 1; while (h <= 256); do { h = h / 3; for (i = h; i <= 255; i++) { vv = runningOrder[i]; j = i; while ( BIGFREQ(runningOrder[j-h]) > BIGFREQ(vv) ) { runningOrder[j] = runningOrder[j-h]; j = j - h; if (j <= (h - 1)) goto zero; } zero: runningOrder[j] = vv; } } while (h != 1); } /*-- The main sorting loop. --*/ numQSorted = 0; for (i = 0; i <= 255; i++) { /*-- Process big buckets, starting with the least full. Basically this is a 3-step process in which we call mainQSort3 to sort the small buckets [ss, j], but also make a big effort to avoid the calls if we can. --*/ ss = runningOrder[i]; /*-- Step 1: Complete the big bucket [ss] by quicksorting any unsorted small buckets [ss, j], for j != ss. Hopefully previous pointer-scanning phases have already completed many of the small buckets [ss, j], so we don't have to sort them at all. --*/ for (j = 0; j <= 255; j++) { if (j != ss) { sb = (ss << 8) + j; if ( ! (ftab[sb] & SETMASK) ) { Int32 lo = ftab[sb] & CLEARMASK; Int32 hi = (ftab[sb+1] & CLEARMASK) - 1; if (hi > lo) { if (verb >= 4) VPrintf4 ( " qsort [0x%x, 0x%x] " "done %d this %d\n", ss, j, numQSorted, hi - lo + 1 ); mainQSort3 ( ptr, block, quadrant, nblock, lo, hi, BZ_N_RADIX, budget ); numQSorted += (hi - lo + 1); if (*budget < 0) return; } } ftab[sb] |= SETMASK; } } AssertH ( !bigDone[ss], 1006 ); /*-- Step 2: Now scan this big bucket [ss] so as to synthesise the sorted order for small buckets [t, ss] for all t, including, magically, the bucket [ss,ss] too. This will avoid doing Real Work in subsequent Step 1's. --*/ { for (j = 0; j <= 255; j++) { copyStart[j] = ftab[(j << 8) + ss] & CLEARMASK; copyEnd [j] = (ftab[(j << 8) + ss + 1] & CLEARMASK) - 1; } for (j = ftab[ss << 8] & CLEARMASK; j < copyStart[ss]; j++) { k = ptr[j]-1; if (k < 0) k += nblock; c1 = block[k]; if (!bigDone[c1]) ptr[ copyStart[c1]++ ] = k; } for (j = (ftab[(ss+1) << 8] & CLEARMASK) - 1; j > copyEnd[ss]; j--) { k = ptr[j]-1; if (k < 0) k += nblock; c1 = block[k]; if (!bigDone[c1]) ptr[ copyEnd[c1]-- ] = k; } } AssertH ( (copyStart[ss]-1 == copyEnd[ss]) || /* Extremely rare case missing in bzip2-1.0.0 and 1.0.1. Necessity for this case is demonstrated by compressing a sequence of approximately 48.5 million of character 251; 1.0.0/1.0.1 will then die here. */ (copyStart[ss] == 0 && copyEnd[ss] == nblock-1), 1007 ) for (j = 0; j <= 255; j++) ftab[(j << 8) + ss] |= SETMASK; /*-- Step 3: The [ss] big bucket is now done. Record this fact, and update the quadrant descriptors. Remember to update quadrants in the overshoot area too, if necessary. The "if (i < 255)" test merely skips this updating for the last bucket processed, since updating for the last bucket is pointless. The quadrant array provides a way to incrementally cache sort orderings, as they appear, so as to make subsequent comparisons in fullGtU() complete faster. For repetitive blocks this makes a big difference (but not big enough to be able to avoid the fallback sorting mechanism, exponential radix sort). The precise meaning is: at all times: for 0 <= i < nblock and 0 <= j <= nblock if block[i] != block[j], then the relative values of quadrant[i] and quadrant[j] are meaningless. else { if quadrant[i] < quadrant[j] then the string starting at i lexicographically precedes the string starting at j else if quadrant[i] > quadrant[j] then the string starting at j lexicographically precedes the string starting at i else the relative ordering of the strings starting at i and j has not yet been determined. } --*/ bigDone[ss] = True; if (i < 255) { Int32 bbStart = ftab[ss << 8] & CLEARMASK; Int32 bbSize = (ftab[(ss+1) << 8] & CLEARMASK) - bbStart; Int32 shifts = 0; while ((bbSize >> shifts) > 65534) shifts++; for (j = bbSize-1; j >= 0; j--) { Int32 a2update = ptr[bbStart + j]; UInt16 qVal = (UInt16)(j >> shifts); quadrant[a2update] = qVal; if (a2update < BZ_N_OVERSHOOT) quadrant[a2update + nblock] = qVal; } AssertH ( ((bbSize-1) >> shifts) <= 65535, 1002 ); } } if (verb >= 4) VPrintf3 ( " %d pointers, %d sorted, %d scanned\n", nblock, numQSorted, nblock - numQSorted ); } #undef BIGFREQ #undef SETMASK #undef CLEARMASK /*---------------------------------------------*/ /* Pre: nblock > 0 arr2 exists for [0 .. nblock-1 +N_OVERSHOOT] ((UChar*)arr2) [0 .. nblock-1] holds block arr1 exists for [0 .. nblock-1] Post: ((UChar*)arr2) [0 .. nblock-1] holds block All other areas of block destroyed ftab [ 0 .. 65536 ] destroyed arr1 [0 .. nblock-1] holds sorted order */ void BZ2_blockSort ( EState* s ) { UInt32* ptr = s->ptr; UChar* block = s->block; UInt32* ftab = s->ftab; Int32 nblock = s->nblock; Int32 verb = s->verbosity; Int32 wfact = s->workFactor; UInt16* quadrant; Int32 budget; Int32 budgetInit; Int32 i; if (nblock < 10000) { fallbackSort ( s->arr1, s->arr2, ftab, nblock, verb ); } else { /* Calculate the location for quadrant, remembering to get the alignment right. Assumes that &(block[0]) is at least 2-byte aligned -- this should be ok since block is really the first section of arr2. */ i = nblock+BZ_N_OVERSHOOT; if (i & 1) i++; quadrant = (UInt16*)(&(block[i])); /* (wfact-1) / 3 puts the default-factor-30 transition point at very roughly the same place as with v0.1 and v0.9.0. Not that it particularly matters any more, since the resulting compressed stream is now the same regardless of whether or not we use the main sort or fallback sort. */ if (wfact < 1 ) wfact = 1; if (wfact > 100) wfact = 100; budgetInit = nblock * ((wfact-1) / 3); budget = budgetInit; mainSort ( ptr, block, quadrant, ftab, nblock, verb, &budget ); if (verb >= 3) VPrintf3 ( " %d work, %d block, ratio %5.2f\n", budgetInit - budget, nblock, (float)(budgetInit - budget) / (float)(nblock==0 ? 1 : nblock) ); if (budget < 0) { if (verb >= 2) VPrintf0 ( " too repetitive; using fallback" " sorting algorithm\n" ); fallbackSort ( s->arr1, s->arr2, ftab, nblock, verb ); } } s->origPtr = -1; for (i = 0; i < s->nblock; i++) if (ptr[i] == 0) { s->origPtr = i; break; }; AssertH( s->origPtr != -1, 1003 ); } /*-------------------------------------------------------------*/ /*--- end blocksort.c ---*/ /*-------------------------------------------------------------*/
/*-------------------------------------------------------------*/ /*--- Private header file for the library. ---*/ /*--- bzlib_private.h ---*/ /*-------------------------------------------------------------*/ /* ------------------------------------------------------------------ This file is part of bzip2/libbzip2, a program and library for lossless, block-sorting data compression. bzip2/libbzip2 version 1.0.8 of 13 July 2019 Copyright (C) 1996-2019 Julian Seward <jseward@acm.org> Please read the WARNING, DISCLAIMER and PATENTS sections in the README file. This program is released under the terms of the license contained in the file LICENSE. ------------------------------------------------------------------ */ #ifndef _BZLIB_PRIVATE_H #define _BZLIB_PRIVATE_H #include <stdlib.h> #ifndef BZ_NO_STDIO #include <stdio.h> #include <ctype.h> #include <string.h> #endif #include "bzlib.h" /*-- General stuff. --*/ #define BZ_VERSION "1.0.8, 13-Jul-2019" typedef char Char; typedef unsigned char Bool; typedef unsigned char UChar; typedef int Int32; typedef unsigned int UInt32; typedef short Int16; typedef unsigned short UInt16; #define True ((Bool)1) #define False ((Bool)0) #ifndef __GNUC__ #define __inline__ /* */ #endif #ifndef BZ_NO_STDIO extern void BZ2_bz__AssertH__fail ( int errcode ); #define AssertH(cond,errcode) \ { if (!(cond)) BZ2_bz__AssertH__fail ( errcode ); } #if BZ_DEBUG #define AssertD(cond,msg) \ { if (!(cond)) { \ fprintf ( stderr, \ "\n\nlibbzip2(debug build): internal error\n\t%s\n", msg );\ exit(1); \ }} #else #define AssertD(cond,msg) /* */ #endif #define VPrintf0(zf) \ fprintf(stderr,zf) #define VPrintf1(zf,za1) \ fprintf(stderr,zf,za1) #define VPrintf2(zf,za1,za2) \ fprintf(stderr,zf,za1,za2) #define VPrintf3(zf,za1,za2,za3) \ fprintf(stderr,zf,za1,za2,za3) #define VPrintf4(zf,za1,za2,za3,za4) \ fprintf(stderr,zf,za1,za2,za3,za4) #define VPrintf5(zf,za1,za2,za3,za4,za5) \ fprintf(stderr,zf,za1,za2,za3,za4,za5) #else extern void bz_internal_error ( int errcode ); #define AssertH(cond,errcode) \ { if (!(cond)) bz_internal_error ( errcode ); } #define AssertD(cond,msg) do { } while (0) #define VPrintf0(zf) do { } while (0) #define VPrintf1(zf,za1) do { } while (0) #define VPrintf2(zf,za1,za2) do { } while (0) #define VPrintf3(zf,za1,za2,za3) do { } while (0) #define VPrintf4(zf,za1,za2,za3,za4) do { } while (0) #define VPrintf5(zf,za1,za2,za3,za4,za5) do { } while (0) #endif #define BZALLOC(nnn) (strm->bzalloc)(strm->opaque,(nnn),1) #define BZFREE(ppp) (strm->bzfree)(strm->opaque,(ppp)) /*-- Header bytes. --*/ #define BZ_HDR_B 0x42 /* 'B' */ #define BZ_HDR_Z 0x5a /* 'Z' */ #define BZ_HDR_h 0x68 /* 'h' */ #define BZ_HDR_0 0x30 /* '0' */ /*-- Constants for the back end. --*/ #define BZ_MAX_ALPHA_SIZE 258 #define BZ_MAX_CODE_LEN 23 #define BZ_RUNA 0 #define BZ_RUNB 1 #define BZ_N_GROUPS 6 #define BZ_G_SIZE 50 #define BZ_N_ITERS 4 #define BZ_MAX_SELECTORS (2 + (900000 / BZ_G_SIZE)) /*-- Stuff for randomising repetitive blocks. --*/ extern Int32 BZ2_rNums[512]; #define BZ_RAND_DECLS \ Int32 rNToGo; \ Int32 rTPos \ #define BZ_RAND_INIT_MASK \ s->rNToGo = 0; \ s->rTPos = 0 \ #define BZ_RAND_MASK ((s->rNToGo == 1) ? 1 : 0) #define BZ_RAND_UPD_MASK \ if (s->rNToGo == 0) { \ s->rNToGo = BZ2_rNums[s->rTPos]; \ s->rTPos++; \ if (s->rTPos == 512) s->rTPos = 0; \ } \ s->rNToGo--; /*-- Stuff for doing CRCs. --*/ extern UInt32 BZ2_crc32Table[256]; #define BZ_INITIALISE_CRC(crcVar) \ { \ crcVar = 0xffffffffL; \ } #define BZ_FINALISE_CRC(crcVar) \ { \ crcVar = ~(crcVar); \ } #define BZ_UPDATE_CRC(crcVar,cha) \ { \ crcVar = (crcVar << 8) ^ \ BZ2_crc32Table[(crcVar >> 24) ^ \ ((UChar)cha)]; \ } /*-- States and modes for compression. --*/ #define BZ_M_IDLE 1 #define BZ_M_RUNNING 2 #define BZ_M_FLUSHING 3 #define BZ_M_FINISHING 4 #define BZ_S_OUTPUT 1 #define BZ_S_INPUT 2 #define BZ_N_RADIX 2 #define BZ_N_QSORT 12 #define BZ_N_SHELL 18 #define BZ_N_OVERSHOOT (BZ_N_RADIX + BZ_N_QSORT + BZ_N_SHELL + 2) /*-- Structure holding all the compression-side stuff. --*/ typedef struct { /* pointer back to the struct bz_stream */ bz_stream* strm; /* mode this stream is in, and whether inputting */ /* or outputting data */ Int32 mode; Int32 state; /* remembers avail_in when flush/finish requested */ UInt32 avail_in_expect; /* for doing the block sorting */ UInt32* arr1; UInt32* arr2; UInt32* ftab; Int32 origPtr; /* aliases for arr1 and arr2 */ UInt32* ptr; UChar* block; UInt16* mtfv; UChar* zbits; /* for deciding when to use the fallback sorting algorithm */ Int32 workFactor; /* run-length-encoding of the input */ UInt32 state_in_ch; Int32 state_in_len; BZ_RAND_DECLS; /* input and output limits and current posns */ Int32 nblock; Int32 nblockMAX; Int32 numZ; Int32 state_out_pos; /* map of bytes used in block */ Int32 nInUse; Bool inUse[256]; UChar unseqToSeq[256]; /* the buffer for bit stream creation */ UInt32 bsBuff; Int32 bsLive; /* block and combined CRCs */ UInt32 blockCRC; UInt32 combinedCRC; /* misc administratium */ Int32 verbosity; Int32 blockNo; Int32 blockSize100k; /* stuff for coding the MTF values */ Int32 nMTF; Int32 mtfFreq [BZ_MAX_ALPHA_SIZE]; UChar selector [BZ_MAX_SELECTORS]; UChar selectorMtf[BZ_MAX_SELECTORS]; UChar len [BZ_N_GROUPS][BZ_MAX_ALPHA_SIZE]; Int32 code [BZ_N_GROUPS][BZ_MAX_ALPHA_SIZE]; Int32 rfreq [BZ_N_GROUPS][BZ_MAX_ALPHA_SIZE]; /* second dimension: only 3 needed; 4 makes index calculations faster */ UInt32 len_pack[BZ_MAX_ALPHA_SIZE][4]; } EState; /*-- externs for compression. --*/ extern void BZ2_blockSort ( EState* ); extern void BZ2_compressBlock ( EState*, Bool ); extern void BZ2_bsInitWrite ( EState* ); extern void BZ2_hbAssignCodes ( Int32*, UChar*, Int32, Int32, Int32 ); extern void BZ2_hbMakeCodeLengths ( UChar*, Int32*, Int32, Int32 ); /*-- states for decompression. --*/ #define BZ_X_IDLE 1 #define BZ_X_OUTPUT 2 #define BZ_X_MAGIC_1 10 #define BZ_X_MAGIC_2 11 #define BZ_X_MAGIC_3 12 #define BZ_X_MAGIC_4 13 #define BZ_X_BLKHDR_1 14 #define BZ_X_BLKHDR_2 15 #define BZ_X_BLKHDR_3 16 #define BZ_X_BLKHDR_4 17 #define BZ_X_BLKHDR_5 18 #define BZ_X_BLKHDR_6 19 #define BZ_X_BCRC_1 20 #define BZ_X_BCRC_2 21 #define BZ_X_BCRC_3 22 #define BZ_X_BCRC_4 23 #define BZ_X_RANDBIT 24 #define BZ_X_ORIGPTR_1 25 #define BZ_X_ORIGPTR_2 26 #define BZ_X_ORIGPTR_3 27 #define BZ_X_MAPPING_1 28 #define BZ_X_MAPPING_2 29 #define BZ_X_SELECTOR_1 30 #define BZ_X_SELECTOR_2 31 #define BZ_X_SELECTOR_3 32 #define BZ_X_CODING_1 33 #define BZ_X_CODING_2 34 #define BZ_X_CODING_3 35 #define BZ_X_MTF_1 36 #define BZ_X_MTF_2 37 #define BZ_X_MTF_3 38 #define BZ_X_MTF_4 39 #define BZ_X_MTF_5 40 #define BZ_X_MTF_6 41 #define BZ_X_ENDHDR_2 42 #define BZ_X_ENDHDR_3 43 #define BZ_X_ENDHDR_4 44 #define BZ_X_ENDHDR_5 45 #define BZ_X_ENDHDR_6 46 #define BZ_X_CCRC_1 47 #define BZ_X_CCRC_2 48 #define BZ_X_CCRC_3 49 #define BZ_X_CCRC_4 50 /*-- Constants for the fast MTF decoder. --*/ #define MTFA_SIZE 4096 #define MTFL_SIZE 16 /*-- Structure holding all the decompression-side stuff. --*/ typedef struct { /* pointer back to the struct bz_stream */ bz_stream* strm; /* state indicator for this stream */ Int32 state; /* for doing the final run-length decoding */ UChar state_out_ch; Int32 state_out_len; Bool blockRandomised; BZ_RAND_DECLS; /* the buffer for bit stream reading */ UInt32 bsBuff; Int32 bsLive; /* misc administratium */ Int32 blockSize100k; Bool smallDecompress; Int32 currBlockNo; Int32 verbosity; /* for undoing the Burrows-Wheeler transform */ Int32 origPtr; UInt32 tPos; Int32 k0; Int32 unzftab[256]; Int32 nblock_used; Int32 cftab[257]; Int32 cftabCopy[257]; /* for undoing the Burrows-Wheeler transform (FAST) */ UInt32 *tt; /* for undoing the Burrows-Wheeler transform (SMALL) */ UInt16 *ll16; UChar *ll4; /* stored and calculated CRCs */ UInt32 storedBlockCRC; UInt32 storedCombinedCRC; UInt32 calculatedBlockCRC; UInt32 calculatedCombinedCRC; /* map of bytes used in block */ Int32 nInUse; Bool inUse[256]; Bool inUse16[16]; UChar seqToUnseq[256]; /* for decoding the MTF values */ UChar mtfa [MTFA_SIZE]; Int32 mtfbase[256 / MTFL_SIZE]; UChar selector [BZ_MAX_SELECTORS]; UChar selectorMtf[BZ_MAX_SELECTORS]; UChar len [BZ_N_GROUPS][BZ_MAX_ALPHA_SIZE]; Int32 limit [BZ_N_GROUPS][BZ_MAX_ALPHA_SIZE]; Int32 base [BZ_N_GROUPS][BZ_MAX_ALPHA_SIZE]; Int32 perm [BZ_N_GROUPS][BZ_MAX_ALPHA_SIZE]; Int32 minLens[BZ_N_GROUPS]; /* save area for scalars in the main decompress code */ Int32 save_i; Int32 save_j; Int32 save_t; Int32 save_alphaSize; Int32 save_nGroups; Int32 save_nSelectors; Int32 save_EOB; Int32 save_groupNo; Int32 save_groupPos; Int32 save_nextSym; Int32 save_nblockMAX; Int32 save_nblock; Int32 save_es; Int32 save_N; Int32 save_curr; Int32 save_zt; Int32 save_zn; Int32 save_zvec; Int32 save_zj; Int32 save_gSel; Int32 save_gMinlen; Int32* save_gLimit; Int32* save_gBase; Int32* save_gPerm; } DState; /*-- Macros for decompression. --*/ #define BZ_GET_FAST(cccc) \ /* c_tPos is unsigned, hence test < 0 is pointless. */ \ if (s->tPos >= (UInt32)100000 * (UInt32)s->blockSize100k) return True; \ s->tPos = s->tt[s->tPos]; \ cccc = (UChar)(s->tPos & 0xff); \ s->tPos >>= 8; #define BZ_GET_FAST_C(cccc) \ /* c_tPos is unsigned, hence test < 0 is pointless. */ \ if (c_tPos >= (UInt32)100000 * (UInt32)ro_blockSize100k) return True; \ c_tPos = c_tt[c_tPos]; \ cccc = (UChar)(c_tPos & 0xff); \ c_tPos >>= 8; #define SET_LL4(i,n) \ { if (((i) & 0x1) == 0) \ s->ll4[(i) >> 1] = (s->ll4[(i) >> 1] & 0xf0) | (n); else \ s->ll4[(i) >> 1] = (s->ll4[(i) >> 1] & 0x0f) | ((n) << 4); \ } #define GET_LL4(i) \ ((((UInt32)(s->ll4[(i) >> 1])) >> (((i) << 2) & 0x4)) & 0xF) #define SET_LL(i,n) \ { s->ll16[i] = (UInt16)(n & 0x0000ffff); \ SET_LL4(i, n >> 16); \ } #define GET_LL(i) \ (((UInt32)s->ll16[i]) | (GET_LL4(i) << 16)) #define BZ_GET_SMALL(cccc) \ /* c_tPos is unsigned, hence test < 0 is pointless. */ \ if (s->tPos >= (UInt32)100000 * (UInt32)s->blockSize100k) return True; \ cccc = BZ2_indexIntoF ( s->tPos, s->cftab ); \ s->tPos = GET_LL(s->tPos); /*-- externs for decompression. --*/ extern Int32 BZ2_indexIntoF ( Int32, Int32* ); extern Int32 BZ2_decompress ( DState* ); extern void BZ2_hbCreateDecodeTables ( Int32*, Int32*, Int32*, UChar*, Int32, Int32, Int32 ); #endif /*-- BZ_NO_STDIO seems to make NULL disappear on some platforms. --*/ #ifdef BZ_NO_STDIO #ifndef NULL #define NULL 0 #endif #endif /*-------------------------------------------------------------*/ /*--- end bzlib_private.h ---*/ /*-------------------------------------------------------------*/
/*-------------------------------------------------------------*/ /*--- Public header file for the library. ---*/ /*--- bzlib.h ---*/ /*-------------------------------------------------------------*/ /* ------------------------------------------------------------------ This file is part of bzip2/libbzip2, a program and library for lossless, block-sorting data compression. bzip2/libbzip2 version 1.0.8 of 13 July 2019 Copyright (C) 1996-2019 Julian Seward <jseward@acm.org> Please read the WARNING, DISCLAIMER and PATENTS sections in the README file. This program is released under the terms of the license contained in the file LICENSE. ------------------------------------------------------------------ */ #ifndef _BZLIB_H #define _BZLIB_H #ifdef __cplusplus extern "C" { #endif #define BZ_RUN 0 #define BZ_FLUSH 1 #define BZ_FINISH 2 #define BZ_OK 0 #define BZ_RUN_OK 1 #define BZ_FLUSH_OK 2 #define BZ_FINISH_OK 3 #define BZ_STREAM_END 4 #define BZ_SEQUENCE_ERROR (-1) #define BZ_PARAM_ERROR (-2) #define BZ_MEM_ERROR (-3) #define BZ_DATA_ERROR (-4) #define BZ_DATA_ERROR_MAGIC (-5) #define BZ_IO_ERROR (-6) #define BZ_UNEXPECTED_EOF (-7) #define BZ_OUTBUFF_FULL (-8) #define BZ_CONFIG_ERROR (-9) typedef struct { char *next_in; unsigned int avail_in; unsigned int total_in_lo32; unsigned int total_in_hi32; char *next_out; unsigned int avail_out; unsigned int total_out_lo32; unsigned int total_out_hi32; void *state; void *(*bzalloc)(void *,int,int); void (*bzfree)(void *,void *); void *opaque; } bz_stream; #ifndef BZ_IMPORT #define BZ_EXPORT #endif #ifndef BZ_NO_STDIO /* Need a definitition for FILE */ #include <stdio.h> #endif #ifdef _WIN32 # include <windows.h> # ifdef small /* windows.h define small to char */ # undef small # endif # ifdef BZ_EXPORT # define BZ_API(func) WINAPI func # define BZ_EXTERN extern # else /* import windows dll dynamically */ # define BZ_API(func) (WINAPI * func) # define BZ_EXTERN # endif #else # define BZ_API(func) func # define BZ_EXTERN extern #endif /*-- Core (low-level) library functions --*/ BZ_EXTERN int BZ_API(BZ2_bzCompressInit) ( bz_stream* strm, int blockSize100k, int verbosity, int workFactor ); BZ_EXTERN int BZ_API(BZ2_bzCompress) ( bz_stream* strm, int action ); BZ_EXTERN int BZ_API(BZ2_bzCompressEnd) ( bz_stream* strm ); BZ_EXTERN int BZ_API(BZ2_bzDecompressInit) ( bz_stream *strm, int verbosity, int small ); BZ_EXTERN int BZ_API(BZ2_bzDecompress) ( bz_stream* strm ); BZ_EXTERN int BZ_API(BZ2_bzDecompressEnd) ( bz_stream *strm ); /*-- High(er) level library functions --*/ #ifndef BZ_NO_STDIO #define BZ_MAX_UNUSED 5000 typedef void BZFILE; BZ_EXTERN BZFILE* BZ_API(BZ2_bzReadOpen) ( int* bzerror, FILE* f, int verbosity, int small, void* unused, int nUnused ); BZ_EXTERN void BZ_API(BZ2_bzReadClose) ( int* bzerror, BZFILE* b ); BZ_EXTERN void BZ_API(BZ2_bzReadGetUnused) ( int* bzerror, BZFILE* b, void** unused, int* nUnused ); BZ_EXTERN int BZ_API(BZ2_bzRead) ( int* bzerror, BZFILE* b, void* buf, int len ); BZ_EXTERN BZFILE* BZ_API(BZ2_bzWriteOpen) ( int* bzerror, FILE* f, int blockSize100k, int verbosity, int workFactor ); BZ_EXTERN void BZ_API(BZ2_bzWrite) ( int* bzerror, BZFILE* b, void* buf, int len ); BZ_EXTERN void BZ_API(BZ2_bzWriteClose) ( int* bzerror, BZFILE* b, int abandon, unsigned int* nbytes_in, unsigned int* nbytes_out ); BZ_EXTERN void BZ_API(BZ2_bzWriteClose64) ( int* bzerror, BZFILE* b, int abandon, unsigned int* nbytes_in_lo32, unsigned int* nbytes_in_hi32, unsigned int* nbytes_out_lo32, unsigned int* nbytes_out_hi32 ); #endif /*-- Utility functions --*/ BZ_EXTERN int BZ_API(BZ2_bzBuffToBuffCompress) ( char* dest, unsigned int* destLen, char* source, unsigned int sourceLen, int blockSize100k, int verbosity, int workFactor ); BZ_EXTERN int BZ_API(BZ2_bzBuffToBuffDecompress) ( char* dest, unsigned int* destLen, char* source, unsigned int sourceLen, int small, int verbosity ); /*-- Code contributed by Yoshioka Tsuneo (tsuneo@rr.iij4u.or.jp) to support better zlib compatibility. This code is not _officially_ part of libbzip2 (yet); I haven't tested it, documented it, or considered the threading-safeness of it. If this code breaks, please contact both Yoshioka and me. --*/ BZ_EXTERN const char * BZ_API(BZ2_bzlibVersion) ( void ); #ifndef BZ_NO_STDIO BZ_EXTERN BZFILE * BZ_API(BZ2_bzopen) ( const char *path, const char *mode ); BZ_EXTERN BZFILE * BZ_API(BZ2_bzdopen) ( int fd, const char *mode ); BZ_EXTERN int BZ_API(BZ2_bzread) ( BZFILE* b, void* buf, int len ); BZ_EXTERN int BZ_API(BZ2_bzwrite) ( BZFILE* b, void* buf, int len ); BZ_EXTERN int BZ_API(BZ2_bzflush) ( BZFILE* b ); BZ_EXTERN void BZ_API(BZ2_bzclose) ( BZFILE* b ); BZ_EXTERN const char * BZ_API(BZ2_bzerror) ( BZFILE *b, int *errnum ); #endif #ifdef __cplusplus } #endif #endif /*-------------------------------------------------------------*/ /*--- end bzlib.h ---*/ /*-------------------------------------------------------------*/
/*-------------------------------------------------------------*/ /*--- Huffman coding low-level stuff ---*/ /*--- huffman.c ---*/ /*-------------------------------------------------------------*/ /* ------------------------------------------------------------------ This file is part of bzip2/libbzip2, a program and library for lossless, block-sorting data compression. bzip2/libbzip2 version 1.0.8 of 13 July 2019 Copyright (C) 1996-2019 Julian Seward <jseward@acm.org> Please read the WARNING, DISCLAIMER and PATENTS sections in the README file. This program is released under the terms of the license contained in the file LICENSE. ------------------------------------------------------------------ */ #include "bzlib_private.h" /*---------------------------------------------------*/ #define WEIGHTOF(zz0) ((zz0) & 0xffffff00) #define DEPTHOF(zz1) ((zz1) & 0x000000ff) #define MYMAX(zz2,zz3) ((zz2) > (zz3) ? (zz2) : (zz3)) #define ADDWEIGHTS(zw1,zw2) \ (WEIGHTOF(zw1)+WEIGHTOF(zw2)) | \ (1 + MYMAX(DEPTHOF(zw1),DEPTHOF(zw2))) #define UPHEAP(z) \ { \ Int32 zz, tmp; \ zz = z; tmp = heap[zz]; \ while (weight[tmp] < weight[heap[zz >> 1]]) { \ heap[zz] = heap[zz >> 1]; \ zz >>= 1; \ } \ heap[zz] = tmp; \ } #define DOWNHEAP(z) \ { \ Int32 zz, yy, tmp; \ zz = z; tmp = heap[zz]; \ while (True) { \ yy = zz << 1; \ if (yy > nHeap) break; \ if (yy < nHeap && \ weight[heap[yy+1]] < weight[heap[yy]]) \ yy++; \ if (weight[tmp] < weight[heap[yy]]) break; \ heap[zz] = heap[yy]; \ zz = yy; \ } \ heap[zz] = tmp; \ } /*---------------------------------------------------*/ void BZ2_hbMakeCodeLengths ( UChar *len, Int32 *freq, Int32 alphaSize, Int32 maxLen ) { /*-- Nodes and heap entries run from 1. Entry 0 for both the heap and nodes is a sentinel. --*/ Int32 nNodes, nHeap, n1, n2, i, j, k; Bool tooLong; Int32 heap [ BZ_MAX_ALPHA_SIZE + 2 ]; Int32 weight [ BZ_MAX_ALPHA_SIZE * 2 ]; Int32 parent [ BZ_MAX_ALPHA_SIZE * 2 ]; for (i = 0; i < alphaSize; i++) weight[i+1] = (freq[i] == 0 ? 1 : freq[i]) << 8; while (True) { nNodes = alphaSize; nHeap = 0; heap[0] = 0; weight[0] = 0; parent[0] = -2; for (i = 1; i <= alphaSize; i++) { parent[i] = -1; nHeap++; heap[nHeap] = i; UPHEAP(nHeap); } AssertH( nHeap < (BZ_MAX_ALPHA_SIZE+2), 2001 ); while (nHeap > 1) { n1 = heap[1]; heap[1] = heap[nHeap]; nHeap--; DOWNHEAP(1); n2 = heap[1]; heap[1] = heap[nHeap]; nHeap--; DOWNHEAP(1); nNodes++; parent[n1] = parent[n2] = nNodes; weight[nNodes] = ADDWEIGHTS(weight[n1], weight[n2]); parent[nNodes] = -1; nHeap++; heap[nHeap] = nNodes; UPHEAP(nHeap); } AssertH( nNodes < (BZ_MAX_ALPHA_SIZE * 2), 2002 ); tooLong = False; for (i = 1; i <= alphaSize; i++) { j = 0; k = i; while (parent[k] >= 0) { k = parent[k]; j++; } len[i-1] = j; if (j > maxLen) tooLong = True; } if (! tooLong) break; /* 17 Oct 04: keep-going condition for the following loop used to be 'i < alphaSize', which missed the last element, theoretically leading to the possibility of the compressor looping. However, this count-scaling step is only needed if one of the generated Huffman code words is longer than maxLen, which up to and including version 1.0.2 was 20 bits, which is extremely unlikely. In version 1.0.3 maxLen was changed to 17 bits, which has minimal effect on compression ratio, but does mean this scaling step is used from time to time, enough to verify that it works. This means that bzip2-1.0.3 and later will only produce Huffman codes with a maximum length of 17 bits. However, in order to preserve backwards compatibility with bitstreams produced by versions pre-1.0.3, the decompressor must still handle lengths of up to 20. */ for (i = 1; i <= alphaSize; i++) { j = weight[i] >> 8; j = 1 + (j / 2); weight[i] = j << 8; } } } /*---------------------------------------------------*/ void BZ2_hbAssignCodes ( Int32 *code, UChar *length, Int32 minLen, Int32 maxLen, Int32 alphaSize ) { Int32 n, vec, i; vec = 0; for (n = minLen; n <= maxLen; n++) { for (i = 0; i < alphaSize; i++) if (length[i] == n) { code[i] = vec; vec++; }; vec <<= 1; } } /*---------------------------------------------------*/ void BZ2_hbCreateDecodeTables ( Int32 *limit, Int32 *base, Int32 *perm, UChar *length, Int32 minLen, Int32 maxLen, Int32 alphaSize ) { Int32 pp, i, j, vec; pp = 0; for (i = minLen; i <= maxLen; i++) for (j = 0; j < alphaSize; j++) if (length[j] == i) { perm[pp] = j; pp++; }; for (i = 0; i < BZ_MAX_CODE_LEN; i++) base[i] = 0; for (i = 0; i < alphaSize; i++) base[length[i]+1]++; for (i = 1; i < BZ_MAX_CODE_LEN; i++) base[i] += base[i-1]; for (i = 0; i < BZ_MAX_CODE_LEN; i++) limit[i] = 0; vec = 0; for (i = minLen; i <= maxLen; i++) { vec += (base[i+1] - base[i]); limit[i] = vec-1; vec <<= 1; } for (i = minLen + 1; i <= maxLen; i++) base[i] = ((limit[i-1] + 1) << 1) - base[i]; } /*-------------------------------------------------------------*/ /*--- end huffman.c ---*/ /*-------------------------------------------------------------*/
/*-------------------------------------------------------------*/ /*--- Table for doing CRCs ---*/ /*--- crctable.c ---*/ /*-------------------------------------------------------------*/ /* ------------------------------------------------------------------ This file is part of bzip2/libbzip2, a program and library for lossless, block-sorting data compression. bzip2/libbzip2 version 1.0.8 of 13 July 2019 Copyright (C) 1996-2019 Julian Seward <jseward@acm.org> Please read the WARNING, DISCLAIMER and PATENTS sections in the README file. This program is released under the terms of the license contained in the file LICENSE. ------------------------------------------------------------------ */ #include "bzlib_private.h" /*-- I think this is an implementation of the AUTODIN-II, Ethernet & FDDI 32-bit CRC standard. Vaguely derived from code by Rob Warnock, in Section 51 of the comp.compression FAQ. --*/ UInt32 BZ2_crc32Table[256] = { /*-- Ugly, innit? --*/ 0x00000000L, 0x04c11db7L, 0x09823b6eL, 0x0d4326d9L, 0x130476dcL, 0x17c56b6bL, 0x1a864db2L, 0x1e475005L, 0x2608edb8L, 0x22c9f00fL, 0x2f8ad6d6L, 0x2b4bcb61L, 0x350c9b64L, 0x31cd86d3L, 0x3c8ea00aL, 0x384fbdbdL, 0x4c11db70L, 0x48d0c6c7L, 0x4593e01eL, 0x4152fda9L, 0x5f15adacL, 0x5bd4b01bL, 0x569796c2L, 0x52568b75L, 0x6a1936c8L, 0x6ed82b7fL, 0x639b0da6L, 0x675a1011L, 0x791d4014L, 0x7ddc5da3L, 0x709f7b7aL, 0x745e66cdL, 0x9823b6e0L, 0x9ce2ab57L, 0x91a18d8eL, 0x95609039L, 0x8b27c03cL, 0x8fe6dd8bL, 0x82a5fb52L, 0x8664e6e5L, 0xbe2b5b58L, 0xbaea46efL, 0xb7a96036L, 0xb3687d81L, 0xad2f2d84L, 0xa9ee3033L, 0xa4ad16eaL, 0xa06c0b5dL, 0xd4326d90L, 0xd0f37027L, 0xddb056feL, 0xd9714b49L, 0xc7361b4cL, 0xc3f706fbL, 0xceb42022L, 0xca753d95L, 0xf23a8028L, 0xf6fb9d9fL, 0xfbb8bb46L, 0xff79a6f1L, 0xe13ef6f4L, 0xe5ffeb43L, 0xe8bccd9aL, 0xec7dd02dL, 0x34867077L, 0x30476dc0L, 0x3d044b19L, 0x39c556aeL, 0x278206abL, 0x23431b1cL, 0x2e003dc5L, 0x2ac12072L, 0x128e9dcfL, 0x164f8078L, 0x1b0ca6a1L, 0x1fcdbb16L, 0x018aeb13L, 0x054bf6a4L, 0x0808d07dL, 0x0cc9cdcaL, 0x7897ab07L, 0x7c56b6b0L, 0x71159069L, 0x75d48ddeL, 0x6b93dddbL, 0x6f52c06cL, 0x6211e6b5L, 0x66d0fb02L, 0x5e9f46bfL, 0x5a5e5b08L, 0x571d7dd1L, 0x53dc6066L, 0x4d9b3063L, 0x495a2dd4L, 0x44190b0dL, 0x40d816baL, 0xaca5c697L, 0xa864db20L, 0xa527fdf9L, 0xa1e6e04eL, 0xbfa1b04bL, 0xbb60adfcL, 0xb6238b25L, 0xb2e29692L, 0x8aad2b2fL, 0x8e6c3698L, 0x832f1041L, 0x87ee0df6L, 0x99a95df3L, 0x9d684044L, 0x902b669dL, 0x94ea7b2aL, 0xe0b41de7L, 0xe4750050L, 0xe9362689L, 0xedf73b3eL, 0xf3b06b3bL, 0xf771768cL, 0xfa325055L, 0xfef34de2L, 0xc6bcf05fL, 0xc27dede8L, 0xcf3ecb31L, 0xcbffd686L, 0xd5b88683L, 0xd1799b34L, 0xdc3abdedL, 0xd8fba05aL, 0x690ce0eeL, 0x6dcdfd59L, 0x608edb80L, 0x644fc637L, 0x7a089632L, 0x7ec98b85L, 0x738aad5cL, 0x774bb0ebL, 0x4f040d56L, 0x4bc510e1L, 0x46863638L, 0x42472b8fL, 0x5c007b8aL, 0x58c1663dL, 0x558240e4L, 0x51435d53L, 0x251d3b9eL, 0x21dc2629L, 0x2c9f00f0L, 0x285e1d47L, 0x36194d42L, 0x32d850f5L, 0x3f9b762cL, 0x3b5a6b9bL, 0x0315d626L, 0x07d4cb91L, 0x0a97ed48L, 0x0e56f0ffL, 0x1011a0faL, 0x14d0bd4dL, 0x19939b94L, 0x1d528623L, 0xf12f560eL, 0xf5ee4bb9L, 0xf8ad6d60L, 0xfc6c70d7L, 0xe22b20d2L, 0xe6ea3d65L, 0xeba91bbcL, 0xef68060bL, 0xd727bbb6L, 0xd3e6a601L, 0xdea580d8L, 0xda649d6fL, 0xc423cd6aL, 0xc0e2d0ddL, 0xcda1f604L, 0xc960ebb3L, 0xbd3e8d7eL, 0xb9ff90c9L, 0xb4bcb610L, 0xb07daba7L, 0xae3afba2L, 0xaafbe615L, 0xa7b8c0ccL, 0xa379dd7bL, 0x9b3660c6L, 0x9ff77d71L, 0x92b45ba8L, 0x9675461fL, 0x8832161aL, 0x8cf30badL, 0x81b02d74L, 0x857130c3L, 0x5d8a9099L, 0x594b8d2eL, 0x5408abf7L, 0x50c9b640L, 0x4e8ee645L, 0x4a4ffbf2L, 0x470cdd2bL, 0x43cdc09cL, 0x7b827d21L, 0x7f436096L, 0x7200464fL, 0x76c15bf8L, 0x68860bfdL, 0x6c47164aL, 0x61043093L, 0x65c52d24L, 0x119b4be9L, 0x155a565eL, 0x18197087L, 0x1cd86d30L, 0x029f3d35L, 0x065e2082L, 0x0b1d065bL, 0x0fdc1becL, 0x3793a651L, 0x3352bbe6L, 0x3e119d3fL, 0x3ad08088L, 0x2497d08dL, 0x2056cd3aL, 0x2d15ebe3L, 0x29d4f654L, 0xc5a92679L, 0xc1683bceL, 0xcc2b1d17L, 0xc8ea00a0L, 0xd6ad50a5L, 0xd26c4d12L, 0xdf2f6bcbL, 0xdbee767cL, 0xe3a1cbc1L, 0xe760d676L, 0xea23f0afL, 0xeee2ed18L, 0xf0a5bd1dL, 0xf464a0aaL, 0xf9278673L, 0xfde69bc4L, 0x89b8fd09L, 0x8d79e0beL, 0x803ac667L, 0x84fbdbd0L, 0x9abc8bd5L, 0x9e7d9662L, 0x933eb0bbL, 0x97ffad0cL, 0xafb010b1L, 0xab710d06L, 0xa6322bdfL, 0xa2f33668L, 0xbcb4666dL, 0xb8757bdaL, 0xb5365d03L, 0xb1f740b4L }; /*-------------------------------------------------------------*/ /*--- end crctable.c ---*/ /*-------------------------------------------------------------*/
/*-------------------------------------------------------------*/ /*--- Table for randomising repetitive blocks ---*/ /*--- randtable.c ---*/ /*-------------------------------------------------------------*/ /* ------------------------------------------------------------------ This file is part of bzip2/libbzip2, a program and library for lossless, block-sorting data compression. bzip2/libbzip2 version 1.0.8 of 13 July 2019 Copyright (C) 1996-2019 Julian Seward <jseward@acm.org> Please read the WARNING, DISCLAIMER and PATENTS sections in the README file. This program is released under the terms of the license contained in the file LICENSE. ------------------------------------------------------------------ */ #include "bzlib_private.h" /*---------------------------------------------*/ Int32 BZ2_rNums[512] = { 619, 720, 127, 481, 931, 816, 813, 233, 566, 247, 985, 724, 205, 454, 863, 491, 741, 242, 949, 214, 733, 859, 335, 708, 621, 574, 73, 654, 730, 472, 419, 436, 278, 496, 867, 210, 399, 680, 480, 51, 878, 465, 811, 169, 869, 675, 611, 697, 867, 561, 862, 687, 507, 283, 482, 129, 807, 591, 733, 623, 150, 238, 59, 379, 684, 877, 625, 169, 643, 105, 170, 607, 520, 932, 727, 476, 693, 425, 174, 647, 73, 122, 335, 530, 442, 853, 695, 249, 445, 515, 909, 545, 703, 919, 874, 474, 882, 500, 594, 612, 641, 801, 220, 162, 819, 984, 589, 513, 495, 799, 161, 604, 958, 533, 221, 400, 386, 867, 600, 782, 382, 596, 414, 171, 516, 375, 682, 485, 911, 276, 98, 553, 163, 354, 666, 933, 424, 341, 533, 870, 227, 730, 475, 186, 263, 647, 537, 686, 600, 224, 469, 68, 770, 919, 190, 373, 294, 822, 808, 206, 184, 943, 795, 384, 383, 461, 404, 758, 839, 887, 715, 67, 618, 276, 204, 918, 873, 777, 604, 560, 951, 160, 578, 722, 79, 804, 96, 409, 713, 940, 652, 934, 970, 447, 318, 353, 859, 672, 112, 785, 645, 863, 803, 350, 139, 93, 354, 99, 820, 908, 609, 772, 154, 274, 580, 184, 79, 626, 630, 742, 653, 282, 762, 623, 680, 81, 927, 626, 789, 125, 411, 521, 938, 300, 821, 78, 343, 175, 128, 250, 170, 774, 972, 275, 999, 639, 495, 78, 352, 126, 857, 956, 358, 619, 580, 124, 737, 594, 701, 612, 669, 112, 134, 694, 363, 992, 809, 743, 168, 974, 944, 375, 748, 52, 600, 747, 642, 182, 862, 81, 344, 805, 988, 739, 511, 655, 814, 334, 249, 515, 897, 955, 664, 981, 649, 113, 974, 459, 893, 228, 433, 837, 553, 268, 926, 240, 102, 654, 459, 51, 686, 754, 806, 760, 493, 403, 415, 394, 687, 700, 946, 670, 656, 610, 738, 392, 760, 799, 887, 653, 978, 321, 576, 617, 626, 502, 894, 679, 243, 440, 680, 879, 194, 572, 640, 724, 926, 56, 204, 700, 707, 151, 457, 449, 797, 195, 791, 558, 945, 679, 297, 59, 87, 824, 713, 663, 412, 693, 342, 606, 134, 108, 571, 364, 631, 212, 174, 643, 304, 329, 343, 97, 430, 751, 497, 314, 983, 374, 822, 928, 140, 206, 73, 263, 980, 736, 876, 478, 430, 305, 170, 514, 364, 692, 829, 82, 855, 953, 676, 246, 369, 970, 294, 750, 807, 827, 150, 790, 288, 923, 804, 378, 215, 828, 592, 281, 565, 555, 710, 82, 896, 831, 547, 261, 524, 462, 293, 465, 502, 56, 661, 821, 976, 991, 658, 869, 905, 758, 745, 193, 768, 550, 608, 933, 378, 286, 215, 979, 792, 961, 61, 688, 793, 644, 986, 403, 106, 366, 905, 644, 372, 567, 466, 434, 645, 210, 389, 550, 919, 135, 780, 773, 635, 389, 707, 100, 626, 958, 165, 504, 920, 176, 193, 713, 857, 265, 203, 50, 668, 108, 645, 990, 626, 197, 510, 357, 358, 850, 858, 364, 936, 638 }; /*-------------------------------------------------------------*/ /*--- end randtable.c ---*/ /*-------------------------------------------------------------*/
/*-------------------------------------------------------------*/ /*--- Compression machinery (not incl block sorting) ---*/ /*--- compress.c ---*/ /*-------------------------------------------------------------*/ /* ------------------------------------------------------------------ This file is part of bzip2/libbzip2, a program and library for lossless, block-sorting data compression. bzip2/libbzip2 version 1.0.8 of 13 July 2019 Copyright (C) 1996-2019 Julian Seward <jseward@acm.org> Please read the WARNING, DISCLAIMER and PATENTS sections in the README file. This program is released under the terms of the license contained in the file LICENSE. ------------------------------------------------------------------ */ /* CHANGES 0.9.0 -- original version. 0.9.0a/b -- no changes in this file. 0.9.0c -- changed setting of nGroups in sendMTFValues() so as to do a bit better on small files */ #include "bzlib_private.h" /*---------------------------------------------------*/ /*--- Bit stream I/O ---*/ /*---------------------------------------------------*/ /*---------------------------------------------------*/ void BZ2_bsInitWrite ( EState* s ) { s->bsLive = 0; s->bsBuff = 0; } /*---------------------------------------------------*/ static void bsFinishWrite ( EState* s ) { while (s->bsLive > 0) { s->zbits[s->numZ] = (UChar)(s->bsBuff >> 24); s->numZ++; s->bsBuff <<= 8; s->bsLive -= 8; } } /*---------------------------------------------------*/ #define bsNEEDW(nz) \ { \ while (s->bsLive >= 8) { \ s->zbits[s->numZ] \ = (UChar)(s->bsBuff >> 24); \ s->numZ++; \ s->bsBuff <<= 8; \ s->bsLive -= 8; \ } \ } /*---------------------------------------------------*/ static __inline__ void bsW ( EState* s, Int32 n, UInt32 v ) { bsNEEDW ( n ); s->bsBuff |= (v << (32 - s->bsLive - n)); s->bsLive += n; } /*---------------------------------------------------*/ static void bsPutUInt32 ( EState* s, UInt32 u ) { bsW ( s, 8, (u >> 24) & 0xffL ); bsW ( s, 8, (u >> 16) & 0xffL ); bsW ( s, 8, (u >> 8) & 0xffL ); bsW ( s, 8, u & 0xffL ); } /*---------------------------------------------------*/ static void bsPutUChar ( EState* s, UChar c ) { bsW( s, 8, (UInt32)c ); } /*---------------------------------------------------*/ /*--- The back end proper ---*/ /*---------------------------------------------------*/ /*---------------------------------------------------*/ static void makeMaps_e ( EState* s ) { Int32 i; s->nInUse = 0; for (i = 0; i < 256; i++) if (s->inUse[i]) { s->unseqToSeq[i] = s->nInUse; s->nInUse++; } } /*---------------------------------------------------*/ static void generateMTFValues ( EState* s ) { UChar yy[256]; Int32 i, j; Int32 zPend; Int32 wr; Int32 EOB; /* After sorting (eg, here), s->arr1 [ 0 .. s->nblock-1 ] holds sorted order, and ((UChar*)s->arr2) [ 0 .. s->nblock-1 ] holds the original block data. The first thing to do is generate the MTF values, and put them in ((UInt16*)s->arr1) [ 0 .. s->nblock-1 ]. Because there are strictly fewer or equal MTF values than block values, ptr values in this area are overwritten with MTF values only when they are no longer needed. The final compressed bitstream is generated into the area starting at (UChar*) (&((UChar*)s->arr2)[s->nblock]) These storage aliases are set up in bzCompressInit(), except for the last one, which is arranged in compressBlock(). */ UInt32* ptr = s->ptr; UChar* block = s->block; UInt16* mtfv = s->mtfv; makeMaps_e ( s ); EOB = s->nInUse+1; for (i = 0; i <= EOB; i++) s->mtfFreq[i] = 0; wr = 0; zPend = 0; for (i = 0; i < s->nInUse; i++) yy[i] = (UChar) i; for (i = 0; i < s->nblock; i++) { UChar ll_i; AssertD ( wr <= i, "generateMTFValues(1)" ); j = ptr[i]-1; if (j < 0) j += s->nblock; ll_i = s->unseqToSeq[block[j]]; AssertD ( ll_i < s->nInUse, "generateMTFValues(2a)" ); if (yy[0] == ll_i) { zPend++; } else { if (zPend > 0) { zPend--; while (True) { if (zPend & 1) { mtfv[wr] = BZ_RUNB; wr++; s->mtfFreq[BZ_RUNB]++; } else { mtfv[wr] = BZ_RUNA; wr++; s->mtfFreq[BZ_RUNA]++; } if (zPend < 2) break; zPend = (zPend - 2) / 2; }; zPend = 0; } { register UChar rtmp; register UChar* ryy_j; register UChar rll_i; rtmp = yy[1]; yy[1] = yy[0]; ryy_j = &(yy[1]); rll_i = ll_i; while ( rll_i != rtmp ) { register UChar rtmp2; ryy_j++; rtmp2 = rtmp; rtmp = *ryy_j; *ryy_j = rtmp2; }; yy[0] = rtmp; j = ryy_j - &(yy[0]); mtfv[wr] = j+1; wr++; s->mtfFreq[j+1]++; } } } if (zPend > 0) { zPend--; while (True) { if (zPend & 1) { mtfv[wr] = BZ_RUNB; wr++; s->mtfFreq[BZ_RUNB]++; } else { mtfv[wr] = BZ_RUNA; wr++; s->mtfFreq[BZ_RUNA]++; } if (zPend < 2) break; zPend = (zPend - 2) / 2; }; zPend = 0; } mtfv[wr] = EOB; wr++; s->mtfFreq[EOB]++; s->nMTF = wr; } /*---------------------------------------------------*/ #define BZ_LESSER_ICOST 0 #define BZ_GREATER_ICOST 15 static void sendMTFValues ( EState* s ) { Int32 v, t, i, j, gs, ge, totc, bt, bc, iter; Int32 nSelectors, alphaSize, minLen, maxLen, selCtr; Int32 nGroups, nBytes; /*-- UChar len [BZ_N_GROUPS][BZ_MAX_ALPHA_SIZE]; is a global since the decoder also needs it. Int32 code[BZ_N_GROUPS][BZ_MAX_ALPHA_SIZE]; Int32 rfreq[BZ_N_GROUPS][BZ_MAX_ALPHA_SIZE]; are also globals only used in this proc. Made global to keep stack frame size small. --*/ UInt16 cost[BZ_N_GROUPS]; Int32 fave[BZ_N_GROUPS]; UInt16* mtfv = s->mtfv; if (s->verbosity >= 3) VPrintf3( " %d in block, %d after MTF & 1-2 coding, " "%d+2 syms in use\n", s->nblock, s->nMTF, s->nInUse ); alphaSize = s->nInUse+2; for (t = 0; t < BZ_N_GROUPS; t++) for (v = 0; v < alphaSize; v++) s->len[t][v] = BZ_GREATER_ICOST; /*--- Decide how many coding tables to use ---*/ AssertH ( s->nMTF > 0, 3001 ); if (s->nMTF < 200) nGroups = 2; else if (s->nMTF < 600) nGroups = 3; else if (s->nMTF < 1200) nGroups = 4; else if (s->nMTF < 2400) nGroups = 5; else nGroups = 6; /*--- Generate an initial set of coding tables ---*/ { Int32 nPart, remF, tFreq, aFreq; nPart = nGroups; remF = s->nMTF; gs = 0; while (nPart > 0) { tFreq = remF / nPart; ge = gs-1; aFreq = 0; while (aFreq < tFreq && ge < alphaSize-1) { ge++; aFreq += s->mtfFreq[ge]; } if (ge > gs && nPart != nGroups && nPart != 1 && ((nGroups-nPart) % 2 == 1)) { aFreq -= s->mtfFreq[ge]; ge--; } if (s->verbosity >= 3) VPrintf5( " initial group %d, [%d .. %d], " "has %d syms (%4.1f%%)\n", nPart, gs, ge, aFreq, (100.0 * (float)aFreq) / (float)(s->nMTF) ); for (v = 0; v < alphaSize; v++) if (v >= gs && v <= ge) s->len[nPart-1][v] = BZ_LESSER_ICOST; else s->len[nPart-1][v] = BZ_GREATER_ICOST; nPart--; gs = ge+1; remF -= aFreq; } } /*--- Iterate up to BZ_N_ITERS times to improve the tables. ---*/ for (iter = 0; iter < BZ_N_ITERS; iter++) { for (t = 0; t < nGroups; t++) fave[t] = 0; for (t = 0; t < nGroups; t++) for (v = 0; v < alphaSize; v++) s->rfreq[t][v] = 0; /*--- Set up an auxiliary length table which is used to fast-track the common case (nGroups == 6). ---*/ if (nGroups == 6) { for (v = 0; v < alphaSize; v++) { s->len_pack[v][0] = (s->len[1][v] << 16) | s->len[0][v]; s->len_pack[v][1] = (s->len[3][v] << 16) | s->len[2][v]; s->len_pack[v][2] = (s->len[5][v] << 16) | s->len[4][v]; } } nSelectors = 0; totc = 0; gs = 0; while (True) { /*--- Set group start & end marks. --*/ if (gs >= s->nMTF) break; ge = gs + BZ_G_SIZE - 1; if (ge >= s->nMTF) ge = s->nMTF-1; /*-- Calculate the cost of this group as coded by each of the coding tables. --*/ for (t = 0; t < nGroups; t++) cost[t] = 0; if (nGroups == 6 && 50 == ge-gs+1) { /*--- fast track the common case ---*/ register UInt32 cost01, cost23, cost45; register UInt16 icv; cost01 = cost23 = cost45 = 0; # define BZ_ITER(nn) \ icv = mtfv[gs+(nn)]; \ cost01 += s->len_pack[icv][0]; \ cost23 += s->len_pack[icv][1]; \ cost45 += s->len_pack[icv][2]; \ BZ_ITER(0); BZ_ITER(1); BZ_ITER(2); BZ_ITER(3); BZ_ITER(4); BZ_ITER(5); BZ_ITER(6); BZ_ITER(7); BZ_ITER(8); BZ_ITER(9); BZ_ITER(10); BZ_ITER(11); BZ_ITER(12); BZ_ITER(13); BZ_ITER(14); BZ_ITER(15); BZ_ITER(16); BZ_ITER(17); BZ_ITER(18); BZ_ITER(19); BZ_ITER(20); BZ_ITER(21); BZ_ITER(22); BZ_ITER(23); BZ_ITER(24); BZ_ITER(25); BZ_ITER(26); BZ_ITER(27); BZ_ITER(28); BZ_ITER(29); BZ_ITER(30); BZ_ITER(31); BZ_ITER(32); BZ_ITER(33); BZ_ITER(34); BZ_ITER(35); BZ_ITER(36); BZ_ITER(37); BZ_ITER(38); BZ_ITER(39); BZ_ITER(40); BZ_ITER(41); BZ_ITER(42); BZ_ITER(43); BZ_ITER(44); BZ_ITER(45); BZ_ITER(46); BZ_ITER(47); BZ_ITER(48); BZ_ITER(49); # undef BZ_ITER cost[0] = cost01 & 0xffff; cost[1] = cost01 >> 16; cost[2] = cost23 & 0xffff; cost[3] = cost23 >> 16; cost[4] = cost45 & 0xffff; cost[5] = cost45 >> 16; } else { /*--- slow version which correctly handles all situations ---*/ for (i = gs; i <= ge; i++) { UInt16 icv = mtfv[i]; for (t = 0; t < nGroups; t++) cost[t] += s->len[t][icv]; } } /*-- Find the coding table which is best for this group, and record its identity in the selector table. --*/ bc = 999999999; bt = -1; for (t = 0; t < nGroups; t++) if (cost[t] < bc) { bc = cost[t]; bt = t; }; totc += bc; fave[bt]++; s->selector[nSelectors] = bt; nSelectors++; /*-- Increment the symbol frequencies for the selected table. --*/ if (nGroups == 6 && 50 == ge-gs+1) { /*--- fast track the common case ---*/ # define BZ_ITUR(nn) s->rfreq[bt][ mtfv[gs+(nn)] ]++ BZ_ITUR(0); BZ_ITUR(1); BZ_ITUR(2); BZ_ITUR(3); BZ_ITUR(4); BZ_ITUR(5); BZ_ITUR(6); BZ_ITUR(7); BZ_ITUR(8); BZ_ITUR(9); BZ_ITUR(10); BZ_ITUR(11); BZ_ITUR(12); BZ_ITUR(13); BZ_ITUR(14); BZ_ITUR(15); BZ_ITUR(16); BZ_ITUR(17); BZ_ITUR(18); BZ_ITUR(19); BZ_ITUR(20); BZ_ITUR(21); BZ_ITUR(22); BZ_ITUR(23); BZ_ITUR(24); BZ_ITUR(25); BZ_ITUR(26); BZ_ITUR(27); BZ_ITUR(28); BZ_ITUR(29); BZ_ITUR(30); BZ_ITUR(31); BZ_ITUR(32); BZ_ITUR(33); BZ_ITUR(34); BZ_ITUR(35); BZ_ITUR(36); BZ_ITUR(37); BZ_ITUR(38); BZ_ITUR(39); BZ_ITUR(40); BZ_ITUR(41); BZ_ITUR(42); BZ_ITUR(43); BZ_ITUR(44); BZ_ITUR(45); BZ_ITUR(46); BZ_ITUR(47); BZ_ITUR(48); BZ_ITUR(49); # undef BZ_ITUR } else { /*--- slow version which correctly handles all situations ---*/ for (i = gs; i <= ge; i++) s->rfreq[bt][ mtfv[i] ]++; } gs = ge+1; } if (s->verbosity >= 3) { VPrintf2 ( " pass %d: size is %d, grp uses are ", iter+1, totc/8 ); for (t = 0; t < nGroups; t++) VPrintf1 ( "%d ", fave[t] ); VPrintf0 ( "\n" ); } /*-- Recompute the tables based on the accumulated frequencies. --*/ /* maxLen was changed from 20 to 17 in bzip2-1.0.3. See comment in huffman.c for details. */ for (t = 0; t < nGroups; t++) BZ2_hbMakeCodeLengths ( &(s->len[t][0]), &(s->rfreq[t][0]), alphaSize, 17 /*20*/ ); } AssertH( nGroups < 8, 3002 ); AssertH( nSelectors < 32768 && nSelectors <= BZ_MAX_SELECTORS, 3003 ); /*--- Compute MTF values for the selectors. ---*/ { UChar pos[BZ_N_GROUPS], ll_i, tmp2, tmp; for (i = 0; i < nGroups; i++) pos[i] = i; for (i = 0; i < nSelectors; i++) { ll_i = s->selector[i]; j = 0; tmp = pos[j]; while ( ll_i != tmp ) { j++; tmp2 = tmp; tmp = pos[j]; pos[j] = tmp2; }; pos[0] = tmp; s->selectorMtf[i] = j; } }; /*--- Assign actual codes for the tables. --*/ for (t = 0; t < nGroups; t++) { minLen = 32; maxLen = 0; for (i = 0; i < alphaSize; i++) { if (s->len[t][i] > maxLen) maxLen = s->len[t][i]; if (s->len[t][i] < minLen) minLen = s->len[t][i]; } AssertH ( !(maxLen > 17 /*20*/ ), 3004 ); AssertH ( !(minLen < 1), 3005 ); BZ2_hbAssignCodes ( &(s->code[t][0]), &(s->len[t][0]), minLen, maxLen, alphaSize ); } /*--- Transmit the mapping table. ---*/ { Bool inUse16[16]; for (i = 0; i < 16; i++) { inUse16[i] = False; for (j = 0; j < 16; j++) if (s->inUse[i * 16 + j]) inUse16[i] = True; } nBytes = s->numZ; for (i = 0; i < 16; i++) if (inUse16[i]) bsW(s,1,1); else bsW(s,1,0); for (i = 0; i < 16; i++) if (inUse16[i]) for (j = 0; j < 16; j++) { if (s->inUse[i * 16 + j]) bsW(s,1,1); else bsW(s,1,0); } if (s->verbosity >= 3) VPrintf1( " bytes: mapping %d, ", s->numZ-nBytes ); } /*--- Now the selectors. ---*/ nBytes = s->numZ; bsW ( s, 3, nGroups ); bsW ( s, 15, nSelectors ); for (i = 0; i < nSelectors; i++) { for (j = 0; j < s->selectorMtf[i]; j++) bsW(s,1,1); bsW(s,1,0); } if (s->verbosity >= 3) VPrintf1( "selectors %d, ", s->numZ-nBytes ); /*--- Now the coding tables. ---*/ nBytes = s->numZ; for (t = 0; t < nGroups; t++) { Int32 curr = s->len[t][0]; bsW ( s, 5, curr ); for (i = 0; i < alphaSize; i++) { while (curr < s->len[t][i]) { bsW(s,2,2); curr++; /* 10 */ }; while (curr > s->len[t][i]) { bsW(s,2,3); curr--; /* 11 */ }; bsW ( s, 1, 0 ); } } if (s->verbosity >= 3) VPrintf1 ( "code lengths %d, ", s->numZ-nBytes ); /*--- And finally, the block data proper ---*/ nBytes = s->numZ; selCtr = 0; gs = 0; while (True) { if (gs >= s->nMTF) break; ge = gs + BZ_G_SIZE - 1; if (ge >= s->nMTF) ge = s->nMTF-1; AssertH ( s->selector[selCtr] < nGroups, 3006 ); if (nGroups == 6 && 50 == ge-gs+1) { /*--- fast track the common case ---*/ UInt16 mtfv_i; UChar* s_len_sel_selCtr = &(s->len[s->selector[selCtr]][0]); Int32* s_code_sel_selCtr = &(s->code[s->selector[selCtr]][0]); # define BZ_ITAH(nn) \ mtfv_i = mtfv[gs+(nn)]; \ bsW ( s, \ s_len_sel_selCtr[mtfv_i], \ s_code_sel_selCtr[mtfv_i] ) BZ_ITAH(0); BZ_ITAH(1); BZ_ITAH(2); BZ_ITAH(3); BZ_ITAH(4); BZ_ITAH(5); BZ_ITAH(6); BZ_ITAH(7); BZ_ITAH(8); BZ_ITAH(9); BZ_ITAH(10); BZ_ITAH(11); BZ_ITAH(12); BZ_ITAH(13); BZ_ITAH(14); BZ_ITAH(15); BZ_ITAH(16); BZ_ITAH(17); BZ_ITAH(18); BZ_ITAH(19); BZ_ITAH(20); BZ_ITAH(21); BZ_ITAH(22); BZ_ITAH(23); BZ_ITAH(24); BZ_ITAH(25); BZ_ITAH(26); BZ_ITAH(27); BZ_ITAH(28); BZ_ITAH(29); BZ_ITAH(30); BZ_ITAH(31); BZ_ITAH(32); BZ_ITAH(33); BZ_ITAH(34); BZ_ITAH(35); BZ_ITAH(36); BZ_ITAH(37); BZ_ITAH(38); BZ_ITAH(39); BZ_ITAH(40); BZ_ITAH(41); BZ_ITAH(42); BZ_ITAH(43); BZ_ITAH(44); BZ_ITAH(45); BZ_ITAH(46); BZ_ITAH(47); BZ_ITAH(48); BZ_ITAH(49); # undef BZ_ITAH } else { /*--- slow version which correctly handles all situations ---*/ for (i = gs; i <= ge; i++) { bsW ( s, s->len [s->selector[selCtr]] [mtfv[i]], s->code [s->selector[selCtr]] [mtfv[i]] ); } } gs = ge+1; selCtr++; } AssertH( selCtr == nSelectors, 3007 ); if (s->verbosity >= 3) VPrintf1( "codes %d\n", s->numZ-nBytes ); } /*---------------------------------------------------*/ void BZ2_compressBlock ( EState* s, Bool is_last_block ) { if (s->nblock > 0) { BZ_FINALISE_CRC ( s->blockCRC ); s->combinedCRC = (s->combinedCRC << 1) | (s->combinedCRC >> 31); s->combinedCRC ^= s->blockCRC; if (s->blockNo > 1) s->numZ = 0; if (s->verbosity >= 2) VPrintf4( " block %d: crc = 0x%08x, " "combined CRC = 0x%08x, size = %d\n", s->blockNo, s->blockCRC, s->combinedCRC, s->nblock ); BZ2_blockSort ( s ); } s->zbits = (UChar*) (&((UChar*)s->arr2)[s->nblock]); /*-- If this is the first block, create the stream header. --*/ if (s->blockNo == 1) { BZ2_bsInitWrite ( s ); bsPutUChar ( s, BZ_HDR_B ); bsPutUChar ( s, BZ_HDR_Z ); bsPutUChar ( s, BZ_HDR_h ); bsPutUChar ( s, (UChar)(BZ_HDR_0 + s->blockSize100k) ); } if (s->nblock > 0) { bsPutUChar ( s, 0x31 ); bsPutUChar ( s, 0x41 ); bsPutUChar ( s, 0x59 ); bsPutUChar ( s, 0x26 ); bsPutUChar ( s, 0x53 ); bsPutUChar ( s, 0x59 ); /*-- Now the block's CRC, so it is in a known place. --*/ bsPutUInt32 ( s, s->blockCRC ); /*-- Now a single bit indicating (non-)randomisation. As of version 0.9.5, we use a better sorting algorithm which makes randomisation unnecessary. So always set the randomised bit to 'no'. Of course, the decoder still needs to be able to handle randomised blocks so as to maintain backwards compatibility with older versions of bzip2. --*/ bsW(s,1,0); bsW ( s, 24, s->origPtr ); generateMTFValues ( s ); sendMTFValues ( s ); } /*-- If this is the last block, add the stream trailer. --*/ if (is_last_block) { bsPutUChar ( s, 0x17 ); bsPutUChar ( s, 0x72 ); bsPutUChar ( s, 0x45 ); bsPutUChar ( s, 0x38 ); bsPutUChar ( s, 0x50 ); bsPutUChar ( s, 0x90 ); bsPutUInt32 ( s, s->combinedCRC ); if (s->verbosity >= 2) VPrintf1( " final combined CRC = 0x%08x\n ", s->combinedCRC ); bsFinishWrite ( s ); } } /*-------------------------------------------------------------*/ /*--- end compress.c ---*/ /*-------------------------------------------------------------*/
/*-------------------------------------------------------------*/ /*--- Decompression machinery ---*/ /*--- decompress.c ---*/ /*-------------------------------------------------------------*/ /* ------------------------------------------------------------------ This file is part of bzip2/libbzip2, a program and library for lossless, block-sorting data compression. bzip2/libbzip2 version 1.0.8 of 13 July 2019 Copyright (C) 1996-2019 Julian Seward <jseward@acm.org> Please read the WARNING, DISCLAIMER and PATENTS sections in the README file. This program is released under the terms of the license contained in the file LICENSE. ------------------------------------------------------------------ */ #include "bzlib_private.h" /*---------------------------------------------------*/ static void makeMaps_d ( DState* s ) { Int32 i; s->nInUse = 0; for (i = 0; i < 256; i++) if (s->inUse[i]) { s->seqToUnseq[s->nInUse] = i; s->nInUse++; } } /*---------------------------------------------------*/ #define RETURN(rrr) \ { retVal = rrr; goto save_state_and_return; }; #define GET_BITS(lll,vvv,nnn) \ case lll: s->state = lll; \ while (True) { \ if (s->bsLive >= nnn) { \ UInt32 v; \ v = (s->bsBuff >> \ (s->bsLive-nnn)) & ((1 << nnn)-1); \ s->bsLive -= nnn; \ vvv = v; \ break; \ } \ if (s->strm->avail_in == 0) RETURN(BZ_OK); \ s->bsBuff \ = (s->bsBuff << 8) | \ ((UInt32) \ (*((UChar*)(s->strm->next_in)))); \ s->bsLive += 8; \ s->strm->next_in++; \ s->strm->avail_in--; \ s->strm->total_in_lo32++; \ if (s->strm->total_in_lo32 == 0) \ s->strm->total_in_hi32++; \ } #define GET_UCHAR(lll,uuu) \ GET_BITS(lll,uuu,8) #define GET_BIT(lll,uuu) \ GET_BITS(lll,uuu,1) /*---------------------------------------------------*/ #define GET_MTF_VAL(label1,label2,lval) \ { \ if (groupPos == 0) { \ groupNo++; \ if (groupNo >= nSelectors) \ RETURN(BZ_DATA_ERROR); \ groupPos = BZ_G_SIZE; \ gSel = s->selector[groupNo]; \ gMinlen = s->minLens[gSel]; \ gLimit = &(s->limit[gSel][0]); \ gPerm = &(s->perm[gSel][0]); \ gBase = &(s->base[gSel][0]); \ } \ groupPos--; \ zn = gMinlen; \ GET_BITS(label1, zvec, zn); \ while (1) { \ if (zn > 20 /* the longest code */) \ RETURN(BZ_DATA_ERROR); \ if (zvec <= gLimit[zn]) break; \ zn++; \ GET_BIT(label2, zj); \ zvec = (zvec << 1) | zj; \ }; \ if (zvec - gBase[zn] < 0 \ || zvec - gBase[zn] >= BZ_MAX_ALPHA_SIZE) \ RETURN(BZ_DATA_ERROR); \ lval = gPerm[zvec - gBase[zn]]; \ } /*---------------------------------------------------*/ Int32 BZ2_decompress ( DState* s ) { UChar uc; Int32 retVal; Int32 minLen, maxLen; bz_stream* strm = s->strm; /* stuff that needs to be saved/restored */ Int32 i; Int32 j; Int32 t; Int32 alphaSize; Int32 nGroups; Int32 nSelectors; Int32 EOB; Int32 groupNo; Int32 groupPos; Int32 nextSym; Int32 nblockMAX; Int32 nblock; Int32 es; Int32 N; Int32 curr; Int32 zt; Int32 zn; Int32 zvec; Int32 zj; Int32 gSel; Int32 gMinlen; Int32* gLimit; Int32* gBase; Int32* gPerm; if (s->state == BZ_X_MAGIC_1) { /*initialise the save area*/ s->save_i = 0; s->save_j = 0; s->save_t = 0; s->save_alphaSize = 0; s->save_nGroups = 0; s->save_nSelectors = 0; s->save_EOB = 0; s->save_groupNo = 0; s->save_groupPos = 0; s->save_nextSym = 0; s->save_nblockMAX = 0; s->save_nblock = 0; s->save_es = 0; s->save_N = 0; s->save_curr = 0; s->save_zt = 0; s->save_zn = 0; s->save_zvec = 0; s->save_zj = 0; s->save_gSel = 0; s->save_gMinlen = 0; s->save_gLimit = NULL; s->save_gBase = NULL; s->save_gPerm = NULL; } /*restore from the save area*/ i = s->save_i; j = s->save_j; t = s->save_t; alphaSize = s->save_alphaSize; nGroups = s->save_nGroups; nSelectors = s->save_nSelectors; EOB = s->save_EOB; groupNo = s->save_groupNo; groupPos = s->save_groupPos; nextSym = s->save_nextSym; nblockMAX = s->save_nblockMAX; nblock = s->save_nblock; es = s->save_es; N = s->save_N; curr = s->save_curr; zt = s->save_zt; zn = s->save_zn; zvec = s->save_zvec; zj = s->save_zj; gSel = s->save_gSel; gMinlen = s->save_gMinlen; gLimit = s->save_gLimit; gBase = s->save_gBase; gPerm = s->save_gPerm; retVal = BZ_OK; switch (s->state) { GET_UCHAR(BZ_X_MAGIC_1, uc); if (uc != BZ_HDR_B) RETURN(BZ_DATA_ERROR_MAGIC); GET_UCHAR(BZ_X_MAGIC_2, uc); if (uc != BZ_HDR_Z) RETURN(BZ_DATA_ERROR_MAGIC); GET_UCHAR(BZ_X_MAGIC_3, uc) if (uc != BZ_HDR_h) RETURN(BZ_DATA_ERROR_MAGIC); GET_BITS(BZ_X_MAGIC_4, s->blockSize100k, 8) if (s->blockSize100k < (BZ_HDR_0 + 1) || s->blockSize100k > (BZ_HDR_0 + 9)) RETURN(BZ_DATA_ERROR_MAGIC); s->blockSize100k -= BZ_HDR_0; if (s->smallDecompress) { s->ll16 = BZALLOC( s->blockSize100k * 100000 * sizeof(UInt16) ); s->ll4 = BZALLOC( ((1 + s->blockSize100k * 100000) >> 1) * sizeof(UChar) ); if (s->ll16 == NULL || s->ll4 == NULL) RETURN(BZ_MEM_ERROR); } else { s->tt = BZALLOC( s->blockSize100k * 100000 * sizeof(Int32) ); if (s->tt == NULL) RETURN(BZ_MEM_ERROR); } GET_UCHAR(BZ_X_BLKHDR_1, uc); if (uc == 0x17) goto endhdr_2; if (uc != 0x31) RETURN(BZ_DATA_ERROR); GET_UCHAR(BZ_X_BLKHDR_2, uc); if (uc != 0x41) RETURN(BZ_DATA_ERROR); GET_UCHAR(BZ_X_BLKHDR_3, uc); if (uc != 0x59) RETURN(BZ_DATA_ERROR); GET_UCHAR(BZ_X_BLKHDR_4, uc); if (uc != 0x26) RETURN(BZ_DATA_ERROR); GET_UCHAR(BZ_X_BLKHDR_5, uc); if (uc != 0x53) RETURN(BZ_DATA_ERROR); GET_UCHAR(BZ_X_BLKHDR_6, uc); if (uc != 0x59) RETURN(BZ_DATA_ERROR); s->currBlockNo++; if (s->verbosity >= 2) VPrintf1 ( "\n [%d: huff+mtf ", s->currBlockNo ); s->storedBlockCRC = 0; GET_UCHAR(BZ_X_BCRC_1, uc); s->storedBlockCRC = (s->storedBlockCRC << 8) | ((UInt32)uc); GET_UCHAR(BZ_X_BCRC_2, uc); s->storedBlockCRC = (s->storedBlockCRC << 8) | ((UInt32)uc); GET_UCHAR(BZ_X_BCRC_3, uc); s->storedBlockCRC = (s->storedBlockCRC << 8) | ((UInt32)uc); GET_UCHAR(BZ_X_BCRC_4, uc); s->storedBlockCRC = (s->storedBlockCRC << 8) | ((UInt32)uc); GET_BITS(BZ_X_RANDBIT, s->blockRandomised, 1); s->origPtr = 0; GET_UCHAR(BZ_X_ORIGPTR_1, uc); s->origPtr = (s->origPtr << 8) | ((Int32)uc); GET_UCHAR(BZ_X_ORIGPTR_2, uc); s->origPtr = (s->origPtr << 8) | ((Int32)uc); GET_UCHAR(BZ_X_ORIGPTR_3, uc); s->origPtr = (s->origPtr << 8) | ((Int32)uc); if (s->origPtr < 0) RETURN(BZ_DATA_ERROR); if (s->origPtr > 10 + 100000*s->blockSize100k) RETURN(BZ_DATA_ERROR); /*--- Receive the mapping table ---*/ for (i = 0; i < 16; i++) { GET_BIT(BZ_X_MAPPING_1, uc); if (uc == 1) s->inUse16[i] = True; else s->inUse16[i] = False; } for (i = 0; i < 256; i++) s->inUse[i] = False; for (i = 0; i < 16; i++) if (s->inUse16[i]) for (j = 0; j < 16; j++) { GET_BIT(BZ_X_MAPPING_2, uc); if (uc == 1) s->inUse[i * 16 + j] = True; } makeMaps_d ( s ); if (s->nInUse == 0) RETURN(BZ_DATA_ERROR); alphaSize = s->nInUse+2; /*--- Now the selectors ---*/ GET_BITS(BZ_X_SELECTOR_1, nGroups, 3); if (nGroups < 2 || nGroups > BZ_N_GROUPS) RETURN(BZ_DATA_ERROR); GET_BITS(BZ_X_SELECTOR_2, nSelectors, 15); if (nSelectors < 1) RETURN(BZ_DATA_ERROR); for (i = 0; i < nSelectors; i++) { j = 0; while (True) { GET_BIT(BZ_X_SELECTOR_3, uc); if (uc == 0) break; j++; if (j >= nGroups) RETURN(BZ_DATA_ERROR); } /* Having more than BZ_MAX_SELECTORS doesn't make much sense since they will never be used, but some implementations might "round up" the number of selectors, so just ignore those. */ if (i < BZ_MAX_SELECTORS) s->selectorMtf[i] = j; } if (nSelectors > BZ_MAX_SELECTORS) nSelectors = BZ_MAX_SELECTORS; /*--- Undo the MTF values for the selectors. ---*/ { UChar pos[BZ_N_GROUPS], tmp, v; for (v = 0; v < nGroups; v++) pos[v] = v; for (i = 0; i < nSelectors; i++) { v = s->selectorMtf[i]; tmp = pos[v]; while (v > 0) { pos[v] = pos[v-1]; v--; } pos[0] = tmp; s->selector[i] = tmp; } } /*--- Now the coding tables ---*/ for (t = 0; t < nGroups; t++) { GET_BITS(BZ_X_CODING_1, curr, 5); for (i = 0; i < alphaSize; i++) { while (True) { if (curr < 1 || curr > 20) RETURN(BZ_DATA_ERROR); GET_BIT(BZ_X_CODING_2, uc); if (uc == 0) break; GET_BIT(BZ_X_CODING_3, uc); if (uc == 0) curr++; else curr--; } s->len[t][i] = curr; } } /*--- Create the Huffman decoding tables ---*/ for (t = 0; t < nGroups; t++) { minLen = 32; maxLen = 0; for (i = 0; i < alphaSize; i++) { if (s->len[t][i] > maxLen) maxLen = s->len[t][i]; if (s->len[t][i] < minLen) minLen = s->len[t][i]; } BZ2_hbCreateDecodeTables ( &(s->limit[t][0]), &(s->base[t][0]), &(s->perm[t][0]), &(s->len[t][0]), minLen, maxLen, alphaSize ); s->minLens[t] = minLen; } /*--- Now the MTF values ---*/ EOB = s->nInUse+1; nblockMAX = 100000 * s->blockSize100k; groupNo = -1; groupPos = 0; for (i = 0; i <= 255; i++) s->unzftab[i] = 0; /*-- MTF init --*/ { Int32 ii, jj, kk; kk = MTFA_SIZE-1; for (ii = 256 / MTFL_SIZE - 1; ii >= 0; ii--) { for (jj = MTFL_SIZE-1; jj >= 0; jj--) { s->mtfa[kk] = (UChar)(ii * MTFL_SIZE + jj); kk--; } s->mtfbase[ii] = kk + 1; } } /*-- end MTF init --*/ nblock = 0; GET_MTF_VAL(BZ_X_MTF_1, BZ_X_MTF_2, nextSym); while (True) { if (nextSym == EOB) break; if (nextSym == BZ_RUNA || nextSym == BZ_RUNB) { es = -1; N = 1; do { /* Check that N doesn't get too big, so that es doesn't go negative. The maximum value that can be RUNA/RUNB encoded is equal to the block size (post the initial RLE), viz, 900k, so bounding N at 2 million should guard against overflow without rejecting any legitimate inputs. */ if (N >= 2*1024*1024) RETURN(BZ_DATA_ERROR); if (nextSym == BZ_RUNA) es = es + (0+1) * N; else if (nextSym == BZ_RUNB) es = es + (1+1) * N; N = N * 2; GET_MTF_VAL(BZ_X_MTF_3, BZ_X_MTF_4, nextSym); } while (nextSym == BZ_RUNA || nextSym == BZ_RUNB); es++; uc = s->seqToUnseq[ s->mtfa[s->mtfbase[0]] ]; s->unzftab[uc] += es; if (s->smallDecompress) while (es > 0) { if (nblock >= nblockMAX) RETURN(BZ_DATA_ERROR); s->ll16[nblock] = (UInt16)uc; nblock++; es--; } else while (es > 0) { if (nblock >= nblockMAX) RETURN(BZ_DATA_ERROR); s->tt[nblock] = (UInt32)uc; nblock++; es--; }; continue; } else { if (nblock >= nblockMAX) RETURN(BZ_DATA_ERROR); /*-- uc = MTF ( nextSym-1 ) --*/ { Int32 ii, jj, kk, pp, lno, off; UInt32 nn; nn = (UInt32)(nextSym - 1); if (nn < MTFL_SIZE) { /* avoid general-case expense */ pp = s->mtfbase[0]; uc = s->mtfa[pp+nn]; while (nn > 3) { Int32 z = pp+nn; s->mtfa[(z) ] = s->mtfa[(z)-1]; s->mtfa[(z)-1] = s->mtfa[(z)-2]; s->mtfa[(z)-2] = s->mtfa[(z)-3]; s->mtfa[(z)-3] = s->mtfa[(z)-4]; nn -= 4; } while (nn > 0) { s->mtfa[(pp+nn)] = s->mtfa[(pp+nn)-1]; nn--; }; s->mtfa[pp] = uc; } else { /* general case */ lno = nn / MTFL_SIZE; off = nn % MTFL_SIZE; pp = s->mtfbase[lno] + off; uc = s->mtfa[pp]; while (pp > s->mtfbase[lno]) { s->mtfa[pp] = s->mtfa[pp-1]; pp--; }; s->mtfbase[lno]++; while (lno > 0) { s->mtfbase[lno]--; s->mtfa[s->mtfbase[lno]] = s->mtfa[s->mtfbase[lno-1] + MTFL_SIZE - 1]; lno--; } s->mtfbase[0]--; s->mtfa[s->mtfbase[0]] = uc; if (s->mtfbase[0] == 0) { kk = MTFA_SIZE-1; for (ii = 256 / MTFL_SIZE-1; ii >= 0; ii--) { for (jj = MTFL_SIZE-1; jj >= 0; jj--) { s->mtfa[kk] = s->mtfa[s->mtfbase[ii] + jj]; kk--; } s->mtfbase[ii] = kk + 1; } } } } /*-- end uc = MTF ( nextSym-1 ) --*/ s->unzftab[s->seqToUnseq[uc]]++; if (s->smallDecompress) s->ll16[nblock] = (UInt16)(s->seqToUnseq[uc]); else s->tt[nblock] = (UInt32)(s->seqToUnseq[uc]); nblock++; GET_MTF_VAL(BZ_X_MTF_5, BZ_X_MTF_6, nextSym); continue; } } /* Now we know what nblock is, we can do a better sanity check on s->origPtr. */ if (s->origPtr < 0 || s->origPtr >= nblock) RETURN(BZ_DATA_ERROR); /*-- Set up cftab to facilitate generation of T^(-1) --*/ /* Check: unzftab entries in range. */ for (i = 0; i <= 255; i++) { if (s->unzftab[i] < 0 || s->unzftab[i] > nblock) RETURN(BZ_DATA_ERROR); } /* Actually generate cftab. */ s->cftab[0] = 0; for (i = 1; i <= 256; i++) s->cftab[i] = s->unzftab[i-1]; for (i = 1; i <= 256; i++) s->cftab[i] += s->cftab[i-1]; /* Check: cftab entries in range. */ for (i = 0; i <= 256; i++) { if (s->cftab[i] < 0 || s->cftab[i] > nblock) { /* s->cftab[i] can legitimately be == nblock */ RETURN(BZ_DATA_ERROR); } } /* Check: cftab entries non-descending. */ for (i = 1; i <= 256; i++) { if (s->cftab[i-1] > s->cftab[i]) { RETURN(BZ_DATA_ERROR); } } s->state_out_len = 0; s->state_out_ch = 0; BZ_INITIALISE_CRC ( s->calculatedBlockCRC ); s->state = BZ_X_OUTPUT; if (s->verbosity >= 2) VPrintf0 ( "rt+rld" ); if (s->smallDecompress) { /*-- Make a copy of cftab, used in generation of T --*/ for (i = 0; i <= 256; i++) s->cftabCopy[i] = s->cftab[i]; /*-- compute the T vector --*/ for (i = 0; i < nblock; i++) { uc = (UChar)(s->ll16[i]); SET_LL(i, s->cftabCopy[uc]); s->cftabCopy[uc]++; } /*-- Compute T^(-1) by pointer reversal on T --*/ i = s->origPtr; j = GET_LL(i); do { Int32 tmp = GET_LL(j); SET_LL(j, i); i = j; j = tmp; } while (i != s->origPtr); s->tPos = s->origPtr; s->nblock_used = 0; if (s->blockRandomised) { BZ_RAND_INIT_MASK; BZ_GET_SMALL(s->k0); s->nblock_used++; BZ_RAND_UPD_MASK; s->k0 ^= BZ_RAND_MASK; } else { BZ_GET_SMALL(s->k0); s->nblock_used++; } } else { /*-- compute the T^(-1) vector --*/ for (i = 0; i < nblock; i++) { uc = (UChar)(s->tt[i] & 0xff); s->tt[s->cftab[uc]] |= (i << 8); s->cftab[uc]++; } s->tPos = s->tt[s->origPtr] >> 8; s->nblock_used = 0; if (s->blockRandomised) { BZ_RAND_INIT_MASK; BZ_GET_FAST(s->k0); s->nblock_used++; BZ_RAND_UPD_MASK; s->k0 ^= BZ_RAND_MASK; } else { BZ_GET_FAST(s->k0); s->nblock_used++; } } RETURN(BZ_OK); endhdr_2: GET_UCHAR(BZ_X_ENDHDR_2, uc); if (uc != 0x72) RETURN(BZ_DATA_ERROR); GET_UCHAR(BZ_X_ENDHDR_3, uc); if (uc != 0x45) RETURN(BZ_DATA_ERROR); GET_UCHAR(BZ_X_ENDHDR_4, uc); if (uc != 0x38) RETURN(BZ_DATA_ERROR); GET_UCHAR(BZ_X_ENDHDR_5, uc); if (uc != 0x50) RETURN(BZ_DATA_ERROR); GET_UCHAR(BZ_X_ENDHDR_6, uc); if (uc != 0x90) RETURN(BZ_DATA_ERROR); s->storedCombinedCRC = 0; GET_UCHAR(BZ_X_CCRC_1, uc); s->storedCombinedCRC = (s->storedCombinedCRC << 8) | ((UInt32)uc); GET_UCHAR(BZ_X_CCRC_2, uc); s->storedCombinedCRC = (s->storedCombinedCRC << 8) | ((UInt32)uc); GET_UCHAR(BZ_X_CCRC_3, uc); s->storedCombinedCRC = (s->storedCombinedCRC << 8) | ((UInt32)uc); GET_UCHAR(BZ_X_CCRC_4, uc); s->storedCombinedCRC = (s->storedCombinedCRC << 8) | ((UInt32)uc); s->state = BZ_X_IDLE; RETURN(BZ_STREAM_END); default: AssertH ( False, 4001 ); } AssertH ( False, 4002 ); save_state_and_return: s->save_i = i; s->save_j = j; s->save_t = t; s->save_alphaSize = alphaSize; s->save_nGroups = nGroups; s->save_nSelectors = nSelectors; s->save_EOB = EOB; s->save_groupNo = groupNo; s->save_groupPos = groupPos; s->save_nextSym = nextSym; s->save_nblockMAX = nblockMAX; s->save_nblock = nblock; s->save_es = es; s->save_N = N; s->save_curr = curr; s->save_zt = zt; s->save_zn = zn; s->save_zvec = zvec; s->save_zj = zj; s->save_gSel = gSel; s->save_gMinlen = gMinlen; s->save_gLimit = gLimit; s->save_gBase = gBase; s->save_gPerm = gPerm; return retVal; } /*-------------------------------------------------------------*/ /*--- end decompress.c ---*/ /*-------------------------------------------------------------*/
/*-------------------------------------------------------------*/ /*--- Library top-level functions. ---*/ /*--- bzlib.c ---*/ /*-------------------------------------------------------------*/ /* ------------------------------------------------------------------ This file is part of bzip2/libbzip2, a program and library for lossless, block-sorting data compression. bzip2/libbzip2 version 1.0.8 of 13 July 2019 Copyright (C) 1996-2019 Julian Seward <jseward@acm.org> Please read the WARNING, DISCLAIMER and PATENTS sections in the README file. This program is released under the terms of the license contained in the file LICENSE. ------------------------------------------------------------------ */ /* CHANGES 0.9.0 -- original version. 0.9.0a/b -- no changes in this file. 0.9.0c -- made zero-length BZ_FLUSH work correctly in bzCompress(). fixed bzWrite/bzRead to ignore zero-length requests. fixed bzread to correctly handle read requests after EOF. wrong parameter order in call to bzDecompressInit in bzBuffToBuffDecompress. Fixed. */ #include "bzlib_private.h" /*---------------------------------------------------*/ /*--- Compression stuff ---*/ /*---------------------------------------------------*/ /*---------------------------------------------------*/ #ifndef BZ_NO_STDIO void BZ2_bz__AssertH__fail ( int errcode ) { fprintf(stderr, "\n\nbzip2/libbzip2: internal error number %d.\n" "This is a bug in bzip2/libbzip2, %s.\n" "Please report it to: bzip2-devel@sourceware.org. If this happened\n" "when you were using some program which uses libbzip2 as a\n" "component, you should also report this bug to the author(s)\n" "of that program. Please make an effort to report this bug;\n" "timely and accurate bug reports eventually lead to higher\n" "quality software. Thanks.\n\n", errcode, BZ2_bzlibVersion() ); if (errcode == 1007) { fprintf(stderr, "\n*** A special note about internal error number 1007 ***\n" "\n" "Experience suggests that a common cause of i.e. 1007\n" "is unreliable memory or other hardware. The 1007 assertion\n" "just happens to cross-check the results of huge numbers of\n" "memory reads/writes, and so acts (unintendedly) as a stress\n" "test of your memory system.\n" "\n" "I suggest the following: try compressing the file again,\n" "possibly monitoring progress in detail with the -vv flag.\n" "\n" "* If the error cannot be reproduced, and/or happens at different\n" " points in compression, you may have a flaky memory system.\n" " Try a memory-test program. I have used Memtest86\n" " (www.memtest86.com). At the time of writing it is free (GPLd).\n" " Memtest86 tests memory much more thorougly than your BIOSs\n" " power-on test, and may find failures that the BIOS doesn't.\n" "\n" "* If the error can be repeatably reproduced, this is a bug in\n" " bzip2, and I would very much like to hear about it. Please\n" " let me know, and, ideally, save a copy of the file causing the\n" " problem -- without which I will be unable to investigate it.\n" "\n" ); } exit(3); } #endif /*---------------------------------------------------*/ static int bz_config_ok ( void ) { if (sizeof(int) != 4) return 0; if (sizeof(short) != 2) return 0; if (sizeof(char) != 1) return 0; return 1; } /*---------------------------------------------------*/ static void* default_bzalloc ( void* opaque, Int32 items, Int32 size ) { void* v = malloc ( items * size ); return v; } static void default_bzfree ( void* opaque, void* addr ) { if (addr != NULL) free ( addr ); } /*---------------------------------------------------*/ static void prepare_new_block ( EState* s ) { Int32 i; s->nblock = 0; s->numZ = 0; s->state_out_pos = 0; BZ_INITIALISE_CRC ( s->blockCRC ); for (i = 0; i < 256; i++) s->inUse[i] = False; s->blockNo++; } /*---------------------------------------------------*/ static void init_RL ( EState* s ) { s->state_in_ch = 256; s->state_in_len = 0; } static Bool isempty_RL ( EState* s ) { if (s->state_in_ch < 256 && s->state_in_len > 0) return False; else return True; } /*---------------------------------------------------*/ int BZ_API(BZ2_bzCompressInit) ( bz_stream* strm, int blockSize100k, int verbosity, int workFactor ) { Int32 n; EState* s; if (!bz_config_ok()) return BZ_CONFIG_ERROR; if (strm == NULL || blockSize100k < 1 || blockSize100k > 9 || workFactor < 0 || workFactor > 250) return BZ_PARAM_ERROR; if (workFactor == 0) workFactor = 30; if (strm->bzalloc == NULL) strm->bzalloc = default_bzalloc; if (strm->bzfree == NULL) strm->bzfree = default_bzfree; s = BZALLOC( sizeof(EState) ); if (s == NULL) return BZ_MEM_ERROR; s->strm = strm; s->arr1 = NULL; s->arr2 = NULL; s->ftab = NULL; n = 100000 * blockSize100k; s->arr1 = BZALLOC( n * sizeof(UInt32) ); s->arr2 = BZALLOC( (n+BZ_N_OVERSHOOT) * sizeof(UInt32) ); s->ftab = BZALLOC( 65537 * sizeof(UInt32) ); if (s->arr1 == NULL || s->arr2 == NULL || s->ftab == NULL) { if (s->arr1 != NULL) BZFREE(s->arr1); if (s->arr2 != NULL) BZFREE(s->arr2); if (s->ftab != NULL) BZFREE(s->ftab); if (s != NULL) BZFREE(s); return BZ_MEM_ERROR; } s->blockNo = 0; s->state = BZ_S_INPUT; s->mode = BZ_M_RUNNING; s->combinedCRC = 0; s->blockSize100k = blockSize100k; s->nblockMAX = 100000 * blockSize100k - 19; s->verbosity = verbosity; s->workFactor = workFactor; s->block = (UChar*)s->arr2; s->mtfv = (UInt16*)s->arr1; s->zbits = NULL; s->ptr = (UInt32*)s->arr1; strm->state = s; strm->total_in_lo32 = 0; strm->total_in_hi32 = 0; strm->total_out_lo32 = 0; strm->total_out_hi32 = 0; init_RL ( s ); prepare_new_block ( s ); return BZ_OK; } /*---------------------------------------------------*/ static void add_pair_to_block ( EState* s ) { Int32 i; UChar ch = (UChar)(s->state_in_ch); for (i = 0; i < s->state_in_len; i++) { BZ_UPDATE_CRC( s->blockCRC, ch ); } s->inUse[s->state_in_ch] = True; switch (s->state_in_len) { case 1: s->block[s->nblock] = (UChar)ch; s->nblock++; break; case 2: s->block[s->nblock] = (UChar)ch; s->nblock++; s->block[s->nblock] = (UChar)ch; s->nblock++; break; case 3: s->block[s->nblock] = (UChar)ch; s->nblock++; s->block[s->nblock] = (UChar)ch; s->nblock++; s->block[s->nblock] = (UChar)ch; s->nblock++; break; default: s->inUse[s->state_in_len-4] = True; s->block[s->nblock] = (UChar)ch; s->nblock++; s->block[s->nblock] = (UChar)ch; s->nblock++; s->block[s->nblock] = (UChar)ch; s->nblock++; s->block[s->nblock] = (UChar)ch; s->nblock++; s->block[s->nblock] = ((UChar)(s->state_in_len-4)); s->nblock++; break; } } /*---------------------------------------------------*/ static void flush_RL ( EState* s ) { if (s->state_in_ch < 256) add_pair_to_block ( s ); init_RL ( s ); } /*---------------------------------------------------*/ #define ADD_CHAR_TO_BLOCK(zs,zchh0) \ { \ UInt32 zchh = (UInt32)(zchh0); \ /*-- fast track the common case --*/ \ if (zchh != zs->state_in_ch && \ zs->state_in_len == 1) { \ UChar ch = (UChar)(zs->state_in_ch); \ BZ_UPDATE_CRC( zs->blockCRC, ch ); \ zs->inUse[zs->state_in_ch] = True; \ zs->block[zs->nblock] = (UChar)ch; \ zs->nblock++; \ zs->state_in_ch = zchh; \ } \ else \ /*-- general, uncommon cases --*/ \ if (zchh != zs->state_in_ch || \ zs->state_in_len == 255) { \ if (zs->state_in_ch < 256) \ add_pair_to_block ( zs ); \ zs->state_in_ch = zchh; \ zs->state_in_len = 1; \ } else { \ zs->state_in_len++; \ } \ } /*---------------------------------------------------*/ static Bool copy_input_until_stop ( EState* s ) { Bool progress_in = False; if (s->mode == BZ_M_RUNNING) { /*-- fast track the common case --*/ while (True) { /*-- block full? --*/ if (s->nblock >= s->nblockMAX) break; /*-- no input? --*/ if (s->strm->avail_in == 0) break; progress_in = True; ADD_CHAR_TO_BLOCK ( s, (UInt32)(*((UChar*)(s->strm->next_in))) ); s->strm->next_in++; s->strm->avail_in--; s->strm->total_in_lo32++; if (s->strm->total_in_lo32 == 0) s->strm->total_in_hi32++; } } else { /*-- general, uncommon case --*/ while (True) { /*-- block full? --*/ if (s->nblock >= s->nblockMAX) break; /*-- no input? --*/ if (s->strm->avail_in == 0) break; /*-- flush/finish end? --*/ if (s->avail_in_expect == 0) break; progress_in = True; ADD_CHAR_TO_BLOCK ( s, (UInt32)(*((UChar*)(s->strm->next_in))) ); s->strm->next_in++; s->strm->avail_in--; s->strm->total_in_lo32++; if (s->strm->total_in_lo32 == 0) s->strm->total_in_hi32++; s->avail_in_expect--; } } return progress_in; } /*---------------------------------------------------*/ static Bool copy_output_until_stop ( EState* s ) { Bool progress_out = False; while (True) { /*-- no output space? --*/ if (s->strm->avail_out == 0) break; /*-- block done? --*/ if (s->state_out_pos >= s->numZ) break; progress_out = True; *(s->strm->next_out) = s->zbits[s->state_out_pos]; s->state_out_pos++; s->strm->avail_out--; s->strm->next_out++; s->strm->total_out_lo32++; if (s->strm->total_out_lo32 == 0) s->strm->total_out_hi32++; } return progress_out; } /*---------------------------------------------------*/ static Bool handle_compress ( bz_stream* strm ) { Bool progress_in = False; Bool progress_out = False; EState* s = strm->state; while (True) { if (s->state == BZ_S_OUTPUT) { progress_out |= copy_output_until_stop ( s ); if (s->state_out_pos < s->numZ) break; if (s->mode == BZ_M_FINISHING && s->avail_in_expect == 0 && isempty_RL(s)) break; prepare_new_block ( s ); s->state = BZ_S_INPUT; if (s->mode == BZ_M_FLUSHING && s->avail_in_expect == 0 && isempty_RL(s)) break; } if (s->state == BZ_S_INPUT) { progress_in |= copy_input_until_stop ( s ); if (s->mode != BZ_M_RUNNING && s->avail_in_expect == 0) { flush_RL ( s ); BZ2_compressBlock ( s, (Bool)(s->mode == BZ_M_FINISHING) ); s->state = BZ_S_OUTPUT; } else if (s->nblock >= s->nblockMAX) { BZ2_compressBlock ( s, False ); s->state = BZ_S_OUTPUT; } else if (s->strm->avail_in == 0) { break; } } } return progress_in || progress_out; } /*---------------------------------------------------*/ int BZ_API(BZ2_bzCompress) ( bz_stream *strm, int action ) { Bool progress; EState* s; if (strm == NULL) return BZ_PARAM_ERROR; s = strm->state; if (s == NULL) return BZ_PARAM_ERROR; if (s->strm != strm) return BZ_PARAM_ERROR; preswitch: switch (s->mode) { case BZ_M_IDLE: return BZ_SEQUENCE_ERROR; case BZ_M_RUNNING: if (action == BZ_RUN) { progress = handle_compress ( strm ); return progress ? BZ_RUN_OK : BZ_PARAM_ERROR; } else if (action == BZ_FLUSH) { s->avail_in_expect = strm->avail_in; s->mode = BZ_M_FLUSHING; goto preswitch; } else if (action == BZ_FINISH) { s->avail_in_expect = strm->avail_in; s->mode = BZ_M_FINISHING; goto preswitch; } else return BZ_PARAM_ERROR; case BZ_M_FLUSHING: if (action != BZ_FLUSH) return BZ_SEQUENCE_ERROR; if (s->avail_in_expect != s->strm->avail_in) return BZ_SEQUENCE_ERROR; progress = handle_compress ( strm ); if (s->avail_in_expect > 0 || !isempty_RL(s) || s->state_out_pos < s->numZ) return BZ_FLUSH_OK; s->mode = BZ_M_RUNNING; return BZ_RUN_OK; case BZ_M_FINISHING: if (action != BZ_FINISH) return BZ_SEQUENCE_ERROR; if (s->avail_in_expect != s->strm->avail_in) return BZ_SEQUENCE_ERROR; progress = handle_compress ( strm ); if (!progress) return BZ_SEQUENCE_ERROR; if (s->avail_in_expect > 0 || !isempty_RL(s) || s->state_out_pos < s->numZ) return BZ_FINISH_OK; s->mode = BZ_M_IDLE; return BZ_STREAM_END; } return BZ_OK; /*--not reached--*/ } /*---------------------------------------------------*/ int BZ_API(BZ2_bzCompressEnd) ( bz_stream *strm ) { EState* s; if (strm == NULL) return BZ_PARAM_ERROR; s = strm->state; if (s == NULL) return BZ_PARAM_ERROR; if (s->strm != strm) return BZ_PARAM_ERROR; if (s->arr1 != NULL) BZFREE(s->arr1); if (s->arr2 != NULL) BZFREE(s->arr2); if (s->ftab != NULL) BZFREE(s->ftab); BZFREE(strm->state); strm->state = NULL; return BZ_OK; } /*---------------------------------------------------*/ /*--- Decompression stuff ---*/ /*---------------------------------------------------*/ /*---------------------------------------------------*/ int BZ_API(BZ2_bzDecompressInit) ( bz_stream* strm, int verbosity, int small ) { DState* s; if (!bz_config_ok()) return BZ_CONFIG_ERROR; if (strm == NULL) return BZ_PARAM_ERROR; if (small != 0 && small != 1) return BZ_PARAM_ERROR; if (verbosity < 0 || verbosity > 4) return BZ_PARAM_ERROR; if (strm->bzalloc == NULL) strm->bzalloc = default_bzalloc; if (strm->bzfree == NULL) strm->bzfree = default_bzfree; s = BZALLOC( sizeof(DState) ); if (s == NULL) return BZ_MEM_ERROR; s->strm = strm; strm->state = s; s->state = BZ_X_MAGIC_1; s->bsLive = 0; s->bsBuff = 0; s->calculatedCombinedCRC = 0; strm->total_in_lo32 = 0; strm->total_in_hi32 = 0; strm->total_out_lo32 = 0; strm->total_out_hi32 = 0; s->smallDecompress = (Bool)small; s->ll4 = NULL; s->ll16 = NULL; s->tt = NULL; s->currBlockNo = 0; s->verbosity = verbosity; return BZ_OK; } /*---------------------------------------------------*/ /* Return True iff data corruption is discovered. Returns False if there is no problem. */ static Bool unRLE_obuf_to_output_FAST ( DState* s ) { UChar k1; if (s->blockRandomised) { while (True) { /* try to finish existing run */ while (True) { if (s->strm->avail_out == 0) return False; if (s->state_out_len == 0) break; *( (UChar*)(s->strm->next_out) ) = s->state_out_ch; BZ_UPDATE_CRC ( s->calculatedBlockCRC, s->state_out_ch ); s->state_out_len--; s->strm->next_out++; s->strm->avail_out--; s->strm->total_out_lo32++; if (s->strm->total_out_lo32 == 0) s->strm->total_out_hi32++; } /* can a new run be started? */ if (s->nblock_used == s->save_nblock+1) return False; /* Only caused by corrupt data stream? */ if (s->nblock_used > s->save_nblock+1) return True; s->state_out_len = 1; s->state_out_ch = s->k0; BZ_GET_FAST(k1); BZ_RAND_UPD_MASK; k1 ^= BZ_RAND_MASK; s->nblock_used++; if (s->nblock_used == s->save_nblock+1) continue; if (k1 != s->k0) { s->k0 = k1; continue; }; s->state_out_len = 2; BZ_GET_FAST(k1); BZ_RAND_UPD_MASK; k1 ^= BZ_RAND_MASK; s->nblock_used++; if (s->nblock_used == s->save_nblock+1) continue; if (k1 != s->k0) { s->k0 = k1; continue; }; s->state_out_len = 3; BZ_GET_FAST(k1); BZ_RAND_UPD_MASK; k1 ^= BZ_RAND_MASK; s->nblock_used++; if (s->nblock_used == s->save_nblock+1) continue; if (k1 != s->k0) { s->k0 = k1; continue; }; BZ_GET_FAST(k1); BZ_RAND_UPD_MASK; k1 ^= BZ_RAND_MASK; s->nblock_used++; s->state_out_len = ((Int32)k1) + 4; BZ_GET_FAST(s->k0); BZ_RAND_UPD_MASK; s->k0 ^= BZ_RAND_MASK; s->nblock_used++; } } else { /* restore */ UInt32 c_calculatedBlockCRC = s->calculatedBlockCRC; UChar c_state_out_ch = s->state_out_ch; Int32 c_state_out_len = s->state_out_len; Int32 c_nblock_used = s->nblock_used; Int32 c_k0 = s->k0; UInt32* c_tt = s->tt; UInt32 c_tPos = s->tPos; char* cs_next_out = s->strm->next_out; unsigned int cs_avail_out = s->strm->avail_out; Int32 ro_blockSize100k = s->blockSize100k; /* end restore */ UInt32 avail_out_INIT = cs_avail_out; Int32 s_save_nblockPP = s->save_nblock+1; unsigned int total_out_lo32_old; while (True) { /* try to finish existing run */ if (c_state_out_len > 0) { while (True) { if (cs_avail_out == 0) goto return_notr; if (c_state_out_len == 1) break; *( (UChar*)(cs_next_out) ) = c_state_out_ch; BZ_UPDATE_CRC ( c_calculatedBlockCRC, c_state_out_ch ); c_state_out_len--; cs_next_out++; cs_avail_out--; } s_state_out_len_eq_one: { if (cs_avail_out == 0) { c_state_out_len = 1; goto return_notr; }; *( (UChar*)(cs_next_out) ) = c_state_out_ch; BZ_UPDATE_CRC ( c_calculatedBlockCRC, c_state_out_ch ); cs_next_out++; cs_avail_out--; } } /* Only caused by corrupt data stream? */ if (c_nblock_used > s_save_nblockPP) return True; /* can a new run be started? */ if (c_nblock_used == s_save_nblockPP) { c_state_out_len = 0; goto return_notr; }; c_state_out_ch = c_k0; BZ_GET_FAST_C(k1); c_nblock_used++; if (k1 != c_k0) { c_k0 = k1; goto s_state_out_len_eq_one; }; if (c_nblock_used == s_save_nblockPP) goto s_state_out_len_eq_one; c_state_out_len = 2; BZ_GET_FAST_C(k1); c_nblock_used++; if (c_nblock_used == s_save_nblockPP) continue; if (k1 != c_k0) { c_k0 = k1; continue; }; c_state_out_len = 3; BZ_GET_FAST_C(k1); c_nblock_used++; if (c_nblock_used == s_save_nblockPP) continue; if (k1 != c_k0) { c_k0 = k1; continue; }; BZ_GET_FAST_C(k1); c_nblock_used++; c_state_out_len = ((Int32)k1) + 4; BZ_GET_FAST_C(c_k0); c_nblock_used++; } return_notr: total_out_lo32_old = s->strm->total_out_lo32; s->strm->total_out_lo32 += (avail_out_INIT - cs_avail_out); if (s->strm->total_out_lo32 < total_out_lo32_old) s->strm->total_out_hi32++; /* save */ s->calculatedBlockCRC = c_calculatedBlockCRC; s->state_out_ch = c_state_out_ch; s->state_out_len = c_state_out_len; s->nblock_used = c_nblock_used; s->k0 = c_k0; s->tt = c_tt; s->tPos = c_tPos; s->strm->next_out = cs_next_out; s->strm->avail_out = cs_avail_out; /* end save */ } return False; } /*---------------------------------------------------*/ __inline__ Int32 BZ2_indexIntoF ( Int32 indx, Int32 *cftab ) { Int32 nb, na, mid; nb = 0; na = 256; do { mid = (nb + na) >> 1; if (indx >= cftab[mid]) nb = mid; else na = mid; } while (na - nb != 1); return nb; } /*---------------------------------------------------*/ /* Return True iff data corruption is discovered. Returns False if there is no problem. */ static Bool unRLE_obuf_to_output_SMALL ( DState* s ) { UChar k1; if (s->blockRandomised) { while (True) { /* try to finish existing run */ while (True) { if (s->strm->avail_out == 0) return False; if (s->state_out_len == 0) break; *( (UChar*)(s->strm->next_out) ) = s->state_out_ch; BZ_UPDATE_CRC ( s->calculatedBlockCRC, s->state_out_ch ); s->state_out_len--; s->strm->next_out++; s->strm->avail_out--; s->strm->total_out_lo32++; if (s->strm->total_out_lo32 == 0) s->strm->total_out_hi32++; } /* can a new run be started? */ if (s->nblock_used == s->save_nblock+1) return False; /* Only caused by corrupt data stream? */ if (s->nblock_used > s->save_nblock+1) return True; s->state_out_len = 1; s->state_out_ch = s->k0; BZ_GET_SMALL(k1); BZ_RAND_UPD_MASK; k1 ^= BZ_RAND_MASK; s->nblock_used++; if (s->nblock_used == s->save_nblock+1) continue; if (k1 != s->k0) { s->k0 = k1; continue; }; s->state_out_len = 2; BZ_GET_SMALL(k1); BZ_RAND_UPD_MASK; k1 ^= BZ_RAND_MASK; s->nblock_used++; if (s->nblock_used == s->save_nblock+1) continue; if (k1 != s->k0) { s->k0 = k1; continue; }; s->state_out_len = 3; BZ_GET_SMALL(k1); BZ_RAND_UPD_MASK; k1 ^= BZ_RAND_MASK; s->nblock_used++; if (s->nblock_used == s->save_nblock+1) continue; if (k1 != s->k0) { s->k0 = k1; continue; }; BZ_GET_SMALL(k1); BZ_RAND_UPD_MASK; k1 ^= BZ_RAND_MASK; s->nblock_used++; s->state_out_len = ((Int32)k1) + 4; BZ_GET_SMALL(s->k0); BZ_RAND_UPD_MASK; s->k0 ^= BZ_RAND_MASK; s->nblock_used++; } } else { while (True) { /* try to finish existing run */ while (True) { if (s->strm->avail_out == 0) return False; if (s->state_out_len == 0) break; *( (UChar*)(s->strm->next_out) ) = s->state_out_ch; BZ_UPDATE_CRC ( s->calculatedBlockCRC, s->state_out_ch ); s->state_out_len--; s->strm->next_out++; s->strm->avail_out--; s->strm->total_out_lo32++; if (s->strm->total_out_lo32 == 0) s->strm->total_out_hi32++; } /* can a new run be started? */ if (s->nblock_used == s->save_nblock+1) return False; /* Only caused by corrupt data stream? */ if (s->nblock_used > s->save_nblock+1) return True; s->state_out_len = 1; s->state_out_ch = s->k0; BZ_GET_SMALL(k1); s->nblock_used++; if (s->nblock_used == s->save_nblock+1) continue; if (k1 != s->k0) { s->k0 = k1; continue; }; s->state_out_len = 2; BZ_GET_SMALL(k1); s->nblock_used++; if (s->nblock_used == s->save_nblock+1) continue; if (k1 != s->k0) { s->k0 = k1; continue; }; s->state_out_len = 3; BZ_GET_SMALL(k1); s->nblock_used++; if (s->nblock_used == s->save_nblock+1) continue; if (k1 != s->k0) { s->k0 = k1; continue; }; BZ_GET_SMALL(k1); s->nblock_used++; s->state_out_len = ((Int32)k1) + 4; BZ_GET_SMALL(s->k0); s->nblock_used++; } } } /*---------------------------------------------------*/ int BZ_API(BZ2_bzDecompress) ( bz_stream *strm ) { Bool corrupt; DState* s; if (strm == NULL) return BZ_PARAM_ERROR; s = strm->state; if (s == NULL) return BZ_PARAM_ERROR; if (s->strm != strm) return BZ_PARAM_ERROR; while (True) { if (s->state == BZ_X_IDLE) return BZ_SEQUENCE_ERROR; if (s->state == BZ_X_OUTPUT) { if (s->smallDecompress) corrupt = unRLE_obuf_to_output_SMALL ( s ); else corrupt = unRLE_obuf_to_output_FAST ( s ); if (corrupt) return BZ_DATA_ERROR; if (s->nblock_used == s->save_nblock+1 && s->state_out_len == 0) { BZ_FINALISE_CRC ( s->calculatedBlockCRC ); if (s->verbosity >= 3) VPrintf2 ( " {0x%08x, 0x%08x}", s->storedBlockCRC, s->calculatedBlockCRC ); if (s->verbosity >= 2) VPrintf0 ( "]" ); if (s->calculatedBlockCRC != s->storedBlockCRC) return BZ_DATA_ERROR; s->calculatedCombinedCRC = (s->calculatedCombinedCRC << 1) | (s->calculatedCombinedCRC >> 31); s->calculatedCombinedCRC ^= s->calculatedBlockCRC; s->state = BZ_X_BLKHDR_1; } else { return BZ_OK; } } if (s->state >= BZ_X_MAGIC_1) { Int32 r = BZ2_decompress ( s ); if (r == BZ_STREAM_END) { if (s->verbosity >= 3) VPrintf2 ( "\n combined CRCs: stored = 0x%08x, computed = 0x%08x", s->storedCombinedCRC, s->calculatedCombinedCRC ); if (s->calculatedCombinedCRC != s->storedCombinedCRC) return BZ_DATA_ERROR; return r; } if (s->state != BZ_X_OUTPUT) return r; } } AssertH ( 0, 6001 ); return 0; /*NOTREACHED*/ } /*---------------------------------------------------*/ int BZ_API(BZ2_bzDecompressEnd) ( bz_stream *strm ) { DState* s; if (strm == NULL) return BZ_PARAM_ERROR; s = strm->state; if (s == NULL) return BZ_PARAM_ERROR; if (s->strm != strm) return BZ_PARAM_ERROR; if (s->tt != NULL) BZFREE(s->tt); if (s->ll16 != NULL) BZFREE(s->ll16); if (s->ll4 != NULL) BZFREE(s->ll4); BZFREE(strm->state); strm->state = NULL; return BZ_OK; } #ifndef BZ_NO_STDIO /*---------------------------------------------------*/ /*--- File I/O stuff ---*/ /*---------------------------------------------------*/ #define BZ_SETERR(eee) \ { \ if (bzerror != NULL) *bzerror = eee; \ if (bzf != NULL) bzf->lastErr = eee; \ } typedef struct { FILE* handle; Char buf[BZ_MAX_UNUSED]; Int32 bufN; Bool writing; bz_stream strm; Int32 lastErr; Bool initialisedOk; } bzFile; /*---------------------------------------------*/ static Bool myfeof ( FILE* f ) { Int32 c = fgetc ( f ); if (c == EOF) return True; ungetc ( c, f ); return False; } /*---------------------------------------------------*/ BZFILE* BZ_API(BZ2_bzWriteOpen) ( int* bzerror, FILE* f, int blockSize100k, int verbosity, int workFactor ) { Int32 ret; bzFile* bzf = NULL; BZ_SETERR(BZ_OK); if (f == NULL || (blockSize100k < 1 || blockSize100k > 9) || (workFactor < 0 || workFactor > 250) || (verbosity < 0 || verbosity > 4)) { BZ_SETERR(BZ_PARAM_ERROR); return NULL; }; if (ferror(f)) { BZ_SETERR(BZ_IO_ERROR); return NULL; }; bzf = malloc ( sizeof(bzFile) ); if (bzf == NULL) { BZ_SETERR(BZ_MEM_ERROR); return NULL; }; BZ_SETERR(BZ_OK); bzf->initialisedOk = False; bzf->bufN = 0; bzf->handle = f; bzf->writing = True; bzf->strm.bzalloc = NULL; bzf->strm.bzfree = NULL; bzf->strm.opaque = NULL; if (workFactor == 0) workFactor = 30; ret = BZ2_bzCompressInit ( &(bzf->strm), blockSize100k, verbosity, workFactor ); if (ret != BZ_OK) { BZ_SETERR(ret); free(bzf); return NULL; }; bzf->strm.avail_in = 0; bzf->initialisedOk = True; return bzf; } /*---------------------------------------------------*/ void BZ_API(BZ2_bzWrite) ( int* bzerror, BZFILE* b, void* buf, int len ) { Int32 n, n2, ret; bzFile* bzf = (bzFile*)b; BZ_SETERR(BZ_OK); if (bzf == NULL || buf == NULL || len < 0) { BZ_SETERR(BZ_PARAM_ERROR); return; }; if (!(bzf->writing)) { BZ_SETERR(BZ_SEQUENCE_ERROR); return; }; if (ferror(bzf->handle)) { BZ_SETERR(BZ_IO_ERROR); return; }; if (len == 0) { BZ_SETERR(BZ_OK); return; }; bzf->strm.avail_in = len; bzf->strm.next_in = buf; while (True) { bzf->strm.avail_out = BZ_MAX_UNUSED; bzf->strm.next_out = bzf->buf; ret = BZ2_bzCompress ( &(bzf->strm), BZ_RUN ); if (ret != BZ_RUN_OK) { BZ_SETERR(ret); return; }; if (bzf->strm.avail_out < BZ_MAX_UNUSED) { n = BZ_MAX_UNUSED - bzf->strm.avail_out; n2 = fwrite ( (void*)(bzf->buf), sizeof(UChar), n, bzf->handle ); if (n != n2 || ferror(bzf->handle)) { BZ_SETERR(BZ_IO_ERROR); return; }; } if (bzf->strm.avail_in == 0) { BZ_SETERR(BZ_OK); return; }; } } /*---------------------------------------------------*/ void BZ_API(BZ2_bzWriteClose) ( int* bzerror, BZFILE* b, int abandon, unsigned int* nbytes_in, unsigned int* nbytes_out ) { BZ2_bzWriteClose64 ( bzerror, b, abandon, nbytes_in, NULL, nbytes_out, NULL ); } void BZ_API(BZ2_bzWriteClose64) ( int* bzerror, BZFILE* b, int abandon, unsigned int* nbytes_in_lo32, unsigned int* nbytes_in_hi32, unsigned int* nbytes_out_lo32, unsigned int* nbytes_out_hi32 ) { Int32 n, n2, ret; bzFile* bzf = (bzFile*)b; if (bzf == NULL) { BZ_SETERR(BZ_OK); return; }; if (!(bzf->writing)) { BZ_SETERR(BZ_SEQUENCE_ERROR); return; }; if (ferror(bzf->handle)) { BZ_SETERR(BZ_IO_ERROR); return; }; if (nbytes_in_lo32 != NULL) *nbytes_in_lo32 = 0; if (nbytes_in_hi32 != NULL) *nbytes_in_hi32 = 0; if (nbytes_out_lo32 != NULL) *nbytes_out_lo32 = 0; if (nbytes_out_hi32 != NULL) *nbytes_out_hi32 = 0; if ((!abandon) && bzf->lastErr == BZ_OK) { while (True) { bzf->strm.avail_out = BZ_MAX_UNUSED; bzf->strm.next_out = bzf->buf; ret = BZ2_bzCompress ( &(bzf->strm), BZ_FINISH ); if (ret != BZ_FINISH_OK && ret != BZ_STREAM_END) { BZ_SETERR(ret); return; }; if (bzf->strm.avail_out < BZ_MAX_UNUSED) { n = BZ_MAX_UNUSED - bzf->strm.avail_out; n2 = fwrite ( (void*)(bzf->buf), sizeof(UChar), n, bzf->handle ); if (n != n2 || ferror(bzf->handle)) { BZ_SETERR(BZ_IO_ERROR); return; }; } if (ret == BZ_STREAM_END) break; } } if ( !abandon && !ferror ( bzf->handle ) ) { fflush ( bzf->handle ); if (ferror(bzf->handle)) { BZ_SETERR(BZ_IO_ERROR); return; }; } if (nbytes_in_lo32 != NULL) *nbytes_in_lo32 = bzf->strm.total_in_lo32; if (nbytes_in_hi32 != NULL) *nbytes_in_hi32 = bzf->strm.total_in_hi32; if (nbytes_out_lo32 != NULL) *nbytes_out_lo32 = bzf->strm.total_out_lo32; if (nbytes_out_hi32 != NULL) *nbytes_out_hi32 = bzf->strm.total_out_hi32; BZ_SETERR(BZ_OK); BZ2_bzCompressEnd ( &(bzf->strm) ); free ( bzf ); } /*---------------------------------------------------*/ BZFILE* BZ_API(BZ2_bzReadOpen) ( int* bzerror, FILE* f, int verbosity, int small, void* unused, int nUnused ) { bzFile* bzf = NULL; int ret; BZ_SETERR(BZ_OK); if (f == NULL || (small != 0 && small != 1) || (verbosity < 0 || verbosity > 4) || (unused == NULL && nUnused != 0) || (unused != NULL && (nUnused < 0 || nUnused > BZ_MAX_UNUSED))) { BZ_SETERR(BZ_PARAM_ERROR); return NULL; }; if (ferror(f)) { BZ_SETERR(BZ_IO_ERROR); return NULL; }; bzf = malloc ( sizeof(bzFile) ); if (bzf == NULL) { BZ_SETERR(BZ_MEM_ERROR); return NULL; }; BZ_SETERR(BZ_OK); bzf->initialisedOk = False; bzf->handle = f; bzf->bufN = 0; bzf->writing = False; bzf->strm.bzalloc = NULL; bzf->strm.bzfree = NULL; bzf->strm.opaque = NULL; while (nUnused > 0) { bzf->buf[bzf->bufN] = *((UChar*)(unused)); bzf->bufN++; unused = ((void*)( 1 + ((UChar*)(unused)) )); nUnused--; } ret = BZ2_bzDecompressInit ( &(bzf->strm), verbosity, small ); if (ret != BZ_OK) { BZ_SETERR(ret); free(bzf); return NULL; }; bzf->strm.avail_in = bzf->bufN; bzf->strm.next_in = bzf->buf; bzf->initialisedOk = True; return bzf; } /*---------------------------------------------------*/ void BZ_API(BZ2_bzReadClose) ( int *bzerror, BZFILE *b ) { bzFile* bzf = (bzFile*)b; BZ_SETERR(BZ_OK); if (bzf == NULL) { BZ_SETERR(BZ_OK); return; }; if (bzf->writing) { BZ_SETERR(BZ_SEQUENCE_ERROR); return; }; if (bzf->initialisedOk) (void)BZ2_bzDecompressEnd ( &(bzf->strm) ); free ( bzf ); } /*---------------------------------------------------*/ int BZ_API(BZ2_bzRead) ( int* bzerror, BZFILE* b, void* buf, int len ) { Int32 n, ret; bzFile* bzf = (bzFile*)b; BZ_SETERR(BZ_OK); if (bzf == NULL || buf == NULL || len < 0) { BZ_SETERR(BZ_PARAM_ERROR); return 0; }; if (bzf->writing) { BZ_SETERR(BZ_SEQUENCE_ERROR); return 0; }; if (len == 0) { BZ_SETERR(BZ_OK); return 0; }; bzf->strm.avail_out = len; bzf->strm.next_out = buf; while (True) { if (ferror(bzf->handle)) { BZ_SETERR(BZ_IO_ERROR); return 0; }; if (bzf->strm.avail_in == 0 && !myfeof(bzf->handle)) { n = fread ( bzf->buf, sizeof(UChar), BZ_MAX_UNUSED, bzf->handle ); if (ferror(bzf->handle)) { BZ_SETERR(BZ_IO_ERROR); return 0; }; bzf->bufN = n; bzf->strm.avail_in = bzf->bufN; bzf->strm.next_in = bzf->buf; } ret = BZ2_bzDecompress ( &(bzf->strm) ); if (ret != BZ_OK && ret != BZ_STREAM_END) { BZ_SETERR(ret); return 0; }; if (ret == BZ_OK && myfeof(bzf->handle) && bzf->strm.avail_in == 0 && bzf->strm.avail_out > 0) { BZ_SETERR(BZ_UNEXPECTED_EOF); return 0; }; if (ret == BZ_STREAM_END) { BZ_SETERR(BZ_STREAM_END); return len - bzf->strm.avail_out; }; if (bzf->strm.avail_out == 0) { BZ_SETERR(BZ_OK); return len; }; } return 0; /*not reached*/ } /*---------------------------------------------------*/ void BZ_API(BZ2_bzReadGetUnused) ( int* bzerror, BZFILE* b, void** unused, int* nUnused ) { bzFile* bzf = (bzFile*)b; if (bzf == NULL) { BZ_SETERR(BZ_PARAM_ERROR); return; }; if (bzf->lastErr != BZ_STREAM_END) { BZ_SETERR(BZ_SEQUENCE_ERROR); return; }; if (unused == NULL || nUnused == NULL) { BZ_SETERR(BZ_PARAM_ERROR); return; }; BZ_SETERR(BZ_OK); *nUnused = bzf->strm.avail_in; *unused = bzf->strm.next_in; } #endif /*---------------------------------------------------*/ /*--- Misc convenience stuff ---*/ /*---------------------------------------------------*/ /*---------------------------------------------------*/ int BZ_API(BZ2_bzBuffToBuffCompress) ( char* dest, unsigned int* destLen, char* source, unsigned int sourceLen, int blockSize100k, int verbosity, int workFactor ) { bz_stream strm; int ret; if (dest == NULL || destLen == NULL || source == NULL || blockSize100k < 1 || blockSize100k > 9 || verbosity < 0 || verbosity > 4 || workFactor < 0 || workFactor > 250) return BZ_PARAM_ERROR; if (workFactor == 0) workFactor = 30; strm.bzalloc = NULL; strm.bzfree = NULL; strm.opaque = NULL; ret = BZ2_bzCompressInit ( &strm, blockSize100k, verbosity, workFactor ); if (ret != BZ_OK) return ret; strm.next_in = source; strm.next_out = dest; strm.avail_in = sourceLen; strm.avail_out = *destLen; ret = BZ2_bzCompress ( &strm, BZ_FINISH ); if (ret == BZ_FINISH_OK) goto output_overflow; if (ret != BZ_STREAM_END) goto errhandler; /* normal termination */ *destLen -= strm.avail_out; BZ2_bzCompressEnd ( &strm ); return BZ_OK; output_overflow: BZ2_bzCompressEnd ( &strm ); return BZ_OUTBUFF_FULL; errhandler: BZ2_bzCompressEnd ( &strm ); return ret; } /*---------------------------------------------------*/ int BZ_API(BZ2_bzBuffToBuffDecompress) ( char* dest, unsigned int* destLen, char* source, unsigned int sourceLen, int small, int verbosity ) { bz_stream strm; int ret; if (dest == NULL || destLen == NULL || source == NULL || (small != 0 && small != 1) || verbosity < 0 || verbosity > 4) return BZ_PARAM_ERROR; strm.bzalloc = NULL; strm.bzfree = NULL; strm.opaque = NULL; ret = BZ2_bzDecompressInit ( &strm, verbosity, small ); if (ret != BZ_OK) return ret; strm.next_in = source; strm.next_out = dest; strm.avail_in = sourceLen; strm.avail_out = *destLen; ret = BZ2_bzDecompress ( &strm ); if (ret == BZ_OK) goto output_overflow_or_eof; if (ret != BZ_STREAM_END) goto errhandler; /* normal termination */ *destLen -= strm.avail_out; BZ2_bzDecompressEnd ( &strm ); return BZ_OK; output_overflow_or_eof: if (strm.avail_out > 0) { BZ2_bzDecompressEnd ( &strm ); return BZ_UNEXPECTED_EOF; } else { BZ2_bzDecompressEnd ( &strm ); return BZ_OUTBUFF_FULL; }; errhandler: BZ2_bzDecompressEnd ( &strm ); return ret; } /*---------------------------------------------------*/ /*-- Code contributed by Yoshioka Tsuneo (tsuneo@rr.iij4u.or.jp) to support better zlib compatibility. This code is not _officially_ part of libbzip2 (yet); I haven't tested it, documented it, or considered the threading-safeness of it. If this code breaks, please contact both Yoshioka and me. --*/ /*---------------------------------------------------*/ /*---------------------------------------------------*/ /*-- return version like "0.9.5d, 4-Sept-1999". --*/ const char * BZ_API(BZ2_bzlibVersion)(void) { return BZ_VERSION; } #ifndef BZ_NO_STDIO /*---------------------------------------------------*/ #if defined(_WIN32) || defined(OS2) || defined(MSDOS) # include <fcntl.h> # include <io.h> # define SET_BINARY_MODE(file) setmode(fileno(file),O_BINARY) #else # define SET_BINARY_MODE(file) #endif static BZFILE * bzopen_or_bzdopen ( const char *path, /* no use when bzdopen */ int fd, /* no use when bzdopen */ const char *mode, int open_mode) /* bzopen: 0, bzdopen:1 */ { int bzerr; char unused[BZ_MAX_UNUSED]; int blockSize100k = 9; int writing = 0; char mode2[10] = ""; FILE *fp = NULL; BZFILE *bzfp = NULL; int verbosity = 0; int workFactor = 30; int smallMode = 0; int nUnused = 0; if (mode == NULL) return NULL; while (*mode) { switch (*mode) { case 'r': writing = 0; break; case 'w': writing = 1; break; case 's': smallMode = 1; break; default: if (isdigit((int)(*mode))) { blockSize100k = *mode-BZ_HDR_0; } } mode++; } strcat(mode2, writing ? "w" : "r" ); strcat(mode2,"b"); /* binary mode */ if (open_mode==0) { if (path==NULL || strcmp(path,"")==0) { fp = (writing ? stdout : stdin); SET_BINARY_MODE(fp); } else { fp = fopen(path,mode2); } } else { #ifdef BZ_STRICT_ANSI fp = NULL; #else fp = fdopen(fd,mode2); #endif } if (fp == NULL) return NULL; if (writing) { /* Guard against total chaos and anarchy -- JRS */ if (blockSize100k < 1) blockSize100k = 1; if (blockSize100k > 9) blockSize100k = 9; bzfp = BZ2_bzWriteOpen(&bzerr,fp,blockSize100k, verbosity,workFactor); } else { bzfp = BZ2_bzReadOpen(&bzerr,fp,verbosity,smallMode, unused,nUnused); } if (bzfp == NULL) { if (fp != stdin && fp != stdout) fclose(fp); return NULL; } return bzfp; } /*---------------------------------------------------*/ /*-- open file for read or write. ex) bzopen("file","w9") case path="" or NULL => use stdin or stdout. --*/ BZFILE * BZ_API(BZ2_bzopen) ( const char *path, const char *mode ) { return bzopen_or_bzdopen(path,-1,mode,/*bzopen*/0); } /*---------------------------------------------------*/ BZFILE * BZ_API(BZ2_bzdopen) ( int fd, const char *mode ) { return bzopen_or_bzdopen(NULL,fd,mode,/*bzdopen*/1); } /*---------------------------------------------------*/ int BZ_API(BZ2_bzread) (BZFILE* b, void* buf, int len ) { int bzerr, nread; if (((bzFile*)b)->lastErr == BZ_STREAM_END) return 0; nread = BZ2_bzRead(&bzerr,b,buf,len); if (bzerr == BZ_OK || bzerr == BZ_STREAM_END) { return nread; } else { return -1; } } /*---------------------------------------------------*/ int BZ_API(BZ2_bzwrite) (BZFILE* b, void* buf, int len ) { int bzerr; BZ2_bzWrite(&bzerr,b,buf,len); if(bzerr == BZ_OK){ return len; }else{ return -1; } } /*---------------------------------------------------*/ int BZ_API(BZ2_bzflush) (BZFILE *b) { /* do nothing now... */ return 0; } /*---------------------------------------------------*/ void BZ_API(BZ2_bzclose) (BZFILE* b) { int bzerr; FILE *fp; if (b==NULL) {return;} fp = ((bzFile *)b)->handle; if(((bzFile*)b)->writing){ BZ2_bzWriteClose(&bzerr,b,0,NULL,NULL); if(bzerr != BZ_OK){ BZ2_bzWriteClose(NULL,b,1,NULL,NULL); } }else{ BZ2_bzReadClose(&bzerr,b); } if(fp!=stdin && fp!=stdout){ fclose(fp); } } /*---------------------------------------------------*/ /*-- return last error code --*/ static const char *bzerrorstrings[] = { "OK" ,"SEQUENCE_ERROR" ,"PARAM_ERROR" ,"MEM_ERROR" ,"DATA_ERROR" ,"DATA_ERROR_MAGIC" ,"IO_ERROR" ,"UNEXPECTED_EOF" ,"OUTBUFF_FULL" ,"CONFIG_ERROR" ,"???" /* for future */ ,"???" /* for future */ ,"???" /* for future */ ,"???" /* for future */ ,"???" /* for future */ ,"???" /* for future */ }; const char * BZ_API(BZ2_bzerror) (BZFILE *b, int *errnum) { int err = ((bzFile *)b)->lastErr; if(err>0) err = 0; *errnum = err; return bzerrorstrings[err*-1]; } #endif /*-------------------------------------------------------------*/ /*--- end bzlib.c ---*/ /*-------------------------------------------------------------*/
532b2455e0b0c6fd3ef067776a61ebd0620437d1f55145ce56ce1ddad23844c5 /usr/bin/bzip2
https://mirrors.kernel.org/gnu/coreutils/coreutils-5.0.tar.bz2 c25b36b8af6e0ad2a875daf4d6196bd0df28a62be7dd252e5f99a4d5d7288d95
# SPDX-FileCopyrightText: 2021 Andrius Å tikonas <andrius@stikonas.eu> # SPDX-FileCopyrightText: 2021 Paul Dersey <pdersey@gmail.com> # SPDX-FileCopyrightText: 2023 Emily Trau <emily@downunderctf.com> # # SPDX-License-Identifier: GPL-3.0-or-later PACKAGE=coreutils PACKAGE_NAME=GNU\ coreutils PACKAGE_BUGREPORT=bug-coreutils@gnu.org PACKAGE_VERSION=5.0 VERSION=5.0 CC = tcc LD = tcc AR = tcc -ar LDFLAGS = -static bindir=$(PREFIX)/bin CFLAGS = -I . -I lib \ -DPACKAGE=\"$(PACKAGE)\" \ -DPACKAGE_NAME=\"$(PACKAGE_NAME)\" \ -DGNU_PACKAGE=\"$(PACKAGE_NAME)\" \ -DPACKAGE_BUGREPORT=\"$(PACKAGE_BUGREPORT)\" \ -DPACKAGE_VERSION=\"$(PACKAGE_VERSION)\" \ -DVERSION=\"$(VERSION)\" \ -DHAVE_LIMITS_H=1 \ -DHAVE_DECL_FREE=1 \ -DHAVE_DECL_MALLOC=1 \ -DHAVE_MALLOC=1 \ -DHAVE_STDLIB_H=1 \ -DHAVE_REALLOC=1 \ -DHAVE_DECL_REALLOC=1 \ -DHAVE_DECL_GETENV=1 \ -DHAVE_DIRENT_H=1 \ -DHAVE_DECL___FPENDING=0 \ -DSTDC_HEADERS=1 \ -DHAVE_ALLOCA_H=1 \ -DHAVE_STRUCT_TIMESPEC=1 \ -DHAVE_STRING_H=1 \ -DHAVE_SYS_TIME_H=1 \ -DTIME_WITH_SYS_TIME=1 \ -DHAVE_STDINT_H=1 \ -DMB_LEN_MAX=16 \ -DLIBDIR=\"$(PREFIX)/lib/mes\" \ -DHAVE_DECL_WCWIDTH=0 \ -DHAVE_SYS_STAT_H=1 \ -DHAVE_INTTYPES_H=1 \ -DHAVE_DECL_MEMCHR=1 \ -DHAVE_MEMORY_H=1 \ -DPENDING_OUTPUT_N_BYTES=1 \ -DCHAR_MIN=0 \ -DLOCALEDIR=NULL \ -DHAVE_FCNTL_H=1 \ -DEPERM=1 \ -DHAVE_DECL_STRTOUL=1 \ -DHAVE_DECL_STRTOULL=1 \ -DHAVE_DECL_STRTOL=1 \ -DHAVE_DECL_STRTOLL=1 \ -DHAVE_RMDIR=1 \ -DRMDIR_ERRNO_NOT_EMPTY=39 \ -DHAVE_DECL_FREE=1 \ -DENOTEMPTY=1 \ -DLSTAT_FOLLOWS_SLASHED_SYMLINK=1 \ -DHAVE_DECL_DIRFD=0 \ -DLC_TIME=\"C\" \ -DLC_COLLATE=\"C\" \ -DHAVE_GETCWD=1 \ -Dmy_strftime=nstrftime \ -Dmkstemp=rpl_mkstemp \ -DDIR_TO_FD\(Dir_p\)=-1 \ -DUTILS_OPEN_MAX=1000 \ -Dmajor_t=unsigned \ -Dminor_t=unsigned .PHONY: all install SRC_DIR=src COREUTILS = basename cat chmod cksum csplit cut dirname echo expand expr factor false fmt fold head hostname id join kill link ln logname mkfifo mkdir mknod nl od paste pathchk pr printf ptx pwd readlink rmdir seq sleep sort split sum tail tee tr tsort unexpand uniq unlink wc whoami tac test touch true yes BINARIES = $(addprefix $(SRC_DIR)/, $(COREUTILS)) ALL=$(BINARIES) $(SRC_DIR)/cp $(SRC_DIR)/ls $(SRC_DIR)/install $(SRC_DIR)/md5sum $(SRC_DIR)/mv $(SRC_DIR)/rm $(SRC_DIR)/sha1sum all: $(BINARIES) $(SRC_DIR)/cp $(SRC_DIR)/ls $(SRC_DIR)/install $(SRC_DIR)/md5sum $(SRC_DIR)/mv $(SRC_DIR)/rm $(SRC_DIR)/sha1sum LIB_DIR = lib LIB_SRC = acl posixtm posixver strftime getopt getopt1 hash hash-pjw addext argmatch backupfile basename canon-host closeout cycle-check diacrit dirname dup-safer error exclude exitfail filemode __fpending file-type fnmatch fopen-safer full-read full-write gethostname getline getstr gettime hard-locale human idcache isdir imaxtostr linebuffer localcharset long-options makepath mbswidth md5 memcasecmp memcoll modechange offtostr path-concat physmem quote quotearg readtokens rpmatch safe-read safe-write same save-cwd savedir settime sha stpcpy stripslash strtoimax strtoumax umaxtostr unicodeio userspec version-etc xgetcwd xgethostname xmalloc xmemcoll xnanosleep xreadlink xstrdup xstrtod xstrtol xstrtoul xstrtoimax xstrtoumax yesno strnlen getcwd sig2str mountlist regex canonicalize mkstemp memrchr euidaccess ftw dirfd obstack strverscmp strftime tempname tsearch LIB_OBJECTS = $(addprefix $(LIB_DIR)/, $(addsuffix .o, $(LIB_SRC))) $(SRC_DIR)/false.c: $(SRC_DIR)/true.c cp $< $@ sed -i -e s/true/false/g \ -e s/success/failure/g \ -e 's/(EXIT_SUCCESS)/(EXIT_FAILURE)/g' \ $@ $(LIB_DIR)/libfettish.a: $(LIB_OBJECTS) $(AR) cr $@ $^ $(BINARIES) : % : %.o $(LIB_DIR)/libfettish.a $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@ $(SRC_DIR)/cp: $(SRC_DIR)/cp.o $(SRC_DIR)/copy.o $(SRC_DIR)/cp-hash.c $(LIB_DIR)/libfettish.a $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@ $(SRC_DIR)/install: $(SRC_DIR)/install.o $(SRC_DIR)/copy.o $(SRC_DIR)/cp-hash.c $(LIB_DIR)/libfettish.a $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@ $(SRC_DIR)/ls: $(SRC_DIR)/ls.o $(SRC_DIR)/ls-ls.o $(LIB_DIR)/libfettish.a $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@ $(SRC_DIR)/md5sum: $(SRC_DIR)/md5.o $(SRC_DIR)/md5sum.o $(LIB_DIR)/libfettish.a $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@ $(SRC_DIR)/mv: $(SRC_DIR)/mv.o $(SRC_DIR)/copy.o $(SRC_DIR)/remove.o $(SRC_DIR)/cp-hash.o $(LIB_DIR)/libfettish.a $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@ $(SRC_DIR)/rm: $(SRC_DIR)/rm.o $(SRC_DIR)/remove.o $(LIB_DIR)/libfettish.a $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@ $(SRC_DIR)/sha1sum: $(SRC_DIR)/sha1sum.o $(SRC_DIR)/md5sum.o $(LIB_DIR)/libfettish.a $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@ install: $(ALL) $(SRC_DIR)/install $^ $(bindir)
/* Copyright (C) 1991, 1992, 1993, 1996, 1997, 1998, 1999, 2001, 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef _FNMATCH_H # define _FNMATCH_H 1 # ifdef __cplusplus extern "C" { # endif # if defined __cplusplus || (defined __STDC__ && __STDC__) || defined WINDOWS32 # if !defined __GLIBC__ || !defined __P # undef __P # define __P(protos) protos # endif # else /* Not C++ or ANSI C. */ # undef __P # define __P(protos) () /* We can get away without defining `const' here only because in this file it is used only inside the prototype for `fnmatch', which is elided in non-ANSI C where `const' is problematical. */ # endif /* C++ or ANSI C. */ /* We #undef these before defining them because some losing systems (HP-UX A.08.07 for example) define these in <unistd.h>. */ # undef FNM_PATHNAME # undef FNM_NOESCAPE # undef FNM_PERIOD /* Bits set in the FLAGS argument to `fnmatch'. */ # define FNM_PATHNAME (1 << 0) /* No wildcard can ever match `/'. */ # define FNM_NOESCAPE (1 << 1) /* Backslashes don't quote special chars. */ # define FNM_PERIOD (1 << 2) /* Leading `.' is matched only explicitly. */ # if !defined _POSIX_C_SOURCE || _POSIX_C_SOURCE < 2 || defined _GNU_SOURCE # define FNM_FILE_NAME FNM_PATHNAME /* Preferred GNU name. */ # define FNM_LEADING_DIR (1 << 3) /* Ignore `/...' after a match. */ # define FNM_CASEFOLD (1 << 4) /* Compare without regard to case. */ # define FNM_EXTMATCH (1 << 5) /* Use ksh-like extended matching. */ # endif /* Value returned by `fnmatch' if STRING does not match PATTERN. */ # define FNM_NOMATCH 1 /* This value is returned if the implementation does not support `fnmatch'. Since this is not the case here it will never be returned but the conformance test suites still require the symbol to be defined. */ # ifdef _XOPEN_SOURCE # define FNM_NOSYS (-1) # endif /* Match NAME against the filename pattern PATTERN, returning zero if it matches, FNM_NOMATCH if not. */ extern int fnmatch __P ((const char *__pattern, const char *__name, int __flags)); # ifdef __cplusplus } # endif #endif /* fnmatch.h */
/* Copyright (C) 1992, 1996, 1997, 1998, 1999, 2003 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ /* * X/Open Portability Guide 4.2: ftw.h */ #ifndef _FTW_H # define _FTW_H 1 # ifdef _LIBC # include <features.h> # else # undef __THROW # define __THROW # undef __BEGIN_DECLS # define __BEGIN_DECLS # undef __END_DECLS # define __END_DECLS # endif # include <sys/types.h> # include <sys/stat.h> /* When compiling stand-alone on a system that does not define __USE_XOPEN_EXTENDED, define that symbol so that all the required declarations appear. */ # if ! defined _LIBC && ! defined __USE_XOPEN_EXTENDED # define FTW_H_STANDALONE 1 # define __USE_XOPEN_EXTENDED 1 # endif __BEGIN_DECLS /* Values for the FLAG argument to the user function passed to `ftw' and 'nftw'. */ enum { FTW_F, /* Regular file. */ # define FTW_F FTW_F FTW_D, /* Directory. */ # define FTW_D FTW_D FTW_DNR, /* Unreadable directory. */ # define FTW_DNR FTW_DNR FTW_NS, /* Unstatable file. */ # define FTW_NS FTW_NS /* Can't chdir to named directory. This can happen only when using FTW_CHDIR. Note that although we can't chdir into that directory, we were able to stat it, so SB (2nd argument to callback) is valid. */ FTW_DCH, # define FTW_DCH FTW_DCH /* Can't chdir to parent of named directory. This can happen only when using FTW_CHDIR. Unlike for FTW_DCH, in this case, SB is not valid. In fact, it is NULL. */ FTW_DCHP, # define FTW_DCHP FTW_DCHP /* nftw calls the user-supplied function at most twice for each directory it encounters. When calling it the first time, it passes this value as the `type'. */ FTW_DPRE, # define FTW_DPRE FTW_DPRE # if defined __USE_BSD || defined __USE_XOPEN_EXTENDED FTW_SL, /* Symbolic link. */ # define FTW_SL FTW_SL # endif # ifdef __USE_XOPEN_EXTENDED /* These flags are only passed from the `nftw' function. */ FTW_DP, /* Directory, all subdirs have been visited. */ # define FTW_DP FTW_DP FTW_SLN /* Symbolic link naming non-existing file. */ # define FTW_SLN FTW_SLN # endif /* extended X/Open */ }; # ifdef __USE_XOPEN_EXTENDED /* Flags for fourth argument of `nftw'. */ enum { FTW_PHYS = 1, /* Perform physical walk, ignore symlinks. */ # define FTW_PHYS FTW_PHYS FTW_MOUNT = 2, /* Report only files on same file system as the argument. */ # define FTW_MOUNT FTW_MOUNT FTW_CHDIR = 4, /* Change to current directory while processing it. */ # define FTW_CHDIR FTW_CHDIR FTW_DEPTH = 8 /* Report files in directory before directory itself.*/ # define FTW_DEPTH FTW_DEPTH }; /* Structure used for fourth argument to callback function for `nftw'. */ struct FTW { int base; int level; int skip; }; # endif /* extended X/Open */ /* Convenient types for callback functions. */ typedef int (*__ftw_func_t) (const char *__filename, const struct stat *__status, int __flag); # ifdef __USE_LARGEFILE64 typedef int (*__ftw64_func_t) (const char *__filename, const struct stat64 *__status, int __flag); # endif # ifdef __USE_XOPEN_EXTENDED typedef int (*__nftw_func_t) (const char *__filename, const struct stat *__status, int __flag, struct FTW *__info); # ifdef __USE_LARGEFILE64 typedef int (*__nftw64_func_t) (const char *__filename, const struct stat64 *__status, int __flag, struct FTW *__info); # endif # endif /* Call a function on every element in a directory tree. */ # ifndef __USE_FILE_OFFSET64 extern int ftw (const char *__dir, __ftw_func_t __func, int __descriptors) __THROW; # else # ifdef __REDIRECT extern int __REDIRECT (ftw, (const char *__dir, __ftw_func_t __func, int __descriptors) __THROW, ftw64); # else # define ftw ftw64 # endif # endif # ifdef __USE_LARGEFILE64 extern int ftw64 (const char *__dir, __ftw64_func_t __func, int __descriptors) __THROW; # endif # ifdef __USE_XOPEN_EXTENDED /* Call a function on every element in a directory tree. FLAG allows to specify the behaviour more detailed. */ # ifndef __USE_FILE_OFFSET64 extern int nftw (const char *__dir, __nftw_func_t __func, int __descriptors, int __flag) __THROW; # else # ifdef __REDIRECT extern int __REDIRECT (nftw, (const char *__dir, __nftw_func_t __func, int __descriptors, int __flag) __THROW, nftw64); # else # define nftw nftw64 # endif # endif # ifdef __USE_LARGEFILE64 extern int nftw64 (const char *__dir, __nftw64_func_t __func, int __descriptors, int __flag) __THROW; # endif # endif /* If we defined __USE_XOPEN_EXTENDED above, undefine it here. */ # ifdef FTW_H_STANDALONE # undef __USE_XOPEN_EXTENDED # endif __END_DECLS #endif /* ftw.h */
/* For use with hsearch(3). */ #ifndef __COMPAR_FN_T # define __COMPAR_FN_T typedef int (*__compar_fn_t) (const void *, const void *); # ifdef __USE_GNU typedef __compar_fn_t comparison_fn_t; # endif #endif /* The tsearch routines are very interesting. They make many assumptions about the compiler. It assumes that the first field in node must be the "key" field, which points to the datum. Everything depends on that. */ /* For tsearch */ typedef enum { preorder, postorder, endorder, leaf } VISIT; /* GCC 2.95 and later have "__restrict"; C99 compilers have "restrict", and "configure" may have defined "restrict". */ #ifndef __restrict # if ! (2 < __GNUC__ || (2 == __GNUC__ && 95 <= __GNUC_MINOR__)) # if defined restrict || 199901L <= __STDC_VERSION__ # define __restrict restrict # else # define __restrict # endif # endif #endif /* Search for an entry matching the given KEY in the tree pointed to by *ROOTP and insert a new element if not found. */ extern void *tsearch (const void *__key, void **__rootp, __compar_fn_t __compar); /* Search for an entry matching the given KEY in the tree pointed to by *ROOTP. If no matching entry is available return NULL. */ extern void *tfind (const void *__key, void *const *__rootp, __compar_fn_t __compar); /* Remove the element matching KEY from the tree pointed to by *ROOTP. */ extern void *tdelete (const void *__restrict __key, void **__restrict __rootp, __compar_fn_t __compar); #ifndef __ACTION_FN_T # define __ACTION_FN_T typedef void (*__action_fn_t) (const void *__nodep, VISIT __value, int __level); #endif /* Walk through the whole tree and call the ACTION callback for every node or leaf. */ extern void twalk (const void *__root, __action_fn_t __action); #ifdef __USE_GNU /* Callback type for function to free a tree node. If the keys are atomic data this function should do nothing. */ typedef void (*__free_fn_t) (void *__nodep); /* Destroy the whole tree, call FREEFCT for each node or leaf. */ extern void tdestroy (void *__root, __free_fn_t __freefct); #endif
SPDX-FileCopyrightText: 2021 Andrius Å tikonas <andrius@stikonas.eu> SPDX-FileCopyrightText: 2021 fosslinux <fosslinux@aussies.space> SPDX-License-Identifier: GPL-2.0-or-later modechange.h uses functions defined in sys/stat.h, so we need to move it to after sys/stat.h include. --- lib/modechange.c 2001-12-09 22:54:19.000000000 +0000 +++ lib/modechange.c 2021-01-17 18:34:22.016427148 +0000 @@ -28,8 +28,8 @@ # include <config.h> #endif -#include "modechange.h" #include <sys/stat.h> +#include "modechange.h" #include "xstrtol.h" #if STDC_HEADERS
/* modechange.c -- file mode manipulation Copyright (C) 1989, 1990, 1997, 1998, 1999, 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@ai.mit.edu> */ /* The ASCII mode string is compiled into a linked list of `struct modechange', which can then be applied to each file to be changed. We do this instead of re-parsing the ASCII string for each file because the compiled form requires less computation to use; when changing the mode of many files, this probably results in a performance gain. */ #if HAVE_CONFIG_H # include <config.h> #endif #include "modechange.h" #include <sys/stat.h> #include "xstrtol.h" #if STDC_HEADERS # include <stdlib.h> #else char *malloc (); #endif #ifndef NULL # define NULL 0 #endif #if STAT_MACROS_BROKEN # undef S_ISDIR #endif #if !defined(S_ISDIR) && defined(S_IFDIR) # define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif /* The traditional octal values corresponding to each mode bit. */ #define SUID 04000 #define SGID 02000 #define SVTX 01000 #define RUSR 00400 #define WUSR 00200 #define XUSR 00100 #define RGRP 00040 #define WGRP 00020 #define XGRP 00010 #define ROTH 00004 #define WOTH 00002 #define XOTH 00001 #define ALLM 07777 /* all octal mode bits */ #ifndef S_ISUID # define S_ISUID SUID #endif #ifndef S_ISGID # define S_ISGID SGID #endif #ifndef S_ISVTX # define S_ISVTX SVTX #endif #ifndef S_IRUSR # define S_IRUSR RUSR #endif #ifndef S_IWUSR # define S_IWUSR WUSR #endif #ifndef S_IXUSR # define S_IXUSR XUSR #endif #ifndef S_IRGRP # define S_IRGRP RGRP #endif #ifndef S_IWGRP # define S_IWGRP WGRP #endif #ifndef S_IXGRP # define S_IXGRP XGRP #endif #ifndef S_IROTH # define S_IROTH ROTH #endif #ifndef S_IWOTH # define S_IWOTH WOTH #endif #ifndef S_IXOTH # define S_IXOTH XOTH #endif #ifndef S_IRWXU # define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) #endif #ifndef S_IRWXG # define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP) #endif #ifndef S_IRWXO # define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH) #endif /* All the mode bits that can be affected by chmod. */ #define CHMOD_MODE_BITS \ (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO) /* Return newly allocated memory to hold one element of type TYPE. */ #define talloc(type) ((type *) malloc (sizeof (type))) /* Create a mode_change entry with the specified `=ddd'-style mode change operation, where NEW_MODE is `ddd'. Return the new entry, or NULL upon failure. */ static struct mode_change * make_node_op_equals (mode_t new_mode) { struct mode_change *p; p = talloc (struct mode_change); if (p == NULL) return p; p->next = NULL; p->op = '='; p->flags = 0; p->value = new_mode; p->affected = CHMOD_MODE_BITS; /* Affect all permissions. */ return p; } /* Append entry E to the end of the link list with the specified HEAD and TAIL. */ static void mode_append_entry (struct mode_change **head, struct mode_change **tail, struct mode_change *e) { if (*head == NULL) *head = *tail = e; else { (*tail)->next = e; *tail = e; } } /* Return a linked list of file mode change operations created from MODE_STRING, an ASCII string that contains either an octal number specifying an absolute mode, or symbolic mode change operations with the form: [ugoa...][[+-=][rwxXstugo...]...][,...] MASKED_OPS is a bitmask indicating which symbolic mode operators (=+-) should not affect bits set in the umask when no users are given. Operators not selected in MASKED_OPS ignore the umask. Return MODE_INVALID if `mode_string' does not contain a valid representation of file mode change operations; return MODE_MEMORY_EXHAUSTED if there is insufficient memory. */ struct mode_change * mode_compile (const char *mode_string, unsigned int masked_ops) { struct mode_change *head; /* First element of the linked list. */ struct mode_change *tail; /* An element of the linked list. */ unsigned long octal_value; /* The mode value, if octal. */ mode_t umask_value; /* The umask value (surprise). */ head = NULL; #ifdef lint tail = NULL; #endif if (xstrtoul (mode_string, NULL, 8, &octal_value, "") == LONGINT_OK) { struct mode_change *p; mode_t mode; if (octal_value != (octal_value & ALLM)) return MODE_INVALID; /* Help the compiler optimize the usual case where mode_t uses the traditional octal representation. */ mode = ((S_ISUID == SUID && S_ISGID == SGID && S_ISVTX == SVTX && S_IRUSR == RUSR && S_IWUSR == WUSR && S_IXUSR == XUSR && S_IRGRP == RGRP && S_IWGRP == WGRP && S_IXGRP == XGRP && S_IROTH == ROTH && S_IWOTH == WOTH && S_IXOTH == XOTH) ? octal_value : (mode_t) ((octal_value & SUID ? S_ISUID : 0) | (octal_value & SGID ? S_ISGID : 0) | (octal_value & SVTX ? S_ISVTX : 0) | (octal_value & RUSR ? S_IRUSR : 0) | (octal_value & WUSR ? S_IWUSR : 0) | (octal_value & XUSR ? S_IXUSR : 0) | (octal_value & RGRP ? S_IRGRP : 0) | (octal_value & WGRP ? S_IWGRP : 0) | (octal_value & XGRP ? S_IXGRP : 0) | (octal_value & ROTH ? S_IROTH : 0) | (octal_value & WOTH ? S_IWOTH : 0) | (octal_value & XOTH ? S_IXOTH : 0))); p = make_node_op_equals (mode); if (p == NULL) return MODE_MEMORY_EXHAUSTED; mode_append_entry (&head, &tail, p); return head; } umask_value = umask (0); umask (umask_value); /* Restore the old value. */ --mode_string; /* One loop iteration for each "ugoa...=+-rwxXstugo...[=+-rwxXstugo...]". */ do { /* Which bits in the mode are operated on. */ mode_t affected_bits = 0; /* `affected_bits' modified by umask. */ mode_t affected_masked; /* Operators to actually use umask on. */ unsigned ops_to_mask = 0; int who_specified_p; affected_bits = 0; ops_to_mask = 0; /* Turn on all the bits in `affected_bits' for each group given. */ for (++mode_string;; ++mode_string) switch (*mode_string) { case 'u': affected_bits |= S_ISUID | S_IRWXU; break; case 'g': affected_bits |= S_ISGID | S_IRWXG; break; case 'o': affected_bits |= S_ISVTX | S_IRWXO; break; case 'a': affected_bits |= CHMOD_MODE_BITS; break; default: goto no_more_affected; } no_more_affected: /* If none specified, affect all bits, except perhaps those set in the umask. */ if (affected_bits) who_specified_p = 1; else { who_specified_p = 0; affected_bits = CHMOD_MODE_BITS; ops_to_mask = masked_ops; } while (*mode_string == '=' || *mode_string == '+' || *mode_string == '-') { struct mode_change *change = talloc (struct mode_change); if (change == NULL) { mode_free (head); return MODE_MEMORY_EXHAUSTED; } change->next = NULL; change->op = *mode_string; /* One of "=+-". */ affected_masked = affected_bits; /* Per the Single Unix Spec, if `who' is not specified and the `=' operator is used, then clear all the bits first. */ if (!who_specified_p && ops_to_mask & (*mode_string == '=' ? MODE_MASK_EQUALS : 0)) { struct mode_change *p = make_node_op_equals (0); if (p == NULL) return MODE_MEMORY_EXHAUSTED; mode_append_entry (&head, &tail, p); } if (ops_to_mask & (*mode_string == '=' ? MODE_MASK_EQUALS : *mode_string == '+' ? MODE_MASK_PLUS : MODE_MASK_MINUS)) affected_masked &= ~umask_value; change->affected = affected_masked; change->value = 0; change->flags = 0; /* Add the element to the tail of the list, so the operations are performed in the correct order. */ mode_append_entry (&head, &tail, change); /* Set `value' according to the bits set in `affected_masked'. */ for (++mode_string;; ++mode_string) switch (*mode_string) { case 'r': change->value |= ((S_IRUSR | S_IRGRP | S_IROTH) & affected_masked); break; case 'w': change->value |= ((S_IWUSR | S_IWGRP | S_IWOTH) & affected_masked); break; case 'X': change->flags |= MODE_X_IF_ANY_X; /* Fall through. */ case 'x': change->value |= ((S_IXUSR | S_IXGRP | S_IXOTH) & affected_masked); break; case 's': /* Set the setuid/gid bits if `u' or `g' is selected. */ change->value |= (S_ISUID | S_ISGID) & affected_masked; break; case 't': /* Set the "save text image" bit if `o' is selected. */ change->value |= S_ISVTX & affected_masked; break; case 'u': /* Set the affected bits to the value of the `u' bits on the same file. */ if (change->value) goto invalid; change->value = S_IRWXU; change->flags |= MODE_COPY_EXISTING; break; case 'g': /* Set the affected bits to the value of the `g' bits on the same file. */ if (change->value) goto invalid; change->value = S_IRWXG; change->flags |= MODE_COPY_EXISTING; break; case 'o': /* Set the affected bits to the value of the `o' bits on the same file. */ if (change->value) goto invalid; change->value = S_IRWXO; change->flags |= MODE_COPY_EXISTING; break; default: goto no_more_values; } no_more_values:; } } while (*mode_string == ','); if (*mode_string == 0) return head; invalid: mode_free (head); return MODE_INVALID; } /* Return a file mode change operation that sets permissions to match those of REF_FILE. Return MODE_BAD_REFERENCE if REF_FILE can't be accessed. */ struct mode_change * mode_create_from_ref (const char *ref_file) { struct mode_change *change; /* the only change element */ struct stat ref_stats; if (stat (ref_file, &ref_stats)) return MODE_BAD_REFERENCE; change = talloc (struct mode_change); if (change == NULL) return MODE_MEMORY_EXHAUSTED; change->op = '='; change->flags = 0; change->affected = CHMOD_MODE_BITS; change->value = ref_stats.st_mode; change->next = NULL; return change; } /* Return file mode OLDMODE, adjusted as indicated by the list of change operations CHANGES. If OLDMODE is a directory, the type `X' change affects it even if no execute bits were set in OLDMODE. The returned value has the S_IFMT bits cleared. */ mode_t mode_adjust (mode_t oldmode, const struct mode_change *changes) { mode_t newmode; /* The adjusted mode and one operand. */ mode_t value; /* The other operand. */ newmode = oldmode & CHMOD_MODE_BITS; for (; changes; changes = changes->next) { if (changes->flags & MODE_COPY_EXISTING) { /* Isolate in `value' the bits in `newmode' to copy, given in the mask `changes->value'. */ value = newmode & changes->value; if (changes->value & S_IRWXU) /* Copy `u' permissions onto `g' and `o'. */ value |= ( (value & S_IRUSR ? S_IRGRP | S_IROTH : 0) | (value & S_IWUSR ? S_IWGRP | S_IWOTH : 0) | (value & S_IXUSR ? S_IXGRP | S_IXOTH : 0)); else if (changes->value & S_IRWXG) /* Copy `g' permissions onto `u' and `o'. */ value |= ( (value & S_IRGRP ? S_IRUSR | S_IROTH : 0) | (value & S_IWGRP ? S_IWUSR | S_IWOTH : 0) | (value & S_IXGRP ? S_IXUSR | S_IXOTH : 0)); else /* Copy `o' permissions onto `u' and `g'. */ value |= ( (value & S_IROTH ? S_IRUSR | S_IRGRP : 0) | (value & S_IWOTH ? S_IWUSR | S_IWGRP : 0) | (value & S_IXOTH ? S_IXUSR | S_IXGRP : 0)); /* In order to change only `u', `g', or `o' permissions, or some combination thereof, clear unselected bits. This cannot be done in mode_compile because the value to which the `changes->affected' mask is applied depends on the old mode of each file. */ value &= changes->affected; } else { value = changes->value; /* If `X', do not affect the execute bits if the file is not a directory and no execute bits are already set. */ if ((changes->flags & MODE_X_IF_ANY_X) && !S_ISDIR (oldmode) && (newmode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) /* Clear the execute bits. */ value &= ~ (S_IXUSR | S_IXGRP | S_IXOTH); } switch (changes->op) { case '=': /* Preserve the previous values in `newmode' of bits that are not affected by this change operation. */ newmode = (newmode & ~changes->affected) | value; break; case '+': newmode |= value; break; case '-': newmode &= ~value; break; } } return newmode; } /* Free the memory used by the list of file mode change operations CHANGES. */ void mode_free (register struct mode_change *changes) { register struct mode_change *next; while (changes) { next = changes->next; free (changes); changes = next; } }
SPDX-FileCopyrightText: 2021 Andrius Å tikonas <andrius@stikonas.eu> SPDX-FileCopyrightText: 2021 fosslinux <fosslinux@aussies.space> SPDX-License-Identifier: GPL-2.0-or-later mbstate_t is a struct that is required. However, it is not defined by mes libc. This implementation was taken from glibc 2.32. --- lib/quotearg.c 2002-11-23 07:08:10.000000000 +0000 +++ lib/quotearg.c 2021-01-17 19:41:59.461095532 +0000 @@ -21,6 +21,7 @@ # include <config.h> #endif +#include "mbstate_t.h" #include "quotearg.h" #include "xalloc.h" --- lib/mbstate_t.h 1970-01-01 01:00:00.000000000 +0100 +++ lib/mbstate_t.h 2021-01-17 19:42:21.341658668 +0000 @@ -0,0 +1,23 @@ +#ifndef ____mbstate_t_defined +#define ____mbstate_t_defined 1 + +/* Integral type unchanged by default argument promotions that can + hold any value corresponding to members of the extended character + set, as well as at least one value that does not correspond to any + member of the extended character set. */ +#ifndef __WINT_TYPE__ +# define __WINT_TYPE__ unsigned int +#endif + +/* Conversion state information. */ +typedef struct +{ + int __count; + union + { + __WINT_TYPE__ __wch; + char __wchb[4]; + } __value; /* Value so far. */ +} mbstate_t; + +#endif
/* quotearg.c - quote arguments for output Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert <eggert@twinsun.com> */ #if HAVE_CONFIG_H # include <config.h> #endif #include "quotearg.h" #include "xalloc.h" #include <ctype.h> #include <errno.h> #include <limits.h> #include <stdlib.h> #include <string.h> #include "gettext.h" #define _(msgid) gettext (msgid) #define N_(msgid) msgid #if HAVE_WCHAR_H /* BSD/OS 4.1 wchar.h requires FILE and struct tm to be declared. */ # include <stdio.h> # include <time.h> # include <wchar.h> #endif #if !HAVE_MBRTOWC /* Disable multibyte processing entirely. Since MB_CUR_MAX is 1, the other macros are defined only for documentation and to satisfy C syntax. */ # undef MB_CUR_MAX # define MB_CUR_MAX 1 # define mbrtowc(pwc, s, n, ps) ((*(pwc) = *(s)) != 0) # define iswprint(wc) isprint ((unsigned char) (wc)) # undef HAVE_MBSINIT #endif #if !defined mbsinit && !HAVE_MBSINIT # define mbsinit(ps) 1 #endif #ifndef iswprint # if HAVE_WCTYPE_H # include <wctype.h> # endif # if !defined iswprint && !HAVE_ISWPRINT # define iswprint(wc) 1 # endif #endif #ifndef SIZE_MAX # define SIZE_MAX ((size_t) -1) #endif #define INT_BITS (sizeof (int) * CHAR_BIT) struct quoting_options { /* Basic quoting style. */ enum quoting_style style; /* Quote the characters indicated by this bit vector even if the quoting style would not normally require them to be quoted. */ int quote_these_too[(UCHAR_MAX / INT_BITS) + 1]; }; /* Names of quoting styles. */ char const *const quoting_style_args[] = { "literal", "shell", "shell-always", "c", "escape", "locale", "clocale", 0 }; /* Correspondences to quoting style names. */ enum quoting_style const quoting_style_vals[] = { literal_quoting_style, shell_quoting_style, shell_always_quoting_style, c_quoting_style, escape_quoting_style, locale_quoting_style, clocale_quoting_style }; /* The default quoting options. */ static struct quoting_options default_quoting_options; /* Allocate a new set of quoting options, with contents initially identical to O if O is not null, or to the default if O is null. It is the caller's responsibility to free the result. */ struct quoting_options * clone_quoting_options (struct quoting_options *o) { int e = errno; struct quoting_options *p = xmalloc (sizeof *p); *p = *(o ? o : &default_quoting_options); errno = e; return p; } /* Get the value of O's quoting style. If O is null, use the default. */ enum quoting_style get_quoting_style (struct quoting_options *o) { return (o ? o : &default_quoting_options)->style; } /* In O (or in the default if O is null), set the value of the quoting style to S. */ void set_quoting_style (struct quoting_options *o, enum quoting_style s) { (o ? o : &default_quoting_options)->style = s; } /* In O (or in the default if O is null), set the value of the quoting options for character C to I. Return the old value. Currently, the only values defined for I are 0 (the default) and 1 (which means to quote the character even if it would not otherwise be quoted). */ int set_char_quoting (struct quoting_options *o, char c, int i) { unsigned char uc = c; int *p = (o ? o : &default_quoting_options)->quote_these_too + uc / INT_BITS; int shift = uc % INT_BITS; int r = (*p >> shift) & 1; *p ^= ((i & 1) ^ r) << shift; return r; } /* MSGID approximates a quotation mark. Return its translation if it has one; otherwise, return either it or "\"", depending on S. */ static char const * gettext_quote (char const *msgid, enum quoting_style s) { char const *translation = _(msgid); if (translation == msgid && s == clocale_quoting_style) translation = "\""; return translation; } /* Place into buffer BUFFER (of size BUFFERSIZE) a quoted version of argument ARG (of size ARGSIZE), using QUOTING_STYLE and the non-quoting-style part of O to control quoting. Terminate the output with a null character, and return the written size of the output, not counting the terminating null. If BUFFERSIZE is too small to store the output string, return the value that would have been returned had BUFFERSIZE been large enough. If ARGSIZE is -1, use the string length of the argument for ARGSIZE. This function acts like quotearg_buffer (BUFFER, BUFFERSIZE, ARG, ARGSIZE, O), except it uses QUOTING_STYLE instead of the quoting style specified by O, and O may not be null. */ static size_t quotearg_buffer_restyled (char *buffer, size_t buffersize, char const *arg, size_t argsize, enum quoting_style quoting_style, struct quoting_options const *o) { size_t i; size_t len = 0; char const *quote_string = 0; size_t quote_string_len = 0; int backslash_escapes = 0; int unibyte_locale = MB_CUR_MAX == 1; #define STORE(c) \ do \ { \ if (len < buffersize) \ buffer[len] = (c); \ len++; \ } \ while (0) switch (quoting_style) { case c_quoting_style: STORE ('"'); backslash_escapes = 1; quote_string = "\""; quote_string_len = 1; break; case escape_quoting_style: backslash_escapes = 1; break; case locale_quoting_style: case clocale_quoting_style: { /* Get translations for open and closing quotation marks. The message catalog should translate "`" to a left quotation mark suitable for the locale, and similarly for "'". If the catalog has no translation, locale_quoting_style quotes `like this', and clocale_quoting_style quotes "like this". For example, an American English Unicode locale should translate "`" to U+201C (LEFT DOUBLE QUOTATION MARK), and should translate "'" to U+201D (RIGHT DOUBLE QUOTATION MARK). A British English Unicode locale should instead translate these to U+2018 (LEFT SINGLE QUOTATION MARK) and U+2019 (RIGHT SINGLE QUOTATION MARK), respectively. */ char const *left = gettext_quote (N_("`"), quoting_style); char const *right = gettext_quote (N_("'"), quoting_style); for (quote_string = left; *quote_string; quote_string++) STORE (*quote_string); backslash_escapes = 1; quote_string = right; quote_string_len = strlen (quote_string); } break; case shell_always_quoting_style: STORE ('\''); quote_string = "'"; quote_string_len = 1; break; default: break; } for (i = 0; ! (argsize == SIZE_MAX ? arg[i] == '\0' : i == argsize); i++) { unsigned char c; unsigned char esc; if (backslash_escapes && quote_string_len && i + quote_string_len <= argsize && memcmp (arg + i, quote_string, quote_string_len) == 0) STORE ('\\'); c = arg[i]; switch (c) { case '\0': if (backslash_escapes) { STORE ('\\'); STORE ('0'); STORE ('0'); c = '0'; } break; case '?': switch (quoting_style) { case shell_quoting_style: goto use_shell_always_quoting_style; case c_quoting_style: if (i + 2 < argsize && arg[i + 1] == '?') switch (arg[i + 2]) { case '!': case '\'': case '(': case ')': case '-': case '/': case '<': case '=': case '>': /* Escape the second '?' in what would otherwise be a trigraph. */ c = arg[i + 2]; i += 2; STORE ('?'); STORE ('\\'); STORE ('?'); break; } break; default: break; } break; case '\a': esc = 'a'; goto c_escape; case '\b': esc = 'b'; goto c_escape; case '\f': esc = 'f'; goto c_escape; case '\n': esc = 'n'; goto c_and_shell_escape; case '\r': esc = 'r'; goto c_and_shell_escape; case '\t': esc = 't'; goto c_and_shell_escape; case '\v': esc = 'v'; goto c_escape; case '\\': esc = c; goto c_and_shell_escape; c_and_shell_escape: if (quoting_style == shell_quoting_style) goto use_shell_always_quoting_style; c_escape: if (backslash_escapes) { c = esc; goto store_escape; } break; case '#': case '~': if (i != 0) break; /* Fall through. */ case ' ': case '!': /* special in bash */ case '"': case '$': case '&': case '(': case ')': case '*': case ';': case '<': case '>': case '[': case '^': /* special in old /bin/sh, e.g. SunOS 4.1.4 */ case '`': case '|': /* A shell special character. In theory, '$' and '`' could be the first bytes of multibyte characters, which means we should check them with mbrtowc, but in practice this doesn't happen so it's not worth worrying about. */ if (quoting_style == shell_quoting_style) goto use_shell_always_quoting_style; break; case '\'': switch (quoting_style) { case shell_quoting_style: goto use_shell_always_quoting_style; case shell_always_quoting_style: STORE ('\''); STORE ('\\'); STORE ('\''); break; default: break; } break; case '%': case '+': case ',': case '-': case '.': case '/': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case ':': case '=': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case ']': case '_': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '{': case '}': /* These characters don't cause problems, no matter what the quoting style is. They cannot start multibyte sequences. */ break; default: /* If we have a multibyte sequence, copy it until we reach its end, find an error, or come back to the initial shift state. For C-like styles, if the sequence has unprintable characters, escape the whole sequence, since we can't easily escape single characters within it. */ { /* Length of multibyte sequence found so far. */ size_t m; int printable; if (unibyte_locale) { m = 1; printable = isprint (c); } else { mbstate_t mbstate; memset (&mbstate, 0, sizeof mbstate); m = 0; printable = 1; if (argsize == SIZE_MAX) argsize = strlen (arg); do { wchar_t w; size_t bytes = mbrtowc (&w, &arg[i + m], argsize - (i + m), &mbstate); if (bytes == 0) break; else if (bytes == (size_t) -1) { printable = 0; break; } else if (bytes == (size_t) -2) { printable = 0; while (i + m < argsize && arg[i + m]) m++; break; } else { if (! iswprint (w)) printable = 0; m += bytes; } } while (! mbsinit (&mbstate)); } if (1 < m || (backslash_escapes && ! printable)) { /* Output a multibyte sequence, or an escaped unprintable unibyte character. */ size_t ilim = i + m; for (;;) { if (backslash_escapes && ! printable) { STORE ('\\'); STORE ('0' + (c >> 6)); STORE ('0' + ((c >> 3) & 7)); c = '0' + (c & 7); } if (ilim <= i + 1) break; STORE (c); c = arg[++i]; } goto store_c; } } } if (! (backslash_escapes && o->quote_these_too[c / INT_BITS] & (1 << (c % INT_BITS)))) goto store_c; store_escape: STORE ('\\'); store_c: STORE (c); } if (quote_string) for (; *quote_string; quote_string++) STORE (*quote_string); if (len < buffersize) buffer[len] = '\0'; return len; use_shell_always_quoting_style: return quotearg_buffer_restyled (buffer, buffersize, arg, argsize, shell_always_quoting_style, o); } /* Place into buffer BUFFER (of size BUFFERSIZE) a quoted version of argument ARG (of size ARGSIZE), using O to control quoting. If O is null, use the default. Terminate the output with a null character, and return the written size of the output, not counting the terminating null. If BUFFERSIZE is too small to store the output string, return the value that would have been returned had BUFFERSIZE been large enough. If ARGSIZE is -1, use the string length of the argument for ARGSIZE. */ size_t quotearg_buffer (char *buffer, size_t buffersize, char const *arg, size_t argsize, struct quoting_options const *o) { struct quoting_options const *p = o ? o : &default_quoting_options; int e = errno; size_t r = quotearg_buffer_restyled (buffer, buffersize, arg, argsize, p->style, p); errno = e; return r; } /* Use storage slot N to return a quoted version of argument ARG. ARG is of size ARGSIZE, but if that is -1, ARG is a null-terminated string. OPTIONS specifies the quoting options. The returned value points to static storage that can be reused by the next call to this function with the same value of N. N must be nonnegative. N is deliberately declared with type "int" to allow for future extensions (using negative values). */ static char * quotearg_n_options (int n, char const *arg, size_t argsize, struct quoting_options const *options) { int e = errno; /* Preallocate a slot 0 buffer, so that the caller can always quote one small component of a "memory exhausted" message in slot 0. */ static char slot0[256]; static unsigned int nslots = 1; unsigned int n0 = n; struct slotvec { size_t size; char *val; }; static struct slotvec slotvec0 = {sizeof slot0, slot0}; static struct slotvec *slotvec = &slotvec0; if (n < 0) abort (); if (nslots <= n0) { unsigned int n1 = n0 + 1; size_t s = n1 * sizeof *slotvec; if (SIZE_MAX / UINT_MAX <= sizeof *slotvec && n1 != s / sizeof *slotvec) xalloc_die (); if (slotvec == &slotvec0) { slotvec = xmalloc (sizeof *slotvec); *slotvec = slotvec0; } slotvec = xrealloc (slotvec, s); memset (slotvec + nslots, 0, (n1 - nslots) * sizeof *slotvec); nslots = n1; } { size_t size = slotvec[n].size; char *val = slotvec[n].val; size_t qsize = quotearg_buffer (val, size, arg, argsize, options); if (size <= qsize) { slotvec[n].size = size = qsize + 1; slotvec[n].val = val = xrealloc (val == slot0 ? 0 : val, size); quotearg_buffer (val, size, arg, argsize, options); } errno = e; return val; } } char * quotearg_n (int n, char const *arg) { return quotearg_n_options (n, arg, SIZE_MAX, &default_quoting_options); } char * quotearg (char const *arg) { return quotearg_n (0, arg); } /* Return quoting options for STYLE, with no extra quoting. */ static struct quoting_options quoting_options_from_style (enum quoting_style style) { struct quoting_options o; o.style = style; memset (o.quote_these_too, 0, sizeof o.quote_these_too); return o; } char * quotearg_n_style (int n, enum quoting_style s, char const *arg) { struct quoting_options const o = quoting_options_from_style (s); return quotearg_n_options (n, arg, SIZE_MAX, &o); } char * quotearg_n_style_mem (int n, enum quoting_style s, char const *arg, size_t argsize) { struct quoting_options const o = quoting_options_from_style (s); return quotearg_n_options (n, arg, argsize, &o); } char * quotearg_style (enum quoting_style s, char const *arg) { return quotearg_n_style (0, s, arg); } char * quotearg_char (char const *arg, char ch) { struct quoting_options options; options = default_quoting_options; set_char_quoting (&options, ch, 1); return quotearg_n_options (0, arg, SIZE_MAX, &options); } char * quotearg_colon (char const *arg) { return quotearg_char (arg, ':'); }
SPDX-FileCopyrightText: 2021 Andrius Å tikonas <andrius@stikonas.eu> SPDX-FileCopyrightText: 2021 fosslinux <fosslinux@aussies.space> SPDX-License-Identifier: GPL-2.0-or-later strcoll() does not exist in mes libc, change it to strcmp. --- src/ls.c +++ src/ls.c @@ -2597,7 +2597,7 @@ xstrcoll (char const *a, char const *b) { int diff; errno = 0; - diff = strcoll (a, b); + diff = strcmp (a, b); if (errno) { error (0, errno, _("cannot compare file names %s and %s"),
/* `dir', `vdir' and `ls' directory listing programs for GNU. Copyright (C) 85, 88, 90, 91, 1995-2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* If ls_mode is LS_MULTI_COL, the multi-column format is the default regardless of the type of output device. This is for the `dir' program. If ls_mode is LS_LONG_FORMAT, the long format is the default regardless of the type of output device. This is for the `vdir' program. If ls_mode is LS_LS, the output format depends on whether the output device is a terminal. This is for the `ls' program. */ /* Written by Richard Stallman and David MacKenzie. */ /* Color support by Peter Anvin <Peter.Anvin@linux.org> and Dennis Flaherty <dennisf@denix.elk.miles.com> based on original patches by Greg Lee <lee@uhunix.uhcc.hawaii.edu>. */ #ifdef _AIX #pragma alloca #endif #include <config.h> #include <sys/types.h> #if HAVE_TERMIOS_H # include <termios.h> #endif #ifdef GWINSZ_IN_SYS_IOCTL # include <sys/ioctl.h> #endif #ifdef WINSIZE_IN_PTEM # include <sys/stream.h> # include <sys/ptem.h> #endif #include <stdio.h> #include <assert.h> #include <setjmp.h> #include <grp.h> #include <pwd.h> #include <getopt.h> #include <signal.h> /* Get MB_CUR_MAX. */ #if HAVE_STDLIB_H # include <stdlib.h> #endif /* Get mbstate_t, mbrtowc(), mbsinit(), wcwidth(). */ #if HAVE_WCHAR_H # include <wchar.h> #endif /* Get iswprint(). */ #if HAVE_WCTYPE_H # include <wctype.h> #endif #if !defined iswprint && !HAVE_ISWPRINT # define iswprint(wc) 1 #endif #ifndef HAVE_DECL_WCWIDTH "this configure-time declaration test was not run" #endif #if !HAVE_DECL_WCWIDTH int wcwidth (); #endif /* If wcwidth() doesn't exist, assume all printable characters have width 1. */ #ifndef wcwidth # if !HAVE_WCWIDTH # define wcwidth(wc) ((wc) == 0 ? 0 : iswprint (wc) ? 1 : -1) # endif #endif #include "system.h" #include <fnmatch.h> #include "acl.h" #include "argmatch.h" #include "dev-ino.h" #include "dirname.h" #include "dirfd.h" #include "error.h" #include "full-write.h" #include "hard-locale.h" #include "hash.h" #include "human.h" #include "filemode.h" #include "inttostr.h" #include "ls.h" #include "mbswidth.h" #include "obstack.h" #include "path-concat.h" #include "quote.h" #include "quotearg.h" #include "same.h" #include "strftime.h" #include "strverscmp.h" #include "xstrtol.h" #include "xreadlink.h" #define PROGRAM_NAME (ls_mode == LS_LS ? "ls" \ : (ls_mode == LS_MULTI_COL \ ? "dir" : "vdir")) #define AUTHORS N_ ("Richard Stallman and David MacKenzie") #define obstack_chunk_alloc malloc #define obstack_chunk_free free /* Return an int indicating the result of comparing two integers. Subtracting doesn't always work, due to overflow. */ #define longdiff(a, b) ((a) < (b) ? -1 : (a) > (b)) /* The field width for inode numbers. On some hosts inode numbers are 64 bits, so columns won't line up exactly when a huge inode number is encountered, but in practice 7 digits is usually enough. */ #ifndef INODE_DIGITS # define INODE_DIGITS 7 #endif /* Arrange to make lstat calls go through the wrapper function on systems with an lstat function that does not dereference symlinks that are specified with a trailing slash. */ #if ! LSTAT_FOLLOWS_SLASHED_SYMLINK int rpl_lstat (const char *, struct stat *); # undef lstat # define lstat(Name, Stat_buf) rpl_lstat(Name, Stat_buf) #endif #if HAVE_STRUCT_DIRENT_D_TYPE && defined DTTOIF # define DT_INIT(Val) = Val #else # define DT_INIT(Val) /* empty */ #endif #ifdef ST_MTIM_NSEC # define TIMESPEC_NS(timespec) ((timespec).ST_MTIM_NSEC) #else # define TIMESPEC_NS(timespec) 0 #endif #if ! HAVE_STRUCT_STAT_ST_AUTHOR # define st_author st_uid #endif /* Cray/Unicos DMF: use the file's migrated, not real, status */ #if HAVE_ST_DM_MODE # define ST_DM_MODE(Stat_buf) ((Stat_buf).st_dm_mode) #else # define ST_DM_MODE(Stat_buf) ((Stat_buf).st_mode) #endif #ifndef LOGIN_NAME_MAX # if _POSIX_LOGIN_NAME_MAX # define LOGIN_NAME_MAX _POSIX_LOGIN_NAME_MAX # else # define LOGIN_NAME_MAX 17 # endif #endif /* The maximum length of a string representation of a user or group ID, not counting any terminating NUL byte. */ #define ID_LENGTH_MAX \ MAX (LOGIN_NAME_MAX - 1, LONGEST_HUMAN_READABLE) enum filetype { unknown DT_INIT (DT_UNKNOWN), fifo DT_INIT (DT_FIFO), chardev DT_INIT (DT_CHR), directory DT_INIT (DT_DIR), blockdev DT_INIT (DT_BLK), normal DT_INIT (DT_REG), symbolic_link DT_INIT (DT_LNK), sock DT_INIT (DT_SOCK), arg_directory DT_INIT (2 * (DT_UNKNOWN | DT_FIFO | DT_CHR | DT_DIR | DT_BLK | DT_REG | DT_LNK | DT_SOCK)) }; struct fileinfo { /* The file name. */ char *name; struct stat stat; /* For symbolic link, name of the file linked to, otherwise zero. */ char *linkname; /* For symbolic link and long listing, st_mode of file linked to, otherwise zero. */ mode_t linkmode; /* For symbolic link and color printing, 1 if linked-to file exists, otherwise 0. */ int linkok; enum filetype filetype; #if HAVE_ACL /* For long listings, true if the file has an access control list. */ bool have_acl; #endif }; #if HAVE_ACL # define FILE_HAS_ACL(F) ((F)->have_acl) #else # define FILE_HAS_ACL(F) 0 #endif #define LEN_STR_PAIR(s) sizeof (s) - 1, s /* Null is a valid character in a color indicator (think about Epson printers, for example) so we have to use a length/buffer string type. */ struct bin_str { int len; /* Number of bytes */ const char *string; /* Pointer to the same */ }; #ifndef STDC_HEADERS time_t time (); #endif char *getgroup (); char *getuser (); static size_t quote_name (FILE *out, const char *name, struct quoting_options const *options, size_t *width); static char *make_link_path (const char *path, const char *linkname); static int decode_switches (int argc, char **argv); static int file_interesting (const struct dirent *next); static uintmax_t gobble_file (const char *name, enum filetype type, int explicit_arg, const char *dirname); static void print_color_indicator (const char *name, mode_t mode, int linkok); static void put_indicator (const struct bin_str *ind); static int put_indicator_direct (const struct bin_str *ind); static int length_of_file_name_and_frills (const struct fileinfo *f); static void add_ignore_pattern (const char *pattern); static void attach (char *dest, const char *dirname, const char *name); static void clear_files (void); static void extract_dirs_from_files (const char *dirname, int ignore_dot_and_dot_dot); static void get_link_name (const char *filename, struct fileinfo *f); static void indent (int from, int to); static void init_column_info (void); static void print_current_files (void); static void print_dir (const char *name, const char *realname); static void print_file_name_and_frills (const struct fileinfo *f); static void print_horizontal (void); static void print_long_format (const struct fileinfo *f); static void print_many_per_line (void); static void print_name_with_quoting (const char *p, mode_t mode, int linkok, struct obstack *stack); static void prep_non_filename_text (void); static void print_type_indicator (mode_t mode); static void print_with_commas (void); static void queue_directory (const char *name, const char *realname); static void sort_files (void); static void parse_ls_color (void); void usage (int status); /* The name the program was run with, stripped of any leading path. */ char *program_name; /* Initial size of hash table. Most hierarchies are likely to be shallower than this. */ #define INITIAL_TABLE_SIZE 30 /* The set of `active' directories, from the current command-line argument to the level in the hierarchy at which files are being listed. A directory is represented by its device and inode numbers (struct dev_ino). A directory is added to this set when ls begins listing it or its entries, and it is removed from the set just after ls has finished processing it. This set is used solely to detect loops, e.g., with mkdir loop; cd loop; ln -s ../loop sub; ls -RL */ static Hash_table *active_dir_set; #define LOOP_DETECT (!!active_dir_set) /* The table of files in the current directory: `files' points to a vector of `struct fileinfo', one per file. `nfiles' is the number of elements space has been allocated for. `files_index' is the number actually in use. */ /* Address of block containing the files that are described. */ static struct fileinfo *files; /* FIXME: rename this to e.g. cwd_file */ /* Length of block that `files' points to, measured in files. */ static int nfiles; /* FIXME: rename this to e.g. cwd_n_alloc */ /* Index of first unused in `files'. */ static int files_index; /* FIXME: rename this to e.g. cwd_n_used */ /* When nonzero, in a color listing, color each symlink name according to the type of file it points to. Otherwise, color them according to the `ln' directive in LS_COLORS. Dangling (orphan) symlinks are treated specially, regardless. This is set when `ln=target' appears in LS_COLORS. */ static int color_symlink_as_referent; /* mode of appropriate file for colorization */ #define FILE_OR_LINK_MODE(File) \ ((color_symlink_as_referent && (File)->linkok) \ ? (File)->linkmode : (File)->stat.st_mode) /* Record of one pending directory waiting to be listed. */ struct pending { char *name; /* If the directory is actually the file pointed to by a symbolic link we were told to list, `realname' will contain the name of the symbolic link, otherwise zero. */ char *realname; struct pending *next; }; static struct pending *pending_dirs; /* Current time in seconds and nanoseconds since 1970, updated as needed when deciding whether a file is recent. */ static time_t current_time = TYPE_MINIMUM (time_t); static int current_time_ns = -1; /* The number of digits to use for block sizes. 4, or more if needed for bigger numbers. */ static int block_size_size; /* Option flags */ /* long_format for lots of info, one per line. one_per_line for just names, one per line. many_per_line for just names, many per line, sorted vertically. horizontal for just names, many per line, sorted horizontally. with_commas for just names, many per line, separated by commas. -l (and other options that imply -l), -1, -C, -x and -m control this parameter. */ enum format { long_format, /* -l and other options that imply -l */ one_per_line, /* -1 */ many_per_line, /* -C */ horizontal, /* -x */ with_commas /* -m */ }; static enum format format; /* `full-iso' uses full ISO-style dates and times. `long-iso' uses longer ISO-style time stamps, though shorter than `full-iso'. `iso' uses shorter ISO-style time stamps. `locale' uses locale-dependent time stamps. */ enum time_style { full_iso_time_style, /* --time-style=full-iso */ long_iso_time_style, /* --time-style=long-iso */ iso_time_style, /* --time-style=iso */ locale_time_style /* --time-style=locale */ }; static char const *const time_style_args[] = { "full-iso", "long-iso", "iso", "locale", 0 }; static enum time_style const time_style_types[] = { full_iso_time_style, long_iso_time_style, iso_time_style, locale_time_style, 0 }; /* Type of time to print or sort by. Controlled by -c and -u. */ enum time_type { time_mtime, /* default */ time_ctime, /* -c */ time_atime /* -u */ }; static enum time_type time_type; /* The file characteristic to sort by. Controlled by -t, -S, -U, -X, -v. */ enum sort_type { sort_none, /* -U */ sort_name, /* default */ sort_extension, /* -X */ sort_time, /* -t */ sort_size, /* -S */ sort_version /* -v */ }; static enum sort_type sort_type; /* Direction of sort. 0 means highest first if numeric, lowest first if alphabetic; these are the defaults. 1 means the opposite order in each case. -r */ static int sort_reverse; /* Nonzero means to display owner information. -g turns this off. */ static int print_owner = 1; /* Nonzero means to display author information. */ static bool print_author; /* Nonzero means to display group information. -G and -o turn this off. */ static int print_group = 1; /* Nonzero means print the user and group id's as numbers rather than as names. -n */ static int numeric_ids; /* Nonzero means mention the size in blocks of each file. -s */ static int print_block_size; /* Human-readable options for output. */ static int human_output_opts; /* The units to use when printing sizes other than file sizes. */ static uintmax_t output_block_size; /* Likewise, but for file sizes. */ static uintmax_t file_output_block_size = 1; /* Precede each line of long output (per file) with a string like `m,n:' where M is the number of characters after the `:' and before the filename and N is the length of the filename. Using this format, Emacs' dired mode starts up twice as fast, and can handle all strange characters in file names. */ static int dired; /* `none' means don't mention the type of files. `classify' means mention file types and mark executables. `file_type' means mention only file types. Controlled by -F, -p, and --indicator-style. */ enum indicator_style { none, /* --indicator-style=none */ classify, /* -F, --indicator-style=classify */ file_type /* -p, --indicator-style=file-type */ }; static enum indicator_style indicator_style; /* Names of indicator styles. */ static char const *const indicator_style_args[] = { "none", "classify", "file-type", 0 }; static enum indicator_style const indicator_style_types[]= { none, classify, file_type }; /* Nonzero means use colors to mark types. Also define the different colors as well as the stuff for the LS_COLORS environment variable. The LS_COLORS variable is now in a termcap-like format. */ static int print_with_color; enum color_type { color_never, /* 0: default or --color=never */ color_always, /* 1: --color=always */ color_if_tty /* 2: --color=tty */ }; enum Dereference_symlink { DEREF_UNDEFINED = 1, DEREF_NEVER, DEREF_COMMAND_LINE_ARGUMENTS, /* -H */ DEREF_COMMAND_LINE_SYMLINK_TO_DIR, /* the default, in certain cases */ DEREF_ALWAYS /* -L */ }; enum indicator_no { C_LEFT, C_RIGHT, C_END, C_NORM, C_FILE, C_DIR, C_LINK, C_FIFO, C_SOCK, C_BLK, C_CHR, C_MISSING, C_ORPHAN, C_EXEC, C_DOOR }; static const char *const indicator_name[]= { "lc", "rc", "ec", "no", "fi", "di", "ln", "pi", "so", "bd", "cd", "mi", "or", "ex", "do", NULL }; struct color_ext_type { struct bin_str ext; /* The extension we're looking for */ struct bin_str seq; /* The sequence to output when we do */ struct color_ext_type *next; /* Next in list */ }; static struct bin_str color_indicator[] = { { LEN_STR_PAIR ("\033[") }, /* lc: Left of color sequence */ { LEN_STR_PAIR ("m") }, /* rc: Right of color sequence */ { 0, NULL }, /* ec: End color (replaces lc+no+rc) */ { LEN_STR_PAIR ("0") }, /* no: Normal */ { LEN_STR_PAIR ("0") }, /* fi: File: default */ { LEN_STR_PAIR ("01;34") }, /* di: Directory: bright blue */ { LEN_STR_PAIR ("01;36") }, /* ln: Symlink: bright cyan */ { LEN_STR_PAIR ("33") }, /* pi: Pipe: yellow/brown */ { LEN_STR_PAIR ("01;35") }, /* so: Socket: bright magenta */ { LEN_STR_PAIR ("01;33") }, /* bd: Block device: bright yellow */ { LEN_STR_PAIR ("01;33") }, /* cd: Char device: bright yellow */ { 0, NULL }, /* mi: Missing file: undefined */ { 0, NULL }, /* or: Orphanned symlink: undefined */ { LEN_STR_PAIR ("01;32") }, /* ex: Executable: bright green */ { LEN_STR_PAIR ("01;35") } /* do: Door: bright magenta */ }; /* FIXME: comment */ static struct color_ext_type *color_ext_list = NULL; /* Buffer for color sequences */ static char *color_buf; /* Nonzero means to check for orphaned symbolic link, for displaying colors. */ static int check_symlink_color; /* Nonzero means mention the inode number of each file. -i */ static int print_inode; /* What to do with symbolic links. Affected by -d, -F, -H, -l (and other options that imply -l), and -L. */ static enum Dereference_symlink dereference; /* Nonzero means when a directory is found, display info on its contents. -R */ static int recursive; /* Nonzero means when an argument is a directory name, display info on it itself. -d */ static int immediate_dirs; /* Nonzero means don't omit files whose names start with `.'. -A */ static int all_files; /* Nonzero means don't omit files `.' and `..' This flag implies `all_files'. -a */ static int really_all_files; /* A linked list of shell-style globbing patterns. If a non-argument file name matches any of these patterns, it is omitted. Controlled by -I. Multiple -I options accumulate. The -B option adds `*~' and `.*~' to this list. */ struct ignore_pattern { const char *pattern; struct ignore_pattern *next; }; static struct ignore_pattern *ignore_patterns; /* Nonzero means output nongraphic chars in file names as `?'. (-q, --hide-control-chars) qmark_funny_chars and the quoting style (-Q, --quoting-style=WORD) are independent. The algorithm is: first, obey the quoting style to get a string representing the file name; then, if qmark_funny_chars is set, replace all nonprintable chars in that string with `?'. It's necessary to replace nonprintable chars even in quoted strings, because we don't want to mess up the terminal if control chars get sent to it, and some quoting methods pass through control chars as-is. */ static int qmark_funny_chars; /* Quoting options for file and dir name output. */ static struct quoting_options *filename_quoting_options; static struct quoting_options *dirname_quoting_options; /* The number of chars per hardware tab stop. Setting this to zero inhibits the use of TAB characters for separating columns. -T */ static int tabsize; /* Nonzero means we are listing the working directory because no non-option arguments were given. */ static int dir_defaulted; /* Nonzero means print each directory name before listing it. */ static int print_dir_name; /* The line length to use for breaking lines in many-per-line format. Can be set with -w. */ static int line_length; /* If nonzero, the file listing format requires that stat be called on each file. */ static int format_needs_stat; /* Similar to `format_needs_stat', but set if only the file type is needed. */ static int format_needs_type; /* strftime formats for non-recent and recent files, respectively, in -l output. */ static char const *long_time_format[2] = { /* strftime format for non-recent files (older than 6 months), in -l output when --time-style=locale is specified. This should contain the year, month and day (at least), in an order that is understood by people in your locale's territory. Please try to keep the number of used screen columns small, because many people work in windows with only 80 columns. But make this as wide as the other string below, for recent files. */ N_("%b %e %Y"), /* strftime format for recent files (younger than 6 months), in -l output when --time-style=locale is specified. This should contain the month, day and time (at least), in an order that is understood by people in your locale's territory. Please try to keep the number of used screen columns small, because many people work in windows with only 80 columns. But make this as wide as the other string above, for non-recent files. */ N_("%b %e %H:%M") }; /* The exit status to use if we don't get any fatal errors. */ static int exit_status; /* For long options that have no equivalent short option, use a non-character as a pseudo short option, starting with CHAR_MAX + 1. */ enum { AUTHOR_OPTION = CHAR_MAX + 1, BLOCK_SIZE_OPTION, COLOR_OPTION, DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR_OPTION, FORMAT_OPTION, FULL_TIME_OPTION, INDICATOR_STYLE_OPTION, QUOTING_STYLE_OPTION, SHOW_CONTROL_CHARS_OPTION, SI_OPTION, SORT_OPTION, TIME_OPTION, TIME_STYLE_OPTION }; static struct option const long_options[] = { {"all", no_argument, 0, 'a'}, {"escape", no_argument, 0, 'b'}, {"directory", no_argument, 0, 'd'}, {"dired", no_argument, 0, 'D'}, {"full-time", no_argument, 0, FULL_TIME_OPTION}, {"human-readable", no_argument, 0, 'h'}, {"inode", no_argument, 0, 'i'}, {"kilobytes", no_argument, 0, 'k'}, /* long form is obsolescent */ {"numeric-uid-gid", no_argument, 0, 'n'}, {"no-group", no_argument, 0, 'G'}, {"hide-control-chars", no_argument, 0, 'q'}, {"reverse", no_argument, 0, 'r'}, {"size", no_argument, 0, 's'}, {"width", required_argument, 0, 'w'}, {"almost-all", no_argument, 0, 'A'}, {"ignore-backups", no_argument, 0, 'B'}, {"classify", no_argument, 0, 'F'}, {"file-type", no_argument, 0, 'p'}, {"si", no_argument, 0, SI_OPTION}, {"dereference-command-line", no_argument, 0, 'H'}, {"dereference-command-line-symlink-to-dir", no_argument, 0, DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR_OPTION}, {"ignore", required_argument, 0, 'I'}, {"indicator-style", required_argument, 0, INDICATOR_STYLE_OPTION}, {"dereference", no_argument, 0, 'L'}, {"literal", no_argument, 0, 'N'}, {"quote-name", no_argument, 0, 'Q'}, {"quoting-style", required_argument, 0, QUOTING_STYLE_OPTION}, {"recursive", no_argument, 0, 'R'}, {"format", required_argument, 0, FORMAT_OPTION}, {"show-control-chars", no_argument, 0, SHOW_CONTROL_CHARS_OPTION}, {"sort", required_argument, 0, SORT_OPTION}, {"tabsize", required_argument, 0, 'T'}, {"time", required_argument, 0, TIME_OPTION}, {"time-style", required_argument, 0, TIME_STYLE_OPTION}, {"color", optional_argument, 0, COLOR_OPTION}, {"block-size", required_argument, 0, BLOCK_SIZE_OPTION}, {"author", no_argument, 0, AUTHOR_OPTION}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; static char const *const format_args[] = { "verbose", "long", "commas", "horizontal", "across", "vertical", "single-column", 0 }; static enum format const format_types[] = { long_format, long_format, with_commas, horizontal, horizontal, many_per_line, one_per_line }; static char const *const sort_args[] = { "none", "time", "size", "extension", "version", 0 }; static enum sort_type const sort_types[] = { sort_none, sort_time, sort_size, sort_extension, sort_version }; static char const *const time_args[] = { "atime", "access", "use", "ctime", "status", 0 }; static enum time_type const time_types[] = { time_atime, time_atime, time_atime, time_ctime, time_ctime }; static char const *const color_args[] = { /* force and none are for compatibility with another color-ls version */ "always", "yes", "force", "never", "no", "none", "auto", "tty", "if-tty", 0 }; static enum color_type const color_types[] = { color_always, color_always, color_always, color_never, color_never, color_never, color_if_tty, color_if_tty, color_if_tty }; /* Information about filling a column. */ struct column_info { int valid_len; int line_len; int *col_arr; }; /* Array with information about column filledness. */ static struct column_info *column_info; /* Maximum number of columns ever possible for this display. */ static int max_idx; /* The minimum width of a colum is 3: 1 character for the name and 2 for the separating white space. */ #define MIN_COLUMN_WIDTH 3 /* This zero-based index is used solely with the --dired option. When that option is in effect, this counter is incremented for each character of output generated by this program so that the beginning and ending indices (in that output) of every file name can be recorded and later output themselves. */ static size_t dired_pos; #define DIRED_PUTCHAR(c) do {putchar ((c)); ++dired_pos;} while (0) /* Write S to STREAM and increment DIRED_POS by S_LEN. */ #define DIRED_FPUTS(s, stream, s_len) \ do {fputs ((s), (stream)); dired_pos += s_len;} while (0) /* Like DIRED_FPUTS, but for use when S is a literal string. */ #define DIRED_FPUTS_LITERAL(s, stream) \ do {fputs ((s), (stream)); dired_pos += sizeof((s)) - 1;} while (0) #define DIRED_INDENT() \ do \ { \ if (dired) \ DIRED_FPUTS_LITERAL (" ", stdout); \ } \ while (0) /* With --dired, store pairs of beginning and ending indices of filenames. */ static struct obstack dired_obstack; /* With --dired, store pairs of beginning and ending indices of any directory names that appear as headers (just before `total' line) for lists of directory entries. Such directory names are seen when listing hierarchies using -R and when a directory is listed with at least one other command line argument. */ static struct obstack subdired_obstack; /* Save the current index on the specified obstack, OBS. */ #define PUSH_CURRENT_DIRED_POS(obs) \ do \ { \ if (dired) \ obstack_grow ((obs), &dired_pos, sizeof (dired_pos)); \ } \ while (0) /* With -R, this stack is used to help detect directory cycles. The device/inode pairs on this stack mirror the pairs in the active_dir_set hash table. */ static struct obstack dev_ino_obstack; /* Push a pair onto the device/inode stack. */ #define DEV_INO_PUSH(Dev, Ino) \ do \ { \ struct dev_ino *di; \ obstack_blank (&dev_ino_obstack, sizeof (struct dev_ino)); \ di = -1 + (struct dev_ino *) obstack_next_free (&dev_ino_obstack); \ di->st_dev = (Dev); \ di->st_ino = (Ino); \ } \ while (0) /* Pop a dev/ino struct off the global dev_ino_obstack and return that struct. */ static struct dev_ino dev_ino_pop (void) { assert (sizeof (struct dev_ino) <= obstack_object_size (&dev_ino_obstack)); obstack_blank (&dev_ino_obstack, -(int) (sizeof (struct dev_ino))); return *(struct dev_ino*) obstack_next_free (&dev_ino_obstack); } #define ASSERT_MATCHING_DEV_INO(Name, Di) \ do \ { \ struct stat sb; \ assert (Name); \ assert (0 <= stat (Name, &sb)); \ assert (sb.st_dev == Di.st_dev); \ assert (sb.st_ino == Di.st_ino); \ } \ while (0) /* Write to standard output PREFIX, followed by the quoting style and a space-separated list of the integers stored in OS all on one line. */ static void dired_dump_obstack (const char *prefix, struct obstack *os) { int n_pos; n_pos = obstack_object_size (os) / sizeof (dired_pos); if (n_pos > 0) { int i; size_t *pos; pos = (size_t *) obstack_finish (os); fputs (prefix, stdout); for (i = 0; i < n_pos; i++) printf (" %lu", (unsigned long) pos[i]); putchar ('\n'); } } static unsigned int dev_ino_hash (void const *x, unsigned int table_size) { struct dev_ino const *p = x; return (uintmax_t) p->st_ino % table_size; } static bool dev_ino_compare (void const *x, void const *y) { struct dev_ino const *a = x; struct dev_ino const *b = y; return SAME_INODE (*a, *b) ? true : false; } static void dev_ino_free (void *x) { free (x); } /* Add the device/inode pair (P->st_dev/P->st_ino) to the set of active directories. Return nonzero if there is already a matching entry in the table. Otherwise, return zero. */ static int visit_dir (dev_t dev, ino_t ino) { struct dev_ino *ent; struct dev_ino *ent_from_table; int found_match; ent = XMALLOC (struct dev_ino, 1); ent->st_ino = ino; ent->st_dev = dev; /* Attempt to insert this entry into the table. */ ent_from_table = hash_insert (active_dir_set, ent); if (ent_from_table == NULL) { /* Insertion failed due to lack of memory. */ xalloc_die (); } found_match = (ent_from_table != ent); if (found_match) { /* ent was not inserted, so free it. */ free (ent); } return found_match; } static void free_pending_ent (struct pending *p) { if (p->name) free (p->name); if (p->realname) free (p->realname); free (p); } static void restore_default_color (void) { if (put_indicator_direct (&color_indicator[C_LEFT]) == 0) put_indicator_direct (&color_indicator[C_RIGHT]); } /* Upon interrupt, suspend, hangup, etc. ensure that the terminal text color is restored to the default. */ static void sighandler (int sig) { #ifndef SA_NOCLDSTOP signal (sig, SIG_IGN); #endif restore_default_color (); /* SIGTSTP is special, since the application can receive that signal more than once. In this case, don't set the signal handler to the default. Instead, just raise the uncatchable SIGSTOP. */ if (sig == SIGTSTP) { sig = SIGSTOP; } else { #ifdef SA_NOCLDSTOP struct sigaction sigact; sigact.sa_handler = SIG_DFL; sigemptyset (&sigact.sa_mask); sigact.sa_flags = 0; sigaction (sig, &sigact, NULL); #else signal (sig, SIG_DFL); #endif } raise (sig); } int main (int argc, char **argv) { register int i; register struct pending *thispend; unsigned int n_files; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); #define N_ENTRIES(Array) (sizeof Array / sizeof *(Array)) assert (N_ENTRIES (color_indicator) + 1 == N_ENTRIES (indicator_name)); exit_status = 0; dir_defaulted = 1; print_dir_name = 1; pending_dirs = 0; i = decode_switches (argc, argv); if (print_with_color) parse_ls_color (); /* Test print_with_color again, because the call to parse_ls_color may have just reset it -- e.g., if LS_COLORS is invalid. */ if (print_with_color) { prep_non_filename_text (); /* Avoid following symbolic links when possible. */ if (color_indicator[C_ORPHAN].string != NULL || (color_indicator[C_MISSING].string != NULL && format == long_format)) check_symlink_color = 1; { unsigned j; static int const sigs[] = { SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM, SIGTSTP }; unsigned nsigs = sizeof sigs / sizeof *sigs; #ifdef SA_NOCLDSTOP struct sigaction oldact, newact; sigset_t caught_signals; sigemptyset (&caught_signals); for (j = 0; j < nsigs; j++) sigaddset (&caught_signals, sigs[j]); newact.sa_handler = sighandler; newact.sa_mask = caught_signals; newact.sa_flags = 0; #endif for (j = 0; j < nsigs; j++) { int sig = sigs[j]; #ifdef SA_NOCLDSTOP sigaction (sig, NULL, &oldact); if (oldact.sa_handler != SIG_IGN) sigaction (sig, &newact, NULL); #else if (signal (sig, SIG_IGN) != SIG_IGN) signal (sig, sighandler); #endif } } } if (dereference == DEREF_UNDEFINED) dereference = ((immediate_dirs || indicator_style == classify || format == long_format) ? DEREF_NEVER : DEREF_COMMAND_LINE_SYMLINK_TO_DIR); /* When using -R, initialize a data structure we'll use to detect any directory cycles. */ if (recursive) { active_dir_set = hash_initialize (INITIAL_TABLE_SIZE, NULL, dev_ino_hash, dev_ino_compare, dev_ino_free); if (active_dir_set == NULL) xalloc_die (); obstack_init (&dev_ino_obstack); } format_needs_stat = sort_type == sort_time || sort_type == sort_size || format == long_format || dereference == DEREF_ALWAYS || print_block_size || print_inode; format_needs_type = (format_needs_stat == 0 && (recursive || print_with_color || indicator_style != none)); if (dired) { obstack_init (&dired_obstack); obstack_init (&subdired_obstack); } nfiles = 100; files = XMALLOC (struct fileinfo, nfiles); files_index = 0; clear_files (); n_files = argc - i; if (0 < n_files) dir_defaulted = 0; for (; i < argc; i++) { gobble_file (argv[i], unknown, 1, ""); } if (dir_defaulted) { if (immediate_dirs) gobble_file (".", directory, 1, ""); else queue_directory (".", 0); } if (files_index) { sort_files (); if (!immediate_dirs) extract_dirs_from_files ("", 0); /* `files_index' might be zero now. */ } /* In the following if/else blocks, it is sufficient to test `pending_dirs' (and not pending_dirs->name) because there may be no markers in the queue at this point. A marker may be enqueued when extract_dirs_from_files is called with a non-empty string or via print_dir. */ if (files_index) { print_current_files (); if (pending_dirs) DIRED_PUTCHAR ('\n'); } else if (n_files <= 1 && pending_dirs && pending_dirs->next == 0) print_dir_name = 0; while (pending_dirs) { thispend = pending_dirs; pending_dirs = pending_dirs->next; if (LOOP_DETECT) { if (thispend->name == NULL) { /* thispend->name == NULL means this is a marker entry indicating we've finished processing the directory. Use its dev/ino numbers to remove the corresponding entry from the active_dir_set hash table. */ struct dev_ino di = dev_ino_pop (); struct dev_ino *found = hash_delete (active_dir_set, &di); /* ASSERT_MATCHING_DEV_INO (thispend->realname, di); */ assert (found); dev_ino_free (found); free_pending_ent (thispend); continue; } } print_dir (thispend->name, thispend->realname); free_pending_ent (thispend); print_dir_name = 1; } if (dired) { /* No need to free these since we're about to exit. */ dired_dump_obstack ("//DIRED//", &dired_obstack); dired_dump_obstack ("//SUBDIRED//", &subdired_obstack); printf ("//DIRED-OPTIONS// --quoting-style=%s\n", quoting_style_args[get_quoting_style (filename_quoting_options)]); } /* Restore default color before exiting */ if (print_with_color) { put_indicator (&color_indicator[C_LEFT]); put_indicator (&color_indicator[C_RIGHT]); } if (LOOP_DETECT) { assert (hash_get_n_entries (active_dir_set) == 0); hash_free (active_dir_set); } exit (exit_status); } /* Set all the option flags according to the switches specified. Return the index of the first non-option argument. */ static int decode_switches (int argc, char **argv) { int c; char *time_style_option = 0; /* Record whether there is an option specifying sort type. */ int sort_type_specified = 0; qmark_funny_chars = 0; /* initialize all switches to default settings */ switch (ls_mode) { case LS_MULTI_COL: /* This is for the `dir' program. */ format = many_per_line; set_quoting_style (NULL, escape_quoting_style); break; case LS_LONG_FORMAT: /* This is for the `vdir' program. */ format = long_format; set_quoting_style (NULL, escape_quoting_style); break; case LS_LS: /* This is for the `ls' program. */ if (isatty (STDOUT_FILENO)) { format = many_per_line; /* See description of qmark_funny_chars, above. */ qmark_funny_chars = 1; } else { format = one_per_line; qmark_funny_chars = 0; } break; default: abort (); } time_type = time_mtime; sort_type = sort_name; sort_reverse = 0; numeric_ids = 0; print_block_size = 0; indicator_style = none; print_inode = 0; dereference = DEREF_UNDEFINED; recursive = 0; immediate_dirs = 0; all_files = 0; really_all_files = 0; ignore_patterns = 0; /* FIXME: put this in a function. */ { char const *q_style = getenv ("QUOTING_STYLE"); if (q_style) { int i = ARGMATCH (q_style, quoting_style_args, quoting_style_vals); if (0 <= i) set_quoting_style (NULL, quoting_style_vals[i]); else error (0, 0, _("ignoring invalid value of environment variable QUOTING_STYLE: %s"), quotearg (q_style)); } } { char const *ls_block_size = getenv ("LS_BLOCK_SIZE"); human_output_opts = human_options (ls_block_size, false, &output_block_size); if (ls_block_size || getenv ("BLOCK_SIZE")) file_output_block_size = output_block_size; } line_length = 80; { char const *p = getenv ("COLUMNS"); if (p && *p) { long int tmp_long; if (xstrtol (p, NULL, 0, &tmp_long, NULL) == LONGINT_OK && 0 < tmp_long && tmp_long <= INT_MAX) { line_length = (int) tmp_long; } else { error (0, 0, _("ignoring invalid width in environment variable COLUMNS: %s"), quotearg (p)); } } } #ifdef TIOCGWINSZ { struct winsize ws; if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 && ws.ws_col != 0) line_length = ws.ws_col; } #endif /* Using the TABSIZE environment variable is not POSIX-approved. Ignore it when POSIXLY_CORRECT is set. */ { char const *p; tabsize = 8; if (!getenv ("POSIXLY_CORRECT") && (p = getenv ("TABSIZE"))) { long int tmp_long; if (xstrtol (p, NULL, 0, &tmp_long, NULL) == LONGINT_OK && 0 <= tmp_long && tmp_long <= INT_MAX) { tabsize = (int) tmp_long; } else { error (0, 0, _("ignoring invalid tab size in environment variable TABSIZE: %s"), quotearg (p)); } } } while ((c = getopt_long (argc, argv, "abcdfghiklmnopqrstuvw:xABCDFGHI:LNQRST:UX1", long_options, NULL)) != -1) { switch (c) { case 0: break; case 'a': all_files = 1; really_all_files = 1; break; case 'b': set_quoting_style (NULL, escape_quoting_style); break; case 'c': time_type = time_ctime; break; case 'd': immediate_dirs = 1; break; case 'f': /* Same as enabling -a -U and disabling -l -s. */ all_files = 1; really_all_files = 1; sort_type = sort_none; sort_type_specified = 1; /* disable -l */ if (format == long_format) format = (isatty (STDOUT_FILENO) ? many_per_line : one_per_line); print_block_size = 0; /* disable -s */ print_with_color = 0; /* disable --color */ break; case 'g': format = long_format; print_owner = 0; break; case 'h': human_output_opts = human_autoscale | human_SI | human_base_1024; file_output_block_size = output_block_size = 1; break; case 'i': print_inode = 1; break; case 'k': human_output_opts = 0; file_output_block_size = output_block_size = 1024; break; case 'l': format = long_format; break; case 'm': format = with_commas; break; case 'n': numeric_ids = 1; format = long_format; break; case 'o': /* Just like -l, but don't display group info. */ format = long_format; print_group = 0; break; case 'p': indicator_style = file_type; break; case 'q': qmark_funny_chars = 1; break; case 'r': sort_reverse = 1; break; case 's': print_block_size = 1; break; case 't': sort_type = sort_time; sort_type_specified = 1; break; case 'u': time_type = time_atime; break; case 'v': sort_type = sort_version; sort_type_specified = 1; break; case 'w': { long int tmp_long; if (xstrtol (optarg, NULL, 0, &tmp_long, NULL) != LONGINT_OK || tmp_long <= 0 || tmp_long > INT_MAX) error (EXIT_FAILURE, 0, _("invalid line width: %s"), quotearg (optarg)); line_length = (int) tmp_long; break; } case 'x': format = horizontal; break; case 'A': really_all_files = 0; all_files = 1; break; case 'B': add_ignore_pattern ("*~"); add_ignore_pattern (".*~"); break; case 'C': format = many_per_line; break; case 'D': dired = 1; break; case 'F': indicator_style = classify; break; case 'G': /* inhibit display of group info */ print_group = 0; break; case 'H': dereference = DEREF_COMMAND_LINE_ARGUMENTS; break; case DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR_OPTION: dereference = DEREF_COMMAND_LINE_SYMLINK_TO_DIR; break; case 'I': add_ignore_pattern (optarg); break; case 'L': dereference = DEREF_ALWAYS; break; case 'N': set_quoting_style (NULL, literal_quoting_style); break; case 'Q': set_quoting_style (NULL, c_quoting_style); break; case 'R': recursive = 1; break; case 'S': sort_type = sort_size; sort_type_specified = 1; break; case 'T': { long int tmp_long; if (xstrtol (optarg, NULL, 0, &tmp_long, NULL) != LONGINT_OK || tmp_long < 0 || tmp_long > INT_MAX) error (EXIT_FAILURE, 0, _("invalid tab size: %s"), quotearg (optarg)); tabsize = (int) tmp_long; break; } case 'U': sort_type = sort_none; sort_type_specified = 1; break; case 'X': sort_type = sort_extension; sort_type_specified = 1; break; case '1': /* -1 has no effect after -l. */ if (format != long_format) format = one_per_line; break; case AUTHOR_OPTION: print_author = true; break; case SORT_OPTION: sort_type = XARGMATCH ("--sort", optarg, sort_args, sort_types); sort_type_specified = 1; break; case TIME_OPTION: time_type = XARGMATCH ("--time", optarg, time_args, time_types); break; case FORMAT_OPTION: format = XARGMATCH ("--format", optarg, format_args, format_types); break; case FULL_TIME_OPTION: format = long_format; time_style_option = "full-iso"; break; case COLOR_OPTION: { int i; if (optarg) i = XARGMATCH ("--color", optarg, color_args, color_types); else /* Using --color with no argument is equivalent to using --color=always. */ i = color_always; print_with_color = (i == color_always || (i == color_if_tty && isatty (STDOUT_FILENO))); if (print_with_color) { /* Don't use TAB characters in output. Some terminal emulators can't handle the combination of tabs and color codes on the same line. */ tabsize = 0; } break; } case INDICATOR_STYLE_OPTION: indicator_style = XARGMATCH ("--indicator-style", optarg, indicator_style_args, indicator_style_types); break; case QUOTING_STYLE_OPTION: set_quoting_style (NULL, XARGMATCH ("--quoting-style", optarg, quoting_style_args, quoting_style_vals)); break; case TIME_STYLE_OPTION: time_style_option = optarg; break; case SHOW_CONTROL_CHARS_OPTION: qmark_funny_chars = 0; break; case BLOCK_SIZE_OPTION: human_output_opts = human_options (optarg, true, &output_block_size); file_output_block_size = output_block_size; break; case SI_OPTION: human_output_opts = human_autoscale | human_SI; file_output_block_size = output_block_size = 1; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } filename_quoting_options = clone_quoting_options (NULL); if (get_quoting_style (filename_quoting_options) == escape_quoting_style) set_char_quoting (filename_quoting_options, ' ', 1); if (indicator_style != none) { char const *p; for (p = "*=@|" + (int) indicator_style - 1; *p; p++) set_char_quoting (filename_quoting_options, *p, 1); } dirname_quoting_options = clone_quoting_options (NULL); set_char_quoting (dirname_quoting_options, ':', 1); /* --dired is meaningful only with --format=long (-l). Otherwise, ignore it. FIXME: warn about this? Alternatively, make --dired imply --format=long? */ if (dired && format != long_format) dired = 0; /* If -c or -u is specified and not -l (or any other option that implies -l), and no sort-type was specified, then sort by the ctime (-c) or atime (-u). The behavior of ls when using either -c or -u but with neither -l nor -t appears to be unspecified by POSIX. So, with GNU ls, `-u' alone means sort by atime (this is the one that's not specified by the POSIX spec), -lu means show atime and sort by name, -lut means show atime and sort by atime. */ if ((time_type == time_ctime || time_type == time_atime) && !sort_type_specified && format != long_format) { sort_type = sort_time; } if (format == long_format) { char *style = time_style_option; static char const posix_prefix[] = "posix-"; if (! style) if (! (style = getenv ("TIME_STYLE"))) style = "posix-long-iso"; while (strncmp (style, posix_prefix, sizeof posix_prefix - 1) == 0) { if (! hard_locale (LC_TIME)) return optind; style += sizeof posix_prefix - 1; } if (*style == '+') { char *p0 = style + 1; char *p1 = strchr (p0, '\n'); if (! p1) p1 = p0; else { if (strchr (p1 + 1, '\n')) error (EXIT_FAILURE, 0, _("invalid time style format %s"), quote (p0)); *p1++ = '\0'; } long_time_format[0] = p0; long_time_format[1] = p1; } else switch (XARGMATCH ("time style", style, time_style_args, time_style_types)) { case full_iso_time_style: long_time_format[0] = long_time_format[1] = "%Y-%m-%d %H:%M:%S.%N %z"; break; case long_iso_time_style: long_time_format[0] = long_time_format[1] = "%Y-%m-%d %H:%M"; break; case iso_time_style: long_time_format[0] = "%Y-%m-%d "; long_time_format[1] = "%m-%d %H:%M"; break; case locale_time_style: if (hard_locale (LC_TIME)) { unsigned int i; for (i = 0; i < 2; i++) long_time_format[i] = dcgettext (NULL, long_time_format[i], LC_TIME); } } } return optind; } /* Parse a string as part of the LS_COLORS variable; this may involve decoding all kinds of escape characters. If equals_end is set an unescaped equal sign ends the string, otherwise only a : or \0 does. Returns the number of characters output, or -1 on failure. The resulting string is *not* null-terminated, but may contain embedded nulls. Note that both dest and src are char **; on return they point to the first free byte after the array and the character that ended the input string, respectively. */ static int get_funky_string (char **dest, const char **src, int equals_end) { int num; /* For numerical codes */ int count; /* Something to count with */ enum { ST_GND, ST_BACKSLASH, ST_OCTAL, ST_HEX, ST_CARET, ST_END, ST_ERROR } state; const char *p; char *q; p = *src; /* We don't want to double-indirect */ q = *dest; /* the whole darn time. */ count = 0; /* No characters counted in yet. */ num = 0; state = ST_GND; /* Start in ground state. */ while (state < ST_END) { switch (state) { case ST_GND: /* Ground state (no escapes) */ switch (*p) { case ':': case '\0': state = ST_END; /* End of string */ break; case '\\': state = ST_BACKSLASH; /* Backslash scape sequence */ ++p; break; case '^': state = ST_CARET; /* Caret escape */ ++p; break; case '=': if (equals_end) { state = ST_END; /* End */ break; } /* else fall through */ default: *(q++) = *(p++); ++count; break; } break; case ST_BACKSLASH: /* Backslash escaped character */ switch (*p) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': state = ST_OCTAL; /* Octal sequence */ num = *p - '0'; break; case 'x': case 'X': state = ST_HEX; /* Hex sequence */ num = 0; break; case 'a': /* Bell */ num = 7; /* Not all C compilers know what \a means */ break; case 'b': /* Backspace */ num = '\b'; break; case 'e': /* Escape */ num = 27; break; case 'f': /* Form feed */ num = '\f'; break; case 'n': /* Newline */ num = '\n'; break; case 'r': /* Carriage return */ num = '\r'; break; case 't': /* Tab */ num = '\t'; break; case 'v': /* Vtab */ num = '\v'; break; case '?': /* Delete */ num = 127; break; case '_': /* Space */ num = ' '; break; case '\0': /* End of string */ state = ST_ERROR; /* Error! */ break; default: /* Escaped character like \ ^ : = */ num = *p; break; } if (state == ST_BACKSLASH) { *(q++) = num; ++count; state = ST_GND; } ++p; break; case ST_OCTAL: /* Octal sequence */ if (*p < '0' || *p > '7') { *(q++) = num; ++count; state = ST_GND; } else num = (num << 3) + (*(p++) - '0'); break; case ST_HEX: /* Hex sequence */ switch (*p) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': num = (num << 4) + (*(p++) - '0'); break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': num = (num << 4) + (*(p++) - 'a') + 10; break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': num = (num << 4) + (*(p++) - 'A') + 10; break; default: *(q++) = num; ++count; state = ST_GND; break; } break; case ST_CARET: /* Caret escape */ state = ST_GND; /* Should be the next state... */ if (*p >= '@' && *p <= '~') { *(q++) = *(p++) & 037; ++count; } else if (*p == '?') { *(q++) = 127; ++count; } else state = ST_ERROR; break; default: abort (); } } *dest = q; *src = p; return state == ST_ERROR ? -1 : count; } static void parse_ls_color (void) { const char *p; /* Pointer to character being parsed */ char *buf; /* color_buf buffer pointer */ int state; /* State of parser */ int ind_no; /* Indicator number */ char label[3]; /* Indicator label */ struct color_ext_type *ext; /* Extension we are working on */ if ((p = getenv ("LS_COLORS")) == NULL || *p == '\0') return; ext = NULL; strcpy (label, "??"); /* This is an overly conservative estimate, but any possible LS_COLORS string will *not* generate a color_buf longer than itself, so it is a safe way of allocating a buffer in advance. */ buf = color_buf = xstrdup (p); state = 1; while (state > 0) { switch (state) { case 1: /* First label character */ switch (*p) { case ':': ++p; break; case '*': /* Allocate new extension block and add to head of linked list (this way a later definition will override an earlier one, which can be useful for having terminal-specific defs override global). */ ext = XMALLOC (struct color_ext_type, 1); ext->next = color_ext_list; color_ext_list = ext; ++p; ext->ext.string = buf; state = (ext->ext.len = get_funky_string (&buf, &p, 1)) < 0 ? -1 : 4; break; case '\0': state = 0; /* Done! */ break; default: /* Assume it is file type label */ label[0] = *(p++); state = 2; break; } break; case 2: /* Second label character */ if (*p) { label[1] = *(p++); state = 3; } else state = -1; /* Error */ break; case 3: /* Equal sign after indicator label */ state = -1; /* Assume failure... */ if (*(p++) == '=')/* It *should* be... */ { for (ind_no = 0; indicator_name[ind_no] != NULL; ++ind_no) { if (STREQ (label, indicator_name[ind_no])) { color_indicator[ind_no].string = buf; state = ((color_indicator[ind_no].len = get_funky_string (&buf, &p, 0)) < 0 ? -1 : 1); break; } } if (state == -1) error (0, 0, _("unrecognized prefix: %s"), quotearg (label)); } break; case 4: /* Equal sign after *.ext */ if (*(p++) == '=') { ext->seq.string = buf; state = (ext->seq.len = get_funky_string (&buf, &p, 0)) < 0 ? -1 : 1; } else state = -1; break; } } if (state < 0) { struct color_ext_type *e; struct color_ext_type *e2; error (0, 0, _("unparsable value for LS_COLORS environment variable")); free (color_buf); for (e = color_ext_list; e != NULL; /* empty */) { e2 = e; e = e->next; free (e2); } print_with_color = 0; } if (color_indicator[C_LINK].len == 6 && !strncmp (color_indicator[C_LINK].string, "target", 6)) color_symlink_as_referent = 1; } /* Request that the directory named NAME have its contents listed later. If REALNAME is nonzero, it will be used instead of NAME when the directory name is printed. This allows symbolic links to directories to be treated as regular directories but still be listed under their real names. NAME == NULL is used to insert a marker entry for the directory named in REALNAME. If F is non-NULL, we use its dev/ino information to save a call to stat -- when doing a recursive (-R) traversal. */ static void queue_directory (const char *name, const char *realname) { struct pending *new; new = XMALLOC (struct pending, 1); new->realname = realname ? xstrdup (realname) : NULL; new->name = name ? xstrdup (name) : NULL; new->next = pending_dirs; pending_dirs = new; } /* Read directory `name', and list the files in it. If `realname' is nonzero, print its name instead of `name'; this is used for symbolic links to directories. */ static void print_dir (const char *name, const char *realname) { register DIR *dirp; register struct dirent *next; register uintmax_t total_blocks = 0; static int first = 1; errno = 0; dirp = opendir (name); if (!dirp) { error (0, errno, "%s", quotearg_colon (name)); exit_status = 1; return; } if (LOOP_DETECT) { struct stat dir_stat; int fd = dirfd (dirp); /* If dirfd failed, endure the overhead of using stat. */ if ((0 <= fd ? fstat (fd, &dir_stat) : stat (name, &dir_stat)) < 0) { error (0, errno, _("cannot determine device and inode of %s"), quotearg_colon (name)); exit_status = 1; return; } /* If we've already visited this dev/inode pair, warn that we've found a loop, and do not process this directory. */ if (visit_dir (dir_stat.st_dev, dir_stat.st_ino)) { error (0, 0, _("not listing already-listed directory: %s"), quotearg_colon (name)); return; } DEV_INO_PUSH (dir_stat.st_dev, dir_stat.st_ino); } /* Read the directory entries, and insert the subfiles into the `files' table. */ clear_files (); while (1) { /* Set errno to zero so we can distinguish between a readdir failure and when readdir simply finds that there are no more entries. */ errno = 0; if ((next = readdir (dirp)) == NULL) { if (errno) { /* Save/restore errno across closedir call. */ int e = errno; closedir (dirp); errno = e; /* Arrange to give a diagnostic after exiting this loop. */ dirp = NULL; } break; } if (file_interesting (next)) { enum filetype type = unknown; #if HAVE_STRUCT_DIRENT_D_TYPE if (next->d_type == DT_BLK || next->d_type == DT_CHR || next->d_type == DT_DIR || next->d_type == DT_FIFO || next->d_type == DT_LNK || next->d_type == DT_REG || next->d_type == DT_SOCK) type = next->d_type; #endif total_blocks += gobble_file (next->d_name, type, 0, name); } } if (dirp == NULL || CLOSEDIR (dirp)) { error (0, errno, _("reading directory %s"), quotearg_colon (name)); exit_status = 1; /* Don't return; print whatever we got. */ } /* Sort the directory contents. */ sort_files (); /* If any member files are subdirectories, perhaps they should have their contents listed rather than being mentioned here as files. */ if (recursive) extract_dirs_from_files (name, 1); if (recursive || print_dir_name) { if (!first) DIRED_PUTCHAR ('\n'); first = 0; DIRED_INDENT (); PUSH_CURRENT_DIRED_POS (&subdired_obstack); dired_pos += quote_name (stdout, realname ? realname : name, dirname_quoting_options, NULL); PUSH_CURRENT_DIRED_POS (&subdired_obstack); DIRED_FPUTS_LITERAL (":\n", stdout); } if (format == long_format || print_block_size) { const char *p; char buf[LONGEST_HUMAN_READABLE + 1]; DIRED_INDENT (); p = _("total"); DIRED_FPUTS (p, stdout, strlen (p)); DIRED_PUTCHAR (' '); p = human_readable (total_blocks, buf, human_output_opts, ST_NBLOCKSIZE, output_block_size); DIRED_FPUTS (p, stdout, strlen (p)); DIRED_PUTCHAR ('\n'); } if (files_index) print_current_files (); } /* Add `pattern' to the list of patterns for which files that match are not listed. */ static void add_ignore_pattern (const char *pattern) { register struct ignore_pattern *ignore; ignore = XMALLOC (struct ignore_pattern, 1); ignore->pattern = pattern; /* Add it to the head of the linked list. */ ignore->next = ignore_patterns; ignore_patterns = ignore; } /* Return nonzero if the file in `next' should be listed. */ static int file_interesting (const struct dirent *next) { register struct ignore_pattern *ignore; for (ignore = ignore_patterns; ignore; ignore = ignore->next) if (fnmatch (ignore->pattern, next->d_name, FNM_PERIOD) == 0) return 0; if (really_all_files || next->d_name[0] != '.' || (all_files && next->d_name[1] != '\0' && (next->d_name[1] != '.' || next->d_name[2] != '\0'))) return 1; return 0; } /* Enter and remove entries in the table `files'. */ /* Empty the table of files. */ static void clear_files (void) { register int i; for (i = 0; i < files_index; i++) { free (files[i].name); if (files[i].linkname) free (files[i].linkname); } files_index = 0; block_size_size = 4; } /* Add a file to the current table of files. Verify that the file exists, and print an error message if it does not. Return the number of blocks that the file occupies. */ static uintmax_t gobble_file (const char *name, enum filetype type, int explicit_arg, const char *dirname) { register uintmax_t blocks; register char *path; if (files_index == nfiles) { nfiles *= 2; files = XREALLOC (files, struct fileinfo, nfiles); } files[files_index].linkname = 0; files[files_index].linkmode = 0; files[files_index].linkok = 0; if (explicit_arg || format_needs_stat || (format_needs_type && (type == unknown /* FIXME: remove this disjunct. I don't think we care about symlinks here, but for now this won't make a big performance difference. */ || type == symbolic_link /* --indicator-style=classify (aka -F) requires that we stat each regular file to see if it's executable. */ || (type == normal && (indicator_style == classify /* This is so that --color ends up highlighting files with the executable bit set even when options like -F are not specified. */ || print_with_color))))) { /* `path' is the absolute pathname of this file. */ int err; if (name[0] == '/' || dirname[0] == 0) path = (char *) name; else { path = (char *) alloca (strlen (name) + strlen (dirname) + 2); attach (path, dirname, name); } switch (dereference) { case DEREF_ALWAYS: err = stat (path, &files[files_index].stat); break; case DEREF_COMMAND_LINE_ARGUMENTS: case DEREF_COMMAND_LINE_SYMLINK_TO_DIR: if (explicit_arg) { int need_lstat; err = stat (path, &files[files_index].stat); if (dereference == DEREF_COMMAND_LINE_ARGUMENTS) break; need_lstat = (err < 0 ? errno == ENOENT : ! S_ISDIR (files[files_index].stat.st_mode)); if (!need_lstat) break; /* stat failed because of ENOENT, maybe indicating a dangling symlink. Or stat succeeded, PATH does not refer to a directory, and --dereference-command-line-symlink-to-dir is in effect. Fall through so that we call lstat instead. */ } default: /* DEREF_NEVER */ err = lstat (path, &files[files_index].stat); break; } if (err < 0) { error (0, errno, "%s", quotearg_colon (path)); exit_status = 1; return 0; } #if HAVE_ACL if (format == long_format) { int n = file_has_acl (path, &files[files_index].stat); files[files_index].have_acl = (0 < n); if (n < 0) error (0, errno, "%s", quotearg_colon (path)); } #endif if (S_ISLNK (files[files_index].stat.st_mode) && (format == long_format || check_symlink_color)) { char *linkpath; struct stat linkstats; get_link_name (path, &files[files_index]); linkpath = make_link_path (path, files[files_index].linkname); /* Avoid following symbolic links when possible, ie, when they won't be traced and when no indicator is needed. */ if (linkpath && (indicator_style != none || check_symlink_color) && stat (linkpath, &linkstats) == 0) { files[files_index].linkok = 1; /* Symbolic links to directories that are mentioned on the command line are automatically traced if not being listed as files. */ if (!explicit_arg || format == long_format || !S_ISDIR (linkstats.st_mode)) { /* Get the linked-to file's mode for the filetype indicator in long listings. */ files[files_index].linkmode = linkstats.st_mode; files[files_index].linkok = 1; } } if (linkpath) free (linkpath); } if (S_ISLNK (files[files_index].stat.st_mode)) files[files_index].filetype = symbolic_link; else if (S_ISDIR (files[files_index].stat.st_mode)) { if (explicit_arg && !immediate_dirs) files[files_index].filetype = arg_directory; else files[files_index].filetype = directory; } else files[files_index].filetype = normal; blocks = ST_NBLOCKS (files[files_index].stat); { char buf[LONGEST_HUMAN_READABLE + 1]; int len = strlen (human_readable (blocks, buf, human_output_opts, ST_NBLOCKSIZE, output_block_size)); if (block_size_size < len) block_size_size = len < 7 ? len : 7; } } else { files[files_index].filetype = type; #if HAVE_STRUCT_DIRENT_D_TYPE files[files_index].stat.st_mode = DTTOIF (type); #endif blocks = 0; } files[files_index].name = xstrdup (name); files_index++; return blocks; } #ifdef S_ISLNK /* Put the name of the file that `filename' is a symbolic link to into the `linkname' field of `f'. */ static void get_link_name (const char *filename, struct fileinfo *f) { f->linkname = xreadlink (filename); if (f->linkname == NULL) { error (0, errno, _("cannot read symbolic link %s"), quotearg_colon (filename)); exit_status = 1; } } /* If `linkname' is a relative path and `path' contains one or more leading directories, return `linkname' with those directories prepended; otherwise, return a copy of `linkname'. If `linkname' is zero, return zero. */ static char * make_link_path (const char *path, const char *linkname) { char *linkbuf; size_t bufsiz; if (linkname == 0) return 0; if (*linkname == '/') return xstrdup (linkname); /* The link is to a relative path. Prepend any leading path in `path' to the link name. */ linkbuf = strrchr (path, '/'); if (linkbuf == 0) return xstrdup (linkname); bufsiz = linkbuf - path + 1; linkbuf = xmalloc (bufsiz + strlen (linkname) + 1); strncpy (linkbuf, path, bufsiz); strcpy (linkbuf + bufsiz, linkname); return linkbuf; } #endif /* Return nonzero if base_name (NAME) ends in `.' or `..' This is so we don't try to recurse on `././././. ...' */ static int basename_is_dot_or_dotdot (const char *name) { char const *base = base_name (name); return DOT_OR_DOTDOT (base); } /* Remove any entries from `files' that are for directories, and queue them to be listed as directories instead. `dirname' is the prefix to prepend to each dirname to make it correct relative to ls's working dir. If IGNORE_DOT_AND_DOT_DOT is nonzero don't treat `.' and `..' as dirs. This is desirable when processing directories recursively. */ static void extract_dirs_from_files (const char *dirname, int ignore_dot_and_dot_dot) { register int i, j; if (*dirname && LOOP_DETECT) { /* Insert a marker entry first. When we dequeue this marker entry, we'll know that DIRNAME has been processed and may be removed from the set of active directories. */ queue_directory (NULL, dirname); } /* Queue the directories last one first, because queueing reverses the order. */ for (i = files_index - 1; i >= 0; i--) if ((files[i].filetype == directory || files[i].filetype == arg_directory) && (!ignore_dot_and_dot_dot || !basename_is_dot_or_dotdot (files[i].name))) { if (files[i].name[0] == '/' || dirname[0] == 0) { queue_directory (files[i].name, files[i].linkname); } else { char *path = path_concat (dirname, files[i].name, NULL); queue_directory (path, files[i].linkname); free (path); } if (files[i].filetype == arg_directory) free (files[i].name); } /* Now delete the directories from the table, compacting all the remaining entries. */ for (i = 0, j = 0; i < files_index; i++) if (files[i].filetype != arg_directory) files[j++] = files[i]; files_index = j; } /* Use strcoll to compare strings in this locale. If an error occurs, report an error and longjmp to failed_strcoll. */ static jmp_buf failed_strcoll; static int xstrcoll (char const *a, char const *b) { int diff; errno = 0; diff = strcoll (a, b); if (errno) { error (0, errno, _("cannot compare file names %s and %s"), quote_n (0, a), quote_n (1, b)); exit_status = 1; longjmp (failed_strcoll, 1); } return diff; } /* Comparison routines for sorting the files. */ typedef void const *V; static inline int cmp_ctime (struct fileinfo const *a, struct fileinfo const *b, int (*cmp) (char const *, char const *)) { int diff = CTIME_CMP (b->stat, a->stat); return diff ? diff : cmp (a->name, b->name); } static int compare_ctime (V a, V b) { return cmp_ctime (a, b, xstrcoll); } static int compstr_ctime (V a, V b) { return cmp_ctime (a, b, strcmp); } static int rev_cmp_ctime (V a, V b) { return compare_ctime (b, a); } static int rev_str_ctime (V a, V b) { return compstr_ctime (b, a); } static inline int cmp_mtime (struct fileinfo const *a, struct fileinfo const *b, int (*cmp) (char const *, char const *)) { int diff = MTIME_CMP (b->stat, a->stat); return diff ? diff : cmp (a->name, b->name); } static int compare_mtime (V a, V b) { return cmp_mtime (a, b, xstrcoll); } static int compstr_mtime (V a, V b) { return cmp_mtime (a, b, strcmp); } static int rev_cmp_mtime (V a, V b) { return compare_mtime (b, a); } static int rev_str_mtime (V a, V b) { return compstr_mtime (b, a); } static inline int cmp_atime (struct fileinfo const *a, struct fileinfo const *b, int (*cmp) (char const *, char const *)) { int diff = ATIME_CMP (b->stat, a->stat); return diff ? diff : cmp (a->name, b->name); } static int compare_atime (V a, V b) { return cmp_atime (a, b, xstrcoll); } static int compstr_atime (V a, V b) { return cmp_atime (a, b, strcmp); } static int rev_cmp_atime (V a, V b) { return compare_atime (b, a); } static int rev_str_atime (V a, V b) { return compstr_atime (b, a); } static inline int cmp_size (struct fileinfo const *a, struct fileinfo const *b, int (*cmp) (char const *, char const *)) { int diff = longdiff (b->stat.st_size, a->stat.st_size); return diff ? diff : cmp (a->name, b->name); } static int compare_size (V a, V b) { return cmp_size (a, b, xstrcoll); } static int compstr_size (V a, V b) { return cmp_size (a, b, strcmp); } static int rev_cmp_size (V a, V b) { return compare_size (b, a); } static int rev_str_size (V a, V b) { return compstr_size (b, a); } static inline int cmp_version (struct fileinfo const *a, struct fileinfo const *b) { return strverscmp (a->name, b->name); } static int compare_version (V a, V b) { return cmp_version (a, b); } static int rev_cmp_version (V a, V b) { return compare_version (b, a); } static inline int cmp_name (struct fileinfo const *a, struct fileinfo const *b, int (*cmp) (char const *, char const *)) { return cmp (a->name, b->name); } static int compare_name (V a, V b) { return cmp_name (a, b, xstrcoll); } static int compstr_name (V a, V b) { return cmp_name (a, b, strcmp); } static int rev_cmp_name (V a, V b) { return compare_name (b, a); } static int rev_str_name (V a, V b) { return compstr_name (b, a); } /* Compare file extensions. Files with no extension are `smallest'. If extensions are the same, compare by filenames instead. */ static inline int cmp_extension (struct fileinfo const *a, struct fileinfo const *b, int (*cmp) (char const *, char const *)) { char const *base1 = strrchr (a->name, '.'); char const *base2 = strrchr (b->name, '.'); int diff = cmp (base1 ? base1 : "", base2 ? base2 : ""); return diff ? diff : cmp (a->name, b->name); } static int compare_extension (V a, V b) { return cmp_extension (a, b, xstrcoll); } static int compstr_extension (V a, V b) { return cmp_extension (a, b, strcmp); } static int rev_cmp_extension (V a, V b) { return compare_extension (b, a); } static int rev_str_extension (V a, V b) { return compstr_extension (b, a); } /* Sort the files now in the table. */ static void sort_files (void) { int (*func) (V, V); switch (sort_type) { case sort_none: return; case sort_time: switch (time_type) { case time_ctime: func = sort_reverse ? rev_cmp_ctime : compare_ctime; break; case time_mtime: func = sort_reverse ? rev_cmp_mtime : compare_mtime; break; case time_atime: func = sort_reverse ? rev_cmp_atime : compare_atime; break; default: abort (); } break; case sort_name: func = sort_reverse ? rev_cmp_name : compare_name; break; case sort_extension: func = sort_reverse ? rev_cmp_extension : compare_extension; break; case sort_size: func = sort_reverse ? rev_cmp_size : compare_size; break; case sort_version: func = sort_reverse ? rev_cmp_version : compare_version; break; default: abort (); } /* Try strcoll. If it fails, fall back on strcmp. We can't safely ignore strcoll failures, as a failing strcoll might be a comparison function that is not a total order, and if we ignored the failure this might cause qsort to dump core. */ if (setjmp (failed_strcoll)) { switch (sort_type) { case sort_time: switch (time_type) { case time_ctime: func = sort_reverse ? rev_str_ctime : compstr_ctime; break; case time_mtime: func = sort_reverse ? rev_str_mtime : compstr_mtime; break; case time_atime: func = sort_reverse ? rev_str_atime : compstr_atime; break; default: abort (); } break; case sort_name: func = sort_reverse ? rev_str_name : compstr_name; break; case sort_extension: func = sort_reverse ? rev_str_extension : compstr_extension; break; case sort_size: func = sort_reverse ? rev_str_size : compstr_size; break; default: abort (); } } qsort (files, files_index, sizeof (struct fileinfo), func); } /* List all the files now in the table. */ static void print_current_files (void) { register int i; switch (format) { case one_per_line: for (i = 0; i < files_index; i++) { print_file_name_and_frills (files + i); putchar ('\n'); } break; case many_per_line: init_column_info (); print_many_per_line (); break; case horizontal: init_column_info (); print_horizontal (); break; case with_commas: print_with_commas (); break; case long_format: for (i = 0; i < files_index; i++) { print_long_format (files + i); DIRED_PUTCHAR ('\n'); } break; } } /* Return the expected number of columns in a long-format time stamp, or zero if it cannot be calculated. */ static int long_time_expected_width (void) { static int width = -1; if (width < 0) { time_t epoch = 0; struct tm const *tm = localtime (&epoch); char const *fmt = long_time_format[0]; char initbuf[100]; char *buf = initbuf; size_t bufsize = sizeof initbuf; size_t len; for (;;) { *buf = '\1'; len = nstrftime (buf, bufsize, fmt, tm, 0, 0); if (len || ! *buf) break; buf = alloca (bufsize *= 2); } width = mbsnwidth (buf, len, 0); if (width < 0) width = 0; } return width; } /* Get the current time. */ static void get_current_time (void) { #if HAVE_CLOCK_GETTIME && defined CLOCK_REALTIME { struct timespec timespec; if (clock_gettime (CLOCK_REALTIME, ×pec) == 0) { current_time = timespec.tv_sec; current_time_ns = timespec.tv_nsec; return; } } #endif /* The clock does not have nanosecond resolution, so get the maximum possible value for the current time that is consistent with the reported clock. That way, files are not considered to be in the future merely because their time stamps have higher resolution than the clock resolution. */ #if HAVE_GETTIMEOFDAY { struct timeval timeval; if (gettimeofday (&timeval, NULL) == 0) { current_time = timeval.tv_sec; current_time_ns = timeval.tv_usec * 1000 + 999; return; } } #endif current_time = time (NULL); current_time_ns = 999999999; } /* Format into BUFFER the name or id of the user with id U. Return the length of the formatted buffer, not counting the terminating null. */ static size_t format_user (char *buffer, uid_t u) { char const *name = (numeric_ids ? NULL : getuser (u)); if (name) sprintf (buffer, "%-8s ", name); else sprintf (buffer, "%-8lu ", (unsigned long) u); return strlen (buffer); } /* Print information about F in long format. */ static void print_long_format (const struct fileinfo *f) { char modebuf[12]; char init_bigbuf [LONGEST_HUMAN_READABLE + 1 /* inode */ + LONGEST_HUMAN_READABLE + 1 /* size in blocks */ + sizeof (modebuf) - 1 + 1 /* mode string */ + LONGEST_HUMAN_READABLE + 1 /* st_nlink */ + ID_LENGTH_MAX + 1 /* owner name */ + ID_LENGTH_MAX + 1 /* group name */ + ID_LENGTH_MAX + 1 /* author name */ + LONGEST_HUMAN_READABLE + 1 /* major device number */ + LONGEST_HUMAN_READABLE + 1 /* minor device number */ + 35 + 1 /* usual length of time/date -- may be longer; see below */ ]; char *buf = init_bigbuf; size_t bufsize = sizeof (init_bigbuf); size_t s; char *p; time_t when; int when_ns IF_LINT (= 0); struct tm *when_local; /* Compute mode string. On most systems, it's based on st_mode. On systems with migration (via the stat.st_dm_mode field), use the file's migrated status. */ mode_string (ST_DM_MODE (f->stat), modebuf); modebuf[10] = (FILE_HAS_ACL (f) ? '+' : ' '); modebuf[11] = '\0'; switch (time_type) { case time_ctime: when = f->stat.st_ctime; when_ns = TIMESPEC_NS (f->stat.st_ctim); break; case time_mtime: when = f->stat.st_mtime; when_ns = TIMESPEC_NS (f->stat.st_mtim); break; case time_atime: when = f->stat.st_atime; when_ns = TIMESPEC_NS (f->stat.st_atim); break; } p = buf; if (print_inode) { char hbuf[LONGEST_HUMAN_READABLE + 1]; sprintf (p, "%*s ", INODE_DIGITS, umaxtostr (f->stat.st_ino, hbuf)); p += strlen (p); } if (print_block_size) { char hbuf[LONGEST_HUMAN_READABLE + 1]; sprintf (p, "%*s ", block_size_size, human_readable (ST_NBLOCKS (f->stat), hbuf, human_output_opts, ST_NBLOCKSIZE, output_block_size)); p += strlen (p); } /* The last byte of the mode string is the POSIX "optional alternate access method flag". */ sprintf (p, "%s %3lu ", modebuf, (unsigned long) f->stat.st_nlink); p += strlen (p); if (print_owner) p += format_user (p, f->stat.st_uid); if (print_group) { char const *group_name = (numeric_ids ? NULL : getgroup (f->stat.st_gid)); if (group_name) sprintf (p, "%-8s ", group_name); else sprintf (p, "%-8lu ", (unsigned long) f->stat.st_gid); p += strlen (p); } if (print_author) p += format_user (p, f->stat.st_author); if (S_ISCHR (f->stat.st_mode) || S_ISBLK (f->stat.st_mode)) sprintf (p, "%3lu, %3lu ", (unsigned long) major (f->stat.st_rdev), (unsigned long) minor (f->stat.st_rdev)); else { char hbuf[LONGEST_HUMAN_READABLE + 1]; uintmax_t size = f->stat.st_size; /* POSIX requires that the size be printed without a sign, even when negative. Assume the typical case where negative sizes are actually positive values that have wrapped around. */ size += (f->stat.st_size < 0) * ((uintmax_t) OFF_T_MAX - OFF_T_MIN + 1); sprintf (p, "%8s ", human_readable (size, hbuf, human_output_opts, 1, file_output_block_size)); } p += strlen (p); if ((when_local = localtime (&when))) { time_t six_months_ago; int recent; char const *fmt; /* If the file appears to be in the future, update the current time, in case the file happens to have been modified since the last time we checked the clock. */ if (current_time < when || (current_time == when && current_time_ns < when_ns)) { /* Note that get_current_time calls gettimeofday which, on some non- compliant systems, clobbers the buffer used for localtime's result. But it's ok here, because we use a gettimeofday wrapper that saves and restores the buffer around the gettimeofday call. */ get_current_time (); } /* Consider a time to be recent if it is within the past six months. A Gregorian year has 365.2425 * 24 * 60 * 60 == 31556952 seconds on the average. Write this value as an integer constant to avoid floating point hassles. */ six_months_ago = current_time - 31556952 / 2; recent = (six_months_ago <= when && (when < current_time || (when == current_time && when_ns <= current_time_ns))); fmt = long_time_format[recent]; for (;;) { char *newbuf; *p = '\1'; s = nstrftime (p, buf + bufsize - p - 1, fmt, when_local, 0, when_ns); if (s || ! *p) break; newbuf = alloca (bufsize *= 2); memcpy (newbuf, buf, p - buf); p = newbuf + (p - buf); buf = newbuf; } p += s; *p++ = ' '; /* NUL-terminate the string -- fputs (via DIRED_FPUTS) requires it. */ *p = '\0'; } else { /* The time cannot be represented as a local time; print it as a huge integer number of seconds. */ char hbuf[INT_BUFSIZE_BOUND (intmax_t)]; sprintf (p, "%*s ", long_time_expected_width (), (TYPE_SIGNED (time_t) ? imaxtostr (when, hbuf) : umaxtostr (when, hbuf))); p += strlen (p); } DIRED_INDENT (); DIRED_FPUTS (buf, stdout, p - buf); print_name_with_quoting (f->name, FILE_OR_LINK_MODE (f), f->linkok, &dired_obstack); if (f->filetype == symbolic_link) { if (f->linkname) { DIRED_FPUTS_LITERAL (" -> ", stdout); print_name_with_quoting (f->linkname, f->linkmode, f->linkok - 1, NULL); if (indicator_style != none) print_type_indicator (f->linkmode); } } else if (indicator_style != none) print_type_indicator (f->stat.st_mode); } /* Output to OUT a quoted representation of the file name NAME, using OPTIONS to control quoting. Produce no output if OUT is NULL. Store the number of screen columns occupied by NAME's quoted representation into WIDTH, if non-NULL. Return the number of bytes produced. */ static size_t quote_name (FILE *out, const char *name, struct quoting_options const *options, size_t *width) { char smallbuf[BUFSIZ]; size_t len = quotearg_buffer (smallbuf, sizeof smallbuf, name, -1, options); char *buf; size_t displayed_width IF_LINT (= 0); if (len < sizeof smallbuf) buf = smallbuf; else { buf = (char *) alloca (len + 1); quotearg_buffer (buf, len + 1, name, -1, options); } if (qmark_funny_chars) { #if HAVE_MBRTOWC if (MB_CUR_MAX > 1) { char const *p = buf; char const *plimit = buf + len; char *q = buf; displayed_width = 0; while (p < plimit) switch (*p) { case ' ': case '!': case '"': case '#': case '%': case '&': case '\'': case '(': case ')': case '*': case '+': case ',': case '-': case '.': case '/': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case ':': case ';': case '<': case '=': case '>': case '?': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '[': case '\\': case ']': case '^': case '_': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}': case '~': /* These characters are printable ASCII characters. */ *q++ = *p++; displayed_width += 1; break; default: /* If we have a multibyte sequence, copy it until we reach its end, replacing each non-printable multibyte character with a single question mark. */ { mbstate_t mbstate; memset (&mbstate, 0, sizeof mbstate); do { wchar_t wc; size_t bytes; int w; bytes = mbrtowc (&wc, p, plimit - p, &mbstate); if (bytes == (size_t) -1) { /* An invalid multibyte sequence was encountered. Skip one input byte, and put a question mark. */ p++; *q++ = '?'; displayed_width += 1; break; } if (bytes == (size_t) -2) { /* An incomplete multibyte character at the end. Replace it entirely with a question mark. */ p = plimit; *q++ = '?'; displayed_width += 1; break; } if (bytes == 0) /* A null wide character was encountered. */ bytes = 1; w = wcwidth (wc); if (w >= 0) { /* A printable multibyte character. Keep it. */ for (; bytes > 0; --bytes) *q++ = *p++; displayed_width += w; } else { /* An unprintable multibyte character. Replace it entirely with a question mark. */ p += bytes; *q++ = '?'; displayed_width += 1; } } while (! mbsinit (&mbstate)); } break; } /* The buffer may have shrunk. */ len = q - buf; } else #endif { char *p = buf; char const *plimit = buf + len; while (p < plimit) { if (! ISPRINT ((unsigned char) *p)) *p = '?'; p++; } displayed_width = len; } } else if (width != NULL) { #if HAVE_MBRTOWC if (MB_CUR_MAX > 1) displayed_width = mbsnwidth (buf, len, 0); else #endif { char const *p = buf; char const *plimit = buf + len; displayed_width = 0; while (p < plimit) { if (ISPRINT ((unsigned char) *p)) displayed_width++; p++; } } } if (out != NULL) fwrite (buf, 1, len, out); if (width != NULL) *width = displayed_width; return len; } static void print_name_with_quoting (const char *p, mode_t mode, int linkok, struct obstack *stack) { if (print_with_color) print_color_indicator (p, mode, linkok); if (stack) PUSH_CURRENT_DIRED_POS (stack); dired_pos += quote_name (stdout, p, filename_quoting_options, NULL); if (stack) PUSH_CURRENT_DIRED_POS (stack); if (print_with_color) prep_non_filename_text (); } static void prep_non_filename_text (void) { if (color_indicator[C_END].string != NULL) put_indicator (&color_indicator[C_END]); else { put_indicator (&color_indicator[C_LEFT]); put_indicator (&color_indicator[C_NORM]); put_indicator (&color_indicator[C_RIGHT]); } } /* Print the file name of `f' with appropriate quoting. Also print file size, inode number, and filetype indicator character, as requested by switches. */ static void print_file_name_and_frills (const struct fileinfo *f) { char buf[MAX (LONGEST_HUMAN_READABLE + 1, INT_BUFSIZE_BOUND (uintmax_t))]; if (print_inode) printf ("%*s ", INODE_DIGITS, umaxtostr (f->stat.st_ino, buf)); if (print_block_size) printf ("%*s ", block_size_size, human_readable (ST_NBLOCKS (f->stat), buf, human_output_opts, ST_NBLOCKSIZE, output_block_size)); print_name_with_quoting (f->name, FILE_OR_LINK_MODE (f), f->linkok, NULL); if (indicator_style != none) print_type_indicator (f->stat.st_mode); } static void print_type_indicator (mode_t mode) { int c; if (S_ISREG (mode)) { if (indicator_style == classify && (mode & S_IXUGO)) c ='*'; else c = 0; } else { if (S_ISDIR (mode)) c = '/'; else if (S_ISLNK (mode)) c = '@'; else if (S_ISFIFO (mode)) c = '|'; else if (S_ISSOCK (mode)) c = '='; else if (S_ISDOOR (mode)) c = '>'; else c = 0; } if (c) DIRED_PUTCHAR (c); } static void print_color_indicator (const char *name, mode_t mode, int linkok) { int type = C_FILE; struct color_ext_type *ext; /* Color extension */ size_t len; /* Length of name */ /* Is this a nonexistent file? If so, linkok == -1. */ if (linkok == -1 && color_indicator[C_MISSING].string != NULL) { ext = NULL; type = C_MISSING; } else { if (S_ISDIR (mode)) type = C_DIR; else if (S_ISLNK (mode)) type = ((!linkok && color_indicator[C_ORPHAN].string) ? C_ORPHAN : C_LINK); else if (S_ISFIFO (mode)) type = C_FIFO; else if (S_ISSOCK (mode)) type = C_SOCK; else if (S_ISBLK (mode)) type = C_BLK; else if (S_ISCHR (mode)) type = C_CHR; else if (S_ISDOOR (mode)) type = C_DOOR; if (type == C_FILE && (mode & S_IXUGO) != 0) type = C_EXEC; /* Check the file's suffix only if still classified as C_FILE. */ ext = NULL; if (type == C_FILE) { /* Test if NAME has a recognized suffix. */ len = strlen (name); name += len; /* Pointer to final \0. */ for (ext = color_ext_list; ext != NULL; ext = ext->next) { if ((size_t) ext->ext.len <= len && strncmp (name - ext->ext.len, ext->ext.string, ext->ext.len) == 0) break; } } } put_indicator (&color_indicator[C_LEFT]); put_indicator (ext ? &(ext->seq) : &color_indicator[type]); put_indicator (&color_indicator[C_RIGHT]); } /* Output a color indicator (which may contain nulls). */ static void put_indicator (const struct bin_str *ind) { register int i; register const char *p; p = ind->string; for (i = ind->len; i > 0; --i) putchar (*(p++)); } /* Output a color indicator, but don't use stdio, for use from signal handlers. Return zero if the write is successful or if the string length is zero. Return nonzero if the write fails. */ static int put_indicator_direct (const struct bin_str *ind) { size_t len; if (ind->len <= 0) return 0; len = ind->len; return (full_write (STDOUT_FILENO, ind->string, len) != len); } static int length_of_file_name_and_frills (const struct fileinfo *f) { register int len = 0; size_t name_width; if (print_inode) len += INODE_DIGITS + 1; if (print_block_size) len += 1 + block_size_size; quote_name (NULL, f->name, filename_quoting_options, &name_width); len += name_width; if (indicator_style != none) { mode_t filetype = f->stat.st_mode; if (S_ISREG (filetype)) { if (indicator_style == classify && (f->stat.st_mode & S_IXUGO)) len += 1; } else if (S_ISDIR (filetype) || S_ISLNK (filetype) || S_ISFIFO (filetype) || S_ISSOCK (filetype) || S_ISDOOR (filetype) ) len += 1; } return len; } static void print_many_per_line (void) { struct column_info *line_fmt; int filesno; /* Index into files. */ int row; /* Current row. */ int max_name_length; /* Length of longest file name + frills. */ int name_length; /* Length of each file name + frills. */ int pos; /* Current character column. */ int cols; /* Number of files across. */ int rows; /* Maximum number of files down. */ int max_cols; /* Normally the maximum number of columns is determined by the screen width. But if few files are available this might limit it as well. */ max_cols = max_idx > files_index ? files_index : max_idx; /* Compute the maximum number of possible columns. */ for (filesno = 0; filesno < files_index; ++filesno) { int i; name_length = length_of_file_name_and_frills (files + filesno); for (i = 0; i < max_cols; ++i) { if (column_info[i].valid_len) { int idx = filesno / ((files_index + i) / (i + 1)); int real_length = name_length + (idx == i ? 0 : 2); if (real_length > column_info[i].col_arr[idx]) { column_info[i].line_len += (real_length - column_info[i].col_arr[idx]); column_info[i].col_arr[idx] = real_length; column_info[i].valid_len = column_info[i].line_len < line_length; } } } } /* Find maximum allowed columns. */ for (cols = max_cols; cols > 1; --cols) { if (column_info[cols - 1].valid_len) break; } line_fmt = &column_info[cols - 1]; /* Calculate the number of rows that will be in each column except possibly for a short column on the right. */ rows = files_index / cols + (files_index % cols != 0); for (row = 0; row < rows; row++) { int col = 0; filesno = row; pos = 0; /* Print the next row. */ while (1) { print_file_name_and_frills (files + filesno); name_length = length_of_file_name_and_frills (files + filesno); max_name_length = line_fmt->col_arr[col++]; filesno += rows; if (filesno >= files_index) break; indent (pos + name_length, pos + max_name_length); pos += max_name_length; } putchar ('\n'); } } static void print_horizontal (void) { struct column_info *line_fmt; int filesno; int max_name_length; int name_length; int cols; int pos; int max_cols; /* Normally the maximum number of columns is determined by the screen width. But if few files are available this might limit it as well. */ max_cols = max_idx > files_index ? files_index : max_idx; /* Compute the maximum file name length. */ max_name_length = 0; for (filesno = 0; filesno < files_index; ++filesno) { int i; name_length = length_of_file_name_and_frills (files + filesno); for (i = 0; i < max_cols; ++i) { if (column_info[i].valid_len) { int idx = filesno % (i + 1); int real_length = name_length + (idx == i ? 0 : 2); if (real_length > column_info[i].col_arr[idx]) { column_info[i].line_len += (real_length - column_info[i].col_arr[idx]); column_info[i].col_arr[idx] = real_length; column_info[i].valid_len = column_info[i].line_len < line_length; } } } } /* Find maximum allowed columns. */ for (cols = max_cols; cols > 1; --cols) { if (column_info[cols - 1].valid_len) break; } line_fmt = &column_info[cols - 1]; pos = 0; /* Print first entry. */ print_file_name_and_frills (files); name_length = length_of_file_name_and_frills (files); max_name_length = line_fmt->col_arr[0]; /* Now the rest. */ for (filesno = 1; filesno < files_index; ++filesno) { int col = filesno % cols; if (col == 0) { putchar ('\n'); pos = 0; } else { indent (pos + name_length, pos + max_name_length); pos += max_name_length; } print_file_name_and_frills (files + filesno); name_length = length_of_file_name_and_frills (files + filesno); max_name_length = line_fmt->col_arr[col]; } putchar ('\n'); } static void print_with_commas (void) { int filesno; int pos, old_pos; pos = 0; for (filesno = 0; filesno < files_index; filesno++) { old_pos = pos; pos += length_of_file_name_and_frills (files + filesno); if (filesno + 1 < files_index) pos += 2; /* For the comma and space */ if (old_pos != 0 && pos >= line_length) { putchar ('\n'); pos -= old_pos; } print_file_name_and_frills (files + filesno); if (filesno + 1 < files_index) { putchar (','); putchar (' '); } } putchar ('\n'); } /* Assuming cursor is at position FROM, indent up to position TO. Use a TAB character instead of two or more spaces whenever possible. */ static void indent (int from, int to) { while (from < to) { if (tabsize > 0 && to / tabsize > (from + 1) / tabsize) { putchar ('\t'); from += tabsize - from % tabsize; } else { putchar (' '); from++; } } } /* Put DIRNAME/NAME into DEST, handling `.' and `/' properly. */ /* FIXME: maybe remove this function someday. See about using a non-malloc'ing version of path_concat. */ static void attach (char *dest, const char *dirname, const char *name) { const char *dirnamep = dirname; /* Copy dirname if it is not ".". */ if (dirname[0] != '.' || dirname[1] != 0) { while (*dirnamep) *dest++ = *dirnamep++; /* Add '/' if `dirname' doesn't already end with it. */ if (dirnamep > dirname && dirnamep[-1] != '/') *dest++ = '/'; } while (*name) *dest++ = *name++; *dest = 0; } static void init_column_info (void) { int i; int allocate = 0; max_idx = line_length / MIN_COLUMN_WIDTH; if (max_idx == 0) max_idx = 1; if (column_info == NULL) { column_info = XMALLOC (struct column_info, max_idx); allocate = 1; } for (i = 0; i < max_idx; ++i) { int j; column_info[i].valid_len = 1; column_info[i].line_len = (i + 1) * MIN_COLUMN_WIDTH; if (allocate) column_info[i].col_arr = XMALLOC (int, i + 1); for (j = 0; j <= i; ++j) column_info[i].col_arr[j] = MIN_COLUMN_WIDTH; } } void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("Usage: %s [OPTION]... [FILE]...\n"), program_name); fputs (_("\ List information about the FILEs (the current directory by default).\n\ Sort entries alphabetically if none of -cftuSUX nor --sort.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ -a, --all do not hide entries starting with .\n\ -A, --almost-all do not list implied . and ..\n\ --author print the author of each file\n\ -b, --escape print octal escapes for nongraphic characters\n\ "), stdout); fputs (_("\ --block-size=SIZE use SIZE-byte blocks\n\ -B, --ignore-backups do not list implied entries ending with ~\n\ -c with -lt: sort by, and show, ctime (time of last\n\ modification of file status information)\n\ with -l: show ctime and sort by name\n\ otherwise: sort by ctime\n\ "), stdout); fputs (_("\ -C list entries by columns\n\ --color[=WHEN] control whether color is used to distinguish file\n\ types. WHEN may be `never', `always', or `auto'\n\ -d, --directory list directory entries instead of contents,\n\ and do not dereference symbolic links\n\ -D, --dired generate output designed for Emacs' dired mode\n\ "), stdout); fputs (_("\ -f do not sort, enable -aU, disable -lst\n\ -F, --classify append indicator (one of */=@|) to entries\n\ --format=WORD across -x, commas -m, horizontal -x, long -l,\n\ single-column -1, verbose -l, vertical -C\n\ --full-time like -l --time-style=full-iso\n\ "), stdout); fputs (_("\ -g like -l, but do not list owner\n\ -G, --no-group inhibit display of group information\n\ -h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G)\n\ --si likewise, but use powers of 1000 not 1024\n\ -H, --dereference-command-line\n\ follow symbolic links listed on the command line\n\ --dereference-command-line-symlink-to-dir\n\ follow each command line symbolic link\n\ that points to a directory\n\ "), stdout); fputs (_("\ --indicator-style=WORD append indicator with style WORD to entry names:\n\ none (default), classify (-F), file-type (-p)\n\ -i, --inode print index number of each file\n\ -I, --ignore=PATTERN do not list implied entries matching shell PATTERN\n\ -k like --block-size=1K\n\ "), stdout); fputs (_("\ -l use a long listing format\n\ -L, --dereference when showing file information for a symbolic\n\ link, show information for the file the link\n\ references rather than for the link itself\n\ -m fill width with a comma separated list of entries\n\ "), stdout); fputs (_("\ -n, --numeric-uid-gid like -l, but list numeric UIDs and GIDs\n\ -N, --literal print raw entry names (don't treat e.g. control\n\ characters specially)\n\ -o like -l, but do not list group information\n\ -p, --file-type append indicator (one of /=@|) to entries\n\ "), stdout); fputs (_("\ -q, --hide-control-chars print ? instead of non graphic characters\n\ --show-control-chars show non graphic characters as-is (default\n\ unless program is `ls' and output is a terminal)\n\ -Q, --quote-name enclose entry names in double quotes\n\ --quoting-style=WORD use quoting style WORD for entry names:\n\ literal, locale, shell, shell-always, c, escape\n\ "), stdout); fputs (_("\ -r, --reverse reverse order while sorting\n\ -R, --recursive list subdirectories recursively\n\ -s, --size print size of each file, in blocks\n\ "), stdout); fputs (_("\ -S sort by file size\n\ --sort=WORD extension -X, none -U, size -S, time -t,\n\ version -v\n\ status -c, time -t, atime -u, access -u, use -u\n\ --time=WORD show time as WORD instead of modification time:\n\ atime, access, use, ctime or status; use\n\ specified time as sort key if --sort=time\n\ "), stdout); fputs (_("\ --time-style=STYLE show times using style STYLE:\n\ full-iso, long-iso, iso, locale, +FORMAT\n\ FORMAT is interpreted like `date'; if FORMAT is\n\ FORMAT1<newline>FORMAT2, FORMAT1 applies to\n\ non-recent files and FORMAT2 to recent files;\n\ if STYLE is prefixed with `posix-', STYLE\n\ takes effect only outside the POSIX locale\n\ -t sort by modification time\n\ -T, --tabsize=COLS assume tab stops at each COLS instead of 8\n\ "), stdout); fputs (_("\ -u with -lt: sort by, and show, access time\n\ with -l: show access time and sort by name\n\ otherwise: sort by access time\n\ -U do not sort; list entries in directory order\n\ -v sort by version\n\ "), stdout); fputs (_("\ -w, --width=COLS assume screen width instead of current value\n\ -x list entries by lines instead of by columns\n\ -X sort alphabetically by entry extension\n\ -1 list one file per line\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\n\ SIZE may be (or may be an integer optionally followed by) one of following:\n\ kB 1000, K 1024, MB 1,000,000, M 1,048,576, and so on for G, T, P, E, Z, Y.\n\ "), stdout); fputs (_("\ \n\ By default, color is not used to distinguish types of files. That is\n\ equivalent to using --color=none. Using the --color option without the\n\ optional WHEN argument is equivalent to using --color=always. With\n\ --color=auto, color codes are output only if standard output is connected\n\ to a terminal (tty).\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); }
SPDX-FileCopyrightText: 2021 Andrius Å tikonas <andrius@stikonas.eu> SPDX-License-Identifier: GPL-2.0-or-later getdate.c is pre-compiled from getdate.y At this point we don't have bison yet and in any case getdate.y does not compile when generated with modern bison. --- src/touch.c.orig 2021-03-13 18:16:05.344355958 +0000 +++ src/touch.c 2021-03-13 18:16:26.204891355 +0000 @@ -306,7 +306,7 @@ case 'd': flexible_date++; - newtime = get_date (optarg, NULL); + newtime = 0; if (newtime == (time_t) -1) error (EXIT_FAILURE, 0, _("invalid date format %s"), quote (optarg)); date_set++;
/* touch -- change modification and access times of files Copyright (C) 87, 1989-1991, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Rubin, Arnold Robbins, Jim Kingdon, David MacKenzie, and Randy Smith. */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include "system.h" #include "argmatch.h" #include "error.h" #include "getdate.h" #include "posixtm.h" #include "posixver.h" #include "quote.h" #include "safe-read.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "touch" #define AUTHORS \ N_ ("Paul Rubin, Arnold Robbins, Jim Kingdon, David MacKenzie, and Randy Smith") #ifndef STDC_HEADERS time_t time (); #endif /* Bitmasks for `change_times'. */ #define CH_ATIME 1 #define CH_MTIME 2 #if !defined O_NDELAY # define O_NDELAY 0 #endif #if !defined O_NONBLOCK # define O_NONBLOCK O_NDELAY #endif #if !defined O_NOCTTY # define O_NOCTTY 0 #endif #if !defined EISDIR # define EISDIR 0 #endif /* The name by which this program was run. */ char *program_name; /* Which timestamps to change. */ static int change_times; /* (-c) If nonzero, don't create if not already there. */ static int no_create; /* (-d) If nonzero, date supplied on command line in get_date formats. */ static int flexible_date; /* (-r) If nonzero, use times from a reference file. */ static int use_ref; /* (-t) If nonzero, date supplied on command line in POSIX format. */ static int posix_date; /* If nonzero, the only thing we have to do is change both the modification and access time to the current time, so we don't have to own the file, just be able to read and write it. On some systems, we can do this if we own the file, even though we have neither read nor write access to it. */ static int amtime_now; /* New time to use when setting time. */ static time_t newtime; /* File to use for -r. */ static char *ref_file; /* Info about the reference file. */ static struct stat ref_stats; /* For long options that have no equivalent short option, use a non-character as a pseudo short option, starting with CHAR_MAX + 1. */ enum { TIME_OPTION = CHAR_MAX + 1 }; static struct option const longopts[] = { {"time", required_argument, 0, TIME_OPTION}, {"no-create", no_argument, 0, 'c'}, {"date", required_argument, 0, 'd'}, {"file", required_argument, 0, 'r'}, /* FIXME: phase out --file */ {"reference", required_argument, 0, 'r'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {0, 0, 0, 0} }; /* Valid arguments to the `--time' option. */ static char const* const time_args[] = { "atime", "access", "use", "mtime", "modify", 0 }; /* The bits in `change_times' that those arguments set. */ static int const time_masks[] = { CH_ATIME, CH_ATIME, CH_ATIME, CH_MTIME, CH_MTIME }; /* Update the time of file FILE according to the options given. Return 0 if successful, 1 if an error occurs. */ static int touch (const char *file) { int status; struct stat sbuf; int fd = -1; int open_errno = 0; if (! no_create) { /* Try to open FILE, creating it if necessary. */ fd = open (file, O_WRONLY | O_CREAT | O_NONBLOCK | O_NOCTTY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); /* Don't save a copy of errno if it's EISDIR, since that would lead touch to give a bogus diagnostic for e.g., `touch /' (assuming we don't own / or have write access to it). On Solaris 5.6, and probably other systems, it is EINVAL. On SunOS4, it's EPERM. */ if (fd == -1 && errno != EISDIR && errno != EINVAL && errno != EPERM) open_errno = errno; } if (! amtime_now) { /* We're setting only one of the time values. stat the target to get the other one. If we have the file descriptor already, use fstat. Otherwise, either we're in no-create mode (and hence didn't call open) or FILE is inaccessible or a directory, so we have to use stat. */ if (fd != -1 ? fstat (fd, &sbuf) : stat (file, &sbuf)) { if (open_errno) error (0, open_errno, _("creating %s"), quote (file)); else { if (no_create && errno == ENOENT) return 0; error (0, errno, _("failed to get attributes of %s"), quote (file)); } close (fd); return 1; } } if (fd != -1 && close (fd) < 0) { error (0, errno, _("creating %s"), quote (file)); return 1; } if (amtime_now) { /* Pass NULL to utime so it will not fail if we just have write access to the file, but don't own it. */ status = utime (file, NULL); } else { struct utimbuf utb; /* There's currently no interface to set file timestamps with better than 1-second resolution, so discard any fractional part of the source timestamp. */ if (use_ref) { utb.actime = ref_stats.st_atime; utb.modtime = ref_stats.st_mtime; } else utb.actime = utb.modtime = newtime; if (!(change_times & CH_ATIME)) utb.actime = sbuf.st_atime; if (!(change_times & CH_MTIME)) utb.modtime = sbuf.st_mtime; status = utime (file, &utb); } if (status) { if (open_errno) { /* The wording of this diagnostic should cover at least two cases: - the file does not exist, but the parent directory is unwritable - the file exists, but it isn't writable I think it's not worth trying to distinguish them. */ error (0, open_errno, _("cannot touch %s"), quote (file)); } else { if (no_create && errno == ENOENT) return 0; error (0, errno, _("setting times of %s"), quote (file)); } return 1; } return 0; } void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("Usage: %s [OPTION]... FILE...\n"), program_name); fputs (_("\ Update the access and modification times of each FILE to the current time.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ -a change only the access time\n\ -c, --no-create do not create any files\n\ -d, --date=STRING parse STRING and use it instead of current time\n\ -f (ignored)\n\ -m change only the modification time\n\ "), stdout); fputs (_("\ -r, --reference=FILE use this file's times instead of current time\n\ -t STAMP use [[CC]YY]MMDDhhmm[.ss] instead of current time\n\ --time=WORD set time given by WORD: access atime use (same as -a)\n\ modify mtime (same as -m)\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ Note that the -d and -t options accept different time-date formats.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } int main (int argc, char **argv) { int c; int date_set = 0; int err = 0; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); change_times = no_create = use_ref = posix_date = flexible_date = 0; while ((c = getopt_long (argc, argv, "acd:fmr:t:", longopts, NULL)) != -1) { switch (c) { case 0: break; case 'a': change_times |= CH_ATIME; break; case 'c': no_create++; break; case 'd': flexible_date++; newtime = get_date (optarg, NULL); if (newtime == (time_t) -1) error (EXIT_FAILURE, 0, _("invalid date format %s"), quote (optarg)); date_set++; break; case 'f': break; case 'm': change_times |= CH_MTIME; break; case 'r': use_ref++; ref_file = optarg; break; case 't': posix_date++; if (! posixtime (&newtime, optarg, PDS_LEADING_YEAR | PDS_CENTURY | PDS_SECONDS)) error (EXIT_FAILURE, 0, _("invalid date format %s"), quote (optarg)); date_set++; break; case TIME_OPTION: /* --time */ change_times |= XARGMATCH ("--time", optarg, time_args, time_masks); break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } if (change_times == 0) change_times = CH_ATIME | CH_MTIME; if ((use_ref && (posix_date || flexible_date)) || (posix_date && flexible_date)) { error (0, 0, _("cannot specify times from more than one source")); usage (EXIT_FAILURE); } if (use_ref) { if (stat (ref_file, &ref_stats)) error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), quote (ref_file)); date_set++; } /* The obsolete `MMDDhhmm[YY]' form is valid IFF there are two or more non-option arguments. */ if (!date_set && 2 <= argc - optind && !STREQ (argv[optind - 1], "--") && posix2_version () < 200112) { if (posixtime (&newtime, argv[optind], PDS_TRAILING_YEAR)) { if (! getenv ("POSIXLY_CORRECT")) { struct tm const *tm = localtime (&newtime); error (0, 0, _("warning: `touch %s' is obsolete; use\ `touch -t %04d%02d%02d%02d%02d.%02d'"), argv[optind], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); } optind++; date_set++; } } if (!date_set) { if ((change_times & (CH_ATIME | CH_MTIME)) == (CH_ATIME | CH_MTIME)) amtime_now = 1; else time (&newtime); } if (optind == argc) { error (0, 0, _("file arguments missing")); usage (EXIT_FAILURE); } for (; optind < argc; ++optind) err += touch (argv[optind]); exit (err != 0); }
SPDX-FileCopyrightText: 2022 Andrius Å tikonas <andrius@stikonas.eu> SPDX-FileCopyrightText: 2009 Eric Blake <ebb9@byu.net> SPDX-License-Identifier: GPL-2.0-or-later touch: add -h to change symlink timestamps, where supported diff -r -U3 coreutils-5.0.orig/src/touch.c coreutils-5.0/src/touch.c --- src/touch.c 2002-12-20 20:09:22.000000000 +0000 +++ src/touch.c 2022-05-16 20:31:37.801988595 +0100 @@ -77,6 +77,9 @@ /* (-r) If nonzero, use times from a reference file. */ static int use_ref; +/* (-h) If true, change the times of an existing symlink, if possible. */ +static int no_dereference; + /* (-t) If nonzero, date supplied on command line in POSIX format. */ static int posix_date; @@ -110,6 +113,7 @@ {"date", required_argument, 0, 'd'}, {"file", required_argument, 0, 'r'}, /* FIXME: phase out --file */ {"reference", required_argument, 0, 'r'}, + {"no-dereference", no_argument, NULL, 'h'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {0, 0, 0, 0} @@ -138,7 +142,7 @@ int fd = -1; int open_errno = 0; - if (! no_create) + if (! (no_create || no_dereference)) { /* Try to open FILE, creating it if necessary. */ fd = open (file, O_WRONLY | O_CREAT | O_NONBLOCK | O_NOCTTY, @@ -158,7 +162,7 @@ the other one. If we have the file descriptor already, use fstat. Otherwise, either we're in no-create mode (and hence didn't call open) or FILE is inaccessible or a directory, so we have to use stat. */ - if (fd != -1 ? fstat (fd, &sbuf) : stat (file, &sbuf)) + if (fd != -1 ? fstat (fd, &sbuf) : (no_dereference ? lstat (file, &sbuf) : stat (file, &sbuf))) { if (open_errno) error (0, open_errno, _("creating %s"), quote (file)); @@ -223,7 +227,7 @@ } else { - if (no_create && errno == ENOENT) + if ((no_create || no_dereference) && errno == ENOENT) return 0; error (0, errno, _("setting times of %s"), quote (file)); } @@ -254,6 +258,9 @@ -c, --no-create do not create any files\n\ -d, --date=STRING parse STRING and use it instead of current time\n\ -f (ignored)\n\ + -h, --no-dereference affect each symbolic link instead of any referenced\n\ + file (useful only on systems that can change the\n\ + timestamps of a symlink)\n\ -m change only the modification time\n\ "), stdout); fputs (_("\ @@ -289,7 +296,7 @@ change_times = no_create = use_ref = posix_date = flexible_date = 0; - while ((c = getopt_long (argc, argv, "acd:fmr:t:", longopts, NULL)) != -1) + while ((c = getopt_long (argc, argv, "acd:fhmr:t:", longopts, NULL)) != -1) { switch (c) { @@ -315,6 +322,10 @@ case 'f': break; + case 'h': + no_dereference = true; + break; + case 'm': change_times |= CH_MTIME; break; @@ -358,7 +369,10 @@ if (use_ref) { - if (stat (ref_file, &ref_stats)) + /* Don't use (no_dereference ? lstat : stat) (args), since stat + might be an object-like macro. */ + if (no_dereference ? lstat (ref_file, &ref_stats) + : stat (ref_file, &ref_stats)) error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), quote (ref_file)); date_set++;
SPDX-FileCopyrightText: 2022 Andrius Å tikonas <andrius@stikonas.eu> SPDX-License-Identifier: GPL-2.0-or-later uint64_t is not supported in tcc 0.9.27 --- lib/tempname.c 2002-12-01 10:40:32.000000000 +0000 +++ lib/tempname.c 2022-06-22 20:57:37.449423973 +0100 @@ -231,8 +231,8 @@ { int len; char *XXXXXX; - static uint64_t value; - uint64_t random_time_bits; + static unsigned long long value; + unsigned long long random_time_bits; unsigned int count; int fd = -1; int save_errno = errno; @@ -278,7 +278,7 @@ for (count = 0; count < attempts; value += 7777, ++count) { - uint64_t v = value; + unsigned long long v = value; /* Fill in the random bits. */ XXXXXX[0] = letters[v % 62];
/* tempname.c - generate the name of a temporary file. Copyright (C) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <sys/types.h> #include <assert.h> #include <errno.h> #ifndef __set_errno # define __set_errno(Val) errno = (Val) #endif #include <stdio.h> #ifndef P_tmpdir # define P_tmpdir "/tmp" #endif #ifndef TMP_MAX # define TMP_MAX 238328 #endif #ifndef __GT_FILE # define __GT_FILE 0 # define __GT_BIGFILE 1 # define __GT_DIR 2 # define __GT_NOCREATE 3 #endif #if STDC_HEADERS || _LIBC # include <stddef.h> # include <string.h> #endif #include <stdlib.h> #if HAVE_FCNTL_H || _LIBC # include <fcntl.h> #endif #if HAVE_SYS_TIME_H || _LIBC # include <sys/time.h> #endif #if HAVE_STDINT_H || _LIBC # include <stdint.h> #endif #if HAVE_UNISTD_H || _LIBC # include <unistd.h> #endif #include <sys/stat.h> #if STAT_MACROS_BROKEN # undef S_ISDIR #endif #if !defined S_ISDIR && defined S_IFDIR # define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) #endif #if !S_IRUSR && S_IREAD # define S_IRUSR S_IREAD #endif #if !S_IRUSR # define S_IRUSR 00400 #endif #if !S_IWUSR && S_IWRITE # define S_IWUSR S_IWRITE #endif #if !S_IWUSR # define S_IWUSR 00200 #endif #if !S_IXUSR && S_IEXEC # define S_IXUSR S_IEXEC #endif #if !S_IXUSR # define S_IXUSR 00100 #endif #if _LIBC # define struct_stat64 struct stat64 #else # define struct_stat64 struct stat # define __getpid getpid # define __gettimeofday gettimeofday # define __mkdir mkdir # define __open open # define __open64 open # define __lxstat64(version, path, buf) lstat (path, buf) # define __xstat64(version, path, buf) stat (path, buf) #endif #if ! (HAVE___SECURE_GETENV || _LIBC) # define __secure_getenv getenv #endif #ifdef _LIBC # include <hp-timing.h> # if HP_TIMING_AVAIL # define RANDOM_BITS(Var) \ if (__builtin_expect (value == UINT64_C (0), 0)) \ { \ /* If this is the first time this function is used initialize \ the variable we accumulate the value in to some somewhat \ random value. If we'd not do this programs at startup time \ might have a reduced set of possible names, at least on slow \ machines. */ \ struct timeval tv; \ __gettimeofday (&tv, NULL); \ value = ((uint64_t) tv.tv_usec << 16) ^ tv.tv_sec; \ } \ HP_TIMING_NOW (Var) # endif #endif /* Use the widest available unsigned type if uint64_t is not available. The algorithm below extracts a number less than 62**6 (approximately 2**35.725) from uint64_t, so ancient hosts where uintmax_t is only 32 bits lose about 3.725 bits of randomness, which is better than not having mkstemp at all. */ #if !defined UINT64_MAX && !defined uint64_t # define uint64_t uintmax_t #endif /* Return nonzero if DIR is an existent directory. */ static int direxists (const char *dir) { struct_stat64 buf; return __xstat64 (_STAT_VER, dir, &buf) == 0 && S_ISDIR (buf.st_mode); } /* Path search algorithm, for tmpnam, tmpfile, etc. If DIR is non-null and exists, uses it; otherwise uses the first of $TMPDIR, P_tmpdir, /tmp that exists. Copies into TMPL a template suitable for use with mk[s]temp. Will fail (-1) if DIR is non-null and doesn't exist, none of the searched dirs exists, or there's not enough space in TMPL. */ int __path_search (char *tmpl, size_t tmpl_len, const char *dir, const char *pfx, int try_tmpdir) { const char *d; size_t dlen, plen; if (!pfx || !pfx[0]) { pfx = "file"; plen = 4; } else { plen = strlen (pfx); if (plen > 5) plen = 5; } if (try_tmpdir) { d = __secure_getenv ("TMPDIR"); if (d != NULL && direxists (d)) dir = d; else if (dir != NULL && direxists (dir)) /* nothing */ ; else dir = NULL; } if (dir == NULL) { if (direxists (P_tmpdir)) dir = P_tmpdir; else if (strcmp (P_tmpdir, "/tmp") != 0 && direxists ("/tmp")) dir = "/tmp"; else { __set_errno (ENOENT); return -1; } } dlen = strlen (dir); while (dlen > 1 && dir[dlen - 1] == '/') dlen--; /* remove trailing slashes */ /* check we have room for "${dir}/${pfx}XXXXXX\0" */ if (tmpl_len < dlen + 1 + plen + 6 + 1) { __set_errno (EINVAL); return -1; } sprintf (tmpl, "%.*s/%.*sXXXXXX", (int) dlen, dir, (int) plen, pfx); return 0; } /* These are the characters used in temporary filenames. */ static const char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; /* Generate a temporary file name based on TMPL. TMPL must match the rules for mk[s]temp (i.e. end in "XXXXXX"). The name constructed does not exist at the time of the call to __gen_tempname. TMPL is overwritten with the result. KIND may be one of: __GT_NOCREATE: simply verify that the name does not exist at the time of the call. __GT_FILE: create the file using open(O_CREAT|O_EXCL) and return a read-write fd. The file is mode 0600. __GT_BIGFILE: same as __GT_FILE but use open64(). __GT_DIR: create a directory, which will be mode 0700. We use a clever algorithm to get hard-to-predict names. */ int __gen_tempname (char *tmpl, int kind) { int len; char *XXXXXX; static uint64_t value; uint64_t random_time_bits; unsigned int count; int fd = -1; int save_errno = errno; struct_stat64 st; /* A lower bound on the number of temporary files to attempt to generate. The maximum total number of temporary file names that can exist for a given template is 62**6. It should never be necessary to try all these combinations. Instead if a reasonable number of names is tried (we define reasonable as 62**3) fail to give the system administrator the chance to remove the problems. */ unsigned int attempts_min = 62 * 62 * 62; /* The number of times to attempt to generate a temporary file. To conform to POSIX, this must be no smaller than TMP_MAX. */ unsigned int attempts = attempts_min < TMP_MAX ? TMP_MAX : attempts_min; len = strlen (tmpl); if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX")) { __set_errno (EINVAL); return -1; } /* This is where the Xs start. */ XXXXXX = &tmpl[len - 6]; /* Get some more or less random data. */ #ifdef RANDOM_BITS RANDOM_BITS (random_time_bits); #else # if HAVE_GETTIMEOFDAY || _LIBC { struct timeval tv; __gettimeofday (&tv, NULL); random_time_bits = ((uint64_t) tv.tv_usec << 16) ^ tv.tv_sec; } # else random_time_bits = time (NULL); # endif #endif value += random_time_bits ^ __getpid (); for (count = 0; count < attempts; value += 7777, ++count) { uint64_t v = value; /* Fill in the random bits. */ XXXXXX[0] = letters[v % 62]; v /= 62; XXXXXX[1] = letters[v % 62]; v /= 62; XXXXXX[2] = letters[v % 62]; v /= 62; XXXXXX[3] = letters[v % 62]; v /= 62; XXXXXX[4] = letters[v % 62]; v /= 62; XXXXXX[5] = letters[v % 62]; switch (kind) { case __GT_FILE: fd = __open (tmpl, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); break; case __GT_BIGFILE: fd = __open64 (tmpl, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); break; case __GT_DIR: fd = __mkdir (tmpl, S_IRUSR | S_IWUSR | S_IXUSR); break; case __GT_NOCREATE: /* This case is backward from the other three. __gen_tempname succeeds if __xstat fails because the name does not exist. Note the continue to bypass the common logic at the bottom of the loop. */ if (__lxstat64 (_STAT_VER, tmpl, &st) < 0) { if (errno == ENOENT) { __set_errno (save_errno); return 0; } else /* Give up now. */ return -1; } continue; default: assert (! "invalid KIND in __gen_tempname"); } if (fd >= 0) { __set_errno (save_errno); return fd; } else if (errno != EEXIST) return -1; } /* We got out of the loop because we ran out of combinations to try. */ __set_errno (EEXIST); return -1; }
SPDX-FileCopyrightText: 2023 Emily Trau <emily@downunderctf.com> SPDX-License-Identifier: GPL-2.0-or-later strcoll() does not exist in mes libc, change it to strcmp. --- src/expr.c +++ src/expr.c @@ -332,7 +332,7 @@ nextarg (char *str) return 0; else { - int r = strcoll (*args, str) == 0; + int r = strcmp (*args, str) == 0; args += r; return r; } @@ -668,7 +668,7 @@ eval2 (void) r = eval3 (); tostring (l); tostring (r); - lval = strcoll (l->u.s, r->u.s); + lval = strcmp (l->u.s, r->u.s); rval = 0; if (toarith (l) && toarith (r)) {
/* expr -- evaluate expressions. Copyright (C) 86, 1991-1997, 1999-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Author: Mike Parker. This program evaluates expressions. Each token (operator, operand, parenthesis) of the expression must be a seperate argument. The parser used is a reasonably general one, though any incarnation of it is language-specific. It is especially nice for expressions. No parse tree is needed; a new node is evaluated immediately. One function can handle multiple operators all of equal precedence, provided they all associate ((x op x) op x). Define EVAL_TRACE to print an evaluation trace. */ #include <config.h> #include <stdio.h> #include <sys/types.h> #include "system.h" #include <regex.h> #include "long-options.h" #include "error.h" #include "closeout.h" #include "inttostr.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "expr" #define AUTHORS "Mike Parker" #undef NEW #define NEW(Type) XMALLOC (Type, 1) #define OLD(x) free ((char *) x) /* The kinds of value we can have. */ enum valtype { integer, string }; typedef enum valtype TYPE; /* A value is.... */ struct valinfo { TYPE type; /* Which kind. */ union { /* The value itself. */ intmax_t i; char *s; } u; }; typedef struct valinfo VALUE; /* The arguments given to the program, minus the program name. */ static char **args; /* The name this program was run with. */ char *program_name; static VALUE *eval (void); static int nomoreargs (void); static int null (VALUE *v); static void printv (VALUE *v); void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s EXPRESSION\n\ or: %s OPTION\n\ "), program_name, program_name); putchar ('\n'); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ Print the value of EXPRESSION to standard output. A blank line below\n\ separates increasing precedence groups. EXPRESSION may be:\n\ \n\ ARG1 | ARG2 ARG1 if it is neither null nor 0, otherwise ARG2\n\ \n\ ARG1 & ARG2 ARG1 if neither argument is null or 0, otherwise 0\n\ "), stdout); fputs (_("\ \n\ ARG1 < ARG2 ARG1 is less than ARG2\n\ ARG1 <= ARG2 ARG1 is less than or equal to ARG2\n\ ARG1 = ARG2 ARG1 is equal to ARG2\n\ ARG1 != ARG2 ARG1 is unequal to ARG2\n\ ARG1 >= ARG2 ARG1 is greater than or equal to ARG2\n\ ARG1 > ARG2 ARG1 is greater than ARG2\n\ "), stdout); fputs (_("\ \n\ ARG1 + ARG2 arithmetic sum of ARG1 and ARG2\n\ ARG1 - ARG2 arithmetic difference of ARG1 and ARG2\n\ "), stdout); fputs (_("\ \n\ ARG1 * ARG2 arithmetic product of ARG1 and ARG2\n\ ARG1 / ARG2 arithmetic quotient of ARG1 divided by ARG2\n\ ARG1 % ARG2 arithmetic remainder of ARG1 divided by ARG2\n\ "), stdout); fputs (_("\ \n\ STRING : REGEXP anchored pattern match of REGEXP in STRING\n\ \n\ match STRING REGEXP same as STRING : REGEXP\n\ substr STRING POS LENGTH substring of STRING, POS counted from 1\n\ index STRING CHARS index in STRING where any CHARS is found, or 0\n\ length STRING length of STRING\n\ "), stdout); fputs (_("\ + TOKEN interpret TOKEN as a string, even if it is a\n\ keyword like `match' or an operator like `/'\n\ \n\ ( EXPRESSION ) value of EXPRESSION\n\ "), stdout); fputs (_("\ \n\ Beware that many operators need to be escaped or quoted for shells.\n\ Comparisons are arithmetic if both ARGs are numbers, else lexicographical.\n\ Pattern matches return the string matched between \\( and \\) or null; if\n\ \\( and \\) are not used, they return the number of characters matched or 0.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } int main (int argc, char **argv) { VALUE *v; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, AUTHORS, usage); /* The above handles --help and --version. Since there is no other invocation of getopt, handle `--' here. */ if (argc > 1 && STREQ (argv[1], "--")) { --argc; ++argv; } if (argc == 1) { error (0, 0, _("too few arguments")); usage (EXIT_FAILURE); } args = argv + 1; v = eval (); if (!nomoreargs ()) error (2, 0, _("syntax error")); printv (v); exit (null (v)); } /* Return a VALUE for I. */ static VALUE * int_value (intmax_t i) { VALUE *v; v = NEW (VALUE); v->type = integer; v->u.i = i; return v; } /* Return a VALUE for S. */ static VALUE * str_value (char *s) { VALUE *v; v = NEW (VALUE); v->type = string; v->u.s = xstrdup (s); return v; } /* Free VALUE V, including structure components. */ static void freev (VALUE *v) { if (v->type == string) free (v->u.s); OLD (v); } /* Print VALUE V. */ static void printv (VALUE *v) { char *p; char buf[INT_STRLEN_BOUND (intmax_t) + 1]; switch (v->type) { case integer: p = imaxtostr (v->u.i, buf); break; case string: p = v->u.s; break; default: abort (); } puts (p); } /* Return nonzero if V is a null-string or zero-number. */ static int null (VALUE *v) { switch (v->type) { case integer: return v->u.i == 0; case string: return v->u.s[0] == '\0' || strcmp (v->u.s, "0") == 0; default: abort (); } } /* Coerce V to a string value (can't fail). */ static void tostring (VALUE *v) { char buf[INT_STRLEN_BOUND (intmax_t) + 1]; switch (v->type) { case integer: v->u.s = xstrdup (imaxtostr (v->u.i, buf)); v->type = string; break; case string: break; default: abort (); } } /* Coerce V to an integer value. Return 1 on success, 0 on failure. */ static int toarith (VALUE *v) { intmax_t i; int neg; char *cp; switch (v->type) { case integer: return 1; case string: i = 0; cp = v->u.s; neg = (*cp == '-'); if (neg) cp++; do { if (ISDIGIT (*cp)) i = i * 10 + *cp - '0'; else return 0; } while (*++cp); free (v->u.s); v->u.i = i * (neg ? -1 : 1); v->type = integer; return 1; default: abort (); } } /* Return nonzero and advance if the next token matches STR exactly. STR must not be NULL. */ static int nextarg (char *str) { if (*args == NULL) return 0; else { int r = strcoll (*args, str) == 0; args += r; return r; } } /* Return nonzero if there no more tokens. */ static int nomoreargs (void) { return *args == 0; } #ifdef EVAL_TRACE /* Print evaluation trace and args remaining. */ static void trace (fxn) char *fxn; { char **a; printf ("%s:", fxn); for (a = args; *a; a++) printf (" %s", *a); putchar ('\n'); } #endif /* Do the : operator. SV is the VALUE for the lhs (the string), PV is the VALUE for the rhs (the pattern). */ static VALUE * docolon (VALUE *sv, VALUE *pv) { VALUE *v; const char *errmsg; struct re_pattern_buffer re_buffer; struct re_registers re_regs; size_t len; int matchlen; tostring (sv); tostring (pv); if (pv->u.s[0] == '^') { error (0, 0, _("\ warning: unportable BRE: `%s': using `^' as the first character\n\ of the basic regular expression is not portable; it is being ignored"), pv->u.s); } len = strlen (pv->u.s); memset (&re_buffer, 0, sizeof (re_buffer)); memset (&re_regs, 0, sizeof (re_regs)); re_buffer.allocated = 2 * len; if (re_buffer.allocated < len) xalloc_die (); re_buffer.buffer = (unsigned char *) xmalloc (re_buffer.allocated); re_buffer.translate = 0; re_syntax_options = RE_SYNTAX_POSIX_BASIC; errmsg = re_compile_pattern (pv->u.s, len, &re_buffer); if (errmsg) error (2, 0, "%s", errmsg); matchlen = re_match (&re_buffer, sv->u.s, strlen (sv->u.s), 0, &re_regs); if (0 <= matchlen) { /* Were \(...\) used? */ if (re_buffer.re_nsub > 0)/* was (re_regs.start[1] >= 0) */ { sv->u.s[re_regs.end[1]] = '\0'; v = str_value (sv->u.s + re_regs.start[1]); } else v = int_value (matchlen); } else { /* Match failed -- return the right kind of null. */ if (re_buffer.re_nsub > 0) v = str_value (""); else v = int_value (0); } free (re_buffer.buffer); return v; } /* Handle bare operands and ( expr ) syntax. */ static VALUE * eval7 (void) { VALUE *v; #ifdef EVAL_TRACE trace ("eval7"); #endif if (nomoreargs ()) error (2, 0, _("syntax error")); if (nextarg ("(")) { v = eval (); if (!nextarg (")")) error (2, 0, _("syntax error")); return v; } if (nextarg (")")) error (2, 0, _("syntax error")); return str_value (*args++); } /* Handle match, substr, index, and length keywords, and quoting "+". */ static VALUE * eval6 (void) { VALUE *l; VALUE *r; VALUE *v; VALUE *i1; VALUE *i2; #ifdef EVAL_TRACE trace ("eval6"); #endif if (nextarg ("+")) { if (nomoreargs ()) error (2, 0, _("syntax error")); return str_value (*args++); } else if (nextarg ("length")) { r = eval6 (); tostring (r); v = int_value (strlen (r->u.s)); freev (r); return v; } else if (nextarg ("match")) { l = eval6 (); r = eval6 (); v = docolon (l, r); freev (l); freev (r); return v; } else if (nextarg ("index")) { l = eval6 (); r = eval6 (); tostring (l); tostring (r); v = int_value (strcspn (l->u.s, r->u.s) + 1); if (v->u.i == strlen (l->u.s) + 1) v->u.i = 0; freev (l); freev (r); return v; } else if (nextarg ("substr")) { l = eval6 (); i1 = eval6 (); i2 = eval6 (); tostring (l); if (!toarith (i1) || !toarith (i2) || strlen (l->u.s) < i1->u.i || i1->u.i <= 0 || i2->u.i <= 0) v = str_value (""); else { v = NEW (VALUE); v->type = string; v->u.s = strncpy ((char *) xmalloc (i2->u.i + 1), l->u.s + i1->u.i - 1, i2->u.i); v->u.s[i2->u.i] = 0; } freev (l); freev (i1); freev (i2); return v; } else return eval7 (); } /* Handle : operator (pattern matching). Calls docolon to do the real work. */ static VALUE * eval5 (void) { VALUE *l; VALUE *r; VALUE *v; #ifdef EVAL_TRACE trace ("eval5"); #endif l = eval6 (); while (1) { if (nextarg (":")) { r = eval6 (); v = docolon (l, r); freev (l); freev (r); l = v; } else return l; } } /* Handle *, /, % operators. */ static VALUE * eval4 (void) { VALUE *l; VALUE *r; enum { multiply, divide, mod } fxn; intmax_t val; #ifdef EVAL_TRACE trace ("eval4"); #endif l = eval5 (); while (1) { if (nextarg ("*")) fxn = multiply; else if (nextarg ("/")) fxn = divide; else if (nextarg ("%")) fxn = mod; else return l; r = eval5 (); if (!toarith (l) || !toarith (r)) error (2, 0, _("non-numeric argument")); if (fxn == multiply) val = l->u.i * r->u.i; else { if (r->u.i == 0) error (2, 0, _("division by zero")); val = fxn == divide ? l->u.i / r->u.i : l->u.i % r->u.i; } freev (l); freev (r); l = int_value (val); } } /* Handle +, - operators. */ static VALUE * eval3 (void) { VALUE *l; VALUE *r; enum { plus, minus } fxn; intmax_t val; #ifdef EVAL_TRACE trace ("eval3"); #endif l = eval4 (); while (1) { if (nextarg ("+")) fxn = plus; else if (nextarg ("-")) fxn = minus; else return l; r = eval4 (); if (!toarith (l) || !toarith (r)) error (2, 0, _("non-numeric argument")); val = fxn == plus ? l->u.i + r->u.i : l->u.i - r->u.i; freev (l); freev (r); l = int_value (val); } } /* Handle comparisons. */ static VALUE * eval2 (void) { VALUE *l; VALUE *r; enum { less_than, less_equal, equal, not_equal, greater_equal, greater_than } fxn; int val; intmax_t lval; intmax_t rval; #ifdef EVAL_TRACE trace ("eval2"); #endif l = eval3 (); while (1) { if (nextarg ("<")) fxn = less_than; else if (nextarg ("<=")) fxn = less_equal; else if (nextarg ("=") || nextarg ("==")) fxn = equal; else if (nextarg ("!=")) fxn = not_equal; else if (nextarg (">=")) fxn = greater_equal; else if (nextarg (">")) fxn = greater_than; else return l; r = eval3 (); tostring (l); tostring (r); lval = strcoll (l->u.s, r->u.s); rval = 0; if (toarith (l) && toarith (r)) { lval = l->u.i; rval = r->u.i; } switch (fxn) { case less_than: val = (lval < rval); break; case less_equal: val = (lval <= rval); break; case equal: val = (lval == rval); break; case not_equal: val = (lval != rval); break; case greater_equal: val = (lval >= rval); break; case greater_than: val = (lval > rval); break; default: abort (); } freev (l); freev (r); l = int_value (val); } } /* Handle &. */ static VALUE * eval1 (void) { VALUE *l; VALUE *r; #ifdef EVAL_TRACE trace ("eval1"); #endif l = eval2 (); while (1) { if (nextarg ("&")) { r = eval2 (); if (null (l) || null (r)) { freev (l); freev (r); l = int_value (0); } else freev (r); } else return l; } } /* Handle |. */ static VALUE * eval (void) { VALUE *l; VALUE *r; #ifdef EVAL_TRACE trace ("eval"); #endif l = eval1 (); while (1) { if (nextarg ("|")) { r = eval1 (); if (null (l)) { freev (l); l = r; } else freev (r); } else return l; } }
SPDX-FileCopyrightText: 2023 Emily Trau <emily@downunderctf.com> SPDX-License-Identifier: GPL-2.0-or-later strcoll() does not exist in mes libc, change it to strcmp. hard_LC_COLLATE is used but not declared when HAVE_SETLOCALE is unset. --- lib/memcoll.c +++ lib/memcoll.c @@ -47,7 +47,7 @@ memcoll (char *s1, size_t s1len, char *s2, size_t s2len) s1[s1len++] = '\0'; s2[s2len++] = '\0'; - while (! (errno = 0, (diff = strcoll (s1, s2)) || errno)) + while (! (errno = 0, (diff = strcmp (s1, s2)) || errno)) { /* strcoll found no difference, but perhaps it was fooled by NUL characters in the data. Work around this problem by advancing --- src/sort.c +++ src/sort.c @@ -91,13 +91,13 @@ double strtod (); #define NEGATION_SIGN '-' #define NUMERIC_ZERO '0' +/* Nonzero if the corresponding locales are hard. */ +static int hard_LC_COLLATE; #if HAVE_SETLOCALE static char decimal_point; static int th_sep; /* if CHAR_MAX + 1, then there is no thousands separator */ -/* Nonzero if the corresponding locales are hard. */ -static int hard_LC_COLLATE; # if HAVE_NL_LANGINFO static int hard_LC_TIME; # endif
/* Locale-specific memory comparison. Copyright 1999, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Contributed by Paul Eggert <eggert@twinsun.com>. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <errno.h> #ifndef errno extern int errno; #endif #include <sys/types.h> #if HAVE_STRING_H # include <string.h> #endif /* Compare S1 (with length S1LEN) and S2 (with length S2LEN) according to the LC_COLLATE locale. S1 and S2 do not overlap, and are not adjacent. Temporarily modify the bytes after S1 and S2, but restore their original contents before returning. Set errno to an error number if there is an error, and to zero otherwise. */ int memcoll (char *s1, size_t s1len, char *s2, size_t s2len) { int diff; char n1 = s1[s1len]; char n2 = s2[s2len]; s1[s1len++] = '\0'; s2[s2len++] = '\0'; while (! (errno = 0, (diff = strcoll (s1, s2)) || errno)) { /* strcoll found no difference, but perhaps it was fooled by NUL characters in the data. Work around this problem by advancing past the NUL chars. */ size_t size1 = strlen (s1) + 1; size_t size2 = strlen (s2) + 1; s1 += size1; s2 += size2; s1len -= size1; s2len -= size2; if (s1len == 0) { if (s2len != 0) diff = -1; break; } else if (s2len == 0) { diff = 1; break; } } s1[s1len - 1] = n1; s2[s2len - 1] = n2; return diff; }
/* sort - sort lines of text (with all kinds of options). Copyright (C) 88, 1991-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Written December 1988 by Mike Haertel. The author may be reached (Email) at the address mike@gnu.ai.mit.edu, or (US mail) as Mike Haertel c/o Free Software Foundation. Ørn E. Hansen added NLS support in 1997. */ #include <config.h> #include <getopt.h> #include <sys/types.h> #include <signal.h> #include <stdio.h> #include <assert.h> #include "system.h" #include "long-options.h" #include "error.h" #include "hard-locale.h" #include "inttostr.h" #include "physmem.h" #include "posixver.h" #include "stdio-safer.h" #include "xmemcoll.h" #include "xstrtol.h" #if HAVE_SYS_RESOURCE_H # include <sys/resource.h> #endif #ifndef RLIMIT_DATA struct rlimit { size_t rlim_cur; }; # define getrlimit(Resource, Rlp) (-1) #endif /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "sort" #define AUTHORS N_ ("Mike Haertel and Paul Eggert") #if HAVE_LANGINFO_CODESET # include <langinfo.h> #endif #ifndef SA_NOCLDSTOP # define sigprocmask(How, Set, Oset) /* empty */ # define sigset_t int #endif #ifndef STDC_HEADERS double strtod (); #endif /* Undefine, to avoid warning about redefinition on some systems. */ /* FIXME: Remove these: use MIN/MAX from sys2.h. */ #undef min #define min(a, b) ((a) < (b) ? (a) : (b)) #undef max #define max(a, b) ((a) > (b) ? (a) : (b)) #define UCHAR_LIM (UCHAR_MAX + 1) #define UCHAR(c) ((unsigned char) (c)) #ifndef DEFAULT_TMPDIR # define DEFAULT_TMPDIR "/tmp" #endif /* Use this as exit status in case of error, not EXIT_FAILURE. This is necessary because EXIT_FAILURE is usually 1 and POSIX requires that sort exit with status 1 IFF invoked with -c and the input is not properly sorted. Any other irregular exit must exit with a status code greater than 1. */ #define SORT_FAILURE 2 #define SORT_OUT_OF_ORDER 1 #define C_DECIMAL_POINT '.' #define NEGATION_SIGN '-' #define NUMERIC_ZERO '0' #if HAVE_SETLOCALE static char decimal_point; static int th_sep; /* if CHAR_MAX + 1, then there is no thousands separator */ /* Nonzero if the corresponding locales are hard. */ static int hard_LC_COLLATE; # if HAVE_NL_LANGINFO static int hard_LC_TIME; # endif # define IS_THOUSANDS_SEP(x) ((x) == th_sep) #else # define decimal_point C_DECIMAL_POINT # define IS_THOUSANDS_SEP(x) 0 #endif #define NONZERO(x) (x != 0) /* The kind of blanks for '-b' to skip in various options. */ enum blanktype { bl_start, bl_end, bl_both }; /* The character marking end of line. Default to \n. */ static int eolchar = '\n'; /* Lines are held in core as counted strings. */ struct line { char *text; /* Text of the line. */ size_t length; /* Length including final newline. */ char *keybeg; /* Start of first key. */ char *keylim; /* Limit of first key. */ }; /* Input buffers. */ struct buffer { char *buf; /* Dynamically allocated buffer, partitioned into 3 regions: - input data; - unused area; - an array of lines, in reverse order. */ size_t used; /* Number of bytes used for input data. */ size_t nlines; /* Number of lines in the line array. */ size_t alloc; /* Number of bytes allocated. */ size_t left; /* Number of bytes left from previous reads. */ size_t line_bytes; /* Number of bytes to reserve for each line. */ int eof; /* An EOF has been read. */ }; struct keyfield { size_t sword; /* Zero-origin 'word' to start at. */ size_t schar; /* Additional characters to skip. */ int skipsblanks; /* Skip leading white space at start. */ size_t eword; /* Zero-origin first word after field. */ size_t echar; /* Additional characters in field. */ int skipeblanks; /* Skip trailing white space at finish. */ int *ignore; /* Boolean array of characters to ignore. */ char *translate; /* Translation applied to characters. */ int numeric; /* Flag for numeric comparison. Handle strings of digits with optional decimal point, but no exponential notation. */ int general_numeric; /* Flag for general, numeric comparison. Handle numbers in exponential notation. */ int month; /* Flag for comparison by month name. */ int reverse; /* Reverse the sense of comparison. */ struct keyfield *next; /* Next keyfield to try. */ }; struct month { char *name; int val; }; /* The name this program was run with. */ char *program_name; /* FIXME: None of these tables work with multibyte character sets. Also, there are many other bugs when handling multibyte characters, or even unibyte encodings where line boundaries are not in the initial shift state. One way to fix this is to rewrite `sort' to use wide characters internally, but doing this with good performance is a bit tricky. */ /* Table of white space. */ static int blanks[UCHAR_LIM]; /* Table of non-printing characters. */ static int nonprinting[UCHAR_LIM]; /* Table of non-dictionary characters (not letters, digits, or blanks). */ static int nondictionary[UCHAR_LIM]; /* Translation table folding lower case to upper. */ static char fold_toupper[UCHAR_LIM]; #define MONTHS_PER_YEAR 12 /* Table mapping month names to integers. Alphabetic order allows binary search. */ static struct month monthtab[] = { {"APR", 4}, {"AUG", 8}, {"DEC", 12}, {"FEB", 2}, {"JAN", 1}, {"JUL", 7}, {"JUN", 6}, {"MAR", 3}, {"MAY", 5}, {"NOV", 11}, {"OCT", 10}, {"SEP", 9} }; /* During the merge phase, the number of files to merge at once. */ #define NMERGE 16 /* Minimum size for a merge or check buffer. */ #define MIN_MERGE_BUFFER_SIZE (2 + sizeof (struct line)) /* Minimum sort size; the code might not work with smaller sizes. */ #define MIN_SORT_SIZE (NMERGE * MIN_MERGE_BUFFER_SIZE) /* The number of bytes needed for a merge or check buffer, which can function relatively efficiently even if it holds only one line. If a longer line is seen, this value is increased. */ static size_t merge_buffer_size = MAX (MIN_MERGE_BUFFER_SIZE, 256 * 1024); /* The approximate maximum number of bytes of main memory to use, as specified by the user. Zero if the user has not specified a size. */ static size_t sort_size; /* The guessed size for non-regular files. */ #define INPUT_FILE_SIZE_GUESS (1024 * 1024) /* Array of directory names in which any temporary files are to be created. */ static char const **temp_dirs; /* Number of temporary directory names used. */ static size_t temp_dir_count; /* Number of allocated slots in temp_dirs. */ static size_t temp_dir_alloc; /* Flag to reverse the order of all comparisons. */ static int reverse; /* Flag for stable sort. This turns off the last ditch bytewise comparison of lines, and instead leaves lines in the same order they were read if all keys compare equal. */ static int stable; /* Tab character separating fields. If NUL, then fields are separated by the empty string between a non-whitespace character and a whitespace character. */ static char tab; /* Flag to remove consecutive duplicate lines from the output. Only the last of a sequence of equal lines will be output. */ static int unique; /* Nonzero if any of the input files are the standard input. */ static int have_read_stdin; /* List of key field comparisons to be tried. */ static struct keyfield *keylist; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... [FILE]...\n\ "), program_name); fputs (_("\ Write sorted concatenation of all FILE(s) to standard output.\n\ \n\ Ordering options:\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ -b, --ignore-leading-blanks ignore leading blanks\n\ -d, --dictionary-order consider only blanks and alphanumeric characters\n\ -f, --ignore-case fold lower case to upper case characters\n\ "), stdout); fputs (_("\ -g, --general-numeric-sort compare according to general numerical value\n\ -i, --ignore-nonprinting consider only printable characters\n\ -M, --month-sort compare (unknown) < `JAN' < ... < `DEC'\n\ -n, --numeric-sort compare according to string numerical value\n\ -r, --reverse reverse the result of comparisons\n\ \n\ "), stdout); fputs (_("\ Other options:\n\ \n\ -c, --check check whether input is sorted; do not sort\n\ -k, --key=POS1[,POS2] start a key at POS1, end it at POS 2 (origin 1)\n\ -m, --merge merge already sorted files; do not sort\n\ -o, --output=FILE write result to FILE instead of standard output\n\ -s, --stable stabilize sort by disabling last-resort comparison\n\ -S, --buffer-size=SIZE use SIZE for main memory buffer\n\ "), stdout); printf (_("\ -t, --field-separator=SEP use SEP instead of non- to whitespace transition\n\ -T, --temporary-directory=DIR use DIR for temporaries, not $TMPDIR or %s\n\ multiple options specify multiple directories\n\ -u, --unique with -c: check for strict ordering\n\ otherwise: output only the first of an equal run\n\ "), DEFAULT_TMPDIR); fputs (_("\ -z, --zero-terminated end lines with 0 byte, not newline\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ POS is F[.C][OPTS], where F is the field number and C the character position\n\ in the field. OPTS is one or more single-letter ordering options, which\n\ override global ordering options for that key. If no key is given, use the\n\ entire line as the key.\n\ \n\ SIZE may be followed by the following multiplicative suffixes:\n\ "), stdout); fputs (_("\ % 1% of memory, b 1, K 1024 (default), and so on for M, G, T, P, E, Z, Y.\n\ \n\ With no FILE, or when FILE is -, read standard input.\n\ \n\ *** WARNING ***\n\ The locale specified by the environment affects sort order.\n\ Set LC_ALL=C to get the traditional sort order that uses\n\ native byte values.\n\ "), stdout ); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } /* Don't use EXIT_FAILURE here in case it is defined to be 1. POSIX requires that sort return 1 IFF invoked with -c and the input is not properly sorted. */ assert (status == 0 || status == SORT_FAILURE); exit (status); } #define COMMON_SHORT_OPTIONS "-bcdfgik:mMno:rsS:t:T:uz" static struct option const long_options[] = { {"ignore-leading-blanks", no_argument, NULL, 'b'}, {"check", no_argument, NULL, 'c'}, {"dictionary-order", no_argument, NULL, 'd'}, {"ignore-case", no_argument, NULL, 'f'}, {"general-numeric-sort", no_argument, NULL, 'g'}, {"ignore-nonprinting", no_argument, NULL, 'i'}, {"key", required_argument, NULL, 'k'}, {"merge", no_argument, NULL, 'm'}, {"month-sort", no_argument, NULL, 'M'}, {"numeric-sort", no_argument, NULL, 'n'}, {"output", required_argument, NULL, 'o'}, {"reverse", no_argument, NULL, 'r'}, {"stable", no_argument, NULL, 's'}, {"buffer-size", required_argument, NULL, 'S'}, {"field-separator", required_argument, NULL, 't'}, {"temporary-directory", required_argument, NULL, 'T'}, {"unique", no_argument, NULL, 'u'}, {"zero-terminated", no_argument, NULL, 'z'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {0, 0, 0, 0}, }; /* The set of signals that are caught. */ static sigset_t caught_signals; /* The list of temporary files. */ struct tempnode { struct tempnode *volatile next; char name[1]; /* Actual size is 1 + file name length. */ }; static struct tempnode *volatile temphead; /* Clean up any remaining temporary files. */ static void cleanup (void) { struct tempnode *node; for (node = temphead; node; node = node->next) unlink (node->name); } /* Report MESSAGE for FILE, then clean up and exit. */ static void die (char const *, char const *) ATTRIBUTE_NORETURN; static void die (char const *message, char const *file) { error (0, errno, "%s: %s", message, file); exit (SORT_FAILURE); } /* Create a new temporary file, returning its newly allocated name. Store into *PFP a stream open for writing. */ static char * create_temp_file (FILE **pfp) { static char const slashbase[] = "/sortXXXXXX"; static size_t temp_dir_index; sigset_t oldset; int fd; int saved_errno; char const *temp_dir = temp_dirs[temp_dir_index]; size_t len = strlen (temp_dir); struct tempnode *node = (struct tempnode *) xmalloc (sizeof node->next + len + sizeof slashbase); char *file = node->name; memcpy (file, temp_dir, len); memcpy (file + len, slashbase, sizeof slashbase); node->next = temphead; if (++temp_dir_index == temp_dir_count) temp_dir_index = 0; /* Create the temporary file in a critical section, to avoid races. */ sigprocmask (SIG_BLOCK, &caught_signals, &oldset); fd = mkstemp (file); if (0 <= fd) temphead = node; saved_errno = errno; sigprocmask (SIG_SETMASK, &oldset, NULL); errno = saved_errno; if (fd < 0 || (*pfp = fdopen (fd, "w")) == NULL) die (_("cannot create temporary file"), file); return file; } static FILE * xfopen (const char *file, const char *how) { FILE *fp; if (STREQ (file, "-")) { if (*how == 'r') { have_read_stdin = 1; fp = stdin; } else fp = stdout; } else { if ((fp = fopen_safer (file, how)) == NULL) die (_("open failed"), file); } return fp; } /* Close FP, whose name is FILE, and report any errors. */ static void xfclose (FILE *fp, char const *file) { if (fp == stdin) { /* Allow reading stdin from tty more than once. */ if (feof (fp)) clearerr (fp); } else { if (fclose (fp) != 0) die (_("close failed"), file); } } static void write_bytes (const char *buf, size_t n_bytes, FILE *fp, const char *output_file) { if (fwrite (buf, 1, n_bytes, fp) != n_bytes) die (_("write failed"), output_file); } /* Append DIR to the array of temporary directory names. */ static void add_temp_dir (char const *dir) { if (temp_dir_count == temp_dir_alloc) { temp_dir_alloc = temp_dir_alloc ? temp_dir_alloc * 2 : 16; temp_dirs = xrealloc (temp_dirs, sizeof (temp_dirs) * temp_dir_alloc); } temp_dirs[temp_dir_count++] = dir; } /* Search through the list of temporary files for NAME; remove it if it is found on the list. */ static void zaptemp (const char *name) { struct tempnode *volatile *pnode; struct tempnode *node; for (pnode = &temphead; (node = *pnode); pnode = &node->next) if (node->name == name) { unlink (name); *pnode = node->next; free ((char *) node); break; } } #if HAVE_NL_LANGINFO static int struct_month_cmp (const void *m1, const void *m2) { return strcmp (((const struct month *) m1)->name, ((const struct month *) m2)->name); } #endif /* Initialize the character class tables. */ static void inittables (void) { int i; for (i = 0; i < UCHAR_LIM; ++i) { if (ISBLANK (i)) blanks[i] = 1; if (!ISPRINT (i)) nonprinting[i] = 1; if (!ISALNUM (i) && !ISBLANK (i)) nondictionary[i] = 1; if (ISLOWER (i)) fold_toupper[i] = toupper (i); else fold_toupper[i] = i; } #if HAVE_NL_LANGINFO /* If we're not in the "C" locale, read different names for months. */ if (hard_LC_TIME) { for (i = 0; i < MONTHS_PER_YEAR; i++) { char *s; size_t s_len; size_t j; char *name; s = (char *) nl_langinfo (ABMON_1 + i); s_len = strlen (s); monthtab[i].name = name = (char *) xmalloc (s_len + 1); monthtab[i].val = i + 1; for (j = 0; j < s_len; j++) name[j] = fold_toupper[UCHAR (s[j])]; name[j] = '\0'; } qsort ((void *) monthtab, MONTHS_PER_YEAR, sizeof (struct month), struct_month_cmp); } #endif } /* Specify the amount of main memory to use when sorting. */ static void specify_sort_size (char const *s) { uintmax_t n; char *suffix; enum strtol_error e = xstrtoumax (s, &suffix, 10, &n, "EgGkKmMPtTYZ"); /* The default unit is KiB. */ if (e == LONGINT_OK && ISDIGIT (suffix[-1])) { if (n <= UINTMAX_MAX / 1024) n *= 1024; else e = LONGINT_OVERFLOW; } /* A 'b' suffix means bytes; a '%' suffix means percent of memory. */ if (e == LONGINT_INVALID_SUFFIX_CHAR && ISDIGIT (suffix[-1]) && ! suffix[1]) switch (suffix[0]) { case 'b': e = LONGINT_OK; break; case '%': { double mem = physmem_total () * n / 100; /* Use "<", not "<=", to avoid problems with rounding. */ if (mem < UINTMAX_MAX) { n = mem; e = LONGINT_OK; } else e = LONGINT_OVERFLOW; } break; } if (e == LONGINT_OK) { sort_size = n; if (sort_size == n) { sort_size = MAX (sort_size, MIN_SORT_SIZE); return; } e = LONGINT_OVERFLOW; } STRTOL_FATAL_ERROR (s, _("sort size"), e); } /* Return the default sort size. */ static size_t default_sort_size (void) { /* Let MEM be available memory or 1/8 of total memory, whichever is greater. */ double avail = physmem_available (); double total = physmem_total (); double mem = MAX (avail, total / 8); struct rlimit rlimit; /* Let SIZE be MEM, but no more than the maximum object size or system resource limits. Avoid the MIN macro here, as it is not quite right when only one argument is floating point. Don't bother to check for values like RLIM_INFINITY since in practice they are not much less than SIZE_MAX. */ size_t size = SIZE_MAX; if (mem < size) size = mem; if (getrlimit (RLIMIT_DATA, &rlimit) == 0 && rlimit.rlim_cur < size) size = rlimit.rlim_cur; #ifdef RLIMIT_AS if (getrlimit (RLIMIT_AS, &rlimit) == 0 && rlimit.rlim_cur < size) size = rlimit.rlim_cur; #endif /* Leave a large safety margin for the above limits, as failure can occur when they are exceeded. */ size /= 2; #ifdef RLIMIT_RSS /* Leave a 1/16 margin for RSS to leave room for code, stack, etc. Exceeding RSS is not fatal, but can be quite slow. */ if (getrlimit (RLIMIT_RSS, &rlimit) == 0 && rlimit.rlim_cur / 16 * 15 < size) size = rlimit.rlim_cur / 16 * 15; #endif /* Use no less than the minimum. */ return MAX (size, MIN_SORT_SIZE); } /* Return the sort buffer size to use with the input files identified by FPS and FILES, which are alternate paths to the same files. NFILES gives the number of input files; NFPS may be less. Assume that each input line requires LINE_BYTES extra bytes' worth of line information. Return at most SIZE_BOUND. */ static size_t sort_buffer_size (FILE *const *fps, int nfps, char *const *files, int nfiles, size_t line_bytes, size_t size_bound) { /* In the worst case, each input byte is a newline. */ size_t worst_case_per_input_byte = line_bytes + 1; /* Keep enough room for one extra input line and an extra byte. This extra room might be needed when preparing to read EOF. */ size_t size = worst_case_per_input_byte + 1; int i; for (i = 0; i < nfiles; i++) { struct stat st; off_t file_size; size_t worst_case; if ((i < nfps ? fstat (fileno (fps[i]), &st) : strcmp (files[i], "-") == 0 ? fstat (STDIN_FILENO, &st) : stat (files[i], &st)) != 0) die (_("stat failed"), files[i]); file_size = S_ISREG (st.st_mode) ? st.st_size : INPUT_FILE_SIZE_GUESS; /* Add the amount of memory needed to represent the worst case where the input consists entirely of newlines followed by a single non-newline. Check for overflow. */ worst_case = file_size * worst_case_per_input_byte + 1; if (file_size != worst_case / worst_case_per_input_byte || size_bound - size <= worst_case) return size_bound; size += worst_case; } return size; } /* Initialize BUF. Reserve LINE_BYTES bytes for each line; LINE_BYTES must be at least sizeof (struct line). Allocate ALLOC bytes initially. */ static void initbuf (struct buffer *buf, size_t line_bytes, size_t alloc) { /* Ensure that the line array is properly aligned. If the desired size cannot be allocated, repeatedly halve it until allocation succeeds. The smaller allocation may hurt overall performance, but that's better than failing. */ for (;;) { alloc += sizeof (struct line) - alloc % sizeof (struct line); buf->buf = malloc (alloc); if (buf->buf) break; alloc /= 2; if (alloc <= line_bytes + 1) xalloc_die (); } buf->line_bytes = line_bytes; buf->alloc = alloc; buf->used = buf->left = buf->nlines = 0; buf->eof = 0; } /* Return one past the limit of the line array. */ static inline struct line * buffer_linelim (struct buffer const *buf) { return (struct line *) (buf->buf + buf->alloc); } /* Return a pointer to the first character of the field specified by KEY in LINE. */ static char * begfield (const struct line *line, const struct keyfield *key) { register char *ptr = line->text, *lim = ptr + line->length - 1; register size_t sword = key->sword; register size_t schar = key->schar; register size_t remaining_bytes; if (tab) while (ptr < lim && sword--) { while (ptr < lim && *ptr != tab) ++ptr; if (ptr < lim) ++ptr; } else while (ptr < lim && sword--) { while (ptr < lim && blanks[UCHAR (*ptr)]) ++ptr; while (ptr < lim && !blanks[UCHAR (*ptr)]) ++ptr; } if (key->skipsblanks) while (ptr < lim && blanks[UCHAR (*ptr)]) ++ptr; /* Advance PTR by SCHAR (if possible), but no further than LIM. */ remaining_bytes = lim - ptr; if (schar < remaining_bytes) ptr += schar; else ptr = lim; return ptr; } /* Return the limit of (a pointer to the first character after) the field in LINE specified by KEY. */ static char * limfield (const struct line *line, const struct keyfield *key) { register char *ptr = line->text, *lim = ptr + line->length - 1; register size_t eword = key->eword, echar = key->echar; register size_t remaining_bytes; /* Note: from the POSIX spec: The leading field separator itself is included in a field when -t is not used. FIXME: move this comment up... */ /* Move PTR past EWORD fields or to one past the last byte on LINE, whichever comes first. If there are more than EWORD fields, leave PTR pointing at the beginning of the field having zero-based index, EWORD. If a delimiter character was specified (via -t), then that `beginning' is the first character following the delimiting TAB. Otherwise, leave PTR pointing at the first `blank' character after the preceding field. */ if (tab) while (ptr < lim && eword--) { while (ptr < lim && *ptr != tab) ++ptr; if (ptr < lim && (eword | echar)) ++ptr; } else while (ptr < lim && eword--) { while (ptr < lim && blanks[UCHAR (*ptr)]) ++ptr; while (ptr < lim && !blanks[UCHAR (*ptr)]) ++ptr; } #ifdef POSIX_UNSPECIFIED /* The following block of code makes GNU sort incompatible with standard Unix sort, so it's ifdef'd out for now. The POSIX spec isn't clear on how to interpret this. FIXME: request clarification. From: kwzh@gnu.ai.mit.edu (Karl Heuer) Date: Thu, 30 May 96 12:20:41 -0400 [Translated to POSIX 1003.1-2001 terminology by Paul Eggert.] [...]I believe I've found another bug in `sort'. $ cat /tmp/sort.in a b c 2 d pq rs 1 t $ textutils-1.15/src/sort -k1.7,1.7 </tmp/sort.in a b c 2 d pq rs 1 t $ /bin/sort -k1.7,1.7 </tmp/sort.in pq rs 1 t a b c 2 d Unix sort produced the answer I expected: sort on the single character in column 7. GNU sort produced different results, because it disagrees on the interpretation of the key-end spec "M.N". Unix sort reads this as "skip M-1 fields, then N-1 characters"; but GNU sort wants it to mean "skip M-1 fields, then either N-1 characters or the rest of the current field, whichever comes first". This extra clause applies only to key-ends, not key-starts. */ /* Make LIM point to the end of (one byte past) the current field. */ if (tab) { char *newlim; newlim = memchr (ptr, tab, lim - ptr); if (newlim) lim = newlim; } else { char *newlim; newlim = ptr; while (newlim < lim && blanks[UCHAR (*newlim)]) ++newlim; while (newlim < lim && !blanks[UCHAR (*newlim)]) ++newlim; lim = newlim; } #endif /* If we're skipping leading blanks, don't start counting characters until after skipping past any leading blanks. */ if (key->skipsblanks) while (ptr < lim && blanks[UCHAR (*ptr)]) ++ptr; /* Advance PTR by ECHAR (if possible), but no further than LIM. */ remaining_bytes = lim - ptr; if (echar < remaining_bytes) ptr += echar; else ptr = lim; return ptr; } /* FIXME */ static void trim_trailing_blanks (const char *a_start, char **a_end) { while (*a_end > a_start && blanks[UCHAR (*(*a_end - 1))]) --(*a_end); } /* Fill BUF reading from FP, moving buf->left bytes from the end of buf->buf to the beginning first. If EOF is reached and the file wasn't terminated by a newline, supply one. Set up BUF's line table too. FILE is the name of the file corresponding to FP. Return nonzero if some input was read. */ static int fillbuf (struct buffer *buf, register FILE *fp, char const *file) { struct keyfield const *key = keylist; char eol = eolchar; size_t line_bytes = buf->line_bytes; size_t mergesize = merge_buffer_size - MIN_MERGE_BUFFER_SIZE; if (buf->eof) return 0; if (buf->used != buf->left) { memmove (buf->buf, buf->buf + buf->used - buf->left, buf->left); buf->used = buf->left; buf->nlines = 0; } for (;;) { char *ptr = buf->buf + buf->used; struct line *linelim = buffer_linelim (buf); struct line *line = linelim - buf->nlines; size_t avail = (char *) linelim - buf->nlines * line_bytes - ptr; char *line_start = buf->nlines ? line->text + line->length : buf->buf; while (line_bytes + 1 < avail) { /* Read as many bytes as possible, but do not read so many bytes that there might not be enough room for the corresponding line array. The worst case is when the rest of the input file consists entirely of newlines, except that the last byte is not a newline. */ size_t readsize = (avail - 1) / (line_bytes + 1); size_t bytes_read = fread (ptr, 1, readsize, fp); char *ptrlim = ptr + bytes_read; char *p; avail -= bytes_read; if (bytes_read != readsize) { if (ferror (fp)) die (_("read failed"), file); if (feof (fp)) { buf->eof = 1; if (buf->buf == ptrlim) return 0; if (ptrlim[-1] != eol) *ptrlim++ = eol; } } /* Find and record each line in the just-read input. */ while ((p = memchr (ptr, eol, ptrlim - ptr))) { ptr = p + 1; line--; line->text = line_start; line->length = ptr - line_start; mergesize = MAX (mergesize, line->length); avail -= line_bytes; if (key) { /* Precompute the position of the first key for efficiency. */ line->keylim = (key->eword == (size_t) -1 ? p : limfield (line, key)); if (key->sword != (size_t) -1) line->keybeg = begfield (line, key); else { if (key->skipsblanks) while (blanks[UCHAR (*line_start)]) line_start++; line->keybeg = line_start; } if (key->skipeblanks) trim_trailing_blanks (line->keybeg, &line->keylim); } line_start = ptr; } ptr = ptrlim; if (buf->eof) break; } buf->used = ptr - buf->buf; buf->nlines = buffer_linelim (buf) - line; if (buf->nlines != 0) { buf->left = ptr - line_start; merge_buffer_size = mergesize + MIN_MERGE_BUFFER_SIZE; return 1; } /* The current input line is too long to fit in the buffer. Double the buffer size and try again. */ if (2 * buf->alloc < buf->alloc) xalloc_die (); buf->alloc *= 2; buf->buf = xrealloc (buf->buf, buf->alloc); } } /* Compare strings A and B containing decimal fractions < 1. Each string should begin with a decimal point followed immediately by the digits of the fraction. Strings not of this form are considered to be zero. */ /* The goal here, is to take two numbers a and b... compare these in parallel. Instead of converting each, and then comparing the outcome. Most likely stopping the comparison before the conversion is complete. The algorithm used, in the old sort: Algorithm: fraccompare Action : compare two decimal fractions accepts : char *a, char *b returns : -1 if a<b, 0 if a=b, 1 if a>b. implement: if *a == decimal_point AND *b == decimal_point find first character different in a and b. if both are digits, return the difference *a - *b. if *a is a digit skip past zeros if digit return 1, else 0 if *b is a digit skip past zeros if digit return -1, else 0 if *a is a decimal_point skip past decimal_point and zeros if digit return 1, else 0 if *b is a decimal_point skip past decimal_point and zeros if digit return -1, else 0 return 0 */ static int fraccompare (register const char *a, register const char *b) { if (*a == decimal_point && *b == decimal_point) { while (*++a == *++b) if (! ISDIGIT (*a)) return 0; if (ISDIGIT (*a) && ISDIGIT (*b)) return *a - *b; if (ISDIGIT (*a)) goto a_trailing_nonzero; if (ISDIGIT (*b)) goto b_trailing_nonzero; return 0; } else if (*a++ == decimal_point) { a_trailing_nonzero: while (*a == NUMERIC_ZERO) a++; return ISDIGIT (*a); } else if (*b++ == decimal_point) { b_trailing_nonzero: while (*b == NUMERIC_ZERO) b++; return - ISDIGIT (*b); } return 0; } /* Compare strings A and B as numbers without explicitly converting them to machine numbers. Comparatively slow for short strings, but asymptotically hideously fast. */ static int numcompare (register const char *a, register const char *b) { register int tmpa, tmpb, tmp; register size_t loga, logb; tmpa = *a; tmpb = *b; while (blanks[UCHAR (tmpa)]) tmpa = *++a; while (blanks[UCHAR (tmpb)]) tmpb = *++b; if (tmpa == NEGATION_SIGN) { do tmpa = *++a; while (tmpa == NUMERIC_ZERO || IS_THOUSANDS_SEP (tmpa)); if (tmpb != NEGATION_SIGN) { if (tmpa == decimal_point) do tmpa = *++a; while (tmpa == NUMERIC_ZERO); if (ISDIGIT (tmpa)) return -1; while (tmpb == NUMERIC_ZERO || IS_THOUSANDS_SEP (tmpb)) tmpb = *++b; if (tmpb == decimal_point) do tmpb = *++b; while (tmpb == NUMERIC_ZERO); if (ISDIGIT (tmpb)) return -1; return 0; } do tmpb = *++b; while (tmpb == NUMERIC_ZERO || IS_THOUSANDS_SEP (tmpb)); while (tmpa == tmpb && ISDIGIT (tmpa)) { do tmpa = *++a; while (IS_THOUSANDS_SEP (tmpa)); do tmpb = *++b; while (IS_THOUSANDS_SEP (tmpb)); } if ((tmpa == decimal_point && !ISDIGIT (tmpb)) || (tmpb == decimal_point && !ISDIGIT (tmpa))) return -fraccompare (a, b); tmp = tmpb - tmpa; for (loga = 0; ISDIGIT (tmpa); ++loga) do tmpa = *++a; while (IS_THOUSANDS_SEP (tmpa)); for (logb = 0; ISDIGIT (tmpb); ++logb) do tmpb = *++b; while (IS_THOUSANDS_SEP (tmpb)); if (loga != logb) return loga < logb ? 1 : -1; if (!loga) return 0; return tmp; } else if (tmpb == NEGATION_SIGN) { do tmpb = *++b; while (tmpb == NUMERIC_ZERO || IS_THOUSANDS_SEP (tmpb)); if (tmpb == decimal_point) do tmpb = *++b; while (tmpb == NUMERIC_ZERO); if (ISDIGIT (tmpb)) return 1; while (tmpa == NUMERIC_ZERO || IS_THOUSANDS_SEP (tmpa)) tmpa = *++a; if (tmpa == decimal_point) do tmpa = *++a; while (tmpa == NUMERIC_ZERO); if (ISDIGIT (tmpa)) return 1; return 0; } else { while (tmpa == NUMERIC_ZERO || IS_THOUSANDS_SEP (tmpa)) tmpa = *++a; while (tmpb == NUMERIC_ZERO || IS_THOUSANDS_SEP (tmpb)) tmpb = *++b; while (tmpa == tmpb && ISDIGIT (tmpa)) { do tmpa = *++a; while (IS_THOUSANDS_SEP (tmpa)); do tmpb = *++b; while (IS_THOUSANDS_SEP (tmpb)); } if ((tmpa == decimal_point && !ISDIGIT (tmpb)) || (tmpb == decimal_point && !ISDIGIT (tmpa))) return fraccompare (a, b); tmp = tmpa - tmpb; for (loga = 0; ISDIGIT (tmpa); ++loga) do tmpa = *++a; while (IS_THOUSANDS_SEP (tmpa)); for (logb = 0; ISDIGIT (tmpb); ++logb) do tmpb = *++b; while (IS_THOUSANDS_SEP (tmpb)); if (loga != logb) return loga < logb ? -1 : 1; if (!loga) return 0; return tmp; } } static int general_numcompare (const char *sa, const char *sb) { /* FIXME: add option to warn about failed conversions. */ /* FIXME: maybe add option to try expensive FP conversion only if A and B can't be compared more cheaply/accurately. */ char *ea; char *eb; double a = strtod (sa, &ea); double b = strtod (sb, &eb); /* Put conversion errors at the start of the collating sequence. */ if (sa == ea) return sb == eb ? 0 : -1; if (sb == eb) return 1; /* Sort numbers in the usual way, where -0 == +0. Put NaNs after conversion errors but before numbers; sort them by internal bit-pattern, for lack of a more portable alternative. */ return (a < b ? -1 : a > b ? 1 : a == b ? 0 : b == b ? -1 : a == a ? 1 : memcmp ((char *) &a, (char *) &b, sizeof a)); } /* Return an integer in 1..12 of the month name S with length LEN. Return 0 if the name in S is not recognized. */ static int getmonth (const char *s, size_t len) { char *month; register size_t i; register int lo = 0, hi = MONTHS_PER_YEAR, result; while (len > 0 && blanks[UCHAR (*s)]) { ++s; --len; } if (len == 0) return 0; month = (char *) alloca (len + 1); for (i = 0; i < len; ++i) month[i] = fold_toupper[UCHAR (s[i])]; while (blanks[UCHAR (month[i - 1])]) --i; month[i] = '\0'; do { int ix = (lo + hi) / 2; if (strncmp (month, monthtab[ix].name, strlen (monthtab[ix].name)) < 0) hi = ix; else lo = ix; } while (hi - lo > 1); result = (!strncmp (month, monthtab[lo].name, strlen (monthtab[lo].name)) ? monthtab[lo].val : 0); return result; } /* Compare two lines A and B trying every key in sequence until there are no more keys or a difference is found. */ static int keycompare (const struct line *a, const struct line *b) { struct keyfield *key = keylist; /* For the first iteration only, the key positions have been precomputed for us. */ register char *texta = a->keybeg; register char *textb = b->keybeg; register char *lima = a->keylim; register char *limb = b->keylim; int diff; for (;;) { register unsigned char *translate = (unsigned char *) key->translate; register int *ignore = key->ignore; /* Find the lengths. */ size_t lena = lima <= texta ? 0 : lima - texta; size_t lenb = limb <= textb ? 0 : limb - textb; if (key->skipeblanks) { char *a_end = texta + lena; char *b_end = textb + lenb; trim_trailing_blanks (texta, &a_end); trim_trailing_blanks (textb, &b_end); lena = a_end - texta; lenb = b_end - textb; } /* Actually compare the fields. */ if (key->numeric | key->general_numeric) { char savea = *lima, saveb = *limb; *lima = *limb = '\0'; diff = ((key->numeric ? numcompare : general_numcompare) (texta, textb)); *lima = savea, *limb = saveb; } else if (key->month) diff = getmonth (texta, lena) - getmonth (textb, lenb); /* Sorting like this may become slow, so in a simple locale the user can select a faster sort that is similar to ascii sort */ else if (HAVE_SETLOCALE && hard_LC_COLLATE) { if (ignore || translate) { char *copy_a = (char *) alloca (lena + 1 + lenb + 1); char *copy_b = copy_a + lena + 1; size_t new_len_a, new_len_b, i; /* Ignore and/or translate chars before comparing. */ for (new_len_a = new_len_b = i = 0; i < max (lena, lenb); i++) { if (i < lena) { copy_a[new_len_a] = (translate ? translate[UCHAR (texta[i])] : texta[i]); if (!ignore || !ignore[UCHAR (texta[i])]) ++new_len_a; } if (i < lenb) { copy_b[new_len_b] = (translate ? translate[UCHAR (textb[i])] : textb [i]); if (!ignore || !ignore[UCHAR (textb[i])]) ++new_len_b; } } diff = xmemcoll (copy_a, new_len_a, copy_b, new_len_b); } else if (lena == 0) diff = - NONZERO (lenb); else if (lenb == 0) goto greater; else diff = xmemcoll (texta, lena, textb, lenb); } else if (ignore) { #define CMP_WITH_IGNORE(A, B) \ do \ { \ for (;;) \ { \ while (texta < lima && ignore[UCHAR (*texta)]) \ ++texta; \ while (textb < limb && ignore[UCHAR (*textb)]) \ ++textb; \ if (! (texta < lima && textb < limb)) \ break; \ diff = UCHAR (A) - UCHAR (B); \ if (diff) \ goto not_equal; \ ++texta; \ ++textb; \ } \ \ diff = (texta < lima) - (textb < limb); \ } \ while (0) if (translate) CMP_WITH_IGNORE (translate[UCHAR (*texta)], translate[UCHAR (*textb)]); else CMP_WITH_IGNORE (UCHAR (*texta), UCHAR (*textb)); } else if (lena == 0) diff = - NONZERO (lenb); else if (lenb == 0) goto greater; else { if (translate) { while (texta < lima && textb < limb) { diff = (UCHAR (translate[UCHAR (*texta++)]) - UCHAR (translate[UCHAR (*textb++)])); if (diff) goto not_equal; } } else { diff = memcmp (texta, textb, min (lena, lenb)); if (diff) goto not_equal; } diff = lena < lenb ? -1 : lena != lenb; } if (diff) goto not_equal; key = key->next; if (! key) break; /* Find the beginning and limit of the next field. */ if (key->eword != (size_t) -1) lima = limfield (a, key), limb = limfield (b, key); else lima = a->text + a->length - 1, limb = b->text + b->length - 1; if (key->sword != (size_t) -1) texta = begfield (a, key), textb = begfield (b, key); else { texta = a->text, textb = b->text; if (key->skipsblanks) { while (texta < lima && blanks[UCHAR (*texta)]) ++texta; while (textb < limb && blanks[UCHAR (*textb)]) ++textb; } } } return 0; greater: diff = 1; not_equal: return key->reverse ? -diff : diff; } /* Compare two lines A and B, returning negative, zero, or positive depending on whether A compares less than, equal to, or greater than B. */ static int compare (register const struct line *a, register const struct line *b) { int diff; size_t alen, blen; /* First try to compare on the specified keys (if any). The only two cases with no key at all are unadorned sort, and unadorned sort -r. */ if (keylist) { diff = keycompare (a, b); alloca (0); if (diff != 0 || unique || stable) return diff; } /* If the keys all compare equal (or no keys were specified) fall through to the default comparison. */ alen = a->length - 1, blen = b->length - 1; if (alen == 0) diff = - NONZERO (blen); else if (blen == 0) diff = NONZERO (alen); else if (HAVE_SETLOCALE && hard_LC_COLLATE) diff = xmemcoll (a->text, alen, b->text, blen); else if (! (diff = memcmp (a->text, b->text, min (alen, blen)))) diff = alen < blen ? -1 : alen != blen; return reverse ? -diff : diff; } /* Check that the lines read from the given FP come in order. Print a diagnostic (FILE_NAME, line number, contents of line) to stderr and return one if they are not in order. Otherwise, print no diagnostic and return zero. */ static int checkfp (FILE *fp, char *file_name) { struct buffer buf; /* Input buffer. */ struct line temp; /* Copy of previous line. */ size_t alloc = 0; uintmax_t line_number = 0; struct keyfield *key = keylist; int nonunique = 1 - unique; int disordered = 0; initbuf (&buf, sizeof (struct line), MAX (merge_buffer_size, sort_size)); temp.text = NULL; while (fillbuf (&buf, fp, file_name)) { struct line const *line = buffer_linelim (&buf); struct line const *linebase = line - buf.nlines; /* Make sure the line saved from the old buffer contents is less than or equal to the first line of the new buffer. */ if (alloc && nonunique <= compare (&temp, line - 1)) { found_disorder: { struct line const *disorder_line = line - 1; uintmax_t disorder_line_number = buffer_linelim (&buf) - disorder_line + line_number; char hr_buf[INT_BUFSIZE_BOUND (uintmax_t)]; fprintf (stderr, _("%s: %s:%s: disorder: "), program_name, file_name, umaxtostr (disorder_line_number, hr_buf)); write_bytes (disorder_line->text, disorder_line->length, stderr, _("standard error")); disordered = 1; break; } } /* Compare each line in the buffer with its successor. */ while (linebase < --line) if (nonunique <= compare (line, line - 1)) goto found_disorder; line_number += buf.nlines; /* Save the last line of the buffer. */ if (alloc < line->length) { do { alloc *= 2; if (! alloc) { alloc = line->length; break; } } while (alloc < line->length); temp.text = xrealloc (temp.text, alloc); } memcpy (temp.text, line->text, line->length); temp.length = line->length; if (key) { temp.keybeg = temp.text + (line->keybeg - line->text); temp.keylim = temp.text + (line->keylim - line->text); } } xfclose (fp, file_name); free (buf.buf); if (temp.text) free (temp.text); return disordered; } /* Merge lines from FILES onto OFP. NFILES cannot be greater than NMERGE. Close input and output files before returning. OUTPUT_FILE gives the name of the output file; if OFP is NULL, the output file has not been opened yet. */ static void mergefps (char **files, register int nfiles, FILE *ofp, const char *output_file) { FILE *fps[NMERGE]; /* Input streams for each file. */ struct buffer buffer[NMERGE]; /* Input buffers for each file. */ struct line saved; /* Saved line storage for unique check. */ struct line const *savedline = NULL; /* &saved if there is a saved line. */ size_t savealloc = 0; /* Size allocated for the saved line. */ struct line const *cur[NMERGE]; /* Current line in each line table. */ struct line const *base[NMERGE]; /* Base of each line table. */ int ord[NMERGE]; /* Table representing a permutation of fps, such that cur[ord[0]] is the smallest line and will be next output. */ register int i, j, t; struct keyfield *key = keylist; saved.text = NULL; /* Read initial lines from each input file. */ for (i = 0; i < nfiles; ) { fps[i] = xfopen (files[i], "r"); initbuf (&buffer[i], sizeof (struct line), MAX (merge_buffer_size, sort_size / nfiles)); if (fillbuf (&buffer[i], fps[i], files[i])) { struct line const *linelim = buffer_linelim (&buffer[i]); cur[i] = linelim - 1; base[i] = linelim - buffer[i].nlines; i++; } else { /* fps[i] is empty; eliminate it from future consideration. */ xfclose (fps[i], files[i]); zaptemp (files[i]); free (buffer[i].buf); --nfiles; for (j = i; j < nfiles; ++j) files[j] = files[j + 1]; } } if (! ofp) ofp = xfopen (output_file, "w"); /* Set up the ord table according to comparisons among input lines. Since this only reorders two items if one is strictly greater than the other, it is stable. */ for (i = 0; i < nfiles; ++i) ord[i] = i; for (i = 1; i < nfiles; ++i) if (0 < compare (cur[ord[i - 1]], cur[ord[i]])) t = ord[i - 1], ord[i - 1] = ord[i], ord[i] = t, i = 0; /* Repeatedly output the smallest line until no input remains. */ while (nfiles) { struct line const *smallest = cur[ord[0]]; /* If uniquified output is turned on, output only the first of an identical series of lines. */ if (unique) { if (savedline && compare (savedline, smallest)) { savedline = 0; write_bytes (saved.text, saved.length, ofp, output_file); } if (!savedline) { savedline = &saved; if (savealloc < smallest->length) { do if (! savealloc) { savealloc = smallest->length; break; } while ((savealloc *= 2) < smallest->length); saved.text = xrealloc (saved.text, savealloc); } saved.length = smallest->length; memcpy (saved.text, smallest->text, saved.length); if (key) { saved.keybeg = saved.text + (smallest->keybeg - smallest->text); saved.keylim = saved.text + (smallest->keylim - smallest->text); } } } else write_bytes (smallest->text, smallest->length, ofp, output_file); /* Check if we need to read more lines into core. */ if (base[ord[0]] < smallest) cur[ord[0]] = smallest - 1; else { if (fillbuf (&buffer[ord[0]], fps[ord[0]], files[ord[0]])) { struct line const *linelim = buffer_linelim (&buffer[ord[0]]); cur[ord[0]] = linelim - 1; base[ord[0]] = linelim - buffer[ord[0]].nlines; } else { /* We reached EOF on fps[ord[0]]. */ for (i = 1; i < nfiles; ++i) if (ord[i] > ord[0]) --ord[i]; --nfiles; xfclose (fps[ord[0]], files[ord[0]]); zaptemp (files[ord[0]]); free (buffer[ord[0]].buf); for (i = ord[0]; i < nfiles; ++i) { fps[i] = fps[i + 1]; files[i] = files[i + 1]; buffer[i] = buffer[i + 1]; cur[i] = cur[i + 1]; base[i] = base[i + 1]; } for (i = 0; i < nfiles; ++i) ord[i] = ord[i + 1]; continue; } } /* The new line just read in may be larger than other lines already in core; push it back in the queue until we encounter a line larger than it. */ for (i = 1; i < nfiles; ++i) { t = compare (cur[ord[0]], cur[ord[i]]); if (!t) t = ord[0] - ord[i]; if (t < 0) break; } t = ord[0]; for (j = 1; j < i; ++j) ord[j - 1] = ord[j]; ord[i - 1] = t; } if (unique && savedline) { write_bytes (saved.text, saved.length, ofp, output_file); free (saved.text); } xfclose (ofp, output_file); } /* Sort the array LINES with NLINES members, using TEMP for temporary space. The input and output arrays are in reverse order, and LINES and TEMP point just past the end of their respective arrays. */ static void sortlines (struct line *lines, size_t nlines, struct line *temp) { register struct line *lo, *hi, *t; register size_t nlo, nhi; if (nlines == 2) { if (0 < compare (&lines[-1], &lines[-2])) { struct line tmp = lines[-1]; lines[-1] = lines[-2]; lines[-2] = tmp; } return; } nlo = nlines / 2; lo = lines; nhi = nlines - nlo; hi = lines - nlo; if (nlo > 1) sortlines (lo, nlo, temp); if (nhi > 1) sortlines (hi, nhi, temp); t = temp; while (nlo && nhi) if (compare (lo - 1, hi - 1) <= 0) *--t = *--lo, --nlo; else *--t = *--hi, --nhi; while (nlo--) *--t = *--lo; for (lo = lines, nlo = nlines - nhi, t = temp; nlo; --nlo) *--lo = *--t; } /* Return the index of the first of NFILES FILES that is the same file as OUTFILE. If none can be the same, return NFILES. Consider an input pipe to be the same as OUTFILE, since the pipe might be the output of a command like "cat OUTFILE". */ static int first_same_file (char **files, int nfiles, char const *outfile) { int i; int got_outstat = 0; struct stat instat, outstat; for (i = 0; i < nfiles; i++) { int standard_input = STREQ (files[i], "-"); if (STREQ (outfile, files[i]) && ! standard_input) return i; if (! got_outstat) { got_outstat = 1; if ((STREQ (outfile, "-") ? fstat (STDOUT_FILENO, &outstat) : stat (outfile, &outstat)) != 0) return nfiles; } if (((standard_input ? fstat (STDIN_FILENO, &instat) : stat (files[i], &instat)) == 0) && (S_ISFIFO (instat.st_mode) || SAME_INODE (instat, outstat))) return i; } return nfiles; } /* Check that each of the NFILES FILES is ordered. Return a count of disordered files. */ static int check (char **files, int nfiles) { int i, disorders = 0; FILE *fp; for (i = 0; i < nfiles; ++i) { fp = xfopen (files[i], "r"); disorders += checkfp (fp, files[i]); } return disorders; } /* Merge NFILES FILES onto OUTPUT_FILE. However, merge at most MAX_MERGE input files directly onto OUTPUT_FILE. MAX_MERGE cannot exceed NMERGE. */ static void merge (char **files, int nfiles, int max_merge, char const *output_file) { while (max_merge < nfiles) { FILE *tfp; int i, t = 0; char *temp; for (i = 0; i < nfiles / NMERGE; ++i) { temp = create_temp_file (&tfp); mergefps (&files[i * NMERGE], NMERGE, tfp, temp); files[t++] = temp; } temp = create_temp_file (&tfp); mergefps (&files[i * NMERGE], nfiles % NMERGE, tfp, temp); files[t++] = temp; nfiles = t; if (nfiles == 1) break; } mergefps (files, nfiles, NULL, output_file); } /* Sort NFILES FILES onto OUTPUT_FILE. */ static void sort (char **files, int nfiles, char const *output_file) { struct buffer buf; int n_temp_files = 0; int output_file_created = 0; static size_t size; if (! size && ! (size = sort_size)) size = default_sort_size (); buf.alloc = 0; while (nfiles) { char const *temp_output; char const *file = *files; FILE *fp = xfopen (file, "r"); FILE *tfp; if (! buf.alloc) initbuf (&buf, 2 * sizeof (struct line), sort_buffer_size (&fp, 1, files, nfiles, 2 * sizeof (struct line), size)); buf.eof = 0; files++; nfiles--; while (fillbuf (&buf, fp, file)) { struct line *line; struct line *linebase; if (buf.eof && nfiles && (2 * sizeof (struct line) + 1 < (buf.alloc - buf.used - 2 * sizeof (struct line) * buf.nlines))) { /* End of file, but there is more input and buffer room. Concatenate the next input file; this is faster in the usual case. */ buf.left = buf.used; break; } line = buffer_linelim (&buf); linebase = line - buf.nlines; sortlines (line, buf.nlines, linebase); if (buf.eof && !nfiles && !n_temp_files && !buf.left) { xfclose (fp, file); tfp = xfopen (output_file, "w"); temp_output = output_file; output_file_created = 1; } else { ++n_temp_files; temp_output = create_temp_file (&tfp); } do { line--; write_bytes (line->text, line->length, tfp, temp_output); if (unique) while (linebase < line && compare (line, line - 1) == 0) line--; } while (linebase < line); xfclose (tfp, temp_output); if (output_file_created) goto finish; } xfclose (fp, file); } finish: free (buf.buf); if (! output_file_created) { int i = n_temp_files; struct tempnode *node; char **tempfiles = (char **) xmalloc (n_temp_files * sizeof (char *)); for (node = temphead; i > 0; node = node->next) tempfiles[--i] = node->name; merge (tempfiles, n_temp_files, NMERGE, output_file); free ((char *) tempfiles); } } /* Insert key KEY at the end of the key list. */ static void insertkey (struct keyfield *key) { struct keyfield **p; for (p = &keylist; *p; p = &(*p)->next) continue; *p = key; key->next = NULL; } /* Report a bad field specification SPEC, with extra info MSGID. */ static void badfieldspec (char const *, char const *) ATTRIBUTE_NORETURN; static void badfieldspec (char const *spec, char const *msgid) { error (SORT_FAILURE, 0, _("%s: invalid field specification `%s'"), _(msgid), spec); abort (); } /* Parse the leading integer in STRING and store the resulting value (which must fit into size_t) into *VAL. Return the address of the suffix after the integer. If MSGID is NULL, return NULL after failure; otherwise, report MSGID and exit on failure. */ static char const * parse_field_count (char const *string, size_t *val, char const *msgid) { char *suffix; uintmax_t n; switch (xstrtoumax (string, &suffix, 10, &n, "")) { case LONGINT_OK: case LONGINT_INVALID_SUFFIX_CHAR: *val = n; if (*val == n) break; /* Fall through. */ case LONGINT_OVERFLOW: if (msgid) error (SORT_FAILURE, 0, _("%s: count `%.*s' too large"), _(msgid), (int) (suffix - string), string); return NULL; case LONGINT_INVALID: if (msgid) error (SORT_FAILURE, 0, _("%s: invalid count at start of `%s'"), _(msgid), string); return NULL; } return suffix; } /* Handle interrupts and hangups. */ static void sighandler (int sig) { #ifndef SA_NOCLDSTOP signal (sig, SIG_IGN); #endif cleanup (); #ifdef SA_NOCLDSTOP { struct sigaction sigact; sigact.sa_handler = SIG_DFL; sigemptyset (&sigact.sa_mask); sigact.sa_flags = 0; sigaction (sig, &sigact, NULL); } #else signal (sig, SIG_DFL); #endif raise (sig); } /* Set the ordering options for KEY specified in S. Return the address of the first character in S that is not a valid ordering option. BLANKTYPE is the kind of blanks that 'b' should skip. */ static char * set_ordering (register const char *s, struct keyfield *key, enum blanktype blanktype) { while (*s) { switch (*s) { case 'b': if (blanktype == bl_start || blanktype == bl_both) key->skipsblanks = 1; if (blanktype == bl_end || blanktype == bl_both) key->skipeblanks = 1; break; case 'd': key->ignore = nondictionary; break; case 'f': key->translate = fold_toupper; break; case 'g': key->general_numeric = 1; break; case 'i': key->ignore = nonprinting; break; case 'M': key->month = 1; break; case 'n': key->numeric = 1; break; case 'r': key->reverse = 1; break; default: return (char *) s; } ++s; } return (char *) s; } static struct keyfield * new_key (void) { struct keyfield *key = (struct keyfield *) xcalloc (1, sizeof *key); key->eword = -1; return key; } int main (int argc, char **argv) { struct keyfield *key; struct keyfield gkey; char const *s; int c = 0; int checkonly = 0, mergeonly = 0, nfiles = 0; int posix_pedantic = (getenv ("POSIXLY_CORRECT") != NULL); bool obsolete_usage = (posix2_version () < 200112); char const *short_options = (obsolete_usage ? COMMON_SHORT_OPTIONS "y::" : COMMON_SHORT_OPTIONS "y:"); char *minus = "-", **files; char const *outfile = minus; static int const sigs[] = { SIGHUP, SIGINT, SIGPIPE, SIGTERM }; unsigned nsigs = sizeof sigs / sizeof *sigs; #ifdef SA_NOCLDSTOP struct sigaction oldact, newact; #endif program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (cleanup); hard_LC_COLLATE = hard_locale (LC_COLLATE); #if HAVE_NL_LANGINFO hard_LC_TIME = hard_locale (LC_TIME); #endif #if HAVE_SETLOCALE /* Let's get locale's representation of the decimal point */ { struct lconv *lconvp = localeconv (); /* If the locale doesn't define a decimal point, or if the decimal point is multibyte, use the C decimal point. We don't support multibyte decimal points yet. */ decimal_point = *lconvp->decimal_point; if (! decimal_point || lconvp->decimal_point[1]) decimal_point = C_DECIMAL_POINT; /* We don't support multibyte thousands separators yet. */ th_sep = *lconvp->thousands_sep; if (! th_sep || lconvp->thousands_sep[1]) th_sep = CHAR_MAX + 1; } #endif have_read_stdin = 0; inittables (); /* Change the way library functions fail. */ xalloc_exit_failure = SORT_FAILURE; xmemcoll_exit_failure = SORT_FAILURE; #ifdef SA_NOCLDSTOP { unsigned i; sigemptyset (&caught_signals); for (i = 0; i < nsigs; i++) sigaddset (&caught_signals, sigs[i]); newact.sa_handler = sighandler; newact.sa_mask = caught_signals; newact.sa_flags = 0; } #endif { unsigned i; for (i = 0; i < nsigs; i++) { int sig = sigs[i]; #ifdef SA_NOCLDSTOP sigaction (sig, NULL, &oldact); if (oldact.sa_handler != SIG_IGN) sigaction (sig, &newact, NULL); #else if (signal (sig, SIG_IGN) != SIG_IGN) signal (sig, sighandler); #endif } } gkey.sword = gkey.eword = -1; gkey.ignore = NULL; gkey.translate = NULL; gkey.numeric = gkey.general_numeric = gkey.month = gkey.reverse = 0; gkey.skipsblanks = gkey.skipeblanks = 0; files = (char **) xmalloc (sizeof (char *) * argc); for (;;) { /* Parse an operand as a file after "--" was seen; or if pedantic and a file was seen, unless the POSIX version predates 1003.1-2001 and -c was not seen and the operand is "-o FILE" or "-oFILE". */ if (c == -1 || (posix_pedantic && nfiles != 0 && ! (obsolete_usage && ! checkonly && optind != argc && argv[optind][0] == '-' && argv[optind][1] == 'o' && (argv[optind][2] || optind + 1 != argc))) || ((c = getopt_long (argc, argv, short_options, long_options, NULL)) == -1)) { if (optind == argc) break; files[nfiles++] = argv[optind++]; } else switch (c) { case 1: key = NULL; if (obsolete_usage && optarg[0] == '+') { /* Treat +POS1 [-POS2] as a key if possible; but silently treat an operand as a file if it is not a valid +POS1. */ key = new_key (); s = parse_field_count (optarg + 1, &key->sword, NULL); if (s && *s == '.') s = parse_field_count (s + 1, &key->schar, NULL); if (! (key->sword | key->schar)) key->sword = -1; if (! s || *set_ordering (s, key, bl_start)) { free (key); key = NULL; } else { if (optind != argc && argv[optind][0] == '-' && ISDIGIT (argv[optind][1])) { char const *optarg1 = argv[optind++]; s = parse_field_count (optarg1 + 1, &key->eword, N_("invalid number after `-'")); if (*s == '.') s = parse_field_count (s + 1, &key->echar, N_("invalid number after `.'")); if (*set_ordering (s, key, bl_end)) badfieldspec (optarg1, N_("stray character in field spec")); } insertkey (key); } } if (! key) files[nfiles++] = optarg; break; case 'b': case 'd': case 'f': case 'g': case 'i': case 'M': case 'n': case 'r': { char str[2]; str[0] = c; str[1] = '\0'; set_ordering (str, &gkey, bl_both); } break; case 'c': checkonly = 1; break; case 'k': key = new_key (); /* Get POS1. */ s = parse_field_count (optarg, &key->sword, N_("invalid number at field start")); if (! key->sword--) { /* Provoke with `sort -k0' */ badfieldspec (optarg, N_("field number is zero")); } if (*s == '.') { s = parse_field_count (s + 1, &key->schar, N_("invalid number after `.'")); if (! key->schar--) { /* Provoke with `sort -k1.0' */ badfieldspec (optarg, N_("character offset is zero")); } } if (! (key->sword | key->schar)) key->sword = -1; s = set_ordering (s, key, bl_start); if (*s != ',') { key->eword = -1; key->echar = 0; } else { /* Get POS2. */ s = parse_field_count (s + 1, &key->eword, N_("invalid number after `,'")); if (! key->eword--) { /* Provoke with `sort -k1,0' */ badfieldspec (optarg, N_("field number is zero")); } if (*s == '.') s = parse_field_count (s + 1, &key->echar, N_("invalid number after `.'")); else { /* `-k 2,3' is equivalent to `+1 -3'. */ key->eword++; } s = set_ordering (s, key, bl_end); } if (*s) badfieldspec (optarg, N_("stray character in field spec")); insertkey (key); break; case 'm': mergeonly = 1; break; case 'o': outfile = optarg; break; case 's': stable = 1; break; case 'S': specify_sort_size (optarg); break; case 't': tab = optarg[0]; if (tab && optarg[1]) { /* Provoke with `sort -txx'. Complain about "multi-character tab" instead of "multibyte tab", so that the diagnostic's wording does not need to be changed once multibyte characters are supported. */ error (SORT_FAILURE, 0, _("multi-character tab `%s'"), optarg); } break; case 'T': add_temp_dir (optarg); break; case 'u': unique = 1; break; case 'y': /* Accept and ignore e.g. -y0 for compatibility with Solaris 2.x through Solaris 7. -y is marked as obsolete starting with Solaris 8. */ break; case 'z': eolchar = 0; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (SORT_FAILURE); } } /* Inheritance of global options to individual keys. */ for (key = keylist; key; key = key->next) if (!key->ignore && !key->translate && !key->skipsblanks && !key->reverse && !key->skipeblanks && !key->month && !key->numeric && !key->general_numeric) { key->ignore = gkey.ignore; key->translate = gkey.translate; key->skipsblanks = gkey.skipsblanks; key->skipeblanks = gkey.skipeblanks; key->month = gkey.month; key->numeric = gkey.numeric; key->general_numeric = gkey.general_numeric; key->reverse = gkey.reverse; } if (!keylist && (gkey.ignore || gkey.translate || gkey.skipsblanks || gkey.skipeblanks || gkey.month || gkey.numeric || gkey.general_numeric)) insertkey (&gkey); reverse = gkey.reverse; if (temp_dir_count == 0) { char const *tmp_dir = getenv ("TMPDIR"); add_temp_dir (tmp_dir ? tmp_dir : DEFAULT_TMPDIR); } if (nfiles == 0) { nfiles = 1; files = − } if (checkonly) { if (nfiles > 1) error (SORT_FAILURE, 0, _("extra operand `%s' not allowed with -c"), files[1]); /* POSIX requires that sort return 1 IFF invoked with -c and the input is not properly sorted. */ exit (check (files, nfiles) == 0 ? EXIT_SUCCESS : SORT_OUT_OF_ORDER); } if (mergeonly) { int max_merge = first_same_file (files, MIN (nfiles, NMERGE), outfile); merge (files, nfiles, max_merge, outfile); } else sort (files, nfiles, outfile); if (have_read_stdin && fclose (stdin) == EOF) die (_("close failed"), "-"); exit (EXIT_SUCCESS); }
SPDX-FileCopyrightText: 2005 Paul Eggert <eggert@cs.ucla.edu> SPDX-FileCopyrightText: 2023 Emily Trau <emily@downunderctf.com> SPDX-License-Identifier: GPL-2.0-or-later uniq: don't assume fopen cannot return stdin or stdout. Backport of https://git.savannah.gnu.org/cgit/coreutils.git/commit/?id=786ebb2ceca72f69aa2de701671fb41f53cb1489 --- src/uniq.c +++ src/uniq.c @@ -30,6 +30,7 @@ #include "error.h" #include "hard-locale.h" #include "posixver.h" +#include "stdio-safer.h" #include "xmemcoll.h" #include "xstrtol.h" #include "memcasecmp.h" @@ -267,20 +268,26 @@ check_file (const char *infile, const char *outfile) FILE *ostream; struct linebuffer lb1, lb2; struct linebuffer *thisline, *prevline; + bool is_stdin = STREQ (infile, "-"); + bool is_stdout = STREQ (outfile, "-"); - if (STREQ (infile, "-")) + if (is_stdin) istream = stdin; else - istream = fopen (infile, "r"); - if (istream == NULL) - error (EXIT_FAILURE, errno, "%s", infile); + { + istream = fopen_safer (infile, "r"); + if (! istream) + error (EXIT_FAILURE, errno, "%s", infile); + } - if (STREQ (outfile, "-")) + if (is_stdout) ostream = stdout; else - ostream = fopen (outfile, "w"); - if (ostream == NULL) - error (EXIT_FAILURE, errno, "%s", outfile); + { + ostream = fopen_safer (outfile, "w"); + if (! ostream) + error (EXIT_FAILURE, errno, "%s", outfile); + } thisline = &lb1; prevline = &lb2; @@ -377,12 +384,12 @@ check_file (const char *infile, const char *outfile) } closefiles: - if (ferror (istream) || fclose (istream) == EOF) + if (!is_stdin && (ferror (istream) || fclose (istream) != 0)) error (EXIT_FAILURE, errno, _("error reading %s"), infile); /* Close ostream only if it's not stdout -- the latter is closed via the atexit-invoked close_stdout. */ - if (ostream != stdout && (ferror (ostream) || fclose (ostream) == EOF)) + if (!is_stdout && (ferror (ostream) || fclose (ostream) != 0)) error (EXIT_FAILURE, errno, _("error writing %s"), outfile); free (lb1.buffer);
/* uniq -- remove duplicate lines from a sorted file Copyright (C) 86, 91, 1995-2002, Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Richard Stallman and David MacKenzie. */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include "system.h" #include "closeout.h" #include "argmatch.h" #include "linebuffer.h" #include "error.h" #include "hard-locale.h" #include "posixver.h" #include "xmemcoll.h" #include "xstrtol.h" #include "memcasecmp.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "uniq" #define AUTHORS N_ ("Richard Stallman and David MacKenzie") #define SWAP_LINES(A, B) \ do \ { \ struct linebuffer *_tmp; \ _tmp = (A); \ (A) = (B); \ (B) = _tmp; \ } \ while (0) /* The name this program was run with. */ char *program_name; /* Nonzero if the LC_COLLATE locale is hard. */ static int hard_LC_COLLATE; /* Number of fields to skip on each line when doing comparisons. */ static size_t skip_fields; /* Number of chars to skip after skipping any fields. */ static size_t skip_chars; /* Number of chars to compare. */ static size_t check_chars; enum countmode { count_occurrences, /* -c Print count before output lines. */ count_none /* Default. Do not print counts. */ }; /* Whether and how to precede the output lines with a count of the number of times they occurred in the input. */ static enum countmode countmode; enum output_mode { output_repeated, /* -d Only lines that are repeated. */ output_all_repeated, /* -D All lines that are repeated. */ output_unique, /* -u Only lines that are not repeated. */ output_all /* Default. Print first copy of each line. */ }; /* Which lines to output. */ static enum output_mode mode; /* If nonzero, ignore case when comparing. */ static int ignore_case; enum delimit_method { /* No delimiters output. --all-repeated[=none] */ DM_NONE, /* Delimiter precedes all groups. --all-repeated=prepend */ DM_PREPEND, /* Delimit all groups. --all-repeated=separate */ DM_SEPARATE }; static char const *const delimit_method_string[] = { "none", "prepend", "separate", 0 }; static enum delimit_method const delimit_method_map[] = { DM_NONE, DM_PREPEND, DM_SEPARATE }; /* Select whether/how to delimit groups of duplicate lines. */ static enum delimit_method delimit_groups; static struct option const longopts[] = { {"count", no_argument, NULL, 'c'}, {"repeated", no_argument, NULL, 'd'}, {"all-repeated", optional_argument, NULL, 'D'}, {"ignore-case", no_argument, NULL, 'i'}, {"unique", no_argument, NULL, 'u'}, {"skip-fields", required_argument, NULL, 'f'}, {"skip-chars", required_argument, NULL, 's'}, {"check-chars", required_argument, NULL, 'w'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... [INPUT [OUTPUT]]\n\ "), program_name); fputs (_("\ Discard all but one of successive identical lines from INPUT (or\n\ standard input), writing to OUTPUT (or standard output).\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ -c, --count prefix lines by the number of occurrences\n\ -d, --repeated only print duplicate lines\n\ "), stdout); fputs (_("\ -D, --all-repeated[=delimit-method] print all duplicate lines\n\ delimit-method={none(default),prepend,separate}\n\ Delimiting is done with blank lines.\n\ -f, --skip-fields=N avoid comparing the first N fields\n\ -i, --ignore-case ignore differences in case when comparing\n\ -s, --skip-chars=N avoid comparing the first N characters\n\ -u, --unique only print unique lines\n\ "), stdout); fputs (_("\ -w, --check-chars=N compare no more than N characters in lines\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ A field is a run of whitespace, then non-whitespace characters.\n\ Fields are skipped before chars.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } /* Convert OPT to size_t, reporting an error using MSGID if it does not fit. */ static size_t size_opt (char const *opt, char const *msgid) { unsigned long int size; if (xstrtoul (opt, NULL, 10, &size, "") != LONGINT_OK || SIZE_MAX < size) error (EXIT_FAILURE, 0, "%s: %s", opt, _(msgid)); return size; } /* Given a linebuffer LINE, return a pointer to the beginning of the line's field to be compared. */ static char * find_field (const struct linebuffer *line) { register size_t count; register char *lp = line->buffer; register size_t size = line->length - 1; register size_t i = 0; for (count = 0; count < skip_fields && i < size; count++) { while (i < size && ISBLANK (lp[i])) i++; while (i < size && !ISBLANK (lp[i])) i++; } for (count = 0; count < skip_chars && i < size; count++) i++; return lp + i; } /* Return zero if two strings OLD and NEW match, nonzero if not. OLD and NEW point not to the beginnings of the lines but rather to the beginnings of the fields to compare. OLDLEN and NEWLEN are their lengths. */ static int different (char *old, char *new, size_t oldlen, size_t newlen) { if (check_chars < oldlen) oldlen = check_chars; if (check_chars < newlen) newlen = check_chars; if (ignore_case) { /* FIXME: This should invoke strcoll somehow. */ return oldlen != newlen || memcasecmp (old, new, oldlen); } else if (HAVE_SETLOCALE && hard_LC_COLLATE) return xmemcoll (old, oldlen, new, newlen); else return oldlen != newlen || memcmp (old, new, oldlen); } /* Output the line in linebuffer LINE to stream STREAM provided that the switches say it should be output. If requested, print the number of times it occurred, as well; LINECOUNT + 1 is the number of times that the line occurred. */ static void writeline (const struct linebuffer *line, FILE *stream, int linecount) { if ((mode == output_unique && linecount != 0) || (mode == output_repeated && linecount == 0) || (mode == output_all_repeated && linecount == 0)) return; if (countmode == count_occurrences) fprintf (stream, "%7d\t", linecount + 1); fwrite (line->buffer, sizeof (char), line->length, stream); } /* Process input file INFILE with output to OUTFILE. If either is "-", use the standard I/O stream for it instead. */ static void check_file (const char *infile, const char *outfile) { FILE *istream; FILE *ostream; struct linebuffer lb1, lb2; struct linebuffer *thisline, *prevline; if (STREQ (infile, "-")) istream = stdin; else istream = fopen (infile, "r"); if (istream == NULL) error (EXIT_FAILURE, errno, "%s", infile); if (STREQ (outfile, "-")) ostream = stdout; else ostream = fopen (outfile, "w"); if (ostream == NULL) error (EXIT_FAILURE, errno, "%s", outfile); thisline = &lb1; prevline = &lb2; initbuffer (thisline); initbuffer (prevline); /* The duplication in the following `if' and `else' blocks is an optimization to distinguish the common case (in which none of the following options has been specified: --count, -repeated, --all-repeated, --unique) from the others. In the common case, this optimization lets uniq output each different line right away, without waiting to see if the next one is different. */ if (mode == output_all && countmode == count_none) { char *prevfield IF_LINT (= NULL); size_t prevlen IF_LINT (= 0); while (!feof (istream)) { char *thisfield; size_t thislen; if (readline (thisline, istream) == 0) break; thisfield = find_field (thisline); thislen = thisline->length - 1 - (thisfield - thisline->buffer); if (prevline->length == 0 || different (thisfield, prevfield, thislen, prevlen)) { fwrite (thisline->buffer, sizeof (char), thisline->length, ostream); SWAP_LINES (prevline, thisline); prevfield = thisfield; prevlen = thislen; } } } else { char *prevfield; size_t prevlen; int match_count = 0; int first_delimiter = 1; if (readline (prevline, istream) == 0) goto closefiles; prevfield = find_field (prevline); prevlen = prevline->length - 1 - (prevfield - prevline->buffer); while (!feof (istream)) { int match; char *thisfield; size_t thislen; if (readline (thisline, istream) == 0) break; thisfield = find_field (thisline); thislen = thisline->length - 1 - (thisfield - thisline->buffer); match = !different (thisfield, prevfield, thislen, prevlen); if (match) ++match_count; if (mode == output_all_repeated && delimit_groups != DM_NONE) { if (!match) { if (match_count) /* a previous match */ first_delimiter = 0; /* Only used when DM_SEPARATE */ } else if (match_count == 1) { if ((delimit_groups == DM_PREPEND) || (delimit_groups == DM_SEPARATE && !first_delimiter)) putc ('\n', ostream); } } if (!match || mode == output_all_repeated) { writeline (prevline, ostream, match_count); SWAP_LINES (prevline, thisline); prevfield = thisfield; prevlen = thislen; if (!match) match_count = 0; } } writeline (prevline, ostream, match_count); } closefiles: if (ferror (istream) || fclose (istream) == EOF) error (EXIT_FAILURE, errno, _("error reading %s"), infile); /* Close ostream only if it's not stdout -- the latter is closed via the atexit-invoked close_stdout. */ if (ostream != stdout && (ferror (ostream) || fclose (ostream) == EOF)) error (EXIT_FAILURE, errno, _("error writing %s"), outfile); free (lb1.buffer); free (lb2.buffer); } int main (int argc, char **argv) { int optc = 0; bool posixly_correct = (getenv ("POSIXLY_CORRECT") != NULL); bool obsolete_skip_fields = false; int nfiles = 0; char const *file[2]; file[0] = file[1] = "-"; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); hard_LC_COLLATE = hard_locale (LC_COLLATE); atexit (close_stdout); skip_chars = 0; skip_fields = 0; check_chars = SIZE_MAX; mode = output_all; countmode = count_none; delimit_groups = DM_NONE; for (;;) { /* Parse an operand with leading "+" as a file after "--" was seen; or if pedantic and a file was seen; or if not obsolete. */ if (optc == -1 || (posixly_correct && nfiles != 0) || ((optc = getopt_long (argc, argv, "-0123456789Dcdf:is:uw:", longopts, NULL)) == -1)) { if (optind == argc) break; if (nfiles == 2) { error (0, 0, _("extra operand `%s'"), argv[optind]); usage (EXIT_FAILURE); } file[nfiles++] = argv[optind++]; } else switch (optc) { case 1: { unsigned long int size; if (optarg[0] == '+' && posix2_version () < 200112 && xstrtoul (optarg, NULL, 10, &size, "") == LONGINT_OK && size <= SIZE_MAX) skip_chars = size; else if (nfiles == 2) { error (0, 0, _("extra operand `%s'"), optarg); usage (EXIT_FAILURE); } else file[nfiles++] = optarg; } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { size_t s = skip_fields; skip_fields = s * 10 + optc - '0'; if (SIZE_MAX / 10 < s || skip_fields < s) error (EXIT_FAILURE, 0, "%s", _("invalid number of fields to skip")); obsolete_skip_fields = true; } break; case 'c': countmode = count_occurrences; break; case 'd': mode = output_repeated; break; case 'D': mode = output_all_repeated; if (optarg == NULL) delimit_groups = DM_NONE; else delimit_groups = XARGMATCH ("--all-repeated", optarg, delimit_method_string, delimit_method_map); break; case 'f': /* Like '-#'. */ skip_fields = size_opt (optarg, N_("invalid number of fields to skip")); break; case 'i': ignore_case = 1; break; case 's': /* Like '+#'. */ skip_chars = size_opt (optarg, N_("invalid number of bytes to skip")); break; case 'u': mode = output_unique; break; case 'w': check_chars = size_opt (optarg, N_("invalid number of bytes to compare")); break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } if (obsolete_skip_fields && 200112 <= posix2_version ()) { error (0, 0, _("`-%lu' option is obsolete; use `-f %lu'"), (unsigned long) skip_fields, (unsigned long) skip_fields); usage (EXIT_FAILURE); } if (countmode == count_occurrences && mode == output_all_repeated) { error (0, 0, _("printing all duplicated lines and repeat counts is meaningless")); usage (EXIT_FAILURE); } check_file (file[0], file[1]); exit (EXIT_SUCCESS); }
/* basename -- strip directory and suffix from filenames Copyright (C) 1990-1997, 1999-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Usage: basename name [suffix] NAME is a pathname; SUFFIX is a suffix to strip from it. basename /usr/foo/lossage/functions.l => functions.l basename /usr/foo/lossage/functions.l .l => functions basename functions.lisp p => functions.lis */ #include <config.h> #include <stdio.h> #include <sys/types.h> #include "system.h" #include "long-options.h" #include "dirname.h" #include "error.h" #include "closeout.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "basename" #define AUTHORS "FIXME unknown" /* The name this program was run with. */ char *program_name; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s NAME [SUFFIX]\n\ or: %s OPTION\n\ "), program_name, program_name); fputs (_("\ Print NAME with any leading directory components removed.\n\ If specified, also remove a trailing SUFFIX.\n\ \n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } /* Remove SUFFIX from the end of NAME if it is there, unless NAME consists entirely of SUFFIX. */ static void remove_suffix (char *name, const char *suffix) { char *np; const char *sp; np = name + strlen (name); sp = suffix + strlen (suffix); while (np > name && sp > suffix) if (*--np != *--sp) return; if (np > name) *np = '\0'; } int main (int argc, char **argv) { char *name; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, AUTHORS, usage); /* The above handles --help and --version. Since there is no other invocation of getopt, handle `--' here. */ if (argc > 1 && STREQ (argv[1], "--")) { --argc; ++argv; } if (argc == 1 || argc > 3) { error (0, 0, (argc == 1 ? _("too few arguments") : _("too many arguments"))); usage (EXIT_FAILURE); } name = base_name (argv[1]); name[base_len (name)] = '\0'; if (argc == 3) remove_suffix (name, argv[2]); puts (name); exit (EXIT_SUCCESS); }
/* system-dependent definitions for fileutils, textutils, and sh-utils packages. Copyright (C) 1989, 1991-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Include sys/types.h before this file. */ #if 2 <= __GLIBC__ && 2 <= __GLIBC_MINOR__ # if ! defined _SYS_TYPES_H you must include <sys/types.h> before including this file # endif #endif #include <sys/stat.h> #if !defined HAVE_MKFIFO # define mkfifo(path, mode) (mknod ((path), (mode) | S_IFIFO, 0)) #endif #if HAVE_SYS_PARAM_H # include <sys/param.h> #endif /* <unistd.h> should be included before any preprocessor test of _POSIX_VERSION. */ #if HAVE_UNISTD_H # include <unistd.h> #endif #ifndef STDIN_FILENO # define STDIN_FILENO 0 #endif #ifndef STDOUT_FILENO # define STDOUT_FILENO 1 #endif #ifndef STDERR_FILENO # define STDERR_FILENO 2 #endif #if HAVE_LIMITS_H /* limits.h must come before pathmax.h because limits.h on some systems undefs PATH_MAX, whereas pathmax.h sets PATH_MAX. */ # include <limits.h> #endif #include "pathmax.h" #if TIME_WITH_SYS_TIME # include <sys/time.h> # include <time.h> #else # if HAVE_SYS_TIME_H # include <sys/time.h> # else # include <time.h> # endif #endif /* Since major is a function on SVR4, we can't use `ifndef major'. */ #if MAJOR_IN_MKDEV # include <sys/mkdev.h> # define HAVE_MAJOR #endif #if MAJOR_IN_SYSMACROS # include <sys/sysmacros.h> # define HAVE_MAJOR #endif #ifdef major /* Might be defined in sys/types.h. */ # define HAVE_MAJOR #endif #ifndef HAVE_MAJOR # define major(dev) (((dev) >> 8) & 0xff) # define minor(dev) ((dev) & 0xff) # define makedev(maj, min) (((maj) << 8) | (min)) #endif #undef HAVE_MAJOR #if HAVE_UTIME_H # include <utime.h> #endif /* Some systems (even some that do have <utime.h>) don't declare this structure anywhere. */ #ifndef HAVE_STRUCT_UTIMBUF struct utimbuf { long actime; long modtime; }; #endif /* Don't use bcopy! Use memmove if source and destination may overlap, memcpy otherwise. */ #if HAVE_STRING_H # if !STDC_HEADERS && HAVE_MEMORY_H # include <memory.h> # endif # include <string.h> #else # include <strings.h> #endif #if ! HAVE_DECL_MEMRCHR void *memrchr (const void *, int, size_t); #endif #include <errno.h> #ifndef errno extern int errno; #endif #if HAVE_STDBOOL_H # include <stdbool.h> #else typedef enum {false = 0, true = 1} bool; #endif #if HAVE_STDLIB_H # define getopt system_getopt # include <stdlib.h> # undef getopt #endif /* The following test is to work around the gross typo in systems like Sony NEWS-OS Release 4.0C, whereby EXIT_FAILURE is defined to 0, not 1. */ #if !EXIT_FAILURE # undef EXIT_FAILURE # define EXIT_FAILURE 1 #endif #ifndef EXIT_SUCCESS # define EXIT_SUCCESS 0 #endif #if HAVE_FCNTL_H # include <fcntl.h> #else # include <sys/file.h> #endif #if !defined SEEK_SET # define SEEK_SET 0 # define SEEK_CUR 1 # define SEEK_END 2 #endif #ifndef F_OK # define F_OK 0 # define X_OK 1 # define W_OK 2 # define R_OK 4 #endif /* For systems that distinguish between text and binary I/O. O_BINARY is usually declared in fcntl.h */ #if !defined O_BINARY && defined _O_BINARY /* For MSC-compatible compilers. */ # define O_BINARY _O_BINARY # define O_TEXT _O_TEXT #endif #ifdef __BEOS__ /* BeOS 5 has O_BINARY and O_TEXT, but they have no effect. */ # undef O_BINARY # undef O_TEXT #endif #if O_BINARY # ifndef __DJGPP__ # define setmode _setmode # define fileno(_fp) _fileno (_fp) # endif /* not DJGPP */ # define SET_MODE(_f, _m) setmode (_f, _m) # define SET_BINARY(_f) do {if (!isatty(_f)) setmode (_f, O_BINARY);} while (0) # define SET_BINARY2(_f1, _f2) \ do { \ if (!isatty (_f1)) \ { \ setmode (_f1, O_BINARY); \ if (!isatty (_f2)) \ setmode (_f2, O_BINARY); \ } \ } while(0) #else # define SET_MODE(_f, _m) (void)0 # define SET_BINARY(f) (void)0 # define SET_BINARY2(f1,f2) (void)0 # define O_BINARY 0 # define O_TEXT 0 #endif /* O_BINARY */ #if HAVE_DIRENT_H # include <dirent.h> # define NLENGTH(direct) (strlen((direct)->d_name)) #else /* not HAVE_DIRENT_H */ # define dirent direct # define NLENGTH(direct) ((direct)->d_namlen) # if HAVE_SYS_NDIR_H # include <sys/ndir.h> # endif /* HAVE_SYS_NDIR_H */ # if HAVE_SYS_DIR_H # include <sys/dir.h> # endif /* HAVE_SYS_DIR_H */ # if HAVE_NDIR_H # include <ndir.h> # endif /* HAVE_NDIR_H */ #endif /* HAVE_DIRENT_H */ #if CLOSEDIR_VOID /* Fake a return value. */ # define CLOSEDIR(d) (closedir (d), 0) #else # define CLOSEDIR(d) closedir (d) #endif /* Get or fake the disk device blocksize. Usually defined by sys/param.h (if at all). */ #if !defined DEV_BSIZE && defined BSIZE # define DEV_BSIZE BSIZE #endif #if !defined DEV_BSIZE && defined BBSIZE /* SGI */ # define DEV_BSIZE BBSIZE #endif #ifndef DEV_BSIZE # define DEV_BSIZE 4096 #endif /* Extract or fake data from a `struct stat'. ST_BLKSIZE: Preferred I/O blocksize for the file, in bytes. ST_NBLOCKS: Number of blocks in the file, including indirect blocks. ST_NBLOCKSIZE: Size of blocks used when calculating ST_NBLOCKS. */ #ifndef HAVE_STRUCT_STAT_ST_BLOCKS # define ST_BLKSIZE(statbuf) DEV_BSIZE # if defined _POSIX_SOURCE || !defined BSIZE /* fileblocks.c uses BSIZE. */ # define ST_NBLOCKS(statbuf) \ ((statbuf).st_size / ST_NBLOCKSIZE + ((statbuf).st_size % ST_NBLOCKSIZE != 0)) # else /* !_POSIX_SOURCE && BSIZE */ # define ST_NBLOCKS(statbuf) \ (S_ISREG ((statbuf).st_mode) \ || S_ISDIR ((statbuf).st_mode) \ ? st_blocks ((statbuf).st_size) : 0) # endif /* !_POSIX_SOURCE && BSIZE */ #else /* HAVE_STRUCT_STAT_ST_BLOCKS */ /* Some systems, like Sequents, return st_blksize of 0 on pipes. Also, when running `rsh hpux11-system cat any-file', cat would determine that the output stream had an st_blksize of 2147421096. So here we arbitrarily limit the `optimal' block size to 4MB. If anyone knows of a system for which the legitimate value for st_blksize can exceed 4MB, please report it as a bug in this code. */ # define ST_BLKSIZE(statbuf) ((0 < (statbuf).st_blksize \ && (statbuf).st_blksize <= (1 << 22)) /* 4MB */ \ ? (statbuf).st_blksize : DEV_BSIZE) # if defined hpux || defined __hpux__ || defined __hpux /* HP-UX counts st_blocks in 1024-byte units. This loses when mixing HP-UX and BSD filesystems with NFS. */ # define ST_NBLOCKSIZE 1024 # else /* !hpux */ # if defined _AIX && defined _I386 /* AIX PS/2 counts st_blocks in 4K units. */ # define ST_NBLOCKSIZE (4 * 1024) # else /* not AIX PS/2 */ # if defined _CRAY # define ST_NBLOCKS(statbuf) \ (S_ISREG ((statbuf).st_mode) \ || S_ISDIR ((statbuf).st_mode) \ ? (statbuf).st_blocks * ST_BLKSIZE(statbuf)/ST_NBLOCKSIZE : 0) # endif /* _CRAY */ # endif /* not AIX PS/2 */ # endif /* !hpux */ #endif /* HAVE_STRUCT_STAT_ST_BLOCKS */ #ifndef ST_NBLOCKS # define ST_NBLOCKS(statbuf) ((statbuf).st_blocks) #endif #ifndef ST_NBLOCKSIZE # define ST_NBLOCKSIZE 512 #endif #include "sys2.h"
/* long-options.h -- declaration for --help- and --version-handling function. Copyright (C) 1993, 1994, 1998, 1999 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Jim Meyering. */ #ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif #endif void parse_long_options PARAMS ((int _argc, char **_argv, const char *_command_name, const char *_package, const char *_version, const char *_authors, void (*_usage) (int)));
/* Copyright (C) 1998, 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef DIRNAME_H_ # define DIRNAME_H_ 1 # ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif # endif # ifndef DIRECTORY_SEPARATOR # define DIRECTORY_SEPARATOR '/' # endif # ifndef ISSLASH # define ISSLASH(C) ((C) == DIRECTORY_SEPARATOR) # endif # ifndef FILESYSTEM_PREFIX_LEN # define FILESYSTEM_PREFIX_LEN(Filename) 0 # endif char *base_name PARAMS ((char const *path)); char *dir_name PARAMS ((char const *path)); size_t base_len PARAMS ((char const *path)); size_t dir_len PARAMS ((char const *path)); int strip_trailing_slashes PARAMS ((char *path)); #endif /* not DIRNAME_H_ */
/* Declaration for error-reporting function Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. Bugs can be reported to bug-glibc@prep.ai.mit.edu. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef _ERROR_H #define _ERROR_H 1 #ifndef __attribute__ /* This feature is available in gcc versions 2.5 and later. */ # if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) # define __attribute__(Spec) /* empty */ # endif /* The __-protected variants of `format' and `printf' attributes are accepted by gcc versions 2.6.4 (effectively 2.7) and later. */ # if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7) # define __format__ format # define __printf__ printf # endif #endif #ifdef __cplusplus extern "C" { #endif #if defined (__STDC__) && __STDC__ /* Print a message with `fprintf (stderr, FORMAT, ...)'; if ERRNUM is nonzero, follow it with ": " and strerror (ERRNUM). If STATUS is nonzero, terminate the program with `exit (STATUS)'. */ extern void error (int status, int errnum, const char *format, ...) __attribute__ ((__format__ (__printf__, 3, 4))); extern void error_at_line (int status, int errnum, const char *fname, unsigned int lineno, const char *format, ...) __attribute__ ((__format__ (__printf__, 5, 6))); /* If NULL, error will flush stdout, then print on stderr the program name, a colon and a space. Otherwise, error will call this function without parameters instead. */ extern void (*error_print_progname) (void); #else void error (); void error_at_line (); extern void (*error_print_progname) (); #endif /* This variable is incremented each time `error' is called. */ extern unsigned int error_message_count; /* Sometimes we want to have at most one error per line. This variable controls whether this mode is selected or not. */ extern int error_one_per_line; #ifdef __cplusplus } #endif #endif /* error.h */
#ifndef CLOSEOUT_H # define CLOSEOUT_H 1 # ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif # endif void close_stdout_set_status PARAMS ((int status)); void close_stdout_set_file_name PARAMS ((const char *file)); void close_stdout PARAMS ((void)); void close_stdout_status PARAMS ((int status)); #endif
/* acl.c - access control lists Copyright (C) 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Written by Paul Eggert. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <sys/stat.h> #ifndef S_ISLNK # define S_ISLNK(Mode) 0 #endif #include "acl.h" #include <errno.h> #ifndef ENOSYS # define ENOSYS (-1) #endif #ifndef MIN_ACL_ENTRIES # define MIN_ACL_ENTRIES 4 #endif /* Return 1 if PATH has a nontrivial access control list, 0 if not, and -1 (setting errno) if an error is encountered. */ int file_has_acl (char const *path, struct stat const *pathstat) { /* FIXME: This implementation should work on recent-enough versions of HP-UX, Solaris, and Unixware, but it simply returns 0 with POSIX 1003.1e (draft 17 -- abandoned), AIX, GNU/Linux, Irix, and Tru64. Please see Samba's source/lib/sysacls.c file for fix-related ideas. */ #if HAVE_ACL && defined GETACLCNT if (! S_ISLNK (pathstat->st_mode)) { int n = acl (path, GETACLCNT, 0, NULL); return n < 0 ? (errno == ENOSYS ? 0 : -1) : (MIN_ACL_ENTRIES < n); } #endif return 0; }
/* Parse dates for touch and date. Copyright (C) 1989, 1990, 1991, 1998, 2000-2002 Free Software Foundation Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Yacc-based version written by Jim Kingdon and David MacKenzie. Rewritten by Jim Meyering. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #if HAVE_STDBOOL_H # include <stdbool.h> #else typedef enum {false = 0, true = 1} bool; #endif #include <stdio.h> #if HAVE_STDLIB_H # include <stdlib.h> #endif #include <sys/types.h> #if HAVE_STRING_H # include <string.h> #else # include <strings.h> #endif #ifdef TM_IN_SYS_TIME # include <sys/time.h> #else # include <time.h> #endif #include "posixtm.h" #include "unlocked-io.h" /* ISDIGIT differs from isdigit, as follows: - Its arg may be any int or unsigned int; it need not be an unsigned char. - It's guaranteed to evaluate its argument exactly once. - It's typically faster. POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to ISDIGIT_LOCALE unless it's important to use the locale's definition of `digit' even when the host does not conform to POSIX. */ #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9) time_t mktime (); /* POSIX requires: touch -t [[CC]YY]mmddhhmm[.ss] FILE... 8, 10, or 12 digits, followed by optional .ss (PDS_LEADING_YEAR | PDS_CENTURY | PDS_SECONDS) touch mmddhhmm[YY] FILE... (obsoleted by POSIX 1003.1-2001) 8 or 10 digits (PDS_TRAILING_YEAR) date mmddhhmm[[CC]YY] 8, 10, or 12 digits (PDS_TRAILING_YEAR | PDS_CENTURY) */ static int year (struct tm *tm, const int *digit_pair, size_t n, int allow_century) { switch (n) { case 1: tm->tm_year = *digit_pair; /* Deduce the century based on the year. POSIX requires that 00-68 be interpreted as 2000-2068, and that 69-99 be interpreted as 1969-1999. */ if (digit_pair[0] <= 68) tm->tm_year += 100; break; case 2: if (!allow_century) return 1; tm->tm_year = digit_pair[0] * 100 + digit_pair[1] - 1900; break; case 0: { time_t now; struct tm *tmp; /* Use current year. */ time (&now); tmp = localtime (&now); if (! tmp) return 1; tm->tm_year = tmp->tm_year; } break; default: abort (); } return 0; } static int posix_time_parse (struct tm *tm, const char *s, unsigned int syntax_bits) { const char *dot = NULL; int pair[6]; int *p; unsigned int i; size_t s_len = strlen (s); size_t len = (((syntax_bits & PDS_SECONDS) && (dot = strchr (s, '.'))) ? (size_t) (dot - s) : s_len); if (len != 8 && len != 10 && len != 12) return 1; if (dot) { if (!(syntax_bits & PDS_SECONDS)) return 1; if (s_len - len != 3) return 1; } for (i = 0; i < len; i++) if (!ISDIGIT (s[i])) return 1; len /= 2; for (i = 0; i < len; i++) pair[i] = 10 * (s[2*i] - '0') + s[2*i + 1] - '0'; p = pair; if (syntax_bits & PDS_LEADING_YEAR) { if (year (tm, p, len - 4, syntax_bits & PDS_CENTURY)) return 1; p += len - 4; len = 4; } /* Handle 8 digits worth of `MMDDhhmm'. */ tm->tm_mon = *p++ - 1; tm->tm_mday = *p++; tm->tm_hour = *p++; tm->tm_min = *p++; len -= 4; /* Handle any trailing year. */ if (syntax_bits & PDS_TRAILING_YEAR) { if (year (tm, p, len, syntax_bits & PDS_CENTURY)) return 1; } /* Handle seconds. */ if (!dot) { tm->tm_sec = 0; } else { int seconds; ++dot; if (!ISDIGIT (dot[0]) || !ISDIGIT (dot[1])) return 1; seconds = 10 * (dot[0] - '0') + dot[1] - '0'; tm->tm_sec = seconds; } return 0; } /* Parse a POSIX-style date, returning true if successful. */ bool posixtime (time_t *p, const char *s, unsigned int syntax_bits) { struct tm tm0; struct tm tm1; struct tm const *tm; time_t t; if (posix_time_parse (&tm0, s, syntax_bits)) return false; tm1 = tm0; tm1.tm_isdst = -1; t = mktime (&tm1); if (t != (time_t) -1) tm = &tm1; else { /* mktime returns -1 for errors, but -1 is also a valid time_t value. Check whether an error really occurred. */ tm = localtime (&t); if (! tm) return false; } /* Reject dates like "September 31" and times like "25:61". */ if ((tm0.tm_year ^ tm->tm_year) | (tm0.tm_mon ^ tm->tm_mon) | (tm0.tm_mday ^ tm->tm_mday) | (tm0.tm_hour ^ tm->tm_hour) | (tm0.tm_min ^ tm->tm_min) | (tm0.tm_sec ^ tm->tm_sec)) return false; *p = t; return true; } #ifdef TEST_POSIXTIME /* Test mainly with syntax_bits == 13 (aka: (PDS_LEADING_YEAR | PDS_CENTURY | PDS_SECONDS)) This test data assumes Universal Time, e.g., TZ="UTC0". This test data also assumes that time_t is signed and is at least 39 bits wide, so that it can represent all years from 0000 through 9999. A host with 32-bit signed time_t can represent only time stamps in the range 1901-12-13 20:45:52 through 2038-01-18 03:14:07 UTC, assuming POSIX time_t with no leap seconds, so test cases outside this range will not work on such a host. Also, the first two lines of test data assume that the current year is 2002. BEGIN-DATA 12131415.16 13 1039788916 Fri Dec 13 14:15:16 2002 12131415.16 13 1039788916 Fri Dec 13 14:15:16 2002 000001010000.00 13 -62167132800 Sun Jan 1 00:00:00 0000 190112132045.52 13 -2147483648 Fri Dec 13 20:45:52 1901 190112132045.53 13 -2147483647 Fri Dec 13 20:45:53 1901 190112132046.52 13 -2147483588 Fri Dec 13 20:46:52 1901 190112132145.52 13 -2147480048 Fri Dec 13 21:45:52 1901 190112142045.52 13 -2147397248 Sat Dec 14 20:45:52 1901 190201132045.52 13 -2144805248 Mon Jan 13 20:45:52 1902 196912312359.59 13 -1 Wed Dec 31 23:59:59 1969 197001010000.00 13 0 Thu Jan 1 00:00:00 1970 197001010000.01 13 1 Thu Jan 1 00:00:01 1970 197001010001.00 13 60 Thu Jan 1 00:01:00 1970 197001010100.00 13 3600 Thu Jan 1 01:00:00 1970 197001020000.00 13 86400 Fri Jan 2 00:00:00 1970 197002010000.00 13 2678400 Sun Feb 1 00:00:00 1970 197101010000.00 13 31536000 Fri Jan 1 00:00:00 1971 197001000000.00 13 * * 197000010000.00 13 * * 197001010000.60 13 * * 197001010060.00 13 * * 197001012400.00 13 * * 197001320000.00 13 * * 197013010000.00 13 * * 203801190314.06 13 2147483646 Tue Jan 19 03:14:06 2038 203801190314.07 13 2147483647 Tue Jan 19 03:14:07 2038 203801190314.08 13 2147483648 Tue Jan 19 03:14:08 2038 999912312359.59 13 253402300799 Fri Dec 31 23:59:59 9999 1112131415 13 1323785700 Tue Dec 13 14:15:00 2011 1112131415.16 13 1323785716 Tue Dec 13 14:15:16 2011 201112131415.16 13 1323785716 Tue Dec 13 14:15:16 2011 191112131415.16 13 -1831974284 Wed Dec 13 14:15:16 1911 203712131415.16 13 2144326516 Sun Dec 13 14:15:16 2037 3712131415.16 13 2144326516 Sun Dec 13 14:15:16 2037 6812131415.16 13 3122633716 Thu Dec 13 14:15:16 2068 6912131415.16 13 -1590284 Sat Dec 13 14:15:16 1969 7012131415.16 13 29945716 Sun Dec 13 14:15:16 1970 1213141599 2 945094500 Mon Dec 13 14:15:00 1999 1213141500 2 976716900 Wed Dec 13 14:15:00 2000 END-DATA */ # define MAX_BUFF_LEN 1024 int main () { char buff[MAX_BUFF_LEN + 1]; buff[MAX_BUFF_LEN] = 0; while (fgets (buff, MAX_BUFF_LEN, stdin) && buff[0]) { char time_str[MAX_BUFF_LEN]; unsigned int syntax_bits; time_t t; if (sscanf (buff, "%s %u", time_str, &syntax_bits) != 2) printf ("*\n"); else { printf ("%-15s %2u ", time_str, syntax_bits); if (posixtime (&t, time_str, syntax_bits)) printf ("%12ld %s", (long) t, ctime (&t)); else printf ("%12s %s", "*", "*\n"); } } exit (0); } #endif /* Local Variables: compile-command: "gcc -DTEST_POSIXTIME -DHAVE_CONFIG_H -I.. -g -O -Wall -W posixtm.c" End: */
/* Which POSIX version to conform to, for utilities. Copyright (C) 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <limits.h> #include <stdlib.h> #if !HAVE_DECL_GETENV && !defined getenv char *getenv (); #endif #if HAVE_UNISTD_H # include <unistd.h> #endif #ifndef _POSIX2_VERSION # define _POSIX2_VERSION 0 #endif /* The POSIX version that utilities should conform to. The default is specified by the system. */ int posix2_version (void) { long int v = _POSIX2_VERSION; char const *s = getenv ("_POSIX2_VERSION"); if (s && *s) { char *e; long int i = strtol (s, &e, 10); if (! *e) v = i; } return v < INT_MIN ? INT_MIN : v < INT_MAX ? v : INT_MAX; }
/* Copyright (C) 1991-1999, 2000, 2001, 2003 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. Bugs can be reported to bug-glibc@prep.ai.mit.edu. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #ifdef _LIBC # define HAVE_LIMITS_H 1 # define HAVE_MBLEN 1 # define HAVE_MBRLEN 1 # define HAVE_STRUCT_ERA_ENTRY 1 # define HAVE_TM_GMTOFF 1 # define HAVE_TM_ZONE 1 # define HAVE_TZNAME 1 # define HAVE_TZSET 1 # define MULTIBYTE_IS_FORMAT_SAFE 1 # define STDC_HEADERS 1 # include "../locale/localeinfo.h" #endif #if defined emacs && !defined HAVE_BCOPY # define HAVE_MEMCPY 1 #endif #include <ctype.h> #include <sys/types.h> /* Some systems define `time_t' here. */ #ifdef TIME_WITH_SYS_TIME # include <sys/time.h> # include <time.h> #else # ifdef HAVE_SYS_TIME_H # include <sys/time.h> # else # include <time.h> # endif #endif #if HAVE_TZNAME extern char *tzname[]; #endif /* Do multibyte processing if multibytes are supported, unless multibyte sequences are safe in formats. Multibyte sequences are safe if they cannot contain byte sequences that look like format conversion specifications. The GNU C Library uses UTF8 multibyte encoding, which is safe for formats, but strftime.c can be used with other C libraries that use unsafe encodings. */ #define DO_MULTIBYTE (HAVE_MBLEN && ! MULTIBYTE_IS_FORMAT_SAFE) #if DO_MULTIBYTE # if HAVE_MBRLEN # include <wchar.h> # else /* Simulate mbrlen with mblen as best we can. */ # define mbstate_t int # define mbrlen(s, n, ps) mblen (s, n) # define mbsinit(ps) (*(ps) == 0) # endif static const mbstate_t mbstate_zero; #endif #if HAVE_LIMITS_H # include <limits.h> #endif #if STDC_HEADERS # include <stddef.h> # include <stdlib.h> # include <string.h> #else # ifndef HAVE_MEMCPY # define memcpy(d, s, n) bcopy ((s), (d), (n)) # endif #endif #ifdef COMPILE_WIDE # include <endian.h> # define CHAR_T wchar_t # define UCHAR_T unsigned int # define L_(Str) L##Str # define NLW(Sym) _NL_W##Sym # define MEMCPY(d, s, n) __wmemcpy (d, s, n) # define STRLEN(s) __wcslen (s) #else # define CHAR_T char # define UCHAR_T unsigned char # define L_(Str) Str # define NLW(Sym) Sym # if !defined STDC_HEADERS && !defined HAVE_MEMCPY # define MEMCPY(d, s, n) bcopy ((s), (d), (n)) # else # define MEMCPY(d, s, n) memcpy ((d), (s), (n)) # endif # define STRLEN(s) strlen (s) # ifdef _LIBC # define MEMPCPY(d, s, n) __mempcpy (d, s, n) # else # ifndef HAVE_MEMPCPY # define MEMPCPY(d, s, n) ((void *) ((char *) memcpy (d, s, n) + (n))) # endif # endif #endif #ifndef __P # if defined __GNUC__ || (defined __STDC__ && __STDC__) # define __P(args) args # else # define __P(args) () # endif /* GCC. */ #endif /* Not __P. */ #ifndef PTR # ifdef __STDC__ # define PTR void * # else # define PTR char * # endif #endif #ifndef CHAR_BIT # define CHAR_BIT 8 #endif #ifndef NULL # define NULL 0 #endif #define TYPE_SIGNED(t) ((t) -1 < 0) /* Bound on length of the string representing an integer value of type t. Subtract one for the sign bit if t is signed; 302 / 1000 is log10 (2) rounded up; add one for integer division truncation; add one more for a minus sign if t is signed. */ #define INT_STRLEN_BOUND(t) \ ((sizeof (t) * CHAR_BIT - TYPE_SIGNED (t)) * 302 / 1000 + 1 + TYPE_SIGNED (t)) #define TM_YEAR_BASE 1900 #ifndef __isleap /* Nonzero if YEAR is a leap year (every 4 years, except every 100th isn't, and every 400th is). */ # define __isleap(year) \ ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0)) #endif #ifdef _LIBC # define my_strftime_gmtime_r __gmtime_r # define my_strftime_localtime_r __localtime_r # define tzname __tzname # define tzset __tzset #else /* If we're a strftime substitute in a GNU program, then prefer gmtime to gmtime_r, since many gmtime_r implementations are buggy. Similarly for localtime_r. */ # if ! HAVE_TM_GMTOFF static struct tm *my_strftime_gmtime_r __P ((const time_t *, struct tm *)); static struct tm * my_strftime_gmtime_r (t, tp) const time_t *t; struct tm *tp; { struct tm *l = gmtime (t); if (! l) return 0; *tp = *l; return tp; } static struct tm *my_strftime_localtime_r __P ((const time_t *, struct tm *)); static struct tm * my_strftime_localtime_r (t, tp) const time_t *t; struct tm *tp; { struct tm *l = localtime (t); if (! l) return 0; *tp = *l; return tp; } # endif /* ! HAVE_TM_GMTOFF */ #endif /* ! defined _LIBC */ #if !defined memset && !defined HAVE_MEMSET && !defined _LIBC /* Some systems lack the `memset' function and we don't want to introduce additional dependencies. */ /* The SGI compiler reportedly barfs on the trailing null if we use a string constant as the initializer. 28 June 1997, rms. */ static const CHAR_T spaces[16] = /* " " */ { L_(' '),L_(' '),L_(' '),L_(' '),L_(' '),L_(' '),L_(' '),L_(' '), L_(' '),L_(' '),L_(' '),L_(' '),L_(' '),L_(' '),L_(' '),L_(' ') }; static const CHAR_T zeroes[16] = /* "0000000000000000" */ { L_('0'),L_('0'),L_('0'),L_('0'),L_('0'),L_('0'),L_('0'),L_('0'), L_('0'),L_('0'),L_('0'),L_('0'),L_('0'),L_('0'),L_('0'),L_('0') }; # define memset_space(P, Len) \ do { \ int _len = (Len); \ \ do \ { \ int _this = _len > 16 ? 16 : _len; \ (P) = MEMPCPY ((P), spaces, _this * sizeof (CHAR_T)); \ _len -= _this; \ } \ while (_len > 0); \ } while (0) # define memset_zero(P, Len) \ do { \ int _len = (Len); \ \ do \ { \ int _this = _len > 16 ? 16 : _len; \ (P) = MEMPCPY ((P), zeroes, _this * sizeof (CHAR_T)); \ _len -= _this; \ } \ while (_len > 0); \ } while (0) #else # ifdef COMPILE_WIDE # define memset_space(P, Len) (wmemset ((P), L' ', (Len)), (P) += (Len)) # define memset_zero(P, Len) (wmemset ((P), L'0', (Len)), (P) += (Len)) # else # define memset_space(P, Len) (memset ((P), ' ', (Len)), (P) += (Len)) # define memset_zero(P, Len) (memset ((P), '0', (Len)), (P) += (Len)) # endif #endif #define add(n, f) \ do \ { \ int _n = (n); \ int _delta = width - _n; \ int _incr = _n + (_delta > 0 ? _delta : 0); \ if ((size_t) _incr >= maxsize - i) \ return 0; \ if (p) \ { \ if (_delta > 0) \ { \ if (pad == L_('0')) \ memset_zero (p, _delta); \ else \ memset_space (p, _delta); \ } \ f; \ p += _n; \ } \ i += _incr; \ } while (0) #define cpy(n, s) \ add ((n), \ if (to_lowcase) \ memcpy_lowcase (p, (s), _n LOCALE_ARG); \ else if (to_uppcase) \ memcpy_uppcase (p, (s), _n LOCALE_ARG); \ else \ MEMCPY ((PTR) p, (const PTR) (s), _n)) #ifdef COMPILE_WIDE # ifndef USE_IN_EXTENDED_LOCALE_MODEL # undef __mbsrtowcs_l # define __mbsrtowcs_l(d, s, l, st, loc) __mbsrtowcs (d, s, l, st) # endif # define widen(os, ws, l) \ { \ mbstate_t __st; \ const char *__s = os; \ memset (&__st, '\0', sizeof (__st)); \ l = __mbsrtowcs_l (NULL, &__s, 0, &__st, loc); \ ws = (wchar_t *) alloca ((l + 1) * sizeof (wchar_t)); \ (void) __mbsrtowcs_l (ws, &__s, l, &__st, loc); \ } #endif #if defined _LIBC && defined USE_IN_EXTENDED_LOCALE_MODEL /* We use this code also for the extended locale handling where the function gets as an additional argument the locale which has to be used. To access the values we have to redefine the _NL_CURRENT macro. */ # define strftime __strftime_l # define wcsftime __wcsftime_l # undef _NL_CURRENT # define _NL_CURRENT(category, item) \ (current->values[_NL_ITEM_INDEX (item)].string) # define LOCALE_PARAM , loc # define LOCALE_ARG , loc # define LOCALE_PARAM_DECL __locale_t loc; # define LOCALE_PARAM_PROTO , __locale_t loc # define HELPER_LOCALE_ARG , current #else # define LOCALE_PARAM # define LOCALE_PARAM_PROTO # define LOCALE_ARG # define LOCALE_PARAM_DECL # ifdef _LIBC # define HELPER_LOCALE_ARG , _NL_CURRENT_DATA (LC_TIME) # else # define HELPER_LOCALE_ARG # endif #endif #ifdef COMPILE_WIDE # ifdef USE_IN_EXTENDED_LOCALE_MODEL # define TOUPPER(Ch, L) __towupper_l (Ch, L) # define TOLOWER(Ch, L) __towlower_l (Ch, L) # else # define TOUPPER(Ch, L) towupper (Ch) # define TOLOWER(Ch, L) towlower (Ch) # endif #else # ifdef _LIBC # ifdef USE_IN_EXTENDED_LOCALE_MODEL # define TOUPPER(Ch, L) __toupper_l (Ch, L) # define TOLOWER(Ch, L) __tolower_l (Ch, L) # else # define TOUPPER(Ch, L) toupper (Ch) # define TOLOWER(Ch, L) tolower (Ch) # endif # else # define TOUPPER(Ch, L) (islower (Ch) ? toupper (Ch) : (Ch)) # define TOLOWER(Ch, L) (isupper (Ch) ? tolower (Ch) : (Ch)) # endif #endif /* We don't use `isdigit' here since the locale dependent interpretation is not what we want here. We only need to accept the arabic digits in the ASCII range. One day there is perhaps a more reliable way to accept other sets of digits. */ #define ISDIGIT(Ch) ((unsigned int) (Ch) - L_('0') <= 9) static CHAR_T *memcpy_lowcase __P ((CHAR_T *dest, const CHAR_T *src, size_t len LOCALE_PARAM_PROTO)); static CHAR_T * memcpy_lowcase (dest, src, len LOCALE_PARAM) CHAR_T *dest; const CHAR_T *src; size_t len; LOCALE_PARAM_DECL { while (len-- > 0) dest[len] = TOLOWER ((UCHAR_T) src[len], loc); return dest; } static CHAR_T *memcpy_uppcase __P ((CHAR_T *dest, const CHAR_T *src, size_t len LOCALE_PARAM_PROTO)); static CHAR_T * memcpy_uppcase (dest, src, len LOCALE_PARAM) CHAR_T *dest; const CHAR_T *src; size_t len; LOCALE_PARAM_DECL { while (len-- > 0) dest[len] = TOUPPER ((UCHAR_T) src[len], loc); return dest; } #if ! HAVE_TM_GMTOFF /* Yield the difference between *A and *B, measured in seconds, ignoring leap seconds. */ # define tm_diff ftime_tm_diff static int tm_diff __P ((const struct tm *, const struct tm *)); static int tm_diff (a, b) const struct tm *a; const struct tm *b; { /* Compute intervening leap days correctly even if year is negative. Take care to avoid int overflow in leap day calculations, but it's OK to assume that A and B are close to each other. */ int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3); int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3); int a100 = a4 / 25 - (a4 % 25 < 0); int b100 = b4 / 25 - (b4 % 25 < 0); int a400 = a100 >> 2; int b400 = b100 >> 2; int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400); int years = a->tm_year - b->tm_year; int days = (365 * years + intervening_leap_days + (a->tm_yday - b->tm_yday)); return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour)) + (a->tm_min - b->tm_min)) + (a->tm_sec - b->tm_sec)); } #endif /* ! HAVE_TM_GMTOFF */ /* The number of days from the first day of the first ISO week of this year to the year day YDAY with week day WDAY. ISO weeks start on Monday; the first ISO week has the year's first Thursday. YDAY may be as small as YDAY_MINIMUM. */ #define ISO_WEEK_START_WDAY 1 /* Monday */ #define ISO_WEEK1_WDAY 4 /* Thursday */ #define YDAY_MINIMUM (-366) static int iso_week_days __P ((int, int)); #ifdef __GNUC__ __inline__ #endif static int iso_week_days (yday, wday) int yday; int wday; { /* Add enough to the first operand of % to make it nonnegative. */ int big_enough_multiple_of_7 = (-YDAY_MINIMUM / 7 + 2) * 7; return (yday - (yday - wday + ISO_WEEK1_WDAY + big_enough_multiple_of_7) % 7 + ISO_WEEK1_WDAY - ISO_WEEK_START_WDAY); } #if !(defined _NL_CURRENT || HAVE_STRFTIME) static CHAR_T const weekday_name[][10] = { L_("Sunday"), L_("Monday"), L_("Tuesday"), L_("Wednesday"), L_("Thursday"), L_("Friday"), L_("Saturday") }; static CHAR_T const month_name[][10] = { L_("January"), L_("February"), L_("March"), L_("April"), L_("May"), L_("June"), L_("July"), L_("August"), L_("September"), L_("October"), L_("November"), L_("December") }; #endif /* When compiling this file, GNU applications can #define my_strftime to a symbol (typically nstrftime) to get an extended strftime with extra arguments UT and NS. Emacs is a special case for now, but this Emacs-specific code can be removed once Emacs's config.h defines my_strftime. */ #if defined emacs && !defined my_strftime # define my_strftime nstrftime #endif #ifdef my_strftime # define extra_args , ut, ns # define extra_args_spec int ut; int ns; # define extra_args_spec_iso , int ut, int ns #else # ifdef COMPILE_WIDE # define my_strftime wcsftime # define nl_get_alt_digit _nl_get_walt_digit # else # define my_strftime strftime # define nl_get_alt_digit _nl_get_alt_digit # endif # define extra_args # define extra_args_spec # define extra_args_spec_iso /* We don't have this information in general. */ # define ut 0 # define ns 0 #endif #if !defined _LIBC && HAVE_TZNAME && HAVE_TZSET /* Solaris 2.5 tzset sometimes modifies the storage returned by localtime. Work around this bug by copying *tp before it might be munged. */ size_t _strftime_copytm __P ((char *, size_t, const char *, const struct tm * extra_args_spec_iso)); size_t my_strftime (s, maxsize, format, tp extra_args) CHAR_T *s; size_t maxsize; const CHAR_T *format; const struct tm *tp; extra_args_spec { struct tm tmcopy; tmcopy = *tp; return _strftime_copytm (s, maxsize, format, &tmcopy extra_args); } # undef my_strftime # define my_strftime _strftime_copytm #endif /* Write information from TP into S according to the format string FORMAT, writing no more that MAXSIZE characters (including the terminating '\0') and returning number of characters written. If S is NULL, nothing will be written anywhere, so to determine how many characters would be written, use NULL for S and (size_t) UINT_MAX for MAXSIZE. */ size_t my_strftime (s, maxsize, format, tp extra_args LOCALE_PARAM) CHAR_T *s; size_t maxsize; const CHAR_T *format; const struct tm *tp; extra_args_spec LOCALE_PARAM_DECL { #if defined _LIBC && defined USE_IN_EXTENDED_LOCALE_MODEL struct locale_data *const current = loc->__locales[LC_TIME]; #endif int hour12 = tp->tm_hour; #ifdef _NL_CURRENT /* We cannot make the following values variables since we must delay the evaluation of these values until really needed since some expressions might not be valid in every situation. The `struct tm' might be generated by a strptime() call that initialized only a few elements. Dereference the pointers only if the format requires this. Then it is ok to fail if the pointers are invalid. */ # define a_wkday \ ((const CHAR_T *) _NL_CURRENT (LC_TIME, NLW(ABDAY_1) + tp->tm_wday)) # define f_wkday \ ((const CHAR_T *) _NL_CURRENT (LC_TIME, NLW(DAY_1) + tp->tm_wday)) # define a_month \ ((const CHAR_T *) _NL_CURRENT (LC_TIME, NLW(ABMON_1) + tp->tm_mon)) # define f_month \ ((const CHAR_T *) _NL_CURRENT (LC_TIME, NLW(MON_1) + tp->tm_mon)) # define ampm \ ((const CHAR_T *) _NL_CURRENT (LC_TIME, tp->tm_hour > 11 \ ? NLW(PM_STR) : NLW(AM_STR))) # define aw_len STRLEN (a_wkday) # define am_len STRLEN (a_month) # define ap_len STRLEN (ampm) #else # if !HAVE_STRFTIME # define f_wkday (weekday_name[tp->tm_wday]) # define f_month (month_name[tp->tm_mon]) # define a_wkday f_wkday # define a_month f_month # define ampm (L_("AMPM") + 2 * (tp->tm_hour > 11)) size_t aw_len = 3; size_t am_len = 3; size_t ap_len = 2; # endif #endif const char *zone; size_t i = 0; CHAR_T *p = s; const CHAR_T *f; #if DO_MULTIBYTE && !defined COMPILE_WIDE const char *format_end = NULL; #endif zone = NULL; #if HAVE_TM_ZONE /* The POSIX test suite assumes that setting the environment variable TZ to a new value before calling strftime() will influence the result (the %Z format) even if the information in TP is computed with a totally different time zone. This is bogus: though POSIX allows bad behavior like this, POSIX does not require it. Do the right thing instead. */ zone = (const char *) tp->tm_zone; #endif #if HAVE_TZNAME if (ut) { if (! (zone && *zone)) zone = "GMT"; } else { /* POSIX.1 requires that local time zone information be used as though strftime called tzset. */ # if HAVE_TZSET tzset (); # endif } #endif if (hour12 > 12) hour12 -= 12; else if (hour12 == 0) hour12 = 12; for (f = format; *f != '\0'; ++f) { int pad = 0; /* Padding for number ('-', '_', or 0). */ int modifier; /* Field modifier ('E', 'O', or 0). */ int digits; /* Max digits for numeric format. */ int number_value; /* Numeric value to be printed. */ int negative_number; /* 1 if the number is negative. */ const CHAR_T *subfmt; CHAR_T *bufp; CHAR_T buf[1 + (sizeof (int) < sizeof (time_t) ? INT_STRLEN_BOUND (time_t) : INT_STRLEN_BOUND (int))]; int width = -1; int to_lowcase = 0; int to_uppcase = 0; int change_case = 0; int format_char; #if DO_MULTIBYTE && !defined COMPILE_WIDE switch (*f) { case L_('%'): break; case L_('\b'): case L_('\t'): case L_('\n'): case L_('\v'): case L_('\f'): case L_('\r'): case L_(' '): case L_('!'): case L_('"'): case L_('#'): case L_('&'): case L_('\''): case L_('('): case L_(')'): case L_('*'): case L_('+'): case L_(','): case L_('-'): case L_('.'): case L_('/'): case L_('0'): case L_('1'): case L_('2'): case L_('3'): case L_('4'): case L_('5'): case L_('6'): case L_('7'): case L_('8'): case L_('9'): case L_(':'): case L_(';'): case L_('<'): case L_('='): case L_('>'): case L_('?'): case L_('A'): case L_('B'): case L_('C'): case L_('D'): case L_('E'): case L_('F'): case L_('G'): case L_('H'): case L_('I'): case L_('J'): case L_('K'): case L_('L'): case L_('M'): case L_('N'): case L_('O'): case L_('P'): case L_('Q'): case L_('R'): case L_('S'): case L_('T'): case L_('U'): case L_('V'): case L_('W'): case L_('X'): case L_('Y'): case L_('Z'): case L_('['): case L_('\\'): case L_(']'): case L_('^'): case L_('_'): case L_('a'): case L_('b'): case L_('c'): case L_('d'): case L_('e'): case L_('f'): case L_('g'): case L_('h'): case L_('i'): case L_('j'): case L_('k'): case L_('l'): case L_('m'): case L_('n'): case L_('o'): case L_('p'): case L_('q'): case L_('r'): case L_('s'): case L_('t'): case L_('u'): case L_('v'): case L_('w'): case L_('x'): case L_('y'): case L_('z'): case L_('{'): case L_('|'): case L_('}'): case L_('~'): /* The C Standard requires these 98 characters (plus '%') to be in the basic execution character set. None of these characters can start a multibyte sequence, so they need not be analyzed further. */ add (1, *p = *f); continue; default: /* Copy this multibyte sequence until we reach its end, find an error, or come back to the initial shift state. */ { mbstate_t mbstate = mbstate_zero; size_t len = 0; size_t fsize; if (! format_end) format_end = f + strlen (f) + 1; fsize = format_end - f; do { size_t bytes = mbrlen (f + len, fsize - len, &mbstate); if (bytes == 0) break; if (bytes == (size_t) -2) { len += strlen (f + len); break; } if (bytes == (size_t) -1) { len++; break; } len += bytes; } while (! mbsinit (&mbstate)); cpy (len, f); f += len - 1; continue; } } #else /* ! DO_MULTIBYTE */ /* Either multibyte encodings are not supported, they are safe for formats, so any non-'%' byte can be copied through, or this is the wide character version. */ if (*f != L_('%')) { add (1, *p = *f); continue; } #endif /* ! DO_MULTIBYTE */ /* Check for flags that can modify a format. */ while (1) { switch (*++f) { /* This influences the number formats. */ case L_('_'): case L_('-'): case L_('0'): pad = *f; continue; /* This changes textual output. */ case L_('^'): to_uppcase = 1; continue; case L_('#'): change_case = 1; continue; default: break; } break; } /* As a GNU extension we allow to specify the field width. */ if (ISDIGIT (*f)) { width = 0; do { if (width > INT_MAX / 10 || (width == INT_MAX / 10 && *f - L_('0') > INT_MAX % 10)) /* Avoid overflow. */ width = INT_MAX; else { width *= 10; width += *f - L_('0'); } ++f; } while (ISDIGIT (*f)); } /* Check for modifiers. */ switch (*f) { case L_('E'): case L_('O'): modifier = *f++; break; default: modifier = 0; break; } /* Now do the specified format. */ format_char = *f; switch (format_char) { #define DO_NUMBER(d, v) \ digits = d > width ? d : width; \ number_value = v; goto do_number #define DO_NUMBER_SPACEPAD(d, v) \ digits = d > width ? d : width; \ number_value = v; goto do_number_spacepad case L_('%'): if (modifier != 0) goto bad_format; add (1, *p = *f); break; case L_('a'): if (modifier != 0) goto bad_format; if (change_case) { to_uppcase = 1; to_lowcase = 0; } #if defined _NL_CURRENT || !HAVE_STRFTIME cpy (aw_len, a_wkday); break; #else goto underlying_strftime; #endif case 'A': if (modifier != 0) goto bad_format; if (change_case) { to_uppcase = 1; to_lowcase = 0; } #if defined _NL_CURRENT || !HAVE_STRFTIME cpy (STRLEN (f_wkday), f_wkday); break; #else goto underlying_strftime; #endif case L_('b'): case L_('h'): if (change_case) { to_uppcase = 1; to_lowcase = 0; } if (modifier != 0) goto bad_format; #if defined _NL_CURRENT || !HAVE_STRFTIME cpy (am_len, a_month); break; #else goto underlying_strftime; #endif case L_('B'): if (modifier != 0) goto bad_format; if (change_case) { to_uppcase = 1; to_lowcase = 0; } #if defined _NL_CURRENT || !HAVE_STRFTIME cpy (STRLEN (f_month), f_month); break; #else goto underlying_strftime; #endif case L_('c'): if (modifier == L_('O')) goto bad_format; #ifdef _NL_CURRENT if (! (modifier == 'E' && (*(subfmt = (const CHAR_T *) _NL_CURRENT (LC_TIME, NLW(ERA_D_T_FMT))) != '\0'))) subfmt = (const CHAR_T *) _NL_CURRENT (LC_TIME, NLW(D_T_FMT)); #else # if HAVE_STRFTIME goto underlying_strftime; # else subfmt = L_("%a %b %e %H:%M:%S %Y"); # endif #endif subformat: { CHAR_T *old_start = p; size_t len = my_strftime (NULL, (size_t) -1, subfmt, tp extra_args LOCALE_ARG); add (len, my_strftime (p, maxsize - i, subfmt, tp extra_args LOCALE_ARG)); if (to_uppcase) while (old_start < p) { *old_start = TOUPPER ((UCHAR_T) *old_start, loc); ++old_start; } } break; #if HAVE_STRFTIME && ! (defined _NL_CURRENT && HAVE_STRUCT_ERA_ENTRY) underlying_strftime: { /* The relevant information is available only via the underlying strftime implementation, so use that. */ char ufmt[4]; char *u = ufmt; char ubuf[1024]; /* enough for any single format in practice */ size_t len; /* Make sure we're calling the actual underlying strftime. In some cases, config.h contains something like "#define strftime rpl_strftime". */ # ifdef strftime # undef strftime size_t strftime (); # endif *u++ = '%'; if (modifier != 0) *u++ = modifier; *u++ = format_char; *u = '\0'; len = strftime (ubuf, sizeof ubuf, ufmt, tp); if (len == 0 && ubuf[0] != '\0') return 0; cpy (len, ubuf); } break; #endif case L_('C'): if (modifier == L_('O')) goto bad_format; if (modifier == L_('E')) { #if HAVE_STRUCT_ERA_ENTRY struct era_entry *era = _nl_get_era_entry (tp HELPER_LOCALE_ARG); if (era) { # ifdef COMPILE_WIDE size_t len = __wcslen (era->era_wname); cpy (len, era->era_wname); # else size_t len = strlen (era->era_name); cpy (len, era->era_name); # endif break; } #else # if HAVE_STRFTIME goto underlying_strftime; # endif #endif } { int year = tp->tm_year + TM_YEAR_BASE; DO_NUMBER (1, year / 100 - (year % 100 < 0)); } case L_('x'): if (modifier == L_('O')) goto bad_format; #ifdef _NL_CURRENT if (! (modifier == L_('E') && (*(subfmt = (const CHAR_T *)_NL_CURRENT (LC_TIME, NLW(ERA_D_FMT))) != L_('\0')))) subfmt = (const CHAR_T *) _NL_CURRENT (LC_TIME, NLW(D_FMT)); goto subformat; #else # if HAVE_STRFTIME goto underlying_strftime; # else /* Fall through. */ # endif #endif case L_('D'): if (modifier != 0) goto bad_format; subfmt = L_("%m/%d/%y"); goto subformat; case L_('d'): if (modifier == L_('E')) goto bad_format; DO_NUMBER (2, tp->tm_mday); case L_('e'): if (modifier == L_('E')) goto bad_format; DO_NUMBER_SPACEPAD (2, tp->tm_mday); /* All numeric formats set DIGITS and NUMBER_VALUE and then jump to one of these two labels. */ do_number_spacepad: /* Force `_' flag unless overwritten by `0' flag. */ if (pad != L_('0')) pad = L_('_'); do_number: /* Format the number according to the MODIFIER flag. */ if (modifier == L_('O') && 0 <= number_value) { #ifdef _NL_CURRENT /* Get the locale specific alternate representation of the number NUMBER_VALUE. If none exist NULL is returned. */ const CHAR_T *cp = nl_get_alt_digit (number_value HELPER_LOCALE_ARG); if (cp != NULL) { size_t digitlen = STRLEN (cp); if (digitlen != 0) { cpy (digitlen, cp); break; } } #else # if HAVE_STRFTIME goto underlying_strftime; # endif #endif } { unsigned int u = number_value; bufp = buf + sizeof (buf) / sizeof (buf[0]); negative_number = number_value < 0; if (negative_number) u = -u; do *--bufp = u % 10 + L_('0'); while ((u /= 10) != 0); } do_number_sign_and_padding: if (negative_number) *--bufp = L_('-'); if (pad != L_('-')) { int padding = digits - (buf + (sizeof (buf) / sizeof (buf[0])) - bufp); if (padding > 0) { if (pad == L_('_')) { if ((size_t) padding >= maxsize - i) return 0; if (p) memset_space (p, padding); i += padding; width = width > padding ? width - padding : 0; } else { if ((size_t) digits >= maxsize - i) return 0; if (negative_number) { ++bufp; if (p) *p++ = L_('-'); ++i; } if (p) memset_zero (p, padding); i += padding; width = 0; } } } cpy (buf + sizeof (buf) / sizeof (buf[0]) - bufp, bufp); break; case L_('F'): if (modifier != 0) goto bad_format; subfmt = L_("%Y-%m-%d"); goto subformat; case L_('H'): if (modifier == L_('E')) goto bad_format; DO_NUMBER (2, tp->tm_hour); case L_('I'): if (modifier == L_('E')) goto bad_format; DO_NUMBER (2, hour12); case L_('k'): /* GNU extension. */ if (modifier == L_('E')) goto bad_format; DO_NUMBER_SPACEPAD (2, tp->tm_hour); case L_('l'): /* GNU extension. */ if (modifier == L_('E')) goto bad_format; DO_NUMBER_SPACEPAD (2, hour12); case L_('j'): if (modifier == L_('E')) goto bad_format; DO_NUMBER (3, 1 + tp->tm_yday); case L_('M'): if (modifier == L_('E')) goto bad_format; DO_NUMBER (2, tp->tm_min); case L_('m'): if (modifier == L_('E')) goto bad_format; DO_NUMBER (2, tp->tm_mon + 1); #ifndef _LIBC case L_('N'): /* GNU extension. */ if (modifier == L_('E')) goto bad_format; number_value = ns; if (width != -1) { /* Take an explicit width less than 9 as a precision. */ int j; for (j = width; j < 9; j++) number_value /= 10; } DO_NUMBER (9, number_value); #endif case L_('n'): add (1, *p = L_('\n')); break; case L_('P'): to_lowcase = 1; #if !defined _NL_CURRENT && HAVE_STRFTIME format_char = L_('p'); #endif /* FALLTHROUGH */ case L_('p'): if (change_case) { to_uppcase = 0; to_lowcase = 1; } #if defined _NL_CURRENT || !HAVE_STRFTIME cpy (ap_len, ampm); break; #else goto underlying_strftime; #endif case L_('R'): subfmt = L_("%H:%M"); goto subformat; case L_('r'): #ifdef _NL_CURRENT if (*(subfmt = (const CHAR_T *) _NL_CURRENT (LC_TIME, NLW(T_FMT_AMPM))) == L_('\0')) #endif subfmt = L_("%I:%M:%S %p"); goto subformat; case L_('S'): if (modifier == L_('E')) goto bad_format; DO_NUMBER (2, tp->tm_sec); case L_('s'): /* GNU extension. */ { struct tm ltm; time_t t; ltm = *tp; t = mktime (<m); /* Generate string value for T using time_t arithmetic; this works even if sizeof (long) < sizeof (time_t). */ bufp = buf + sizeof (buf) / sizeof (buf[0]); negative_number = t < 0; do { int d = t % 10; t /= 10; if (negative_number) { d = -d; /* Adjust if division truncates to minus infinity. */ if (0 < -1 % 10 && d < 0) { t++; d += 10; } } *--bufp = d + L_('0'); } while (t != 0); digits = 1; goto do_number_sign_and_padding; } case L_('X'): if (modifier == L_('O')) goto bad_format; #ifdef _NL_CURRENT if (! (modifier == L_('E') && (*(subfmt = (const CHAR_T *) _NL_CURRENT (LC_TIME, NLW(ERA_T_FMT))) != L_('\0')))) subfmt = (const CHAR_T *) _NL_CURRENT (LC_TIME, NLW(T_FMT)); goto subformat; #else # if HAVE_STRFTIME goto underlying_strftime; # else /* Fall through. */ # endif #endif case L_('T'): subfmt = L_("%H:%M:%S"); goto subformat; case L_('t'): add (1, *p = L_('\t')); break; case L_('u'): DO_NUMBER (1, (tp->tm_wday - 1 + 7) % 7 + 1); case L_('U'): if (modifier == L_('E')) goto bad_format; DO_NUMBER (2, (tp->tm_yday - tp->tm_wday + 7) / 7); case L_('V'): case L_('g'): case L_('G'): if (modifier == L_('E')) goto bad_format; { int year = tp->tm_year + TM_YEAR_BASE; int days = iso_week_days (tp->tm_yday, tp->tm_wday); if (days < 0) { /* This ISO week belongs to the previous year. */ year--; days = iso_week_days (tp->tm_yday + (365 + __isleap (year)), tp->tm_wday); } else { int d = iso_week_days (tp->tm_yday - (365 + __isleap (year)), tp->tm_wday); if (0 <= d) { /* This ISO week belongs to the next year. */ year++; days = d; } } switch (*f) { case L_('g'): DO_NUMBER (2, (year % 100 + 100) % 100); case L_('G'): DO_NUMBER (1, year); default: DO_NUMBER (2, days / 7 + 1); } } case L_('W'): if (modifier == L_('E')) goto bad_format; DO_NUMBER (2, (tp->tm_yday - (tp->tm_wday - 1 + 7) % 7 + 7) / 7); case L_('w'): if (modifier == L_('E')) goto bad_format; DO_NUMBER (1, tp->tm_wday); case L_('Y'): if (modifier == 'E') { #if HAVE_STRUCT_ERA_ENTRY struct era_entry *era = _nl_get_era_entry (tp HELPER_LOCALE_ARG); if (era) { # ifdef COMPILE_WIDE subfmt = era->era_wformat; # else subfmt = era->era_format; # endif goto subformat; } #else # if HAVE_STRFTIME goto underlying_strftime; # endif #endif } if (modifier == L_('O')) goto bad_format; else DO_NUMBER (1, tp->tm_year + TM_YEAR_BASE); case L_('y'): if (modifier == L_('E')) { #if HAVE_STRUCT_ERA_ENTRY struct era_entry *era = _nl_get_era_entry (tp HELPER_LOCALE_ARG); if (era) { int delta = tp->tm_year - era->start_date[0]; DO_NUMBER (1, (era->offset + delta * era->absolute_direction)); } #else # if HAVE_STRFTIME goto underlying_strftime; # endif #endif } DO_NUMBER (2, (tp->tm_year % 100 + 100) % 100); case L_('Z'): if (change_case) { to_uppcase = 0; to_lowcase = 1; } #if HAVE_TZNAME /* The tzset() call might have changed the value. */ if (!(zone && *zone) && tp->tm_isdst >= 0) zone = tzname[tp->tm_isdst]; #endif if (! zone) zone = ""; #ifdef COMPILE_WIDE { /* The zone string is always given in multibyte form. We have to transform it first. */ wchar_t *wczone; size_t len; widen (zone, wczone, len); cpy (len, wczone); } #else cpy (strlen (zone), zone); #endif break; case L_('z'): if (tp->tm_isdst < 0) break; { int diff; #if HAVE_TM_GMTOFF diff = tp->tm_gmtoff; #else if (ut) diff = 0; else { struct tm gtm; struct tm ltm; time_t lt; ltm = *tp; lt = mktime (<m); if (lt == (time_t) -1) { /* mktime returns -1 for errors, but -1 is also a valid time_t value. Check whether an error really occurred. */ struct tm tm; if (! my_strftime_localtime_r (<, &tm) || ((ltm.tm_sec ^ tm.tm_sec) | (ltm.tm_min ^ tm.tm_min) | (ltm.tm_hour ^ tm.tm_hour) | (ltm.tm_mday ^ tm.tm_mday) | (ltm.tm_mon ^ tm.tm_mon) | (ltm.tm_year ^ tm.tm_year))) break; } if (! my_strftime_gmtime_r (<, >m)) break; diff = tm_diff (<m, >m); } #endif if (diff < 0) { add (1, *p = L_('-')); diff = -diff; } else add (1, *p = L_('+')); diff /= 60; DO_NUMBER (4, (diff / 60) * 100 + diff % 60); } case L_('\0'): /* GNU extension: % at end of format. */ --f; /* Fall through. */ default: /* Unknown format; output the format, including the '%', since this is most likely the right thing to do if a multibyte string has been misparsed. */ bad_format: { int flen; for (flen = 1; f[1 - flen] != L_('%'); flen++) continue; cpy (flen, &f[1 - flen]); } break; } } if (p && maxsize != 0) *p = L_('\0'); return i; } #ifdef _LIBC libc_hidden_def (my_strftime) #endif #ifdef emacs /* For Emacs we have a separate interface which corresponds to the normal strftime function plus the ut argument, but without the ns argument. */ size_t emacs_strftimeu (s, maxsize, format, tp, ut) char *s; size_t maxsize; const char *format; const struct tm *tp; int ut; { return my_strftime (s, maxsize, format, tp, ut, 0); } #endif
/* Getopt for GNU. NOTE: getopt is now part of the C library, so if you don't know what "Keep this file name-space clean" means, talk to drepper@gnu.org before changing it! Copyright (C) 1987,88,89,90,91,92,93,94,95,96,98,99,2000,2001,2002 Free Software Foundation, Inc. This file is part of the GNU C Library. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>. Ditto for AIX 3.2 and <stdlib.h>. */ #ifndef _NO_PROTO # define _NO_PROTO #endif #ifdef HAVE_CONFIG_H # include <config.h> #endif #if !defined __STDC__ || !__STDC__ /* This is a separate conditional since some stdc systems reject `defined (const)'. */ # ifndef const # define const # endif #endif #include <stdio.h> /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #define GETOPT_INTERFACE_VERSION 2 #if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 # include <gnu-versions.h> # if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION # define ELIDE_CODE # endif #endif #ifndef ELIDE_CODE /* This needs to come after some library #include to get __GNU_LIBRARY__ defined. */ #ifdef __GNU_LIBRARY__ /* Don't include stdlib.h for non-GNU C libraries because some of them contain conflicting prototypes for getopt. */ # include <stdlib.h> # include <unistd.h> #endif /* GNU C library. */ #ifdef VMS # include <unixlib.h> # if HAVE_STRING_H - 0 # include <string.h> # endif #endif #ifdef _LIBC # include <libintl.h> #else /* This is for other GNU distributions with internationalized messages. */ # include "gettext.h" #endif #define _(msgid) gettext (msgid) #if defined _LIBC && defined USE_IN_LIBIO # include <wchar.h> #endif #ifndef attribute_hidden # define attribute_hidden #endif /* This version of `getopt' appears to the caller like standard Unix `getopt' but it behaves differently for the user, since it allows the user to intersperse the options with the other arguments. As `getopt' works, it permutes the elements of ARGV so that, when it is done, all the options precede everything else. Thus all application programs are extended to handle flexible argument order. Setting the environment variable POSIXLY_CORRECT disables permutation. Then the behavior is completely standard. GNU application programs can use a third alternative mode in which they can distinguish the relative order of options and other arguments. */ #include "getopt.h" /* For communication from `getopt' to the caller. When `getopt' finds an option that takes an argument, the argument value is returned here. Also, when `ordering' is RETURN_IN_ORDER, each non-option ARGV-element is returned here. */ char *optarg; /* Index in ARGV of the next element to be scanned. This is used for communication to and from the caller and for communication between successive calls to `getopt'. On entry to `getopt', zero means this is the first call; initialize. When `getopt' returns -1, this is the index of the first of the non-option elements that the caller should itself scan. Otherwise, `optind' communicates from one call to the next how much of ARGV has been scanned so far. */ /* 1003.2 says this must be 1 before any call. */ int optind = 1; /* Formerly, initialization of getopt depended on optind==0, which causes problems with re-calling getopt as programs generally don't know that. */ int __getopt_initialized attribute_hidden; /* The next char to be scanned in the option-element in which the last option character we returned was found. This allows us to pick up the scan where we left off. If this is zero, or a null string, it means resume the scan by advancing to the next ARGV-element. */ static char *nextchar; /* Callers store zero here to inhibit the error message for unrecognized options. */ int opterr = 1; /* Set to an option character which was unrecognized. This must be initialized on some systems to avoid linking in the system's own getopt implementation. */ int optopt = '?'; /* Describe how to deal with options that follow non-option ARGV-elements. If the caller did not specify anything, the default is REQUIRE_ORDER if the environment variable POSIXLY_CORRECT is defined, PERMUTE otherwise. REQUIRE_ORDER means don't recognize them as options; stop option processing when the first non-option is seen. This is what Unix does. This mode of operation is selected by either setting the environment variable POSIXLY_CORRECT, or using `+' as the first character of the list of option characters. PERMUTE is the default. We permute the contents of ARGV as we scan, so that eventually all the non-options are at the end. This allows options to be given in any order, even with programs that were not written to expect this. RETURN_IN_ORDER is an option available to programs that were written to expect options and other ARGV-elements in any order and that care about the ordering of the two. We describe each non-option ARGV-element as if it were the argument of an option with character code 1. Using `-' as the first character of the list of option characters selects this mode of operation. The special argument `--' forces an end of option-scanning regardless of the value of `ordering'. In the case of RETURN_IN_ORDER, only `--' can cause `getopt' to return -1 with `optind' != ARGC. */ static enum { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER } ordering; /* Value of POSIXLY_CORRECT environment variable. */ static char *posixly_correct; #ifdef __GNU_LIBRARY__ /* We want to avoid inclusion of string.h with non-GNU libraries because there are many ways it can cause trouble. On some systems, it contains special magic macros that don't work in GCC. */ # include <string.h> # define my_index strchr #else # if HAVE_STRING_H # include <string.h> # else # include <strings.h> # endif /* Avoid depending on library functions or files whose names are inconsistent. */ #ifndef getenv extern char *getenv (); #endif static char * my_index (str, chr) const char *str; int chr; { while (*str) { if (*str == chr) return (char *) str; str++; } return 0; } /* If using GCC, we can safely declare strlen this way. If not using GCC, it is ok not to declare it. */ #ifdef __GNUC__ /* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h. That was relevant to code that was here before. */ # if (!defined __STDC__ || !__STDC__) && !defined strlen /* gcc with -traditional declares the built-in strlen to return int, and has done so at least since version 2.4.5. -- rms. */ extern int strlen (const char *); # endif /* not __STDC__ */ #endif /* __GNUC__ */ #endif /* not __GNU_LIBRARY__ */ /* Handle permutation of arguments. */ /* Describe the part of ARGV that contains non-options that have been skipped. `first_nonopt' is the index in ARGV of the first of them; `last_nonopt' is the index after the last of them. */ static int first_nonopt; static int last_nonopt; #ifdef _LIBC /* Stored original parameters. XXX This is no good solution. We should rather copy the args so that we can compare them later. But we must not use malloc(3). */ extern int __libc_argc; extern char **__libc_argv; /* Bash 2.0 gives us an environment variable containing flags indicating ARGV elements that should not be considered arguments. */ # ifdef USE_NONOPTION_FLAGS /* Defined in getopt_init.c */ extern char *__getopt_nonoption_flags; static int nonoption_flags_max_len; static int nonoption_flags_len; # endif # ifdef USE_NONOPTION_FLAGS # define SWAP_FLAGS(ch1, ch2) \ if (nonoption_flags_len > 0) \ { \ char __tmp = __getopt_nonoption_flags[ch1]; \ __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ __getopt_nonoption_flags[ch2] = __tmp; \ } # else # define SWAP_FLAGS(ch1, ch2) # endif #else /* !_LIBC */ # define SWAP_FLAGS(ch1, ch2) #endif /* _LIBC */ /* Exchange two adjacent subsequences of ARGV. One subsequence is elements [first_nonopt,last_nonopt) which contains all the non-options that have been skipped so far. The other is elements [last_nonopt,optind), which contains all the options processed since those non-options were skipped. `first_nonopt' and `last_nonopt' are relocated so that they describe the new indices of the non-options in ARGV after they are moved. */ #if defined __STDC__ && __STDC__ static void exchange (char **); #endif static void exchange (argv) char **argv; { int bottom = first_nonopt; int middle = last_nonopt; int top = optind; char *tem; /* Exchange the shorter segment with the far end of the longer segment. That puts the shorter segment into the right place. It leaves the longer segment in the right place overall, but it consists of two parts that need to be swapped next. */ #if defined _LIBC && defined USE_NONOPTION_FLAGS /* First make sure the handling of the `__getopt_nonoption_flags' string can work normally. Our top argument must be in the range of the string. */ if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len) { /* We must extend the array. The user plays games with us and presents new arguments. */ char *new_str = malloc (top + 1); if (new_str == NULL) nonoption_flags_len = nonoption_flags_max_len = 0; else { memset (__mempcpy (new_str, __getopt_nonoption_flags, nonoption_flags_max_len), '\0', top + 1 - nonoption_flags_max_len); nonoption_flags_max_len = top + 1; __getopt_nonoption_flags = new_str; } } #endif while (top > middle && middle > bottom) { if (top - middle > middle - bottom) { /* Bottom segment is the short one. */ int len = middle - bottom; register int i; /* Swap it with the top part of the top segment. */ for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[top - (middle - bottom) + i]; argv[top - (middle - bottom) + i] = tem; SWAP_FLAGS (bottom + i, top - (middle - bottom) + i); } /* Exclude the moved bottom segment from further swapping. */ top -= len; } else { /* Top segment is the short one. */ int len = top - middle; register int i; /* Swap it with the bottom part of the bottom segment. */ for (i = 0; i < len; i++) { tem = argv[bottom + i]; argv[bottom + i] = argv[middle + i]; argv[middle + i] = tem; SWAP_FLAGS (bottom + i, middle + i); } /* Exclude the moved top segment from further swapping. */ bottom += len; } } /* Update records for the slots the non-options now occupy. */ first_nonopt += (optind - last_nonopt); last_nonopt = optind; } /* Initialize the internal data when the first call is made. */ #if defined __STDC__ && __STDC__ static const char *_getopt_initialize (int, char *const *, const char *); #endif static const char * _getopt_initialize (argc, argv, optstring) int argc; char *const *argv; const char *optstring; { /* Start processing options with ARGV-element 1 (since ARGV-element 0 is the program name); the sequence of previously skipped non-option ARGV-elements is empty. */ first_nonopt = last_nonopt = optind; nextchar = NULL; posixly_correct = getenv ("POSIXLY_CORRECT"); /* Determine how to handle the ordering of options and nonoptions. */ if (optstring[0] == '-') { ordering = RETURN_IN_ORDER; ++optstring; } else if (optstring[0] == '+') { ordering = REQUIRE_ORDER; ++optstring; } else if (posixly_correct != NULL) ordering = REQUIRE_ORDER; else ordering = PERMUTE; #if defined _LIBC && defined USE_NONOPTION_FLAGS if (posixly_correct == NULL && argc == __libc_argc && argv == __libc_argv) { if (nonoption_flags_max_len == 0) { if (__getopt_nonoption_flags == NULL || __getopt_nonoption_flags[0] == '\0') nonoption_flags_max_len = -1; else { const char *orig_str = __getopt_nonoption_flags; int len = nonoption_flags_max_len = strlen (orig_str); if (nonoption_flags_max_len < argc) nonoption_flags_max_len = argc; __getopt_nonoption_flags = (char *) malloc (nonoption_flags_max_len); if (__getopt_nonoption_flags == NULL) nonoption_flags_max_len = -1; else memset (__mempcpy (__getopt_nonoption_flags, orig_str, len), '\0', nonoption_flags_max_len - len); } } nonoption_flags_len = nonoption_flags_max_len; } else nonoption_flags_len = 0; #endif return optstring; } /* Scan elements of ARGV (whose length is ARGC) for option characters given in OPTSTRING. If an element of ARGV starts with '-', and is not exactly "-" or "--", then it is an option element. The characters of this element (aside from the initial '-') are option characters. If `getopt' is called repeatedly, it returns successively each of the option characters from each of the option elements. If `getopt' finds another option character, it returns that character, updating `optind' and `nextchar' so that the next call to `getopt' can resume the scan with the following option character or ARGV-element. If there are no more option characters, `getopt' returns -1. Then `optind' is the index in ARGV of the first ARGV-element that is not an option. (The ARGV-elements have been permuted so that those that are not options now come last.) OPTSTRING is a string containing the legitimate option characters. If an option character is seen that is not listed in OPTSTRING, return '?' after printing an error message. If you set `opterr' to zero, the error message is suppressed but we still return '?'. If a char in OPTSTRING is followed by a colon, that means it wants an arg, so the following text in the same ARGV-element, or the text of the following ARGV-element, is returned in `optarg'. Two colons mean an option that wants an optional arg; if there is text in the current ARGV-element, it is returned in `optarg', otherwise `optarg' is set to zero. If OPTSTRING starts with `-' or `+', it requests different methods of handling the non-option ARGV-elements. See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. Long-named options begin with `--' instead of `-'. Their names may be abbreviated as long as the abbreviation is unique or is an exact match for some defined option. If they have an argument, it follows the option name in the same ARGV-element, separated from the option name by a `=', or else the in next ARGV-element. When `getopt' finds a long-named option, it returns 0 if that option's `flag' field is nonzero, the value of the option's `val' field if the `flag' field is zero. The elements of ARGV aren't really const, because we permute them. But we pretend they're const in the prototype to be compatible with other systems. LONGOPTS is a vector of `struct option' terminated by an element containing a name which is zero. LONGIND returns the index in LONGOPT of the long-named option found. It is only valid when a long-named option has been found by the most recent call. If LONG_ONLY is nonzero, '-' as well as '--' can introduce long-named options. */ int _getopt_internal (argc, argv, optstring, longopts, longind, long_only) int argc; char *const *argv; const char *optstring; const struct option *longopts; int *longind; int long_only; { int print_errors = opterr; if (optstring[0] == ':') print_errors = 0; if (argc < 1) return -1; optarg = NULL; if (optind == 0 || !__getopt_initialized) { if (optind == 0) optind = 1; /* Don't scan ARGV[0], the program name. */ optstring = _getopt_initialize (argc, argv, optstring); __getopt_initialized = 1; } /* Test whether ARGV[optind] points to a non-option argument. Either it does not have option syntax, or there is an environment flag from the shell indicating it is not an option. The later information is only used when the used in the GNU libc. */ #if defined _LIBC && defined USE_NONOPTION_FLAGS # define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \ || (optind < nonoption_flags_len \ && __getopt_nonoption_flags[optind] == '1')) #else # define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0') #endif if (nextchar == NULL || *nextchar == '\0') { /* Advance to the next ARGV-element. */ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been moved back by the user (who may also have changed the arguments). */ if (last_nonopt > optind) last_nonopt = optind; if (first_nonopt > optind) first_nonopt = optind; if (ordering == PERMUTE) { /* If we have just processed some options following some non-options, exchange them so that the options come first. */ if (first_nonopt != last_nonopt && last_nonopt != optind) exchange ((char **) argv); else if (last_nonopt != optind) first_nonopt = optind; /* Skip any additional non-options and extend the range of non-options previously skipped. */ while (optind < argc && NONOPTION_P) optind++; last_nonopt = optind; } /* The special ARGV-element `--' means premature end of options. Skip it like a null option, then exchange with previous non-options as if it were an option, then skip everything else like a non-option. */ if (optind != argc && !strcmp (argv[optind], "--")) { optind++; if (first_nonopt != last_nonopt && last_nonopt != optind) exchange ((char **) argv); else if (first_nonopt == last_nonopt) first_nonopt = optind; last_nonopt = argc; optind = argc; } /* If we have done all the ARGV-elements, stop the scan and back over any non-options that we skipped and permuted. */ if (optind == argc) { /* Set the next-arg-index to point at the non-options that we previously skipped, so the caller will digest them. */ if (first_nonopt != last_nonopt) optind = first_nonopt; return -1; } /* If we have come to a non-option and did not permute it, either stop the scan or describe it to the caller and pass it by. */ if (NONOPTION_P) { if (ordering == REQUIRE_ORDER) return -1; optarg = argv[optind++]; return 1; } /* We have found another option-ARGV-element. Skip the initial punctuation. */ nextchar = (argv[optind] + 1 + (longopts != NULL && argv[optind][1] == '-')); } /* Decode the current option-ARGV-element. */ /* Check whether the ARGV-element is a long option. If long_only and the ARGV-element has the form "-f", where f is a valid short option, don't consider it an abbreviated form of a long option that starts with f. Otherwise there would be no way to give the -f short option. On the other hand, if there's a long option "fubar" and the ARGV-element is "-fu", do consider that an abbreviation of the long option, just like "--fu", and not "-f" with arg "u". This distinction seems to be the most useful approach. */ if (longopts != NULL && (argv[optind][1] == '-' || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1]))))) { char *nameend; const struct option *p; const struct option *pfound = NULL; int exact = 0; int ambig = 0; int indfound = -1; int option_index; for (nameend = nextchar; *nameend && *nameend != '='; nameend++) /* Do nothing. */ ; /* Test all long options for either exact match or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp (p->name, nextchar, nameend - nextchar)) { if ((unsigned int) (nameend - nextchar) == (unsigned int) strlen (p->name)) { /* Exact match found. */ pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { /* First nonexact match found. */ pfound = p; indfound = option_index; } else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val) /* Second or later nonexact match found. */ ambig = 1; } if (ambig && !exact) { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("%s: option `%s' is ambiguous\n"), argv[0], argv[optind]) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("%s: option `%s' is ambiguous\n"), argv[0], argv[optind]); #endif } nextchar += strlen (nextchar); optind++; optopt = 0; return '?'; } if (pfound != NULL) { option_index = indfound; optind++; if (*nameend) { /* Don't test has_arg with >, because some C compilers don't allow it to be used on enums. */ if (pfound->has_arg) optarg = nameend + 1; else { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; int n; #endif if (argv[optind - 1][1] == '-') { /* --option */ #if defined _LIBC && defined USE_IN_LIBIO n = __asprintf (&buf, _("\ %s: option `--%s' doesn't allow an argument\n"), argv[0], pfound->name); #else fprintf (stderr, _("\ %s: option `--%s' doesn't allow an argument\n"), argv[0], pfound->name); #endif } else { /* +option or -option */ #if defined _LIBC && defined USE_IN_LIBIO n = __asprintf (&buf, _("\ %s: option `%c%s' doesn't allow an argument\n"), argv[0], argv[optind - 1][0], pfound->name); #else fprintf (stderr, _("\ %s: option `%c%s' doesn't allow an argument\n"), argv[0], argv[optind - 1][0], pfound->name); #endif } #if defined _LIBC && defined USE_IN_LIBIO if (n >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #endif } nextchar += strlen (nextchar); optopt = pfound->val; return '?'; } } else if (pfound->has_arg == 1) { if (optind < argc) optarg = argv[optind++]; else { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("\ %s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("%s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]); #endif } nextchar += strlen (nextchar); optopt = pfound->val; return optstring[0] == ':' ? ':' : '?'; } } nextchar += strlen (nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } /* Can't find it as a long option. If this is not getopt_long_only, or the option starts with '--' or is not a valid short option, then it's an error. Otherwise interpret it as a short option. */ if (!long_only || argv[optind][1] == '-' || my_index (optstring, *nextchar) == NULL) { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; int n; #endif if (argv[optind][1] == '-') { /* --option */ #if defined _LIBC && defined USE_IN_LIBIO n = __asprintf (&buf, _("%s: unrecognized option `--%s'\n"), argv[0], nextchar); #else fprintf (stderr, _("%s: unrecognized option `--%s'\n"), argv[0], nextchar); #endif } else { /* +option or -option */ #if defined _LIBC && defined USE_IN_LIBIO n = __asprintf (&buf, _("%s: unrecognized option `%c%s'\n"), argv[0], argv[optind][0], nextchar); #else fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), argv[0], argv[optind][0], nextchar); #endif } #if defined _LIBC && defined USE_IN_LIBIO if (n >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #endif } nextchar = (char *) ""; optind++; optopt = 0; return '?'; } } /* Look at and handle the next short option-character. */ { char c = *nextchar++; char *temp = my_index (optstring, c); /* Increment `optind' when we start to process its last character. */ if (*nextchar == '\0') ++optind; if (temp == NULL || c == ':') { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; int n; #endif if (posixly_correct) { /* 1003.2 specifies the format of this message. */ #if defined _LIBC && defined USE_IN_LIBIO n = __asprintf (&buf, _("%s: illegal option -- %c\n"), argv[0], c); #else fprintf (stderr, _("%s: illegal option -- %c\n"), argv[0], c); #endif } else { #if defined _LIBC && defined USE_IN_LIBIO n = __asprintf (&buf, _("%s: invalid option -- %c\n"), argv[0], c); #else fprintf (stderr, _("%s: invalid option -- %c\n"), argv[0], c); #endif } #if defined _LIBC && defined USE_IN_LIBIO if (n >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #endif } optopt = c; return '?'; } /* Convenience. Treat POSIX -W foo same as long option --foo */ if (temp[0] == 'W' && temp[1] == ';') { char *nameend; const struct option *p; const struct option *pfound = NULL; int exact = 0; int ambig = 0; int indfound = 0; int option_index; /* This is an option that requires an argument. */ if (*nextchar != '\0') { optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ optind++; } else if (optind == argc) { if (print_errors) { /* 1003.2 specifies the format of this message. */ #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("%s: option requires an argument -- %c\n"), argv[0], c) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("%s: option requires an argument -- %c\n"), argv[0], c); #endif } optopt = c; if (optstring[0] == ':') c = ':'; else c = '?'; return c; } else /* We already incremented `optind' once; increment it again when taking next ARGV-elt as argument. */ optarg = argv[optind++]; /* optarg is now the argument, see if it's in the table of longopts. */ for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++) /* Do nothing. */ ; /* Test all long options for either exact match or abbreviated matches. */ for (p = longopts, option_index = 0; p->name; p++, option_index++) if (!strncmp (p->name, nextchar, nameend - nextchar)) { if ((unsigned int) (nameend - nextchar) == strlen (p->name)) { /* Exact match found. */ pfound = p; indfound = option_index; exact = 1; break; } else if (pfound == NULL) { /* First nonexact match found. */ pfound = p; indfound = option_index; } else /* Second or later nonexact match found. */ ambig = 1; } if (ambig && !exact) { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("%s: option `-W %s' is ambiguous\n"), argv[0], argv[optind]) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"), argv[0], argv[optind]); #endif } nextchar += strlen (nextchar); optind++; return '?'; } if (pfound != NULL) { option_index = indfound; if (*nameend) { /* Don't test has_arg with >, because some C compilers don't allow it to be used on enums. */ if (pfound->has_arg) optarg = nameend + 1; else { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("\ %s: option `-W %s' doesn't allow an argument\n"), argv[0], pfound->name) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("\ %s: option `-W %s' doesn't allow an argument\n"), argv[0], pfound->name); #endif } nextchar += strlen (nextchar); return '?'; } } else if (pfound->has_arg == 1) { if (optind < argc) optarg = argv[optind++]; else { if (print_errors) { #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("\ %s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("%s: option `%s' requires an argument\n"), argv[0], argv[optind - 1]); #endif } nextchar += strlen (nextchar); return optstring[0] == ':' ? ':' : '?'; } } nextchar += strlen (nextchar); if (longind != NULL) *longind = option_index; if (pfound->flag) { *(pfound->flag) = pfound->val; return 0; } return pfound->val; } nextchar = NULL; return 'W'; /* Let the application handle it. */ } if (temp[1] == ':') { if (temp[2] == ':') { /* This is an option that accepts an argument optionally. */ if (*nextchar != '\0') { optarg = nextchar; optind++; } else optarg = NULL; nextchar = NULL; } else { /* This is an option that requires an argument. */ if (*nextchar != '\0') { optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ optind++; } else if (optind == argc) { if (print_errors) { /* 1003.2 specifies the format of this message. */ #if defined _LIBC && defined USE_IN_LIBIO char *buf; if (__asprintf (&buf, _("\ %s: option requires an argument -- %c\n"), argv[0], c) >= 0) { if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s", buf); else fputs (buf, stderr); free (buf); } #else fprintf (stderr, _("%s: option requires an argument -- %c\n"), argv[0], c); #endif } optopt = c; if (optstring[0] == ':') c = ':'; else c = '?'; } else /* We already incremented `optind' once; increment it again when taking next ARGV-elt as argument. */ optarg = argv[optind++]; nextchar = NULL; } } return c; } } int getopt (argc, argv, optstring) int argc; char *const *argv; const char *optstring; { return _getopt_internal (argc, argv, optstring, (const struct option *) 0, (int *) 0, 0); } #endif /* Not ELIDE_CODE. */ #ifdef TEST /* Compile with -DTEST to make an executable for use in testing the above definition of `getopt'. */ int main (argc, argv) int argc; char **argv; { int c; int digit_optind = 0; while (1) { int this_option_optind = optind ? optind : 1; c = getopt (argc, argv, "abc:d:0123456789"); if (c == -1) break; switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (digit_optind != 0 && digit_optind != this_option_optind) printf ("digits occur in two different argv-elements.\n"); digit_optind = this_option_optind; printf ("option %c\n", c); break; case 'a': printf ("option a\n"); break; case 'b': printf ("option b\n"); break; case 'c': printf ("option c with value `%s'\n", optarg); break; case '?': break; default: printf ("?? getopt returned character code 0%o ??\n", c); } } if (optind < argc) { printf ("non-option ARGV-elements: "); while (optind < argc) printf ("%s ", argv[optind++]); printf ("\n"); } exit (0); } #endif /* TEST */
/* getopt_long and getopt_long_only entry points for GNU getopt. Copyright (C) 1987,88,89,90,91,92,93,94,96,97,98 Free Software Foundation, Inc. This file is part of the GNU C Library. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #ifdef _LIBC # include <getopt.h> #else # include "getopt.h" #endif #if !defined __STDC__ || !__STDC__ /* This is a separate conditional since some stdc systems reject `defined (const)'. */ #ifndef const #define const #endif #endif #include <stdio.h> /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #define GETOPT_INTERFACE_VERSION 2 #if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 #include <gnu-versions.h> #if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION #define ELIDE_CODE #endif #endif #ifndef ELIDE_CODE /* This needs to come after some library #include to get __GNU_LIBRARY__ defined. */ #ifdef __GNU_LIBRARY__ #include <stdlib.h> #endif #ifndef NULL #define NULL 0 #endif int getopt_long (argc, argv, options, long_options, opt_index) int argc; char *const *argv; const char *options; const struct option *long_options; int *opt_index; { return _getopt_internal (argc, argv, options, long_options, opt_index, 0); } /* Like getopt_long, but '-' as well as '--' can indicate a long option. If an option that starts with '-' (not '--') doesn't match a long option, but does match a short option, it is parsed as a short option instead. */ int getopt_long_only (argc, argv, options, long_options, opt_index) int argc; char *const *argv; const char *options; const struct option *long_options; int *opt_index; { return _getopt_internal (argc, argv, options, long_options, opt_index, 1); } # ifdef _LIBC libc_hidden_def (getopt_long) libc_hidden_def (getopt_long_only) # endif #endif /* Not ELIDE_CODE. */ #ifdef TEST #include <stdio.h> int main (argc, argv) int argc; char **argv; { int c; int digit_optind = 0; while (1) { int this_option_optind = optind ? optind : 1; int option_index = 0; static struct option long_options[] = { {"add", 1, 0, 0}, {"append", 0, 0, 0}, {"delete", 1, 0, 0}, {"verbose", 0, 0, 0}, {"create", 0, 0, 0}, {"file", 1, 0, 0}, {0, 0, 0, 0} }; c = getopt_long (argc, argv, "abc:d:0123456789", long_options, &option_index); if (c == -1) break; switch (c) { case 0: printf ("option %s", long_options[option_index].name); if (optarg) printf (" with arg %s", optarg); printf ("\n"); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (digit_optind != 0 && digit_optind != this_option_optind) printf ("digits occur in two different argv-elements.\n"); digit_optind = this_option_optind; printf ("option %c\n", c); break; case 'a': printf ("option a\n"); break; case 'b': printf ("option b\n"); break; case 'c': printf ("option c with value `%s'\n", optarg); break; case 'd': printf ("option d with value `%s'\n", optarg); break; case '?': break; default: printf ("?? getopt returned character code 0%o ??\n", c); } } if (optind < argc) { printf ("non-option ARGV-elements: "); while (optind < argc) printf ("%s ", argv[optind++]); printf ("\n"); } exit (0); } #endif /* TEST */
/* hash - hashing table processing. Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. Written by Jim Meyering, 1992. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* A generic hash table package. */ /* Define USE_OBSTACK to 1 if you want the allocator to use obstacks instead of malloc. If you change USE_OBSTACK, you have to recompile! */ #if HAVE_CONFIG_H # include <config.h> #endif #if HAVE_STDLIB_H # include <stdlib.h> #endif #if HAVE_STDBOOL_H # include <stdbool.h> #else typedef enum {false = 0, true = 1} bool; #endif #include <stdio.h> #include <assert.h> #ifndef HAVE_DECL_FREE "this configure-time declaration test was not run" #endif #if !HAVE_DECL_FREE void free (); #endif #ifndef HAVE_DECL_MALLOC "this configure-time declaration test was not run" #endif #if !HAVE_DECL_MALLOC char *malloc (); #endif #if USE_OBSTACK # include "obstack.h" # ifndef obstack_chunk_alloc # define obstack_chunk_alloc malloc # endif # ifndef obstack_chunk_free # define obstack_chunk_free free # endif #endif #include "hash.h" struct hash_table { /* The array of buckets starts at BUCKET and extends to BUCKET_LIMIT-1, for a possibility of N_BUCKETS. Among those, N_BUCKETS_USED buckets are not empty, there are N_ENTRIES active entries in the table. */ struct hash_entry *bucket; struct hash_entry *bucket_limit; unsigned n_buckets; unsigned n_buckets_used; unsigned n_entries; /* Tuning arguments, kept in a physicaly separate structure. */ const Hash_tuning *tuning; /* Three functions are given to `hash_initialize', see the documentation block for this function. In a word, HASHER randomizes a user entry into a number up from 0 up to some maximum minus 1; COMPARATOR returns true if two user entries compare equally; and DATA_FREER is the cleanup function for a user entry. */ Hash_hasher hasher; Hash_comparator comparator; Hash_data_freer data_freer; /* A linked list of freed struct hash_entry structs. */ struct hash_entry *free_entry_list; #if USE_OBSTACK /* Whenever obstacks are used, it is possible to allocate all overflowed entries into a single stack, so they all can be freed in a single operation. It is not clear if the speedup is worth the trouble. */ struct obstack entry_stack; #endif }; /* A hash table contains many internal entries, each holding a pointer to some user provided data (also called a user entry). An entry indistinctly refers to both the internal entry and its associated user entry. A user entry contents may be hashed by a randomization function (the hashing function, or just `hasher' for short) into a number (or `slot') between 0 and the current table size. At each slot position in the hash table, starts a linked chain of entries for which the user data all hash to this slot. A bucket is the collection of all entries hashing to the same slot. A good `hasher' function will distribute entries rather evenly in buckets. In the ideal case, the length of each bucket is roughly the number of entries divided by the table size. Finding the slot for a data is usually done in constant time by the `hasher', and the later finding of a precise entry is linear in time with the size of the bucket. Consequently, a larger hash table size (that is, a larger number of buckets) is prone to yielding shorter chains, *given* the `hasher' function behaves properly. Long buckets slow down the lookup algorithm. One might use big hash table sizes in hope to reduce the average length of buckets, but this might become inordinate, as unused slots in the hash table take some space. The best bet is to make sure you are using a good `hasher' function (beware that those are not that easy to write! :-), and to use a table size larger than the actual number of entries. */ /* If an insertion makes the ratio of nonempty buckets to table size larger than the growth threshold (a number between 0.0 and 1.0), then increase the table size by multiplying by the growth factor (a number greater than 1.0). The growth threshold defaults to 0.8, and the growth factor defaults to 1.414, meaning that the table will have doubled its size every second time 80% of the buckets get used. */ #define DEFAULT_GROWTH_THRESHOLD 0.8 #define DEFAULT_GROWTH_FACTOR 1.414 /* If a deletion empties a bucket and causes the ratio of used buckets to table size to become smaller than the shrink threshold (a number between 0.0 and 1.0), then shrink the table by multiplying by the shrink factor (a number greater than the shrink threshold but smaller than 1.0). The shrink threshold and factor default to 0.0 and 1.0, meaning that the table never shrinks. */ #define DEFAULT_SHRINK_THRESHOLD 0.0 #define DEFAULT_SHRINK_FACTOR 1.0 /* Use this to initialize or reset a TUNING structure to some sensible values. */ static const Hash_tuning default_tuning = { DEFAULT_SHRINK_THRESHOLD, DEFAULT_SHRINK_FACTOR, DEFAULT_GROWTH_THRESHOLD, DEFAULT_GROWTH_FACTOR, false }; /* Information and lookup. */ /* The following few functions provide information about the overall hash table organization: the number of entries, number of buckets and maximum length of buckets. */ /* Return the number of buckets in the hash table. The table size, the total number of buckets (used plus unused), or the maximum number of slots, are the same quantity. */ unsigned hash_get_n_buckets (const Hash_table *table) { return table->n_buckets; } /* Return the number of slots in use (non-empty buckets). */ unsigned hash_get_n_buckets_used (const Hash_table *table) { return table->n_buckets_used; } /* Return the number of active entries. */ unsigned hash_get_n_entries (const Hash_table *table) { return table->n_entries; } /* Return the length of the longest chain (bucket). */ unsigned hash_get_max_bucket_length (const Hash_table *table) { struct hash_entry *bucket; unsigned max_bucket_length = 0; for (bucket = table->bucket; bucket < table->bucket_limit; bucket++) { if (bucket->data) { struct hash_entry *cursor = bucket; unsigned bucket_length = 1; while (cursor = cursor->next, cursor) bucket_length++; if (bucket_length > max_bucket_length) max_bucket_length = bucket_length; } } return max_bucket_length; } /* Do a mild validation of a hash table, by traversing it and checking two statistics. */ bool hash_table_ok (const Hash_table *table) { struct hash_entry *bucket; unsigned n_buckets_used = 0; unsigned n_entries = 0; for (bucket = table->bucket; bucket < table->bucket_limit; bucket++) { if (bucket->data) { struct hash_entry *cursor = bucket; /* Count bucket head. */ n_buckets_used++; n_entries++; /* Count bucket overflow. */ while (cursor = cursor->next, cursor) n_entries++; } } if (n_buckets_used == table->n_buckets_used && n_entries == table->n_entries) return true; return false; } void hash_print_statistics (const Hash_table *table, FILE *stream) { unsigned n_entries = hash_get_n_entries (table); unsigned n_buckets = hash_get_n_buckets (table); unsigned n_buckets_used = hash_get_n_buckets_used (table); unsigned max_bucket_length = hash_get_max_bucket_length (table); fprintf (stream, "# entries: %u\n", n_entries); fprintf (stream, "# buckets: %u\n", n_buckets); fprintf (stream, "# buckets used: %u (%.2f%%)\n", n_buckets_used, (100.0 * n_buckets_used) / n_buckets); fprintf (stream, "max bucket length: %u\n", max_bucket_length); } /* If ENTRY matches an entry already in the hash table, return the entry from the table. Otherwise, return NULL. */ void * hash_lookup (const Hash_table *table, const void *entry) { struct hash_entry *bucket = table->bucket + table->hasher (entry, table->n_buckets); struct hash_entry *cursor; assert (bucket < table->bucket_limit); if (bucket->data == NULL) return NULL; for (cursor = bucket; cursor; cursor = cursor->next) if (table->comparator (entry, cursor->data)) return cursor->data; return NULL; } /* Walking. */ /* The functions in this page traverse the hash table and process the contained entries. For the traversal to work properly, the hash table should not be resized nor modified while any particular entry is being processed. In particular, entries should not be added or removed. */ /* Return the first data in the table, or NULL if the table is empty. */ void * hash_get_first (const Hash_table *table) { struct hash_entry *bucket; if (table->n_entries == 0) return NULL; for (bucket = table->bucket; bucket < table->bucket_limit; bucket++) if (bucket->data) return bucket->data; assert (0); return NULL; } /* Return the user data for the entry following ENTRY, where ENTRY has been returned by a previous call to either `hash_get_first' or `hash_get_next'. Return NULL if there are no more entries. */ void * hash_get_next (const Hash_table *table, const void *entry) { struct hash_entry *bucket = table->bucket + table->hasher (entry, table->n_buckets); struct hash_entry *cursor; assert (bucket < table->bucket_limit); /* Find next entry in the same bucket. */ for (cursor = bucket; cursor; cursor = cursor->next) if (cursor->data == entry && cursor->next) return cursor->next->data; /* Find first entry in any subsequent bucket. */ while (++bucket < table->bucket_limit) if (bucket->data) return bucket->data; /* None found. */ return NULL; } /* Fill BUFFER with pointers to active user entries in the hash table, then return the number of pointers copied. Do not copy more than BUFFER_SIZE pointers. */ unsigned hash_get_entries (const Hash_table *table, void **buffer, unsigned buffer_size) { unsigned counter = 0; struct hash_entry *bucket; struct hash_entry *cursor; for (bucket = table->bucket; bucket < table->bucket_limit; bucket++) { if (bucket->data) { for (cursor = bucket; cursor; cursor = cursor->next) { if (counter >= buffer_size) return counter; buffer[counter++] = cursor->data; } } } return counter; } /* Call a PROCESSOR function for each entry of a hash table, and return the number of entries for which the processor function returned success. A pointer to some PROCESSOR_DATA which will be made available to each call to the processor function. The PROCESSOR accepts two arguments: the first is the user entry being walked into, the second is the value of PROCESSOR_DATA as received. The walking continue for as long as the PROCESSOR function returns nonzero. When it returns zero, the walking is interrupted. */ unsigned hash_do_for_each (const Hash_table *table, Hash_processor processor, void *processor_data) { unsigned counter = 0; struct hash_entry *bucket; struct hash_entry *cursor; for (bucket = table->bucket; bucket < table->bucket_limit; bucket++) { if (bucket->data) { for (cursor = bucket; cursor; cursor = cursor->next) { if (!(*processor) (cursor->data, processor_data)) return counter; counter++; } } } return counter; } /* Allocation and clean-up. */ /* Return a hash index for a NUL-terminated STRING between 0 and N_BUCKETS-1. This is a convenience routine for constructing other hashing functions. */ #if USE_DIFF_HASH /* About hashings, Paul Eggert writes to me (FP), on 1994-01-01: "Please see B. J. McKenzie, R. Harries & T. Bell, Selecting a hashing algorithm, Software--practice & experience 20, 2 (Feb 1990), 209-224. Good hash algorithms tend to be domain-specific, so what's good for [diffutils'] io.c may not be good for your application." */ unsigned hash_string (const char *string, unsigned n_buckets) { # ifndef CHAR_BIT # define CHAR_BIT 8 # endif # define ROTATE_LEFT(Value, Shift) \ ((Value) << (Shift) | (Value) >> ((sizeof (unsigned) * CHAR_BIT) - (Shift))) # define HASH_ONE_CHAR(Value, Byte) \ ((Byte) + ROTATE_LEFT (Value, 7)) unsigned value = 0; for (; *string; string++) value = HASH_ONE_CHAR (value, *(const unsigned char *) string); return value % n_buckets; # undef ROTATE_LEFT # undef HASH_ONE_CHAR } #else /* not USE_DIFF_HASH */ /* This one comes from `recode', and performs a bit better than the above as per a few experiments. It is inspired from a hashing routine found in the very old Cyber `snoop', itself written in typical Greg Mansfield style. (By the way, what happened to this excellent man? Is he still alive?) */ unsigned hash_string (const char *string, unsigned n_buckets) { unsigned value = 0; while (*string) value = ((value * 31 + (int) *(const unsigned char *) string++) % n_buckets); return value; } #endif /* not USE_DIFF_HASH */ /* Return true if CANDIDATE is a prime number. CANDIDATE should be an odd number at least equal to 11. */ static bool is_prime (unsigned long candidate) { unsigned long divisor = 3; unsigned long square = divisor * divisor; while (square < candidate && (candidate % divisor)) { divisor++; square += 4 * divisor; divisor++; } return (candidate % divisor ? true : false); } /* Round a given CANDIDATE number up to the nearest prime, and return that prime. Primes lower than 10 are merely skipped. */ static unsigned long next_prime (unsigned long candidate) { /* Skip small primes. */ if (candidate < 10) candidate = 10; /* Make it definitely odd. */ candidate |= 1; while (!is_prime (candidate)) candidate += 2; return candidate; } void hash_reset_tuning (Hash_tuning *tuning) { *tuning = default_tuning; } /* For the given hash TABLE, check the user supplied tuning structure for reasonable values, and return true if there is no gross error with it. Otherwise, definitively reset the TUNING field to some acceptable default in the hash table (that is, the user loses the right of further modifying tuning arguments), and return false. */ static bool check_tuning (Hash_table *table) { const Hash_tuning *tuning = table->tuning; if (tuning->growth_threshold > 0.0 && tuning->growth_threshold < 1.0 && tuning->growth_factor > 1.0 && tuning->shrink_threshold >= 0.0 && tuning->shrink_threshold < 1.0 && tuning->shrink_factor > tuning->shrink_threshold && tuning->shrink_factor <= 1.0 && tuning->shrink_threshold < tuning->growth_threshold) return true; table->tuning = &default_tuning; return false; } /* Allocate and return a new hash table, or NULL upon failure. The initial number of buckets is automatically selected so as to _guarantee_ that you may insert at least CANDIDATE different user entries before any growth of the hash table size occurs. So, if have a reasonably tight a-priori upper bound on the number of entries you intend to insert in the hash table, you may save some table memory and insertion time, by specifying it here. If the IS_N_BUCKETS field of the TUNING structure is true, the CANDIDATE argument has its meaning changed to the wanted number of buckets. TUNING points to a structure of user-supplied values, in case some fine tuning is wanted over the default behavior of the hasher. If TUNING is NULL, the default tuning parameters are used instead. The user-supplied HASHER function should be provided. It accepts two arguments ENTRY and TABLE_SIZE. It computes, by hashing ENTRY contents, a slot number for that entry which should be in the range 0..TABLE_SIZE-1. This slot number is then returned. The user-supplied COMPARATOR function should be provided. It accepts two arguments pointing to user data, it then returns true for a pair of entries that compare equal, or false otherwise. This function is internally called on entries which are already known to hash to the same bucket index. The user-supplied DATA_FREER function, when not NULL, may be later called with the user data as an argument, just before the entry containing the data gets freed. This happens from within `hash_free' or `hash_clear'. You should specify this function only if you want these functions to free all of your `data' data. This is typically the case when your data is simply an auxiliary struct that you have malloc'd to aggregate several values. */ Hash_table * hash_initialize (unsigned candidate, const Hash_tuning *tuning, Hash_hasher hasher, Hash_comparator comparator, Hash_data_freer data_freer) { Hash_table *table; struct hash_entry *bucket; if (hasher == NULL || comparator == NULL) return NULL; table = (Hash_table *) malloc (sizeof (Hash_table)); if (table == NULL) return NULL; if (!tuning) tuning = &default_tuning; table->tuning = tuning; if (!check_tuning (table)) { /* Fail if the tuning options are invalid. This is the only occasion when the user gets some feedback about it. Once the table is created, if the user provides invalid tuning options, we silently revert to using the defaults, and ignore further request to change the tuning options. */ free (table); return NULL; } table->n_buckets = next_prime (tuning->is_n_buckets ? candidate : (unsigned) (candidate / tuning->growth_threshold)); table->bucket = (struct hash_entry *) malloc (table->n_buckets * sizeof (struct hash_entry)); if (table->bucket == NULL) { free (table); return NULL; } table->bucket_limit = table->bucket + table->n_buckets; for (bucket = table->bucket; bucket < table->bucket_limit; bucket++) { bucket->data = NULL; bucket->next = NULL; } table->n_buckets_used = 0; table->n_entries = 0; table->hasher = hasher; table->comparator = comparator; table->data_freer = data_freer; table->free_entry_list = NULL; #if USE_OBSTACK obstack_init (&table->entry_stack); #endif return table; } /* Make all buckets empty, placing any chained entries on the free list. Apply the user-specified function data_freer (if any) to the datas of any affected entries. */ void hash_clear (Hash_table *table) { struct hash_entry *bucket; for (bucket = table->bucket; bucket < table->bucket_limit; bucket++) { if (bucket->data) { struct hash_entry *cursor; struct hash_entry *next; /* Free the bucket overflow. */ for (cursor = bucket->next; cursor; cursor = next) { if (table->data_freer) (*table->data_freer) (cursor->data); cursor->data = NULL; next = cursor->next; /* Relinking is done one entry at a time, as it is to be expected that overflows are either rare or short. */ cursor->next = table->free_entry_list; table->free_entry_list = cursor; } /* Free the bucket head. */ if (table->data_freer) (*table->data_freer) (bucket->data); bucket->data = NULL; bucket->next = NULL; } } table->n_buckets_used = 0; table->n_entries = 0; } /* Reclaim all storage associated with a hash table. If a data_freer function has been supplied by the user when the hash table was created, this function applies it to the data of each entry before freeing that entry. */ void hash_free (Hash_table *table) { struct hash_entry *bucket; struct hash_entry *cursor; struct hash_entry *next; /* Call the user data_freer function. */ if (table->data_freer && table->n_entries) { for (bucket = table->bucket; bucket < table->bucket_limit; bucket++) { if (bucket->data) { for (cursor = bucket; cursor; cursor = cursor->next) { (*table->data_freer) (cursor->data); } } } } #if USE_OBSTACK obstack_free (&table->entry_stack, NULL); #else /* Free all bucket overflowed entries. */ for (bucket = table->bucket; bucket < table->bucket_limit; bucket++) { for (cursor = bucket->next; cursor; cursor = next) { next = cursor->next; free (cursor); } } /* Also reclaim the internal list of previously freed entries. */ for (cursor = table->free_entry_list; cursor; cursor = next) { next = cursor->next; free (cursor); } #endif /* Free the remainder of the hash table structure. */ free (table->bucket); free (table); } /* Insertion and deletion. */ /* Get a new hash entry for a bucket overflow, possibly by reclying a previously freed one. If this is not possible, allocate a new one. */ static struct hash_entry * allocate_entry (Hash_table *table) { struct hash_entry *new; if (table->free_entry_list) { new = table->free_entry_list; table->free_entry_list = new->next; } else { #if USE_OBSTACK new = (struct hash_entry *) obstack_alloc (&table->entry_stack, sizeof (struct hash_entry)); #else new = (struct hash_entry *) malloc (sizeof (struct hash_entry)); #endif } return new; } /* Free a hash entry which was part of some bucket overflow, saving it for later recycling. */ static void free_entry (Hash_table *table, struct hash_entry *entry) { entry->data = NULL; entry->next = table->free_entry_list; table->free_entry_list = entry; } /* This private function is used to help with insertion and deletion. When ENTRY matches an entry in the table, return a pointer to the corresponding user data and set *BUCKET_HEAD to the head of the selected bucket. Otherwise, return NULL. When DELETE is true and ENTRY matches an entry in the table, unlink the matching entry. */ static void * hash_find_entry (Hash_table *table, const void *entry, struct hash_entry **bucket_head, bool delete) { struct hash_entry *bucket = table->bucket + table->hasher (entry, table->n_buckets); struct hash_entry *cursor; assert (bucket < table->bucket_limit); *bucket_head = bucket; /* Test for empty bucket. */ if (bucket->data == NULL) return NULL; /* See if the entry is the first in the bucket. */ if ((*table->comparator) (entry, bucket->data)) { void *data = bucket->data; if (delete) { if (bucket->next) { struct hash_entry *next = bucket->next; /* Bump the first overflow entry into the bucket head, then save the previous first overflow entry for later recycling. */ *bucket = *next; free_entry (table, next); } else { bucket->data = NULL; } } return data; } /* Scan the bucket overflow. */ for (cursor = bucket; cursor->next; cursor = cursor->next) { if ((*table->comparator) (entry, cursor->next->data)) { void *data = cursor->next->data; if (delete) { struct hash_entry *next = cursor->next; /* Unlink the entry to delete, then save the freed entry for later recycling. */ cursor->next = next->next; free_entry (table, next); } return data; } } /* No entry found. */ return NULL; } /* For an already existing hash table, change the number of buckets through specifying CANDIDATE. The contents of the hash table are preserved. The new number of buckets is automatically selected so as to _guarantee_ that the table may receive at least CANDIDATE different user entries, including those already in the table, before any other growth of the hash table size occurs. If TUNING->IS_N_BUCKETS is true, then CANDIDATE specifies the exact number of buckets desired. */ bool hash_rehash (Hash_table *table, unsigned candidate) { Hash_table *new_table; struct hash_entry *bucket; struct hash_entry *cursor; struct hash_entry *next; new_table = hash_initialize (candidate, table->tuning, table->hasher, table->comparator, table->data_freer); if (new_table == NULL) return false; /* Merely reuse the extra old space into the new table. */ #if USE_OBSTACK obstack_free (&new_table->entry_stack, NULL); new_table->entry_stack = table->entry_stack; #endif new_table->free_entry_list = table->free_entry_list; for (bucket = table->bucket; bucket < table->bucket_limit; bucket++) if (bucket->data) for (cursor = bucket; cursor; cursor = next) { void *data = cursor->data; struct hash_entry *new_bucket = (new_table->bucket + new_table->hasher (data, new_table->n_buckets)); assert (new_bucket < new_table->bucket_limit); next = cursor->next; if (new_bucket->data) { if (cursor == bucket) { /* Allocate or recycle an entry, when moving from a bucket header into a bucket overflow. */ struct hash_entry *new_entry = allocate_entry (new_table); if (new_entry == NULL) return false; new_entry->data = data; new_entry->next = new_bucket->next; new_bucket->next = new_entry; } else { /* Merely relink an existing entry, when moving from a bucket overflow into a bucket overflow. */ cursor->next = new_bucket->next; new_bucket->next = cursor; } } else { /* Free an existing entry, when moving from a bucket overflow into a bucket header. Also take care of the simple case of moving from a bucket header into a bucket header. */ new_bucket->data = data; new_table->n_buckets_used++; if (cursor != bucket) free_entry (new_table, cursor); } } free (table->bucket); table->bucket = new_table->bucket; table->bucket_limit = new_table->bucket_limit; table->n_buckets = new_table->n_buckets; table->n_buckets_used = new_table->n_buckets_used; table->free_entry_list = new_table->free_entry_list; /* table->n_entries already holds its value. */ #if USE_OBSTACK table->entry_stack = new_table->entry_stack; #endif free (new_table); return true; } /* If ENTRY matches an entry already in the hash table, return the pointer to the entry from the table. Otherwise, insert ENTRY and return ENTRY. Return NULL if the storage required for insertion cannot be allocated. */ void * hash_insert (Hash_table *table, const void *entry) { void *data; struct hash_entry *bucket; assert (entry); /* cannot insert a NULL entry */ /* If there's a matching entry already in the table, return that. */ if ((data = hash_find_entry (table, entry, &bucket, false)) != NULL) return data; /* ENTRY is not matched, it should be inserted. */ if (bucket->data) { struct hash_entry *new_entry = allocate_entry (table); if (new_entry == NULL) return NULL; /* Add ENTRY in the overflow of the bucket. */ new_entry->data = (void *) entry; new_entry->next = bucket->next; bucket->next = new_entry; table->n_entries++; return (void *) entry; } /* Add ENTRY right in the bucket head. */ bucket->data = (void *) entry; table->n_entries++; table->n_buckets_used++; /* If the growth threshold of the buckets in use has been reached, increase the table size and rehash. There's no point in checking the number of entries: if the hashing function is ill-conditioned, rehashing is not likely to improve it. */ if (table->n_buckets_used > table->tuning->growth_threshold * table->n_buckets) { /* Check more fully, before starting real work. If tuning arguments became invalid, the second check will rely on proper defaults. */ check_tuning (table); if (table->n_buckets_used > table->tuning->growth_threshold * table->n_buckets) { const Hash_tuning *tuning = table->tuning; unsigned candidate = (unsigned) (tuning->is_n_buckets ? (table->n_buckets * tuning->growth_factor) : (table->n_buckets * tuning->growth_factor * tuning->growth_threshold)); /* If the rehash fails, arrange to return NULL. */ if (!hash_rehash (table, candidate)) entry = NULL; } } return (void *) entry; } /* If ENTRY is already in the table, remove it and return the just-deleted data (the user may want to deallocate its storage). If ENTRY is not in the table, don't modify the table and return NULL. */ void * hash_delete (Hash_table *table, const void *entry) { void *data; struct hash_entry *bucket; data = hash_find_entry (table, entry, &bucket, true); if (!data) return NULL; table->n_entries--; if (!bucket->data) { table->n_buckets_used--; /* If the shrink threshold of the buckets in use has been reached, rehash into a smaller table. */ if (table->n_buckets_used < table->tuning->shrink_threshold * table->n_buckets) { /* Check more fully, before starting real work. If tuning arguments became invalid, the second check will rely on proper defaults. */ check_tuning (table); if (table->n_buckets_used < table->tuning->shrink_threshold * table->n_buckets) { const Hash_tuning *tuning = table->tuning; unsigned candidate = (unsigned) (tuning->is_n_buckets ? table->n_buckets * tuning->shrink_factor : (table->n_buckets * tuning->shrink_factor * tuning->growth_threshold)); hash_rehash (table, candidate); } } } return data; } /* Testing. */ #if TESTING void hash_print (const Hash_table *table) { struct hash_entry *bucket; for (bucket = table->bucket; bucket < table->bucket_limit; bucket++) { struct hash_entry *cursor; if (bucket) printf ("%d:\n", bucket - table->bucket); for (cursor = bucket; cursor; cursor = cursor->next) { char *s = (char *) cursor->data; /* FIXME */ if (s) printf (" %s\n", s); } } } #endif /* TESTING */
/* hash-pjw.c -- compute a hash value from a NUL-terminated string. Copyright 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #include "hash-pjw.h" /* A hash function for NUL-terminated char* strings using the method described in Aho, Sethi, & Ullman, p 436. */ unsigned int hash_pjw (const void *x, unsigned int tablesize) { const char *s = x; unsigned int h = 0; unsigned int g; while (*s != 0) { h = (h << 4) + *s++; if ((g = h & (unsigned int) 0xf0000000) != 0) h = (h ^ (g >> 24)) ^ g; } return (h % tablesize); }
/* addext.c -- add an extension to a file name Copyright 1990, 1997, 1998, 1999, 2001, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@gnu.ai.mit.edu> and Paul Eggert */ #if HAVE_CONFIG_H # include <config.h> #endif #ifndef HAVE_DOS_FILE_NAMES # define HAVE_DOS_FILE_NAMES 0 #endif #ifndef HAVE_LONG_FILE_NAMES # define HAVE_LONG_FILE_NAMES 0 #endif #if HAVE_LIMITS_H # include <limits.h> #endif #ifndef _POSIX_NAME_MAX # define _POSIX_NAME_MAX 14 #endif #include <sys/types.h> #if HAVE_STRING_H # include <string.h> #else # include <strings.h> #endif #if HAVE_UNISTD_H # include <unistd.h> #endif #include <errno.h> #ifndef errno extern int errno; #endif #include "backupfile.h" #include "dirname.h" /* Append to FILENAME the extension EXT, unless the result would be too long, in which case just append the character E. */ void addext (char *filename, char const *ext, int e) { char *s = base_name (filename); size_t slen = base_len (s); size_t extlen = strlen (ext); size_t slen_max = HAVE_LONG_FILE_NAMES ? 255 : _POSIX_NAME_MAX; #if HAVE_PATHCONF && defined _PC_NAME_MAX if (_POSIX_NAME_MAX < slen + extlen || HAVE_DOS_FILE_NAMES) { /* The new base name is long enough to require a pathconf check. */ long name_max; errno = 0; if (s == filename) name_max = pathconf (".", _PC_NAME_MAX); else { char c = *s; if (! ISSLASH (c)) *s = 0; name_max = pathconf (filename, _PC_NAME_MAX); *s = c; } if (0 <= name_max || errno == 0) slen_max = name_max == (long) (size_t) name_max ? name_max : -1; } #endif if (HAVE_DOS_FILE_NAMES && slen_max <= 12) { /* Live within DOS's 8.3 limit. */ char *dot = strchr (s, '.'); if (dot) { slen -= dot + 1 - s; s = dot + 1; slen_max = 3; } else slen_max = 8; extlen = 9; /* Don't use EXT. */ } if (slen + extlen <= slen_max) strcpy (s + slen, ext); else { if (slen_max <= slen) slen = slen_max - 1; s[slen] = e; s[slen + 1] = 0; } }
/* argmatch.c -- find a match for a string in an array Copyright (C) 1990, 1998, 1999, 2001, 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@ai.mit.edu> Modified by Akim Demaille <demaille@inf.enst.fr> */ #if HAVE_CONFIG_H # include <config.h> #endif /* Specification. */ #include "argmatch.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include "gettext.h" #define _(msgid) gettext (msgid) #include "error.h" #include "quotearg.h" #include "quote.h" #include "unlocked-io.h" /* When reporting an invalid argument, show nonprinting characters by using the quoting style ARGMATCH_QUOTING_STYLE. Do not use literal_quoting_style. */ #ifndef ARGMATCH_QUOTING_STYLE # define ARGMATCH_QUOTING_STYLE locale_quoting_style #endif #ifndef EXIT_FAILURE # define EXIT_FAILURE 1 #endif /* Non failing version of argmatch call this function after failing. */ #ifndef ARGMATCH_DIE # define ARGMATCH_DIE exit (EXIT_FAILURE) #endif #ifdef ARGMATCH_DIE_DECL ARGMATCH_DIE_DECL; #endif static void __argmatch_die (void) { ARGMATCH_DIE; } /* Used by XARGMATCH and XARGCASEMATCH. See description in argmatch.h. Default to __argmatch_die, but allow caller to change this at run-time. */ argmatch_exit_fn argmatch_die = __argmatch_die; /* If ARG is an unambiguous match for an element of the null-terminated array ARGLIST, return the index in ARGLIST of the matched element, else -1 if it does not match any element or -2 if it is ambiguous (is a prefix of more than one element). If VALLIST is none null, use it to resolve ambiguities limited to synonyms, i.e., for "yes", "yop" -> 0 "no", "nope" -> 1 "y" is a valid argument, for `0', and "n" for `1'. */ int argmatch (const char *arg, const char *const *arglist, const char *vallist, size_t valsize) { int i; /* Temporary index in ARGLIST. */ size_t arglen; /* Length of ARG. */ int matchind = -1; /* Index of first nonexact match. */ int ambiguous = 0; /* If nonzero, multiple nonexact match(es). */ arglen = strlen (arg); /* Test all elements for either exact match or abbreviated matches. */ for (i = 0; arglist[i]; i++) { if (!strncmp (arglist[i], arg, arglen)) { if (strlen (arglist[i]) == arglen) /* Exact match found. */ return i; else if (matchind == -1) /* First nonexact match found. */ matchind = i; else { /* Second nonexact match found. */ if (vallist == NULL || memcmp (vallist + valsize * matchind, vallist + valsize * i, valsize)) { /* There is a real ambiguity, or we could not disambiguate. */ ambiguous = 1; } } } } if (ambiguous) return -2; else return matchind; } /* Error reporting for argmatch. CONTEXT is a description of the type of entity that was being matched. VALUE is the invalid value that was given. PROBLEM is the return value from argmatch. */ void argmatch_invalid (const char *context, const char *value, int problem) { char const *format = (problem == -1 ? _("invalid argument %s for %s") : _("ambiguous argument %s for %s")); error (0, 0, format, quotearg_n_style (0, ARGMATCH_QUOTING_STYLE, value), quote_n (1, context)); } /* List the valid arguments for argmatch. ARGLIST is the same as in argmatch. VALLIST is a pointer to an array of values. VALSIZE is the size of the elements of VALLIST */ void argmatch_valid (const char *const *arglist, const char *vallist, size_t valsize) { int i; const char *last_val = NULL; /* We try to put synonyms on the same line. The assumption is that synonyms follow each other */ fprintf (stderr, _("Valid arguments are:")); for (i = 0; arglist[i]; i++) if ((i == 0) || memcmp (last_val, vallist + valsize * i, valsize)) { fprintf (stderr, "\n - `%s'", arglist[i]); last_val = vallist + valsize * i; } else { fprintf (stderr, ", `%s'", arglist[i]); } putc ('\n', stderr); } /* Never failing versions of the previous functions. CONTEXT is the context for which argmatch is called (e.g., "--version-control", or "$VERSION_CONTROL" etc.). Upon failure, calls the (supposed never to return) function EXIT_FN. */ int __xargmatch_internal (const char *context, const char *arg, const char *const *arglist, const char *vallist, size_t valsize, argmatch_exit_fn exit_fn) { int res = argmatch (arg, arglist, vallist, valsize); if (res >= 0) /* Success. */ return res; /* We failed. Explain why. */ argmatch_invalid (context, arg, res); argmatch_valid (arglist, vallist, valsize); (*exit_fn) (); return -1; /* To please the compilers. */ } /* Look for VALUE in VALLIST, an array of objects of size VALSIZE and return the first corresponding argument in ARGLIST */ const char * argmatch_to_argument (const char *value, const char *const *arglist, const char *vallist, size_t valsize) { int i; for (i = 0; arglist[i]; i++) if (!memcmp (value, vallist + valsize * i, valsize)) return arglist[i]; return NULL; } #ifdef TEST /* * Based on "getversion.c" by David MacKenzie <djm@gnu.ai.mit.edu> */ char *program_name; extern const char *getenv (); /* When to make backup files. */ enum backup_type { /* Never make backups. */ none, /* Make simple backups of every file. */ simple, /* Make numbered backups of files that already have numbered backups, and simple backups of the others. */ numbered_existing, /* Make numbered backups of every file. */ numbered }; /* Two tables describing arguments (keys) and their corresponding values */ static const char *const backup_args[] = { "no", "none", "off", "simple", "never", "existing", "nil", "numbered", "t", 0 }; static const enum backup_type backup_vals[] = { none, none, none, simple, simple, numbered_existing, numbered_existing, numbered, numbered }; int main (int argc, const char *const *argv) { const char *cp; enum backup_type backup_type = none; program_name = (char *) argv[0]; if (argc > 2) { fprintf (stderr, "Usage: %s [VERSION_CONTROL]\n", program_name); exit (1); } if ((cp = getenv ("VERSION_CONTROL"))) backup_type = XARGMATCH ("$VERSION_CONTROL", cp, backup_args, backup_vals); if (argc == 2) backup_type = XARGMATCH (program_name, argv[1], backup_args, backup_vals); printf ("The version control is `%s'\n", ARGMATCH_TO_ARGUMENT (backup_type, backup_args, backup_vals)); return 0; } #endif
/* backupfile.c -- make Emacs style backup file names Copyright (C) 1990,91,92,93,94,95,96,97,98,99,2000, 2001, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@gnu.ai.mit.edu>. Some algorithms adapted from GNU Emacs. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #include <sys/types.h> #if HAVE_STRING_H # include <string.h> #else # include <strings.h> #endif #if HAVE_DIRENT_H # include <dirent.h> # define NLENGTH(direct) strlen ((direct)->d_name) #else # define dirent direct # define NLENGTH(direct) ((size_t) (direct)->d_namlen) # if HAVE_SYS_NDIR_H # include <sys/ndir.h> # endif # if HAVE_SYS_DIR_H # include <sys/dir.h> # endif # if HAVE_NDIR_H # include <ndir.h> # endif #endif #if CLOSEDIR_VOID /* Fake a return value. */ # define CLOSEDIR(d) (closedir (d), 0) #else # define CLOSEDIR(d) closedir (d) #endif #if HAVE_STDLIB_H # include <stdlib.h> #endif #ifndef HAVE_DECL_GETENV "this configure-time declaration test was not run" #endif #if !HAVE_DECL_GETENV char *getenv (); #endif #ifndef HAVE_DECL_MALLOC "this configure-time declaration test was not run" #endif #if !HAVE_DECL_MALLOC char *malloc (); #endif #if HAVE_DIRENT_H || HAVE_NDIR_H || HAVE_SYS_DIR_H || HAVE_SYS_NDIR_H # define HAVE_DIR 1 #else # define HAVE_DIR 0 #endif #if HAVE_LIMITS_H # include <limits.h> #endif #ifndef CHAR_BIT # define CHAR_BIT 8 #endif /* Upper bound on the string length of an integer converted to string. 302 / 1000 is ceil (log10 (2.0)). Subtract 1 for the sign bit; add 1 for integer division truncation; add 1 more for a minus sign. */ #define INT_STRLEN_BOUND(t) ((sizeof (t) * CHAR_BIT - 1) * 302 / 1000 + 2) /* ISDIGIT differs from isdigit, as follows: - Its arg may be any int or unsigned int; it need not be an unsigned char. - It's guaranteed to evaluate its argument exactly once. - It's typically faster. POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to ISDIGIT_LOCALE unless it's important to use the locale's definition of `digit' even when the host does not conform to POSIX. */ #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9) #if D_INO_IN_DIRENT # define REAL_DIR_ENTRY(dp) ((dp)->d_ino != 0) #else # define REAL_DIR_ENTRY(dp) 1 #endif #include "argmatch.h" #include "backupfile.h" #include "dirname.h" /* The extension added to file names to produce a simple (as opposed to numbered) backup file name. */ const char *simple_backup_suffix = "~"; static int max_backup_version PARAMS ((const char *, const char *)); static int version_number PARAMS ((const char *, const char *, size_t)); /* Return the name of the new backup file for file FILE, allocated with malloc. Return 0 if out of memory. FILE must not end with a '/' unless it is the root directory. Do not call this function if backup_type == none. */ char * find_backup_file_name (const char *file, enum backup_type backup_type) { size_t backup_suffix_size_max; size_t file_len = strlen (file); size_t numbered_suffix_size_max = INT_STRLEN_BOUND (int) + 4; char *s; const char *suffix = simple_backup_suffix; /* Allow room for simple or `.~N~' backups. */ backup_suffix_size_max = strlen (simple_backup_suffix) + 1; if (HAVE_DIR && backup_suffix_size_max < numbered_suffix_size_max) backup_suffix_size_max = numbered_suffix_size_max; s = malloc (file_len + 1 + backup_suffix_size_max + numbered_suffix_size_max); if (s) { #if HAVE_DIR if (backup_type != simple) { int highest_backup; size_t dirlen = dir_len (file); memcpy (s, file, dirlen); if (dirlen == FILESYSTEM_PREFIX_LEN (file)) s[dirlen++] = '.'; s[dirlen] = '\0'; highest_backup = max_backup_version (base_name (file), s); if (! (backup_type == numbered_existing && highest_backup == 0)) { char *numbered_suffix = s + (file_len + backup_suffix_size_max); sprintf (numbered_suffix, ".~%d~", highest_backup + 1); suffix = numbered_suffix; } } #endif /* HAVE_DIR */ strcpy (s, file); addext (s, suffix, '~'); } return s; } #if HAVE_DIR /* Return the number of the highest-numbered backup file for file FILE in directory DIR. If there are no numbered backups of FILE in DIR, or an error occurs reading DIR, return 0. */ static int max_backup_version (const char *file, const char *dir) { DIR *dirp; struct dirent *dp; int highest_version; int this_version; size_t file_name_length; dirp = opendir (dir); if (!dirp) return 0; highest_version = 0; file_name_length = base_len (file); while ((dp = readdir (dirp)) != 0) { if (!REAL_DIR_ENTRY (dp) || NLENGTH (dp) < file_name_length + 4) continue; this_version = version_number (file, dp->d_name, file_name_length); if (this_version > highest_version) highest_version = this_version; } if (CLOSEDIR (dirp)) return 0; return highest_version; } /* If BACKUP is a numbered backup of BASE, return its version number; otherwise return 0. BASE_LENGTH is the length of BASE. */ static int version_number (const char *base, const char *backup, size_t base_length) { int version; const char *p; version = 0; if (strncmp (base, backup, base_length) == 0 && backup[base_length] == '.' && backup[base_length + 1] == '~') { for (p = &backup[base_length + 2]; ISDIGIT (*p); ++p) version = version * 10 + *p - '0'; if (p[0] != '~' || p[1]) version = 0; } return version; } #endif /* HAVE_DIR */ static const char * const backup_args[] = { /* In a series of synonyms, present the most meaning full first, so that argmatch_valid be more readable. */ "none", "off", "simple", "never", "existing", "nil", "numbered", "t", 0 }; static const enum backup_type backup_types[] = { none, none, simple, simple, numbered_existing, numbered_existing, numbered, numbered }; /* Return the type of backup specified by VERSION. If VERSION is NULL or the empty string, return numbered_existing. If VERSION is invalid or ambiguous, fail with a diagnostic appropriate for the specified CONTEXT. Unambiguous abbreviations are accepted. */ enum backup_type get_version (const char *context, const char *version) { if (version == 0 || *version == 0) return numbered_existing; else return XARGMATCH (context, version, backup_args, backup_types); } /* Return the type of backup specified by VERSION. If VERSION is NULL, use the value of the envvar VERSION_CONTROL. If the specified string is invalid or ambiguous, fail with a diagnostic appropriate for the specified CONTEXT. Unambiguous abbreviations are accepted. */ enum backup_type xget_version (const char *context, const char *version) { if (version && *version) return get_version (context, version); else return get_version ("$VERSION_CONTROL", getenv ("VERSION_CONTROL")); }
/* basename.c -- return the last element in a path Copyright (C) 1990, 1998, 1999, 2000, 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #if STDC_HEADERS || HAVE_STRING_H # include <string.h> #endif #include "dirname.h" /* In general, we can't use the builtin `basename' function if available, since it has different meanings in different environments. In some environments the builtin `basename' modifies its argument. Return the address of the last file name component of NAME. If NAME has no file name components because it is all slashes, return NAME if it is empty, the address of its last slash otherwise. */ char * base_name (char const *name) { char const *base = name + FILESYSTEM_PREFIX_LEN (name); char const *p; for (p = base; *p; p++) { if (ISSLASH (*p)) { /* Treat multiple adjacent slashes like a single slash. */ do p++; while (ISSLASH (*p)); /* If the file name ends in slash, use the trailing slash as the basename if no non-slashes have been found. */ if (! *p) { if (ISSLASH (*base)) base = p - 1; break; } /* *P is a non-slash preceded by a slash. */ base = p; } } return (char *) base; } /* Return the length of of the basename NAME. Typically NAME is the value returned by base_name. Act like strlen (NAME), except omit redundant trailing slashes. */ size_t base_len (char const *name) { size_t len; for (len = strlen (name); 1 < len && ISSLASH (name[len - 1]); len--) continue; return len; }
/* Host name canonicalization Copyright (C) 1995, 1999, 2000 Free Software Foundation, Inc. Written by Miles Bader <miles@gnu.ai.mit.edu> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <sys/types.h> #ifdef HAVE_UNISTD_H # include <unistd.h> #endif #ifdef HAVE_STDLIB_H # include <stdlib.h> #endif #ifdef HAVE_STRING_H # include <string.h> #endif #ifdef HAVE_NETDB_H # include <netdb.h> #endif #ifdef HAVE_SYS_SOCKET_H # include <sys/socket.h> #endif #ifdef HAVE_NETINET_IN_H # include <netinet/in.h> #endif #ifdef HAVE_ARPA_INET_H # include <arpa/inet.h> #endif /* Returns the canonical hostname associated with HOST (allocated in a static buffer), or 0 if it can't be determined. */ char * canon_host (const char *host) { #ifdef HAVE_GETHOSTBYNAME struct hostent *he = gethostbyname (host); if (he) { # ifdef HAVE_GETHOSTBYADDR char *addr = 0; /* Try and get an ascii version of the numeric host address. */ switch (he->h_addrtype) { # ifdef HAVE_INET_NTOA case AF_INET: addr = inet_ntoa (*(struct in_addr *) he->h_addr); break; # endif /* HAVE_INET_NTOA */ } if (addr && strcmp (he->h_name, addr) == 0) { /* gethostbyname has returned a string representation of the IP address, for example, "127.0.0.1". So now, look up the host name via the address. Although it may seem reasonable to look up the host name via the address, we must not pass `he->h_addr' directly to gethostbyaddr because on some systems he->h_addr is located in a static library buffer that is reused in the gethostbyaddr call. Make a copy and use that instead. */ char *h_addr_copy = (char *) malloc (he->h_length); if (h_addr_copy == NULL) he = NULL; else { memcpy (h_addr_copy, he->h_addr, he->h_length); he = gethostbyaddr (h_addr_copy, he->h_length, he->h_addrtype); free (h_addr_copy); } } # endif /* HAVE_GETHOSTBYADDR */ if (he) return (char *) (he->h_name); } #endif /* HAVE_GETHOSTBYNAME */ return 0; } #ifdef TEST_CANON_HOST int main (int argc, char **argv) { int i; for (i = 1; i < argc; i++) { char *s = canon_host (argv[i]); printf ("%s: %s\n", argv[i], (s ? s : "<undef>")); } exit (0); } #endif /* TEST_CANON_HOST */
/* closeout.c - close standard output Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #if HAVE_STDLIB_H # include <stdlib.h> #endif #ifndef EXIT_FAILURE # define EXIT_FAILURE 1 #endif #include <stdio.h> #include <errno.h> #ifndef errno extern int errno; #endif #include "gettext.h" #define _(msgid) gettext (msgid) #include "closeout.h" #include "error.h" #include "quotearg.h" #include "unlocked-io.h" #include "__fpending.h" static int default_exit_status = EXIT_FAILURE; static const char *file_name; /* Set the value to be used for the exit status when close_stdout is called. This is useful when it is not convenient to call close_stdout_status, e.g., when close_stdout is called via atexit. */ void close_stdout_set_status (int status) { default_exit_status = status; } /* Set the file name to be reported in the event an error is detected by close_stdout_status. */ void close_stdout_set_file_name (const char *file) { file_name = file; } /* Close standard output, exiting with status STATUS on failure. If a program writes *anything* to stdout, that program should `fflush' stdout and make sure that it succeeds before exiting. Otherwise, suppose that you go to the extreme of checking the return status of every function that does an explicit write to stdout. The last printf can succeed in writing to the internal stream buffer, and yet the fclose(stdout) could still fail (due e.g., to a disk full error) when it tries to write out that buffered data. Thus, you would be left with an incomplete output file and the offending program would exit successfully. FIXME: note the fflush suggested above is implicit in the fclose we actually do below. Consider doing only the fflush and/or using setvbuf to inhibit buffering. Besides, it's wasteful to check the return value from every call that writes to stdout -- just let the internal stream state record the failure. That's what the ferror test is checking below. It's important to detect such failures and exit nonzero because many tools (most notably `make' and other build-management systems) depend on being able to detect failure in other tools via their exit status. */ void close_stdout_status (int status) { int e = ferror (stdout) ? 0 : -1; /* If the stream's error bit is clear and there is nothing to flush, then return right away. */ if (e && __fpending (stdout) == 0) return; if (fclose (stdout) != 0) e = errno; if (0 <= e) { char const *write_error = _("write error"); if (file_name) error (status, e, "%s: %s", quotearg_colon (file_name), write_error); else error (status, e, "%s", write_error); } } /* Close standard output, exiting with status EXIT_FAILURE on failure. */ void close_stdout (void) { close_stdout_status (default_exit_status); }
/* help detect directory cycles efficiently Copyright 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Jim Meyering */ #if HAVE_CONFIG_H # include <config.h> #endif #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <assert.h> #include <stdlib.h> #if HAVE_STDBOOL_H # include <stdbool.h> #else typedef enum {false = 0, true = 1} bool; #endif #include "cycle-check.h" #include "xalloc.h" #define SAME_INODE(Stat_buf_1, Stat_buf_2) \ ((Stat_buf_1).st_ino == (Stat_buf_2).st_ino \ && (Stat_buf_1).st_dev == (Stat_buf_2).st_dev) #define CC_MAGIC 9827862 static inline bool is_power_of_two (unsigned int i) { return (i & (i - 1)) == 0; } void cycle_check_init (struct cycle_check_state *state) { state->chdir_counter = 0; state->magic = CC_MAGIC; } /* In traversing a directory hierarchy, call this function once for each descending chdir call, with SB corresponding to the chdir operand. If SB corresponds to a directory that has already been seen, return true to indicate that there is a directory cycle. Note that this is done `lazily', which means that some of the directories in the cycle may be processed twice before the cycle is detected. */ bool cycle_check (struct cycle_check_state *state, struct stat const *sb) { assert (state->magic == CC_MAGIC); /* If the current directory ever happens to be the same as the one we last recorded for the cycle detection, then it's obviously part of a cycle. */ if (state->chdir_counter && SAME_INODE (*sb, state->dev_ino)) return true; /* If the number of `descending' chdir calls is a power of two, record the dev/ino of the current directory. */ if (is_power_of_two (++(state->chdir_counter))) { state->dev_ino.st_dev = sb->st_dev; state->dev_ino.st_ino = sb->st_ino; } return false; }
/* Diacritics processing for a few character codes. Copyright (C) 1990, 1991, 1992, 1993, 2000 Free Software Foundation, Inc. François Pinard <pinard@iro.umontreal.ca>, 1988. All this file is a temporary hack, waiting for locales in GNU. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include "diacrit.h" /* ISO 8859-1 Latin-1 code is used as the underlying character set. If MSDOS is defined, IBM-PC's character set code is used instead. */ /*--------------------------------------------------------------------. | For each alphabetic character, returns what it would be without its | | possible diacritic symbol. | `--------------------------------------------------------------------*/ const char diacrit_base[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 0, 0, 0, 0, 0, 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, 0, 0, #ifdef __MSDOS__ 'C', 'u', 'e', 'a', 'a', 'a', 'a', 'c', 'e', 'e', 'e', 'i', 'i', 'i', 'A', 'A', 'E', 'e', 'E', 'o', 'o', 'o', 'u', 'u', 'y', 'O', 'U', 0, 0, 0, 0, 0, 'a', 'i', 'o', 'u', 'n', 'N', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, #else 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'C', 'E', 'E', 'E', 'E', 'I', 'I', 'I', 'I', 0, 'N', 'O', 'O', 'O', 'O', 'O', 0, 'O', 'U', 'U', 'U', 'U', 'Y', 0, 0, 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'c', 'e', 'e', 'e', 'e', 'i', 'i', 'i', 'i', 0, 'n', 'o', 'o', 'o', 'o', 'o', 0, 'o', 'u', 'u', 'u', 'u', 'y', 0, 'y', #endif }; /*------------------------------------------------------------------------. | For each alphabetic character, returns a code of what its diacritic is, | | according to the following codes: 1 (eE) over aA for latin diphtongs; 2 | | (') acute accent; 3 (`) grave accent; 4 (^) circumflex accent; 5 (") | | umlaut or diaraesis; 6 (~) tilda; 7 (,) cedilla; 8 (o) covering degree | | symbol; 9 (|) slashed character. | `------------------------------------------------------------------------*/ const char diacrit_diac[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, #ifdef __MSDOS__ 7, 5, 2, 4, 5, 3, 8, 7, 4, 5, 3, 5, 4, 3, 5, 8, 2, 1, 1, 4, 5, 3, 4, 3, 5, 5, 5, 0, 0, 0, 0, 0, 2, 2, 2, 2, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, #else 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 4, 6, 5, 8, 1, 7, 3, 2, 4, 5, 3, 2, 4, 5, 0, 6, 3, 2, 4, 6, 5, 0, 9, 3, 2, 4, 5, 2, 0, 0, 3, 2, 4, 6, 5, 8, 1, 7, 3, 2, 4, 5, 3, 2, 4, 5, 0, 6, 3, 2, 4, 6, 5, 0, 9, 3, 2, 4, 5, 2, 0, 0, #endif };
/* dirname.c -- return all but the last element in a path Copyright 1990, 1998, 2000, 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #if STDC_HEADERS || HAVE_STRING_H # include <string.h> #endif #include "dirname.h" #include "xalloc.h" /* Return the length of `dirname (PATH)', or zero if PATH is in the working directory. Works properly even if there are trailing slashes (by effectively ignoring them). */ size_t dir_len (char const *path) { size_t prefix_length = FILESYSTEM_PREFIX_LEN (path); size_t length; /* Strip the basename and any redundant slashes before it. */ for (length = base_name (path) - path; prefix_length < length; length--) if (! ISSLASH (path[length - 1])) return length; /* But don't strip the only slash from "/". */ return prefix_length + ISSLASH (path[prefix_length]); } /* Return the leading directories part of PATH, allocated with xmalloc. Works properly even if there are trailing slashes (by effectively ignoring them). */ char * dir_name (char const *path) { size_t length = dir_len (path); int append_dot = (length == FILESYSTEM_PREFIX_LEN (path)); char *newpath = xmalloc (length + append_dot + 1); memcpy (newpath, path, length); if (append_dot) newpath[length++] = '.'; newpath[length] = 0; return newpath; } #ifdef TEST_DIRNAME /* Run the test like this (expect no output): gcc -DHAVE_CONFIG_H -DTEST_DIRNAME -I.. -O -Wall \ basename.c dirname.c xmalloc.c sed -n '/^BEGIN-DATA$/,/^END-DATA$/p' dirname.c|grep -v DATA|./a.out BEGIN-DATA foo//// . bar/foo//// bar foo/ . / / . . a . END-DATA */ # define MAX_BUFF_LEN 1024 # include <stdio.h> int main () { char buff[MAX_BUFF_LEN + 1]; buff[MAX_BUFF_LEN] = 0; while (fgets (buff, MAX_BUFF_LEN, stdin) && buff[0]) { char path[MAX_BUFF_LEN]; char expected_result[MAX_BUFF_LEN]; char const *result; sscanf (buff, "%s %s", path, expected_result); result = dir_name (path); if (strcmp (result, expected_result)) printf ("%s: got %s, expected %s\n", path, result, expected_result); } return 0; } #endif
/* Invoke dup, but avoid some glitches. Copyright (C) 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <errno.h> #ifndef errno extern int errno; #endif #if HAVE_FCNTL_H # include <fcntl.h> #endif #if HAVE_UNISTD_H # include <unistd.h> #endif #ifndef STDERR_FILENO # define STDERR_FILENO 2 #endif #include <unistd-safer.h> /* Like dup, but do not return STDIN_FILENO, STDOUT_FILENO, or STDERR_FILENO. */ int dup_safer (int fd) { #ifdef F_DUPFD return fcntl (fd, F_DUPFD, STDERR_FILENO + 1); #else int f = dup (fd); if (0 <= f && f <= STDERR_FILENO) { int f1 = dup_safer (f); int e = errno; close (f); errno = e; f = f1; } return f; #endif }
/* Error handler for noninteractive utilities Copyright (C) 1990-1998, 2000, 2001, 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@gnu.ai.mit.edu>. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #ifdef _LIBC # include <libintl.h> #else # include "gettext.h" #endif #ifdef _LIBC # include <wchar.h> # define mbsrtowcs __mbsrtowcs #endif #if HAVE_VPRINTF || HAVE_DOPRNT || _LIBC # if __STDC__ # include <stdarg.h> # define VA_START(args, lastarg) va_start(args, lastarg) # else # include <varargs.h> # define VA_START(args, lastarg) va_start(args) # endif #else # define va_alist a1, a2, a3, a4, a5, a6, a7, a8 # define va_dcl char *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8; #endif #if STDC_HEADERS || _LIBC # include <stdlib.h> # include <string.h> #else void exit (); #endif #include "error.h" #if !_LIBC # include "unlocked-io.h" #endif #ifndef _ # define _(String) String #endif /* If NULL, error will flush stdout, then print on stderr the program name, a colon and a space. Otherwise, error will call this function without parameters instead. */ void (*error_print_progname) ( #if __STDC__ - 0 void #endif ); /* This variable is incremented each time `error' is called. */ unsigned int error_message_count; #ifdef _LIBC /* In the GNU C library, there is a predefined variable for this. */ # define program_name program_invocation_name # include <errno.h> # include <libio/libioP.h> /* In GNU libc we want do not want to use the common name `error' directly. Instead make it a weak alias. */ extern void __error (int status, int errnum, const char *message, ...) __attribute__ ((__format__ (__printf__, 3, 4))); extern void __error_at_line (int status, int errnum, const char *file_name, unsigned int line_number, const char *message, ...) __attribute__ ((__format__ (__printf__, 5, 6)));; # define error __error # define error_at_line __error_at_line # include <libio/iolibio.h> # define fflush(s) INTUSE(_IO_fflush) (s) # undef putc # define putc(c, fp) INTUSE(_IO_putc) (c, fp) #else /* not _LIBC */ # if !HAVE_DECL_STRERROR_R && STRERROR_R_CHAR_P # ifndef HAVE_DECL_STRERROR_R "this configure-time declaration test was not run" # endif char *strerror_r (); # endif /* The calling program should define program_name and set it to the name of the executing program. */ extern char *program_name; # if HAVE_STRERROR_R || defined strerror_r # define __strerror_r strerror_r # else # if HAVE_STRERROR # ifndef HAVE_DECL_STRERROR "this configure-time declaration test was not run" # endif # if !HAVE_DECL_STRERROR char *strerror (); # endif # else static char * private_strerror (int errnum) { extern char *sys_errlist[]; extern int sys_nerr; if (errnum > 0 && errnum <= sys_nerr) return _(sys_errlist[errnum]); return _("Unknown system error"); } # define strerror private_strerror # endif /* HAVE_STRERROR */ # endif /* HAVE_STRERROR_R || defined strerror_r */ #endif /* not _LIBC */ static void print_errno_message (int errnum) { char const *s; #if defined HAVE_STRERROR_R || _LIBC char errbuf[1024]; # if STRERROR_R_CHAR_P || _LIBC s = __strerror_r (errnum, errbuf, sizeof errbuf); # else if (__strerror_r (errnum, errbuf, sizeof errbuf) == 0) s = errbuf; else s = 0; # endif #else s = strerror (errnum); #endif #if !_LIBC if (! s) s = _("Unknown system error"); #endif #if _LIBC if (_IO_fwide (stderr, 0) > 0) { __fwprintf (stderr, L": %s", s); return; } #endif fprintf (stderr, ": %s", s); } #ifdef VA_START static void error_tail (int status, int errnum, const char *message, va_list args) { # if HAVE_VPRINTF || _LIBC # if _LIBC if (_IO_fwide (stderr, 0) > 0) { # define ALLOCA_LIMIT 2000 size_t len = strlen (message) + 1; wchar_t *wmessage = NULL; mbstate_t st; size_t res; const char *tmp; do { if (len < ALLOCA_LIMIT) wmessage = (wchar_t *) alloca (len * sizeof (wchar_t)); else { if (wmessage != NULL && len / 2 < ALLOCA_LIMIT) wmessage = NULL; wmessage = (wchar_t *) realloc (wmessage, len * sizeof (wchar_t)); if (wmessage == NULL) { fputws_unlocked (L"out of memory\n", stderr); return; } } memset (&st, '\0', sizeof (st)); tmp =message; } while ((res = mbsrtowcs (wmessage, &tmp, len, &st)) == len); if (res == (size_t) -1) /* The string cannot be converted. */ wmessage = (wchar_t *) L"???"; __vfwprintf (stderr, wmessage, args); } else # endif vfprintf (stderr, message, args); # else _doprnt (message, args, stderr); # endif va_end (args); ++error_message_count; if (errnum) print_errno_message (errnum); # if _LIBC if (_IO_fwide (stderr, 0) > 0) putwc (L'\n', stderr); else # endif putc ('\n', stderr); fflush (stderr); if (status) exit (status); } #endif /* Print the program name and error message MESSAGE, which is a printf-style format string with optional args. If ERRNUM is nonzero, print its corresponding system error message. Exit with status STATUS if it is nonzero. */ /* VARARGS */ void #if defined VA_START && __STDC__ error (int status, int errnum, const char *message, ...) #else error (status, errnum, message, va_alist) int status; int errnum; char *message; va_dcl #endif { #ifdef VA_START va_list args; #endif fflush (stdout); #ifdef _LIBC _IO_flockfile (stderr); #endif if (error_print_progname) (*error_print_progname) (); else { #if _LIBC if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s: ", program_name); else #endif fprintf (stderr, "%s: ", program_name); } #ifdef VA_START VA_START (args, message); error_tail (status, errnum, message, args); #else fprintf (stderr, message, a1, a2, a3, a4, a5, a6, a7, a8); ++error_message_count; if (errnum) print_errno_message (errnum); putc ('\n', stderr); fflush (stderr); if (status) exit (status); #endif #ifdef _LIBC _IO_funlockfile (stderr); #endif } /* Sometimes we want to have at most one error per line. This variable controls whether this mode is selected or not. */ int error_one_per_line; void #if defined VA_START && __STDC__ error_at_line (int status, int errnum, const char *file_name, unsigned int line_number, const char *message, ...) #else error_at_line (status, errnum, file_name, line_number, message, va_alist) int status; int errnum; const char *file_name; unsigned int line_number; char *message; va_dcl #endif { #ifdef VA_START va_list args; #endif if (error_one_per_line) { static const char *old_file_name; static unsigned int old_line_number; if (old_line_number == line_number && (file_name == old_file_name || strcmp (old_file_name, file_name) == 0)) /* Simply return and print nothing. */ return; old_file_name = file_name; old_line_number = line_number; } fflush (stdout); #ifdef _LIBC _IO_flockfile (stderr); #endif if (error_print_progname) (*error_print_progname) (); else { #if _LIBC if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s: ", program_name); else #endif fprintf (stderr, "%s:", program_name); } if (file_name != NULL) { #if _LIBC if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s:%d: ", file_name, line_number); else #endif fprintf (stderr, "%s:%d: ", file_name, line_number); } #ifdef VA_START VA_START (args, message); error_tail (status, errnum, message, args); #else fprintf (stderr, message, a1, a2, a3, a4, a5, a6, a7, a8); ++error_message_count; if (errnum) print_errno_message (errnum); putc ('\n', stderr); fflush (stderr); if (status) exit (status); #endif #ifdef _LIBC _IO_funlockfile (stderr); #endif } #ifdef _LIBC /* Make the weak alias. */ # undef error # undef error_at_line weak_alias (__error, error) weak_alias (__error_at_line, error_at_line) #endif
/* exclude.c -- exclude file names Copyright (C) 1992, 1993, 1994, 1997, 1999, 2000, 2001, 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert <eggert@twinsun.com> */ #if HAVE_CONFIG_H # include <config.h> #endif #if HAVE_STDBOOL_H # include <stdbool.h> #else typedef enum {false = 0, true = 1} bool; #endif #include <errno.h> #ifndef errno extern int errno; #endif #include <stdio.h> #if HAVE_SYS_TYPES_H # include <sys/types.h> #endif #if HAVE_STDLIB_H # include <stdlib.h> #endif #if HAVE_STRING_H # include <string.h> #endif #if HAVE_STRINGS_H # include <strings.h> #endif #if HAVE_INTTYPES_H # include <inttypes.h> #else # if HAVE_STDINT_H # include <stdint.h> # endif #endif #include "exclude.h" #include "fnmatch.h" #include "unlocked-io.h" #include "xalloc.h" #ifndef SIZE_MAX # define SIZE_MAX ((size_t) -1) #endif /* Verify a requirement at compile-time (unlike assert, which is runtime). */ #define verify(name, assertion) struct name { char a[(assertion) ? 1 : -1]; } /* Non-GNU systems lack these options, so we don't need to check them. */ #ifndef FNM_CASEFOLD # define FNM_CASEFOLD 0 #endif #ifndef FNM_LEADING_DIR # define FNM_LEADING_DIR 0 #endif verify (EXCLUDE_macros_do_not_collide_with_FNM_macros, (((EXCLUDE_ANCHORED | EXCLUDE_INCLUDE | EXCLUDE_WILDCARDS) & (FNM_PATHNAME | FNM_NOESCAPE | FNM_PERIOD | FNM_LEADING_DIR | FNM_CASEFOLD)) == 0)); /* An exclude pattern-options pair. The options are fnmatch options ORed with EXCLUDE_* options. */ struct patopts { char const *pattern; int options; }; /* An exclude list, of pattern-options pairs. */ struct exclude { struct patopts *exclude; size_t exclude_alloc; size_t exclude_count; }; /* Return a newly allocated and empty exclude list. */ struct exclude * new_exclude (void) { struct exclude *ex = (struct exclude *) xmalloc (sizeof *ex); ex->exclude_count = 0; ex->exclude_alloc = (1 << 6); /* This must be a power of 2. */ ex->exclude = (struct patopts *) xmalloc (ex->exclude_alloc * sizeof ex->exclude[0]); return ex; } /* Free the storage associated with an exclude list. */ void free_exclude (struct exclude *ex) { free (ex->exclude); free (ex); } /* Return zero if PATTERN matches F, obeying OPTIONS, except that (unlike fnmatch) wildcards are disabled in PATTERN. */ static int fnmatch_no_wildcards (char const *pattern, char const *f, int options) { if (! (options & FNM_LEADING_DIR)) return ((options & FNM_CASEFOLD) ? strcasecmp (pattern, f) : strcmp (pattern, f)); else { size_t patlen = strlen (pattern); int r = ((options & FNM_CASEFOLD) ? strncasecmp (pattern, f, patlen) : strncmp (pattern, f, patlen)); if (! r) { r = f[patlen]; if (r == '/') r = 0; } return r; } } /* Return true if EX excludes F. */ bool excluded_filename (struct exclude const *ex, char const *f) { size_t exclude_count = ex->exclude_count; /* If no options are given, the default is to include. */ if (exclude_count == 0) return false; else { struct patopts const *exclude = ex->exclude; size_t i; /* Otherwise, the default is the opposite of the first option. */ bool excluded = !! (exclude[0].options & EXCLUDE_INCLUDE); /* Scan through the options, seeing whether they change F from excluded to included or vice versa. */ for (i = 0; i < exclude_count; i++) { char const *pattern = exclude[i].pattern; int options = exclude[i].options; if (excluded == !! (options & EXCLUDE_INCLUDE)) { int (*matcher) (char const *, char const *, int) = (options & EXCLUDE_WILDCARDS ? fnmatch : fnmatch_no_wildcards); bool matched = ((*matcher) (pattern, f, options) == 0); char const *p; if (! (options & EXCLUDE_ANCHORED)) for (p = f; *p && ! matched; p++) if (*p == '/' && p[1] != '/') matched = ((*matcher) (pattern, p + 1, options) == 0); excluded ^= matched; } } return excluded; } } /* Append to EX the exclusion PATTERN with OPTIONS. */ void add_exclude (struct exclude *ex, char const *pattern, int options) { struct patopts *patopts; if (ex->exclude_alloc <= ex->exclude_count) { size_t s = 2 * ex->exclude_alloc; if (! (0 < s && s <= SIZE_MAX / sizeof ex->exclude[0])) xalloc_die (); ex->exclude_alloc = s; ex->exclude = (struct patopts *) xrealloc (ex->exclude, s * sizeof ex->exclude[0]); } patopts = &ex->exclude[ex->exclude_count++]; patopts->pattern = pattern; patopts->options = options; } /* Use ADD_FUNC to append to EX the patterns in FILENAME, each with OPTIONS. LINE_END terminates each pattern in the file. Return -1 on failure, 0 on success. */ int add_exclude_file (void (*add_func) (struct exclude *, char const *, int), struct exclude *ex, char const *filename, int options, char line_end) { bool use_stdin = filename[0] == '-' && !filename[1]; FILE *in; char *buf; char *p; char const *pattern; char const *lim; size_t buf_alloc = (1 << 10); /* This must be a power of two. */ size_t buf_count = 0; int c; int e = 0; if (use_stdin) in = stdin; else if (! (in = fopen (filename, "r"))) return -1; buf = xmalloc (buf_alloc); while ((c = getc (in)) != EOF) { buf[buf_count++] = c; if (buf_count == buf_alloc) { buf_alloc *= 2; if (! buf_alloc) xalloc_die (); buf = xrealloc (buf, buf_alloc); } } if (ferror (in)) e = errno; if (!use_stdin && fclose (in) != 0) e = errno; buf = xrealloc (buf, buf_count + 1); for (pattern = p = buf, lim = buf + buf_count; p <= lim; p++) if (p < lim ? *p == line_end : buf < p && p[-1]) { *p = '\0'; (*add_func) (ex, pattern, options); pattern = p + 1; } errno = e; return e ? -1 : 0; }
/* Failure exit status Copyright (C) 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #if HAVE_STDLIB_H # include <stdlib.h> #endif #ifndef EXIT_FAILURE # define EXIT_FAILURE 1 #endif int volatile exit_failure = EXIT_FAILURE;
/* filemode.c -- make a string describing file modes Copyright (C) 1985, 1990, 1993, 1998-2000 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <sys/types.h> #include <sys/stat.h> #include "filemode.h" #if !S_IRUSR # if S_IREAD # define S_IRUSR S_IREAD # else # define S_IRUSR 00400 # endif #endif #if !S_IWUSR # if S_IWRITE # define S_IWUSR S_IWRITE # else # define S_IWUSR 00200 # endif #endif #if !S_IXUSR # if S_IEXEC # define S_IXUSR S_IEXEC # else # define S_IXUSR 00100 # endif #endif #if !S_IRGRP # define S_IRGRP (S_IRUSR >> 3) #endif #if !S_IWGRP # define S_IWGRP (S_IWUSR >> 3) #endif #if !S_IXGRP # define S_IXGRP (S_IXUSR >> 3) #endif #if !S_IROTH # define S_IROTH (S_IRUSR >> 6) #endif #if !S_IWOTH # define S_IWOTH (S_IWUSR >> 6) #endif #if !S_IXOTH # define S_IXOTH (S_IXUSR >> 6) #endif #ifdef STAT_MACROS_BROKEN # undef S_ISBLK # undef S_ISCHR # undef S_ISDIR # undef S_ISFIFO # undef S_ISLNK # undef S_ISMPB # undef S_ISMPC # undef S_ISNWK # undef S_ISREG # undef S_ISSOCK #endif /* STAT_MACROS_BROKEN. */ #if !defined S_ISBLK && defined S_IFBLK # define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) #endif #if !defined S_ISCHR && defined S_IFCHR # define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) #endif #if !defined S_ISDIR && defined S_IFDIR # define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif #if !defined S_ISREG && defined S_IFREG # define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #endif #if !defined S_ISFIFO && defined S_IFIFO # define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) #endif #if !defined S_ISLNK && defined S_IFLNK # define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) #endif #if !defined S_ISSOCK && defined S_IFSOCK # define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) #endif #if !defined S_ISMPB && defined S_IFMPB /* V7 */ # define S_ISMPB(m) (((m) & S_IFMT) == S_IFMPB) # define S_ISMPC(m) (((m) & S_IFMT) == S_IFMPC) #endif #if !defined S_ISNWK && defined S_IFNWK /* HP/UX */ # define S_ISNWK(m) (((m) & S_IFMT) == S_IFNWK) #endif #if !defined S_ISDOOR && defined S_IFDOOR /* Solaris 2.5 and up */ # define S_ISDOOR(m) (((m) & S_IFMT) == S_IFDOOR) #endif #if !defined S_ISCTG && defined S_IFCTG /* MassComp */ # define S_ISCTG(m) (((m) & S_IFMT) == S_IFCTG) #endif /* Set the 's' and 't' flags in file attributes string CHARS, according to the file mode BITS. */ static void setst (mode_t bits, char *chars) { #ifdef S_ISUID if (bits & S_ISUID) { if (chars[3] != 'x') /* Set-uid, but not executable by owner. */ chars[3] = 'S'; else chars[3] = 's'; } #endif #ifdef S_ISGID if (bits & S_ISGID) { if (chars[6] != 'x') /* Set-gid, but not executable by group. */ chars[6] = 'S'; else chars[6] = 's'; } #endif #ifdef S_ISVTX if (bits & S_ISVTX) { if (chars[9] != 'x') /* Sticky, but not executable by others. */ chars[9] = 'T'; else chars[9] = 't'; } #endif } /* Return a character indicating the type of file described by file mode BITS: 'd' for directories 'D' for doors 'b' for block special files 'c' for character special files 'n' for network special files 'm' for multiplexor files 'M' for an off-line (regular) file 'l' for symbolic links 's' for sockets 'p' for fifos 'C' for contigous data files '-' for regular files '?' for any other file type. */ static char ftypelet (mode_t bits) { #ifdef S_ISBLK if (S_ISBLK (bits)) return 'b'; #endif if (S_ISCHR (bits)) return 'c'; if (S_ISDIR (bits)) return 'd'; if (S_ISREG (bits)) return '-'; #ifdef S_ISFIFO if (S_ISFIFO (bits)) return 'p'; #endif #ifdef S_ISLNK if (S_ISLNK (bits)) return 'l'; #endif #ifdef S_ISSOCK if (S_ISSOCK (bits)) return 's'; #endif #ifdef S_ISMPC if (S_ISMPC (bits)) return 'm'; #endif #ifdef S_ISNWK if (S_ISNWK (bits)) return 'n'; #endif #ifdef S_ISDOOR if (S_ISDOOR (bits)) return 'D'; #endif #ifdef S_ISCTG if (S_ISCTG (bits)) return 'C'; #endif /* The following two tests are for Cray DMF (Data Migration Facility), which is a HSM file system. A migrated file has a `st_dm_mode' that is different from the normal `st_mode', so any tests for migrated files should use the former. */ #ifdef S_ISOFD if (S_ISOFD (bits)) /* off line, with data */ return 'M'; #endif #ifdef S_ISOFL /* off line, with no data */ if (S_ISOFL (bits)) return 'M'; #endif return '?'; } /* Like filemodestring, but only the relevant part of the `struct stat' is given as an argument. */ void mode_string (mode_t mode, char *str) { str[0] = ftypelet (mode); str[1] = mode & S_IRUSR ? 'r' : '-'; str[2] = mode & S_IWUSR ? 'w' : '-'; str[3] = mode & S_IXUSR ? 'x' : '-'; str[4] = mode & S_IRGRP ? 'r' : '-'; str[5] = mode & S_IWGRP ? 'w' : '-'; str[6] = mode & S_IXGRP ? 'x' : '-'; str[7] = mode & S_IROTH ? 'r' : '-'; str[8] = mode & S_IWOTH ? 'w' : '-'; str[9] = mode & S_IXOTH ? 'x' : '-'; setst (mode, str); } /* filemodestring - fill in string STR with an ls-style ASCII representation of the st_mode field of file stats block STATP. 10 characters are stored in STR; no terminating null is added. The characters stored in STR are: 0 File type. 'd' for directory, 'c' for character special, 'b' for block special, 'm' for multiplex, 'l' for symbolic link, 's' for socket, 'p' for fifo, '-' for regular, '?' for any other file type 1 'r' if the owner may read, '-' otherwise. 2 'w' if the owner may write, '-' otherwise. 3 'x' if the owner may execute, 's' if the file is set-user-id, '-' otherwise. 'S' if the file is set-user-id, but the execute bit isn't set. 4 'r' if group members may read, '-' otherwise. 5 'w' if group members may write, '-' otherwise. 6 'x' if group members may execute, 's' if the file is set-group-id, '-' otherwise. 'S' if it is set-group-id but not executable. 7 'r' if any user may read, '-' otherwise. 8 'w' if any user may write, '-' otherwise. 9 'x' if any user may execute, 't' if the file is "sticky" (will be retained in swap space after execution), '-' otherwise. 'T' if the file is sticky but not executable. */ void filemodestring (struct stat *statp, char *str) { mode_string (statp->st_mode, str); }
/* __fpending.c -- return the number of pending output bytes on a stream Copyright (C) 2000 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Jim Meyering. */ #if HAVE_CONFIG_H # include <config.h> #endif #include "__fpending.h" size_t __fpending (FILE *fp) { return PENDING_OUTPUT_N_BYTES; }
/* Return a string describing the type of a file. Copyright (C) 1993, 1994, 2001, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <sys/types.h> #include <sys/stat.h> #include "file-type.h" #include <gettext.h> #define _(text) gettext (text) char const * file_type (struct stat const *st) { /* See POSIX 1003.1-2001 XCU Table 4-8 lines 17093-17107 for some of these formats. To keep diagnostics grammatical in English, the returned string must start with a consonant. */ if (S_ISREG (st->st_mode)) return st->st_size == 0 ? _("regular empty file") : _("regular file"); if (S_ISDIR (st->st_mode)) return _("directory"); if (S_ISBLK (st->st_mode)) return _("block special file"); if (S_ISCHR (st->st_mode)) return _("character special file"); if (S_ISFIFO (st->st_mode)) return _("fifo"); if (S_ISLNK (st->st_mode)) return _("symbolic link"); if (S_ISSOCK (st->st_mode)) return _("socket"); if (S_TYPEISMQ (st)) return _("message queue"); if (S_TYPEISSEM (st)) return _("semaphore"); if (S_TYPEISSHM (st)) return _("shared memory object"); return _("weird file"); }
/* Copyright (C) 1991, 1992, 1993, 1996, 1997, 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif /* Enable GNU extensions in fnmatch.h. */ #ifndef _GNU_SOURCE # define _GNU_SOURCE 1 #endif #ifdef __GNUC__ # define alloca __builtin_alloca # define HAVE_ALLOCA 1 #else # if defined HAVE_ALLOCA_H || defined _LIBC # include <alloca.h> # else # ifdef _AIX # pragma alloca # else # ifndef alloca char *alloca (); # endif # endif # endif #endif #if ! defined __builtin_expect && __GNUC__ < 3 # define __builtin_expect(expr, expected) (expr) #endif #include <assert.h> #include <errno.h> #include <fnmatch.h> #include <ctype.h> #if HAVE_STRING_H || defined _LIBC # include <string.h> #else # if HAVE_STRINGS_H # include <strings.h> # endif #endif #if defined STDC_HEADERS || defined _LIBC # include <stddef.h> # include <stdlib.h> #endif #define WIDE_CHAR_SUPPORT (HAVE_WCTYPE_H && HAVE_WCHAR_H && HAVE_BTOWC) /* For platform which support the ISO C amendement 1 functionality we support user defined character classes. */ #if defined _LIBC || WIDE_CHAR_SUPPORT /* Solaris 2.5 has a bug: <wchar.h> must be included before <wctype.h>. */ # include <wchar.h> # include <wctype.h> #endif /* We need some of the locale data (the collation sequence information) but there is no interface to get this information in general. Therefore we support a correct implementation only in glibc. */ #ifdef _LIBC # include "../locale/localeinfo.h" # include "../locale/elem-hash.h" # include "../locale/coll-lookup.h" # include <shlib-compat.h> # define CONCAT(a,b) __CONCAT(a,b) # define mbsinit __mbsinit # define mbsrtowcs __mbsrtowcs # define fnmatch __fnmatch extern int fnmatch (const char *pattern, const char *string, int flags); #endif /* We often have to test for FNM_FILE_NAME and FNM_PERIOD being both set. */ #define NO_LEADING_PERIOD(flags) \ ((flags & (FNM_FILE_NAME | FNM_PERIOD)) == (FNM_FILE_NAME | FNM_PERIOD)) /* Comment out all this code if we are using the GNU C Library, are not actually compiling the library itself, and have not detected a bug in the library. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #if defined _LIBC || !defined __GNU_LIBRARY__ || !HAVE_FNMATCH_GNU # if defined STDC_HEADERS || !defined isascii # define ISASCII(c) 1 # else # define ISASCII(c) isascii(c) # endif # ifdef isblank # define ISBLANK(c) (ISASCII (c) && isblank (c)) # else # define ISBLANK(c) ((c) == ' ' || (c) == '\t') # endif # ifdef isgraph # define ISGRAPH(c) (ISASCII (c) && isgraph (c)) # else # define ISGRAPH(c) (ISASCII (c) && isprint (c) && !isspace (c)) # endif # define ISPRINT(c) (ISASCII (c) && isprint (c)) # define ISDIGIT(c) (ISASCII (c) && isdigit (c)) # define ISALNUM(c) (ISASCII (c) && isalnum (c)) # define ISALPHA(c) (ISASCII (c) && isalpha (c)) # define ISCNTRL(c) (ISASCII (c) && iscntrl (c)) # define ISLOWER(c) (ISASCII (c) && islower (c)) # define ISPUNCT(c) (ISASCII (c) && ispunct (c)) # define ISSPACE(c) (ISASCII (c) && isspace (c)) # define ISUPPER(c) (ISASCII (c) && isupper (c)) # define ISXDIGIT(c) (ISASCII (c) && isxdigit (c)) # define STREQ(s1, s2) ((strcmp (s1, s2) == 0)) # if defined _LIBC || WIDE_CHAR_SUPPORT /* The GNU C library provides support for user-defined character classes and the functions from ISO C amendement 1. */ # ifdef CHARCLASS_NAME_MAX # define CHAR_CLASS_MAX_LENGTH CHARCLASS_NAME_MAX # else /* This shouldn't happen but some implementation might still have this problem. Use a reasonable default value. */ # define CHAR_CLASS_MAX_LENGTH 256 # endif # ifdef _LIBC # define IS_CHAR_CLASS(string) __wctype (string) # else # define IS_CHAR_CLASS(string) wctype (string) # endif # ifdef _LIBC # define ISWCTYPE(WC, WT) __iswctype (WC, WT) # else # define ISWCTYPE(WC, WT) iswctype (WC, WT) # endif # if (HAVE_MBSTATE_T && HAVE_MBSRTOWCS) || _LIBC /* In this case we are implementing the multibyte character handling. */ # define HANDLE_MULTIBYTE 1 # endif # else # define CHAR_CLASS_MAX_LENGTH 6 /* Namely, `xdigit'. */ # define IS_CHAR_CLASS(string) \ (STREQ (string, "alpha") || STREQ (string, "upper") \ || STREQ (string, "lower") || STREQ (string, "digit") \ || STREQ (string, "alnum") || STREQ (string, "xdigit") \ || STREQ (string, "space") || STREQ (string, "print") \ || STREQ (string, "punct") || STREQ (string, "graph") \ || STREQ (string, "cntrl") || STREQ (string, "blank")) # endif /* Avoid depending on library functions or files whose names are inconsistent. */ # if !defined _LIBC && !defined getenv && !HAVE_DECL_GETENV extern char *getenv (); # endif # ifndef errno extern int errno; # endif /* Global variable. */ static int posixly_correct; # ifndef internal_function /* Inside GNU libc we mark some function in a special way. In other environments simply ignore the marking. */ # define internal_function # endif /* Note that this evaluates C many times. */ # ifdef _LIBC # define FOLD(c) ((flags & FNM_CASEFOLD) ? tolower (c) : (c)) # else # define FOLD(c) ((flags & FNM_CASEFOLD) && ISUPPER (c) ? tolower (c) : (c)) # endif # define CHAR char # define UCHAR unsigned char # define INT int # define FCT internal_fnmatch # define EXT ext_match # define END end_pattern # define L(CS) CS # ifdef _LIBC # define BTOWC(C) __btowc (C) # else # define BTOWC(C) btowc (C) # endif # define STRLEN(S) strlen (S) # define STRCAT(D, S) strcat (D, S) # ifdef _LIBC # define MEMPCPY(D, S, N) __mempcpy (D, S, N) # else # if HAVE_MEMPCPY # define MEMPCPY(D, S, N) mempcpy (D, S, N) # else # define MEMPCPY(D, S, N) ((void *) ((char *) memcpy (D, S, N) + (N))) # endif # endif # define MEMCHR(S, C, N) memchr (S, C, N) # define STRCOLL(S1, S2) strcoll (S1, S2) # include "fnmatch_loop.c" # if HANDLE_MULTIBYTE # define FOLD(c) ((flags & FNM_CASEFOLD) ? towlower (c) : (c)) # define CHAR wchar_t # define UCHAR wint_t # define INT wint_t # define FCT internal_fnwmatch # define EXT ext_wmatch # define END end_wpattern # define L(CS) L##CS # define BTOWC(C) (C) # ifdef _LIBC # define STRLEN(S) __wcslen (S) # define STRCAT(D, S) __wcscat (D, S) # define MEMPCPY(D, S, N) __wmempcpy (D, S, N) # else # define STRLEN(S) wcslen (S) # define STRCAT(D, S) wcscat (D, S) # if HAVE_WMEMPCPY # define MEMPCPY(D, S, N) wmempcpy (D, S, N) # else # define MEMPCPY(D, S, N) (wmemcpy (D, S, N) + (N)) # endif # endif # define MEMCHR(S, C, N) wmemchr (S, C, N) # define STRCOLL(S1, S2) wcscoll (S1, S2) # define WIDE_CHAR_VERSION 1 # undef IS_CHAR_CLASS /* We have to convert the wide character string in a multibyte string. But we know that the character class names consist of alphanumeric characters from the portable character set, and since the wide character encoding for a member of the portable character set is the same code point as its single-byte encoding, we can use a simplified method to convert the string to a multibyte character string. */ static wctype_t is_char_class (const wchar_t *wcs) { char s[CHAR_CLASS_MAX_LENGTH + 1]; char *cp = s; do { /* Test for a printable character from the portable character set. */ # ifdef _LIBC if (*wcs < 0x20 || *wcs > 0x7e || *wcs == 0x24 || *wcs == 0x40 || *wcs == 0x60) return (wctype_t) 0; # else switch (*wcs) { case L' ': case L'!': case L'"': case L'#': case L'%': case L'&': case L'\'': case L'(': case L')': case L'*': case L'+': case L',': case L'-': case L'.': case L'/': case L'0': case L'1': case L'2': case L'3': case L'4': case L'5': case L'6': case L'7': case L'8': case L'9': case L':': case L';': case L'<': case L'=': case L'>': case L'?': case L'A': case L'B': case L'C': case L'D': case L'E': case L'F': case L'G': case L'H': case L'I': case L'J': case L'K': case L'L': case L'M': case L'N': case L'O': case L'P': case L'Q': case L'R': case L'S': case L'T': case L'U': case L'V': case L'W': case L'X': case L'Y': case L'Z': case L'[': case L'\\': case L']': case L'^': case L'_': case L'a': case L'b': case L'c': case L'd': case L'e': case L'f': case L'g': case L'h': case L'i': case L'j': case L'k': case L'l': case L'm': case L'n': case L'o': case L'p': case L'q': case L'r': case L's': case L't': case L'u': case L'v': case L'w': case L'x': case L'y': case L'z': case L'{': case L'|': case L'}': case L'~': break; default: return (wctype_t) 0; } # endif /* Avoid overrunning the buffer. */ if (cp == s + CHAR_CLASS_MAX_LENGTH) return (wctype_t) 0; *cp++ = (char) *wcs++; } while (*wcs != L'\0'); *cp = '\0'; # ifdef _LIBC return __wctype (s); # else return wctype (s); # endif } # define IS_CHAR_CLASS(string) is_char_class (string) # include "fnmatch_loop.c" # endif int fnmatch (pattern, string, flags) const char *pattern; const char *string; int flags; { # if HANDLE_MULTIBYTE if (__builtin_expect (MB_CUR_MAX, 1) != 1) { mbstate_t ps; size_t n; wchar_t *wpattern; wchar_t *wstring; /* Convert the strings into wide characters. */ memset (&ps, '\0', sizeof (ps)); n = mbsrtowcs (NULL, &pattern, 0, &ps); if (__builtin_expect (n, 0) == (size_t) -1) /* Something wrong. XXX Do we have to set `errno' to something which mbsrtows hasn't already done? */ return -1; wpattern = (wchar_t *) alloca ((n + 1) * sizeof (wchar_t)); assert (mbsinit (&ps)); (void) mbsrtowcs (wpattern, &pattern, n + 1, &ps); assert (mbsinit (&ps)); n = mbsrtowcs (NULL, &string, 0, &ps); if (__builtin_expect (n, 0) == (size_t) -1) /* Something wrong. XXX Do we have to set `errno' to something which mbsrtows hasn't already done? */ return -1; wstring = (wchar_t *) alloca ((n + 1) * sizeof (wchar_t)); assert (mbsinit (&ps)); (void) mbsrtowcs (wstring, &string, n + 1, &ps); return internal_fnwmatch (wpattern, wstring, wstring + n, flags & FNM_PERIOD, flags); } # endif /* mbstate_t and mbsrtowcs or _LIBC. */ return internal_fnmatch (pattern, string, string + strlen (string), flags & FNM_PERIOD, flags); } # ifdef _LIBC # undef fnmatch versioned_symbol (libc, __fnmatch, fnmatch, GLIBC_2_2_3); # if SHLIB_COMPAT(libc, GLIBC_2_0, GLIBC_2_2_3) strong_alias (__fnmatch, __fnmatch_old) compat_symbol (libc, __fnmatch_old, fnmatch, GLIBC_2_0); # endif # endif #endif /* _LIBC or not __GNU_LIBRARY__. */
/* Invoke fopen, but avoid some glitches. Copyright (C) 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert. */ #if HAVE_CONFIG_H # include <config.h> #endif #if HAVE_UNISTD_H # include <unistd.h> #endif #include <unistd-safer.h> #ifndef STDERR_FILENO # define STDERR_FILENO 2 #endif #include <errno.h> #ifndef errno extern int errno; #endif #include <stdio.h> #include <stdio-safer.h> /* Like fopen, but do not return stdin, stdout, or stderr. */ FILE * fopen_safer (char const *file, char const *mode) { FILE *fp = fopen (file, mode); if (fp) { int fd = fileno (fp); if (0 <= fd && fd <= STDERR_FILENO) { int f = dup_safer (fd); if (f < 0) { int e = errno; fclose (fp); errno = e; return NULL; } if (fclose (fp) != 0 || ! (fp = fdopen (f, mode))) { int e = errno; close (f); errno = e; return NULL; } } } return fp; }
/* An interface to read that retries after partial reads and interrupts. Copyright (C) 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #define FULL_READ #include "full-write.c"
/* An interface to read and write that retries (if necessary) until complete. Copyright (C) 1993, 1994, 1997-2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <errno.h> #ifndef errno extern int errno; #endif #ifdef FULL_READ # include "full-read.h" # include "safe-read.h" # define safe_rw safe_read # define full_rw full_read # undef const # define const /* empty */ #else # include "full-write.h" # include "safe-write.h" # define safe_rw safe_write # define full_rw full_write #endif #ifdef FULL_READ /* Set errno to zero upon EOF. */ # define ZERO_BYTE_TRANSFER_ERRNO 0 #else /* Some buggy drivers return 0 when one tries to write beyond a device's end. (Example: Linux 1.2.13 on /dev/fd0.) Set errno to ENOSPC so they get a sensible diagnostic. */ # define ZERO_BYTE_TRANSFER_ERRNO ENOSPC #endif /* Write(read) COUNT bytes at BUF to(from) descriptor FD, retrying if interrupted or if a partial write(read) occurs. Return the number of bytes transferred. When writing, set errno if fewer than COUNT bytes are written. When reading, if fewer than COUNT bytes are read, you must examine errno to distinguish failure from EOF (errno == 0). */ size_t full_rw (int fd, const void *buf, size_t count) { size_t total = 0; const char *ptr = buf; while (count > 0) { size_t n_rw = safe_rw (fd, ptr, count); if (n_rw == (size_t) -1) break; if (n_rw == 0) { errno = ZERO_BYTE_TRANSFER_ERRNO; break; } total += n_rw; ptr += n_rw; count -= n_rw; } return total; }
/* gethostname emulation for SysV and POSIX.1. Copyright (C) 1992 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* David MacKenzie <djm@gnu.ai.mit.edu> */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #ifdef HAVE_UNAME # include <sys/utsname.h> #endif /* Put up to LEN chars of the host name into NAME. Null terminate it if the name is shorter than LEN. Return 0 if ok, -1 if error. */ int gethostname (name, len) char *name; int len; { #ifdef HAVE_UNAME struct utsname uts; if (uname (&uts) == -1) return -1; if (len > sizeof (uts.nodename)) { /* More space than we need is available. */ name[sizeof (uts.nodename)] = '\0'; len = sizeof (uts.nodename); } strncpy (name, uts.nodename, len); #else strcpy (name, ""); /* Hardcode your system name if you want. */ #endif return 0; }
/* getline.c -- Replacement for GNU C library function getline Copyright (C) 1993, 1996, 1997, 1998, 2000 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Jan Brittenson, bson@gnu.ai.mit.edu. */ #if HAVE_CONFIG_H # include <config.h> #endif /* The `getdelim' function is only declared if the following symbol is defined. */ #ifndef _GNU_SOURCE # define _GNU_SOURCE 1 #endif #include <stdio.h> #include <sys/types.h> #if defined __GNU_LIBRARY__ && HAVE_GETDELIM int getline (char **lineptr, size_t *n, FILE *stream) { return getdelim (lineptr, n, '\n', stream); } #else /* ! have getdelim */ # include "getstr.h" int getline (char **lineptr, size_t *n, FILE *stream) { return getstr (lineptr, n, stream, '\n', 0, 0); } int getdelim (char **lineptr, size_t *n, int delimiter, FILE *stream) { return getstr (lineptr, n, stream, delimiter, 0, 0); } #endif
/* getstr.c -- core function for GNU C library getline replacement function Copyright (C) 1993, 1996-2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Jan Brittenson, bson@gnu.ai.mit.edu. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #include <sys/types.h> #if STDC_HEADERS # include <stdlib.h> #else char *malloc (), *realloc (); #endif #include "unlocked-io.h" /* Always add at least this many bytes when extending the buffer. */ #define MIN_CHUNK 64 /* Read up to (and including) a delimiter DELIM1 from STREAM into *LINEPTR + OFFSET (and NUL-terminate it). If DELIM2 is non-zero, then read up and including the first occurrence of DELIM1 or DELIM2. *LINEPTR is a pointer returned from malloc (or NULL), pointing to *N characters of space. It is realloc'd as necessary. Return the number of characters read (not including the NUL terminator), or -1 on error or EOF. */ int getstr (char **lineptr, size_t *n, FILE *stream, int delim1, int delim2, size_t offset) { size_t nchars_avail; /* Allocated but unused chars in *LINEPTR. */ char *read_pos; /* Where we're reading into *LINEPTR. */ int ret; if (!lineptr || !n || !stream) return -1; if (!*lineptr) { *n = MIN_CHUNK; *lineptr = malloc (*n); if (!*lineptr) return -1; } if (*n < offset) return -1; nchars_avail = *n - offset; read_pos = *lineptr + offset; for (;;) { register int c = getc (stream); /* We always want at least one char left in the buffer, since we always (unless we get an error while reading the first char) NUL-terminate the line buffer. */ if (nchars_avail < 2) { if (*n > MIN_CHUNK) *n *= 2; else *n += MIN_CHUNK; nchars_avail = *n + *lineptr - read_pos; *lineptr = realloc (*lineptr, *n); if (!*lineptr) return -1; read_pos = *n - nchars_avail + *lineptr; } if (c == EOF || ferror (stream)) { /* Return partial line, if any. */ if (read_pos == *lineptr) return -1; else break; } *read_pos++ = c; nchars_avail--; if (c == delim1 || (delim2 && c == delim2)) /* Return the line. */ break; } /* Done - NUL terminate and return the number of chars read. */ *read_pos = '\0'; ret = read_pos - (*lineptr + offset); return ret; }
/* gettime -- get the system clock Copyright (C) 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include "timespec.h" /* Get the system time. */ int gettime (struct timespec *ts) { #if defined CLOCK_REALTIME && HAVE_CLOCK_GETTIME if (clock_gettime (CLOCK_REALTIME, ts) == 0) return 0; #endif { struct timeval tv; int r = gettimeofday (&tv, 0); if (r == 0) { ts->tv_sec = tv.tv_sec; ts->tv_nsec = tv.tv_usec * 1000; } return r; } }
/* hard-locale.c -- Determine whether a locale is hard. Copyright (C) 1997, 1998, 1999, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #if HAVE_LOCALE_H # include <locale.h> #endif #if HAVE_STDLIB_H # include <stdlib.h> #endif #if HAVE_STRING_H # include <string.h> #endif #include "hard-locale.h" /* Return nonzero if the current CATEGORY locale is hard, i.e. if you can't get away with assuming traditional C or POSIX behavior. */ int hard_locale (int category) { #if ! HAVE_SETLOCALE return 0; #else int hard = 1; char const *p = setlocale (category, 0); if (p) { # if defined __GLIBC__ && 2 <= __GLIBC__ if (strcmp (p, "C") == 0 || strcmp (p, "POSIX") == 0) hard = 0; # else char *locale = malloc (strlen (p) + 1); if (locale) { strcpy (locale, p); /* Temporarily set the locale to the "C" and "POSIX" locales to find their names, so that we can determine whether one or the other is the caller's locale. */ if (((p = setlocale (category, "C")) && strcmp (p, locale) == 0) || ((p = setlocale (category, "POSIX")) && strcmp (p, locale) == 0)) hard = 0; /* Restore the caller's locale. */ setlocale (category, locale); free (locale); } # endif } return hard; #endif }
/* human.c -- print human readable file size Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Originally contributed by lm@sgi.com; --si, output block size selection, large file support, and grouping added by eggert@twinsun.com. */ #if HAVE_CONFIG_H # include <config.h> #endif #if HAVE_STDBOOL_H # include <stdbool.h> #else typedef enum {false = 0, true = 1} bool; #endif #if HAVE_INTTYPES_H # include <inttypes.h> #else # if HAVE_STDINT_H # include <stdint.h> # endif #endif #ifndef SIZE_MAX # define SIZE_MAX ((size_t) -1) #endif #ifndef UINTMAX_MAX # define UINTMAX_MAX ((uintmax_t) -1) #endif #include <limits.h> #if HAVE_LOCALE_H && HAVE_LOCALECONV # include <locale.h> #endif #if HAVE_STDLIB_H # include <stdlib.h> #endif #ifndef HAVE_DECL_GETENV "this configure-time declaration test was not run" #endif #if !HAVE_DECL_GETENV char *getenv (); #endif #if HAVE_STRING_H # include <string.h> #endif #if HAVE_STRINGS_H # include <strings.h> #endif #include <stdio.h> #include <sys/types.h> #include <gettext.h> #define _(text) gettext (text) #include <argmatch.h> #include <error.h> #include <xstrtol.h> #include "human.h" /* The maximum length of a suffix like "KiB". */ #define HUMAN_READABLE_SUFFIX_LENGTH_MAX 3 static const char power_letter[] = { 0, /* not used */ 'K', /* kibi ('k' for kilo is a special case) */ 'M', /* mega or mebi */ 'G', /* giga or gibi */ 'T', /* tera or tebi */ 'P', /* peta or pebi */ 'E', /* exa or exbi */ 'Z', /* zetta or 2**70 */ 'Y' /* yotta or 2**80 */ }; /* If INEXACT_STYLE is not human_round_to_nearest, and if easily possible, adjust VALUE according to the style. */ static long double adjust_value (int inexact_style, long double value) { /* Do not use the floorl or ceill functions, as that would mean checking for their presence and possibly linking with the standard math library, which is a porting pain. So leave the value alone if it is too large to easily round. */ if (inexact_style != human_round_to_nearest && value < UINTMAX_MAX) { uintmax_t u = value; value = u + (inexact_style == human_ceiling && u != value); } return value; } /* Group the digits of NUMBER according to the grouping rules of the current locale. NUMBER contains NUMBERLEN digits. Modify the bytes pointed to by NUMBER in place, subtracting 1 from NUMBER for each byte inserted. Return the starting address of the modified number. To group the digits, use GROUPING and THOUSANDS_SEP as in `struct lconv' from <locale.h>. */ static char * group_number (char *number, size_t numberlen, char const *grouping, char const *thousands_sep) { register char *d; size_t grouplen = SIZE_MAX; size_t thousands_seplen = strlen (thousands_sep); size_t i = numberlen; /* The maximum possible value for NUMBERLEN is the number of digits in the square of the largest uintmax_t, so double the size of uintmax_t before converting to a bound. 302 / 1000 is ceil (log10 (2.0)). Add 1 for integer division truncation. */ char buf[2 * sizeof (uintmax_t) * CHAR_BIT * 302 / 1000 + 1]; memcpy (buf, number, numberlen); d = number + numberlen; for (;;) { unsigned char g = *grouping; if (g) { grouplen = g < CHAR_MAX ? g : i; grouping++; } if (i < grouplen) grouplen = i; d -= grouplen; i -= grouplen; memcpy (d, buf + i, grouplen); if (i == 0) return d; d -= thousands_seplen; memcpy (d, thousands_sep, thousands_seplen); } } /* Convert N to a human readable format in BUF, using the options OPTS. N is expressed in units of FROM_BLOCK_SIZE. FROM_BLOCK_SIZE must be nonnegative. Use units of TO_BLOCK_SIZE in the output number. TO_BLOCK_SIZE must be positive. Use (OPTS & (human_round_to_nearest | human_floor | human_ceiling)) to determine whether to take the ceiling or floor of any result that cannot be expressed exactly. If (OPTS & human_group_digits), group the thousands digits according to the locale, e.g., `1,000,000' in an American English locale. If (OPTS & human_autoscale), deduce the output block size automatically; TO_BLOCK_SIZE must be 1 but it has no effect on the output. Use powers of 1024 if (OPTS & human_base_1024), and powers of 1000 otherwise. For example, assuming powers of 1024, 8500 would be converted to 8.3, 133456345 to 127, 56990456345 to 53, and so on. Numbers smaller than the power aren't modified. human_autoscale is normally used together with human_SI. If (OPTS & human_SI), append an SI prefix indicating which power is being used. If in addition (OPTS & human_B), append "B" (if base 1000) or "iB" (if base 1024) to the SI prefix. When ((OPTS & human_SI) && ! (OPTS & human_autoscale)), TO_BLOCK_SIZE must be a power of 1024 or of 1000, depending on (OPTS & human_base_1024). */ char * human_readable (uintmax_t n, char *buf, int opts, uintmax_t from_block_size, uintmax_t to_block_size) { int inexact_style = opts & (human_round_to_nearest | human_floor | human_ceiling); unsigned int base = opts & human_base_1024 ? 1024 : 1000; uintmax_t amt; int tenths; int exponent = -1; int exponent_max = sizeof power_letter - 1; char *p; char *psuffix; char const *integerlim; /* 0 means adjusted N == AMT.TENTHS; 1 means AMT.TENTHS < adjusted N < AMT.TENTHS + 0.05; 2 means adjusted N == AMT.TENTHS + 0.05; 3 means AMT.TENTHS + 0.05 < adjusted N < AMT.TENTHS + 0.1. */ int rounding; char const *decimal_point = "."; size_t decimal_pointlen = 1; char const *grouping = ""; char const *thousands_sep = ""; #if HAVE_LOCALE_H && HAVE_LOCALECONV struct lconv const *l = localeconv (); size_t pointlen = strlen (l->decimal_point); if (0 < pointlen && pointlen <= MB_LEN_MAX) { decimal_point = l->decimal_point; decimal_pointlen = pointlen; } grouping = l->grouping; if (strlen (l->thousands_sep) <= MB_LEN_MAX) thousands_sep = l->thousands_sep; #endif psuffix = buf + LONGEST_HUMAN_READABLE - HUMAN_READABLE_SUFFIX_LENGTH_MAX; p = psuffix; /* Adjust AMT out of FROM_BLOCK_SIZE units and into TO_BLOCK_SIZE units. If this can be done exactly with integer arithmetic, do not use floating point operations. */ if (to_block_size <= from_block_size) { if (from_block_size % to_block_size == 0) { uintmax_t multiplier = from_block_size / to_block_size; amt = n * multiplier; if (amt / multiplier == n) { tenths = 0; rounding = 0; goto use_integer_arithmetic; } } } else if (from_block_size != 0 && to_block_size % from_block_size == 0) { uintmax_t divisor = to_block_size / from_block_size; uintmax_t r10 = (n % divisor) * 10; uintmax_t r2 = (r10 % divisor) * 2; amt = n / divisor; tenths = r10 / divisor; rounding = r2 < divisor ? 0 < r2 : 2 + (divisor < r2); goto use_integer_arithmetic; } { /* Either the result cannot be computed easily using uintmax_t, or from_block_size is zero. Fall back on floating point. FIXME: This can yield answers that are slightly off. */ long double dto_block_size = to_block_size; long double damt = n * (from_block_size / dto_block_size); size_t buflen; size_t nonintegerlen; if (! (opts & human_autoscale)) { sprintf (buf, "%.0Lf", adjust_value (inexact_style, damt)); buflen = strlen (buf); nonintegerlen = 0; } else { long double e = 1; exponent = 0; do { e *= base; exponent++; } while (e * base <= damt && exponent < exponent_max); damt /= e; sprintf (buf, "%.1Lf", adjust_value (inexact_style, damt)); buflen = strlen (buf); nonintegerlen = decimal_pointlen + 1; if (1 + nonintegerlen + ! (opts & human_base_1024) < buflen || ((opts & human_suppress_point_zero) && buf[buflen - 1] == '0')) { sprintf (buf, "%.0Lf", adjust_value (inexact_style, damt * 10) / 10); buflen = strlen (buf); nonintegerlen = 0; } } p = psuffix - buflen; memmove (p, buf, buflen); integerlim = p + buflen - nonintegerlen; } goto do_grouping; use_integer_arithmetic: { /* The computation can be done exactly, with integer arithmetic. Use power of BASE notation if requested and if adjusted AMT is large enough. */ if (opts & human_autoscale) { exponent = 0; if (base <= amt) { do { unsigned r10 = (amt % base) * 10 + tenths; unsigned r2 = (r10 % base) * 2 + (rounding >> 1); amt /= base; tenths = r10 / base; rounding = (r2 < base ? (r2 + rounding) != 0 : 2 + (base < r2 + rounding)); exponent++; } while (base <= amt && exponent < exponent_max); if (amt < 10) { if (inexact_style == human_round_to_nearest ? 2 < rounding + (tenths & 1) : inexact_style == human_ceiling && 0 < rounding) { tenths++; rounding = 0; if (tenths == 10) { amt++; tenths = 0; } } if (amt < 10 && (tenths || ! (opts & human_suppress_point_zero))) { *--p = '0' + tenths; p -= decimal_pointlen; memcpy (p, decimal_point, decimal_pointlen); tenths = rounding = 0; } } } } if (inexact_style == human_ceiling ? 0 < tenths + rounding : inexact_style == human_round_to_nearest ? 5 < tenths + (2 < rounding + (amt & 1)) : /* inexact_style == human_floor */ 0) { amt++; if ((opts & human_autoscale) && amt == base && exponent < exponent_max) { exponent++; if (! (opts & human_suppress_point_zero)) { *--p = '0'; p -= decimal_pointlen; memcpy (p, decimal_point, decimal_pointlen); } amt = 1; } } integerlim = p; do { int digit = amt % 10; *--p = digit + '0'; } while ((amt /= 10) != 0); } do_grouping: if (opts & human_group_digits) p = group_number (p, integerlim - p, grouping, thousands_sep); if (opts & human_SI) { if (exponent < 0) { uintmax_t power; exponent = 0; for (power = 1; power < to_block_size; power *= base) if (++exponent == exponent_max) break; } if (exponent) *psuffix++ = (! (opts & human_base_1024) && exponent == 1 ? 'k' : power_letter[exponent]); if (opts & human_B) { if ((opts & human_base_1024) && exponent) *psuffix++ = 'i'; *psuffix++ = 'B'; } } *psuffix = '\0'; return p; } /* The default block size used for output. This number may change in the future as disks get larger. */ #ifndef DEFAULT_BLOCK_SIZE # define DEFAULT_BLOCK_SIZE 1024 #endif static char const *const block_size_args[] = { "human-readable", "si", 0 }; static int const block_size_opts[] = { human_autoscale + human_SI + human_base_1024, human_autoscale + human_SI }; static uintmax_t default_block_size (void) { return getenv ("POSIXLY_CORRECT") ? 512 : DEFAULT_BLOCK_SIZE; } static strtol_error humblock (char const *spec, uintmax_t *block_size, int *options) { int i; int opts = 0; if (! spec && ! (spec = getenv ("BLOCK_SIZE"))) *block_size = default_block_size (); else { if (*spec == '\'') { opts |= human_group_digits; spec++; } if (0 <= (i = ARGMATCH (spec, block_size_args, block_size_opts))) { opts |= block_size_opts[i]; *block_size = 1; } else { char *ptr; strtol_error e = xstrtoumax (spec, &ptr, 0, block_size, "eEgGkKmMpPtTyYzZ0"); if (e != LONGINT_OK) return e; if (*ptr) return LONGINT_INVALID_SUFFIX_CHAR; for (; ! ('0' <= *spec && *spec <= '9'); spec++) if (spec == ptr) { opts |= human_SI; if (ptr[-1] == 'B') opts |= human_B; if (ptr[-1] != 'B' || ptr[-2] == 'i') opts |= human_base_1024; break; } } } *options = opts; return LONGINT_OK; } int human_options (char const *spec, bool report_errors, uintmax_t *block_size) { int opts; strtol_error e = humblock (spec, block_size, &opts); if (*block_size == 0) { *block_size = default_block_size (); e = LONGINT_INVALID; } if (e != LONGINT_OK && report_errors) STRTOL_FATAL_ERROR (spec, _("block size"), e); return opts; }
/* idcache.c -- map user and group IDs, cached for speed Copyright (C) 1985, 1988, 1989, 1990, 1997, 1998, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #include <sys/types.h> #include <pwd.h> #include <grp.h> #if STDC_HEADERS || HAVE_STRING_H # include <string.h> #else # include <strings.h> #endif #if HAVE_UNISTD_H # include <unistd.h> #endif #include "xalloc.h" #ifndef _POSIX_VERSION struct passwd *getpwuid (); struct passwd *getpwnam (); struct group *getgrgid (); struct group *getgrnam (); #endif #ifdef __DJGPP__ static char digits[] = "0123456789"; #endif struct userid { union { uid_t u; gid_t g; } id; char *name; struct userid *next; }; static struct userid *user_alist; /* The members of this list have names not in the local passwd file. */ static struct userid *nouser_alist; /* Translate UID to a login name, with cache, or NULL if unresolved. */ char * getuser (uid_t uid) { register struct userid *tail; struct passwd *pwent; for (tail = user_alist; tail; tail = tail->next) if (tail->id.u == uid) return tail->name; pwent = getpwuid (uid); tail = (struct userid *) xmalloc (sizeof (struct userid)); tail->id.u = uid; tail->name = pwent ? xstrdup (pwent->pw_name) : NULL; /* Add to the head of the list, so most recently used is first. */ tail->next = user_alist; user_alist = tail; return tail->name; } /* Translate USER to a UID, with cache. Return NULL if there is no such user. (We also cache which user names have no passwd entry, so we don't keep looking them up.) */ uid_t * getuidbyname (const char *user) { register struct userid *tail; struct passwd *pwent; for (tail = user_alist; tail; tail = tail->next) /* Avoid a function call for the most common case. */ if (*tail->name == *user && !strcmp (tail->name, user)) return &tail->id.u; for (tail = nouser_alist; tail; tail = tail->next) /* Avoid a function call for the most common case. */ if (*tail->name == *user && !strcmp (tail->name, user)) return 0; pwent = getpwnam (user); #ifdef __DJGPP__ /* We need to pretend to be the user USER, to make pwd functions know about an arbitrary user name. */ if (!pwent && strspn (user, digits) < strlen (user)) { setenv ("USER", user, 1); pwent = getpwnam (user); /* now it will succeed */ } #endif tail = (struct userid *) xmalloc (sizeof (struct userid)); tail->name = xstrdup (user); /* Add to the head of the list, so most recently used is first. */ if (pwent) { tail->id.u = pwent->pw_uid; tail->next = user_alist; user_alist = tail; return &tail->id.u; } tail->next = nouser_alist; nouser_alist = tail; return 0; } /* Use the same struct as for userids. */ static struct userid *group_alist; static struct userid *nogroup_alist; /* Translate GID to a group name, with cache, or NULL if unresolved. */ char * getgroup (gid_t gid) { register struct userid *tail; struct group *grent; for (tail = group_alist; tail; tail = tail->next) if (tail->id.g == gid) return tail->name; grent = getgrgid (gid); tail = (struct userid *) xmalloc (sizeof (struct userid)); tail->id.g = gid; tail->name = grent ? xstrdup (grent->gr_name) : NULL; /* Add to the head of the list, so most recently used is first. */ tail->next = group_alist; group_alist = tail; return tail->name; } /* Translate GROUP to a GID, with cache. Return NULL if there is no such group. (We also cache which group names have no group entry, so we don't keep looking them up.) */ gid_t * getgidbyname (const char *group) { register struct userid *tail; struct group *grent; for (tail = group_alist; tail; tail = tail->next) /* Avoid a function call for the most common case. */ if (*tail->name == *group && !strcmp (tail->name, group)) return &tail->id.g; for (tail = nogroup_alist; tail; tail = tail->next) /* Avoid a function call for the most common case. */ if (*tail->name == *group && !strcmp (tail->name, group)) return 0; grent = getgrnam (group); #ifdef __DJGPP__ /* We need to pretend to belong to group GROUP, to make grp functions know about any arbitrary group name. */ if (!grent && strspn (group, digits) < strlen (group)) { setenv ("GROUP", group, 1); grent = getgrnam (group); /* now it will succeed */ } #endif tail = (struct userid *) xmalloc (sizeof (struct userid)); tail->name = xstrdup (group); /* Add to the head of the list, so most recently used is first. */ if (grent) { tail->id.g = grent->gr_gid; tail->next = group_alist; group_alist = tail; return &tail->id.g; } tail->next = nogroup_alist; nogroup_alist = tail; return 0; }
/* isdir.c -- determine whether a directory exists Copyright (C) 1990, 1998 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <sys/types.h> #include <sys/stat.h> #if STAT_MACROS_BROKEN # undef S_ISDIR #endif #if !defined S_ISDIR && defined S_IFDIR # define S_ISDIR(Mode) (((Mode) & S_IFMT) == S_IFDIR) #endif /* If PATH is an existing directory or symbolic link to a directory, return nonzero, else 0. */ int isdir (const char *path) { struct stat stats; return stat (path, &stats) == 0 && S_ISDIR (stats.st_mode); }
#define inttostr imaxtostr #define inttype intmax_t #include "inttostr.c"
/* inttostr.c -- convert integers to printable strings Copyright (C) 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert */ #include "inttostr.h" /* Convert I to a printable string in BUF, which must be at least INT_BUFSIZE_BOUND (INTTYPE) bytes long. Return the address of the printable string, which need not start at BUF. */ char * inttostr (inttype i, char *buf) { char *p = buf + INT_STRLEN_BOUND (inttype); *p = 0; if (i < 0) { do *--p = '0' - i % 10; while ((i /= 10) != 0); *--p = '-'; } else { do *--p = '0' + i % 10; while ((i /= 10) != 0); } return p; }
/* inttostr.h -- convert integers to printable strings Copyright (C) 2001, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert */ #if HAVE_CONFIG_H # include <config.h> #endif #if HAVE_INTTYPES_H # include <inttypes.h> #endif #if HAVE_LIMITS_H # include <limits.h> #endif #ifndef CHAR_BIT # define CHAR_BIT 8 #endif #if HAVE_SYS_TYPES_H # include <sys/types.h> #endif /* Upper bound on the string length of an integer converted to string. 302 / 1000 is ceil (log10 (2.0)). Subtract 1 for the sign bit; add 1 for integer division truncation; add 1 more for a minus sign. */ #define INT_STRLEN_BOUND(t) ((sizeof (t) * CHAR_BIT - 1) * 302 / 1000 + 2) #define INT_BUFSIZE_BOUND(t) (INT_STRLEN_BOUND (t) + 1) #ifndef PARAMS # if defined PROTOTYPES || defined __STDC__ # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif #endif char *offtostr PARAMS ((off_t, char *)); char *imaxtostr PARAMS ((intmax_t, char *)); char *umaxtostr PARAMS ((uintmax_t, char *));
/* linebuffer.c -- read arbitrarily long lines Copyright (C) 1986, 1991, 1998, 1999, 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Richard Stallman. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #include <sys/types.h> #include "linebuffer.h" #include "unlocked-io.h" #include "xalloc.h" void free (); /* Initialize linebuffer LINEBUFFER for use. */ void initbuffer (struct linebuffer *linebuffer) { linebuffer->length = 0; linebuffer->size = 200; linebuffer->buffer = xmalloc (linebuffer->size); } /* Read an arbitrarily long line of text from STREAM into LINEBUFFER. Keep the newline; append a newline if it's the last line of a file that ends in a non-newline character. Do not null terminate. Therefore the stream can contain NUL bytes, and the length (including the newline) is returned in linebuffer->length. Return NULL upon error, or when STREAM is empty. Otherwise, return LINEBUFFER. */ struct linebuffer * readline (struct linebuffer *linebuffer, FILE *stream) { int c; char *buffer = linebuffer->buffer; char *p = linebuffer->buffer; char *end = buffer + linebuffer->size; /* Sentinel. */ if (feof (stream) || ferror (stream)) return NULL; do { c = getc (stream); if (c == EOF) { if (p == buffer) return NULL; if (p[-1] == '\n') break; c = '\n'; } if (p == end) { linebuffer->size *= 2; buffer = xrealloc (buffer, linebuffer->size); p = p - linebuffer->buffer + buffer; linebuffer->buffer = buffer; end = buffer + linebuffer->size; } *p++ = c; } while (c != '\n'); linebuffer->length = p - buffer; return linebuffer; } /* Free linebuffer LINEBUFFER and its data, all allocated with malloc. */ void freebuffer (struct linebuffer *linebuffer) { free (linebuffer->buffer); free (linebuffer); }
/* Determine a canonical name for the current locale's character encoding. Copyright (C) 2000-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Bruno Haible <bruno@clisp.org>. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #if HAVE_STDDEF_H # include <stddef.h> #endif #include <stdio.h> #if HAVE_STRING_H # include <string.h> #else # include <strings.h> #endif #if HAVE_STDLIB_H # include <stdlib.h> #endif #if defined _WIN32 || defined __WIN32__ # undef WIN32 /* avoid warning on mingw32 */ # define WIN32 #endif #if defined __EMX__ /* Assume EMX program runs on OS/2, even if compiled under DOS. */ # define OS2 #endif #if !defined WIN32 # if HAVE_LANGINFO_CODESET # include <langinfo.h> # else # if HAVE_SETLOCALE # include <locale.h> # endif # endif #elif defined WIN32 # define WIN32_LEAN_AND_MEAN # include <windows.h> #endif #if defined OS2 # define INCL_DOS # include <os2.h> #endif #if defined _WIN32 || defined __WIN32__ || defined __EMX__ || defined __DJGPP__ /* Win32, OS/2, DOS */ # define ISSLASH(C) ((C) == '/' || (C) == '\\') #endif #ifndef DIRECTORY_SEPARATOR # define DIRECTORY_SEPARATOR '/' #endif #ifndef ISSLASH # define ISSLASH(C) ((C) == DIRECTORY_SEPARATOR) #endif #ifdef HAVE_GETC_UNLOCKED # undef getc # define getc getc_unlocked #endif #ifdef __cplusplus /* When compiling with "gcc -x c++", produce a function with C linkage. */ extern "C" const char * locale_charset (void); #endif /* The following static variable is declared 'volatile' to avoid a possible multithread problem in the function get_charset_aliases. If we are running in a threaded environment, and if two threads initialize 'charset_aliases' simultaneously, both will produce the same value, and everything will be ok if the two assignments to 'charset_aliases' are atomic. But I don't know what will happen if the two assignments mix. */ #if __STDC__ != 1 # define volatile /* empty */ #endif /* Pointer to the contents of the charset.alias file, if it has already been read, else NULL. Its format is: ALIAS_1 '\0' CANONICAL_1 '\0' ... ALIAS_n '\0' CANONICAL_n '\0' '\0' */ static const char * volatile charset_aliases; /* Return a pointer to the contents of the charset.alias file. */ static const char * get_charset_aliases () { const char *cp; cp = charset_aliases; if (cp == NULL) { #if !defined WIN32 FILE *fp; const char *dir = LIBDIR; const char *base = "charset.alias"; char *file_name; /* Concatenate dir and base into freshly allocated file_name. */ { size_t dir_len = strlen (dir); size_t base_len = strlen (base); int add_slash = (dir_len > 0 && !ISSLASH (dir[dir_len - 1])); file_name = (char *) malloc (dir_len + add_slash + base_len + 1); if (file_name != NULL) { memcpy (file_name, dir, dir_len); if (add_slash) file_name[dir_len] = DIRECTORY_SEPARATOR; memcpy (file_name + dir_len + add_slash, base, base_len + 1); } } if (file_name == NULL || (fp = fopen (file_name, "r")) == NULL) /* Out of memory or file not found, treat it as empty. */ cp = ""; else { /* Parse the file's contents. */ int c; char buf1[50+1]; char buf2[50+1]; char *res_ptr = NULL; size_t res_size = 0; size_t l1, l2; for (;;) { c = getc (fp); if (c == EOF) break; if (c == '\n' || c == ' ' || c == '\t') continue; if (c == '#') { /* Skip comment, to end of line. */ do c = getc (fp); while (!(c == EOF || c == '\n')); if (c == EOF) break; continue; } ungetc (c, fp); if (fscanf (fp, "%50s %50s", buf1, buf2) < 2) break; l1 = strlen (buf1); l2 = strlen (buf2); if (res_size == 0) { res_size = l1 + 1 + l2 + 1; res_ptr = (char *) malloc (res_size + 1); } else { res_size += l1 + 1 + l2 + 1; res_ptr = (char *) realloc (res_ptr, res_size + 1); } if (res_ptr == NULL) { /* Out of memory. */ res_size = 0; break; } strcpy (res_ptr + res_size - (l2 + 1) - (l1 + 1), buf1); strcpy (res_ptr + res_size - (l2 + 1), buf2); } fclose (fp); if (res_size == 0) cp = ""; else { *(res_ptr + res_size) = '\0'; cp = res_ptr; } } if (file_name != NULL) free (file_name); #else /* To avoid the troubles of installing a separate file in the same directory as the DLL and of retrieving the DLL's directory at runtime, simply inline the aliases here. */ # if defined WIN32 cp = "CP936" "\0" "GBK" "\0" "CP1361" "\0" "JOHAB" "\0" "CP20127" "\0" "ASCII" "\0" "CP20866" "\0" "KOI8-R" "\0" "CP21866" "\0" "KOI8-RU" "\0" "CP28591" "\0" "ISO-8859-1" "\0" "CP28592" "\0" "ISO-8859-2" "\0" "CP28593" "\0" "ISO-8859-3" "\0" "CP28594" "\0" "ISO-8859-4" "\0" "CP28595" "\0" "ISO-8859-5" "\0" "CP28596" "\0" "ISO-8859-6" "\0" "CP28597" "\0" "ISO-8859-7" "\0" "CP28598" "\0" "ISO-8859-8" "\0" "CP28599" "\0" "ISO-8859-9" "\0" "CP28605" "\0" "ISO-8859-15" "\0"; # endif #endif charset_aliases = cp; } return cp; } /* Determine the current locale's character encoding, and canonicalize it into one of the canonical names listed in config.charset. The result must not be freed; it is statically allocated. If the canonical name cannot be determined, the result is a non-canonical name. */ #ifdef STATIC STATIC #endif const char * locale_charset () { const char *codeset; const char *aliases; #if !(defined WIN32 || defined OS2) # if HAVE_LANGINFO_CODESET /* Most systems support nl_langinfo (CODESET) nowadays. */ codeset = nl_langinfo (CODESET); # else /* On old systems which lack it, use setlocale or getenv. */ const char *locale = NULL; /* But most old systems don't have a complete set of locales. Some (like SunOS 4 or DJGPP) have only the C locale. Therefore we don't use setlocale here; it would return "C" when it doesn't support the locale name the user has set. */ # if HAVE_SETLOCALE && 0 locale = setlocale (LC_CTYPE, NULL); # endif if (locale == NULL || locale[0] == '\0') { locale = getenv ("LC_ALL"); if (locale == NULL || locale[0] == '\0') { locale = getenv ("LC_CTYPE"); if (locale == NULL || locale[0] == '\0') locale = getenv ("LANG"); } } /* On some old systems, one used to set locale = "iso8859_1". On others, you set it to "language_COUNTRY.charset". In any case, we resolve it through the charset.alias file. */ codeset = locale; # endif #elif defined WIN32 static char buf[2 + 10 + 1]; /* Woe32 has a function returning the locale's codepage as a number. */ sprintf (buf, "CP%u", GetACP ()); codeset = buf; #elif defined OS2 const char *locale; static char buf[2 + 10 + 1]; ULONG cp[3]; ULONG cplen; /* Allow user to override the codeset, as set in the operating system, with standard language environment variables. */ locale = getenv ("LC_ALL"); if (locale == NULL || locale[0] == '\0') { locale = getenv ("LC_CTYPE"); if (locale == NULL || locale[0] == '\0') locale = getenv ("LANG"); } if (locale != NULL && locale[0] != '\0') { /* If the locale name contains an encoding after the dot, return it. */ const char *dot = strchr (locale, '.'); if (dot != NULL) { const char *modifier; dot++; /* Look for the possible @... trailer and remove it, if any. */ modifier = strchr (dot, '@'); if (modifier == NULL) return dot; if (modifier - dot < sizeof (buf)) { memcpy (buf, dot, modifier - dot); buf [modifier - dot] = '\0'; return buf; } } /* Resolve through the charset.alias file. */ codeset = locale; } else { /* OS/2 has a function returning the locale's codepage as a number. */ if (DosQueryCp (sizeof (cp), cp, &cplen)) codeset = ""; else { sprintf (buf, "CP%u", cp[0]); codeset = buf; } } #endif if (codeset == NULL) /* The canonical name cannot be determined. */ codeset = ""; /* Resolve alias. */ for (aliases = get_charset_aliases (); *aliases != '\0'; aliases += strlen (aliases) + 1, aliases += strlen (aliases) + 1) if (strcmp (codeset, aliases) == 0 || (aliases[0] == '*' && aliases[1] == '\0')) { codeset = aliases + strlen (aliases) + 1; break; } /* Don't return an empty string. GNU libc and GNU libiconv interpret the empty string as denoting "the locale's character encoding", thus GNU libiconv would call this function a second time. */ if (codeset[0] == '\0') codeset = "ASCII"; return codeset; }
/* Utility to accept --help and --version options as unobtrusively as possible. Copyright (C) 1993-1994, 1998-1999, 2000, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Jim Meyering. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #include <getopt.h> #if HAVE_STDLIB_H # include <stdlib.h> #endif #include "long-options.h" #include "version-etc.h" static struct option const long_options[] = { {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'v'}, {0, 0, 0, 0} }; /* Process long options --help and --version, but only if argc == 2. Be careful not to gobble up `--'. */ void parse_long_options (int argc, char **argv, const char *command_name, const char *package, const char *version, const char *authors, void (*usage_func)()) { int c; int saved_opterr; saved_opterr = opterr; /* Don't print an error message for unrecognized options. */ opterr = 0; if (argc == 2 && (c = getopt_long (argc, argv, "+", long_options, NULL)) != -1) { switch (c) { case 'h': (*usage_func) (0); case 'v': version_etc (stdout, command_name, package, version, authors); exit (0); default: /* Don't process any other long-named options. */ break; } } /* Restore previous value. */ opterr = saved_opterr; /* Reset this to zero so that getopt internals get initialized from the probably-new parameters when/if getopt is called later. */ optind = 0; }
/* makepath.c -- Ensure that a directory path exists. Copyright (C) 1990, 1997, 1998, 1999, 2000, 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@gnu.ai.mit.edu> and Jim Meyering. */ #if HAVE_CONFIG_H # include <config.h> #endif #if __GNUC__ # define alloca __builtin_alloca #else # if HAVE_ALLOCA_H # include <alloca.h> # else # ifdef _AIX # pragma alloca # else char *alloca (); # endif # endif #endif #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #if HAVE_UNISTD_H # include <unistd.h> #endif #if STAT_MACROS_BROKEN # undef S_ISDIR #endif #if !defined S_ISDIR && defined S_IFDIR # define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif #ifndef S_IRWXUGO # define S_IRWXUGO (S_IRWXU | S_IRWXG | S_IRWXO) #endif #if STDC_HEADERS # include <stdlib.h> #endif #include <errno.h> #ifndef errno extern int errno; #endif #if HAVE_STRING_H # include <string.h> #else # include <strings.h> # ifndef strchr # define strchr index # endif #endif #ifndef S_ISUID # define S_ISUID 04000 #endif #ifndef S_ISGID # define S_ISGID 02000 #endif #ifndef S_ISVTX # define S_ISVTX 01000 #endif #ifndef S_IRUSR # define S_IRUSR 0200 #endif #ifndef S_IWUSR # define S_IWUSR 0200 #endif #ifndef S_IXUSR # define S_IXUSR 0100 #endif #ifndef S_IRWXU # define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) #endif #define WX_USR (S_IWUSR | S_IXUSR) /* Include this before libintl.h so we get our definition of PARAMS. */ #include "makepath.h" #include "gettext.h" #define _(msgid) gettext (msgid) #include "save-cwd.h" #include "dirname.h" #include "error.h" #include "quote.h" #define CLEANUP_CWD \ do \ { \ /* We're done operating on basename_dir. \ Restore working directory. */ \ if (do_chdir) \ { \ if (restore_cwd (&cwd) != 0) \ { \ int _saved_errno = errno; \ error (0, errno, \ _("failed to return to initial working directory")); \ free_cwd (&cwd); \ errno = _saved_errno; \ return 1; \ } \ free_cwd (&cwd); \ } \ } \ while (0) #define CLEANUP \ do \ { \ umask (oldmask); \ CLEANUP_CWD; \ } \ while (0) /* Attempt to create directory DIR (aka DIRPATH) with the specified MODE. If CREATED_DIR_P is non-NULL, set *CREATED_DIR_P to non-zero if this function creates DIR and to zero otherwise. Give a diagnostic and return non-zero if DIR cannot be created or cannot be determined to exist already. Use DIRPATH in any diagnostic, not DIR. Note that if DIR already exists, this function returns zero (indicating success) and sets *CREATED_DIR_P to zero. */ int make_dir (const char *dir, const char *dirpath, mode_t mode, int *created_dir_p) { int fail = 0; int created_dir; created_dir = (mkdir (dir, mode) == 0); if (!created_dir) { struct stat stats; int saved_errno = errno; /* The mkdir and stat calls below may appear to be reversed. They are not. It is important to call mkdir first and then to call stat (to distinguish the three cases) only if mkdir fails. The alternative to this approach is to `stat' each directory, then to call mkdir if it doesn't exist. But if some other process were to create the directory between the stat & mkdir, the mkdir would fail with EEXIST. */ if (stat (dir, &stats)) { error (0, saved_errno, _("cannot create directory %s"), quote (dirpath)); fail = 1; } else if (!S_ISDIR (stats.st_mode)) { error (0, 0, _("%s exists but is not a directory"), quote (dirpath)); fail = 1; } else { /* DIR (aka DIRPATH) already exists and is a directory. */ } } if (created_dir_p) *created_dir_p = created_dir; return fail; } /* Ensure that the directory ARGPATH exists. Create any leading directories that don't already exist, with permissions PARENT_MODE. If the last element of ARGPATH does not exist, create it as a new directory with permissions MODE. If OWNER and GROUP are non-negative, use them to set the UID and GID of any created directories. If VERBOSE_FMT_STRING is nonzero, use it as a printf format string for printing a message after successfully making a directory, with the name of the directory that was just made as an argument. If PRESERVE_EXISTING is non-zero and ARGPATH is an existing directory, then do not attempt to set its permissions and ownership. Return 0 if ARGPATH exists as a directory with the proper ownership and permissions when done, otherwise 1. */ int make_path (const char *argpath, int mode, int parent_mode, uid_t owner, gid_t group, int preserve_existing, const char *verbose_fmt_string) { struct stat stats; int retval = 0; if (stat (argpath, &stats)) { char *slash; int tmp_mode; /* Initial perms for leading dirs. */ int re_protect; /* Should leading dirs be unwritable? */ struct ptr_list { char *dirname_end; struct ptr_list *next; }; struct ptr_list *p, *leading_dirs = NULL; int do_chdir; /* Whether to chdir before each mkdir. */ struct saved_cwd cwd; char *basename_dir; char *dirpath; /* Temporarily relax umask in case it's overly restrictive. */ mode_t oldmask = umask (0); /* Make a copy of ARGPATH that we can scribble NULs on. */ dirpath = (char *) alloca (strlen (argpath) + 1); strcpy (dirpath, argpath); strip_trailing_slashes (dirpath); /* If leading directories shouldn't be writable or executable, or should have set[ug]id or sticky bits set and we are setting their owners, we need to fix their permissions after making them. */ if (((parent_mode & WX_USR) != WX_USR) || ((owner != (uid_t) -1 || group != (gid_t) -1) && (parent_mode & (S_ISUID | S_ISGID | S_ISVTX)) != 0)) { tmp_mode = S_IRWXU; re_protect = 1; } else { tmp_mode = parent_mode; re_protect = 0; } /* If we can record the current working directory, we may be able to do the chdir optimization. */ do_chdir = !save_cwd (&cwd); /* If we've saved the cwd and DIRPATH is an absolute pathname, we must chdir to `/' in order to enable the chdir optimization. So if chdir ("/") fails, turn off the optimization. */ if (do_chdir && *dirpath == '/' && chdir ("/") < 0) do_chdir = 0; slash = dirpath; /* Skip over leading slashes. */ while (*slash == '/') slash++; while (1) { int newly_created_dir; int fail; /* slash points to the leftmost unprocessed component of dirpath. */ basename_dir = slash; slash = strchr (slash, '/'); if (slash == NULL) break; /* If we're *not* doing chdir before each mkdir, then we have to refer to the target using the full (multi-component) directory name. */ if (!do_chdir) basename_dir = dirpath; *slash = '\0'; fail = make_dir (basename_dir, dirpath, tmp_mode, &newly_created_dir); if (fail) { CLEANUP; return 1; } if (newly_created_dir) { if (verbose_fmt_string) error (0, 0, verbose_fmt_string, quote (dirpath)); if ((owner != (uid_t) -1 || group != (gid_t) -1) && chown (basename_dir, owner, group) #if defined AFS && defined EPERM && errno != EPERM #endif ) { error (0, errno, _("cannot change owner and/or group of %s"), quote (dirpath)); CLEANUP; return 1; } if (re_protect) { struct ptr_list *new = (struct ptr_list *) alloca (sizeof (struct ptr_list)); new->dirname_end = slash; new->next = leading_dirs; leading_dirs = new; } } /* If we were able to save the initial working directory, then we can use chdir to change into each directory before creating an entry in that directory. This avoids making stat and mkdir process O(n^2) file name components. */ if (do_chdir && chdir (basename_dir) < 0) { error (0, errno, _("cannot chdir to directory %s"), quote (dirpath)); CLEANUP; return 1; } *slash++ = '/'; /* Avoid unnecessary calls to `stat' when given pathnames containing multiple adjacent slashes. */ while (*slash == '/') slash++; } if (!do_chdir) basename_dir = dirpath; /* Done creating leading directories. Restore original umask. */ umask (oldmask); /* We're done making leading directories. Create the final component of the path. */ if (make_dir (basename_dir, dirpath, mode, NULL)) { CLEANUP; return 1; } if (verbose_fmt_string != NULL) error (0, 0, verbose_fmt_string, quote (dirpath)); if (owner != (uid_t) -1 || group != (gid_t) -1) { if (chown (basename_dir, owner, group) #ifdef AFS && errno != EPERM #endif ) { error (0, errno, _("cannot change owner and/or group of %s"), quote (dirpath)); retval = 1; } } /* The above chown may have turned off some permission bits in MODE. Another reason we may have to use chmod here is that mkdir(2) is required to honor only the file permission bits. In particular, it need not honor the `special' bits, so if MODE includes any special bits, set them here. */ if ((mode & ~S_IRWXUGO) && chmod (basename_dir, mode)) { error (0, errno, _("cannot change permissions of %s"), quote (dirpath)); retval = 1; } CLEANUP_CWD; /* If the mode for leading directories didn't include owner "wx" privileges, we have to reset their protections to the correct value. */ for (p = leading_dirs; p != NULL; p = p->next) { *(p->dirname_end) = '\0'; if (chmod (dirpath, parent_mode)) { error (0, errno, "cannot change permissions of %s", quote (dirpath)); retval = 1; } } } else { /* We get here if the entire path already exists. */ const char *dirpath = argpath; if (!S_ISDIR (stats.st_mode)) { error (0, 0, _("%s exists but is not a directory"), quote (dirpath)); return 1; } if (!preserve_existing) { /* chown must precede chmod because on some systems, chown clears the set[ug]id bits for non-superusers, resulting in incorrect permissions. On System V, users can give away files with chown and then not be able to chmod them. So don't give files away. */ if ((owner != (uid_t) -1 || group != (gid_t) -1) && chown (dirpath, owner, group) #ifdef AFS && errno != EPERM #endif ) { error (0, errno, _("cannot change owner and/or group of %s"), quote (dirpath)); retval = 1; } if (chmod (dirpath, mode)) { error (0, errno, _("cannot change permissions of %s"), quote (dirpath)); retval = 1; } } } return retval; }
/* Determine the number of screen columns needed for a string. Copyright (C) 2000-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Bruno Haible <haible@clisp.cons.org>. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif /* Specification. */ #include "mbswidth.h" /* Get MB_CUR_MAX. */ #include <stdlib.h> #include <string.h> /* Get isprint(). */ #include <ctype.h> /* Get mbstate_t, mbrtowc(), mbsinit(), wcwidth(). */ #if HAVE_WCHAR_H # include <wchar.h> #endif /* Get iswprint(), iswcntrl(). */ #if HAVE_WCTYPE_H # include <wctype.h> #endif #if !defined iswprint && !HAVE_ISWPRINT # define iswprint(wc) 1 #endif #if !defined iswcntrl && !HAVE_ISWCNTRL # define iswcntrl(wc) 0 #endif #ifndef mbsinit # if !HAVE_MBSINIT # define mbsinit(ps) 1 # endif #endif #ifndef HAVE_DECL_WCWIDTH "this configure-time declaration test was not run" #endif #if !HAVE_DECL_WCWIDTH int wcwidth (); #endif #ifndef wcwidth # if !HAVE_WCWIDTH /* wcwidth doesn't exist, so assume all printable characters have width 1. */ # define wcwidth(wc) ((wc) == 0 ? 0 : iswprint (wc) ? 1 : -1) # endif #endif /* Get ISPRINT. */ #if defined (STDC_HEADERS) || (!defined (isascii) && !defined (HAVE_ISASCII)) # define IN_CTYPE_DOMAIN(c) 1 #else # define IN_CTYPE_DOMAIN(c) isascii(c) #endif /* Undefine to protect against the definition in wctype.h of solaris2.6. */ #undef ISPRINT #define ISPRINT(c) (IN_CTYPE_DOMAIN (c) && isprint (c)) #undef ISCNTRL #define ISCNTRL(c) (IN_CTYPE_DOMAIN (c) && iscntrl (c)) /* Returns the number of columns needed to represent the multibyte character string pointed to by STRING. If a non-printable character occurs, and MBSW_REJECT_UNPRINTABLE is specified, -1 is returned. With flags = MBSW_REJECT_INVALID | MBSW_REJECT_UNPRINTABLE, this is the multibyte analogon of the wcswidth function. */ int mbswidth (const char *string, int flags) { return mbsnwidth (string, strlen (string), flags); } /* Returns the number of columns needed to represent the multibyte character string pointed to by STRING of length NBYTES. If a non-printable character occurs, and MBSW_REJECT_UNPRINTABLE is specified, -1 is returned. */ int mbsnwidth (const char *string, size_t nbytes, int flags) { const char *p = string; const char *plimit = p + nbytes; int width; width = 0; #if HAVE_MBRTOWC if (MB_CUR_MAX > 1) { while (p < plimit) switch (*p) { case ' ': case '!': case '"': case '#': case '%': case '&': case '\'': case '(': case ')': case '*': case '+': case ',': case '-': case '.': case '/': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case ':': case ';': case '<': case '=': case '>': case '?': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '[': case '\\': case ']': case '^': case '_': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}': case '~': /* These characters are printable ASCII characters. */ p++; width++; break; default: /* If we have a multibyte sequence, scan it up to its end. */ { mbstate_t mbstate; memset (&mbstate, 0, sizeof mbstate); do { wchar_t wc; size_t bytes; int w; bytes = mbrtowc (&wc, p, plimit - p, &mbstate); if (bytes == (size_t) -1) /* An invalid multibyte sequence was encountered. */ { if (!(flags & MBSW_REJECT_INVALID)) { p++; width++; break; } else return -1; } if (bytes == (size_t) -2) /* An incomplete multibyte character at the end. */ { if (!(flags & MBSW_REJECT_INVALID)) { p = plimit; width++; break; } else return -1; } if (bytes == 0) /* A null wide character was encountered. */ bytes = 1; w = wcwidth (wc); if (w >= 0) /* A printable multibyte character. */ width += w; else /* An unprintable multibyte character. */ if (!(flags & MBSW_REJECT_UNPRINTABLE)) width += (iswcntrl (wc) ? 0 : 1); else return -1; p += bytes; } while (! mbsinit (&mbstate)); } break; } return width; } #endif while (p < plimit) { unsigned char c = (unsigned char) *p++; if (ISPRINT (c)) width++; else if (!(flags & MBSW_REJECT_UNPRINTABLE)) width += (ISCNTRL (c) ? 0 : 1); else return -1; } return width; }
/* md5.c - Functions to compute MD5 message digest of files or memory blocks according to the definition of MD5 in RFC 1321 from April 1992. Copyright (C) 1995, 1996, 2001, 2003 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. Bugs can be reported to bug-glibc@prep.ai.mit.edu. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <sys/types.h> #if STDC_HEADERS || defined _LIBC # include <stdlib.h> # include <string.h> #else # ifndef HAVE_MEMCPY # define memcpy(d, s, n) bcopy ((s), (d), (n)) # endif #endif #include "md5.h" #include "unlocked-io.h" #ifdef _LIBC # include <endian.h> # if __BYTE_ORDER == __BIG_ENDIAN # define WORDS_BIGENDIAN 1 # endif /* We need to keep the namespace clean so define the MD5 function protected using leading __ . */ # define md5_init_ctx __md5_init_ctx # define md5_process_block __md5_process_block # define md5_process_bytes __md5_process_bytes # define md5_finish_ctx __md5_finish_ctx # define md5_read_ctx __md5_read_ctx # define md5_stream __md5_stream # define md5_buffer __md5_buffer #endif #ifdef WORDS_BIGENDIAN # define SWAP(n) \ (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24)) #else # define SWAP(n) (n) #endif #define BLOCKSIZE 4096 /* Ensure that BLOCKSIZE is a multiple of 64. */ #if BLOCKSIZE % 64 != 0 /* FIXME-someday (soon?): use #error instead of this kludge. */ "invalid BLOCKSIZE" #endif /* This array contains the bytes used to pad the buffer to the next 64-byte boundary. (RFC 1321, 3.1: Step 1) */ static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ }; /* Initialize structure containing state of computation. (RFC 1321, 3.3: Step 3) */ void md5_init_ctx (ctx) struct md5_ctx *ctx; { ctx->A = 0x67452301; ctx->B = 0xefcdab89; ctx->C = 0x98badcfe; ctx->D = 0x10325476; ctx->total[0] = ctx->total[1] = 0; ctx->buflen = 0; } /* Put result from CTX in first 16 bytes following RESBUF. The result must be in little endian byte order. IMPORTANT: On some systems it is required that RESBUF is correctly aligned for a 32 bits value. */ void * md5_read_ctx (ctx, resbuf) const struct md5_ctx *ctx; void *resbuf; { ((md5_uint32 *) resbuf)[0] = SWAP (ctx->A); ((md5_uint32 *) resbuf)[1] = SWAP (ctx->B); ((md5_uint32 *) resbuf)[2] = SWAP (ctx->C); ((md5_uint32 *) resbuf)[3] = SWAP (ctx->D); return resbuf; } /* Process the remaining bytes in the internal buffer and the usual prolog according to the standard and write the result to RESBUF. IMPORTANT: On some systems it is required that RESBUF is correctly aligned for a 32 bits value. */ void * md5_finish_ctx (ctx, resbuf) struct md5_ctx *ctx; void *resbuf; { /* Take yet unprocessed bytes into account. */ md5_uint32 bytes = ctx->buflen; size_t pad; /* Now count remaining bytes. */ ctx->total[0] += bytes; if (ctx->total[0] < bytes) ++ctx->total[1]; pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes; memcpy (&ctx->buffer[bytes], fillbuf, pad); /* Put the 64-bit file length in *bits* at the end of the buffer. */ *(md5_uint32 *) &ctx->buffer[bytes + pad] = SWAP (ctx->total[0] << 3); *(md5_uint32 *) &ctx->buffer[bytes + pad + 4] = SWAP ((ctx->total[1] << 3) | (ctx->total[0] >> 29)); /* Process last bytes. */ md5_process_block (ctx->buffer, bytes + pad + 8, ctx); return md5_read_ctx (ctx, resbuf); } /* Compute MD5 message digest for bytes read from STREAM. The resulting message digest number will be written into the 16 bytes beginning at RESBLOCK. */ int md5_stream (stream, resblock) FILE *stream; void *resblock; { struct md5_ctx ctx; char buffer[BLOCKSIZE + 72]; size_t sum; /* Initialize the computation context. */ md5_init_ctx (&ctx); /* Iterate over full file contents. */ while (1) { /* We read the file in blocks of BLOCKSIZE bytes. One call of the computation function processes the whole buffer so that with the next round of the loop another block can be read. */ size_t n; sum = 0; /* Read block. Take care for partial reads. */ while (1) { n = fread (buffer + sum, 1, BLOCKSIZE - sum, stream); sum += n; if (sum == BLOCKSIZE) break; if (n == 0) { /* Check for the error flag IFF N == 0, so that we don't exit the loop after a partial read due to e.g., EAGAIN or EWOULDBLOCK. */ if (ferror (stream)) return 1; goto process_partial_block; } /* We've read at least one byte, so ignore errors. But always check for EOF, since feof may be true even though N > 0. Otherwise, we could end up calling fread after EOF. */ if (feof (stream)) goto process_partial_block; } /* Process buffer with BLOCKSIZE bytes. Note that BLOCKSIZE % 64 == 0 */ md5_process_block (buffer, BLOCKSIZE, &ctx); } process_partial_block:; /* Process any remaining bytes. */ if (sum > 0) md5_process_bytes (buffer, sum, &ctx); /* Construct result in desired memory. */ md5_finish_ctx (&ctx, resblock); return 0; } /* Compute MD5 message digest for LEN bytes beginning at BUFFER. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. */ void * md5_buffer (buffer, len, resblock) const char *buffer; size_t len; void *resblock; { struct md5_ctx ctx; /* Initialize the computation context. */ md5_init_ctx (&ctx); /* Process whole buffer but last len % 64 bytes. */ md5_process_bytes (buffer, len, &ctx); /* Put result in desired memory area. */ return md5_finish_ctx (&ctx, resblock); } void md5_process_bytes (buffer, len, ctx) const void *buffer; size_t len; struct md5_ctx *ctx; { /* When we already have some bits in our internal buffer concatenate both inputs first. */ if (ctx->buflen != 0) { size_t left_over = ctx->buflen; size_t add = 128 - left_over > len ? len : 128 - left_over; memcpy (&ctx->buffer[left_over], buffer, add); ctx->buflen += add; if (ctx->buflen > 64) { md5_process_block (ctx->buffer, ctx->buflen & ~63, ctx); ctx->buflen &= 63; /* The regions in the following copy operation cannot overlap. */ memcpy (ctx->buffer, &ctx->buffer[(left_over + add) & ~63], ctx->buflen); } buffer = (const char *) buffer + add; len -= add; } /* Process available complete blocks. */ if (len >= 64) { #if !_STRING_ARCH_unaligned /* To check alignment gcc has an appropriate operator. Other compilers don't. */ # if __GNUC__ >= 2 # define UNALIGNED_P(p) (((md5_uintptr) p) % __alignof__ (md5_uint32) != 0) # else # define UNALIGNED_P(p) (((md5_uintptr) p) % sizeof (md5_uint32) != 0) # endif if (UNALIGNED_P (buffer)) while (len > 64) { md5_process_block (memcpy (ctx->buffer, buffer, 64), 64, ctx); buffer = (const char *) buffer + 64; len -= 64; } else #endif { md5_process_block (buffer, len & ~63, ctx); buffer = (const char *) buffer + (len & ~63); len &= 63; } } /* Move remaining bytes in internal buffer. */ if (len > 0) { size_t left_over = ctx->buflen; memcpy (&ctx->buffer[left_over], buffer, len); left_over += len; if (left_over >= 64) { md5_process_block (ctx->buffer, 64, ctx); left_over -= 64; memcpy (ctx->buffer, &ctx->buffer[64], left_over); } ctx->buflen = left_over; } } /* These are the four functions used in the four steps of the MD5 algorithm and defined in the RFC 1321. The first function is a little bit optimized (as found in Colin Plumbs public domain implementation). */ /* #define FF(b, c, d) ((b & c) | (~b & d)) */ #define FF(b, c, d) (d ^ (b & (c ^ d))) #define FG(b, c, d) FF (d, b, c) #define FH(b, c, d) (b ^ c ^ d) #define FI(b, c, d) (c ^ (b | ~d)) /* Process LEN bytes of BUFFER, accumulating context into CTX. It is assumed that LEN % 64 == 0. */ void md5_process_block (buffer, len, ctx) const void *buffer; size_t len; struct md5_ctx *ctx; { md5_uint32 correct_words[16]; const md5_uint32 *words = buffer; size_t nwords = len / sizeof (md5_uint32); const md5_uint32 *endp = words + nwords; md5_uint32 A = ctx->A; md5_uint32 B = ctx->B; md5_uint32 C = ctx->C; md5_uint32 D = ctx->D; /* First increment the byte count. RFC 1321 specifies the possible length of the file up to 2^64 bits. Here we only compute the number of bytes. Do a double word increment. */ ctx->total[0] += len; if (ctx->total[0] < len) ++ctx->total[1]; /* Process all bytes in the buffer with 64 bytes in each round of the loop. */ while (words < endp) { md5_uint32 *cwp = correct_words; md5_uint32 A_save = A; md5_uint32 B_save = B; md5_uint32 C_save = C; md5_uint32 D_save = D; /* First round: using the given function, the context and a constant the next context is computed. Because the algorithms processing unit is a 32-bit word and it is determined to work on words in little endian byte order we perhaps have to change the byte order before the computation. To reduce the work for the next steps we store the swapped words in the array CORRECT_WORDS. */ #define OP(a, b, c, d, s, T) \ do \ { \ a += FF (b, c, d) + (*cwp++ = SWAP (*words)) + T; \ ++words; \ a = rol (a, s); \ a += b; \ } \ while (0) /* Before we start, one word to the strange constants. They are defined in RFC 1321 as T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64, or perl -e 'foreach(1..64){printf "0x%08x\n", int (4294967296 * abs (sin $_))}' */ /* Round 1. */ OP (A, B, C, D, 7, 0xd76aa478); OP (D, A, B, C, 12, 0xe8c7b756); OP (C, D, A, B, 17, 0x242070db); OP (B, C, D, A, 22, 0xc1bdceee); OP (A, B, C, D, 7, 0xf57c0faf); OP (D, A, B, C, 12, 0x4787c62a); OP (C, D, A, B, 17, 0xa8304613); OP (B, C, D, A, 22, 0xfd469501); OP (A, B, C, D, 7, 0x698098d8); OP (D, A, B, C, 12, 0x8b44f7af); OP (C, D, A, B, 17, 0xffff5bb1); OP (B, C, D, A, 22, 0x895cd7be); OP (A, B, C, D, 7, 0x6b901122); OP (D, A, B, C, 12, 0xfd987193); OP (C, D, A, B, 17, 0xa679438e); OP (B, C, D, A, 22, 0x49b40821); /* For the second to fourth round we have the possibly swapped words in CORRECT_WORDS. Redefine the macro to take an additional first argument specifying the function to use. */ #undef OP #define OP(f, a, b, c, d, k, s, T) \ do \ { \ a += f (b, c, d) + correct_words[k] + T; \ a = rol (a, s); \ a += b; \ } \ while (0) /* Round 2. */ OP (FG, A, B, C, D, 1, 5, 0xf61e2562); OP (FG, D, A, B, C, 6, 9, 0xc040b340); OP (FG, C, D, A, B, 11, 14, 0x265e5a51); OP (FG, B, C, D, A, 0, 20, 0xe9b6c7aa); OP (FG, A, B, C, D, 5, 5, 0xd62f105d); OP (FG, D, A, B, C, 10, 9, 0x02441453); OP (FG, C, D, A, B, 15, 14, 0xd8a1e681); OP (FG, B, C, D, A, 4, 20, 0xe7d3fbc8); OP (FG, A, B, C, D, 9, 5, 0x21e1cde6); OP (FG, D, A, B, C, 14, 9, 0xc33707d6); OP (FG, C, D, A, B, 3, 14, 0xf4d50d87); OP (FG, B, C, D, A, 8, 20, 0x455a14ed); OP (FG, A, B, C, D, 13, 5, 0xa9e3e905); OP (FG, D, A, B, C, 2, 9, 0xfcefa3f8); OP (FG, C, D, A, B, 7, 14, 0x676f02d9); OP (FG, B, C, D, A, 12, 20, 0x8d2a4c8a); /* Round 3. */ OP (FH, A, B, C, D, 5, 4, 0xfffa3942); OP (FH, D, A, B, C, 8, 11, 0x8771f681); OP (FH, C, D, A, B, 11, 16, 0x6d9d6122); OP (FH, B, C, D, A, 14, 23, 0xfde5380c); OP (FH, A, B, C, D, 1, 4, 0xa4beea44); OP (FH, D, A, B, C, 4, 11, 0x4bdecfa9); OP (FH, C, D, A, B, 7, 16, 0xf6bb4b60); OP (FH, B, C, D, A, 10, 23, 0xbebfbc70); OP (FH, A, B, C, D, 13, 4, 0x289b7ec6); OP (FH, D, A, B, C, 0, 11, 0xeaa127fa); OP (FH, C, D, A, B, 3, 16, 0xd4ef3085); OP (FH, B, C, D, A, 6, 23, 0x04881d05); OP (FH, A, B, C, D, 9, 4, 0xd9d4d039); OP (FH, D, A, B, C, 12, 11, 0xe6db99e5); OP (FH, C, D, A, B, 15, 16, 0x1fa27cf8); OP (FH, B, C, D, A, 2, 23, 0xc4ac5665); /* Round 4. */ OP (FI, A, B, C, D, 0, 6, 0xf4292244); OP (FI, D, A, B, C, 7, 10, 0x432aff97); OP (FI, C, D, A, B, 14, 15, 0xab9423a7); OP (FI, B, C, D, A, 5, 21, 0xfc93a039); OP (FI, A, B, C, D, 12, 6, 0x655b59c3); OP (FI, D, A, B, C, 3, 10, 0x8f0ccc92); OP (FI, C, D, A, B, 10, 15, 0xffeff47d); OP (FI, B, C, D, A, 1, 21, 0x85845dd1); OP (FI, A, B, C, D, 8, 6, 0x6fa87e4f); OP (FI, D, A, B, C, 15, 10, 0xfe2ce6e0); OP (FI, C, D, A, B, 6, 15, 0xa3014314); OP (FI, B, C, D, A, 13, 21, 0x4e0811a1); OP (FI, A, B, C, D, 4, 6, 0xf7537e82); OP (FI, D, A, B, C, 11, 10, 0xbd3af235); OP (FI, C, D, A, B, 2, 15, 0x2ad7d2bb); OP (FI, B, C, D, A, 9, 21, 0xeb86d391); /* Add the starting values of the context. */ A += A_save; B += B_save; C += C_save; D += D_save; } /* Put checksum in context given as argument. */ ctx->A = A; ctx->B = B; ctx->C = C; ctx->D = D; }
/* Case-insensitive buffer comparator. Copyright (C) 1996, 1997, 2000 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Jim Meyering. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <sys/types.h> #include <ctype.h> #if defined (STDC_HEADERS) || (!defined (isascii) && !defined (HAVE_ISASCII)) # define IN_CTYPE_DOMAIN(Char) 1 #else # define IN_CTYPE_DOMAIN(Char) isascii(Char) #endif #define ISLOWER(Char) (IN_CTYPE_DOMAIN (Char) && islower (Char)) #if _LIBC || STDC_HEADERS # define TOUPPER(Char) toupper (Char) #else # define TOUPPER(Char) (ISLOWER (Char) ? toupper (Char) : (Char)) #endif #include "memcasecmp.h" /* Like memcmp, but ignore differences in case. Convert to upper case (not lower) before comparing so that join -i works with sort -f. */ int memcasecmp (const void *vs1, const void *vs2, size_t n) { unsigned int i; unsigned char const *s1 = (unsigned char const *) vs1; unsigned char const *s2 = (unsigned char const *) vs2; for (i = 0; i < n; i++) { unsigned char u1 = *s1++; unsigned char u2 = *s2++; if (TOUPPER (u1) != TOUPPER (u2)) return TOUPPER (u1) - TOUPPER (u2); } return 0; }
#define inttostr offtostr #define inttype off_t #include "inttostr.c"
/* path-concat.c -- concatenate two arbitrary pathnames Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Jim Meyering. */ #if HAVE_CONFIG_H # include <config.h> #endif #ifndef HAVE_MEMPCPY # define mempcpy(D, S, N) ((void *) ((char *) memcpy (D, S, N) + (N))) #endif #include <stdio.h> #if HAVE_STRING_H # if !STDC_HEADERS && HAVE_MEMORY_H # include <memory.h> # endif # include <string.h> #else # if HAVE_STRINGS_H # include <strings.h> # endif #endif #if HAVE_STDLIB_H # include <stdlib.h> #endif #if HAVE_UNISTD_H # include <unistd.h> #endif #ifndef HAVE_DECL_MALLOC "this configure-time declaration test was not run" #endif #if !HAVE_DECL_MALLOC char *malloc (); #endif #ifndef strdup char *strdup (); #endif #include "dirname.h" #include "xalloc.h" #include "path-concat.h" /* Concatenate two pathname components, DIR and BASE, in newly-allocated storage and return the result. Return 0 if out of memory. Add a slash between DIR and BASE in the result if neither would contribute one. If each would contribute at least one, elide one from the end of DIR. Otherwise, simply concatenate DIR and BASE. In any case, if BASE_IN_RESULT is non-NULL, set *BASE_IN_RESULT to point to the copy of BASE in the returned concatenation. DIR may be NULL, BASE must not be. Return NULL if memory is exhausted. */ char * path_concat (const char *dir, const char *base, char **base_in_result) { char *p; char *p_concat; size_t baselen; size_t dirlen; if (!dir) { p_concat = strdup (base); if (base_in_result) *base_in_result = p_concat; return p_concat; } /* DIR is not empty. */ baselen = base_len (base); dirlen = strlen (dir); p_concat = malloc (dirlen + baselen + 2); if (!p_concat) return 0; p = mempcpy (p_concat, dir, dirlen); if (FILESYSTEM_PREFIX_LEN (dir) < dirlen) { if (ISSLASH (*(p - 1)) && ISSLASH (*base)) --p; else if (!ISSLASH (*(p - 1)) && !ISSLASH (*base)) *p++ = DIRECTORY_SEPARATOR; } if (base_in_result) *base_in_result = p; memcpy (p, base, baselen); p[baselen] = '\0'; return p_concat; } /* Same, but die when memory is exhausted. */ char * xpath_concat (const char *dir, const char *base, char **base_in_result) { char *res = path_concat (dir, base, base_in_result); if (! res) xalloc_die (); return res; }
/* Calculate the size of physical memory. Copyright 2000, 2001, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert. */ #if HAVE_CONFIG_H # include <config.h> #endif #if HAVE_UNISTD_H # include <unistd.h> #endif #if HAVE_SYS_PSTAT_H # include <sys/pstat.h> #endif #if HAVE_SYS_SYSMP_H # include <sys/sysmp.h> #endif #if HAVE_SYS_SYSINFO_H && HAVE_MACHINE_HAL_SYSINFO_H # include <sys/sysinfo.h> # include <machine/hal_sysinfo.h> #endif #if HAVE_SYS_TABLE_H # include <sys/table.h> #endif #include <sys/types.h> #if HAVE_SYS_PARAM_H # include <sys/param.h> #endif #if HAVE_SYS_SYSCTL_H # include <sys/sysctl.h> #endif #if HAVE_SYS_SYSTEMCFG_H # include <sys/systemcfg.h> #endif #ifdef _WIN32 # define WIN32_LEAN_AND_MEAN # include <windows.h> /* MEMORYSTATUSEX is missing from older windows headers, so define a local replacement. */ typedef struct { DWORD dwLength; DWORD dwMemoryLoad; DWORDLONG ullTotalPhys; DWORDLONG ullAvailPhys; DWORDLONG ullTotalPageFile; DWORDLONG ullAvailPageFile; DWORDLONG ullTotalVirtual; DWORDLONG ullAvailVirtual; DWORDLONG ullAvailExtendedVirtual; } lMEMORYSTATUSEX; typedef WINBOOL (WINAPI *PFN_MS_EX) (lMEMORYSTATUSEX*); #endif #include "physmem.h" #define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0])) /* Return the total amount of physical memory. */ double physmem_total () { #if defined _SC_PHYS_PAGES && defined _SC_PAGESIZE { /* This works on linux-gnu, solaris2 and cygwin. */ double pages = sysconf (_SC_PHYS_PAGES); double pagesize = sysconf (_SC_PAGESIZE); if (0 <= pages && 0 <= pagesize) return pages * pagesize; } #endif #if HAVE_PSTAT_GETSTATIC { /* This works on hpux11. */ struct pst_static pss; if (0 <= pstat_getstatic (&pss, sizeof pss, 1, 0)) { double pages = pss.physical_memory; double pagesize = pss.page_size; if (0 <= pages && 0 <= pagesize) return pages * pagesize; } } #endif #if HAVE_SYSMP && defined MP_SAGET && defined MPSA_RMINFO && defined _SC_PAGESIZE { /* This works on irix6. */ struct rminfo realmem; if (sysmp (MP_SAGET, MPSA_RMINFO, &realmem, sizeof realmem) == 0) { double pagesize = sysconf (_SC_PAGESIZE); double pages = realmem.physmem; if (0 <= pages && 0 <= pagesize) return pages * pagesize; } } #endif #if HAVE_GETSYSINFO && defined GSI_PHYSMEM { /* This works on Tru64 UNIX V4/5. */ int physmem; if (getsysinfo (GSI_PHYSMEM, (caddr_t) &physmem, sizeof (physmem), NULL, NULL, NULL) == 1) { double kbytes = physmem; if (0 <= kbytes) return kbytes * 1024.0; } } #endif #if HAVE_SYSCTL && defined HW_PHYSMEM { /* This works on *bsd and darwin. */ unsigned int physmem; size_t len = sizeof physmem; static int mib[2] = { CTL_HW, HW_PHYSMEM }; if (sysctl (mib, ARRAY_SIZE (mib), &physmem, &len, NULL, 0) == 0 && len == sizeof (physmem)) return (double) physmem; } #endif #if HAVE__SYSTEM_CONFIGURATION /* This works on AIX. */ return _system_configuration.physmem; #endif #if defined _WIN32 { /* this works on windows */ PFN_MS_EX pfnex; HMODULE h = GetModuleHandle ("kernel32.dll"); if (!h) return 0.0; /* Use GlobalMemoryStatusEx if available. */ if ((pfnex = (PFN_MS_EX) GetProcAddress (h, "GlobalMemoryStatusEx"))) { lMEMORYSTATUSEX lms_ex; lms_ex.dwLength = sizeof lms_ex; if (!pfnex (&lms_ex)) return 0.0; return (double) lms_ex.ullTotalPhys; } /* Fall back to GlobalMemoryStatus which is always available. but returns wrong results for physical memory > 4GB. */ else { MEMORYSTATUS ms; GlobalMemoryStatus (&ms); return (double) ms.dwTotalPhys; } } #endif /* Guess 64 MB. It's probably an older host, so guess small. */ return 64 * 1024 * 1024; } /* Return the amount of physical memory available. */ double physmem_available () { #if defined _SC_AVPHYS_PAGES && defined _SC_PAGESIZE { /* This works on linux-gnu, solaris2 and cygwin. */ double pages = sysconf (_SC_AVPHYS_PAGES); double pagesize = sysconf (_SC_PAGESIZE); if (0 <= pages && 0 <= pagesize) return pages * pagesize; } #endif #if HAVE_PSTAT_GETSTATIC && HAVE_PSTAT_GETDYNAMIC { /* This works on hpux11. */ struct pst_static pss; struct pst_dynamic psd; if (0 <= pstat_getstatic (&pss, sizeof pss, 1, 0) && 0 <= pstat_getdynamic (&psd, sizeof psd, 1, 0)) { double pages = psd.psd_free; double pagesize = pss.page_size; if (0 <= pages && 0 <= pagesize) return pages * pagesize; } } #endif #if HAVE_SYSMP && defined MP_SAGET && defined MPSA_RMINFO && defined _SC_PAGESIZE { /* This works on irix6. */ struct rminfo realmem; if (sysmp (MP_SAGET, MPSA_RMINFO, &realmem, sizeof realmem) == 0) { double pagesize = sysconf (_SC_PAGESIZE); double pages = realmem.availrmem; if (0 <= pages && 0 <= pagesize) return pages * pagesize; } } #endif #if HAVE_TABLE && defined TBL_VMSTATS { /* This works on Tru64 UNIX V4/5. */ struct tbl_vmstats vmstats; if (table (TBL_VMSTATS, 0, &vmstats, 1, sizeof (vmstats)) == 1) { double pages = vmstats.free_count; double pagesize = vmstats.pagesize; if (0 <= pages && 0 <= pagesize) return pages * pagesize; } } #endif #if HAVE_SYSCTL && defined HW_USERMEM { /* This works on *bsd and darwin. */ unsigned int usermem; size_t len = sizeof usermem; static int mib[2] = { CTL_HW, HW_USERMEM }; if (sysctl (mib, ARRAY_SIZE (mib), &usermem, &len, NULL, 0) == 0 && len == sizeof (usermem)) return (double) usermem; } #endif #if defined _WIN32 { /* this works on windows */ PFN_MS_EX pfnex; HMODULE h = GetModuleHandle ("kernel32.dll"); if (!h) return 0.0; /* Use GlobalMemoryStatusEx if available. */ if ((pfnex = (PFN_MS_EX) GetProcAddress (h, "GlobalMemoryStatusEx"))) { lMEMORYSTATUSEX lms_ex; lms_ex.dwLength = sizeof lms_ex; if (!pfnex (&lms_ex)) return 0.0; return (double) lms_ex.ullAvailPhys; } /* Fall back to GlobalMemoryStatus which is always available. but returns wrong results for physical memory > 4GB */ else { MEMORYSTATUS ms; GlobalMemoryStatus (&ms); return (double) ms.dwAvailPhys; } } #endif /* Guess 25% of physical memory. */ return physmem_total () / 4; } #if DEBUG # include <stdio.h> # include <stdlib.h> int main () { printf ("%12.f %12.f\n", physmem_total (), physmem_available ()); exit (0); } #endif /* DEBUG */ /* Local Variables: compile-command: "gcc -DDEBUG -DHAVE_CONFIG_H -I.. -g -O -Wall -W physmem.c" End: */
/* quote.c - quote arguments for output Copyright (C) 1998, 1999, 2000, 2001, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert <eggert@twinsun.com> */ #if HAVE_CONFIG_H # include <config.h> #endif #if HAVE_STDDEF_H # include <stddef.h> /* For the definition of size_t on windows w/MSVC. */ #endif #include <sys/types.h> #include "quotearg.h" #include "quote.h" /* Return an unambiguous printable representation of NAME, allocated in slot N, suitable for diagnostics. */ char const * quote_n (int n, char const *name) { return quotearg_n_style (n, locale_quoting_style, name); } /* Return an unambiguous printable representation of NAME, suitable for diagnostics. */ char const * quote (char const *name) { return quote_n (0, name); }
/* readtokens.c -- Functions for reading tokens from an input stream. Copyright (C) 1990-1991, 1999, 2001, 2003 Jim Meyering. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Written by Jim Meyering. */ /* This almost supercedes xreadline stuff -- using delim="\n" gives the same functionality, except that these functions would never return empty lines. To Do: - To allow '\0' as a delimiter, I will have to change interfaces to permit specification of delimiter-string length. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #ifdef STDC_HEADERS # include <stdlib.h> #endif #if defined (STDC_HEADERS) || defined(HAVE_STRING_H) # include <string.h> /* An ANSI string.h and pre-ANSI memory.h might conflict. */ # if !defined (STDC_HEADERS) && defined (HAVE_MEMORY_H) # include <memory.h> # endif /* not STDC_HEADERS and HAVE_MEMORY_H */ #else /* not STDC_HEADERS and not HAVE_STRING_H */ # include <strings.h> /* memory.h and strings.h conflict on some systems. */ #endif /* not STDC_HEADERS and not HAVE_STRING_H */ #include "readtokens.h" #include "unlocked-io.h" #include "xalloc.h" #define STREQ(a,b) ((a) == (b) || ((a) && (b) && *(a) == *(b) \ && strcmp(a, b) == 0)) /* Initialize a tokenbuffer. */ void init_tokenbuffer (tokenbuffer) token_buffer *tokenbuffer; { tokenbuffer->size = INITIAL_TOKEN_LENGTH; tokenbuffer->buffer = ((char *) xmalloc (INITIAL_TOKEN_LENGTH)); } /* Read a token from `stream' into `tokenbuffer'. Upon return, the token is in tokenbuffer->buffer and has a trailing '\0' instead of the original delimiter. The function value is the length of the token not including the final '\0'. When EOF is reached (i.e. on the call after the last token is read), -1 is returned and tokenbuffer isn't modified. This function will work properly on lines containing NUL bytes and on files that aren't newline-terminated. */ long readtoken (FILE *stream, const char *delim, int n_delim, token_buffer *tokenbuffer) { char *p; int c, i, n; static const char *saved_delim = NULL; static char isdelim[256]; int same_delimiters; if (delim == NULL && saved_delim == NULL) abort (); same_delimiters = 0; if (delim != saved_delim && saved_delim != NULL) { same_delimiters = 1; for (i = 0; i < n_delim; i++) { if (delim[i] != saved_delim[i]) { same_delimiters = 0; break; } } } if (!same_delimiters) { const char *t; unsigned int j; saved_delim = delim; for (j = 0; j < sizeof (isdelim); j++) isdelim[j] = 0; for (t = delim; *t; t++) isdelim[(unsigned char) *t] = 1; } p = tokenbuffer->buffer; n = tokenbuffer->size; i = 0; /* FIXME: don't fool with this caching BS. Use strchr instead. */ /* skip over any leading delimiters */ for (c = getc (stream); c >= 0 && isdelim[c]; c = getc (stream)) { /* empty */ } for (;;) { if (i >= n) { n = 3 * (n / 2 + 1); p = xrealloc (p, (unsigned int) n); } if (c < 0) { if (i == 0) return (-1); p[i] = 0; break; } if (isdelim[c]) { p[i] = 0; break; } p[i++] = c; c = getc (stream); } tokenbuffer->buffer = p; tokenbuffer->size = n; return (i); } /* Return a NULL-terminated array of pointers to tokens read from `stream.' The number of tokens is returned as the value of the function. All storage is obtained through calls to malloc(); %%% Question: is it worth it to do a single %%% realloc() of `tokens' just before returning? */ int readtokens (FILE *stream, int projected_n_tokens, const char *delim, int n_delim, char ***tokens_out, long **token_lengths) { token_buffer tb, *token = &tb; int token_length; char **tokens; long *lengths; int sz; int n_tokens; n_tokens = 0; if (projected_n_tokens > 0) projected_n_tokens++; /* add one for trailing NULL pointer */ else projected_n_tokens = 64; sz = projected_n_tokens; tokens = (char **) xmalloc (sz * sizeof (char *)); lengths = (long *) xmalloc (sz * sizeof (long)); init_tokenbuffer (token); for (;;) { char *tmp; token_length = readtoken (stream, delim, n_delim, token); if (n_tokens >= sz) { sz *= 2; tokens = (char **) xrealloc (tokens, sz * sizeof (char *)); lengths = (long *) xrealloc (lengths, sz * sizeof (long)); } if (token_length < 0) { /* don't increment n_tokens for NULL entry */ tokens[n_tokens] = NULL; lengths[n_tokens] = -1; break; } tmp = (char *) xmalloc ((token_length + 1) * sizeof (char)); lengths[n_tokens] = token_length; tokens[n_tokens] = strncpy (tmp, token->buffer, (unsigned) (token_length + 1)); n_tokens++; } free (token->buffer); *tokens_out = tokens; if (token_lengths != NULL) *token_lengths = lengths; return n_tokens; }
/* Determine whether string value is affirmation or negative response according to current locale's data. Copyright (C) 1996, 1998, 2000, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #if STDC_HEADERS || _LIBC # include <stddef.h> # include <stdlib.h> #else # ifndef NULL # define NULL 0 # endif #endif #if ENABLE_NLS # include <sys/types.h> # if HAVE_LIMITS_H # include <limits.h> # endif # include <regex.h> # include "gettext.h" # define _(msgid) gettext (msgid) static int try (const char *response, const char *pattern, const int match, const int nomatch, const char **lastp, regex_t *re) { if (pattern != *lastp) { /* The pattern has changed. */ if (*lastp) { /* Free the old compiled pattern. */ regfree (re); *lastp = NULL; } /* Compile the pattern and cache it for future runs. */ if (regcomp (re, pattern, REG_EXTENDED) != 0) return -1; *lastp = pattern; } /* See if the regular expression matches RESPONSE. */ return regexec (re, response, 0, NULL, 0) == 0 ? match : nomatch; } #endif int rpmatch (const char *response) { #if ENABLE_NLS /* Match against one of the response patterns, compiling the pattern first if necessary. */ /* We cache the response patterns and compiled regexps here. */ static const char *yesexpr, *noexpr; static regex_t yesre, nore; int result; return ((result = try (response, _("^[yY]"), 1, 0, &yesexpr, &yesre)) ? result : try (response, _("^[nN]"), 0, -1, &noexpr, &nore)); #else /* Test against "^[yY]" and "^[nN]", hardcoded to avoid requiring regex */ return (*response == 'y' || *response == 'Y' ? 1 : *response == 'n' || *response == 'N' ? 0 : -1); #endif }
/* An interface to read and write that retries after interrupts. Copyright (C) 1993, 1994, 1998, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif /* Get ssize_t. */ #include <sys/types.h> #if HAVE_UNISTD_H # include <unistd.h> #endif #include <errno.h> #ifndef errno extern int errno; #endif #ifdef EINTR # define IS_EINTR(x) ((x) == EINTR) #else # define IS_EINTR(x) 0 #endif #include <limits.h> #ifndef CHAR_BIT # define CHAR_BIT 8 #endif /* The extra casts work around common compiler bugs. */ #define TYPE_SIGNED(t) (! ((t) 0 < (t) -1)) /* The outer cast is needed to work around a bug in Cray C 5.0.3.0. It is necessary at least when t == time_t. */ #define TYPE_MINIMUM(t) ((t) (TYPE_SIGNED (t) \ ? ~ (t) 0 << (sizeof (t) * CHAR_BIT - 1) : (t) 0)) #define TYPE_MAXIMUM(t) ((t) (~ (t) 0 - TYPE_MINIMUM (t))) #ifndef INT_MAX # define INT_MAX TYPE_MAXIMUM (int) #endif #ifdef SAFE_WRITE # include "safe-write.h" # define safe_rw safe_write # define rw write #else # include "safe-read.h" # define safe_rw safe_read # define rw read # undef const # define const /* empty */ #endif /* Read(write) up to COUNT bytes at BUF from(to) descriptor FD, retrying if interrupted. Return the actual number of bytes read(written), zero for EOF, or SAFE_READ_ERROR(SAFE_WRITE_ERROR) upon error. */ size_t safe_rw (int fd, void const *buf, size_t count) { ssize_t result; /* POSIX limits COUNT to SSIZE_MAX, but we limit it further, requiring that COUNT <= INT_MAX, to avoid triggering a bug in Tru64 5.1. When decreasing COUNT, keep the file pointer block-aligned. Note that in any case, read(write) may succeed, yet read(write) fewer than COUNT bytes, so the caller must be prepared to handle partial results. */ if (count > INT_MAX) count = INT_MAX & ~8191; do { result = rw (fd, buf, count); } while (result < 0 && IS_EINTR (errno)); return (size_t) result; }
/* An interface to write that retries after interrupts. Copyright (C) 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #define SAFE_WRITE #include "safe-read.c"
/* Determine whether two file names refer to the same file. Copyright (C) 1997-2000, 2002-2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* written by Jim Meyering */ #if HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #ifdef HAVE_UNISTD_H # include <unistd.h> #endif #if HAVE_STDLIB_H # include <stdlib.h> #endif #include <sys/types.h> #include <sys/stat.h> #include <ctype.h> #include <errno.h> #ifndef errno extern int errno; #endif #if HAVE_STRING_H # include <string.h> #else # include <strings.h> #endif #include "same.h" #include "dirname.h" #include "error.h" #include "xalloc.h" #ifndef HAVE_DECL_FREE "this configure-time declaration test was not run" #endif #if !HAVE_DECL_FREE void free (); #endif #define SAME_INODE(Stat_buf_1, Stat_buf_2) \ ((Stat_buf_1).st_ino == (Stat_buf_2).st_ino \ && (Stat_buf_1).st_dev == (Stat_buf_2).st_dev) /* Return nonzero if SOURCE and DEST point to the same name in the same directory. */ int same_name (const char *source, const char *dest) { /* Compare the basenames. */ char const *source_basename = base_name (source); char const *dest_basename = base_name (dest); size_t source_baselen = base_len (source_basename); size_t dest_baselen = base_len (dest_basename); if (source_baselen == dest_baselen && memcmp (source_basename, dest_basename, dest_baselen) == 0) { struct stat source_dir_stats; struct stat dest_dir_stats; char *source_dirname, *dest_dirname; /* Compare the parent directories (via the device and inode numbers). */ source_dirname = dir_name (source); dest_dirname = dir_name (dest); if (stat (source_dirname, &source_dir_stats)) { /* Shouldn't happen. */ error (1, errno, "%s", source_dirname); } if (stat (dest_dirname, &dest_dir_stats)) { /* Shouldn't happen. */ error (1, errno, "%s", dest_dirname); } free (source_dirname); free (dest_dirname); if (SAME_INODE (source_dir_stats, dest_dir_stats)) return 1; } return 0; }
/* save-cwd.c -- Save and restore current working directory. Copyright (C) 1995, 1997, 1998, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Jim Meyering. */ #if HAVE_CONFIG_H # include "config.h" #endif #include <stdio.h> #ifdef STDC_HEADERS # include <stdlib.h> #endif #if HAVE_UNISTD_H # include <unistd.h> #endif #if HAVE_FCNTL_H # include <fcntl.h> #else # include <sys/file.h> #endif #include <errno.h> #ifndef errno extern int errno; #endif #ifndef O_DIRECTORY # define O_DIRECTORY 0 #endif #include "save-cwd.h" #include "error.h" #include "xgetcwd.h" /* Record the location of the current working directory in CWD so that the program may change to other directories and later use restore_cwd to return to the recorded location. This function may allocate space using malloc (via xgetcwd) or leave a file descriptor open; use free_cwd to perform the necessary free or close. Upon failure, no memory is allocated, any locally opened file descriptors are closed; return non-zero -- in that case, free_cwd need not be called, but doing so is ok. Otherwise, return zero. The `raison d'etre' for this interface is that some systems lack support for fchdir, and getcwd is not robust or as efficient. So, we prefer to use the open/fchdir approach, but fall back on getcwd if necessary. Some systems lack fchdir altogether: OS/2, Cygwin (as of March 2003), SCO Xenix. At least SunOS4 and Irix 5.3 provide the function, yet it doesn't work for partitions on which auditing is enabled. */ int save_cwd (struct saved_cwd *cwd) { static int have_working_fchdir = 1; cwd->desc = -1; cwd->name = NULL; if (have_working_fchdir) { #if HAVE_FCHDIR cwd->desc = open (".", O_RDONLY | O_DIRECTORY); if (cwd->desc < 0) { error (0, errno, "cannot open current directory"); return 1; } # if __sun__ || sun /* On SunOS 4 and IRIX 5.3, fchdir returns EINVAL when auditing is enabled, so we have to fall back to chdir. */ if (fchdir (cwd->desc)) { if (errno == EINVAL) { close (cwd->desc); cwd->desc = -1; have_working_fchdir = 0; } else { error (0, errno, "current directory"); close (cwd->desc); cwd->desc = -1; return 1; } } # endif /* __sun__ || sun */ #else # define fchdir(x) (abort (), 0) have_working_fchdir = 0; #endif } if (!have_working_fchdir) { cwd->name = xgetcwd (); if (cwd->name == NULL) { error (0, errno, "cannot get current directory"); return 1; } } return 0; } /* Change to recorded location, CWD, in directory hierarchy. Upon failure, return nonzero (errno is set by chdir or fchdir). Upon success, return zero. */ int restore_cwd (const struct saved_cwd *cwd) { if (0 <= cwd->desc) return fchdir (cwd->desc) < 0; else return chdir (cwd->name) < 0; } void free_cwd (struct saved_cwd *cwd) { if (cwd->desc >= 0) close (cwd->desc); if (cwd->name) free (cwd->name); }
/* savedir.c -- save the list of files in a directory in a string Copyright 1990, 1997, 1998, 1999, 2000, 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@gnu.ai.mit.edu>. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <sys/types.h> #include <errno.h> #ifndef errno extern int errno; #endif #if HAVE_DIRENT_H # include <dirent.h> #else # define dirent direct # if HAVE_SYS_NDIR_H # include <sys/ndir.h> # endif # if HAVE_SYS_DIR_H # include <sys/dir.h> # endif # if HAVE_NDIR_H # include <ndir.h> # endif #endif #ifdef CLOSEDIR_VOID /* Fake a return value. */ # define CLOSEDIR(d) (closedir (d), 0) #else # define CLOSEDIR(d) closedir (d) #endif #ifdef STDC_HEADERS # include <stdlib.h> # include <string.h> #endif #ifndef NULL # define NULL 0 #endif #include "savedir.h" #include "xalloc.h" /* Return a freshly allocated string containing the filenames in directory DIR, separated by '\0' characters; the end is marked by two '\0' characters in a row. Return NULL (setting errno) if DIR cannot be opened, read, or closed. */ #ifndef NAME_SIZE_DEFAULT # define NAME_SIZE_DEFAULT 512 #endif char * savedir (const char *dir) { DIR *dirp; struct dirent *dp; char *name_space; size_t allocated = NAME_SIZE_DEFAULT; size_t used = 0; int save_errno; dirp = opendir (dir); if (dirp == NULL) return NULL; name_space = xmalloc (allocated); errno = 0; while ((dp = readdir (dirp)) != NULL) { /* Skip "", ".", and "..". "" is returned by at least one buggy implementation: Solaris 2.4 readdir on NFS filesystems. */ char const *entry = dp->d_name; if (entry[entry[0] != '.' ? 0 : entry[1] != '.' ? 1 : 2] != '\0') { size_t entry_size = strlen (entry) + 1; if (used + entry_size < used) xalloc_die (); if (allocated <= used + entry_size) { do { if (2 * allocated < allocated) xalloc_die (); allocated *= 2; } while (allocated <= used + entry_size); name_space = xrealloc (name_space, allocated); } memcpy (name_space + used, entry, entry_size); used += entry_size; } } name_space[used] = '\0'; save_errno = errno; if (CLOSEDIR (dirp) != 0) save_errno = errno; if (save_errno != 0) { free (name_space); errno = save_errno; return NULL; } return name_space; }
/* settime -- set the system clock Copyright (C) 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include "timespec.h" /* Set the system time. */ int settime (struct timespec const *ts) { #if defined CLOCK_REALTIME && HAVE_CLOCK_SETTIME if (clock_settime (CLOCK_REALTIME, ts) == 0) return 0; #endif { struct timeval tv; tv.tv_sec = ts->tv_sec; tv.tv_usec = ts->tv_nsec / 1000; return settimeofday (&tv, 0); } }
/* sha.c - Functions to compute the SHA1 hash (message-digest) of files or blocks of memory. Complies to the NIST specification FIPS-180-1. Copyright (C) 2000, 2001, 2003 Scott G. Miller Credits: Robert Klep <robert@ilse.nl> -- Expansion function fix */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <sys/types.h> #if STDC_HEADERS || defined _LIBC # include <stdlib.h> # include <string.h> #else # ifndef HAVE_MEMCPY # define memcpy(d, s, n) bcopy ((s), (d), (n)) # endif #endif #include "md5.h" #include "sha.h" #include "unlocked-io.h" /* Not-swap is a macro that does an endian swap on architectures that are big-endian, as SHA needs some data in a little-endian format */ #ifdef WORDS_BIGENDIAN # define NOTSWAP(n) (n) # define SWAP(n) \ (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24)) #else # define NOTSWAP(n) \ (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24)) # define SWAP(n) (n) #endif #define BLOCKSIZE 4096 /* Ensure that BLOCKSIZE is a multiple of 64. */ #if BLOCKSIZE % 64 != 0 /* FIXME-someday (soon?): use #error instead of this kludge. */ "invalid BLOCKSIZE" #endif /* This array contains the bytes used to pad the buffer to the next 64-byte boundary. (RFC 1321, 3.1: Step 1) */ static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ }; /* Takes a pointer to a 160 bit block of data (five 32 bit ints) and intializes it to the start constants of the SHA1 algorithm. This must be called before using hash in the call to sha_hash */ void sha_init_ctx (struct sha_ctx *ctx) { ctx->A = 0x67452301; ctx->B = 0xefcdab89; ctx->C = 0x98badcfe; ctx->D = 0x10325476; ctx->E = 0xc3d2e1f0; ctx->total[0] = ctx->total[1] = 0; ctx->buflen = 0; } /* Put result from CTX in first 20 bytes following RESBUF. The result must be in little endian byte order. IMPORTANT: On some systems it is required that RESBUF is correctly aligned for a 32 bits value. */ void * sha_read_ctx (const struct sha_ctx *ctx, void *resbuf) { ((md5_uint32 *) resbuf)[0] = NOTSWAP (ctx->A); ((md5_uint32 *) resbuf)[1] = NOTSWAP (ctx->B); ((md5_uint32 *) resbuf)[2] = NOTSWAP (ctx->C); ((md5_uint32 *) resbuf)[3] = NOTSWAP (ctx->D); ((md5_uint32 *) resbuf)[4] = NOTSWAP (ctx->E); return resbuf; } /* Process the remaining bytes in the internal buffer and the usual prolog according to the standard and write the result to RESBUF. IMPORTANT: On some systems it is required that RESBUF is correctly aligned for a 32 bits value. */ void * sha_finish_ctx (struct sha_ctx *ctx, void *resbuf) { /* Take yet unprocessed bytes into account. */ md5_uint32 bytes = ctx->buflen; size_t pad; /* Now count remaining bytes. */ ctx->total[0] += bytes; if (ctx->total[0] < bytes) ++ctx->total[1]; pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes; memcpy (&ctx->buffer[bytes], fillbuf, pad); /* Put the 64-bit file length in *bits* at the end of the buffer. */ *(md5_uint32 *) &ctx->buffer[bytes + pad + 4] = NOTSWAP (ctx->total[0] << 3); *(md5_uint32 *) &ctx->buffer[bytes + pad] = NOTSWAP ((ctx->total[1] << 3) | (ctx->total[0] >> 29)); /* Process last bytes. */ sha_process_block (ctx->buffer, bytes + pad + 8, ctx); return sha_read_ctx (ctx, resbuf); } /* Compute SHA1 message digest for bytes read from STREAM. The resulting message digest number will be written into the 16 bytes beginning at RESBLOCK. */ int sha_stream (FILE *stream, void *resblock) { struct sha_ctx ctx; char buffer[BLOCKSIZE + 72]; size_t sum; /* Initialize the computation context. */ sha_init_ctx (&ctx); /* Iterate over full file contents. */ while (1) { /* We read the file in blocks of BLOCKSIZE bytes. One call of the computation function processes the whole buffer so that with the next round of the loop another block can be read. */ size_t n; sum = 0; /* Read block. Take care for partial reads. */ while (1) { n = fread (buffer + sum, 1, BLOCKSIZE - sum, stream); sum += n; if (sum == BLOCKSIZE) break; if (n == 0) { /* Check for the error flag IFF N == 0, so that we don't exit the loop after a partial read due to e.g., EAGAIN or EWOULDBLOCK. */ if (ferror (stream)) return 1; goto process_partial_block; } /* We've read at least one byte, so ignore errors. But always check for EOF, since feof may be true even though N > 0. Otherwise, we could end up calling fread after EOF. */ if (feof (stream)) goto process_partial_block; } /* Process buffer with BLOCKSIZE bytes. Note that BLOCKSIZE % 64 == 0 */ sha_process_block (buffer, BLOCKSIZE, &ctx); } process_partial_block:; /* Process any remaining bytes. */ if (sum > 0) sha_process_bytes (buffer, sum, &ctx); /* Construct result in desired memory. */ sha_finish_ctx (&ctx, resblock); return 0; } /* Compute MD5 message digest for LEN bytes beginning at BUFFER. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. */ void * sha_buffer (const char *buffer, size_t len, void *resblock) { struct sha_ctx ctx; /* Initialize the computation context. */ sha_init_ctx (&ctx); /* Process whole buffer but last len % 64 bytes. */ sha_process_bytes (buffer, len, &ctx); /* Put result in desired memory area. */ return sha_finish_ctx (&ctx, resblock); } void sha_process_bytes (const void *buffer, size_t len, struct sha_ctx *ctx) { /* When we already have some bits in our internal buffer concatenate both inputs first. */ if (ctx->buflen != 0) { size_t left_over = ctx->buflen; size_t add = 128 - left_over > len ? len : 128 - left_over; memcpy (&ctx->buffer[left_over], buffer, add); ctx->buflen += add; if (ctx->buflen > 64) { sha_process_block (ctx->buffer, ctx->buflen & ~63, ctx); ctx->buflen &= 63; /* The regions in the following copy operation cannot overlap. */ memcpy (ctx->buffer, &ctx->buffer[(left_over + add) & ~63], ctx->buflen); } buffer = (const char *) buffer + add; len -= add; } /* Process available complete blocks. */ if (len >= 64) { #if !_STRING_ARCH_unaligned /* To check alignment gcc has an appropriate operator. Other compilers don't. */ # if __GNUC__ >= 2 # define UNALIGNED_P(p) (((md5_uintptr) p) % __alignof__ (md5_uint32) != 0) # else # define UNALIGNED_P(p) (((md5_uintptr) p) % sizeof (md5_uint32) != 0) # endif if (UNALIGNED_P (buffer)) while (len > 64) { sha_process_block (memcpy (ctx->buffer, buffer, 64), 64, ctx); buffer = (const char *) buffer + 64; len -= 64; } else #endif { sha_process_block (buffer, len & ~63, ctx); buffer = (const char *) buffer + (len & ~63); len &= 63; } } /* Move remaining bytes in internal buffer. */ if (len > 0) { size_t left_over = ctx->buflen; memcpy (&ctx->buffer[left_over], buffer, len); left_over += len; if (left_over >= 64) { sha_process_block (ctx->buffer, 64, ctx); left_over -= 64; memcpy (ctx->buffer, &ctx->buffer[64], left_over); } ctx->buflen = left_over; } } /* --- Code below is the primary difference between md5.c and sha.c --- */ /* SHA1 round constants */ #define K1 0x5a827999L #define K2 0x6ed9eba1L #define K3 0x8f1bbcdcL #define K4 0xca62c1d6L /* Round functions. Note that F2 is the same as F4. */ #define F1(B,C,D) ( D ^ ( B & ( C ^ D ) ) ) #define F2(B,C,D) (B ^ C ^ D) #define F3(B,C,D) ( ( B & C ) | ( D & ( B | C ) ) ) #define F4(B,C,D) (B ^ C ^ D) /* Process LEN bytes of BUFFER, accumulating context into CTX. It is assumed that LEN % 64 == 0. Most of this code comes from GnuPG's cipher/sha1.c. */ void sha_process_block (const void *buffer, size_t len, struct sha_ctx *ctx) { const md5_uint32 *words = buffer; size_t nwords = len / sizeof (md5_uint32); const md5_uint32 *endp = words + nwords; md5_uint32 x[16]; md5_uint32 a = ctx->A; md5_uint32 b = ctx->B; md5_uint32 c = ctx->C; md5_uint32 d = ctx->D; md5_uint32 e = ctx->E; /* First increment the byte count. RFC 1321 specifies the possible length of the file up to 2^64 bits. Here we only compute the number of bytes. Do a double word increment. */ ctx->total[0] += len; if (ctx->total[0] < len) ++ctx->total[1]; #define M(I) ( tm = x[I&0x0f] ^ x[(I-14)&0x0f] \ ^ x[(I-8)&0x0f] ^ x[(I-3)&0x0f] \ , (x[I&0x0f] = rol(tm, 1)) ) #define R(A,B,C,D,E,F,K,M) do { E += rol( A, 5 ) \ + F( B, C, D ) \ + K \ + M; \ B = rol( B, 30 ); \ } while(0) while (words < endp) { md5_uint32 tm; int t; /* FIXME: see sha1.c for a better implementation. */ for (t = 0; t < 16; t++) { x[t] = NOTSWAP (*words); words++; } R( a, b, c, d, e, F1, K1, x[ 0] ); R( e, a, b, c, d, F1, K1, x[ 1] ); R( d, e, a, b, c, F1, K1, x[ 2] ); R( c, d, e, a, b, F1, K1, x[ 3] ); R( b, c, d, e, a, F1, K1, x[ 4] ); R( a, b, c, d, e, F1, K1, x[ 5] ); R( e, a, b, c, d, F1, K1, x[ 6] ); R( d, e, a, b, c, F1, K1, x[ 7] ); R( c, d, e, a, b, F1, K1, x[ 8] ); R( b, c, d, e, a, F1, K1, x[ 9] ); R( a, b, c, d, e, F1, K1, x[10] ); R( e, a, b, c, d, F1, K1, x[11] ); R( d, e, a, b, c, F1, K1, x[12] ); R( c, d, e, a, b, F1, K1, x[13] ); R( b, c, d, e, a, F1, K1, x[14] ); R( a, b, c, d, e, F1, K1, x[15] ); R( e, a, b, c, d, F1, K1, M(16) ); R( d, e, a, b, c, F1, K1, M(17) ); R( c, d, e, a, b, F1, K1, M(18) ); R( b, c, d, e, a, F1, K1, M(19) ); R( a, b, c, d, e, F2, K2, M(20) ); R( e, a, b, c, d, F2, K2, M(21) ); R( d, e, a, b, c, F2, K2, M(22) ); R( c, d, e, a, b, F2, K2, M(23) ); R( b, c, d, e, a, F2, K2, M(24) ); R( a, b, c, d, e, F2, K2, M(25) ); R( e, a, b, c, d, F2, K2, M(26) ); R( d, e, a, b, c, F2, K2, M(27) ); R( c, d, e, a, b, F2, K2, M(28) ); R( b, c, d, e, a, F2, K2, M(29) ); R( a, b, c, d, e, F2, K2, M(30) ); R( e, a, b, c, d, F2, K2, M(31) ); R( d, e, a, b, c, F2, K2, M(32) ); R( c, d, e, a, b, F2, K2, M(33) ); R( b, c, d, e, a, F2, K2, M(34) ); R( a, b, c, d, e, F2, K2, M(35) ); R( e, a, b, c, d, F2, K2, M(36) ); R( d, e, a, b, c, F2, K2, M(37) ); R( c, d, e, a, b, F2, K2, M(38) ); R( b, c, d, e, a, F2, K2, M(39) ); R( a, b, c, d, e, F3, K3, M(40) ); R( e, a, b, c, d, F3, K3, M(41) ); R( d, e, a, b, c, F3, K3, M(42) ); R( c, d, e, a, b, F3, K3, M(43) ); R( b, c, d, e, a, F3, K3, M(44) ); R( a, b, c, d, e, F3, K3, M(45) ); R( e, a, b, c, d, F3, K3, M(46) ); R( d, e, a, b, c, F3, K3, M(47) ); R( c, d, e, a, b, F3, K3, M(48) ); R( b, c, d, e, a, F3, K3, M(49) ); R( a, b, c, d, e, F3, K3, M(50) ); R( e, a, b, c, d, F3, K3, M(51) ); R( d, e, a, b, c, F3, K3, M(52) ); R( c, d, e, a, b, F3, K3, M(53) ); R( b, c, d, e, a, F3, K3, M(54) ); R( a, b, c, d, e, F3, K3, M(55) ); R( e, a, b, c, d, F3, K3, M(56) ); R( d, e, a, b, c, F3, K3, M(57) ); R( c, d, e, a, b, F3, K3, M(58) ); R( b, c, d, e, a, F3, K3, M(59) ); R( a, b, c, d, e, F4, K4, M(60) ); R( e, a, b, c, d, F4, K4, M(61) ); R( d, e, a, b, c, F4, K4, M(62) ); R( c, d, e, a, b, F4, K4, M(63) ); R( b, c, d, e, a, F4, K4, M(64) ); R( a, b, c, d, e, F4, K4, M(65) ); R( e, a, b, c, d, F4, K4, M(66) ); R( d, e, a, b, c, F4, K4, M(67) ); R( c, d, e, a, b, F4, K4, M(68) ); R( b, c, d, e, a, F4, K4, M(69) ); R( a, b, c, d, e, F4, K4, M(70) ); R( e, a, b, c, d, F4, K4, M(71) ); R( d, e, a, b, c, F4, K4, M(72) ); R( c, d, e, a, b, F4, K4, M(73) ); R( b, c, d, e, a, F4, K4, M(74) ); R( a, b, c, d, e, F4, K4, M(75) ); R( e, a, b, c, d, F4, K4, M(76) ); R( d, e, a, b, c, F4, K4, M(77) ); R( c, d, e, a, b, F4, K4, M(78) ); R( b, c, d, e, a, F4, K4, M(79) ); a = ctx->A += a; b = ctx->B += b; c = ctx->C += c; d = ctx->D += d; e = ctx->E += e; } }
/* stpcpy.c -- copy a string and return pointer to end of new string Copyright (C) 1992, 1995, 1997, 1998 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. Bugs can be reported to bug-glibc@prep.ai.mit.edu. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <string.h> #undef __stpcpy #undef stpcpy #ifndef weak_alias # define __stpcpy stpcpy #endif /* Copy SRC to DEST, returning the address of the terminating '\0' in DEST. */ char * __stpcpy (char *dest, const char *src) { register char *d = dest; register const char *s = src; do *d++ = *s; while (*s++ != '\0'); return d - 1; } #ifdef weak_alias weak_alias (__stpcpy, stpcpy) #endif
/* stripslash.c -- remove redundant trailing slashes from a file name Copyright (C) 1990, 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #if STDC_HEADERS || HAVE_STRING_H # include <string.h> #else # include <strings.h> #endif #include "dirname.h" /* Remove trailing slashes from PATH. Return nonzero if a trailing slash was removed. This is useful when using filename completion from a shell that adds a "/" after directory names (such as tcsh and bash), because the Unix rename and rmdir system calls return an "Invalid argument" error when given a path that ends in "/" (except for the root directory). */ int strip_trailing_slashes (char *path) { char *base = base_name (path); char *base_lim = base + base_len (base); int had_slash = *base_lim; *base_lim = '\0'; return had_slash; }
/* Convert string representation of a number into an intmax_t value. Copyright 1999, 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert. */ #if HAVE_CONFIG_H # include <config.h> #endif #if HAVE_INTTYPES_H # include <inttypes.h> #endif #if HAVE_STDLIB_H # include <stdlib.h> #endif #ifndef PARAMS # if defined PROTOTYPES || defined __STDC__ # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif #endif /* Verify a requirement at compile-time (unlike assert, which is runtime). */ #define verify(name, assertion) struct name { char a[(assertion) ? 1 : -1]; } #ifdef UNSIGNED # ifndef HAVE_DECL_STRTOUL "this configure-time declaration test was not run" # endif # if !HAVE_DECL_STRTOUL unsigned long strtoul PARAMS ((char const *, char **, int)); # endif # ifndef HAVE_DECL_STRTOULL "this configure-time declaration test was not run" # endif # if !HAVE_DECL_STRTOULL && HAVE_UNSIGNED_LONG_LONG unsigned long long strtoull PARAMS ((char const *, char **, int)); # endif #else # ifndef HAVE_DECL_STRTOL "this configure-time declaration test was not run" # endif # if !HAVE_DECL_STRTOL long strtol PARAMS ((char const *, char **, int)); # endif # ifndef HAVE_DECL_STRTOLL "this configure-time declaration test was not run" # endif # if !HAVE_DECL_STRTOLL && HAVE_UNSIGNED_LONG_LONG long long strtoll PARAMS ((char const *, char **, int)); # endif #endif #ifdef UNSIGNED # undef HAVE_LONG_LONG # define HAVE_LONG_LONG HAVE_UNSIGNED_LONG_LONG # define INT uintmax_t # define strtoimax strtoumax # define strtol strtoul # define strtoll strtoull #else # define INT intmax_t #endif INT strtoimax (char const *ptr, char **endptr, int base) { #if HAVE_LONG_LONG verify (size_is_that_of_long_or_long_long, (sizeof (INT) == sizeof (long) || sizeof (INT) == sizeof (long long))); if (sizeof (INT) != sizeof (long)) return strtoll (ptr, endptr, base); #else verify (size_is_that_of_long, sizeof (INT) == sizeof (long)); #endif return strtol (ptr, endptr, base); }
#define UNSIGNED 1 #include "strtoimax.c"
#define inttostr umaxtostr #define inttype uintmax_t #include "inttostr.c"
/* Unicode character output to streams with locale dependent encoding. Copyright (C) 2000-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Bruno Haible <haible@clisp.cons.org>. */ /* Note: This file requires the locale_charset() function. See in libiconv-1.8/libcharset/INTEGRATE for how to obtain it. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #if HAVE_STDDEF_H # include <stddef.h> #endif #include <stdio.h> #if HAVE_STRING_H # include <string.h> #else # include <strings.h> #endif #include <errno.h> #ifndef errno extern int errno; #endif #if HAVE_ICONV # include <iconv.h> #endif #include <error.h> #include "gettext.h" #define _(msgid) gettext (msgid) #define N_(msgid) msgid /* Specification. */ #include "unicodeio.h" /* When we pass a Unicode character to iconv(), we must pass it in a suitable encoding. The standardized Unicode encodings are UTF-8, UCS-2, UCS-4, UTF-16, UTF-16BE, UTF-16LE, UTF-7. UCS-2 supports only characters up to \U0000FFFF. UTF-16 and variants support only characters up to \U0010FFFF. UTF-7 is way too complex and not supported by glibc-2.1. UCS-4 specification leaves doubts about endianness and byte order mark. glibc currently interprets it as big endian without byte order mark, but this is not backed by an RFC. So we use UTF-8. It supports characters up to \U7FFFFFFF and is unambiguously defined. */ /* Stores the UTF-8 representation of the Unicode character wc in r[0..5]. Returns the number of bytes stored, or -1 if wc is out of range. */ static int utf8_wctomb (unsigned char *r, unsigned int wc) { int count; if (wc < 0x80) count = 1; else if (wc < 0x800) count = 2; else if (wc < 0x10000) count = 3; else if (wc < 0x200000) count = 4; else if (wc < 0x4000000) count = 5; else if (wc <= 0x7fffffff) count = 6; else return -1; switch (count) { /* Note: code falls through cases! */ case 6: r[5] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0x4000000; case 5: r[4] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0x200000; case 4: r[3] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0x10000; case 3: r[2] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0x800; case 2: r[1] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0xc0; case 1: r[0] = wc; } return count; } /* Luckily, the encoding's name is platform independent. */ #define UTF8_NAME "UTF-8" /* Converts the Unicode character CODE to its multibyte representation in the current locale and calls the SUCCESS callback on the resulting byte sequence. If an error occurs, invokes the FAILURE callback instead, passing it CODE and an English error string. Returns whatever the callback returned. Assumes that the locale doesn't change between two calls. */ long unicode_to_mb (unsigned int code, long (*success) PARAMS ((const char *buf, size_t buflen, void *callback_arg)), long (*failure) PARAMS ((unsigned int code, const char *msg, void *callback_arg)), void *callback_arg) { static int initialized; static int is_utf8; #if HAVE_ICONV static iconv_t utf8_to_local; #endif char inbuf[6]; int count; if (!initialized) { extern const char *locale_charset PARAMS ((void)); const char *charset = locale_charset (); is_utf8 = !strcmp (charset, UTF8_NAME); #if HAVE_ICONV if (!is_utf8) { utf8_to_local = iconv_open (charset, UTF8_NAME); if (utf8_to_local == (iconv_t)(-1)) /* For an unknown encoding, assume ASCII. */ utf8_to_local = iconv_open ("ASCII", UTF8_NAME); } #endif initialized = 1; } /* Test whether the utf8_to_local converter is available at all. */ if (!is_utf8) { #if HAVE_ICONV if (utf8_to_local == (iconv_t)(-1)) return failure (code, N_("iconv function not usable"), callback_arg); #else return failure (code, N_("iconv function not available"), callback_arg); #endif } /* Convert the character to UTF-8. */ count = utf8_wctomb ((unsigned char *) inbuf, code); if (count < 0) return failure (code, N_("character out of range"), callback_arg); #if HAVE_ICONV if (!is_utf8) { char outbuf[25]; const char *inptr; size_t inbytesleft; char *outptr; size_t outbytesleft; size_t res; inptr = inbuf; inbytesleft = count; outptr = outbuf; outbytesleft = sizeof (outbuf); /* Convert the character from UTF-8 to the locale's charset. */ res = iconv (utf8_to_local, (ICONV_CONST char **)&inptr, &inbytesleft, &outptr, &outbytesleft); if (inbytesleft > 0 || res == (size_t)(-1) /* Irix iconv() inserts a NUL byte if it cannot convert. */ # if !defined _LIBICONV_VERSION && (defined sgi || defined __sgi) || (res > 0 && code != 0 && outptr - outbuf == 1 && *outbuf == '\0') # endif ) return failure (code, NULL, callback_arg); /* Avoid glibc-2.1 bug and Solaris 2.7 bug. */ # if defined _LIBICONV_VERSION \ || !((__GLIBC__ - 0 == 2 && __GLIBC_MINOR__ - 0 <= 1) || defined __sun) /* Get back to the initial shift state. */ res = iconv (utf8_to_local, NULL, NULL, &outptr, &outbytesleft); if (res == (size_t)(-1)) return failure (code, NULL, callback_arg); # endif return success (outbuf, outptr - outbuf, callback_arg); } #endif /* At this point, is_utf8 is true, so no conversion is needed. */ return success (inbuf, count, callback_arg); } /* Simple success callback that outputs the converted string. The STREAM is passed as callback_arg. */ long fwrite_success_callback (const char *buf, size_t buflen, void *callback_arg) { FILE *stream = (FILE *) callback_arg; fwrite (buf, 1, buflen, stream); return 0; } /* Simple failure callback that displays an error and exits. */ static long exit_failure_callback (unsigned int code, const char *msg, void *callback_arg) { if (msg == NULL) error (1, 0, _("cannot convert U+%04X to local character set"), code); else error (1, 0, _("cannot convert U+%04X to local character set: %s"), code, gettext (msg)); return -1; } /* Simple failure callback that displays a fallback representation in plain ASCII, using the same notation as ISO C99 strings. */ static long fallback_failure_callback (unsigned int code, const char *msg, void *callback_arg) { FILE *stream = (FILE *) callback_arg; if (code < 0x10000) fprintf (stream, "\\u%04X", code); else fprintf (stream, "\\U%08X", code); return -1; } /* Outputs the Unicode character CODE to the output stream STREAM. Upon failure, exit if exit_on_error is true, otherwise output a fallback notation. */ void print_unicode_char (FILE *stream, unsigned int code, int exit_on_error) { unicode_to_mb (code, fwrite_success_callback, exit_on_error ? exit_failure_callback : fallback_failure_callback, stream); }
/* userspec.c -- Parse a user and group string. Copyright (C) 1989-1992, 1997, 1998, 2000, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@gnu.ai.mit.edu>. */ #if HAVE_CONFIG_H # include <config.h> #endif #ifdef __GNUC__ # define alloca __builtin_alloca #else # if HAVE_ALLOCA_H # include <alloca.h> # else # ifdef _AIX # pragma alloca # else char *alloca (); # endif # endif #endif #include <stdio.h> #include <sys/types.h> #include <pwd.h> #include <grp.h> #if HAVE_SYS_PARAM_H # include <sys/param.h> #endif #if HAVE_LIMITS_H # include <limits.h> #endif #if HAVE_STRING_H # include <string.h> #else # include <strings.h> # ifndef strchr # define strchr index # endif #endif #if STDC_HEADERS # include <stdlib.h> #endif #if HAVE_UNISTD_H # include <unistd.h> #endif #include "xalloc.h" #include "xstrtol.h" #include "gettext.h" #define _(msgid) gettext (msgid) #define N_(msgid) msgid #ifndef _POSIX_VERSION struct passwd *getpwnam (); struct group *getgrnam (); struct group *getgrgid (); #endif #ifndef HAVE_ENDGRENT # define endgrent() ((void) 0) #endif #ifndef HAVE_ENDPWENT # define endpwent() ((void) 0) #endif #ifndef CHAR_BIT # define CHAR_BIT 8 #endif /* The extra casts work around common compiler bugs. */ #define TYPE_SIGNED(t) (! ((t) 0 < (t) -1)) /* The outer cast is needed to work around a bug in Cray C 5.0.3.0. It is necessary at least when t == time_t. */ #define TYPE_MINIMUM(t) ((t) (TYPE_SIGNED (t) \ ? ~ (t) 0 << (sizeof (t) * CHAR_BIT - 1) : (t) 0)) #define TYPE_MAXIMUM(t) ((t) (~ (t) 0 - TYPE_MINIMUM (t))) #ifndef UID_T_MAX # define UID_T_MAX TYPE_MAXIMUM (uid_t) #endif #ifndef GID_T_MAX # define GID_T_MAX TYPE_MAXIMUM (gid_t) #endif /* MAXUID may come from limits.h or sys/params.h. */ #ifndef MAXUID # define MAXUID UID_T_MAX #endif #ifndef MAXGID # define MAXGID GID_T_MAX #endif /* Perform the equivalent of the statement `dest = strdup (src);', but obtaining storage via alloca instead of from the heap. */ #define V_STRDUP(dest, src) \ do \ { \ int _len = strlen ((src)); \ (dest) = (char *) alloca (_len + 1); \ strcpy (dest, src); \ } \ while (0) /* ISDIGIT differs from isdigit, as follows: - Its arg may be any int or unsigned int; it need not be an unsigned char. - It's guaranteed to evaluate its argument exactly once. - It's typically faster. POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to ISDIGIT_LOCALE unless it's important to use the locale's definition of `digit' even when the host does not conform to POSIX. */ #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9) #ifndef strdup char *strdup (); #endif /* Return nonzero if STR represents an unsigned decimal integer, otherwise return 0. */ static int is_number (const char *str) { for (; *str; str++) if (!ISDIGIT (*str)) return 0; return 1; } /* Extract from NAME, which has the form "[user][:.][group]", a USERNAME, UID U, GROUPNAME, and GID G. Either user or group, or both, must be present. If the group is omitted but the ":" separator is given, use the given user's login group. If SPEC_ARG contains a `:', then use that as the separator, ignoring any `.'s. If there is no `:', but there is a `.', then first look up the entire SPEC_ARG as a login name. If that look-up fails, then try again interpreting the `.' as a separator. USERNAME and GROUPNAME will be in newly malloc'd memory. Either one might be NULL instead, indicating that it was not given and the corresponding numeric ID was left unchanged. Return NULL if successful, a static error message string if not. */ const char * parse_user_spec (const char *spec_arg, uid_t *uid, gid_t *gid, char **username_arg, char **groupname_arg) { static const char *E_invalid_user = N_("invalid user"); static const char *E_invalid_group = N_("invalid group"); static const char *E_bad_spec = N_("cannot get the login group of a numeric UID"); static const char *E_cannot_omit_both = N_("cannot omit both user and group"); const char *error_msg; char *spec; /* A copy we can write on. */ struct passwd *pwd; struct group *grp; char *g, *u, *separator; char *groupname; int maybe_retry = 0; char *dot = NULL; error_msg = NULL; *username_arg = *groupname_arg = NULL; groupname = NULL; V_STRDUP (spec, spec_arg); /* Find the POSIX `:' separator if there is one. */ separator = strchr (spec, ':'); /* If there is no colon, then see if there's a `.'. */ if (separator == NULL) { dot = strchr (spec, '.'); /* If there's no colon but there is a `.', then first look up the whole spec, in case it's an OWNER name that includes a dot. If that fails, then we'll try again, but interpreting the `.' as a separator. */ /* FIXME: accepting `.' as the separator is contrary to POSIX. someday we should drop support for this. */ if (dot) maybe_retry = 1; } retry: /* Replace separator with a NUL. */ if (separator != NULL) *separator = '\0'; /* Set U and G to non-zero length strings corresponding to user and group specifiers or to NULL. */ u = (*spec == '\0' ? NULL : spec); g = (separator == NULL || *(separator + 1) == '\0' ? NULL : separator + 1); if (u == NULL && g == NULL) return _(E_cannot_omit_both); #ifdef __DJGPP__ /* Pretend that we are the user U whose group is G. This makes pwd and grp functions ``know'' about the UID and GID of these. */ if (u && !is_number (u)) setenv ("USER", u, 1); if (g && !is_number (g)) setenv ("GROUP", g, 1); #endif if (u != NULL) { pwd = getpwnam (u); if (pwd == NULL) { if (!is_number (u)) error_msg = E_invalid_user; else { int use_login_group; use_login_group = (separator != NULL && g == NULL); if (use_login_group) error_msg = E_bad_spec; else { unsigned long int tmp_long; if (xstrtoul (u, NULL, 0, &tmp_long, NULL) != LONGINT_OK || tmp_long > MAXUID) return _(E_invalid_user); *uid = tmp_long; } } } else { *uid = pwd->pw_uid; if (g == NULL && separator != NULL) { /* A separator was given, but a group was not specified, so get the login group. */ *gid = pwd->pw_gid; grp = getgrgid (pwd->pw_gid); if (grp == NULL) { /* This is enough room to hold the unsigned decimal representation of any 32-bit quantity and the trailing zero byte. */ char uint_buf[21]; sprintf (uint_buf, "%u", (unsigned) (pwd->pw_gid)); V_STRDUP (groupname, uint_buf); } else { V_STRDUP (groupname, grp->gr_name); } endgrent (); } } endpwent (); } if (g != NULL && error_msg == NULL) { /* Explicit group. */ grp = getgrnam (g); if (grp == NULL) { if (!is_number (g)) error_msg = E_invalid_group; else { unsigned long int tmp_long; if (xstrtoul (g, NULL, 0, &tmp_long, NULL) != LONGINT_OK || tmp_long > MAXGID) return _(E_invalid_group); *gid = tmp_long; } } else *gid = grp->gr_gid; endgrent (); /* Save a file descriptor. */ if (error_msg == NULL) V_STRDUP (groupname, g); } if (error_msg == NULL) { if (u != NULL) { *username_arg = strdup (u); if (*username_arg == NULL) error_msg = xalloc_msg_memory_exhausted; } if (groupname != NULL && error_msg == NULL) { *groupname_arg = strdup (groupname); if (*groupname_arg == NULL) { if (*username_arg != NULL) { free (*username_arg); *username_arg = NULL; } error_msg = xalloc_msg_memory_exhausted; } } } if (error_msg && maybe_retry) { maybe_retry = 0; separator = dot; error_msg = NULL; goto retry; } return _(error_msg); } #ifdef TEST # define NULL_CHECK(s) ((s) == NULL ? "(null)" : (s)) int main (int argc, char **argv) { int i; for (i = 1; i < argc; i++) { const char *e; char *username, *groupname; uid_t uid; gid_t gid; char *tmp; tmp = strdup (argv[i]); e = parse_user_spec (tmp, &uid, &gid, &username, &groupname); free (tmp); printf ("%s: %u %u %s %s %s\n", argv[i], (unsigned int) uid, (unsigned int) gid, NULL_CHECK (username), NULL_CHECK (groupname), NULL_CHECK (e)); } exit (0); } #endif
/* Utility to help print --version output in a consistent format. Copyright (C) 1999, 2000, 2001, 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Jim Meyering. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #include "unlocked-io.h" #include "version-etc.h" #include "gettext.h" #define _(msgid) gettext (msgid) /* Default copyright goes to the FSF. */ char* version_etc_copyright = /* Do *not* mark this string for translation. */ "Copyright (C) 2003 Free Software Foundation, Inc."; /* Display the --version information the standard way. If COMMAND_NAME is NULL, the PACKAGE is asumed to be the name of the program. The formats are therefore: PACKAGE VERSION or COMMAND_NAME (PACKAGE) VERSION. */ void version_etc (FILE *stream, const char *command_name, const char *package, const char *version, const char *authors) { if (command_name) fprintf (stream, "%s (%s) %s\n", command_name, package, version); else fprintf (stream, "%s %s\n", package, version); fprintf (stream, _("Written by %s.\n"), authors); putc ('\n', stream); fputs (version_etc_copyright, stream); putc ('\n', stream); fputs (_("\ This is free software; see the source for copying conditions. There is NO\n\ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"), stream); }
/* xgetcwd.c -- return current directory with unlimited length Copyright (C) 1992, 1996, 2000, 2001, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@gnu.ai.mit.edu>. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #include <errno.h> #ifndef errno extern int errno; #endif #include <sys/types.h> #if HAVE_STDLIB_H # include <stdlib.h> #endif #if HAVE_UNISTD_H # include <unistd.h> #endif #if HAVE_GETCWD char *getcwd (); #else # include "pathmax.h" # define INITIAL_BUFFER_SIZE (PATH_MAX + 1) char *getwd (); # define getcwd(Buf, Max) getwd (Buf) #endif #include "xalloc.h" #include "xgetcwd.h" /* Return the current directory, newly allocated, assuming it fits within PATH_MAX bytes -- this is a common system-imposed limit on how getcwd works. Upon an out-of-memory error, call xalloc_die. Upon any other type of error, return NULL. */ char * xgetcwd (void) { #if HAVE_GETCWD_NULL char *cwd = getcwd (NULL, 0); if (! cwd && errno == ENOMEM) xalloc_die (); return cwd; #else /* The initial buffer size for the working directory. A power of 2 detects arithmetic overflow earlier, but is not required. */ # ifndef INITIAL_BUFFER_SIZE # define INITIAL_BUFFER_SIZE 128 # endif size_t buf_size = INITIAL_BUFFER_SIZE; while (1) { char *buf = xmalloc (buf_size); char *cwd = getcwd (buf, buf_size); int saved_errno; if (cwd) return cwd; saved_errno = errno; free (buf); if (saved_errno != ERANGE) return NULL; buf_size *= 2; if (buf_size == 0) xalloc_die (); } #endif }
/* xgethostname.c -- return current hostname with unlimited length Copyright (C) 1992, 1996, 2000, 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* written by Jim Meyering */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <sys/types.h> #include <errno.h> #ifndef errno extern int errno; #endif #include "error.h" #include "xalloc.h" #ifndef ENAMETOOLONG # define ENAMETOOLONG 9999 #endif #ifndef EXIT_FAILURE # define EXIT_FAILURE 1 #endif int gethostname (); #ifndef INITIAL_HOSTNAME_LENGTH # define INITIAL_HOSTNAME_LENGTH 34 #endif char * xgethostname () { char *hostname; size_t size; size = INITIAL_HOSTNAME_LENGTH; /* Use size + 1 here rather than size to work around the bug in SunOS5.5's gethostname whereby it NUL-terminates HOSTNAME even when the name is longer than the supplied buffer. */ hostname = xmalloc (size + 1); while (1) { int k = size - 1; int err; errno = 0; hostname[k] = '\0'; err = gethostname (hostname, size); if (err >= 0 && hostname[k] == '\0') break; else if (err < 0 && errno != ENAMETOOLONG && errno != 0) error (EXIT_FAILURE, errno, "gethostname"); size *= 2; hostname = xrealloc (hostname, size + 1); } return hostname; }
/* xmalloc.c -- malloc with out of memory checking Copyright (C) 1990-1999, 2000, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <sys/types.h> #if STDC_HEADERS # include <stdlib.h> #else void *calloc (); void *malloc (); void *realloc (); void free (); #endif #include "gettext.h" #define _(msgid) gettext (msgid) #define N_(msgid) msgid #include "error.h" #include "xalloc.h" #ifndef EXIT_FAILURE # define EXIT_FAILURE 1 #endif /* The following tests require AC_PREREQ(2.54). */ #ifndef HAVE_MALLOC "you must run the autoconf test for a GNU libc compatible malloc" #endif #ifndef HAVE_REALLOC "you must run the autoconf test for a GNU libc compatible realloc" #endif /* Exit value when the requested amount of memory is not available. The caller may set it to some other value. */ int xalloc_exit_failure = EXIT_FAILURE; /* If non NULL, call this function when memory is exhausted. */ void (*xalloc_fail_func) PARAMS ((void)) = 0; /* If XALLOC_FAIL_FUNC is NULL, or does return, display this message before exiting when memory is exhausted. Goes through gettext. */ char const xalloc_msg_memory_exhausted[] = N_("memory exhausted"); void xalloc_die (void) { if (xalloc_fail_func) (*xalloc_fail_func) (); error (xalloc_exit_failure, 0, "%s", _(xalloc_msg_memory_exhausted)); /* The `noreturn' cannot be given to error, since it may return if its first argument is 0. To help compilers understand the xalloc_die does terminate, call exit. */ exit (EXIT_FAILURE); } /* Allocate N bytes of memory dynamically, with error checking. */ void * xmalloc (size_t n) { void *p; p = malloc (n); if (p == 0) xalloc_die (); return p; } /* Change the size of an allocated block of memory P to N bytes, with error checking. */ void * xrealloc (void *p, size_t n) { p = realloc (p, n); if (p == 0) xalloc_die (); return p; } /* Allocate memory for N elements of S bytes, with error checking. */ void * xcalloc (size_t n, size_t s) { void *p; p = calloc (n, s); if (p == 0) xalloc_die (); return p; }
/* Locale-specific memory comparison. Copyright (C) 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Contributed by Paul Eggert <eggert@twinsun.com>. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <errno.h> #ifndef errno extern int errno; #endif #include <stdlib.h> #include "gettext.h" #define _(msgid) gettext (msgid) #include "error.h" #include "memcoll.h" #include "quotearg.h" #include "xmemcoll.h" /* Exit value when xmemcoll fails. The caller may set it to some other value. */ int xmemcoll_exit_failure = EXIT_FAILURE; /* Compare S1 (with length S1LEN) and S2 (with length S2LEN) according to the LC_COLLATE locale. S1 and S2 do not overlap, and are not adjacent. Temporarily modify the bytes after S1 and S2, but restore their original contents before returning. Report an error and exit if there is an error. */ int xmemcoll (char *s1, size_t s1len, char *s2, size_t s2len) { int diff = memcoll (s1, s1len, s2, s2len); int collation_errno = errno; if (collation_errno) { error (0, collation_errno, _("string comparison failed")); error (0, 0, _("Set LC_ALL='C' to work around the problem.")); error (xmemcoll_exit_failure, 0, _("The strings compared were %s and %s."), quotearg_n_style_mem (0, locale_quoting_style, s1, s1len), quotearg_n_style_mem (1, locale_quoting_style, s2, s2len)); } return diff; }
/* xnanosleep.c -- a more convenient interface to nanosleep Copyright (C) 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Mostly written (for sleep.c) by Paul Eggert. Factored out (creating this file) by Jim Meyering. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #include <assert.h> #include <errno.h> #include <sys/types.h> #include <time.h> #if HAVE_CLOCK_GETTIME && defined CLOCK_REALTIME # define USE_CLOCK_GETTIME 1 #else # define USE_CLOCK_GETTIME 0 #endif #if ! USE_CLOCK_GETTIME # include <sys/time.h> #endif #ifndef CHAR_BIT # define CHAR_BIT 8 #endif /* The extra casts work around common compiler bugs. */ #define TYPE_SIGNED(t) (! ((t) 0 < (t) -1)) /* The outer cast is needed to work around a bug in Cray C 5.0.3.0. It is necessary at least when t == time_t. */ #define TYPE_MINIMUM(t) ((t) (TYPE_SIGNED (t) \ ? ~ (t) 0 << (sizeof (t) * CHAR_BIT - 1) : (t) 0)) #define TYPE_MAXIMUM(t) ((t) (~ (t) 0 - TYPE_MINIMUM (t))) #ifndef TIME_T_MAX # define TIME_T_MAX TYPE_MAXIMUM (time_t) #endif #include "timespec.h" #include "xalloc.h" #include "xnanosleep.h" #include "xstrtod.h" /* Subtract the `struct timespec' values X and Y, storing the difference in DIFF. Return 1 if the difference is positive, otherwise 0. Derived from code in the GNU libc manual. */ static int timespec_subtract (struct timespec *diff, const struct timespec *x, struct timespec *y) { /* Perform the carry for the later subtraction by updating Y. */ if (x->tv_nsec < y->tv_nsec) { int nsec = (y->tv_nsec - x->tv_nsec) / 1000000000 + 1; y->tv_nsec -= 1000000000 * nsec; y->tv_sec += nsec; } if (1000000000 < x->tv_nsec - y->tv_nsec) { int nsec = (y->tv_nsec - x->tv_nsec) / 1000000000; y->tv_nsec += 1000000000 * nsec; y->tv_sec -= nsec; } /* Compute the time remaining to wait. `tv_nsec' is certainly positive. */ diff->tv_sec = x->tv_sec - y->tv_sec; diff->tv_nsec = x->tv_nsec - y->tv_nsec; /* Return 1 if result is positive. */ return y->tv_sec < x->tv_sec; } struct timespec * clock_get_realtime (struct timespec *ts) { int fail; #if USE_CLOCK_GETTIME fail = clock_gettime (CLOCK_REALTIME, ts); #else struct timeval tv; fail = gettimeofday (&tv, NULL); if (!fail) { ts->tv_sec = tv.tv_sec; ts->tv_nsec = 1000 * tv.tv_usec; } #endif if (fail) return NULL; return ts; } /* Sleep until the time (call it WAKE_UP_TIME) specified as SECONDS seconds after the time this function is called. SECONDS must be non-negative. If SECONDS is so large that it is not representable as a `struct timespec', then use the maximum value for that interval. Return -1 on failure (setting errno), 0 on success. */ int xnanosleep (double seconds) { int overflow; double ns; struct timespec ts_start; struct timespec ts_sleep; struct timespec ts_stop; assert (0 <= seconds); if (clock_get_realtime (&ts_start) == NULL) return -1; /* Separate whole seconds from nanoseconds. Be careful to detect any overflow. */ ts_sleep.tv_sec = seconds; ns = 1e9 * (seconds - ts_sleep.tv_sec); overflow = ! (ts_sleep.tv_sec <= seconds && 0 <= ns && ns <= 1e9); ts_sleep.tv_nsec = ns; /* Round up to the next whole number, if necessary, so that we always sleep for at least the requested amount of time. Assuming the default rounding mode, we don't have to worry about the rounding error when computing 'ns' above, since the error won't cause 'ns' to drop below an integer boundary. */ ts_sleep.tv_nsec += (ts_sleep.tv_nsec < ns); /* Normalize the interval length. nanosleep requires this. */ if (1000000000 <= ts_sleep.tv_nsec) { time_t t = ts_sleep.tv_sec + 1; /* Detect integer overflow. */ overflow |= (t < ts_sleep.tv_sec); ts_sleep.tv_sec = t; ts_sleep.tv_nsec -= 1000000000; } /* Compute the time until which we should sleep. */ ts_stop.tv_sec = ts_start.tv_sec + ts_sleep.tv_sec; ts_stop.tv_nsec = ts_start.tv_nsec + ts_sleep.tv_nsec; if (1000000000 <= ts_stop.tv_nsec) { ++ts_stop.tv_sec; ts_stop.tv_nsec -= 1000000000; } /* Detect integer overflow. */ overflow |= (ts_stop.tv_sec < ts_start.tv_sec || (ts_stop.tv_sec == ts_start.tv_sec && ts_stop.tv_nsec < ts_start.tv_nsec)); if (overflow) { /* Fix ts_sleep and ts_stop, which may be garbage due to overflow. */ ts_sleep.tv_sec = ts_stop.tv_sec = TIME_T_MAX; ts_sleep.tv_nsec = ts_stop.tv_nsec = 999999999; } while (nanosleep (&ts_sleep, NULL) != 0) { if (errno != EINTR) return -1; /* POSIX.1-2001 requires that when a process is suspended, then resumed, nanosleep (A, B) returns -1, sets errno to EINTR, and sets *B to the time remaining at the point of resumption. However, some versions of the Linux kernel incorrectly return the time remaining at the point of suspension. Work around this bug by computing the remaining time here, rather than by relying on nanosleep's computation. */ if (! timespec_subtract (&ts_sleep, &ts_stop, clock_get_realtime (&ts_start))) break; } return 0; }
/* xreadlink.c -- readlink wrapper to return the link name in malloc'd storage Copyright 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Jim Meyering <jim@meyering.net> */ #if HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #include <errno.h> #ifndef errno extern int errno; #endif #if HAVE_LIMITS_H # include <limits.h> #endif #if HAVE_SYS_TYPES_H # include <sys/types.h> #endif #if HAVE_STDLIB_H # include <stdlib.h> #endif #if HAVE_UNISTD_H # include <unistd.h> #endif #ifndef SIZE_MAX # define SIZE_MAX ((size_t) -1) #endif #ifndef SSIZE_MAX # define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2)) #endif #include "xalloc.h" #include "xreadlink.h" /* Call readlink to get the symbolic link value of FILENAME. Return a pointer to that NUL-terminated string in malloc'd storage. If readlink fails, return NULL (caller may use errno to diagnose). If realloc fails, or if the link value is longer than SIZE_MAX :-), give a diagnostic and exit. */ char * xreadlink (char const *filename) { /* The initial buffer size for the link value. A power of 2 detects arithmetic overflow earlier, but is not required. */ size_t buf_size = 128; while (1) { char *buffer = xmalloc (buf_size); ssize_t link_length = readlink (filename, buffer, buf_size); if (link_length < 0) { int saved_errno = errno; free (buffer); errno = saved_errno; return NULL; } if ((size_t) link_length < buf_size) { buffer[link_length] = 0; return buffer; } free (buffer); buf_size *= 2; if (SSIZE_MAX < buf_size || (SIZE_MAX / 2 < SSIZE_MAX && buf_size == 0)) xalloc_die (); } }
/* xstrdup.c -- copy a string with out of memory checking Copyright (C) 1990, 1996, 1998, 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #if STDC_HEADERS || HAVE_STRING_H # include <string.h> #else # include <strings.h> #endif #include <sys/types.h> #include "xalloc.h" /* Return a newly allocated copy of STRING. */ char * xstrdup (const char *string) { return strcpy (xmalloc (strlen (string) + 1), string); }
/* xstrtod.c - error-checking interface to strtod Copyright (C) 1996, 1999, 2000 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Jim Meyering. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #ifdef STDC_HEADERS # include <stdlib.h> #else double strtod (); #endif #include <errno.h> #include <stdio.h> #include <limits.h> #include "xstrtod.h" /* Tell the compiler that non-default rounding modes are used. */ #if 199901 <= __STDC_VERSION__ #pragma STDC FENV_ACCESS ON #endif /* An interface to strtod that encapsulates all the error checking one should usually perform. Like strtod, but upon successful conversion put the result in *RESULT and return zero. Return non-zero and don't modify *RESULT upon any failure. */ int xstrtod (str, ptr, result) const char *str; const char **ptr; double *result; { double val; char *terminator; int fail; fail = 0; errno = 0; val = strtod (str, &terminator); /* Having a non-zero terminator is an error only when PTR is NULL. */ if (terminator == str || (ptr == NULL && *terminator != '\0')) fail = 1; else { /* Allow underflow (in which case strtod returns zero), but flag overflow as an error. */ if (val != 0.0 && errno == ERANGE) fail = 1; } if (ptr != NULL) *ptr = terminator; *result = val; return fail; }
/* A more useful interface to strtol. Copyright (C) 1995, 1996, 1998-2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Jim Meyering. */ #if HAVE_CONFIG_H # include <config.h> #endif #ifndef __strtol # define __strtol strtol # define __strtol_t long int # define __xstrtol xstrtol #endif /* Some pre-ANSI implementations (e.g. SunOS 4) need stderr defined if assertion checking is enabled. */ #include <stdio.h> #if STDC_HEADERS # include <stdlib.h> #endif #if HAVE_STRING_H # include <string.h> #else # include <strings.h> # ifndef strchr # define strchr index # endif #endif #include <assert.h> #include <ctype.h> #include <errno.h> #ifndef errno extern int errno; #endif #if HAVE_LIMITS_H # include <limits.h> #endif #ifndef CHAR_BIT # define CHAR_BIT 8 #endif /* The extra casts work around common compiler bugs. */ #define TYPE_SIGNED(t) (! ((t) 0 < (t) -1)) /* The outer cast is needed to work around a bug in Cray C 5.0.3.0. It is necessary at least when t == time_t. */ #define TYPE_MINIMUM(t) ((t) (TYPE_SIGNED (t) \ ? ~ (t) 0 << (sizeof (t) * CHAR_BIT - 1) : (t) 0)) #define TYPE_MAXIMUM(t) (~ (t) 0 - TYPE_MINIMUM (t)) #if defined (STDC_HEADERS) || (!defined (isascii) && !defined (HAVE_ISASCII)) # define IN_CTYPE_DOMAIN(c) 1 #else # define IN_CTYPE_DOMAIN(c) isascii(c) #endif #define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c)) #include "xstrtol.h" #if !HAVE_DECL_STRTOL && !defined strtol long int strtol (); #endif #if !HAVE_DECL_STRTOUL && !defined strtoul unsigned long int strtoul (); #endif #if !HAVE_DECL_STRTOIMAX && !defined strtoimax intmax_t strtoimax (); #endif #if !HAVE_DECL_STRTOUMAX && !defined strtoumax uintmax_t strtoumax (); #endif static int bkm_scale (__strtol_t *x, int scale_factor) { __strtol_t product = *x * scale_factor; if (*x != product / scale_factor) return 1; *x = product; return 0; } static int bkm_scale_by_power (__strtol_t *x, int base, int power) { while (power--) if (bkm_scale (x, base)) return 1; return 0; } /* FIXME: comment. */ strtol_error __xstrtol (const char *s, char **ptr, int strtol_base, __strtol_t *val, const char *valid_suffixes) { char *t_ptr; char **p; __strtol_t tmp; assert (0 <= strtol_base && strtol_base <= 36); p = (ptr ? ptr : &t_ptr); if (! TYPE_SIGNED (__strtol_t)) { const char *q = s; while (ISSPACE ((unsigned char) *q)) ++q; if (*q == '-') return LONGINT_INVALID; } errno = 0; tmp = __strtol (s, p, strtol_base); if (errno != 0) return LONGINT_OVERFLOW; if (*p == s) { /* If there is no number but there is a valid suffix, assume the number is 1. The string is invalid otherwise. */ if (valid_suffixes && **p && strchr (valid_suffixes, **p)) tmp = 1; else return LONGINT_INVALID; } /* Let valid_suffixes == NULL mean `allow any suffix'. */ /* FIXME: update all callers except the ones that allow suffixes after the number, changing last parameter NULL to `""'. */ if (!valid_suffixes) { *val = tmp; return LONGINT_OK; } if (**p != '\0') { int base = 1024; int suffixes = 1; int overflow; if (!strchr (valid_suffixes, **p)) { *val = tmp; return LONGINT_INVALID_SUFFIX_CHAR; } if (strchr (valid_suffixes, '0')) { /* The ``valid suffix'' '0' is a special flag meaning that an optional second suffix is allowed, which can change the base. A suffix "B" (e.g. "100MB") stands for a power of 1000, whereas a suffix "iB" (e.g. "100MiB") stands for a power of 1024. If no suffix (e.g. "100M"), assume power-of-1024. */ switch (p[0][1]) { case 'i': if (p[0][2] == 'B') suffixes += 2; break; case 'B': case 'D': /* 'D' is obsolescent */ base = 1000; suffixes++; break; } } switch (**p) { case 'b': overflow = bkm_scale (&tmp, 512); break; case 'B': overflow = bkm_scale (&tmp, 1024); break; case 'c': overflow = 0; break; case 'E': /* exa or exbi */ overflow = bkm_scale_by_power (&tmp, base, 6); break; case 'G': /* giga or gibi */ case 'g': /* 'g' is undocumented; for compatibility only */ overflow = bkm_scale_by_power (&tmp, base, 3); break; case 'k': /* kilo */ case 'K': /* kibi */ overflow = bkm_scale_by_power (&tmp, base, 1); break; case 'M': /* mega or mebi */ case 'm': /* 'm' is undocumented; for compatibility only */ overflow = bkm_scale_by_power (&tmp, base, 2); break; case 'P': /* peta or pebi */ overflow = bkm_scale_by_power (&tmp, base, 5); break; case 'T': /* tera or tebi */ case 't': /* 't' is undocumented; for compatibility only */ overflow = bkm_scale_by_power (&tmp, base, 4); break; case 'w': overflow = bkm_scale (&tmp, 2); break; case 'Y': /* yotta or 2**80 */ overflow = bkm_scale_by_power (&tmp, base, 8); break; case 'Z': /* zetta or 2**70 */ overflow = bkm_scale_by_power (&tmp, base, 7); break; default: *val = tmp; return LONGINT_INVALID_SUFFIX_CHAR; break; } if (overflow) return LONGINT_OVERFLOW; (*p) += suffixes; } *val = tmp; return LONGINT_OK; } #ifdef TESTING_XSTRTO # include <stdio.h> # include "error.h" char *program_name; int main (int argc, char** argv) { strtol_error s_err; int i; program_name = argv[0]; for (i=1; i<argc; i++) { char *p; __strtol_t val; s_err = __xstrtol (argv[i], &p, 0, &val, "bckmw"); if (s_err == LONGINT_OK) { printf ("%s->%lu (%s)\n", argv[i], val, p); } else { STRTOL_FATAL_ERROR (argv[i], "arg", s_err); } } exit (0); } #endif /* TESTING_XSTRTO */
#define __strtol strtoul #define __strtol_t unsigned long int #define __xstrtol xstrtoul #include "xstrtol.c"
/* xstrtoimax.c -- A more useful interface to strtoimax. Copyright 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Cloned by Jim Meyering. */ #if HAVE_CONFIG_H # include <config.h> #endif #if HAVE_INTTYPES_H # include <inttypes.h> #endif #define __strtol strtoimax #define __strtol_t intmax_t #define __xstrtol xstrtoimax #include "xstrtol.c"
/* xstrtoumax.c -- A more useful interface to strtoumax. Copyright 1999 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert. */ #if HAVE_CONFIG_H # include <config.h> #endif #if HAVE_INTTYPES_H # include <inttypes.h> #endif #define __strtol strtoumax #define __strtol_t uintmax_t #define __xstrtol xstrtoumax #include "xstrtol.c"
/* yesno.c -- read a yes/no response from stdin Copyright (C) 1990, 1998, 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <ctype.h> #if HAVE_STDLIB_H # include <stdlib.h> #endif #include <stdio.h> #include "unlocked-io.h" /* Read one line from standard input and return nonzero if that line begins with y or Y, otherwise return 0. */ int rpmatch (); int yesno () { /* We make some assumptions here: a) leading white space in the response are not vital b) the first 128 characters of the answer are enough (the rest can be ignored) I cannot think for a situation where this is not ok. --drepper@gnu */ char buf[128]; int len = 0; int c; while ((c = getchar ()) != EOF && c != '\n') if ((len > 0 && len < 127) || (len == 0 && !isspace (c))) buf[len++] = c; buf[len] = '\0'; return rpmatch (buf) == 1; }
/* Find the length of STRING, but scan at most MAXLEN characters. Copyright (C) 1996, 1997, 1998, 2000-2002 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #if HAVE_STRING_H # if !STDC_HEADERS && HAVE_MEMORY_H # include <memory.h> # endif /* Temporarily redefine strnlen so that an inconsistent prototype (on at least AIX4.3.2.0 w/gcc-2.95.3) doesn't cause trouble. */ # define strnlen system_strnlen # include <string.h> # undef strnlen #else # include <strings.h> #endif #ifndef HAVE_DECL_MEMCHR "this configure-time declaration test was not run" #endif #if !HAVE_DECL_MEMCHR char *memchr (); #endif #undef __strnlen #undef strnlen #ifndef weak_alias # define __strnlen strnlen #endif /* Find the length of STRING, but scan at most MAXLEN characters. If no '\0' terminator is found in that many characters, return MAXLEN. */ size_t __strnlen (const char *string, size_t maxlen) { const char *end = memchr (string, '\0', maxlen); return end ? (size_t) (end - string) : maxlen; } #ifdef weak_alias weak_alias (__strnlen, strnlen) #endif
/* Provide a replacement for the POSIX getcwd function. Copyright (C) 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* written by Jim Meyering */ #include <config.h> #include <stdlib.h> #include <string.h> #include <errno.h> #ifndef errno extern int errno; #endif #include <sys/types.h> #include "pathmax.h" #include "same.h" /* Guess high, because that makes the test below more conservative. But this is a kludge, because we should really use pathconf (".", _PC_NAME_MAX). But it's probably not worth the cost. */ #define KLUDGE_POSIX_NAME_MAX 255 #define MAX_SAFE_LEN (PATH_MAX - 1 - KLUDGE_POSIX_NAME_MAX - 1) /* Undefine getcwd here, as near the use as possible, in case any of the files included above define it to rpl_getcwd. */ #undef getcwd /* Any declaration of getcwd from headers included above has been changed to a declaration of rpl_getcwd. Declare it here. */ extern char *getcwd (char *buf, size_t size); /* This is a wrapper for getcwd. Some implementations (at least GNU libc 2.3.1 + linux-2.4.20) return non-NULL for a working directory name longer than PATH_MAX, yet the returned string is a strict prefix of the desired directory name. Upon such a failure, free the offending string, set errno to ENAMETOOLONG, and return NULL. I've heard that this is a Linux kernel bug, and that it has been fixed between 2.4.21-pre3 and 2.4.21-pre4. */ char * rpl_getcwd (char *buf, size_t size) { char *cwd = getcwd (buf, size); if (cwd == NULL) return NULL; if (strlen (cwd) <= MAX_SAFE_LEN || same_name (cwd, ".")) return cwd; free (cwd); errno = ENAMETOOLONG; return NULL; }
/* Define PATH_MAX somehow. Requires sys/types.h. Copyright (C) 1992, 1999, 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef _PATHMAX_H # define _PATHMAX_H # if HAVE_UNISTD_H # include <unistd.h> # endif /* Non-POSIX BSD systems might have gcc's limits.h, which doesn't define PATH_MAX but might cause redefinition warnings when sys/param.h is later included (as on MORE/BSD 4.3). */ # if defined _POSIX_VERSION || (defined HAVE_LIMITS_H && !defined __GNUC__) # include <limits.h> # endif # ifndef _POSIX_PATH_MAX # define _POSIX_PATH_MAX 255 # endif # if !defined PATH_MAX && defined _PC_PATH_MAX # define PATH_MAX (pathconf ("/", _PC_PATH_MAX) < 1 ? 1024 \ : pathconf ("/", _PC_PATH_MAX)) # endif /* Don't include sys/param.h if it already has been. */ # if defined HAVE_SYS_PARAM_H && !defined PATH_MAX && !defined MAXPATHLEN # include <sys/param.h> # endif # if !defined PATH_MAX && defined MAXPATHLEN # define PATH_MAX MAXPATHLEN # endif # ifndef PATH_MAX # define PATH_MAX _POSIX_PATH_MAX # endif #endif /* _PATHMAX_H */
/* Determine whether two file names refer to the same file. Copyright (C) 1997-2000, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef SAME_H_ # define SAME_H_ 1 int same_name (const char *source, const char *dest); #endif /* SAME_H_ */
/* sig2str.c -- convert between signal names and numbers Copyright (C) 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <limits.h> #include <signal.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include "sig2str.h" #ifndef SIGRTMIN # define SIGRTMIN 0 # undef SIGRTMAX #endif #ifndef SIGRTMAX # define SIGRTMAX (SIGRTMIN - 1) #endif #define NUMNAME(name) { SIG##name, #name } /* Signal names and numbers. Put the preferred name first. */ static struct numname { int num; char const name[8]; } numname_table[] = { /* Signals required by POSIX 1003.1-2001 base, listed in traditional numeric order. */ #ifdef SIGHUP NUMNAME (HUP), #endif #ifdef SIGINT NUMNAME (INT), #endif #ifdef SIGQUIT NUMNAME (QUIT), #endif #ifdef SIGILL NUMNAME (ILL), #endif #ifdef SIGTRAP NUMNAME (TRAP), #endif #ifdef SIGABRT NUMNAME (ABRT), #endif #ifdef SIGFPE NUMNAME (FPE), #endif #ifdef SIGKILL NUMNAME (KILL), #endif #ifdef SIGBUS NUMNAME (BUS), #endif #ifdef SIGSEGV NUMNAME (SEGV), #endif #ifdef SIGPIPE NUMNAME (PIPE), #endif #ifdef SIGALRM NUMNAME (ALRM), #endif #ifdef SIGTERM NUMNAME (TERM), #endif #ifdef SIGUSR1 NUMNAME (USR1), #endif #ifdef SIGUSR2 NUMNAME (USR2), #endif #ifdef SIGCHLD NUMNAME (CHLD), #endif #ifdef SIGURG NUMNAME (URG), #endif #ifdef SIGSTOP NUMNAME (STOP), #endif #ifdef SIGTSTP NUMNAME (TSTP), #endif #ifdef SIGCONT NUMNAME (CONT), #endif #ifdef SIGTTIN NUMNAME (TTIN), #endif #ifdef SIGTTOU NUMNAME (TTOU), #endif /* Signals required by POSIX 1003.1-2001 with the XSI extension. */ #ifdef SIGSYS NUMNAME (SYS), #endif #ifdef SIGPOLL NUMNAME (POLL), #endif #ifdef SIGVTALRM NUMNAME (VTALRM), #endif #ifdef SIGPROF NUMNAME (PROF), #endif #ifdef SIGXCPU NUMNAME (XCPU), #endif #ifdef SIGXFSZ NUMNAME (XFSZ), #endif /* Unix Version 7. */ #ifdef SIGIOT NUMNAME (IOT), /* Older name for ABRT. */ #endif #ifdef SIGEMT NUMNAME (EMT), #endif /* USG Unix. */ #ifdef SIGPHONE NUMNAME (PHONE), #endif #ifdef SIGWIND NUMNAME (WIND), #endif /* Unix System V. */ #ifdef SIGCLD NUMNAME (CLD), #endif #ifdef SIGPWR NUMNAME (PWR), #endif /* GNU/Linux 2.2 and Solaris 8. */ #ifdef SIGCANCEL NUMNAME (CANCEL), #endif #ifdef SIGLWP NUMNAME (LWP), #endif #ifdef SIGWAITING NUMNAME (WAITING), #endif #ifdef SIGFREEZE NUMNAME (FREEZE), #endif #ifdef SIGTHAW NUMNAME (THAW), #endif #ifdef SIGLOST NUMNAME (LOST), #endif #ifdef SIGWINCH NUMNAME (WINCH), #endif /* GNU/Linux 2.2. */ #ifdef SIGINFO NUMNAME (INFO), #endif #ifdef SIGIO NUMNAME (IO), #endif #ifdef SIGSTKFLT NUMNAME (STKFLT), #endif /* AIX 5L. */ #ifdef SIGDANGER NUMNAME (DANGER), #endif #ifdef SIGGRANT NUMNAME (GRANT), #endif #ifdef SIGMIGRATE NUMNAME (MIGRATE), #endif #ifdef SIGMSG NUMNAME (MSG), #endif #ifdef SIGPRE NUMNAME (PRE), #endif #ifdef SIGRETRACT NUMNAME (RETRACT), #endif #ifdef SIGSAK NUMNAME (SAK), #endif #ifdef SIGSOUND NUMNAME (SOUND), #endif /* Older AIX versions. */ #ifdef SIGALRM1 NUMNAME (ALRM1), /* unknown; taken from Bash 2.05 */ #endif #ifdef SIGKAP NUMNAME (KAP), /* Older name for SIGGRANT. */ #endif #ifdef SIGVIRT NUMNAME (VIRT), /* unknown; taken from Bash 2.05 */ #endif #ifdef SIGWINDOW NUMNAME (WINDOW), /* Older name for SIGWINCH. */ #endif /* BeOS */ #ifdef SIGKILLTHR NUMNAME (KILLTHR), #endif /* Older HP-UX versions. */ #ifdef SIGDIL NUMNAME (DIL), #endif /* Korn shell and Bash, of uncertain vintage. */ { 0, "EXIT" } }; #define NUMNAME_ENTRIES (sizeof numname_table / sizeof numname_table[0]) /* ISDIGIT differs from isdigit, as follows: - Its arg may be any int or unsigned int; it need not be an unsigned char. - It's guaranteed to evaluate its argument exactly once. - It's typically faster. POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to ISDIGIT_LOCALE unless it's important to use the locale's definition of `digit' even when the host does not conform to POSIX. */ #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9) /* Convert the signal name SIGNAME to a signal number. Return the signal number if successful, -1 otherwise. */ static int str2signum (char const *signame) { if (ISDIGIT (*signame)) { char *endp; long int n = strtol (signame, &endp, 10); if (! *endp && n <= SIGNUM_BOUND) return n; } else { unsigned i; for (i = 0; i < NUMNAME_ENTRIES; i++) if (strcmp (numname_table[i].name, signame) == 0) return numname_table[i].num; { char *endp; int rtmin = SIGRTMIN; int rtmax = SIGRTMAX; if (0 < rtmin && strncmp (signame, "RTMIN", 5) == 0) { long int n = strtol (signame + 5, &endp, 10); if (! *endp && 0 <= n && n <= rtmax - rtmin) return rtmin + n; } else if (0 < rtmax && strncmp (signame, "RTMAX", 5) == 0) { long int n = strtol (signame + 5, &endp, 10); if (! *endp && rtmin - rtmax <= n && n <= 0) return rtmax + n; } } } return -1; } /* Convert the signal name SIGNAME to the signal number *SIGNUM. Return 0 if successful, -1 otherwise. */ int str2sig (char const *signame, int *signum) { *signum = str2signum (signame); return *signum < 0 ? -1 : 0; } /* Convert SIGNUM to a signal name in SIGNAME. SIGNAME must point to a buffer of at least SIG2STR_MAX bytes. Return 0 if successful, -1 otherwise. */ int sig2str (int signum, char *signame) { unsigned i; for (i = 0; i < NUMNAME_ENTRIES; i++) if (numname_table[i].num == signum) { strcpy (signame, numname_table[i].name); return 0; } { int rtmin = SIGRTMIN; int rtmax = SIGRTMAX; if (! (rtmin <= signum && signum <= rtmax)) return -1; if (signum <= rtmin + (rtmax - rtmin) / 2) { int delta = signum - rtmin; sprintf (signame, delta ? "RTMIN+%d" : "RTMIN", delta); } else { int delta = rtmax - signum; sprintf (signame, delta ? "RTMAX-%d" : "RTMAX", delta); } return 0; } }
/* mountlist.c -- return a list of mounted filesystems Copyright (C) 1991, 1992, 1997-2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #include <sys/types.h> #ifdef STDC_HEADERS # include <stdlib.h> #else void free (); #endif #if defined STDC_HEADERS || defined HAVE_STRING_H # include <string.h> #else # include <strings.h> #endif #include "xalloc.h" #ifndef strstr char *strstr (); #endif #include <errno.h> #ifndef errno extern int errno; #endif #ifdef HAVE_FCNTL_H # include <fcntl.h> #endif #ifdef HAVE_UNISTD_H # include <unistd.h> #endif #if HAVE_SYS_PARAM_H # include <sys/param.h> #endif #if defined MOUNTED_GETFSSTAT /* OSF_1 and Darwin1.3.x */ # if HAVE_SYS_UCRED_H # include <sys/ucred.h> /* needed by powerpc-apple-darwin1.3.7 */ # endif # if HAVE_SYS_MOUNT_H # include <sys/mount.h> # endif # if HAVE_SYS_FS_TYPES_H # include <sys/fs_types.h> /* needed by powerpc-apple-darwin1.3.7 */ # endif # if HAVE_STRUCT_FSSTAT_F_FSTYPENAME # define FS_TYPE(Ent) ((Ent).f_fstypename) # else # define FS_TYPE(Ent) mnt_names[(Ent).f_type] # endif #endif /* MOUNTED_GETFSSTAT */ #ifdef MOUNTED_GETMNTENT1 /* 4.3BSD, SunOS, HP-UX, Dynix, Irix. */ # include <mntent.h> # if !defined MOUNTED # if defined _PATH_MOUNTED /* GNU libc */ # define MOUNTED _PATH_MOUNTED # endif # if defined MNT_MNTTAB /* HP-UX. */ # define MOUNTED MNT_MNTTAB # endif # if defined MNTTABNAME /* Dynix. */ # define MOUNTED MNTTABNAME # endif # endif #endif #ifdef MOUNTED_GETMNTINFO /* 4.4BSD. */ # include <sys/mount.h> #endif #ifdef MOUNTED_GETMNT /* Ultrix. */ # include <sys/mount.h> # include <sys/fs_types.h> #endif #ifdef MOUNTED_FS_STAT_DEV /* BeOS. */ # include <fs_info.h> # include <dirent.h> #endif #ifdef MOUNTED_FREAD /* SVR2. */ # include <mnttab.h> #endif #ifdef MOUNTED_FREAD_FSTYP /* SVR3. */ # include <mnttab.h> # include <sys/fstyp.h> # include <sys/statfs.h> #endif #ifdef MOUNTED_LISTMNTENT # include <mntent.h> #endif #ifdef MOUNTED_GETMNTENT2 /* SVR4. */ # include <sys/mnttab.h> #endif #ifdef MOUNTED_VMOUNT /* AIX. */ # include <fshelp.h> # include <sys/vfs.h> #endif #ifdef DOLPHIN /* So special that it's not worth putting this in autoconf. */ # undef MOUNTED_FREAD_FSTYP # define MOUNTED_GETMNTTBL #endif #if HAVE_SYS_MNTENT_H /* This is to get MNTOPT_IGNORE on e.g. SVR4. */ # include <sys/mntent.h> #endif #undef MNT_IGNORE #if defined MNTOPT_IGNORE && defined HAVE_HASMNTOPT # define MNT_IGNORE(M) hasmntopt ((M), MNTOPT_IGNORE) #else # define MNT_IGNORE(M) 0 #endif #include "mountlist.h" #include "unlocked-io.h" #ifdef MOUNTED_GETMNTENT1 /* 4.3BSD, SunOS, HP-UX, Dynix, Irix. */ /* Return the value of the hexadecimal number represented by CP. No prefix (like '0x') or suffix (like 'h') is expected to be part of CP. */ /* FIXME: this can overflow */ static int xatoi (char *cp) { int val; val = 0; while (*cp) { if (*cp >= 'a' && *cp <= 'f') val = val * 16 + *cp - 'a' + 10; else if (*cp >= 'A' && *cp <= 'F') val = val * 16 + *cp - 'A' + 10; else if (*cp >= '0' && *cp <= '9') val = val * 16 + *cp - '0'; else break; cp++; } return val; } #endif /* MOUNTED_GETMNTENT1. */ #if MOUNTED_GETMNTINFO # if ! HAVE_F_FSTYPENAME_IN_STATFS static char * fstype_to_string (short t) { switch (t) { # ifdef MOUNT_PC case MOUNT_PC: return "pc"; # endif # ifdef MOUNT_MFS case MOUNT_MFS: return "mfs"; # endif # ifdef MOUNT_LO case MOUNT_LO: return "lo"; # endif # ifdef MOUNT_TFS case MOUNT_TFS: return "tfs"; # endif # ifdef MOUNT_TMP case MOUNT_TMP: return "tmp"; # endif # ifdef MOUNT_UFS case MOUNT_UFS: return "ufs" ; # endif # ifdef MOUNT_NFS case MOUNT_NFS: return "nfs" ; # endif # ifdef MOUNT_MSDOS case MOUNT_MSDOS: return "msdos" ; # endif # ifdef MOUNT_LFS case MOUNT_LFS: return "lfs" ; # endif # ifdef MOUNT_LOFS case MOUNT_LOFS: return "lofs" ; # endif # ifdef MOUNT_FDESC case MOUNT_FDESC: return "fdesc" ; # endif # ifdef MOUNT_PORTAL case MOUNT_PORTAL: return "portal" ; # endif # ifdef MOUNT_NULL case MOUNT_NULL: return "null" ; # endif # ifdef MOUNT_UMAP case MOUNT_UMAP: return "umap" ; # endif # ifdef MOUNT_KERNFS case MOUNT_KERNFS: return "kernfs" ; # endif # ifdef MOUNT_PROCFS case MOUNT_PROCFS: return "procfs" ; # endif # ifdef MOUNT_AFS case MOUNT_AFS: return "afs" ; # endif # ifdef MOUNT_CD9660 case MOUNT_CD9660: return "cd9660" ; # endif # ifdef MOUNT_UNION case MOUNT_UNION: return "union" ; # endif # ifdef MOUNT_DEVFS case MOUNT_DEVFS: return "devfs" ; # endif # ifdef MOUNT_EXT2FS case MOUNT_EXT2FS: return "ext2fs" ; # endif default: return "?"; } } # endif /* ! HAVE_F_FSTYPENAME_IN_STATFS */ /* __NetBSD__ || BSD_NET2 || __OpenBSD__ */ static char * fsp_to_string (const struct statfs *fsp) { # if defined HAVE_F_FSTYPENAME_IN_STATFS return (char *) (fsp->f_fstypename); # else return fstype_to_string (fsp->f_type); # endif } #endif /* MOUNTED_GETMNTINFO */ #ifdef MOUNTED_VMOUNT /* AIX. */ static char * fstype_to_string (int t) { struct vfs_ent *e; e = getvfsbytype (t); if (!e || !e->vfsent_name) return "none"; else return e->vfsent_name; } #endif /* MOUNTED_VMOUNT */ /* Return a list of the currently mounted filesystems, or NULL on error. Add each entry to the tail of the list so that they stay in order. If NEED_FS_TYPE is nonzero, ensure that the filesystem type fields in the returned list are valid. Otherwise, they might not be. */ struct mount_entry * read_filesystem_list (int need_fs_type) { struct mount_entry *mount_list; struct mount_entry *me; struct mount_entry **mtail = &mount_list; #ifdef MOUNTED_LISTMNTENT { struct tabmntent *mntlist, *p; struct mntent *mnt; struct mount_entry *me; /* the third and fourth arguments could be used to filter mounts, but Crays doesn't seem to have any mounts that we want to remove. Specifically, automount create normal NFS mounts. */ if(listmntent(&mntlist, KMTAB, NULL, NULL) < 0) return NULL; for (p = mntlist; p; p = p->next) { mnt = p->ment; me = (struct mount_entry*) xmalloc(sizeof (struct mount_entry)); me->me_devname = xstrdup(mnt->mnt_fsname); me->me_mountdir = xstrdup(mnt->mnt_dir); me->me_type = xstrdup(mnt->mnt_type); me->me_dummy = ME_DUMMY (me->me_devname, me->me_type); me->me_remote = ME_REMOTE (me->me_devname, me->me_type); me->me_dev = -1; *mtail = me; mtail = &me->me_next; } freemntlist(mntlist); } #endif #ifdef MOUNTED_GETMNTENT1 /* 4.3BSD, SunOS, HP-UX, Dynix, Irix. */ { struct mntent *mnt; char *table = MOUNTED; FILE *fp; char *devopt; fp = setmntent (table, "r"); if (fp == NULL) return NULL; while ((mnt = getmntent (fp))) { me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry)); me->me_devname = xstrdup (mnt->mnt_fsname); me->me_mountdir = xstrdup (mnt->mnt_dir); me->me_type = xstrdup (mnt->mnt_type); me->me_dummy = ME_DUMMY (me->me_devname, me->me_type); me->me_remote = ME_REMOTE (me->me_devname, me->me_type); devopt = strstr (mnt->mnt_opts, "dev="); if (devopt) { if (devopt[4] == '0' && (devopt[5] == 'x' || devopt[5] == 'X')) me->me_dev = xatoi (devopt + 6); else me->me_dev = xatoi (devopt + 4); } else me->me_dev = (dev_t) -1; /* Magic; means not known yet. */ /* Add to the linked list. */ *mtail = me; mtail = &me->me_next; } if (endmntent (fp) == 0) goto free_then_fail; } #endif /* MOUNTED_GETMNTENT1. */ #ifdef MOUNTED_GETMNTINFO /* 4.4BSD. */ { struct statfs *fsp; int entries; entries = getmntinfo (&fsp, MNT_NOWAIT); if (entries < 0) return NULL; for (; entries-- > 0; fsp++) { char *fs_type = fsp_to_string (fsp); me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry)); me->me_devname = xstrdup (fsp->f_mntfromname); me->me_mountdir = xstrdup (fsp->f_mntonname); me->me_type = fs_type; me->me_dummy = ME_DUMMY (me->me_devname, me->me_type); me->me_remote = ME_REMOTE (me->me_devname, me->me_type); me->me_dev = (dev_t) -1; /* Magic; means not known yet. */ /* Add to the linked list. */ *mtail = me; mtail = &me->me_next; } } #endif /* MOUNTED_GETMNTINFO */ #ifdef MOUNTED_GETMNT /* Ultrix. */ { int offset = 0; int val; struct fs_data fsd; while (errno = 0, 0 < (val = getmnt (&offset, &fsd, sizeof (fsd), NOSTAT_MANY, (char *) 0))) { me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry)); me->me_devname = xstrdup (fsd.fd_req.devname); me->me_mountdir = xstrdup (fsd.fd_req.path); me->me_type = gt_names[fsd.fd_req.fstype]; me->me_dummy = ME_DUMMY (me->me_devname, me->me_type); me->me_remote = ME_REMOTE (me->me_devname, me->me_type); me->me_dev = fsd.fd_req.dev; /* Add to the linked list. */ *mtail = me; mtail = &me->me_next; } if (val < 0) goto free_then_fail; } #endif /* MOUNTED_GETMNT. */ #if defined MOUNTED_FS_STAT_DEV /* BeOS */ { /* The next_dev() and fs_stat_dev() system calls give the list of all filesystems, including the information returned by statvfs() (fs type, total blocks, free blocks etc.), but without the mount point. But on BeOS all filesystems except / are mounted in the rootfs, directly under /. The directory name of the mount point is often, but not always, identical to the volume name of the device. We therefore get the list of subdirectories of /, and the list of all filesystems, and match the two lists. */ DIR *dirp; struct rootdir_entry { char *name; dev_t dev; ino_t ino; struct rootdir_entry *next; }; struct rootdir_entry *rootdir_list; struct rootdir_entry **rootdir_tail; int32 pos; dev_t dev; fs_info fi; /* All volumes are mounted in the rootfs, directly under /. */ rootdir_list = NULL; rootdir_tail = &rootdir_list; dirp = opendir ("/"); if (dirp) { struct dirent *d; while ((d = readdir (dirp)) != NULL) { char *name; struct stat statbuf; if (strcmp (d->d_name, "..") == 0) continue; if (strcmp (d->d_name, ".") == 0) name = xstrdup ("/"); else { name = xmalloc (1 + strlen (d->d_name) + 1); name[0] = '/'; strcpy (name + 1, d->d_name); } if (lstat (name, &statbuf) >= 0 && S_ISDIR (statbuf.st_mode)) { struct rootdir_entry *re; re = (struct rootdir_entry *) xmalloc (sizeof (struct rootdir_entry)); re->name = name; re->dev = statbuf.st_dev; re->ino = statbuf.st_ino; /* Add to the linked list. */ *rootdir_tail = re; rootdir_tail = &re->next; } else free (name); } closedir (dirp); } *rootdir_tail = NULL; for (pos = 0; (dev = next_dev (&pos)) >= 0; ) if (fs_stat_dev (dev, &fi) >= 0) { /* Note: fi.dev == dev. */ struct rootdir_entry *re; for (re = rootdir_list; re; re = re->next) if (re->dev == fi.dev && re->ino == fi.root) break; me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry)); me->me_devname = xstrdup (fi.device_name[0] != '\0' ? fi.device_name : fi.fsh_name); me->me_mountdir = xstrdup (re != NULL ? re->name : fi.fsh_name); me->me_type = xstrdup (fi.fsh_name); me->me_dev = fi.dev; me->me_dummy = 0; me->me_remote = (fi.flags & B_FS_IS_SHARED) != 0; /* Add to the linked list. */ *mtail = me; mtail = &me->me_next; } *mtail = NULL; while (rootdir_list != NULL) { struct rootdir_entry *re = rootdir_list; rootdir_list = re->next; free (re->name); free (re); } } #endif /* MOUNTED_FS_STAT_DEV */ #if defined MOUNTED_GETFSSTAT /* __alpha running OSF_1 */ { int numsys, counter, bufsize; struct statfs *stats; numsys = getfsstat ((struct statfs *)0, 0L, MNT_WAIT); if (numsys < 0) return (NULL); bufsize = (1 + numsys) * sizeof (struct statfs); stats = (struct statfs *)xmalloc (bufsize); numsys = getfsstat (stats, bufsize, MNT_WAIT); if (numsys < 0) { free (stats); return (NULL); } for (counter = 0; counter < numsys; counter++) { me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry)); me->me_devname = xstrdup (stats[counter].f_mntfromname); me->me_mountdir = xstrdup (stats[counter].f_mntonname); me->me_type = xstrdup (FS_TYPE (stats[counter])); me->me_dummy = ME_DUMMY (me->me_devname, me->me_type); me->me_remote = ME_REMOTE (me->me_devname, me->me_type); me->me_dev = (dev_t) -1; /* Magic; means not known yet. */ /* Add to the linked list. */ *mtail = me; mtail = &me->me_next; } free (stats); } #endif /* MOUNTED_GETFSSTAT */ #if defined MOUNTED_FREAD || defined MOUNTED_FREAD_FSTYP /* SVR[23]. */ { struct mnttab mnt; char *table = "/etc/mnttab"; FILE *fp; fp = fopen (table, "r"); if (fp == NULL) return NULL; while (fread (&mnt, sizeof mnt, 1, fp) > 0) { me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry)); # ifdef GETFSTYP /* SVR3. */ me->me_devname = xstrdup (mnt.mt_dev); # else me->me_devname = xmalloc (strlen (mnt.mt_dev) + 6); strcpy (me->me_devname, "/dev/"); strcpy (me->me_devname + 5, mnt.mt_dev); # endif me->me_mountdir = xstrdup (mnt.mt_filsys); me->me_dev = (dev_t) -1; /* Magic; means not known yet. */ me->me_type = ""; # ifdef GETFSTYP /* SVR3. */ if (need_fs_type) { struct statfs fsd; char typebuf[FSTYPSZ]; if (statfs (me->me_mountdir, &fsd, sizeof fsd, 0) != -1 && sysfs (GETFSTYP, fsd.f_fstyp, typebuf) != -1) me->me_type = xstrdup (typebuf); } # endif me->me_dummy = ME_DUMMY (me->me_devname, me->me_type); me->me_remote = ME_REMOTE (me->me_devname, me->me_type); /* Add to the linked list. */ *mtail = me; mtail = &me->me_next; } if (ferror (fp)) { int saved_errno = errno; fclose (fp); errno = saved_errno; goto free_then_fail; } if (fclose (fp) == EOF) goto free_then_fail; } #endif /* MOUNTED_FREAD || MOUNTED_FREAD_FSTYP. */ #ifdef MOUNTED_GETMNTTBL /* DolphinOS goes it's own way */ { struct mntent **mnttbl=getmnttbl(),**ent; for (ent=mnttbl;*ent;ent++) { me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry)); me->me_devname = xstrdup ( (*ent)->mt_resource); me->me_mountdir = xstrdup( (*ent)->mt_directory); me->me_type = xstrdup ((*ent)->mt_fstype); me->me_dummy = ME_DUMMY (me->me_devname, me->me_type); me->me_remote = ME_REMOTE (me->me_devname, me->me_type); me->me_dev = (dev_t) -1; /* Magic; means not known yet. */ /* Add to the linked list. */ *mtail = me; mtail = &me->me_next; } endmnttbl(); } #endif #ifdef MOUNTED_GETMNTENT2 /* SVR4. */ { struct mnttab mnt; char *table = MNTTAB; FILE *fp; int ret; int lockfd = -1; # if defined F_RDLCK && defined F_SETLKW /* MNTTAB_LOCK is a macro name of our own invention; it's not present in e.g. Solaris 2.6. If the SVR4 folks ever define a macro for this file name, we should use their macro name instead. (Why not just lock MNTTAB directly? We don't know.) */ # ifndef MNTTAB_LOCK # define MNTTAB_LOCK "/etc/.mnttab.lock" # endif lockfd = open (MNTTAB_LOCK, O_RDONLY); if (0 <= lockfd) { struct flock flock; flock.l_type = F_RDLCK; flock.l_whence = SEEK_SET; flock.l_start = 0; flock.l_len = 0; while (fcntl (lockfd, F_SETLKW, &flock) == -1) if (errno != EINTR) { int saved_errno = errno; close (lockfd); errno = saved_errno; return NULL; } } else if (errno != ENOENT) return NULL; # endif errno = 0; fp = fopen (table, "r"); if (fp == NULL) ret = errno; else { while ((ret = getmntent (fp, &mnt)) == 0) { me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry)); me->me_devname = xstrdup (mnt.mnt_special); me->me_mountdir = xstrdup (mnt.mnt_mountp); me->me_type = xstrdup (mnt.mnt_fstype); me->me_dummy = MNT_IGNORE (&mnt) != 0; me->me_remote = ME_REMOTE (me->me_devname, me->me_type); me->me_dev = (dev_t) -1; /* Magic; means not known yet. */ /* Add to the linked list. */ *mtail = me; mtail = &me->me_next; } ret = fclose (fp) == EOF ? errno : 0 < ret ? 0 : -1; } if (0 <= lockfd && close (lockfd) != 0) ret = errno; if (0 <= ret) { errno = ret; goto free_then_fail; } } #endif /* MOUNTED_GETMNTENT2. */ #ifdef MOUNTED_VMOUNT /* AIX. */ { int bufsize; char *entries, *thisent; struct vmount *vmp; int n_entries; int i; /* Ask how many bytes to allocate for the mounted filesystem info. */ if (mntctl (MCTL_QUERY, sizeof bufsize, (struct vmount *) &bufsize) != 0) return NULL; entries = xmalloc (bufsize); /* Get the list of mounted filesystems. */ n_entries = mntctl (MCTL_QUERY, bufsize, (struct vmount *) entries); if (n_entries < 0) { int saved_errno = errno; free (entries); errno = saved_errno; return NULL; } for (i = 0, thisent = entries; i < n_entries; i++, thisent += vmp->vmt_length) { char *options, *ignore; vmp = (struct vmount *) thisent; me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry)); if (vmp->vmt_flags & MNT_REMOTE) { char *host, *path; me->me_remote = 1; /* Prepend the remote pathname. */ host = thisent + vmp->vmt_data[VMT_HOSTNAME].vmt_off; path = thisent + vmp->vmt_data[VMT_OBJECT].vmt_off; me->me_devname = xmalloc (strlen (host) + strlen (path) + 2); strcpy (me->me_devname, host); strcat (me->me_devname, ":"); strcat (me->me_devname, path); } else { me->me_remote = 0; me->me_devname = xstrdup (thisent + vmp->vmt_data[VMT_OBJECT].vmt_off); } me->me_mountdir = xstrdup (thisent + vmp->vmt_data[VMT_STUB].vmt_off); me->me_type = xstrdup (fstype_to_string (vmp->vmt_gfstype)); options = thisent + vmp->vmt_data[VMT_ARGS].vmt_off; ignore = strstr (options, "ignore"); me->me_dummy = (ignore && (ignore == options || ignore[-1] == ',') && (ignore[sizeof "ignore" - 1] == ',' || ignore[sizeof "ignore" - 1] == '\0')); me->me_dev = (dev_t) -1; /* vmt_fsid might be the info we want. */ /* Add to the linked list. */ *mtail = me; mtail = &me->me_next; } free (entries); } #endif /* MOUNTED_VMOUNT. */ *mtail = NULL; return mount_list; free_then_fail: { int saved_errno = errno; *mtail = NULL; while (mount_list) { me = mount_list->me_next; free (mount_list->me_devname); free (mount_list->me_mountdir); /* FIXME: me_type is not always malloced. */ free (mount_list); mount_list = me; } errno = saved_errno; return NULL; } }
/* Extended regular expression matching and search library, version 0.12. (Implements POSIX draft P1003.2/D11.2, except for some of the internationalization features.) Copyright (C) 1993-1999, 2000, 2001 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ /* AIX requires this to be the first thing in the file. */ #if defined _AIX && !defined REGEX_MALLOC #pragma alloca #endif #undef _GNU_SOURCE #define _GNU_SOURCE #ifdef HAVE_CONFIG_H # include <config.h> #endif #ifndef PARAMS # if defined __GNUC__ || (defined __STDC__ && __STDC__) # define PARAMS(args) args # else # define PARAMS(args) () # endif /* GCC. */ #endif /* Not PARAMS. */ #ifndef INSIDE_RECURSION # if defined STDC_HEADERS && !defined emacs # include <stddef.h> # else /* We need this for `regex.h', and perhaps for the Emacs include files. */ # include <sys/types.h> # endif # define WIDE_CHAR_SUPPORT (HAVE_WCTYPE_H && HAVE_WCHAR_H && HAVE_BTOWC) /* For platform which support the ISO C amendement 1 functionality we support user defined character classes. */ # if defined _LIBC || WIDE_CHAR_SUPPORT /* Solaris 2.5 has a bug: <wchar.h> must be included before <wctype.h>. */ # include <wchar.h> # include <wctype.h> # endif # ifdef _LIBC /* We have to keep the namespace clean. */ # define regfree(preg) __regfree (preg) # define regexec(pr, st, nm, pm, ef) __regexec (pr, st, nm, pm, ef) # define regcomp(preg, pattern, cflags) __regcomp (preg, pattern, cflags) # define regerror(errcode, preg, errbuf, errbuf_size) \ __regerror(errcode, preg, errbuf, errbuf_size) # define re_set_registers(bu, re, nu, st, en) \ __re_set_registers (bu, re, nu, st, en) # define re_match_2(bufp, string1, size1, string2, size2, pos, regs, stop) \ __re_match_2 (bufp, string1, size1, string2, size2, pos, regs, stop) # define re_match(bufp, string, size, pos, regs) \ __re_match (bufp, string, size, pos, regs) # define re_search(bufp, string, size, startpos, range, regs) \ __re_search (bufp, string, size, startpos, range, regs) # define re_compile_pattern(pattern, length, bufp) \ __re_compile_pattern (pattern, length, bufp) # define re_set_syntax(syntax) __re_set_syntax (syntax) # define re_search_2(bufp, st1, s1, st2, s2, startpos, range, regs, stop) \ __re_search_2 (bufp, st1, s1, st2, s2, startpos, range, regs, stop) # define re_compile_fastmap(bufp) __re_compile_fastmap (bufp) # define btowc __btowc # define iswctype __iswctype # define mbrtowc __mbrtowc # define wcslen __wcslen # define wcscoll __wcscoll # define wcrtomb __wcrtomb /* We are also using some library internals. */ # include <locale/localeinfo.h> # include <locale/elem-hash.h> # include <langinfo.h> # include <locale/coll-lookup.h> # endif /* This is for other GNU distributions with internationalized messages. */ # if (HAVE_LIBINTL_H && ENABLE_NLS) || defined _LIBC # include <libintl.h> # ifdef _LIBC # undef gettext # define gettext(msgid) __dcgettext ("libc", msgid, LC_MESSAGES) # endif # else # define gettext(msgid) (msgid) # endif # ifndef gettext_noop /* This define is so xgettext can find the internationalizable strings. */ # define gettext_noop(String) String # endif /* Support for bounded pointers. */ # if !defined _LIBC && !defined __BOUNDED_POINTERS__ # define __bounded /* nothing */ # define __unbounded /* nothing */ # define __ptrvalue /* nothing */ # endif /* The `emacs' switch turns on certain matching commands that make sense only in Emacs. */ # ifdef emacs # include "lisp.h" # include "buffer.h" # include "syntax.h" # else /* not emacs */ /* If we are not linking with Emacs proper, we can't use the relocating allocator even if config.h says that we can. */ # undef REL_ALLOC # if defined STDC_HEADERS || defined _LIBC # include <stdlib.h> # else char *malloc (); char *realloc (); # endif /* When used in Emacs's lib-src, we need to get bzero and bcopy somehow. If nothing else has been done, use the method below. */ # ifdef INHIBIT_STRING_HEADER # if !(defined HAVE_BZERO && defined HAVE_BCOPY) # if !defined bzero && !defined bcopy # undef INHIBIT_STRING_HEADER # endif # endif # endif /* This is the normal way of making sure we have a bcopy and a bzero. This is used in most programs--a few other programs avoid this by defining INHIBIT_STRING_HEADER. */ # ifndef INHIBIT_STRING_HEADER # if defined HAVE_STRING_H || defined STDC_HEADERS || defined _LIBC # include <string.h> # ifndef bzero # ifndef _LIBC # define bzero(s, n) (memset (s, '\0', n), (s)) # else # define bzero(s, n) __bzero (s, n) # endif # endif # else # include <strings.h> # ifndef memcmp # define memcmp(s1, s2, n) bcmp (s1, s2, n) # endif # ifndef memcpy # define memcpy(d, s, n) (bcopy (s, d, n), (d)) # endif # endif # endif /* Define the syntax stuff for \<, \>, etc. */ /* This must be nonzero for the wordchar and notwordchar pattern commands in re_match_2. */ # ifndef Sword # define Sword 1 # endif # ifdef SWITCH_ENUM_BUG # define SWITCH_ENUM_CAST(x) ((int)(x)) # else # define SWITCH_ENUM_CAST(x) (x) # endif # endif /* not emacs */ # if defined _LIBC || HAVE_LIMITS_H # include <limits.h> # endif # ifndef MB_LEN_MAX # define MB_LEN_MAX 1 # endif /* Get the interface, including the syntax bits. */ # include <regex.h> /* isalpha etc. are used for the character classes. */ # include <ctype.h> /* Jim Meyering writes: "... Some ctype macros are valid only for character codes that isascii says are ASCII (SGI's IRIX-4.0.5 is one such system --when using /bin/cc or gcc but without giving an ansi option). So, all ctype uses should be through macros like ISPRINT... If STDC_HEADERS is defined, then autoconf has verified that the ctype macros don't need to be guarded with references to isascii. ... Defining isascii to 1 should let any compiler worth its salt eliminate the && through constant folding." Solaris defines some of these symbols so we must undefine them first. */ # if defined STDC_HEADERS || (!defined isascii && !defined HAVE_ISASCII) # define IN_CTYPE_DOMAIN(c) 1 # else # define IN_CTYPE_DOMAIN(c) isascii(c) # endif # ifdef isblank # define ISBLANK(c) (IN_CTYPE_DOMAIN (c) && isblank (c)) # else # define ISBLANK(c) ((c) == ' ' || (c) == '\t') # endif # ifdef isgraph # define ISGRAPH(c) (IN_CTYPE_DOMAIN (c) && isgraph (c)) # else # define ISGRAPH(c) (IN_CTYPE_DOMAIN (c) && isprint (c) && !isspace (c)) # endif # undef ISPRINT # define ISPRINT(c) (IN_CTYPE_DOMAIN (c) && isprint (c)) # define ISDIGIT(c) (IN_CTYPE_DOMAIN (c) && isdigit (c)) # define ISALNUM(c) (IN_CTYPE_DOMAIN (c) && isalnum (c)) # define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c)) # define ISCNTRL(c) (IN_CTYPE_DOMAIN (c) && iscntrl (c)) # define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c)) # define ISPUNCT(c) (IN_CTYPE_DOMAIN (c) && ispunct (c)) # define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c)) # define ISUPPER(c) (IN_CTYPE_DOMAIN (c) && isupper (c)) # define ISXDIGIT(c) (IN_CTYPE_DOMAIN (c) && isxdigit (c)) # ifdef _tolower # define TOLOWER(c) _tolower(c) # else # define TOLOWER(c) tolower(c) # endif # ifndef NULL # define NULL (void *)0 # endif /* We remove any previous definition of `SIGN_EXTEND_CHAR', since ours (we hope) works properly with all combinations of machines, compilers, `char' and `unsigned char' argument types. (Per Bothner suggested the basic approach.) */ # undef SIGN_EXTEND_CHAR # if __STDC__ # define SIGN_EXTEND_CHAR(c) ((signed char) (c)) # else /* not __STDC__ */ /* As in Harbison and Steele. */ # define SIGN_EXTEND_CHAR(c) ((((unsigned char) (c)) ^ 128) - 128) # endif # ifndef emacs /* How many characters in the character set. */ # define CHAR_SET_SIZE 256 # ifdef SYNTAX_TABLE extern char *re_syntax_table; # else /* not SYNTAX_TABLE */ static char re_syntax_table[CHAR_SET_SIZE]; static void init_syntax_once PARAMS ((void)); static void init_syntax_once () { register int c; static int done = 0; if (done) return; bzero (re_syntax_table, sizeof re_syntax_table); for (c = 0; c < CHAR_SET_SIZE; ++c) if (ISALNUM (c)) re_syntax_table[c] = Sword; re_syntax_table['_'] = Sword; done = 1; } # endif /* not SYNTAX_TABLE */ # define SYNTAX(c) re_syntax_table[(unsigned char) (c)] # endif /* emacs */ /* Should we use malloc or alloca? If REGEX_MALLOC is not defined, we use `alloca' instead of `malloc'. This is because using malloc in re_search* or re_match* could cause memory leaks when C-g is used in Emacs; also, malloc is slower and causes storage fragmentation. On the other hand, malloc is more portable, and easier to debug. Because we sometimes use alloca, some routines have to be macros, not functions -- `alloca'-allocated space disappears at the end of the function it is called in. */ # ifdef REGEX_MALLOC # define REGEX_ALLOCATE malloc # define REGEX_REALLOCATE(source, osize, nsize) realloc (source, nsize) # define REGEX_FREE free # else /* not REGEX_MALLOC */ /* Emacs already defines alloca, sometimes. */ # ifndef alloca /* Make alloca work the best possible way. */ # ifdef __GNUC__ # define alloca __builtin_alloca # else /* not __GNUC__ */ # if HAVE_ALLOCA_H # include <alloca.h> # endif /* HAVE_ALLOCA_H */ # endif /* not __GNUC__ */ # endif /* not alloca */ # define REGEX_ALLOCATE alloca /* Assumes a `char *destination' variable. */ # define REGEX_REALLOCATE(source, osize, nsize) \ (destination = (char *) alloca (nsize), \ memcpy (destination, source, osize)) /* No need to do anything to free, after alloca. */ # define REGEX_FREE(arg) ((void)0) /* Do nothing! But inhibit gcc warning. */ # endif /* not REGEX_MALLOC */ /* Define how to allocate the failure stack. */ # if defined REL_ALLOC && defined REGEX_MALLOC # define REGEX_ALLOCATE_STACK(size) \ r_alloc (&failure_stack_ptr, (size)) # define REGEX_REALLOCATE_STACK(source, osize, nsize) \ r_re_alloc (&failure_stack_ptr, (nsize)) # define REGEX_FREE_STACK(ptr) \ r_alloc_free (&failure_stack_ptr) # else /* not using relocating allocator */ # ifdef REGEX_MALLOC # define REGEX_ALLOCATE_STACK malloc # define REGEX_REALLOCATE_STACK(source, osize, nsize) realloc (source, nsize) # define REGEX_FREE_STACK free # else /* not REGEX_MALLOC */ # define REGEX_ALLOCATE_STACK alloca # define REGEX_REALLOCATE_STACK(source, osize, nsize) \ REGEX_REALLOCATE (source, osize, nsize) /* No need to explicitly free anything. */ # define REGEX_FREE_STACK(arg) # endif /* not REGEX_MALLOC */ # endif /* not using relocating allocator */ /* True if `size1' is non-NULL and PTR is pointing anywhere inside `string1' or just past its end. This works if PTR is NULL, which is a good thing. */ # define FIRST_STRING_P(ptr) \ (size1 && string1 <= (ptr) && (ptr) <= string1 + size1) /* (Re)Allocate N items of type T using malloc, or fail. */ # define TALLOC(n, t) ((t *) malloc ((n) * sizeof (t))) # define RETALLOC(addr, n, t) ((addr) = (t *) realloc (addr, (n) * sizeof (t))) # define RETALLOC_IF(addr, n, t) \ if (addr) RETALLOC((addr), (n), t); else (addr) = TALLOC ((n), t) # define REGEX_TALLOC(n, t) ((t *) REGEX_ALLOCATE ((n) * sizeof (t))) # define BYTEWIDTH 8 /* In bits. */ # define STREQ(s1, s2) ((strcmp (s1, s2) == 0)) # undef MAX # undef MIN # define MAX(a, b) ((a) > (b) ? (a) : (b)) # define MIN(a, b) ((a) < (b) ? (a) : (b)) typedef char boolean; # define false 0 # define true 1 static reg_errcode_t byte_regex_compile _RE_ARGS ((const char *pattern, size_t size, reg_syntax_t syntax, struct re_pattern_buffer *bufp)); static int byte_re_match_2_internal PARAMS ((struct re_pattern_buffer *bufp, const char *string1, int size1, const char *string2, int size2, int pos, struct re_registers *regs, int stop)); static int byte_re_search_2 PARAMS ((struct re_pattern_buffer *bufp, const char *string1, int size1, const char *string2, int size2, int startpos, int range, struct re_registers *regs, int stop)); static int byte_re_compile_fastmap PARAMS ((struct re_pattern_buffer *bufp)); #ifdef MBS_SUPPORT static reg_errcode_t wcs_regex_compile _RE_ARGS ((const char *pattern, size_t size, reg_syntax_t syntax, struct re_pattern_buffer *bufp)); static int wcs_re_match_2_internal PARAMS ((struct re_pattern_buffer *bufp, const char *cstring1, int csize1, const char *cstring2, int csize2, int pos, struct re_registers *regs, int stop, wchar_t *string1, int size1, wchar_t *string2, int size2, int *mbs_offset1, int *mbs_offset2)); static int wcs_re_search_2 PARAMS ((struct re_pattern_buffer *bufp, const char *string1, int size1, const char *string2, int size2, int startpos, int range, struct re_registers *regs, int stop)); static int wcs_re_compile_fastmap PARAMS ((struct re_pattern_buffer *bufp)); #endif /* These are the command codes that appear in compiled regular expressions. Some opcodes are followed by argument bytes. A command code can specify any interpretation whatsoever for its arguments. Zero bytes may appear in the compiled regular expression. */ typedef enum { no_op = 0, /* Succeed right away--no more backtracking. */ succeed, /* Followed by one byte giving n, then by n literal bytes. */ exactn, # ifdef MBS_SUPPORT /* Same as exactn, but contains binary data. */ exactn_bin, # endif /* Matches any (more or less) character. */ anychar, /* Matches any one char belonging to specified set. First following byte is number of bitmap bytes. Then come bytes for a bitmap saying which chars are in. Bits in each byte are ordered low-bit-first. A character is in the set if its bit is 1. A character too large to have a bit in the map is automatically not in the set. */ /* ifdef MBS_SUPPORT, following element is length of character classes, length of collating symbols, length of equivalence classes, length of character ranges, and length of characters. Next, character class element, collating symbols elements, equivalence class elements, range elements, and character elements follow. See regex_compile function. */ charset, /* Same parameters as charset, but match any character that is not one of those specified. */ charset_not, /* Start remembering the text that is matched, for storing in a register. Followed by one byte with the register number, in the range 0 to one less than the pattern buffer's re_nsub field. Then followed by one byte with the number of groups inner to this one. (This last has to be part of the start_memory only because we need it in the on_failure_jump of re_match_2.) */ start_memory, /* Stop remembering the text that is matched and store it in a memory register. Followed by one byte with the register number, in the range 0 to one less than `re_nsub' in the pattern buffer, and one byte with the number of inner groups, just like `start_memory'. (We need the number of inner groups here because we don't have any easy way of finding the corresponding start_memory when we're at a stop_memory.) */ stop_memory, /* Match a duplicate of something remembered. Followed by one byte containing the register number. */ duplicate, /* Fail unless at beginning of line. */ begline, /* Fail unless at end of line. */ endline, /* Succeeds if at beginning of buffer (if emacs) or at beginning of string to be matched (if not). */ begbuf, /* Analogously, for end of buffer/string. */ endbuf, /* Followed by two byte relative address to which to jump. */ jump, /* Same as jump, but marks the end of an alternative. */ jump_past_alt, /* Followed by two-byte relative address of place to resume at in case of failure. */ /* ifdef MBS_SUPPORT, the size of address is 1. */ on_failure_jump, /* Like on_failure_jump, but pushes a placeholder instead of the current string position when executed. */ on_failure_keep_string_jump, /* Throw away latest failure point and then jump to following two-byte relative address. */ /* ifdef MBS_SUPPORT, the size of address is 1. */ pop_failure_jump, /* Change to pop_failure_jump if know won't have to backtrack to match; otherwise change to jump. This is used to jump back to the beginning of a repeat. If what follows this jump clearly won't match what the repeat does, such that we can be sure that there is no use backtracking out of repetitions already matched, then we change it to a pop_failure_jump. Followed by two-byte address. */ /* ifdef MBS_SUPPORT, the size of address is 1. */ maybe_pop_jump, /* Jump to following two-byte address, and push a dummy failure point. This failure point will be thrown away if an attempt is made to use it for a failure. A `+' construct makes this before the first repeat. Also used as an intermediary kind of jump when compiling an alternative. */ /* ifdef MBS_SUPPORT, the size of address is 1. */ dummy_failure_jump, /* Push a dummy failure point and continue. Used at the end of alternatives. */ push_dummy_failure, /* Followed by two-byte relative address and two-byte number n. After matching N times, jump to the address upon failure. */ /* ifdef MBS_SUPPORT, the size of address is 1. */ succeed_n, /* Followed by two-byte relative address, and two-byte number n. Jump to the address N times, then fail. */ /* ifdef MBS_SUPPORT, the size of address is 1. */ jump_n, /* Set the following two-byte relative address to the subsequent two-byte number. The address *includes* the two bytes of number. */ /* ifdef MBS_SUPPORT, the size of address is 1. */ set_number_at, wordchar, /* Matches any word-constituent character. */ notwordchar, /* Matches any char that is not a word-constituent. */ wordbeg, /* Succeeds if at word beginning. */ wordend, /* Succeeds if at word end. */ wordbound, /* Succeeds if at a word boundary. */ notwordbound /* Succeeds if not at a word boundary. */ # ifdef emacs ,before_dot, /* Succeeds if before point. */ at_dot, /* Succeeds if at point. */ after_dot, /* Succeeds if after point. */ /* Matches any character whose syntax is specified. Followed by a byte which contains a syntax code, e.g., Sword. */ syntaxspec, /* Matches any character whose syntax is not that specified. */ notsyntaxspec # endif /* emacs */ } re_opcode_t; #endif /* not INSIDE_RECURSION */ #ifdef BYTE # define CHAR_T char # define UCHAR_T unsigned char # define COMPILED_BUFFER_VAR bufp->buffer # define OFFSET_ADDRESS_SIZE 2 # define PREFIX(name) byte_##name # define ARG_PREFIX(name) name # define PUT_CHAR(c) putchar (c) #else # ifdef WCHAR # define CHAR_T wchar_t # define UCHAR_T wchar_t # define COMPILED_BUFFER_VAR wc_buffer # define OFFSET_ADDRESS_SIZE 1 /* the size which STORE_NUMBER macro use */ # define CHAR_CLASS_SIZE ((__alignof__(wctype_t)+sizeof(wctype_t))/sizeof(CHAR_T)+1) # define PREFIX(name) wcs_##name # define ARG_PREFIX(name) c##name /* Should we use wide stream?? */ # define PUT_CHAR(c) printf ("%C", c); # define TRUE 1 # define FALSE 0 # else # ifdef MBS_SUPPORT # define WCHAR # define INSIDE_RECURSION # include "regex.c" # undef INSIDE_RECURSION # endif # define BYTE # define INSIDE_RECURSION # include "regex.c" # undef INSIDE_RECURSION # endif #endif #include "unlocked-io.h" #ifdef INSIDE_RECURSION /* Common operations on the compiled pattern. */ /* Store NUMBER in two contiguous bytes starting at DESTINATION. */ /* ifdef MBS_SUPPORT, we store NUMBER in 1 element. */ # ifdef WCHAR # define STORE_NUMBER(destination, number) \ do { \ *(destination) = (UCHAR_T)(number); \ } while (0) # else /* BYTE */ # define STORE_NUMBER(destination, number) \ do { \ (destination)[0] = (number) & 0377; \ (destination)[1] = (number) >> 8; \ } while (0) # endif /* WCHAR */ /* Same as STORE_NUMBER, except increment DESTINATION to the byte after where the number is stored. Therefore, DESTINATION must be an lvalue. */ /* ifdef MBS_SUPPORT, we store NUMBER in 1 element. */ # define STORE_NUMBER_AND_INCR(destination, number) \ do { \ STORE_NUMBER (destination, number); \ (destination) += OFFSET_ADDRESS_SIZE; \ } while (0) /* Put into DESTINATION a number stored in two contiguous bytes starting at SOURCE. */ /* ifdef MBS_SUPPORT, we store NUMBER in 1 element. */ # ifdef WCHAR # define EXTRACT_NUMBER(destination, source) \ do { \ (destination) = *(source); \ } while (0) # else /* BYTE */ # define EXTRACT_NUMBER(destination, source) \ do { \ (destination) = *(source) & 0377; \ (destination) += SIGN_EXTEND_CHAR (*((source) + 1)) << 8; \ } while (0) # endif # ifdef DEBUG static void PREFIX(extract_number) _RE_ARGS ((int *dest, UCHAR_T *source)); static void PREFIX(extract_number) (dest, source) int *dest; UCHAR_T *source; { # ifdef WCHAR *dest = *source; # else /* BYTE */ int temp = SIGN_EXTEND_CHAR (*(source + 1)); *dest = *source & 0377; *dest += temp << 8; # endif } # ifndef EXTRACT_MACROS /* To debug the macros. */ # undef EXTRACT_NUMBER # define EXTRACT_NUMBER(dest, src) PREFIX(extract_number) (&dest, src) # endif /* not EXTRACT_MACROS */ # endif /* DEBUG */ /* Same as EXTRACT_NUMBER, except increment SOURCE to after the number. SOURCE must be an lvalue. */ # define EXTRACT_NUMBER_AND_INCR(destination, source) \ do { \ EXTRACT_NUMBER (destination, source); \ (source) += OFFSET_ADDRESS_SIZE; \ } while (0) # ifdef DEBUG static void PREFIX(extract_number_and_incr) _RE_ARGS ((int *destination, UCHAR_T **source)); static void PREFIX(extract_number_and_incr) (destination, source) int *destination; UCHAR_T **source; { PREFIX(extract_number) (destination, *source); *source += OFFSET_ADDRESS_SIZE; } # ifndef EXTRACT_MACROS # undef EXTRACT_NUMBER_AND_INCR # define EXTRACT_NUMBER_AND_INCR(dest, src) \ PREFIX(extract_number_and_incr) (&dest, &src) # endif /* not EXTRACT_MACROS */ # endif /* DEBUG */ /* If DEBUG is defined, Regex prints many voluminous messages about what it is doing (if the variable `debug' is nonzero). If linked with the main program in `iregex.c', you can enter patterns and strings interactively. And if linked with the main program in `main.c' and the other test files, you can run the already-written tests. */ # ifdef DEBUG # ifndef DEFINED_ONCE /* We use standard I/O for debugging. */ # include <stdio.h> /* It is useful to test things that ``must'' be true when debugging. */ # include <assert.h> static int debug; # define DEBUG_STATEMENT(e) e # define DEBUG_PRINT1(x) if (debug) printf (x) # define DEBUG_PRINT2(x1, x2) if (debug) printf (x1, x2) # define DEBUG_PRINT3(x1, x2, x3) if (debug) printf (x1, x2, x3) # define DEBUG_PRINT4(x1, x2, x3, x4) if (debug) printf (x1, x2, x3, x4) # endif /* not DEFINED_ONCE */ # define DEBUG_PRINT_COMPILED_PATTERN(p, s, e) \ if (debug) PREFIX(print_partial_compiled_pattern) (s, e) # define DEBUG_PRINT_DOUBLE_STRING(w, s1, sz1, s2, sz2) \ if (debug) PREFIX(print_double_string) (w, s1, sz1, s2, sz2) /* Print the fastmap in human-readable form. */ # ifndef DEFINED_ONCE void print_fastmap (fastmap) char *fastmap; { unsigned was_a_range = 0; unsigned i = 0; while (i < (1 << BYTEWIDTH)) { if (fastmap[i++]) { was_a_range = 0; putchar (i - 1); while (i < (1 << BYTEWIDTH) && fastmap[i]) { was_a_range = 1; i++; } if (was_a_range) { printf ("-"); putchar (i - 1); } } } putchar ('\n'); } # endif /* not DEFINED_ONCE */ /* Print a compiled pattern string in human-readable form, starting at the START pointer into it and ending just before the pointer END. */ void PREFIX(print_partial_compiled_pattern) (start, end) UCHAR_T *start; UCHAR_T *end; { int mcnt, mcnt2; UCHAR_T *p1; UCHAR_T *p = start; UCHAR_T *pend = end; if (start == NULL) { printf ("(null)\n"); return; } /* Loop over pattern commands. */ while (p < pend) { # ifdef _LIBC printf ("%td:\t", p - start); # else printf ("%ld:\t", (long int) (p - start)); # endif switch ((re_opcode_t) *p++) { case no_op: printf ("/no_op"); break; case exactn: mcnt = *p++; printf ("/exactn/%d", mcnt); do { putchar ('/'); PUT_CHAR (*p++); } while (--mcnt); break; # ifdef MBS_SUPPORT case exactn_bin: mcnt = *p++; printf ("/exactn_bin/%d", mcnt); do { printf("/%lx", (long int) *p++); } while (--mcnt); break; # endif /* MBS_SUPPORT */ case start_memory: mcnt = *p++; printf ("/start_memory/%d/%ld", mcnt, (long int) *p++); break; case stop_memory: mcnt = *p++; printf ("/stop_memory/%d/%ld", mcnt, (long int) *p++); break; case duplicate: printf ("/duplicate/%ld", (long int) *p++); break; case anychar: printf ("/anychar"); break; case charset: case charset_not: { # ifdef WCHAR int i, length; wchar_t *workp = p; printf ("/charset [%s", (re_opcode_t) *(workp - 1) == charset_not ? "^" : ""); p += 5; length = *workp++; /* the length of char_classes */ for (i=0 ; i<length ; i++) printf("[:%lx:]", (long int) *p++); length = *workp++; /* the length of collating_symbol */ for (i=0 ; i<length ;) { printf("[."); while(*p != 0) PUT_CHAR((i++,*p++)); i++,p++; printf(".]"); } length = *workp++; /* the length of equivalence_class */ for (i=0 ; i<length ;) { printf("[="); while(*p != 0) PUT_CHAR((i++,*p++)); i++,p++; printf("=]"); } length = *workp++; /* the length of char_range */ for (i=0 ; i<length ; i++) { wchar_t range_start = *p++; wchar_t range_end = *p++; printf("%C-%C", range_start, range_end); } length = *workp++; /* the length of char */ for (i=0 ; i<length ; i++) printf("%C", *p++); putchar (']'); # else register int c, last = -100; register int in_range = 0; printf ("/charset [%s", (re_opcode_t) *(p - 1) == charset_not ? "^" : ""); assert (p + *p < pend); for (c = 0; c < 256; c++) if (c / 8 < *p && (p[1 + (c/8)] & (1 << (c % 8)))) { /* Are we starting a range? */ if (last + 1 == c && ! in_range) { putchar ('-'); in_range = 1; } /* Have we broken a range? */ else if (last + 1 != c && in_range) { putchar (last); in_range = 0; } if (! in_range) putchar (c); last = c; } if (in_range) putchar (last); putchar (']'); p += 1 + *p; # endif /* WCHAR */ } break; case begline: printf ("/begline"); break; case endline: printf ("/endline"); break; case on_failure_jump: PREFIX(extract_number_and_incr) (&mcnt, &p); # ifdef _LIBC printf ("/on_failure_jump to %td", p + mcnt - start); # else printf ("/on_failure_jump to %ld", (long int) (p + mcnt - start)); # endif break; case on_failure_keep_string_jump: PREFIX(extract_number_and_incr) (&mcnt, &p); # ifdef _LIBC printf ("/on_failure_keep_string_jump to %td", p + mcnt - start); # else printf ("/on_failure_keep_string_jump to %ld", (long int) (p + mcnt - start)); # endif break; case dummy_failure_jump: PREFIX(extract_number_and_incr) (&mcnt, &p); # ifdef _LIBC printf ("/dummy_failure_jump to %td", p + mcnt - start); # else printf ("/dummy_failure_jump to %ld", (long int) (p + mcnt - start)); # endif break; case push_dummy_failure: printf ("/push_dummy_failure"); break; case maybe_pop_jump: PREFIX(extract_number_and_incr) (&mcnt, &p); # ifdef _LIBC printf ("/maybe_pop_jump to %td", p + mcnt - start); # else printf ("/maybe_pop_jump to %ld", (long int) (p + mcnt - start)); # endif break; case pop_failure_jump: PREFIX(extract_number_and_incr) (&mcnt, &p); # ifdef _LIBC printf ("/pop_failure_jump to %td", p + mcnt - start); # else printf ("/pop_failure_jump to %ld", (long int) (p + mcnt - start)); # endif break; case jump_past_alt: PREFIX(extract_number_and_incr) (&mcnt, &p); # ifdef _LIBC printf ("/jump_past_alt to %td", p + mcnt - start); # else printf ("/jump_past_alt to %ld", (long int) (p + mcnt - start)); # endif break; case jump: PREFIX(extract_number_and_incr) (&mcnt, &p); # ifdef _LIBC printf ("/jump to %td", p + mcnt - start); # else printf ("/jump to %ld", (long int) (p + mcnt - start)); # endif break; case succeed_n: PREFIX(extract_number_and_incr) (&mcnt, &p); p1 = p + mcnt; PREFIX(extract_number_and_incr) (&mcnt2, &p); # ifdef _LIBC printf ("/succeed_n to %td, %d times", p1 - start, mcnt2); # else printf ("/succeed_n to %ld, %d times", (long int) (p1 - start), mcnt2); # endif break; case jump_n: PREFIX(extract_number_and_incr) (&mcnt, &p); p1 = p + mcnt; PREFIX(extract_number_and_incr) (&mcnt2, &p); printf ("/jump_n to %d, %d times", p1 - start, mcnt2); break; case set_number_at: PREFIX(extract_number_and_incr) (&mcnt, &p); p1 = p + mcnt; PREFIX(extract_number_and_incr) (&mcnt2, &p); # ifdef _LIBC printf ("/set_number_at location %td to %d", p1 - start, mcnt2); # else printf ("/set_number_at location %ld to %d", (long int) (p1 - start), mcnt2); # endif break; case wordbound: printf ("/wordbound"); break; case notwordbound: printf ("/notwordbound"); break; case wordbeg: printf ("/wordbeg"); break; case wordend: printf ("/wordend"); break; # ifdef emacs case before_dot: printf ("/before_dot"); break; case at_dot: printf ("/at_dot"); break; case after_dot: printf ("/after_dot"); break; case syntaxspec: printf ("/syntaxspec"); mcnt = *p++; printf ("/%d", mcnt); break; case notsyntaxspec: printf ("/notsyntaxspec"); mcnt = *p++; printf ("/%d", mcnt); break; # endif /* emacs */ case wordchar: printf ("/wordchar"); break; case notwordchar: printf ("/notwordchar"); break; case begbuf: printf ("/begbuf"); break; case endbuf: printf ("/endbuf"); break; default: printf ("?%ld", (long int) *(p-1)); } putchar ('\n'); } # ifdef _LIBC printf ("%td:\tend of pattern.\n", p - start); # else printf ("%ld:\tend of pattern.\n", (long int) (p - start)); # endif } void PREFIX(print_compiled_pattern) (bufp) struct re_pattern_buffer *bufp; { UCHAR_T *buffer = (UCHAR_T*) bufp->buffer; PREFIX(print_partial_compiled_pattern) (buffer, buffer + bufp->used / sizeof(UCHAR_T)); printf ("%ld bytes used/%ld bytes allocated.\n", bufp->used, bufp->allocated); if (bufp->fastmap_accurate && bufp->fastmap) { printf ("fastmap: "); print_fastmap (bufp->fastmap); } # ifdef _LIBC printf ("re_nsub: %Zd\t", bufp->re_nsub); # else printf ("re_nsub: %ld\t", (long int) bufp->re_nsub); # endif printf ("regs_alloc: %d\t", bufp->regs_allocated); printf ("can_be_null: %d\t", bufp->can_be_null); printf ("newline_anchor: %d\n", bufp->newline_anchor); printf ("no_sub: %d\t", bufp->no_sub); printf ("not_bol: %d\t", bufp->not_bol); printf ("not_eol: %d\t", bufp->not_eol); printf ("syntax: %lx\n", bufp->syntax); /* Perhaps we should print the translate table? */ } void PREFIX(print_double_string) (where, string1, size1, string2, size2) const CHAR_T *where; const CHAR_T *string1; const CHAR_T *string2; int size1; int size2; { int this_char; if (where == NULL) printf ("(null)"); else { int cnt; if (FIRST_STRING_P (where)) { for (this_char = where - string1; this_char < size1; this_char++) PUT_CHAR (string1[this_char]); where = string2; } cnt = 0; for (this_char = where - string2; this_char < size2; this_char++) { PUT_CHAR (string2[this_char]); if (++cnt > 100) { fputs ("...", stdout); break; } } } } # ifndef DEFINED_ONCE void printchar (c) int c; { putc (c, stderr); } # endif # else /* not DEBUG */ # ifndef DEFINED_ONCE # undef assert # define assert(e) # define DEBUG_STATEMENT(e) # define DEBUG_PRINT1(x) # define DEBUG_PRINT2(x1, x2) # define DEBUG_PRINT3(x1, x2, x3) # define DEBUG_PRINT4(x1, x2, x3, x4) # endif /* not DEFINED_ONCE */ # define DEBUG_PRINT_COMPILED_PATTERN(p, s, e) # define DEBUG_PRINT_DOUBLE_STRING(w, s1, sz1, s2, sz2) # endif /* not DEBUG */ # ifdef WCHAR /* This convert a multibyte string to a wide character string. And write their correspondances to offset_buffer(see below) and write whether each wchar_t is binary data to is_binary. This assume invalid multibyte sequences as binary data. We assume offset_buffer and is_binary is already allocated enough space. */ static size_t convert_mbs_to_wcs (CHAR_T *dest, const unsigned char* src, size_t len, int *offset_buffer, char *is_binary); static size_t convert_mbs_to_wcs (dest, src, len, offset_buffer, is_binary) CHAR_T *dest; const unsigned char* src; size_t len; /* the length of multibyte string. */ /* It hold correspondances between src(char string) and dest(wchar_t string) for optimization. e.g. src = "xxxyzz" dest = {'X', 'Y', 'Z'} (each "xxx", "y" and "zz" represent one multibyte character corresponding to 'X', 'Y' and 'Z'.) offset_buffer = {0, 0+3("xxx"), 0+3+1("y"), 0+3+1+2("zz")} = {0, 3, 4, 6} */ int *offset_buffer; char *is_binary; { wchar_t *pdest = dest; const unsigned char *psrc = src; size_t wc_count = 0; mbstate_t mbs; int i, consumed; size_t mb_remain = len; size_t mb_count = 0; /* Initialize the conversion state. */ memset (&mbs, 0, sizeof (mbstate_t)); offset_buffer[0] = 0; for( ; mb_remain > 0 ; ++wc_count, ++pdest, mb_remain -= consumed, psrc += consumed) { consumed = mbrtowc (pdest, psrc, mb_remain, &mbs); if (consumed <= 0) /* failed to convert. maybe src contains binary data. So we consume 1 byte manualy. */ { *pdest = *psrc; consumed = 1; is_binary[wc_count] = TRUE; } else is_binary[wc_count] = FALSE; /* In sjis encoding, we use yen sign as escape character in place of reverse solidus. So we convert 0x5c(yen sign in sjis) to not 0xa5(yen sign in UCS2) but 0x5c(reverse solidus in UCS2). */ if (consumed == 1 && (int) *psrc == 0x5c && (int) *pdest == 0xa5) *pdest = (wchar_t) *psrc; offset_buffer[wc_count + 1] = mb_count += consumed; } /* Fill remain of the buffer with sentinel. */ for (i = wc_count + 1 ; i <= len ; i++) offset_buffer[i] = mb_count + 1; return wc_count; } # endif /* WCHAR */ #else /* not INSIDE_RECURSION */ /* Set by `re_set_syntax' to the current regexp syntax to recognize. Can also be assigned to arbitrarily: each pattern buffer stores its own syntax, so it can be changed between regex compilations. */ /* This has no initializer because initialized variables in Emacs become read-only after dumping. */ reg_syntax_t re_syntax_options; /* Specify the precise syntax of regexps for compilation. This provides for compatibility for various utilities which historically have different, incompatible syntaxes. The argument SYNTAX is a bit mask comprised of the various bits defined in regex.h. We return the old syntax. */ reg_syntax_t re_set_syntax (syntax) reg_syntax_t syntax; { reg_syntax_t ret = re_syntax_options; re_syntax_options = syntax; # ifdef DEBUG if (syntax & RE_DEBUG) debug = 1; else if (debug) /* was on but now is not */ debug = 0; # endif /* DEBUG */ return ret; } # ifdef _LIBC weak_alias (__re_set_syntax, re_set_syntax) # endif /* This table gives an error message for each of the error codes listed in regex.h. Obviously the order here has to be same as there. POSIX doesn't require that we do anything for REG_NOERROR, but why not be nice? */ static const char re_error_msgid[] = { # define REG_NOERROR_IDX 0 gettext_noop ("Success") /* REG_NOERROR */ "\0" # define REG_NOMATCH_IDX (REG_NOERROR_IDX + sizeof "Success") gettext_noop ("No match") /* REG_NOMATCH */ "\0" # define REG_BADPAT_IDX (REG_NOMATCH_IDX + sizeof "No match") gettext_noop ("Invalid regular expression") /* REG_BADPAT */ "\0" # define REG_ECOLLATE_IDX (REG_BADPAT_IDX + sizeof "Invalid regular expression") gettext_noop ("Invalid collation character") /* REG_ECOLLATE */ "\0" # define REG_ECTYPE_IDX (REG_ECOLLATE_IDX + sizeof "Invalid collation character") gettext_noop ("Invalid character class name") /* REG_ECTYPE */ "\0" # define REG_EESCAPE_IDX (REG_ECTYPE_IDX + sizeof "Invalid character class name") gettext_noop ("Trailing backslash") /* REG_EESCAPE */ "\0" # define REG_ESUBREG_IDX (REG_EESCAPE_IDX + sizeof "Trailing backslash") gettext_noop ("Invalid back reference") /* REG_ESUBREG */ "\0" # define REG_EBRACK_IDX (REG_ESUBREG_IDX + sizeof "Invalid back reference") gettext_noop ("Unmatched [ or [^") /* REG_EBRACK */ "\0" # define REG_EPAREN_IDX (REG_EBRACK_IDX + sizeof "Unmatched [ or [^") gettext_noop ("Unmatched ( or \\(") /* REG_EPAREN */ "\0" # define REG_EBRACE_IDX (REG_EPAREN_IDX + sizeof "Unmatched ( or \\(") gettext_noop ("Unmatched \\{") /* REG_EBRACE */ "\0" # define REG_BADBR_IDX (REG_EBRACE_IDX + sizeof "Unmatched \\{") gettext_noop ("Invalid content of \\{\\}") /* REG_BADBR */ "\0" # define REG_ERANGE_IDX (REG_BADBR_IDX + sizeof "Invalid content of \\{\\}") gettext_noop ("Invalid range end") /* REG_ERANGE */ "\0" # define REG_ESPACE_IDX (REG_ERANGE_IDX + sizeof "Invalid range end") gettext_noop ("Memory exhausted") /* REG_ESPACE */ "\0" # define REG_BADRPT_IDX (REG_ESPACE_IDX + sizeof "Memory exhausted") gettext_noop ("Invalid preceding regular expression") /* REG_BADRPT */ "\0" # define REG_EEND_IDX (REG_BADRPT_IDX + sizeof "Invalid preceding regular expression") gettext_noop ("Premature end of regular expression") /* REG_EEND */ "\0" # define REG_ESIZE_IDX (REG_EEND_IDX + sizeof "Premature end of regular expression") gettext_noop ("Regular expression too big") /* REG_ESIZE */ "\0" # define REG_ERPAREN_IDX (REG_ESIZE_IDX + sizeof "Regular expression too big") gettext_noop ("Unmatched ) or \\)") /* REG_ERPAREN */ }; static const size_t re_error_msgid_idx[] = { REG_NOERROR_IDX, REG_NOMATCH_IDX, REG_BADPAT_IDX, REG_ECOLLATE_IDX, REG_ECTYPE_IDX, REG_EESCAPE_IDX, REG_ESUBREG_IDX, REG_EBRACK_IDX, REG_EPAREN_IDX, REG_EBRACE_IDX, REG_BADBR_IDX, REG_ERANGE_IDX, REG_ESPACE_IDX, REG_BADRPT_IDX, REG_EEND_IDX, REG_ESIZE_IDX, REG_ERPAREN_IDX }; #endif /* INSIDE_RECURSION */ #ifndef DEFINED_ONCE /* Avoiding alloca during matching, to placate r_alloc. */ /* Define MATCH_MAY_ALLOCATE unless we need to make sure that the searching and matching functions should not call alloca. On some systems, alloca is implemented in terms of malloc, and if we're using the relocating allocator routines, then malloc could cause a relocation, which might (if the strings being searched are in the ralloc heap) shift the data out from underneath the regexp routines. Here's another reason to avoid allocation: Emacs processes input from X in a signal handler; processing X input may call malloc; if input arrives while a matching routine is calling malloc, then we're scrod. But Emacs can't just block input while calling matching routines; then we don't notice interrupts when they come in. So, Emacs blocks input around all regexp calls except the matching calls, which it leaves unprotected, in the faith that they will not malloc. */ /* Normally, this is fine. */ # define MATCH_MAY_ALLOCATE /* When using GNU C, we are not REALLY using the C alloca, no matter what config.h may say. So don't take precautions for it. */ # ifdef __GNUC__ # undef C_ALLOCA # endif /* The match routines may not allocate if (1) they would do it with malloc and (2) it's not safe for them to use malloc. Note that if REL_ALLOC is defined, matching would not use malloc for the failure stack, but we would still use it for the register vectors; so REL_ALLOC should not affect this. */ # if (defined C_ALLOCA || defined REGEX_MALLOC) && defined emacs # undef MATCH_MAY_ALLOCATE # endif #endif /* not DEFINED_ONCE */ #ifdef INSIDE_RECURSION /* Failure stack declarations and macros; both re_compile_fastmap and re_match_2 use a failure stack. These have to be macros because of REGEX_ALLOCATE_STACK. */ /* Number of failure points for which to initially allocate space when matching. If this number is exceeded, we allocate more space, so it is not a hard limit. */ # ifndef INIT_FAILURE_ALLOC # define INIT_FAILURE_ALLOC 5 # endif /* Roughly the maximum number of failure points on the stack. Would be exactly that if always used MAX_FAILURE_ITEMS items each time we failed. This is a variable only so users of regex can assign to it; we never change it ourselves. */ # ifdef INT_IS_16BIT # ifndef DEFINED_ONCE # if defined MATCH_MAY_ALLOCATE /* 4400 was enough to cause a crash on Alpha OSF/1, whose default stack limit is 2mb. */ long int re_max_failures = 4000; # else long int re_max_failures = 2000; # endif # endif union PREFIX(fail_stack_elt) { UCHAR_T *pointer; long int integer; }; typedef union PREFIX(fail_stack_elt) PREFIX(fail_stack_elt_t); typedef struct { PREFIX(fail_stack_elt_t) *stack; unsigned long int size; unsigned long int avail; /* Offset of next open position. */ } PREFIX(fail_stack_type); # else /* not INT_IS_16BIT */ # ifndef DEFINED_ONCE # if defined MATCH_MAY_ALLOCATE /* 4400 was enough to cause a crash on Alpha OSF/1, whose default stack limit is 2mb. */ int re_max_failures = 4000; # else int re_max_failures = 2000; # endif # endif union PREFIX(fail_stack_elt) { UCHAR_T *pointer; int integer; }; typedef union PREFIX(fail_stack_elt) PREFIX(fail_stack_elt_t); typedef struct { PREFIX(fail_stack_elt_t) *stack; unsigned size; unsigned avail; /* Offset of next open position. */ } PREFIX(fail_stack_type); # endif /* INT_IS_16BIT */ # ifndef DEFINED_ONCE # define FAIL_STACK_EMPTY() (fail_stack.avail == 0) # define FAIL_STACK_PTR_EMPTY() (fail_stack_ptr->avail == 0) # define FAIL_STACK_FULL() (fail_stack.avail == fail_stack.size) # endif /* Define macros to initialize and free the failure stack. Do `return -2' if the alloc fails. */ # ifdef MATCH_MAY_ALLOCATE # define INIT_FAIL_STACK() \ do { \ fail_stack.stack = (PREFIX(fail_stack_elt_t) *) \ REGEX_ALLOCATE_STACK (INIT_FAILURE_ALLOC * sizeof (PREFIX(fail_stack_elt_t))); \ \ if (fail_stack.stack == NULL) \ return -2; \ \ fail_stack.size = INIT_FAILURE_ALLOC; \ fail_stack.avail = 0; \ } while (0) # define RESET_FAIL_STACK() REGEX_FREE_STACK (fail_stack.stack) # else # define INIT_FAIL_STACK() \ do { \ fail_stack.avail = 0; \ } while (0) # define RESET_FAIL_STACK() # endif /* Double the size of FAIL_STACK, up to approximately `re_max_failures' items. Return 1 if succeeds, and 0 if either ran out of memory allocating space for it or it was already too large. REGEX_REALLOCATE_STACK requires `destination' be declared. */ # define DOUBLE_FAIL_STACK(fail_stack) \ ((fail_stack).size > (unsigned) (re_max_failures * MAX_FAILURE_ITEMS) \ ? 0 \ : ((fail_stack).stack = (PREFIX(fail_stack_elt_t) *) \ REGEX_REALLOCATE_STACK ((fail_stack).stack, \ (fail_stack).size * sizeof (PREFIX(fail_stack_elt_t)), \ ((fail_stack).size << 1) * sizeof (PREFIX(fail_stack_elt_t))),\ \ (fail_stack).stack == NULL \ ? 0 \ : ((fail_stack).size <<= 1, \ 1))) /* Push pointer POINTER on FAIL_STACK. Return 1 if was able to do so and 0 if ran out of memory allocating space to do so. */ # define PUSH_PATTERN_OP(POINTER, FAIL_STACK) \ ((FAIL_STACK_FULL () \ && !DOUBLE_FAIL_STACK (FAIL_STACK)) \ ? 0 \ : ((FAIL_STACK).stack[(FAIL_STACK).avail++].pointer = POINTER, \ 1)) /* Push a pointer value onto the failure stack. Assumes the variable `fail_stack'. Probably should only be called from within `PUSH_FAILURE_POINT'. */ # define PUSH_FAILURE_POINTER(item) \ fail_stack.stack[fail_stack.avail++].pointer = (UCHAR_T *) (item) /* This pushes an integer-valued item onto the failure stack. Assumes the variable `fail_stack'. Probably should only be called from within `PUSH_FAILURE_POINT'. */ # define PUSH_FAILURE_INT(item) \ fail_stack.stack[fail_stack.avail++].integer = (item) /* Push a fail_stack_elt_t value onto the failure stack. Assumes the variable `fail_stack'. Probably should only be called from within `PUSH_FAILURE_POINT'. */ # define PUSH_FAILURE_ELT(item) \ fail_stack.stack[fail_stack.avail++] = (item) /* These three POP... operations complement the three PUSH... operations. All assume that `fail_stack' is nonempty. */ # define POP_FAILURE_POINTER() fail_stack.stack[--fail_stack.avail].pointer # define POP_FAILURE_INT() fail_stack.stack[--fail_stack.avail].integer # define POP_FAILURE_ELT() fail_stack.stack[--fail_stack.avail] /* Used to omit pushing failure point id's when we're not debugging. */ # ifdef DEBUG # define DEBUG_PUSH PUSH_FAILURE_INT # define DEBUG_POP(item_addr) *(item_addr) = POP_FAILURE_INT () # else # define DEBUG_PUSH(item) # define DEBUG_POP(item_addr) # endif /* Push the information about the state we will need if we ever fail back to it. Requires variables fail_stack, regstart, regend, reg_info, and num_regs_pushed be declared. DOUBLE_FAIL_STACK requires `destination' be declared. Does `return FAILURE_CODE' if runs out of memory. */ # define PUSH_FAILURE_POINT(pattern_place, string_place, failure_code) \ do { \ char *destination; \ /* Must be int, so when we don't save any registers, the arithmetic \ of 0 + -1 isn't done as unsigned. */ \ /* Can't be int, since there is not a shred of a guarantee that int \ is wide enough to hold a value of something to which pointer can \ be assigned */ \ active_reg_t this_reg; \ \ DEBUG_STATEMENT (failure_id++); \ DEBUG_STATEMENT (nfailure_points_pushed++); \ DEBUG_PRINT2 ("\nPUSH_FAILURE_POINT #%u:\n", failure_id); \ DEBUG_PRINT2 (" Before push, next avail: %d\n", (fail_stack).avail);\ DEBUG_PRINT2 (" size: %d\n", (fail_stack).size);\ \ DEBUG_PRINT2 (" slots needed: %ld\n", NUM_FAILURE_ITEMS); \ DEBUG_PRINT2 (" available: %d\n", REMAINING_AVAIL_SLOTS); \ \ /* Ensure we have enough space allocated for what we will push. */ \ while (REMAINING_AVAIL_SLOTS < NUM_FAILURE_ITEMS) \ { \ if (!DOUBLE_FAIL_STACK (fail_stack)) \ return failure_code; \ \ DEBUG_PRINT2 ("\n Doubled stack; size now: %d\n", \ (fail_stack).size); \ DEBUG_PRINT2 (" slots available: %d\n", REMAINING_AVAIL_SLOTS);\ } \ \ /* Push the info, starting with the registers. */ \ DEBUG_PRINT1 ("\n"); \ \ if (1) \ for (this_reg = lowest_active_reg; this_reg <= highest_active_reg; \ this_reg++) \ { \ DEBUG_PRINT2 (" Pushing reg: %lu\n", this_reg); \ DEBUG_STATEMENT (num_regs_pushed++); \ \ DEBUG_PRINT2 (" start: %p\n", regstart[this_reg]); \ PUSH_FAILURE_POINTER (regstart[this_reg]); \ \ DEBUG_PRINT2 (" end: %p\n", regend[this_reg]); \ PUSH_FAILURE_POINTER (regend[this_reg]); \ \ DEBUG_PRINT2 (" info: %p\n ", \ reg_info[this_reg].word.pointer); \ DEBUG_PRINT2 (" match_null=%d", \ REG_MATCH_NULL_STRING_P (reg_info[this_reg])); \ DEBUG_PRINT2 (" active=%d", IS_ACTIVE (reg_info[this_reg])); \ DEBUG_PRINT2 (" matched_something=%d", \ MATCHED_SOMETHING (reg_info[this_reg])); \ DEBUG_PRINT2 (" ever_matched=%d", \ EVER_MATCHED_SOMETHING (reg_info[this_reg])); \ DEBUG_PRINT1 ("\n"); \ PUSH_FAILURE_ELT (reg_info[this_reg].word); \ } \ \ DEBUG_PRINT2 (" Pushing low active reg: %ld\n", lowest_active_reg);\ PUSH_FAILURE_INT (lowest_active_reg); \ \ DEBUG_PRINT2 (" Pushing high active reg: %ld\n", highest_active_reg);\ PUSH_FAILURE_INT (highest_active_reg); \ \ DEBUG_PRINT2 (" Pushing pattern %p:\n", pattern_place); \ DEBUG_PRINT_COMPILED_PATTERN (bufp, pattern_place, pend); \ PUSH_FAILURE_POINTER (pattern_place); \ \ DEBUG_PRINT2 (" Pushing string %p: `", string_place); \ DEBUG_PRINT_DOUBLE_STRING (string_place, string1, size1, string2, \ size2); \ DEBUG_PRINT1 ("'\n"); \ PUSH_FAILURE_POINTER (string_place); \ \ DEBUG_PRINT2 (" Pushing failure id: %u\n", failure_id); \ DEBUG_PUSH (failure_id); \ } while (0) # ifndef DEFINED_ONCE /* This is the number of items that are pushed and popped on the stack for each register. */ # define NUM_REG_ITEMS 3 /* Individual items aside from the registers. */ # ifdef DEBUG # define NUM_NONREG_ITEMS 5 /* Includes failure point id. */ # else # define NUM_NONREG_ITEMS 4 # endif /* We push at most this many items on the stack. */ /* We used to use (num_regs - 1), which is the number of registers this regexp will save; but that was changed to 5 to avoid stack overflow for a regexp with lots of parens. */ # define MAX_FAILURE_ITEMS (5 * NUM_REG_ITEMS + NUM_NONREG_ITEMS) /* We actually push this many items. */ # define NUM_FAILURE_ITEMS \ (((0 \ ? 0 : highest_active_reg - lowest_active_reg + 1) \ * NUM_REG_ITEMS) \ + NUM_NONREG_ITEMS) /* How many items can still be added to the stack without overflowing it. */ # define REMAINING_AVAIL_SLOTS ((fail_stack).size - (fail_stack).avail) # endif /* not DEFINED_ONCE */ /* Pops what PUSH_FAIL_STACK pushes. We restore into the parameters, all of which should be lvalues: STR -- the saved data position. PAT -- the saved pattern position. LOW_REG, HIGH_REG -- the highest and lowest active registers. REGSTART, REGEND -- arrays of string positions. REG_INFO -- array of information about each subexpression. Also assumes the variables `fail_stack' and (if debugging), `bufp', `pend', `string1', `size1', `string2', and `size2'. */ # define POP_FAILURE_POINT(str, pat, low_reg, high_reg, regstart, regend, reg_info)\ { \ DEBUG_STATEMENT (unsigned failure_id;) \ active_reg_t this_reg; \ const UCHAR_T *string_temp; \ \ assert (!FAIL_STACK_EMPTY ()); \ \ /* Remove failure points and point to how many regs pushed. */ \ DEBUG_PRINT1 ("POP_FAILURE_POINT:\n"); \ DEBUG_PRINT2 (" Before pop, next avail: %d\n", fail_stack.avail); \ DEBUG_PRINT2 (" size: %d\n", fail_stack.size); \ \ assert (fail_stack.avail >= NUM_NONREG_ITEMS); \ \ DEBUG_POP (&failure_id); \ DEBUG_PRINT2 (" Popping failure id: %u\n", failure_id); \ \ /* If the saved string location is NULL, it came from an \ on_failure_keep_string_jump opcode, and we want to throw away the \ saved NULL, thus retaining our current position in the string. */ \ string_temp = POP_FAILURE_POINTER (); \ if (string_temp != NULL) \ str = (const CHAR_T *) string_temp; \ \ DEBUG_PRINT2 (" Popping string %p: `", str); \ DEBUG_PRINT_DOUBLE_STRING (str, string1, size1, string2, size2); \ DEBUG_PRINT1 ("'\n"); \ \ pat = (UCHAR_T *) POP_FAILURE_POINTER (); \ DEBUG_PRINT2 (" Popping pattern %p:\n", pat); \ DEBUG_PRINT_COMPILED_PATTERN (bufp, pat, pend); \ \ /* Restore register info. */ \ high_reg = (active_reg_t) POP_FAILURE_INT (); \ DEBUG_PRINT2 (" Popping high active reg: %ld\n", high_reg); \ \ low_reg = (active_reg_t) POP_FAILURE_INT (); \ DEBUG_PRINT2 (" Popping low active reg: %ld\n", low_reg); \ \ if (1) \ for (this_reg = high_reg; this_reg >= low_reg; this_reg--) \ { \ DEBUG_PRINT2 (" Popping reg: %ld\n", this_reg); \ \ reg_info[this_reg].word = POP_FAILURE_ELT (); \ DEBUG_PRINT2 (" info: %p\n", \ reg_info[this_reg].word.pointer); \ \ regend[this_reg] = (const CHAR_T *) POP_FAILURE_POINTER (); \ DEBUG_PRINT2 (" end: %p\n", regend[this_reg]); \ \ regstart[this_reg] = (const CHAR_T *) POP_FAILURE_POINTER (); \ DEBUG_PRINT2 (" start: %p\n", regstart[this_reg]); \ } \ else \ { \ for (this_reg = highest_active_reg; this_reg > high_reg; this_reg--) \ { \ reg_info[this_reg].word.integer = 0; \ regend[this_reg] = 0; \ regstart[this_reg] = 0; \ } \ highest_active_reg = high_reg; \ } \ \ set_regs_matched_done = 0; \ DEBUG_STATEMENT (nfailure_points_popped++); \ } /* POP_FAILURE_POINT */ /* Structure for per-register (a.k.a. per-group) information. Other register information, such as the starting and ending positions (which are addresses), and the list of inner groups (which is a bits list) are maintained in separate variables. We are making a (strictly speaking) nonportable assumption here: that the compiler will pack our bit fields into something that fits into the type of `word', i.e., is something that fits into one item on the failure stack. */ /* Declarations and macros for re_match_2. */ typedef union { PREFIX(fail_stack_elt_t) word; struct { /* This field is one if this group can match the empty string, zero if not. If not yet determined, `MATCH_NULL_UNSET_VALUE'. */ # define MATCH_NULL_UNSET_VALUE 3 unsigned match_null_string_p : 2; unsigned is_active : 1; unsigned matched_something : 1; unsigned ever_matched_something : 1; } bits; } PREFIX(register_info_type); # ifndef DEFINED_ONCE # define REG_MATCH_NULL_STRING_P(R) ((R).bits.match_null_string_p) # define IS_ACTIVE(R) ((R).bits.is_active) # define MATCHED_SOMETHING(R) ((R).bits.matched_something) # define EVER_MATCHED_SOMETHING(R) ((R).bits.ever_matched_something) /* Call this when have matched a real character; it sets `matched' flags for the subexpressions which we are currently inside. Also records that those subexprs have matched. */ # define SET_REGS_MATCHED() \ do \ { \ if (!set_regs_matched_done) \ { \ active_reg_t r; \ set_regs_matched_done = 1; \ for (r = lowest_active_reg; r <= highest_active_reg; r++) \ { \ MATCHED_SOMETHING (reg_info[r]) \ = EVER_MATCHED_SOMETHING (reg_info[r]) \ = 1; \ } \ } \ } \ while (0) # endif /* not DEFINED_ONCE */ /* Registers are set to a sentinel when they haven't yet matched. */ static CHAR_T PREFIX(reg_unset_dummy); # define REG_UNSET_VALUE (&PREFIX(reg_unset_dummy)) # define REG_UNSET(e) ((e) == REG_UNSET_VALUE) /* Subroutine declarations and macros for regex_compile. */ static void PREFIX(store_op1) _RE_ARGS ((re_opcode_t op, UCHAR_T *loc, int arg)); static void PREFIX(store_op2) _RE_ARGS ((re_opcode_t op, UCHAR_T *loc, int arg1, int arg2)); static void PREFIX(insert_op1) _RE_ARGS ((re_opcode_t op, UCHAR_T *loc, int arg, UCHAR_T *end)); static void PREFIX(insert_op2) _RE_ARGS ((re_opcode_t op, UCHAR_T *loc, int arg1, int arg2, UCHAR_T *end)); static boolean PREFIX(at_begline_loc_p) _RE_ARGS ((const CHAR_T *pattern, const CHAR_T *p, reg_syntax_t syntax)); static boolean PREFIX(at_endline_loc_p) _RE_ARGS ((const CHAR_T *p, const CHAR_T *pend, reg_syntax_t syntax)); # ifdef WCHAR static reg_errcode_t wcs_compile_range _RE_ARGS ((CHAR_T range_start, const CHAR_T **p_ptr, const CHAR_T *pend, char *translate, reg_syntax_t syntax, UCHAR_T *b, CHAR_T *char_set)); static void insert_space _RE_ARGS ((int num, CHAR_T *loc, CHAR_T *end)); # else /* BYTE */ static reg_errcode_t byte_compile_range _RE_ARGS ((unsigned int range_start, const char **p_ptr, const char *pend, char *translate, reg_syntax_t syntax, unsigned char *b)); # endif /* WCHAR */ /* Fetch the next character in the uncompiled pattern---translating it if necessary. Also cast from a signed character in the constant string passed to us by the user to an unsigned char that we can use as an array index (in, e.g., `translate'). */ /* ifdef MBS_SUPPORT, we translate only if character <= 0xff, because it is impossible to allocate 4GB array for some encodings which have 4 byte character_set like UCS4. */ # ifndef PATFETCH # ifdef WCHAR # define PATFETCH(c) \ do {if (p == pend) return REG_EEND; \ c = (UCHAR_T) *p++; \ if (translate && (c <= 0xff)) c = (UCHAR_T) translate[c]; \ } while (0) # else /* BYTE */ # define PATFETCH(c) \ do {if (p == pend) return REG_EEND; \ c = (unsigned char) *p++; \ if (translate) c = (unsigned char) translate[c]; \ } while (0) # endif /* WCHAR */ # endif /* Fetch the next character in the uncompiled pattern, with no translation. */ # define PATFETCH_RAW(c) \ do {if (p == pend) return REG_EEND; \ c = (UCHAR_T) *p++; \ } while (0) /* Go backwards one character in the pattern. */ # define PATUNFETCH p-- /* If `translate' is non-null, return translate[D], else just D. We cast the subscript to translate because some data is declared as `char *', to avoid warnings when a string constant is passed. But when we use a character as a subscript we must make it unsigned. */ /* ifdef MBS_SUPPORT, we translate only if character <= 0xff, because it is impossible to allocate 4GB array for some encodings which have 4 byte character_set like UCS4. */ # ifndef TRANSLATE # ifdef WCHAR # define TRANSLATE(d) \ ((translate && ((UCHAR_T) (d)) <= 0xff) \ ? (char) translate[(unsigned char) (d)] : (d)) # else /* BYTE */ # define TRANSLATE(d) \ (translate ? (char) translate[(unsigned char) (d)] : (d)) # endif /* WCHAR */ # endif /* Macros for outputting the compiled pattern into `buffer'. */ /* If the buffer isn't allocated when it comes in, use this. */ # define INIT_BUF_SIZE (32 * sizeof(UCHAR_T)) /* Make sure we have at least N more bytes of space in buffer. */ # ifdef WCHAR # define GET_BUFFER_SPACE(n) \ while (((unsigned long)b - (unsigned long)COMPILED_BUFFER_VAR \ + (n)*sizeof(CHAR_T)) > bufp->allocated) \ EXTEND_BUFFER () # else /* BYTE */ # define GET_BUFFER_SPACE(n) \ while ((unsigned long) (b - bufp->buffer + (n)) > bufp->allocated) \ EXTEND_BUFFER () # endif /* WCHAR */ /* Make sure we have one more byte of buffer space and then add C to it. */ # define BUF_PUSH(c) \ do { \ GET_BUFFER_SPACE (1); \ *b++ = (UCHAR_T) (c); \ } while (0) /* Ensure we have two more bytes of buffer space and then append C1 and C2. */ # define BUF_PUSH_2(c1, c2) \ do { \ GET_BUFFER_SPACE (2); \ *b++ = (UCHAR_T) (c1); \ *b++ = (UCHAR_T) (c2); \ } while (0) /* As with BUF_PUSH_2, except for three bytes. */ # define BUF_PUSH_3(c1, c2, c3) \ do { \ GET_BUFFER_SPACE (3); \ *b++ = (UCHAR_T) (c1); \ *b++ = (UCHAR_T) (c2); \ *b++ = (UCHAR_T) (c3); \ } while (0) /* Store a jump with opcode OP at LOC to location TO. We store a relative address offset by the three bytes the jump itself occupies. */ # define STORE_JUMP(op, loc, to) \ PREFIX(store_op1) (op, loc, (int) ((to) - (loc) - (1 + OFFSET_ADDRESS_SIZE))) /* Likewise, for a two-argument jump. */ # define STORE_JUMP2(op, loc, to, arg) \ PREFIX(store_op2) (op, loc, (int) ((to) - (loc) - (1 + OFFSET_ADDRESS_SIZE)), arg) /* Like `STORE_JUMP', but for inserting. Assume `b' is the buffer end. */ # define INSERT_JUMP(op, loc, to) \ PREFIX(insert_op1) (op, loc, (int) ((to) - (loc) - (1 + OFFSET_ADDRESS_SIZE)), b) /* Like `STORE_JUMP2', but for inserting. Assume `b' is the buffer end. */ # define INSERT_JUMP2(op, loc, to, arg) \ PREFIX(insert_op2) (op, loc, (int) ((to) - (loc) - (1 + OFFSET_ADDRESS_SIZE)),\ arg, b) /* This is not an arbitrary limit: the arguments which represent offsets into the pattern are two bytes long. So if 2^16 bytes turns out to be too small, many things would have to change. */ /* Any other compiler which, like MSC, has allocation limit below 2^16 bytes will have to use approach similar to what was done below for MSC and drop MAX_BUF_SIZE a bit. Otherwise you may end up reallocating to 0 bytes. Such thing is not going to work too well. You have been warned!! */ # ifndef DEFINED_ONCE # if defined _MSC_VER && !defined WIN32 /* Microsoft C 16-bit versions limit malloc to approx 65512 bytes. The REALLOC define eliminates a flurry of conversion warnings, but is not required. */ # define MAX_BUF_SIZE 65500L # define REALLOC(p,s) realloc ((p), (size_t) (s)) # else # define MAX_BUF_SIZE (1L << 16) # define REALLOC(p,s) realloc ((p), (s)) # endif /* Extend the buffer by twice its current size via realloc and reset the pointers that pointed into the old block to point to the correct places in the new one. If extending the buffer results in it being larger than MAX_BUF_SIZE, then flag memory exhausted. */ # if __BOUNDED_POINTERS__ # define SET_HIGH_BOUND(P) (__ptrhigh (P) = __ptrlow (P) + bufp->allocated) # define MOVE_BUFFER_POINTER(P) \ (__ptrlow (P) += incr, SET_HIGH_BOUND (P), __ptrvalue (P) += incr) # define ELSE_EXTEND_BUFFER_HIGH_BOUND \ else \ { \ SET_HIGH_BOUND (b); \ SET_HIGH_BOUND (begalt); \ if (fixup_alt_jump) \ SET_HIGH_BOUND (fixup_alt_jump); \ if (laststart) \ SET_HIGH_BOUND (laststart); \ if (pending_exact) \ SET_HIGH_BOUND (pending_exact); \ } # else # define MOVE_BUFFER_POINTER(P) (P) += incr # define ELSE_EXTEND_BUFFER_HIGH_BOUND # endif # endif /* not DEFINED_ONCE */ # ifdef WCHAR # define EXTEND_BUFFER() \ do { \ UCHAR_T *old_buffer = COMPILED_BUFFER_VAR; \ int wchar_count; \ if (bufp->allocated + sizeof(UCHAR_T) > MAX_BUF_SIZE) \ return REG_ESIZE; \ bufp->allocated <<= 1; \ if (bufp->allocated > MAX_BUF_SIZE) \ bufp->allocated = MAX_BUF_SIZE; \ /* How many characters the new buffer can have? */ \ wchar_count = bufp->allocated / sizeof(UCHAR_T); \ if (wchar_count == 0) wchar_count = 1; \ /* Truncate the buffer to CHAR_T align. */ \ bufp->allocated = wchar_count * sizeof(UCHAR_T); \ RETALLOC (COMPILED_BUFFER_VAR, wchar_count, UCHAR_T); \ bufp->buffer = (char*)COMPILED_BUFFER_VAR; \ if (COMPILED_BUFFER_VAR == NULL) \ return REG_ESPACE; \ /* If the buffer moved, move all the pointers into it. */ \ if (old_buffer != COMPILED_BUFFER_VAR) \ { \ int incr = COMPILED_BUFFER_VAR - old_buffer; \ MOVE_BUFFER_POINTER (b); \ MOVE_BUFFER_POINTER (begalt); \ if (fixup_alt_jump) \ MOVE_BUFFER_POINTER (fixup_alt_jump); \ if (laststart) \ MOVE_BUFFER_POINTER (laststart); \ if (pending_exact) \ MOVE_BUFFER_POINTER (pending_exact); \ } \ ELSE_EXTEND_BUFFER_HIGH_BOUND \ } while (0) # else /* BYTE */ # define EXTEND_BUFFER() \ do { \ UCHAR_T *old_buffer = COMPILED_BUFFER_VAR; \ if (bufp->allocated == MAX_BUF_SIZE) \ return REG_ESIZE; \ bufp->allocated <<= 1; \ if (bufp->allocated > MAX_BUF_SIZE) \ bufp->allocated = MAX_BUF_SIZE; \ bufp->buffer = (UCHAR_T *) REALLOC (COMPILED_BUFFER_VAR, \ bufp->allocated); \ if (COMPILED_BUFFER_VAR == NULL) \ return REG_ESPACE; \ /* If the buffer moved, move all the pointers into it. */ \ if (old_buffer != COMPILED_BUFFER_VAR) \ { \ int incr = COMPILED_BUFFER_VAR - old_buffer; \ MOVE_BUFFER_POINTER (b); \ MOVE_BUFFER_POINTER (begalt); \ if (fixup_alt_jump) \ MOVE_BUFFER_POINTER (fixup_alt_jump); \ if (laststart) \ MOVE_BUFFER_POINTER (laststart); \ if (pending_exact) \ MOVE_BUFFER_POINTER (pending_exact); \ } \ ELSE_EXTEND_BUFFER_HIGH_BOUND \ } while (0) # endif /* WCHAR */ # ifndef DEFINED_ONCE /* Since we have one byte reserved for the register number argument to {start,stop}_memory, the maximum number of groups we can report things about is what fits in that byte. */ # define MAX_REGNUM 255 /* But patterns can have more than `MAX_REGNUM' registers. We just ignore the excess. */ typedef unsigned regnum_t; /* Macros for the compile stack. */ /* Since offsets can go either forwards or backwards, this type needs to be able to hold values from -(MAX_BUF_SIZE - 1) to MAX_BUF_SIZE - 1. */ /* int may be not enough when sizeof(int) == 2. */ typedef long pattern_offset_t; typedef struct { pattern_offset_t begalt_offset; pattern_offset_t fixup_alt_jump; pattern_offset_t inner_group_offset; pattern_offset_t laststart_offset; regnum_t regnum; } compile_stack_elt_t; typedef struct { compile_stack_elt_t *stack; unsigned size; unsigned avail; /* Offset of next open position. */ } compile_stack_type; # define INIT_COMPILE_STACK_SIZE 32 # define COMPILE_STACK_EMPTY (compile_stack.avail == 0) # define COMPILE_STACK_FULL (compile_stack.avail == compile_stack.size) /* The next available element. */ # define COMPILE_STACK_TOP (compile_stack.stack[compile_stack.avail]) # endif /* not DEFINED_ONCE */ /* Set the bit for character C in a list. */ # ifndef DEFINED_ONCE # define SET_LIST_BIT(c) \ (b[((unsigned char) (c)) / BYTEWIDTH] \ |= 1 << (((unsigned char) c) % BYTEWIDTH)) # endif /* DEFINED_ONCE */ /* Get the next unsigned number in the uncompiled pattern. */ # define GET_UNSIGNED_NUMBER(num) \ { \ while (p != pend) \ { \ PATFETCH (c); \ if (c < '0' || c > '9') \ break; \ if (num <= RE_DUP_MAX) \ { \ if (num < 0) \ num = 0; \ num = num * 10 + c - '0'; \ } \ } \ } # ifndef DEFINED_ONCE # if defined _LIBC || WIDE_CHAR_SUPPORT /* The GNU C library provides support for user-defined character classes and the functions from ISO C amendement 1. */ # ifdef CHARCLASS_NAME_MAX # define CHAR_CLASS_MAX_LENGTH CHARCLASS_NAME_MAX # else /* This shouldn't happen but some implementation might still have this problem. Use a reasonable default value. */ # define CHAR_CLASS_MAX_LENGTH 256 # endif # ifdef _LIBC # define IS_CHAR_CLASS(string) __wctype (string) # else # define IS_CHAR_CLASS(string) wctype (string) # endif # else # define CHAR_CLASS_MAX_LENGTH 6 /* Namely, `xdigit'. */ # define IS_CHAR_CLASS(string) \ (STREQ (string, "alpha") || STREQ (string, "upper") \ || STREQ (string, "lower") || STREQ (string, "digit") \ || STREQ (string, "alnum") || STREQ (string, "xdigit") \ || STREQ (string, "space") || STREQ (string, "print") \ || STREQ (string, "punct") || STREQ (string, "graph") \ || STREQ (string, "cntrl") || STREQ (string, "blank")) # endif # endif /* DEFINED_ONCE */ # ifndef MATCH_MAY_ALLOCATE /* If we cannot allocate large objects within re_match_2_internal, we make the fail stack and register vectors global. The fail stack, we grow to the maximum size when a regexp is compiled. The register vectors, we adjust in size each time we compile a regexp, according to the number of registers it needs. */ static PREFIX(fail_stack_type) fail_stack; /* Size with which the following vectors are currently allocated. That is so we can make them bigger as needed, but never make them smaller. */ # ifdef DEFINED_ONCE static int regs_allocated_size; static const char ** regstart, ** regend; static const char ** old_regstart, ** old_regend; static const char **best_regstart, **best_regend; static const char **reg_dummy; # endif /* DEFINED_ONCE */ static PREFIX(register_info_type) *PREFIX(reg_info); static PREFIX(register_info_type) *PREFIX(reg_info_dummy); /* Make the register vectors big enough for NUM_REGS registers, but don't make them smaller. */ static void PREFIX(regex_grow_registers) (num_regs) int num_regs; { if (num_regs > regs_allocated_size) { RETALLOC_IF (regstart, num_regs, const char *); RETALLOC_IF (regend, num_regs, const char *); RETALLOC_IF (old_regstart, num_regs, const char *); RETALLOC_IF (old_regend, num_regs, const char *); RETALLOC_IF (best_regstart, num_regs, const char *); RETALLOC_IF (best_regend, num_regs, const char *); RETALLOC_IF (PREFIX(reg_info), num_regs, PREFIX(register_info_type)); RETALLOC_IF (reg_dummy, num_regs, const char *); RETALLOC_IF (PREFIX(reg_info_dummy), num_regs, PREFIX(register_info_type)); regs_allocated_size = num_regs; } } # endif /* not MATCH_MAY_ALLOCATE */ # ifndef DEFINED_ONCE static boolean group_in_compile_stack _RE_ARGS ((compile_stack_type compile_stack, regnum_t regnum)); # endif /* not DEFINED_ONCE */ /* `regex_compile' compiles PATTERN (of length SIZE) according to SYNTAX. Returns one of error codes defined in `regex.h', or zero for success. Assumes the `allocated' (and perhaps `buffer') and `translate' fields are set in BUFP on entry. If it succeeds, results are put in BUFP (if it returns an error, the contents of BUFP are undefined): `buffer' is the compiled pattern; `syntax' is set to SYNTAX; `used' is set to the length of the compiled pattern; `fastmap_accurate' is zero; `re_nsub' is the number of subexpressions in PATTERN; `not_bol' and `not_eol' are zero; The `fastmap' and `newline_anchor' fields are neither examined nor set. */ /* Return, freeing storage we allocated. */ # ifdef WCHAR # define FREE_STACK_RETURN(value) \ return (free(pattern), free(mbs_offset), free(is_binary), free (compile_stack.stack), value) # else # define FREE_STACK_RETURN(value) \ return (free (compile_stack.stack), value) # endif /* WCHAR */ static reg_errcode_t PREFIX(regex_compile) (ARG_PREFIX(pattern), ARG_PREFIX(size), syntax, bufp) const char *ARG_PREFIX(pattern); size_t ARG_PREFIX(size); reg_syntax_t syntax; struct re_pattern_buffer *bufp; { /* We fetch characters from PATTERN here. Even though PATTERN is `char *' (i.e., signed), we declare these variables as unsigned, so they can be reliably used as array indices. */ register UCHAR_T c, c1; #ifdef WCHAR /* A temporary space to keep wchar_t pattern and compiled pattern. */ CHAR_T *pattern, *COMPILED_BUFFER_VAR; size_t size; /* offset buffer for optimization. See convert_mbs_to_wc. */ int *mbs_offset = NULL; /* It hold whether each wchar_t is binary data or not. */ char *is_binary = NULL; /* A flag whether exactn is handling binary data or not. */ char is_exactn_bin = FALSE; #endif /* WCHAR */ /* A random temporary spot in PATTERN. */ const CHAR_T *p1; /* Points to the end of the buffer, where we should append. */ register UCHAR_T *b; /* Keeps track of unclosed groups. */ compile_stack_type compile_stack; /* Points to the current (ending) position in the pattern. */ #ifdef WCHAR const CHAR_T *p; const CHAR_T *pend; #else /* BYTE */ const CHAR_T *p = pattern; const CHAR_T *pend = pattern + size; #endif /* WCHAR */ /* How to translate the characters in the pattern. */ RE_TRANSLATE_TYPE translate = bufp->translate; /* Address of the count-byte of the most recently inserted `exactn' command. This makes it possible to tell if a new exact-match character can be added to that command or if the character requires a new `exactn' command. */ UCHAR_T *pending_exact = 0; /* Address of start of the most recently finished expression. This tells, e.g., postfix * where to find the start of its operand. Reset at the beginning of groups and alternatives. */ UCHAR_T *laststart = 0; /* Address of beginning of regexp, or inside of last group. */ UCHAR_T *begalt; /* Address of the place where a forward jump should go to the end of the containing expression. Each alternative of an `or' -- except the last -- ends with a forward jump of this sort. */ UCHAR_T *fixup_alt_jump = 0; /* Counts open-groups as they are encountered. Remembered for the matching close-group on the compile stack, so the same register number is put in the stop_memory as the start_memory. */ regnum_t regnum = 0; #ifdef WCHAR /* Initialize the wchar_t PATTERN and offset_buffer. */ p = pend = pattern = TALLOC(csize + 1, CHAR_T); mbs_offset = TALLOC(csize + 1, int); is_binary = TALLOC(csize + 1, char); if (pattern == NULL || mbs_offset == NULL || is_binary == NULL) { free(pattern); free(mbs_offset); free(is_binary); return REG_ESPACE; } pattern[csize] = L'\0'; /* sentinel */ size = convert_mbs_to_wcs(pattern, cpattern, csize, mbs_offset, is_binary); pend = p + size; if (size < 0) { free(pattern); free(mbs_offset); free(is_binary); return REG_BADPAT; } #endif #ifdef DEBUG DEBUG_PRINT1 ("\nCompiling pattern: "); if (debug) { unsigned debug_count; for (debug_count = 0; debug_count < size; debug_count++) PUT_CHAR (pattern[debug_count]); putchar ('\n'); } #endif /* DEBUG */ /* Initialize the compile stack. */ compile_stack.stack = TALLOC (INIT_COMPILE_STACK_SIZE, compile_stack_elt_t); if (compile_stack.stack == NULL) { #ifdef WCHAR free(pattern); free(mbs_offset); free(is_binary); #endif return REG_ESPACE; } compile_stack.size = INIT_COMPILE_STACK_SIZE; compile_stack.avail = 0; /* Initialize the pattern buffer. */ bufp->syntax = syntax; bufp->fastmap_accurate = 0; bufp->not_bol = bufp->not_eol = 0; /* Set `used' to zero, so that if we return an error, the pattern printer (for debugging) will think there's no pattern. We reset it at the end. */ bufp->used = 0; /* Always count groups, whether or not bufp->no_sub is set. */ bufp->re_nsub = 0; #if !defined emacs && !defined SYNTAX_TABLE /* Initialize the syntax table. */ init_syntax_once (); #endif if (bufp->allocated == 0) { if (bufp->buffer) { /* If zero allocated, but buffer is non-null, try to realloc enough space. This loses if buffer's address is bogus, but that is the user's responsibility. */ #ifdef WCHAR /* Free bufp->buffer and allocate an array for wchar_t pattern buffer. */ free(bufp->buffer); COMPILED_BUFFER_VAR = TALLOC (INIT_BUF_SIZE/sizeof(UCHAR_T), UCHAR_T); #else RETALLOC (COMPILED_BUFFER_VAR, INIT_BUF_SIZE, UCHAR_T); #endif /* WCHAR */ } else { /* Caller did not allocate a buffer. Do it for them. */ COMPILED_BUFFER_VAR = TALLOC (INIT_BUF_SIZE / sizeof(UCHAR_T), UCHAR_T); } if (!COMPILED_BUFFER_VAR) FREE_STACK_RETURN (REG_ESPACE); #ifdef WCHAR bufp->buffer = (char*)COMPILED_BUFFER_VAR; #endif /* WCHAR */ bufp->allocated = INIT_BUF_SIZE; } #ifdef WCHAR else COMPILED_BUFFER_VAR = (UCHAR_T*) bufp->buffer; #endif begalt = b = COMPILED_BUFFER_VAR; /* Loop through the uncompiled pattern until we're at the end. */ while (p != pend) { PATFETCH (c); switch (c) { case '^': { if ( /* If at start of pattern, it's an operator. */ p == pattern + 1 /* If context independent, it's an operator. */ || syntax & RE_CONTEXT_INDEP_ANCHORS /* Otherwise, depends on what's come before. */ || PREFIX(at_begline_loc_p) (pattern, p, syntax)) BUF_PUSH (begline); else goto normal_char; } break; case '$': { if ( /* If at end of pattern, it's an operator. */ p == pend /* If context independent, it's an operator. */ || syntax & RE_CONTEXT_INDEP_ANCHORS /* Otherwise, depends on what's next. */ || PREFIX(at_endline_loc_p) (p, pend, syntax)) BUF_PUSH (endline); else goto normal_char; } break; case '+': case '?': if ((syntax & RE_BK_PLUS_QM) || (syntax & RE_LIMITED_OPS)) goto normal_char; handle_plus: case '*': /* If there is no previous pattern... */ if (!laststart) { if (syntax & RE_CONTEXT_INVALID_OPS) FREE_STACK_RETURN (REG_BADRPT); else if (!(syntax & RE_CONTEXT_INDEP_OPS)) goto normal_char; } { /* Are we optimizing this jump? */ boolean keep_string_p = false; /* 1 means zero (many) matches is allowed. */ char zero_times_ok = 0, many_times_ok = 0; /* If there is a sequence of repetition chars, collapse it down to just one (the right one). We can't combine interval operators with these because of, e.g., `a{2}*', which should only match an even number of `a's. */ for (;;) { zero_times_ok |= c != '+'; many_times_ok |= c != '?'; if (p == pend) break; PATFETCH (c); if (c == '*' || (!(syntax & RE_BK_PLUS_QM) && (c == '+' || c == '?'))) ; else if (syntax & RE_BK_PLUS_QM && c == '\\') { if (p == pend) FREE_STACK_RETURN (REG_EESCAPE); PATFETCH (c1); if (!(c1 == '+' || c1 == '?')) { PATUNFETCH; PATUNFETCH; break; } c = c1; } else { PATUNFETCH; break; } /* If we get here, we found another repeat character. */ } /* Star, etc. applied to an empty pattern is equivalent to an empty pattern. */ if (!laststart) break; /* Now we know whether or not zero matches is allowed and also whether or not two or more matches is allowed. */ if (many_times_ok) { /* More than one repetition is allowed, so put in at the end a backward relative jump from `b' to before the next jump we're going to put in below (which jumps from laststart to after this jump). But if we are at the `*' in the exact sequence `.*\n', insert an unconditional jump backwards to the ., instead of the beginning of the loop. This way we only push a failure point once, instead of every time through the loop. */ assert (p - 1 > pattern); /* Allocate the space for the jump. */ GET_BUFFER_SPACE (1 + OFFSET_ADDRESS_SIZE); /* We know we are not at the first character of the pattern, because laststart was nonzero. And we've already incremented `p', by the way, to be the character after the `*'. Do we have to do something analogous here for null bytes, because of RE_DOT_NOT_NULL? */ if (TRANSLATE (*(p - 2)) == TRANSLATE ('.') && zero_times_ok && p < pend && TRANSLATE (*p) == TRANSLATE ('\n') && !(syntax & RE_DOT_NEWLINE)) { /* We have .*\n. */ STORE_JUMP (jump, b, laststart); keep_string_p = true; } else /* Anything else. */ STORE_JUMP (maybe_pop_jump, b, laststart - (1 + OFFSET_ADDRESS_SIZE)); /* We've added more stuff to the buffer. */ b += 1 + OFFSET_ADDRESS_SIZE; } /* On failure, jump from laststart to b + 3, which will be the end of the buffer after this jump is inserted. */ /* ifdef WCHAR, 'b + 1 + OFFSET_ADDRESS_SIZE' instead of 'b + 3'. */ GET_BUFFER_SPACE (1 + OFFSET_ADDRESS_SIZE); INSERT_JUMP (keep_string_p ? on_failure_keep_string_jump : on_failure_jump, laststart, b + 1 + OFFSET_ADDRESS_SIZE); pending_exact = 0; b += 1 + OFFSET_ADDRESS_SIZE; if (!zero_times_ok) { /* At least one repetition is required, so insert a `dummy_failure_jump' before the initial `on_failure_jump' instruction of the loop. This effects a skip over that instruction the first time we hit that loop. */ GET_BUFFER_SPACE (1 + OFFSET_ADDRESS_SIZE); INSERT_JUMP (dummy_failure_jump, laststart, laststart + 2 + 2 * OFFSET_ADDRESS_SIZE); b += 1 + OFFSET_ADDRESS_SIZE; } } break; case '.': laststart = b; BUF_PUSH (anychar); break; case '[': { boolean had_char_class = false; #ifdef WCHAR CHAR_T range_start = 0xffffffff; #else unsigned int range_start = 0xffffffff; #endif if (p == pend) FREE_STACK_RETURN (REG_EBRACK); #ifdef WCHAR /* We assume a charset(_not) structure as a wchar_t array. charset[0] = (re_opcode_t) charset(_not) charset[1] = l (= length of char_classes) charset[2] = m (= length of collating_symbols) charset[3] = n (= length of equivalence_classes) charset[4] = o (= length of char_ranges) charset[5] = p (= length of chars) charset[6] = char_class (wctype_t) charset[6+CHAR_CLASS_SIZE] = char_class (wctype_t) ... charset[l+5] = char_class (wctype_t) charset[l+6] = collating_symbol (wchar_t) ... charset[l+m+5] = collating_symbol (wchar_t) ifdef _LIBC we use the index if _NL_COLLATE_SYMB_EXTRAMB instead of wchar_t string. charset[l+m+6] = equivalence_classes (wchar_t) ... charset[l+m+n+5] = equivalence_classes (wchar_t) ifdef _LIBC we use the index in _NL_COLLATE_WEIGHT instead of wchar_t string. charset[l+m+n+6] = range_start charset[l+m+n+7] = range_end ... charset[l+m+n+2o+4] = range_start charset[l+m+n+2o+5] = range_end ifdef _LIBC we use the value looked up in _NL_COLLATE_COLLSEQ instead of wchar_t character. charset[l+m+n+2o+6] = char ... charset[l+m+n+2o+p+5] = char */ /* We need at least 6 spaces: the opcode, the length of char_classes, the length of collating_symbols, the length of equivalence_classes, the length of char_ranges, the length of chars. */ GET_BUFFER_SPACE (6); /* Save b as laststart. And We use laststart as the pointer to the first element of the charset here. In other words, laststart[i] indicates charset[i]. */ laststart = b; /* We test `*p == '^' twice, instead of using an if statement, so we only need one BUF_PUSH. */ BUF_PUSH (*p == '^' ? charset_not : charset); if (*p == '^') p++; /* Push the length of char_classes, the length of collating_symbols, the length of equivalence_classes, the length of char_ranges and the length of chars. */ BUF_PUSH_3 (0, 0, 0); BUF_PUSH_2 (0, 0); /* Remember the first position in the bracket expression. */ p1 = p; /* charset_not matches newline according to a syntax bit. */ if ((re_opcode_t) b[-6] == charset_not && (syntax & RE_HAT_LISTS_NOT_NEWLINE)) { BUF_PUSH('\n'); laststart[5]++; /* Update the length of characters */ } /* Read in characters and ranges, setting map bits. */ for (;;) { if (p == pend) FREE_STACK_RETURN (REG_EBRACK); PATFETCH (c); /* \ might escape characters inside [...] and [^...]. */ if ((syntax & RE_BACKSLASH_ESCAPE_IN_LISTS) && c == '\\') { if (p == pend) FREE_STACK_RETURN (REG_EESCAPE); PATFETCH (c1); BUF_PUSH(c1); laststart[5]++; /* Update the length of chars */ range_start = c1; continue; } /* Could be the end of the bracket expression. If it's not (i.e., when the bracket expression is `[]' so far), the ']' character bit gets set way below. */ if (c == ']' && p != p1 + 1) break; /* Look ahead to see if it's a range when the last thing was a character class. */ if (had_char_class && c == '-' && *p != ']') FREE_STACK_RETURN (REG_ERANGE); /* Look ahead to see if it's a range when the last thing was a character: if this is a hyphen not at the beginning or the end of a list, then it's the range operator. */ if (c == '-' && !(p - 2 >= pattern && p[-2] == '[') && !(p - 3 >= pattern && p[-3] == '[' && p[-2] == '^') && *p != ']') { reg_errcode_t ret; /* Allocate the space for range_start and range_end. */ GET_BUFFER_SPACE (2); /* Update the pointer to indicate end of buffer. */ b += 2; ret = wcs_compile_range (range_start, &p, pend, translate, syntax, b, laststart); if (ret != REG_NOERROR) FREE_STACK_RETURN (ret); range_start = 0xffffffff; } else if (p[0] == '-' && p[1] != ']') { /* This handles ranges made up of characters only. */ reg_errcode_t ret; /* Move past the `-'. */ PATFETCH (c1); /* Allocate the space for range_start and range_end. */ GET_BUFFER_SPACE (2); /* Update the pointer to indicate end of buffer. */ b += 2; ret = wcs_compile_range (c, &p, pend, translate, syntax, b, laststart); if (ret != REG_NOERROR) FREE_STACK_RETURN (ret); range_start = 0xffffffff; } /* See if we're at the beginning of a possible character class. */ else if (syntax & RE_CHAR_CLASSES && c == '[' && *p == ':') { /* Leave room for the null. */ char str[CHAR_CLASS_MAX_LENGTH + 1]; PATFETCH (c); c1 = 0; /* If pattern is `[[:'. */ if (p == pend) FREE_STACK_RETURN (REG_EBRACK); for (;;) { PATFETCH (c); if ((c == ':' && *p == ']') || p == pend) break; if (c1 < CHAR_CLASS_MAX_LENGTH) str[c1++] = c; else /* This is in any case an invalid class name. */ str[0] = '\0'; } str[c1] = '\0'; /* If isn't a word bracketed by `[:' and `:]': undo the ending character, the letters, and leave the leading `:' and `[' (but store them as character). */ if (c == ':' && *p == ']') { wctype_t wt; uintptr_t alignedp; /* Query the character class as wctype_t. */ wt = IS_CHAR_CLASS (str); if (wt == 0) FREE_STACK_RETURN (REG_ECTYPE); /* Throw away the ] at the end of the character class. */ PATFETCH (c); if (p == pend) FREE_STACK_RETURN (REG_EBRACK); /* Allocate the space for character class. */ GET_BUFFER_SPACE(CHAR_CLASS_SIZE); /* Update the pointer to indicate end of buffer. */ b += CHAR_CLASS_SIZE; /* Move data which follow character classes not to violate the data. */ insert_space(CHAR_CLASS_SIZE, laststart + 6 + laststart[1], b - 1); alignedp = ((uintptr_t)(laststart + 6 + laststart[1]) + __alignof__(wctype_t) - 1) & ~(uintptr_t)(__alignof__(wctype_t) - 1); /* Store the character class. */ *((wctype_t*)alignedp) = wt; /* Update length of char_classes */ laststart[1] += CHAR_CLASS_SIZE; had_char_class = true; } else { c1++; while (c1--) PATUNFETCH; BUF_PUSH ('['); BUF_PUSH (':'); laststart[5] += 2; /* Update the length of characters */ range_start = ':'; had_char_class = false; } } else if (syntax & RE_CHAR_CLASSES && c == '[' && (*p == '=' || *p == '.')) { CHAR_T str[128]; /* Should be large enough. */ CHAR_T delim = *p; /* '=' or '.' */ # ifdef _LIBC uint32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); # endif PATFETCH (c); c1 = 0; /* If pattern is `[[=' or '[[.'. */ if (p == pend) FREE_STACK_RETURN (REG_EBRACK); for (;;) { PATFETCH (c); if ((c == delim && *p == ']') || p == pend) break; if (c1 < sizeof (str) - 1) str[c1++] = c; else /* This is in any case an invalid class name. */ str[0] = '\0'; } str[c1] = '\0'; if (c == delim && *p == ']' && str[0] != '\0') { unsigned int i, offset; /* If we have no collation data we use the default collation in which each character is in a class by itself. It also means that ASCII is the character set and therefore we cannot have character with more than one byte in the multibyte representation. */ /* If not defined _LIBC, we push the name and `\0' for the sake of matching performance. */ int datasize = c1 + 1; # ifdef _LIBC int32_t idx = 0; if (nrules == 0) # endif { if (c1 != 1) FREE_STACK_RETURN (REG_ECOLLATE); } # ifdef _LIBC else { const int32_t *table; const int32_t *weights; const int32_t *extra; const int32_t *indirect; wint_t *cp; /* This #include defines a local function! */ # include <locale/weightwc.h> if(delim == '=') { /* We push the index for equivalence class. */ cp = (wint_t*)str; table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEWC); weights = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_WEIGHTWC); extra = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_EXTRAWC); indirect = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_INDIRECTWC); idx = findidx ((const wint_t**)&cp); if (idx == 0 || cp < (wint_t*) str + c1) /* This is no valid character. */ FREE_STACK_RETURN (REG_ECOLLATE); str[0] = (wchar_t)idx; } else /* delim == '.' */ { /* We push collation sequence value for collating symbol. */ int32_t table_size; const int32_t *symb_table; const unsigned char *extra; int32_t idx; int32_t elem; int32_t second; int32_t hash; char char_str[c1]; /* We have to convert the name to a single-byte string. This is possible since the names consist of ASCII characters and the internal representation is UCS4. */ for (i = 0; i < c1; ++i) char_str[i] = str[i]; table_size = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_SYMB_HASH_SIZEMB); symb_table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_TABLEMB); extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB); /* Locate the character in the hashing table. */ hash = elem_hash (char_str, c1); idx = 0; elem = hash % table_size; second = hash % (table_size - 2); while (symb_table[2 * elem] != 0) { /* First compare the hashing value. */ if (symb_table[2 * elem] == hash && c1 == extra[symb_table[2 * elem + 1]] && memcmp (char_str, &extra[symb_table[2 * elem + 1] + 1], c1) == 0) { /* Yep, this is the entry. */ idx = symb_table[2 * elem + 1]; idx += 1 + extra[idx]; break; } /* Next entry. */ elem += second; } if (symb_table[2 * elem] != 0) { /* Compute the index of the byte sequence in the table. */ idx += 1 + extra[idx]; /* Adjust for the alignment. */ idx = (idx + 3) & ~3; str[0] = (wchar_t) idx + 4; } else if (symb_table[2 * elem] == 0 && c1 == 1) { /* No valid character. Match it as a single byte character. */ had_char_class = false; BUF_PUSH(str[0]); /* Update the length of characters */ laststart[5]++; range_start = str[0]; /* Throw away the ] at the end of the collating symbol. */ PATFETCH (c); /* exit from the switch block. */ continue; } else FREE_STACK_RETURN (REG_ECOLLATE); } datasize = 1; } # endif /* Throw away the ] at the end of the equivalence class (or collating symbol). */ PATFETCH (c); /* Allocate the space for the equivalence class (or collating symbol) (and '\0' if needed). */ GET_BUFFER_SPACE(datasize); /* Update the pointer to indicate end of buffer. */ b += datasize; if (delim == '=') { /* equivalence class */ /* Calculate the offset of char_ranges, which is next to equivalence_classes. */ offset = laststart[1] + laststart[2] + laststart[3] +6; /* Insert space. */ insert_space(datasize, laststart + offset, b - 1); /* Write the equivalence_class and \0. */ for (i = 0 ; i < datasize ; i++) laststart[offset + i] = str[i]; /* Update the length of equivalence_classes. */ laststart[3] += datasize; had_char_class = true; } else /* delim == '.' */ { /* collating symbol */ /* Calculate the offset of the equivalence_classes, which is next to collating_symbols. */ offset = laststart[1] + laststart[2] + 6; /* Insert space and write the collationg_symbol and \0. */ insert_space(datasize, laststart + offset, b-1); for (i = 0 ; i < datasize ; i++) laststart[offset + i] = str[i]; /* In re_match_2_internal if range_start < -1, we assume -range_start is the offset of the collating symbol which is specified as the character of the range start. So we assign -(laststart[1] + laststart[2] + 6) to range_start. */ range_start = -(laststart[1] + laststart[2] + 6); /* Update the length of collating_symbol. */ laststart[2] += datasize; had_char_class = false; } } else { c1++; while (c1--) PATUNFETCH; BUF_PUSH ('['); BUF_PUSH (delim); laststart[5] += 2; /* Update the length of characters */ range_start = delim; had_char_class = false; } } else { had_char_class = false; BUF_PUSH(c); laststart[5]++; /* Update the length of characters */ range_start = c; } } #else /* BYTE */ /* Ensure that we have enough space to push a charset: the opcode, the length count, and the bitset; 34 bytes in all. */ GET_BUFFER_SPACE (34); laststart = b; /* We test `*p == '^' twice, instead of using an if statement, so we only need one BUF_PUSH. */ BUF_PUSH (*p == '^' ? charset_not : charset); if (*p == '^') p++; /* Remember the first position in the bracket expression. */ p1 = p; /* Push the number of bytes in the bitmap. */ BUF_PUSH ((1 << BYTEWIDTH) / BYTEWIDTH); /* Clear the whole map. */ bzero (b, (1 << BYTEWIDTH) / BYTEWIDTH); /* charset_not matches newline according to a syntax bit. */ if ((re_opcode_t) b[-2] == charset_not && (syntax & RE_HAT_LISTS_NOT_NEWLINE)) SET_LIST_BIT ('\n'); /* Read in characters and ranges, setting map bits. */ for (;;) { if (p == pend) FREE_STACK_RETURN (REG_EBRACK); PATFETCH (c); /* \ might escape characters inside [...] and [^...]. */ if ((syntax & RE_BACKSLASH_ESCAPE_IN_LISTS) && c == '\\') { if (p == pend) FREE_STACK_RETURN (REG_EESCAPE); PATFETCH (c1); SET_LIST_BIT (c1); range_start = c1; continue; } /* Could be the end of the bracket expression. If it's not (i.e., when the bracket expression is `[]' so far), the ']' character bit gets set way below. */ if (c == ']' && p != p1 + 1) break; /* Look ahead to see if it's a range when the last thing was a character class. */ if (had_char_class && c == '-' && *p != ']') FREE_STACK_RETURN (REG_ERANGE); /* Look ahead to see if it's a range when the last thing was a character: if this is a hyphen not at the beginning or the end of a list, then it's the range operator. */ if (c == '-' && !(p - 2 >= pattern && p[-2] == '[') && !(p - 3 >= pattern && p[-3] == '[' && p[-2] == '^') && *p != ']') { reg_errcode_t ret = byte_compile_range (range_start, &p, pend, translate, syntax, b); if (ret != REG_NOERROR) FREE_STACK_RETURN (ret); range_start = 0xffffffff; } else if (p[0] == '-' && p[1] != ']') { /* This handles ranges made up of characters only. */ reg_errcode_t ret; /* Move past the `-'. */ PATFETCH (c1); ret = byte_compile_range (c, &p, pend, translate, syntax, b); if (ret != REG_NOERROR) FREE_STACK_RETURN (ret); range_start = 0xffffffff; } /* See if we're at the beginning of a possible character class. */ else if (syntax & RE_CHAR_CLASSES && c == '[' && *p == ':') { /* Leave room for the null. */ char str[CHAR_CLASS_MAX_LENGTH + 1]; PATFETCH (c); c1 = 0; /* If pattern is `[[:'. */ if (p == pend) FREE_STACK_RETURN (REG_EBRACK); for (;;) { PATFETCH (c); if ((c == ':' && *p == ']') || p == pend) break; if (c1 < CHAR_CLASS_MAX_LENGTH) str[c1++] = c; else /* This is in any case an invalid class name. */ str[0] = '\0'; } str[c1] = '\0'; /* If isn't a word bracketed by `[:' and `:]': undo the ending character, the letters, and leave the leading `:' and `[' (but set bits for them). */ if (c == ':' && *p == ']') { # if defined _LIBC || WIDE_CHAR_SUPPORT boolean is_lower = STREQ (str, "lower"); boolean is_upper = STREQ (str, "upper"); wctype_t wt; int ch; wt = IS_CHAR_CLASS (str); if (wt == 0) FREE_STACK_RETURN (REG_ECTYPE); /* Throw away the ] at the end of the character class. */ PATFETCH (c); if (p == pend) FREE_STACK_RETURN (REG_EBRACK); for (ch = 0; ch < 1 << BYTEWIDTH; ++ch) { if (iswctype (btowc (ch), wt)) SET_LIST_BIT (ch); if (translate && (is_upper || is_lower) && (ISUPPER (ch) || ISLOWER (ch))) SET_LIST_BIT (ch); } had_char_class = true; # else int ch; boolean is_alnum = STREQ (str, "alnum"); boolean is_alpha = STREQ (str, "alpha"); boolean is_blank = STREQ (str, "blank"); boolean is_cntrl = STREQ (str, "cntrl"); boolean is_digit = STREQ (str, "digit"); boolean is_graph = STREQ (str, "graph"); boolean is_lower = STREQ (str, "lower"); boolean is_print = STREQ (str, "print"); boolean is_punct = STREQ (str, "punct"); boolean is_space = STREQ (str, "space"); boolean is_upper = STREQ (str, "upper"); boolean is_xdigit = STREQ (str, "xdigit"); if (!IS_CHAR_CLASS (str)) FREE_STACK_RETURN (REG_ECTYPE); /* Throw away the ] at the end of the character class. */ PATFETCH (c); if (p == pend) FREE_STACK_RETURN (REG_EBRACK); for (ch = 0; ch < 1 << BYTEWIDTH; ch++) { /* This was split into 3 if's to avoid an arbitrary limit in some compiler. */ if ( (is_alnum && ISALNUM (ch)) || (is_alpha && ISALPHA (ch)) || (is_blank && ISBLANK (ch)) || (is_cntrl && ISCNTRL (ch))) SET_LIST_BIT (ch); if ( (is_digit && ISDIGIT (ch)) || (is_graph && ISGRAPH (ch)) || (is_lower && ISLOWER (ch)) || (is_print && ISPRINT (ch))) SET_LIST_BIT (ch); if ( (is_punct && ISPUNCT (ch)) || (is_space && ISSPACE (ch)) || (is_upper && ISUPPER (ch)) || (is_xdigit && ISXDIGIT (ch))) SET_LIST_BIT (ch); if ( translate && (is_upper || is_lower) && (ISUPPER (ch) || ISLOWER (ch))) SET_LIST_BIT (ch); } had_char_class = true; # endif /* libc || wctype.h */ } else { c1++; while (c1--) PATUNFETCH; SET_LIST_BIT ('['); SET_LIST_BIT (':'); range_start = ':'; had_char_class = false; } } else if (syntax & RE_CHAR_CLASSES && c == '[' && *p == '=') { unsigned char str[MB_LEN_MAX + 1]; # ifdef _LIBC uint32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); # endif PATFETCH (c); c1 = 0; /* If pattern is `[[='. */ if (p == pend) FREE_STACK_RETURN (REG_EBRACK); for (;;) { PATFETCH (c); if ((c == '=' && *p == ']') || p == pend) break; if (c1 < MB_LEN_MAX) str[c1++] = c; else /* This is in any case an invalid class name. */ str[0] = '\0'; } str[c1] = '\0'; if (c == '=' && *p == ']' && str[0] != '\0') { /* If we have no collation data we use the default collation in which each character is in a class by itself. It also means that ASCII is the character set and therefore we cannot have character with more than one byte in the multibyte representation. */ # ifdef _LIBC if (nrules == 0) # endif { if (c1 != 1) FREE_STACK_RETURN (REG_ECOLLATE); /* Throw away the ] at the end of the equivalence class. */ PATFETCH (c); /* Set the bit for the character. */ SET_LIST_BIT (str[0]); } # ifdef _LIBC else { /* Try to match the byte sequence in `str' against those known to the collate implementation. First find out whether the bytes in `str' are actually from exactly one character. */ const int32_t *table; const unsigned char *weights; const unsigned char *extra; const int32_t *indirect; int32_t idx; const unsigned char *cp = str; int ch; /* This #include defines a local function! */ # include <locale/weight.h> table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB); weights = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_WEIGHTMB); extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_EXTRAMB); indirect = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_INDIRECTMB); idx = findidx (&cp); if (idx == 0 || cp < str + c1) /* This is no valid character. */ FREE_STACK_RETURN (REG_ECOLLATE); /* Throw away the ] at the end of the equivalence class. */ PATFETCH (c); /* Now we have to go throught the whole table and find all characters which have the same first level weight. XXX Note that this is not entirely correct. we would have to match multibyte sequences but this is not possible with the current implementation. */ for (ch = 1; ch < 256; ++ch) /* XXX This test would have to be changed if we would allow matching multibyte sequences. */ if (table[ch] > 0) { int32_t idx2 = table[ch]; size_t len = weights[idx2]; /* Test whether the lenghts match. */ if (weights[idx] == len) { /* They do. New compare the bytes of the weight. */ size_t cnt = 0; while (cnt < len && (weights[idx + 1 + cnt] == weights[idx2 + 1 + cnt])) ++cnt; if (cnt == len) /* They match. Mark the character as acceptable. */ SET_LIST_BIT (ch); } } } # endif had_char_class = true; } else { c1++; while (c1--) PATUNFETCH; SET_LIST_BIT ('['); SET_LIST_BIT ('='); range_start = '='; had_char_class = false; } } else if (syntax & RE_CHAR_CLASSES && c == '[' && *p == '.') { unsigned char str[128]; /* Should be large enough. */ # ifdef _LIBC uint32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); # endif PATFETCH (c); c1 = 0; /* If pattern is `[[.'. */ if (p == pend) FREE_STACK_RETURN (REG_EBRACK); for (;;) { PATFETCH (c); if ((c == '.' && *p == ']') || p == pend) break; if (c1 < sizeof (str)) str[c1++] = c; else /* This is in any case an invalid class name. */ str[0] = '\0'; } str[c1] = '\0'; if (c == '.' && *p == ']' && str[0] != '\0') { /* If we have no collation data we use the default collation in which each character is the name for its own class which contains only the one character. It also means that ASCII is the character set and therefore we cannot have character with more than one byte in the multibyte representation. */ # ifdef _LIBC if (nrules == 0) # endif { if (c1 != 1) FREE_STACK_RETURN (REG_ECOLLATE); /* Throw away the ] at the end of the equivalence class. */ PATFETCH (c); /* Set the bit for the character. */ SET_LIST_BIT (str[0]); range_start = ((const unsigned char *) str)[0]; } # ifdef _LIBC else { /* Try to match the byte sequence in `str' against those known to the collate implementation. First find out whether the bytes in `str' are actually from exactly one character. */ int32_t table_size; const int32_t *symb_table; const unsigned char *extra; int32_t idx; int32_t elem; int32_t second; int32_t hash; table_size = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_SYMB_HASH_SIZEMB); symb_table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_TABLEMB); extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB); /* Locate the character in the hashing table. */ hash = elem_hash (str, c1); idx = 0; elem = hash % table_size; second = hash % (table_size - 2); while (symb_table[2 * elem] != 0) { /* First compare the hashing value. */ if (symb_table[2 * elem] == hash && c1 == extra[symb_table[2 * elem + 1]] && memcmp (str, &extra[symb_table[2 * elem + 1] + 1], c1) == 0) { /* Yep, this is the entry. */ idx = symb_table[2 * elem + 1]; idx += 1 + extra[idx]; break; } /* Next entry. */ elem += second; } if (symb_table[2 * elem] == 0) /* This is no valid character. */ FREE_STACK_RETURN (REG_ECOLLATE); /* Throw away the ] at the end of the equivalence class. */ PATFETCH (c); /* Now add the multibyte character(s) we found to the accept list. XXX Note that this is not entirely correct. we would have to match multibyte sequences but this is not possible with the current implementation. Also, we have to match collating symbols, which expand to more than one file, as a whole and not allow the individual bytes. */ c1 = extra[idx++]; if (c1 == 1) range_start = extra[idx]; while (c1-- > 0) { SET_LIST_BIT (extra[idx]); ++idx; } } # endif had_char_class = false; } else { c1++; while (c1--) PATUNFETCH; SET_LIST_BIT ('['); SET_LIST_BIT ('.'); range_start = '.'; had_char_class = false; } } else { had_char_class = false; SET_LIST_BIT (c); range_start = c; } } /* Discard any (non)matching list bytes that are all 0 at the end of the map. Decrease the map-length byte too. */ while ((int) b[-1] > 0 && b[b[-1] - 1] == 0) b[-1]--; b += b[-1]; #endif /* WCHAR */ } break; case '(': if (syntax & RE_NO_BK_PARENS) goto handle_open; else goto normal_char; case ')': if (syntax & RE_NO_BK_PARENS) goto handle_close; else goto normal_char; case '\n': if (syntax & RE_NEWLINE_ALT) goto handle_alt; else goto normal_char; case '|': if (syntax & RE_NO_BK_VBAR) goto handle_alt; else goto normal_char; case '{': if (syntax & RE_INTERVALS && syntax & RE_NO_BK_BRACES) goto handle_interval; else goto normal_char; case '\\': if (p == pend) FREE_STACK_RETURN (REG_EESCAPE); /* Do not translate the character after the \, so that we can distinguish, e.g., \B from \b, even if we normally would translate, e.g., B to b. */ PATFETCH_RAW (c); switch (c) { case '(': if (syntax & RE_NO_BK_PARENS) goto normal_backslash; handle_open: bufp->re_nsub++; regnum++; if (COMPILE_STACK_FULL) { RETALLOC (compile_stack.stack, compile_stack.size << 1, compile_stack_elt_t); if (compile_stack.stack == NULL) return REG_ESPACE; compile_stack.size <<= 1; } /* These are the values to restore when we hit end of this group. They are all relative offsets, so that if the whole pattern moves because of realloc, they will still be valid. */ COMPILE_STACK_TOP.begalt_offset = begalt - COMPILED_BUFFER_VAR; COMPILE_STACK_TOP.fixup_alt_jump = fixup_alt_jump ? fixup_alt_jump - COMPILED_BUFFER_VAR + 1 : 0; COMPILE_STACK_TOP.laststart_offset = b - COMPILED_BUFFER_VAR; COMPILE_STACK_TOP.regnum = regnum; /* We will eventually replace the 0 with the number of groups inner to this one. But do not push a start_memory for groups beyond the last one we can represent in the compiled pattern. */ if (regnum <= MAX_REGNUM) { COMPILE_STACK_TOP.inner_group_offset = b - COMPILED_BUFFER_VAR + 2; BUF_PUSH_3 (start_memory, regnum, 0); } compile_stack.avail++; fixup_alt_jump = 0; laststart = 0; begalt = b; /* If we've reached MAX_REGNUM groups, then this open won't actually generate any code, so we'll have to clear pending_exact explicitly. */ pending_exact = 0; break; case ')': if (syntax & RE_NO_BK_PARENS) goto normal_backslash; if (COMPILE_STACK_EMPTY) { if (syntax & RE_UNMATCHED_RIGHT_PAREN_ORD) goto normal_backslash; else FREE_STACK_RETURN (REG_ERPAREN); } handle_close: if (fixup_alt_jump) { /* Push a dummy failure point at the end of the alternative for a possible future `pop_failure_jump' to pop. See comments at `push_dummy_failure' in `re_match_2'. */ BUF_PUSH (push_dummy_failure); /* We allocated space for this jump when we assigned to `fixup_alt_jump', in the `handle_alt' case below. */ STORE_JUMP (jump_past_alt, fixup_alt_jump, b - 1); } /* See similar code for backslashed left paren above. */ if (COMPILE_STACK_EMPTY) { if (syntax & RE_UNMATCHED_RIGHT_PAREN_ORD) goto normal_char; else FREE_STACK_RETURN (REG_ERPAREN); } /* Since we just checked for an empty stack above, this ``can't happen''. */ assert (compile_stack.avail != 0); { /* We don't just want to restore into `regnum', because later groups should continue to be numbered higher, as in `(ab)c(de)' -- the second group is #2. */ regnum_t this_group_regnum; compile_stack.avail--; begalt = COMPILED_BUFFER_VAR + COMPILE_STACK_TOP.begalt_offset; fixup_alt_jump = COMPILE_STACK_TOP.fixup_alt_jump ? COMPILED_BUFFER_VAR + COMPILE_STACK_TOP.fixup_alt_jump - 1 : 0; laststart = COMPILED_BUFFER_VAR + COMPILE_STACK_TOP.laststart_offset; this_group_regnum = COMPILE_STACK_TOP.regnum; /* If we've reached MAX_REGNUM groups, then this open won't actually generate any code, so we'll have to clear pending_exact explicitly. */ pending_exact = 0; /* We're at the end of the group, so now we know how many groups were inside this one. */ if (this_group_regnum <= MAX_REGNUM) { UCHAR_T *inner_group_loc = COMPILED_BUFFER_VAR + COMPILE_STACK_TOP.inner_group_offset; *inner_group_loc = regnum - this_group_regnum; BUF_PUSH_3 (stop_memory, this_group_regnum, regnum - this_group_regnum); } } break; case '|': /* `\|'. */ if (syntax & RE_LIMITED_OPS || syntax & RE_NO_BK_VBAR) goto normal_backslash; handle_alt: if (syntax & RE_LIMITED_OPS) goto normal_char; /* Insert before the previous alternative a jump which jumps to this alternative if the former fails. */ GET_BUFFER_SPACE (1 + OFFSET_ADDRESS_SIZE); INSERT_JUMP (on_failure_jump, begalt, b + 2 + 2 * OFFSET_ADDRESS_SIZE); pending_exact = 0; b += 1 + OFFSET_ADDRESS_SIZE; /* The alternative before this one has a jump after it which gets executed if it gets matched. Adjust that jump so it will jump to this alternative's analogous jump (put in below, which in turn will jump to the next (if any) alternative's such jump, etc.). The last such jump jumps to the correct final destination. A picture: _____ _____ | | | | | v | v a | b | c If we are at `b', then fixup_alt_jump right now points to a three-byte space after `a'. We'll put in the jump, set fixup_alt_jump to right after `b', and leave behind three bytes which we'll fill in when we get to after `c'. */ if (fixup_alt_jump) STORE_JUMP (jump_past_alt, fixup_alt_jump, b); /* Mark and leave space for a jump after this alternative, to be filled in later either by next alternative or when know we're at the end of a series of alternatives. */ fixup_alt_jump = b; GET_BUFFER_SPACE (1 + OFFSET_ADDRESS_SIZE); b += 1 + OFFSET_ADDRESS_SIZE; laststart = 0; begalt = b; break; case '{': /* If \{ is a literal. */ if (!(syntax & RE_INTERVALS) /* If we're at `\{' and it's not the open-interval operator. */ || (syntax & RE_NO_BK_BRACES)) goto normal_backslash; handle_interval: { /* If got here, then the syntax allows intervals. */ /* At least (most) this many matches must be made. */ int lower_bound = -1, upper_bound = -1; /* Place in the uncompiled pattern (i.e., just after the '{') to go back to if the interval is invalid. */ const CHAR_T *beg_interval = p; if (p == pend) goto invalid_interval; GET_UNSIGNED_NUMBER (lower_bound); if (c == ',') { GET_UNSIGNED_NUMBER (upper_bound); if (upper_bound < 0) upper_bound = RE_DUP_MAX; } else /* Interval such as `{1}' => match exactly once. */ upper_bound = lower_bound; if (! (0 <= lower_bound && lower_bound <= upper_bound)) goto invalid_interval; if (!(syntax & RE_NO_BK_BRACES)) { if (c != '\\' || p == pend) goto invalid_interval; PATFETCH (c); } if (c != '}') goto invalid_interval; /* If it's invalid to have no preceding re. */ if (!laststart) { if (syntax & RE_CONTEXT_INVALID_OPS && !(syntax & RE_INVALID_INTERVAL_ORD)) FREE_STACK_RETURN (REG_BADRPT); else if (syntax & RE_CONTEXT_INDEP_OPS) laststart = b; else goto unfetch_interval; } /* We just parsed a valid interval. */ if (RE_DUP_MAX < upper_bound) FREE_STACK_RETURN (REG_BADBR); /* If the upper bound is zero, don't want to succeed at all; jump from `laststart' to `b + 3', which will be the end of the buffer after we insert the jump. */ /* ifdef WCHAR, 'b + 1 + OFFSET_ADDRESS_SIZE' instead of 'b + 3'. */ if (upper_bound == 0) { GET_BUFFER_SPACE (1 + OFFSET_ADDRESS_SIZE); INSERT_JUMP (jump, laststart, b + 1 + OFFSET_ADDRESS_SIZE); b += 1 + OFFSET_ADDRESS_SIZE; } /* Otherwise, we have a nontrivial interval. When we're all done, the pattern will look like: set_number_at <jump count> <upper bound> set_number_at <succeed_n count> <lower bound> succeed_n <after jump addr> <succeed_n count> <body of loop> jump_n <succeed_n addr> <jump count> (The upper bound and `jump_n' are omitted if `upper_bound' is 1, though.) */ else { /* If the upper bound is > 1, we need to insert more at the end of the loop. */ unsigned nbytes = 2 + 4 * OFFSET_ADDRESS_SIZE + (upper_bound > 1) * (2 + 4 * OFFSET_ADDRESS_SIZE); GET_BUFFER_SPACE (nbytes); /* Initialize lower bound of the `succeed_n', even though it will be set during matching by its attendant `set_number_at' (inserted next), because `re_compile_fastmap' needs to know. Jump to the `jump_n' we might insert below. */ INSERT_JUMP2 (succeed_n, laststart, b + 1 + 2 * OFFSET_ADDRESS_SIZE + (upper_bound > 1) * (1 + 2 * OFFSET_ADDRESS_SIZE) , lower_bound); b += 1 + 2 * OFFSET_ADDRESS_SIZE; /* Code to initialize the lower bound. Insert before the `succeed_n'. The `5' is the last two bytes of this `set_number_at', plus 3 bytes of the following `succeed_n'. */ /* ifdef WCHAR, The '1+2*OFFSET_ADDRESS_SIZE' is the 'set_number_at', plus '1+OFFSET_ADDRESS_SIZE' of the following `succeed_n'. */ PREFIX(insert_op2) (set_number_at, laststart, 1 + 2 * OFFSET_ADDRESS_SIZE, lower_bound, b); b += 1 + 2 * OFFSET_ADDRESS_SIZE; if (upper_bound > 1) { /* More than one repetition is allowed, so append a backward jump to the `succeed_n' that starts this interval. When we've reached this during matching, we'll have matched the interval once, so jump back only `upper_bound - 1' times. */ STORE_JUMP2 (jump_n, b, laststart + 2 * OFFSET_ADDRESS_SIZE + 1, upper_bound - 1); b += 1 + 2 * OFFSET_ADDRESS_SIZE; /* The location we want to set is the second parameter of the `jump_n'; that is `b-2' as an absolute address. `laststart' will be the `set_number_at' we're about to insert; `laststart+3' the number to set, the source for the relative address. But we are inserting into the middle of the pattern -- so everything is getting moved up by 5. Conclusion: (b - 2) - (laststart + 3) + 5, i.e., b - laststart. We insert this at the beginning of the loop so that if we fail during matching, we'll reinitialize the bounds. */ PREFIX(insert_op2) (set_number_at, laststart, b - laststart, upper_bound - 1, b); b += 1 + 2 * OFFSET_ADDRESS_SIZE; } } pending_exact = 0; break; invalid_interval: if (!(syntax & RE_INVALID_INTERVAL_ORD)) FREE_STACK_RETURN (p == pend ? REG_EBRACE : REG_BADBR); unfetch_interval: /* Match the characters as literals. */ p = beg_interval; c = '{'; if (syntax & RE_NO_BK_BRACES) goto normal_char; else goto normal_backslash; } #ifdef emacs /* There is no way to specify the before_dot and after_dot operators. rms says this is ok. --karl */ case '=': BUF_PUSH (at_dot); break; case 's': laststart = b; PATFETCH (c); BUF_PUSH_2 (syntaxspec, syntax_spec_code[c]); break; case 'S': laststart = b; PATFETCH (c); BUF_PUSH_2 (notsyntaxspec, syntax_spec_code[c]); break; #endif /* emacs */ case 'w': if (syntax & RE_NO_GNU_OPS) goto normal_char; laststart = b; BUF_PUSH (wordchar); break; case 'W': if (syntax & RE_NO_GNU_OPS) goto normal_char; laststart = b; BUF_PUSH (notwordchar); break; case '<': if (syntax & RE_NO_GNU_OPS) goto normal_char; BUF_PUSH (wordbeg); break; case '>': if (syntax & RE_NO_GNU_OPS) goto normal_char; BUF_PUSH (wordend); break; case 'b': if (syntax & RE_NO_GNU_OPS) goto normal_char; BUF_PUSH (wordbound); break; case 'B': if (syntax & RE_NO_GNU_OPS) goto normal_char; BUF_PUSH (notwordbound); break; case '`': if (syntax & RE_NO_GNU_OPS) goto normal_char; BUF_PUSH (begbuf); break; case '\'': if (syntax & RE_NO_GNU_OPS) goto normal_char; BUF_PUSH (endbuf); break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (syntax & RE_NO_BK_REFS) goto normal_char; c1 = c - '0'; if (c1 > regnum) FREE_STACK_RETURN (REG_ESUBREG); /* Can't back reference to a subexpression if inside of it. */ if (group_in_compile_stack (compile_stack, (regnum_t) c1)) goto normal_char; laststart = b; BUF_PUSH_2 (duplicate, c1); break; case '+': case '?': if (syntax & RE_BK_PLUS_QM) goto handle_plus; else goto normal_backslash; default: normal_backslash: /* You might think it would be useful for \ to mean not to translate; but if we don't translate it it will never match anything. */ c = TRANSLATE (c); goto normal_char; } break; default: /* Expects the character in `c'. */ normal_char: /* If no exactn currently being built. */ if (!pending_exact #ifdef WCHAR /* If last exactn handle binary(or character) and new exactn handle character(or binary). */ || is_exactn_bin != is_binary[p - 1 - pattern] #endif /* WCHAR */ /* If last exactn not at current position. */ || pending_exact + *pending_exact + 1 != b /* We have only one byte following the exactn for the count. */ || *pending_exact == (1 << BYTEWIDTH) - 1 /* If followed by a repetition operator. */ || *p == '*' || *p == '^' || ((syntax & RE_BK_PLUS_QM) ? *p == '\\' && (p[1] == '+' || p[1] == '?') : (*p == '+' || *p == '?')) || ((syntax & RE_INTERVALS) && ((syntax & RE_NO_BK_BRACES) ? *p == '{' : (p[0] == '\\' && p[1] == '{')))) { /* Start building a new exactn. */ laststart = b; #ifdef WCHAR /* Is this exactn binary data or character? */ is_exactn_bin = is_binary[p - 1 - pattern]; if (is_exactn_bin) BUF_PUSH_2 (exactn_bin, 0); else BUF_PUSH_2 (exactn, 0); #else BUF_PUSH_2 (exactn, 0); #endif /* WCHAR */ pending_exact = b - 1; } BUF_PUSH (c); (*pending_exact)++; break; } /* switch (c) */ } /* while p != pend */ /* Through the pattern now. */ if (fixup_alt_jump) STORE_JUMP (jump_past_alt, fixup_alt_jump, b); if (!COMPILE_STACK_EMPTY) FREE_STACK_RETURN (REG_EPAREN); /* If we don't want backtracking, force success the first time we reach the end of the compiled pattern. */ if (syntax & RE_NO_POSIX_BACKTRACKING) BUF_PUSH (succeed); #ifdef WCHAR free (pattern); free (mbs_offset); free (is_binary); #endif free (compile_stack.stack); /* We have succeeded; set the length of the buffer. */ #ifdef WCHAR bufp->used = (uintptr_t) b - (uintptr_t) COMPILED_BUFFER_VAR; #else bufp->used = b - bufp->buffer; #endif #ifdef DEBUG if (debug) { DEBUG_PRINT1 ("\nCompiled pattern: \n"); PREFIX(print_compiled_pattern) (bufp); } #endif /* DEBUG */ #ifndef MATCH_MAY_ALLOCATE /* Initialize the failure stack to the largest possible stack. This isn't necessary unless we're trying to avoid calling alloca in the search and match routines. */ { int num_regs = bufp->re_nsub + 1; /* Since DOUBLE_FAIL_STACK refuses to double only if the current size is strictly greater than re_max_failures, the largest possible stack is 2 * re_max_failures failure points. */ if (fail_stack.size < (2 * re_max_failures * MAX_FAILURE_ITEMS)) { fail_stack.size = (2 * re_max_failures * MAX_FAILURE_ITEMS); # ifdef emacs if (! fail_stack.stack) fail_stack.stack = (PREFIX(fail_stack_elt_t) *) xmalloc (fail_stack.size * sizeof (PREFIX(fail_stack_elt_t))); else fail_stack.stack = (PREFIX(fail_stack_elt_t) *) xrealloc (fail_stack.stack, (fail_stack.size * sizeof (PREFIX(fail_stack_elt_t)))); # else /* not emacs */ if (! fail_stack.stack) fail_stack.stack = (PREFIX(fail_stack_elt_t) *) malloc (fail_stack.size * sizeof (PREFIX(fail_stack_elt_t))); else fail_stack.stack = (PREFIX(fail_stack_elt_t) *) realloc (fail_stack.stack, (fail_stack.size * sizeof (PREFIX(fail_stack_elt_t)))); # endif /* not emacs */ } PREFIX(regex_grow_registers) (num_regs); } #endif /* not MATCH_MAY_ALLOCATE */ return REG_NOERROR; } /* regex_compile */ /* Subroutines for `regex_compile'. */ /* Store OP at LOC followed by two-byte integer parameter ARG. */ /* ifdef WCHAR, integer parameter is 1 wchar_t. */ static void PREFIX(store_op1) (op, loc, arg) re_opcode_t op; UCHAR_T *loc; int arg; { *loc = (UCHAR_T) op; STORE_NUMBER (loc + 1, arg); } /* Like `store_op1', but for two two-byte parameters ARG1 and ARG2. */ /* ifdef WCHAR, integer parameter is 1 wchar_t. */ static void PREFIX(store_op2) (op, loc, arg1, arg2) re_opcode_t op; UCHAR_T *loc; int arg1, arg2; { *loc = (UCHAR_T) op; STORE_NUMBER (loc + 1, arg1); STORE_NUMBER (loc + 1 + OFFSET_ADDRESS_SIZE, arg2); } /* Copy the bytes from LOC to END to open up three bytes of space at LOC for OP followed by two-byte integer parameter ARG. */ /* ifdef WCHAR, integer parameter is 1 wchar_t. */ static void PREFIX(insert_op1) (op, loc, arg, end) re_opcode_t op; UCHAR_T *loc; int arg; UCHAR_T *end; { register UCHAR_T *pfrom = end; register UCHAR_T *pto = end + 1 + OFFSET_ADDRESS_SIZE; while (pfrom != loc) *--pto = *--pfrom; PREFIX(store_op1) (op, loc, arg); } /* Like `insert_op1', but for two two-byte parameters ARG1 and ARG2. */ /* ifdef WCHAR, integer parameter is 1 wchar_t. */ static void PREFIX(insert_op2) (op, loc, arg1, arg2, end) re_opcode_t op; UCHAR_T *loc; int arg1, arg2; UCHAR_T *end; { register UCHAR_T *pfrom = end; register UCHAR_T *pto = end + 1 + 2 * OFFSET_ADDRESS_SIZE; while (pfrom != loc) *--pto = *--pfrom; PREFIX(store_op2) (op, loc, arg1, arg2); } /* P points to just after a ^ in PATTERN. Return true if that ^ comes after an alternative or a begin-subexpression. We assume there is at least one character before the ^. */ static boolean PREFIX(at_begline_loc_p) (pattern, p, syntax) const CHAR_T *pattern, *p; reg_syntax_t syntax; { const CHAR_T *prev = p - 2; boolean prev_prev_backslash = prev > pattern && prev[-1] == '\\'; return /* After a subexpression? */ (*prev == '(' && (syntax & RE_NO_BK_PARENS || prev_prev_backslash)) /* After an alternative? */ || (*prev == '|' && (syntax & RE_NO_BK_VBAR || prev_prev_backslash)); } /* The dual of at_begline_loc_p. This one is for $. We assume there is at least one character after the $, i.e., `P < PEND'. */ static boolean PREFIX(at_endline_loc_p) (p, pend, syntax) const CHAR_T *p, *pend; reg_syntax_t syntax; { const CHAR_T *next = p; boolean next_backslash = *next == '\\'; const CHAR_T *next_next = p + 1 < pend ? p + 1 : 0; return /* Before a subexpression? */ (syntax & RE_NO_BK_PARENS ? *next == ')' : next_backslash && next_next && *next_next == ')') /* Before an alternative? */ || (syntax & RE_NO_BK_VBAR ? *next == '|' : next_backslash && next_next && *next_next == '|'); } #else /* not INSIDE_RECURSION */ /* Returns true if REGNUM is in one of COMPILE_STACK's elements and false if it's not. */ static boolean group_in_compile_stack (compile_stack, regnum) compile_stack_type compile_stack; regnum_t regnum; { int this_element; for (this_element = compile_stack.avail - 1; this_element >= 0; this_element--) if (compile_stack.stack[this_element].regnum == regnum) return true; return false; } #endif /* not INSIDE_RECURSION */ #ifdef INSIDE_RECURSION #ifdef WCHAR /* This insert space, which size is "num", into the pattern at "loc". "end" must point the end of the allocated buffer. */ static void insert_space (num, loc, end) int num; CHAR_T *loc; CHAR_T *end; { register CHAR_T *pto = end; register CHAR_T *pfrom = end - num; while (pfrom >= loc) *pto-- = *pfrom--; } #endif /* WCHAR */ #ifdef WCHAR static reg_errcode_t wcs_compile_range (range_start_char, p_ptr, pend, translate, syntax, b, char_set) CHAR_T range_start_char; const CHAR_T **p_ptr, *pend; CHAR_T *char_set, *b; RE_TRANSLATE_TYPE translate; reg_syntax_t syntax; { const CHAR_T *p = *p_ptr; CHAR_T range_start, range_end; reg_errcode_t ret; # ifdef _LIBC uint32_t nrules; uint32_t start_val, end_val; # endif if (p == pend) return REG_ERANGE; # ifdef _LIBC nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); if (nrules != 0) { const char *collseq = (const char *) _NL_CURRENT(LC_COLLATE, _NL_COLLATE_COLLSEQWC); const unsigned char *extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB); if (range_start_char < -1) { /* range_start is a collating symbol. */ int32_t *wextra; /* Retreive the index and get collation sequence value. */ wextra = (int32_t*)(extra + char_set[-range_start_char]); start_val = wextra[1 + *wextra]; } else start_val = collseq_table_lookup(collseq, TRANSLATE(range_start_char)); end_val = collseq_table_lookup (collseq, TRANSLATE (p[0])); /* Report an error if the range is empty and the syntax prohibits this. */ ret = ((syntax & RE_NO_EMPTY_RANGES) && (start_val > end_val))? REG_ERANGE : REG_NOERROR; /* Insert space to the end of the char_ranges. */ insert_space(2, b - char_set[5] - 2, b - 1); *(b - char_set[5] - 2) = (wchar_t)start_val; *(b - char_set[5] - 1) = (wchar_t)end_val; char_set[4]++; /* ranges_index */ } else # endif { range_start = (range_start_char >= 0)? TRANSLATE (range_start_char): range_start_char; range_end = TRANSLATE (p[0]); /* Report an error if the range is empty and the syntax prohibits this. */ ret = ((syntax & RE_NO_EMPTY_RANGES) && (range_start > range_end))? REG_ERANGE : REG_NOERROR; /* Insert space to the end of the char_ranges. */ insert_space(2, b - char_set[5] - 2, b - 1); *(b - char_set[5] - 2) = range_start; *(b - char_set[5] - 1) = range_end; char_set[4]++; /* ranges_index */ } /* Have to increment the pointer into the pattern string, so the caller isn't still at the ending character. */ (*p_ptr)++; return ret; } #else /* BYTE */ /* Read the ending character of a range (in a bracket expression) from the uncompiled pattern *P_PTR (which ends at PEND). We assume the starting character is in `P[-2]'. (`P[-1]' is the character `-'.) Then we set the translation of all bits between the starting and ending characters (inclusive) in the compiled pattern B. Return an error code. We use these short variable names so we can use the same macros as `regex_compile' itself. */ static reg_errcode_t byte_compile_range (range_start_char, p_ptr, pend, translate, syntax, b) unsigned int range_start_char; const char **p_ptr, *pend; RE_TRANSLATE_TYPE translate; reg_syntax_t syntax; unsigned char *b; { unsigned this_char; const char *p = *p_ptr; reg_errcode_t ret; # if _LIBC const unsigned char *collseq; unsigned int start_colseq; unsigned int end_colseq; # else unsigned end_char; # endif if (p == pend) return REG_ERANGE; /* Have to increment the pointer into the pattern string, so the caller isn't still at the ending character. */ (*p_ptr)++; /* Report an error if the range is empty and the syntax prohibits this. */ ret = syntax & RE_NO_EMPTY_RANGES ? REG_ERANGE : REG_NOERROR; # if _LIBC collseq = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQMB); start_colseq = collseq[(unsigned char) TRANSLATE (range_start_char)]; end_colseq = collseq[(unsigned char) TRANSLATE (p[0])]; for (this_char = 0; this_char <= (unsigned char) -1; ++this_char) { unsigned int this_colseq = collseq[(unsigned char) TRANSLATE (this_char)]; if (start_colseq <= this_colseq && this_colseq <= end_colseq) { SET_LIST_BIT (TRANSLATE (this_char)); ret = REG_NOERROR; } } # else /* Here we see why `this_char' has to be larger than an `unsigned char' -- we would otherwise go into an infinite loop, since all characters <= 0xff. */ range_start_char = TRANSLATE (range_start_char); /* TRANSLATE(p[0]) is casted to char (not unsigned char) in TRANSLATE, and some compilers cast it to int implicitly, so following for_loop may fall to (almost) infinite loop. e.g. If translate[p[0]] = 0xff, end_char may equals to 0xffffffff. To avoid this, we cast p[0] to unsigned int and truncate it. */ end_char = ((unsigned)TRANSLATE(p[0]) & ((1 << BYTEWIDTH) - 1)); for (this_char = range_start_char; this_char <= end_char; ++this_char) { SET_LIST_BIT (TRANSLATE (this_char)); ret = REG_NOERROR; } # endif return ret; } #endif /* WCHAR */ /* re_compile_fastmap computes a ``fastmap'' for the compiled pattern in BUFP. A fastmap records which of the (1 << BYTEWIDTH) possible characters can start a string that matches the pattern. This fastmap is used by re_search to skip quickly over impossible starting points. The caller must supply the address of a (1 << BYTEWIDTH)-byte data area as BUFP->fastmap. We set the `fastmap', `fastmap_accurate', and `can_be_null' fields in the pattern buffer. Returns 0 if we succeed, -2 if an internal error. */ #ifdef WCHAR /* local function for re_compile_fastmap. truncate wchar_t character to char. */ static unsigned char truncate_wchar (CHAR_T c); static unsigned char truncate_wchar (c) CHAR_T c; { unsigned char buf[MB_CUR_MAX]; mbstate_t state; int retval; memset (&state, '\0', sizeof (state)); retval = wcrtomb (buf, c, &state); return retval > 0 ? buf[0] : (unsigned char) c; } #endif /* WCHAR */ static int PREFIX(re_compile_fastmap) (bufp) struct re_pattern_buffer *bufp; { int j, k; #ifdef MATCH_MAY_ALLOCATE PREFIX(fail_stack_type) fail_stack; #endif #ifndef REGEX_MALLOC char *destination; #endif register char *fastmap = bufp->fastmap; #ifdef WCHAR /* We need to cast pattern to (wchar_t*), because we casted this compiled pattern to (char*) in regex_compile. */ UCHAR_T *pattern = (UCHAR_T*)bufp->buffer; register UCHAR_T *pend = (UCHAR_T*) (bufp->buffer + bufp->used); #else /* BYTE */ UCHAR_T *pattern = bufp->buffer; register UCHAR_T *pend = pattern + bufp->used; #endif /* WCHAR */ UCHAR_T *p = pattern; #ifdef REL_ALLOC /* This holds the pointer to the failure stack, when it is allocated relocatably. */ fail_stack_elt_t *failure_stack_ptr; #endif /* Assume that each path through the pattern can be null until proven otherwise. We set this false at the bottom of switch statement, to which we get only if a particular path doesn't match the empty string. */ boolean path_can_be_null = true; /* We aren't doing a `succeed_n' to begin with. */ boolean succeed_n_p = false; assert (fastmap != NULL && p != NULL); INIT_FAIL_STACK (); bzero (fastmap, 1 << BYTEWIDTH); /* Assume nothing's valid. */ bufp->fastmap_accurate = 1; /* It will be when we're done. */ bufp->can_be_null = 0; while (1) { if (p == pend || *p == succeed) { /* We have reached the (effective) end of pattern. */ if (!FAIL_STACK_EMPTY ()) { bufp->can_be_null |= path_can_be_null; /* Reset for next path. */ path_can_be_null = true; p = fail_stack.stack[--fail_stack.avail].pointer; continue; } else break; } /* We should never be about to go beyond the end of the pattern. */ assert (p < pend); switch (SWITCH_ENUM_CAST ((re_opcode_t) *p++)) { /* I guess the idea here is to simply not bother with a fastmap if a backreference is used, since it's too hard to figure out the fastmap for the corresponding group. Setting `can_be_null' stops `re_search_2' from using the fastmap, so that is all we do. */ case duplicate: bufp->can_be_null = 1; goto done; /* Following are the cases which match a character. These end with `break'. */ #ifdef WCHAR case exactn: fastmap[truncate_wchar(p[1])] = 1; break; #else /* BYTE */ case exactn: fastmap[p[1]] = 1; break; #endif /* WCHAR */ #ifdef MBS_SUPPORT case exactn_bin: fastmap[p[1]] = 1; break; #endif #ifdef WCHAR /* It is hard to distinguish fastmap from (multi byte) characters which depends on current locale. */ case charset: case charset_not: case wordchar: case notwordchar: bufp->can_be_null = 1; goto done; #else /* BYTE */ case charset: for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--) if (p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH))) fastmap[j] = 1; break; case charset_not: /* Chars beyond end of map must be allowed. */ for (j = *p * BYTEWIDTH; j < (1 << BYTEWIDTH); j++) fastmap[j] = 1; for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--) if (!(p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH)))) fastmap[j] = 1; break; case wordchar: for (j = 0; j < (1 << BYTEWIDTH); j++) if (SYNTAX (j) == Sword) fastmap[j] = 1; break; case notwordchar: for (j = 0; j < (1 << BYTEWIDTH); j++) if (SYNTAX (j) != Sword) fastmap[j] = 1; break; #endif /* WCHAR */ case anychar: { int fastmap_newline = fastmap['\n']; /* `.' matches anything ... */ for (j = 0; j < (1 << BYTEWIDTH); j++) fastmap[j] = 1; /* ... except perhaps newline. */ if (!(bufp->syntax & RE_DOT_NEWLINE)) fastmap['\n'] = fastmap_newline; /* Return if we have already set `can_be_null'; if we have, then the fastmap is irrelevant. Something's wrong here. */ else if (bufp->can_be_null) goto done; /* Otherwise, have to check alternative paths. */ break; } #ifdef emacs case syntaxspec: k = *p++; for (j = 0; j < (1 << BYTEWIDTH); j++) if (SYNTAX (j) == (enum syntaxcode) k) fastmap[j] = 1; break; case notsyntaxspec: k = *p++; for (j = 0; j < (1 << BYTEWIDTH); j++) if (SYNTAX (j) != (enum syntaxcode) k) fastmap[j] = 1; break; /* All cases after this match the empty string. These end with `continue'. */ case before_dot: case at_dot: case after_dot: continue; #endif /* emacs */ case no_op: case begline: case endline: case begbuf: case endbuf: case wordbound: case notwordbound: case wordbeg: case wordend: case push_dummy_failure: continue; case jump_n: case pop_failure_jump: case maybe_pop_jump: case jump: case jump_past_alt: case dummy_failure_jump: EXTRACT_NUMBER_AND_INCR (j, p); p += j; if (j > 0) continue; /* Jump backward implies we just went through the body of a loop and matched nothing. Opcode jumped to should be `on_failure_jump' or `succeed_n'. Just treat it like an ordinary jump. For a * loop, it has pushed its failure point already; if so, discard that as redundant. */ if ((re_opcode_t) *p != on_failure_jump && (re_opcode_t) *p != succeed_n) continue; p++; EXTRACT_NUMBER_AND_INCR (j, p); p += j; /* If what's on the stack is where we are now, pop it. */ if (!FAIL_STACK_EMPTY () && fail_stack.stack[fail_stack.avail - 1].pointer == p) fail_stack.avail--; continue; case on_failure_jump: case on_failure_keep_string_jump: handle_on_failure_jump: EXTRACT_NUMBER_AND_INCR (j, p); /* For some patterns, e.g., `(a?)?', `p+j' here points to the end of the pattern. We don't want to push such a point, since when we restore it above, entering the switch will increment `p' past the end of the pattern. We don't need to push such a point since we obviously won't find any more fastmap entries beyond `pend'. Such a pattern can match the null string, though. */ if (p + j < pend) { if (!PUSH_PATTERN_OP (p + j, fail_stack)) { RESET_FAIL_STACK (); return -2; } } else bufp->can_be_null = 1; if (succeed_n_p) { EXTRACT_NUMBER_AND_INCR (k, p); /* Skip the n. */ succeed_n_p = false; } continue; case succeed_n: /* Get to the number of times to succeed. */ p += OFFSET_ADDRESS_SIZE; /* Increment p past the n for when k != 0. */ EXTRACT_NUMBER_AND_INCR (k, p); if (k == 0) { p -= 2 * OFFSET_ADDRESS_SIZE; succeed_n_p = true; /* Spaghetti code alert. */ goto handle_on_failure_jump; } continue; case set_number_at: p += 2 * OFFSET_ADDRESS_SIZE; continue; case start_memory: case stop_memory: p += 2; continue; default: abort (); /* We have listed all the cases. */ } /* switch *p++ */ /* Getting here means we have found the possible starting characters for one path of the pattern -- and that the empty string does not match. We need not follow this path further. Instead, look at the next alternative (remembered on the stack), or quit if no more. The test at the top of the loop does these things. */ path_can_be_null = false; p = pend; } /* while p */ /* Set `can_be_null' for the last path (also the first path, if the pattern is empty). */ bufp->can_be_null |= path_can_be_null; done: RESET_FAIL_STACK (); return 0; } #else /* not INSIDE_RECURSION */ int re_compile_fastmap (bufp) struct re_pattern_buffer *bufp; { # ifdef MBS_SUPPORT if (MB_CUR_MAX != 1) return wcs_re_compile_fastmap(bufp); else # endif return byte_re_compile_fastmap(bufp); } /* re_compile_fastmap */ #ifdef _LIBC weak_alias (__re_compile_fastmap, re_compile_fastmap) #endif /* Set REGS to hold NUM_REGS registers, storing them in STARTS and ENDS. Subsequent matches using PATTERN_BUFFER and REGS will use this memory for recording register information. STARTS and ENDS must be allocated using the malloc library routine, and must each be at least NUM_REGS * sizeof (regoff_t) bytes long. If NUM_REGS == 0, then subsequent matches should allocate their own register data. Unless this function is called, the first search or match using PATTERN_BUFFER will allocate its own register data, without freeing the old data. */ void re_set_registers (bufp, regs, num_regs, starts, ends) struct re_pattern_buffer *bufp; struct re_registers *regs; unsigned num_regs; regoff_t *starts, *ends; { if (num_regs) { bufp->regs_allocated = REGS_REALLOCATE; regs->num_regs = num_regs; regs->start = starts; regs->end = ends; } else { bufp->regs_allocated = REGS_UNALLOCATED; regs->num_regs = 0; regs->start = regs->end = (regoff_t *) 0; } } #ifdef _LIBC weak_alias (__re_set_registers, re_set_registers) #endif /* Searching routines. */ /* Like re_search_2, below, but only one string is specified, and doesn't let you say where to stop matching. */ int re_search (bufp, string, size, startpos, range, regs) struct re_pattern_buffer *bufp; const char *string; int size, startpos, range; struct re_registers *regs; { return re_search_2 (bufp, NULL, 0, string, size, startpos, range, regs, size); } #ifdef _LIBC weak_alias (__re_search, re_search) #endif /* Using the compiled pattern in BUFP->buffer, first tries to match the virtual concatenation of STRING1 and STRING2, starting first at index STARTPOS, then at STARTPOS + 1, and so on. STRING1 and STRING2 have length SIZE1 and SIZE2, respectively. RANGE is how far to scan while trying to match. RANGE = 0 means try only at STARTPOS; in general, the last start tried is STARTPOS + RANGE. In REGS, return the indices of the virtual concatenation of STRING1 and STRING2 that matched the entire BUFP->buffer and its contained subexpressions. Do not consider matching one past the index STOP in the virtual concatenation of STRING1 and STRING2. We return either the position in the strings at which the match was found, -1 if no match, or -2 if error (such as failure stack overflow). */ int re_search_2 (bufp, string1, size1, string2, size2, startpos, range, regs, stop) struct re_pattern_buffer *bufp; const char *string1, *string2; int size1, size2; int startpos; int range; struct re_registers *regs; int stop; { # ifdef MBS_SUPPORT if (MB_CUR_MAX != 1) return wcs_re_search_2 (bufp, string1, size1, string2, size2, startpos, range, regs, stop); else # endif return byte_re_search_2 (bufp, string1, size1, string2, size2, startpos, range, regs, stop); } /* re_search_2 */ #ifdef _LIBC weak_alias (__re_search_2, re_search_2) #endif #endif /* not INSIDE_RECURSION */ #ifdef INSIDE_RECURSION #ifdef MATCH_MAY_ALLOCATE # define FREE_VAR(var) if (var) REGEX_FREE (var); var = NULL #else # define FREE_VAR(var) if (var) free (var); var = NULL #endif #ifdef WCHAR # define MAX_ALLOCA_SIZE 2000 # define FREE_WCS_BUFFERS() \ do { \ if (size1 > MAX_ALLOCA_SIZE) \ { \ free (wcs_string1); \ free (mbs_offset1); \ } \ else \ { \ FREE_VAR (wcs_string1); \ FREE_VAR (mbs_offset1); \ } \ if (size2 > MAX_ALLOCA_SIZE) \ { \ free (wcs_string2); \ free (mbs_offset2); \ } \ else \ { \ FREE_VAR (wcs_string2); \ FREE_VAR (mbs_offset2); \ } \ } while (0) #endif static int PREFIX(re_search_2) (bufp, string1, size1, string2, size2, startpos, range, regs, stop) struct re_pattern_buffer *bufp; const char *string1, *string2; int size1, size2; int startpos; int range; struct re_registers *regs; int stop; { int val; register char *fastmap = bufp->fastmap; register RE_TRANSLATE_TYPE translate = bufp->translate; int total_size = size1 + size2; int endpos = startpos + range; #ifdef WCHAR /* We need wchar_t* buffers correspond to cstring1, cstring2. */ wchar_t *wcs_string1 = NULL, *wcs_string2 = NULL; /* We need the size of wchar_t buffers correspond to csize1, csize2. */ int wcs_size1 = 0, wcs_size2 = 0; /* offset buffer for optimizatoin. See convert_mbs_to_wc. */ int *mbs_offset1 = NULL, *mbs_offset2 = NULL; /* They hold whether each wchar_t is binary data or not. */ char *is_binary = NULL; #endif /* WCHAR */ /* Check for out-of-range STARTPOS. */ if (startpos < 0 || startpos > total_size) return -1; /* Fix up RANGE if it might eventually take us outside the virtual concatenation of STRING1 and STRING2. Make sure we won't move STARTPOS below 0 or above TOTAL_SIZE. */ if (endpos < 0) range = 0 - startpos; else if (endpos > total_size) range = total_size - startpos; /* If the search isn't to be a backwards one, don't waste time in a search for a pattern that must be anchored. */ if (bufp->used > 0 && range > 0 && ((re_opcode_t) bufp->buffer[0] == begbuf /* `begline' is like `begbuf' if it cannot match at newlines. */ || ((re_opcode_t) bufp->buffer[0] == begline && !bufp->newline_anchor))) { if (startpos > 0) return -1; else range = 1; } #ifdef emacs /* In a forward search for something that starts with \=. don't keep searching past point. */ if (bufp->used > 0 && (re_opcode_t) bufp->buffer[0] == at_dot && range > 0) { range = PT - startpos; if (range <= 0) return -1; } #endif /* emacs */ /* Update the fastmap now if not correct already. */ if (fastmap && !bufp->fastmap_accurate) if (re_compile_fastmap (bufp) == -2) return -2; #ifdef WCHAR /* Allocate wchar_t array for wcs_string1 and wcs_string2 and fill them with converted string. */ if (size1 != 0) { if (size1 > MAX_ALLOCA_SIZE) { wcs_string1 = TALLOC (size1 + 1, CHAR_T); mbs_offset1 = TALLOC (size1 + 1, int); is_binary = TALLOC (size1 + 1, char); } else { wcs_string1 = REGEX_TALLOC (size1 + 1, CHAR_T); mbs_offset1 = REGEX_TALLOC (size1 + 1, int); is_binary = REGEX_TALLOC (size1 + 1, char); } if (!wcs_string1 || !mbs_offset1 || !is_binary) { if (size1 > MAX_ALLOCA_SIZE) { free (wcs_string1); free (mbs_offset1); free (is_binary); } else { FREE_VAR (wcs_string1); FREE_VAR (mbs_offset1); FREE_VAR (is_binary); } return -2; } wcs_size1 = convert_mbs_to_wcs(wcs_string1, string1, size1, mbs_offset1, is_binary); wcs_string1[wcs_size1] = L'\0'; /* for a sentinel */ if (size1 > MAX_ALLOCA_SIZE) free (is_binary); else FREE_VAR (is_binary); } if (size2 != 0) { if (size2 > MAX_ALLOCA_SIZE) { wcs_string2 = TALLOC (size2 + 1, CHAR_T); mbs_offset2 = TALLOC (size2 + 1, int); is_binary = TALLOC (size2 + 1, char); } else { wcs_string2 = REGEX_TALLOC (size2 + 1, CHAR_T); mbs_offset2 = REGEX_TALLOC (size2 + 1, int); is_binary = REGEX_TALLOC (size2 + 1, char); } if (!wcs_string2 || !mbs_offset2 || !is_binary) { FREE_WCS_BUFFERS (); if (size2 > MAX_ALLOCA_SIZE) free (is_binary); else FREE_VAR (is_binary); return -2; } wcs_size2 = convert_mbs_to_wcs(wcs_string2, string2, size2, mbs_offset2, is_binary); wcs_string2[wcs_size2] = L'\0'; /* for a sentinel */ if (size2 > MAX_ALLOCA_SIZE) free (is_binary); else FREE_VAR (is_binary); } #endif /* WCHAR */ /* Loop through the string, looking for a place to start matching. */ for (;;) { /* If a fastmap is supplied, skip quickly over characters that cannot be the start of a match. If the pattern can match the null string, however, we don't need to skip characters; we want the first null string. */ if (fastmap && startpos < total_size && !bufp->can_be_null) { if (range > 0) /* Searching forwards. */ { register const char *d; register int lim = 0; int irange = range; if (startpos < size1 && startpos + range >= size1) lim = range - (size1 - startpos); d = (startpos >= size1 ? string2 - size1 : string1) + startpos; /* Written out as an if-else to avoid testing `translate' inside the loop. */ if (translate) while (range > lim && !fastmap[(unsigned char) translate[(unsigned char) *d++]]) range--; else while (range > lim && !fastmap[(unsigned char) *d++]) range--; startpos += irange - range; } else /* Searching backwards. */ { register CHAR_T c = (size1 == 0 || startpos >= size1 ? string2[startpos - size1] : string1[startpos]); if (!fastmap[(unsigned char) TRANSLATE (c)]) goto advance; } } /* If can't match the null string, and that's all we have left, fail. */ if (range >= 0 && startpos == total_size && fastmap && !bufp->can_be_null) { #ifdef WCHAR FREE_WCS_BUFFERS (); #endif return -1; } #ifdef WCHAR val = wcs_re_match_2_internal (bufp, string1, size1, string2, size2, startpos, regs, stop, wcs_string1, wcs_size1, wcs_string2, wcs_size2, mbs_offset1, mbs_offset2); #else /* BYTE */ val = byte_re_match_2_internal (bufp, string1, size1, string2, size2, startpos, regs, stop); #endif /* BYTE */ #ifndef REGEX_MALLOC # ifdef C_ALLOCA alloca (0); # endif #endif if (val >= 0) { #ifdef WCHAR FREE_WCS_BUFFERS (); #endif return startpos; } if (val == -2) { #ifdef WCHAR FREE_WCS_BUFFERS (); #endif return -2; } advance: if (!range) break; else if (range > 0) { range--; startpos++; } else { range++; startpos--; } } #ifdef WCHAR FREE_WCS_BUFFERS (); #endif return -1; } #ifdef WCHAR /* This converts PTR, a pointer into one of the search wchar_t strings `string1' and `string2' into an multibyte string offset from the beginning of that string. We use mbs_offset to optimize. See convert_mbs_to_wcs. */ # define POINTER_TO_OFFSET(ptr) \ (FIRST_STRING_P (ptr) \ ? ((regoff_t)(mbs_offset1 != NULL? mbs_offset1[(ptr)-string1] : 0)) \ : ((regoff_t)((mbs_offset2 != NULL? mbs_offset2[(ptr)-string2] : 0) \ + csize1))) #else /* BYTE */ /* This converts PTR, a pointer into one of the search strings `string1' and `string2' into an offset from the beginning of that string. */ # define POINTER_TO_OFFSET(ptr) \ (FIRST_STRING_P (ptr) \ ? ((regoff_t) ((ptr) - string1)) \ : ((regoff_t) ((ptr) - string2 + size1))) #endif /* WCHAR */ /* Macros for dealing with the split strings in re_match_2. */ #define MATCHING_IN_FIRST_STRING (dend == end_match_1) /* Call before fetching a character with *d. This switches over to string2 if necessary. */ #define PREFETCH() \ while (d == dend) \ { \ /* End of string2 => fail. */ \ if (dend == end_match_2) \ goto fail; \ /* End of string1 => advance to string2. */ \ d = string2; \ dend = end_match_2; \ } /* Test if at very beginning or at very end of the virtual concatenation of `string1' and `string2'. If only one string, it's `string2'. */ #define AT_STRINGS_BEG(d) ((d) == (size1 ? string1 : string2) || !size2) #define AT_STRINGS_END(d) ((d) == end2) /* Test if D points to a character which is word-constituent. We have two special cases to check for: if past the end of string1, look at the first character in string2; and if before the beginning of string2, look at the last character in string1. */ #ifdef WCHAR /* Use internationalized API instead of SYNTAX. */ # define WORDCHAR_P(d) \ (iswalnum ((wint_t)((d) == end1 ? *string2 \ : (d) == string2 - 1 ? *(end1 - 1) : *(d))) != 0 \ || ((d) == end1 ? *string2 \ : (d) == string2 - 1 ? *(end1 - 1) : *(d)) == L'_') #else /* BYTE */ # define WORDCHAR_P(d) \ (SYNTAX ((d) == end1 ? *string2 \ : (d) == string2 - 1 ? *(end1 - 1) : *(d)) \ == Sword) #endif /* WCHAR */ /* Disabled due to a compiler bug -- see comment at case wordbound */ #if 0 /* Test if the character before D and the one at D differ with respect to being word-constituent. */ #define AT_WORD_BOUNDARY(d) \ (AT_STRINGS_BEG (d) || AT_STRINGS_END (d) \ || WORDCHAR_P (d - 1) != WORDCHAR_P (d)) #endif /* Free everything we malloc. */ #ifdef MATCH_MAY_ALLOCATE # ifdef WCHAR # define FREE_VARIABLES() \ do { \ REGEX_FREE_STACK (fail_stack.stack); \ FREE_VAR (regstart); \ FREE_VAR (regend); \ FREE_VAR (old_regstart); \ FREE_VAR (old_regend); \ FREE_VAR (best_regstart); \ FREE_VAR (best_regend); \ FREE_VAR (reg_info); \ FREE_VAR (reg_dummy); \ FREE_VAR (reg_info_dummy); \ if (!cant_free_wcs_buf) \ { \ FREE_VAR (string1); \ FREE_VAR (string2); \ FREE_VAR (mbs_offset1); \ FREE_VAR (mbs_offset2); \ } \ } while (0) # else /* BYTE */ # define FREE_VARIABLES() \ do { \ REGEX_FREE_STACK (fail_stack.stack); \ FREE_VAR (regstart); \ FREE_VAR (regend); \ FREE_VAR (old_regstart); \ FREE_VAR (old_regend); \ FREE_VAR (best_regstart); \ FREE_VAR (best_regend); \ FREE_VAR (reg_info); \ FREE_VAR (reg_dummy); \ FREE_VAR (reg_info_dummy); \ } while (0) # endif /* WCHAR */ #else # ifdef WCHAR # define FREE_VARIABLES() \ do { \ if (!cant_free_wcs_buf) \ { \ FREE_VAR (string1); \ FREE_VAR (string2); \ FREE_VAR (mbs_offset1); \ FREE_VAR (mbs_offset2); \ } \ } while (0) # else /* BYTE */ # define FREE_VARIABLES() ((void)0) /* Do nothing! But inhibit gcc warning. */ # endif /* WCHAR */ #endif /* not MATCH_MAY_ALLOCATE */ /* These values must meet several constraints. They must not be valid register values; since we have a limit of 255 registers (because we use only one byte in the pattern for the register number), we can use numbers larger than 255. They must differ by 1, because of NUM_FAILURE_ITEMS above. And the value for the lowest register must be larger than the value for the highest register, so we do not try to actually save any registers when none are active. */ #define NO_HIGHEST_ACTIVE_REG (1 << BYTEWIDTH) #define NO_LOWEST_ACTIVE_REG (NO_HIGHEST_ACTIVE_REG + 1) #else /* not INSIDE_RECURSION */ /* Matching routines. */ #ifndef emacs /* Emacs never uses this. */ /* re_match is like re_match_2 except it takes only a single string. */ int re_match (bufp, string, size, pos, regs) struct re_pattern_buffer *bufp; const char *string; int size, pos; struct re_registers *regs; { int result; # ifdef MBS_SUPPORT if (MB_CUR_MAX != 1) result = wcs_re_match_2_internal (bufp, NULL, 0, string, size, pos, regs, size, NULL, 0, NULL, 0, NULL, NULL); else # endif result = byte_re_match_2_internal (bufp, NULL, 0, string, size, pos, regs, size); # ifndef REGEX_MALLOC # ifdef C_ALLOCA alloca (0); # endif # endif return result; } # ifdef _LIBC weak_alias (__re_match, re_match) # endif #endif /* not emacs */ #endif /* not INSIDE_RECURSION */ #ifdef INSIDE_RECURSION static boolean PREFIX(group_match_null_string_p) _RE_ARGS ((UCHAR_T **p, UCHAR_T *end, PREFIX(register_info_type) *reg_info)); static boolean PREFIX(alt_match_null_string_p) _RE_ARGS ((UCHAR_T *p, UCHAR_T *end, PREFIX(register_info_type) *reg_info)); static boolean PREFIX(common_op_match_null_string_p) _RE_ARGS ((UCHAR_T **p, UCHAR_T *end, PREFIX(register_info_type) *reg_info)); static int PREFIX(bcmp_translate) _RE_ARGS ((const CHAR_T *s1, const CHAR_T *s2, int len, char *translate)); #else /* not INSIDE_RECURSION */ /* re_match_2 matches the compiled pattern in BUFP against the the (virtual) concatenation of STRING1 and STRING2 (of length SIZE1 and SIZE2, respectively). We start matching at POS, and stop matching at STOP. If REGS is non-null and the `no_sub' field of BUFP is nonzero, we store offsets for the substring each group matched in REGS. See the documentation for exactly how many groups we fill. We return -1 if no match, -2 if an internal error (such as the failure stack overflowing). Otherwise, we return the length of the matched substring. */ int re_match_2 (bufp, string1, size1, string2, size2, pos, regs, stop) struct re_pattern_buffer *bufp; const char *string1, *string2; int size1, size2; int pos; struct re_registers *regs; int stop; { int result; # ifdef MBS_SUPPORT if (MB_CUR_MAX != 1) result = wcs_re_match_2_internal (bufp, string1, size1, string2, size2, pos, regs, stop, NULL, 0, NULL, 0, NULL, NULL); else # endif result = byte_re_match_2_internal (bufp, string1, size1, string2, size2, pos, regs, stop); #ifndef REGEX_MALLOC # ifdef C_ALLOCA alloca (0); # endif #endif return result; } #ifdef _LIBC weak_alias (__re_match_2, re_match_2) #endif #endif /* not INSIDE_RECURSION */ #ifdef INSIDE_RECURSION #ifdef WCHAR static int count_mbs_length PARAMS ((int *, int)); /* This check the substring (from 0, to length) of the multibyte string, to which offset_buffer correspond. And count how many wchar_t_characters the substring occupy. We use offset_buffer to optimization. See convert_mbs_to_wcs. */ static int count_mbs_length(offset_buffer, length) int *offset_buffer; int length; { int upper, lower; /* Check whether the size is valid. */ if (length < 0) return -1; if (offset_buffer == NULL) return 0; /* If there are no multibyte character, offset_buffer[i] == i. Optmize for this case. */ if (offset_buffer[length] == length) return length; /* Set up upper with length. (because for all i, offset_buffer[i] >= i) */ upper = length; lower = 0; while (true) { int middle = (lower + upper) / 2; if (middle == lower || middle == upper) break; if (offset_buffer[middle] > length) upper = middle; else if (offset_buffer[middle] < length) lower = middle; else return middle; } return -1; } #endif /* WCHAR */ /* This is a separate function so that we can force an alloca cleanup afterwards. */ #ifdef WCHAR static int wcs_re_match_2_internal (bufp, cstring1, csize1, cstring2, csize2, pos, regs, stop, string1, size1, string2, size2, mbs_offset1, mbs_offset2) struct re_pattern_buffer *bufp; const char *cstring1, *cstring2; int csize1, csize2; int pos; struct re_registers *regs; int stop; /* string1 == string2 == NULL means string1/2, size1/2 and mbs_offset1/2 need seting up in this function. */ /* We need wchar_t* buffers correspond to cstring1, cstring2. */ wchar_t *string1, *string2; /* We need the size of wchar_t buffers correspond to csize1, csize2. */ int size1, size2; /* offset buffer for optimizatoin. See convert_mbs_to_wc. */ int *mbs_offset1, *mbs_offset2; #else /* BYTE */ static int byte_re_match_2_internal (bufp, string1, size1,string2, size2, pos, regs, stop) struct re_pattern_buffer *bufp; const char *string1, *string2; int size1, size2; int pos; struct re_registers *regs; int stop; #endif /* BYTE */ { /* General temporaries. */ int mcnt; UCHAR_T *p1; #ifdef WCHAR /* They hold whether each wchar_t is binary data or not. */ char *is_binary = NULL; /* If true, we can't free string1/2, mbs_offset1/2. */ int cant_free_wcs_buf = 1; #endif /* WCHAR */ /* Just past the end of the corresponding string. */ const CHAR_T *end1, *end2; /* Pointers into string1 and string2, just past the last characters in each to consider matching. */ const CHAR_T *end_match_1, *end_match_2; /* Where we are in the data, and the end of the current string. */ const CHAR_T *d, *dend; /* Where we are in the pattern, and the end of the pattern. */ #ifdef WCHAR UCHAR_T *pattern, *p; register UCHAR_T *pend; #else /* BYTE */ UCHAR_T *p = bufp->buffer; register UCHAR_T *pend = p + bufp->used; #endif /* WCHAR */ /* Mark the opcode just after a start_memory, so we can test for an empty subpattern when we get to the stop_memory. */ UCHAR_T *just_past_start_mem = 0; /* We use this to map every character in the string. */ RE_TRANSLATE_TYPE translate = bufp->translate; /* Failure point stack. Each place that can handle a failure further down the line pushes a failure point on this stack. It consists of restart, regend, and reg_info for all registers corresponding to the subexpressions we're currently inside, plus the number of such registers, and, finally, two char *'s. The first char * is where to resume scanning the pattern; the second one is where to resume scanning the strings. If the latter is zero, the failure point is a ``dummy''; if a failure happens and the failure point is a dummy, it gets discarded and the next next one is tried. */ #ifdef MATCH_MAY_ALLOCATE /* otherwise, this is global. */ PREFIX(fail_stack_type) fail_stack; #endif #ifdef DEBUG static unsigned failure_id; unsigned nfailure_points_pushed = 0, nfailure_points_popped = 0; #endif #ifdef REL_ALLOC /* This holds the pointer to the failure stack, when it is allocated relocatably. */ fail_stack_elt_t *failure_stack_ptr; #endif /* We fill all the registers internally, independent of what we return, for use in backreferences. The number here includes an element for register zero. */ size_t num_regs = bufp->re_nsub + 1; /* The currently active registers. */ active_reg_t lowest_active_reg = NO_LOWEST_ACTIVE_REG; active_reg_t highest_active_reg = NO_HIGHEST_ACTIVE_REG; /* Information on the contents of registers. These are pointers into the input strings; they record just what was matched (on this attempt) by a subexpression part of the pattern, that is, the regnum-th regstart pointer points to where in the pattern we began matching and the regnum-th regend points to right after where we stopped matching the regnum-th subexpression. (The zeroth register keeps track of what the whole pattern matches.) */ #ifdef MATCH_MAY_ALLOCATE /* otherwise, these are global. */ const CHAR_T **regstart, **regend; #endif /* If a group that's operated upon by a repetition operator fails to match anything, then the register for its start will need to be restored because it will have been set to wherever in the string we are when we last see its open-group operator. Similarly for a register's end. */ #ifdef MATCH_MAY_ALLOCATE /* otherwise, these are global. */ const CHAR_T **old_regstart, **old_regend; #endif /* The is_active field of reg_info helps us keep track of which (possibly nested) subexpressions we are currently in. The matched_something field of reg_info[reg_num] helps us tell whether or not we have matched any of the pattern so far this time through the reg_num-th subexpression. These two fields get reset each time through any loop their register is in. */ #ifdef MATCH_MAY_ALLOCATE /* otherwise, this is global. */ PREFIX(register_info_type) *reg_info; #endif /* The following record the register info as found in the above variables when we find a match better than any we've seen before. This happens as we backtrack through the failure points, which in turn happens only if we have not yet matched the entire string. */ unsigned best_regs_set = false; #ifdef MATCH_MAY_ALLOCATE /* otherwise, these are global. */ const CHAR_T **best_regstart, **best_regend; #endif /* Logically, this is `best_regend[0]'. But we don't want to have to allocate space for that if we're not allocating space for anything else (see below). Also, we never need info about register 0 for any of the other register vectors, and it seems rather a kludge to treat `best_regend' differently than the rest. So we keep track of the end of the best match so far in a separate variable. We initialize this to NULL so that when we backtrack the first time and need to test it, it's not garbage. */ const CHAR_T *match_end = NULL; /* This helps SET_REGS_MATCHED avoid doing redundant work. */ int set_regs_matched_done = 0; /* Used when we pop values we don't care about. */ #ifdef MATCH_MAY_ALLOCATE /* otherwise, these are global. */ const CHAR_T **reg_dummy; PREFIX(register_info_type) *reg_info_dummy; #endif #ifdef DEBUG /* Counts the total number of registers pushed. */ unsigned num_regs_pushed = 0; #endif /* Definitions for state transitions. More efficiently for gcc. */ #ifdef __GNUC__ # if defined HAVE_SUBTRACT_LOCAL_LABELS && defined SHARED # define NEXT \ do \ { \ int offset; \ const void *__unbounded ptr; \ offset = (p == pend \ ? 0 : jmptable[SWITCH_ENUM_CAST ((re_opcode_t) *p++)]); \ ptr = &&end_of_pattern + offset; \ goto *ptr; \ } \ while (0) # define REF(x) \ &&label_##x - &&end_of_pattern # define JUMP_TABLE_TYPE const int # else # define NEXT \ do \ { \ const void *__unbounded ptr; \ ptr = (p == pend ? &&end_of_pattern \ : jmptable[SWITCH_ENUM_CAST ((re_opcode_t) *p++)]); \ goto *ptr; \ } \ while (0) # define REF(x) \ &&label_##x # define JUMP_TABLE_TYPE const void *const # endif # define CASE(x) label_##x static JUMP_TABLE_TYPE jmptable[] = { REF (no_op), REF (succeed), REF (exactn), # ifdef MBS_SUPPORT REF (exactn_bin), # endif REF (anychar), REF (charset), REF (charset_not), REF (start_memory), REF (stop_memory), REF (duplicate), REF (begline), REF (endline), REF (begbuf), REF (endbuf), REF (jump), REF (jump_past_alt), REF (on_failure_jump), REF (on_failure_keep_string_jump), REF (pop_failure_jump), REF (maybe_pop_jump), REF (dummy_failure_jump), REF (push_dummy_failure), REF (succeed_n), REF (jump_n), REF (set_number_at), REF (wordchar), REF (notwordchar), REF (wordbeg), REF (wordend), REF (wordbound), REF (notwordbound) # ifdef emacs ,REF (before_dot), REF (at_dot), REF (after_dot), REF (syntaxspec), REF (notsyntaxspec) # endif }; #else # define NEXT \ break # define CASE(x) \ case x #endif DEBUG_PRINT1 ("\n\nEntering re_match_2.\n"); INIT_FAIL_STACK (); #ifdef MATCH_MAY_ALLOCATE /* Do not bother to initialize all the register variables if there are no groups in the pattern, as it takes a fair amount of time. If there are groups, we include space for register 0 (the whole pattern), even though we never use it, since it simplifies the array indexing. We should fix this. */ if (bufp->re_nsub) { regstart = REGEX_TALLOC (num_regs, const CHAR_T *); regend = REGEX_TALLOC (num_regs, const CHAR_T *); old_regstart = REGEX_TALLOC (num_regs, const CHAR_T *); old_regend = REGEX_TALLOC (num_regs, const CHAR_T *); best_regstart = REGEX_TALLOC (num_regs, const CHAR_T *); best_regend = REGEX_TALLOC (num_regs, const CHAR_T *); reg_info = REGEX_TALLOC (num_regs, PREFIX(register_info_type)); reg_dummy = REGEX_TALLOC (num_regs, const CHAR_T *); reg_info_dummy = REGEX_TALLOC (num_regs, PREFIX(register_info_type)); if (!(regstart && regend && old_regstart && old_regend && reg_info && best_regstart && best_regend && reg_dummy && reg_info_dummy)) { FREE_VARIABLES (); return -2; } } else { /* We must initialize all our variables to NULL, so that `FREE_VARIABLES' doesn't try to free them. */ regstart = regend = old_regstart = old_regend = best_regstart = best_regend = reg_dummy = NULL; reg_info = reg_info_dummy = (PREFIX(register_info_type) *) NULL; } #endif /* MATCH_MAY_ALLOCATE */ /* The starting position is bogus. */ #ifdef WCHAR if (pos < 0 || pos > csize1 + csize2) #else /* BYTE */ if (pos < 0 || pos > size1 + size2) #endif { FREE_VARIABLES (); return -1; } #ifdef WCHAR /* Allocate wchar_t array for string1 and string2 and fill them with converted string. */ if (string1 == NULL && string2 == NULL) { /* We need seting up buffers here. */ /* We must free wcs buffers in this function. */ cant_free_wcs_buf = 0; if (csize1 != 0) { string1 = REGEX_TALLOC (csize1 + 1, CHAR_T); mbs_offset1 = REGEX_TALLOC (csize1 + 1, int); is_binary = REGEX_TALLOC (csize1 + 1, char); if (!string1 || !mbs_offset1 || !is_binary) { FREE_VAR (string1); FREE_VAR (mbs_offset1); FREE_VAR (is_binary); return -2; } } if (csize2 != 0) { string2 = REGEX_TALLOC (csize2 + 1, CHAR_T); mbs_offset2 = REGEX_TALLOC (csize2 + 1, int); is_binary = REGEX_TALLOC (csize2 + 1, char); if (!string2 || !mbs_offset2 || !is_binary) { FREE_VAR (string1); FREE_VAR (mbs_offset1); FREE_VAR (string2); FREE_VAR (mbs_offset2); FREE_VAR (is_binary); return -2; } size2 = convert_mbs_to_wcs(string2, cstring2, csize2, mbs_offset2, is_binary); string2[size2] = L'\0'; /* for a sentinel */ FREE_VAR (is_binary); } } /* We need to cast pattern to (wchar_t*), because we casted this compiled pattern to (char*) in regex_compile. */ p = pattern = (CHAR_T*)bufp->buffer; pend = (CHAR_T*)(bufp->buffer + bufp->used); #endif /* WCHAR */ /* Initialize subexpression text positions to -1 to mark ones that no start_memory/stop_memory has been seen for. Also initialize the register information struct. */ for (mcnt = 1; (unsigned) mcnt < num_regs; mcnt++) { regstart[mcnt] = regend[mcnt] = old_regstart[mcnt] = old_regend[mcnt] = REG_UNSET_VALUE; REG_MATCH_NULL_STRING_P (reg_info[mcnt]) = MATCH_NULL_UNSET_VALUE; IS_ACTIVE (reg_info[mcnt]) = 0; MATCHED_SOMETHING (reg_info[mcnt]) = 0; EVER_MATCHED_SOMETHING (reg_info[mcnt]) = 0; } /* We move `string1' into `string2' if the latter's empty -- but not if `string1' is null. */ if (size2 == 0 && string1 != NULL) { string2 = string1; size2 = size1; string1 = 0; size1 = 0; #ifdef WCHAR mbs_offset2 = mbs_offset1; csize2 = csize1; mbs_offset1 = NULL; csize1 = 0; #endif } end1 = string1 + size1; end2 = string2 + size2; /* Compute where to stop matching, within the two strings. */ #ifdef WCHAR if (stop <= csize1) { mcnt = count_mbs_length(mbs_offset1, stop); end_match_1 = string1 + mcnt; end_match_2 = string2; } else { if (stop > csize1 + csize2) stop = csize1 + csize2; end_match_1 = end1; mcnt = count_mbs_length(mbs_offset2, stop-csize1); end_match_2 = string2 + mcnt; } if (mcnt < 0) { /* count_mbs_length return error. */ FREE_VARIABLES (); return -1; } #else if (stop <= size1) { end_match_1 = string1 + stop; end_match_2 = string2; } else { end_match_1 = end1; end_match_2 = string2 + stop - size1; } #endif /* WCHAR */ /* `p' scans through the pattern as `d' scans through the data. `dend' is the end of the input string that `d' points within. `d' is advanced into the following input string whenever necessary, but this happens before fetching; therefore, at the beginning of the loop, `d' can be pointing at the end of a string, but it cannot equal `string2'. */ #ifdef WCHAR if (size1 > 0 && pos <= csize1) { mcnt = count_mbs_length(mbs_offset1, pos); d = string1 + mcnt; dend = end_match_1; } else { mcnt = count_mbs_length(mbs_offset2, pos-csize1); d = string2 + mcnt; dend = end_match_2; } if (mcnt < 0) { /* count_mbs_length return error. */ FREE_VARIABLES (); return -1; } #else if (size1 > 0 && pos <= size1) { d = string1 + pos; dend = end_match_1; } else { d = string2 + pos - size1; dend = end_match_2; } #endif /* WCHAR */ DEBUG_PRINT1 ("The compiled pattern is:\n"); DEBUG_PRINT_COMPILED_PATTERN (bufp, p, pend); DEBUG_PRINT1 ("The string to match is: `"); DEBUG_PRINT_DOUBLE_STRING (d, string1, size1, string2, size2); DEBUG_PRINT1 ("'\n"); /* This loops over pattern commands. It exits by returning from the function if the match is complete, or it drops through if the match fails at this starting point in the input data. */ for (;;) { #ifdef _LIBC DEBUG_PRINT2 ("\n%p: ", p); #else DEBUG_PRINT2 ("\n0x%x: ", p); #endif #ifdef __GNUC__ NEXT; #else if (p == pend) #endif { #ifdef __GNUC__ end_of_pattern: #endif /* End of pattern means we might have succeeded. */ DEBUG_PRINT1 ("end of pattern ... "); /* If we haven't matched the entire string, and we want the longest match, try backtracking. */ if (d != end_match_2) { /* 1 if this match ends in the same string (string1 or string2) as the best previous match. */ boolean same_str_p = (FIRST_STRING_P (match_end) == MATCHING_IN_FIRST_STRING); /* 1 if this match is the best seen so far. */ boolean best_match_p; /* AIX compiler got confused when this was combined with the previous declaration. */ if (same_str_p) best_match_p = d > match_end; else best_match_p = !MATCHING_IN_FIRST_STRING; DEBUG_PRINT1 ("backtracking.\n"); if (!FAIL_STACK_EMPTY ()) { /* More failure points to try. */ /* If exceeds best match so far, save it. */ if (!best_regs_set || best_match_p) { best_regs_set = true; match_end = d; DEBUG_PRINT1 ("\nSAVING match as best so far.\n"); for (mcnt = 1; (unsigned) mcnt < num_regs; mcnt++) { best_regstart[mcnt] = regstart[mcnt]; best_regend[mcnt] = regend[mcnt]; } } goto fail; } /* If no failure points, don't restore garbage. And if last match is real best match, don't restore second best one. */ else if (best_regs_set && !best_match_p) { restore_best_regs: /* Restore best match. It may happen that `dend == end_match_1' while the restored d is in string2. For example, the pattern `x.*y.*z' against the strings `x-' and `y-z-', if the two strings are not consecutive in memory. */ DEBUG_PRINT1 ("Restoring best registers.\n"); d = match_end; dend = ((d >= string1 && d <= end1) ? end_match_1 : end_match_2); for (mcnt = 1; (unsigned) mcnt < num_regs; mcnt++) { regstart[mcnt] = best_regstart[mcnt]; regend[mcnt] = best_regend[mcnt]; } } } /* d != end_match_2 */ succeed_label: DEBUG_PRINT1 ("Accepting match.\n"); /* If caller wants register contents data back, do it. */ if (regs && !bufp->no_sub) { /* Have the register data arrays been allocated? */ if (bufp->regs_allocated == REGS_UNALLOCATED) { /* No. So allocate them with malloc. We need one extra element beyond `num_regs' for the `-1' marker GNU code uses. */ regs->num_regs = MAX (RE_NREGS, num_regs + 1); regs->start = TALLOC (regs->num_regs, regoff_t); regs->end = TALLOC (regs->num_regs, regoff_t); if (regs->start == NULL || regs->end == NULL) { FREE_VARIABLES (); return -2; } bufp->regs_allocated = REGS_REALLOCATE; } else if (bufp->regs_allocated == REGS_REALLOCATE) { /* Yes. If we need more elements than were already allocated, reallocate them. If we need fewer, just leave it alone. */ if (regs->num_regs < num_regs + 1) { regs->num_regs = num_regs + 1; RETALLOC (regs->start, regs->num_regs, regoff_t); RETALLOC (regs->end, regs->num_regs, regoff_t); if (regs->start == NULL || regs->end == NULL) { FREE_VARIABLES (); return -2; } } } else { /* These braces fend off a "empty body in an else-statement" warning under GCC when assert expands to nothing. */ assert (bufp->regs_allocated == REGS_FIXED); } /* Convert the pointer data in `regstart' and `regend' to indices. Register zero has to be set differently, since we haven't kept track of any info for it. */ if (regs->num_regs > 0) { regs->start[0] = pos; #ifdef WCHAR if (MATCHING_IN_FIRST_STRING) regs->end[0] = (mbs_offset1 != NULL ? mbs_offset1[d-string1] : 0); else regs->end[0] = csize1 + (mbs_offset2 != NULL ? mbs_offset2[d-string2] : 0); #else regs->end[0] = (MATCHING_IN_FIRST_STRING ? ((regoff_t) (d - string1)) : ((regoff_t) (d - string2 + size1))); #endif /* WCHAR */ } /* Go through the first `min (num_regs, regs->num_regs)' registers, since that is all we initialized. */ for (mcnt = 1; (unsigned) mcnt < MIN (num_regs, regs->num_regs); mcnt++) { if (REG_UNSET (regstart[mcnt]) || REG_UNSET (regend[mcnt])) regs->start[mcnt] = regs->end[mcnt] = -1; else { regs->start[mcnt] = (regoff_t) POINTER_TO_OFFSET (regstart[mcnt]); regs->end[mcnt] = (regoff_t) POINTER_TO_OFFSET (regend[mcnt]); } } /* If the regs structure we return has more elements than were in the pattern, set the extra elements to -1. If we (re)allocated the registers, this is the case, because we always allocate enough to have at least one -1 at the end. */ for (mcnt = num_regs; (unsigned) mcnt < regs->num_regs; mcnt++) regs->start[mcnt] = regs->end[mcnt] = -1; } /* regs && !bufp->no_sub */ DEBUG_PRINT4 ("%u failure points pushed, %u popped (%u remain).\n", nfailure_points_pushed, nfailure_points_popped, nfailure_points_pushed - nfailure_points_popped); DEBUG_PRINT2 ("%u registers pushed.\n", num_regs_pushed); #ifdef WCHAR if (MATCHING_IN_FIRST_STRING) mcnt = mbs_offset1 != NULL ? mbs_offset1[d-string1] : 0; else mcnt = (mbs_offset2 != NULL ? mbs_offset2[d-string2] : 0) + csize1; mcnt -= pos; #else mcnt = d - pos - (MATCHING_IN_FIRST_STRING ? string1 : string2 - size1); #endif /* WCHAR */ DEBUG_PRINT2 ("Returning %d from re_match_2.\n", mcnt); FREE_VARIABLES (); return mcnt; } #ifndef __GNUC__ /* Otherwise match next pattern command. */ switch (SWITCH_ENUM_CAST ((re_opcode_t) *p++)) { #endif /* Ignore these. Used to ignore the n of succeed_n's which currently have n == 0. */ CASE (no_op): DEBUG_PRINT1 ("EXECUTING no_op.\n"); NEXT; CASE (succeed): DEBUG_PRINT1 ("EXECUTING succeed.\n"); goto succeed_label; /* Match the next n pattern characters exactly. The following byte in the pattern defines n, and the n bytes after that are the characters to match. */ CASE (exactn): #ifdef MBS_SUPPORT CASE (exactn_bin): #endif mcnt = *p++; DEBUG_PRINT2 ("EXECUTING exactn %d.\n", mcnt); /* This is written out as an if-else so we don't waste time testing `translate' inside the loop. */ if (translate) { do { PREFETCH (); #ifdef WCHAR if (*d <= 0xff) { if ((UCHAR_T) translate[(unsigned char) *d++] != (UCHAR_T) *p++) goto fail; } else { if (*d++ != (CHAR_T) *p++) goto fail; } #else if ((UCHAR_T) translate[(unsigned char) *d++] != (UCHAR_T) *p++) goto fail; #endif /* WCHAR */ } while (--mcnt); } else { do { PREFETCH (); if (*d++ != (CHAR_T) *p++) goto fail; } while (--mcnt); } SET_REGS_MATCHED (); NEXT; /* Match any character except possibly a newline or a null. */ CASE (anychar): DEBUG_PRINT1 ("EXECUTING anychar.\n"); PREFETCH (); if ((!(bufp->syntax & RE_DOT_NEWLINE) && TRANSLATE (*d) == '\n') || (bufp->syntax & RE_DOT_NOT_NULL && TRANSLATE (*d) == '\000')) goto fail; SET_REGS_MATCHED (); DEBUG_PRINT2 (" Matched `%ld'.\n", (long int) *d); d++; NEXT; CASE (charset): CASE (charset_not): { register UCHAR_T c; #ifdef WCHAR unsigned int i, char_class_length, coll_symbol_length, equiv_class_length, ranges_length, chars_length, length; CHAR_T *workp, *workp2, *charset_top; #define WORK_BUFFER_SIZE 128 CHAR_T str_buf[WORK_BUFFER_SIZE]; # ifdef _LIBC uint32_t nrules; # endif /* _LIBC */ #endif /* WCHAR */ boolean not = (re_opcode_t) *(p - 1) == charset_not; DEBUG_PRINT2 ("EXECUTING charset%s.\n", not ? "_not" : ""); PREFETCH (); c = TRANSLATE (*d); /* The character to match. */ #ifdef WCHAR # ifdef _LIBC nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES); # endif /* _LIBC */ charset_top = p - 1; char_class_length = *p++; coll_symbol_length = *p++; equiv_class_length = *p++; ranges_length = *p++; chars_length = *p++; /* p points charset[6], so the address of the next instruction (charset[l+m+n+2o+k+p']) equals p[l+m+n+2*o+p'], where l=length of char_classes, m=length of collating_symbol, n=equivalence_class, o=length of char_range, p'=length of character. */ workp = p; /* Update p to indicate the next instruction. */ p += char_class_length + coll_symbol_length+ equiv_class_length + 2*ranges_length + chars_length; /* match with char_class? */ for (i = 0; i < char_class_length ; i += CHAR_CLASS_SIZE) { wctype_t wctype; uintptr_t alignedp = ((uintptr_t)workp + __alignof__(wctype_t) - 1) & ~(uintptr_t)(__alignof__(wctype_t) - 1); wctype = *((wctype_t*)alignedp); workp += CHAR_CLASS_SIZE; if (iswctype((wint_t)c, wctype)) goto char_set_matched; } /* match with collating_symbol? */ # ifdef _LIBC if (nrules != 0) { const unsigned char *extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB); for (workp2 = workp + coll_symbol_length ; workp < workp2 ; workp++) { int32_t *wextra; wextra = (int32_t*)(extra + *workp++); for (i = 0; i < *wextra; ++i) if (TRANSLATE(d[i]) != wextra[1 + i]) break; if (i == *wextra) { /* Update d, however d will be incremented at char_set_matched:, we decrement d here. */ d += i - 1; goto char_set_matched; } } } else /* (nrules == 0) */ # endif /* If we can't look up collation data, we use wcscoll instead. */ { for (workp2 = workp + coll_symbol_length ; workp < workp2 ;) { const CHAR_T *backup_d = d, *backup_dend = dend; length = wcslen (workp); /* If wcscoll(the collating symbol, whole string) > 0, any substring of the string never match with the collating symbol. */ if (wcscoll (workp, d) > 0) { workp += length + 1; continue; } /* First, we compare the collating symbol with the first character of the string. If it don't match, we add the next character to the compare buffer in turn. */ for (i = 0 ; i < WORK_BUFFER_SIZE-1 ; i++, d++) { int match; if (d == dend) { if (dend == end_match_2) break; d = string2; dend = end_match_2; } /* add next character to the compare buffer. */ str_buf[i] = TRANSLATE(*d); str_buf[i+1] = '\0'; match = wcscoll (workp, str_buf); if (match == 0) goto char_set_matched; if (match < 0) /* (str_buf > workp) indicate (str_buf + X > workp), because for all X (str_buf + X > str_buf). So we don't need continue this loop. */ break; /* Otherwise(str_buf < workp), (str_buf+next_character) may equals (workp). So we continue this loop. */ } /* not matched */ d = backup_d; dend = backup_dend; workp += length + 1; } } /* match with equivalence_class? */ # ifdef _LIBC if (nrules != 0) { const CHAR_T *backup_d = d, *backup_dend = dend; /* Try to match the equivalence class against those known to the collate implementation. */ const int32_t *table; const int32_t *weights; const int32_t *extra; const int32_t *indirect; int32_t idx, idx2; wint_t *cp; size_t len; /* This #include defines a local function! */ # include <locale/weightwc.h> table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEWC); weights = (const wint_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_WEIGHTWC); extra = (const wint_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_EXTRAWC); indirect = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_INDIRECTWC); /* Write 1 collating element to str_buf, and get its index. */ idx2 = 0; for (i = 0 ; idx2 == 0 && i < WORK_BUFFER_SIZE - 1; i++) { cp = (wint_t*)str_buf; if (d == dend) { if (dend == end_match_2) break; d = string2; dend = end_match_2; } str_buf[i] = TRANSLATE(*(d+i)); str_buf[i+1] = '\0'; /* sentinel */ idx2 = findidx ((const wint_t**)&cp); } /* Update d, however d will be incremented at char_set_matched:, we decrement d here. */ d = backup_d + ((wchar_t*)cp - (wchar_t*)str_buf - 1); if (d >= dend) { if (dend == end_match_2) d = dend; else { d = string2; dend = end_match_2; } } len = weights[idx2]; for (workp2 = workp + equiv_class_length ; workp < workp2 ; workp++) { idx = (int32_t)*workp; /* We already checked idx != 0 in regex_compile. */ if (idx2 != 0 && len == weights[idx]) { int cnt = 0; while (cnt < len && (weights[idx + 1 + cnt] == weights[idx2 + 1 + cnt])) ++cnt; if (cnt == len) goto char_set_matched; } } /* not matched */ d = backup_d; dend = backup_dend; } else /* (nrules == 0) */ # endif /* If we can't look up collation data, we use wcscoll instead. */ { for (workp2 = workp + equiv_class_length ; workp < workp2 ;) { const CHAR_T *backup_d = d, *backup_dend = dend; length = wcslen (workp); /* If wcscoll(the collating symbol, whole string) > 0, any substring of the string never match with the collating symbol. */ if (wcscoll (workp, d) > 0) { workp += length + 1; break; } /* First, we compare the equivalence class with the first character of the string. If it don't match, we add the next character to the compare buffer in turn. */ for (i = 0 ; i < WORK_BUFFER_SIZE - 1 ; i++, d++) { int match; if (d == dend) { if (dend == end_match_2) break; d = string2; dend = end_match_2; } /* add next character to the compare buffer. */ str_buf[i] = TRANSLATE(*d); str_buf[i+1] = '\0'; match = wcscoll (workp, str_buf); if (match == 0) goto char_set_matched; if (match < 0) /* (str_buf > workp) indicate (str_buf + X > workp), because for all X (str_buf + X > str_buf). So we don't need continue this loop. */ break; /* Otherwise(str_buf < workp), (str_buf+next_character) may equals (workp). So we continue this loop. */ } /* not matched */ d = backup_d; dend = backup_dend; workp += length + 1; } } /* match with char_range? */ # ifdef _LIBC if (nrules != 0) { uint32_t collseqval; const char *collseq = (const char *) _NL_CURRENT(LC_COLLATE, _NL_COLLATE_COLLSEQWC); collseqval = collseq_table_lookup (collseq, c); for (; workp < p - chars_length ;) { uint32_t start_val, end_val; /* We already compute the collation sequence value of the characters (or collating symbols). */ start_val = (uint32_t) *workp++; /* range_start */ end_val = (uint32_t) *workp++; /* range_end */ if (start_val <= collseqval && collseqval <= end_val) goto char_set_matched; } } else # endif { /* We set range_start_char at str_buf[0], range_end_char at str_buf[4], and compared char at str_buf[2]. */ str_buf[1] = 0; str_buf[2] = c; str_buf[3] = 0; str_buf[5] = 0; for (; workp < p - chars_length ;) { wchar_t *range_start_char, *range_end_char; /* match if (range_start_char <= c <= range_end_char). */ /* If range_start(or end) < 0, we assume -range_start(end) is the offset of the collating symbol which is specified as the character of the range start(end). */ /* range_start */ if (*workp < 0) range_start_char = charset_top - (*workp++); else { str_buf[0] = *workp++; range_start_char = str_buf; } /* range_end */ if (*workp < 0) range_end_char = charset_top - (*workp++); else { str_buf[4] = *workp++; range_end_char = str_buf + 4; } if (wcscoll (range_start_char, str_buf+2) <= 0 && wcscoll (str_buf+2, range_end_char) <= 0) goto char_set_matched; } } /* match with char? */ for (; workp < p ; workp++) if (c == *workp) goto char_set_matched; not = !not; char_set_matched: if (not) goto fail; #else /* Cast to `unsigned' instead of `unsigned char' in case the bit list is a full 32 bytes long. */ if (c < (unsigned) (*p * BYTEWIDTH) && p[1 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH))) not = !not; p += 1 + *p; if (!not) goto fail; #undef WORK_BUFFER_SIZE #endif /* WCHAR */ SET_REGS_MATCHED (); d++; NEXT; } /* The beginning of a group is represented by start_memory. The arguments are the register number in the next byte, and the number of groups inner to this one in the next. The text matched within the group is recorded (in the internal registers data structure) under the register number. */ CASE (start_memory): DEBUG_PRINT3 ("EXECUTING start_memory %ld (%ld):\n", (long int) *p, (long int) p[1]); /* Find out if this group can match the empty string. */ p1 = p; /* To send to group_match_null_string_p. */ if (REG_MATCH_NULL_STRING_P (reg_info[*p]) == MATCH_NULL_UNSET_VALUE) REG_MATCH_NULL_STRING_P (reg_info[*p]) = PREFIX(group_match_null_string_p) (&p1, pend, reg_info); /* Save the position in the string where we were the last time we were at this open-group operator in case the group is operated upon by a repetition operator, e.g., with `(a*)*b' against `ab'; then we want to ignore where we are now in the string in case this attempt to match fails. */ old_regstart[*p] = REG_MATCH_NULL_STRING_P (reg_info[*p]) ? REG_UNSET (regstart[*p]) ? d : regstart[*p] : regstart[*p]; DEBUG_PRINT2 (" old_regstart: %d\n", POINTER_TO_OFFSET (old_regstart[*p])); regstart[*p] = d; DEBUG_PRINT2 (" regstart: %d\n", POINTER_TO_OFFSET (regstart[*p])); IS_ACTIVE (reg_info[*p]) = 1; MATCHED_SOMETHING (reg_info[*p]) = 0; /* Clear this whenever we change the register activity status. */ set_regs_matched_done = 0; /* This is the new highest active register. */ highest_active_reg = *p; /* If nothing was active before, this is the new lowest active register. */ if (lowest_active_reg == NO_LOWEST_ACTIVE_REG) lowest_active_reg = *p; /* Move past the register number and inner group count. */ p += 2; just_past_start_mem = p; NEXT; /* The stop_memory opcode represents the end of a group. Its arguments are the same as start_memory's: the register number, and the number of inner groups. */ CASE (stop_memory): DEBUG_PRINT3 ("EXECUTING stop_memory %ld (%ld):\n", (long int) *p, (long int) p[1]); /* We need to save the string position the last time we were at this close-group operator in case the group is operated upon by a repetition operator, e.g., with `((a*)*(b*)*)*' against `aba'; then we want to ignore where we are now in the string in case this attempt to match fails. */ old_regend[*p] = REG_MATCH_NULL_STRING_P (reg_info[*p]) ? REG_UNSET (regend[*p]) ? d : regend[*p] : regend[*p]; DEBUG_PRINT2 (" old_regend: %d\n", POINTER_TO_OFFSET (old_regend[*p])); regend[*p] = d; DEBUG_PRINT2 (" regend: %d\n", POINTER_TO_OFFSET (regend[*p])); /* This register isn't active anymore. */ IS_ACTIVE (reg_info[*p]) = 0; /* Clear this whenever we change the register activity status. */ set_regs_matched_done = 0; /* If this was the only register active, nothing is active anymore. */ if (lowest_active_reg == highest_active_reg) { lowest_active_reg = NO_LOWEST_ACTIVE_REG; highest_active_reg = NO_HIGHEST_ACTIVE_REG; } else { /* We must scan for the new highest active register, since it isn't necessarily one less than now: consider (a(b)c(d(e)f)g). When group 3 ends, after the f), the new highest active register is 1. */ UCHAR_T r = *p - 1; while (r > 0 && !IS_ACTIVE (reg_info[r])) r--; /* If we end up at register zero, that means that we saved the registers as the result of an `on_failure_jump', not a `start_memory', and we jumped to past the innermost `stop_memory'. For example, in ((.)*) we save registers 1 and 2 as a result of the *, but when we pop back to the second ), we are at the stop_memory 1. Thus, nothing is active. */ if (r == 0) { lowest_active_reg = NO_LOWEST_ACTIVE_REG; highest_active_reg = NO_HIGHEST_ACTIVE_REG; } else highest_active_reg = r; } /* If just failed to match something this time around with a group that's operated on by a repetition operator, try to force exit from the ``loop'', and restore the register information for this group that we had before trying this last match. */ if ((!MATCHED_SOMETHING (reg_info[*p]) || just_past_start_mem == p - 1) && (p + 2) < pend) { boolean is_a_jump_n = false; p1 = p + 2; mcnt = 0; switch ((re_opcode_t) *p1++) { case jump_n: is_a_jump_n = true; case pop_failure_jump: case maybe_pop_jump: case jump: case dummy_failure_jump: EXTRACT_NUMBER_AND_INCR (mcnt, p1); if (is_a_jump_n) p1 += OFFSET_ADDRESS_SIZE; break; default: /* do nothing */ ; } p1 += mcnt; /* If the next operation is a jump backwards in the pattern to an on_failure_jump right before the start_memory corresponding to this stop_memory, exit from the loop by forcing a failure after pushing on the stack the on_failure_jump's jump in the pattern, and d. */ if (mcnt < 0 && (re_opcode_t) *p1 == on_failure_jump && (re_opcode_t) p1[1+OFFSET_ADDRESS_SIZE] == start_memory && p1[2+OFFSET_ADDRESS_SIZE] == *p) { /* If this group ever matched anything, then restore what its registers were before trying this last failed match, e.g., with `(a*)*b' against `ab' for regstart[1], and, e.g., with `((a*)*(b*)*)*' against `aba' for regend[3]. Also restore the registers for inner groups for, e.g., `((a*)(b*))*' against `aba' (register 3 would otherwise get trashed). */ if (EVER_MATCHED_SOMETHING (reg_info[*p])) { unsigned r; EVER_MATCHED_SOMETHING (reg_info[*p]) = 0; /* Restore this and inner groups' (if any) registers. */ for (r = *p; r < (unsigned) *p + (unsigned) *(p + 1); r++) { regstart[r] = old_regstart[r]; /* xx why this test? */ if (old_regend[r] >= regstart[r]) regend[r] = old_regend[r]; } } p1++; EXTRACT_NUMBER_AND_INCR (mcnt, p1); PUSH_FAILURE_POINT (p1 + mcnt, d, -2); goto fail; } } /* Move past the register number and the inner group count. */ p += 2; NEXT; /* \<digit> has been turned into a `duplicate' command which is followed by the numeric value of <digit> as the register number. */ CASE (duplicate): { register const CHAR_T *d2, *dend2; int regno = *p++; /* Get which register to match against. */ DEBUG_PRINT2 ("EXECUTING duplicate %d.\n", regno); /* Can't back reference a group which we've never matched. */ if (REG_UNSET (regstart[regno]) || REG_UNSET (regend[regno])) goto fail; /* Where in input to try to start matching. */ d2 = regstart[regno]; /* Where to stop matching; if both the place to start and the place to stop matching are in the same string, then set to the place to stop, otherwise, for now have to use the end of the first string. */ dend2 = ((FIRST_STRING_P (regstart[regno]) == FIRST_STRING_P (regend[regno])) ? regend[regno] : end_match_1); for (;;) { /* If necessary, advance to next segment in register contents. */ while (d2 == dend2) { if (dend2 == end_match_2) break; if (dend2 == regend[regno]) break; /* End of string1 => advance to string2. */ d2 = string2; dend2 = regend[regno]; } /* At end of register contents => success */ if (d2 == dend2) break; /* If necessary, advance to next segment in data. */ PREFETCH (); /* How many characters left in this segment to match. */ mcnt = dend - d; /* Want how many consecutive characters we can match in one shot, so, if necessary, adjust the count. */ if (mcnt > dend2 - d2) mcnt = dend2 - d2; /* Compare that many; failure if mismatch, else move past them. */ if (translate ? PREFIX(bcmp_translate) (d, d2, mcnt, translate) : memcmp (d, d2, mcnt*sizeof(UCHAR_T))) goto fail; d += mcnt, d2 += mcnt; /* Do this because we've match some characters. */ SET_REGS_MATCHED (); } } NEXT; /* begline matches the empty string at the beginning of the string (unless `not_bol' is set in `bufp'), and, if `newline_anchor' is set, after newlines. */ CASE (begline): DEBUG_PRINT1 ("EXECUTING begline.\n"); if (AT_STRINGS_BEG (d)) { if (!bufp->not_bol) { NEXT; } } else if (d[-1] == '\n' && bufp->newline_anchor) { NEXT; } /* In all other cases, we fail. */ goto fail; /* endline is the dual of begline. */ CASE (endline): DEBUG_PRINT1 ("EXECUTING endline.\n"); if (AT_STRINGS_END (d)) { if (!bufp->not_eol) { NEXT; } } /* We have to ``prefetch'' the next character. */ else if ((d == end1 ? *string2 : *d) == '\n' && bufp->newline_anchor) { NEXT; } goto fail; /* Match at the very beginning of the data. */ CASE (begbuf): DEBUG_PRINT1 ("EXECUTING begbuf.\n"); if (AT_STRINGS_BEG (d)) { NEXT; } goto fail; /* Match at the very end of the data. */ CASE (endbuf): DEBUG_PRINT1 ("EXECUTING endbuf.\n"); if (AT_STRINGS_END (d)) { NEXT; } goto fail; /* on_failure_keep_string_jump is used to optimize `.*\n'. It pushes NULL as the value for the string on the stack. Then `pop_failure_point' will keep the current value for the string, instead of restoring it. To see why, consider matching `foo\nbar' against `.*\n'. The .* matches the foo; then the . fails against the \n. But the next thing we want to do is match the \n against the \n; if we restored the string value, we would be back at the foo. Because this is used only in specific cases, we don't need to check all the things that `on_failure_jump' does, to make sure the right things get saved on the stack. Hence we don't share its code. The only reason to push anything on the stack at all is that otherwise we would have to change `anychar's code to do something besides goto fail in this case; that seems worse than this. */ CASE (on_failure_keep_string_jump): DEBUG_PRINT1 ("EXECUTING on_failure_keep_string_jump"); EXTRACT_NUMBER_AND_INCR (mcnt, p); #ifdef _LIBC DEBUG_PRINT3 (" %d (to %p):\n", mcnt, p + mcnt); #else DEBUG_PRINT3 (" %d (to 0x%x):\n", mcnt, p + mcnt); #endif PUSH_FAILURE_POINT (p + mcnt, NULL, -2); NEXT; /* Uses of on_failure_jump: Each alternative starts with an on_failure_jump that points to the beginning of the next alternative. Each alternative except the last ends with a jump that in effect jumps past the rest of the alternatives. (They really jump to the ending jump of the following alternative, because tensioning these jumps is a hassle.) Repeats start with an on_failure_jump that points past both the repetition text and either the following jump or pop_failure_jump back to this on_failure_jump. */ CASE (on_failure_jump): on_failure: DEBUG_PRINT1 ("EXECUTING on_failure_jump"); EXTRACT_NUMBER_AND_INCR (mcnt, p); #ifdef _LIBC DEBUG_PRINT3 (" %d (to %p)", mcnt, p + mcnt); #else DEBUG_PRINT3 (" %d (to 0x%x)", mcnt, p + mcnt); #endif /* If this on_failure_jump comes right before a group (i.e., the original * applied to a group), save the information for that group and all inner ones, so that if we fail back to this point, the group's information will be correct. For example, in \(a*\)*\1, we need the preceding group, and in \(zz\(a*\)b*\)\2, we need the inner group. */ /* We can't use `p' to check ahead because we push a failure point to `p + mcnt' after we do this. */ p1 = p; /* We need to skip no_op's before we look for the start_memory in case this on_failure_jump is happening as the result of a completed succeed_n, as in \(a\)\{1,3\}b\1 against aba. */ while (p1 < pend && (re_opcode_t) *p1 == no_op) p1++; if (p1 < pend && (re_opcode_t) *p1 == start_memory) { /* We have a new highest active register now. This will get reset at the start_memory we are about to get to, but we will have saved all the registers relevant to this repetition op, as described above. */ highest_active_reg = *(p1 + 1) + *(p1 + 2); if (lowest_active_reg == NO_LOWEST_ACTIVE_REG) lowest_active_reg = *(p1 + 1); } DEBUG_PRINT1 (":\n"); PUSH_FAILURE_POINT (p + mcnt, d, -2); NEXT; /* A smart repeat ends with `maybe_pop_jump'. We change it to either `pop_failure_jump' or `jump'. */ CASE (maybe_pop_jump): EXTRACT_NUMBER_AND_INCR (mcnt, p); DEBUG_PRINT2 ("EXECUTING maybe_pop_jump %d.\n", mcnt); { register UCHAR_T *p2 = p; /* Compare the beginning of the repeat with what in the pattern follows its end. If we can establish that there is nothing that they would both match, i.e., that we would have to backtrack because of (as in, e.g., `a*a') then we can change to pop_failure_jump, because we'll never have to backtrack. This is not true in the case of alternatives: in `(a|ab)*' we do need to backtrack to the `ab' alternative (e.g., if the string was `ab'). But instead of trying to detect that here, the alternative has put on a dummy failure point which is what we will end up popping. */ /* Skip over open/close-group commands. If what follows this loop is a ...+ construct, look at what begins its body, since we will have to match at least one of that. */ while (1) { if (p2 + 2 < pend && ((re_opcode_t) *p2 == stop_memory || (re_opcode_t) *p2 == start_memory)) p2 += 3; else if (p2 + 2 + 2 * OFFSET_ADDRESS_SIZE < pend && (re_opcode_t) *p2 == dummy_failure_jump) p2 += 2 + 2 * OFFSET_ADDRESS_SIZE; else break; } p1 = p + mcnt; /* p1[0] ... p1[2] are the `on_failure_jump' corresponding to the `maybe_finalize_jump' of this case. Examine what follows. */ /* If we're at the end of the pattern, we can change. */ if (p2 == pend) { /* Consider what happens when matching ":\(.*\)" against ":/". I don't really understand this code yet. */ p[-(1+OFFSET_ADDRESS_SIZE)] = (UCHAR_T) pop_failure_jump; DEBUG_PRINT1 (" End of pattern: change to `pop_failure_jump'.\n"); } else if ((re_opcode_t) *p2 == exactn #ifdef MBS_SUPPORT || (re_opcode_t) *p2 == exactn_bin #endif || (bufp->newline_anchor && (re_opcode_t) *p2 == endline)) { register UCHAR_T c = *p2 == (UCHAR_T) endline ? '\n' : p2[2]; if (((re_opcode_t) p1[1+OFFSET_ADDRESS_SIZE] == exactn #ifdef MBS_SUPPORT || (re_opcode_t) p1[1+OFFSET_ADDRESS_SIZE] == exactn_bin #endif ) && p1[3+OFFSET_ADDRESS_SIZE] != c) { p[-(1+OFFSET_ADDRESS_SIZE)] = (UCHAR_T) pop_failure_jump; #ifdef WCHAR DEBUG_PRINT3 (" %C != %C => pop_failure_jump.\n", (wint_t) c, (wint_t) p1[3+OFFSET_ADDRESS_SIZE]); #else DEBUG_PRINT3 (" %c != %c => pop_failure_jump.\n", (char) c, (char) p1[3+OFFSET_ADDRESS_SIZE]); #endif } #ifndef WCHAR else if ((re_opcode_t) p1[3] == charset || (re_opcode_t) p1[3] == charset_not) { int not = (re_opcode_t) p1[3] == charset_not; if (c < (unsigned) (p1[4] * BYTEWIDTH) && p1[5 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH))) not = !not; /* `not' is equal to 1 if c would match, which means that we can't change to pop_failure_jump. */ if (!not) { p[-3] = (unsigned char) pop_failure_jump; DEBUG_PRINT1 (" No match => pop_failure_jump.\n"); } } #endif /* not WCHAR */ } #ifndef WCHAR else if ((re_opcode_t) *p2 == charset) { /* We win if the first character of the loop is not part of the charset. */ if ((re_opcode_t) p1[3] == exactn && ! ((int) p2[1] * BYTEWIDTH > (int) p1[5] && (p2[2 + p1[5] / BYTEWIDTH] & (1 << (p1[5] % BYTEWIDTH))))) { p[-3] = (unsigned char) pop_failure_jump; DEBUG_PRINT1 (" No match => pop_failure_jump.\n"); } else if ((re_opcode_t) p1[3] == charset_not) { int idx; /* We win if the charset_not inside the loop lists every character listed in the charset after. */ for (idx = 0; idx < (int) p2[1]; idx++) if (! (p2[2 + idx] == 0 || (idx < (int) p1[4] && ((p2[2 + idx] & ~ p1[5 + idx]) == 0)))) break; if (idx == p2[1]) { p[-3] = (unsigned char) pop_failure_jump; DEBUG_PRINT1 (" No match => pop_failure_jump.\n"); } } else if ((re_opcode_t) p1[3] == charset) { int idx; /* We win if the charset inside the loop has no overlap with the one after the loop. */ for (idx = 0; idx < (int) p2[1] && idx < (int) p1[4]; idx++) if ((p2[2 + idx] & p1[5 + idx]) != 0) break; if (idx == p2[1] || idx == p1[4]) { p[-3] = (unsigned char) pop_failure_jump; DEBUG_PRINT1 (" No match => pop_failure_jump.\n"); } } } #endif /* not WCHAR */ } p -= OFFSET_ADDRESS_SIZE; /* Point at relative address again. */ if ((re_opcode_t) p[-1] != pop_failure_jump) { p[-1] = (UCHAR_T) jump; DEBUG_PRINT1 (" Match => jump.\n"); goto unconditional_jump; } /* Note fall through. */ /* The end of a simple repeat has a pop_failure_jump back to its matching on_failure_jump, where the latter will push a failure point. The pop_failure_jump takes off failure points put on by this pop_failure_jump's matching on_failure_jump; we got through the pattern to here from the matching on_failure_jump, so didn't fail. */ CASE (pop_failure_jump): { /* We need to pass separate storage for the lowest and highest registers, even though we don't care about the actual values. Otherwise, we will restore only one register from the stack, since lowest will == highest in `pop_failure_point'. */ active_reg_t dummy_low_reg, dummy_high_reg; UCHAR_T *pdummy = NULL; const CHAR_T *sdummy = NULL; DEBUG_PRINT1 ("EXECUTING pop_failure_jump.\n"); POP_FAILURE_POINT (sdummy, pdummy, dummy_low_reg, dummy_high_reg, reg_dummy, reg_dummy, reg_info_dummy); } /* Note fall through. */ unconditional_jump: #ifdef _LIBC DEBUG_PRINT2 ("\n%p: ", p); #else DEBUG_PRINT2 ("\n0x%x: ", p); #endif /* Note fall through. */ /* Unconditionally jump (without popping any failure points). */ CASE (jump): EXTRACT_NUMBER_AND_INCR (mcnt, p); /* Get the amount to jump. */ DEBUG_PRINT2 ("EXECUTING jump %d ", mcnt); p += mcnt; /* Do the jump. */ #ifdef _LIBC DEBUG_PRINT2 ("(to %p).\n", p); #else DEBUG_PRINT2 ("(to 0x%x).\n", p); #endif NEXT; /* We need this opcode so we can detect where alternatives end in `group_match_null_string_p' et al. */ CASE (jump_past_alt): DEBUG_PRINT1 ("EXECUTING jump_past_alt.\n"); goto unconditional_jump; /* Normally, the on_failure_jump pushes a failure point, which then gets popped at pop_failure_jump. We will end up at pop_failure_jump, also, and with a pattern of, say, `a+', we are skipping over the on_failure_jump, so we have to push something meaningless for pop_failure_jump to pop. */ CASE (dummy_failure_jump): DEBUG_PRINT1 ("EXECUTING dummy_failure_jump.\n"); /* It doesn't matter what we push for the string here. What the code at `fail' tests is the value for the pattern. */ PUSH_FAILURE_POINT (NULL, NULL, -2); goto unconditional_jump; /* At the end of an alternative, we need to push a dummy failure point in case we are followed by a `pop_failure_jump', because we don't want the failure point for the alternative to be popped. For example, matching `(a|ab)*' against `aab' requires that we match the `ab' alternative. */ CASE (push_dummy_failure): DEBUG_PRINT1 ("EXECUTING push_dummy_failure.\n"); /* See comments just above at `dummy_failure_jump' about the two zeroes. */ PUSH_FAILURE_POINT (NULL, NULL, -2); NEXT; /* Have to succeed matching what follows at least n times. After that, handle like `on_failure_jump'. */ CASE (succeed_n): EXTRACT_NUMBER (mcnt, p + OFFSET_ADDRESS_SIZE); DEBUG_PRINT2 ("EXECUTING succeed_n %d.\n", mcnt); assert (mcnt >= 0); /* Originally, this is how many times we HAVE to succeed. */ if (mcnt > 0) { mcnt--; p += OFFSET_ADDRESS_SIZE; STORE_NUMBER_AND_INCR (p, mcnt); #ifdef _LIBC DEBUG_PRINT3 (" Setting %p to %d.\n", p - OFFSET_ADDRESS_SIZE , mcnt); #else DEBUG_PRINT3 (" Setting 0x%x to %d.\n", p - OFFSET_ADDRESS_SIZE , mcnt); #endif } else if (mcnt == 0) { #ifdef _LIBC DEBUG_PRINT2 (" Setting two bytes from %p to no_op.\n", p + OFFSET_ADDRESS_SIZE); #else DEBUG_PRINT2 (" Setting two bytes from 0x%x to no_op.\n", p + OFFSET_ADDRESS_SIZE); #endif /* _LIBC */ #ifdef WCHAR p[1] = (UCHAR_T) no_op; #else p[2] = (UCHAR_T) no_op; p[3] = (UCHAR_T) no_op; #endif /* WCHAR */ goto on_failure; } NEXT; CASE (jump_n): EXTRACT_NUMBER (mcnt, p + OFFSET_ADDRESS_SIZE); DEBUG_PRINT2 ("EXECUTING jump_n %d.\n", mcnt); /* Originally, this is how many times we CAN jump. */ if (mcnt) { mcnt--; STORE_NUMBER (p + OFFSET_ADDRESS_SIZE, mcnt); #ifdef _LIBC DEBUG_PRINT3 (" Setting %p to %d.\n", p + OFFSET_ADDRESS_SIZE, mcnt); #else DEBUG_PRINT3 (" Setting 0x%x to %d.\n", p + OFFSET_ADDRESS_SIZE, mcnt); #endif /* _LIBC */ goto unconditional_jump; } /* If don't have to jump any more, skip over the rest of command. */ else p += 2 * OFFSET_ADDRESS_SIZE; NEXT; CASE (set_number_at): { DEBUG_PRINT1 ("EXECUTING set_number_at.\n"); EXTRACT_NUMBER_AND_INCR (mcnt, p); p1 = p + mcnt; EXTRACT_NUMBER_AND_INCR (mcnt, p); #ifdef _LIBC DEBUG_PRINT3 (" Setting %p to %d.\n", p1, mcnt); #else DEBUG_PRINT3 (" Setting 0x%x to %d.\n", p1, mcnt); #endif STORE_NUMBER (p1, mcnt); NEXT; } #if 0 /* The DEC Alpha C compiler 3.x generates incorrect code for the test WORDCHAR_P (d - 1) != WORDCHAR_P (d) in the expansion of AT_WORD_BOUNDARY, so this code is disabled. Expanding the macro and introducing temporary variables works around the bug. */ CASE (wordbound): DEBUG_PRINT1 ("EXECUTING wordbound.\n"); if (AT_WORD_BOUNDARY (d)) { NEXT; } goto fail; CASE (notwordbound): DEBUG_PRINT1 ("EXECUTING notwordbound.\n"); if (AT_WORD_BOUNDARY (d)) goto fail; NEXT; #else CASE (wordbound): { boolean prevchar, thischar; DEBUG_PRINT1 ("EXECUTING wordbound.\n"); if (AT_STRINGS_BEG (d) || AT_STRINGS_END (d)) { NEXT; } prevchar = WORDCHAR_P (d - 1); thischar = WORDCHAR_P (d); if (prevchar != thischar) { NEXT; } goto fail; } CASE (notwordbound): { boolean prevchar, thischar; DEBUG_PRINT1 ("EXECUTING notwordbound.\n"); if (AT_STRINGS_BEG (d) || AT_STRINGS_END (d)) goto fail; prevchar = WORDCHAR_P (d - 1); thischar = WORDCHAR_P (d); if (prevchar != thischar) goto fail; NEXT; } #endif CASE (wordbeg): DEBUG_PRINT1 ("EXECUTING wordbeg.\n"); if (!AT_STRINGS_END (d) && WORDCHAR_P (d) && (AT_STRINGS_BEG (d) || !WORDCHAR_P (d - 1))) { NEXT; } goto fail; CASE (wordend): DEBUG_PRINT1 ("EXECUTING wordend.\n"); if (!AT_STRINGS_BEG (d) && WORDCHAR_P (d - 1) && (AT_STRINGS_END (d) || !WORDCHAR_P (d))) { NEXT; } goto fail; #ifdef emacs CASE (before_dot): DEBUG_PRINT1 ("EXECUTING before_dot.\n"); if (PTR_CHAR_POS ((unsigned char *) d) >= point) goto fail; NEXT; CASE (at_dot): DEBUG_PRINT1 ("EXECUTING at_dot.\n"); if (PTR_CHAR_POS ((unsigned char *) d) != point) goto fail; NEXT; CASE (after_dot): DEBUG_PRINT1 ("EXECUTING after_dot.\n"); if (PTR_CHAR_POS ((unsigned char *) d) <= point) goto fail; NEXT; CASE (syntaxspec): DEBUG_PRINT2 ("EXECUTING syntaxspec %d.\n", mcnt); mcnt = *p++; goto matchsyntax; CASE (wordchar): DEBUG_PRINT1 ("EXECUTING Emacs wordchar.\n"); mcnt = (int) Sword; matchsyntax: PREFETCH (); /* Can't use *d++ here; SYNTAX may be an unsafe macro. */ d++; if (SYNTAX (d[-1]) != (enum syntaxcode) mcnt) goto fail; SET_REGS_MATCHED (); NEXT; CASE (notsyntaxspec): DEBUG_PRINT2 ("EXECUTING notsyntaxspec %d.\n", mcnt); mcnt = *p++; goto matchnotsyntax; CASE (notwordchar): DEBUG_PRINT1 ("EXECUTING Emacs notwordchar.\n"); mcnt = (int) Sword; matchnotsyntax: PREFETCH (); /* Can't use *d++ here; SYNTAX may be an unsafe macro. */ d++; if (SYNTAX (d[-1]) == (enum syntaxcode) mcnt) goto fail; SET_REGS_MATCHED (); NEXT; #else /* not emacs */ CASE (wordchar): DEBUG_PRINT1 ("EXECUTING non-Emacs wordchar.\n"); PREFETCH (); if (!WORDCHAR_P (d)) goto fail; SET_REGS_MATCHED (); d++; NEXT; CASE (notwordchar): DEBUG_PRINT1 ("EXECUTING non-Emacs notwordchar.\n"); PREFETCH (); if (WORDCHAR_P (d)) goto fail; SET_REGS_MATCHED (); d++; NEXT; #endif /* not emacs */ #ifndef __GNUC__ default: abort (); } continue; /* Successfully executed one pattern command; keep going. */ #endif /* We goto here if a matching operation fails. */ fail: if (!FAIL_STACK_EMPTY ()) { /* A restart point is known. Restore to that state. */ DEBUG_PRINT1 ("\nFAIL:\n"); POP_FAILURE_POINT (d, p, lowest_active_reg, highest_active_reg, regstart, regend, reg_info); /* If this failure point is a dummy, try the next one. */ if (!p) goto fail; /* If we failed to the end of the pattern, don't examine *p. */ assert (p <= pend); if (p < pend) { boolean is_a_jump_n = false; /* If failed to a backwards jump that's part of a repetition loop, need to pop this failure point and use the next one. */ switch ((re_opcode_t) *p) { case jump_n: is_a_jump_n = true; case maybe_pop_jump: case pop_failure_jump: case jump: p1 = p + 1; EXTRACT_NUMBER_AND_INCR (mcnt, p1); p1 += mcnt; if ((is_a_jump_n && (re_opcode_t) *p1 == succeed_n) || (!is_a_jump_n && (re_opcode_t) *p1 == on_failure_jump)) goto fail; break; default: /* do nothing */ ; } } if (d >= string1 && d <= end1) dend = end_match_1; } else break; /* Matching at this starting point really fails. */ } /* for (;;) */ if (best_regs_set) goto restore_best_regs; FREE_VARIABLES (); return -1; /* Failure to match. */ } /* re_match_2 */ /* Subroutine definitions for re_match_2. */ /* We are passed P pointing to a register number after a start_memory. Return true if the pattern up to the corresponding stop_memory can match the empty string, and false otherwise. If we find the matching stop_memory, sets P to point to one past its number. Otherwise, sets P to an undefined byte less than or equal to END. We don't handle duplicates properly (yet). */ static boolean PREFIX(group_match_null_string_p) (p, end, reg_info) UCHAR_T **p, *end; PREFIX(register_info_type) *reg_info; { int mcnt; /* Point to after the args to the start_memory. */ UCHAR_T *p1 = *p + 2; while (p1 < end) { /* Skip over opcodes that can match nothing, and return true or false, as appropriate, when we get to one that can't, or to the matching stop_memory. */ switch ((re_opcode_t) *p1) { /* Could be either a loop or a series of alternatives. */ case on_failure_jump: p1++; EXTRACT_NUMBER_AND_INCR (mcnt, p1); /* If the next operation is not a jump backwards in the pattern. */ if (mcnt >= 0) { /* Go through the on_failure_jumps of the alternatives, seeing if any of the alternatives cannot match nothing. The last alternative starts with only a jump, whereas the rest start with on_failure_jump and end with a jump, e.g., here is the pattern for `a|b|c': /on_failure_jump/0/6/exactn/1/a/jump_past_alt/0/6 /on_failure_jump/0/6/exactn/1/b/jump_past_alt/0/3 /exactn/1/c So, we have to first go through the first (n-1) alternatives and then deal with the last one separately. */ /* Deal with the first (n-1) alternatives, which start with an on_failure_jump (see above) that jumps to right past a jump_past_alt. */ while ((re_opcode_t) p1[mcnt-(1+OFFSET_ADDRESS_SIZE)] == jump_past_alt) { /* `mcnt' holds how many bytes long the alternative is, including the ending `jump_past_alt' and its number. */ if (!PREFIX(alt_match_null_string_p) (p1, p1 + mcnt - (1 + OFFSET_ADDRESS_SIZE), reg_info)) return false; /* Move to right after this alternative, including the jump_past_alt. */ p1 += mcnt; /* Break if it's the beginning of an n-th alternative that doesn't begin with an on_failure_jump. */ if ((re_opcode_t) *p1 != on_failure_jump) break; /* Still have to check that it's not an n-th alternative that starts with an on_failure_jump. */ p1++; EXTRACT_NUMBER_AND_INCR (mcnt, p1); if ((re_opcode_t) p1[mcnt-(1+OFFSET_ADDRESS_SIZE)] != jump_past_alt) { /* Get to the beginning of the n-th alternative. */ p1 -= 1 + OFFSET_ADDRESS_SIZE; break; } } /* Deal with the last alternative: go back and get number of the `jump_past_alt' just before it. `mcnt' contains the length of the alternative. */ EXTRACT_NUMBER (mcnt, p1 - OFFSET_ADDRESS_SIZE); if (!PREFIX(alt_match_null_string_p) (p1, p1 + mcnt, reg_info)) return false; p1 += mcnt; /* Get past the n-th alternative. */ } /* if mcnt > 0 */ break; case stop_memory: assert (p1[1] == **p); *p = p1 + 2; return true; default: if (!PREFIX(common_op_match_null_string_p) (&p1, end, reg_info)) return false; } } /* while p1 < end */ return false; } /* group_match_null_string_p */ /* Similar to group_match_null_string_p, but doesn't deal with alternatives: It expects P to be the first byte of a single alternative and END one byte past the last. The alternative can contain groups. */ static boolean PREFIX(alt_match_null_string_p) (p, end, reg_info) UCHAR_T *p, *end; PREFIX(register_info_type) *reg_info; { int mcnt; UCHAR_T *p1 = p; while (p1 < end) { /* Skip over opcodes that can match nothing, and break when we get to one that can't. */ switch ((re_opcode_t) *p1) { /* It's a loop. */ case on_failure_jump: p1++; EXTRACT_NUMBER_AND_INCR (mcnt, p1); p1 += mcnt; break; default: if (!PREFIX(common_op_match_null_string_p) (&p1, end, reg_info)) return false; } } /* while p1 < end */ return true; } /* alt_match_null_string_p */ /* Deals with the ops common to group_match_null_string_p and alt_match_null_string_p. Sets P to one after the op and its arguments, if any. */ static boolean PREFIX(common_op_match_null_string_p) (p, end, reg_info) UCHAR_T **p, *end; PREFIX(register_info_type) *reg_info; { int mcnt; boolean ret; int reg_no; UCHAR_T *p1 = *p; switch ((re_opcode_t) *p1++) { case no_op: case begline: case endline: case begbuf: case endbuf: case wordbeg: case wordend: case wordbound: case notwordbound: #ifdef emacs case before_dot: case at_dot: case after_dot: #endif break; case start_memory: reg_no = *p1; assert (reg_no > 0 && reg_no <= MAX_REGNUM); ret = PREFIX(group_match_null_string_p) (&p1, end, reg_info); /* Have to set this here in case we're checking a group which contains a group and a back reference to it. */ if (REG_MATCH_NULL_STRING_P (reg_info[reg_no]) == MATCH_NULL_UNSET_VALUE) REG_MATCH_NULL_STRING_P (reg_info[reg_no]) = ret; if (!ret) return false; break; /* If this is an optimized succeed_n for zero times, make the jump. */ case jump: EXTRACT_NUMBER_AND_INCR (mcnt, p1); if (mcnt >= 0) p1 += mcnt; else return false; break; case succeed_n: /* Get to the number of times to succeed. */ p1 += OFFSET_ADDRESS_SIZE; EXTRACT_NUMBER_AND_INCR (mcnt, p1); if (mcnt == 0) { p1 -= 2 * OFFSET_ADDRESS_SIZE; EXTRACT_NUMBER_AND_INCR (mcnt, p1); p1 += mcnt; } else return false; break; case duplicate: if (!REG_MATCH_NULL_STRING_P (reg_info[*p1])) return false; break; case set_number_at: p1 += 2 * OFFSET_ADDRESS_SIZE; default: /* All other opcodes mean we cannot match the empty string. */ return false; } *p = p1; return true; } /* common_op_match_null_string_p */ /* Return zero if TRANSLATE[S1] and TRANSLATE[S2] are identical for LEN bytes; nonzero otherwise. */ static int PREFIX(bcmp_translate) (s1, s2, len, translate) const CHAR_T *s1, *s2; register int len; RE_TRANSLATE_TYPE translate; { register const UCHAR_T *p1 = (const UCHAR_T *) s1; register const UCHAR_T *p2 = (const UCHAR_T *) s2; while (len) { #ifdef WCHAR if (((*p1<=0xff)?translate[*p1++]:*p1++) != ((*p2<=0xff)?translate[*p2++]:*p2++)) return 1; #else /* BYTE */ if (translate[*p1++] != translate[*p2++]) return 1; #endif /* WCHAR */ len--; } return 0; } #else /* not INSIDE_RECURSION */ /* Entry points for GNU code. */ /* re_compile_pattern is the GNU regular expression compiler: it compiles PATTERN (of length SIZE) and puts the result in BUFP. Returns 0 if the pattern was valid, otherwise an error string. Assumes the `allocated' (and perhaps `buffer') and `translate' fields are set in BUFP on entry. We call regex_compile to do the actual compilation. */ const char * re_compile_pattern (pattern, length, bufp) const char *pattern; size_t length; struct re_pattern_buffer *bufp; { reg_errcode_t ret; /* GNU code is written to assume at least RE_NREGS registers will be set (and at least one extra will be -1). */ bufp->regs_allocated = REGS_UNALLOCATED; /* And GNU code determines whether or not to get register information by passing null for the REGS argument to re_match, etc., not by setting no_sub. */ bufp->no_sub = 0; /* Match anchors at newline. */ bufp->newline_anchor = 1; # ifdef MBS_SUPPORT if (MB_CUR_MAX != 1) ret = wcs_regex_compile (pattern, length, re_syntax_options, bufp); else # endif ret = byte_regex_compile (pattern, length, re_syntax_options, bufp); if (!ret) return NULL; return gettext (re_error_msgid + re_error_msgid_idx[(int) ret]); } #ifdef _LIBC weak_alias (__re_compile_pattern, re_compile_pattern) #endif /* Entry points compatible with 4.2 BSD regex library. We don't define them unless specifically requested. */ #if defined _REGEX_RE_COMP || defined _LIBC /* BSD has one and only one pattern buffer. */ static struct re_pattern_buffer re_comp_buf; char * #ifdef _LIBC /* Make these definitions weak in libc, so POSIX programs can redefine these names if they don't use our functions, and still use regcomp/regexec below without link errors. */ weak_function #endif re_comp (s) const char *s; { reg_errcode_t ret; if (!s) { if (!re_comp_buf.buffer) return gettext ("No previous regular expression"); return 0; } if (!re_comp_buf.buffer) { re_comp_buf.buffer = (unsigned char *) malloc (200); if (re_comp_buf.buffer == NULL) return (char *) gettext (re_error_msgid + re_error_msgid_idx[(int) REG_ESPACE]); re_comp_buf.allocated = 200; re_comp_buf.fastmap = (char *) malloc (1 << BYTEWIDTH); if (re_comp_buf.fastmap == NULL) return (char *) gettext (re_error_msgid + re_error_msgid_idx[(int) REG_ESPACE]); } /* Since `re_exec' always passes NULL for the `regs' argument, we don't need to initialize the pattern buffer fields which affect it. */ /* Match anchors at newlines. */ re_comp_buf.newline_anchor = 1; # ifdef MBS_SUPPORT if (MB_CUR_MAX != 1) ret = wcs_regex_compile (s, strlen (s), re_syntax_options, &re_comp_buf); else # endif ret = byte_regex_compile (s, strlen (s), re_syntax_options, &re_comp_buf); if (!ret) return NULL; /* Yes, we're discarding `const' here if !HAVE_LIBINTL. */ return (char *) gettext (re_error_msgid + re_error_msgid_idx[(int) ret]); } int #ifdef _LIBC weak_function #endif re_exec (s) const char *s; { const int len = strlen (s); return 0 <= re_search (&re_comp_buf, s, len, 0, len, (struct re_registers *) 0); } #endif /* _REGEX_RE_COMP */ /* POSIX.2 functions. Don't define these for Emacs. */ #ifndef emacs /* regcomp takes a regular expression as a string and compiles it. PREG is a regex_t *. We do not expect any fields to be initialized, since POSIX says we shouldn't. Thus, we set `buffer' to the compiled pattern; `used' to the length of the compiled pattern; `syntax' to RE_SYNTAX_POSIX_EXTENDED if the REG_EXTENDED bit in CFLAGS is set; otherwise, to RE_SYNTAX_POSIX_BASIC; `newline_anchor' to REG_NEWLINE being set in CFLAGS; `fastmap' to an allocated space for the fastmap; `fastmap_accurate' to zero; `re_nsub' to the number of subexpressions in PATTERN. PATTERN is the address of the pattern string. CFLAGS is a series of bits which affect compilation. If REG_EXTENDED is set, we use POSIX extended syntax; otherwise, we use POSIX basic syntax. If REG_NEWLINE is set, then . and [^...] don't match newline. Also, regexec will try a match beginning after every newline. If REG_ICASE is set, then we considers upper- and lowercase versions of letters to be equivalent when matching. If REG_NOSUB is set, then when PREG is passed to regexec, that routine will report only success or failure, and nothing about the registers. It returns 0 if it succeeds, nonzero if it doesn't. (See regex.h for the return codes and their meanings.) */ int regcomp (preg, pattern, cflags) regex_t *preg; const char *pattern; int cflags; { reg_errcode_t ret; reg_syntax_t syntax = (cflags & REG_EXTENDED) ? RE_SYNTAX_POSIX_EXTENDED : RE_SYNTAX_POSIX_BASIC; /* regex_compile will allocate the space for the compiled pattern. */ preg->buffer = 0; preg->allocated = 0; preg->used = 0; /* Try to allocate space for the fastmap. */ preg->fastmap = (char *) malloc (1 << BYTEWIDTH); if (cflags & REG_ICASE) { unsigned i; preg->translate = (RE_TRANSLATE_TYPE) malloc (CHAR_SET_SIZE * sizeof (*(RE_TRANSLATE_TYPE)0)); if (preg->translate == NULL) return (int) REG_ESPACE; /* Map uppercase characters to corresponding lowercase ones. */ for (i = 0; i < CHAR_SET_SIZE; i++) preg->translate[i] = ISUPPER (i) ? TOLOWER (i) : i; } else preg->translate = NULL; /* If REG_NEWLINE is set, newlines are treated differently. */ if (cflags & REG_NEWLINE) { /* REG_NEWLINE implies neither . nor [^...] match newline. */ syntax &= ~RE_DOT_NEWLINE; syntax |= RE_HAT_LISTS_NOT_NEWLINE; /* It also changes the matching behavior. */ preg->newline_anchor = 1; } else preg->newline_anchor = 0; preg->no_sub = !!(cflags & REG_NOSUB); /* POSIX says a null character in the pattern terminates it, so we can use strlen here in compiling the pattern. */ # ifdef MBS_SUPPORT if (MB_CUR_MAX != 1) ret = wcs_regex_compile (pattern, strlen (pattern), syntax, preg); else # endif ret = byte_regex_compile (pattern, strlen (pattern), syntax, preg); /* POSIX doesn't distinguish between an unmatched open-group and an unmatched close-group: both are REG_EPAREN. */ if (ret == REG_ERPAREN) ret = REG_EPAREN; if (ret == REG_NOERROR && preg->fastmap) { /* Compute the fastmap now, since regexec cannot modify the pattern buffer. */ if (re_compile_fastmap (preg) == -2) { /* Some error occurred while computing the fastmap, just forget about it. */ free (preg->fastmap); preg->fastmap = NULL; } } return (int) ret; } #ifdef _LIBC weak_alias (__regcomp, regcomp) #endif /* regexec searches for a given pattern, specified by PREG, in the string STRING. If NMATCH is zero or REG_NOSUB was set in the cflags argument to `regcomp', we ignore PMATCH. Otherwise, we assume PMATCH has at least NMATCH elements, and we set them to the offsets of the corresponding matched substrings. EFLAGS specifies `execution flags' which affect matching: if REG_NOTBOL is set, then ^ does not match at the beginning of the string; if REG_NOTEOL is set, then $ does not match at the end. We return 0 if we find a match and REG_NOMATCH if not. */ int regexec (preg, string, nmatch, pmatch, eflags) const regex_t *preg; const char *string; size_t nmatch; regmatch_t pmatch[]; int eflags; { int ret; struct re_registers regs; regex_t private_preg; int len = strlen (string); boolean want_reg_info = !preg->no_sub && nmatch > 0; private_preg = *preg; private_preg.not_bol = !!(eflags & REG_NOTBOL); private_preg.not_eol = !!(eflags & REG_NOTEOL); /* The user has told us exactly how many registers to return information about, via `nmatch'. We have to pass that on to the matching routines. */ private_preg.regs_allocated = REGS_FIXED; if (want_reg_info) { regs.num_regs = nmatch; regs.start = TALLOC (nmatch * 2, regoff_t); if (regs.start == NULL) return (int) REG_NOMATCH; regs.end = regs.start + nmatch; } /* Perform the searching operation. */ ret = re_search (&private_preg, string, len, /* start: */ 0, /* range: */ len, want_reg_info ? ®s : (struct re_registers *) 0); /* Copy the register information to the POSIX structure. */ if (want_reg_info) { if (ret >= 0) { unsigned r; for (r = 0; r < nmatch; r++) { pmatch[r].rm_so = regs.start[r]; pmatch[r].rm_eo = regs.end[r]; } } /* If we needed the temporary register info, free the space now. */ free (regs.start); } /* We want zero return to mean success, unlike `re_search'. */ return ret >= 0 ? (int) REG_NOERROR : (int) REG_NOMATCH; } #ifdef _LIBC weak_alias (__regexec, regexec) #endif /* Returns a message corresponding to an error code, ERRCODE, returned from either regcomp or regexec. We don't use PREG here. */ size_t regerror (errcode, preg, errbuf, errbuf_size) int errcode; const regex_t *preg; char *errbuf; size_t errbuf_size; { const char *msg; size_t msg_size; if (errcode < 0 || errcode >= (int) (sizeof (re_error_msgid_idx) / sizeof (re_error_msgid_idx[0]))) /* Only error codes returned by the rest of the code should be passed to this routine. If we are given anything else, or if other regex code generates an invalid error code, then the program has a bug. Dump core so we can fix it. */ abort (); msg = gettext (re_error_msgid + re_error_msgid_idx[errcode]); msg_size = strlen (msg) + 1; /* Includes the null. */ if (errbuf_size != 0) { if (msg_size > errbuf_size) { #if defined HAVE_MEMPCPY || defined _LIBC *((char *) __mempcpy (errbuf, msg, errbuf_size - 1)) = '\0'; #else memcpy (errbuf, msg, errbuf_size - 1); errbuf[errbuf_size - 1] = 0; #endif } else memcpy (errbuf, msg, msg_size); } return msg_size; } #ifdef _LIBC weak_alias (__regerror, regerror) #endif /* Free dynamically allocated space used by PREG. */ void regfree (preg) regex_t *preg; { if (preg->buffer != NULL) free (preg->buffer); preg->buffer = NULL; preg->allocated = 0; preg->used = 0; if (preg->fastmap != NULL) free (preg->fastmap); preg->fastmap = NULL; preg->fastmap_accurate = 0; if (preg->translate != NULL) free (preg->translate); preg->translate = NULL; } #ifdef _LIBC weak_alias (__regfree, regfree) #endif #endif /* not emacs */ #endif /* not INSIDE_RECURSION */ #undef STORE_NUMBER #undef STORE_NUMBER_AND_INCR #undef EXTRACT_NUMBER #undef EXTRACT_NUMBER_AND_INCR #undef DEBUG_PRINT_COMPILED_PATTERN #undef DEBUG_PRINT_DOUBLE_STRING #undef INIT_FAIL_STACK #undef RESET_FAIL_STACK #undef DOUBLE_FAIL_STACK #undef PUSH_PATTERN_OP #undef PUSH_FAILURE_POINTER #undef PUSH_FAILURE_INT #undef PUSH_FAILURE_ELT #undef POP_FAILURE_POINTER #undef POP_FAILURE_INT #undef POP_FAILURE_ELT #undef DEBUG_PUSH #undef DEBUG_POP #undef PUSH_FAILURE_POINT #undef POP_FAILURE_POINT #undef REG_UNSET_VALUE #undef REG_UNSET #undef PATFETCH #undef PATFETCH_RAW #undef PATUNFETCH #undef TRANSLATE #undef INIT_BUF_SIZE #undef GET_BUFFER_SPACE #undef BUF_PUSH #undef BUF_PUSH_2 #undef BUF_PUSH_3 #undef STORE_JUMP #undef STORE_JUMP2 #undef INSERT_JUMP #undef INSERT_JUMP2 #undef EXTEND_BUFFER #undef GET_UNSIGNED_NUMBER #undef FREE_STACK_RETURN # undef POINTER_TO_OFFSET # undef MATCHING_IN_FRST_STRING # undef PREFETCH # undef AT_STRINGS_BEG # undef AT_STRINGS_END # undef WORDCHAR_P # undef FREE_VAR # undef FREE_VARIABLES # undef NO_HIGHEST_ACTIVE_REG # undef NO_LOWEST_ACTIVE_REG # undef CHAR_T # undef UCHAR_T # undef COMPILED_BUFFER_VAR # undef OFFSET_ADDRESS_SIZE # undef CHAR_CLASS_SIZE # undef PREFIX # undef ARG_PREFIX # undef PUT_CHAR # undef BYTE # undef WCHAR # define DEFINED_ONCE
/* Return the canonical absolute name of a given file. Copyright (C) 1996-2001, 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #ifdef STDC_HEADERS # include <stdlib.h> #else void free (); #endif #if defined STDC_HEADERS || defined HAVE_STRING_H # include <string.h> #else # include <strings.h> #endif #if HAVE_SYS_PARAM_H # include <sys/param.h> #endif #include <sys/stat.h> #include <errno.h> #include "path-concat.h" #include "xalloc.h" #include "xgetcwd.h" #ifndef errno extern int errno; #endif #ifndef __set_errno # define __set_errno(Val) errno = (Val) #endif #if !HAVE_RESOLVEPATH /* If __PTRDIFF_TYPE__ is defined, as with GNU C, use that; that way we don't pollute the namespace with <stddef.h>'s symbols. Otherwise, if <stddef.h> is available, include it and use ptrdiff_t. In traditional C, long is the best that we can do. */ # ifdef __PTRDIFF_TYPE__ # define PTR_INT_TYPE __PTRDIFF_TYPE__ # else # ifdef HAVE_STDDEF_H # include <stddef.h> # define PTR_INT_TYPE ptrdiff_t # else # define PTR_INT_TYPE long # endif # endif # include "pathmax.h" # include "xreadlink.h" # ifdef STAT_MACROS_BROKEN # undef S_ISLNK # endif # ifndef S_ISLNK # ifdef S_IFLNK # define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) # endif # endif #endif /* !HAVE_RESOLVEPATH */ /* Return the canonical absolute name of file NAME. A canonical name does not contain any `.', `..' components nor any repeated path separators ('/') or symlinks. All path components must exist. The result is malloc'd. */ char * canonicalize_file_name (const char *name) { #if HAVE_RESOLVEPATH char *resolved, *extra_buf = NULL; size_t resolved_size; ssize_t resolved_len; #else /* !HAVE_RESOLVEPATH */ char *rpath, *dest, *extra_buf = NULL; const char *start, *end, *rpath_limit; size_t extra_len = 0; int num_links = 0; #endif /* !HAVE_RESOLVEPATH */ if (name == NULL) { __set_errno (EINVAL); return NULL; } if (name[0] == '\0') { __set_errno (ENOENT); return NULL; } #if HAVE_RESOLVEPATH /* All known hosts with resolvepath (e.g. Solaris 7) don't turn relative names into absolute ones, so prepend the working directory if the path is not absolute. */ if (name[0] != '/') { char *wd; if (!(wd = xgetcwd ())); return NULL; extra_buf = path_concat (wd, name, NULL); if (!extra_buf) xalloc_die (); name = extra_buf; free (wd); } resolved_size = strlen (name); while (1) { resolved_size = 2 * resolved_size + 1; resolved = xmalloc (resolved_size); resolved_len = resolvepath (name, resolved, resolved_size); if (resolved_len < resolved_size) break; free (resolved); } if (resolved_len < 0) { free (resolved); resolved = NULL; } free (extra_buf); return resolved; #else /* !HAVE_RESOLVEPATH */ if (name[0] != '/') { rpath = xgetcwd (); if (!rpath) return NULL; dest = strchr (rpath, '\0'); if (dest < rpath + PATH_MAX) { rpath = xrealloc (rpath, PATH_MAX); rpath_limit = rpath + PATH_MAX; } else { rpath_limit = dest; } } else { rpath = xmalloc (PATH_MAX); rpath_limit = rpath + PATH_MAX; rpath[0] = '/'; dest = rpath + 1; } for (start = end = name; *start; start = end) { /* Skip sequence of multiple path-separators. */ while (*start == '/') ++start; /* Find end of path component. */ for (end = start; *end && *end != '/'; ++end) /* Nothing. */; if (end - start == 0) break; else if (end - start == 1 && start[0] == '.') /* nothing */; else if (end - start == 2 && start[0] == '.' && start[1] == '.') { /* Back up to previous component, ignore if at root already. */ if (dest > rpath + 1) while ((--dest)[-1] != '/'); } else { struct stat st; if (dest[-1] != '/') *dest++ = '/'; if (dest + (end - start) >= rpath_limit) { PTR_INT_TYPE dest_offset = dest - rpath; size_t new_size = rpath_limit - rpath; if (end - start + 1 > PATH_MAX) new_size += end - start + 1; else new_size += PATH_MAX; rpath = (char *) xrealloc (rpath, new_size); rpath_limit = rpath + new_size; dest = rpath + dest_offset; } dest = memcpy (dest, start, end - start); dest += end - start; *dest = '\0'; if (lstat (rpath, &st) < 0) goto error; # ifdef S_ISLNK if (S_ISLNK (st.st_mode)) { char *buf; size_t n, len; # ifdef MAXSYMLINKS if (++num_links > MAXSYMLINKS) { __set_errno (ELOOP); goto error; } # endif /* MAXSYMLINKS */ buf = xreadlink (rpath); if (!buf) goto error; n = strlen (buf); len = strlen (end); if (!extra_len) { extra_len = ((n + len + 1) > PATH_MAX) ? (n + len + 1) : PATH_MAX; extra_buf = xmalloc (extra_len); } else if ((n + len + 1) > extra_len) { extra_len = n + len + 1; extra_buf = xrealloc (extra_buf, extra_len); } /* Careful here, end may be a pointer into extra_buf... */ memmove (&extra_buf[n], end, len + 1); name = end = memcpy (extra_buf, buf, n); if (buf[0] == '/') dest = rpath + 1; /* It's an absolute symlink */ else /* Back up to previous component, ignore if at root already: */ if (dest > rpath + 1) while ((--dest)[-1] != '/'); free (buf); } # endif /* S_ISLNK */ } } if (dest > rpath + 1 && dest[-1] == '/') --dest; *dest = '\0'; free (extra_buf); return rpath; error: free (extra_buf); free (rpath); return NULL; #endif /* !HAVE_RESOLVEPATH */ }
/* Copyright (C) 1998, 1999, 2001 Free Software Foundation, Inc. This file is derived from the one in the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <config.h> /* Disable the definition of mkstemp to rpl_mkstemp (from config.h) in this file. Otherwise, we'd get conflicting prototypes for rpl_mkstemp on most systems. */ #undef mkstemp #include <stdio.h> #include <stdlib.h> #ifndef __GT_FILE # define __GT_FILE 0 #endif int __gen_tempname (); /* Generate a unique temporary file name from TEMPLATE. The last six characters of TEMPLATE must be "XXXXXX"; they are replaced with a string that makes the filename unique. Then open the file and return a fd. */ int rpl_mkstemp (char *template) { return __gen_tempname (template, __GT_FILE); }
/* memrchr -- find the last occurrence of a byte in a memory block Copyright (C) 1991, 93, 96, 97, 99, 2000 Free Software Foundation, Inc. Based on strlen implementation by Torbjorn Granlund (tege@sics.se), with help from Dan Sahlin (dan@sics.se) and commentary by Jim Blandy (jimb@ai.mit.edu); adaptation to memchr suggested by Dick Karpinski (dick@cca.ucsf.edu), and implemented by Roland McGrath (roland@ai.mit.edu). The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <stdlib.h> #undef __ptr_t #if defined (__cplusplus) || (defined (__STDC__) && __STDC__) # define __ptr_t void * #else /* Not C++ or ANSI C. */ # define __ptr_t char * #endif /* C++ or ANSI C. */ #if defined (_LIBC) # include <string.h> # include <memcopy.h> #else # define reg_char char #endif #if defined (HAVE_LIMITS_H) || defined (_LIBC) # include <limits.h> #endif #define LONG_MAX_32_BITS 2147483647 #ifndef LONG_MAX # define LONG_MAX LONG_MAX_32_BITS #endif #include <sys/types.h> #undef __memrchr #undef memrchr #ifndef weak_alias # define __memrchr memrchr #endif /* Search no more than N bytes of S for C. */ __ptr_t __memrchr (s, c_in, n) const __ptr_t s; int c_in; size_t n; { const unsigned char *char_ptr; const unsigned long int *longword_ptr; unsigned long int longword, magic_bits, charmask; unsigned reg_char c; c = (unsigned char) c_in; /* Handle the last few characters by reading one character at a time. Do this until CHAR_PTR is aligned on a longword boundary. */ for (char_ptr = (const unsigned char *) s + n; n > 0 && ((unsigned long int) char_ptr & (sizeof (longword) - 1)) != 0; --n) if (*--char_ptr == c) return (__ptr_t) char_ptr; /* All these elucidatory comments refer to 4-byte longwords, but the theory applies equally well to 8-byte longwords. */ longword_ptr = (unsigned long int *) char_ptr; /* Bits 31, 24, 16, and 8 of this number are zero. Call these bits the "holes." Note that there is a hole just to the left of each byte, with an extra at the end: bits: 01111110 11111110 11111110 11111111 bytes: AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD The 1-bits make sure that carries propagate to the next 0-bit. The 0-bits provide holes for carries to fall into. */ if (sizeof (longword) != 4 && sizeof (longword) != 8) abort (); #if LONG_MAX <= LONG_MAX_32_BITS magic_bits = 0x7efefeff; #else magic_bits = ((unsigned long int) 0x7efefefe << 32) | 0xfefefeff; #endif /* Set up a longword, each of whose bytes is C. */ charmask = c | (c << 8); charmask |= charmask << 16; #if LONG_MAX > LONG_MAX_32_BITS charmask |= charmask << 32; #endif /* Instead of the traditional loop which tests each character, we will test a longword at a time. The tricky part is testing if *any of the four* bytes in the longword in question are zero. */ while (n >= sizeof (longword)) { /* We tentatively exit the loop if adding MAGIC_BITS to LONGWORD fails to change any of the hole bits of LONGWORD. 1) Is this safe? Will it catch all the zero bytes? Suppose there is a byte with all zeros. Any carry bits propagating from its left will fall into the hole at its least significant bit and stop. Since there will be no carry from its most significant bit, the LSB of the byte to the left will be unchanged, and the zero will be detected. 2) Is this worthwhile? Will it ignore everything except zero bytes? Suppose every byte of LONGWORD has a bit set somewhere. There will be a carry into bit 8. If bit 8 is set, this will carry into bit 16. If bit 8 is clear, one of bits 9-15 must be set, so there will be a carry into bit 16. Similarly, there will be a carry into bit 24. If one of bits 24-30 is set, there will be a carry into bit 31, so all of the hole bits will be changed. The one misfire occurs when bits 24-30 are clear and bit 31 is set; in this case, the hole at bit 31 is not changed. If we had access to the processor carry flag, we could close this loophole by putting the fourth hole at bit 32! So it ignores everything except 128's, when they're aligned properly. 3) But wait! Aren't we looking for C, not zero? Good point. So what we do is XOR LONGWORD with a longword, each of whose bytes is C. This turns each byte that is C into a zero. */ longword = *--longword_ptr ^ charmask; /* Add MAGIC_BITS to LONGWORD. */ if ((((longword + magic_bits) /* Set those bits that were unchanged by the addition. */ ^ ~longword) /* Look at only the hole bits. If any of the hole bits are unchanged, most likely one of the bytes was a zero. */ & ~magic_bits) != 0) { /* Which of the bytes was C? If none of them were, it was a misfire; continue the search. */ const unsigned char *cp = (const unsigned char *) longword_ptr; #if LONG_MAX > 2147483647 if (cp[7] == c) return (__ptr_t) &cp[7]; if (cp[6] == c) return (__ptr_t) &cp[6]; if (cp[5] == c) return (__ptr_t) &cp[5]; if (cp[4] == c) return (__ptr_t) &cp[4]; #endif if (cp[3] == c) return (__ptr_t) &cp[3]; if (cp[2] == c) return (__ptr_t) &cp[2]; if (cp[1] == c) return (__ptr_t) &cp[1]; if (cp[0] == c) return (__ptr_t) cp; } n -= sizeof (longword); } char_ptr = (const unsigned char *) longword_ptr; while (n-- > 0) { if (*--char_ptr == c) return (__ptr_t) char_ptr; } return 0; } #ifdef weak_alias weak_alias (__memrchr, memrchr) #endif
/* euidaccess -- check if effective user id can access file Copyright (C) 1990, 1991, 1995, 1998, 2000, 2003 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie and Torbjorn Granlund. Adapted for GNU C library by Roland McGrath. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <sys/types.h> #include <sys/stat.h> #ifdef S_IEXEC # ifndef S_IXUSR # define S_IXUSR S_IEXEC # endif # ifndef S_IXGRP # define S_IXGRP (S_IEXEC >> 3) # endif # ifndef S_IXOTH # define S_IXOTH (S_IEXEC >> 6) # endif #endif /* S_IEXEC */ #if defined (HAVE_UNISTD_H) || defined (_LIBC) # include <unistd.h> #endif #ifdef _POSIX_VERSION # include <limits.h> # if !defined(NGROUPS_MAX) || NGROUPS_MAX < 1 # undef NGROUPS_MAX # define NGROUPS_MAX sysconf (_SC_NGROUPS_MAX) # endif /* NGROUPS_MAX */ #else /* not _POSIX_VERSION */ uid_t getuid (); gid_t getgid (); uid_t geteuid (); gid_t getegid (); # include <sys/param.h> # if !defined(NGROUPS_MAX) && defined(NGROUPS) # define NGROUPS_MAX NGROUPS # endif /* not NGROUPS_MAX and NGROUPS */ #endif /* not POSIX_VERSION */ #include <errno.h> #ifndef errno extern int errno; #endif #ifndef __set_errno # define __set_errno(val) errno = (val) #endif #include "euidaccess.h" #if defined(EACCES) && !defined(EACCESS) # define EACCESS EACCES #endif #ifndef F_OK # define F_OK 0 # define X_OK 1 # define W_OK 2 # define R_OK 4 #endif #if !defined (S_IROTH) && defined (R_OK) # define S_IROTH R_OK #endif #if !defined (S_IWOTH) && defined (W_OK) # define S_IWOTH W_OK #endif #if !defined (S_IXOTH) && defined (X_OK) # define S_IXOTH X_OK #endif #ifdef _LIBC # define group_member __group_member # define euidaccess __euidaccess #else /* The user's real user id. */ static uid_t uid; /* The user's real group id. */ static gid_t gid; # if HAVE_GETGROUPS int group_member (); # else # define group_member(gid) 0 # endif #endif /* The user's effective user id. */ static uid_t euid; /* The user's effective group id. */ static gid_t egid; /* Nonzero if UID, GID, EUID, and EGID have valid values. */ static int have_ids; /* Return 0 if the user has permission of type MODE on file PATH; otherwise, return -1 and set `errno' to EACCESS. Like access, except that it uses the effective user and group id's instead of the real ones, and it does not check for read-only filesystem, text busy, etc. */ int euidaccess (const char *path, int mode) { struct stat stats; int granted; #ifdef _LIBC if (! __libc_enable_secure) /* If we are not set-uid or set-gid, access does the same. */ return __access (path, mode); #else if (have_ids == 0) { have_ids = 1; uid = getuid (); gid = getgid (); euid = geteuid (); egid = getegid (); } if (uid == euid && gid == egid) /* If we are not set-uid or set-gid, access does the same. */ return access (path, mode); #endif if (stat (path, &stats)) return -1; mode &= (X_OK | W_OK | R_OK); /* Clear any bogus bits. */ #if R_OK != S_IROTH || W_OK != S_IWOTH || X_OK != S_IXOTH ?error Oops, portability assumptions incorrect. #endif if (mode == F_OK) return 0; /* The file exists. */ #ifdef _LIBC /* Now we need the IDs. */ if (have_ids == 0) { have_ids = 1; euid = __geteuid (); egid = __getegid (); } #endif /* The super-user can read and write any file, and execute any file that anyone can execute. */ if (euid == 0 && ((mode & X_OK) == 0 || (stats.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))) return 0; if (euid == stats.st_uid) granted = (unsigned) (stats.st_mode & (mode << 6)) >> 6; else if (egid == stats.st_gid || group_member (stats.st_gid)) granted = (unsigned) (stats.st_mode & (mode << 3)) >> 3; else granted = (stats.st_mode & mode); if (granted == mode) return 0; __set_errno (EACCESS); return -1; } #undef euidaccess #ifdef weak_alias weak_alias (__euidaccess, euidaccess) #endif #ifdef TEST # include <stdio.h> # include <errno.h> # include "error.h" char *program_name; int main (argc, argv) int argc; char **argv; { char *file; int mode; int err; program_name = argv[0]; if (argc < 3) abort (); file = argv[1]; mode = atoi (argv[2]); err = euidaccess (file, mode); printf ("%d\n", err); if (err != 0) error (0, errno, "%s", file); exit (0); } #endif
/* File tree walker functions. Copyright (C) 1996-2001, 2002, 2003 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #if __GNUC__ # define alloca __builtin_alloca #else # if HAVE_ALLOCA_H # include <alloca.h> # else # ifdef _AIX # pragma alloca # else char *alloca (); # endif # endif #endif #include <sys/types.h> #if defined _LIBC # include <dirent.h> # define NAMLEN(dirent) _D_EXACT_NAMLEN (dirent) #else # if HAVE_DIRENT_H # include <dirent.h> # define NAMLEN(dirent) strlen ((dirent)->d_name) # else # define dirent direct # define NAMLEN(dirent) (dirent)->d_namlen # if HAVE_SYS_NDIR_H # include <sys/ndir.h> # endif # if HAVE_SYS_DIR_H # include <sys/dir.h> # endif # if HAVE_NDIR_H # include <ndir.h> # endif # endif #endif #include <errno.h> #include <ftw.h> #include <limits.h> #include <search.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #if HAVE_SYS_PARAM_H || defined _LIBC # include <sys/param.h> #endif #ifdef _LIBC # include <include/sys/stat.h> #else # include <sys/stat.h> #endif #if ! _LIBC && !HAVE_DECL_STPCPY && !defined stpcpy char *stpcpy (); #endif #if ! _LIBC && ! defined HAVE_MEMPCPY && ! defined mempcpy /* Be CAREFUL that there are no side effects in N. */ # define mempcpy(D, S, N) ((void *) ((char *) memcpy (D, S, N) + (N))) #endif /* #define NDEBUG 1 */ #include <assert.h> #include "save-cwd.h" #ifndef _LIBC # undef __chdir # define __chdir chdir # undef __closedir # define __closedir closedir # undef __fchdir # define __fchdir fchdir # undef __mempcpy # define __mempcpy mempcpy # undef __opendir # define __opendir opendir # undef __readdir64 # define __readdir64 readdir # undef __stpcpy # define __stpcpy stpcpy # undef __tdestroy # define __tdestroy tdestroy # undef __tfind # define __tfind tfind # undef __tsearch # define __tsearch tsearch # undef internal_function # define internal_function /* empty */ # undef dirent64 # define dirent64 dirent # undef MAX # define MAX(a, b) ((a) > (b) ? (a) : (b)) #endif /* Arrange to make lstat calls go through the wrapper function on systems with an lstat function that does not dereference symlinks that are specified with a trailing slash. */ #if ! _LIBC && ! LSTAT_FOLLOWS_SLASHED_SYMLINK int rpl_lstat (const char *, struct stat *); # undef lstat # define lstat(Name, Stat_buf) rpl_lstat(Name, Stat_buf) #endif #ifndef __set_errno # define __set_errno(Val) errno = (Val) #endif /* Support for the LFS API version. */ #ifndef FTW_NAME # define FTW_NAME ftw # define NFTW_NAME nftw # define INO_T ino_t # define FTW_STAT stat # ifdef _LIBC # define LXSTAT __lxstat # define XSTAT __xstat # else # define LXSTAT(V,f,sb) lstat (f,sb) # define XSTAT(V,f,sb) stat (f,sb) # endif # define FTW_FUNC_T __ftw_func_t # define NFTW_FUNC_T __nftw_func_t #endif /* We define PATH_MAX if the system does not provide a definition. This does not artificially limit any operation. PATH_MAX is simply used as a guesstimate for the expected maximal path length. Buffers will be enlarged if necessary. */ #ifndef PATH_MAX # define PATH_MAX 1024 #endif #ifndef S_IFMT # define S_IFMT 0170000 #endif #if STAT_MACROS_BROKEN # undef S_ISLNK #endif #ifndef S_ISLNK # ifdef S_IFLNK # define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) # else # define S_ISLNK(m) 0 # endif #endif struct dir_data { DIR *stream; char *content; }; struct known_object { dev_t dev; INO_T ino; }; struct ftw_data { /* Array with pointers to open directory streams. */ struct dir_data **dirstreams; size_t actdir; size_t maxdir; /* Buffer containing name of currently processed object. */ char *dirbuf; size_t dirbufsize; /* Passed as fourth argument to `nftw' callback. The `base' member tracks the content of the `dirbuf'. */ struct FTW ftw; /* Flags passed to `nftw' function. 0 for `ftw'. */ int flags; /* Conversion array for flag values. It is the identity mapping for `nftw' calls, otherwise it maps the values to those known by `ftw'. */ const int *cvt_arr; /* Callback function. We always use the `nftw' form. */ NFTW_FUNC_T func; /* Device of starting point. Needed for FTW_MOUNT. */ dev_t dev; /* Data structure for keeping fingerprints of already processed object. This is needed when not using FTW_PHYS. */ void *known_objects; }; /* Internally we use the FTW_* constants used for `nftw'. When invoked as `ftw', map each flag to the subset of values used by `ftw'. */ static const int nftw_arr[] = { FTW_F, FTW_D, FTW_DNR, FTW_NS, FTW_SL, FTW_DP, FTW_SLN }; static const int ftw_arr[] = { FTW_F, FTW_D, FTW_DNR, FTW_NS, FTW_F, FTW_D, FTW_NS }; /* Forward declarations of local functions. */ static int ftw_dir (struct ftw_data *data, struct FTW_STAT *st) internal_function; static int object_compare (const void *p1, const void *p2) { /* We don't need a sophisticated and useful comparison. We are only interested in equality. However, we must be careful not to accidentally compare `holes' in the structure. */ const struct known_object *kp1 = p1, *kp2 = p2; int cmp1; cmp1 = (kp1->ino > kp2->ino) - (kp1->ino < kp2->ino); if (cmp1 != 0) return cmp1; return (kp1->dev > kp2->dev) - (kp1->dev < kp2->dev); } static inline int add_object (struct ftw_data *data, struct FTW_STAT *st) { struct known_object *newp = malloc (sizeof (struct known_object)); if (newp == NULL) return -1; newp->dev = st->st_dev; newp->ino = st->st_ino; return __tsearch (newp, &data->known_objects, object_compare) ? 0 : -1; } static inline int find_object (struct ftw_data *data, struct FTW_STAT *st) { struct known_object obj; obj.dev = st->st_dev; obj.ino = st->st_ino; return __tfind (&obj, &data->known_objects, object_compare) != NULL; } static inline int open_dir_stream (struct ftw_data *data, struct dir_data *dirp) { int result = 0; if (data->dirstreams[data->actdir] != NULL) { /* Oh, oh. We must close this stream. Get all remaining entries and store them as a list in the `content' member of the `struct dir_data' variable. */ size_t bufsize = 1024; char *buf = malloc (bufsize); if (buf == NULL) result = -1; else { DIR *st = data->dirstreams[data->actdir]->stream; struct dirent64 *d; size_t actsize = 0; while ((d = __readdir64 (st)) != NULL) { size_t this_len = NAMLEN (d); if (actsize + this_len + 2 >= bufsize) { char *newp; bufsize += MAX (1024, 2 * this_len); newp = (char *) realloc (buf, bufsize); if (newp == NULL) { /* No more memory. */ int save_err = errno; free (buf); __set_errno (save_err); result = -1; break; } buf = newp; } *((char *) __mempcpy (buf + actsize, d->d_name, this_len)) = '\0'; actsize += this_len + 1; } /* Terminate the list with an additional NUL byte. */ buf[actsize++] = '\0'; /* Shrink the buffer to what we actually need. */ data->dirstreams[data->actdir]->content = realloc (buf, actsize); if (data->dirstreams[data->actdir]->content == NULL) { int save_err = errno; free (buf); __set_errno (save_err); result = -1; } else { __closedir (st); data->dirstreams[data->actdir]->stream = NULL; data->dirstreams[data->actdir] = NULL; } } } /* Open the new stream. */ if (result == 0) { const char *name = ((data->flags & FTW_CHDIR) ? data->dirbuf + data->ftw.base: data->dirbuf); assert (data->dirstreams[data->actdir] == NULL); dirp->stream = __opendir (name); if (dirp->stream == NULL) result = -1; else { dirp->content = NULL; data->dirstreams[data->actdir] = dirp; if (++data->actdir == data->maxdir) data->actdir = 0; } } return result; } static inline int process_entry (struct ftw_data *data, struct dir_data *dir, const char *name, size_t namlen) { struct FTW_STAT st; int result = 0; int flag = 0; size_t new_buflen; if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) /* Don't process the "." and ".." entries. */ return 0; new_buflen = data->ftw.base + namlen + 2; if (data->dirbufsize < new_buflen) { /* Enlarge the buffer. */ char *newp; data->dirbufsize = 2 * new_buflen; newp = (char *) realloc (data->dirbuf, data->dirbufsize); if (newp == NULL) return -1; data->dirbuf = newp; } *((char *) __mempcpy (data->dirbuf + data->ftw.base, name, namlen)) = '\0'; if ((data->flags & FTW_CHDIR) == 0) name = data->dirbuf; if (((data->flags & FTW_PHYS) ? LXSTAT (_STAT_VER, name, &st) : XSTAT (_STAT_VER, name, &st)) < 0) { if (errno != EACCES && errno != ENOENT) result = -1; else if (!(data->flags & FTW_PHYS) && LXSTAT (_STAT_VER, name, &st) == 0 && S_ISLNK (st.st_mode)) flag = FTW_SLN; else flag = FTW_NS; } else { if (S_ISDIR (st.st_mode)) flag = FTW_D; else if (S_ISLNK (st.st_mode)) flag = FTW_SL; else flag = FTW_F; } if (result == 0 && (flag == FTW_NS || !(data->flags & FTW_MOUNT) || st.st_dev == data->dev)) { if (flag == FTW_D) { if ((data->flags & FTW_PHYS) || (!find_object (data, &st) /* Remember the object. */ && (result = add_object (data, &st)) == 0)) { /* When processing a directory as part of a depth-first traversal, invoke the user's callback function with type=FTW_DPRE just before processing any entry in that directory. And if the callback sets ftw.skip, then don't process any entries of the directory. */ if ((data->flags & FTW_DEPTH) && (result = (*data->func) (data->dirbuf, &st, FTW_DPRE, &data->ftw)) == 0 && ! data->ftw.skip) result = ftw_dir (data, &st); if (result == 0 && (data->flags & FTW_CHDIR)) { /* Change back to the parent directory. */ int done = 0; if (dir->stream != NULL) if (__fchdir (dirfd (dir->stream)) == 0) done = 1; if (!done) { if (data->ftw.base == 1) { if (__chdir ("/") < 0) result = -1; } else if (__chdir ("..") < 0) result = -1; } if (result < 0) { result = (*data->func) (data->dirbuf, NULL, FTW_DCHP, &data->ftw); } } } } else result = (*data->func) (data->dirbuf, &st, data->cvt_arr[flag], &data->ftw); } return result; } static int internal_function ftw_dir (struct ftw_data *data, struct FTW_STAT *st) { struct dir_data dir; struct dirent64 *d; int previous_base = data->ftw.base; int result; char *startp; /* Open the stream for this directory. This might require that another stream has to be closed. */ result = open_dir_stream (data, &dir); if (result != 0) { if (errno == EACCES) /* We cannot read the directory. Signal this with a special flag. */ result = (*data->func) (data->dirbuf, st, FTW_DNR, &data->ftw); return result; } /* First, report the directory (if not depth-first). */ if (!(data->flags & FTW_DEPTH)) { result = (*data->func) (data->dirbuf, st, FTW_D, &data->ftw); if (result != 0) return result; } /* If necessary, change to this directory. */ if (data->flags & FTW_CHDIR) { if (__fchdir (dirfd (dir.stream)) < 0) { if (errno == ENOSYS) { if (__chdir (data->dirbuf) < 0) result = -1; } else result = -1; } if (result != 0) { int save_err = errno; __closedir (dir.stream); __set_errno (save_err); if (data->actdir-- == 0) data->actdir = data->maxdir - 1; data->dirstreams[data->actdir] = NULL; /* We cannot change to the directory. Signal this with a special flag. */ result = (*data->func) (data->dirbuf, st, FTW_DCH, &data->ftw); return result; } } /* Next, update the `struct FTW' information. */ ++data->ftw.level; startp = strchr (data->dirbuf, '\0'); /* There always must be a directory name. */ assert (startp != data->dirbuf); if (startp[-1] != '/') *startp++ = '/'; data->ftw.base = startp - data->dirbuf; while (dir.stream != NULL && (d = __readdir64 (dir.stream)) != NULL) { result = process_entry (data, &dir, d->d_name, NAMLEN (d)); if (result != 0) break; } if (dir.stream != NULL) { /* The stream is still open. I.e., we did not need more descriptors. Simply close the stream now. */ int save_err = errno; assert (dir.content == NULL); __closedir (dir.stream); __set_errno (save_err); if (data->actdir-- == 0) data->actdir = data->maxdir - 1; data->dirstreams[data->actdir] = NULL; } else { int save_err; char *runp = dir.content; while (result == 0 && *runp != '\0') { char *endp = strchr (runp, '\0'); result = process_entry (data, &dir, runp, endp - runp); runp = endp + 1; } save_err = errno; free (dir.content); __set_errno (save_err); } /* Prepare the return, revert the `struct FTW' information. */ data->dirbuf[data->ftw.base - 1] = '\0'; --data->ftw.level; data->ftw.base = previous_base; /* Finally, if we process depth-first report the directory. */ if (result == 0 && (data->flags & FTW_DEPTH)) result = (*data->func) (data->dirbuf, st, FTW_DP, &data->ftw); return result; } #ifdef _LIBC # define ISSLASH(C) ((C) == '/') # define FILESYSTEM_PREFIX_LEN(Filename) 0 #endif /* In general, we can't use the builtin `basename' function if available, since it has different meanings in different environments. In some environments the builtin `basename' modifies its argument. Return the address of the last file name component of NAME. If NAME has no file name components because it is all slashes, return NAME if it is empty, the address of its last slash otherwise. */ static char * base_name (char const *name) { char const *base = name + FILESYSTEM_PREFIX_LEN (name); char const *p; for (p = base; *p; p++) { if (ISSLASH (*p)) { /* Treat multiple adjacent slashes like a single slash. */ do p++; while (ISSLASH (*p)); /* If the file name ends in slash, use the trailing slash as the basename if no non-slashes have been found. */ if (! *p) { if (ISSLASH (*base)) base = p - 1; break; } /* *P is a non-slash preceded by a slash. */ base = p; } } return (char *) base; } static int internal_function ftw_startup (const char *dir, int is_nftw, NFTW_FUNC_T func, int descriptors, int flags) { struct ftw_data data; struct FTW_STAT st; int result = 0; int save_err; struct saved_cwd cwd; size_t dir_len; /* First make sure the parameters are reasonable. */ if (dir[0] == '\0') { __set_errno (ENOENT); return -1; } data.maxdir = descriptors < 1 ? 1 : descriptors; data.actdir = 0; data.dirstreams = (struct dir_data **) alloca (data.maxdir * sizeof (struct dir_data *)); if (data.dirstreams == NULL) return -1; memset (data.dirstreams, '\0', data.maxdir * sizeof (struct dir_data *)); /* PATH_MAX is always defined when we get here. */ dir_len = strlen (dir); data.dirbufsize = MAX (2 * dir_len, PATH_MAX); data.dirbuf = (char *) malloc (data.dirbufsize); if (data.dirbuf == NULL) return -1; memcpy (data.dirbuf, dir, dir_len + 1); data.ftw.level = 0; /* Find offset of basename. */ data.ftw.base = base_name (data.dirbuf) - data.dirbuf; data.flags = flags; /* This assignment might seem to be strange but it is what we want. The trick is that the first three arguments to the `ftw' and `nftw' callback functions are equal. Therefore we can call in every case the callback using the format of the `nftw' version and get the correct result since the stack layout for a function call in C allows this. */ data.func = func; /* Since we internally use the complete set of FTW_* values we need to reduce the value range before calling a `ftw' callback. */ data.cvt_arr = is_nftw ? nftw_arr : ftw_arr; /* No object known so far. */ data.known_objects = NULL; /* Now go to the directory containing the initial file/directory. */ if (flags & FTW_CHDIR) { if (save_cwd (&cwd)) result = -1; else if (data.ftw.base > 0) { /* Change to the directory the file is in. In data.dirbuf we have a writable copy of the file name. Just NUL terminate it for now and change the directory. */ if (data.ftw.base == 1) /* I.e., the file is in the root directory. */ result = __chdir ("/"); else { char ch = data.dirbuf[data.ftw.base - 1]; data.dirbuf[data.ftw.base - 1] = '\0'; result = __chdir (data.dirbuf); data.dirbuf[data.ftw.base - 1] = ch; } } } /* Get stat info for start directory. */ if (result == 0) { const char *name = ((data.flags & FTW_CHDIR) ? data.dirbuf + data.ftw.base : data.dirbuf); if (((flags & FTW_PHYS) ? LXSTAT (_STAT_VER, name, &st) : XSTAT (_STAT_VER, name, &st)) < 0) { if (!(flags & FTW_PHYS) && errno == ENOENT && LXSTAT (_STAT_VER, name, &st) == 0 && S_ISLNK (st.st_mode)) result = (*data.func) (data.dirbuf, &st, data.cvt_arr[FTW_SLN], &data.ftw); else /* No need to call the callback since we cannot say anything about the object. */ result = -1; } else { if (S_ISDIR (st.st_mode)) { /* Remember the device of the initial directory in case FTW_MOUNT is given. */ data.dev = st.st_dev; /* We know this directory now. */ if (!(flags & FTW_PHYS)) result = add_object (&data, &st); if (result == 0) { /* If we're doing a depth-first traversal, give the user a chance to prune the top-level directory. */ if ((flags & FTW_DEPTH) && (result = (*data.func) (data.dirbuf, &st, FTW_DPRE, &data.ftw)) == 0 && ! data.ftw.skip) result = ftw_dir (&data, &st); } } else { int flag = S_ISLNK (st.st_mode) ? FTW_SL : FTW_F; result = (*data.func) (data.dirbuf, &st, data.cvt_arr[flag], &data.ftw); } } } /* Return to the start directory (if necessary). */ if (flags & FTW_CHDIR) { save_err = errno; /* If restore_cwd fails and there wasn't a prior failure, then let this new errno override any prior value. FIXME: ideally, we'd be able to return some indication of what the failure means. Otherwise, the caller will have a hard time distinguishing between e.g., `out of memory' and this sort of failure. */ if (restore_cwd (&cwd) && result == 0) { save_err = errno; result = -1; } __set_errno (save_err); } /* Free all memory. */ save_err = errno; __tdestroy (data.known_objects, free); free (data.dirbuf); __set_errno (save_err); return result; } /* Entry points. */ int FTW_NAME (path, func, descriptors) const char *path; FTW_FUNC_T func; int descriptors; { return ftw_startup (path, 0, (NFTW_FUNC_T) func, descriptors, 0); } int NFTW_NAME (path, func, descriptors, flags) const char *path; NFTW_FUNC_T func; int descriptors; int flags; { return ftw_startup (path, 1, func, descriptors, flags); }
/* dirfd.c -- return the file descriptor associated with an open DIR* Copyright (C) 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Jim Meyering. */ #if HAVE_CONFIG_H # include <config.h> #endif #include "dirfd.h" int dirfd (DIR const *dir_p) { return DIR_TO_FD (dir_p); }
/* obstack.c - subroutines used implicitly by object stack macros Copyright (C) 1988-1994, 1996-1999, 2000-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include "obstack.h" /* NOTE BEFORE MODIFYING THIS FILE: This version number must be incremented whenever callers compiled using an old obstack.h can no longer properly call the functions in this obstack.c. */ #define OBSTACK_INTERFACE_VERSION 1 /* Comment out all this code if we are using the GNU C Library, and are not actually compiling the library itself, and the installed library supports the same library interface we do. This code is part of the GNU C Library, but also included in many other GNU distributions. Compiling and linking in this code is a waste when using the GNU C library (especially if it is a shared library). Rather than having every GNU program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ #include <stdio.h> /* Random thing to get __GNU_LIBRARY__. */ #if !defined _LIBC && defined __GNU_LIBRARY__ && __GNU_LIBRARY__ > 1 # include <gnu-versions.h> # if _GNU_OBSTACK_INTERFACE_VERSION == OBSTACK_INTERFACE_VERSION # define ELIDE_CODE # endif #endif #if defined _LIBC && defined USE_IN_LIBIO # include <wchar.h> #endif #ifndef ELIDE_CODE # if defined __STDC__ && __STDC__ # define POINTER void * # else # define POINTER char * # endif /* Determine default alignment. */ struct fooalign {char x; double d;}; # define DEFAULT_ALIGNMENT \ ((PTR_INT_TYPE) ((char *) &((struct fooalign *) 0)->d - (char *) 0)) /* If malloc were really smart, it would round addresses to DEFAULT_ALIGNMENT. But in fact it might be less smart and round addresses to as much as DEFAULT_ROUNDING. So we prepare for it to do that. */ union fooround {long x; double d;}; # define DEFAULT_ROUNDING (sizeof (union fooround)) /* When we copy a long block of data, this is the unit to do it with. On some machines, copying successive ints does not work; in such a case, redefine COPYING_UNIT to `long' (if that works) or `char' as a last resort. */ # ifndef COPYING_UNIT # define COPYING_UNIT int # endif /* The functions allocating more room by calling `obstack_chunk_alloc' jump to the handler pointed to by `obstack_alloc_failed_handler'. This can be set to a user defined function which should either abort gracefully or use longjump - but shouldn't return. This variable by default points to the internal function `print_and_abort'. */ # if defined __STDC__ && __STDC__ static void print_and_abort (void); void (*obstack_alloc_failed_handler) (void) = print_and_abort; # else static void print_and_abort (); void (*obstack_alloc_failed_handler) () = print_and_abort; # endif /* Exit value used when `print_and_abort' is used. */ # if defined __GNU_LIBRARY__ || defined HAVE_STDLIB_H # include <stdlib.h> # endif # ifndef EXIT_FAILURE # define EXIT_FAILURE 1 # endif int obstack_exit_failure = EXIT_FAILURE; /* The non-GNU-C macros copy the obstack into this global variable to avoid multiple evaluation. */ struct obstack *_obstack; /* Define a macro that either calls functions with the traditional malloc/free calling interface, or calls functions with the mmalloc/mfree interface (that adds an extra first argument), based on the state of use_extra_arg. For free, do not use ?:, since some compilers, like the MIPS compilers, do not allow (expr) ? void : void. */ # if defined __STDC__ && __STDC__ # define CALL_CHUNKFUN(h, size) \ (((h) -> use_extra_arg) \ ? (*(h)->chunkfun) ((h)->extra_arg, (size)) \ : (*(struct _obstack_chunk *(*) (long)) (h)->chunkfun) ((size))) # define CALL_FREEFUN(h, old_chunk) \ do { \ if ((h) -> use_extra_arg) \ (*(h)->freefun) ((h)->extra_arg, (old_chunk)); \ else \ (*(void (*) (void *)) (h)->freefun) ((old_chunk)); \ } while (0) # else # define CALL_CHUNKFUN(h, size) \ (((h) -> use_extra_arg) \ ? (*(h)->chunkfun) ((h)->extra_arg, (size)) \ : (*(struct _obstack_chunk *(*) ()) (h)->chunkfun) ((size))) # define CALL_FREEFUN(h, old_chunk) \ do { \ if ((h) -> use_extra_arg) \ (*(h)->freefun) ((h)->extra_arg, (old_chunk)); \ else \ (*(void (*) ()) (h)->freefun) ((old_chunk)); \ } while (0) # endif /* Initialize an obstack H for use. Specify chunk size SIZE (0 means default). Objects start on multiples of ALIGNMENT (0 means use default). CHUNKFUN is the function to use to allocate chunks, and FREEFUN the function to free them. Return nonzero if successful, calls obstack_alloc_failed_handler if allocation fails. */ int _obstack_begin (h, size, alignment, chunkfun, freefun) struct obstack *h; int size; int alignment; # if defined __STDC__ && __STDC__ POINTER (*chunkfun) (long); void (*freefun) (void *); # else POINTER (*chunkfun) (); void (*freefun) (); # endif { register struct _obstack_chunk *chunk; /* points to new chunk */ if (alignment == 0) alignment = (int) DEFAULT_ALIGNMENT; if (size == 0) /* Default size is what GNU malloc can fit in a 4096-byte block. */ { /* 12 is sizeof (mhead) and 4 is EXTRA from GNU malloc. Use the values for range checking, because if range checking is off, the extra bytes won't be missed terribly, but if range checking is on and we used a larger request, a whole extra 4096 bytes would be allocated. These number are irrelevant to the new GNU malloc. I suspect it is less sensitive to the size of the request. */ int extra = ((((12 + DEFAULT_ROUNDING - 1) & ~(DEFAULT_ROUNDING - 1)) + 4 + DEFAULT_ROUNDING - 1) & ~(DEFAULT_ROUNDING - 1)); size = 4096 - extra; } # if defined __STDC__ && __STDC__ h->chunkfun = (struct _obstack_chunk * (*)(void *, long)) chunkfun; h->freefun = (void (*) (void *, struct _obstack_chunk *)) freefun; # else h->chunkfun = (struct _obstack_chunk * (*)()) chunkfun; h->freefun = freefun; # endif h->chunk_size = size; h->alignment_mask = alignment - 1; h->use_extra_arg = 0; chunk = h->chunk = CALL_CHUNKFUN (h, h -> chunk_size); if (!chunk) (*obstack_alloc_failed_handler) (); h->next_free = h->object_base = chunk->contents; h->chunk_limit = chunk->limit = (char *) chunk + h->chunk_size; chunk->prev = 0; /* The initial chunk now contains no empty object. */ h->maybe_empty_object = 0; h->alloc_failed = 0; return 1; } int _obstack_begin_1 (h, size, alignment, chunkfun, freefun, arg) struct obstack *h; int size; int alignment; # if defined __STDC__ && __STDC__ POINTER (*chunkfun) (POINTER, long); void (*freefun) (POINTER, POINTER); # else POINTER (*chunkfun) (); void (*freefun) (); # endif POINTER arg; { register struct _obstack_chunk *chunk; /* points to new chunk */ if (alignment == 0) alignment = (int) DEFAULT_ALIGNMENT; if (size == 0) /* Default size is what GNU malloc can fit in a 4096-byte block. */ { /* 12 is sizeof (mhead) and 4 is EXTRA from GNU malloc. Use the values for range checking, because if range checking is off, the extra bytes won't be missed terribly, but if range checking is on and we used a larger request, a whole extra 4096 bytes would be allocated. These number are irrelevant to the new GNU malloc. I suspect it is less sensitive to the size of the request. */ int extra = ((((12 + DEFAULT_ROUNDING - 1) & ~(DEFAULT_ROUNDING - 1)) + 4 + DEFAULT_ROUNDING - 1) & ~(DEFAULT_ROUNDING - 1)); size = 4096 - extra; } # if defined __STDC__ && __STDC__ h->chunkfun = (struct _obstack_chunk * (*)(void *,long)) chunkfun; h->freefun = (void (*) (void *, struct _obstack_chunk *)) freefun; # else h->chunkfun = (struct _obstack_chunk * (*)()) chunkfun; h->freefun = freefun; # endif h->chunk_size = size; h->alignment_mask = alignment - 1; h->extra_arg = arg; h->use_extra_arg = 1; chunk = h->chunk = CALL_CHUNKFUN (h, h -> chunk_size); if (!chunk) (*obstack_alloc_failed_handler) (); h->next_free = h->object_base = chunk->contents; h->chunk_limit = chunk->limit = (char *) chunk + h->chunk_size; chunk->prev = 0; /* The initial chunk now contains no empty object. */ h->maybe_empty_object = 0; h->alloc_failed = 0; return 1; } /* Allocate a new current chunk for the obstack *H on the assumption that LENGTH bytes need to be added to the current object, or a new object of length LENGTH allocated. Copies any partial object from the end of the old chunk to the beginning of the new one. */ void _obstack_newchunk (h, length) struct obstack *h; int length; { register struct _obstack_chunk *old_chunk = h->chunk; register struct _obstack_chunk *new_chunk; register long new_size; register long obj_size = h->next_free - h->object_base; register long i; long already; char *object_base; /* Compute size for new chunk. */ new_size = (obj_size + length) + (obj_size >> 3) + h->alignment_mask + 100; if (new_size < h->chunk_size) new_size = h->chunk_size; /* Allocate and initialize the new chunk. */ new_chunk = CALL_CHUNKFUN (h, new_size); if (!new_chunk) (*obstack_alloc_failed_handler) (); h->chunk = new_chunk; new_chunk->prev = old_chunk; new_chunk->limit = h->chunk_limit = (char *) new_chunk + new_size; /* Compute an aligned object_base in the new chunk */ object_base = __INT_TO_PTR ((__PTR_TO_INT (new_chunk->contents) + h->alignment_mask) & ~ (h->alignment_mask)); /* Move the existing object to the new chunk. Word at a time is fast and is safe if the object is sufficiently aligned. */ if (h->alignment_mask + 1 >= DEFAULT_ALIGNMENT) { for (i = obj_size / sizeof (COPYING_UNIT) - 1; i >= 0; i--) ((COPYING_UNIT *)object_base)[i] = ((COPYING_UNIT *)h->object_base)[i]; /* We used to copy the odd few remaining bytes as one extra COPYING_UNIT, but that can cross a page boundary on a machine which does not do strict alignment for COPYING_UNITS. */ already = obj_size / sizeof (COPYING_UNIT) * sizeof (COPYING_UNIT); } else already = 0; /* Copy remaining bytes one by one. */ for (i = already; i < obj_size; i++) object_base[i] = h->object_base[i]; /* If the object just copied was the only data in OLD_CHUNK, free that chunk and remove it from the chain. But not if that chunk might contain an empty object. */ if (h->object_base == old_chunk->contents && ! h->maybe_empty_object) { new_chunk->prev = old_chunk->prev; CALL_FREEFUN (h, old_chunk); } h->object_base = object_base; h->next_free = h->object_base + obj_size; /* The new chunk certainly contains no empty object yet. */ h->maybe_empty_object = 0; } /* Return nonzero if object OBJ has been allocated from obstack H. This is here for debugging. If you use it in a program, you are probably losing. */ # if defined __STDC__ && __STDC__ /* Suppress -Wmissing-prototypes warning. We don't want to declare this in obstack.h because it is just for debugging. */ int _obstack_allocated_p (struct obstack *h, POINTER obj); # endif int _obstack_allocated_p (h, obj) struct obstack *h; POINTER obj; { register struct _obstack_chunk *lp; /* below addr of any objects in this chunk */ register struct _obstack_chunk *plp; /* point to previous chunk if any */ lp = (h)->chunk; /* We use >= rather than > since the object cannot be exactly at the beginning of the chunk but might be an empty object exactly at the end of an adjacent chunk. */ while (lp != 0 && ((POINTER) lp >= obj || (POINTER) (lp)->limit < obj)) { plp = lp->prev; lp = plp; } return lp != 0; } /* Free objects in obstack H, including OBJ and everything allocate more recently than OBJ. If OBJ is zero, free everything in H. */ # undef obstack_free /* This function has two names with identical definitions. This is the first one, called from non-ANSI code. */ void _obstack_free (h, obj) struct obstack *h; POINTER obj; { register struct _obstack_chunk *lp; /* below addr of any objects in this chunk */ register struct _obstack_chunk *plp; /* point to previous chunk if any */ lp = h->chunk; /* We use >= because there cannot be an object at the beginning of a chunk. But there can be an empty object at that address at the end of another chunk. */ while (lp != 0 && ((POINTER) lp >= obj || (POINTER) (lp)->limit < obj)) { plp = lp->prev; CALL_FREEFUN (h, lp); lp = plp; /* If we switch chunks, we can't tell whether the new current chunk contains an empty object, so assume that it may. */ h->maybe_empty_object = 1; } if (lp) { h->object_base = h->next_free = (char *) (obj); h->chunk_limit = lp->limit; h->chunk = lp; } else if (obj != 0) /* obj is not in any of the chunks! */ abort (); } /* This function is used from ANSI code. */ void obstack_free (h, obj) struct obstack *h; POINTER obj; { register struct _obstack_chunk *lp; /* below addr of any objects in this chunk */ register struct _obstack_chunk *plp; /* point to previous chunk if any */ lp = h->chunk; /* We use >= because there cannot be an object at the beginning of a chunk. But there can be an empty object at that address at the end of another chunk. */ while (lp != 0 && ((POINTER) lp >= obj || (POINTER) (lp)->limit < obj)) { plp = lp->prev; CALL_FREEFUN (h, lp); lp = plp; /* If we switch chunks, we can't tell whether the new current chunk contains an empty object, so assume that it may. */ h->maybe_empty_object = 1; } if (lp) { h->object_base = h->next_free = (char *) (obj); h->chunk_limit = lp->limit; h->chunk = lp; } else if (obj != 0) /* obj is not in any of the chunks! */ abort (); } int _obstack_memory_used (h) struct obstack *h; { register struct _obstack_chunk* lp; register int nbytes = 0; for (lp = h->chunk; lp != 0; lp = lp->prev) { nbytes += lp->limit - (char *) lp; } return nbytes; } /* Define the error handler. */ # ifdef _LIBC # include <libintl.h> # else # include "gettext.h" # endif # define _(msgid) gettext (msgid) # if defined _LIBC && defined USE_IN_LIBIO # include <libio/iolibio.h> # define fputs(s, f) _IO_fputs (s, f) # endif # ifndef __attribute__ /* This feature is available in gcc versions 2.5 and later. */ # if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) # define __attribute__(Spec) /* empty */ # endif # endif static void __attribute__ ((noreturn)) print_and_abort () { /* Don't change any of these strings. Yes, it would be possible to add the newline to the string and use fputs or so. But this must not happen because the "memory exhausted" message appears in other places like this and the translation should be reused instead of creating a very similar string which requires a separate translation. */ # if defined _LIBC && defined USE_IN_LIBIO if (_IO_fwide (stderr, 0) > 0) __fwprintf (stderr, L"%s\n", _("memory exhausted")); else # endif fprintf (stderr, "%s\n", _("memory exhausted")); exit (obstack_exit_failure); } # if 0 /* These are now turned off because the applications do not use it and it uses bcopy via obstack_grow, which causes trouble on sysV. */ /* Now define the functional versions of the obstack macros. Define them to simply use the corresponding macros to do the job. */ # if defined __STDC__ && __STDC__ /* These function definitions do not work with non-ANSI preprocessors; they won't pass through the macro names in parentheses. */ /* The function names appear in parentheses in order to prevent the macro-definitions of the names from being expanded there. */ POINTER (obstack_base) (obstack) struct obstack *obstack; { return obstack_base (obstack); } POINTER (obstack_next_free) (obstack) struct obstack *obstack; { return obstack_next_free (obstack); } int (obstack_object_size) (obstack) struct obstack *obstack; { return obstack_object_size (obstack); } int (obstack_room) (obstack) struct obstack *obstack; { return obstack_room (obstack); } int (obstack_make_room) (obstack, length) struct obstack *obstack; int length; { return obstack_make_room (obstack, length); } void (obstack_grow) (obstack, data, length) struct obstack *obstack; const POINTER data; int length; { obstack_grow (obstack, data, length); } void (obstack_grow0) (obstack, data, length) struct obstack *obstack; const POINTER data; int length; { obstack_grow0 (obstack, data, length); } void (obstack_1grow) (obstack, character) struct obstack *obstack; int character; { obstack_1grow (obstack, character); } void (obstack_blank) (obstack, length) struct obstack *obstack; int length; { obstack_blank (obstack, length); } void (obstack_1grow_fast) (obstack, character) struct obstack *obstack; int character; { obstack_1grow_fast (obstack, character); } void (obstack_blank_fast) (obstack, length) struct obstack *obstack; int length; { obstack_blank_fast (obstack, length); } POINTER (obstack_finish) (obstack) struct obstack *obstack; { return obstack_finish (obstack); } POINTER (obstack_alloc) (obstack, length) struct obstack *obstack; int length; { return obstack_alloc (obstack, length); } POINTER (obstack_copy) (obstack, address, length) struct obstack *obstack; const POINTER address; int length; { return obstack_copy (obstack, address, length); } POINTER (obstack_copy0) (obstack, address, length) struct obstack *obstack; const POINTER address; int length; { return obstack_copy0 (obstack, address, length); } # endif /* __STDC__ */ # endif /* 0 */ #endif /* !ELIDE_CODE */
/* Compare strings while treating digits characters numerically. Copyright (C) 1997, 2000, 2002 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Jean-François Bignolles <bignolle@ecoledoc.ibp.fr>, 1997. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #include <string.h> #include <ctype.h> /* states: S_N: normal, S_I: comparing integral part, S_F: comparing fractional parts, S_Z: idem but with leading Zeroes only */ #define S_N 0x0 #define S_I 0x4 #define S_F 0x8 #define S_Z 0xC /* result_type: CMP: return diff; LEN: compare using len_diff/diff */ #define CMP 2 #define LEN 3 /* ISDIGIT differs from isdigit, as follows: - Its arg may be any int or unsigned int; it need not be an unsigned char. - It's guaranteed to evaluate its argument exactly once. - It's typically faster. POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to ISDIGIT_LOCALE unless it's important to use the locale's definition of `digit' even when the host does not conform to POSIX. */ #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9) #undef __strverscmp #undef strverscmp #ifndef weak_alias # define __strverscmp strverscmp #endif /* Compare S1 and S2 as strings holding indices/version numbers, returning less than, equal to or greater than zero if S1 is less than, equal to or greater than S2 (for more info, see the texinfo doc). */ int __strverscmp (const char *s1, const char *s2) { const unsigned char *p1 = (const unsigned char *) s1; const unsigned char *p2 = (const unsigned char *) s2; unsigned char c1, c2; int state; int diff; /* Symbol(s) 0 [1-9] others (padding) Transition (10) 0 (01) d (00) x (11) - */ static const unsigned int next_state[] = { /* state x d 0 - */ /* S_N */ S_N, S_I, S_Z, S_N, /* S_I */ S_N, S_I, S_I, S_I, /* S_F */ S_N, S_F, S_F, S_F, /* S_Z */ S_N, S_F, S_Z, S_Z }; static const int result_type[] = { /* state x/x x/d x/0 x/- d/x d/d d/0 d/- 0/x 0/d 0/0 0/- -/x -/d -/0 -/- */ /* S_N */ CMP, CMP, CMP, CMP, CMP, LEN, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, /* S_I */ CMP, -1, -1, CMP, 1, LEN, LEN, CMP, 1, LEN, LEN, CMP, CMP, CMP, CMP, CMP, /* S_F */ CMP, CMP, CMP, CMP, CMP, LEN, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, /* S_Z */ CMP, 1, 1, CMP, -1, CMP, CMP, CMP, -1, CMP, CMP, CMP }; if (p1 == p2) return 0; c1 = *p1++; c2 = *p2++; /* Hint: '0' is a digit too. */ state = S_N | ((c1 == '0') + (ISDIGIT (c1) != 0)); while ((diff = c1 - c2) == 0 && c1 != '\0') { state = next_state[state]; c1 = *p1++; c2 = *p2++; state |= (c1 == '0') + (ISDIGIT (c1) != 0); } state = result_type[state << 2 | ((c2 == '0') + (ISDIGIT (c2) != 0))]; switch (state) { case CMP: return diff; case LEN: while (ISDIGIT (*p1++)) if (!ISDIGIT (*p2++)) return 1; return ISDIGIT (*p2) ? -1 : diff; default: return state; } } #ifdef weak_alias weak_alias (__strverscmp, strverscmp) #endif
/* Copyright (C) 1995, 1996, 1997, 2000 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Bernd Schmidt <crux@Pool.Informatik.RWTH-Aachen.DE>, 1997. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ /* Tree search for red/black trees. The algorithm for adding nodes is taken from one of the many "Algorithms" books by Robert Sedgewick, although the implementation differs. The algorithm for deleting nodes can probably be found in a book named "Introduction to Algorithms" by Cormen/Leiserson/Rivest. At least that's the book that my professor took most algorithms from during the "Data Structures" course... Totally public domain. */ /* Red/black trees are binary trees in which the edges are colored either red or black. They have the following properties: 1. The number of black edges on every path from the root to a leaf is constant. 2. No two red edges are adjacent. Therefore there is an upper bound on the length of every path, it's O(log n) where n is the number of nodes in the tree. No path can be longer than 1+2*P where P is the length of the shortest path in the tree. Useful for the implementation: 3. If one of the children of a node is NULL, then the other one is red (if it exists). In the implementation, not the edges are colored, but the nodes. The color interpreted as the color of the edge leading to this node. The color is meaningless for the root node, but we color the root node black for convenience. All added nodes are red initially. Adding to a red/black tree is rather easy. The right place is searched with a usual binary tree search. Additionally, whenever a node N is reached that has two red successors, the successors are colored black and the node itself colored red. This moves red edges up the tree where they pose less of a problem once we get to really insert the new node. Changing N's color to red may violate rule 2, however, so rotations may become necessary to restore the invariants. Adding a new red leaf may violate the same rule, so afterwards an additional check is run and the tree possibly rotated. Deleting is hairy. There are mainly two nodes involved: the node to be deleted (n1), and another node that is to be unchained from the tree (n2). If n1 has a successor (the node with a smallest key that is larger than n1), then the successor becomes n2 and its contents are copied into n1, otherwise n1 becomes n2. Unchaining a node may violate rule 1: if n2 is black, one subtree is missing one black edge afterwards. The algorithm must try to move this error upwards towards the root, so that the subtree that does not have enough black edges becomes the whole tree. Once that happens, the error has disappeared. It may not be necessary to go all the way up, since it is possible that rotations and recoloring can fix the error before that. Although the deletion algorithm must walk upwards through the tree, we do not store parent pointers in the nodes. Instead, delete allocates a small array of parent pointers and fills it while descending the tree. Since we know that the length of a path is O(log n), where n is the number of nodes, this is likely to use less memory. */ /* Tree rotations look like this: A C / \ / \ B C A G / \ / \ --> / \ D E F G B F / \ D E In this case, A has been rotated left. This preserves the ordering of the binary tree. */ #if HAVE_CONFIG_H # include <config.h> #endif #if __GNUC__ # define alloca __builtin_alloca #else # if HAVE_ALLOCA_H # include <alloca.h> # else # ifdef _AIX # pragma alloca # else char *alloca (); # endif # endif #endif #include <stdlib.h> #include <string.h> #include <search.h> #ifndef weak_alias # define __tsearch tsearch # define __tfind tfind # define __tdelete tdelete # define __twalk twalk # define __tdestroy tdestroy #endif #ifndef _LIBC # define weak_alias(f,g) # define internal_function #endif typedef struct node_t { /* Callers expect this to be the first element in the structure - do not move! */ const void *key; struct node_t *left; struct node_t *right; unsigned int red:1; } *node; typedef const struct node_t *const_node; #undef DEBUGGING #ifdef DEBUGGING /* Routines to check tree invariants. */ # include <assert.h> # define CHECK_TREE(a) check_tree(a) static void check_tree_recurse (node p, int d_sofar, int d_total) { if (p == NULL) { assert (d_sofar == d_total); return; } check_tree_recurse (p->left, d_sofar + (p->left && !p->left->red), d_total); check_tree_recurse (p->right, d_sofar + (p->right && !p->right->red), d_total); if (p->left) assert (!(p->left->red && p->red)); if (p->right) assert (!(p->right->red && p->red)); } static void check_tree (node root) { int cnt = 0; node p; if (root == NULL) return; root->red = 0; for(p = root->left; p; p = p->left) cnt += !p->red; check_tree_recurse (root, 0, cnt); } #else # define CHECK_TREE(a) #endif /* Possibly "split" a node with two red successors, and/or fix up two red edges in a row. ROOTP is a pointer to the lowest node we visited, PARENTP and GPARENTP pointers to its parent/grandparent. P_R and GP_R contain the comparison values that determined which way was taken in the tree to reach ROOTP. MODE is 1 if we need not do the split, but must check for two red edges between GPARENTP and ROOTP. */ static void maybe_split_for_insert (node *rootp, node *parentp, node *gparentp, int p_r, int gp_r, int mode) { node root = *rootp; node *rp, *lp; rp = &(*rootp)->right; lp = &(*rootp)->left; /* See if we have to split this node (both successors red). */ if (mode == 1 || ((*rp) != NULL && (*lp) != NULL && (*rp)->red && (*lp)->red)) { /* This node becomes red, its successors black. */ root->red = 1; if (*rp) (*rp)->red = 0; if (*lp) (*lp)->red = 0; /* If the parent of this node is also red, we have to do rotations. */ if (parentp != NULL && (*parentp)->red) { node gp = *gparentp; node p = *parentp; /* There are two main cases: 1. The edge types (left or right) of the two red edges differ. 2. Both red edges are of the same type. There exist two symmetries of each case, so there is a total of 4 cases. */ if ((p_r > 0) != (gp_r > 0)) { /* Put the child at the top of the tree, with its parent and grandparent as successors. */ p->red = 1; gp->red = 1; root->red = 0; if (p_r < 0) { /* Child is left of parent. */ p->left = *rp; *rp = p; gp->right = *lp; *lp = gp; } else { /* Child is right of parent. */ p->right = *lp; *lp = p; gp->left = *rp; *rp = gp; } *gparentp = root; } else { *gparentp = *parentp; /* Parent becomes the top of the tree, grandparent and child are its successors. */ p->red = 0; gp->red = 1; if (p_r < 0) { /* Left edges. */ gp->left = p->right; p->right = gp; } else { /* Right edges. */ gp->right = p->left; p->left = gp; } } } } } /* Find or insert datum into search tree. KEY is the key to be located, ROOTP is the address of tree root, COMPAR the ordering function. */ void * __tsearch (const void *key, void **vrootp, __compar_fn_t compar) { node q; node *parentp = NULL, *gparentp = NULL; node *rootp = (node *) vrootp; node *nextp; int r = 0, p_r = 0, gp_r = 0; /* No they might not, Mr Compiler. */ if (rootp == NULL) return NULL; /* This saves some additional tests below. */ if (*rootp != NULL) (*rootp)->red = 0; CHECK_TREE (*rootp); nextp = rootp; while (*nextp != NULL) { node root = *rootp; r = (*compar) (key, root->key); if (r == 0) return root; maybe_split_for_insert (rootp, parentp, gparentp, p_r, gp_r, 0); /* If that did any rotations, parentp and gparentp are now garbage. That doesn't matter, because the values they contain are never used again in that case. */ nextp = r < 0 ? &root->left : &root->right; if (*nextp == NULL) break; gparentp = parentp; parentp = rootp; rootp = nextp; gp_r = p_r; p_r = r; } q = (struct node_t *) malloc (sizeof (struct node_t)); if (q != NULL) { *nextp = q; /* link new node to old */ q->key = key; /* initialize new node */ q->red = 1; q->left = q->right = NULL; } if (nextp != rootp) /* There may be two red edges in a row now, which we must avoid by rotating the tree. */ maybe_split_for_insert (nextp, rootp, parentp, r, p_r, 1); return q; } #ifdef weak_alias weak_alias (__tsearch, tsearch) #endif /* Find datum in search tree. KEY is the key to be located, ROOTP is the address of tree root, COMPAR the ordering function. */ void * __tfind (key, vrootp, compar) const void *key; void *const *vrootp; __compar_fn_t compar; { node *rootp = (node *) vrootp; if (rootp == NULL) return NULL; CHECK_TREE (*rootp); while (*rootp != NULL) { node root = *rootp; int r; r = (*compar) (key, root->key); if (r == 0) return root; rootp = r < 0 ? &root->left : &root->right; } return NULL; } #ifdef weak_alias weak_alias (__tfind, tfind) #endif /* Delete node with given key. KEY is the key to be deleted, ROOTP is the address of the root of tree, COMPAR the comparison function. */ void * __tdelete (const void *key, void **vrootp, __compar_fn_t compar) { node p, q, r, retval; int cmp; node *rootp = (node *) vrootp; node root, unchained; /* Stack of nodes so we remember the parents without recursion. It's _very_ unlikely that there are paths longer than 40 nodes. The tree would need to have around 250.000 nodes. */ int stacksize = 40; int sp = 0; node **nodestack = (node **) alloca (sizeof (node *) * stacksize); if (rootp == NULL) return NULL; p = *rootp; if (p == NULL) return NULL; CHECK_TREE (p); while ((cmp = (*compar) (key, (*rootp)->key)) != 0) { if (sp == stacksize) { node **newstack; stacksize += 20; newstack = (node **) alloca (sizeof (node *) * stacksize); nodestack = memcpy (newstack, nodestack, sp * sizeof (node *)); } nodestack[sp++] = rootp; p = *rootp; rootp = ((cmp < 0) ? &(*rootp)->left : &(*rootp)->right); if (*rootp == NULL) return NULL; } /* This is bogus if the node to be deleted is the root... this routine really should return an integer with 0 for success, -1 for failure and errno = ESRCH or something. */ retval = p; /* We don't unchain the node we want to delete. Instead, we overwrite it with its successor and unchain the successor. If there is no successor, we really unchain the node to be deleted. */ root = *rootp; r = root->right; q = root->left; if (q == NULL || r == NULL) unchained = root; else { node *parent = rootp, *up = &root->right; for (;;) { if (sp == stacksize) { node **newstack; stacksize += 20; newstack = (node **) alloca (sizeof (node *) * stacksize); nodestack = memcpy (newstack, nodestack, sp * sizeof (node *)); } nodestack[sp++] = parent; parent = up; if ((*up)->left == NULL) break; up = &(*up)->left; } unchained = *up; } /* We know that either the left or right successor of UNCHAINED is NULL. R becomes the other one, it is chained into the parent of UNCHAINED. */ r = unchained->left; if (r == NULL) r = unchained->right; if (sp == 0) *rootp = r; else { q = *nodestack[sp-1]; if (unchained == q->right) q->right = r; else q->left = r; } if (unchained != root) root->key = unchained->key; if (!unchained->red) { /* Now we lost a black edge, which means that the number of black edges on every path is no longer constant. We must balance the tree. */ /* NODESTACK now contains all parents of R. R is likely to be NULL in the first iteration. */ /* NULL nodes are considered black throughout - this is necessary for correctness. */ while (sp > 0 && (r == NULL || !r->red)) { node *pp = nodestack[sp - 1]; p = *pp; /* Two symmetric cases. */ if (r == p->left) { /* Q is R's brother, P is R's parent. The subtree with root R has one black edge less than the subtree with root Q. */ q = p->right; if (q != NULL && q->red) { /* If Q is red, we know that P is black. We rotate P left so that Q becomes the top node in the tree, with P below it. P is colored red, Q is colored black. This action does not change the black edge count for any leaf in the tree, but we will be able to recognize one of the following situations, which all require that Q is black. */ q->red = 0; p->red = 1; /* Left rotate p. */ p->right = q->left; q->left = p; *pp = q; /* Make sure pp is right if the case below tries to use it. */ nodestack[sp++] = pp = &q->left; q = p->right; } /* We know that Q can't be NULL here. We also know that Q is black. */ if ((q->left == NULL || !q->left->red) && (q->right == NULL || !q->right->red)) { /* Q has two black successors. We can simply color Q red. The whole subtree with root P is now missing one black edge. Note that this action can temporarily make the tree invalid (if P is red). But we will exit the loop in that case and set P black, which both makes the tree valid and also makes the black edge count come out right. If P is black, we are at least one step closer to the root and we'll try again the next iteration. */ q->red = 1; r = p; } else { /* Q is black, one of Q's successors is red. We can repair the tree with one operation and will exit the loop afterwards. */ if (q->right == NULL || !q->right->red) { /* The left one is red. We perform the same action as in maybe_split_for_insert where two red edges are adjacent but point in different directions: Q's left successor (let's call it Q2) becomes the top of the subtree we are looking at, its parent (Q) and grandparent (P) become its successors. The former successors of Q2 are placed below P and Q. P becomes black, and Q2 gets the color that P had. This changes the black edge count only for node R and its successors. */ node q2 = q->left; q2->red = p->red; p->right = q2->left; q->left = q2->right; q2->right = q; q2->left = p; *pp = q2; p->red = 0; } else { /* It's the right one. Rotate P left. P becomes black, and Q gets the color that P had. Q's right successor also becomes black. This changes the black edge count only for node R and its successors. */ q->red = p->red; p->red = 0; q->right->red = 0; /* left rotate p */ p->right = q->left; q->left = p; *pp = q; } /* We're done. */ sp = 1; r = NULL; } } else { /* Comments: see above. */ q = p->left; if (q != NULL && q->red) { q->red = 0; p->red = 1; p->left = q->right; q->right = p; *pp = q; nodestack[sp++] = pp = &q->right; q = p->left; } if ((q->right == NULL || !q->right->red) && (q->left == NULL || !q->left->red)) { q->red = 1; r = p; } else { if (q->left == NULL || !q->left->red) { node q2 = q->right; q2->red = p->red; p->left = q2->right; q->right = q2->left; q2->left = q; q2->right = p; *pp = q2; p->red = 0; } else { q->red = p->red; p->red = 0; q->left->red = 0; p->left = q->right; q->right = p; *pp = q; } sp = 1; r = NULL; } } --sp; } if (r != NULL) r->red = 0; } free (unchained); return retval; } #ifdef weak_alias weak_alias (__tdelete, tdelete) #endif /* Walk the nodes of a tree. ROOT is the root of the tree to be walked, ACTION the function to be called at each node. LEVEL is the level of ROOT in the whole tree. */ static void internal_function trecurse (const void *vroot, __action_fn_t action, int level) { const_node root = (const_node) vroot; if (root->left == NULL && root->right == NULL) (*action) (root, leaf, level); else { (*action) (root, preorder, level); if (root->left != NULL) trecurse (root->left, action, level + 1); (*action) (root, postorder, level); if (root->right != NULL) trecurse (root->right, action, level + 1); (*action) (root, endorder, level); } } /* Walk the nodes of a tree. ROOT is the root of the tree to be walked, ACTION the function to be called at each node. */ void __twalk (const void *vroot, __action_fn_t action) { const_node root = (const_node) vroot; CHECK_TREE (root); if (root != NULL && action != NULL) trecurse (root, action, 0); } #ifdef weak_alias weak_alias (__twalk, twalk) #endif /* The standardized functions miss an important functionality: the tree cannot be removed easily. We provide a function to do this. */ static void internal_function tdestroy_recurse (node root, void (*freefct)(void *)) { if (root->left != NULL) tdestroy_recurse (root->left, freefct); if (root->right != NULL) tdestroy_recurse (root->right, freefct); (*freefct) ((void *) root->key); /* Free the node itself. */ free (root); } void __tdestroy (void *vroot, void (*freefct)(void *)) { node root = (node) vroot; CHECK_TREE (root); if (root != NULL) tdestroy_recurse (root, freefct); } #ifdef weak_alias weak_alias (__tdestroy, tdestroy) #endif
/* cat -- concatenate files and print on the standard output. Copyright (C) 88, 90, 91, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Differences from the Unix cat: * Always unbuffered, -u is ignored. * Usually much faster than other versions of cat, the difference is especially apparent when using the -v option. By tege@sics.se, Torbjorn Granlund, advised by rms, Richard Stallman. */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #ifndef _POSIX_SOURCE # include <sys/ioctl.h> #endif #include "system.h" #include "closeout.h" #include "error.h" #include "full-write.h" #include "safe-read.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "cat" #define AUTHORS N_ ("Torbjorn Granlund and Richard M. Stallman") /* Undefine, to avoid warning about redefinition on some systems. */ #undef max #define max(h,i) ((h) > (i) ? (h) : (i)) /* Name under which this program was invoked. */ char *program_name; /* Name of input file. May be "-". */ static char *infile; /* Descriptor on which input file is open. */ static int input_desc; /* Buffer for line numbers. An 11 digit counter may overflow within an hour on a P2/466, an 18 digit counter needs about 1000y */ #define LINE_COUNTER_BUF_LEN 20 static char line_buf[LINE_COUNTER_BUF_LEN] = { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '0', '\t', '\0' }; /* Position in `line_buf' where printing starts. This will not change unless the number of lines is larger than 999999. */ static char *line_num_print = line_buf + LINE_COUNTER_BUF_LEN - 8; /* Position of the first digit in `line_buf'. */ static char *line_num_start = line_buf + LINE_COUNTER_BUF_LEN - 3; /* Position of the last digit in `line_buf'. */ static char *line_num_end = line_buf + LINE_COUNTER_BUF_LEN - 3; /* Preserves the `cat' function's local `newlines' between invocations. */ static int newlines2 = 0; /* Count of non-fatal error conditions. */ static int exit_status = 0; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION] [FILE]...\n\ "), program_name); fputs (_("\ Concatenate FILE(s), or standard input, to standard output.\n\ \n\ -A, --show-all equivalent to -vET\n\ -b, --number-nonblank number nonblank output lines\n\ -e equivalent to -vE\n\ -E, --show-ends display $ at end of each line\n\ -n, --number number all output lines\n\ -s, --squeeze-blank never more than one single blank line\n\ "), stdout); fputs (_("\ -t equivalent to -vT\n\ -T, --show-tabs display TAB characters as ^I\n\ -u (ignored)\n\ -v, --show-nonprinting use ^ and M- notation, except for LFD and TAB\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ With no FILE, or when FILE is -, read standard input.\n\ "), stdout); #if O_BINARY fputs (_("\ \n\ -B, --binary use binary writes to the console device.\n\n\ "), stdout); #endif printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } /* Compute the next line number. */ static void next_line_num (void) { char *endp = line_num_end; do { if ((*endp)++ < '9') return; *endp-- = '0'; } while (endp >= line_num_start); if (line_num_start > line_buf) *--line_num_start = '1'; else *line_buf = '>'; if (line_num_start < line_num_print) line_num_print--; } /* Plain cat. Copies the file behind `input_desc' to STDOUT_FILENO. */ static void simple_cat ( /* Pointer to the buffer, used by reads and writes. */ char *buf, /* Number of characters preferably read or written by each read and write call. */ int bufsize) { /* Actual number of characters read, and therefore written. */ size_t n_read; /* Loop until the end of the file. */ for (;;) { /* Read a block of input. */ n_read = safe_read (input_desc, buf, bufsize); if (n_read == SAFE_READ_ERROR) { error (0, errno, "%s", infile); exit_status = 1; return; } /* End of this file? */ if (n_read == 0) break; /* Write this block out. */ { /* The following is ok, since we know that 0 < n_read. */ size_t n = n_read; if (full_write (STDOUT_FILENO, buf, n) != n) error (EXIT_FAILURE, errno, _("write error")); } } } /* Cat the file behind INPUT_DESC to the file behind OUTPUT_DESC. Called if any option more than -u was specified. A newline character is always put at the end of the buffer, to make an explicit test for buffer end unnecessary. */ static void cat ( /* Pointer to the beginning of the input buffer. */ char *inbuf, /* Number of characters read in each read call. */ size_t insize, /* Pointer to the beginning of the output buffer. */ char *outbuf, /* Number of characters written by each write call. */ size_t outsize, /* Variables that have values according to the specified options. */ int quote, int output_tabs, int numbers, int numbers_at_empty_lines, int mark_line_ends, int squeeze_empty_lines) { /* Last character read from the input buffer. */ unsigned char ch; /* Pointer to the next character in the input buffer. */ char *bpin; /* Pointer to the first non-valid byte in the input buffer, i.e. the current end of the buffer. */ char *eob; /* Pointer to the position where the next character shall be written. */ char *bpout; /* Number of characters read by the last read call. */ size_t n_read; /* Determines how many consecutive newlines there have been in the input. 0 newlines makes NEWLINES -1, 1 newline makes NEWLINES 1, etc. Initially 0 to indicate that we are at the beginning of a new line. The "state" of the procedure is determined by NEWLINES. */ int newlines = newlines2; #ifdef FIONREAD /* If nonzero, use the FIONREAD ioctl, as an optimization. (On Ultrix, it is not supported on NFS filesystems.) */ int use_fionread = 1; #endif /* The inbuf pointers are initialized so that BPIN > EOB, and thereby input is read immediately. */ eob = inbuf; bpin = eob + 1; bpout = outbuf; for (;;) { do { /* Write if there are at least OUTSIZE bytes in OUTBUF. */ if (outbuf + outsize <= bpout) { char *wp = outbuf; size_t remaining_bytes; do { if (full_write (STDOUT_FILENO, wp, outsize) != outsize) error (EXIT_FAILURE, errno, _("write error")); wp += outsize; remaining_bytes = bpout - wp; } while (outsize <= remaining_bytes); /* Move the remaining bytes to the beginning of the buffer. */ memmove (outbuf, wp, remaining_bytes); bpout = outbuf + remaining_bytes; } /* Is INBUF empty? */ if (bpin > eob) { #ifdef FIONREAD int n_to_read = 0; /* Is there any input to read immediately? If not, we are about to wait, so write all buffered output before waiting. */ if (use_fionread && ioctl (input_desc, FIONREAD, &n_to_read) < 0) { /* Ultrix returns EOPNOTSUPP on NFS; HP-UX returns ENOTTY on pipes. SunOS returns EINVAL and More/BSD returns ENODEV on special files like /dev/null. Irix-5 returns ENOSYS on pipes. */ if (errno == EOPNOTSUPP || errno == ENOTTY || errno == EINVAL || errno == ENODEV # ifdef ENOSYS || errno == ENOSYS # endif ) use_fionread = 0; else { error (0, errno, _("cannot do ioctl on `%s'"), infile); exit_status = 1; newlines2 = newlines; return; } } if (n_to_read == 0) #endif { size_t n_write = bpout - outbuf; if (full_write (STDOUT_FILENO, outbuf, n_write) != n_write) error (EXIT_FAILURE, errno, _("write error")); bpout = outbuf; } /* Read more input into INBUF. */ n_read = safe_read (input_desc, inbuf, insize); if (n_read == SAFE_READ_ERROR) { error (0, errno, "%s", infile); exit_status = 1; newlines2 = newlines; return; } if (n_read == 0) { newlines2 = newlines; return; } /* Update the pointers and insert a sentinel at the buffer end. */ bpin = inbuf; eob = bpin + n_read; *eob = '\n'; } else { /* It was a real (not a sentinel) newline. */ /* Was the last line empty? (i.e. have two or more consecutive newlines been read?) */ if (++newlines > 0) { if (newlines >= 2) { /* Limit this to 2 here. Otherwise, with lots of consecutive newlines, the counter could wrap around at INT_MAX. */ newlines = 2; /* Are multiple adjacent empty lines to be substituted by single ditto (-s), and this was the second empty line? */ if (squeeze_empty_lines) { ch = *bpin++; continue; } } /* Are line numbers to be written at empty lines (-n)? */ if (numbers && numbers_at_empty_lines) { next_line_num (); bpout = stpcpy (bpout, line_num_print); } } /* Output a currency symbol if requested (-e). */ if (mark_line_ends) *bpout++ = '$'; /* Output the newline. */ *bpout++ = '\n'; } ch = *bpin++; } while (ch == '\n'); /* Are we at the beginning of a line, and line numbers are requested? */ if (newlines >= 0 && numbers) { next_line_num (); bpout = stpcpy (bpout, line_num_print); } /* Here CH cannot contain a newline character. */ /* The loops below continue until a newline character is found, which means that the buffer is empty or that a proper newline has been found. */ /* If quoting, i.e. at least one of -v, -e, or -t specified, scan for chars that need conversion. */ if (quote) { for (;;) { if (ch >= 32) { if (ch < 127) *bpout++ = ch; else if (ch == 127) { *bpout++ = '^'; *bpout++ = '?'; } else { *bpout++ = 'M'; *bpout++ = '-'; if (ch >= 128 + 32) { if (ch < 128 + 127) *bpout++ = ch - 128; else { *bpout++ = '^'; *bpout++ = '?'; } } else { *bpout++ = '^'; *bpout++ = ch - 128 + 64; } } } else if (ch == '\t' && output_tabs) *bpout++ = '\t'; else if (ch == '\n') { newlines = -1; break; } else { *bpout++ = '^'; *bpout++ = ch + 64; } ch = *bpin++; } } else { /* Not quoting, neither of -v, -e, or -t specified. */ for (;;) { if (ch == '\t' && !output_tabs) { *bpout++ = '^'; *bpout++ = ch + 64; } else if (ch != '\n') *bpout++ = ch; else { newlines = -1; break; } ch = *bpin++; } } } } /* This is gross, but necessary, because of the way close_stdout works and because this program closes STDOUT_FILENO directly. */ static void (*closeout_func) (void) = close_stdout; static void close_stdout_wrapper (void) { if (closeout_func) (*closeout_func) (); } int main (int argc, char **argv) { /* Optimal size of i/o operations of output. */ size_t outsize; /* Optimal size of i/o operations of input. */ size_t insize; /* Pointer to the input buffer. */ char *inbuf; /* Pointer to the output buffer. */ char *outbuf; int c; /* Index in argv to processed argument. */ int argind; /* Device number of the output (file or whatever). */ dev_t out_dev; /* I-node number of the output. */ ino_t out_ino; /* Nonzero if the output file should not be the same as any input file. */ int check_redirection = 1; /* Nonzero if we have ever read standard input. */ int have_read_stdin = 0; struct stat stat_buf; /* Variables that are set according to the specified options. */ int numbers = 0; int numbers_at_empty_lines = 1; int squeeze_empty_lines = 0; int mark_line_ends = 0; int quote = 0; int output_tabs = 1; #if O_BINARY int binary_files = 0; int binary_output = 0; #endif int file_open_mode = O_RDONLY; /* If nonzero, call cat, otherwise call simple_cat to do the actual work. */ int options = 0; static struct option const long_options[] = { {"number-nonblank", no_argument, NULL, 'b'}, {"number", no_argument, NULL, 'n'}, {"squeeze-blank", no_argument, NULL, 's'}, {"show-nonprinting", no_argument, NULL, 'v'}, {"show-ends", no_argument, NULL, 'E'}, {"show-tabs", no_argument, NULL, 'T'}, {"show-all", no_argument, NULL, 'A'}, #if O_BINARY {"binary", no_argument, NULL, 'B'}, #endif {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); /* Arrange to close stdout if we exit via the case_GETOPT_HELP_CHAR or case_GETOPT_VERSION_CHAR code. */ atexit (close_stdout_wrapper); /* Parse command line options. */ while ((c = getopt_long (argc, argv, #if O_BINARY "benstuvABET" #else "benstuvAET" #endif , long_options, NULL)) != -1) { switch (c) { case 0: break; case 'b': ++options; numbers = 1; numbers_at_empty_lines = 0; break; case 'e': ++options; mark_line_ends = 1; quote = 1; break; case 'n': ++options; numbers = 1; break; case 's': ++options; squeeze_empty_lines = 1; break; case 't': ++options; output_tabs = 0; quote = 1; break; case 'u': /* We provide the -u feature unconditionally. */ break; case 'v': ++options; quote = 1; break; case 'A': ++options; quote = 1; mark_line_ends = 1; output_tabs = 0; break; #if O_BINARY case 'B': ++options; binary_files = 1; break; #endif case 'E': ++options; mark_line_ends = 1; break; case 'T': ++options; output_tabs = 0; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } /* Don't close stdout on exit from here on. */ closeout_func = NULL; /* Get device, i-node number, and optimal blocksize of output. */ if (fstat (STDOUT_FILENO, &stat_buf) < 0) error (EXIT_FAILURE, errno, _("standard output")); outsize = ST_BLKSIZE (stat_buf); /* Input file can be output file for non-regular files. fstat on pipes returns S_IFSOCK on some systems, S_IFIFO on others, so the checking should not be done for those types, and to allow things like cat < /dev/tty > /dev/tty, checking is not done for device files either. */ if (S_ISREG (stat_buf.st_mode)) { out_dev = stat_buf.st_dev; out_ino = stat_buf.st_ino; } else { check_redirection = 0; #ifdef lint /* Suppress `used before initialized' warning. */ out_dev = 0; out_ino = 0; #endif } #if O_BINARY /* We always read and write in BINARY mode, since this is the best way to copy the files verbatim. Exceptions are when they request line numbering, squeezing of empty lines or marking lines' ends: then we use text I/O, because otherwise -b, -s and -E would surprise users on DOS/Windows where a line with only CR-LF is an empty line. (Besides, if they ask for one of these options, they don't care much about the original file contents anyway). */ if ((!isatty (STDOUT_FILENO) && !(numbers || squeeze_empty_lines || mark_line_ends)) || binary_files) { /* Switch stdout to BINARY mode. */ binary_output = 1; SET_BINARY (STDOUT_FILENO); /* When stdout is in binary mode, make sure all input files are also read in binary mode. */ file_open_mode |= O_BINARY; } else if (quote) { /* If they want to see the non-printables, let's show them those CR characters as well, so make the input binary. But keep console output in text mode, so that LF causes both CR and LF on output, and the output is readable. */ file_open_mode |= O_BINARY; SET_BINARY (0); /* Setting stdin to binary switches the console device to raw I/O, which also affects stdout to console. Undo that. */ if (isatty (STDOUT_FILENO)) setmode (STDOUT_FILENO, O_TEXT); } #endif /* Check if any of the input files are the same as the output file. */ /* Main loop. */ infile = "-"; argind = optind; do { if (argind < argc) infile = argv[argind]; if (infile[0] == '-' && infile[1] == 0) { have_read_stdin = 1; input_desc = 0; #if O_BINARY /* Switch stdin to BINARY mode if needed. */ if (binary_output) { int tty_in = isatty (input_desc); /* If stdin is a terminal device, and it is the ONLY input file (i.e. we didn't write anything to the output yet), switch the output back to TEXT mode. This is so "cat > xyzzy" creates a DOS-style text file, like people expect. */ if (tty_in && optind <= argc) setmode (STDOUT_FILENO, O_TEXT); else { SET_BINARY (input_desc); # ifdef __DJGPP__ /* This is DJGPP-specific. By default, switching console to binary mode disables SIGINT. But we want terminal reads to be interruptible. */ if (tty_in) __djgpp_set_ctrl_c (1); # endif } } #endif } else { input_desc = open (infile, file_open_mode); if (input_desc < 0) { error (0, errno, "%s", infile); exit_status = 1; continue; } } if (fstat (input_desc, &stat_buf) < 0) { error (0, errno, "%s", infile); exit_status = 1; goto contin; } insize = ST_BLKSIZE (stat_buf); /* Compare the device and i-node numbers of this input file with the corresponding values of the (output file associated with) stdout, and skip this input file if they coincide. Input files cannot be redirected to themselves. */ if (check_redirection && stat_buf.st_dev == out_dev && stat_buf.st_ino == out_ino && (input_desc != STDIN_FILENO)) { error (0, 0, _("%s: input file is output file"), infile); exit_status = 1; goto contin; } /* Select which version of `cat' to use. If any options (more than -u, --version, or --help) were specified, use `cat', otherwise use `simple_cat'. */ if (options == 0) { insize = max (insize, outsize); inbuf = xmalloc (insize); simple_cat (inbuf, insize); } else { inbuf = xmalloc (insize + 1); /* Why are (OUTSIZE - 1 + INSIZE * 4 + LINE_COUNTER_BUF_LEN) bytes allocated for the output buffer? A test whether output needs to be written is done when the input buffer empties or when a newline appears in the input. After output is written, at most (OUTSIZE - 1) bytes will remain in the buffer. Now INSIZE bytes of input is read. Each input character may grow by a factor of 4 (by the prepending of M-^). If all characters do, and no newlines appear in this block of input, we will have at most (OUTSIZE - 1 + INSIZE * 4) bytes in the buffer. If the last character in the preceding block of input was a newline, a line number may be written (according to the given options) as the first thing in the output buffer. (Done after the new input is read, but before processing of the input begins.) A line number requires seldom more than LINE_COUNTER_BUF_LEN positions. */ outbuf = xmalloc (outsize - 1 + insize * 4 + LINE_COUNTER_BUF_LEN); cat (inbuf, insize, outbuf, outsize, quote, output_tabs, numbers, numbers_at_empty_lines, mark_line_ends, squeeze_empty_lines); free (outbuf); } free (inbuf); contin: if (!STREQ (infile, "-") && close (input_desc) < 0) { error (0, errno, "%s", infile); exit_status = 1; } } while (++argind < argc); if (have_read_stdin && close (STDIN_FILENO) < 0) error (EXIT_FAILURE, errno, _("closing standard input")); if (close (STDOUT_FILENO) < 0) error (EXIT_FAILURE, errno, _("closing standard output")); exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); }
/* Declarations for getopt. Copyright (C) 1989-1994, 1996-1999, 2001 Free Software Foundation, Inc. This file is part of the GNU C Library. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef _GETOPT_H #ifndef __need_getopt # define _GETOPT_H 1 #endif /* If __GNU_LIBRARY__ is not already defined, either we are being used standalone, or this is the first header included in the source file. If we are being used with glibc, we need to include <features.h>, but that does not exist if we are standalone. So: if __GNU_LIBRARY__ is not defined, include <ctype.h>, which will pull in <features.h> for us if it's from glibc. (Why ctype.h? It's guaranteed to exist and it doesn't flood the namespace with stuff the way some other headers do.) */ #if !defined __GNU_LIBRARY__ # include <ctype.h> #endif #ifdef __cplusplus extern "C" { #endif /* For communication from `getopt' to the caller. When `getopt' finds an option that takes an argument, the argument value is returned here. Also, when `ordering' is RETURN_IN_ORDER, each non-option ARGV-element is returned here. */ extern char *optarg; /* Index in ARGV of the next element to be scanned. This is used for communication to and from the caller and for communication between successive calls to `getopt'. On entry to `getopt', zero means this is the first call; initialize. When `getopt' returns -1, this is the index of the first of the non-option elements that the caller should itself scan. Otherwise, `optind' communicates from one call to the next how much of ARGV has been scanned so far. */ extern int optind; /* Callers store zero here to inhibit the error message `getopt' prints for unrecognized options. */ extern int opterr; /* Set to an option character which was unrecognized. */ extern int optopt; #ifndef __need_getopt /* Describe the long-named options requested by the application. The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector of `struct option' terminated by an element containing a name which is zero. The field `has_arg' is: no_argument (or 0) if the option does not take an argument, required_argument (or 1) if the option requires an argument, optional_argument (or 2) if the option takes an optional argument. If the field `flag' is not NULL, it points to a variable that is set to the value given in the field `val' when the option is found, but left unchanged if the option is not found. To have a long-named option do something other than set an `int' to a compiled-in constant, such as set a value from `optarg', set the option's `flag' field to zero and its `val' field to a nonzero value (the equivalent single-letter option character, if there is one). For long options that have a zero `flag' field, `getopt' returns the contents of the `val' field. */ struct option { # if (defined __STDC__ && __STDC__) || defined __cplusplus const char *name; # else char *name; # endif /* has_arg can't be an enum because some compilers complain about type mismatches in all the code that assumes it is an int. */ int has_arg; int *flag; int val; }; /* Names for the values of the `has_arg' field of `struct option'. */ # define no_argument 0 # define required_argument 1 # define optional_argument 2 #endif /* need getopt */ /* Get definitions and prototypes for functions to process the arguments in ARGV (ARGC of them, minus the program name) for options given in OPTS. Return the option character from OPTS just read. Return -1 when there are no more options. For unrecognized options, or options missing arguments, `optopt' is set to the option letter, and '?' is returned. The OPTS string is a list of characters which are recognized option letters, optionally followed by colons, specifying that that letter takes an argument, to be placed in `optarg'. If a letter in OPTS is followed by two colons, its argument is optional. This behavior is specific to the GNU `getopt'. The argument `--' causes premature termination of argument scanning, explicitly telling `getopt' that there are no more options. If OPTS begins with `--', then non-option arguments are treated as arguments to the option '\0'. This behavior is specific to the GNU `getopt'. */ #if (defined __STDC__ && __STDC__) || defined __cplusplus # ifdef __GNU_LIBRARY__ /* Many other libraries have conflicting prototypes for getopt, with differences in the consts, in stdlib.h. To avoid compilation errors, only prototype getopt for the GNU C library. */ extern int getopt (int ___argc, char *const *___argv, const char *__shortopts); # else /* not __GNU_LIBRARY__ */ extern int getopt (); # endif /* __GNU_LIBRARY__ */ # ifndef __need_getopt extern int getopt_long (int ___argc, char *const *___argv, const char *__shortopts, const struct option *__longopts, int *__longind); extern int getopt_long_only (int ___argc, char *const *___argv, const char *__shortopts, const struct option *__longopts, int *__longind); /* Internal only. Users should not call this directly. */ extern int _getopt_internal (int ___argc, char *const *___argv, const char *__shortopts, const struct option *__longopts, int *__longind, int __long_only); # endif #else /* not __STDC__ */ extern int getopt (); # ifndef __need_getopt extern int getopt_long (); extern int getopt_long_only (); extern int _getopt_internal (); # endif #endif /* __STDC__ */ #ifdef __cplusplus } #endif /* Make sure we later can get all the definitions and declarations. */ #undef __need_getopt #endif /* getopt.h */
/* An interface to write() that writes all it is asked to write. Copyright (C) 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <stddef.h> /* Write COUNT bytes at BUF to descriptor FD, retrying if interrupted or if partial writes occur. Return the number of bytes successfully written, setting errno if that is less than COUNT. */ extern size_t full_write (int fd, const void *buf, size_t count);
/* An interface to read() that retries after interrupts. Copyright (C) 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <stddef.h> #define SAFE_READ_ERROR ((size_t) -1) /* Read up to COUNT bytes at BUF from descriptor FD, retrying if interrupted. Return the actual number of bytes read, zero for EOF, or SAFE_READ_ERROR upon error. */ extern size_t safe_read (int fd, void *buf, size_t count);
/* chmod -- change permission modes of files Copyright (C) 89, 90, 91, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@gnu.ai.mit.edu> */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include "system.h" #include "error.h" #include "filemode.h" #include "modechange.h" #include "quote.h" #include "savedir.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "chmod" #define AUTHORS "David MacKenzie" enum Change_status { CH_SUCCEEDED, CH_FAILED, CH_NO_CHANGE_REQUESTED }; enum Verbosity { /* Print a message for each file that is processed. */ V_high, /* Print a message for each file whose attributes we change. */ V_changes_only, /* Do not be verbose. This is the default. */ V_off }; static int change_dir_mode (const char *dir, const struct mode_change *changes); /* The name the program was run with. */ char *program_name; /* If nonzero, change the modes of directories recursively. */ static int recurse; /* If nonzero, force silence (no error messages). */ static int force_silent; /* Level of verbosity. */ static enum Verbosity verbosity = V_off; /* The argument to the --reference option. Use the owner and group IDs of this file. This file must exist. */ static char *reference_file; /* For long options that have no equivalent short option, use a non-character as a pseudo short option, starting with CHAR_MAX + 1. */ enum { REFERENCE_FILE_OPTION = CHAR_MAX + 1 }; static struct option const long_options[] = { {"recursive", no_argument, 0, 'R'}, {"changes", no_argument, 0, 'c'}, {"silent", no_argument, 0, 'f'}, {"quiet", no_argument, 0, 'f'}, {"reference", required_argument, 0, REFERENCE_FILE_OPTION}, {"verbose", no_argument, 0, 'v'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {0, 0, 0, 0} }; static int mode_changed (const char *file, mode_t old_mode) { struct stat new_stats; if (stat (file, &new_stats)) { if (force_silent == 0) error (0, errno, _("getting new attributes of %s"), quote (file)); return 0; } return old_mode != new_stats.st_mode; } /* Tell the user how/if the MODE of FILE has been changed. CHANGED describes what (if anything) has happened. */ static void describe_change (const char *file, mode_t mode, enum Change_status changed) { char perms[11]; /* "-rwxrwxrwx" ls-style modes. */ const char *fmt; mode_string (mode, perms); perms[10] = '\0'; /* `mode_string' does not null terminate. */ switch (changed) { case CH_SUCCEEDED: fmt = _("mode of %s changed to %04lo (%s)\n"); break; case CH_FAILED: fmt = _("failed to change mode of %s to %04lo (%s)\n"); break; case CH_NO_CHANGE_REQUESTED: fmt = _("mode of %s retained as %04lo (%s)\n"); break; default: abort (); } printf (fmt, quote (file), (unsigned long) (mode & CHMOD_MODE_BITS), &perms[1]); } /* Change the mode of FILE according to the list of operations CHANGES. If DEREF_SYMLINK is nonzero and FILE is a symbolic link, change the mode of the referenced file. If DEREF_SYMLINK is zero, ignore symbolic links. Return 0 if successful, 1 if errors occurred. */ static int change_file_mode (const char *file, const struct mode_change *changes, const int deref_symlink) { struct stat file_stats; mode_t newmode; int errors = 0; int fail; int saved_errno; if (deref_symlink ? stat (file, &file_stats) : lstat (file, &file_stats)) { if (force_silent == 0) error (0, errno, _("failed to get attributes of %s"), quote (file)); return 1; } #ifdef S_ISLNK if (S_ISLNK (file_stats.st_mode)) return 0; #endif newmode = mode_adjust (file_stats.st_mode, changes); fail = chmod (file, newmode); saved_errno = errno; if (verbosity == V_high || (verbosity == V_changes_only && !fail && mode_changed (file, file_stats.st_mode))) describe_change (file, newmode, (fail ? CH_FAILED : CH_SUCCEEDED)); if (fail) { if (force_silent == 0) error (0, saved_errno, _("changing permissions of %s"), quote (file)); errors = 1; } if (recurse && S_ISDIR (file_stats.st_mode)) errors |= change_dir_mode (file, changes); return errors; } /* Recursively change the modes of the files in directory DIR according to the list of operations CHANGES. Return 0 if successful, 1 if errors occurred. */ static int change_dir_mode (const char *dir, const struct mode_change *changes) { char *name_space, *namep; char *path; /* Full path of each entry to process. */ unsigned dirlength; /* Length of DIR and '\0'. */ unsigned filelength; /* Length of each pathname to process. */ unsigned pathlength; /* Bytes allocated for `path'. */ int errors = 0; name_space = savedir (dir); if (name_space == NULL) { if (force_silent == 0) error (0, errno, "%s", quote (dir)); return 1; } dirlength = strlen (dir) + 1; /* + 1 is for the trailing '/'. */ pathlength = dirlength + 1; /* Give `path' a dummy value; it will be reallocated before first use. */ path = xmalloc (pathlength); strcpy (path, dir); path[dirlength - 1] = '/'; for (namep = name_space; *namep; namep += filelength - dirlength) { filelength = dirlength + strlen (namep) + 1; if (filelength > pathlength) { pathlength = filelength * 2; path = xrealloc (path, pathlength); } strcpy (path + dirlength, namep); errors |= change_file_mode (path, changes, 0); } free (path); free (name_space); return errors; } void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\ or: %s [OPTION]... OCTAL-MODE FILE...\n\ or: %s [OPTION]... --reference=RFILE FILE...\n\ "), program_name, program_name, program_name); fputs (_("\ Change the mode of each FILE to MODE.\n\ \n\ -c, --changes like verbose but report only when a change is made\n\ -f, --silent, --quiet suppress most error messages\n\ -v, --verbose output a diagnostic for every file processed\n\ --reference=RFILE use RFILE's mode instead of MODE values\n\ -R, --recursive change files and directories recursively\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ Each MODE is one or more of the letters ugoa, one of the symbols +-= and\n\ one or more of the letters rwxXstugo.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } /* Parse the ASCII mode given on the command line into a linked list of `struct mode_change' and apply that to each file argument. */ int main (int argc, char **argv) { struct mode_change *changes; int errors = 0; int modeind = 0; /* Index of the mode argument in `argv'. */ int thisind; int c; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); recurse = force_silent = 0; while (1) { thisind = optind ? optind : 1; c = getopt_long (argc, argv, "RcfvrwxXstugoa,+-=", long_options, NULL); if (c == -1) break; switch (c) { case 0: break; case 'r': case 'w': case 'x': case 'X': case 's': case 't': case 'u': case 'g': case 'o': case 'a': case ',': case '+': case '-': case '=': if (modeind != 0 && modeind != thisind) { static char char_string[2] = {0, 0}; char_string[0] = c; error (EXIT_FAILURE, 0, _("invalid character %s in mode string %s"), quote_n (0, char_string), quote_n (1, argv[thisind])); } modeind = thisind; break; case REFERENCE_FILE_OPTION: reference_file = optarg; break; case 'R': recurse = 1; break; case 'c': verbosity = V_changes_only; break; case 'f': force_silent = 1; break; case 'v': verbosity = V_high; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } if (modeind == 0 && reference_file == NULL) modeind = optind++; if (optind >= argc) { error (0, 0, _("too few arguments")); usage (EXIT_FAILURE); } changes = (reference_file ? mode_create_from_ref (reference_file) : mode_compile (argv[modeind], MODE_MASK_ALL)); if (changes == MODE_INVALID) error (EXIT_FAILURE, 0, _("invalid mode string: %s"), quote (argv[modeind])); else if (changes == MODE_MEMORY_EXHAUSTED) xalloc_die (); else if (changes == MODE_BAD_REFERENCE) error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), quote (reference_file)); for (; optind < argc; ++optind) errors |= change_file_mode (argv[optind], changes, 1); exit (errors); }
#ifndef FILEMODE_H_ # if HAVE_CONFIG_H # include <config.h> # endif # include <sys/types.h> # ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif # endif void mode_string PARAMS ((mode_t mode, char *str)); #endif
/* modechange.h -- definitions for file mode manipulation Copyright (C) 1989, 1990, 1997 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Masks for the `flags' field in a `struct mode_change'. */ #if ! defined MODECHANGE_H_ # define MODECHANGE_H_ # if HAVE_CONFIG_H # include <config.h> # endif # include <sys/types.h> /* Affect the execute bits only if at least one execute bit is set already, or if the file is a directory. */ # define MODE_X_IF_ANY_X 01 /* If set, copy some existing permissions for u, g, or o onto the other two. Which of u, g, or o is copied is determined by which bits are set in the `value' field. */ # define MODE_COPY_EXISTING 02 struct mode_change { char op; /* One of "=+-". */ char flags; /* Special operations. */ mode_t affected; /* Set for u/g/o/s/s/t, if to be affected. */ mode_t value; /* Bits to add/remove. */ struct mode_change *next; /* Link to next change in list. */ }; /* Masks for mode_compile argument. */ # define MODE_MASK_EQUALS 1 # define MODE_MASK_PLUS 2 # define MODE_MASK_MINUS 4 # define MODE_MASK_ALL (MODE_MASK_EQUALS | MODE_MASK_PLUS | MODE_MASK_MINUS) /* Error return values for mode_compile. */ # define MODE_INVALID (struct mode_change *) 0 # define MODE_MEMORY_EXHAUSTED (struct mode_change *) 1 # define MODE_BAD_REFERENCE (struct mode_change *) 2 # ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif # endif struct mode_change *mode_compile PARAMS ((const char *, unsigned)); struct mode_change *mode_create_from_ref PARAMS ((const char *)); mode_t mode_adjust PARAMS ((mode_t, const struct mode_change *)); void mode_free PARAMS ((struct mode_change *)); #endif
/* quote.h - prototypes for quote.c Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif #endif char const *quote_n PARAMS ((int n, char const *name)); char const *quote PARAMS ((char const *name));
#if !defined SAVEDIR_H_ # define SAVEDIR_H_ # ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif # endif char *savedir PARAMS ((const char *dir)); #endif
/* cksum -- calculate and print POSIX checksums and sizes of files Copyright (C) 92, 1995-2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Q. Frank Xia, qx@math.columbia.edu. Cosmetic changes and reorganization by David MacKenzie, djm@gnu.ai.mit.edu. Usage: cksum [file...] The code segment between "#ifdef CRCTAB" and "#else" is the code which calculates the "crctab". It is included for those who want verify the correctness of the "crctab". To recreate the "crctab", do following: cc -DCRCTAB -o crctab cksum.c crctab > crctab.h As Bruce Evans pointed out to me, the crctab in the sample C code in 4.9.10 Rationale of P1003.2/D11.2 is represented in reversed order. Namely, 0x01 is represented as 0x80, 0x02 is represented as 0x40, etc. The generating polynomial is crctab[0x80]=0xedb88320 instead of crctab[1]=0x04C11DB7. But the code works only for a non-reverse order crctab. Therefore, the sample implementation is wrong. This software is compatible with neither the System V nor the BSD `sum' program. It is supposed to conform to P1003.2/D11.2, except foreign language interface (4.9.5.3 of P1003.2/D11.2) support. Any inconsistency with the standard except 4.9.5.3 is a bug. */ #include <config.h> /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "cksum" #define AUTHORS "Q. Frank Xia" #include <stdio.h> #include <sys/types.h> #include "system.h" #if !defined UINT_FAST32_MAX && !defined uint_fast32_t # define uint_fast32_t unsigned int #endif #ifdef CRCTAB # define BIT(x) ((uint_fast32_t) 1 << (x)) # define SBIT BIT (31) /* The generating polynomial is 32 26 23 22 16 12 11 10 8 7 5 4 2 1 G(X)=X + X + X + X + X + X + X + X + X + X + X + X + X + X + 1 The i bit in GEN is set if X^i is a summand of G(X) except X^32. */ # define GEN (BIT (26) | BIT (23) | BIT (22) | BIT (16) | BIT (12) \ | BIT (11) | BIT (10) | BIT (8) | BIT (7) | BIT (5) \ | BIT (4) | BIT (2) | BIT (1) | BIT (0)) static uint_fast32_t r[8]; static void fill_r (void) { int i; r[0] = GEN; for (i = 1; i < 8; i++) r[i] = (r[i - 1] << 1) ^ ((r[i - 1] & SBIT) ? GEN : 0); } static uint_fast32_t remainder (int m) { uint_fast32_t rem = 0; int i; for (i = 0; i < 8; i++) if (BIT (i) & m) rem ^= r[i]; return rem & 0xFFFFFFFF; /* Make it run on 64-bit machine. */ } int main (void) { int i; fill_r (); printf ("static uint_fast32_t crctab[256] =\n{\n 0x0"); for (i = 0; i < 51; i++) { printf (",\n 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X", remainder (i * 5 + 1), remainder (i * 5 + 2), remainder (i * 5 + 3), remainder (i * 5 + 4), remainder (i * 5 + 5)); } printf ("\n};\n"); exit (EXIT_SUCCESS); } #else /* !CRCTAB */ # include <getopt.h> # include "closeout.h" # include "long-options.h" # include "error.h" # include "inttostr.h" /* Number of bytes to read at once. */ # define BUFLEN (1 << 16) /* The name this program was run with. */ char *program_name; static struct option const long_options[] = { {0, 0, 0, 0} }; static uint_fast32_t crctab[256] = { 0x0, 0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005, 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61, 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD, 0x4C11DB70, 0x48D0C6C7, 0x4593E01E, 0x4152FDA9, 0x5F15ADAC, 0x5BD4B01B, 0x569796C2, 0x52568B75, 0x6A1936C8, 0x6ED82B7F, 0x639B0DA6, 0x675A1011, 0x791D4014, 0x7DDC5DA3, 0x709F7B7A, 0x745E66CD, 0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039, 0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5, 0xBE2B5B58, 0xBAEA46EF, 0xB7A96036, 0xB3687D81, 0xAD2F2D84, 0xA9EE3033, 0xA4AD16EA, 0xA06C0B5D, 0xD4326D90, 0xD0F37027, 0xDDB056FE, 0xD9714B49, 0xC7361B4C, 0xC3F706FB, 0xCEB42022, 0xCA753D95, 0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1, 0xE13EF6F4, 0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D, 0x34867077, 0x30476DC0, 0x3D044B19, 0x39C556AE, 0x278206AB, 0x23431B1C, 0x2E003DC5, 0x2AC12072, 0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16, 0x018AEB13, 0x054BF6A4, 0x0808D07D, 0x0CC9CDCA, 0x7897AB07, 0x7C56B6B0, 0x71159069, 0x75D48DDE, 0x6B93DDDB, 0x6F52C06C, 0x6211E6B5, 0x66D0FB02, 0x5E9F46BF, 0x5A5E5B08, 0x571D7DD1, 0x53DC6066, 0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA, 0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E, 0xBFA1B04B, 0xBB60ADFC, 0xB6238B25, 0xB2E29692, 0x8AAD2B2F, 0x8E6C3698, 0x832F1041, 0x87EE0DF6, 0x99A95DF3, 0x9D684044, 0x902B669D, 0x94EA7B2A, 0xE0B41DE7, 0xE4750050, 0xE9362689, 0xEDF73B3E, 0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2, 0xC6BCF05F, 0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686, 0xD5B88683, 0xD1799B34, 0xDC3ABDED, 0xD8FBA05A, 0x690CE0EE, 0x6DCDFD59, 0x608EDB80, 0x644FC637, 0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB, 0x4F040D56, 0x4BC510E1, 0x46863638, 0x42472B8F, 0x5C007B8A, 0x58C1663D, 0x558240E4, 0x51435D53, 0x251D3B9E, 0x21DC2629, 0x2C9F00F0, 0x285E1D47, 0x36194D42, 0x32D850F5, 0x3F9B762C, 0x3B5A6B9B, 0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF, 0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623, 0xF12F560E, 0xF5EE4BB9, 0xF8AD6D60, 0xFC6C70D7, 0xE22B20D2, 0xE6EA3D65, 0xEBA91BBC, 0xEF68060B, 0xD727BBB6, 0xD3E6A601, 0xDEA580D8, 0xDA649D6F, 0xC423CD6A, 0xC0E2D0DD, 0xCDA1F604, 0xC960EBB3, 0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7, 0xAE3AFBA2, 0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B, 0x9B3660C6, 0x9FF77D71, 0x92B45BA8, 0x9675461F, 0x8832161A, 0x8CF30BAD, 0x81B02D74, 0x857130C3, 0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640, 0x4E8EE645, 0x4A4FFBF2, 0x470CDD2B, 0x43CDC09C, 0x7B827D21, 0x7F436096, 0x7200464F, 0x76C15BF8, 0x68860BFD, 0x6C47164A, 0x61043093, 0x65C52D24, 0x119B4BE9, 0x155A565E, 0x18197087, 0x1CD86D30, 0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC, 0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088, 0x2497D08D, 0x2056CD3A, 0x2D15EBE3, 0x29D4F654, 0xC5A92679, 0xC1683BCE, 0xCC2B1D17, 0xC8EA00A0, 0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB, 0xDBEE767C, 0xE3A1CBC1, 0xE760D676, 0xEA23F0AF, 0xEEE2ED18, 0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4, 0x89B8FD09, 0x8D79E0BE, 0x803AC667, 0x84FBDBD0, 0x9ABC8BD5, 0x9E7D9662, 0x933EB0BB, 0x97FFAD0C, 0xAFB010B1, 0xAB710D06, 0xA6322BDF, 0xA2F33668, 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4 }; /* Nonzero if any of the files read were the standard input. */ static int have_read_stdin; /* Calculate and print the checksum and length in bytes of file FILE, or of the standard input if FILE is "-". If PRINT_NAME is nonzero, print FILE next to the checksum and size. Return 0 if successful, -1 if an error occurs. */ static int cksum (const char *file, int print_name) { unsigned char buf[BUFLEN]; uint_fast32_t crc = 0; uintmax_t length = 0; size_t bytes_read; register FILE *fp; char length_buf[INT_BUFSIZE_BOUND (uintmax_t)]; char const *hp; if (STREQ (file, "-")) { fp = stdin; have_read_stdin = 1; } else { fp = fopen (file, "r"); if (fp == NULL) { error (0, errno, "%s", file); return -1; } } /* Read input in BINARY mode, unless it is a console device. */ SET_BINARY (fileno (fp)); while ((bytes_read = fread (buf, 1, BUFLEN, fp)) > 0) { unsigned char *cp = buf; if (length + bytes_read < length) error (EXIT_FAILURE, 0, _("%s: file too long"), file); length += bytes_read; while (bytes_read--) crc = (crc << 8) ^ crctab[((crc >> 24) ^ *cp++) & 0xFF]; if (feof (fp)) break; } if (ferror (fp)) { error (0, errno, "%s", file); if (!STREQ (file, "-")) fclose (fp); return -1; } if (!STREQ (file, "-") && fclose (fp) == EOF) { error (0, errno, "%s", file); return -1; } hp = umaxtostr (length, length_buf); for (; length; length >>= 8) crc = (crc << 8) ^ crctab[((crc >> 24) ^ length) & 0xFF]; crc = ~crc & 0xFFFFFFFF; if (print_name) printf ("%u %s %s\n", (unsigned) crc, hp, file); else printf ("%u %s\n", (unsigned) crc, hp); if (ferror (stdout)) error (EXIT_FAILURE, errno, "-: %s", _("write error")); return 0; } void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [FILE]...\n\ or: %s [OPTION]\n\ "), program_name, program_name); fputs (_("\ Print CRC checksum and byte counts of each FILE.\n\ \n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } int main (int argc, char **argv) { int i, c; int errors = 0; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE, VERSION, AUTHORS, usage); have_read_stdin = 0; while ((c = getopt_long (argc, argv, "", long_options, NULL)) != -1) { switch (c) { case 0: break; default: usage (EXIT_FAILURE); } } if (optind == argc) errors |= cksum ("-", 0); else { for (i = optind; i < argc; i++) errors |= cksum (argv[i], 1); } if (have_read_stdin && fclose (stdin) == EOF) error (EXIT_FAILURE, errno, "-"); exit (errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } #endif /* !CRCTAB */
/* csplit - split a file into sections determined by context lines Copyright (C) 91, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Stuart Kemp, cpsrk@groper.jcu.edu.au. Modified by David MacKenzie, djm@gnu.ai.mit.edu. */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include <signal.h> #include "system.h" #include "closeout.h" #include <regex.h> #include "error.h" #include "inttostr.h" #include "safe-read.h" #include "xstrtol.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "csplit" #define AUTHORS N_ ("Stuart Kemp and David MacKenzie") #ifndef TRUE # define FALSE 0 # define TRUE 1 #endif /* Increment size of area for control records. */ #define ALLOC_SIZE 20 /* The default prefix for output file names. */ #define DEFAULT_PREFIX "xx" typedef int boolean; /* A compiled pattern arg. */ struct control { char *regexpr; /* Non-compiled regular expression. */ struct re_pattern_buffer re_compiled; /* Compiled regular expression. */ int offset; /* Offset from regexp to split at. */ uintmax_t lines_required; /* Number of lines required. */ uintmax_t repeat; /* Repeat count. */ int repeat_forever; /* Non-zero if `*' used as a repeat count. */ int argnum; /* ARGV index. */ boolean ignore; /* If true, produce no output (for regexp). */ }; /* Initial size of data area in buffers. */ #define START_SIZE 8191 /* Increment size for data area. */ #define INCR_SIZE 2048 /* Number of lines kept in each node in line list. */ #define CTRL_SIZE 80 #ifdef DEBUG /* Some small values to test the algorithms. */ # define START_SIZE 200 # define INCR_SIZE 10 # define CTRL_SIZE 1 #endif /* A string with a length count. */ struct cstring { unsigned int len; char *str; }; /* Pointers to the beginnings of lines in the buffer area. These structures are linked together if needed. */ struct line { unsigned used; /* Number of offsets used in this struct. */ unsigned insert_index; /* Next offset to use when inserting line. */ unsigned retrieve_index; /* Next index to use when retrieving line. */ struct cstring starts[CTRL_SIZE]; /* Lines in the data area. */ struct line *next; /* Next in linked list. */ }; /* The structure to hold the input lines. Contains a pointer to the data area and a list containing pointers to the individual lines. */ struct buffer_record { unsigned bytes_alloc; /* Size of the buffer area. */ unsigned bytes_used; /* Bytes used in the buffer area. */ unsigned start_line; /* First line number in this buffer. */ unsigned first_available; /* First line that can be retrieved. */ unsigned num_lines; /* Number of complete lines in this buffer. */ char *buffer; /* Data area. */ struct line *line_start; /* Head of list of pointers to lines. */ struct line *curr_line; /* The line start record currently in use. */ struct buffer_record *next; }; static void close_output_file (void); static void create_output_file (void); static void delete_all_files (void); static void save_line_to_file (const struct cstring *line); void usage (int status); /* The name this program was run with. */ char *program_name; /* Convert the number of 8-bit bytes of a binary representation to the number of characters required to represent the same quantity as an unsigned octal. For example, a 32-bit (4-byte) quantity may require a field width as wide as 11 characters. */ static const unsigned int bytes_to_octal_digits[] = {0, 3, 6, 8, 11, 14, 16, 19, 22, 25, 27, 30, 32, 35, 38, 41, 43}; /* Input file descriptor. */ static int input_desc = 0; /* List of available buffers. */ static struct buffer_record *free_list = NULL; /* Start of buffer list. */ static struct buffer_record *head = NULL; /* Partially read line. */ static char *hold_area = NULL; /* Number of chars in `hold_area'. */ static unsigned hold_count = 0; /* Number of the last line in the buffers. */ static unsigned last_line_number = 0; /* Number of the line currently being examined. */ static unsigned current_line = 0; /* If TRUE, we have read EOF. */ static boolean have_read_eof = FALSE; /* Name of output files. */ static char *filename_space = NULL; /* Prefix part of output file names. */ static char *prefix = NULL; /* Suffix part of output file names. */ static char *suffix = NULL; /* Number of digits to use in output file names. */ static int digits = 2; /* Number of files created so far. */ static unsigned int files_created = 0; /* Number of bytes written to current file. */ static unsigned int bytes_written; /* Output file pointer. */ static FILE *output_stream = NULL; /* Output file name. */ static char *output_filename = NULL; /* Perhaps it would be cleaner to pass arg values instead of indexes. */ static char **global_argv; /* If TRUE, do not print the count of bytes in each output file. */ static boolean suppress_count; /* If TRUE, remove output files on error. */ static boolean remove_files; /* If TRUE, remove all output files which have a zero length. */ static boolean elide_empty_files; /* The compiled pattern arguments, which determine how to split the input file. */ static struct control *controls; /* Number of elements in `controls'. */ static unsigned int control_used; static struct option const longopts[] = { {"digits", required_argument, NULL, 'n'}, {"quiet", no_argument, NULL, 'q'}, {"silent", no_argument, NULL, 's'}, {"keep-files", no_argument, NULL, 'k'}, {"elide-empty-files", no_argument, NULL, 'z'}, {"prefix", required_argument, NULL, 'f'}, {"suffix-format", required_argument, NULL, 'b'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; /* Optionally remove files created so far; then exit. Called when an error detected. */ static void cleanup (void) { if (output_stream) close_output_file (); if (remove_files) delete_all_files (); } static void cleanup_fatal (void) { cleanup (); exit (EXIT_FAILURE); } static RETSIGTYPE interrupt_handler (int sig) { #ifdef SA_NOCLDSTOP struct sigaction sigact; sigact.sa_handler = SIG_DFL; sigemptyset (&sigact.sa_mask); sigact.sa_flags = 0; sigaction (sig, &sigact, NULL); #else signal (sig, SIG_DFL); #endif cleanup (); raise (sig); } /* Keep track of NUM chars of a partial line in buffer START. These chars will be retrieved later when another large buffer is read. It is not necessary to create a new buffer for these chars; instead, we keep a pointer to the existing buffer. This buffer *is* on the free list, and when the next buffer is obtained from this list (even if it is this one), these chars will be placed at the start of the new buffer. */ static void save_to_hold_area (char *start, unsigned int num) { hold_area = start; hold_count = num; } /* Read up to MAX_N_BYTES chars from the input stream into DEST. Return the number of chars read. */ /* FIXME: MAX_N_BYTES should be of type size_t, but if you pull that thread, you'll find there are many other `unsigned' types in this file that should also be changed. */ static size_t read_input (char *dest, int max_n_bytes) { size_t bytes_read; if (max_n_bytes == 0) return 0; bytes_read = safe_read (input_desc, dest, max_n_bytes); if (bytes_read == 0) have_read_eof = TRUE; if (bytes_read == SAFE_READ_ERROR) { error (0, errno, _("read error")); cleanup_fatal (); } return bytes_read; } /* Initialize existing line record P. */ static void clear_line_control (struct line *p) { p->used = 0; p->insert_index = 0; p->retrieve_index = 0; } /* Initialize all line records in B. */ static void clear_all_line_control (struct buffer_record *b) { struct line *l; for (l = b->line_start; l; l = l->next) clear_line_control (l); } /* Return a new, initialized line record. */ static struct line * new_line_control (void) { struct line *p; p = (struct line *) xmalloc (sizeof (struct line)); p->next = NULL; clear_line_control (p); return p; } /* Record LINE_START, which is the address of the start of a line of length LINE_LEN in the large buffer, in the lines buffer of B. */ static void keep_new_line (struct buffer_record *b, char *line_start, int line_len) { struct line *l; /* If there is no existing area to keep line info, get some. */ if (b->line_start == NULL) b->line_start = b->curr_line = new_line_control (); /* If existing area for lines is full, get more. */ if (b->curr_line->used == CTRL_SIZE) { b->curr_line->next = new_line_control (); b->curr_line = b->curr_line->next; } l = b->curr_line; /* Record the start of the line, and update counters. */ l->starts[l->insert_index].str = line_start; l->starts[l->insert_index].len = line_len; l->used++; l->insert_index++; } /* Scan the buffer in B for newline characters and record the line start locations and lengths in B. Return the number of lines found in this buffer. There may be an incomplete line at the end of the buffer; a pointer is kept to this area, which will be used when the next buffer is filled. */ static unsigned int record_line_starts (struct buffer_record *b) { char *line_start; /* Start of current line. */ char *line_end; /* End of each line found. */ unsigned int bytes_left; /* Length of incomplete last line. */ unsigned int lines; /* Number of lines found. */ unsigned int line_length; /* Length of each line found. */ if (b->bytes_used == 0) return 0; lines = 0; line_start = b->buffer; bytes_left = b->bytes_used; for (;;) { line_end = memchr (line_start, '\n', bytes_left); if (line_end == NULL) break; line_length = line_end - line_start + 1; keep_new_line (b, line_start, line_length); bytes_left -= line_length; line_start = line_end + 1; lines++; } /* Check for an incomplete last line. */ if (bytes_left) { if (have_read_eof) { keep_new_line (b, line_start, bytes_left); lines++; } else save_to_hold_area (line_start, bytes_left); } b->num_lines = lines; b->first_available = b->start_line = last_line_number + 1; last_line_number += lines; return lines; } /* Return a new buffer with room to store SIZE bytes, plus an extra byte for safety. */ static struct buffer_record * create_new_buffer (unsigned int size) { struct buffer_record *new_buffer; new_buffer = (struct buffer_record *) xmalloc (sizeof (struct buffer_record)); new_buffer->buffer = (char *) xmalloc (size + 1); new_buffer->bytes_alloc = size; new_buffer->line_start = new_buffer->curr_line = NULL; return new_buffer; } /* Return a new buffer of at least MINSIZE bytes. If a buffer of at least that size is currently free, use it, otherwise create a new one. */ static struct buffer_record * get_new_buffer (unsigned int min_size) { struct buffer_record *p, *q; struct buffer_record *new_buffer; /* Buffer to return. */ unsigned int alloc_size; /* Actual size that will be requested. */ alloc_size = START_SIZE; while (min_size > alloc_size) alloc_size += INCR_SIZE; if (free_list == NULL) new_buffer = create_new_buffer (alloc_size); else { /* Use first-fit to find a buffer. */ p = new_buffer = NULL; q = free_list; do { if (q->bytes_alloc >= min_size) { if (p == NULL) free_list = q->next; else p->next = q->next; break; } p = q; q = q->next; } while (q); new_buffer = (q ? q : create_new_buffer (alloc_size)); new_buffer->curr_line = new_buffer->line_start; clear_all_line_control (new_buffer); } new_buffer->num_lines = 0; new_buffer->bytes_used = 0; new_buffer->start_line = new_buffer->first_available = last_line_number + 1; new_buffer->next = NULL; return new_buffer; } /* Add buffer BUF to the list of free buffers. */ static void free_buffer (struct buffer_record *buf) { buf->next = free_list; free_list = buf; } /* Append buffer BUF to the linked list of buffers that contain some data yet to be processed. */ static void save_buffer (struct buffer_record *buf) { struct buffer_record *p; buf->next = NULL; buf->curr_line = buf->line_start; if (head == NULL) head = buf; else { for (p = head; p->next; p = p->next) /* Do nothing. */ ; p->next = buf; } } /* Fill a buffer of input. Set the initial size of the buffer to a default. Fill the buffer (from the hold area and input stream) and find the individual lines. If no lines are found (the buffer is too small to hold the next line), release the current buffer (whose contents would have been put in the hold area) and repeat the process with another large buffer until at least one entire line has been read. Return TRUE if a new buffer was obtained, otherwise false (in which case end-of-file must have been encountered). */ static boolean load_buffer (void) { struct buffer_record *b; unsigned int bytes_wanted = START_SIZE; /* Minimum buffer size. */ unsigned int bytes_avail; /* Size of new buffer created. */ unsigned int lines_found; /* Number of lines in this new buffer. */ char *p; /* Place to load into buffer. */ if (have_read_eof) return FALSE; /* We must make the buffer at least as large as the amount of data in the partial line left over from the last call. */ if (bytes_wanted < hold_count) bytes_wanted = hold_count; do { b = get_new_buffer (bytes_wanted); bytes_avail = b->bytes_alloc; /* Size of buffer returned. */ p = b->buffer; /* First check the `holding' area for a partial line. */ if (hold_count) { if (p != hold_area) memcpy (p, hold_area, hold_count); p += hold_count; b->bytes_used += hold_count; bytes_avail -= hold_count; hold_count = 0; } b->bytes_used += (unsigned int) read_input (p, bytes_avail); lines_found = record_line_starts (b); bytes_wanted = b->bytes_alloc * 2; if (!lines_found) free_buffer (b); } while (!lines_found && !have_read_eof); if (lines_found) save_buffer (b); return lines_found != 0; } /* Return the line number of the first line that has not yet been retrieved. */ static unsigned int get_first_line_in_buffer (void) { if (head == NULL && !load_buffer ()) error (EXIT_FAILURE, errno, _("input disappeared")); return head->first_available; } /* Return a pointer to the logical first line in the buffer and make the next line the logical first line. Return NULL if there is no more input. */ static struct cstring * remove_line (void) { struct cstring *line; /* Return value. */ struct line *l; /* For convenience. */ if (head == NULL && !load_buffer ()) return NULL; if (current_line < head->first_available) current_line = head->first_available; ++(head->first_available); l = head->curr_line; line = &l->starts[l->retrieve_index]; /* Advance index to next line. */ if (++l->retrieve_index == l->used) { /* Go on to the next line record. */ head->curr_line = l->next; if (head->curr_line == NULL || head->curr_line->used == 0) { /* Go on to the next data block. */ struct buffer_record *b = head; head = head->next; free_buffer (b); } } return line; } /* Search the buffers for line LINENUM, reading more input if necessary. Return a pointer to the line, or NULL if it is not found in the file. */ static struct cstring * find_line (unsigned int linenum) { struct buffer_record *b; if (head == NULL && !load_buffer ()) return NULL; if (linenum < head->start_line) return NULL; for (b = head;;) { if (linenum < b->start_line + b->num_lines) { /* The line is in this buffer. */ struct line *l; unsigned int offset; /* How far into the buffer the line is. */ l = b->line_start; offset = linenum - b->start_line; /* Find the control record. */ while (offset >= CTRL_SIZE) { l = l->next; offset -= CTRL_SIZE; } return &l->starts[offset]; } if (b->next == NULL && !load_buffer ()) return NULL; b = b->next; /* Try the next data block. */ } } /* Return TRUE if at least one more line is available for input. */ static boolean no_more_lines (void) { return (find_line (current_line + 1) == NULL) ? TRUE : FALSE; } /* Set the name of the input file to NAME and open it. */ static void set_input_file (const char *name) { if (STREQ (name, "-")) input_desc = 0; else { input_desc = open (name, O_RDONLY); if (input_desc < 0) error (EXIT_FAILURE, errno, "%s", name); } } /* Write all lines from the beginning of the buffer up to, but not including, line LAST_LINE, to the current output file. If IGNORE is TRUE, do not output lines selected here. ARGNUM is the index in ARGV of the current pattern. */ static void write_to_file (unsigned int last_line, boolean ignore, int argnum) { struct cstring *line; unsigned int first_line; /* First available input line. */ unsigned int lines; /* Number of lines to output. */ unsigned int i; first_line = get_first_line_in_buffer (); if (first_line > last_line) { error (0, 0, _("%s: line number out of range"), global_argv[argnum]); cleanup_fatal (); } lines = last_line - first_line; for (i = 0; i < lines; i++) { line = remove_line (); if (line == NULL) { error (0, 0, _("%s: line number out of range"), global_argv[argnum]); cleanup_fatal (); } if (!ignore) save_line_to_file (line); } } /* Output any lines left after all regexps have been processed. */ static void dump_rest_of_file (void) { struct cstring *line; while ((line = remove_line ()) != NULL) save_line_to_file (line); } /* Handle an attempt to read beyond EOF under the control of record P, on iteration REPETITION if nonzero. */ static void handle_line_error (const struct control *p, int repetition) { char buf[INT_BUFSIZE_BOUND (uintmax_t)]; fprintf (stderr, _("%s: `%s': line number out of range"), program_name, umaxtostr (p->lines_required, buf)); if (repetition) fprintf (stderr, _(" on repetition %d\n"), repetition); else fprintf (stderr, "\n"); cleanup_fatal (); } /* Determine the line number that marks the end of this file, then get those lines and save them to the output file. P is the control record. REPETITION is the repetition number. */ static void process_line_count (const struct control *p, int repetition) { unsigned int linenum; uintmax_t last_line_to_save = p->lines_required * (repetition + 1); struct cstring *line; create_output_file (); linenum = get_first_line_in_buffer (); while (linenum++ < last_line_to_save) { line = remove_line (); if (line == NULL) handle_line_error (p, repetition); save_line_to_file (line); } close_output_file (); /* Ensure that the line number specified is not 1 greater than the number of lines in the file. */ if (no_more_lines ()) handle_line_error (p, repetition); } static void regexp_error (struct control *p, int repetition, boolean ignore) { fprintf (stderr, _("%s: `%s': match not found"), program_name, global_argv[p->argnum]); if (repetition) fprintf (stderr, _(" on repetition %d\n"), repetition); else fprintf (stderr, "\n"); if (!ignore) { dump_rest_of_file (); close_output_file (); } cleanup_fatal (); } /* Read the input until a line matches the regexp in P, outputting it unless P->IGNORE is TRUE. REPETITION is this repeat-count; 0 means the first time. */ static void process_regexp (struct control *p, int repetition) { struct cstring *line; /* From input file. */ unsigned int line_len; /* To make "$" in regexps work. */ unsigned int break_line; /* First line number of next file. */ boolean ignore = p->ignore; /* If TRUE, skip this section. */ int ret; if (!ignore) create_output_file (); /* If there is no offset for the regular expression, or it is positive, then it is not necessary to buffer the lines. */ if (p->offset >= 0) { for (;;) { line = find_line (++current_line); if (line == NULL) { if (p->repeat_forever) { if (!ignore) { dump_rest_of_file (); close_output_file (); } exit (EXIT_SUCCESS); } else regexp_error (p, repetition, ignore); } line_len = line->len; if (line->str[line_len - 1] == '\n') line_len--; ret = re_search (&p->re_compiled, line->str, line_len, 0, line_len, (struct re_registers *) 0); if (ret == -2) { error (0, 0, _("error in regular expression search")); cleanup_fatal (); } if (ret == -1) { line = remove_line (); if (!ignore) save_line_to_file (line); } else break; } } else { /* Buffer the lines. */ for (;;) { line = find_line (++current_line); if (line == NULL) { if (p->repeat_forever) { if (!ignore) { dump_rest_of_file (); close_output_file (); } exit (EXIT_SUCCESS); } else regexp_error (p, repetition, ignore); } line_len = line->len; if (line->str[line_len - 1] == '\n') line_len--; ret = re_search (&p->re_compiled, line->str, line_len, 0, line_len, (struct re_registers *) 0); if (ret == -2) { error (0, 0, _("error in regular expression search")); cleanup_fatal (); } if (ret >= 0) break; } } /* Account for any offset from this regexp. */ break_line = current_line + p->offset; write_to_file (break_line, ignore, p->argnum); if (!ignore) close_output_file (); if (p->offset > 0) current_line = break_line; } /* Split the input file according to the control records we have built. */ static void split_file (void) { unsigned int i, j; for (i = 0; i < control_used; i++) { if (controls[i].regexpr) { for (j = 0; (controls[i].repeat_forever || j <= controls[i].repeat); j++) process_regexp (&controls[i], j); } else { for (j = 0; (controls[i].repeat_forever || j <= controls[i].repeat); j++) process_line_count (&controls[i], j); } } create_output_file (); dump_rest_of_file (); close_output_file (); } /* Return the name of output file number NUM. */ static char * make_filename (unsigned int num) { strcpy (filename_space, prefix); if (suffix) sprintf (filename_space+strlen(prefix), suffix, num); else sprintf (filename_space+strlen(prefix), "%0*d", digits, num); return filename_space; } /* Create the next output file. */ static void create_output_file (void) { output_filename = make_filename (files_created); output_stream = fopen (output_filename, "w"); if (output_stream == NULL) { error (0, errno, "%s", output_filename); cleanup_fatal (); } files_created++; bytes_written = 0; } /* Delete all the files we have created. */ static void delete_all_files (void) { unsigned int i; char *name; for (i = 0; i < files_created; i++) { name = make_filename (i); if (unlink (name)) error (0, errno, "%s", name); } } /* Close the current output file and print the count of characters in this file. */ static void close_output_file (void) { if (output_stream) { if (ferror (output_stream) || fclose (output_stream) == EOF) { error (0, errno, _("write error for `%s'"), output_filename); output_stream = NULL; cleanup_fatal (); } if (bytes_written == 0 && elide_empty_files) { if (unlink (output_filename)) error (0, errno, "%s", output_filename); files_created--; } else { /* FIXME: if we write to stdout here, we have to close stdout and check for errors. */ if (!suppress_count) fprintf (stdout, "%d\n", bytes_written); } output_stream = NULL; } } /* Save line LINE to the output file and increment the character count for the current file. */ static void save_line_to_file (const struct cstring *line) { fwrite (line->str, sizeof (char), line->len, output_stream); bytes_written += line->len; } /* Return a new, initialized control record. */ static struct control * new_control_record (void) { static unsigned control_allocated = 0; /* Total space allocated. */ struct control *p; if (control_allocated == 0) { control_allocated = ALLOC_SIZE; controls = (struct control *) xmalloc (sizeof (struct control) * control_allocated); } else if (control_used == control_allocated) { control_allocated += ALLOC_SIZE; controls = (struct control *) xrealloc ((char *) controls, sizeof (struct control) * control_allocated); } p = &controls[control_used++]; p->regexpr = NULL; p->repeat = 0; p->repeat_forever = 0; p->lines_required = 0; p->offset = 0; return p; } /* Check if there is a numeric offset after a regular expression. STR is the entire command line argument. P is the control record for this regular expression. NUM is the numeric part of STR. */ static void check_for_offset (struct control *p, const char *str, const char *num) { unsigned long val; if (*num != '-' && *num != '+') error (EXIT_FAILURE, 0, _("%s: `+' or `-' expected after delimeter"), str); if (xstrtoul (num + 1, NULL, 10, &val, "") != LONGINT_OK || val > UINT_MAX) error (EXIT_FAILURE, 0, _("%s: integer expected after `%c'"), str, *num); p->offset = (unsigned int) val; if (*num == '-') p->offset = -p->offset; } /* Given that the first character of command line arg STR is '{', make sure that the rest of the string is a valid repeat count and store its value in P. ARGNUM is the ARGV index of STR. */ static void parse_repeat_count (int argnum, struct control *p, char *str) { uintmax_t val; char *end; end = str + strlen (str) - 1; if (*end != '}') error (EXIT_FAILURE, 0, _("%s: `}' is required in repeat count"), str); *end = '\0'; if (str+1 == end-1 && *(str+1) == '*') p->repeat_forever = 1; else { if (xstrtoumax (str + 1, NULL, 10, &val, "") != LONGINT_OK) { error (EXIT_FAILURE, 0, _("%s}: integer required between `{' and `}'"), global_argv[argnum]); } p->repeat = val; } *end = '}'; } /* Extract the regular expression from STR and check for a numeric offset. STR should start with the regexp delimiter character. Return a new control record for the regular expression. ARGNUM is the ARGV index of STR. Unless IGNORE is TRUE, mark these lines for output. */ static struct control * extract_regexp (int argnum, boolean ignore, char *str) { int len; /* Number of chars in this regexp. */ char delim = *str; char *closing_delim; struct control *p; const char *err; closing_delim = strrchr (str + 1, delim); if (closing_delim == NULL) error (EXIT_FAILURE, 0, _("%s: closing delimeter `%c' missing"), str, delim); len = closing_delim - str - 1; p = new_control_record (); p->argnum = argnum; p->ignore = ignore; p->regexpr = (char *) xmalloc ((unsigned) (len + 1)); strncpy (p->regexpr, str + 1, len); p->re_compiled.allocated = len * 2; p->re_compiled.buffer = (unsigned char *) xmalloc (p->re_compiled.allocated); p->re_compiled.fastmap = xmalloc (256); p->re_compiled.translate = 0; err = re_compile_pattern (p->regexpr, len, &p->re_compiled); if (err) { error (0, 0, _("%s: invalid regular expression: %s"), str, err); cleanup_fatal (); } if (closing_delim[1]) check_for_offset (p, str, closing_delim + 1); return p; } /* Extract the break patterns from args START through ARGC - 1 of ARGV. After each pattern, check if the next argument is a repeat count. */ static void parse_patterns (int argc, int start, char **argv) { int i; /* Index into ARGV. */ struct control *p; /* New control record created. */ uintmax_t val; static uintmax_t last_val = 0; for (i = start; i < argc; i++) { if (*argv[i] == '/' || *argv[i] == '%') { p = extract_regexp (i, *argv[i] == '%', argv[i]); } else { p = new_control_record (); p->argnum = i; if (xstrtoumax (argv[i], NULL, 10, &val, "") != LONGINT_OK) error (EXIT_FAILURE, 0, _("%s: invalid pattern"), argv[i]); if (val == 0) error (EXIT_FAILURE, 0, _("%s: line number must be greater than zero"), argv[i]); if (val < last_val) { char buf[INT_BUFSIZE_BOUND (uintmax_t)]; error (EXIT_FAILURE, 0, _("line number `%s' is smaller than preceding line number, %s"), argv[i], umaxtostr (last_val, buf)); } if (val == last_val) error (0, 0, _("warning: line number `%s' is the same as preceding line number"), argv[i]); last_val = val; p->lines_required = val; } if (i + 1 < argc && *argv[i + 1] == '{') { /* We have a repeat count. */ i++; parse_repeat_count (i, p, argv[i]); } } } static unsigned get_format_flags (char **format_ptr) { unsigned count = 0; for (; **format_ptr; (*format_ptr)++) { switch (**format_ptr) { case '-': break; case '+': case ' ': count++; break; case '#': count += 2; /* Allow for 0x prefix preceeding an `x' conversion. */ break; default: return count; } } return count; } static unsigned get_format_width (char **format_ptr) { unsigned count = 0; char *start; int ch_save; start = *format_ptr; for (; ISDIGIT (**format_ptr); (*format_ptr)++) continue; ch_save = **format_ptr; **format_ptr = '\0'; /* In the case where no minimum field width is explicitly specified, allow for enough octal digits to represent the value of LONG_MAX. */ count = ((*format_ptr == start) ? bytes_to_octal_digits[sizeof (long)] /* FIXME: don't use atoi, it may silently overflow. Besides, we know the result is non-negative, so shouldn't need that cast. */ : (unsigned) atoi (start)); **format_ptr = ch_save; return count; } static unsigned get_format_prec (char **format_ptr) { unsigned count = 0; char *start; int ch_save; int is_negative; if (**format_ptr != '.') return 0; (*format_ptr)++; if (**format_ptr == '-' || **format_ptr == '+') { is_negative = (**format_ptr == '-'); (*format_ptr)++; } else { is_negative = 0; } start = *format_ptr; for (; ISDIGIT (**format_ptr); (*format_ptr)++) continue; /* ANSI 4.9.6.1 says that if the precision is negative, it's as good as not there. */ if (is_negative) start = *format_ptr; ch_save = **format_ptr; **format_ptr = '\0'; count = (*format_ptr == start) ? 11 : atoi (start); **format_ptr = ch_save; return count; } static void get_format_conv_type (char **format_ptr) { int ch = *((*format_ptr)++); switch (ch) { case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': break; case 0: error (EXIT_FAILURE, 0, _("missing conversion specifier in suffix")); break; default: if (ISPRINT (ch)) error (EXIT_FAILURE, 0, _("invalid conversion specifier in suffix: %c"), ch); else error (EXIT_FAILURE, 0, _("invalid conversion specifier in suffix: \\%.3o"), ch); } } static unsigned max_out (char *format) { unsigned out_count = 0; unsigned percents = 0; for (; *format; ) { int ch = *format++; if (ch != '%') out_count++; else { percents++; out_count += get_format_flags (&format); { int width = get_format_width (&format); int prec = get_format_prec (&format); out_count += MAX (width, prec); } get_format_conv_type (&format); } } if (percents == 0) error (EXIT_FAILURE, 0, _("missing %% conversion specification in suffix")); else if (percents > 1) error (EXIT_FAILURE, 0, _("too many %% conversion specifications in suffix")); return out_count; } int main (int argc, char **argv) { int optc; unsigned long val; #ifdef SA_NOCLDSTOP struct sigaction oldact, newact; #endif program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); global_argv = argv; controls = NULL; control_used = 0; suppress_count = FALSE; remove_files = TRUE; prefix = DEFAULT_PREFIX; /* Change the way xmalloc and xrealloc fail. */ xalloc_fail_func = cleanup; #ifdef SA_NOCLDSTOP newact.sa_handler = interrupt_handler; sigemptyset (&newact.sa_mask); newact.sa_flags = 0; sigaction (SIGHUP, NULL, &oldact); if (oldact.sa_handler != SIG_IGN) sigaction (SIGHUP, &newact, NULL); sigaction (SIGINT, NULL, &oldact); if (oldact.sa_handler != SIG_IGN) sigaction (SIGINT, &newact, NULL); sigaction (SIGQUIT, NULL, &oldact); if (oldact.sa_handler != SIG_IGN) sigaction (SIGQUIT, &newact, NULL); sigaction (SIGTERM, NULL, &oldact); if (oldact.sa_handler != SIG_IGN) sigaction (SIGTERM, &newact, NULL); #else if (signal (SIGHUP, SIG_IGN) != SIG_IGN) signal (SIGHUP, interrupt_handler); if (signal (SIGINT, SIG_IGN) != SIG_IGN) signal (SIGINT, interrupt_handler); if (signal (SIGQUIT, SIG_IGN) != SIG_IGN) signal (SIGQUIT, interrupt_handler); if (signal (SIGTERM, SIG_IGN) != SIG_IGN) signal (SIGTERM, interrupt_handler); #endif while ((optc = getopt_long (argc, argv, "f:b:kn:sqz", longopts, NULL)) != -1) switch (optc) { case 0: break; case 'f': prefix = optarg; break; case 'b': suffix = optarg; break; case 'k': remove_files = FALSE; break; case 'n': if (xstrtoul (optarg, NULL, 10, &val, "") != LONGINT_OK || val > INT_MAX) error (EXIT_FAILURE, 0, _("%s: invalid number"), optarg); digits = (int) val; break; case 's': case 'q': suppress_count = TRUE; break; case 'z': elide_empty_files = TRUE; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } if (argc - optind < 2) { error (0, 0, _("too few arguments")); usage (EXIT_FAILURE); } if (suffix) filename_space = (char *) xmalloc (strlen (prefix) + max_out (suffix) + 2); else filename_space = (char *) xmalloc (strlen (prefix) + digits + 2); set_input_file (argv[optind++]); parse_patterns (argc, optind, argv); split_file (); if (close (input_desc) < 0) { error (0, errno, _("read error")); cleanup_fatal (); } exit (EXIT_SUCCESS); } void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... FILE PATTERN...\n\ "), program_name); fputs (_("\ Output pieces of FILE separated by PATTERN(s) to files `xx01', `xx02', ...,\n\ and output byte counts of each piece to standard output.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ -b, --suffix-format=FORMAT use sprintf FORMAT instead of %d\n\ -f, --prefix=PREFIX use PREFIX instead of `xx'\n\ -k, --keep-files do not remove output files on errors\n\ "), stdout); fputs (_("\ -n, --digits=DIGITS use specified number of digits instead of 2\n\ -s, --quiet, --silent do not print counts of output file sizes\n\ -z, --elide-empty-files remove empty output files\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ Read standard input if FILE is -. Each PATTERN may be:\n\ "), stdout); fputs (_("\ \n\ INTEGER copy up to but not including specified line number\n\ /REGEXP/[OFFSET] copy up to but not including a matching line\n\ %REGEXP%[OFFSET] skip to, but not including a matching line\n\ {INTEGER} repeat the previous pattern specified number of times\n\ {*} repeat the previous pattern as many times as possible\n\ \n\ A line OFFSET is a required `+' or `-' followed by a positive integer.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); }
/* Definitions for data structures and routines for the regular expression library, version 0.12. Copyright (C) 1985,1989-93,1995-98,2000,2001 Free Software Foundation, Inc. This file is part of the GNU C Library. Its master source is NOT part of the C library, however. The master source lives in /gd/gnu/lib. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #ifndef _REGEX_H #define _REGEX_H 1 /* Allow the use in C++ code. */ #ifdef __cplusplus extern "C" { #endif /* POSIX says that <sys/types.h> must be included (by the caller) before <regex.h>. */ #if !defined _POSIX_C_SOURCE && !defined _POSIX_SOURCE && defined VMS /* VMS doesn't have `size_t' in <sys/types.h>, even though POSIX says it should be there. */ # include <stddef.h> #endif /* The following two types have to be signed and unsigned integer type wide enough to hold a value of a pointer. For most ANSI compilers ptrdiff_t and size_t should be likely OK. Still size of these two types is 2 for Microsoft C. Ugh... */ typedef long int s_reg_t; typedef unsigned long int active_reg_t; /* The following bits are used to determine the regexp syntax we recognize. The set/not-set meanings are chosen so that Emacs syntax remains the value 0. The bits are given in alphabetical order, and the definitions shifted by one from the previous bit; thus, when we add or remove a bit, only one other definition need change. */ typedef unsigned long int reg_syntax_t; /* If this bit is not set, then \ inside a bracket expression is literal. If set, then such a \ quotes the following character. */ #define RE_BACKSLASH_ESCAPE_IN_LISTS ((unsigned long int) 1) /* If this bit is not set, then + and ? are operators, and \+ and \? are literals. If set, then \+ and \? are operators and + and ? are literals. */ #define RE_BK_PLUS_QM (RE_BACKSLASH_ESCAPE_IN_LISTS << 1) /* If this bit is set, then character classes are supported. They are: [:alpha:], [:upper:], [:lower:], [:digit:], [:alnum:], [:xdigit:], [:space:], [:print:], [:punct:], [:graph:], and [:cntrl:]. If not set, then character classes are not supported. */ #define RE_CHAR_CLASSES (RE_BK_PLUS_QM << 1) /* If this bit is set, then ^ and $ are always anchors (outside bracket expressions, of course). If this bit is not set, then it depends: ^ is an anchor if it is at the beginning of a regular expression or after an open-group or an alternation operator; $ is an anchor if it is at the end of a regular expression, or before a close-group or an alternation operator. This bit could be (re)combined with RE_CONTEXT_INDEP_OPS, because POSIX draft 11.2 says that * etc. in leading positions is undefined. We already implemented a previous draft which made those constructs invalid, though, so we haven't changed the code back. */ #define RE_CONTEXT_INDEP_ANCHORS (RE_CHAR_CLASSES << 1) /* If this bit is set, then special characters are always special regardless of where they are in the pattern. If this bit is not set, then special characters are special only in some contexts; otherwise they are ordinary. Specifically, * + ? and intervals are only special when not after the beginning, open-group, or alternation operator. */ #define RE_CONTEXT_INDEP_OPS (RE_CONTEXT_INDEP_ANCHORS << 1) /* If this bit is set, then *, +, ?, and { cannot be first in an re or immediately after an alternation or begin-group operator. */ #define RE_CONTEXT_INVALID_OPS (RE_CONTEXT_INDEP_OPS << 1) /* If this bit is set, then . matches newline. If not set, then it doesn't. */ #define RE_DOT_NEWLINE (RE_CONTEXT_INVALID_OPS << 1) /* If this bit is set, then . doesn't match NUL. If not set, then it does. */ #define RE_DOT_NOT_NULL (RE_DOT_NEWLINE << 1) /* If this bit is set, nonmatching lists [^...] do not match newline. If not set, they do. */ #define RE_HAT_LISTS_NOT_NEWLINE (RE_DOT_NOT_NULL << 1) /* If this bit is set, either \{...\} or {...} defines an interval, depending on RE_NO_BK_BRACES. If not set, \{, \}, {, and } are literals. */ #define RE_INTERVALS (RE_HAT_LISTS_NOT_NEWLINE << 1) /* If this bit is set, +, ? and | aren't recognized as operators. If not set, they are. */ #define RE_LIMITED_OPS (RE_INTERVALS << 1) /* If this bit is set, newline is an alternation operator. If not set, newline is literal. */ #define RE_NEWLINE_ALT (RE_LIMITED_OPS << 1) /* If this bit is set, then `{...}' defines an interval, and \{ and \} are literals. If not set, then `\{...\}' defines an interval. */ #define RE_NO_BK_BRACES (RE_NEWLINE_ALT << 1) /* If this bit is set, (...) defines a group, and \( and \) are literals. If not set, \(...\) defines a group, and ( and ) are literals. */ #define RE_NO_BK_PARENS (RE_NO_BK_BRACES << 1) /* If this bit is set, then \<digit> matches <digit>. If not set, then \<digit> is a back-reference. */ #define RE_NO_BK_REFS (RE_NO_BK_PARENS << 1) /* If this bit is set, then | is an alternation operator, and \| is literal. If not set, then \| is an alternation operator, and | is literal. */ #define RE_NO_BK_VBAR (RE_NO_BK_REFS << 1) /* If this bit is set, then an ending range point collating higher than the starting range point, as in [z-a], is invalid. If not set, then when ending range point collates higher than the starting range point, the range is ignored. */ #define RE_NO_EMPTY_RANGES (RE_NO_BK_VBAR << 1) /* If this bit is set, then an unmatched ) is ordinary. If not set, then an unmatched ) is invalid. */ #define RE_UNMATCHED_RIGHT_PAREN_ORD (RE_NO_EMPTY_RANGES << 1) /* If this bit is set, succeed as soon as we match the whole pattern, without further backtracking. */ #define RE_NO_POSIX_BACKTRACKING (RE_UNMATCHED_RIGHT_PAREN_ORD << 1) /* If this bit is set, do not process the GNU regex operators. If not set, then the GNU regex operators are recognized. */ #define RE_NO_GNU_OPS (RE_NO_POSIX_BACKTRACKING << 1) /* If this bit is set, turn on internal regex debugging. If not set, and debugging was on, turn it off. This only works if regex.c is compiled -DDEBUG. We define this bit always, so that all that's needed to turn on debugging is to recompile regex.c; the calling code can always have this bit set, and it won't affect anything in the normal case. */ #define RE_DEBUG (RE_NO_GNU_OPS << 1) /* If this bit is set, a syntactically invalid interval is treated as a string of ordinary characters. For example, the ERE 'a{1' is treated as 'a\{1'. */ #define RE_INVALID_INTERVAL_ORD (RE_DEBUG << 1) /* This global variable defines the particular regexp syntax to use (for some interfaces). When a regexp is compiled, the syntax used is stored in the pattern buffer, so changing this does not affect already-compiled regexps. */ extern reg_syntax_t re_syntax_options; /* Define combinations of the above bits for the standard possibilities. (The [[[ comments delimit what gets put into the Texinfo file, so don't delete them!) */ /* [[[begin syntaxes]]] */ #define RE_SYNTAX_EMACS 0 #define RE_SYNTAX_AWK \ (RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DOT_NOT_NULL \ | RE_NO_BK_PARENS | RE_NO_BK_REFS \ | RE_NO_BK_VBAR | RE_NO_EMPTY_RANGES \ | RE_DOT_NEWLINE | RE_CONTEXT_INDEP_ANCHORS \ | RE_UNMATCHED_RIGHT_PAREN_ORD | RE_NO_GNU_OPS) #define RE_SYNTAX_GNU_AWK \ ((RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DEBUG) \ & ~(RE_DOT_NOT_NULL | RE_INTERVALS | RE_CONTEXT_INDEP_OPS)) #define RE_SYNTAX_POSIX_AWK \ (RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS \ | RE_INTERVALS | RE_NO_GNU_OPS) #define RE_SYNTAX_GREP \ (RE_BK_PLUS_QM | RE_CHAR_CLASSES \ | RE_HAT_LISTS_NOT_NEWLINE | RE_INTERVALS \ | RE_NEWLINE_ALT) #define RE_SYNTAX_EGREP \ (RE_CHAR_CLASSES | RE_CONTEXT_INDEP_ANCHORS \ | RE_CONTEXT_INDEP_OPS | RE_HAT_LISTS_NOT_NEWLINE \ | RE_NEWLINE_ALT | RE_NO_BK_PARENS \ | RE_NO_BK_VBAR) #define RE_SYNTAX_POSIX_EGREP \ (RE_SYNTAX_EGREP | RE_INTERVALS | RE_NO_BK_BRACES \ | RE_INVALID_INTERVAL_ORD) /* P1003.2/D11.2, section 4.20.7.1, lines 5078ff. */ #define RE_SYNTAX_ED RE_SYNTAX_POSIX_BASIC #define RE_SYNTAX_SED RE_SYNTAX_POSIX_BASIC /* Syntax bits common to both basic and extended POSIX regex syntax. */ #define _RE_SYNTAX_POSIX_COMMON \ (RE_CHAR_CLASSES | RE_DOT_NEWLINE | RE_DOT_NOT_NULL \ | RE_INTERVALS | RE_NO_EMPTY_RANGES) #define RE_SYNTAX_POSIX_BASIC \ (_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM) /* Differs from ..._POSIX_BASIC only in that RE_BK_PLUS_QM becomes RE_LIMITED_OPS, i.e., \? \+ \| are not recognized. Actually, this isn't minimal, since other operators, such as \`, aren't disabled. */ #define RE_SYNTAX_POSIX_MINIMAL_BASIC \ (_RE_SYNTAX_POSIX_COMMON | RE_LIMITED_OPS) #define RE_SYNTAX_POSIX_EXTENDED \ (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ | RE_CONTEXT_INDEP_OPS | RE_NO_BK_BRACES \ | RE_NO_BK_PARENS | RE_NO_BK_VBAR \ | RE_CONTEXT_INVALID_OPS | RE_UNMATCHED_RIGHT_PAREN_ORD) /* Differs from ..._POSIX_EXTENDED in that RE_CONTEXT_INDEP_OPS is removed and RE_NO_BK_REFS is added. */ #define RE_SYNTAX_POSIX_MINIMAL_EXTENDED \ (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ | RE_CONTEXT_INVALID_OPS | RE_NO_BK_BRACES \ | RE_NO_BK_PARENS | RE_NO_BK_REFS \ | RE_NO_BK_VBAR | RE_UNMATCHED_RIGHT_PAREN_ORD) /* [[[end syntaxes]]] */ /* Maximum number of duplicates an interval can allow. Some systems (erroneously) define this in other header files, but we want our value, so remove any previous define. */ #ifdef RE_DUP_MAX # undef RE_DUP_MAX #endif /* If sizeof(int) == 2, then ((1 << 15) - 1) overflows. */ #define RE_DUP_MAX (0x7fff) /* POSIX `cflags' bits (i.e., information for `regcomp'). */ /* If this bit is set, then use extended regular expression syntax. If not set, then use basic regular expression syntax. */ #define REG_EXTENDED 1 /* If this bit is set, then ignore case when matching. If not set, then case is significant. */ #define REG_ICASE (REG_EXTENDED << 1) /* If this bit is set, then anchors do not match at newline characters in the string. If not set, then anchors do match at newlines. */ #define REG_NEWLINE (REG_ICASE << 1) /* If this bit is set, then report only success or fail in regexec. If not set, then returns differ between not matching and errors. */ #define REG_NOSUB (REG_NEWLINE << 1) /* POSIX `eflags' bits (i.e., information for regexec). */ /* If this bit is set, then the beginning-of-line operator doesn't match the beginning of the string (presumably because it's not the beginning of a line). If not set, then the beginning-of-line operator does match the beginning of the string. */ #define REG_NOTBOL 1 /* Like REG_NOTBOL, except for the end-of-line. */ #define REG_NOTEOL (1 << 1) /* If any error codes are removed, changed, or added, update the `re_error_msg' table in regex.c. */ typedef enum { #ifdef _XOPEN_SOURCE REG_ENOSYS = -1, /* This will never happen for this implementation. */ #endif REG_NOERROR = 0, /* Success. */ REG_NOMATCH, /* Didn't find a match (for regexec). */ /* POSIX regcomp return error codes. (In the order listed in the standard.) */ REG_BADPAT, /* Invalid pattern. */ REG_ECOLLATE, /* Not implemented. */ REG_ECTYPE, /* Invalid character class name. */ REG_EESCAPE, /* Trailing backslash. */ REG_ESUBREG, /* Invalid back reference. */ REG_EBRACK, /* Unmatched left bracket. */ REG_EPAREN, /* Parenthesis imbalance. */ REG_EBRACE, /* Unmatched \{. */ REG_BADBR, /* Invalid contents of \{\}. */ REG_ERANGE, /* Invalid range end. */ REG_ESPACE, /* Ran out of memory. */ REG_BADRPT, /* No preceding re for repetition op. */ /* Error codes we've added. */ REG_EEND, /* Premature end. */ REG_ESIZE, /* Compiled pattern bigger than 2^16 bytes. */ REG_ERPAREN /* Unmatched ) or \); not returned from regcomp. */ } reg_errcode_t; /* This data structure represents a compiled pattern. Before calling the pattern compiler, the fields `buffer', `allocated', `fastmap', `translate', and `no_sub' can be set. After the pattern has been compiled, the `re_nsub' field is available. All other fields are private to the regex routines. */ #ifndef RE_TRANSLATE_TYPE # define RE_TRANSLATE_TYPE char * #endif struct re_pattern_buffer { /* [[[begin pattern_buffer]]] */ /* Space that holds the compiled pattern. It is declared as `unsigned char *' because its elements are sometimes used as array indexes. */ unsigned char *buffer; /* Number of bytes to which `buffer' points. */ unsigned long int allocated; /* Number of bytes actually used in `buffer'. */ unsigned long int used; /* Syntax setting with which the pattern was compiled. */ reg_syntax_t syntax; /* Pointer to a fastmap, if any, otherwise zero. re_search uses the fastmap, if there is one, to skip over impossible starting points for matches. */ char *fastmap; /* Either a translate table to apply to all characters before comparing them, or zero for no translation. The translation is applied to a pattern when it is compiled and to a string when it is matched. */ RE_TRANSLATE_TYPE translate; /* Number of subexpressions found by the compiler. */ size_t re_nsub; /* Zero if this pattern cannot match the empty string, one else. Well, in truth it's used only in `re_search_2', to see whether or not we should use the fastmap, so we don't set this absolutely perfectly; see `re_compile_fastmap' (the `duplicate' case). */ unsigned can_be_null : 1; /* If REGS_UNALLOCATED, allocate space in the `regs' structure for `max (RE_NREGS, re_nsub + 1)' groups. If REGS_REALLOCATE, reallocate space if necessary. If REGS_FIXED, use what's there. */ #define REGS_UNALLOCATED 0 #define REGS_REALLOCATE 1 #define REGS_FIXED 2 unsigned regs_allocated : 2; /* Set to zero when `regex_compile' compiles a pattern; set to one by `re_compile_fastmap' if it updates the fastmap. */ unsigned fastmap_accurate : 1; /* If set, `re_match_2' does not return information about subexpressions. */ unsigned no_sub : 1; /* If set, a beginning-of-line anchor doesn't match at the beginning of the string. */ unsigned not_bol : 1; /* Similarly for an end-of-line anchor. */ unsigned not_eol : 1; /* If true, an anchor at a newline matches. */ unsigned newline_anchor : 1; /* [[[end pattern_buffer]]] */ }; typedef struct re_pattern_buffer regex_t; /* Type for byte offsets within the string. POSIX mandates this. */ typedef int regoff_t; /* This is the structure we store register match data in. See regex.texinfo for a full description of what registers match. */ struct re_registers { unsigned num_regs; regoff_t *start; regoff_t *end; }; /* If `regs_allocated' is REGS_UNALLOCATED in the pattern buffer, `re_match_2' returns information about at least this many registers the first time a `regs' structure is passed. */ #ifndef RE_NREGS # define RE_NREGS 30 #endif /* POSIX specification for registers. Aside from the different names than `re_registers', POSIX uses an array of structures, instead of a structure of arrays. */ typedef struct { regoff_t rm_so; /* Byte offset from string's start to substring's start. */ regoff_t rm_eo; /* Byte offset from string's start to substring's end. */ } regmatch_t; /* Declarations for routines. */ /* To avoid duplicating every routine declaration -- once with a prototype (if we are ANSI), and once without (if we aren't) -- we use the following macro to declare argument types. This unfortunately clutters up the declarations a bit, but I think it's worth it. */ #if __STDC__ # define _RE_ARGS(args) args #else /* not __STDC__ */ # define _RE_ARGS(args) () #endif /* not __STDC__ */ /* Sets the current default syntax to SYNTAX, and return the old syntax. You can also simply assign to the `re_syntax_options' variable. */ extern reg_syntax_t re_set_syntax _RE_ARGS ((reg_syntax_t syntax)); /* Compile the regular expression PATTERN, with length LENGTH and syntax given by the global `re_syntax_options', into the buffer BUFFER. Return NULL if successful, and an error string if not. */ extern const char *re_compile_pattern _RE_ARGS ((const char *pattern, size_t length, struct re_pattern_buffer *buffer)); /* Compile a fastmap for the compiled pattern in BUFFER; used to accelerate searches. Return 0 if successful and -2 if was an internal error. */ extern int re_compile_fastmap _RE_ARGS ((struct re_pattern_buffer *buffer)); /* Search in the string STRING (with length LENGTH) for the pattern compiled into BUFFER. Start searching at position START, for RANGE characters. Return the starting position of the match, -1 for no match, or -2 for an internal error. Also return register information in REGS (if REGS and BUFFER->no_sub are nonzero). */ extern int re_search _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string, int length, int start, int range, struct re_registers *regs)); /* Like `re_search', but search in the concatenation of STRING1 and STRING2. Also, stop searching at index START + STOP. */ extern int re_search_2 _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1, int length1, const char *string2, int length2, int start, int range, struct re_registers *regs, int stop)); /* Like `re_search', but return how many characters in STRING the regexp in BUFFER matched, starting at position START. */ extern int re_match _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string, int length, int start, struct re_registers *regs)); /* Relates to `re_match' as `re_search_2' relates to `re_search'. */ extern int re_match_2 _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1, int length1, const char *string2, int length2, int start, struct re_registers *regs, int stop)); /* Set REGS to hold NUM_REGS registers, storing them in STARTS and ENDS. Subsequent matches using BUFFER and REGS will use this memory for recording register information. STARTS and ENDS must be allocated with malloc, and must each be at least `NUM_REGS * sizeof (regoff_t)' bytes long. If NUM_REGS == 0, then subsequent matches should allocate their own register data. Unless this function is called, the first search or match using PATTERN_BUFFER will allocate its own register data, without freeing the old data. */ extern void re_set_registers _RE_ARGS ((struct re_pattern_buffer *buffer, struct re_registers *regs, unsigned num_regs, regoff_t *starts, regoff_t *ends)); #if defined _REGEX_RE_COMP || defined _LIBC # ifndef _CRAY /* 4.2 bsd compatibility. */ extern char *re_comp _RE_ARGS ((const char *)); extern int re_exec _RE_ARGS ((const char *)); # endif #endif /* GCC 2.95 and later have "__restrict"; C99 compilers have "restrict", and "configure" may have defined "restrict". */ #ifndef __restrict # if ! (2 < __GNUC__ || (2 == __GNUC__ && 95 <= __GNUC_MINOR__)) # if defined restrict || 199901L <= __STDC_VERSION__ # define __restrict restrict # else # define __restrict # endif # endif #endif /* gcc 3.1 and up support the [restrict] syntax. */ #ifndef __restrict_arr # if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1) # define __restrict_arr __restrict # else # define __restrict_arr # endif #endif /* POSIX compatibility. */ extern int regcomp _RE_ARGS ((regex_t *__restrict __preg, const char *__restrict __pattern, int __cflags)); extern int regexec _RE_ARGS ((const regex_t *__restrict __preg, const char *__restrict __string, size_t __nmatch, regmatch_t __pmatch[__restrict_arr], int __eflags)); extern size_t regerror _RE_ARGS ((int __errcode, const regex_t *__preg, char *__errbuf, size_t __errbuf_size)); extern void regfree _RE_ARGS ((regex_t *__preg)); #ifdef __cplusplus } #endif /* C++ */ #endif /* regex.h */ /* Local variables: make-backup-files: t version-control: t trim-versions-without-asking: nil End: */
/* A more useful interface to strtol. Copyright 1995, 1996, 1998, 1999, 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef XSTRTOL_H_ # define XSTRTOL_H_ 1 # if HAVE_INTTYPES_H # include <inttypes.h> /* for uintmax_t */ # endif # ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif # endif # ifndef _STRTOL_ERROR enum strtol_error { LONGINT_OK, LONGINT_INVALID, LONGINT_INVALID_SUFFIX_CHAR, LONGINT_OVERFLOW }; typedef enum strtol_error strtol_error; # endif # define _DECLARE_XSTRTOL(name, type) \ strtol_error \ name PARAMS ((const char *s, char **ptr, int base, \ type *val, const char *valid_suffixes)); _DECLARE_XSTRTOL (xstrtol, long int) _DECLARE_XSTRTOL (xstrtoul, unsigned long int) _DECLARE_XSTRTOL (xstrtoimax, intmax_t) _DECLARE_XSTRTOL (xstrtoumax, uintmax_t) # define _STRTOL_ERROR(Exit_code, Str, Argument_type_string, Err) \ do \ { \ switch ((Err)) \ { \ case LONGINT_OK: \ abort (); \ \ case LONGINT_INVALID: \ error ((Exit_code), 0, "invalid %s `%s'", \ (Argument_type_string), (Str)); \ break; \ \ case LONGINT_INVALID_SUFFIX_CHAR: \ error ((Exit_code), 0, "invalid character following %s in `%s'", \ (Argument_type_string), (Str)); \ break; \ \ case LONGINT_OVERFLOW: \ error ((Exit_code), 0, "%s `%s' too large", \ (Argument_type_string), (Str)); \ break; \ } \ } \ while (0) # define STRTOL_FATAL_ERROR(Str, Argument_type_string, Err) \ _STRTOL_ERROR (2, Str, Argument_type_string, Err) # define STRTOL_FAIL_WARN(Str, Argument_type_string, Err) \ _STRTOL_ERROR (0, Str, Argument_type_string, Err) #endif /* not XSTRTOL_H_ */
/* cut - remove parts of lines of files Copyright (C) 1984, 1997, 1998, 1999, 2000, 2001, 2002, 2003 by David M. Ihnat This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David Ihnat. */ /* POSIX changes, bug fixes, long-named options, and cleanup by David MacKenzie <djm@gnu.ai.mit.edu>. Rewrite cut_fields and cut_bytes -- Jim Meyering. */ #include <config.h> #include <stdio.h> #include <assert.h> #include <getopt.h> #include <sys/types.h> #include "system.h" #include "getstr.h" #include "closeout.h" #include "error.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "cut" #define AUTHORS N_ ("David Ihnat, David MacKenzie, and Jim Meyering") #define FATAL_ERROR(Message) \ do \ { \ error (0, 0, (Message)); \ usage (2); \ } \ while (0) /* Append LOW, HIGH to the list RP of range pairs, allocating additional space if necessary. Update local variable N_RP. When allocating, update global variable N_RP_ALLOCATED. */ #define ADD_RANGE_PAIR(rp, low, high) \ do \ { \ if (n_rp >= n_rp_allocated) \ { \ n_rp_allocated *= 2; \ (rp) = (struct range_pair *) xrealloc ((char *) (rp), \ n_rp_allocated * sizeof (*(rp))); \ } \ rp[n_rp].lo = (low); \ rp[n_rp].hi = (high); \ ++n_rp; \ } \ while (0) struct range_pair { unsigned int lo; unsigned int hi; }; /* This buffer is used to support the semantics of the -s option (or lack of same) when the specified field list includes (does not include) the first field. In both of those cases, the entire first field must be read into this buffer to determine whether it is followed by a delimiter or a newline before any of it may be output. Otherwise, cut_fields can do the job without using this buffer. */ static char *field_1_buffer; /* The number of bytes allocated for FIELD_1_BUFFER. */ static size_t field_1_bufsize; /* The largest field or byte index used as an endpoint of a closed or degenerate range specification; this doesn't include the starting index of right-open-ended ranges. For example, with either range spec `2-5,9-', `2-3,5,9-' this variable would be set to 5. */ static unsigned int max_range_endpoint; /* If nonzero, this is the index of the first field in a range that goes to end of line. */ static unsigned int eol_range_start; /* A nonzero, non-1 value with which to distinguish the index corresponding to the lower bound of a range. */ #define RANGE_START_SENTINEL 2 /* In byte mode, which bytes to output. In field mode, which DELIM-separated fields to output. Both bytes and fields are numbered starting with 1, so the zeroth element of this array is unused. A field or byte K has been selected if (K <= MAX_RANGE_ENDPOINT and PRINTABLE_FIELD[K]) || (EOL_RANGE_START > 0 && K >= EOL_RANGE_START). */ static int *printable_field; enum operating_mode { undefined_mode, /* Output characters that are in the given bytes. */ byte_mode, /* Output the given delimeter-separated fields. */ field_mode }; /* The name this program was run with. */ char *program_name; static enum operating_mode operating_mode; /* If nonzero do not output lines containing no delimeter characters. Otherwise, all such lines are printed. This option is valid only with field mode. */ static int suppress_non_delimited; /* The delimeter character for field mode. */ static int delim; /* Nonzero if the --output-delimiter=STRING option was specified. */ static int output_delimiter_specified; /* The length of output_delimiter_string. */ static size_t output_delimiter_length; /* The output field separator string. Defaults to the 1-character string consisting of the input delimiter. */ static char *output_delimiter_string; /* Nonzero if we have ever read standard input. */ static int have_read_stdin; /* For long options that have no equivalent short option, use a non-character as a pseudo short option, starting with CHAR_MAX + 1. */ enum { OUTPUT_DELIMITER_OPTION = CHAR_MAX + 1 }; static struct option const longopts[] = { {"bytes", required_argument, 0, 'b'}, {"characters", required_argument, 0, 'c'}, {"fields", required_argument, 0, 'f'}, {"delimiter", required_argument, 0, 'd'}, {"only-delimited", no_argument, 0, 's'}, {"output-delimiter", required_argument, 0, OUTPUT_DELIMITER_OPTION}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {0, 0, 0, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... [FILE]...\n\ "), program_name); fputs (_("\ Print selected parts of lines from each FILE to standard output.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ -b, --bytes=LIST output only these bytes\n\ -c, --characters=LIST output only these characters\n\ -d, --delimiter=DELIM use DELIM instead of TAB for field delimiter\n\ "), stdout); fputs (_("\ -f, --fields=LIST output only these fields; also print any line\n\ that contains no delimiter character, unless\n\ the -s option is specified\n\ -n (ignored)\n\ "), stdout); fputs (_("\ -s, --only-delimited do not print lines not containing delimiters\n\ --output-delimiter=STRING use STRING as the output delimiter\n\ the default is to use the input delimiter\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ Use one, and only one of -b, -c or -f. Each LIST is made up of one\n\ range, or many ranges separated by commas. Each range is one of:\n\ \n\ N N'th byte, character or field, counted from 1\n\ N- from N'th byte, character or field, to end of line\n\ N-M from N'th to M'th (included) byte, character or field\n\ -M from first to M'th (included) byte, character or field\n\ \n\ With no FILE, or when FILE is -, read standard input.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } /* Return nonzero if the K'th field or byte is printable. When returning nonzero, if RANGE_START is non-NULL, set *RANGE_START to nonzero if K is the beginning of a range, and set *RANGE_START to zero if K is not the beginning of a range. */ static int print_kth (unsigned int k, int *range_start) { if (0 < eol_range_start && eol_range_start <= k) { if (range_start) *range_start = (k == eol_range_start); return 1; } if (k <= max_range_endpoint && printable_field[k]) { if (range_start) *range_start = (printable_field[k] == RANGE_START_SENTINEL); return 1; } return 0; } /* Given the list of field or byte range specifications FIELDSTR, set MAX_RANGE_ENDPOINT and allocate and initialize the PRINTABLE_FIELD array. If there is a right-open-ended range, set EOL_RANGE_START to its starting index. FIELDSTR should be composed of one or more numbers or ranges of numbers, separated by blanks or commas. Incomplete ranges may be given: `-m' means `1-m'; `n-' means `n' through end of line. Return nonzero if FIELDSTR contains at least one field specification, zero otherwise. */ /* FIXME-someday: What if the user wants to cut out the 1,000,000-th field of some huge input file? This function shouldn't have to allocate a table of a million ints just so we can test every field < 10^6 with an array dereference. Instead, consider using a dynamic hash table. It would be simpler and nearly as good a solution to use a 32K x 4-byte table with one bit per field index instead of a whole `int' per index. */ static int set_fields (const char *fieldstr) { unsigned int initial = 1; /* Value of first number in a range. */ unsigned int value = 0; /* If nonzero, a number being accumulated. */ int dash_found = 0; /* Nonzero if a '-' is found in this field. */ int field_found = 0; /* Non-zero if at least one field spec has been processed. */ struct range_pair *rp; unsigned int n_rp; unsigned int n_rp_allocated; unsigned int i; n_rp = 0; n_rp_allocated = 16; rp = (struct range_pair *) xmalloc (n_rp_allocated * sizeof (*rp)); /* Collect and store in RP the range end points. It also sets EOL_RANGE_START if appropriate. */ for (;;) { if (*fieldstr == '-') { /* Starting a range. */ if (dash_found) FATAL_ERROR (_("invalid byte or field list")); dash_found++; fieldstr++; if (value) { initial = value; value = 0; } else initial = 1; } else if (*fieldstr == ',' || ISBLANK (*fieldstr) || *fieldstr == '\0') { /* Ending the string, or this field/byte sublist. */ if (dash_found) { dash_found = 0; /* A range. Possibilites: -n, m-n, n-. In any case, `initial' contains the start of the range. */ if (value == 0) { /* `n-'. From `initial' to end of line. */ eol_range_start = initial; field_found = 1; } else { /* `m-n' or `-n' (1-n). */ if (value < initial) FATAL_ERROR (_("invalid byte or field list")); /* Is there already a range going to end of line? */ if (eol_range_start != 0) { /* Yes. Is the new sequence already contained in the old one? If so, no processing is necessary. */ if (initial < eol_range_start) { /* No, the new sequence starts before the old. Does the old range going to end of line extend into the new range? */ if (eol_range_start <= value) { /* Yes. Simply move the end of line marker. */ eol_range_start = initial; } else { /* No. A simple range, before and disjoint from the range going to end of line. Fill it. */ ADD_RANGE_PAIR (rp, initial, value); } /* In any case, some fields were selected. */ field_found = 1; } } else { /* There is no range going to end of line. */ ADD_RANGE_PAIR (rp, initial, value); field_found = 1; } value = 0; } } else if (value != 0) { /* A simple field number, not a range. */ ADD_RANGE_PAIR (rp, value, value); value = 0; field_found = 1; } if (*fieldstr == '\0') { break; } fieldstr++; } else if (ISDIGIT (*fieldstr)) { /* FIXME: detect overflow? */ value = 10 * value + *fieldstr - '0'; fieldstr++; } else FATAL_ERROR (_("invalid byte or field list")); } max_range_endpoint = 0; for (i = 0; i < n_rp; i++) { if (rp[i].hi > max_range_endpoint) max_range_endpoint = rp[i].hi; } /* Allocate an array large enough so that it may be indexed by the field numbers corresponding to all finite ranges (i.e. `2-6' or `-4', but not `5-') in FIELDSTR. */ printable_field = (int *) xmalloc ((max_range_endpoint + 1) * sizeof (int)); memset (printable_field, 0, (max_range_endpoint + 1) * sizeof (int)); /* Set the array entries corresponding to integers in the ranges of RP. */ for (i = 0; i < n_rp; i++) { unsigned int j = rp[i].lo; /* Mark the first position of field or range with a sentinel, but not if it's already part of another range. */ if (j <= rp[i].hi && ! printable_field[j]) printable_field[j] = RANGE_START_SENTINEL; for (++j; j <= rp[i].hi; j++) { printable_field[j] = 1; } } free (rp); return field_found; } /* Read from stream STREAM, printing to standard output any selected bytes. */ static void cut_bytes (FILE *stream) { unsigned int byte_idx; /* Number of bytes in the line so far. */ /* Whether to begin printing delimiters between ranges for the current line. Set after we've begun printing data corresponding to the first range. */ int print_delimiter; byte_idx = 0; print_delimiter = 0; while (1) { register int c; /* Each character from the file. */ c = getc (stream); if (c == '\n') { putchar ('\n'); byte_idx = 0; print_delimiter = 0; } else if (c == EOF) { if (byte_idx > 0) putchar ('\n'); break; } else { int range_start; if (print_kth (++byte_idx, &range_start)) { if (range_start && print_delimiter && output_delimiter_specified) { fwrite (output_delimiter_string, sizeof (char), output_delimiter_length, stdout); } print_delimiter = 1; putchar (c); } } } } /* Read from stream STREAM, printing to standard output any selected fields. */ static void cut_fields (FILE *stream) { int c; unsigned int field_idx; int found_any_selected_field; int buffer_first_field; int empty_input; found_any_selected_field = 0; field_idx = 1; c = getc (stream); empty_input = (c == EOF); if (c != EOF) ungetc (c, stream); /* To support the semantics of the -s flag, we may have to buffer all of the first field to determine whether it is `delimited.' But that is unnecessary if all non-delimited lines must be printed and the first field has been selected, or if non-delimited lines must be suppressed and the first field has *not* been selected. That is because a non-delimited line has exactly one field. */ buffer_first_field = (suppress_non_delimited ^ !print_kth (1, NULL)); while (1) { if (field_idx == 1 && buffer_first_field) { int len; size_t n_bytes; len = getstr (&field_1_buffer, &field_1_bufsize, stream, delim, '\n', 0); if (len < 0) { if (ferror (stream) || feof (stream)) break; xalloc_die (); } n_bytes = len; assert (n_bytes != 0); /* If the first field extends to the end of line (it is not delimited) and we are printing all non-delimited lines, print this one. */ if ((unsigned char) field_1_buffer[n_bytes - 1] != delim) { if (suppress_non_delimited) { /* Empty. */ } else { fwrite (field_1_buffer, sizeof (char), n_bytes, stdout); /* Make sure the output line is newline terminated. */ if (field_1_buffer[n_bytes - 1] != '\n') putchar ('\n'); } continue; } if (print_kth (1, NULL)) { /* Print the field, but not the trailing delimiter. */ fwrite (field_1_buffer, sizeof (char), n_bytes - 1, stdout); found_any_selected_field = 1; } ++field_idx; } if (c != EOF) { if (print_kth (field_idx, NULL)) { if (found_any_selected_field) { fwrite (output_delimiter_string, sizeof (char), output_delimiter_length, stdout); } found_any_selected_field = 1; while ((c = getc (stream)) != delim && c != '\n' && c != EOF) { putchar (c); } } else { while ((c = getc (stream)) != delim && c != '\n' && c != EOF) { /* Empty. */ } } } if (c == '\n') { c = getc (stream); if (c != EOF) { ungetc (c, stream); c = '\n'; } } if (c == delim) ++field_idx; else if (c == '\n' || c == EOF) { if (found_any_selected_field || (!empty_input && !(suppress_non_delimited && field_idx == 1))) putchar ('\n'); if (c == EOF) break; field_idx = 1; found_any_selected_field = 0; } } } static void cut_stream (FILE *stream) { if (operating_mode == byte_mode) cut_bytes (stream); else cut_fields (stream); } /* Process file FILE to standard output. Return 0 if successful, 1 if not. */ static int cut_file (char *file) { FILE *stream; if (STREQ (file, "-")) { have_read_stdin = 1; stream = stdin; } else { stream = fopen (file, "r"); if (stream == NULL) { error (0, errno, "%s", file); return 1; } } cut_stream (stream); if (ferror (stream)) { error (0, errno, "%s", file); return 1; } if (STREQ (file, "-")) clearerr (stream); /* Also clear EOF. */ else if (fclose (stream) == EOF) { error (0, errno, "%s", file); return 1; } return 0; } int main (int argc, char **argv) { int optc, exit_status = 0; int delim_specified = 0; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); operating_mode = undefined_mode; /* By default, all non-delimited lines are printed. */ suppress_non_delimited = 0; delim = '\0'; have_read_stdin = 0; while ((optc = getopt_long (argc, argv, "b:c:d:f:ns", longopts, NULL)) != -1) { switch (optc) { case 0: break; case 'b': case 'c': /* Build the byte list. */ if (operating_mode != undefined_mode) FATAL_ERROR (_("only one type of list may be specified")); operating_mode = byte_mode; if (set_fields (optarg) == 0) FATAL_ERROR (_("missing list of positions")); break; case 'f': /* Build the field list. */ if (operating_mode != undefined_mode) FATAL_ERROR (_("only one type of list may be specified")); operating_mode = field_mode; if (set_fields (optarg) == 0) FATAL_ERROR (_("missing list of fields")); break; case 'd': /* New delimiter. */ /* Interpret -d '' to mean `use the NUL byte as the delimiter.' */ if (optarg[0] != '\0' && optarg[1] != '\0') FATAL_ERROR (_("the delimiter must be a single character")); delim = (unsigned char) optarg[0]; delim_specified = 1; break; case OUTPUT_DELIMITER_OPTION: output_delimiter_specified = 1; /* Interpret --output-delimiter='' to mean `use the NUL byte as the delimiter.' */ output_delimiter_length = (optarg[0] == '\0' ? 1 : strlen (optarg)); output_delimiter_string = xstrdup (optarg); break; case 'n': break; case 's': suppress_non_delimited = 1; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (2); } } if (operating_mode == undefined_mode) FATAL_ERROR (_("you must specify a list of bytes, characters, or fields")); if (delim != '\0' && operating_mode != field_mode) FATAL_ERROR (_("an input delimiter may be specified only\ when operating on fields")); if (suppress_non_delimited && operating_mode != field_mode) FATAL_ERROR (_("suppressing non-delimited lines makes sense\n\ \tonly when operating on fields")); if (!delim_specified) delim = '\t'; if (output_delimiter_string == NULL) { static char dummy[2]; dummy[0] = delim; dummy[1] = '\0'; output_delimiter_string = dummy; output_delimiter_length = 1; } if (optind == argc) exit_status |= cut_file ("-"); else for (; optind < argc; optind++) exit_status |= cut_file (argv[optind]); if (have_read_stdin && fclose (stdin) == EOF) { error (0, errno, "-"); exit_status = 1; } exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); }
#ifndef GETSTR_H_ # define GETSTR_H_ 1 # include <stdio.h> # ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif # endif int getstr PARAMS ((char **lineptr, size_t *n, FILE *stream, int delim1, int delim2, size_t offset)); #endif
/* dirname -- strip filename suffix from pathname Copyright (C) 1990-1997, 1999-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie and Jim Meyering. */ #include <config.h> #include <stdio.h> #include <sys/types.h> #include "system.h" #include "long-options.h" #include "error.h" #include "dirname.h" #include "closeout.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "dirname" #define AUTHORS N_ ("David MacKenzie and Jim Meyering") /* The name this program was run with. */ char *program_name; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s NAME\n\ or: %s OPTION\n\ "), program_name, program_name); fputs (_("\ Print NAME with its trailing /component removed; if NAME contains no /'s,\n\ output `.' (meaning the current directory).\n\ \n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } int main (int argc, char **argv) { static char const dot = '.'; char const *result; size_t len; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, AUTHORS, usage); /* The above handles --help and --version. Since there is no other invocation of getopt, handle `--' here. */ if (argc > 1 && STREQ (argv[1], "--")) { --argc; ++argv; } if (argc != 2) { error (0, 0, argc < 2 ? _("too few arguments") : _("too many arguments")); usage (EXIT_FAILURE); } result = argv[1]; len = dir_len (result); if (! len) { result = ˙ len = 1; } fwrite (result, 1, len, stdout); putchar ('\n'); exit (EXIT_SUCCESS); }
/* echo.c, derived from code echo.c in Bash. Copyright (C) 87,89, 1991-1997, 1999-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <config.h> #include <stdio.h> #include <sys/types.h> #include "system.h" #include "closeout.h" #include "long-options.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "echo" #define AUTHORS "FIXME unknown" /* echo [-neE] [arg ...] Output the ARGs. If -n is specified, the trailing newline is suppressed. If the -e option is given, interpretation of the following backslash-escaped characters is turned on: \a alert (bell) \b backspace \c suppress trailing newline \f form feed \n new line \r carriage return \t horizontal tab \v vertical tab \\ backslash \num the character whose ASCII code is NUM (octal). You can explicitly turn off the interpretation of the above characters on System V systems with the -E option. */ /* If defined, interpret backslash escapes if -e is given. */ #define V9_ECHO /* If defined, interpret backslash escapes unless -E is given. V9_ECHO must also be defined. */ /* #define V9_DEFAULT */ #if defined (V9_ECHO) # if defined (V9_DEFAULT) # define VALID_ECHO_OPTIONS "neE" # else # define VALID_ECHO_OPTIONS "ne" # endif /* !V9_DEFAULT */ #else /* !V9_ECHO */ # define VALID_ECHO_OPTIONS "n" #endif /* !V9_ECHO */ /* The name this program was run with. */ char *program_name; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("Usage: %s [OPTION]... [STRING]...\n"), program_name); fputs (_("\ Echo the STRING(s) to standard output.\n\ \n\ -n do not output the trailing newline\n\ -e enable interpretation of the backslash-escaped characters\n\ listed below\n\ -E disable interpretation of those sequences in STRINGs\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ Without -E, the following sequences are recognized and interpolated:\n\ \n\ \\NNN the character whose ASCII code is NNN (octal)\n\ \\\\ backslash\n\ \\a alert (BEL)\n\ \\b backspace\n\ "), stdout); fputs (_("\ \\c suppress trailing newline\n\ \\f form feed\n\ \\n new line\n\ \\r carriage return\n\ \\t horizontal tab\n\ \\v vertical tab\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } /* Print the words in LIST to standard output. If the first word is `-n', then don't print a trailing newline. We also support the echo syntax from Version 9 unix systems. */ int main (int argc, char **argv) { int display_return = 1, do_v9 = 0; int allow_options = 1; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); /* Don't recognize --help or --version if POSIXLY_CORRECT is set. */ if (getenv ("POSIXLY_CORRECT") == NULL) parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, AUTHORS, usage); else allow_options = 0; /* System V machines already have a /bin/sh with a v9 behaviour. We use the identical behaviour for these machines so that the existing system shell scripts won't barf. */ #if defined (V9_ECHO) && defined (V9_DEFAULT) do_v9 = allow_options; #endif --argc; ++argv; while (argc > 0 && *argv[0] == '-') { register char *temp; register int i; /* If it appears that we are handling options, then make sure that all of the options specified are actually valid. Otherwise, the string should just be echoed. */ temp = argv[0] + 1; for (i = 0; temp[i]; i++) { if (strrchr (VALID_ECHO_OPTIONS, temp[i]) == 0) goto just_echo; } if (!*temp) goto just_echo; /* All of the options in TEMP are valid options to ECHO. Handle them. */ while (*temp) { if (allow_options && *temp == 'n') display_return = 0; #if defined (V9_ECHO) else if (allow_options && *temp == 'e') do_v9 = 1; # if defined (V9_DEFAULT) else if (allow_options && *temp == 'E') do_v9 = 0; # endif /* V9_DEFAULT */ #endif /* V9_ECHO */ else goto just_echo; temp++; } argc--; argv++; } just_echo: if (argc > 0) { #if defined (V9_ECHO) if (do_v9) { while (argc > 0) { register char *s = argv[0]; register int c; while ((c = *s++)) { if (c == '\\' && *s) { switch (c = *s++) { case 'a': c = '\007'; break; case 'b': c = '\b'; break; case 'c': display_return = 0; continue; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = (int) 0x0B; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': c -= '0'; if (*s >= '0' && *s <= '7') c = c * 8 + (*s++ - '0'); if (*s >= '0' && *s <= '7') c = c * 8 + (*s++ - '0'); break; case '\\': break; default: putchar ('\\'); break; } } putchar(c); } argc--; argv++; if (argc > 0) putchar(' '); } } else #endif /* V9_ECHO */ { while (argc > 0) { fputs (argv[0], stdout); argc--; argv++; if (argc > 0) putchar (' '); } } } if (display_return) putchar ('\n'); exit (EXIT_SUCCESS); }
/* expand - convert tabs to spaces Copyright (C) 89, 91, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* By default, convert all tabs to spaces. Preserves backspace characters in the output; they decrement the column count for tab calculations. The default action is equivalent to -8. Options: --tabs=tab1[,tab2[,...]] -t tab1[,tab2[,...]] -tab1[,tab2[,...]] If only one tab stop is given, set the tabs tab1 spaces apart instead of the default 8. Otherwise, set the tabs at columns tab1, tab2, etc. (numbered from 0); replace any tabs beyond the tabstops given with single spaces. --initial -i Only convert initial tabs on each line to spaces. David MacKenzie <djm@gnu.ai.mit.edu> */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include "system.h" #include "closeout.h" #include "error.h" #include "posixver.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "expand" #define AUTHORS "David MacKenzie" /* The number of bytes added at a time to the amount of memory allocated for the output line. */ #define OUTPUT_BLOCK 256 /* The number of bytes added at a time to the amount of memory allocated for the list of tabstops. */ #define TABLIST_BLOCK 256 /* The name this program was run with. */ char *program_name; /* If nonzero, convert blanks even after nonblank characters have been read on the line. */ static int convert_entire_line; /* If nonzero, the size of all tab stops. If zero, use `tab_list' instead. */ static int tab_size; /* Array of the explicit column numbers of the tab stops; after `tab_list' is exhausted, each additional tab is replaced by a space. The first column is column 0. */ static int *tab_list; /* The index of the first invalid element of `tab_list', where the next element can be added. */ static int first_free_tab; /* Null-terminated array of input filenames. */ static char **file_list; /* Default for `file_list' if no files are given on the command line. */ static char *stdin_argv[] = { "-", NULL }; /* Nonzero if we have ever read standard input. */ static int have_read_stdin; /* Status to return to the system. */ static int exit_status; static struct option const longopts[] = { {"tabs", required_argument, NULL, 't'}, {"initial", no_argument, NULL, 'i'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... [FILE]...\n\ "), program_name); fputs (_("\ Convert tabs in each FILE to spaces, writing to standard output.\n\ With no FILE, or when FILE is -, read standard input.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ -i, --initial do not convert TABs after non whitespace\n\ -t, --tabs=NUMBER have tabs NUMBER characters apart, not 8\n\ "), stdout); fputs (_("\ -t, --tabs=LIST use comma separated list of explicit tab positions\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } /* Add tab stop TABVAL to the end of `tab_list', except if TABVAL is -1, do nothing. */ static void add_tabstop (int tabval) { if (tabval == -1) return; if (first_free_tab % TABLIST_BLOCK == 0) tab_list = (int *) xrealloc ((char *) tab_list, (first_free_tab + TABLIST_BLOCK * sizeof (tab_list[0]))); tab_list[first_free_tab++] = tabval; } /* Add the comma or blank separated list of tabstops STOPS to the list of tabstops. */ static void parse_tabstops (char *stops) { int tabval = -1; for (; *stops; stops++) { if (*stops == ',' || ISBLANK (*stops)) { add_tabstop (tabval); tabval = -1; } else if (ISDIGIT (*stops)) { if (tabval == -1) tabval = 0; tabval = tabval * 10 + *stops - '0'; } else error (EXIT_FAILURE, 0, _("tab size contains an invalid character")); } add_tabstop (tabval); } /* Check that the list of tabstops TABS, with ENTRIES entries, contains only nonzero, ascending values. */ static void validate_tabstops (int *tabs, int entries) { int prev_tab = 0; int i; for (i = 0; i < entries; i++) { if (tabs[i] == 0) error (EXIT_FAILURE, 0, _("tab size cannot be 0")); if (tabs[i] <= prev_tab) error (EXIT_FAILURE, 0, _("tab sizes must be ascending")); prev_tab = tabs[i]; } } /* Close the old stream pointer FP if it is non-NULL, and return a new one opened to read the next input file. Open a filename of `-' as the standard input. Return NULL if there are no more input files. */ static FILE * next_file (FILE *fp) { static char *prev_file; char *file; if (fp) { if (ferror (fp)) { error (0, errno, "%s", prev_file); exit_status = 1; } if (fp == stdin) clearerr (fp); /* Also clear EOF. */ else if (fclose (fp) == EOF) { error (0, errno, "%s", prev_file); exit_status = 1; } } while ((file = *file_list++) != NULL) { if (file[0] == '-' && file[1] == '\0') { have_read_stdin = 1; prev_file = file; return stdin; } fp = fopen (file, "r"); if (fp) { prev_file = file; return fp; } error (0, errno, "%s", file); exit_status = 1; } return NULL; } /* Change tabs to spaces, writing to stdout. Read each file in `file_list', in order. */ static void expand (void) { FILE *fp; /* Input stream. */ int c; /* Each input character. */ int tab_index = 0; /* Index in `tab_list' of next tabstop. */ int column = 0; /* Column on screen of the next char. */ int next_tab_column; /* Column the next tab stop is on. */ int convert = 1; /* If nonzero, perform translations. */ fp = next_file ((FILE *) NULL); if (fp == NULL) return; /* Binary I/O will preserve the original EOL style (DOS/Unix) of files. */ SET_BINARY2 (fileno (fp), STDOUT_FILENO); for (;;) { c = getc (fp); if (c == EOF) { fp = next_file (fp); if (fp == NULL) break; /* No more files. */ else { SET_BINARY2 (fileno (fp), STDOUT_FILENO); continue; } } if (c == '\n') { putchar (c); tab_index = 0; column = 0; convert = 1; } else if (c == '\t' && convert) { if (tab_size == 0) { /* Do not let tab_index == first_free_tab; stop when it is 1 less. */ while (tab_index < first_free_tab - 1 && column >= tab_list[tab_index]) tab_index++; next_tab_column = tab_list[tab_index]; if (tab_index < first_free_tab - 1) tab_index++; if (column >= next_tab_column) next_tab_column = column + 1; /* Ran out of tab stops. */ } else { next_tab_column = column + tab_size - column % tab_size; } while (column < next_tab_column) { putchar (' '); ++column; } } else { if (convert) { if (c == '\b') { if (column > 0) --column; } else { ++column; if (convert_entire_line == 0) convert = 0; } } putchar (c); } } } int main (int argc, char **argv) { int tabval = -1; /* Value of tabstop being read, or -1. */ int c; /* Option character. */ bool obsolete_tablist = false; have_read_stdin = 0; exit_status = 0; convert_entire_line = 1; tab_list = NULL; first_free_tab = 0; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); while ((c = getopt_long (argc, argv, "it:,0123456789", longopts, NULL)) != -1) { switch (c) { case 0: break; case '?': usage (EXIT_FAILURE); case 'i': convert_entire_line = 0; break; case 't': parse_tabstops (optarg); break; case ',': add_tabstop (tabval); tabval = -1; obsolete_tablist = true; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: if (tabval == -1) tabval = 0; tabval = tabval * 10 + c - '0'; obsolete_tablist = true; break; } } if (obsolete_tablist && 200112 <= posix2_version ()) { error (0, 0, _("`-LIST' option is obsolete; use `-t LIST'")); usage (EXIT_FAILURE); } add_tabstop (tabval); validate_tabstops (tab_list, first_free_tab); if (first_free_tab == 0) tab_size = 8; else if (first_free_tab == 1) tab_size = tab_list[0]; else tab_size = 0; if (optind == argc) file_list = stdin_argv; else file_list = &argv[optind]; expand (); if (have_read_stdin && fclose (stdin) == EOF) error (EXIT_FAILURE, errno, "-"); exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); }
int posix2_version (void);
/* factor -- print prime factors of n. Copyright (C) 86, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Rubin <phr@ocf.berkeley.edu>. Adapted for GNU, fixed to factor UINT_MAX by Jim Meyering. */ #include <config.h> #include <stdio.h> #include <sys/types.h> #include <assert.h> #define NDEBUG 1 #include "system.h" #include "closeout.h" #include "error.h" #include "inttostr.h" #include "long-options.h" #include "readtokens.h" #include "xstrtol.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "factor" #define AUTHORS "Paul Rubin" /* Token delimiters when reading from a file. */ #define DELIM "\n\t " /* FIXME: if this program is ever modified to factor integers larger than 2^128, this constant (and the algorithm :-) will have to change. */ #define MAX_N_FACTORS 128 /* The trial divisor increment wheel. Use it to skip over divisors that are composites of 2, 3, 5, 7, or 11. The part from WHEEL_START up to WHEEL_END is reused periodically, while the "lead in" is used to test for those primes and to jump onto the wheel. For more information, see http://www.utm.edu/research/primes/glossary/WheelFactorization.html */ #include "wheel-size.h" /* For the definition of WHEEL_SIZE. */ static const unsigned int wheel_tab[] = { #include "wheel.h" }; #define WHEEL_START (wheel_tab + WHEEL_SIZE) #define WHEEL_END (wheel_tab + (sizeof wheel_tab / sizeof wheel_tab[0])) /* The name this program was run with. */ char *program_name; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [NUMBER]...\n\ or: %s OPTION\n\ "), program_name, program_name); fputs (_("\ Print the prime factors of each NUMBER.\n\ \n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ Print the prime factors of all specified integer NUMBERs. If no arguments\n\ are specified on the command line, they are read from standard input.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } /* FIXME: comment */ static int factor (uintmax_t n0, int max_n_factors, uintmax_t *factors) { register uintmax_t n = n0, d, q; int n_factors = 0; unsigned int const *w = wheel_tab; if (n < 1) return n_factors; /* The exit condition in the following loop is correct because any time it is tested one of these 3 conditions holds: (1) d divides n (2) n is prime (3) n is composite but has no factors less than d. If (1) or (2) obviously the right thing happens. If (3), then since n is composite it is >= d^2. */ d = 2; do { q = n / d; while (n == q * d) { assert (n_factors < max_n_factors); factors[n_factors++] = d; n = q; q = n / d; } d += *(w++); if (w == WHEEL_END) w = WHEEL_START; } while (d <= q); if (n != 1 || n0 == 1) { assert (n_factors < max_n_factors); factors[n_factors++] = n; } return n_factors; } /* FIXME: comment */ static int print_factors (const char *s) { uintmax_t factors[MAX_N_FACTORS]; uintmax_t n; int n_factors; int i; char buf[INT_BUFSIZE_BOUND (uintmax_t)]; if (xstrtoumax (s, NULL, 10, &n, "") != LONGINT_OK) { error (0, 0, _("`%s' is not a valid positive integer"), s); return 1; } n_factors = factor (n, MAX_N_FACTORS, factors); printf ("%s:", umaxtostr (n, buf)); for (i = 0; i < n_factors; i++) printf (" %s", umaxtostr (factors[i], buf)); putchar ('\n'); return 0; } static int do_stdin (void) { int fail = 0; token_buffer tokenbuffer; init_tokenbuffer (&tokenbuffer); for (;;) { long int token_length; token_length = readtoken (stdin, DELIM, sizeof (DELIM) - 1, &tokenbuffer); if (token_length < 0) break; fail |= print_factors (tokenbuffer.buffer); } free (tokenbuffer.buffer); return fail; } int main (int argc, char **argv) { int fail; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, AUTHORS, usage); /* The above handles --help and --version. Since there is no other invocation of getopt, handle `--' here. */ if (argc > 1 && STREQ (argv[1], "--")) { --argc; ++argv; } fail = 0; if (argc == 1) fail = do_stdin (); else { int i; for (i = 1; i < argc; i++) fail |= print_factors (argv[i]); } if (fail) usage (EXIT_FAILURE); exit (fail); }
#ifndef H_READTOKENS_H # define H_READTOKENS_H # ifndef INITIAL_TOKEN_LENGTH # define INITIAL_TOKEN_LENGTH 20 # endif # ifndef TOKENBUFFER_DEFINED # define TOKENBUFFER_DEFINED struct tokenbuffer { long size; char *buffer; }; typedef struct tokenbuffer token_buffer; # endif /* not TOKENBUFFER_DEFINED */ # undef __P # if defined (__STDC__) && __STDC__ # define __P(x) x # else # define __P(x) () # endif void init_tokenbuffer __P ((token_buffer *tokenbuffer)); long readtoken __P ((FILE *stream, const char *delim, int n_delim, token_buffer *tokenbuffer)); int readtokens __P ((FILE *stream, int projected_n_tokens, const char *delim, int n_delim, char ***tokens_out, long **token_lengths)); #endif /* not H_READTOKENS_H */
#define WHEEL_SIZE 5
/* The first 4 elements correspond to the incremental offsets of the first 5 primes (2 3 5 7 11). The 5(th) element is the difference between that last prime and the next largest integer that is not a multiple of those primes. The remaining numbers define the wheel. For more information, see http://www.utm.edu/research/primes/glossary/WheelFactorization.html. */ 1, 2, 2, 4, 2, 4, 2, 4, 6, 2, 6, 4, 2, 4, 6, 6, 2, 6, 4, 2, 6, 4, 6, 8, 4, 2, 4, 2, 4, 14, 4, 6, 2, 10, 2, 6, 6, 4, 2, 4, 6, 2, 10, 2, 4, 2, 12, 10, 2, 4, 2, 4, 6, 2, 6, 4, 6, 6, 6, 2, 6, 4, 2, 6, 4, 6, 8, 4, 2, 4, 6, 8, 6, 10, 2, 4, 6, 2, 6, 6, 4, 2, 4, 6, 2, 6, 4, 2, 6, 10, 2, 10, 2, 4, 2, 4, 6, 8, 4, 2, 4, 12, 2, 6, 4, 2, 6, 4, 6, 12, 2, 4, 2, 4, 8, 6, 4, 6, 2, 4, 6, 2, 6, 10, 2, 4, 6, 2, 6, 4, 2, 4, 2, 10, 2, 10, 2, 4, 6, 6, 2, 6, 6, 4, 6, 6, 2, 6, 4, 2, 6, 4, 6, 8, 4, 2, 6, 4, 8, 6, 4, 6, 2, 4, 6, 8, 6, 4, 2, 10, 2, 6, 4, 2, 4, 2, 10, 2, 10, 2, 4, 2, 4, 8, 6, 4, 2, 4, 6, 6, 2, 6, 4, 8, 4, 6, 8, 4, 2, 4, 2, 4, 8, 6, 4, 6, 6, 6, 2, 6, 6, 4, 2, 4, 6, 2, 6, 4, 2, 4, 2, 10, 2, 10, 2, 6, 4, 6, 2, 6, 4, 2, 4, 6, 6, 8, 4, 2, 6, 10, 8, 4, 2, 4, 2, 4, 8, 10, 6, 2, 4, 8, 6, 6, 4, 2, 4, 6, 2, 6, 4, 6, 2, 10, 2, 10, 2, 4, 2, 4, 6, 2, 6, 4, 2, 4, 6, 6, 2, 6, 6, 6, 4, 6, 8, 4, 2, 4, 2, 4, 8, 6, 4, 8, 4, 6, 2, 6, 6, 4, 2, 4, 6, 8, 4, 2, 4, 2, 10, 2, 10, 2, 4, 2, 4, 6, 2, 10, 2, 4, 6, 8, 6, 4, 2, 6, 4, 6, 8, 4, 6, 2, 4, 8, 6, 4, 6, 2, 4, 6, 2, 6, 6, 4, 6, 6, 2, 6, 6, 4, 2, 10, 2, 10, 2, 4, 2, 4, 6, 2, 6, 4, 2, 10, 6, 2, 6, 4, 2, 6, 4, 6, 8, 4, 2, 4, 2, 12, 6, 4, 6, 2, 4, 6, 2, 12, 4, 2, 4, 8, 6, 4, 2, 4, 2, 10, 2, 10, 6, 2, 4, 6, 2, 6, 4, 2, 4, 6, 6, 2, 6, 4, 2, 10, 6, 8, 6, 4, 2, 4, 8, 6, 4, 6, 2, 4, 6, 2, 6, 6, 6, 4, 6, 2, 6, 4, 2, 4, 2, 10, 12, 2, 4, 2, 10, 2, 6, 4, 2, 4, 6, 6, 2, 10, 2, 6, 4, 14, 4, 2, 4, 2, 4, 8, 6, 4, 6, 2, 4, 6, 2, 6, 6, 4, 2, 4, 6, 2, 6, 4, 2, 4, 12, 2, 12
/* Exit with a status code indicating success. Copyright (C) 1999-2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <config.h> #include <stdio.h> #include <sys/types.h> #include "system.h" #include "version-etc.h" #include "closeout.h" #define PROGRAM_NAME "true" #define AUTHORS "Jim Meyering" /* The name this program was run with. */ char *program_name; void usage (int status) { printf (_("\ Usage: %s [ignored command line arguments]\n\ or: %s OPTION\n\ Exit with a status code indicating success.\n\ \n\ These option names may not be abbreviated.\n\ \n\ "), program_name, program_name); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); exit (status); } int main (int argc, char **argv) { program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); /* Recognize --help or --version only if it's the only command-line argument and if POSIXLY_CORRECT is not set. */ if (argc == 2 && getenv ("POSIXLY_CORRECT") == NULL) { if (STREQ (argv[1], "--help")) usage (EXIT_SUCCESS); if (STREQ (argv[1], "--version")) version_etc (stdout, PROGRAM_NAME, GNU_PACKAGE, VERSION, AUTHORS); } exit (EXIT_SUCCESS); }
/* Utility to help print --version output in a consistent format. Copyright (C) 1999 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Jim Meyering. */ #ifndef VERSION_ETC_H # define VERSION_ETC_H 1 # ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif # endif extern char *version_etc_copyright; void version_etc PARAMS ((FILE *stream, const char *command_name, const char *package, const char *version, const char *authors)); #endif /* VERSION_ETC_H */
/* GNU fmt -- simple text formatter. Copyright (C) 1994-2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Ross Paterson <rap@doc.ic.ac.uk>. */ #include <config.h> #include <stdio.h> #include <sys/types.h> #include <getopt.h> /* Redefine. Otherwise, systems (Unicos for one) with headers that define it to be a type get syntax errors for the variable declaration below. */ #define word unused_word_type #include "system.h" #include "closeout.h" #include "error.h" #include "xstrtol.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "fmt" #define AUTHORS "Ross Paterson" /* The following parameters represent the program's idea of what is "best". Adjust to taste, subject to the caveats given. */ /* Default longest permitted line length (max_width). */ #define WIDTH 75 /* Prefer lines to be LEEWAY % shorter than the maximum width, giving room for optimization. */ #define LEEWAY 7 /* The default secondary indent of tagged paragraph used for unindented one-line paragraphs not preceded by any multi-line paragraphs. */ #define DEF_INDENT 3 /* Costs and bonuses are expressed as the equivalent departure from the optimal line length, multiplied by 10. e.g. assigning something a cost of 50 means that it is as bad as a line 5 characters too short or too long. The definition of SHORT_COST(n) should not be changed. However, EQUIV(n) may need tuning. */ typedef long COST; #define MAXCOST TYPE_MAXIMUM (COST) #define SQR(n) ((n) * (n)) #define EQUIV(n) SQR ((COST) (n)) /* Cost of a filled line n chars longer or shorter than best_width. */ #define SHORT_COST(n) EQUIV ((n) * 10) /* Cost of the difference between adjacent filled lines. */ #define RAGGED_COST(n) (SHORT_COST (n) / 2) /* Basic cost per line. */ #define LINE_COST EQUIV (70) /* Cost of breaking a line after the first word of a sentence, where the length of the word is N. */ #define WIDOW_COST(n) (EQUIV (200) / ((n) + 2)) /* Cost of breaking a line before the last word of a sentence, where the length of the word is N. */ #define ORPHAN_COST(n) (EQUIV (150) / ((n) + 2)) /* Bonus for breaking a line at the end of a sentence. */ #define SENTENCE_BONUS EQUIV (50) /* Cost of breaking a line after a period not marking end of a sentence. With the definition of sentence we are using (borrowed from emacs, see get_line()) such a break would then look like a sentence break. Hence we assign a very high cost -- it should be avoided unless things are really bad. */ #define NOBREAK_COST EQUIV (600) /* Bonus for breaking a line before open parenthesis. */ #define PAREN_BONUS EQUIV (40) /* Bonus for breaking a line after other punctuation. */ #define PUNCT_BONUS EQUIV(40) /* Credit for breaking a long paragraph one line later. */ #define LINE_CREDIT EQUIV(3) /* Size of paragraph buffer, in words and characters. Longer paragraphs are handled neatly (cf. flush_paragraph()), so there's little to gain by making these larger. */ #define MAXWORDS 1000 #define MAXCHARS 5000 /* Extra ctype(3)-style macros. */ #define isopen(c) (strchr ("([`'\"", c) != NULL) #define isclose(c) (strchr (")]'\"", c) != NULL) #define isperiod(c) (strchr (".?!", c) != NULL) /* Size of a tab stop, for expansion on input and re-introduction on output. */ #define TABWIDTH 8 /* Miscellaneous definitions. */ #undef TRUE #define TRUE true #undef FALSE #define FALSE false /* Word descriptor structure. */ typedef struct Word WORD; struct Word { /* Static attributes determined during input. */ const char *text; /* the text of the word */ int length; /* length of this word */ int space; /* the size of the following space */ unsigned int paren:1; /* starts with open paren */ unsigned int period:1; /* ends in [.?!])* */ unsigned int punct:1; /* ends in punctuation */ unsigned int final:1; /* end of sentence */ /* The remaining fields are computed during the optimization. */ int line_length; /* length of the best line starting here */ COST best_cost; /* cost of best paragraph starting here */ WORD *next_break; /* break which achieves best_cost */ }; /* Forward declarations. */ static void set_prefix (char *p); static void fmt (FILE *f); static bool get_paragraph (FILE *f); static int get_line (FILE *f, int c); static int get_prefix (FILE *f); static int get_space (FILE *f, int c); static int copy_rest (FILE *f, int c); static bool same_para (int c); static void flush_paragraph (void); static void fmt_paragraph (void); static void check_punctuation (WORD *w); static COST base_cost (WORD *this); static COST line_cost (WORD *next, int len); static void put_paragraph (WORD *finish); static void put_line (WORD *w, int indent); static void put_word (WORD *w); static void put_space (int space); /* The name this program was run with. */ const char *program_name; /* Option values. */ /* If TRUE, first 2 lines may have different indent (default FALSE). */ static bool crown; /* If TRUE, first 2 lines _must_ have different indent (default FALSE). */ static bool tagged; /* If TRUE, each line is a paragraph on its own (default FALSE). */ static bool split; /* If TRUE, don't preserve inter-word spacing (default FALSE). */ static bool uniform; /* Prefix minus leading and trailing spaces (default ""). */ static const char *prefix; /* User-supplied maximum line width (default WIDTH). The only output lines longer than this will each comprise a single word. */ static int max_width; /* Values derived from the option values. */ /* The length of prefix minus leading space. */ static int prefix_full_length; /* The length of the leading space trimmed from the prefix. */ static int prefix_lead_space; /* The length of prefix minus leading and trailing space. */ static int prefix_length; /* The preferred width of text lines, set to LEEWAY % less than max_width. */ static int best_width; /* Dynamic variables. */ /* Start column of the character most recently read from the input file. */ static int in_column; /* Start column of the next character to be written to stdout. */ static int out_column; /* Space for the paragraph text -- longer paragraphs are handled neatly (cf. flush_paragraph()). */ static char parabuf[MAXCHARS]; /* A pointer into parabuf, indicating the first unused character position. */ static char *wptr; /* The words of a paragraph -- longer paragraphs are handled neatly (cf. flush_paragraph()). */ static WORD word[MAXWORDS]; /* A pointer into the above word array, indicating the first position after the last complete word. Sometimes it will point at an incomplete word. */ static WORD *word_limit; /* If TRUE, current input file contains tab characters, and so tabs can be used for white space on output. */ static bool tabs; /* Space before trimmed prefix on each line of the current paragraph. */ static int prefix_indent; /* Indentation of the first line of the current paragraph. */ static int first_indent; /* Indentation of other lines of the current paragraph */ static int other_indent; /* To detect the end of a paragraph, we need to look ahead to the first non-blank character after the prefix on the next line, or the first character on the following line that failed to match the prefix. We can reconstruct the lookahead from that character (next_char), its position on the line (in_column) and the amount of space before the prefix (next_prefix_indent). See get_paragraph() and copy_rest(). */ /* The last character read from the input file. */ static int next_char; /* The space before the trimmed prefix (or part of it) on the next line after the current paragraph. */ static int next_prefix_indent; /* If nonzero, the length of the last line output in the current paragraph, used to charge for raggedness at the split point for long paragraphs chosen by fmt_paragraph(). */ static int last_line_length; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("Usage: %s [-DIGITS] [OPTION]... [FILE]...\n"), program_name); fputs (_("\ Reformat each paragraph in the FILE(s), writing to standard output.\n\ If no FILE or if FILE is `-', read standard input.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ -c, --crown-margin preserve indentation of first two lines\n\ -p, --prefix=STRING combine only lines having STRING as prefix\n\ -s, --split-only split long lines, but do not refill\n\ "), stdout); fputs (_("\ -t, --tagged-paragraph indentation of first line different from second\n\ -u, --uniform-spacing one space between words, two after sentences\n\ -w, --width=NUMBER maximum line width (default of 75 columns)\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ In -wNUMBER, the letter `w' may be omitted.\n"), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } /* Decode options and launch execution. */ static const struct option long_options[] = { {"crown-margin", no_argument, NULL, 'c'}, {"prefix", required_argument, NULL, 'p'}, {"split-only", no_argument, NULL, 's'}, {"tagged-paragraph", no_argument, NULL, 't'}, {"uniform-spacing", no_argument, NULL, 'u'}, {"width", required_argument, NULL, 'w'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {0, 0, 0, 0}, }; int main (register int argc, register char **argv) { int optchar; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); crown = tagged = split = uniform = FALSE; max_width = WIDTH; prefix = ""; prefix_length = prefix_lead_space = prefix_full_length = 0; if (argc > 1 && argv[1][0] == '-' && ISDIGIT (argv[1][1])) { const char *s = argv[1] + 1; max_width = 0; /* Old option syntax; a dash followed by one or more digits. Move past the number. */ for (; ISDIGIT (*s); ++s) { int old_max = max_width; max_width = max_width * 10 + *s - '0'; if (INT_MAX / 10 < old_max || max_width < old_max) error (EXIT_FAILURE, 0, _("invalid width option: `%s'"), argv[1]); } /* Make the options we just parsed invisible to getopt. */ argv[1] = argv[0]; argv++; argc--; } while ((optchar = getopt_long (argc, argv, "0123456789cstuw:p:", long_options, NULL)) != -1) switch (optchar) { default: usage (EXIT_FAILURE); case 0: break; case 'c': crown = TRUE; break; case 's': split = TRUE; break; case 't': tagged = TRUE; break; case 'u': uniform = TRUE; break; case 'w': { long int tmp_long; if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK || tmp_long <= 0 || tmp_long > INT_MAX) error (EXIT_FAILURE, 0, _("invalid width: `%s'"), optarg); max_width = (int) tmp_long; } break; case 'p': set_prefix (optarg); break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); } best_width = max_width * (2 * (100 - LEEWAY) + 1) / 200; if (optind == argc) fmt (stdin); else { for (; optind < argc; optind++) { char *file = argv[optind]; if (STREQ (file, "-")) fmt (stdin); else { FILE *in_stream; in_stream = fopen (file, "r"); if (in_stream != NULL) { fmt (in_stream); if (fclose (in_stream) == EOF) error (EXIT_FAILURE, errno, "%s", file); } else error (0, errno, "%s", file); } } } exit (EXIT_SUCCESS); } /* Trim space from the front and back of the string P, yielding the prefix, and record the lengths of the prefix and the space trimmed. */ static void set_prefix (register char *p) { register char *s; prefix_lead_space = 0; while (*p == ' ') { prefix_lead_space++; p++; } prefix = p; prefix_full_length = strlen (p); s = p + prefix_full_length; while (s > p && s[-1] == ' ') s--; *s = '\0'; prefix_length = s - p; } /* read file F and send formatted output to stdout. */ static void fmt (FILE *f) { tabs = FALSE; other_indent = 0; next_char = get_prefix (f); while (get_paragraph (f)) { fmt_paragraph (); put_paragraph (word_limit); } } /* Set the global variable `other_indent' according to SAME_PARAGRAPH and other global variables. */ static void set_other_indent (bool same_paragraph) { if (split) other_indent = first_indent; else if (crown) { other_indent = (same_paragraph ? in_column : first_indent); } else if (tagged) { if (same_paragraph && in_column != first_indent) { other_indent = in_column; } /* Only one line: use the secondary indent from last time if it splits, or 0 if there have been no multi-line paragraphs in the input so far. But if these rules make the two indents the same, pick a new secondary indent. */ else if (other_indent == first_indent) other_indent = first_indent == 0 ? DEF_INDENT : 0; } else { other_indent = first_indent; } } /* Read a paragraph from input file F. A paragraph consists of a maximal number of non-blank (excluding any prefix) lines subject to: * In split mode, a paragraph is a single non-blank line. * In crown mode, the second and subsequent lines must have the same indentation, but possibly different from the indent of the first line. * Tagged mode is similar, but the first and second lines must have different indentations. * Otherwise, all lines of a paragraph must have the same indent. If a prefix is in effect, it must be present at the same indent for each line in the paragraph. Return FALSE if end-of-file was encountered before the start of a paragraph, else TRUE. */ static bool get_paragraph (FILE *f) { register int c; last_line_length = 0; c = next_char; /* Scan (and copy) blank lines, and lines not introduced by the prefix. */ while (c == '\n' || c == EOF || next_prefix_indent < prefix_lead_space || in_column < next_prefix_indent + prefix_full_length) { c = copy_rest (f, c); if (c == EOF) { next_char = EOF; return FALSE; } putchar ('\n'); c = get_prefix (f); } /* Got a suitable first line for a paragraph. */ prefix_indent = next_prefix_indent; first_indent = in_column; wptr = parabuf; word_limit = word; c = get_line (f, c); set_other_indent (same_para (c)); /* Read rest of paragraph (unless split is specified). */ if (split) { /* empty */ } else if (crown) { if (same_para (c)) { do { /* for each line till the end of the para */ c = get_line (f, c); } while (same_para (c) && in_column == other_indent); } } else if (tagged) { if (same_para (c) && in_column != first_indent) { do { /* for each line till the end of the para */ c = get_line (f, c); } while (same_para (c) && in_column == other_indent); } } else { while (same_para (c) && in_column == other_indent) c = get_line (f, c); } (word_limit - 1)->period = (word_limit - 1)->final = TRUE; next_char = c; return TRUE; } /* Copy to the output a line that failed to match the prefix, or that was blank after the prefix. In the former case, C is the character that failed to match the prefix. In the latter, C is \n or EOF. Return the character (\n or EOF) ending the line. */ static int copy_rest (FILE *f, register int c) { register const char *s; out_column = 0; if (in_column > next_prefix_indent && c != '\n' && c != EOF) { put_space (next_prefix_indent); for (s = prefix; out_column != in_column && *s; out_column++) putchar (*(unsigned char *)s++); put_space (in_column - out_column); } while (c != '\n' && c != EOF) { putchar (c); c = getc (f); } return c; } /* Return TRUE if a line whose first non-blank character after the prefix (if any) is C could belong to the current paragraph, otherwise FALSE. */ static bool same_para (register int c) { return (next_prefix_indent == prefix_indent && in_column >= next_prefix_indent + prefix_full_length && c != '\n' && c != EOF); } /* Read a line from input file F, given first non-blank character C after the prefix, and the following indent, and break it into words. A word is a maximal non-empty string of non-white characters. A word ending in [.?!]["')\]]* and followed by end-of-line or at least two spaces ends a sentence, as in emacs. Return the first non-blank character of the next line. */ static int get_line (FILE *f, register int c) { int start; register char *end_of_parabuf; register WORD *end_of_word; end_of_parabuf = ¶buf[MAXCHARS]; end_of_word = &word[MAXWORDS - 2]; do { /* for each word in a line */ /* Scan word. */ word_limit->text = wptr; do { if (wptr == end_of_parabuf) { set_other_indent (true); flush_paragraph (); } *wptr++ = c; c = getc (f); } while (c != EOF && !ISSPACE (c)); in_column += word_limit->length = wptr - word_limit->text; check_punctuation (word_limit); /* Scan inter-word space. */ start = in_column; c = get_space (f, c); word_limit->space = in_column - start; word_limit->final = (c == EOF || (word_limit->period && (c == '\n' || word_limit->space > 1))); if (c == '\n' || c == EOF || uniform) word_limit->space = word_limit->final ? 2 : 1; if (word_limit == end_of_word) { set_other_indent (true); flush_paragraph (); } word_limit++; if (c == EOF) return EOF; } while (c != '\n'); return get_prefix (f); } /* Read a prefix from input file F. Return either first non-matching character, or first non-blank character after the prefix. */ static int get_prefix (FILE *f) { register int c; in_column = 0; c = get_space (f, getc (f)); if (prefix_length == 0) next_prefix_indent = prefix_lead_space < in_column ? prefix_lead_space : in_column; else { const char *p; next_prefix_indent = in_column; for (p = prefix; *p != '\0'; p++) { if (c != *(unsigned char *)p) return c; in_column++; c = getc (f); } c = get_space (f, c); } return c; } /* Read blank characters from input file F, starting with C, and keeping in_column up-to-date. Return first non-blank character. */ static int get_space (FILE *f, register int c) { for (;;) { if (c == ' ') in_column++; else if (c == '\t') { tabs = TRUE; in_column = (in_column / TABWIDTH + 1) * TABWIDTH; } else return c; c = getc (f); } } /* Set extra fields in word W describing any attached punctuation. */ static void check_punctuation (register WORD *w) { const unsigned char *start, *finish; start = (unsigned char *) w->text; finish = start + (w->length - 1); w->paren = isopen (*start); w->punct = ISPUNCT (*finish); while (isclose (*finish) && finish > start) finish--; w->period = isperiod (*finish); } /* Flush part of the paragraph to make room. This function is called on hitting the limit on the number of words or characters. */ static void flush_paragraph (void) { WORD *split_point; register WORD *w; int shift; COST best_break; /* In the special case where it's all one word, just flush it. */ if (word_limit == word) { printf ("%*s", wptr - parabuf, parabuf); wptr = parabuf; return; } /* Otherwise: - format what you have so far as a paragraph, - find a low-cost line break near the end, - output to there, - make that the start of the paragraph. */ fmt_paragraph (); /* Choose a good split point. */ split_point = word_limit; best_break = MAXCOST; for (w = word->next_break; w != word_limit; w = w->next_break) { if (w->best_cost - w->next_break->best_cost < best_break) { split_point = w; best_break = w->best_cost - w->next_break->best_cost; } if (best_break <= MAXCOST - LINE_CREDIT) best_break += LINE_CREDIT; } put_paragraph (split_point); /* Copy text of words down to start of parabuf -- we use memmove because the source and target may overlap. */ memmove (parabuf, split_point->text, (size_t) (wptr - split_point->text)); shift = split_point->text - parabuf; wptr -= shift; /* Adjust text pointers. */ for (w = split_point; w <= word_limit; w++) w->text -= shift; /* Copy words from split_point down to word -- we use memmove because the source and target may overlap. */ memmove ((char *) word, (char *) split_point, (word_limit - split_point + 1) * sizeof (WORD)); word_limit -= split_point - word; } /* Compute the optimal formatting for the whole paragraph by computing and remembering the optimal formatting for each suffix from the empty one to the whole paragraph. */ static void fmt_paragraph (void) { register WORD *start, *w; register int len; register COST wcost, best; int saved_length; word_limit->best_cost = 0; saved_length = word_limit->length; word_limit->length = max_width; /* sentinel */ for (start = word_limit - 1; start >= word; start--) { best = MAXCOST; len = start == word ? first_indent : other_indent; /* At least one word, however long, in the line. */ w = start; len += w->length; do { w++; /* Consider breaking before w. */ wcost = line_cost (w, len) + w->best_cost; if (start == word && last_line_length > 0) wcost += RAGGED_COST (len - last_line_length); if (wcost < best) { best = wcost; start->next_break = w; start->line_length = len; } /* This is a kludge to keep us from computing `len' as the sum of the sentinel length and some non-zero number. Since the sentinel w->length may be INT_MAX, adding to that would give a negative result. */ if (w == word_limit) break; len += (w - 1)->space + w->length; /* w > start >= word */ } while (len < max_width); start->best_cost = best + base_cost (start); } word_limit->length = saved_length; } /* Return the constant component of the cost of breaking before the word THIS. */ static COST base_cost (register WORD *this) { register COST cost; cost = LINE_COST; if (this > word) { if ((this - 1)->period) { if ((this - 1)->final) cost -= SENTENCE_BONUS; else cost += NOBREAK_COST; } else if ((this - 1)->punct) cost -= PUNCT_BONUS; else if (this > word + 1 && (this - 2)->final) cost += WIDOW_COST ((this - 1)->length); } if (this->paren) cost -= PAREN_BONUS; else if (this->final) cost += ORPHAN_COST (this->length); return cost; } /* Return the component of the cost of breaking before word NEXT that depends on LEN, the length of the line beginning there. */ static COST line_cost (register WORD *next, register int len) { register int n; register COST cost; if (next == word_limit) return 0; n = best_width - len; cost = SHORT_COST (n); if (next->next_break != word_limit) { n = len - next->line_length; cost += RAGGED_COST (n); } return cost; } /* Output to stdout a paragraph from word up to (but not including) FINISH, which must be in the next_break chain from word. */ static void put_paragraph (register WORD *finish) { register WORD *w; put_line (word, first_indent); for (w = word->next_break; w != finish; w = w->next_break) put_line (w, other_indent); } /* Output to stdout the line beginning with word W, beginning in column INDENT, including the prefix (if any). */ static void put_line (register WORD *w, int indent) { register WORD *endline; out_column = 0; put_space (prefix_indent); fputs (prefix, stdout); out_column += prefix_length; put_space (indent - out_column); endline = w->next_break - 1; for (; w != endline; w++) { put_word (w); put_space (w->space); } put_word (w); last_line_length = out_column; putchar ('\n'); } /* Output to stdout the word W. */ static void put_word (register WORD *w) { register const char *s; register int n; s = w->text; for (n = w->length; n != 0; n--) putchar (*s++); out_column += w->length; } /* Output to stdout SPACE spaces, or equivalent tabs. */ static void put_space (int space) { register int space_target, tab_target; space_target = out_column + space; if (tabs) { tab_target = space_target / TABWIDTH * TABWIDTH; if (out_column + 1 < tab_target) while (out_column < tab_target) { putchar ('\t'); out_column = (out_column / TABWIDTH + 1) * TABWIDTH; } } while (out_column < space_target) { putchar (' '); out_column++; } }
/* fold -- wrap each input line to fit in specified width. Copyright (C) 91, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie, djm@gnu.ai.mit.edu. */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include "system.h" #include "closeout.h" #include "error.h" #include "posixver.h" #include "xstrtol.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "fold" #define AUTHORS "David MacKenzie" /* The name this program was run with. */ char *program_name; /* If nonzero, try to break on whitespace. */ static int break_spaces; /* If nonzero, count bytes, not column positions. */ static int count_bytes; /* If nonzero, at least one of the files we read was standard input. */ static int have_read_stdin; static struct option const longopts[] = { {"bytes", no_argument, NULL, 'b'}, {"spaces", no_argument, NULL, 's'}, {"width", required_argument, NULL, 'w'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... [FILE]...\n\ "), program_name); fputs (_("\ Wrap input lines in each FILE (standard input by default), writing to\n\ standard output.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ -b, --bytes count bytes rather than columns\n\ -s, --spaces break at spaces\n\ -w, --width=WIDTH use WIDTH columns instead of 80\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } /* Assuming the current column is COLUMN, return the column that printing C will move the cursor to. The first column is 0. */ static int adjust_column (int column, char c) { if (!count_bytes) { if (c == '\b') { if (column > 0) column--; } else if (c == '\r') column = 0; else if (c == '\t') column = column + 8 - column % 8; else /* if (isprint (c)) */ column++; } else column++; return column; } /* Fold file FILENAME, or standard input if FILENAME is "-", to stdout, with maximum line length WIDTH. Return 0 if successful, 1 if an error occurs. */ static int fold_file (char *filename, int width) { FILE *istream; register int c; int column = 0; /* Screen column where next char will go. */ int offset_out = 0; /* Index in `line_out' for next char. */ static char *line_out = NULL; static int allocated_out = 0; if (STREQ (filename, "-")) { istream = stdin; have_read_stdin = 1; } else istream = fopen (filename, "r"); if (istream == NULL) { error (0, errno, "%s", filename); return 1; } while ((c = getc (istream)) != EOF) { if (offset_out + 1 >= allocated_out) { allocated_out += 1024; line_out = xrealloc (line_out, allocated_out); } if (c == '\n') { line_out[offset_out++] = c; fwrite (line_out, sizeof (char), (size_t) offset_out, stdout); column = offset_out = 0; continue; } rescan: column = adjust_column (column, c); if (column > width) { /* This character would make the line too long. Print the line plus a newline, and make this character start the next line. */ if (break_spaces) { /* Look for the last blank. */ int logical_end; for (logical_end = offset_out - 1; logical_end >= 0; logical_end--) if (ISBLANK (line_out[logical_end])) break; if (logical_end >= 0) { int i; /* Found a blank. Don't output the part after it. */ logical_end++; fwrite (line_out, sizeof (char), (size_t) logical_end, stdout); putchar ('\n'); /* Move the remainder to the beginning of the next line. The areas being copied here might overlap. */ memmove (line_out, line_out + logical_end, offset_out - logical_end); offset_out -= logical_end; for (column = i = 0; i < offset_out; i++) column = adjust_column (column, line_out[i]); goto rescan; } } else { if (offset_out == 0) { line_out[offset_out++] = c; continue; } } line_out[offset_out++] = '\n'; fwrite (line_out, sizeof (char), (size_t) offset_out, stdout); column = offset_out = 0; goto rescan; } line_out[offset_out++] = c; } if (offset_out) fwrite (line_out, sizeof (char), (size_t) offset_out, stdout); if (ferror (istream)) { error (0, errno, "%s", filename); if (!STREQ (filename, "-")) fclose (istream); return 1; } if (!STREQ (filename, "-") && fclose (istream) == EOF) { error (0, errno, "%s", filename); return 1; } return 0; } int main (int argc, char **argv) { int width = 80; int i; int optc; int errs = 0; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); break_spaces = count_bytes = have_read_stdin = 0; /* Turn any numeric options into -w options. */ for (i = 1; i < argc; i++) { char const *a = argv[i]; if (a[0] == '-') { if (a[1] == '-' && ! a[2]) break; if (ISDIGIT (a[1])) { char *s = xmalloc (strlen (a) + 2); s[0] = '-'; s[1] = 'w'; strcpy (s + 2, a + 1); argv[i] = s; if (200112 <= posix2_version ()) { error (0, 0, _("`%s' option is obsolete; use `%s'"), a, s); usage (EXIT_FAILURE); } } } } while ((optc = getopt_long (argc, argv, "bsw:", longopts, NULL)) != -1) { switch (optc) { case 0: break; case 'b': /* Count bytes rather than columns. */ count_bytes = 1; break; case 's': /* Break at word boundaries. */ break_spaces = 1; break; case 'w': /* Line width. */ { long int tmp_long; if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK || tmp_long <= 0 || tmp_long > INT_MAX) error (EXIT_FAILURE, 0, _("invalid number of columns: `%s'"), optarg); width = (int) tmp_long; } break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } if (argc == optind) errs |= fold_file ("-", width); else for (i = optind; i < argc; i++) errs |= fold_file (argv[i], width); if (have_read_stdin && fclose (stdin) == EOF) error (EXIT_FAILURE, errno, "-"); exit (errs == 0 ? EXIT_SUCCESS : EXIT_FAILURE); }
/* head -- output first part of file(s) Copyright (C) 89, 90, 91, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Options: (see usage) Reads from standard input if no files are given or when a filename of ``-'' is encountered. By default, filename headers are printed only if more than one file is given. By default, prints the first 10 lines (head -n 10). David MacKenzie <djm@gnu.ai.mit.edu> */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include "system.h" #include "closeout.h" #include "error.h" #include "posixver.h" #include "xstrtol.h" #include "safe-read.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "head" #define AUTHORS "David MacKenzie" /* Number of lines/chars/blocks to head. */ #define DEFAULT_NUMBER 10 /* Size of atomic reads. */ #define BUFSIZE (512 * 8) /* If nonzero, print filename headers. */ static int print_headers; /* When to print the filename banners. */ enum header_mode { multiple_files, always, never }; /* Options corresponding to header_mode values. */ static char const header_mode_option[][4] = { "", " -v", " -q" }; /* The name this program was run with. */ char *program_name; /* Have we ever read standard input? */ static int have_read_stdin; static struct option const long_options[] = { {"bytes", required_argument, NULL, 'c'}, {"lines", required_argument, NULL, 'n'}, {"quiet", no_argument, NULL, 'q'}, {"silent", no_argument, NULL, 'q'}, {"verbose", no_argument, NULL, 'v'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... [FILE]...\n\ "), program_name); fputs (_("\ Print first 10 lines of each FILE to standard output.\n\ With more than one FILE, precede each with a header giving the file name.\n\ With no FILE, or when FILE is -, read standard input.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ -c, --bytes=SIZE print first SIZE bytes\n\ -n, --lines=NUMBER print first NUMBER lines instead of first 10\n\ "), stdout); fputs (_("\ -q, --quiet, --silent never print headers giving file names\n\ -v, --verbose always print headers giving file names\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ SIZE may have a multiplier suffix: b for 512, k for 1K, m for 1 Meg.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } static void write_header (const char *filename) { static int first_file = 1; printf ("%s==> %s <==\n", (first_file ? "" : "\n"), filename); first_file = 0; } static int head_bytes (const char *filename, int fd, uintmax_t bytes_to_write) { char buffer[BUFSIZE]; size_t bytes_to_read = BUFSIZE; /* Need BINARY I/O for the byte counts to be accurate. */ SET_BINARY2 (fd, fileno (stdout)); while (bytes_to_write) { size_t bytes_read; if (bytes_to_write < bytes_to_read) bytes_to_read = bytes_to_write; bytes_read = safe_read (fd, buffer, bytes_to_read); if (bytes_read == SAFE_READ_ERROR) { error (0, errno, "%s", filename); return 1; } if (bytes_read == 0) break; if (fwrite (buffer, 1, bytes_read, stdout) == 0) error (EXIT_FAILURE, errno, _("write error")); bytes_to_write -= bytes_read; } return 0; } static int head_lines (const char *filename, int fd, uintmax_t lines_to_write) { char buffer[BUFSIZE]; /* Need BINARY I/O for the byte counts to be accurate. */ SET_BINARY2 (fd, fileno (stdout)); while (lines_to_write) { size_t bytes_read = safe_read (fd, buffer, BUFSIZE); size_t bytes_to_write = 0; if (bytes_read == SAFE_READ_ERROR) { error (0, errno, "%s", filename); return 1; } if (bytes_read == 0) break; while (bytes_to_write < bytes_read) if (buffer[bytes_to_write++] == '\n' && --lines_to_write == 0) { off_t n_bytes_past_EOL = bytes_read - bytes_to_write; /* If we have read more data than that on the specified number of lines, try to seek back to the position we would have gotten to had we been reading one byte at a time. */ if (lseek (fd, -n_bytes_past_EOL, SEEK_CUR) < 0) { int e = errno; struct stat st; if (fstat (fd, &st) != 0 || S_ISREG (st.st_mode)) error (0, e, _("cannot reposition file pointer for %s"), filename); } break; } if (fwrite (buffer, 1, bytes_to_write, stdout) == 0) error (EXIT_FAILURE, errno, _("write error")); } return 0; } static int head (const char *filename, int fd, uintmax_t n_units, int count_lines) { if (print_headers) write_header (filename); if (count_lines) return head_lines (filename, fd, n_units); else return head_bytes (filename, fd, n_units); } static int head_file (const char *filename, uintmax_t n_units, int count_lines) { int fd; if (STREQ (filename, "-")) { have_read_stdin = 1; return head (_("standard input"), STDIN_FILENO, n_units, count_lines); } else { fd = open (filename, O_RDONLY); if (fd >= 0) { int errors; errors = head (filename, fd, n_units, count_lines); if (close (fd) == 0) return errors; } error (0, errno, "%s", filename); return 1; } } /* Convert a string of decimal digits, N_STRING, with a single, optional suffix character (b, k, or m) to an integral value. Upon successful conversion, return that value. If it cannot be converted, give a diagnostic and exit. COUNT_LINES indicates whether N_STRING is a number of bytes or a number of lines. It is used solely to give a more specific diagnostic. */ static uintmax_t string_to_integer (int count_lines, const char *n_string) { strtol_error s_err; uintmax_t n; s_err = xstrtoumax (n_string, NULL, 10, &n, "bkm"); if (s_err == LONGINT_OVERFLOW) { error (EXIT_FAILURE, 0, _("%s: %s is so large that it is not representable"), n_string, count_lines ? _("number of lines") : _("number of bytes")); } if (s_err != LONGINT_OK) { error (EXIT_FAILURE, 0, "%s: %s", n_string, (count_lines ? _("invalid number of lines") : _("invalid number of bytes"))); } return n; } int main (int argc, char **argv) { enum header_mode header_mode = multiple_files; int exit_status = 0; int c; /* Number of items to print. */ uintmax_t n_units = DEFAULT_NUMBER; /* If nonzero, interpret the numeric argument as the number of lines. Otherwise, interpret it as the number of bytes. */ int count_lines = 1; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); have_read_stdin = 0; print_headers = 0; if (1 < argc && argv[1][0] == '-' && ISDIGIT (argv[1][1])) { char *a = argv[1]; char *n_string = ++a; char *end_n_string; char multiplier_char = 0; /* Old option syntax; a dash, one or more digits, and one or more option letters. Move past the number. */ do ++a; while (ISDIGIT (*a)); /* Pointer to the byte after the last digit. */ end_n_string = a; /* Parse any appended option letters. */ for (; *a; a++) { switch (*a) { case 'c': count_lines = 0; multiplier_char = 0; break; case 'b': case 'k': case 'm': count_lines = 0; multiplier_char = *a; break; case 'l': count_lines = 1; break; case 'q': header_mode = never; break; case 'v': header_mode = always; break; default: error (0, 0, _("unrecognized option `-%c'"), *a); usage (EXIT_FAILURE); } } if (200112 <= posix2_version ()) { error (0, 0, _("`-%s' option is obsolete; use `-%c %.*s%.*s%s'"), n_string, count_lines ? 'n' : 'c', (int) (end_n_string - n_string), n_string, multiplier_char != 0, &multiplier_char, header_mode_option[header_mode]); usage (EXIT_FAILURE); } /* Append the multiplier character (if any) onto the end of the digit string. Then add NUL byte if necessary. */ *end_n_string = multiplier_char; if (multiplier_char) *(++end_n_string) = 0; n_units = string_to_integer (count_lines, n_string); /* Make the options we just parsed invisible to getopt. */ argv[1] = argv[0]; argv++; argc--; /* FIXME: allow POSIX options if there were obsolescent ones? */ } while ((c = getopt_long (argc, argv, "c:n:qv", long_options, NULL)) != -1) { switch (c) { case 0: break; case 'c': count_lines = 0; n_units = string_to_integer (count_lines, optarg); break; case 'n': count_lines = 1; n_units = string_to_integer (count_lines, optarg); break; case 'q': header_mode = never; break; case 'v': header_mode = always; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } if (header_mode == always || (header_mode == multiple_files && optind < argc - 1)) print_headers = 1; if (optind == argc) exit_status |= head_file ("-", n_units, count_lines); for (; optind < argc; ++optind) exit_status |= head_file (argv[optind], n_units, count_lines); if (have_read_stdin && close (STDIN_FILENO) < 0) error (EXIT_FAILURE, errno, "-"); exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); }
/* hostname - set or print the name of current host system Copyright (C) 1994-1997, 1999-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Jim Meyering <meyering@comco.com> */ #include <config.h> #include <stdio.h> #include <sys/types.h> #include "system.h" #include "closeout.h" #include "long-options.h" #include "error.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "hostname" #define AUTHORS "Jim Meyering" #if !defined(HAVE_SETHOSTNAME) && defined(HAVE_SYSINFO) && \ defined (HAVE_SYS_SYSTEMINFO_H) && defined(HAVE_LIMITS_H) # include <sys/systeminfo.h> int sethostname (name, namelen) char *name; int namelen; { /* Using sysinfo() is the SVR4 mechanism to set a hostname. */ int result; result = sysinfo (SI_SET_HOSTNAME, name, namelen); return (result == -1 ? result : 0); } # define HAVE_SETHOSTNAME 1 /* Now we have it... */ #endif char *xgethostname (); /* The name this program was run with. */ char *program_name; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [NAME]\n\ or: %s OPTION\n\ Print or set the hostname of the current system.\n\ \n\ "), program_name, program_name); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } int main (int argc, char **argv) { char *hostname; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, AUTHORS, usage); #ifdef HAVE_SETHOSTNAME if (argc == 2) { int err; /* Set hostname to argv[1]. */ err = sethostname (argv[1], strlen (argv[1])); if (err != 0) error (EXIT_FAILURE, errno, _("cannot set hostname to `%s'"), argv[1]); exit (EXIT_SUCCESS); } #else if (argc == 2) error (EXIT_FAILURE, 0, _("cannot set hostname; this system lacks the functionality")); #endif if (argc == 1) { hostname = xgethostname (); if (hostname == NULL) error (EXIT_FAILURE, errno, _("cannot determine hostname")); printf ("%s\n", hostname); } else { error (2, 0, _("too many arguments")); usage (EXIT_FAILURE); } exit (EXIT_SUCCESS); }
/* id -- print real and effective UIDs and GIDs Copyright (C) 1989-2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Arnold Robbins. Major rewrite by David MacKenzie, djm@gnu.ai.mit.edu. */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include <pwd.h> #include <grp.h> #include <getopt.h> #include "system.h" #include "error.h" #include "closeout.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "id" #define AUTHORS N_ ("Arnold Robbins and David MacKenzie") #ifndef _POSIX_VERSION struct passwd *getpwuid (); struct group *getgrgid (); uid_t getuid (); gid_t getgid (); uid_t geteuid (); gid_t getegid (); #endif /* not _POSIX_VERSION */ int getugroups (); static void print_user (uid_t uid); static void print_group (gid_t gid); static void print_group_list (const char *username); static void print_full_info (const char *username); /* The name this program was run with. */ char *program_name; /* If nonzero, output user/group name instead of ID number. -n */ static int use_name = 0; /* The real and effective IDs of the user to print. */ static uid_t ruid, euid; static gid_t rgid, egid; /* The number of errors encountered so far. */ static int problems = 0; static struct option const longopts[] = { {"group", no_argument, NULL, 'g'}, {"groups", no_argument, NULL, 'G'}, {"name", no_argument, NULL, 'n'}, {"real", no_argument, NULL, 'r'}, {"user", no_argument, NULL, 'u'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("Usage: %s [OPTION]... [USERNAME]\n"), program_name); fputs (_("\ Print information for USERNAME, or the current user.\n\ \n\ -a ignore, for compatibility with other versions\n\ -g, --group print only the effective group ID\n\ -G, --groups print all group IDs\n\ -n, --name print a name instead of a number, for -ugG\n\ -r, --real print the real ID instead of the effective ID, with -ugG\n\ -u, --user print only the effective user ID\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ Without any OPTION, print some useful set of identified information.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } int main (int argc, char **argv) { int optc; /* If nonzero, output the list of all group IDs. -G */ int just_group_list = 0; /* If nonzero, output only the group ID(s). -g */ int just_group = 0; /* If nonzero, output real UID/GID instead of default effective UID/GID. -r */ int use_real = 0; /* If nonzero, output only the user ID(s). -u */ int just_user = 0; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); while ((optc = getopt_long (argc, argv, "agnruG", longopts, NULL)) != -1) { switch (optc) { case 0: break; case 'a': /* Ignore -a, for compatibility with SVR4. */ break; case 'g': just_group = 1; break; case 'n': use_name = 1; break; case 'r': use_real = 1; break; case 'u': just_user = 1; break; case 'G': just_group_list = 1; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } if (just_user + just_group + just_group_list > 1) error (EXIT_FAILURE, 0, _("cannot print only user and only group")); if (just_user + just_group + just_group_list == 0 && (use_real || use_name)) error (EXIT_FAILURE, 0, _("cannot print only names or real IDs in default format")); if (argc - optind > 1) usage (EXIT_FAILURE); if (argc - optind == 1) { struct passwd *pwd = getpwnam (argv[optind]); if (pwd == NULL) error (EXIT_FAILURE, 0, _("%s: No such user"), argv[optind]); ruid = euid = pwd->pw_uid; rgid = egid = pwd->pw_gid; } else { euid = geteuid (); ruid = getuid (); egid = getegid (); rgid = getgid (); } if (just_user) print_user (use_real ? ruid : euid); else if (just_group) print_group (use_real ? rgid : egid); else if (just_group_list) print_group_list (argv[optind]); else print_full_info (argv[optind]); putchar ('\n'); exit (problems != 0); } /* Print the name or value of user ID UID. */ static void print_user (uid_t uid) { struct passwd *pwd = NULL; if (use_name) { pwd = getpwuid (uid); if (pwd == NULL) { error (0, 0, _("cannot find name for user ID %u"), uid); problems++; } } if (pwd == NULL) printf ("%u", (unsigned) uid); else printf ("%s", pwd->pw_name); } /* Print the name or value of group ID GID. */ static void print_group (gid_t gid) { struct group *grp = NULL; if (use_name) { grp = getgrgid (gid); if (grp == NULL) { error (0, 0, _("cannot find name for group ID %u"), gid); problems++; } } if (grp == NULL) printf ("%u", (unsigned) gid); else printf ("%s", grp->gr_name); } #if HAVE_GETGROUPS /* FIXME: document */ static int xgetgroups (const char *username, gid_t gid, int *n_groups, GETGROUPS_T **groups) { int max_n_groups; int ng; GETGROUPS_T *g; int fail = 0; if (username == 0) max_n_groups = getgroups (0, NULL); else max_n_groups = getugroups (0, NULL, username, gid); /* Add 1 just in case max_n_groups is zero. */ g = (GETGROUPS_T *) xmalloc (max_n_groups * sizeof (GETGROUPS_T) + 1); if (username == 0) ng = getgroups (max_n_groups, g); else ng = getugroups (max_n_groups, g, username, gid); if (ng < 0) { error (0, errno, _("cannot get supplemental group list")); ++fail; free (groups); } if (!fail) { *n_groups = ng; *groups = g; } return fail; } #endif /* HAVE_GETGROUPS */ /* Print all of the distinct groups the user is in. */ static void print_group_list (const char *username) { struct passwd *pwd; pwd = getpwuid (ruid); if (pwd == NULL) problems++; print_group (rgid); if (egid != rgid) { putchar (' '); print_group (egid); } #if HAVE_GETGROUPS { int n_groups; GETGROUPS_T *groups; register int i; if (xgetgroups (username, (pwd ? pwd->pw_gid : (gid_t) -1), &n_groups, &groups)) { ++problems; return; } for (i = 0; i < n_groups; i++) if (groups[i] != rgid && groups[i] != egid) { putchar (' '); print_group (groups[i]); } free (groups); } #endif /* HAVE_GETGROUPS */ } /* Print all of the info about the user's user and group IDs. */ static void print_full_info (const char *username) { struct passwd *pwd; struct group *grp; printf ("uid=%u", (unsigned) ruid); pwd = getpwuid (ruid); if (pwd == NULL) problems++; else printf ("(%s)", pwd->pw_name); printf (" gid=%u", (unsigned) rgid); grp = getgrgid (rgid); if (grp == NULL) problems++; else printf ("(%s)", grp->gr_name); if (euid != ruid) { printf (" euid=%u", (unsigned) euid); pwd = getpwuid (euid); if (pwd == NULL) problems++; else printf ("(%s)", pwd->pw_name); } if (egid != rgid) { printf (" egid=%u", (unsigned) egid); grp = getgrgid (egid); if (grp == NULL) problems++; else printf ("(%s)", grp->gr_name); } #if HAVE_GETGROUPS { int n_groups; GETGROUPS_T *groups; register int i; if (xgetgroups (username, (pwd ? pwd->pw_gid : (gid_t) -1), &n_groups, &groups)) { ++problems; return; } if (n_groups > 0) fputs (_(" groups="), stdout); for (i = 0; i < n_groups; i++) { if (i > 0) putchar (','); printf ("%u", (unsigned) groups[i]); grp = getgrgid (groups[i]); if (grp == NULL) problems++; else printf ("(%s)", grp->gr_name); } free (groups); } #endif /* HAVE_GETGROUPS */ }
/* join - join lines of two files on a common field Copyright (C) 91, 1995-2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Written by Mike Haertel, mike@gnu.ai.mit.edu. */ #include <config.h> #include <stdio.h> #include <assert.h> #include <sys/types.h> #include <getopt.h> #include "system.h" #include "closeout.h" #include "error.h" #include "hard-locale.h" #include "linebuffer.h" #include "memcasecmp.h" #include "xmemcoll.h" #include "xstrtol.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "join" #define AUTHORS "Mike Haertel" #define join system_join /* Undefine, to avoid warning about redefinition on some systems. */ #undef min #undef max #define min(A, B) ((A) < (B) ? (A) : (B)) #define max(A, B) ((A) > (B) ? (A) : (B)) /* An element of the list identifying which fields to print for each output line. */ struct outlist { /* File number: 0, 1, or 2. 0 means use the join field. 1 means use the first file argument, 2 the second. */ int file; /* Field index (zero-based), specified only when FILE is 1 or 2. */ int field; struct outlist *next; }; /* A field of a line. */ struct field { const unsigned char *beg; /* First character in field. */ size_t len; /* The length of the field. */ }; /* A line read from an input file. */ struct line { struct linebuffer buf; /* The line itself. */ int nfields; /* Number of elements in `fields'. */ int nfields_allocated; /* Number of elements in `fields'. */ struct field *fields; }; /* One or more consecutive lines read from a file that all have the same join field value. */ struct seq { int count; /* Elements used in `lines'. */ int alloc; /* Elements allocated in `lines'. */ struct line *lines; }; /* The name this program was run with. */ char *program_name; /* Nonzero if the LC_COLLATE locale is hard. */ static int hard_LC_COLLATE; /* If nonzero, print unpairable lines in file 1 or 2. */ static int print_unpairables_1, print_unpairables_2; /* If nonzero, print pairable lines. */ static int print_pairables; /* Empty output field filler. */ static char *empty_filler; /* Field to join on. */ static int join_field_1, join_field_2; /* List of fields to print. */ static struct outlist outlist_head; /* Last element in `outlist', where a new element can be added. */ static struct outlist *outlist_end = &outlist_head; /* Tab character separating fields; if this is NUL fields are separated by any nonempty string of white space, otherwise by exactly one tab character. */ static unsigned char tab; /* When using getopt_long_only, no long option can start with a character that is a short option. */ static struct option const longopts[] = { {"ignore-case", no_argument, NULL, 'i'}, {"j", required_argument, NULL, 'j'}, {"j1", required_argument, NULL, '1'}, {"j2", required_argument, NULL, '2'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; /* Used to print non-joining lines */ static struct line uni_blank; /* If nonzero, ignore case when comparing join fields. */ static int ignore_case; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... FILE1 FILE2\n\ "), program_name); fputs (_("\ For each pair of input lines with identical join fields, write a line to\n\ standard output. The default join field is the first, delimited\n\ by whitespace. When FILE1 or FILE2 (not both) is -, read standard input.\n\ \n\ -a FILENUM print unpairable lines coming from file FILENUM, where\n\ FILENUM is 1 or 2, corresponding to FILE1 or FILE2\n\ -e EMPTY replace missing input fields with EMPTY\n\ "), stdout); fputs (_("\ -i, --ignore-case ignore differences in case when comparing fields\n\ -j FIELD (obsolescent) equivalent to `-1 FIELD -2 FIELD'\n\ -j1 FIELD (obsolescent) equivalent to `-1 FIELD'\n\ -j2 FIELD (obsolescent) equivalent to `-2 FIELD'\n\ -o FORMAT obey FORMAT while constructing output line\n\ -t CHAR use CHAR as input and output field separator\n\ "), stdout); fputs (_("\ -v FILENUM like -a FILENUM, but suppress joined output lines\n\ -1 FIELD join on this FIELD of file 1\n\ -2 FIELD join on this FIELD of file 2\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ Unless -t CHAR is given, leading blanks separate fields and are ignored,\n\ else fields are separated by CHAR. Any FIELD is a field number counted\n\ from 1. FORMAT is one or more comma or blank separated specifications,\n\ each being `FILENUM.FIELD' or `0'. Default FORMAT outputs the join field,\n\ the remaining fields from FILE1, the remaining fields from FILE2, all\n\ separated by CHAR.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } static void ADD_FIELD (struct line *line, const unsigned char *field, size_t len) { if (line->nfields >= line->nfields_allocated) { line->nfields_allocated = (3 * line->nfields_allocated) / 2 + 1; line->fields = (struct field *) xrealloc ((char *) line->fields, (line->nfields_allocated * sizeof (struct field))); } line->fields[line->nfields].beg = field; line->fields[line->nfields].len = len; ++(line->nfields); } /* Fill in the `fields' structure in LINE. */ static void xfields (struct line *line) { int i; unsigned char *ptr0 = (unsigned char *) line->buf.buffer; unsigned char *ptr; unsigned char *lim; ptr = ptr0; lim = ptr0 + line->buf.length - 1; if (!tab) { /* Skip leading blanks before the first field. */ while (ptr < lim && ISBLANK (*ptr)) ++ptr; } for (i = 0; ptr < lim; ++i) { if (tab) { unsigned char *beg; beg = ptr; while (ptr < lim && *ptr != tab) ++ptr; ADD_FIELD (line, beg, ptr - beg); if (ptr < lim) ++ptr; } else { unsigned char *beg; beg = ptr; while (ptr < lim && !ISBLANK (*ptr)) ++ptr; ADD_FIELD (line, beg, ptr - beg); while (ptr < lim && ISBLANK (*ptr)) ++ptr; } } if (ptr != ptr0 && ((!tab && ISBLANK (ptr[-1])) || ptr[-1] == tab)) { /* Add one more (empty) field because the last character of the line was a delimiter. */ ADD_FIELD (line, NULL, 0); } } /* Read a line from FP into LINE and split it into fields. Return 0 if EOF, 1 otherwise. */ static int get_line (FILE *fp, struct line *line) { initbuffer (&line->buf); if (! readline (&line->buf, fp)) { free (line->buf.buffer); line->buf.buffer = NULL; return 0; } line->nfields_allocated = 0; line->nfields = 0; line->fields = NULL; xfields (line); return 1; } static void freeline (struct line *line) { free ((char *) line->fields); free (line->buf.buffer); line->buf.buffer = NULL; } static void initseq (struct seq *seq) { seq->count = 0; seq->alloc = 1; seq->lines = (struct line *) xmalloc (seq->alloc * sizeof (struct line)); } /* Read a line from FP and add it to SEQ. Return 0 if EOF, 1 otherwise. */ static int getseq (FILE *fp, struct seq *seq) { if (seq->count == seq->alloc) { seq->alloc *= 2; seq->lines = (struct line *) xrealloc ((char *) seq->lines, seq->alloc * sizeof (struct line)); } if (get_line (fp, &seq->lines[seq->count])) { ++seq->count; return 1; } return 0; } static void delseq (struct seq *seq) { int i; for (i = 0; i < seq->count; i++) if (seq->lines[i].buf.buffer) freeline (&seq->lines[i]); free ((char *) seq->lines); } /* Return <0 if the join field in LINE1 compares less than the one in LINE2; >0 if it compares greater; 0 if it compares equal. Report an error and exit if the comparison fails. */ static int keycmp (struct line *line1, struct line *line2) { /* Start of field to compare in each file. */ const unsigned char *beg1, *beg2; size_t len1, len2; /* Length of fields to compare. */ int diff; if (join_field_1 < line1->nfields) { beg1 = line1->fields[join_field_1].beg; len1 = line1->fields[join_field_1].len; } else { beg1 = NULL; len1 = 0; } if (join_field_2 < line2->nfields) { beg2 = line2->fields[join_field_2].beg; len2 = line2->fields[join_field_2].len; } else { beg2 = NULL; len2 = 0; } if (len1 == 0) return len2 == 0 ? 0 : -1; if (len2 == 0) return 1; /* Use an if-statement here rather than a function variable to avoid portability hassles of getting a non-conflicting declaration of memcmp. */ if (ignore_case) { /* FIXME: ignore_case does not work with NLS (in particular, with multibyte chars). */ diff = memcasecmp (beg1, beg2, min (len1, len2)); } else { if (HAVE_SETLOCALE && hard_LC_COLLATE) return xmemcoll ((char *) beg1, len1, (char *) beg2, len2); diff = memcmp (beg1, beg2, min (len1, len2)); } if (diff) return diff; return len1 < len2 ? -1 : len1 != len2; } /* Print field N of LINE if it exists and is nonempty, otherwise `empty_filler' if it is nonempty. */ static void prfield (int n, struct line *line) { size_t len; if (n < line->nfields) { len = line->fields[n].len; if (len) fwrite (line->fields[n].beg, 1, len, stdout); else if (empty_filler) fputs (empty_filler, stdout); } else if (empty_filler) fputs (empty_filler, stdout); } /* Print the join of LINE1 and LINE2. */ static void prjoin (struct line *line1, struct line *line2) { const struct outlist *outlist; outlist = outlist_head.next; if (outlist) { const struct outlist *o; o = outlist; while (1) { int field; struct line *line; if (o->file == 0) { if (line1 == &uni_blank) { line = line2; field = join_field_2; } else { line = line1; field = join_field_1; } } else { line = (o->file == 1 ? line1 : line2); assert (o->field >= 0); field = o->field; } prfield (field, line); o = o->next; if (o == NULL) break; putchar (tab ? tab : ' '); } putchar ('\n'); } else { int i; if (line1 == &uni_blank) { struct line *t; t = line1; line1 = line2; line2 = t; } prfield (join_field_1, line1); for (i = 0; i < join_field_1 && i < line1->nfields; ++i) { putchar (tab ? tab : ' '); prfield (i, line1); } for (i = join_field_1 + 1; i < line1->nfields; ++i) { putchar (tab ? tab : ' '); prfield (i, line1); } for (i = 0; i < join_field_2 && i < line2->nfields; ++i) { putchar (tab ? tab : ' '); prfield (i, line2); } for (i = join_field_2 + 1; i < line2->nfields; ++i) { putchar (tab ? tab : ' '); prfield (i, line2); } putchar ('\n'); } } /* Print the join of the files in FP1 and FP2. */ static void join (FILE *fp1, FILE *fp2) { struct seq seq1, seq2; struct line line; int diff, i, j, eof1, eof2; /* Read the first line of each file. */ initseq (&seq1); getseq (fp1, &seq1); initseq (&seq2); getseq (fp2, &seq2); while (seq1.count && seq2.count) { diff = keycmp (&seq1.lines[0], &seq2.lines[0]); if (diff < 0) { if (print_unpairables_1) prjoin (&seq1.lines[0], &uni_blank); freeline (&seq1.lines[0]); seq1.count = 0; getseq (fp1, &seq1); continue; } if (diff > 0) { if (print_unpairables_2) prjoin (&uni_blank, &seq2.lines[0]); freeline (&seq2.lines[0]); seq2.count = 0; getseq (fp2, &seq2); continue; } /* Keep reading lines from file1 as long as they continue to match the current line from file2. */ eof1 = 0; do if (!getseq (fp1, &seq1)) { eof1 = 1; ++seq1.count; break; } while (!keycmp (&seq1.lines[seq1.count - 1], &seq2.lines[0])); /* Keep reading lines from file2 as long as they continue to match the current line from file1. */ eof2 = 0; do if (!getseq (fp2, &seq2)) { eof2 = 1; ++seq2.count; break; } while (!keycmp (&seq1.lines[0], &seq2.lines[seq2.count - 1])); if (print_pairables) { for (i = 0; i < seq1.count - 1; ++i) for (j = 0; j < seq2.count - 1; ++j) prjoin (&seq1.lines[i], &seq2.lines[j]); } for (i = 0; i < seq1.count - 1; ++i) freeline (&seq1.lines[i]); if (!eof1) { seq1.lines[0] = seq1.lines[seq1.count - 1]; seq1.count = 1; } else seq1.count = 0; for (i = 0; i < seq2.count - 1; ++i) freeline (&seq2.lines[i]); if (!eof2) { seq2.lines[0] = seq2.lines[seq2.count - 1]; seq2.count = 1; } else seq2.count = 0; } if (print_unpairables_1 && seq1.count) { prjoin (&seq1.lines[0], &uni_blank); freeline (&seq1.lines[0]); while (get_line (fp1, &line)) { prjoin (&line, &uni_blank); freeline (&line); } } if (print_unpairables_2 && seq2.count) { prjoin (&uni_blank, &seq2.lines[0]); freeline (&seq2.lines[0]); while (get_line (fp2, &line)) { prjoin (&uni_blank, &line); freeline (&line); } } delseq (&seq1); delseq (&seq2); } /* Add a field spec for field FIELD of file FILE to `outlist'. */ static void add_field (int file, int field) { struct outlist *o; assert (file == 0 || file == 1 || file == 2); assert (file == 0 ? field < 0 : field >= 0); o = (struct outlist *) xmalloc (sizeof (struct outlist)); o->file = file; o->field = field; o->next = NULL; /* Add to the end of the list so the fields are in the right order. */ outlist_end->next = o; outlist_end = o; } /* Convert a single field specifier string, S, to a *FILE_INDEX, *FIELD_INDEX pair. In S, the field index string is 1-based; *FIELD_INDEX is zero-based. If S is valid, return zero. Otherwise, give a diagnostic, don't update *FILE_INDEX or *FIELD_INDEX, and return nonzero. */ static int decode_field_spec (const char *s, int *file_index, int *field_index) { int invalid = 1; /* The first character must be 0, 1, or 2. */ switch (s[0]) { case '0': if (s[1] == '\0') { *file_index = 0; /* Give *field_index an invalid value. */ *field_index = -1; invalid = 0; } else { /* `0' must be all alone -- no `.FIELD'. */ error (0, 0, _("invalid field specifier: `%s'"), s); } break; case '1': case '2': if (s[1] == '.' && s[2] != '\0') { strtol_error s_err; long int tmp_long; s_err = xstrtol (s + 2, NULL, 10, &tmp_long, ""); if (s_err != LONGINT_OK || tmp_long <= 0 || tmp_long > INT_MAX) { error (0, 0, _("invalid field number: `%s'"), s + 2); } else { *file_index = s[0] - '0'; /* Convert to a zero-based index. */ *field_index = (int) tmp_long - 1; invalid = 0; } } break; default: error (0, 0, _("invalid file number in field spec: `%s'"), s); break; } return invalid; } /* Add the comma or blank separated field spec(s) in STR to `outlist'. Return nonzero to indicate failure. */ static int add_field_list (const char *c_str) { char *p, *str; /* Make a writable copy of c_str. */ str = (char *) alloca (strlen (c_str) + 1); strcpy (str, c_str); p = str; do { int invalid; int file_index, field_index; char *spec_item = p; p = strpbrk (p, ", \t"); if (p) *p++ = 0; invalid = decode_field_spec (spec_item, &file_index, &field_index); if (invalid) return 1; add_field (file_index, field_index); uni_blank.nfields = max (uni_blank.nfields, field_index); } while (p); return 0; } /* Create a blank line with COUNT fields separated by tabs. */ static void make_blank (struct line *blank, int count) { int i; unsigned char *buffer; struct field *fields; blank->nfields = count; blank->buf.size = blank->buf.length = count + 1; blank->buf.buffer = xmalloc (blank->buf.size); buffer = (unsigned char *) blank->buf.buffer; blank->fields = fields = (struct field *) xmalloc (sizeof (struct field) * count); for (i = 0; i < count; i++) { buffer[i] = '\t'; fields[i].beg = &buffer[i]; fields[i].len = 0; } buffer[i] = '\n'; } int main (int argc, char **argv) { char *names[2]; FILE *fp1, *fp2; int optc, prev_optc = 0, nfiles; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); hard_LC_COLLATE = hard_locale (LC_COLLATE); atexit (close_stdout); /* Initialize this before parsing options. In parsing options, it may be increased. */ uni_blank.nfields = 1; nfiles = 0; print_pairables = 1; while ((optc = getopt_long_only (argc, argv, "-a:e:i1:2:o:t:v:", longopts, NULL)) != -1) { long int val; switch (optc) { case 0: break; case 'v': print_pairables = 0; /* Fall through. */ case 'a': if (xstrtol (optarg, NULL, 10, &val, "") != LONGINT_OK || (val != 1 && val != 2)) error (EXIT_FAILURE, 0, _("invalid field number: `%s'"), optarg); if (val == 1) print_unpairables_1 = 1; else print_unpairables_2 = 1; break; case 'e': empty_filler = optarg; break; case 'i': ignore_case = 1; break; case '1': if (xstrtol (optarg, NULL, 10, &val, "") != LONGINT_OK || val <= 0 || val > INT_MAX) { error (EXIT_FAILURE, 0, _("invalid field number for file 1: `%s'"), optarg); } join_field_1 = (int) val - 1; break; case '2': if (xstrtol (optarg, NULL, 10, &val, "") != LONGINT_OK || val <= 0 || val > INT_MAX) error (EXIT_FAILURE, 0, _("invalid field number for file 2: `%s'"), optarg); join_field_2 = (int) val - 1; break; case 'j': if (xstrtol (optarg, NULL, 10, &val, "") != LONGINT_OK || val <= 0 || val > INT_MAX) error (EXIT_FAILURE, 0, _("invalid field number: `%s'"), optarg); join_field_1 = join_field_2 = (int) val - 1; break; case 'o': if (add_field_list (optarg)) exit (EXIT_FAILURE); break; case 't': tab = *optarg; break; case 1: /* Non-option argument. */ if (prev_optc == 'o' && optind <= argc - 2) { if (add_field_list (optarg)) exit (EXIT_FAILURE); /* Might be continuation of args to -o. */ continue; /* Don't change `prev_optc'. */ } if (nfiles > 1) { error (0, 0, _("too many non-option arguments")); usage (EXIT_FAILURE); } names[nfiles++] = optarg; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } prev_optc = optc; } /* Now that we've seen the options, we can construct the blank line structure. */ make_blank (&uni_blank, uni_blank.nfields); if (nfiles != 2) { error (0, 0, _("too few non-option arguments")); usage (EXIT_FAILURE); } fp1 = STREQ (names[0], "-") ? stdin : fopen (names[0], "r"); if (!fp1) error (EXIT_FAILURE, errno, "%s", names[0]); fp2 = STREQ (names[1], "-") ? stdin : fopen (names[1], "r"); if (!fp2) error (EXIT_FAILURE, errno, "%s", names[1]); if (fp1 == fp2) error (EXIT_FAILURE, errno, _("both files cannot be standard input")); join (fp1, fp2); if (fp1 != stdin && fclose (fp1) == EOF) error (EXIT_FAILURE, errno, "%s", names[0]); if (fp2 != stdin && fclose (fp2) == EOF) error (EXIT_FAILURE, errno, "%s", names[1]); if ((fp1 == stdin || fp2 == stdin) && fclose (stdin) == EOF) error (EXIT_FAILURE, errno, "-"); exit (EXIT_SUCCESS); }
#ifndef HARD_LOCALE_H_ # define HARD_LOCALE_H_ 1 # if HAVE_CONFIG_H # include <config.h> # endif # ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif # endif int hard_locale PARAMS ((int)); #endif /* HARD_LOCALE_H_ */
/* linebuffer.h -- declarations for reading arbitrarily long lines Copyright (C) 1986, 1991, 1998, 1999 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if !defined LINEBUFFER_H # define LINEBUFFER_H /* A `struct linebuffer' holds a line of text. */ struct linebuffer { size_t size; /* Allocated. */ size_t length; /* Used. */ char *buffer; }; # ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif # endif /* Initialize linebuffer LINEBUFFER for use. */ void initbuffer PARAMS ((struct linebuffer *linebuffer)); /* Read an arbitrarily long line of text from STREAM into LINEBUFFER. Keep the newline; append a newline if it's the last line of a file that ends in a non-newline character. Do not null terminate. Return LINEBUFFER, except at end of file return 0. */ struct linebuffer *readline PARAMS ((struct linebuffer *linebuffer, FILE *stream)); /* Free linebuffer LINEBUFFER and its data, all allocated with malloc. */ void freebuffer PARAMS ((struct linebuffer *)); #endif /* LINEBUFFER_H */
#ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif #endif int memcasecmp PARAMS ((const void *vs1, const void *vs2, size_t n));
extern int xmemcoll_exit_failure; int xmemcoll (char *, size_t, char *, size_t);
/* kill -- send a signal to a process Copyright (C) 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert. */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include <signal.h> #if HAVE_SYS_WAIT_H # include <sys/wait.h> #endif #ifndef WIFSIGNALED # define WIFSIGNALED(s) (((s) & 0xFFFF) - 1 < (unsigned int) 0xFF) #endif #ifndef WTERMSIG # define WTERMSIG(s) ((s) & 0x7F) #endif #include "system.h" #include "closeout.h" #include "error.h" #include "sig2str.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "kill" #define AUTHORS "Paul Eggert" #if ! (HAVE_DECL_STRTOIMAX || defined strtoimax) intmax_t strtoimax (); #endif #if ! (HAVE_DECL_STRSIGNAL || defined strsignal) # if ! (HAVE_DECL_SYS_SIGLIST || defined sys_siglist) # if HAVE_DECL__SYS_SIGLIST || defined _sys_siglist # define sys_siglist _sys_siglist # endif # endif # if HAVE_DECL_SYS_SIGLIST || defined sys_siglist # define strsignal(signum) (0 <= (signum) && (signum) <= SIGNUM_BOUND \ ? sys_siglist[signum] \ : 0) # endif # ifndef strsignal # define strsignal(signum) 0 # endif #endif /* The name this program was run with, for error messages. */ char *program_name; static char const short_options[] = "0::1::2::3::4::5::6::7::8::9::" "A::B::C::D::E::F::G::H::I::J::K::L::M::" "N::O::P::Q::R::S::T::U::V::W::X::Y::Z::" "ln:s:t"; static struct option const long_options[] = { {"list", no_argument, NULL, 'l'}, {"signal", required_argument, NULL, 's'}, {"table", no_argument, NULL, 't'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [-s SIGNAL | -SIGNAL] PID...\n\ or: %s -l [SIGNAL]...\n\ or: %s -t [SIGNAL]...\n\ "), program_name, program_name, program_name); fputs (_("\ Send signals to processes, or list signals.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ -s, --signal=SIGNAL, -SIGNAL\n\ specify the name or number of the signal to be sent\n\ -l, --list list signal names, or convert signal names to/from numbers\n\ -t, --table print a table of signal information\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\n\ SIGNAL may be a signal name like `HUP', or a signal number like `1',\n\ or an exit status of a process terminated by a signal.\n\ PID is an integer; if negative it identifies a process group.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } /* Convert OPERAND to a signal number with printable representation SIGNAME. Return the signal number, or -1 if unsuccessful. */ static int operand2sig (char const *operand, char *signame) { int signum; if (ISDIGIT (*operand)) { char *endp; long int l = (errno = 0, strtol (operand, &endp, 10)); int i = l; signum = (operand == endp || *endp || errno || i != l ? -1 : WIFSIGNALED (i) ? WTERMSIG (i) : i); } else { /* Convert signal to upper case in the C locale, not in the current locale. Don't assume ASCII; it might be EBCDIC. */ char *upcased = xstrdup (operand); char *p; for (p = upcased; *p; p++) if (strchr ("abcdefghijklmnopqrstuvwxyz", *p)) *p += 'A' - 'a'; /* Look for the signal name, possibly prefixed by "SIG", and possibly lowercased. */ if (! (str2sig (upcased, &signum) == 0 || (upcased[0] == 'S' && upcased[1] == 'I' && upcased[2] == 'G' && str2sig (upcased + 3, &signum) == 0))) signum = -1; free (upcased); } if (signum < 0 || sig2str (signum, signame) != 0) { error (0, 0, _("%s: invalid signal"), operand); return -1; } return signum; } /* Print a row of `kill -t' output. NUM_WIDTH is the maximum signal number width, and SIGNUM is the signal number to print. The maximum name width is NAME_WIDTH, and SIGNAME is the name to print. */ static void print_table_row (unsigned int num_width, int signum, unsigned int name_width, char const *signame) { char const *description = strsignal (signum); printf ("%*d %-*s %s\n", num_width, signum, name_width, signame, description ? description : "?"); } /* Print a list of signal names. If TABLE, print a table. Print the names specified by ARGV if nonzero; otherwise, print all known names. Return a suitable exit status. */ static int list_signals (bool table, char *const *argv) { int signum; int status = EXIT_SUCCESS; char signame[SIG2STR_MAX]; if (table) { unsigned int name_width = 0; /* Compute the maximum width of a signal number. */ unsigned int num_width = 1; for (signum = 1; signum <= SIGNUM_BOUND / 10; signum *= 10) num_width++; /* Compute the maximum width of a signal name. */ for (signum = 1; signum <= SIGNUM_BOUND; signum++) if (sig2str (signum, signame) == 0) { size_t len = strlen (signame); if (name_width < len) name_width = len; } if (argv) for (; *argv; argv++) { signum = operand2sig (*argv, signame); if (signum < 0) status = EXIT_FAILURE; else print_table_row (num_width, signum, name_width, signame); } else for (signum = 1; signum <= SIGNUM_BOUND; signum++) if (sig2str (signum, signame) == 0) print_table_row (num_width, signum, name_width, signame); } else { if (argv) for (; *argv; argv++) { signum = operand2sig (*argv, signame); if (signum < 0) status = EXIT_FAILURE; else { if (ISDIGIT (**argv)) puts (signame); else printf ("%d\n", signum); } } else for (signum = 1; signum <= SIGNUM_BOUND; signum++) if (sig2str (signum, signame) == 0) puts (signame); } return status; } /* Send signal SIGNUM to all the processes or process groups specified by ARGV. Return a suitable exit status. */ static int send_signals (int signum, char *const *argv) { int status = EXIT_SUCCESS; char const *arg = *argv; if (! arg) { error (0, 0, _("missing operand after `%s'"), argv[-1]); usage (EXIT_FAILURE); } do { char *endp; intmax_t n = (errno = 0, strtoimax (arg, &endp, 10)); pid_t pid = n; if (errno == ERANGE || pid != n || arg == endp || *endp) { error (0, 0, _("%s: invalid process id"), arg); status = EXIT_FAILURE; } else if (kill (pid, signum) != 0) { error (0, errno, "%s", arg); status = EXIT_FAILURE; } } while ((arg = *++argv)); return status; } int main (int argc, char **argv) { int optc; bool list = false; bool table = false; int signum = -1; char signame[SIG2STR_MAX]; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); while ((optc = getopt_long (argc, argv, short_options, long_options, NULL)) != -1) switch (optc) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (optind != 2) { /* This option is actually a process-id. */ optind--; goto no_more_options; } /* Fall through. */ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': if (! optarg) optarg = argv[optind - 1] + strlen (argv[optind - 1]); if (optarg != argv[optind - 1] + 2) { error (0, 0, _("invalid option -- %c"), optc); usage (EXIT_FAILURE); } optarg--; /* Fall through. */ case 'n': /* -n is not documented, but is for Bash compatibility. */ case 's': if (0 <= signum) { error (0, 0, _("%s: multiple signals specified"), optarg); usage (EXIT_FAILURE); } signum = operand2sig (optarg, signame); if (signum < 0) usage (EXIT_FAILURE); break; case 't': table = true; /* Fall through. */ case 'l': if (list) { error (0, 0, _("multiple -l or -t options specified")); usage (EXIT_FAILURE); } list = true; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } no_more_options:; if (signum < 0) signum = SIGTERM; else if (list) { error (0, 0, _("cannot combine signal with -l or -t")); usage (EXIT_FAILURE); } return (list ? list_signals (table, optind == argc ? NULL : argv + optind) : send_signals (signum, argv + optind)); }
/* link utility for GNU. Copyright (C) 2001-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Michael Stone */ /* Implementation overview: Simply call the system 'link' function */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include <assert.h> #include "system.h" #include "error.h" #include "long-options.h" #include "quote.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "link" #define AUTHORS "Michael Stone" /* Name this program was run with. */ char *program_name; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s FILE1 FILE2\n\ or: %s OPTION\n"), program_name, program_name); fputs (_("Call the link function to create a link named FILE2\ to an existing FILE1.\n\n"), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } int main (int argc, char **argv) { program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, AUTHORS, usage); /* The above handles --help and --version. Since there is no other invocation of getopt, handle `--' here. */ if (1 < argc && STREQ (argv[1], "--")) { --argc; ++argv; } if (argc < 3) { error (0, 0, _("too few arguments")); usage (EXIT_FAILURE); } if (3 < argc) { error (0, 0, _("too many arguments")); usage (EXIT_FAILURE); } if (link (argv[1], argv[2]) != 0) error (EXIT_FAILURE, errno, _("cannot create link %s to %s"), quote_n (0, argv[2]), quote_n (1, argv[1])); exit (EXIT_SUCCESS); }
/* `ln' program to create links between files. Copyright (C) 86, 89, 90, 91, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Mike Parker and David MacKenzie. */ #ifdef _AIX #pragma alloca #endif #include <config.h> #include <stdio.h> #include <sys/types.h> #include <getopt.h> #include "system.h" #include "same.h" #include "backupfile.h" #include "dirname.h" #include "error.h" #include "quote.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "ln" #define AUTHORS N_ ("Mike Parker and David MacKenzie") /* For long options that have no equivalent short option, use a non-character as a pseudo short option, starting with CHAR_MAX + 1. */ enum { TARGET_DIRECTORY_OPTION = CHAR_MAX + 1 }; int link (); /* Some systems don't declare this anywhere. */ #ifdef S_ISLNK int symlink (); #endif /* In being careful not even to try to make hard links to directories, we have to know whether link(2) follows symlinks. If it does, then we have to *stat* the `source' to see if the resulting link would be to a directory. Otherwise, we have to use *lstat* so that we allow users to make hard links to symlinks-that-point-to-directories. */ #if LINK_FOLLOWS_SYMLINKS # define STAT_LIKE_LINK(File, Stat_buf) \ stat (File, Stat_buf) #else # define STAT_LIKE_LINK(File, Stat_buf) \ lstat (File, Stat_buf) #endif /* Construct a string NEW_DEST by concatenating DEST, a slash, and basename(SOURCE) in alloca'd memory. Don't modify DEST or SOURCE. */ #define PATH_BASENAME_CONCAT(new_dest, dest, source) \ do \ { \ const char *source_base; \ char *tmp_source; \ \ tmp_source = (char *) alloca (strlen ((source)) + 1); \ strcpy (tmp_source, (source)); \ strip_trailing_slashes (tmp_source); \ source_base = base_name (tmp_source); \ \ (new_dest) = (char *) alloca (strlen ((dest)) + 1 \ + strlen (source_base) + 1); \ stpcpy (stpcpy (stpcpy ((new_dest), (dest)), "/"), source_base);\ } \ while (0) int isdir (); int yesno (); /* The name by which the program was run, for error messages. */ char *program_name; /* FIXME: document */ enum backup_type backup_type; /* A pointer to the function used to make links. This will point to either `link' or `symlink'. */ static int (*linkfunc) (); /* If nonzero, make symbolic links; otherwise, make hard links. */ static int symbolic_link; /* If nonzero, ask the user before removing existing files. */ static int interactive; /* If nonzero, remove existing files unconditionally. */ static int remove_existing_files; /* If nonzero, list each file as it is moved. */ static int verbose; /* If nonzero, allow the superuser to make hard links to directories. */ static int hard_dir_link; /* If nonzero, and the specified destination is a symbolic link to a directory, treat it just as if it were a directory. Otherwise, the command `ln --force --no-dereference file symlink-to-dir' deletes symlink-to-dir before creating the new link. */ static int dereference_dest_dir_symlinks = 1; static struct option const long_options[] = { {"backup", optional_argument, NULL, 'b'}, {"directory", no_argument, NULL, 'F'}, {"no-dereference", no_argument, NULL, 'n'}, {"force", no_argument, NULL, 'f'}, {"interactive", no_argument, NULL, 'i'}, {"suffix", required_argument, NULL, 'S'}, {"target-directory", required_argument, NULL, TARGET_DIRECTORY_OPTION}, {"symbolic", no_argument, NULL, 's'}, {"verbose", no_argument, NULL, 'v'}, {"version-control", required_argument, NULL, 'V'}, /* Deprecated. FIXME. */ {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; /* Make a link DEST to the (usually) existing file SOURCE. Symbolic links to nonexistent files are allowed. If DEST is a directory, put the link to SOURCE in that directory. Return 1 if there is an error, otherwise 0. */ static int do_link (const char *source, const char *dest) { struct stat source_stats; struct stat dest_stats; char *dest_backup = NULL; int lstat_status; int backup_succeeded = 0; /* Use stat here instead of lstat. On SVR4, link does not follow symlinks, so this check disallows making hard links to symlinks that point to directories. Big deal. On other systems, link follows symlinks, so this check is right. */ if (!symbolic_link) { if (STAT_LIKE_LINK (source, &source_stats) != 0) { error (0, errno, _("accessing %s"), quote (source)); return 1; } if (S_ISLNK (source_stats.st_mode)) { error (0, 0, _("%s: warning: making a hard link to a symbolic link\ is not portable"), quote (source)); } if (!hard_dir_link && S_ISDIR (source_stats.st_mode)) { error (0, 0, _("%s: hard link not allowed for directory"), quote (source)); return 1; } } lstat_status = lstat (dest, &dest_stats); if (lstat_status != 0 && errno != ENOENT) { error (0, errno, _("accessing %s"), quote (dest)); return 1; } /* If the destination is a directory or (it is a symlink to a directory and the user has not specified --no-dereference), then form the actual destination name by appending base_name (source) to the specified destination directory. */ if ((lstat_status == 0 && S_ISDIR (dest_stats.st_mode)) #ifdef S_ISLNK || (dereference_dest_dir_symlinks && (lstat_status == 0 && S_ISLNK (dest_stats.st_mode) && isdir (dest))) #endif ) { /* Target is a directory; build the full filename. */ char *new_dest; PATH_BASENAME_CONCAT (new_dest, dest, source); dest = new_dest; /* Get stats for new DEST. */ lstat_status = lstat (dest, &dest_stats); if (lstat_status != 0 && errno != ENOENT) { error (0, errno, _("accessing %s"), quote (dest)); return 1; } } /* If --force (-f) has been specified without --backup, then before making a link ln must remove the destination file if it exists. (with --backup, it just renames any existing destination file) But if the source and destination are the same, don't remove anything and fail right here. */ if (remove_existing_files && lstat_status == 0 /* Allow `ln -sf --backup k k' to succeed in creating the self-referential symlink, but don't allow the hard-linking equivalent: `ln -f k k' (with or without --backup) to get beyond this point, because the error message you'd get is misleading. */ && (backup_type == none || !symbolic_link) && (!symbolic_link || stat (source, &source_stats) == 0) && source_stats.st_dev == dest_stats.st_dev && source_stats.st_ino == dest_stats.st_ino /* The following detects whether removing DEST will also remove SOURCE. If the file has only one link then both are surely the same link. Otherwise check whether they point to the same name in the same directory. */ && (source_stats.st_nlink == 1 || same_name (source, dest))) { error (0, 0, _("%s and %s are the same file"), quote_n (0, source), quote_n (1, dest)); return 1; } if (lstat_status == 0 || lstat (dest, &dest_stats) == 0) { if (S_ISDIR (dest_stats.st_mode)) { error (0, 0, _("%s: cannot overwrite directory"), quote (dest)); return 1; } if (interactive) { fprintf (stderr, _("%s: replace %s? "), program_name, quote (dest)); if (!yesno ()) return 0; } else if (!remove_existing_files && backup_type == none) { error (0, 0, _("%s: File exists"), quote (dest)); return 1; } if (backup_type != none) { char *tmp_backup = find_backup_file_name (dest, backup_type); if (tmp_backup == NULL) xalloc_die (); dest_backup = (char *) alloca (strlen (tmp_backup) + 1); strcpy (dest_backup, tmp_backup); free (tmp_backup); if (rename (dest, dest_backup)) { if (errno != ENOENT) { error (0, errno, _("cannot backup %s"), quote (dest)); return 1; } else dest_backup = NULL; } else { backup_succeeded = 1; } } /* Try to unlink DEST even if we may have renamed it. In some unusual cases (when DEST and DEST_BACKUP are hard-links that refer to the same file), rename succeeds and DEST remains. If we didn't remove DEST in that case, the subsequent LINKFUNC call would fail. */ if (unlink (dest) && errno != ENOENT) { error (0, errno, _("cannot remove %s"), quote (dest)); return 1; } } else if (errno != ENOENT) { error (0, errno, _("accessing %s"), quote (dest)); return 1; } if (verbose) { printf ((symbolic_link ? _("create symbolic link %s to %s") : _("create hard link %s to %s")), quote_n (0, dest), quote_n (1, source)); if (backup_succeeded) printf (_(" (backup: %s)"), quote (dest_backup)); putchar ('\n'); } if ((*linkfunc) (source, dest) == 0) { return 0; } error (0, errno, (symbolic_link ? _("creating symbolic link %s to %s") : _("creating hard link %s to %s")), quote_n (0, dest), quote_n (1, source)); if (dest_backup) { if (rename (dest_backup, dest)) error (0, errno, _("cannot un-backup %s"), quote (dest)); } return 1; } void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... TARGET [LINK_NAME]\n\ or: %s [OPTION]... TARGET... DIRECTORY\n\ or: %s [OPTION]... --target-directory=DIRECTORY TARGET...\n\ "), program_name, program_name, program_name); fputs (_("\ Create a link to the specified TARGET with optional LINK_NAME.\n\ If LINK_NAME is omitted, a link with the same basename as the TARGET is\n\ created in the current directory. When using the second form with more\n\ than one TARGET, the last argument must be a directory; create links\n\ in DIRECTORY to each TARGET. Create hard links by default, symbolic\n\ links with --symbolic. When creating hard links, each TARGET must exist.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ --backup[=CONTROL] make a backup of each existing destination file\n\ -b like --backup but does not accept an argument\n\ -d, -F, --directory hard link directories (super-user only)\n\ -f, --force remove existing destination files\n\ "), stdout); fputs (_("\ -n, --no-dereference treat destination that is a symlink to a\n\ directory as if it were a normal file\n\ -i, --interactive prompt whether to remove destinations\n\ -s, --symbolic make symbolic links instead of hard links\n\ "), stdout); fputs (_("\ -S, --suffix=SUFFIX override the usual backup suffix\n\ --target-directory=DIRECTORY specify the DIRECTORY in which to create\n\ the links\n\ -v, --verbose print name of each file before linking\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ The version control method may be selected via the --backup option or through\n\ the VERSION_CONTROL environment variable. Here are the values:\n\ \n\ "), stdout); fputs (_("\ none, off never make backups (even if --backup is given)\n\ numbered, t make numbered backups\n\ existing, nil numbered if numbered backups exist, simple otherwise\n\ simple, never always make simple backups\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } int main (int argc, char **argv) { int c; int errors; int make_backups = 0; char *backup_suffix_string; char *version_control_string = NULL; char *target_directory = NULL; int target_directory_specified; unsigned int n_files; char **file; int dest_is_dir; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); /* FIXME: consider not calling getenv for SIMPLE_BACKUP_SUFFIX unless we'll actually use backup_suffix_string. */ backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX"); symbolic_link = remove_existing_files = interactive = verbose = hard_dir_link = 0; errors = 0; while ((c = getopt_long (argc, argv, "bdfinsvFS:V:", long_options, NULL)) != -1) { switch (c) { case 0: /* Long-named option. */ break; case 'V': /* FIXME: this is deprecated. Remove it in 2001. */ error (0, 0, _("warning: --version-control (-V) is obsolete; support for\ it\nwill be removed in some future release. Use --backup=%s instead." ), optarg); /* Fall through. */ case 'b': make_backups = 1; if (optarg) version_control_string = optarg; break; case 'd': case 'F': hard_dir_link = 1; break; case 'f': remove_existing_files = 1; interactive = 0; break; case 'i': remove_existing_files = 0; interactive = 1; break; case 'n': dereference_dest_dir_symlinks = 0; break; case 's': #ifdef S_ISLNK symbolic_link = 1; #else error (EXIT_FAILURE, 0, _("symbolic links are not supported on this system")); #endif break; case TARGET_DIRECTORY_OPTION: target_directory = optarg; break; case 'v': verbose = 1; break; case 'S': make_backups = 1; backup_suffix_string = optarg; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); break; } } n_files = argc - optind; file = argv + optind; if (n_files == 0) { error (0, 0, _("missing file argument")); usage (EXIT_FAILURE); } target_directory_specified = (target_directory != NULL); if (!target_directory) target_directory = file[n_files - 1]; /* If target directory is not specified, and there's only one file argument, then pretend `.' was given as the second argument. */ if (!target_directory_specified && n_files == 1) { static char *dummy[2]; dummy[0] = file[0]; dummy[1] = "."; file = dummy; n_files = 2; dest_is_dir = 1; } else { dest_is_dir = isdir (target_directory); } if (symbolic_link) linkfunc = symlink; else linkfunc = link; if (target_directory_specified && !dest_is_dir) { error (0, 0, _("%s: specified target directory is not a directory"), quote (target_directory)); usage (EXIT_FAILURE); } if (backup_suffix_string) simple_backup_suffix = xstrdup (backup_suffix_string); backup_type = (make_backups ? xget_version (_("backup type"), version_control_string) : none); if (target_directory_specified || n_files > 2) { unsigned int i; unsigned int last_file_idx = (target_directory_specified ? n_files - 1 : n_files - 2); if (!target_directory_specified && !dest_is_dir) error (EXIT_FAILURE, 0, _("when making multiple links, last argument must be a directory")); for (i = 0; i <= last_file_idx; ++i) errors += do_link (file[i], target_directory); } else { struct stat source_stats; const char *source; char *dest; char *new_dest; source = file[0]; dest = file[1]; /* When the destination is specified with a trailing slash and the source exists but is not a directory, convert the user's command `ln source dest/' to `ln source dest/basename(source)'. */ if (dest[strlen (dest) - 1] == '/' && lstat (source, &source_stats) == 0 && !S_ISDIR (source_stats.st_mode)) { PATH_BASENAME_CONCAT (new_dest, dest, source); } else { new_dest = dest; } errors = do_link (source, new_dest); } exit (errors != 0); }
/* logname -- print user's login name Copyright (C) 1990-1997, 1999-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <config.h> #include <stdio.h> #include <sys/types.h> #include <getopt.h> #include "system.h" #include "closeout.h" #include "long-options.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "logname" #define AUTHORS "FIXME: unknown" /* The name this program was run with. */ char *program_name; static struct option const long_options[] = { {0, 0, 0, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("Usage: %s [OPTION]\n"), program_name); fputs (_("\ Print the name of the current user.\n\ \n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } int main (int argc, char **argv) { register char *cp; int c; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, AUTHORS, usage); while ((c = getopt_long (argc, argv, "", long_options, NULL)) != -1) { switch (c) { case 0: break; default: usage (EXIT_FAILURE); } } if (argc - optind != 0) usage (EXIT_FAILURE); /* POSIX requires using getlogin (or equivalent code). */ cp = getlogin (); if (cp) { puts (cp); exit (EXIT_SUCCESS); } /* POSIX prohibits using a fallback technique. */ fprintf (stderr, _("%s: no login name\n"), argv[0]); exit (EXIT_FAILURE); }
/* mkfifo -- make fifo's (named pipes) Copyright (C) 90, 91, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* David MacKenzie <djm@ai.mit.edu> */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include "system.h" #include "error.h" #include "modechange.h" #include "quote.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "mkfifo" #define AUTHORS "David MacKenzie" /* The name this program was run with. */ char *program_name; static struct option const longopts[] = { {"mode", required_argument, NULL, 'm'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; #ifdef S_ISFIFO void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("Usage: %s [OPTION] NAME...\n"), program_name); fputs (_("\ Create named pipes (FIFOs) with the given NAMEs.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ -m, --mode=MODE set permission mode (as in chmod), not a=rw - umask\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } #endif int main (int argc, char **argv) { mode_t newmode; struct mode_change *change; const char *specified_mode; int errors = 0; int optc; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); specified_mode = NULL; #ifndef S_ISFIFO error (4, 0, _("fifo files not supported")); #else while ((optc = getopt_long (argc, argv, "m:", longopts, NULL)) != -1) { switch (optc) { case 0: break; case 'm': specified_mode = optarg; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } if (optind == argc) { error (0, 0, _("too few arguments")); usage (EXIT_FAILURE); } newmode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (specified_mode) { newmode &= ~ umask (0); change = mode_compile (specified_mode, 0); if (change == MODE_INVALID) error (EXIT_FAILURE, 0, _("invalid mode")); else if (change == MODE_MEMORY_EXHAUSTED) xalloc_die (); newmode = mode_adjust (newmode, change); } for (; optind < argc; ++optind) { int fail = mkfifo (argv[optind], newmode); if (fail) error (0, errno, _("cannot create fifo %s"), quote (argv[optind])); /* If the containing directory happens to have a default ACL, chmod ensures the file mode permission bits are still set as desired. */ if (fail == 0 && specified_mode) { fail = chmod (argv[optind], newmode); if (fail) error (0, errno, _("cannot set permissions of fifo %s"), quote (argv[optind])); } if (fail) errors = 1; } exit (errors); #endif }
/* mkdir -- make directories Copyright (C) 90, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* David MacKenzie <djm@ai.mit.edu> */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include "system.h" #include "dirname.h" #include "error.h" #include "makepath.h" #include "modechange.h" #include "quote.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "mkdir" #define AUTHORS "David MacKenzie" /* The name this program was run with. */ char *program_name; /* If nonzero, ensure that all parents of the specified directory exist. */ static int create_parents; static struct option const longopts[] = { {"mode", required_argument, NULL, 'm'}, {"parents", no_argument, NULL, 'p'}, {"verbose", no_argument, NULL, 'v'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("Usage: %s [OPTION] DIRECTORY...\n"), program_name); fputs (_("\ Create the DIRECTORY(ies), if they do not already exist.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ -m, --mode=MODE set permission mode (as in chmod), not rwxrwxrwx - umask\n\ -p, --parents no error if existing, make parent directories as needed\n\ -v, --verbose print a message for each created directory\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } int main (int argc, char **argv) { mode_t newmode; mode_t parent_mode; const char *specified_mode = NULL; const char *verbose_fmt_string = NULL; int errors = 0; int optc; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); create_parents = 0; while ((optc = getopt_long (argc, argv, "pm:v", longopts, NULL)) != -1) { switch (optc) { case 0: /* Long option. */ break; case 'p': create_parents = 1; break; case 'm': specified_mode = optarg; break; case 'v': /* --verbose */ verbose_fmt_string = _("created directory %s"); break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } if (optind == argc) { error (0, 0, _("too few arguments")); usage (EXIT_FAILURE); } newmode = S_IRWXUGO; { mode_t umask_value = umask (0); umask (umask_value); /* Restore the old value. */ parent_mode = (newmode & (~ umask_value)) | S_IWUSR | S_IXUSR; } if (specified_mode) { struct mode_change *change = mode_compile (specified_mode, 0); newmode &= ~ umask (0); if (change == MODE_INVALID) error (EXIT_FAILURE, 0, _("invalid mode %s"), quote (specified_mode)); else if (change == MODE_MEMORY_EXHAUSTED) xalloc_die (); newmode = mode_adjust (newmode, change); } for (; optind < argc; ++optind) { int fail = 0; if (create_parents) { char *dir = argv[optind]; fail = make_path (dir, newmode, parent_mode, -1, -1, 1, verbose_fmt_string); } else { const char *dir = argv[optind]; int dir_created; fail = make_dir (dir, dir, newmode, &dir_created); if (fail) { /* make_dir already gave a diagnostic. */ } else if (!create_parents && !dir_created) { /* make_dir `succeeds' when DIR already exists. In that case, mkdir must fail, unless --parents (-p) was specified. */ error (0, EEXIST, _("cannot create directory %s"), quote (dir)); fail = 1; } else if (verbose_fmt_string) error (0, 0, verbose_fmt_string, quote (dir)); /* mkdir(2) is required to honor only the file permission bits. In particular, it needn't do anything about `special' bits, so if any were set in newmode, apply them with chmod. This extra step is necessary in some cases when the containing directory has a default ACL. */ /* Set the permissions only if this directory has just been created. */ if (fail == 0 && specified_mode && dir_created) { fail = chmod (dir, newmode); if (fail) error (0, errno, _("cannot set permissions of directory %s"), quote (dir)); } } if (fail) errors = 1; } exit (errors); }
#ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif #endif int make_path PARAMS ((const char *_argpath, int _mode, int _parent_mode, uid_t _owner, gid_t _group, int _preserve_existing, const char *_verbose_fmt_string)); int make_dir PARAMS ((const char *dir, const char *dirpath, mode_t mode, int *created_dir_p));
/* mknod -- make special files Copyright (C) 90, 91, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@ai.mit.edu> */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include "system.h" #include "error.h" #include "modechange.h" #include "quote.h" #include "xstrtol.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "mknod" #define AUTHORS "David MacKenzie" /* The name this program was run with. */ char *program_name; static struct option const longopts[] = { {"mode", required_argument, NULL, 'm'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("Usage: %s [OPTION]... NAME TYPE [MAJOR MINOR]\n"), program_name); fputs (_("\ Create the special file NAME of the given TYPE.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ -m, --mode=MODE set permission mode (as in chmod), not a=rw - umask\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ Both MAJOR and MINOR must be specified when TYPE is b, c, or u, and they\n\ must be omitted when TYPE is p. If MAJOR or MINOR begins with 0x or 0X,\n\ it is interpreted as hexadecimal; otherwise, if it begins with 0, as octal;\n\ otherwise, as decimal. TYPE may be:\n\ "), stdout); fputs (_("\ \n\ b create a block (buffered) special file\n\ c, u create a character (unbuffered) special file\n\ p create a FIFO\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } int main (int argc, char **argv) { mode_t newmode; struct mode_change *change; const char *specified_mode; int optc; mode_t node_type; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); specified_mode = NULL; while ((optc = getopt_long (argc, argv, "m:", longopts, NULL)) != -1) { switch (optc) { case 0: break; case 'm': specified_mode = optarg; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } newmode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (specified_mode) { newmode &= ~ umask (0); change = mode_compile (specified_mode, 0); if (change == MODE_INVALID) error (EXIT_FAILURE, 0, _("invalid mode")); else if (change == MODE_MEMORY_EXHAUSTED) xalloc_die (); newmode = mode_adjust (newmode, change); } if (argc - optind != 2 && argc - optind != 4) { const char *msg; if (argc - optind < 2) msg = _("too few arguments"); else if (argc - optind > 4) msg = _("too many arguments"); else msg = _("wrong number of arguments"); error (0, 0, "%s", msg); usage (EXIT_FAILURE); } /* Only check the first character, to allow mnemonic usage like `mknod /dev/rst0 character 18 0'. */ switch (argv[optind + 1][0]) { case 'b': /* `block' or `buffered' */ #ifndef S_IFBLK error (4, 0, _("block special files not supported")); #else node_type = S_IFBLK; #endif goto block_or_character; case 'c': /* `character' */ case 'u': /* `unbuffered' */ #ifndef S_IFCHR error (4, 0, _("character special files not supported")); #else node_type = S_IFCHR; #endif goto block_or_character; block_or_character: if (argc - optind != 4) { error (0, 0, _("\ when creating special files, major and minor device\n\ numbers must be specified")); usage (EXIT_FAILURE); } { char const *s_major = argv[optind + 2]; char const *s_minor = argv[optind + 3]; uintmax_t i_major, i_minor; dev_t device; if (xstrtoumax (s_major, NULL, 0, &i_major, NULL) != LONGINT_OK || i_major != (major_t) i_major) error (EXIT_FAILURE, 0, _("invalid major device number %s"), quote (s_major)); if (xstrtoumax (s_minor, NULL, 0, &i_minor, NULL) != LONGINT_OK || i_minor != (minor_t) i_minor) error (EXIT_FAILURE, 0, _("invalid minor device number %s"), quote (s_minor)); device = makedev (i_major, i_minor); #ifdef NODEV if (device == NODEV) error (EXIT_FAILURE, 0, _("invalid device %s %s"), s_major, s_minor); #endif if (mknod (argv[optind], newmode | node_type, device) != 0) error (EXIT_FAILURE, errno, "%s", quote (argv[optind])); } break; case 'p': /* `pipe' */ #ifndef S_ISFIFO error (4, 0, _("fifo files not supported")); #else if (argc - optind != 2) { error (0, 0, _("\ major and minor device numbers may not be specified for fifo files")); usage (EXIT_FAILURE); } if (mkfifo (argv[optind], newmode)) error (EXIT_FAILURE, errno, "%s", quote (argv[optind])); #endif break; default: error (0, 0, "invalid device type %s", quote (argv[optind + 1])); usage (EXIT_FAILURE); } /* Perform an explicit chmod to ensure the file mode permission bits are set as specified. This extra step is necessary in some cases when the containing directory has a default ACL. */ if (specified_mode) { if (chmod (argv[optind], newmode)) error (0, errno, _("cannot set permissions of %s"), quote (argv[optind])); } exit (EXIT_SUCCESS); }
/* nl -- number lines of files Copyright (C) 89, 92, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Scott Bartram (nancy!scott@uunet.uu.net) Revised by David MacKenzie (djm@gnu.ai.mit.edu) */ #include <config.h> #include <stdio.h> #include <sys/types.h> #include <getopt.h> #include "system.h" #include "closeout.h" #include <regex.h> #include "error.h" #include "linebuffer.h" #include "xstrtol.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "nl" #define AUTHORS N_ ("Scott Bartram and David MacKenzie") #ifndef TRUE # define TRUE 1 # define FALSE 0 #endif /* Line-number formats. */ enum number_format { FORMAT_RIGHT_NOLZ, /* Right justified, no leading zeroes. */ FORMAT_RIGHT_LZ, /* Right justified, leading zeroes. */ FORMAT_LEFT /* Left justified, no leading zeroes. */ }; /* Default section delimiter characters. */ #define DEFAULT_SECTION_DELIMITERS "\\:" /* Types of input lines: either one of the section delimiters, or text to output. */ enum section { Header, Body, Footer, Text }; /* The name this program was run with. */ char *program_name; /* Format of body lines (-b). */ static char *body_type = "t"; /* Format of header lines (-h). */ static char *header_type = "n"; /* Format of footer lines (-f). */ static char *footer_type = "n"; /* Format currently being used (body, header, or footer). */ static char *current_type; /* Regex for body lines to number (-bp). */ static struct re_pattern_buffer body_regex; /* Regex for header lines to number (-hp). */ static struct re_pattern_buffer header_regex; /* Regex for footer lines to number (-fp). */ static struct re_pattern_buffer footer_regex; /* Pointer to current regex, if any. */ static struct re_pattern_buffer *current_regex = NULL; /* Separator string to print after line number (-s). */ static char *separator_str = "\t"; /* Input section delimiter string (-d). */ static char *section_del = DEFAULT_SECTION_DELIMITERS; /* Header delimiter string. */ static char *header_del = NULL; /* Header section delimiter length. */ static size_t header_del_len; /* Body delimiter string. */ static char *body_del = NULL; /* Body section delimiter length. */ static size_t body_del_len; /* Footer delimiter string. */ static char *footer_del = NULL; /* Footer section delimiter length. */ static size_t footer_del_len; /* Input buffer. */ static struct linebuffer line_buf; /* printf format string for line number. */ static char *print_fmt; /* printf format string for unnumbered lines. */ static char *print_no_line_fmt = NULL; /* Starting line number on each page (-v). */ static int starting_line_number = 1; /* Line number increment (-i). */ static int page_incr = 1; /* If TRUE, reset line number at start of each page (-p). */ static int reset_numbers = TRUE; /* Number of blank lines to consider to be one line for numbering (-l). */ static int blank_join = 1; /* Width of line numbers (-w). */ static int lineno_width = 6; /* Line number format (-n). */ static enum number_format lineno_format = FORMAT_RIGHT_NOLZ; /* Current print line number. */ static int line_no; /* Nonzero if we have ever read standard input. */ static int have_read_stdin; static struct option const longopts[] = { {"header-numbering", required_argument, NULL, 'h'}, {"body-numbering", required_argument, NULL, 'b'}, {"footer-numbering", required_argument, NULL, 'f'}, {"starting-line-number", required_argument, NULL, 'v'}, {"page-increment", required_argument, NULL, 'i'}, {"no-renumber", no_argument, NULL, 'p'}, {"join-blank-lines", required_argument, NULL, 'l'}, {"number-separator", required_argument, NULL, 's'}, {"number-width", required_argument, NULL, 'w'}, {"number-format", required_argument, NULL, 'n'}, {"section-delimiter", required_argument, NULL, 'd'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; /* Print a usage message and quit. */ void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... [FILE]...\n\ "), program_name); fputs (_("\ Write each FILE to standard output, with line numbers added.\n\ With no FILE, or when FILE is -, read standard input.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ -b, --body-numbering=STYLE use STYLE for numbering body lines\n\ -d, --section-delimiter=CC use CC for separating logical pages\n\ -f, --footer-numbering=STYLE use STYLE for numbering footer lines\n\ "), stdout); fputs (_("\ -h, --header-numbering=STYLE use STYLE for numbering header lines\n\ -i, --page-increment=NUMBER line number increment at each line\n\ -l, --join-blank-lines=NUMBER group of NUMBER empty lines counted as one\n\ -n, --number-format=FORMAT insert line numbers according to FORMAT\n\ -p, --no-renumber do not reset line numbers at logical pages\n\ -s, --number-separator=STRING add STRING after (possible) line number\n\ "), stdout); fputs (_("\ -v, --first-page=NUMBER first line number on each logical page\n\ -w, --number-width=NUMBER use NUMBER columns for line numbers\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ By default, selects -v1 -i1 -l1 -sTAB -w6 -nrn -hn -bt -fn. CC are\n\ two delimiter characters for separating logical pages, a missing\n\ second character implies :. Type \\\\ for \\. STYLE is one of:\n\ "), stdout); fputs (_("\ \n\ a number all lines\n\ t number only nonempty lines\n\ n number no lines\n\ pREGEXP number only lines that contain a match for REGEXP\n\ \n\ FORMAT is one of:\n\ \n\ ln left justified, no leading zeros\n\ rn right justified, no leading zeros\n\ rz right justified, leading zeros\n\ \n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } /* Build the printf format string, based on `lineno_format'. */ static void build_print_fmt (void) { print_fmt = xmalloc ( 1 /* for `%' */ + 1 /* for `-' or `0' */ + INT_STRLEN_BOUND (lineno_width) + 1 /* for `d' */ + 1 /* for trailing NUL byte */ ); switch (lineno_format) { case FORMAT_RIGHT_NOLZ: sprintf (print_fmt, "%%%dd", lineno_width); break; case FORMAT_RIGHT_LZ: sprintf (print_fmt, "%%0%dd", lineno_width); break; case FORMAT_LEFT: sprintf (print_fmt, "%%-%dd", lineno_width); break; } } /* Set the command line flag TYPEP and possibly the regex pointer REGEXP, according to `optarg'. */ static int build_type_arg (char **typep, struct re_pattern_buffer *regexp) { const char *errmsg; int rval = TRUE; int optlen; switch (*optarg) { case 'a': case 't': case 'n': *typep = optarg; break; case 'p': *typep = optarg++; optlen = strlen (optarg); regexp->allocated = optlen * 2; regexp->buffer = (unsigned char *) xmalloc (regexp->allocated); regexp->translate = NULL; regexp->fastmap = xmalloc (256); regexp->fastmap_accurate = 0; errmsg = re_compile_pattern (optarg, optlen, regexp); if (errmsg) error (EXIT_FAILURE, 0, "%s", errmsg); break; default: rval = FALSE; break; } return rval; } /* Print the line number and separator; increment the line number. */ static void print_lineno (void) { printf (print_fmt, line_no); fputs (separator_str, stdout); line_no += page_incr; } /* Switch to a header section. */ static void proc_header (void) { current_type = header_type; current_regex = &header_regex; if (reset_numbers) line_no = starting_line_number; putchar ('\n'); } /* Switch to a body section. */ static void proc_body (void) { current_type = body_type; current_regex = &body_regex; putchar ('\n'); } /* Switch to a footer section. */ static void proc_footer (void) { current_type = footer_type; current_regex = &footer_regex; putchar ('\n'); } /* Process a regular text line in `line_buf'. */ static void proc_text (void) { static int blank_lines = 0; /* Consecutive blank lines so far. */ switch (*current_type) { case 'a': if (blank_join > 1) { if (1 < line_buf.length || ++blank_lines == blank_join) { print_lineno (); blank_lines = 0; } else puts (print_no_line_fmt); } else print_lineno (); break; case 't': if (1 < line_buf.length) print_lineno (); else puts (print_no_line_fmt); break; case 'n': puts (print_no_line_fmt); break; case 'p': if (re_search (current_regex, line_buf.buffer, line_buf.length - 1, 0, line_buf.length - 1, (struct re_registers *) 0) < 0) puts (print_no_line_fmt); else print_lineno (); break; } fwrite (line_buf.buffer, sizeof (char), line_buf.length, stdout); } /* Return the type of line in `line_buf'. */ static enum section check_section (void) { size_t len = line_buf.length - 1; if (len < 2 || memcmp (line_buf.buffer, section_del, 2)) return Text; if (len == header_del_len && !memcmp (line_buf.buffer, header_del, header_del_len)) return Header; if (len == body_del_len && !memcmp (line_buf.buffer, body_del, body_del_len)) return Body; if (len == footer_del_len && !memcmp (line_buf.buffer, footer_del, footer_del_len)) return Footer; return Text; } /* Read and process the file pointed to by FP. */ static void process_file (FILE *fp) { while (readline (&line_buf, fp)) { switch ((int) check_section ()) { case Header: proc_header (); break; case Body: proc_body (); break; case Footer: proc_footer (); break; case Text: proc_text (); break; } } } /* Process file FILE to standard output. Return 0 if successful, 1 if not. */ static int nl_file (const char *file) { FILE *stream; if (STREQ (file, "-")) { have_read_stdin = 1; stream = stdin; } else { stream = fopen (file, "r"); if (stream == NULL) { error (0, errno, "%s", file); return 1; } } process_file (stream); if (ferror (stream)) { error (0, errno, "%s", file); return 1; } if (STREQ (file, "-")) clearerr (stream); /* Also clear EOF. */ else if (fclose (stream) == EOF) { error (0, errno, "%s", file); return 1; } return 0; } int main (int argc, char **argv) { int c, exit_status = 0; size_t len; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); have_read_stdin = 0; while ((c = getopt_long (argc, argv, "h:b:f:v:i:pl:s:w:n:d:", longopts, NULL)) != -1) { switch (c) { case 0: break; case 'h': if (build_type_arg (&header_type, &header_regex) != TRUE) usage (2); break; case 'b': if (build_type_arg (&body_type, &body_regex) != TRUE) usage (2); break; case 'f': if (build_type_arg (&footer_type, &footer_regex) != TRUE) usage (2); break; case 'v': { long int tmp_long; if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK /* Allow it to be negative. */ || tmp_long > INT_MAX) error (EXIT_FAILURE, 0, _("invalid starting line number: `%s'"), optarg); starting_line_number = (int) tmp_long; } break; case 'i': { long int tmp_long; if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK || tmp_long <= 0 || tmp_long > INT_MAX) error (EXIT_FAILURE, 0, _("invalid line number increment: `%s'"), optarg); page_incr = (int) tmp_long; } break; case 'p': reset_numbers = FALSE; break; case 'l': { long int tmp_long; if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK || tmp_long <= 0 || tmp_long > INT_MAX) error (EXIT_FAILURE, 0, _("invalid number of blank lines: `%s'"), optarg); blank_join = (int) tmp_long; } break; case 's': separator_str = optarg; break; case 'w': { long int tmp_long; if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK || tmp_long <= 0 || tmp_long > INT_MAX) error (EXIT_FAILURE, 0, _("invalid line number field width: `%s'"), optarg); lineno_width = (int) tmp_long; } break; case 'n': switch (*optarg) { case 'l': if (optarg[1] == 'n') lineno_format = FORMAT_LEFT; else usage (2); break; case 'r': switch (optarg[1]) { case 'n': lineno_format = FORMAT_RIGHT_NOLZ; break; case 'z': lineno_format = FORMAT_RIGHT_LZ; break; default: usage (2); break; } break; default: usage (2); break; } break; case 'd': section_del = optarg; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (2); break; } } /* Initialize the section delimiters. */ len = strlen (section_del); header_del_len = len * 3; header_del = xmalloc (header_del_len + 1); strcat (strcat (strcpy (header_del, section_del), section_del), section_del); body_del_len = len * 2; body_del = xmalloc (body_del_len + 1); strcat (strcpy (body_del, section_del), section_del); footer_del_len = len; footer_del = xmalloc (footer_del_len + 1); strcpy (footer_del, section_del); /* Initialize the input buffer. */ initbuffer (&line_buf); /* Initialize the printf format for unnumbered lines. */ len = strlen (separator_str); print_no_line_fmt = xmalloc (lineno_width + len + 1); memset (print_no_line_fmt, ' ', lineno_width + len); print_no_line_fmt[lineno_width + len] = '\0'; line_no = starting_line_number; current_type = body_type; current_regex = &body_regex; build_print_fmt (); /* Main processing. */ if (optind == argc) exit_status |= nl_file ("-"); else for (; optind < argc; optind++) exit_status |= nl_file (argv[optind]); if (have_read_stdin && fclose (stdin) == EOF) { error (0, errno, "-"); exit_status = 1; } exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); }
/* od -- dump files in octal and other formats Copyright (C) 92, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Jim Meyering. */ #include <config.h> #include <stdio.h> #include <assert.h> #include <getopt.h> #include <sys/types.h> #include "system.h" #include "closeout.h" #include "error.h" #include "posixver.h" #include "xstrtol.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "od" #define AUTHORS "Jim Meyering" #if defined(__GNUC__) || defined(STDC_HEADERS) # include <float.h> #endif #ifdef HAVE_LONG_DOUBLE typedef long double LONG_DOUBLE; #else typedef double LONG_DOUBLE; #endif /* The default number of input bytes per output line. */ #define DEFAULT_BYTES_PER_BLOCK 16 /* The number of decimal digits of precision in a float. */ #ifndef FLT_DIG # define FLT_DIG 7 #endif /* The number of decimal digits of precision in a double. */ #ifndef DBL_DIG # define DBL_DIG 15 #endif /* The number of decimal digits of precision in a long double. */ #ifndef LDBL_DIG # define LDBL_DIG DBL_DIG #endif #if HAVE_UNSIGNED_LONG_LONG typedef unsigned long long ulonglong_t; #else /* This is just a place-holder to avoid a few `#if' directives. In this case, the type isn't actually used. */ typedef unsigned long int ulonglong_t; #endif enum size_spec { NO_SIZE, CHAR, SHORT, INT, LONG, LONG_LONG, /* FIXME: add INTMAX support, too */ FLOAT_SINGLE, FLOAT_DOUBLE, FLOAT_LONG_DOUBLE, N_SIZE_SPECS }; enum output_format { SIGNED_DECIMAL, UNSIGNED_DECIMAL, OCTAL, HEXADECIMAL, FLOATING_POINT, NAMED_CHARACTER, CHARACTER }; /* Each output format specification (from `-t spec' or from old-style options) is represented by one of these structures. */ struct tspec { enum output_format fmt; enum size_spec size; void (*print_function) (size_t, const char *, const char *); char *fmt_string; int hexl_mode_trailer; int field_width; }; /* The name this program was run with. */ char *program_name; /* Convert the number of 8-bit bytes of a binary representation to the number of characters (digits + sign if the type is signed) required to represent the same quantity in the specified base/type. For example, a 32-bit (4-byte) quantity may require a field width as wide as the following for these types: 11 unsigned octal 11 signed decimal 10 unsigned decimal 8 unsigned hexadecimal */ static const unsigned int bytes_to_oct_digits[] = {0, 3, 6, 8, 11, 14, 16, 19, 22, 25, 27, 30, 32, 35, 38, 41, 43}; static const unsigned int bytes_to_signed_dec_digits[] = {1, 4, 6, 8, 11, 13, 16, 18, 20, 23, 25, 28, 30, 33, 35, 37, 40}; static const unsigned int bytes_to_unsigned_dec_digits[] = {0, 3, 5, 8, 10, 13, 15, 17, 20, 22, 25, 27, 29, 32, 34, 37, 39}; static const unsigned int bytes_to_hex_digits[] = {0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32}; /* Convert enum size_spec to the size of the named type. */ static const int width_bytes[] = { -1, sizeof (char), sizeof (short int), sizeof (int), sizeof (long int), sizeof (ulonglong_t), sizeof (float), sizeof (double), sizeof (LONG_DOUBLE) }; /* Ensure that for each member of `enum size_spec' there is an initializer in the width_bytes array. */ struct dummy { int assert_width_bytes_matches_size_spec_decl [sizeof width_bytes / sizeof width_bytes[0] == N_SIZE_SPECS ? 1 : -1]; }; /* Names for some non-printing characters. */ static const char *const charname[33] = { "nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel", "bs", "ht", "nl", "vt", "ff", "cr", "so", "si", "dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb", "can", "em", "sub", "esc", "fs", "gs", "rs", "us", "sp" }; /* Address base (8, 10 or 16). */ static int address_base; /* The number of octal digits required to represent the largest address value. */ #define MAX_ADDRESS_LENGTH \ ((sizeof (uintmax_t) * CHAR_BIT + CHAR_BIT - 1) / 3) /* Width of a normal address. */ static int address_pad_len; static size_t string_min; static int flag_dump_strings; /* Non-zero if we should recognize the older non-option arguments that specified at most one file and optional arguments specifying offset and pseudo-start address. */ static int traditional; /* Non-zero if an old-style `pseudo-address' was specified. */ static int flag_pseudo_start; /* The difference between the old-style pseudo starting address and the number of bytes to skip. */ static uintmax_t pseudo_offset; /* Function that accepts an address and an optional following char, and prints the address and char to stdout. */ static void (*format_address) (uintmax_t, char); /* The number of input bytes to skip before formatting and writing. */ static uintmax_t n_bytes_to_skip = 0; /* When zero, MAX_BYTES_TO_FORMAT and END_OFFSET are ignored, and all input is formatted. */ static int limit_bytes_to_format = 0; /* The maximum number of bytes that will be formatted. */ static uintmax_t max_bytes_to_format; /* The offset of the first byte after the last byte to be formatted. */ static uintmax_t end_offset; /* When nonzero and two or more consecutive blocks are equal, format only the first block and output an asterisk alone on the following line to indicate that identical blocks have been elided. */ static int abbreviate_duplicate_blocks = 1; /* An array of specs describing how to format each input block. */ static struct tspec *spec; /* The number of format specs. */ static size_t n_specs; /* The allocated length of SPEC. */ static size_t n_specs_allocated; /* The number of input bytes formatted per output line. It must be a multiple of the least common multiple of the sizes associated with the specified output types. It should be as large as possible, but no larger than 16 -- unless specified with the -w option. */ static size_t bytes_per_block; /* Human-readable representation of *file_list (for error messages). It differs from *file_list only when *file_list is "-". */ static char const *input_filename; /* A NULL-terminated list of the file-arguments from the command line. */ static char const *const *file_list; /* Initializer for file_list if no file-arguments were specified on the command line. */ static char const *const default_file_list[] = {"-", NULL}; /* The input stream associated with the current file. */ static FILE *in_stream; /* If nonzero, at least one of the files we read was standard input. */ static int have_read_stdin; #define MAX_INTEGRAL_TYPE_SIZE sizeof (ulonglong_t) static enum size_spec integral_type_size[MAX_INTEGRAL_TYPE_SIZE + 1]; #define MAX_FP_TYPE_SIZE sizeof(LONG_DOUBLE) static enum size_spec fp_type_size[MAX_FP_TYPE_SIZE + 1]; #define COMMON_SHORT_OPTIONS "A:N:abcdfhij:lot:vx" /* For long options that have no equivalent short option, use a non-character as a pseudo short option, starting with CHAR_MAX + 1. */ enum { TRADITIONAL_OPTION = CHAR_MAX + 1 }; static struct option const long_options[] = { {"skip-bytes", required_argument, NULL, 'j'}, {"address-radix", required_argument, NULL, 'A'}, {"read-bytes", required_argument, NULL, 'N'}, {"format", required_argument, NULL, 't'}, {"output-duplicates", no_argument, NULL, 'v'}, {"strings", optional_argument, NULL, 's'}, {"traditional", no_argument, NULL, TRADITIONAL_OPTION}, {"width", optional_argument, NULL, 'w'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... [FILE]...\n\ or: %s --traditional [FILE] [[+]OFFSET [[+]LABEL]]\n\ "), program_name, program_name); fputs (_("\n\ Write an unambiguous representation, octal bytes by default,\n\ of FILE to standard output. With more than one FILE argument,\n\ concatenate them in the listed order to form the input.\n\ With no FILE, or when FILE is -, read standard input.\n\ \n\ "), stdout); fputs (_("\ All arguments to long options are mandatory for short options.\n\ "), stdout); fputs (_("\ -A, --address-radix=RADIX decide how file offsets are printed\n\ -j, --skip-bytes=BYTES skip BYTES input bytes first\n\ "), stdout); fputs (_("\ -N, --read-bytes=BYTES limit dump to BYTES input bytes\n\ -s, --strings[=BYTES] output strings of at least BYTES graphic chars\n\ -t, --format=TYPE select output format or formats\n\ -v, --output-duplicates do not use * to mark line suppression\n\ -w, --width[=BYTES] output BYTES bytes per output line\n\ --traditional accept arguments in traditional form\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ Traditional format specifications may be intermixed; they accumulate:\n\ -a same as -t a, select named characters\n\ -b same as -t oC, select octal bytes\n\ -c same as -t c, select ASCII characters or backslash escapes\n\ -d same as -t u2, select unsigned decimal shorts\n\ "), stdout); fputs (_("\ -f same as -t fF, select floats\n\ -h same as -t x2, select hexadecimal shorts\n\ -i same as -t d2, select decimal shorts\n\ -l same as -t d4, select decimal longs\n\ -o same as -t o2, select octal shorts\n\ -x same as -t x2, select hexadecimal shorts\n\ "), stdout); fputs (_("\ \n\ For older syntax (second call format), OFFSET means -j OFFSET. LABEL\n\ is the pseudo-address at first byte printed, incremented when dump is\n\ progressing. For OFFSET and LABEL, a 0x or 0X prefix indicates\n\ hexadecimal, suffixes may be . for octal and b for multiply by 512.\n\ \n\ TYPE is made up of one or more of these specifications:\n\ \n\ a named character\n\ c ASCII character or backslash escape\n\ "), stdout); fputs (_("\ d[SIZE] signed decimal, SIZE bytes per integer\n\ f[SIZE] floating point, SIZE bytes per integer\n\ o[SIZE] octal, SIZE bytes per integer\n\ u[SIZE] unsigned decimal, SIZE bytes per integer\n\ x[SIZE] hexadecimal, SIZE bytes per integer\n\ "), stdout); fputs (_("\ \n\ SIZE is a number. For TYPE in doux, SIZE may also be C for\n\ sizeof(char), S for sizeof(short), I for sizeof(int) or L for\n\ sizeof(long). If TYPE is f, SIZE may also be F for sizeof(float), D\n\ for sizeof(double) or L for sizeof(long double).\n\ "), stdout); fputs (_("\ \n\ RADIX is d for decimal, o for octal, x for hexadecimal or n for none.\n\ BYTES is hexadecimal with 0x or 0X prefix, it is multiplied by 512\n\ with b suffix, by 1024 with k and by 1048576 with m. Adding a z suffix to\n\ any type adds a display of printable characters to the end of each line\n\ of output. \ "), stdout); fputs (_("\ --string without a number implies 3. --width without a number\n\ implies 32. By default, od uses -A o -t d2 -w 16.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } /* Compute the greatest common denominator of U and V using Euclid's algorithm. */ static unsigned int gcd (unsigned int u, unsigned int v) { unsigned int t; while (v != 0) { t = u % v; u = v; v = t; } return u; } /* Compute the least common multiple of U and V. */ static unsigned int lcm (unsigned int u, unsigned int v) { unsigned int t = gcd (u, v); if (t == 0) return 0; return u * v / t; } static void print_s_char (size_t n_bytes, const char *block, const char *fmt_string) { size_t i; for (i = n_bytes; i > 0; i--) { int tmp = (unsigned) *(const unsigned char *) block; if (tmp > SCHAR_MAX) tmp -= SCHAR_MAX - SCHAR_MIN + 1; assert (tmp <= SCHAR_MAX); printf (fmt_string, tmp); block += sizeof (unsigned char); } } static void print_char (size_t n_bytes, const char *block, const char *fmt_string) { size_t i; for (i = n_bytes; i > 0; i--) { unsigned int tmp = *(const unsigned char *) block; printf (fmt_string, tmp); block += sizeof (unsigned char); } } static void print_s_short (size_t n_bytes, const char *block, const char *fmt_string) { size_t i; for (i = n_bytes / sizeof (unsigned short); i > 0; i--) { int tmp = (unsigned) *(const unsigned short *) block; if (tmp > SHRT_MAX) tmp -= SHRT_MAX - SHRT_MIN + 1; assert (tmp <= SHRT_MAX); printf (fmt_string, tmp); block += sizeof (unsigned short); } } static void print_short (size_t n_bytes, const char *block, const char *fmt_string) { size_t i; for (i = n_bytes / sizeof (unsigned short); i > 0; i--) { unsigned int tmp = *(const unsigned short *) block; printf (fmt_string, tmp); block += sizeof (unsigned short); } } static void print_int (size_t n_bytes, const char *block, const char *fmt_string) { size_t i; for (i = n_bytes / sizeof (unsigned int); i > 0; i--) { unsigned int tmp = *(const unsigned int *) block; printf (fmt_string, tmp); block += sizeof (unsigned int); } } static void print_long (size_t n_bytes, const char *block, const char *fmt_string) { size_t i; for (i = n_bytes / sizeof (unsigned long); i > 0; i--) { unsigned long tmp = *(const unsigned long *) block; printf (fmt_string, tmp); block += sizeof (unsigned long); } } static void print_long_long (size_t n_bytes, const char *block, const char *fmt_string) { size_t i; for (i = n_bytes / sizeof (ulonglong_t); i > 0; i--) { ulonglong_t tmp = *(const ulonglong_t *) block; printf (fmt_string, tmp); block += sizeof (ulonglong_t); } } static void print_float (size_t n_bytes, const char *block, const char *fmt_string) { size_t i; for (i = n_bytes / sizeof (float); i > 0; i--) { float tmp = *(const float *) block; printf (fmt_string, tmp); block += sizeof (float); } } static void print_double (size_t n_bytes, const char *block, const char *fmt_string) { size_t i; for (i = n_bytes / sizeof (double); i > 0; i--) { double tmp = *(const double *) block; printf (fmt_string, tmp); block += sizeof (double); } } #ifdef HAVE_LONG_DOUBLE static void print_long_double (size_t n_bytes, const char *block, const char *fmt_string) { size_t i; for (i = n_bytes / sizeof (LONG_DOUBLE); i > 0; i--) { LONG_DOUBLE tmp = *(const LONG_DOUBLE *) block; printf (fmt_string, tmp); block += sizeof (LONG_DOUBLE); } } #endif static void dump_hexl_mode_trailer (size_t n_bytes, const char *block) { size_t i; fputs (" >", stdout); for (i = n_bytes; i > 0; i--) { unsigned int c = *(const unsigned char *) block; unsigned int c2 = (ISPRINT(c) ? c : '.'); putchar (c2); block += sizeof (unsigned char); } putchar ('<'); } static void print_named_ascii (size_t n_bytes, const char *block, const char *unused_fmt_string ATTRIBUTE_UNUSED) { size_t i; for (i = n_bytes; i > 0; i--) { unsigned int c = *(const unsigned char *) block; unsigned int masked_c = (0x7f & c); const char *s; char buf[5]; if (masked_c == 127) s = "del"; else if (masked_c <= 040) s = charname[masked_c]; else { sprintf (buf, " %c", masked_c); s = buf; } printf (" %3s", s); block += sizeof (unsigned char); } } static void print_ascii (size_t n_bytes, const char *block, const char *unused_fmt_string ATTRIBUTE_UNUSED) { size_t i; for (i = n_bytes; i > 0; i--) { unsigned int c = *(const unsigned char *) block; const char *s; char buf[5]; switch (c) { case '\0': s = " \\0"; break; case '\007': s = " \\a"; break; case '\b': s = " \\b"; break; case '\f': s = " \\f"; break; case '\n': s = " \\n"; break; case '\r': s = " \\r"; break; case '\t': s = " \\t"; break; case '\v': s = " \\v"; break; default: sprintf (buf, (ISPRINT (c) ? " %c" : "%03o"), c); s = (const char *) buf; } printf (" %3s", s); block += sizeof (unsigned char); } } /* Convert a null-terminated (possibly zero-length) string S to an unsigned long integer value. If S points to a non-digit set *P to S, *VAL to 0, and return 0. Otherwise, accumulate the integer value of the string of digits. If the string of digits represents a value larger than ULONG_MAX, don't modify *VAL or *P and return nonzero. Otherwise, advance *P to the first non-digit after S, set *VAL to the result of the conversion and return zero. */ static int simple_strtoul (const char *s, const char **p, long unsigned int *val) { unsigned long int sum; sum = 0; while (ISDIGIT (*s)) { unsigned int c = *s++ - '0'; if (sum > (ULONG_MAX - c) / 10) return 1; sum = sum * 10 + c; } *p = s; *val = sum; return 0; } /* If S points to a single valid modern od format string, put a description of that format in *TSPEC, make *NEXT point at the character following the just-decoded format (if *NEXT is non-NULL), and return zero. If S is not valid, don't modify *NEXT or *TSPEC, give a diagnostic, and return nonzero. For example, if S were "d4afL" *NEXT would be set to "afL" and *TSPEC would be { fmt = SIGNED_DECIMAL; size = INT or LONG; (whichever integral_type_size[4] resolves to) print_function = print_int; (assuming size == INT) fmt_string = "%011d%c"; } S_ORIG is solely for reporting errors. It should be the full format string argument. */ static int decode_one_format (const char *s_orig, const char *s, const char **next, struct tspec *tspec) { enum size_spec size_spec; unsigned long int size; enum output_format fmt; const char *pre_fmt_string; char *fmt_string; void (*print_function) (size_t, const char *, const char *); const char *p; unsigned int c; unsigned int field_width = 0; assert (tspec != NULL); switch (*s) { case 'd': case 'o': case 'u': case 'x': c = *s; ++s; switch (*s) { case 'C': ++s; size = sizeof (char); break; case 'S': ++s; size = sizeof (short); break; case 'I': ++s; size = sizeof (int); break; case 'L': ++s; size = sizeof (long int); break; default: if (simple_strtoul (s, &p, &size) != 0) { /* The integer at P in S would overflow an unsigned long. A digit string that long is sufficiently odd looking that the following diagnostic is sufficient. */ error (0, 0, _("invalid type string `%s'"), s_orig); return 1; } if (p == s) size = sizeof (int); else { if (MAX_INTEGRAL_TYPE_SIZE < size || integral_type_size[size] == NO_SIZE) { error (0, 0, _("invalid type string `%s';\n\ this system doesn't provide a %lu-byte integral type"), s_orig, size); return 1; } s = p; } break; } #define ISPEC_TO_FORMAT(Spec, Min_format, Long_format, Max_format) \ ((Spec) == LONG_LONG ? (Max_format) \ : ((Spec) == LONG ? (Long_format) \ : (Min_format))) \ #define FMT_BYTES_ALLOCATED 9 fmt_string = xmalloc (FMT_BYTES_ALLOCATED); size_spec = integral_type_size[size]; switch (c) { case 'd': fmt = SIGNED_DECIMAL; sprintf (fmt_string, " %%%u%s", (field_width = bytes_to_signed_dec_digits[size]), ISPEC_TO_FORMAT (size_spec, "d", "ld", PRIdMAX)); break; case 'o': fmt = OCTAL; sprintf (fmt_string, " %%0%u%s", (field_width = bytes_to_oct_digits[size]), ISPEC_TO_FORMAT (size_spec, "o", "lo", PRIoMAX)); break; case 'u': fmt = UNSIGNED_DECIMAL; sprintf (fmt_string, " %%%u%s", (field_width = bytes_to_unsigned_dec_digits[size]), ISPEC_TO_FORMAT (size_spec, "u", "lu", PRIuMAX)); break; case 'x': fmt = HEXADECIMAL; sprintf (fmt_string, " %%0%u%s", (field_width = bytes_to_hex_digits[size]), ISPEC_TO_FORMAT (size_spec, "x", "lx", PRIxMAX)); break; default: abort (); } assert (strlen (fmt_string) < FMT_BYTES_ALLOCATED); switch (size_spec) { case CHAR: print_function = (fmt == SIGNED_DECIMAL ? print_s_char : print_char); break; case SHORT: print_function = (fmt == SIGNED_DECIMAL ? print_s_short : print_short); break; case INT: print_function = print_int; break; case LONG: print_function = print_long; break; case LONG_LONG: print_function = print_long_long; break; default: abort (); } break; case 'f': fmt = FLOATING_POINT; ++s; switch (*s) { case 'F': ++s; size = sizeof (float); break; case 'D': ++s; size = sizeof (double); break; case 'L': ++s; size = sizeof (LONG_DOUBLE); break; default: if (simple_strtoul (s, &p, &size) != 0) { /* The integer at P in S would overflow an unsigned long. A digit string that long is sufficiently odd looking that the following diagnostic is sufficient. */ error (0, 0, _("invalid type string `%s'"), s_orig); return 1; } if (p == s) size = sizeof (double); else { if (size > MAX_FP_TYPE_SIZE || fp_type_size[size] == NO_SIZE) { error (0, 0, _("invalid type string `%s';\n\ this system doesn't provide a %lu-byte floating point type"), s_orig, size); return 1; } s = p; } break; } size_spec = fp_type_size[size]; switch (size_spec) { case FLOAT_SINGLE: print_function = print_float; /* Don't use %#e; not all systems support it. */ pre_fmt_string = " %%%d.%de"; fmt_string = xmalloc (strlen (pre_fmt_string)); sprintf (fmt_string, pre_fmt_string, (field_width = FLT_DIG + 8), FLT_DIG); break; case FLOAT_DOUBLE: print_function = print_double; pre_fmt_string = " %%%d.%de"; fmt_string = xmalloc (strlen (pre_fmt_string)); sprintf (fmt_string, pre_fmt_string, (field_width = DBL_DIG + 8), DBL_DIG); break; #ifdef HAVE_LONG_DOUBLE case FLOAT_LONG_DOUBLE: print_function = print_long_double; pre_fmt_string = " %%%d.%dLe"; fmt_string = xmalloc (strlen (pre_fmt_string)); sprintf (fmt_string, pre_fmt_string, (field_width = LDBL_DIG + 8), LDBL_DIG); break; #endif default: abort (); } break; case 'a': ++s; fmt = NAMED_CHARACTER; size_spec = CHAR; fmt_string = NULL; print_function = print_named_ascii; field_width = 3; break; case 'c': ++s; fmt = CHARACTER; size_spec = CHAR; fmt_string = NULL; print_function = print_ascii; field_width = 3; break; default: error (0, 0, _("invalid character `%c' in type string `%s'"), *s, s_orig); return 1; } tspec->size = size_spec; tspec->fmt = fmt; tspec->print_function = print_function; tspec->fmt_string = fmt_string; tspec->field_width = field_width; tspec->hexl_mode_trailer = (*s == 'z'); if (tspec->hexl_mode_trailer) s++; if (next != NULL) *next = s; return 0; } /* Given a list of one or more input filenames FILE_LIST, set the global file pointer IN_STREAM and the global string INPUT_FILENAME to the first one that can be successfully opened. Modify FILE_LIST to reference the next filename in the list. A file name of "-" is interpreted as standard input. If any file open fails, give an error message and return nonzero. */ static int open_next_file (void) { int err = 0; do { input_filename = *file_list; if (input_filename == NULL) return err; ++file_list; if (STREQ (input_filename, "-")) { input_filename = _("standard input"); in_stream = stdin; have_read_stdin = 1; } else { in_stream = fopen (input_filename, "r"); if (in_stream == NULL) { error (0, errno, "%s", input_filename); err = 1; } } } while (in_stream == NULL); if (limit_bytes_to_format && !flag_dump_strings) SETVBUF (in_stream, NULL, _IONBF, 0); SET_BINARY (fileno (in_stream)); return err; } /* Test whether there have been errors on in_stream, and close it if it is not standard input. Return nonzero if there has been an error on in_stream or stdout; return zero otherwise. This function will report more than one error only if both a read and a write error have occurred. */ static int check_and_close (void) { int err = 0; if (in_stream != NULL) { if (ferror (in_stream)) { error (0, errno, "%s", input_filename); if (in_stream != stdin) fclose (in_stream); err = 1; } else if (in_stream != stdin && fclose (in_stream) == EOF) { error (0, errno, "%s", input_filename); err = 1; } in_stream = NULL; } if (ferror (stdout)) { error (0, errno, _("standard output")); err = 1; } return err; } /* Decode the modern od format string S. Append the decoded representation to the global array SPEC, reallocating SPEC if necessary. Return zero if S is valid, nonzero otherwise. */ static int decode_format_string (const char *s) { const char *s_orig = s; assert (s != NULL); while (*s != '\0') { struct tspec tspec; const char *next; if (decode_one_format (s_orig, s, &next, &tspec)) return 1; assert (s != next); s = next; if (n_specs >= n_specs_allocated) { n_specs_allocated = 1 + (3 * n_specs_allocated) / 2; spec = (struct tspec *) xrealloc ((char *) spec, (n_specs_allocated * sizeof (struct tspec))); } memcpy ((char *) &spec[n_specs], (char *) &tspec, sizeof (struct tspec)); ++n_specs; } return 0; } /* Given a list of one or more input filenames FILE_LIST, set the global file pointer IN_STREAM to position N_SKIP in the concatenation of those files. If any file operation fails or if there are fewer than N_SKIP bytes in the combined input, give an error message and return nonzero. When possible, use seek rather than read operations to advance IN_STREAM. */ static int skip (uintmax_t n_skip) { int err = 0; if (n_skip == 0) return 0; while (in_stream != NULL) /* EOF. */ { struct stat file_stats; /* First try seeking. For large offsets, this extra work is worthwhile. If the offset is below some threshold it may be more efficient to move the pointer by reading. There are two issues when trying to seek: - the file must be seekable. - before seeking to the specified position, make sure that the new position is in the current file. Try to do that by getting file's size using fstat. But that will work only for regular files. */ if (fstat (fileno (in_stream), &file_stats) == 0) { /* The st_size field is valid only for regular files (and for symbolic links, which cannot occur here). If the number of bytes left to skip is at least as large as the size of the current file, we can decrement n_skip and go on to the next file. */ if (S_ISREG (file_stats.st_mode) && 0 <= file_stats.st_size) { if ((uintmax_t) file_stats.st_size <= n_skip) n_skip -= file_stats.st_size; else { if (fseeko (in_stream, n_skip, SEEK_CUR) != 0) { error (0, errno, "%s", input_filename); err = 1; } n_skip = 0; } } /* If it's not a regular file with nonnegative size, position the file pointer by reading. */ else { char buf[BUFSIZ]; size_t n_bytes_read, n_bytes_to_read = BUFSIZ; while (0 < n_skip) { if (n_skip < n_bytes_to_read) n_bytes_to_read = n_skip; n_bytes_read = fread (buf, 1, n_bytes_to_read, in_stream); n_skip -= n_bytes_read; if (n_bytes_read != n_bytes_to_read) break; } } if (n_skip == 0) break; } else /* cannot fstat() file */ { error (0, errno, "%s", input_filename); err = 1; } err |= check_and_close (); err |= open_next_file (); } if (n_skip != 0) error (EXIT_FAILURE, 0, _("cannot skip past end of combined input")); return err; } static void format_address_none (uintmax_t address ATTRIBUTE_UNUSED, char c ATTRIBUTE_UNUSED) { } static void format_address_std (uintmax_t address, char c) { char buf[MAX_ADDRESS_LENGTH + 2]; char *p = buf + sizeof buf; char const *pbound; *--p = '\0'; *--p = c; pbound = p - address_pad_len; /* Use a special case of the code for each base. This is measurably faster than generic code. */ switch (address_base) { case 8: do *--p = '0' + (address & 7); while ((address >>= 3) != 0); break; case 10: do *--p = '0' + (address % 10); while ((address /= 10) != 0); break; case 16: do *--p = "0123456789abcdef"[address & 15]; while ((address >>= 4) != 0); break; } while (pbound < p) *--p = '0'; fputs (p, stdout); } static void format_address_paren (uintmax_t address, char c) { putchar ('('); format_address_std (address, ')'); putchar (c); } static void format_address_label (uintmax_t address, char c) { format_address_std (address, ' '); format_address_paren (address + pseudo_offset, c); } /* Write N_BYTES bytes from CURR_BLOCK to standard output once for each of the N_SPEC format specs. CURRENT_OFFSET is the byte address of CURR_BLOCK in the concatenation of input files, and it is printed (optionally) only before the output line associated with the first format spec. When duplicate blocks are being abbreviated, the output for a sequence of identical input blocks is the output for the first block followed by an asterisk alone on a line. It is valid to compare the blocks PREV_BLOCK and CURR_BLOCK only when N_BYTES == BYTES_PER_BLOCK. That condition may be false only for the last input block -- and then only when it has not been padded to length BYTES_PER_BLOCK. */ static void write_block (uintmax_t current_offset, size_t n_bytes, const char *prev_block, const char *curr_block) { static int first = 1; static int prev_pair_equal = 0; #define EQUAL_BLOCKS(b1, b2) (memcmp ((b1), (b2), bytes_per_block) == 0) if (abbreviate_duplicate_blocks && !first && n_bytes == bytes_per_block && EQUAL_BLOCKS (prev_block, curr_block)) { if (prev_pair_equal) { /* The two preceding blocks were equal, and the current block is the same as the last one, so print nothing. */ } else { printf ("*\n"); prev_pair_equal = 1; } } else { size_t i; prev_pair_equal = 0; for (i = 0; i < n_specs; i++) { if (i == 0) format_address (current_offset, '\0'); else printf ("%*s", address_pad_len, ""); (*spec[i].print_function) (n_bytes, curr_block, spec[i].fmt_string); if (spec[i].hexl_mode_trailer) { /* space-pad out to full line width, then dump the trailer */ int datum_width = width_bytes[spec[i].size]; int blank_fields = (bytes_per_block - n_bytes) / datum_width; int field_width = spec[i].field_width + 1; printf ("%*s", blank_fields * field_width, ""); dump_hexl_mode_trailer (n_bytes, curr_block); } putchar ('\n'); } } first = 0; } /* Read a single byte into *C from the concatenation of the input files named in the global array FILE_LIST. On the first call to this function, the global variable IN_STREAM is expected to be an open stream associated with the input file INPUT_FILENAME. If IN_STREAM is at end-of-file, close it and update the global variables IN_STREAM and INPUT_FILENAME so they correspond to the next file in the list. Then try to read a byte from the newly opened file. Repeat if necessary until EOF is reached for the last file in FILE_LIST, then set *C to EOF and return. Subsequent calls do likewise. The return value is nonzero if any errors occured, zero otherwise. */ static int read_char (int *c) { int err = 0; *c = EOF; while (in_stream != NULL) /* EOF. */ { *c = fgetc (in_stream); if (*c != EOF) break; err |= check_and_close (); err |= open_next_file (); } return err; } /* Read N bytes into BLOCK from the concatenation of the input files named in the global array FILE_LIST. On the first call to this function, the global variable IN_STREAM is expected to be an open stream associated with the input file INPUT_FILENAME. If all N bytes cannot be read from IN_STREAM, close IN_STREAM and update the global variables IN_STREAM and INPUT_FILENAME. Then try to read the remaining bytes from the newly opened file. Repeat if necessary until EOF is reached for the last file in FILE_LIST. On subsequent calls, don't modify BLOCK and return zero. Set *N_BYTES_IN_BUFFER to the number of bytes read. If an error occurs, it will be detected through ferror when the stream is about to be closed. If there is an error, give a message but continue reading as usual and return nonzero. Otherwise return zero. */ static int read_block (size_t n, char *block, size_t *n_bytes_in_buffer) { int err = 0; assert (0 < n && n <= bytes_per_block); *n_bytes_in_buffer = 0; if (n == 0) return 0; while (in_stream != NULL) /* EOF. */ { size_t n_needed; size_t n_read; n_needed = n - *n_bytes_in_buffer; n_read = fread (block + *n_bytes_in_buffer, 1, n_needed, in_stream); *n_bytes_in_buffer += n_read; if (n_read == n_needed) break; err |= check_and_close (); err |= open_next_file (); } return err; } /* Return the least common multiple of the sizes associated with the format specs. */ static int get_lcm (void) { size_t i; int l_c_m = 1; for (i = 0; i < n_specs; i++) l_c_m = lcm (l_c_m, width_bytes[(int) spec[i].size]); return l_c_m; } /* If S is a valid traditional offset specification with an optional leading '+' return nonzero and set *OFFSET to the offset it denotes. */ static int parse_old_offset (const char *s, uintmax_t *offset) { int radix; enum strtol_error s_err; if (*s == '\0') return 0; /* Skip over any leading '+'. */ if (s[0] == '+') ++s; /* Determine the radix we'll use to interpret S. If there is a `.', it's decimal, otherwise, if the string begins with `0X'or `0x', it's hexadecimal, else octal. */ if (strchr (s, '.') != NULL) radix = 10; else { if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) radix = 16; else radix = 8; } s_err = xstrtoumax (s, NULL, radix, offset, "Bb"); if (s_err != LONGINT_OK) { STRTOL_FAIL_WARN (s, _("old-style offset"), s_err); return 0; } return 1; } /* Read a chunk of size BYTES_PER_BLOCK from the input files, write the formatted block to standard output, and repeat until the specified maximum number of bytes has been read or until all input has been processed. If the last block read is smaller than BYTES_PER_BLOCK and its size is not a multiple of the size associated with a format spec, extend the input block with zero bytes until its length is a multiple of all format spec sizes. Write the final block. Finally, write on a line by itself the offset of the byte after the last byte read. Accumulate return values from calls to read_block and check_and_close, and if any was nonzero, return nonzero. Otherwise, return zero. */ static int dump (void) { char *block[2]; uintmax_t current_offset; int idx; int err; size_t n_bytes_read; block[0] = (char *) alloca (bytes_per_block); block[1] = (char *) alloca (bytes_per_block); current_offset = n_bytes_to_skip; idx = 0; err = 0; if (limit_bytes_to_format) { while (1) { size_t n_needed; if (current_offset >= end_offset) { n_bytes_read = 0; break; } n_needed = MIN (end_offset - current_offset, (uintmax_t) bytes_per_block); err |= read_block (n_needed, block[idx], &n_bytes_read); if (n_bytes_read < bytes_per_block) break; assert (n_bytes_read == bytes_per_block); write_block (current_offset, n_bytes_read, block[!idx], block[idx]); current_offset += n_bytes_read; idx = !idx; } } else { while (1) { err |= read_block (bytes_per_block, block[idx], &n_bytes_read); if (n_bytes_read < bytes_per_block) break; assert (n_bytes_read == bytes_per_block); write_block (current_offset, n_bytes_read, block[!idx], block[idx]); current_offset += n_bytes_read; idx = !idx; } } if (n_bytes_read > 0) { int l_c_m; size_t bytes_to_write; l_c_m = get_lcm (); /* Make bytes_to_write the smallest multiple of l_c_m that is at least as large as n_bytes_read. */ bytes_to_write = l_c_m * ((n_bytes_read + l_c_m - 1) / l_c_m); memset (block[idx] + n_bytes_read, 0, bytes_to_write - n_bytes_read); write_block (current_offset, bytes_to_write, block[!idx], block[idx]); current_offset += n_bytes_read; } format_address (current_offset, '\n'); if (limit_bytes_to_format && current_offset >= end_offset) err |= check_and_close (); return err; } /* STRINGS mode. Find each "string constant" in the input. A string constant is a run of at least `string_min' ASCII graphic (or formatting) characters terminated by a null. Based on a function written by Richard Stallman for a traditional version of od. Return nonzero if an error occurs. Otherwise, return zero. */ static int dump_strings (void) { size_t bufsize = MAX (100, string_min); char *buf = xmalloc (bufsize); uintmax_t address = n_bytes_to_skip; int err; err = 0; while (1) { size_t i; int c; /* See if the next `string_min' chars are all printing chars. */ tryline: if (limit_bytes_to_format && (end_offset < string_min || end_offset - string_min <= address)) break; for (i = 0; i < string_min; i++) { err |= read_char (&c); address++; if (c < 0) { free (buf); return err; } if (!ISPRINT (c)) /* Found a non-printing. Try again starting with next char. */ goto tryline; buf[i] = c; } /* We found a run of `string_min' printable characters. Now see if it is terminated with a null byte. */ while (!limit_bytes_to_format || address < end_offset) { if (i == bufsize) { bufsize = 1 + 3 * bufsize / 2; buf = xrealloc (buf, bufsize); } err |= read_char (&c); address++; if (c < 0) { free (buf); return err; } if (c == '\0') break; /* It is; print this string. */ if (!ISPRINT (c)) goto tryline; /* It isn't; give up on this string. */ buf[i++] = c; /* String continues; store it all. */ } /* If we get here, the string is all printable and null-terminated, so print it. It is all in `buf' and `i' is its length. */ buf[i] = 0; format_address (address - i - 1, ' '); for (i = 0; (c = buf[i]); i++) { switch (c) { case '\007': fputs ("\\a", stdout); break; case '\b': fputs ("\\b", stdout); break; case '\f': fputs ("\\f", stdout); break; case '\n': fputs ("\\n", stdout); break; case '\r': fputs ("\\r", stdout); break; case '\t': fputs ("\\t", stdout); break; case '\v': fputs ("\\v", stdout); break; default: putc (c, stdout); } } putchar ('\n'); } /* We reach this point only if we search through (max_bytes_to_format - string_min) bytes before reaching EOF. */ free (buf); err |= check_and_close (); return err; } int main (int argc, char **argv) { int c; int n_files; size_t i; int l_c_m; size_t desired_width IF_LINT (= 0); int width_specified = 0; int n_failed_decodes = 0; int err; char const *short_options = (posix2_version () < 200112 ? COMMON_SHORT_OPTIONS "s::w::" : COMMON_SHORT_OPTIONS "s:w:"); /* The old-style `pseudo starting address' to be printed in parentheses after any true address. */ uintmax_t pseudo_start IF_LINT (= 0); program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); err = 0; for (i = 0; i <= MAX_INTEGRAL_TYPE_SIZE; i++) integral_type_size[i] = NO_SIZE; integral_type_size[sizeof (char)] = CHAR; integral_type_size[sizeof (short int)] = SHORT; integral_type_size[sizeof (int)] = INT; integral_type_size[sizeof (long int)] = LONG; #if HAVE_UNSIGNED_LONG_LONG /* If `long' and `long long' have the same size, it's fine to overwrite the entry for `long' with this one. */ integral_type_size[sizeof (ulonglong_t)] = LONG_LONG; #endif for (i = 0; i <= MAX_FP_TYPE_SIZE; i++) fp_type_size[i] = NO_SIZE; fp_type_size[sizeof (float)] = FLOAT_SINGLE; /* The array entry for `double' is filled in after that for LONG_DOUBLE so that if `long double' is the same type or if long double isn't supported FLOAT_LONG_DOUBLE will never be used. */ fp_type_size[sizeof (LONG_DOUBLE)] = FLOAT_LONG_DOUBLE; fp_type_size[sizeof (double)] = FLOAT_DOUBLE; n_specs = 0; n_specs_allocated = 5; spec = (struct tspec *) xmalloc (n_specs_allocated * sizeof (struct tspec)); format_address = format_address_std; address_base = 8; address_pad_len = 7; flag_dump_strings = 0; while ((c = getopt_long (argc, argv, short_options, long_options, NULL)) != -1) { uintmax_t tmp; enum strtol_error s_err; switch (c) { case 0: break; case 'A': switch (optarg[0]) { case 'd': format_address = format_address_std; address_base = 10; address_pad_len = 7; break; case 'o': format_address = format_address_std; address_base = 8; address_pad_len = 7; break; case 'x': format_address = format_address_std; address_base = 16; address_pad_len = 6; break; case 'n': format_address = format_address_none; address_pad_len = 0; break; default: error (EXIT_FAILURE, 0, _("invalid output address radix `%c'; \ it must be one character from [doxn]"), optarg[0]); break; } break; case 'j': s_err = xstrtoumax (optarg, NULL, 0, &n_bytes_to_skip, "bkm"); if (s_err != LONGINT_OK) STRTOL_FATAL_ERROR (optarg, _("skip argument"), s_err); break; case 'N': limit_bytes_to_format = 1; s_err = xstrtoumax (optarg, NULL, 0, &max_bytes_to_format, "bkm"); if (s_err != LONGINT_OK) STRTOL_FATAL_ERROR (optarg, _("limit argument"), s_err); break; case 's': if (optarg == NULL) string_min = 3; else { s_err = xstrtoumax (optarg, NULL, 0, &tmp, "bkm"); if (s_err != LONGINT_OK) STRTOL_FATAL_ERROR (optarg, _("minimum string length"), s_err); /* The minimum string length may be no larger than SIZE_MAX, since we may allocate a buffer of this size. */ if (SIZE_MAX < tmp) error (EXIT_FAILURE, 0, _("%s is too large"), optarg); string_min = tmp; } flag_dump_strings = 1; break; case 't': if (decode_format_string (optarg)) ++n_failed_decodes; break; case 'v': abbreviate_duplicate_blocks = 0; break; case TRADITIONAL_OPTION: traditional = 1; break; /* The next several cases map the traditional format specification options to the corresponding modern format specs. GNU od accepts any combination of old- and new-style options. Format specification options accumulate. */ #define CASE_OLD_ARG(old_char,new_string) \ case old_char: \ { \ if (decode_format_string (new_string)) \ ++n_failed_decodes; \ } \ break CASE_OLD_ARG ('a', "a"); CASE_OLD_ARG ('b', "oC"); CASE_OLD_ARG ('c', "c"); CASE_OLD_ARG ('d', "u2"); CASE_OLD_ARG ('f', "fF"); CASE_OLD_ARG ('h', "x2"); CASE_OLD_ARG ('i', "d2"); CASE_OLD_ARG ('l', "d4"); CASE_OLD_ARG ('o', "o2"); CASE_OLD_ARG ('x', "x2"); /* FIXME: POSIX 1003.1-2001 with XSI requires this: CASE_OLD_ARG ('s', "d2"); for the traditional syntax, but this conflicts with case 's' above. */ #undef CASE_OLD_ARG case 'w': width_specified = 1; if (optarg == NULL) { desired_width = 32; } else { uintmax_t w_tmp; s_err = xstrtoumax (optarg, NULL, 10, &w_tmp, ""); if (s_err != LONGINT_OK) STRTOL_FATAL_ERROR (optarg, _("width specification"), s_err); if (SIZE_MAX < w_tmp) error (EXIT_FAILURE, 0, _("%s is too large"), optarg); desired_width = w_tmp; } break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); break; } } if (n_failed_decodes > 0) exit (EXIT_FAILURE); if (flag_dump_strings && n_specs > 0) error (EXIT_FAILURE, 0, _("no type may be specified when dumping strings")); n_files = argc - optind; /* If the --traditional option is used, there may be from 0 to 3 remaining command line arguments; handle each case separately. od [file] [[+]offset[.][b] [[+]label[.][b]]] The offset and pseudo_start have the same syntax. FIXME: POSIX 1003.1-2001 with XSI requires support for the traditional syntax even if --traditional is not given. */ if (traditional) { uintmax_t offset; if (n_files == 1) { if (parse_old_offset (argv[optind], &offset)) { n_bytes_to_skip = offset; --n_files; ++argv; } } else if (n_files == 2) { uintmax_t o1, o2; if (parse_old_offset (argv[optind], &o1) && parse_old_offset (argv[optind + 1], &o2)) { n_bytes_to_skip = o1; flag_pseudo_start = 1; pseudo_start = o2; argv += 2; n_files -= 2; } else if (parse_old_offset (argv[optind + 1], &o2)) { n_bytes_to_skip = o2; --n_files; argv[optind + 1] = argv[optind]; ++argv; } else { error (0, 0, _("invalid second operand in compatibility mode `%s'"), argv[optind + 1]); usage (EXIT_FAILURE); } } else if (n_files == 3) { uintmax_t o1, o2; if (parse_old_offset (argv[optind + 1], &o1) && parse_old_offset (argv[optind + 2], &o2)) { n_bytes_to_skip = o1; flag_pseudo_start = 1; pseudo_start = o2; argv[optind + 2] = argv[optind]; argv += 2; n_files -= 2; } else { error (0, 0, _("in compatibility mode, the last two arguments must be offsets")); usage (EXIT_FAILURE); } } else if (n_files > 3) { error (0, 0, _("compatibility mode supports at most three arguments")); usage (EXIT_FAILURE); } if (flag_pseudo_start) { if (format_address == format_address_none) { address_base = 8; address_pad_len = 7; format_address = format_address_paren; } else format_address = format_address_label; } } if (limit_bytes_to_format) { end_offset = n_bytes_to_skip + max_bytes_to_format; if (end_offset < n_bytes_to_skip) error (EXIT_FAILURE, 0, "skip-bytes + read-bytes is too large"); } if (n_specs == 0) { if (decode_one_format ("o2", "o2", NULL, &(spec[0]))) { /* This happens on Cray systems that don't have a 2-byte integral type. */ exit (EXIT_FAILURE); } n_specs = 1; } if (n_files > 0) { /* Set the global pointer FILE_LIST so that it references the first file-argument on the command-line. */ file_list = (char const *const *) &argv[optind]; } else { /* No files were listed on the command line. Set the global pointer FILE_LIST so that it references the null-terminated list of one name: "-". */ file_list = default_file_list; } /* open the first input file */ err |= open_next_file (); if (in_stream == NULL) goto cleanup; /* skip over any unwanted header bytes */ err |= skip (n_bytes_to_skip); if (in_stream == NULL) goto cleanup; pseudo_offset = (flag_pseudo_start ? pseudo_start - n_bytes_to_skip : 0); /* Compute output block length. */ l_c_m = get_lcm (); if (width_specified) { if (desired_width != 0 && desired_width % l_c_m == 0) bytes_per_block = desired_width; else { error (0, 0, _("warning: invalid width %lu; using %d instead"), (unsigned long) desired_width, l_c_m); bytes_per_block = l_c_m; } } else { if (l_c_m < DEFAULT_BYTES_PER_BLOCK) bytes_per_block = l_c_m * (DEFAULT_BYTES_PER_BLOCK / l_c_m); else bytes_per_block = l_c_m; } #ifdef DEBUG for (i = 0; i < n_specs; i++) { printf (_("%d: fmt=\"%s\" width=%d\n"), i, spec[i].fmt_string, width_bytes[spec[i].size]); } #endif err |= (flag_dump_strings ? dump_strings () : dump ()); cleanup:; if (have_read_stdin && fclose (stdin) == EOF) error (EXIT_FAILURE, errno, _("standard input")); exit (err == 0 ? EXIT_SUCCESS : EXIT_FAILURE); }
/* paste - merge lines of files Copyright (C) 1984, 1997-2002 by David M. Ihnat This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David Ihnat. */ /* The list of valid escape sequences has been expanded over the Unix version, to include \b, \f, \r, and \v. POSIX changes, bug fixes, long-named options, and cleanup by David MacKenzie <djm@gnu.ai.mit.edu>. Options: --serial -s Paste one file at a time rather than one line from each file. --delimiters=delim-list -d delim-list Consecutively use the characters in DELIM-LIST instead of tab to separate merged lines. When DELIM-LIST is exhausted, start again at its beginning. A FILE of `-' means standard input. If no FILEs are given, standard input is used. */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include "system.h" #include "closeout.h" #include "error.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "paste" #define AUTHORS N_ ("David M. Ihnat and David MacKenzie") /* Indicates that no delimiter should be added in the current position. */ #define EMPTY_DELIM '\0' static FILE dummy_closed; /* Element marking a file that has reached EOF and been closed. */ #define CLOSED (&dummy_closed) static FILE dummy_endlist; /* Element marking end of list of open files. */ #define ENDLIST (&dummy_endlist) /* Name this program was run with. */ char *program_name; /* If nonzero, we have read standard input at some point. */ static int have_read_stdin; /* If nonzero, merge subsequent lines of each file rather than corresponding lines from each file in parallel. */ static int serial_merge; /* The delimeters between lines of input files (used cyclically). */ static char *delims; /* A pointer to the character after the end of `delims'. */ static char *delim_end; static struct option const longopts[] = { {"serial", no_argument, 0, 's'}, {"delimiters", required_argument, 0, 'd'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {0, 0, 0, 0} }; /* Replace backslash representations of special characters in STRPTR with their actual values. The set of possible backslash characters has been expanded beyond that recognized by the Unix version. Return a pointer to the character after the new end of STRPTR. */ static char * collapse_escapes (char *strptr) { register char *strout; strout = strptr; /* Start at the same place, anyway. */ while (*strptr) { if (*strptr != '\\') /* Is it an escape character? */ *strout++ = *strptr++; /* No, just transfer it. */ else { switch (*++strptr) { case '0': *strout++ = EMPTY_DELIM; break; case 'b': *strout++ = '\b'; break; case 'f': *strout++ = '\f'; break; case 'n': *strout++ = '\n'; break; case 'r': *strout++ = '\r'; break; case 't': *strout++ = '\t'; break; case 'v': *strout++ = '\v'; break; default: *strout++ = *strptr; break; } strptr++; } } return strout; } /* Perform column paste on the NFILES files named in FNAMPTR. Return 0 if no errors, 1 if one or more files could not be opened or read. */ static int paste_parallel (int nfiles, char **fnamptr) { int errors = 0; /* 1 if open or read errors occur. */ /* Number of files for which space is allocated in `delbuf' and `fileptr'. Enlarged as necessary. */ int file_list_size = 12; int chr IF_LINT (= 0); /* Input character. */ int line_length; /* Number of chars in line. */ int somedone; /* 0 if all files empty for this line. */ /* If all files are just ready to be closed, or will be on this round, the string of delimiters must be preserved. delbuf[0] through delbuf[file_list_size] store the delimiters for closed files. */ char *delbuf; size_t delims_saved; /* Number of delims saved in `delbuf'. */ register char *delimptr; /* Cycling pointer into `delims'. */ FILE **fileptr; /* Streams open to the files to process. */ int files_open; /* Number of files still open to process. */ int i; /* Loop index. */ int opened_stdin = 0; /* Nonzero if any fopen got fd 0. */ delbuf = (char *) xmalloc (file_list_size + 2); fileptr = (FILE **) xmalloc ((file_list_size + 1) * sizeof (FILE *)); /* Attempt to open all files. This could be expanded to an infinite number of files, but at the (considerable) expense of remembering each file and its current offset, then opening/reading/closing. */ for (files_open = 0; files_open < nfiles; ++files_open) { if (files_open == file_list_size - 2) { file_list_size += 12; delbuf = (char *) xrealloc (delbuf, file_list_size + 2); fileptr = (FILE **) xrealloc ((char *) fileptr, (file_list_size + 1) * sizeof (FILE *)); } if (STREQ (fnamptr[files_open], "-")) { have_read_stdin = 1; fileptr[files_open] = stdin; } else { fileptr[files_open] = fopen (fnamptr[files_open], "r"); if (fileptr[files_open] == NULL) error (EXIT_FAILURE, errno, "%s", fnamptr[files_open]); else if (fileno (fileptr[files_open]) == 0) opened_stdin = 1; } } fileptr[files_open] = ENDLIST; if (opened_stdin && have_read_stdin) error (EXIT_FAILURE, 0, _("standard input is closed")); /* Read a line from each file and output it to stdout separated by a delimiter, until we go through the loop without successfully reading from any of the files. */ while (files_open) { /* Set up for the next line. */ somedone = 0; delimptr = delims; delims_saved = 0; for (i = 0; fileptr[i] != ENDLIST && files_open; i++) { line_length = 0; /* Clear so we can easily detect EOF. */ if (fileptr[i] != CLOSED) { chr = getc (fileptr[i]); if (chr != EOF && delims_saved) { fwrite (delbuf, sizeof (char), delims_saved, stdout); delims_saved = 0; } while (chr != EOF) { line_length++; if (chr == '\n') break; putc (chr, stdout); chr = getc (fileptr[i]); } } if (line_length == 0) { /* EOF, read error, or closed file. If an EOF or error, close the file and mark it in the list. */ if (fileptr[i] != CLOSED) { if (ferror (fileptr[i])) { error (0, errno, "%s", fnamptr[i]); errors = 1; } if (fileptr[i] == stdin) clearerr (fileptr[i]); /* Also clear EOF. */ else if (fclose (fileptr[i]) == EOF) { error (0, errno, "%s", fnamptr[i]); errors = 1; } fileptr[i] = CLOSED; files_open--; } if (fileptr[i + 1] == ENDLIST) { /* End of this output line. Is this the end of the whole thing? */ if (somedone) { /* No. Some files were not closed for this line. */ if (delims_saved) { fwrite (delbuf, sizeof (char), delims_saved, stdout); delims_saved = 0; } putc ('\n', stdout); } continue; /* Next read of files, or exit. */ } else { /* Closed file; add delimiter to `delbuf'. */ if (*delimptr != EMPTY_DELIM) delbuf[delims_saved++] = *delimptr; if (++delimptr == delim_end) delimptr = delims; } } else { /* Some data read. */ somedone++; /* Except for last file, replace last newline with delim. */ if (fileptr[i + 1] != ENDLIST) { if (chr != '\n') putc (chr, stdout); if (*delimptr != EMPTY_DELIM) putc (*delimptr, stdout); if (++delimptr == delim_end) delimptr = delims; } else putc (chr, stdout); } } } return errors; } /* Perform serial paste on the NFILES files named in FNAMPTR. Return 0 if no errors, 1 if one or more files could not be opened or read. */ static int paste_serial (int nfiles, char **fnamptr) { int errors = 0; /* 1 if open or read errors occur. */ register int charnew, charold; /* Current and previous char read. */ register char *delimptr; /* Current delimiter char. */ register FILE *fileptr; /* Open for reading current file. */ for (; nfiles; nfiles--, fnamptr++) { if (STREQ (*fnamptr, "-")) { have_read_stdin = 1; fileptr = stdin; } else { fileptr = fopen (*fnamptr, "r"); if (fileptr == NULL) { error (0, errno, "%s", *fnamptr); errors = 1; continue; } } delimptr = delims; /* Set up for delimiter string. */ charold = getc (fileptr); if (charold != EOF) { /* `charold' is set up. Hit it! Keep reading characters, stashing them in `charnew'; output `charold', converting to the appropriate delimiter character if needed. After the EOF, output `charold' if it's a newline; otherwise, output it and then a newline. */ while ((charnew = getc (fileptr)) != EOF) { /* Process the old character. */ if (charold == '\n') { if (*delimptr != EMPTY_DELIM) putc (*delimptr, stdout); if (++delimptr == delim_end) delimptr = delims; } else putc (charold, stdout); charold = charnew; } /* Hit EOF. Process that last character. */ putc (charold, stdout); } if (charold != '\n') putc ('\n', stdout); if (ferror (fileptr)) { error (0, errno, "%s", *fnamptr); errors = 1; } if (fileptr == stdin) clearerr (fileptr); /* Also clear EOF. */ else if (fclose (fileptr) == EOF) { error (0, errno, "%s", *fnamptr); errors = 1; } } return errors; } void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... [FILE]...\n\ "), program_name); fputs (_("\ Write lines consisting of the sequentially corresponding lines from\n\ each FILE, separated by TABs, to standard output.\n\ With no FILE, or when FILE is -, read standard input.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ -d, --delimiters=LIST reuse characters from LIST instead of TABs\n\ -s, --serial paste one file at a time instead of in parallel\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); /* FIXME: add a couple of examples. */ printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } int main (int argc, char **argv) { int optc, exit_status; char default_delims[2], zero_delims[3]; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); have_read_stdin = 0; serial_merge = 0; delims = default_delims; strcpy (delims, "\t"); strcpy (zero_delims, "\\0"); while ((optc = getopt_long (argc, argv, "d:s", longopts, NULL)) != -1) { switch (optc) { case 0: break; case 'd': /* Delimiter character(s). */ if (optarg[0] == '\0') optarg = zero_delims; delims = optarg; break; case 's': serial_merge++; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } if (optind == argc) argv[argc++] = "-"; delim_end = collapse_escapes (delims); if (!serial_merge) exit_status = paste_parallel (argc - optind, &argv[optind]); else exit_status = paste_serial (argc - optind, &argv[optind]); if (have_read_stdin && fclose (stdin) == EOF) error (EXIT_FAILURE, errno, "-"); exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); }
/* pathchk -- check whether pathnames are valid or portable Copyright (C) 1991-2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Usage: pathchk [-p] [--portability] path... For each PATH, print a message if any of these conditions are false: * all existing leading directories in PATH have search (execute) permission * strlen (PATH) <= PATH_MAX * strlen (each_directory_in_PATH) <= NAME_MAX Exit status: 0 All PATH names passed all of the tests. 1 An error occurred. Options: -p, --portability Instead of performing length checks on the underlying filesystem, test the length of the pathname and its components against the POSIX minimum limits for portability, _POSIX_NAME_MAX and _POSIX_PATH_MAX in 2.9.2. Also check that the pathname contains no character not in the portable filename character set. David MacKenzie <djm@gnu.ai.mit.edu> and Jim Meyering <meyering@cs.utexas.edu> */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include <errno.h> #ifndef errno extern int errno; #endif #include "system.h" #include "error.h" #include "long-options.h" #include "closeout.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "pathchk" #define AUTHORS N_ ("David MacKenzie and Jim Meyering") #define NEED_PATHCONF_WRAPPER 0 #if HAVE_PATHCONF # ifndef PATH_MAX # define PATH_MAX_FOR(p) pathconf_wrapper ((p), _PC_PATH_MAX) # define NEED_PATHCONF_WRAPPER 1 # endif /* not PATH_MAX */ # ifndef NAME_MAX # define NAME_MAX_FOR(p) pathconf_wrapper ((p), _PC_NAME_MAX); # undef NEED_PATHCONF_WRAPPER # define NEED_PATHCONF_WRAPPER 1 # endif /* not NAME_MAX */ #else # include <sys/param.h> # ifndef PATH_MAX # ifdef MAXPATHLEN # define PATH_MAX MAXPATHLEN # else /* not MAXPATHLEN */ # define PATH_MAX _POSIX_PATH_MAX # endif /* not MAXPATHLEN */ # endif /* not PATH_MAX */ # ifndef NAME_MAX # ifdef MAXNAMLEN # define NAME_MAX MAXNAMLEN # else /* not MAXNAMLEN */ # define NAME_MAX _POSIX_NAME_MAX # endif /* not MAXNAMLEN */ # endif /* not NAME_MAX */ #endif #ifndef _POSIX_PATH_MAX # define _POSIX_PATH_MAX 255 #endif #ifndef _POSIX_NAME_MAX # define _POSIX_NAME_MAX 14 #endif #ifndef PATH_MAX_FOR # define PATH_MAX_FOR(p) PATH_MAX #endif #ifndef NAME_MAX_FOR # define NAME_MAX_FOR(p) NAME_MAX #endif static int validate_path (char *path, int portability); /* The name this program was run with. */ char *program_name; static struct option const longopts[] = { {"portability", no_argument, NULL, 'p'}, {NULL, 0, NULL, 0} }; #if NEED_PATHCONF_WRAPPER /* Distinguish between the cases when pathconf fails and when it reports there is no limit (the latter is the case for PATH_MAX on the Hurd). When there is no limit, return LONG_MAX. Otherwise, return pathconf's return value. */ static long int pathconf_wrapper (const char *filename, int param) { long int ret; errno = 0; ret = pathconf (filename, param); if (ret < 0 && errno == 0) return LONG_MAX; return ret; } #endif void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("Usage: %s [OPTION]... NAME...\n"), program_name); fputs (_("\ Diagnose unportable constructs in NAME.\n\ \n\ -p, --portability check for all POSIX systems, not only this one\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } int main (int argc, char **argv) { int exit_status = 0; int check_portability = 0; int optc; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, AUTHORS, usage); while ((optc = getopt_long (argc, argv, "p", longopts, NULL)) != -1) { switch (optc) { case 0: break; case 'p': check_portability = 1; break; default: usage (EXIT_FAILURE); } } if (optind == argc) { error (0, 0, _("too few arguments")); usage (EXIT_FAILURE); } for (; optind < argc; ++optind) exit_status |= validate_path (argv[optind], check_portability); exit (exit_status); } /* Each element is nonzero if the corresponding ASCII character is in the POSIX portable character set, and zero if it is not. In addition, the entry for `/' is nonzero to simplify checking. */ static char const portable_chars[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; /* If PATH contains only portable characters, return 1, else 0. */ static int portable_chars_only (const char *path) { const char *p; for (p = path; *p; ++p) if (portable_chars[(unsigned char) *p] == 0) { error (0, 0, _("path `%s' contains nonportable character `%c'"), path, *p); return 0; } return 1; } /* Return 1 if PATH is a usable leading directory, 0 if not, 2 if it doesn't exist. */ static int dir_ok (const char *path) { struct stat stats; if (stat (path, &stats)) return 2; if (!S_ISDIR (stats.st_mode)) { error (0, 0, _("`%s' is not a directory"), path); return 0; } /* Use access to test for search permission because testing permission bits of st_mode can lose with new access control mechanisms. Of course, access loses if you're running setuid. */ if (access (path, X_OK) != 0) { if (errno == EACCES) error (0, 0, _("directory `%s' is not searchable"), path); else error (0, errno, "%s", path); return 0; } return 1; } /* Make sure that strlen (PATH) <= PATH_MAX && strlen (each-existing-directory-in-PATH) <= NAME_MAX If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and _POSIX_NAME_MAX instead, and make sure that PATH contains no characters not in the POSIX portable filename character set, which consists of A-Z, a-z, 0-9, ., _, -. Make sure that all leading directories along PATH that exist have `x' permission. Return 0 if all of these tests are successful, 1 if any fail. */ static int validate_path (char *path, int portability) { long int path_max; int last_elem; /* Nonzero if checking last element of path. */ int exists IF_LINT (= 0); /* 2 if the path element exists. */ char *slash; char *parent; /* Last existing leading directory so far. */ if (portability && !portable_chars_only (path)) return 1; if (*path == '\0') return 0; /* Figure out the parent of the first element in PATH. */ parent = xstrdup (*path == '/' ? "/" : "."); slash = path; last_elem = 0; while (1) { long int name_max; long int length; /* Length of partial path being checked. */ char *start; /* Start of path element being checked. */ /* Find the end of this element of the path. Then chop off the rest of the path after this element. */ while (*slash == '/') slash++; start = slash; slash = strchr (slash, '/'); if (slash != NULL) *slash = '\0'; else { last_elem = 1; slash = strchr (start, '\0'); } if (!last_elem) { exists = dir_ok (path); if (exists == 0) { free (parent); return 1; } } length = slash - start; /* Since we know that `parent' is a directory, it's ok to call pathconf with it as the argument. (If `parent' isn't a directory or doesn't exist, the behavior of pathconf is undefined.) But if `parent' is a directory and is on a remote file system, it's likely that pathconf can't give us a reasonable value and will return -1. (NFS and tempfs are not POSIX . . .) In that case, we have no choice but to assume the pessimal POSIX minimums. */ name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent); if (name_max < 0) name_max = _POSIX_NAME_MAX; if (length > name_max) { error (0, 0, _("name `%s' has length %ld; exceeds limit of %ld"), start, length, name_max); free (parent); return 1; } if (last_elem) break; if (exists == 1) { free (parent); parent = xstrdup (path); } *slash++ = '/'; } /* `parent' is now the last existing leading directory in the whole path, so it's ok to call pathconf with it as the argument. */ path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent); if (path_max < 0) path_max = _POSIX_PATH_MAX; free (parent); if (strlen (path) > (size_t) path_max) { error (0, 0, _("path `%s' has length %d; exceeds limit of %ld"), path, strlen (path), path_max); return 1; } return 0; }
/* pr -- convert text files for printing. Copyright (C) 88, 91, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* By Pete TerMaat, with considerable refinement by Roland Huebner. */ /* Things to watch: Sys V screws up on ... pr -n -3 -s: /usr/dict/words pr -m -o10 -n /usr/dict/words{,,,} pr -6 -a -n -o5 /usr/dict/words Ideas: Keep a things_to_do list of functions to call when we know we have something to print. Cleaner than current series of checks. Improve the printing of control prefixes. Expand the filename in the centered header line to a full pathname. Concept: If the input_tab_char differs from the default value TAB (`-e[CHAR[...]]' is used), any input text tab is expanded to the default width of 8 spaces (compare char_to_clump). - Same as SunOS does. The treatment of the number_separator (compare add_line_number): The default value TAB of the number_separator (`-n[SEP[...]]') doesn't be thought to be an input character. An optional `-e'-input has no effect. - With single column output only one POSIX requirement has to be met: The default n-separator should be a TAB. The consequence is a different width between the number and the text if the output position of the separator changes, i.e. it depends upon the left margin used. That's not nice but easy-to-use together with the defaults of other utilities, e.g. sort or cut. - Same as SunOS does. - With multicolumn output two conflicting POSIX requirements exist: First `default n-separator is TAB', second `output text columns shall be of equal width'. Moreover POSIX specifies the number+separator a part of the column, together with `-COLUMN' and `-a -COLUMN'. (With -m output the number shall occupy each line only once. Exactly the same situation as single column output exists.) GNU pr gives priority to the 2nd requirement and observes POSIX column definition. The n-separator TAB is expanded to the same number of spaces in each column using the default value 8. Tabification is only performed if it is compatible with the output position. Consequence: The output text columns are of equal width. The layout of a page does not change if the left margin varies. - Looks better than the SunOS approach. SunOS pr gives priority to the 1st requirement. n-separator TAB width varies with each column. Only the width of text part of the column is fixed. Consequence: The output text columns don't have equal width. The widths and the layout of the whole page varies with the left margin. An overflow of the line length (without margin) over the input value PAGE_WIDTH may occur. The interference of the POSIX-compliant small letter options -w and -s: (`interference' means `setting a _separator_ with -s switches off the column sturctur and the default - not generally - page_width, acts on -w option') options: text form / separator: equivalent new options: -w l -s[x] -------------------------------------------------------------------- 1. -- -- columns / space -- trunc. to page_width = 72 2. -- -s[:] full lines / TAB[:] -J --sep-string[="<TAB>"|:] no truncation 3. -w l -- columns / space -W l trunc. to page_width = l 4. -w l -s[:] columns / no sep.[:] -W l --sep-string[=:] trunc. to page_width = l -------------------------------------------------------------------- Options: Including version 1.22i: Some SMALL LETTER options has been redefined with the object of a better POSIX compliance. The output of some further cases has been adapted to other UNIXes. A violation of downward compatibility has to be accepted. Some NEW CAPITAL LETTER options ( -J, -S, -W) has been introduced to turn off unexpected interferences of small letter options (-s and -w together with the three column options). -N option and the second argument LAST_PAGE of +FIRST_PAGE offer more flexibility; The detailed handling of form feeds set in the input files requires -T option. Capital letter options dominate small letter ones. Some of the option-arguments cannot be specified as separate arguments from the preceding option letter (already stated in POSIX specification). Form feeds in the input cause page breaks in the output. Multiple form feeds produce empty pages. +FIRST_PAGE[:LAST_PAGE], --pages=FIRST_PAGE[:LAST_PAGE] begin [stop] printing with page FIRST_[LAST_]PAGE -COLUMN, --columns=COLUMN Produce output that is COLUMN columns wide and print columns down, unless -a is used. Balance number of lines in the columns on each page. -a, --across Print columns across rather than down, used together with -COLUMN. The input one two three four will be printed with `-a -3' as one two three four -b Balance columns on the last page. -b is no longer an independent option. It's always used together with -COLUMN (unless -a is used) to get a consistent formulation with "FF set by hand" in input files. Each formfeed found terminates the number of lines to be read with the actual page. The situation for printing columns down is equivalent to that on the last page. So we need a balancing. Keeping -b as an underground option guarantees some downward compatibility. Utilities using pr with -b (a most frequently used form) still work as usual. -c, --show-control-chars Print unprintable characters as control prefixes. Control-g is printed as ^G (use hat notation) and octal backslash notation. -d, --double-space Double space the output. -D FORMAT, --date-format=FORMAT Use FORMAT for the header date. -e[CHAR[WIDTH]], --expand-tabs[=CHAR[WIDTH]] Expand tabs to spaces on input. Optional argument CHAR is the input TAB character. (Default is TAB). Optional argument WIDTH is the input TAB character's width. (Default is 8.) -F, -f, --form-feed Use formfeeds instead of newlines to separate pages. A three line HEADER is used, no TRAILER with -F, without -F both HEADER and TRAILER are made of five lines. -h HEADER, --header=HEADER Replace the filename in the header with the string HEADER. A centered header is used. -i[CHAR[WIDTH]], --output-tabs[=CHAR[WIDTH]] Replace spaces with tabs on output. Optional argument CHAR is the output TAB character. (Default is TAB). Optional argument WIDTH is the output TAB character's width. (Default is 8) -J, --join-lines Merge lines of full length, turns off -W/-w line truncation, no column alignment, --sep-string[=STRING] sets separators, works with all column options (-COLUMN | -a -COLUMN | -m). -J has been introduced (together with -W and --sep-string) to disentangle the old (POSIX compliant) options -w, -s along with the 3 column options. -l PAGE_LENGTH, --length=PAGE_LENGTH Set the page length to PAGE_LENGTH lines. Default is 66, including 5 lines of HEADER and 5 lines of TRAILER without -F, but only 3 lines of HEADER and no TRAILER with -F (i.e the number of text lines defaults to 56 or 63 respectively). -m, --merge Print files in parallel; pad_across_to align columns; truncate lines and print separator strings; Do it also with empty columns to get a continuous line numbering and column marking by separators throughout the whole merged file. Empty pages in some input files produce empty columns [marked by separators] in the merged pages. Completely empty merged pages show no column separators at all. The layout of a merged page is ruled by the largest form feed distance of the single pages at that page. Shorter columns will be filled up with empty lines. Together with -J option join lines of full length and set separators when -S option is used. -n[SEP[DIGITS]], --number-lines[=SEP[DIGITS]] Provide DIGITS digit line numbering (default for DIGITS is 5). With multicolumn output the number occupies the first DIGITS column positions of each text column or only each line of -m output. With single column output the number precedes each line just as -m output. Optional argument SEP is the character appended to the line number to separate it from the text followed. The default separator is a TAB. In a strict sense a TAB is always printed with single column output only. The TAB-width varies with the TAB-position, e.g. with the left margin specified by -o option. With multicolumn output priority is given to `equal width of output columns' (a POSIX specification). The TAB-width is fixed to the value of the 1st column and does not change with different values of left margin. That means a fixed number of spaces is always printed in the place of a TAB. The tabification depends upon the output position. Default counting of the line numbers starts with 1st line of the input file (not the 1st line printed, compare the --page option and -N option). -N NUMBER, --first-line-number=NUMBER Start line counting with the number NUMBER at the 1st line of first page printed (mostly not the 1st line of the input file). -o MARGIN, --indent=MARGIN Offset each line with a margin MARGIN spaces wide. Total page width is the size of the margin plus the PAGE_WIDTH set with -W/-w option. -r, --no-file-warnings Omit warning when a file cannot be opened. -s[CHAR], --separator[=CHAR] Separate columns by a single character CHAR, default for CHAR is the TAB character without -w and 'no char' with -w. Without `-s' default separator `space' is set. -s[CHAR] turns off line truncation of all 3 column options (-COLUMN|-a -COLUMN|-m) except -w is set. That is a POSIX compliant formulation. The source code translates -s into the new options -S and -J, also -W if required. -S STRING, --sep-string[=STRING] Separate columns by any string STRING. The -S option doesn't react upon the -W/-w option (unlike -s option does). It defines a separator nothing else. Without -S: Default separator TAB is used with -J and `space' otherwise (same as -S" "). With -S "": No separator is used. Quotes should be used with blanks and some shell active characters. -S is problematic because in its obsolete form you cannot use -S "STRING", but in its standard form you must use -S "STRING" if STRING is empty. Use --sep-string to avoid the ambiguity. -t, --omit-header Do not print headers or footers but retain form feeds set in the input files. -T, --omit-pagination Do not print headers or footers, eliminate any pagination by form feeds set in the input files. -v, --show-nonprinting Print unprintable characters as escape sequences. Use octal backslash notation. Control-G becomes \007. -w PAGE_WIDTH, --width=PAGE_WIDTH Set page width to PAGE_WIDTH characters for multiple text-column output only (default for PAGE_WIDTH is 72). -s[CHAR] turns off the default page width and any line truncation. Lines of full length will be merged, regardless of the column options set. A POSIX compliant formulation. -W PAGE_WIDTH, --page-width=PAGE_WIDTH Set the page width to PAGE_WIDTH characters. That's valid with and without a column option. Text lines will be truncated, unless -J is used. Together with one of the column options (-COLUMN| -a -COLUMN| -m) column alignment is always used. Default is 72 characters. Without -W PAGE_WIDTH - but with one of the column options default truncation of 72 characters is used (to keep downward compatibility and to simplify most frequently met column tasks). Column alignment and column separators are used. - and without any of the column options NO line truncation is used (to keep downward compatibility and to meet most frequent tasks). That's equivalent to -W 72 -J . With/without -W PAGE_WIDTH the header line is always truncated to avoid line overflow. (In pr versions newer than 1.14 -S option does no longer affect -W option.) */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include <time.h> #include "system.h" #include "closeout.h" #include "error.h" #include "mbswidth.h" #include "posixver.h" #include "xstrtol.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "pr" #define AUTHORS N_ ("Pete TerMaat and Roland Huebner") #ifndef TRUE # define TRUE 1 # define FALSE 0 #endif /* Used with start_position in the struct COLUMN described below. If start_position == ANYWHERE, we aren't truncating columns and can begin printing a column anywhere. Otherwise we must pad to the horizontal position start_position. */ #define ANYWHERE 0 /* Each column has one of these structures allocated for it. If we're only dealing with one file, fp is the same for all columns. The general strategy is to spend time setting up these column structures (storing columns if necessary), after which printing is a matter of flitting from column to column and calling print_func. Parallel files, single files printing across in multiple columns, and single files printing down in multiple columns all fit the same printing loop. print_func Function used to print lines in this column. If we're storing this column it will be print_stored(), Otherwise it will be read_line(). char_func Function used to process characters in this column. If we're storing this column it will be store_char(), otherwise it will be print_char(). current_line Index of the current entry in line_vector, which contains the index of the first character of the current line in buff[]. lines_stored Number of lines in this column which are stored in buff. lines_to_print If we're storing this column, lines_to_print is the number of stored_lines which remain to be printed. Otherwise it is the number of lines we can print without exceeding lines_per_body. start_position The horizontal position we want to be in before we print the first character in this column. numbered True means precede this column with a line number. */ struct COLUMN; struct COLUMN { FILE *fp; /* Input stream for this column. */ char const *name; /* File name. */ enum { OPEN, FF_FOUND, /* used with -b option, set with \f, changed to ON_HOLD after print_header */ ON_HOLD, /* Hit a form feed. */ CLOSED } status; /* Status of the file pointer. */ /* Func to print lines in this col. */ int (*print_func) (struct COLUMN *); /* Func to print/store chars in this col. */ void (*char_func) (int); int current_line; /* Index of current place in line_vector. */ int lines_stored; /* Number of lines stored in buff. */ int lines_to_print; /* No. lines stored or space left on page. */ int start_position; /* Horizontal position of first char. */ int numbered; int full_page_printed; /* True means printed without a FF found. */ /* p->full_page_printed controls a special case of "FF set by hand": True means a full page has been printed without FF found. To avoid an additional empty page we have to ignore a FF immediately following in the next line. */ }; typedef struct COLUMN COLUMN; #define NULLCOL (COLUMN *)0 static int char_to_clump (int c); static int read_line (COLUMN *p); static int print_page (void); static int print_stored (COLUMN *p); static int open_file (char *name, COLUMN *p); static int skip_to_page (int page); static void print_header (void); static void pad_across_to (int position); static void add_line_number (COLUMN *p); static void getoptarg (char *arg, char switch_char, char *character, int *number); void usage (int status); static void print_files (int number_of_files, char **av); static void init_parameters (int number_of_files); static void init_header (char *filename, int desc); static int init_fps (int number_of_files, char **av); static void init_funcs (void); static void init_store_cols (void); static void store_columns (void); static void balance (int total_stored); static void store_char (int c); static void pad_down (int lines); static void read_rest_of_line (COLUMN *p); static void skip_read (COLUMN *p, int column_number); static void print_char (int c); static void cleanup (void); static void first_last_page (char *pages); static void print_sep_string (void); static void separator_string (const char *optarg_S); /* The name under which this program was invoked. */ char *program_name; /* All of the columns to print. */ static COLUMN *column_vector; /* When printing a single file in multiple downward columns, we store the leftmost columns contiguously in buff. To print a line from buff, get the index of the first character from line_vector[i], and print up to line_vector[i + 1]. */ static char *buff; /* Index of the position in buff where the next character will be stored. */ static int buff_current; /* The number of characters in buff. Used for allocation of buff and to detect overflow of buff. */ static int buff_allocated; /* Array of indices into buff. Each entry is an index of the first character of a line. This is used when storing lines to facilitate shuffling when we do column balancing on the last page. */ static int *line_vector; /* Array of horizonal positions. For each line in line_vector, end_vector[line] is the horizontal position we are in after printing that line. We keep track of this so that we know how much we need to pad to prepare for the next column. */ static int *end_vector; /* (-m) True means we're printing multiple files in parallel. */ static int parallel_files = FALSE; /* (-m) True means a line starts with some empty columns (some files already CLOSED or ON_HOLD) which we have to align. */ static int align_empty_cols; /* (-m) True means we have not yet found any printable column in a line. align_empty_cols = TRUE has to be maintained. */ static int empty_line; /* (-m) False means printable column output precedes a form feed found. Column alignment is done only once. No additional action with that form feed. True means we found only a form feed in a column. Maybe we have to do some column alignment with that form feed. */ static int FF_only; /* (-[0-9]+) True means we're given an option explicitly specifying number of columns. Used to detect when this option is used with -m and when translating old options to new/long options. */ static int explicit_columns = FALSE; /* (-t|-T) False means we aren't printing headers and footers. */ static int extremities = TRUE; /* (-t) True means we retain all FF set by hand in input files. False is set with -T option. */ static int keep_FF = FALSE; static int print_a_FF = FALSE; /* True means we need to print a header as soon as we know we've got input to print after it. */ static int print_a_header; /* (-f) True means use formfeeds instead of newlines to separate pages. */ static int use_form_feed = FALSE; /* True means we have read the standard input. */ static int have_read_stdin = FALSE; /* True means the -a flag has been given. */ static int print_across_flag = FALSE; /* True means we're printing one file in multiple (>1) downward columns. */ static int storing_columns = TRUE; /* (-b) True means balance columns on the last page as Sys V does. */ /* That's no longer an independent option. With storing_columns = TRUE balance_columns = TRUE is used too (s. function init_parameters). We get a consistent formulation with "FF set by hand" in input files. */ static int balance_columns = FALSE; /* (-l) Number of lines on a page, including header and footer lines. */ static int lines_per_page = 66; /* Number of lines in the header and footer can be reset to 0 using the -t flag. */ static int lines_per_header = 5; static int lines_per_body; static int lines_per_footer = 5; /* (-w|-W) Width in characters of the page. Does not include the width of the margin. */ static int chars_per_line = 72; /* (-w|W) True means we truncate lines longer than chars_per_column. */ static int truncate_lines = FALSE; /* (-J) True means we join lines without any line truncation. -J dominates -w option. */ static int join_lines = FALSE; /* Number of characters in a column. Based on col_sep_length and page width. */ static int chars_per_column; /* (-e) True means convert tabs to spaces on input. */ static int untabify_input = FALSE; /* (-e) The input tab character. */ static char input_tab_char = '\t'; /* (-e) Tabstops are at chars_per_tab, 2*chars_per_tab, 3*chars_per_tab, ... where the leftmost column is 1. */ static int chars_per_input_tab = 8; /* (-i) True means convert spaces to tabs on output. */ static int tabify_output = FALSE; /* (-i) The output tab character. */ static char output_tab_char = '\t'; /* (-i) The width of the output tab. */ static int chars_per_output_tab = 8; /* Keeps track of pending white space. When we hit a nonspace character after some whitespace, we print whitespace, tabbing if necessary to get to output_position + spaces_not_printed. */ static int spaces_not_printed; /* (-o) Number of spaces in the left margin (tabs used when possible). */ static int chars_per_margin = 0; /* Position where the next character will fall. Leftmost position is 0 + chars_per_margin. Rightmost position is chars_per_margin + chars_per_line - 1. This is important for converting spaces to tabs on output. */ static int output_position; /* Horizontal position relative to the current file. (output_position depends on where we are on the page; input_position depends on where we are in the file.) Important for converting tabs to spaces on input. */ static int input_position; /* Count number of failed opens so we can exit with nonzero status if there were any. */ static int failed_opens = 0; /* The number of spaces taken up if we print a tab character with width c_ from position h_. */ #define TAB_WIDTH(c_, h_) ((c_) - ((h_) % (c_))) /* The horizontal position we'll be at after printing a tab character of width c_ from the position h_. */ #define POS_AFTER_TAB(c_, h_) ((h_) + TAB_WIDTH (c_, h_)) /* (-NNN) Number of columns of text to print. */ static int columns = 1; /* (+NNN:MMM) Page numbers on which to begin and stop printing. first_page_number = 0 will be used to check input only. */ static int first_page_number = 0; static int last_page_number = 0; /* Number of files open (not closed, not on hold). */ static int files_ready_to_read = 0; /* Current page number. Displayed in header. */ static int page_number; /* Current line number. Displayed when -n flag is specified. When printing files in parallel (-m flag), line numbering is as follows: 1 foo goo moo 2 hoo too zoo When printing files across (-a flag), ... 1 foo 2 moo 3 goo 4 hoo 5 too 6 zoo Otherwise, line numbering is as follows: 1 foo 3 goo 5 too 2 moo 4 hoo 6 zoo */ static int line_number; /* With line_number overflow, we use power_10 to cut off the higher-order digits of the line_number */ static int power_10; /* (-n) True means lines should be preceded by numbers. */ static int numbered_lines = FALSE; /* (-n) Character which follows each line number. */ static char number_separator = '\t'; /* (-n) line counting starts with 1st line of input file (not with 1st line of 1st page printed). */ static int line_count = 1; /* (-n) True means counting of skipped lines starts with 1st line of input file. False means -N option is used in addition, counting of skipped lines not required. */ static int skip_count = TRUE; /* (-N) Counting starts with start_line_number = NUMBER at 1st line of first page printed, usually not 1st page of input file. */ static int start_line_num = 1; /* (-n) Width in characters of a line number. */ static int chars_per_number = 5; /* Used when widening the first column to accommodate numbers -- only needed when printing files in parallel. Includes width of both the number and the number_separator. */ static int number_width; /* Buffer sprintf uses to format a line number. */ static char *number_buff; /* (-v) True means unprintable characters are printed as escape sequences. control-g becomes \007. */ static int use_esc_sequence = FALSE; /* (-c) True means unprintable characters are printed as control prefixes. control-g becomes ^G. */ static int use_cntrl_prefix = FALSE; /* (-d) True means output is double spaced. */ static int double_space = FALSE; /* Number of files opened initially in init_files. Should be 1 unless we're printing multiple files in parallel. */ static int total_files = 0; /* (-r) True means don't complain if we can't open a file. */ static int ignore_failed_opens = FALSE; /* (-S) True means we separate columns with a specified string. -S option does not affect line truncation nor column alignment. */ static int use_col_separator = FALSE; /* String used to separate columns if the -S option has been specified. Default without -S but together with one of the column options -a|COLUMN|-m is a `space' and with the -J option a `tab'. */ static char *col_sep_string = ""; static int col_sep_length = 0; static char *column_separator = " "; static char *line_separator = "\t"; /* Number of separator characters waiting to be printed as soon as we know that we have any input remaining to be printed. */ static int separators_not_printed; /* Position we need to pad to, as soon as we know that we have input remaining to be printed. */ static int padding_not_printed; /* True means we should pad the end of the page. Remains false until we know we have a page to print. */ static int pad_vertically; /* (-h) String of characters used in place of the filename in the header. */ static char *custom_header; /* (-D) Date format for the header. */ static char const *date_format; /* Date and file name for the header. */ static char *date_text; static char const *file_text; /* Output columns available, not counting the date and file name. */ static int header_width_available; static int *clump_buff; /* True means we read the line no. lines_per_body in skip_read called by skip_to_page. That variable controls the coincidence of a "FF set by hand" and "full_page_printed", see above the definition of structure COLUMN. */ static int last_line = FALSE; /* For long options that have no equivalent short option, use a non-character as a pseudo short option, starting with CHAR_MAX + 1. */ enum { COLUMNS_OPTION = CHAR_MAX + 1, PAGES_OPTION }; #define COMMON_SHORT_OPTIONS \ "-0123456789D:FJN:TW:abcde::fh:i::l:mn::o:rs::tvw:" static struct option const long_options[] = { {"pages", required_argument, NULL, PAGES_OPTION}, {"columns", required_argument, NULL, COLUMNS_OPTION}, {"across", no_argument, NULL, 'a'}, {"show-control-chars", no_argument, NULL, 'c'}, {"double-space", no_argument, NULL, 'd'}, {"date-format", required_argument, NULL, 'D'}, {"expand-tabs", optional_argument, NULL, 'e'}, {"form-feed", no_argument, NULL, 'f'}, {"header", required_argument, NULL, 'h'}, {"output-tabs", optional_argument, NULL, 'i'}, {"join-lines", no_argument, NULL, 'J'}, {"length", required_argument, NULL, 'l'}, {"merge", no_argument, NULL, 'm'}, {"number-lines", optional_argument, NULL, 'n'}, {"first-line-number", required_argument, NULL, 'N'}, {"indent", required_argument, NULL, 'o'}, {"no-file-warnings", no_argument, NULL, 'r'}, {"separator", optional_argument, NULL, 's'}, {"sep-string", optional_argument, NULL, 'S'}, {"omit-header", no_argument, NULL, 't'}, {"omit-pagination", no_argument, NULL, 'T'}, {"show-nonprinting", no_argument, NULL, 'v'}, {"width", required_argument, NULL, 'w'}, {"page-width", required_argument, NULL, 'W'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {0, 0, 0, 0} }; /* Return the number of columns that have either an open file or stored lines. */ static int cols_ready_to_print (void) { COLUMN *q; int i; int n; n = 0; for (q = column_vector, i = 0; i < columns; ++q, ++i) if (q->status == OPEN || q->status == FF_FOUND || /* With -b: To print a header only */ (storing_columns && q->lines_stored > 0 && q->lines_to_print > 0)) ++n; return n; } /* Estimate first_ / last_page_number using option +FIRST_PAGE:LAST_PAGE */ static void first_last_page (char *pages) { char *str1; if (*pages == ':') { error (0, 0, _("`--pages' invalid range of page numbers: `%s'"), pages); usage (2); } str1 = strchr (pages, ':'); if (str1 != NULL) *str1 = '\0'; { long int tmp_long; if (xstrtol (pages, NULL, 10, &tmp_long, "") != LONGINT_OK || tmp_long < 1 || tmp_long > INT_MAX) error (EXIT_FAILURE, 0, _("`--pages' invalid starting page number: `%s'"), pages); first_page_number = (int) tmp_long; } if (str1 == NULL) return; { long int tmp_long; if (xstrtol (str1 + 1, NULL, 10, &tmp_long, "") != LONGINT_OK || tmp_long <= 0 || tmp_long > INT_MAX) error (EXIT_FAILURE, 0, _("`--pages' invalid ending page number: `%s'"), str1 + 1); last_page_number = (int) tmp_long; } if (first_page_number > last_page_number) error (EXIT_FAILURE, 0, _("`--pages' starting page number is larger than ending page number")); } /* Estimate length of col_sep_string with option -S. */ static void separator_string (const char *optarg_S) { col_sep_length = (int) strlen (optarg_S); col_sep_string = (char *) xmalloc (col_sep_length + 1); strcpy (col_sep_string, optarg_S); } int main (int argc, char **argv) { int c; int accum = 0; int n_files; int old_options = FALSE; int old_w = FALSE; int old_s = FALSE; char **file_names; char const *short_options = (posix2_version () < 200112 ? COMMON_SHORT_OPTIONS "S::" : COMMON_SHORT_OPTIONS "S:"); program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); n_files = 0; file_names = (argc > 1 ? (char **) xmalloc ((argc - 1) * sizeof (char *)) : NULL); while ((c = getopt_long (argc, argv, short_options, long_options, NULL)) != -1) { if (ISDIGIT (c)) { accum = accum * 10 + c - '0'; columns = accum; explicit_columns = TRUE; continue; } if (accum > 0) /* reset for subsequent params */ accum = 0; switch (c) { case 0: /* getopt long option */ break; case 1: /* Non-option argument. */ if (*optarg == '+') { /* long option --page dominates old `+FIRST_PAGE ...' */ if (first_page_number <= 0 && last_page_number <= 0) first_last_page (optarg); } else file_names[n_files++] = optarg; break; case PAGES_OPTION: /* --pages=FIRST_PAGE[:LAST_PAGE] */ { /* dominates old opt +... */ if (optarg) first_last_page (optarg); else error (EXIT_FAILURE, 0, _("`--pages=FIRST_PAGE[:LAST_PAGE]' missing argument")); break; } case COLUMNS_OPTION: /* --columns=COLUMN */ { long int tmp_long; if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK || tmp_long <= 0 || tmp_long > INT_MAX) { error (EXIT_FAILURE, 0, _("`--columns=COLUMN' invalid number of columns: `%s'"), optarg); } columns = (int) tmp_long; break; } case 'a': print_across_flag = TRUE; storing_columns = FALSE; break; case 'b': balance_columns = TRUE; break; case 'c': use_cntrl_prefix = TRUE; break; case 'd': double_space = TRUE; break; case 'D': date_format = optarg; break; case 'e': if (optarg) getoptarg (optarg, 'e', &input_tab_char, &chars_per_input_tab); /* Could check tab width > 0. */ untabify_input = TRUE; break; case 'f': case 'F': use_form_feed = TRUE; break; case 'h': custom_header = optarg; break; case 'i': if (optarg) getoptarg (optarg, 'i', &output_tab_char, &chars_per_output_tab); /* Could check tab width > 0. */ tabify_output = TRUE; break; case 'J': join_lines = TRUE; break; case 'l': { long int tmp_long; if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK || tmp_long <= 0 || tmp_long > INT_MAX) { error (EXIT_FAILURE, 0, _("`-l PAGE_LENGTH' invalid number of lines: `%s'"), optarg); } lines_per_page = (int) tmp_long; break; } case 'm': parallel_files = TRUE; storing_columns = FALSE; break; case 'n': numbered_lines = TRUE; if (optarg) getoptarg (optarg, 'n', &number_separator, &chars_per_number); break; case 'N': skip_count = FALSE; { long int tmp_long; if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK || tmp_long > INT_MAX) { error (EXIT_FAILURE, 0, _("`-N NUMBER' invalid starting line number: `%s'"), optarg); } start_line_num = (int) tmp_long; break; } case 'o': { long int tmp_long; if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK || tmp_long < 0 || tmp_long > INT_MAX) error (EXIT_FAILURE, 0, _("`-o MARGIN' invalid line offset: `%s'"), optarg); chars_per_margin = (int) tmp_long; break; } case 'r': ignore_failed_opens = TRUE; break; case 's': old_options = TRUE; old_s = TRUE; if (!use_col_separator && optarg) separator_string (optarg); break; case 'S': old_s = FALSE; /* Reset an additional input of -s, -S dominates -s */ col_sep_string = ""; col_sep_length = 0; use_col_separator = TRUE; if (optarg) separator_string (optarg); break; case 't': extremities = FALSE; keep_FF = TRUE; break; case 'T': extremities = FALSE; keep_FF = FALSE; break; case 'v': use_esc_sequence = TRUE; break; case 'w': old_options = TRUE; old_w = TRUE; { long int tmp_long; if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK || tmp_long <= 0 || tmp_long > INT_MAX) error (EXIT_FAILURE, 0, _("`-w PAGE_WIDTH' invalid number of characters: `%s'"), optarg); if (!truncate_lines) chars_per_line = (int) tmp_long; break; } case 'W': old_w = FALSE; /* dominates -w */ truncate_lines = TRUE; { long int tmp_long; if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK || tmp_long <= 0 || tmp_long > INT_MAX) error (EXIT_FAILURE, 0, _("`-W PAGE_WIDTH' invalid number of characters: `%s'"), optarg); chars_per_line = (int) tmp_long; break; } case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (2); break; } } if (! date_format) date_format = (getenv ("POSIXLY_CORRECT") ? dcgettext (NULL, "%b %e %H:%M %Y", LC_TIME) : "%Y-%m-%d %H:%M"); /* Now we can set a reasonable initial value: */ if (first_page_number == 0) first_page_number = 1; if (parallel_files && explicit_columns) error (EXIT_FAILURE, 0, _("Cannot specify number of columns when printing in parallel.")); if (parallel_files && print_across_flag) error (EXIT_FAILURE, 0, _("Cannot specify both printing across and printing in parallel.")); /* Translate some old short options to new/long options. To meet downward compatibility with other UNIX pr utilities and some POSIX specifications. */ if (old_options) { if (old_w) { if (parallel_files || explicit_columns) { /* activate -W */ truncate_lines = TRUE; if (old_s) /* adapt HP-UX and SunOS: -s = no separator; activate -S */ use_col_separator = TRUE; } else /* old -w sets width with columns only activate -J */ join_lines = TRUE; } else if (!use_col_separator) { /* No -S option read */ if (old_s && (parallel_files || explicit_columns)) { if (!truncate_lines) { /* old -s (without -w and -W) annuls column alignment, uses fields, activate -J */ join_lines = TRUE; if (col_sep_length > 0) /* activate -S */ use_col_separator = TRUE; } else /* with -W */ /* adapt HP-UX and SunOS: -s = no separator; activate -S */ use_col_separator = TRUE; } } } for (; optind < argc; optind++) { file_names[n_files++] = argv[optind]; } if (n_files == 0) { /* No file arguments specified; read from standard input. */ print_files (0, NULL); } else { if (parallel_files) print_files (n_files, file_names); else { int i; for (i = 0; i < n_files; i++) print_files (1, &file_names[i]); } } cleanup (); if (have_read_stdin && fclose (stdin) == EOF) error (EXIT_FAILURE, errno, _("standard input")); if (failed_opens > 0) exit (EXIT_FAILURE); exit (EXIT_SUCCESS); } /* Parse options of the form -scNNN. Example: -nck, where 'n' is the option, c is the optional number separator, and k is the optional width of the field used when printing a number. */ static void getoptarg (char *arg, char switch_char, char *character, int *number) { if (!ISDIGIT (*arg)) *character = *arg++; if (*arg) { long int tmp_long; if (xstrtol (arg, NULL, 10, &tmp_long, "") != LONGINT_OK || tmp_long <= 0 || tmp_long > INT_MAX) { error (0, 0, _("`-%c' extra characters or invalid number in the argument: `%s'"), switch_char, arg); usage (2); } *number = (int) tmp_long; } } /* Set parameters related to formatting. */ static void init_parameters (int number_of_files) { int chars_used_by_number = 0; if (use_form_feed) { lines_per_header = 3; lines_per_footer = 0; } lines_per_body = lines_per_page - lines_per_header - lines_per_footer; if (lines_per_body <= 0) { extremities = FALSE; keep_FF = TRUE; } if (extremities == FALSE) lines_per_body = lines_per_page; if (double_space) lines_per_body = lines_per_body / 2; /* If input is stdin, cannot print parallel files. BSD dumps core on this. */ if (number_of_files == 0) parallel_files = FALSE; if (parallel_files) columns = number_of_files; /* One file, multi columns down: -b option is set to get a consistent formulation with "FF set by hand" in input files. */ if (storing_columns) balance_columns = TRUE; /* Tabification is assumed for multiple columns. */ if (columns > 1) { if (!use_col_separator) { /* Use default separator */ if (join_lines) col_sep_string = line_separator; else col_sep_string = column_separator; col_sep_length = 1; use_col_separator = TRUE; } /* It's rather pointless to define a TAB separator with column alignment */ else if (!join_lines && *col_sep_string == '\t') col_sep_string = column_separator; truncate_lines = TRUE; untabify_input = TRUE; tabify_output = TRUE; } else storing_columns = FALSE; /* -J dominates -w in any case */ if (join_lines) truncate_lines = FALSE; if (numbered_lines) { int tmp_i; int chars_per_default_tab = 8; line_count = start_line_num; /* To allow input tab-expansion (-e sensitive) use: if (number_separator == input_tab_char) number_width = chars_per_number + TAB_WIDTH (chars_per_input_tab, chars_per_number); */ /* Estimate chars_per_text without any margin and keep it constant. */ if (number_separator == '\t') number_width = chars_per_number + TAB_WIDTH (chars_per_default_tab, chars_per_number); else number_width = chars_per_number + 1; /* The number is part of the column width unless we are printing files in parallel. */ if (parallel_files) chars_used_by_number = number_width; /* We use power_10 to cut off the higher-order digits of the line_number in function add_line_number */ tmp_i = chars_per_number; for (power_10 = 1; tmp_i > 0; --tmp_i) power_10 = 10 * power_10; } chars_per_column = (chars_per_line - chars_used_by_number - (columns - 1) * col_sep_length) / columns; if (chars_per_column < 1) error (EXIT_FAILURE, 0, _("page width too narrow")); if (numbered_lines) { if (number_buff != NULL) free (number_buff); number_buff = (char *) xmalloc (2 * chars_per_number); } /* Pick the maximum between the tab width and the width of an escape sequence. The width of an escape sequence (4) isn't the lower limit any longer. We've to use 8 as the lower limit, if we use chars_per_default_tab = 8 to expand a tab which is not an input_tab-char. */ if (clump_buff != NULL) free (clump_buff); clump_buff = (int *) xmalloc ((chars_per_input_tab > 8 ? chars_per_input_tab : 8) * sizeof (int)); } /* Open the necessary files, maintaining a COLUMN structure for each column. With multiple files, each column p has a different p->fp. With single files, each column p has the same p->fp. Return 1 if (number_of_files > 0) and no files can be opened, 0 otherwise. With each column/file p, p->full_page_printed is initialized, see also open_file. */ static int init_fps (int number_of_files, char **av) { int i, files_left; COLUMN *p; FILE *firstfp; char const *firstname; total_files = 0; if (column_vector != NULLCOL) free ((char *) column_vector); column_vector = (COLUMN *) xmalloc (columns * sizeof (COLUMN)); if (parallel_files) { files_left = number_of_files; for (p = column_vector; files_left--; ++p, ++av) { if (open_file (*av, p) == 0) { --p; --columns; } } if (columns == 0) return 1; init_header ("", -1); } else { p = column_vector; if (number_of_files > 0) { if (open_file (*av, p) == 0) return 1; init_header (*av, fileno (p->fp)); p->lines_stored = 0; } else { p->name = _("standard input"); p->fp = stdin; have_read_stdin = TRUE; p->status = OPEN; p->full_page_printed = FALSE; ++total_files; init_header ("", -1); p->lines_stored = 0; } firstname = p->name; firstfp = p->fp; for (i = columns - 1, ++p; i; --i, ++p) { p->name = firstname; p->fp = firstfp; p->status = OPEN; p->full_page_printed = FALSE; p->lines_stored = 0; } } files_ready_to_read = total_files; return 0; } /* Determine print_func and char_func, the functions used by each column for printing and/or storing. Determine the horizontal position desired when we begin printing a column (p->start_position). */ static void init_funcs (void) { int i, h, h_next; COLUMN *p; h = chars_per_margin; if (!truncate_lines) h_next = ANYWHERE; else { /* When numbering lines of parallel files, we enlarge the first column to accomodate the number. Looks better than the Sys V approach. */ if (parallel_files && numbered_lines) h_next = h + chars_per_column + number_width; else h_next = h + chars_per_column; } /* Enlarge p->start_position of first column to use the same form of padding_not_printed with all columns. */ h = h + col_sep_length; /* This loop takes care of all but the rightmost column. */ for (p = column_vector, i = 1; i < columns; ++p, ++i) { if (storing_columns) /* One file, multi columns down. */ { p->char_func = store_char; p->print_func = print_stored; } else /* One file, multi columns across; or parallel files. */ { p->char_func = print_char; p->print_func = read_line; } /* Number only the first column when printing files in parallel. */ p->numbered = numbered_lines && (!parallel_files || i == 1); p->start_position = h; /* If we don't truncate lines, all start_positions are ANYWHERE, except the first column's start_position when using a margin. */ if (!truncate_lines) { h = ANYWHERE; h_next = ANYWHERE; } else { h = h_next + col_sep_length; h_next = h + chars_per_column; } } /* The rightmost column. Doesn't need to be stored unless we intend to balance columns on the last page. */ if (storing_columns && balance_columns) { p->char_func = store_char; p->print_func = print_stored; } else { p->char_func = print_char; p->print_func = read_line; } p->numbered = numbered_lines && (!parallel_files || i == 1); p->start_position = h; } /* Open a file. Return nonzero if successful, zero if failed. With each file p, p->full_page_printed is initialized, see also init_fps. */ static int open_file (char *name, COLUMN *p) { if (STREQ (name, "-")) { p->name = _("standard input"); p->fp = stdin; have_read_stdin = 1; } else { p->name = name; p->fp = fopen (name, "r"); } if (p->fp == NULL) { ++failed_opens; if (!ignore_failed_opens) error (0, errno, "%s", name); return 0; } p->status = OPEN; p->full_page_printed = FALSE; ++total_files; return 1; } /* Close the file in P. If we aren't dealing with multiple files in parallel, we change the status of all columns in the column list to reflect the close. */ static void close_file (COLUMN *p) { COLUMN *q; int i; if (p->status == CLOSED) return; if (ferror (p->fp)) error (EXIT_FAILURE, errno, "%s", p->name); if (p->fp != stdin && fclose (p->fp) == EOF) error (EXIT_FAILURE, errno, "%s", p->name); if (!parallel_files) { for (q = column_vector, i = columns; i; ++q, --i) { q->status = CLOSED; if (q->lines_stored == 0) { q->lines_to_print = 0; } } } else { p->status = CLOSED; p->lines_to_print = 0; } --files_ready_to_read; } /* Put a file on hold until we start a new page, since we've hit a form feed. If we aren't dealing with parallel files, we must change the status of all columns in the column list. */ static void hold_file (COLUMN *p) { COLUMN *q; int i; if (!parallel_files) for (q = column_vector, i = columns; i; ++q, --i) { if (storing_columns) q->status = FF_FOUND; else q->status = ON_HOLD; } else p->status = ON_HOLD; p->lines_to_print = 0; --files_ready_to_read; } /* Undo hold_file -- go through the column list and change any ON_HOLD columns to OPEN. Used at the end of each page. */ static void reset_status (void) { int i = columns; COLUMN *p; for (p = column_vector; i; --i, ++p) if (p->status == ON_HOLD) { p->status = OPEN; files_ready_to_read++; } if (storing_columns) { if (column_vector->status == CLOSED) /* We use the info to output an error message in skip_to_page. */ files_ready_to_read = 0; else files_ready_to_read = 1; } } /* Print a single file, or multiple files in parallel. Set up the list of columns, opening the necessary files. Allocate space for storing columns, if necessary. Skip to first_page_number, if user has asked to skip leading pages. Determine which functions are appropriate to store/print lines in each column. Print the file(s). */ static void print_files (int number_of_files, char **av) { init_parameters (number_of_files); if (init_fps (number_of_files, av)) return; if (storing_columns) init_store_cols (); if (first_page_number > 1) { if (!skip_to_page (first_page_number)) return; else page_number = first_page_number; } else page_number = 1; init_funcs (); line_number = line_count; while (print_page ()) ; } /* Initialize header information. If DESC is non-negative, it is a file descriptor open to FILENAME for reading. */ static void init_header (char *filename, int desc) { char *buf; char initbuf[MAX (256, INT_STRLEN_BOUND (long) + 1)]; struct stat st; struct tm *tm; /* If parallel files or standard input, use current date. */ if (STREQ (filename, "-")) desc = -1; if (desc < 0 || fstat (desc, &st) != 0) st.st_mtime = time (NULL); buf = initbuf; tm = localtime (&st.st_mtime); if (! tm) sprintf (buf, "%ld", (long) st.st_mtime); else { size_t bufsize = sizeof initbuf; for (;;) { *buf = '\1'; if (strftime (buf, bufsize, date_format, tm) || ! *buf) break; buf = alloca (bufsize *= 2); } } if (date_text) free (date_text); date_text = xstrdup (buf); file_text = custom_header ? custom_header : desc < 0 ? "" : filename; header_width_available = (chars_per_line - mbswidth (date_text, 0) - mbswidth (file_text, 0)); } /* Set things up for printing a page Scan through the columns ... Determine which are ready to print (i.e., which have lines stored or open files) Set p->lines_to_print appropriately (to p->lines_stored if we're storing, or lines_per_body if we're reading straight from the file) Keep track of this total so we know when to stop printing */ static void init_page (void) { int j; COLUMN *p; if (storing_columns) { store_columns (); for (j = columns - 1, p = column_vector; j; --j, ++p) { p->lines_to_print = p->lines_stored; } /* Last column. */ if (balance_columns) { p->lines_to_print = p->lines_stored; } /* Since we're not balancing columns, we don't need to store the rightmost column. Read it straight from the file. */ else { if (p->status == OPEN) { p->lines_to_print = lines_per_body; } else p->lines_to_print = 0; } } else for (j = columns, p = column_vector; j; --j, ++p) if (p->status == OPEN) { p->lines_to_print = lines_per_body; } else p->lines_to_print = 0; } /* Align empty columns and print separators. Empty columns will be formed by files with status ON_HOLD or CLOSED when printing multiple files in parallel. */ static void align_column (COLUMN *p) { padding_not_printed = p->start_position; if (padding_not_printed - col_sep_length > 0) { pad_across_to (padding_not_printed - col_sep_length); padding_not_printed = ANYWHERE; } if (use_col_separator) print_sep_string (); if (p->numbered) add_line_number (p); } /* Print one page. As long as there are lines left on the page and columns ready to print, Scan across the column list if the column has stored lines or the file is open pad to the appropriate spot print the column pad the remainder of the page with \n or \f as requested reset the status of all files -- any files which where on hold because of formfeeds are now put back into the lineup. */ static int print_page (void) { int j; int lines_left_on_page; COLUMN *p; /* Used as an accumulator (with | operator) of successive values of pad_vertically. The trick is to set pad_vertically to zero before each run through the inner loop, then after that loop, it tells us whether a line was actually printed (whether a newline needs to be output -- or two for double spacing). But those values have to be accumulated (in pv) so we can invoke pad_down properly after the outer loop completes. */ int pv; init_page (); if (cols_ready_to_print () == 0) return FALSE; if (extremities) print_a_header = TRUE; /* Don't pad unless we know a page was printed. */ pad_vertically = FALSE; pv = FALSE; lines_left_on_page = lines_per_body; if (double_space) lines_left_on_page *= 2; while (lines_left_on_page > 0 && cols_ready_to_print () > 0) { output_position = 0; spaces_not_printed = 0; separators_not_printed = 0; pad_vertically = FALSE; align_empty_cols = FALSE; empty_line = TRUE; for (j = 1, p = column_vector; j <= columns; ++j, ++p) { input_position = 0; if (p->lines_to_print > 0 || p->status == FF_FOUND) { FF_only = FALSE; padding_not_printed = p->start_position; if (!(p->print_func) (p)) read_rest_of_line (p); pv |= pad_vertically; --p->lines_to_print; if (p->lines_to_print <= 0) { if (cols_ready_to_print () <= 0) break; } /* File p changed its status to ON_HOLD or CLOSED */ if (parallel_files && p->status != OPEN) { if (empty_line) align_empty_cols = TRUE; else if (p->status == CLOSED || (p->status == ON_HOLD && FF_only)) align_column (p); } } else if (parallel_files) { /* File status ON_HOLD or CLOSED */ if (empty_line) align_empty_cols = TRUE; else align_column (p); } /* We need it also with an empty column */ if (use_col_separator) ++separators_not_printed; } if (pad_vertically) { putchar ('\n'); --lines_left_on_page; } if (cols_ready_to_print () <= 0 && !extremities) break; if (double_space && pv) { putchar ('\n'); --lines_left_on_page; } } if (lines_left_on_page == 0) for (j = 1, p = column_vector; j <= columns; ++j, ++p) if (p->status == OPEN) p->full_page_printed = TRUE; pad_vertically = pv; if (pad_vertically && extremities) pad_down (lines_left_on_page + lines_per_footer); else if (keep_FF && print_a_FF) { putchar ('\f'); print_a_FF = FALSE; } if (last_page_number && page_number > last_page_number) return FALSE; /* Stop printing with LAST_PAGE */ reset_status (); /* Change ON_HOLD to OPEN. */ return TRUE; /* More pages to go. */ } /* Allocate space for storing columns. This is necessary when printing multiple columns from a single file. Lines are stored consecutively in buff, separated by '\0'. The following doesn't apply any longer - any tuning possible? (We can't use a fixed offset since with the '-s' flag lines aren't truncated.) We maintain a list (line_vector) of pointers to the beginnings of lines in buff. We allocate one more than the number of lines because the last entry tells us the index of the last character, which we need to know in order to print the last line in buff. */ static void init_store_cols (void) { int total_lines = lines_per_body * columns; int chars_if_truncate = total_lines * (chars_per_column + 1); if (line_vector != NULL) free ((int *) line_vector); /* FIXME: here's where it was allocated. */ line_vector = (int *) xmalloc ((total_lines + 1) * sizeof (int *)); if (end_vector != NULL) free ((int *) end_vector); end_vector = (int *) xmalloc (total_lines * sizeof (int *)); if (buff != NULL) free (buff); buff_allocated = (use_col_separator ? 2 * chars_if_truncate : chars_if_truncate); /* Tune this. */ buff = (char *) xmalloc (buff_allocated); } /* Store all but the rightmost column. (Used when printing a single file in multiple downward columns) For each column set p->current_line to be the index in line_vector of the first line in the column For each line in the column store the line in buff add to line_vector the index of the line's first char buff_start is the index in buff of the first character in the current line. */ static void store_columns (void) { int i, j; int line = 0; int buff_start; int last_col; /* The rightmost column which will be saved in buff */ COLUMN *p; buff_current = 0; buff_start = 0; if (balance_columns) last_col = columns; else last_col = columns - 1; for (i = 1, p = column_vector; i <= last_col; ++i, ++p) p->lines_stored = 0; for (i = 1, p = column_vector; i <= last_col && files_ready_to_read; ++i, ++p) { p->current_line = line; for (j = lines_per_body; j && files_ready_to_read; --j) if (p->status == OPEN) /* Redundant. Clean up. */ { input_position = 0; if (!read_line (p)) read_rest_of_line (p); if (p->status == OPEN || buff_start != buff_current) { ++p->lines_stored; line_vector[line] = buff_start; end_vector[line++] = input_position; buff_start = buff_current; } } } /* Keep track of the location of the last char in buff. */ line_vector[line] = buff_start; if (balance_columns) balance (line); } static void balance (int total_stored) { COLUMN *p; int i, lines; int first_line = 0; for (i = 1, p = column_vector; i <= columns; ++i, ++p) { lines = total_stored / columns; if (i <= total_stored % columns) ++lines; p->lines_stored = lines; p->current_line = first_line; first_line += lines; } } /* Store a character in the buffer. */ static void store_char (int c) { if (buff_current >= buff_allocated) { /* May be too generous. */ buff_allocated = 2 * buff_allocated; buff = (char *) xrealloc (buff, buff_allocated * sizeof (char)); } buff[buff_current++] = (char) c; } static void add_line_number (COLUMN *p) { int i; char *s; int left_cut; /* Cutting off the higher-order digits is more informative than lower-order cut off*/ if (line_number < power_10) sprintf (number_buff, "%*d", chars_per_number, line_number); else { left_cut = line_number % power_10; sprintf (number_buff, "%0*d", chars_per_number, left_cut); } line_number++; s = number_buff; for (i = chars_per_number; i > 0; i--) (p->char_func) ((int) *s++); if (columns > 1) { /* Tabification is assumed for multiple columns, also for n-separators, but `default n-separator = TAB' hasn't been given priority over equal column_width also specified by POSIX. */ if (number_separator == '\t') { i = number_width - chars_per_number; while (i-- > 0) (p->char_func) ((int) ' '); } else (p->char_func) ((int) number_separator); } else /* To comply with POSIX, we avoid any expansion of default TAB separator with a single column output. No column_width requirement has to be considered. */ { (p->char_func) ((int) number_separator); if (number_separator == '\t') output_position = POS_AFTER_TAB (chars_per_output_tab, output_position); } if (truncate_lines && !parallel_files) input_position += number_width; } /* Print (or store) padding until the current horizontal position is position. */ static void pad_across_to (int position) { register int h = output_position; if (tabify_output) spaces_not_printed = position - output_position; else { while (++h <= position) putchar (' '); output_position = position; } } /* Pad to the bottom of the page. If the user has requested a formfeed, use one. Otherwise, use newlines. */ static void pad_down (int lines) { register int i; if (use_form_feed) putchar ('\f'); else for (i = lines; i; --i) putchar ('\n'); } /* Read the rest of the line. Read from the current column's file until an end of line is hit. Used when we've truncated a line and we no longer need to print or store its characters. */ static void read_rest_of_line (COLUMN *p) { register int c; FILE *f = p->fp; while ((c = getc (f)) != '\n') { if (c == '\f') { if ((c = getc (f)) != '\n') ungetc (c, f); if (keep_FF) print_a_FF = TRUE; hold_file (p); break; } else if (c == EOF) { close_file (p); break; } } } /* Read a line with skip_to_page. Read from the current column's file until an end of line is hit. Used when we read full lines to skip pages. With skip_to_page we have to check for FF-coincidence which is done in function read_line otherwise. Count lines of skipped pages to find the line number of 1st page printed relative to 1st line of input file (start_line_num). */ static void skip_read (COLUMN *p, int column_number) { register int c; FILE *f = p->fp; int i, single_ff = FALSE; COLUMN *q; /* Read 1st character in a line or any character succeeding a FF */ if ((c = getc (f)) == '\f' && p->full_page_printed) /* A FF-coincidence with a previous full_page_printed. To avoid an additional empty page, eliminate the FF */ if ((c = getc (f)) == '\n') c = getc (f); p->full_page_printed = FALSE; /* 1st character a FF means a single FF without any printable characters. Don't count it as a line with -n option. */ if (c == '\f') single_ff = TRUE; /* Preparing for a FF-coincidence: Maybe we finish that page without a FF found */ if (last_line) p->full_page_printed = TRUE; while (c != '\n') { if (c == '\f') { /* No FF-coincidence possible, no catching up of a FF-coincidence with next page */ if (last_line) { if (!parallel_files) for (q = column_vector, i = columns; i; ++q, --i) q->full_page_printed = FALSE; else p->full_page_printed = FALSE; } if ((c = getc (f)) != '\n') ungetc (c, f); hold_file (p); break; } else if (c == EOF) { close_file (p); break; } c = getc (f); } if (skip_count) if ((!parallel_files || column_number == 1) && !single_ff) ++line_count; } /* If we're tabifying output, When print_char encounters white space it keeps track of our desired horizontal position and delays printing until this function is called. */ static void print_white_space (void) { register int h_new; register int h_old = output_position; register int goal = h_old + spaces_not_printed; while (goal - h_old > 1 && (h_new = POS_AFTER_TAB (chars_per_output_tab, h_old)) <= goal) { putchar (output_tab_char); h_old = h_new; } while (++h_old <= goal) putchar (' '); output_position = goal; spaces_not_printed = 0; } /* Print column separators. We keep a count until we know that we'll be printing a line, then print_sep_string() is called. */ static void print_sep_string () { char *s; int l = col_sep_length; s = col_sep_string; if (separators_not_printed <= 0) { /* We'll be starting a line with chars_per_margin, anything else? */ if (spaces_not_printed > 0) print_white_space (); } else { for (; separators_not_printed > 0; --separators_not_printed) { while (l-- > 0) { /* 3 types of sep_strings: spaces only, spaces and chars, chars only */ if (*s == ' ') { /* We're tabifying output; consecutive spaces in sep_string may have to be converted to tabs */ s++; ++spaces_not_printed; } else { if (spaces_not_printed > 0) print_white_space (); putchar (*s++); ++output_position; } } /* sep_string ends with some spaces */ if (spaces_not_printed > 0) print_white_space (); } } } /* Print (or store, depending on p->char_func) a clump of N characters. */ static void print_clump (COLUMN *p, int n, int *clump) { while (n--) (p->char_func) (*clump++); } /* Print a character. Update the following comment: process-char hasn't been used any longer. If we're tabifying, all tabs have been converted to spaces by process_char(). Keep a count of consecutive spaces, and when a nonspace is encountered, call print_white_space() to print the required number of tabs and spaces. */ static void print_char (int c) { if (tabify_output) { if (c == ' ') { ++spaces_not_printed; return; } else if (spaces_not_printed > 0) print_white_space (); /* Nonprintables are assumed to have width 0, except '\b'. */ if (!ISPRINT (c)) { if (c == '\b') --output_position; } else ++output_position; } putchar (c); } /* Skip to page PAGE before printing. PAGE may be larger than total number of pages. */ static int skip_to_page (int page) { int n, i, j; COLUMN *p; for (n = 1; n < page; ++n) { for (i = 1; i < lines_per_body; ++i) { for (j = 1, p = column_vector; j <= columns; ++j, ++p) if (p->status == OPEN) skip_read (p, j); } last_line = TRUE; for (j = 1, p = column_vector; j <= columns; ++j, ++p) if (p->status == OPEN) skip_read (p, j); if (storing_columns) /* change FF_FOUND to ON_HOLD */ for (j = 1, p = column_vector; j <= columns; ++j, ++p) if (p->status != CLOSED) p->status = ON_HOLD; reset_status (); last_line = FALSE; if (files_ready_to_read < 1) { /* It's very helpful, normally the total number of pages is not known in advance. */ error (0, 0, _("starting page number larger than total number of pages: `%d'"), n); break; } } return files_ready_to_read > 0; } /* Print a header. Formfeeds are assumed to use up two lines at the beginning of the page. */ static void print_header (void) { char page_text[256 + INT_STRLEN_BOUND (int)]; int available_width; int lhs_spaces; int rhs_spaces; if (!use_form_feed) printf ("\n\n"); output_position = 0; pad_across_to (chars_per_margin); print_white_space (); /* The translator must ensure that formatting the translation of "Page %d" does not generate more than (sizeof page_text - 1) bytes. */ sprintf (page_text, _("Page %d"), page_number++); available_width = header_width_available - mbswidth (page_text, 0); available_width = MAX (0, available_width); lhs_spaces = available_width >> 1; rhs_spaces = available_width - lhs_spaces; printf ("%s%*s%s%*s%s\n\n\n", date_text, lhs_spaces, " ", file_text, rhs_spaces, " ", page_text); print_a_header = FALSE; output_position = 0; } /* Print (or store, if p->char_func is store_char()) a line. Read a character to determine whether we have a line or not. (We may hit EOF, \n, or \f) Once we know we have a line, set pad_vertically = TRUE, meaning it's safe to pad down at the end of the page, since we do have a page. print a header if needed. pad across to padding_not_printed if needed. print any separators which need to be printed. print a line number if it needs to be printed. Print the clump which corresponds to the first character. Enter a loop and keep printing until an end of line condition exists, or until we exceed chars_per_column. Return FALSE if we exceed chars_per_column before reading an end of line character, TRUE otherwise. */ static int read_line (COLUMN *p) { int c; int chars IF_LINT (= 0); int last_input_position; int j, k; COLUMN *q; /* read 1st character in each line or any character succeeding a FF: */ c = getc (p->fp); last_input_position = input_position; if (c == '\f' && p->full_page_printed) if ((c = getc (p->fp)) == '\n') c = getc (p->fp); p->full_page_printed = FALSE; switch (c) { case '\f': if ((c = getc (p->fp)) != '\n') ungetc (c, p->fp); FF_only = TRUE; if (print_a_header && !storing_columns) { pad_vertically = TRUE; print_header (); } else if (keep_FF) print_a_FF = TRUE; hold_file (p); return TRUE; case EOF: close_file (p); return TRUE; case '\n': break; default: chars = char_to_clump (c); } if (truncate_lines && input_position > chars_per_column) { input_position = last_input_position; return FALSE; } if (p->char_func != store_char) { pad_vertically = TRUE; if (print_a_header && !storing_columns) print_header (); if (parallel_files && align_empty_cols) { /* We have to align empty columns at the beginning of a line. */ k = separators_not_printed; separators_not_printed = 0; for (j = 1, q = column_vector; j <= k; ++j, ++q) { align_column (q); separators_not_printed += 1; } padding_not_printed = p->start_position; if (truncate_lines) spaces_not_printed = chars_per_column; else spaces_not_printed = 0; align_empty_cols = FALSE; } if (padding_not_printed - col_sep_length > 0) { pad_across_to (padding_not_printed - col_sep_length); padding_not_printed = ANYWHERE; } if (use_col_separator) print_sep_string (); } if (p->numbered) add_line_number (p); empty_line = FALSE; if (c == '\n') return TRUE; print_clump (p, chars, clump_buff); for (;;) { c = getc (p->fp); switch (c) { case '\n': return TRUE; case '\f': if ((c = getc (p->fp)) != '\n') ungetc (c, p->fp); if (keep_FF) print_a_FF = TRUE; hold_file (p); return TRUE; case EOF: close_file (p); return TRUE; } last_input_position = input_position; chars = char_to_clump (c); if (truncate_lines && input_position > chars_per_column) { input_position = last_input_position; return FALSE; } print_clump (p, chars, clump_buff); } } /* Print a line from buff. If this function has been called, we know we have "something to print". But it remains to be seen whether we have a real text page or an empty page (a single form feed) with/without a header only. Therefore first we set pad_vertically to TRUE and print a header if necessary. If FF_FOUND and we are using -t|-T option we omit any newline by setting pad_vertically to FALSE (see print_page). Otherwise we pad across if necessary, print separators if necessary and text of COLUMN *p. Return TRUE, meaning there is no need to call read_rest_of_line. */ static int print_stored (COLUMN *p) { COLUMN *q; int i; int line = p->current_line++; register char *first = &buff[line_vector[line]]; /* FIXME UMR: Uninitialized memory read: * This is occurring while in: print_stored [pr.c:2239] * Reading 4 bytes from 0x5148c in the heap. * Address 0x5148c is 4 bytes into a malloc'd block at 0x51488 of 676 bytes * This block was allocated from: malloc [rtlib.o] xmalloc [xmalloc.c:94] init_store_cols [pr.c:1648] */ register char *last = &buff[line_vector[line + 1]]; pad_vertically = TRUE; if (print_a_header) print_header (); if (p->status == FF_FOUND) { for (i = 1, q = column_vector; i <= columns; ++i, ++q) q->status = ON_HOLD; if (column_vector->lines_to_print <= 0) { if (!extremities) pad_vertically = FALSE; return TRUE; /* print a header only */ } } if (padding_not_printed - col_sep_length > 0) { pad_across_to (padding_not_printed - col_sep_length); padding_not_printed = ANYWHERE; } if (use_col_separator) print_sep_string (); while (first != last) print_char (*first++); if (spaces_not_printed == 0) { output_position = p->start_position + end_vector[line]; if (p->start_position - col_sep_length == chars_per_margin) output_position -= col_sep_length; } return TRUE; } /* Convert a character to the proper format and return the number of characters in the resulting clump. Increment input_position by the width of the clump. Tabs are converted to clumps of spaces. Nonprintable characters may be converted to clumps of escape sequences or control prefixes. Note: the width of a clump is not necessarily equal to the number of characters in clump_buff. (e.g, the width of '\b' is -1, while the number of characters is 1.) */ static int char_to_clump (int c) { register int *s = clump_buff; register int i; char esc_buff[4]; int width; int chars; int chars_per_c = 8; if (c == input_tab_char) chars_per_c = chars_per_input_tab; if (c == input_tab_char || c == '\t') { width = TAB_WIDTH (chars_per_c, input_position); if (untabify_input) { for (i = width; i; --i) *s++ = ' '; chars = width; } else { *s = c; chars = 1; } } else if (!ISPRINT (c)) { if (use_esc_sequence) { width = 4; chars = 4; *s++ = '\\'; sprintf (esc_buff, "%03o", c); for (i = 0; i <= 2; ++i) *s++ = (int) esc_buff[i]; } else if (use_cntrl_prefix) { if (c < 0200) { width = 2; chars = 2; *s++ = '^'; *s++ = c ^ 0100; } else { width = 4; chars = 4; *s++ = '\\'; sprintf (esc_buff, "%03o", c); for (i = 0; i <= 2; ++i) *s++ = (int) esc_buff[i]; } } else if (c == '\b') { width = -1; chars = 1; *s = c; } else { width = 0; chars = 1; *s = c; } } else { width = 1; chars = 1; *s = c; } input_position += width; return chars; } /* We've just printed some files and need to clean up things before looking for more options and printing the next batch of files. Free everything we've xmalloc'ed, except `header'. */ static void cleanup (void) { if (number_buff) free (number_buff); if (clump_buff) free (clump_buff); if (column_vector) free (column_vector); if (line_vector) free (line_vector); if (end_vector) free (end_vector); if (buff) free (buff); } /* Complain, print a usage message, and die. */ void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... [FILE]...\n\ "), program_name); fputs (_("\ Paginate or columnate FILE(s) for printing.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ +FIRST_PAGE[:LAST_PAGE], --pages=FIRST_PAGE[:LAST_PAGE]\n\ begin [stop] printing with page FIRST_[LAST_]PAGE\n\ -COLUMN, --columns=COLUMN\n\ output COLUMN columns and print columns down,\n\ unless -a is used. Balance number of lines in the\n\ columns on each page.\n\ "), stdout); fputs (_("\ -a, --across print columns across rather than down, used together\n\ with -COLUMN\n\ -c, --show-control-chars\n\ use hat notation (^G) and octal backslash notation\n\ -d, --double-space\n\ double space the output\n\ "), stdout); fputs (_("\ -D, --date-format=FORMAT\n\ use FORMAT for the header date\n\ -e[CHAR[WIDTH]], --expand-tabs[=CHAR[WIDTH]]\n\ expand input CHARs (TABs) to tab WIDTH (8)\n\ -F, -f, --form-feed\n\ use form feeds instead of newlines to separate pages\n\ (by a 3-line page header with -F or a 5-line header\n\ and trailer without -F)\n\ "), stdout); fputs (_("\ -h HEADER, --header=HEADER\n\ use a centered HEADER instead of filename in page header,\n\ -h \"\" prints a blank line, don't use -h\"\"\n\ -i[CHAR[WIDTH]], --output-tabs[=CHAR[WIDTH]]\n\ replace spaces with CHARs (TABs) to tab WIDTH (8)\n\ -J, --join-lines merge full lines, turns off -W line truncation, no column\n\ alignment, --sep-string[=STRING] sets separators\n\ "), stdout); fputs (_("\ -l PAGE_LENGTH, --length=PAGE_LENGTH\n\ set the page length to PAGE_LENGTH (66) lines\n\ (default number of lines of text 56, and with -F 63)\n\ -m, --merge print all files in parallel, one in each column,\n\ truncate lines, but join lines of full length with -J\n\ "), stdout); fputs (_("\ -n[SEP[DIGITS]], --number-lines[=SEP[DIGITS]]\n\ number lines, use DIGITS (5) digits, then SEP (TAB),\n\ default counting starts with 1st line of input file\n\ -N NUMBER, --first-line-number=NUMBER\n\ start counting with NUMBER at 1st line of first\n\ page printed (see +FIRST_PAGE)\n\ "), stdout); fputs (_("\ -o MARGIN, --indent=MARGIN\n\ offset each line with MARGIN (zero) spaces, do not\n\ affect -w or -W, MARGIN will be added to PAGE_WIDTH\n\ -r, --no-file-warnings\n\ omit warning when a file cannot be opened\n\ "), stdout); fputs (_("\ -s[CHAR],--separator[=CHAR]\n\ separate columns by a single character, default for CHAR\n\ is the <TAB> character without -w and \'no char\' with -w\n\ -s[CHAR] turns off line truncation of all 3 column\n\ options (-COLUMN|-a -COLUMN|-m) except -w is set\n\ "), stdout); fputs (_("\ -SSTRING, --sep-string[=STRING]\n\ "), stdout); fputs (_("\ separate columns by STRING,\n\ without -S: Default separator <TAB> with -J and <space>\n\ otherwise (same as -S\" \"), no effect on column options\n\ -t, --omit-header omit page headers and trailers\n\ "), stdout); fputs (_("\ -T, --omit-pagination\n\ omit page headers and trailers, eliminate any pagination\n\ by form feeds set in input files\n\ -v, --show-nonprinting\n\ use octal backslash notation\n\ -w PAGE_WIDTH, --width=PAGE_WIDTH\n\ set page width to PAGE_WIDTH (72) characters for\n\ multiple text-column output only, -s[char] turns off (72)\n\ "), stdout); fputs (_("\ -W PAGE_WIDTH, --page-width=PAGE_WIDTH\n\ set page width to PAGE_WIDTH (72) characters always,\n\ truncate lines, except -J option is set, no interference\n\ with -S or -s\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ -T implied by -l nn when nn <= 10 or <= 3 with -F. With no FILE, or when\n\ FILE is -, read standard input.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); }
/* Determine the number of screen columns needed for a string. Copyright (C) 2000-2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <stddef.h> /* Avoid a clash of our mbswidth() with a function of the same name defined in UnixWare 7.1.1 <wchar.h>. We need this #include before the #define below. */ #if HAVE_WCHAR_H # include <wchar.h> #endif /* Optional flags to influence mbswidth/mbsnwidth behavior. */ /* If this bit is set, return -1 upon finding an invalid or incomplete character. Otherwise, assume invalid characters have width 1. */ #define MBSW_REJECT_INVALID 1 /* If this bit is set, return -1 upon finding a non-printable character. Otherwise, assume unprintable characters have width 0 if they are control characters and 1 otherwise. */ #define MBSW_REJECT_UNPRINTABLE 2 /* Returns the number of screen columns needed for STRING. */ #define mbswidth gnu_mbswidth /* avoid clash with UnixWare 7.1.1 function */ extern int mbswidth (const char *string, int flags); /* Returns the number of screen columns needed for the NBYTES bytes starting at BUF. */ extern int mbsnwidth (const char *buf, size_t nbytes, int flags);
/* printf - format and print data Copyright (C) 1990-2003, Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Usage: printf format [argument...] A front end to the printf function that lets it be used from the shell. Backslash escapes: \" = double quote \\ = backslash \a = alert (bell) \b = backspace \c = produce no further output \f = form feed \n = new line \r = carriage return \t = horizontal tab \v = vertical tab \0ooo = octal number (ooo is 0 to 3 digits) \xhh = hexadecimal number (hhh is 1 to 2 digits) \uhhhh = 16-bit Unicode character (hhhh is 4 digits) \Uhhhhhhhh = 32-bit Unicode character (hhhhhhhh is 8 digits) Additional directive: %b = print an argument string, interpreting backslash escapes The `format' argument is re-used as many times as necessary to convert all of the given arguments. David MacKenzie <djm@gnu.ai.mit.edu> */ #include <config.h> #include <stdio.h> #include <sys/types.h> #include <getopt.h> #include "system.h" #include "long-options.h" #include "error.h" #include "closeout.h" #include "unicodeio.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "printf" #define AUTHORS "David MacKenzie" #ifndef STDC_HEADERS double strtod (); long int strtol (); unsigned long int strtoul (); #endif #define isodigit(c) ((c) >= '0' && (c) <= '7') #define hextobin(c) ((c) >= 'a' && (c) <= 'f' ? (c) - 'a' + 10 : \ (c) >= 'A' && (c) <= 'F' ? (c) - 'A' + 10 : (c) - '0') #define octtobin(c) ((c) - '0') /* A value for field_width or precision that indicates it was not specified. */ #define UNSPECIFIED INT_MIN /* The value to return to the calling program. */ static int exit_status; /* Non-zero if the POSIXLY_CORRECT environment variable is set. */ static int posixly_correct; /* This message appears in N_() here rather than just in _() below because the sole use would have been in a #define. */ static char *const cfcc_msg = N_("warning: %s: character(s) following character constant have been ignored"); /* The name this program was run with. */ char *program_name; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s FORMAT [ARGUMENT]...\n\ or: %s OPTION\n\ "), program_name, program_name); fputs (_("\ Print ARGUMENT(s) according to FORMAT.\n\ \n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ FORMAT controls the output as in C printf. Interpreted sequences are:\n\ \n\ \\\" double quote\n\ \\0NNN character with octal value NNN (0 to 3 digits)\n\ \\\\ backslash\n\ "), stdout); fputs (_("\ \\a alert (BEL)\n\ \\b backspace\n\ \\c produce no further output\n\ \\f form feed\n\ "), stdout); fputs (_("\ \\n new line\n\ \\r carriage return\n\ \\t horizontal tab\n\ \\v vertical tab\n\ "), stdout); fputs (_("\ \\xNN byte with hexadecimal value NN (1 to 2 digits)\n\ \n\ \\uNNNN character with hexadecimal value NNNN (4 digits)\n\ \\UNNNNNNNN character with hexadecimal value NNNNNNNN (8 digits)\n\ "), stdout); fputs (_("\ %% a single %\n\ %b ARGUMENT as a string with `\\' escapes interpreted\n\ \n\ and all C format specifications ending with one of diouxXfeEgGcs, with\n\ ARGUMENTs converted to proper type first. Variable widths are handled.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } static void verify (const char *s, const char *end) { if (errno) { error (0, errno, "%s", s); exit_status = 1; } else if (*end) { if (s == end) error (0, 0, _("%s: expected a numeric value"), s); else error (0, 0, _("%s: value not completely converted"), s); exit_status = 1; } } #define STRTOX(TYPE, FUNC_NAME, LIB_FUNC_EXPR) \ static TYPE \ FUNC_NAME (s) \ const char *s; \ { \ char *end; \ TYPE val; \ \ if (*s == '\"' || *s == '\'') \ { \ val = *(unsigned char *) ++s; \ /* If POSIXLY_CORRECT is not set, then give a warning that there \ are characters following the character constant and that GNU \ printf is ignoring those characters. If POSIXLY_CORRECT *is* \ set, then don't give the warning. */ \ if (*++s != 0 && !posixly_correct) \ error (0, 0, _(cfcc_msg), s); \ } \ else \ { \ errno = 0; \ val = LIB_FUNC_EXPR; \ verify (s, end); \ } \ return val; \ } \ STRTOX (unsigned long int, xstrtoul, (strtoul (s, &end, 0))) STRTOX (long int, xstrtol, (strtol (s, &end, 0))) STRTOX (double, xstrtod, (strtod (s, &end))) /* Output a single-character \ escape. */ static void print_esc_char (int c) { switch (c) { case 'a': /* Alert. */ putchar (7); break; case 'b': /* Backspace. */ putchar (8); break; case 'c': /* Cancel the rest of the output. */ exit (EXIT_SUCCESS); break; case 'f': /* Form feed. */ putchar (12); break; case 'n': /* New line. */ putchar (10); break; case 'r': /* Carriage return. */ putchar (13); break; case 't': /* Horizontal tab. */ putchar (9); break; case 'v': /* Vertical tab. */ putchar (11); break; default: putchar (c); break; } } /* Print a \ escape sequence starting at ESCSTART. Return the number of characters in the escape sequence besides the backslash. */ static int print_esc (const char *escstart) { register const char *p = escstart + 1; int esc_value = 0; /* Value of \nnn escape. */ int esc_length; /* Length of \nnn escape. */ if (!posixly_correct && *p == 'x') { /* A hexadecimal \xhh escape sequence must have 1 or 2 hex. digits. */ for (esc_length = 0, ++p; esc_length < 2 && ISXDIGIT (*p); ++esc_length, ++p) esc_value = esc_value * 16 + hextobin (*p); if (esc_length == 0) error (EXIT_FAILURE, 0, _("missing hexadecimal number in escape")); putchar (esc_value); } else if (*p == '0') { /* An octal \0ooo escape sequence has 0 to 3 octal digits after the leading \0. */ for (esc_length = 0, ++p; esc_length < 3 && isodigit (*p); ++esc_length, ++p) esc_value = esc_value * 8 + octtobin (*p); putchar (esc_value); } else if (*p && strchr ("\"\\abcfnrtv", *p)) print_esc_char (*p++); else if (!posixly_correct && (*p == 'u' || *p == 'U')) { char esc_char = *p; unsigned int uni_value; uni_value = 0; for (esc_length = (esc_char == 'u' ? 4 : 8), ++p; esc_length > 0; --esc_length, ++p) { if (!ISXDIGIT (*p)) error (EXIT_FAILURE, 0, _("missing hexadecimal number in escape")); uni_value = uni_value * 16 + hextobin (*p); } /* A universal character name shall not specify a character short identifier in the range 00000000 through 00000020, 0000007F through 0000009F, or 0000D800 through 0000DFFF inclusive. A universal character name shall not designate a character in the required character set. */ if ((uni_value <= 0x9f && uni_value != 0x24 && uni_value != 0x40 && uni_value != 0x60) || (uni_value >= 0xd800 && uni_value <= 0xdfff)) error (EXIT_FAILURE, 0, _("invalid universal character name \\%c%0*x"), esc_char, (esc_char == 'u' ? 4 : 8), uni_value); print_unicode_char (stdout, uni_value, 0); } else { putchar ('\\'); if (*p) { putchar (*p); p++; } } return p - escstart - 1; } /* Print string STR, evaluating \ escapes. */ static void print_esc_string (const char *str) { for (; *str; str++) if (*str == '\\') str += print_esc (str); else putchar (*str); } /* Output a % directive. START is the start of the directive, LENGTH is its length, and ARGUMENT is its argument. If FIELD_WIDTH or PRECISION is UNSPECIFIED, they are args for '*' values in those fields. */ static void print_direc (const char *start, size_t length, int field_width, int precision, const char *argument) { char *p; /* Null-terminated copy of % directive. */ p = xmalloc ((unsigned) (length + 1)); strncpy (p, start, length); p[length] = 0; switch (p[length - 1]) { case 'd': case 'i': if (field_width == UNSPECIFIED) { if (precision == UNSPECIFIED) printf (p, xstrtol (argument)); else printf (p, precision, xstrtol (argument)); } else { if (precision == UNSPECIFIED) printf (p, field_width, xstrtol (argument)); else printf (p, field_width, precision, xstrtol (argument)); } break; case 'o': case 'u': case 'x': case 'X': if (field_width == UNSPECIFIED) { if (precision == UNSPECIFIED) printf (p, xstrtoul (argument)); else printf (p, precision, xstrtoul (argument)); } else { if (precision == UNSPECIFIED) printf (p, field_width, xstrtoul (argument)); else printf (p, field_width, precision, xstrtoul (argument)); } break; case 'f': case 'e': case 'E': case 'g': case 'G': if (field_width == UNSPECIFIED) { if (precision == UNSPECIFIED) printf (p, xstrtod (argument)); else printf (p, precision, xstrtod (argument)); } else { if (precision == UNSPECIFIED) printf (p, field_width, xstrtod (argument)); else printf (p, field_width, precision, xstrtod (argument)); } break; case 'c': printf (p, *argument); break; case 's': if (field_width == UNSPECIFIED) { if (precision == UNSPECIFIED) printf (p, argument); else printf (p, precision, argument); } else { if (precision == UNSPECIFIED) printf (p, field_width, argument); else printf (p, field_width, precision, argument); } break; } free (p); } /* Print the text in FORMAT, using ARGV (with ARGC elements) for arguments to any `%' directives. Return the number of elements of ARGV used. */ static int print_formatted (const char *format, int argc, char **argv) { int save_argc = argc; /* Preserve original value. */ const char *f; /* Pointer into `format'. */ const char *direc_start; /* Start of % directive. */ size_t direc_length; /* Length of % directive. */ int field_width; /* Arg to first '*', or UNSPECIFIED if none. */ int precision; /* Arg to second '*', or UNSPECIFIED if none. */ for (f = format; *f; ++f) { switch (*f) { case '%': direc_start = f++; direc_length = 1; field_width = precision = UNSPECIFIED; if (*f == '%') { putchar ('%'); break; } if (*f == 'b') { if (argc > 0) { print_esc_string (*argv); ++argv; --argc; } break; } while (*f == ' ' || *f == '#' || *f == '+' || *f == '-') { ++f; ++direc_length; } if (*f == '*') { ++f; ++direc_length; if (argc > 0) { field_width = xstrtoul (*argv); if (field_width == UNSPECIFIED) error (EXIT_FAILURE, 0, _("invalid field width: %s"), *argv); ++argv; --argc; } else field_width = 0; } else while (ISDIGIT (*f)) { ++f; ++direc_length; } if (*f == '.') { ++f; ++direc_length; if (*f == '*') { ++f; ++direc_length; if (argc > 0) { precision = xstrtoul (*argv); if (precision == UNSPECIFIED) error (EXIT_FAILURE, 0, _("invalid precision: %s"), *argv); ++argv; --argc; } else precision = 0; } else while (ISDIGIT (*f)) { ++f; ++direc_length; } } if (*f == 'l' || *f == 'L' || *f == 'h') { ++f; ++direc_length; } if (! (*f && strchr ("diouxXfeEgGcs", *f))) error (EXIT_FAILURE, 0, _("%%%c: invalid directive"), *f); ++direc_length; if (argc > 0) { print_direc (direc_start, direc_length, field_width, precision, *argv); ++argv; --argc; } else print_direc (direc_start, direc_length, field_width, precision, ""); break; case '\\': f += print_esc (f); break; default: putchar (*f); } } return save_argc - argc; } int main (int argc, char **argv) { char *format; int args_used; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); exit_status = 0; /* Don't recognize --help or --version if POSIXLY_CORRECT is set. */ posixly_correct = (getenv ("POSIXLY_CORRECT") != NULL); if (!posixly_correct) parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, AUTHORS, usage); /* The above handles --help and --version. Since there is no other invocation of getopt, handle `--' here. */ if (1 < argc && STREQ (argv[1], "--")) { --argc; ++argv; } if (argc == 1) { fprintf (stderr, _("Usage: %s format [argument...]\n"), program_name); exit (EXIT_FAILURE); } format = argv[1]; argc -= 2; argv += 2; do { args_used = print_formatted (format, argc, argv); argc -= args_used; argv += args_used; } while (args_used > 0 && argc > 0); if (argc > 0) error (0, 0, _("warning: ignoring excess arguments, starting with `%s'"), argv[0]); exit (exit_status); }
/* Unicode character output to streams with locale dependent encoding. Copyright (C) 2000-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef UNICODEIO_H # define UNICODEIO_H # include <stdio.h> # ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif # endif /* Outputs the Unicode character CODE to the output stream STREAM. Upon failure, exit if exit_on_error is true, otherwise output a fallback notation. */ extern void print_unicode_char PARAMS ((FILE *stream, unsigned int code, int exit_on_error)); /* Simple success callback that outputs the converted string. The STREAM is passed as callback_arg. */ extern long fwrite_success_callback PARAMS ((const char *buf, size_t buflen, void *callback_arg)); #endif
/* Permuted index for GNU, with keywords in their context. Copyright (C) 1990, 1991, 1993, 1998-2003 Free Software Foundation, Inc. François Pinard <pinard@iro.umontreal.ca>, 1988. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. François Pinard <pinard@iro.umontreal.ca> */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include "system.h" #include "closeout.h" #include "argmatch.h" #include "bumpalloc.h" #include "diacrit.h" #include "error.h" #include "regex.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "ptx" #define AUTHORS "François Pinard" /* Number of possible characters in a byte. */ #define CHAR_SET_SIZE 256 #define ISODIGIT(C) ((C) >= '0' && (C) <= '7') #define HEXTOBIN(C) ((C) >= 'a' && (C) <= 'f' ? (C)-'a'+10 \ : (C) >= 'A' && (C) <= 'F' ? (C)-'A'+10 : (C)-'0') #define OCTTOBIN(C) ((C) - '0') /* Debugging the memory allocator. */ #if WITH_DMALLOC # define MALLOC_FUNC_CHECK 1 # include <dmalloc.h> #endif /* Global definitions. */ /* Reallocation step when swallowing non regular files. The value is not the actual reallocation step, but its base two logarithm. */ #define SWALLOW_REALLOC_LOG 12 /* Imported from "regex.c". */ #define Sword 1 /* The name this program was run with. */ char *program_name; /* Program options. */ enum Format { UNKNOWN_FORMAT, /* output format still unknown */ DUMB_FORMAT, /* output for a dumb terminal */ ROFF_FORMAT, /* output for `troff' or `nroff' */ TEX_FORMAT /* output for `TeX' or `LaTeX' */ }; int gnu_extensions = 1; /* trigger all GNU extensions */ int auto_reference = 0; /* references are `file_name:line_number:' */ int input_reference = 0; /* references at beginning of input lines */ int right_reference = 0; /* output references after right context */ int line_width = 72; /* output line width in characters */ int gap_size = 3; /* number of spaces between output fields */ const char *truncation_string = "/"; /* string used to mark line truncations */ const char *macro_name = "xx"; /* macro name for roff or TeX output */ enum Format output_format = UNKNOWN_FORMAT; /* output format */ int ignore_case = 0; /* fold lower to upper case for sorting */ const char *context_regex_string = NULL; /* raw regex for end of context */ const char *word_regex_string = NULL; /* raw regex for a keyword */ const char *break_file = NULL; /* name of the `Break characters' file */ const char *only_file = NULL; /* name of the `Only words' file */ const char *ignore_file = NULL; /* name of the `Ignore words' file */ /* A BLOCK delimit a region in memory of arbitrary size, like the copy of a whole file. A WORD is something smaller, its length should fit in a short integer. A WORD_TABLE may contain several WORDs. */ typedef struct { char *start; /* pointer to beginning of region */ char *end; /* pointer to end + 1 of region */ } BLOCK; typedef struct { char *start; /* pointer to beginning of region */ short size; /* length of the region */ } WORD; typedef struct { WORD *start; /* array of WORDs */ size_t length; /* number of entries */ } WORD_TABLE; /* Pattern description tables. */ /* For each character, provide its folded equivalent. */ unsigned char folded_chars[CHAR_SET_SIZE]; /* For each character, indicate if it is part of a word. */ char syntax_table[CHAR_SET_SIZE]; char *re_syntax_table = syntax_table; /* Compiled regex for end of context. */ struct re_pattern_buffer *context_regex; /* End of context pattern register indices. */ struct re_registers context_regs; /* Compiled regex for a keyword. */ struct re_pattern_buffer *word_regex; /* Keyword pattern register indices. */ struct re_registers word_regs; /* A word characters fastmap is used only when no word regexp has been provided. A word is then made up of a sequence of one or more characters allowed by the fastmap. Contains !0 if character allowed in word. Not only this is faster in most cases, but it simplifies the implementation of the Break files. */ char word_fastmap[CHAR_SET_SIZE]; /* Maximum length of any word read. */ int maximum_word_length; /* Maximum width of any reference used. */ int reference_max_width; /* Ignore and Only word tables. */ WORD_TABLE ignore_table; /* table of words to ignore */ WORD_TABLE only_table; /* table of words to select */ #define ALLOC_NEW_WORD(table) \ BUMP_ALLOC ((table)->start, (table)->length, 8, WORD) /* Source text table, and scanning macros. */ int number_input_files; /* number of text input files */ int total_line_count; /* total number of lines seen so far */ const char **input_file_name; /* array of text input file names */ int *file_line_count; /* array of `total_line_count' values at end */ BLOCK text_buffer; /* file to study */ char *text_buffer_maxend; /* allocated end of text_buffer */ /* SKIP_NON_WHITE used only for getting or skipping the reference. */ #define SKIP_NON_WHITE(cursor, limit) \ while (cursor < limit && !ISSPACE(*cursor)) \ cursor++ #define SKIP_WHITE(cursor, limit) \ while (cursor < limit && ISSPACE(*cursor)) \ cursor++ #define SKIP_WHITE_BACKWARDS(cursor, start) \ while (cursor > start && ISSPACE(cursor[-1])) \ cursor-- #define SKIP_SOMETHING(cursor, limit) \ if (word_regex_string) \ { \ int count; \ count = re_match (word_regex, cursor, limit - cursor, 0, NULL); \ cursor += count <= 0 ? 1 : count; \ } \ else if (word_fastmap[(unsigned char) *cursor]) \ while (cursor < limit && word_fastmap[(unsigned char) *cursor]) \ cursor++; \ else \ cursor++ /* Occurrences table. The `keyword' pointer provides the central word, which is surrounded by a left context and a right context. The `keyword' and `length' field allow full 8-bit characters keys, even including NULs. At other places in this program, the name `keyafter' refers to the keyword followed by its right context. The left context does not extend, towards the beginning of the file, further than a distance given by the `left' value. This value is relative to the keyword beginning, it is usually negative. This insures that, except for white space, we will never have to backward scan the source text, when it is time to generate the final output lines. The right context, indirectly attainable through the keyword end, does not extend, towards the end of the file, further than a distance given by the `right' value. This value is relative to the keyword beginning, it is usually positive. When automatic references are used, the `reference' value is the overall line number in all input files read so far, in this case, it is of type (int). When input references are used, the `reference' value indicates the distance between the keyword beginning and the start of the reference field, it is of type (DELTA) and usually negative. */ typedef short DELTA; /* to hold displacement within one context */ typedef struct { WORD key; /* description of the keyword */ DELTA left; /* distance to left context start */ DELTA right; /* distance to right context end */ int reference; /* reference descriptor */ } OCCURS; /* The various OCCURS tables are indexed by the language. But the time being, there is no such multiple language support. */ OCCURS *occurs_table[1]; /* all words retained from the read text */ size_t number_of_occurs[1]; /* number of used slots in occurs_table */ #define ALLOC_NEW_OCCURS(language) \ BUMP_ALLOC (occurs_table[language], number_of_occurs[language], 9, OCCURS) /* Communication among output routines. */ /* Indicate if special output processing is requested for each character. */ char edited_flag[CHAR_SET_SIZE]; int half_line_width; /* half of line width, reference excluded */ int before_max_width; /* maximum width of before field */ int keyafter_max_width; /* maximum width of keyword-and-after field */ int truncation_string_length; /* length of string used to flag truncation */ /* When context is limited by lines, wraparound may happen on final output: the `head' pointer gives access to some supplementary left context which will be seen at the end of the output line, the `tail' pointer gives access to some supplementary right context which will be seen at the beginning of the output line. */ BLOCK tail; /* tail field */ int tail_truncation; /* flag truncation after the tail field */ BLOCK before; /* before field */ int before_truncation; /* flag truncation before the before field */ BLOCK keyafter; /* keyword-and-after field */ int keyafter_truncation; /* flag truncation after the keyafter field */ BLOCK head; /* head field */ int head_truncation; /* flag truncation before the head field */ BLOCK reference; /* reference field for input reference mode */ /* Miscellaneous routines. */ /*------------------------------------------------------. | Duplicate string STRING, while evaluating \-escapes. | `------------------------------------------------------*/ /* Loosely adapted from GNU sh-utils printf.c code. */ static char * copy_unescaped_string (const char *string) { char *result; /* allocated result */ char *cursor; /* cursor in result */ int value; /* value of \nnn escape */ int length; /* length of \nnn escape */ result = xmalloc (strlen (string) + 1); cursor = result; while (*string) if (*string == '\\') { string++; switch (*string) { case 'x': /* \xhhh escape, 3 chars maximum */ value = 0; for (length = 0, string++; length < 3 && ISXDIGIT (*string); length++, string++) value = value * 16 + HEXTOBIN (*string); if (length == 0) { *cursor++ = '\\'; *cursor++ = 'x'; } else *cursor++ = value; break; case '0': /* \0ooo escape, 3 chars maximum */ value = 0; for (length = 0, string++; length < 3 && ISODIGIT (*string); length++, string++) value = value * 8 + OCTTOBIN (*string); *cursor++ = value; break; case 'a': /* alert */ #if __STDC__ *cursor++ = '\a'; #else *cursor++ = 7; #endif string++; break; case 'b': /* backspace */ *cursor++ = '\b'; string++; break; case 'c': /* cancel the rest of the output */ while (*string) string++; break; case 'f': /* form feed */ *cursor++ = '\f'; string++; break; case 'n': /* new line */ *cursor++ = '\n'; string++; break; case 'r': /* carriage return */ *cursor++ = '\r'; string++; break; case 't': /* horizontal tab */ *cursor++ = '\t'; string++; break; case 'v': /* vertical tab */ #if __STDC__ *cursor++ = '\v'; #else *cursor++ = 11; #endif string++; break; default: *cursor++ = '\\'; *cursor++ = *string++; break; } } else *cursor++ = *string++; *cursor = '\0'; return result; } /*-------------------------------------------------------------------. | Compile the regex represented by STRING, diagnose and abort if any | | error. Returns the compiled regex structure. | `-------------------------------------------------------------------*/ static struct re_pattern_buffer * alloc_and_compile_regex (const char *string) { struct re_pattern_buffer *pattern; /* newly allocated structure */ const char *message; /* error message returned by regex.c */ pattern = (struct re_pattern_buffer *) xmalloc (sizeof (struct re_pattern_buffer)); memset (pattern, 0, sizeof (struct re_pattern_buffer)); pattern->buffer = NULL; pattern->allocated = 0; pattern->translate = ignore_case ? (char *) folded_chars : NULL; pattern->fastmap = (char *) xmalloc ((size_t) CHAR_SET_SIZE); message = re_compile_pattern (string, (int) strlen (string), pattern); if (message) error (EXIT_FAILURE, 0, _("%s (for regexp `%s')"), message, string); /* The fastmap should be compiled before `re_match'. The following call is not mandatory, because `re_search' is always called sooner, and it compiles the fastmap if this has not been done yet. */ re_compile_fastmap (pattern); /* Do not waste extra allocated space. */ if (pattern->allocated > pattern->used) { pattern->buffer = (unsigned char *) xrealloc (pattern->buffer, (size_t) pattern->used); pattern->allocated = pattern->used; } return pattern; } /*------------------------------------------------------------------------. | This will initialize various tables for pattern match and compiles some | | regexps. | `------------------------------------------------------------------------*/ static void initialize_regex (void) { int character; /* character value */ /* Initialize the regex syntax table. */ for (character = 0; character < CHAR_SET_SIZE; character++) syntax_table[character] = ISALPHA (character) ? Sword : 0; /* Initialize the case folding table. */ if (ignore_case) for (character = 0; character < CHAR_SET_SIZE; character++) folded_chars[character] = TOUPPER (character); /* Unless the user already provided a description of the end of line or end of sentence sequence, select an end of line sequence to compile. If the user provided an empty definition, thus disabling end of line or sentence feature, make it NULL to speed up tests. If GNU extensions are enabled, use end of sentence like in GNU emacs. If disabled, use end of lines. */ if (context_regex_string) { if (!*context_regex_string) context_regex_string = NULL; } else if (gnu_extensions && !input_reference) context_regex_string = "[.?!][]\"')}]*\\($\\|\t\\| \\)[ \t\n]*"; else context_regex_string = "\n"; if (context_regex_string) context_regex = alloc_and_compile_regex (context_regex_string); /* If the user has already provided a non-empty regexp to describe words, compile it. Else, unless this has already been done through a user provided Break character file, construct a fastmap of characters that may appear in a word. If GNU extensions enabled, include only letters of the underlying character set. If disabled, include almost everything, even punctuations; stop only on white space. */ if (word_regex_string && *word_regex_string) word_regex = alloc_and_compile_regex (word_regex_string); else if (!break_file) { if (gnu_extensions) { /* Simulate \w+. */ for (character = 0; character < CHAR_SET_SIZE; character++) word_fastmap[character] = ISALPHA (character) ? 1 : 0; } else { /* Simulate [^ \t\n]+. */ memset (word_fastmap, 1, CHAR_SET_SIZE); word_fastmap[' '] = 0; word_fastmap['\t'] = 0; word_fastmap['\n'] = 0; } } } /*------------------------------------------------------------------------. | This routine will attempt to swallow a whole file name FILE_NAME into a | | contiguous region of memory and return a description of it into BLOCK. | | Standard input is assumed whenever FILE_NAME is NULL, empty or "-". | | | | Previously, in some cases, white space compression was attempted while | | inputting text. This was defeating some regexps like default end of | | sentence, which checks for two consecutive spaces. If white space | | compression is ever reinstated, it should be in output routines. | `------------------------------------------------------------------------*/ static void swallow_file_in_memory (const char *file_name, BLOCK *block) { int file_handle; /* file descriptor number */ struct stat stat_block; /* stat block for file */ size_t allocated_length; /* allocated length of memory buffer */ size_t used_length; /* used length in memory buffer */ int read_length; /* number of character gotten on last read */ /* As special cases, a file name which is NULL or "-" indicates standard input, which is already opened. In all other cases, open the file from its name. */ bool using_stdin = !file_name || !*file_name || strcmp (file_name, "-") == 0; if (using_stdin) file_handle = STDIN_FILENO; else if ((file_handle = open (file_name, O_RDONLY)) < 0) error (EXIT_FAILURE, errno, "%s", file_name); /* If the file is a plain, regular file, allocate the memory buffer all at once and swallow the file in one blow. In other cases, read the file repeatedly in smaller chunks until we have it all, reallocating memory once in a while, as we go. */ if (fstat (file_handle, &stat_block) < 0) error (EXIT_FAILURE, errno, "%s", file_name); if (S_ISREG (stat_block.st_mode)) { size_t in_memory_size; block->start = (char *) xmalloc ((size_t) stat_block.st_size); if ((in_memory_size = read (file_handle, block->start, (size_t) stat_block.st_size)) != stat_block.st_size) { #if MSDOS /* On MSDOS, in memory size may be smaller than the file size, because of end of line conversions. But it can never be smaller than half the file size, because the minimum is when all lines are empty and terminated by CR+LF. */ if (in_memory_size != (size_t)-1 && in_memory_size >= stat_block.st_size / 2) block->start = (char *) xrealloc (block->start, in_memory_size); else #endif /* not MSDOS */ error (EXIT_FAILURE, errno, "%s", file_name); } block->end = block->start + in_memory_size; } else { block->start = (char *) xmalloc ((size_t) 1 << SWALLOW_REALLOC_LOG); used_length = 0; allocated_length = (1 << SWALLOW_REALLOC_LOG); while (read_length = read (file_handle, block->start + used_length, allocated_length - used_length), read_length > 0) { used_length += read_length; if (used_length == allocated_length) { allocated_length += (1 << SWALLOW_REALLOC_LOG); block->start = (char *) xrealloc (block->start, allocated_length); } } if (read_length < 0) error (EXIT_FAILURE, errno, "%s", file_name); block->end = block->start + used_length; } /* Close the file, but only if it was not the standard input. */ if (! using_stdin && close (file_handle) != 0) error (EXIT_FAILURE, errno, "%s", file_name); } /* Sort and search routines. */ /*--------------------------------------------------------------------------. | Compare two words, FIRST and SECOND, and return 0 if they are identical. | | Return less than 0 if the first word goes before the second; return | | greater than 0 if the first word goes after the second. | | | | If a word is indeed a prefix of the other, the shorter should go first. | `--------------------------------------------------------------------------*/ static int compare_words (const void *void_first, const void *void_second) { #define first ((const WORD *) void_first) #define second ((const WORD *) void_second) int length; /* minimum of two lengths */ int counter; /* cursor in words */ int value; /* value of comparison */ length = first->size < second->size ? first->size : second->size; if (ignore_case) { for (counter = 0; counter < length; counter++) { value = (folded_chars [(unsigned char) (first->start[counter])] - folded_chars [(unsigned char) (second->start[counter])]); if (value != 0) return value; } } else { for (counter = 0; counter < length; counter++) { value = ((unsigned char) first->start[counter] - (unsigned char) second->start[counter]); if (value != 0) return value; } } return first->size - second->size; #undef first #undef second } /*-----------------------------------------------------------------------. | Decides which of two OCCURS, FIRST or SECOND, should lexicographically | | go first. In case of a tie, preserve the original order through a | | pointer comparison. | `-----------------------------------------------------------------------*/ static int compare_occurs (const void *void_first, const void *void_second) { #define first ((const OCCURS *) void_first) #define second ((const OCCURS *) void_second) int value; value = compare_words (&first->key, &second->key); return value == 0 ? first->key.start - second->key.start : value; #undef first #undef second } /*------------------------------------------------------------. | Return !0 if WORD appears in TABLE. Uses a binary search. | `------------------------------------------------------------*/ static int search_table (WORD *word, WORD_TABLE *table) { int lowest; /* current lowest possible index */ int highest; /* current highest possible index */ int middle; /* current middle index */ int value; /* value from last comparison */ lowest = 0; highest = table->length - 1; while (lowest <= highest) { middle = (lowest + highest) / 2; value = compare_words (word, table->start + middle); if (value < 0) highest = middle - 1; else if (value > 0) lowest = middle + 1; else return 1; } return 0; } /*---------------------------------------------------------------------. | Sort the whole occurs table in memory. Presumably, `qsort' does not | | take intermediate copies or table elements, so the sort will be | | stabilized throughout the comparison routine. | `---------------------------------------------------------------------*/ static void sort_found_occurs (void) { /* Only one language for the time being. */ qsort (occurs_table[0], number_of_occurs[0], sizeof (OCCURS), compare_occurs); } /* Parameter files reading routines. */ /*----------------------------------------------------------------------. | Read a file named FILE_NAME, containing a set of break characters. | | Build a content to the array word_fastmap in which all characters are | | allowed except those found in the file. Characters may be repeated. | `----------------------------------------------------------------------*/ static void digest_break_file (const char *file_name) { BLOCK file_contents; /* to receive a copy of the file */ char *cursor; /* cursor in file copy */ swallow_file_in_memory (file_name, &file_contents); /* Make the fastmap and record the file contents in it. */ memset (word_fastmap, 1, CHAR_SET_SIZE); for (cursor = file_contents.start; cursor < file_contents.end; cursor++) word_fastmap[(unsigned char) *cursor] = 0; if (!gnu_extensions) { /* If GNU extensions are enabled, the only way to avoid newline as a break character is to write all the break characters in the file with no newline at all, not even at the end of the file. If disabled, spaces, tabs and newlines are always considered as break characters even if not included in the break file. */ word_fastmap[' '] = 0; word_fastmap['\t'] = 0; word_fastmap['\n'] = 0; } /* Return the space of the file, which is no more required. */ free (file_contents.start); } /*-----------------------------------------------------------------------. | Read a file named FILE_NAME, containing one word per line, then | | construct in TABLE a table of WORD descriptors for them. The routine | | swallows the whole file in memory; this is at the expense of space | | needed for newlines, which are useless; however, the reading is fast. | `-----------------------------------------------------------------------*/ static void digest_word_file (const char *file_name, WORD_TABLE *table) { BLOCK file_contents; /* to receive a copy of the file */ char *cursor; /* cursor in file copy */ char *word_start; /* start of the current word */ swallow_file_in_memory (file_name, &file_contents); table->start = NULL; table->length = 0; /* Read the whole file. */ cursor = file_contents.start; while (cursor < file_contents.end) { /* Read one line, and save the word in contains. */ word_start = cursor; while (cursor < file_contents.end && *cursor != '\n') cursor++; /* Record the word in table if it is not empty. */ if (cursor > word_start) { ALLOC_NEW_WORD (table); table->start[table->length].start = word_start; table->start[table->length].size = cursor - word_start; table->length++; } /* This test allows for an incomplete line at end of file. */ if (cursor < file_contents.end) cursor++; } /* Finally, sort all the words read. */ qsort (table->start, table->length, (size_t) sizeof (WORD), compare_words); } /* Keyword recognition and selection. */ /*----------------------------------------------------------------------. | For each keyword in the source text, constructs an OCCURS structure. | `----------------------------------------------------------------------*/ static void find_occurs_in_text (void) { char *cursor; /* for scanning the source text */ char *scan; /* for scanning the source text also */ char *line_start; /* start of the current input line */ char *line_scan; /* newlines scanned until this point */ int reference_length; /* length of reference in input mode */ WORD possible_key; /* possible key, to ease searches */ OCCURS *occurs_cursor; /* current OCCURS under construction */ char *context_start; /* start of left context */ char *context_end; /* end of right context */ char *word_start; /* start of word */ char *word_end; /* end of word */ char *next_context_start; /* next start of left context */ /* reference_length is always used within `if (input_reference)'. However, GNU C diagnoses that it may be used uninitialized. The following assignment is merely to shut it up. */ reference_length = 0; /* Tracking where lines start is helpful for reference processing. In auto reference mode, this allows counting lines. In input reference mode, this permits finding the beginning of the references. The first line begins with the file, skip immediately this very first reference in input reference mode, to help further rejection any word found inside it. Also, unconditionally assigning these variable has the happy effect of shutting up lint. */ line_start = text_buffer.start; line_scan = line_start; if (input_reference) { SKIP_NON_WHITE (line_scan, text_buffer.end); reference_length = line_scan - line_start; SKIP_WHITE (line_scan, text_buffer.end); } /* Process the whole buffer, one line or one sentence at a time. */ for (cursor = text_buffer.start; cursor < text_buffer.end; cursor = next_context_start) { /* `context_start' gets initialized before the processing of each line, or once for the whole buffer if no end of line or sentence sequence separator. */ context_start = cursor; /* If a end of line or end of sentence sequence is defined and non-empty, `next_context_start' will be recomputed to be the end of each line or sentence, before each one is processed. If no such sequence, then `next_context_start' is set at the end of the whole buffer, which is then considered to be a single line or sentence. This test also accounts for the case of an incomplete line or sentence at the end of the buffer. */ if (context_regex_string && (re_search (context_regex, cursor, text_buffer.end - cursor, 0, text_buffer.end - cursor, &context_regs) >= 0)) next_context_start = cursor + context_regs.end[0]; else next_context_start = text_buffer.end; /* Include the separator into the right context, but not any suffix white space in this separator; this insures it will be seen in output and will not take more space than necessary. */ context_end = next_context_start; SKIP_WHITE_BACKWARDS (context_end, context_start); /* Read and process a single input line or sentence, one word at a time. */ while (1) { if (word_regex) /* If a word regexp has been compiled, use it to skip at the beginning of the next word. If there is no such word, exit the loop. */ { if (re_search (word_regex, cursor, context_end - cursor, 0, context_end - cursor, &word_regs) < 0) break; word_start = cursor + word_regs.start[0]; word_end = cursor + word_regs.end[0]; } else /* Avoid re_search and use the fastmap to skip to the beginning of the next word. If there is no more word in the buffer, exit the loop. */ { scan = cursor; while (scan < context_end && !word_fastmap[(unsigned char) *scan]) scan++; if (scan == context_end) break; word_start = scan; while (scan < context_end && word_fastmap[(unsigned char) *scan]) scan++; word_end = scan; } /* Skip right to the beginning of the found word. */ cursor = word_start; /* Skip any zero length word. Just advance a single position, then go fetch the next word. */ if (word_end == word_start) { cursor++; continue; } /* This is a genuine, non empty word, so save it as a possible key. Then skip over it. Also, maintain the maximum length of all words read so far. It is mandatory to take the maximum length of all words in the file, without considering if they are actually kept or rejected, because backward jumps at output generation time may fall in *any* word. */ possible_key.start = cursor; possible_key.size = word_end - word_start; cursor += possible_key.size; if (possible_key.size > maximum_word_length) maximum_word_length = possible_key.size; /* In input reference mode, update `line_start' from its previous value. Count the lines just in case auto reference mode is also selected. If it happens that the word just matched is indeed part of a reference; just ignore it. */ if (input_reference) { while (line_scan < possible_key.start) if (*line_scan == '\n') { total_line_count++; line_scan++; line_start = line_scan; SKIP_NON_WHITE (line_scan, text_buffer.end); reference_length = line_scan - line_start; } else line_scan++; if (line_scan > possible_key.start) continue; } /* Ignore the word if an `Ignore words' table exists and if it is part of it. Also ignore the word if an `Only words' table and if it is *not* part of it. It is allowed that both tables be used at once, even if this may look strange for now. Just ignore a word that would appear in both. If regexps are eventually implemented for these tables, the Ignore table could then reject words that would have been previously accepted by the Only table. */ if (ignore_file && search_table (&possible_key, &ignore_table)) continue; if (only_file && !search_table (&possible_key, &only_table)) continue; /* A non-empty word has been found. First of all, insure proper allocation of the next OCCURS, and make a pointer to where it will be constructed. */ ALLOC_NEW_OCCURS (0); occurs_cursor = occurs_table[0] + number_of_occurs[0]; /* Define the refence field, if any. */ if (auto_reference) { /* While auto referencing, update `line_start' from its previous value, counting lines as we go. If input referencing at the same time, `line_start' has been advanced earlier, and the following loop is never really executed. */ while (line_scan < possible_key.start) if (*line_scan == '\n') { total_line_count++; line_scan++; line_start = line_scan; SKIP_NON_WHITE (line_scan, text_buffer.end); } else line_scan++; occurs_cursor->reference = total_line_count; } else if (input_reference) { /* If only input referencing, `line_start' has been computed earlier to detect the case the word matched would be part of the reference. The reference position is simply the value of `line_start'. */ occurs_cursor->reference = (DELTA) (line_start - possible_key.start); if (reference_length > reference_max_width) reference_max_width = reference_length; } /* Exclude the reference from the context in simple cases. */ if (input_reference && line_start == context_start) { SKIP_NON_WHITE (context_start, context_end); SKIP_WHITE (context_start, context_end); } /* Completes the OCCURS structure. */ occurs_cursor->key = possible_key; occurs_cursor->left = context_start - possible_key.start; occurs_cursor->right = context_end - possible_key.start; number_of_occurs[0]++; } } } /* Formatting and actual output - service routines. */ /*-----------------------------------------. | Prints some NUMBER of spaces on stdout. | `-----------------------------------------*/ static void print_spaces (int number) { int counter; for (counter = number; counter > 0; counter--) putchar (' '); } /*-------------------------------------. | Prints the field provided by FIELD. | `-------------------------------------*/ static void print_field (BLOCK field) { char *cursor; /* Cursor in field to print */ int character; /* Current character */ int base; /* Base character, without diacritic */ int diacritic; /* Diacritic code for the character */ /* Whitespace is not really compressed. Instead, each white space character (tab, vt, ht etc.) is printed as one single space. */ for (cursor = field.start; cursor < field.end; cursor++) { character = (unsigned char) *cursor; if (edited_flag[character]) { /* First check if this is a diacriticized character. This works only for TeX. I do not know how diacriticized letters work with `roff'. Please someone explain it to me! */ diacritic = todiac (character); if (diacritic != 0 && output_format == TEX_FORMAT) { base = tobase (character); switch (diacritic) { case 1: /* Latin diphthongs */ switch (base) { case 'o': fputs ("\\oe{}", stdout); break; case 'O': fputs ("\\OE{}", stdout); break; case 'a': fputs ("\\ae{}", stdout); break; case 'A': fputs ("\\AE{}", stdout); break; default: putchar (' '); } break; case 2: /* Acute accent */ printf ("\\'%s%c", (base == 'i' ? "\\" : ""), base); break; case 3: /* Grave accent */ printf ("\\`%s%c", (base == 'i' ? "\\" : ""), base); break; case 4: /* Circumflex accent */ printf ("\\^%s%c", (base == 'i' ? "\\" : ""), base); break; case 5: /* Diaeresis */ printf ("\\\"%s%c", (base == 'i' ? "\\" : ""), base); break; case 6: /* Tilde accent */ printf ("\\~%s%c", (base == 'i' ? "\\" : ""), base); break; case 7: /* Cedilla */ printf ("\\c{%c}", base); break; case 8: /* Small circle beneath */ switch (base) { case 'a': fputs ("\\aa{}", stdout); break; case 'A': fputs ("\\AA{}", stdout); break; default: putchar (' '); } break; case 9: /* Strike through */ switch (base) { case 'o': fputs ("\\o{}", stdout); break; case 'O': fputs ("\\O{}", stdout); break; default: putchar (' '); } break; } } else /* This is not a diacritic character, so handle cases which are really specific to `roff' or TeX. All white space processing is done as the default case of this switch. */ switch (character) { case '"': /* In roff output format, double any quote. */ putchar ('"'); putchar ('"'); break; case '$': case '%': case '&': case '#': case '_': /* In TeX output format, precede these with a backslash. */ putchar ('\\'); putchar (character); break; case '{': case '}': /* In TeX output format, precede these with a backslash and force mathematical mode. */ printf ("$\\%c$", character); break; case '\\': /* In TeX output mode, request production of a backslash. */ fputs ("\\backslash{}", stdout); break; default: /* Any other flagged character produces a single space. */ putchar (' '); } } else putchar (*cursor); } } /* Formatting and actual output - planning routines. */ /*--------------------------------------------------------------------. | From information collected from command line options and input file | | readings, compute and fix some output parameter values. | `--------------------------------------------------------------------*/ static void fix_output_parameters (void) { int file_index; /* index in text input file arrays */ int line_ordinal; /* line ordinal value for reference */ char ordinal_string[12]; /* edited line ordinal for reference */ int reference_width; /* width for the whole reference */ int character; /* character ordinal */ const char *cursor; /* cursor in some constant strings */ /* In auto reference mode, the maximum width of this field is precomputed and subtracted from the overall line width. Add one for the column which separate the file name from the line number. */ if (auto_reference) { reference_max_width = 0; for (file_index = 0; file_index < number_input_files; file_index++) { line_ordinal = file_line_count[file_index] + 1; if (file_index > 0) line_ordinal -= file_line_count[file_index - 1]; sprintf (ordinal_string, "%d", line_ordinal); reference_width = strlen (ordinal_string); if (input_file_name[file_index]) reference_width += strlen (input_file_name[file_index]); if (reference_width > reference_max_width) reference_max_width = reference_width; } reference_max_width++; reference.start = (char *) xmalloc ((size_t) reference_max_width + 1); } /* If the reference appears to the left of the output line, reserve some space for it right away, including one gap size. */ if ((auto_reference || input_reference) && !right_reference) line_width -= reference_max_width + gap_size; /* The output lines, minimally, will contain from left to right a left context, a gap, and a keyword followed by the right context with no special intervening gap. Half of the line width is dedicated to the left context and the gap, the other half is dedicated to the keyword and the right context; these values are computed once and for all here. There also are tail and head wrap around fields, used when the keyword is near the beginning or the end of the line, or when some long word cannot fit in, but leave place from wrapped around shorter words. The maximum width of these fields are recomputed separately for each line, on a case by case basis. It is worth noting that it cannot happen that both the tail and head fields are used at once. */ half_line_width = line_width / 2; before_max_width = half_line_width - gap_size; keyafter_max_width = half_line_width; /* If truncation_string is the empty string, make it NULL to speed up tests. In this case, truncation_string_length will never get used, so there is no need to set it. */ if (truncation_string && *truncation_string) truncation_string_length = strlen (truncation_string); else truncation_string = NULL; if (gnu_extensions) { /* When flagging truncation at the left of the keyword, the truncation mark goes at the beginning of the before field, unless there is a head field, in which case the mark goes at the left of the head field. When flagging truncation at the right of the keyword, the mark goes at the end of the keyafter field, unless there is a tail field, in which case the mark goes at the end of the tail field. Only eight combination cases could arise for truncation marks: . None. . One beginning the before field. . One beginning the head field. . One ending the keyafter field. . One ending the tail field. . One beginning the before field, another ending the keyafter field. . One ending the tail field, another beginning the before field. . One ending the keyafter field, another beginning the head field. So, there is at most two truncation marks, which could appear both on the left side of the center of the output line, both on the right side, or one on either side. */ before_max_width -= 2 * truncation_string_length; keyafter_max_width -= 2 * truncation_string_length; } else { /* I never figured out exactly how UNIX' ptx plans the output width of its various fields. If GNU extensions are disabled, do not try computing the field widths correctly; instead, use the following formula, which does not completely imitate UNIX' ptx, but almost. */ keyafter_max_width -= 2 * truncation_string_length + 1; } /* Compute which characters need special output processing. Initialize by flagging any white space character. Some systems do not consider form feed as a space character, but we do. */ for (character = 0; character < CHAR_SET_SIZE; character++) edited_flag[character] = ISSPACE (character) != 0; edited_flag['\f'] = 1; /* Complete the special character flagging according to selected output format. */ switch (output_format) { case UNKNOWN_FORMAT: /* Should never happen. */ case DUMB_FORMAT: break; case ROFF_FORMAT: /* `Quote' characters should be doubled. */ edited_flag['"'] = 1; break; case TEX_FORMAT: /* Various characters need special processing. */ for (cursor = "$%&#_{}\\"; *cursor; cursor++) edited_flag[(unsigned char) *cursor] = 1; /* Any character with 8th bit set will print to a single space, unless it is diacriticized. */ for (character = 0200; character < CHAR_SET_SIZE; character++) edited_flag[character] = todiac (character) != 0; break; } } /*------------------------------------------------------------------. | Compute the position and length of all the output fields, given a | | pointer to some OCCURS. | `------------------------------------------------------------------*/ static void define_all_fields (OCCURS *occurs) { int tail_max_width; /* allowable width of tail field */ int head_max_width; /* allowable width of head field */ char *cursor; /* running cursor in source text */ char *left_context_start; /* start of left context */ char *right_context_end; /* end of right context */ char *left_field_start; /* conservative start for `head'/`before' */ int file_index; /* index in text input file arrays */ const char *file_name; /* file name for reference */ int line_ordinal; /* line ordinal for reference */ /* Define `keyafter', start of left context and end of right context. `keyafter' starts at the saved position for keyword and extend to the right from the end of the keyword, eating separators or full words, but not beyond maximum allowed width for `keyafter' field or limit for the right context. Suffix spaces will be removed afterwards. */ keyafter.start = occurs->key.start; keyafter.end = keyafter.start + occurs->key.size; left_context_start = keyafter.start + occurs->left; right_context_end = keyafter.start + occurs->right; cursor = keyafter.end; while (cursor < right_context_end && cursor <= keyafter.start + keyafter_max_width) { keyafter.end = cursor; SKIP_SOMETHING (cursor, right_context_end); } if (cursor <= keyafter.start + keyafter_max_width) keyafter.end = cursor; keyafter_truncation = truncation_string && keyafter.end < right_context_end; SKIP_WHITE_BACKWARDS (keyafter.end, keyafter.start); /* When the left context is wide, it might take some time to catch up from the left context boundary to the beginning of the `head' or `before' fields. So, in this case, to speed the catchup, we jump back from the keyword, using some secure distance, possibly falling in the middle of a word. A secure backward jump would be at least half the maximum width of a line, plus the size of the longest word met in the whole input. We conclude this backward jump by a skip forward of at least one word. In this manner, we should not inadvertently accept only part of a word. From the reached point, when it will be time to fix the beginning of `head' or `before' fields, we will skip forward words or delimiters until we get sufficiently near. */ if (-occurs->left > half_line_width + maximum_word_length) { left_field_start = keyafter.start - (half_line_width + maximum_word_length); SKIP_SOMETHING (left_field_start, keyafter.start); } else left_field_start = keyafter.start + occurs->left; /* `before' certainly ends at the keyword, but not including separating spaces. It starts after than the saved value for the left context, by advancing it until it falls inside the maximum allowed width for the before field. There will be no prefix spaces either. `before' only advances by skipping single separators or whole words. */ before.start = left_field_start; before.end = keyafter.start; SKIP_WHITE_BACKWARDS (before.end, before.start); while (before.start + before_max_width < before.end) SKIP_SOMETHING (before.start, before.end); if (truncation_string) { cursor = before.start; SKIP_WHITE_BACKWARDS (cursor, text_buffer.start); before_truncation = cursor > left_context_start; } else before_truncation = 0; SKIP_WHITE (before.start, text_buffer.end); /* The tail could not take more columns than what has been left in the left context field, and a gap is mandatory. It starts after the right context, and does not contain prefixed spaces. It ends at the end of line, the end of buffer or when the tail field is full, whichever comes first. It cannot contain only part of a word, and has no suffixed spaces. */ tail_max_width = before_max_width - (before.end - before.start) - gap_size; if (tail_max_width > 0) { tail.start = keyafter.end; SKIP_WHITE (tail.start, text_buffer.end); tail.end = tail.start; cursor = tail.end; while (cursor < right_context_end && cursor < tail.start + tail_max_width) { tail.end = cursor; SKIP_SOMETHING (cursor, right_context_end); } if (cursor < tail.start + tail_max_width) tail.end = cursor; if (tail.end > tail.start) { keyafter_truncation = 0; tail_truncation = truncation_string && tail.end < right_context_end; } else tail_truncation = 0; SKIP_WHITE_BACKWARDS (tail.end, tail.start); } else { /* No place left for a tail field. */ tail.start = NULL; tail.end = NULL; tail_truncation = 0; } /* `head' could not take more columns than what has been left in the right context field, and a gap is mandatory. It ends before the left context, and does not contain suffixed spaces. Its pointer is advanced until the head field has shrunk to its allowed width. It cannot contain only part of a word, and has no suffixed spaces. */ head_max_width = keyafter_max_width - (keyafter.end - keyafter.start) - gap_size; if (head_max_width > 0) { head.end = before.start; SKIP_WHITE_BACKWARDS (head.end, text_buffer.start); head.start = left_field_start; while (head.start + head_max_width < head.end) SKIP_SOMETHING (head.start, head.end); if (head.end > head.start) { before_truncation = 0; head_truncation = (truncation_string && head.start > left_context_start); } else head_truncation = 0; SKIP_WHITE (head.start, head.end); } else { /* No place left for a head field. */ head.start = NULL; head.end = NULL; head_truncation = 0; } if (auto_reference) { /* Construct the reference text in preallocated space from the file name and the line number. Find out in which file the reference occurred. Standard input yields an empty file name. Insure line numbers are one based, even if they are computed zero based. */ file_index = 0; while (file_line_count[file_index] < occurs->reference) file_index++; file_name = input_file_name[file_index]; if (!file_name) file_name = ""; line_ordinal = occurs->reference + 1; if (file_index > 0) line_ordinal -= file_line_count[file_index - 1]; sprintf (reference.start, "%s:%d", file_name, line_ordinal); reference.end = reference.start + strlen (reference.start); } else if (input_reference) { /* Reference starts at saved position for reference and extends right until some white space is met. */ reference.start = keyafter.start + (DELTA) occurs->reference; reference.end = reference.start; SKIP_NON_WHITE (reference.end, right_context_end); } } /* Formatting and actual output - control routines. */ /*----------------------------------------------------------------------. | Output the current output fields as one line for `troff' or `nroff'. | `----------------------------------------------------------------------*/ static void output_one_roff_line (void) { /* Output the `tail' field. */ printf (".%s \"", macro_name); print_field (tail); if (tail_truncation) fputs (truncation_string, stdout); putchar ('"'); /* Output the `before' field. */ fputs (" \"", stdout); if (before_truncation) fputs (truncation_string, stdout); print_field (before); putchar ('"'); /* Output the `keyafter' field. */ fputs (" \"", stdout); print_field (keyafter); if (keyafter_truncation) fputs (truncation_string, stdout); putchar ('"'); /* Output the `head' field. */ fputs (" \"", stdout); if (head_truncation) fputs (truncation_string, stdout); print_field (head); putchar ('"'); /* Conditionally output the `reference' field. */ if (auto_reference || input_reference) { fputs (" \"", stdout); print_field (reference); putchar ('"'); } putchar ('\n'); } /*---------------------------------------------------------. | Output the current output fields as one line for `TeX'. | `---------------------------------------------------------*/ static void output_one_tex_line (void) { BLOCK key; /* key field, isolated */ BLOCK after; /* after field, isolated */ char *cursor; /* running cursor in source text */ printf ("\\%s ", macro_name); putchar ('{'); print_field (tail); fputs ("}{", stdout); print_field (before); fputs ("}{", stdout); key.start = keyafter.start; after.end = keyafter.end; cursor = keyafter.start; SKIP_SOMETHING (cursor, keyafter.end); key.end = cursor; after.start = cursor; print_field (key); fputs ("}{", stdout); print_field (after); fputs ("}{", stdout); print_field (head); putchar ('}'); if (auto_reference || input_reference) { putchar ('{'); print_field (reference); putchar ('}'); } putchar ('\n'); } /*-------------------------------------------------------------------. | Output the current output fields as one line for a dumb terminal. | `-------------------------------------------------------------------*/ static void output_one_dumb_line (void) { if (!right_reference) { if (auto_reference) { /* Output the `reference' field, in such a way that GNU emacs next-error will handle it. The ending colon is taken from the gap which follows. */ print_field (reference); putchar (':'); print_spaces (reference_max_width + gap_size - (reference.end - reference.start) - 1); } else { /* Output the `reference' field and its following gap. */ print_field (reference); print_spaces (reference_max_width + gap_size - (reference.end - reference.start)); } } if (tail.start < tail.end) { /* Output the `tail' field. */ print_field (tail); if (tail_truncation) fputs (truncation_string, stdout); print_spaces (half_line_width - gap_size - (before.end - before.start) - (before_truncation ? truncation_string_length : 0) - (tail.end - tail.start) - (tail_truncation ? truncation_string_length : 0)); } else print_spaces (half_line_width - gap_size - (before.end - before.start) - (before_truncation ? truncation_string_length : 0)); /* Output the `before' field. */ if (before_truncation) fputs (truncation_string, stdout); print_field (before); print_spaces (gap_size); /* Output the `keyafter' field. */ print_field (keyafter); if (keyafter_truncation) fputs (truncation_string, stdout); if (head.start < head.end) { /* Output the `head' field. */ print_spaces (half_line_width - (keyafter.end - keyafter.start) - (keyafter_truncation ? truncation_string_length : 0) - (head.end - head.start) - (head_truncation ? truncation_string_length : 0)); if (head_truncation) fputs (truncation_string, stdout); print_field (head); } else if ((auto_reference || input_reference) && right_reference) print_spaces (half_line_width - (keyafter.end - keyafter.start) - (keyafter_truncation ? truncation_string_length : 0)); if ((auto_reference || input_reference) && right_reference) { /* Output the `reference' field. */ print_spaces (gap_size); print_field (reference); } putchar ('\n'); } /*------------------------------------------------------------------------. | Scan the whole occurs table and, for each entry, output one line in the | | appropriate format. | `------------------------------------------------------------------------*/ static void generate_all_output (void) { size_t occurs_index; /* index of keyword entry being processed */ OCCURS *occurs_cursor; /* current keyword entry being processed */ /* The following assignments are useful to provide default values in case line contexts or references are not used, in which case these variables would never be computed. */ tail.start = NULL; tail.end = NULL; tail_truncation = 0; head.start = NULL; head.end = NULL; head_truncation = 0; /* Loop over all keyword occurrences. */ occurs_cursor = occurs_table[0]; for (occurs_index = 0; occurs_index < number_of_occurs[0]; occurs_index++) { /* Compute the exact size of every field and whenever truncation flags are present or not. */ define_all_fields (occurs_cursor); /* Produce one output line according to selected format. */ switch (output_format) { case UNKNOWN_FORMAT: /* Should never happen. */ case DUMB_FORMAT: output_one_dumb_line (); break; case ROFF_FORMAT: output_one_roff_line (); break; case TEX_FORMAT: output_one_tex_line (); break; } /* Advance the cursor into the occurs table. */ occurs_cursor++; } } /* Option decoding and main program. */ /*------------------------------------------------------. | Print program identification and options, then exit. | `------------------------------------------------------*/ void usage (int status) { if (status != EXIT_SUCCESS) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... [INPUT]... (without -G)\n\ or: %s -G [OPTION]... [INPUT [OUTPUT]]\n"), program_name, program_name); fputs (_("\ Output a permuted index, including context, of the words in the input files.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ -A, --auto-reference output automatically generated references\n\ -C, --copyright display Copyright and copying conditions\n\ -G, --traditional behave more like System V `ptx'\n\ -F, --flag-truncation=STRING use STRING for flagging line truncations\n\ "), stdout); fputs (_("\ -M, --macro-name=STRING macro name to use instead of `xx'\n\ -O, --format=roff generate output as roff directives\n\ -R, --right-side-refs put references at right, not counted in -w\n\ -S, --sentence-regexp=REGEXP for end of lines or end of sentences\n\ -T, --format=tex generate output as TeX directives\n\ "), stdout); fputs (_("\ -W, --word-regexp=REGEXP use REGEXP to match each keyword\n\ -b, --break-file=FILE word break characters in this FILE\n\ -f, --ignore-case fold lower case to upper case for sorting\n\ -g, --gap-size=NUMBER gap size in columns between output fields\n\ -i, --ignore-file=FILE read ignore word list from FILE\n\ -o, --only-file=FILE read only word list from this FILE\n\ "), stdout); fputs (_("\ -r, --references first field of each line is a reference\n\ -t, --typeset-mode - not implemented -\n\ -w, --width=NUMBER output width in columns, reference excluded\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ With no FILE or if FILE is -, read Standard Input. `-F /' by default.\n\ "), stdout); } exit (status); } /*----------------------------------------------------------------------. | Main program. Decode ARGC arguments passed through the ARGV array of | | strings, then launch execution. | `----------------------------------------------------------------------*/ /* Long options equivalences. */ static const struct option long_options[] = { {"auto-reference", no_argument, NULL, 'A'}, {"break-file", required_argument, NULL, 'b'}, {"copyright", no_argument, NULL, 'C'}, {"flag-truncation", required_argument, NULL, 'F'}, {"ignore-case", no_argument, NULL, 'f'}, {"gap-size", required_argument, NULL, 'g'}, {"ignore-file", required_argument, NULL, 'i'}, {"macro-name", required_argument, NULL, 'M'}, {"only-file", required_argument, NULL, 'o'}, {"references", no_argument, NULL, 'r'}, {"right-side-refs", no_argument, NULL, 'R'}, {"format", required_argument, NULL, 10}, {"sentence-regexp", required_argument, NULL, 'S'}, {"traditional", no_argument, NULL, 'G'}, {"typeset-mode", no_argument, NULL, 't'}, {"width", required_argument, NULL, 'w'}, {"word-regexp", required_argument, NULL, 'W'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {0, 0, 0, 0}, }; static char const* const format_args[] = { "roff", "tex", 0 }; static enum Format const format_vals[] = { ROFF_FORMAT, TEX_FORMAT }; int main (int argc, char **argv) { int optchar; /* argument character */ int file_index; /* index in text input file arrays */ /* Decode program options. */ program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); #if HAVE_SETCHRCLASS setchrclass (NULL); #endif while (optchar = getopt_long (argc, argv, "ACF:GM:ORS:TW:b:i:fg:o:trw:", long_options, NULL), optchar != EOF) { switch (optchar) { default: usage (EXIT_FAILURE); case 0: break; case 'C': fputs (_("\ This program is free software; you can redistribute it and/or modify\n\ it under the terms of the GNU General Public License as published by\n\ the Free Software Foundation; either version 2, or (at your option)\n\ any later version.\n\ \n\ "), stdout); fputs (_("\ This program is distributed in the hope that it will be useful,\n\ but WITHOUT ANY WARRANTY; without even the implied warranty of\n\ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\ GNU General Public License for more details.\n\ \n\ "), stdout); fputs (_("\ You should have received a copy of the GNU General Public License\n\ along with this program; if not, write to the Free Software Foundation,\n\ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.\n"), stdout); exit (EXIT_SUCCESS); case 'G': gnu_extensions = 0; break; case 'b': break_file = optarg; break; case 'f': ignore_case = 1; break; case 'g': gap_size = atoi (optarg); break; case 'i': ignore_file = optarg; break; case 'o': only_file = optarg; break; case 'r': input_reference = 1; break; case 't': /* Yet to understand... */ break; case 'w': line_width = atoi (optarg); break; case 'A': auto_reference = 1; break; case 'F': truncation_string = copy_unescaped_string (optarg); break; case 'M': macro_name = optarg; break; case 'O': output_format = ROFF_FORMAT; break; case 'R': right_reference = 1; break; case 'S': context_regex_string = copy_unescaped_string (optarg); break; case 'T': output_format = TEX_FORMAT; break; case 'W': word_regex_string = copy_unescaped_string (optarg); break; case 10: output_format = XARGMATCH ("--format", optarg, format_args, format_vals); case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); } } /* Change the default Ignore file if one is defined. */ #ifdef DEFAULT_IGNORE_FILE if (!ignore_file) ignore_file = DEFAULT_IGNORE_FILE; #endif /* Process remaining arguments. If GNU extensions are enabled, process all arguments as input parameters. If disabled, accept at most two arguments, the second of which is an output parameter. */ if (optind == argc) { /* No more argument simply means: read standard input. */ input_file_name = (const char **) xmalloc (sizeof (const char *)); file_line_count = (int *) xmalloc (sizeof (int)); number_input_files = 1; input_file_name[0] = NULL; } else if (gnu_extensions) { number_input_files = argc - optind; input_file_name = (const char **) xmalloc (number_input_files * sizeof (const char *)); file_line_count = (int *) xmalloc (number_input_files * sizeof (int)); for (file_index = 0; file_index < number_input_files; file_index++) { input_file_name[file_index] = argv[optind]; if (!*argv[optind] || strcmp (argv[optind], "-") == 0) input_file_name[0] = NULL; else input_file_name[0] = argv[optind]; optind++; } } else { /* There is one necessary input file. */ number_input_files = 1; input_file_name = (const char **) xmalloc (sizeof (const char *)); file_line_count = (int *) xmalloc (sizeof (int)); if (!*argv[optind] || strcmp (argv[optind], "-") == 0) input_file_name[0] = NULL; else input_file_name[0] = argv[optind]; optind++; /* Redirect standard output, only if requested. */ if (optind < argc) { /* FIXME: don't fclose here? */ fclose (stdout); if (fopen (argv[optind], "w") == NULL) error (EXIT_FAILURE, errno, "%s", argv[optind]); optind++; } /* Diagnose any other argument as an error. */ if (optind < argc) usage (EXIT_FAILURE); } /* If the output format has not been explicitly selected, choose dumb terminal format if GNU extensions are enabled, else `roff' format. */ if (output_format == UNKNOWN_FORMAT) output_format = gnu_extensions ? DUMB_FORMAT : ROFF_FORMAT; /* Initialize the main tables. */ initialize_regex (); /* Read `Break character' file, if any. */ if (break_file) digest_break_file (break_file); /* Read `Ignore words' file and `Only words' files, if any. If any of these files is empty, reset the name of the file to NULL, to avoid unnecessary calls to search_table. */ if (ignore_file) { digest_word_file (ignore_file, &ignore_table); if (ignore_table.length == 0) ignore_file = NULL; } if (only_file) { digest_word_file (only_file, &only_table); if (only_table.length == 0) only_file = NULL; } /* Prepare to study all the input files. */ number_of_occurs[0] = 0; total_line_count = 0; maximum_word_length = 0; reference_max_width = 0; for (file_index = 0; file_index < number_input_files; file_index++) { /* Read the file in core, than study it. */ swallow_file_in_memory (input_file_name[file_index], &text_buffer); find_occurs_in_text (); /* Maintain for each file how many lines has been read so far when its end is reached. Incrementing the count first is a simple kludge to handle a possible incomplete line at end of file. */ total_line_count++; file_line_count[file_index] = total_line_count; } /* Do the output process phase. */ sort_found_occurs (); fix_output_parameters (); generate_all_output (); /* All done. */ exit (EXIT_SUCCESS); }
/* argmatch.h -- definitions and prototypes for argmatch.c Copyright (C) 1990, 1998, 1999, 2001, 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@ai.mit.edu> Modified by Akim Demaille <demaille@inf.enst.fr> */ #ifndef ARGMATCH_H_ # define ARGMATCH_H_ 1 # include <stddef.h> # define ARRAY_CARDINALITY(Array) (sizeof (Array) / sizeof *(Array)) # define ARGMATCH_CONSTRAINT(Arglist, Vallist) \ (ARRAY_CARDINALITY (Arglist) == ARRAY_CARDINALITY (Vallist) + 1) /* Assert there are as many real arguments as there are values (argument list ends with a NULL guard). ARGMATCH_VERIFY is preferred, since it is guaranteed to be checked at compile-time. ARGMATCH_ASSERT is for backward compatibility only. */ # define ARGMATCH_VERIFY(Arglist, Vallist) \ struct argmatch_verify \ { \ char argmatch_verify[ARGMATCH_CONSTRAINT(Arglist, Vallist) ? 1 : -1]; \ } # define ARGMATCH_ASSERT(Arglist, Vallist) \ assert (ARGMATCH_CONSTRAINT (Arglist, Vallist)) /* Return the index of the element of ARGLIST (NULL terminated) that matches with ARG. If VALLIST is not NULL, then use it to resolve false ambiguities (i.e., different matches of ARG but corresponding to the same values in VALLIST). */ int argmatch (char const *arg, char const *const *arglist, char const *vallist, size_t valsize); # define ARGMATCH(Arg, Arglist, Vallist) \ argmatch (Arg, Arglist, (char const *) (Vallist), sizeof *(Vallist)) /* xargmatch calls this function when it fails. This function should not return. By default, this is a function that calls ARGMATCH_DIE which in turn defaults to `exit (EXIT_FAILURE)'. */ typedef void (*argmatch_exit_fn) (void); extern argmatch_exit_fn argmatch_die; /* Report on stderr why argmatch failed. Report correct values. */ void argmatch_invalid (char const *context, char const *value, int problem); /* Left for compatibility with the old name invalid_arg */ # define invalid_arg(Context, Value, Problem) \ argmatch_invalid (Context, Value, Problem) /* Report on stderr the list of possible arguments. */ void argmatch_valid (char const *const *arglist, char const *vallist, size_t valsize); # define ARGMATCH_VALID(Arglist, Vallist) \ argmatch_valid (Arglist, (char const *) (Vallist), sizeof *(Vallist)) /* Same as argmatch, but upon failure, reports a explanation on the failure, and exits using the function EXIT_FN. */ int __xargmatch_internal (char const *context, char const *arg, char const *const *arglist, char const *vallist, size_t valsize, argmatch_exit_fn exit_fn); /* Programmer friendly interface to __xargmatch_internal. */ # define XARGMATCH(Context, Arg, Arglist, Vallist) \ ((Vallist) [__xargmatch_internal (Context, Arg, Arglist, \ (char const *) (Vallist), \ sizeof *(Vallist), \ argmatch_die)]) /* Convert a value into a corresponding argument. */ char const *argmatch_to_argument (char const *value, char const *const *arglist, char const *vallist, size_t valsize); # define ARGMATCH_TO_ARGUMENT(Value, Arglist, Vallist) \ argmatch_to_argument (Value, Arglist, \ (char const *) (Vallist), sizeof *(Vallist)) #endif /* ARGMATCH_H_ */
/* BUMP_ALLOC macro - increase table allocation by one element. Copyright (C) 1990, 1991, 1993, 1998, 2000 Free Software Foundation, Inc. François Pinard <pinard@iro.umontreal.ca>, 1990. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /*-------------------------------------------------------------------------. | Bump the allocation of the array pointed to by TABLE whenever required. | | The table already has already COUNT elements in it, this macro ensure it | | has enough space to accommodate at least one more element. Space is | | allocated (2 ^ EXPONENT) elements at a time. Each element of the array | | is of type TYPE. | `-------------------------------------------------------------------------*/ /* Routines `xmalloc' and `xrealloc' are called to do the actual memory management. This implies that the program will abort with a "memory exhausted" error if any problem arise. To work correctly, at least EXPONENT and TYPE should always be the same for all uses of this macro for any given TABLE. A secure way to achieve this is to never use this macro directly, but use it to define other macros, which would then be TABLE-specific. The first time through, COUNT is usually zero. Note that COUNT is not updated by this macro, but it should be update elsewhere, later. This is convenient, because it allows TABLE[COUNT] to refer to the new element at the end. Once its construction is completed, COUNT++ will record it in the table. Calling this macro several times in a row without updating COUNT is a bad thing to do. */ #define BUMP_ALLOC(Table, Count, Exponent, Type) \ BUMP_ALLOC_WITH_SIZE ((Table), (Count), (Exponent), Type, sizeof (Type)) /* In cases `sizeof TYPE' would not always yield the correct value for the size of each element entry, this macro accepts a supplementary SIZE argument. The EXPONENT, TYPE and SIZE parameters should still have the same value for all macro calls related to a specific TABLE. */ #define BUMP_ALLOC_WITH_SIZE(Table, Count, Exponent, Type, Size) \ do \ { \ if (((Count) & (~(~0 << (Exponent)))) == 0) \ { \ if ((Count) == 0) \ (Table) = (Type *) xmalloc ((1 << (Exponent)) * (Size)); \ else \ (Table) = (Type *) \ xrealloc ((Table), ((Count) + (1 << (Exponent))) * (Size)); \ } \ } \ while (0)
/* Diacritics processing for a few character codes. Copyright (C) 1990, 1991, 1992, 1993 Free Software Foundation, Inc. François Pinard <pinard@iro.umontreal.ca>, 1988. All this file is a temporary hack, waiting for locales in GNU. */ extern const char diacrit_base[]; /* characters without diacritics */ extern const char diacrit_diac[]; /* diacritic code for each character */ /* Returns CHAR without its diacritic. CHAR is known to be alphabetic. */ #define tobase(Char) (diacrit_base[(unsigned char) (Char)]) /* Returns a diacritic code for CHAR. CHAR is known to be alphabetic. */ #define todiac(Char) (diacrit_diac[(unsigned char) (Char)])
/* pwd - print current directory Copyright (C) 1994-1997, 1999-2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Jim Meyering <meyering@comco.com> */ #include <config.h> #include <stdio.h> #include <sys/types.h> #include "system.h" #include "closeout.h" #include "long-options.h" #include "error.h" #include "xgetcwd.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "pwd" #define AUTHORS "Jim Meyering" /* The name this program was run with. */ char *program_name; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("Usage: %s [OPTION]\n"), program_name); fputs (_("\ Print the full filename of the current working directory.\n\ \n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } int main (int argc, char **argv) { char *wd; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, AUTHORS, usage); if (argc != 1) error (0, 0, _("ignoring non-option arguments")); wd = xgetcwd (); if (wd == NULL) error (EXIT_FAILURE, errno, _("cannot get current directory")); printf ("%s\n", wd); exit (EXIT_SUCCESS); }
/* prototype for xgetcwd Copyright (C) 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ extern char *xgetcwd (void);
/* readlink -- display value of a symbolic link. Copyright (C) 2002, 2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Dmitry V. Levin */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <limits.h> #include <getopt.h> #include "system.h" #include "canonicalize.h" #include "error.h" #include "xreadlink.h" #include "long-options.h" #include "quote.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "readlink" #define AUTHORS "Dmitry V. Levin" /* Name this program was run with. */ char *program_name; /* If nonzero, canonicalize file name. */ static int canonicalize; /* If nonzero, do not output the trailing newline. */ static int no_newline; /* If nonzero, report error messages. */ static int verbose; static struct option const longopts[] = { {"canonicalize", no_argument, 0, 'f'}, {"no-newline", no_argument, 0, 'n'}, {"quiet", no_argument, 0, 'q'}, {"silent", no_argument, 0, 's'}, {"verbose", no_argument, 0, 'v'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; void usage (int status) { if (status != EXIT_SUCCESS) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("Usage: %s [OPTION]... FILE\n"), program_name); fputs (_("Display value of a symbolic link on standard output.\n\n"), stdout); fputs (_("\ -f, --canonicalize canonicalize by following every symlink in every\n\ component of the given path recursively\n\ -n, --no-newline do not output the trailing newline\n\ -q, --quiet,\n\ -s, --silent suppress most error messages\n\ -v, --verbose report error messages\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } int main (int argc, char *const argv[]) { const char *fname; char *value; int optc; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); if (argc < 1) error (EXIT_FAILURE, 0, _("too few arguments")); program_name = argv[0]; while ((optc = getopt_long (argc, argv, "fnqsv", longopts, NULL)) != -1) { switch (optc) { case 0: break; case 'f': canonicalize = 1; break; case 'n': no_newline = 1; break; case 'q': case 's': verbose = 0; break; case 'v': verbose = 1; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } if (optind >= argc) { error (EXIT_SUCCESS, 0, _("too few arguments")); usage (EXIT_FAILURE); } fname = argv[optind++]; if (optind < argc) { error (EXIT_SUCCESS, 0, _("too many arguments")); usage (EXIT_FAILURE); } value = (canonicalize ? canonicalize_file_name : xreadlink) (fname); if (value) { printf ("%s%s", value, (no_newline ? "" : "\n")); free (value); return EXIT_SUCCESS; } if (verbose) error (EXIT_SUCCESS, errno, "%s", fname); return EXIT_FAILURE; }
#if !HAVE_CANONICALIZE_FILE_NAME char *canonicalize_file_name (const char *); #endif
#ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif #endif char *xreadlink PARAMS ((char const *));
/* rmdir -- remove directories Copyright (C) 90, 91, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Options: -p, --parent Remove any parent dirs that are explicitly mentioned in an argument, if they become empty after the argument file is removed. David MacKenzie <djm@ai.mit.edu> */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include "system.h" #include "dirname.h" #include "error.h" #include "quote.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "rmdir" #define AUTHORS "David MacKenzie" #ifndef EEXIST # define EEXIST 0 #endif #ifndef ENOTEMPTY # define ENOTEMPTY 0 #endif /* The name this program was run with. */ char *program_name; /* If nonzero, remove empty parent directories. */ static int empty_paths; /* If nonzero, don't treat failure to remove a nonempty directory as an error. */ static int ignore_fail_on_non_empty; /* If nonzero, output a diagnostic for every directory processed. */ static int verbose; /* For long options that have no equivalent short option, use a non-character as a pseudo short option, starting with CHAR_MAX + 1. */ enum { IGNORE_FAIL_ON_NON_EMPTY_OPTION = CHAR_MAX + 1 }; static struct option const longopts[] = { /* Don't name this `--force' because it's not close enough in meaning to e.g. rm's -f option. */ {"ignore-fail-on-non-empty", no_argument, NULL, IGNORE_FAIL_ON_NON_EMPTY_OPTION}, {"path", no_argument, NULL, 'p'}, {"parents", no_argument, NULL, 'p'}, {"verbose", no_argument, NULL, 'v'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; /* Return nonzero if ERROR_NUMBER is one of the values associated with a failed rmdir due to non-empty target directory. */ static int errno_rmdir_non_empty (int error_number) { return (error_number == RMDIR_ERRNO_NOT_EMPTY); } /* Remove any empty parent directories of PATH. If PATH contains slash characters, at least one of them (beginning with the rightmost) is replaced with a NUL byte. */ static int remove_parents (char *path) { char *slash; int fail = 0; strip_trailing_slashes (path); while (1) { slash = strrchr (path, '/'); if (slash == NULL) break; /* Remove any characters after the slash, skipping any extra slashes in a row. */ while (slash > path && *slash == '/') --slash; slash[1] = 0; /* Give a diagnostic for each attempted removal if --verbose. */ if (verbose) error (0, 0, _("removing directory, %s"), path); fail = rmdir (path); if (fail) { /* Stop quietly if --ignore-fail-on-non-empty. */ if (ignore_fail_on_non_empty && errno_rmdir_non_empty (errno)) { fail = 0; } else { error (0, errno, "%s", quote (path)); } break; } } return fail; } void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("Usage: %s [OPTION]... DIRECTORY...\n"), program_name); fputs (_("\ Remove the DIRECTORY(ies), if they are empty.\n\ \n\ --ignore-fail-on-non-empty\n\ ignore each failure that is solely because a directory\n\ is non-empty\n\ "), stdout); fputs (_("\ -p, --parents remove DIRECTORY, then try to remove each directory\n\ component of that path name. E.g., `rmdir -p a/b/c' is\n\ similar to `rmdir a/b/c a/b a'.\n\ -v, --verbose output a diagnostic for every directory processed\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } int main (int argc, char **argv) { int errors = 0; int optc; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); empty_paths = 0; while ((optc = getopt_long (argc, argv, "pv", longopts, NULL)) != -1) { switch (optc) { case 0: /* Long option. */ break; case 'p': empty_paths = 1; break; case IGNORE_FAIL_ON_NON_EMPTY_OPTION: ignore_fail_on_non_empty = 1; break; case 'v': verbose = 1; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } if (optind == argc) { error (0, 0, _("too few arguments")); usage (EXIT_FAILURE); } for (; optind < argc; ++optind) { int fail; char *dir = argv[optind]; /* Give a diagnostic for each attempted removal if --verbose. */ if (verbose) error (0, 0, _("removing directory, %s"), dir); fail = rmdir (dir); if (fail) { if (ignore_fail_on_non_empty && errno_rmdir_non_empty (errno)) continue; error (0, errno, "%s", quote (dir)); errors = 1; } else if (empty_paths) { errors += remove_parents (dir); } } exit (errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE); }
/* seq - print sequence of numbers to standard output. Copyright (C) 1994-2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Ulrich Drepper. */ #include <config.h> #include <getopt.h> #include <math.h> #include <stdio.h> #include <sys/types.h> #include "system.h" #include "closeout.h" #include "error.h" #include "xstrtol.h" #include "xstrtod.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "seq" #define AUTHORS "Ulrich Drepper" /* If nonzero print all number with equal width. */ static int equal_width; /* The name that this program was run with. */ char *program_name; /* The string used to separate two numbers. */ static char *separator; /* The string output after all numbers have been output. Usually "\n" or "\0". */ /* FIXME: make this an option. */ static char *terminator = "\n"; /* The representation of the decimal point in the current locale. Always "." if the localeconv function is not supported. */ static char *decimal_point = "."; /* The starting number. */ static double first; /* The increment. */ static double step; /* The last number. */ static double last; static struct option const long_options[] = { { "equal-width", no_argument, NULL, 'w'}, { "format", required_argument, NULL, 'f'}, { "separator", required_argument, NULL, 's'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, { NULL, 0, NULL, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... LAST\n\ or: %s [OPTION]... FIRST LAST\n\ or: %s [OPTION]... FIRST INCREMENT LAST\n\ "), program_name, program_name, program_name); fputs (_("\ Print numbers from FIRST to LAST, in steps of INCREMENT.\n\ \n\ -f, --format=FORMAT use printf style floating-point FORMAT (default: %g)\n\ -s, --separator=STRING use STRING to separate numbers (default: \\n)\n\ -w, --equal-width equalize width by padding with leading zeroes\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ If FIRST or INCREMENT is omitted, it defaults to 1.\n\ FIRST, INCREMENT, and LAST are interpreted as floating point values.\n\ INCREMENT should be positive if FIRST is smaller than LAST, and negative\n\ otherwise. When given, the FORMAT argument must contain exactly one of\n\ the printf-style, floating point output formats %e, %f, %g\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } /* Read a double value from the command line. Return if the string is correct else signal error. */ static double scan_double_arg (const char *arg) { double ret_val; if (xstrtod (arg, NULL, &ret_val)) { error (0, 0, _("invalid floating point argument: %s"), arg); usage (EXIT_FAILURE); } return ret_val; } /* Check whether the format string is valid for a single `double' argument. Return 0 if not, 1 if correct. */ static int valid_format (const char *fmt) { while (*fmt != '\0') { if (*fmt == '%') { fmt++; if (*fmt != '%') break; } fmt++; } if (*fmt == '\0') return 0; fmt += strspn (fmt, "-+#0 '"); if (ISDIGIT (*fmt) || *fmt == '.') { fmt += strspn (fmt, "0123456789"); if (*fmt == '.') { ++fmt; fmt += strspn (fmt, "0123456789"); } } if (!(*fmt == 'e' || *fmt == 'f' || *fmt == 'g')) return 0; fmt++; while (*fmt != '\0') { if (*fmt == '%') { fmt++; if (*fmt != '%') return 0; } fmt++; } return 1; } /* Actually print the sequence of numbers in the specified range, with the given or default stepping and format. */ static int print_numbers (const char *fmt) { if (first > last) { int i; if (step >= 0) { error (0, 0, _("when the starting value is larger than the limit,\n\ the increment must be negative")); usage (EXIT_FAILURE); } printf (fmt, first); for (i = 1; /* empty */; i++) { double x = first + i * step; if (x < last) break; fputs (separator, stdout); printf (fmt, x); } } else { int i; if (step <= 0) { error (0, 0, _("when the starting value is smaller than the limit,\n\ the increment must be positive")); usage (EXIT_FAILURE); } printf (fmt, first); for (i = 1; /* empty */; i++) { double x = first + i * step; if (x > last) break; fputs (separator, stdout); printf (fmt, x); } } fputs (terminator, stdout); return 0; } #if HAVE_RINT && HAVE_MODF && HAVE_FLOOR /* Return a printf-style format string with which all selected numbers will format to strings of the same width. */ static char * get_width_format () { static char buffer[256]; int full_width; int frac_width; int width1, width2; double max_val; double min_val; double temp; if (first > last) { min_val = first - step * floor ((first - last) / step); max_val = first; } else { min_val = first; max_val = first + step * floor ((last - first) / step); } sprintf (buffer, "%g", rint (max_val)); if (buffer[strspn (buffer, "0123456789")] != '\0') return "%g"; width1 = strlen (buffer); if (min_val < 0.0) { double int_min_val = rint (min_val); sprintf (buffer, "%g", int_min_val); if (buffer[strspn (buffer, "-0123456789")] != '\0') return "%g"; /* On some systems, `seq -w -.1 .1 .1' results in buffer being `-0'. On others, it is just `0'. The former results in better output. */ width2 = (int_min_val == 0 ? 2 : strlen (buffer)); width1 = width1 > width2 ? width1 : width2; } full_width = width1; sprintf (buffer, "%g", 1.0 + modf (fabs (min_val), &temp)); width1 = strlen (buffer); if (width1 == 1) width1 = 0; else { if (buffer[0] != '1' /* FIXME: assumes that decimal_point is a single character string. */ || buffer[1] != decimal_point[0] || buffer[2 + strspn (&buffer[2], "0123456789")] != '\0') return "%g"; width1 -= 2; } sprintf (buffer, "%g", 1.0 + modf (fabs (step), &temp)); width2 = strlen (buffer); if (width2 == 1) width2 = 0; else { if (buffer[0] != '1' /* FIXME: assumes that decimal_point is a single character string. */ || buffer[1] != decimal_point[0] || buffer[2 + strspn (&buffer[2], "0123456789")] != '\0') return "%g"; width2 -= 2; } frac_width = width1 > width2 ? width1 : width2; if (frac_width) sprintf (buffer, "%%0%d.%df", full_width + 1 + frac_width, frac_width); else sprintf (buffer, "%%0%dg", full_width); return buffer; } #else /* one of the math functions rint, modf, floor is missing. */ static char * get_width_format (void) { /* We cannot compute the needed information to determine the correct answer. So we simply return a value that works for all cases. */ return "%g"; } #endif int main (int argc, char **argv) { int errs; int optc; int step_is_set; /* The printf(3) format used for output. */ char *format_str = NULL; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); equal_width = 0; separator = "\n"; first = 1.0; step_is_set = 0; /* Figure out the locale's idea of a decimal point. */ #if HAVE_LOCALECONV { struct lconv *locale; locale = localeconv (); /* Paranoia. */ if (locale && locale->decimal_point && locale->decimal_point[0] != '\0') decimal_point = locale->decimal_point; } #endif /* We have to handle negative numbers in the command line but this conflicts with the command line arguments. So explicitly check first whether the next argument looks like a negative number. */ while (optind < argc) { if (argv[optind][0] == '-' && ((optc = argv[optind][1]) == decimal_point[0] || ISDIGIT (optc))) { /* means negative number */ break; } optc = getopt_long (argc, argv, "+f:s:w", long_options, NULL); if (optc == -1) break; switch (optc) { case 0: break; case 'f': format_str = optarg; break; case 's': separator = optarg; break; case 'w': equal_width = 1; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } if (argc - optind < 1) { error (0, 0, _("too few arguments")); usage (EXIT_FAILURE); } if (3 < argc - optind) { error (0, 0, _("too many arguments")); usage (EXIT_FAILURE); } if (format_str && !valid_format (format_str)) { error (0, 0, _("invalid format string: `%s'"), format_str); usage (EXIT_FAILURE); } last = scan_double_arg (argv[optind++]); if (optind < argc) { first = last; last = scan_double_arg (argv[optind++]); if (optind < argc) { step = last; step_is_set = 1; last = scan_double_arg (argv[optind++]); } } if (format_str != NULL && equal_width) { error (0, 0, _("\ format string may not be specified when printing equal width strings")); usage (EXIT_FAILURE); } if (!step_is_set) { step = first <= last ? 1.0 : -1.0; } if (format_str == NULL) { if (equal_width) format_str = get_width_format (); else format_str = "%g"; } errs = print_numbers (format_str); exit (errs); }
#ifndef XSTRTOD_H # define XSTRTOD_H 1 # ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif # endif int xstrtod PARAMS ((const char *str, const char **ptr, double *result)); #endif /* not XSTRTOD_H */
/* sleep - delay for a specified amount of time. Copyright (C) 84, 1991-1997, 1999-2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <config.h> #include <stdio.h> #include <assert.h> #include <sys/types.h> #include <getopt.h> #include "system.h" #include "closeout.h" #include "error.h" #include "long-options.h" #include "xnanosleep.h" #include "xstrtod.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "sleep" #define AUTHORS N_ ("Jim Meyering and Paul Eggert") /* The name by which this program was run. */ char *program_name; static struct option const long_options[] = { {0, 0, 0, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s NUMBER[SUFFIX]...\n\ or: %s OPTION\n\ Pause for NUMBER seconds. SUFFIX may be `s' for seconds (the default),\n\ `m' for minutes, `h' for hours or `d' for days. Unlike most implementations\n\ that require NUMBER be an integer, here NUMBER may be an arbitrary floating\n\ point number.\n\ \n\ "), program_name, program_name); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } /* Given a floating point value *X, and a suffix character, SUFFIX_CHAR, scale *X by the multiplier implied by SUFFIX_CHAR. SUFFIX_CHAR may be the NUL byte or `s' to denote seconds, `m' for minutes, `h' for hours, or `d' for days. If SUFFIX_CHAR is invalid, don't modify *X and return nonzero. Otherwise return zero. */ static int apply_suffix (double *x, char suffix_char) { unsigned int multiplier; switch (suffix_char) { case 0: case 's': multiplier = 1; break; case 'm': multiplier = 60; break; case 'h': multiplier = 60 * 60; break; case 'd': multiplier = 60 * 60 * 24; break; default: multiplier = 0; } if (multiplier == 0) return 1; *x *= multiplier; return 0; } int main (int argc, char **argv) { int i; double seconds = 0.0; int c; int fail = 0; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, AUTHORS, usage); while ((c = getopt_long (argc, argv, "", long_options, NULL)) != -1) { switch (c) { case 0: break; default: usage (EXIT_FAILURE); } } if (argc == 1) { error (0, 0, _("too few arguments")); usage (EXIT_FAILURE); } for (i = optind; i < argc; i++) { double s; const char *p; if (xstrtod (argv[i], &p, &s) /* Nonnegative interval. */ || ! (0 <= s) /* No extra chars after the number and an optional s,m,h,d char. */ || (*p && *(p+1)) /* Check any suffix char and update S based on the suffix. */ || apply_suffix (&s, *p)) { error (0, 0, _("invalid time interval `%s'"), argv[i]); fail = 1; } seconds += s; } if (fail) usage (EXIT_FAILURE); if (xnanosleep (seconds)) error (EXIT_FAILURE, errno, _("cannot read realtime clock")); exit (EXIT_SUCCESS); }
int xnanosleep (double);
#ifndef PHYSMEM_H_ # define PHYSMEM_H_ 1 # if HAVE_CONFIG_H # include <config.h> # endif # ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif # endif double physmem_total PARAMS ((void)); double physmem_available PARAMS ((void)); #endif /* PHYSMEM_H_ */
#ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif #endif FILE *fopen_safer PARAMS ((char const *, char const *));
/* split.c -- split a file into pieces. Copyright (C) 88, 91, 1995-2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* By tege@sics.se, with rms. To do: * Implement -t CHAR or -t REGEX to specify break characters other than newline. */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include "system.h" #include "closeout.h" #include "dirname.h" #include "error.h" #include "full-read.h" #include "full-write.h" #include "posixver.h" #include "safe-read.h" #include "xstrtol.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "split" #define AUTHORS N_ ("Torbjorn Granlund and Richard M. Stallman") #define DEFAULT_SUFFIX_LENGTH 2 /* The name this program was run with. */ char *program_name; /* Base name of output files. */ static char const *outbase; /* Name of output files. */ static char *outfile; /* Pointer to the end of the prefix in OUTFILE. Suffixes are inserted here. */ static char *outfile_mid; /* Length of OUTFILE's suffix. */ static size_t suffix_length = DEFAULT_SUFFIX_LENGTH; /* Name of input file. May be "-". */ static char *infile; /* Descriptor on which input file is open. */ static int input_desc; /* Descriptor on which output file is open. */ static int output_desc; /* If nonzero, print a diagnostic on standard error just before each output file is opened. */ static int verbose; static struct option const longopts[] = { {"bytes", required_argument, NULL, 'b'}, {"lines", required_argument, NULL, 'l'}, {"line-bytes", required_argument, NULL, 'C'}, {"suffix-length", required_argument, NULL, 'a'}, {"verbose", no_argument, &verbose, 0}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION] [INPUT [PREFIX]]\n\ "), program_name); fputs (_("\ Output fixed-size pieces of INPUT to PREFIXaa, PREFIXab, ...; default\n\ PREFIX is `x'. With no INPUT, or when INPUT is -, read standard input.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fprintf (stdout, _("\ -a, --suffix-length=N use suffixes of length N (default %d)\n\ -b, --bytes=SIZE put SIZE bytes per output file\n\ -C, --line-bytes=SIZE put at most SIZE bytes of lines per output file\n\ -l, --lines=NUMBER put NUMBER lines per output file\n\ "), DEFAULT_SUFFIX_LENGTH); fputs (_("\ --verbose print a diagnostic to standard error just\n\ before each output file is opened\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ SIZE may have a multiplier suffix: b for 512, k for 1K, m for 1 Meg.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } /* Compute the next sequential output file name and store it into the string `outfile'. */ static void next_file_name (void) { if (! outfile) { /* Allocate and initialize the first file name. */ size_t outbase_length = strlen (outbase); size_t outfile_length = outbase_length + suffix_length; if (outfile_length + 1 < outbase_length) xalloc_die (); outfile = xmalloc (outfile_length + 1); outfile_mid = outfile + outbase_length; memcpy (outfile, outbase, outbase_length); memset (outfile_mid, 'a', suffix_length); outfile[outfile_length] = 0; #if ! _POSIX_NO_TRUNC && HAVE_PATHCONF && defined _PC_NAME_MAX /* POSIX requires that if the output file name is too long for its directory, `split' must fail without creating any files. This must be checked for explicitly on operating systems that silently truncate file names. */ { char *dir = dir_name (outfile); long name_max = pathconf (dir, _PC_NAME_MAX); if (0 <= name_max && name_max < base_len (base_name (outfile))) error (EXIT_FAILURE, ENAMETOOLONG, "%s", outfile); free (dir); } #endif } else { /* Increment the suffix in place, if possible. */ char *p; for (p = outfile_mid + suffix_length; outfile_mid < p; *--p = 'a') if (p[-1]++ != 'z') return; error (EXIT_FAILURE, 0, _("Output file suffixes exhausted")); } } /* Write BYTES bytes at BP to an output file. If NEW_FILE_FLAG is nonzero, open the next output file. Otherwise add to the same output file already in use. */ static void cwrite (int new_file_flag, const char *bp, size_t bytes) { if (new_file_flag) { if (output_desc >= 0 && close (output_desc) < 0) error (EXIT_FAILURE, errno, "%s", outfile); next_file_name (); if (verbose) fprintf (stderr, _("creating file `%s'\n"), outfile); output_desc = open (outfile, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666); if (output_desc < 0) error (EXIT_FAILURE, errno, "%s", outfile); } if (full_write (output_desc, bp, bytes) != bytes) error (EXIT_FAILURE, errno, "%s", outfile); } /* Split into pieces of exactly NCHARS bytes. Use buffer BUF, whose size is BUFSIZE. */ static void bytes_split (size_t nchars, char *buf, size_t bufsize) { size_t n_read; int new_file_flag = 1; size_t to_read; size_t to_write = nchars; char *bp_out; do { n_read = full_read (input_desc, buf, bufsize); if (n_read == SAFE_READ_ERROR) error (EXIT_FAILURE, errno, "%s", infile); bp_out = buf; to_read = n_read; for (;;) { if (to_read < to_write) { if (to_read) /* do not write 0 bytes! */ { cwrite (new_file_flag, bp_out, to_read); to_write -= to_read; new_file_flag = 0; } break; } cwrite (new_file_flag, bp_out, to_write); bp_out += to_write; to_read -= to_write; new_file_flag = 1; to_write = nchars; } } while (n_read == bufsize); } /* Split into pieces of exactly NLINES lines. Use buffer BUF, whose size is BUFSIZE. */ static void lines_split (size_t nlines, char *buf, size_t bufsize) { size_t n_read; char *bp, *bp_out, *eob; int new_file_flag = 1; size_t n = 0; do { n_read = full_read (input_desc, buf, bufsize); if (n_read == SAFE_READ_ERROR) error (EXIT_FAILURE, errno, "%s", infile); bp = bp_out = buf; eob = bp + n_read; *eob = '\n'; for (;;) { bp = memchr (bp, '\n', eob - bp + 1); if (bp == eob) { if (eob != bp_out) /* do not write 0 bytes! */ { size_t len = eob - bp_out; cwrite (new_file_flag, bp_out, len); new_file_flag = 0; } break; } ++bp; if (++n >= nlines) { cwrite (new_file_flag, bp_out, bp - bp_out); bp_out = bp; new_file_flag = 1; n = 0; } } } while (n_read == bufsize); } /* Split into pieces that are as large as possible while still not more than NCHARS bytes, and are split on line boundaries except where lines longer than NCHARS bytes occur. */ static void line_bytes_split (size_t nchars) { size_t n_read; char *bp; int eof = 0; size_t n_buffered = 0; char *buf = (char *) xmalloc (nchars); do { /* Fill up the full buffer size from the input file. */ n_read = full_read (input_desc, buf + n_buffered, nchars - n_buffered); if (n_read == SAFE_READ_ERROR) error (EXIT_FAILURE, errno, "%s", infile); n_buffered += n_read; if (n_buffered != nchars) eof = 1; /* Find where to end this chunk. */ bp = buf + n_buffered; if (n_buffered == nchars) { while (bp > buf && bp[-1] != '\n') bp--; } /* If chunk has no newlines, use all the chunk. */ if (bp == buf) bp = buf + n_buffered; /* Output the chars as one output file. */ cwrite (1, buf, bp - buf); /* Discard the chars we just output; move rest of chunk down to be the start of the next chunk. Source and destination probably overlap. */ n_buffered -= bp - buf; if (n_buffered > 0) memmove (buf, bp, n_buffered); } while (!eof); free (buf); } #define FAIL_ONLY_ONE_WAY() \ do \ { \ error (0, 0, _("cannot split in more than one way")); \ usage (EXIT_FAILURE); \ } \ while (0) int main (int argc, char **argv) { struct stat stat_buf; size_t num; /* numeric argument from command line */ enum { type_undef, type_bytes, type_byteslines, type_lines, type_digits } split_type = type_undef; size_t in_blk_size; /* optimal block size of input file device */ char *buf; /* file i/o buffer */ size_t accum = 0; int c; int digits_optind = 0; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); /* Parse command line options. */ infile = "-"; outbase = "x"; while (1) { /* This is the argv-index of the option we will read next. */ int this_optind = optind ? optind : 1; long int tmp_long; c = getopt_long (argc, argv, "0123456789C:a:b:l:", longopts, NULL); if (c == -1) break; switch (c) { case 0: break; case 'a': { unsigned long tmp; if (xstrtoul (optarg, NULL, 10, &tmp, "") != LONGINT_OK || SIZE_MAX < tmp) { error (0, 0, _("%s: invalid suffix length"), optarg); usage (EXIT_FAILURE); } suffix_length = tmp; } break; case 'b': if (split_type != type_undef) FAIL_ONLY_ONE_WAY (); split_type = type_bytes; if (xstrtol (optarg, NULL, 10, &tmp_long, "bkm") != LONGINT_OK || tmp_long < 0 || tmp_long > INT_MAX) { error (0, 0, _("%s: invalid number of bytes"), optarg); usage (EXIT_FAILURE); } accum = /* FIXME: */ (int) tmp_long; break; case 'l': if (split_type != type_undef) FAIL_ONLY_ONE_WAY (); split_type = type_lines; if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK || tmp_long < 0 || tmp_long > INT_MAX) { error (0, 0, _("%s: invalid number of lines"), optarg); usage (EXIT_FAILURE); } accum = /* FIXME */ (int) tmp_long; break; case 'C': if (split_type != type_undef) FAIL_ONLY_ONE_WAY (); split_type = type_byteslines; if (xstrtol (optarg, NULL, 10, &tmp_long, "bkm") != LONGINT_OK || tmp_long < 0 || tmp_long > INT_MAX) { error (0, 0, _("%s: invalid number of bytes"), optarg); usage (EXIT_FAILURE); } accum = /* FIXME */ (int) tmp_long; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (split_type != type_undef && split_type != type_digits) FAIL_ONLY_ONE_WAY (); if (digits_optind != 0 && digits_optind != this_optind) accum = 0; /* More than one number given; ignore other. */ digits_optind = this_optind; split_type = type_digits; accum = accum * 10 + c - '0'; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } if (digits_optind && 200112 <= posix2_version ()) { error (0, 0, _("`-%d' option is obsolete; use `-l %d'"), accum, accum); usage (EXIT_FAILURE); } /* Handle default case. */ if (split_type == type_undef) { split_type = type_lines; accum = 1000; } if (accum < 1) { error (0, 0, _("invalid number")); usage (EXIT_FAILURE); } num = accum; /* Get out the filename arguments. */ if (optind < argc) infile = argv[optind++]; if (optind < argc) outbase = argv[optind++]; if (optind < argc) { error (0, 0, _("too many arguments")); usage (EXIT_FAILURE); } /* Open the input file. */ if (STREQ (infile, "-")) input_desc = 0; else { input_desc = open (infile, O_RDONLY); if (input_desc < 0) error (EXIT_FAILURE, errno, "%s", infile); } /* Binary I/O is safer when bytecounts are used. */ SET_BINARY (input_desc); /* No output file is open now. */ output_desc = -1; /* Get the optimal block size of input device and make a buffer. */ if (fstat (input_desc, &stat_buf) < 0) error (EXIT_FAILURE, errno, "%s", infile); in_blk_size = ST_BLKSIZE (stat_buf); buf = xmalloc (in_blk_size + 1); switch (split_type) { case type_digits: case type_lines: lines_split (num, buf, in_blk_size); break; case type_bytes: bytes_split (num, buf, in_blk_size); break; case type_byteslines: line_bytes_split (num); break; default: abort (); } if (close (input_desc) < 0) error (EXIT_FAILURE, errno, "%s", infile); if (output_desc >= 0 && close (output_desc) < 0) error (EXIT_FAILURE, errno, "%s", outfile); exit (EXIT_SUCCESS); }
/* An interface to read() that reads all it is asked to read. Copyright (C) 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, read to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <stddef.h> /* Read COUNT bytes at BUF to descriptor FD, retrying if interrupted or if partial reads occur. Return the number of bytes successfully read, setting errno if that is less than COUNT. errno = 0 means EOF. */ extern size_t full_read (int fd, void *buf, size_t count);
/* sum -- checksum and count the blocks in a file Copyright (C) 86, 89, 91, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Like BSD sum or SysV sum -r, except like SysV sum if -s option is given. */ /* Written by Kayvan Aghaiepour and David MacKenzie. */ #include <config.h> #include <stdio.h> #include <sys/types.h> #include <getopt.h> #include "system.h" #include "closeout.h" #include "error.h" #include "human.h" #include "safe-read.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "sum" #define AUTHORS N_ ("Kayvan Aghaiepour and David MacKenzie") /* The name this program was run with. */ char *program_name; /* Nonzero if any of the files read were the standard input. */ static int have_read_stdin; static struct option const longopts[] = { {"sysv", no_argument, NULL, 's'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... [FILE]...\n\ "), program_name); fputs (_("\ Print checksum and block counts for each FILE.\n\ \n\ -r defeat -s, use BSD sum algorithm, use 1K blocks\n\ -s, --sysv use System V sum algorithm, use 512 bytes blocks\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ With no FILE, or when FILE is -, read standard input.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } /* Calculate and print the rotated checksum and the size in 1K blocks of file FILE, or of the standard input if FILE is "-". If PRINT_NAME is >1, print FILE next to the checksum and size. The checksum varies depending on sizeof(int). Return 0 if successful, -1 if an error occurs. */ static int bsd_sum_file (const char *file, int print_name) { register FILE *fp; register int checksum = 0; /* The checksum mod 2^16. */ register uintmax_t total_bytes = 0; /* The number of bytes. */ register int ch; /* Each character read. */ char hbuf[LONGEST_HUMAN_READABLE + 1]; if (STREQ (file, "-")) { fp = stdin; have_read_stdin = 1; } else { fp = fopen (file, "r"); if (fp == NULL) { error (0, errno, "%s", file); return -1; } } /* Need binary I/O, or else byte counts and checksums are incorrect. */ SET_BINARY (fileno(fp)); while ((ch = getc (fp)) != EOF) { total_bytes++; checksum = (checksum >> 1) + ((checksum & 1) << 15); checksum += ch; checksum &= 0xffff; /* Keep it within bounds. */ } if (ferror (fp)) { error (0, errno, "%s", file); if (!STREQ (file, "-")) fclose (fp); return -1; } if (!STREQ (file, "-") && fclose (fp) == EOF) { error (0, errno, "%s", file); return -1; } printf ("%05d %5s", checksum, human_readable (total_bytes, hbuf, human_ceiling, 1, 1024)); if (print_name > 1) printf (" %s", file); putchar ('\n'); return 0; } /* Calculate and print the checksum and the size in 512-byte blocks of file FILE, or of the standard input if FILE is "-". If PRINT_NAME is >0, print FILE next to the checksum and size. Return 0 if successful, -1 if an error occurs. */ static int sysv_sum_file (const char *file, int print_name) { int fd; unsigned char buf[8192]; uintmax_t total_bytes = 0; char hbuf[LONGEST_HUMAN_READABLE + 1]; int r; int checksum; /* The sum of all the input bytes, modulo (UINT_MAX + 1). */ unsigned int s = 0; if (STREQ (file, "-")) { fd = 0; have_read_stdin = 1; } else { fd = open (file, O_RDONLY); if (fd == -1) { error (0, errno, "%s", file); return -1; } } /* Need binary I/O, or else byte counts and checksums are incorrect. */ SET_BINARY (fd); while (1) { size_t i; size_t bytes_read = safe_read (fd, buf, sizeof buf); if (bytes_read == 0) break; if (bytes_read == SAFE_READ_ERROR) { error (0, errno, "%s", file); if (!STREQ (file, "-")) close (fd); return -1; } for (i = 0; i < bytes_read; i++) s += buf[i]; total_bytes += bytes_read; } if (!STREQ (file, "-") && close (fd) == -1) { error (0, errno, "%s", file); return -1; } r = (s & 0xffff) + ((s & 0xffffffff) >> 16); checksum = (r & 0xffff) + (r >> 16); printf ("%d %s", checksum, human_readable (total_bytes, hbuf, human_ceiling, 1, 512)); if (print_name) printf (" %s", file); putchar ('\n'); return 0; } int main (int argc, char **argv) { int errors = 0; int optc; int files_given; int (*sum_func) (const char *, int) = bsd_sum_file; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); have_read_stdin = 0; while ((optc = getopt_long (argc, argv, "rs", longopts, NULL)) != -1) { switch (optc) { case 0: break; case 'r': /* For SysV compatibility. */ sum_func = bsd_sum_file; break; case 's': sum_func = sysv_sum_file; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } files_given = argc - optind; if (files_given == 0) { if ((*sum_func) ("-", files_given) < 0) errors = 1; } else for (; optind < argc; optind++) if ((*sum_func) (argv[optind], files_given) < 0) errors = 1; if (have_read_stdin && fclose (stdin) == EOF) error (EXIT_FAILURE, errno, "-"); exit (errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE); }
#ifndef HUMAN_H_ # define HUMAN_H_ 1 /* Before including this file, you need something like the following: #if HAVE_CONFIG_H # include <config.h> #endif #if HAVE_STDBOOL_H # include <stdbool.h> #else typedef enum {false = 0, true = 1} bool; #endif #if HAVE_INTTYPES_H # include <inttypes.h> #else # if HAVE_STDINT_H # include <stdint.h> # endif #endif #include <limits.h> so that the proper identifiers are all declared. */ /* A conservative bound on the maximum length of a human-readable string. The output can be the square of the largest uintmax_t, so double its size before converting to a bound. 302 / 1000 is ceil (log10 (2.0)). Add 1 for integer division truncation. Also, the output can have a thousands separator between every digit, so multiply by MB_LEN_MAX + 1 and then subtract MB_LEN_MAX. Finally, append 3, the maximum length of a suffix. */ # define LONGEST_HUMAN_READABLE \ ((2 * sizeof (uintmax_t) * CHAR_BIT * 302 / 1000 + 1) * (MB_LEN_MAX + 1) \ - MB_LEN_MAX + 3) /* Options for human_readable. */ enum { /* Unless otherwise specified these options may be ORed together. */ /* The following three options are mutually exclusive. */ /* Round to plus infinity (default). */ human_ceiling = 0, /* Round to nearest, ties to even. */ human_round_to_nearest = 1, /* Round to minus infinity. */ human_floor = 2, /* Group digits together, e.g. `1,000,000'. This uses the locale-defined grouping; the traditional C locale does not group, so this has effect only if some other locale is in use. */ human_group_digits = 4, /* When autoscaling, suppress ".0" at end. */ human_suppress_point_zero = 8, /* Scale output and use SI-style units, ignoring the output block size. */ human_autoscale = 16, /* Prefer base 1024 to base 1000. */ human_base_1024 = 32, /* Append SI prefix, e.g. "k" or "M". */ human_SI = 64, /* Append "B" (if base 1000) or "iB" (if base 1024) to SI prefix. */ human_B = 128 }; char *human_readable (uintmax_t, char *, int, uintmax_t, uintmax_t); int human_options (char const *, bool, uintmax_t *); #endif /* HUMAN_H_ */
/* tail -- output the last part of file(s) Copyright (C) 1989, 90, 91, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Can display any amount of data, unlike the Unix version, which uses a fixed size buffer and therefore can only deliver a limited number of lines. Original version by Paul Rubin <phr@ocf.berkeley.edu>. Extensions by David MacKenzie <djm@gnu.ai.mit.edu>. tail -f for multiple files by Ian Lance Taylor <ian@airs.com>. */ #include <config.h> #include <stdio.h> #include <assert.h> #include <getopt.h> #include <sys/types.h> #include <signal.h> #include "system.h" #include "closeout.h" #include "argmatch.h" #include "error.h" #include "inttostr.h" #include "posixver.h" #include "safe-read.h" #include "xnanosleep.h" #include "xstrtol.h" #include "xstrtod.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "tail" #define AUTHORS \ N_ ("Paul Rubin, David MacKenzie, Ian Lance Taylor, and Jim Meyering") #ifndef ENOSYS /* Some systems don't have ENOSYS -- this should be a big enough value that no valid errno value will match it. */ # define ENOSYS 99999 #endif /* Number of items to tail. */ #define DEFAULT_N_LINES 10 /* Size of atomic reads. */ #ifndef BUFSIZ # define BUFSIZ (512 * 8) #endif /* A special value for dump_remainder's N_BYTES parameter. */ #define COPY_TO_EOF OFF_T_MAX /* FIXME: make Follow_name the default? */ #define DEFAULT_FOLLOW_MODE Follow_descriptor enum Follow_mode { /* Follow the name of each file: if the file is renamed, try to reopen that name and track the end of the new file if/when it's recreated. This is useful for tracking logs that are occasionally rotated. */ Follow_name = 1, /* Follow each descriptor obtained upon opening a file. That means we'll continue to follow the end of a file even after it has been renamed or unlinked. */ Follow_descriptor = 2 }; /* The types of files for which tail works. */ #define IS_TAILABLE_FILE_TYPE(Mode) \ (S_ISREG (Mode) || S_ISFIFO (Mode) || S_ISCHR (Mode)) static char const *const follow_mode_string[] = { "descriptor", "name", 0 }; static enum Follow_mode const follow_mode_map[] = { Follow_descriptor, Follow_name, }; struct File_spec { /* The actual file name, or "-" for stdin. */ char *name; /* File descriptor on which the file is open; -1 if it's not open. */ int fd; /* The size of the file the last time we checked. */ off_t size; /* The device and inode of the file the last time we checked. */ dev_t dev; ino_t ino; /* The specified name initially referred to a directory or some other type for which tail isn't meaningful. Unlike for a permission problem (tailable, below) once this is set, the name is not checked ever again. */ int ignore; /* See description of DEFAULT_MAX_N_... below. */ unsigned int n_unchanged_stats; /* See description of DEFAULT_MAX_N_... below. */ unsigned int n_consecutive_size_changes; /* A file is tailable if it exists, is readable, and is of type IS_TAILABLE_FILE_TYPE. */ int tailable; /* The value of errno seen last time we checked this file. */ int errnum; }; /* Keep trying to open a file even if it is inaccessible when tail starts or if it becomes inaccessible later -- useful only with -f. */ static int reopen_inaccessible_files; /* If nonzero, interpret the numeric argument as the number of lines. Otherwise, interpret it as the number of bytes. */ static int count_lines; /* Whether we follow the name of each file or the file descriptor that is initially associated with each name. */ static enum Follow_mode follow_mode = Follow_descriptor; /* If nonzero, read from the ends of all specified files until killed. */ static int forever; /* If nonzero, count from start of file instead of end. */ static int from_start; /* If nonzero, print filename headers. */ static int print_headers; /* When to print the filename banners. */ enum header_mode { multiple_files, always, never }; /* When tailing a file by name, if there have been this many consecutive iterations for which the size has remained the same, then open/fstat the file to determine if that file name is still associated with the same device/inode-number pair as before. This option is meaningful only when following by name. --max-unchanged-stats=N */ #define DEFAULT_MAX_N_UNCHANGED_STATS_BETWEEN_OPENS 5 static unsigned long max_n_unchanged_stats_between_opens = DEFAULT_MAX_N_UNCHANGED_STATS_BETWEEN_OPENS; /* This variable is used to ensure that a file that is unlinked or moved aside, yet always growing will be recognized as having been renamed. After detecting this many consecutive size changes for a file, open/fstat the file to determine if that file name is still associated with the same device/inode-number pair as before. This option is meaningful only when following by name. --max-consecutive-size-changes=N */ #define DEFAULT_MAX_N_CONSECUTIVE_SIZE_CHANGES 200 static unsigned long max_n_consecutive_size_changes_between_opens = DEFAULT_MAX_N_CONSECUTIVE_SIZE_CHANGES; /* The name this program was run with. */ char *program_name; /* The process ID of the process (presumably on the current host) that is writing to all followed files. */ static pid_t pid; /* Nonzero if we have ever read standard input. */ static int have_read_stdin; /* For long options that have no equivalent short option, use a non-character as a pseudo short option, starting with CHAR_MAX + 1. */ enum { RETRY_OPTION = CHAR_MAX + 1, MAX_UNCHANGED_STATS_OPTION, /* FIXME: remove this in 2001, unless someone can show a good reason to keep it. */ MAX_CONSECUTIVE_SIZE_CHANGES_OPTION, PID_OPTION, LONG_FOLLOW_OPTION }; static struct option const long_options[] = { /* --allow-missing is deprecated; use --retry instead FIXME: remove it some day */ {"allow-missing", no_argument, NULL, RETRY_OPTION}, {"bytes", required_argument, NULL, 'c'}, {"follow", optional_argument, NULL, LONG_FOLLOW_OPTION}, {"lines", required_argument, NULL, 'n'}, {"max-unchanged-stats", required_argument, NULL, MAX_UNCHANGED_STATS_OPTION}, {"max-consecutive-size-changes", required_argument, NULL, MAX_CONSECUTIVE_SIZE_CHANGES_OPTION}, {"pid", required_argument, NULL, PID_OPTION}, {"quiet", no_argument, NULL, 'q'}, {"retry", no_argument, NULL, RETRY_OPTION}, {"silent", no_argument, NULL, 'q'}, {"sleep-interval", required_argument, NULL, 's'}, {"verbose", no_argument, NULL, 'v'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... [FILE]...\n\ "), program_name); printf (_("\ Print the last %d lines of each FILE to standard output.\n\ With more than one FILE, precede each with a header giving the file name.\n\ With no FILE, or when FILE is -, read standard input.\n\ \n\ "), DEFAULT_N_LINES); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ --retry keep trying to open a file even if it is\n\ inaccessible when tail starts or if it becomes\n\ inaccessible later -- useful only with -f\n\ -c, --bytes=N output the last N bytes\n\ "), stdout); fputs (_("\ -f, --follow[={name|descriptor}]\n\ output appended data as the file grows;\n\ -f, --follow, and --follow=descriptor are\n\ equivalent\n\ -F same as --follow=name --retry\n\ "), stdout); printf (_("\ -n, --lines=N output the last N lines, instead of the last %d\n\ --max-unchanged-stats=N\n\ with --follow=name, reopen a FILE which has not\n\ changed size after N (default %d) iterations\n\ to see if it has been unlinked or renamed\n\ (this is the usual case of rotated log files)\n\ "), DEFAULT_N_LINES, DEFAULT_MAX_N_UNCHANGED_STATS_BETWEEN_OPENS ); fputs (_("\ --pid=PID with -f, terminate after process ID, PID dies\n\ -q, --quiet, --silent never output headers giving file names\n\ -s, --sleep-interval=S with -f, sleep for approximately S seconds\n\ (default 1.0) between iterations.\n\ -v, --verbose always output headers giving file names\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ If the first character of N (the number of bytes or lines) is a `+',\n\ print beginning with the Nth item from the start of each file, otherwise,\n\ print the last N items in the file. N may have a multiplier suffix:\n\ b for 512, k for 1024, m for 1048576 (1 Meg).\n\ \n\ "), stdout); fputs (_("\ With --follow (-f), tail defaults to following the file descriptor, which\n\ means that even if a tail'ed file is renamed, tail will continue to track\n\ its end. \ "), stdout); fputs (_("\ This default behavior is not desirable when you really want to\n\ track the actual name of the file, not the file descriptor (e.g., log\n\ rotation). Use --follow=name in that case. That causes tail to track the\n\ named file by reopening it periodically to see if it has been removed and\n\ recreated by some other program.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } static int valid_file_spec (struct File_spec const *f) { /* Exactly one of the following subexpressions must be true. */ return ((f->fd == -1) ^ (f->errnum == 0)); } static char * pretty_name (struct File_spec const *f) { return (STREQ (f->name, "-") ? "standard input" : f->name); } static void xwrite (int fd, char const *buffer, size_t n_bytes) { assert (fd == STDOUT_FILENO); if (n_bytes > 0 && fwrite (buffer, 1, n_bytes, stdout) == 0) error (EXIT_FAILURE, errno, _("write error")); } static void close_fd (int fd, const char *filename) { if (fd != -1 && fd != STDIN_FILENO && close (fd)) { error (0, errno, _("closing %s (fd=%d)"), filename, fd); } } static void write_header (const char *pretty_filename) { static int first_file = 1; printf ("%s==> %s <==\n", (first_file ? "" : "\n"), pretty_filename); first_file = 0; } /* Read and output N_BYTES of file PRETTY_FILENAME starting at the current position in FD. If N_BYTES is COPY_TO_EOF, then copy until end of file. Return the number of bytes read from the file. */ static long dump_remainder (const char *pretty_filename, int fd, off_t n_bytes) { char buffer[BUFSIZ]; size_t bytes_read; long n_written; off_t n_remaining = n_bytes; n_written = 0; while (1) { long n = MIN (n_remaining, (off_t) BUFSIZ); bytes_read = safe_read (fd, buffer, n); if (bytes_read == SAFE_READ_ERROR) error (EXIT_FAILURE, errno, "%s", pretty_filename); if (bytes_read == 0) break; xwrite (STDOUT_FILENO, buffer, bytes_read); n_remaining -= bytes_read; n_written += bytes_read; } return n_written; } /* Call lseek with the specified arguments, where file descriptor FD corresponds to the file, FILENAME. Give a diagnostic and exit nonzero if lseek fails. */ static void xlseek (int fd, off_t offset, int whence, char const *filename) { off_t new_offset = lseek (fd, offset, whence); char buf[INT_BUFSIZE_BOUND (off_t)]; char *s; if (0 <= new_offset) return; s = offtostr (offset, buf); switch (whence) { case SEEK_SET: error (EXIT_FAILURE, errno, _("%s: cannot seek to offset %s"), filename, s); break; case SEEK_CUR: error (EXIT_FAILURE, errno, _("%s: cannot seek to relative offset %s"), filename, s); break; case SEEK_END: error (EXIT_FAILURE, errno, _("%s: cannot seek to end-relative offset %s"), filename, s); break; default: abort (); } } /* Print the last N_LINES lines from the end of file FD. Go backward through the file, reading `BUFSIZ' bytes at a time (except probably the first), until we hit the start of the file or have read NUMBER newlines. START_POS is the starting position of the read pointer for the file associated with FD (may be nonzero). FILE_LENGTH is the length of the file (one more than the offset of the last byte of the file). Return 0 if successful, 1 if an error occurred. */ static int file_lines (const char *pretty_filename, int fd, long int n_lines, off_t start_pos, off_t file_length) { char buffer[BUFSIZ]; size_t bytes_read; off_t pos = file_length; if (n_lines == 0) return 0; /* Set `bytes_read' to the size of the last, probably partial, buffer; 0 < `bytes_read' <= `BUFSIZ'. */ bytes_read = (pos - start_pos) % BUFSIZ; if (bytes_read == 0) bytes_read = BUFSIZ; /* Make `pos' a multiple of `BUFSIZ' (0 if the file is short), so that all reads will be on block boundaries, which might increase efficiency. */ pos -= bytes_read; xlseek (fd, pos, SEEK_SET, pretty_filename); bytes_read = safe_read (fd, buffer, bytes_read); if (bytes_read == SAFE_READ_ERROR) { error (0, errno, "%s", pretty_filename); return 1; } /* Count the incomplete line on files that don't end with a newline. */ if (bytes_read && buffer[bytes_read - 1] != '\n') --n_lines; do { /* Scan backward, counting the newlines in this bufferfull. */ size_t n = bytes_read; while (n) { char const *nl; nl = memrchr (buffer, '\n', n); if (nl == NULL) break; n = nl - buffer; if (n_lines-- == 0) { /* If this newline isn't the last character in the buffer, output the part that is after it. */ if (n != bytes_read - 1) xwrite (STDOUT_FILENO, nl + 1, bytes_read - (n + 1)); dump_remainder (pretty_filename, fd, file_length - (pos + bytes_read)); return 0; } } /* Not enough newlines in that bufferfull. */ if (pos == start_pos) { /* Not enough lines in the file; print the entire file. */ xlseek (fd, start_pos, SEEK_SET, pretty_filename); dump_remainder (pretty_filename, fd, file_length); return 0; } pos -= BUFSIZ; xlseek (fd, pos, SEEK_SET, pretty_filename); bytes_read = safe_read (fd, buffer, BUFSIZ); if (bytes_read == SAFE_READ_ERROR) { error (0, errno, "%s", pretty_filename); return 1; } } while (bytes_read > 0); return 0; } /* Print the last N_LINES lines from the end of the standard input, open for reading as pipe FD. Buffer the text as a linked list of LBUFFERs, adding them as needed. Return 0 if successful, 1 if an error occured. */ static int pipe_lines (const char *pretty_filename, int fd, long int n_lines) { struct linebuffer { int nbytes, nlines; char buffer[BUFSIZ]; struct linebuffer *next; }; typedef struct linebuffer LBUFFER; LBUFFER *first, *last, *tmp; int i; /* Index into buffers. */ int total_lines = 0; /* Total number of newlines in all buffers. */ int errors = 0; int nbytes; /* Size of most recent read */ first = last = (LBUFFER *) xmalloc (sizeof (LBUFFER)); first->nbytes = first->nlines = 0; first->next = NULL; tmp = (LBUFFER *) xmalloc (sizeof (LBUFFER)); /* Input is always read into a fresh buffer. */ while ((nbytes = tmp->nbytes = safe_read (fd, tmp->buffer, BUFSIZ)) > 0) { tmp->nlines = 0; tmp->next = NULL; /* Count the number of newlines just read. */ for (i = 0; i < tmp->nbytes; i++) if (tmp->buffer[i] == '\n') ++tmp->nlines; total_lines += tmp->nlines; /* If there is enough room in the last buffer read, just append the new one to it. This is because when reading from a pipe, `nbytes' can often be very small. */ if (tmp->nbytes + last->nbytes < BUFSIZ) { memcpy (&last->buffer[last->nbytes], tmp->buffer, tmp->nbytes); last->nbytes += tmp->nbytes; last->nlines += tmp->nlines; } else { /* If there's not enough room, link the new buffer onto the end of the list, then either free up the oldest buffer for the next read if that would leave enough lines, or else malloc a new one. Some compaction mechanism is possible but probably not worthwhile. */ last = last->next = tmp; if (total_lines - first->nlines > n_lines) { tmp = first; total_lines -= first->nlines; first = first->next; } else tmp = (LBUFFER *) xmalloc (sizeof (LBUFFER)); } } free ((char *) tmp); if (nbytes < 0) { error (0, errno, "%s", pretty_filename); errors = 1; goto free_lbuffers; } /* If the file is empty, then bail out. */ if (last->nbytes == 0) goto free_lbuffers; /* This prevents a core dump when the pipe contains no newlines. */ if (n_lines == 0) goto free_lbuffers; /* Count the incomplete line on files that don't end with a newline. */ if (last->buffer[last->nbytes - 1] != '\n') { ++last->nlines; ++total_lines; } /* Run through the list, printing lines. First, skip over unneeded buffers. */ for (tmp = first; total_lines - tmp->nlines > n_lines; tmp = tmp->next) total_lines -= tmp->nlines; /* Find the correct beginning, then print the rest of the file. */ if (total_lines > n_lines) { const char *cp; /* Skip `total_lines' - `n_lines' newlines. We made sure that `total_lines' - `n_lines' <= `tmp->nlines'. */ cp = tmp->buffer; for (i = total_lines - n_lines; i; --i) while (*cp++ != '\n') /* Do nothing. */ ; i = cp - tmp->buffer; } else i = 0; xwrite (STDOUT_FILENO, &tmp->buffer[i], tmp->nbytes - i); for (tmp = tmp->next; tmp; tmp = tmp->next) xwrite (STDOUT_FILENO, tmp->buffer, tmp->nbytes); free_lbuffers: while (first) { tmp = first->next; free ((char *) first); first = tmp; } return errors; } /* Print the last N_BYTES characters from the end of pipe FD. This is a stripped down version of pipe_lines. Return 0 if successful, 1 if an error occurred. */ static int pipe_bytes (const char *pretty_filename, int fd, off_t n_bytes) { struct charbuffer { int nbytes; char buffer[BUFSIZ]; struct charbuffer *next; }; typedef struct charbuffer CBUFFER; CBUFFER *first, *last, *tmp; int i; /* Index into buffers. */ int total_bytes = 0; /* Total characters in all buffers. */ int errors = 0; first = last = (CBUFFER *) xmalloc (sizeof (CBUFFER)); first->nbytes = 0; first->next = NULL; tmp = (CBUFFER *) xmalloc (sizeof (CBUFFER)); /* Input is always read into a fresh buffer. */ while ((tmp->nbytes = safe_read (fd, tmp->buffer, BUFSIZ)) > 0) { tmp->next = NULL; total_bytes += tmp->nbytes; /* If there is enough room in the last buffer read, just append the new one to it. This is because when reading from a pipe, `nbytes' can often be very small. */ if (tmp->nbytes + last->nbytes < BUFSIZ) { memcpy (&last->buffer[last->nbytes], tmp->buffer, tmp->nbytes); last->nbytes += tmp->nbytes; } else { /* If there's not enough room, link the new buffer onto the end of the list, then either free up the oldest buffer for the next read if that would leave enough characters, or else malloc a new one. Some compaction mechanism is possible but probably not worthwhile. */ last = last->next = tmp; if (total_bytes - first->nbytes > n_bytes) { tmp = first; total_bytes -= first->nbytes; first = first->next; } else { tmp = (CBUFFER *) xmalloc (sizeof (CBUFFER)); } } } if (tmp->nbytes == -1) { error (0, errno, "%s", pretty_filename); errors = 1; free ((char *) tmp); goto free_cbuffers; } free ((char *) tmp); /* Run through the list, printing characters. First, skip over unneeded buffers. */ for (tmp = first; total_bytes - tmp->nbytes > n_bytes; tmp = tmp->next) total_bytes -= tmp->nbytes; /* Find the correct beginning, then print the rest of the file. We made sure that `total_bytes' - `n_bytes' <= `tmp->nbytes'. */ if (total_bytes > n_bytes) i = total_bytes - n_bytes; else i = 0; xwrite (STDOUT_FILENO, &tmp->buffer[i], tmp->nbytes - i); for (tmp = tmp->next; tmp; tmp = tmp->next) xwrite (STDOUT_FILENO, tmp->buffer, tmp->nbytes); free_cbuffers: while (first) { tmp = first->next; free ((char *) first); first = tmp; } return errors; } /* Skip N_BYTES characters from the start of pipe FD, and print any extra characters that were read beyond that. Return 1 on error, 0 if ok, -1 if EOF. */ static int start_bytes (const char *pretty_filename, int fd, off_t n_bytes) { char buffer[BUFSIZ]; size_t bytes_read = 0; while (0 < n_bytes) { bytes_read = safe_read (fd, buffer, BUFSIZ); if (bytes_read == 0) return -1; if (bytes_read == SAFE_READ_ERROR) { error (0, errno, "%s", pretty_filename); return 1; } n_bytes -= bytes_read; } if (n_bytes < 0) xwrite (STDOUT_FILENO, &buffer[bytes_read + n_bytes], -n_bytes); return 0; } /* Skip N_LINES lines at the start of file or pipe FD, and print any extra characters that were read beyond that. Return 1 on error, 0 if ok, -1 if EOF. */ static int start_lines (const char *pretty_filename, int fd, long int n_lines) { char buffer[BUFSIZ]; size_t bytes_read = 0; size_t bytes_to_skip = 0; if (n_lines == 0) return 0; while (n_lines) { bytes_to_skip = 0; bytes_read = safe_read (fd, buffer, BUFSIZ); if (bytes_read == 0) /* EOF */ return -1; if (bytes_read == SAFE_READ_ERROR) /* error */ { error (0, errno, "%s", pretty_filename); return 1; } while (bytes_to_skip < bytes_read) if (buffer[bytes_to_skip++] == '\n' && --n_lines == 0) break; } if (bytes_to_skip < bytes_read) { xwrite (STDOUT_FILENO, &buffer[bytes_to_skip], bytes_read - bytes_to_skip); } return 0; } /* FIXME: describe */ static void recheck (struct File_spec *f) { /* open/fstat the file and announce if dev/ino have changed */ struct stat new_stats; int fd; int fail = 0; int is_stdin = (STREQ (f->name, "-")); int was_tailable = f->tailable; int prev_errnum = f->errnum; int new_file; assert (valid_file_spec (f)); fd = (is_stdin ? STDIN_FILENO : open (f->name, O_RDONLY)); /* If the open fails because the file doesn't exist, then mark the file as not tailable. */ f->tailable = !(reopen_inaccessible_files && fd == -1); if (fd == -1 || fstat (fd, &new_stats) < 0) { fail = 1; f->errnum = errno; if (!f->tailable) { if (was_tailable) { /* FIXME-maybe: detect the case in which the file first becomes unreadable (perms), and later becomes readable again and can be seen to be the same file (dev/ino). Otherwise, tail prints the entire contents of the file when it becomes readable. */ error (0, f->errnum, _("`%s' has become inaccessible"), pretty_name (f)); } else { /* say nothing... it's still not tailable */ } } else if (prev_errnum != errno) { error (0, errno, "%s", pretty_name (f)); } } else if (!IS_TAILABLE_FILE_TYPE (new_stats.st_mode)) { fail = 1; f->errnum = -1; error (0, 0, _("`%s' has been replaced with an untailable file;\ giving up on this name"), pretty_name (f)); f->ignore = 1; } else { f->errnum = 0; } new_file = 0; if (fail) { close_fd (fd, pretty_name (f)); close_fd (f->fd, pretty_name (f)); f->fd = -1; } else if (prev_errnum && prev_errnum != ENOENT) { new_file = 1; assert (f->fd == -1); error (0, 0, _("`%s' has become accessible"), pretty_name (f)); } else if (f->ino != new_stats.st_ino || f->dev != new_stats.st_dev) { new_file = 1; if (f->fd == -1) { error (0, 0, _("`%s' has appeared; following end of new file"), pretty_name (f)); } else { /* Close the old one. */ close_fd (f->fd, pretty_name (f)); /* File has been replaced (e.g., via log rotation) -- tail the new one. */ error (0, 0, _("`%s' has been replaced; following end of new file"), pretty_name (f)); } } else { if (f->fd == -1) { /* This happens when one iteration finds the file missing, then the preceding <dev,inode> pair is reused as the file is recreated. */ new_file = 1; } else { close_fd (fd, pretty_name (f)); } } if (new_file) { /* Record new file info in f. */ f->fd = fd; f->size = 0; /* Start at the beginning of the file... */ f->dev = new_stats.st_dev; f->ino = new_stats.st_ino; f->n_unchanged_stats = 0; f->n_consecutive_size_changes = 0; f->ignore = 0; xlseek (f->fd, f->size, SEEK_SET, pretty_name (f)); } } /* FIXME: describe */ static unsigned int n_live_files (const struct File_spec *f, int n_files) { int i; unsigned int n_live = 0; for (i = 0; i < n_files; i++) { if (f[i].fd >= 0) ++n_live; } return n_live; } /* Tail NFILES files forever, or until killed. The pertinent information for each file is stored in an entry of F. Loop over each of them, doing an fstat to see if they have changed size, and an occasional open/fstat to see if any dev/ino pair has changed. If none of them have changed size in one iteration, sleep for a while and try again. Continue until the user interrupts us. */ static void tail_forever (struct File_spec *f, int nfiles, double sleep_interval) { int last; int writer_is_dead = 0; last = nfiles - 1; while (1) { int i; int any_changed; any_changed = 0; for (i = 0; i < nfiles; i++) { struct stat stats; if (f[i].ignore) continue; if (f[i].fd < 0) { recheck (&f[i]); continue; } if (fstat (f[i].fd, &stats) < 0) { f[i].fd = -1; f[i].errnum = errno; error (0, errno, "%s", pretty_name (&f[i])); continue; } if (stats.st_size == f[i].size) { f[i].n_consecutive_size_changes = 0; if ((max_n_unchanged_stats_between_opens <= f[i].n_unchanged_stats++) && follow_mode == Follow_name) { recheck (&f[i]); f[i].n_unchanged_stats = 0; } continue; } /* Ensure that a file that's unlinked or moved aside, yet always growing will be recognized as having been renamed. */ if ((max_n_consecutive_size_changes_between_opens <= f[i].n_consecutive_size_changes++) && follow_mode == Follow_name) { f[i].n_consecutive_size_changes = 0; recheck (&f[i]); continue; } /* This file has changed size. Print out what we can, and then keep looping. */ any_changed = 1; /* reset counter */ f[i].n_unchanged_stats = 0; if (stats.st_size < f[i].size) { error (0, 0, _("%s: file truncated"), pretty_name (&f[i])); last = i; xlseek (f[i].fd, (off_t) stats.st_size, SEEK_SET, pretty_name (&f[i])); f[i].size = stats.st_size; continue; } if (i != last) { if (print_headers) write_header (pretty_name (&f[i])); last = i; } f[i].size += dump_remainder (pretty_name (&f[i]), f[i].fd, COPY_TO_EOF); } if (n_live_files (f, nfiles) == 0 && ! reopen_inaccessible_files) { error (0, 0, _("no files remaining")); break; } /* If none of the files changed size, sleep. */ if (!any_changed) { if (writer_is_dead) break; if (xnanosleep (sleep_interval)) error (EXIT_FAILURE, errno, _("cannot read realtime clock")); /* Once the writer is dead, read the files once more to avoid a race condition. */ writer_is_dead = (pid != 0 && kill (pid, 0) != 0 /* Handle the case in which you cannot send a signal to the writer, so kill fails and sets errno to EPERM. */ && errno != EPERM); } } } /* Output the last N_BYTES bytes of file FILENAME open for reading in FD. Return 0 if successful, 1 if an error occurred. */ static int tail_bytes (const char *pretty_filename, int fd, off_t n_bytes) { struct stat stats; /* We need binary input, since `tail' relies on `lseek' and byte counts, while binary output will preserve the style (Unix/DOS) of text file. */ SET_BINARY2 (fd, STDOUT_FILENO); if (fstat (fd, &stats)) { error (0, errno, "%s", pretty_filename); return 1; } if (from_start) { if (S_ISREG (stats.st_mode)) { xlseek (fd, n_bytes, SEEK_CUR, pretty_filename); } else { int t; if ((t = start_bytes (pretty_filename, fd, n_bytes)) < 0) return 0; if (t) return 1; } dump_remainder (pretty_filename, fd, COPY_TO_EOF); } else { if (S_ISREG (stats.st_mode)) { off_t current_pos, end_pos; off_t bytes_remaining; if ((current_pos = lseek (fd, (off_t) 0, SEEK_CUR)) != -1 && (end_pos = lseek (fd, (off_t) 0, SEEK_END)) != -1) { off_t diff; /* Be careful here. The current position may actually be beyond the end of the file. */ bytes_remaining = (diff = end_pos - current_pos) < 0 ? 0 : diff; } else { error (0, errno, "%s", pretty_filename); return 1; } if (bytes_remaining <= n_bytes) { /* From the current position to end of file, there are no more bytes than have been requested. So reposition the file pointer to the incoming current position and print everything after that. */ xlseek (fd, current_pos, SEEK_SET, pretty_filename); } else { /* There are more bytes remaining than were requested. Back up. */ xlseek (fd, -n_bytes, SEEK_END, pretty_filename); } dump_remainder (pretty_filename, fd, n_bytes); } else return pipe_bytes (pretty_filename, fd, n_bytes); } return 0; } /* Output the last N_LINES lines of file FILENAME open for reading in FD. Return 0 if successful, 1 if an error occurred. */ static int tail_lines (const char *pretty_filename, int fd, long int n_lines) { struct stat stats; /* We need binary input, since `tail' relies on `lseek' and byte counts, while binary output will preserve the style (Unix/DOS) of text file. */ SET_BINARY2 (fd, STDOUT_FILENO); if (fstat (fd, &stats)) { error (0, errno, "%s", pretty_filename); return 1; } if (from_start) { int t; if ((t = start_lines (pretty_filename, fd, n_lines)) < 0) return 0; if (t) return 1; dump_remainder (pretty_filename, fd, COPY_TO_EOF); } else { off_t length; off_t start_pos; /* Use file_lines only if FD refers to a regular file for which lseek (... SEEK_END) works. */ if (S_ISREG (stats.st_mode) && (start_pos = lseek (fd, (off_t) 0, SEEK_CUR)) != -1 && start_pos < (length = lseek (fd, (off_t) 0, SEEK_END))) { if (length != 0 && file_lines (pretty_filename, fd, n_lines, start_pos, length)) return 1; } else return pipe_lines (pretty_filename, fd, n_lines); } return 0; } /* Display the last N_UNITS units of file FILENAME, open for reading in FD. Return 0 if successful, 1 if an error occurred. */ static int tail (const char *pretty_filename, int fd, off_t n_units) { if (count_lines) return tail_lines (pretty_filename, fd, (long) n_units); else return tail_bytes (pretty_filename, fd, n_units); } /* Display the last N_UNITS units of the file described by F. Return 0 if successful, 1 if an error occurred. */ static int tail_file (struct File_spec *f, off_t n_units) { int fd, errors; int is_stdin = (STREQ (f->name, "-")); if (is_stdin) { have_read_stdin = 1; fd = STDIN_FILENO; } else { fd = open (f->name, O_RDONLY); } f->tailable = !(reopen_inaccessible_files && fd == -1); if (fd == -1) { if (forever) { f->fd = -1; f->errnum = errno; f->ignore = 0; f->ino = 0; f->dev = 0; } error (0, errno, "%s", pretty_name (f)); errors = 1; } else { if (print_headers) write_header (pretty_name (f)); errors = tail (pretty_name (f), fd, n_units); if (forever) { struct stat stats; f->errnum = 0; if (fstat (fd, &stats) < 0) { errors = 1; f->errnum = errno; error (0, errno, "%s", pretty_name (f)); } else if (!IS_TAILABLE_FILE_TYPE (stats.st_mode)) { error (0, 0, _("%s: cannot follow end of this type of file;\ giving up on this name"), pretty_name (f)); errors = 1; f->errnum = -1; f->ignore = 1; } if (errors) { close_fd (fd, pretty_name (f)); f->fd = -1; } else { f->fd = fd; f->size = stats.st_size; f->dev = stats.st_dev; f->ino = stats.st_ino; f->n_unchanged_stats = 0; f->n_consecutive_size_changes = 0; f->ignore = 0; } } else { if (!is_stdin && close (fd)) { error (0, errno, "%s", pretty_name (f)); errors = 1; } } } return errors; } /* If the command line arguments are of the obsolescent form and the option string is well-formed, set *FAIL to zero, set *N_UNITS, the globals COUNT_LINES, FOREVER, and FROM_START, and return non-zero. Otherwise, if the command line arguments appear to be of the obsolescent form but the option string is malformed, set *FAIL to non-zero, don't modify any other parameter or global variable, and return non-zero. Otherwise, return zero and don't modify any parameter or global variable. */ static int parse_obsolescent_option (int argc, const char *const *argv, off_t *n_units, int *fail) { const char *p = argv[1]; const char *n_string = NULL; const char *n_string_end; bool obsolete_usage; int t_from_start; int t_count_lines; int t_forever; /* With the obsolescent form, there is one option string and (technically) at most one file argument. But we allow two or more by default. */ if (argc < 2) return 0; obsolete_usage = (posix2_version () < 200112); /* If P starts with `+' and the POSIX version predates 1003.1-2001, or if P starts with `-N' (where N is a digit), or `-l', then it is obsolescent. Return zero otherwise. */ if (! ((p[0] == '+' && obsolete_usage) || (p[0] == '-' && (p[1] == 'l' || ISDIGIT (p[1]))))) return 0; if (*p == '+') t_from_start = 1; else if (*p == '-') t_from_start = 0; else return 0; ++p; if (ISDIGIT (*p)) { n_string = p; do { ++p; } while (ISDIGIT (*p)); } n_string_end = p; t_count_lines = 1; if (*p == 'c' || *p == 'b') { t_count_lines = 0; ++p; } else if (*p == 'l') { ++p; } t_forever = 0; if (*p == 'f') { t_forever = 1; ++p; } if (*p != '\0') { /* If (argv[1] begins with a `+' or if it begins with `-' followed by a digit), but has an invalid suffix character, give a diagnostic and indicate to caller that this *is* of the obsolescent form, but that it's an invalid option. */ if (t_from_start || n_string) { error (0, 0, _("%c: invalid suffix character in obsolescent option" ), *p); *fail = 1; return 1; } /* Otherwise, it might be a valid non-obsolescent option like -n. */ return 0; } *fail = 0; if (n_string == NULL) *n_units = DEFAULT_N_LINES; else { strtol_error s_err; uintmax_t tmp; char *end; s_err = xstrtoumax (n_string, &end, 10, &tmp, *n_string_end == 'b' ? "b" : NULL); if (s_err == LONGINT_OK && tmp <= OFF_T_MAX) *n_units = (off_t) tmp; else { /* Extract a NUL-terminated string for the error message. */ size_t len = n_string_end - n_string; char *n_string_tmp = xmalloc (len + 1); strncpy (n_string_tmp, n_string, len); n_string_tmp[len] = '\0'; error (0, 0, _("%s: %s is so large that it is not representable"), n_string_tmp, (t_count_lines ? _("number of lines") : _("number of bytes"))); free (n_string_tmp); *fail = 1; } } if (!*fail) { if (argc > 3) { /* When POSIXLY_CORRECT is set, enforce the `at most one file argument' requirement. */ if (getenv ("POSIXLY_CORRECT")) { error (0, 0, _("\ too many arguments; When using tail's obsolescent option syntax (%s)\n\ there may be no more than one file argument. Use the equivalent -n or -c\n\ option instead."), argv[1]); *fail = 1; return 1; } #if DISABLED /* FIXME: enable or remove this warning. */ error (0, 0, _("\ Warning: it is not portable to use two or more file arguments with\n\ tail's obsolescent option syntax (%s). Use the equivalent -n or -c\n\ option instead."), argv[1]); #endif } if (! obsolete_usage) { error (0, 0, _("`%s' option is obsolete; use `%s-%c %.*s'"), argv[1], t_forever ? " -f" : "", t_count_lines ? 'n' : 'c', (int) (n_string_end - n_string), n_string); usage (EXIT_FAILURE); } /* Set globals. */ from_start = t_from_start; count_lines = t_count_lines; forever = t_forever; } return 1; } static void parse_options (int argc, char **argv, off_t *n_units, enum header_mode *header_mode, double *sleep_interval) { int c; count_lines = 1; forever = from_start = print_headers = 0; while ((c = getopt_long (argc, argv, "c:n:fFqs:v", long_options, NULL)) != -1) { switch (c) { case 0: break; case 'F': forever = 1; follow_mode = Follow_name; reopen_inaccessible_files = 1; break; case 'c': case 'n': count_lines = (c == 'n'); if (*optarg == '+') from_start = 1; else if (*optarg == '-') ++optarg; { strtol_error s_err; uintmax_t n; s_err = xstrtoumax (optarg, NULL, 10, &n, "bkm"); if (s_err != LONGINT_OK) { error (EXIT_FAILURE, 0, "%s: %s", optarg, (c == 'n' ? _("invalid number of lines") : _("invalid number of bytes"))); } if (OFF_T_MAX < n) error (EXIT_FAILURE, 0, _("%s is larger than the maximum file size on this system"), optarg); *n_units = (off_t) n; } break; case 'f': case LONG_FOLLOW_OPTION: forever = 1; if (optarg == NULL) follow_mode = DEFAULT_FOLLOW_MODE; else follow_mode = XARGMATCH ("--follow", optarg, follow_mode_string, follow_mode_map); break; case RETRY_OPTION: reopen_inaccessible_files = 1; break; case MAX_UNCHANGED_STATS_OPTION: /* --max-unchanged-stats=N */ if (xstrtoul (optarg, NULL, 10, &max_n_unchanged_stats_between_opens, "") != LONGINT_OK) { error (EXIT_FAILURE, 0, _("%s: invalid maximum number of unchanged stats between opens"), optarg); } break; case MAX_CONSECUTIVE_SIZE_CHANGES_OPTION: /* --max-consecutive-size-changes=N */ if (xstrtoul (optarg, NULL, 10, &max_n_consecutive_size_changes_between_opens, "") != LONGINT_OK) { error (EXIT_FAILURE, 0, _("%s: invalid maximum number of consecutive size changes"), optarg); } break; case PID_OPTION: { strtol_error s_err; unsigned long int tmp_ulong; s_err = xstrtoul (optarg, NULL, 10, &tmp_ulong, ""); if (s_err != LONGINT_OK || tmp_ulong > PID_T_MAX) { error (EXIT_FAILURE, 0, _("%s: invalid PID"), optarg); } pid = tmp_ulong; } break; case 'q': *header_mode = never; break; case 's': { double s; if (xstrtod (optarg, NULL, &s) || ! (0 <= s)) error (EXIT_FAILURE, 0, _("%s: invalid number of seconds"), optarg); *sleep_interval = s; } break; case 'v': *header_mode = always; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } if (reopen_inaccessible_files && follow_mode != Follow_name) error (0, 0, _("warning: --retry is useful only when following by name")); if (pid && !forever) error (0, 0, _("warning: PID ignored; --pid=PID is useful only when following")); else if (pid && kill (pid, 0) != 0 && errno == ENOSYS) { error (0, 0, _("warning: --pid=PID is not supported on this system")); pid = 0; } } int main (int argc, char **argv) { enum header_mode header_mode = multiple_files; int exit_status = 0; /* If from_start, the number of items to skip before printing; otherwise, the number of items at the end of the file to print. Although the type is signed, the value is never negative. */ off_t n_units = DEFAULT_N_LINES; int n_files; char **file; struct File_spec *F; int i; /* The number of seconds to sleep between iterations. During one iteration, every file name or descriptor is checked to see if it has changed. */ double sleep_interval = 1.0; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); have_read_stdin = 0; { int fail; if (parse_obsolescent_option (argc, (const char *const *) argv, &n_units, &fail)) { if (fail) exit (EXIT_FAILURE); optind = 2; } else { parse_options (argc, argv, &n_units, &header_mode, &sleep_interval); } } /* To start printing with item N_UNITS from the start of the file, skip N_UNITS - 1 items. `tail -n +0' is actually meaningless, but for Unix compatibility it's treated the same as `tail -n +1'. */ if (from_start) { if (n_units) --n_units; } n_files = argc - optind; file = argv + optind; if (n_files == 0) { static char *dummy_stdin = "-"; n_files = 1; file = &dummy_stdin; } F = (struct File_spec *) xmalloc (n_files * sizeof (F[0])); for (i = 0; i < n_files; i++) F[i].name = file[i]; if (header_mode == always || (header_mode == multiple_files && n_files > 1)) print_headers = 1; for (i = 0; i < n_files; i++) exit_status |= tail_file (&F[i], n_units); if (forever) { /* This fflush appears to be required only on Solaris2.7. */ if (fflush (stdout) < 0) error (EXIT_FAILURE, errno, _("write error")); SETVBUF (stdout, NULL, _IONBF, 0); tail_forever (F, n_files, sleep_interval); } if (have_read_stdin && close (STDIN_FILENO) < 0) error (EXIT_FAILURE, errno, "-"); exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); }
/* tee - read from standard input and write to standard output and files. Copyright (C) 85,1990-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Mike Parker, Richard M. Stallman, and David MacKenzie */ #include <config.h> #include <stdio.h> #include <sys/types.h> #include <signal.h> #include <getopt.h> #include "system.h" #include "closeout.h" #include "error.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "tee" #define AUTHORS N_ ("Mike Parker, Richard M. Stallman, and David MacKenzie") static int tee (int nfiles, const char **files); /* If nonzero, append to output files rather than truncating them. */ static int append; /* If nonzero, ignore interrupts. */ static int ignore_interrupts; /* The name that this program was run with. */ char *program_name; static struct option const long_options[] = { {"append", no_argument, NULL, 'a'}, {"ignore-interrupts", no_argument, NULL, 'i'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("Usage: %s [OPTION]... [FILE]...\n"), program_name); fputs (_("\ Copy standard input to each FILE, and also to standard output.\n\ \n\ -a, --append append to the given FILEs, do not overwrite\n\ -i, --ignore-interrupts ignore interrupt signals\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } int main (int argc, char **argv) { int errs; int optc; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); append = 0; ignore_interrupts = 0; while ((optc = getopt_long (argc, argv, "ai", long_options, NULL)) != -1) { switch (optc) { case 0: break; case 'a': append = 1; break; case 'i': ignore_interrupts = 1; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } if (ignore_interrupts) { #ifdef _POSIX_SOURCE struct sigaction sigact; sigact.sa_handler = SIG_IGN; sigemptyset (&sigact.sa_mask); sigact.sa_flags = 0; sigaction (SIGINT, &sigact, NULL); #else /* !_POSIX_SOURCE */ signal (SIGINT, SIG_IGN); #endif /* _POSIX_SOURCE */ } /* Don't let us be killed if one of the output files is a pipe that doesn't consume all its input. */ #ifdef _POSIX_SOURCE { struct sigaction sigact; sigact.sa_handler = SIG_IGN; sigemptyset (&sigact.sa_mask); sigact.sa_flags = 0; sigaction (SIGPIPE, &sigact, NULL); } #else signal (SIGPIPE, SIG_IGN); #endif /* Do *not* warn if tee is given no file arguments. POSIX requires that it work when given no arguments. */ errs = tee (argc - optind, (const char **) &argv[optind]); if (close (STDIN_FILENO) != 0) error (EXIT_FAILURE, errno, _("standard input")); exit (errs); } /* Copy the standard input into each of the NFILES files in FILES and into the standard output. Return 0 if successful, 1 if any errors occur. */ static int tee (int nfiles, const char **files) { FILE **descriptors; char buffer[BUFSIZ]; int bytes_read, i; int ret = 0; const char *mode_string = (append ? "a" : "w"); descriptors = (FILE **) xmalloc ((nfiles + 1) * sizeof (descriptors[0])); /* Move all the names `up' one in the argv array to make room for the entry for standard output. This writes into argv[argc]. */ for (i = nfiles; i >= 1; i--) files[i] = files[i - 1]; SET_BINARY2 (0, 1); /* In the array of NFILES + 1 descriptors, make the first one correspond to standard output. */ descriptors[0] = stdout; files[0] = _("standard output"); SETVBUF (stdout, NULL, _IONBF, 0); for (i = 1; i <= nfiles; i++) { descriptors[i] = fopen (files[i], mode_string); if (descriptors[i] == NULL) { error (0, errno, "%s", files[i]); ret = 1; } else { SETVBUF (descriptors[i], NULL, _IONBF, 0); SET_BINARY (fileno (descriptors[i])); } } while (1) { bytes_read = read (0, buffer, sizeof buffer); #ifdef EINTR if (bytes_read < 0 && errno == EINTR) continue; #endif if (bytes_read <= 0) break; /* Write to all NFILES + 1 descriptors. Standard output is the first one. */ for (i = 0; i <= nfiles; i++) { if (descriptors[i] != NULL) fwrite (buffer, bytes_read, 1, descriptors[i]); } } if (bytes_read == -1) { error (0, errno, _("read error")); ret = 1; } /* Close the files, but not standard output. */ for (i = 1; i <= nfiles; i++) if (descriptors[i] != NULL && (ferror (descriptors[i]) || fclose (descriptors[i]) == EOF)) { error (0, errno, "%s", files[i]); ret = 1; } free (descriptors); return ret; }
/* tr -- a filter to translate characters Copyright (C) 91, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Jim Meyering */ #include <config.h> #include <stdio.h> #include <assert.h> #include <errno.h> #include <sys/types.h> #include <getopt.h> #include "system.h" #include "closeout.h" #include "error.h" #include "safe-read.h" #include "xstrtol.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "tr" #define AUTHORS "Jim Meyering" #define N_CHARS (UCHAR_MAX + 1) /* A pointer to a filtering function. */ typedef size_t (*Filter) (/* unsigned char *, size_t, Filter */); /* Convert from character C to its index in the collating sequence array. Just cast to an unsigned int to avoid problems with sign-extension. */ #define ORD(c) (unsigned int)(c) /* The inverse of ORD. */ #define CHR(i) (unsigned char)(i) /* The value for Spec_list->state that indicates to get_next that it should initialize the tail pointer. Its value should be as large as possible to avoid conflict a valid value for the state field -- and that may be as large as any valid repeat_count. */ #define BEGIN_STATE (INT_MAX - 1) /* The value for Spec_list->state that indicates to get_next that the element pointed to by Spec_list->tail is being considered for the first time on this pass through the list -- it indicates that get_next should make any necessary initializations. */ #define NEW_ELEMENT (BEGIN_STATE + 1) /* A value distinct from any character that may have been stored in a buffer as the result of a block-read in the function squeeze_filter. */ #define NOT_A_CHAR (unsigned int)(-1) /* The following (but not CC_NO_CLASS) are indices into the array of valid character class strings. */ enum Char_class { CC_ALNUM = 0, CC_ALPHA = 1, CC_BLANK = 2, CC_CNTRL = 3, CC_DIGIT = 4, CC_GRAPH = 5, CC_LOWER = 6, CC_PRINT = 7, CC_PUNCT = 8, CC_SPACE = 9, CC_UPPER = 10, CC_XDIGIT = 11, CC_NO_CLASS = 9999 }; /* Character class to which a character (returned by get_next) belonged; but it is set only if the construct from which the character was obtained was one of the character classes [:upper:] or [:lower:]. The value is used only when translating and then, only to make sure that upper and lower class constructs have the same relative positions in string1 and string2. */ enum Upper_Lower_class { UL_LOWER = 0, UL_UPPER = 1, UL_NONE = 2 }; /* A shortcut to ensure that when constructing the translation array, one of the values returned by paired calls to get_next (from s1 and s2) is from [:upper:] and the other is from [:lower:], or neither is from upper or lower. By default, GNU tr permits the identity mappings: from [:upper:] to [:upper:] and [:lower:] to [:lower:]. But when POSIXLY_CORRECT is set, those evoke diagnostics. This array is indexed by values of type enum Upper_Lower_class. */ static int const class_ok[3][3] = { {1, 1, 0}, {1, 1, 0}, {0, 0, 1} }; /* The type of a List_element. See build_spec_list for more details. */ enum Range_element_type { RE_NO_TYPE = 0, RE_NORMAL_CHAR, RE_RANGE, RE_CHAR_CLASS, RE_EQUIV_CLASS, RE_REPEATED_CHAR }; /* One construct in one of tr's argument strings. For example, consider the POSIX version of the classic tr command: tr -cs 'a-zA-Z_' '[\n*]' String1 has 3 constructs, two of which are ranges (a-z and A-Z), and a single normal character, `_'. String2 has one construct. */ struct List_element { enum Range_element_type type; struct List_element *next; union { int normal_char; struct /* unnamed */ { unsigned int first_char; unsigned int last_char; } range; enum Char_class char_class; int equiv_code; struct /* unnamed */ { unsigned int the_repeated_char; size_t repeat_count; } repeated_char; } u; }; /* Each of tr's argument strings is parsed into a form that is easier to work with: a linked list of constructs (struct List_element). Each Spec_list structure also encapsulates various attributes of the corresponding argument string. The attributes are used mainly to verify that the strings are valid in the context of any options specified (like -s, -d, or -c). The main exception is the member `tail', which is first used to construct the list. After construction, it is used by get_next to save its state when traversing the list. The member `state' serves a similar function. */ struct Spec_list { /* Points to the head of the list of range elements. The first struct is a dummy; its members are never used. */ struct List_element *head; /* When appending, points to the last element. When traversing via get_next(), points to the element to process next. Setting Spec_list.state to the value BEGIN_STATE before calling get_next signals get_next to initialize tail to point to head->next. */ struct List_element *tail; /* Used to save state between calls to get_next(). */ unsigned int state; /* Length, in the sense that length ('a-z[:digit:]123abc') is 42 ( = 26 + 10 + 6). */ size_t length; /* The number of [c*] and [c*0] constructs that appear in this spec. */ int n_indefinite_repeats; /* If n_indefinite_repeats is nonzero, this points to the List_element corresponding to the last [c*] or [c*0] construct encountered in this spec. Otherwise it is undefined. */ struct List_element *indefinite_repeat_element; /* Non-zero if this spec contains at least one equivalence class construct e.g. [=c=]. */ int has_equiv_class; /* Non-zero if this spec contains at least one character class construct. E.g. [:digit:]. */ int has_char_class; /* Non-zero if this spec contains at least one of the character class constructs (all but upper and lower) that aren't allowed in s2. */ int has_restricted_char_class; }; /* A representation for escaped string1 or string2. As a string is parsed, any backslash-escaped characters (other than octal or \a, \b, \f, \n, etc.) are marked as such in this structure by setting the corresponding entry in the ESCAPED vector. */ struct E_string { unsigned char *s; int *escaped; size_t len; }; /* Return nonzero if the Ith character of escaped string ES matches C and is not escaped itself. */ #define ES_MATCH(ES, I, C) ((ES)->s[(I)] == (C) && !(ES)->escaped[(I)]) /* The name by which this program was run. */ char *program_name; /* When nonzero, each sequence in the input of a repeated character (call it c) is replaced (in the output) by a single occurrence of c for every c in the squeeze set. */ static int squeeze_repeats = 0; /* When nonzero, removes characters in the delete set from input. */ static int delete = 0; /* Use the complement of set1 in place of set1. */ static int complement = 0; /* When nonzero, this flag causes GNU tr to provide strict compliance with POSIX draft 1003.2.11.2. The POSIX spec says that when -d is used without -s, string2 (if present) must be ignored. Silently ignoring arguments is a bad idea. The default GNU behavior is to give a usage message and exit. Additionally, when this flag is nonzero, tr prints warnings on stderr if it is being used in a manner that is not portable. Applicable warnings are given by default, but are suppressed if the environment variable `POSIXLY_CORRECT' is set, since being POSIX conformant means we can't issue such messages. Warnings on the following topics are suppressed when this variable is nonzero: 1. Ambiguous octal escapes. */ static int posix_pedantic; /* When tr is performing translation and string1 is longer than string2, POSIX says that the result is undefined. That gives the implementor of a POSIX conforming version of tr two reasonable choices for the semantics of this case. * The BSD tr pads string2 to the length of string1 by repeating the last character in string2. * System V tr ignores characters in string1 that have no corresponding character in string2. That is, string1 is effectively truncated to the length of string2. When nonzero, this flag causes GNU tr to imitate the behavior of System V tr when translating with string1 longer than string2. The default is to emulate BSD tr. This flag is ignored in modes where no translation is performed. Emulating the System V tr in this exceptional case causes the relatively common BSD idiom: tr -cs A-Za-z0-9 '\012' to break (it would convert only zero bytes, rather than all non-alphanumerics, to newlines). WARNING: This switch does not provide general BSD or System V compatibility. For example, it doesn't disable the interpretation of the POSIX constructs [:alpha:], [=c=], and [c*10], so if by some unfortunate coincidence you use such constructs in scripts expecting to use some other version of tr, the scripts will break. */ static int truncate_set1 = 0; /* An alias for (!delete && non_option_args == 2). It is set in main and used there and in validate(). */ static int translating; #ifndef BUFSIZ # define BUFSIZ 8192 #endif #define IO_BUF_SIZE BUFSIZ static unsigned char io_buf[IO_BUF_SIZE]; static char const *const char_class_name[] = { "alnum", "alpha", "blank", "cntrl", "digit", "graph", "lower", "print", "punct", "space", "upper", "xdigit" }; #define N_CHAR_CLASSES (sizeof(char_class_name) / sizeof(char_class_name[0])) typedef char SET_TYPE; /* Array of boolean values. A character `c' is a member of the squeeze set if and only if in_squeeze_set[c] is true. The squeeze set is defined by the last (possibly, the only) string argument on the command line when the squeeze option is given. */ static SET_TYPE in_squeeze_set[N_CHARS]; /* Array of boolean values. A character `c' is a member of the delete set if and only if in_delete_set[c] is true. The delete set is defined by the first (or only) string argument on the command line when the delete option is given. */ static SET_TYPE in_delete_set[N_CHARS]; /* Array of character values defining the translation (if any) that tr is to perform. Translation is performed only when there are two specification strings and the delete switch is not given. */ static char xlate[N_CHARS]; static struct option const long_options[] = { {"complement", no_argument, NULL, 'c'}, {"delete", no_argument, NULL, 'd'}, {"squeeze-repeats", no_argument, NULL, 's'}, {"truncate-set1", no_argument, NULL, 't'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... SET1 [SET2]\n\ "), program_name); fputs (_("\ Translate, squeeze, and/or delete characters from standard input,\n\ writing to standard output.\n\ \n\ -c, --complement first complement SET1\n\ -d, --delete delete characters in SET1, do not translate\n\ -s, --squeeze-repeats replace each input sequence of a repeated character\n\ that is listed in SET1 with a single occurrence\n\ of that character\n\ -t, --truncate-set1 first truncate SET1 to length of SET2\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ SETs are specified as strings of characters. Most represent themselves.\n\ Interpreted sequences are:\n\ \n\ \\NNN character with octal value NNN (1 to 3 octal digits)\n\ \\\\ backslash\n\ \\a audible BEL\n\ \\b backspace\n\ \\f form feed\n\ \\n new line\n\ \\r return\n\ \\t horizontal tab\n\ "), stdout); fputs (_("\ \\v vertical tab\n\ CHAR1-CHAR2 all characters from CHAR1 to CHAR2 in ascending order\n\ [CHAR*] in SET2, copies of CHAR until length of SET1\n\ [CHAR*REPEAT] REPEAT copies of CHAR, REPEAT octal if starting with 0\n\ [:alnum:] all letters and digits\n\ [:alpha:] all letters\n\ [:blank:] all horizontal whitespace\n\ [:cntrl:] all control characters\n\ [:digit:] all digits\n\ "), stdout); fputs (_("\ [:graph:] all printable characters, not including space\n\ [:lower:] all lower case letters\n\ [:print:] all printable characters, including space\n\ [:punct:] all punctuation characters\n\ [:space:] all horizontal or vertical whitespace\n\ [:upper:] all upper case letters\n\ [:xdigit:] all hexadecimal digits\n\ [=CHAR=] all characters which are equivalent to CHAR\n\ "), stdout); fputs (_("\ \n\ Translation occurs if -d is not given and both SET1 and SET2 appear.\n\ -t may be used only when translating. SET2 is extended to length of\n\ SET1 by repeating its last character as necessary. \ "), stdout); fputs (_("\ Excess characters\n\ of SET2 are ignored. Only [:lower:] and [:upper:] are guaranteed to\n\ expand in ascending order; used in SET2 while translating, they may\n\ only be used in pairs to specify case conversion. \ "), stdout); fputs (_("\ -s uses SET1 if not\n\ translating nor deleting; else squeezing uses SET2 and occurs after\n\ translation or deletion.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } /* Return nonzero if the character C is a member of the equivalence class containing the character EQUIV_CLASS. */ static int is_equiv_class_member (unsigned int equiv_class, unsigned int c) { return (equiv_class == c); } /* Return nonzero if the character C is a member of the character class CHAR_CLASS. */ static int is_char_class_member (enum Char_class char_class, unsigned int c) { int result; switch (char_class) { case CC_ALNUM: result = ISALNUM (c); break; case CC_ALPHA: result = ISALPHA (c); break; case CC_BLANK: result = ISBLANK (c); break; case CC_CNTRL: result = ISCNTRL (c); break; case CC_DIGIT: result = ISDIGIT_LOCALE (c); break; case CC_GRAPH: result = ISGRAPH (c); break; case CC_LOWER: result = ISLOWER (c); break; case CC_PRINT: result = ISPRINT (c); break; case CC_PUNCT: result = ISPUNCT (c); break; case CC_SPACE: result = ISSPACE (c); break; case CC_UPPER: result = ISUPPER (c); break; case CC_XDIGIT: result = ISXDIGIT (c); break; default: abort (); break; } return result; } static void es_free (struct E_string *es) { free (es->s); free (es->escaped); } /* Perform the first pass over each range-spec argument S, converting all \c and \ddd escapes to their one-byte representations. The conversion is done in-place, so S must point to writable storage. If an invalid quote sequence is found print an error message and return nonzero. Otherwise set *LEN to the length of the resulting string and return zero. The resulting array of characters may contain zero-bytes; however, on input, S is assumed to be null-terminated, and hence cannot contain actual (non-escaped) zero bytes. */ static int unquote (const unsigned char *s, struct E_string *es) { size_t i, j; size_t len; len = strlen ((char *) s); es->s = (unsigned char *) xmalloc (len); es->escaped = (int *) xmalloc (len * sizeof (es->escaped[0])); for (i = 0; i < len; i++) es->escaped[i] = 0; j = 0; for (i = 0; s[i]; i++) { switch (s[i]) { int c; case '\\': switch (s[i + 1]) { int oct_digit; case '\\': c = '\\'; break; case 'a': c = '\007'; break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': c = s[i + 1] - '0'; oct_digit = s[i + 2] - '0'; if (0 <= oct_digit && oct_digit <= 7) { c = 8 * c + oct_digit; ++i; oct_digit = s[i + 2] - '0'; if (0 <= oct_digit && oct_digit <= 7) { if (8 * c + oct_digit < N_CHARS) { c = 8 * c + oct_digit; ++i; } else if (!posix_pedantic) { /* A 3-digit octal number larger than \377 won't fit in 8 bits. So we stop when adding the next digit would put us over the limit and give a warning about the ambiguity. POSIX isn't clear on this, but one person has said that in his interpretation, POSIX says tr can't even give a warning. */ error (0, 0, _("warning: the ambiguous octal escape \ \\%c%c%c is being\n\tinterpreted as the 2-byte sequence \\0%c%c, `%c'"), s[i], s[i + 1], s[i + 2], s[i], s[i + 1], s[i + 2]); } } } break; case '\0': error (0, 0, _("invalid backslash escape at end of string")); return 1; default: if (posix_pedantic) { error (0, 0, _("invalid backslash escape `\\%c'"), s[i + 1]); return 1; } else { c = s[i + 1]; es->escaped[j] = 1; } } ++i; es->s[j++] = c; break; default: es->s[j++] = s[i]; break; } } es->len = j; return 0; } /* If CLASS_STR is a valid character class string, return its index in the global char_class_name array. Otherwise, return CC_NO_CLASS. */ static enum Char_class look_up_char_class (const unsigned char *class_str, size_t len) { unsigned int i; for (i = 0; i < N_CHAR_CLASSES; i++) if (strncmp ((const char *) class_str, char_class_name[i], len) == 0 && strlen (char_class_name[i]) == len) return (enum Char_class) i; return CC_NO_CLASS; } /* Return a newly allocated string with a printable version of C. This function is used solely for formatting error messages. */ static char * make_printable_char (unsigned int c) { char *buf = xmalloc (5); assert (c < N_CHARS); if (ISPRINT (c)) { buf[0] = c; buf[1] = '\0'; } else { sprintf (buf, "\\%03o", c); } return buf; } /* Return a newly allocated copy of S which is suitable for printing. LEN is the number of characters in S. Most non-printing (isprint) characters are represented by a backslash followed by 3 octal digits. However, the characters represented by \c escapes where c is one of [abfnrtv] are represented by their 2-character \c sequences. This function is used solely for printing error messages. */ static char * make_printable_str (const unsigned char *s, size_t len) { /* Worst case is that every character expands to a backslash followed by a 3-character octal escape sequence. */ char *printable_buf = xmalloc (4 * len + 1); char *p = printable_buf; size_t i; for (i = 0; i < len; i++) { char buf[5]; char *tmp = NULL; switch (s[i]) { case '\\': tmp = "\\"; break; case '\007': tmp = "\\a"; break; case '\b': tmp = "\\b"; break; case '\f': tmp = "\\f"; break; case '\n': tmp = "\\n"; break; case '\r': tmp = "\\r"; break; case '\t': tmp = "\\t"; break; case '\v': tmp = "\\v"; break; default: if (ISPRINT (s[i])) { buf[0] = s[i]; buf[1] = '\0'; } else sprintf (buf, "\\%03o", s[i]); tmp = buf; break; } p = stpcpy (p, tmp); } return printable_buf; } /* Append a newly allocated structure representing a character C to the specification list LIST. */ static void append_normal_char (struct Spec_list *list, unsigned int c) { struct List_element *new; new = (struct List_element *) xmalloc (sizeof (struct List_element)); new->next = NULL; new->type = RE_NORMAL_CHAR; new->u.normal_char = c; assert (list->tail); list->tail->next = new; list->tail = new; } /* Append a newly allocated structure representing the range of characters from FIRST to LAST to the specification list LIST. Return nonzero if LAST precedes FIRST in the collating sequence, zero otherwise. This means that '[c-c]' is acceptable. */ static int append_range (struct Spec_list *list, unsigned int first, unsigned int last) { struct List_element *new; if (ORD (first) > ORD (last)) { char *tmp1 = make_printable_char (first); char *tmp2 = make_printable_char (last); error (0, 0, _("range-endpoints of `%s-%s' are in reverse collating sequence order"), tmp1, tmp2); free (tmp1); free (tmp2); return 1; } new = (struct List_element *) xmalloc (sizeof (struct List_element)); new->next = NULL; new->type = RE_RANGE; new->u.range.first_char = first; new->u.range.last_char = last; assert (list->tail); list->tail->next = new; list->tail = new; return 0; } /* If CHAR_CLASS_STR is a valid character class string, append a newly allocated structure representing that character class to the end of the specification list LIST and return 0. If CHAR_CLASS_STR is not a valid string return nonzero. */ static int append_char_class (struct Spec_list *list, const unsigned char *char_class_str, size_t len) { enum Char_class char_class; struct List_element *new; char_class = look_up_char_class (char_class_str, len); if (char_class == CC_NO_CLASS) return 1; new = (struct List_element *) xmalloc (sizeof (struct List_element)); new->next = NULL; new->type = RE_CHAR_CLASS; new->u.char_class = char_class; assert (list->tail); list->tail->next = new; list->tail = new; return 0; } /* Append a newly allocated structure representing a [c*n] repeated character construct to the specification list LIST. THE_CHAR is the single character to be repeated, and REPEAT_COUNT is a non-negative repeat count. */ static void append_repeated_char (struct Spec_list *list, unsigned int the_char, size_t repeat_count) { struct List_element *new; new = (struct List_element *) xmalloc (sizeof (struct List_element)); new->next = NULL; new->type = RE_REPEATED_CHAR; new->u.repeated_char.the_repeated_char = the_char; new->u.repeated_char.repeat_count = repeat_count; assert (list->tail); list->tail->next = new; list->tail = new; } /* Given a string, EQUIV_CLASS_STR, from a [=str=] context and the length of that string, LEN, if LEN is exactly one, append a newly allocated structure representing the specified equivalence class to the specification list, LIST and return zero. If LEN is not 1, return nonzero. */ static int append_equiv_class (struct Spec_list *list, const unsigned char *equiv_class_str, size_t len) { struct List_element *new; if (len != 1) return 1; new = (struct List_element *) xmalloc (sizeof (struct List_element)); new->next = NULL; new->type = RE_EQUIV_CLASS; new->u.equiv_code = *equiv_class_str; assert (list->tail); list->tail->next = new; list->tail = new; return 0; } /* Return a newly allocated copy of the LEN-byte prefix of P. The returned string may contain NUL bytes and is *not* NUL-terminated. */ static unsigned char * xmemdup (const unsigned char *p, size_t len) { unsigned char *tmp = (unsigned char *) xmalloc (len); /* Use memcpy rather than strncpy because `p' may contain zero-bytes. */ memcpy (tmp, p, len); return tmp; } /* Search forward starting at START_IDX for the 2-char sequence (PRE_BRACKET_CHAR,']') in the string P of length P_LEN. If such a sequence is found, set *RESULT_IDX to the index of the first character and return nonzero. Otherwise return zero. P may contain zero bytes. */ static int find_closing_delim (const struct E_string *es, size_t start_idx, unsigned int pre_bracket_char, size_t *result_idx) { size_t i; for (i = start_idx; i < es->len - 1; i++) if (es->s[i] == pre_bracket_char && es->s[i + 1] == ']' && !es->escaped[i] && !es->escaped[i + 1]) { *result_idx = i; return 1; } return 0; } /* Parse the bracketed repeat-char syntax. If the P_LEN characters beginning with P[ START_IDX ] comprise a valid [c*n] construct, then set *CHAR_TO_REPEAT, *REPEAT_COUNT, and *CLOSING_BRACKET_IDX and return zero. If the second character following the opening bracket is not `*' or if no closing bracket can be found, return -1. If a closing bracket is found and the second char is `*', but the string between the `*' and `]' isn't empty, an octal number, or a decimal number, print an error message and return -2. */ static int find_bracketed_repeat (const struct E_string *es, size_t start_idx, unsigned int *char_to_repeat, size_t *repeat_count, size_t *closing_bracket_idx) { size_t i; assert (start_idx + 1 < es->len); if (!ES_MATCH (es, start_idx + 1, '*')) return -1; for (i = start_idx + 2; i < es->len; i++) { if (ES_MATCH (es, i, ']')) { size_t digit_str_len = i - start_idx - 2; *char_to_repeat = es->s[start_idx]; if (digit_str_len == 0) { /* We've matched [c*] -- no explicit repeat count. */ *repeat_count = 0; *closing_bracket_idx = i; return 0; } /* Here, we have found [c*s] where s should be a string of octal (if it starts with `0') or decimal digits. */ { const char *digit_str = (const char *) &es->s[start_idx + 2]; unsigned long int tmp_ulong; char *d_end; int base = 10; /* Select the base manually so we can be sure it's either 8 or 10. If the spec allowed it to be interpreted as hexadecimal, we could have used `0' and let xstrtoul decide. */ if (*digit_str == '0') { base = 8; ++digit_str; --digit_str_len; } if (xstrtoul (digit_str, &d_end, base, &tmp_ulong, NULL) != LONGINT_OK || BEGIN_STATE < tmp_ulong || digit_str + digit_str_len != d_end) { char *tmp = make_printable_str (es->s + start_idx + 2, i - start_idx - 2); error (0, 0, _("invalid repeat count `%s' in [c*n] construct"), tmp); free (tmp); return -2; } *repeat_count = tmp_ulong; } *closing_bracket_idx = i; return 0; } } return -1; /* No bracket found. */ } /* Return nonzero if the string at ES->s[IDX] matches the regular expression `\*[0-9]*\]', zero otherwise. To match, the `*' and the `]' must not be escaped. */ static int star_digits_closebracket (const struct E_string *es, size_t idx) { size_t i; if (!ES_MATCH (es, idx, '*')) return 0; for (i = idx + 1; i < es->len; i++) { if (!ISDIGIT (es->s[i])) { if (ES_MATCH (es, i, ']')) return 1; return 0; } } return 0; } /* Convert string UNESCAPED_STRING (which has been preprocessed to convert backslash-escape sequences) of length LEN characters into a linked list of the following 5 types of constructs: - [:str:] Character class where `str' is one of the 12 valid strings. - [=c=] Equivalence class where `c' is any single character. - [c*n] Repeat the single character `c' `n' times. n may be omitted. However, if `n' is present, it must be a non-negative octal or decimal integer. - r-s Range of characters from `r' to `s'. The second endpoint must not precede the first in the current collating sequence. - c Any other character is interpreted as itself. */ static int build_spec_list (const struct E_string *es, struct Spec_list *result) { const unsigned char *p; size_t i; p = es->s; /* The main for-loop below recognizes the 4 multi-character constructs. A character that matches (in its context) none of the multi-character constructs is classified as `normal'. Since all multi-character constructs have at least 3 characters, any strings of length 2 or less are composed solely of normal characters. Hence, the index of the outer for-loop runs only as far as LEN-2. */ for (i = 0; i + 2 < es->len; /* empty */) { if (ES_MATCH (es, i, '[')) { int matched_multi_char_construct; size_t closing_bracket_idx; unsigned int char_to_repeat; size_t repeat_count; int err; matched_multi_char_construct = 1; if (ES_MATCH (es, i + 1, ':') || ES_MATCH (es, i + 1, '=')) { size_t closing_delim_idx; int found; found = find_closing_delim (es, i + 2, p[i + 1], &closing_delim_idx); if (found) { int parse_failed; size_t opnd_str_len = closing_delim_idx - 1 - (i + 2) + 1; unsigned char *opnd_str; if (opnd_str_len == 0) { if (p[i + 1] == ':') error (0, 0, _("missing character class name `[::]'")); else error (0, 0, _("missing equivalence class character `[==]'")); return 1; } opnd_str = xmemdup (p + i + 2, opnd_str_len); if (p[i + 1] == ':') { parse_failed = append_char_class (result, opnd_str, opnd_str_len); /* FIXME: big comment. */ if (parse_failed) { if (star_digits_closebracket (es, i + 2)) { free (opnd_str); goto try_bracketed_repeat; } else { char *tmp = make_printable_str (opnd_str, opnd_str_len); error (0, 0, _("invalid character class `%s'"), tmp); free (tmp); return 1; } } } else { parse_failed = append_equiv_class (result, opnd_str, opnd_str_len); /* FIXME: big comment. */ if (parse_failed) { if (star_digits_closebracket (es, i + 2)) { free (opnd_str); goto try_bracketed_repeat; } else { char *tmp = make_printable_str (opnd_str, opnd_str_len); error (0, 0, _("%s: equivalence class operand must be a single character"), tmp); free (tmp); return 1; } } } free (opnd_str); /* Return nonzero if append_*_class reports a problem. */ if (parse_failed) return 1; else i = closing_delim_idx + 2; continue; } /* Else fall through. This could be [:*] or [=*]. */ } try_bracketed_repeat: /* Determine whether this is a bracketed repeat range matching the RE \[.\*(dec_or_oct_number)?\]. */ err = find_bracketed_repeat (es, i + 1, &char_to_repeat, &repeat_count, &closing_bracket_idx); if (err == 0) { append_repeated_char (result, char_to_repeat, repeat_count); i = closing_bracket_idx + 1; } else if (err == -1) { matched_multi_char_construct = 0; } else { /* Found a string that looked like [c*n] but the numeric part was invalid. */ return 1; } if (matched_multi_char_construct) continue; /* We reach this point if P does not match [:str:], [=c=], [c*n], or [c*]. Now, see if P looks like a range `[-c' (from `[' to `c'). */ } /* Look ahead one char for ranges like a-z. */ if (ES_MATCH (es, i + 1, '-')) { if (append_range (result, p[i], p[i + 2])) return 1; i += 3; } else { append_normal_char (result, p[i]); ++i; } } /* Now handle the (2 or fewer) remaining characters p[i]..p[es->len - 1]. */ for (; i < es->len; i++) append_normal_char (result, p[i]); return 0; } /* Given a Spec_list S (with its saved state implicit in the values of its members `tail' and `state'), return the next single character in the expansion of S's constructs. If the last character of S was returned on the previous call or if S was empty, this function returns -1. For example, successive calls to get_next where S represents the spec-string 'a-d[y*3]' will return the sequence of values a, b, c, d, y, y, y, -1. Finally, if the construct from which the returned character comes is [:upper:] or [:lower:], the parameter CLASS is given a value to indicate which it was. Otherwise CLASS is set to UL_NONE. This value is used only when constructing the translation table to verify that any occurrences of upper and lower class constructs in the spec-strings appear in the same relative positions. */ static int get_next (struct Spec_list *s, enum Upper_Lower_class *class) { struct List_element *p; int return_val; int i; if (class) *class = UL_NONE; if (s->state == BEGIN_STATE) { s->tail = s->head->next; s->state = NEW_ELEMENT; } p = s->tail; if (p == NULL) return -1; switch (p->type) { case RE_NORMAL_CHAR: return_val = p->u.normal_char; s->state = NEW_ELEMENT; s->tail = p->next; break; case RE_RANGE: if (s->state == NEW_ELEMENT) s->state = ORD (p->u.range.first_char); else ++(s->state); return_val = CHR (s->state); if (s->state == ORD (p->u.range.last_char)) { s->tail = p->next; s->state = NEW_ELEMENT; } break; case RE_CHAR_CLASS: if (class) { int upper_or_lower; switch (p->u.char_class) { case CC_LOWER: *class = UL_LOWER; upper_or_lower = 1; break; case CC_UPPER: *class = UL_UPPER; upper_or_lower = 1; break; default: upper_or_lower = 0; break; } if (upper_or_lower) { s->tail = p->next; s->state = NEW_ELEMENT; return_val = 0; break; } } if (s->state == NEW_ELEMENT) { for (i = 0; i < N_CHARS; i++) if (is_char_class_member (p->u.char_class, i)) break; assert (i < N_CHARS); s->state = i; } assert (is_char_class_member (p->u.char_class, s->state)); return_val = CHR (s->state); for (i = s->state + 1; i < N_CHARS; i++) if (is_char_class_member (p->u.char_class, i)) break; if (i < N_CHARS) s->state = i; else { s->tail = p->next; s->state = NEW_ELEMENT; } break; case RE_EQUIV_CLASS: /* FIXME: this assumes that each character is alone in its own equivalence class (which appears to be correct for my LC_COLLATE. But I don't know of any function that allows one to determine a character's equivalence class. */ return_val = p->u.equiv_code; s->state = NEW_ELEMENT; s->tail = p->next; break; case RE_REPEATED_CHAR: /* Here, a repeat count of n == 0 means don't repeat at all. */ if (p->u.repeated_char.repeat_count == 0) { s->tail = p->next; s->state = NEW_ELEMENT; return_val = get_next (s, class); } else { if (s->state == NEW_ELEMENT) { s->state = 0; } ++(s->state); return_val = p->u.repeated_char.the_repeated_char; if (p->u.repeated_char.repeat_count > 0 && s->state == p->u.repeated_char.repeat_count) { s->tail = p->next; s->state = NEW_ELEMENT; } } break; case RE_NO_TYPE: abort (); break; default: abort (); break; } return return_val; } /* This is a minor kludge. This function is called from get_spec_stats to determine the cardinality of a set derived from a complemented string. It's a kludge in that some of the same operations are (duplicated) performed in set_initialize. */ static int card_of_complement (struct Spec_list *s) { int c; int cardinality = N_CHARS; SET_TYPE in_set[N_CHARS]; memset (in_set, 0, N_CHARS * sizeof (in_set[0])); s->state = BEGIN_STATE; while ((c = get_next (s, NULL)) != -1) if (!in_set[c]++) --cardinality; return cardinality; } /* Gather statistics about the spec-list S in preparation for the tests in validate that determine the consistency of the specs. This function is called at most twice; once for string1, and again for any string2. LEN_S1 < 0 indicates that this is the first call and that S represents string1. When LEN_S1 >= 0, it is the length of the expansion of the constructs in string1, and we can use its value to resolve any indefinite repeat construct in S (which represents string2). Hence, this function has the side-effect that it converts a valid [c*] construct in string2 to [c*n] where n is large enough (or 0) to give string2 the same length as string1. For example, with the command tr a-z 'A[\n*]Z' on the second call to get_spec_stats, LEN_S1 would be 26 and S (representing string2) would be converted to 'A[\n*24]Z'. */ static void get_spec_stats (struct Spec_list *s) { struct List_element *p; int len = 0; s->n_indefinite_repeats = 0; s->has_equiv_class = 0; s->has_restricted_char_class = 0; s->has_char_class = 0; for (p = s->head->next; p; p = p->next) { switch (p->type) { int i; case RE_NORMAL_CHAR: ++len; break; case RE_RANGE: assert (p->u.range.last_char >= p->u.range.first_char); len += p->u.range.last_char - p->u.range.first_char + 1; break; case RE_CHAR_CLASS: s->has_char_class = 1; for (i = 0; i < N_CHARS; i++) if (is_char_class_member (p->u.char_class, i)) ++len; switch (p->u.char_class) { case CC_UPPER: case CC_LOWER: break; default: s->has_restricted_char_class = 1; break; } break; case RE_EQUIV_CLASS: for (i = 0; i < N_CHARS; i++) if (is_equiv_class_member (p->u.equiv_code, i)) ++len; s->has_equiv_class = 1; break; case RE_REPEATED_CHAR: if (p->u.repeated_char.repeat_count > 0) len += p->u.repeated_char.repeat_count; else if (p->u.repeated_char.repeat_count == 0) { s->indefinite_repeat_element = p; ++(s->n_indefinite_repeats); } break; case RE_NO_TYPE: assert (0); break; } } s->length = len; } static void get_s1_spec_stats (struct Spec_list *s1) { get_spec_stats (s1); if (complement) s1->length = card_of_complement (s1); } static void get_s2_spec_stats (struct Spec_list *s2, size_t len_s1) { get_spec_stats (s2); if (len_s1 >= s2->length && s2->n_indefinite_repeats == 1) { s2->indefinite_repeat_element->u.repeated_char.repeat_count = len_s1 - s2->length; s2->length = len_s1; } } static void spec_init (struct Spec_list *spec_list) { spec_list->head = spec_list->tail = (struct List_element *) xmalloc (sizeof (struct List_element)); spec_list->head->next = NULL; } /* This function makes two passes over the argument string S. The first one converts all \c and \ddd escapes to their one-byte representations. The second constructs a linked specification list, SPEC_LIST, of the characters and constructs that comprise the argument string. If either of these passes detects an error, this function returns nonzero. */ static int parse_str (const unsigned char *s, struct Spec_list *spec_list) { struct E_string es; int fail; fail = unquote (s, &es); if (!fail) fail = build_spec_list (&es, spec_list); es_free (&es); return fail; } /* Given two specification lists, S1 and S2, and assuming that S1->length > S2->length, append a single [c*n] element to S2 where c is the last character in the expansion of S2 and n is the difference between the two lengths. Upon successful completion, S2->length is set to S1->length. The only way this function can fail to make S2 as long as S1 is when S2 has zero-length, since in that case, there is no last character to repeat. So S2->length is required to be at least 1. Providing this functionality allows the user to do some pretty non-BSD (and non-portable) things: For example, the command tr -cs '[:upper:]0-9' '[:lower:]' is almost guaranteed to give results that depend on your collating sequence. */ static void string2_extend (const struct Spec_list *s1, struct Spec_list *s2) { struct List_element *p; int char_to_repeat; int i; assert (translating); assert (s1->length > s2->length); assert (s2->length > 0); p = s2->tail; switch (p->type) { case RE_NORMAL_CHAR: char_to_repeat = p->u.normal_char; break; case RE_RANGE: char_to_repeat = p->u.range.last_char; break; case RE_CHAR_CLASS: for (i = N_CHARS; i >= 0; i--) if (is_char_class_member (p->u.char_class, i)) break; assert (i >= 0); char_to_repeat = CHR (i); break; case RE_REPEATED_CHAR: char_to_repeat = p->u.repeated_char.the_repeated_char; break; case RE_EQUIV_CLASS: /* This shouldn't happen, because validate exits with an error if it finds an equiv class in string2 when translating. */ abort (); break; case RE_NO_TYPE: abort (); break; default: abort (); break; } append_repeated_char (s2, char_to_repeat, s1->length - s2->length); s2->length = s1->length; } /* Return non-zero if S is a non-empty list in which exactly one character (but potentially, many instances of it) appears. E.g. [X*] or xxxxxxxx. */ static int homogeneous_spec_list (struct Spec_list *s) { int b, c; s->state = BEGIN_STATE; if ((b = get_next (s, NULL)) == -1) return 0; while ((c = get_next (s, NULL)) != -1) if (c != b) return 0; return 1; } /* Die with an error message if S1 and S2 describe strings that are not valid with the given command line switches. A side effect of this function is that if a valid [c*] or [c*0] construct appears in string2, it is converted to [c*n] with a value for n that makes s2->length == s1->length. By the same token, if the --truncate-set1 option is not given, S2 may be extended. */ static void validate (struct Spec_list *s1, struct Spec_list *s2) { get_s1_spec_stats (s1); if (s1->n_indefinite_repeats > 0) { error (EXIT_FAILURE, 0, _("the [c*] repeat construct may not appear in string1")); } if (s2) { get_s2_spec_stats (s2, s1->length); if (s2->n_indefinite_repeats > 1) { error (EXIT_FAILURE, 0, _("only one [c*] repeat construct may appear in string2")); } if (translating) { if (s2->has_equiv_class) { error (EXIT_FAILURE, 0, _("[=c=] expressions may not appear in string2 \ when translating")); } if (s1->length > s2->length) { if (!truncate_set1) { /* string2 must be non-empty unless --truncate-set1 is given or string1 is empty. */ if (s2->length == 0) error (EXIT_FAILURE, 0, _("when not truncating set1, string2 must be non-empty")); string2_extend (s1, s2); } } if (complement && s1->has_char_class && ! (s2->length == s1->length && homogeneous_spec_list (s2))) { error (EXIT_FAILURE, 0, _("when translating with complemented character classes,\ \nstring2 must map all characters in the domain to one")); } if (s2->has_restricted_char_class) { error (EXIT_FAILURE, 0, _("when translating, the only character classes that may \ appear in\nstring2 are `upper' and `lower'")); } } else /* Not translating. */ { if (s2->n_indefinite_repeats > 0) error (EXIT_FAILURE, 0, _("the [c*] construct may appear in string2 only \ when translating")); } } } /* Read buffers of SIZE bytes via the function READER (if READER is NULL, read from stdin) until EOF. When non-NULL, READER is either read_and_delete or read_and_xlate. After each buffer is read, it is processed and written to stdout. The buffers are processed so that multiple consecutive occurrences of the same character in the input stream are replaced by a single occurrence of that character if the character is in the squeeze set. */ static void squeeze_filter (unsigned char *buf, size_t size, Filter reader) { unsigned int char_to_squeeze = NOT_A_CHAR; size_t i = 0; size_t nr = 0; for (;;) { size_t begin; if (i >= nr) { if (reader == NULL) { nr = safe_read (0, (char *) buf, size); if (nr == SAFE_READ_ERROR) error (EXIT_FAILURE, errno, _("read error")); } else { nr = (*reader) (buf, size, NULL); } if (nr == 0) break; i = 0; } begin = i; if (char_to_squeeze == NOT_A_CHAR) { size_t out_len; /* Here, by being a little tricky, we can get a significant performance increase in most cases when the input is reasonably large. Since tr will modify the input only if two consecutive (and identical) input characters are in the squeeze set, we can step by two through the data when searching for a character in the squeeze set. This means there may be a little more work in a few cases and perhaps twice as much work in the worst cases where most of the input is removed by squeezing repeats. But most uses of this functionality seem to remove less than 20-30% of the input. */ for (; i < nr && !in_squeeze_set[buf[i]]; i += 2) ; /* empty */ /* There is a special case when i == nr and we've just skipped a character (the last one in buf) that is in the squeeze set. */ if (i == nr && in_squeeze_set[buf[i - 1]]) --i; if (i >= nr) out_len = nr - begin; else { char_to_squeeze = buf[i]; /* We're about to output buf[begin..i]. */ out_len = i - begin + 1; /* But since we stepped by 2 in the loop above, out_len may be one too large. */ if (i > 0 && buf[i - 1] == char_to_squeeze) --out_len; /* Advance i to the index of first character to be considered when looking for a char different from char_to_squeeze. */ ++i; } if (out_len > 0 && fwrite ((char *) &buf[begin], 1, out_len, stdout) == 0) error (EXIT_FAILURE, errno, _("write error")); } if (char_to_squeeze != NOT_A_CHAR) { /* Advance i to index of first char != char_to_squeeze (or to nr if all the rest of the characters in this buffer are the same as char_to_squeeze). */ for (; i < nr && buf[i] == char_to_squeeze; i++) ; /* empty */ if (i < nr) char_to_squeeze = NOT_A_CHAR; /* If (i >= nr) we've squeezed the last character in this buffer. So now we have to read a new buffer and continue comparing characters against char_to_squeeze. */ } } } /* Read buffers of SIZE bytes from stdin until one is found that contains at least one character not in the delete set. Store in the array BUF, all characters from that buffer that are not in the delete set, and return the number of characters saved or 0 upon EOF. */ static size_t read_and_delete (unsigned char *buf, size_t size, Filter not_used) { size_t n_saved; static int hit_eof = 0; assert (not_used == NULL); if (hit_eof) return 0; /* This enclosing do-while loop is to make sure that we don't return zero (indicating EOF) when we've just deleted all the characters in a buffer. */ do { size_t i; size_t nr = safe_read (0, (char *) buf, size); if (nr == SAFE_READ_ERROR) error (EXIT_FAILURE, errno, _("read error")); if (nr == 0) { hit_eof = 1; return 0; } /* This first loop may be a waste of code, but gives much better performance when no characters are deleted in the beginning of a buffer. It just avoids the copying of buf[i] into buf[n_saved] when it would be a NOP. */ for (i = 0; i < nr && !in_delete_set[buf[i]]; i++) /* empty */ ; n_saved = i; for (++i; i < nr; i++) if (!in_delete_set[buf[i]]) buf[n_saved++] = buf[i]; } while (n_saved == 0); return n_saved; } /* Read at most SIZE bytes from stdin into the array BUF. Then perform the in-place and one-to-one mapping specified by the global array `xlate'. Return the number of characters read, or 0 upon EOF. */ static size_t read_and_xlate (unsigned char *buf, size_t size, Filter not_used) { size_t bytes_read = 0; static int hit_eof = 0; size_t i; assert (not_used == NULL); if (hit_eof) return 0; bytes_read = safe_read (0, (char *) buf, size); if (bytes_read == SAFE_READ_ERROR) error (EXIT_FAILURE, errno, _("read error")); if (bytes_read == 0) { hit_eof = 1; return 0; } for (i = 0; i < bytes_read; i++) buf[i] = xlate[buf[i]]; return bytes_read; } /* Initialize a boolean membership set IN_SET with the character values obtained by traversing the linked list of constructs S using the function `get_next'. If COMPLEMENT_THIS_SET is nonzero the resulting set is complemented. */ static void set_initialize (struct Spec_list *s, int complement_this_set, SET_TYPE *in_set) { int c; size_t i; memset (in_set, 0, N_CHARS * sizeof (in_set[0])); s->state = BEGIN_STATE; while ((c = get_next (s, NULL)) != -1) in_set[c] = 1; if (complement_this_set) for (i = 0; i < N_CHARS; i++) in_set[i] = (!in_set[i]); } int main (int argc, char **argv) { int c; int non_option_args; struct Spec_list buf1, buf2; struct Spec_list *s1 = &buf1; struct Spec_list *s2 = &buf2; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); while ((c = getopt_long (argc, argv, "cdst", long_options, NULL)) != -1) { switch (c) { case 0: break; case 'c': complement = 1; break; case 'd': delete = 1; break; case 's': squeeze_repeats = 1; break; case 't': truncate_set1 = 1; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (2); break; } } posix_pedantic = (getenv ("POSIXLY_CORRECT") != NULL); non_option_args = argc - optind; translating = (non_option_args == 2 && !delete); /* Change this test if it is valid to give tr no options and no args at all. POSIX doesn't specifically say anything either way, but it looks like they implied it's invalid by omission. If you want to make tr do a slow imitation of `cat' use `tr a a'. */ if (non_option_args > 2) { error (0, 0, _("too many arguments")); usage (2); } if (!delete && !squeeze_repeats && non_option_args != 2) error (EXIT_FAILURE, 0, _("two strings must be given when translating")); if (delete && squeeze_repeats && non_option_args != 2) error (EXIT_FAILURE, 0, _("two strings must be given when both \ deleting and squeezing repeats")); /* If --delete is given without --squeeze-repeats, then only one string argument may be specified. But POSIX says to ignore any string2 in this case, so if POSIXLY_CORRECT is set, pretend we never saw string2. But I think this deserves a fatal error, so that's the default. */ if ((delete && !squeeze_repeats) && non_option_args != 1) { if (posix_pedantic && non_option_args == 2) --non_option_args; else error (EXIT_FAILURE, 0, _("only one string may be given when deleting \ without squeezing repeats")); } if (squeeze_repeats && non_option_args == 0) error (EXIT_FAILURE, 0, _("at least one string must be given when squeezing repeats")); spec_init (s1); if (parse_str ((unsigned char *) argv[optind], s1)) exit (EXIT_FAILURE); if (non_option_args == 2) { spec_init (s2); if (parse_str ((unsigned char *) argv[optind + 1], s2)) exit (EXIT_FAILURE); } else s2 = NULL; validate (s1, s2); /* Use binary I/O, since `tr' is sometimes used to transliterate non-printable characters, or characters which are stripped away by text-mode reads (like CR and ^Z). */ SET_BINARY2 (STDIN_FILENO, STDOUT_FILENO); if (squeeze_repeats && non_option_args == 1) { set_initialize (s1, complement, in_squeeze_set); squeeze_filter (io_buf, IO_BUF_SIZE, NULL); } else if (delete && non_option_args == 1) { size_t nr; set_initialize (s1, complement, in_delete_set); do { nr = read_and_delete (io_buf, IO_BUF_SIZE, NULL); if (nr > 0 && fwrite ((char *) io_buf, 1, nr, stdout) == 0) error (EXIT_FAILURE, errno, _("write error")); } while (nr > 0); } else if (squeeze_repeats && delete && non_option_args == 2) { set_initialize (s1, complement, in_delete_set); set_initialize (s2, 0, in_squeeze_set); squeeze_filter (io_buf, IO_BUF_SIZE, read_and_delete); } else if (translating) { if (complement) { int i; SET_TYPE *in_s1 = in_delete_set; set_initialize (s1, 0, in_s1); s2->state = BEGIN_STATE; for (i = 0; i < N_CHARS; i++) xlate[i] = i; for (i = 0; i < N_CHARS; i++) { if (!in_s1[i]) { int ch = get_next (s2, NULL); assert (ch != -1 || truncate_set1); if (ch == -1) { /* This will happen when tr is invoked like e.g. tr -cs A-Za-z0-9 '\012'. */ break; } xlate[i] = ch; } } assert (get_next (s2, NULL) == -1 || truncate_set1); } else { int c1, c2; int i; enum Upper_Lower_class class_s1; enum Upper_Lower_class class_s2; for (i = 0; i < N_CHARS; i++) xlate[i] = i; s1->state = BEGIN_STATE; s2->state = BEGIN_STATE; for (;;) { c1 = get_next (s1, &class_s1); c2 = get_next (s2, &class_s2); if (!class_ok[(int) class_s1][(int) class_s2]) error (EXIT_FAILURE, 0, _("misaligned [:upper:] and/or [:lower:] construct")); if (class_s1 == UL_LOWER && class_s2 == UL_UPPER) { for (i = 0; i < N_CHARS; i++) if (ISLOWER (i)) xlate[i] = toupper (i); } else if (class_s1 == UL_UPPER && class_s2 == UL_LOWER) { for (i = 0; i < N_CHARS; i++) if (ISUPPER (i)) xlate[i] = tolower (i); } else if ((class_s1 == UL_LOWER && class_s2 == UL_LOWER) || (class_s1 == UL_UPPER && class_s2 == UL_UPPER)) { /* By default, GNU tr permits the identity mappings: from [:upper:] to [:upper:] and [:lower:] to [:lower:]. But when POSIXLY_CORRECT is set, those evoke diagnostics. */ if (posix_pedantic) { error (EXIT_FAILURE, 0, _("\ invalid identity mapping; when translating, any [:lower:] or [:upper:]\n\ construct in string1 must be aligned with a corresponding construct\n\ ([:upper:] or [:lower:], respectively) in string2")); } } else { /* The following should have been checked by validate... */ if (c1 == -1 || c2 == -1) break; xlate[c1] = c2; } } assert (c1 == -1 || truncate_set1); } if (squeeze_repeats) { set_initialize (s2, 0, in_squeeze_set); squeeze_filter (io_buf, IO_BUF_SIZE, read_and_xlate); } else { size_t bytes_read; do { bytes_read = read_and_xlate (io_buf, IO_BUF_SIZE, NULL); if (bytes_read > 0 && fwrite ((char *) io_buf, 1, bytes_read, stdout) == 0) error (EXIT_FAILURE, errno, _("write error")); } while (bytes_read > 0); } } if (close (STDIN_FILENO) != 0) error (EXIT_FAILURE, errno, _("standard input")); exit (EXIT_SUCCESS); }
/* tsort - topological sort. Copyright (C) 1998-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Mark Kettenis <kettenis@phys.uva.nl>. */ /* The topological sort is done according to Algorithm T (Topological sort) in Donald E. Knuth, The Art of Computer Programming, Volume 1/Fundamental Algorithms, page 262. */ #include <config.h> #include <stdio.h> #include <assert.h> #include <getopt.h> #include <sys/types.h> #include "system.h" #include "closeout.h" #include "long-options.h" #include "error.h" #include "readtokens.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "tsort" #define AUTHORS "Mark Kettenis" /* Token delimiters when reading from a file. */ #define DELIM " \t\n" /* Members of the list of successors. */ struct successor { struct item *suc; struct successor *next; }; /* Each string is held in core as the head of a list of successors. */ struct item { const char *str; struct item *left, *right; int balance; int count; struct item *qlink; struct successor *top; }; /* The name this program was run with. */ char *program_name; /* Nonzero if any of the input files are the standard input. */ static int have_read_stdin; /* The error code to return to the system. */ static int exit_status; /* The head of the sorted list. */ static struct item *head = NULL; /* The tail of the list of `zeros', strings that have no predecessors. */ static struct item *zeros = NULL; /* Used for loop detection. */ static struct item *loop = NULL; /* The number of strings to sort. */ static int n_strings = 0; static struct option const long_options[] = { { NULL, 0, NULL, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION] [FILE]\n\ Write totally ordered list consistent with the partial ordering in FILE.\n\ With no FILE, or when FILE is -, read standard input.\n\ \n\ "), program_name); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } /* Create a new item/node for STR. */ static struct item * new_item (const char *str) { struct item *k = xmalloc (sizeof (struct item)); k->str = (str ? xstrdup (str): NULL); k->left = k->right = NULL; k->balance = 0; /* T1. Initialize (COUNT[k] <- 0 and TOP[k] <- ^). */ k->count = 0; k->qlink = NULL; k->top = NULL; return k; } /* Search binary tree rooted at *ROOT for STR. Allocate a new tree if *ROOT is NULL. Insert a node/item for STR if not found. Return the node/item found/created for STR. This is done according to Algorithm A (Balanced tree search and insertion) in Donald E. Knuth, The Art of Computer Programming, Volume 3/Searching and Sorting, pages 455--457. */ static struct item * search_item (struct item *root, const char *str) { struct item *p, *q, *r, *s, *t; int a; assert (root); /* Make sure the tree is not empty, since that is what the algorithm below expects. */ if (root->right == NULL) return (root->right = new_item (str)); /* A1. Initialize. */ t = root; s = p = root->right; for (;;) { /* A2. Compare. */ a = strcmp (str, p->str); if (a == 0) return p; /* A3 & A4. Move left & right. */ if (a < 0) q = p->left; else q = p->right; if (q == NULL) { /* A5. Insert. */ q = new_item (str); /* A3 & A4. (continued). */ if (a < 0) p->left = q; else p->right = q; /* A6. Adjust balance factors. */ assert (!STREQ (str, s->str)); if (strcmp (str, s->str) < 0) { r = p = s->left; a = -1; } else { r = p = s->right; a = 1; } while (p != q) { assert (!STREQ (str, p->str)); if (strcmp (str, p->str) < 0) { p->balance = -1; p = p->left; } else { p->balance = 1; p = p->right; } } /* A7. Balancing act. */ if (s->balance == 0 || s->balance == -a) { s->balance += a; return q; } if (r->balance == a) { /* A8. Single Rotation. */ p = r; if (a < 0) { s->left = r->right; r->right = s; } else { s->right = r->left; r->left = s; } s->balance = r->balance = 0; } else { /* A9. Double rotation. */ if (a < 0) { p = r->right; r->right = p->left; p->left = r; s->left = p->right; p->right = s; } else { p = r->left; r->left = p->right; p->right = r; s->right = p->left; p->left = s; } s->balance = 0; r->balance = 0; if (p->balance == a) s->balance = -a; else if (p->balance == -a) r->balance = a; p->balance = 0; } /* A10. Finishing touch. */ if (s == t->right) t->right = p; else t->left = p; return q; } /* A3 & A4. (continued). */ if (q->balance) { t = p; s = q; } p = q; } /* NOTREACHED */ } /* Record the fact that J precedes K. */ static void record_relation (struct item *j, struct item *k) { struct successor *p; if (!STREQ (j->str, k->str)) { k->count++; p = xmalloc (sizeof (struct successor)); p->suc = k; p->next = j->top; j->top = p; } } static int count_items (struct item *unused ATTRIBUTE_UNUSED) { n_strings++; return 0; } static int scan_zeros (struct item *k) { /* Ignore strings that have already been printed. */ if (k->count == 0 && k->str) { if (head == NULL) head = k; else zeros->qlink = k; zeros = k; } return 0; } /* Try to detect the loop. If we have detected that K is part of a loop, print the loop on standard error, remove a relation to break the loop, and return non-zero. The loop detection strategy is as follows: Realise that what we're dealing with is essentially a directed graph. If we find an item that is part of a graph that contains a cycle we traverse the graph in backwards direction. In general there is no unique way to do this, but that is no problem. If we encounter an item that we have encountered before, we know that we've found a cycle. All we have to do now is retrace our steps, printing out the items until we encounter that item again. (This is not necessarily the item that we started from originally.) Since the order in which the items are stored in the tree is not related to the specified partial ordering, we may need to walk the tree several times before the loop has completely been constructed. If the loop was found, the global variable LOOP will be NULL. */ static int detect_loop (struct item *k) { if (k->count > 0) { /* K does not have to be part of a cycle. It is however part of a graph that contains a cycle. */ if (loop == NULL) /* Start traversing the graph at K. */ loop = k; else { struct successor **p = &k->top; while (*p) { if ((*p)->suc == loop) { if (k->qlink) { /* We have found a loop. Retrace our steps. */ while (loop) { struct item *tmp = loop->qlink; fprintf (stderr, "%s: %s\n", program_name, loop->str); /* Until we encounter K again. */ if (loop == k) { /* Remove relation. */ (*p)->suc->count--; *p = (*p)->next; break; } /* Tidy things up since we might have to detect another loop. */ loop->qlink = NULL; loop = tmp; } while (loop) { struct item *tmp = loop->qlink; loop->qlink = NULL; loop = tmp; } /* Since we have found the loop, stop walking the tree. */ return 1; } else { k->qlink = loop; loop = k; break; } } p = &(*p)->next; } } } return 0; } /* Recurse (sub)tree rooted at ROOT, calling ACTION for each node. Stop when ACTION returns non-zero. */ static int recurse_tree (struct item *root, int (*action) (struct item *)) { if (root->left == NULL && root->right == NULL) return (*action) (root); else { if (root->left != NULL) if (recurse_tree (root->left, action)) return 1; if ((*action) (root)) return 1; if (root->right != NULL) if (recurse_tree (root->right, action)) return 1; } return 0; } /* Walk the tree specified by the head ROOT, calling ACTION for each node. */ static void walk_tree (struct item *root, int (*action) (struct item *)) { if (root->right) recurse_tree (root->right, action); } /* Do a topological sort on FILE. */ static void tsort (const char *file) { struct item *root; struct item *j = NULL; struct item *k = NULL; register FILE *fp; token_buffer tokenbuffer; /* Intialize the head of the tree will hold the strings we're sorting. */ root = new_item (NULL); if (STREQ (file, "-")) { fp = stdin; have_read_stdin = 1; } else { fp = fopen (file, "r"); if (fp == NULL) error (EXIT_FAILURE, errno, "%s", file); } init_tokenbuffer (&tokenbuffer); while (1) { long int len; /* T2. Next Relation. */ len = readtoken (fp, DELIM, sizeof (DELIM) - 1, &tokenbuffer); if (len < 0) break; assert (len != 0); k = search_item (root, tokenbuffer.buffer); if (j) { /* T3. Record the relation. */ record_relation (j, k); k = NULL; } j = k; } /* T1. Initialize (N <- n). */ walk_tree (root, count_items); while (n_strings > 0) { /* T4. Scan for zeros. */ walk_tree (root, scan_zeros); while (head) { struct successor *p = head->top; /* T5. Output front of queue. */ printf ("%s\n", head->str); head->str = NULL; /* Avoid printing the same string twice. */ n_strings--; /* T6. Erase relations. */ while (p) { p->suc->count--; if (p->suc->count == 0) { zeros->qlink = p->suc; zeros = p->suc; } p = p->next; } /* T7. Remove from queue. */ head = head->qlink; } /* T8. End of process. */ assert (n_strings >= 0); if (n_strings > 0) { /* The input contains a loop. */ error (0, 0, _("%s: input contains a loop:"), (have_read_stdin ? "-" : file)); exit_status = 1; /* Print the loop and remove a relation to break it. */ do walk_tree (root, detect_loop); while (loop); } } } int main (int argc, char **argv) { int opt; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); exit_status = 0; parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE, VERSION, AUTHORS, usage); while ((opt = getopt_long (argc, argv, "", long_options, NULL)) != -1) switch (opt) { case 0: /* long option */ break; default: usage (EXIT_FAILURE); } have_read_stdin = 0; if (optind + 1 < argc) { error (0, 0, _("only one argument may be specified")); usage (EXIT_FAILURE); } if (optind < argc) tsort (argv[optind]); else tsort ("-"); if (have_read_stdin && fclose (stdin) == EOF) error (EXIT_FAILURE, errno, _("standard input")); exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); }
/* unexpand - convert spaces to tabs Copyright (C) 89, 91, 1995-2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* By default, convert only maximal strings of initial blanks and tabs into tabs. Preserves backspace characters in the output; they decrement the column count for tab calculations. The default action is equivalent to -8. Options: --tabs=tab1[,tab2[,...]] -t tab1[,tab2[,...]] -tab1[,tab2[,...]] If only one tab stop is given, set the tabs tab1 spaces apart instead of the default 8. Otherwise, set the tabs at columns tab1, tab2, etc. (numbered from 0); replace any tabs beyond the tabstops given with single spaces. --all -a Use tabs wherever they would replace 2 or more spaces, not just at the beginnings of lines. David MacKenzie <djm@gnu.ai.mit.edu> */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include "system.h" #include "closeout.h" #include "error.h" #include "posixver.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "unexpand" #define AUTHORS "David MacKenzie" /* The number of bytes added at a time to the amount of memory allocated for the output line. */ #define OUTPUT_BLOCK 256 /* The number of bytes added at a time to the amount of memory allocated for the list of tabstops. */ #define TABLIST_BLOCK 256 /* A sentinel value that's placed at the end of the list of tab stops. This value must be a large number, but not so large that adding the length of a line to it would cause the column variable to overflow. */ #define TAB_STOP_SENTINEL INT_MAX /* The name this program was run with. */ char *program_name; /* If nonzero, convert blanks even after nonblank characters have been read on the line. */ static int convert_entire_line; /* If nonzero, the size of all tab stops. If zero, use `tab_list' instead. */ static int tab_size; /* Array of the explicit column numbers of the tab stops; after `tab_list' is exhausted, the rest of the line is printed unchanged. The first column is column 0. */ static int *tab_list; /* The index of the first invalid element of `tab_list', where the next element can be added. */ static int first_free_tab; /* Null-terminated array of input filenames. */ static char **file_list; /* Default for `file_list' if no files are given on the command line. */ static char *stdin_argv[] = { "-", NULL }; /* Nonzero if we have ever read standard input. */ static int have_read_stdin; /* Status to return to the system. */ static int exit_status; /* For long options that have no equivalent short option, use a non-character as a pseudo short option, starting with CHAR_MAX + 1. */ enum { CONVERT_FIRST_ONLY_OPTION = CHAR_MAX + 1 }; static struct option const longopts[] = { {"tabs", required_argument, NULL, 't'}, {"all", no_argument, NULL, 'a'}, {"first-only", no_argument, NULL, CONVERT_FIRST_ONLY_OPTION}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; /* Add tab stop TABVAL to the end of `tab_list', except if TABVAL is -1, do nothing. */ static void add_tabstop (int tabval) { if (tabval == -1) return; if (first_free_tab % TABLIST_BLOCK == 0) tab_list = (int *) xrealloc ((char *) tab_list, first_free_tab + TABLIST_BLOCK); tab_list[first_free_tab++] = tabval; } /* Add the comma or blank separated list of tabstops STOPS to the list of tabstops. */ static void parse_tabstops (const char *stops) { int tabval = -1; for (; *stops; stops++) { if (*stops == ',' || ISBLANK (*stops)) { add_tabstop (tabval); tabval = -1; } else if (ISDIGIT (*stops)) { if (tabval == -1) tabval = 0; tabval = tabval * 10 + *stops - '0'; } else error (EXIT_FAILURE, 0, _("tab size contains an invalid character")); } add_tabstop (tabval); } /* Check that the list of tabstops TABS, with ENTRIES entries, contains only nonzero, ascending values. */ static void validate_tabstops (const int *tabs, int entries) { int prev_tab = 0; int i; for (i = 0; i < entries; i++) { if (tabs[i] == 0) error (EXIT_FAILURE, 0, _("tab size cannot be 0")); if (tabs[i] <= prev_tab) error (EXIT_FAILURE, 0, _("tab sizes must be ascending")); prev_tab = tabs[i]; } } /* Close the old stream pointer FP if it is non-NULL, and return a new one opened to read the next input file. Open a filename of `-' as the standard input. Return NULL if there are no more input files. */ static FILE * next_file (FILE *fp) { static char *prev_file; char *file; if (fp) { if (ferror (fp)) { error (0, errno, "%s", prev_file); exit_status = 1; } if (fp == stdin) clearerr (fp); /* Also clear EOF. */ else if (fclose (fp) == EOF) { error (0, errno, "%s", prev_file); exit_status = 1; } } while ((file = *file_list++) != NULL) { if (file[0] == '-' && file[1] == '\0') { have_read_stdin = 1; prev_file = file; return stdin; } fp = fopen (file, "r"); if (fp) { prev_file = file; return fp; } error (0, errno, "%s", file); exit_status = 1; } return NULL; } /* Change spaces to tabs, writing to stdout. Read each file in `file_list', in order. */ static void unexpand (void) { FILE *fp; /* Input stream. */ int c; /* Each input character. */ /* Index in `tab_list' of next tabstop: */ int tab_index = 0; /* For calculating width of pending tabs. */ int print_tab_index = 0; /* For printing as many tabs as possible. */ unsigned int column = 0; /* Column on screen of next char. */ int next_tab_column; /* Column the next tab stop is on. */ int convert = 1; /* If nonzero, perform translations. */ unsigned int pending = 0; /* Pending columns of blanks. */ fp = next_file ((FILE *) NULL); if (fp == NULL) return; /* Binary I/O will preserve the original EOL style (DOS/Unix) of files. */ SET_BINARY2 (fileno (fp), STDOUT_FILENO); for (;;) { c = getc (fp); if (c == ' ' && convert && column < TAB_STOP_SENTINEL) { ++pending; ++column; } else if (c == '\t' && convert) { if (tab_size == 0) { /* Do not let tab_index == first_free_tab; stop when it is 1 less. */ while (tab_index < first_free_tab - 1 && column >= tab_list[tab_index]) tab_index++; next_tab_column = tab_list[tab_index]; if (tab_index < first_free_tab - 1) tab_index++; if (column >= next_tab_column) { convert = 0; /* Ran out of tab stops. */ goto flush_pend; } } else { next_tab_column = column + tab_size - column % tab_size; } pending += next_tab_column - column; column = next_tab_column; } else { flush_pend: /* Flush pending spaces. Print as many tabs as possible, then print the rest as spaces. */ if (pending == 1) { putchar (' '); pending = 0; } column -= pending; while (pending > 0) { if (tab_size == 0) { /* Do not let print_tab_index == first_free_tab; stop when it is 1 less. */ while (print_tab_index < first_free_tab - 1 && column >= tab_list[print_tab_index]) print_tab_index++; next_tab_column = tab_list[print_tab_index]; if (print_tab_index < first_free_tab - 1) print_tab_index++; } else { next_tab_column = column + tab_size - column % tab_size; } if (next_tab_column - column <= pending) { putchar ('\t'); pending -= next_tab_column - column; column = next_tab_column; } else { --print_tab_index; column += pending; while (pending != 0) { putchar (' '); pending--; } } } if (c == EOF) { fp = next_file (fp); if (fp == NULL) break; /* No more files. */ else { SET_BINARY2 (fileno (fp), STDOUT_FILENO); continue; } } if (convert) { if (c == '\b') { if (column > 0) --column; } else { ++column; if (convert_entire_line == 0) convert = 0; } } putchar (c); if (c == '\n') { tab_index = print_tab_index = 0; column = pending = 0; convert = 1; } } } } void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... [FILE]...\n\ "), program_name); fputs (_("\ Convert spaces in each FILE to tabs, writing to standard output.\n\ With no FILE, or when FILE is -, read standard input.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ -a, --all convert all whitespace, instead of just initial whitespace\n\ --first-only convert only leading sequences of whitespace (overrides -a)\n\ -t, --tabs=N have tabs N characters apart instead of 8 (enables -a)\n\ -t, --tabs=LIST use comma separated LIST of tab positions (enables -a)\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } int main (int argc, char **argv) { int tabval = -1; /* Value of tabstop being read, or -1. */ int c; /* Option character. */ /* If nonzero, cancel the effect of any -a (explicit or implicit in -t), so that only leading white space will be considered. */ int convert_first_only = 0; bool obsolete_tablist = false; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); have_read_stdin = 0; exit_status = 0; convert_entire_line = 0; tab_list = NULL; first_free_tab = 0; while ((c = getopt_long (argc, argv, ",0123456789at:", longopts, NULL)) != -1) { switch (c) { case 0: break; case '?': usage (EXIT_FAILURE); case 'a': convert_entire_line = 1; break; case 't': convert_entire_line = 1; parse_tabstops (optarg); break; case CONVERT_FIRST_ONLY_OPTION: convert_first_only = 1; break; case ',': add_tabstop (tabval); tabval = -1; obsolete_tablist = true; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: if (tabval == -1) tabval = 0; tabval = tabval * 10 + c - '0'; obsolete_tablist = true; break; } } if (obsolete_tablist && 200112 <= posix2_version ()) { error (0, 0, _("`-LIST' option is obsolete; use `--first-only -t LIST'")); usage (EXIT_FAILURE); } if (convert_first_only) convert_entire_line = 0; add_tabstop (tabval); validate_tabstops (tab_list, first_free_tab); if (first_free_tab == 0) tab_size = 8; else if (first_free_tab == 1) tab_size = tab_list[0]; else { /* Append a sentinel to the list of tab stop indices. */ add_tabstop (TAB_STOP_SENTINEL); tab_size = 0; } if (optind == argc) file_list = stdin_argv; else file_list = &argv[optind]; unexpand (); if (have_read_stdin && fclose (stdin) == EOF) error (EXIT_FAILURE, errno, "-"); exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); }
/* unlink utility for GNU. Copyright (C) 2001-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Michael Stone */ /* Implementation overview: Simply call the system 'unlink' function */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include <assert.h> #include "system.h" #include "error.h" #include "long-options.h" #include "quote.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "unlink" #define AUTHORS "Michael Stone" /* Name this program was run with. */ char *program_name; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s FILE\n\ or: %s OPTION\n"), program_name, program_name); fputs (_("Call the unlink function to remove the specified FILE.\n\n"), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } int main (int argc, char **argv) { program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); /* Don't recognize --help or --version if POSIXLY_CORRECT is set. */ if (getenv ("POSIXLY_CORRECT") == NULL) parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, AUTHORS, usage); /* The above handles --help and --version. Since there is no other invocation of getopt, handle `--' here. */ if (1 < argc && STREQ (argv[1], "--")) { --argc; ++argv; } if (argc < 2) { error (0, 0, _("too few arguments")); usage (EXIT_FAILURE); } if (2 < argc) { error (0, 0, _("too many arguments")); usage (EXIT_FAILURE); } if (unlink (argv[1]) != 0) error (EXIT_FAILURE, errno, _("cannot unlink %s"), quote (argv[1])); exit (EXIT_SUCCESS); }
/* wc - print the number of bytes, words, and lines in files Copyright (C) 85, 91, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Rubin, phr@ocf.berkeley.edu and David MacKenzie, djm@gnu.ai.mit.edu. */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> /* Get mbstate_t, mbrtowc(), wcwidth(). */ #if HAVE_WCHAR_H # include <wchar.h> #endif /* Get iswprint(), iswspace(). */ #if HAVE_WCTYPE_H # include <wctype.h> #endif #if !defined iswprint && !HAVE_ISWPRINT # define iswprint(wc) 1 #endif #if !defined iswspace && !HAVE_ISWSPACE # define iswspace(wc) \ ((wc) == (unsigned char) (wc) && ISSPACE ((unsigned char) (wc))) #endif /* Include this after wctype.h so that we `#undef' ISPRINT (from Solaris's euc.h, from widec.h, from wctype.h) before redefining and using it. */ #include "system.h" #include "closeout.h" #include "error.h" #include "inttostr.h" #include "safe-read.h" /* Some systems, like BeOS, have multibyte encodings but lack mbstate_t. */ #if HAVE_MBRTOWC && defined mbstate_t # define mbrtowc(pwc, s, n, ps) (mbrtowc) (pwc, s, n, 0) #endif #ifndef HAVE_DECL_WCWIDTH "this configure-time declaration test was not run" #endif #if !HAVE_DECL_WCWIDTH extern int wcwidth (); #endif /* If wcwidth() doesn't exist, assume all printable characters have width 1. */ #if !defined wcwidth && !HAVE_WCWIDTH # define wcwidth(wc) ((wc) == 0 ? 0 : iswprint (wc) ? 1 : -1) #endif /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "wc" #define AUTHORS N_ ("Paul Rubin and David MacKenzie") /* Size of atomic reads. */ #define BUFFER_SIZE (16 * 1024) /* The name this program was run with. */ char *program_name; /* Cumulative number of lines, words, chars and bytes in all files so far. max_line_length is the maximum over all files processed so far. */ static uintmax_t total_lines; static uintmax_t total_words; static uintmax_t total_chars; static uintmax_t total_bytes; static uintmax_t max_line_length; /* Which counts to print. */ static int print_lines, print_words, print_chars, print_bytes; static int print_linelength; /* Nonzero if we have ever read the standard input. */ static int have_read_stdin; /* The error code to return to the system. */ static int exit_status; /* If nonzero, do not line up columns but instead separate numbers by a single space as specified in Single Unix Specification and POSIX. */ static int posixly_correct; static struct option const longopts[] = { {"bytes", no_argument, NULL, 'c'}, {"chars", no_argument, NULL, 'm'}, {"lines", no_argument, NULL, 'l'}, {"words", no_argument, NULL, 'w'}, {"max-line-length", no_argument, NULL, 'L'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... [FILE]...\n\ "), program_name); fputs (_("\ Print byte, word, and newline counts for each FILE, and a total line if\n\ more than one FILE is specified. With no FILE, or when FILE is -,\n\ read standard input.\n\ -c, --bytes print the byte counts\n\ -m, --chars print the character counts\n\ -l, --lines print the newline counts\n\ "), stdout); fputs (_("\ -L, --max-line-length print the length of the longest line\n\ -w, --words print the word counts\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } static void write_counts (uintmax_t lines, uintmax_t words, uintmax_t chars, uintmax_t bytes, uintmax_t linelength, const char *file) { char buf[INT_BUFSIZE_BOUND (uintmax_t)]; char const *space = ""; char const *format_int = (posixly_correct ? "%s" : "%7s"); char const *format_sp_int = (posixly_correct ? "%s%s" : "%s%7s"); if (print_lines) { printf (format_int, umaxtostr (lines, buf)); space = " "; } if (print_words) { printf (format_sp_int, space, umaxtostr (words, buf)); space = " "; } if (print_chars) { printf (format_sp_int, space, umaxtostr (chars, buf)); space = " "; } if (print_bytes) { printf (format_sp_int, space, umaxtostr (bytes, buf)); space = " "; } if (print_linelength) { printf (format_sp_int, space, umaxtostr (linelength, buf)); } if (*file) printf (" %s", file); putchar ('\n'); } static void wc (int fd, const char *file) { char buf[BUFFER_SIZE + 1]; size_t bytes_read; uintmax_t lines, words, chars, bytes, linelength; int count_bytes, count_chars, count_complicated; lines = words = chars = bytes = linelength = 0; /* If in the current locale, chars are equivalent to bytes, we prefer counting bytes, because that's easier. */ #if HAVE_MBRTOWC && (MB_LEN_MAX > 1) if (MB_CUR_MAX > 1) { count_bytes = print_bytes; count_chars = print_chars; } else #endif { count_bytes = print_bytes + print_chars; count_chars = 0; } count_complicated = print_words + print_linelength; /* We need binary input, since `wc' relies on `lseek' and byte counts. */ SET_BINARY (fd); /* When counting only bytes, save some line- and word-counting overhead. If FD is a `regular' Unix file, using lseek is enough to get its `size' in bytes. Otherwise, read blocks of BUFFER_SIZE bytes at a time until EOF. Note that the `size' (number of bytes) that wc reports is smaller than stats.st_size when the file is not positioned at its beginning. That's why the lseek calls below are necessary. For example the command `(dd ibs=99k skip=1 count=0; ./wc -c) < /etc/group' should make wc report `0' bytes. */ if (count_bytes && !count_chars && !print_lines && !count_complicated) { off_t current_pos, end_pos; struct stat stats; if (fstat (fd, &stats) == 0 && S_ISREG (stats.st_mode) && (current_pos = lseek (fd, (off_t) 0, SEEK_CUR)) != -1 && (end_pos = lseek (fd, (off_t) 0, SEEK_END)) != -1) { off_t diff; /* Be careful here. The current position may actually be beyond the end of the file. As in the example above. */ bytes = (diff = end_pos - current_pos) < 0 ? 0 : diff; } else { while ((bytes_read = safe_read (fd, buf, BUFFER_SIZE)) > 0) { if (bytes_read == SAFE_READ_ERROR) { error (0, errno, "%s", file); exit_status = 1; break; } bytes += bytes_read; } } } else if (!count_chars && !count_complicated) { /* Use a separate loop when counting only lines or lines and bytes -- but not chars or words. */ while ((bytes_read = safe_read (fd, buf, BUFFER_SIZE)) > 0) { register char *p = buf; if (bytes_read == SAFE_READ_ERROR) { error (0, errno, "%s", file); exit_status = 1; break; } while ((p = memchr (p, '\n', (buf + bytes_read) - p))) { ++p; ++lines; } bytes += bytes_read; } } #if HAVE_MBRTOWC && (MB_LEN_MAX > 1) # define SUPPORT_OLD_MBRTOWC 1 else if (MB_CUR_MAX > 1) { int in_word = 0; uintmax_t linepos = 0; mbstate_t state; uintmax_t last_error_line = 0; int last_error_errno = 0; # if SUPPORT_OLD_MBRTOWC /* Back-up the state before each multibyte character conversion and move the last incomplete character of the buffer to the front of the buffer. This is needed because we don't know whether the `mbrtowc' function updates the state when it returns -2, - this is the ISO C 99 and glibc-2.2 behaviour - or not - amended ANSI C, glibc-2.1 and Solaris 2.7 behaviour. We don't have an autoconf test for this, yet. */ size_t prev = 0; /* number of bytes carried over from previous round */ # else const size_t prev = 0; # endif memset (&state, 0, sizeof (mbstate_t)); while ((bytes_read = safe_read (fd, buf + prev, BUFFER_SIZE - prev)) > 0) { const char *p; # if SUPPORT_OLD_MBRTOWC mbstate_t backup_state; # endif if (bytes_read == SAFE_READ_ERROR) { error (0, errno, "%s", file); exit_status = 1; break; } bytes += bytes_read; p = buf; bytes_read += prev; do { wchar_t wide_char; size_t n; # if SUPPORT_OLD_MBRTOWC backup_state = state; # endif n = mbrtowc (&wide_char, p, bytes_read, &state); if (n == (size_t) -2) { # if SUPPORT_OLD_MBRTOWC state = backup_state; # endif break; } if (n == (size_t) -1) { /* Signal repeated errors only once per line. */ if (!(lines + 1 == last_error_line && errno == last_error_errno)) { char line_number_buf[INT_BUFSIZE_BOUND (uintmax_t)]; last_error_line = lines + 1; last_error_errno = errno; error (0, errno, "%s:%s", file, umaxtostr (last_error_line, line_number_buf)); } p++; bytes_read--; } else { if (n == 0) { wide_char = 0; n = 1; } p += n; bytes_read -= n; chars++; switch (wide_char) { case '\n': lines++; /* Fall through. */ case '\r': case '\f': if (linepos > linelength) linelength = linepos; linepos = 0; goto mb_word_separator; case '\t': linepos += 8 - (linepos % 8); goto mb_word_separator; case ' ': linepos++; /* Fall through. */ case '\v': mb_word_separator: if (in_word) { in_word = 0; words++; } break; default: if (iswprint (wide_char)) { int width = wcwidth (wide_char); if (width > 0) linepos += width; if (iswspace (wide_char)) goto mb_word_separator; in_word = 1; } break; } } } while (bytes_read > 0); # if SUPPORT_OLD_MBRTOWC if (bytes_read > 0) { if (bytes_read == BUFFER_SIZE) { /* Encountered a very long redundant shift sequence. */ p++; bytes_read--; } memmove (buf, p, bytes_read); } prev = bytes_read; # endif } if (linepos > linelength) linelength = linepos; if (in_word) words++; } #endif else { int in_word = 0; uintmax_t linepos = 0; while ((bytes_read = safe_read (fd, buf, BUFFER_SIZE)) > 0) { const char *p = buf; if (bytes_read == SAFE_READ_ERROR) { error (0, errno, "%s", file); exit_status = 1; break; } bytes += bytes_read; do { switch (*p++) { case '\n': lines++; /* Fall through. */ case '\r': case '\f': if (linepos > linelength) linelength = linepos; linepos = 0; goto word_separator; case '\t': linepos += 8 - (linepos % 8); goto word_separator; case ' ': linepos++; /* Fall through. */ case '\v': word_separator: if (in_word) { in_word = 0; words++; } break; default: if (ISPRINT ((unsigned char) p[-1])) { linepos++; if (ISSPACE ((unsigned char) p[-1])) goto word_separator; in_word = 1; } break; } } while (--bytes_read); } if (linepos > linelength) linelength = linepos; if (in_word) words++; } if (count_chars < print_chars) chars = bytes; write_counts (lines, words, chars, bytes, linelength, file); total_lines += lines; total_words += words; total_chars += chars; total_bytes += bytes; if (linelength > max_line_length) max_line_length = linelength; } static void wc_file (const char *file) { if (STREQ (file, "-")) { have_read_stdin = 1; wc (0, file); } else { int fd = open (file, O_RDONLY); if (fd == -1) { error (0, errno, "%s", file); exit_status = 1; return; } wc (fd, file); if (close (fd)) { error (0, errno, "%s", file); exit_status = 1; } } } int main (int argc, char **argv) { int optc; int nfiles; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); exit_status = 0; posixly_correct = (getenv ("POSIXLY_CORRECT") != NULL); print_lines = print_words = print_chars = print_bytes = print_linelength = 0; total_lines = total_words = total_chars = total_bytes = max_line_length = 0; while ((optc = getopt_long (argc, argv, "clLmw", longopts, NULL)) != -1) switch (optc) { case 0: break; case 'c': print_bytes = 1; break; case 'm': print_chars = 1; break; case 'l': print_lines = 1; break; case 'w': print_words = 1; break; case 'L': print_linelength = 1; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } if (print_lines + print_words + print_chars + print_bytes + print_linelength == 0) print_lines = print_words = print_bytes = 1; nfiles = argc - optind; if (nfiles == 0) { have_read_stdin = 1; wc (0, ""); } else { for (; optind < argc; ++optind) wc_file (argv[optind]); if (nfiles > 1) write_counts (total_lines, total_words, total_chars, total_bytes, max_line_length, _("total")); } if (have_read_stdin && close (STDIN_FILENO) != 0) error (EXIT_FAILURE, errno, "-"); exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); }
/* whoami -- print effective userid Copyright (C) 89,90, 1991-1997, 1999-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Equivalent to `id -un'. */ /* Written by Richard Mlynarik. */ #include <config.h> #include <stdio.h> #include <sys/types.h> #include <pwd.h> #include <getopt.h> #include "system.h" #include "long-options.h" #include "closeout.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "whoami" #define AUTHORS "Richard Mlynarik" /* The name this program was run with. */ char *program_name; static struct option const long_options[] = { {0, 0, 0, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("Usage: %s [OPTION]...\n"), program_name); fputs (_("\ Print the user name associated with the current effective user id.\n\ Same as id -un.\n\ \n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } int main (int argc, char **argv) { register struct passwd *pw; register uid_t uid; int c; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, AUTHORS, usage); while ((c = getopt_long (argc, argv, "", long_options, NULL)) != -1) { switch (c) { case 0: break; default: usage (EXIT_FAILURE); } } if (optind != argc) usage (EXIT_FAILURE); uid = geteuid (); pw = getpwuid (uid); if (pw) { puts (pw->pw_name); exit (EXIT_SUCCESS); } fprintf (stderr, _("%s: cannot find username for UID %u\n"), program_name, (unsigned) uid); exit (EXIT_FAILURE); }
/* tac - concatenate and print files in reverse Copyright (C) 1988-1991, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Jay Lepreau (lepreau@cs.utah.edu). GNU enhancements by David MacKenzie (djm@gnu.ai.mit.edu). */ /* Copy each FILE, or the standard input if none are given or when a FILE name of "-" is encountered, to the standard output with the order of the records reversed. The records are separated by instances of a string, or a newline if none is given. By default, the separator string is attached to the end of the record that it follows in the file. Options: -b, --before The separator is attached to the beginning of the record that it precedes in the file. -r, --regex The separator is a regular expression. -s, --separator=separator Use SEPARATOR as the record separator. To reverse a file byte by byte, use (in bash, ksh, or sh): tac -r -s '.\| ' file */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include "system.h" #include "closeout.h" #include <regex.h> #include "error.h" #include "safe-read.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "tac" #define AUTHORS N_ ("Jay Lepreau and David MacKenzie") #if defined __MSDOS__ || defined _WIN32 /* Define this to non-zero on systems for which the regular mechanism (of unlinking an open file and expecting to be able to write, seek back to the beginning, then reread it) doesn't work. E.g., on Windows and DOS systems. */ # define DONT_UNLINK_WHILE_OPEN 1 #endif #ifndef DEFAULT_TMPDIR # define DEFAULT_TMPDIR "/tmp" #endif /* The number of bytes per atomic read. */ #define INITIAL_READSIZE 8192 /* The number of bytes per atomic write. */ #define WRITESIZE 8192 /* The name this program was run with. */ char *program_name; /* The string that separates the records of the file. */ static char *separator; /* If nonzero, print `separator' along with the record preceding it in the file; otherwise with the record following it. */ static int separator_ends_record; /* 0 if `separator' is to be matched as a regular expression; otherwise, the length of `separator', used as a sentinel to stop the search. */ static size_t sentinel_length; /* The length of a match with `separator'. If `sentinel_length' is 0, `match_length' is computed every time a match succeeds; otherwise, it is simply the length of `separator'. */ static int match_length; /* The input buffer. */ static char *G_buffer; /* The number of bytes to read at once into `buffer'. */ static size_t read_size; /* The size of `buffer'. This is read_size * 2 + sentinel_length + 2. The extra 2 bytes allow `past_end' to have a value beyond the end of `G_buffer' and `match_start' to run off the front of `G_buffer'. */ static unsigned G_buffer_size; /* The compiled regular expression representing `separator'. */ static struct re_pattern_buffer compiled_separator; static struct option const longopts[] = { {"before", no_argument, NULL, 'b'}, {"regex", no_argument, NULL, 'r'}, {"separator", required_argument, NULL, 's'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... [FILE]...\n\ "), program_name); fputs (_("\ Write each FILE to standard output, last line first.\n\ With no FILE, or when FILE is -, read standard input.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ -b, --before attach the separator before instead of after\n\ -r, --regex interpret the separator as a regular expression\n\ -s, --separator=STRING use STRING as the separator instead of newline\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } /* Print the characters from START to PAST_END - 1. If START is NULL, just flush the buffer. */ static void output (const char *start, const char *past_end) { static char buffer[WRITESIZE]; static size_t bytes_in_buffer = 0; size_t bytes_to_add = past_end - start; size_t bytes_available = WRITESIZE - bytes_in_buffer; if (start == 0) { fwrite (buffer, 1, bytes_in_buffer, stdout); bytes_in_buffer = 0; return; } /* Write out as many full buffers as possible. */ while (bytes_to_add >= bytes_available) { memcpy (buffer + bytes_in_buffer, start, bytes_available); bytes_to_add -= bytes_available; start += bytes_available; fwrite (buffer, 1, WRITESIZE, stdout); bytes_in_buffer = 0; bytes_available = WRITESIZE; } memcpy (buffer + bytes_in_buffer, start, bytes_to_add); bytes_in_buffer += bytes_to_add; } /* Print in reverse the file open on descriptor FD for reading FILE. Return 0 if ok, 1 if an error occurs. */ static int tac_seekable (int input_fd, const char *file) { /* Pointer to the location in `G_buffer' where the search for the next separator will begin. */ char *match_start; /* Pointer to one past the rightmost character in `G_buffer' that has not been printed yet. */ char *past_end; /* Length of the record growing in `G_buffer'. */ size_t saved_record_size; /* Offset in the file of the next read. */ off_t file_pos; /* Nonzero if `output' has not been called yet for any file. Only used when the separator is attached to the preceding record. */ int first_time = 1; char first_char = *separator; /* Speed optimization, non-regexp. */ char *separator1 = separator + 1; /* Speed optimization, non-regexp. */ int match_length1 = match_length - 1; /* Speed optimization, non-regexp. */ struct re_registers regs; /* Find the size of the input file. */ file_pos = lseek (input_fd, (off_t) 0, SEEK_END); if (file_pos < 1) return 0; /* It's an empty file. */ /* Arrange for the first read to lop off enough to leave the rest of the file a multiple of `read_size'. Since `read_size' can change, this may not always hold during the program run, but since it usually will, leave it here for i/o efficiency (page/sector boundaries and all that). Note: the efficiency gain has not been verified. */ saved_record_size = file_pos % read_size; if (saved_record_size == 0) saved_record_size = read_size; file_pos -= saved_record_size; /* `file_pos' now points to the start of the last (probably partial) block in the input file. */ if (lseek (input_fd, file_pos, SEEK_SET) < 0) error (0, errno, "%s: seek failed", file); if (safe_read (input_fd, G_buffer, saved_record_size) != saved_record_size) { error (0, errno, "%s", file); return 1; } match_start = past_end = G_buffer + saved_record_size; /* For non-regexp search, move past impossible positions for a match. */ if (sentinel_length) match_start -= match_length1; for (;;) { /* Search backward from `match_start' - 1 to `G_buffer' for a match with `separator'; for speed, use strncmp if `separator' contains no metacharacters. If the match succeeds, set `match_start' to point to the start of the match and `match_length' to the length of the match. Otherwise, make `match_start' < `G_buffer'. */ if (sentinel_length == 0) { int i = match_start - G_buffer; int ret; ret = re_search (&compiled_separator, G_buffer, i, i - 1, -i, ®s); if (ret == -1) match_start = G_buffer - 1; else if (ret == -2) { error (EXIT_FAILURE, 0, _("error in regular expression search")); } else { match_start = G_buffer + regs.start[0]; match_length = regs.end[0] - regs.start[0]; } } else { /* `match_length' is constant for non-regexp boundaries. */ while (*--match_start != first_char || (match_length1 && strncmp (match_start + 1, separator1, match_length1))) /* Do nothing. */ ; } /* Check whether we backed off the front of `G_buffer' without finding a match for `separator'. */ if (match_start < G_buffer) { if (file_pos == 0) { /* Hit the beginning of the file; print the remaining record. */ output (G_buffer, past_end); return 0; } saved_record_size = past_end - G_buffer; if (saved_record_size > read_size) { /* `G_buffer_size' is about twice `read_size', so since we want to read in another `read_size' bytes before the data already in `G_buffer', we need to increase `G_buffer_size'. */ char *newbuffer; int offset = sentinel_length ? sentinel_length : 1; read_size *= 2; G_buffer_size = read_size * 2 + sentinel_length + 2; newbuffer = xrealloc (G_buffer - offset, G_buffer_size); newbuffer += offset; /* Adjust the pointers for the new buffer location. */ match_start += newbuffer - G_buffer; past_end += newbuffer - G_buffer; G_buffer = newbuffer; } /* Back up to the start of the next bufferfull of the file. */ if (file_pos >= read_size) file_pos -= read_size; else { read_size = file_pos; file_pos = 0; } lseek (input_fd, file_pos, SEEK_SET); /* Shift the pending record data right to make room for the new. The source and destination regions probably overlap. */ memmove (G_buffer + read_size, G_buffer, saved_record_size); past_end = G_buffer + read_size + saved_record_size; /* For non-regexp searches, avoid unneccessary scanning. */ if (sentinel_length) match_start = G_buffer + read_size; else match_start = past_end; if (safe_read (input_fd, G_buffer, read_size) != read_size) { error (0, errno, "%s", file); return 1; } } else { /* Found a match of `separator'. */ if (separator_ends_record) { char *match_end = match_start + match_length; /* If this match of `separator' isn't at the end of the file, print the record. */ if (first_time == 0 || match_end != past_end) output (match_end, past_end); past_end = match_end; first_time = 0; } else { output (match_start, past_end); past_end = match_start; } /* For non-regex matching, we can back up. */ if (sentinel_length > 0) match_start -= match_length - 1; } } } /* Print FILE in reverse. Return 0 if ok, 1 if an error occurs. */ static int tac_file (const char *file) { int errors; FILE *in; in = fopen (file, "r"); if (in == NULL) { error (0, errno, "%s", file); return 1; } SET_BINARY (fileno (in)); errors = tac_seekable (fileno (in), file); if (ferror (in) || fclose (in) == EOF) { error (0, errno, "%s", file); return 1; } return errors; } #if DONT_UNLINK_WHILE_OPEN static const char *file_to_remove; static FILE *fp_to_close; static void unlink_tempfile (void) { fclose (fp_to_close); unlink (file_to_remove); } static void record_tempfile (const char *fn, FILE *fp) { if (!file_to_remove) { file_to_remove = fn; fp_to_close = fp; atexit (unlink_tempfile); } } #endif /* Make a copy of the standard input in `FIXME'. */ static void save_stdin (FILE **g_tmp, char **g_tempfile) { static char *template = NULL; static char *tempdir; char *tempfile; FILE *tmp; int fd; if (template == NULL) { tempdir = getenv ("TMPDIR"); if (tempdir == NULL) tempdir = DEFAULT_TMPDIR; template = xmalloc (strlen (tempdir) + 11); } sprintf (template, "%s/tacXXXXXX", tempdir); tempfile = template; fd = mkstemp (template); if (fd == -1) error (EXIT_FAILURE, errno, "%s", tempfile); tmp = fdopen (fd, "w+"); if (tmp == NULL) error (EXIT_FAILURE, errno, "%s", tempfile); #if DONT_UNLINK_WHILE_OPEN record_tempfile (tempfile, tmp); #else unlink (tempfile); #endif while (1) { size_t bytes_read = safe_read (STDIN_FILENO, G_buffer, read_size); if (bytes_read == 0) break; if (bytes_read == SAFE_READ_ERROR) error (EXIT_FAILURE, errno, _("stdin: read error")); if (fwrite (G_buffer, 1, bytes_read, tmp) != bytes_read) break; } if (ferror (tmp) || fflush (tmp) == EOF) error (EXIT_FAILURE, errno, "%s", tempfile); SET_BINARY (fileno (tmp)); *g_tmp = tmp; *g_tempfile = tempfile; } /* Print the standard input in reverse, saving it to temporary file first if it is a pipe. Return 0 if ok, 1 if an error occurs. */ static int tac_stdin (void) { int errors; struct stat stats; /* No tempfile is needed for "tac < file". Use fstat instead of checking for errno == ESPIPE because lseek doesn't work on some special files but doesn't return an error, either. */ if (fstat (STDIN_FILENO, &stats)) { error (0, errno, _("standard input")); return 1; } if (S_ISREG (stats.st_mode)) { errors = tac_seekable (fileno (stdin), _("standard input")); } else { FILE *tmp_stream; char *tmp_file; save_stdin (&tmp_stream, &tmp_file); errors = tac_seekable (fileno (tmp_stream), tmp_file); } return errors; } #if 0 /* BUF_END points one byte past the end of the buffer to be searched. */ static void * memrchr (const char *buf_start, const char *buf_end, int c) { const char *p = buf_end; while (buf_start <= --p) { if (*(const unsigned char *) p == c) return (void *) p; } return NULL; } /* FIXME: describe */ static int tac_mem (const char *buf, size_t n_bytes, FILE *out) { const char *nl; const char *bol; if (n_bytes == 0) return 0; nl = memrchr (buf, buf + n_bytes, '\n'); bol = (nl == NULL ? buf : nl + 1); /* If the last line of the input file has no terminating newline, treat it as a special case. */ if (bol < buf + n_bytes) { /* Print out the line from bol to end of input. */ fwrite (bol, 1, (buf + n_bytes) - bol, out); /* Add a newline here. Otherwise, the first and second lines of output would appear to have been joined. */ fputc ('\n', out); } while ((nl = memrchr (buf, bol - 1, '\n')) != NULL) { /* Output the line (which includes a trailing newline) from NL+1 to BOL-1. */ fwrite (nl + 1, 1, bol - (nl + 1), out); bol = nl + 1; } /* If there's anything left, output the last line: BUF .. BOL-1. When the first byte of the input is a newline, there is nothing left to do here. */ if (buf < bol) fwrite (buf, 1, bol - buf, out); /* FIXME: this is work in progress.... */ return ferror (out); } /* FIXME: describe */ static int tac_stdin_to_mem (void) { char *buf = NULL; size_t bufsiz = 8 * BUFSIZ; size_t delta = 8 * BUFSIZ; size_t n_bytes = 0; while (1) { size_t bytes_read; if (buf == NULL) buf = (char *) malloc (bufsiz); else buf = (char *) realloc (buf, bufsiz); if (buf == NULL) { /* Free the buffer and fall back on the code that relies on a temporary file. */ free (buf); /* FIXME */ abort (); } bytes_read = safe_read (STDIN_FILENO, buf + n_bytes, bufsiz - n_bytes); if (bytes_read == 0) break; if (bytes_read == SAFE_READ_ERROR) error (EXIT_FAILURE, errno, _("stdin: read error")); n_bytes += bytes_read; bufsiz += delta; } tac_mem (buf, n_bytes, stdout); return 0; } #endif int main (int argc, char **argv) { const char *error_message; /* Return value from re_compile_pattern. */ int optc, errors; int have_read_stdin = 0; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); errors = 0; separator = "\n"; sentinel_length = 1; separator_ends_record = 1; while ((optc = getopt_long (argc, argv, "brs:", longopts, NULL)) != -1) { switch (optc) { case 0: break; case 'b': separator_ends_record = 0; break; case 'r': sentinel_length = 0; break; case 's': separator = optarg; if (*separator == 0) error (EXIT_FAILURE, 0, _("separator cannot be empty")); break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } if (sentinel_length == 0) { compiled_separator.allocated = 100; compiled_separator.buffer = (unsigned char *) xmalloc (compiled_separator.allocated); compiled_separator.fastmap = xmalloc (256); compiled_separator.translate = 0; error_message = re_compile_pattern (separator, strlen (separator), &compiled_separator); if (error_message) error (EXIT_FAILURE, 0, "%s", error_message); } else match_length = sentinel_length = strlen (separator); read_size = INITIAL_READSIZE; /* A precaution that will probably never be needed. */ while (sentinel_length * 2 >= read_size) read_size *= 2; G_buffer_size = read_size * 2 + sentinel_length + 2; G_buffer = xmalloc (G_buffer_size); if (sentinel_length) { strcpy (G_buffer, separator); G_buffer += sentinel_length; } else { ++G_buffer; } if (optind == argc) { have_read_stdin = 1; /* We need binary I/O, since `tac' relies on `lseek' and byte counts. */ SET_BINARY2 (STDIN_FILENO, STDOUT_FILENO); errors = tac_stdin (); } else { for (; optind < argc; ++optind) { if (STREQ (argv[optind], "-")) { have_read_stdin = 1; SET_BINARY2 (STDIN_FILENO, STDOUT_FILENO); errors |= tac_stdin (); } else { /* Binary output will leave the lines' ends (NL or CR/LF) intact when the output is a disk file. Writing a file with CR/LF pairs at end of lines in text mode has no visible effect on console output, since two CRs in a row are just like one CR. */ SET_BINARY (STDOUT_FILENO); errors |= tac_file (argv[optind]); } } } /* Flush the output buffer. */ output ((char *) NULL, (char *) NULL); if (have_read_stdin && close (STDIN_FILENO) < 0) error (EXIT_FAILURE, errno, "-"); exit (errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE); }
/* GNU test program (ksb and mjb) */ /* Modified to run with the GNU shell by bfox. */ /* Copyright (C) 1987-2003 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Define TEST_STANDALONE to get the /bin/test version. Otherwise, you get the shell builtin version. */ /* #define TEST_STANDALONE */ #include <config.h> #include <stdio.h> #include <sys/types.h> /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "test" #define TEST_STANDALONE 1 #if !defined (TEST_STANDALONE) # include "shell.h" # include "posixstat.h" # include "filecntl.h" #else /* TEST_STANDALONE */ # include "system.h" # include "error.h" # include "euidaccess.h" # if !defined (S_IXUGO) # define S_IXUGO 0111 # endif /* S_IXUGO */ # if defined (_POSIX_VERSION) # include <limits.h> # else /* !_POSIX_VERSION */ # include <sys/param.h> # endif /* _POSIX_VERSION */ # define whitespace(c) (((c) == ' ') || ((c) == '\t')) # define digit(c) ((c) >= '0' && (c) <= '9') # define digit_value(c) ((c) - '0') char *program_name; #endif /* TEST_STANDALONE */ #if !defined (_POSIX_VERSION) # include <sys/file.h> #endif /* !_POSIX_VERSION */ #include <errno.h> #ifndef errno extern int errno; #endif #undef STREQ #define STREQ(a, b) ((a)[0] == (b)[0] && strcmp (a, b) == 0) #if !defined (member) # define member(c, s) ((c) ? (strchr ((s), (c)) ? 1 : 0) : 0) #endif /* !member */ extern gid_t getegid (); extern uid_t geteuid (); #if !defined (R_OK) # define R_OK 4 # define W_OK 2 # define X_OK 1 # define F_OK 0 #endif /* R_OK */ /* This name is used solely when printing --version information. */ #define PROGRAM_NAME "test" /* The following few defines control the truth and false output of each stage. TRUE and FALSE are what we use to compute the final output value. SHELL_BOOLEAN is the form which returns truth or falseness in shell terms. TRUTH_OR is how to do logical or with TRUE and FALSE. TRUTH_AND is how to do logical and with TRUE and FALSE.. Default is TRUE = 1, FALSE = 0, TRUTH_OR = a | b, TRUTH_AND = a & b, SHELL_BOOLEAN = (!value). */ #define TRUE 1 #define FALSE 0 #define SHELL_BOOLEAN(value) (!(value)) #define TRUTH_OR(a, b) ((a) | (b)) #define TRUTH_AND(a, b) ((a) & (b)) #if defined (TEST_STANDALONE) # define test_exit(val) exit (val) #else static jmp_buf test_exit_buf; static int test_error_return = 0; # define test_exit(val) test_error_return = val, longjmp (test_exit_buf, 1) #endif /* !TEST_STANDALONE */ static int pos; /* The offset of the current argument in ARGV. */ static int argc; /* The number of arguments present in ARGV. */ static char **argv; /* The argument list. */ static int unop (int op); static int binop (char *s); static int unary_operator (void); static int binary_operator (void); static int two_arguments (void); static int three_arguments (void); static int posixtest (void); static int expr (void); static int term (void); static int and (void); static int or (void); static void test_syntax_error (char const *format, char const *arg) ATTRIBUTE_NORETURN; static void beyond (void) ATTRIBUTE_NORETURN; static void test_syntax_error (char const *format, char const *arg) { fprintf (stderr, "%s: ", argv[0]); fprintf (stderr, format, arg); fflush (stderr); test_exit (SHELL_BOOLEAN (FALSE)); } #if HAVE_SETREUID && HAVE_SETREGID /* Do the same thing access(2) does, but use the effective uid and gid. */ static int eaccess (char const *file, int mode) { static int have_ids; static uid_t uid, euid; static gid_t gid, egid; int result; if (have_ids == 0) { have_ids = 1; uid = getuid (); gid = getgid (); euid = geteuid (); egid = getegid (); } /* Set the real user and group IDs to the effective ones. */ if (uid != euid) setreuid (euid, uid); if (gid != egid) setregid (egid, gid); result = access (file, mode); /* Restore them. */ if (uid != euid) setreuid (uid, euid); if (gid != egid) setregid (gid, egid); return result; } #else # define eaccess(F, M) euidaccess (F, M) #endif /* Increment our position in the argument list. Check that we're not past the end of the argument list. This check is supressed if the argument is FALSE. Made a macro for efficiency. */ #define advance(f) \ do \ { \ ++pos; \ if ((f) && pos >= argc) \ beyond (); \ } \ while (0) #if !defined (advance) static int advance (int f) { ++pos; if (f && pos >= argc) beyond (); } #endif /* advance */ #define unary_advance() \ do \ { \ advance (1); \ ++pos; \ } \ while (0) /* * beyond - call when we're beyond the end of the argument list (an * error condition) */ static void beyond (void) { test_syntax_error (_("argument expected\n"), NULL); } /* Syntax error for when an integer argument was expected, but something else was found. */ static void integer_expected_error (char const *pch) { test_syntax_error (_("integer expression expected %s\n"), pch); } /* Return nonzero if the characters pointed to by STRING constitute a valid number. Stuff the converted number into RESULT if RESULT is not null. */ static int isint (register char *string, intmax_t *result) { int sign; intmax_t value; sign = 1; value = 0; if (result) *result = 0; /* Skip leading whitespace characters. */ while (whitespace (*string)) string++; if (!*string) return (0); /* We allow leading `-' or `+'. */ if (*string == '-' || *string == '+') { if (!digit (string[1])) return (0); if (*string == '-') sign = -1; string++; } while (digit (*string)) { if (result) value = (value * 10) + digit_value (*string); string++; } /* Skip trailing whitespace, if any. */ while (whitespace (*string)) string++; /* Error if not at end of string. */ if (*string) return (0); if (result) { value *= sign; *result = value; } return (1); } /* Find the modification time of FILE, and stuff it into *AGE. Return 0 if successful, -1 if not. */ static int age_of (char *filename, time_t *age) { struct stat finfo; int r = stat (filename, &finfo); if (r == 0) *age = finfo.st_mtime; return r; } /* * term - parse a term and return 1 or 0 depending on whether the term * evaluates to true or false, respectively. * * term ::= * '-'('h'|'d'|'f'|'r'|'s'|'w'|'c'|'b'|'p'|'u'|'g'|'k') filename * '-'('L'|'x') filename * '-t' [ int ] * '-'('z'|'n') string * string * string ('!='|'=') string * <int> '-'(eq|ne|le|lt|ge|gt) <int> * file '-'(nt|ot|ef) file * '(' <expr> ')' * int ::= * '-l' string * positive and negative integers */ static int term (void) { int value; if (pos >= argc) beyond (); /* Deal with leading "not"'s. */ if ('!' == argv[pos][0] && '\000' == argv[pos][1]) { value = FALSE; while (pos < argc && '!' == argv[pos][0] && '\000' == argv[pos][1]) { advance (1); value ^= (TRUE); } return (value ^ (term ())); } /* A paren-bracketed argument. */ if (argv[pos][0] == '(' && !argv[pos][1]) { advance (1); value = expr (); if (!argv[pos]) test_syntax_error (_("')' expected\n"), NULL); else if (argv[pos][0] != ')' || argv[pos][1]) test_syntax_error (_("')' expected, found %s\n"), argv[pos]); advance (0); return (TRUE == (value)); } /* are there enough arguments left that this could be dyadic? */ if (((pos + 3 <= argc) && binop (argv[pos + 1])) || ((pos + 4 <= argc && STREQ (argv[pos], "-l") && binop (argv[pos + 2])))) value = binary_operator (); /* Might be a switch type argument */ else if ('-' == argv[pos][0] && argv[pos][1] && 0 == argv[pos][2]) { if (unop (argv[pos][1])) value = unary_operator (); else test_syntax_error (_("%s: unary operator expected\n"), argv[pos]); } else { value = (argv[pos][0] != '\0'); advance (0); } return (value); } static int binary_operator (void) { register int op; struct stat stat_buf, stat_spare; intmax_t l, r; int value; /* Are the left and right integer expressions of the form '-l string'? */ int l_is_l, r_is_l; if (strcmp (argv[pos], "-l") == 0) { l_is_l = 1; op = pos + 2; /* Make sure that OP is still a valid binary operator. */ if ((op >= argc - 1) || (binop (argv[op]) == 0)) test_syntax_error (_("%s: binary operator expected\n"), argv[op]); advance (0); } else { l_is_l = 0; op = pos + 1; } if ((op < argc - 2) && (strcmp (argv[op + 1], "-l") == 0)) { r_is_l = 1; advance (0); } else r_is_l = 0; if (argv[op][0] == '-') { /* check for eq, nt, and stuff */ switch (argv[op][1]) { default: break; case 'l': if (argv[op][2] == 't' && !argv[op][3]) { /* lt */ if (l_is_l) l = strlen (argv[op - 1]); else { if (!isint (argv[op - 1], &l)) integer_expected_error (_("before -lt")); } if (r_is_l) r = strlen (argv[op + 2]); else { if (!isint (argv[op + 1], &r)) integer_expected_error (_("after -lt")); } pos += 3; return (TRUE == (l < r)); } if (argv[op][2] == 'e' && !argv[op][3]) { /* le */ if (l_is_l) l = strlen (argv[op - 1]); else { if (!isint (argv[op - 1], &l)) integer_expected_error (_("before -le")); } if (r_is_l) r = strlen (argv[op + 2]); else { if (!isint (argv[op + 1], &r)) integer_expected_error (_("after -le")); } pos += 3; return (TRUE == (l <= r)); } break; case 'g': if (argv[op][2] == 't' && !argv[op][3]) { /* gt integer greater than */ if (l_is_l) l = strlen (argv[op - 1]); else { if (!isint (argv[op - 1], &l)) integer_expected_error (_("before -gt")); } if (r_is_l) r = strlen (argv[op + 2]); else { if (!isint (argv[op + 1], &r)) integer_expected_error (_("after -gt")); } pos += 3; return (TRUE == (l > r)); } if (argv[op][2] == 'e' && !argv[op][3]) { /* ge - integer greater than or equal to */ if (l_is_l) l = strlen (argv[op - 1]); else { if (!isint (argv[op - 1], &l)) integer_expected_error (_("before -ge")); } if (r_is_l) r = strlen (argv[op + 2]); else { if (!isint (argv[op + 1], &r)) integer_expected_error (_("after -ge")); } pos += 3; return (TRUE == (l >= r)); } break; case 'n': if (argv[op][2] == 't' && !argv[op][3]) { /* nt - newer than */ time_t lt, rt; int le, re; pos += 3; if (l_is_l || r_is_l) test_syntax_error (_("-nt does not accept -l\n"), NULL); le = age_of (argv[op - 1], <); re = age_of (argv[op + 1], &rt); return le > re || (le == 0 && lt > rt); } if (argv[op][2] == 'e' && !argv[op][3]) { /* ne - integer not equal */ if (l_is_l) l = strlen (argv[op - 1]); else { if (!isint (argv[op - 1], &l)) integer_expected_error (_("before -ne")); } if (r_is_l) r = strlen (argv[op + 2]); else { if (!isint (argv[op + 1], &r)) integer_expected_error (_("after -ne")); } pos += 3; return (TRUE == (l != r)); } break; case 'e': if (argv[op][2] == 'q' && !argv[op][3]) { /* eq - integer equal */ if (l_is_l) l = strlen (argv[op - 1]); else { if (!isint (argv[op - 1], &l)) integer_expected_error (_("before -eq")); } if (r_is_l) r = strlen (argv[op + 2]); else { if (!isint (argv[op + 1], &r)) integer_expected_error (_("after -eq")); } pos += 3; return (TRUE == (l == r)); } if (argv[op][2] == 'f' && !argv[op][3]) { /* ef - hard link? */ pos += 3; if (l_is_l || r_is_l) test_syntax_error (_("-ef does not accept -l\n"), NULL); if (stat (argv[op - 1], &stat_buf) < 0) return (FALSE); if (stat (argv[op + 1], &stat_spare) < 0) return (FALSE); return (TRUE == (stat_buf.st_dev == stat_spare.st_dev && stat_buf.st_ino == stat_spare.st_ino)); } break; case 'o': if ('t' == argv[op][2] && '\000' == argv[op][3]) { /* ot - older than */ time_t lt, rt; int le, re; pos += 3; if (l_is_l || r_is_l) test_syntax_error (_("-ot does not accept -l\n"), NULL); le = age_of (argv[op - 1], <); re = age_of (argv[op + 1], &rt); return le < re || (re == 0 && lt < rt); } break; } test_syntax_error (_("unknown binary operator"), argv[op]); } if (argv[op][0] == '=' && !argv[op][1]) { value = (strcmp (argv[pos], argv[pos + 2]) == 0); pos += 3; return (TRUE == value); } if (strcmp (argv[op], "!=") == 0) { value = (strcmp (argv[pos], argv[pos + 2]) != 0); pos += 3; return (TRUE == value); } /* Not reached. */ abort (); } static int unary_operator (void) { int value; struct stat stat_buf; switch (argv[pos][1]) { default: return (FALSE); /* All of the following unary operators use unary_advance (), which checks to make sure that there is an argument, and then advances pos right past it. This means that pos - 1 is the location of the argument. */ case 'a': /* file exists in the file system? */ case 'e': unary_advance (); value = -1 != stat (argv[pos - 1], &stat_buf); return (TRUE == value); case 'r': /* file is readable? */ unary_advance (); value = -1 != eaccess (argv[pos - 1], R_OK); return (TRUE == value); case 'w': /* File is writable? */ unary_advance (); value = -1 != eaccess (argv[pos - 1], W_OK); return (TRUE == value); case 'x': /* File is executable? */ unary_advance (); value = -1 != eaccess (argv[pos - 1], X_OK); return (TRUE == value); case 'O': /* File is owned by you? */ unary_advance (); if (stat (argv[pos - 1], &stat_buf) < 0) return (FALSE); return (TRUE == (geteuid () == stat_buf.st_uid)); case 'G': /* File is owned by your group? */ unary_advance (); if (stat (argv[pos - 1], &stat_buf) < 0) return (FALSE); return (TRUE == (getegid () == stat_buf.st_gid)); case 'f': /* File is a file? */ unary_advance (); if (stat (argv[pos - 1], &stat_buf) < 0) return (FALSE); /* Under POSIX, -f is true if the given file exists and is a regular file. */ return (TRUE == ((S_ISREG (stat_buf.st_mode)) || (0 == (stat_buf.st_mode & S_IFMT)))); case 'd': /* File is a directory? */ unary_advance (); if (stat (argv[pos - 1], &stat_buf) < 0) return (FALSE); return (TRUE == (S_ISDIR (stat_buf.st_mode))); case 's': /* File has something in it? */ unary_advance (); if (stat (argv[pos - 1], &stat_buf) < 0) return (FALSE); return (TRUE == (stat_buf.st_size > (off_t) 0)); case 'S': /* File is a socket? */ #if !defined (S_ISSOCK) return (FALSE); #else unary_advance (); if (stat (argv[pos - 1], &stat_buf) < 0) return (FALSE); return (TRUE == (S_ISSOCK (stat_buf.st_mode))); #endif /* S_ISSOCK */ case 'c': /* File is character special? */ unary_advance (); if (stat (argv[pos - 1], &stat_buf) < 0) return (FALSE); return (TRUE == (S_ISCHR (stat_buf.st_mode))); case 'b': /* File is block special? */ unary_advance (); if (stat (argv[pos - 1], &stat_buf) < 0) return (FALSE); return (TRUE == (S_ISBLK (stat_buf.st_mode))); case 'p': /* File is a named pipe? */ unary_advance (); #ifndef S_ISFIFO return (FALSE); #else if (stat (argv[pos - 1], &stat_buf) < 0) return (FALSE); return (TRUE == (S_ISFIFO (stat_buf.st_mode))); #endif /* S_ISFIFO */ case 'L': /* Same as -h */ /*FALLTHROUGH*/ case 'h': /* File is a symbolic link? */ unary_advance (); #ifndef S_ISLNK return (FALSE); #else /* An empty filename is not a valid pathname. */ if ((argv[pos - 1][0] == '\0') || (lstat (argv[pos - 1], &stat_buf) < 0)) return (FALSE); return (TRUE == (S_ISLNK (stat_buf.st_mode))); #endif /* S_IFLNK */ case 'u': /* File is setuid? */ unary_advance (); #ifndef S_ISUID return (FALSE); #else if (stat (argv[pos - 1], &stat_buf) < 0) return (FALSE); return (TRUE == (0 != (stat_buf.st_mode & S_ISUID))); #endif case 'g': /* File is setgid? */ unary_advance (); #ifndef S_ISGID return (FALSE); #else if (stat (argv[pos - 1], &stat_buf) < 0) return (FALSE); return (TRUE == (0 != (stat_buf.st_mode & S_ISGID))); #endif case 'k': /* File has sticky bit set? */ unary_advance (); if (stat (argv[pos - 1], &stat_buf) < 0) return (FALSE); #ifndef S_ISVTX /* This is not Posix, and is not defined on some Posix systems. */ return (FALSE); #else return (TRUE == (0 != (stat_buf.st_mode & S_ISVTX))); #endif case 't': /* File (fd) is a terminal? */ { intmax_t fd; advance (0); if (pos < argc) { if (!isint (argv[pos], &fd)) integer_expected_error (_("after -t")); advance (0); } else { fd = 1; } return (TRUE == (fd == (int) fd && isatty (fd))); } case 'n': /* True if arg has some length. */ unary_advance (); return (TRUE == (argv[pos - 1][0] != 0)); case 'z': /* True if arg has no length. */ unary_advance (); return (TRUE == (argv[pos - 1][0] == '\0')); } } /* * and: * term * term '-a' and */ static int and (void) { int value; value = term (); while ((pos < argc) && strcmp (argv[pos], "-a") == 0) { advance (0); value = TRUTH_AND (value, and ()); } return (TRUE == value); } /* * or: * and * and '-o' or */ static int or (void) { int value; value = and (); while ((pos < argc) && strcmp (argv[pos], "-o") == 0) { advance (0); value = TRUTH_OR (value, or ()); } return (TRUE == value); } /* * expr: * or */ static int expr (void) { if (pos >= argc) beyond (); return (FALSE ^ (or ())); /* Same with this. */ } /* Return TRUE if S is one of the test command's binary operators. */ static int binop (char *s) { return ((STREQ (s, "=")) || (STREQ (s, "!=")) || (STREQ (s, "-nt")) || (STREQ (s, "-ot")) || (STREQ (s, "-ef")) || (STREQ (s, "-eq")) || (STREQ (s, "-ne")) || (STREQ (s, "-lt")) || (STREQ (s, "-le")) || (STREQ (s, "-gt")) || (STREQ (s, "-ge"))); } /* Return nonzero if OP is one of the test command's unary operators. */ static int unop (int op) { return (member (op, "abcdefgkLhprsStuwxOGnz")); } static int one_argument (const char *s) { if (STREQ (s, "-t")) return (TRUE == (isatty (1))); return strlen (s) != 0; } static int two_arguments (void) { int value; if (STREQ (argv[pos], "!")) value = ! one_argument (argv[pos+1]); else if (argv[pos][0] == '-' && argv[pos][1] != '\0' && argv[pos][2] == '\0') { if (unop (argv[pos][1])) value = unary_operator (); else test_syntax_error (_("%s: unary operator expected\n"), argv[pos]); } else beyond (); return (value); } static int three_arguments (void) { int value; if (STREQ (argv[pos], "!")) { advance (1); value = !two_arguments (); } else if (binop (argv[pos+1])) { value = binary_operator (); pos = argc; } else if ((STREQ (argv[pos+1], "-a")) || (STREQ (argv[pos+1], "-o")) || (argv[pos][0] == '(')) value = expr (); else test_syntax_error (_("%s: binary operator expected\n"), argv[pos+1]); return (value); } /* This is an implementation of a Posix.2 proposal by David Korn. */ static int posixtest (void) { int value; switch (argc - 1) /* one extra passed in */ { case 0: value = FALSE; pos = argc; break; case 1: value = one_argument (argv[1]); pos = argc; break; case 2: value = two_arguments (); pos = argc; break; case 3: value = three_arguments (); break; case 4: if (STREQ (argv[pos], "!")) { advance (1); value = !three_arguments (); break; } /* FALLTHROUGH */ case 5: default: value = expr (); } return (value); } #if defined (TEST_STANDALONE) # include "long-options.h" # include "closeout.h" void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s EXPRESSION\n\ or: [ EXPRESSION ]\n\ or: %s OPTION\n\ "), program_name, program_name); fputs (_("\ Exit with the status determined by EXPRESSION.\n\ \n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ EXPRESSION is true or false and sets exit status. It is one of:\n\ "), stdout); fputs (_("\ \n\ ( EXPRESSION ) EXPRESSION is true\n\ ! EXPRESSION EXPRESSION is false\n\ EXPRESSION1 -a EXPRESSION2 both EXPRESSION1 and EXPRESSION2 are true\n\ EXPRESSION1 -o EXPRESSION2 either EXPRESSION1 or EXPRESSION2 is true\n\ "), stdout); fputs (_("\ \n\ [-n] STRING the length of STRING is nonzero\n\ -z STRING the length of STRING is zero\n\ STRING1 = STRING2 the strings are equal\n\ STRING1 != STRING2 the strings are not equal\n\ "), stdout); fputs (_("\ \n\ INTEGER1 -eq INTEGER2 INTEGER1 is equal to INTEGER2\n\ INTEGER1 -ge INTEGER2 INTEGER1 is greater than or equal to INTEGER2\n\ INTEGER1 -gt INTEGER2 INTEGER1 is greater than INTEGER2\n\ INTEGER1 -le INTEGER2 INTEGER1 is less than or equal to INTEGER2\n\ INTEGER1 -lt INTEGER2 INTEGER1 is less than INTEGER2\n\ INTEGER1 -ne INTEGER2 INTEGER1 is not equal to INTEGER2\n\ "), stdout); fputs (_("\ \n\ FILE1 -ef FILE2 FILE1 and FILE2 have the same device and inode numbers\n\ FILE1 -nt FILE2 FILE1 is newer (modification date) than FILE2\n\ FILE1 -ot FILE2 FILE1 is older than FILE2\n\ "), stdout); fputs (_("\ \n\ -b FILE FILE exists and is block special\n\ -c FILE FILE exists and is character special\n\ -d FILE FILE exists and is a directory\n\ -e FILE FILE exists\n\ "), stdout); fputs (_("\ -f FILE FILE exists and is a regular file\n\ -g FILE FILE exists and is set-group-ID\n\ -h FILE FILE exists and is a symbolic link (same as -L)\n\ -G FILE FILE exists and is owned by the effective group ID\n\ -k FILE FILE exists and has its sticky bit set\n\ "), stdout); fputs (_("\ -L FILE FILE exists and is a symbolic link (same as -h)\n\ -O FILE FILE exists and is owned by the effective user ID\n\ -p FILE FILE exists and is a named pipe\n\ -r FILE FILE exists and is readable\n\ -s FILE FILE exists and has a size greater than zero\n\ "), stdout); fputs (_("\ -S FILE FILE exists and is a socket\n\ -t [FD] file descriptor FD (stdout by default) is opened on a terminal\n\ -u FILE FILE exists and its set-user-ID bit is set\n\ -w FILE FILE exists and is writable\n\ -x FILE FILE exists and is executable\n\ "), stdout); fputs (_("\ \n\ Beware that parentheses need to be escaped (e.g., by backslashes) for shells.\n\ INTEGER may also be -l STRING, which evaluates to the length of STRING.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } #endif /* TEST_STANDALONE */ #if !defined (TEST_STANDALONE) # define main test_command #endif #define AUTHORS N_ ("FIXME: ksb and mjb") /* * [: * '[' expr ']' * test: * test expr */ int main (int margc, char **margv) { int value; #if !defined (TEST_STANDALONE) int code; code = setjmp (test_exit_buf); if (code) return (test_error_return); #else /* TEST_STANDALONE */ program_name = margv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); #endif /* TEST_STANDALONE */ argv = margv; if (margv[0] && strcmp (margv[0], "[") == 0) { /* Don't recognize --help or --version if POSIXLY_CORRECT is set. */ if (getenv ("POSIXLY_CORRECT") == NULL) parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, AUTHORS, usage); --margc; if (margc < 2) test_exit (SHELL_BOOLEAN (FALSE)); if (margv[margc] && strcmp (margv[margc], "]") != 0) test_syntax_error (_("missing `]'\n"), NULL); } argc = margc; pos = 1; if (pos >= argc) test_exit (SHELL_BOOLEAN (FALSE)); parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, AUTHORS, usage); value = posixtest (); if (pos != argc) test_syntax_error (_("too many arguments\n"), NULL); test_exit (SHELL_BOOLEAN (value)); }
/* Copyright (C) 1995, 1997, 1998 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif #endif #ifdef vms # include <types.h> # include <time.h> #else # include <sys/types.h> # if TIME_WITH_SYS_TIME # include <sys/time.h> # include <time.h> # else # if HAVE_SYS_TIME_H # include <sys/time.h> # else # include <time.h> # endif # endif #endif /* defined (vms) */ time_t get_date PARAMS ((const char *p, const time_t *now));
#ifndef POSIXTM_H_ # define POSIXTM_H_ /* POSIX Date Syntax flags. */ # define PDS_LEADING_YEAR 1 # define PDS_TRAILING_YEAR 2 # define PDS_CENTURY 4 # define PDS_SECONDS 8 # ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif # endif bool posixtime PARAMS ((time_t *p, const char *s, unsigned int syntax_bits)); #endif
/* yes - output a string repeatedly until killed Copyright (C) 1991-1997, 1999-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* David MacKenzie <djm@gnu.ai.mit.edu> */ #include <config.h> #include <stdio.h> #include <sys/types.h> #include <getopt.h> #include "error.h" #include "system.h" #include "long-options.h" #include "closeout.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "yes" #define AUTHORS "David MacKenzie" /* How many iterations between ferror checks. */ #define UNROLL 10000 /* The name this program was run with. */ char *program_name; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [STRING]...\n\ or: %s OPTION\n\ "), program_name, program_name); fputs (_("\ Repeatedly output a line with all specified STRING(s), or `y'.\n\ \n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } int main (int argc, char **argv) { program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); /* Don't recognize --help or --version if POSIXLY_CORRECT is set. */ if (getenv ("POSIXLY_CORRECT") == NULL) parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, AUTHORS, usage); if (argc == 1) { while (1) { int i; for (i = 0; i < UNROLL; i++) puts ("y"); if (ferror (stdout)) break; } } else { while (1) { int i; for (i = 0; i < UNROLL; i++) { int j; for (j = 1; j < argc; j++) { fputs (argv[j], stdout); putchar (j == argc - 1 ? '\n' : ' '); } } if (ferror (stdout)) break; } } error (0, errno, "standard output"); exit (EXIT_FAILURE); }
/* cp.c -- file copying (main routines) Copyright (C) 89, 90, 91, 1995-2003 Free Software Foundation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Written by Torbjorn Granlund, David MacKenzie, and Jim Meyering. */ #ifdef _AIX #pragma alloca #endif #include <config.h> #include <stdio.h> #include <sys/types.h> #include <assert.h> #include <getopt.h> #include "system.h" #include "argmatch.h" #include "backupfile.h" #include "copy.h" #include "cp-hash.h" #include "error.h" #include "dirname.h" #include "path-concat.h" #include "quote.h" #define ASSIGN_BASENAME_STRDUPA(Dest, File_name) \ do \ { \ char *tmp_abns_; \ ASSIGN_STRDUPA (tmp_abns_, (File_name)); \ strip_trailing_slashes (tmp_abns_); \ Dest = base_name (tmp_abns_); \ } \ while (0) /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "cp" #define AUTHORS N_ ("Torbjorn Granlund, David MacKenzie, and Jim Meyering") #ifndef _POSIX_VERSION uid_t geteuid (); #endif /* Used by do_copy, make_path_private, and re_protect to keep a list of leading directories whose protections need to be fixed after copying. */ struct dir_attr { int is_new_dir; int slash_offset; struct dir_attr *next; }; /* For long options that have no equivalent short option, use a non-character as a pseudo short option, starting with CHAR_MAX + 1. */ enum { COPY_CONTENTS_OPTION = CHAR_MAX + 1, NO_PRESERVE_ATTRIBUTES_OPTION, PARENTS_OPTION, PRESERVE_ATTRIBUTES_OPTION, REPLY_OPTION, SPARSE_OPTION, STRIP_TRAILING_SLASHES_OPTION, TARGET_DIRECTORY_OPTION, UNLINK_DEST_BEFORE_OPENING }; /* Initial number of entries in each hash table entry's table of inodes. */ #define INITIAL_HASH_MODULE 100 /* Initial number of entries in the inode hash table. */ #define INITIAL_ENTRY_TAB_SIZE 70 /* The invocation name of this program. */ char *program_name; /* If nonzero, the command "cp x/e_file e_dir" uses "e_dir/x/e_file" as its destination instead of the usual "e_dir/e_file." */ static int flag_path = 0; /* Remove any trailing slashes from each SOURCE argument. */ static int remove_trailing_slashes; static char const *const sparse_type_string[] = { "never", "auto", "always", 0 }; static enum Sparse_type const sparse_type[] = { SPARSE_NEVER, SPARSE_AUTO, SPARSE_ALWAYS }; /* Valid arguments to the `--reply' option. */ static char const* const reply_args[] = { "yes", "no", "query", 0 }; /* The values that correspond to the above strings. */ static int const reply_vals[] = { I_ALWAYS_YES, I_ALWAYS_NO, I_ASK_USER }; /* The error code to return to the system. */ static int exit_status = 0; static struct option const long_opts[] = { {"archive", no_argument, NULL, 'a'}, {"backup", optional_argument, NULL, 'b'}, {"copy-contents", no_argument, NULL, COPY_CONTENTS_OPTION}, {"dereference", no_argument, NULL, 'L'}, {"force", no_argument, NULL, 'f'}, {"interactive", no_argument, NULL, 'i'}, {"link", no_argument, NULL, 'l'}, {"no-dereference", no_argument, NULL, 'P'}, {"no-preserve", required_argument, NULL, NO_PRESERVE_ATTRIBUTES_OPTION}, {"one-file-system", no_argument, NULL, 'x'}, {"parents", no_argument, NULL, PARENTS_OPTION}, {"path", no_argument, NULL, PARENTS_OPTION}, /* Deprecated. */ {"preserve", optional_argument, NULL, PRESERVE_ATTRIBUTES_OPTION}, {"recursive", no_argument, NULL, 'R'}, {"remove-destination", no_argument, NULL, UNLINK_DEST_BEFORE_OPENING}, {"reply", required_argument, NULL, REPLY_OPTION}, {"sparse", required_argument, NULL, SPARSE_OPTION}, {"strip-trailing-slashes", no_argument, NULL, STRIP_TRAILING_SLASHES_OPTION}, {"suffix", required_argument, NULL, 'S'}, {"symbolic-link", no_argument, NULL, 's'}, {"target-directory", required_argument, NULL, TARGET_DIRECTORY_OPTION}, {"update", no_argument, NULL, 'u'}, {"verbose", no_argument, NULL, 'v'}, {"version-control", required_argument, NULL, 'V'}, /* Deprecated. FIXME. */ {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... SOURCE DEST\n\ or: %s [OPTION]... SOURCE... DIRECTORY\n\ or: %s [OPTION]... --target-directory=DIRECTORY SOURCE...\n\ "), program_name, program_name, program_name); fputs (_("\ Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ -a, --archive same as -dpR\n\ --backup[=CONTROL] make a backup of each existing destination file\n\ -b like --backup but does not accept an argument\n\ --copy-contents copy contents of special files when recursive\n\ -d same as --no-dereference --preserve=link\n\ "), stdout); fputs (_("\ --no-dereference never follow symbolic links\n\ -f, --force if an existing destination file cannot be\n\ opened, remove it and try again\n\ -i, --interactive prompt before overwrite\n\ -H follow command-line symbolic links\n\ "), stdout); fputs (_("\ -l, --link link files instead of copying\n\ -L, --dereference always follow symbolic links\n\ -p same as --preserve=mode,ownership,timestamps\n\ --preserve[=ATTR_LIST] preserve the specified attributes (default:\n\ mode,ownership,timestamps), if possible\n\ additional attributes: links, all\n\ "), stdout); fputs (_("\ --no-preserve=ATTR_LIST don't preserve the specified attributes\n\ --parents append source path to DIRECTORY\n\ -P same as `--no-dereference'\n\ "), stdout); fputs (_("\ -R, -r, --recursive copy directories recursively\n\ --remove-destination remove each existing destination file before\n\ attempting to open it (contrast with --force)\n\ "), stdout); fputs (_("\ --reply={yes,no,query} specify how to handle the prompt about an\n\ existing destination file\n\ --sparse=WHEN control creation of sparse files\n\ --strip-trailing-slashes remove any trailing slashes from each SOURCE\n\ argument\n\ "), stdout); fputs (_("\ -s, --symbolic-link make symbolic links instead of copying\n\ -S, --suffix=SUFFIX override the usual backup suffix\n\ --target-directory=DIRECTORY move all SOURCE arguments into DIRECTORY\n\ "), stdout); fputs (_("\ -u, --update copy only when the SOURCE file is newer\n\ than the destination file or when the\n\ destination file is missing\n\ -v, --verbose explain what is being done\n\ -x, --one-file-system stay on this file system\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ By default, sparse SOURCE files are detected by a crude heuristic and the\n\ corresponding DEST file is made sparse as well. That is the behavior\n\ selected by --sparse=auto. Specify --sparse=always to create a sparse DEST\n\ file whenever the SOURCE file contains a long enough sequence of zero bytes.\n\ Use --sparse=never to inhibit creation of sparse files.\n\ \n\ "), stdout); fputs (_("\ The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ The version control method may be selected via the --backup option or through\n\ the VERSION_CONTROL environment variable. Here are the values:\n\ \n\ "), stdout); fputs (_("\ none, off never make backups (even if --backup is given)\n\ numbered, t make numbered backups\n\ existing, nil numbered if numbered backups exist, simple otherwise\n\ simple, never always make simple backups\n\ "), stdout); fputs (_("\ \n\ As a special case, cp makes a backup of SOURCE when the force and backup\n\ options are given and SOURCE and DEST are the same name for an existing,\n\ regular file.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } /* Ensure that the parent directories of CONST_DST_PATH have the correct protections, for the --parents option. This is done after all copying has been completed, to allow permissions that don't include user write/execute. SRC_OFFSET is the index in CONST_DST_PATH of the beginning of the source directory name. ATTR_LIST is a null-terminated linked list of structures that indicates the end of the filename of each intermediate directory in CONST_DST_PATH that may need to have its attributes changed. The command `cp --parents --preserve a/b/c d/e_dir' changes the attributes of the directories d/e_dir/a and d/e_dir/a/b to match the corresponding source directories regardless of whether they existed before the `cp' command was given. Return 0 if the parent of CONST_DST_PATH and any intermediate directories specified by ATTR_LIST have the proper permissions when done, otherwise 1. */ static int re_protect (const char *const_dst_path, int src_offset, struct dir_attr *attr_list, const struct cp_options *x) { struct dir_attr *p; char *dst_path; /* A copy of CONST_DST_PATH we can change. */ char *src_path; /* The source name in `dst_path'. */ uid_t myeuid = geteuid (); dst_path = (char *) alloca (strlen (const_dst_path) + 1); strcpy (dst_path, const_dst_path); src_path = dst_path + src_offset; for (p = attr_list; p; p = p->next) { struct stat src_sb; dst_path[p->slash_offset] = '\0'; if ((*(x->xstat)) (src_path, &src_sb)) { error (0, errno, _("failed to get attributes of %s"), quote (src_path)); return 1; } /* Adjust the times (and if possible, ownership) for the copy. chown turns off set[ug]id bits for non-root, so do the chmod last. */ if (x->preserve_timestamps) { struct utimbuf utb; /* There's currently no interface to set file timestamps with better than 1-second resolution, so discard any fractional part of the source timestamp. */ utb.actime = src_sb.st_atime; utb.modtime = src_sb.st_mtime; if (utime (dst_path, &utb)) { error (0, errno, _("failed to preserve times for %s"), quote (dst_path)); return 1; } } if (x->preserve_ownership) { /* If non-root uses -p, it's ok if we can't preserve ownership. But root probably wants to know, e.g. if NFS disallows it, or if the target system doesn't support file ownership. */ if (chown (dst_path, src_sb.st_uid, src_sb.st_gid) && ((errno != EPERM && errno != EINVAL) || myeuid == 0)) { error (0, errno, _("failed to preserve ownership for %s"), quote (dst_path)); return 1; } } if (x->preserve_mode || p->is_new_dir) { if (chmod (dst_path, src_sb.st_mode & x->umask_kill)) { error (0, errno, _("failed to preserve permissions for %s"), quote (dst_path)); return 1; } } dst_path[p->slash_offset] = '/'; } return 0; } /* Ensure that the parent directory of CONST_DIRPATH exists, for the --parents option. SRC_OFFSET is the index in CONST_DIRPATH (which is a destination path) of the beginning of the source directory name. Create any leading directories that don't already exist, giving them permissions MODE. If VERBOSE_FMT_STRING is nonzero, use it as a printf format string for printing a message after successfully making a directory. The format should take two string arguments: the names of the source and destination directories. Creates a linked list of attributes of intermediate directories, *ATTR_LIST, for re_protect to use after calling copy. Sets *NEW_DST to 1 if this function creates parent of CONST_DIRPATH. Return 0 if parent of CONST_DIRPATH exists as a directory with the proper permissions when done, otherwise 1. */ /* FIXME: find a way to synch this function with the one in lib/makepath.c. */ static int make_path_private (const char *const_dirpath, int src_offset, int mode, const char *verbose_fmt_string, struct dir_attr **attr_list, int *new_dst, int (*xstat)()) { struct stat stats; char *dirpath; /* A copy of CONST_DIRPATH we can change. */ char *src; /* Source name in `dirpath'. */ char *dst_dirname; /* Leading path of `dirpath'. */ size_t dirlen; /* Length of leading path of `dirpath'. */ dirpath = (char *) alloca (strlen (const_dirpath) + 1); strcpy (dirpath, const_dirpath); src = dirpath + src_offset; dirlen = dir_len (dirpath); dst_dirname = (char *) alloca (dirlen + 1); memcpy (dst_dirname, dirpath, dirlen); dst_dirname[dirlen] = '\0'; *attr_list = NULL; if ((*xstat) (dst_dirname, &stats)) { /* Parent of CONST_DIRNAME does not exist. Make all missing intermediate directories. */ char *slash; slash = src; while (*slash == '/') slash++; while ((slash = strchr (slash, '/'))) { /* Add this directory to the list of directories whose modes need fixing later. */ struct dir_attr *new = (struct dir_attr *) xmalloc (sizeof (struct dir_attr)); new->slash_offset = slash - dirpath; new->next = *attr_list; *attr_list = new; *slash = '\0'; if ((*xstat) (dirpath, &stats)) { /* This element of the path does not exist. We must set *new_dst and new->is_new_dir inside this loop because, for example, in the command `cp --parents ../a/../b/c e_dir', make_path_private creates only e_dir/../a if ./b already exists. */ *new_dst = 1; new->is_new_dir = 1; if (mkdir (dirpath, mode)) { error (0, errno, _("cannot make directory %s"), quote (dirpath)); return 1; } else { if (verbose_fmt_string != NULL) printf (verbose_fmt_string, src, dirpath); } } else if (!S_ISDIR (stats.st_mode)) { error (0, 0, _("%s exists but is not a directory"), quote (dirpath)); return 1; } else { new->is_new_dir = 0; *new_dst = 0; } *slash++ = '/'; /* Avoid unnecessary calls to `stat' when given pathnames containing multiple adjacent slashes. */ while (*slash == '/') slash++; } } /* We get here if the parent of `dirpath' already exists. */ else if (!S_ISDIR (stats.st_mode)) { error (0, 0, _("%s exists but is not a directory"), quote (dst_dirname)); return 1; } else { *new_dst = 0; } return 0; } /* Scan the arguments, and copy each by calling copy. Return 0 if successful, 1 if any errors occur. */ static int do_copy (int n_files, char **file, const char *target_directory, struct cp_options *x) { const char *dest; struct stat sb; int new_dst = 0; int ret = 0; int dest_is_dir = 0; if (n_files <= 0) { error (0, 0, _("missing file argument")); usage (EXIT_FAILURE); } if (n_files == 1 && !target_directory) { error (0, 0, _("missing destination file")); usage (EXIT_FAILURE); } if (target_directory) dest = target_directory; else { dest = file[n_files - 1]; --n_files; } /* Initialize these hash tables only if we'll need them. The problems they're used to detect can arise only if there are two or more files to copy. */ if (n_files >= 2) { dest_info_init (x); src_info_init (x); } if (lstat (dest, &sb)) { if (errno != ENOENT) { error (0, errno, _("accessing %s"), quote (dest)); return 1; } new_dst = 1; } else { struct stat sbx; /* If `dest' is not a symlink to a nonexistent file, use the results of stat instead of lstat, so we can copy files into symlinks to directories. */ if (stat (dest, &sbx) == 0) sb = sbx; dest_is_dir = S_ISDIR (sb.st_mode); } if (!dest_is_dir) { if (target_directory) { error (0, 0, _("%s: specified target is not a directory"), quote (dest)); usage (EXIT_FAILURE); } if (n_files > 1) { error (0, 0, _("copying multiple files, but last argument %s is not a directory"), quote (dest)); usage (EXIT_FAILURE); } } if (dest_is_dir) { /* cp file1...filen edir Copy the files `file1' through `filen' to the existing directory `edir'. */ int i; for (i = 0; i < n_files; i++) { char *dst_path; int parent_exists = 1; /* True if dir_name (dst_path) exists. */ struct dir_attr *attr_list; char *arg_in_concat = NULL; char *arg = file[i]; /* Trailing slashes are meaningful (i.e., maybe worth preserving) only in the source file names. */ if (remove_trailing_slashes) strip_trailing_slashes (arg); if (flag_path) { char *arg_no_trailing_slash; /* Use `arg' without trailing slashes in constructing destination file names. Otherwise, we can end up trying to create a directory via `mkdir ("dst/foo/"...', which is not portable. It fails, due to the trailing slash, on at least NetBSD 1.[34] systems. */ ASSIGN_STRDUPA (arg_no_trailing_slash, arg); strip_trailing_slashes (arg_no_trailing_slash); /* Append all of `arg' (minus any trailing slash) to `dest'. */ dst_path = path_concat (dest, arg_no_trailing_slash, &arg_in_concat); if (dst_path == NULL) xalloc_die (); /* For --parents, we have to make sure that the directory dir_name (dst_path) exists. We may have to create a few leading directories. */ parent_exists = !make_path_private (dst_path, arg_in_concat - dst_path, S_IRWXU, (x->verbose ? "%s -> %s\n" : NULL), &attr_list, &new_dst, x->xstat); } else { char *arg_base; /* Append the last component of `arg' to `dest'. */ ASSIGN_BASENAME_STRDUPA (arg_base, arg); /* For `cp -R source/.. dest', don't copy into `dest/..'. */ dst_path = (STREQ (arg_base, "..") ? xstrdup (dest) : path_concat (dest, arg_base, NULL)); } if (!parent_exists) { /* make_path_private failed, so don't even attempt the copy. */ ret = 1; } else { int copy_into_self; ret |= copy (arg, dst_path, new_dst, x, ©_into_self, NULL); if (flag_path) { ret |= re_protect (dst_path, arg_in_concat - dst_path, attr_list, x); } } free (dst_path); } return ret; } else /* if (n_files == 1) */ { char *new_dest; char *source; int unused; struct stat source_stats; if (flag_path) { error (0, 0, _("when preserving paths, the destination must be a directory")); usage (EXIT_FAILURE); } source = file[0]; /* When the force and backup options have been specified and the source and destination are the same name for an existing regular file, convert the user's command, e.g., `cp --force --backup foo foo' to `cp --force foo fooSUFFIX' where SUFFIX is determined by any version control options used. */ if (x->unlink_dest_after_failed_open && x->backup_type != none && STREQ (source, dest) && !new_dst && S_ISREG (sb.st_mode)) { static struct cp_options x_tmp; new_dest = find_backup_file_name (dest, x->backup_type); /* Set x->backup_type to `none' so that the normal backup mechanism is not used when performing the actual copy. backup_type must be set to `none' only *after* the above call to find_backup_file_name -- that function uses backup_type to determine the suffix it applies. */ x_tmp = *x; x_tmp.backup_type = none; x = &x_tmp; if (new_dest == NULL) xalloc_die (); } /* When the destination is specified with a trailing slash and the source exists but is not a directory, convert the user's command `cp source dest/' to `cp source dest/basename(source)'. Doing this ensures that the command `cp non-directory file/' will now fail rather than performing the copy. COPY diagnoses the case of `cp directory non-directory'. */ else if (dest[strlen (dest) - 1] == '/' && lstat (source, &source_stats) == 0 && !S_ISDIR (source_stats.st_mode)) { char *source_base; ASSIGN_BASENAME_STRDUPA (source_base, source); new_dest = (char *) alloca (strlen (dest) + strlen (source_base) + 1); stpcpy (stpcpy (new_dest, dest), source_base); } else { new_dest = (char *) dest; } return copy (source, new_dest, new_dst, x, &unused, NULL); } /* unreachable */ } static void cp_option_init (struct cp_options *x) { x->copy_as_regular = 1; x->dereference = DEREF_UNDEFINED; x->unlink_dest_before_opening = 0; x->unlink_dest_after_failed_open = 0; x->hard_link = 0; x->interactive = I_UNSPECIFIED; x->myeuid = geteuid (); x->move_mode = 0; x->one_file_system = 0; x->preserve_ownership = 0; x->preserve_links = 0; x->preserve_mode = 0; x->preserve_timestamps = 0; x->require_preserve = 0; x->recursive = 0; x->sparse_mode = SPARSE_AUTO; x->symbolic_link = 0; x->set_mode = 0; x->mode = 0; /* Not used. */ x->stdin_tty = 0; /* Find out the current file creation mask, to knock the right bits when using chmod. The creation mask is set to be liberal, so that created directories can be written, even if it would not have been allowed with the mask this process was started with. */ x->umask_kill = ~ umask (0); x->update = 0; x->verbose = 0; x->dest_info = NULL; x->src_info = NULL; } /* Given a string, ARG, containing a comma-separated list of arguments to the --preserve option, set the appropriate fields of X to ON_OFF. */ static void decode_preserve_arg (char const *arg, struct cp_options *x, int on_off) { enum File_attribute { PRESERVE_MODE, PRESERVE_TIMESTAMPS, PRESERVE_OWNERSHIP, PRESERVE_LINK, PRESERVE_ALL }; static enum File_attribute const preserve_vals[] = { PRESERVE_MODE, PRESERVE_TIMESTAMPS, PRESERVE_OWNERSHIP, PRESERVE_LINK, PRESERVE_ALL }; /* Valid arguments to the `--preserve' option. */ static char const* const preserve_args[] = { "mode", "timestamps", "ownership", "links", "all", 0 }; char *arg_writable = xstrdup (arg); char *s = arg_writable; do { /* find next comma */ char *comma = strchr (s, ','); enum File_attribute val; /* If we found a comma, put a NUL in its place and advance. */ if (comma) *comma++ = 0; /* process S. */ val = XARGMATCH ("--preserve", s, preserve_args, preserve_vals); switch (val) { case PRESERVE_MODE: x->preserve_mode = on_off; break; case PRESERVE_TIMESTAMPS: x->preserve_timestamps = on_off; break; case PRESERVE_OWNERSHIP: x->preserve_ownership = on_off; break; case PRESERVE_LINK: x->preserve_links = on_off; break; case PRESERVE_ALL: x->preserve_mode = on_off; x->preserve_timestamps = on_off; x->preserve_ownership = on_off; x->preserve_links = on_off; break; default: abort (); } s = comma; } while (s); free (arg_writable); } int main (int argc, char **argv) { int c; int make_backups = 0; char *backup_suffix_string; char *version_control_string = NULL; struct cp_options x; int copy_contents = 0; char *target_directory = NULL; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); cp_option_init (&x); /* FIXME: consider not calling getenv for SIMPLE_BACKUP_SUFFIX unless we'll actually use backup_suffix_string. */ backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX"); while ((c = getopt_long (argc, argv, "abdfHilLprsuvxPRS:V:", long_opts, NULL)) != -1) { switch (c) { case 0: break; case SPARSE_OPTION: x.sparse_mode = XARGMATCH ("--sparse", optarg, sparse_type_string, sparse_type); break; case 'a': /* Like -dpPR. */ x.dereference = DEREF_NEVER; x.preserve_links = 1; x.preserve_ownership = 1; x.preserve_mode = 1; x.preserve_timestamps = 1; x.require_preserve = 1; x.recursive = 1; break; case 'V': /* FIXME: this is deprecated. Remove it in 2001. */ error (0, 0, _("warning: --version-control (-V) is obsolete; support for\ it\nwill be removed in some future release. Use --backup=%s instead." ), optarg); /* Fall through. */ case 'b': make_backups = 1; if (optarg) version_control_string = optarg; break; case COPY_CONTENTS_OPTION: copy_contents = 1; break; case 'd': x.preserve_links = 1; x.dereference = DEREF_NEVER; break; case 'f': x.unlink_dest_after_failed_open = 1; break; case 'H': x.dereference = DEREF_COMMAND_LINE_ARGUMENTS; break; case 'i': x.interactive = I_ASK_USER; break; case 'l': x.hard_link = 1; break; case 'L': x.dereference = DEREF_ALWAYS; break; case 'P': x.dereference = DEREF_NEVER; break; case NO_PRESERVE_ATTRIBUTES_OPTION: decode_preserve_arg (optarg, &x, 0); break; case PRESERVE_ATTRIBUTES_OPTION: if (optarg == NULL) { /* Fall through to the case for `p' below. */ } else { decode_preserve_arg (optarg, &x, 1); x.require_preserve = 1; break; } case 'p': x.preserve_ownership = 1; x.preserve_mode = 1; x.preserve_timestamps = 1; x.require_preserve = 1; break; case PARENTS_OPTION: flag_path = 1; break; case 'r': case 'R': x.recursive = 1; break; case REPLY_OPTION: x.interactive = XARGMATCH ("--reply", optarg, reply_args, reply_vals); break; case UNLINK_DEST_BEFORE_OPENING: x.unlink_dest_before_opening = 1; break; case STRIP_TRAILING_SLASHES_OPTION: remove_trailing_slashes = 1; break; case 's': #ifdef S_ISLNK x.symbolic_link = 1; #else error (EXIT_FAILURE, 0, _("symbolic links are not supported on this system")); #endif break; case TARGET_DIRECTORY_OPTION: target_directory = optarg; break; case 'u': x.update = 1; break; case 'v': x.verbose = 1; break; case 'x': x.one_file_system = 1; break; case 'S': make_backups = 1; backup_suffix_string = optarg; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } if (x.hard_link && x.symbolic_link) { error (0, 0, _("cannot make both hard and symbolic links")); usage (EXIT_FAILURE); } if (backup_suffix_string) simple_backup_suffix = xstrdup (backup_suffix_string); x.backup_type = (make_backups ? xget_version (_("backup type"), version_control_string) : none); if (x.preserve_mode == 1) x.umask_kill = ~ (mode_t) 0; if (x.dereference == DEREF_UNDEFINED) { if (x.recursive) /* This is compatible with FreeBSD. */ x.dereference = DEREF_NEVER; else x.dereference = DEREF_ALWAYS; } /* The key difference between -d (--no-dereference) and not is the version of `stat' to call. */ if (x.dereference == DEREF_NEVER) x.xstat = lstat; else { /* For DEREF_COMMAND_LINE_ARGUMENTS, x.xstat must be stat for each command line argument, but must later be `lstat' for any symlinks that are found via recursive traversal. */ x.xstat = stat; } if (x.recursive) x.copy_as_regular = copy_contents; /* If --force (-f) was specified and we're in link-creation mode, first remove any existing destination file. */ if (x.unlink_dest_after_failed_open && (x.hard_link || x.symbolic_link)) x.unlink_dest_before_opening = 1; /* Allocate space for remembering copied and created files. */ hash_init (); exit_status |= do_copy (argc - optind, argv + optind, target_directory, &x); forget_all (); exit (exit_status); }
/* copy.c -- core functions for copying files and directories Copyright (C) 89, 90, 91, 1995-2003 Free Software Foundation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Extracted from cp.c and librarified by Jim Meyering. */ #ifdef _AIX #pragma alloca #endif #include <config.h> #include <stdio.h> #include <assert.h> #include <sys/types.h> #if HAVE_HURD_H # include <hurd.h> #endif #include "system.h" #include "error.h" #include "backupfile.h" #include "savedir.h" #include "copy.h" #include "cp-hash.h" #include "hash.h" #include "hash-pjw.h" #include "same.h" #include "dirname.h" #include "full-write.h" #include "path-concat.h" #include "quote.h" #include "same.h" #include "xreadlink.h" #define DO_CHOWN(Chown, File, New_uid, New_gid) \ (Chown (File, New_uid, New_gid) \ /* If non-root uses -p, it's ok if we can't preserve ownership. \ But root probably wants to know, e.g. if NFS disallows it, \ or if the target system doesn't support file ownership. */ \ && ((errno != EPERM && errno != EINVAL) || x->myeuid == 0)) #define SAME_OWNER(A, B) ((A).st_uid == (B).st_uid) #define SAME_GROUP(A, B) ((A).st_gid == (B).st_gid) #define SAME_OWNER_AND_GROUP(A, B) (SAME_OWNER (A, B) && SAME_GROUP (A, B)) #define UNWRITABLE(File_name, File_mode) \ ( /* euidaccess is not meaningful for symlinks */ \ ! S_ISLNK (File_mode) \ && euidaccess (File_name, W_OK) != 0) struct dir_list { struct dir_list *parent; ino_t ino; dev_t dev; }; /* Describe a just-created or just-renamed destination file. */ struct F_triple { char const* name; ino_t st_ino; dev_t st_dev; }; /* Initial size of the above hash table. */ #define DEST_INFO_INITIAL_CAPACITY 61 int euidaccess (); int yesno (); static int copy_internal (const char *src_path, const char *dst_path, int new_dst, dev_t device, struct dir_list *ancestors, const struct cp_options *x, int command_line_arg, int *copy_into_self, int *rename_succeeded); /* Pointers to the file names: they're used in the diagnostic that is issued when we detect the user is trying to copy a directory into itself. */ static char const *top_level_src_path; static char const *top_level_dst_path; /* The invocation name of this program. */ extern char *program_name; /* Encapsulate selection of the file mode to be applied to new non-directories. */ static mode_t get_dest_mode (const struct cp_options *option, mode_t mode) { /* In some applications (e.g., install), use precisely the specified mode. */ if (option->set_mode) return option->mode; /* Honor the umask for `cp', but not for `mv' or `cp -p'. In addition, `cp' without -p must clear the set-user-ID and set-group-ID bits. POSIX requires it do that when creating new files. */ if (!option->move_mode && !option->preserve_mode) mode &= (option->umask_kill & ~(S_ISUID | S_ISGID)); return mode; } /* FIXME: describe */ /* FIXME: rewrite this to use a hash table so we avoid the quadratic performance hit that's probably noticeable only on trees deeper than a few hundred levels. See use of active_dir_map in remove.c */ static int is_ancestor (const struct stat *sb, const struct dir_list *ancestors) { while (ancestors != 0) { if (ancestors->ino == sb->st_ino && ancestors->dev == sb->st_dev) return 1; ancestors = ancestors->parent; } return 0; } /* Read the contents of the directory SRC_PATH_IN, and recursively copy the contents to DST_PATH_IN. NEW_DST is nonzero if DST_PATH_IN is a directory that was created previously in the recursion. SRC_SB and ANCESTORS describe SRC_PATH_IN. Set *COPY_INTO_SELF to nonzero if SRC_PATH_IN is a parent of (or the same as) DST_PATH_IN; otherwise, set it to zero. Return 0 if successful, -1 if an error occurs. */ static int copy_dir (const char *src_path_in, const char *dst_path_in, int new_dst, const struct stat *src_sb, struct dir_list *ancestors, const struct cp_options *x, int *copy_into_self) { char *name_space; char *namep; struct cp_options non_command_line_options = *x; int ret = 0; name_space = savedir (src_path_in); if (name_space == NULL) { /* This diagnostic is a bit vague because savedir can fail in several different ways. */ error (0, errno, _("cannot access %s"), quote (src_path_in)); return -1; } /* For cp's -H option, dereference command line arguments, but do not dereference symlinks that are found via recursive traversal. */ if (x->dereference == DEREF_COMMAND_LINE_ARGUMENTS) non_command_line_options.xstat = lstat; namep = name_space; while (*namep != '\0') { int local_copy_into_self; char *src_path = path_concat (src_path_in, namep, NULL); char *dst_path = path_concat (dst_path_in, namep, NULL); if (dst_path == NULL || src_path == NULL) xalloc_die (); ret |= copy_internal (src_path, dst_path, new_dst, src_sb->st_dev, ancestors, &non_command_line_options, 0, &local_copy_into_self, NULL); *copy_into_self |= local_copy_into_self; free (dst_path); free (src_path); namep += strlen (namep) + 1; } free (name_space); return -ret; } /* Copy a regular file from SRC_PATH to DST_PATH. If the source file contains holes, copies holes and blocks of zeros in the source file as holes in the destination file. (Holes are read as zeroes by the `read' system call.) Use DST_MODE as the 3rd argument in the call to open. X provides many option settings. Return 0 if successful, -1 if an error occurred. *NEW_DST is as in copy_internal. SRC_SB is the result of calling xstat (aka stat in this case) on SRC_PATH. */ static int copy_reg (const char *src_path, const char *dst_path, const struct cp_options *x, mode_t dst_mode, int *new_dst, struct stat const *src_sb) { char *buf; int buf_size; int dest_desc; int source_desc; struct stat sb; struct stat src_open_sb; char *cp; int *ip; int return_val = 0; off_t n_read_total = 0; int last_write_made_hole = 0; int make_holes = (x->sparse_mode == SPARSE_ALWAYS); source_desc = open (src_path, O_RDONLY); if (source_desc < 0) { error (0, errno, _("cannot open %s for reading"), quote (src_path)); return -1; } if (fstat (source_desc, &src_open_sb)) { error (0, errno, _("cannot fstat %s"), quote (src_path)); return_val = -1; goto close_src_desc; } /* Compare the source dev/ino from the open file to the incoming, saved ones obtained via a previous call to stat. */ if (! SAME_INODE (*src_sb, src_open_sb)) { error (0, 0, _("skipping file %s, as it was replaced while being copied"), quote (src_path)); return_val = -1; goto close_src_desc; } /* These semantics are required for cp. The if-block will be taken in move_mode. */ if (*new_dst) { dest_desc = open (dst_path, O_WRONLY | O_CREAT, dst_mode); } else { dest_desc = open (dst_path, O_WRONLY | O_TRUNC, dst_mode); if (dest_desc < 0 && x->unlink_dest_after_failed_open) { if (unlink (dst_path)) { error (0, errno, _("cannot remove %s"), quote (dst_path)); return_val = -1; goto close_src_desc; } /* Tell caller that the destination file was unlinked. */ *new_dst = 1; /* Try the open again, but this time with different flags. */ dest_desc = open (dst_path, O_WRONLY | O_CREAT, dst_mode); } } if (dest_desc < 0) { error (0, errno, _("cannot create regular file %s"), quote (dst_path)); return_val = -1; goto close_src_desc; } /* Determine the optimal buffer size. */ if (fstat (dest_desc, &sb)) { error (0, errno, _("cannot fstat %s"), quote (dst_path)); return_val = -1; goto close_src_and_dst_desc; } buf_size = ST_BLKSIZE (sb); #if HAVE_STRUCT_STAT_ST_BLOCKS if (x->sparse_mode == SPARSE_AUTO && S_ISREG (sb.st_mode)) { /* Use a heuristic to determine whether SRC_PATH contains any sparse blocks. */ if (fstat (source_desc, &sb)) { error (0, errno, _("cannot fstat %s"), quote (src_path)); return_val = -1; goto close_src_and_dst_desc; } /* If the file has fewer blocks than would normally be needed for a file of its size, then at least one of the blocks in the file is a hole. */ if (S_ISREG (sb.st_mode) && sb.st_size / ST_NBLOCKSIZE > ST_NBLOCKS (sb)) make_holes = 1; } #endif /* Make a buffer with space for a sentinel at the end. */ buf = (char *) alloca (buf_size + sizeof (int)); for (;;) { ssize_t n_read = read (source_desc, buf, buf_size); if (n_read < 0) { #ifdef EINTR if (errno == EINTR) continue; #endif error (0, errno, _("reading %s"), quote (src_path)); return_val = -1; goto close_src_and_dst_desc; } if (n_read == 0) break; n_read_total += n_read; ip = 0; if (make_holes) { buf[n_read] = 1; /* Sentinel to stop loop. */ /* Find first nonzero *word*, or the word with the sentinel. */ ip = (int *) buf; while (*ip++ == 0) ; /* Find the first nonzero *byte*, or the sentinel. */ cp = (char *) (ip - 1); while (*cp++ == 0) ; /* If we found the sentinel, the whole input block was zero, and we can make a hole. */ if (cp > buf + n_read) { /* Make a hole. */ if (lseek (dest_desc, (off_t) n_read, SEEK_CUR) < 0L) { error (0, errno, _("cannot lseek %s"), quote (dst_path)); return_val = -1; goto close_src_and_dst_desc; } last_write_made_hole = 1; } else /* Clear to indicate that a normal write is needed. */ ip = 0; } if (ip == 0) { size_t n = n_read; if (full_write (dest_desc, buf, n) != n) { error (0, errno, _("writing %s"), quote (dst_path)); return_val = -1; goto close_src_and_dst_desc; } last_write_made_hole = 0; } } /* If the file ends with a `hole', something needs to be written at the end. Otherwise the kernel would truncate the file at the end of the last write operation. */ if (last_write_made_hole) { #if HAVE_FTRUNCATE /* Write a null character and truncate it again. */ if (full_write (dest_desc, "", 1) != 1 || ftruncate (dest_desc, n_read_total) < 0) #else /* Seek backwards one character and write a null. */ if (lseek (dest_desc, (off_t) -1, SEEK_CUR) < 0L || full_write (dest_desc, "", 1) != 1) #endif { error (0, errno, _("writing %s"), quote (dst_path)); return_val = -1; } } close_src_and_dst_desc: if (close (dest_desc) < 0) { error (0, errno, _("closing %s"), quote (dst_path)); return_val = -1; } close_src_desc: if (close (source_desc) < 0) { error (0, errno, _("closing %s"), quote (src_path)); return_val = -1; } return return_val; } /* Return nonzero if it's ok that the source and destination files are the `same' by some measure. The goal is to avoid making the `copy' operation remove both copies of the file in that case, while still allowing the user to e.g., move or copy a regular file onto a symlink that points to it. Try to minimize the cost of this function in the common case. */ static int same_file_ok (const char *src_path, const struct stat *src_sb, const char *dst_path, const struct stat *dst_sb, const struct cp_options *x, int *return_now) { const struct stat *src_sb_link; const struct stat *dst_sb_link; struct stat tmp_dst_sb; struct stat tmp_src_sb; int same_link; int same = (SAME_INODE (*src_sb, *dst_sb)); *return_now = 0; /* FIXME: this should (at the very least) be moved into the following if-block. More likely, it should be removed, because it inhibits making backups. But removing it will result in a change in behavior that will probably have to be documented -- and tests will have to be updated. */ if (same && x->hard_link) { *return_now = 1; return 1; } if (x->xstat == lstat) { same_link = same; /* If both the source and destination files are symlinks (and we'll know this here IFF preserving symlinks (aka xstat == lstat), then it's ok -- as long as they are distinct. */ if (S_ISLNK (src_sb->st_mode) && S_ISLNK (dst_sb->st_mode)) return ! same_name (src_path, dst_path); src_sb_link = src_sb; dst_sb_link = dst_sb; } else { if (!same) return 1; if (lstat (dst_path, &tmp_dst_sb) || lstat (src_path, &tmp_src_sb)) return 1; src_sb_link = &tmp_src_sb; dst_sb_link = &tmp_dst_sb; same_link = SAME_INODE (*src_sb_link, *dst_sb_link); /* If both are symlinks, then it's ok, but only if the destination will be unlinked before being opened. This is like the test above, but with the addition of the unlink_dest_before_opening conjunct because otherwise, with two symlinks to the same target, we'd end up truncating the source file. */ if (S_ISLNK (src_sb_link->st_mode) && S_ISLNK (dst_sb_link->st_mode) && x->unlink_dest_before_opening) return 1; } /* The backup code ensures there's a copy, so it's usually ok to remove any destination file. One exception is when both source and destination are the same directory entry. In that case, moving the destination file aside (in making the backup) would also rename the source file and result in an error. */ if (x->backup_type != none) { if (!same_link) { /* In copy mode when dereferencing symlinks, if the source is a symlink and the dest is not, then backing up the destination (moving it aside) would make it a dangling symlink, and the subsequent attempt to open it in copy_reg would fail with a misleading diagnostic. Avoid that by returning zero in that case so the caller can make cp (or mv when it has to resort to reading the source file) fail now. */ /* FIXME-note: even with the following kludge, we can still provoke the offending diagnostic. It's just a little harder to do :-) $ rm -f a b c; touch c; ln -s c b; ln -s b a; cp -b a b cp: cannot open `a' for reading: No such file or directory That's misleading, since a subsequent `ls' shows that `a' is still there. One solution would be to open the source file *before* moving aside the destination, but that'd involve a big rewrite. */ if ( ! x->move_mode && x->dereference != DEREF_NEVER && S_ISLNK (src_sb_link->st_mode) && ! S_ISLNK (dst_sb_link->st_mode)) return 0; return 1; } return ! same_name (src_path, dst_path); } #if 0 /* FIXME: use or remove */ /* If we're making a backup, we'll detect the problem case in copy_reg because SRC_PATH will no longer exist. Allowing the test to be deferred lets cp do some useful things. But when creating hardlinks and SRC_PATH is a symlink but DST_PATH is not we must test anyway. */ if (x->hard_link || !S_ISLNK (src_sb_link->st_mode) || S_ISLNK (dst_sb_link->st_mode)) return 1; if (x->dereference != DEREF_NEVER) return 1; #endif /* They may refer to the same file if we're in move mode and the target is a symlink. That is ok, since we remove any existing destination file before opening it -- via `rename' if they're on the same file system, via `unlink (DST_PATH)' otherwise. It's also ok if they're distinct hard links to the same file. */ if ((x->move_mode || x->unlink_dest_before_opening) && (S_ISLNK (dst_sb_link->st_mode) || (same_link && !same_name (src_path, dst_path)))) return 1; /* If neither is a symlink, then it's ok as long as they aren't hard links to the same file. */ if (!S_ISLNK (src_sb_link->st_mode) && !S_ISLNK (dst_sb_link->st_mode)) { if (!SAME_INODE (*src_sb_link, *dst_sb_link)) return 1; /* If they are the same file, it's ok if we're making hard links. */ if (x->hard_link) { *return_now = 1; return 1; } } /* It's ok to remove a destination symlink. But that works only when we unlink before opening the destination and when the source and destination files are on the same partition. */ if (x->unlink_dest_before_opening && S_ISLNK (dst_sb_link->st_mode)) return dst_sb_link->st_dev == src_sb_link->st_dev; if (x->xstat == lstat) { if ( ! S_ISLNK (src_sb_link->st_mode)) tmp_src_sb = *src_sb_link; else if (stat (src_path, &tmp_src_sb)) return 1; if ( ! S_ISLNK (dst_sb_link->st_mode)) tmp_dst_sb = *dst_sb_link; else if (stat (dst_path, &tmp_dst_sb)) return 1; if ( ! SAME_INODE (tmp_src_sb, tmp_dst_sb)) return 1; /* FIXME: shouldn't this be testing whether we're making symlinks? */ if (x->hard_link) { *return_now = 1; return 1; } } return 0; } static void overwrite_prompt (char const *dst_path, struct stat const *dst_sb) { if (euidaccess (dst_path, W_OK) != 0) { fprintf (stderr, _("%s: overwrite %s, overriding mode %04lo? "), program_name, quote (dst_path), (unsigned long) (dst_sb->st_mode & CHMOD_MODE_BITS)); } else { fprintf (stderr, _("%s: overwrite %s? "), program_name, quote (dst_path)); } } /* Hash an F_triple. */ static unsigned int triple_hash (void const *x, unsigned int table_size) { struct F_triple const *p = x; /* Also take the name into account, so that when moving N hard links to the same file (all listed on the command line) all into the same directory, we don't experience any N^2 behavior. */ /* FIXME-maybe: is it worth the overhead of doing this just to avoid N^2 in such an unusual case? N would have to be very large to make the N^2 factor noticable, and one would probably encounter a limit on the length of a command line before it became a problem. */ unsigned int tmp = hash_pjw (p->name, table_size); /* Ignoring the device number here should be fine. */ return (tmp | p->st_ino) % table_size; } /* Hash an F_triple. */ static unsigned int triple_hash_no_name (void const *x, unsigned int table_size) { struct F_triple const *p = x; /* Ignoring the device number here should be fine. */ return p->st_ino % table_size; } /* Compare two F_triple structs. */ static bool triple_compare (void const *x, void const *y) { struct F_triple const *a = x; struct F_triple const *b = y; return (SAME_INODE (*a, *b) && same_name (a->name, b->name)) ? true : false; } /* Free an F_triple. */ static void triple_free (void *x) { struct F_triple *a = x; free ((char *) (a->name)); free (a); } /* Initialize the hash table implementing a set of F_triple entries corresponding to destination files. */ void dest_info_init (struct cp_options *x) { x->dest_info = hash_initialize (DEST_INFO_INITIAL_CAPACITY, NULL, triple_hash, triple_compare, triple_free); } /* Initialize the hash table implementing a set of F_triple entries corresponding to source files listed on the command line. */ void src_info_init (struct cp_options *x) { /* Note that we use triple_hash_no_name here. Contrast with the use of triple_hash above. That is necessary because a source file may be specified in many different ways. We want to warn about this cp a a d/ as well as this: cp a ./a d/ */ x->src_info = hash_initialize (DEST_INFO_INITIAL_CAPACITY, NULL, triple_hash_no_name, triple_compare, triple_free); } /* Return nonzero if there is an entry in hash table, HT, for the file described by FILENAME and STATS. Otherwise, return zero. */ static int seen_file (Hash_table const *ht, char const *filename, struct stat const *stats) { struct F_triple new_ent; if (ht == NULL) return 0; new_ent.name = filename; new_ent.st_ino = stats->st_ino; new_ent.st_dev = stats->st_dev; return !!hash_lookup (ht, &new_ent); } /* Record destination filename, FILENAME, and dev/ino from *STATS, in the hash table, HT. If HT is NULL, return immediately. If STATS is NULL, call lstat on FILENAME to get the device and inode numbers. If that lstat fails, simply return. If memory allocation fails, exit immediately. */ static void record_file (Hash_table *ht, char const *filename, struct stat const *stats) { struct F_triple *ent; if (ht == NULL) return; ent = (struct F_triple *) xmalloc (sizeof *ent); ent->name = xstrdup (filename); if (stats) { ent->st_ino = stats->st_ino; ent->st_dev = stats->st_dev; } else { struct stat sb; if (lstat (filename, &sb)) return; ent->st_ino = sb.st_ino; ent->st_dev = sb.st_dev; } { struct F_triple *ent_from_table = hash_insert (ht, ent); if (ent_from_table == NULL) { /* Insertion failed due to lack of memory. */ xalloc_die (); } if (ent_from_table != ent) { /* There was alread a matching entry in the table, so ENT was not inserted. Free it. */ triple_free (ent); } } } /* Copy the file SRC_PATH to the file DST_PATH. The files may be of any type. NEW_DST should be nonzero if the file DST_PATH cannot exist because its parent directory was just created; NEW_DST should be zero if DST_PATH might already exist. DEVICE is the device number of the parent directory, or 0 if the parent of this file is not known. ANCESTORS points to a linked, null terminated list of devices and inodes of parent directories of SRC_PATH. COMMAND_LINE_ARG is nonzero iff SRC_PATH was specified on the command line. Set *COPY_INTO_SELF to nonzero if SRC_PATH is a parent of (or the same as) DST_PATH; otherwise, set it to zero. Return 0 if successful, 1 if an error occurs. */ static int copy_internal (const char *src_path, const char *dst_path, int new_dst, dev_t device, struct dir_list *ancestors, const struct cp_options *x, int command_line_arg, int *copy_into_self, int *rename_succeeded) { struct stat src_sb; struct stat dst_sb; mode_t src_mode; mode_t src_type; char *earlier_file = NULL; char *dst_backup = NULL; int backup_succeeded = 0; int delayed_fail; int copied_as_regular = 0; int ran_chown = 0; int preserve_metadata; if (x->move_mode && rename_succeeded) *rename_succeeded = 0; *copy_into_self = 0; if ((*(x->xstat)) (src_path, &src_sb)) { error (0, errno, _("cannot stat %s"), quote (src_path)); return 1; } src_type = src_sb.st_mode; src_mode = src_sb.st_mode; if (S_ISDIR (src_type) && !x->recursive) { error (0, 0, _("omitting directory %s"), quote (src_path)); return 1; } /* Detect the case in which the same source file appears more than once on the command line and no backup option has been selected. If so, simply warn and don't copy it the second time. This check is enabled only if x->src_info is non-NULL. */ if (command_line_arg) { if ( ! S_ISDIR (src_sb.st_mode) && x->backup_type == none && seen_file (x->src_info, src_path, &src_sb)) { error (0, 0, _("warning: source file %s specified more than once"), quote (src_path)); return 0; } record_file (x->src_info, src_path, &src_sb); } if (!new_dst) { if ((*(x->xstat)) (dst_path, &dst_sb)) { if (errno != ENOENT) { error (0, errno, _("cannot stat %s"), quote (dst_path)); return 1; } else { new_dst = 1; } } else { int return_now; int ok = same_file_ok (src_path, &src_sb, dst_path, &dst_sb, x, &return_now); if (return_now) return 0; if (! ok) { error (0, 0, _("%s and %s are the same file"), quote_n (0, src_path), quote_n (1, dst_path)); return 1; } if (!S_ISDIR (dst_sb.st_mode)) { if (S_ISDIR (src_type)) { error (0, 0, _("cannot overwrite non-directory %s with directory %s"), quote_n (0, dst_path), quote_n (1, src_path)); return 1; } /* Don't let the user destroy their data, even if they try hard: This mv command must fail (likewise for cp): rm -rf a b c; mkdir a b c; touch a/f b/f; mv a/f b/f c Otherwise, the contents of b/f would be lost. In the case of `cp', b/f would be lost if the user simulated a move using cp and rm. Note that it works fine if you use --backup=numbered. */ if (command_line_arg && x->backup_type != numbered && seen_file (x->dest_info, dst_path, &dst_sb)) { error (0, 0, _("will not overwrite just-created %s with %s"), quote_n (0, dst_path), quote_n (1, src_path)); return 1; } } if (!S_ISDIR (src_type)) { if (S_ISDIR (dst_sb.st_mode)) { error (0, 0, _("cannot overwrite directory %s with non-directory"), quote (dst_path)); return 1; } if (x->update && MTIME_CMP (src_sb, dst_sb) <= 0) { /* We're using --update and the source file is older than the destination file, so there is no need to copy or move. */ /* Pretend the rename succeeded, so the caller (mv) doesn't end up removing the source file. */ if (rename_succeeded) *rename_succeeded = 1; return 0; } } /* When there is an existing destination file, we may end up returning early, and hence not copying/moving the file. This may be due to an interactive `negative' reply to the prompt about the existing file. It may also be due to the use of the --reply=no option. */ if (!S_ISDIR (src_type)) { /* cp and mv treat -i and -f differently. */ if (x->move_mode) { if ((x->interactive == I_ALWAYS_NO && UNWRITABLE (dst_path, dst_sb.st_mode)) || ((x->interactive == I_ASK_USER || (x->interactive == I_UNSPECIFIED && x->stdin_tty && UNWRITABLE (dst_path, dst_sb.st_mode))) && (overwrite_prompt (dst_path, &dst_sb), 1) && ! yesno ())) { /* Pretend the rename succeeded, so the caller (mv) doesn't end up removing the source file. */ if (rename_succeeded) *rename_succeeded = 1; return 0; } } else { if (x->interactive == I_ALWAYS_NO || (x->interactive == I_ASK_USER && (overwrite_prompt (dst_path, &dst_sb), 1) && ! yesno ())) { return 0; } } } if (x->move_mode) { /* In move_mode, DEST may not be an existing directory. */ if (S_ISDIR (dst_sb.st_mode)) { error (0, 0, _("cannot overwrite directory %s"), quote (dst_path)); return 1; } /* Don't allow user to move a directory onto a non-directory. */ if (S_ISDIR (src_sb.st_mode) && !S_ISDIR (dst_sb.st_mode)) { error (0, 0, _("cannot move directory onto non-directory: %s -> %s"), quote_n (0, src_path), quote_n (0, dst_path)); return 1; } } if (x->backup_type != none && !S_ISDIR (dst_sb.st_mode)) { char *tmp_backup = find_backup_file_name (dst_path, x->backup_type); if (tmp_backup == NULL) xalloc_die (); /* Detect (and fail) when creating the backup file would destroy the source file. Before, running the commands cd /tmp; rm -f a a~; : > a; echo A > a~; cp --b=simple a~ a would leave two zero-length files: a and a~. */ /* FIXME: but simply change e.g., the final a~ to `./a~' and the source will still be destroyed. */ if (STREQ (tmp_backup, src_path)) { const char *fmt; fmt = (x->move_mode ? _("backing up %s would destroy source; %s not moved") : _("backing up %s would destroy source; %s not copied")); error (0, 0, fmt, quote_n (0, dst_path), quote_n (1, src_path)); free (tmp_backup); return 1; } dst_backup = (char *) alloca (strlen (tmp_backup) + 1); strcpy (dst_backup, tmp_backup); free (tmp_backup); if (rename (dst_path, dst_backup)) { if (errno != ENOENT) { error (0, errno, _("cannot backup %s"), quote (dst_path)); return 1; } else { dst_backup = NULL; } } else { backup_succeeded = 1; } new_dst = 1; } else if (! S_ISDIR (dst_sb.st_mode) && (x->unlink_dest_before_opening || (x->xstat == lstat && ! S_ISREG (src_sb.st_mode)))) { if (unlink (dst_path) && errno != ENOENT) { error (0, errno, _("cannot remove %s"), quote (dst_path)); return 1; } new_dst = 1; } } } /* If the source is a directory, we don't always create the destination directory. So --verbose should not announce anything until we're sure we'll create a directory. */ if (x->verbose && !S_ISDIR (src_type)) { printf ("%s -> %s", quote_n (0, src_path), quote_n (1, dst_path)); if (backup_succeeded) printf (_(" (backup: %s)"), quote (dst_backup)); putchar ('\n'); } /* Associate the destination path with the source device and inode so that if we encounter a matching dev/ino pair in the source tree we can arrange to create a hard link between the corresponding names in the destination tree. Sometimes, when preserving links, we have to record dev/ino even though st_nlink == 1: - when using -H and processing a command line argument; that command line argument could be a symlink pointing to another command line argument. With `cp -H --preserve=link', we hard-link those two destination files. - likewise for -L except that it applies to all files, not just command line arguments. Also record directory dev/ino when using --recursive. We'll use that info to detect this problem: cp -R dir dir. FIXME-maybe: ideally, directory info would be recorded in a separate hash table, since such entries are useful only while a single command line hierarchy is being copied -- so that separate table could be cleared between command line args. Using the same hash table to preserve hard links means that it may not be cleared. */ if ((x->preserve_links && (1 < src_sb.st_nlink || (command_line_arg && x->dereference == DEREF_COMMAND_LINE_ARGUMENTS) || x->dereference == DEREF_ALWAYS)) || (x->recursive && S_ISDIR (src_type))) { earlier_file = remember_copied (dst_path, src_sb.st_ino, src_sb.st_dev); } /* Did we copy this inode somewhere else (in this command line argument) and therefore this is a second hard link to the inode? */ if (earlier_file) { /* Avoid damaging the destination filesystem by refusing to preserve hard-linked directories (which are found at least in Netapp snapshot directories). */ if (S_ISDIR (src_type)) { /* If src_path and earlier_file refer to the same directory entry, then warn about copying a directory into itself. */ if (same_name (src_path, earlier_file)) { error (0, 0, _("cannot copy a directory, %s, into itself, %s"), quote_n (0, top_level_src_path), quote_n (1, top_level_dst_path)); *copy_into_self = 1; } else { error (0, 0, _("will not create hard link %s to directory %s"), quote_n (0, dst_path), quote_n (1, earlier_file)); } goto un_backup; } { int link_failed; link_failed = link (earlier_file, dst_path); /* If the link failed because of an existing destination, remove that file and then call link again. */ if (link_failed && errno == EEXIST) { if (unlink (dst_path)) { error (0, errno, _("cannot remove %s"), quote (dst_path)); goto un_backup; } link_failed = link (earlier_file, dst_path); } if (link_failed) { error (0, errno, _("cannot create hard link %s to %s"), quote_n (0, dst_path), quote_n (1, earlier_file)); goto un_backup; } return 0; } } if (x->move_mode) { if (rename (src_path, dst_path) == 0) { if (x->verbose && S_ISDIR (src_type)) printf ("%s -> %s\n", quote_n (0, src_path), quote_n (1, dst_path)); if (rename_succeeded) *rename_succeeded = 1; if (command_line_arg) { /* Record destination dev/ino/filename, so that if we are asked to overwrite that file again, we can detect it and fail. */ /* It's fine to use the _source_ stat buffer (src_sb) to get the _destination_ dev/ino, since the rename above can't have changed those, and `mv' always uses lstat. We could limit it further by operating only on non-directories. */ record_file (x->dest_info, dst_path, &src_sb); } return 0; } /* FIXME: someday, consider what to do when moving a directory into itself but when source and destination are on different devices. */ /* This happens when attempting to rename a directory to a subdirectory of itself. */ if (errno == EINVAL /* When src_path is on an NFS file system, some types of clients, e.g., SunOS4.1.4 and IRIX-5.3, set errno to EIO instead. Testing for this here risks misinterpreting a real I/O error as an attempt to move a directory into itself, so FIXME: consider not doing this. */ || errno == EIO /* And with SunOS-4.1.4 client and OpenBSD-2.3 server, we get ENOTEMPTY. */ || errno == ENOTEMPTY) { /* FIXME: this is a little fragile in that it relies on rename(2) failing with a specific errno value. Expect problems on non-POSIX systems. */ error (0, 0, _("cannot move %s to a subdirectory of itself, %s"), quote_n (0, top_level_src_path), quote_n (1, top_level_dst_path)); /* Note that there is no need to call forget_created here, (compare with the other calls in this file) since the destination directory didn't exist before. */ *copy_into_self = 1; /* FIXME-cleanup: Don't return zero here; adjust mv.c accordingly. The only caller that uses this code (mv.c) ends up setting its exit status to nonzero when copy_into_self is nonzero. */ return 0; } /* WARNING: there probably exist systems for which an inter-device rename fails with a value of errno not handled here. If/as those are reported, add them to the condition below. If this happens to you, please do the following and send the output to the bug-reporting address (e.g., in the output of cp --help): touch k; perl -e 'rename "k","/tmp/k" or print "$!(",$!+0,")\n"' where your current directory is on one partion and /tmp is the other. Also, please try to find the E* errno macro name corresponding to the diagnostic and parenthesized integer, and include that in your e-mail. One way to do that is to run a command like this find /usr/include/. -type f \ | xargs grep 'define.*\<E[A-Z]*\>.*\<18\>' /dev/null where you'd replace `18' with the integer in parentheses that was output from the perl one-liner above. If necessary, of course, change `/tmp' to some other directory. */ if (errno != EXDEV) { /* There are many ways this can happen due to a race condition. When something happens between the initial xstat and the subsequent rename, we can get many different types of errors. For example, if the destination is initially a non-directory or non-existent, but it is created as a directory, the rename fails. If two `mv' commands try to rename the same file at about the same time, one will succeed and the other will fail. If the permissions on the directory containing the source or destination file are made too restrictive, the rename will fail. Etc. */ error (0, errno, _("cannot move %s to %s"), quote_n (0, src_path), quote_n (1, dst_path)); forget_created (src_sb.st_ino, src_sb.st_dev); return 1; } /* The rename attempt has failed. Remove any existing destination file so that a cross-device `mv' acts as if it were really using the rename syscall. */ if (unlink (dst_path) && errno != ENOENT) { error (0, errno, _("inter-device move failed: %s to %s; unable to remove target"), quote_n (0, src_path), quote_n (1, dst_path)); forget_created (src_sb.st_ino, src_sb.st_dev); return 1; } new_dst = 1; } delayed_fail = 0; /* In certain modes (cp's --symbolic-link), and for certain file types (symlinks and hard links) it doesn't make sense to preserve metadata, or it's possible to preserve only some of it. In such cases, set this variable to zero. */ preserve_metadata = 1; if (S_ISDIR (src_type)) { struct dir_list *dir; /* If this directory has been copied before during the recursion, there is a symbolic link to an ancestor directory of the symbolic link. It is impossible to continue to copy this, unless we've got an infinite disk. */ if (is_ancestor (&src_sb, ancestors)) { error (0, 0, _("cannot copy cyclic symbolic link %s"), quote (src_path)); goto un_backup; } /* Insert the current directory in the list of parents. */ dir = (struct dir_list *) alloca (sizeof (struct dir_list)); dir->parent = ancestors; dir->ino = src_sb.st_ino; dir->dev = src_sb.st_dev; if (new_dst || !S_ISDIR (dst_sb.st_mode)) { /* Create the new directory writable and searchable, so we can create new entries in it. */ if (mkdir (dst_path, (src_mode & x->umask_kill) | S_IRWXU)) { error (0, errno, _("cannot create directory %s"), quote (dst_path)); goto un_backup; } /* Insert the created directory's inode and device numbers into the search structure, so that we can avoid copying it again. */ if (remember_created (dst_path)) goto un_backup; if (x->verbose) printf ("%s -> %s\n", quote_n (0, src_path), quote_n (1, dst_path)); } /* Are we crossing a file system boundary? */ if (x->one_file_system && device != 0 && device != src_sb.st_dev) return 0; /* Copy the contents of the directory. */ if (copy_dir (src_path, dst_path, new_dst, &src_sb, dir, x, copy_into_self)) { /* Don't just return here -- otherwise, the failure to read a single file in a source directory would cause the containing destination directory not to have owner/perms set properly. */ delayed_fail = 1; } } #ifdef S_ISLNK else if (x->symbolic_link) { preserve_metadata = 0; if (*src_path != '/') { /* Check that DST_PATH denotes a file in the current directory. */ struct stat dot_sb; struct stat dst_parent_sb; char *dst_parent; int in_current_dir; dst_parent = dir_name (dst_path); in_current_dir = (STREQ (".", dst_parent) /* If either stat call fails, it's ok not to report the failure and say dst_path is in the current directory. Other things will fail later. */ || stat (".", &dot_sb) || stat (dst_parent, &dst_parent_sb) || SAME_INODE (dot_sb, dst_parent_sb)); free (dst_parent); if (! in_current_dir) { error (0, 0, _("%s: can make relative symbolic links only in current directory"), quote (dst_path)); goto un_backup; } } if (symlink (src_path, dst_path)) { error (0, errno, _("cannot create symbolic link %s to %s"), quote_n (0, dst_path), quote_n (1, src_path)); goto un_backup; } } #endif else if (x->hard_link) { preserve_metadata = 0; if (link (src_path, dst_path)) { error (0, errno, _("cannot create link %s"), quote (dst_path)); goto un_backup; } } else if (S_ISREG (src_type) || (x->copy_as_regular && !S_ISDIR (src_type) #ifdef S_ISLNK && !S_ISLNK (src_type) #endif )) { copied_as_regular = 1; /* POSIX says the permission bits of the source file must be used as the 3rd argument in the open call, but that's not consistent with historical practice. */ if (copy_reg (src_path, dst_path, x, get_dest_mode (x, src_mode), &new_dst, &src_sb)) goto un_backup; } else #ifdef S_ISFIFO if (S_ISFIFO (src_type)) { if (mkfifo (dst_path, get_dest_mode (x, src_mode))) { error (0, errno, _("cannot create fifo %s"), quote (dst_path)); goto un_backup; } } else #endif if (S_ISBLK (src_type) || S_ISCHR (src_type) #ifdef S_ISSOCK || S_ISSOCK (src_type) #endif ) { if (mknod (dst_path, get_dest_mode (x, src_mode), src_sb.st_rdev)) { error (0, errno, _("cannot create special file %s"), quote (dst_path)); goto un_backup; } } else #ifdef S_ISLNK if (S_ISLNK (src_type)) { char *src_link_val = xreadlink (src_path); if (src_link_val == NULL) { error (0, errno, _("cannot read symbolic link %s"), quote (src_path)); goto un_backup; } if (!symlink (src_link_val, dst_path)) free (src_link_val); else { int saved_errno = errno; int same_link = 0; if (x->update && !new_dst && S_ISLNK (dst_sb.st_mode)) { /* See if the destination is already the desired symlink. */ size_t src_link_len = strlen (src_link_val); char *dest_link_val = (char *) alloca (src_link_len + 1); int dest_link_len = readlink (dst_path, dest_link_val, src_link_len + 1); if ((size_t) dest_link_len == src_link_len && strncmp (dest_link_val, src_link_val, src_link_len) == 0) same_link = 1; } free (src_link_val); if (! same_link) { error (0, saved_errno, _("cannot create symbolic link %s"), quote (dst_path)); goto un_backup; } } /* There's no need to preserve timestamps or permissions. */ preserve_metadata = 0; if (x->preserve_ownership) { /* Preserve the owner and group of the just-`copied' symbolic link, if possible. */ # if HAVE_LCHOWN if (DO_CHOWN (lchown, dst_path, src_sb.st_uid, src_sb.st_gid)) { error (0, errno, _("failed to preserve ownership for %s"), dst_path); goto un_backup; } # else /* Can't preserve ownership of symlinks. FIXME: maybe give a warning or even error for symlinks in directories with the sticky bit set -- there, not preserving owner/group is a potential security problem. */ # endif } } else #endif { error (0, 0, _("%s has unknown file type"), quote (src_path)); goto un_backup; } if (command_line_arg) record_file (x->dest_info, dst_path, NULL); if ( ! preserve_metadata) return 0; /* POSIX says that `cp -p' must restore the following: - permission bits - setuid, setgid bits - owner and group If it fails to restore any of those, we may give a warning but the destination must not be removed. FIXME: implement the above. */ /* Adjust the times (and if possible, ownership) for the copy. chown turns off set[ug]id bits for non-root, so do the chmod last. */ if (x->preserve_timestamps) { struct utimbuf utb; /* There's currently no interface to set file timestamps with better than 1-second resolution, so discard any fractional part of the source timestamp. */ utb.actime = src_sb.st_atime; utb.modtime = src_sb.st_mtime; if (utime (dst_path, &utb)) { error (0, errno, _("preserving times for %s"), quote (dst_path)); if (x->require_preserve) return 1; } } /* Avoid calling chown if we know it's not necessary. */ if (x->preserve_ownership && (new_dst || !SAME_OWNER_AND_GROUP (src_sb, dst_sb))) { ran_chown = 1; if (DO_CHOWN (chown, dst_path, src_sb.st_uid, src_sb.st_gid)) { error (0, errno, _("failed to preserve ownership for %s"), quote (dst_path)); if (x->require_preserve) return 1; } } #if HAVE_STRUCT_STAT_ST_AUTHOR /* Preserve the st_author field. */ { file_t file = file_name_lookup (dst_path, 0, 0); if (file_chauthor (file, src_sb.st_author)) error (0, errno, _("failed to preserve authorship for %s"), quote (dst_path)); mach_port_deallocate (mach_task_self (), file); } #endif /* Permissions of newly-created regular files were set upon `open' in copy_reg. But don't return early if there were any special bits and we had to run chown, because the chown must have reset those bits. */ if ((new_dst && copied_as_regular) && !(ran_chown && (src_mode & ~S_IRWXUGO))) return delayed_fail; if ((x->preserve_mode || new_dst) && (x->copy_as_regular || S_ISREG (src_type) || S_ISDIR (src_type))) { if (chmod (dst_path, get_dest_mode (x, src_mode))) { error (0, errno, _("setting permissions for %s"), quote (dst_path)); if (x->set_mode || x->require_preserve) return 1; } } return delayed_fail; un_backup: /* We have failed to create the destination file. If we've just added a dev/ino entry via the remember_copied call above (i.e., unless we've just failed to create a hard link), remove the entry associating the source dev/ino with the destination file name, so we don't try to `preserve' a link to a file we didn't create. */ if (earlier_file == NULL) forget_created (src_sb.st_ino, src_sb.st_dev); if (dst_backup) { if (rename (dst_backup, dst_path)) error (0, errno, _("cannot un-backup %s"), quote (dst_path)); else { if (x->verbose) printf (_("%s -> %s (unbackup)\n"), quote_n (0, dst_backup), quote_n (1, dst_path)); } } return 1; } static int valid_options (const struct cp_options *co) { assert (co != NULL); assert (VALID_BACKUP_TYPE (co->backup_type)); /* FIXME: for some reason this assertion always fails, at least on Solaris2.5.1. Just disable it for now. */ /* assert (co->xstat == lstat || co->xstat == stat); */ /* Make sure xstat and dereference are consistent. */ /* FIXME */ assert (VALID_SPARSE_MODE (co->sparse_mode)); return 1; } /* Copy the file SRC_PATH to the file DST_PATH. The files may be of any type. NONEXISTENT_DST should be nonzero if the file DST_PATH is known not to exist (e.g., because its parent directory was just created); NONEXISTENT_DST should be zero if DST_PATH might already exist. OPTIONS is ... FIXME-describe Set *COPY_INTO_SELF to nonzero if SRC_PATH is a parent of (or the same as) DST_PATH; otherwise, set it to zero. Return 0 if successful, 1 if an error occurs. */ int copy (const char *src_path, const char *dst_path, int nonexistent_dst, const struct cp_options *options, int *copy_into_self, int *rename_succeeded) { assert (valid_options (options)); /* Record the file names: they're used in case of error, when copying a directory into itself. I don't like to make these tools do *any* extra work in the common case when that work is solely to handle exceptional cases, but in this case, I don't see a way to derive the top level source and destination directory names where they're used. An alternative is to use COPY_INTO_SELF and print the diagnostic from every caller -- but I don't want to do that. */ top_level_src_path = src_path; top_level_dst_path = dst_path; return copy_internal (src_path, dst_path, nonexistent_dst, 0, NULL, options, 1, copy_into_self, rename_succeeded); }
/* cp-hash.c -- file copying (hash search routines) Copyright (C) 89, 90, 91, 1995-2002 Free Software Foundation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Written by Torbjorn Granlund, Sweden (tege@sics.se). Rewritten to use lib/hash.c by Jim Meyering. */ #include <config.h> #include <stdio.h> #include <sys/types.h> #include "system.h" #include "same.h" #include "quote.h" #include "hash.h" #include "error.h" #include "cp-hash.h" /* Use ST_DEV and ST_INO as the key, FILENAME as the value. These are used e.g., in copy.c to associate the destination path with the source device/inode pair so that if we encounter a matching dev/ino pair in the source tree we can arrange to create a hard link between the corresponding names in the destination tree. */ struct Src_to_dest { ino_t st_ino; dev_t st_dev; /* Destination path name (of non-directory or pre-existing directory) corresponding to the dev/ino of a copied file, or the destination path name corresponding to a dev/ino pair for a newly-created directory. */ char const* name; }; /* This table maps source dev/ino to destination file name. We use it to preserve hard links when copying. */ static Hash_table *src_to_dest; /* Initial size of the above hash table. */ #define INITIAL_TABLE_SIZE 103 static unsigned int src_to_dest_hash (void const *x, unsigned int table_size) { struct Src_to_dest const *p = x; /* Ignoring the device number here should be fine. */ /* The cast to uintmax_t prevents negative remainders if st_ino is negative. */ return (uintmax_t) p->st_ino % table_size; } /* Compare two Src_to_dest entries. Return true if their keys are judged `equal'. */ static bool src_to_dest_compare (void const *x, void const *y) { struct Src_to_dest const *a = x; struct Src_to_dest const *b = y; return SAME_INODE (*a, *b) ? true : false; } static void src_to_dest_free (void *x) { struct Src_to_dest *a = x; free ((char *) (a->name)); free (x); } /* Remove the entry matching INO/DEV from the table that maps source ino/dev to destination file name. */ void forget_created (ino_t ino, dev_t dev) { struct Src_to_dest probe; struct Src_to_dest *ent; probe.st_ino = ino; probe.st_dev = dev; probe.name = NULL; ent = hash_delete (src_to_dest, &probe); if (ent) src_to_dest_free (ent); } /* Add PATH to the list of files that we have created. Return 1 if we can't stat PATH, otherwise 0. */ int remember_created (const char *path) { struct stat sb; if (stat (path, &sb) < 0) { error (0, errno, "%s", quote (path)); return 1; } remember_copied (path, sb.st_ino, sb.st_dev); return 0; } /* Add path NAME, copied from inode number INO and device number DEV, to the list of files we have copied. Return NULL if inserted, otherwise non-NULL. */ char * remember_copied (const char *name, ino_t ino, dev_t dev) { struct Src_to_dest *ent; struct Src_to_dest *ent_from_table; ent = (struct Src_to_dest *) xmalloc (sizeof *ent); ent->name = xstrdup (name); ent->st_ino = ino; ent->st_dev = dev; ent_from_table = hash_insert (src_to_dest, ent); if (ent_from_table == NULL) { /* Insertion failed due to lack of memory. */ xalloc_die (); } /* Determine whether there was already an entry in the table with a matching key. If so, free ENT (it wasn't inserted) and return the `name' from the table entry. */ if (ent_from_table != ent) { src_to_dest_free (ent); return (char *) ent_from_table->name; } /* New key; insertion succeeded. */ return NULL; } /* Initialize the hash table. */ void hash_init (void) { src_to_dest = hash_initialize (INITIAL_TABLE_SIZE, NULL, src_to_dest_hash, src_to_dest_compare, src_to_dest_free); if (src_to_dest == NULL) xalloc_die (); } /* Reset the hash structure in the global variable `htab' to contain no entries. */ void forget_all (void) { hash_free (src_to_dest); }
/* hash - hashing table processing. Copyright (C) 1998, 1999, 2001 Free Software Foundation, Inc. Written by Jim Meyering <meyering@ascend.com>, 1998. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* A generic hash table package. */ /* Make sure USE_OBSTACK is defined to 1 if you want the allocator to use obstacks instead of malloc, and recompile `hash.c' with same setting. */ #ifndef HASH_H_ # define HASH_H_ # ifndef PARAMS # if PROTOTYPES || __STDC__ # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif # endif typedef unsigned (*Hash_hasher) PARAMS ((const void *, unsigned)); typedef bool (*Hash_comparator) PARAMS ((const void *, const void *)); typedef void (*Hash_data_freer) PARAMS ((void *)); typedef bool (*Hash_processor) PARAMS ((void *, void *)); struct hash_entry { void *data; struct hash_entry *next; }; struct hash_tuning { /* This structure is mainly used for `hash_initialize', see the block documentation of `hash_reset_tuning' for more complete comments. */ float shrink_threshold; /* ratio of used buckets to trigger a shrink */ float shrink_factor; /* ratio of new smaller size to original size */ float growth_threshold; /* ratio of used buckets to trigger a growth */ float growth_factor; /* ratio of new bigger size to original size */ bool is_n_buckets; /* if CANDIDATE really means table size */ }; typedef struct hash_tuning Hash_tuning; struct hash_table; typedef struct hash_table Hash_table; /* Information and lookup. */ unsigned hash_get_n_buckets PARAMS ((const Hash_table *)); unsigned hash_get_n_buckets_used PARAMS ((const Hash_table *)); unsigned hash_get_n_entries PARAMS ((const Hash_table *)); unsigned hash_get_max_bucket_length PARAMS ((const Hash_table *)); bool hash_table_ok PARAMS ((const Hash_table *)); void hash_print_statistics PARAMS ((const Hash_table *, FILE *)); void *hash_lookup PARAMS ((const Hash_table *, const void *)); /* Walking. */ void *hash_get_first PARAMS ((const Hash_table *)); void *hash_get_next PARAMS ((const Hash_table *, const void *)); unsigned hash_get_entries PARAMS ((const Hash_table *, void **, unsigned)); unsigned hash_do_for_each PARAMS ((const Hash_table *, Hash_processor, void *)); /* Allocation and clean-up. */ unsigned hash_string PARAMS ((const char *, unsigned)); void hash_reset_tuning PARAMS ((Hash_tuning *)); Hash_table *hash_initialize PARAMS ((unsigned, const Hash_tuning *, Hash_hasher, Hash_comparator, Hash_data_freer)); void hash_clear PARAMS ((Hash_table *)); void hash_free PARAMS ((Hash_table *)); /* Insertion and deletion. */ bool hash_rehash PARAMS ((Hash_table *, unsigned)); void *hash_insert PARAMS ((Hash_table *, const void *)); void *hash_delete PARAMS ((Hash_table *, const void *)); #endif
void hash_init (void); void forget_all (void); void forget_created (ino_t ino, dev_t dev); char *remember_copied (const char *node, ino_t ino, dev_t dev); int remember_created (const char *path);
#include "ls.h" int ls_mode = LS_LS;
/* This is for the `ls' program. */ #define LS_LS 1 /* This is for the `dir' program. */ #define LS_MULTI_COL 2 /* This is for the `vdir' program. */ #define LS_LONG_FORMAT 3 extern int ls_mode;
/* install - copy files and set attributes Copyright (C) 89, 90, 91, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by David MacKenzie <djm@gnu.ai.mit.edu> */ #ifdef _AIX #pragma alloca #endif #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include <pwd.h> #include <grp.h> #include "system.h" #include "backupfile.h" #include "error.h" #include "cp-hash.h" #include "copy.h" #include "dirname.h" #include "makepath.h" #include "modechange.h" #include "path-concat.h" #include "quote.h" #include "xstrtol.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "install" #define AUTHORS "David MacKenzie" #if HAVE_SYS_WAIT_H # include <sys/wait.h> #endif struct passwd *getpwnam (); struct group *getgrnam (); #ifndef _POSIX_VERSION uid_t getuid (); gid_t getgid (); #endif #if ! HAVE_ENDGRENT # define endgrent() ((void) 0) #endif #if ! HAVE_ENDPWENT # define endpwent() ((void) 0) #endif /* Initial number of entries in each hash table entry's table of inodes. */ #define INITIAL_HASH_MODULE 100 /* Initial number of entries in the inode hash table. */ #define INITIAL_ENTRY_TAB_SIZE 70 /* Number of bytes of a file to copy at a time. */ #define READ_SIZE (32 * 1024) int isdir (); int stat (); static int change_timestamps (const char *from, const char *to); static int change_attributes (const char *path); static int copy_file (const char *from, const char *to, const struct cp_options *x); static int install_file_to_path (const char *from, const char *to, const struct cp_options *x); static int install_file_in_dir (const char *from, const char *to_dir, const struct cp_options *x); static int install_file_in_file (const char *from, const char *to, const struct cp_options *x); static void get_ids (void); static void strip (const char *path); void usage (int status); /* The name this program was run with, for error messages. */ char *program_name; /* The user name that will own the files, or NULL to make the owner the current user ID. */ static char *owner_name; /* The user ID corresponding to `owner_name'. */ static uid_t owner_id; /* The group name that will own the files, or NULL to make the group the current group ID. */ static char *group_name; /* The group ID corresponding to `group_name'. */ static gid_t group_id; /* The permissions to which the files will be set. The umask has no effect. */ static mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; /* If nonzero, strip executable files after copying them. */ static int strip_files; /* If nonzero, install a directory instead of a regular file. */ static int dir_arg; static struct option const long_options[] = { {"backup", optional_argument, NULL, 'b'}, {"directory", no_argument, NULL, 'd'}, {"group", required_argument, NULL, 'g'}, {"mode", required_argument, NULL, 'm'}, {"owner", required_argument, NULL, 'o'}, {"preserve-timestamps", no_argument, NULL, 'p'}, {"strip", no_argument, NULL, 's'}, {"suffix", required_argument, NULL, 'S'}, {"version-control", required_argument, NULL, 'V'}, /* Deprecated. FIXME. */ {"verbose", no_argument, NULL, 'v'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; static void cp_option_init (struct cp_options *x) { x->copy_as_regular = 1; x->dereference = DEREF_ALWAYS; x->unlink_dest_before_opening = 1; x->unlink_dest_after_failed_open = 0; x->hard_link = 0; x->interactive = I_UNSPECIFIED; x->move_mode = 0; x->myeuid = geteuid (); x->one_file_system = 0; x->preserve_ownership = 0; x->preserve_links = 0; x->preserve_mode = 0; x->preserve_timestamps = 0; x->require_preserve = 0; x->recursive = 0; x->sparse_mode = SPARSE_AUTO; x->symbolic_link = 0; x->backup_type = none; /* Create destination files initially writable so we can run strip on them. Although GNU strip works fine on read-only files, some others would fail. */ x->set_mode = 1; x->mode = S_IRUSR | S_IWUSR; x->stdin_tty = 0; x->umask_kill = 0; x->update = 0; x->verbose = 0; x->xstat = stat; x->dest_info = NULL; x->src_info = NULL; } int main (int argc, char **argv) { int optc; int errors = 0; const char *specified_mode = NULL; int make_backups = 0; char *backup_suffix_string; char *version_control_string = NULL; int mkdir_and_install = 0; struct cp_options x; int n_files; char **file; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); cp_option_init (&x); owner_name = NULL; group_name = NULL; strip_files = 0; dir_arg = 0; umask (0); /* FIXME: consider not calling getenv for SIMPLE_BACKUP_SUFFIX unless we'll actually use backup_suffix_string. */ backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX"); while ((optc = getopt_long (argc, argv, "bcsDdg:m:o:pvV:S:", long_options, NULL)) != -1) { switch (optc) { case 0: break; case 'V': /* FIXME: this is deprecated. Remove it in 2001. */ error (0, 0, _("warning: --version-control (-V) is obsolete; support for\ it\nwill be removed in some future release. Use --backup=%s instead." ), optarg); /* Fall through. */ case 'b': make_backups = 1; if (optarg) version_control_string = optarg; break; case 'c': break; case 's': strip_files = 1; break; case 'd': dir_arg = 1; break; case 'D': mkdir_and_install = 1; break; case 'v': x.verbose = 1; break; case 'g': group_name = optarg; break; case 'm': specified_mode = optarg; break; case 'o': owner_name = optarg; break; case 'p': x.preserve_timestamps = 1; break; case 'S': make_backups = 1; backup_suffix_string = optarg; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } /* Check for invalid combinations of arguments. */ if (dir_arg && strip_files) error (EXIT_FAILURE, 0, _("the strip option may not be used when installing a directory")); if (backup_suffix_string) simple_backup_suffix = xstrdup (backup_suffix_string); x.backup_type = (make_backups ? xget_version (_("backup type"), version_control_string) : none); n_files = argc - optind; file = argv + optind; if (n_files == 0 || (n_files == 1 && !dir_arg)) { error (0, 0, _("too few arguments")); usage (EXIT_FAILURE); } if (specified_mode) { struct mode_change *change = mode_compile (specified_mode, 0); if (change == MODE_INVALID) error (EXIT_FAILURE, 0, _("invalid mode %s"), quote (specified_mode)); else if (change == MODE_MEMORY_EXHAUSTED) xalloc_die (); mode = mode_adjust (0, change); } get_ids (); if (dir_arg) { int i; for (i = 0; i < n_files; i++) { errors |= make_path (file[i], mode, mode, owner_id, group_id, 0, (x.verbose ? _("creating directory %s") : NULL)); } } else { /* FIXME: it's a little gross that this initialization is required by copy.c::copy. */ hash_init (); if (n_files == 2) { if (mkdir_and_install) errors = install_file_to_path (file[0], file[1], &x); else if (!isdir (file[1])) errors = install_file_in_file (file[0], file[1], &x); else errors = install_file_in_dir (file[0], file[1], &x); } else { int i; const char *dest = file[n_files - 1]; if (!isdir (dest)) { error (0, 0, _("installing multiple files, but last argument, %s \ is not a directory"), quote (dest)); usage (EXIT_FAILURE); } dest_info_init (&x); for (i = 0; i < n_files - 1; i++) { errors |= install_file_in_dir (file[i], dest, &x); } } } exit (errors); } /* Copy file FROM onto file TO, creating any missing parent directories of TO. Return 0 if successful, 1 if an error occurs */ static int install_file_to_path (const char *from, const char *to, const struct cp_options *x) { char *dest_dir; int fail = 0; dest_dir = dir_name (to); /* check to make sure this is a path (not install a b ) */ if (!STREQ (dest_dir, ".") && !isdir (dest_dir)) { /* Someone will probably ask for a new option or three to specify owner, group, and permissions for parent directories. Remember that this option is intended mainly to help installers when the distribution doesn't provide proper install rules. */ #define DIR_MODE (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) fail = make_path (dest_dir, DIR_MODE, DIR_MODE, owner_id, group_id, 0, (x->verbose ? _("creating directory %s") : NULL)); } if (fail == 0) fail = install_file_in_file (from, to, x); free (dest_dir); return fail; } /* Copy file FROM onto file TO and give TO the appropriate attributes. Return 0 if successful, 1 if an error occurs. */ static int install_file_in_file (const char *from, const char *to, const struct cp_options *x) { if (copy_file (from, to, x)) return 1; if (strip_files) strip (to); if (change_attributes (to)) return 1; if (x->preserve_timestamps) return change_timestamps (from, to); return 0; } /* Copy file FROM into directory TO_DIR, keeping its same name, and give the copy the appropriate attributes. Return 0 if successful, 1 if not. */ static int install_file_in_dir (const char *from, const char *to_dir, const struct cp_options *x) { const char *from_base; char *to; int ret; from_base = base_name (from); to = path_concat (to_dir, from_base, NULL); ret = install_file_in_file (from, to, x); free (to); return ret; } /* Copy file FROM onto file TO, creating TO if necessary. Return 0 if the copy is successful, 1 if not. */ static int copy_file (const char *from, const char *to, const struct cp_options *x) { int fail; int nonexistent_dst = 0; int copy_into_self; /* Allow installing from non-regular files like /dev/null. Charles Karney reported that some Sun version of install allows that and that sendmail's installation process relies on the behavior. */ if (isdir (from)) { error (0, 0, _("%s is a directory"), quote (from)); return 1; } fail = copy (from, to, nonexistent_dst, x, ©_into_self, NULL); return fail; } /* Set the attributes of file or directory PATH. Return 0 if successful, 1 if not. */ static int change_attributes (const char *path) { int err = 0; /* chown must precede chmod because on some systems, chown clears the set[ug]id bits for non-superusers, resulting in incorrect permissions. On System V, users can give away files with chown and then not be able to chmod them. So don't give files away. We don't normally ignore errors from chown because the idea of the install command is that the file is supposed to end up with precisely the attributes that the user specified (or defaulted). If the file doesn't end up with the group they asked for, they'll want to know. But AFS returns EPERM when you try to change a file's group; thus the kludge. */ if (chown (path, owner_id, group_id) #ifdef AFS && errno != EPERM #endif ) { error (0, errno, "cannot change ownership of %s", quote (path)); err = 1; } if (!err && chmod (path, mode)) { error (0, errno, "cannot change permissions of %s", quote (path)); err = 1; } return err; } /* Set the timestamps of file TO to match those of file FROM. Return 0 if successful, 1 if not. */ static int change_timestamps (const char *from, const char *to) { struct stat stb; struct utimbuf utb; if (stat (from, &stb)) { error (0, errno, _("cannot obtain time stamps for %s"), quote (from)); return 1; } /* There's currently no interface to set file timestamps with better than 1-second resolution, so discard any fractional part of the source timestamp. */ utb.actime = stb.st_atime; utb.modtime = stb.st_mtime; if (utime (to, &utb)) { error (0, errno, _("cannot set time stamps for %s"), quote (to)); return 1; } return 0; } /* Strip the symbol table from the file PATH. We could dig the magic number out of the file first to determine whether to strip it, but the header files and magic numbers vary so much from system to system that making it portable would be very difficult. Not worth the effort. */ static void strip (const char *path) { int status; pid_t pid = fork (); switch (pid) { case -1: error (EXIT_FAILURE, errno, _("fork system call failed")); break; case 0: /* Child. */ execlp ("strip", "strip", path, NULL); error (EXIT_FAILURE, errno, _("cannot run strip")); break; default: /* Parent. */ /* Parent process. */ while (pid != wait (&status)) /* Wait for kid to finish. */ /* Do nothing. */ ; if (status) error (EXIT_FAILURE, 0, _("strip failed")); break; } } /* Initialize the user and group ownership of the files to install. */ static void get_ids (void) { struct passwd *pw; struct group *gr; if (owner_name) { pw = getpwnam (owner_name); if (pw == NULL) { unsigned long int tmp; if (xstrtoul (owner_name, NULL, 0, &tmp, NULL) != LONGINT_OK || UID_T_MAX < tmp) error (EXIT_FAILURE, 0, _("invalid user %s"), quote (owner_name)); owner_id = tmp; } else owner_id = pw->pw_uid; endpwent (); } else owner_id = (uid_t) -1; if (group_name) { gr = getgrnam (group_name); if (gr == NULL) { unsigned long int tmp; if (xstrtoul (group_name, NULL, 0, &tmp, NULL) != LONGINT_OK || GID_T_MAX < tmp) error (EXIT_FAILURE, 0, _("invalid group %s"), quote (group_name)); group_id = tmp; } else group_id = gr->gr_gid; endgrent (); } else group_id = (gid_t) -1; } void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... SOURCE DEST (1st format)\n\ or: %s [OPTION]... SOURCE... DIRECTORY (2nd format)\n\ or: %s -d [OPTION]... DIRECTORY... (3rd format)\n\ "), program_name, program_name, program_name); fputs (_("\ In the first two formats, copy SOURCE to DEST or multiple SOURCE(s) to\n\ the existing DIRECTORY, while setting permission modes and owner/group.\n\ In the third format, create all components of the given DIRECTORY(ies).\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ --backup[=CONTROL] make a backup of each existing destination file\n\ -b like --backup but does not accept an argument\n\ -c (ignored)\n\ -d, --directory treat all arguments as directory names; create all\n\ components of the specified directories\n\ "), stdout); fputs (_("\ -D create all leading components of DEST except the last,\n\ then copy SOURCE to DEST; useful in the 1st format\n\ -g, --group=GROUP set group ownership, instead of process' current group\n\ -m, --mode=MODE set permission mode (as in chmod), instead of rwxr-xr-x\n\ -o, --owner=OWNER set ownership (super-user only)\n\ "), stdout); fputs (_("\ -p, --preserve-timestamps apply access/modification times of SOURCE files\n\ to corresponding destination files\n\ -s, --strip strip symbol tables, only for 1st and 2nd formats\n\ -S, --suffix=SUFFIX override the usual backup suffix\n\ -v, --verbose print the name of each directory as it is created\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ The version control method may be selected via the --backup option or through\n\ the VERSION_CONTROL environment variable. Here are the values:\n\ \n\ "), stdout); fputs (_("\ none, off never make backups (even if --backup is given)\n\ numbered, t make numbered backups\n\ existing, nil numbered if numbered backups exist, simple otherwise\n\ simple, never always make simple backups\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); }
#include <config.h> #include <stdio.h> #include <sys/types.h> #include "system.h" #include "checksum.h" int algorithm = ALG_MD5;
#include <config.h> #include <sys/types.h> #include <stdio.h> /* For long options that have no equivalent short option, use a non-character as a pseudo short option, starting with CHAR_MAX + 1. */ enum { ALG_UNSPECIFIED = 0, ALG_MD5 = CHAR_MAX + 1, ALG_SHA1 }; extern int algorithm;
/* Compute MD5 or SHA1 checksum of files or strings Copyright (C) 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>. */ #include <config.h> #include <getopt.h> #include <stdio.h> #include <sys/types.h> #include "system.h" #include "md5.h" #include "sha.h" #include "checksum.h" #include "getline.h" #include "closeout.h" #include "error.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME (algorithm == ALG_MD5 ? "md5sum" : "shasum") #define AUTHORS N_ ("Ulrich Drepper and Scott Miller") /* Most systems do not distinguish between external and internal text representations. */ /* FIXME: This begs for an autoconf test. */ #if O_BINARY # define OPENOPTS(BINARY) ((BINARY) != 0 ? TEXT1TO1 : TEXTCNVT) # define TEXT1TO1 "rb" # define TEXTCNVT "r" #else # if defined VMS # define OPENOPTS(BINARY) ((BINARY) != 0 ? TEXT1TO1 : TEXTCNVT) # define TEXT1TO1 "rb", "ctx=stm" # define TEXTCNVT "r", "ctx=stm" # else # if UNIX || __UNIX__ || unix || __unix__ || _POSIX_VERSION # define OPENOPTS(BINARY) "r" # else /* The following line is intended to evoke an error. Using #error is not portable enough. */ "Cannot determine system type." # endif # endif #endif #define DIGEST_TYPE_STRING(Alg) ((Alg) == ALG_MD5 ? "MD5" : "SHA1") #define DIGEST_STREAM(Alg) ((Alg) == ALG_MD5 ? md5_stream : sha_stream) #define DIGEST_BITS(Alg) ((Alg) == ALG_MD5 ? 128 : 160) #define DIGEST_HEX_BYTES(Alg) (DIGEST_BITS (Alg) / 4) #define DIGEST_BIN_BYTES(Alg) (DIGEST_BITS (Alg) / 8) #define MAX_DIGEST_BIN_BYTES MAX (DIGEST_BIN_BYTES (ALG_MD5), \ DIGEST_BIN_BYTES (ALG_SHA1)) /* The minimum length of a valid digest line. This length does not include any newline character at the end of a line. */ #define MIN_DIGEST_LINE_LENGTH(Alg) \ (DIGEST_HEX_BYTES (Alg) /* length of hexadecimal message digest */ \ + 2 /* blank and binary indicator */ \ + 1 /* minimum filename length */ ) /* Nonzero if any of the files read were the standard input. */ static int have_read_stdin; /* The minimum length of a valid checksum line for the selected algorithm. */ static size_t min_digest_line_length; /* Set to the length of a digest hex string for the selected algorithm. */ static size_t digest_hex_bytes; /* With --check, don't generate any output. The exit code indicates success or failure. */ static int status_only = 0; /* With --check, print a message to standard error warning about each improperly formatted checksum line. */ static int warn = 0; /* Declared and set via one of the wrapper .c files. */ /* int algorithm = ALG_UNSPECIFIED; */ /* The name this program was run with. */ char *program_name; static const struct option long_options[] = { { "binary", no_argument, 0, 'b' }, { "check", no_argument, 0, 'c' }, { "status", no_argument, 0, 2 }, { "string", required_argument, 0, 1 }, { "text", no_argument, 0, 't' }, { "warn", no_argument, 0, 'w' }, { GETOPT_HELP_OPTION_DECL }, { GETOPT_VERSION_OPTION_DECL }, { NULL, 0, NULL, 0 } }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION] [FILE]...\n\ or: %s [OPTION] --check [FILE]\n\ Print or check %s (%d-bit) checksums.\n\ With no FILE, or when FILE is -, read standard input.\n\ "), program_name, program_name, DIGEST_TYPE_STRING (algorithm), DIGEST_BITS (algorithm)); printf (_("\ \n\ -b, --binary read files in binary mode (default on DOS/Windows)\n\ -c, --check check %s sums against given list\n\ -t, --text read files in text mode (default)\n\ \n\ "), DIGEST_TYPE_STRING (algorithm)); fputs (_("\ The following two options are useful only when verifying checksums:\n\ --status don't output anything, status code shows success\n\ -w, --warn warn about improperly formated checksum lines\n\ \n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\ \n\ The sums are computed as described in %s. When checking, the input\n\ should be a former output of this program. The default mode is to print\n\ a line with checksum, a character indicating type (`*' for binary, ` ' for\n\ text), and name for each FILE.\n"), (algorithm == ALG_MD5 ? "RFC 1321" : "FIPS-180-1")); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } /* Split the string S (of length S_LEN) into three parts: a hexadecimal digest, binary flag, and the file name. S is modified. */ static int split_3 (char *s, size_t s_len, unsigned char **hex_digest, int *binary, char **file_name) { size_t i; int escaped_filename = 0; #define ISWHITE(c) ((c) == ' ' || (c) == '\t') i = 0; while (ISWHITE (s[i])) ++i; /* Ignore this line if it is too short. Each line must have at least `min_digest_line_length - 1' (or one more, if the first is a backslash) more characters to contain correct message digest information. */ if (s_len - i < min_digest_line_length + (s[i] == '\\')) return 1; if (s[i] == '\\') { ++i; escaped_filename = 1; } *hex_digest = (unsigned char *) &s[i]; /* The first field has to be the n-character hexadecimal representation of the message digest. If it is not followed immediately by a white space it's an error. */ i += digest_hex_bytes; if (!ISWHITE (s[i])) return 1; s[i++] = '\0'; if (s[i] != ' ' && s[i] != '*') return 1; *binary = (s[i++] == '*'); /* All characters between the type indicator and end of line are significant -- that includes leading and trailing white space. */ *file_name = &s[i]; if (escaped_filename) { /* Translate each `\n' string in the file name to a NEWLINE, and each `\\' string to a backslash. */ char *dst = &s[i]; while (i < s_len) { switch (s[i]) { case '\\': if (i == s_len - 1) { /* A valid line does not end with a backslash. */ return 1; } ++i; switch (s[i++]) { case 'n': *dst++ = '\n'; break; case '\\': *dst++ = '\\'; break; default: /* Only `\' or `n' may follow a backslash. */ return 1; } break; case '\0': /* The file name may not contain a NUL. */ return 1; break; default: *dst++ = s[i++]; break; } } *dst = '\0'; } return 0; } static int hex_digits (unsigned char const *s) { while (*s) { if (!ISXDIGIT (*s)) return 0; ++s; } return 1; } /* An interface to the function, DIGEST_STREAM, (either md5_stream or sha_stream). Operate on FILENAME (it may be "-") and put the result in *BIN_RESULT. Return non-zero upon failure, zero to indicate success. */ static int digest_file (const char *filename, int binary, unsigned char *bin_result, int (*digest_stream)(FILE *, void *)) { FILE *fp; int err; if (STREQ (filename, "-")) { have_read_stdin = 1; fp = stdin; #if O_BINARY /* If we need binary reads from a pipe or redirected stdin, we need to switch it to BINARY mode here, since stdin is already open. */ if (binary) SET_BINARY (fileno (stdin)); #endif } else { /* OPENOPTS is a macro. It varies with the system. Some systems distinguish between internal and external text representations. */ fp = fopen (filename, OPENOPTS (binary)); if (fp == NULL) { error (0, errno, "%s", filename); return 1; } } err = (*digest_stream) (fp, bin_result); if (err) { error (0, errno, "%s", filename); if (fp != stdin) fclose (fp); return 1; } if (fp != stdin && fclose (fp) == EOF) { error (0, errno, "%s", filename); return 1; } return 0; } static int digest_check (const char *checkfile_name, int (*digest_stream)(FILE *, void *)) { FILE *checkfile_stream; int n_properly_formated_lines = 0; int n_mismatched_checksums = 0; int n_open_or_read_failures = 0; unsigned char bin_buffer[MAX_DIGEST_BIN_BYTES]; size_t line_number; char *line; size_t line_chars_allocated; if (STREQ (checkfile_name, "-")) { have_read_stdin = 1; checkfile_name = _("standard input"); checkfile_stream = stdin; } else { checkfile_stream = fopen (checkfile_name, "r"); if (checkfile_stream == NULL) { error (0, errno, "%s", checkfile_name); return 1; } } SET_MODE (fileno (checkfile_stream), O_TEXT); line_number = 0; line = NULL; line_chars_allocated = 0; do { char *filename; int binary; unsigned char *hex_digest; int err; int line_length; ++line_number; line_length = getline (&line, &line_chars_allocated, checkfile_stream); if (line_length <= 0) break; /* Ignore comment lines, which begin with a '#' character. */ if (line[0] == '#') continue; /* Remove any trailing newline. */ if (line[line_length - 1] == '\n') line[--line_length] = '\0'; err = split_3 (line, line_length, &hex_digest, &binary, &filename); if (err || !hex_digits (hex_digest)) { if (warn) { error (0, 0, _("%s: %lu: improperly formatted %s checksum line"), checkfile_name, (unsigned long) line_number, DIGEST_TYPE_STRING (algorithm)); } } else { static const char bin2hex[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; int fail; ++n_properly_formated_lines; fail = digest_file (filename, binary, bin_buffer, digest_stream); if (fail) { ++n_open_or_read_failures; if (!status_only) { printf (_("%s: FAILED open or read\n"), filename); fflush (stdout); } } else { size_t digest_bin_bytes = digest_hex_bytes / 2; size_t cnt; /* Compare generated binary number with text representation in check file. Ignore case of hex digits. */ for (cnt = 0; cnt < digest_bin_bytes; ++cnt) { if (TOLOWER (hex_digest[2 * cnt]) != bin2hex[bin_buffer[cnt] >> 4] || (TOLOWER (hex_digest[2 * cnt + 1]) != (bin2hex[bin_buffer[cnt] & 0xf]))) break; } if (cnt != digest_bin_bytes) ++n_mismatched_checksums; if (!status_only) { printf ("%s: %s\n", filename, (cnt != digest_bin_bytes ? _("FAILED") : _("OK"))); fflush (stdout); } } } } while (!feof (checkfile_stream) && !ferror (checkfile_stream)); if (line) free (line); if (ferror (checkfile_stream)) { error (0, 0, _("%s: read error"), checkfile_name); return 1; } if (checkfile_stream != stdin && fclose (checkfile_stream) == EOF) { error (0, errno, "%s", checkfile_name); return 1; } if (n_properly_formated_lines == 0) { /* Warn if no tests are found. */ error (0, 0, _("%s: no properly formatted %s checksum lines found"), checkfile_name, DIGEST_TYPE_STRING (algorithm)); } else { if (!status_only) { int n_computed_checkums = (n_properly_formated_lines - n_open_or_read_failures); if (n_open_or_read_failures > 0) { error (0, 0, _("WARNING: %d of %d listed %s could not be read"), n_open_or_read_failures, n_properly_formated_lines, (n_properly_formated_lines == 1 ? _("file") : _("files"))); } if (n_mismatched_checksums > 0) { error (0, 0, _("WARNING: %d of %d computed %s did NOT match"), n_mismatched_checksums, n_computed_checkums, (n_computed_checkums == 1 ? _("checksum") : _("checksums"))); } } } return ((n_properly_formated_lines > 0 && n_mismatched_checksums == 0 && n_open_or_read_failures == 0) ? 0 : 1); } int main (int argc, char **argv) { unsigned char bin_buffer[MAX_DIGEST_BIN_BYTES]; int do_check = 0; int opt; char **string = NULL; size_t n_strings = 0; size_t err = 0; int file_type_specified = 0; #if O_BINARY /* Binary is default on MSDOS, so the actual file contents are used in computation. */ int binary = 1; #else /* Text is default of the Plumb/Lankester format. */ int binary = 0; #endif /* Setting values of global variables. */ program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); while ((opt = getopt_long (argc, argv, "bctw", long_options, NULL)) != -1) switch (opt) { case 0: /* long option */ break; case 1: /* --string */ { if (string == NULL) string = (char **) xmalloc ((argc - 1) * sizeof (char *)); if (optarg == NULL) optarg = ""; string[n_strings++] = optarg; } break; case 'b': file_type_specified = 1; binary = 1; break; case 'c': do_check = 1; break; case 2: status_only = 1; warn = 0; break; case 't': file_type_specified = 1; binary = 0; break; case 'w': status_only = 0; warn = 1; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } min_digest_line_length = MIN_DIGEST_LINE_LENGTH (algorithm); digest_hex_bytes = DIGEST_HEX_BYTES (algorithm); if (file_type_specified && do_check) { error (0, 0, _("the --binary and --text options are meaningless when \ verifying checksums")); usage (EXIT_FAILURE); } if (n_strings > 0 && do_check) { error (0, 0, _("the --string and --check options are mutually exclusive")); usage (EXIT_FAILURE); } if (status_only && !do_check) { error (0, 0, _("the --status option is meaningful only when verifying checksums")); usage (EXIT_FAILURE); } if (warn && !do_check) { error (0, 0, _("the --warn option is meaningful only when verifying checksums")); usage (EXIT_FAILURE); } if (n_strings > 0) { size_t i; if (optind < argc) { error (0, 0, _("no files may be specified when using --string")); usage (EXIT_FAILURE); } for (i = 0; i < n_strings; ++i) { size_t cnt; if (algorithm == ALG_MD5) md5_buffer (string[i], strlen (string[i]), bin_buffer); else sha_buffer (string[i], strlen (string[i]), bin_buffer); for (cnt = 0; cnt < (digest_hex_bytes / 2); ++cnt) printf ("%02x", bin_buffer[cnt]); printf (" \"%s\"\n", string[i]); } } else if (do_check) { if (optind + 1 < argc) { error (0, 0, _("only one argument may be specified when using --check")); usage (EXIT_FAILURE); } err = digest_check ((optind == argc) ? "-" : argv[optind], DIGEST_STREAM (algorithm)); } else { if (optind == argc) argv[argc++] = "-"; for (; optind < argc; ++optind) { int fail; char *file = argv[optind]; fail = digest_file (file, binary, bin_buffer, DIGEST_STREAM (algorithm)); err |= fail; if (!fail) { size_t i; /* Output a leading backslash if the file name contains a newline or backslash. */ if (strchr (file, '\n') || strchr (file, '\\')) putchar ('\\'); for (i = 0; i < (digest_hex_bytes / 2); ++i) printf ("%02x", bin_buffer[i]); putchar (' '); if (binary) putchar ('*'); else putchar (' '); /* Translate each NEWLINE byte to the string, "\\n", and each backslash to "\\\\". */ for (i = 0; i < strlen (file); ++i) { switch (file[i]) { case '\n': fputs ("\\n", stdout); break; case '\\': fputs ("\\\\", stdout); break; default: putchar (file[i]); break; } } putchar ('\n'); } } } if (have_read_stdin && fclose (stdin) == EOF) error (EXIT_FAILURE, errno, _("standard input")); exit (err == 0 ? EXIT_SUCCESS : EXIT_FAILURE); }
/* md5.h - Declaration of functions and data types used for MD5 sum computing library functions. Copyright (C) 1995, 1996, 1999 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. Bugs can be reported to bug-glibc@prep.ai.mit.edu. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef _MD5_H #define _MD5_H 1 #include <stdio.h> #if defined HAVE_LIMITS_H || _LIBC # include <limits.h> #endif /* The following contortions are an attempt to use the C preprocessor to determine an unsigned integral type that is 32 bits wide. An alternative approach is to use autoconf's AC_CHECK_SIZEOF macro, but doing that would require that the configure script compile and *run* the resulting executable. Locally running cross-compiled executables is usually not possible. */ #ifdef _LIBC # include <stdint.h> typedef uint32_t md5_uint32; typedef uintptr_t md5_uintptr; #else # if defined __STDC__ && __STDC__ # define UINT_MAX_32_BITS 4294967295U # else # define UINT_MAX_32_BITS 0xFFFFFFFF # endif /* If UINT_MAX isn't defined, assume it's a 32-bit type. This should be valid for all systems GNU cares about because that doesn't include 16-bit systems, and only modern systems (that certainly have <limits.h>) have 64+-bit integral types. */ # ifndef UINT_MAX # define UINT_MAX UINT_MAX_32_BITS # endif # if UINT_MAX == UINT_MAX_32_BITS typedef unsigned int md5_uint32; # else # if USHRT_MAX == UINT_MAX_32_BITS typedef unsigned short md5_uint32; # else # if ULONG_MAX == UINT_MAX_32_BITS typedef unsigned long md5_uint32; # else /* The following line is intended to evoke an error. Using #error is not portable enough. */ "Cannot determine unsigned 32-bit data type." # endif # endif # endif /* We have to make a guess about the integer type equivalent in size to pointers which should always be correct. */ typedef unsigned long int md5_uintptr; #endif #undef __P #if defined (__STDC__) && __STDC__ #define __P(x) x #else #define __P(x) () #endif /* Structure to save state of computation between the single steps. */ struct md5_ctx { md5_uint32 A; md5_uint32 B; md5_uint32 C; md5_uint32 D; md5_uint32 total[2]; md5_uint32 buflen; char buffer[128]; }; /* * The following three functions are build up the low level used in * the functions `md5_stream' and `md5_buffer'. */ /* Initialize structure containing state of computation. (RFC 1321, 3.3: Step 3) */ extern void md5_init_ctx __P ((struct md5_ctx *ctx)); /* Starting with the result of former calls of this function (or the initialization function update the context for the next LEN bytes starting at BUFFER. It is necessary that LEN is a multiple of 64!!! */ extern void md5_process_block __P ((const void *buffer, size_t len, struct md5_ctx *ctx)); /* Starting with the result of former calls of this function (or the initialization function update the context for the next LEN bytes starting at BUFFER. It is NOT required that LEN is a multiple of 64. */ extern void md5_process_bytes __P ((const void *buffer, size_t len, struct md5_ctx *ctx)); /* Process the remaining bytes in the buffer and put result from CTX in first 16 bytes following RESBUF. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. IMPORTANT: On some systems it is required that RESBUF be correctly aligned for a 32 bits value. */ extern void *md5_finish_ctx __P ((struct md5_ctx *ctx, void *resbuf)); /* Put result from CTX in first 16 bytes following RESBUF. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. IMPORTANT: On some systems it is required that RESBUF is correctly aligned for a 32 bits value. */ extern void *md5_read_ctx __P ((const struct md5_ctx *ctx, void *resbuf)); /* Compute MD5 message digest for bytes read from STREAM. The resulting message digest number will be written into the 16 bytes beginning at RESBLOCK. */ extern int md5_stream __P ((FILE *stream, void *resblock)); /* Compute MD5 message digest for LEN bytes beginning at BUFFER. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. */ extern void *md5_buffer __P ((const char *buffer, size_t len, void *resblock)); /* The following is from gnupg-1.0.2's cipher/bithelp.h. */ /* Rotate a 32 bit integer by n bytes */ #if defined __GNUC__ && defined __i386__ static inline md5_uint32 rol(md5_uint32 x, int n) { __asm__("roll %%cl,%0" :"=r" (x) :"0" (x),"c" (n)); return x; } #else # define rol(x,n) ( ((x) << (n)) | ((x) >> (32-(n))) ) #endif #endif
/* sha.h - Declaration of functions and datatypes for SHA1 sum computing library functions. Copyright (C) 1999, Scott G. Miller */ #ifndef _SHA_H # define _SHA_H 1 # include <stdio.h> # include "md5.h" /* Structure to save state of computation between the single steps. */ struct sha_ctx { md5_uint32 A; md5_uint32 B; md5_uint32 C; md5_uint32 D; md5_uint32 E; md5_uint32 total[2]; md5_uint32 buflen; char buffer[128]; }; /* Starting with the result of former calls of this function (or the initialization function update the context for the next LEN bytes starting at BUFFER. It is necessary that LEN is a multiple of 64!!! */ extern void sha_process_block __P ((const void *buffer, size_t len, struct sha_ctx *ctx)); /* Starting with the result of former calls of this function (or the initialization function update the context for the next LEN bytes starting at BUFFER. It is NOT required that LEN is a multiple of 64. */ extern void sha_process_bytes __P((const void *buffer, size_t len, struct sha_ctx *ctx)); /* Initialize structure containing state of computation. */ extern void sha_init_ctx __P ((struct sha_ctx *ctx)); /* Process the remaining bytes in the buffer and put result from CTX in first 16 bytes following RESBUF. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. IMPORTANT: On some systems it is required that RESBUF is correctly aligned for a 32 bits value. */ extern void *sha_finish_ctx __P ((struct sha_ctx *ctx, void *resbuf)); /* Put result from CTX in first 16 bytes following RESBUF. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. IMPORTANT: On some systems it is required that RESBUF is correctly aligned for a 32 bits value. */ extern void *sha_read_ctx __P ((const struct sha_ctx *ctx, void *resbuf)); /* Compute MD5 message digest for bytes read from STREAM. The resulting message digest number will be written into the 16 bytes beginning at RESBLOCK. */ extern int sha_stream __P ((FILE *stream, void *resblock)); /* Compute MD5 message digest for LEN bytes beginning at BUFFER. The result is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII representation of the message digest. */ extern void *sha_buffer __P ((const char *buffer, size_t len, void *resblock)); #endif
/* Copyright (C) 1995, 1997, 1999 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef GETLINE_H_ # define GETLINE_H_ 1 # include <stdio.h> # ifndef PARAMS # if defined PROTOTYPES || (defined __STDC__ && __STDC__) # define PARAMS(Args) Args # else # define PARAMS(Args) () # endif # endif # if __GLIBC__ < 2 int getline PARAMS ((char **_lineptr, size_t *_n, FILE *_stream)); int getdelim PARAMS ((char **_lineptr, size_t *_n, int _delimiter, FILE *_stream)); # endif #endif /* not GETLINE_H_ */
/* mv -- move or rename files Copyright (C) 86, 89, 90, 91, 1995-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Mike Parker, David MacKenzie, and Jim Meyering */ #ifdef _AIX #pragma alloca #endif #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include <assert.h> #include "system.h" #include "argmatch.h" #include "backupfile.h" #include "copy.h" #include "cp-hash.h" #include "dirname.h" #include "error.h" #include "path-concat.h" #include "quote.h" #include "remove.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "mv" #define AUTHORS N_ ("Mike Parker, David MacKenzie, and Jim Meyering") /* Initial number of entries in each hash table entry's table of inodes. */ #define INITIAL_HASH_MODULE 100 /* Initial number of entries in the inode hash table. */ #define INITIAL_ENTRY_TAB_SIZE 70 /* For long options that have no equivalent short option, use a non-character as a pseudo short option, starting with CHAR_MAX + 1. */ enum { TARGET_DIRECTORY_OPTION = CHAR_MAX + 1, STRIP_TRAILING_SLASHES_OPTION, REPLY_OPTION }; int isdir (); int lstat (); /* The name this program was run with. */ char *program_name; /* Remove any trailing slashes from each SOURCE argument. */ static int remove_trailing_slashes; /* Valid arguments to the `--reply' option. */ static char const* const reply_args[] = { "yes", "no", "query", 0 }; /* The values that correspond to the above strings. */ static int const reply_vals[] = { I_ALWAYS_YES, I_ALWAYS_NO, I_ASK_USER }; static struct option const long_options[] = { {"backup", optional_argument, NULL, 'b'}, {"force", no_argument, NULL, 'f'}, {"interactive", no_argument, NULL, 'i'}, {"reply", required_argument, NULL, REPLY_OPTION}, {"strip-trailing-slashes", no_argument, NULL, STRIP_TRAILING_SLASHES_OPTION}, {"suffix", required_argument, NULL, 'S'}, {"target-directory", required_argument, NULL, TARGET_DIRECTORY_OPTION}, {"update", no_argument, NULL, 'u'}, {"verbose", no_argument, NULL, 'v'}, {"version-control", required_argument, NULL, 'V'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; static void rm_option_init (struct rm_options *x) { x->unlink_dirs = 0; x->ignore_missing_files = 0; x->recursive = 1; /* Should we prompt for removal, too? No. Prompting for the `move' part is enough. It implies removal. */ x->interactive = 0; x->stdin_tty = 0; x->verbose = 0; } static void cp_option_init (struct cp_options *x) { x->copy_as_regular = 0; /* FIXME: maybe make this an option */ x->dereference = DEREF_NEVER; x->unlink_dest_before_opening = 0; x->unlink_dest_after_failed_open = 0; x->hard_link = 0; x->interactive = I_UNSPECIFIED; x->move_mode = 1; x->myeuid = geteuid (); x->one_file_system = 0; x->preserve_ownership = 1; x->preserve_links = 1; x->preserve_mode = 1; x->preserve_timestamps = 1; x->require_preserve = 0; /* FIXME: maybe make this an option */ x->recursive = 1; x->sparse_mode = SPARSE_AUTO; /* FIXME: maybe make this an option */ x->symbolic_link = 0; x->set_mode = 0; x->mode = 0; x->stdin_tty = isatty (STDIN_FILENO); /* Find out the current file creation mask, to knock the right bits when using chmod. The creation mask is set to be liberal, so that created directories can be written, even if it would not have been allowed with the mask this process was started with. */ x->umask_kill = ~ umask (0); x->update = 0; x->verbose = 0; x->xstat = lstat; x->dest_info = NULL; x->src_info = NULL; } /* If PATH is an existing directory, return nonzero, else 0. */ static int is_real_dir (const char *path) { struct stat stats; return lstat (path, &stats) == 0 && S_ISDIR (stats.st_mode); } /* Move SOURCE onto DEST. Handles cross-filesystem moves. If SOURCE is a directory, DEST must not exist. Return 0 if successful, non-zero if an error occurred. */ static int do_move (const char *source, const char *dest, const struct cp_options *x) { static int first = 1; int copy_into_self; int rename_succeeded; int fail; if (first) { first = 0; /* Allocate space for remembering copied and created files. */ hash_init (); } fail = copy (source, dest, 0, x, ©_into_self, &rename_succeeded); if (!fail) { char const *dir_to_remove; if (copy_into_self) { /* In general, when copy returns with copy_into_self set, SOURCE is the same as, or a parent of DEST. In this case we know it's a parent. It doesn't make sense to move a directory into itself, and besides in some situations doing so would give highly nonintuitive results. Run this `mkdir b; touch a c; mv * b' in an empty directory. Here's the result of running echo `find b -print`: b b/a b/b b/b/a b/c. Notice that only file `a' was copied into b/b. Handle this by giving a diagnostic, removing the copied-into-self directory, DEST (`b/b' in the example), and failing. */ dir_to_remove = NULL; fail = 1; } else if (rename_succeeded) { /* No need to remove anything. SOURCE was successfully renamed to DEST. Or the user declined to rename a file. */ dir_to_remove = NULL; } else { /* This may mean SOURCE and DEST referred to different devices. It may also conceivably mean that even though they referred to the same device, rename wasn't implemented for that device. E.g., (from Joel N. Weber), [...] there might someday be cases where you can't rename but you can copy where the device name is the same, especially on Hurd. Consider an ftpfs with a primitive ftp server that supports uploading, downloading and deleting, but not renaming. Also, note that comparing device numbers is not a reliable check for `can-rename'. Some systems can be set up so that files from many different physical devices all have the same st_dev field. This is a feature of some NFS mounting configurations. We reach this point if SOURCE has been successfully copied to DEST. Now we have to remove SOURCE. This function used to resort to copying only when rename failed and set errno to EXDEV. */ dir_to_remove = source; } if (dir_to_remove != NULL) { struct rm_options rm_options; enum RM_status status; rm_option_init (&rm_options); rm_options.verbose = x->verbose; status = rm (1, &dir_to_remove, &rm_options); assert (VALID_STATUS (status)); if (status == RM_ERROR) fail = 1; } } return fail; } /* Move file SOURCE onto DEST. Handles the case when DEST is a directory. DEST_IS_DIR must be nonzero when DEST is a directory or a symlink to a directory and zero otherwise. Return 0 if successful, non-zero if an error occurred. */ static int movefile (char *source, char *dest, int dest_is_dir, const struct cp_options *x) { int dest_had_trailing_slash = strip_trailing_slashes (dest); int fail; /* This code was introduced to handle the ambiguity in the semantics of mv that is induced by the varying semantics of the rename function. Some systems (e.g., Linux) have a rename function that honors a trailing slash, while others (like Solaris 5,6,7) have a rename function that ignores a trailing slash. I believe the Linux rename semantics are POSIX and susv2 compliant. */ if (remove_trailing_slashes) strip_trailing_slashes (source); /* In addition to when DEST is a directory, if DEST has a trailing slash and neither SOURCE nor DEST is a directory, presume the target is DEST/`basename source`. This converts `mv x y/' to `mv x y/x'. This change means that the command `mv any file/' will now fail rather than performing the move. The case when SOURCE is a directory and DEST is not is properly diagnosed by do_move. */ if (dest_is_dir || (dest_had_trailing_slash && !is_real_dir (source))) { /* DEST is a directory; build full target filename. */ char const *src_basename = base_name (source); char *new_dest = path_concat (dest, src_basename, NULL); if (new_dest == NULL) xalloc_die (); strip_trailing_slashes (new_dest); fail = do_move (source, new_dest, x); free (new_dest); } else { fail = do_move (source, dest, x); } return fail; } void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... SOURCE DEST\n\ or: %s [OPTION]... SOURCE... DIRECTORY\n\ or: %s [OPTION]... --target-directory=DIRECTORY SOURCE...\n\ "), program_name, program_name, program_name); fputs (_("\ Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.\n\ \n\ "), stdout); fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ --backup[=CONTROL] make a backup of each existing destination file\n\ -b like --backup but does not accept an argument\n\ -f, --force do not prompt before overwriting\n\ equivalent to --reply=yes\n\ -i, --interactive prompt before overwrite\n\ equivalent to --reply=query\n\ "), stdout); fputs (_("\ --reply={yes,no,query} specify how to handle the prompt about an\n\ existing destination file\n\ --strip-trailing-slashes remove any trailing slashes from each SOURCE\n\ argument\n\ -S, --suffix=SUFFIX override the usual backup suffix\n\ "), stdout); fputs (_("\ --target-directory=DIRECTORY move all SOURCE arguments into DIRECTORY\n\ -u, --update move only when the SOURCE file is newer\n\ than the destination file or when the\n\ destination file is missing\n\ -v, --verbose explain what is being done\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ The version control method may be selected via the --backup option or through\n\ the VERSION_CONTROL environment variable. Here are the values:\n\ \n\ "), stdout); fputs (_("\ none, off never make backups (even if --backup is given)\n\ numbered, t make numbered backups\n\ existing, nil numbered if numbered backups exist, simple otherwise\n\ simple, never always make simple backups\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } int main (int argc, char **argv) { int c; int errors; int make_backups = 0; int dest_is_dir; char *backup_suffix_string; char *version_control_string = NULL; struct cp_options x; char *target_directory = NULL; int target_directory_specified; unsigned int n_files; char **file; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); cp_option_init (&x); /* FIXME: consider not calling getenv for SIMPLE_BACKUP_SUFFIX unless we'll actually use backup_suffix_string. */ backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX"); errors = 0; while ((c = getopt_long (argc, argv, "bfiuvS:V:", long_options, NULL)) != -1) { switch (c) { case 0: break; case 'V': /* FIXME: this is deprecated. Remove it in 2001. */ error (0, 0, _("warning: --version-control (-V) is obsolete; support for\ it\nwill be removed in some future release. Use --backup=%s instead." ), optarg); /* Fall through. */ case 'b': make_backups = 1; if (optarg) version_control_string = optarg; break; case 'f': x.interactive = I_ALWAYS_YES; break; case 'i': x.interactive = I_ASK_USER; break; case REPLY_OPTION: x.interactive = XARGMATCH ("--reply", optarg, reply_args, reply_vals); break; case STRIP_TRAILING_SLASHES_OPTION: remove_trailing_slashes = 1; break; case TARGET_DIRECTORY_OPTION: target_directory = optarg; break; case 'u': x.update = 1; break; case 'v': x.verbose = 1; break; case 'S': make_backups = 1; backup_suffix_string = optarg; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } n_files = argc - optind; file = argv + optind; target_directory_specified = (target_directory != NULL); if (target_directory == NULL && n_files != 0) target_directory = file[n_files - 1]; dest_is_dir = (n_files > 0 && isdir (target_directory)); if (n_files == 0 || (n_files == 1 && !target_directory_specified)) { error (0, 0, _("missing file argument")); usage (EXIT_FAILURE); } if (target_directory_specified) { if (!dest_is_dir) { error (0, 0, _("specified target, %s is not a directory"), quote (target_directory)); usage (EXIT_FAILURE); } } else if (n_files > 2 && !dest_is_dir) { error (0, 0, _("when moving multiple files, last argument must be a directory")); usage (EXIT_FAILURE); } if (backup_suffix_string) simple_backup_suffix = xstrdup (backup_suffix_string); x.backup_type = (make_backups ? xget_version (_("backup type"), version_control_string) : none); /* Move each arg but the last into the target_directory. */ { unsigned int last_file_idx = (target_directory_specified ? n_files - 1 : n_files - 2); unsigned int i; /* Initialize the hash table only if we'll need it. The problem it is used to detect can arise only if there are two or more files to move. */ if (last_file_idx) dest_info_init (&x); for (i = 0; i <= last_file_idx; ++i) errors |= movefile (file[i], target_directory, dest_is_dir, &x); } exit (errors); }
/* remove.c -- core functions for removing files and directories Copyright (C) 88, 90, 91, 1994-2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Extracted from rm.c and librarified, then rewritten by Jim Meyering. */ #ifdef _AIX #pragma alloca #endif #include <config.h> #include <stdio.h> #include <sys/types.h> #include <setjmp.h> #include <assert.h> #include "save-cwd.h" #include "system.h" #include "cycle-check.h" #include "dirname.h" #include "error.h" #include "euidaccess.h" #include "file-type.h" #include "hash.h" #include "hash-pjw.h" #include "obstack.h" #include "quote.h" #include "remove.h" /* Avoid shadowing warnings because these are functions declared in dirname.h as well as locals used below. */ #define dir_name rm_dir_name #define dir_len rm_dir_len #define obstack_chunk_alloc malloc #define obstack_chunk_free free /* FIXME: if possible, use autoconf... */ #ifdef __GLIBC__ # define ROOT_CAN_UNLINK_DIRS 0 #else # define ROOT_CAN_UNLINK_DIRS 1 #endif enum Ternary { T_UNKNOWN = 2, T_NO, T_YES }; typedef enum Ternary Ternary; /* The prompt function may be called twice a given directory. The first time, we ask whether to descend into it, and the second time, we ask whether to remove it. */ enum Prompt_action { PA_DESCEND_INTO_DIR = 2, PA_REMOVE_DIR }; /* On systems with an lstat function that accepts the empty string, arrange to make lstat calls go through the wrapper function. */ #if HAVE_LSTAT_EMPTY_STRING_BUG int rpl_lstat (const char *, struct stat *); # define lstat(Name, Stat_buf) rpl_lstat(Name, Stat_buf) #endif #ifdef D_INO_IN_DIRENT # define D_INO(dp) ((dp)->d_ino) # define ENABLE_CYCLE_CHECK #else /* Some systems don't have inodes, so fake them to avoid lots of ifdefs. */ # define D_INO(dp) 1 #endif /* Initial capacity of per-directory hash table of entries that have been processed but not been deleted. */ #define HT_UNREMOVABLE_INITIAL_CAPACITY 13 /* An entry in the active directory stack. Each entry corresponds to an `active' directory. */ struct AD_ent { /* For a given active directory, this is the set of names of entries in that directory that could/should not be removed. For example, `.' and `..', as well as files/dirs for which unlink/rmdir failed e.g., due to access restrictions. */ Hash_table *unremovable; /* Record the status for a given active directory; we need to know whether an entry was not removed, either because of an error or because the user declined. */ enum RM_status status; union { /* The directory's dev/ino. Used to ensure that `chdir some-subdir', then `chdir ..' takes us back to the same directory from which we started). (valid for all but the bottommost entry on the stack. */ struct dev_ino a; /* Enough information to restore the initial working directory. (valid only for the bottommost entry on the stack) */ struct saved_cwd saved_cwd; } u; }; int yesno (); extern char *program_name; struct dirstack_state { /* The name of the directory (starting with and relative to a command line argument) being processed. When a subdirectory is entered, a new component is appended (pushed). Remove (pop) the top component upon chdir'ing out of a directory. This is used to form the full name of the current directory or a file therein, when necessary. */ struct obstack dir_stack; /* Stack of lengths of directory names (including trailing slash) appended to dir_stack. We have to have a separate stack of lengths (rather than just popping back to previous slash) because the first element pushed onto the dir stack may contain slashes. */ struct obstack len_stack; /* Stack of active directory entries. The first `active' directory is the initial working directory. Additional active dirs are pushed onto the stack as we `chdir' into each directory to be processed. When finished with the hierarchy under a directory, pop the active dir stack. */ struct obstack Active_dir; /* Used to detect cycles. */ struct cycle_check_state cycle_check_state; /* Target of a longjmp in case rm detects a directory cycle. */ jmp_buf current_arg_jumpbuf; }; typedef struct dirstack_state Dirstack_state; Dirstack_state * ds_init () { Dirstack_state *ds = XMALLOC (struct dirstack_state, 1); obstack_init (&ds->dir_stack); obstack_init (&ds->len_stack); obstack_init (&ds->Active_dir); return ds; } void ds_free (Dirstack_state *ds) { obstack_free (&ds->dir_stack, NULL); obstack_free (&ds->len_stack, NULL); obstack_free (&ds->Active_dir, NULL); } static void hash_freer (void *x) { free (x); } static bool hash_compare_strings (void const *x, void const *y) { return STREQ (x, y) ? true : false; } static inline void push_dir (Dirstack_state *ds, const char *dir_name) { size_t len; len = strlen (dir_name); /* Append the string onto the stack. */ obstack_grow (&ds->dir_stack, dir_name, len); /* Append a trailing slash. */ obstack_1grow (&ds->dir_stack, '/'); /* Add one for the slash. */ ++len; /* Push the length (including slash) onto its stack. */ obstack_grow (&ds->len_stack, &len, sizeof (len)); } /* Return the entry name of the directory on the top of the stack in malloc'd storage. */ static inline char * top_dir (Dirstack_state const *ds) { int n_lengths = obstack_object_size (&ds->len_stack) / sizeof (size_t); size_t *length = (size_t *) obstack_base (&ds->len_stack); size_t top_len = length[n_lengths - 1]; char const *p = obstack_next_free (&ds->dir_stack) - top_len; char *q = xmalloc (top_len); memcpy (q, p, top_len - 1); q[top_len - 1] = 0; return q; } static inline void pop_dir (Dirstack_state *ds) { int n_lengths = obstack_object_size (&ds->len_stack) / sizeof (size_t); size_t *length = (size_t *) obstack_base (&ds->len_stack); size_t top_len; assert (n_lengths > 0); top_len = length[n_lengths - 1]; assert (top_len >= 2); /* Pop off the specified length of pathname. */ assert (obstack_object_size (&ds->dir_stack) >= top_len); obstack_blank (&ds->dir_stack, -top_len); /* Pop the length stack, too. */ assert (obstack_object_size (&ds->len_stack) >= sizeof (size_t)); obstack_blank (&ds->len_stack, -(int) sizeof (size_t)); } /* Copy the SRC_LEN bytes of data beginning at SRC into the DST_LEN-byte buffer, DST, so that the last source byte is at the end of the destination buffer. If SRC_LEN is longer than DST_LEN, then set *TRUNCATED to non-zero. Set *RESULT to point to the beginning of (the portion of) the source data in DST. Return the number of bytes remaining in the destination buffer. */ static size_t right_justify (char *dst, size_t dst_len, const char *src, size_t src_len, char **result, int *truncated) { const char *sp; char *dp; if (src_len <= dst_len) { sp = src; dp = dst + (dst_len - src_len); *truncated = 0; } else { sp = src + (src_len - dst_len); dp = dst; src_len = dst_len; *truncated = 1; } *result = memcpy (dp, sp, src_len); return dst_len - src_len; } /* Using the global directory name obstack, create the full path to FILENAME. Return it in sometimes-realloc'd space that should not be freed by the caller. Realloc as necessary. If realloc fails, use a static buffer and put as long a suffix in that buffer as possible. */ #define full_filename(Filename) full_filename_ (ds, Filename) static char * full_filename_ (Dirstack_state const *ds, const char *filename) { static char *buf = NULL; static size_t n_allocated = 0; int dir_len = obstack_object_size (&ds->dir_stack); char *dir_name = (char *) obstack_base (&ds->dir_stack); size_t n_bytes_needed; size_t filename_len; filename_len = strlen (filename); n_bytes_needed = dir_len + filename_len + 1; if (n_allocated < n_bytes_needed) { /* This code requires that realloc accept NULL as the first arg. This function must not use xrealloc. Otherwise, an out-of-memory error involving a file name to be expanded here wouldn't ever be issued. Use realloc and fall back on using a static buffer if memory allocation fails. */ buf = realloc (buf, n_bytes_needed); n_allocated = n_bytes_needed; if (buf == NULL) { #define SBUF_SIZE 512 #define ELLIPSES_PREFIX "[...]" static char static_buf[SBUF_SIZE]; int truncated; size_t len; char *p; len = right_justify (static_buf, SBUF_SIZE, filename, filename_len + 1, &p, &truncated); right_justify (static_buf, len, dir_name, dir_len, &p, &truncated); if (truncated) { memcpy (static_buf, ELLIPSES_PREFIX, sizeof (ELLIPSES_PREFIX) - 1); } return p; } } if (filename_len == 1 && *filename == '.' && dir_len) { /* FILENAME is just `.' and dir_len is nonzero. Copy the directory part, omitting the trailing slash, and append a trailing zero byte. */ char *p = mempcpy (buf, dir_name, dir_len - 1); *p = 0; } else { /* Copy the directory part, including trailing slash, and then append the filename part, including a trailing zero byte. */ memcpy (mempcpy (buf, dir_name, dir_len), filename, filename_len + 1); assert (strlen (buf) + 1 == n_bytes_needed); } return buf; } static size_t AD_stack_height (Dirstack_state const *ds) { return obstack_object_size (&ds->Active_dir) / sizeof (struct AD_ent); } static struct AD_ent * AD_stack_top (Dirstack_state const *ds) { return (struct AD_ent *) ((char *) obstack_next_free (&ds->Active_dir) - sizeof (struct AD_ent)); } static void AD_stack_pop (Dirstack_state *ds) { /* operate on Active_dir. pop and free top entry */ struct AD_ent *top = AD_stack_top (ds); if (top->unremovable) hash_free (top->unremovable); obstack_blank (&ds->Active_dir, -(int) sizeof (struct AD_ent)); pop_dir (ds); } /* chdir `up' one level. Whenever using chdir '..', verify that the post-chdir dev/ino numbers for `.' match the saved ones. Return the name (in malloc'd storage) of the directory (usually now empty) from which we're coming. */ static char * AD_pop_and_chdir (Dirstack_state *ds) { /* Get the name of the current directory from the top of the stack. */ char *dir = top_dir (ds); enum RM_status old_status = AD_stack_top(ds)->status; struct stat sb; struct AD_ent *top; AD_stack_pop (ds); /* Propagate any failure to parent. */ UPDATE_STATUS (AD_stack_top(ds)->status, old_status); assert (AD_stack_height (ds)); top = AD_stack_top (ds); if (1 < AD_stack_height (ds)) { /* We can give a better diagnostic here, since the target is relative. */ if (chdir ("..")) { error (EXIT_FAILURE, errno, _("cannot chdir from %s to .."), quote (full_filename ("."))); } } else { if (restore_cwd (&top->u.saved_cwd)) error (EXIT_FAILURE, errno, _("failed to return to initial working directory")); } if (lstat (".", &sb)) error (EXIT_FAILURE, errno, _("cannot lstat `.' in %s"), quote (full_filename ("."))); if (1 < AD_stack_height (ds)) { /* Ensure that post-chdir dev/ino match the stored ones. */ if ( ! SAME_INODE (sb, top->u.a)) error (EXIT_FAILURE, 0, _("%s changed dev/ino"), quote (full_filename ("."))); } return dir; } /* Initialize *HT if it is NULL. Insert FILENAME into HT. */ static void AD_mark_helper (Hash_table **ht, char const *filename) { if (*ht == NULL) *ht = hash_initialize (HT_UNREMOVABLE_INITIAL_CAPACITY, NULL, hash_pjw, hash_compare_strings, hash_freer); if (*ht == NULL) xalloc_die (); if (! hash_insert (*ht, filename)) xalloc_die (); } /* Mark FILENAME (in current directory) as unremovable. */ static void AD_mark_as_unremovable (Dirstack_state *ds, char const *filename) { AD_mark_helper (&AD_stack_top(ds)->unremovable, xstrdup (filename)); } /* Mark the current directory as unremovable. I.e., mark the entry in the parent directory corresponding to `.'. This happens e.g., when an opendir fails and the only name the caller has conveniently at hand is `.'. */ static void AD_mark_current_as_unremovable (Dirstack_state *ds) { struct AD_ent *top = AD_stack_top (ds); const char *curr = top_dir (ds); assert (1 < AD_stack_height (ds)); --top; AD_mark_helper (&top->unremovable, curr); } /* Push the initial cwd info onto the stack. This will always be the bottommost entry on the stack. */ static void AD_push_initial (Dirstack_state *ds, struct saved_cwd const *cwd) { struct AD_ent *top; /* Extend the stack. */ obstack_blank (&ds->Active_dir, sizeof (struct AD_ent)); /* Fill in the new values. */ top = AD_stack_top (ds); top->u.saved_cwd = *cwd; top->unremovable = NULL; } /* Push info about the current working directory (".") onto the active directory stack. DIR is the ./-relative name through which we've just `chdir'd to this directory. DIR_SB_FROM_PARENT is the result of calling lstat on DIR from the parent of DIR. */ static void AD_push (Dirstack_state *ds, char const *dir, struct stat const *dir_sb_from_parent) { struct stat sb; struct AD_ent *top; push_dir (ds, dir); if (lstat (".", &sb)) error (EXIT_FAILURE, errno, _("cannot lstat `.' in %s"), quote (full_filename ("."))); if ( ! SAME_INODE (sb, *dir_sb_from_parent)) error (EXIT_FAILURE, errno, _("%s changed dev/ino"), quote (full_filename ("."))); /* Extend the stack. */ obstack_blank (&ds->Active_dir, sizeof (struct AD_ent)); /* Fill in the new values. */ top = AD_stack_top (ds); top->u.a.st_dev = sb.st_dev; top->u.a.st_ino = sb.st_ino; top->unremovable = NULL; } static int AD_is_removable (Dirstack_state const *ds, char const *file) { struct AD_ent *top = AD_stack_top (ds); return ! (top->unremovable && hash_lookup (top->unremovable, file)); } static bool is_empty_dir (char const *dir) { DIR *dirp = opendir (dir); if (dirp == NULL) { closedir (dirp); return false; } while (1) { struct dirent *dp; const char *f; errno = 0; dp = readdir (dirp); if (dp == NULL) { int saved_errno = errno; closedir (dirp); return saved_errno == 0 ? true : false; } f = dp->d_name; if ( ! DOT_OR_DOTDOT (f)) { closedir (dirp); return false; } } } /* Prompt whether to remove FILENAME, if required via a combination of the options specified by X and/or file attributes. If the file may be removed, return RM_OK. If the user declines to remove the file, return RM_USER_DECLINED. If not ignoring missing files and we cannot lstat FILENAME, then return RM_ERROR. Depending on MODE, ask whether to `descend into' or to `remove' the directory FILENAME. MODE is ignored when FILENAME is not a directory. Set *IS_EMPTY to T_YES if FILENAME is an empty directory, and it is appropriate to try to remove it with rmdir (e.g. recursive mode). Don't even try to set *IS_EMPTY when MODE == PA_REMOVE_DIR. Set *IS_DIR to T_YES or T_NO if we happen to determine whether FILENAME is a directory. */ static enum RM_status prompt (Dirstack_state const *ds, char const *filename, struct rm_options const *x, enum Prompt_action mode, Ternary *is_dir, Ternary *is_empty) { int write_protected = 0; *is_empty = T_UNKNOWN; *is_dir = T_UNKNOWN; if ((!x->ignore_missing_files && (x->interactive || x->stdin_tty) && (write_protected = (euidaccess (filename, W_OK) && errno == EACCES))) || x->interactive) { struct stat sbuf; if (lstat (filename, &sbuf)) { /* lstat failed. This happens e.g., with `rm '''. */ error (0, errno, _("cannot lstat %s"), quote (full_filename (filename))); return RM_ERROR; } /* Using permissions doesn't make sense for symlinks. */ if (S_ISLNK (sbuf.st_mode)) { if ( ! x->interactive) return RM_OK; write_protected = 0; } /* Issue the prompt. */ { char const *quoted_name = quote (full_filename (filename)); *is_dir = (S_ISDIR (sbuf.st_mode) ? T_YES : T_NO); /* FIXME: use a variant of error (instead of fprintf) that doesn't append a newline. Then we won't have to declare program_name in this file. */ if (S_ISDIR (sbuf.st_mode) && x->recursive && mode == PA_DESCEND_INTO_DIR && ((*is_empty = (is_empty_dir (filename) ? T_YES : T_NO)) == T_NO)) fprintf (stderr, (write_protected ? _("%s: descend into write-protected directory %s? ") : _("%s: descend into directory %s? ")), program_name, quoted_name); else { /* TRANSLATORS: You may find it more convenient to translate the equivalent of _("%s: remove %s (write-protected) %s? "). It should avoid grammatical problems with the output of file_type. */ fprintf (stderr, (write_protected ? _("%s: remove write-protected %s %s? ") : _("%s: remove %s %s? ")), program_name, file_type (&sbuf), quoted_name); } if (!yesno ()) return RM_USER_DECLINED; } } return RM_OK; } #if HAVE_STRUCT_DIRENT_D_TYPE # define DT_IS_DIR(D) ((D)->d_type == DT_DIR) #else /* Use this only if the member exists -- i.e., don't return 0. */ # define DT_IS_DIR(D) do_not_use_this_macro #endif #define DO_UNLINK(Filename, X) \ do \ { \ if (unlink (Filename) == 0) \ { \ if ((X)->verbose) \ printf (_("removed %s\n"), quote (full_filename (Filename))); \ return RM_OK; \ } \ \ if (errno == ENOENT && (X)->ignore_missing_files) \ return RM_OK; \ } \ while (0) #define DO_RMDIR(Filename, X) \ do \ { \ if (rmdir (Filename) == 0) \ { \ if ((X)->verbose) \ printf (_("removed directory: %s\n"), \ quote (full_filename (Filename))); \ return RM_OK; \ } \ \ if (errno == ENOENT && (X)->ignore_missing_files) \ return RM_OK; \ \ if (errno == ENOTEMPTY || errno == EEXIST) \ return RM_NONEMPTY_DIR; \ } \ while (0) /* Remove the file or directory specified by FILENAME. Return RM_OK if it is removed, and RM_ERROR or RM_USER_DECLINED if not. But if FILENAME specifies a non-empty directory, return RM_NONEMPTY_DIR. */ static enum RM_status remove_entry (Dirstack_state const *ds, char const *filename, struct rm_options const *x, struct dirent const *dp) { Ternary is_dir; Ternary is_empty_directory; enum RM_status s = prompt (ds, filename, x, PA_DESCEND_INTO_DIR, &is_dir, &is_empty_directory); if (s != RM_OK) return s; /* Why bother with the following #if/#else block? Because on systems with an unlink function that *can* unlink directories, we must determine the type of each entry before removing it. Otherwise, we'd risk unlinking an entire directory tree simply by unlinking a single directory; then all the storage associated with that hierarchy would not be freed until the next reboot. Not nice. To avoid that, on such slightly losing systems, we need to call lstat to determine the type of each entry, and that represents extra overhead that -- it turns out -- we can avoid on GNU-libc-based systems, since there, unlink will never remove a directory. */ #if ROOT_CAN_UNLINK_DIRS /* If we don't already know whether FILENAME is a directory, find out now. Then, if it's a non-directory, we can use unlink on it. */ if (is_dir == T_UNKNOWN) { # if HAVE_STRUCT_DIRENT_D_TYPE if (dp && dp->d_type != DT_UNKNOWN) is_dir = DT_IS_DIR (dp) ? T_YES : T_NO; else # endif { struct stat sbuf; if (lstat (filename, &sbuf)) { if (errno == ENOENT && x->ignore_missing_files) return RM_OK; error (0, errno, _("cannot lstat %s"), quote (full_filename (filename))); return RM_ERROR; } is_dir = S_ISDIR (sbuf.st_mode) ? T_YES : T_NO; } } if (is_dir == T_NO) { /* At this point, barring race conditions, FILENAME is known to be a non-directory, so it's ok to try to unlink it. */ DO_UNLINK (filename, x); /* unlink failed with some other error code. report it. */ error (0, errno, _("cannot remove %s"), quote (full_filename (filename))); return RM_ERROR; } if (! x->recursive) { error (0, EISDIR, _("cannot remove directory %s"), quote (full_filename (filename))); return RM_ERROR; } if (is_empty_directory == T_YES) { DO_RMDIR (filename, x); /* Don't diagnose any failure here. It'll be detected when the caller tries another way. */ } #else /* ! ROOT_CAN_UNLINK_DIRS */ if (is_dir == T_YES && ! x->recursive) { error (0, EISDIR, _("cannot remove directory %s"), quote (full_filename (filename))); return RM_ERROR; } /* is_empty_directory is set iff it's ok to use rmdir. Note that it's set only in interactive mode -- in which case it's an optimization that arranges so that the user is asked just once whether to remove the directory. */ if (is_empty_directory == T_YES) DO_RMDIR (filename, x); /* If we happen to know that FILENAME is a directory, return now and let the caller remove it -- this saves the overhead of a failed unlink call. If FILENAME is a command-line argument, then dp is NULL, so we'll first try to unlink it. Using unlink here is ok, because it cannot remove a directory. */ if ((dp && DT_IS_DIR (dp)) || is_dir == T_YES) return RM_NONEMPTY_DIR; DO_UNLINK (filename, x); /* Accept either EISDIR or EPERM as an indication that FILENAME may be a directory. POSIX says that unlink must set errno to EPERM when it fails to remove a directory, while Linux-2.4.18 sets it to EISDIR. */ if ((errno != EISDIR && errno != EPERM) || ! x->recursive) { /* some other error code. Report it and fail. Likewise, if we're trying to remove a directory without the --recursive option. */ error (0, errno, _("cannot remove %s"), quote (full_filename (filename))); return RM_ERROR; } #endif return RM_NONEMPTY_DIR; } /* Remove entries in `.', the current working directory (cwd). Upon finding a directory that is both non-empty and that can be chdir'd into, return RM_OK and set *SUBDIR and fill in SUBDIR_SB, where SUBDIR is the malloc'd name of the subdirectory if the chdir succeeded, NULL otherwise (e.g., if opendir failed or if there was no subdirectory). Likewise, SUBDIR_SB is the result of calling lstat on SUBDIR. Return RM_OK if all entries are removed. Return RM_ERROR if any entry cannot be removed. Otherwise, return RM_USER_DECLINED if the user declines to remove at least one entry. Remove as much as possible, continuing even if we fail to remove some entries. */ static enum RM_status remove_cwd_entries (Dirstack_state *ds, char **subdir, struct stat *subdir_sb, struct rm_options const *x) { DIR *dirp = opendir ("."); struct AD_ent *top = AD_stack_top (ds); enum RM_status status = top->status; assert (VALID_STATUS (status)); *subdir = NULL; if (dirp == NULL) { if (errno != ENOENT || !x->ignore_missing_files) { error (0, errno, _("cannot open directory %s"), quote (full_filename ("."))); return RM_ERROR; } } while (1) { struct dirent *dp; enum RM_status tmp_status; const char *f; /* Set errno to zero so we can distinguish between a readdir failure and when readdir simply finds that there are no more entries. */ errno = 0; if ((dp = readdir (dirp)) == NULL) { if (errno) { /* Save/restore errno across closedir call. */ int e = errno; closedir (dirp); errno = e; /* Arrange to give a diagnostic after exiting this loop. */ dirp = NULL; } break; } f = dp->d_name; if (DOT_OR_DOTDOT (f)) continue; /* Skip files we've already tried/failed to remove. */ if ( ! AD_is_removable (ds, f)) continue; /* Pass dp->d_type info to remove_entry so the non-glibc case can decide whether to use unlink or chdir. Systems without the d_type member will have to endure the performance hit of first calling lstat F. */ tmp_status = remove_entry (ds, f, x, dp); switch (tmp_status) { case RM_OK: /* do nothing */ break; case RM_ERROR: case RM_USER_DECLINED: AD_mark_as_unremovable (ds, f); UPDATE_STATUS (status, tmp_status); break; case RM_NONEMPTY_DIR: { /* Save a copy of errno, in case the preceding unlink (from remove_entry's DO_UNLINK) of a non-directory failed due to EPERM. */ int saved_errno = errno; /* Record dev/ino of F so that we can compare that with dev/ino of `.' after the chdir. This dev/ino pair is also used in cycle detection. */ if (lstat (f, subdir_sb)) error (EXIT_FAILURE, errno, _("cannot lstat %s"), quote (full_filename (f))); if (chdir (f)) { /* It is much more common that we reach this point for an inaccessible directory. Hence the second diagnostic, below. However it is also possible that F is a non-directory. That can happen when we use the `! ROOT_CAN_UNLINK_DIRS' block of code and when DO_UNLINK fails due to EPERM. In that case, give a better diagnostic. */ if (errno == ENOTDIR) error (0, saved_errno, _("cannot remove %s"), quote (full_filename (f))); else error (0, errno, _("cannot chdir from %s to %s"), quote_n (0, full_filename (".")), quote_n (1, f)); AD_mark_as_unremovable (ds, f); status = RM_ERROR; break; } if (cycle_check (&ds->cycle_check_state, subdir_sb)) { error (0, 0, _("\ WARNING: Circular directory structure.\n\ This almost certainly means that you have a corrupted file system.\n\ NOTIFY YOUR SYSTEM MANAGER.\n\ The following directory is part of the cycle:\n %s\n"), quote (full_filename ("."))); longjmp (ds->current_arg_jumpbuf, 1); } *subdir = xstrdup (f); break; } } /* Record status for this directory. */ UPDATE_STATUS (top->status, status); if (*subdir) break; } if (dirp == NULL || CLOSEDIR (dirp) != 0) { /* Note that this diagnostic serves for both readdir and closedir failures. */ error (0, errno, _("reading directory %s"), quote (full_filename ("."))); status = RM_ERROR; } return status; } /* Do this after each call to AD_push or AD_push_initial. Because the status = RM_OK bit is too remove-specific to go into the general-purpose AD_* package. */ #define AD_INIT_OTHER_MEMBERS() \ do \ { \ AD_stack_top(ds)->status = RM_OK; \ } \ while (0) /* Remove the hierarchy rooted at DIR. Do that by changing into DIR, then removing its contents, then returning to the original working directory and removing DIR itself. Don't use recursion. Be careful when using chdir ".." that we return to the same directory from which we came, if necessary. Return 1 for success, 0 if some file cannot be removed or if a chdir fails. If the working directory cannot be restored, exit immediately. */ static enum RM_status remove_dir (Dirstack_state *ds, char const *dir, struct saved_cwd **cwd_state, struct rm_options const *x) { enum RM_status status; struct stat dir_sb; /* Save any errno (from caller's failed remove_entry call), in case DIR is not a directory, so that we can give a reasonable diagnostic. */ int saved_errno = errno; if (*cwd_state == NULL) { *cwd_state = XMALLOC (struct saved_cwd, 1); if (save_cwd (*cwd_state)) return RM_ERROR; AD_push_initial (ds, *cwd_state); AD_INIT_OTHER_MEMBERS (); } /* There is a race condition in that an attacker could replace the nonempty directory, DIR, with a symlink between the preceding call to rmdir (in our caller) and the chdir below. However, the following lstat, along with the `stat (".",...' and dev/ino comparison in AD_push ensure that we detect it and fail. */ if (lstat (dir, &dir_sb)) { error (0, errno, _("cannot lstat %s"), quote (full_filename (dir))); return RM_ERROR; } if (chdir (dir)) { if (! S_ISDIR (dir_sb.st_mode)) { /* This happens on Linux-2.4.18 when a non-privileged user tries to delete a file that is owned by another user in a directory like /tmp that has the S_ISVTX flag set. */ assert (saved_errno == EPERM); error (0, saved_errno, _("cannot remove %s"), quote (full_filename (dir))); } else { error (0, errno, _("cannot chdir from %s to %s"), quote_n (0, full_filename (".")), quote_n (1, dir)); } return RM_ERROR; } AD_push (ds, dir, &dir_sb); AD_INIT_OTHER_MEMBERS (); status = RM_OK; while (1) { char *subdir = NULL; struct stat subdir_sb; enum RM_status tmp_status = remove_cwd_entries (ds, &subdir, &subdir_sb, x); if (tmp_status != RM_OK) { UPDATE_STATUS (status, tmp_status); AD_mark_current_as_unremovable (ds); } if (subdir) { AD_push (ds, subdir, &subdir_sb); AD_INIT_OTHER_MEMBERS (); free (subdir); continue; } /* Execution reaches this point when we've removed the last removable entry from the current directory. */ { char *d = AD_pop_and_chdir (ds); /* Try to remove D only if remove_cwd_entries succeeded. */ if (tmp_status == RM_OK) { /* This does a little more work than necessary when it actually prompts the user. E.g., we already know that D is a directory and that it's almost certainly empty, yet we lstat it. But that's no big deal since we're interactive. */ Ternary is_dir; Ternary is_empty; enum RM_status s = prompt (ds, d, x, PA_REMOVE_DIR, &is_dir, &is_empty); if (s != RM_OK) { free (d); return s; } if (rmdir (d) == 0) { if (x->verbose) printf (_("removed directory: %s\n"), quote (full_filename (d))); } else { error (0, errno, _("cannot remove directory %s"), quote (full_filename (d))); AD_mark_as_unremovable (ds, d); status = RM_ERROR; UPDATE_STATUS (AD_stack_top(ds)->status, status); } } free (d); if (AD_stack_height (ds) == 1) break; } } return status; } /* Remove the file or directory specified by FILENAME. Return RM_OK if it is removed, and RM_ERROR or RM_USER_DECLINED if not. On input, the first time this function is called, CWD_STATE should be the address of a NULL pointer. Do not modify it for any subsequent calls. On output, it is either that same NULL pointer or the address of a malloc'd `struct saved_cwd' that may be freed. */ static enum RM_status rm_1 (Dirstack_state *ds, char const *filename, struct rm_options const *x, struct saved_cwd **cwd_state) { char *base = base_name (filename); enum RM_status status; if (DOT_OR_DOTDOT (base)) { error (0, 0, _("cannot remove `.' or `..'")); return RM_ERROR; } status = remove_entry (ds, filename, x, NULL); if (status != RM_NONEMPTY_DIR) return status; return remove_dir (ds, filename, cwd_state, x); } /* Remove all files and/or directories specified by N_FILES and FILE. Apply the options in X. */ enum RM_status rm (size_t n_files, char const *const *file, struct rm_options const *x) { struct saved_cwd *cwd_state = NULL; Dirstack_state *ds; /* Put the following two variables in static storage, so they can't be clobbered by the potential longjmp into this function. */ static enum RM_status status = RM_OK; static size_t i; ds = ds_init (); for (i = 0; i < n_files; i++) { enum RM_status s; cycle_check_init (&ds->cycle_check_state); /* In the event that rm_1->remove_dir->remove_cwd_entries detects a directory cycle, arrange to fail, give up on this FILE, but continue on with any other arguments. */ if (setjmp (ds->current_arg_jumpbuf)) s = RM_ERROR; else s = rm_1 (ds, file[i], x, &cwd_state); assert (VALID_STATUS (s)); UPDATE_STATUS (status, s); } ds_free (ds); XFREE (cwd_state); return status; }
/* `rm' file deletion utility for GNU. Copyright (C) 88, 90, 91, 1994-2003 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Rubin, David MacKenzie, and Richard Stallman. Reworked to use chdir and avoid recursion by Jim Meyering. */ /* Implementation overview: In the `usual' case, RM saves no state for directories it is processing. When a removal fails (either due to an error or to an interactive `no' reply), the failure is noted (see description of `ht' in remove.c's remove_cwd_entries function) so that when/if the containing directory is reopened, RM doesn't try to remove the entry again. RM may delete arbitrarily deep hierarchies -- even ones in which file names (from root to leaf) are longer than the system-imposed maximum. It does this by using chdir to change to each directory in turn before removing the entries in that directory. RM detects directory cycles lazily. See lib/cycle-check.c. RM is careful to avoid forming full file names whenever possible. A full file name is formed only when it is about to be used -- e.g. in a diagnostic or in an interactive-mode prompt. RM minimizes the number of lstat system calls it makes. On systems that have valid d_type data in directory entries, RM makes only one lstat call per command line argument -- regardless of the depth of the hierarchy. */ #include <config.h> #include <stdio.h> #include <getopt.h> #include <sys/types.h> #include <assert.h> #include "system.h" #include "error.h" #include "remove.h" #include "save-cwd.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "rm" #define AUTHORS \ N_ ("Paul Rubin, David MacKenzie, Richard Stallman, and Jim Meyering") /* Name this program was run with. */ char *program_name; /* For long options that have no equivalent short option, use a non-character as a pseudo short option, starting with CHAR_MAX + 1. */ enum { PRESUME_INPUT_TTY_OPTION = CHAR_MAX + 1 }; static struct option const long_opts[] = { {"directory", no_argument, NULL, 'd'}, {"force", no_argument, NULL, 'f'}, {"interactive", no_argument, NULL, 'i'}, /* This is solely for testing. Do not document. */ /* It is relatively difficult to ensure that there is a tty on stdin. Since rm acts differently depending on that, without this option, it'd be harder to test the parts of rm that depend on that setting. */ {"presume-input-tty", no_argument, NULL, PRESUME_INPUT_TTY_OPTION}, {"recursive", no_argument, NULL, 'r'}, {"verbose", no_argument, NULL, 'v'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("Usage: %s [OPTION]... FILE...\n"), program_name); fputs (_("\ Remove (unlink) the FILE(s).\n\ \n\ -d, --directory unlink FILE, even if it is a non-empty directory\n\ (super-user only)\n\ -f, --force ignore nonexistent files, never prompt\n\ -i, --interactive prompt before any removal\n\ -r, -R, --recursive remove the contents of directories recursively\n\ -v, --verbose explain what is being done\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); printf (_("\ \n\ To remove a file whose name starts with a `-', for example `-foo',\n\ use one of these commands:\n\ %s -- -foo\n\ \n\ %s ./-foo\n\ "), program_name, program_name); fputs (_("\ \n\ Note that if you use rm to remove a file, it is usually possible to recover\n\ the contents of that file. If you want more assurance that the contents are\n\ truly unrecoverable, consider using shred.\n\ "), stdout); printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); } exit (status); } static void rm_option_init (struct rm_options *x) { x->unlink_dirs = 0; x->ignore_missing_files = 0; x->interactive = 0; x->recursive = 0; x->stdin_tty = isatty (STDIN_FILENO); x->verbose = 0; } int main (int argc, char **argv) { struct rm_options x; int fail = 0; int c; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); rm_option_init (&x); while ((c = getopt_long (argc, argv, "dfirvR", long_opts, NULL)) != -1) { switch (c) { case 0: /* Long option. */ break; case 'd': x.unlink_dirs = 1; break; case 'f': x.interactive = 0; x.ignore_missing_files = 1; break; case 'i': x.interactive = 1; x.ignore_missing_files = 0; break; case 'r': case 'R': x.recursive = 1; break; case PRESUME_INPUT_TTY_OPTION: x.stdin_tty = 1; break; case 'v': x.verbose = 1; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_FAILURE); } } if (optind == argc) { if (x.ignore_missing_files) exit (EXIT_SUCCESS); else { error (0, 0, _("too few arguments")); usage (EXIT_FAILURE); } } { size_t n_files = argc - optind; char const *const *file = (char const *const *) argv + optind; enum RM_status status = rm (n_files, file, &x); assert (VALID_STATUS (status)); if (status == RM_ERROR) fail = 1; } exit (fail); }
#ifndef REMOVE_H # define REMOVE_H struct rm_options { /* If nonzero, ignore nonexistent files. */ int ignore_missing_files; /* If nonzero, query the user about whether to remove each file. */ int interactive; /* If nonzero, recursively remove directories. */ int recursive; /* If nonzero, stdin is a tty. */ int stdin_tty; /* If nonzero, remove directories with unlink instead of rmdir, and don't require a directory to be empty before trying to unlink it. Only works for the super-user. */ int unlink_dirs; /* If nonzero, display the name of each file removed. */ int verbose; }; enum RM_status { /* These must be listed in order of increasing seriousness. */ RM_OK = 2, RM_USER_DECLINED, RM_ERROR, RM_NONEMPTY_DIR }; # define VALID_STATUS(S) \ ((S) == RM_OK || (S) == RM_USER_DECLINED || (S) == RM_ERROR) # define UPDATE_STATUS(S, New_value) \ do \ { \ if ((New_value) == RM_ERROR \ || ((New_value) == RM_USER_DECLINED && (S) == RM_OK)) \ (S) = (New_value); \ } \ while (0) enum RM_status rm (size_t n_files, char const *const *file, struct rm_options const *x); #endif
#ifndef SAVE_CWD_H # define SAVE_CWD_H 1 struct saved_cwd { int desc; char *name; }; int save_cwd (struct saved_cwd *cwd); int restore_cwd (const struct saved_cwd *cwd); void free_cwd (struct saved_cwd *cwd); #endif /* SAVE_CWD_H */
#include <config.h> #include <stdio.h> #include <sys/types.h> #include "system.h" #include "checksum.h" int algorithm = ALG_SHA1;
dafe043ca795c8bea27a6bc5cbdfbc4f59671809f6f9774b9e94831ea6405a80 /usr/bin/install 78cf8e0ee96e7428496d0fdccc60e8465f7aaf3728a463123fefb8f46bc5d073 /usr/bin/basename 70146b4ce3092cd7ccaf933d2eb6e93c1b12358240838f66a1a7743a8100e870 /usr/bin/cat c0b5b1eb102784c8202ec82990fffc4c57ba0da5724ac44544994a1d5a50615f /usr/bin/chmod 113910e58f8bb9506f5fd3c5394e7792b47bf9c7e7479941882c1368481b2e40 /usr/bin/cksum 2f666cac19e38d7653975e68485ade9f6a4bb9414fa9364716414b294f3720d1 /usr/bin/cp 27f6fe3aa713d62cd9367ccafb2c52029f1f87e2d07cbadf7b71023240149fda /usr/bin/csplit 11068650333e2c0af8e9c8abb79c3d5c8fbe261b5a72b454ca632ad3ec5b965b /usr/bin/cut 7f5afda1460b2148f8b362bc37e252caf6bf02df10aab02a21f5581295921b97 /usr/bin/dirname ecbbb0202e9b8274c93e41d831eddc809f7714c6e8004136e2fc7f1a0a30e799 /usr/bin/echo a2962caac7a407f50a94c1d6496de9ca65c8ff66a3aa3e814a69f6aa3253dea2 /usr/bin/expand 61ffbe47da01a828ce0a0e2b871deb065bcaf874b831c019131d62bb5156e194 /usr/bin/expr 28c8920fb8ce6e5ed74f8be8a854688b8aa5155baf70812259c471fd84d2d716 /usr/bin/factor 1a851ce640196e1562d392f2a71cf294937513a43fd9b52e146c803722bd6c9e /usr/bin/false fda23f5d23a65799794b49b3befa8d515e1e0f221c4b67e9dead07ca4b492c4c /usr/bin/fmt 2241e52196cc6f204528c32d844361cb917031cdfe5efda90ee6f2f3b2fb6a1a /usr/bin/fold 813bc16b973c2d13b96980d22d8ad49974aac356226781868af68973ba8229a2 /usr/bin/head 7948309bd45223be43e385367a56cf5fcd30ac7c94685423e651fab2d1c0a80d /usr/bin/hostname fd55b2ed55a27de3c57dccf64c1457613a109c231dc0f8a40b650fb2f6114ab2 /usr/bin/id 9cb2877544e4dfc2521d54c3b18811dad170a6b31aeaa7556b73edcf57cb6668 /usr/bin/join a532dfb88e4226ba1600ab8eb3c7a2801b6b7bc2d50200ba70470723322babfe /usr/bin/kill 495a20a8342ab6d61806342ef61d8ad135a7c8f2bfa8a2edbd6cb5e839d577a7 /usr/bin/link 38b90400c1be7792872bbb0781627b2dba35a70abf41899e9ee19d59ef351fe9 /usr/bin/ln 323097645c46aab33711f562ff7ed1fda4609970d7e55e6ab2fd31dd1ed0bfb0 /usr/bin/logname 354547f7467df48dd0bbaf9c7f36c8168dc8a38ccfe97409847dbb50c05cba90 /usr/bin/mkfifo 2326009b57ace4bc7da598d322fcf7c2cdcd702723c6cdb81a50a1ceb0631933 /usr/bin/mkdir f964752c462185f7ea54246b4e53bca7ee84cfc4b590bef04baa8700312e2224 /usr/bin/mknod a9311865c1e95766e3be810617fce3892d5ea693fd3cdb26d680f75c579670f2 /usr/bin/nl c68b80b2dff6423f5c92ec1cf02f30c61c9560f2a3499acf617aeee7c407d7b5 /usr/bin/od 775ec8b0a5c37a7360535c1c040b5c98e6177e69f978f520ee928efab39ed519 /usr/bin/paste b45c0d13af7f9b1cad618fdcd48ec7db80d9e59acf37ced076c4a65412b3a5a7 /usr/bin/pathchk 267b1a8a23bf764f833cef10da9f0c77583234baed24bd67362a008015231ee3 /usr/bin/pr 6375dd4cb3d1326c895844fa7128d3d7c88df9da7b85830e23bc61d99e0bc186 /usr/bin/printf 9665a522e43fb6d2a7d66c217873cc9eb02936a51ff97b426214316b76a92792 /usr/bin/ptx 7af4c3a707f382730836223ce8651850e78e9aac4079cf2e0e7996cac8449011 /usr/bin/pwd 6c2fc79aef8bcb5ce79961481b6d30fb5bf3648d71e60ab1df9a75621f0471d0 /usr/bin/readlink 3249f3b8a09e6862ce1a0c09b4e805fa03f6cfef70ee81c70704f0b481b20f45 /usr/bin/rmdir 014f6c56864a02903fcddc8f681459ab455524f12ace562fb4ff50b67cd07dab /usr/bin/seq de46fe9655bad40463d8af57ec6fe0dd4d56fb4056a7fcef9e57eebf9c497959 /usr/bin/sleep a6bbb377c517f2c00652b16f8eb14e8ba54fa318faf6c90fa82ebcc84fb14986 /usr/bin/sort 7d62c06a21c4eb9989f01bacf406bffe1b7e9d69596eac4fa6373e9ce7f7cb3b /usr/bin/split 00a0f9b9dc137193e6ddb0b7dba679ee98865dfc69589bcb619de0044fdb5550 /usr/bin/sum 38b82cf9f2a4818061cabc32e78df8cc552c1384cecf669b8a221f44fb1cf06b /usr/bin/tail e2683f200a8c14eef6d92d6ee3b4fdbc48b4f1cf5ea58ccab57259f6b8a315d5 /usr/bin/tee ff9492d8e088acf1a689e907c9550ba78029a778bd2818674d4989cddbc28853 /usr/bin/tr 159651d7a72db3e4752ee184288c55a1fee0484443aa67e4f3a9984ad7320e78 /usr/bin/tsort 9f6f55239fa384564f78347b70c1287310283121446d63071045f6fd3eac90b1 /usr/bin/unexpand 58b692ef7cc3f644eca0b21cd792ac6037202788e9bcee1204ba155a750e27ca /usr/bin/uniq 1c6c0306829c5c0695d9e5336682aac3494eb78b86b0806fb8b94b4edbe9e670 /usr/bin/unlink c601ba79b716d782bd8f01e2e3804bd2a66fdc6519c7c27dc709def51558ed53 /usr/bin/wc 762c3ac63eeafd49de5f324b16b1895167110e2ddee553c6f5198c7d71880caf /usr/bin/whoami e0a3acd8a5ea91c112bc63281f54a1e56d11991f0ec3b72d5a5becaf11bf6c9a /usr/bin/tac a728386f022a00b79a683ab39f9f85a2561e110ff6e6786d304320d8ed9e4e97 /usr/bin/test 3948a25742e25552b30b018269038723d7f6eb80a4c18c2f85bb84fe13729930 /usr/bin/touch f4f039c380c8f979bd40b02fc458840db3f1086d7e7f9a209e860340a548a42b /usr/bin/true 43e5a7794084a505fdfd367d6d66e5f0b8ba3c3d843cdc6036416d49c68b79b5 /usr/bin/yes 3e187de38d3c75ef52d74c4bba8dae228dad81e81e9209e3994a6bffe8ee09c9 /usr/bin/ls 4917fb8bb8a4fb9e24b6cc27de9dd1944acef84b8f5f9b150c0aa9f87b37f6ca /usr/bin/md5sum 8c8ed60125c5b34b4d3d156baf2b4c682dee313bc795bb1b4b15427827d62ca8 /usr/bin/mv 48c1f9f1847bff208be5bf04ea5897a0c1454f5b8e748a451e755631519c85f1 /usr/bin/rm 5d717b9176ebb05216c869e7ee9711e0fdc6ac2f8dec15d9e94a2ed35832c9c9 /usr/bin/sha1sum
http://downloads.sourceforge.net/project/heirloom/heirloom-devtools/070527/heirloom-devtools-070527.tar.bz2 9f233d8b78e4351fe9dd2d50d83958a0e5af36f54e9818521458a08e058691ba
SPDX-FileCopyrightText: 2021 Andrius Å tikonas <andrius@stikonas.eu> SPDX-FileCopyrightText: 2021 fosslinux <fosslinux@aussies.space> SPDX-FileCopyrightText: 2023 Emily Trau <emily@downunderctf.com> SPDX-License-Identifier: CDDL-1.0 Remove all kinds of wchar support. Mes Libc does not support wchar in any form, so we need to remove it. heirloom-devtools is all kinds of broken in this way. C standard dictates that wchar_t may be defined as char, however heirloom-devtools does not respect this, which makes this non-trivial. diff -U3 -r yacc/dextern yacc/dextern --- yacc/dextern 2005-11-10 20:31:45.000000000 +0000 +++ yacc/dextern 2021-01-25 18:21:45.286602527 +0000 @@ -39,7 +39,6 @@ #include <ctype.h> #include <string.h> #include <stdlib.h> -#include <wctype.h> /* MANIFEST CONSTANT DEFINITIONS */ #define WORD32 @@ -166,17 +165,17 @@ } ITEM; typedef struct toksymb { - wchar_t *name; + char *name; int value; } TOKSYMB; typedef struct mbclit { - wchar_t character; + char character; int tvalue; /* token issued for the character */ } MBCLIT; typedef struct ntsymb { - wchar_t *name; + char *name; int tvalue; } NTSYMB; @@ -212,7 +211,7 @@ extern int nprod; /* number of productions */ extern int **prdptr; /* pointers to descriptions of productions */ extern int *levprd; /* contains production levels to break conflicts */ -extern wchar_t *had_act; /* set if reduction has associated action code */ +extern char *had_act; /* set if reduction has associated action code */ /* state information */ @@ -273,24 +272,15 @@ extern void go2out(void); extern void hideprod(void); extern void callopt(void); -extern void warray(wchar_t *, int *, int); -extern wchar_t *symnam(int); -extern wchar_t *writem(int *); +extern void warray(char *, int *, int); +extern char *symnam(int); +extern char *writem(int *); extern void exp_mem(int); extern void exp_act(int **); extern int apack(int *, int); extern int state(int); -extern void fprintf3(FILE *, const char *, const wchar_t *, const char *, ...); -extern void error3(const char *, const wchar_t *, const char *, ...); - - /* multibyte i/o */ - -#undef getwc -#define getwc(f) yacc_getwc(f) -extern wint_t yacc_getwc(FILE *); -#undef putwc -#define putwc(c, f) yacc_putwc(c, f) -extern wint_t yacc_putwc(wchar_t, FILE *); +extern void fprintf3(FILE *, const char *, const char *, const char *, ...); +extern void error3(const char *, const char *, const char *, ...); /* yaccpar location */ diff -U3 -r yacc/y1.c yacc/y1.c --- yacc/y1.c 2005-11-26 17:37:56.000000000 +0000 +++ yacc/y1.c 2021-01-25 18:02:31.251542365 +0000 @@ -41,12 +41,11 @@ #include <unistd.h> #include <locale.h> #include <stdarg.h> /* For error() */ -#include <wchar.h> static void mktbls(void); static void others(void); static void summary(void); -static wchar_t *chcopy(wchar_t *, wchar_t *); +static char *chcopy(char *, char *); static int setunion(int *, int *); static void prlook(LOOKSETS *); static void cpres(void); @@ -229,13 +228,13 @@ if (finput == NULL) error("cannot find parser %s", parser); - warray(L"yyr1", levprd, nprod); + warray("yyr1", levprd, nprod); aryfil(temp1, nprod, 0); /* had_act[i] is either 1 or 0 */ PLOOP(1, i) temp1[i] = ((prdptr[i+1] - prdptr[i]-2) << 1) | had_act[i]; - warray(L"yyr2", temp1, nprod); + warray("yyr2", temp1, nprod); aryfil(temp1, nstate, -10000000); TLOOP(i) @@ -244,14 +243,14 @@ NTLOOP(i) for (j = ntstates[i]; j != 0; j = mstates[j]) temp1[j] = -i; - warray(L"yychk", temp1, nstate); + warray("yychk", temp1, nstate); - warray(L"yydef", defact, nstate); + warray("yydef", defact, nstate); if ((fdebug = fopen(DEBUGNAME, "r")) == NULL) error("cannot open yacc.debug"); - while ((c = getwc(fdebug)) != EOF) - putwc(c, ftable); + while ((c = getc(fdebug)) != EOF) + putc(c, ftable); fclose(fdebug); ZAPFILE(DEBUGNAME); @@ -259,19 +258,19 @@ fprintf(ftable, "# line\t1 \"%s\"\n", parser); tmpline = 1; /* copy parser text */ - while ((c = getwc(finput)) != EOF) { + while ((c = getc(finput)) != EOF) { if (c == '\n') tmpline++; if (c == L'$') { - if ((c = getwc(finput)) != L'A') - putwc(L'$', ftable); + if ((c = getc(finput)) != L'A') + putc(L'$', ftable); else { /* copy actions */ tmpline++; faction = fopen(ACTNAME, "r"); if (faction == NULL) error("cannot open action tempfile"); - while ((c = getwc(faction)) != EOF) - putwc(c, ftable); + while ((c = getc(faction)) != EOF) + putc(c, ftable); fclose(faction); if (gen_lines) fprintf(ftable, @@ -279,18 +278,18 @@ tmpline, parser); ZAPFILE(ACTNAME); - c = getwc(finput); + c = getc(finput); } } - putwc(c, ftable); + putc(c, ftable); } fclose(ftable); } /* copies string q into p, returning next free char ptr */ -static wchar_t * -chcopy(wchar_t *p, wchar_t *q) +static char * +chcopy(char *p, char *q) { while (*p = *q++) ++p; @@ -299,16 +298,16 @@ #define ISIZE 400 /* creates output string for item pointed to by pp */ -wchar_t * +char * writem(int *pp) { int i, *p; static int isize = ISIZE; - static wchar_t *sarr = NULL; - wchar_t *q; + static char *sarr = NULL; + char *q; if (sarr == NULL) { - sarr = malloc(sizeof (wchar_t) * isize); + sarr = malloc(sizeof (char) * isize); if (sarr == NULL) error("could not allocate output string array"); for (i = 0; i < isize; ++i) @@ -317,7 +316,7 @@ for (p = pp; *p > 0; ++p) /* EMPTY */; p = prdptr[-*p]; q = chcopy(sarr, nontrst[*p-NTBASE].name); - q = chcopy(q, L" : "); + q = chcopy(q, " : "); for (;;) { *q++ = ++p == pp ? L'_' : L' '; @@ -326,7 +325,7 @@ break; q = chcopy(q, symnam(i)); while (q > &sarr[isize-30]) { - static wchar_t *sarrbase; + static char *sarrbase; sarrbase = sarr; isize += ISIZE; @@ -339,17 +338,17 @@ /* an item calling for a reduction */ if ((i = *pp) < 0) { - q = chcopy(q, L" ("); - swprintf(q, q + isize - sarr, L"%d)", -i); + q = chcopy(q, " ("); + snprintf(q, q + isize - sarr, "%d)", -i); } return (sarr); } /* return a pointer to the name of symbol i */ -wchar_t * +char * symnam(int i) { - wchar_t *cp; + char *cp; cp = (i >= NTBASE) ? nontrst[i-NTBASE].name : tokset[i].name; if (*cp == L' ') diff -U3 -r yacc/y2.c yacc/y2.c --- yacc/y2.c 2005-11-26 17:39:44.000000000 +0000 +++ yacc/y2.c 2021-01-25 19:02:52.472120036 +0000 @@ -35,9 +35,9 @@ * Sccsid @(#)y2.c 1.11 (gritter) 11/26/05 */ +#include <getopt.h> #include "dextern" #include "sgs.h" -#include <wchar.h> #include <unistd.h> #define IDENTIFIER 257 @@ -66,17 +66,17 @@ char *infile; /* input file name */ static int numbval; /* value of an input number */ static int toksize = NAMESIZE; -static wchar_t *tokname; /* input token name */ +static char *tokname; /* input token name */ char *parser = NULL; /* location of common parser */ static void finact(void); -static wchar_t *cstash(wchar_t *); +static char *cstash(char *); static void defout(void); static void cpyunion(void); static void cpycode(void); static void cpyact(int); -static void lhsfill(wchar_t *); -static void rhsfill(wchar_t *); +static void lhsfill(char *); +static void rhsfill(char *); static void lrprnt(void); static void beg_debug(void); static void end_toks(void); @@ -85,9 +85,9 @@ static void exp_prod(void); static void exp_ntok(void); static void exp_nonterm(void); -static int defin(int, wchar_t *); +static int defin(int, char *); static int gettok(void); -static int chfind(int, wchar_t *); +static int chfind(int, char *); static int skipcom(void); static int findchtok(int); static void put_prefix_define(char *); @@ -101,11 +101,11 @@ * points to initial block - more space * is allocated as needed. */ -static wchar_t cnamesblk0[CNAMSZ]; -static wchar_t *cnames = cnamesblk0; +static char cnamesblk0[CNAMSZ]; +static char *cnames = cnamesblk0; /* place where next name is to be put in */ -static wchar_t *cnamp = cnamesblk0; +static char *cnamp = cnamesblk0; /* number of defined symbols output */ static int ndefout = 3; @@ -113,7 +113,7 @@ /* storage of types */ static int defunion = 0; /* union of types defined? */ static int ntypes = 0; /* number of types defined */ -static wchar_t *typeset[NTYPES]; /* pointers to type tags */ +static char *typeset[NTYPES]; /* pointers to type tags */ /* symbol tables for tokens and nonterminals */ @@ -143,8 +143,8 @@ /* output string */ -static wchar_t *lhstext; -static wchar_t *rhstext; +static char *lhstext; +static char *rhstext; /* storage for grammar rules */ @@ -158,7 +158,7 @@ int **prdptr; int *levprd; -wchar_t *had_act; +char *had_act; /* flag for generating the # line's default is yes */ int gen_lines = 1; @@ -181,7 +181,7 @@ int c; int *p; char *cp; - wchar_t actname[8]; + char actname[8]; unsigned int options = 0; char *file_prefix = DEFAULT_PREFIX; char *sym_prefix = ""; @@ -192,16 +192,16 @@ fdefine = NULL; i = 1; - tokname = malloc(sizeof (wchar_t) * toksize); + tokname = malloc(sizeof (char) * toksize); tokset = malloc(sizeof (TOKSYMB) * ntoksz); toklev = malloc(sizeof (int) * ntoksz); nontrst = malloc(sizeof (NTSYMB) * nnontersz); mem0 = malloc(sizeof (int) * new_memsize); prdptr = malloc(sizeof (int *) * (nprodsz+2)); levprd = malloc(sizeof (int) * (nprodsz+2)); - had_act = calloc(nprodsz + 2, sizeof (wchar_t)); - lhstext = malloc(sizeof (wchar_t) * LHS_TEXT_LEN); - rhstext = malloc(sizeof (wchar_t) * RHS_TEXT_LEN); + had_act = calloc(nprodsz + 2, sizeof (char)); + lhstext = malloc(sizeof (char) * LHS_TEXT_LEN); + rhstext = malloc(sizeof (char) * RHS_TEXT_LEN); aryfil(toklev, ntoksz, 0); aryfil(levprd, nprodsz, 0); for (ii = 0; ii < ntoksz; ++ii) @@ -307,10 +307,10 @@ lineno = 1; cnamp = cnames; - defin(0, L"$end"); + defin(0, "$end"); extval = 0400; - defin(0, L"error"); - defin(1, L"$accept"); + defin(0, "error"); + defin(1, "$accept"); mem = mem0; lev = 0; ty = 0; @@ -588,7 +588,7 @@ /* process a rule */ if (t == L'|') { - rhsfill((wchar_t *)0); /* restart fill of rhs */ + rhsfill((char *)0); /* restart fill of rhs */ *mem = *prdptr[nprod-1]; if (++mem >= &tracemem[new_memsize]) exp_mem(1); @@ -638,8 +638,8 @@ /* action within rule... */ lrprnt(); /* dump lhs, rhs */ - swprintf(actname, sizeof actname, - L"$$%d", nprod); + snprintf(actname, sizeof actname, + "$$%d", nprod); /* * make it nonterminal */ @@ -722,8 +722,8 @@ if (gen_lines) fprintf(ftable, "\n# line %d \"%s\"\n", lineno, infile); - while ((c = getwc(finput)) != EOF) - putwc(c, ftable); + while ((c = getc(finput)) != EOF) + putc(c, ftable); } fclose(finput); } @@ -736,15 +736,15 @@ fprintf(ftable, "# define YYERRCODE %d\n", tokset[2].value); } -static wchar_t * +static char * cstash(s) -register wchar_t *s; +register char *s; { - wchar_t *temp; + char *temp; static int used = 0; static int used_save = 0; static int exp_cname = CNAMSZ; - int len = wcslen(s); + int len = strlen(s); /* * 2/29/88 - @@ -755,7 +755,7 @@ exp_cname += CNAMSZ; if (!used) free(cnames); - if ((cnames = malloc(sizeof (wchar_t)*exp_cname)) == NULL) + if ((cnames = malloc(sizeof (char)*exp_cname)) == NULL) error("cannot expand string dump"); cnamp = cnames; used = 0; @@ -770,7 +770,7 @@ } static int -defin(int t, register wchar_t *s) +defin(int t, register char *s) { /* define s to be a terminal if t=0 or a nonterminal if t=1 */ @@ -820,7 +820,7 @@ } else if (s[2] <= L'7' && s[2] >= L'0') { /* \nnn sequence */ int i = 3; val = s[2] - L'0'; - while (iswdigit(s[i]) && i <= 4) { + while (isdigit(s[i]) && i <= 4) { if (s[i] >= L'0' && s[i] <= L'7') val = val * 8 + s[i] - L'0'; else @@ -831,17 +831,17 @@ error("illegal \\nnn construction"); if (val > 255) error( -"\\nnn exceed \\377; use \\xnnnnnnnn for wchar_t value of multibyte char"); +"\\nnn exceed \\377; use \\xnnnnnnnn for char value of multibyte char"); if (val == 0 && i >= 4) error("'\\000' is illegal"); } else if (s[2] == L'x') { /* hexadecimal \xnnn sequence */ int i = 3; val = 0; warning(1, "\\x is ANSI C hex escape"); - if (iswxdigit(s[i])) - while (iswxdigit(s[i])) { + if (isxdigit(s[i])) + while (isxdigit(s[i])) { int tmpval; - if (iswdigit(s[i])) + if (isdigit(s[i])) tmpval = s[i] - L'0'; else if (s[i] >= L'a') tmpval = s[i] - L'a' + 10; @@ -876,7 +876,7 @@ /* write out the defines (at the end of the declaration section) */ register int i, c; - register wchar_t *cp; + register char *cp; for (i = ndefout; i <= ntokens; ++i) { @@ -889,8 +889,8 @@ } for (; (c = *cp) != 0; ++cp) { - if (iswlower(c) || iswupper(c) || - iswdigit(c) || c == L'_') /* EMPTY */; + if (islower(c) || isupper(c) || + isdigit(c) || c == L'_') /* EMPTY */; else goto nodef; } @@ -919,14 +919,14 @@ reserve = 0; lineno += peekline; peekline = 0; - c = getwc(finput); + c = getc(finput); /* * while (c == ' ' || c == '\n' || c == '\t' || c == '\f') { */ - while (iswspace(c)) { + while (isspace(c)) { if (c == L'\n') ++lineno; - c = getwc(finput); + c = getc(finput); } if (c == L'/') { /* skip comment */ lineno += skipcom(); @@ -938,11 +938,11 @@ case EOF: return (ENDFILE); case L'{': - ungetwc(c, finput); + ungetc(c, finput); return (L'='); /* action ... */ case L'<': /* get, and look up, a type name (union member name) */ i = 0; - while ((c = getwc(finput)) != L'>' && + while ((c = getc(finput)) != L'>' && c != EOF && c != L'\n') { tokname[i] = c; if (++i >= toksize) @@ -954,7 +954,7 @@ if (i == 0) error("missing type name in < ... > clause"); for (i = 1; i <= ntypes; ++i) { - if (!wcscmp(typeset[i], tokname)) { + if (!strcmp(typeset[i], tokname)) { numbval = i; return (TYPENAME); } @@ -968,11 +968,11 @@ tokname[0] = L' '; i = 1; for (;;) { - c = getwc(finput); + c = getc(finput); if (c == L'\n' || c == EOF) error("illegal or missing ' or \""); if (c == L'\\') { - c = getwc(finput); + c = getc(finput); tokname[i] = L'\\'; if (++i >= toksize) exp_tokname(); @@ -986,7 +986,7 @@ case L'%': case L'\\': - switch (c = getwc(finput)) { + switch (c = getc(finput)) { case L'0': return (TERM); case L'<': return (LEFT); @@ -1001,81 +1001,81 @@ default: - if (iswdigit(c)) { /* number */ + if (isdigit(c)) { /* number */ numbval = c - L'0'; base = (c == L'0') ? 8 : 10; - for (c = getwc(finput); - iswdigit(c); - c = getwc(finput)) { + for (c = getc(finput); + isdigit(c); + c = getc(finput)) { numbval = numbval*base + c - L'0'; } - ungetwc(c, finput); + ungetc(c, finput); return (NUMBER); - } else if (iswlower(c) || iswupper(c) || + } else if (islower(c) || isupper(c) || c == L'_' || c == L'.' || c == L'$') { i = 0; - while (iswlower(c) || iswupper(c) || - iswdigit(c) || c == L'_' || + while (islower(c) || isupper(c) || + isdigit(c) || c == L'_' || c == L'.' || c == L'$') { tokname[i] = c; - if (reserve && iswupper(c)) - tokname[i] = towlower(c); + if (reserve && isupper(c)) + tokname[i] = tolower(c); if (++i >= toksize) exp_tokname(); - c = getwc(finput); + c = getc(finput); } } else return (c); - ungetwc(c, finput); + ungetc(c, finput); } tokname[i] = 0; if (reserve) { /* find a reserved word */ - if (!wcscmp(tokname, L"term")) + if (!strcmp(tokname, "term")) return (TERM); - if (!wcscmp(tokname, L"token")) + if (!strcmp(tokname, "token")) return (TERM); - if (!wcscmp(tokname, L"left")) + if (!strcmp(tokname, "left")) return (LEFT); - if (!wcscmp(tokname, L"nonassoc")) + if (!strcmp(tokname, "nonassoc")) return (BINARY); - if (!wcscmp(tokname, L"binary")) + if (!strcmp(tokname, "binary")) return (BINARY); - if (!wcscmp(tokname, L"right")) + if (!strcmp(tokname, "right")) return (RIGHT); - if (!wcscmp(tokname, L"prec")) + if (!strcmp(tokname, "prec")) return (PREC); - if (!wcscmp(tokname, L"start")) + if (!strcmp(tokname, "start")) return (START); - if (!wcscmp(tokname, L"type")) + if (!strcmp(tokname, "type")) return (TYPEDEF); - if (!wcscmp(tokname, L"union")) + if (!strcmp(tokname, "union")) return (UNION); error("invalid escape, or illegal reserved word: %ls", tokname); } /* look ahead to distinguish IDENTIFIER from C_IDENTIFIER */ - c = getwc(finput); + c = getc(finput); /* * while (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '/') * { */ - while (iswspace(c) || c == L'/') { + while (isspace(c) || c == L'/') { if (c == L'\n') { ++peekline; } else if (c == L'/') { /* look for comments */ peekline += skipcom(); } - c = getwc(finput); + c = getc(finput); } if (c == L':') return (C_IDENTIFIER); - ungetwc(c, finput); + ungetc(c, finput); return (IDENTIFIER); } @@ -1096,19 +1096,19 @@ } static int -chfind(int t, register wchar_t *s) +chfind(int t, register char *s) { int i; if (s[0] == ' ') t = 0; TLOOP(i) { - if (!wcscmp(s, tokset[i].name)) { + if (!strcmp(s, tokset[i].name)) { return (i); } } NTLOOP(i) { - if (!wcscmp(s, nontrst[i].name)) { + if (!strcmp(s, nontrst[i].name)) { return (i + NTBASE); } } @@ -1137,11 +1137,11 @@ level = 0; for (;;) { - if ((c = getwc(finput)) == EOF) + if ((c = getc(finput)) == EOF) error("EOF encountered while processing %%union"); - putwc(c, ftable); + putc(c, ftable); if (fdefine) - putwc(c, fdefine); + putc(c, fdefine); switch (c) { @@ -1172,29 +1172,29 @@ /* copies code between \{ and \} */ int c; - c = getwc(finput); + c = getc(finput); if (c == L'\n') { - c = getwc(finput); + c = getc(finput); lineno++; } if (gen_lines) fprintf(ftable, "\n# line %d \"%s\"\n", lineno, infile); while (c != EOF) { if (c == L'\\') { - if ((c = getwc(finput)) == L'}') + if ((c = getc(finput)) == L'}') return; else - putwc(L'\\', ftable); + putc(L'\\', ftable); } else if (c == L'%') { - if ((c = getwc(finput)) == L'}') + if ((c = getc(finput)) == L'}') return; else - putwc(L'%', ftable); + putc(L'%', ftable); } - putwc(c, ftable); + putc(c, ftable); if (c == L'\n') ++lineno; - c = getwc(finput); + c = getc(finput); } error("eof before %%}"); } @@ -1207,17 +1207,17 @@ /* skipcom is called after reading a / */ - if (getwc(finput) != L'*') + if (getc(finput) != L'*') error("illegal comment"); - c = getwc(finput); + c = getc(finput); while (c != EOF) { while (c == L'*') { - if ((c = getwc(finput)) == L'/') + if ((c = getc(finput)) == L'/') return (i); } if (c == L'\n') ++i; - c = getwc(finput); + c = getc(finput); } error("EOF inside comment"); /* NOTREACHED */ @@ -1229,7 +1229,7 @@ { /* copy C action to the next ; or closing } */ int brac, c, match, i, t, j, s, tok, argument, m; - wchar_t id_name[NAMESIZE+1]; + char id_name[NAMESIZE+1]; int id_idx = 0; if (gen_lines) { @@ -1239,12 +1239,12 @@ brac = 0; id_name[0] = 0; loop: - c = getwc(finput); + c = getc(finput); swt: switch (c) { case L';': if (brac == 0) { - putwc(c, faction); + putc(c, faction); return; } goto lcopy; @@ -1255,13 +1255,13 @@ s = 1; tok = -1; argument = 1; - while ((c = getwc(finput)) == L' ' || c == L'\t') /* EMPTY */; + while ((c = getc(finput)) == L' ' || c == L'\t') /* EMPTY */; if (c == L'<') { /* type description */ - ungetwc(c, finput); + ungetc(c, finput); if (gettok() != TYPENAME) error("bad syntax on $<ident> clause"); tok = numbval; - c = getwc(finput); + c = getc(finput); } if (c == L'$') { fprintf(faction, "yyval"); @@ -1272,10 +1272,10 @@ } goto loop; } - if (iswalpha(c)) { + if (isalpha(c)) { int same = 0; int id_sw = 0; - ungetwc(c, finput); + ungetc(c, finput); if (gettok() != IDENTIFIER) error("bad action format"); /* @@ -1290,16 +1290,16 @@ id_sw = 1; else id_sw = 0; - while ((c = getwc(finput)) == L' ' || + while ((c = getc(finput)) == L' ' || c == L'\t') /* EMPTY */; if (c == L'#') { - while ((c = getwc(finput)) == L' ' || + while ((c = getc(finput)) == L' ' || c == L'\t') /* EMPTY */; - if (iswdigit(c)) { + if (isdigit(c)) { m = 0; - while (iswdigit(c)) { + while (isdigit(c)) { m = m*10+c-L'0'; - c = getwc(finput); + c = getc(finput); } argument = m; } else @@ -1339,13 +1339,13 @@ } if (c == '-') { s = -s; - c = getwc(finput); + c = getc(finput); } - if (iswdigit(c)) { + if (isdigit(c)) { j = 0; - while (iswdigit(c)) { + while (isdigit(c)) { j = j*10 + c - L'0'; - c = getwc(finput); + c = getc(finput); } j = j*s - offset; if (j > 0) { @@ -1363,51 +1363,51 @@ } goto swt; } - putwc(L'$', faction); + putc(L'$', faction); if (s < 0) - putwc(L'-', faction); + putc(L'-', faction); goto swt; case L'}': if (--brac) goto lcopy; - putwc(c, faction); + putc(c, faction); return; case L'/': /* look for comments */ - putwc(c, faction); - c = getwc(finput); + putc(c, faction); + c = getc(finput); if (c != L'*') goto swt; /* it really is a comment */ - putwc(c, faction); - c = getwc(finput); + putc(c, faction); + c = getc(finput); while (c != EOF) { while (c == L'*') { - putwc(c, faction); - if ((c = getwc(finput)) == L'/') + putc(c, faction); + if ((c = getc(finput)) == L'/') goto lcopy; } - putwc(c, faction); + putc(c, faction); if (c == L'\n') ++lineno; - c = getwc(finput); + c = getc(finput); } error("EOF inside comment"); /* FALLTHRU */ case L'\'': /* character constant */ case L'"': /* character string */ match = c; - putwc(c, faction); - while ((c = getwc(finput)) != EOF) { + putc(c, faction); + while ((c = getc(finput)) != EOF) { if (c == L'\\') { - putwc(c, faction); - c = getwc(finput); + putc(c, faction); + c = getc(finput); if (c == L'\n') ++lineno; } else if (c == match) goto lcopy; else if (c == L'\n') error("newline in string or char. const."); - putwc(c, faction); + putc(c, faction); } error("EOF in string or character constant"); /* FALLTHRU */ @@ -1419,7 +1419,7 @@ goto lcopy; } lcopy: - putwc(c, faction); + putc(c, faction); /* * Save the possible identifier name. * Used to print out a warning message. @@ -1434,7 +1434,7 @@ * If c has a possibility to be a * part of identifier, save it. */ - else if (iswalnum(c) || c == L'_') { + else if (isalnum(c) || c == L'_') { id_name[id_idx++] = c; id_name[id_idx] = 0; } else { @@ -1446,28 +1446,28 @@ static void lhsfill(s) /* new rule, dump old (if exists), restart strings */ -wchar_t *s; +char *s; { static int lhs_len = LHS_TEXT_LEN; - int s_lhs = wcslen(s); + int s_lhs = strlen(s); if (s_lhs >= lhs_len) { lhs_len = s_lhs + 2; - lhstext = realloc(lhstext, sizeof (wchar_t)*lhs_len); + lhstext = realloc(lhstext, sizeof (char)*lhs_len); if (lhstext == NULL) error("couldn't expanded LHS length"); } rhsfill(NULL); - wcscpy(lhstext, s); /* don't worry about too long of a name */ + strcpy(lhstext, s); /* don't worry about too long of a name */ } static void -rhsfill(wchar_t *s) /* either name or 0 */ +rhsfill(char *s) /* either name or 0 */ { - static wchar_t *loc; /* next free location in rhstext */ + static char *loc; /* next free location in rhstext */ static int rhs_len = RHS_TEXT_LEN; static int used = 0; - int s_rhs = (s == NULL ? 0 : wcslen(s)); - register wchar_t *p; + int s_rhs = (s == NULL ? 0 : strlen(s)); + register char *p; if (!s) /* print out and erase old text */ { @@ -1481,10 +1481,10 @@ used = loc - rhstext; if ((s_rhs + 3) >= (rhs_len - used)) { - static wchar_t *textbase; + static char *textbase; textbase = rhstext; rhs_len += s_rhs + RHS_TEXT_LEN; - rhstext = realloc(rhstext, sizeof (wchar_t)*rhs_len); + rhstext = realloc(rhstext, sizeof (char)*rhs_len); if (rhstext == NULL) error("couldn't expanded RHS length"); loc = loc - textbase + rhstext; @@ -1508,15 +1508,15 @@ static void lrprnt (void) /* print out the left and right hand sides */ { - wchar_t *rhs; - wchar_t *m_rhs = NULL; + char *rhs; + char *m_rhs = NULL; if (!*rhstext) /* empty rhs - print usual comment */ - rhs = L" /* empty */"; + rhs = " /* empty */"; else { int idx1; /* tmp idx used to find if there are d_quotes */ int idx2; /* tmp idx used to generate escaped string */ - wchar_t *p; + char *p; /* * Check if there are any double quote in RHS. */ @@ -1525,8 +1525,8 @@ /* * A double quote is found. */ - idx2 = wcslen(rhstext)*2; - p = m_rhs = malloc((idx2 + 1)*sizeof (wchar_t)); + idx2 = strlen(rhstext)*2; + p = m_rhs = malloc((idx2 + 1)*sizeof (char)); if (m_rhs == NULL) error( "Couldn't allocate memory for RHS."); @@ -1617,7 +1617,7 @@ exp_tokname(void) { toksize += NAMESIZE; - tokname = realloc(tokname, sizeof (wchar_t) * toksize); + tokname = realloc(tokname, sizeof (char) * toksize); } @@ -1633,7 +1633,7 @@ prdptr = realloc(prdptr, sizeof (int *) * (nprodsz+2)); levprd = realloc(levprd, sizeof (int) * (nprodsz+2)); - had_act = realloc(had_act, sizeof (wchar_t) * (nprodsz+2)); + had_act = realloc(had_act, sizeof (char) * (nprodsz+2)); for (i = nprodsz-NPROD; i < nprodsz+2; ++i) had_act[i] = 0; diff -U3 -r yacc/y3.c yacc/y3.c --- yacc/y3.c 2005-11-26 17:37:56.000000000 +0000 +++ yacc/y3.c 2021-01-25 19:01:48.390375872 +0000 @@ -41,7 +41,7 @@ static void precftn(int, int, int); static void wract(int); static void wrstate(int); -static void wdef(wchar_t *, int); +static void wdef(char *, int); #ifndef NOLIBW static void wrmbchars(void); #endif /* !NOLIBW */ @@ -121,7 +121,7 @@ } fprintf(ftable, "\t};\n"); - wdef(L"YYNPROD", nprod); + wdef("YYNPROD", nprod); #ifndef NOLIBW if (nmbchars > 0) { wrmbchars(); @@ -485,14 +485,14 @@ } static void -wdef(wchar_t *s, int n) +wdef(char *s, int n) { /* output a definition of s to the value n */ fprintf(ftable, "# define %ls %d\n", s, n); } void -warray(wchar_t *s, int *v, int n) +warray(char *s, int *v, int n) { register int i; fprintf(ftable, "static YYCONST yytabelem %ls[]={\n", s); @@ -549,11 +549,11 @@ wrmbchars(void) { int i; - wdef(L"YYNMBCHARS", nmbchars); + wdef("YYNMBCHARS", nmbchars); qsort(mbchars, nmbchars, sizeof (*mbchars), (int (*)(const void *, const void *))cmpmbchars); fprintf(ftable, - "static struct{\n\twchar_t character;" + "static struct{\n\tchar character;" "\n\tint tvalue;\n}yymbchars[YYNMBCHARS]={\n"); for (i = 0; i < nmbchars; ++i) { fprintf(ftable, "\t{%#x,%d}", diff -U3 -r yacc/y4.c yacc/y4.c --- yacc/y4.c 2005-11-26 17:37:56.000000000 +0000 +++ yacc/y4.c 2021-01-25 19:02:06.780876425 +0000 @@ -36,7 +36,6 @@ */ #include "dextern" -#include <wchar.h> #include <unistd.h> #define NOMORE -1000 @@ -44,7 +43,7 @@ static void stin(int); static void osummary(void); static void aoutput(void); -static void arout(wchar_t *, int *, int); +static void arout(char *, int *, int); static int nxti(void); static int gtnm(void); @@ -414,13 +413,13 @@ /* write out the optimized parser */ fprintf(ftable, "# define YYLAST %d\n", maxa-amem + 1); - arout(L"yyact", amem, (maxa - amem) + 1); - arout(L"yypact", indgo, nstate); - arout(L"yypgo", pgo, nnonter + 1); + arout("yyact", amem, (maxa - amem) + 1); + arout("yypact", indgo, nstate); + arout("yypgo", pgo, nnonter + 1); } static void -arout(wchar_t *s, int *v, int n) +arout(char *s, int *v, int n) { register int i; @@ -448,8 +447,8 @@ s = 1; val = 0; - while ((c = getwc(finput)) != EOF) { - if (iswdigit(c)) + while ((c = getc(finput)) != EOF) { + if (isdigit(c)) val = val * 10 + c - L'0'; else if (c == L'-') s = -1; diff -U3 -r yacc/Makefile.mk yacc/Makefile.mk --- yacc/Makefile.mk 2007-01-04 23:31:24.000000000 +0000 +++ yacc/Makefile.mk 2021-01-25 01:25:15.592235579 +0000 @@ -1,4 +1,4 @@ -YOBJ = y1.o y2.o y3.o y4.o y5.o getopt.o +YOBJ = y1.o y2.o y3.o y4.o getopt.o LOBJ = libmai.o libzer.o
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 1993 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ /* from OpenSolaris "dextern 6.17 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)dextern 1.6 (gritter) 11/10/05 */ #include <stdio.h> #include <ctype.h> #include <string.h> #include <stdlib.h> #include <wctype.h> /* MANIFEST CONSTANT DEFINITIONS */ #define WORD32 /* base of nonterminal internal numbers */ #define NTBASE (10000000) /* internal codes for error and accept actions */ #define ERRCODE 8190 #define ACCEPTCODE 8191 /* sizes and limits */ #define ACTSIZE 4000 #define MEMSIZE 2000 #define PSTSIZE 1024 #define NSTATES 1000 #define NTERMS 127 #define NPROD 300 #define NNONTERM 600 #define TEMPSIZE 800 #define CNAMSZ 1000 #define LSETSIZE 950 #define WSETSIZE 850 #define NAMESIZE 50 #define NTYPES 1000 #define NMBCHARSZ 100 #define LKFACTOR 5 #ifdef WORD32 /* bit packing macros (may be machine dependent) */ #define BIT(a, i) ((a)[(i)>>5] & (1<<((i)&037))) #define SETBIT(a, i) ((a)[(i)>>5] |= (1<<((i)&037))) /* number of words needed to hold n+1 bits */ #define NWORDS(n) (((n)+32)/32) #else /* bit packing macros (may be machine dependent) */ #define BIT(a, i) ((a)[(i)>>4] & (1<<((i)&017))) #define SETBIT(a, i) ((a)[(i)>>4] |= (1<<((i)&017))) /* number of words needed to hold n+1 bits */ #define NWORDS(n) (((n)+16)/16) #endif /* * relationships which must hold: * TBITSET ints must hold NTERMS+1 bits... * WSETSIZE >= NNONTERM * LSETSIZE >= NNONTERM * TEMPSIZE >= NTERMS + NNONTERMs + 1 * TEMPSIZE >= NSTATES */ /* associativities */ #define NOASC 0 /* no assoc. */ #define LASC 1 /* left assoc. */ #define RASC 2 /* right assoc. */ #define BASC 3 /* binary assoc. */ /* flags for state generation */ #define DONE 0 #define MUSTDO 1 #define MUSTLOOKAHEAD 2 /* flags for a rule having an action, and being reduced */ #define ACTFLAG 04 #define REDFLAG 010 /* output parser flags */ #define YYFLAG1 (-10000000) /* macros for getting associativity and precedence levels */ #define ASSOC(i) ((i)&07) #define PLEVEL(i) (((i)>>4)&077) #define TYPE(i) ((i>>10)&077) /* macros for setting associativity and precedence levels */ #define SETASC(i, j) i |= j #define SETPLEV(i, j) i |= (j<<4) #define SETTYPE(i, j) i |= (j<<10) /* looping macros */ #define TLOOP(i) for (i = 1; i <= ntokens; ++i) #define NTLOOP(i) for (i = 0; i <= nnonter; ++i) #define PLOOP(s, i) for (i = s; i < nprod; ++i) #define SLOOP(i) for (i = 0; i < nstate; ++i) #define WSBUMP(x) ++x #define WSLOOP(s, j) for (j = s; j < &wsets[cwp]; ++j) #define ITMLOOP(i, p, q) q = pstate[i+1]; for (p = pstate[i]; p < q; ++p) #define SETLOOP(i) for (i = 0; i < tbitset; ++i) /* I/O descriptors */ extern FILE * finput; /* input file */ extern FILE * faction; /* file for saving actions */ extern FILE * fdefine; /* file for #defines */ extern FILE * ftable; /* y.tab.c file */ extern FILE * ftemp; /* tempfile to pass 2 */ extern FILE * fdebug; /* tempfile for two debugging info arrays */ extern FILE * foutput; /* y.output file */ /* structure declarations */ typedef struct looksets { int *lset; } LOOKSETS; typedef struct item { int *pitem; LOOKSETS *look; } ITEM; typedef struct toksymb { wchar_t *name; int value; } TOKSYMB; typedef struct mbclit { wchar_t character; int tvalue; /* token issued for the character */ } MBCLIT; typedef struct ntsymb { wchar_t *name; int tvalue; } NTSYMB; typedef struct wset { int *pitem; int flag; LOOKSETS ws; } WSET; /* token information */ extern int ntokens; /* number of tokens */ extern TOKSYMB *tokset; extern int ntoksz; /* * multibyte (c > 255) character literals are * handled as though they were tokens except * that it generates a separate mapping table. */ extern int nmbchars; /* number of mb literals */ extern MBCLIT *mbchars; extern int nmbcharsz; /* nonterminal information */ extern int nnonter; /* the number of nonterminals */ extern NTSYMB *nontrst; extern int nnontersz; /* grammar rule information */ extern int nprod; /* number of productions */ extern int **prdptr; /* pointers to descriptions of productions */ extern int *levprd; /* contains production levels to break conflicts */ extern wchar_t *had_act; /* set if reduction has associated action code */ /* state information */ extern int nstate; /* number of states */ extern ITEM **pstate; /* pointers to the descriptions of the states */ extern int *tystate; /* contains type information about the states */ extern int *defact; /* the default action of the state */ extern int size; /* lookahead set information */ extern int TBITSET; extern LOOKSETS *lkst; extern int nolook; /* flag to turn off lookahead computations */ /* working set information */ extern WSET *wsets; /* storage for productions */ extern int *mem0; extern int *mem; extern int *tracemem; extern int new_memsize; /* storage for action table */ extern int *amem; extern int *memp; /* next free action table position */ extern int *indgo; /* index to the stored goto table */ extern int new_actsize; /* temporary vector, indexable by states, terms, or ntokens */ extern int *temp1; extern int lineno; /* current line number */ /* statistics collection variables */ extern int zzgoent; extern int zzgobest; extern int zzacent; extern int zzexcp; extern int zzrrconf; extern int zzsrconf; /* define external functions */ extern void setup(int, char * []); extern void closure(int); extern void output(void); extern void aryfil(int *, int, int); extern void error(char *, ...); extern void warning(int, char *, ...); extern void putitem(int *, LOOKSETS *); extern void go2out(void); extern void hideprod(void); extern void callopt(void); extern void warray(wchar_t *, int *, int); extern wchar_t *symnam(int); extern wchar_t *writem(int *); extern void exp_mem(int); extern void exp_act(int **); extern int apack(int *, int); extern int state(int); extern void fprintf3(FILE *, const char *, const wchar_t *, const char *, ...); extern void error3(const char *, const wchar_t *, const char *, ...); /* multibyte i/o */ #undef getwc #define getwc(f) yacc_getwc(f) extern wint_t yacc_getwc(FILE *); #undef putwc #define putwc(c, f) yacc_putwc(c, f) extern wint_t yacc_putwc(wchar_t, FILE *); /* yaccpar location */ extern char *parser; /* default settings for a number of macros */ /* name of yacc tempfiles */ #ifndef TEMPNAME #define TEMPNAME "yacc.tmp" #endif #ifndef ACTNAME #define ACTNAME "yacc.acts" #endif #ifndef DEBUGNAME #define DEBUGNAME "yacc.debug" #endif /* command to clobber tempfiles after use */ #ifndef ZAPFILE #define ZAPFILE(x) unlink(x) #endif
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 1990 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ /* from OpenSolaris "y1.c 6.27 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)y1.c 1.7 (gritter) 11/26/05 */ #include "dextern" #include <sys/param.h> #include <errno.h> #include <unistd.h> #include <locale.h> #include <stdarg.h> /* For error() */ #include <wchar.h> static void mktbls(void); static void others(void); static void summary(void); static wchar_t *chcopy(wchar_t *, wchar_t *); static int setunion(int *, int *); static void prlook(LOOKSETS *); static void cpres(void); static void cpfir(void); static void cempty(void); static void stagen(void); static LOOKSETS *flset(LOOKSETS *); static void exp_lkst(void); static void exp_wsets(void); static void exp_states(void); static void exp_psmem(void); /* lookahead computations */ int TBITSET; static int tbitset; /* size of lookahead sets */ LOOKSETS *lkst; static int lsetsize; static int nlset = 0; /* next lookahead set index */ int nolook = 0; /* flag to suppress lookahead computations */ static LOOKSETS clset; /* temporary storage for lookahead computations */ static ITEM *psmem, *zzmemsz; static int new_pstsize = PSTSIZE; /* working set computations */ WSET *wsets; int cwp; static int wsetsz = 0; /* number of WSET items in wsets block */ /* state information */ int nstate = 0; /* number of states */ static int nstatesz = NSTATES; /* number of state space allocated */ ITEM **pstate; /* ptr to descriptions of the states */ int *tystate; /* contains type info about the states */ int *indgo; /* index to the stored goto table */ static int *tmp_lset; static int *tstates; /* states generated by terminal gotos */ static int *ntstates; /* states generated by non-term gotos */ static int *mstates; /* chain of overflows of term/nonterm */ /* generation lists */ /* storage for the actions in the parser */ int *amem, *memp; /* next free action table position */ int new_actsize = ACTSIZE; /* other storage areas */ int *temp1; /* temp storate, indexed by terms+ntokens or states */ int lineno = 0; /* current input line number */ int size; static int fatfl = 1; /* if on, error is fatal */ static int nerrors = 0; /* number of errors */ /* storage for information about the nonterminals */ static int ***pres; /* vector of pointers to productions */ /* yielding each nonterminal */ static LOOKSETS **pfirst; /* vector of pointers to first sets for */ /* each nonterminal */ static int *pempty; /* vector of nonterminals nontrivially */ /* deriving e */ extern int nprodsz; static char *sav_argv0; char run_directory[MAXPATHLEN]; char current_work_directory[MAXPATHLEN]; int main(int argc, char *argv[]) { setlocale(LC_CTYPE, ""); sav_argv0 = argv[0]; setup(argc, argv); /* initialize and read productions */ TBITSET = NWORDS(ntoksz*LKFACTOR); tbitset = NWORDS(ntokens*LKFACTOR); mktbls(); cpres(); /* make table of which productions yield a */ /* given nonterminal */ cempty(); /* make a table of which nonterminals can match */ /* the empty string */ cpfir(); /* make a table of firsts of nonterminals */ stagen(); /* generate the states */ output(); /* write the states and the tables */ go2out(); hideprod(); summary(); callopt(); others(); return 0; } static void mktbls(void) { int i; size = ntoksz + nnontersz +1; if (size < nstatesz) size = nstatesz; if (size < new_memsize) size = new_memsize; amem = malloc(sizeof (int) * new_actsize); psmem = malloc(sizeof (ITEM) * new_pstsize); if ((psmem == NULL) || (amem == NULL)) error("couldn't allocate initial table"); zzmemsz = psmem; memp = amem; /* * For lkst */ #define INIT_LSIZE nnontersz*LKFACTOR tmp_lset = calloc((size_t)(TBITSET * (INIT_LSIZE+1)), sizeof (int)); if (tmp_lset == NULL) error("could not allocate lookset array"); lkst = malloc(sizeof (LOOKSETS) * (INIT_LSIZE + 1)); for (i = 0; i <= INIT_LSIZE; ++i) lkst[i].lset = tmp_lset + TBITSET * i; tmp_lset = NULL; /* * For wsets */ tmp_lset = calloc((size_t)(TBITSET * (nnontersz+1)), sizeof (int)); if (tmp_lset == NULL) error("could not allocate lookset array"); wsets = (WSET *) malloc(sizeof (WSET) * (nnontersz + 1)); for (i = 0; i <= nnontersz; ++i) wsets[i].ws.lset = tmp_lset + TBITSET * i; tmp_lset = NULL; clset.lset = malloc(sizeof (int)*TBITSET); tstates = malloc(sizeof (int)*(ntoksz + 1)); ntstates = malloc(sizeof (int)*(nnontersz + 1)); temp1 = malloc(sizeof (int)*size); pres = malloc(sizeof (int **)*(nnontersz + 2)); pfirst = malloc(sizeof (LOOKSETS *)*(nnontersz + 2)); pempty = malloc(sizeof (int)*(nnontersz + 1)); pstate = malloc(sizeof (ITEM *)*(nstatesz+2)); tystate = malloc(sizeof (int)*nstatesz); indgo = malloc(sizeof (int)*nstatesz); mstates = malloc(sizeof (int)*nstatesz); defact = malloc(sizeof (int)*nstatesz); if ((lkst == NULL) || (wsets == NULL) || (tstates == NULL) || (ntstates == NULL) || (temp1 == NULL) || (pres == NULL) || (pfirst == NULL) || (pempty == NULL) || (pstate == NULL) || (tystate == NULL) || (indgo == NULL) || (mstates == NULL) || (defact == NULL) || (clset.lset == NULL)) error("cannot allocate tables in mktbls()"); aryfil(ntstates, nnontersz+1, 0); aryfil(tstates, ntoksz+1, 0); wsetsz = nnontersz + 1; lsetsize = INIT_LSIZE + 1; } /* put out other arrays, copy the parsers */ static void others(void) { extern int gen_lines; register int c, i, j; int tmpline; /* This routine has been "stolen" from the driver */ if (parser == NULL) parser = PARSER; finput = fopen(parser, "r"); if (finput == NULL) error("cannot find parser %s", parser); warray(L"yyr1", levprd, nprod); aryfil(temp1, nprod, 0); /* had_act[i] is either 1 or 0 */ PLOOP(1, i) temp1[i] = ((prdptr[i+1] - prdptr[i]-2) << 1) | had_act[i]; warray(L"yyr2", temp1, nprod); aryfil(temp1, nstate, -10000000); TLOOP(i) for (j = tstates[i]; j != 0; j = mstates[j]) temp1[j] = tokset[i].value; NTLOOP(i) for (j = ntstates[i]; j != 0; j = mstates[j]) temp1[j] = -i; warray(L"yychk", temp1, nstate); warray(L"yydef", defact, nstate); if ((fdebug = fopen(DEBUGNAME, "r")) == NULL) error("cannot open yacc.debug"); while ((c = getwc(fdebug)) != EOF) putwc(c, ftable); fclose(fdebug); ZAPFILE(DEBUGNAME); if (gen_lines) fprintf(ftable, "# line\t1 \"%s\"\n", parser); tmpline = 1; /* copy parser text */ while ((c = getwc(finput)) != EOF) { if (c == '\n') tmpline++; if (c == L'$') { if ((c = getwc(finput)) != L'A') putwc(L'$', ftable); else { /* copy actions */ tmpline++; faction = fopen(ACTNAME, "r"); if (faction == NULL) error("cannot open action tempfile"); while ((c = getwc(faction)) != EOF) putwc(c, ftable); fclose(faction); if (gen_lines) fprintf(ftable, "\n# line\t%d \"%s\"", tmpline, parser); ZAPFILE(ACTNAME); c = getwc(finput); } } putwc(c, ftable); } fclose(ftable); } /* copies string q into p, returning next free char ptr */ static wchar_t * chcopy(wchar_t *p, wchar_t *q) { while (*p = *q++) ++p; return (p); } #define ISIZE 400 /* creates output string for item pointed to by pp */ wchar_t * writem(int *pp) { int i, *p; static int isize = ISIZE; static wchar_t *sarr = NULL; wchar_t *q; if (sarr == NULL) { sarr = malloc(sizeof (wchar_t) * isize); if (sarr == NULL) error("could not allocate output string array"); for (i = 0; i < isize; ++i) sarr[i] = L' '; } for (p = pp; *p > 0; ++p) /* EMPTY */; p = prdptr[-*p]; q = chcopy(sarr, nontrst[*p-NTBASE].name); q = chcopy(q, L" : "); for (;;) { *q++ = ++p == pp ? L'_' : L' '; *q = 0; if ((i = *p) <= 0) break; q = chcopy(q, symnam(i)); while (q > &sarr[isize-30]) { static wchar_t *sarrbase; sarrbase = sarr; isize += ISIZE; sarr = realloc(sarr, sizeof (*sarr) * isize); if (sarr == NULL) error("cannot expand sarr arrays"); q = q - sarrbase + sarr; } } /* an item calling for a reduction */ if ((i = *pp) < 0) { q = chcopy(q, L" ("); swprintf(q, q + isize - sarr, L"%d)", -i); } return (sarr); } /* return a pointer to the name of symbol i */ wchar_t * symnam(int i) { wchar_t *cp; cp = (i >= NTBASE) ? nontrst[i-NTBASE].name : tokset[i].name; if (*cp == L' ') ++cp; return (cp); } static int zzcwp = 0; static int zzclose = 0; int zzgoent = 0; int zzgobest = 0; int zzacent = 0; int zzexcp = 0; int zzsrconf = 0; int zzrrconf = 0; /* output the summary on the tty */ static void summary(void) { if (foutput != NULL) { fprintf(foutput, "\n%d/%d terminals, %d/%d nonterminals\n", ntokens, ntoksz, nnonter, nnontersz); fprintf(foutput, "%d/%d grammar rules, %d/%d states\n", nprod, nprodsz, nstate, nstatesz); fprintf(foutput, "%d shift/reduce, %d reduce/reduce conflicts reported\n", zzsrconf, zzrrconf); fprintf(foutput, "%d/%d working sets used\n", zzcwp, wsetsz); fprintf(foutput, "memory: states,etc. %d/%d, parser %d/%d\n", mem-tracemem, new_memsize, memp-amem, new_actsize); fprintf(foutput, "%d/%d distinct lookahead sets\n", nlset, lsetsize); fprintf(foutput, "%d extra closures\n", zzclose - 2*nstate); fprintf(foutput, "%d shift entries, %d exceptions\n", zzacent, zzexcp); fprintf(foutput, "%d goto entries\n", zzgoent); fprintf(foutput, "%d entries saved by goto default\n", zzgobest); } if (zzsrconf != 0 || zzrrconf != 0) { fprintf(stderr, "\nconflicts: "); if (zzsrconf) fprintf(stderr, "%d shift/reduce", zzsrconf); if (zzsrconf && zzrrconf) fprintf(stderr, ", "); if (zzrrconf) fprintf(stderr, "%d reduce/reduce", zzrrconf); fprintf(stderr, "\n"); } if (ftemp != NULL) fclose(ftemp); if (fdefine != NULL) fclose(fdefine); } /* write out error comment */ void error(char *s, ...) { extern char *infile; va_list ap; va_start(ap, s); ++nerrors; if (!lineno) fprintf(stderr, "command line: fatal: "); else { fprintf(stderr, "\"%s\", ", infile); fprintf(stderr, "line %d: fatal: ", lineno); } vfprintf(stderr, s, ap); fprintf(stderr, "\n"); va_end(ap); if (!fatfl) return; summary(); exit(1); } /* * Print out a warning message. */ void warning(int flag, char *s, ...) { extern char *infile; va_list ap; va_start(ap, s); fprintf(stderr, "\"%s\", ", infile); /* * If flag, print lineno as well. */ if (flag == 0) fprintf(stderr, "warning: "); else fprintf(stderr, "line %d: warning: ", lineno); vfprintf(stderr, s, ap); fprintf(stderr, "\n"); va_end(ap); } /* set elements 0 through n-1 to c */ void aryfil(int *v, int n, int c) { int i; for (i = 0; i < n; ++i) v[i] = c; } /* set a to the union of a and b */ /* return 1 if b is not a subset of a, 0 otherwise */ static int setunion(register int *a, register int *b) { register int i, x, sub; sub = 0; SETLOOP(i) { *a = (x = *a) | *b++; if (*a++ != x) sub = 1; } return (sub); } static void prlook(LOOKSETS *p) { register int j, *pp; pp = p->lset; if (pp == 0) fprintf(foutput, "\tNULL"); else { fprintf(foutput, " { "); TLOOP(j) { if (BIT(pp, j)) fprintf(foutput, "%ls ", symnam(j)); } fprintf(foutput, "}"); } } /* * compute an array with the beginnings of productions yielding * given nonterminals * The array pres points to these lists * the array pyield has the lists: the total size is only NPROD+1 */ static void cpres(void) { register int **ptrpy; int **pyield; register int c, j, i; /* * 2/29/88 - * nprodsz is the size of the tables describing the productions. * Normally this will be NPROD unless the production tables have * been expanded, in which case the tables will be NPROD * N(where * N is the number of times the tables had to be expanded.) */ if ((pyield = malloc(sizeof (int *) * nprodsz)) == NULL) error("cannot allocate space for pyield array"); ptrpy = pyield; NTLOOP(i) { c = i+NTBASE; pres[i] = ptrpy; fatfl = 0; /* make undefined symbols nonfatal */ PLOOP(0, j) { if (*prdptr[j] == c) /* linear search for all c's */ *ptrpy++ = prdptr[j] + 1; } if (pres[i] == ptrpy) { /* c not found */ error("undefined nonterminal: %ls", nontrst[i].name); } } pres[i] = ptrpy; fatfl = 1; if (nerrors) { summary(); exit(1); } if (ptrpy != &pyield[nprod]) error("internal Yacc error: pyield %d", ptrpy-&pyield[nprod]); } static int indebug = 0; /* compute an array with the first of nonterminals */ static void cpfir(void) { register int *p, **s, i, **t, ch, changes; zzcwp = nnonter; NTLOOP(i) { aryfil(wsets[i].ws.lset, tbitset, 0); t = pres[i+1]; /* initially fill the sets */ for (s = pres[i]; s < t; ++s) { /* check if ch is non-terminal */ for (p = *s; (ch = *p) > 0; ++p) { if (ch < NTBASE) { /* should be token */ SETBIT(wsets[i].ws.lset, ch); break; } else if (!pempty[ch-NTBASE]) break; } } } /* now, reflect transitivity */ changes = 1; while (changes) { changes = 0; NTLOOP(i) { t = pres[i+1]; for (s = pres[i]; s < t; ++s) { for (p = *s; (ch = (*p-NTBASE)) >= 0; ++p) { changes |= setunion(wsets[i].ws.lset, wsets[ch].ws.lset); if (!pempty[ch]) break; } } } } NTLOOP(i) pfirst[i] = flset(&wsets[i].ws); if (!indebug) return; if ((foutput != NULL)) { NTLOOP(i) { fprintf(foutput, "\n%ls: ", nontrst[i].name); prlook(pfirst[i]); fprintf(foutput, " %d\n", pempty[i]); } } } /* sorts last state,and sees if it equals earlier ones. returns state number */ int state(int c) { int size1, size2; register int i; ITEM *p1, *p2, *k, *l, *q1, *q2; p1 = pstate[nstate]; p2 = pstate[nstate+1]; if (p1 == p2) return (0); /* null state */ /* sort the items */ for (k = p2 - 1; k > p1; k--) { /* make k the biggest */ for (l = k-1; l >= p1; --l) if (l->pitem > k->pitem) { int *s; LOOKSETS *ss; s = k->pitem; k->pitem = l->pitem; l->pitem = s; ss = k->look; k->look = l->look; l->look = ss; } } size1 = p2 - p1; /* size of state */ for (i = (c >= NTBASE) ? ntstates[c-NTBASE] : tstates[c]; i != 0; i = mstates[i]) { /* get ith state */ q1 = pstate[i]; q2 = pstate[i+1]; size2 = q2 - q1; if (size1 != size2) continue; k = p1; for (l = q1; l < q2; l++) { if (l->pitem != k->pitem) break; ++k; } if (l != q2) continue; /* found it */ pstate[nstate+1] = pstate[nstate]; /* delete last state */ /* fix up lookaheads */ if (nolook) return (i); for (l = q1, k = p1; l < q2; ++l, ++k) { int s; SETLOOP(s) clset.lset[s] = l->look->lset[s]; if (setunion(clset.lset, k->look->lset)) { tystate[i] = MUSTDO; /* register the new set */ l->look = flset(&clset); } } return (i); } /* state is new */ if (nolook) error("yacc state/nolook error"); pstate[nstate+2] = p2; if (nstate+1 >= nstatesz) exp_states(); if (c >= NTBASE) { mstates[nstate] = ntstates[c - NTBASE]; ntstates[c - NTBASE] = nstate; } else { mstates[nstate] = tstates[c]; tstates[c] = nstate; } tystate[nstate] = MUSTDO; return (nstate++); } static int pidebug = 0; void putitem(int *ptr, LOOKSETS *lptr) { register ITEM *j; if (pidebug && (foutput != NULL)) fprintf(foutput, "putitem(%ls), state %d\n", writem(ptr), nstate); j = pstate[nstate+1]; j->pitem = ptr; if (!nolook) j->look = flset(lptr); pstate[nstate+1] = ++j; if (j > zzmemsz) { zzmemsz = j; if (zzmemsz >= &psmem[new_pstsize]) exp_psmem(); /* error("out of state space"); */ } } /* * mark nonterminals which derive the empty string * also, look for nonterminals which don't derive any token strings */ static void cempty(void) { #define EMPTY 1 #define WHOKNOWS 0 #define OK 1 register int i, *p; /* * first, use the array pempty to detect productions * that can never be reduced */ /* set pempty to WHONOWS */ aryfil(pempty, nnonter+1, WHOKNOWS); /* * now, look at productions, marking nonterminals which * derive something */ more: PLOOP(0, i) { if (pempty[*prdptr[i] - NTBASE]) continue; for (p = prdptr[i] + 1; *p >= 0; ++p) if (*p >= NTBASE && pempty[*p-NTBASE] == WHOKNOWS) break; if (*p < 0) { /* production can be derived */ pempty[*prdptr[i]-NTBASE] = OK; goto more; } } /* now, look at the nonterminals, to see if they are all OK */ NTLOOP(i) { /* * the added production rises or falls as the * start symbol ... */ if (i == 0) continue; if (pempty[i] != OK) { fatfl = 0; error("nonterminal %ls never derives any token string", nontrst[i].name); } } if (nerrors) { summary(); exit(1); } /* * now, compute the pempty array, to see which nonterminals * derive the empty string */ /* set pempty to WHOKNOWS */ aryfil(pempty, nnonter+1, WHOKNOWS); /* loop as long as we keep finding empty nonterminals */ again: PLOOP(1, i) { /* not known to be empty */ if (pempty[*prdptr[i]-NTBASE] == WHOKNOWS) { for (p = prdptr[i]+1; *p >= NTBASE && pempty[*p-NTBASE] == EMPTY; ++p); /* we have a nontrivially empty nonterminal */ if (*p < 0) { pempty[*prdptr[i]-NTBASE] = EMPTY; goto again; /* got one ... try for another */ } } } } /* generate the states */ static int gsdebug = 0; static void stagen(void) { int i, j; register int c; register WSET *p, *q; /* initialize */ nstate = 0; pstate[0] = pstate[1] = psmem; aryfil(clset.lset, tbitset, 0); putitem(prdptr[0] + 1, &clset); tystate[0] = MUSTDO; nstate = 1; pstate[2] = pstate[1]; aryfil(amem, new_actsize, 0); /* now, the main state generation loop */ more: SLOOP(i) { if (tystate[i] != MUSTDO) continue; tystate[i] = DONE; aryfil(temp1, nnonter + 1, 0); /* take state i, close it, and do gotos */ closure(i); WSLOOP(wsets, p) { /* generate goto's */ if (p->flag) continue; p->flag = 1; c = *(p->pitem); if (c <= 1) { if (pstate[i+1]-pstate[i] <= p-wsets) tystate[i] = MUSTLOOKAHEAD; continue; } /* do a goto on c */ WSLOOP(p, q) { /* this item contributes to the goto */ if (c == *(q->pitem)) { putitem(q->pitem + 1, &q->ws); q->flag = 1; } } if (c < NTBASE) state(c); /* register new state */ else temp1[c-NTBASE] = state(c); } if (gsdebug && (foutput != NULL)) { fprintf(foutput, "%d: ", i); NTLOOP(j) { if (temp1[j]) fprintf(foutput, "%ls %d, ", nontrst[j].name, temp1[j]); } fprintf(foutput, "\n"); } indgo[i] = apack(&temp1[1], nnonter - 1) - 1; goto more; /* we have done one goto; do some more */ } /* no more to do... stop */ } /* generate the closure of state i */ static int cldebug = 0; /* debugging flag for closure */ void closure(int i) { int c, ch, work, k; register WSET *u, *v; int *pi; int **s, **t; ITEM *q; register ITEM *p; int idx1 = 0; ++zzclose; /* first, copy kernel of state i to wsets */ cwp = 0; ITMLOOP(i, p, q) { wsets[cwp].pitem = p->pitem; wsets[cwp].flag = 1; /* this item must get closed */ SETLOOP(k) wsets[cwp].ws.lset[k] = p->look->lset[k]; WSBUMP(cwp); } /* now, go through the loop, closing each item */ work = 1; while (work) { work = 0; /* * WSLOOP(wsets, u) { */ for (idx1 = 0; idx1 < cwp; idx1++) { u = &wsets[idx1]; if (u->flag == 0) continue; c = *(u->pitem); /* dot is before c */ if (c < NTBASE) { u->flag = 0; /* * only interesting case is where . is * before nonterminal */ continue; } /* compute the lookahead */ aryfil(clset.lset, tbitset, 0); /* find items involving c */ WSLOOP(u, v) { if (v->flag == 1 && *(pi = v->pitem) == c) { v->flag = 0; if (nolook) continue; while ((ch = *++pi) > 0) { /* terminal symbol */ if (ch < NTBASE) { SETBIT(clset.lset, ch); break; } /* nonterminal symbol */ setunion(clset.lset, pfirst[ch-NTBASE]->lset); if (!pempty[ch-NTBASE]) break; } if (ch <= 0) setunion(clset.lset, v->ws.lset); } } /* now loop over productions derived from c */ c -= NTBASE; /* c is now nonterminal number */ t = pres[c+1]; for (s = pres[c]; s < t; ++s) { /* put these items into the closure */ WSLOOP(wsets, v) { /* is the item there */ /* yes, it is there */ if (v->pitem == *s) { if (nolook) goto nexts; if (setunion(v->ws.lset, clset.lset)) v->flag = work = 1; goto nexts; } } /* not there; make a new entry */ if (cwp + 1 >= wsetsz) exp_wsets(); wsets[cwp].pitem = *s; wsets[cwp].flag = 1; if (!nolook) { work = 1; SETLOOP(k) wsets[cwp].ws.lset[k] = clset.lset[k]; } WSBUMP(cwp); nexts:; } } } /* have computed closure; flags are reset; return */ if (&wsets[cwp] > &wsets[zzcwp]) zzcwp = cwp; if (cldebug && (foutput != NULL)) { fprintf(foutput, "\nState %d, nolook = %d\n", i, nolook); WSLOOP(wsets, u) { if (u->flag) fprintf(foutput, "flag set!\n"); u->flag = 0; fprintf(foutput, "\t%ls", writem(u->pitem)); prlook(&u->ws); fprintf(foutput, "\n"); } } } static LOOKSETS * flset(LOOKSETS *p) { /* decide if the lookahead set pointed to by p is known */ /* return pointer to a perminent location for the set */ int j, *w; register int *u, *v; register LOOKSETS *q; for (q = &lkst[nlset]; q-- > lkst; ) { u = p->lset; v = q->lset; w = & v[tbitset]; while (v < w) if (*u++ != *v++) goto more; /* we have matched */ return (q); more:; } /* add a new one */ q = &lkst[nlset++]; if (nlset >= lsetsize) { exp_lkst(); q = &lkst[nlset++]; } SETLOOP(j) q->lset[j] = p->lset[j]; return (q); } static void exp_lkst(void) { int i, j; static LOOKSETS *lookbase; lookbase = lkst; lsetsize += LSETSIZE; tmp_lset = calloc(TBITSET * (lsetsize-LSETSIZE), sizeof (int)); if (tmp_lset == NULL) error("could not expand lookset array"); lkst = realloc(lkst, sizeof (LOOKSETS) * lsetsize); for (i = lsetsize-LSETSIZE, j = 0; i < lsetsize; ++i, ++j) lkst[i].lset = tmp_lset + TBITSET * j; tmp_lset = NULL; if (lkst == NULL) error("could not expand lookahead sets"); for (i = 0; i <= nnonter; ++i) pfirst[i] = pfirst[i] - lookbase + lkst; for (i = 0; i <= nstate+1; ++i) { if (psmem[i].look) psmem[i].look = psmem[i].look - lookbase + lkst; if (pstate[i]->look) pstate[i]->look = pstate[i]->look - lookbase + lkst; } } static void exp_wsets(void) { int i, j; wsetsz += WSETSIZE; tmp_lset = calloc(TBITSET * (wsetsz-WSETSIZE), sizeof (int)); if (tmp_lset == NULL) error("could not expand lookset array"); wsets = realloc(wsets, sizeof (WSET) * wsetsz); for (i = wsetsz-WSETSIZE, j = 0; i < wsetsz; ++i, ++j) wsets[i].ws.lset = tmp_lset + TBITSET * j; tmp_lset = NULL; if (wsets == NULL) error("could not expand working sets"); } static void exp_states(void) { nstatesz += NSTATES; pstate = realloc(pstate, sizeof (ITEM *)*(nstatesz+2)); mstates = realloc(mstates, sizeof (int)*nstatesz); defact = realloc(defact, sizeof (int)*nstatesz); tystate = realloc(tystate, sizeof (int)*nstatesz); indgo = realloc(indgo, sizeof (int)*nstatesz); if ((*pstate == NULL) || (tystate == NULL) || (defact == NULL) || (indgo == NULL) || (mstates == NULL)) error("cannot expand table of states"); } static void exp_psmem(void) { int i; new_pstsize += PSTSIZE; psmem = realloc(psmem, sizeof (ITEM) * new_pstsize); if (psmem == NULL) error("cannot expand pstate memory"); zzmemsz = zzmemsz - pstate[0] + psmem; for (i = 1; i <= nstate+1; ++i) pstate[i] = pstate[i] - pstate[0] + psmem; pstate[0] = psmem; }
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2002 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ /* from OpenSolaris "y2.c 6.35 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)y2.c 1.11 (gritter) 11/26/05 */ #include "dextern" #include "sgs.h" #include <wchar.h> #include <unistd.h> #define IDENTIFIER 257 #define MARK 258 #define TERM 259 #define LEFT 260 #define RIGHT 261 #define BINARY 262 #define PREC 263 #define LCURLY 264 #define C_IDENTIFIER 265 /* name followed by colon */ #define NUMBER 266 #define START 267 #define TYPEDEF 268 #define TYPENAME 269 #define UNION 270 #define ENDFILE 0 #define LHS_TEXT_LEN 80 /* length of lhstext */ #define RHS_TEXT_LEN 640 /* length of rhstext */ /* communication variables between various I/O routines */ #define v_FLAG 0x01 #define d_FLAG 0x02 #define DEFAULT_PREFIX "y" char *infile; /* input file name */ static int numbval; /* value of an input number */ static int toksize = NAMESIZE; static wchar_t *tokname; /* input token name */ char *parser = NULL; /* location of common parser */ static void finact(void); static wchar_t *cstash(wchar_t *); static void defout(void); static void cpyunion(void); static void cpycode(void); static void cpyact(int); static void lhsfill(wchar_t *); static void rhsfill(wchar_t *); static void lrprnt(void); static void beg_debug(void); static void end_toks(void); static void end_debug(void); static void exp_tokname(void); static void exp_prod(void); static void exp_ntok(void); static void exp_nonterm(void); static int defin(int, wchar_t *); static int gettok(void); static int chfind(int, wchar_t *); static int skipcom(void); static int findchtok(int); static void put_prefix_define(char *); /* storage of names */ /* * initial block to place token and * nonterminal names are stored * points to initial block - more space * is allocated as needed. */ static wchar_t cnamesblk0[CNAMSZ]; static wchar_t *cnames = cnamesblk0; /* place where next name is to be put in */ static wchar_t *cnamp = cnamesblk0; /* number of defined symbols output */ static int ndefout = 3; /* storage of types */ static int defunion = 0; /* union of types defined? */ static int ntypes = 0; /* number of types defined */ static wchar_t *typeset[NTYPES]; /* pointers to type tags */ /* symbol tables for tokens and nonterminals */ int ntokens = 0; int ntoksz = NTERMS; TOKSYMB *tokset; int *toklev; int nnonter = -1; NTSYMB *nontrst; int nnontersz = NNONTERM; static int start; /* start symbol */ /* assigned token type values */ static int extval = 0; /* input and output file descriptors */ FILE *finput; /* yacc input file */ FILE *faction; /* file for saving actions */ FILE *fdefine; /* file for # defines */ FILE *ftable; /* y.tab.c file */ FILE *ftemp; /* tempfile to pass 2 */ FILE *fdebug; /* where the strings for debugging are stored */ FILE *foutput; /* y.output file */ /* output string */ static wchar_t *lhstext; static wchar_t *rhstext; /* storage for grammar rules */ int *mem0; /* production storage */ int *mem; int *tracemem; extern int *optimmem; int new_memsize = MEMSIZE; int nprod = 1; /* number of productions */ int nprodsz = NPROD; int **prdptr; int *levprd; wchar_t *had_act; /* flag for generating the # line's default is yes */ int gen_lines = 1; int act_lines = 0; /* flag for whether to include runtime debugging */ static int gen_testing = 0; /* flag for version stamping--default turned off */ static char *v_stmp = "n"; int nmbchars = 0; /* number of mb literals in mbchars */ MBCLIT *mbchars = (MBCLIT *) 0; /* array of mb literals */ int nmbcharsz = 0; /* allocated space for mbchars */ void setup(int argc, char *argv[]) { int ii, i, j, lev, t, ty; /* ty is the sequencial number of token name in tokset */ int c; int *p; char *cp; wchar_t actname[8]; unsigned int options = 0; char *file_prefix = DEFAULT_PREFIX; char *sym_prefix = ""; #define F_NAME_LENGTH 4096 char fname[F_NAME_LENGTH+1]; foutput = NULL; fdefine = NULL; i = 1; tokname = malloc(sizeof (wchar_t) * toksize); tokset = malloc(sizeof (TOKSYMB) * ntoksz); toklev = malloc(sizeof (int) * ntoksz); nontrst = malloc(sizeof (NTSYMB) * nnontersz); mem0 = malloc(sizeof (int) * new_memsize); prdptr = malloc(sizeof (int *) * (nprodsz+2)); levprd = malloc(sizeof (int) * (nprodsz+2)); had_act = calloc(nprodsz + 2, sizeof (wchar_t)); lhstext = malloc(sizeof (wchar_t) * LHS_TEXT_LEN); rhstext = malloc(sizeof (wchar_t) * RHS_TEXT_LEN); aryfil(toklev, ntoksz, 0); aryfil(levprd, nprodsz, 0); for (ii = 0; ii < ntoksz; ++ii) tokset[ii].value = 0; for (ii = 0; ii < nnontersz; ++ii) nontrst[ii].tvalue = 0; aryfil(mem0, new_memsize, 0); mem = mem0; tracemem = mem0; while ((c = getopt(argc, argv, "vVdltp:Q:Y:P:b:")) != EOF) switch (c) { case 'v': options |= v_FLAG; break; case 'V': fprintf(stderr, "yacc: %s , %s\n", pkg, rel); break; case 'Q': v_stmp = optarg; if (*v_stmp != 'y' && *v_stmp != 'n') error("yacc: -Q should be followed by [y/n]"); break; case 'd': options |= d_FLAG; break; case 'l': gen_lines = 0; /* don't gen #lines */ break; case 't': gen_testing = 1; /* set YYDEBUG on */ break; case 'Y': cp = malloc(strlen(optarg) + sizeof ("/yaccpar") + 1); cp = strcpy(cp, optarg); parser = strcat(cp, "/yaccpar"); break; case 'P': parser = optarg; break; case 'p': if (strcmp(optarg, "yy") != 0) sym_prefix = optarg; else sym_prefix = ""; break; case 'b': file_prefix = optarg; break; case '?': default: fprintf(stderr, "Usage: yacc [-vVdlt] [-Q(y/n)] [-P driver_file] file\n"); exit(1); } /* * Open y.output if -v is specified */ if (options & v_FLAG) { strncpy(fname, file_prefix, F_NAME_LENGTH-strlen(".output")); strcat(fname, ".output"); foutput = fopen(fname, "w"); if (foutput == NULL) error("cannot open y.output"); } /* * Open y.tab.h if -d is specified */ if (options & d_FLAG) { strncpy(fname, file_prefix, F_NAME_LENGTH-strlen(".tab.h")); strcat(fname, ".tab.h"); fdefine = fopen(fname, "w"); if (fdefine == NULL) error("cannot open y.tab.h"); } fdebug = fopen(DEBUGNAME, "w"); if (fdebug == NULL) error("cannot open yacc.debug"); /* * Open y.tab.c */ strncpy(fname, file_prefix, F_NAME_LENGTH-strlen(".tab.c")); strcat(fname, ".tab.c"); ftable = fopen(fname, "w"); if (ftable == NULL) error("cannot open %s", fname); ftemp = fopen(TEMPNAME, "w"); faction = fopen(ACTNAME, "w"); if (ftemp == NULL || faction == NULL) error("cannot open temp file"); if ((finput = fopen(infile = argv[optind], "r")) == NULL) error("cannot open input file"); lineno = 1; cnamp = cnames; defin(0, L"$end"); extval = 0400; defin(0, L"error"); defin(1, L"$accept"); mem = mem0; lev = 0; ty = 0; i = 0; beg_debug(); /* initialize fdebug file */ /* * sorry -- no yacc parser here..... * we must bootstrap somehow... */ t = gettok(); if (*v_stmp == 'y') fprintf(ftable, "\ #if __GNUC__ >= 3 && __GNUC_MINOR__ >= 4 || __GNUC__ >= 4\n\ #define YYUSED __attribute__ ((used))\n\ #elif defined __GNUC__\n\ #define YYUSED __attribute__ ((unused))\n\ #else\n\ #define YYUSED\n\ #endif\n\ static const char yyident[] USED = \"yacc: %s\"\n", rel); for (; t != MARK && t != ENDFILE; ) { int tok_in_line; switch (t) { case L';': t = gettok(); break; case START: if ((t = gettok()) != IDENTIFIER) { error("bad %%start construction"); } start = chfind(1, tokname); t = gettok(); continue; case TYPEDEF: tok_in_line = 0; if ((t = gettok()) != TYPENAME) error("bad syntax in %%type"); ty = numbval; for (;;) { t = gettok(); switch (t) { case IDENTIFIER: /* * The following lines are idented to left. */ tok_in_line = 1; if ((t = chfind(1, tokname)) < NTBASE) { j = TYPE(toklev[t]); if (j != 0 && j != ty) { error("type redeclaration of token %ls", tokset[t].name); } else SETTYPE(toklev[t], ty); } else { j = nontrst[t-NTBASE].tvalue; if (j != 0 && j != ty) { error( "type redeclaration of nonterminal %ls", nontrst[t-NTBASE].name); } else nontrst[t-NTBASE].tvalue = ty; } /* FALLTHRU */ /* * End Indentation */ case L',': continue; case L';': t = gettok(); break; default: break; } if (!tok_in_line) error( "missing tokens or illegal tokens"); break; } continue; case UNION: /* copy the union declaration to the output */ cpyunion(); defunion = 1; t = gettok(); continue; case LEFT: case BINARY: case RIGHT: i++; /* FALLTHRU */ case TERM: tok_in_line = 0; /* nonzero means new prec. and assoc. */ lev = (t-TERM) | 04; ty = 0; /* get identifiers so defined */ t = gettok(); if (t == TYPENAME) { /* there is a type defined */ ty = numbval; t = gettok(); } for (;;) { switch (t) { case L',': t = gettok(); continue; case L';': break; case IDENTIFIER: tok_in_line = 1; j = chfind(0, tokname); if (j > NTBASE) { error("%ls is not a token.", tokname); } if (lev & ~04) { if (ASSOC(toklev[j]) & ~04) error( "redeclaration of precedence of %ls", tokname); SETASC(toklev[j], lev); SETPLEV(toklev[j], i); } else { if (ASSOC(toklev[j])) warning(1, "redeclaration of precedence of %ls.", tokname); SETASC(toklev[j], lev); } if (ty) { if (TYPE(toklev[j])) error( "redeclaration of type of %ls", tokname); SETTYPE(toklev[j], ty); } if ((t = gettok()) == NUMBER) { tokset[j].value = numbval; if (j < ndefout && j > 2) { error( "type number of %ls should be defined earlier", tokset[j].name); } if (numbval >= -YYFLAG1) { error( "token numbers must be less than %d", -YYFLAG1); } t = gettok(); } continue; } if (!tok_in_line) error( "missing tokens or illegal tokens"); break; } continue; case LCURLY: defout(); cpycode(); t = gettok(); continue; default: error("syntax error"); } } if (t == ENDFILE) { error("unexpected EOF before %%%%"); } /* t is MARK */ defout(); end_toks(); /* all tokens dumped - get ready for reductions */ fprintf(ftable, "\n#ifdef __STDC__\n"); fprintf(ftable, "#include <stdlib.h>\n"); fprintf(ftable, "#include <string.h>\n"); fprintf(ftable, "#define YYCONST const\n"); fprintf(ftable, "#else\n"); fprintf(ftable, "#include <malloc.h>\n"); fprintf(ftable, "#include <memory.h>\n"); fprintf(ftable, "#define YYCONST\n"); fprintf(ftable, "#endif\n"); if (sym_prefix[0] != '\0') put_prefix_define(sym_prefix); fprintf(ftable, "\n#if defined(__cplusplus) || defined(__STDC__)\n"); fprintf(ftable, "\n#if defined(__cplusplus) && defined(__EXTERN_C__)\n"); fprintf(ftable, "extern \"C\" {\n"); fprintf(ftable, "#endif\n"); fprintf(ftable, "#ifndef yyerror\n"); fprintf(ftable, "#if defined(__cplusplus)\n"); fprintf(ftable, " void yyerror(YYCONST char *);\n"); fprintf(ftable, "#endif\n"); fprintf(ftable, "#endif\n"); fprintf(ftable, "#ifndef yylex\n"); fprintf(ftable, " int yylex(void);\n"); fprintf(ftable, "#endif\n"); fprintf(ftable, " int yyparse(void);\n"); fprintf(ftable, "#if defined(__cplusplus) && defined(__EXTERN_C__)\n"); fprintf(ftable, "}\n"); fprintf(ftable, "#endif\n"); fprintf(ftable, "\n#endif\n\n"); fprintf(ftable, "#define yyclearin yychar = -1\n"); fprintf(ftable, "#define yyerrok yyerrflag = 0\n"); fprintf(ftable, "extern int yychar;\nextern int yyerrflag;\n"); if (!(defunion || ntypes)) fprintf(ftable, "#ifndef YYSTYPE\n#define YYSTYPE int\n#endif\n"); fprintf(ftable, "YYSTYPE yylval;\n"); fprintf(ftable, "YYSTYPE yyval;\n"); fprintf(ftable, "typedef int yytabelem;\n"); fprintf(ftable, "#ifndef YYMAXDEPTH\n#define YYMAXDEPTH 150\n#endif\n"); fprintf(ftable, "#if YYMAXDEPTH > 0\n"); fprintf(ftable, "int yy_yys[YYMAXDEPTH], *yys = yy_yys;\n"); fprintf(ftable, "YYSTYPE yy_yyv[YYMAXDEPTH], *yyv = yy_yyv;\n"); fprintf(ftable, "#else /* user does initial allocation */\n"); fprintf(ftable, "int *yys;\nYYSTYPE *yyv;\n#endif\n"); fprintf(ftable, "static int yymaxdepth = YYMAXDEPTH;\n"); prdptr[0] = mem; /* added production */ *mem++ = NTBASE; /* if start is 0, we will overwrite with the lhs of the first rule */ *mem++ = start; *mem++ = 1; *mem++ = 0; prdptr[1] = mem; while ((t = gettok()) == LCURLY) cpycode(); if (t != C_IDENTIFIER) error("bad syntax on first rule"); if (!start) prdptr[0][1] = chfind(1, tokname); /* read rules */ while (t != MARK && t != ENDFILE) { /* process a rule */ if (t == L'|') { rhsfill((wchar_t *)0); /* restart fill of rhs */ *mem = *prdptr[nprod-1]; if (++mem >= &tracemem[new_memsize]) exp_mem(1); } else if (t == C_IDENTIFIER) { *mem = chfind(1, tokname); if (*mem < NTBASE) error("illegal nonterminal in grammar rule"); if (++mem >= &tracemem[new_memsize]) exp_mem(1); lhsfill(tokname); /* new rule: restart strings */ } else error("illegal rule: missing semicolon or | ?"); /* read rule body */ t = gettok(); more_rule: while (t == IDENTIFIER) { *mem = chfind(1, tokname); if (*mem < NTBASE) levprd[nprod] = toklev[*mem]& ~04; if (++mem >= &tracemem[new_memsize]) exp_mem(1); rhsfill(tokname); /* add to rhs string */ t = gettok(); } if (t == PREC) { if (gettok() != IDENTIFIER) error("illegal %%prec syntax"); j = chfind(2, tokname); if (j >= NTBASE) error("nonterminal %ls illegal after %%prec", nontrst[j-NTBASE].name); levprd[nprod] = toklev[j] & ~04; t = gettok(); } if (t == L'=') { had_act[nprod] = 1; levprd[nprod] |= ACTFLAG; fprintf(faction, "\ncase %d:", nprod); cpyact(mem-prdptr[nprod] - 1); fprintf(faction, " break;"); if ((t = gettok()) == IDENTIFIER) { /* action within rule... */ lrprnt(); /* dump lhs, rhs */ swprintf(actname, sizeof actname, L"$$%d", nprod); /* * make it nonterminal */ j = chfind(1, actname); /* * the current rule will become rule * number nprod+1 move the contents down, * and make room for the null */ if (mem + 2 >= &tracemem[new_memsize]) exp_mem(1); for (p = mem; p >= prdptr[nprod]; --p) p[2] = *p; mem += 2; /* enter null production for action */ p = prdptr[nprod]; *p++ = j; *p++ = -nprod; /* update the production information */ levprd[nprod+1] = levprd[nprod] & ~ACTFLAG; levprd[nprod] = ACTFLAG; if (++nprod >= nprodsz) exp_prod(); prdptr[nprod] = p; /* * make the action appear in * the original rule */ *mem++ = j; if (mem >= &tracemem[new_memsize]) exp_mem(1); /* get some more of the rule */ goto more_rule; } } while (t == L';') t = gettok(); *mem++ = -nprod; if (mem >= &tracemem[new_memsize]) exp_mem(1); /* check that default action is reasonable */ if (ntypes && !(levprd[nprod] & ACTFLAG) && nontrst[*prdptr[nprod]-NTBASE].tvalue) { /* no explicit action, LHS has value */ register int tempty; tempty = prdptr[nprod][1]; if (tempty < 0) error("must return a value, since LHS has a type"); else if (tempty >= NTBASE) tempty = nontrst[tempty-NTBASE].tvalue; else tempty = TYPE(toklev[tempty]); if (tempty != nontrst[*prdptr[nprod]-NTBASE].tvalue) { error( "default action causes potential type clash"); } } if (++nprod >= nprodsz) exp_prod(); prdptr[nprod] = mem; levprd[nprod] = 0; } /* end of all rules */ end_debug(); /* finish fdebug file's input */ finact(); if (t == MARK) { if (gen_lines) fprintf(ftable, "\n# line %d \"%s\"\n", lineno, infile); while ((c = getwc(finput)) != EOF) putwc(c, ftable); } fclose(finput); } static void finact(void) { /* finish action routine */ fclose(faction); fprintf(ftable, "# define YYERRCODE %d\n", tokset[2].value); } static wchar_t * cstash(s) register wchar_t *s; { wchar_t *temp; static int used = 0; static int used_save = 0; static int exp_cname = CNAMSZ; int len = wcslen(s); /* * 2/29/88 - * Don't need to expand the table, just allocate new space. */ used_save = used; while (len >= (exp_cname - used_save)) { exp_cname += CNAMSZ; if (!used) free(cnames); if ((cnames = malloc(sizeof (wchar_t)*exp_cname)) == NULL) error("cannot expand string dump"); cnamp = cnames; used = 0; } temp = cnamp; do { *cnamp++ = *s; } while (*s++); used += cnamp - temp; return (temp); } static int defin(int t, register wchar_t *s) { /* define s to be a terminal if t=0 or a nonterminal if t=1 */ register int val = 0; if (t) { if (++nnonter >= nnontersz) exp_nonterm(); nontrst[nnonter].name = cstash(s); return (NTBASE + nnonter); } /* must be a token */ if (++ntokens >= ntoksz) exp_ntok(); tokset[ntokens].name = cstash(s); /* establish value for token */ if (s[0] == L' ' && s[2] == 0) { /* single character literal */ val = findchtok(s[1]); } else if (s[0] == L' ' && s[1] == L'\\') { /* escape sequence */ if (s[3] == 0) { /* single character escape sequence */ switch (s[2]) { /* character which is escaped */ case L'a': warning(1, "\\a is ANSI C \"alert\" character"); #if __STDC__ - 1 == 0 val = L'\a'; break; #else val = L'\007'; break; #endif case L'v': val = L'\v'; break; case L'n': val = L'\n'; break; case L'r': val = L'\r'; break; case L'b': val = L'\b'; break; case L't': val = L'\t'; break; case L'f': val = L'\f'; break; case L'\'': val = L'\''; break; case L'"': val = L'"'; break; case L'?': val = L'?'; break; case L'\\': val = L'\\'; break; default: error("invalid escape"); } } else if (s[2] <= L'7' && s[2] >= L'0') { /* \nnn sequence */ int i = 3; val = s[2] - L'0'; while (iswdigit(s[i]) && i <= 4) { if (s[i] >= L'0' && s[i] <= L'7') val = val * 8 + s[i] - L'0'; else error("illegal octal number"); i++; } if (s[i] != 0) error("illegal \\nnn construction"); if (val > 255) error( "\\nnn exceed \\377; use \\xnnnnnnnn for wchar_t value of multibyte char"); if (val == 0 && i >= 4) error("'\\000' is illegal"); } else if (s[2] == L'x') { /* hexadecimal \xnnn sequence */ int i = 3; val = 0; warning(1, "\\x is ANSI C hex escape"); if (iswxdigit(s[i])) while (iswxdigit(s[i])) { int tmpval; if (iswdigit(s[i])) tmpval = s[i] - L'0'; else if (s[i] >= L'a') tmpval = s[i] - L'a' + 10; else tmpval = s[i] - L'A' + 10; val = 16 * val + tmpval; i++; } else error("illegal hexadecimal number"); if (s[i] != 0) error("illegal \\xnn construction"); #define LWCHAR_MAX 0x7fffffff if ((unsigned)val > LWCHAR_MAX) error(" \\xnnnnnnnn exceed %#x", LWCHAR_MAX); if (val == 0) error("'\\x00' is illegal"); val = findchtok(val); } else error("invalid escape"); } else { val = extval++; } tokset[ntokens].value = val; toklev[ntokens] = 0; return (ntokens); } static void defout(void) { /* write out the defines (at the end of the declaration section) */ register int i, c; register wchar_t *cp; for (i = ndefout; i <= ntokens; ++i) { cp = tokset[i].name; if (*cp == L' ') /* literals */ { fprintf(fdebug, "\t\"%ls\",\t%d,\n", tokset[i].name + 1, tokset[i].value); continue; /* was cp++ */ } for (; (c = *cp) != 0; ++cp) { if (iswlower(c) || iswupper(c) || iswdigit(c) || c == L'_') /* EMPTY */; else goto nodef; } fprintf(fdebug, "\t\"%ls\",\t%d,\n", tokset[i].name, tokset[i].value); fprintf(ftable, "# define %ls %d\n", tokset[i].name, tokset[i].value); if (fdefine != NULL) fprintf(fdefine, "# define %ls %d\n", tokset[i].name, tokset[i].value); nodef:; } ndefout = ntokens+1; } static int gettok(void) { register int i, base; static int peekline; /* number of '\n' seen in lookahead */ register int c, match, reserve; begin: reserve = 0; lineno += peekline; peekline = 0; c = getwc(finput); /* * while (c == ' ' || c == '\n' || c == '\t' || c == '\f') { */ while (iswspace(c)) { if (c == L'\n') ++lineno; c = getwc(finput); } if (c == L'/') { /* skip comment */ lineno += skipcom(); goto begin; } switch (c) { case EOF: return (ENDFILE); case L'{': ungetwc(c, finput); return (L'='); /* action ... */ case L'<': /* get, and look up, a type name (union member name) */ i = 0; while ((c = getwc(finput)) != L'>' && c != EOF && c != L'\n') { tokname[i] = c; if (++i >= toksize) exp_tokname(); } if (c != L'>') error("unterminated < ... > clause"); tokname[i] = 0; if (i == 0) error("missing type name in < ... > clause"); for (i = 1; i <= ntypes; ++i) { if (!wcscmp(typeset[i], tokname)) { numbval = i; return (TYPENAME); } } typeset[numbval = ++ntypes] = cstash(tokname); return (TYPENAME); case L'"': case L'\'': match = c; tokname[0] = L' '; i = 1; for (;;) { c = getwc(finput); if (c == L'\n' || c == EOF) error("illegal or missing ' or \""); if (c == L'\\') { c = getwc(finput); tokname[i] = L'\\'; if (++i >= toksize) exp_tokname(); } else if (c == match) break; tokname[i] = c; if (++i >= toksize) exp_tokname(); } break; case L'%': case L'\\': switch (c = getwc(finput)) { case L'0': return (TERM); case L'<': return (LEFT); case L'2': return (BINARY); case L'>': return (RIGHT); case L'%': case L'\\': return (MARK); case L'=': return (PREC); case L'{': return (LCURLY); default: reserve = 1; } default: if (iswdigit(c)) { /* number */ numbval = c - L'0'; base = (c == L'0') ? 8 : 10; for (c = getwc(finput); iswdigit(c); c = getwc(finput)) { numbval = numbval*base + c - L'0'; } ungetwc(c, finput); return (NUMBER); } else if (iswlower(c) || iswupper(c) || c == L'_' || c == L'.' || c == L'$') { i = 0; while (iswlower(c) || iswupper(c) || iswdigit(c) || c == L'_' || c == L'.' || c == L'$') { tokname[i] = c; if (reserve && iswupper(c)) tokname[i] = towlower(c); if (++i >= toksize) exp_tokname(); c = getwc(finput); } } else return (c); ungetwc(c, finput); } tokname[i] = 0; if (reserve) { /* find a reserved word */ if (!wcscmp(tokname, L"term")) return (TERM); if (!wcscmp(tokname, L"token")) return (TERM); if (!wcscmp(tokname, L"left")) return (LEFT); if (!wcscmp(tokname, L"nonassoc")) return (BINARY); if (!wcscmp(tokname, L"binary")) return (BINARY); if (!wcscmp(tokname, L"right")) return (RIGHT); if (!wcscmp(tokname, L"prec")) return (PREC); if (!wcscmp(tokname, L"start")) return (START); if (!wcscmp(tokname, L"type")) return (TYPEDEF); if (!wcscmp(tokname, L"union")) return (UNION); error("invalid escape, or illegal reserved word: %ls", tokname); } /* look ahead to distinguish IDENTIFIER from C_IDENTIFIER */ c = getwc(finput); /* * while (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '/') * { */ while (iswspace(c) || c == L'/') { if (c == L'\n') { ++peekline; } else if (c == L'/') { /* look for comments */ peekline += skipcom(); } c = getwc(finput); } if (c == L':') return (C_IDENTIFIER); ungetwc(c, finput); return (IDENTIFIER); } static int fdtype(int t) { /* determine the type of a symbol */ register int v; if (t >= NTBASE) v = nontrst[t-NTBASE].tvalue; else v = TYPE(toklev[t]); if (v <= 0) error("must specify type for %ls", (t >= NTBASE) ? nontrst[t-NTBASE].name: tokset[t].name); return (v); } static int chfind(int t, register wchar_t *s) { int i; if (s[0] == ' ') t = 0; TLOOP(i) { if (!wcscmp(s, tokset[i].name)) { return (i); } } NTLOOP(i) { if (!wcscmp(s, nontrst[i].name)) { return (i + NTBASE); } } /* cannot find name */ if (t > 1) error("%ls should have been defined earlier", s); return (defin(t, s)); } static void cpyunion(void) { /* * copy the union declaration to the output, * and the define file if present */ int level, c; if (gen_lines) fprintf(ftable, "\n# line %d \"%s\"\n", lineno, infile); fprintf(ftable, "typedef union\n"); if (fdefine) fprintf(fdefine, "\ntypedef union\n"); fprintf(ftable, "#ifdef __cplusplus\n\tYYSTYPE\n#endif\n"); if (fdefine) fprintf(fdefine, "#ifdef __cplusplus\n\tYYSTYPE\n#endif\n"); level = 0; for (;;) { if ((c = getwc(finput)) == EOF) error("EOF encountered while processing %%union"); putwc(c, ftable); if (fdefine) putwc(c, fdefine); switch (c) { case L'\n': ++lineno; break; case L'{': ++level; break; case L'}': --level; if (level == 0) { /* we are finished copying */ fprintf(ftable, " YYSTYPE;\n"); if (fdefine) fprintf(fdefine, " YYSTYPE;\nextern YYSTYPE yylval;\n"); return; } } } } static void cpycode(void) { /* copies code between \{ and \} */ int c; c = getwc(finput); if (c == L'\n') { c = getwc(finput); lineno++; } if (gen_lines) fprintf(ftable, "\n# line %d \"%s\"\n", lineno, infile); while (c != EOF) { if (c == L'\\') { if ((c = getwc(finput)) == L'}') return; else putwc(L'\\', ftable); } else if (c == L'%') { if ((c = getwc(finput)) == L'}') return; else putwc(L'%', ftable); } putwc(c, ftable); if (c == L'\n') ++lineno; c = getwc(finput); } error("eof before %%}"); } static int skipcom(void) { /* skip over comments */ register int c, i = 0; /* i is the number of lines skipped */ /* skipcom is called after reading a / */ if (getwc(finput) != L'*') error("illegal comment"); c = getwc(finput); while (c != EOF) { while (c == L'*') { if ((c = getwc(finput)) == L'/') return (i); } if (c == L'\n') ++i; c = getwc(finput); } error("EOF inside comment"); /* NOTREACHED */ return 0; } static void cpyact(int offset) { /* copy C action to the next ; or closing } */ int brac, c, match, i, t, j, s, tok, argument, m; wchar_t id_name[NAMESIZE+1]; int id_idx = 0; if (gen_lines) { fprintf(faction, "\n# line %d \"%s\"\n", lineno, infile); act_lines++; } brac = 0; id_name[0] = 0; loop: c = getwc(finput); swt: switch (c) { case L';': if (brac == 0) { putwc(c, faction); return; } goto lcopy; case L'{': brac++; goto lcopy; case L'$': s = 1; tok = -1; argument = 1; while ((c = getwc(finput)) == L' ' || c == L'\t') /* EMPTY */; if (c == L'<') { /* type description */ ungetwc(c, finput); if (gettok() != TYPENAME) error("bad syntax on $<ident> clause"); tok = numbval; c = getwc(finput); } if (c == L'$') { fprintf(faction, "yyval"); if (ntypes) { /* put out the proper tag... */ if (tok < 0) tok = fdtype(*prdptr[nprod]); fprintf(faction, ".%ls", typeset[tok]); } goto loop; } if (iswalpha(c)) { int same = 0; int id_sw = 0; ungetwc(c, finput); if (gettok() != IDENTIFIER) error("bad action format"); /* * Save the number of non-terminal */ id_sw = nnonter; t = chfind(1, tokname); /* * Check if the identifier is added as a non-terminal */ if (id_sw != nnonter) id_sw = 1; else id_sw = 0; while ((c = getwc(finput)) == L' ' || c == L'\t') /* EMPTY */; if (c == L'#') { while ((c = getwc(finput)) == L' ' || c == L'\t') /* EMPTY */; if (iswdigit(c)) { m = 0; while (iswdigit(c)) { m = m*10+c-L'0'; c = getwc(finput); } argument = m; } else error("illegal character \"#\""); } if (argument < 1) error("illegal action argument no."); for (i = 1; i <= offset; ++i) if (prdptr[nprod][i] == t) if (++same == argument) { fprintf(faction, "yypvt[-%d]", offset-i); if (ntypes) { if (tok < 0) tok = /* CSTYLED */ fdtype(prdptr[nprod][i]); fprintf(faction, ".%ls", typeset[tok]); } goto swt; } /* * This used to be handled as error. * Treat this as a valid C statement. * (Likely id with $ in.) * If non-terminal is added, remove it from the list. */ fprintf(faction, "$%ls", tokname); warning(1, "Illegal character '$' in Ansi C symbol: %ls$%ls.", id_name, tokname); if (id_sw == 1) --nnonter; goto swt; } if (c == '-') { s = -s; c = getwc(finput); } if (iswdigit(c)) { j = 0; while (iswdigit(c)) { j = j*10 + c - L'0'; c = getwc(finput); } j = j*s - offset; if (j > 0) { error("Illegal use of $%d", j + offset); } fprintf(faction, "yypvt[-%d]", -j); if (ntypes) { /* put out the proper tag */ if (j + offset <= 0 && tok < 0) error("must specify type of $%d", j + offset); if (tok < 0) tok = fdtype(prdptr[nprod][j+offset]); fprintf(faction, ".%ls", typeset[tok]); } goto swt; } putwc(L'$', faction); if (s < 0) putwc(L'-', faction); goto swt; case L'}': if (--brac) goto lcopy; putwc(c, faction); return; case L'/': /* look for comments */ putwc(c, faction); c = getwc(finput); if (c != L'*') goto swt; /* it really is a comment */ putwc(c, faction); c = getwc(finput); while (c != EOF) { while (c == L'*') { putwc(c, faction); if ((c = getwc(finput)) == L'/') goto lcopy; } putwc(c, faction); if (c == L'\n') ++lineno; c = getwc(finput); } error("EOF inside comment"); /* FALLTHRU */ case L'\'': /* character constant */ case L'"': /* character string */ match = c; putwc(c, faction); while ((c = getwc(finput)) != EOF) { if (c == L'\\') { putwc(c, faction); c = getwc(finput); if (c == L'\n') ++lineno; } else if (c == match) goto lcopy; else if (c == L'\n') error("newline in string or char. const."); putwc(c, faction); } error("EOF in string or character constant"); /* FALLTHRU */ case EOF: error("action does not terminate"); /* FALLTHRU */ case L'\n': ++lineno; goto lcopy; } lcopy: putwc(c, faction); /* * Save the possible identifier name. * Used to print out a warning message. */ if (id_idx >= NAMESIZE) { /* * Error. Silently ignore. */ ; } /* * If c has a possibility to be a * part of identifier, save it. */ else if (iswalnum(c) || c == L'_') { id_name[id_idx++] = c; id_name[id_idx] = 0; } else { id_idx = 0; id_name[id_idx] = 0; } goto loop; } static void lhsfill(s) /* new rule, dump old (if exists), restart strings */ wchar_t *s; { static int lhs_len = LHS_TEXT_LEN; int s_lhs = wcslen(s); if (s_lhs >= lhs_len) { lhs_len = s_lhs + 2; lhstext = realloc(lhstext, sizeof (wchar_t)*lhs_len); if (lhstext == NULL) error("couldn't expanded LHS length"); } rhsfill(NULL); wcscpy(lhstext, s); /* don't worry about too long of a name */ } static void rhsfill(wchar_t *s) /* either name or 0 */ { static wchar_t *loc; /* next free location in rhstext */ static int rhs_len = RHS_TEXT_LEN; static int used = 0; int s_rhs = (s == NULL ? 0 : wcslen(s)); register wchar_t *p; if (!s) /* print out and erase old text */ { if (*lhstext) /* there was an old rule - dump it */ lrprnt(); (loc = rhstext)[0] = 0; return; } /* add to stuff in rhstext */ p = s; used = loc - rhstext; if ((s_rhs + 3) >= (rhs_len - used)) { static wchar_t *textbase; textbase = rhstext; rhs_len += s_rhs + RHS_TEXT_LEN; rhstext = realloc(rhstext, sizeof (wchar_t)*rhs_len); if (rhstext == NULL) error("couldn't expanded RHS length"); loc = loc - textbase + rhstext; } *loc++ = L' '; if (*s == L' ') /* special quoted symbol */ { *loc++ = L'\''; /* add first quote */ p++; } while (*loc = *p++) if (loc++ > &rhstext[ RHS_TEXT_LEN ] - 3) break; if (*s == L' ') *loc++ = L'\''; *loc = 0; /* terminate the string */ } static void lrprnt (void) /* print out the left and right hand sides */ { wchar_t *rhs; wchar_t *m_rhs = NULL; if (!*rhstext) /* empty rhs - print usual comment */ rhs = L" /* empty */"; else { int idx1; /* tmp idx used to find if there are d_quotes */ int idx2; /* tmp idx used to generate escaped string */ wchar_t *p; /* * Check if there are any double quote in RHS. */ for (idx1 = 0; rhstext[idx1] != 0; idx1++) { if (rhstext[idx1] == L'"') { /* * A double quote is found. */ idx2 = wcslen(rhstext)*2; p = m_rhs = malloc((idx2 + 1)*sizeof (wchar_t)); if (m_rhs == NULL) error( "Couldn't allocate memory for RHS."); /* * Copy string */ for (idx2 = 0; rhstext[idx2] != 0; idx2++) { /* * Check if this quote is escaped or not */ if (rhstext[idx2] == L'"') { int tmp_l = idx2-1; int cnt = 0; while (tmp_l >= 0 && rhstext[tmp_l] == '\\') { cnt++; tmp_l--; } /* * If quote is not escaped, * then escape it. */ if (cnt%2 == 0) *p++ = L'\\'; } *p++ = rhstext[idx2]; } *p = 0; /* * Break from the loop */ break; } } if (m_rhs == NULL) rhs = rhstext; else rhs = m_rhs; } fprintf(fdebug, "\t\"%ls :%ls\",\n", lhstext, rhs); if (m_rhs) free(m_rhs); } static void beg_debug (void) /* dump initial sequence for fdebug file */ { fprintf(fdebug, "typedef struct\n"); fprintf(fdebug, "#ifdef __cplusplus\n\tyytoktype\n"); fprintf(fdebug, "#endif\n{\n"); fprintf(fdebug, "#ifdef __cplusplus\nconst\n#endif\n"); fprintf(fdebug, "char *t_name; int t_val; } yytoktype;\n"); fprintf(fdebug, "#ifndef YYDEBUG\n#\tdefine YYDEBUG\t%d", gen_testing); fprintf(fdebug, "\t/*%sallow debugging */\n#endif\n\n", gen_testing ? " " : " don't "); fprintf(fdebug, "#if YYDEBUG\n\nyytoktype yytoks[] =\n{\n"); } static void end_toks (void) /* finish yytoks array, get ready for yyred's strings */ { fprintf(fdebug, "\t\"-unknown-\",\t-1\t/* ends search */\n"); fprintf(fdebug, "};\n\n"); fprintf(fdebug, "#ifdef __cplusplus\nconst\n#endif\n"); fprintf(fdebug, "char * yyreds[] =\n{\n"); fprintf(fdebug, "\t\"-no such reduction-\",\n"); } static void end_debug (void) /* finish yyred array, close file */ { lrprnt(); /* dump last lhs, rhs */ fprintf(fdebug, "};\n#endif /* YYDEBUG */\n"); fclose(fdebug); } /* * 2/29/88 - * The normal length for token sizes is NAMESIZE - If a token is * seen that has a longer length, expand "tokname" by NAMESIZE. */ static void exp_tokname(void) { toksize += NAMESIZE; tokname = realloc(tokname, sizeof (wchar_t) * toksize); } /* * 2/29/88 - * */ static void exp_prod(void) { int i; nprodsz += NPROD; prdptr = realloc(prdptr, sizeof (int *) * (nprodsz+2)); levprd = realloc(levprd, sizeof (int) * (nprodsz+2)); had_act = realloc(had_act, sizeof (wchar_t) * (nprodsz+2)); for (i = nprodsz-NPROD; i < nprodsz+2; ++i) had_act[i] = 0; if ((*prdptr == NULL) || (levprd == NULL) || (had_act == NULL)) error("couldn't expand productions"); } /* * 2/29/88 - * Expand the number of terminals. Initially there are NTERMS; * each time space runs out, the size is increased by NTERMS. * The total size, however, cannot exceed MAXTERMS because of * the way LOOKSETS(struct looksets) is set up. * Tables affected: * tokset, toklev : increased to ntoksz * * tables with initial dimensions of TEMPSIZE must be changed if * (ntoksz + NNONTERM) >= TEMPSIZE : temp1[] */ static void exp_ntok(void) { ntoksz += NTERMS; tokset = realloc(tokset, sizeof (TOKSYMB) * ntoksz); toklev = realloc(toklev, sizeof (int) * ntoksz); if ((tokset == NULL) || (toklev == NULL)) error("couldn't expand NTERMS"); } static void exp_nonterm(void) { nnontersz += NNONTERM; nontrst = realloc(nontrst, sizeof (TOKSYMB) * nnontersz); if (nontrst == NULL) error("couldn't expand NNONTERM"); } void exp_mem(int flag) { int i; static int *membase; new_memsize += MEMSIZE; membase = tracemem; tracemem = realloc(tracemem, sizeof (int) * new_memsize); if (tracemem == NULL) error("couldn't expand mem table"); if (flag) { for (i = 0; i <= nprod; ++i) prdptr[i] = prdptr[i] - membase + tracemem; mem = mem - membase + tracemem; } else { size += MEMSIZE; temp1 = realloc(temp1, sizeof (int)*size); optimmem = optimmem - membase + tracemem; } } static int findchtok(int chlit) /* * findchtok(chlit) returns the token number for a character literal * chlit that is "bigger" than 255 -- the max char value that the * original yacc was build for. This yacc treate them as though * an ordinary token. */ { int i; if (chlit < 0xff) return (chlit); /* single-byte char */ for (i = 0; i < nmbchars; ++i) { if (mbchars->character == chlit) return (mbchars->tvalue); } /* Not found. Register it! */ if (++nmbchars > nmbcharsz) { /* Make sure there's enough space */ nmbcharsz += NMBCHARSZ; mbchars = realloc(mbchars, sizeof (MBCLIT)*nmbcharsz); if (mbchars == NULL) error("too many character literals"); } mbchars[nmbchars-1].character = chlit; return (mbchars[nmbchars-1].tvalue = extval++); /* Return the newly assigned token. */ } /* * When -p is specified, symbol prefix for * yy{parse, lex, error}(), * yy{lval, val, char, debug, errflag, nerrs} * are defined to the specified name. */ static void put_prefix_define(char *pre) { char *syms[] = { /* Functions */ "parse", "lex", "error", /* Variables */ "lval", "val", "char", "debug", "errflag", "nerrs", NULL}; int i; for (i = 0; syms[i]; i++) fprintf(ftable, "#define\tyy%s\t%s%s\n", syms[i], pre, syms[i]); }
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 1990 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ /* from OpenSolaris "y3.c 6.17 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)y3.c 1.5 (gritter) 11/26/05 */ #include "dextern" static void go2gen(int); static void precftn(int, int, int); static void wract(int); static void wrstate(int); static void wdef(wchar_t *, int); #ifndef NOLIBW static void wrmbchars(void); #endif /* !NOLIBW */ /* important local variables */ static int lastred; /* number of the last reduction of a state */ int *defact; extern int *toklev; extern int cwp; /* print the output for the states */ void output(void) { int i, k, c; register WSET *u, *v; fprintf(ftable, "static YYCONST yytabelem yyexca[] ={\n"); SLOOP(i) { /* output the stuff for state i */ nolook = !(tystate[i] == MUSTLOOKAHEAD); closure(i); /* output actions */ nolook = 1; aryfil(temp1, ntoksz+nnontersz+1, 0); WSLOOP(wsets, u) { c = *(u->pitem); if (c > 1 && c < NTBASE && temp1[c] == 0) { WSLOOP(u, v) { if (c == *(v->pitem)) putitem(v->pitem + 1, (LOOKSETS *)0); } temp1[c] = state(c); } else if (c > NTBASE && temp1[(c -= NTBASE) + ntokens] == 0) { temp1[c + ntokens] = amem[indgo[i] + c]; } } if (i == 1) temp1[1] = ACCEPTCODE; /* now, we have the shifts; look at the reductions */ lastred = 0; WSLOOP(wsets, u) { c = *(u->pitem); if (c <= 0) { /* reduction */ lastred = -c; TLOOP(k) { if (BIT(u->ws.lset, k)) { if (temp1[k] == 0) temp1[k] = c; else if (temp1[k] < 0) { /* * reduce/reduce * conflict */ if (foutput != NULL) fprintf(foutput, "\n%d: reduce/reduce conflict" " (red'ns %d and %d ) on %ls", i, -temp1[k], lastred, symnam(k)); if (-temp1[k] > lastred) temp1[k] = -lastred; ++zzrrconf; } else /* * potentia * shift/reduce * conflict. */ precftn(lastred, k, i); } } } } wract(i); } fprintf(ftable, "\t};\n"); wdef(L"YYNPROD", nprod); #ifndef NOLIBW if (nmbchars > 0) { wrmbchars(); } #endif /* !NOLIBW */ } static int pkdebug = 0; int apack(int *p, int n) { /* pack state i from temp1 into amem */ int off; register int *pp, *qq; int *q, /**r,*/ *rr; int diff; /* * we don't need to worry about checking because we * we will only look up entries known to be there... */ /* eliminate leading and trailing 0's */ q = p + n; for (pp = p, off = 0; *pp == 0 && pp <= q; ++pp, --off) /* EMPTY */; if (pp > q) return (0); /* no actions */ p = pp; /* now, find a place for the elements from p to q, inclusive */ /* for( rr=amem; rr<=r; ++rr,++off ){ */ /* try rr */ rr = amem; for (; ; ++rr, ++off) { while (rr >= &amem[new_actsize-1]) exp_act(&rr); qq = rr; for (pp = p; pp <= q; ++pp, ++qq) { if (*pp) { diff = qq - rr; while (qq >= &amem[new_actsize-1]) { exp_act(&rr); qq = diff + rr; } if (*pp != *qq && *qq != 0) goto nextk; } } /* we have found an acceptable k */ if (pkdebug && foutput != NULL) fprintf(foutput, "off = %d, k = %d\n", off, rr-amem); qq = rr; for (pp = p; pp <= q; ++pp, ++qq) { if (*pp) { diff = qq - rr; while (qq >= &amem[new_actsize-1]) { exp_act(&rr); qq = diff + rr; } if (qq > memp) memp = qq; *qq = *pp; } } if (pkdebug && foutput != NULL) { for (pp = amem; pp <= memp; pp += 10) { fprintf(foutput, "\t"); for (qq = pp; qq <= pp + 9; ++qq) fprintf(foutput, "%d ", *qq); fprintf(foutput, "\n"); } } return (off); nextk:; } /* error("no space in action table" ); */ /* NOTREACHED */ } void go2out(void) { /* output the gotos for the nontermninals */ int i, j, k, best, count, cbest, times; fprintf(ftemp, "$\n"); /* mark begining of gotos */ for (i = 1; i <= nnonter; ++i) { go2gen(i); /* find the best one to make default */ best = -1; times = 0; for (j = 0; j < nstate; ++j) { /* is j the most frequent */ if (tystate[j] == 0) continue; if (tystate[j] == best) continue; /* is tystate[j] the most frequent */ count = 0; cbest = tystate[j]; for (k = j; k < nstate; ++k) if (tystate[k] == cbest) ++count; if (count > times) { best = cbest; times = count; } } /* best is now the default entry */ zzgobest += (times-1); for (j = 0; j < nstate; ++j) { if (tystate[j] != 0 && tystate[j] != best) { fprintf(ftemp, "%d,%d,", j, tystate[j]); zzgoent += 1; } } /* now, the default */ zzgoent += 1; fprintf(ftemp, "%d\n", best); } } static int g2debug = 0; static void go2gen(int c) { /* output the gotos for nonterminal c */ int i, work, cc; ITEM *p, *q; /* first, find nonterminals with gotos on c */ aryfil(temp1, nnonter + 1, 0); temp1[c] = 1; work = 1; while (work) { work = 0; PLOOP(0, i) { if ((cc = prdptr[i][1] - NTBASE) >= 0) { /* cc is a nonterminal */ if (temp1[cc] != 0) { /* * cc has a goto on c * thus, the left side of * production i does too. */ cc = *prdptr[i] - NTBASE; if (temp1[cc] == 0) { work = 1; temp1[cc] = 1; } } } } } /* now, we have temp1[c] = 1 if a goto on c in closure of cc */ if (g2debug && foutput != NULL) { fprintf(foutput, "%ls: gotos on ", nontrst[c].name); NTLOOP(i) if (temp1[i]) fprintf(foutput, "%ls ", nontrst[i].name); fprintf(foutput, "\n"); } /* now, go through and put gotos into tystate */ aryfil(tystate, nstate, 0); SLOOP(i) { ITMLOOP(i, p, q) { if ((cc = *p->pitem) >= NTBASE) { if (temp1[cc -= NTBASE]) { /* goto on c is possible */ tystate[i] = amem[indgo[i] + c]; break; } } } } } /* decide a shift/reduce conflict by precedence. */ static void precftn(int r, int t, int s) { /* * r is a rule number, t a token number * the conflict is in state s * temp1[t] is changed to reflect the action */ int lp, lt, action; lp = levprd[r]; lt = toklev[t]; if (PLEVEL(lt) == 0 || PLEVEL(lp) == 0) { /* conflict */ if (foutput != NULL) fprintf(foutput, "\n%d: shift/reduce conflict" " (shift %d, red'n %d) on %ls", s, temp1[t], r, symnam(t)); ++zzsrconf; return; } if (PLEVEL(lt) == PLEVEL(lp)) action = ASSOC(lt) & ~04; else if (PLEVEL(lt) > PLEVEL(lp)) action = RASC; /* shift */ else action = LASC; /* reduce */ switch (action) { case BASC: /* error action */ temp1[t] = ERRCODE; return; case LASC: /* reduce */ temp1[t] = -r; return; } } static void wract(int i) { /* output state i */ /* temp1 has the actions, lastred the default */ int p, p0, p1; int ntimes, tred, count, j; int flag; /* find the best choice for lastred */ lastred = 0; ntimes = 0; TLOOP(j) { if (temp1[j] >= 0) continue; if (temp1[j] + lastred == 0) continue; /* count the number of appearances of temp1[j] */ count = 0; tred = -temp1[j]; levprd[tred] |= REDFLAG; TLOOP(p) { if (temp1[p] + tred == 0) ++count; } if (count > ntimes) { lastred = tred; ntimes = count; } } /* * for error recovery, arrange that, if there is a shift on the * error recovery token, `error', that the default be the error action */ if (temp1[2] > 0) lastred = 0; /* clear out entries in temp1 which equal lastred */ TLOOP(p) { if (temp1[p] + lastred == 0) temp1[p] = 0; } wrstate(i); defact[i] = lastred; flag = 0; TLOOP(p0) { if ((p1 = temp1[p0]) != 0) { if (p1 < 0) { p1 = -p1; goto exc; } else if (p1 == ACCEPTCODE) { p1 = -1; goto exc; } else if (p1 == ERRCODE) { p1 = 0; goto exc; exc: if (flag++ == 0) fprintf(ftable, "-1, %d,\n", i); fprintf(ftable, "\t%d, %d,\n", tokset[p0].value, p1); ++zzexcp; } else { fprintf(ftemp, "%d,%d,", tokset[p0].value, p1); ++zzacent; } } } if (flag) { defact[i] = -2; fprintf(ftable, "\t-2, %d,\n", lastred); } fprintf(ftemp, "\n"); } static void wrstate(int i) { /* writes state i */ register int j0, j1; register ITEM *pp, *qq; register WSET *u; if (foutput == NULL) return; fprintf(foutput, "\nstate %d\n", i); ITMLOOP(i, pp, qq) { fprintf(foutput, "\t%ls\n", writem(pp->pitem)); } if (tystate[i] == MUSTLOOKAHEAD) { /* print out empty productions in closure */ WSLOOP(wsets + (pstate[i + 1] - pstate[i]), u) { if (*(u->pitem) < 0) fprintf(foutput, "\t%ls\n", writem(u->pitem)); } } /* check for state equal to another */ TLOOP(j0) if ((j1 = temp1[j0]) != 0) { fprintf(foutput, "\n\t%ls ", symnam(j0)); if (j1 > 0) { /* shift, error, or accept */ if (j1 == ACCEPTCODE) fprintf(foutput, "accept"); else if (j1 == ERRCODE) fprintf(foutput, "error"); else fprintf(foutput, "shift %d", j1); } else fprintf(foutput, "reduce %d", -j1); } /* output the final production */ if (lastred) fprintf(foutput, "\n\t. reduce %d\n\n", lastred); else fprintf(foutput, "\n\t. error\n\n"); /* now, output nonterminal actions */ j1 = ntokens; for (j0 = 1; j0 <= nnonter; ++j0) { if (temp1[++j1]) fprintf(foutput, "\t%ls goto %d\n", symnam(j0 + NTBASE), temp1[j1]); } } static void wdef(wchar_t *s, int n) { /* output a definition of s to the value n */ fprintf(ftable, "# define %ls %d\n", s, n); } void warray(wchar_t *s, int *v, int n) { register int i; fprintf(ftable, "static YYCONST yytabelem %ls[]={\n", s); for (i = 0; i < n; ) { if (i % 10 == 0) fprintf(ftable, "\n"); fprintf(ftable, "%6d", v[i]); if (++i == n) fprintf(ftable, " };\n"); else fprintf(ftable, ","); } } void hideprod(void) { /* * in order to free up the mem and amem arrays for the optimizer, * and still be able to output yyr1, etc., after the sizes of * the action array is known, we hide the nonterminals * derived by productions in levprd. */ register int i, j; j = 0; levprd[0] = 0; PLOOP(1, i) { if (!(levprd[i] & REDFLAG)) { ++j; if (foutput != NULL) { fprintf(foutput, "Rule not reduced: %ls\n", writem(prdptr[i])); } } levprd[i] = *prdptr[i] - NTBASE; } if (j) fprintf(stderr, "%d rules never reduced\n", j); } #ifndef NOLIBW static int cmpmbchars(MBCLIT *p, MBCLIT *q) { /* Compare two MBLITs. */ return ((p->character) - (q->character)); } static void wrmbchars(void) { int i; wdef(L"YYNMBCHARS", nmbchars); qsort(mbchars, nmbchars, sizeof (*mbchars), (int (*)(const void *, const void *))cmpmbchars); fprintf(ftable, "static struct{\n\twchar_t character;" "\n\tint tvalue;\n}yymbchars[YYNMBCHARS]={\n"); for (i = 0; i < nmbchars; ++i) { fprintf(ftable, "\t{%#x,%d}", (int)mbchars[i].character, mbchars[i].tvalue); if (i < nmbchars - 1) { /* Not the last. */ fprintf(ftable, ",\n"); } } fprintf(ftable, "\n};\n"); } #endif /* !NOLIBW */
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 1990 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ /* from OpenSolaris "y4.c 6.15 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)y4.c 1.5 (gritter) 11/26/05 */ #include "dextern" #include <wchar.h> #include <unistd.h> #define NOMORE -1000 static void gin(int); static void stin(int); static void osummary(void); static void aoutput(void); static void arout(wchar_t *, int *, int); static int nxti(void); static int gtnm(void); static int *ggreed; static int *pgo; static int *yypgo; static int maxspr = 0; /* maximum spread of any entry */ static int maxoff = 0; /* maximum offset into an array */ int *optimmem; static int *maxa; static int nxdb = 0; static int adb = 0; void callopt(void) { register int i, *p, j, k, *q; ggreed = malloc(sizeof (int) * size); pgo = malloc(sizeof (int) * size); yypgo = &nontrst[0].tvalue; /* read the arrays from tempfile and set parameters */ if ((finput = fopen(TEMPNAME, "r")) == NULL) error("optimizer cannot open tempfile"); optimmem = tracemem; pgo[0] = 0; temp1[0] = 0; nstate = 0; nnonter = 0; for (;;) { switch (gtnm()) { case L'\n': temp1[++nstate] = (--optimmem) - tracemem; /* FALLTHRU */ case L',': continue; case L'$': break; default: error("bad tempfile"); } break; } temp1[nstate] = yypgo[0] = (--optimmem) - tracemem; for (;;) { switch (gtnm()) { case L'\n': yypgo[++nnonter] = optimmem-tracemem; /* FALLTHRU */ case L',': continue; case EOF: break; default: error("bad tempfile"); } break; } yypgo[nnonter--] = (--optimmem) - tracemem; for (i = 0; i < nstate; ++i) { k = 32000000; j = 0; q = tracemem + temp1[i+1]; for (p = tracemem + temp1[i]; p < q; p += 2) { if (*p > j) j = *p; if (*p < k) k = *p; } if (k <= j) { /* * nontrivial situation * temporarily, kill this for compatibility */ /* j -= k; j is now the range */ if (k > maxoff) maxoff = k; } tystate[i] = (temp1[i+1] - temp1[i]) + 2*j; if (j > maxspr) maxspr = j; } /* initialize ggreed table */ for (i = 1; i <= nnonter; ++i) { ggreed[i] = 1; j = 0; /* minimum entry index is always 0 */ q = tracemem + yypgo[i+1] -1; for (p = tracemem + yypgo[i]; p < q; p += 2) { ggreed[i] += 2; if (*p > j) j = *p; } ggreed[i] = ggreed[i] + 2*j; if (j > maxoff) maxoff = j; } /* now, prepare to put the shift actions into the amem array */ for (i = 0; i < new_actsize; ++i) amem[i] = 0; maxa = amem; for (i = 0; i < nstate; ++i) { if (tystate[i] == 0 && adb > 1) fprintf(ftable, "State %d: null\n", i); indgo[i] = YYFLAG1; } while ((i = nxti()) != NOMORE) { if (i >= 0) stin(i); else gin(-i); } if (adb > 2) { /* print a array */ for (p = amem; p <= maxa; p += 10) { fprintf(ftable, "%4d ", p-amem); for (i = 0; i < 10; ++i) fprintf(ftable, "%4d ", p[i]); fprintf(ftable, "\n"); } } /* write out the output appropriate to the language */ aoutput(); osummary(); ZAPFILE(TEMPNAME); } static void gin(int i) { register int *r, *s, *q1, *q2; int *p; /* enter gotos on nonterminal i into array amem */ ggreed[i] = 0; q2 = tracemem + yypgo[i+1] - 1; q1 = tracemem + yypgo[i]; /* now, find a place for it */ /* for( p=amem; p < &amem[new_actsize]; ++p ){ */ p = amem; for (;;) { while (p >= &amem[new_actsize]) exp_act(&p); if (*p) goto nextgp; for (r = q1; r < q2; r += 2) { s = p + *r + 1; /* * Check if action table needs to * be expanded or not. If so, * expand it. */ while (s >= &amem[new_actsize]) { exp_act(&p); s = p + *r + 1; } if (*s) goto nextgp; if (s > maxa) { while ((maxa = s) >= &amem[new_actsize]) /* error( "amem array overflow" ); */ exp_act(&p); } } /* we have found a spot */ *p = *q2; if (p > maxa) { while ((maxa = p) >= &amem[new_actsize]) /* error("amem array overflow"); */ exp_act(&p); } for (r = q1; r < q2; r += 2) { s = p + *r + 1; /* * Check if action table needs to * be expanded or not. If so, * expand it. */ while (s >= &amem[new_actsize]) { exp_act(&p); s = p + *r + 1; } *s = r[1]; } pgo[i] = p - amem; if (adb > 1) fprintf(ftable, "Nonterminal %d, entry at %d\n", i, pgo[i]); goto nextgi; nextgp: ++p; } /* error( "cannot place goto %d\n", i ); */ nextgi:; } static void stin(int i) { register int *r, n, nn, flag, j, *q1, *q2; int *s; tystate[i] = 0; /* Enter state i into the amem array */ q2 = tracemem + temp1[i + 1]; q1 = tracemem + temp1[i]; /* Find an acceptable place */ nn = -maxoff; more: for (n = nn; n < new_actsize; ++n) { flag = 0; for (r = q1; r < q2; r += 2) { s = *r + n + amem; if (s < amem) goto nextn; /* * Check if action table needs to * be expanded or not. If so, * expand it. */ while (s >= &amem[new_actsize]) { exp_act(NULL); s = *r + n + amem; } if (*s == 0) ++flag; else if (*s != r[1]) goto nextn; } /* * check that the position equals another * only if the states are identical */ for (j = 0; j < nstate; ++j) { if (indgo[j] == n) { if (flag) /* * we have some disagreement. */ goto nextn; if (temp1[j+1] + temp1[i] == temp1[j] + temp1[i+1]) { /* states are equal */ indgo[i] = n; if (adb > 1) fprintf(ftable, "State %d: entry at" " %d equals state %d\n", i, n, j); return; } goto nextn; /* we have some disagreement */ } } for (r = q1; r < q2; r += 2) { while ((s = *r + n + amem) >= &amem[new_actsize]) { /* * error( "out of space"); */ exp_act(NULL); } if (s > maxa) maxa = s; if (*s != 0 && *s != r[1]) error( "clobber of amem array, pos'n %d, by %d", s-amem, r[1]); *s = r[1]; } indgo[i] = n; if (adb > 1) fprintf(ftable, "State %d: entry at %d\n", i, indgo[i]); return; nextn:; } /* error( "Error; failure to place state %d\n", i ); */ exp_act(NULL); nn = new_actsize - ACTSIZE; goto more; /* NOTREACHED */ } static int nxti(void) { /* finds the next i */ register int i, max, maxi = 0; max = 0; for (i = 1; i <= nnonter; ++i) if (ggreed[i] >= max) { max = ggreed[i]; maxi = -i; } for (i = 0; i < nstate; ++i) if (tystate[i] >= max) { max = tystate[i]; maxi = i; } if (nxdb) fprintf(ftable, "nxti = %d, max = %d\n", maxi, max); if (max == 0) return (NOMORE); else return (maxi); } static void osummary(void) { /* write summary */ register int i, *p; if (foutput == NULL) return; i = 0; for (p = maxa; p >= amem; --p) { if (*p == 0) ++i; } fprintf(foutput, "Optimizer space used: input %d/%d, output %d/%d\n", optimmem-tracemem + 1, new_memsize, maxa-amem + 1, new_actsize); fprintf(foutput, "%d table entries, %d zero\n", (maxa-amem) + 1, i); fprintf(foutput, "maximum spread: %d, maximum offset: %d\n", maxspr, maxoff); } static void aoutput(void) { /* this version is for C */ /* write out the optimized parser */ fprintf(ftable, "# define YYLAST %d\n", maxa-amem + 1); arout(L"yyact", amem, (maxa - amem) + 1); arout(L"yypact", indgo, nstate); arout(L"yypgo", pgo, nnonter + 1); } static void arout(wchar_t *s, int *v, int n) { register int i; fprintf(ftable, "static YYCONST yytabelem %ls[]={\n", s); for (i = 0; i < n; ) { if (i % 10 == 0) fprintf(ftable, "\n"); fprintf(ftable, "%6d", v[i]); if (++i == n) fprintf(ftable, " };\n"); else fprintf(ftable, ","); } } static int gtnm(void) { register int s, val, c; /* read and convert an integer from the standard input */ /* return the terminating character */ /* blanks, tabs, and newlines are ignored */ s = 1; val = 0; while ((c = getwc(finput)) != EOF) { if (iswdigit(c)) val = val * 10 + c - L'0'; else if (c == L'-') s = -1; else break; } *optimmem++ = s*val; if (optimmem >= &tracemem[new_memsize]) exp_mem(0); return (c); } void exp_act(int **ptr) { static int *actbase; int i; new_actsize += ACTSIZE; actbase = amem; amem = realloc(amem, sizeof (int) * new_actsize); if (amem == NULL) error("couldn't expand action table"); for (i = new_actsize-ACTSIZE; i < new_actsize; ++i) amem[i] = 0; if (ptr != NULL) *ptr = *ptr - actbase + amem; if (memp >= amem) memp = memp - actbase + amem; if (maxa >= amem) maxa = maxa - actbase + amem; }
YOBJ = y1.o y2.o y3.o y4.o y5.o getopt.o LOBJ = libmai.o libzer.o .c.o: ; $(CC) -c $(CFLAGS) $(CPPFLAGS) $(WARN) -DPARSER='"$(LIBDIR)/yaccpar"' $< all: yacc liby.a yacc: $(YOBJ) $(CC) $(LDFLAGS) $(YOBJ) $(LIBS) -o yacc liby.a: $(LOBJ) $(AR) -rv liby.a $(LOBJ) $(RANLIB) $@ install: all test -d $(ROOT)$(BINDIR) || mkdir -p $(ROOT)$(BINDIR) test -d $(ROOT)$(LIBDIR) || mkdir -p $(ROOT)$(LIBDIR) $(INSTALL) -c -m 755 yacc $(ROOT)$(BINDIR)/yacc $(STRIP) $(ROOT)$(BINDIR)/yacc $(INSTALL) -c -m 644 yaccpar $(ROOT)$(LIBDIR)/yaccpar $(INSTALL) -c -m 644 liby.a $(ROOT)$(LIBDIR)/liby.a test -d $(ROOT)$(MANDIR)/man1 || mkdir -p $(ROOT)$(MANDIR)/man1 $(INSTALL) -c -m 644 yacc.1 $(ROOT)$(MANDIR)/man1/yacc.1 clean: rm -f yacc liby.a $(YOBJ) $(LOBJ) y.tab.[ch] core log *~ mrproper: clean libmai.o: libmai.c libzer.o: libzer.c y1.o: y1.c dextern y2.o: y2.c dextern sgs.h y3.o: y3.c dextern y4.o: y4.c dextern
SPDX-FileCopyrightText: 2021 Andrius Å tikonas <andrius@stikonas.eu> SPDX-FileCopyrightText: 2021 fosslinux <fosslinux@aussies.space> SPDX-License-Identifier: CDDL-1.0 From 508eb06d40498acf954fc51ecb9171d2ce2236f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20=C5=A0tikonas?= <andrius@stikonas.eu> Date: Wed, 27 Jan 2021 00:40:19 +0000 Subject: [PATCH] Workaround for lex to work with mes libc. Similarly to yacc, remove wchar. See yacc patch for further information. --- lex/Makefile.mk | 2 +- lex/allprint.c | 6 ++-- lex/ldefs.c | 8 ----- lex/main.c | 1 + lex/parser.y | 77 +++++++++++++++++++++++++++++++++++-------------- lex/reject.c | 1 - lex/sub1.c | 44 ++++++++++++++++++---------- lex/sub3.c | 2 +- 8 files changed, 89 insertions(+), 52 deletions(-) diff --git lex/Makefile.mk lex/Makefile.mk index 577f7cb..a413867 100644 --- lex/Makefile.mk +++ lex/Makefile.mk @@ -1,4 +1,4 @@ -XOBJ = main.o sub1.o sub2.o sub3.o header.o wcio.o parser.o getopt.o lsearch.o +XOBJ = main.o sub1.o sub2.o sub3.o header.o parser.o getopt.o lsearch.o LOBJ = allprint.o libmain.o reject.o yyless.o yywrap.o \ allprint_w.o reject_w.o yyless_w.o reject_e.o yyless_e.o diff --git lex/allprint.c lex/allprint.c index 6e82495..cb0c6bb 100644 --- lex/allprint.c +++ lex/allprint.c @@ -42,8 +42,6 @@ #include <sys/euc.h> #include <widec.h> #endif -#include <wctype.h> -#include <wchar.h> extern FILE *yyout; @@ -78,10 +76,10 @@ allprint(CHR c) fprintf(yyout, "\\_"); break; default: - if (!iswprint(c)) + if (!iwprint(c)) fprintf(yyout, "\\x%-2x", (int)c); else - putwc(c, yyout); + putc(c, yyout); break; } } diff --git lex/ldefs.c lex/ldefs.c index ff99665..c5dcbaf 100644 --- lex/ldefs.c +++ lex/ldefs.c @@ -43,7 +43,6 @@ #ifdef __sun #include <widec.h> #endif -#include <wctype.h> #define CHR wchar_t #define BYTE char @@ -296,13 +295,6 @@ int mn1(int a, intptr_t d); int mn0(int a); int dupl(int n); -#undef getwc -#define getwc(f) lex_getwc(f) -extern wint_t lex_getwc(FILE *); -#undef putwc -#define putwc(c, f) lex_putwc(c, f) -extern wint_t lex_putwc(wchar_t, FILE *); - #undef index #define index lex_index diff --git lex/main.c lex/main.c index 8aee8ea..52c892a 100644 --- lex/main.c +++ lex/main.c @@ -38,6 +38,7 @@ * Sccsid @(#)main.c 1.9 (gritter) 11/26/05 */ +#include <getopt.h> #include <string.h> #include "once.h" #include "sgs.h" diff --git lex/parser.y lex/parser.y index b8618e3..34a7e9a 100644 --- lex/parser.y +++ lex/parser.y @@ -43,7 +43,6 @@ void yyerror(char *); #include <ctype.h> -#include <wchar.h> #include <inttypes.h> #ifndef __sun #define wcsetno(c) 0 @@ -289,6 +288,19 @@ r: CHAR ; %% + +/* +Copy multibyte string into char string. +Mes C library does not support wide strings, and fprintf truncates all strings to 1 character. +This happens because wchar_t strings have 0 in a second byte. +*/ +int +wstrcpy(char const *destination, wchar_t const *source) { + int i = 0; + do { + destination[i]=source[i]; + } while (source[i++] != 0); +} int yylex(void) { @@ -353,7 +365,7 @@ yylex(void) (*(p+2) == 'O')) { if(lgatflg) error("Too late for %%pointer"); - while(*p && !iswspace(*p)) + while(*p && !isspace(*p)) p++; isArray = 0; continue; @@ -397,7 +409,7 @@ yylex(void) (*(p+2) == 'R')) { if(lgatflg) error("Too late for %%array"); - while(*p && !iswspace(*p)) + while(*p && !isspace(*p)) p++; isArray = 1; continue; @@ -426,7 +438,7 @@ yylex(void) if(handleeuc) error("\ Character table (%t) is supported only in ASCII compatibility mode.\n"); - ZCH = wcstol(p+2, NULL, 10); + ZCH = strtol(p+2, NULL, 10); if (ZCH < NCH) ZCH = NCH; if (ZCH > 2*NCH) error("ch table needs redeclaration"); chset = TRUE; @@ -438,13 +450,13 @@ Character table (%t) is supported only in ASCII compatibility mode.\n"); continue; } while(digit(*p)) p++; - if(!iswspace(*p)) error("bad translation format"); - while(iswspace(*p)) p++; + if(!isspace(*p)) error("bad translation format"); + while(isspace(*p)) p++; t = p; while(*t){ c = ctrans(&t); if(ctable[(unsigned)c]){ - if (iswprint(c)) + if (isprint(c)) warning("Character '%lc' used twice",c); else @@ -485,8 +497,12 @@ Character table (%t) is supported only in ASCII compatibility mode.\n"); while(getl(p) && scomp(p, L_PctCbr) != 0) if(p[0]=='/' && p[1]=='*') cpycom(p); - else - fprintf(fout,"%ls\n",p); + else { + char p2[100]; + wstrcpy(p2, p); + fprintf(fout,"%ls\n",p2); + memset(p2, 0, sizeof p2); + } if(p[0] == '%') continue; if (*p) error("EOF before %%%%"); else error("EOF before %%}"); @@ -501,12 +517,12 @@ Character table (%t) is supported only in ASCII compatibility mode.\n"); start: lgate(); - while(*p && !iswspace(*p) && ((*p) != (wchar_t)',')) p++; + while(*p && !isspace(*p) && ((*p) != (wchar_t)',')) p++; n = TRUE; while(n){ - while(*p && (iswspace(*p) || ((*p) == (wchar_t)','))) p++; + while(*p && (isspace(*p) || ((*p) == (wchar_t)','))) p++; t = p; - while(*p && !iswspace(*p) && ((*p) != (wchar_t)',')) { + while(*p && !isspace(*p) && ((*p) != (wchar_t)',')) { if(!isascii(*p)) error("None-ASCII characters in start condition."); p++; @@ -516,7 +532,10 @@ start: if (*t == 0) continue; i = sptr*2; if(!ratfor)fprintf(fout,"# "); - fprintf(fout,"define %ls %d\n",t,i); + char t2[100]; + wstrcpy(t2, t); + fprintf(fout,"define %ls %d\n",t2,i); + memset(t2, 0, sizeof t2); scopy(t,sp); sname[sptr] = sp; /* XCU4: save exclusive flag with start name */ @@ -537,14 +556,20 @@ start: case ' ': case '\t': /* must be code */ lgate(); if( p[1]=='/' && p[2]=='*' ) cpycom(p); - else fprintf(fout, "%ls\n",p); + + else { + char p2[100]; + wstrcpy(p2, p); + fprintf(fout, "%ls\n",p2); + memset(p2, 0, sizeof p2); + } continue; case '/': /* look for comments */ lgate(); if((*(p+1))=='*') cpycom(p); /* FALLTHRU */ default: /* definition */ - while(*p && !iswspace(*p)) p++; + while(*p && !isspace(*p)) p++; if(*p == 0) continue; prev = *p; @@ -557,7 +582,7 @@ start: } } else { /* still sect 1, but prev != '\n' */ p = bptr; - while(*p && iswspace(*p)) p++; + while(*p && isspace(*p)) p++; if(*p == 0) warning("No translation given - null string assumed"); scopy(p,token); @@ -632,8 +657,11 @@ start: while(!eof&& getl(buf) && scomp(L_PctCbr,buf)!=0) if(buf[0]=='/' && buf[1]=='*') cpycom(buf); - else - fprintf(fout,"%ls\n",buf); + else { + char buf2[100]; + wstrcpy(buf2, p); + fprintf(fout,"%ls\n",buf2); + } continue; } if(peek == '%'){ @@ -944,9 +972,16 @@ Character range specified between different codesets."); else fprintf(fout, "\n# line %d \"%s\"\n", yyline-1, sargv[optind]); - fprintf(fout,"%ls\n",buf); - while(getl(buf) && !eof) - fprintf(fout,"%ls\n",buf); + char buf2[100]; + wstrcpy(buf2, buf); + fprintf(fout,"%ls\n",buf2); + memset(buf2, 0, sizeof buf2); + while(getl(buf) && !eof) { + wstrcpy(buf2, buf); + fprintf(fout,"%ls\n",buf2); + memset(buf2, 0, sizeof buf2); + } + memset(buf2, 0, sizeof buf2); } return(freturn(0)); diff --git lex/reject.c lex/reject.c index 31928e7..ef08c57 100644 --- lex/reject.c +++ lex/reject.c @@ -38,7 +38,6 @@ #include <euc.h> #include <widec.h> #else /* !sun */ -#include <wchar.h> #endif /* !sun */ #include <limits.h> #endif diff --git lex/sub1.c lex/sub1.c index b867948..eead84b 100644 --- lex/sub1.c +++ lex/sub1.c @@ -38,7 +38,6 @@ #include "ldefs.c" #include <limits.h> -#include <wchar.h> #include <ctype.h> #include <stdarg.h> @@ -394,6 +393,19 @@ cclinter(int sw) } } +int +mbtowc(wchar_t *pwc, const char *s, size_t n) +{ + if (s != 0) { + if (n < 1) + return -1; + if (pwc != 0) + *pwc = *s & 0377; + return *s != '\0'; + } else + return 0; +} + int usescape(int c) { @@ -546,7 +558,7 @@ cpyact(void) break; case ';': if (brac == 0) { - putwc(c, fout); + putc(c, fout); putc('\n', fout); return (1); } @@ -558,26 +570,26 @@ cpyact(void) case '}': brac--; if (brac == 0) { - putwc(c, fout); + putc(c, fout); putc('\n', fout); return (1); } break; case '/': - putwc(c, fout); + putc(c, fout); c = gch(); if (c != '*') goto swt; - putwc(c, fout); + putc(c, fout); savline = yyline; while (c = gch()) { while (c == '*') { - putwc(c, fout); + putc(c, fout); if ((c = gch()) == '/') { putc('/', fout); while ((c = gch()) == ' ' || c == '\t' || c == '\n') - putwc(c, fout); + putc(c, fout); goto swt; } } @@ -590,16 +602,16 @@ cpyact(void) case '\'': /* character constant */ case '"': /* character string */ mth = c; - putwc(c, fout); + putc(c, fout); while (c = gch()) { if (c == '\\') { - putwc(c, fout); + putc(c, fout); c = gch(); } else if (c == mth) goto loop; - putwc(c, fout); + putc(c, fout); if (c == '\n') { yyline--; error( @@ -620,7 +632,7 @@ cpyact(void) loop: if (c != ' ' && c != '\t' && c != '\n') sw = FALSE; - putwc(c, fout); + putc(c, fout); if (peek == '\n' && !brac && copy_line) { putc('\n', fout); return (1); @@ -636,7 +648,7 @@ gch(void) int c; prev = pres; c = pres = peek; - peek = pushptr > pushc ? *--pushptr : getwc(fin); + peek = pushptr > pushc ? *--pushptr : getc(fin); while (peek == EOF) { if (no_input) { if (!yyline) @@ -650,7 +662,7 @@ gch(void) if (fin == NULL) error("Cannot open file -- %s", sargv[optind]); - peek = getwc(fin); + peek = getc(fin); } else break; } else { @@ -856,11 +868,11 @@ allprint(CHR c) printf("\\_"); break; default: - if (!iswprint(c)) { + if (!isprint(c)) { printf("\\x%-2x", c); /* up to fashion. */ charc += 3; } else - putwc(c, stdout); + putc(c, stdout); break; } charc++; @@ -903,7 +915,7 @@ sect1dump(void) allprint(i); putchar(' '); iswprint(ctable[i]) ? - putwc(ctable[i], stdout) : + putc(ctable[i], stdout) : printf("%d", ctable[i]); putchar('\n'); } diff --git lex/sub3.c lex/sub3.c index 186bcbc..aa07f66 100644 --- lex/sub3.c +++ lex/sub3.c @@ -147,7 +147,7 @@ remch(wchar_t c) */ if (!handleeuc) { if (!isascii(c)) - if (iswprint(c)) + if (isprint(c)) warning( "Non-ASCII character '%lc' in pattern; use -w or -e lex option.", c); else warning( -- 2.26.2
XOBJ = main.o sub1.o sub2.o sub3.o header.o wcio.o parser.o getopt.o lsearch.o LOBJ = allprint.o libmain.o reject.o yyless.o yywrap.o \ allprint_w.o reject_w.o yyless_w.o reject_e.o yyless_e.o WFLAGS = -DEUC -DJLSLEX -DWOPTION EFLAGS = -DEUC -DJLSLEX -DEOPTION LEXDIR = $(LIBDIR)/lex .c.o: ; $(CC) -c $(CFLAGS) $(CPPFLAGS) $(WARN) -DFORMPATH='"$(LEXDIR)"' $< all: lex libl.a lex: $(XOBJ) $(CC) $(LDFLAGS) $(XOBJ) $(LIBS) -o lex libl.a: $(LOBJ) $(AR) -rv libl.a $(LOBJ) $(RANLIB) $@ allprint_w.o: allprint.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $(WARN) $(WFLAGS) allprint.c -o $@ reject_w.o: reject.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $(WARN) $(WFLAGS) reject.c -o $@ yyless_w.o: yyless.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $(WARN) $(WFLAGS) yyless.c -o $@ reject_e.o: reject.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $(WARN) $(EFLAGS) reject.c -o $@ yyless_e.o: yyless.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $(WARN) $(EFLAGS) yyless.c -o $@ install: all test -d $(ROOT)$(BINDIR) || mkdir -p $(ROOT)$(BINDIR) test -d $(ROOT)$(LEXDIR) || mkdir -p $(ROOT)$(LEXDIR) $(INSTALL) -c -m 755 lex $(ROOT)$(BINDIR)/lex $(STRIP) $(ROOT)$(BINDIR)/lex $(INSTALL) -c -m 644 ncform $(ROOT)$(LEXDIR)/ncform $(INSTALL) -c -m 644 nceucform $(ROOT)$(LEXDIR)/nceucform $(INSTALL) -c -m 644 nrform $(ROOT)$(LEXDIR)/nrform $(INSTALL) -c -m 644 libl.a $(ROOT)$(LIBDIR)/libl.a test -d $(ROOT)$(MANDIR)/man1 || mkdir -p $(ROOT)$(MANDIR)/man1 $(INSTALL) -c -m 644 lex.1 $(ROOT)$(MANDIR)/man1/lex.1 clean: rm -f lex libl.a $(XOBJ) $(LOBJ) parser.c core log *~ mrproper: clean allprint.o: allprint.c header.o: header.c ldefs.c ldefs.o: ldefs.c libmain.o: libmain.c main.o: main.c once.h ldefs.c sgs.h reject.o: reject.c sub1.o: sub1.c ldefs.c sub2.o: sub2.c ldefs.c sub3.o: sub3.c ldefs.c search.h yyless.o: yyless.c yywrap.o: yywrap.c lsearch.o: search.h
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1989 AT&T */ /* All Rights Reserved */ /* from OpenSolaris "allprint.c 6.11 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)allprint.c 1.4 (gritter) 11/27/05 */ #include <stdio.h> #include <stdlib.h> #include <ctype.h> #ifdef __sun #include <sys/euc.h> #include <widec.h> #endif #include <wctype.h> #include <wchar.h> extern FILE *yyout; #ifndef JLSLEX #define CHR char #endif #ifdef WOPTION #define CHR wchar_t #define sprint sprint_w #define allprint allprint_w #endif #ifdef EOPTION #define CHR wchar_t #endif void allprint(CHR c) { switch (c) { case '\n': fprintf(yyout, "\\n"); break; case '\t': fprintf(yyout, "\\t"); break; case '\b': fprintf(yyout, "\\b"); break; case ' ': fprintf(yyout, "\\_"); break; default: if (!iswprint(c)) fprintf(yyout, "\\x%-2x", (int)c); else putwc(c, yyout); break; } } void sprint(CHR *s) { while (*s) allprint(*s++); }
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. * All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ /* from OpenSolaris "ldefs.c 6.16 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)ldefs.c 1.7 (gritter) 4/14/07 */ #include <stdio.h> #include <stdlib.h> #include <inttypes.h> #ifdef __sun #include <widec.h> #endif #include <wctype.h> #define CHR wchar_t #define BYTE char #define Boolean char #define LONG_WCHAR_T 1 #define PP 1 #ifdef u370 #define CWIDTH 8 #define CMASK 0377 #define ASCII 1 #else #ifdef unix #define CWIDTH 7 #define CMASK 0177 #define ASCII 1 #endif #ifdef gcos #define CWIDTH 9 #define CMASK 0777 #define ASCII 1 #endif #ifdef ibm #define CWIDTH 8 #define CMASK 0377 #define EBCDIC 1 #endif #endif #define NCH 256 #define TOKENSIZE 10000 #define DEFSIZE 1000 #define DEFCHAR 2000 #define BUF_SIZ 2000 #define STARTCHAR 2560 #define STARTSIZE 256 #define CCLSIZE 20000 #ifdef SMALL #define TREESIZE 600 #define NTRANS 1500 #define NSTATES 300 #define MAXPOS 1500 #define MAXPOSSTATE 500 #define NOUTPUT 1500 #endif #ifndef SMALL #define TREESIZE 1000 #define NSTATES 500 #define MAXPOS 2500 #define MAXPOSSTATE 4*1000 #define NTRANS 2000 #define NOUTPUT 4*3000 #endif #define NACTIONS 4*1000 #define ALITTLEEXTRA 300 #define RCCL 0x4000 #define RNCCL 0x4001 #define RSTR 0x4002 #define RSCON 0x4003 /* XCU4: add RXSCON */ #define RXSCON 0x4011 #define RNEWE 0x4004 #define FINAL 0x4005 #define RNULLS 0x4006 #define RCAT 0x4007 #define STAR 0x4008 #define PLUS 0x4009 #define QUEST 0x400a #define DIV 0x400b #define BAR 0x400c #define CARAT 0x400d #define S1FINAL 0x400e #define S2FINAL 0x400f #define DOT 0x4010 #define ISOPERATOR(n) ((n & 0xc080) == 0x4000) /* * New to JLE; this is not really a node tag. * This is used in a string pointed to by * the leaf of an RCCL or RNCCL node as a * special prefix code that substitutes * the infix '-' range operator. For * example, a lex character class "[_0-9a-zA-Z]" * would be translated to the intermidiate * form: * RCCL * | * | * v * "_<RANGE>09<RANGE>a-z<RANGE>A-Z" */ #define RANGE 0x40ff #define MAXNCG 1000 extern int ncgidtbl; extern int ncg; /* ncg == ncgidtbl * 2 */ typedef unsigned long lchar; extern lchar yycgidtbl[]; extern int yycgid(wchar_t); extern Boolean handleeuc; /* TRUE iff -w or -e option is specified. */ extern Boolean widecio; /* TRUE iff -w option is specified. */ #define DEFSECTION 1 #define RULESECTION 2 #define ENDSECTION 5 #define PC 1 #define PS 1 #ifdef DEBUG #define LINESIZE 110 extern int yydebug; extern int debug; /* 1 = on */ extern int charc; #endif #ifndef DEBUG #define freturn(s) s #endif #undef FALSE #undef TRUE enum { FALSE, TRUE }; extern int optind; extern int no_input; extern int sargc; extern char **sargv; extern char *v_stmp; extern char *release_string; extern CHR buf[]; extern int ratfor; /* 1 = ratfor, 0 = C */ extern int fatal; extern int n_error; extern int copy_line; extern int yyline; /* line number of file */ extern int sect; extern int eof; extern int lgatflg; extern int divflg; extern int funcflag; extern int pflag; extern int casecount; extern int chset; /* 1 = CHR set modified */ extern FILE *fin, *fout, *fother, *errorf; extern int fptr; extern char *ratname, *cname; extern int prev; /* previous input character */ extern int pres; /* present input character */ extern int peek; /* next input character */ extern int *name; extern intptr_t *left; extern intptr_t *right; extern int *parent; extern Boolean *nullstr; extern int tptr; extern CHR pushc[TOKENSIZE]; extern CHR *pushptr; extern CHR slist[STARTSIZE]; extern CHR *slptr; extern CHR **def, **subs, *dchar; extern CHR **sname, *schar; /* XCU4: %x exclusive start */ extern int *exclusive; extern CHR *ccl; extern CHR *ccptr; extern CHR *dp, *sp; extern int dptr, sptr; extern CHR *bptr; /* store input position */ extern CHR *tmpstat; extern int count; extern int **foll; extern int *nxtpos; extern int *positions; extern int *gotof; extern int *nexts; extern CHR *nchar; extern int **state; extern int *sfall; /* fallback state num */ extern Boolean *cpackflg; /* true if state has been character packed */ extern int *atable, aptr; extern int nptr; extern Boolean symbol[MAXNCG]; extern CHR cindex[MAXNCG]; extern int xstate; extern int stnum; extern int ctable[]; extern int ZCH; extern int ccount; extern CHR match[MAXNCG]; extern BYTE extra[]; extern CHR *pcptr, *pchar; extern int pchlen; extern int nstates, maxpos; extern int yytop; extern int report; extern int ntrans, treesize, outsize; extern long rcount; extern int optim; extern int *verify, *advance, *stoff; extern int scon; extern CHR *psave; extern CHR *getl(CHR *); extern void *myalloc(int, int); void phead1(void); void phead2(void); void ptail(void); void statistics(void); void error_tail(void); void error(const char *, ...); void warning(const char *, ...); void lgate(void); void scopy(CHR *s, CHR *t); void cclinter(int sw); void cpycom(CHR *p); void munput(int t, CHR *p); void cfoll(int v); void cgoto(void); void mkmatch(void); void layout(void); void remch(wchar_t c); void sortcgidtbl(void); void repbycgid(void); int gch(void); int slength(CHR *s); int yyparse(void); int scomp(CHR *x, CHR *y); int space(int ch); int siconv(CHR *t); int digit(int c); int ctrans(CHR **ss); int cpyact(void); int lookup(CHR *s, CHR **t); int usescape(int c); int alpha(int c); int mn2(int a, intptr_t d, intptr_t c); int mn1(int a, intptr_t d); int mn0(int a); int dupl(int n); #undef getwc #define getwc(f) lex_getwc(f) extern wint_t lex_getwc(FILE *); #undef putwc #define putwc(c, f) lex_putwc(c, f) extern wint_t lex_putwc(wchar_t, FILE *); #undef index #define index lex_index extern int isArray; /* XCU4: for %array %pointer */
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. * All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ /* Copyright 1976, Bell Telephone Laboratories, Inc. */ /* from OpenSolaris "main.c 6.16 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)main.c 1.9 (gritter) 11/26/05 */ #include <string.h> #include "once.h" #include "sgs.h" #include <locale.h> #include <limits.h> #include <unistd.h> static wchar_t L_INITIAL[] = {'I', 'N', 'I', 'T', 'I', 'A', 'L', 0}; static void get1core(void); static void free1core(void); static void get2core(void); static void free2core(void); static void get3core(void); #ifdef DEBUG static void free3core(void); #endif int main(int argc, char **argv) { int i; int c; char *path = NULL; Boolean eoption = 0, woption = 0; sargv = argv; sargc = argc; errorf = stderr; setlocale(LC_CTYPE, ""); #ifdef DEBUG while ((c = getopt(argc, argv, "dyctvnewVQ:Y:")) != EOF) { #else while ((c = getopt(argc, argv, "ctvnewVQ:Y:")) != EOF) { #endif switch (c) { #ifdef DEBUG case 'd': debug++; break; case 'y': yydebug = TRUE; break; #endif case 'V': fprintf(stderr, "lex:%s , %s\n", pkg, rel); break; case 'Q': v_stmp = optarg; if (*v_stmp != 'y' && *v_stmp != 'n') error( "lex: -Q should be followed by [y/n]"); break; case 'Y': path = malloc(strlen(optarg) + sizeof ("/nceucform") + 1); path = strcpy(path, optarg); break; case 'c': ratfor = FALSE; break; case 't': fout = stdout; break; case 'v': report = 1; break; case 'n': report = 0; break; case 'w': case 'W': woption = 1; handleeuc = 1; widecio = 1; break; case 'e': case 'E': eoption = 1; handleeuc = 1; widecio = 0; break; default: fprintf(stderr, "Usage: lex [-ctvnV] [-Q(y/n)] [files...]\n"); exit(1); } } if (woption && eoption) { error( "You may not specify both -w and -e simultaneously."); } no_input = argc - optind; if (no_input) { /* XCU4: recognize "-" file operand for stdin */ if (strcmp(argv[optind], "-") == 0) fin = stdin; else { fin = fopen(argv[optind], "r"); if (fin == NULL) error( "Can't open input file -- %s", argv[optind]); } } else fin = stdin; /* may be gotten: def, subs, sname, schar, ccl, dchar */ gch(); /* may be gotten: name, left, right, nullstr, parent */ get1core(); scopy(L_INITIAL, sp); sname[0] = sp; sp += slength(L_INITIAL) + 1; sname[1] = 0; /* XCU4: %x exclusive start */ exclusive[0] = 0; if (!handleeuc) { /* * Set ZCH and ncg to their default values * as they may be needed to handle %t directive. */ ZCH = ncg = NCH; /* ncg behaves as constant in this mode. */ } /* may be disposed of: def, subs, dchar */ if (yyparse()) exit(1); /* error return code */ if (handleeuc) { ncg = ncgidtbl * 2; ZCH = ncg; if (ncg >= MAXNCG) error( "Too complex rules -- requires too many char groups."); sortcgidtbl(); } repbycgid(); /* Call this even in ASCII compat. mode. */ /* * maybe get: * tmpstat, foll, positions, gotof, nexts, * nchar, state, atable, sfall, cpackflg */ free1core(); get2core(); ptail(); mkmatch(); #ifdef DEBUG if (debug) pccl(); #endif sect = ENDSECTION; if (tptr > 0) cfoll(tptr-1); #ifdef DEBUG if (debug) pfoll(); #endif cgoto(); #ifdef DEBUG if (debug) { printf("Print %d states:\n", stnum + 1); for (i = 0; i <= stnum; i++) stprt(i); } #endif /* * may be disposed of: * positions, tmpstat, foll, state, name, * left, right, parent, ccl, schar, sname * maybe get: verify, advance, stoff */ free2core(); get3core(); layout(); /* * may be disposed of: * verify, advance, stoff, nexts, nchar, * gotof, atable, ccpackflg, sfall */ #ifdef DEBUG free3core(); #endif if (path == NULL) { static char formpath[sizeof FORMPATH + 20] = FORMPATH; path = formpath; } if (handleeuc) { if (ratfor) error("Ratfor is not supported by -w or -e option."); strcat(path, "/nceucform"); } else strcat(path, ratfor ? "/nrform" : "/ncform"); fother = fopen(path, "r"); if (fother == NULL) error("Lex driver missing, file %s", path); while ((i = getc(fother)) != EOF) putc(i, fout); fclose(fother); fclose(fout); if (report == 1) statistics(); fclose(stdout); fclose(stderr); return (0); /* success return code */ } static void get1core(void) { ccptr = ccl = myalloc(CCLSIZE, sizeof (*ccl)); pcptr = pchar = myalloc(pchlen, sizeof (*pchar)); def = myalloc(DEFSIZE, sizeof (*def)); subs = myalloc(DEFSIZE, sizeof (*subs)); dp = dchar = myalloc(DEFCHAR, sizeof (*dchar)); sname = myalloc(STARTSIZE, sizeof (*sname)); /* XCU4: exclusive start array */ exclusive = myalloc(STARTSIZE, sizeof (*exclusive)); sp = schar = myalloc(STARTCHAR, sizeof (*schar)); if (ccl == 0 || def == 0 || pchar == 0 || subs == 0 || dchar == 0 || sname == 0 || exclusive == 0 || schar == 0) error("Too little core to begin"); } static void free1core(void) { free(def); free(subs); free(dchar); } static void get2core(void) { int i; gotof = myalloc(nstates, sizeof (*gotof)); nexts = myalloc(ntrans, sizeof (*nexts)); nchar = myalloc(ntrans, sizeof (*nchar)); state = myalloc(nstates, sizeof (*state)); atable =myalloc(nstates, sizeof (*atable)); sfall = myalloc(nstates, sizeof (*sfall)); cpackflg = myalloc(nstates, sizeof (*cpackflg)); tmpstat = myalloc(tptr+1, sizeof (*tmpstat)); foll = myalloc(tptr+1, sizeof (*foll)); nxtpos = positions = myalloc(maxpos, sizeof (*positions)); if (tmpstat == 0 || foll == 0 || positions == 0 || gotof == 0 || nexts == 0 || nchar == 0 || state == 0 || atable == 0 || sfall == 0 || cpackflg == 0) error("Too little core for state generation"); for (i = 0; i <= tptr; i++) foll[i] = 0; } static void free2core(void) { free(positions); free(tmpstat); free(foll); free(name); free(left); free(right); free(parent); free(nullstr); free(state); free(sname); /* XCU4: exclusive start array */ free(exclusive); free(schar); free(ccl); } static void get3core(void) { verify = myalloc(outsize, sizeof (*verify)); advance = myalloc(outsize, sizeof (*advance)); stoff = myalloc(stnum+2, sizeof (*stoff)); if (verify == 0 || advance == 0 || stoff == 0) error("Too little core for final packing"); } #ifdef DEBUG static void free3core(void) { free(advance); free(verify); free(stoff); free(gotof); free(nexts); free(nchar); free(atable); free(sfall); free(cpackflg); } #endif void * myalloc(int a, int b) { void *i; i = calloc(a, b); if (i == NULL) warning("calloc returns a 0"); return (i); } void yyerror(char *s) { fprintf(stderr, "\"%s\":line %d: Error: %s\n", sargv[optind], yyline, s); }
%{ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ %} /* * Copyright 2005 Sun Microsystems, Inc. * All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ %{ /* from OpenSolaris "parser.y 6.15 05/06/10 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)parser.y 1.8 (gritter) 11/26/05 */ void yyerror(char *); #include <ctype.h> #include <wchar.h> #include <inttypes.h> #ifndef __sun #define wcsetno(c) 0 #endif %} /* parser.y */ /* XCU4: add XSCON: %x exclusive start token */ /* XCU4: add ARRAY: %a yytext is char array */ /* XCU4: add POINTER: %p yytext is a pointer to char */ %token CHAR CCL NCCL STR DELIM SCON ITER NEWE NULLS XSCON ARRAY POINTER %nonassoc ARRAY POINTER %left XSCON SCON NEWE %left '/' /* * XCU4: lower the precedence of $ and ^ to less than the or operator * per Spec. 1170 */ %left '$' '^' %left '|' %left CHAR CCL NCCL '(' '.' STR NULLS %left ITER %left CAT %left '*' '+' '?' %{ #include "ldefs.c" #define YYSTYPE union _yystype_ union _yystype_ { int i; CHR *cp; }; int peekon = 0; /* need this to check if "^" came in a definition section */ %} %% %{ int i; int j,k; int g; CHR *p; static wchar_t L_PctUpT[]= {'%', 'T', 0}; static wchar_t L_PctLoT[]= {'%', 't', 0}; static wchar_t L_PctCbr[]= {'%', '}', 0}; %} acc : lexinput ={ # ifdef DEBUG if(debug) sect2dump(); # endif } ; lexinput: defns delim prods end | defns delim end ={ if(!funcflag)phead2(); funcflag = TRUE; } | error ={ # ifdef DEBUG if(debug) { sect1dump(); sect2dump(); } # endif fatal = 0; n_error++; error("Illegal definition"); fatal = 1; } ; end: delim | ; defns: defns STR STR ={ scopy($2.cp,dp); def[dptr] = dp; dp += slength($2.cp) + 1; scopy($3.cp,dp); subs[dptr++] = dp; if(dptr >= DEFSIZE) error("Too many definitions"); dp += slength($3.cp) + 1; if(dp >= dchar+DEFCHAR) error("Definitions too long"); subs[dptr]=def[dptr]=0; /* for lookup - require ending null */ } | ; delim: DELIM ={ # ifdef DEBUG if(sect == DEFSECTION && debug) sect1dump(); # endif sect++; } ; prods: prods pr ={ $$.i = mn2(RNEWE,$1.i,$2.i); } | pr ={ $$.i = $1.i;} ; pr: r NEWE ={ if(divflg == TRUE) i = mn1(S1FINAL,casecount); else i = mn1(FINAL,casecount); $$.i = mn2(RCAT,$1.i,i); divflg = FALSE; if((++casecount)>NACTIONS) error("Too many (>%d) pattern-action rules.", NACTIONS); } | error NEWE ={ # ifdef DEBUG if(debug) sect2dump(); # endif fatal = 0; yyline--; n_error++; error("Illegal rule"); fatal = 1; yyline++; } r: CHAR ={ $$.i = mn0($1.i); } | STR ={ p = (CHR *)$1.cp; i = mn0((unsigned)(*p++)); while(*p) i = mn2(RSTR,i,(unsigned)(*p++)); $$.i = i; } | '.' ={ $$.i = mn0(DOT); } | CCL ={ $$.i = mn1(RCCL,(intptr_t)$1.cp); } | NCCL ={ $$.i = mn1(RNCCL,(intptr_t)$1.cp); } | r '*' ={ $$.i = mn1(STAR,$1.i); } | r '+' ={ $$.i = mn1(PLUS,$1.i); } | r '?' ={ $$.i = mn1(QUEST,$1.i); } | r '|' r ={ $$.i = mn2(BAR,$1.i,$3.i); } | r r %prec CAT ={ $$.i = mn2(RCAT,$1.i,$2.i); } | r '/' r ={ if(!divflg){ j = mn1(S2FINAL,-casecount); i = mn2(RCAT,$1.i,j); $$.i = mn2(DIV,i,$3.i); } else { $$.i = mn2(RCAT,$1.i,$3.i); error("illegal extra slash"); } divflg = TRUE; } | r ITER ',' ITER '}' ={ if($2.i > $4.i){ i = $2.i; $2.i = $4.i; $4.i = i; } if($4.i <= 0) error("iteration range must be positive"); else { j = $1.i; for(k = 2; k<=$2.i;k++) j = mn2(RCAT,j,dupl($1.i)); for(i = $2.i+1; i<=$4.i; i++){ g = dupl($1.i); for(k=2;k<=i;k++) g = mn2(RCAT,g,dupl($1.i)); j = mn2(BAR,j,g); } $$.i = j; } } | r ITER '}' ={ if($2.i < 0)error("can't have negative iteration"); else if($2.i == 0) $$.i = mn0(RNULLS); else { j = $1.i; for(k=2;k<=$2.i;k++) j = mn2(RCAT,j,dupl($1.i)); $$.i = j; } } | r ITER ',' '}' ={ /* from n to infinity */ if($2.i < 0)error("can't have negative iteration"); else if($2.i == 0) $$.i = mn1(STAR,$1.i); else if($2.i == 1)$$.i = mn1(PLUS,$1.i); else { /* >= 2 iterations minimum */ j = $1.i; for(k=2;k<$2.i;k++) j = mn2(RCAT,j,dupl($1.i)); k = mn1(PLUS,dupl($1.i)); $$.i = mn2(RCAT,j,k); } } | SCON r ={ $$.i = mn2(RSCON,$2.i,(intptr_t)$1.cp); } /* XCU4: add XSCON */ | XSCON r ={ $$.i = mn2(RXSCON,$2.i,(intptr_t)$1.cp); } | '^' r ={ $$.i = mn1(CARAT,$2.i); } | r '$' ={ i = mn0('\n'); if(!divflg){ j = mn1(S2FINAL,-casecount); k = mn2(RCAT,$1.i,j); $$.i = mn2(DIV,k,i); } else $$.i = mn2(RCAT,$1.i,i); divflg = TRUE; } | '(' r ')' ={ $$.i = $2.i; } | NULLS ={ $$.i = mn0(RNULLS); } /* XCU4: add ARRAY and POINTER */ | ARRAY ={ isArray = 1; }; | POINTER ={ isArray = 0; }; ; %% int yylex(void) { CHR *p; int i; CHR *xp; int lex_startcond_lookupval; CHR *t, c; int n, j = 0, k, x; CHR ch; static int sectbegin; static CHR token[TOKENSIZE]; static int iter; int ccs; /* Current CodeSet. */ CHR *ccp; int exclusive_flag; /* XCU4: exclusive start flag */ # ifdef DEBUG yylval.i = 0; # endif if(sect == DEFSECTION) { /* definitions section */ while(!eof) { if(prev == '\n'){ /* next char is at beginning of line */ getl(p=buf); switch(*p){ case '%': switch(c= *(p+1)){ case '%': if(scomp(p, (CHR *)"%%")) { p++; while(*(++p)) if(!space(*p)) { warning("invalid string following %%%% be ignored"); break; } } lgate(); if(!ratfor)fprintf(fout,"# "); fprintf(fout,"define YYNEWLINE %d\n",ctable['\n']); if(!ratfor) { fprintf(fout,"int yylex(){\nint nstr = 0; extern int yyprevious;\n"); } sectbegin = TRUE; i = treesize*(sizeof(*name)+sizeof(*left)+ sizeof(*right)+sizeof(*nullstr)+sizeof(*parent))+ALITTLEEXTRA; p = myalloc(i,1); if(p == NULL) error("Too little core for parse tree"); free(p); name = myalloc(treesize,sizeof(*name)); left = myalloc(treesize,sizeof(*left)); right = myalloc(treesize,sizeof(*right)); nullstr = myalloc(treesize,sizeof(*nullstr)); parent = myalloc(treesize,sizeof(*parent)); if(name == 0 || left == 0 || right == 0 || parent == 0 || nullstr == 0) error("Too little core for parse tree"); return(freturn(DELIM)); case 'p': case 'P': /* %p or %pointer */ if ((*(p+2) == 'o') || (*(p+2) == 'O')) { if(lgatflg) error("Too late for %%pointer"); while(*p && !iswspace(*p)) p++; isArray = 0; continue; } /* has overridden number of positions */ p += 2; maxpos = siconv(p); if (maxpos<=0)error("illegal position number"); # ifdef DEBUG if (debug) printf("positions (%%p) now %d\n",maxpos); # endif if(report == 2)report = 1; continue; case 'n': case 'N': /* has overridden number of states */ p += 2; nstates = siconv(p); if(nstates<=0)error("illegal state number"); # ifdef DEBUG if(debug)printf( " no. states (%%n) now %d\n",nstates); # endif if(report == 2)report = 1; continue; case 'e': case 'E': /* has overridden number of tree nodes */ p += 2; treesize = siconv(p); if(treesize<=0)error("illegal number of parse tree nodes"); # ifdef DEBUG if (debug) printf("treesize (%%e) now %d\n",treesize); # endif if(report == 2)report = 1; continue; case 'o': case 'O': p += 2; outsize = siconv(p); if(outsize<=0)error("illegal size of output array"); if (report ==2) report=1; continue; case 'a': case 'A': /* %a or %array */ if ((*(p+2) == 'r') || (*(p+2) == 'R')) { if(lgatflg) error("Too late for %%array"); while(*p && !iswspace(*p)) p++; isArray = 1; continue; } /* has overridden number of transitions */ p += 2; ntrans = siconv(p); if(ntrans<=0)error("illegal translation number"); # ifdef DEBUG if (debug)printf("N. trans (%%a) now %d\n",ntrans); # endif if(report == 2)report = 1; continue; case 'k': case 'K': /* overriden packed char classes */ p += 2; free(pchar); pchlen = siconv(p); if(pchlen<=0)error("illegal number of packed character class"); # ifdef DEBUG if (debug) printf( "Size classes (%%k) now %d\n",pchlen); # endif pchar=pcptr=myalloc(pchlen, sizeof(*pchar)); if (report==2) report=1; continue; case 't': case 'T': /* character set specifier */ if(handleeuc) error("\ Character table (%t) is supported only in ASCII compatibility mode.\n"); ZCH = wcstol(p+2, NULL, 10); if (ZCH < NCH) ZCH = NCH; if (ZCH > 2*NCH) error("ch table needs redeclaration"); chset = TRUE; for(i = 0; i<ZCH; i++) ctable[i] = 0; while(getl(p) && scomp(p,L_PctUpT) != 0 && scomp(p,L_PctLoT) != 0){ if((n = siconv(p)) <= 0 || n > ZCH){ error("Character value %d out of range",n); continue; } while(digit(*p)) p++; if(!iswspace(*p)) error("bad translation format"); while(iswspace(*p)) p++; t = p; while(*t){ c = ctrans(&t); if(ctable[(unsigned)c]){ if (iswprint(c)) warning("Character '%lc' used twice",c); else error("Chararter %o used twice",c); } else ctable[(unsigned)c] = n; t++; } p = buf; } { char chused[2*NCH]; int kr; for(i=0; i<ZCH; i++) chused[i]=0; for(i=0; i<NCH; i++) chused[ctable[i]]=1; for(kr=i=1; i<NCH; i++) if (ctable[i]==0) { while (chused[kr] == 0) kr++; ctable[i]=kr; chused[kr]=1; } } lgate(); continue; case 'r': case 'R': c = 'r'; /* FALLTHRU */ case 'c': case 'C': if(lgatflg) error("Too late for language specifier"); ratfor = (c == 'r'); continue; case '{': lgate(); while(getl(p) && scomp(p, L_PctCbr) != 0) if(p[0]=='/' && p[1]=='*') cpycom(p); else fprintf(fout,"%ls\n",p); if(p[0] == '%') continue; if (*p) error("EOF before %%%%"); else error("EOF before %%}"); break; case 'x': case 'X': /* XCU4: exclusive start conditions */ exclusive_flag = 1; goto start; case 's': case 'S': /* start conditions */ exclusive_flag = 0; start: lgate(); while(*p && !iswspace(*p) && ((*p) != (wchar_t)',')) p++; n = TRUE; while(n){ while(*p && (iswspace(*p) || ((*p) == (wchar_t)','))) p++; t = p; while(*p && !iswspace(*p) && ((*p) != (wchar_t)',')) { if(!isascii(*p)) error("None-ASCII characters in start condition."); p++; } if(!*p) n = FALSE; *p++ = 0; if (*t == 0) continue; i = sptr*2; if(!ratfor)fprintf(fout,"# "); fprintf(fout,"define %ls %d\n",t,i); scopy(t,sp); sname[sptr] = sp; /* XCU4: save exclusive flag with start name */ exclusive[sptr++] = exclusive_flag; sname[sptr] = 0; /* required by lookup */ if(sptr >= STARTSIZE) error("Too many start conditions"); sp += slength(sp) + 1; if(sp >= schar+STARTCHAR) error("Start conditions too long"); } continue; default: error("Invalid request %s",p); continue; } /* end of switch after seeing '%' */ break; case ' ': case '\t': /* must be code */ lgate(); if( p[1]=='/' && p[2]=='*' ) cpycom(p); else fprintf(fout, "%ls\n",p); continue; case '/': /* look for comments */ lgate(); if((*(p+1))=='*') cpycom(p); /* FALLTHRU */ default: /* definition */ while(*p && !iswspace(*p)) p++; if(*p == 0) continue; prev = *p; *p = 0; bptr = p+1; yylval.cp = (CHR *)buf; if(digit(buf[0])) warning("Substitution strings may not begin with digits"); return(freturn(STR)); } } else { /* still sect 1, but prev != '\n' */ p = bptr; while(*p && iswspace(*p)) p++; if(*p == 0) warning("No translation given - null string assumed"); scopy(p,token); yylval.cp = (CHR *)token; prev = '\n'; return(freturn(STR)); } } error("unexpected EOF before %%%%"); /* end of section one processing */ } else if(sect == RULESECTION){ /* rules and actions */ lgate(); while(!eof){ static int first_test=TRUE, first_value; static int reverse=FALSE; switch(c=gch()){ case '\0': if(n_error)error_tail(); return(freturn(0)); case '\n': if(prev == '\n') continue; x = NEWE; break; case ' ': case '\t': if(prev == '\n') copy_line = TRUE; if(sectbegin == TRUE){ cpyact(); copy_line = FALSE; while((c=gch()) && c != '\n'); continue; } if(!funcflag)phead2(); funcflag = TRUE; if(ratfor)fprintf(fout,"%d\n",30000+casecount); else fprintf(fout,"case %d:\n",casecount); if(cpyact()){ if(ratfor)fprintf(fout,"goto 30997\n"); else fprintf(fout,"break;\n"); } while((c=gch()) && c != '\n') { if (c=='/') { if((c=gch())=='*') { c=gch(); while(c !=EOF) { while (c=='*') if ((c=gch()) == '/') goto w_loop; c = gch(); } error("EOF inside comment"); } else warning("undefined string"); } else if (c=='}') error("illegal extra \"}\""); w_loop: ; } /* while ((c=gch())== ' ' || c == '\t') ; */ /* if (!space(c)) error("undefined action string"); */ if(peek == ' ' || peek == '\t' || sectbegin == TRUE){ fatal = 0; n_error++; error("executable statements should occur right after %%%%"); fatal = 1; continue; } x = NEWE; break; case '%': if(prev != '\n') goto character; if(peek == '{'){ /* included code */ getl(buf); while(!eof&& getl(buf) && scomp(L_PctCbr,buf)!=0) if(buf[0]=='/' && buf[1]=='*') cpycom(buf); else fprintf(fout,"%ls\n",buf); continue; } if(peek == '%'){ c = gch(); c = gch(); x = DELIM; break; } goto character; case '|': if(peek == ' ' || peek == '\t' || peek == '\n'){ if(ratfor)fprintf(fout,"%d\n",30000+casecount++); else fprintf(fout,"case %d:\n",casecount++); continue; } x = '|'; break; case '$': if(peek == '\n' || peek == ' ' || peek == '\t' || peek == '|' || peek == '/'){ x = c; break; } goto character; case '^': if(peekon && (prev == '}')){ x = c; break; } if(prev != '\n' && scon != TRUE) goto character; /* valid only at line begin */ x = c; break; case '?': case '+': case '*': if(prev == '\n' ) { fatal = 0; n_error++; error("illegal operator -- %c",c); fatal = 1; } /* FALLTHRU */ case '.': case '(': case ')': case ',': case '/': x = c; break; case '}': iter = FALSE; x = c; break; case '{': /* either iteration or definition */ if(digit(c=gch())){ /* iteration */ iter = TRUE; if(prev=='{') first_test = TRUE; ieval: i = 0; while(digit(c)){ token[i++] = c; c = gch(); } token[i] = 0; yylval.i = siconv(token); if(first_test) { first_test = FALSE; first_value = yylval.i; } else if(first_value>yylval.i)warning("the values between braces are reversed"); ch = c; munput('c',&ch); x = ITER; break; } else { /* definition */ i = 0; while(c && c!='}'){ token[i++] = c; if(i >= TOKENSIZE) error("definition too long"); c = gch(); } token[i] = 0; i = lookup(token,def); if(i < 0) error("definition %ls not found",token); else munput('s',(CHR *)(subs[i])); if (peek == '^') peekon = 1; continue; } case '<': /* start condition ? */ if(prev != '\n') /* not at line begin, not start */ goto character; t = slptr; do { i = 0; if(!isascii(c = gch())) error("Non-ASCII characters in start condition."); while(c != ',' && c && c != '>'){ token[i++] = c; if(i >= TOKENSIZE) error("string name too long"); if(!isascii(c = gch())) error("None-ASCII characters in start condition."); } token[i] = 0; if(i == 0) goto character; i = lookup(token,sname); lex_startcond_lookupval = i; if(i < 0) { fatal = 0; n_error++; error("undefined start condition %ls",token); fatal = 1; continue; } *slptr++ = i+1; } while(c && c != '>'); *slptr++ = 0; /* check if previous value re-usable */ for (xp=slist; xp<t; ) { if (scomp(xp, t)==0) break; while (*xp++); } if (xp<t) { /* re-use previous pointer to string */ slptr=t; t=xp; } if(slptr > slist+STARTSIZE) /* note not packed */ error("Too many start conditions used"); yylval.cp = (CHR *)t; /* XCU4: add XSCON */ if (exclusive[lex_startcond_lookupval]) x = XSCON; else x = SCON; break; case '"': i = 0; while((c=gch()) && c != '"' && c != '\n'){ if(c == '\\') c = usescape(c=gch()); remch(c); token[i++] = c; if(i >= TOKENSIZE){ warning("String too long"); i = TOKENSIZE-1; break; } } if(c == '\n') { yyline--; warning("Non-terminated string"); yyline++; } token[i] = 0; if(i == 0)x = NULLS; else if(i == 1){ yylval.i = (unsigned)token[0]; x = CHAR; } else { yylval.cp = (CHR *)token; x = STR; } break; case '[': reverse = FALSE; x = CCL; if((c = gch()) == '^'){ x = NCCL; reverse = TRUE; c = gch(); } i = 0; while(c != ']' && c){ static int light=TRUE, ESCAPE=FALSE; if(c == '-' && prev == '^' && reverse){ symbol[(unsigned)c] = 1; c = gch(); continue; } if(c == '\\') { c = usescape(c=gch()); ESCAPE = TRUE; } if(c=='-' && !ESCAPE && prev!='[' && peek!=']'){ /* range specified */ if (light) { c = gch(); if(c == '\\') c=usescape(c=gch()); remch(c); k = c; ccs=wcsetno(k); if(wcsetno(j)!=ccs) error("\ Character range specified between different codesets."); if((unsigned)j > (unsigned)k) { n = j; j = k; k = n; } if(!handleeuc) if(!(('A'<=j && k<='Z') || ('a'<=j && k<='z') || ('0'<=j && k<='9'))) warning("Non-portable Character Class"); token[i++] = RANGE; token[i++] = j; token[i++] = k; light = FALSE; } else { error("unmatched hyphen"); if(symbol[(unsigned)c])warning("\"%c\" redefined inside brackets",c); else symbol[(unsigned)c] = 1; } ESCAPE = FALSE; } else { j = c; remch(c); token[i++] = c; /* Remember whatever.*/ light = TRUE; ESCAPE = FALSE; } c = gch(); } /* try to pack ccl's */ token[i] = 0; ccp = ccl; while (ccp < ccptr && scomp(token, ccp) != 0) ccp++; if (ccp < ccptr) { /* found in ccl */ yylval.cp = ccp; } else { /* not in ccl, add it */ scopy(token,ccptr); yylval.cp = ccptr; ccptr += slength(token) + 1; if(ccptr >= ccl+CCLSIZE) error("Too many large character classes"); } break; case '\\': c = usescape(c=gch()); default: character: if(iter){ /* second part of an iteration */ iter = FALSE; if('0' <= c && c <= '9') goto ieval; } remch(c); if(alpha(peek)){ i = 0; yylval.cp = (CHR *)token; token[i++] = c; while(alpha(peek)) { remch(token[i++] = gch()); if(i >= TOKENSIZE) { warning("string too long"); i = TOKENSIZE - 1; break; } } if(peek == '?' || peek == '*' || peek == '+') munput('c',&token[--i]); token[i] = 0; if(i == 1){ yylval.i = (unsigned)(token[0]); x = CHAR; } else x = STR; } else { yylval.i = (unsigned)c; x = CHAR; } } scon = FALSE; peekon = 0; if((x == SCON) || (x == XSCON)) scon = TRUE; sectbegin = FALSE; return(freturn(x)); /* NOTREACHED */ } } /* section three */ lgate(); ptail(); # ifdef DEBUG if(debug) fprintf(fout,"\n/*this comes from section three - debug */\n"); # endif if(getl(buf) && !eof) { if (sargv[optind] == NULL) fprintf(fout, "\n# line %d\n", yyline-1); else fprintf(fout, "\n# line %d \"%s\"\n", yyline-1, sargv[optind]); fprintf(fout,"%ls\n",buf); while(getl(buf) && !eof) fprintf(fout,"%ls\n",buf); } return(freturn(0)); } /* end of yylex */ # ifdef DEBUG freturn(i) int i; { if(yydebug) { printf("now return "); if((unsigned)i < NCH) allprint(i); else printf("%d",i); printf(" yylval = "); switch(i){ case STR: case CCL: case NCCL: strpt(yylval.cp); break; case CHAR: allprint(yylval.i); break; default: printf("%d",yylval.i); break; } putchar('\n'); } return(i); } # endif
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* Copyright (c) 1989 AT&T */ /* All Rights Reserved */ /* from OpenSolaris "reject.c 6.10 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)reject.c 1.4 (gritter) 11/27/05 */ #include <stdio.h> #ifdef EUC #ifdef __sun #include <euc.h> #include <widec.h> #else /* !sun */ #include <wchar.h> #endif /* !sun */ #include <limits.h> #endif #ifndef JLSLEX #define CHR char #define YYTEXT yytext #define YYLENG yyleng #define YYINPUT yyinput #define YYUNPUT yyunput #define YYOUTPUT yyoutput #define YYREJECT yyreject #endif #ifdef WOPTION #define CHR wchar_t #define YYTEXT yytext #define YYLENG yyleng #define YYINPUT yyinput #define YYUNPUT yyunput #define YYOUTPUT yyoutput #define YYREJECT yyreject_w #endif #ifdef EOPTION #define CHR wchar_t #define YYTEXT yywtext #define YYLENG yywleng #define YYINPUT yywinput #define YYUNPUT yywunput #define YYOUTPUT yywoutput #define YYREJECT yyreject_e extern unsigned char yytext[]; extern int yyleng; #endif #if defined(__cplusplus) || defined(__STDC__) extern int yyback(int *, int); extern int YYINPUT(void); extern void YYUNPUT(int); #ifdef EUC static int yyracc(int); #else extern int yyracc(int); #endif #ifdef EOPTION extern size_t wcstombs(char *, const wchar_t *, size_t); #endif #endif extern FILE *yyout, *yyin; extern int yyprevious, *yyfnd; extern char yyextra[]; extern int YYLENG; extern CHR YYTEXT[]; extern struct {int *yyaa, *yybb; int *yystops; } *yylstate[], **yylsp, **yyolsp; #if defined(__cplusplus) || defined(__STDC__) int YYREJECT(void) #else YYREJECT() #endif { for (; yylsp < yyolsp; yylsp++) YYTEXT[YYLENG++] = YYINPUT(); if (*yyfnd > 0) return (yyracc(*yyfnd++)); while (yylsp-- > yylstate) { YYUNPUT(YYTEXT[YYLENG-1]); YYTEXT[--YYLENG] = 0; if (*yylsp != 0 && (yyfnd = (*yylsp)->yystops) && *yyfnd > 0) return (yyracc(*yyfnd++)); } #ifdef EOPTION yyleng = wcstombs((char *)yytext, YYTEXT, YYLENG*MB_LEN_MAX); #endif if (YYTEXT[0] == 0) return (0); YYLENG = 0; #ifdef EOPTION yyleng = 0; #endif return (-1); } #ifdef EUC static #endif #if defined(__cplusplus) || defined(__STDC__) int yyracc(int m) #else yyracc(m) #endif { yyolsp = yylsp; if (yyextra[m]) { while (yyback((*yylsp)->yystops, -m) != 1 && yylsp > yylstate) { yylsp--; YYUNPUT(YYTEXT[--YYLENG]); } } yyprevious = YYTEXT[YYLENG-1]; YYTEXT[YYLENG] = 0; #ifdef EOPTION yyleng = wcstombs((char *)yytext, YYTEXT, YYLENG*MB_LEN_MAX); #endif return (m); }
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. * All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ /* from OpenSolaris "sub1.c 6.18 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)sub1.c 1.5 (gritter) 11/26/05 */ #include "ldefs.c" #include <limits.h> #include <wchar.h> #include <ctype.h> #include <stdarg.h> /* * return next line of input, throw away trailing '\n' * and also throw away trailing blanks (spaces and tabs) * returns 0 if eof is had immediately */ CHR * getl(CHR *p) { int c; CHR *s, *t, *u = NULL; int blank = 0; t = s = p; while (((c = gch()) != 0) && c != '\n') { if (t >= &p[BUF_SIZ]) error("definitions too long"); if (c == ' ' || c == '\t') { if (!blank) { blank = 1; u = t; } } else blank = 0; *t++ = c; } if (blank) *u = 0; else *t = 0; if (c == 0 && s == t) return ((CHR *) 0); prev = '\n'; pres = '\n'; return (s); } int space(int ch) { switch (ch) { case ' ': case '\t': case '\n': return (1); } return (0); } int digit(int c) { return (c >= '0' && c <= '9'); } /* VARARGS1 */ void error(const char *s, ...) { va_list ap; /* if(!eof) */ if (!yyline) fprintf(errorf, "Command line: "); else { fprintf(errorf, !no_input ? "" : "\"%s\":", sargv[optind]); fprintf(errorf, "line %d: ", yyline); } fprintf(errorf, "Error: "); va_start(ap, s); vfprintf(errorf, s, ap); va_end(ap); putc('\n', errorf); if (fatal) error_tail(); } void error_tail(void) { #ifdef DEBUG if (debug && sect != ENDSECTION) { sect1dump(); sect2dump(); } #endif if (report == 1) statistics(); exit(1); /* NOTREACHED */ } /* VARARGS1 */ void warning(const char *s, ...) { va_list ap; if (!eof) if (!yyline) fprintf(errorf, "Command line: "); else { fprintf(errorf, !no_input?"":"\"%s\":", sargv[optind]); fprintf(errorf, "line %d: ", yyline); } fprintf(errorf, "Warning: "); va_start(ap, s); vfprintf(errorf, s, ap); va_end(ap); putc('\n', errorf); fflush(errorf); if (fout) fflush(fout); fflush(stdout); } int index(int a, CHR *s) { int k; for (k = 0; s[k]; k++) if (s[k] == a) return (k); return (-1); } int alpha(int c) { return ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z'); } int printable(int c) { return (c > 040 && c < 0177); } void lgate(void) { char fname[20]; if (lgatflg) return; lgatflg = 1; if (fout == NULL) { sprintf(fname, "lex.yy.%c", ratfor ? 'r' : 'c'); fout = fopen(fname, "w"); } if (fout == NULL) error("Can't open %s", fname); if (ratfor) fprintf(fout, "#\n"); phead1(); } /* * scopy(ptr to str, ptr to str) - copy first arg str to second * returns ptr to second arg */ void scopy(CHR *s, CHR *t) { CHR *i; i = t; while (*i++ = *s++); } /* * convert string t, return integer value */ int siconv(CHR *t) { int i, sw; CHR *s; s = t; while (space(*s)) s++; if (!digit(*s) && *s != '-') error("missing translation value"); sw = 0; if (*s == '-') { sw = 1; s++; } if (!digit(*s)) error("incomplete translation format"); i = 0; while ('0' <= *s && *s <= '9') i = i * 10 + (*(s++)-'0'); return (sw ? -i : i); } /* * slength(ptr to str) - return integer length of string arg * excludes '\0' terminator */ int slength(CHR *s) { int n; CHR *t; t = s; for (n = 0; *t++; n++); return (n); } /* * scomp(x,y) - return -1 if x < y, * 0 if x == y, * return 1 if x > y, all lexicographically */ int scomp(CHR *x, CHR *y) { CHR *a, *d; a = (CHR *) x; d = (CHR *) y; while (*a || *d) { if (*a > *d) return (1); if (*a < *d) return (-1); a++; d++; } return (0); } int ctrans(CHR **ss) { int c, k; if ((c = **ss) != '\\') return (c); switch (c = *++*ss) { case 'a': c = '\a'; warning("\\a is ANSI C \"alert\" character"); break; case 'v': c = '\v'; break; case 'n': c = '\n'; break; case 't': c = '\t'; break; case 'r': c = '\r'; break; case 'b': c = '\b'; break; case 'f': c = 014; break; /* form feed for ascii */ case '\\': c = '\\'; break; case 'x': { int dd; warning("\\x is ANSI C hex escape"); if (digit((dd = *++*ss)) || ('a' <= dd && dd <= 'f') || ('A' <= dd && dd <= 'F')) { c = 0; while (digit(dd) || ('A' <= dd && dd <= 'F') || ('a' <= dd && dd <= 'f')) { if (digit(dd)) c = c*16 + dd - '0'; else if (dd >= 'a') c = c*16 + 10 + dd - 'a'; else c = c*16 + 10 + dd - 'A'; dd = *++*ss; } } else c = 'x'; break; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': c -= '0'; while ((k = *(*ss+1)) >= '0' && k <= '7') { c = c*8 + k - '0'; (*ss)++; } break; } return (c); } void cclinter(int sw) { /* sw = 1 ==> ccl */ int i, j, k; int m; if (!sw) { /* is NCCL */ for (i = 1; i < ncg; i++) symbol[i] ^= 1; /* reverse value */ } for (i = 1; i < ncg; i++) if (symbol[i]) break; if (i >= ncg) return; i = cindex[i]; /* see if ccl is already in our table */ j = 0; if (i) { for (j = 1; j < ncg; j++) { if ((symbol[j] && cindex[j] != i) || (!symbol[j] && cindex[j] == i)) break; } } if (j >= ncg) return; /* already in */ m = 0; k = 0; for (i = 1; i < ncg; i++) { if (symbol[i]) { if (!cindex[i]) { cindex[i] = ccount; symbol[i] = 0; m = 1; } else k = 1; } } /* m == 1 implies last value of ccount has been used */ if (m) ccount++; if (k == 0) return; /* is now in as ccount wholly */ /* intersection must be computed */ for (i = 1; i < ncg; i++) { if (symbol[i]) { m = 0; j = cindex[i]; /* will be non-zero */ for (k = 1; k < ncg; k++) { if (cindex[k] == j) { if (symbol[k]) symbol[k] = 0; else { cindex[k] = ccount; m = 1; } } } if (m) ccount++; } } } int usescape(int c) { char d; switch (c) { case 'a': c = '\a'; warning("\\a is ANSI C \"alert\" character"); break; case 'v': c = '\v'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'b': c = '\b'; break; case 'f': c = 014; break; /* form feed for ascii */ case 'x': { int dd; if (digit((dd = gch())) || ('A' <= dd && dd <= 'F') || ('a' <= dd && dd <= 'f')) { c = 0; while (digit(dd) || ('A' <= dd && dd <= 'F') || ('a' <= dd && dd <= 'f')) { if (digit(dd)) c = c*16 + dd - '0'; else if (dd >= 'a') c = c*16 + 10 + dd - 'a'; else c = c*16 + 10 + dd - 'A'; if (!digit(peek) && !('A' <= peek && peek <= 'F') && !('a' <= peek && peek <= 'f')) break; dd = gch(); } } else c = 'x'; break; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': c -= '0'; while ('0' <= (d = gch()) && d <= '7') { c = c * 8 + (d-'0'); if (!('0' <= peek && peek <= '7')) break; } break; } if (handleeuc && !isascii(c)) { char tmpchar = c & 0x00ff; wchar_t wc; mbtowc(&wc, &tmpchar, sizeof (tmpchar)); c = wc; } return (c); } int lookup(CHR *s, CHR **t) { int i; i = 0; while (*t) { if (scomp(s, *t) == 0) return (i); i++; t++; } return (-1); } void cpycom(CHR *p) { static CHR *t; static int c; t = p; if (sargv[optind] == NULL) fprintf(fout, "\n# line %d\n", yyline); else fprintf(fout, "\n# line %d \"%s\"\n", yyline, sargv[optind]); putc(*t, fout), t++; putc(*t, fout), t++; while (*t) { while (*t == '*') { putc(*t, fout), t++; if (*t == '/') goto backcall; } /* * FIX BUG #1058428, not parsing comments correctly * that span more than one line */ if (*t != '\0') putc(*t, fout), t++; } putc('\n', fout); while (c = gch()) { while (c == '*') { putc(c, fout); if ((c = gch()) == '/') { while ((c = gch()) == ' ' || c == '\t'); if (!space(c)) error("unacceptable statement"); prev = '\n'; goto backcall; } } putc(c, fout); } error("unexpected EOF inside comment"); backcall: putc('/', fout); putc('\n', fout); } /* * copy C action to the next ; or closing */ int cpyact(void) { int brac, c, mth; static int sw, savline; brac = 0; sw = TRUE; savline = yyline; if (sargv[optind] == NULL) fprintf(fout, "\n# line %d\n", yyline); else fprintf(fout, "\n# line %d \"%s\"\n", yyline, sargv[optind]); while (!eof) { c = gch(); swt: switch (c) { case '|': if (brac == 0 && sw == TRUE) { if (peek == '|') gch(); /* eat up an extra '|' */ return (0); } break; case ';': if (brac == 0) { putwc(c, fout); putc('\n', fout); return (1); } break; case '{': brac++; savline = yyline; break; case '}': brac--; if (brac == 0) { putwc(c, fout); putc('\n', fout); return (1); } break; case '/': putwc(c, fout); c = gch(); if (c != '*') goto swt; putwc(c, fout); savline = yyline; while (c = gch()) { while (c == '*') { putwc(c, fout); if ((c = gch()) == '/') { putc('/', fout); while ((c = gch()) == ' ' || c == '\t' || c == '\n') putwc(c, fout); goto swt; } } putc(c, fout); } yyline = savline; error("EOF inside comment"); /* NOTREACHED */ break; case '\'': /* character constant */ case '"': /* character string */ mth = c; putwc(c, fout); while (c = gch()) { if (c == '\\') { putwc(c, fout); c = gch(); } else if (c == mth) goto loop; putwc(c, fout); if (c == '\n') { yyline--; error( "Non-terminated string or character constant"); } } error("EOF in string or character constant"); /* NOTREACHED */ break; case '\0': yyline = savline; error("Action does not terminate"); /* NOTREACHED */ break; default: break; /* usual character */ } loop: if (c != ' ' && c != '\t' && c != '\n') sw = FALSE; putwc(c, fout); if (peek == '\n' && !brac && copy_line) { putc('\n', fout); return (1); } } error("Premature EOF"); return (0); } int gch(void) { int c; prev = pres; c = pres = peek; peek = pushptr > pushc ? *--pushptr : getwc(fin); while (peek == EOF) { if (no_input) { if (!yyline) error("Cannot read from -- %s", sargv[optind]); if (optind < sargc-1) { yyline = 0; if (fin != stdin) fclose(fin); fin = fopen(sargv[++optind], "r"); if (fin == NULL) error("Cannot open file -- %s", sargv[optind]); peek = getwc(fin); } else break; } else { if (fin != stdin) fclose(fin); if (!yyline) error("Cannot read from -- standard input"); else break; } } if (c == EOF) { eof = TRUE; return (0); } if (c == '\n') yyline++; return (c); } int mn2(int a, intptr_t d, intptr_t c) { if (tptr >= treesize) { tptr++; error("Parse tree too big %s", (treesize == TREESIZE ? "\nTry using %e num" : "")); } if (d >= treesize) { error("Parse error"); } name[tptr] = a; left[tptr] = d; right[tptr] = c; parent[tptr] = 0; nullstr[tptr] = 0; switch (a) { case RSTR: parent[d] = tptr; break; case BAR: case RNEWE: if (nullstr[d] || nullstr[c]) nullstr[tptr] = TRUE; parent[d] = parent[c] = tptr; break; case RCAT: case DIV: if (nullstr[d] && nullstr[c]) nullstr[tptr] = TRUE; parent[d] = parent[c] = tptr; break; /* XCU4: add RXSCON */ case RXSCON: case RSCON: parent[d] = tptr; nullstr[tptr] = nullstr[d]; break; #ifdef DEBUG default: warning("bad switch mn2 %d %d", a, d); break; #endif } return (tptr++); } int mn1(int a, intptr_t d) { if (tptr >= treesize) { tptr++; error("Parse tree too big %s", (treesize == TREESIZE ? "\nTry using %e num" : "")); } name[tptr] = a; left[tptr] = d; parent[tptr] = 0; nullstr[tptr] = 0; switch (a) { case RCCL: case RNCCL: if (slength((CHR *)d) == 0) nullstr[tptr] = TRUE; break; case STAR: case QUEST: nullstr[tptr] = TRUE; parent[d] = tptr; break; case PLUS: case CARAT: nullstr[tptr] = nullstr[d]; parent[d] = tptr; break; case S2FINAL: nullstr[tptr] = TRUE; break; #ifdef DEBUG case FINAL: case S1FINAL: break; default: warning("bad switch mn1 %d %d", a, d); break; #endif } return (tptr++); } int mn0(int a) { if (tptr >= treesize) { tptr++; error("Parse tree too big %s", (treesize == TREESIZE ? "\nTry using %e num" : "")); } name[tptr] = a; parent[tptr] = 0; nullstr[tptr] = 0; if (ISOPERATOR(a)) { switch (a) { case DOT: break; case RNULLS: nullstr[tptr] = TRUE; break; #ifdef DEBUG default: warning("bad switch mn0 %d", a); break; #endif } } return (tptr++); } void munput(int t, CHR *p) { int i, j; if (t == 'c') { *pushptr++ = peek; peek = *p; } else if (t == 's') { *pushptr++ = peek; peek = p[0]; i = slength(p); for (j = i - 1; j >= 1; j--) *pushptr++ = p[j]; } if (pushptr >= pushc + TOKENSIZE) error("Too many characters pushed"); } int dupl(int n) { /* duplicate the subtree whose root is n, return ptr to it */ int i; i = name[n]; if (!ISOPERATOR(i)) return (mn0(i)); switch (i) { case DOT: case RNULLS: return (mn0(i)); case RCCL: case RNCCL: case FINAL: case S1FINAL: case S2FINAL: return (mn1(i, left[n])); case STAR: case QUEST: case PLUS: case CARAT: return (mn1(i, dupl(left[n]))); /* XCU4: add RXSCON */ case RSTR: case RSCON: case RXSCON: return (mn2(i, dupl(left[n]), right[n])); case BAR: case RNEWE: case RCAT: case DIV: return (mn2(i, dupl(left[n]), dupl(right[n]))); } return (0); } #ifdef DEBUG void allprint(CHR c) { switch (c) { case 014: printf("\\f"); charc++; break; case '\n': printf("\\n"); charc++; break; case '\t': printf("\\t"); charc++; break; case '\b': printf("\\b"); charc++; break; case ' ': printf("\\_"); break; default: if (!iswprint(c)) { printf("\\x%-2x", c); /* up to fashion. */ charc += 3; } else putwc(c, stdout); break; } charc++; } void strpt(CHR *s) { charc = 0; while (*s) { allprint(*s++); if (charc > LINESIZE) { charc = 0; printf("\n\t"); } } } void sect1dump(void) { int i; printf("Sect 1:\n"); if (def[0]) { printf("str trans\n"); i = -1; while (def[++i]) printf("%ls\t%ls\n", def[i], subs[i]); } if (sname[0]) { printf("start names\n"); i = -1; while (sname[++i]) printf("%ls\n", sname[i]); } if (chset == TRUE) { printf("char set changed\n"); for (i = 1; i < NCH; i++) { if (i != ctable[i]) { allprint(i); putchar(' '); iswprint(ctable[i]) ? putwc(ctable[i], stdout) : printf("%d", ctable[i]); putchar('\n'); } } } } void sect2dump(void) { printf("Sect 2:\n"); treedump(); } void treedump(void) { int t; CHR *p; printf("treedump %d nodes:\n", tptr); for (t = 0; t < tptr; t++) { printf("%4d ", t); parent[t] ? printf("p=%4d", parent[t]) : printf(" "); printf(" "); if (!ISOPERATOR(name[t])) { allprint(name[t]); } else switch (name[t]) { case RSTR: printf("%ld ", (long)left[t]); allprint(right[t]); break; case RCCL: printf("ccl "); strpt((CHR *)left[t]); break; case RNCCL: printf("nccl "); strpt((CHR *)left[t]); break; case DIV: printf("/ %ld %ld", (long)left[t], (long)right[t]); break; case BAR: printf("| %ld %ld", (long)left[t], (long)right[t]); break; case RCAT: printf("cat %ld %ld", (long)left[t], (long)right[t]); break; case PLUS: printf("+ %ld", (long)left[t]); break; case STAR: printf("* %ld", (long)left[t]); break; case CARAT: printf("^ %ld", (long)left[t]); break; case QUEST: printf("? %ld", (long)left[t]); break; case RNULLS: printf("nullstring"); break; case FINAL: printf("final %ld", (long)left[t]); break; case S1FINAL: printf("s1final %ld", (long)left[t]); break; case S2FINAL: printf("s2final %ld", (long)left[t]); break; case RNEWE: printf("new %ld %ld", (long)left[t], (long)right[t]); break; /* XCU4: add RXSCON */ case RXSCON: p = (CHR *)right[t]; printf("exstart %s", sname[*p++-1]); while (*p) printf(", %ls", sname[*p++-1]); printf(" %ld", (long)left[t]); break; case RSCON: p = (CHR *)right[t]; printf("start %s", sname[*p++-1]); while (*p) printf(", %ls", sname[*p++-1]); printf(" %ld", (long)left[t]); break; case DOT: printf("dot"); break; default: printf( "unknown %d %ld %ld", name[t], (long)left[t], (long)right[t]); break; } if (nullstr[t]) printf("\t(null poss.)"); putchar('\n'); } } #endif
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. * All rights reserved. * Use is subject to license terms. */ /* from OpenSolaris "sub3.c 1.8 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)sub3.c 1.4 (gritter) 11/26/05 */ /* * sub3.c ... ALE enhancement. * Since a typical Asian language has a huge character set, it is not * ideal to index an array by a character code itself, which requires * as large as 2**16 entries per array. * To get arround this problem, we identify a set of characters that * causes the same transition on all states and call it character group. * Every character in a same character group has a unique number called * character group id. A function yycgid(c) maps the character c (in process * code) to the id. This mapping is determined by analyzing all regular * expressions in the lex program. * */ #include <stdlib.h> #ifdef __sun #include <widec.h> #endif #include "search.h" #include "ldefs.c" #include <ctype.h> /* * "lchar" stands for linearized character. It is a variant of * process code. AT&T's 16-bit process code has a drawback in which * for three three process code C, D and E where C <= D <= E, * codeset(C)==codeset(E) does not mean codeset(D)==codeset(C). * In other words, four codesets alternates as the magnitude * of character increases. * The lchar representation holds this property: * If three lchar C', D' and E' have the relationship C' < D' < E' and * codeset(C') == codeset(E') then D' is guaranteed to belong to * the same codeset as C' and E'. * lchar is implemented as 32 bit entities and the function linearize() * that maps a wchar_t to lchar is defined below. There is no * reverse function for it though. * The 32-bit process code by AT&T, used only for Taiwanese version at the * time of wrting, has no such problem and we use it as it is. */ lchar yycgidtbl[MAXNCG] = { 0, /* For ease of computation of the id. */ '\n', /* Newline is always special because '.' exclude it. */ 0x000000ff, /* The upper limit of codeset 0. */ 0x20ffffff, /* The upper limit of codeset 2. */ 0x40ffffff /* The upper limit of codeset 3. */ /* 0x60ffffff The upper limit of codeset 1. */ /* Above assumes the number of significant bits of wchar_t is <= 24. */ }; int ncgidtbl = 5; /* # elements in yycgidtbl. */ int ncg; /* Should set to ncgidtbl*2; this is the largest value yycgid() */ /* returns plus 1. */ static void setsymbol(int i); /* * For given 16-bit wchar_t (See NOTE), lchar is computed as illustrated below: * * wc: axxxxxxbyyyyyyy * * returns: 0ab0000000000000axxxxxxxbyyyyyyy * * linearize() doesn't do any if compiled with 32-bit wchar_t, use of * which is flagged with LONG_WCHAR_T macro. * NOTE: * The implementation is highly depends on the process code representation. * This function should be modified when 32-bit process code is used. * There is no need to keep 'a' and 'b' bits in the lower half of lchar. * You can actually omit these and squeeze the xxxxxx part one bit right. * We don't do that here just in sake of speed. */ lchar linearize(wchar_t wc) { #ifdef LONG_WCHAR_T return ((lchar)wc); /* Don't do anything. */ #else lchar prefix; switch (wc&0x8080) { case 0x0000: prefix = 0x00000000; break; case 0x0080: prefix = 0x20000000; break; case 0x8000: prefix = 0x40000000; break; case 0x8080: prefix = 0x60000000; break; } return (prefix|wc); #endif } /* compare liniear characters pointed to by pc1 and pc2 */ int cmplc(const void *arg1, const void *arg2) { lchar *pc1 = (lchar *)arg1; lchar *pc2 = (lchar *)arg2; if (*pc1 > *pc2) return (1); else if (*pc1 == *pc2) return (0); else return (-1); } void remch(wchar_t c) { lchar lc = linearize(c); /* * User-friendliness consideration: * Make sure no EUC chars are used in reg. exp. */ if (!handleeuc) { if (!isascii(c)) if (iswprint(c)) warning( "Non-ASCII character '%lc' in pattern; use -w or -e lex option.", c); else warning( "Non-ASCII character of value %#x in pattern; use -w or -e lex option.", c); /* In any case, we don't need to construct ncgidtbl[]. */ return; } xlsearch(&lc, yycgidtbl, (unsigned *)&ncgidtbl, sizeof (lchar), cmplc); } void sortcgidtbl(void) { if (!handleeuc) return; qsort(yycgidtbl, ncgidtbl, sizeof (lchar), cmplc); } /* * int yycgid(wchar_t c) * Takes c and returns its character group id, determind by the * following algorithm. The program also uses the binary search * algorithm, generalized from Knuth (6.2.1) Algorithm B. * * This function computes the "character group id" based on * a table yycgidtbl of which each lchar entry is pre-sorted * in ascending sequence The number of valid entries is given * by YYNCGIDTBL. There is no duplicate entries in yycgidtbl. * const int YYNCGIDTBL; * lchar yycgidtbl[YYNCGIDTBL]; * * yycgidtbl[0] is guaranteed to have zero. * * For given c, yycgid(c) returns: * 2*i iff yycgidtbl[i] == lc * 2*i+1 iff yycgidtbl[i] < lc < yycgidtbl[i+1] * YYNCGIDTBL*2-1 * iff yycgidtbl[YYNCGIDTBL-1] < lc * where lc=linearize(c). * * Some interesting properties.: * 1. For any c, 0 <= yycgid(c) <= 2*YYNCGIDTBL-1 * 2. yycgid(c) == 0 iff c == 0. * 3. For any wchar_t c and d, if linearize(c) < linearize(d) then * yycgid(c) <= yycgid(d). * 4. For any wchar_t c and d, if yycgid(c) < yycgid(d) then * linearize(c) < linearize(d). */ #define YYNCGIDTBL ncgidtbl int yycgid(wchar_t c) { int first = 0; int last = YYNCGIDTBL - 1; lchar lc; /* * In ASCII compat. mode, each character forms a "group" and the * group-id is itself... */ if (!handleeuc) return (c); lc = linearize(c); /* An exceptional case: yycgidtbl[YYNCGIDTBL-1] < lc */ if (yycgidtbl[YYNCGIDTBL - 1] < lc) return (YYNCGIDTBL*2 - 1); while (last >= 0) { int i = (first+last)/2; if (lc == yycgidtbl[i]) return (2*i); /* lc exactly matches an element. */ else if (yycgidtbl[i] < lc) { if (lc < yycgidtbl[i+1]) return (2*i+1); /* lc is in between two elements. */ else first = i + 1; } else last = i - 1; } error( "system error in yycgid():binary search failed for c=0x%04x\n", c); return (0); } /* * repbycgid --- replaces each character in the parsing tree by its * character group id. This, however, should be called even in * the ASCII compat. mode to process DOT nodes and to call cclinter() * for the DOT and CCL nodes. */ void repbycgid(void) { int i, c; for (i = 0; i < tptr; ++i) { c = name[i]; if (!ISOPERATOR(c)) { /* If not an operator, it must be a char. */ name[i] = yycgid((wchar_t)c); /* So replace it. */ #ifdef DEBUG if (debug) { printf("name[%d]:'%c'->%d;\n", i, c, name[i]); } #endif } else if (c == RSTR) { c = right[i]; right[i] = yycgid((wchar_t)c); #ifdef DEBUG if (debug) { printf( "name[%d].right:'%c'->%d;\n", i, c, right[i]); } #endif } else if ((c == RCCL) || (c == RNCCL)) { CHR cc, *s; int j; CHR ccltoken[CCLSIZE]; CHR *ccp; int m; /* * This node represetns a character class RE [ccccc] * s points to the string of characters that forms * the class and/or a special prefix notation * <RANGE>XY which corresponds to the RE X-Y, * characters in the range of X and Y. Here, * X <= Y is guranteed. * We transform these characters into a string * of sorted character group ids. * * There is another mechanism of packing tables * that is inherited from the ASCII lex. Call of * cclinter() is required for this packing. * This used to be done as yylex() reads the lex * rules but we have to do this here because the * transition table is made to work on the char-group * ids and the mapping cannot be determined until * the entire file is read. */ #ifdef DEBUG if (debug) { printf("name[%d]:R[N]CCL of \"", i); strpt((CHR *)left[i]); printf(" -> {"); } #endif /* Prepare symbol[] for cclinter(). */ for (j = 0; j < ncg; ++j) symbol[j] = FALSE; s = (CHR *) left[i]; while (cc = *s++) { if (cc == RANGE) { int low, high, i; /* * Special form: <RANGE>XY * This means the range X-Y. * We mark all symbols[] * elements for yycgid(X) thru * yycgid(Y), inclusively. */ low = yycgid(*s++); high = yycgid(*s++); for (i = low; i <= high; ++i) setsymbol(i); } else { setsymbol(yycgid(cc)); } } /* Now make a transformed string of cgids. */ s = ccptr; m = 0; for (j = 0; j < ncg; ++j) if (symbol[j]) { ccltoken[m++] = (CHR)j; #ifdef DEBUG if (debug) printf("%d, ", j); #endif } #ifdef DEBUG if (debug) printf("}\n"); #endif ccltoken[m] = 0; ccp = ccl; while (ccp < ccptr && scomp(ccltoken, ccp) != 0) ccp++; if (ccp < ccptr) { /* character class found in ccl */ left[i] = (intptr_t)ccp; } else { /* not in ccl, add it */ left[i] = (intptr_t)ccptr; scopy(ccltoken, ccptr); ccptr += slength(ccltoken) + 1; if (ccptr > ccl + CCLSIZE) error("Too many large character classes"); } cclinter(c == RCCL); } else if (c == DOT) { if (psave == 0) { /* First DOT node. */ int j, nlid; /* * Make symbol[k]=TRUE for all k * except k == yycgid('\n'). */ nlid = yycgid('\n'); psave = ccptr; for (j = 1; j < ncg; ++j) { if (j == nlid) { symbol[j] = FALSE; } else { symbol[j] = TRUE; *ccptr++ = (CHR) j; } } *ccptr++ = 0; if (ccptr > ccl + CCLSIZE) error("Too many large character classes"); } /* Mimic mn1(RCCL,psave)... */ name[i] = RCCL; left[i] = (intptr_t)psave; cclinter(1); } } #ifdef DEBUG if (debug) { printf("treedump after repbycgid().\n"); treedump(); } #endif } static void setsymbol(int i) { if (i > sizeof (symbol)) error("setsymbol: (SYSERR) %d out of range", i); symbol[i] = TRUE; }
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)main.c 1.2 (gritter) 6/14/05 */ #if __GNUC__ >= 3 && __GNUC_MINOR__ >= 4 || __GNUC__ >= 4 #define USED __attribute__ ((used)) #elif defined __GNUC__ #define USED __attribute__ ((unused)) #else #define USED #endif static const char sccsid[] USED = "@(#)yacc.sl 2.6 (gritter) 11/26/05"; static const char pkg[] = "Heirloom Development Tools"; static const char rel[] = "2.6 (gritter) 11/26/05"; /* SLIST */ /* dextern: * Sccsid @(#)dextern 1.6 (gritter) 11/10/05 getopt.c: * Sccsid @(#)getopt.c 1.8 (gritter) 8/2/05 libmai.c: * Sccsid @(#)libmai.c 1.3 (gritter) 6/18/05 libzer.c: * Sccsid @(#)libzer.c 1.3 (gritter) 6/18/05 y1.c: * Sccsid @(#)y1.c 1.7 (gritter) 11/26/05 y2.c: * Sccsid @(#)y2.c 1.11 (gritter) 11/26/05 y3.c: * Sccsid @(#)y3.c 1.5 (gritter) 11/26/05 y4.c: * Sccsid @(#)y4.c 1.5 (gritter) 11/26/05 y5.c: * Sccsid @(#)y5.c 1.1 (gritter) 6/25/05 yaccpar: * Sccsid @(#)yaccpar 1.5 (gritter) 11/26/05 */
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)getopt.c 1.9 (gritter) 4/2/07 */ /* from OpenSolaris "getopt.c 1.23 05/06/08 SMI" */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ /* * See getopt(3C) and SUS/XPG getopt() for function definition and * requirements. * * This actual implementation is a bit looser than the specification * as it allows any character other than ':' to be used as an option * character - The specification only guarantees the alnum characters * ([a-z][A-Z][0-9]). */ #include <unistd.h> #include <string.h> #include <stdio.h> char *optarg = NULL; int optind = 1; int opterr = 1; int optopt = 0; #define ERR(s, c) err(s, c, optstring, argv[0]) static void err(const char *s, int c, const char *optstring, const char *argv0) { char errbuf[256], *ep = errbuf; const char *cp; if (opterr && optstring[0] != ':') { for (cp = argv0; *cp && ep<&errbuf[sizeof errbuf]; cp++, ep++) *ep = *cp; for (cp = ": "; *cp && ep<&errbuf[sizeof errbuf]; cp++, ep++) *ep = *cp; for (cp = s; *cp && ep<&errbuf[sizeof errbuf]; cp++, ep++) *ep = *cp; for (cp = " -- "; *cp && ep<&errbuf[sizeof errbuf]; cp++, ep++) *ep = *cp; if (ep<&errbuf[sizeof errbuf]) *ep++ = c; if (ep<&errbuf[sizeof errbuf]) *ep++ = '\n'; write(2, errbuf, ep - errbuf); } } /* * getopt_sp is required to keep state between successive calls to getopt() * while extracting aggregated options (ie: -abcd). Hence, getopt() is not * thread safe or reentrant, but it really doesn't matter. * * So, why isn't this "static" you ask? Because the historical Bourne * shell has actually latched on to this little piece of private data. */ int getopt_sp = 1; /* * Determine if the specified character (c) is present in the string * (optstring) as a regular, single character option. If the option is found, * return a pointer into optstring pointing at the option character, * otherwise return null. The character ':' is not allowed. */ static char * parse(const char *optstring, const char c) { char *cp = (char *)optstring; if (c == ':') return (NULL); do { if (*cp == c) return (cp); } while (*cp++ != '\0'); return (NULL); } /* * External function entry point. */ int getopt(int argc, char *const *argv, const char *optstring) { char c; char *cp; /* * Has the end of the options been encountered? The following * implements the SUS requirements: * * If, when getopt() is called: * argv[optind] is a null pointer * *argv[optind] is not the character '-' * argv[optind] points to the string "-" * getopt() returns -1 without changing optind. If * argv[optind] points to the string "--" * getopt() returns -1 after incrementing optind. */ if (getopt_sp == 1) { if (optind >= argc || argv[optind][0] != '-' || argv[optind] == NULL || argv[optind][1] == '\0') return (EOF); else if (strcmp(argv[optind], "--") == 0) { optind++; return (EOF); } } /* * Getting this far indicates that an option has been encountered. * Note that the syntax of optstring applies special meanings to * the characters ':' and '(', so they are not permissible as * option letters. A special meaning is also applied to the ')' * character, but its meaning can be determined from context. * Note that the specification only requires that the alnum * characters be accepted. */ optopt = c = (unsigned char)argv[optind][getopt_sp]; optarg = NULL; if ((cp = parse(optstring, c)) == NULL) { /* LINTED: variable format specifier */ ERR("illegal option", c); if (argv[optind][++getopt_sp] == '\0') { optind++; getopt_sp = 1; } return ('?'); } optopt = c = *cp; /* * A valid option has been identified. If it should have an * option-argument, process that now. SUS defines the setting * of optarg as follows: * * 1. If the option was the last character in the string pointed to * by an element of argv, then optarg contains the next element * of argv, and optind is incremented by 2. If the resulting * value of optind is not less than argc, this indicates a * missing option-argument, and getopt() returns an error * indication. * * 2. Otherwise, optarg points to the string following the option * character in that element of argv, and optind is incremented * by 1. * * The second clause allows -abcd (where b requires an option-argument) * to be interpreted as "-a -b cd". */ if (*(cp + 1) == ':') { /* The option takes an argument */ if (argv[optind][getopt_sp+1] != '\0') { optarg = &argv[optind++][getopt_sp+1]; } else if (++optind >= argc) { /* LINTED: variable format specifier */ ERR("option requires an argument", c); getopt_sp = 1; optarg = NULL; return (optstring[0] == ':' ? ':' : '?'); } else optarg = argv[optind++]; getopt_sp = 1; } else { /* The option does NOT take an argument */ if (argv[optind][++getopt_sp] == '\0') { getopt_sp = 1; optind++; } optarg = NULL; } return (c); } /* getopt() */
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* Copyright (c) 1989 AT&T */ /* All Rights Reserved */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* from OpenSolaris "libmai.c 6.9 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)libmai.c 1.3 (gritter) 6/18/05 */ #include <locale.h> extern int yyparse(void); int main(void) { setlocale(LC_ALL, ""); yyparse(); return (0); }
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* Copyright (c) 1989 AT&T */ /* All Rights Reserved */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* from OpenSolaris "libzer.c 6.6 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)libzer.c 1.3 (gritter) 6/18/05 */ #include <stdio.h> void yyerror(const char *s) { fprintf(stderr, "%s\n", s); }
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 1993 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ /* from OpenSolaris "yaccpar 6.18 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)yaccpar 1.5 (gritter) 11/26/05 */ /* ** Skeleton parser driver for yacc output */ /* ** yacc user known macros and defines */ #define YYERROR goto yyerrlab #define YYACCEPT return(0) #define YYABORT return(1) #define YYBACKUP( newtoken, newvalue )\ {\ if ( yychar >= 0 || ( yyr2[ yytmp ] >> 1 ) != 1 )\ {\ yyerror( "syntax error - cannot backup" );\ goto yyerrlab;\ }\ yychar = newtoken;\ yystate = *yyps;\ yylval = newvalue;\ goto yynewstate;\ } #define YYRECOVERING() (!!yyerrflag) #define YYNEW(type) malloc(sizeof(type) * yynewmax) #define YYCOPY(to, from, type) \ (type *) memcpy(to, (char *) from, yymaxdepth * sizeof (type)) #define YYENLARGE( from, type) \ (type *) realloc((char *) from, yynewmax * sizeof(type)) #ifndef YYDEBUG # define YYDEBUG 1 /* make debugging available */ #endif /* ** user known globals */ int yydebug; /* set to 1 to get debugging */ /* ** driver internal defines */ #define YYFLAG (-10000000) /* ** global variables used by the parser */ YYSTYPE *yypv; /* top of value stack */ int *yyps; /* top of state stack */ int yystate; /* current state */ int yytmp; /* extra var (lasts between blocks) */ int yynerrs; /* number of errors */ int yyerrflag; /* error recovery flag */ int yychar; /* current input token number */ #ifdef YYNMBCHARS #define YYLEX() yycvtok(yylex()) /* ** yycvtok - return a token if i is a wchar_t value that exceeds 255. ** If i<255, i itself is the token. If i>255 but the neither ** of the 30th or 31st bit is on, i is already a token. */ #if defined(__STDC__) || defined(__cplusplus) int yycvtok(int i) #else int yycvtok(i) int i; #endif { int first = 0; int last = YYNMBCHARS - 1; int mid; wchar_t j; if(i&0x60000000){/*Must convert to a token. */ if( yymbchars[last].character < i ){ return i;/*Giving up*/ } while ((last>=first)&&(first>=0)) {/*Binary search loop*/ mid = (first+last)/2; j = yymbchars[mid].character; if( j==i ){/*Found*/ return yymbchars[mid].tvalue; }else if( j<i ){ first = mid + 1; }else{ last = mid -1; } } /*No entry in the table.*/ return i;/* Giving up.*/ }else{/* i is already a token. */ return i; } } #else/*!YYNMBCHARS*/ #define YYLEX() yylex() #endif/*!YYNMBCHARS*/ /* ** yyparse - return 0 if worked, 1 if syntax error not recovered from */ #if defined(__STDC__) || defined(__cplusplus) int yyparse(void) #else int yyparse() #endif { register YYSTYPE *yypvt = 0; /* top of value stack for $vars */ #if defined(__cplusplus) || defined(lint) || defined(__GNUC__) /* hacks to please C++, lint, and gcc - goto's inside switch should never be executed */ static int _yaccpar_lint_hack = -1; switch (_yaccpar_lint_hack) { case 1: goto yyerrlab; case 2: goto yynewstate; } #endif /* ** Initialize externals - yyparse may be called more than once */ yypv = &yyv[-1]; yyps = &yys[-1]; yystate = 0; yytmp = 0; yynerrs = 0; yyerrflag = 0; yychar = -1; #if YYMAXDEPTH <= 0 if (yymaxdepth <= 0) { if ((yymaxdepth = YYEXPAND(0)) <= 0) { yyerror("yacc initialization error"); YYABORT; } } #endif { register YYSTYPE *yy_pv; /* top of value stack */ register int *yy_ps; /* top of state stack */ register int yy_state; /* current state */ register int yy_n; /* internal state number info */ goto yystack; /* moved from 6 lines above to here to please C++ */ /* ** get globals into registers. ** branch to here only if YYBACKUP was called. */ yynewstate: yy_pv = yypv; yy_ps = yyps; yy_state = yystate; goto yy_newstate; /* ** get globals into registers. ** either we just started, or we just finished a reduction */ yystack: yy_pv = yypv; yy_ps = yyps; yy_state = yystate; /* ** top of for (;;) loop while no reductions done */ yy_stack: /* ** put a state and value onto the stacks */ #if YYDEBUG /* ** if debugging, look up token value in list of value vs. ** name pairs. 0 and negative (-1) are special values. ** Note: linear search is used since time is not a real ** consideration while debugging. */ if ( yydebug ) { register int yy_i; printf( "State %d, token ", yy_state ); if ( yychar == 0 ) printf( "end-of-file\n" ); else if ( yychar < 0 ) printf( "-none-\n" ); else { for ( yy_i = 0; yytoks[yy_i].t_val >= 0; yy_i++ ) { if ( yytoks[yy_i].t_val == yychar ) break; } printf( "%s\n", yytoks[yy_i].t_name ); } } #endif /* YYDEBUG */ if ( ++yy_ps >= &yys[ yymaxdepth ] ) /* room on stack? */ { /* ** reallocate and recover. Note that pointers ** have to be reset, or bad things will happen */ long yyps_index = (yy_ps - yys); long yypv_index = (yy_pv - yyv); long yypvt_index = (yypvt - yyv); int yynewmax; #ifdef YYEXPAND yynewmax = YYEXPAND(yymaxdepth); #else yynewmax = 2 * yymaxdepth; /* double table size */ if (yymaxdepth == YYMAXDEPTH) /* first time growth */ { char *newyys = (char *)YYNEW(int); char *newyyv = (char *)YYNEW(YYSTYPE); if (newyys != 0 && newyyv != 0) { yys = YYCOPY(newyys, yys, int); yyv = YYCOPY(newyyv, yyv, YYSTYPE); } else yynewmax = 0; /* failed */ } else /* not first time */ { yys = YYENLARGE(yys, int); yyv = YYENLARGE(yyv, YYSTYPE); if (yys == 0 || yyv == 0) yynewmax = 0; /* failed */ } #endif if (yynewmax <= yymaxdepth) /* tables not expanded */ { yyerror( "yacc stack overflow" ); YYABORT; } yymaxdepth = yynewmax; yy_ps = yys + yyps_index; yy_pv = yyv + yypv_index; yypvt = yyv + yypvt_index; } *yy_ps = yy_state; *++yy_pv = yyval; /* ** we have a new state - find out what to do */ yy_newstate: if ( ( yy_n = yypact[ yy_state ] ) <= YYFLAG ) goto yydefault; /* simple state */ #if YYDEBUG /* ** if debugging, need to mark whether new token grabbed */ yytmp = yychar < 0; #endif if ( ( yychar < 0 ) && ( ( yychar = YYLEX() ) < 0 ) ) yychar = 0; /* reached EOF */ #if YYDEBUG if ( yydebug && yytmp ) { register int yy_i; printf( "Received token " ); if ( yychar == 0 ) printf( "end-of-file\n" ); else if ( yychar < 0 ) printf( "-none-\n" ); else { for ( yy_i = 0; yytoks[yy_i].t_val >= 0; yy_i++ ) { if ( yytoks[yy_i].t_val == yychar ) break; } printf( "%s\n", yytoks[yy_i].t_name ); } } #endif /* YYDEBUG */ if ( ( ( yy_n += yychar ) < 0 ) || ( yy_n >= YYLAST ) ) goto yydefault; if ( yychk[ yy_n = yyact[ yy_n ] ] == yychar ) /*valid shift*/ { yychar = -1; yyval = yylval; yy_state = yy_n; if ( yyerrflag > 0 ) yyerrflag--; goto yy_stack; } yydefault: if ( ( yy_n = yydef[ yy_state ] ) == -2 ) { #if YYDEBUG yytmp = yychar < 0; #endif if ( ( yychar < 0 ) && ( ( yychar = YYLEX() ) < 0 ) ) yychar = 0; /* reached EOF */ #if YYDEBUG if ( yydebug && yytmp ) { register int yy_i; printf( "Received token " ); if ( yychar == 0 ) printf( "end-of-file\n" ); else if ( yychar < 0 ) printf( "-none-\n" ); else { for ( yy_i = 0; yytoks[yy_i].t_val >= 0; yy_i++ ) { if ( yytoks[yy_i].t_val == yychar ) { break; } } printf( "%s\n", yytoks[yy_i].t_name ); } } #endif /* YYDEBUG */ /* ** look through exception table */ { register YYCONST int *yyxi = yyexca; while ( ( *yyxi != -1 ) || ( yyxi[1] != yy_state ) ) { yyxi += 2; } while ( ( *(yyxi += 2) >= 0 ) && ( *yyxi != yychar ) ) ; if ( ( yy_n = yyxi[1] ) < 0 ) YYACCEPT; } } /* ** check for syntax error */ if ( yy_n == 0 ) /* have an error */ { /* no worry about speed here! */ switch ( yyerrflag ) { case 0: /* new error */ yyerror( "syntax error" ); goto skip_init; yyerrlab: /* ** get globals into registers. ** we have a user generated syntax type error */ yy_pv = yypv; yy_ps = yyps; yy_state = yystate; skip_init: yynerrs++; /* FALLTHRU */ case 1: case 2: /* incompletely recovered error */ /* try again... */ yyerrflag = 3; /* ** find state where "error" is a legal ** shift action */ while ( yy_ps >= yys ) { yy_n = yypact[ *yy_ps ] + YYERRCODE; if ( yy_n >= 0 && yy_n < YYLAST && yychk[yyact[yy_n]] == YYERRCODE) { /* ** simulate shift of "error" */ yy_state = yyact[ yy_n ]; goto yy_stack; } /* ** current state has no shift on ** "error", pop stack */ #if YYDEBUG # define _POP_ "Error recovery pops state %d, uncovers state %d\n" if ( yydebug ) printf( _POP_, *yy_ps, yy_ps[-1] ); # undef _POP_ #endif yy_ps--; yy_pv--; } /* ** there is no state on stack with "error" as ** a valid shift. give up. */ YYABORT; case 3: /* no shift yet; eat a token */ #if YYDEBUG /* ** if debugging, look up token in list of ** pairs. 0 and negative shouldn't occur, ** but since timing doesn't matter when ** debugging, it doesn't hurt to leave the ** tests here. */ if ( yydebug ) { register int yy_i; printf( "Error recovery discards " ); if ( yychar == 0 ) printf( "token end-of-file\n" ); else if ( yychar < 0 ) printf( "token -none-\n" ); else { for ( yy_i = 0; yytoks[yy_i].t_val >= 0; yy_i++ ) { if ( yytoks[yy_i].t_val == yychar ) { break; } } printf( "token %s\n", yytoks[yy_i].t_name ); } } #endif /* YYDEBUG */ if ( yychar == 0 ) /* reached EOF. quit */ YYABORT; yychar = -1; goto yy_newstate; } }/* end if ( yy_n == 0 ) */ /* ** reduction by production yy_n ** put stack tops, etc. so things right after switch */ #if YYDEBUG /* ** if debugging, print the string that is the user's ** specification of the reduction which is just about ** to be done. */ if ( yydebug ) printf( "Reduce by (%d) \"%s\"\n", yy_n, yyreds[ yy_n ] ); #endif yytmp = yy_n; /* value to switch over */ yypvt = yy_pv; /* $vars top of value stack */ /* ** Look in goto table for next state ** Sorry about using yy_state here as temporary ** register variable, but why not, if it works... ** If yyr2[ yy_n ] doesn't have the low order bit ** set, then there is no action to be done for ** this reduction. So, no saving & unsaving of ** registers done. The only difference between the ** code just after the if and the body of the if is ** the goto yy_stack in the body. This way the test ** can be made before the choice of what to do is needed. */ { /* length of production doubled with extra bit */ register int yy_len = yyr2[ yy_n ]; if ( !( yy_len & 01 ) ) { yy_len >>= 1; yyval = ( yy_pv -= yy_len )[1]; /* $$ = $1 */ yy_state = yypgo[ yy_n = yyr1[ yy_n ] ] + *( yy_ps -= yy_len ) + 1; if ( yy_state >= YYLAST || yychk[ yy_state = yyact[ yy_state ] ] != -yy_n ) { yy_state = yyact[ yypgo[ yy_n ] ]; } goto yy_stack; } yy_len >>= 1; yyval = ( yy_pv -= yy_len )[1]; /* $$ = $1 */ yy_state = yypgo[ yy_n = yyr1[ yy_n ] ] + *( yy_ps -= yy_len ) + 1; if ( yy_state >= YYLAST || yychk[ yy_state = yyact[ yy_state ] ] != -yy_n ) { yy_state = yyact[ yypgo[ yy_n ] ]; } } /* save until reenter driver code */ yystate = yy_state; yyps = yy_ps; yypv = yy_pv; } /* ** code supplied by user is placed in this switch */ switch( yytmp ) { $A } goto yystack; /* reset registers in driver code */ }
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. * All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ /* from OpenSolaris "once.h 6.9 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)once.h 1.4 (gritter) 11/26/05 */ #include "ldefs.c" /* once.c */ /* because of external definitions, this code should occur only once */ int ctable[2*NCH] = { 0, 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 }; int ZCH = NCH; FILE *fout = NULL, *errorf; int sect = DEFSECTION; int prev = '\n'; /* previous input character */ int pres = '\n'; /* present input character */ int peek = '\n'; /* next input character */ CHR *pushptr = pushc; CHR *slptr = slist; #ifndef CNAME #define CNAME "./ncform" #endif #ifndef RATNAME #define RATNAME "./nrform" #endif char *cname = CNAME; char *ratname = RATNAME; int ccount = 1; int casecount = 1; int aptr = 1; int nstates = NSTATES, maxpos = MAXPOS; int treesize = TREESIZE, ntrans = NTRANS; int yytop; int outsize = NOUTPUT; int sptr = 1; int optim = TRUE; int report = 2; int debug; /* 1 = on */ int charc; char *v_stmp = "n"; int no_input; int copy_line; int n_error = 0; int fatal = 1; int sargc; char **sargv; CHR buf[BUF_SIZ]; int ratfor; /* 1 = ratfor, 0 = C */ int yyline; /* line number of file */ int eof; int lgatflg; int divflg; int funcflag; int pflag; int chset; /* 1 = char set modified */ FILE *fin, *fother; int fptr; int *name; intptr_t *left; intptr_t *right; int *parent; Boolean *nullstr; int tptr; CHR pushc[TOKENSIZE]; CHR slist[STARTSIZE]; CHR **def, **subs, *dchar; /* XCU4: %x exclusive start */ int *exclusive; CHR **sname, *schar; CHR *ccl; CHR *ccptr; CHR *dp, *sp; int dptr; CHR *bptr; /* store input position */ CHR *tmpstat; int count; int **foll; int *nxtpos; int *positions; int *gotof; int *nexts; CHR *nchar; int **state; int *sfall; /* fallback state num */ Boolean *cpackflg; /* true if state has been character packed */ int *atable; int nptr; Boolean symbol[MAXNCG]; CHR cindex[MAXNCG]; int xstate; int stnum; CHR match[MAXNCG]; BYTE extra[NACTIONS]; CHR *pchar, *pcptr; int pchlen = TOKENSIZE; long rcount; int *verify, *advance, *stoff; int scon; CHR *psave; Boolean handleeuc = FALSE; Boolean widecio = FALSE; int isArray = 1; /* XCU4: for %array %pointer */
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany */ const char sccsid[] = "@(#)lex.sl 2.13 (gritter) 4/14/07"; const char pkg[] = "Heirloom Development Tools"; const char rel[] = "2.13 (gritter) 4/14/07"; /* SLIST */ /* allprint.c: * Sccsid @(#)allprint.c 1.4 (gritter) 11/27/05 getopt.c: * Sccsid @(#)getopt.c 1.9 (gritter) 4/2/07 header.c: * Sccsid @(#)header.c 1.12 (gritter) 9/23/06 ldefs.c: * Sccsid @(#)ldefs.c 1.7 (gritter) 4/14/07 libmain.c: * Sccsid @(#)libmain.c 1.4 (gritter) 11/26/05 lsearch.c: * Sccsid @(#)lsearch.c 1.4 (gritter) 11/26/05 main.c: * Sccsid @(#)main.c 1.9 (gritter) 11/26/05 once.h: * Sccsid @(#)once.h 1.4 (gritter) 11/26/05 parser.y: * Sccsid @(#)parser.y 1.8 (gritter) 11/26/05 reject.c: * Sccsid @(#)reject.c 1.4 (gritter) 11/27/05 search.h: * Sccsid @(#)search.h 1.4 (gritter) 11/26/05 sub1.c: * Sccsid @(#)sub1.c 1.5 (gritter) 11/26/05 sub2.c: * Sccsid @(#)sub2.c 1.7 (gritter) 01/12/07 sub3.c: * Sccsid @(#)sub3.c 1.4 (gritter) 11/26/05 wcio.c: * Sccsid @(#)wcio.c 1.1 (gritter) 6/25/05 yyless.c: * Sccsid @(#)yyless.c 1.6 (gritter) 11/27/05 yywrap.c: * Sccsid @(#)yywrap.c 1.3 (gritter) 6/18/05 nceucform: * Sccsid @(#)nceucform 1.6 (gritter) 11/18/05 ncform: * Sccsid @(#)ncform 1.4 (gritter) 11/18/05 nrform:# Sccsid @(#)nrform 1.4 (gritter) 10/20/06 */
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. * All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ /* from OpenSolaris "sub2.c 6.15 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)sub2.c 1.7 (gritter) 01/12/07 */ #include "ldefs.c" static void add(int **array, int n); static void follow(int v); static void first(int v); static void nextstate(int s, int c); static void packtrans(int st, CHR *tch, int *tst, int cnt, int tryit); static void acompute(int s); static void rprint(int *a, char *s, int n); static void shiftr(int *a, int n); static void upone(int *a, int n); static void bprint(char *a, char *s, int n); static int notin(int n); static int member(int d, CHR *t); #ifdef PP static void padd(int **array, int n); #endif void cfoll(int v) { int i, j, k; CHR *p; i = name[v]; if (!ISOPERATOR(i)) i = 1; switch (i) { case 1: case RSTR: case RCCL: case RNCCL: case RNULLS: for (j = 0; j < tptr; j++) tmpstat[j] = FALSE; count = 0; follow(v); #ifdef PP padd(foll, v); /* packing version */ #else add(foll, v); /* no packing version */ #endif if (i == RSTR) cfoll(left[v]); else if (i == RCCL || i == RNCCL) { for (j = 1; j < ncg; j++) symbol[j] = (i == RNCCL); p = (CHR *) left[v]; while (*p) symbol[*p++] = (i == RCCL); p = pcptr; for (j = 1; j < ncg; j++) if (symbol[j]) { for (k = 0; p + k < pcptr; k++) if (cindex[j] == *(p + k)) break; if (p + k >= pcptr) *pcptr++ = cindex[j]; } *pcptr++ = 0; if (pcptr > pchar + pchlen) error( "Too many packed character classes"); left[v] = (intptr_t)p; name[v] = RCCL; /* RNCCL eliminated */ #ifdef DEBUG if (debug && *p) { printf("ccl %d: %d", v, *p++); while (*p) printf(", %d", *p++); putchar('\n'); } #endif } break; case CARAT: cfoll(left[v]); break; /* XCU4: add RXSCON */ case RXSCON: case STAR: case PLUS: case QUEST: case RSCON: cfoll(left[v]); break; case BAR: case RCAT: case DIV: case RNEWE: cfoll(left[v]); cfoll(right[v]); break; #ifdef DEBUG case FINAL: case S1FINAL: case S2FINAL: break; default: warning("bad switch cfoll %d", v); #endif } } #ifdef DEBUG void pfoll(void) { int i, k, *p; int j; /* print sets of chars which may follow positions */ printf("pos\tchars\n"); for (i = 0; i < tptr; i++) if (p = foll[i]) { j = *p++; if (j >= 1) { printf("%d:\t%d", i, *p++); for (k = 2; k <= j; k++) printf(", %d", *p++); putchar('\n'); } } } #endif static void add(int **array, int n) { int i, *temp; CHR *ctemp; temp = nxtpos; ctemp = tmpstat; array[n] = nxtpos; /* note no packing is done in positions */ *temp++ = count; for (i = 0; i < tptr; i++) if (ctemp[i] == TRUE) *temp++ = i; nxtpos = temp; if (nxtpos >= positions+maxpos) error( "Too many positions %s", (maxpos == MAXPOS ? "\nTry using %p num" : "")); } static void follow(int v) { int p; if (v >= tptr-1) return; p = parent[v]; if (p == 0) return; switch (name[p]) { /* will not be CHAR RNULLS FINAL S1FINAL S2FINAL RCCL RNCCL */ case RSTR: if (tmpstat[p] == FALSE) { count++; tmpstat[p] = TRUE; } break; case STAR: case PLUS: first(v); follow(p); break; case BAR: case QUEST: case RNEWE: follow(p); break; case RCAT: case DIV: if (v == left[p]) { if (nullstr[right[p]]) follow(p); first(right[p]); } else follow(p); break; /* XCU4: add RXSCON */ case RXSCON: case RSCON: case CARAT: follow(p); break; #ifdef DEBUG default: warning("bad switch follow %d", p); #endif } } /* * Check if I have a RXSCON in my upper node */ static int check_me(int v) { int tmp = parent[v]; while (name[tmp] != RNEWE) { if (name[tmp] == RXSCON) return (1); tmp = parent[tmp]; } return (0); } /* calculate set of positions with v as root which can be active initially */ static void first(int v) { int i; CHR *p; i = name[v]; if (!ISOPERATOR(i)) i = 1; switch (i) { case 1: case RCCL: case RNCCL: case RNULLS: case FINAL: case S1FINAL: case S2FINAL: /* * XCU4: if we are working on an exclusive start state and * the parent of this position is not RXSCON or RSTR this * is not an active position. * * (There is a possibility that RSXCON appreas as the * (parent)* node. Check it by check_me().) */ if ((exclusive[stnum/2]) && ISOPERATOR(name[parent[v]]) && (name[parent[v]] != RXSCON) && (name[parent[v]] != RSTR) && (check_me(v) == 0)) { break; } if (tmpstat[v] == FALSE) { count++; tmpstat[v] = TRUE; } break; case BAR: case RNEWE: first(left[v]); first(right[v]); break; case CARAT: if (stnum % 2 == 1) first(left[v]); break; /* XCU4: add RXSCON */ case RXSCON: case RSCON: i = stnum/2 +1; p = (CHR *) right[v]; while (*p) if (*p++ == i) { first(left[v]); break; } break; case STAR: case QUEST: case PLUS: case RSTR: /* * XCU4: if we are working on an exclusive start state and * the parent of this position is not RXSCON or RSTR this * is not an active position. * * (There is a possibility that RSXCON appreas as the * (parent)* node. Check it by check_me().) */ if ((exclusive[stnum/2]) && ISOPERATOR(name[parent[v]]) && (name[parent[v]] != RXSCON) && (name[parent[v]] != RSTR) && (check_me(v) == 0)) { break; } first(left[v]); break; case RCAT: case DIV: first(left[v]); if (nullstr[left[v]]) first(right[v]); break; #ifdef DEBUG default: warning("bad switch first %d", v); #endif } } void cgoto(void) { int i, j; static int s; int npos, curpos, n; int tryit; CHR tch[MAXNCG]; int tst[MAXNCG]; CHR *q; /* generate initial state, for each start condition */ if (ratfor) { fprintf(fout, "blockdata\n"); fprintf(fout, "common /Lvstop/ vstop\n"); fprintf(fout, "define Svstop %d\n", nstates+1); fprintf(fout, "integer vstop(Svstop)\n"); } else fprintf(fout, "int yyvstop[] = {\n0,\n"); while (stnum < 2 || stnum/2 < sptr) { for (i = 0; i < tptr; i++) tmpstat[i] = 0; count = 0; if (tptr > 0) first(tptr-1); add(state, stnum); #ifdef DEBUG if (debug) { if (stnum > 1) printf("%ls:\n", sname[stnum/2]); pstate(stnum); } #endif stnum++; } stnum--; /* even stnum = might not be at line begin */ /* odd stnum = must be at line begin */ /* even states can occur anywhere, odd states only at line begin */ for (s = 0; s <= stnum; s++) { tryit = FALSE; cpackflg[s] = FALSE; sfall[s] = -1; acompute(s); for (i = 0; i < ncg; i++) symbol[i] = 0; npos = *state[s]; for (i = 1; i <= npos; i++) { curpos = *(state[s]+i); if (!ISOPERATOR(name[curpos])) symbol[name[curpos]] = TRUE; else { switch (name[curpos]) { case RCCL: tryit = TRUE; q = (CHR *)left[curpos]; while (*q) { for (j = 1; j < ncg; j++) if (cindex[j] == *q) symbol[j] = TRUE; q++; } break; case RSTR: symbol[right[curpos]] = TRUE; break; #ifdef DEBUG case RNULLS: case FINAL: case S1FINAL: case S2FINAL: break; default: warning( "bad switch cgoto %d state %d", curpos, s); break; #endif } } } #ifdef DEBUG if (debug) { printf("State %d transitions on char-group {", s); charc = 0; for (i = 1; i < ncg; i++) { if (symbol[i]) { printf("%d,", i); } if (i == ncg-1) printf("}\n"); if (charc > LINESIZE/4) { charc = 0; printf("\n\t"); } } } #endif /* for each char, calculate next state */ n = 0; for (i = 1; i < ncg; i++) { if (symbol[i]) { /* executed for each state, transition pair */ nextstate(s, i); xstate = notin(stnum); if (xstate == -2) warning("bad state %d %o", s, i); else if (xstate == -1) { if (stnum+1 >= nstates) { stnum++; error("Too many states %s", (nstates == NSTATES ? "\nTry using %n num":"")); } add(state, ++stnum); #ifdef DEBUG if (debug) pstate(stnum); #endif tch[n] = i; tst[n++] = stnum; } else { /* xstate >= 0 ==> state exists */ tch[n] = i; tst[n++] = xstate; } } } tch[n] = 0; tst[n] = -1; /* pack transitions into permanent array */ if (n > 0) packtrans(s, tch, tst, n, tryit); else gotof[s] = -1; } ratfor ? fprintf(fout, "end\n") : fprintf(fout, "0};\n"); } /* * Beware -- 70% of total CPU time is spent in this subroutine - * if you don't believe me - try it yourself ! */ static void nextstate(int s, int c) { int j, *newpos; CHR *temp, *tz; int *pos, i, *f, num, curpos, number; /* state to goto from state s on char c */ num = *state[s]; temp = tmpstat; pos = state[s] + 1; for (i = 0; i < num; i++) { curpos = *pos++; j = name[curpos]; if ((!ISOPERATOR(j)) && j == c || j == RSTR && c == right[curpos] || j == RCCL && member(c, (CHR *) left[curpos])) { f = foll[curpos]; number = *f; newpos = f+1; for (j = 0; j < number; j++) temp[*newpos++] = 2; } } j = 0; tz = temp + tptr; while (temp < tz) { if (*temp == 2) { j++; *temp++ = 1; } else *temp++ = 0; } count = j; } static int notin(int n) { /* see if tmpstat occurs previously */ int *j, k; CHR *temp; int i; if (count == 0) return (-2); temp = tmpstat; for (i = n; i >= 0; i--) { /* for each state */ j = state[i]; if (count == *j++) { for (k = 0; k < count; k++) if (!temp[*j++]) break; if (k >= count) return (i); } } return (-1); } static void packtrans(int st, CHR *tch, int *tst, int cnt, int tryit) { /* * pack transitions into nchar, nexts * nchar is terminated by '\0', nexts uses cnt, followed by elements * gotof[st] = index into nchr, nexts for state st * sfall[st] = t implies t is fall back state for st * == -1 implies no fall back */ int cmin, cval, tcnt, diff, p, *ast; int i, j, k; CHR *ach; int go[MAXNCG], temp[MAXNCG], index, c; int swork[MAXNCG]; CHR cwork[MAXNCG]; int upper; rcount += (long)cnt; cmin = -1; cval = ncg; ast = tst; ach = tch; /* try to pack transitions using ccl's */ if (!optim) goto nopack; /* skip all compaction */ if (tryit) { /* ccl's used */ for (i = 1; i < ncg; i++) { go[i] = temp[i] = -1; symbol[i] = 1; } for (i = 0; i < cnt; i++) { index = (unsigned char) tch[i]; if ((index >= 0) && (index < NCH)) { go[index] = tst[i]; symbol[index] = 0; } else { fprintf(stderr, "lex`sub2`packtran: tch[%d] out of bounds (%ld)\n", i, (long)tch[i]); } } for (i = 0; i < cnt; i++) { c = match[tch[i]]; if (go[c] != tst[i] || c == tch[i]) temp[tch[i]] = tst[i]; } /* fill in error entries */ for (i = 1; i < ncg; i++) if (symbol[i]) temp[i] = -2; /* error trans */ /* count them */ k = 0; for (i = 1; i < ncg; i++) if (temp[i] != -1) k++; if (k < cnt) { /* compress by char */ #ifdef DEBUG if (debug) printf( "use compression %d, %d vs %d\n", st, k, cnt); #endif k = 0; for (i = 1; i < ncg; i++) if (temp[i] != -1) { cwork[k] = i; swork[k++] = (temp[i] == -2 ? -1 : temp[i]); } cwork[k] = 0; #ifdef PC ach = cwork; ast = swork; cnt = k; cpackflg[st] = TRUE; #endif } } /* * get most similar state * reject state with more transitions, * state already represented by a third state, * and state which is compressed by char if ours is not to be */ for (i = 0; i < st; i++) { if (sfall[i] != -1) continue; if (cpackflg[st] == 1) if (!(cpackflg[i] == 1)) continue; p = gotof[i]; if (p == -1) /* no transitions */ continue; tcnt = nexts[p]; if (tcnt > cnt) continue; diff = 0; k = 0; j = 0; upper = p + tcnt; while (ach[j] && p < upper) { while (ach[j] < nchar[p] && ach[j]) { diff++; j++; } if (ach[j] == 0) break; if (ach[j] > nchar[p]) { diff = ncg; break; } /* ach[j] == nchar[p] */ if (ast[j] != nexts[++p] || ast[j] == -1 || (cpackflg[st] && ach[j] != match[ach[j]])) diff++; j++; } while (ach[j]) { diff++; j++; } if (p < upper) diff = ncg; if (diff < cval && diff < tcnt) { cval = diff; cmin = i; if (cval == 0) break; } } /* cmin = state "most like" state st */ #ifdef DEBUG if (debug) printf("select st %d for st %d diff %d\n", cmin, st, cval); #endif #ifdef PS if (cmin != -1) { /* if we can use st cmin */ gotof[st] = nptr; k = 0; sfall[st] = cmin; p = gotof[cmin] + 1; j = 0; while (ach[j]) { /* if cmin has a transition on c, then so will st */ /* st may be "larger" than cmin, however */ while (ach[j] < nchar[p-1] && ach[j]) { k++; nchar[nptr] = ach[j]; nexts[++nptr] = ast[j]; j++; } if (nchar[p-1] == 0) break; if (ach[j] > nchar[p-1]) { warning("bad transition %d %d", st, cmin); goto nopack; } /* ach[j] == nchar[p-1] */ if (ast[j] != nexts[p] || ast[j] == -1 || (cpackflg[st] && ach[j] != match[ach[j]])) { k++; nchar[nptr] = ach[j]; nexts[++nptr] = ast[j]; } p++; j++; } while (ach[j]) { nchar[nptr] = ach[j]; nexts[++nptr] = ast[j++]; k++; } nexts[gotof[st]] = cnt = k; nchar[nptr++] = 0; } else { #endif nopack: /* stick it in */ gotof[st] = nptr; nexts[nptr] = cnt; for (i = 0; i < cnt; i++) { nchar[nptr] = ach[i]; nexts[++nptr] = ast[i]; } nchar[nptr++] = 0; #ifdef PS } #endif if (cnt < 1) { gotof[st] = -1; nptr--; } else if (nptr > ntrans) error( "Too many transitions %s", (ntrans == NTRANS ? "\nTry using %a num" : "")); } #ifdef DEBUG void pstate(int s) { int *p, i, j; printf("State %d:\n", s); p = state[s]; i = *p++; if (i == 0) return; printf("%4d", *p++); for (j = 1; j < i; j++) { printf(", %4d", *p++); if (j%30 == 0) putchar('\n'); } putchar('\n'); } #endif static int member(int d, CHR *t) { int c; CHR *s; c = d; s = t; c = cindex[c]; while (*s) if (*s++ == c) return (1); return (0); } #ifdef DEBUG void stprt(int i) { int p, t; printf("State %d:", i); /* print actions, if any */ t = atable[i]; if (t != -1) printf(" final"); putchar('\n'); if (cpackflg[i] == TRUE) printf("backup char in use\n"); if (sfall[i] != -1) printf("fall back state %d\n", sfall[i]); p = gotof[i]; if (p == -1) return; printf("(%d transitions)\n", nexts[p]); while (nchar[p]) { charc = 0; if (nexts[p+1] >= 0) printf("%d\t", nexts[p+1]); else printf("err\t"); allprint(nchar[p++]); while (nexts[p] == nexts[p+1] && nchar[p]) { if (charc > LINESIZE) { charc = 0; printf("\n\t"); } allprint(nchar[p++]); } putchar('\n'); } putchar('\n'); } #endif /* compute action list = set of poss. actions */ static void acompute(int s) { int *p, i, j; int q, r; int cnt, m; int temp[MAXPOSSTATE], k, neg[MAXPOSSTATE], n; k = 0; n = 0; p = state[s]; cnt = *p++; if (cnt > MAXPOSSTATE) error("Too many positions for one state - acompute"); for (i = 0; i < cnt; i++) { q = *p; if (name[q] == FINAL) temp[k++] = left[q]; else if (name[q] == S1FINAL) { temp[k++] = left[q]; if ((r = left[q]) >= NACTIONS) error( "INTERNAL ERROR:left[%d]==%d>=NACTIONS", q, r); extra[r] = 1; } else if (name[q] == S2FINAL) neg[n++] = left[q]; p++; } atable[s] = -1; if (k < 1 && n < 1) return; #ifdef DEBUG if (debug) printf("final %d actions:", s); #endif /* sort action list */ for (i = 0; i < k; i++) for (j = i+1; j < k; j++) if (temp[j] < temp[i]) { m = temp[j]; temp[j] = temp[i]; temp[i] = m; } /* remove dups */ for (i = 0; i < k-1; i++) if (temp[i] == temp[i+1]) temp[i] = 0; /* copy to permanent quarters */ atable[s] = aptr; #ifdef DEBUG if (!ratfor) fprintf(fout, "/* actions for state %d */", s); #endif putc('\n', fout); for (i = 0; i < k; i++) if (temp[i] != 0) { ratfor ? fprintf(fout, "data vstop(%d)/%d/\n", aptr, temp[i]) : fprintf(fout, "%d,\n", temp[i]); #ifdef DEBUG if (debug) printf("%d ", temp[i]); #endif aptr++; } for (i = 0; i < n; i++) { /* copy fall back actions - all neg */ ratfor ? fprintf(fout, "data vstop(%d)/%d/\n", aptr, neg[i]) : fprintf(fout, "%d,\n", neg[i]); aptr++; #ifdef DEBUG if (debug) printf("%d ", neg[i]); #endif } #ifdef DEBUG if (debug) putchar('\n'); #endif ratfor ? fprintf(fout, "data vstop (%d)/0/\n", aptr) : fprintf(fout, "0, \n"); aptr++; } #ifdef DEBUG void pccl(void) { /* print character class sets */ int i, j; printf("char class intersection\n"); for (i = 0; i < ccount; i++) { charc = 0; printf("class %d:\n\t", i); for (j = 1; j < ncg; j++) if (cindex[j] == i) { allprint(j); if (charc > LINESIZE) { printf("\n\t"); charc = 0; } } putchar('\n'); } charc = 0; printf("match:\n"); for (i = 0; i < ncg; i++) { allprint(match[i]); if (charc > LINESIZE) { putchar('\n'); charc = 0; } } putchar('\n'); } #endif void mkmatch(void) { int i; CHR tab[MAXNCG]; for (i = 0; i < ccount; i++) tab[i] = 0; for (i = 1; i < ncg; i++) if (tab[cindex[i]] == 0) tab[cindex[i]] = i; /* tab[i] = principal char for new ccl i */ for (i = 1; i < ncg; i++) match[i] = tab[cindex[i]]; } void layout(void) { /* format and output final program's tables */ int i, j, k; int top, bot, startup, omin; startup = 0; for (i = 0; i < outsize; i++) verify[i] = advance[i] = 0; omin = 0; yytop = 0; for (i = 0; i <= stnum; i++) { /* for each state */ j = gotof[i]; if (j == -1) { stoff[i] = 0; continue; } bot = j; while (nchar[j]) j++; top = j - 1; #if DEBUG if (debug) { printf("State %d: (layout)\n", i); for (j = bot; j <= top; j++) { printf(" %o", nchar[j]); if (j % 10 == 0) putchar('\n'); } putchar('\n'); } #endif while (verify[omin+ZCH]) omin++; startup = omin; #if DEBUG if (debug) printf( "bot,top %d, %d startup begins %d\n", bot, top, startup); #endif if (chset) { do { startup += 1; if (startup > outsize - ZCH) error("output table overflow"); for (j = bot; j <= top; j++) { k = startup+ctable[nchar[j]]; if (verify[k]) break; } } while (j <= top); #if DEBUG if (debug) printf(" startup will be %d\n", startup); #endif /* have found place */ for (j = bot; j <= top; j++) { k = startup + ctable[nchar[j]]; if (ctable[nchar[j]] <= 0) printf( "j %d nchar %ld ctable.nch %d\n", j, (long)nchar[j], ctable[nchar[k]]); verify[k] = i + 1; /* state number + 1 */ advance[k] = nexts[j+1]+1; if (yytop < k) yytop = k; } } else { do { startup += 1; if (startup > outsize - ZCH) error("output table overflow"); for (j = bot; j <= top; j++) { k = startup + nchar[j]; if (verify[k]) break; } } while (j <= top); /* have found place */ #if DEBUG if (debug) printf(" startup going to be %d\n", startup); #endif for (j = bot; j <= top; j++) { k = startup + nchar[j]; verify[k] = i+1; /* state number + 1 */ advance[k] = nexts[j+1] + 1; if (yytop < k) yytop = k; } } stoff[i] = startup; } /* stoff[i] = offset into verify, advance for trans for state i */ /* put out yywork */ if (ratfor) { fprintf(fout, "define YYTOPVAL %d\n", yytop); rprint(verify, "verif", yytop+1); rprint(advance, "advan", yytop+1); shiftr(stoff, stnum); rprint(stoff, "stoff", stnum+1); shiftr(sfall, stnum); upone(sfall, stnum+1); rprint(sfall, "fall", stnum+1); bprint(extra, "extra", casecount+1); bprint((char *)match, "match", ncg); shiftr(atable, stnum); rprint(atable, "atable", stnum+1); } fprintf(fout, "# define YYTYPE %s\n", stnum+1 >= NCH ? "int" : "unsigned char"); fprintf(fout, "struct yywork { YYTYPE verify, advance; } yycrank[] = {\n"); for (i = 0; i <= yytop; i += 4) { for (j = 0; j < 4; j++) { k = i+j; if (verify[k]) fprintf(fout, "{ %d,%d },\t", verify[k], advance[k]); else fprintf(fout, "{ 0,0 },\t"); } putc('\n', fout); } fprintf(fout, "{0,0}};\n"); /* put out yysvec */ fprintf(fout, "struct yysvf yysvec[] = {\n"); fprintf(fout, "{ 0,\t0,\t0 },\n"); for (i = 0; i <= stnum; i++) { /* for each state */ if (cpackflg[i]) stoff[i] = -stoff[i]; fprintf(fout, "{ yycrank+%d,\t", stoff[i]); if (sfall[i] != -1) fprintf(fout, "yysvec+%d,\t", sfall[i]+1); /* state + 1 */ else fprintf(fout, "0,\t\t"); if (atable[i] != -1) fprintf(fout, "yyvstop+%d", atable[i]); else fprintf(fout, "0"); #ifdef DEBUG fprintf(fout, " },\t\t/* state %d */", i); #endif fprintf(fout, " },\t\t/* state %d */", i); } fprintf(fout, "{ 0,\t0,\t0}};\n"); /* put out yymatch */ fprintf(fout, "struct yywork *yytop = yycrank+%d;\n", yytop); fprintf(fout, "struct yysvf *yybgin = yysvec+1;\n"); if (optim) { if (handleeuc) { fprintf(fout, "int yymatch[] = {\n"); } else { fprintf(fout, "char yymatch[] = {\n"); } if (chset == 0) { /* no chset, put out in normal order */ for (i = 0; i < ncg; i += 8) { for (j = 0; j < 8; j++) { int fbch; fbch = match[i+j]; fprintf(fout, "%3d, ", fbch); } putc('\n', fout); } } else { int *fbarr; fbarr = myalloc(2*MAXNCG, sizeof (*fbarr)); if (fbarr == 0) error("No space for char table reverse", 0); for (i = 0; i < MAXNCG; i++) fbarr[i] = 0; for (i = 0; i < ncg; i++) fbarr[ctable[i]] = ctable[match[i]]; for (i = 0; i < ncg; i += 8) { for (j = 0; j < 8; j++) fprintf(fout, "0%-3o,", fbarr[i+j]); putc('\n', fout); } free(fbarr); } fprintf(fout, "0};\n"); } /* put out yyextra */ fprintf(fout, "char yyextra[] = {\n"); for (i = 0; i < casecount; i += 8) { for (j = 0; j < 8; j++) fprintf(fout, "%d,", i+j < NACTIONS ? extra[i+j] : 0); putc('\n', fout); } fprintf(fout, "0};\n"); if (handleeuc) { /* Put out yycgidtbl */ fprintf(fout, "#define YYNCGIDTBL %d\n", ncgidtbl); fprintf(fout, "\tunsigned long yycgidtbl[]={"); /* * Use "unsigned long" instead of "lchar" to minimize * the name-space polution for the application program. */ for (i = 0; i < ncgidtbl; ++i) { if (i%8 == 0) fprintf(fout, "\n\t\t"); fprintf(fout, "0x%08lx, ", yycgidtbl[i]); } fprintf(fout, "\n\t0};\n"); } } static void rprint(int *a, char *s, int n) { int i; fprintf(fout, "block data\n"); fprintf(fout, "common /L%s/ %s\n", s, s); fprintf(fout, "define S%s %d\n", s, n); fprintf(fout, "integer %s (S%s)\n", s, s); for (i = 1; i <= n; i++) { if (i%8 == 1) fprintf(fout, "data "); fprintf(fout, "%s (%d)/%d/", s, i, a[i]); fprintf(fout, (i%8 && i < n) ? ", " : "\n"); } fprintf(fout, "end\n"); } static void shiftr(int *a, int n) { int i; for (i = n; i >= 0; i--) a[i+1] = a[i]; } static void upone(int *a, int n) { int i; for (i = 0; i <= n; i++) a[i]++; } static void bprint(char *a, char *s, int n) { int i, j, k; fprintf(fout, "block data\n"); fprintf(fout, "common /L%s/ %s\n", s, s); fprintf(fout, "define S%s %d\n", s, n); fprintf(fout, "integer %s (S%s)\n", s, s); for (i = 1; i < n; i += 8) { fprintf(fout, "data %s (%d)/%d/", s, i, a[i]); for (j = 1; j < 8; j++) { k = i+j; if (k < n) fprintf(fout, ", %s (%d)/%d/", s, k, a[k]); } putc('\n', fout); } fprintf(fout, "end\n"); } #ifdef PP static void padd(int **array, int n) { int i, *j, k; array[n] = nxtpos; if (count == 0) { *nxtpos++ = 0; return; } for (i = tptr-1; i >= 0; i--) { j = array[i]; if (j && *j++ == count) { for (k = 0; k < count; k++) if (!tmpstat[*j++]) break; if (k >= count) { array[n] = array[i]; return; } } } add(array, n); } #endif
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. * All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ /* from OpenSolaris "header.c 6.22 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)header.c 1.12 (gritter) 9/23/06 */ #include "ldefs.c" static void rhd1(void); static void chd1(void); static void chd2(void); static void ctail(void); static void rtail(void); void phead1(void) { ratfor ? rhd1() : chd1(); } static void chd1(void) { if (*v_stmp == 'y') { extern const char rel[]; fprintf(fout, "\ #if __GNUC__ >= 3 && __GNUC_MINOR__ >= 4 || __GNUC__ >= 4\n\ #define YYUSED __attribute__ ((used))\n\ #elif defined __GNUC__\n\ #define YYUSED __attribute__ ((unused))\n\ #else\n\ #define YYUSED\n\ #endif\n\ static const char yylexid[] USED = \"lex: %s\"\n", rel); } if (handleeuc) { fprintf(fout, "#ifndef EUC\n"); fprintf(fout, "#define EUC\n"); fprintf(fout, "#endif\n"); fprintf(fout, "#include <stdio.h>\n"); fprintf(fout, "#include <stdlib.h>\n"); fprintf(fout, "#ifdef __sun\n"); fprintf(fout, "#include <widec.h>\n"); fprintf(fout, "#else /* !__sun */\n"); fprintf(fout, "#include <wchar.h>\n"); fprintf(fout, "#endif /* !__sun */\n"); if (widecio) { /* -w option */ fprintf(fout, "#define YYTEXT yytext\n"); fprintf(fout, "#define YYLENG yyleng\n"); fprintf(fout, "#ifndef __cplusplus\n"); fprintf(fout, "#define YYINPUT input\n"); fprintf(fout, "#define YYOUTPUT output\n"); fprintf(fout, "#else\n"); fprintf(fout, "#define YYINPUT lex_input\n"); fprintf(fout, "#define YYOUTPUT lex_output\n"); fprintf(fout, "#endif\n"); fprintf(fout, "#define YYUNPUT unput\n"); } else { /* -e option */ fprintf(fout, "#include <limits.h>\n"); fprintf(fout, "#ifdef __sun\n"); fprintf(fout, "#include <sys/euc.h>\n"); fprintf(fout, "#endif /* __sun */\n"); fprintf(fout, "#define YYLEX_E 1\n"); fprintf(fout, "#define YYTEXT yywtext\n"); fprintf(fout, "#define YYLENG yywleng\n"); fprintf(fout, "#define YYINPUT yywinput\n"); fprintf(fout, "#define YYOUTPUT yywoutput\n"); fprintf(fout, "#define YYUNPUT yywunput\n"); } } else { /* ASCII compatibility mode. */ fprintf(fout, "#include <stdio.h>\n"); fprintf(fout, "#include <stdlib.h>\n"); } if (ZCH > NCH) fprintf(fout, "# define U(x) ((x)&0377)\n"); else fprintf(fout, "# define U(x) x\n"); fprintf(fout, "# define NLSTATE yyprevious=YYNEWLINE\n"); fprintf(fout, "# define BEGIN yybgin = yysvec + 1 +\n"); fprintf(fout, "# define INITIAL 0\n"); fprintf(fout, "# define YYLERR yysvec\n"); fprintf(fout, "# define YYSTATE (yyestate-yysvec-1)\n"); if (optim) fprintf(fout, "# define YYOPTIM 1\n"); #ifdef DEBUG fprintf(fout, "# define LEXDEBUG 1\n"); #endif fprintf(fout, "# ifndef YYLMAX \n"); fprintf(fout, "# define YYLMAX BUFSIZ\n"); fprintf(fout, "# endif \n"); fprintf(fout, "#ifndef __cplusplus\n"); if (widecio) fprintf(fout, "# define output(c) (void)putwc(c,yyout)\n"); else fprintf(fout, "# define output(c) (void)putc(c,yyout)\n"); fprintf(fout, "#else\n"); if (widecio) fprintf(fout, "# define lex_output(c) (void)putwc(c,yyout)\n"); else fprintf(fout, "# define lex_output(c) (void)putc(c,yyout)\n"); fprintf(fout, "#endif\n"); fprintf(fout, "\n#if defined(__cplusplus) || defined(__STDC__)\n"); fprintf(fout, "\n#if defined(__cplusplus) && defined(__EXTERN_C__)\n"); fprintf(fout, "extern \"C\" {\n"); fprintf(fout, "#endif\n"); fprintf(fout, "\tint yyback(int *, int);\n"); /* ? */ fprintf(fout, "\tint yyinput(void);\n"); /* ? */ fprintf(fout, "\tint yylook(void);\n"); /* ? */ fprintf(fout, "\tvoid yyoutput(int);\n"); /* ? */ fprintf(fout, "\tint yyracc(int);\n"); /* ? */ fprintf(fout, "\tint yyreject(void);\n"); /* ? */ fprintf(fout, "\tvoid yyunput(int);\n"); /* ? */ fprintf(fout, "\tint yylex(void);\n"); fprintf(fout, "#ifdef YYLEX_E\n"); fprintf(fout, "\tvoid yywoutput(wchar_t);\n"); fprintf(fout, "\twchar_t yywinput(void);\n"); fprintf(fout, "\tvoid yywunput(wchar_t);\n"); fprintf(fout, "#endif\n"); /* XCU4: type of yyless is int */ fprintf(fout, "#ifndef yyless\n"); fprintf(fout, "\tint yyless(int);\n"); fprintf(fout, "#endif\n"); fprintf(fout, "#ifndef yywrap\n"); fprintf(fout, "\tint yywrap(void);\n"); fprintf(fout, "#endif\n"); fprintf(fout, "#ifdef LEXDEBUG\n"); fprintf(fout, "\tvoid allprint(char);\n"); fprintf(fout, "\tvoid sprint(char *);\n"); fprintf(fout, "#endif\n"); fprintf(fout, "#if defined(__cplusplus) && defined(__EXTERN_C__)\n"); fprintf(fout, "}\n"); fprintf(fout, "#endif\n\n"); fprintf(fout, "#ifdef __cplusplus\n"); fprintf(fout, "extern \"C\" {\n"); fprintf(fout, "#endif\n"); fprintf(fout, "\tvoid exit(int);\n"); fprintf(fout, "#ifdef __cplusplus\n"); fprintf(fout, "}\n"); fprintf(fout, "#endif\n\n"); fprintf(fout, "#endif\n"); fprintf(fout, "# define unput(c)" " {yytchar= (c);if(yytchar=='\\n')yylineno--;*yysptr++=yytchar;}\n"); fprintf(fout, "# define yymore() (yymorfg=1)\n"); if (widecio) { fprintf(fout, "#ifndef __cplusplus\n"); fprintf(fout, "%s%d%s\n", "# define input() (((yytchar=yysptr>yysbuf?U(*--yysptr):yygetwchar())==", ctable['\n'], "?(yylineno++,yytchar):yytchar)==EOF?0:yytchar)"); fprintf(fout, "#else\n"); fprintf(fout, "%s%d%s\n", "# define lex_input() (((yytchar=yysptr>yysbuf?U(*--yysptr):yygetwchar())==", ctable['\n'], "?(yylineno++,yytchar):yytchar)==EOF?0:yytchar)"); fprintf(fout, "#endif\n"); fprintf(fout, "# define ECHO (void)fprintf(yyout, \"%%ls\",yytext)\n"); fprintf(fout, "# define REJECT { nstr = yyreject_w(); goto yyfussy;}\n"); fprintf(fout, "#define yyless yyless_w\n"); fprintf(fout, "int yyreject_w(void);\n"); fprintf(fout, "int yyleng;\n"); /* * XCU4: * If %array, yytext[] contains the token. * If %pointer, yytext is a pointer to yy_tbuf[]. */ if (isArray) { fprintf(fout, "#define YYISARRAY\n"); fprintf(fout, "wchar_t yytext[YYLMAX];\n"); } else { fprintf(fout, "wchar_t yy_tbuf[YYLMAX];\n"); fprintf(fout, "wchar_t * yytext = yy_tbuf;\n"); fprintf(fout, "int yytextsz = YYLMAX;\n"); fprintf(fout, "#ifndef YYTEXTSZINC\n"); fprintf(fout, "#define YYTEXTSZINC 100\n"); fprintf(fout, "#endif\n"); } } else { fprintf(fout, "#ifndef __cplusplus\n"); fprintf(fout, "%s%d%s\n", "# define input() (((yytchar=yysptr>yysbuf?U(*--yysptr):getc(yyin))==", ctable['\n'], "?(yylineno++,yytchar):yytchar)==EOF?0:yytchar)"); fprintf(fout, "#else\n"); fprintf(fout, "%s%d%s\n", "# define lex_input() (((yytchar=yysptr>yysbuf?U(*--yysptr):getc(yyin))==", ctable['\n'], "?(yylineno++,yytchar):yytchar)==EOF?0:yytchar)"); fprintf(fout, "#endif\n"); fprintf(fout, "#define ECHO fprintf(yyout, \"%%s\",yytext)\n"); if (handleeuc) { fprintf(fout, "# define REJECT { nstr = yyreject_e(); goto yyfussy;}\n"); fprintf(fout, "int yyreject_e(void);\n"); fprintf(fout, "int yyleng;\n"); fprintf(fout, "size_t yywleng;\n"); /* * XCU4: * If %array, yytext[] contains the token. * If %pointer, yytext is a pointer to yy_tbuf[]. */ if (isArray) { fprintf(fout, "#define YYISARRAY\n"); fprintf(fout, "unsigned char yytext[YYLMAX*MB_LEN_MAX];\n"); fprintf(fout, "wchar_t yywtext[YYLMAX];\n"); } else { fprintf(fout, "wchar_t yy_twbuf[YYLMAX];\n"); fprintf(fout, "wchar_t yy_tbuf[YYLMAX*MB_LEN_MAX];\n"); fprintf(fout, "unsigned char * yytext =" "(unsigned char *)yy_tbuf;\n"); fprintf(fout, "wchar_t * yywtext = yy_twbuf;\n"); fprintf(fout, "int yytextsz = YYLMAX;\n"); fprintf(fout, "#ifndef YYTEXTSZINC\n"); fprintf(fout, "#define YYTEXTSZINC 100\n"); fprintf(fout, "#endif\n"); } } else { fprintf(fout, "# define REJECT { nstr = yyreject(); goto yyfussy;}\n"); fprintf(fout, "int yyleng;\n"); /* * XCU4: * If %array, yytext[] contains the token. * If %pointer, yytext is a pointer to yy_tbuf[]. */ if (isArray) { fprintf(fout, "#define YYISARRAY\n"); fprintf(fout, "char yytext[YYLMAX];\n"); } else { fprintf(fout, "char yy_tbuf[YYLMAX];\n"); fprintf(fout, "char * yytext = yy_tbuf;\n"); fprintf(fout, "int yytextsz = YYLMAX;\n"); fprintf(fout, "#ifndef YYTEXTSZINC\n"); fprintf(fout, "#define YYTEXTSZINC 100\n"); fprintf(fout, "#endif\n"); } } } fprintf(fout, "int yymorfg;\n"); if (handleeuc) fprintf(fout, "extern wchar_t *yysptr, yysbuf[];\n"); else fprintf(fout, "extern char *yysptr, yysbuf[];\n"); fprintf(fout, "int yytchar;\n"); fprintf(fout, "FILE *yyin = (FILE *)-1, *yyout = (FILE *)-1;\n"); fprintf(fout, "#if defined (__GNUC__)\n"); fprintf(fout, "static void _yyioinit(void) __attribute__ ((constructor));\n"); fprintf(fout, "#elif defined (__SUNPRO_C)\n"); fprintf(fout, "#pragma init (_yyioinit)\n"); fprintf(fout, "#elif defined (__HP_aCC) || defined (__hpux)\n"); fprintf(fout, "#pragma INIT \"_yyioinit\"\n"); fprintf(fout, "#endif\n"); fprintf(fout, "static void _yyioinit(void) {\n"); fprintf(fout, "yyin = stdin; yyout = stdout; }\n"); fprintf(fout, "extern int yylineno;\n"); fprintf(fout, "struct yysvf { \n"); fprintf(fout, "\tstruct yywork *yystoff;\n"); fprintf(fout, "\tstruct yysvf *yyother;\n"); fprintf(fout, "\tint *yystops;};\n"); fprintf(fout, "struct yysvf *yyestate;\n"); fprintf(fout, "extern struct yysvf yysvec[], *yybgin;\n"); } static void rhd1(void) { fprintf(fout, "integer function yylex(dummy)\n"); fprintf(fout, "define YYLMAX 200\n"); fprintf(fout, "define ECHO call yyecho(yytext,yyleng)\n"); fprintf(fout, "define REJECT nstr = yyrjct(yytext,yyleng);goto 30998\n"); fprintf(fout, "integer nstr,yylook,yywrap\n"); fprintf(fout, "integer yyleng, yytext(YYLMAX)\n"); fprintf(fout, "common /yyxel/ yyleng, yytext\n"); fprintf(fout, "common /yyldat/ yyfnd, yymorf, yyprev, yybgin, yylsp, yylsta\n"); fprintf(fout, "integer yyfnd, yymorf, yyprev, yybgin, yylsp, yylsta(YYLMAX)\n"); fprintf(fout, "for(;;){\n"); fprintf(fout, "\t30999 nstr = yylook(dummy)\n"); fprintf(fout, "\tgoto 30998\n"); fprintf(fout, "\t30000 k = yywrap(dummy)\n"); fprintf(fout, "\tif(k .ne. 0){\n"); fprintf(fout, "\tyylex=0; return; }\n"); fprintf(fout, "\t\telse goto 30998\n"); } void phead2(void) { if (!ratfor) chd2(); } static void chd2(void) { fprintf(fout, "if (yyin == (FILE *)-1) yyin = stdin;\n"); fprintf(fout, "if (yyout == (FILE *)-1) yyout = stdout;\n"); fprintf(fout, "#if defined (__cplusplus) || defined (__GNUC__)\n"); fprintf(fout, "/* to avoid CC and lint complaining yyfussy not being used ...*/\n"); fprintf(fout, "{static int __lex_hack = 0;\n"); fprintf(fout, "if (__lex_hack) { yyprevious = 0; goto yyfussy; } }\n"); fprintf(fout, "#endif\n"); fprintf(fout, "while((nstr = yylook()) >= 0)\n"); fprintf(fout, "yyfussy: switch(nstr){\n"); fprintf(fout, "case 0:\n"); fprintf(fout, "if(yywrap()) return(0); break;\n"); } void ptail(void) { if (!pflag) ratfor ? rtail() : ctail(); pflag = 1; } static void ctail(void) { fprintf(fout, "case -1:\nbreak;\n"); /* for reject */ fprintf(fout, "default:\n"); fprintf(fout, "(void)fprintf(yyout,\"bad switch yylook %%d\",nstr);\n"); fprintf(fout, "} return(0); }\n"); fprintf(fout, "/* end of yylex */\n"); } static void rtail(void) { int i; fprintf(fout, "\n30998 if(nstr .lt. 0 .or. nstr .gt. %d)goto 30999\n", casecount); fprintf(fout, "nstr = nstr + 1\n"); fprintf(fout, "goto(\n"); for (i = 0; i < casecount; i++) fprintf(fout, "%d,\n", 30000+i); fprintf(fout, "30999),nstr\n"); fprintf(fout, "30997 continue\n"); fprintf(fout, "}\nend\n"); } void statistics(void) { fprintf(errorf, "%d/%d nodes(%%e), %d/%d positions(%%p), %d/%d (%%n), %ld transitions,\n", tptr, treesize, nxtpos-positions, maxpos, stnum + 1, nstates, rcount); fprintf(errorf, "%d/%d packed char classes(%%k), ", pcptr-pchar, pchlen); if (optim) fprintf(errorf, " %d/%d packed transitions(%%a), ", nptr, ntrans); fprintf(errorf, " %d/%d output slots(%%o)", yytop, outsize); putc('\n', errorf); }
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)getopt.c 1.9 (gritter) 4/2/07 */ /* from OpenSolaris "getopt.c 1.23 05/06/08 SMI" */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ /* * See getopt(3C) and SUS/XPG getopt() for function definition and * requirements. * * This actual implementation is a bit looser than the specification * as it allows any character other than ':' to be used as an option * character - The specification only guarantees the alnum characters * ([a-z][A-Z][0-9]). */ #include <unistd.h> #include <string.h> #include <stdio.h> char *optarg = NULL; int optind = 1; int opterr = 1; int optopt = 0; #define ERR(s, c) err(s, c, optstring, argv[0]) static void err(const char *s, int c, const char *optstring, const char *argv0) { char errbuf[256], *ep = errbuf; const char *cp; if (opterr && optstring[0] != ':') { for (cp = argv0; *cp && ep<&errbuf[sizeof errbuf]; cp++, ep++) *ep = *cp; for (cp = ": "; *cp && ep<&errbuf[sizeof errbuf]; cp++, ep++) *ep = *cp; for (cp = s; *cp && ep<&errbuf[sizeof errbuf]; cp++, ep++) *ep = *cp; for (cp = " -- "; *cp && ep<&errbuf[sizeof errbuf]; cp++, ep++) *ep = *cp; if (ep<&errbuf[sizeof errbuf]) *ep++ = c; if (ep<&errbuf[sizeof errbuf]) *ep++ = '\n'; write(2, errbuf, ep - errbuf); } } /* * getopt_sp is required to keep state between successive calls to getopt() * while extracting aggregated options (ie: -abcd). Hence, getopt() is not * thread safe or reentrant, but it really doesn't matter. * * So, why isn't this "static" you ask? Because the historical Bourne * shell has actually latched on to this little piece of private data. */ int getopt_sp = 1; /* * Determine if the specified character (c) is present in the string * (optstring) as a regular, single character option. If the option is found, * return a pointer into optstring pointing at the option character, * otherwise return null. The character ':' is not allowed. */ static char * parse(const char *optstring, const char c) { char *cp = (char *)optstring; if (c == ':') return (NULL); do { if (*cp == c) return (cp); } while (*cp++ != '\0'); return (NULL); } /* * External function entry point. */ int getopt(int argc, char *const *argv, const char *optstring) { char c; char *cp; /* * Has the end of the options been encountered? The following * implements the SUS requirements: * * If, when getopt() is called: * argv[optind] is a null pointer * *argv[optind] is not the character '-' * argv[optind] points to the string "-" * getopt() returns -1 without changing optind. If * argv[optind] points to the string "--" * getopt() returns -1 after incrementing optind. */ if (getopt_sp == 1) { if (optind >= argc || argv[optind][0] != '-' || argv[optind] == NULL || argv[optind][1] == '\0') return (EOF); else if (strcmp(argv[optind], "--") == 0) { optind++; return (EOF); } } /* * Getting this far indicates that an option has been encountered. * Note that the syntax of optstring applies special meanings to * the characters ':' and '(', so they are not permissible as * option letters. A special meaning is also applied to the ')' * character, but its meaning can be determined from context. * Note that the specification only requires that the alnum * characters be accepted. */ optopt = c = (unsigned char)argv[optind][getopt_sp]; optarg = NULL; if ((cp = parse(optstring, c)) == NULL) { /* LINTED: variable format specifier */ ERR("illegal option", c); if (argv[optind][++getopt_sp] == '\0') { optind++; getopt_sp = 1; } return ('?'); } optopt = c = *cp; /* * A valid option has been identified. If it should have an * option-argument, process that now. SUS defines the setting * of optarg as follows: * * 1. If the option was the last character in the string pointed to * by an element of argv, then optarg contains the next element * of argv, and optind is incremented by 2. If the resulting * value of optind is not less than argc, this indicates a * missing option-argument, and getopt() returns an error * indication. * * 2. Otherwise, optarg points to the string following the option * character in that element of argv, and optind is incremented * by 1. * * The second clause allows -abcd (where b requires an option-argument) * to be interpreted as "-a -b cd". */ if (*(cp + 1) == ':') { /* The option takes an argument */ if (argv[optind][getopt_sp+1] != '\0') { optarg = &argv[optind++][getopt_sp+1]; } else if (++optind >= argc) { /* LINTED: variable format specifier */ ERR("option requires an argument", c); getopt_sp = 1; optarg = NULL; return (optstring[0] == ':' ? ':' : '?'); } else optarg = argv[optind++]; getopt_sp = 1; } else { /* The option does NOT take an argument */ if (argv[optind][++getopt_sp] == '\0') { getopt_sp = 1; optind++; } optarg = NULL; } return (c); } /* getopt() */
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* from OpenSolaris "lsearch.c 1.15 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)lsearch.c 1.4 (gritter) 11/26/05 */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ /* * Linear search algorithm, generalized from Knuth (6.1) Algorithm Q. * * This version no longer has anything to do with Knuth's Algorithm Q, * which first copies the new element into the table, then looks for it. * The assumption there was that the cost of checking for the end of the * table before each comparison outweighed the cost of the comparison, which * isn't true when an arbitrary comparison function must be called and when the * copy itself takes a significant number of cycles. * Actually, it has now reverted to Algorithm S, which is "simpler." */ #include <sys/types.h> #include <stddef.h> #include <string.h> #include "search.h" void * xlsearch(const void *ky, void *bs, unsigned *nelp, unsigned width, int (*compar)(const void *, const void *)) { char *key = (char *)ky; char *base = (char *)bs; char *next = base + *nelp * width; /* End of table */ void *res; for (; base < next; base += width) if ((*compar)(key, base) == 0) return (base); /* Key found */ ++*nelp; /* Not found, add to table */ res = memcpy(base, key, width); /* base now == next */ return (res); }
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ /* * Copyright 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef SEARCH_H #define SEARCH_H /* from OpenSolaris "search.h 1.19 05/06/08 SMI" SVr4.0 1.3.1.11 */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)search.h 1.4 (gritter) 11/26/05 */ #include <sys/types.h> void *xlsearch(const void *, void *, unsigned *, unsigned, int (*)(const void *, const void *)); #endif /* SEARCH_H */
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* Copyright (c) 1989 AT&T */ /* All Rights Reserved */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* from OpenSolaris "libmain.c 6.6 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)libmain.c 1.4 (gritter) 11/26/05 */ #include "stdio.h" extern int yylex(void); int main(void) { yylex(); return (0); }
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* from OpenSolaris "yyless.c 6.14 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)yyless.c 1.6 (gritter) 11/27/05 */ #include <stdlib.h> #ifdef __sun #include <sys/euc.h> #include <widec.h> #endif #include <limits.h> #include <inttypes.h> #include <unistd.h> extern int yyprevious; #ifndef JLSLEX #define CHR char extern CHR yytext[]; #define YYTEXT yytext #define YYLENG yyleng #define YYINPUT yyinput #define YYUNPUT yyunput #define YYOUTPUT yyoutput #endif #ifdef WOPTION #define CHR wchar_t extern CHR yytext[]; #define YYTEXT yytext #define YYLENG yyleng #define YYINPUT yyinput #define YYUNPUT yyunput #define YYOUTPUT yyoutput #define yyless yyless_w #endif #ifdef EOPTION #define CHR wchar_t extern int yyleng; extern CHR yytext[]; extern CHR yywtext[]; #define YYTEXT yywtext #define YYLENG yywleng #define YYINPUT yywinput #define YYUNPUT yywunput #define YYOUTPUT yywoutput #define yyless yyless_e #endif extern int YYLENG; #if defined(__STDC__) extern void YYUNPUT(int); #endif #if defined(__cplusplus) || defined(__STDC__) /* XCU4: type of yyless() changes to int */ int yyless(int x) #else yyless(x) int x; #endif { register CHR *lastch, *ptr; lastch = YYTEXT+YYLENG; if (x >= 0 && x <= YYLENG) ptr = x + YYTEXT; else { if (sizeof (int) != sizeof (intptr_t)) { static int seen = 0; if (!seen) { write(2, "warning: yyless pointer arg truncated\n", 39); seen = 1; } } /* * The cast on the next line papers over an unconscionable nonportable * glitch to allow the caller to hand the function a pointer instead of * an integer and hope that it gets figured out properly. But it's * that way on all systems. */ ptr = (CHR *)(intptr_t)x; } while (lastch > ptr) YYUNPUT(*--lastch); *lastch = 0; if (ptr > YYTEXT) yyprevious = *--lastch; YYLENG = ptr-YYTEXT; #ifdef EOPTION yyleng = wcstombs((char *)yytext, YYTEXT, YYLENG*MB_LEN_MAX); #endif return (0); }
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* Copyright (c) 1989 AT&T */ /* All Rights Reserved */ /* from OpenSolaris "yywrap.c 6.4 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)yywrap.c 1.3 (gritter) 6/18/05 */ #if defined(__cplusplus) || defined(__STDC__) int yywrap(void) #else yywrap() #endif { return(1); }
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* Copyright (c) 1989 AT&T */ /* All Rights Reserved */ /* from OpenSolaris "ncform 6.14 05/06/10 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany * * Sccsid @(#)ncform 1.4 (gritter) 11/18/05 */ int yylineno =1; # define YYU(x) x # define NLSTATE yyprevious=YYNEWLINE struct yysvf *yylstate [YYLMAX], **yylsp, **yyolsp; char yysbuf[YYLMAX]; char *yysptr = yysbuf; int *yyfnd; extern struct yysvf *yyestate; int yyprevious = YYNEWLINE; #if defined(__cplusplus) || defined(__STDC__) int yylook(void) #else yylook() #endif { register struct yysvf *yystate, **lsp; register struct yywork *yyt; struct yysvf *yyz; int yych, yyfirst; struct yywork *yyr; # ifdef LEXDEBUG int debug; # endif char *yylastch; /* start off machines */ # ifdef LEXDEBUG debug = 0; # endif yyfirst=1; if (!yymorfg) yylastch = yytext; else { yymorfg=0; yylastch = yytext+yyleng; } for(;;){ lsp = yylstate; yyestate = yystate = yybgin; if (yyprevious==YYNEWLINE) yystate++; for (;;){ # ifdef LEXDEBUG if(debug)fprintf(yyout,"state %d\n",yystate-yysvec-1); # endif yyt = yystate->yystoff; if(yyt == yycrank && !yyfirst){ /* may not be any transitions */ yyz = yystate->yyother; if(yyz == 0)break; if(yyz->yystoff == yycrank)break; } #ifndef __cplusplus *yylastch++ = yych = input(); #else *yylastch++ = yych = lex_input(); #endif #ifdef YYISARRAY if(yylastch > &yytext[YYLMAX]) { fprintf(yyout,"Input string too long, limit %d\n",YYLMAX); exit(1); } #else if (yylastch >= &yytext[ yytextsz ]) { int x = yylastch - yytext; yytextsz += YYTEXTSZINC; if (yytext == yy_tbuf) { yytext = (char *) malloc(yytextsz); memcpy(yytext, yy_tbuf, sizeof (yy_tbuf)); } else yytext = (char *) realloc(yytext, yytextsz); if (!yytext) { fprintf(yyout, "Cannot realloc yytext\n"); exit(1); } yylastch = yytext + x; } #endif yyfirst=0; tryagain: # ifdef LEXDEBUG if(debug){ fprintf(yyout,"char "); allprint(yych); putchar('\n'); } # endif yyr = yyt; if ( yyt > yycrank){ yyt = yyr + yych; if (yyt <= yytop && yyt->verify+yysvec == yystate){ if(yyt->advance+yysvec == YYLERR) /* error transitions */ {unput(*--yylastch);break;} *lsp++ = yystate = yyt->advance+yysvec; if(lsp > &yylstate[YYLMAX]) { fprintf(yyout,"Input string too long, limit %d\n",YYLMAX); exit(1); } goto contin; } } # ifdef YYOPTIM else if(yyt < yycrank) { /* r < yycrank */ yyt = yyr = yycrank+(yycrank-yyt); # ifdef LEXDEBUG if(debug)fprintf(yyout,"compressed state\n"); # endif yyt = yyt + yych; if(yyt <= yytop && yyt->verify+yysvec == yystate){ if(yyt->advance+yysvec == YYLERR) /* error transitions */ {unput(*--yylastch);break;} *lsp++ = yystate = yyt->advance+yysvec; if(lsp > &yylstate[YYLMAX]) { fprintf(yyout,"Input string too long, limit %d\n",YYLMAX); exit(1); } goto contin; } yyt = yyr + YYU(yymatch[yych]); # ifdef LEXDEBUG if(debug){ fprintf(yyout,"try fall back character "); allprint(YYU(yymatch[yych])); putchar('\n'); } # endif if(yyt <= yytop && yyt->verify+yysvec == yystate){ if(yyt->advance+yysvec == YYLERR) /* error transition */ {unput(*--yylastch);break;} *lsp++ = yystate = yyt->advance+yysvec; if(lsp > &yylstate[YYLMAX]) { fprintf(yyout,"Input string too long, limit %d\n",YYLMAX); exit(1); } goto contin; } } if ((yystate = yystate->yyother) && (yyt= yystate->yystoff) != yycrank){ # ifdef LEXDEBUG if(debug)fprintf(yyout,"fall back to state %d\n",yystate-yysvec-1); # endif goto tryagain; } # endif else {unput(*--yylastch);break;} contin: # ifdef LEXDEBUG if(debug){ fprintf(yyout,"state %d char ",yystate-yysvec-1); allprint(yych); putchar('\n'); } # endif ; } # ifdef LEXDEBUG if(debug){ fprintf(yyout,"stopped at %d with ",*(lsp-1)-yysvec-1); allprint(yych); putchar('\n'); } # endif while (lsp-- > yylstate){ *yylastch-- = 0; if (*lsp != 0 && (yyfnd= (*lsp)->yystops) && *yyfnd > 0){ yyolsp = lsp; if(yyextra[*yyfnd]){ /* must backup */ while(yyback((*lsp)->yystops,-*yyfnd) != 1 && lsp > yylstate){ lsp--; unput(*yylastch--); } } yyprevious = YYU(*yylastch); yylsp = lsp; yyleng = yylastch-yytext+1; yytext[yyleng] = 0; # ifdef LEXDEBUG if(debug){ fprintf(yyout,"\nmatch "); sprint(yytext); fprintf(yyout," action %d\n",*yyfnd); } # endif return(*yyfnd++); } unput(*yylastch); } if (yytext[0] == 0 /* && feof(yyin) */) { yysptr=yysbuf; return(0); } #ifndef __cplusplus yyprevious = yytext[0] = input(); if (yyprevious>0) output(yyprevious); #else yyprevious = yytext[0] = lex_input(); if (yyprevious>0) lex_output(yyprevious); #endif yylastch=yytext; # ifdef LEXDEBUG if(debug)putchar('\n'); # endif } } #if defined(__cplusplus) || defined(__STDC__) int yyback(int *p, int m) #else yyback(p, m) int *p; #endif { if (p==0) return(0); while (*p) { if (*p++ == m) return(1); } return(0); } /* the following are only used in the lex library */ #if defined(__cplusplus) || defined(__STDC__) int yyinput(void) #else yyinput() #endif { #ifndef __cplusplus return(input()); #else return(lex_input()); #endif } #if defined(__cplusplus) || defined(__STDC__) void yyoutput(int c) #else yyoutput(c) int c; #endif { #ifndef __cplusplus output(c); #else lex_output(c); #endif } #if defined(__cplusplus) || defined(__STDC__) void yyunput(int c) #else yyunput(c) int c; #endif { unput(c); }
9f5186c86f8a2c7a0eb39d11f7017fc70d5a3940d56c648f93f6a3189ac1d67f /usr/bin/yacc 0fa43f12e3e32987211a433a9692f939b2c5d0dc32cd5523f9ad50bb441ac580 /usr/bin/lex ffe696afc1bda32a5f4035e29b3275cab73a27df7635ccbe02ed49a30374ccdd /usr/lib/mes/libl.a bf3fb293f1ff89ee3dbcb08166c64b7a6793b49a12673d7633e3353ebea80d4d /yaccpar ee0f187b844f50d64c912bfcb5d73706662846d6d8a90b8b1fb20dda60464734 /lex/ncform
https://mirrors.kernel.org/gnu/bash/bash-2.05b.tar.gz ba03d412998cc54bd0b0f2d6c32100967d3137098affdc2d32e6e7c11b163fe4
# SPDX-FileCopyrightText: 2021 Andrius Å tikonas <andrius@stikonas.eu> # SPDX-FileCopyrightText: 2021 fosslinux <fosslinux@aussies.space> # # SPDX-License-Identifier: GPL-3.0-or-later include common.mk CFLAGS = \ -I. \ -Iinclude \ -Ilib \ -Ilib/sh \ -Ibuiltins \ $(COMMON_CFLAGS) LDFLAGS = -L. -Lbuiltins -static LIBRARIES = libsh.a builtins/libbuiltins.a libglob.a libtilde.a SHLIB_FILES = clktck getcwd getenv oslib setlinebuf strcasecmp strerror strtod \ vprint itos rename zread zwrite shtty inet_aton netopen \ strpbrk timeval clock makepath pathcanon pathphys stringlist stringvec \ tmpfile spell strtrans strindex shquote snprintf mailstat fmtulong \ fmtullong strtoll strtoull strtoimax strtoumax fmtumax netconn mktime \ strftime xstrchr zcatfd # FIXME: for some reason these don't get picked up correctly in the # final linking cmd SHLIB_ODD_FILES = zcatfd strtoumax spell pathphys SHLIB_OBJS = $(addprefix lib/sh/, $(addsuffix .o, $(SHLIB_FILES))) SHLIB_ODD_OBJS = $(addprefix lib/sh/, $(addsuffix .o, $(SHLIB_ODD_FILES))) MKBUILTINS_OBJS = builtins/mkbuiltins.o BUILTINS_DEFS = $(addprefix builtins/, $(addsuffix .def, $(BUILTINS_DEF_FILES))) GLOB_FILES = glob strmatch smatch xmbsrtowcs GLOB_OBJS = $(addprefix lib/glob/, $(addsuffix .o, $(GLOB_FILES))) TILDE_OBJS = lib/tilde/tilde.o MKSYNTAX_OBJS = mksyntax.o MKSIGNAMES_OBJS = support/mksignames.o FILES = shell eval y.tab general make_cmd print_cmd dispose_cmd execute_cmd \ variables copy_cmd error expr flags nojobs subst hashcmd hashlib mailcheck \ trap input unwind_prot pathexp sig test version alias array arrayfunc \ braces bracecomp bashhist bashline list stringlib locale findcmd redir \ pcomplete pcomplib syntax xmalloc siglist OBJS = $(addsuffix .o, $(FILES)) all: bash # Builtins mkbuiltins: $(MKBUILTINS_OBJS) $(CC) $(CFLAGS) $(MKBUILTINS_OBJS) $(LDFLAGS) -o $@ ./mkbuiltins -externfile builtins/builtext.h -structfile builtins/builtins.c -noproduction $(BUILTINS_DEFS) # libsh libsh.a: $(SHLIB_OBJS) $(AR) cr $@ $^ # libglob libglob.a: $(GLOB_OBJS) $(AR) cr $@ $^ # libtilde libtilde.a: $(TILDE_OBJS) $(AR) cr $@ $^ # The actual program mksyntax: $(MKSYNTAX_OBJS) $(CC) $^ $(LDFLAGS) -o $@ -lgetopt syntax.c: mksyntax ./mksyntax -o $@ mksignames: $(MKSIGNAMES_OBJS) $(CC) $^ $(LDFLAGS) -o $@ -lgetopt signames.h: mksignames ./mksignames $@ y.tab.c: parse.y yacc -d $^ trap.c: signames.h bash: libsh.a libglob.a libtilde.a $(OBJS) $(CC) -o bash $(LIBRARIES) $(OBJS) $(SHLIB_ODD_OBJS) $(LDFLAGS) -lsh -lbuiltins -lglob -ltilde
# SPDX-FileCopyrightText: 2021 fosslinux <fosslinux@aussies.space> # # SPDX-License-Identifier: GPL-3.0-or-later .SUFFIXES = .def include ../common.mk CFLAGS = \ -I. \ -I.. \ -I../include \ -I../lib \ $(COMMON_CFLAGS) BUILTINS_DEFS = $(addsuffix .def, $(BUILTINS_DEF_FILES)) BUILTINS_DEF_OBJS = $(addsuffix .o, $(BUILTINS_DEF_FILES)) BUILTINS_STATIC_FILES = common evalstring evalfile getopt bashgetopt BUILTINS_STATIC_OBJS = $(addsuffix .o, $(BUILTINS_STATIC_FILES)) BUILTINS_OBJS = $(BUILTINS_DEF_OBJS) $(BUILTINS_STATIC_OBJS) %.o: %.def ../mkbuiltins $< $(CC) -c $(CFLAGS) -o $@ $*.c libbuiltins.a: $(BUILTINS_OBJS) builtins.o $(AR) cr $@ $^
# SPDX-FileCopyrightText: 2021 Paul Dersey <pdersey@gmail.com> # SPDX-FileCopyrightText: 2021 fosslinux <fosslinux@aussies.space> # # SPDX-License-Identifier: GPL-3.0-or-later CC = tcc LD = tcc AR = tcc -ar COMMON_CFLAGS = \ -DHAVE_DIRENT_H \ -DSTRUCT_DIRENT_HAS_D_INO \ -DHAVE_STDINT_H \ -DHAVE_LIMITS_H \ -DHAVE_STRING_H \ -DHAVE_INTTYPES_H \ -DRETSIGTYPE=void \ -DHUGE_VAL=10000000000.0 \ -DTERMIO_TTY_DRIVER \ -DPREFER_STDARG \ -DHAVE_DECL_STRTOL \ -DHAVE_DECL_STRTOLL \ -DHAVE_DECL_STRTOUL \ -DHAVE_DECL_STRTOULL \ -DHAVE_TZNAME \ -DPIPESIZE=4096 \ -DDEFAULT_PATH_VALUE=\"$(prefix)/bin\" \ -DSTANDARD_UTILS_PATH=\"$(prefix)/bin\" \ -DPPROMPT=\"$ \" \ -DSPROMPT=\"$ \" \ -DCONF_MACHTYPE=\"bootstrap\" \ -DGETGROUPS_T=int \ -DCOND_COMMAND \ -DCONF_HOSTTYPE=\"i386\" \ -DCONF_OSTYPE=\"linux\" \ -DDEFAULT_MAIL_DIRECTORY=\"/fake-mail\" \ -DVOID_SIGHANDLER \ -DDISTVERSION=\"2.05b\" \ -DBUILDVERSION=\"0\" \ -DSCCSVERSION=\"2.05b\" \ -DLC_ALL=\"C\" \ -DHAVE_STRERROR \ -DHAVE_MEMSET \ -DHAVE_DUP2 \ -DHAVE_STRTOUL \ -DHAVE_STRTOULL \ -DHAVE_STRCHR \ -DHAVE_BCOPY \ -DHAVE_BZERO \ -DHAVE_POSIX_SIGNALS \ -DHAVE_GETCWD \ -DHAVE_SYS_SIGLIST \ -Dendpwent\(x\)=0 \ -Denable_hostname_completion\(on_or_off\)=0 BUILTINS_DEF_FILES = alias bind break builtin cd colon command complete declare \ echo enable eval exec exit fc fg_bg hash history jobs kill let read return \ set setattr shift source suspend test times trap type ulimit umask wait \ getopts pushd shopt printf
SPDX-FileCopyrightText: 2021 fosslinux <fosslinux@aussies.space> SPDX-License-Identifier: GPL-2.0-or-later mes libc does not have locale support... diff --git lib/sh/snprintf.c lib/sh/snprintf.c index 7669576..747aeba 100644 --- lib/sh/snprintf.c +++ lib/sh/snprintf.c @@ -376,7 +376,7 @@ static void xfree __P((void *)); if ((p)->flags & PF_STAR_P) \ (p)->precision = GETARG (int) -#if defined (HAVE_LOCALE_H) +#if 0 # define GETLOCALEDATA(d, t, g) \ do \ { \
/* build a test version with gcc -g -DDRIVER -I../.. -I../../include -o test-snprintf snprintf.c fmtu*long.o */ /* Unix snprintf implementation. derived from inetutils/libinetutils/snprintf.c Version 1.1 Copyright (C) 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General License for more details. You should have received a copy of the GNU General License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Revision History: 1.1: * added changes from Miles Bader * corrected a bug with %f * added support for %#g * added more comments :-) 1.0: * supporting must ANSI syntaxic_sugars 0.0: * support %s %c %d THANKS(for the patches and ideas): Miles Bader Cyrille Rustom Jacek Slabocewiz Mike Parker(mouse) */ /* * Currently doesn't handle (and bash/readline doesn't use): * *M$ width, precision specifications * %N$ numbered argument conversions * inf, nan floating values imperfect (if isinf(), isnan() not in libc) * support for `F' is imperfect, since underlying printf may not handle it */ #define FLOATING_POINT #ifdef HAVE_CONFIG_H # include <config.h> #endif #if defined(DRIVER) && !defined(HAVE_CONFIG_H) #define HAVE_LONG_LONG #define HAVE_LONG_DOUBLE #ifdef __linux__ #define HAVE_PRINTF_A_FORMAT #endif #define HAVE_ISINF_IN_LIBC #define PREFER_STDARG #define HAVE_STRINGIZE #define HAVE_LIMITS_H #define HAVE_STDDEF_H #define HAVE_LOCALE_H #define intmax_t long #endif #if !defined (HAVE_SNPRINTF) || !defined (HAVE_ASPRINTF) #include <bashtypes.h> #if defined(PREFER_STDARG) # include <stdarg.h> #else # include <varargs.h> #endif #ifdef HAVE_LIMITS_H # include <limits.h> #endif #include <bashansi.h> #ifdef HAVE_STDDEF_H # include <stddef.h> #endif #include <chartypes.h> #ifdef HAVE_STDINT_H # include <stdint.h> #endif #ifdef FLOATING_POINT # include <float.h> /* for manifest constants */ # include <stdio.h> /* for sprintf */ #endif #include <typemax.h> #ifdef HAVE_LOCALE_H # include <locale.h> #endif #include "stdc.h" #include <shmbutil.h> #ifndef DRIVER # include "shell.h" #else # define FL_PREFIX 0x01 /* add 0x, 0X, or 0 prefix as appropriate */ # define FL_ADDBASE 0x02 /* add base# prefix to converted value */ # define FL_HEXUPPER 0x04 /* use uppercase when converting to hex */ # define FL_UNSIGNED 0x08 /* don't add any sign */ extern char *fmtulong __P((unsigned long int, int, char *, size_t, int)); extern char *fmtullong __P((unsigned long long int, int, char *, size_t, int)); #endif #ifndef FREE # define FREE(x) if (x) free (x) #endif /* Bound on length of the string representing an integer value of type T. Subtract one for the sign bit if T is signed; 302 / 1000 is log10 (2) rounded up; add one for integer division truncation; add one more for a minus sign if t is signed. */ #define INT_STRLEN_BOUND(t) \ ((sizeof (t) * CHAR_BIT - TYPE_SIGNED (t)) * 302 / 1000 \ + 1 + TYPE_SIGNED (t)) /* conversion flags */ #define PF_ALTFORM 0x00001 /* # */ #define PF_HEXPREFIX 0x00002 /* 0[Xx] */ #define PF_LADJUST 0x00004 /* - */ #define PF_ZEROPAD 0x00008 /* 0 */ #define PF_PLUS 0x00010 /* + */ #define PF_SPACE 0x00020 /* ' ' */ #define PF_THOUSANDS 0x00040 /* ' */ #define PF_DOT 0x00080 /* `.precision' */ #define PF_STAR_P 0x00100 /* `*' after precision */ #define PF_STAR_W 0x00200 /* `*' before or without precision */ /* length modifiers */ #define PF_SIGNEDCHAR 0x00400 /* hh */ #define PF_SHORTINT 0x00800 /* h */ #define PF_LONGINT 0x01000 /* l */ #define PF_LONGLONG 0x02000 /* ll */ #define PF_LONGDBL 0x04000 /* L */ #define PF_INTMAX_T 0x08000 /* j */ #define PF_SIZE_T 0x10000 /* z */ #define PF_PTRDIFF_T 0x20000 /* t */ #define PF_ALLOCBUF 0x40000 /* for asprintf, vasprintf */ #define PFM_SN 0x01 /* snprintf, vsnprintf */ #define PFM_AS 0x02 /* asprintf, vasprintf */ #define ASBUFSIZE 128 #define x_digs "0123456789abcdef" #define X_digs "0123456789ABCDEF" static char intbuf[INT_STRLEN_BOUND(unsigned long) + 1]; static int decpoint; static int thoussep; static char *grouping; /* * For the FLOATING POINT FORMAT : * the challenge was finding a way to * manipulate the Real numbers without having * to resort to mathematical function(it * would require to link with -lm) and not * going down to the bit pattern(not portable) * * so a number, a real is: real = integral + fraction integral = ... + a(2)*10^2 + a(1)*10^1 + a(0)*10^0 fraction = b(1)*10^-1 + b(2)*10^-2 + ... where: 0 <= a(i) => 9 0 <= b(i) => 9 from then it was simple math */ /* * size of the buffer for the integral part * and the fraction part */ #define MAX_INT 99 + 1 /* 1 for the null */ #define MAX_FRACT 307 + 1 /* * These functions use static buffers to store the results, * and so are not reentrant */ #define itoa(n) fmtulong(n, 10, intbuf, sizeof(intbuf), 0); #define dtoa(n, p, f) numtoa(n, 10, p, f) #define SWAP_INT(a,b) {int t; t = (a); (a) = (b); (b) = t;} #define GETARG(type) (va_arg(args, type)) /* Macros that do proper sign extension and handle length modifiers. Used for the integer conversion specifiers. */ #define GETSIGNED(p) \ (((p)->flags & PF_LONGINT) \ ? GETARG (long) \ : (((p)->flags & PF_SHORTINT) ? (long)(short)GETARG (int) \ : (long)GETARG (int))) #define GETUNSIGNED(p) \ (((p)->flags & PF_LONGINT) \ ? GETARG (unsigned long) \ : (((p)->flags & PF_SHORTINT) ? (unsigned long)(unsigned short)GETARG (int) \ : (unsigned long)GETARG (unsigned int))) #ifdef HAVE_LONG_DOUBLE #define GETLDOUBLE(p) GETARG (long double) #endif #define GETDOUBLE(p) GETARG (double) #define SET_SIZE_FLAGS(p, type) \ if (sizeof (type) > sizeof (int)) \ (p)->flags |= PF_LONGINT; \ if (sizeof (type) > sizeof (long)) \ (p)->flags |= PF_LONGLONG; /* this struct holds everything we need */ struct DATA { int length; char *base; /* needed for [v]asprintf */ char *holder; int counter; const char *pf; /* FLAGS */ int flags; int justify; int width, precision; char pad; }; /* the floating point stuff */ #ifdef FLOATING_POINT static double pow_10 __P((int)); static int log_10 __P((double)); static double integral __P((double, double *)); static char *numtoa __P((double, int, int, char **)); #endif static void init_data __P((struct DATA *, char *, size_t, const char *, int)); static void init_conv_flag __P((struct DATA *)); /* for the format */ #ifdef FLOATING_POINT static void floating __P((struct DATA *, double)); static void exponent __P((struct DATA *, double)); #endif static void number __P((struct DATA *, unsigned long, int)); #ifdef HAVE_LONG_LONG static void lnumber __P((struct DATA *, unsigned long long, int)); #endif static void pointer __P((struct DATA *, unsigned long)); static void strings __P((struct DATA *, char *)); #ifdef FLOATING_POINT # define FALLBACK_FMTSIZE 32 # define FALLBACK_BASE 4096 # define LFALLBACK_BASE 5120 # ifdef HAVE_LONG_DOUBLE static void ldfallback __P((struct DATA *, const char *, const char *, long double)); # endif static void dfallback __P((struct DATA *, const char *, const char *, double)); #endif static char *groupnum __P((char *)); #ifdef DRIVER static void memory_error_and_abort (); static void *xmalloc __P((size_t)); static void *xrealloc __P((void *, size_t)); static void xfree __P((void *)); #else # include <xmalloc.h> #endif /* those are defines specific to snprintf to hopefully * make the code clearer :-) */ #define RIGHT 1 #define LEFT 0 #define NOT_FOUND -1 #define FOUND 1 #define MAX_FIELD 15 /* round off to the precision */ #define ROUND(d, p) \ (d < 0.) ? \ d - pow_10(-(p)->precision) * 0.5 : \ d + pow_10(-(p)->precision) * 0.5 /* set default precision */ #define DEF_PREC(p) \ if ((p)->precision == NOT_FOUND) \ (p)->precision = 6 /* put a char. increment the number of chars written even if we've exceeded the vsnprintf/snprintf buffer size (for the return value) */ #define PUT_CHAR(c, p) \ do \ { \ if (((p)->flags & PF_ALLOCBUF) && ((p)->counter >= (p)->length - 1)) \ { \ (p)->length += ASBUFSIZE; \ (p)->base = (char *)xrealloc((p)->base, (p)->length); \ (p)->holder = (p)->base + (p)->counter; /* in case reallocated */ \ } \ if ((p)->counter < (p)->length) \ *(p)->holder++ = (c); \ (p)->counter++; \ } \ while (0) /* Output a string. P->WIDTH has already been adjusted for padding. */ #define PUT_STRING(string, len, p) \ do \ { \ PAD_RIGHT (p); \ while ((len)-- > 0) \ { \ PUT_CHAR (*(string), (p)); \ (string)++; \ } \ PAD_LEFT (p); \ } \ while (0) #define PUT_PLUS(d, p, zero) \ if ((d) > zero && (p)->justify == RIGHT) \ PUT_CHAR('+', p) #define PUT_SPACE(d, p, zero) \ if (((p)->flags & PF_SPACE) && (d) > zero) \ PUT_CHAR(' ', p) /* pad right */ #define PAD_RIGHT(p) \ if ((p)->width > 0 && (p)->justify != LEFT) \ for (; (p)->width > 0; (p)->width--) \ PUT_CHAR((p)->pad, p) /* pad left */ #define PAD_LEFT(p) \ if ((p)->width > 0 && (p)->justify == LEFT) \ for (; (p)->width > 0; (p)->width--) \ PUT_CHAR((p)->pad, p) /* if width and prec. in the args */ #define STAR_ARGS(p) \ if ((p)->flags & PF_STAR_W) \ (p)->width = GETARG (int); \ if ((p)->flags & PF_STAR_P) \ (p)->precision = GETARG (int) #if defined (HAVE_LOCALE_H) # define GETLOCALEDATA(d, t, g) \ do \ { \ struct lconv *lv; \ if ((d) == 0) { \ (d) = '.'; (t) = -1; (g) = 0; /* defaults */ \ lv = localeconv(); \ if (lv) \ { \ if (lv->decimal_point && lv->decimal_point[0]) \ (d) = lv->decimal_point[0]; \ if (lv->thousands_sep && lv->thousands_sep[0]) \ (t) = lv->thousands_sep[0]; \ (g) = lv->grouping ? lv->grouping : ""; \ if (*(g) == '\0' || *(g) == CHAR_MAX || (t) == -1) (g) = 0; \ } \ } \ } \ while (0); #else # define GETLOCALEDATA(d, t, g) \ ( (d) = '.', (t) = ',', g = "\003" ) #endif #ifdef FLOATING_POINT /* * Find the nth power of 10 */ static double pow_10(n) int n; { double P; /* handle common cases with fast switch statement. */ switch (n) { case -3: return .001; case -2: return .01; case -1: return .1; case 0: return 1.; case 1: return 10.; case 2: return 100.; case 3: return 1000.; } if (n < 0) { P = .0001; for (n += 4; n < 0; n++) P /= 10.; } else { P = 10000.; for (n -= 4; n > 0; n--) P *= 10.; } return P; } /* * Find the integral part of the log in base 10 * Note: this not a real log10() I just need and approximation(integerpart) of x in: 10^x ~= r * log_10(200) = 2; * log_10(250) = 2; */ static int log_10(r) double r; { int i = 0; double result = 1.; if (r < 0.) r = -r; if (r < 1.) { while (result >= r) { result /= 10.; i++; } return (-i); } else { while (result <= r) { result *= 10.; i++; } return (i - 1); } } /* * This function return the fraction part of a double * and set in ip the integral part. * In many ways it resemble the modf() found on most Un*x */ static double integral(real, ip) double real; double *ip; { int j; double i, s, p; double real_integral = 0.; /* take care of the obvious */ /* equal to zero ? */ if (real == 0.) { *ip = 0.; return (0.); } /* negative number ? */ if (real < 0.) real = -real; /* a fraction ? */ if ( real < 1.) { *ip = 0.; return real; } /* the real work :-) */ for (j = log_10(real); j >= 0; j--) { p = pow_10(j); s = (real - real_integral)/p; i = 0.; while (i + 1. <= s) i++; real_integral += i*p; } *ip = real_integral; return (real - real_integral); } #define PRECISION 1.e-6 /* * return an ascii representation of the integral part of the number * and set fract to be an ascii representation of the fraction part * the container for the fraction and the integral part or staticly * declare with fix size */ static char * numtoa(number, base, precision, fract) double number; int base, precision; char **fract; { register int i, j; double ip, fp; /* integer and fraction part */ double fraction; int digits = MAX_INT - 1; static char integral_part[MAX_INT]; static char fraction_part[MAX_FRACT]; double sign; int ch; /* taking care of the obvious case: 0.0 */ if (number == 0.) { integral_part[0] = '0'; integral_part[1] = '\0'; fraction_part[0] = '0'; fraction_part[1] = '\0'; if (fract) *fract = fraction_part; return integral_part; } /* for negative numbers */ if ((sign = number) < 0.) { number = -number; digits--; /* sign consume one digit */ } fraction = integral(number, &ip); number = ip; /* do the integral part */ if (ip == 0.) { integral_part[0] = '0'; i = 1; } else { for ( i = 0; i < digits && number != 0.; ++i) { number /= base; fp = integral(number, &ip); ch = (int)((fp + PRECISION)*base); /* force to round */ integral_part[i] = (ch <= 9) ? ch + '0' : ch + 'a' - 10; if (! ISXDIGIT((unsigned char)integral_part[i])) break; /* bail out overflow !! */ number = ip; } } /* Oh No !! out of bound, ho well fill it up ! */ if (number != 0.) for (i = 0; i < digits; ++i) integral_part[i] = '9'; /* put the sign ? */ if (sign < 0.) integral_part[i++] = '-'; integral_part[i] = '\0'; /* reverse every thing */ for ( i--, j = 0; j < i; j++, i--) SWAP_INT(integral_part[i], integral_part[j]); /* the fractional part */ for (i=0, fp=fraction; precision > 0 && i < MAX_FRACT ; i++, precision--) { fraction_part[i] = (int)((fp + PRECISION)*10. + '0'); if (! DIGIT(fraction_part[i])) /* underflow ? */ break; fp = (fp*10.0) - (double)(long)((fp + PRECISION)*10.); } fraction_part[i] = '\0'; if (fract != (char **)0) *fract = fraction_part; return integral_part; } #endif /* for %d and friends, it puts in holder * the representation with the right padding */ static void number(p, d, base) struct DATA *p; unsigned long d; int base; { char *tmp, *t; long sd; int flags; sd = d; /* signed for ' ' padding in base 10 */ flags = (*p->pf == 'u' || *p->pf == 'U') ? FL_UNSIGNED : 0; if (*p->pf == 'X') flags |= FL_HEXUPPER; tmp = fmtulong (d, base, intbuf, sizeof(intbuf), flags); t = 0; if ((p->flags & PF_THOUSANDS)) { GETLOCALEDATA(decpoint, thoussep, grouping); if (grouping && (t = groupnum (tmp))) tmp = t; } p->width -= strlen(tmp); PAD_RIGHT(p); switch (base) { case 10: PUT_PLUS(sd, p, 0); PUT_SPACE(sd, p, 0); break; case 8: if (p->flags & PF_ALTFORM) PUT_CHAR('0', p); break; case 16: if (p->flags & PF_ALTFORM) { PUT_CHAR('0', p); PUT_CHAR(*p->pf, p); } break; } while (*tmp) { PUT_CHAR(*tmp, p); tmp++; } PAD_LEFT(p); FREE (t); } #ifdef HAVE_LONG_LONG /* * identical to number() but works for `long long' */ static void lnumber(p, d, base) struct DATA *p; unsigned long long d; int base; { char *tmp, *t; long long sd; int flags; sd = d; /* signed for ' ' padding in base 10 */ flags = (*p->pf == 'u' || *p->pf == 'U') ? FL_UNSIGNED : 0; if (*p->pf == 'X') flags |= FL_HEXUPPER; tmp = fmtullong (d, base, intbuf, sizeof(intbuf), flags); t = 0; if ((p->flags & PF_THOUSANDS)) { GETLOCALEDATA(decpoint, thoussep, grouping); if (grouping && (t = groupnum (tmp))) tmp = t; } p->width -= strlen(tmp); PAD_RIGHT(p); switch (base) { case 10: PUT_PLUS(sd, p, 0); PUT_SPACE(sd, p, 0); break; case 8: if (p->flags & PF_ALTFORM) PUT_CHAR('0', p); break; case 16: if (p->flags & PF_ALTFORM) { PUT_CHAR('0', p); PUT_CHAR(*p->pf, p); } break; } while (*tmp) { PUT_CHAR(*tmp, p); tmp++; } PAD_LEFT(p); FREE (t); } #endif static void pointer(p, d) struct DATA *p; unsigned long d; { char *tmp; tmp = fmtulong(d, 16, intbuf, sizeof(intbuf), 0); p->width -= strlen(tmp); PAD_RIGHT(p); /* prefix '0x' for pointers */ PUT_CHAR('0', p); PUT_CHAR('x', p); while (*tmp) { PUT_CHAR(*tmp, p); tmp++; } PAD_LEFT(p); } /* %s strings */ static void strings(p, tmp) struct DATA *p; char *tmp; { size_t len; len = strlen(tmp); if (p->precision != NOT_FOUND) /* the smallest number */ len = (len < p->precision ? len : p->precision); p->width -= len; PUT_STRING (tmp, len, p); } #if HANDLE_MULTIBYTE /* %ls wide-character strings */ static void wstrings(p, tmp) struct DATA *p; wchar_t *tmp; { size_t len; mbstate_t mbs; char *os; const wchar_t *ws; memset (&mbs, '\0', sizeof (mbstate_t)); ws = (const wchar_t *)tmp; os = (char *)NULL; if (p->precision != NOT_FOUND) { os = (char *)xmalloc (p->precision + 1); len = wcsrtombs (os, &ws, p->precision, &mbs); } else { len = wcsrtombs (NULL, &ws, 0, &mbs); if (len != (size_t)-1) { memset (&mbs, '\0', sizeof (mbstate_t)); os = (char *)xmalloc (len + 1); (void)wcsrtombs (os, &ws, len + 1, &mbs); } } if (len == (size_t)-1) { /* invalid multibyte sequence; bail now. */ FREE (os); return; } p->width -= len; PUT_STRING (os, len, p); free (os); } static void wchars (p, wc) struct DATA *p; wint_t wc; { char *lbuf, *l; mbstate_t mbs; size_t len; lbuf = (char *)malloc (MB_CUR_MAX+1); if (lbuf == 0) return; memset (&mbs, '\0', sizeof (mbstate_t)); len = wcrtomb (lbuf, wc, &mbs); if (len == (size_t)-1) /* conversion failed; bail now. */ return; p->width -= len; l = lbuf; PUT_STRING (l, len, p); free (lbuf); } #endif /* HANDLE_MULTIBYTE */ #ifdef FLOATING_POINT #ifndef HAVE_ISINF_IN_LIBC /* Half-assed versions, since we don't want to link with libm. */ static int isinf(d) double d; { #ifdef DBL_MAX if (d < DBL_MIN) return -1; else if (d > DBL_MAX) return 1; else #endif return 0; } static int isnan(d) double d; { return 0; } #endif /* Check for [+-]infinity and NaN. If MODE == 1, we check for Infinity, else (mode == 2) we check for NaN. This does the necessary printing. Returns 1 if Inf or Nan, 0 if not. */ static int chkinfnan(p, d, mode) struct DATA *p; double d; int mode; /* == 1 for inf, == 2 for nan */ { int i; char *tmp; char *big, *small; i = (mode == 1) ? isinf(d) : isnan(d); if (i == 0) return 0; big = (mode == 1) ? "INF" : "NAN"; small = (mode == 1) ? "inf" : "nan"; tmp = (*p->pf == 'F' || *p->pf == 'G' || *p->pf == 'E') ? big : small; if (i < 0) PUT_CHAR('-', p); while (*tmp) { PUT_CHAR (*tmp, p); tmp++; } return 1; } /* %f %F %g %G floating point representation */ static void floating(p, d) struct DATA *p; double d; { char *tmp, *tmp2, *t; int i; if (chkinfnan(p, d, 1) || chkinfnan(p, d, 2)) return; /* already printed nan or inf */ GETLOCALEDATA(decpoint, thoussep, grouping); DEF_PREC(p); d = ROUND(d, p); tmp = dtoa(d, p->precision, &tmp2); t = 0; if ((p->flags & PF_THOUSANDS) && grouping && (t = groupnum (tmp))) tmp = t; /* calculate the padding. 1 for the dot */ p->width = p->width - ((d > 0. && p->justify == RIGHT) ? 1:0) - ((p->flags & PF_SPACE) ? 1:0) - strlen(tmp) - p->precision - 1; PAD_RIGHT(p); PUT_PLUS(d, p, 0.); PUT_SPACE(d, p, 0.); while (*tmp) { /* the integral */ PUT_CHAR(*tmp, p); tmp++; } FREE (t); if (p->precision != 0 || (p->flags & PF_ALTFORM)) PUT_CHAR(decpoint, p); /* put the '.' */ if ((*p->pf == 'g' || *p->pf == 'G') && (p->flags & PF_ALTFORM) == 0) /* smash the trailing zeros unless altform */ for (i = strlen(tmp2) - 1; i >= 0 && tmp2[i] == '0'; i--) tmp2[i] = '\0'; for (; *tmp2; tmp2++) PUT_CHAR(*tmp2, p); /* the fraction */ PAD_LEFT(p); } /* %e %E %g %G exponent representation */ static void exponent(p, d) struct DATA *p; double d; { char *tmp, *tmp2; int j, i; if (chkinfnan(p, d, 1) || chkinfnan(p, d, 2)) return; /* already printed nan or inf */ GETLOCALEDATA(decpoint, thoussep, grouping); DEF_PREC(p); j = log_10(d); d = d / pow_10(j); /* get the Mantissa */ d = ROUND(d, p); tmp = dtoa(d, p->precision, &tmp2); /* 1 for unit, 1 for the '.', 1 for 'e|E', * 1 for '+|-', 2 for 'exp' */ /* calculate how much padding need */ p->width = p->width - ((d > 0. && p->justify == RIGHT) ? 1:0) - ((p->flags & PF_SPACE) ? 1:0) - p->precision - 6; PAD_RIGHT(p); PUT_PLUS(d, p, 0.); PUT_SPACE(d, p, 0.); while (*tmp) { PUT_CHAR(*tmp, p); tmp++; } if (p->precision != 0 || (p->flags & PF_ALTFORM)) PUT_CHAR(decpoint, p); /* the '.' */ if ((*p->pf == 'g' || *p->pf == 'G') && (p->flags & PF_ALTFORM) == 0) /* smash the trailing zeros unless altform */ for (i = strlen(tmp2) - 1; i >= 0 && tmp2[i] == '0'; i--) tmp2[i] = '\0'; for (; *tmp2; tmp2++) PUT_CHAR(*tmp2, p); /* the fraction */ /* the exponent put the 'e|E' */ if (*p->pf == 'g' || *p->pf == 'e') PUT_CHAR('e', p); else PUT_CHAR('E', p); /* the sign of the exp */ if (j > 0) PUT_CHAR('+', p); else { PUT_CHAR('-', p); j = -j; } tmp = itoa(j); /* pad out to at least two spaces. pad with `0' if the exponent is a single digit. */ if (j <= 9) PUT_CHAR('0', p); /* the exponent */ while (*tmp) { PUT_CHAR(*tmp, p); tmp++; } PAD_LEFT(p); } #endif /* Return a new string with the digits in S grouped according to the locale's grouping info and thousands separator. If no grouping should be performed, this returns NULL; the caller needs to check for it. */ static char * groupnum (s) char *s; { char *se, *ret, *re, *g; int len, slen; if (grouping == 0 || *grouping <= 0 || *grouping == CHAR_MAX) return ((char *)NULL); /* find min grouping to size returned string */ for (len = *grouping, g = grouping; *g; g++) if (*g > 0 && *g < len) len = *g; slen = strlen (s); len = slen / len + 1; ret = (char *)xmalloc (slen + len + 1); re = ret + slen + len; *re = '\0'; g = grouping; se = s + slen; len = *g; while (se > s) { *--re = *--se; /* handle `-' inserted by numtoa() and the fmtu* family here. */ if (se > s && se[-1] == '-') continue; /* begin new group. */ if (--len == 0 && se > s) { *--re = thoussep; len = *++g; /* was g++, but that uses first char twice (glibc bug, too) */ if (*g == '\0') len = *--g; /* use previous grouping */ else if (*g == CHAR_MAX) { do *--re = *--se; while (se > s); break; } } } if (re > ret) #ifdef HAVE_MEMMOVE memmove (ret, re, strlen (re) + 1); #else strcpy (ret, re); #endif return ret; } /* initialize the conversion specifiers */ static void init_conv_flag (p) struct DATA *p; { p->flags &= PF_ALLOCBUF; /* preserve PF_ALLOCBUF flag */ p->precision = p->width = NOT_FOUND; p->justify = NOT_FOUND; p->pad = ' '; } static void init_data (p, string, length, format, mode) struct DATA *p; char *string; size_t length; const char *format; int mode; { p->length = length - 1; /* leave room for '\0' */ p->holder = p->base = string; p->pf = format; p->counter = 0; p->flags = (mode == PFM_AS) ? PF_ALLOCBUF : 0; } static int #if defined (__STDC__) vsnprintf_internal(struct DATA *data, char *string, size_t length, const char *format, va_list args) #else vsnprintf_internal(data, string, length, format, args) struct DATA *data; char *string; size_t length; const char *format; va_list args; #endif { double d; /* temporary holder */ #ifdef HAVE_LONG_DOUBLE long double ld; /* for later */ #endif unsigned long ul; #ifdef HAVE_LONG_LONG unsigned long long ull; #endif int state, i, c, n; char *s; #if HANDLE_MULTIBYTE wchar_t *ws; wint_t wc; #endif const char *convstart; /* Sanity check, the string length must be >= 0. C99 actually says that LENGTH can be zero here, in the case of snprintf/vsnprintf (it's never 0 in the case of asprintf/vasprintf), and the return value is the number of characters that would have been written. */ if (length < 0) return -1; if (format == 0) return 0; /* Reset these for each call because the locale might have changed. */ decpoint = thoussep = 0; grouping = 0; for (; c = *(data->pf); data->pf++) { if (c != '%') { PUT_CHAR (c, data); continue; } convstart = data->pf; init_conv_flag (data); /* initialise format flags */ state = 1; for (state = 1; state && *data->pf; ) { c = *(++data->pf); /* fmtend = data->pf */ #if defined (FLOATING_POINT) && defined (HAVE_LONG_DOUBLE) if (data->flags & PF_LONGDBL) { switch (c) { case 'f': case 'F': case 'e': case 'E': case 'g': case 'G': # ifdef HAVE_PRINTF_A_FORMAT case 'a': case 'A': # endif STAR_ARGS (data); ld = GETLDOUBLE (data); ldfallback (data, convstart, data->pf, ld); goto conv_break; } } #endif /* FLOATING_POINT && HAVE_LONG_DOUBLE */ switch (c) { /* Parse format flags */ case '\0': /* a NULL here ? ? bail out */ *data->holder = '\0'; return data->counter; break; case '#': data->flags |= PF_ALTFORM; continue; case '0': data->flags |= PF_ZEROPAD; data->pad = '0'; continue; case '*': if (data->flags & PF_DOT) data->flags |= PF_STAR_P; else data->flags |= PF_STAR_W; continue; case '-': data->flags |= PF_LADJUST; data->justify = LEFT; continue; case ' ': if ((data->flags & PF_PLUS) == 0) data->flags |= PF_SPACE; continue; case '+': data->flags |= PF_PLUS; data->justify = RIGHT; continue; case '\'': data->flags |= PF_THOUSANDS; continue; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': n = 0; do { n = n * 10 + TODIGIT(c); c = *(++data->pf); } while (DIGIT(c)); data->pf--; /* went too far */ if (n < 0) n = 0; if (data->flags & PF_DOT) data->precision = n; else data->width = n; continue; /* optional precision */ case '.': data->flags |= PF_DOT; data->precision = 0; continue; /* length modifiers */ case 'h': data->flags |= (data->flags & PF_SHORTINT) ? PF_SIGNEDCHAR : PF_SHORTINT; continue; case 'l': data->flags |= (data->flags & PF_LONGINT) ? PF_LONGLONG : PF_LONGINT; continue; case 'L': data->flags |= PF_LONGDBL; continue; case 'q': data->flags |= PF_LONGLONG; continue; case 'j': data->flags |= PF_INTMAX_T; SET_SIZE_FLAGS(data, intmax_t); continue; case 'z': data->flags |= PF_SIZE_T; SET_SIZE_FLAGS(data, size_t); continue; case 't': data->flags |= PF_PTRDIFF_T; SET_SIZE_FLAGS(data, ptrdiff_t); continue; /* Conversion specifiers */ #ifdef FLOATING_POINT case 'f': /* float, double */ case 'F': STAR_ARGS(data); d = GETDOUBLE(data); floating(data, d); conv_break: state = 0; break; case 'g': case 'G': STAR_ARGS(data); DEF_PREC(data); d = GETDOUBLE(data); i = log_10(d); /* * for '%g|%G' ANSI: use f if exponent * is in the range or [-4,p] exclusively * else use %e|%E */ if (-4 < i && i < data->precision) { /* reset precision */ data->precision -= i + 1; floating(data, d); } else { /* reduce precision by 1 because of leading digit before decimal point in e format. */ data->precision--; exponent(data, d); } state = 0; break; case 'e': case 'E': /* Exponent double */ STAR_ARGS(data); d = GETDOUBLE(data); exponent(data, d); state = 0; break; # ifdef HAVE_PRINTF_A_FORMAT case 'a': case 'A': STAR_ARGS(data); d = GETDOUBLE(data); dfallback(data, convstart, data->pf, d); state = 0; break; # endif /* HAVE_PRINTF_A_FORMAT */ #endif /* FLOATING_POINT */ case 'U': data->flags |= PF_LONGINT; /* FALLTHROUGH */ case 'u': STAR_ARGS(data); #ifdef HAVE_LONG_LONG if (data->flags & PF_LONGLONG) { ull = GETARG (unsigned long long); lnumber(data, ull, 10); } else #endif { ul = GETUNSIGNED(data); number(data, ul, 10); } state = 0; break; case 'D': data->flags |= PF_LONGINT; /* FALLTHROUGH */ case 'd': /* decimal */ case 'i': STAR_ARGS(data); #ifdef HAVE_LONG_LONG if (data->flags & PF_LONGLONG) { ull = GETARG (long long); lnumber(data, ull, 10); } else #endif { ul = GETSIGNED(data); number(data, ul, 10); } state = 0; break; case 'o': /* octal */ STAR_ARGS(data); #ifdef HAVE_LONG_LONG if (data->flags & PF_LONGLONG) { ull = GETARG (unsigned long long); lnumber(data, ull, 8); } else #endif { ul = GETUNSIGNED(data); number(data, ul, 8); } state = 0; break; case 'x': case 'X': /* hexadecimal */ STAR_ARGS(data); #ifdef HAVE_LONG_LONG if (data->flags & PF_LONGLONG) { ull = GETARG (unsigned long long); lnumber(data, ull, 16); } else #endif { ul = GETUNSIGNED(data); number(data, ul, 16); } state = 0; break; case 'p': STAR_ARGS(data); ul = (unsigned long)GETARG (void *); pointer(data, ul); state = 0; break; #if HANDLE_MULTIBYTE case 'C': data->flags |= PF_LONGINT; /* FALLTHROUGH */ #endif case 'c': /* character */ STAR_ARGS(data); #if HANDLE_MULTIBYTE if (data->flags & PF_LONGINT) { wc = GETARG (wint_t); wchars (data, wc); } else #endif { ul = GETARG (int); PUT_CHAR(ul, data); } state = 0; break; #if HANDLE_MULTIBYTE case 'S': data->flags |= PF_LONGINT; /* FALLTHROUGH */ #endif case 's': /* string */ STAR_ARGS(data); #if HANDLE_MULTIBYTE if (data->flags & PF_LONGINT) { ws = GETARG (wchar_t *); wstrings (data, ws); } else #endif { s = GETARG (char *); strings(data, s); } state = 0; break; case 'n': #ifdef HAVE_LONG_LONG if (data->flags & PF_LONGLONG) *(GETARG (long long *)) = data->counter; else #endif if (data->flags & PF_LONGINT) *(GETARG (long *)) = data->counter; else if (data->flags & PF_SHORTINT) *(GETARG (short *)) = data->counter; else *(GETARG (int *)) = data->counter; state = 0; break; case '%': /* nothing just % */ PUT_CHAR('%', data); state = 0; break; default: /* is this an error ? maybe bail out */ state = 0; break; } /* end switch */ } /* end of `%' for loop */ } /* end of format string for loop */ if (data->length >= 0) *data->holder = '\0'; /* the end ye ! */ return data->counter; } #if defined (FLOATING_POINT) && defined (HAVE_LONG_DOUBLE) /* * Printing floating point numbers accurately is an art. I'm not good * at it. Fall back to sprintf for long double formats. */ static void ldfallback (data, fs, fe, ld) struct DATA *data; const char *fs, *fe; long double ld; { register char *x; char fmtbuf[FALLBACK_FMTSIZE], *obuf; int fl; obuf = (char *)xmalloc(LFALLBACK_BASE + (data->precision < 6 ? 6 : data->precision) + 2); fl = fe - fs + 1; strncpy (fmtbuf, fs, fl); fmtbuf[fl] = '\0'; sprintf (obuf, fmtbuf, ld); for (x = obuf; *x; x++) PUT_CHAR (*x, data); xfree (obuf); } #endif /* FLOATING_POINT && HAVE_LONG_DOUBLE */ #ifdef FLOATING_POINT /* Used for %a, %A if the libc printf supports them. */ static void dfallback (data, fs, fe, d) struct DATA *data; const char *fs, *fe; double d; { register char *x; char fmtbuf[FALLBACK_FMTSIZE], obuf[FALLBACK_BASE]; int fl; fl = fe - fs + 1; strncpy (fmtbuf, fs, fl); fmtbuf[fl] = '\0'; sprintf (obuf, fmtbuf, d); for (x = obuf; *x; x++) PUT_CHAR (*x, data); } #endif /* FLOATING_POINT */ #ifndef HAVE_SNPRINTF int #if defined (__STDC__) vsnprintf(char *string, size_t length, const char *format, va_list args) #else vsnprintf(string, length, format, args) char *string; size_t length; const char *format; va_list args; #endif { struct DATA data; if (string == 0 && length != 0) return 0; init_data (&data, string, length, format, PFM_SN); return (vsnprintf_internal(&data, string, length, format, args)); } int #if defined(PREFER_STDARG) snprintf(char *string, size_t length, const char * format, ...) #else snprintf(string, length, format, va_alist) char *string; size_t length; const char *format; va_dcl #endif { struct DATA data; int rval; va_list args; SH_VA_START(args, format); if (string == 0 && length != 0) return 0; init_data (&data, string, length, format, PFM_SN); rval = vsnprintf_internal (&data, string, length, format, args); va_end(args); return rval; } #endif /* HAVE_SNPRINTF */ #ifndef HAVE_ASPRINTF int #if defined (__STDC__) vasprintf(char **stringp, const char *format, va_list args) #else vasprintf(stringp, format, args) char **stringp; const char *format; va_list args; #endif { struct DATA data; char *string; int r; string = (char *)xmalloc(ASBUFSIZE); init_data (&data, string, ASBUFSIZE, format, PFM_AS); r = vsnprintf_internal(&data, string, ASBUFSIZE, format, args); *stringp = data.base; /* not string in case reallocated */ return r; } int #if defined(PREFER_STDARG) asprintf(char **stringp, const char * format, ...) #else asprintf(stringp, format, va_alist) char **stringp; const char *format; va_dcl #endif { int rval; va_list args; SH_VA_START(args, format); rval = vasprintf (stringp, format, args); va_end(args); return rval; } #endif #endif #ifdef DRIVER static void memory_error_and_abort () { write (2, "out of virtual memory\n", 22); abort (); } static void * xmalloc(bytes) size_t bytes; { void *ret; ret = malloc(bytes); if (ret == 0) memory_error_and_abort (); return ret; } static void * xrealloc (pointer, bytes) void *pointer; size_t bytes; { void *ret; ret = pointer ? realloc(pointer, bytes) : malloc(bytes); if (ret == 0) memory_error_and_abort (); return ret; } static void xfree(x) void *x; { if (x) free (x); } /* set of small tests for snprintf() */ main() { char holder[100]; char *h; int i, si, ai; #ifdef HAVE_LOCALE_H setlocale(LC_ALL, ""); #endif #if 1 si = snprintf((char *)NULL, 0, "abcde\n"); printf("snprintf returns %d with NULL first argument and size of 0\n", si); si = snprintf(holder, 0, "abcde\n"); printf("snprintf returns %d with non-NULL first argument and size of 0\n", si); si = snprintf((char *)NULL, 16, "abcde\n"); printf("snprintf returns %d with NULL first argument and non-zero size\n", si); /* printf("Suite of test for snprintf:\n"); printf("a_format\n"); printf("printf() format\n"); printf("snprintf() format\n\n"); */ /* Checking the field widths */ printf("/%%ld %%ld/, 336, 336\n"); snprintf(holder, sizeof holder, "/%ld %ld/\n", 336, 336); asprintf(&h, "/%ld %ld/\n", 336, 336); printf("/%ld %ld/\n", 336, 336); printf("%s", holder); printf("%s\n", h); printf("/%%d/, 336\n"); snprintf(holder, sizeof holder, "/%d/\n", 336); asprintf(&h, "/%d/\n", 336); printf("/%d/\n", 336); printf("%s", holder); printf("%s\n", h); printf("/%%2d/, 336\n"); snprintf(holder, sizeof holder, "/%2d/\n", 336); asprintf(&h, "/%2d/\n", 336); printf("/%2d/\n", 336); printf("%s", holder); printf("%s\n", h); printf("/%%10d/, 336\n"); snprintf(holder, sizeof holder, "/%10d/\n", 336); asprintf(&h, "/%10d/\n", 336); printf("/%10d/\n", 336); printf("%s", holder); printf("%s\n", h); printf("/%%-10d/, 336\n"); snprintf(holder, sizeof holder, "/%-10d/\n", 336); asprintf(&h, "/%-10d/\n", 336); printf("/%-10d/\n", 336); printf("%s", holder); printf("%s\n", h); /* floating points */ printf("/%%f/, 1234.56\n"); snprintf(holder, sizeof holder, "/%f/\n", 1234.56); asprintf(&h, "/%f/\n", 1234.56); printf("/%f/\n", 1234.56); printf("%s", holder); printf("%s\n", h); printf("/%%e/, 1234.56\n"); snprintf(holder, sizeof holder, "/%e/\n", 1234.56); asprintf(&h, "/%e/\n", 1234.56); printf("/%e/\n", 1234.56); printf("%s", holder); printf("%s\n", h); printf("/%%4.2f/, 1234.56\n"); snprintf(holder, sizeof holder, "/%4.2f/\n", 1234.56); asprintf(&h, "/%4.2f/\n", 1234.56); printf("/%4.2f/\n", 1234.56); printf("%s", holder); printf("%s\n", h); printf("/%%3.1f/, 1234.56\n"); snprintf(holder, sizeof holder, "/%3.1f/\n", 1234.56); asprintf(&h, "/%3.1f/\n", 1234.56); printf("/%3.1f/\n", 1234.56); printf("%s", holder); printf("%s\n", h); printf("/%%10.3f/, 1234.56\n"); snprintf(holder, sizeof holder, "/%10.3f/\n", 1234.56); asprintf(&h, "/%10.3f/\n", 1234.56); printf("/%10.3f/\n", 1234.56); printf("%s", holder); printf("%s\n", h); printf("/%%10.3e/, 1234.56\n"); snprintf(holder, sizeof holder, "/%10.3e/\n", 1234.56); asprintf(&h, "/%10.3e/\n", 1234.56); printf("/%10.3e/\n", 1234.56); printf("%s", holder); printf("%s\n", h); printf("/%%+4.2f/, 1234.56\n"); snprintf(holder, sizeof holder, "/%+4.2f/\n", 1234.56); asprintf(&h, "/%+4.2f/\n", 1234.56); printf("/%+4.2f/\n", 1234.56); printf("%s", holder); printf("%s\n", h); printf("/%%010.2f/, 1234.56\n"); snprintf(holder, sizeof holder, "/%010.2f/\n", 1234.56); asprintf(&h, "/%010.2f/\n", 1234.56); printf("/%010.2f/\n", 1234.56); printf("%s", holder); printf("%s\n", h); #define BLURB "Outstanding acting !" /* strings precisions */ printf("/%%2s/, \"%s\"\n", BLURB); snprintf(holder, sizeof holder, "/%2s/\n", BLURB); asprintf(&h, "/%2s/\n", BLURB); printf("/%2s/\n", BLURB); printf("%s", holder); printf("%s\n", h); printf("/%%22s/ %s\n", BLURB); snprintf(holder, sizeof holder, "/%22s/\n", BLURB); asprintf(&h, "/%22s/\n", BLURB); printf("/%22s/\n", BLURB); printf("%s", holder); printf("%s\n", h); printf("/%%22.5s/ %s\n", BLURB); snprintf(holder, sizeof holder, "/%22.5s/\n", BLURB); asprintf(&h, "/%22.5s/\n", BLURB); printf("/%22.5s/\n", BLURB); printf("%s", holder); printf("%s\n", h); printf("/%%-22.5s/ %s\n", BLURB); snprintf(holder, sizeof holder, "/%-22.5s/\n", BLURB); asprintf(&h, "/%-22.5s/\n", BLURB); printf("/%-22.5s/\n", BLURB); printf("%s", holder); printf("%s\n", h); /* see some flags */ printf("%%x %%X %%#x, 31, 31, 31\n"); snprintf(holder, sizeof holder, "%x %X %#x\n", 31, 31, 31); asprintf(&h, "%x %X %#x\n", 31, 31, 31); printf("%x %X %#x\n", 31, 31, 31); printf("%s", holder); printf("%s\n", h); printf("**%%d**%% d**%% d**, 42, 42, -42\n"); snprintf(holder, sizeof holder, "**%d**% d**% d**\n", 42, 42, -42); asprintf(&h, "**%d**% d**% d**\n", 42, 42, -42); printf("**%d**% d**% d**\n", 42, 42, -42); printf("%s", holder); printf("%s\n", h); /* other flags */ printf("/%%g/, 31.4\n"); snprintf(holder, sizeof holder, "/%g/\n", 31.4); asprintf(&h, "/%g/\n", 31.4); printf("/%g/\n", 31.4); printf("%s", holder); printf("%s\n", h); printf("/%%.6g/, 31.4\n"); snprintf(holder, sizeof holder, "/%.6g/\n", 31.4); asprintf(&h, "/%.6g/\n", 31.4); printf("/%.6g/\n", 31.4); printf("%s", holder); printf("%s\n", h); printf("/%%.1G/, 31.4\n"); snprintf(holder, sizeof holder, "/%.1G/\n", 31.4); asprintf(&h, "/%.1G/\n", 31.4); printf("/%.1G/\n", 31.4); printf("%s", holder); printf("%s\n", h); printf("/%%.1G/, 3100000000.4\n"); snprintf(holder, sizeof holder, "/%.1G/\n", 3100000000.4); asprintf(&h, "/%.1G/\n", 3100000000.4); printf("/%.1G/\n", 3100000000.4); printf("%s", holder); printf("%s\n", h); printf("abc%%n\n"); printf("abc%n", &i); printf("%d\n", i); snprintf(holder, sizeof holder, "abc%n", &i); printf("%s", holder); printf("%d\n\n", i); asprintf(&h, "abc%n", &i); printf("%s", h); printf("%d\n\n", i); printf("%%*.*s --> 10.10\n"); snprintf(holder, sizeof holder, "%*.*s\n", 10, 10, BLURB); asprintf(&h, "%*.*s\n", 10, 10, BLURB); printf("%*.*s\n", 10, 10, BLURB); printf("%s", holder); printf("%s\n", h); printf("%%%%%%%%\n"); snprintf(holder, sizeof holder, "%%%%\n"); asprintf(&h, "%%%%\n"); printf("%%%%\n"); printf("%s", holder); printf("%s\n", h); #define BIG "Hello this is a too big string for the buffer" /* printf("A buffer to small of 10, trying to put this:\n");*/ printf("<%%>, %s\n", BIG); i = snprintf(holder, 10, "%s\n", BIG); i = asprintf(&h, "%s", BIG); printf("<%s>\n", BIG); printf("<%s>\n", holder); printf("<%s>\n\n", h); printf ("<%%p> vsnprintf\n"); i = snprintf(holder, 100, "%p", vsnprintf); i = asprintf(&h, "%p", vsnprintf); printf("<%p>\n", vsnprintf); printf("<%s>\n", holder); printf("<%s>\n\n", h); printf ("<%%lu> LONG_MAX+1\n"); i = snprintf(holder, 100, "%lu", (unsigned long)(LONG_MAX)+1); i = asprintf(&h, "%lu", (unsigned long)(LONG_MAX)+1); printf("<%lu>\n", (unsigned long)(LONG_MAX)+1); printf("<%s>\n", holder); printf("<%s>\n\n", h); #ifdef HAVE_LONG_LONG printf ("<%%llu> LLONG_MAX+1\n"); i = snprintf(holder, 100, "%llu", (unsigned long long)(LLONG_MAX)+1); i = asprintf(&h, "%llu", (unsigned long long)(LLONG_MAX)+1); printf("<%llu>\n", (unsigned long long)(LLONG_MAX)+1); printf("<%s>\n", holder); printf("<%s>\n\n", h); #endif #ifdef HAVE_LONG_DOUBLE printf ("<%%6.2LE> 42.42\n"); i = snprintf(holder, 100, "%6.2LE", (long double)42.42); i = asprintf(&h, "%6.2LE", (long double)42.42); printf ("<%6.2LE>\n", (long double)42.42); printf ("<%s>\n", holder); printf ("<%s>\n\n", h); #endif #ifdef HAVE_PRINTF_A_FORMAT printf ("<%%6.2A> 42.42\n"); i = snprintf(holder, 100, "%6.2A", 42.42); i = asprintf(&h, "%6.2A", 42.42); printf ("<%6.2A>\n", 42.42); printf ("<%s>\n", holder); printf ("<%s>\n\n", h); printf ("<%%6.2LA> 42.42\n"); i = snprintf(holder, 100, "%6.2LA", (long double)42.42); i = asprintf(&h, "%6.2LA", (long double)42.42); printf ("<%6.2LA>\n", (long double)42.42); printf ("<%s>\n", holder); printf ("<%s>\n\n", h); #endif printf ("<%%.10240f> DBL_MAX\n"); si = snprintf(holder, 100, "%.10240f", DBL_MAX); ai = asprintf(&h, "%.10240f", DBL_MAX); printf ("<%.10240f>\n", DBL_MAX); printf ("<%d> <%s>\n", si, holder); printf ("<%d> <%s>\n\n", ai, h); printf ("<%%.10240Lf> LDBL_MAX\n"); si = snprintf(holder, 100, "%.10240Lf", (long double)LDBL_MAX); ai = asprintf(&h, "%.10240Lf", (long double)LDBL_MAX); printf ("<%.10240Lf>\n", (long double)LDBL_MAX); printf ("<%d> <%s>\n", si, holder); printf ("<%d> <%s>\n\n", ai, h); /* huh? */ printf("/%%g/, 421.2345\n"); snprintf(holder, sizeof holder, "/%g/\n", 421.2345); asprintf(&h, "/%g/\n", 421.2345); printf("/%g/\n", 421.2345); printf("%s", holder); printf("%s\n", h); printf("/%%g/, 4214.2345\n"); snprintf(holder, sizeof holder, "/%g/\n", 4214.2345); asprintf(&h, "/%g/\n", 4214.2345); printf("/%g/\n", 4214.2345); printf("%s", holder); printf("%s\n", h); printf("/%%.5g/, 4214.2345\n"); snprintf(holder, sizeof holder, "/%.5g/\n", 4214.2345); asprintf(&h, "/%.5g/\n", 4214.2345); printf("/%.5g/\n", 4214.2345); printf("%s", holder); printf("%s\n", h); printf("/%%.4g/, 4214.2345\n"); snprintf(holder, sizeof holder, "/%.4g/\n", 4214.2345); asprintf(&h, "/%.4g/\n", 4214.2345); printf("/%.4g/\n", 4214.2345); printf("%s", holder); printf("%s\n", h); printf("/%%'ld %%'ld/, 12345, 1234567\n"); snprintf(holder, sizeof holder, "/%'ld %'ld/\n", 12345, 1234567); asprintf(&h, "/%'ld %'ld/\n", 12345, 1234567); printf("/%'ld %'ld/\n", 12345, 1234567); printf("%s", holder); printf("%s\n", h); printf("/%%'ld %%'ld/, 336, 3336\n"); snprintf(holder, sizeof holder, "/%'ld %'ld/\n", 336, 3336); asprintf(&h, "/%'ld %'ld/\n", 336, 3336); printf("/%'ld %'ld/\n", 336, 3336); printf("%s", holder); printf("%s\n", h); printf("/%%'ld %%'ld/, -42786, -142786\n"); snprintf(holder, sizeof holder, "/%'ld %'ld/\n", -42786, -142786); asprintf(&h, "/%'ld %'ld/\n", -42786, -142786); printf("/%'ld %'ld/\n", -42786, -142786); printf("%s", holder); printf("%s\n", h); printf("/%%'f %%'f/, 421.2345, 421234.56789\n"); snprintf(holder, sizeof holder, "/%'f %'f/\n", 421.2345, 421234.56789); asprintf(&h, "/%'f %'f/\n", 421.2345, 421234.56789); printf("/%'f %'f/\n", 421.2345, 421234.56789); printf("%s", holder); printf("%s\n", h); printf("/%%'f %%'f/, -421.2345, -421234.56789\n"); snprintf(holder, sizeof holder, "/%'f %'f/\n", -421.2345, -421234.56789); asprintf(&h, "/%'f %'f/\n", -421.2345, -421234.56789); printf("/%'f %'f/\n", -421.2345, -421234.56789); printf("%s", holder); printf("%s\n", h); printf("/%%'g %%'g/, 421.2345, 421234.56789\n"); snprintf(holder, sizeof holder, "/%'g %'g/\n", 421.2345, 421234.56789); asprintf(&h, "/%'g %'g/\n", 421.2345, 421234.56789); printf("/%'g %'g/\n", 421.2345, 421234.56789); printf("%s", holder); printf("%s\n", h); printf("/%%'g %%'g/, -421.2345, -421234.56789\n"); snprintf(holder, sizeof holder, "/%'g %'g/\n", -421.2345, -421234.56789); asprintf(&h, "/%'g %'g/\n", -421.2345, -421234.56789); printf("/%'g %'g/\n", -421.2345, -421234.56789); printf("%s", holder); printf("%s\n", h); #endif printf("/%%'g/, 4213455.8392\n"); snprintf(holder, sizeof holder, "/%'g/\n", 4213455.8392); asprintf(&h, "/%'g/\n", 4213455.8392); printf("/%'g/\n", 4213455.8392); printf("%s", holder); printf("%s\n", h); exit (0); } #endif
SPDX-FileCopyrightText: 2021 fosslinux <fosslinux@aussies.space> SPDX-License-Identifier: GPL-2.0-or-later int name, namelen; is wrong for mes libc, it is char* name, so we modify tinycc to reflect this. diff --git lib/sh/oslib.c lib/sh/oslib.c index 90d7be9..37fdf2a 100644 --- lib/sh/oslib.c +++ lib/sh/oslib.c @@ -192,8 +192,7 @@ bzero (s, n) # include <sys/utsname.h> int gethostname (name, namelen) - char *name; - int namelen; + char *name; int namelen; { int i; struct utsname ut; @@ -209,7 +208,7 @@ gethostname (name, namelen) # else /* !HAVE_UNAME */ int gethostname (name, namelen) - int name, namelen; + char* name; int namelen; { strncpy (name, "unknown", namelen); name[namelen] = '\0';
/* oslib.c - functions present only in some unix versions. */ /* Copyright (C) 1995 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA */ #include <config.h> #include <bashtypes.h> #ifndef _MINIX # include <sys/param.h> #endif #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #if defined (HAVE_LIMITS_H) # include <limits.h> #endif #include <posixstat.h> #include <filecntl.h> #include <bashansi.h> #include <stdio.h> #include <errno.h> #include <chartypes.h> #include <shell.h> #if !defined (errno) extern int errno; #endif /* !errno */ /* Make the functions strchr and strrchr if they do not exist. */ #if !defined (HAVE_STRCHR) char * strchr (string, c) char *string; int c; { register char *s; for (s = string; s && *s; s++) if (*s == c) return (s); return ((char *) NULL); } char * strrchr (string, c) char *string; int c; { register char *s, *t; for (s = string, t = (char *)NULL; s && *s; s++) if (*s == c) t = s; return (t); } #endif /* !HAVE_STRCHR */ #if !defined (HAVE_DUP2) || defined (DUP2_BROKEN) /* Replacement for dup2 (), for those systems which either don't have it, or supply one with broken behaviour. */ int dup2 (fd1, fd2) int fd1, fd2; { int saved_errno, r; /* If FD1 is not a valid file descriptor, then return immediately with an error. */ if (fcntl (fd1, F_GETFL, 0) == -1) return (-1); if (fd2 < 0 || fd2 >= getdtablesize ()) { errno = EBADF; return (-1); } if (fd1 == fd2) return (0); saved_errno = errno; (void) close (fd2); r = fcntl (fd1, F_DUPFD, fd2); if (r >= 0) errno = saved_errno; else if (errno == EINVAL) errno = EBADF; /* Force the new file descriptor to remain open across exec () calls. */ SET_OPEN_ON_EXEC (fd2); return (r); } #endif /* !HAVE_DUP2 */ /* * Return the total number of available file descriptors. * * On some systems, like 4.2BSD and its descendents, there is a system call * that returns the size of the descriptor table: getdtablesize(). There are * lots of ways to emulate this on non-BSD systems. * * On System V.3, this can be obtained via a call to ulimit: * return (ulimit(4, 0L)); * * On other System V systems, NOFILE is defined in /usr/include/sys/param.h * (this is what we assume below), so we can simply use it: * return (NOFILE); * * On POSIX systems, there are specific functions for retrieving various * configuration parameters: * return (sysconf(_SC_OPEN_MAX)); * */ #if !defined (HAVE_GETDTABLESIZE) int getdtablesize () { # if defined (_POSIX_VERSION) && defined (HAVE_SYSCONF) && defined (_SC_OPEN_MAX) return (sysconf(_SC_OPEN_MAX)); /* Posix systems use sysconf */ # else /* ! (_POSIX_VERSION && HAVE_SYSCONF && _SC_OPEN_MAX) */ # if defined (ULIMIT_MAXFDS) return (ulimit (4, 0L)); /* System V.3 systems use ulimit(4, 0L) */ # else /* !ULIMIT_MAXFDS */ # if defined (NOFILE) /* Other systems use NOFILE */ return (NOFILE); # else /* !NOFILE */ return (20); /* XXX - traditional value is 20 */ # endif /* !NOFILE */ # endif /* !ULIMIT_MAXFDS */ # endif /* ! (_POSIX_VERSION && _SC_OPEN_MAX) */ } #endif /* !HAVE_GETDTABLESIZE */ #if !defined (HAVE_BCOPY) # if defined (bcopy) # undef bcopy # endif void bcopy (s,d,n) char *d, *s; int n; { FASTCOPY (s, d, n); } #endif /* !HAVE_BCOPY */ #if !defined (HAVE_BZERO) # if defined (bzero) # undef bzero # endif void bzero (s, n) char *s; int n; { register int i; register char *r; for (i = 0, r = s; i < n; i++) *r++ = '\0'; } #endif #if !defined (HAVE_GETHOSTNAME) # if defined (HAVE_UNAME) # include <sys/utsname.h> int gethostname (name, namelen) char *name; int namelen; { int i; struct utsname ut; --namelen; uname (&ut); i = strlen (ut.nodename) + 1; strncpy (name, ut.nodename, i < namelen ? i : namelen); name[namelen] = '\0'; return (0); } # else /* !HAVE_UNAME */ int gethostname (name, namelen) int name, namelen; { strncpy (name, "unknown", namelen); name[namelen] = '\0'; return 0; } # endif /* !HAVE_UNAME */ #endif /* !HAVE_GETHOSTNAME */ #if !defined (HAVE_KILLPG) int killpg (pgrp, sig) pid_t pgrp; int sig; { return (kill (-pgrp, sig)); } #endif /* !HAVE_KILLPG */ #if !defined (HAVE_MKFIFO) && defined (PROCESS_SUBSTITUTION) int mkfifo (path, mode) char *path; int mode; { #if defined (S_IFIFO) return (mknod (path, (mode | S_IFIFO), 0)); #else /* !S_IFIFO */ return (-1); #endif /* !S_IFIFO */ } #endif /* !HAVE_MKFIFO && PROCESS_SUBSTITUTION */ #define DEFAULT_MAXGROUPS 64 int getmaxgroups () { static int maxgroups = -1; if (maxgroups > 0) return maxgroups; #if defined (HAVE_SYSCONF) && defined (_SC_NGROUPS_MAX) maxgroups = sysconf (_SC_NGROUPS_MAX); #else # if defined (NGROUPS_MAX) maxgroups = NGROUPS_MAX; # else /* !NGROUPS_MAX */ # if defined (NGROUPS) maxgroups = NGROUPS; # else /* !NGROUPS */ maxgroups = DEFAULT_MAXGROUPS; # endif /* !NGROUPS */ # endif /* !NGROUPS_MAX */ #endif /* !HAVE_SYSCONF || !SC_NGROUPS_MAX */ if (maxgroups <= 0) maxgroups = DEFAULT_MAXGROUPS; return maxgroups; } long getmaxchild () { static long maxchild = -1L; if (maxchild > 0) return maxchild; #if defined (HAVE_SYSCONF) && defined (_SC_CHILD_MAX) maxchild = sysconf (_SC_CHILD_MAX); #else # if defined (CHILD_MAX) maxchild = CHILD_MAX; # else # if defined (MAXUPRC) maxchild = MAXUPRC; # endif /* MAXUPRC */ # endif /* CHILD_MAX */ #endif /* !HAVE_SYSCONF || !_SC_CHILD_MAX */ return (maxchild); }
SPDX-FileCopyrightText: 2021 fosslinux <fosslinux@aussies.space> SPDX-License-Identifier: GPL-2.0-or-later We don't actually want any of these things, which should really be hidden behind the ifdefs given here to disable them when they are not being used (as we do). --- execute_cmd.c 2021-01-15 09:38:55.730307635 +1100 +++ execute_cmd.c 2021-01-15 09:43:41.046896754 +1100 @@ -286,12 +286,18 @@ { if (currently_executing_command->type == cm_simple) return currently_executing_command->value.Simple->line; +#ifdef COND_COMMAND else if (currently_executing_command->type == cm_cond) return currently_executing_command->value.Cond->line; +#endif +#ifdef DPAREN_ARITHMETIC else if (currently_executing_command->type == cm_arith) return currently_executing_command->value.Arith->line; +#endif +#ifdef ARITH_FOR_COMMAND else if (currently_executing_command->type == cm_arith_for) return currently_executing_command->value.ArithFor->line; +#endif else return line_number; }
/* execute_command.c -- Execute a COMMAND structure. */ /* Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if !defined (__GNUC__) && !defined (HAVE_ALLOCA_H) && defined (_AIX) #pragma alloca #endif /* _AIX && RISC6000 && !__GNUC__ */ #include <stdio.h> #include "chartypes.h" #include "bashtypes.h" #ifndef _MINIX # include <sys/file.h> #endif #include "filecntl.h" #include "posixstat.h" #include <signal.h> #ifndef _MINIX # include <sys/param.h> #endif #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "posixtime.h" #if defined (HAVE_SYS_RESOURCE_H) && !defined (RLIMTYPE) # include <sys/resource.h> #endif #if defined (HAVE_SYS_TIMES_H) && defined (HAVE_TIMES) # include <sys/times.h> #endif #include <errno.h> #if !defined (errno) extern int errno; #endif #include "bashansi.h" #include "memalloc.h" #include "shell.h" #include <y.tab.h> /* use <...> so we pick it up from the build directory */ #include "flags.h" #include "builtins.h" #include "hashlib.h" #include "jobs.h" #include "execute_cmd.h" #include "findcmd.h" #include "redir.h" #include "trap.h" #include "pathexp.h" #include "hashcmd.h" #if defined (COND_COMMAND) # include "test.h" #endif #include "builtins/common.h" #include "builtins/builtext.h" /* list of builtins */ #include <glob/strmatch.h> #include <tilde/tilde.h> #if defined (BUFFERED_INPUT) # include "input.h" #endif #if defined (ALIAS) # include "alias.h" #endif #if defined (HISTORY) # include "bashhist.h" #endif extern int posixly_correct; extern int breaking, continuing, loop_level; extern int expand_aliases; extern int parse_and_execute_level, running_trap, trap_line_number; extern int command_string_index, line_number; extern int dot_found_in_search; extern int already_making_children; extern char *the_printed_command, *shell_name; extern pid_t last_command_subst_pid; extern sh_builtin_func_t *last_shell_builtin, *this_shell_builtin; extern char **subshell_argv, **subshell_envp; extern int subshell_argc; #if 0 extern char *glob_argv_flags; #endif extern int close __P((int)); /* Static functions defined and used in this file. */ static void close_pipes __P((int, int)); static void do_piping __P((int, int)); static void bind_lastarg __P((char *)); static int shell_control_structure __P((enum command_type)); static void cleanup_redirects __P((REDIRECT *)); #if defined (JOB_CONTROL) static int restore_signal_mask __P((sigset_t *)); #endif static void async_redirect_stdin __P((void)); static int builtin_status __P((int)); static int execute_for_command __P((FOR_COM *)); #if defined (SELECT_COMMAND) static int print_index_and_element __P((int, int, WORD_LIST *)); static void indent __P((int, int)); static void print_select_list __P((WORD_LIST *, int, int, int)); static char *select_query __P((WORD_LIST *, int, char *, int)); static int execute_select_command __P((SELECT_COM *)); #endif #if defined (DPAREN_ARITHMETIC) static int execute_arith_command __P((ARITH_COM *)); #endif #if defined (COND_COMMAND) static int execute_cond_node __P((COND_COM *)); static int execute_cond_command __P((COND_COM *)); #endif #if defined (COMMAND_TIMING) static int mkfmt __P((char *, int, int, time_t, int)); static void print_formatted_time __P((FILE *, char *, time_t, int, time_t, int, time_t, int, int)); static int time_command __P((COMMAND *, int, int, int, struct fd_bitmap *)); #endif #if defined (ARITH_FOR_COMMAND) static intmax_t eval_arith_for_expr __P((WORD_LIST *, int *)); static int execute_arith_for_command __P((ARITH_FOR_COM *)); #endif static int execute_case_command __P((CASE_COM *)); static int execute_while_command __P((WHILE_COM *)); static int execute_until_command __P((WHILE_COM *)); static int execute_while_or_until __P((WHILE_COM *, int)); static int execute_if_command __P((IF_COM *)); static int execute_null_command __P((REDIRECT *, int, int, int, pid_t)); static void fix_assignment_words __P((WORD_LIST *)); static int execute_simple_command __P((SIMPLE_COM *, int, int, int, struct fd_bitmap *)); static int execute_builtin __P((sh_builtin_func_t *, WORD_LIST *, int, int)); static int execute_function __P((SHELL_VAR *, WORD_LIST *, int, struct fd_bitmap *, int, int)); static int execute_builtin_or_function __P((WORD_LIST *, sh_builtin_func_t *, SHELL_VAR *, REDIRECT *, struct fd_bitmap *, int)); static void execute_subshell_builtin_or_function __P((WORD_LIST *, REDIRECT *, sh_builtin_func_t *, SHELL_VAR *, int, int, int, struct fd_bitmap *, int)); static void execute_disk_command __P((WORD_LIST *, REDIRECT *, char *, int, int, int, struct fd_bitmap *, int)); static char *getinterp __P((char *, int, int *)); static void initialize_subshell __P((void)); static int execute_in_subshell __P((COMMAND *, int, int, int, struct fd_bitmap *)); static int execute_pipeline __P((COMMAND *, int, int, int, struct fd_bitmap *)); static int execute_connection __P((COMMAND *, int, int, int, struct fd_bitmap *)); static int execute_intern_function __P((WORD_DESC *, COMMAND *)); /* The line number that the currently executing function starts on. */ static int function_line_number; /* Set to 1 if fd 0 was the subject of redirection to a subshell. Global so that reader_loop can set it to zero before executing a command. */ int stdin_redir; /* The name of the command that is currently being executed. `test' needs this, for example. */ char *this_command_name; static COMMAND *currently_executing_command; struct stat SB; /* used for debugging */ static int special_builtin_failed; /* For catching RETURN in a function. */ int return_catch_flag; int return_catch_value; procenv_t return_catch; /* The value returned by the last synchronous command. */ int last_command_exit_value; /* The list of redirections to perform which will undo the redirections that I made in the shell. */ REDIRECT *redirection_undo_list = (REDIRECT *)NULL; /* The list of redirections to perform which will undo the internal redirections performed by the `exec' builtin. These are redirections that must be undone even when exec discards redirection_undo_list. */ REDIRECT *exec_redirection_undo_list = (REDIRECT *)NULL; /* Non-zero if we have just forked and are currently running in a subshell environment. */ int subshell_environment; /* Currently-executing shell function. */ SHELL_VAR *this_shell_function; struct fd_bitmap *current_fds_to_close = (struct fd_bitmap *)NULL; #define FD_BITMAP_DEFAULT_SIZE 32 /* Functions to allocate and deallocate the structures used to pass information from the shell to its children about file descriptors to close. */ struct fd_bitmap * new_fd_bitmap (size) int size; { struct fd_bitmap *ret; ret = (struct fd_bitmap *)xmalloc (sizeof (struct fd_bitmap)); ret->size = size; if (size) { ret->bitmap = (char *)xmalloc (size); memset (ret->bitmap, '\0', size); } else ret->bitmap = (char *)NULL; return (ret); } void dispose_fd_bitmap (fdbp) struct fd_bitmap *fdbp; { FREE (fdbp->bitmap); free (fdbp); } void close_fd_bitmap (fdbp) struct fd_bitmap *fdbp; { register int i; if (fdbp) { for (i = 0; i < fdbp->size; i++) if (fdbp->bitmap[i]) { close (i); fdbp->bitmap[i] = 0; } } } /* Return the line number of the currently executing command. */ int executing_line_number () { if (executing && (variable_context == 0 || interactive_shell == 0) && currently_executing_command) { if (currently_executing_command->type == cm_simple) return currently_executing_command->value.Simple->line; else if (currently_executing_command->type == cm_cond) return currently_executing_command->value.Cond->line; else if (currently_executing_command->type == cm_arith) return currently_executing_command->value.Arith->line; else if (currently_executing_command->type == cm_arith_for) return currently_executing_command->value.ArithFor->line; else return line_number; } else if (running_trap) return trap_line_number; else return line_number; } /* Execute the command passed in COMMAND. COMMAND is exactly what read_command () places into GLOBAL_COMMAND. See "command.h" for the details of the command structure. EXECUTION_SUCCESS or EXECUTION_FAILURE are the only possible return values. Executing a command with nothing in it returns EXECUTION_SUCCESS. */ int execute_command (command) COMMAND *command; { struct fd_bitmap *bitmap; int result; current_fds_to_close = (struct fd_bitmap *)NULL; bitmap = new_fd_bitmap (FD_BITMAP_DEFAULT_SIZE); begin_unwind_frame ("execute-command"); add_unwind_protect (dispose_fd_bitmap, (char *)bitmap); /* Just do the command, but not asynchronously. */ result = execute_command_internal (command, 0, NO_PIPE, NO_PIPE, bitmap); dispose_fd_bitmap (bitmap); discard_unwind_frame ("execute-command"); #if defined (PROCESS_SUBSTITUTION) /* don't unlink fifos if we're in a shell function; wait until the function returns. */ if (variable_context == 0) unlink_fifo_list (); #endif /* PROCESS_SUBSTITUTION */ return (result); } /* Return 1 if TYPE is a shell control structure type. */ static int shell_control_structure (type) enum command_type type; { switch (type) { case cm_for: #if defined (ARITH_FOR_COMMAND) case cm_arith_for: #endif #if defined (SELECT_COMMAND) case cm_select: #endif #if defined (DPAREN_ARITHMETIC) case cm_arith: #endif #if defined (COND_COMMAND) case cm_cond: #endif case cm_case: case cm_while: case cm_until: case cm_if: case cm_group: return (1); default: return (0); } } /* A function to use to unwind_protect the redirection undo list for loops. */ static void cleanup_redirects (list) REDIRECT *list; { do_redirections (list, 1, 0, 0); dispose_redirects (list); } #if 0 /* Function to unwind_protect the redirections for functions and builtins. */ static void cleanup_func_redirects (list) REDIRECT *list; { do_redirections (list, 1, 0, 0); } #endif void dispose_exec_redirects () { if (exec_redirection_undo_list) { dispose_redirects (exec_redirection_undo_list); exec_redirection_undo_list = (REDIRECT *)NULL; } } #if defined (JOB_CONTROL) /* A function to restore the signal mask to its proper value when the shell is interrupted or errors occur while creating a pipeline. */ static int restore_signal_mask (set) sigset_t *set; { return (sigprocmask (SIG_SETMASK, set, (sigset_t *)NULL)); } #endif /* JOB_CONTROL */ #ifdef DEBUG /* A debugging function that can be called from gdb, for instance. */ void open_files () { register int i; int f, fd_table_size; fd_table_size = getdtablesize (); fprintf (stderr, "pid %ld open files:", (long)getpid ()); for (i = 3; i < fd_table_size; i++) { if ((f = fcntl (i, F_GETFD, 0)) != -1) fprintf (stderr, " %d (%s)", i, f ? "close" : "open"); } fprintf (stderr, "\n"); } #endif static void async_redirect_stdin () { int fd; fd = open ("/dev/null", O_RDONLY); if (fd > 0) { dup2 (fd, 0); close (fd); } else if (fd < 0) internal_error ("cannot redirect standard input from /dev/null: %s", strerror (errno)); } #define DESCRIBE_PID(pid) do { if (interactive) describe_pid (pid); } while (0) /* Execute the command passed in COMMAND, perhaps doing it asynchrounously. COMMAND is exactly what read_command () places into GLOBAL_COMMAND. ASYNCHROUNOUS, if non-zero, says to do this command in the background. PIPE_IN and PIPE_OUT are file descriptors saying where input comes from and where it goes. They can have the value of NO_PIPE, which means I/O is stdin/stdout. FDS_TO_CLOSE is a list of file descriptors to close once the child has been forked. This list often contains the unusable sides of pipes, etc. EXECUTION_SUCCESS or EXECUTION_FAILURE are the only possible return values. Executing a command with nothing in it returns EXECUTION_SUCCESS. */ int execute_command_internal (command, asynchronous, pipe_in, pipe_out, fds_to_close) COMMAND *command; int asynchronous; int pipe_in, pipe_out; struct fd_bitmap *fds_to_close; { int exec_result, invert, ignore_return, was_error_trap; REDIRECT *my_undo_list, *exec_undo_list; volatile pid_t last_pid; if (command == 0 || breaking || continuing || read_but_dont_execute) return (EXECUTION_SUCCESS); run_pending_traps (); if (running_trap == 0) currently_executing_command = command; invert = (command->flags & CMD_INVERT_RETURN) != 0; /* If we're inverting the return value and `set -e' has been executed, we don't want a failing command to inadvertently cause the shell to exit. */ if (exit_immediately_on_error && invert) /* XXX */ command->flags |= CMD_IGNORE_RETURN; /* XXX */ exec_result = EXECUTION_SUCCESS; /* If a command was being explicitly run in a subshell, or if it is a shell control-structure, and it has a pipe, then we do the command in a subshell. */ if (command->type == cm_subshell && (command->flags & CMD_NO_FORK)) return (execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close)); if (command->type == cm_subshell || (command->flags & (CMD_WANT_SUBSHELL|CMD_FORCE_SUBSHELL)) || (shell_control_structure (command->type) && (pipe_out != NO_PIPE || pipe_in != NO_PIPE || asynchronous))) { pid_t paren_pid; /* Fork a subshell, turn off the subshell bit, turn off job control and call execute_command () on the command again. */ paren_pid = make_child (savestring (make_command_string (command)), asynchronous); if (paren_pid == 0) exit (execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close)); /* NOTREACHED */ else { close_pipes (pipe_in, pipe_out); #if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD) unlink_fifo_list (); #endif /* If we are part of a pipeline, and not the end of the pipeline, then we should simply return and let the last command in the pipe be waited for. If we are not in a pipeline, or are the last command in the pipeline, then we wait for the subshell and return its exit status as usual. */ if (pipe_out != NO_PIPE) return (EXECUTION_SUCCESS); stop_pipeline (asynchronous, (COMMAND *)NULL); if (asynchronous == 0) { last_command_exit_value = wait_for (paren_pid); /* If we have to, invert the return value. */ if (invert) exec_result = ((last_command_exit_value == EXECUTION_SUCCESS) ? EXECUTION_FAILURE : EXECUTION_SUCCESS); else exec_result = last_command_exit_value; return (last_command_exit_value = exec_result); } else { DESCRIBE_PID (paren_pid); run_pending_traps (); return (EXECUTION_SUCCESS); } } } #if defined (COMMAND_TIMING) if (command->flags & CMD_TIME_PIPELINE) { if (asynchronous) { command->flags |= CMD_FORCE_SUBSHELL; exec_result = execute_command_internal (command, 1, pipe_in, pipe_out, fds_to_close); } else { exec_result = time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close); if (running_trap == 0) currently_executing_command = (COMMAND *)NULL; } return (exec_result); } #endif /* COMMAND_TIMING */ if (shell_control_structure (command->type) && command->redirects) stdin_redir = stdin_redirects (command->redirects); /* Handle WHILE FOR CASE etc. with redirections. (Also '&' input redirection.) */ if (do_redirections (command->redirects, 1, 1, 0) != 0) { cleanup_redirects (redirection_undo_list); redirection_undo_list = (REDIRECT *)NULL; dispose_exec_redirects (); return (EXECUTION_FAILURE); } if (redirection_undo_list) { my_undo_list = (REDIRECT *)copy_redirects (redirection_undo_list); dispose_redirects (redirection_undo_list); redirection_undo_list = (REDIRECT *)NULL; } else my_undo_list = (REDIRECT *)NULL; if (exec_redirection_undo_list) { exec_undo_list = (REDIRECT *)copy_redirects (exec_redirection_undo_list); dispose_redirects (exec_redirection_undo_list); exec_redirection_undo_list = (REDIRECT *)NULL; } else exec_undo_list = (REDIRECT *)NULL; if (my_undo_list || exec_undo_list) begin_unwind_frame ("loop_redirections"); if (my_undo_list) add_unwind_protect ((Function *)cleanup_redirects, my_undo_list); if (exec_undo_list) add_unwind_protect ((Function *)dispose_redirects, exec_undo_list); ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0; QUIT; switch (command->type) { case cm_simple: { /* We can't rely on this variable retaining its value across a call to execute_simple_command if a longjmp occurs as the result of a `return' builtin. This is true for sure with gcc. */ last_pid = last_made_pid; was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0; if (ignore_return && command->value.Simple) command->value.Simple->flags |= CMD_IGNORE_RETURN; if (command->flags & CMD_STDIN_REDIR) command->value.Simple->flags |= CMD_STDIN_REDIR; exec_result = execute_simple_command (command->value.Simple, pipe_in, pipe_out, asynchronous, fds_to_close); /* The temporary environment should be used for only the simple command immediately following its definition. */ dispose_used_env_vars (); #if (defined (ultrix) && defined (mips)) || defined (C_ALLOCA) /* Reclaim memory allocated with alloca () on machines which may be using the alloca emulation code. */ (void) alloca (0); #endif /* (ultrix && mips) || C_ALLOCA */ /* If we forked to do the command, then we must wait_for () the child. */ /* XXX - this is something to watch out for if there are problems when the shell is compiled without job control. */ if (already_making_children && pipe_out == NO_PIPE && last_pid != last_made_pid) { stop_pipeline (asynchronous, (COMMAND *)NULL); if (asynchronous) { DESCRIBE_PID (last_made_pid); } else #if !defined (JOB_CONTROL) /* Do not wait for asynchronous processes started from startup files. */ if (last_made_pid != last_asynchronous_pid) #endif /* When executing a shell function that executes other commands, this causes the last simple command in the function to be waited for twice. */ exec_result = wait_for (last_made_pid); #if defined (RECYCLES_PIDS) /* LynxOS, for one, recycles pids very quickly -- so quickly that a new process may have the same pid as the last one created. This has been reported to fix the problem. */ if (exec_result == 0) last_made_pid = NO_PID; #endif } } if (was_error_trap && ignore_return == 0 && invert == 0 && exec_result != EXECUTION_SUCCESS) { last_command_exit_value = exec_result; run_error_trap (); } if (ignore_return == 0 && invert == 0 && ((posixly_correct && interactive == 0 && special_builtin_failed) || (exit_immediately_on_error && (exec_result != EXECUTION_SUCCESS)))) { last_command_exit_value = exec_result; run_pending_traps (); jump_to_top_level (EXITPROG); } break; case cm_for: if (ignore_return) command->value.For->flags |= CMD_IGNORE_RETURN; exec_result = execute_for_command (command->value.For); break; #if defined (ARITH_FOR_COMMAND) case cm_arith_for: if (ignore_return) command->value.ArithFor->flags |= CMD_IGNORE_RETURN; exec_result = execute_arith_for_command (command->value.ArithFor); break; #endif #if defined (SELECT_COMMAND) case cm_select: if (ignore_return) command->value.Select->flags |= CMD_IGNORE_RETURN; exec_result = execute_select_command (command->value.Select); break; #endif case cm_case: if (ignore_return) command->value.Case->flags |= CMD_IGNORE_RETURN; exec_result = execute_case_command (command->value.Case); break; case cm_while: if (ignore_return) command->value.While->flags |= CMD_IGNORE_RETURN; exec_result = execute_while_command (command->value.While); break; case cm_until: if (ignore_return) command->value.While->flags |= CMD_IGNORE_RETURN; exec_result = execute_until_command (command->value.While); break; case cm_if: if (ignore_return) command->value.If->flags |= CMD_IGNORE_RETURN; exec_result = execute_if_command (command->value.If); break; case cm_group: /* This code can be executed from either of two paths: an explicit '{}' command, or via a function call. If we are executed via a function call, we have already taken care of the function being executed in the background (down there in execute_simple_command ()), and this command should *not* be marked as asynchronous. If we are executing a regular '{}' group command, and asynchronous == 1, we must want to execute the whole command in the background, so we need a subshell, and we want the stuff executed in that subshell (this group command) to be executed in the foreground of that subshell (i.e. there will not be *another* subshell forked). What we do is to force a subshell if asynchronous, and then call execute_command_internal again with asynchronous still set to 1, but with the original group command, so the printed command will look right. The code above that handles forking off subshells will note that both subshell and async are on, and turn off async in the child after forking the subshell (but leave async set in the parent, so the normal call to describe_pid is made). This turning off async is *crucial*; if it is not done, this will fall into an infinite loop of executions through this spot in subshell after subshell until the process limit is exhausted. */ if (asynchronous) { command->flags |= CMD_FORCE_SUBSHELL; exec_result = execute_command_internal (command, 1, pipe_in, pipe_out, fds_to_close); } else { if (ignore_return && command->value.Group->command) command->value.Group->command->flags |= CMD_IGNORE_RETURN; exec_result = execute_command_internal (command->value.Group->command, asynchronous, pipe_in, pipe_out, fds_to_close); } break; case cm_connection: exec_result = execute_connection (command, asynchronous, pipe_in, pipe_out, fds_to_close); break; #if defined (DPAREN_ARITHMETIC) case cm_arith: if (ignore_return) command->value.Arith->flags |= CMD_IGNORE_RETURN; exec_result = execute_arith_command (command->value.Arith); break; #endif #if defined (COND_COMMAND) case cm_cond: if (ignore_return) command->value.Cond->flags |= CMD_IGNORE_RETURN; exec_result = execute_cond_command (command->value.Cond); break; #endif case cm_function_def: exec_result = execute_intern_function (command->value.Function_def->name, command->value.Function_def->command); break; default: command_error ("execute_command", CMDERR_BADTYPE, command->type, 0); } if (my_undo_list) { do_redirections (my_undo_list, 1, 0, 0); dispose_redirects (my_undo_list); } if (exec_undo_list) dispose_redirects (exec_undo_list); if (my_undo_list || exec_undo_list) discard_unwind_frame ("loop_redirections"); /* Invert the return value if we have to */ if (invert) exec_result = (exec_result == EXECUTION_SUCCESS) ? EXECUTION_FAILURE : EXECUTION_SUCCESS; last_command_exit_value = exec_result; run_pending_traps (); if (running_trap == 0) currently_executing_command = (COMMAND *)NULL; return (last_command_exit_value); } #if defined (COMMAND_TIMING) #if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY) extern struct timeval *difftimeval __P((struct timeval *, struct timeval *, struct timeval *)); extern struct timeval *addtimeval __P((struct timeval *, struct timeval *, struct timeval *)); extern int timeval_to_cpu __P((struct timeval *, struct timeval *, struct timeval *)); #endif #define POSIX_TIMEFORMAT "real %2R\nuser %2U\nsys %2S" #define BASH_TIMEFORMAT "\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS" static int precs[] = { 0, 100, 10, 1 }; /* Expand one `%'-prefixed escape sequence from a time format string. */ static int mkfmt (buf, prec, lng, sec, sec_fraction) char *buf; int prec, lng; time_t sec; int sec_fraction; { time_t min; char abuf[INT_STRLEN_BOUND(time_t) + 1]; int ind, aind; ind = 0; abuf[sizeof(abuf) - 1] = '\0'; /* If LNG is non-zero, we want to decompose SEC into minutes and seconds. */ if (lng) { min = sec / 60; sec %= 60; aind = sizeof(abuf) - 2; do abuf[aind--] = (min % 10) + '0'; while (min /= 10); aind++; while (abuf[aind]) buf[ind++] = abuf[aind++]; buf[ind++] = 'm'; } /* Now add the seconds. */ aind = sizeof (abuf) - 2; do abuf[aind--] = (sec % 10) + '0'; while (sec /= 10); aind++; while (abuf[aind]) buf[ind++] = abuf[aind++]; /* We want to add a decimal point and PREC places after it if PREC is nonzero. PREC is not greater than 3. SEC_FRACTION is between 0 and 999. */ if (prec != 0) { buf[ind++] = '.'; for (aind = 1; aind <= prec; aind++) { buf[ind++] = (sec_fraction / precs[aind]) + '0'; sec_fraction %= precs[aind]; } } if (lng) buf[ind++] = 's'; buf[ind] = '\0'; return (ind); } /* Interpret the format string FORMAT, interpolating the following escape sequences: %[prec][l][RUS] where the optional `prec' is a precision, meaning the number of characters after the decimal point, the optional `l' means to format using minutes and seconds (MMmNN[.FF]s), like the `times' builtin', and the last character is one of R number of seconds of `real' time U number of seconds of `user' time S number of seconds of `system' time An occurrence of `%%' in the format string is translated to a `%'. The result is printed to FP, a pointer to a FILE. The other variables are the seconds and thousandths of a second of real, user, and system time, resectively. */ static void print_formatted_time (fp, format, rs, rsf, us, usf, ss, ssf, cpu) FILE *fp; char *format; time_t rs; int rsf; time_t us; int usf; time_t ss; int ssf, cpu; { int prec, lng, len; char *str, *s, ts[INT_STRLEN_BOUND (time_t) + sizeof ("mSS.FFFF")]; time_t sum; int sum_frac; int sindex, ssize; len = strlen (format); ssize = (len + 64) - (len % 64); str = (char *)xmalloc (ssize); sindex = 0; for (s = format; *s; s++) { if (*s != '%' || s[1] == '\0') { RESIZE_MALLOCED_BUFFER (str, sindex, 1, ssize, 64); str[sindex++] = *s; } else if (s[1] == '%') { s++; RESIZE_MALLOCED_BUFFER (str, sindex, 1, ssize, 64); str[sindex++] = *s; } else if (s[1] == 'P') { s++; if (cpu > 10000) cpu = 10000; sum = cpu / 100; sum_frac = (cpu % 100) * 10; len = mkfmt (ts, 2, 0, sum, sum_frac); RESIZE_MALLOCED_BUFFER (str, sindex, len, ssize, 64); strcpy (str + sindex, ts); sindex += len; } else { prec = 3; /* default is three places past the decimal point. */ lng = 0; /* default is to not use minutes or append `s' */ s++; if (DIGIT (*s)) /* `precision' */ { prec = *s++ - '0'; if (prec > 3) prec = 3; } if (*s == 'l') /* `length extender' */ { lng = 1; s++; } if (*s == 'R' || *s == 'E') len = mkfmt (ts, prec, lng, rs, rsf); else if (*s == 'U') len = mkfmt (ts, prec, lng, us, usf); else if (*s == 'S') len = mkfmt (ts, prec, lng, ss, ssf); else { internal_error ("bad format character in time format: %c", *s); free (str); return; } RESIZE_MALLOCED_BUFFER (str, sindex, len, ssize, 64); strcpy (str + sindex, ts); sindex += len; } } str[sindex] = '\0'; fprintf (fp, "%s\n", str); fflush (fp); free (str); } static int time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close) COMMAND *command; int asynchronous, pipe_in, pipe_out; struct fd_bitmap *fds_to_close; { int rv, posix_time, old_flags; time_t rs, us, ss; int rsf, usf, ssf; int cpu; char *time_format; #if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY) struct timeval real, user, sys; struct timeval before, after; struct timezone dtz; struct rusage selfb, selfa, kidsb, kidsa; /* a = after, b = before */ #else # if defined (HAVE_TIMES) clock_t tbefore, tafter, real, user, sys; struct tms before, after; # endif #endif #if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY) gettimeofday (&before, &dtz); getrusage (RUSAGE_SELF, &selfb); getrusage (RUSAGE_CHILDREN, &kidsb); #else # if defined (HAVE_TIMES) tbefore = times (&before); # endif #endif posix_time = (command->flags & CMD_TIME_POSIX); old_flags = command->flags; command->flags &= ~(CMD_TIME_PIPELINE|CMD_TIME_POSIX); rv = execute_command_internal (command, asynchronous, pipe_in, pipe_out, fds_to_close); command->flags = old_flags; rs = us = ss = 0; rsf = usf = ssf = cpu = 0; #if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY) gettimeofday (&after, &dtz); getrusage (RUSAGE_SELF, &selfa); getrusage (RUSAGE_CHILDREN, &kidsa); difftimeval (&real, &before, &after); timeval_to_secs (&real, &rs, &rsf); addtimeval (&user, difftimeval(&after, &selfb.ru_utime, &selfa.ru_utime), difftimeval(&before, &kidsb.ru_utime, &kidsa.ru_utime)); timeval_to_secs (&user, &us, &usf); addtimeval (&sys, difftimeval(&after, &selfb.ru_stime, &selfa.ru_stime), difftimeval(&before, &kidsb.ru_stime, &kidsa.ru_stime)); timeval_to_secs (&sys, &ss, &ssf); cpu = timeval_to_cpu (&real, &user, &sys); #else # if defined (HAVE_TIMES) tafter = times (&after); real = tafter - tbefore; clock_t_to_secs (real, &rs, &rsf); user = (after.tms_utime - before.tms_utime) + (after.tms_cutime - before.tms_cutime); clock_t_to_secs (user, &us, &usf); sys = (after.tms_stime - before.tms_stime) + (after.tms_cstime - before.tms_cstime); clock_t_to_secs (sys, &ss, &ssf); cpu = (real == 0) ? 0 : ((user + sys) * 10000) / real; # else rs = us = ss = 0; rsf = usf = ssf = cpu = 0; # endif #endif if (posix_time) time_format = POSIX_TIMEFORMAT; else if ((time_format = get_string_value ("TIMEFORMAT")) == 0) time_format = BASH_TIMEFORMAT; if (time_format && *time_format) print_formatted_time (stderr, time_format, rs, rsf, us, usf, ss, ssf, cpu); return rv; } #endif /* COMMAND_TIMING */ /* Execute a command that's supposed to be in a subshell. This must be called after make_child and we must be running in the child process. The caller will return or exit() immediately with the value this returns. */ static int execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close) COMMAND *command; int asynchronous; int pipe_in, pipe_out; struct fd_bitmap *fds_to_close; { int user_subshell, return_code, function_value, should_redir_stdin, invert; int ois; COMMAND *tcom; USE_VAR(user_subshell); USE_VAR(invert); USE_VAR(tcom); USE_VAR(asynchronous); should_redir_stdin = (asynchronous && (command->flags & CMD_STDIN_REDIR) && pipe_in == NO_PIPE && stdin_redirects (command->redirects) == 0); invert = (command->flags & CMD_INVERT_RETURN) != 0; user_subshell = command->type == cm_subshell || ((command->flags & CMD_WANT_SUBSHELL) != 0); command->flags &= ~(CMD_FORCE_SUBSHELL | CMD_WANT_SUBSHELL | CMD_INVERT_RETURN); /* If a command is asynchronous in a subshell (like ( foo ) & or the special case of an asynchronous GROUP command where the the subshell bit is turned on down in case cm_group: below), turn off `asynchronous', so that two subshells aren't spawned. This seems semantically correct to me. For example, ( foo ) & seems to say ``do the command `foo' in a subshell environment, but don't wait for that subshell to finish'', and "{ foo ; bar ; } &" seems to me to be like functions or builtins in the background, which executed in a subshell environment. I just don't see the need to fork two subshells. */ /* Don't fork again, we are already in a subshell. A `doubly async' shell is not interactive, however. */ if (asynchronous) { #if defined (JOB_CONTROL) /* If a construct like ( exec xxx yyy ) & is given while job control is active, we want to prevent exec from putting the subshell back into the original process group, carefully undoing all the work we just did in make_child. */ original_pgrp = -1; #endif /* JOB_CONTROL */ ois = interactive_shell; interactive_shell = 0; /* This test is to prevent alias expansion by interactive shells that run `(command) &' but to allow scripts that have enabled alias expansion with `shopt -s expand_alias' to continue to expand aliases. */ if (ois != interactive_shell) expand_aliases = 0; asynchronous = 0; } /* Subshells are neither login nor interactive. */ login_shell = interactive = 0; subshell_environment = user_subshell ? SUBSHELL_PAREN : SUBSHELL_ASYNC; reset_terminating_signals (); /* in sig.c */ /* Cancel traps, in trap.c. */ restore_original_signals (); if (asynchronous) setup_async_signals (); #if defined (JOB_CONTROL) set_sigchld_handler (); #endif /* JOB_CONTROL */ set_sigint_handler (); #if defined (JOB_CONTROL) /* Delete all traces that there were any jobs running. This is only for subshells. */ without_job_control (); #endif /* JOB_CONTROL */ if (fds_to_close) close_fd_bitmap (fds_to_close); do_piping (pipe_in, pipe_out); /* If this is a user subshell, set a flag if stdin was redirected. This is used later to decide whether to redirect fd 0 to /dev/null for async commands in the subshell. This adds more sh compatibility, but I'm not sure it's the right thing to do. */ if (user_subshell) { stdin_redir = stdin_redirects (command->redirects); restore_default_signal (0); } /* If this is an asynchronous command (command &), we want to redirect the standard input from /dev/null in the absence of any specific redirection involving stdin. */ if (should_redir_stdin && stdin_redir == 0) async_redirect_stdin (); /* Do redirections, then dispose of them before recursive call. */ if (command->redirects) { if (do_redirections (command->redirects, 1, 0, 0) != 0) exit (invert ? EXECUTION_SUCCESS : EXECUTION_FAILURE); dispose_redirects (command->redirects); command->redirects = (REDIRECT *)NULL; } tcom = (command->type == cm_subshell) ? command->value.Subshell->command : command; /* Make sure the subshell inherits any CMD_IGNORE_RETURN flag. */ if ((command->flags & CMD_IGNORE_RETURN) && tcom != command) tcom->flags |= CMD_IGNORE_RETURN; /* If this is a simple command, tell execute_disk_command that it might be able to get away without forking and simply exec. This means things like ( sleep 10 ) will only cause one fork. If we're timing the command or inverting its return value, however, we cannot do this optimization. */ if (user_subshell && (tcom->type == cm_simple || tcom->type == cm_subshell) && ((tcom->flags & CMD_TIME_PIPELINE) == 0) && ((tcom->flags & CMD_INVERT_RETURN) == 0)) { tcom->flags |= CMD_NO_FORK; if (tcom->type == cm_simple) tcom->value.Simple->flags |= CMD_NO_FORK; } invert = (tcom->flags & CMD_INVERT_RETURN) != 0; tcom->flags &= ~CMD_INVERT_RETURN; /* If we're inside a function while executing this subshell, we need to handle a possible `return'. */ function_value = 0; if (return_catch_flag) function_value = setjmp (return_catch); if (function_value) return_code = return_catch_value; else return_code = execute_command_internal (tcom, asynchronous, NO_PIPE, NO_PIPE, fds_to_close); /* If we are asked to, invert the return value. */ if (invert) return_code = (return_code == EXECUTION_SUCCESS) ? EXECUTION_FAILURE : EXECUTION_SUCCESS; /* If we were explicitly placed in a subshell with (), we need to do the `shell cleanup' things, such as running traps[0]. */ if (user_subshell && signal_is_trapped (0)) { last_command_exit_value = return_code; return_code = run_exit_trap (); } return (return_code); /* NOTREACHED */ } static int execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close) COMMAND *command; int asynchronous, pipe_in, pipe_out; struct fd_bitmap *fds_to_close; { int prev, fildes[2], new_bitmap_size, dummyfd, ignore_return, exec_result; COMMAND *cmd; struct fd_bitmap *fd_bitmap; #if defined (JOB_CONTROL) sigset_t set, oset; BLOCK_CHILD (set, oset); #endif /* JOB_CONTROL */ ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0; prev = pipe_in; cmd = command; while (cmd && cmd->type == cm_connection && cmd->value.Connection && cmd->value.Connection->connector == '|') { /* Make a pipeline between the two commands. */ if (pipe (fildes) < 0) { sys_error ("pipe error"); #if defined (JOB_CONTROL) terminate_current_pipeline (); kill_current_pipeline (); #endif /* JOB_CONTROL */ last_command_exit_value = EXECUTION_FAILURE; /* The unwind-protects installed below will take care of closing all of the open file descriptors. */ throw_to_top_level (); return (EXECUTION_FAILURE); /* XXX */ } /* Here is a problem: with the new file close-on-exec code, the read end of the pipe (fildes[0]) stays open in the first process, so that process will never get a SIGPIPE. There is no way to signal the first process that it should close fildes[0] after forking, so it remains open. No SIGPIPE is ever sent because there is still a file descriptor open for reading connected to the pipe. We take care of that here. This passes around a bitmap of file descriptors that must be closed after making a child process in execute_simple_command. */ /* We need fd_bitmap to be at least as big as fildes[0]. If fildes[0] is less than fds_to_close->size, then use fds_to_close->size. */ new_bitmap_size = (fildes[0] < fds_to_close->size) ? fds_to_close->size : fildes[0] + 8; fd_bitmap = new_fd_bitmap (new_bitmap_size); /* Now copy the old information into the new bitmap. */ xbcopy ((char *)fds_to_close->bitmap, (char *)fd_bitmap->bitmap, fds_to_close->size); /* And mark the pipe file descriptors to be closed. */ fd_bitmap->bitmap[fildes[0]] = 1; /* In case there are pipe or out-of-processes errors, we want all these file descriptors to be closed when unwind-protects are run, and the storage used for the bitmaps freed up. */ begin_unwind_frame ("pipe-file-descriptors"); add_unwind_protect (dispose_fd_bitmap, fd_bitmap); add_unwind_protect (close_fd_bitmap, fd_bitmap); if (prev >= 0) add_unwind_protect (close, prev); dummyfd = fildes[1]; add_unwind_protect (close, dummyfd); #if defined (JOB_CONTROL) add_unwind_protect (restore_signal_mask, &oset); #endif /* JOB_CONTROL */ if (ignore_return && cmd->value.Connection->first) cmd->value.Connection->first->flags |= CMD_IGNORE_RETURN; execute_command_internal (cmd->value.Connection->first, asynchronous, prev, fildes[1], fd_bitmap); if (prev >= 0) close (prev); prev = fildes[0]; close (fildes[1]); dispose_fd_bitmap (fd_bitmap); discard_unwind_frame ("pipe-file-descriptors"); cmd = cmd->value.Connection->second; } /* Now execute the rightmost command in the pipeline. */ if (ignore_return && cmd) cmd->flags |= CMD_IGNORE_RETURN; exec_result = execute_command_internal (cmd, asynchronous, prev, pipe_out, fds_to_close); if (prev >= 0) close (prev); #if defined (JOB_CONTROL) UNBLOCK_CHILD (oset); #endif return (exec_result); } static int execute_connection (command, asynchronous, pipe_in, pipe_out, fds_to_close) COMMAND *command; int asynchronous, pipe_in, pipe_out; struct fd_bitmap *fds_to_close; { REDIRECT *rp; COMMAND *tc, *second; int ignore_return, exec_result; ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0; switch (command->value.Connection->connector) { /* Do the first command asynchronously. */ case '&': tc = command->value.Connection->first; if (tc == 0) return (EXECUTION_SUCCESS); rp = tc->redirects; if (ignore_return) tc->flags |= CMD_IGNORE_RETURN; tc->flags |= CMD_AMPERSAND; /* If this shell was compiled without job control support, if we are currently in a subshell via `( xxx )', or if job control is not active then the standard input for an asynchronous command is forced to /dev/null. */ #if defined (JOB_CONTROL) if ((subshell_environment || !job_control) && !stdin_redir) #else if (!stdin_redir) #endif /* JOB_CONTROL */ tc->flags |= CMD_STDIN_REDIR; exec_result = execute_command_internal (tc, 1, pipe_in, pipe_out, fds_to_close); if (tc->flags & CMD_STDIN_REDIR) tc->flags &= ~CMD_STDIN_REDIR; second = command->value.Connection->second; if (second) { if (ignore_return) second->flags |= CMD_IGNORE_RETURN; exec_result = execute_command_internal (second, asynchronous, pipe_in, pipe_out, fds_to_close); } break; /* Just call execute command on both sides. */ case ';': if (ignore_return) { if (command->value.Connection->first) command->value.Connection->first->flags |= CMD_IGNORE_RETURN; if (command->value.Connection->second) command->value.Connection->second->flags |= CMD_IGNORE_RETURN; } QUIT; execute_command (command->value.Connection->first); QUIT; exec_result = execute_command_internal (command->value.Connection->second, asynchronous, pipe_in, pipe_out, fds_to_close); break; case '|': exec_result = execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close); break; case AND_AND: case OR_OR: if (asynchronous) { /* If we have something like `a && b &' or `a || b &', run the && or || stuff in a subshell. Force a subshell and just call execute_command_internal again. Leave asynchronous on so that we get a report from the parent shell about the background job. */ command->flags |= CMD_FORCE_SUBSHELL; exec_result = execute_command_internal (command, 1, pipe_in, pipe_out, fds_to_close); break; } /* Execute the first command. If the result of that is successful and the connector is AND_AND, or the result is not successful and the connector is OR_OR, then execute the second command, otherwise return. */ if (command->value.Connection->first) command->value.Connection->first->flags |= CMD_IGNORE_RETURN; exec_result = execute_command (command->value.Connection->first); QUIT; if (((command->value.Connection->connector == AND_AND) && (exec_result == EXECUTION_SUCCESS)) || ((command->value.Connection->connector == OR_OR) && (exec_result != EXECUTION_SUCCESS))) { if (ignore_return && command->value.Connection->second) command->value.Connection->second->flags |= CMD_IGNORE_RETURN; exec_result = execute_command (command->value.Connection->second); } break; default: command_error ("execute_connection", CMDERR_BADCONN, command->value.Connection->connector, 0); jump_to_top_level (DISCARD); exec_result = EXECUTION_FAILURE; } return exec_result; } #define REAP() \ do \ { \ if (!interactive_shell) \ reap_dead_jobs (); \ } \ while (0) /* Execute a FOR command. The syntax is: FOR word_desc IN word_list; DO command; DONE */ static int execute_for_command (for_command) FOR_COM *for_command; { register WORD_LIST *releaser, *list; SHELL_VAR *v; char *identifier; int retval; #if 0 SHELL_VAR *old_value = (SHELL_VAR *)NULL; /* Remember the old value of x. */ #endif if (check_identifier (for_command->name, 1) == 0) { if (posixly_correct && interactive_shell == 0) { last_command_exit_value = EX_USAGE; jump_to_top_level (EXITPROG); } return (EXECUTION_FAILURE); } loop_level++; identifier = for_command->name->word; list = releaser = expand_words_no_vars (for_command->map_list); begin_unwind_frame ("for"); add_unwind_protect (dispose_words, releaser); #if 0 if (lexical_scoping) { old_value = copy_variable (find_variable (identifier)); if (old_value) add_unwind_protect (dispose_variable, old_value); } #endif if (for_command->flags & CMD_IGNORE_RETURN) for_command->action->flags |= CMD_IGNORE_RETURN; for (retval = EXECUTION_SUCCESS; list; list = list->next) { QUIT; this_command_name = (char *)NULL; v = bind_variable (identifier, list->word->word); if (readonly_p (v) || noassign_p (v)) { if (readonly_p (v) && interactive_shell == 0 && posixly_correct) { last_command_exit_value = EXECUTION_FAILURE; jump_to_top_level (FORCE_EOF); } else { run_unwind_frame ("for"); loop_level--; return (EXECUTION_FAILURE); } } retval = execute_command (for_command->action); REAP (); QUIT; if (breaking) { breaking--; break; } if (continuing) { continuing--; if (continuing) break; } } loop_level--; #if 0 if (lexical_scoping) { if (!old_value) unbind_variable (identifier); else { SHELL_VAR *new_value; new_value = bind_variable (identifier, value_cell(old_value)); new_value->attributes = old_value->attributes; dispose_variable (old_value); } } #endif dispose_words (releaser); discard_unwind_frame ("for"); return (retval); } #if defined (ARITH_FOR_COMMAND) /* Execute an arithmetic for command. The syntax is for (( init ; step ; test )) do body done The execution should be exactly equivalent to eval \(\( init \)\) while eval \(\( test \)\) ; do body; eval \(\( step \)\) done */ static intmax_t eval_arith_for_expr (l, okp) WORD_LIST *l; int *okp; { WORD_LIST *new; intmax_t expresult; new = expand_words_no_vars (l); if (new) { if (echo_command_at_execute) xtrace_print_arith_cmd (new); this_command_name = "(("; /* )) for expression error messages */ if (signal_is_trapped (DEBUG_TRAP) && signal_is_ignored (DEBUG_TRAP) == 0) run_debug_trap (); expresult = evalexp (new->word->word, okp); dispose_words (new); } else { expresult = 0; if (okp) *okp = 1; } return (expresult); } static int execute_arith_for_command (arith_for_command) ARITH_FOR_COM *arith_for_command; { intmax_t expresult; int expok, body_status, arith_lineno, save_lineno; body_status = EXECUTION_SUCCESS; loop_level++; if (arith_for_command->flags & CMD_IGNORE_RETURN) arith_for_command->action->flags |= CMD_IGNORE_RETURN; this_command_name = "(("; /* )) for expression error messages */ /* save the starting line number of the command so we can reset line_number before executing each expression -- for $LINENO and the DEBUG trap. */ arith_lineno = arith_for_command->line; if (variable_context && interactive_shell) line_number = arith_lineno -= function_line_number; /* Evaluate the initialization expression. */ expresult = eval_arith_for_expr (arith_for_command->init, &expok); if (expok == 0) return (EXECUTION_FAILURE); while (1) { /* Evaluate the test expression. */ save_lineno = line_number; line_number = arith_lineno; expresult = eval_arith_for_expr (arith_for_command->test, &expok); line_number = save_lineno; if (expok == 0) { body_status = EXECUTION_FAILURE; break; } REAP (); if (expresult == 0) break; /* Execute the body of the arithmetic for command. */ QUIT; body_status = execute_command (arith_for_command->action); QUIT; /* Handle any `break' or `continue' commands executed by the body. */ if (breaking) { breaking--; break; } if (continuing) { continuing--; if (continuing) break; } /* Evaluate the step expression. */ save_lineno = line_number; line_number = arith_lineno; expresult = eval_arith_for_expr (arith_for_command->step, &expok); line_number = save_lineno; if (expok == 0) { body_status = EXECUTION_FAILURE; break; } } loop_level--; return (body_status); } #endif #if defined (SELECT_COMMAND) static int LINES, COLS, tabsize; #define RP_SPACE ") " #define RP_SPACE_LEN 2 /* XXX - does not handle numbers > 1000000 at all. */ #define NUMBER_LEN(s) \ ((s < 10) ? 1 \ : ((s < 100) ? 2 \ : ((s < 1000) ? 3 \ : ((s < 10000) ? 4 \ : ((s < 100000) ? 5 \ : 6))))) static int print_index_and_element (len, ind, list) int len, ind; WORD_LIST *list; { register WORD_LIST *l; register int i; if (list == 0) return (0); for (i = ind, l = list; l && --i; l = l->next) ; fprintf (stderr, "%*d%s%s", len, ind, RP_SPACE, l->word->word); return (STRLEN (l->word->word)); } static void indent (from, to) int from, to; { while (from < to) { if ((to / tabsize) > (from / tabsize)) { putc ('\t', stderr); from += tabsize - from % tabsize; } else { putc (' ', stderr); from++; } } } static void print_select_list (list, list_len, max_elem_len, indices_len) WORD_LIST *list; int list_len, max_elem_len, indices_len; { int ind, row, elem_len, pos, cols, rows; int first_column_indices_len, other_indices_len; if (list == 0) { putc ('\n', stderr); return; } cols = max_elem_len ? COLS / max_elem_len : 1; if (cols == 0) cols = 1; rows = list_len ? list_len / cols + (list_len % cols != 0) : 1; cols = list_len ? list_len / rows + (list_len % rows != 0) : 1; if (rows == 1) { rows = cols; cols = 1; } first_column_indices_len = NUMBER_LEN (rows); other_indices_len = indices_len; for (row = 0; row < rows; row++) { ind = row; pos = 0; while (1) { indices_len = (pos == 0) ? first_column_indices_len : other_indices_len; elem_len = print_index_and_element (indices_len, ind + 1, list); elem_len += indices_len + RP_SPACE_LEN; ind += rows; if (ind >= list_len) break; indent (pos + elem_len, pos + max_elem_len); pos += max_elem_len; } putc ('\n', stderr); } } /* Print the elements of LIST, one per line, preceded by an index from 1 to LIST_LEN. Then display PROMPT and wait for the user to enter a number. If the number is between 1 and LIST_LEN, return that selection. If EOF is read, return a null string. If a blank line is entered, or an invalid number is entered, the loop is executed again. */ static char * select_query (list, list_len, prompt, print_menu) WORD_LIST *list; int list_len; char *prompt; int print_menu; { int max_elem_len, indices_len, len; intmax_t reply; WORD_LIST *l; char *repl_string, *t; t = get_string_value ("LINES"); LINES = (t && *t) ? atoi (t) : 24; t = get_string_value ("COLUMNS"); COLS = (t && *t) ? atoi (t) : 80; #if 0 t = get_string_value ("TABSIZE"); tabsize = (t && *t) ? atoi (t) : 8; if (tabsize <= 0) tabsize = 8; #else tabsize = 8; #endif max_elem_len = 0; for (l = list; l; l = l->next) { len = STRLEN (l->word->word); if (len > max_elem_len) max_elem_len = len; } indices_len = NUMBER_LEN (list_len); max_elem_len += indices_len + RP_SPACE_LEN + 2; while (1) { if (print_menu) print_select_list (list, list_len, max_elem_len, indices_len); fprintf (stderr, "%s", prompt); fflush (stderr); QUIT; if (read_builtin ((WORD_LIST *)NULL) == EXECUTION_FAILURE) { putchar ('\n'); return ((char *)NULL); } repl_string = get_string_value ("REPLY"); if (*repl_string == 0) { print_menu = 1; continue; } if (legal_number (repl_string, &reply) == 0) return ""; if (reply < 1 || reply > list_len) return ""; for (l = list; l && --reply; l = l->next) ; return (l->word->word); } } /* Execute a SELECT command. The syntax is: SELECT word IN list DO command_list DONE Only `break' or `return' in command_list will terminate the command. */ static int execute_select_command (select_command) SELECT_COM *select_command; { WORD_LIST *releaser, *list; SHELL_VAR *v; char *identifier, *ps3_prompt, *selection; int retval, list_len, show_menu; if (check_identifier (select_command->name, 1) == 0) return (EXECUTION_FAILURE); loop_level++; identifier = select_command->name->word; /* command and arithmetic substitution, parameter and variable expansion, word splitting, pathname expansion, and quote removal. */ list = releaser = expand_words_no_vars (select_command->map_list); list_len = list_length (list); if (list == 0 || list_len == 0) { if (list) dispose_words (list); return (EXECUTION_SUCCESS); } begin_unwind_frame ("select"); add_unwind_protect (dispose_words, releaser); if (select_command->flags & CMD_IGNORE_RETURN) select_command->action->flags |= CMD_IGNORE_RETURN; retval = EXECUTION_SUCCESS; show_menu = 1; while (1) { ps3_prompt = get_string_value ("PS3"); if (ps3_prompt == 0) ps3_prompt = "#? "; QUIT; selection = select_query (list, list_len, ps3_prompt, show_menu); QUIT; if (selection == 0) { /* select_query returns EXECUTION_FAILURE if the read builtin fails, so we want to return failure in this case. */ retval = EXECUTION_FAILURE; break; } v = bind_variable (identifier, selection); if (readonly_p (v) || noassign_p (v)) { if (readonly_p (v) && interactive_shell == 0 && posixly_correct) { last_command_exit_value = EXECUTION_FAILURE; jump_to_top_level (FORCE_EOF); } else { run_unwind_frame ("select"); return (EXECUTION_FAILURE); } } retval = execute_command (select_command->action); REAP (); QUIT; if (breaking) { breaking--; break; } if (continuing) { continuing--; if (continuing) break; } #if defined (KSH_COMPATIBLE_SELECT) show_menu = 0; selection = get_string_value ("REPLY"); if (selection && *selection == '\0') show_menu = 1; #endif } loop_level--; run_unwind_frame ("select"); return (retval); } #endif /* SELECT_COMMAND */ /* Execute a CASE command. The syntax is: CASE word_desc IN pattern_list ESAC. The pattern_list is a linked list of pattern clauses; each clause contains some patterns to compare word_desc against, and an associated command to execute. */ static int execute_case_command (case_command) CASE_COM *case_command; { register WORD_LIST *list; WORD_LIST *wlist, *es; PATTERN_LIST *clauses; char *word, *pattern; int retval, match, ignore_return; /* Posix.2 specifies that the WORD is tilde expanded. */ if (member ('~', case_command->word->word)) { word = bash_tilde_expand (case_command->word->word, 0); free (case_command->word->word); case_command->word->word = word; } wlist = expand_word_unsplit (case_command->word, 0); word = wlist ? string_list (wlist) : savestring (""); dispose_words (wlist); retval = EXECUTION_SUCCESS; ignore_return = case_command->flags & CMD_IGNORE_RETURN; begin_unwind_frame ("case"); add_unwind_protect ((Function *)xfree, word); #define EXIT_CASE() goto exit_case_command for (clauses = case_command->clauses; clauses; clauses = clauses->next) { QUIT; for (list = clauses->patterns; list; list = list->next) { /* Posix.2 specifies to tilde expand each member of the pattern list. */ if (member ('~', list->word->word)) { pattern = bash_tilde_expand (list->word->word, 0); free (list->word->word); list->word->word = pattern; } es = expand_word_leave_quoted (list->word, 0); if (es && es->word && es->word->word && *(es->word->word)) pattern = quote_string_for_globbing (es->word->word, QGLOB_CVTNULL); else { pattern = (char *)xmalloc (1); pattern[0] = '\0'; } /* Since the pattern does not undergo quote removal (as per Posix.2, section 3.9.4.3), the strmatch () call must be able to recognize backslashes as escape characters. */ match = strmatch (pattern, word, FNMATCH_EXTFLAG) != FNM_NOMATCH; free (pattern); dispose_words (es); if (match) { if (clauses->action && ignore_return) clauses->action->flags |= CMD_IGNORE_RETURN; retval = execute_command (clauses->action); EXIT_CASE (); } QUIT; } } exit_case_command: free (word); discard_unwind_frame ("case"); return (retval); } #define CMD_WHILE 0 #define CMD_UNTIL 1 /* The WHILE command. Syntax: WHILE test DO action; DONE. Repeatedly execute action while executing test produces EXECUTION_SUCCESS. */ static int execute_while_command (while_command) WHILE_COM *while_command; { return (execute_while_or_until (while_command, CMD_WHILE)); } /* UNTIL is just like WHILE except that the test result is negated. */ static int execute_until_command (while_command) WHILE_COM *while_command; { return (execute_while_or_until (while_command, CMD_UNTIL)); } /* The body for both while and until. The only difference between the two is that the test value is treated differently. TYPE is CMD_WHILE or CMD_UNTIL. The return value for both commands should be EXECUTION_SUCCESS if no commands in the body are executed, and the status of the last command executed in the body otherwise. */ static int execute_while_or_until (while_command, type) WHILE_COM *while_command; int type; { int return_value, body_status; body_status = EXECUTION_SUCCESS; loop_level++; while_command->test->flags |= CMD_IGNORE_RETURN; if (while_command->flags & CMD_IGNORE_RETURN) while_command->action->flags |= CMD_IGNORE_RETURN; while (1) { return_value = execute_command (while_command->test); REAP (); /* Need to handle `break' in the test when we would break out of the loop. The job control code will set `breaking' to loop_level when a job in a loop is stopped with SIGTSTP. If the stopped job is in the loop test, `breaking' will not be reset unless we do this, and the shell will cease to execute commands. */ if (type == CMD_WHILE && return_value != EXECUTION_SUCCESS) { if (breaking) breaking--; break; } if (type == CMD_UNTIL && return_value == EXECUTION_SUCCESS) { if (breaking) breaking--; break; } QUIT; body_status = execute_command (while_command->action); QUIT; if (breaking) { breaking--; break; } if (continuing) { continuing--; if (continuing) break; } } loop_level--; return (body_status); } /* IF test THEN command [ELSE command]. IF also allows ELIF in the place of ELSE IF, but the parser makes *that* stupidity transparent. */ static int execute_if_command (if_command) IF_COM *if_command; { int return_value; if_command->test->flags |= CMD_IGNORE_RETURN; return_value = execute_command (if_command->test); if (return_value == EXECUTION_SUCCESS) { QUIT; if (if_command->true_case && (if_command->flags & CMD_IGNORE_RETURN)) if_command->true_case->flags |= CMD_IGNORE_RETURN; return (execute_command (if_command->true_case)); } else { QUIT; if (if_command->false_case && (if_command->flags & CMD_IGNORE_RETURN)) if_command->false_case->flags |= CMD_IGNORE_RETURN; return (execute_command (if_command->false_case)); } } #if defined (DPAREN_ARITHMETIC) static int execute_arith_command (arith_command) ARITH_COM *arith_command; { int expok; intmax_t expresult; WORD_LIST *new; expresult = 0; this_command_name = "(("; /* )) */ /* If we're in a function, update the line number information. */ if (variable_context && interactive_shell) line_number = arith_command->line - function_line_number; /* Run the debug trap before each arithmetic command, but do it after we update the line number information and before we expand the various words in the expression. */ if (signal_is_trapped (DEBUG_TRAP) && signal_is_ignored (DEBUG_TRAP) == 0) run_debug_trap (); new = expand_words (arith_command->exp); /* If we're tracing, make a new word list with `((' at the front and `))' at the back and print it. */ if (echo_command_at_execute) xtrace_print_arith_cmd (new); expresult = evalexp (new->word->word, &expok); dispose_words (new); if (expok == 0) return (EXECUTION_FAILURE); return (expresult == 0 ? EXECUTION_FAILURE : EXECUTION_SUCCESS); } #endif /* DPAREN_ARITHMETIC */ #if defined (COND_COMMAND) static char *nullstr = ""; static int execute_cond_node (cond) COND_COM *cond; { int result, invert, patmatch; char *arg1, *arg2; invert = (cond->flags & CMD_INVERT_RETURN); if (cond->type == COND_EXPR) result = execute_cond_node (cond->left); else if (cond->type == COND_OR) { result = execute_cond_node (cond->left); if (result != EXECUTION_SUCCESS) result = execute_cond_node (cond->right); } else if (cond->type == COND_AND) { result = execute_cond_node (cond->left); if (result == EXECUTION_SUCCESS) result = execute_cond_node (cond->right); } else if (cond->type == COND_UNARY) { arg1 = cond_expand_word (cond->left->op, 0); if (arg1 == 0) arg1 = nullstr; if (echo_command_at_execute) xtrace_print_cond_term (cond->type, invert, cond->op, arg1, (char *)NULL); result = unary_test (cond->op->word, arg1) ? EXECUTION_SUCCESS : EXECUTION_FAILURE; if (arg1 != nullstr) free (arg1); } else if (cond->type == COND_BINARY) { patmatch = ((cond->op->word[1] == '=') && (cond->op->word[2] == '\0') && (cond->op->word[0] == '!' || cond->op->word[0] == '=') || (cond->op->word[0] == '=' && cond->op->word[1] == '\0')); arg1 = cond_expand_word (cond->left->op, 0); if (arg1 == 0) arg1 = nullstr; arg2 = cond_expand_word (cond->right->op, patmatch); if (arg2 == 0) arg2 = nullstr; if (echo_command_at_execute) xtrace_print_cond_term (cond->type, invert, cond->op, arg1, arg2); result = binary_test (cond->op->word, arg1, arg2, TEST_PATMATCH|TEST_ARITHEXP) ? EXECUTION_SUCCESS : EXECUTION_FAILURE; if (arg1 != nullstr) free (arg1); if (arg2 != nullstr) free (arg2); } else { command_error ("execute_cond_node", CMDERR_BADTYPE, cond->type, 0); jump_to_top_level (DISCARD); result = EXECUTION_FAILURE; } if (invert) result = (result == EXECUTION_SUCCESS) ? EXECUTION_FAILURE : EXECUTION_SUCCESS; return result; } static int execute_cond_command (cond_command) COND_COM *cond_command; { int result; result = EXECUTION_SUCCESS; this_command_name = "[["; /* If we're in a function, update the line number information. */ if (variable_context && interactive_shell) line_number = cond_command->line - function_line_number; /* Run the debug trap before each conditional command, but do it after we update the line number information. */ if (signal_is_trapped (DEBUG_TRAP) && signal_is_ignored (DEBUG_TRAP) == 0) run_debug_trap (); #if 0 debug_print_cond_command (cond_command); #endif last_command_exit_value = result = execute_cond_node (cond_command); return (result); } #endif /* COND_COMMAND */ static void bind_lastarg (arg) char *arg; { SHELL_VAR *var; if (arg == 0) arg = ""; var = bind_variable ("_", arg); VUNSETATTR (var, att_exported); } /* Execute a null command. Fork a subshell if the command uses pipes or is to be run asynchronously. This handles all the side effects that are supposed to take place. */ static int execute_null_command (redirects, pipe_in, pipe_out, async, old_last_command_subst_pid) REDIRECT *redirects; int pipe_in, pipe_out, async; pid_t old_last_command_subst_pid; { if (pipe_in != NO_PIPE || pipe_out != NO_PIPE || async) { /* We have a null command, but we really want a subshell to take care of it. Just fork, do piping and redirections, and exit. */ if (make_child ((char *)NULL, async) == 0) { /* Cancel traps, in trap.c. */ restore_original_signals (); /* XXX */ do_piping (pipe_in, pipe_out); subshell_environment = SUBSHELL_ASYNC; if (do_redirections (redirects, 1, 0, 0) == 0) exit (EXECUTION_SUCCESS); else exit (EXECUTION_FAILURE); } else { close_pipes (pipe_in, pipe_out); #if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD) unlink_fifo_list (); #endif return (EXECUTION_SUCCESS); } } else { /* Even if there aren't any command names, pretend to do the redirections that are specified. The user expects the side effects to take place. If the redirections fail, then return failure. Otherwise, if a command substitution took place while expanding the command or a redirection, return the value of that substitution. Otherwise, return EXECUTION_SUCCESS. */ if (do_redirections (redirects, 0, 0, 0) != 0) return (EXECUTION_FAILURE); else if (old_last_command_subst_pid != last_command_subst_pid) return (last_command_exit_value); else return (EXECUTION_SUCCESS); } } /* This is a hack to suppress word splitting for assignment statements given as arguments to builtins with the ASSIGNMENT_BUILTIN flag set. */ static void fix_assignment_words (words) WORD_LIST *words; { WORD_LIST *w; struct builtin *b; if (words == 0) return; b = 0; for (w = words; w; w = w->next) if (w->word->flags & W_ASSIGNMENT) { if (b == 0) { b = builtin_address_internal (words->word->word, 0); if (b == 0 || (b->flags & ASSIGNMENT_BUILTIN) == 0) return; } w->word->flags |= (W_NOSPLIT|W_NOGLOB|W_TILDEEXP); } } /* The meaty part of all the executions. We have to start hacking the real execution of commands here. Fork a process, set things up, execute the command. */ static int execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close) SIMPLE_COM *simple_command; int pipe_in, pipe_out, async; struct fd_bitmap *fds_to_close; { WORD_LIST *words, *lastword; char *command_line, *lastarg, *temp; int first_word_quoted, result, builtin_is_special, already_forked, dofork; pid_t old_last_command_subst_pid, old_last_async_pid; sh_builtin_func_t *builtin; SHELL_VAR *func; result = EXECUTION_SUCCESS; special_builtin_failed = builtin_is_special = 0; command_line = (char *)0; /* If we're in a function, update the line number information. */ if (variable_context && interactive_shell) line_number = simple_command->line - function_line_number; /* Run the debug trap before each simple command, but do it after we update the line number information. */ if (signal_is_trapped (DEBUG_TRAP) && signal_is_ignored (DEBUG_TRAP) == 0) run_debug_trap (); /* Remember what this command line looks like at invocation. */ command_string_index = 0; print_simple_command (simple_command); first_word_quoted = simple_command->words ? (simple_command->words->word->flags & W_QUOTED): 0; old_last_command_subst_pid = last_command_subst_pid; old_last_async_pid = last_asynchronous_pid; already_forked = dofork = 0; /* If we're in a pipeline or run in the background, set DOFORK so we make the child early, before word expansion. This keeps assignment statements from affecting the parent shell's environment when they should not. */ dofork = pipe_in != NO_PIPE || pipe_out != NO_PIPE || async; /* Something like `%2 &' should restart job 2 in the background, not cause the shell to fork here. */ if (dofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE && simple_command->words && simple_command->words->word && simple_command->words->word->word && (simple_command->words->word->word[0] == '%')) dofork = 0; if (dofork) { /* XXX memory leak if expand_words() error causes a jump_to_top_level */ command_line = savestring (the_printed_command); /* Do this now, because execute_disk_command will do it anyway in the vast majority of cases. */ maybe_make_export_env (); if (make_child (command_line, async) == 0) { already_forked = 1; simple_command->flags |= CMD_NO_FORK; subshell_environment = (pipe_in != NO_PIPE || pipe_out != NO_PIPE) ? (SUBSHELL_PIPE|SUBSHELL_FORK) : (SUBSHELL_ASYNC|SUBSHELL_FORK); /* We need to do this before piping to handle some really pathological cases where one of the pipe file descriptors is < 2. */ if (fds_to_close) close_fd_bitmap (fds_to_close); do_piping (pipe_in, pipe_out); pipe_in = pipe_out = NO_PIPE; last_asynchronous_pid = old_last_async_pid; } else { close_pipes (pipe_in, pipe_out); #if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD) unlink_fifo_list (); #endif command_line = (char *)NULL; /* don't free this. */ bind_lastarg ((char *)NULL); return (result); } } /* If we are re-running this as the result of executing the `command' builtin, do not expand the command words a second time. */ if ((simple_command->flags & CMD_INHIBIT_EXPANSION) == 0) { current_fds_to_close = fds_to_close; fix_assignment_words (simple_command->words); words = expand_words (simple_command->words); current_fds_to_close = (struct fd_bitmap *)NULL; } else words = copy_word_list (simple_command->words); /* It is possible for WORDS not to have anything left in it. Perhaps all the words consisted of `$foo', and there was no variable `$foo'. */ if (words == 0) { result = execute_null_command (simple_command->redirects, pipe_in, pipe_out, already_forked ? 0 : async, old_last_command_subst_pid); if (already_forked) exit (result); else { bind_lastarg ((char *)NULL); set_pipestatus_from_exit (result); return (result); } } lastarg = (char *)NULL; begin_unwind_frame ("simple-command"); if (echo_command_at_execute) xtrace_print_word_list (words); builtin = (sh_builtin_func_t *)NULL; func = (SHELL_VAR *)NULL; if ((simple_command->flags & CMD_NO_FUNCTIONS) == 0) { /* Posix.2 says special builtins are found before functions. We don't set builtin_is_special anywhere other than here, because this path is followed only when the `command' builtin is *not* being used, and we don't want to exit the shell if a special builtin executed with `command builtin' fails. `command' is not a special builtin. */ if (posixly_correct) { builtin = find_special_builtin (words->word->word); if (builtin) builtin_is_special = 1; } if (builtin == 0) func = find_function (words->word->word); } add_unwind_protect (dispose_words, words); QUIT; /* Bind the last word in this command to "$_" after execution. */ for (lastword = words; lastword->next; lastword = lastword->next) ; lastarg = lastword->word->word; #if defined (JOB_CONTROL) /* Is this command a job control related thing? */ if (words->word->word[0] == '%' && already_forked == 0) { this_command_name = async ? "bg" : "fg"; last_shell_builtin = this_shell_builtin; this_shell_builtin = builtin_address (this_command_name); result = (*this_shell_builtin) (words); goto return_result; } /* One other possiblilty. The user may want to resume an existing job. If they do, find out whether this word is a candidate for a running job. */ if (job_control && already_forked == 0 && async == 0 && !first_word_quoted && !words->next && words->word->word[0] && !simple_command->redirects && pipe_in == NO_PIPE && pipe_out == NO_PIPE && (temp = get_string_value ("auto_resume"))) { int job, jflags, started_status; jflags = JM_STOPPED|JM_FIRSTMATCH; if (STREQ (temp, "exact")) jflags |= JM_EXACT; else if (STREQ (temp, "substring")) jflags |= JM_SUBSTRING; else jflags |= JM_PREFIX; job = get_job_by_name (words->word->word, jflags); if (job != NO_JOB) { run_unwind_frame ("simple-command"); this_command_name = "fg"; last_shell_builtin = this_shell_builtin; this_shell_builtin = builtin_address ("fg"); started_status = start_job (job, 1); return ((started_status < 0) ? EXECUTION_FAILURE : started_status); } } #endif /* JOB_CONTROL */ /* Remember the name of this command globally. */ this_command_name = words->word->word; QUIT; /* This command could be a shell builtin or a user-defined function. We have already found special builtins by this time, so we do not set builtin_is_special. If this is a function or builtin, and we have pipes, then fork a subshell in here. Otherwise, just execute the command directly. */ if (func == 0 && builtin == 0) builtin = find_shell_builtin (this_command_name); last_shell_builtin = this_shell_builtin; this_shell_builtin = builtin; if (builtin || func) { if (already_forked) { /* reset_terminating_signals (); */ /* XXX */ /* Cancel traps, in trap.c. */ restore_original_signals (); if (async) { if ((simple_command->flags & CMD_STDIN_REDIR) && pipe_in == NO_PIPE && (stdin_redirects (simple_command->redirects) == 0)) async_redirect_stdin (); setup_async_signals (); } execute_subshell_builtin_or_function (words, simple_command->redirects, builtin, func, pipe_in, pipe_out, async, fds_to_close, simple_command->flags); } else { result = execute_builtin_or_function (words, builtin, func, simple_command->redirects, fds_to_close, simple_command->flags); if (builtin) { if (result > EX_SHERRBASE) { result = builtin_status (result); if (builtin_is_special) special_builtin_failed = 1; } /* In POSIX mode, if there are assignment statements preceding a special builtin, they persist after the builtin completes. */ if (posixly_correct && builtin_is_special && temporary_env) merge_temporary_env (); } else /* function */ { if (result == EX_USAGE) result = EX_BADUSAGE; else if (result > EX_SHERRBASE) result = EXECUTION_FAILURE; } set_pipestatus_from_exit (result); goto return_result; } } if (command_line == 0) command_line = savestring (the_printed_command); execute_disk_command (words, simple_command->redirects, command_line, pipe_in, pipe_out, async, fds_to_close, simple_command->flags); return_result: bind_lastarg (lastarg); FREE (command_line); run_unwind_frame ("simple-command"); return (result); } /* Translate the special builtin exit statuses. We don't really need a function for this; it's a placeholder for future work. */ static int builtin_status (result) int result; { int r; switch (result) { case EX_USAGE: r = EX_BADUSAGE; break; case EX_REDIRFAIL: case EX_BADSYNTAX: case EX_BADASSIGN: case EX_EXPFAIL: r = EXECUTION_FAILURE; break; default: r = EXECUTION_SUCCESS; break; } return (r); } static int execute_builtin (builtin, words, flags, subshell) sh_builtin_func_t *builtin; WORD_LIST *words; int flags, subshell; { int old_e_flag, result, eval_unwind; int isbltinenv; old_e_flag = exit_immediately_on_error; /* The eval builtin calls parse_and_execute, which does not know about the setting of flags, and always calls the execution functions with flags that will exit the shell on an error if -e is set. If the eval builtin is being called, and we're supposed to ignore the exit value of the command, we turn the -e flag off ourselves, then restore it when the command completes. */ if (subshell == 0 && builtin == eval_builtin && (flags & CMD_IGNORE_RETURN)) { begin_unwind_frame ("eval_builtin"); unwind_protect_int (exit_immediately_on_error); exit_immediately_on_error = 0; eval_unwind = 1; } else eval_unwind = 0; /* The temporary environment for a builtin is supposed to apply to all commands executed by that builtin. Currently, this is a problem only with the `source' and `eval' builtins. */ isbltinenv = (builtin == source_builtin || builtin == eval_builtin); if (isbltinenv) { if (subshell == 0) begin_unwind_frame ("builtin_env"); if (temporary_env) { push_scope (VC_BLTNENV, temporary_env); if (subshell == 0) add_unwind_protect (pop_scope, "1"); temporary_env = (HASH_TABLE *)NULL; } } /* `return' does a longjmp() back to a saved environment in execute_function. If a variable assignment list preceded the command, and the shell is running in POSIX mode, we need to merge that into the shell_variables table, since `return' is a POSIX special builtin. */ if (posixly_correct && subshell == 0 && builtin == return_builtin && temporary_env) { begin_unwind_frame ("return_temp_env"); add_unwind_protect (merge_temporary_env, (char *)NULL); } result = ((*builtin) (words->next)); /* This shouldn't happen, but in case `return' comes back instead of longjmp'ing, we need to unwind. */ if (posixly_correct && subshell == 0 && builtin == return_builtin && temporary_env) discard_unwind_frame ("return_temp_env"); if (subshell == 0 && isbltinenv) run_unwind_frame ("builtin_env"); if (eval_unwind) { exit_immediately_on_error += old_e_flag; discard_unwind_frame ("eval_builtin"); } return (result); } static int execute_function (var, words, flags, fds_to_close, async, subshell) SHELL_VAR *var; WORD_LIST *words; int flags; struct fd_bitmap *fds_to_close; int async, subshell; { int return_val, result; COMMAND *tc, *fc; char *debug_trap, *error_trap; USE_VAR(fc); tc = (COMMAND *)copy_command (function_cell (var)); if (tc && (flags & CMD_IGNORE_RETURN)) tc->flags |= CMD_IGNORE_RETURN; if (subshell == 0) { begin_unwind_frame ("function_calling"); push_context (var->name, subshell, temporary_env); add_unwind_protect (pop_context, (char *)NULL); unwind_protect_int (line_number); unwind_protect_int (return_catch_flag); unwind_protect_jmp_buf (return_catch); add_unwind_protect (dispose_command, (char *)tc); unwind_protect_pointer (this_shell_function); unwind_protect_int (loop_level); } else push_context (var->name, subshell, temporary_env); /* don't unwind-protect for subshells */ temporary_env = (HASH_TABLE *)NULL; this_shell_function = var; make_funcname_visible (1); debug_trap = TRAP_STRING(DEBUG_TRAP); error_trap = TRAP_STRING(ERROR_TRAP); /* The order of the unwind protects for debug_trap and error_trap is important here! unwind-protect commands are run in reverse order of registration. If this causes problems, take out the xfree unwind-protect calls and live with the small memory leak. */ if (debug_trap && (trace_p (var) == 0)) { if (subshell == 0) { debug_trap = savestring (debug_trap); add_unwind_protect (xfree, debug_trap); add_unwind_protect (set_debug_trap, debug_trap); } restore_default_signal (DEBUG_TRAP); } if (error_trap) { if (subshell == 0) { error_trap = savestring (error_trap); add_unwind_protect (xfree, error_trap); add_unwind_protect (set_error_trap, error_trap); } restore_default_signal (ERROR_TRAP); } /* The temporary environment for a function is supposed to apply to all commands executed within the function body. */ remember_args (words->next, 1); /* Number of the line on which the function body starts. */ if (interactive_shell) line_number = function_line_number = tc->line; if (subshell) { #if defined (JOB_CONTROL) stop_pipeline (async, (COMMAND *)NULL); #endif fc = (tc->type == cm_group) ? tc->value.Group->command : tc; if (fc && (flags & CMD_IGNORE_RETURN)) fc->flags |= CMD_IGNORE_RETURN; } else fc = tc; return_catch_flag++; return_val = setjmp (return_catch); if (return_val) result = return_catch_value; else result = execute_command_internal (fc, 0, NO_PIPE, NO_PIPE, fds_to_close); if (subshell == 0) run_unwind_frame ("function_calling"); if (variable_context == 0 || this_shell_function == 0) make_funcname_visible (0); return (result); } /* A convenience routine for use by other parts of the shell to execute a particular shell function. */ int execute_shell_function (var, words) SHELL_VAR *var; WORD_LIST *words; { int ret; struct fd_bitmap *bitmap; bitmap = new_fd_bitmap (FD_BITMAP_DEFAULT_SIZE); begin_unwind_frame ("execute-shell-function"); add_unwind_protect (dispose_fd_bitmap, (char *)bitmap); ret = execute_function (var, words, 0, bitmap, 0, 0); dispose_fd_bitmap (bitmap); discard_unwind_frame ("execute-shell-function"); return ret; } /* Execute a shell builtin or function in a subshell environment. This routine does not return; it only calls exit(). If BUILTIN is non-null, it points to a function to call to execute a shell builtin; otherwise VAR points at the body of a function to execute. WORDS is the arguments to the command, REDIRECTS specifies redirections to perform before the command is executed. */ static void execute_subshell_builtin_or_function (words, redirects, builtin, var, pipe_in, pipe_out, async, fds_to_close, flags) WORD_LIST *words; REDIRECT *redirects; sh_builtin_func_t *builtin; SHELL_VAR *var; int pipe_in, pipe_out, async; struct fd_bitmap *fds_to_close; int flags; { int result, r; #if defined (JOB_CONTROL) int jobs_hack; jobs_hack = (builtin == jobs_builtin) && ((subshell_environment & SUBSHELL_ASYNC) == 0 || pipe_out != NO_PIPE); #endif /* A subshell is neither a login shell nor interactive. */ login_shell = interactive = 0; subshell_environment = SUBSHELL_ASYNC; maybe_make_export_env (); /* XXX - is this needed? */ #if defined (JOB_CONTROL) /* Eradicate all traces of job control after we fork the subshell, so all jobs begun by this subshell are in the same process group as the shell itself. */ /* Allow the output of `jobs' to be piped. */ if (jobs_hack) kill_current_pipeline (); else without_job_control (); set_sigchld_handler (); #endif /* JOB_CONTROL */ set_sigint_handler (); if (fds_to_close) close_fd_bitmap (fds_to_close); do_piping (pipe_in, pipe_out); if (do_redirections (redirects, 1, 0, 0) != 0) exit (EXECUTION_FAILURE); if (builtin) { /* Give builtins a place to jump back to on failure, so we don't go back up to main(). */ result = setjmp (top_level); if (result == EXITPROG) exit (last_command_exit_value); else if (result) exit (EXECUTION_FAILURE); else { r = execute_builtin (builtin, words, flags, 1); if (r == EX_USAGE) r = EX_BADUSAGE; exit (r); } } else exit (execute_function (var, words, flags, fds_to_close, async, 1)); } /* Execute a builtin or function in the current shell context. If BUILTIN is non-null, it is the builtin command to execute, otherwise VAR points to the body of a function. WORDS are the command's arguments, REDIRECTS are the redirections to perform. FDS_TO_CLOSE is the usual bitmap of file descriptors to close. If BUILTIN is exec_builtin, the redirections specified in REDIRECTS are not undone before this function returns. */ static int execute_builtin_or_function (words, builtin, var, redirects, fds_to_close, flags) WORD_LIST *words; sh_builtin_func_t *builtin; SHELL_VAR *var; REDIRECT *redirects; struct fd_bitmap *fds_to_close; int flags; { int result; REDIRECT *saved_undo_list; sh_builtin_func_t *saved_this_shell_builtin; if (do_redirections (redirects, 1, 1, 0) != 0) { cleanup_redirects (redirection_undo_list); redirection_undo_list = (REDIRECT *)NULL; dispose_exec_redirects (); return (EX_REDIRFAIL); /* was EXECUTION_FAILURE */ } saved_this_shell_builtin = this_shell_builtin; saved_undo_list = redirection_undo_list; /* Calling the "exec" builtin changes redirections forever. */ if (builtin == exec_builtin) { dispose_redirects (saved_undo_list); saved_undo_list = exec_redirection_undo_list; exec_redirection_undo_list = (REDIRECT *)NULL; } else dispose_exec_redirects (); if (saved_undo_list) { begin_unwind_frame ("saved redirects"); add_unwind_protect (cleanup_redirects, (char *)saved_undo_list); } redirection_undo_list = (REDIRECT *)NULL; if (builtin) result = execute_builtin (builtin, words, flags, 0); else result = execute_function (var, words, flags, fds_to_close, 0, 0); /* If we are executing the `command' builtin, but this_shell_builtin is set to `exec_builtin', we know that we have something like `command exec [redirection]', since otherwise `exec' would have overwritten the shell and we wouldn't get here. In this case, we want to behave as if the `command' builtin had not been specified and preserve the redirections. */ if (builtin == command_builtin && this_shell_builtin == exec_builtin) { if (saved_undo_list) dispose_redirects (saved_undo_list); redirection_undo_list = exec_redirection_undo_list; saved_undo_list = exec_redirection_undo_list = (REDIRECT *)NULL; discard_unwind_frame ("saved_redirects"); } if (saved_undo_list) { redirection_undo_list = saved_undo_list; discard_unwind_frame ("saved redirects"); } if (redirection_undo_list) { cleanup_redirects (redirection_undo_list); redirection_undo_list = (REDIRECT *)NULL; } return (result); } void setup_async_signals () { #if defined (__BEOS__) set_signal_handler (SIGHUP, SIG_IGN); /* they want csh-like behavior */ #endif #if defined (JOB_CONTROL) if (job_control == 0) #endif { set_signal_handler (SIGINT, SIG_IGN); set_signal_ignored (SIGINT); set_signal_handler (SIGQUIT, SIG_IGN); set_signal_ignored (SIGQUIT); } } /* Execute a simple command that is hopefully defined in a disk file somewhere. 1) fork () 2) connect pipes 3) look up the command 4) do redirections 5) execve () 6) If the execve failed, see if the file has executable mode set. If so, and it isn't a directory, then execute its contents as a shell script. Note that the filename hashing stuff has to take place up here, in the parent. This is probably why the Bourne style shells don't handle it, since that would require them to go through this gnarly hair, for no good reason. */ static void execute_disk_command (words, redirects, command_line, pipe_in, pipe_out, async, fds_to_close, cmdflags) WORD_LIST *words; REDIRECT *redirects; char *command_line; int pipe_in, pipe_out, async; struct fd_bitmap *fds_to_close; int cmdflags; { char *pathname, *command, **args; int nofork; pid_t pid; nofork = (cmdflags & CMD_NO_FORK); /* Don't fork, just exec, if no pipes */ pathname = words->word->word; #if defined (RESTRICTED_SHELL) if (restricted && xstrchr (pathname, '/')) { internal_error ("%s: restricted: cannot specify `/' in command names", pathname); last_command_exit_value = EXECUTION_FAILURE; return; } #endif /* RESTRICTED_SHELL */ command = search_for_command (pathname); if (command) { maybe_make_export_env (); put_command_name_into_env (command); } /* We have to make the child before we check for the non-existence of COMMAND, since we want the error messages to be redirected. */ /* If we can get away without forking and there are no pipes to deal with, don't bother to fork, just directly exec the command. */ if (nofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE) pid = 0; else pid = make_child (savestring (command_line), async); if (pid == 0) { int old_interactive; #if 0 /* This has been disabled for the time being. */ #if !defined (ARG_MAX) || ARG_MAX >= 10240 if (posixly_correct == 0) put_gnu_argv_flags_into_env ((long)getpid (), glob_argv_flags); #endif #endif /* Cancel traps, in trap.c. */ restore_original_signals (); /* restore_original_signals may have undone the work done by make_child to ensure that SIGINT and SIGQUIT are ignored in asynchronous children. */ if (async) { if ((cmdflags & CMD_STDIN_REDIR) && pipe_in == NO_PIPE && (stdin_redirects (redirects) == 0)) async_redirect_stdin (); setup_async_signals (); } /* This functionality is now provided by close-on-exec of the file descriptors manipulated by redirection and piping. Some file descriptors still need to be closed in all children because of the way bash does pipes; fds_to_close is a bitmap of all such file descriptors. */ if (fds_to_close) close_fd_bitmap (fds_to_close); do_piping (pipe_in, pipe_out); old_interactive = interactive; if (async) interactive = 0; subshell_environment = SUBSHELL_FORK; if (redirects && (do_redirections (redirects, 1, 0, 0) != 0)) { #if defined (PROCESS_SUBSTITUTION) /* Try to remove named pipes that may have been created as the result of redirections. */ unlink_fifo_list (); #endif /* PROCESS_SUBSTITUTION */ exit (EXECUTION_FAILURE); } if (async) interactive = old_interactive; if (command == 0) { internal_error ("%s: command not found", pathname); exit (EX_NOTFOUND); /* Posix.2 says the exit status is 127 */ } /* Execve expects the command name to be in args[0]. So we leave it there, in the same format that the user used to type it in. */ args = strvec_from_word_list (words, 0, 0, (int *)NULL); exit (shell_execve (command, args, export_env)); } else { /* Make sure that the pipes are closed in the parent. */ close_pipes (pipe_in, pipe_out); #if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD) unlink_fifo_list (); #endif FREE (command); } } /* CPP defines to decide whether a particular index into the #! line corresponds to a valid interpreter name or argument character, or whitespace. The MSDOS define is to allow \r to be treated the same as \n. */ #if !defined (MSDOS) # define STRINGCHAR(ind) \ (ind < sample_len && !whitespace (sample[ind]) && sample[ind] != '\n') # define WHITECHAR(ind) \ (ind < sample_len && whitespace (sample[ind])) #else /* MSDOS */ # define STRINGCHAR(ind) \ (ind < sample_len && !whitespace (sample[ind]) && sample[ind] != '\n' && sample[ind] != '\r') # define WHITECHAR(ind) \ (ind < sample_len && whitespace (sample[ind])) #endif /* MSDOS */ static char * getinterp (sample, sample_len, endp) char *sample; int sample_len, *endp; { register int i; char *execname; int start; /* Find the name of the interpreter to exec. */ for (i = 2; i < sample_len && whitespace (sample[i]); i++) ; for (start = i; STRINGCHAR(i); i++) ; execname = substring (sample, start, i); if (endp) *endp = i; return execname; } #if !defined (HAVE_HASH_BANG_EXEC) /* If the operating system on which we're running does not handle the #! executable format, then help out. SAMPLE is the text read from the file, SAMPLE_LEN characters. COMMAND is the name of the script; it and ARGS, the arguments given by the user, will become arguments to the specified interpreter. ENV is the environment to pass to the interpreter. The word immediately following the #! is the interpreter to execute. A single argument to the interpreter is allowed. */ static int execute_shell_script (sample, sample_len, command, args, env) char *sample; int sample_len; char *command; char **args, **env; { char *execname, *firstarg; int i, start, size_increment, larry; /* Find the name of the interpreter to exec. */ execname = getinterp (sample, sample_len, &i); size_increment = 1; /* Now the argument, if any. */ for (firstarg = (char *)NULL, start = i; WHITECHAR(i); i++) ; /* If there is more text on the line, then it is an argument for the interpreter. */ if (STRINGCHAR(i)) { for (start = i; STRINGCHAR(i); i++) ; firstarg = substring ((char *)sample, start, i); size_increment = 2; } larry = strvec_len (args) + size_increment; args = strvec_resize (args, larry + 1); for (i = larry - 1; i; i--) args[i] = args[i - size_increment]; args[0] = execname; if (firstarg) { args[1] = firstarg; args[2] = command; } else args[1] = command; args[larry] = (char *)NULL; return (shell_execve (execname, args, env)); } #undef STRINGCHAR #undef WHITECHAR #endif /* !HAVE_HASH_BANG_EXEC */ static void initialize_subshell () { #if defined (ALIAS) /* Forget about any aliases that we knew of. We are in a subshell. */ delete_all_aliases (); #endif /* ALIAS */ #if defined (HISTORY) /* Forget about the history lines we have read. This is a non-interactive subshell. */ history_lines_this_session = 0; #endif #if defined (JOB_CONTROL) /* Forget about the way job control was working. We are in a subshell. */ without_job_control (); set_sigchld_handler (); #endif /* JOB_CONTROL */ /* Reset the values of the shell flags and options. */ reset_shell_flags (); reset_shell_options (); reset_shopt_options (); /* Zero out builtin_env, since this could be a shell script run from a sourced file with a temporary environment supplied to the `source/.' builtin. Such variables are not supposed to be exported (empirical testing with sh and ksh). Just throw it away; don't worry about a memory leak. */ if (vc_isbltnenv (shell_variables)) shell_variables = shell_variables->down; clear_unwind_protect_list (0); /* We're no longer inside a shell function. */ variable_context = return_catch_flag = 0; /* If we're not interactive, close the file descriptor from which we're reading the current shell script. */ if (interactive_shell == 0) unset_bash_input (1); } #if defined (HAVE_SETOSTYPE) && defined (_POSIX_SOURCE) # define SETOSTYPE(x) __setostype(x) #else # define SETOSTYPE(x) #endif #define READ_SAMPLE_BUF(file, buf, len) \ do \ { \ fd = open(file, O_RDONLY); \ if (fd >= 0) \ { \ len = read (fd, buf, 80); \ close (fd); \ } \ else \ len = -1; \ } \ while (0) /* Call execve (), handling interpreting shell scripts, and handling exec failures. */ int shell_execve (command, args, env) char *command; char **args, **env; { struct stat finfo; int larray, i, fd; char sample[80]; int sample_len; SETOSTYPE (0); /* Some systems use for USG/POSIX semantics */ execve (command, args, env); i = errno; /* error from execve() */ SETOSTYPE (1); /* If we get to this point, then start checking out the file. Maybe it is something we can hack ourselves. */ if (i != ENOEXEC) { if ((stat (command, &finfo) == 0) && (S_ISDIR (finfo.st_mode))) internal_error ("%s: is a directory", command); else if (executable_file (command) == 0) { errno = i; file_error (command); } else { /* The file has the execute bits set, but the kernel refuses to run it for some reason. See why. */ #if defined (HAVE_HASH_BANG_EXEC) READ_SAMPLE_BUF (command, sample, sample_len); if (sample_len > 2 && sample[0] == '#' && sample[1] == '!') { char *interp; interp = getinterp (sample, sample_len, (int *)NULL); errno = i; sys_error ("%s: %s: bad interpreter", command, interp ? interp : ""); FREE (interp); return (EX_NOEXEC); } #endif errno = i; file_error (command); } return ((i == ENOENT) ? EX_NOTFOUND : EX_NOEXEC); /* XXX Posix.2 says that exit status is 126 */ } /* This file is executable. If it begins with #!, then help out people with losing operating systems. Otherwise, check to see if it is a binary file by seeing if the contents of the first line (or up to 80 characters) are in the ASCII set. If it's a text file, execute the contents as shell commands, otherwise return 126 (EX_BINARY_FILE). */ READ_SAMPLE_BUF (command, sample, sample_len); if (sample_len == 0) return (EXECUTION_SUCCESS); /* Is this supposed to be an executable script? If so, the format of the line is "#! interpreter [argument]". A single argument is allowed. The BSD kernel restricts the length of the entire line to 32 characters (32 bytes being the size of the BSD exec header), but we allow 80 characters. */ if (sample_len > 0) { #if !defined (HAVE_HASH_BANG_EXEC) if (sample_len > 2 && sample[0] == '#' && sample[1] == '!') return (execute_shell_script (sample, sample_len, command, args, env)); else #endif if (check_binary_file (sample, sample_len)) { internal_error ("%s: cannot execute binary file", command); return (EX_BINARY_FILE); } } /* We have committed to attempting to execute the contents of this file as shell commands. */ initialize_subshell (); set_sigint_handler (); /* Insert the name of this shell into the argument list. */ larray = strvec_len (args) + 1; args = strvec_resize (args, larray + 1); for (i = larray - 1; i; i--) args[i] = args[i - 1]; args[0] = shell_name; args[1] = command; args[larray] = (char *)NULL; if (args[0][0] == '-') args[0]++; #if defined (RESTRICTED_SHELL) if (restricted) change_flag ('r', FLAG_OFF); #endif if (subshell_argv) { /* Can't free subshell_argv[0]; that is shell_name. */ for (i = 1; i < subshell_argc; i++) free (subshell_argv[i]); free (subshell_argv); } dispose_command (currently_executing_command); /* XXX */ currently_executing_command = (COMMAND *)NULL; subshell_argc = larray; subshell_argv = args; subshell_envp = env; unbind_args (); /* remove the positional parameters */ longjmp (subshell_top_level, 1); /*NOTREACHED*/ } static int execute_intern_function (name, function) WORD_DESC *name; COMMAND *function; { SHELL_VAR *var; if (check_identifier (name, posixly_correct) == 0) { if (posixly_correct && interactive_shell == 0) { last_command_exit_value = EX_USAGE; jump_to_top_level (EXITPROG); } return (EXECUTION_FAILURE); } var = find_function (name->word); if (var && (readonly_p (var) || noassign_p (var))) { if (readonly_p (var)) internal_error ("%s: readonly function", var->name); return (EXECUTION_FAILURE); } bind_function (name->word, function); return (EXECUTION_SUCCESS); } #if defined (INCLUDE_UNUSED) #if defined (PROCESS_SUBSTITUTION) void close_all_files () { register int i, fd_table_size; fd_table_size = getdtablesize (); if (fd_table_size > 256) /* clamp to a reasonable value */ fd_table_size = 256; for (i = 3; i < fd_table_size; i++) close (i); } #endif /* PROCESS_SUBSTITUTION */ #endif static void close_pipes (in, out) int in, out; { if (in >= 0) close (in); if (out >= 0) close (out); } /* Redirect input and output to be from and to the specified pipes. NO_PIPE and REDIRECT_BOTH are handled correctly. */ static void do_piping (pipe_in, pipe_out) int pipe_in, pipe_out; { if (pipe_in != NO_PIPE) { if (dup2 (pipe_in, 0) < 0) sys_error ("cannot duplicate fd %d to fd 0", pipe_in); if (pipe_in > 0) close (pipe_in); } if (pipe_out != NO_PIPE) { if (pipe_out != REDIRECT_BOTH) { if (dup2 (pipe_out, 1) < 0) sys_error ("cannot duplicate fd %d to fd 1", pipe_out); if (pipe_out == 0 || pipe_out > 1) close (pipe_out); } else { if (dup2 (1, 2) < 0) sys_error ("cannot duplicate fd 1 to fd 2"); } } }
SPDX-FileCopyrightText: 2021 fosslinux <fosslinux@aussies.space> SPDX-License-Identifier: GPL-2.0-or-later mes libc + setting locale = not worky. --- locale.c 2021-01-15 09:38:55.729307629 +1100 +++ locale.c 2021-01-15 11:19:01.929391346 +1100 @@ -190,7 +190,7 @@ set_lang (var, value) char *var, *value; { - return ((lc_all == 0 || *lc_all == 0) ? setlocale (LC_ALL, value?value:"") != NULL : 0); + return 0; } /* Get the value of one of the locale variables (LC_MESSAGES, LC_CTYPE) */
/* locale.c - Miscellaneous internationalization functions. */ /* Copyright (C) 1996 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #include "bashtypes.h" #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "bashintl.h" #include "bashansi.h" #include <stdio.h> #include "chartypes.h" #include "shell.h" #include "input.h" /* For bash_input */ extern int dump_translatable_strings, dump_po_strings; /* The current locale when the program begins */ static char *default_locale; /* The current domain for textdomain(3). */ static char *default_domain; static char *default_dir; /* tracks the value of LC_ALL; used to override values for other locale categories */ static char *lc_all; /* Set the value of default_locale and make the current locale the system default locale. This should be called very early in main(). */ void set_default_locale () { #if defined (HAVE_SETLOCALE) default_locale = setlocale (LC_ALL, ""); if (default_locale) default_locale = savestring (default_locale); #endif /* HAVE_SETLOCALE */ } /* Set default values for LC_CTYPE, LC_COLLATE, and LC_MESSAGES if they are not specified in the environment, but LANG or LC_ALL is. This should be called from main() after parsing the environment. */ void set_default_locale_vars () { char *val; #if defined (HAVE_SETLOCALE) val = get_string_value ("LC_CTYPE"); if (val == 0 && lc_all && *lc_all) setlocale (LC_CTYPE, lc_all); # if defined (LC_COLLATE) val = get_string_value ("LC_COLLATE"); if (val == 0 && lc_all && *lc_all) setlocale (LC_COLLATE, lc_all); # endif /* LC_COLLATE */ # if defined (LC_MESSAGES) val = get_string_value ("LC_MESSAGES"); if (val == 0 && lc_all && *lc_all) setlocale (LC_MESSAGES, lc_all); # endif /* LC_MESSAGES */ # if defined (LC_NUMERIC) val = get_string_value ("LC_NUMERIC"); if (val == 0 && lc_all && *lc_all) setlocale (LC_NUMERIC, lc_all); # endif /* LC_NUMERIC */ #endif /* HAVE_SETLOCALE */ val = get_string_value ("TEXTDOMAIN"); if (val && *val) { FREE (default_domain); default_domain = savestring (val); textdomain (default_domain); } val = get_string_value ("TEXTDOMAINDIR"); if (val && *val) { FREE (default_dir); default_dir = savestring (val); bindtextdomain (default_domain, default_dir); } } /* Set one of the locale categories (specified by VAR) to VALUE. Returns 1 if successful, 0 otherwise. */ int set_locale_var (var, value) char *var, *value; { if (var[0] == 'T' && var[10] == 0) /* TEXTDOMAIN */ { FREE (default_domain); default_domain = value ? savestring (value) : (char *)NULL; textdomain (default_domain); return (1); } else if (var[0] == 'T') /* TEXTDOMAINDIR */ { FREE (default_dir); default_dir = value ? savestring (value) : (char *)NULL; bindtextdomain (default_domain, default_dir); return (1); } /* var[0] == 'L' && var[1] == 'C' && var[2] == '_' */ else if (var[3] == 'A') /* LC_ALL */ { FREE (lc_all); if (value) lc_all = savestring (value); else if (default_locale) lc_all = savestring (default_locale); else { lc_all = (char *)xmalloc (1); lc_all[0] = '\0'; } #if defined (HAVE_SETLOCALE) return (setlocale (LC_ALL, lc_all) != 0); #else return (1); #endif } #if defined (HAVE_SETLOCALE) else if (var[3] == 'C' && var[4] == 'T') /* LC_CTYPE */ { if (lc_all == 0 || *lc_all == '\0') return (setlocale (LC_CTYPE, value ? value : "") != 0); } else if (var[3] == 'C' && var[4] == 'O') /* LC_COLLATE */ { # if defined (LC_COLLATE) if (lc_all == 0 || *lc_all == '\0') return (setlocale (LC_COLLATE, value ? value : "") != 0); # endif /* LC_COLLATE */ } else if (var[3] == 'M' && var[4] == 'E') /* LC_MESSAGES */ { # if defined (LC_MESSAGES) if (lc_all == 0 || *lc_all == '\0') return (setlocale (LC_MESSAGES, value ? value : "") != 0); # endif /* LC_MESSAGES */ } else if (var[3] == 'N' && var[4] == 'U') /* LC_NUMERIC */ { # if defined (LC_NUMERIC) if (lc_all == 0 || *lc_all == '\0') return (setlocale (LC_NUMERIC, value ? value : "") != 0); # endif /* LC_NUMERIC */ } #endif /* HAVE_SETLOCALE */ return (0); } /* Called when LANG is assigned a value. Sets LC_ALL category with setlocale(3) if that has not already been set. Doesn't change any shell variables. */ int set_lang (var, value) char *var, *value; { return ((lc_all == 0 || *lc_all == 0) ? setlocale (LC_ALL, value?value:"") != NULL : 0); } /* Get the value of one of the locale variables (LC_MESSAGES, LC_CTYPE) */ char * get_locale_var (var) char *var; { char *locale; locale = lc_all; if (locale == 0) locale = get_string_value (var); if (locale == 0) locale = default_locale; return (locale); } /* Translate the contents of STRING, a $"..." quoted string, according to the current locale. In the `C' or `POSIX' locale, or if gettext() is not available, the passed string is returned unchanged. The length of the translated string is returned in LENP, if non-null. */ char * localetrans (string, len, lenp) char *string; int len, *lenp; { char *locale, *t; #if defined (HAVE_GETTEXT) char *translated; int tlen; #endif /* Don't try to translate null strings. */ if (string == 0 || *string == 0) { if (lenp) *lenp = 0; return ((char *)NULL); } locale = get_locale_var ("LC_MESSAGES"); /* If we don't have setlocale() or the current locale is `C' or `POSIX', just return the string. If we don't have gettext(), there's no use doing anything else. */ #if defined (HAVE_GETTEXT) if (locale == 0 || locale[0] == '\0' || (locale[0] == 'C' && locale[1] == '\0') || STREQ (locale, "POSIX")) #endif { t = (char *)xmalloc (len + 1); strcpy (t, string); if (lenp) *lenp = len; return (t); } #if defined (HAVE_GETTEXT) /* Now try to translate it. */ translated = gettext (string); if (translated == string) /* gettext returns its argument if untranslatable */ { t = (char *)xmalloc (len + 1); strcpy (t, string); if (lenp) *lenp = len; } else { tlen = strlen (translated); t = (char *)xmalloc (tlen + 1); strcpy (t, translated); if (lenp) *lenp = tlen; } return (t); #endif /* HAVE_GETTEXT */ } /* Change a bash string into a string suitable for inclusion in a `po' file. This backslash-escapes `"' and `\' and changes newlines into \\\n"\n". */ char * mk_msgstr (string, foundnlp) char *string; int *foundnlp; { register int c, len; char *result, *r, *s; for (len = 0, s = string; s && *s; s++) { len++; if (*s == '"' || *s == '\\') len++; else if (*s == '\n') len += 5; } r = result = (char *)xmalloc (len + 3); *r++ = '"'; for (s = string; s && (c = *s); s++) { if (c == '\n') /* <NL> -> \n"<NL>" */ { *r++ = '\\'; *r++ = 'n'; *r++ = '"'; *r++ = '\n'; *r++ = '"'; if (foundnlp) *foundnlp = 1; continue; } if (c == '"' || c == '\\') *r++ = '\\'; *r++ = c; } *r++ = '"'; *r++ = '\0'; return result; } /* $"..." -- Translate the portion of STRING between START and END according to current locale using gettext (if available) and return the result. The caller will take care of leaving the quotes intact. The string will be left without the leading `$' by the caller. If translation is performed, the translated string will be double-quoted by the caller. The length of the translated string is returned in LENP, if non-null. */ char * localeexpand (string, start, end, lineno, lenp) char *string; int start, end, lineno, *lenp; { int len, tlen, foundnl; char *temp, *t, *t2; temp = (char *)xmalloc (end - start + 1); for (tlen = 0, len = start; len < end; ) temp[tlen++] = string[len++]; temp[tlen] = '\0'; /* If we're just dumping translatable strings, don't do anything with the string itself, but if we're dumping in `po' file format, convert it into a form more palatable to gettext(3) and friends by quoting `"' and `\' with backslashes and converting <NL> into `\n"<NL>"'. If we find a newline in TEMP, we first output a `msgid ""' line and then the translated string; otherwise we output the `msgid' and translated string all on one line. */ if (dump_translatable_strings) { if (dump_po_strings) { foundnl = 0; t = mk_msgstr (temp, &foundnl); t2 = foundnl ? "\"\"\n" : ""; printf ("#: %s:%d\nmsgid %s%s\nmsgstr \"\"\n", yy_input_name (), lineno, t2, t); free (t); } else printf ("\"%s\"\n", temp); if (lenp) *lenp = tlen; return (temp); } else if (*temp) { t = localetrans (temp, tlen, &len); free (temp); if (lenp) *lenp = len; return (t); } else { if (lenp) *lenp = 0; return (temp); } }
SPDX-FileCopyrightText: 2021 fosslinux <fosslinux@aussies.space> SPDX-License-Identifier: GPL-2.0-or-later We do not have /dev at this stage of the bootstrap, including /dev/tty. For some reason, bash has a fixation on /dev/tty, even though we are not interactive. Removing this check entirely fixes this issue. diff --color -ru shell.c --- shell.c 2002-07-02 01:27:11.000000000 +1000 +++ shell.c 2021-01-16 11:23:36.407287955 +1100 @@ -342,8 +342,6 @@ # endif #endif - check_dev_tty (); - #ifdef __CYGWIN__ _cygwin32_check_tmp ();
/* shell.c -- GNU's idea of the POSIX shell specification. */ /* Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. Birthdate: Sunday, January 10th, 1988. Initial author: Brian Fox */ #define INSTALL_DEBUG_MODE #include "config.h" #include "bashtypes.h" #ifndef _MINIX # include <sys/file.h> #endif #include "posixstat.h" #include "posixtime.h" #include "bashansi.h" #include <stdio.h> #include <signal.h> #include <errno.h> #include "filecntl.h" #include <pwd.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #define NEED_SH_SETLINEBUF_DECL /* used in externs.h */ #include "shell.h" #include "flags.h" #include "trap.h" #include "mailcheck.h" #include "builtins.h" #include "builtins/common.h" #if defined (JOB_CONTROL) #include "jobs.h" #endif /* JOB_CONTROL */ #include "input.h" #include "execute_cmd.h" #include "findcmd.h" #if defined (USING_BASH_MALLOC) && defined (DEBUG) && !defined (DISABLE_MALLOC_WRAPPERS) # include <malloc/shmalloc.h> #endif #if defined (HISTORY) # include "bashhist.h" # include <readline/history.h> #endif #include <tilde/tilde.h> #include <glob/strmatch.h> #if defined (__OPENNT) # include <opennt/opennt.h> #endif #if !defined (HAVE_GETPW_DECLS) extern struct passwd *getpwuid (); #endif /* !HAVE_GETPW_DECLS */ #if !defined (errno) extern int errno; #endif #if defined (NO_MAIN_ENV_ARG) extern char **environ; /* used if no third argument to main() */ #endif extern char *dist_version, *release_status; extern int patch_level, build_version; extern int shell_level; extern int subshell_environment; extern int last_command_exit_value; extern int line_number; extern char *primary_prompt, *secondary_prompt; extern int expand_aliases; extern char *this_command_name; extern int array_needs_making; /* Non-zero means that this shell has already been run; i.e. you should call shell_reinitialize () if you need to start afresh. */ int shell_initialized = 0; COMMAND *global_command = (COMMAND *)NULL; /* Information about the current user. */ struct user_info current_user = { (uid_t)-1, (uid_t)-1, (gid_t)-1, (gid_t)-1, (char *)NULL, (char *)NULL, (char *)NULL }; /* The current host's name. */ char *current_host_name = (char *)NULL; /* Non-zero means that this shell is a login shell. Specifically: 0 = not login shell. 1 = login shell from getty (or equivalent fake out) -1 = login shell from "--login" (or -l) flag. -2 = both from getty, and from flag. */ int login_shell = 0; /* Non-zero means that at this moment, the shell is interactive. In general, this means that the shell is at this moment reading input from the keyboard. */ int interactive = 0; /* Non-zero means that the shell was started as an interactive shell. */ int interactive_shell = 0; /* Non-zero means to send a SIGHUP to all jobs when an interactive login shell exits. */ int hup_on_exit = 0; /* Tells what state the shell was in when it started: 0 = non-interactive shell script 1 = interactive 2 = -c command 3 = wordexp evaluation This is a superset of the information provided by interactive_shell. */ int startup_state = 0; /* Special debugging helper. */ int debugging_login_shell = 0; /* The environment that the shell passes to other commands. */ char **shell_environment; /* Non-zero when we are executing a top-level command. */ int executing = 0; /* The number of commands executed so far. */ int current_command_number = 1; /* Non-zero is the recursion depth for commands. */ int indirection_level = 0; /* The name of this shell, as taken from argv[0]. */ char *shell_name = (char *)NULL; /* time in seconds when the shell was started */ time_t shell_start_time; /* Are we running in an emacs shell window? */ int running_under_emacs; /* The name of the .(shell)rc file. */ static char *bashrc_file = "~/.bashrc"; /* Non-zero means to act more like the Bourne shell on startup. */ static int act_like_sh; /* Non-zero if this shell is being run by `su'. */ static int su_shell; /* Non-zero if we have already expanded and sourced $ENV. */ static int sourced_env; /* Is this shell running setuid? */ static int running_setuid; /* Values for the long-winded argument names. */ static int debugging; /* Do debugging things. */ static int no_rc; /* Don't execute ~/.bashrc */ static int no_profile; /* Don't execute .profile */ static int do_version; /* Display interesting version info. */ static int make_login_shell; /* Make this shell be a `-bash' shell. */ static int want_initial_help; /* --help option */ int no_line_editing = 0; /* Don't do fancy line editing. */ int posixly_correct = 0; /* Non-zero means posix.2 superset. */ int dump_translatable_strings; /* Dump strings in $"...", don't execute. */ int dump_po_strings; /* Dump strings in $"..." in po format */ int wordexp_only = 0; /* Do word expansion only */ /* Some long-winded argument names. These are obviously new. */ #define Int 1 #define Charp 2 struct { char *name; int type; int *int_value; char **char_value; } long_args[] = { { "debug", Int, &debugging, (char **)0x0 }, { "dump-po-strings", Int, &dump_po_strings, (char **)0x0 }, { "dump-strings", Int, &dump_translatable_strings, (char **)0x0 }, { "help", Int, &want_initial_help, (char **)0x0 }, { "init-file", Charp, (int *)0x0, &bashrc_file }, { "login", Int, &make_login_shell, (char **)0x0 }, { "noediting", Int, &no_line_editing, (char **)0x0 }, { "noprofile", Int, &no_profile, (char **)0x0 }, { "norc", Int, &no_rc, (char **)0x0 }, { "posix", Int, &posixly_correct, (char **)0x0 }, { "rcfile", Charp, (int *)0x0, &bashrc_file }, #if defined (RESTRICTED_SHELL) { "restricted", Int, &restricted, (char **)0x0 }, #endif { "verbose", Int, &echo_input_at_read, (char **)0x0 }, { "version", Int, &do_version, (char **)0x0 }, { "wordexp", Int, &wordexp_only, (char **)0x0 }, { (char *)0x0, Int, (int *)0x0, (char **)0x0 } }; /* These are extern so execute_simple_command can set them, and then longjmp back to main to execute a shell script, instead of calling main () again and resulting in indefinite, possibly fatal, stack growth. */ procenv_t subshell_top_level; int subshell_argc; char **subshell_argv; char **subshell_envp; #if defined (BUFFERED_INPUT) /* The file descriptor from which the shell is reading input. */ int default_buffered_input = -1; #endif /* The following two variables are not static so they can show up in $-. */ int read_from_stdin; /* -s flag supplied */ int want_pending_command; /* -c flag supplied */ int malloc_trace_at_exit = 0; static int shell_reinitialized = 0; static char *local_pending_command; static FILE *default_input; static STRING_INT_ALIST *shopt_alist; static int shopt_ind = 0, shopt_len = 0; static int parse_long_options __P((char **, int, int)); static int parse_shell_options __P((char **, int, int)); static int bind_args __P((char **, int, int, int)); static void add_shopt_to_alist __P((char *, int)); static void run_shopt_alist __P((void)); static void execute_env_file __P((char *)); static void run_startup_files __P((void)); static int open_shell_script __P((char *)); static void set_bash_input __P((void)); static int run_one_command __P((char *)); static int run_wordexp __P((char *)); static int uidget __P((void)); static void init_interactive __P((void)); static void init_noninteractive __P((void)); static void set_shell_name __P((char *)); static void shell_initialize __P((void)); static void shell_reinitialize __P((void)); static void show_shell_usage __P((FILE *, int)); #ifdef __CYGWIN__ static void _cygwin32_check_tmp () { struct stat sb; if (stat ("/tmp", &sb) < 0) internal_warning ("could not find /tmp, please create!"); else { if (S_ISDIR (sb.st_mode) == 0) internal_warning ("/tmp must be a valid directory name"); } } #endif /* __CYGWIN__ */ #if defined (NO_MAIN_ENV_ARG) /* systems without third argument to main() */ int main (argc, argv) int argc; char **argv; #else /* !NO_MAIN_ENV_ARG */ int main (argc, argv, env) int argc; char **argv, **env; #endif /* !NO_MAIN_ENV_ARG */ { register int i; int code, old_errexit_flag; #if defined (RESTRICTED_SHELL) int saverst; #endif volatile int locally_skip_execution; volatile int arg_index, top_level_arg_index; #ifdef __OPENNT char **env; env = environ; #endif /* __OPENNT */ USE_VAR(argc); USE_VAR(argv); USE_VAR(env); USE_VAR(code); USE_VAR(old_errexit_flag); #if defined (RESTRICTED_SHELL) USE_VAR(saverst); #endif /* Catch early SIGINTs. */ code = setjmp (top_level); if (code) exit (2); #if defined (USING_BASH_MALLOC) && defined (DEBUG) && !defined (DISABLE_MALLOC_WRAPPERS) # if 1 malloc_set_register (1); # endif #endif check_dev_tty (); #ifdef __CYGWIN__ _cygwin32_check_tmp (); #endif /* __CYGWIN__ */ /* Wait forever if we are debugging a login shell. */ while (debugging_login_shell); set_default_locale (); running_setuid = uidget (); if (getenv ("POSIXLY_CORRECT") || getenv ("POSIX_PEDANTIC")) posixly_correct = 1; #if defined (USE_GNU_MALLOC_LIBRARY) mcheck (programming_error, (void (*) ())0); #endif /* USE_GNU_MALLOC_LIBRARY */ if (setjmp (subshell_top_level)) { argc = subshell_argc; argv = subshell_argv; env = subshell_envp; sourced_env = 0; } shell_reinitialized = 0; /* Initialize `local' variables for all `invocations' of main (). */ arg_index = 1; local_pending_command = (char *)NULL; want_pending_command = locally_skip_execution = read_from_stdin = 0; default_input = stdin; #if defined (BUFFERED_INPUT) default_buffered_input = -1; #endif /* Fix for the `infinite process creation' bug when running shell scripts from startup files on System V. */ login_shell = make_login_shell = 0; /* If this shell has already been run, then reinitialize it to a vanilla state. */ if (shell_initialized || shell_name) { /* Make sure that we do not infinitely recurse as a login shell. */ if (*shell_name == '-') shell_name++; shell_reinitialize (); if (setjmp (top_level)) exit (2); } shell_environment = env; set_shell_name (argv[0]); shell_start_time = NOW; /* NOW now defined in general.h */ /* Parse argument flags from the input line. */ /* Find full word arguments first. */ arg_index = parse_long_options (argv, arg_index, argc); if (want_initial_help) { show_shell_usage (stdout, 1); exit (EXECUTION_SUCCESS); } if (do_version) { show_shell_version (1); exit (EXECUTION_SUCCESS); } /* All done with full word options; do standard shell option parsing.*/ this_command_name = shell_name; /* for error reporting */ arg_index = parse_shell_options (argv, arg_index, argc); /* If user supplied the "--login" (or -l) flag, then set and invert LOGIN_SHELL. */ if (make_login_shell) { login_shell++; login_shell = -login_shell; } set_login_shell (login_shell != 0); if (dump_po_strings) dump_translatable_strings = 1; if (dump_translatable_strings) read_but_dont_execute = 1; if (running_setuid && privileged_mode == 0) disable_priv_mode (); /* Need to get the argument to a -c option processed in the above loop. The next arg is a command to execute, and the following args are $0...$n respectively. */ if (want_pending_command) { local_pending_command = argv[arg_index]; if (local_pending_command == 0) { report_error ("-c: option requires an argument"); exit (EX_USAGE); } arg_index++; } this_command_name = (char *)NULL; cmd_init(); /* initialize the command object caches */ /* First, let the outside world know about our interactive status. A shell is interactive if the `-i' flag was given, or if all of the following conditions are met: no -c command no arguments remaining or the -s flag given standard input is a terminal standard output is a terminal Refer to Posix.2, the description of the `sh' utility. */ if (forced_interactive || /* -i flag */ (!local_pending_command && /* No -c command and ... */ wordexp_only == 0 && /* No --wordexp and ... */ ((arg_index == argc) || /* no remaining args or... */ read_from_stdin) && /* -s flag with args, and */ isatty (fileno (stdin)) && /* Input is a terminal and */ isatty (fileno (stdout)))) /* output is a terminal. */ init_interactive (); else init_noninteractive (); #define CLOSE_FDS_AT_LOGIN #if defined (CLOSE_FDS_AT_LOGIN) /* * Some systems have the bad habit of starting login shells with lots of open * file descriptors. For instance, most systems that have picked up the * pre-4.0 Sun YP code leave a file descriptor open each time you call one * of the getpw* functions, and it's set to be open across execs. That * means one for login, one for xterm, one for shelltool, etc. */ if (login_shell && interactive_shell) { for (i = 3; i < 20; i++) close (i); } #endif /* CLOSE_FDS_AT_LOGIN */ /* If we're in a strict Posix.2 mode, turn on interactive comments, alias expansion in non-interactive shells, and other Posix.2 things. */ if (posixly_correct) { bind_variable ("POSIXLY_CORRECT", "y"); sv_strict_posix ("POSIXLY_CORRECT"); } /* Now we run the shopt_alist and process the options. */ if (shopt_alist) run_shopt_alist (); /* From here on in, the shell must be a normal functioning shell. Variables from the environment are expected to be set, etc. */ shell_initialize (); set_default_locale_vars (); if (interactive_shell) { char *term; term = getenv ("TERM"); no_line_editing |= term && (STREQ (term, "emacs")); term = getenv ("EMACS"); running_under_emacs = term ? ((strmatch ("*term*", term, 0) == 0) ? 2 : 1) : 0; no_line_editing |= term && term[0] == 't' && term[1] == '\0'; } top_level_arg_index = arg_index; old_errexit_flag = exit_immediately_on_error; /* Give this shell a place to longjmp to before executing the startup files. This allows users to press C-c to abort the lengthy startup. */ code = setjmp (top_level); if (code) { if (code == EXITPROG) exit_shell (last_command_exit_value); else { #if defined (JOB_CONTROL) /* Reset job control, since run_startup_files turned it off. */ set_job_control (interactive_shell); #endif /* Reset value of `set -e', since it's turned off before running the startup files. */ exit_immediately_on_error += old_errexit_flag; locally_skip_execution++; } } arg_index = top_level_arg_index; /* Execute the start-up scripts. */ if (interactive_shell == 0) { unbind_variable ("PS1"); unbind_variable ("PS2"); interactive = 0; #if 0 /* This has already been done by init_noninteractive */ expand_aliases = posixly_correct; #endif } else { change_flag ('i', FLAG_ON); interactive = 1; } #if defined (RESTRICTED_SHELL) /* Set restricted_shell based on whether the basename of $0 indicates that the shell should be restricted or if the `-r' option was supplied at startup. */ restricted_shell = shell_is_restricted (shell_name); /* If the `-r' option is supplied at invocation, make sure that the shell is not in restricted mode when running the startup files. */ saverst = restricted; restricted = 0; #endif /* The startup files are run with `set -e' temporarily disabled. */ if (locally_skip_execution == 0 && running_setuid == 0) { old_errexit_flag = exit_immediately_on_error; exit_immediately_on_error = 0; run_startup_files (); exit_immediately_on_error += old_errexit_flag; } /* If we are invoked as `sh', turn on Posix mode. */ if (act_like_sh) { bind_variable ("POSIXLY_CORRECT", "y"); sv_strict_posix ("POSIXLY_CORRECT"); } #if defined (RESTRICTED_SHELL) /* Turn on the restrictions after executing the startup files. This means that `bash -r' or `set -r' invoked from a startup file will turn on the restrictions after the startup files are executed. */ restricted = saverst || restricted; if (shell_reinitialized == 0) maybe_make_restricted (shell_name); #endif /* RESTRICTED_SHELL */ if (wordexp_only) { startup_state = 3; last_command_exit_value = run_wordexp (argv[arg_index]); exit_shell (last_command_exit_value); } if (local_pending_command) { arg_index = bind_args (argv, arg_index, argc, 0); startup_state = 2; #if defined (ONESHOT) executing = 1; run_one_command (local_pending_command); exit_shell (last_command_exit_value); #else /* ONESHOT */ with_input_from_string (local_pending_command, "-c"); goto read_and_execute; #endif /* !ONESHOT */ } /* Get possible input filename and set up default_buffered_input or default_input as appropriate. */ if (arg_index != argc && read_from_stdin == 0) { open_shell_script (argv[arg_index]); arg_index++; } else if (interactive == 0) /* In this mode, bash is reading a script from stdin, which is a pipe or redirected file. */ #if defined (BUFFERED_INPUT) default_buffered_input = fileno (stdin); /* == 0 */ #else setbuf (default_input, (char *)NULL); #endif /* !BUFFERED_INPUT */ set_bash_input (); /* Bind remaining args to $1 ... $n */ arg_index = bind_args (argv, arg_index, argc, 1); /* Do the things that should be done only for interactive shells. */ if (interactive_shell) { /* Set up for checking for presence of mail. */ remember_mail_dates (); reset_mail_timer (); #if defined (HISTORY) /* Initialize the interactive history stuff. */ bash_initialize_history (); /* Don't load the history from the history file if we've already saved some lines in this session (e.g., by putting `history -s xx' into one of the startup files). */ if (shell_initialized == 0 && history_lines_this_session == 0) load_history (); #endif /* HISTORY */ /* Initialize terminal state for interactive shells after the .bash_profile and .bashrc are interpreted. */ get_tty_state (); } #if !defined (ONESHOT) read_and_execute: #endif /* !ONESHOT */ shell_initialized = 1; /* Read commands until exit condition. */ reader_loop (); exit_shell (last_command_exit_value); } static int parse_long_options (argv, arg_start, arg_end) char **argv; int arg_start, arg_end; { int arg_index, longarg, i; char *arg_string; arg_index = arg_start; while ((arg_index != arg_end) && (arg_string = argv[arg_index]) && (*arg_string == '-')) { longarg = 0; /* Make --login equivalent to -login. */ if (arg_string[1] == '-' && arg_string[2]) { longarg = 1; arg_string++; } for (i = 0; long_args[i].name; i++) { if (STREQ (arg_string + 1, long_args[i].name)) { if (long_args[i].type == Int) *long_args[i].int_value = 1; else if (argv[++arg_index] == 0) { report_error ("%s: option requires an argument", long_args[i].name); exit (EX_USAGE); } else *long_args[i].char_value = argv[arg_index]; break; } } if (long_args[i].name == 0) { if (longarg) { report_error ("%s: invalid option", argv[arg_index]); show_shell_usage (stderr, 0); exit (EX_USAGE); } break; /* No such argument. Maybe flag arg. */ } arg_index++; } return (arg_index); } static int parse_shell_options (argv, arg_start, arg_end) char **argv; int arg_start, arg_end; { int arg_index; int arg_character, on_or_off, next_arg, i; char *o_option, *arg_string; arg_index = arg_start; while (arg_index != arg_end && (arg_string = argv[arg_index]) && (*arg_string == '-' || *arg_string == '+')) { /* There are flag arguments, so parse them. */ next_arg = arg_index + 1; /* A single `-' signals the end of options. From the 4.3 BSD sh. An option `--' means the same thing; this is the standard getopt(3) meaning. */ if (arg_string[0] == '-' && (arg_string[1] == '\0' || (arg_string[1] == '-' && arg_string[2] == '\0'))) return (next_arg); i = 1; on_or_off = arg_string[0]; while (arg_character = arg_string[i++]) { switch (arg_character) { case 'c': want_pending_command = 1; break; case 'l': make_login_shell = 1; break; case 's': read_from_stdin = 1; break; case 'o': o_option = argv[next_arg]; if (o_option == 0) { list_minus_o_opts (-1, (on_or_off == '-') ? 0 : 1); break; } if (set_minus_o_option (on_or_off, o_option) != EXECUTION_SUCCESS) exit (EX_USAGE); next_arg++; break; case 'O': /* Since some of these can be overridden by the normal interactive/non-interactive shell initialization or initializing posix mode, we save the options and process them after initialization. */ o_option = argv[next_arg]; if (o_option == 0) { shopt_listopt (o_option, (on_or_off == '-') ? 0 : 1); break; } add_shopt_to_alist (o_option, on_or_off); next_arg++; break; case 'D': dump_translatable_strings = 1; break; default: if (change_flag (arg_character, on_or_off) == FLAG_ERROR) { report_error ("%c%c: invalid option", on_or_off, arg_character); show_shell_usage (stderr, 0); exit (EX_USAGE); } } } /* Can't do just a simple increment anymore -- what about "bash -abouo emacs ignoreeof -hP"? */ arg_index = next_arg; } return (arg_index); } /* Exit the shell with status S. */ void exit_shell (s) int s; { /* Do trap[0] if defined. Allow it to override the exit status passed to us. */ if (signal_is_trapped (0)) s = run_exit_trap (); #if defined (PROCESS_SUBSTITUTION) unlink_fifo_list (); #endif /* PROCESS_SUBSTITUTION */ #if defined (HISTORY) if (interactive_shell) maybe_save_shell_history (); #endif /* HISTORY */ #if defined (JOB_CONTROL) /* If the user has run `shopt -s huponexit', hangup all jobs when we exit an interactive login shell. ksh does this unconditionally. */ if (interactive_shell && login_shell && hup_on_exit) hangup_all_jobs (); /* If this shell is interactive, terminate all stopped jobs and restore the original terminal process group. Don't do this if we're in a subshell and calling exit_shell after, for example, a failed word expansion. */ if (subshell_environment == 0) end_job_control (); #endif /* JOB_CONTROL */ /* Always return the exit status of the last command to our parent. */ sh_exit (s); } /* A wrapper for exit that (optionally) can do other things, like malloc statistics tracing. */ void sh_exit (s) int s; { #if defined (MALLOC_DEBUG) && defined (USING_BASH_MALLOC) if (malloc_trace_at_exit) trace_malloc_stats (get_name_for_error (), (char *)NULL); #endif exit (s); } /* Source the bash startup files. If POSIXLY_CORRECT is non-zero, we obey the Posix.2 startup file rules: $ENV is expanded, and if the file it names exists, that file is sourced. The Posix.2 rules are in effect for interactive shells only. (section 4.56.5.3) */ /* Execute ~/.bashrc for most shells. Never execute it if ACT_LIKE_SH is set, or if NO_RC is set. If the executable file "/usr/gnu/src/bash/foo" contains: #!/usr/gnu/bin/bash echo hello then: COMMAND EXECUTE BASHRC -------------------------------- bash -c foo NO bash foo NO foo NO rsh machine ls YES (for rsh, which calls `bash -c') rsh machine foo YES (for shell started by rsh) NO (for foo!) echo ls | bash NO login NO bash YES */ static void execute_env_file (env_file) char *env_file; { char *fn; if (env_file && *env_file) { fn = expand_string_unsplit_to_string (env_file, Q_DOUBLE_QUOTES); if (fn && *fn) maybe_execute_file (fn, 1); FREE (fn); } } static void run_startup_files () { #if defined (JOB_CONTROL) int old_job_control; #endif int sourced_login, run_by_ssh; /* get the rshd/sshd case out of the way first. */ if (interactive_shell == 0 && no_rc == 0 && login_shell == 0 && act_like_sh == 0 && local_pending_command) { #ifdef SSH_SOURCE_BASHRC run_by_ssh = (find_variable ("SSH_CLIENT") != (SHELL_VAR *)0) || (find_variable ("SSH2_CLIENT") != (SHELL_VAR *)0); #else run_by_ssh = 0; #endif /* If we were run by sshd or we think we were run by rshd, execute ~/.bashrc if we are a top-level shell. */ if ((run_by_ssh || isnetconn (fileno (stdin))) && shell_level < 2) { #ifdef SYS_BASHRC # if defined (__OPENNT) maybe_execute_file (_prefixInstallPath(SYS_BASHRC, NULL, 0), 1); # else maybe_execute_file (SYS_BASHRC, 1); # endif #endif maybe_execute_file (bashrc_file, 1); return; } } #if defined (JOB_CONTROL) /* Startup files should be run without job control enabled. */ old_job_control = interactive_shell ? set_job_control (0) : 0; #endif sourced_login = 0; /* A shell begun with the --login (or -l) flag that is not in posix mode runs the login shell startup files, no matter whether or not it is interactive. If NON_INTERACTIVE_LOGIN_SHELLS is defined, run the startup files if argv[0][0] == '-' as well. */ #if defined (NON_INTERACTIVE_LOGIN_SHELLS) if (login_shell && posixly_correct == 0) #else if (login_shell < 0 && posixly_correct == 0) #endif { /* We don't execute .bashrc for login shells. */ no_rc++; /* Execute /etc/profile and one of the personal login shell initialization files. */ if (no_profile == 0) { maybe_execute_file (SYS_PROFILE, 1); if (act_like_sh) /* sh */ maybe_execute_file ("~/.profile", 1); else if ((maybe_execute_file ("~/.bash_profile", 1) == 0) && (maybe_execute_file ("~/.bash_login", 1) == 0)) /* bash */ maybe_execute_file ("~/.profile", 1); } sourced_login = 1; } /* A non-interactive shell not named `sh' and not in posix mode reads and executes commands from $BASH_ENV. If `su' starts a shell with `-c cmd' and `-su' as the name of the shell, we want to read the startup files. No other non-interactive shells read any startup files. */ if (interactive_shell == 0 && !(su_shell && login_shell)) { if (posixly_correct == 0 && act_like_sh == 0 && privileged_mode == 0 && sourced_env++ == 0) execute_env_file (get_string_value ("BASH_ENV")); return; } /* Interactive shell or `-su' shell. */ if (posixly_correct == 0) /* bash, sh */ { if (login_shell && sourced_login++ == 0) { /* We don't execute .bashrc for login shells. */ no_rc++; /* Execute /etc/profile and one of the personal login shell initialization files. */ if (no_profile == 0) { maybe_execute_file (SYS_PROFILE, 1); if (act_like_sh) /* sh */ maybe_execute_file ("~/.profile", 1); else if ((maybe_execute_file ("~/.bash_profile", 1) == 0) && (maybe_execute_file ("~/.bash_login", 1) == 0)) /* bash */ maybe_execute_file ("~/.profile", 1); } } /* bash */ if (act_like_sh == 0 && no_rc == 0) { #ifdef SYS_BASHRC # if defined (__OPENNT) maybe_execute_file (_prefixInstallPath(SYS_BASHRC, NULL, 0), 1); # else maybe_execute_file (SYS_BASHRC, 1); # endif #endif maybe_execute_file (bashrc_file, 1); } /* sh */ else if (act_like_sh && privileged_mode == 0 && sourced_env++ == 0) execute_env_file (get_string_value ("ENV")); } else /* bash --posix, sh --posix */ { /* bash and sh */ if (interactive_shell && privileged_mode == 0 && sourced_env++ == 0) execute_env_file (get_string_value ("ENV")); } #if defined (JOB_CONTROL) set_job_control (old_job_control); #endif } #if defined (RESTRICTED_SHELL) /* Return 1 if the shell should be a restricted one based on NAME or the value of `restricted'. Don't actually do anything, just return a boolean value. */ int shell_is_restricted (name) char *name; { char *temp; if (restricted) return 1; temp = base_pathname (name); return (STREQ (temp, RESTRICTED_SHELL_NAME)); } /* Perhaps make this shell a `restricted' one, based on NAME. If the basename of NAME is "rbash", then this shell is restricted. The name of the restricted shell is a configurable option, see config.h. In a restricted shell, PATH, SHELL, ENV, and BASH_ENV are read-only and non-unsettable. Do this also if `restricted' is already set to 1; maybe the shell was started with -r. */ int maybe_make_restricted (name) char *name; { char *temp; temp = base_pathname (name); if (restricted || (STREQ (temp, RESTRICTED_SHELL_NAME))) { set_var_read_only ("PATH"); set_var_read_only ("SHELL"); set_var_read_only ("ENV"); set_var_read_only ("BASH_ENV"); restricted = 1; } return (restricted); } #endif /* RESTRICTED_SHELL */ /* Fetch the current set of uids and gids and return 1 if we're running setuid or setgid. */ static int uidget () { uid_t u; u = getuid (); if (current_user.uid != u) { FREE (current_user.user_name); FREE (current_user.shell); FREE (current_user.home_dir); current_user.user_name = current_user.shell = current_user.home_dir = (char *)NULL; } current_user.uid = u; current_user.gid = getgid (); current_user.euid = geteuid (); current_user.egid = getegid (); /* See whether or not we are running setuid or setgid. */ return (current_user.uid != current_user.euid) || (current_user.gid != current_user.egid); } void disable_priv_mode () { setuid (current_user.uid); setgid (current_user.gid); current_user.euid = current_user.uid; current_user.egid = current_user.gid; } static int run_wordexp (words) char *words; { int code, nw, nb; WORD_LIST *wl, *result; code = setjmp (top_level); if (code != NOT_JUMPED) { switch (code) { /* Some kind of throw to top_level has occured. */ case FORCE_EOF: return last_command_exit_value = 127; case EXITPROG: return last_command_exit_value; case DISCARD: return last_command_exit_value = 1; default: command_error ("run_wordexp", CMDERR_BADJUMP, code, 0); } } /* Run it through the parser to get a list of words and expand them */ if (words && *words) { with_input_from_string (words, "--wordexp"); if (parse_command () != 0) return (126); if (global_command == 0) { printf ("0\n0\n"); return (0); } if (global_command->type != cm_simple) return (126); wl = global_command->value.Simple->words; result = wl ? expand_words_no_vars (wl) : (WORD_LIST *)0; } else result = (WORD_LIST *)0; last_command_exit_value = 0; if (result == 0) { printf ("0\n0\n"); return (0); } /* Count up the number of words and bytes, and print them. Don't count the trailing NUL byte. */ for (nw = nb = 0, wl = result; wl; wl = wl->next) { nw++; nb += strlen (wl->word->word); } printf ("%u\n%u\n", nw, nb); /* Print each word on a separate line. This will have to be changed when the interface to glibc is completed. */ for (wl = result; wl; wl = wl->next) printf ("%s\n", wl->word->word); return (0); } #if defined (ONESHOT) /* Run one command, given as the argument to the -c option. Tell parse_and_execute not to fork for a simple command. */ static int run_one_command (command) char *command; { int code; code = setjmp (top_level); if (code != NOT_JUMPED) { #if defined (PROCESS_SUBSTITUTION) unlink_fifo_list (); #endif /* PROCESS_SUBSTITUTION */ switch (code) { /* Some kind of throw to top_level has occured. */ case FORCE_EOF: return last_command_exit_value = 127; case EXITPROG: return last_command_exit_value; case DISCARD: return last_command_exit_value = 1; default: command_error ("run_one_command", CMDERR_BADJUMP, code, 0); } } return (parse_and_execute (savestring (command), "-c", SEVAL_NOHIST)); } #endif /* ONESHOT */ static int bind_args (argv, arg_start, arg_end, start_index) char **argv; int arg_start, arg_end, start_index; { register int i; WORD_LIST *args; for (i = arg_start, args = (WORD_LIST *)NULL; i != arg_end; i++) args = make_word_list (make_word (argv[i]), args); if (args) { args = REVERSE_LIST (args, WORD_LIST *); if (start_index == 0) /* bind to $0...$n for sh -c command */ { /* Posix.2 4.56.3 says that the first argument after sh -c command becomes $0, and the rest of the arguments become $1...$n */ shell_name = savestring (args->word->word); FREE (dollar_vars[0]); dollar_vars[0] = savestring (args->word->word); remember_args (args->next, 1); } else /* bind to $1...$n for shell script */ remember_args (args, 1); dispose_words (args); } return (i); } void unbind_args () { remember_args ((WORD_LIST *)NULL, 1); } static int open_shell_script (script_name) char *script_name; { int fd, e, fd_is_tty; char *filename, *path_filename; char sample[80]; int sample_len; struct stat sb; free (dollar_vars[0]); dollar_vars[0] = savestring (script_name); filename = savestring (script_name); fd = open (filename, O_RDONLY); if ((fd < 0) && (errno == ENOENT) && (absolute_program (filename) == 0)) { e = errno; /* If it's not in the current directory, try looking through PATH for it. */ path_filename = find_path_file (script_name); if (path_filename) { free (filename); filename = path_filename; fd = open (filename, O_RDONLY); } else errno = e; } if (fd < 0) { e = errno; file_error (filename); exit ((e == ENOENT) ? EX_NOTFOUND : EX_NOINPUT); } #ifdef HAVE_DEV_FD fd_is_tty = isatty (fd); #else fd_is_tty = 0; #endif /* Only do this with non-tty file descriptors we can seek on. */ if (fd_is_tty == 0 && (lseek (fd, 0L, 1) != -1)) { /* Check to see if the `file' in `bash file' is a binary file according to the same tests done by execute_simple_command (), and report an error and exit if it is. */ sample_len = read (fd, sample, sizeof (sample)); if (sample_len < 0) { e = errno; if ((fstat (fd, &sb) == 0) && S_ISDIR (sb.st_mode)) internal_error ("%s: is a directory", filename); else { errno = e; file_error (filename); } exit (EX_NOEXEC); } else if (sample_len > 0 && (check_binary_file (sample, sample_len))) { internal_error ("%s: cannot execute binary file", filename); exit (EX_BINARY_FILE); } /* Now rewind the file back to the beginning. */ lseek (fd, 0L, 0); } /* Open the script. But try to move the file descriptor to a randomly large one, in the hopes that any descriptors used by the script will not match with ours. */ fd = move_to_high_fd (fd, 0, -1); #if defined (__CYGWIN__) && defined (O_TEXT) setmode (fd, O_TEXT); #endif #if defined (BUFFERED_INPUT) default_buffered_input = fd; SET_CLOSE_ON_EXEC (default_buffered_input); #else /* !BUFFERED_INPUT */ default_input = fdopen (fd, "r"); if (default_input == 0) { file_error (filename); exit (EX_NOTFOUND); } SET_CLOSE_ON_EXEC (fd); if (fileno (default_input) != fd) SET_CLOSE_ON_EXEC (fileno (default_input)); #endif /* !BUFFERED_INPUT */ /* Just about the only way for this code to be executed is if something like `bash -i /dev/stdin' is executed. */ if (interactive_shell && fd_is_tty) { dup2 (fd, 0); close (fd); fd = 0; #if defined (BUFFERED_INPUT) default_buffered_input = 0; #else fclose (default_input); default_input = stdin; #endif } else if (forced_interactive && fd_is_tty == 0) /* But if a script is called with something like `bash -i scriptname', we need to do a non-interactive setup here, since we didn't do it before. */ init_noninteractive (); free (filename); return (fd); } /* Initialize the input routines for the parser. */ static void set_bash_input () { /* Make sure the fd from which we are reading input is not in no-delay mode. */ #if defined (BUFFERED_INPUT) if (interactive == 0) sh_unset_nodelay_mode (default_buffered_input); else #endif /* !BUFFERED_INPUT */ sh_unset_nodelay_mode (fileno (stdin)); /* with_input_from_stdin really means `with_input_from_readline' */ if (interactive && no_line_editing == 0) with_input_from_stdin (); else #if defined (BUFFERED_INPUT) { if (interactive == 0) with_input_from_buffered_stream (default_buffered_input, dollar_vars[0]); else with_input_from_stream (default_input, dollar_vars[0]); } #else /* !BUFFERED_INPUT */ with_input_from_stream (default_input, dollar_vars[0]); #endif /* !BUFFERED_INPUT */ } /* Close the current shell script input source and forget about it. This is extern so execute_cmd.c:initialize_subshell() can call it. If CHECK_ZERO is non-zero, we close default_buffered_input even if it's the standard input (fd 0). */ void unset_bash_input (check_zero) int check_zero; { #if defined (BUFFERED_INPUT) if ((check_zero && default_buffered_input >= 0) || (check_zero == 0 && default_buffered_input > 0)) { close_buffered_fd (default_buffered_input); default_buffered_input = bash_input.location.buffered_fd = -1; } #else /* !BUFFERED_INPUT */ if (default_input) { fclose (default_input); default_input = (FILE *)NULL; } #endif /* !BUFFERED_INPUT */ } #if !defined (PROGRAM) # define PROGRAM "bash" #endif static void set_shell_name (argv0) char *argv0; { /* Here's a hack. If the name of this shell is "sh", then don't do any startup files; just try to be more like /bin/sh. */ shell_name = base_pathname (argv0); if (*shell_name == '-') { shell_name++; login_shell++; } if (shell_name[0] == 's' && shell_name[1] == 'h' && shell_name[2] == '\0') act_like_sh++; if (shell_name[0] == 's' && shell_name[1] == 'u' && shell_name[2] == '\0') su_shell++; shell_name = argv0; FREE (dollar_vars[0]); dollar_vars[0] = savestring (shell_name); /* A program may start an interactive shell with "execl ("/bin/bash", "-", NULL)". If so, default the name of this shell to our name. */ if (!shell_name || !*shell_name || (shell_name[0] == '-' && !shell_name[1])) shell_name = PROGRAM; } static void init_interactive () { interactive_shell = startup_state = interactive = 1; expand_aliases = 1; } static void init_noninteractive () { #if defined (HISTORY) bash_history_reinit (0); #endif /* HISTORY */ interactive_shell = startup_state = interactive = 0; expand_aliases = posixly_correct; /* XXX - was 0 not posixly_correct */ no_line_editing = 1; #if defined (JOB_CONTROL) set_job_control (0); #endif /* JOB_CONTROL */ } void get_current_user_info () { struct passwd *entry; /* Don't fetch this more than once. */ if (current_user.user_name == 0) { entry = getpwuid (current_user.uid); if (entry) { current_user.user_name = savestring (entry->pw_name); current_user.shell = (entry->pw_shell && entry->pw_shell[0]) ? savestring (entry->pw_shell) : savestring ("/bin/sh"); current_user.home_dir = savestring (entry->pw_dir); } else { current_user.user_name = savestring ("I have no name!"); current_user.shell = savestring ("/bin/sh"); current_user.home_dir = savestring ("/"); } endpwent (); } } /* Do whatever is necessary to initialize the shell. Put new initializations in here. */ static void shell_initialize () { char hostname[256]; /* Line buffer output for stderr and stdout. */ if (shell_initialized == 0) { sh_setlinebuf (stderr); sh_setlinebuf (stdout); } /* Sort the array of shell builtins so that the binary search in find_shell_builtin () works correctly. */ initialize_shell_builtins (); /* Initialize the trap signal handlers before installing our own signal handlers. traps.c:restore_original_signals () is responsible for restoring the original default signal handlers. That function is called when we make a new child. */ initialize_traps (); initialize_signals (0); /* It's highly unlikely that this will change. */ if (current_host_name == 0) { /* Initialize current_host_name. */ if (gethostname (hostname, 255) < 0) current_host_name = "??host??"; else current_host_name = savestring (hostname); } /* Initialize the stuff in current_user that comes from the password file. We don't need to do this right away if the shell is not interactive. */ if (interactive_shell) get_current_user_info (); /* Initialize our interface to the tilde expander. */ tilde_initialize (); /* Initialize internal and environment variables. Don't import shell functions from the environment if we are running in privileged or restricted mode or if the shell is running setuid. */ #if defined (RESTRICTED_SHELL) initialize_shell_variables (shell_environment, privileged_mode||restricted||running_setuid); #else initialize_shell_variables (shell_environment, privileged_mode||running_setuid); #endif /* Initialize the data structures for storing and running jobs. */ initialize_job_control (0); /* Initialize input streams to null. */ initialize_bash_input (); initialize_flags (); /* Initialize the shell options. Don't import the shell options from the environment variable $SHELLOPTS if we are running in privileged or restricted mode or if the shell is running setuid. */ #if defined (RESTRICTED_SHELL) initialize_shell_options (privileged_mode||restricted||running_setuid); #else initialize_shell_options (privileged_mode||running_setuid); #endif } /* Function called by main () when it appears that the shell has already had some initialization performed. This is supposed to reset the world back to a pristine state, as if we had been exec'ed. */ static void shell_reinitialize () { /* The default shell prompts. */ primary_prompt = PPROMPT; secondary_prompt = SPROMPT; /* Things that get 1. */ current_command_number = 1; /* We have decided that the ~/.bashrc file should not be executed for the invocation of each shell script. If the variable $ENV (or $BASH_ENV) is set, its value is used as the name of a file to source. */ no_rc = no_profile = 1; /* Things that get 0. */ login_shell = make_login_shell = interactive = executing = 0; debugging = do_version = line_number = last_command_exit_value = 0; forced_interactive = interactive_shell = subshell_environment = 0; expand_aliases = 0; #if defined (HISTORY) bash_history_reinit (0); #endif /* HISTORY */ #if defined (RESTRICTED_SHELL) restricted = 0; #endif /* RESTRICTED_SHELL */ /* Ensure that the default startup file is used. (Except that we don't execute this file for reinitialized shells). */ bashrc_file = "~/.bashrc"; /* Delete all variables and functions. They will be reinitialized when the environment is parsed. */ delete_all_contexts (shell_variables); delete_all_variables (shell_functions); shell_reinitialized = 1; } static void show_shell_usage (fp, extra) FILE *fp; int extra; { int i; char *set_opts, *s, *t; if (extra) fprintf (fp, "GNU bash, version %s-(%s)\n", shell_version_string (), MACHTYPE); fprintf (fp, "Usage:\t%s [GNU long option] [option] ...\n\t%s [GNU long option] [option] script-file ...\n", shell_name, shell_name); fputs ("GNU long options:\n", fp); for (i = 0; long_args[i].name; i++) fprintf (fp, "\t--%s\n", long_args[i].name); fputs ("Shell options:\n", fp); fputs ("\t-irsD or -c command or -O shopt_option\t\t(invocation only)\n", fp); for (i = 0, set_opts = 0; shell_builtins[i].name; i++) if (STREQ (shell_builtins[i].name, "set")) set_opts = savestring (shell_builtins[i].short_doc); if (set_opts) { s = xstrchr (set_opts, '['); if (s == 0) s = set_opts; while (*++s == '-') ; t = xstrchr (s, ']'); if (t) *t = '\0'; fprintf (fp, "\t-%s or -o option\n", s); free (set_opts); } if (extra) { fprintf (fp, "Type `%s -c \"help set\"' for more information about shell options.\n", shell_name); fprintf (fp, "Type `%s -c help' for more information about shell builtin commands.\n", shell_name); fprintf (fp, "Use the `bashbug' command to report bugs.\n"); } } static void add_shopt_to_alist (opt, on_or_off) char *opt; int on_or_off; { if (shopt_ind >= shopt_len) { shopt_len += 8; shopt_alist = (STRING_INT_ALIST *)xrealloc (shopt_alist, shopt_len * sizeof (shopt_alist[0])); } shopt_alist[shopt_ind].word = opt; shopt_alist[shopt_ind].token = on_or_off; shopt_ind++; } static void run_shopt_alist () { register int i; for (i = 0; i < shopt_ind; i++) if (shopt_setopt (shopt_alist[i].word, (shopt_alist[i].token == '-')) != EXECUTION_SUCCESS) exit (EX_USAGE); free (shopt_alist); shopt_alist = 0; shopt_ind = shopt_len = 0; }
/* mkbuiltins.c - Create builtins.c, builtext.h, and builtdoc.c from a single source file called builtins.def. */ /* Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #ifndef _MINIX #include "../bashtypes.h" #include <sys/file.h> #endif #include "posixstat.h" #include "filecntl.h" #include "../bashansi.h" #include <stdio.h> #include <errno.h> #include "stdc.h" #define DOCFILE "builtins.texi" #ifndef errno extern int errno; #endif static char *xmalloc (), *xrealloc (); #if !defined (__STDC__) && !defined (strcpy) extern char *strcpy (); #endif /* !__STDC__ && !strcpy */ #define savestring(x) strcpy (xmalloc (1 + strlen (x)), (x)) #define whitespace(c) (((c) == ' ') || ((c) == '\t')) /* Flag values that builtins can have. */ #define BUILTIN_FLAG_SPECIAL 0x01 #define BUILTIN_FLAG_ASSIGNMENT 0x02 /* If this stream descriptor is non-zero, then write texinfo documentation to it. */ FILE *documentation_file = (FILE *)NULL; /* Non-zero means to only produce documentation. */ int only_documentation = 0; /* Non-zero means to not do any productions. */ int inhibit_production = 0; /* Non-zero means to produce separate help files for each builtin, named by the builtin name, in `./helpfiles'. */ int separate_helpfiles = 0; /* The name of a directory into which the separate external help files will eventually be installed. */ char *helpfile_directory; /* The name of a directory to precede the filename when reporting errors. */ char *error_directory = (char *)NULL; /* The name of the structure file. */ char *struct_filename = (char *)NULL; /* The name of the external declaration file. */ char *extern_filename = (char *)NULL; /* Here is a structure for manipulating arrays of data. */ typedef struct { int size; /* Number of slots allocated to array. */ int sindex; /* Current location in array. */ int width; /* Size of each element. */ int growth_rate; /* How fast to grow. */ char **array; /* The array itself. */ } ARRAY; /* Here is a structure defining a single BUILTIN. */ typedef struct { char *name; /* The name of this builtin. */ char *function; /* The name of the function to call. */ char *shortdoc; /* The short documentation for this builtin. */ char *docname; /* Possible name for documentation string. */ ARRAY *longdoc; /* The long documentation for this builtin. */ ARRAY *dependencies; /* Null terminated array of #define names. */ int flags; /* Flags for this builtin. */ } BUILTIN_DESC; /* Here is a structure which defines a DEF file. */ typedef struct { char *filename; /* The name of the input def file. */ ARRAY *lines; /* The contents of the file. */ int line_number; /* The current line number. */ char *production; /* The name of the production file. */ FILE *output; /* Open file stream for PRODUCTION. */ ARRAY *builtins; /* Null terminated array of BUILTIN_DESC *. */ } DEF_FILE; /* The array of all builtins encountered during execution of this code. */ ARRAY *saved_builtins = (ARRAY *)NULL; /* The Posix.2 so-called `special' builtins. */ char *special_builtins[] = { ":", ".", "source", "break", "continue", "eval", "exec", "exit", "export", "readonly", "return", "set", "shift", "trap", "unset", (char *)NULL }; /* The builtin commands that take assignment statements as arguments. */ char *assignment_builtins[] = { "alias", "declare", "export", "local", "readonly", "typeset", (char *)NULL }; /* Forward declarations. */ static int is_special_builtin (); static int is_assignment_builtin (); #if !defined (HAVE_RENAME) static int rename (); #endif void extract_info (); void file_error (); void line_error (); void write_file_headers (); void write_file_footers (); void write_ifdefs (); void write_endifs (); void write_documentation (); void write_longdocs (); void write_builtins (); int write_helpfiles (); void free_defs (); void add_documentation (); void must_be_building (); void remove_trailing_whitespace (); #define document_name(b) ((b)->docname ? (b)->docname : (b)->name) /* For each file mentioned on the command line, process it and write the information to STRUCTFILE and EXTERNFILE, while creating the production file if neccessary. */ int main (argc, argv) int argc; char **argv; { int arg_index = 1; FILE *structfile, *externfile; char *documentation_filename, *temp_struct_filename; structfile = externfile = (FILE *)NULL; documentation_filename = DOCFILE; temp_struct_filename = (char *)NULL; while (arg_index < argc && argv[arg_index][0] == '-') { char *arg = argv[arg_index++]; if (strcmp (arg, "-externfile") == 0) extern_filename = argv[arg_index++]; else if (strcmp (arg, "-structfile") == 0) struct_filename = argv[arg_index++]; else if (strcmp (arg, "-noproduction") == 0) inhibit_production = 1; else if (strcmp (arg, "-document") == 0) documentation_file = fopen (documentation_filename, "w"); else if (strcmp (arg, "-D") == 0) { int len; if (error_directory) free (error_directory); error_directory = xmalloc (2 + strlen (argv[arg_index])); strcpy (error_directory, argv[arg_index]); len = strlen (error_directory); if (len && error_directory[len - 1] != '/') strcat (error_directory, "/"); arg_index++; } else if (strcmp (arg, "-documentonly") == 0) { only_documentation = 1; documentation_file = fopen (documentation_filename, "w"); } else if (strcmp (arg, "-H") == 0) { separate_helpfiles = 1; helpfile_directory = argv[arg_index++]; } else { fprintf (stderr, "%s: Unknown flag %s.\n", argv[0], arg); exit (2); } } /* If there are no files to process, just quit now. */ if (arg_index == argc) exit (0); if (!only_documentation) { /* Open the files. */ if (struct_filename) { temp_struct_filename = xmalloc (15); sprintf (temp_struct_filename, "mk-%ld", (long) getpid ()); structfile = fopen (temp_struct_filename, "w"); if (!structfile) file_error (temp_struct_filename); } if (extern_filename) { externfile = fopen (extern_filename, "w"); if (!externfile) file_error (extern_filename); } /* Write out the headers. */ write_file_headers (structfile, externfile); } if (documentation_file) { fprintf (documentation_file, "@c Table of builtins created with %s.\n", argv[0]); fprintf (documentation_file, "@ftable @asis\n"); } /* Process the .def files. */ while (arg_index < argc) { register char *arg; arg = argv[arg_index++]; extract_info (arg, structfile, externfile); } /* Close the files. */ if (!only_documentation) { /* Write the footers. */ write_file_footers (structfile, externfile); if (structfile) { write_longdocs (structfile, saved_builtins); fclose (structfile); rename (temp_struct_filename, struct_filename); } if (externfile) fclose (externfile); } if (separate_helpfiles) { write_helpfiles (saved_builtins); } if (documentation_file) { fprintf (documentation_file, "@end ftable\n"); fclose (documentation_file); } exit (0); } /* **************************************************************** */ /* */ /* Array Functions and Manipulators */ /* */ /* **************************************************************** */ /* Make a new array, and return a pointer to it. The array will contain elements of size WIDTH, and is initialized to no elements. */ ARRAY * array_create (width) int width; { ARRAY *array; array = (ARRAY *)xmalloc (sizeof (ARRAY)); array->size = 0; array->sindex = 0; array->width = width; /* Default to increasing size in units of 20. */ array->growth_rate = 20; array->array = (char **)NULL; return (array); } /* Copy the array of strings in ARRAY. */ ARRAY * copy_string_array (array) ARRAY *array; { register int i; ARRAY *copy; if (!array) return (ARRAY *)NULL; copy = array_create (sizeof (char *)); copy->size = array->size; copy->sindex = array->sindex; copy->width = array->width; copy->array = (char **)xmalloc ((1 + array->sindex) * sizeof (char *)); for (i = 0; i < array->sindex; i++) copy->array[i] = savestring (array->array[i]); copy->array[i] = (char *)NULL; return (copy); } /* Add ELEMENT to ARRAY, growing the array if neccessary. */ void array_add (element, array) char *element; ARRAY *array; { if (array->sindex + 2 > array->size) array->array = (char **)xrealloc (array->array, (array->size += array->growth_rate) * array->width); #if defined (HAVE_BCOPY) bcopy (&element, (char *) &(array->array[array->sindex]), array->width); array->sindex++; bzero ((char *) &(array->array[array->sindex]), array->width); #else array->array[array->sindex++] = element; array->array[array->sindex] = (char *)NULL; #endif /* !HAVE_BCOPY */ } /* Free an allocated array and data pointer. */ void array_free (array) ARRAY *array; { if (array->array) free (array->array); free (array); } /* **************************************************************** */ /* */ /* Processing a DEF File */ /* */ /* **************************************************************** */ /* The definition of a function. */ typedef int Function (); typedef int mk_handler_func_t __P((char *, DEF_FILE *, char *)); /* Structure handles processor directives. */ typedef struct { char *directive; mk_handler_func_t *function; } HANDLER_ENTRY; extern int builtin_handler __P((char *, DEF_FILE *, char *)); extern int function_handler __P((char *, DEF_FILE *, char *)); extern int short_doc_handler __P((char *, DEF_FILE *, char *)); extern int comment_handler __P((char *, DEF_FILE *, char *)); extern int depends_on_handler __P((char *, DEF_FILE *, char *)); extern int produces_handler __P((char *, DEF_FILE *, char *)); extern int end_handler __P((char *, DEF_FILE *, char *)); extern int docname_handler __P((char *, DEF_FILE *, char *)); HANDLER_ENTRY handlers[] = { { "BUILTIN", builtin_handler }, { "DOCNAME", docname_handler }, { "FUNCTION", function_handler }, { "SHORT_DOC", short_doc_handler }, { "$", comment_handler }, { "COMMENT", comment_handler }, { "DEPENDS_ON", depends_on_handler }, { "PRODUCES", produces_handler }, { "END", end_handler }, { (char *)NULL, (mk_handler_func_t *)NULL } }; /* Return the entry in the table of handlers for NAME. */ HANDLER_ENTRY * find_directive (directive) char *directive; { register int i; for (i = 0; handlers[i].directive; i++) if (strcmp (handlers[i].directive, directive) == 0) return (&handlers[i]); return ((HANDLER_ENTRY *)NULL); } /* Non-zero indicates that a $BUILTIN has been seen, but not the corresponding $END. */ static int building_builtin = 0; /* Non-zero means to output cpp line and file information before printing the current line to the production file. */ int output_cpp_line_info = 0; /* The main function of this program. Read FILENAME and act on what is found. Lines not starting with a dollar sign are copied to the $PRODUCES target, if one is present. Lines starting with a dollar sign are directives to this program, specifying the name of the builtin, the function to call, the short documentation and the long documentation strings. FILENAME can contain multiple $BUILTINs, but only one $PRODUCES target. After the file has been processed, write out the names of builtins found in each $BUILTIN. Plain text found before the $PRODUCES is ignored, as is "$$ comment text". */ void extract_info (filename, structfile, externfile) char *filename; FILE *structfile, *externfile; { register int i; DEF_FILE *defs; struct stat finfo; size_t file_size; char *buffer, *line; int fd, nr; if (stat (filename, &finfo) == -1) file_error (filename); fd = open (filename, O_RDONLY, 0666); if (fd == -1) file_error (filename); file_size = (size_t)finfo.st_size; buffer = xmalloc (1 + file_size); if ((nr = read (fd, buffer, file_size)) < 0) file_error (filename); /* This is needed on WIN32, and does not hurt on Unix. */ if (nr < file_size) file_size = nr; close (fd); if (nr == 0) { fprintf (stderr, "mkbuiltins: %s: skipping zero-length file\n", filename); return; } /* Create and fill in the initial structure describing this file. */ defs = (DEF_FILE *)xmalloc (sizeof (DEF_FILE)); defs->filename = filename; defs->lines = array_create (sizeof (char *)); defs->line_number = 0; defs->production = (char *)NULL; defs->output = (FILE *)NULL; defs->builtins = (ARRAY *)NULL; /* Build the array of lines. */ i = 0; while (i < file_size) { array_add (&buffer[i], defs->lines); while (buffer[i] != '\n' && i < file_size) i++; buffer[i++] = '\0'; } /* Begin processing the input file. We don't write any output until we have a file to write output to. */ output_cpp_line_info = 1; /* Process each line in the array. */ for (i = 0; line = defs->lines->array[i]; i++) { defs->line_number = i; if (*line == '$') { register int j; char *directive; HANDLER_ENTRY *handler; /* Isolate the directive. */ for (j = 0; line[j] && !whitespace (line[j]); j++); directive = xmalloc (j); strncpy (directive, line + 1, j - 1); directive[j -1] = '\0'; /* Get the function handler and call it. */ handler = find_directive (directive); if (!handler) { line_error (defs, "Unknown directive `%s'", directive); free (directive); continue; } else { /* Advance to the first non-whitespace character. */ while (whitespace (line[j])) j++; /* Call the directive handler with the FILE, and ARGS. */ (*(handler->function)) (directive, defs, line + j); } free (directive); } else { if (building_builtin) add_documentation (defs, line); else if (defs->output) { if (output_cpp_line_info) { /* If we're handed an absolute pathname, don't prepend the directory name. */ if (defs->filename[0] == '/') fprintf (defs->output, "#line %d \"%s\"\n", defs->line_number + 1, defs->filename); else fprintf (defs->output, "#line %d \"%s%s\"\n", defs->line_number + 1, error_directory ? error_directory : "./", defs->filename); output_cpp_line_info = 0; } fprintf (defs->output, "%s\n", line); } } } /* Close the production file. */ if (defs->output) fclose (defs->output); /* The file has been processed. Write the accumulated builtins to the builtins.c file, and write the extern definitions to the builtext.h file. */ write_builtins (defs, structfile, externfile); free (buffer); free_defs (defs); } #define free_safely(x) if (x) free (x) static void free_builtin (builtin) BUILTIN_DESC *builtin; { register int i; free_safely (builtin->name); free_safely (builtin->function); free_safely (builtin->shortdoc); free_safely (builtin->docname); if (builtin->longdoc) array_free (builtin->longdoc); if (builtin->dependencies) { for (i = 0; builtin->dependencies->array[i]; i++) free (builtin->dependencies->array[i]); array_free (builtin->dependencies); } } /* Free all of the memory allocated to a DEF_FILE. */ void free_defs (defs) DEF_FILE *defs; { register int i; register BUILTIN_DESC *builtin; if (defs->production) free (defs->production); if (defs->lines) array_free (defs->lines); if (defs->builtins) { for (i = 0; builtin = (BUILTIN_DESC *)defs->builtins->array[i]; i++) { free_builtin (builtin); free (builtin); } array_free (defs->builtins); } free (defs); } /* **************************************************************** */ /* */ /* The Handler Functions Themselves */ /* */ /* **************************************************************** */ /* Strip surrounding whitespace from STRING, and return a pointer to the start of it. */ char * strip_whitespace (string) char *string; { while (whitespace (*string)) string++; remove_trailing_whitespace (string); return (string); } /* Remove only the trailing whitespace from STRING. */ void remove_trailing_whitespace (string) char *string; { register int i; i = strlen (string) - 1; while (i > 0 && whitespace (string[i])) i--; string[++i] = '\0'; } /* Ensure that there is a argument in STRING and return it. FOR_WHOM is the name of the directive which needs the argument. DEFS is the DEF_FILE in which the directive is found. If there is no argument, produce an error. */ char * get_arg (for_whom, defs, string) char *for_whom, *string; DEF_FILE *defs; { char *new; new = strip_whitespace (string); if (!*new) line_error (defs, "%s requires an argument", for_whom); return (savestring (new)); } /* Error if not building a builtin. */ void must_be_building (directive, defs) char *directive; DEF_FILE *defs; { if (!building_builtin) line_error (defs, "%s must be inside of a $BUILTIN block", directive); } /* Return the current builtin. */ BUILTIN_DESC * current_builtin (directive, defs) char *directive; DEF_FILE *defs; { must_be_building (directive, defs); if (defs->builtins) return ((BUILTIN_DESC *)defs->builtins->array[defs->builtins->sindex - 1]); else return ((BUILTIN_DESC *)NULL); } /* Add LINE to the long documentation for the current builtin. Ignore blank lines until the first non-blank line has been seen. */ void add_documentation (defs, line) DEF_FILE *defs; char *line; { register BUILTIN_DESC *builtin; builtin = current_builtin ("(implied LONGDOC)", defs); remove_trailing_whitespace (line); if (!*line && !builtin->longdoc) return; if (!builtin->longdoc) builtin->longdoc = array_create (sizeof (char *)); array_add (line, builtin->longdoc); } /* How to handle the $BUILTIN directive. */ int builtin_handler (self, defs, arg) char *self; DEF_FILE *defs; char *arg; { BUILTIN_DESC *new; char *name; /* If we are already building a builtin, we cannot start a new one. */ if (building_builtin) { line_error (defs, "%s found before $END", self); return (-1); } output_cpp_line_info++; /* Get the name of this builtin, and stick it in the array. */ name = get_arg (self, defs, arg); /* If this is the first builtin, create the array to hold them. */ if (!defs->builtins) defs->builtins = array_create (sizeof (BUILTIN_DESC *)); new = (BUILTIN_DESC *)xmalloc (sizeof (BUILTIN_DESC)); new->name = name; new->function = (char *)NULL; new->shortdoc = (char *)NULL; new->docname = (char *)NULL; new->longdoc = (ARRAY *)NULL; new->dependencies = (ARRAY *)NULL; new->flags = 0; if (is_special_builtin (name)) new->flags |= BUILTIN_FLAG_SPECIAL; if (is_assignment_builtin (name)) new->flags |= BUILTIN_FLAG_ASSIGNMENT; array_add ((char *)new, defs->builtins); building_builtin = 1; return (0); } /* How to handle the $FUNCTION directive. */ int function_handler (self, defs, arg) char *self; DEF_FILE *defs; char *arg; { register BUILTIN_DESC *builtin; builtin = current_builtin (self, defs); if (builtin == 0) { line_error (defs, "syntax error: no current builtin for $FUNCTION directive"); exit (1); } if (builtin->function) line_error (defs, "%s already has a function (%s)", builtin->name, builtin->function); else builtin->function = get_arg (self, defs, arg); return (0); } /* How to handle the $DOCNAME directive. */ int docname_handler (self, defs, arg) char *self; DEF_FILE *defs; char *arg; { register BUILTIN_DESC *builtin; builtin = current_builtin (self, defs); if (builtin->docname) line_error (defs, "%s already had a docname (%s)", builtin->name, builtin->docname); else builtin->docname = get_arg (self, defs, arg); return (0); } /* How to handle the $SHORT_DOC directive. */ int short_doc_handler (self, defs, arg) char *self; DEF_FILE *defs; char *arg; { register BUILTIN_DESC *builtin; builtin = current_builtin (self, defs); if (builtin->shortdoc) line_error (defs, "%s already has short documentation (%s)", builtin->name, builtin->shortdoc); else builtin->shortdoc = get_arg (self, defs, arg); return (0); } /* How to handle the $COMMENT directive. */ int comment_handler (self, defs, arg) char *self; DEF_FILE *defs; char *arg; { return (0); } /* How to handle the $DEPENDS_ON directive. */ int depends_on_handler (self, defs, arg) char *self; DEF_FILE *defs; char *arg; { register BUILTIN_DESC *builtin; char *dependent; builtin = current_builtin (self, defs); dependent = get_arg (self, defs, arg); if (!builtin->dependencies) builtin->dependencies = array_create (sizeof (char *)); array_add (dependent, builtin->dependencies); return (0); } /* How to handle the $PRODUCES directive. */ int produces_handler (self, defs, arg) char *self; DEF_FILE *defs; char *arg; { /* If just hacking documentation, don't change any of the production files. */ if (only_documentation) return (0); output_cpp_line_info++; if (defs->production) line_error (defs, "%s already has a %s definition", defs->filename, self); else { defs->production = get_arg (self, defs, arg); if (inhibit_production) return (0); defs->output = fopen (defs->production, "w"); if (!defs->output) file_error (defs->production); fprintf (defs->output, "/* %s, created from %s. */\n", defs->production, defs->filename); } return (0); } /* How to handle the $END directive. */ int end_handler (self, defs, arg) char *self; DEF_FILE *defs; char *arg; { must_be_building (self, defs); building_builtin = 0; return (0); } /* **************************************************************** */ /* */ /* Error Handling Functions */ /* */ /* **************************************************************** */ /* Produce an error for DEFS with FORMAT and ARGS. */ void line_error (defs, format, arg1, arg2) DEF_FILE *defs; char *format, *arg1, *arg2; { if (defs->filename[0] != '/') fprintf (stderr, "%s", error_directory ? error_directory : "./"); fprintf (stderr, "%s:%d:", defs->filename, defs->line_number + 1); fprintf (stderr, format, arg1, arg2); fprintf (stderr, "\n"); fflush (stderr); } /* Print error message for FILENAME. */ void file_error (filename) char *filename; { perror (filename); exit (2); } /* **************************************************************** */ /* */ /* xmalloc and xrealloc () */ /* */ /* **************************************************************** */ static void memory_error_and_abort (); static char * xmalloc (bytes) int bytes; { char *temp = (char *)malloc (bytes); if (!temp) memory_error_and_abort (); return (temp); } static char * xrealloc (pointer, bytes) char *pointer; int bytes; { char *temp; if (!pointer) temp = (char *)malloc (bytes); else temp = (char *)realloc (pointer, bytes); if (!temp) memory_error_and_abort (); return (temp); } static void memory_error_and_abort () { fprintf (stderr, "mkbuiltins: out of virtual memory\n"); abort (); } /* **************************************************************** */ /* */ /* Creating the Struct and Extern Files */ /* */ /* **************************************************************** */ /* Return a pointer to a newly allocated builtin which is an exact copy of BUILTIN. */ BUILTIN_DESC * copy_builtin (builtin) BUILTIN_DESC *builtin; { BUILTIN_DESC *new; new = (BUILTIN_DESC *)xmalloc (sizeof (BUILTIN_DESC)); new->name = savestring (builtin->name); new->shortdoc = savestring (builtin->shortdoc); new->longdoc = copy_string_array (builtin->longdoc); new->dependencies = copy_string_array (builtin->dependencies); new->function = builtin->function ? savestring (builtin->function) : (char *)NULL; new->docname = builtin->docname ? savestring (builtin->docname) : (char *)NULL; return (new); } /* How to save away a builtin. */ void save_builtin (builtin) BUILTIN_DESC *builtin; { BUILTIN_DESC *newbuiltin; newbuiltin = copy_builtin (builtin); /* If this is the first builtin to be saved, create the array to hold it. */ if (!saved_builtins) saved_builtins = array_create (sizeof (BUILTIN_DESC *)); array_add ((char *)newbuiltin, saved_builtins); } /* Flags that mean something to write_documentation (). */ #define STRING_ARRAY 1 #define TEXINFO 2 #define PLAINTEXT 4 char *structfile_header[] = { "/* builtins.c -- the built in shell commands. */", "", "/* This file is manufactured by ./mkbuiltins, and should not be", " edited by hand. See the source to mkbuiltins for details. */", "", "/* Copyright (C) 1987-2002 Free Software Foundation, Inc.", "", " This file is part of GNU Bash, the Bourne Again SHell.", "", " Bash is free software; you can redistribute it and/or modify it", " under the terms of the GNU General Public License as published by", " the Free Software Foundation; either version 2, or (at your option)", " any later version.", "", " Bash is distributed in the hope that it will be useful, but WITHOUT", " ANY WARRANTY; without even the implied warranty of MERCHANTABILITY", " or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public", " License for more details.", "", " You should have received a copy of the GNU General Public License", " along with Bash; see the file COPYING. If not, write to the Free", " Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */", "", "/* The list of shell builtins. Each element is name, function, flags,", " long-doc, short-doc. The long-doc field contains a pointer to an array", " of help lines. The function takes a WORD_LIST *; the first word in the", " list is the first arg to the command. The list has already had word", " expansion performed.", "", " Functions which need to look at only the simple commands (e.g.", " the enable_builtin ()), should ignore entries where", " (array[i].function == (sh_builtin_func_t *)NULL). Such entries are for", " the list of shell reserved control structures, like `if' and `while'.", " The end of the list is denoted with a NULL name field. */", "", "#include \"../builtins.h\"", (char *)NULL }; char *structfile_footer[] = { " { (char *)0x0, (sh_builtin_func_t *)0x0, 0, (char **)0x0, (char *)0x0 }", "};", "", "struct builtin *shell_builtins = static_shell_builtins;", "struct builtin *current_builtin;", "", "int num_shell_builtins =", "\tsizeof (static_shell_builtins) / sizeof (struct builtin) - 1;", (char *)NULL }; /* Write out any neccessary opening information for STRUCTFILE and EXTERNFILE. */ void write_file_headers (structfile, externfile) FILE *structfile, *externfile; { register int i; if (structfile) { for (i = 0; structfile_header[i]; i++) fprintf (structfile, "%s\n", structfile_header[i]); fprintf (structfile, "#include \"%s\"\n", extern_filename ? extern_filename : "builtext.h"); fprintf (structfile, "\nstruct builtin static_shell_builtins[] = {\n"); } if (externfile) fprintf (externfile, "/* %s - The list of builtins found in libbuiltins.a. */\n", extern_filename ? extern_filename : "builtext.h"); } /* Write out any necessary closing information for STRUCTFILE and EXTERNFILE. */ void write_file_footers (structfile, externfile) FILE *structfile, *externfile; { register int i; /* Write out the footers. */ if (structfile) { for (i = 0; structfile_footer[i]; i++) fprintf (structfile, "%s\n", structfile_footer[i]); } } /* Write out the information accumulated in DEFS to STRUCTFILE and EXTERNFILE. */ void write_builtins (defs, structfile, externfile) DEF_FILE *defs; FILE *structfile, *externfile; { register int i; /* Write out the information. */ if (defs->builtins) { register BUILTIN_DESC *builtin; for (i = 0; i < defs->builtins->sindex; i++) { builtin = (BUILTIN_DESC *)defs->builtins->array[i]; /* Write out any #ifdefs that may be there. */ if (!only_documentation) { if (builtin->dependencies) { write_ifdefs (externfile, builtin->dependencies->array); write_ifdefs (structfile, builtin->dependencies->array); } /* Write the extern definition. */ if (externfile) { if (builtin->function) fprintf (externfile, "extern int %s __P((WORD_LIST *));\n", builtin->function); fprintf (externfile, "extern char * const %s_doc[];\n", document_name (builtin)); } /* Write the structure definition. */ if (structfile) { fprintf (structfile, " { \"%s\", ", builtin->name); if (builtin->function) fprintf (structfile, "%s, ", builtin->function); else fprintf (structfile, "(sh_builtin_func_t *)0x0, "); fprintf (structfile, "%s%s%s, %s_doc,\n", "BUILTIN_ENABLED | STATIC_BUILTIN", (builtin->flags & BUILTIN_FLAG_SPECIAL) ? " | SPECIAL_BUILTIN" : "", (builtin->flags & BUILTIN_FLAG_ASSIGNMENT) ? " | ASSIGNMENT_BUILTIN" : "", document_name (builtin)); fprintf (structfile, " \"%s\", (char *)NULL },\n", builtin->shortdoc ? builtin->shortdoc : builtin->name); } if (structfile || separate_helpfiles) /* Save away this builtin for later writing of the long documentation strings. */ save_builtin (builtin); /* Write out the matching #endif, if neccessary. */ if (builtin->dependencies) { if (externfile) write_endifs (externfile, builtin->dependencies->array); if (structfile) write_endifs (structfile, builtin->dependencies->array); } } if (documentation_file) { fprintf (documentation_file, "@item %s\n", builtin->name); write_documentation (documentation_file, builtin->longdoc->array, 0, TEXINFO); } } } } /* Write out the long documentation strings in BUILTINS to STREAM. */ void write_longdocs (stream, builtins) FILE *stream; ARRAY *builtins; { register int i; register BUILTIN_DESC *builtin; char *dname; char *sarray[2]; for (i = 0; i < builtins->sindex; i++) { builtin = (BUILTIN_DESC *)builtins->array[i]; if (builtin->dependencies) write_ifdefs (stream, builtin->dependencies->array); /* Write the long documentation strings. */ dname = document_name (builtin); fprintf (stream, "char * const %s_doc[] =", dname); if (separate_helpfiles) { int l = strlen (helpfile_directory) + strlen (dname) + 1; sarray[0] = (char *)xmalloc (l + 1); sprintf (sarray[0], "%s/%s", helpfile_directory, dname); sarray[1] = (char *)NULL; write_documentation (stream, sarray, 0, STRING_ARRAY); free (sarray[0]); } else write_documentation (stream, builtin->longdoc->array, 0, STRING_ARRAY); if (builtin->dependencies) write_endifs (stream, builtin->dependencies->array); } } /* Write an #ifdef string saying what needs to be defined (or not defined) in order to allow compilation of the code that will follow. STREAM is the stream to write the information to, DEFINES is a null terminated array of define names. If a define is preceded by an `!', then the sense of the test is reversed. */ void write_ifdefs (stream, defines) FILE *stream; char **defines; { register int i; if (!stream) return; fprintf (stream, "#if "); for (i = 0; defines[i]; i++) { char *def = defines[i]; if (*def == '!') fprintf (stream, "!defined (%s)", def + 1); else fprintf (stream, "defined (%s)", def); if (defines[i + 1]) fprintf (stream, " && "); } fprintf (stream, "\n"); } /* Write an #endif string saying what defines controlled the compilation of the immediately preceding code. STREAM is the stream to write the information to. DEFINES is a null terminated array of define names. */ void write_endifs (stream, defines) FILE *stream; char **defines; { register int i; if (!stream) return; fprintf (stream, "#endif /* "); for (i = 0; defines[i]; i++) { fprintf (stream, "%s", defines[i]); if (defines[i + 1]) fprintf (stream, " && "); } fprintf (stream, " */\n"); } /* Write DOCUMENTAION to STREAM, perhaps surrounding it with double-quotes and quoting special characters in the string. */ void write_documentation (stream, documentation, indentation, flags) FILE *stream; char **documentation; int indentation, flags; { register int i, j; register char *line; int string_array, texinfo; if (!stream) return; string_array = flags & STRING_ARRAY; if (string_array) fprintf (stream, " {\n#if defined (HELP_BUILTIN)\n"); for (i = 0, texinfo = (flags & TEXINFO); line = documentation[i]; i++) { /* Allow #ifdef's to be written out verbatim. */ if (*line == '#') { if (string_array) fprintf (stream, "%s\n", line); continue; } if (string_array) fprintf (stream, " \""); if (indentation) for (j = 0; j < indentation; j++) fprintf (stream, " "); if (string_array) { for (j = 0; line[j]; j++) { switch (line[j]) { case '\\': case '"': fprintf (stream, "\\%c", line[j]); break; default: fprintf (stream, "%c", line[j]); } } fprintf (stream, "\",\n"); } else if (texinfo) { for (j = 0; line[j]; j++) { switch (line[j]) { case '@': case '{': case '}': fprintf (stream, "@%c", line[j]); break; default: fprintf (stream, "%c", line[j]); } } fprintf (stream, "\n"); } else fprintf (stream, "%s\n", line); } if (string_array) fprintf (stream, "#endif /* HELP_BUILTIN */\n (char *)NULL\n};\n"); } int write_helpfiles (builtins) ARRAY *builtins; { char *helpfile, *bname; FILE *helpfp; int i, hdlen; BUILTIN_DESC *builtin; i = mkdir ("helpfiles", 0777); if (i < 0 && errno != EEXIST) { fprintf (stderr, "write_helpfiles: helpfiles: cannot create directory\n"); return -1; } hdlen = strlen ("helpfiles/"); for (i = 0; i < builtins->sindex; i++) { builtin = (BUILTIN_DESC *)builtins->array[i]; bname = document_name (builtin); helpfile = (char *)xmalloc (hdlen + strlen (bname) + 1); sprintf (helpfile, "helpfiles/%s", bname); helpfp = fopen (helpfile, "w"); if (helpfp == 0) { fprintf (stderr, "write_helpfiles: cannot open %s\n", helpfile); free (helpfile); continue; } write_documentation (helpfp, builtin->longdoc->array, 4, PLAINTEXT); fflush (helpfp); fclose (helpfp); free (helpfile); } return 0; } static int _find_in_table (name, name_table) char *name, *name_table[]; { register int i; for (i = 0; name_table[i]; i++) if (strcmp (name, name_table[i]) == 0) return 1; return 0; } static int is_special_builtin (name) char *name; { return (_find_in_table (name, special_builtins)); } static int is_assignment_builtin (name) char *name; { return (_find_in_table (name, assignment_builtins)); } #if !defined (HAVE_RENAME) static int rename (from, to) char *from, *to; { unlink (to); if (link (from, to) < 0) return (-1); unlink (from); return (0); } #endif /* !HAVE_RENAME */
This file is alias.def, from which is created alias.c It implements the builtins "alias" and "unalias" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $BUILTIN alias $FUNCTION alias_builtin $DEPENDS_ON ALIAS $PRODUCES alias.c $SHORT_DOC alias [-p] [name[=value] ... ] `alias' with no arguments or with the -p option prints the list of aliases in the form alias NAME=VALUE on standard output. Otherwise, an alias is defined for each NAME whose VALUE is given. A trailing space in VALUE causes the next word to be checked for alias substitution when the alias is expanded. Alias returns true unless a NAME is given for which no alias has been defined. $END #include <config.h> #if defined (ALIAS) #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif # include "../bashansi.h" # include <stdio.h> # include "../shell.h" # include "../alias.h" # include "common.h" # include "bashgetopt.h" static void print_alias __P((alias_t *)); /* Hack the alias command in a Korn shell way. */ int alias_builtin (list) WORD_LIST *list; { int any_failed, offset, pflag; alias_t **alias_list, *t; char *name, *value; pflag = 0; reset_internal_getopt (); while ((offset = internal_getopt (list, "p")) != -1) { switch (offset) { case 'p': pflag = 1; break; default: builtin_usage (); return (EX_USAGE); } } list = loptend; if (list == 0 || pflag) { if (aliases == 0) return (EXECUTION_SUCCESS); alias_list = all_aliases (); if (alias_list == 0) return (EXECUTION_SUCCESS); for (offset = 0; alias_list[offset]; offset++) print_alias (alias_list[offset]); free (alias_list); /* XXX - Do not free the strings. */ if (list == 0) return (EXECUTION_SUCCESS); } any_failed = 0; while (list) { name = list->word->word; for (offset = 0; name[offset] && name[offset] != '='; offset++) ; if (offset && name[offset] == '=') { name[offset] = '\0'; value = name + offset + 1; add_alias (name, value); } else { t = find_alias (name); if (t) print_alias (t); else { sh_notfound (name); any_failed++; } } list = list->next; } return (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS); } #endif /* ALIAS */ $BUILTIN unalias $FUNCTION unalias_builtin $DEPENDS_ON ALIAS $SHORT_DOC unalias [-a] [name ...] Remove NAMEs from the list of defined aliases. If the -a option is given, then remove all alias definitions. $END #if defined (ALIAS) /* Remove aliases named in LIST from the aliases database. */ int unalias_builtin (list) register WORD_LIST *list; { register alias_t *alias; int opt, aflag; aflag = 0; reset_internal_getopt (); while ((opt = internal_getopt (list, "a")) != -1) { switch (opt) { case 'a': aflag = 1; break; default: builtin_usage (); return (EX_USAGE); } } list = loptend; if (aflag) { delete_all_aliases (); return (EXECUTION_SUCCESS); } aflag = 0; while (list) { alias = find_alias (list->word->word); if (alias) remove_alias (alias->name); else { sh_notfound (list->word->word); aflag++; } list = list->next; } return (aflag ? EXECUTION_FAILURE : EXECUTION_SUCCESS); } /* Output ALIAS in such a way as to allow it to be read back in. */ static void print_alias (alias) alias_t *alias; { char *value; value = sh_single_quote (alias->value); printf ("alias %s=%s\n", alias->name, value); free (value); fflush (stdout); } #endif /* ALIAS */
This file is bind.def, from which is created bind.c. It implements the builtin "bind" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES bind.c #include <config.h> $BUILTIN bind $DEPENDS_ON READLINE $FUNCTION bind_builtin $SHORT_DOC bind [-lpvsPVS] [-m keymap] [-f filename] [-q name] [-u name] [-r keyseq] [-x keyseq:shell-command] [keyseq:readline-function or readline-command] Bind a key sequence to a Readline function or a macro, or set a Readline variable. The non-option argument syntax is equivalent to that found in ~/.inputrc, but must be passed as a single argument: bind '"\C-x\C-r": re-read-init-file'. bind accepts the following options: -m keymap Use `keymap' as the keymap for the duration of this command. Acceptable keymap names are emacs, emacs-standard, emacs-meta, emacs-ctlx, vi, vi-move, vi-command, and vi-insert. -l List names of functions. -P List function names and bindings. -p List functions and bindings in a form that can be reused as input. -r keyseq Remove the binding for KEYSEQ. -x keyseq:shell-command Cause SHELL-COMMAND to be executed when KEYSEQ is entered. -f filename Read key bindings from FILENAME. -q function-name Query about which keys invoke the named function. -u function-name Unbind all keys which are bound to the named function. -V List variable names and values -v List variable names and values in a form that can be reused as input. -S List key sequences that invoke macros and their values -s List key sequences that invoke macros and their values in a form that can be reused as input. $END #if defined (READLINE) #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include <stdio.h> #include <errno.h> #if !defined (errno) extern int errno; #endif /* !errno */ #include <readline/readline.h> #include <readline/history.h> #include "../shell.h" #include "../bashline.h" #include "bashgetopt.h" #include "common.h" static int query_bindings __P((char *)); static int unbind_command __P((char *)); extern int no_line_editing; #define BIND_RETURN(x) do { return_code = x; goto bind_exit; } while (0) #define LFLAG 0x0001 #define PFLAG 0x0002 #define FFLAG 0x0004 #define VFLAG 0x0008 #define QFLAG 0x0010 #define MFLAG 0x0020 #define RFLAG 0x0040 #define PPFLAG 0x0080 #define VVFLAG 0x0100 #define SFLAG 0x0200 #define SSFLAG 0x0400 #define UFLAG 0x0800 #define XFLAG 0x1000 int bind_builtin (list) WORD_LIST *list; { int return_code; Keymap kmap, saved_keymap; int flags, opt; char *initfile, *map_name, *fun_name, *unbind_name, *remove_seq, *cmd_seq; if (no_line_editing) return (EXECUTION_FAILURE); kmap = saved_keymap = (Keymap) NULL; flags = 0; initfile = map_name = fun_name = unbind_name = remove_seq = (char *)NULL; return_code = EXECUTION_SUCCESS; if (!bash_readline_initialized) initialize_readline (); begin_unwind_frame ("bind_builtin"); unwind_protect_var (rl_outstream); rl_outstream = stdout; reset_internal_getopt (); while ((opt = internal_getopt (list, "lvpVPsSf:q:u:m:r:x:")) != EOF) { switch (opt) { case 'l': flags |= LFLAG; break; case 'v': flags |= VFLAG; break; case 'p': flags |= PFLAG; break; case 'f': flags |= FFLAG; initfile = list_optarg; break; case 'm': flags |= MFLAG; map_name = list_optarg; break; case 'q': flags |= QFLAG; fun_name = list_optarg; break; case 'u': flags |= UFLAG; unbind_name = list_optarg; break; case 'r': flags |= RFLAG; remove_seq = list_optarg; break; case 'V': flags |= VVFLAG; break; case 'P': flags |= PPFLAG; break; case 's': flags |= SFLAG; break; case 'S': flags |= SSFLAG; break; case 'x': flags |= XFLAG; cmd_seq = list_optarg; break; default: builtin_usage (); BIND_RETURN (EX_USAGE); } } list = loptend; /* First, see if we need to install a special keymap for this command. Then start on the arguments. */ if ((flags & MFLAG) && map_name) { kmap = rl_get_keymap_by_name (map_name); if (!kmap) { builtin_error ("`%s': invalid keymap name", map_name); BIND_RETURN (EXECUTION_FAILURE); } } if (kmap) { saved_keymap = rl_get_keymap (); rl_set_keymap (kmap); } /* XXX - we need to add exclusive use tests here. It doesn't make sense to use some of these options together. */ /* Now hack the option arguments */ if (flags & LFLAG) rl_list_funmap_names (); if (flags & PFLAG) rl_function_dumper (1); if (flags & PPFLAG) rl_function_dumper (0); if (flags & SFLAG) rl_macro_dumper (1); if (flags & SSFLAG) rl_macro_dumper (0); if (flags & VFLAG) rl_variable_dumper (1); if (flags & VVFLAG) rl_variable_dumper (0); if ((flags & FFLAG) && initfile) { if (rl_read_init_file (initfile) != 0) { builtin_error ("%s: cannot read: %s", initfile, strerror (errno)); BIND_RETURN (EXECUTION_FAILURE); } } if ((flags & QFLAG) && fun_name) return_code = query_bindings (fun_name); if ((flags & UFLAG) && unbind_name) return_code = unbind_command (unbind_name); if ((flags & RFLAG) && remove_seq) { if (rl_set_key (remove_seq, (rl_command_func_t *)NULL, rl_get_keymap ()) != 0) { builtin_error ("`%s': cannot unbind", remove_seq); BIND_RETURN (EXECUTION_FAILURE); } } if (flags & XFLAG) return_code = bind_keyseq_to_unix_command (cmd_seq); /* Process the rest of the arguments as binding specifications. */ while (list) { rl_parse_and_bind (list->word->word); list = list->next; } bind_exit: if (saved_keymap) rl_set_keymap (saved_keymap); run_unwind_frame ("bind_builtin"); return (return_code); } static int query_bindings (name) char *name; { rl_command_func_t *function; char **keyseqs; int j; function = rl_named_function (name); if (function == 0) { builtin_error ("`%s': unknown function name", name); return EXECUTION_FAILURE; } keyseqs = rl_invoking_keyseqs (function); if (!keyseqs) { printf ("%s is not bound to any keys.\n", name); return EXECUTION_FAILURE; } printf ("%s can be invoked via ", name); for (j = 0; j < 5 && keyseqs[j]; j++) printf ("\"%s\"%s", keyseqs[j], keyseqs[j + 1] ? ", " : ".\n"); if (keyseqs[j]) printf ("...\n"); strvec_dispose (keyseqs); return EXECUTION_SUCCESS; } static int unbind_command (name) char *name; { rl_command_func_t *function; function = rl_named_function (name); if (function == 0) { builtin_error ("`%s': unknown function name", name); return EXECUTION_FAILURE; } rl_unbind_function_in_map (function, rl_get_keymap ()); return EXECUTION_SUCCESS; } #endif /* READLINE */
This file is break.def, from which is created break.c. It implements the builtins "break" and "continue" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES break.c $BUILTIN break $FUNCTION break_builtin $SHORT_DOC break [n] Exit from within a FOR, WHILE or UNTIL loop. If N is specified, break N levels. $END #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "../shell.h" #include "common.h" extern char *this_command_name; extern int posixly_correct; static int check_loop_level __P((void)); /* The depth of while's and until's. */ int loop_level = 0; /* Non-zero when a "break" instruction is encountered. */ int breaking = 0; /* Non-zero when we have encountered a continue instruction. */ int continuing = 0; /* Set up to break x levels, where x defaults to 1, but can be specified as the first argument. */ int break_builtin (list) WORD_LIST *list; { intmax_t newbreak; if (check_loop_level () == 0) return (EXECUTION_SUCCESS); newbreak = get_numeric_arg (list, 1); if (newbreak <= 0) { sh_erange (list->word->word, "loop count"); breaking = loop_level; return (EXECUTION_FAILURE); } if (newbreak > loop_level) newbreak = loop_level; breaking = newbreak; return (EXECUTION_SUCCESS); } $BUILTIN continue $FUNCTION continue_builtin $SHORT_DOC continue [n] Resume the next iteration of the enclosing FOR, WHILE or UNTIL loop. If N is specified, resume at the N-th enclosing loop. $END /* Set up to continue x levels, where x defaults to 1, but can be specified as the first argument. */ int continue_builtin (list) WORD_LIST *list; { intmax_t newcont; if (check_loop_level () == 0) return (EXECUTION_SUCCESS); newcont = get_numeric_arg (list, 1); if (newcont <= 0) { sh_erange (list->word->word, "loop count"); breaking = loop_level; return (EXECUTION_FAILURE); } if (newcont > loop_level) newcont = loop_level; continuing = newcont; return (EXECUTION_SUCCESS); } /* Return non-zero if a break or continue command would be okay. Print an error message if break or continue is meaningless here. */ static int check_loop_level () { #if defined (BREAK_COMPLAINS) if (loop_level == 0 && posixly_correct == 0) builtin_error ("only meaningful in a `for', `while', or `until' loop"); #endif /* BREAK_COMPLAINS */ return (loop_level); }
This file is builtin.def, from which is created builtin.c. It implements the builtin "builtin" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES builtin.c $BUILTIN builtin $FUNCTION builtin_builtin $SHORT_DOC builtin [shell-builtin [arg ...]] Run a shell builtin. This is useful when you wish to rename a shell builtin to be a function, but need the functionality of the builtin within the function itself. $END #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "../shell.h" #include "common.h" #include "bashgetopt.h" extern char *this_command_name; /* Run the command mentioned in list directly, without going through the normal alias/function/builtin/filename lookup process. */ int builtin_builtin (list) WORD_LIST *list; { sh_builtin_func_t *function; register char *command; if (no_options (list)) return (EX_USAGE); list = loptend; /* skip over possible `--' */ if (list == 0) return (EXECUTION_SUCCESS); command = list->word->word; #if defined (DISABLED_BUILTINS) function = builtin_address (command); #else /* !DISABLED_BUILTINS */ function = find_shell_builtin (command); #endif /* !DISABLED_BUILTINS */ if (!function) { builtin_error ("%s: not a shell builtin", command); return (EXECUTION_FAILURE); } else { this_command_name = command; list = list->next; return ((*function) (list)); } }
This file is cd.def, from which is created cd.c. It implements the builtins "cd" and "pwd" in Bash. Copyright (C) 1987, 1989, 1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES cd.c #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "../bashtypes.h" #include "posixdir.h" #include "posixstat.h" #ifndef _MINIX #include <sys/param.h> #endif #include <stdio.h> #include "../bashansi.h" #include <errno.h> #include <tilde/tilde.h> #include "../shell.h" #include "../flags.h" #include "maxpath.h" #include "common.h" #include "bashgetopt.h" #if !defined (errno) extern int errno; #endif /* !errno */ extern int posixly_correct; extern int array_needs_making; extern char *bash_getcwd_errstr; static int bindpwd __P((int)); static int change_to_directory __P((char *, int)); static char *cdspell __P((char *)); /* Change this to 1 to get cd spelling correction by default. */ int cdspelling = 0; int cdable_vars; $BUILTIN cd $FUNCTION cd_builtin $SHORT_DOC cd [-L|-P] [dir] Change the current directory to DIR. The variable $HOME is the default DIR. The variable CDPATH defines the search path for the directory containing DIR. Alternative directory names in CDPATH are separated by a colon (:). A null directory name is the same as the current directory, i.e. `.'. If DIR begins with a slash (/), then CDPATH is not used. If the directory is not found, and the shell option `cdable_vars' is set, then try the word as a variable name. If that variable has a value, then cd to the value of that variable. The -P option says to use the physical directory structure instead of following symbolic links; the -L option forces symbolic links to be followed. $END static int bindpwd (no_symlinks) int no_symlinks; { char *dirname, *pwdvar; int old_anm; SHELL_VAR *tvar; #define tcwd the_current_working_directory dirname = tcwd ? (no_symlinks ? sh_physpath (tcwd, 0) : tcwd) : get_working_directory ("cd"); #undef tcwd old_anm = array_needs_making; pwdvar = get_string_value ("PWD"); tvar = bind_variable ("OLDPWD", pwdvar); if (old_anm == 0 && array_needs_making && exported_p (tvar)) { update_export_env_inplace ("OLDPWD=", 7, pwdvar); array_needs_making = 0; } tvar = bind_variable ("PWD", dirname); if (old_anm == 0 && array_needs_making && exported_p (tvar)) { update_export_env_inplace ("PWD=", 4, dirname); array_needs_making = 0; } if (dirname && dirname != the_current_working_directory) free (dirname); return (EXECUTION_SUCCESS); } /* Call get_working_directory to reset the value of the_current_working_directory () */ static char * resetpwd () { char *tdir; FREE (the_current_working_directory); the_current_working_directory = (char *)NULL; tdir = get_working_directory ("cd"); return (tdir); } #define LCD_DOVARS 0x001 #define LCD_DOSPELL 0x002 #define LCD_PRINTPATH 0x004 #define LCD_FREEDIRNAME 0x010 /* This builtin is ultimately the way that all user-visible commands should change the current working directory. It is called by cd_to_string (), so the programming interface is simple, and it handles errors and restrictions properly. */ int cd_builtin (list) WORD_LIST *list; { char *dirname, *cdpath, *path, *temp; int path_index, no_symlinks, opt, lflag; #if defined (RESTRICTED_SHELL) if (restricted) { sh_restricted ((char *)NULL); return (EXECUTION_FAILURE); } #endif /* RESTRICTED_SHELL */ no_symlinks = no_symbolic_links; reset_internal_getopt (); while ((opt = internal_getopt (list, "LP")) != -1) { switch (opt) { case 'P': no_symlinks = 1; break; case 'L': no_symlinks = 0; break; default: builtin_usage (); return (EXECUTION_FAILURE); } } list = loptend; lflag = (cdable_vars ? LCD_DOVARS : 0) | ((interactive && cdspelling) ? LCD_DOSPELL : 0); if (list == 0) { /* `cd' without arguments is equivalent to `cd $HOME' */ dirname = get_string_value ("HOME"); if (dirname == 0) { builtin_error ("HOME not set"); return (EXECUTION_FAILURE); } lflag = 0; } else if (list->word->word[0] == '-' && list->word->word[1] == '\0') { /* This is `cd -', equivalent to `cd $OLDPWD' */ dirname = get_string_value ("OLDPWD"); if (dirname == 0) { builtin_error ("OLDPWD not set"); return (EXECUTION_FAILURE); } lflag = interactive ? LCD_PRINTPATH : 0; } else if (absolute_pathname (list->word->word)) dirname = list->word->word; else if (cdpath = get_string_value ("CDPATH")) { dirname = list->word->word; /* Find directory in $CDPATH. */ path_index = 0; while (path = extract_colon_unit (cdpath, &path_index)) { /* OPT is 1 if the path element is non-empty */ opt = path[0] != '\0'; temp = sh_makepath (path, dirname, MP_DOTILDE); free (path); if (change_to_directory (temp, no_symlinks)) { /* POSIX.2 says that if a nonempty directory from CDPATH is used to find the directory to change to, the new directory name is echoed to stdout, whether or not the shell is interactive. */ if (opt) printf ("%s\n", no_symlinks ? temp : the_current_working_directory); free (temp); /* Posix.2 says that after using CDPATH, the resultant value of $PWD will not contain `.' or `..'. */ return (bindpwd (posixly_correct || no_symlinks)); } else free (temp); } /* POSIX.2 says that if `.' does not appear in $CDPATH, we don't try the current directory, so we just punt now with an error message if POSIXLY_CORRECT is non-zero. The check for cdpath[0] is so we don't mistakenly treat a CDPATH value of "" as not specifying the current directory. */ if (posixly_correct && cdpath[0]) { builtin_error ("%s: %s", dirname, strerror (ENOENT)); return (EXECUTION_FAILURE); } } else dirname = list->word->word; /* When we get here, DIRNAME is the directory to change to. If we chdir successfully, just return. */ if (change_to_directory (dirname, no_symlinks)) { if (lflag & LCD_PRINTPATH) printf ("%s\n", dirname); return (bindpwd (no_symlinks)); } /* If the user requests it, then perhaps this is the name of a shell variable, whose value contains the directory to change to. */ if (lflag & LCD_DOVARS) { temp = get_string_value (dirname); if (temp && change_to_directory (temp, no_symlinks)) { printf ("%s\n", temp); return (bindpwd (no_symlinks)); } } /* If the user requests it, try to find a directory name similar in spelling to the one requested, in case the user made a simple typo. This is similar to the UNIX 8th and 9th Edition shells. */ if (lflag & LCD_DOSPELL) { temp = cdspell (dirname); if (temp && change_to_directory (temp, no_symlinks)) { printf ("%s\n", temp); return (bindpwd (no_symlinks)); } else FREE (temp); } builtin_error ("%s: %s", dirname, strerror (errno)); return (EXECUTION_FAILURE); } $BUILTIN pwd $FUNCTION pwd_builtin $SHORT_DOC pwd [-PL] Print the current working directory. With the -P option, pwd prints the physical directory, without any symbolic links; the -L option makes pwd follow symbolic links. $END /* Non-zero means that pwd always prints the physical directory, without symbolic links. */ static int verbatim_pwd; /* Print the name of the current working directory. */ int pwd_builtin (list) WORD_LIST *list; { char *directory; int opt; verbatim_pwd = no_symbolic_links; reset_internal_getopt (); while ((opt = internal_getopt (list, "LP")) != -1) { switch (opt) { case 'P': verbatim_pwd = 1; break; case 'L': verbatim_pwd = 0; break; default: builtin_usage (); return (EXECUTION_FAILURE); } } list = loptend; #define tcwd the_current_working_directory directory = tcwd ? (verbatim_pwd ? sh_physpath (tcwd, 0) : tcwd) : get_working_directory ("pwd"); #undef tcwd if (directory) { printf ("%s\n", directory); if (directory != the_current_working_directory) free (directory); fflush (stdout); if (ferror (stdout)) { builtin_error ("write error: %s", strerror (errno)); return (EXECUTION_FAILURE); } return (EXECUTION_SUCCESS); } else return (EXECUTION_FAILURE); } /* Do the work of changing to the directory NEWDIR. Handle symbolic link following, etc. This function *must* return with the_current_working_directory either set to NULL (in which case getcwd() will eventually be called), or set to a string corresponding to the working directory. Return 1 on success, 0 on failure. */ static int change_to_directory (newdir, nolinks) char *newdir; int nolinks; { char *t, *tdir; int err, canon_failed; tdir = (char *)NULL; if (the_current_working_directory == 0) { t = get_working_directory ("chdir"); FREE (t); } t = make_absolute (newdir, the_current_working_directory); /* TDIR is either the canonicalized absolute pathname of NEWDIR (nolinks == 0) or the absolute physical pathname of NEWDIR (nolinks != 0). */ tdir = nolinks ? sh_physpath (t, 0) : sh_canonpath (t, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); /* Use the canonicalized version of NEWDIR, or, if canonicalization failed, use the non-canonical form. */ canon_failed = 0; if (tdir && *tdir) free (t); else { FREE (tdir); tdir = t; canon_failed = 1; } /* In POSIX mode, if we're resolving symlinks logically and sh_canonpath returns NULL (because it checks the path, it will return NULL if the resolved path doesn't exist), fail immediately. */ if (posixly_correct && nolinks == 0 && canon_failed) { errno = ENOENT; return (0); } /* If the chdir succeeds, update the_current_working_directory. */ if (chdir (nolinks ? newdir : tdir) == 0) { /* If canonicalization failed, but the chdir succeeded, reset the shell's idea of the_current_working_directory. */ if (canon_failed) resetpwd (); else { FREE (the_current_working_directory); the_current_working_directory = tdir; } return (1); } /* We failed to change to the appropriate directory name. If we tried what the user passed (nolinks != 0), punt now. */ if (nolinks) return (0); err = errno; free (tdir); /* We're not in physical mode (nolinks == 0), but we failed to change to the canonicalized directory name (TDIR). Try what the user passed verbatim. If we succeed, reinitialize the_current_working_directory. */ if (chdir (newdir) == 0) { tdir = resetpwd (); FREE (tdir); return (1); } else { errno = err; return (0); } } /* Code for cd spelling correction. Original patch submitted by Neil Russel (caret@c-side.com). */ static char * cdspell (dirname) char *dirname; { int n; char *guess; n = (strlen (dirname) * 3 + 1) / 2 + 1; guess = (char *)xmalloc (n); switch (spname (dirname, guess)) { case -1: default: free (guess); return (char *)NULL; case 0: case 1: return guess; } }
This file is colon.def, from which is created colon.c. It implements the builtin ":" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES colon.c $BUILTIN : $DOCNAME colon $FUNCTION colon_builtin $SHORT_DOC : No effect; the command does nothing. A zero exit code is returned. $END $BUILTIN true $FUNCTION colon_builtin $SHORT_DOC true Return a successful result. $END $BUILTIN false $FUNCTION false_builtin $SHORT_DOC false Return an unsuccessful result. $END /* Return a successful result. */ int colon_builtin (ignore) char *ignore; { return (0); } /* Return an unsuccessful result. */ int false_builtin (ignore) char *ignore; { return (1); }
This file is command.def, from which is created command.c. It implements the builtin "command" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES command.c $BUILTIN command $FUNCTION command_builtin $SHORT_DOC command [-pVv] command [arg ...] Runs COMMAND with ARGS ignoring shell functions. If you have a shell function called `ls', and you wish to call the command `ls', you can say "command ls". If the -p option is given, a default value is used for PATH that is guaranteed to find all of the standard utilities. If the -V or -v option is given, a string is printed describing COMMAND. The -V option produces a more verbose description. $END #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "../bashansi.h" #include "../shell.h" #include "../execute_cmd.h" #include "../flags.h" #include "bashgetopt.h" #include "common.h" #if defined (_CS_PATH) && defined (HAVE_CONFSTR) && !HAVE_DECL_CONFSTR extern size_t confstr __P((int, char *, size_t)); #endif extern int subshell_environment; static void restore_path __P((char *)); static char *get_standard_path __P((void)); /* Run the commands mentioned in LIST without paying attention to shell functions. */ int command_builtin (list) WORD_LIST *list; { int result, verbose, use_standard_path, opt; char *old_path, *standard_path; COMMAND *command; verbose = use_standard_path = 0; reset_internal_getopt (); while ((opt = internal_getopt (list, "pvV")) != -1) { switch (opt) { case 'p': use_standard_path = 1; break; case 'V': verbose = CDESC_SHORTDESC; /* look in common.h for constants */ break; case 'v': verbose = CDESC_REUSABLE; /* ditto */ break; default: builtin_usage (); return (EX_USAGE); } } list = loptend; if (list == 0) return (EXECUTION_SUCCESS); if (verbose) { int found, any_found; for (any_found = 0; list; list = list->next) { found = describe_command (list->word->word, verbose); if (found == 0) sh_notfound (list->word->word); any_found += found; } return (any_found ? EXECUTION_SUCCESS : EXECUTION_FAILURE); } #if defined (RESTRICTED_SHELL) if (use_standard_path && restricted) { sh_restricted ("-p"); return (EXECUTION_FAILURE); } #endif begin_unwind_frame ("command_builtin"); /* We don't want this to be reparsed (consider command echo 'foo &'), so just make a simple_command structure and call execute_command with it. */ if (use_standard_path) { old_path = get_string_value ("PATH"); /* If old_path is NULL, $PATH is unset. If so, we want to make sure it's unset after this command completes. */ if (old_path) old_path = savestring (old_path); add_unwind_protect ((Function *)restore_path, old_path); standard_path = get_standard_path (); bind_variable ("PATH", standard_path ? standard_path : ""); FREE (standard_path); } #define COMMAND_BUILTIN_FLAGS (CMD_NO_FUNCTIONS | CMD_INHIBIT_EXPANSION | CMD_COMMAND_BUILTIN) command = make_bare_simple_command (); command->value.Simple->words = (WORD_LIST *)copy_word_list (list); command->value.Simple->redirects = (REDIRECT *)NULL; command->flags |= COMMAND_BUILTIN_FLAGS; command->value.Simple->flags |= COMMAND_BUILTIN_FLAGS; #if 0 /* This breaks for things like ( cd /tmp ; command z ababa ; echo next ) or $(command echo a ; command echo b;) or even { command echo a; command echo b; } & */ /* If we're in a subshell, see if we can get away without forking again, since we've already forked to run this builtin. */ if (subshell_environment) { command->flags |= CMD_NO_FORK; command->value.Simple->flags |= CMD_NO_FORK; } #endif add_unwind_protect ((char *)dispose_command, command); result = execute_command (command); run_unwind_frame ("command_builtin"); return (result); } /* Restore the value of the $PATH variable after replacing it when executing `command -p'. */ static void restore_path (var) char *var; { if (var) { bind_variable ("PATH", var); free (var); } else unbind_variable ("PATH"); } /* Return a value for PATH that is guaranteed to find all of the standard utilities. This uses Posix.2 configuration variables, if present. It uses a value defined in config.h as a last resort. */ static char * get_standard_path () { #if defined (_CS_PATH) && defined (HAVE_CONFSTR) char *p; size_t len; len = (size_t)confstr (_CS_PATH, (char *)NULL, (size_t)0); if (len > 0) { p = (char *)xmalloc (len + 2); *p = '\0'; confstr (_CS_PATH, p, len); return (p); } else return (savestring (STANDARD_UTILS_PATH)); #else /* !_CS_PATH || !HAVE_CONFSTR */ # if defined (CS_PATH) return (savestring (CS_PATH)); # else return (savestring (STANDARD_UTILS_PATH)); # endif /* !CS_PATH */ #endif /* !_CS_PATH || !HAVE_CONFSTR */ }
This file is complete.def, from which is created complete.c. It implements the builtins "complete" and "compgen" in Bash. Copyright (C) 1999-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES complete.c $BUILTIN complete $DEPENDS_ON PROGRAMMABLE_COMPLETION $FUNCTION complete_builtin $SHORT_DOC complete [-abcdefgjksuv] [-pr] [-o option] [-A action] [-G globpat] [-W wordlist] [-P prefix] [-S suffix] [-X filterpat] [-F function] [-C command] [name ...] For each NAME, specify how arguments are to be completed. If the -p option is supplied, or if no options are supplied, existing completion specifications are printed in a way that allows them to be reused as input. The -r option removes a completion specification for each NAME, or, if no NAMEs are supplied, all completion specifications. $END #include <config.h> #include <stdio.h> #include "../bashtypes.h" #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "../bashansi.h" #include "../shell.h" #include "../builtins.h" #include "../pcomplete.h" #include "common.h" #include "bashgetopt.h" #include <readline/readline.h> #define STRDUP(x) ((x) ? savestring (x) : (char *)NULL) static int find_compact __P((char *)); static int find_compopt __P((char *)); static int build_actions __P((WORD_LIST *, int *, int *, unsigned long *, unsigned long *)); static int remove_cmd_completions __P((WORD_LIST *)); static int print_one_completion __P((char *, COMPSPEC *)); static int print_compitem __P((BUCKET_CONTENTS *)); static void print_all_completions __P((void)); static int print_cmd_completions __P((WORD_LIST *)); static char *Garg, *Warg, *Parg, *Sarg, *Xarg, *Farg, *Carg; static struct _compacts { char *actname; int actflag; int actopt; } compacts[] = { { "alias", CA_ALIAS, 'a' }, { "arrayvar", CA_ARRAYVAR, 0 }, { "binding", CA_BINDING, 0 }, { "builtin", CA_BUILTIN, 'b' }, { "command", CA_COMMAND, 'c' }, { "directory", CA_DIRECTORY, 'd' }, { "disabled", CA_DISABLED, 0 }, { "enabled", CA_ENABLED, 0 }, { "export", CA_EXPORT, 'e' }, { "file", CA_FILE, 'f' }, { "function", CA_FUNCTION, 0 }, { "helptopic", CA_BUILTIN, 0 }, /* for now */ { "hostname", CA_HOSTNAME, 0 }, { "group", CA_GROUP, 'g' }, { "job", CA_JOB, 'j' }, { "keyword", CA_KEYWORD, 'k' }, { "running", CA_RUNNING, 0 }, { "service", CA_SERVICE, 's' }, { "setopt", CA_SETOPT, 0 }, { "shopt", CA_SHOPT, 0 }, { "signal", CA_SIGNAL, 0 }, { "stopped", CA_STOPPED, 0 }, { "user", CA_USER, 'u' }, { "variable", CA_VARIABLE, 'v' }, { (char *)NULL, 0, 0 }, }; /* This should be a STRING_INT_ALIST */ static struct _compopt { char *optname; int optflag; } compopts[] = { { "default", COPT_DEFAULT }, { "dirnames", COPT_DIRNAMES }, { "filenames",COPT_FILENAMES}, { "nospace", COPT_NOSPACE }, { (char *)NULL, 0 }, }; static int find_compact (name) char *name; { register int i; for (i = 0; compacts[i].actname; i++) if (STREQ (name, compacts[i].actname)) return i; return -1; } static int find_compopt (name) char *name; { register int i; for (i = 0; compopts[i].optname; i++) if (STREQ (name, compopts[i].optname)) return i; return -1; } /* Build the actions and compspec options from the options specified in LIST. ACTP is a pointer to an unsigned long in which to place the bitmap of actions. OPTP is a pointer to an unsigned long in which to place the btmap of compspec options (arguments to `-o'). PP, if non-null, gets 1 if -p is supplied; RP, if non-null, gets 1 if -r is supplied. If either is null, the corresponding option generates an error. This also sets variables corresponding to options that take arguments as a side effect; the caller should ensure that those variables are set to NULL before calling build_actions. Return value: EX_USAGE = bad option EXECUTION_SUCCESS = some options supplied EXECUTION_FAILURE = no options supplied */ static int build_actions (list, pp, rp, actp, optp) WORD_LIST *list; int *pp, *rp; unsigned long *actp, *optp; { int opt, ind, opt_given; unsigned long acts, copts; acts = copts = (unsigned long)0L; opt_given = 0; reset_internal_getopt (); while ((opt = internal_getopt (list, "abcdefgjko:prsuvA:G:W:P:S:X:F:C:")) != -1) { opt_given = 1; switch (opt) { case 'r': if (rp) { *rp = 1; break; } else { sh_invalidopt ("-r"); builtin_usage (); return (EX_USAGE); } case 'p': if (pp) { *pp = 1; break; } else { sh_invalidopt ("-p"); builtin_usage (); return (EX_USAGE); } case 'a': acts |= CA_ALIAS; break; case 'b': acts |= CA_BUILTIN; break; case 'c': acts |= CA_COMMAND; break; case 'd': acts |= CA_DIRECTORY; break; case 'e': acts |= CA_EXPORT; break; case 'f': acts |= CA_FILE; break; case 'g': acts |= CA_GROUP; break; case 'j': acts |= CA_JOB; break; case 'k': acts |= CA_KEYWORD; break; case 's': acts |= CA_SERVICE; break; case 'u': acts |= CA_USER; break; case 'v': acts |= CA_VARIABLE; break; case 'o': ind = find_compopt (list_optarg); if (ind < 0) { sh_invalidoptname (list_optarg); return (EX_USAGE); } copts |= compopts[ind].optflag; break; case 'A': ind = find_compact (list_optarg); if (ind < 0) { builtin_error ("%s: invalid action name", list_optarg); return (EX_USAGE); } acts |= compacts[ind].actflag; break; case 'C': Carg = list_optarg; break; case 'F': Farg = list_optarg; break; case 'G': Garg = list_optarg; break; case 'P': Parg = list_optarg; break; case 'S': Sarg = list_optarg; break; case 'W': Warg = list_optarg; break; case 'X': Xarg = list_optarg; break; default: builtin_usage (); return (EX_USAGE); } } *actp = acts; *optp = copts; return (opt_given ? EXECUTION_SUCCESS : EXECUTION_FAILURE); } /* Add, remove, and display completion specifiers. */ int complete_builtin (list) WORD_LIST *list; { int opt_given, pflag, rflag, rval; unsigned long acts, copts; COMPSPEC *cs; if (list == 0) { print_all_completions (); return (EXECUTION_SUCCESS); } opt_given = pflag = rflag = 0; acts = copts = (unsigned long)0L; Garg = Warg = Parg = Sarg = Xarg = Farg = Carg = (char *)NULL; cs = (COMPSPEC *)NULL; /* Build the actions from the arguments. Also sets the [A-Z]arg variables as a side effect if they are supplied as options. */ rval = build_actions (list, &pflag, &rflag, &acts, &copts); if (rval == EX_USAGE) return (rval); opt_given = rval != EXECUTION_FAILURE; list = loptend; /* -p overrides everything else */ if (pflag || (list == 0 && opt_given == 0)) { if (list == 0) { print_all_completions (); return (EXECUTION_SUCCESS); } return (print_cmd_completions (list)); } /* next, -r overrides everything else. */ if (rflag) { if (list == 0) { progcomp_flush (); return (EXECUTION_SUCCESS); } return (remove_cmd_completions (list)); } if (list == 0 && opt_given) { builtin_usage (); return (EX_USAGE); } /* If we get here, we need to build a compspec and add it for each remaining argument. */ cs = compspec_create (); cs->actions = acts; cs->options = copts; cs->globpat = STRDUP (Garg); cs->words = STRDUP (Warg); cs->prefix = STRDUP (Parg); cs->suffix = STRDUP (Sarg); cs->funcname = STRDUP (Farg); cs->command = STRDUP (Carg); cs->filterpat = STRDUP (Xarg); for (rval = EXECUTION_SUCCESS ; list; list = list->next) { /* Add CS as the compspec for the specified commands. */ if (progcomp_insert (list->word->word, cs) == 0) rval = EXECUTION_FAILURE; } return (rval); } static int remove_cmd_completions (list) WORD_LIST *list; { WORD_LIST *l; int ret; for (ret = EXECUTION_SUCCESS, l = list; l; l = l->next) { if (progcomp_remove (l->word->word) == 0) { builtin_error ("%s: no completion specification", l->word->word); ret = EXECUTION_FAILURE; } } return ret; } #define SQPRINTARG(a, f) \ do { \ if (a) \ { \ x = sh_single_quote (a); \ printf ("%s %s ", f, x); \ free (x); \ } \ } while (0) #define PRINTARG(a, f) \ do { \ if (a) \ printf ("%s %s ", f, a); \ } while (0) #define PRINTOPT(a, f) \ do { \ if (acts & a) \ printf ("%s ", f); \ } while (0) #define PRINTACT(a, f) \ do { \ if (acts & a) \ printf ("-A %s ", f); \ } while (0) #define PRINTCOMPOPT(a, f) \ do { \ if (copts & a) \ printf ("-o %s ", f); \ } while (0) static int print_one_completion (cmd, cs) char *cmd; COMPSPEC *cs; { unsigned long acts, copts; char *x; printf ("complete "); copts = cs->options; /* First, print the -o options. */ PRINTCOMPOPT (COPT_DEFAULT, "default"); PRINTCOMPOPT (COPT_DIRNAMES, "dirnames"); PRINTCOMPOPT (COPT_FILENAMES, "filenames"); PRINTCOMPOPT (COPT_NOSPACE, "nospace"); acts = cs->actions; /* simple flags next */ PRINTOPT (CA_ALIAS, "-a"); PRINTOPT (CA_BUILTIN, "-b"); PRINTOPT (CA_COMMAND, "-c"); PRINTOPT (CA_DIRECTORY, "-d"); PRINTOPT (CA_EXPORT, "-e"); PRINTOPT (CA_FILE, "-f"); PRINTOPT (CA_GROUP, "-g"); PRINTOPT (CA_JOB, "-j"); PRINTOPT (CA_KEYWORD, "-k"); PRINTOPT (CA_SERVICE, "-s"); PRINTOPT (CA_USER, "-u"); PRINTOPT (CA_VARIABLE, "-v"); /* now the rest of the actions */ PRINTACT (CA_ARRAYVAR, "arrayvar"); PRINTACT (CA_BINDING, "binding"); PRINTACT (CA_DISABLED, "disabled"); PRINTACT (CA_ENABLED, "enabled"); PRINTACT (CA_FUNCTION, "function"); PRINTACT (CA_HELPTOPIC, "helptopic"); PRINTACT (CA_HOSTNAME, "hostname"); PRINTACT (CA_RUNNING, "running"); PRINTACT (CA_SETOPT, "setopt"); PRINTACT (CA_SHOPT, "shopt"); PRINTACT (CA_SIGNAL, "signal"); PRINTACT (CA_STOPPED, "stopped"); /* now the rest of the arguments */ /* arguments that require quoting */ SQPRINTARG (cs->globpat, "-G"); SQPRINTARG (cs->words, "-W"); SQPRINTARG (cs->prefix, "-P"); SQPRINTARG (cs->suffix, "-S"); SQPRINTARG (cs->filterpat, "-X"); /* simple arguments that don't require quoting */ PRINTARG (cs->funcname, "-F"); PRINTARG (cs->command, "-C"); printf ("%s\n", cmd); return (0); } static int print_compitem (item) BUCKET_CONTENTS *item; { COMPSPEC *cs; char *cmd; cmd = item->key; cs = (COMPSPEC *)item->data; return (print_one_completion (cmd, cs)); } static void print_all_completions () { progcomp_walk (print_compitem); } static int print_cmd_completions (list) WORD_LIST *list; { WORD_LIST *l; COMPSPEC *cs; int ret; for (ret = EXECUTION_SUCCESS, l = list; l; l = l->next) { cs = progcomp_search (l->word->word); if (cs) print_one_completion (l->word->word, cs); else { builtin_error ("%s: no completion specification", l->word->word); ret = EXECUTION_FAILURE; } } return (ret); } $BUILTIN compgen $DEPENDS_ON PROGRAMMABLE_COMPLETION $FUNCTION compgen_builtin $SHORT_DOC compgen [-abcdefgjksuv] [-o option] [-A action] [-G globpat] [-W wordlist] [-P prefix] [-S suffix] [-X filterpat] [-F function] [-C command] [word] Display the possible completions depending on the options. Intended to be used from within a shell function generating possible completions. If the optional WORD argument is supplied, matches against WORD are generated. $END int compgen_builtin (list) WORD_LIST *list; { int rval; unsigned long acts, copts; COMPSPEC *cs; STRINGLIST *sl; char *word; if (list == 0) return (EXECUTION_SUCCESS); acts = copts = (unsigned long)0L; Garg = Warg = Parg = Sarg = Xarg = Farg = Carg = (char *)NULL; cs = (COMPSPEC *)NULL; /* Build the actions from the arguments. Also sets the [A-Z]arg variables as a side effect if they are supplied as options. */ rval = build_actions (list, (int *)NULL, (int *)NULL, &acts, &copts); if (rval == EX_USAGE) return (rval); if (rval == EXECUTION_FAILURE) return (EXECUTION_SUCCESS); list = loptend; word = (list && list->word) ? list->word->word : ""; if (Farg) internal_warning ("compgen: -F option may not work as you expect"); if (Carg) internal_warning ("compgen: -C option may not work as you expect"); /* If we get here, we need to build a compspec and evaluate it. */ cs = compspec_create (); cs->actions = acts; cs->options = copts; cs->refcount = 1; cs->globpat = STRDUP (Garg); cs->words = STRDUP (Warg); cs->prefix = STRDUP (Parg); cs->suffix = STRDUP (Sarg); cs->funcname = STRDUP (Farg); cs->command = STRDUP (Carg); cs->filterpat = STRDUP (Xarg); rval = EXECUTION_FAILURE; sl = gen_compspec_completions (cs, "compgen", word, 0, 0); /* This isn't perfect, but it's the best we can do, given what readline exports from its set of completion utility functions. */ if ((sl == 0 || sl->list_len == 0) && (copts & COPT_DEFAULT)) { char **matches; matches = rl_completion_matches (word, rl_filename_completion_function); sl = completions_to_stringlist (matches); strvec_dispose (matches); } if (sl) { if (sl->list && sl->list_len) { rval = EXECUTION_SUCCESS; strlist_print (sl, (char *)NULL); } strlist_dispose (sl); } compspec_dispose (cs); return (rval); }
This file is declare.def, from which is created declare.c. It implements the builtins "declare" and "local" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES declare.c $BUILTIN declare $FUNCTION declare_builtin $SHORT_DOC declare [-afFirtx] [-p] name[=value] ... Declare variables and/or give them attributes. If no NAMEs are given, then display the values of variables instead. The -p option will display the attributes and values of each NAME. The flags are: -a to make NAMEs arrays (if supported) -f to select from among function names only -F to display function names without definitions -i to make NAMEs have the `integer' attribute -r to make NAMEs readonly -t to make NAMEs have the `trace' attribute -x to make NAMEs export Variables with the integer attribute have arithmetic evaluation (see `let') done when the variable is assigned to. When displaying values of variables, -f displays a function's name and definition. The -F option restricts the display to function name only. Using `+' instead of `-' turns off the given attribute instead. When used in a function, makes NAMEs local, as with the `local' command. $END $BUILTIN typeset $FUNCTION declare_builtin $SHORT_DOC typeset [-afFirtx] [-p] name[=value] ... Obsolete. See `declare'. $END #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include <stdio.h> #include "../bashansi.h" #include "../shell.h" #include "common.h" #include "builtext.h" #include "bashgetopt.h" extern int array_needs_making; static int declare_internal __P((register WORD_LIST *, int)); /* Declare or change variable attributes. */ int declare_builtin (list) register WORD_LIST *list; { return (declare_internal (list, 0)); } $BUILTIN local $FUNCTION local_builtin $SHORT_DOC local name[=value] ... Create a local variable called NAME, and give it VALUE. LOCAL can only be used within a function; it makes the variable NAME have a visible scope restricted to that function and its children. $END int local_builtin (list) register WORD_LIST *list; { if (variable_context) return (declare_internal (list, 1)); else { builtin_error ("can only be used in a function"); return (EXECUTION_FAILURE); } } #if defined (ARRAY_VARS) # define DECLARE_OPTS "+afiprtxF" #else # define DECLARE_OPTS "+fiprtxF" #endif /* The workhorse function. */ static int declare_internal (list, local_var) register WORD_LIST *list; int local_var; { int flags_on, flags_off, *flags, any_failed, assign_error, pflag, nodefs, opt; char *t, *subscript_start; SHELL_VAR *var; flags_on = flags_off = any_failed = assign_error = pflag = nodefs = 0; reset_internal_getopt (); while ((opt = internal_getopt (list, DECLARE_OPTS)) != EOF) { flags = list_opttype == '+' ? &flags_off : &flags_on; switch (opt) { case 'a': #if defined (ARRAY_VARS) *flags |= att_array; #endif break; case 'p': if (local_var == 0) pflag++; break; case 'F': nodefs++; *flags |= att_function; break; case 'f': *flags |= att_function; break; case 'i': *flags |= att_integer; break; case 'r': *flags |= att_readonly; break; case 't': *flags |= att_trace; break; case 'x': *flags |= att_exported; array_needs_making = 1; break; default: builtin_usage (); return (EX_USAGE); } } list = loptend; /* If there are no more arguments left, then we just want to show some variables. */ if (list == 0) /* declare -[afFirtx] */ { /* Show local variables defined at this context level if this is the `local' builtin. */ if (local_var) { register SHELL_VAR **vlist; register int i; vlist = all_local_variables (); if (vlist) { for (i = 0; vlist[i]; i++) print_assignment (vlist[i]); free (vlist); } } else { if (flags_on == 0) set_builtin ((WORD_LIST *)NULL); else set_or_show_attributes ((WORD_LIST *)NULL, flags_on, nodefs); } fflush (stdout); return (EXECUTION_SUCCESS); } if (pflag) /* declare -p [-afFirtx] name [name...] */ { for (any_failed = 0; list; list = list->next) { pflag = show_name_attributes (list->word->word, nodefs); if (pflag) { sh_notfound (list->word->word); any_failed++; } } return (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS); } #define NEXT_VARIABLE() free (name); list = list->next; continue /* There are arguments left, so we are making variables. */ while (list) /* declare [-afFirx] name [name ...] */ { char *value, *name; int offset; #if defined (ARRAY_VARS) int making_array_special, compound_array_assign, simple_array_assign; #endif name = savestring (list->word->word); offset = assignment (name); if (offset) /* declare [-afFirx] name=value */ { name[offset] = '\0'; value = name + offset + 1; } else value = ""; #if defined (ARRAY_VARS) compound_array_assign = simple_array_assign = 0; subscript_start = (char *)NULL; if (t = strchr (name, '[')) /* ] */ { subscript_start = t; *t = '\0'; making_array_special = 1; } else making_array_special = 0; #endif if (legal_identifier (name) == 0) { sh_invalidid (name); assign_error++; NEXT_VARIABLE (); } /* If VARIABLE_CONTEXT has a non-zero value, then we are executing inside of a function. This means we should make local variables, not global ones. */ /* XXX - this has consequences when we're making a local copy of a variable that was in the temporary environment. Watch out for this. */ if (variable_context && ((flags_on & att_function) == 0)) { #if defined (ARRAY_VARS) if ((flags_on & att_array) || making_array_special) var = make_local_array_variable (name); else #endif var = make_local_variable (name); if (var == 0) { any_failed++; NEXT_VARIABLE (); } } else var = (SHELL_VAR *)NULL; /* If we are declaring a function, then complain about it in some way. We don't let people make functions by saying `typeset -f foo=bar'. */ /* There should be a way, however, to let people look at a particular function definition by saying `typeset -f foo'. */ if (flags_on & att_function) { if (offset) /* declare -f [-rix] foo=bar */ { builtin_error ("cannot use `-f' to make functions"); free (name); return (EXECUTION_FAILURE); } else /* declare -f [-rx] name [name...] */ { var = find_function (name); if (var) { if (readonly_p (var) && (flags_off & att_readonly)) { builtin_error ("%s: readonly function", name); any_failed++; NEXT_VARIABLE (); } /* declare -[Ff] name [name...] */ if (flags_on == att_function && flags_off == 0) { t = nodefs ? var->name : named_function_string (name, function_cell (var), 1); printf ("%s\n", t); } else /* declare -[fF] -[rx] name [name...] */ { VSETATTR (var, flags_on); VUNSETATTR (var, flags_off); } } else any_failed++; NEXT_VARIABLE (); } } else /* declare -[airx] name [name...] */ { /* Non-null if we just created or fetched a local variable. */ if (var == 0) var = find_variable (name); if (var == 0) { #if defined (ARRAY_VARS) if ((flags_on & att_array) || making_array_special) var = make_new_array_variable (name); else #endif var = bind_variable (name, ""); } /* Cannot use declare +r to turn off readonly attribute. */ if (readonly_p (var) && (flags_off & att_readonly)) { sh_readonly (name); any_failed++; NEXT_VARIABLE (); } /* Cannot use declare to assign value to readonly or noassign variable. */ if ((readonly_p (var) || noassign_p (var)) && offset) { if (readonly_p (var)) sh_readonly (name); assign_error++; NEXT_VARIABLE (); } #if defined (ARRAY_VARS) if ((making_array_special || (flags_on & att_array) || array_p (var)) && offset) { if (value[0] == '(' && strchr (value, ')')) compound_array_assign = 1; else simple_array_assign = 1; } /* Cannot use declare +a name to remove an array variable. */ if ((flags_off & att_array) && array_p (var)) { builtin_error ("%s: cannot destroy array variables in this way", name); any_failed++; NEXT_VARIABLE (); } /* declare -a name makes name an array variable. */ if ((making_array_special || (flags_on & att_array)) && array_p (var) == 0) var = convert_var_to_array (var); #endif /* ARRAY_VARS */ VSETATTR (var, flags_on); VUNSETATTR (var, flags_off); #if defined (ARRAY_VARS) if (offset && compound_array_assign) assign_array_var_from_string (var, value); else if (simple_array_assign && subscript_start) { /* declare [-a] name[N]=value */ *subscript_start = '['; /* ] */ var = assign_array_element (name, value); *subscript_start = '\0'; } else if (simple_array_assign) /* let bind_array_variable take care of this. */ bind_array_variable (name, 0, value); else #endif /* bind_variable_value duplicates the essential internals of bind_variable() */ if (offset) bind_variable_value (var, value); /* If we found this variable in the temporary environment, as with `var=value declare -x var', make sure it is treated identically to `var=value export var'. Do the same for `declare -r' and `readonly'. Preserve the attributes, except for att_tempvar. */ /* XXX -- should this create a variable in the global scope, or modify the local variable flags? ksh93 has it modify the global scope. Need to handle case like in set_var_attribute where a temporary variable is in the same table as the function local vars. */ if ((flags_on & (att_exported|att_readonly)) && tempvar_p (var)) { SHELL_VAR *tv; char *tvalue; tv = find_tempenv_variable (var->name); if (tv) { tvalue = var_isset (var) ? savestring (value_cell (var)) : savestring (""); tv = bind_variable (var->name, tvalue); tv->attributes |= var->attributes & ~att_tempvar; if (tv->context > 0) VSETATTR (tv, att_propagate); free (tvalue); } VSETATTR (var, att_propagate); } } stupidly_hack_special_variables (name); NEXT_VARIABLE (); } return (assign_error ? EX_BADASSIGN : ((any_failed == 0) ? EXECUTION_SUCCESS : EXECUTION_FAILURE)); }
This file is echo.def, from which is created echo.c. It implements the builtin "echo" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES echo.c #include <config.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "../bashansi.h" #include <stdio.h> #include "../shell.h" $BUILTIN echo $FUNCTION echo_builtin $DEPENDS_ON V9_ECHO $SHORT_DOC echo [-neE] [arg ...] Output the ARGs. If -n is specified, the trailing newline is suppressed. If the -e option is given, interpretation of the following backslash-escaped characters is turned on: \a alert (bell) \b backspace \c suppress trailing newline \E escape character \f form feed \n new line \r carriage return \t horizontal tab \v vertical tab \\ backslash \num the character whose ASCII code is NUM (octal). You can explicitly turn off the interpretation of the above characters with the -E option. $END $BUILTIN echo $FUNCTION echo_builtin $DEPENDS_ON !V9_ECHO $SHORT_DOC echo [-n] [arg ...] Output the ARGs. If -n is specified, the trailing newline is suppressed. $END #if defined (V9_ECHO) # define VALID_ECHO_OPTIONS "neE" #else /* !V9_ECHO */ # define VALID_ECHO_OPTIONS "n" #endif /* !V9_ECHO */ /* System V machines already have a /bin/sh with a v9 behaviour. We give Bash the identical behaviour for these machines so that the existing system shells won't barf. Regrettably, the SUS v2 has standardized the Sys V echo behavior. This variable is external so that we can have a `shopt' variable to control it at runtime. */ #if defined (DEFAULT_ECHO_TO_XPG) int xpg_echo = 1; #else int xpg_echo = 0; #endif /* DEFAULT_ECHO_TO_XPG */ /* Print the words in LIST to standard output. If the first word is `-n', then don't print a trailing newline. We also support the echo syntax from Version 9 Unix systems. */ int echo_builtin (list) WORD_LIST *list; { int display_return, do_v9, i, len; char *temp, *s; do_v9 = xpg_echo; display_return = 1; for (; list && (temp = list->word->word) && *temp == '-'; list = list->next) { /* If it appears that we are handling options, then make sure that all of the options specified are actually valid. Otherwise, the string should just be echoed. */ temp++; for (i = 0; temp[i]; i++) { if (strchr (VALID_ECHO_OPTIONS, temp[i]) == 0) break; } /* echo - and echo -<nonopt> both mean to just echo the arguments. */ if (*temp == 0 || temp[i]) break; /* All of the options in TEMP are valid options to ECHO. Handle them. */ while (i = *temp++) { switch (i) { case 'n': display_return = 0; break; #if defined (V9_ECHO) case 'e': do_v9 = 1; break; case 'E': do_v9 = 0; break; #endif /* V9_ECHO */ default: goto just_echo; /* XXX */ } } } just_echo: while (list) { i = len = 0; temp = do_v9 ? ansicstr (list->word->word, STRLEN (list->word->word), 1, &i, &len) : list->word->word; if (temp) { if (do_v9) { for (s = temp; len > 0; len--) putchar (*s++); } else printf ("%s", temp); #if defined (SunOS5) fflush (stdout); /* Fix for bug in SunOS 5.5 printf(3) */ #endif } if (do_v9 && temp) free (temp); list = list->next; if (i) { display_return = 0; break; } if (list) putchar(' '); } if (display_return) putchar ('\n'); fflush (stdout); if (ferror (stdout)) { clearerr (stdout); return (EXECUTION_FAILURE); } return (EXECUTION_SUCCESS); }
This file is enable.def, from which is created enable.c. It implements the builtin "enable" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES enable.c $BUILTIN enable $FUNCTION enable_builtin $SHORT_DOC enable [-pnds] [-a] [-f filename] [name ...] Enable and disable builtin shell commands. This allows you to use a disk command which has the same name as a shell builtin without specifying a full pathname. If -n is used, the NAMEs become disabled; otherwise NAMEs are enabled. For example, to use the `test' found in $PATH instead of the shell builtin version, type `enable -n test'. On systems supporting dynamic loading, the -f option may be used to load new builtins from the shared object FILENAME. The -d option will delete a builtin previously loaded with -f. If no non-option names are given, or the -p option is supplied, a list of builtins is printed. The -a option means to print every builtin with an indication of whether or not it is enabled. The -s option restricts the output to the POSIX.2 `special' builtins. The -n option displays a list of all disabled builtins. $END #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include <stdio.h> #include "../bashansi.h" #include "../shell.h" #include "../builtins.h" #include "../flags.h" #include "common.h" #include "bashgetopt.h" #if defined (PROGRAMMABLE_COMPLETION) # include "../pcomplete.h" #endif #define ENABLED 1 #define DISABLED 2 #define SPECIAL 4 #define AFLAG 0x01 #define DFLAG 0x02 #define FFLAG 0x04 #define NFLAG 0x08 #define PFLAG 0x10 #define SFLAG 0x20 #if defined (HAVE_DLOPEN) && defined (HAVE_DLSYM) static int dyn_load_builtin __P((WORD_LIST *, int, char *)); #endif #if defined (HAVE_DLCLOSE) static int dyn_unload_builtin __P((char *)); static void delete_builtin __P((struct builtin *)); static int local_dlclose __P((void *)); #endif static void list_some_builtins __P((int)); static int enable_shell_command __P((char *, int)); /* Enable/disable shell commands present in LIST. If list is not specified, then print out a list of shell commands showing which are enabled and which are disabled. */ int enable_builtin (list) WORD_LIST *list; { int result, flags; int opt, filter; #if defined (HAVE_DLOPEN) && defined (HAVE_DLSYM) char *filename; #endif result = EXECUTION_SUCCESS; flags = 0; reset_internal_getopt (); while ((opt = internal_getopt (list, "adnpsf:")) != -1) { switch (opt) { case 'a': flags |= AFLAG; break; case 'n': flags |= NFLAG; break; case 'p': flags |= PFLAG; break; case 's': flags |= SFLAG; break; case 'f': #if defined (HAVE_DLOPEN) && defined (HAVE_DLSYM) flags |= FFLAG; filename = list_optarg; break; #else builtin_error ("dynamic loading not available"); return (EX_USAGE); #endif #if defined (HAVE_DLCLOSE) case 'd': flags |= DFLAG; break; #else builtin_error ("dynamic loading not available"); return (EX_USAGE); #endif /* HAVE_DLCLOSE */ default: builtin_usage (); return (EX_USAGE); } } list = loptend; #if defined (RESTRICTED_SHELL) /* Restricted shells cannot load new builtins. */ if (restricted && (flags & (FFLAG|DFLAG))) { sh_restricted ((char *)NULL); return (EXECUTION_FAILURE); } #endif if (list == 0 || (flags & PFLAG)) { filter = (flags & AFLAG) ? (ENABLED | DISABLED) : (flags & NFLAG) ? DISABLED : ENABLED; if (flags & SFLAG) filter |= SPECIAL; list_some_builtins (filter); } #if defined (HAVE_DLOPEN) && defined (HAVE_DLSYM) else if (flags & FFLAG) { filter = (flags & NFLAG) ? DISABLED : ENABLED; if (flags & SFLAG) filter |= SPECIAL; result = dyn_load_builtin (list, filter, filename); #if defined (PROGRAMMABLE_COMPLETION) set_itemlist_dirty (&it_builtins); #endif } #endif #if defined (HAVE_DLCLOSE) else if (flags & DFLAG) { while (list) { opt = dyn_unload_builtin (list->word->word); if (opt == EXECUTION_FAILURE) result = EXECUTION_FAILURE; list = list->next; } #if defined (PROGRAMMABLE_COMPLETION) set_itemlist_dirty (&it_builtins); #endif } #endif else { while (list) { opt = enable_shell_command (list->word->word, flags & NFLAG); if (opt == EXECUTION_FAILURE) { builtin_error ("%s: not a shell builtin", list->word->word); result = EXECUTION_FAILURE; } list = list->next; } } return (result); } /* List some builtins. FILTER is a mask with two slots: ENABLED and DISABLED. */ static void list_some_builtins (filter) int filter; { register int i; for (i = 0; i < num_shell_builtins; i++) { if (shell_builtins[i].function == 0 || (shell_builtins[i].flags & BUILTIN_DELETED)) continue; if ((filter & SPECIAL) && (shell_builtins[i].flags & SPECIAL_BUILTIN) == 0) continue; if ((filter & ENABLED) && (shell_builtins[i].flags & BUILTIN_ENABLED)) printf ("enable %s\n", shell_builtins[i].name); else if ((filter & DISABLED) && ((shell_builtins[i].flags & BUILTIN_ENABLED) == 0)) printf ("enable -n %s\n", shell_builtins[i].name); } } /* Enable the shell command NAME. If DISABLE_P is non-zero, then disable NAME instead. */ static int enable_shell_command (name, disable_p) char *name; int disable_p; { struct builtin *b; b = builtin_address_internal (name, 1); if (b == 0) return (EXECUTION_FAILURE); if (disable_p) b->flags &= ~BUILTIN_ENABLED; #if defined (RESTRICTED_SHELL) else if (restricted && ((b->flags & BUILTIN_ENABLED) == 0)) { sh_restricted ((char *)NULL); return (EXECUTION_FAILURE); } #endif else b->flags |= BUILTIN_ENABLED; #if defined (PROGRAMMABLE_COMPLETION) set_itemlist_dirty (&it_enabled); set_itemlist_dirty (&it_disabled); #endif return (EXECUTION_SUCCESS); } #if defined (HAVE_DLOPEN) && defined (HAVE_DLSYM) #if defined (HAVE_DLFCN_H) # include <dlfcn.h> #endif static int dyn_load_builtin (list, flags, filename) WORD_LIST *list; int flags; char *filename; { WORD_LIST *l; void *handle; int total, size, new, replaced; char *struct_name, *name; struct builtin **new_builtins, *b, *new_shell_builtins, *old_builtin; if (list == 0) return (EXECUTION_FAILURE); #ifndef RTLD_LAZY #define RTLD_LAZY 1 #endif #if defined (_AIX) handle = dlopen (filename, RTLD_NOW|RTLD_GLOBAL); #else handle = dlopen (filename, RTLD_LAZY); #endif /* !_AIX */ if (handle == 0) { builtin_error ("cannot open shared object %s: %s", filename, dlerror ()); return (EXECUTION_FAILURE); } for (new = 0, l = list; l; l = l->next, new++) ; new_builtins = (struct builtin **)xmalloc (new * sizeof (struct builtin *)); /* For each new builtin in the shared object, find it and its describing structure. If this is overwriting an existing builtin, do so, otherwise save the loaded struct for creating the new list of builtins. */ for (replaced = new = 0; list; list = list->next) { name = list->word->word; size = strlen (name); struct_name = (char *)xmalloc (size + 8); strcpy (struct_name, name); strcpy (struct_name + size, "_struct"); b = (struct builtin *)dlsym (handle, struct_name); if (b == 0) { builtin_error ("cannot find %s in shared object %s: %s", struct_name, filename, dlerror ()); free (struct_name); continue; } free (struct_name); b->flags &= ~STATIC_BUILTIN; if (flags & SPECIAL) b->flags |= SPECIAL_BUILTIN; b->handle = handle; if (old_builtin = builtin_address_internal (name, 1)) { replaced++; FASTCOPY ((char *)b, (char *)old_builtin, sizeof (struct builtin)); } else new_builtins[new++] = b; } if (replaced == 0 && new == 0) { free (new_builtins); dlclose (handle); return (EXECUTION_FAILURE); } if (new) { total = num_shell_builtins + new; size = (total + 1) * sizeof (struct builtin); new_shell_builtins = (struct builtin *)xmalloc (size); FASTCOPY ((char *)shell_builtins, (char *)new_shell_builtins, num_shell_builtins * sizeof (struct builtin)); for (replaced = 0; replaced < new; replaced++) FASTCOPY ((char *)new_builtins[replaced], (char *)&new_shell_builtins[num_shell_builtins + replaced], sizeof (struct builtin)); new_shell_builtins[total].name = (char *)0; new_shell_builtins[total].function = (sh_builtin_func_t *)0; new_shell_builtins[total].flags = 0; if (shell_builtins != static_shell_builtins) free (shell_builtins); shell_builtins = new_shell_builtins; num_shell_builtins = total; initialize_shell_builtins (); } free (new_builtins); return (EXECUTION_SUCCESS); } #endif #if defined (HAVE_DLCLOSE) static void delete_builtin (b) struct builtin *b; { int ind, size; struct builtin *new_shell_builtins; /* XXX - funky pointer arithmetic - XXX */ #ifdef __STDC__ ind = b - shell_builtins; #else ind = ((int)b - (int)shell_builtins) / sizeof (struct builtin); #endif size = num_shell_builtins * sizeof (struct builtin); new_shell_builtins = (struct builtin *)xmalloc (size); /* Copy shell_builtins[0]...shell_builtins[ind - 1] to new_shell_builtins */ if (ind) FASTCOPY ((char *)shell_builtins, (char *)new_shell_builtins, ind * sizeof (struct builtin)); /* Copy shell_builtins[ind+1]...shell_builtins[num_shell_builtins to new_shell_builtins, starting at ind. */ FASTCOPY ((char *)(&shell_builtins[ind+1]), (char *)(&new_shell_builtins[ind]), (num_shell_builtins - ind) * sizeof (struct builtin)); if (shell_builtins != static_shell_builtins) free (shell_builtins); /* The result is still sorted. */ num_shell_builtins--; shell_builtins = new_shell_builtins; } /* Tenon's MachTen has a dlclose that doesn't return a value, so we finesse it with a local wrapper. */ static int local_dlclose (handle) void *handle; { #if !defined (__MACHTEN__) return (dlclose (handle)); #else /* __MACHTEN__ */ dlclose (handle); return ((dlerror () != NULL) ? -1 : 0); #endif /* __MACHTEN__ */ } static int dyn_unload_builtin (name) char *name; { struct builtin *b; void *handle; int ref, i; b = builtin_address_internal (name, 1); if (b == 0) { builtin_error ("%s: not a shell builtin", name); return (EXECUTION_FAILURE); } if (b->flags & STATIC_BUILTIN) { builtin_error ("%s: not dynamically loaded", name); return (EXECUTION_FAILURE); } handle = (void *)b->handle; for (ref = i = 0; i < num_shell_builtins; i++) { if (shell_builtins[i].handle == b->handle) ref++; } /* Don't remove the shared object unless the reference count of builtins using it drops to zero. */ if (ref == 1 && local_dlclose (handle) != 0) { builtin_error ("%s: cannot delete: %s", name, dlerror ()); return (EXECUTION_FAILURE); } /* Now remove this entry from the builtin table and reinitialize. */ delete_builtin (b); return (EXECUTION_SUCCESS); } #endif
This file is eval.def, from which is created eval.c. It implements the builtin "eval" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES eval.c $BUILTIN eval $FUNCTION eval_builtin $SHORT_DOC eval [arg ...] Read ARGs as input to the shell and execute the resulting command(s). $END #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "../shell.h" #include "bashgetopt.h" #include "common.h" /* Parse the string that these words make, and execute the command found. */ int eval_builtin (list) WORD_LIST *list; { if (no_options (list)) return (EX_USAGE); list = loptend; /* skip over possible `--' */ /* Note that parse_and_execute () frees the string it is passed. */ return (list ? parse_and_execute (string_list (list), "eval", SEVAL_NOHIST) : EXECUTION_SUCCESS); }
This file is exec.def, from which is created exec.c. It implements the builtin "exec" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES exec.c $BUILTIN exec $FUNCTION exec_builtin $SHORT_DOC exec [-cl] [-a name] file [redirection ...] Exec FILE, replacing this shell with the specified program. If FILE is not specified, the redirections take effect in this shell. If the first argument is `-l', then place a dash in the zeroth arg passed to FILE, as login does. If the `-c' option is supplied, FILE is executed with a null environment. The `-a' option means to make set argv[0] of the executed process to NAME. If the file cannot be executed and the shell is not interactive, then the shell exits, unless the shell option `execfail' is set. $END #include <config.h> #include "../bashtypes.h" #include "posixstat.h" #include <signal.h> #include <errno.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "../bashansi.h" #include "../shell.h" #include "../execute_cmd.h" #include "../findcmd.h" #if defined (JOB_CONTROL) # include "../jobs.h" #endif #include "../flags.h" #include "../trap.h" #if defined (HISTORY) # include "../bashhist.h" #endif #include "common.h" #include "bashgetopt.h" /* Not all systems declare ERRNO in errno.h... and some systems #define it! */ #if !defined (errno) extern int errno; #endif /* !errno */ extern int subshell_environment; extern REDIRECT *redirection_undo_list; int no_exit_on_failed_exec; /* If the user wants this to look like a login shell, then prepend a `-' onto NAME and return the new name. */ static char * mkdashname (name) char *name; { char *ret; ret = (char *)xmalloc (2 + strlen (name)); ret[0] = '-'; strcpy (ret + 1, name); return ret; } int exec_builtin (list) WORD_LIST *list; { int exit_value = EXECUTION_FAILURE; int cleanenv, login, opt; char *argv0, *command, **args, **env, *newname, *com2; cleanenv = login = 0; argv0 = (char *)NULL; reset_internal_getopt (); while ((opt = internal_getopt (list, "cla:")) != -1) { switch (opt) { case 'c': cleanenv = 1; break; case 'l': login = 1; break; case 'a': argv0 = list_optarg; break; default: builtin_usage (); return (EX_USAGE); } } list = loptend; /* First, let the redirections remain. */ dispose_redirects (redirection_undo_list); redirection_undo_list = (REDIRECT *)NULL; if (list == 0) return (EXECUTION_SUCCESS); #if defined (RESTRICTED_SHELL) if (restricted) { sh_restricted ((char *)NULL); return (EXECUTION_FAILURE); } #endif /* RESTRICTED_SHELL */ args = strvec_from_word_list (list, 1, 0, (int *)NULL); /* A command with a slash anywhere in its name is not looked up in $PATH. */ command = absolute_program (args[0]) ? args[0] : search_for_command (args[0]); if (command == 0) { sh_notfound (args[0]); exit_value = EX_NOTFOUND; /* As per Posix.2, 3.14.6 */ goto failed_exec; } com2 = full_pathname (command); if (com2) { if (command != args[0]) free (command); command = com2; } if (argv0) { free (args[0]); args[0] = login ? mkdashname (argv0) : savestring (argv0); } else if (login) { newname = mkdashname (args[0]); free (args[0]); args[0] = newname; } /* Decrement SHLVL by 1 so a new shell started here has the same value, preserving the appearance. After we do that, we need to change the exported environment to include the new value. */ if (cleanenv == 0) adjust_shell_level (-1); if (cleanenv) env = (char **)NULL; else { maybe_make_export_env (); env = export_env; } #if defined (HISTORY) if (interactive_shell && subshell_environment == 0) maybe_save_shell_history (); #endif /* HISTORY */ restore_original_signals (); #if defined (JOB_CONTROL) if (subshell_environment == 0) end_job_control (); #endif /* JOB_CONTROL */ shell_execve (command, args, env); /* We have to set this to NULL because shell_execve has called realloc() to stuff more items at the front of the array, which may have caused the memory to be freed by realloc(). We don't want to free it twice. */ args = (char **)NULL; if (cleanenv == 0) adjust_shell_level (1); if (executable_file (command) == 0) { builtin_error ("%s: cannot execute: %s", command, strerror (errno)); exit_value = EX_NOEXEC; /* As per Posix.2, 3.14.6 */ } else file_error (command); failed_exec: if (command) free (command); if (subshell_environment || (interactive == 0 && no_exit_on_failed_exec == 0)) exit_shell (exit_value); if (args) strvec_dispose (args); initialize_traps (); initialize_signals (1); #if defined (JOB_CONTROL) restart_job_control (); #endif /* JOB_CONTROL */ return (exit_value); }
This file is exit.def, from which is created exit.c. It implements the builtins "exit", and "logout" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES exit.c $BUILTIN exit $FUNCTION exit_builtin $SHORT_DOC exit [n] Exit the shell with a status of N. If N is omitted, the exit status is that of the last command executed. $END #include <config.h> #include "../bashtypes.h" #include <stdio.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "../shell.h" #include "../jobs.h" #include "common.h" #include "builtext.h" /* for jobs_builtin */ extern int last_command_exit_value; extern int subshell_environment; extern sh_builtin_func_t *this_shell_builtin; extern sh_builtin_func_t *last_shell_builtin; static int exit_or_logout __P((WORD_LIST *)); static int sourced_logout; int exit_builtin (list) WORD_LIST *list; { if (interactive) { fprintf (stderr, login_shell ? "logout\n" : "exit\n"); fflush (stderr); } return (exit_or_logout (list)); } $BUILTIN logout $FUNCTION logout_builtin $SHORT_DOC logout Logout of a login shell. $END /* How to logout. */ int logout_builtin (list) WORD_LIST *list; { if (login_shell == 0 /* && interactive */) { builtin_error ("not login shell: use `exit'"); return (EXECUTION_FAILURE); } else return (exit_or_logout (list)); } static int exit_or_logout (list) WORD_LIST *list; { int exit_value; #if defined (JOB_CONTROL) int exit_immediate_okay; exit_immediate_okay = (interactive == 0 || last_shell_builtin == exit_builtin || last_shell_builtin == logout_builtin || last_shell_builtin == jobs_builtin); /* Check for stopped jobs if the user wants to. */ if (!exit_immediate_okay) { register int i; for (i = 0; i < job_slots; i++) if (jobs[i] && STOPPED (i)) { fprintf (stderr, "There are stopped jobs.\n"); /* This is NOT superfluous because EOF can get here without going through the command parser. Set both last and this so that either `exit', `logout', or ^D will work to exit immediately if nothing intervenes. */ this_shell_builtin = last_shell_builtin = exit_builtin; return (EXECUTION_FAILURE); } } #endif /* JOB_CONTROL */ /* Get return value if present. This means that you can type `logout 5' to a shell, and it returns 5. */ exit_value = get_exitstat (list); /* Run our `~/.bash_logout' file if it exists, and this is a login shell. */ if (login_shell && sourced_logout++ == 0 && subshell_environment == 0) { maybe_execute_file ("~/.bash_logout", 1); #ifdef SYS_BASH_LOGOUT maybe_execute_file (SYS_BASH_LOGOUT, 1); #endif } last_command_exit_value = exit_value; /* Exit the program. */ jump_to_top_level (EXITPROG); /*NOTREACHED*/ }
This file is fc.def, from which is created fc.c. It implements the builtin "fc" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES fc.c $BUILTIN fc $FUNCTION fc_builtin $DEPENDS_ON HISTORY $SHORT_DOC fc [-e ename] [-nlr] [first] [last] or fc -s [pat=rep] [cmd] fc is used to list or edit and re-execute commands from the history list. FIRST and LAST can be numbers specifying the range, or FIRST can be a string, which means the most recent command beginning with that string. -e ENAME selects which editor to use. Default is FCEDIT, then EDITOR, then vi. -l means list lines instead of editing. -n means no line numbers listed. -r means reverse the order of the lines (making it newest listed first). With the `fc -s [pat=rep ...] [command]' format, the command is re-executed after the substitution OLD=NEW is performed. A useful alias to use with this is r='fc -s', so that typing `r cc' runs the last command beginning with `cc' and typing `r' re-executes the last command. $END #include <config.h> #if defined (HISTORY) #ifndef _MINIX # include <sys/param.h> #endif #include "../bashtypes.h" #include "posixstat.h" #ifndef _MINIX # include <sys/file.h> #endif #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <stdio.h> #include <chartypes.h> #include "../bashansi.h" #include <errno.h> #include "../shell.h" #include "../builtins.h" #include "../flags.h" #include "../bashhist.h" #include "maxpath.h" #include <readline/history.h> #include "bashgetopt.h" #include "common.h" #if !defined (errno) extern int errno; #endif /* !errno */ extern int echo_input_at_read; extern int current_command_line_count; extern int literal_history; extern int unlink __P((const char *)); extern FILE *sh_mktmpfp __P((char *, int, char **)); /* **************************************************************** */ /* */ /* The K*rn shell style fc command (Fix Command) */ /* */ /* **************************************************************** */ /* fc builtin command (fix command) for Bash for those who like K*rn-style history better than csh-style. fc [-e ename] [-nlr] [first] [last] FIRST and LAST can be numbers specifying the range, or FIRST can be a string, which means the most recent command beginning with that string. -e ENAME selects which editor to use. Default is FCEDIT, then EDITOR, then the editor which corresponds to the current readline editing mode, then vi. -l means list lines instead of editing. -n means no line numbers listed. -r means reverse the order of the lines (making it newest listed first). fc -e - [pat=rep ...] [command] fc -s [pat=rep ...] [command] Equivalent to !command:sg/pat/rep execpt there can be multiple PAT=REP's. */ /* Data structure describing a list of global replacements to perform. */ typedef struct repl { struct repl *next; char *pat; char *rep; } REPL; /* Accessors for HIST_ENTRY lists that are called HLIST. */ #define histline(i) (hlist[(i)]->line) #define histdata(i) (hlist[(i)]->data) #define FREE_RLIST() \ do { \ for (rl = rlist; rl; ) { \ REPL *r; \ r = rl->next; \ if (rl->pat) \ free (rl->pat); \ if (rl->rep) \ free (rl->rep); \ free (rl); \ rl = r; \ } \ } while (0) static char *fc_dosubs __P((char *, REPL *)); static char *fc_gethist __P((char *, HIST_ENTRY **)); static int fc_gethnum __P((char *, HIST_ENTRY **)); static int fc_number __P((WORD_LIST *)); static void fc_replhist __P((char *)); #ifdef INCLUDE_UNUSED static char *fc_readline __P((FILE *)); static void fc_addhist __P((char *)); #endif /* String to execute on a file that we want to edit. */ #define FC_EDIT_COMMAND "${FCEDIT:-${EDITOR:-vi}}" int fc_builtin (list) WORD_LIST *list; { register int i; register char *sep; int numbering, reverse, listing, execute; int histbeg, histend, last_hist, retval, opt; FILE *stream; REPL *rlist, *rl; char *ename, *command, *newcom; HIST_ENTRY **hlist; char *fn; numbering = 1; reverse = listing = execute = 0; ename = (char *)NULL; /* Parse out the options and set which of the two forms we're in. */ reset_internal_getopt (); lcurrent = list; /* XXX */ while (fc_number (loptend = lcurrent) == 0 && (opt = internal_getopt (list, ":e:lnrs")) != -1) { switch (opt) { case 'n': numbering = 0; break; case 'l': listing = 1; break; case 'r': reverse = 1; break; case 's': execute = 1; break; case 'e': ename = list_optarg; break; default: builtin_usage (); return (EX_USAGE); } } list = loptend; if (ename && (*ename == '-') && (ename[1] == '\0')) execute = 1; /* The "execute" form of the command (re-run, with possible string substitutions). */ if (execute) { rlist = (REPL *)NULL; while (list && ((sep = (char *)strchr (list->word->word, '=')) != NULL)) { *sep++ = '\0'; rl = (REPL *)xmalloc (sizeof (REPL)); rl->next = (REPL *)NULL; rl->pat = savestring (list->word->word); rl->rep = savestring (sep); if (rlist == NULL) rlist = rl; else { rl->next = rlist; rlist = rl; } list = list->next; } /* If we have a list of substitutions to do, then reverse it to get the replacements in the proper order. */ rlist = REVERSE_LIST (rlist, REPL *); hlist = history_list (); /* If we still have something in list, it is a command spec. Otherwise, we use the most recent command in time. */ command = fc_gethist (list ? list->word->word : (char *)NULL, hlist); if (command == NULL) { builtin_error ("no command found"); if (rlist) FREE_RLIST (); return (EXECUTION_FAILURE); } if (rlist) { newcom = fc_dosubs (command, rlist); free (command); FREE_RLIST (); command = newcom; } fprintf (stderr, "%s\n", command); fc_replhist (command); /* replace `fc -s' with command */ return (parse_and_execute (command, "fc", SEVAL_NOHIST)); } /* This is the second form of the command (the list-or-edit-and-rerun form). */ hlist = history_list (); if (hlist == 0) return (EXECUTION_SUCCESS); for (i = 0; hlist[i]; i++); /* With the Bash implementation of history, the current command line ("fc blah..." and so on) is already part of the history list by the time we get to this point. This just skips over that command and makes the last command that this deals with be the last command the user entered before the fc. We need to check whether the line was actually added (HISTIGNORE may have caused it to not be), so we check hist_last_line_added. */ last_hist = i - 1 - hist_last_line_added; if (list) { histbeg = fc_gethnum (list->word->word, hlist); list = list->next; if (list) histend = fc_gethnum (list->word->word, hlist); else histend = listing ? last_hist : histbeg; } else { /* The default for listing is the last 16 history items. */ if (listing) { histend = last_hist; histbeg = histend - 16; if (histbeg < 0) histbeg = 0; } else /* For editing, it is the last history command. */ histbeg = histend = last_hist; } /* We print error messages for line specifications out of range. */ if ((histbeg < 0) || (histend < 0)) { sh_erange ((char *)NULL, "history specification"); return (EXECUTION_FAILURE); } if (histend < histbeg) { i = histend; histend = histbeg; histbeg = i; reverse = 1; } if (listing) stream = stdout; else { numbering = 0; stream = sh_mktmpfp ("bash-fc", MT_USERANDOM|MT_USETMPDIR, &fn); if (stream == 0) { builtin_error ("cannot open temp file %s", fn ? fn : ""); FREE (fn); return (EXECUTION_FAILURE); } } for (i = reverse ? histend : histbeg; reverse ? i >= histbeg : i <= histend; reverse ? i-- : i++) { QUIT; if (numbering) fprintf (stream, "%d", i + history_base); if (listing) fprintf (stream, "\t%c", histdata (i) ? '*' : ' '); fprintf (stream, "%s\n", histline (i)); } if (listing) return (EXECUTION_SUCCESS); fclose (stream); /* Now edit the file of commands. */ if (ename) { command = (char *)xmalloc (strlen (ename) + strlen (fn) + 2); sprintf (command, "%s %s", ename, fn); } else { command = (char *)xmalloc (3 + strlen (FC_EDIT_COMMAND) + strlen (fn)); sprintf (command, "%s %s", FC_EDIT_COMMAND, fn); } retval = parse_and_execute (command, "fc", SEVAL_NOHIST); if (retval != EXECUTION_SUCCESS) { unlink (fn); free (fn); return (EXECUTION_FAILURE); } /* Make sure parse_and_execute doesn't turn this off, even though a call to parse_and_execute farther up the function call stack (e.g., if this is called by vi_edit_and_execute_command) may have already called bash_history_disable. */ remember_on_history = 1; /* Turn on the `v' flag while fc_execute_file runs so the commands will be echoed as they are read by the parser. */ begin_unwind_frame ("fc builtin"); add_unwind_protect ((Function *)xfree, fn); add_unwind_protect (unlink, fn); unwind_protect_int (echo_input_at_read); echo_input_at_read = 1; retval = fc_execute_file (fn); run_unwind_frame ("fc builtin"); return (retval); } /* Return 1 if LIST->word->word is a legal number for fc's use. */ static int fc_number (list) WORD_LIST *list; { char *s; if (list == 0) return 0; s = list->word->word; if (*s == '-') s++; return (legal_number (s, (intmax_t *)NULL)); } /* Return an absolute index into HLIST which corresponds to COMMAND. If COMMAND is a number, then it was specified in relative terms. If it is a string, then it is the start of a command line present in HLIST. */ static int fc_gethnum (command, hlist) char *command; HIST_ENTRY **hlist; { int sign = 1, n, clen; register int i, j; register char *s; /* Count history elements. */ for (i = 0; hlist[i]; i++); /* With the Bash implementation of history, the current command line ("fc blah..." and so on) is already part of the history list by the time we get to this point. This just skips over that command and makes the last command that this deals with be the last command the user entered before the fc. We need to check whether the line was actually added (HISTIGNORE may have caused it to not be), so we check hist_last_line_added. */ i -= 1 + hist_last_line_added; /* No specification defaults to most recent command. */ if (command == NULL) return (i); /* Otherwise, there is a specification. It can be a number relative to the current position, or an absolute history number. */ s = command; /* Handle possible leading minus sign. */ if (s && (*s == '-')) { sign = -1; s++; } if (s && DIGIT(*s)) { n = atoi (s); n *= sign; /* If the value is negative or zero, then it is an offset from the current history item. */ if (n < 0) { n += i + 1; return (n < 0 ? 0 : n); } else if (n == 0) return (i); else { n -= history_base; return (i < n ? i : n); } } clen = strlen (command); for (j = i; j >= 0; j--) { if (STREQN (command, histline (j), clen)) return (j); } return (-1); } /* Locate the most recent history line which begins with COMMAND in HLIST, and return a malloc()'ed copy of it. */ static char * fc_gethist (command, hlist) char *command; HIST_ENTRY **hlist; { int i; if (!hlist) return ((char *)NULL); i = fc_gethnum (command, hlist); if (i >= 0) return (savestring (histline (i))); else return ((char *)NULL); } #ifdef INCLUDE_UNUSED /* Read the edited history lines from STREAM and return them one at a time. This can read unlimited length lines. The caller should free the storage. */ static char * fc_readline (stream) FILE *stream; { register int c; int line_len = 0, lindex = 0; char *line = (char *)NULL; while ((c = getc (stream)) != EOF) { if ((lindex + 2) >= line_len) line = (char *)xrealloc (line, (line_len += 128)); if (c == '\n') { line[lindex++] = '\n'; line[lindex++] = '\0'; return (line); } else line[lindex++] = c; } if (!lindex) { if (line) free (line); return ((char *)NULL); } if (lindex + 2 >= line_len) line = (char *)xrealloc (line, lindex + 3); line[lindex++] = '\n'; /* Finish with newline if none in file */ line[lindex++] = '\0'; return (line); } #endif /* Perform the SUBS on COMMAND. SUBS is a list of substitutions, and COMMAND is a simple string. Return a pointer to a malloc'ed string which contains the substituted command. */ static char * fc_dosubs (command, subs) char *command; REPL *subs; { register char *new, *t; register REPL *r; for (new = savestring (command), r = subs; r; r = r->next) { t = strsub (new, r->pat, r->rep, 1); free (new); new = t; } return (new); } /* Use `command' to replace the last entry in the history list, which, by this time, is `fc blah...'. The intent is that the new command become the history entry, and that `fc' should never appear in the history list. This way you can do `r' to your heart's content. */ static void fc_replhist (command) char *command; { register int i; HIST_ENTRY **hlist, *histent, *discard; int n; if (command == 0 || *command == '\0') return; hlist = history_list (); if (hlist == NULL) return; for (i = 0; hlist[i]; i++); i--; /* History_get () takes a parameter that should be offset by history_base. */ histent = history_get (history_base + i); /* Don't free this */ if (histent == NULL) return; n = strlen (command); if (command[n - 1] == '\n') command[n - 1] = '\0'; if (command && *command) { discard = remove_history (i); if (discard) { FREE (discard->line); free ((char *) discard); } maybe_add_history (command); /* Obeys HISTCONTROL setting. */ } } #ifdef INCLUDE_UNUSED /* Add LINE to the history, after removing a single trailing newline. */ static void fc_addhist (line) char *line; { register int n; n = strlen (line); if (line[n - 1] == '\n') line[n - 1] = '\0'; if (line && *line) maybe_add_history (line); } #endif #endif /* HISTORY */
This file is fg_bg.def, from which is created fg_bg.c. It implements the builtins "bg" and "fg" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES fg_bg.c $BUILTIN fg $FUNCTION fg_builtin $DEPENDS_ON JOB_CONTROL $SHORT_DOC fg [job_spec] Place JOB_SPEC in the foreground, and make it the current job. If JOB_SPEC is not present, the shell's notion of the current job is used. $END #include <config.h> #include "../bashtypes.h" #include <signal.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "../shell.h" #include "../jobs.h" #include "common.h" #include "bashgetopt.h" #if defined (JOB_CONTROL) extern char *this_command_name; static int fg_bg __P((WORD_LIST *, int)); /* How to bring a job into the foreground. */ int fg_builtin (list) WORD_LIST *list; { int fg_bit; register WORD_LIST *t; if (job_control == 0) { sh_nojobs ((char *)NULL); return (EXECUTION_FAILURE); } if (no_options (list)) return (EX_USAGE); list = loptend; /* If the last arg on the line is '&', then start this job in the background. Else, fg the job. */ for (t = list; t && t->next; t = t->next) ; fg_bit = (t && t->word->word[0] == '&' && t->word->word[1] == '\0') == 0; return (fg_bg (list, fg_bit)); } #endif /* JOB_CONTROL */ $BUILTIN bg $FUNCTION bg_builtin $DEPENDS_ON JOB_CONTROL $SHORT_DOC bg [job_spec] Place JOB_SPEC in the background, as if it had been started with `&'. If JOB_SPEC is not present, the shell's notion of the current job is used. $END #if defined (JOB_CONTROL) /* How to put a job into the background. */ int bg_builtin (list) WORD_LIST *list; { if (job_control == 0) { sh_nojobs ((char *)NULL); return (EXECUTION_FAILURE); } if (no_options (list)) return (EX_USAGE); list = loptend; return (fg_bg (list, 0)); } /* How to put a job into the foreground/background. */ static int fg_bg (list, foreground) WORD_LIST *list; int foreground; { sigset_t set, oset; int job, status, old_async_pid; BLOCK_CHILD (set, oset); job = get_job_spec (list); if (job < 0 || job >= job_slots || jobs[job] == 0) { if (job != DUP_JOB) sh_badjob (list ? list->word->word : "current"); goto failure; } /* Or if jobs[job]->pgrp == shell_pgrp. */ if (IS_JOBCONTROL (job) == 0) { builtin_error ("job %%%d started without job control", job + 1); goto failure; } if (foreground == 0) { old_async_pid = last_asynchronous_pid; last_asynchronous_pid = jobs[job]->pgrp; /* As per Posix.2 5.4.2 */ } status = start_job (job, foreground); if (status >= 0) { /* win: */ UNBLOCK_CHILD (oset); return (status); } else { if (foreground == 0) last_asynchronous_pid = old_async_pid; failure: UNBLOCK_CHILD (oset); return (EXECUTION_FAILURE); } } #endif /* JOB_CONTROL */
This file is hash.def, from which is created hash.c. It implements the builtin "hash" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES hash.c $BUILTIN hash $FUNCTION hash_builtin $SHORT_DOC hash [-lr] [-p pathname] [-dt] [name ...] For each NAME, the full pathname of the command is determined and remembered. If the -p option is supplied, PATHNAME is used as the full pathname of NAME, and no path search is performed. The -r option causes the shell to forget all remembered locations. The -d option causes the shell to forget the remembered location of each NAME. If the -t option is supplied the full pathname to which each NAME corresponds is printed. If multiple NAME arguments are supplied with -t, the NAME is printed before the hashed full pathname. The -l option causes output to be displayed in a format that may be reused as input. If no arguments are given, information about remembered commands is displayed. $END #include <config.h> #include <stdio.h> #include "../bashtypes.h" #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <errno.h> #include "../bashansi.h" #include "../shell.h" #include "../builtins.h" #include "../flags.h" #include "../findcmd.h" #include "../hashcmd.h" #include "common.h" #include "bashgetopt.h" extern int dot_found_in_search; extern char *this_command_name; static int add_hashed_command __P((char *, int)); static int print_hash_info __P((BUCKET_CONTENTS *)); static int print_portable_hash_info __P((BUCKET_CONTENTS *)); static int print_hashed_commands __P((int)); static int list_hashed_filename_targets __P((WORD_LIST *, int)); /* Print statistics on the current state of hashed commands. If LIST is not empty, then rehash (or hash in the first place) the specified commands. */ int hash_builtin (list) WORD_LIST *list; { int expunge_hash_table, list_targets, list_portably, delete, opt; char *w, *pathname; if (hashing_enabled == 0) { builtin_error ("hashing disabled"); return (EXECUTION_FAILURE); } expunge_hash_table = list_targets = list_portably = delete = 0; pathname = (char *)NULL; reset_internal_getopt (); while ((opt = internal_getopt (list, "dlp:rt")) != -1) { switch (opt) { case 'd': delete = 1; break; case 'l': list_portably = 1; break; case 'p': pathname = list_optarg; break; case 'r': expunge_hash_table = 1; break; case 't': list_targets = 1; break; default: builtin_usage (); return (EX_USAGE); } } list = loptend; /* hash -t requires at least one argument. */ if (list == 0 && list_targets) { sh_needarg ("-t"); return (EXECUTION_FAILURE); } /* We want hash -r to be silent, but hash -- to print hashing info, so we test expunge_hash_table. */ if (list == 0 && expunge_hash_table == 0) { if (print_hashed_commands (list_portably) == 0) printf ("%s: hash table empty\n", this_command_name); return (EXECUTION_SUCCESS); } if (expunge_hash_table) phash_flush (); /* If someone runs `hash -r -t xyz' he will be disappointed. */ if (list_targets) return (list_hashed_filename_targets (list, list_portably)); #if defined (RESTRICTED_SHELL) if (restricted && pathname && strchr (pathname, '/')) { sh_restricted (pathname); return (EXECUTION_FAILURE); } #endif for (opt = EXECUTION_SUCCESS; list; list = list->next) { /* Add, remove or rehash the specified commands. */ w = list->word->word; if (pathname) { if (is_directory (pathname)) { #ifdef EISDIR builtin_error ("%s: %s", pathname, strerror (EISDIR)); #else builtin_error ("%s: is a directory", pathname); #endif opt = EXECUTION_FAILURE; } else phash_insert (w, pathname, 0, 0); } else if (absolute_program (w)) continue; else if (delete && phash_remove (w)) { sh_notfound (w); opt = EXECUTION_FAILURE; } else if (add_hashed_command (w, 0)) opt = EXECUTION_FAILURE; } fflush (stdout); return (opt); } static int add_hashed_command (w, quiet) char *w; int quiet; { int rv; char *full_path; rv = 0; if (find_function (w) == 0 && find_shell_builtin (w) == 0) { full_path = find_user_command (w); if (full_path && executable_file (full_path)) phash_insert (w, full_path, dot_found_in_search, 0); else { if (quiet == 0) sh_notfound (w); rv++; } FREE (full_path); } return (rv); } /* Print information about current hashed info. */ static int print_hash_info (item) BUCKET_CONTENTS *item; { printf ("%4d\t%s\n", item->times_found, pathdata(item)->path); return 0; } static int print_portable_hash_info (item) BUCKET_CONTENTS *item; { printf ("builtin hash -p %s %s\n", pathdata(item)->path, item->key); return 0; } static int print_hashed_commands (fmt) int fmt; { if (hashed_filenames == 0 || HASH_ENTRIES (hashed_filenames) == 0) return (0); if (fmt == 0) printf ("hits\tcommand\n"); hash_walk (hashed_filenames, fmt ? print_portable_hash_info : print_hash_info); return (1); } static int list_hashed_filename_targets (list, fmt) WORD_LIST *list; int fmt; { int all_found, multiple; char *target; WORD_LIST *l; all_found = 1; multiple = list->next != 0; for (l = list; l; l = l->next) { target = phash_search (l->word->word); if (target == 0) { all_found = 0; sh_notfound (l->word->word); continue; } if (fmt) printf ("builtin hash -p %s %s\n", target, l->word->word); else { if (multiple) printf ("%s\t", l->word->word); printf ("%s\n", target); } } return (all_found ? EXECUTION_SUCCESS : EXECUTION_FAILURE); }
This file is history.def, from which is created history.c. It implements the builtin "history" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES history.c $BUILTIN history $FUNCTION history_builtin $DEPENDS_ON HISTORY $SHORT_DOC history [-c] [-d offset] [n] or history -awrn [filename] or history -ps arg [arg...] Display the history list with line numbers. Lines listed with with a `*' have been modified. Argument of N says to list only the last N lines. The `-c' option causes the history list to be cleared by deleting all of the entries. The `-d' option deletes the history entry at offset OFFSET. The `-w' option writes out the current history to the history file; `-r' means to read the file and append the contents to the history list instead. `-a' means to append history lines from this session to the history file. Argument `-n' means to read all history lines not already read from the history file and append them to the history list. If FILENAME is given, then that is used as the history file else if $HISTFILE has a value, that is used, else ~/.bash_history. If the -s option is supplied, the non-option ARGs are appended to the history list as a single entry. The -p option means to perform history expansion on each ARG and display the result, without storing anything in the history list. $END #include <config.h> #if defined (HISTORY) #include "../bashtypes.h" #ifndef _MINIX # include <sys/file.h> #endif #include "posixstat.h" #include "filecntl.h" #include <errno.h> #include <stdio.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "../bashansi.h" #include "../shell.h" #include "../bashhist.h" #include <readline/history.h> #include "bashgetopt.h" #include "common.h" #if !defined (errno) extern int errno; #endif extern int current_command_line_count; static void display_history __P((WORD_LIST *)); static int delete_histent __P((int)); static int delete_last_history __P((void)); static void push_history __P((WORD_LIST *)); static int expand_and_print_history __P((WORD_LIST *)); #define AFLAG 0x01 #define RFLAG 0x02 #define WFLAG 0x04 #define NFLAG 0x08 #define SFLAG 0x10 #define PFLAG 0x20 #define CFLAG 0x40 #define DFLAG 0x80 int history_builtin (list) WORD_LIST *list; { int flags, opt, result, old_history_lines; char *filename, *delete_arg; intmax_t delete_offset; flags = 0; reset_internal_getopt (); while ((opt = internal_getopt (list, "acd:npsrw")) != -1) { switch (opt) { case 'a': flags |= AFLAG; break; case 'c': flags |= CFLAG; break; case 'n': flags |= NFLAG; break; case 'r': flags |= RFLAG; break; case 'w': flags |= WFLAG; break; case 's': flags |= SFLAG; break; case 'd': flags |= DFLAG; delete_arg = list_optarg; break; case 'p': #if defined (BANG_HISTORY) flags |= PFLAG; #endif break; default: builtin_usage (); return (EX_USAGE); } } list = loptend; opt = flags & (AFLAG|RFLAG|WFLAG|NFLAG); if (opt && opt != AFLAG && opt != RFLAG && opt != WFLAG && opt != NFLAG) { builtin_error ("cannot use more than one of -anrw"); return (EXECUTION_FAILURE); } /* clear the history, but allow other arguments to add to it again. */ if (flags & CFLAG) { clear_history (); if (list == 0) return (EXECUTION_SUCCESS); } if (flags & SFLAG) { if (list) push_history (list); return (EXECUTION_SUCCESS); } #if defined (BANG_HISTORY) else if (flags & PFLAG) { if (list) return (expand_and_print_history (list)); return (EXECUTION_SUCCESS); } #endif else if (flags & DFLAG) { if ((legal_number (delete_arg, &delete_offset) == 0) || (delete_offset < history_base) || (delete_offset > (history_base + history_length))) { sh_erange (delete_arg, "history position"); return (EXECUTION_FAILURE); } opt = delete_offset; result = delete_histent (opt - history_base); /* Since remove_history changes history_length, this can happen if we delete the last history entry. */ if (where_history () > history_length) history_set_pos (history_length); return (result ? EXECUTION_SUCCESS : EXECUTION_FAILURE); } else if ((flags & (AFLAG|RFLAG|NFLAG|WFLAG|CFLAG)) == 0) { display_history (list); return (EXECUTION_SUCCESS); } filename = list ? list->word->word : get_string_value ("HISTFILE"); result = EXECUTION_SUCCESS; if (flags & AFLAG) /* Append session's history to file. */ result = maybe_append_history (filename); else if (flags & WFLAG) /* Write entire history. */ result = write_history (filename); else if (flags & RFLAG) /* Read entire file. */ result = read_history (filename); else if (flags & NFLAG) /* Read `new' history from file. */ { /* Read all of the lines in the file that we haven't already read. */ old_history_lines = history_lines_in_file; using_history (); result = read_history_range (filename, history_lines_in_file, -1); using_history (); history_lines_in_file = where_history (); history_lines_this_session += history_lines_in_file - old_history_lines; } return (result ? EXECUTION_FAILURE : EXECUTION_SUCCESS); } /* Accessors for HIST_ENTRY lists that are called HLIST. */ #define histline(i) (hlist[(i)]->line) #define histdata(i) (hlist[(i)]->data) static void display_history (list) WORD_LIST *list; { register int i; intmax_t limit; HIST_ENTRY **hlist; if (list) { limit = get_numeric_arg (list, 0); if (limit < 0) limit = -limit; } else limit = -1; hlist = history_list (); if (hlist) { for (i = 0; hlist[i]; i++) ; if (0 <= limit && limit < i) i -= limit; else i = 0; while (hlist[i]) { QUIT; printf ("%5d%c %s\n", i + history_base, histdata(i) ? '*' : ' ', histline(i)); i++; } } } /* Delete and free the history list entry at offset I. */ static int delete_histent (i) int i; { HIST_ENTRY *discard; discard = remove_history (i); if (discard) { if (discard->line) free (discard->line); free ((char *) discard); } return 1; } static int delete_last_history () { register int i; HIST_ENTRY **hlist, *histent; hlist = history_list (); if (hlist == NULL) return 0; for (i = 0; hlist[i]; i++) ; i--; /* History_get () takes a parameter that must be offset by history_base. */ histent = history_get (history_base + i); /* Don't free this */ if (histent == NULL) return 0; return (delete_histent (i)); } /* Remove the last entry in the history list and add each argument in LIST to the history. */ static void push_history (list) WORD_LIST *list; { char *s; /* Delete the last history entry if it was a single entry added to the history list (generally the `history -s' itself), or if `history -s' is being used in a compound command and the compound command was added to the history as a single element (command-oriented history). If you don't want history -s to remove the compound command from the history, change #if 0 to #if 1 below. */ #if 0 if (hist_last_line_added && delete_last_history () == 0) #else if ((hist_last_line_added || (current_command_line_count > 0 && current_command_first_line_saved && command_oriented_history)) && delete_last_history () == 0) #endif return; s = string_list (list); /* Call check_add_history with FORCE set to 1 to skip the check against current_command_line_count. If history -s is used in a compound command, the above code will delete the compound command's history entry and this call will add the line to the history as a separate entry. Without FORCE=1, if current_command_line_count were > 1, the line would be appended to the entry before the just-deleted entry. */ check_add_history (s, 1); /* obeys HISTCONTROL, HISTIGNORE */ free (s); } #if defined (BANG_HISTORY) static int expand_and_print_history (list) WORD_LIST *list; { char *s; int r, result; if (hist_last_line_added && delete_last_history () == 0) return EXECUTION_FAILURE; result = EXECUTION_SUCCESS; while (list) { r = history_expand (list->word->word, &s); if (r < 0) { builtin_error ("%s: history expansion failed", list->word->word); result = EXECUTION_FAILURE; } else { fputs (s, stdout); putchar ('\n'); } FREE (s); list = list->next; } fflush (stdout); return result; } #endif /* BANG_HISTORY */ #endif /* HISTORY */
This file is jobs.def, from which is created jobs.c. It implements the builtins "jobs" and "disown" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES jobs.c $BUILTIN jobs $FUNCTION jobs_builtin $DEPENDS_ON JOB_CONTROL $SHORT_DOC jobs [-lnprs] [jobspec ...] or jobs -x command [args] Lists the active jobs. The -l option lists process id's in addition to the normal information; the -p option lists process id's only. If -n is given, only processes that have changed status since the last notification are printed. JOBSPEC restricts output to that job. The -r and -s options restrict output to running and stopped jobs only, respectively. Without options, the status of all active jobs is printed. If -x is given, COMMAND is run after all job specifications that appear in ARGS have been replaced with the process ID of that job's process group leader. $END #include <config.h> #if defined (JOB_CONTROL) #include "../bashtypes.h" #include <signal.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "../bashansi.h" #include "../shell.h" #include "../jobs.h" #include "../execute_cmd.h" #include "bashgetopt.h" #include "common.h" #define JSTATE_ANY 0x0 #define JSTATE_RUNNING 0x1 #define JSTATE_STOPPED 0x2 static int execute_list_with_replacements __P((WORD_LIST *)); /* The `jobs' command. Prints outs a list of active jobs. If the argument `-l' is given, then the process id's are printed also. If the argument `-p' is given, print the process group leader's pid only. If `-n' is given, only processes that have changed status since the last notification are printed. If -x is given, replace all job specs with the pid of the appropriate process group leader and execute the command. The -r and -s options mean to print info about running and stopped jobs only, respectively. */ int jobs_builtin (list) WORD_LIST *list; { int form, execute, state, opt, any_failed, job; sigset_t set, oset; if (job_control == 0 && interactive_shell == 0) return (EXECUTION_SUCCESS); execute = any_failed = 0; form = JLIST_STANDARD; state = JSTATE_ANY; reset_internal_getopt (); while ((opt = internal_getopt (list, "lpnxrs")) != -1) { switch (opt) { case 'l': form = JLIST_LONG; break; case 'p': form = JLIST_PID_ONLY; break; case 'n': form = JLIST_CHANGED_ONLY; break; case 'x': if (form != JLIST_STANDARD) { builtin_error ("no other options allowed with `-x'"); return (EXECUTION_FAILURE); } execute++; break; case 'r': state = JSTATE_RUNNING; break; case 's': state = JSTATE_STOPPED; break; default: builtin_usage (); return (EX_USAGE); } } list = loptend; if (execute) return (execute_list_with_replacements (list)); if (!list) { switch (state) { case JSTATE_ANY: list_all_jobs (form); break; case JSTATE_RUNNING: list_running_jobs (form); break; case JSTATE_STOPPED: list_stopped_jobs (form); break; } return (EXECUTION_SUCCESS); } while (list) { BLOCK_CHILD (set, oset); job = get_job_spec (list); if ((job == NO_JOB) || !jobs || !jobs[job]) { sh_badjob (list->word->word); any_failed++; } else if (job != DUP_JOB) list_one_job ((JOB *)NULL, form, 0, job); UNBLOCK_CHILD (oset); list = list->next; } return (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS); } static int execute_list_with_replacements (list) WORD_LIST *list; { register WORD_LIST *l; int job, result; /* First do the replacement of job specifications with pids. */ for (l = list; l; l = l->next) { if (l->word->word[0] == '%') /* we have a winner */ { job = get_job_spec (l); /* A bad job spec is not really a job spec! Pass it through. */ if (job < 0 || job >= job_slots || !jobs[job]) continue; free (l->word->word); l->word->word = itos (jobs[job]->pgrp); } } /* Next make a new simple command and execute it. */ begin_unwind_frame ("jobs_builtin"); { COMMAND *command = (COMMAND *)NULL; add_unwind_protect (dispose_command, command); command = make_bare_simple_command (); command->value.Simple->words = copy_word_list (list); command->value.Simple->redirects = (REDIRECT *)NULL; command->flags |= CMD_INHIBIT_EXPANSION; command->value.Simple->flags |= CMD_INHIBIT_EXPANSION; result = execute_command (command); } run_unwind_frame ("jobs_builtin"); return (result); } #endif /* JOB_CONTROL */ $BUILTIN disown $FUNCTION disown_builtin $DEPENDS_ON JOB_CONTROL $SHORT_DOC disown [-h] [-ar] [jobspec ...] By default, removes each JOBSPEC argument from the table of active jobs. If the -h option is given, the job is not removed from the table, but is marked so that SIGHUP is not sent to the job if the shell receives a SIGHUP. The -a option, when JOBSPEC is not supplied, means to remove all jobs from the job table; the -r option means to remove only running jobs. $END #if defined (JOB_CONTROL) int disown_builtin (list) WORD_LIST *list; { int opt, job, retval, nohup_only, running_jobs, all_jobs; sigset_t set, oset; intmax_t pid_value; nohup_only = running_jobs = all_jobs = 0; reset_internal_getopt (); while ((opt = internal_getopt (list, "ahr")) != -1) { switch (opt) { case 'a': all_jobs = 1; break; case 'h': nohup_only = 1; break; case 'r': running_jobs = 1; break; default: builtin_usage (); return (EX_USAGE); } } list = loptend; retval = EXECUTION_SUCCESS; /* `disown -a' or `disown -r' */ if (list == 0 && (all_jobs || running_jobs)) { if (nohup_only) nohup_all_jobs (running_jobs); else delete_all_jobs (running_jobs); return (EXECUTION_SUCCESS); } do { BLOCK_CHILD (set, oset); job = (list && legal_number (list->word->word, &pid_value) && pid_value == (pid_t) pid_value) ? get_job_by_pid ((pid_t) pid_value, 0) : get_job_spec (list); if (job == NO_JOB || jobs == 0 || job < 0 || job >= job_slots || jobs[job] == 0) { sh_badjob (list ? list->word->word : "current"); retval = EXECUTION_FAILURE; } else if (nohup_only) nohup_job (job); else delete_job (job, 1); UNBLOCK_CHILD (oset); if (list) list = list->next; } while (list); return (retval); } #endif /* JOB_CONTROL */
This file is kill.def, from which is created kill.c. It implements the builtin "kill" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES kill.c $BUILTIN kill $FUNCTION kill_builtin $DEPENDS_ON JOB_CONTROL $SHORT_DOC kill [-s sigspec | -n signum | -sigspec] [pid | job]... or kill -l [sigspec] Send the processes named by PID (or JOB) the signal SIGSPEC. If SIGSPEC is not present, then SIGTERM is assumed. An argument of `-l' lists the signal names; if arguments follow `-l' they are assumed to be signal numbers for which names should be listed. Kill is a shell builtin for two reasons: it allows job IDs to be used instead of process IDs, and, if you have reached the limit on processes that you can create, you don't have to start a process to kill another one. $END #include <config.h> #include <stdio.h> #include <errno.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "../bashansi.h" #include "../shell.h" #include "../trap.h" #include "../jobs.h" #include "common.h" /* Not all systems declare ERRNO in errno.h... and some systems #define it! */ #if !defined (errno) extern int errno; #endif /* !errno */ #if defined (JOB_CONTROL) extern int posixly_correct; #if !defined (CONTINUE_AFTER_KILL_ERROR) # define CONTINUE_OR_FAIL return (EXECUTION_FAILURE) #else # define CONTINUE_OR_FAIL goto continue_killing #endif /* CONTINUE_AFTER_KILL_ERROR */ /* Here is the kill builtin. We only have it so that people can type kill -KILL %1? No, if you fill up the process table this way you can still kill some. */ int kill_builtin (list) WORD_LIST *list; { int sig, any_succeeded, listing, saw_signal; char *sigspec, *word; pid_t pid; intmax_t pid_value; if (list == 0) { builtin_usage (); return (EXECUTION_FAILURE); } any_succeeded = listing = saw_signal = 0; sig = SIGTERM; sigspec = "TERM"; /* Process options. */ while (list) { word = list->word->word; if (ISOPTION (word, 'l')) { listing++; list = list->next; } else if (ISOPTION (word, 's') || ISOPTION (word, 'n')) { list = list->next; if (list) { sigspec = list->word->word; if (sigspec[0] == '0' && sigspec[1] == '\0') sig = 0; else sig = decode_signal (sigspec); list = list->next; } else { sh_needarg (word); return (EXECUTION_FAILURE); } } else if (ISOPTION (word, '-')) { list = list->next; break; } else if (ISOPTION (word, '?')) { builtin_usage (); return (EXECUTION_SUCCESS); } /* If this is a signal specification then process it. We only process the first one seen; other arguments may signify process groups (e.g, -num == process group num). */ else if ((*word == '-') && !saw_signal) { sigspec = word + 1; sig = decode_signal (sigspec); saw_signal++; list = list->next; } else break; } if (listing) return (display_signal_list (list, 0)); /* OK, we are killing processes. */ if (sig == NO_SIG) { sh_invalidsig (sigspec); return (EXECUTION_FAILURE); } if (list == 0) { builtin_usage (); return (EXECUTION_FAILURE); } while (list) { word = list->word->word; if (*word == '-') word++; /* Use the entire argument in case of minus sign presence. */ if (*word && legal_number (list->word->word, &pid_value) && (pid_value == (pid_t)pid_value)) { pid = (pid_t) pid_value; if ((pid < -1 ? kill_pid (-pid, sig, 1) : kill_pid (pid, sig, 0)) < 0) goto signal_error; else any_succeeded++; } else if (*list->word->word && *list->word->word != '%') { builtin_error ("%s: no such pid", list->word->word); CONTINUE_OR_FAIL; } else if (*word && (interactive || job_control)) /* Posix.2 says you can kill without job control active (4.32.4) */ { /* Must be a job spec. Check it out. */ int job; sigset_t set, oset; BLOCK_CHILD (set, oset); job = get_job_spec (list); if (job < 0 || job >= job_slots || !jobs[job]) { if (job != DUP_JOB) sh_badjob (list->word->word); UNBLOCK_CHILD (oset); CONTINUE_OR_FAIL; } /* Job spec used. Kill the process group. If the job was started without job control, then its pgrp == shell_pgrp, so we have to be careful. We take the pid of the first job in the pipeline in that case. */ pid = IS_JOBCONTROL (job) ? jobs[job]->pgrp : jobs[job]->pipe->pid; UNBLOCK_CHILD (oset); if (kill_pid (pid, sig, 1) < 0) { signal_error: if (errno == EINVAL) sh_invalidsig (sigspec); else builtin_error ("(%ld) - %s", (long)pid, strerror (errno)); CONTINUE_OR_FAIL; } else any_succeeded++; } else { sh_badpid (list->word->word); CONTINUE_OR_FAIL; } continue_killing: list = list->next; } return (any_succeeded ? EXECUTION_SUCCESS : EXECUTION_FAILURE); } #endif /* JOB_CONTROL */
This file is let.def, from which is created let.c. It implements the builtin "let" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $BUILTIN let $FUNCTION let_builtin $PRODUCES let.c $SHORT_DOC let arg [arg ...] Each ARG is an arithmetic expression to be evaluated. Evaluation is done in fixed-width integers with no check for overflow, though division by 0 is trapped and flagged as an error. The following list of operators is grouped into levels of equal-precedence operators. The levels are listed in order of decreasing precedence. id++, id-- variable post-increment, post-decrement ++id, --id variable pre-increment, pre-decrement -, + unary minus, plus !, ~ logical and bitwise negation ** exponentiation *, /, % multiplication, division, remainder +, - addition, subtraction <<, >> left and right bitwise shifts <=, >=, <, > comparison ==, != equality, inequality & bitwise AND ^ bitwise XOR | bitwise OR && logical AND || logical OR expr ? expr : expr conditional expression =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |= assignment Shell variables are allowed as operands. The name of the variable is replaced by its value (coerced to a fixed-width integer) within an expression. The variable need not have its integer attribute turned on to be used in an expression. Operators are evaluated in order of precedence. Sub-expressions in parentheses are evaluated first and may override the precedence rules above. If the last ARG evaluates to 0, let returns 1; 0 is returned otherwise. $END #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "../shell.h" #include "common.h" /* Arithmetic LET function. */ int let_builtin (list) WORD_LIST *list; { intmax_t ret; int expok; /* Skip over leading `--' argument. */ if (list && list->word && ISOPTION (list->word->word, '-')) list = list->next; if (list == 0) { builtin_error ("expression expected"); return (EXECUTION_FAILURE); } for (; list; list = list->next) { ret = evalexp (list->word->word, &expok); if (expok == 0) return (EXECUTION_FAILURE); } return ((ret == 0) ? EXECUTION_FAILURE : EXECUTION_SUCCESS); } #ifdef INCLUDE_UNUSED int exp_builtin (list) WORD_LIST *list; { char *exp; intmax_t ret; int expok; if (list == 0) { builtin_error ("expression expected"); return (EXECUTION_FAILURE); } exp = string_list (list); ret = evalexp (exp, &expok); (void)free (exp); return (((ret == 0) || (expok == 0)) ? EXECUTION_FAILURE : EXECUTION_SUCCESS); } #endif
This file is read.def, from which is created read.c. It implements the builtin "read" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES read.c $BUILTIN read $FUNCTION read_builtin $SHORT_DOC read [-ers] [-u fd] [-t timeout] [-p prompt] [-a array] [-n nchars] [-d delim] [name ...] One line is read from the standard input, or from file descriptor FD if the -u option is supplied, and the first word is assigned to the first NAME, the second word to the second NAME, and so on, with leftover words assigned to the last NAME. Only the characters found in $IFS are recognized as word delimiters. If no NAMEs are supplied, the line read is stored in the REPLY variable. If the -r option is given, this signifies `raw' input, and backslash escaping is disabled. The -d option causes read to continue until the first character of DELIM is read, rather than newline. If the -p option is supplied, the string PROMPT is output without a trailing newline before attempting to read. If -a is supplied, the words read are assigned to sequential indices of ARRAY, starting at zero. If -e is supplied and the shell is interactive, readline is used to obtain the line. If -n is supplied with a non-zero NCHARS argument, read returns after NCHARS characters have been read. The -s option causes input coming from a terminal to not be echoed. The -t option causes read to time out and return failure if a complete line of input is not read within TIMEOUT seconds. If the TMOUT variable is set, its value is the default timeout. The return code is zero, unless end-of-file is encountered, read times out, or an invalid file descriptor is supplied as the argument to -u. $END #include <config.h> #include "bashtypes.h" #include "posixstat.h" #include <stdio.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <signal.h> #include <errno.h> #ifdef __CYGWIN__ # include <fcntl.h> # include <io.h> #endif #include "../shell.h" #include "common.h" #include "bashgetopt.h" #include <shtty.h> #if defined (READLINE) #include "../bashline.h" #include <readline/readline.h> #endif #if !defined(errno) extern int errno; #endif extern int interrupt_immediately; #if defined (READLINE) static char *edit_line __P((char *)); static void set_eol_delim __P((int)); static void reset_eol_delim __P((char *)); #endif static SHELL_VAR *bind_read_variable __P((char *, char *)); static sighandler sigalrm __P((int)); static void reset_alarm __P((void)); static procenv_t alrmbuf; static SigHandler *old_alrm; static unsigned char delim; static sighandler sigalrm (s) int s; { longjmp (alrmbuf, 1); } static void reset_alarm () { set_signal_handler (SIGALRM, old_alrm); alarm (0); } /* Read the value of the shell variables whose names follow. The reading is done from the current input stream, whatever that may be. Successive words of the input line are assigned to the variables mentioned in LIST. The last variable in LIST gets the remainder of the words on the line. If no variables are mentioned in LIST, then the default variable is $REPLY. */ int read_builtin (list) WORD_LIST *list; { register char *varname; int size, i, pass_next, saw_escape, eof, opt, retval, code; int input_is_tty, input_is_pipe, unbuffered_read; int raw, edit, nchars, silent, have_timeout, fd; unsigned int tmout; intmax_t intval; char c; char *input_string, *orig_input_string, *ifs_chars, *prompt, *arrayname; char *e, *t, *t1; struct stat tsb; SHELL_VAR *var; #if defined (ARRAY_VARS) WORD_LIST *alist; #endif #if defined (READLINE) char *rlbuf; int rlind; #endif USE_VAR(size); USE_VAR(i); USE_VAR(pass_next); USE_VAR(saw_escape); USE_VAR(input_is_pipe); /* USE_VAR(raw); */ USE_VAR(edit); USE_VAR(tmout); USE_VAR(nchars); USE_VAR(silent); USE_VAR(ifs_chars); USE_VAR(prompt); USE_VAR(arrayname); #if defined (READLINE) USE_VAR(rlbuf); USE_VAR(rlind); #endif USE_VAR(list); i = 0; /* Index into the string that we are reading. */ raw = edit = 0; /* Not reading raw input by default. */ silent = 0; arrayname = prompt = (char *)NULL; fd = 0; /* file descriptor to read from */ #if defined (READLINE) rlbuf = (char *)0; rlind = 0; #endif tmout = 0; /* no timeout */ nchars = input_is_tty = input_is_pipe = unbuffered_read = have_timeout = 0; delim = '\n'; /* read until newline */ reset_internal_getopt (); while ((opt = internal_getopt (list, "ersa:d:n:p:t:u:")) != -1) { switch (opt) { case 'r': raw = 1; break; case 'p': prompt = list_optarg; break; case 's': silent = 1; break; case 'e': #if defined (READLINE) edit = 1; #endif break; #if defined (ARRAY_VARS) case 'a': arrayname = list_optarg; break; #endif case 't': code = legal_number (list_optarg, &intval); if (code == 0 || intval < 0 || intval != (unsigned int)intval) { builtin_error ("%s: invalid timeout specification", list_optarg); return (EXECUTION_FAILURE); } else { have_timeout = 1; tmout = intval; } break; case 'n': code = legal_number (list_optarg, &intval); if (code == 0 || intval < 0 || intval != (int)intval) { sh_invalidnum (list_optarg); return (EXECUTION_FAILURE); } else nchars = intval; break; case 'u': code = legal_number (list_optarg, &intval); if (code == 0 || intval < 0 || intval != (int)intval) { builtin_error ("%s: invalid file descriptor specification", list_optarg); return (EXECUTION_FAILURE); } else fd = intval; if (sh_validfd (fd) == 0) { builtin_error ("%d: invalid file descriptor: %s", fd, strerror (errno)); return (EXECUTION_FAILURE); } break; case 'd': delim = *list_optarg; break; default: builtin_usage (); return (EX_USAGE); } } list = loptend; /* `read -t 0 var' returns failure immediately. XXX - should it test whether input is available with select/FIONREAD, and fail if those are unavailable? */ if (have_timeout && tmout == 0) return (EXECUTION_FAILURE); /* IF IFS is unset, we use the default of " \t\n". */ ifs_chars = getifs (); if (ifs_chars == 0) /* XXX - shouldn't happen */ ifs_chars = ""; input_string = (char *)xmalloc (size = 112); /* XXX was 128 */ /* $TMOUT, if set, is the default timeout for read. */ if (have_timeout == 0 && (e = get_string_value ("TMOUT"))) { code = legal_number (e, &intval); if (code == 0 || intval < 0 || intval != (unsigned int)intval) tmout = 0; else tmout = intval; } begin_unwind_frame ("read_builtin"); input_is_tty = isatty (fd); if (input_is_tty == 0) #ifndef __CYGWIN__ input_is_pipe = (lseek (0, 0L, SEEK_CUR) < 0) && (errno == ESPIPE); #else input_is_pipe = 1; #endif /* If the -p, -e or -s flags were given, but input is not coming from the terminal, turn them off. */ if ((prompt || edit || silent) && input_is_tty == 0) { prompt = (char *)NULL; edit = silent = 0; } #if defined (READLINE) if (edit) add_unwind_protect (xfree, rlbuf); #endif if (prompt && edit == 0) { fprintf (stderr, "%s", prompt); fflush (stderr); } pass_next = 0; /* Non-zero signifies last char was backslash. */ saw_escape = 0; /* Non-zero signifies that we saw an escape char */ if (tmout > 0) { /* Turn off the timeout if stdin is a regular file (e.g. from input redirection). */ if ((fstat (fd, &tsb) < 0) || S_ISREG (tsb.st_mode)) tmout = 0; } if (tmout > 0) { code = setjmp (alrmbuf); if (code) { run_unwind_frame ("read_builtin"); return (EXECUTION_FAILURE); } old_alrm = set_signal_handler (SIGALRM, sigalrm); add_unwind_protect (reset_alarm, (char *)NULL); alarm (tmout); } /* If we've been asked to read only NCHARS chars, or we're using some character other than newline to terminate the line, do the right thing to readline or the tty. */ if (nchars > 0 || delim != '\n') { #if defined (READLINE) if (edit) { if (nchars > 0) { unwind_protect_int (rl_num_chars_to_read); rl_num_chars_to_read = nchars; } if (delim != '\n') { set_eol_delim (delim); add_unwind_protect (reset_eol_delim, (char *)NULL); } } else #endif if (input_is_tty) { ttsave (); if (silent) ttcbreak (); else ttonechar (); add_unwind_protect ((Function *)ttrestore, (char *)NULL); } } else if (silent) /* turn off echo but leave term in canonical mode */ { ttsave (); ttnoecho (); add_unwind_protect ((Function *)ttrestore, (char *)NULL); } /* This *must* be the top unwind-protect on the stack, so the manipulation of the unwind-protect stack after the realloc() works right. */ add_unwind_protect (xfree, input_string); interrupt_immediately++; unbuffered_read = (nchars > 0) || (delim != '\n') || input_is_pipe; #if defined (__CYGWIN__) && defined (O_TEXT) setmode (0, O_TEXT); #endif for (eof = retval = 0;;) { #if defined (READLINE) if (edit) { if (rlbuf && rlbuf[rlind] == '\0') { xfree (rlbuf); rlbuf = (char *)0; } if (rlbuf == 0) { rlbuf = edit_line (prompt ? prompt : ""); rlind = 0; } if (rlbuf == 0) { eof = 1; break; } c = rlbuf[rlind++]; } else { #endif if (unbuffered_read) retval = zread (fd, &c, 1); else retval = zreadc (fd, &c); if (retval <= 0) { eof = 1; break; } #if defined (READLINE) } #endif if (i + 2 >= size) { input_string = (char *)xrealloc (input_string, size += 128); remove_unwind_protect (); add_unwind_protect (xfree, input_string); } /* If the next character is to be accepted verbatim, a backslash newline pair still disappears from the input. */ if (pass_next) { if (c == '\n') i--; /* back up over the CTLESC */ else input_string[i++] = c; pass_next = 0; continue; } if (c == '\\' && raw == 0) { pass_next++; saw_escape++; input_string[i++] = CTLESC; continue; } if ((unsigned char)c == delim) break; if (c == CTLESC || c == CTLNUL) { saw_escape++; input_string[i++] = CTLESC; } input_string[i++] = c; if (nchars > 0 && i >= nchars) break; } input_string[i] = '\0'; #if 1 if (retval < 0) { builtin_error ("read error: %d: %s", fd, strerror (errno)); return (EXECUTION_FAILURE); } #endif if (tmout > 0) reset_alarm (); if (nchars > 0 || delim != '\n') { #if defined (READLINE) if (edit) { if (nchars > 0) rl_num_chars_to_read = 0; if (delim != '\n') reset_eol_delim ((char *)NULL); } else #endif if (input_is_tty) ttrestore (); } else if (silent) ttrestore (); if (unbuffered_read == 0) zsyncfd (fd); interrupt_immediately--; discard_unwind_frame ("read_builtin"); retval = eof ? EXECUTION_FAILURE : EXECUTION_SUCCESS; #if defined (ARRAY_VARS) /* If -a was given, take the string read, break it into a list of words, an assign them to `arrayname' in turn. */ if (arrayname) { var = find_or_make_array_variable (arrayname, 1); if (var == 0) return EXECUTION_FAILURE; /* readonly or noassign */ array_flush (array_cell (var)); alist = list_string (input_string, ifs_chars, 0); if (alist) { assign_array_var_from_word_list (var, alist); dispose_words (alist); } xfree (input_string); return (retval); } #endif /* ARRAY_VARS */ /* If there are no variables, save the text of the line read to the variable $REPLY. ksh93 strips leading and trailing IFS whitespace, so that `read x ; echo "$x"' and `read ; echo "$REPLY"' behave the same way, but I believe that the difference in behaviors is useful enough to not do it. Without the bash behavior, there is no way to read a line completely without interpretation or modification unless you mess with $IFS (e.g., setting it to the empty string). If you disagree, change the occurrences of `#if 0' to `#if 1' below. */ if (list == 0) { #if 0 orig_input_string = input_string; for (t = input_string; ifs_chars && *ifs_chars && spctabnl(*t) && isifs(*t); t++) ; input_string = t; input_string = strip_trailing_ifs_whitespace (input_string, ifs_chars, saw_escape); #endif if (saw_escape) { t = dequote_string (input_string); var = bind_variable ("REPLY", t); free (t); } else var = bind_variable ("REPLY", input_string); VUNSETATTR (var, att_invisible); free (input_string); return (retval); } /* This code implements the Posix.2 spec for splitting the words read and assigning them to variables. */ orig_input_string = input_string; /* Remove IFS white space at the beginning of the input string. If $IFS is null, no field splitting is performed. */ for (t = input_string; ifs_chars && *ifs_chars && spctabnl(*t) && isifs(*t); t++) ; input_string = t; for (; list->next; list = list->next) { varname = list->word->word; #if defined (ARRAY_VARS) if (legal_identifier (varname) == 0 && valid_array_reference (varname) == 0) #else if (legal_identifier (varname) == 0) #endif { sh_invalidid (varname); xfree (orig_input_string); return (EXECUTION_FAILURE); } /* If there are more variables than words read from the input, the remaining variables are set to the empty string. */ if (*input_string) { /* This call updates INPUT_STRING. */ t = get_word_from_string (&input_string, ifs_chars, &e); if (t) *e = '\0'; /* Don't bother to remove the CTLESC unless we added one somewhere while reading the string. */ if (t && saw_escape) { t1 = dequote_string (t); var = bind_read_variable (varname, t1); xfree (t1); } else var = bind_read_variable (varname, t); } else { t = (char *)0; var = bind_read_variable (varname, ""); } FREE (t); if (var == 0) { xfree (orig_input_string); return (EXECUTION_FAILURE); } stupidly_hack_special_variables (varname); VUNSETATTR (var, att_invisible); } /* Now assign the rest of the line to the last variable argument. */ #if defined (ARRAY_VARS) if (legal_identifier (list->word->word) == 0 && valid_array_reference (list->word->word) == 0) #else if (legal_identifier (list->word->word) == 0) #endif { sh_invalidid (list->word->word); xfree (orig_input_string); return (EXECUTION_FAILURE); } /* This has to be done this way rather than using string_list and list_string because Posix.2 says that the last variable gets the remaining words and their intervening separators. */ input_string = strip_trailing_ifs_whitespace (input_string, ifs_chars, saw_escape); if (saw_escape) { t = dequote_string (input_string); var = bind_read_variable (list->word->word, t); xfree (t); } else var = bind_read_variable (list->word->word, input_string); stupidly_hack_special_variables (list->word->word); if (var) VUNSETATTR (var, att_invisible); xfree (orig_input_string); return (retval); } static SHELL_VAR * bind_read_variable (name, value) char *name, *value; { #if defined (ARRAY_VARS) if (valid_array_reference (name) == 0) return (bind_variable (name, value)); else return (assign_array_element (name, value)); #else /* !ARRAY_VARS */ return bind_variable (name, value); #endif /* !ARRAY_VARS */ } #if defined (READLINE) static rl_completion_func_t *old_attempted_completion_function; static char * edit_line (p) char *p; { char *ret; int len; if (!bash_readline_initialized) initialize_readline (); old_attempted_completion_function = rl_attempted_completion_function; rl_attempted_completion_function = (rl_completion_func_t *)NULL; ret = readline (p); rl_attempted_completion_function = old_attempted_completion_function; if (ret == 0) return ret; len = strlen (ret); ret = (char *)xrealloc (ret, len + 2); ret[len++] = delim; ret[len] = '\0'; return ret; } static int old_delim_ctype; static rl_command_func_t *old_delim_func; static int old_newline_ctype; static rl_command_func_t *old_newline_func; static unsigned char delim_char; static void set_eol_delim (c) int c; { Keymap cmap; if (bash_readline_initialized == 0) initialize_readline (); cmap = rl_get_keymap (); /* Change newline to self-insert */ old_newline_ctype = cmap[RETURN].type; old_newline_func = cmap[RETURN].function; cmap[RETURN].type = ISFUNC; cmap[RETURN].function = rl_insert; /* Bind the delimiter character to accept-line. */ old_delim_ctype = cmap[c].type; old_delim_func = cmap[c].function; cmap[c].type = ISFUNC; cmap[c].function = rl_newline; delim_char = c; } static void reset_eol_delim (cp) char *cp; { Keymap cmap; cmap = rl_get_keymap (); cmap[RETURN].type = old_newline_ctype; cmap[RETURN].function = old_newline_func; cmap[delim_char].type = old_delim_ctype; cmap[delim_char].function = old_delim_func; } #endif
This file is return.def, from which is created return.c. It implements the builtin "return" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES return.c $BUILTIN return $FUNCTION return_builtin $SHORT_DOC return [n] Causes a function to exit with the return value specified by N. If N is omitted, the return status is that of the last command. $END #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "../shell.h" #include "common.h" extern int last_command_exit_value; extern int subshell_environment; extern int return_catch_flag, return_catch_value; /* If we are executing a user-defined function then exit with the value specified as an argument. if no argument is given, then the last exit status is used. */ int return_builtin (list) WORD_LIST *list; { return_catch_value = get_exitstat (list); if (return_catch_flag) longjmp (return_catch, 1); else { builtin_error ("can only `return' from a function or sourced script"); return (EXECUTION_FAILURE); } }
This file is set.def, from which is created set.c. It implements the "set" and "unset" builtins in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES set.c #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include <stdio.h> #include "../bashansi.h" #include "../shell.h" #include "../flags.h" #include "common.h" #include "bashgetopt.h" #if defined (READLINE) # include "../input.h" # include "../bashline.h" # include <readline/readline.h> #endif #if defined (HISTORY) # include "../bashhist.h" #endif extern int posixly_correct, ignoreeof, eof_encountered_limit; #if defined (HISTORY) extern int dont_save_function_defs; #endif #if defined (READLINE) extern int no_line_editing; #endif /* READLINE */ $BUILTIN set $FUNCTION set_builtin $SHORT_DOC set [--abefhkmnptuvxBCHP] [-o option] [arg ...] -a Mark variables which are modified or created for export. -b Notify of job termination immediately. -e Exit immediately if a command exits with a non-zero status. -f Disable file name generation (globbing). -h Remember the location of commands as they are looked up. -k All assignment arguments are placed in the environment for a command, not just those that precede the command name. -m Job control is enabled. -n Read commands but do not execute them. -o option-name Set the variable corresponding to option-name: allexport same as -a braceexpand same as -B #if defined (READLINE) emacs use an emacs-style line editing interface #endif /* READLINE */ errexit same as -e hashall same as -h #if defined (BANG_HISTORY) histexpand same as -H #endif /* BANG_HISTORY */ #if defined (HISTORY) history enable command history #endif ignoreeof the shell will not exit upon reading EOF interactive-comments allow comments to appear in interactive commands keyword same as -k monitor same as -m noclobber same as -C noexec same as -n noglob same as -f nolog currently accepted but ignored notify same as -b nounset same as -u onecmd same as -t physical same as -P posix change the behavior of bash where the default operation differs from the 1003.2 standard to match the standard privileged same as -p verbose same as -v #if defined (READLINE) vi use a vi-style line editing interface #endif /* READLINE */ xtrace same as -x -p Turned on whenever the real and effective user ids do not match. Disables processing of the $ENV file and importing of shell functions. Turning this option off causes the effective uid and gid to be set to the real uid and gid. -t Exit after reading and executing one command. -u Treat unset variables as an error when substituting. -v Print shell input lines as they are read. -x Print commands and their arguments as they are executed. #if defined (BRACE_EXPANSION) -B the shell will perform brace expansion #endif /* BRACE_EXPANSION */ -C If set, disallow existing regular files to be overwritten by redirection of output. #if defined (BANG_HISTORY) -H Enable ! style history substitution. This flag is on by default. #endif /* BANG_HISTORY */ -P If set, do not follow symbolic links when executing commands such as cd which change the current directory. Using + rather than - causes these flags to be turned off. The flags can also be used upon invocation of the shell. The current set of flags may be found in $-. The remaining n ARGs are positional parameters and are assigned, in order, to $1, $2, .. $n. If no ARGs are given, all shell variables are printed. $END typedef int setopt_set_func_t __P((int, char *)); typedef int setopt_get_func_t __P((char *)); static void print_minus_o_option __P((char *, int, int)); static void print_all_shell_variables __P((void)); static int set_ignoreeof __P((int, char *)); static int set_posix_mode __P((int, char *)); #if defined (READLINE) static int set_edit_mode __P((int, char *)); static int get_edit_mode __P((char *)); #endif #if defined (HISTORY) static int bash_set_history __P((int, char *)); #endif static char *on = "on"; static char *off = "off"; /* A struct used to match long options for set -o to the corresponding option letter or internal variable. The functions can be called to dynamically generate values. */ struct { char *name; int letter; int *variable; setopt_set_func_t *set_func; setopt_get_func_t *get_func; } o_options[] = { { "allexport", 'a', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, #if defined (BRACE_EXPANSION) { "braceexpand",'B', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, #endif #if defined (READLINE) { "emacs", '\0', (int *)NULL, set_edit_mode, get_edit_mode }, #endif { "errexit", 'e', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, { "hashall", 'h', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, #if defined (BANG_HISTORY) { "histexpand", 'H', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, #endif /* BANG_HISTORY */ #if defined (HISTORY) { "history", '\0', &remember_on_history, bash_set_history, (setopt_get_func_t *)NULL }, #endif { "ignoreeof", '\0', &ignoreeof, set_ignoreeof, (setopt_get_func_t *)NULL }, { "interactive-comments", '\0', &interactive_comments, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, { "keyword", 'k', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, { "monitor", 'm', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, { "noclobber", 'C', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, { "noexec", 'n', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, { "noglob", 'f', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, #if defined (HISTORY) { "nolog", '\0', &dont_save_function_defs, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, #endif #if defined (JOB_CONTROL) { "notify", 'b', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, #endif /* JOB_CONTROL */ { "nounset", 'u', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, { "onecmd", 't', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, { "physical", 'P', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, { "posix", '\0', &posixly_correct, set_posix_mode, (setopt_get_func_t *)NULL }, { "privileged", 'p', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, { "verbose", 'v', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, #if defined (READLINE) { "vi", '\0', (int *)NULL, set_edit_mode, get_edit_mode }, #endif { "xtrace", 'x', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, {(char *)NULL, 0 , (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, }; #define N_O_OPTIONS (sizeof (o_options) / sizeof (o_options[0])) #define GET_BINARY_O_OPTION_VALUE(i, name) \ ((o_options[i].get_func) ? (*o_options[i].get_func) (name) \ : (*o_options[i].variable)) #define SET_BINARY_O_OPTION_VALUE(i, onoff, name) \ ((o_options[i].set_func) ? (*o_options[i].set_func) (onoff, name) \ : (*o_options[i].variable = (onoff == FLAG_ON))) int minus_o_option_value (name) char *name; { register int i; int *on_or_off; for (i = 0; o_options[i].name; i++) { if (STREQ (name, o_options[i].name)) { if (o_options[i].letter) { on_or_off = find_flag (o_options[i].letter); return ((on_or_off == FLAG_UNKNOWN) ? -1 : *on_or_off); } else return (GET_BINARY_O_OPTION_VALUE (i, name)); } } return (-1); } #define MINUS_O_FORMAT "%-15s\t%s\n" static void print_minus_o_option (name, value, pflag) char *name; int value, pflag; { if (pflag == 0) printf (MINUS_O_FORMAT, name, value ? on : off); else printf ("set %co %s\n", value ? '-' : '+', name); } void list_minus_o_opts (mode, reusable) int mode, reusable; { register int i; int *on_or_off, value; for (i = 0; o_options[i].name; i++) { if (o_options[i].letter) { value = 0; on_or_off = find_flag (o_options[i].letter); if (on_or_off == FLAG_UNKNOWN) on_or_off = &value; if (mode == -1 || mode == *on_or_off) print_minus_o_option (o_options[i].name, *on_or_off, reusable); } else { value = GET_BINARY_O_OPTION_VALUE (i, o_options[i].name); if (mode == -1 || mode == value) print_minus_o_option (o_options[i].name, value, reusable); } } } char ** get_minus_o_opts () { char **ret; int i; ret = strvec_create (N_O_OPTIONS + 1); for (i = 0; o_options[i].name; i++) ret[i] = o_options[i].name; ret[i] = (char *)NULL; return ret; } static int set_ignoreeof (on_or_off, option_name) int on_or_off; char *option_name; { ignoreeof = on_or_off == FLAG_ON; unbind_variable ("ignoreeof"); if (ignoreeof) bind_variable ("IGNOREEOF", "10"); else unbind_variable ("IGNOREEOF"); sv_ignoreeof ("IGNOREEOF"); return 0; } static int set_posix_mode (on_or_off, option_name) int on_or_off; char *option_name; { posixly_correct = on_or_off == FLAG_ON; if (posixly_correct == 0) unbind_variable ("POSIXLY_CORRECT"); else bind_variable ("POSIXLY_CORRECT", "y"); sv_strict_posix ("POSIXLY_CORRECT"); return (0); } #if defined (READLINE) /* Magic. This code `knows' how readline handles rl_editing_mode. */ static int set_edit_mode (on_or_off, option_name) int on_or_off; char *option_name; { int isemacs; if (on_or_off == FLAG_ON) { rl_variable_bind ("editing-mode", option_name); if (interactive) with_input_from_stdin (); no_line_editing = 0; } else { isemacs = rl_editing_mode == 1; if ((isemacs && *option_name == 'e') || (!isemacs && *option_name == 'v')) { if (interactive) with_input_from_stream (stdin, "stdin"); no_line_editing = 1; } } return 1-no_line_editing; } static int get_edit_mode (name) char *name; { return (*name == 'e' ? no_line_editing == 0 && rl_editing_mode == 1 : no_line_editing == 0 && rl_editing_mode == 0); } #endif /* READLINE */ #if defined (HISTORY) static int bash_set_history (on_or_off, option_name) int on_or_off; char *option_name; { if (on_or_off == FLAG_ON) { bash_history_enable (); if (history_lines_this_session == 0) load_history (); } else bash_history_disable (); return (1 - remember_on_history); } #endif int set_minus_o_option (on_or_off, option_name) int on_or_off; char *option_name; { register int i; for (i = 0; o_options[i].name; i++) { if (STREQ (option_name, o_options[i].name)) { if (o_options[i].letter == 0) { SET_BINARY_O_OPTION_VALUE (i, on_or_off, option_name); return (EXECUTION_SUCCESS); } else { if (change_flag (o_options[i].letter, on_or_off) == FLAG_ERROR) { sh_invalidoptname (option_name); return (EXECUTION_FAILURE); } else return (EXECUTION_SUCCESS); } } } sh_invalidoptname (option_name); return (EXECUTION_FAILURE); } static void print_all_shell_variables () { SHELL_VAR **vars; vars = all_shell_variables (); if (vars) { print_var_list (vars); free (vars); } /* POSIX.2 does not allow function names and definitions to be output when `set' is invoked without options (PASC Interp #202). */ if (posixly_correct == 0) { vars = all_shell_functions (); if (vars) { print_func_list (vars); free (vars); } } } void set_shellopts () { char *value; char tflag[N_O_OPTIONS]; int vsize, i, vptr, *ip, exported; SHELL_VAR *v; for (vsize = i = 0; o_options[i].name; i++) { tflag[i] = 0; if (o_options[i].letter) { ip = find_flag (o_options[i].letter); if (ip && *ip) { vsize += strlen (o_options[i].name) + 1; tflag[i] = 1; } } else if (GET_BINARY_O_OPTION_VALUE (i, o_options[i].name)) { vsize += strlen (o_options[i].name) + 1; tflag[i] = 1; } } value = (char *)xmalloc (vsize + 1); for (i = vptr = 0; o_options[i].name; i++) { if (tflag[i]) { strcpy (value + vptr, o_options[i].name); vptr += strlen (o_options[i].name); value[vptr++] = ':'; } } if (vptr) vptr--; /* cut off trailing colon */ value[vptr] = '\0'; v = find_variable ("SHELLOPTS"); /* Turn off the read-only attribute so we can bind the new value, and note whether or not the variable was exported. */ if (v) { VUNSETATTR (v, att_readonly); exported = exported_p (v); } else exported = 0; v = bind_variable ("SHELLOPTS", value); /* Turn the read-only attribute back on, and turn off the export attribute if it was set implicitly by mark_modified_vars and SHELLOPTS was not exported before we bound the new value. */ VSETATTR (v, att_readonly); if (mark_modified_vars && exported == 0 && exported_p (v)) VUNSETATTR (v, att_exported); free (value); } void parse_shellopts (value) char *value; { char *vname; int vptr; vptr = 0; while (vname = extract_colon_unit (value, &vptr)) { set_minus_o_option (FLAG_ON, vname); free (vname); } } void initialize_shell_options (no_shellopts) int no_shellopts; { char *temp; SHELL_VAR *var; if (no_shellopts == 0) { var = find_variable ("SHELLOPTS"); /* set up any shell options we may have inherited. */ if (var && imported_p (var)) { temp = (array_p (var)) ? (char *)NULL : savestring (value_cell (var)); if (temp) { parse_shellopts (temp); free (temp); } } } /* Set up the $SHELLOPTS variable. */ set_shellopts (); } /* Reset the values of the -o options that are not also shell flags. This is called from execute_cmd.c:initialize_subshell() when setting up a subshell to run an executable shell script without a leading `#!'. */ void reset_shell_options () { #if defined (HISTORY) remember_on_history = 1; #endif ignoreeof = 0; } /* Set some flags from the word values in the input list. If LIST is empty, then print out the values of the variables instead. If LIST contains non-flags, then set $1 - $9 to the successive words of LIST. */ int set_builtin (list) WORD_LIST *list; { int on_or_off, flag_name, force_assignment, opts_changed; WORD_LIST *l; register char *arg; char s[3]; if (list == 0) { print_all_shell_variables (); return (EXECUTION_SUCCESS); } /* Check validity of flag arguments. */ reset_internal_getopt (); while ((flag_name = internal_getopt (list, optflags)) != -1) { switch (flag_name) { case '?': builtin_usage (); return (list_optopt == '?' ? EXECUTION_SUCCESS : EX_USAGE); default: break; } } /* Do the set command. While the list consists of words starting with '-' or '+' treat them as flags, otherwise, start assigning them to $1 ... $n. */ for (force_assignment = opts_changed = 0; list; ) { arg = list->word->word; /* If the argument is `--' or `-' then signal the end of the list and remember the remaining arguments. */ if (arg[0] == '-' && (!arg[1] || (arg[1] == '-' && !arg[2]))) { list = list->next; /* `set --' unsets the positional parameters. */ if (arg[1] == '-') force_assignment = 1; /* Until told differently, the old shell behaviour of `set - [arg ...]' being equivalent to `set +xv [arg ...]' stands. Posix.2 says the behaviour is marked as obsolescent. */ else { change_flag ('x', '+'); change_flag ('v', '+'); opts_changed = 1; } break; } if ((on_or_off = *arg) && (on_or_off == '-' || on_or_off == '+')) { while (flag_name = *++arg) { if (flag_name == '?') { builtin_usage (); return (EXECUTION_SUCCESS); } else if (flag_name == 'o') /* -+o option-name */ { char *option_name; WORD_LIST *opt; opt = list->next; if (opt == 0) { list_minus_o_opts (-1, (on_or_off == '+')); continue; } option_name = opt->word->word; if (option_name == 0 || *option_name == '\0' || *option_name == '-' || *option_name == '+') { list_minus_o_opts (-1, (on_or_off == '+')); continue; } list = list->next; /* Skip over option name. */ opts_changed = 1; if (set_minus_o_option (on_or_off, option_name) != EXECUTION_SUCCESS) { set_shellopts (); return (EXECUTION_FAILURE); } } else if (change_flag (flag_name, on_or_off) == FLAG_ERROR) { s[0] = on_or_off; s[1] = flag_name; s[2] = '\0'; sh_invalidopt (s); builtin_usage (); set_shellopts (); return (EXECUTION_FAILURE); } opts_changed = 1; } } else { break; } list = list->next; } /* Assigning $1 ... $n */ if (list || force_assignment) remember_args (list, 1); /* Set up new value of $SHELLOPTS */ if (opts_changed) set_shellopts (); return (EXECUTION_SUCCESS); } $BUILTIN unset $FUNCTION unset_builtin $SHORT_DOC unset [-f] [-v] [name ...] For each NAME, remove the corresponding variable or function. Given the `-v', unset will only act on variables. Given the `-f' flag, unset will only act on functions. With neither flag, unset first tries to unset a variable, and if that fails, then tries to unset a function. Some variables cannot be unset; also see readonly. $END #define NEXT_VARIABLE() any_failed++; list = list->next; continue; int unset_builtin (list) WORD_LIST *list; { int unset_function, unset_variable, unset_array, opt, any_failed; char *name; unset_function = unset_variable = unset_array = any_failed = 0; reset_internal_getopt (); while ((opt = internal_getopt (list, "fv")) != -1) { switch (opt) { case 'f': unset_function = 1; break; case 'v': unset_variable = 1; break; default: builtin_usage (); return (EX_USAGE); } } list = loptend; if (unset_function && unset_variable) { builtin_error ("cannot simultaneously unset a function and a variable"); return (EXECUTION_FAILURE); } while (list) { SHELL_VAR *var; int tem; #if defined (ARRAY_VARS) char *t; #endif name = list->word->word; #if defined (ARRAY_VARS) unset_array = 0; if (!unset_function && valid_array_reference (name)) { t = strchr (name, '['); *t++ = '\0'; unset_array++; } #endif /* Bash allows functions with names which are not valid identifiers to be created when not in posix mode, so check only when in posix mode when unsetting a function. */ if (((unset_function && posixly_correct) || !unset_function) && legal_identifier (name) == 0) { sh_invalidid (name); NEXT_VARIABLE (); } var = unset_function ? find_function (name) : find_variable (name); if (var && !unset_function && non_unsettable_p (var)) { builtin_error ("%s: cannot unset", name); NEXT_VARIABLE (); } /* Posix.2 says that unsetting readonly variables is an error. */ if (var && readonly_p (var)) { builtin_error ("%s: cannot unset: readonly %s", name, unset_function ? "function" : "variable"); NEXT_VARIABLE (); } /* Unless the -f option is supplied, the name refers to a variable. */ #if defined (ARRAY_VARS) if (var && unset_array) { if (array_p (var) == 0) { builtin_error ("%s: not an array variable", name); NEXT_VARIABLE (); } else { tem = unbind_array_element (var, t); if (tem == -1) any_failed++; } } else #endif /* ARRAY_VARS */ tem = unset_function ? unbind_func (name) : unbind_variable (name); /* This is what Posix.2 draft 11+ says. ``If neither -f nor -v is specified, the name refers to a variable; if a variable by that name does not exist, a function by that name, if any, shall be unset.'' */ if (tem == -1 && !unset_function && !unset_variable) tem = unbind_func (name); /* SUSv3, POSIX.1-2001 say: ``Unsetting a variable or function that was not previously set shall not be considered an error.'' */ if (unset_function == 0) stupidly_hack_special_variables (name); list = list->next; } return (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS); }
This file is setattr.def, from which is created setattr.c. It implements the builtins "export" and "readonly", in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES setattr.c #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include <stdio.h> #include "../bashansi.h" #include "../shell.h" #include "common.h" #include "bashgetopt.h" extern int posixly_correct; extern int array_needs_making; extern char *this_command_name; extern sh_builtin_func_t *this_shell_builtin; #ifdef ARRAY_VARS extern int declare_builtin __P((WORD_LIST *)); #endif #define READONLY_OR_EXPORT \ (this_shell_builtin == readonly_builtin || this_shell_builtin == export_builtin) $BUILTIN export $FUNCTION export_builtin $SHORT_DOC export [-nf] [name[=value] ...] or export -p NAMEs are marked for automatic export to the environment of subsequently executed commands. If the -f option is given, the NAMEs refer to functions. If no NAMEs are given, or if `-p' is given, a list of all names that are exported in this shell is printed. An argument of `-n' says to remove the export property from subsequent NAMEs. An argument of `--' disables further option processing. $END /* For each variable name in LIST, make that variable appear in the environment passed to simple commands. If there is no LIST, then print all such variables. An argument of `-n' says to remove the exported attribute from variables named in LIST. An argument of -f indicates that the names present in LIST refer to functions. */ int export_builtin (list) register WORD_LIST *list; { return (set_or_show_attributes (list, att_exported, 0)); } $BUILTIN readonly $FUNCTION readonly_builtin $SHORT_DOC readonly [-anf] [name[=value] ...] or readonly -p The given NAMEs are marked readonly and the values of these NAMEs may not be changed by subsequent assignment. If the -f option is given, then functions corresponding to the NAMEs are so marked. If no arguments are given, or if `-p' is given, a list of all readonly names is printed. An argument of `-n' says to remove the readonly property from subsequent NAMEs. The `-a' option means to treat each NAME as an array variable. An argument of `--' disables further option processing. $END /* For each variable name in LIST, make that variable readonly. Given an empty LIST, print out all existing readonly variables. */ int readonly_builtin (list) register WORD_LIST *list; { return (set_or_show_attributes (list, att_readonly, 0)); } #if defined (ARRAY_VARS) # define ATTROPTS "afnp" #else # define ATTROPTS "fnp" #endif /* For each variable name in LIST, make that variable have the specified ATTRIBUTE. An arg of `-n' says to remove the attribute from the the remaining names in LIST. */ int set_or_show_attributes (list, attribute, nodefs) register WORD_LIST *list; int attribute, nodefs; { register SHELL_VAR *var; int assign, undo, functions_only, arrays_only, any_failed, assign_error, opt; char *name; #if defined (ARRAY_VARS) WORD_LIST *nlist, *tlist; WORD_DESC *w; #endif undo = functions_only = arrays_only = any_failed = assign_error = 0; /* Read arguments from the front of the list. */ reset_internal_getopt (); while ((opt = internal_getopt (list, ATTROPTS)) != -1) { switch (opt) { case 'n': undo = 1; break; case 'f': functions_only = 1; break; #if defined (ARRAY_VARS) case 'a': arrays_only = 1; break; #endif case 'p': break; default: builtin_usage (); return (EX_USAGE); } } list = loptend; if (list) { if (attribute & att_exported) array_needs_making = 1; /* Cannot undo readonly status, silently disallowed. */ if (undo && (attribute & att_readonly)) attribute &= ~att_readonly; while (list) { name = list->word->word; if (functions_only) /* xxx -f name */ { var = find_function (name); if (var == 0) { builtin_error ("%s: not a function", name); any_failed++; } else SETVARATTR (var, attribute, undo); list = list->next; continue; } /* xxx [-np] name[=value] */ assign = assignment (name); if (assign) name[assign] = '\0'; if (legal_identifier (name) == 0) { sh_invalidid (name); if (assign) assign_error++; else any_failed++; list = list->next; continue; } if (assign) /* xxx [-np] name=value */ { name[assign] = '='; #if defined (ARRAY_VARS) /* Let's try something here. Turn readonly -a xxx=yyy into declare -ra xxx=yyy and see what that gets us. */ if (arrays_only) { tlist = list->next; list->next = (WORD_LIST *)NULL; w = make_word ("-ra"); nlist = make_word_list (w, list); opt = declare_builtin (nlist); if (opt != EXECUTION_SUCCESS) assign_error++; list->next = tlist; dispose_word (w); free (nlist); } else #endif /* This word has already been expanded once with command and parameter expansion. Call do_assignment_no_expand (), which does not do command or parameter substitution. If the assignment is not performed correctly, flag an error. */ if (do_assignment_no_expand (name) == 0) assign_error++; name[assign] = '\0'; } set_var_attribute (name, attribute, undo); list = list->next; } } else { SHELL_VAR **variable_list; register int i; if ((attribute & att_function) || functions_only) { variable_list = all_shell_functions (); if (attribute != att_function) attribute &= ~att_function; /* so declare -xf works, for example */ } else variable_list = all_shell_variables (); #if defined (ARRAY_VARS) if (attribute & att_array) { arrays_only++; if (attribute != att_array) attribute &= ~att_array; } #endif if (variable_list) { for (i = 0; var = variable_list[i]; i++) { #if defined (ARRAY_VARS) if (arrays_only && array_p (var) == 0) continue; #endif if ((var->attributes & attribute)) show_var_attributes (var, READONLY_OR_EXPORT, nodefs); } free (variable_list); } } return (assign_error ? EX_BADASSIGN : ((any_failed == 0) ? EXECUTION_SUCCESS : EXECUTION_FAILURE)); } /* Show the attributes for shell variable VAR. If NODEFS is non-zero, don't show function definitions along with the name. If PATTR is non-zero, it indicates we're being called from `export' or `readonly'. In POSIX mode, this prints the name of the calling builtin (`export' or `readonly') instead of `declare', and doesn't print function defs when called by `export' or `readonly'. */ int show_var_attributes (var, pattr, nodefs) SHELL_VAR *var; int pattr, nodefs; { char flags[8], *x; int i; i = 0; /* pattr == 0 means we are called from `declare'. */ if (pattr == 0 || posixly_correct == 0) { #if defined (ARRAY_VARS) if (array_p (var)) flags[i++] = 'a'; #endif if (function_p (var)) flags[i++] = 'f'; if (integer_p (var)) flags[i++] = 'i'; if (readonly_p (var)) flags[i++] = 'r'; if (trace_p (var)) flags[i++] = 't'; if (exported_p (var)) flags[i++] = 'x'; } else { #if defined (ARRAY_VARS) if (array_p (var)) flags[i++] = 'a'; #endif if (function_p (var)) flags[i++] = 'f'; } flags[i] = '\0'; /* If we're printing functions with definitions, print the function def first, then the attributes, instead of printing output that can't be reused as input to recreate the current state. */ if (function_p (var) && nodefs == 0 && (pattr == 0 || posixly_correct == 0)) { printf ("%s\n", named_function_string (var->name, function_cell (var), 1)); nodefs++; if (pattr == 0 && i == 1 && flags[0] == 'f') return 0; /* don't print `declare -f name' */ } if (pattr == 0 || posixly_correct == 0) printf ("declare -%s ", i ? flags : "-"); else if (i) printf ("%s -%s ", this_command_name, flags); else printf ("%s ", this_command_name); #if defined (ARRAY_VARS) if (array_p (var)) print_array_assignment (var, 1); else #endif /* force `readonly' and `export' to not print out function definitions when in POSIX mode. */ if (nodefs || (function_p (var) && pattr != 0 && posixly_correct)) printf ("%s\n", var->name); else if (function_p (var)) printf ("%s\n", named_function_string (var->name, function_cell (var), 1)); else if (invisible_p (var)) printf ("%s\n", var->name); else { x = sh_double_quote (var_isset (var) ? value_cell (var) : ""); printf ("%s=%s\n", var->name, x); free (x); } return (0); } int show_name_attributes (name, nodefs) char *name; int nodefs; { SHELL_VAR *var; var = find_variable_internal (name, 1); if (var && invisible_p (var) == 0) { show_var_attributes (var, READONLY_OR_EXPORT, nodefs); return (0); } else return (1); } void set_var_attribute (name, attribute, undo) char *name; int attribute, undo; { SHELL_VAR *var, *tv; char *tvalue; if (undo) var = find_variable (name); else { tv = find_tempenv_variable (name); /* XXX -- need to handle case where tv is a temp variable in a function-scope context, since function_env has been merged into the local variables table. */ if (tv && tempvar_p (tv)) { tvalue = var_isset (tv) ? savestring (value_cell (tv)) : savestring (""); var = bind_variable (tv->name, tvalue); var->attributes |= tv->attributes & ~att_tempvar; VSETATTR (tv, att_propagate); if (var->context != 0) VSETATTR (var, att_propagate); SETVARATTR (tv, attribute, undo); /* XXX */ free (tvalue); } else { var = find_variable_internal (name, 0); if (var == 0) { var = bind_variable (name, (char *)NULL); VSETATTR (var, att_invisible); } else if (var->context != 0) VSETATTR (var, att_propagate); } } if (var) SETVARATTR (var, attribute, undo); if (var && (exported_p (var) || (attribute & att_exported))) array_needs_making++; /* XXX */ }
This file is shift.def, from which is created shift.c. It implements the builtin "shift" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES shift.c #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "../bashansi.h" #include "../shell.h" #include "common.h" $BUILTIN shift $FUNCTION shift_builtin $SHORT_DOC shift [n] The positional parameters from $N+1 ... are renamed to $1 ... If N is not given, it is assumed to be 1. $END int print_shift_error; /* Shift the arguments ``left''. Shift DOLLAR_VARS down then take one off of REST_OF_ARGS and place it into DOLLAR_VARS[9]. If LIST has anything in it, it is a number which says where to start the shifting. Return > 0 if `times' > $#, otherwise 0. */ int shift_builtin (list) WORD_LIST *list; { intmax_t times; register int count; WORD_LIST *temp; times = get_numeric_arg (list, 0); if (times == 0) return (EXECUTION_SUCCESS); else if (times < 0) { sh_erange (list->word->word, "shift count"); return (EXECUTION_FAILURE); } else if (times > number_of_args ()) { if (print_shift_error) sh_erange (list->word->word, "shift count"); return (EXECUTION_FAILURE); } while (times-- > 0) { if (dollar_vars[1]) free (dollar_vars[1]); for (count = 1; count < 9; count++) dollar_vars[count] = dollar_vars[count + 1]; if (rest_of_args) { temp = rest_of_args; dollar_vars[9] = savestring (temp->word->word); rest_of_args = rest_of_args->next; temp->next = (WORD_LIST *)NULL; dispose_words (temp); } else dollar_vars[9] = (char *)NULL; } return (EXECUTION_SUCCESS); }
This file is source.def, from which is created source.c. It implements the builtins "." and "source" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES source.c $BUILTIN source $FUNCTION source_builtin $SHORT_DOC source filename Read and execute commands from FILENAME and return. The pathnames in $PATH are used to find the directory containing FILENAME. $END $BUILTIN . $DOCNAME dot $FUNCTION source_builtin $SHORT_DOC . filename Read and execute commands from FILENAME and return. The pathnames in $PATH are used to find the directory containing FILENAME. $END /* source.c - Implements the `.' and `source' builtins. */ #include <config.h> #include "../bashtypes.h" #include "posixstat.h" #include "filecntl.h" #ifndef _MINIX # include <sys/file.h> #endif #include <errno.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "../bashansi.h" #include "../shell.h" #include "../findcmd.h" #include "common.h" #include "bashgetopt.h" #if !defined (errno) extern int errno; #endif /* !errno */ #if defined (RESTRICTED_SHELL) extern int restricted; #endif /* If non-zero, `.' uses $PATH to look up the script to be sourced. */ int source_uses_path = 1; /* If non-zero, `.' looks in the current directory if the filename argument is not found in the $PATH. */ int source_searches_cwd = 1; /* If this . script is supplied arguments, we save the dollar vars and replace them with the script arguments for the duration of the script's execution. If the script does not change the dollar vars, we restore what we saved. If the dollar vars are changed in the script, and we are not executing a shell function, we leave the new values alone and free the saved values. */ static void maybe_pop_dollar_vars () { if (variable_context == 0 && (dollar_vars_changed () & ARGS_SETBLTIN)) dispose_saved_dollar_vars (); else pop_dollar_vars (); set_dollar_vars_unchanged (); } /* Read and execute commands from the file passed as argument. Guess what. This cannot be done in a subshell, since things like variable assignments take place in there. So, I open the file, place it into a large string, close the file, and then execute the string. */ int source_builtin (list) WORD_LIST *list; { int result; char *filename; if (no_options (list)) return (EX_USAGE); list = loptend; if (list == 0) { builtin_error ("filename argument required"); builtin_usage (); return (EX_USAGE); } #if defined (RESTRICTED_SHELL) if (restricted && strchr (list->word->word, '/')) { sh_restricted (list->word->word); return (EXECUTION_FAILURE); } #endif filename = (char *)NULL; if (source_uses_path) filename = find_path_file (list->word->word); if (filename == 0) { if (source_searches_cwd == 0) { builtin_error ("%s: file not found", list->word->word); return (EXECUTION_FAILURE); } else filename = savestring (list->word->word); } begin_unwind_frame ("source"); add_unwind_protect ((Function *)xfree, filename); if (list->next) { push_dollar_vars (); add_unwind_protect ((Function *)maybe_pop_dollar_vars, (char *)NULL); remember_args (list->next, 1); } set_dollar_vars_unchanged (); result = source_file (filename); run_unwind_frame ("source"); return (result); }
This file is suspend.def, from which is created suspend.c. It implements the builtin "suspend" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES suspend.c $BUILTIN suspend $DEPENDS_ON JOB_CONTROL $FUNCTION suspend_builtin $SHORT_DOC suspend [-f] Suspend the execution of this shell until it receives a SIGCONT signal. The `-f' if specified says not to complain about this being a login shell if it is; just suspend anyway. $END #include <config.h> #if defined (JOB_CONTROL) #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "../bashtypes.h" #include <signal.h> #include "../shell.h" #include "../jobs.h" #include "common.h" #include "bashgetopt.h" static SigHandler *old_cont; #if 0 static SigHandler *old_stop; #endif /* Continue handler. */ sighandler suspend_continue (sig) int sig; { set_signal_handler (SIGCONT, old_cont); #if 0 set_signal_handler (SIGSTOP, old_stop); #endif SIGRETURN (0); } /* Suspending the shell. If -f is the arg, then do the suspend no matter what. Otherwise, complain if a login shell. */ int suspend_builtin (list) WORD_LIST *list; { int opt, force; reset_internal_getopt (); force = 0; while ((opt = internal_getopt (list, "f")) != -1) switch (opt) { case 'f': force++; break; default: builtin_usage (); return (EX_USAGE); } list = loptend; if (job_control == 0) { sh_nojobs ("cannot suspend"); return (EXECUTION_FAILURE); } if (force == 0) { no_args (list); if (login_shell) { builtin_error ("cannot suspend a login shell"); return (EXECUTION_FAILURE); } } old_cont = (SigHandler *)set_signal_handler (SIGCONT, suspend_continue); #if 0 old_stop = (SigHandler *)set_signal_handler (SIGSTOP, SIG_DFL); #endif killpg (shell_pgrp, SIGSTOP); return (EXECUTION_SUCCESS); } #endif /* JOB_CONTROL */
This file is test.def, from which is created test.c. It implements the builtin "test" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES test.c $BUILTIN test $FUNCTION test_builtin $SHORT_DOC test [expr] Exits with a status of 0 (true) or 1 (false) depending on the evaluation of EXPR. Expressions may be unary or binary. Unary expressions are often used to examine the status of a file. There are string operators as well, and numeric comparison operators. File operators: -a FILE True if file exists. -b FILE True if file is block special. -c FILE True if file is character special. -d FILE True if file is a directory. -e FILE True if file exists. -f FILE True if file exists and is a regular file. -g FILE True if file is set-group-id. -h FILE True if file is a symbolic link. -L FILE True if file is a symbolic link. -k FILE True if file has its `sticky' bit set. -p FILE True if file is a named pipe. -r FILE True if file is readable by you. -s FILE True if file exists and is not empty. -S FILE True if file is a socket. -t FD True if FD is opened on a terminal. -u FILE True if the file is set-user-id. -w FILE True if the file is writable by you. -x FILE True if the file is executable by you. -O FILE True if the file is effectively owned by you. -G FILE True if the file is effectively owned by your group. -N FILE True if the file has been modified since it was last read. FILE1 -nt FILE2 True if file1 is newer than file2 (according to modification date). FILE1 -ot FILE2 True if file1 is older than file2. FILE1 -ef FILE2 True if file1 is a hard link to file2. String operators: -z STRING True if string is empty. -n STRING STRING True if string is not empty. STRING1 = STRING2 True if the strings are equal. STRING1 != STRING2 True if the strings are not equal. STRING1 < STRING2 True if STRING1 sorts before STRING2 lexicographically. STRING1 > STRING2 True if STRING1 sorts after STRING2 lexicographically. Other operators: -o OPTION True if the shell option OPTION is enabled. ! EXPR True if expr is false. EXPR1 -a EXPR2 True if both expr1 AND expr2 are true. EXPR1 -o EXPR2 True if either expr1 OR expr2 is true. arg1 OP arg2 Arithmetic tests. OP is one of -eq, -ne, -lt, -le, -gt, or -ge. Arithmetic binary operators return true if ARG1 is equal, not-equal, less-than, less-than-or-equal, greater-than, or greater-than-or-equal than ARG2. $END $BUILTIN [ $DOCNAME test_bracket $FUNCTION test_builtin $SHORT_DOC [ arg... ] This is a synonym for the "test" builtin, but the last argument must be a literal `]', to match the opening `['. $END #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "../bashansi.h" #include "../shell.h" #include "../test.h" #include "common.h" extern char *this_command_name; /* TEST/[ builtin. */ int test_builtin (list) WORD_LIST *list; { char **argv; int argc, result; /* We let Matthew Bradburn and Kevin Braunsdorf's code do the actual test command. So turn the list of args into an array of strings, since that is what their code wants. */ if (list == 0) { if (this_command_name[0] == '[' && !this_command_name[1]) { builtin_error ("missing `]'"); return (EX_BADUSAGE); } return (EXECUTION_FAILURE); } argv = make_builtin_argv (list, &argc); result = test_command (argc, argv); free ((char *)argv); return (result); }
This file is times.def, from which is created times.c. It implements the builtin "times" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES times.c $BUILTIN times $FUNCTION times_builtin $SHORT_DOC times Print the accumulated user and system times for processes run from the shell. $END #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include <stdio.h> #include "../bashtypes.h" #include "../shell.h" #include <posixtime.h> #if defined (HAVE_SYS_TIMES_H) # include <sys/times.h> #endif /* HAVE_SYS_TIMES_H */ #if defined (HAVE_SYS_RESOURCE_H) && !defined (RLIMTYPE) # include <sys/resource.h> #endif #include "common.h" /* Print the totals for system and user time used. */ int times_builtin (list) WORD_LIST *list; { #if defined (HAVE_GETRUSAGE) && defined (HAVE_TIMEVAL) && defined (RUSAGE_SELF) struct rusage self, kids; USE_VAR(list); if (no_options (list)) return (EX_USAGE); getrusage (RUSAGE_SELF, &self); getrusage (RUSAGE_CHILDREN, &kids); /* terminated child processes */ print_timeval (stdout, &self.ru_utime); putchar (' '); print_timeval (stdout, &self.ru_stime); putchar ('\n'); print_timeval (stdout, &kids.ru_utime); putchar (' '); print_timeval (stdout, &kids.ru_stime); putchar ('\n'); #else # if defined (HAVE_TIMES) /* This uses the POSIX.1/XPG5 times(2) interface, which fills in a `struct tms' with values of type clock_t. */ struct tms t; USE_VAR(list); if (no_options (list)) return (EX_USAGE); times (&t); print_clock_t (stdout, t.tms_utime); putchar (' '); print_clock_t (stdout, t.tms_stime); putchar ('\n'); print_clock_t (stdout, t.tms_cutime); putchar (' '); print_clock_t (stdout, t.tms_cstime); putchar ('\n'); # else /* !HAVE_TIMES */ USE_VAR(list); if (no_options (list)) return (EX_USAGE); printf ("0.00 0.00\n0.00 0.00\n"); # endif /* HAVE_TIMES */ #endif /* !HAVE_TIMES */ return (EXECUTION_SUCCESS); }
This file is trap.def, from which is created trap.c. It implements the builtin "trap" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES trap.c $BUILTIN trap $FUNCTION trap_builtin $SHORT_DOC trap [arg] [signal_spec ...] or trap -l The command ARG is to be read and executed when the shell receives signal(s) SIGNAL_SPEC. If ARG is absent all specified signals are reset to their original values. If ARG is the null string each SIGNAL_SPEC is ignored by the shell and by the commands it invokes. If a SIGNAL_SPEC is EXIT (0) the command ARG is executed on exit from the shell. If a SIGNAL_SPEC is DEBUG, ARG is executed after every command. If ARG is `-p' then the trap commands associated with each SIGNAL_SPEC are displayed. If no arguments are supplied or if only `-p' is given, trap prints the list of commands associated with each signal number. Each SIGNAL_SPEC is either a signal name in <signal.h> or a signal number. `trap -l' prints a list of signal names and their corresponding numbers. Note that a signal can be sent to the shell with "kill -signal $$". $END #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "../bashtypes.h" #include <signal.h> #include <stdio.h> #include "../bashansi.h" #include "../shell.h" #include "../trap.h" #include "common.h" #include "bashgetopt.h" static void showtrap __P((int)); static int display_traps __P((WORD_LIST *)); /* The trap command: trap <arg> <signal ...> trap <signal ...> trap -l trap -p [sigspec ...] trap [--] Set things up so that ARG is executed when SIGNAL(s) N is recieved. If ARG is the empty string, then ignore the SIGNAL(s). If there is no ARG, then set the trap for SIGNAL(s) to its original value. Just plain "trap" means to print out the list of commands associated with each signal number. Single arg of "-l" means list the signal names. */ /* Possible operations to perform on the list of signals.*/ #define SET 0 /* Set this signal to first_arg. */ #define REVERT 1 /* Revert to this signals original value. */ #define IGNORE 2 /* Ignore this signal. */ extern int posixly_correct; int trap_builtin (list) WORD_LIST *list; { int list_signal_names, display, result, opt; list_signal_names = display = 0; result = EXECUTION_SUCCESS; reset_internal_getopt (); while ((opt = internal_getopt (list, "lp")) != -1) { switch (opt) { case 'l': list_signal_names++; break; case 'p': display++; break; default: builtin_usage (); return (EX_USAGE); } } list = loptend; if (list_signal_names) return (display_signal_list ((WORD_LIST *)NULL, 1)); else if (display || list == 0) return (display_traps (list)); else { char *first_arg; int operation, sig; operation = SET; first_arg = list->word->word; if (first_arg && *first_arg && (*first_arg != '-' || first_arg[1]) && signal_object_p (first_arg)) operation = REVERT; else { list = list->next; if (*first_arg == '\0') operation = IGNORE; else if (first_arg[0] == '-' && !first_arg[1]) operation = REVERT; } while (list) { sig = decode_signal (list->word->word); if (sig == NO_SIG) { sh_invalidsig (list->word->word); result = EXECUTION_FAILURE; } else { switch (operation) { case SET: set_signal (sig, first_arg); break; case REVERT: restore_default_signal (sig); /* Signals that the shell treats specially need special handling. */ switch (sig) { case SIGINT: if (interactive) set_signal_handler (SIGINT, sigint_sighandler); else set_signal_handler (SIGINT, termination_unwind_protect); break; case SIGQUIT: /* Always ignore SIGQUIT. */ set_signal_handler (SIGQUIT, SIG_IGN); break; case SIGTERM: #if defined (JOB_CONTROL) case SIGTTIN: case SIGTTOU: case SIGTSTP: #endif /* JOB_CONTROL */ if (interactive) set_signal_handler (sig, SIG_IGN); break; } break; case IGNORE: ignore_signal (sig); break; } } list = list->next; } } return (result); } static void showtrap (i) int i; { char *t, *p, *sn; p = trap_list[i]; if (p == (char *)DEFAULT_SIG) return; t = (p == (char *)IGNORE_SIG) ? (char *)NULL : sh_single_quote (p); sn = signal_name (i); /* Make sure that signals whose names are unknown (for whatever reason) are printed as signal numbers. */ if (STREQN (sn, "SIGJUNK", 7) || STREQN (sn, "unknown", 7)) printf ("trap -- %s %d\n", t ? t : "''", i); else if (posixly_correct) { if (STREQN (sn, "SIG", 3)) printf ("trap -- %s %s\n", t ? t : "''", sn+3); else printf ("trap -- %s %s\n", t ? t : "''", sn); } else printf ("trap -- %s %s\n", t ? t : "''", sn); FREE (t); } static int display_traps (list) WORD_LIST *list; { int result, i; if (list == 0) { for (i = 0; i < BASH_NSIG; i++) showtrap (i); return (EXECUTION_SUCCESS); } for (result = EXECUTION_SUCCESS; list; list = list->next) { i = decode_signal (list->word->word); if (i == NO_SIG) { sh_invalidsig (list->word->word); result = EXECUTION_FAILURE; } else showtrap (i); } return (result); }
This file is type.def, from which is created type.c. It implements the builtin "type" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES type.c $BUILTIN type $FUNCTION type_builtin $SHORT_DOC type [-afptP] name [name ...] For each NAME, indicate how it would be interpreted if used as a command name. If the -t option is used, `type' outputs a single word which is one of `alias', `keyword', `function', `builtin', `file' or `', if NAME is an alias, shell reserved word, shell function, shell builtin, disk file, or unfound, respectively. If the -p flag is used, `type' either returns the name of the disk file that would be executed, or nothing if `type -t NAME' would not return `file'. If the -a flag is used, `type' displays all of the places that contain an executable named `file'. This includes aliases, builtins, and functions, if and only if the -p flag is not also used. The -f flag suppresses shell function lookup. The -P flag forces a PATH search for each NAME, even if it is an alias, builtin, or function, and returns the name of the disk file that would be executed. $END #include <config.h> #include "../bashtypes.h" #include "posixstat.h" #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <stdio.h> #include "../bashansi.h" #include "../shell.h" #include "../findcmd.h" #include "../hashcmd.h" #if defined (ALIAS) #include "../alias.h" #endif /* ALIAS */ #include "common.h" #include "bashgetopt.h" extern int find_reserved_word __P((char *)); extern char *this_command_name; /* For each word in LIST, find out what the shell is going to do with it as a simple command. i.e., which file would this shell use to execve, or if it is a builtin command, or an alias. Possible flag arguments: -t Returns the "type" of the object, one of `alias', `keyword', `function', `builtin', or `file'. -p Returns the pathname of the file if -type is a file. -a Returns all occurrences of words, whether they be a filename in the path, alias, function, or builtin. -f Suppress shell function lookup, like `command'. -P Force a path search even in the presence of other definitions. Order of evaluation: alias keyword function builtin file */ int type_builtin (list) WORD_LIST *list; { int dflags, successful_finds, opt; WORD_LIST *this; if (list == 0) return (EXECUTION_SUCCESS); dflags = CDESC_SHORTDESC; /* default */ successful_finds = 0; /* Handle the obsolescent `-type', `-path', and `-all' by prescanning the arguments and converting those options to the form that internal_getopt recognizes. Converts `--type', `--path', and `--all' also. THIS SHOULD REALLY GO AWAY. */ for (this = list; this && this->word->word[0] == '-'; this = this->next) { char *flag = &(this->word->word[1]); if (STREQ (flag, "type") || STREQ (flag, "-type")) { this->word->word[1] = 't'; this->word->word[2] = '\0'; } else if (STREQ (flag, "path") || STREQ (flag, "-path")) { this->word->word[1] = 'p'; this->word->word[2] = '\0'; } else if (STREQ (flag, "all") || STREQ (flag, "-all")) { this->word->word[1] = 'a'; this->word->word[2] = '\0'; } } reset_internal_getopt (); while ((opt = internal_getopt (list, "afptP")) != -1) { switch (opt) { case 'a': dflags |= CDESC_ALL; break; case 'f': dflags |= CDESC_NOFUNCS; break; case 'p': dflags |= CDESC_PATH_ONLY; dflags &= ~(CDESC_TYPE|CDESC_SHORTDESC); break; case 't': dflags |= CDESC_TYPE; dflags &= ~(CDESC_PATH_ONLY|CDESC_SHORTDESC); break; case 'P': /* shorthand for type -ap */ dflags |= (CDESC_PATH_ONLY|CDESC_FORCE_PATH); dflags &= ~(CDESC_TYPE|CDESC_SHORTDESC); break; default: builtin_usage (); return (EX_USAGE); } } list = loptend; while (list) { int found; found = describe_command (list->word->word, dflags); if (!found && (dflags & (CDESC_PATH_ONLY|CDESC_TYPE)) == 0) sh_notfound (list->word->word); successful_finds += found; list = list->next; } fflush (stdout); return ((successful_finds != 0) ? EXECUTION_SUCCESS : EXECUTION_FAILURE); } /* * Describe COMMAND as required by the type and command builtins. * * Behavior is controlled by DFLAGS. Flag values are * CDESC_ALL print all descriptions of a command * CDESC_SHORTDESC print the description for type and command -V * CDESC_REUSABLE print in a format that may be reused as input * CDESC_TYPE print the type for type -t * CDESC_PATH_ONLY print the path for type -p * CDESC_FORCE_PATH force a path search for type -P * CDESC_NOFUNCS skip function lookup for type -f * * CDESC_ALL says whether or not to look for all occurrences of COMMAND, or * return after finding it once. */ int describe_command (command, dflags) char *command; int dflags; { int found, i, found_file, f, all; char *full_path, *x; SHELL_VAR *func; #if defined (ALIAS) alias_t *alias; #endif all = (dflags & CDESC_ALL) != 0; found = found_file = 0; full_path = (char *)NULL; #if defined (ALIAS) /* Command is an alias? */ if (((dflags & CDESC_FORCE_PATH) == 0) && (alias = find_alias (command))) { if (dflags & CDESC_TYPE) puts ("alias"); else if (dflags & CDESC_SHORTDESC) printf ("%s is aliased to `%s'\n", command, alias->value); else if (dflags & CDESC_REUSABLE) { x = sh_single_quote (alias->value); printf ("alias %s=%s\n", command, x); free (x); } found = 1; if (all == 0) return (1); } #endif /* ALIAS */ /* Command is a shell reserved word? */ if (((dflags & CDESC_FORCE_PATH) == 0) && (i = find_reserved_word (command)) >= 0) { if (dflags & CDESC_TYPE) puts ("keyword"); else if (dflags & CDESC_SHORTDESC) printf ("%s is a shell keyword\n", command); else if (dflags & CDESC_REUSABLE) printf ("%s\n", command); found = 1; if (all == 0) return (1); } /* Command is a function? */ if (((dflags & (CDESC_FORCE_PATH|CDESC_NOFUNCS)) == 0) && (func = find_function (command))) { if (dflags & CDESC_TYPE) puts ("function"); else if (dflags & CDESC_SHORTDESC) { #define PRETTY_PRINT_FUNC 1 char *result; printf ("%s is a function\n", command); /* We're blowing away THE_PRINTED_COMMAND here... */ result = named_function_string (command, (COMMAND *) function_cell (func), PRETTY_PRINT_FUNC); printf ("%s\n", result); #undef PRETTY_PRINT_FUNC } else if (dflags & CDESC_REUSABLE) printf ("%s\n", command); found = 1; if (all == 0) return (1); } /* Command is a builtin? */ if (((dflags & CDESC_FORCE_PATH) == 0) && find_shell_builtin (command)) { if (dflags & CDESC_TYPE) puts ("builtin"); else if (dflags & CDESC_SHORTDESC) printf ("%s is a shell builtin\n", command); else if (dflags & CDESC_REUSABLE) printf ("%s\n", command); found = 1; if (all == 0) return (1); } /* Command is a disk file? */ /* If the command name given is already an absolute command, just check to see if it is executable. */ if (absolute_program (command)) { f = file_status (command); if (f & FS_EXECABLE) { if (dflags & CDESC_TYPE) puts ("file"); else if (dflags & CDESC_SHORTDESC) printf ("%s is %s\n", command, command); else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY)) printf ("%s\n", command); /* There's no use looking in the hash table or in $PATH, because they're not consulted when an absolute program name is supplied. */ return (1); } } /* If the user isn't doing "-a", then we might care about whether the file is present in our hash table. */ if (all == 0 || (dflags & CDESC_FORCE_PATH)) { if (full_path = phash_search (command)) { if (dflags & CDESC_TYPE) puts ("file"); else if (dflags & CDESC_SHORTDESC) printf ("%s is hashed (%s)\n", command, full_path); else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY)) printf ("%s\n", full_path); free (full_path); return (1); } } /* Now search through $PATH. */ while (1) { if (all == 0) full_path = find_user_command (command); else full_path = user_command_matches (command, FS_EXEC_ONLY, found_file); /* XXX - should that be FS_EXEC_PREFERRED? */ if (!full_path) break; /* If we found the command as itself by looking through $PATH, it probably doesn't exist. Check whether or not the command is an executable file. If it's not, don't report a match. */ if (STREQ (full_path, command)) { f = file_status (full_path); if ((f & FS_EXECABLE) == 0) { free (full_path); full_path = (char *)NULL; if (all == 0) break; } else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY|CDESC_SHORTDESC)) full_path = sh_makepath ((char *)NULL, full_path, MP_DOCWD); } found_file++; found = 1; if (dflags & CDESC_TYPE) puts ("file"); else if (dflags & CDESC_SHORTDESC) printf ("%s is %s\n", command, full_path); else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY)) printf ("%s\n", full_path); free (full_path); full_path = (char *)NULL; if (all == 0) break; } return (found); }
This file is ulimit.def, from which is created ulimit.c. It implements the builtin "ulimit" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES ulimit.c $BUILTIN ulimit $FUNCTION ulimit_builtin $DEPENDS_ON !_MINIX $SHORT_DOC ulimit [-SHacdflmnpstuv] [limit] Ulimit provides control over the resources available to processes started by the shell, on systems that allow such control. If an option is given, it is interpreted as follows: -S use the `soft' resource limit -H use the `hard' resource limit -a all current limits are reported -c the maximum size of core files created -d the maximum size of a process's data segment -f the maximum size of files created by the shell -l the maximum size a process may lock into memory -m the maximum resident set size -n the maximum number of open file descriptors -p the pipe buffer size -s the maximum stack size -t the maximum amount of cpu time in seconds -u the maximum number of user processes -v the size of virtual memory If LIMIT is given, it is the new value of the specified resource; the special LIMIT values `soft', `hard', and `unlimited' stand for the current soft limit, the current hard limit, and no limit, respectively. Otherwise, the current value of the specified resource is printed. If no option is given, then -f is assumed. Values are in 1024-byte increments, except for -t, which is in seconds, -p, which is in increments of 512 bytes, and -u, which is an unscaled number of processes. $END #if !defined (_MINIX) #include <config.h> #include "../bashtypes.h" #ifndef _MINIX # include <sys/param.h> #endif #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <stdio.h> #include <errno.h> #include "../shell.h" #include "common.h" #include "bashgetopt.h" #include "pipesize.h" #if !defined (errno) extern int errno; #endif /* For some reason, HPUX chose to make these definitions visible only if _KERNEL is defined, so we define _KERNEL before including <sys/resource.h> and #undef it afterward. */ #if defined (HAVE_RESOURCE) # include <sys/time.h> # if defined (HPUX) && defined (RLIMIT_NEEDS_KERNEL) # define _KERNEL # endif # include <sys/resource.h> # if defined (HPUX) && defined (RLIMIT_NEEDS_KERNEL) # undef _KERNEL # endif #else # include <sys/times.h> #endif #if defined (HAVE_LIMITS_H) # include <limits.h> #endif /* Check for the most basic symbols. If they aren't present, this system's <sys/resource.h> isn't very useful to us. */ #if !defined (RLIMIT_FSIZE) || !defined (HAVE_GETRLIMIT) # undef HAVE_RESOURCE #endif #if !defined (RLIMTYPE) # define RLIMTYPE long # define string_to_rlimtype(s) strtol(s, (char **)NULL, 10) # define print_rlimtype(num, nl) printf ("%ld%s", num, nl ? "\n" : "") #endif /* Some systems use RLIMIT_NOFILE, others use RLIMIT_OFILE */ #if defined (HAVE_RESOURCE) && defined (RLIMIT_OFILE) && !defined (RLIMIT_NOFILE) # define RLIMIT_NOFILE RLIMIT_OFILE #endif /* HAVE_RESOURCE && RLIMIT_OFILE && !RLIMIT_NOFILE */ /* Some systems have these, some do not. */ #ifdef RLIMIT_FSIZE # define RLIMIT_FILESIZE RLIMIT_FSIZE #else # define RLIMIT_FILESIZE 256 #endif #define RLIMIT_PIPESIZE 257 #ifdef RLIMIT_NOFILE # define RLIMIT_OPENFILES RLIMIT_NOFILE #else # define RLIMIT_OPENFILES 258 #endif #ifdef RLIMIT_VMEM # define RLIMIT_VIRTMEM RLIMIT_VMEM # define RLIMIT_VMBLKSZ 1024 #else # ifdef RLIMIT_AS # define RLIMIT_VIRTMEM RLIMIT_AS # define RLIMIT_VMBLKSZ 1024 # else # define RLIMIT_VIRTMEM 259 # define RLIMIT_VMBLKSZ 1 # endif #endif #ifdef RLIMIT_NPROC # define RLIMIT_MAXUPROC RLIMIT_NPROC #else # define RLIMIT_MAXUPROC 260 #endif #if !defined (RLIM_INFINITY) # define RLIM_INFINITY 0x7fffffff #endif #if !defined (RLIM_SAVED_CUR) # define RLIM_SAVED_CUR RLIM_INFINITY #endif #if !defined (RLIM_SAVED_MAX) # define RLIM_SAVED_MAX RLIM_INFINITY #endif #define LIMIT_HARD 0x01 #define LIMIT_SOFT 0x02 static int _findlim __P((int)); static int ulimit_internal __P((int, char *, int, int)); static int get_limit __P((int, RLIMTYPE *, RLIMTYPE *)); static int set_limit __P((int, RLIMTYPE, int)); static void printone __P((int, RLIMTYPE, int)); static void print_all_limits __P((int)); static int set_all_limits __P((int, RLIMTYPE)); static int filesize __P((RLIMTYPE *)); static int pipesize __P((RLIMTYPE *)); static int getmaxuprc __P((RLIMTYPE *)); static int getmaxvm __P((RLIMTYPE *, RLIMTYPE *)); typedef struct { int option; /* The ulimit option for this limit. */ int parameter; /* Parameter to pass to get_limit (). */ int block_factor; /* Blocking factor for specific limit. */ char *description; /* Descriptive string to output. */ char *units; /* scale */ } RESOURCE_LIMITS; static RESOURCE_LIMITS limits[] = { #ifdef RLIMIT_CORE { 'c', RLIMIT_CORE, 1024, "core file size", "blocks" }, #endif #ifdef RLIMIT_DATA { 'd', RLIMIT_DATA, 1024, "data seg size", "kbytes" }, #endif { 'f', RLIMIT_FILESIZE, 1024, "file size", "blocks" }, #ifdef RLIMIT_MEMLOCK { 'l', RLIMIT_MEMLOCK, 1024, "max locked memory", "kbytes" }, #endif #ifdef RLIMIT_RSS { 'm', RLIMIT_RSS, 1024, "max memory size", "kbytes" }, #endif /* RLIMIT_RSS */ { 'n', RLIMIT_OPENFILES, 1, "open files", (char *)NULL}, { 'p', RLIMIT_PIPESIZE, 512, "pipe size", "512 bytes" }, #ifdef RLIMIT_STACK { 's', RLIMIT_STACK, 1024, "stack size", "kbytes" }, #endif #ifdef RLIMIT_CPU { 't', RLIMIT_CPU, 1, "cpu time", "seconds" }, #endif /* RLIMIT_CPU */ { 'u', RLIMIT_MAXUPROC, 1, "max user processes", (char *)NULL }, #if defined (HAVE_RESOURCE) { 'v', RLIMIT_VIRTMEM, RLIMIT_VMBLKSZ, "virtual memory", "kbytes" }, #endif #ifdef RLIMIT_SWAP { 'w', RLIMIT_SWAP, 1024, "swap size", "kbytes" }, #endif { -1, -1, -1, (char *)NULL, (char *)NULL } }; #define NCMDS (sizeof(limits) / sizeof(limits[0])) typedef struct _cmd { int cmd; char *arg; } ULCMD; static ULCMD *cmdlist; static int ncmd; static int cmdlistsz; #if !defined (HAVE_RESOURCE) && !defined (HAVE_ULIMIT) long ulimit (cmd, newlim) int cmd; long newlim; { errno = EINVAL; return -1; } #endif /* !HAVE_RESOURCE && !HAVE_ULIMIT */ static int _findlim (opt) int opt; { register int i; for (i = 0; limits[i].option > 0; i++) if (limits[i].option == opt) return i; return -1; } static char optstring[4 + 2 * NCMDS]; /* Report or set limits associated with certain per-process resources. See the help documentation in builtins.c for a full description. */ int ulimit_builtin (list) register WORD_LIST *list; { register char *s; int c, limind, mode, opt, all_limits; mode = 0; all_limits = 0; /* Idea stolen from pdksh -- build option string the first time called. */ if (optstring[0] == 0) { s = optstring; *s++ = 'a'; *s++ = 'S'; *s++ = 'H'; for (c = 0; limits[c].option > 0; c++) { *s++ = limits[c].option; *s++ = ';'; } *s = '\0'; } /* Initialize the command list. */ if (cmdlistsz == 0) cmdlist = (ULCMD *)xmalloc ((cmdlistsz = 16) * sizeof (ULCMD)); ncmd = 0; reset_internal_getopt (); while ((opt = internal_getopt (list, optstring)) != -1) { switch (opt) { case 'a': all_limits++; break; /* -S and -H are modifiers, not real options. */ case 'S': mode |= LIMIT_SOFT; break; case 'H': mode |= LIMIT_HARD; break; case '?': builtin_usage (); return (EX_USAGE); default: if (ncmd >= cmdlistsz) cmdlist = (ULCMD *)xrealloc (cmdlist, (cmdlistsz *= 2) * sizeof (ULCMD)); cmdlist[ncmd].cmd = opt; cmdlist[ncmd++].arg = list_optarg; break; } } list = loptend; if (all_limits) { #ifdef NOTYET if (list) /* setting */ { if (STREQ (list->word->word, "unlimited") == 0) { builtin_error ("%s: invalid limit argument", list->word->word); return (EXECUTION_FAILURE); } return (set_all_limits (mode == 0 ? LIMIT_SOFT|LIMIT_HARD : mode, RLIM_INFINITY)); } #endif print_all_limits (mode == 0 ? LIMIT_SOFT : mode); return (EXECUTION_SUCCESS); } /* default is `ulimit -f' */ if (ncmd == 0) { cmdlist[ncmd].cmd = 'f'; /* `ulimit something' is same as `ulimit -f something' */ cmdlist[ncmd++].arg = list ? list->word->word : (char *)NULL; if (list) list = list->next; } /* verify each command in the list. */ for (c = 0; c < ncmd; c++) { limind = _findlim (cmdlist[c].cmd); if (limind == -1) { builtin_error ("`%c': bad command", cmdlist[c].cmd); return (EX_USAGE); } } for (c = 0; c < ncmd; c++) if (ulimit_internal (cmdlist[c].cmd, cmdlist[c].arg, mode, ncmd > 1) == EXECUTION_FAILURE) return (EXECUTION_FAILURE); return (EXECUTION_SUCCESS); } static int ulimit_internal (cmd, cmdarg, mode, multiple) int cmd; char *cmdarg; int mode, multiple; { int opt, limind, setting; int block_factor; RLIMTYPE soft_limit, hard_limit, real_limit, limit; setting = cmdarg != 0; limind = _findlim (cmd); if (mode == 0) mode = setting ? (LIMIT_HARD|LIMIT_SOFT) : LIMIT_SOFT; opt = get_limit (limind, &soft_limit, &hard_limit); if (opt < 0) { builtin_error ("%s: cannot get limit: %s", limits[limind].description, strerror (errno)); return (EXECUTION_FAILURE); } if (setting == 0) /* print the value of the specified limit */ { printone (limind, (mode & LIMIT_SOFT) ? soft_limit : hard_limit, multiple); return (EXECUTION_SUCCESS); } /* Setting the limit. */ if (STREQ (cmdarg, "hard")) real_limit = hard_limit; else if (STREQ (cmdarg, "soft")) real_limit = soft_limit; else if (STREQ (cmdarg, "unlimited")) real_limit = RLIM_INFINITY; else if (all_digits (cmdarg)) { limit = string_to_rlimtype (cmdarg); block_factor = limits[limind].block_factor; real_limit = limit * block_factor; if ((real_limit / block_factor) != limit) { sh_erange (cmdarg, "limit"); return (EXECUTION_FAILURE); } } else { sh_invalidnum (cmdarg); return (EXECUTION_FAILURE); } if (set_limit (limind, real_limit, mode) < 0) { builtin_error ("%s: cannot modify limit: %s", limits[limind].description, strerror (errno)); return (EXECUTION_FAILURE); } return (EXECUTION_SUCCESS); } static int get_limit (ind, softlim, hardlim) int ind; RLIMTYPE *softlim, *hardlim; { RLIMTYPE value; #if defined (HAVE_RESOURCE) struct rlimit limit; #endif if (limits[ind].parameter >= 256) { switch (limits[ind].parameter) { case RLIMIT_FILESIZE: if (filesize (&value) < 0) return -1; break; case RLIMIT_PIPESIZE: if (pipesize (&value) < 0) return -1; break; case RLIMIT_OPENFILES: value = (RLIMTYPE)getdtablesize (); break; case RLIMIT_VIRTMEM: return (getmaxvm (softlim, hardlim)); case RLIMIT_MAXUPROC: if (getmaxuprc (&value) < 0) return -1; break; default: errno = EINVAL; return -1; } *softlim = *hardlim = value; return (0); } else { #if defined (HAVE_RESOURCE) if (getrlimit (limits[ind].parameter, &limit) < 0) return -1; *softlim = limit.rlim_cur; *hardlim = limit.rlim_max; # if defined (HPUX9) if (limits[ind].parameter == RLIMIT_FILESIZE) { *softlim *= 512; *hardlim *= 512; /* Ugh. */ } else # endif /* HPUX9 */ return 0; #else errno = EINVAL; return -1; #endif } } static int set_limit (ind, newlim, mode) int ind; RLIMTYPE newlim; int mode; { #if defined (HAVE_RESOURCE) struct rlimit limit; RLIMTYPE val; #endif if (limits[ind].parameter >= 256) switch (limits[ind].parameter) { case RLIMIT_FILESIZE: #if !defined (HAVE_RESOURCE) return (ulimit (2, newlim / 512L)); #else errno = EINVAL; return -1; #endif case RLIMIT_OPENFILES: #if defined (HAVE_SETDTABLESIZE) # if defined (__CYGWIN__) /* Grrr... Cygwin declares setdtablesize as void. */ setdtablesize (newlim); return 0; # else return (setdtablesize (newlim)); # endif #endif case RLIMIT_PIPESIZE: case RLIMIT_VIRTMEM: case RLIMIT_MAXUPROC: default: errno = EINVAL; return -1; } else { #if defined (HAVE_RESOURCE) if (getrlimit (limits[ind].parameter, &limit) < 0) return -1; # if defined (HPUX9) if (limits[ind].parameter == RLIMIT_FILESIZE) newlim /= 512; /* Ugh. */ # endif /* HPUX9 */ val = (current_user.euid != 0 && newlim == RLIM_INFINITY && (mode & LIMIT_HARD) == 0 && /* XXX -- test */ (limit.rlim_cur <= limit.rlim_max)) ? limit.rlim_max : newlim; if (mode & LIMIT_SOFT) limit.rlim_cur = val; if (mode & LIMIT_HARD) limit.rlim_max = val; return (setrlimit (limits[ind].parameter, &limit)); #else errno = EINVAL; return -1; #endif } } static int getmaxvm (softlim, hardlim) RLIMTYPE *softlim, *hardlim; { #if defined (HAVE_RESOURCE) struct rlimit datalim, stacklim; if (getrlimit (RLIMIT_DATA, &datalim) < 0) return -1; if (getrlimit (RLIMIT_STACK, &stacklim) < 0) return -1; /* Protect against overflow. */ *softlim = (datalim.rlim_cur / 1024L) + (stacklim.rlim_cur / 1024L); *hardlim = (datalim.rlim_max / 1024L) + (stacklim.rlim_max / 1024L); return 0; #else errno = EINVAL; return -1; #endif /* HAVE_RESOURCE */ } static int filesize(valuep) RLIMTYPE *valuep; { #if !defined (HAVE_RESOURCE) long result; if ((result = ulimit (1, 0L)) < 0) return -1; else *valuep = (RLIMTYPE) result * 512; return 0; #else errno = EINVAL; return -1; #endif } static int pipesize (valuep) RLIMTYPE *valuep; { #if defined (PIPE_BUF) /* This is defined on Posix systems. */ *valuep = (RLIMTYPE) PIPE_BUF; return 0; #else # if defined (PIPESIZE) /* This is defined by running a program from the Makefile. */ *valuep = (RLIMTYPE) PIPESIZE; return 0; # else errno = EINVAL; return -1; # endif /* PIPESIZE */ #endif /* PIPE_BUF */ } static int getmaxuprc (valuep) RLIMTYPE *valuep; { long maxchild; maxchild = getmaxchild (); if (maxchild < 0) { errno = EINVAL; return -1; } else { *valuep = (RLIMTYPE) maxchild; return 0; } } static void print_all_limits (mode) int mode; { register int i; RLIMTYPE softlim, hardlim; if (mode == 0) mode |= LIMIT_SOFT; for (i = 0; limits[i].option > 0; i++) { if (get_limit (i, &softlim, &hardlim) < 0) builtin_error ("%s: cannot get limit: %s", limits[i].description, strerror (errno)); else printone (i, (mode & LIMIT_SOFT) ? softlim : hardlim, 1); } } static void printone (limind, curlim, pdesc) int limind; RLIMTYPE curlim; int pdesc; { char unitstr[64]; if (pdesc) { if (limits[limind].units) sprintf (unitstr, "(%s, -%c) ", limits[limind].units, limits[limind].option); else sprintf (unitstr, "(-%c) ", limits[limind].option); printf ("%-18s %16s", limits[limind].description, unitstr); } if (curlim == RLIM_INFINITY) puts ("unlimited"); else if (curlim == RLIM_SAVED_MAX) puts ("hard"); else if (curlim == RLIM_SAVED_CUR) puts ("soft"); else print_rlimtype ((curlim / limits[limind].block_factor), 1); } /* Set all limits to NEWLIM. NEWLIM currently must be RLIM_INFINITY, which causes all limits to be set as high as possible depending on mode (like csh `unlimit'). Returns -1 if NEWLIM is invalid, 0 if all limits were set successfully, and 1 if at least one limit could not be set. To raise all soft limits to their corresponding hard limits, use ulimit -S -a unlimited To attempt to raise all hard limits to infinity (superuser-only), use ulimit -H -a unlimited To attempt to raise all soft and hard limits to infinity, use ulimit -a unlimited */ static int set_all_limits (mode, newlim) int mode; RLIMTYPE newlim; { register int i; int retval = 0; if (newlim != RLIM_INFINITY) { errno = EINVAL; return -1; } if (mode == 0) mode = LIMIT_SOFT|LIMIT_HARD; for (retval = i = 0; limits[i].option > 0; i++) if (set_limit (i, newlim, mode) < 0) { builtin_error ("%s: cannot modify limit: %s", limits[i].description, strerror (errno)); retval = 1; } return retval; } #endif /* !_MINIX */
This file is umask.def, from which is created umask.c. It implements the builtin "umask" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES umask.c $BUILTIN umask $FUNCTION umask_builtin $SHORT_DOC umask [-p] [-S] [mode] The user file-creation mask is set to MODE. If MODE is omitted, or if `-S' is supplied, the current value of the mask is printed. The `-S' option makes the output symbolic; otherwise an octal number is output. If `-p' is supplied, and MODE is omitted, the output is in a form that may be used as input. If MODE begins with a digit, it is interpreted as an octal number, otherwise it is a symbolic mode string like that accepted by chmod(1). $END #include <config.h> #include "../bashtypes.h" #include "filecntl.h" #ifndef _MINIX # include <sys/file.h> #endif #if defined (HAVE_UNISTD_H) #include <unistd.h> #endif #include <stdio.h> #include <chartypes.h> #include "../shell.h" #include "posixstat.h" #include "common.h" #include "bashgetopt.h" #ifdef __LCC__ #define mode_t int #endif /* **************************************************************** */ /* */ /* UMASK Builtin and Helpers */ /* */ /* **************************************************************** */ static void print_symbolic_umask __P((mode_t)); static int symbolic_umask __P((WORD_LIST *)); /* Set or display the mask used by the system when creating files. Flag of -S means display the umask in a symbolic mode. */ int umask_builtin (list) WORD_LIST *list; { int print_symbolically, opt, umask_value, pflag; mode_t umask_arg; print_symbolically = pflag = 0; reset_internal_getopt (); while ((opt = internal_getopt (list, "Sp")) != -1) { switch (opt) { case 'S': print_symbolically++; break; case 'p': pflag++; break; default: builtin_usage (); return (EX_USAGE); } } list = loptend; if (list) { if (DIGIT (*list->word->word)) { umask_value = read_octal (list->word->word); /* Note that other shells just let you set the umask to zero by specifying a number out of range. This is a problem with those shells. We don't change the umask if the input is lousy. */ if (umask_value == -1) { sh_erange (list->word->word, "octal number"); return (EXECUTION_FAILURE); } } else { umask_value = symbolic_umask (list); if (umask_value == -1) return (EXECUTION_FAILURE); } umask_arg = (mode_t)umask_value; umask (umask_arg); if (print_symbolically) print_symbolic_umask (umask_arg); } else /* Display the UMASK for this user. */ { umask_arg = umask (022); umask (umask_arg); if (pflag) printf ("umask%s ", (print_symbolically ? " -S" : "")); if (print_symbolically) print_symbolic_umask (umask_arg); else printf ("%04lo\n", (unsigned long)umask_arg); } fflush (stdout); return (EXECUTION_SUCCESS); } /* Print the umask in a symbolic form. In the output, a letter is printed if the corresponding bit is clear in the umask. */ static void print_symbolic_umask (um) mode_t um; { char ubits[4], gbits[4], obits[4]; /* u=rwx,g=rwx,o=rwx */ int i; i = 0; if ((um & S_IRUSR) == 0) ubits[i++] = 'r'; if ((um & S_IWUSR) == 0) ubits[i++] = 'w'; if ((um & S_IXUSR) == 0) ubits[i++] = 'x'; ubits[i] = '\0'; i = 0; if ((um & S_IRGRP) == 0) gbits[i++] = 'r'; if ((um & S_IWGRP) == 0) gbits[i++] = 'w'; if ((um & S_IXGRP) == 0) gbits[i++] = 'x'; gbits[i] = '\0'; i = 0; if ((um & S_IROTH) == 0) obits[i++] = 'r'; if ((um & S_IWOTH) == 0) obits[i++] = 'w'; if ((um & S_IXOTH) == 0) obits[i++] = 'x'; obits[i] = '\0'; printf ("u=%s,g=%s,o=%s\n", ubits, gbits, obits); } int parse_symbolic_mode (mode, initial_bits) char *mode; int initial_bits; { int who, op, perm, bits, c; char *s; for (s = mode, bits = initial_bits;;) { who = op = perm = 0; /* Parse the `who' portion of the symbolic mode clause. */ while (member (*s, "agou")) { switch (c = *s++) { case 'u': who |= S_IRWXU; continue; case 'g': who |= S_IRWXG; continue; case 'o': who |= S_IRWXO; continue; case 'a': who |= S_IRWXU | S_IRWXG | S_IRWXO; continue; default: break; } } /* The operation is now sitting in *s. */ op = *s++; switch (op) { case '+': case '-': case '=': break; default: builtin_error ("`%c': invalid symbolic mode operator", op); return (-1); } /* Parse out the `perm' section of the symbolic mode clause. */ while (member (*s, "rwx")) { c = *s++; switch (c) { case 'r': perm |= S_IRUGO; break; case 'w': perm |= S_IWUGO; break; case 'x': perm |= S_IXUGO; break; } } /* Now perform the operation or return an error for a bad permission string. */ if (!*s || *s == ',') { if (who) perm &= who; switch (op) { case '+': bits |= perm; break; case '-': bits &= ~perm; break; case '=': bits &= ~who; bits |= perm; break; /* No other values are possible. */ } if (*s == '\0') break; else s++; /* skip past ',' */ } else { builtin_error ("`%c': invalid symbolic mode character", *s); return (-1); } } return (bits); } /* Set the umask from a symbolic mode string similar to that accepted by chmod. If the -S argument is given, then print the umask in a symbolic form. */ static int symbolic_umask (list) WORD_LIST *list; { int um, bits; /* Get the initial umask. Don't change it yet. */ um = umask (022); umask (um); /* All work is done with the complement of the umask -- it's more intuitive and easier to deal with. It is complemented again before being returned. */ bits = parse_symbolic_mode (list->word->word, ~um); if (bits == -1) return (-1); um = ~bits & 0777; return (um); }
This file is wait.def, from which is created wait.c. It implements the builtin "wait" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $BUILTIN wait $FUNCTION wait_builtin $DEPENDS_ON JOB_CONTROL $PRODUCES wait.c $SHORT_DOC wait [n] Wait for the specified process and report its termination status. If N is not given, all currently active child processes are waited for, and the return code is zero. N may be a process ID or a job specification; if a job spec is given, all processes in the job's pipeline are waited for. $END $BUILTIN wait $FUNCTION wait_builtin $DEPENDS_ON !JOB_CONTROL $SHORT_DOC wait [n] Wait for the specified process and report its termination status. If N is not given, all currently active child processes are waited for, and the return code is zero. N is a process ID; if it is not given, all child processes of the shell are waited for. $END #include <config.h> #include "../bashtypes.h" #include <signal.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <chartypes.h> #include "../bashansi.h" #include "../shell.h" #include "../jobs.h" #include "common.h" #include "bashgetopt.h" extern int interrupt_immediately; extern int wait_signal_received; procenv_t wait_intr_buf; /* Wait for the pid in LIST to stop or die. If no arguments are given, then wait for all of the active background processes of the shell and return 0. If a list of pids or job specs are given, return the exit status of the last one waited for. */ #define WAIT_RETURN(s) \ do \ { \ interrupt_immediately = old_interrupt_immediately;\ return (s);\ } \ while (0) int wait_builtin (list) WORD_LIST *list; { int status, code; volatile int old_interrupt_immediately; USE_VAR(list); if (no_options (list)) return (EX_USAGE); list = loptend; old_interrupt_immediately = interrupt_immediately; interrupt_immediately++; /* POSIX.2 says: When the shell is waiting (by means of the wait utility) for asynchronous commands to complete, the reception of a signal for which a trap has been set shall cause the wait utility to return immediately with an exit status greater than 128, after which the trap associated with the signal shall be taken. We handle SIGINT here; it's the only one that needs to be treated specially (I think), since it's handled specially in {no,}jobs.c. */ code = setjmp (wait_intr_buf); if (code) { status = 128 + wait_signal_received; WAIT_RETURN (status); } /* We support jobs or pids. wait <pid-or-job> [pid-or-job ...] */ /* But wait without any arguments means to wait for all of the shell's currently active background processes. */ if (list == 0) { wait_for_background_pids (); WAIT_RETURN (EXECUTION_SUCCESS); } status = EXECUTION_SUCCESS; while (list) { pid_t pid; char *w; intmax_t pid_value; w = list->word->word; if (DIGIT (*w)) { if (legal_number (w, &pid_value) && pid_value == (pid_t)pid_value) { pid = (pid_t)pid_value; status = wait_for_single_pid (pid); } else { sh_badpid (w); WAIT_RETURN (EXECUTION_FAILURE); } } #if defined (JOB_CONTROL) else if (job_control && *w) /* Must be a job spec. Check it out. */ { int job; sigset_t set, oset; BLOCK_CHILD (set, oset); job = get_job_spec (list); if (job < 0 || job >= job_slots || !jobs[job]) { if (job != DUP_JOB) sh_badjob (list->word->word); UNBLOCK_CHILD (oset); status = 127; /* As per Posix.2, section 4.70.2 */ list = list->next; continue; } /* Job spec used. Wait for the last pid in the pipeline. */ UNBLOCK_CHILD (oset); status = wait_for_job (job); } else if (job_control == 0 && *w == '%') { /* can't use jobspecs as arguments if job control is not active. */ sh_nojobs ((char *)NULL); status = EXECUTION_FAILURE; } #endif /* JOB_CONTROL */ else { sh_badpid (w); status = EXECUTION_FAILURE; } list = list->next; } WAIT_RETURN (status); }
This file is getopts.def, from which is created getopts.c. It implements the builtin "getopts" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES getopts.c $BUILTIN getopts $FUNCTION getopts_builtin $SHORT_DOC getopts optstring name [arg] Getopts is used by shell procedures to parse positional parameters. OPTSTRING contains the option letters to be recognized; if a letter is followed by a colon, the option is expected to have an argument, which should be separated from it by white space. Each time it is invoked, getopts will place the next option in the shell variable $name, initializing name if it does not exist, and the index of the next argument to be processed into the shell variable OPTIND. OPTIND is initialized to 1 each time the shell or a shell script is invoked. When an option requires an argument, getopts places that argument into the shell variable OPTARG. getopts reports errors in one of two ways. If the first character of OPTSTRING is a colon, getopts uses silent error reporting. In this mode, no error messages are printed. If an invalid option is seen, getopts places the option character found into OPTARG. If a required argument is not found, getopts places a ':' into NAME and sets OPTARG to the option character found. If getopts is not in silent mode, and an invalid option is seen, getopts places '?' into NAME and unsets OPTARG. If a required option is not found, a '?' is placed in NAME, OPTARG is unset, and a diagnostic message is printed. If the shell variable OPTERR has the value 0, getopts disables the printing of error messages, even if the first character of OPTSTRING is not a colon. OPTERR has the value 1 by default. Getopts normally parses the positional parameters ($0 - $9), but if more arguments are given, they are parsed instead. $END #include <config.h> #include <stdio.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "../bashansi.h" #include "../shell.h" #include "common.h" #include "bashgetopt.h" #include "getopt.h" #define G_EOF -1 #define G_INVALID_OPT -2 #define G_ARG_MISSING -3 extern char *this_command_name; static int getopts_bind_variable __P((char *, char *)); static int dogetopts __P((int, char **)); /* getopts_reset is magic code for when OPTIND is reset. N is the value that has just been assigned to OPTIND. */ void getopts_reset (newind) int newind; { sh_optind = newind; sh_badopt = 0; } static int getopts_bind_variable (name, value) char *name, *value; { SHELL_VAR *v; if (legal_identifier (name)) { v = bind_variable (name, value); return (v && (readonly_p (v) == 0)) ? EXECUTION_SUCCESS : EXECUTION_FAILURE; } else { sh_invalidid (name); return (EXECUTION_FAILURE); } } /* Error handling is now performed as specified by Posix.2, draft 11 (identical to that of ksh-88). The special handling is enabled if the first character of the option string is a colon; this handling disables diagnostic messages concerning missing option arguments and invalid option characters. The handling is as follows. INVALID OPTIONS: name -> "?" if (special_error) then OPTARG = option character found no error output else OPTARG unset diagnostic message fi MISSING OPTION ARGUMENT; if (special_error) then name -> ":" OPTARG = option character found else name -> "?" OPTARG unset diagnostic message fi */ static int dogetopts (argc, argv) int argc; char **argv; { int ret, special_error, old_opterr, i, n; char strval[2], numval[16]; char *optstr; /* list of options */ char *name; /* variable to get flag val */ char *t; if (argc < 3) { builtin_usage (); return (EX_USAGE); } /* argv[0] is "getopts". */ optstr = argv[1]; name = argv[2]; argc -= 2; argv += 2; special_error = optstr[0] == ':'; if (special_error) { old_opterr = sh_opterr; optstr++; sh_opterr = 0; /* suppress diagnostic messages */ } if (argc > 1) { sh_getopt_restore_state (argv); t = argv[0]; argv[0] = dollar_vars[0]; ret = sh_getopt (argc, argv, optstr); argv[0] = t; } else if (rest_of_args == (WORD_LIST *)NULL) { for (i = 0; i < 10 && dollar_vars[i]; i++) ; sh_getopt_restore_state (dollar_vars); ret = sh_getopt (i, dollar_vars, optstr); } else { register WORD_LIST *words; char **v; for (i = 0; i < 10 && dollar_vars[i]; i++) ; for (words = rest_of_args; words; words = words->next, i++) ; v = strvec_create (i + 1); for (i = 0; i < 10 && dollar_vars[i]; i++) v[i] = dollar_vars[i]; for (words = rest_of_args; words; words = words->next, i++) v[i] = words->word->word; v[i] = (char *)NULL; sh_getopt_restore_state (v); ret = sh_getopt (i, v, optstr); free (v); } if (special_error) sh_opterr = old_opterr; /* Set the OPTIND variable in any case, to handle "--" skipping. It's highly unlikely that 14 digits will be too few. */ if (sh_optind < 10) { numval[14] = sh_optind + '0'; numval[15] = '\0'; i = 14; } else { numval[i = 15] = '\0'; n = sh_optind; do { numval[--i] = (n % 10) + '0'; } while (n /= 10); } bind_variable ("OPTIND", numval + i); /* If an error occurred, decide which one it is and set the return code appropriately. In all cases, the option character in error is in OPTOPT. If an invalid option was encountered, OPTARG is NULL. If a required option argument was missing, OPTARG points to a NULL string (that is, sh_optarg[0] == 0). */ if (ret == '?') { if (sh_optarg == NULL) ret = G_INVALID_OPT; else if (sh_optarg[0] == '\0') ret = G_ARG_MISSING; } if (ret == G_EOF) { getopts_bind_variable (name, "?"); return (EXECUTION_FAILURE); } if (ret == G_INVALID_OPT) { /* Invalid option encountered. */ ret = getopts_bind_variable (name, "?"); if (special_error) { strval[0] = (char)sh_optopt; strval[1] = '\0'; bind_variable ("OPTARG", strval); } else unbind_variable ("OPTARG"); return (ret); } if (ret == G_ARG_MISSING) { /* Required argument missing. */ if (special_error) { ret = getopts_bind_variable (name, ":"); strval[0] = (char)sh_optopt; strval[1] = '\0'; bind_variable ("OPTARG", strval); } else { ret = getopts_bind_variable (name, "?"); unbind_variable ("OPTARG"); } return (ret); } bind_variable ("OPTARG", sh_optarg); strval[0] = (char) ret; strval[1] = '\0'; return (getopts_bind_variable (name, strval)); } /* The getopts builtin. Build an argv, and call dogetopts with it. */ int getopts_builtin (list) WORD_LIST *list; { char **av; int ac, ret; if (list == 0) { builtin_usage (); return EX_USAGE; } reset_internal_getopt (); if (internal_getopt (list, "") != -1) { builtin_usage (); return (EX_USAGE); } list = loptend; av = make_builtin_argv (list, &ac); ret = dogetopts (ac, av); free ((char *)av); return (ret); }
This file is pushd.def, from which is created pushd.c. It implements the builtins "pushd", "popd", and "dirs" in Bash. Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES pushd.c $BUILTIN pushd $FUNCTION pushd_builtin $DEPENDS_ON PUSHD_AND_POPD $SHORT_DOC pushd [dir | +N | -N] [-n] Adds a directory to the top of the directory stack, or rotates the stack, making the new top of the stack the current working directory. With no arguments, exchanges the top two directories. +N Rotates the stack so that the Nth directory (counting from the left of the list shown by `dirs', starting with zero) is at the top. -N Rotates the stack so that the Nth directory (counting from the right of the list shown by `dirs', starting with zero) is at the top. -n suppress the normal change of directory when adding directories to the stack, so only the stack is manipulated. dir adds DIR to the directory stack at the top, making it the new current working directory. You can see the directory stack with the `dirs' command. $END $BUILTIN popd $FUNCTION popd_builtin $DEPENDS_ON PUSHD_AND_POPD $SHORT_DOC popd [+N | -N] [-n] Removes entries from the directory stack. With no arguments, removes the top directory from the stack, and cd's to the new top directory. +N removes the Nth entry counting from the left of the list shown by `dirs', starting with zero. For example: `popd +0' removes the first directory, `popd +1' the second. -N removes the Nth entry counting from the right of the list shown by `dirs', starting with zero. For example: `popd -0' removes the last directory, `popd -1' the next to last. -n suppress the normal change of directory when removing directories from the stack, so only the stack is manipulated. You can see the directory stack with the `dirs' command. $END $BUILTIN dirs $FUNCTION dirs_builtin $DEPENDS_ON PUSHD_AND_POPD $SHORT_DOC dirs [-clpv] [+N] [-N] Display the list of currently remembered directories. Directories find their way onto the list with the `pushd' command; you can get back up through the list with the `popd' command. The -l flag specifies that `dirs' should not print shorthand versions of directories which are relative to your home directory. This means that `~/bin' might be displayed as `/homes/bfox/bin'. The -v flag causes `dirs' to print the directory stack with one entry per line, prepending the directory name with its position in the stack. The -p flag does the same thing, but the stack position is not prepended. The -c flag clears the directory stack by deleting all of the elements. +N displays the Nth entry counting from the left of the list shown by dirs when invoked without options, starting with zero. -N displays the Nth entry counting from the right of the list shown by dirs when invoked without options, starting with zero. $END #include <config.h> #if defined (PUSHD_AND_POPD) #include <stdio.h> #ifndef _MINIX # include <sys/param.h> #endif #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "../bashansi.h" #include <errno.h> #include <tilde/tilde.h> #include "../shell.h" #include "maxpath.h" #include "common.h" #include "builtext.h" #ifdef LOADABLE_BUILTIN # include "builtins.h" #endif #if !defined (errno) extern int errno; #endif /* !errno */ /* The list of remembered directories. */ static char **pushd_directory_list = (char **)NULL; /* Number of existing slots in this list. */ static int directory_list_size; /* Offset to the end of the list. */ static int directory_list_offset; static void pushd_error __P((int, char *)); static void clear_directory_stack __P((void)); static int cd_to_string __P((char *)); static int change_to_temp __P((char *)); static void add_dirstack_element __P((char *)); static int get_dirstack_index __P((intmax_t, int, int *)); #define NOCD 0x01 #define ROTATE 0x02 #define LONGFORM 0x04 #define CLEARSTAK 0x08 int pushd_builtin (list) WORD_LIST *list; { char *temp, *current_directory, *top; int j, flags; intmax_t num; char direction; if (list && list->word && ISOPTION (list->word->word, '-')) list = list->next; /* If there is no argument list then switch current and top of list. */ if (list == 0) { if (directory_list_offset == 0) { builtin_error ("no other directory"); return (EXECUTION_FAILURE); } current_directory = get_working_directory ("pushd"); if (current_directory == 0) return (EXECUTION_FAILURE); j = directory_list_offset - 1; temp = pushd_directory_list[j]; pushd_directory_list[j] = current_directory; j = change_to_temp (temp); free (temp); return j; } for (flags = 0; list; list = list->next) { if (ISOPTION (list->word->word, 'n')) { flags |= NOCD; } else if (ISOPTION (list->word->word, '-')) { list = list->next; break; } else if (list->word->word[0] == '-' && list->word->word[1] == '\0') /* Let `pushd -' work like it used to. */ break; else if (((direction = list->word->word[0]) == '+') || direction == '-') { if (legal_number (list->word->word + 1, &num) == 0) { sh_invalidnum (list->word->word); builtin_usage (); return (EXECUTION_FAILURE); } if (direction == '-') num = directory_list_offset - num; if (num > directory_list_offset || num < 0) { pushd_error (directory_list_offset, list->word->word); return (EXECUTION_FAILURE); } flags |= ROTATE; } else if (*list->word->word == '-') { sh_invalidopt (list->word->word); builtin_usage (); return (EXECUTION_FAILURE); } else break; } if (flags & ROTATE) { /* Rotate the stack num times. Remember, the current directory acts like it is part of the stack. */ temp = get_working_directory ("pushd"); if (num == 0) { j = ((flags & NOCD) == 0) ? change_to_temp (temp) : EXECUTION_SUCCESS; free (temp); return j; } do { top = pushd_directory_list[directory_list_offset - 1]; for (j = directory_list_offset - 2; j > -1; j--) pushd_directory_list[j + 1] = pushd_directory_list[j]; pushd_directory_list[j + 1] = temp; temp = top; num--; } while (num); j = ((flags & NOCD) == 0) ? change_to_temp (temp) : EXECUTION_SUCCESS; free (temp); return j; } if (list == 0) return (EXECUTION_SUCCESS); /* Change to the directory in list->word->word. Save the current directory on the top of the stack. */ current_directory = get_working_directory ("pushd"); if (current_directory == 0) return (EXECUTION_FAILURE); j = ((flags & NOCD) == 0) ? cd_builtin (list) : EXECUTION_SUCCESS; if (j == EXECUTION_SUCCESS) { add_dirstack_element ((flags & NOCD) ? savestring (list->word->word) : current_directory); dirs_builtin ((WORD_LIST *)NULL); if (flags & NOCD) free (current_directory); return (EXECUTION_SUCCESS); } else { free (current_directory); return (EXECUTION_FAILURE); } } /* Pop the directory stack, and then change to the new top of the stack. If LIST is non-null it should consist of a word +N or -N, which says what element to delete from the stack. The default is the top one. */ int popd_builtin (list) WORD_LIST *list; { register int i; intmax_t which; int flags; char direction; char *which_word; which_word = (char *)NULL; for (flags = 0, which = 0, direction = '+'; list; list = list->next) { if (ISOPTION (list->word->word, 'n')) { flags |= NOCD; } else if (ISOPTION (list->word->word, '-')) { list = list->next; break; } else if (((direction = list->word->word[0]) == '+') || direction == '-') { if (legal_number (list->word->word + 1, &which) == 0) { sh_invalidnum (list->word->word); builtin_usage (); return (EXECUTION_FAILURE); } which_word = list->word->word; } else if (*list->word->word == '-') { sh_invalidopt (list->word->word); builtin_usage (); return (EXECUTION_FAILURE); } else break; } if (which > directory_list_offset || (directory_list_offset == 0 && which == 0)) { pushd_error (directory_list_offset, which_word ? which_word : ""); return (EXECUTION_FAILURE); } /* Handle case of no specification, or top of stack specification. */ if ((direction == '+' && which == 0) || (direction == '-' && which == directory_list_offset)) { i = ((flags & NOCD) == 0) ? cd_to_string (pushd_directory_list[directory_list_offset - 1]) : EXECUTION_SUCCESS; if (i != EXECUTION_SUCCESS) return (i); free (pushd_directory_list[--directory_list_offset]); } else { /* Since an offset other than the top directory was specified, remove that directory from the list and shift the remainder of the list into place. */ i = (direction == '+') ? directory_list_offset - which : which; free (pushd_directory_list[i]); directory_list_offset--; /* Shift the remainder of the list into place. */ for (; i < directory_list_offset; i++) pushd_directory_list[i] = pushd_directory_list[i + 1]; } dirs_builtin ((WORD_LIST *)NULL); return (EXECUTION_SUCCESS); } /* Print the current list of directories on the directory stack. */ int dirs_builtin (list) WORD_LIST *list; { int flags, desired_index, index_flag, vflag; intmax_t i; char *temp, *w; for (flags = vflag = index_flag = 0, desired_index = -1, w = ""; list; list = list->next) { if (ISOPTION (list->word->word, 'l')) { flags |= LONGFORM; } else if (ISOPTION (list->word->word, 'c')) { flags |= CLEARSTAK; } else if (ISOPTION (list->word->word, 'v')) { vflag |= 2; } else if (ISOPTION (list->word->word, 'p')) { vflag |= 1; } else if (ISOPTION (list->word->word, '-')) { list = list->next; break; } else if (*list->word->word == '+' || *list->word->word == '-') { int sign; if (legal_number (w = list->word->word + 1, &i) == 0) { sh_invalidnum (list->word->word); builtin_usage (); return (EXECUTION_FAILURE); } sign = (*list->word->word == '+') ? 1 : -1; desired_index = get_dirstack_index (i, sign, &index_flag); } else { sh_invalidopt (list->word->word); builtin_usage (); return (EXECUTION_FAILURE); } } if (flags & CLEARSTAK) { clear_directory_stack (); return (EXECUTION_SUCCESS); } if (index_flag && (desired_index < 0 || desired_index > directory_list_offset)) { pushd_error (directory_list_offset, w); return (EXECUTION_FAILURE); } #define DIRSTACK_FORMAT(temp) \ (flags & LONGFORM) ? temp : polite_directory_format (temp) /* The first directory printed is always the current working directory. */ if (index_flag == 0 || (index_flag == 1 && desired_index == 0)) { temp = get_working_directory ("dirs"); if (temp == 0) temp = savestring ("<no current directory>"); if (vflag & 2) printf ("%2d %s", 0, DIRSTACK_FORMAT (temp)); else printf ("%s", DIRSTACK_FORMAT (temp)); free (temp); if (index_flag) { putchar ('\n'); return EXECUTION_SUCCESS; } } #define DIRSTACK_ENTRY(i) \ (flags & LONGFORM) ? pushd_directory_list[i] \ : polite_directory_format (pushd_directory_list[i]) /* Now print the requested directory stack entries. */ if (index_flag) { if (vflag & 2) printf ("%2d %s", directory_list_offset - desired_index, DIRSTACK_ENTRY (desired_index)); else printf ("%s", DIRSTACK_ENTRY (desired_index)); } else for (i = directory_list_offset - 1; i >= 0; i--) if (vflag >= 2) printf ("\n%2d %s", directory_list_offset - (int)i, DIRSTACK_ENTRY (i)); else printf ("%s%s", (vflag & 1) ? "\n" : " ", DIRSTACK_ENTRY (i)); putchar ('\n'); fflush (stdout); return (EXECUTION_SUCCESS); } static void pushd_error (offset, arg) int offset; char *arg; { if (offset == 0) builtin_error ("directory stack empty"); else sh_erange (arg, "directory stack index"); } static void clear_directory_stack () { register int i; for (i = 0; i < directory_list_offset; i++) free (pushd_directory_list[i]); directory_list_offset = 0; } /* Switch to the directory in NAME. This uses the cd_builtin to do the work, so if the result is EXECUTION_FAILURE then an error message has already been printed. */ static int cd_to_string (name) char *name; { WORD_LIST *tlist; int result; tlist = make_word_list (make_word (name), NULL); result = cd_builtin (tlist); dispose_words (tlist); return (result); } static int change_to_temp (temp) char *temp; { int tt; tt = temp ? cd_to_string (temp) : EXECUTION_FAILURE; if (tt == EXECUTION_SUCCESS) dirs_builtin ((WORD_LIST *)NULL); return (tt); } static void add_dirstack_element (dir) char *dir; { if (directory_list_offset == directory_list_size) pushd_directory_list = strvec_resize (pushd_directory_list, directory_list_size += 10); pushd_directory_list[directory_list_offset++] = dir; } static int get_dirstack_index (ind, sign, indexp) intmax_t ind; int sign, *indexp; { if (indexp) *indexp = sign > 0 ? 1 : 2; /* dirs +0 prints the current working directory. */ /* dirs -0 prints last element in directory stack */ if (ind == 0 && sign > 0) return 0; else if (ind == directory_list_offset) { if (indexp) *indexp = sign > 0 ? 2 : 1; return 0; } else if (ind >= 0 && ind <= directory_list_offset) return (sign > 0 ? directory_list_offset - ind : ind); else return -1; } /* Used by the tilde expansion code. */ char * get_dirstack_from_string (string) char *string; { int ind, sign, index_flag; intmax_t i; sign = 1; if (*string == '-' || *string == '+') { sign = (*string == '-') ? -1 : 1; string++; } if (legal_number (string, &i) == 0) return ((char *)NULL); index_flag = 0; ind = get_dirstack_index (i, sign, &index_flag); if (index_flag && (ind < 0 || ind > directory_list_offset)) return ((char *)NULL); if (index_flag == 0 || (index_flag == 1 && ind == 0)) return (get_string_value ("PWD")); else return (pushd_directory_list[ind]); } #ifdef INCLUDE_UNUSED char * get_dirstack_element (ind, sign) intmax_t ind; int sign; { int i; i = get_dirstack_index (ind, sign, (int *)NULL); return (i < 0 || i > directory_list_offset) ? (char *)NULL : pushd_directory_list[i]; } #endif void set_dirstack_element (ind, sign, value) intmax_t ind; int sign; char *value; { int i; i = get_dirstack_index (ind, sign, (int *)NULL); if (ind == 0 || i < 0 || i > directory_list_offset) return; free (pushd_directory_list[i]); pushd_directory_list[i] = savestring (value); } WORD_LIST * get_directory_stack () { register int i; WORD_LIST *ret; char *d, *t; for (ret = (WORD_LIST *)NULL, i = 0; i < directory_list_offset; i++) { d = polite_directory_format (pushd_directory_list[i]); ret = make_word_list (make_word (d), ret); } /* Now the current directory. */ d = get_working_directory ("dirstack"); i = 0; /* sentinel to decide whether or not to free d */ if (d == 0) d = "."; else { t = polite_directory_format (d); /* polite_directory_format sometimes returns its argument unchanged. If it does not, we can free d right away. If it does, we need to mark d to be deleted later. */ if (t != d) { free (d); d = t; } else /* t == d, so d is what we want */ i = 1; } ret = make_word_list (make_word (d), ret); if (i) free (d); return ret; /* was (REVERSE_LIST (ret, (WORD_LIST *)); */ } #ifdef LOADABLE_BUILTIN static char *dirs_doc[] = { "Display the list of currently remembered directories. Directories", "find their way onto the list with the `pushd' command; you can get", "back up through the list with the `popd' command.", "", "The -l flag specifies that `dirs' should not print shorthand versions", "of directories which are relative to your home directory. This means", "that `~/bin' might be displayed as `/homes/bfox/bin'. The -v flag", "causes `dirs' to print the directory stack with one entry per line,", "prepending the directory name with its position in the stack. The -p", "flag does the same thing, but the stack position is not prepended.", "The -c flag clears the directory stack by deleting all of the elements.", "", "+N displays the Nth entry counting from the left of the list shown by", " dirs when invoked without options, starting with zero.", "", "-N displays the Nth entry counting from the right of the list shown by", " dirs when invoked without options, starting with zero.", (char *)NULL }; static char *pushd_doc[] = { "Adds a directory to the top of the directory stack, or rotates", "the stack, making the new top of the stack the current working", "directory. With no arguments, exchanges the top two directories.", "", "+N Rotates the stack so that the Nth directory (counting", " from the left of the list shown by `dirs', starting with", " zero) is at the top.", "", "-N Rotates the stack so that the Nth directory (counting", " from the right of the list shown by `dirs', starting with", " zero) is at the top.", "", "-n suppress the normal change of directory when adding directories", " to the stack, so only the stack is manipulated.", "", "dir adds DIR to the directory stack at the top, making it the", " new current working directory.", "", "You can see the directory stack with the `dirs' command.", (char *)NULL }; static char *popd_doc[] = { "Removes entries from the directory stack. With no arguments,", "removes the top directory from the stack, and cd's to the new", "top directory.", "", "+N removes the Nth entry counting from the left of the list", " shown by `dirs', starting with zero. For example: `popd +0'", " removes the first directory, `popd +1' the second.", "", "-N removes the Nth entry counting from the right of the list", " shown by `dirs', starting with zero. For example: `popd -0'", " removes the last directory, `popd -1' the next to last.", "", "-n suppress the normal change of directory when removing directories", " from the stack, so only the stack is manipulated.", "", "You can see the directory stack with the `dirs' command.", (char *)NULL }; struct builtin pushd_struct = { "pushd", pushd_builtin, BUILTIN_ENABLED, pushd_doc, "pushd [+N | -N] [-n] [dir]", 0 }; struct builtin popd_struct = { "popd", popd_builtin, BUILTIN_ENABLED, popd_doc, "popd [+N | -N] [-n]", 0 }; struct builtin dirs_struct = { "dirs", dirs_builtin, BUILTIN_ENABLED, dirs_doc, "dirs [-clpv] [+N] [-N]", 0 }; #endif /* LOADABLE_BUILTIN */ #endif /* PUSHD_AND_POPD */
This file is shopt.def, from which is created shopt.c. It implements the Bash `shopt' builtin. Copyright (C) 1994-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. $PRODUCES shopt.c $BUILTIN shopt $FUNCTION shopt_builtin $SHORT_DOC shopt [-pqsu] [-o long-option] optname [optname...] Toggle the values of variables controlling optional behavior. The -s flag means to enable (set) each OPTNAME; the -u flag unsets each OPTNAME. The -q flag suppresses output; the exit status indicates whether each OPTNAME is set or unset. The -o option restricts the OPTNAMEs to those defined for use with `set -o'. With no options, or with the -p option, a list of all settable options is displayed, with an indication of whether or not each is set. $END #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include <stdio.h> #include "../shell.h" #include "../flags.h" #include "common.h" #include "bashgetopt.h" #define UNSETOPT 0 #define SETOPT 1 #define OPTFMT "%-15s\t%s\n" extern int allow_null_glob_expansion, glob_dot_filenames; extern int cdable_vars, mail_warning, source_uses_path; extern int no_exit_on_failed_exec, print_shift_error; extern int check_hashed_filenames, promptvars; extern int cdspelling, expand_aliases; extern int check_window_size; extern int glob_ignore_case; extern int hup_on_exit; extern int xpg_echo; #if defined (EXTENDED_GLOB) extern int extended_glob; #endif #if defined (HISTORY) extern int literal_history, command_oriented_history; extern int force_append_history; #endif #if defined (READLINE) extern int hist_verify, history_reediting, perform_hostname_completion; extern int no_empty_command_completion; extern int enable_hostname_completion __P((int)); #endif #if defined (PROGRAMMABLE_COMPLETION) extern int prog_completion_enabled; #endif #if defined (RESTRICTED_SHELL) extern char *shell_name; #endif static void shopt_error __P((char *)); static int set_interactive_comments __P((int)); #if defined (RESTRICTED_SHELL) static int set_restricted_shell __P((int)); #endif static int shopt_login_shell; typedef int shopt_set_func_t __P((int)); static struct { char *name; int *value; shopt_set_func_t *set_func; } shopt_vars[] = { { "cdable_vars", &cdable_vars, (shopt_set_func_t *)NULL }, { "cdspell", &cdspelling, (shopt_set_func_t *)NULL }, { "checkhash", &check_hashed_filenames, (shopt_set_func_t *)NULL }, { "checkwinsize", &check_window_size, (shopt_set_func_t *)NULL }, #if defined (HISTORY) { "cmdhist", &command_oriented_history, (shopt_set_func_t *)NULL }, #endif { "dotglob", &glob_dot_filenames, (shopt_set_func_t *)NULL }, { "execfail", &no_exit_on_failed_exec, (shopt_set_func_t *)NULL }, { "expand_aliases", &expand_aliases, (shopt_set_func_t *)NULL }, #if defined (EXTENDED_GLOB) { "extglob", &extended_glob, (shopt_set_func_t *)NULL }, #endif #if defined (READLINE) { "histreedit", &history_reediting, (shopt_set_func_t *)NULL }, #endif #if defined (HISTORY) { "histappend", &force_append_history, (shopt_set_func_t *)NULL }, #endif #if defined (READLINE) { "histverify", &hist_verify, (shopt_set_func_t *)NULL }, { "hostcomplete", &perform_hostname_completion, enable_hostname_completion }, #endif { "huponexit", &hup_on_exit, (shopt_set_func_t *)NULL }, { "interactive_comments", &interactive_comments, set_interactive_comments }, #if defined (HISTORY) { "lithist", &literal_history, (shopt_set_func_t *)NULL }, #endif { "login_shell", &shopt_login_shell, set_login_shell }, { "mailwarn", &mail_warning, (shopt_set_func_t *)NULL }, #if defined (READLINE) { "no_empty_cmd_completion", &no_empty_command_completion, (shopt_set_func_t *)NULL }, #endif { "nocaseglob", &glob_ignore_case, (shopt_set_func_t *)NULL }, { "nullglob", &allow_null_glob_expansion, (shopt_set_func_t *)NULL }, #if defined (PROGRAMMABLE_COMPLETION) { "progcomp", &prog_completion_enabled, (shopt_set_func_t *)NULL }, #endif { "promptvars", &promptvars, (shopt_set_func_t *)NULL }, #if defined (RESTRICTED_SHELL) { "restricted_shell", &restricted_shell, set_restricted_shell }, #endif { "shift_verbose", &print_shift_error, (shopt_set_func_t *)NULL }, { "sourcepath", &source_uses_path, (shopt_set_func_t *)NULL }, { "xpg_echo", &xpg_echo, (shopt_set_func_t *)NULL }, { (char *)0, (int *)0, (shopt_set_func_t *)NULL } }; static char *on = "on"; static char *off = "off"; static int find_shopt __P((char *)); static int toggle_shopts __P((int, WORD_LIST *, int)); static void print_shopt __P((char *, int, int)); static int list_shopts __P((WORD_LIST *, int)); static int list_some_shopts __P((int, int)); static int list_shopt_o_options __P((WORD_LIST *, int)); static int list_some_o_options __P((int, int)); static int set_shopt_o_options __P((int, WORD_LIST *, int)); #define SFLAG 0x01 #define UFLAG 0x02 #define QFLAG 0x04 #define OFLAG 0x08 #define PFLAG 0x10 int shopt_builtin (list) WORD_LIST *list; { int opt, flags, rval; flags = 0; reset_internal_getopt (); while ((opt = internal_getopt (list, "psuoq")) != -1) { switch (opt) { case 's': flags |= SFLAG; break; case 'u': flags |= UFLAG; break; case 'q': flags |= QFLAG; break; case 'o': flags |= OFLAG; break; case 'p': flags |= PFLAG; break; default: builtin_usage (); return (EX_USAGE); } } list = loptend; if ((flags & (SFLAG|UFLAG)) == (SFLAG|UFLAG)) { builtin_error ("cannot set and unset shell options simultaneously"); return (EXECUTION_FAILURE); } rval = EXECUTION_SUCCESS; if ((flags & OFLAG) && ((flags & (SFLAG|UFLAG)) == 0)) /* shopt -o */ rval = list_shopt_o_options (list, flags); else if (list && (flags & OFLAG)) /* shopt -so args */ rval = set_shopt_o_options ((flags & SFLAG) ? FLAG_ON : FLAG_OFF, list, flags & QFLAG); else if (flags & OFLAG) /* shopt -so */ rval = list_some_o_options ((flags & SFLAG) ? 1 : 0, flags); else if (list && (flags & (SFLAG|UFLAG))) /* shopt -su args */ rval = toggle_shopts ((flags & SFLAG) ? SETOPT : UNSETOPT, list, flags & QFLAG); else if ((flags & (SFLAG|UFLAG)) == 0) /* shopt [args] */ rval = list_shopts (list, flags); else /* shopt -su */ rval = list_some_shopts ((flags & SFLAG) ? SETOPT : UNSETOPT, flags); return (rval); } /* Reset the options managed by `shopt' to the values they would have at shell startup. */ void reset_shopt_options () { allow_null_glob_expansion = glob_dot_filenames = 0; cdable_vars = mail_warning = 0; no_exit_on_failed_exec = print_shift_error = 0; check_hashed_filenames = cdspelling = expand_aliases = check_window_size = 0; source_uses_path = promptvars = 1; #if defined (EXTENDED_GLOB) extended_glob = 0; #endif #if defined (HISTORY) literal_history = force_append_history = 0; command_oriented_history = 1; #endif #if defined (READLINE) hist_verify = history_reediting = 0; perform_hostname_completion = 1; #endif shopt_login_shell = login_shell; } static int find_shopt (name) char *name; { int i; for (i = 0; shopt_vars[i].name; i++) if (STREQ (name, shopt_vars[i].name)) return i; return -1; } static void shopt_error (s) char *s; { builtin_error ("%s: invalid shell option name", s); } static int toggle_shopts (mode, list, quiet) int mode; WORD_LIST *list; int quiet; { WORD_LIST *l; int ind, rval; for (l = list, rval = EXECUTION_SUCCESS; l; l = l->next) { ind = find_shopt (l->word->word); if (ind < 0) { shopt_error (l->word->word); rval = EXECUTION_FAILURE; } else { *shopt_vars[ind].value = mode; /* 1 for set, 0 for unset */ if (shopt_vars[ind].set_func) (*shopt_vars[ind].set_func) (mode); } } return (rval); } static void print_shopt (name, val, flags) char *name; int val, flags; { if (flags & PFLAG) printf ("shopt %s %s\n", val ? "-s" : "-u", name); else printf (OPTFMT, name, val ? on : off); } /* List the values of all or any of the `shopt' options. Returns 0 if all were listed or all variables queried were on; 1 otherwise. */ static int list_shopts (list, flags) WORD_LIST *list; int flags; { WORD_LIST *l; int i, val, rval; if (list == 0) { for (i = 0; shopt_vars[i].name; i++) { val = *shopt_vars[i].value; if ((flags & QFLAG) == 0) print_shopt (shopt_vars[i].name, val, flags); } return (EXECUTION_SUCCESS); } for (l = list, rval = EXECUTION_SUCCESS; l; l = l->next) { i = find_shopt (l->word->word); if (i < 0) { shopt_error (l->word->word); rval = EXECUTION_FAILURE; continue; } val = *shopt_vars[i].value; if (val == 0) rval = EXECUTION_FAILURE; if ((flags & QFLAG) == 0) print_shopt (l->word->word, val, flags); } return (rval); } static int list_some_shopts (mode, flags) int mode, flags; { int val, i; for (i = 0; shopt_vars[i].name; i++) { val = *shopt_vars[i].value; if (((flags & QFLAG) == 0) && mode == val) print_shopt (shopt_vars[i].name, val, flags); } return (EXECUTION_SUCCESS); } static int list_shopt_o_options (list, flags) WORD_LIST *list; int flags; { WORD_LIST *l; int val, rval; if (list == 0) { if ((flags & QFLAG) == 0) list_minus_o_opts (-1, (flags & PFLAG)); return (EXECUTION_SUCCESS); } for (l = list, rval = EXECUTION_SUCCESS; l; l = l->next) { val = minus_o_option_value (l->word->word); if (val == -1) { sh_invalidoptname (l->word->word); rval = EXECUTION_FAILURE; continue; } if (val == 0) rval = EXECUTION_FAILURE; if ((flags & QFLAG) == 0) { if (flags & PFLAG) printf ("set %co %s\n", val ? '-' : '+', l->word->word); else printf (OPTFMT, l->word->word, val ? on : off); } } return (rval); } static int list_some_o_options (mode, flags) int mode, flags; { if ((flags & QFLAG) == 0) list_minus_o_opts (mode, (flags & PFLAG)); return (EXECUTION_SUCCESS); } static int set_shopt_o_options (mode, list, quiet) int mode; WORD_LIST *list; int quiet; { WORD_LIST *l; int rval; for (l = list, rval = EXECUTION_SUCCESS; l; l = l->next) { if (set_minus_o_option (mode, l->word->word) == EXECUTION_FAILURE) rval = EXECUTION_FAILURE; } set_shellopts (); return rval; } /* If we set or unset interactive_comments with shopt, make sure the change is reflected in $SHELLOPTS. */ static int set_interactive_comments (mode) int mode; { set_shellopts (); return (0); } #if defined (RESTRICTED_SHELL) /* Don't allow the value of restricted_shell to be modified. */ static int set_restricted_shell (mode) int mode; { static int save_restricted = -1; if (save_restricted == -1) save_restricted = shell_is_restricted (shell_name); restricted_shell = save_restricted; return (0); } #endif /* RESTRICTED_SHELL */ /* Not static so shell.c can call it to initialize shopt_login_shell */ int set_login_shell (mode) int mode; { shopt_login_shell = login_shell != 0; return (0); } char ** get_shopt_options () { char **ret; int n, i; n = sizeof (shopt_vars) / sizeof (shopt_vars[0]); ret = strvec_create (n + 1); for (i = 0; shopt_vars[i].name; i++) ret[i] = savestring (shopt_vars[i].name); ret[i] = (char *)NULL; return ret; } /* * External interface for other parts of the shell. NAME is a string option; * MODE is 0 if we want to unset an option; 1 if we want to set an option. * REUSABLE is 1 if we want to print output in a form that may be reused. */ int shopt_setopt (name, mode) char *name; int mode; { WORD_LIST *wl; int r; wl = add_string_to_list (name, (WORD_LIST *)NULL); r = toggle_shopts (mode, wl, 0); dispose_words (wl); return r; } int shopt_listopt (name, reusable) char *name; int reusable; { int i; if (name == 0) return (list_shopts ((WORD_LIST *)NULL, reusable ? PFLAG : 0)); i = find_shopt (name); if (i < 0) { shopt_error (name); return (EXECUTION_FAILURE); } print_shopt (name, *shopt_vars[i].value, reusable ? PFLAG : 0); return (EXECUTION_SUCCESS); }
This file is printf.def, from which is created printf.c. It implements the builtin "printf" in Bash. Copyright (C) 1997-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA $PRODUCES printf.c $BUILTIN printf $FUNCTION printf_builtin $SHORT_DOC printf format [arguments] printf formats and prints ARGUMENTS under control of the FORMAT. FORMAT is a character string which contains three types of objects: plain characters, which are simply copied to standard output, character escape sequences which are converted and copied to the standard output, and format specifications, each of which causes printing of the next successive argument. In addition to the standard printf(1) formats, %b means to expand backslash escape sequences in the corresponding argument, and %q means to quote the argument in a way that can be reused as shell input. $END #include <config.h> #include "../bashtypes.h" #include <errno.h> #if defined (HAVE_LIMITS_H) # include <limits.h> #else /* Assume 32-bit ints. */ # define INT_MAX 2147483647 # define INT_MIN (-2147483647-1) #endif #include <stdio.h> #include <chartypes.h> #ifdef HAVE_INTTYPES_H # include <inttypes.h> #endif #include "../bashansi.h" #include "../shell.h" #include "stdc.h" #include "bashgetopt.h" #include "common.h" #if !defined (PRIdMAX) # if HAVE_LONG_LONG # define PRIdMAX "lld" # else # define PRIdMAX "ld" # endif #endif #if !defined (errno) extern int errno; #endif #define PF(f, func) \ do { \ if (have_fieldwidth && have_precision) \ tw += printf(f, fieldwidth, precision, func); \ else if (have_fieldwidth) \ tw += printf(f, fieldwidth, func); \ else if (have_precision) \ tw += printf(f, precision, func); \ else \ tw += printf(f, func); \ } while (0) /* We free the buffer used by mklong() if it's `too big'. */ #define PRETURN(value) \ do \ { \ if (conv_bufsize > 4096 ) \ { \ free(conv_buf); \ conv_bufsize = 0; \ conv_buf = 0; \ } \ fflush (stdout); \ return (value); \ } \ while (0) #define SKIP1 "#'-+ 0" #define LENMODS "hjlLtz" static void printf_erange __P((char *)); static void printstr __P((char *, char *, int, int, int)); static int tescape __P((char *, int, char *, int *)); static char *bexpand __P((char *, int, int *, int *)); static char *mklong __P((char *, char *, size_t)); static int getchr __P((void)); static char *getstr __P((void)); static int getint __P((void)); static intmax_t getintmax __P((void)); static uintmax_t getuintmax __P((void)); #if defined (HAVE_LONG_DOUBLE) && HAVE_DECL_STRTOLD typedef long double floatmax_t; # define FLOATMAX_CONV "L" # define strtofltmax strtold #else typedef double floatmax_t; # define FLOATMAX_CONV "" # define strtofltmax strtod #endif static floatmax_t getfloatmax __P((void)); static int asciicode __P((void)); static WORD_LIST *garglist; static int retval; static int conversion_error; static char *conv_buf; static size_t conv_bufsize; int printf_builtin (list) WORD_LIST *list; { int ch, fieldwidth, precision; int have_fieldwidth, have_precision; intmax_t tw; char convch, thisch, nextch, *format, *modstart, *fmt, *start; conversion_error = 0; retval = EXECUTION_SUCCESS; if (no_options (list)) return (EX_USAGE); list = loptend; /* skip over possible `--' */ if (list == 0) { builtin_usage (); return (EX_USAGE); } if (list->word->word == 0 || list->word->word[0] == '\0') return (EXECUTION_SUCCESS); format = list->word->word; garglist = list->next; /* If the format string is empty after preprocessing, return immediately. */ if (format == 0 || *format == 0) return (EXECUTION_SUCCESS); /* Basic algorithm is to scan the format string for conversion specifications -- once one is found, find out if the field width or precision is a '*'; if it is, gather up value. Note, format strings are reused as necessary to use up the provided arguments, arguments of zero/null string are provided to use up the format string. */ do { tw = 0; /* find next format specification */ for (fmt = format; *fmt; fmt++) { precision = fieldwidth = 0; have_fieldwidth = have_precision = 0; if (*fmt == '\\') { fmt++; /* A NULL fourth argument to tescape means to not do special processing for \c. */ fmt += tescape (fmt, 1, &nextch, (int *)NULL); putchar (nextch); fmt--; /* for loop will increment it for us again */ continue; } if (*fmt != '%') { putchar (*fmt); continue; } /* ASSERT(*fmt == '%') */ start = fmt++; if (*fmt == '%') /* %% prints a % */ { putchar ('%'); continue; } /* found format specification, skip to field width */ for (; *fmt && strchr(SKIP1, *fmt); ++fmt) ; /* Skip optional field width. */ if (*fmt == '*') { fmt++; have_fieldwidth = 1; fieldwidth = getint (); } else while (DIGIT (*fmt)) fmt++; /* Skip optional '.' and precision */ if (*fmt == '.') { ++fmt; if (*fmt == '*') { fmt++; have_precision = 1; precision = getint (); } else while (DIGIT (*fmt)) fmt++; } /* skip possible format modifiers */ modstart = fmt; while (*fmt && strchr (LENMODS, *fmt)) fmt++; if (*fmt == 0) { builtin_error ("`%s': missing format character", start); PRETURN (EXECUTION_FAILURE); } convch = *fmt; thisch = modstart[0]; nextch = modstart[1]; modstart[0] = convch; modstart[1] = '\0'; switch(convch) { case 'c': { char p; p = getchr (); PF(start, p); break; } case 's': { char *p; p = getstr (); PF(start, p); break; } case 'n': { char *var; var = getstr (); if (var && *var) { if (legal_identifier (var)) bind_var_to_int (var, tw); else { sh_invalidid (var); PRETURN (EXECUTION_FAILURE); } } break; } case 'b': /* expand escapes in argument */ { char *p, *xp; int rlen; p = getstr (); ch = rlen = 0; xp = bexpand (p, strlen (p), &ch, &rlen); if (xp) { /* Have to use printstr because of possible NUL bytes in XP -- printf does not handle that well. */ printstr (start, xp, rlen, fieldwidth, precision); free (xp); } if (ch) PRETURN (retval); break; } case 'q': /* print with shell quoting */ { char *p, *xp; p = getstr (); if (ansic_shouldquote (p)) xp = ansic_quote (p, 0, (int *)0); else xp = sh_backslash_quote (p); if (xp) { /* Use printstr to get fieldwidth and precision right. */ printstr (start, xp, strlen (xp), fieldwidth, precision); free (xp); } break; } case 'd': case 'i': { char *f; long p; intmax_t pp; p = pp = getintmax (); if (p != pp) { f = mklong (start, PRIdMAX, sizeof (PRIdMAX) - 2); PF (f, pp); } else { /* Optimize the common case where the integer fits in "long". This also works around some long long and/or intmax_t library bugs in the common case, e.g. glibc 2.2 x86. */ f = mklong (start, "l", 1); PF (f, p); } break; } case 'o': case 'u': case 'x': case 'X': { char *f; unsigned long p; uintmax_t pp; p = pp = getuintmax (); if (p != pp) { f = mklong (start, PRIdMAX, sizeof (PRIdMAX) - 2); PF (f, pp); } else { f = mklong (start, "l", 1); PF (f, p); } break; } case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': #if defined (HAVE_PRINTF_A_FORMAT) case 'a': case 'A': #endif { char *f; floatmax_t p; p = getfloatmax (); f = mklong (start, FLOATMAX_CONV, sizeof(FLOATMAX_CONV) - 1); PF (f, p); break; } /* We don't output unrecognized format characters; we print an error message and return a failure exit status. */ default: builtin_error ("`%c': invalid format character", convch); PRETURN (EXECUTION_FAILURE); } modstart[0] = thisch; modstart[1] = nextch; } } while (garglist && garglist != list->next); if (conversion_error) retval = EXECUTION_FAILURE; PRETURN (retval); } static void printf_erange (s) char *s; { builtin_error ("warning: %s: %s", s, strerror(ERANGE)); } /* We duplicate a lot of what printf(3) does here. */ static void printstr (fmt, string, len, fieldwidth, precision) char *fmt; /* format */ char *string; /* expanded string argument */ int len; /* length of expanded string */ int fieldwidth; /* argument for width of `*' */ int precision; /* argument for precision of `*' */ { #if 0 char *s; #endif int padlen, nc, ljust, i; int fw, pr; /* fieldwidth and precision */ if (string == 0 || *string == '\0') return; #if 0 s = fmt; #endif if (*fmt == '%') fmt++; ljust = fw = 0; pr = -1; /* skip flags */ while (strchr (SKIP1, *fmt)) { if (*fmt == '-') ljust = 1; fmt++; } /* get fieldwidth, if present */ if (*fmt == '*') { fmt++; fw = fieldwidth; if (fw < 0) { fw = -fw; ljust = 1; } } else if (DIGIT (*fmt)) { fw = *fmt++ - '0'; while (DIGIT (*fmt)) fw = (fw * 10) + (*fmt++ - '0'); } /* get precision, if present */ if (*fmt == '.') { fmt++; if (*fmt == '*') { fmt++; pr = precision; } else if (DIGIT (*fmt)) { pr = *fmt++ - '0'; while (DIGIT (*fmt)) pr = (pr * 10) + (*fmt++ - '0'); } } #if 0 /* If we remove this, get rid of `s'. */ if (*fmt != 'b' && *fmt != 'q') { internal_error ("format parsing problem: %s", s); fw = pr = 0; } #endif /* chars from string to print */ nc = (pr >= 0 && pr <= len) ? pr : len; padlen = fw - nc; if (padlen < 0) padlen = 0; if (ljust) padlen = -padlen; /* leading pad characters */ for (; padlen > 0; padlen--) putchar (' '); /* output NC characters from STRING */ for (i = 0; i < nc; i++) putchar (string[i]); /* output any necessary trailing padding */ for (; padlen < 0; padlen++) putchar (' '); } /* Convert STRING by expanding the escape sequences specified by the POSIX standard for printf's `%b' format string. If SAWC is non-null, recognize `\c' and use that as a string terminator. If we see \c, set *SAWC to 1 before returning. LEN is the length of STRING. */ /* Translate a single backslash-escape sequence starting at ESTART (the character after the backslash) and return the number of characters consumed by the sequence. CP is the place to return the translated value. *SAWC is set to 1 if the escape sequence was \c, since that means to short-circuit the rest of the processing. If SAWC is null, we don't do the \c short-circuiting, and \c is treated as an unrecognized escape sequence. */ static int tescape (estart, trans_squote, cp, sawc) char *estart; int trans_squote; char *cp; int *sawc; { register char *p; int temp, c, evalue; p = estart; switch (c = *p++) { #if defined (__STDC__) case 'a': *cp = '\a'; break; #else case 'a': *cp = '\007'; break; #endif case 'b': *cp = '\b'; break; case 'e': case 'E': *cp = '\033'; break; /* ESC -- non-ANSI */ case 'f': *cp = '\f'; break; case 'n': *cp = '\n'; break; case 'r': *cp = '\r'; break; case 't': *cp = '\t'; break; case 'v': *cp = '\v'; break; /* %b octal constants are `\0' followed by one, two, or three octal digits... */ case '0': /* but, as an extension, the other echo-like octal escape sequences are supported as well. */ case '1': case '2': case '3': case '4': case '5': case '6': case '7': for (temp = 2+(c=='0'), evalue = c - '0'; ISOCTAL (*p) && temp--; p++) evalue = (evalue * 8) + OCTVALUE (*p); *cp = evalue & 0xFF; break; /* And, as another extension, we allow \xNNN, where each N is a hex digit. */ case 'x': for (temp = 2, evalue = 0; ISXDIGIT ((unsigned char)*p) && temp--; p++) evalue = (evalue * 16) + HEXVALUE (*p); if (temp == 2) { builtin_error ("missing hex digit for \\x"); *cp = '\\'; return 0; } *cp = evalue & 0xFF; break; case '\\': /* \\ -> \ */ *cp = c; break; case '\'': /* TRANS_SQUOTE != 0 means \' -> ' */ if (trans_squote) *cp = c; else { *cp = '\\'; return 0; } break; case 'c': if (sawc) { *sawc = 1; break; } /* other backslash escapes are passed through unaltered */ default: *cp = '\\'; return 0; } return (p - estart); } static char * bexpand (string, len, sawc, lenp) char *string; int len, *sawc, *lenp; { int temp; char *ret, *r, *s, c; if (string == 0 || *string == '\0') { if (sawc) *sawc = 0; if (lenp) *lenp = 0; return ((char *)NULL); } ret = (char *)xmalloc (len + 1); for (r = ret, s = string; s && *s; ) { c = *s++; if (c != '\\' || *s == '\0') { *r++ = c; continue; } temp = 0; s += tescape (s, 0, &c, &temp); if (temp) { if (sawc) *sawc = 1; break; } *r++ = c; } *r = '\0'; if (lenp) *lenp = r - ret; return ret; } static char * mklong (str, modifiers, mlen) char *str; char *modifiers; size_t mlen; { size_t len, slen; slen = strlen (str); len = slen + mlen + 1; if (len > conv_bufsize) { conv_bufsize = (((len + 1023) >> 10) << 10); conv_buf = (char *)xrealloc (conv_buf, conv_bufsize); } FASTCOPY (str, conv_buf, slen - 1); FASTCOPY (modifiers, conv_buf + slen - 1, mlen); conv_buf[len - 2] = str[slen - 1]; conv_buf[len - 1] = '\0'; return (conv_buf); } static int getchr () { int ret; if (garglist == 0) return ('\0'); ret = (int)garglist->word->word[0]; garglist = garglist->next; return ret; } static char * getstr () { char *ret; if (garglist == 0) return (""); ret = garglist->word->word; garglist = garglist->next; return ret; } static int getint () { intmax_t ret; ret = getintmax (); if (ret > INT_MAX) { printf_erange (garglist->word->word); ret = INT_MAX; } else if (ret < INT_MIN) { printf_erange (garglist->word->word); ret = INT_MIN; } return ((int)ret); } static intmax_t getintmax () { intmax_t ret; char *ep; if (garglist == 0) return (0); if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"') return asciicode (); errno = 0; ret = strtoimax (garglist->word->word, &ep, 0); if (*ep) { sh_invalidnum (garglist->word->word); /* POSIX.2 says ``...a diagnostic message shall be written to standard error, and the utility shall not exit with a zero exit status, but shall continue processing any remaining operands and shall write the value accumulated at the time the error was detected to standard output.'' Yecch. */ ret = 0; conversion_error = 1; } else if (errno == ERANGE) printf_erange (garglist->word->word); garglist = garglist->next; return (ret); } static uintmax_t getuintmax () { uintmax_t ret; char *ep; if (garglist == 0) return (0); if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"') return asciicode (); errno = 0; ret = strtoumax (garglist->word->word, &ep, 0); if (*ep) { sh_invalidnum (garglist->word->word); /* Same POSIX.2 conversion error requirements as getintmax(). */ ret = 0; conversion_error = 1; } else if (errno == ERANGE) printf_erange (garglist->word->word); garglist = garglist->next; return (ret); } static floatmax_t getfloatmax () { floatmax_t ret; char *ep; if (garglist == 0) return (0); if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"') return asciicode (); errno = 0; ret = strtofltmax (garglist->word->word, &ep); if (*ep) { sh_invalidnum (garglist->word->word); /* Same thing about POSIX.2 conversion error requirements. */ ret = 0; conversion_error = 1; } else if (errno == ERANGE) printf_erange (garglist->word->word); garglist = garglist->next; return (ret); } /* NO check is needed for garglist here. */ static int asciicode () { register int ch; ch = garglist->word->word[1]; garglist = garglist->next; return (ch); }
/* bashtypes.h -- Bash system types. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_BASHTYPES_H_) # define _BASHTYPES_H_ #if defined (CRAY) # define word __word #endif #include <sys/types.h> #if defined (CRAY) # undef word #endif #if defined (HAVE_INTTYPES_H) # include <inttypes.h> #endif #endif /* _BASHTYPES_H_ */
/* posixstat.h -- Posix stat(2) definitions for systems that don't have them. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ /* This file should be included instead of <sys/stat.h>. It relies on the local sys/stat.h to work though. */ #if !defined (_POSIXSTAT_H_) #define _POSIXSTAT_H_ #include <sys/stat.h> #if defined (STAT_MACROS_BROKEN) # undef S_ISBLK # undef S_ISCHR # undef S_ISDIR # undef S_ISFIFO # undef S_ISREG # undef S_ISLNK #endif /* STAT_MACROS_BROKEN */ /* These are guaranteed to work only on isc386 */ #if !defined (S_IFDIR) && !defined (S_ISDIR) # define S_IFDIR 0040000 #endif /* !S_IFDIR && !S_ISDIR */ #if !defined (S_IFMT) # define S_IFMT 0170000 #endif /* !S_IFMT */ /* Posix 1003.1 5.6.1.1 <sys/stat.h> file types */ /* Some Posix-wannabe systems define _S_IF* macros instead of S_IF*, but do not provide the S_IS* macros that Posix requires. */ #if defined (_S_IFMT) && !defined (S_IFMT) #define S_IFMT _S_IFMT #endif #if defined (_S_IFIFO) && !defined (S_IFIFO) #define S_IFIFO _S_IFIFO #endif #if defined (_S_IFCHR) && !defined (S_IFCHR) #define S_IFCHR _S_IFCHR #endif #if defined (_S_IFDIR) && !defined (S_IFDIR) #define S_IFDIR _S_IFDIR #endif #if defined (_S_IFBLK) && !defined (S_IFBLK) #define S_IFBLK _S_IFBLK #endif #if defined (_S_IFREG) && !defined (S_IFREG) #define S_IFREG _S_IFREG #endif #if defined (_S_IFLNK) && !defined (S_IFLNK) #define S_IFLNK _S_IFLNK #endif #if defined (_S_IFSOCK) && !defined (S_IFSOCK) #define S_IFSOCK _S_IFSOCK #endif /* Test for each symbol individually and define the ones necessary (some systems claiming Posix compatibility define some but not all). */ #if defined (S_IFBLK) && !defined (S_ISBLK) #define S_ISBLK(m) (((m)&S_IFMT) == S_IFBLK) /* block device */ #endif #if defined (S_IFCHR) && !defined (S_ISCHR) #define S_ISCHR(m) (((m)&S_IFMT) == S_IFCHR) /* character device */ #endif #if defined (S_IFDIR) && !defined (S_ISDIR) #define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR) /* directory */ #endif #if defined (S_IFREG) && !defined (S_ISREG) #define S_ISREG(m) (((m)&S_IFMT) == S_IFREG) /* file */ #endif #if defined (S_IFIFO) && !defined (S_ISFIFO) #define S_ISFIFO(m) (((m)&S_IFMT) == S_IFIFO) /* fifo - named pipe */ #endif #if defined (S_IFLNK) && !defined (S_ISLNK) #define S_ISLNK(m) (((m)&S_IFMT) == S_IFLNK) /* symbolic link */ #endif #if defined (S_IFSOCK) && !defined (S_ISSOCK) #define S_ISSOCK(m) (((m)&S_IFMT) == S_IFSOCK) /* socket */ #endif /* * POSIX 1003.1 5.6.1.2 <sys/stat.h> File Modes */ #if !defined (S_IRWXU) # if !defined (S_IREAD) # define S_IREAD 00400 # define S_IWRITE 00200 # define S_IEXEC 00100 # endif /* S_IREAD */ # if !defined (S_IRUSR) # define S_IRUSR S_IREAD /* read, owner */ # define S_IWUSR S_IWRITE /* write, owner */ # define S_IXUSR S_IEXEC /* execute, owner */ # define S_IRGRP (S_IREAD >> 3) /* read, group */ # define S_IWGRP (S_IWRITE >> 3) /* write, group */ # define S_IXGRP (S_IEXEC >> 3) /* execute, group */ # define S_IROTH (S_IREAD >> 6) /* read, other */ # define S_IWOTH (S_IWRITE >> 6) /* write, other */ # define S_IXOTH (S_IEXEC >> 6) /* execute, other */ # endif /* !S_IRUSR */ # define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) # define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP) # define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH) #endif /* !S_IRWXU */ /* These are non-standard, but are used in builtins.c$symbolic_umask() */ #define S_IRUGO (S_IRUSR | S_IRGRP | S_IROTH) #define S_IWUGO (S_IWUSR | S_IWGRP | S_IWOTH) #define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH) #endif /* _POSIXSTAT_H_ */
/* filecntl.h - Definitions to set file descriptors to close-on-exec. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_FILECNTL_H_) #define _FILECNTL_H_ #include <fcntl.h> /* Definitions to set file descriptors to close-on-exec, the Posix way. */ #if !defined (FD_CLOEXEC) #define FD_CLOEXEC 1 #endif #define FD_NCLOEXEC 0 #define SET_CLOSE_ON_EXEC(fd) (fcntl ((fd), F_SETFD, FD_CLOEXEC)) #define SET_OPEN_ON_EXEC(fd) (fcntl ((fd), F_SETFD, FD_NCLOEXEC)) /* How to open a file in non-blocking mode, the Posix.1 way. */ #if !defined (O_NONBLOCK) # if defined (O_NDELAY) # define O_NONBLOCK O_NDELAY # else # define O_NONBLOCK 0 # endif #endif #endif /* ! _FILECNTL_H_ */
/* chartypes.h -- extend ctype.h */ /* Copyright (C) 2001 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #ifndef _SH_CHARTYPES_H #define _SH_CHARTYPES_H #include <ctype.h> /* Jim Meyering writes: "... Some ctype macros are valid only for character codes that isascii says are ASCII (SGI's IRIX-4.0.5 is one such system --when using /bin/cc or gcc but without giving an ansi option). So, all ctype uses should be through macros like ISPRINT... If STDC_HEADERS is defined, then autoconf has verified that the ctype macros don't need to be guarded with references to isascii. ... Defining IN_CTYPE_DOMAIN to 1 should let any compiler worth its salt eliminate the && through constant folding." Solaris defines some of these symbols so we must undefine them first. */ #if defined STDC_HEADERS || (!defined isascii && !defined HAVE_ISASCII) # define IN_CTYPE_DOMAIN(c) 1 #else # define IN_CTYPE_DOMAIN(c) isascii(c) #endif #if !defined (isspace) && !defined (HAVE_ISSPACE) # define isspace(c) ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\f') #endif #if !defined (isprint) && !defined (HAVE_ISPRINT) # define isprint(c) (isalpha(c) || isdigit(c) || ispunct(c)) #endif #if defined (isblank) || defined (HAVE_ISBLANK) # define ISBLANK(c) (IN_CTYPE_DOMAIN (c) && isblank (c)) #else # define ISBLANK(c) ((c) == ' ' || (c) == '\t') #endif #if defined (isgraph) || defined (HAVE_ISGRAPH) # define ISGRAPH(c) (IN_CTYPE_DOMAIN (c) && isgraph (c)) #else # define ISGRAPH(c) (IN_CTYPE_DOMAIN (c) && isprint (c) && !isspace (c)) #endif #if !defined (isxdigit) && !defined (HAVE_ISXDIGIT) # define isxdigit(c) (((c) >= '0' && (c) <= '9') || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F')) #endif #undef ISPRINT #define ISPRINT(c) (IN_CTYPE_DOMAIN (c) && isprint (c)) #define ISDIGIT(c) (IN_CTYPE_DOMAIN (c) && isdigit (c)) #define ISALNUM(c) (IN_CTYPE_DOMAIN (c) && isalnum (c)) #define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c)) #define ISCNTRL(c) (IN_CTYPE_DOMAIN (c) && iscntrl (c)) #define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c)) #define ISPUNCT(c) (IN_CTYPE_DOMAIN (c) && ispunct (c)) #define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c)) #define ISUPPER(c) (IN_CTYPE_DOMAIN (c) && isupper (c)) #define ISXDIGIT(c) (IN_CTYPE_DOMAIN (c) && isxdigit (c)) #define ISLETTER(c) (ISALPHA(c)) #define DIGIT(c) ((c) >= '0' && (c) <= '9') #define ISWORD(c) (ISLETTER(c) || DIGIT(c) || ((c) == '_')) #define HEXVALUE(c) \ (((c) >= 'a' && (c) <= 'f') \ ? (c)-'a'+10 \ : (c) >= 'A' && (c) <= 'F' ? (c)-'A'+10 : (c)-'0') #ifndef ISOCTAL # define ISOCTAL(c) ((c) >= '0' && (c) <= '7') #endif #define OCTVALUE(c) ((c) - '0') #define TODIGIT(c) ((c) - '0') #define TOCHAR(c) ((c) + '0') #define TOLOWER(c) (ISUPPER(c) ? tolower(c) : (c)) #define TOUPPER(c) (ISLOWER(c) ? toupper(c) : (c)) #ifndef TOCTRL /* letter to control char -- ASCII. The TOUPPER is in there so \ce and \cE will map to the same character in $'...' expansions. */ # define TOCTRL(x) (TOUPPER(x) & 037) #endif #ifndef UNCTRL /* control char to letter -- ASCII */ # define UNCTRL(x) (TOUPPER((x) | 0x40)) #endif #endif /* _SH_CHARTYPES_H */
/* bashansi.h -- Typically included information required by picky compilers. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_BASHANSI_H_) #define _BASHANSI_H_ #if defined (HAVE_STRING_H) # if ! defined (STDC_HEADERS) && defined (HAVE_MEMORY_H) # include <memory.h> # endif # include <string.h> #endif /* !HAVE_STRING_H */ #if defined (HAVE_STRINGS_H) # include <strings.h> #endif /* !HAVE_STRINGS_H */ #if defined (HAVE_STDLIB_H) # include <stdlib.h> #else # include "ansi_stdlib.h" #endif /* !HAVE_STDLIB_H */ #endif /* !_BASHANSI_H_ */
/* shell.h -- The data structures used by the shell */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "bashjmp.h" #include "command.h" #include "syntax.h" #include "general.h" #include "error.h" #include "variables.h" #include "arrayfunc.h" #include "quit.h" #include "maxpath.h" #include "unwind_prot.h" #include "dispose_cmd.h" #include "make_cmd.h" #include "ocache.h" #include "subst.h" #include "sig.h" #include "pathnames.h" #include "externs.h" #include "version.h" extern int EOF_Reached; #define NO_PIPE -1 #define REDIRECT_BOTH -2 #define NO_VARIABLE -1 /* Values that can be returned by execute_command (). */ #define EXECUTION_FAILURE 1 #define EXECUTION_SUCCESS 0 /* Usage messages by builtins result in a return status of 2. */ #define EX_BADUSAGE 2 /* Special exit statuses used by the shell, internally and externally. */ #define EX_BINARY_FILE 126 #define EX_NOEXEC 126 #define EX_NOINPUT 126 #define EX_NOTFOUND 127 #define EX_SHERRBASE 256 /* all special error values are > this. */ #define EX_BADSYNTAX 257 /* shell syntax error */ #define EX_USAGE 258 /* syntax error in usage */ #define EX_REDIRFAIL 259 /* redirection failed */ #define EX_BADASSIGN 260 /* variable assignment error */ #define EX_EXPFAIL 261 /* word expansion failed */ /* Flag values that control parameter pattern substitution. */ #define MATCH_ANY 0x0 #define MATCH_BEG 0x1 #define MATCH_END 0x2 #define MATCH_TYPEMASK 0x3 #define MATCH_GLOBREP 0x10 #define MATCH_QUOTED 0x20 /* Some needed external declarations. */ extern char **shell_environment; extern WORD_LIST *rest_of_args; /* Generalized global variables. */ extern int executing, login_shell; extern int interactive, interactive_shell; extern int startup_state; /* Structure to pass around that holds a bitmap of file descriptors to close, and the size of that structure. Used in execute_cmd.c. */ struct fd_bitmap { int size; char *bitmap; }; #define FD_BITMAP_SIZE 32 #define CTLESC '\001' #define CTLNUL '\177' /* Information about the current user. */ struct user_info { uid_t uid, euid; gid_t gid, egid; char *user_name; char *shell; /* shell from the password file */ char *home_dir; }; extern struct user_info current_user; /* Force gcc to not clobber X on a longjmp(). Old versions of gcc mangle this badly. */ #if (__GNUC__ > 2) || (__GNUC__ == 2 && __GNUC_MINOR__ > 8) # define USE_VAR(x) ((void) &(x)) #else # define USE_VAR(x) #endif
/* stdc.h -- macros to make source compile on both ANSI C and K&R C compilers. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_STDC_H_) #define _STDC_H_ /* Adapted from BSD /usr/include/sys/cdefs.h. */ /* A function can be defined using prototypes and compile on both ANSI C and traditional C compilers with something like this: extern char *func __P((char *, char *, int)); */ #if !defined (__P) # if defined (__STDC__) || defined (__GNUC__) || defined (__cplusplus) || defined (PROTOTYPES) # define __P(protos) protos # else # define __P(protos) () # endif #endif #if defined (HAVE_STRINGIZE) # define __STRING(x) #x #else # define __STRING(x) "x" #endif #if !defined (__STDC__) #if defined (__GNUC__) /* gcc with -traditional */ # if !defined (signed) # define signed __signed # endif # if !defined (volatile) # define volatile __volatile # endif #else /* !__GNUC__ */ # if !defined (inline) # define inline # endif # if !defined (signed) # define signed # endif # if !defined (volatile) # define volatile # endif #endif /* !__GNUC__ */ #endif /* !__STDC__ */ #ifndef __attribute__ # if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__ # define __attribute__(x) # endif #endif /* For those situations when gcc handles inlining a particular function but other compilers complain. */ #ifdef __GNUC__ # define INLINE inline #else # define INLINE #endif #if defined (PREFER_STDARG) # define SH_VA_START(va, arg) va_start(va, arg) #else # define SH_VA_START(va, arg) va_start(va) #endif #endif /* !_STDC_H_ */
/* bashgetopt.h -- extern declarations for stuff defined in bashgetopt.c. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ /* See getopt.h for the explanation of these variables. */ #if !defined (__BASH_GETOPT_H) # define __BASH_GETOPT_H #include <stdc.h> extern char *list_optarg; extern int list_optopt; extern int list_opttype; extern WORD_LIST *lcurrent; extern WORD_LIST *loptend; extern int internal_getopt __P((WORD_LIST *, char *)); extern void reset_internal_getopt __P((void)); #endif /* !__BASH_GETOPT_H */
/* common.h -- extern declarations for functions defined in common.c. */ /* Copyright (C) 1993-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (__COMMON_H) # define __COMMON_H #include "stdc.h" #define ISOPTION(s, c) (s[0] == '-' && !s[2] && s[1] == c) /* Flag values for parse_and_execute () */ #define SEVAL_NONINT 0x001 #define SEVAL_INTERACT 0x002 #define SEVAL_NOHIST 0x004 #define SEVAL_NOFREE 0x008 /* Flags for describe_command, shared between type.def and command.def */ #define CDESC_ALL 0x001 /* type -a */ #define CDESC_SHORTDESC 0x002 /* command -V */ #define CDESC_REUSABLE 0x004 /* command -v */ #define CDESC_TYPE 0x008 /* type -t */ #define CDESC_PATH_ONLY 0x010 /* type -p */ #define CDESC_FORCE_PATH 0x020 /* type -ap or type -P */ #define CDESC_NOFUNCS 0x040 /* type -f */ /* Flags for get_job_by_name */ #define JM_PREFIX 0x01 /* prefix of job name */ #define JM_SUBSTRING 0x02 /* substring of job name */ #define JM_EXACT 0x04 /* match job name exactly */ #define JM_STOPPED 0x08 /* match stopped jobs only */ #define JM_FIRSTMATCH 0x10 /* return first matching job */ /* Flags for remember_args and value of changed_dollar_vars */ #define ARGS_NONE 0x0 #define ARGS_INVOC 0x01 #define ARGS_FUNC 0x02 #define ARGS_SETBLTIN 0x04 /* Functions from common.c */ extern void builtin_error __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); extern void builtin_usage __P((void)); extern void no_args __P((WORD_LIST *)); extern int no_options __P((WORD_LIST *)); /* common error message functions */ extern void sh_needarg __P((char *)); extern void sh_neednumarg __P((char *)); extern void sh_notfound __P((char *)); extern void sh_invalidopt __P((char *)); extern void sh_invalidoptname __P((char *)); extern void sh_invalidid __P((char *)); extern void sh_invalidnum __P((char *)); extern void sh_invalidsig __P((char *)); extern void sh_erange __P((char *, char *)); extern void sh_badpid __P((char *)); extern void sh_badjob __P((char *)); extern void sh_readonly __P((const char *)); extern void sh_nojobs __P((char *)); extern void sh_restricted __P((char *)); extern char **make_builtin_argv __P((WORD_LIST *, int *)); extern void remember_args __P((WORD_LIST *, int)); extern int dollar_vars_changed __P((void)); extern void set_dollar_vars_unchanged __P((void)); extern void set_dollar_vars_changed __P((void)); extern intmax_t get_numeric_arg __P((WORD_LIST *, int)); extern int get_exitstat __P((WORD_LIST *)); extern int read_octal __P((char *)); /* Keeps track of the current working directory. */ extern char *the_current_working_directory; extern char *get_working_directory __P((char *)); extern void set_working_directory __P((char *)); #if defined (JOB_CONTROL) extern int get_job_by_name __P((const char *, int)); extern int get_job_spec __P((WORD_LIST *)); #endif extern int display_signal_list __P((WORD_LIST *, int)); /* It's OK to declare a function as returning a Function * without providing a definition of what a `Function' is. */ extern struct builtin *builtin_address_internal __P((char *, int)); extern sh_builtin_func_t *find_shell_builtin __P((char *)); extern sh_builtin_func_t *builtin_address __P((char *)); extern sh_builtin_func_t *find_special_builtin __P((char *)); extern void initialize_shell_builtins __P((void)); /* Functions from getopts.def */ extern void getopts_reset __P((int)); /* Functions from set.def */ extern int minus_o_option_value __P((char *)); extern void list_minus_o_opts __P((int, int)); extern char **get_minus_o_opts __P((void)); extern int set_minus_o_option __P((int, char *)); extern void set_shellopts __P((void)); extern void parse_shellopts __P((char *)); extern void initialize_shell_options __P((int)); extern void reset_shell_options __P((void)); /* Functions from shopt.def */ extern void reset_shopt_options __P((void)); extern char **get_shopt_options __P((void)); extern int shopt_setopt __P((char *, int)); extern int shopt_listopt __P((char *, int)); extern int set_login_shell __P((int)); /* Functions from type.def */ extern int describe_command __P((char *, int)); /* Functions from setattr.def */ extern int set_or_show_attributes __P((WORD_LIST *, int, int)); extern int show_var_attributes __P((SHELL_VAR *, int, int)); extern int show_name_attributes __P((char *, int)); extern void set_var_attribute __P((char *, int, int)); /* Functions from pushd.def */ extern char *get_dirstack_from_string __P((char *)); extern char *get_dirstack_element __P((intmax_t, int)); extern void set_dirstack_element __P((intmax_t, int, char *)); extern WORD_LIST *get_directory_stack __P((void)); /* Functions from evalstring.c */ extern int parse_and_execute __P((char *, const char *, int)); extern void parse_and_execute_cleanup __P((void)); /* Functions from evalfile.c */ extern int maybe_execute_file __P((const char *, int)); extern int source_file __P((const char *)); extern int fc_execute_file __P((const char *)); #endif /* !__COMMON_H */
/* Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include <stdio.h> #include <chartypes.h> #include "../bashtypes.h" #include "posixstat.h" #include <signal.h> #include <errno.h> #if defined (PREFER_STDARG) # include <stdarg.h> #else # include <varargs.h> #endif #include "../bashansi.h" #include "../shell.h" #include "maxpath.h" #include "../flags.h" #include "../jobs.h" #include "../builtins.h" #include "../input.h" #include "../execute_cmd.h" #include "../trap.h" #include "bashgetopt.h" #include "common.h" #include "builtext.h" #include <tilde/tilde.h> #if defined (HISTORY) # include "../bashhist.h" #endif #if !defined (errno) extern int errno; #endif /* !errno */ extern int indirection_level, subshell_environment; extern int line_number; extern int last_command_exit_value; extern int running_trap; extern int posixly_correct; extern char *this_command_name, *shell_name; extern char *bash_getcwd_errstr; /* Used by some builtins and the mainline code. */ sh_builtin_func_t *last_shell_builtin = (sh_builtin_func_t *)NULL; sh_builtin_func_t *this_shell_builtin = (sh_builtin_func_t *)NULL; /* **************************************************************** */ /* */ /* Error reporting, usage, and option processing */ /* */ /* **************************************************************** */ /* This is a lot like report_error (), but it is for shell builtins instead of shell control structures, and it won't ever exit the shell. */ void #if defined (PREFER_STDARG) builtin_error (const char *format, ...) #else builtin_error (format, va_alist) const char *format; va_dcl #endif { va_list args; char *name; name = get_name_for_error (); fprintf (stderr, "%s: ", name); if (interactive_shell == 0) fprintf (stderr, "line %d: ", executing_line_number ()); if (this_command_name && *this_command_name) fprintf (stderr, "%s: ", this_command_name); SH_VA_START (args, format); vfprintf (stderr, format, args); va_end (args); fprintf (stderr, "\n"); } /* Print a usage summary for the currently-executing builtin command. */ void builtin_usage () { if (this_command_name && *this_command_name) fprintf (stderr, "%s: usage: ", this_command_name); fprintf (stderr, "%s\n", current_builtin->short_doc); fflush (stderr); } /* Return if LIST is NULL else barf and jump to top_level. Used by some builtins that do not accept arguments. */ void no_args (list) WORD_LIST *list; { if (list) { builtin_error ("too many arguments"); jump_to_top_level (DISCARD); } } /* Check that no options were given to the currently-executing builtin, and return 0 if there were options. */ int no_options (list) WORD_LIST *list; { reset_internal_getopt (); if (internal_getopt (list, "") != -1) { builtin_usage (); return (1); } return (0); } void sh_needarg (s) char *s; { builtin_error ("%s: option requires an argument", s); } void sh_neednumarg (s) char *s; { builtin_error ("%s: numeric argument required", s); } void sh_notfound (s) char *s; { builtin_error ("%s: not found", s); } /* Function called when one of the builtin commands detects an invalid option. */ void sh_invalidopt (s) char *s; { builtin_error ("%s: invalid option", s); } void sh_invalidoptname (s) char *s; { builtin_error ("%s: invalid option name", s); } void sh_invalidid (s) char *s; { builtin_error ("`%s': not a valid identifier", s); } void sh_invalidnum (s) char *s; { builtin_error ("%s: invalid number", s); } void sh_invalidsig (s) char *s; { builtin_error ("%s: invalid signal specification", s); } void sh_badpid (s) char *s; { builtin_error ("`%s': not a pid or valid job spec", s); } void sh_readonly (s) const char *s; { builtin_error ("%s: readonly variable", s); } void sh_erange (s, desc) char *s, *desc; { if (s) builtin_error ("%s: %s out of range", s, desc ? desc : "argument"); else builtin_error ("%s out of range", desc ? desc : "argument"); } #if defined (JOB_CONTROL) void sh_badjob (s) char *s; { builtin_error ("%s: no such job", s); } void sh_nojobs (s) char *s; { if (s) builtin_error ("%s: no job control"); else builtin_error ("no job control"); } #endif #if defined (RESTRICTED_SHELL) void sh_restricted (s) char *s; { if (s) builtin_error ("%s: restricted", s); else builtin_error ("restricted"); } #endif /* **************************************************************** */ /* */ /* Shell positional parameter manipulation */ /* */ /* **************************************************************** */ /* Convert a WORD_LIST into a C-style argv. Return the number of elements in the list in *IP, if IP is non-null. A convenience function for loadable builtins; also used by `test'. */ char ** make_builtin_argv (list, ip) WORD_LIST *list; int *ip; { char **argv; argv = strvec_from_word_list (list, 0, 1, ip); argv[0] = this_command_name; return argv; } /* Remember LIST in $0 ... $9, and REST_OF_ARGS. If DESTRUCTIVE is non-zero, then discard whatever the existing arguments are, else only discard the ones that are to be replaced. */ void remember_args (list, destructive) WORD_LIST *list; int destructive; { register int i; for (i = 1; i < 10; i++) { if ((destructive || list) && dollar_vars[i]) { free (dollar_vars[i]); dollar_vars[i] = (char *)NULL; } if (list) { dollar_vars[i] = savestring (list->word->word); list = list->next; } } /* If arguments remain, assign them to REST_OF_ARGS. Note that copy_word_list (NULL) returns NULL, and that dispose_words (NULL) does nothing. */ if (destructive || list) { dispose_words (rest_of_args); rest_of_args = copy_word_list (list); } if (destructive) set_dollar_vars_changed (); } static int changed_dollar_vars; /* Have the dollar variables been reset to new values since we last checked? */ int dollar_vars_changed () { return (changed_dollar_vars); } void set_dollar_vars_unchanged () { changed_dollar_vars = 0; } void set_dollar_vars_changed () { if (variable_context) changed_dollar_vars |= ARGS_FUNC; else if (this_shell_builtin == set_builtin) changed_dollar_vars |= ARGS_SETBLTIN; else changed_dollar_vars |= ARGS_INVOC; } /* **************************************************************** */ /* */ /* Validating numeric input and arguments */ /* */ /* **************************************************************** */ /* Read a numeric arg for this_command_name, the name of the shell builtin that wants it. LIST is the word list that the arg is to come from. Accept only the numeric argument; report an error if other arguments follow. If FATAL is true, call throw_to_top_level, which exits the shell; if not, call jump_to_top_level (DISCARD), which aborts the current command. */ intmax_t get_numeric_arg (list, fatal) WORD_LIST *list; int fatal; { intmax_t count = 1; if (list && list->word && ISOPTION (list->word->word, '-')) list = list->next; if (list) { register char *arg; arg = list->word->word; if (arg == 0 || (legal_number (arg, &count) == 0)) { sh_neednumarg (list->word->word); if (fatal) throw_to_top_level (); else jump_to_top_level (DISCARD); } no_args (list->next); } return (count); } /* Get an eight-bit status value from LIST */ int get_exitstat (list) WORD_LIST *list; { int status; intmax_t sval; char *arg; if (list && list->word && ISOPTION (list->word->word, '-')) list = list->next; if (list == 0) return (last_command_exit_value); arg = list->word->word; if (arg == 0 || legal_number (arg, &sval) == 0) { sh_neednumarg (list->word->word ? list->word->word : "`'"); return 255; } no_args (list->next); status = sval & 255; return status; } /* Return the octal number parsed from STRING, or -1 to indicate that the string contained a bad number. */ int read_octal (string) char *string; { int result, digits; result = digits = 0; while (*string && ISOCTAL (*string)) { digits++; result = (result * 8) + (*string++ - '0'); if (result > 0777) return -1; } if (digits == 0 || *string) result = -1; return (result); } /* **************************************************************** */ /* */ /* Manipulating the current working directory */ /* */ /* **************************************************************** */ /* Return a consed string which is the current working directory. FOR_WHOM is the name of the caller for error printing. */ char *the_current_working_directory = (char *)NULL; char * get_working_directory (for_whom) char *for_whom; { char *directory; if (no_symbolic_links) { if (the_current_working_directory) free (the_current_working_directory); the_current_working_directory = (char *)NULL; } if (the_current_working_directory == 0) { the_current_working_directory = (char *)xmalloc (PATH_MAX); the_current_working_directory[0] = '\0'; directory = getcwd (the_current_working_directory, PATH_MAX); if (directory == 0) { fprintf (stderr, "%s: could not get current directory: %s: %s\n", (for_whom && *for_whom) ? for_whom : get_name_for_error (), bash_getcwd_errstr, strerror (errno)); free (the_current_working_directory); the_current_working_directory = (char *)NULL; return (char *)NULL; } } return (savestring (the_current_working_directory)); } /* Make NAME our internal idea of the current working directory. */ void set_working_directory (name) char *name; { FREE (the_current_working_directory); the_current_working_directory = savestring (name); } /* **************************************************************** */ /* */ /* Job control support functions */ /* */ /* **************************************************************** */ #if defined (JOB_CONTROL) int get_job_by_name (name, flags) const char *name; int flags; { register int i, wl, cl, match, job; register PROCESS *p; job = NO_JOB; wl = strlen (name); for (i = job_slots - 1; i >= 0; i--) { if (jobs[i] == 0 || ((flags & JM_STOPPED) && JOBSTATE(i) != JSTOPPED)) continue; p = jobs[i]->pipe; do { if (flags & JM_EXACT) { cl = strlen (p->command); match = STREQN (p->command, name, cl); } else if (flags & JM_SUBSTRING) match = strindex (p->command, name) != (char *)0; else match = STREQN (p->command, name, wl); if (match == 0) { p = p->next; continue; } else if (flags & JM_FIRSTMATCH) return i; /* return first match */ else if (job != NO_JOB) { if (this_shell_builtin) builtin_error ("%s: ambiguous job spec", name); else report_error ("%s: ambiguous job spec", name); return (DUP_JOB); } else job = i; } while (p != jobs[i]->pipe); } return (job); } /* Return the job spec found in LIST. */ int get_job_spec (list) WORD_LIST *list; { register char *word; int job, jflags; if (list == 0) return (current_job); word = list->word->word; if (*word == '\0') return (current_job); if (*word == '%') word++; if (DIGIT (*word) && all_digits (word)) { job = atoi (word); #if 0 return (job >= job_slots ? NO_JOB : job - 1); #else return (job > job_slots ? NO_JOB : job - 1); #endif } jflags = 0; switch (*word) { case 0: case '%': case '+': return (current_job); case '-': return (previous_job); case '?': /* Substring search requested. */ jflags |= JM_SUBSTRING; word++; /* FALLTHROUGH */ default: return get_job_by_name (word, jflags); } } #endif /* JOB_CONTROL */ int display_signal_list (list, forcecols) WORD_LIST *list; int forcecols; { register int i, column; char *name; int result; int signum; intmax_t lsignum; result = EXECUTION_SUCCESS; if (!list) { for (i = 1, column = 0; i < NSIG; i++) { name = signal_name (i); if (STREQN (name, "SIGJUNK", 7) || STREQN (name, "Unknown", 7)) continue; if (posixly_correct && !forcecols) printf ("%s%s", name, (i == NSIG - 1) ? "" : " "); else { printf ("%2d) %s", i, name); if (++column < 4) printf ("\t"); else { printf ("\n"); column = 0; } } } if ((posixly_correct && !forcecols) || column != 0) printf ("\n"); return result; } /* List individual signal names or numbers. */ while (list) { if (legal_number (list->word->word, &lsignum)) { /* This is specified by Posix.2 so that exit statuses can be mapped into signal numbers. */ if (lsignum > 128) lsignum -= 128; if (lsignum < 0 || lsignum >= NSIG) { sh_invalidsig (list->word->word); result = EXECUTION_FAILURE; list = list->next; continue; } signum = lsignum; name = signal_name (signum); if (STREQN (name, "SIGJUNK", 7) || STREQN (name, "Unknown", 7)) { list = list->next; continue; } #if defined (JOB_CONTROL) /* POSIX.2 says that `kill -l signum' prints the signal name without the `SIG' prefix. */ printf ("%s\n", (this_shell_builtin == kill_builtin) ? name + 3 : name); #else printf ("%s\n", name); #endif } else { signum = decode_signal (list->word->word); if (signum == NO_SIG) { sh_invalidsig (list->word->word); result = EXECUTION_FAILURE; list = list->next; continue; } printf ("%d\n", signum); } list = list->next; } return (result); } /* **************************************************************** */ /* */ /* Finding builtin commands and their functions */ /* */ /* **************************************************************** */ /* Perform a binary search and return the address of the builtin function whose name is NAME. If the function couldn't be found, or the builtin is disabled or has no function associated with it, return NULL. Return the address of the builtin. DISABLED_OKAY means find it even if the builtin is disabled. */ struct builtin * builtin_address_internal (name, disabled_okay) char *name; int disabled_okay; { int hi, lo, mid, j; hi = num_shell_builtins - 1; lo = 0; while (lo <= hi) { mid = (lo + hi) / 2; j = shell_builtins[mid].name[0] - name[0]; if (j == 0) j = strcmp (shell_builtins[mid].name, name); if (j == 0) { /* It must have a function pointer. It must be enabled, or we must have explicitly allowed disabled functions to be found, and it must not have been deleted. */ if (shell_builtins[mid].function && ((shell_builtins[mid].flags & BUILTIN_DELETED) == 0) && ((shell_builtins[mid].flags & BUILTIN_ENABLED) || disabled_okay)) return (&shell_builtins[mid]); else return ((struct builtin *)NULL); } if (j > 0) hi = mid - 1; else lo = mid + 1; } return ((struct builtin *)NULL); } /* Return the pointer to the function implementing builtin command NAME. */ sh_builtin_func_t * find_shell_builtin (name) char *name; { current_builtin = builtin_address_internal (name, 0); return (current_builtin ? current_builtin->function : (sh_builtin_func_t *)NULL); } /* Return the address of builtin with NAME, whether it is enabled or not. */ sh_builtin_func_t * builtin_address (name) char *name; { current_builtin = builtin_address_internal (name, 1); return (current_builtin ? current_builtin->function : (sh_builtin_func_t *)NULL); } /* Return the function implementing the builtin NAME, but only if it is a POSIX.2 special builtin. */ sh_builtin_func_t * find_special_builtin (name) char *name; { current_builtin = builtin_address_internal (name, 0); return ((current_builtin && (current_builtin->flags & SPECIAL_BUILTIN)) ? current_builtin->function : (sh_builtin_func_t *)NULL); } static int shell_builtin_compare (sbp1, sbp2) struct builtin *sbp1, *sbp2; { int result; if ((result = sbp1->name[0] - sbp2->name[0]) == 0) result = strcmp (sbp1->name, sbp2->name); return (result); } /* Sort the table of shell builtins so that the binary search will work in find_shell_builtin. */ void initialize_shell_builtins () { qsort (shell_builtins, num_shell_builtins, sizeof (struct builtin), (QSFUNC *)shell_builtin_compare); }
/* Copyright (C) 1996 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include <stdio.h> #include <signal.h> #include <errno.h> #include "filecntl.h" #include "../bashansi.h" #include "../shell.h" #include "../jobs.h" #include "../builtins.h" #include "../flags.h" #include "../input.h" #include "../execute_cmd.h" #include "../redir.h" #include "../trap.h" #if defined (HISTORY) # include "../bashhist.h" #endif #include "common.h" #if !defined (errno) extern int errno; #endif #define IS_BUILTIN(s) (builtin_address_internal(s, 0) != (struct builtin *)NULL) extern int indirection_level, startup_state, subshell_environment; extern int line_number; extern int last_command_exit_value; extern int running_trap; extern int posixly_correct; int parse_and_execute_level = 0; static int cat_file __P((REDIRECT *)); /* How to force parse_and_execute () to clean up after itself. */ void parse_and_execute_cleanup () { if (running_trap) { run_trap_cleanup (running_trap - 1); unfreeze_jobs_list (); } run_unwind_frame ("parse_and_execute_top"); } /* Parse and execute the commands in STRING. Returns whatever execute_command () returns. This frees STRING. FLAGS is a flags word; look in common.h for the possible values. Actions are: (flags & SEVAL_NONINT) -> interactive = 0; (flags & SEVAL_INTERACT) -> interactive = 1; (flags & SEVAL_NOHIST) -> call bash_history_disable () (flags & SEVAL_NOFREE) -> don't free STRING when finished */ int parse_and_execute (string, from_file, flags) char *string; const char *from_file; int flags; { int code, x; volatile int should_jump_to_top_level, last_result; char *orig_string; COMMAND *volatile command; orig_string = string; /* Unwind protect this invocation of parse_and_execute (). */ begin_unwind_frame ("parse_and_execute_top"); unwind_protect_int (parse_and_execute_level); unwind_protect_jmp_buf (top_level); unwind_protect_int (indirection_level); unwind_protect_int (line_number); if (flags & (SEVAL_NONINT|SEVAL_INTERACT)) unwind_protect_int (interactive); #if defined (HISTORY) unwind_protect_int (remember_on_history); /* can be used in scripts */ # if defined (BANG_HISTORY) if (interactive_shell) { unwind_protect_int (history_expansion_inhibited); } # endif /* BANG_HISTORY */ #endif /* HISTORY */ if (interactive_shell) { x = get_current_prompt_level (); add_unwind_protect (set_current_prompt_level, x); } add_unwind_protect (pop_stream, (char *)NULL); if (orig_string && ((flags & SEVAL_NOFREE) == 0)) add_unwind_protect (xfree, orig_string); end_unwind_frame (); parse_and_execute_level++; push_stream (1); /* reset the line number */ indirection_level++; if (flags & (SEVAL_NONINT|SEVAL_INTERACT)) interactive = (flags & SEVAL_NONINT) ? 0 : 1; #if defined (HISTORY) if (flags & SEVAL_NOHIST) bash_history_disable (); #endif /* HISTORY */ code = should_jump_to_top_level = 0; last_result = EXECUTION_SUCCESS; command = (COMMAND *)NULL; with_input_from_string (string, from_file); while (*(bash_input.location.string)) { if (interrupt_state) { last_result = EXECUTION_FAILURE; break; } /* Provide a location for functions which `longjmp (top_level)' to jump to. This prevents errors in substitution from restarting the reader loop directly, for example. */ code = setjmp (top_level); if (code) { should_jump_to_top_level = 0; switch (code) { case FORCE_EOF: case EXITPROG: run_unwind_frame ("pe_dispose"); /* Remember to call longjmp (top_level) after the old value for it is restored. */ should_jump_to_top_level = 1; goto out; case DISCARD: run_unwind_frame ("pe_dispose"); last_result = last_command_exit_value = EXECUTION_FAILURE; /* XXX */ if (subshell_environment) { should_jump_to_top_level = 1; goto out; } else { #if 0 dispose_command (command); /* pe_dispose does this */ #endif continue; } default: command_error ("parse_and_execute", CMDERR_BADJUMP, code, 0); break; } } if (parse_command () == 0) { if (interactive_shell == 0 && read_but_dont_execute) { last_result = EXECUTION_SUCCESS; dispose_command (global_command); global_command = (COMMAND *)NULL; } else if (command = global_command) { struct fd_bitmap *bitmap; bitmap = new_fd_bitmap (FD_BITMAP_SIZE); begin_unwind_frame ("pe_dispose"); add_unwind_protect (dispose_fd_bitmap, bitmap); add_unwind_protect (dispose_command, command); /* XXX */ global_command = (COMMAND *)NULL; #if defined (ONESHOT) /* * IF * we were invoked as `bash -c' (startup_state == 2) AND * parse_and_execute has not been called recursively AND * we have parsed the full command (string == '\0') AND * we have a simple command without redirections AND * the command is not being timed * THEN * tell the execution code that we don't need to fork */ if (startup_state == 2 && parse_and_execute_level == 1 && *bash_input.location.string == '\0' && command->type == cm_simple && !command->redirects && !command->value.Simple->redirects && ((command->flags & CMD_TIME_PIPELINE) == 0)) { command->flags |= CMD_NO_FORK; command->value.Simple->flags |= CMD_NO_FORK; } #endif /* ONESHOT */ /* See if this is a candidate for $( <file ). */ if (startup_state == 2 && (subshell_environment & SUBSHELL_COMSUB) && *bash_input.location.string == '\0' && command->type == cm_simple && !command->redirects && (command->flags & CMD_TIME_PIPELINE) == 0 && command->value.Simple->words == 0 && command->value.Simple->redirects && command->value.Simple->redirects->next == 0 && command->value.Simple->redirects->instruction == r_input_direction) { int r; r = cat_file (command->value.Simple->redirects); last_result = (r < 0) ? EXECUTION_FAILURE : EXECUTION_SUCCESS; } else last_result = execute_command_internal (command, 0, NO_PIPE, NO_PIPE, bitmap); dispose_command (command); dispose_fd_bitmap (bitmap); discard_unwind_frame ("pe_dispose"); } } else { last_result = EXECUTION_FAILURE; /* Since we are shell compatible, syntax errors in a script abort the execution of the script. Right? */ break; } } out: run_unwind_frame ("parse_and_execute_top"); if (interrupt_state && parse_and_execute_level == 0) { /* An interrupt during non-interactive execution in an interactive shell (e.g. via $PROMPT_COMMAND) should not cause the shell to exit. */ interactive = interactive_shell; throw_to_top_level (); } if (should_jump_to_top_level) jump_to_top_level (code); return (last_result); } /* Handle a $( < file ) command substitution. This expands the filename, returning errors as appropriate, then just cats the file to the standard output. */ static int cat_file (r) REDIRECT *r; { char lbuf[128], *fn; int fd, rval; ssize_t nr; if (r->instruction != r_input_direction) return -1; /* Get the filename. */ if (posixly_correct && !interactive_shell) disallow_filename_globbing++; fn = redirection_expand (r->redirectee.filename); if (posixly_correct && !interactive_shell) disallow_filename_globbing--; if (fn == 0) { redirection_error (r, AMBIGUOUS_REDIRECT); return -1; } fd = open(fn, O_RDONLY); if (fd < 0) { file_error (fn); free (fn); return -1; } rval = zcatfd (fd, 1, fn); free (fn); close (fd); return (rval); }
/* Copyright (C) 1996 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "../bashtypes.h" #include "posixstat.h" #include "filecntl.h" #include <stdio.h> #include <signal.h> #include <errno.h> #include "../bashansi.h" #include "../shell.h" #include "../jobs.h" #include "../builtins.h" #include "../flags.h" #include "../input.h" #include "../execute_cmd.h" #if defined (HISTORY) # include "../bashhist.h" #endif #include "common.h" #if !defined (errno) extern int errno; #endif /* Flags for _evalfile() */ #define FEVAL_ENOENTOK 0x001 #define FEVAL_BUILTIN 0x002 #define FEVAL_UNWINDPROT 0x004 #define FEVAL_NONINT 0x008 #define FEVAL_LONGJMP 0x010 #define FEVAL_HISTORY 0x020 #define FEVAL_CHECKBINARY 0x040 #define FEVAL_REGFILE 0x080 extern int posixly_correct; extern int indirection_level, startup_state, subshell_environment; extern int return_catch_flag, return_catch_value; extern int last_command_exit_value; /* How many `levels' of sourced files we have. */ int sourcelevel = 0; static int _evalfile (filename, flags) const char *filename; int flags; { volatile int old_interactive; procenv_t old_return_catch; int return_val, fd, result, pflags; char *string; struct stat finfo; size_t file_size; sh_vmsg_func_t *errfunc; USE_VAR(pflags); fd = open (filename, O_RDONLY); if (fd < 0 || (fstat (fd, &finfo) == -1)) { file_error_and_exit: if (((flags & FEVAL_ENOENTOK) == 0) || errno != ENOENT) file_error (filename); if (flags & FEVAL_LONGJMP) { last_command_exit_value = 1; jump_to_top_level (EXITPROG); } return ((flags & FEVAL_BUILTIN) ? EXECUTION_FAILURE : ((errno == ENOENT) ? 0 : -1)); } errfunc = ((flags & FEVAL_BUILTIN) ? builtin_error : internal_error); if (S_ISDIR (finfo.st_mode)) { (*errfunc) ("%s: is a directory", filename); return ((flags & FEVAL_BUILTIN) ? EXECUTION_FAILURE : -1); } else if ((flags & FEVAL_REGFILE) && S_ISREG (finfo.st_mode) == 0) { (*errfunc) ("%s: not a regular file", filename); return ((flags & FEVAL_BUILTIN) ? EXECUTION_FAILURE : -1); } file_size = (size_t)finfo.st_size; /* Check for overflow with large files. */ if (file_size != finfo.st_size || file_size + 1 < file_size) { (*errfunc) ("%s: file is too large", filename); return ((flags & FEVAL_BUILTIN) ? EXECUTION_FAILURE : -1); } #if defined (__CYGWIN__) && defined (O_TEXT) setmode (fd, O_TEXT); #endif string = (char *)xmalloc (1 + file_size); result = read (fd, string, file_size); string[result] = '\0'; return_val = errno; close (fd); errno = return_val; if (result < 0) /* XXX was != file_size, not < 0 */ { free (string); goto file_error_and_exit; } if (result == 0) { free (string); return ((flags & FEVAL_BUILTIN) ? EXECUTION_SUCCESS : 1); } if ((flags & FEVAL_CHECKBINARY) && check_binary_file (string, (result > 80) ? 80 : result)) { free (string); (*errfunc) ("%s: cannot execute binary file", filename); return ((flags & FEVAL_BUILTIN) ? EX_BINARY_FILE : -1); } if (flags & FEVAL_UNWINDPROT) { begin_unwind_frame ("_evalfile"); unwind_protect_int (return_catch_flag); unwind_protect_jmp_buf (return_catch); if (flags & FEVAL_NONINT) unwind_protect_int (interactive); unwind_protect_int (sourcelevel); } else { COPY_PROCENV (return_catch, old_return_catch); if (flags & FEVAL_NONINT) old_interactive = interactive; } if (flags & FEVAL_NONINT) interactive = 0; return_catch_flag++; sourcelevel++; /* set the flags to be passed to parse_and_execute */ pflags = (flags & FEVAL_HISTORY) ? 0 : SEVAL_NOHIST; if (flags & FEVAL_BUILTIN) result = EXECUTION_SUCCESS; return_val = setjmp (return_catch); /* If `return' was seen outside of a function, but in the script, then force parse_and_execute () to clean up. */ if (return_val) { parse_and_execute_cleanup (); result = return_catch_value; } else result = parse_and_execute (string, filename, pflags); if (flags & FEVAL_UNWINDPROT) run_unwind_frame ("_evalfile"); else { if (flags & FEVAL_NONINT) interactive = old_interactive; return_catch_flag--; sourcelevel--; COPY_PROCENV (old_return_catch, return_catch); } return ((flags & FEVAL_BUILTIN) ? result : 1); } int maybe_execute_file (fname, force_noninteractive) const char *fname; int force_noninteractive; { char *filename; int result, flags; filename = bash_tilde_expand (fname, 0); flags = FEVAL_ENOENTOK; if (force_noninteractive) flags |= FEVAL_NONINT; result = _evalfile (filename, flags); free (filename); return result; } #if defined (HISTORY) int fc_execute_file (filename) const char *filename; { int flags; /* We want these commands to show up in the history list if remember_on_history is set. */ flags = FEVAL_ENOENTOK|FEVAL_HISTORY|FEVAL_REGFILE; return (_evalfile (filename, flags)); } #endif /* HISTORY */ int source_file (filename) const char *filename; { int flags; flags = FEVAL_BUILTIN|FEVAL_UNWINDPROT|FEVAL_NONINT; /* POSIX shells exit if non-interactive and file error. */ if (posixly_correct && !interactive_shell) flags |= FEVAL_LONGJMP; return (_evalfile (filename, flags)); }
/* getopt for BASH. Copyright (C) 1993, 1994 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include <stdio.h> #include "memalloc.h" #include "../shell.h" #include "getopt.h" /* For communication from `sh_getopt' to the caller. When `sh_getopt' finds an option that takes an argument, the argument value is returned here. */ char *sh_optarg = 0; /* Index in ARGV of the next element to be scanned. This is used for communication to and from the caller and for communication between successive calls to `sh_getopt'. On entry to `sh_getopt', zero means this is the first call; initialize. When `sh_getopt' returns EOF, this is the index of the first of the non-option elements that the caller should itself scan. Otherwise, `sh_optind' communicates from one call to the next how much of ARGV has been scanned so far. */ /* XXX 1003.2 says this must be 1 before any call. */ int sh_optind = 0; /* Index of the current argument. */ static int sh_curopt; /* The next char to be scanned in the option-element in which the last option character we returned was found. This allows us to pick up the scan where we left off. If this is zero, or a null string, it means resume the scan by advancing to the next ARGV-element. */ static char *nextchar; static int sh_charindex; /* Callers store zero here to inhibit the error message for unrecognized options. */ int sh_opterr = 1; /* Set to an option character which was unrecognized. This must be initialized on some systems to avoid linking in the system's own getopt implementation. */ int sh_optopt = '?'; /* Set to 1 when we see an invalid option; public so getopts can reset it. */ int sh_badopt = 0; /* Scan elements of ARGV (whose length is ARGC) for option characters given in OPTSTRING. If an element of ARGV starts with '-', and is not exactly "-" or "--", then it is an option element. The characters of this element (aside from the initial '-') are option characters. If `sh_getopt' is called repeatedly, it returns successively each of the option characters from each of the option elements. If `sh_getopt' finds another option character, it returns that character, updating `sh_optind' and `nextchar' so that the next call to `sh_getopt' can resume the scan with the following option character or ARGV-element. If there are no more option characters, `sh_getopt' returns `EOF'. Then `sh_optind' is the index in ARGV of the first ARGV-element that is not an option. OPTSTRING is a string containing the legitimate option characters. If an option character is seen that is not listed in OPTSTRING, return '?' after printing an error message. If you set `sh_opterr' to zero, the error message is suppressed but we still return '?'. If a char in OPTSTRING is followed by a colon, that means it wants an arg, so the following text in the same ARGV-element, or the text of the following ARGV-element, is returned in `sh_optarg'. */ /* 1003.2 specifies the format of this message. */ #define BADOPT(x) fprintf (stderr, "%s: illegal option -- %c\n", argv[0], x) #define NEEDARG(x) fprintf (stderr, "%s: option requires an argument -- %c\n", argv[0], x) int sh_getopt (argc, argv, optstring) int argc; char *const *argv; const char *optstring; { char c, *temp; sh_optarg = 0; if (sh_optind >= argc || sh_optind < 0) /* XXX was sh_optind > argc */ { sh_optind = argc; return (EOF); } /* Initialize the internal data when the first call is made. Start processing options with ARGV-element 1 (since ARGV-element 0 is the program name); the sequence of previously skipped non-option ARGV-elements is empty. */ if (sh_optind == 0) { sh_optind = 1; nextchar = (char *)NULL; } if (nextchar == 0 || *nextchar == '\0') { /* If we have done all the ARGV-elements, stop the scan. */ if (sh_optind >= argc) return EOF; temp = argv[sh_optind]; /* Special ARGV-element `--' means premature end of options. Skip it like a null option, and return EOF. */ if (temp[0] == '-' && temp[1] == '-' && temp[2] == '\0') { sh_optind++; return EOF; } /* If we have come to a non-option, either stop the scan or describe it to the caller and pass it by. This makes the pseudo-option `-' mean the end of options, but does not skip over it. */ if (temp[0] != '-' || temp[1] == '\0') return EOF; /* We have found another option-ARGV-element. Start decoding its characters. */ nextchar = argv[sh_curopt = sh_optind] + 1; sh_charindex = 1; } /* Look at and handle the next option-character. */ c = *nextchar++; sh_charindex++; temp = strchr (optstring, c); sh_optopt = c; /* Increment `sh_optind' when we start to process its last character. */ if (nextchar == 0 || *nextchar == '\0') { sh_optind++; nextchar = (char *)NULL; } if (sh_badopt = (temp == NULL || c == ':')) { if (sh_opterr) BADOPT (c); return '?'; } if (temp[1] == ':') { if (nextchar && *nextchar) { /* This is an option that requires an argument. */ sh_optarg = nextchar; /* If we end this ARGV-element by taking the rest as an arg, we must advance to the next element now. */ sh_optind++; } else if (sh_optind == argc) { if (sh_opterr) NEEDARG (c); sh_optopt = c; sh_optarg = ""; /* Needed by getopts. */ c = (optstring[0] == ':') ? ':' : '?'; } else /* We already incremented `sh_optind' once; increment it again when taking next ARGV-elt as argument. */ sh_optarg = argv[sh_optind++]; nextchar = (char *)NULL; } return c; } void sh_getopt_restore_state (argv) char **argv; { if (nextchar) nextchar = argv[sh_curopt] + sh_charindex; } #if 0 void sh_getopt_debug_restore_state (argv) char **argv; { if (nextchar && nextchar != argv[sh_curopt] + sh_charindex) { itrace("sh_getopt_debug_restore_state: resetting nextchar"); nextchar = argv[sh_curopt] + sh_charindex; } } #endif #ifdef TEST /* Compile with -DTEST to make an executable for use in testing the above definition of `sh_getopt'. */ int main (argc, argv) int argc; char **argv; { int c; int digit_sh_optind = 0; while (1) { int this_option_sh_optind = sh_optind ? sh_optind : 1; c = sh_getopt (argc, argv, "abc:d:0123456789"); if (c == EOF) break; switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (digit_sh_optind != 0 && digit_sh_optind != this_option_sh_optind) printf ("digits occur in two different argv-elements.\n"); digit_sh_optind = this_option_sh_optind; printf ("option %c\n", c); break; case 'a': printf ("option a\n"); break; case 'b': printf ("option b\n"); break; case 'c': printf ("option c with value `%s'\n", sh_optarg); break; case '?': break; default: printf ("?? sh_getopt returned character code 0%o ??\n", c); } } if (sh_optind < argc) { printf ("non-option ARGV-elements: "); while (sh_optind < argc) printf ("%s ", argv[sh_optind++]); printf ("\n"); } exit (0); } #endif /* TEST */
/* bashgetopt.c -- `getopt' for use by the builtins. */ /* Copyright (C) 1992-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "../bashansi.h" #include <chartypes.h> #include <errno.h> #include "../shell.h" #include "common.h" #define ISOPT(s) (((*(s) == '-') || (plus && *(s) == '+')) && (s)[1]) #define NOTOPT(s) (((*(s) != '-') && (!plus || *(s) != '+')) || (s)[1] == '\0') static int sp; char *list_optarg; int list_optopt; int list_opttype; static WORD_LIST *lhead = (WORD_LIST *)NULL; WORD_LIST *lcurrent = (WORD_LIST *)NULL; WORD_LIST *loptend; /* Points to the first non-option argument in the list */ int internal_getopt(list, opts) WORD_LIST *list; char *opts; { register int c; register char *cp; int plus; /* nonzero means to handle +option */ static char errstr[3] = { '-', '\0', '\0' }; plus = *opts == '+'; if (plus) opts++; if (list == 0) { list_optarg = (char *)NULL; loptend = (WORD_LIST *)NULL; /* No non-option arguments */ return -1; } if (list != lhead || lhead == 0) { /* Hmmm.... called with a different word list. Reset. */ sp = 1; lcurrent = lhead = list; loptend = (WORD_LIST *)NULL; } if (sp == 1) { if (lcurrent == 0 || NOTOPT(lcurrent->word->word)) { lhead = (WORD_LIST *)NULL; loptend = lcurrent; return(-1); } else if (lcurrent->word->word[0] == '-' && lcurrent->word->word[1] == '-' && lcurrent->word->word[2] == 0) { lhead = (WORD_LIST *)NULL; loptend = lcurrent->next; return(-1); } errstr[0] = list_opttype = lcurrent->word->word[0]; } list_optopt = c = lcurrent->word->word[sp]; if (c == ':' || (cp = strchr(opts, c)) == NULL) { errstr[1] = c; sh_invalidopt (errstr); if (lcurrent->word->word[++sp] == '\0') { lcurrent = lcurrent->next; sp = 1; } list_optarg = NULL; if (lcurrent) loptend = lcurrent->next; return('?'); } if (*++cp == ':' || *cp == ';') { /* `:': Option requires an argument. */ /* `;': option argument may be missing */ /* We allow -l2 as equivalent to -l 2 */ if (lcurrent->word->word[sp+1]) { list_optarg = lcurrent->word->word + sp + 1; lcurrent = lcurrent->next; /* If the specifier is `;', don't set optarg if the next argument looks like another option. */ #if 0 } else if (lcurrent->next && (*cp == ':' || lcurrent->next->word->word[0] != '-')) { #else } else if (lcurrent->next && (*cp == ':' || NOTOPT(lcurrent->next->word->word))) { #endif lcurrent = lcurrent->next; list_optarg = lcurrent->word->word; lcurrent = lcurrent->next; } else if (*cp == ';') { list_optarg = (char *)NULL; lcurrent = lcurrent->next; } else { /* lcurrent->next == NULL */ errstr[1] = c; sh_needarg (errstr); sp = 1; list_optarg = (char *)NULL; return('?'); } sp = 1; } else if (*cp == '#') { /* option requires a numeric argument */ if (lcurrent->word->word[sp+1]) { if (DIGIT(lcurrent->word->word[sp+1])) { list_optarg = lcurrent->word->word + sp + 1; lcurrent = lcurrent->next; } else list_optarg = (char *)NULL; } else { if (lcurrent->next && legal_number(lcurrent->next->word->word, (intmax_t *)0)) { lcurrent = lcurrent->next; list_optarg = lcurrent->word->word; lcurrent = lcurrent->next; } else { errstr[1] = c; sh_neednumarg (errstr); sp = 1; list_optarg = (char *)NULL; return ('?'); } } } else { /* No argument, just return the option. */ if (lcurrent->word->word[++sp] == '\0') { sp = 1; lcurrent = lcurrent->next; } list_optarg = (char *)NULL; } return(c); } /* * reset_internal_getopt -- force the in[ft]ernal getopt to reset */ void reset_internal_getopt () { lhead = lcurrent = loptend = (WORD_LIST *)NULL; sp = 1; }
/* clktck.c - get the value of CLK_TCK. */ /* Copyright (C) 1997 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA */ #include <config.h> #include <bashtypes.h> #include <sys/param.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #if defined (HAVE_LIMITS_H) # include <limits.h> #endif #if !defined (HAVE_SYSCONF) || !defined (_SC_CLK_TCK) # if !defined (CLK_TCK) # if defined (HZ) # define CLK_TCK HZ # else # define CLK_TCK 60 # endif # endif /* !CLK_TCK */ #endif /* !HAVE_SYSCONF && !_SC_CLK_TCK */ long get_clk_tck () { static long retval = 0; if (retval != 0) return (retval); #if defined (HAVE_SYSCONF) && defined (_SC_CLK_TCK) retval = sysconf (_SC_CLK_TCK); #else /* !SYSCONF || !_SC_CLK_TCK */ retval = CLK_TCK; #endif /* !SYSCONF || !_SC_CLK_TCK */ return (retval); }
/* getcwd.c -- stolen from the GNU C library and modified to work with bash. */ /* Copyright (C) 1991 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #if !defined (HAVE_GETCWD) #include <bashtypes.h> #include <errno.h> #if defined (HAVE_LIMITS_H) # include <limits.h> #endif #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <posixdir.h> #include <posixstat.h> #include <maxpath.h> #include <memalloc.h> #include <bashansi.h> #include <xmalloc.h> #if !defined (errno) extern int errno; #endif /* !errno */ #if !defined (HAVE_LSTAT) # define lstat stat #endif #if !defined (NULL) # define NULL 0 #endif /* Get the pathname of the current working directory, and put it in SIZE bytes of BUF. Returns NULL if the directory couldn't be determined or SIZE was too small. If successful, returns BUF. In GNU, if BUF is NULL, an array is allocated with `malloc'; the array is SIZE bytes long, unless SIZE <= 0, in which case it is as big as necessary. */ #if defined (__STDC__) char * getcwd (char *buf, size_t size) #else /* !__STDC__ */ char * getcwd (buf, size) char *buf; size_t size; #endif /* !__STDC__ */ { static const char dots[] = "../../../../../../../../../../../../../../../../../../../../../../../\ ../../../../../../../../../../../../../../../../../../../../../../../../../../\ ../../../../../../../../../../../../../../../../../../../../../../../../../.."; const char *dotp, *dotlist; size_t dotsize; dev_t rootdev, thisdev; ino_t rootino, thisino; char path[PATH_MAX + 1]; register char *pathp; char *pathbuf; size_t pathsize; struct stat st; int saved_errno; if (buf != NULL && size == 0) { errno = EINVAL; return ((char *)NULL); } pathsize = sizeof (path); pathp = &path[pathsize]; *--pathp = '\0'; pathbuf = path; if (stat (".", &st) < 0) return ((char *)NULL); thisdev = st.st_dev; thisino = st.st_ino; if (stat ("/", &st) < 0) return ((char *)NULL); rootdev = st.st_dev; rootino = st.st_ino; saved_errno = 0; dotsize = sizeof (dots) - 1; dotp = &dots[sizeof (dots)]; dotlist = dots; while (!(thisdev == rootdev && thisino == rootino)) { register DIR *dirstream; register struct dirent *d; dev_t dotdev; ino_t dotino; char mount_point; int namlen; /* Look at the parent directory. */ if (dotp == dotlist) { /* My, what a deep directory tree you have, Grandma. */ char *new; if (dotlist == dots) { new = (char *)malloc (dotsize * 2 + 1); if (new == NULL) goto lose; memcpy (new, dots, dotsize); } else { new = (char *)realloc ((PTR_T) dotlist, dotsize * 2 + 1); if (new == NULL) goto lose; } memcpy (&new[dotsize], new, dotsize); dotp = &new[dotsize]; dotsize *= 2; new[dotsize] = '\0'; dotlist = new; } dotp -= 3; /* Figure out if this directory is a mount point. */ if (stat (dotp, &st) < 0) goto lose; dotdev = st.st_dev; dotino = st.st_ino; mount_point = dotdev != thisdev; /* Search for the last directory. */ dirstream = opendir (dotp); if (dirstream == NULL) goto lose; while ((d = readdir (dirstream)) != NULL) { if (d->d_name[0] == '.' && (d->d_name[1] == '\0' || (d->d_name[1] == '.' && d->d_name[2] == '\0'))) continue; if (mount_point || d->d_fileno == thisino) { char *name; namlen = D_NAMLEN(d); name = (char *) alloca (dotlist + dotsize - dotp + 1 + namlen + 1); memcpy (name, dotp, dotlist + dotsize - dotp); name[dotlist + dotsize - dotp] = '/'; memcpy (&name[dotlist + dotsize - dotp + 1], d->d_name, namlen + 1); if (lstat (name, &st) < 0) { #if 0 int save = errno; (void) closedir (dirstream); errno = save; goto lose; #else saved_errno = errno; #endif } if (st.st_dev == thisdev && st.st_ino == thisino) break; } } if (d == NULL) { #if 0 int save = errno; #else int save = errno ? errno : saved_errno; #endif (void) closedir (dirstream); errno = save; goto lose; } else { size_t space; while ((space = pathp - pathbuf) <= namlen) { char *new; if (pathbuf == path) { new = (char *)malloc (pathsize * 2); if (!new) goto lose; } else { new = (char *)realloc ((PTR_T) pathbuf, (pathsize * 2)); if (!new) goto lose; pathp = new + space; } (void) memcpy (new + pathsize + space, pathp, pathsize - space); pathp = new + pathsize + space; pathbuf = new; pathsize *= 2; } pathp -= namlen; (void) memcpy (pathp, d->d_name, namlen); *--pathp = '/'; (void) closedir (dirstream); } thisdev = dotdev; thisino = dotino; } if (pathp == &path[sizeof(path) - 1]) *--pathp = '/'; if (dotlist != dots) free ((PTR_T) dotlist); { size_t len = pathbuf + pathsize - pathp; if (buf == NULL) { if (len < (size_t) size) len = size; buf = (char *) malloc (len); if (buf == NULL) goto lose2; } else if ((size_t) size < len) { errno = ERANGE; goto lose2; } (void) memcpy((PTR_T) buf, (PTR_T) pathp, len); } if (pathbuf != path) free (pathbuf); return (buf); lose: if ((dotlist != dots) && dotlist) { int e = errno; free ((PTR_T) dotlist); errno = e; } lose2: if ((pathbuf != path) && pathbuf) { int e = errno; free ((PTR_T) pathbuf); errno = e; } return ((char *)NULL); } #if defined (TEST) # include <stdio.h> main (argc, argv) int argc; char **argv; { char b[PATH_MAX]; if (getcwd(b, sizeof(b))) { printf ("%s\n", b); exit (0); } else { perror ("cwd: getcwd"); exit (1); } } #endif /* TEST */ #endif /* !HAVE_GETCWD */
/* getenv.c - get environment variable value from the shell's variable list. */ /* Copyright (C) 1997-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA */ #include <config.h> #if defined (CAN_REDEFINE_GETENV) #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <bashansi.h> #include <errno.h> #include <shell.h> #ifndef errno extern int errno; #endif extern char **environ; /* We supply our own version of getenv () because we want library routines to get the changed values of exported variables. */ /* The NeXT C library has getenv () defined and used in the same file. This screws our scheme. However, Bash will run on the NeXT using the C library getenv (), since right now the only environment variable that we care about is HOME, and that is already defined. */ static char *last_tempenv_value = (char *)NULL; char * getenv (name) const char *name; { SHELL_VAR *var; if (name == 0 || *name == '\0') return ((char *)NULL); var = find_tempenv_variable ((char *)name); if (var) { FREE (last_tempenv_value); last_tempenv_value = value_cell (var) ? savestring (value_cell (var)) : (char *)NULL; return (last_tempenv_value); } else if (shell_variables) { var = find_variable ((char *)name); if (var && exported_p (var)) return (value_cell (var)); } else { register int i, len; /* In some cases, s5r3 invokes getenv() before main(); BSD systems using gprof also exhibit this behavior. This means that shell_variables will be 0 when this is invoked. We look up the variable in the real environment in that case. */ for (i = 0, len = strlen (name); environ[i]; i++) { if ((STREQN (environ[i], name, len)) && (environ[i][len] == '=')) return (environ[i] + len + 1); } } return ((char *)NULL); } /* Some versions of Unix use _getenv instead. */ char * _getenv (name) const char *name; { return (getenv (name)); } /* SUSv3 says argument is a `char *'; BSD implementations disagree */ int putenv (str) #ifndef HAVE_STD_PUTENV const char *str; #else char *str; #endif { SHELL_VAR *var; char *name, *value; int offset; if (str == 0 || *str == '\0') { errno = EINVAL; return -1; } offset = assignment (str); if (str[offset] != '=') { errno = EINVAL; return -1; } name = savestring (str); name[offset] = 0; value = name + offset + 1; /* XXX - should we worry about readonly here? */ var = bind_variable (name, value); if (var == 0) { errno = EINVAL; return -1; } VUNSETATTR (var, att_invisible); VSETATTR (var, att_exported); return 0; } #if 0 int _putenv (name) #ifndef HAVE_STD_PUTENV const char *name; #else char *name; #endif { return putenv (name); } #endif int setenv (name, value, rewrite) const char *name; const char *value; int rewrite; { SHELL_VAR *var; char *v; if (name == 0 || *name == '\0' || strchr (name, '=') != 0) { errno = EINVAL; return -1; } var = 0; v = value; /* XXX - should we worry about readonly here? */ if (rewrite == 0) var = find_variable (name); if (var == 0) var = bind_variable (name, v); if (var == 0) return -1; VUNSETATTR (var, att_invisible); VSETATTR (var, att_exported); return 0; } #if 0 int _setenv (name, value, rewrite) const char *name; const char *value; int rewrite; { return setenv (name, value, rewrite); } #endif /* SUSv3 says unsetenv returns int; existing implementations (BSD) disagree. */ #ifdef HAVE_STD_UNSETENV #define UNSETENV_RETURN(N) return(N) #define UNSETENV_RETTYPE int #else #define UNSETENV_RETURN(N) return #define UNSETENV_RETTYPE void #endif UNSETENV_RETTYPE unsetenv (name) const char *name; { if (name == 0 || *name == '\0' || strchr (name, '=') != 0) { errno = EINVAL; UNSETENV_RETURN(-1); } /* XXX - should we just remove the export attribute here? */ #if 1 unbind_variable (name); #else SHELL_VAR *v; v = find_variable (name); if (v) VUNSETATTR (v, att_exported); #endif UNSETENV_RETURN(0); } #endif /* CAN_REDEFINE_GETENV */
/* setlinebuf.c - line-buffer a stdio stream. */ /* Copyright (C) 1997 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA */ #include <config.h> #include <stdio.h> #include <xmalloc.h> #if defined (USING_BASH_MALLOC) # define LBUF_BUFSIZE 1008 #else # define LBUF_BUFSIZE BUFSIZ #endif /* Cause STREAM to buffer lines as opposed to characters or blocks. */ int sh_setlinebuf (stream) FILE *stream; { char *local_linebuf; #if !defined (HAVE_SETLINEBUF) && !defined (HAVE_SETVBUF) return (0); #endif #if defined (USING_BASH_MALLOC) local_linebuf = (char *)xmalloc (LBUF_BUFSIZE); #else local_linebuf = (char *)NULL; #endif #if defined (HAVE_SETVBUF) # if defined (SETVBUF_REVERSED) return (setvbuf (stream, _IOLBF, local_linebuf, LBUF_BUFSIZE)); # else /* !SETVBUF_REVERSED */ return (setvbuf (stream, local_linebuf, _IOLBF, LBUF_BUFSIZE)); # endif /* !SETVBUF_REVERSED */ # else /* !HAVE_SETVBUF */ setlinebuf (stream); return (0); #endif /* !HAVE_SETVBUF */ }
/* xmalloc.h -- defines for the `x' memory allocation functions */ /* Copyright (C) 2001 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_XMALLOC_H_) #define _XMALLOC_H_ #include "stdc.h" #include "bashansi.h" /* Generic pointer type. */ #ifndef PTR_T #if defined (__STDC__) # define PTR_T void * #else # define PTR_T char * #endif #endif /* PTR_T */ /* Allocation functions in xmalloc.c */ extern PTR_T xmalloc __P((size_t)); extern PTR_T xrealloc __P((void *, size_t)); extern void xfree __P((void *)); #if defined(USING_BASH_MALLOC) && !defined (DISABLE_MALLOC_WRAPPERS) extern PTR_T sh_xmalloc __P((size_t, const char *, int)); extern PTR_T sh_xrealloc __P((void *, size_t, const char *, int)); extern void sh_xfree __P((void *, const char *, int)); #define xmalloc(x) sh_xmalloc((x), __FILE__, __LINE__) #define xrealloc(x, n) sh_xrealloc((x), (n), __FILE__, __LINE__) #define xfree(x) sh_xfree((x), __FILE__, __LINE__) #ifdef free #undef free #endif #define free(x) sh_xfree((x), __FILE__, __LINE__) #endif /* USING_BASH_MALLOC */ #endif /* _XMALLOC_H_ */
/* strcasecmp.c - functions for case-insensitive string comparison. */ /* Copyright (C) 1995 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA */ #include <config.h> #if !defined (HAVE_STRCASECMP) #include <stdc.h> #include <bashansi.h> #include <chartypes.h> /* Compare at most COUNT characters from string1 to string2. Case doesn't matter. */ int strncasecmp (string1, string2, count) const char *string1; const char *string2; int count; { register const char *s1; register const char *s2; register int r; if (count <= 0 || (string1 == string2)) return 0; s1 = string1; s2 = string2; do { if ((r = TOLOWER ((unsigned char) *s1) - TOLOWER ((unsigned char) *s2)) != 0) return r; if (*s1++ == '\0') break; s2++; } while (--count != 0); return (0); } /* strcmp (), but caseless. */ int strcasecmp (string1, string2) const char *string1; const char *string2; { register const char *s1; register const char *s2; register int r; s1 = string1; s2 = string2; if (s1 == s2) return (0); while ((r = TOLOWER ((unsigned char)*s1) - TOLOWER ((unsigned char)*s2)) == 0) { if (*s1++ == '\0') return 0; s2++; } return (r); } #endif /* !HAVE_STRCASECMP */
/* strerror.c - string corresponding to a particular value of errno. */ /* Copyright (C) 1995 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA */ #include <config.h> #if !defined (HAVE_STRERROR) #include <bashtypes.h> #ifndef _MINIX # include <sys/param.h> #endif #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <stdio.h> #include <errno.h> #include <shell.h> #if !defined (errno) extern int errno; #endif /* !errno */ /* Return a string corresponding to the error number E. From the ANSI C spec. */ #if defined (strerror) # undef strerror #endif static char *errbase = "Unknown system error "; char * strerror (e) int e; { static char emsg[40]; #if defined (HAVE_SYS_ERRLIST) extern int sys_nerr; extern char *sys_errlist[]; if (e > 0 && e < sys_nerr) return (sys_errlist[e]); else #endif /* HAVE_SYS_ERRLIST */ { char *z; z = itos (e); strcpy (emsg, errbase); strcat (emsg, z); free (z); return (&emsg[0]); } } #endif /* HAVE_STRERROR */
/* Copyright (C) 1991, 1992 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H # include <config.h> #endif #ifndef HAVE_STRTOD #include <errno.h> #ifndef errno extern int errno; #endif #include <chartypes.h> #include <math.h> #if HAVE_FLOAT_H # include <float.h> #else # define DBL_MAX 1.7976931348623159e+308 # define DBL_MIN 2.2250738585072010e-308 #endif #include <bashansi.h> #ifndef NULL # define NULL 0 #endif #ifndef HUGE_VAL # define HUGE_VAL HUGE #endif /* Convert NPTR to a double. If ENDPTR is not NULL, a pointer to the character after the last one used in the number is put in *ENDPTR. */ double strtod (nptr, endptr) const char *nptr; char **endptr; { register const char *s; short sign; /* The number so far. */ double num; int got_dot; /* Found a decimal point. */ int got_digit; /* Seen any digits. */ /* The exponent of the number. */ long int exponent; if (nptr == NULL) { errno = EINVAL; goto noconv; } s = nptr; /* Eat whitespace. */ while (ISSPACE ((unsigned char)*s)) ++s; /* Get the sign. */ sign = *s == '-' ? -1 : 1; if (*s == '-' || *s == '+') ++s; num = 0.0; got_dot = 0; got_digit = 0; exponent = 0; for (;; ++s) { if (DIGIT (*s)) { got_digit = 1; /* Make sure that multiplication by 10 will not overflow. */ if (num > DBL_MAX * 0.1) /* The value of the digit doesn't matter, since we have already gotten as many digits as can be represented in a `double'. This doesn't necessarily mean the result will overflow. The exponent may reduce it to within range. We just need to record that there was another digit so that we can multiply by 10 later. */ ++exponent; else num = (num * 10.0) + (*s - '0'); /* Keep track of the number of digits after the decimal point. If we just divided by 10 here, we would lose precision. */ if (got_dot) --exponent; } else if (!got_dot && *s == '.') /* Record that we have found the decimal point. */ got_dot = 1; else /* Any other character terminates the number. */ break; } if (!got_digit) goto noconv; if (TOLOWER ((unsigned char)*s) == 'e') { /* Get the exponent specified after the `e' or `E'. */ int save = errno; char *end; long int exp; errno = 0; ++s; exp = strtol (s, &end, 10); if (errno == ERANGE) { /* The exponent overflowed a `long int'. It is probably a safe assumption that an exponent that cannot be represented by a `long int' exceeds the limits of a `double'. */ if (endptr != NULL) *endptr = end; if (exp < 0) goto underflow; else goto overflow; } else if (end == s) /* There was no exponent. Reset END to point to the 'e' or 'E', so *ENDPTR will be set there. */ end = (char *) s - 1; errno = save; s = end; exponent += exp; } if (endptr != NULL) *endptr = (char *) s; if (num == 0.0) return 0.0; /* Multiply NUM by 10 to the EXPONENT power, checking for overflow and underflow. */ if (exponent < 0) { if (num < DBL_MIN * pow (10.0, (double) -exponent)) goto underflow; } else if (exponent > 0) { if (num > DBL_MAX * pow (10.0, (double) -exponent)) goto overflow; } num *= pow (10.0, (double) exponent); return num * sign; overflow: /* Return an overflow error. */ errno = ERANGE; return HUGE_VAL * sign; underflow: /* Return an underflow error. */ if (endptr != NULL) *endptr = (char *) nptr; errno = ERANGE; return 0.0; noconv: /* There was no number. */ if (endptr != NULL) *endptr = (char *) nptr; return 0.0; } #endif /* !HAVE_STRTOD */
/* vprint.c -- v[fs]printf() for 4.[23] BSD systems. */ /* Copyright (C) 1987,1989 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #if defined (USE_VFPRINTF_EMULATION) #include <stdio.h> #if !defined (NULL) # if defined (__STDC__) # define NULL ((void *)0) # else # define NULL 0x0 # endif /* __STDC__ */ #endif /* !NULL */ /* * Beware! Don't trust the value returned by either of these functions; it * seems that pre-4.3-tahoe implementations of _doprnt () return the first * argument, i.e. a char *. */ #include <varargs.h> int vfprintf (iop, fmt, ap) FILE *iop; char *fmt; va_list ap; { int len; char localbuf[BUFSIZ]; if (iop->_flag & _IONBF) { iop->_flag &= ~_IONBF; iop->_ptr = iop->_base = localbuf; len = _doprnt (fmt, ap, iop); (void) fflush (iop); iop->_flag |= _IONBF; iop->_base = NULL; iop->_bufsiz = 0; iop->_cnt = 0; } else len = _doprnt (fmt, ap, iop); return (ferror (iop) ? EOF : len); } /* * Ditto for vsprintf */ int vsprintf (str, fmt, ap) char *str, *fmt; va_list ap; { FILE f; int len; f._flag = _IOWRT|_IOSTRG; f._ptr = str; f._cnt = 32767; len = _doprnt (fmt, ap, &f); *f._ptr = 0; return (len); } #endif /* USE_VFPRINTF_EMULATION */
/* itos.c -- Convert integer to string. */ /* Copyright (C) 1998-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <bashansi.h> #include "shell.h" char * inttostr (i, buf, len) intmax_t i; char *buf; size_t len; { return (fmtumax (i, 10, buf, len, 0)); } /* Integer to string conversion. This conses the string; the caller should free it. */ char * itos (i) intmax_t i; { char *p, lbuf[INT_STRLEN_BOUND(intmax_t) + 1]; p = fmtumax (i, 10, lbuf, sizeof(lbuf), 0); return (savestring (p)); } char * uinttostr (i, buf, len) uintmax_t i; char *buf; size_t len; { return (fmtumax (i, 10, buf, len, FL_UNSIGNED)); } /* Integer to string conversion. This conses the string; the caller should free it. */ char * uitos (i) uintmax_t i; { char *p, lbuf[INT_STRLEN_BOUND(uintmax_t) + 1]; p = fmtumax (i, 10, lbuf, sizeof(lbuf), FL_UNSIGNED); return (savestring (p)); }
/* * rename - rename a file */ /* Copyright (C) 1999 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #if !defined (HAVE_RENAME) #include <bashtypes.h> #include <posixstat.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <errno.h> #include <stdc.h> #ifndef errno extern int errno; #endif int rename (from, to) const char *from, *to; { struct stat fb, tb; if (stat (from, &fb) < 0) return -1; if (stat (to, &tb) < 0) { if (errno != ENOENT) return -1; } else { if (fb.st_dev == tb.st_dev && fb.st_ino == tb.st_ino) return 0; /* same file */ if (unlink (to) < 0 && errno != ENOENT) return -1; } if (link (from, to) < 0) return (-1); if (unlink (from) < 0 && errno != ENOENT) { int e = errno; unlink (to); errno = e; return (-1); } return (0); } #endif /* !HAVE_RENAME */
/* Copyright (C) 1999-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #include <sys/types.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <errno.h> #if !defined (errno) extern int errno; #endif #ifndef SEEK_CUR # define SEEK_CUR 1 #endif /* Read LEN bytes from FD into BUF. Retry the read on EINTR. Any other error causes the loop to break. */ ssize_t zread (fd, buf, len) int fd; char *buf; size_t len; { ssize_t r; while ((r = read (fd, buf, len)) < 0 && errno == EINTR) ; return r; } /* Read LEN bytes from FD into BUF. Retry the read on EINTR, up to three interrupts. Any other error causes the loop to break. */ #ifdef NUM_INTR # undef NUM_INTR #endif #define NUM_INTR 3 ssize_t zreadintr (fd, buf, len) int fd; char *buf; size_t len; { ssize_t r; int nintr; for (nintr = 0; ; ) { r = read (fd, buf, len); if (r >= 0) return r; if (r == -1 && errno == EINTR) { if (++nintr > NUM_INTR) return -1; continue; } return r; } } /* Read one character from FD and return it in CP. Return values are as in read(2). This does some local buffering to avoid many one-character calls to read(2), like those the `read' builtin performs. */ static char lbuf[128]; static size_t lind, lused; ssize_t zreadc (fd, cp) int fd; char *cp; { ssize_t nr; if (lind == lused || lused == 0) { nr = zread (fd, lbuf, sizeof (lbuf)); lind = 0; if (nr <= 0) { lused = 0; return nr; } lused = nr; } if (cp) *cp = lbuf[lind++]; return 1; } void zreset () { lind = lused = 0; } /* Sync the seek pointer for FD so that the kernel's idea of the last char read is the last char returned by zreadc. */ void zsyncfd (fd) int fd; { off_t off; off = lused - lind; if (off > 0) lseek (fd, -off, SEEK_CUR); lused = lind = 0; }
/* Copyright (C) 1999-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #include <sys/types.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <errno.h> #if !defined (errno) extern int errno; #endif /* Write NB bytes from BUF to file descriptor FD, retrying the write if it is interrupted. We retry three times if we get a zero-length write. Any other signal causes this function to return prematurely. */ int zwrite (fd, buf, nb) int fd; char *buf; size_t nb; { int n, i, nt; for (n = nb, nt = 0;;) { i = write (fd, buf, n); if (i > 0) { n -= i; if (n <= 0) return nb; buf += i; } else if (i == 0) { if (++nt > 3) return (nb - n); } else if (errno != EINTR) return -1; } }
/* Copyright (C) 1999 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ /* * shtty.c -- abstract interface to the terminal, focusing on capabilities. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #ifdef HAVE_UNISTD_H # include <unistd.h> #endif #include <shtty.h> static TTYSTRUCT ttin, ttout; static int ttsaved = 0; int ttgetattr(fd, ttp) int fd; TTYSTRUCT *ttp; { #ifdef TERMIOS_TTY_DRIVER return tcgetattr(fd, ttp); #else # ifdef TERMIO_TTY_DRIVER return ioctl(fd, TCGETA, ttp); # else return ioctl(fd, TIOCGETP, ttp); # endif #endif } int ttsetattr(fd, ttp) int fd; TTYSTRUCT *ttp; { #ifdef TERMIOS_TTY_DRIVER return tcsetattr(fd, TCSADRAIN, ttp); #else # ifdef TERMIO_TTY_DRIVER return ioctl(fd, TCSETAW, ttp); # else return ioctl(fd, TIOCSETN, ttp); # endif #endif } void ttsave() { if (ttsaved) return; ttgetattr (0, &ttin); ttgetattr (1, &ttout); ttsaved = 1; } void ttrestore() { if (ttsaved == 0) return; ttsetattr (0, &ttin); ttsetattr (1, &ttout); ttsaved = 0; } /* Retrieve the attributes associated with tty fd FD. */ TTYSTRUCT * ttattr (fd) int fd; { if (ttsaved == 0) return ((TTYSTRUCT *)0); if (fd == 0) return &ttin; else if (fd == 1) return &ttout; else return ((TTYSTRUCT *)0); } /* * Change attributes in ttp so that when it is installed using * ttsetattr, the terminal will be in one-char-at-a-time mode. */ int tt_setonechar(ttp) TTYSTRUCT *ttp; { #if defined (TERMIOS_TTY_DRIVER) || defined (TERMIO_TTY_DRIVER) /* XXX - might not want this -- it disables erase and kill processing. */ ttp->c_lflag &= ~ICANON; ttp->c_lflag |= ISIG; # ifdef IEXTEN ttp->c_lflag |= IEXTEN; # endif ttp->c_iflag |= ICRNL; /* make sure we get CR->NL on input */ ttp->c_iflag &= ~INLCR; /* but no NL->CR */ # ifdef OPOST ttp->c_oflag |= OPOST; # endif # ifdef ONLCR ttp->c_oflag |= ONLCR; # endif # ifdef OCRNL ttp->c_oflag &= ~OCRNL; # endif # ifdef ONOCR ttp->c_oflag &= ~ONOCR; # endif # ifdef ONLRET ttp->c_oflag &= ~ONLRET; # endif ttp->c_cc[VMIN] = 1; ttp->c_cc[VTIME] = 0; #else ttp->sg_flags |= CBREAK; #endif return 0; } /* Set the terminal into one-character-at-a-time mode */ int ttonechar () { TTYSTRUCT tt; if (ttsaved == 0) return -1; tt = ttin; if (tt_setonechar(&tt) < 0) return -1; return (ttsetattr (0, &tt)); } /* * Change attributes in ttp so that when it is installed using * ttsetattr, the terminal will be in no-echo mode. */ int tt_setnoecho(ttp) TTYSTRUCT *ttp; { #if defined (TERMIOS_TTY_DRIVER) || defined (TERMIO_TTY_DRIVER) ttp->c_lflag &= ~(ECHO|ECHOK|ECHONL); #else ttp->sg_flags &= ~ECHO; #endif return 0; } /* Set the terminal into no-echo mode */ int ttnoecho () { TTYSTRUCT tt; if (ttsaved == 0) return -1; tt = ttin; if (tt_setnoecho (&tt) < 0) return -1; return (ttsetattr (0, &tt)); } /* * Change attributes in ttp so that when it is installed using * ttsetattr, the terminal will be in eight-bit mode (pass8). */ int tt_seteightbit (ttp) TTYSTRUCT *ttp; { #if defined (TERMIOS_TTY_DRIVER) || defined (TERMIO_TTY_DRIVER) ttp->c_iflag &= ~ISTRIP; ttp->c_cflag |= CS8; ttp->c_cflag &= ~PARENB; #else ttp->sg_flags |= ANYP; #endif return 0; } /* Set the terminal into eight-bit mode */ int tteightbit () { TTYSTRUCT tt; if (ttsaved == 0) return -1; tt = ttin; if (tt_seteightbit (&tt) < 0) return -1; return (ttsetattr (0, &tt)); } /* * Change attributes in ttp so that when it is installed using * ttsetattr, the terminal will be in non-canonical input mode. */ int tt_setnocanon (ttp) TTYSTRUCT *ttp; { #if defined (TERMIOS_TTY_DRIVER) || defined (TERMIO_TTY_DRIVER) ttp->c_lflag &= ~ICANON; #endif return 0; } /* Set the terminal into non-canonical mode */ int ttnocanon () { TTYSTRUCT tt; if (ttsaved == 0) return -1; tt = ttin; if (tt_setnocanon (&tt) < 0) return -1; return (ttsetattr (0, &tt)); } /* * Change attributes in ttp so that when it is installed using * ttsetattr, the terminal will be in cbreak, no-echo mode. */ int tt_setcbreak(ttp) TTYSTRUCT *ttp; { if (tt_setonechar (ttp) < 0) return -1; return (tt_setnoecho (ttp)); } /* Set the terminal into cbreak (no-echo, one-character-at-a-time) mode */ int ttcbreak () { TTYSTRUCT tt; if (ttsaved == 0) return -1; tt = ttin; if (tt_setcbreak (&tt) < 0) return -1; return (ttsetattr (0, &tt)); }
/* Snagged from GNU C library, version 2.0.3. */ /* * ++Copyright++ 1983, 1990, 1993 * - * Copyright (c) 1983, 1990, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * Portions Copyright (c) 1993 by Digital Equipment Corporation. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies, and that * the name of Digital Equipment Corporation not be used in advertising or * publicity pertaining to distribution of the document or software without * specific, written prior permission. * * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DIGITAL EQUIPMENT * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS * SOFTWARE. * - * --Copyright-- */ #if defined(LIBC_SCCS) && !defined(lint) static char sccsid[] = "@(#)inet_addr.c 8.1 (Berkeley) 6/17/93"; static char rcsid[] = "$Id: inet_addr.c,v 1.5 1996/08/14 03:48:37 drepper Exp $"; #endif /* LIBC_SCCS and not lint */ #include <config.h> #if !defined (HAVE_INET_ATON) && defined (HAVE_NETWORK) && defined (HAVE_NETINET_IN_H) && defined (HAVE_ARPA_INET_H) #include <sys/types.h> #include <sys/param.h> #include <netinet/in.h> #include <arpa/inet.h> #ifdef HAVE_UNISTD_H # include <unistd.h> #endif #include <bashansi.h> #include <ctype.h> #include <stdc.h> #ifndef INADDR_NONE # define INADDR_NONE 0xffffffff #endif /* these are compatibility routines, not needed on recent BSD releases */ #if 0 /* Not used, not needed. */ /* * Ascii internet address interpretation routine. * The value returned is in network order. */ u_long inet_addr(cp) register const char *cp; { struct in_addr val; if (inet_aton(cp, &val)) return (val.s_addr); return (INADDR_NONE); } #endif /* * Check whether "cp" is a valid ascii representation * of an Internet address and convert to a binary address. * Returns 1 if the address is valid, 0 if not. * This replaces inet_addr, the return value from which * cannot distinguish between failure and a local broadcast address. */ int inet_aton(cp, addr) register const char *cp; struct in_addr *addr; { register u_bits32_t val; register int base, n; register unsigned char c; u_int parts[4]; register u_int *pp = parts; c = *cp; for (;;) { /* * Collect number up to ``.''. * Values are specified as for C: * 0x=hex, 0=octal, isdigit=decimal. */ #if 0 if (!isdigit(c)) #else if (c != '0' && c != '1' && c != '2' && c != '3' && c != '4' && c != '5' && c != '6' && c != '7' && c != '8' && c != '9') #endif return (0); val = 0; base = 10; if (c == '0') { c = *++cp; if (c == 'x' || c == 'X') base = 16, c = *++cp; else base = 8; } for (;;) { if (isascii(c) && isdigit(c)) { val = (val * base) + (c - '0'); c = *++cp; } else if (base == 16 && isascii(c) && isxdigit(c)) { val = (val << 4) | (c + 10 - (islower(c) ? 'a' : 'A')); c = *++cp; } else break; } if (c == '.') { /* * Internet format: * a.b.c.d * a.b.c (with c treated as 16 bits) * a.b (with b treated as 24 bits) */ if (pp >= parts + 3) return (0); *pp++ = val; c = *++cp; } else break; } /* * Check for trailing characters. */ if (c != '\0' && (!isascii(c) || !isspace(c))) return (0); /* * Concoct the address according to * the number of parts specified. */ n = pp - parts + 1; switch (n) { case 0: return (0); /* initial nondigit */ case 1: /* a -- 32 bits */ break; case 2: /* a.b -- 8.24 bits */ if (val > 0xffffff) return (0); val |= parts[0] << 24; break; case 3: /* a.b.c -- 8.8.16 bits */ if (val > 0xffff) return (0); val |= (parts[0] << 24) | (parts[1] << 16); break; case 4: /* a.b.c.d -- 8.8.8.8 bits */ if (val > 0xff) return (0); val |= (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8); break; } if (addr) addr->s_addr = htonl(val); return (1); } #endif /* !HAVE_INET_ATON */
/* * netopen.c -- functions to make tcp/udp connections * * Chet Ramey * chet@ins.CWRU.Edu */ /* Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #if defined (HAVE_NETWORK) #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <stdio.h> #include <sys/types.h> #if defined (HAVE_SYS_SOCKET_H) # include <sys/socket.h> #endif #if defined (HAVE_NETINET_IN_H) # include <netinet/in.h> #endif #if defined (HAVE_NETDB_H) # include <netdb.h> #endif #if defined (HAVE_ARPA_INET_H) # include <arpa/inet.h> #endif #include <bashansi.h> #include <errno.h> #include <shell.h> #include <xmalloc.h> #ifndef errno extern int errno; #endif #if !defined (HAVE_INET_ATON) extern int inet_aton __P((const char *, struct in_addr *)); #endif #ifndef HAVE_GETADDRINFO /* Stuff the internet address corresponding to HOST into AP, in network byte order. Return 1 on success, 0 on failure. */ static int _getaddr (host, ap) char *host; struct in_addr *ap; { struct hostent *h; int r; r = 0; if (host[0] >= '0' && host[0] <= '9') { /* If the first character is a digit, guess that it's an Internet address and return immediately if inet_aton succeeds. */ r = inet_aton (host, ap); if (r) return r; } #if !defined (HAVE_GETHOSTBYNAME) return 0; #else h = gethostbyname (host); if (h && h->h_addr) { bcopy(h->h_addr, (char *)ap, h->h_length); return 1; } #endif return 0; } /* Return 1 if SERV is a valid port number and stuff the converted value into PP in network byte order. */ static int _getserv (serv, proto, pp) char *serv; int proto; unsigned short *pp; { intmax_t l; unsigned short s; if (legal_number (serv, &l)) { s = (unsigned short)(l & 0xFFFF); if (s != l) return (0); s = htons (s); if (pp) *pp = s; return 1; } else #if defined (HAVE_GETSERVBYNAME) { struct servent *se; se = getservbyname (serv, (proto == 't') ? "tcp" : "udp"); if (se == 0) return 0; if (pp) *pp = se->s_port; /* ports returned in network byte order */ return 1; } #else /* !HAVE_GETSERVBYNAME */ return 0; #endif /* !HAVE_GETSERVBYNAME */ } /* * Open a TCP or UDP connection to HOST on port SERV. Uses the * traditional BSD mechanisms. Returns the connected socket or -1 on error. */ static int _netopen4(host, serv, typ) char *host, *serv; int typ; { struct in_addr ina; struct sockaddr_in sin; unsigned short p; int s, e; if (_getaddr(host, &ina) == 0) { internal_error ("%s: host unknown", host); errno = EINVAL; return -1; } if (_getserv(serv, typ, &p) == 0) { internal_error("%s: invalid service", serv); errno = EINVAL; return -1; } memset ((char *)&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = p; sin.sin_addr = ina; s = socket(AF_INET, (typ == 't') ? SOCK_STREAM : SOCK_DGRAM, 0); if (s < 0) { sys_error ("socket"); return (-1); } if (connect (s, (struct sockaddr *)&sin, sizeof (sin)) < 0) { e = errno; sys_error("connect"); close(s); errno = e; return (-1); } return(s); } #endif /* ! HAVE_GETADDRINFO */ #ifdef HAVE_GETADDRINFO /* * Open a TCP or UDP connection to HOST on port SERV. Uses getaddrinfo(3) * which provides support for IPv6. Returns the connected socket or -1 * on error. */ static int _netopen6 (host, serv, typ) char *host, *serv; int typ; { int s, e; struct addrinfo hints, *res, *res0; int gerr; memset ((char *)&hints, 0, sizeof (hints)); /* XXX -- if problems with IPv6, set to PF_INET for IPv4 only */ #ifdef DEBUG /* PF_INET is the one that works for me */ hints.ai_family = PF_INET; #else hints.ai_family = PF_UNSPEC; #endif hints.ai_socktype = (typ == 't') ? SOCK_STREAM : SOCK_DGRAM; gerr = getaddrinfo (host, serv, &hints, &res0); if (gerr) { if (gerr == EAI_SERVICE) internal_error ("%s: %s", serv, gai_strerror (gerr)); else internal_error ("%s: %s", host, gai_strerror (gerr)); errno = EINVAL; return -1; } for (res = res0; res; res = res->ai_next) { if ((s = socket (res->ai_family, res->ai_socktype, res->ai_protocol)) < 0) { if (res->ai_next) continue; sys_error ("socket"); freeaddrinfo (res0); return -1; } if (connect (s, res->ai_addr, res->ai_addrlen) < 0) { if (res->ai_next) { close (s); continue; } e = errno; sys_error ("connect"); close (s); freeaddrinfo (res0); errno = e; return -1; } freeaddrinfo (res0); break; } return s; } #endif /* HAVE_GETADDRINFO */ /* * Open a TCP or UDP connection to HOST on port SERV. Uses getaddrinfo(3) * if available, falling back to the traditional BSD mechanisms otherwise. * Returns the connected socket or -1 on error. */ static int _netopen(host, serv, typ) char *host, *serv; int typ; { #ifdef HAVE_GETADDRINFO return (_netopen6 (host, serv, typ)); #else return (_netopen4 (host, serv, typ)); #endif } /* * Open a TCP or UDP connection given a path like `/dev/tcp/host/port' to * host `host' on port `port' and return the connected socket. */ int netopen (path) char *path; { char *np, *s, *t; int fd; np = (char *)xmalloc (strlen (path) + 1); strcpy (np, path); s = np + 9; t = strchr (s, '/'); if (t == 0) { internal_error ("%s: bad network path specification", path); return -1; } *t++ = '\0'; fd = _netopen (s, t, path[5]); free (np); return fd; } #if 0 /* * Open a TCP connection to host `host' on the port defined for service * `serv' and return the connected socket. */ int tcpopen (host, serv) char *host, *serv; { return (_netopen (host, serv, 't')); } /* * Open a UDP connection to host `host' on the port defined for service * `serv' and return the connected socket. */ int udpopen (host, serv) char *host, *serv; { return _netopen (host, serv, 'u'); } #endif #else /* !HAVE_NETWORK */ int netopen (path) char *path; { internal_error ("network operations not supported"); return -1; } #endif /* !HAVE_NETWORK */
/* Copyright (C) 1991, 1994 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. Bugs can be reported to bug-glibc@prep.ai.mit.edu. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #if !defined (HAVE_STRPBRK) #include <stdc.h> /* Find the first ocurrence in S of any character in ACCEPT. */ char * strpbrk (s, accept) register const char *s; register const char *accept; { while (*s != '\0') { const char *a = accept; while (*a != '\0') if (*a++ == *s) return (char *) s; ++s; } return 0; } #endif
/* timeval.c - functions to perform operations on struct timevals */ /* Copyright (C) 1999 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #if defined (HAVE_TIMEVAL) #include <sys/types.h> #include <posixtime.h> #include <stdio.h> struct timeval * difftimeval (d, t1, t2) struct timeval *d, *t1, *t2; { d->tv_sec = t2->tv_sec - t1->tv_sec; d->tv_usec = t2->tv_usec - t1->tv_usec; if (d->tv_usec < 0) { d->tv_usec += 1000000; d->tv_sec -= 1; if (d->tv_sec < 0) /* ??? -- BSD/OS does this */ { d->tv_sec = 0; d->tv_usec = 0; } } return d; } struct timeval * addtimeval (d, t1, t2) struct timeval *d, *t1, *t2; { d->tv_sec = t1->tv_sec + t2->tv_sec; d->tv_usec = t1->tv_usec + t2->tv_usec; if (d->tv_usec >= 1000000) { d->tv_usec -= 1000000; d->tv_sec += 1; } return d; } /* Do "cpu = ((user + sys) * 10000) / real;" with timevals. Barely-tested code from Deven T. Corzine <deven@ties.org>. */ int timeval_to_cpu (rt, ut, st) struct timeval *rt, *ut, *st; /* real, user, sys */ { struct timeval t1, t2; register int i; addtimeval (&t1, ut, st); t2.tv_sec = rt->tv_sec; t2.tv_usec = rt->tv_usec; for (i = 0; i < 6; i++) { if ((t1.tv_sec > 99999999) || (t2.tv_sec > 99999999)) break; t1.tv_sec *= 10; t1.tv_sec += t1.tv_usec / 100000; t1.tv_usec *= 10; t1.tv_usec %= 1000000; t2.tv_sec *= 10; t2.tv_sec += t2.tv_usec / 100000; t2.tv_usec *= 10; t2.tv_usec %= 1000000; } for (i = 0; i < 4; i++) { if (t1.tv_sec < 100000000) t1.tv_sec *= 10; else t2.tv_sec /= 10; } return ((t2.tv_sec == 0) ? 0 : t1.tv_sec / t2.tv_sec); } /* Convert a pointer to a struct timeval to seconds and thousandths of a second, returning the values in *SP and *SFP, respectively. This does rounding on the fractional part, not just truncation to three places. */ void timeval_to_secs (tvp, sp, sfp) struct timeval *tvp; time_t *sp; int *sfp; { int rest; *sp = tvp->tv_sec; *sfp = tvp->tv_usec % 1000000; /* pretty much a no-op */ rest = *sfp % 1000; *sfp = (*sfp * 1000) / 1000000; if (rest >= 500) *sfp += 1; /* Sanity check */ if (*sfp >= 1000) { *sp += 1; *sfp -= 1000; } } /* Print the contents of a struct timeval * in a standard way to stdio stream FP. */ void print_timeval (fp, tvp) FILE *fp; struct timeval *tvp; { time_t timestamp; long minutes; int seconds, seconds_fraction; timeval_to_secs (tvp, ×tamp, &seconds_fraction); minutes = timestamp / 60; seconds = timestamp % 60; fprintf (fp, "%ldm%d.%03ds", minutes, seconds, seconds_fraction); } #endif /* HAVE_TIMEVAL */
/* clock.c - operations on struct tms and clock_t's */ /* Copyright (C) 1999 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA */ #include <config.h> #if defined (HAVE_TIMES) #include <sys/types.h> #include <posixtime.h> #if defined (HAVE_SYS_TIMES_H) # include <sys/times.h> #endif #include <stdio.h> #include <stdc.h> extern long get_clk_tck __P((void)); void clock_t_to_secs (t, sp, sfp) clock_t t; time_t *sp; int *sfp; { static long clk_tck = -1; if (clk_tck == -1) clk_tck = get_clk_tck (); *sfp = t % clk_tck; *sfp = (*sfp * 1000) / clk_tck; *sp = t / clk_tck; /* Sanity check */ if (*sfp >= 1000) { *sp += 1; *sfp -= 1000; } } /* Print the time defined by a clock_t (returned by the `times' and `time' system calls) in a standard way to stdio stream FP. This is scaled in terms of the value of CLK_TCK, which is what is returned by the `times' call. */ void print_clock_t (fp, t) FILE *fp; clock_t t; { time_t timestamp; long minutes; int seconds, seconds_fraction; clock_t_to_secs (t, ×tamp, &seconds_fraction); minutes = timestamp / 60; seconds = timestamp % 60; fprintf (fp, "%ldm%d.%03ds", minutes, seconds, seconds_fraction); } #endif /* HAVE_TIMES */
/* makepath.c - glue PATH and DIR together into a full pathname. */ /* Copyright (C) 1987, 1989, 1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include <bashansi.h> #include "shell.h" #include <tilde/tilde.h> #ifndef NULL # define NULL 0 #endif /* MAKE SURE THESE AGREE WITH ../../externs.h. */ #ifndef MP_DOTILDE # define MP_DOTILDE 0x01 # define MP_DOCWD 0x02 # define MP_RMDOT 0x04 #endif extern char *get_working_directory __P((char *)); /* Take PATH, an element from, e.g., $CDPATH, and DIR, a directory name, and paste them together into PATH/DIR. Tilde expansion is performed on PATH if (flags & MP_DOTILDE) is non-zero. If PATH is NULL or the empty string, it is converted to the current directory. A full pathname is used if (flags & MP_DOCWD) is non-zero, otherwise `./' is used. If (flags & MP_RMDOT) is non-zero, any `./' is removed from the beginning of DIR. */ #define MAKEDOT() \ do { \ xpath = (char *)xmalloc (2); \ xpath[0] = '.'; \ xpath[1] = '\0'; \ pathlen = 1; \ } while (0) char * sh_makepath (path, dir, flags) const char *path, *dir; int flags; { int dirlen, pathlen; char *ret, *xpath, *xdir, *r, *s; if (path == 0 || *path == '\0') { if (flags & MP_DOCWD) { xpath = get_working_directory ("sh_makepath"); if (xpath == 0) { ret = get_string_value ("PWD"); if (ret) xpath = savestring (ret); } if (xpath == 0) MAKEDOT(); else pathlen = strlen (xpath); } else MAKEDOT(); } else { xpath = ((flags & MP_DOTILDE) && *path == '~') ? bash_tilde_expand (path, 0) : (char *)path; pathlen = strlen (xpath); } xdir = (char *)dir; dirlen = strlen (xdir); if ((flags & MP_RMDOT) && dir[0] == '.' && dir[1] == '/') { xdir += 2; dirlen -= 2; } r = ret = (char *)xmalloc (2 + dirlen + pathlen); s = xpath; while (*s) *r++ = *s++; if (s[-1] != '/') *r++ = '/'; s = xdir; while (*r++ = *s++) ; if (xpath != path) free (xpath); return (ret); }
/* pathcanon.c -- Canonicalize and manipulate pathnames. */ /* Copyright (C) 2000 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #include <bashtypes.h> #ifndef _MINIX # include <sys/param.h> #endif #include <posixstat.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <filecntl.h> #include <bashansi.h> #include <stdio.h> #include <chartypes.h> #include "shell.h" #if defined (__CYGWIN__) #include <sys/cygwin.h> static int _is_cygdrive (path) char *path; { static char user[MAXPATHLEN]; static char system[MAXPATHLEN]; static int first_time = 1; /* If the path is the first part of a network path, treat it as existing. */ if (path[0] == '/' && path[1] == '/' && !strchr (path + 2, '/')) return 1; /* Otherwise check for /cygdrive prefix. */ if (first_time) { char user_flags[MAXPATHLEN]; char system_flags[MAXPATHLEN]; /* Get the cygdrive info */ cygwin_internal (CW_GET_CYGDRIVE_INFO, user, system, user_flags, system_flags); first_time = 0; } return !strcasecmp (path, user) || !strcasecmp (path, system); } #endif /* __CYGWIN__ */ /* Return 1 if PATH corresponds to a directory. A function for debugging. */ static int _path_isdir (path) char *path; { int l; struct stat sb; l = stat (path, &sb) == 0 && S_ISDIR (sb.st_mode); #if defined (__CYGWIN__) if (l == 0) l = _is_cygdrive (path); #endif return l; } /* Canonicalize PATH, and return a new path. The new path differs from PATH in that: Multple `/'s are collapsed to a single `/'. Leading `./'s and trailing `/.'s are removed. Trailing `/'s are removed. Non-leading `../'s and trailing `..'s are handled by removing portions of the path. */ /* Look for ROOTEDPATH, PATHSEP, DIRSEP, and ISDIRSEP in ../../general.h */ #define DOUBLE_SLASH(p) ((p[0] == '/') && (p[1] == '/') && p[2] != '/') char * sh_canonpath (path, flags) char *path; int flags; { char stub_char; char *result, *p, *q, *base, *dotdot; int rooted, double_slash_path; /* The result cannot be larger than the input PATH. */ result = (flags & PATH_NOALLOC) ? path : savestring (path); /* POSIX.2 says to leave a leading `//' alone. On cygwin, we skip over any leading `x:' (dos drive name). */ if (rooted = ROOTEDPATH(path)) { stub_char = DIRSEP; #if defined (__CYGWIN__) base = (ISALPHA((unsigned char)result[0]) && result[1] == ':') ? result + 3 : result + 1; #else base = result + 1; #endif double_slash_path = DOUBLE_SLASH (path); base += double_slash_path; } else { stub_char = '.'; #if defined (__CYGWIN__) base = (ISALPHA((unsigned char)result[0]) && result[1] == ':') ? result + 2 : result; #else base = result; #endif double_slash_path = 0; } /* * invariants: * base points to the portion of the path we want to modify * p points at beginning of path element we're considering. * q points just past the last path element we wrote (no slash). * dotdot points just past the point where .. cannot backtrack * any further (no slash). */ p = q = dotdot = base; while (*p) { if (ISDIRSEP(p[0])) /* null element */ p++; else if(p[0] == '.' && PATHSEP(p[1])) /* . and ./ */ p += 1; /* don't count the separator in case it is nul */ else if (p[0] == '.' && p[1] == '.' && PATHSEP(p[2])) /* .. and ../ */ { p += 2; /* skip `..' */ if (q > dotdot) /* can backtrack */ { if (flags & PATH_CHECKDOTDOT) { char c; /* Make sure what we have so far corresponds to a valid path before we chop some of it off. */ c = *q; *q = '\0'; if (_path_isdir (result) == 0) { if ((flags & PATH_NOALLOC) == 0) free (result); return ((char *)NULL); } *q = c; } while (--q > dotdot && ISDIRSEP(*q) == 0) ; } else if (rooted == 0) { /* /.. is / but ./../ is .. */ if (q != base) *q++ = DIRSEP; *q++ = '.'; *q++ = '.'; dotdot = q; } } else /* real path element */ { /* add separator if not at start of work portion of result */ if (q != base) *q++ = DIRSEP; while (*p && (ISDIRSEP(*p) == 0)) *q++ = *p++; /* Check here for a valid directory with _path_isdir. */ if (flags & PATH_CHECKEXISTS) { char c; /* Make sure what we have so far corresponds to a valid path before we chop some of it off. */ c = *q; *q = '\0'; if (_path_isdir (result) == 0) { if ((flags & PATH_NOALLOC) == 0) free (result); return ((char *)NULL); } *q = c; } } } /* Empty string is really ``.'' or `/', depending on what we started with. */ if (q == result) *q++ = stub_char; *q = '\0'; /* If the result starts with `//', but the original path does not, we can turn the // into /. Because of how we set `base', this should never be true, but it's a sanity check. */ if (DOUBLE_SLASH(result) && double_slash_path == 0) { if (result[2] == '\0') /* short-circuit for bare `//' */ result[1] = '\0'; else strcpy (result, result + 1); } return (result); }
/* pathphys.c -- Return pathname with all symlinks expanded. */ /* Copyright (C) 2000 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #include <bashtypes.h> #ifndef _MINIX # include <sys/param.h> #endif #include <posixstat.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <filecntl.h> #include <bashansi.h> #include <stdio.h> #include <chartypes.h> #include <errno.h> #include "shell.h" #if !defined (MAXSYMLINKS) # define MAXSYMLINKS 32 #endif #if !defined (errno) extern int errno; #endif /* !errno */ extern char *get_working_directory __P((char *)); static int _path_readlink (path, buf, bufsiz) char *path; char *buf; int bufsiz; { #ifdef HAVE_READLINK return readlink (path, buf, bufsiz); #else errno = EINVAL; return -1; #endif } /* Look for ROOTEDPATH, PATHSEP, DIRSEP, and ISDIRSEP in ../../general.h */ #define DOUBLE_SLASH(p) ((p[0] == '/') && (p[1] == '/') && p[2] != '/') /* * Return PATH with all symlinks expanded in newly-allocated memory. * This always gets a full pathname. */ char * sh_physpath (path, flags) char *path; int flags; { char tbuf[PATH_MAX+1], linkbuf[PATH_MAX+1]; char *result, *p, *q, *qsave, *qbase, *workpath; int double_slash_path, linklen, nlink; nlink = 0; q = result = (char *)xmalloc (PATH_MAX + 1); workpath = (char *)xmalloc (PATH_MAX + 1); strcpy (workpath, path); /* This always gets an absolute pathname. */ /* POSIX.2 says to leave a leading `//' alone. On cygwin, we skip over any leading `x:' (dos drive name). */ #if defined (__CYGWIN__) qbase = (ISALPHA((unsigned char)workpath[0]) && workpath[1] == ':') ? workpath + 3 : workpath + 1; #else qbase = workpath + 1; #endif double_slash_path = DOUBLE_SLASH (workpath); qbase += double_slash_path; for (p = workpath; p < qbase; ) *q++ = *p++; qbase = q; /* * invariants: * qbase points to the portion of the result path we want to modify * p points at beginning of path element we're considering. * q points just past the last path element we wrote (no slash). * * XXX -- need to fix error checking for too-long pathnames */ while (*p) { if (ISDIRSEP(p[0])) /* null element */ p++; else if(p[0] == '.' && PATHSEP(p[1])) /* . and ./ */ p += 1; /* don't count the separator in case it is nul */ else if (p[0] == '.' && p[1] == '.' && PATHSEP(p[2])) /* .. and ../ */ { p += 2; /* skip `..' */ if (q > qbase) { while (--q > qbase && ISDIRSEP(*q) == 0) ; } } else /* real path element */ { /* add separator if not at start of work portion of result */ qsave = q; if (q != qbase) *q++ = DIRSEP; while (*p && (ISDIRSEP(*p) == 0)) *q++ = *p++; *q = '\0'; linklen = _path_readlink (result, linkbuf, PATH_MAX); if (linklen < 0) /* if errno == EINVAL, it's not a symlink */ { if (errno != EINVAL) goto error; continue; } /* It's a symlink, and the value is in LINKBUF. */ nlink++; if (nlink > MAXSYMLINKS) { #ifdef ELOOP errno = ELOOP; #endif error: free (result); free (workpath); return ((char *)NULL); } linkbuf[linklen] = '\0'; /* Form the new pathname by copying the link value to a temporary buffer and appending the rest of `workpath'. Reset p to point to the start of the rest of the path. If the link value is an absolute pathname, reset p, q, and qbase. If not, reset p and q. */ strcpy (tbuf, linkbuf); tbuf[linklen] = '/'; strcpy (tbuf + linklen, p); strcpy (workpath, tbuf); if (ABSPATH(linkbuf)) { q = result; /* Duplicating some code here... */ #if defined (__CYGWIN__) qbase = (ISALPHA((unsigned char)workpath[0]) && workpath[1] == ':') ? workpath + 3 : workpath + 1; #else qbase = workpath + 1; #endif double_slash_path = DOUBLE_SLASH (workpath); qbase += double_slash_path; for (p = workpath; p < qbase; ) *q++ = *p++; qbase = q; } else { p = workpath; q = qsave; } } } *q = '\0'; free (workpath); /* If the result starts with `//', but the original path does not, we can turn the // into /. Because of how we set `qbase', this should never be true, but it's a sanity check. */ if (DOUBLE_SLASH(result) && double_slash_path == 0) { if (result[2] == '\0') /* short-circuit for bare `//' */ result[1] = '\0'; else strcpy (result, result + 1); } return (result); } char * sh_realpath (pathname, resolved) const char *pathname; char *resolved; { char *tdir, *wd; if (pathname == 0 || *pathname == '\0') { errno = (pathname == 0) ? EINVAL : ENOENT; return ((char *)NULL); } if (ABSPATH (pathname) == 0) { wd = get_working_directory ("sh_realpath"); if (wd == 0) return ((char *)NULL); tdir = sh_makepath ((char *)pathname, wd, 0); free (wd); } else tdir = savestring (pathname); wd = sh_physpath (tdir, 0); free (tdir); if (resolved == 0) return (wd); if (wd) { strncpy (resolved, wd, PATH_MAX - 1); resolved[PATH_MAX - 1] = '\0'; return resolved; } else { resolved[0] = '\0'; return wd; } }
/* stringlist.c - functions to handle a generic `list of strings' structure */ /* Copyright (C) 2000-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <stdio.h> #include <bashansi.h> #include "shell.h" #ifdef STRDUP # undef STRDUP #endif #define STRDUP(x) ((x) ? savestring (x) : (char *)NULL) /* Allocate a new STRINGLIST, with room for N strings. */ STRINGLIST * strlist_create (n) int n; { STRINGLIST *ret; register int i; ret = (STRINGLIST *)xmalloc (sizeof (STRINGLIST)); if (n) { ret->list = strvec_create (n+1); ret->list_size = n; for (i = 0; i < n; i++) ret->list[i] = (char *)NULL; } else { ret->list = (char **)NULL; ret->list_size = 0; } ret->list_len = 0; return ret; } STRINGLIST * strlist_resize (sl, n) STRINGLIST *sl; int n; { register int i; if (sl == 0) return (sl = strlist_create (n)); if (n > sl->list_size) { sl->list = strvec_resize (sl->list, n + 1); for (i = sl->list_size; i <= n; i++) sl->list[i] = (char *)NULL; sl->list_size = n; } return sl; } void strlist_flush (sl) STRINGLIST *sl; { if (sl == 0 || sl->list == 0) return; strvec_flush (sl->list); sl->list_len = 0; } void strlist_dispose (sl) STRINGLIST *sl; { if (sl == 0) return; if (sl->list) strvec_dispose (sl->list); free (sl); } int strlist_remove (sl, s) STRINGLIST *sl; char *s; { int r; if (sl == 0 || sl->list == 0 || sl->list_len == 0) return 0; r = strvec_remove (sl->list, s); if (r) sl->list_len--; return r; } STRINGLIST * strlist_copy (sl) STRINGLIST *sl; { STRINGLIST *new; register int i; if (sl == 0) return ((STRINGLIST *)0); new = strlist_create (sl->list_size); /* I'd like to use strvec_copy, but that doesn't copy everything. */ if (sl->list) { for (i = 0; i < sl->list_size; i++) new->list[i] = STRDUP (sl->list[i]); } new->list_size = sl->list_size; new->list_len = sl->list_len; /* just being careful */ if (new->list) new->list[new->list_len] = (char *)NULL; return new; } /* Return a new STRINGLIST with everything from M1 and M2. */ STRINGLIST * strlist_merge (m1, m2) STRINGLIST *m1, *m2; { STRINGLIST *sl; int i, n, l1, l2; l1 = m1 ? m1->list_len : 0; l2 = m2 ? m2->list_len : 0; sl = strlist_create (l1 + l2 + 1); for (i = n = 0; i < l1; i++, n++) sl->list[n] = STRDUP (m1->list[i]); for (i = 0; i < l2; i++, n++) sl->list[n] = STRDUP (m2->list[i]); sl->list_len = n; sl->list[n] = (char *)NULL; return (sl); } /* Make STRINGLIST M1 contain everything in M1 and M2. */ STRINGLIST * strlist_append (m1, m2) STRINGLIST *m1, *m2; { register int i, n, len1, len2; if (m1 == 0) return (m2 ? strlist_copy (m2) : (STRINGLIST *)0); len1 = m1->list_len; len2 = m2 ? m2->list_len : 0; if (len2) { m1 = strlist_resize (m1, len1 + len2 + 1); for (i = 0, n = len1; i < len2; i++, n++) m1->list[n] = STRDUP (m2->list[i]); m1->list[n] = (char *)NULL; m1->list_len = n; } return m1; } STRINGLIST * strlist_prefix_suffix (sl, prefix, suffix) STRINGLIST *sl; char *prefix, *suffix; { int plen, slen, tlen, llen, i; char *t; if (sl == 0 || sl->list == 0 || sl->list_len == 0) return sl; plen = STRLEN (prefix); slen = STRLEN (suffix); if (plen == 0 && slen == 0) return (sl); for (i = 0; i < sl->list_len; i++) { llen = STRLEN (sl->list[i]); tlen = plen + llen + slen + 1; t = (char *)xmalloc (tlen + 1); if (plen) strcpy (t, prefix); strcpy (t + plen, sl->list[i]); if (slen) strcpy (t + plen + llen, suffix); free (sl->list[i]); sl->list[i] = t; } return (sl); } void strlist_print (sl, prefix) STRINGLIST *sl; char *prefix; { register int i; if (sl == 0) return; for (i = 0; i < sl->list_len; i++) printf ("%s%s\n", prefix ? prefix : "", sl->list[i]); } void strlist_walk (sl, func) STRINGLIST *sl; sh_strlist_map_func_t *func; { register int i; if (sl == 0) return; for (i = 0; i < sl->list_len; i++) if ((*func)(sl->list[i]) < 0) break; } void strlist_sort (sl) STRINGLIST *sl; { if (sl == 0 || sl->list_len == 0 || sl->list == 0) return; strvec_sort (sl->list); } STRINGLIST * strlist_from_word_list (list, alloc, starting_index, ip) WORD_LIST *list; int alloc, starting_index, *ip; { STRINGLIST *ret; int slen, len; if (list == 0) { if (ip) *ip = 0; return ((STRINGLIST *)0); } slen = list_length (list); ret = (STRINGLIST *)xmalloc (sizeof (STRINGLIST)); ret->list = strvec_from_word_list (list, alloc, starting_index, &len); ret->list_size = slen + starting_index; ret->list_len = len; if (ip) *ip = len; return ret; } WORD_LIST * strlist_to_word_list (sl, alloc, starting_index) STRINGLIST *sl; int alloc, starting_index; { WORD_LIST *list; if (sl == 0 || sl->list == 0) return ((WORD_LIST *)NULL); list = strvec_to_word_list (sl->list, alloc, starting_index); return list; }
/* stringvec.c - functions for managing arrays of strings. */ /* Copyright (C) 2000-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #include <bashtypes.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <bashansi.h> #include <stdio.h> #include <chartypes.h> #include "shell.h" /* Allocate an array of strings with room for N members. */ char ** strvec_create (n) int n; { return ((char **)xmalloc ((n) * sizeof (char *))); } char ** strvec_resize (array, nsize) char **array; int nsize; { return ((char **)xrealloc (array, nsize * sizeof (char *))); } /* Return the length of ARRAY, a NULL terminated array of char *. */ int strvec_len (array) char **array; { register int i; for (i = 0; array[i]; i++); return (i); } /* Free the contents of ARRAY, a NULL terminated array of char *. */ void strvec_flush (array) char **array; { register int i; if (array == 0) return; for (i = 0; array[i]; i++) free (array[i]); } void strvec_dispose (array) char **array; { if (array == 0) return; strvec_flush (array); free (array); } int strvec_remove (array, name) char **array, *name; { register int i, j; char *x; if (array == 0) return 0; for (i = 0; array[i]; i++) if (STREQ (name, array[i])) { x = array[i]; for (j = i; array[j]; j++) array[j] = array[j + 1]; free (x); return 1; } return 0; } #ifdef INCLUDE_UNUSED /* Find NAME in ARRAY. Return the index of NAME, or -1 if not present. ARRAY should be NULL terminated. */ int strvec_search (array, name) char **array, *name; { int i; for (i = 0; array[i]; i++) if (STREQ (name, array[i])) return (i); return (-1); } #endif /* Allocate and return a new copy of ARRAY and its contents. */ char ** strvec_copy (array) char **array; { register int i; int len; char **ret; len = strvec_len (array); ret = (char **)xmalloc ((len + 1) * sizeof (char *)); for (i = 0; array[i]; i++) ret[i] = savestring (array[i]); ret[i] = (char *)NULL; return (ret); } /* Comparison routine for use with qsort() on arrays of strings. Uses strcoll(3) if available, otherwise it uses strcmp(3). */ int strvec_strcmp (s1, s2) register char **s1, **s2; { #if defined (HAVE_STRCOLL) return (strcoll (*s1, *s2)); #else /* !HAVE_STRCOLL */ int result; if ((result = **s1 - **s2) == 0) result = strcmp (*s1, *s2); return (result); #endif /* !HAVE_STRCOLL */ } /* Sort ARRAY, a null terminated array of pointers to strings. */ void strvec_sort (array) char **array; { qsort (array, strvec_len (array), sizeof (char *), (QSFUNC *)strvec_strcmp); } /* Cons up a new array of words. The words are taken from LIST, which is a WORD_LIST *. If ALLOC is true, everything is malloc'ed, so you should free everything in this array when you are done. The array is NULL terminated. If IP is non-null, it gets the number of words in the returned array. STARTING_INDEX says where to start filling in the returned array; it can be used to reserve space at the beginning of the array. */ char ** strvec_from_word_list (list, alloc, starting_index, ip) WORD_LIST *list; int alloc, starting_index, *ip; { int count; char **array; count = list_length (list); array = (char **)xmalloc ((1 + count + starting_index) * sizeof (char *)); for (count = 0; count < starting_index; count++) array[count] = (char *)NULL; for (count = starting_index; list; count++, list = list->next) array[count] = alloc ? savestring (list->word->word) : list->word->word; array[count] = (char *)NULL; if (ip) *ip = count; return (array); } /* Convert an array of strings into the form used internally by the shell. ALLOC means to allocate new storage for each WORD_DESC in the returned list rather than copy the values in ARRAY. STARTING_INDEX says where in ARRAY to begin. */ WORD_LIST * strvec_to_word_list (array, alloc, starting_index) char **array; int alloc, starting_index; { WORD_LIST *list; WORD_DESC *w; int i, count; if (array == 0 || array[0] == 0) return (WORD_LIST *)NULL; for (count = 0; array[count]; count++) ; for (i = starting_index, list = (WORD_LIST *)NULL; i < count; i++) { w = make_bare_word (alloc ? array[i] : ""); if (alloc == 0) { free (w->word); w->word = array[i]; } list = make_word_list (w, list); } return (REVERSE_LIST (list, WORD_LIST *)); }
/* * tmpfile.c - functions to create and safely open temp files for the shell. */ /* Copyright (C) 2000 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #include <bashtypes.h> #include <posixstat.h> #include <posixtime.h> #include <filecntl.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <stdio.h> #include <errno.h> #include <shell.h> #ifndef errno extern int errno; #endif #define BASEOPENFLAGS (O_CREAT | O_TRUNC | O_EXCL) #define DEFAULT_TMPDIR "." /* bogus default, should be changed */ #define DEFAULT_NAMEROOT "shtmp" extern pid_t dollar_dollar_pid; static char *sys_tmpdir = (char *)NULL; static int ntmpfiles; static int tmpnamelen = -1; static unsigned long filenum = 1L; static char * get_sys_tmpdir () { struct stat sb; if (sys_tmpdir) return sys_tmpdir; #ifdef P_tmpdir sys_tmpdir = P_tmpdir; if (stat (sys_tmpdir, &sb) == 0) return sys_tmpdir; #endif sys_tmpdir = "/tmp"; if (stat (sys_tmpdir, &sb) == 0) return sys_tmpdir; sys_tmpdir = "/var/tmp"; if (stat (sys_tmpdir, &sb) == 0) return sys_tmpdir; sys_tmpdir = "/usr/tmp"; if (stat (sys_tmpdir, &sb) == 0) return sys_tmpdir; sys_tmpdir = DEFAULT_TMPDIR; return sys_tmpdir; } static char * get_tmpdir (flags) int flags; { char *tdir; tdir = (flags & MT_USETMPDIR) ? get_string_value ("TMPDIR") : (char *)NULL; if (tdir == 0) tdir = get_sys_tmpdir (); #if defined (HAVE_PATHCONF) && defined (_PC_NAME_MAX) if (tmpnamelen == -1) tmpnamelen = pathconf (tdir, _PC_NAME_MAX); #else tmpnamelen = 0; #endif return tdir; } char * sh_mktmpname (nameroot, flags) char *nameroot; int flags; { char *filename, *tdir, *lroot; struct stat sb; int r, tdlen; filename = (char *)xmalloc (PATH_MAX + 1); tdir = get_tmpdir (flags); tdlen = strlen (tdir); lroot = nameroot ? nameroot : DEFAULT_NAMEROOT; #ifdef USE_MKTEMP sprintf (filename, "%s/%s.XXXXXX", tdir, lroot); if (mktemp (filename) == 0) { free (filename); filename = NULL; } #else /* !USE_MKTEMP */ while (1) { filenum = (filenum << 1) ^ (unsigned long) time ((time_t *)0) ^ (unsigned long) dollar_dollar_pid ^ (unsigned long) ((flags & MT_USERANDOM) ? get_random_number () : ntmpfiles++); sprintf (filename, "%s/%s-%lu", tdir, lroot, filenum); if (tmpnamelen > 0 && tmpnamelen < 32) filename[tdlen + 1 + tmpnamelen] = '\0'; # ifdef HAVE_LSTAT r = lstat (filename, &sb); # else r = stat (filename, &sb); # endif if (r < 0 && errno == ENOENT) break; } #endif /* !USE_MKTEMP */ return filename; } int sh_mktmpfd (nameroot, flags, namep) char *nameroot; int flags; char **namep; { char *filename, *tdir, *lroot; int fd, tdlen; filename = (char *)xmalloc (PATH_MAX + 1); tdir = get_tmpdir (flags); tdlen = strlen (tdir); lroot = nameroot ? nameroot : DEFAULT_NAMEROOT; #ifdef USE_MKSTEMP sprintf (filename, "%s/%s.XXXXXX", tdir, lroot); fd = mkstemp (filename); if (fd < 0 || namep == 0) { free (filename); filename = NULL; } if (namep) *namep = filename; return fd; #else /* !USE_MKSTEMP */ do { filenum = (filenum << 1) ^ (unsigned long) time ((time_t *)0) ^ (unsigned long) dollar_dollar_pid ^ (unsigned long) ((flags & MT_USERANDOM) ? get_random_number () : ntmpfiles++); sprintf (filename, "%s/%s-%lu", tdir, lroot, filenum); if (tmpnamelen > 0 && tmpnamelen < 32) filename[tdlen + 1 + tmpnamelen] = '\0'; fd = open (filename, BASEOPENFLAGS | ((flags & MT_READWRITE) ? O_RDWR : O_WRONLY), 0600); } while (fd < 0 && errno == EEXIST); if (namep) *namep = filename; else free (filename); return fd; #endif /* !USE_MKSTEMP */ } FILE * sh_mktmpfp (nameroot, flags, namep) char *nameroot; int flags; char **namep; { int fd; FILE *fp; fd = sh_mktmpfd (nameroot, flags, namep); if (fd < 0) return ((FILE *)NULL); fp = fdopen (fd, (flags & MT_READWRITE) ? "w+" : "w"); if (fp == 0) close (fd); return fp; }
/* posixtime.h -- wrapper for time.h, sys/times.h mess. */ /* Copyright (C) 1999 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #ifndef _POSIXTIME_H_ #define _POSIXTIME_H_ /* include this after config.h */ /* Some systems require this, mostly for the definition of `struct timezone'. For example, Dynix/ptx has that definition in <time.h> rather than sys/time.h */ #if defined (TIME_WITH_SYS_TIME) # include <sys/time.h> # include <time.h> #else # if defined (HAVE_SYS_TIME_H) # include <sys/time.h> # else # include <time.h> # endif #endif #if !defined (HAVE_SYSCONF) || !defined (_SC_CLK_TCK) # if !defined (CLK_TCK) # if defined (HZ) # define CLK_TCK HZ # else # define CLK_TCK 60 /* 60HZ */ # endif # endif /* !CLK_TCK */ #endif /* !HAVE_SYSCONF && !_SC_CLK_TCK */ #endif /* _POSIXTIME_H_ */
/* spell.c -- spelling correction for pathnames. */ /* Copyright (C) 2000 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include <bashtypes.h> #include <posixdir.h> #include <posixstat.h> #ifndef _MINIX #include <sys/param.h> #endif #include <stdio.h> #include <bashansi.h> #include <maxpath.h> #include <stdc.h> static int mindist __P((char *, char *, char *)); static int spdist __P((char *, char *)); /* * `spname' and its helpers are inspired by the code in "The UNIX * Programming Environment", Kernighan & Pike, Prentice-Hall 1984, * pages 209 - 213. */ /* * `spname' -- return a correctly spelled filename * * int spname(char * oldname, char * newname) * Returns: -1 if no reasonable match found * 0 if exact match found * 1 if corrected * Stores corrected name in `newname'. */ int spname(oldname, newname) char *oldname; char *newname; { char *op, *np, *p; char guess[PATH_MAX + 1], best[PATH_MAX + 1]; op = oldname; np = newname; for (;;) { while (*op == '/') /* Skip slashes */ *np++ = *op++; *np = '\0'; if (*op == '\0') /* Exact or corrected */ { /* `.' is rarely the right thing. */ if (oldname[1] == '\0' && newname[1] == '\0' && oldname[0] != '.' && newname[0] == '.') return -1; return strcmp(oldname, newname) != 0; } /* Copy next component into guess */ for (p = guess; *op != '/' && *op != '\0'; op++) if (p < guess + PATH_MAX) *p++ = *op; *p = '\0'; if (mindist(newname, guess, best) >= 3) return -1; /* Hopeless */ /* * Add to end of newname */ for (p = best; *np = *p++; np++) ; } } /* * Search directory for a guess */ static int mindist(dir, guess, best) char *dir; char *guess; char *best; { DIR *fd; struct dirent *dp; int dist, x; dist = 3; /* Worst distance */ if (*dir == '\0') dir = "."; if ((fd = opendir(dir)) == NULL) return dist; while ((dp = readdir(fd)) != NULL) { /* * Look for a better guess. If the new guess is as * good as the current one, we take it. This way, * any single character match will be a better match * than ".". */ x = spdist(dp->d_name, guess); if (x <= dist && x != 3) { strcpy(best, dp->d_name); dist = x; if (dist == 0) /* Exact match */ break; } } (void)closedir(fd); /* Don't return `.' */ if (best[0] == '.' && best[1] == '\0') dist = 3; return dist; } /* * `spdist' -- return the "distance" between two names. * * int spname(char * oldname, char * newname) * Returns: 0 if strings are identical * 1 if two characters are transposed * 2 if one character is wrong, added or deleted * 3 otherwise */ static int spdist(cur, new) char *cur, *new; { while (*cur == *new) { if (*cur == '\0') return 0; /* Exact match */ cur++; new++; } if (*cur) { if (*new) { if (cur[1] && new[1] && cur[0] == new[1] && cur[1] == new[0] && strcmp (cur + 2, new + 2) == 0) return 1; /* Transposition */ if (strcmp (cur + 1, new + 1) == 0) return 2; /* One character mismatch */ } if (strcmp(&cur[1], &new[0]) == 0) return 2; /* Extra character */ } if (*new && strcmp(cur, new + 1) == 0) return 2; /* Missing character */ return 3; }
/* strtrans.c - Translate and untranslate strings with ANSI-C escape sequences. */ /* Copyright (C) 2000 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <bashansi.h> #include <stdio.h> #include <chartypes.h> #include "shell.h" #ifdef ESC #undef ESC #endif #define ESC '\033' /* ASCII */ /* Convert STRING by expanding the escape sequences specified by the ANSI C standard. If SAWC is non-null, recognize `\c' and use that as a string terminator. If we see \c, set *SAWC to 1 before returning. LEN is the length of STRING. If (FLAGS&1) is non-zero, that we're translating a string for `echo -e', and therefore should not treat a single quote as a character that may be escaped with a backslash. If (FLAGS&2) is non-zero, we're expanding for the parser and want to quote CTLESC and CTLNUL with CTLESC */ char * ansicstr (string, len, flags, sawc, rlen) char *string; int len, flags, *sawc, *rlen; { int c, temp; char *ret, *r, *s; if (string == 0 || *string == '\0') return ((char *)NULL); ret = (char *)xmalloc (2*len + 1); /* 2*len for possible CTLESC */ for (r = ret, s = string; s && *s; ) { c = *s++; if (c != '\\' || *s == '\0') *r++ = c; else { switch (c = *s++) { #if defined (__STDC__) case 'a': c = '\a'; break; case 'v': c = '\v'; break; #else case 'a': c = '\007'; break; case 'v': c = (int) 0x0B; break; #endif case 'b': c = '\b'; break; case 'e': case 'E': /* ESC -- non-ANSI */ c = ESC; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': /* If (FLAGS & 1), we're translating a string for echo -e (or the equivalent xpg_echo option), so we obey the SUSv3/ POSIX-2001 requirement and accept 0-3 octal digits after a leading `0'. */ temp = 2 + ((flags & 1) && (c == '0')); for (c -= '0'; ISOCTAL (*s) && temp--; s++) c = (c * 8) + OCTVALUE (*s); c &= 0xFF; break; case 'x': /* Hex digit -- non-ANSI */ for (temp = 2, c = 0; ISXDIGIT ((unsigned char)*s) && temp--; s++) c = (c * 16) + HEXVALUE (*s); /* \x followed by non-hex digits is passed through unchanged */ if (temp == 2) { *r++ = '\\'; c = 'x'; } c &= 0xFF; break; case '\\': break; case '\'': if (flags & 1) *r++ = '\\'; break; case 'c': if (sawc) { *sawc = 1; *r = '\0'; if (rlen) *rlen = r - ret; return ret; } else if ((flags & 1) == 0 && (c = *s)) { s++; c = TOCTRL(c); break; } /*FALLTHROUGH*/ default: *r++ = '\\'; break; } if ((flags & 2) && (c == CTLESC || c == CTLNUL)) *r++ = CTLESC; *r++ = c; } } *r = '\0'; if (rlen) *rlen = r - ret; return ret; } /* Take a string STR, possibly containing non-printing characters, and turn it into a $'...' ANSI-C style quoted string. Returns a new string. */ char * ansic_quote (str, flags, rlen) char *str; int flags, *rlen; { char *r, *ret, *s; int l, rsize, t; unsigned char c; if (str == 0 || *str == 0) return ((char *)0); l = strlen (str); rsize = 4 * l + 4; r = ret = (char *)xmalloc (rsize); *r++ = '$'; *r++ = '\''; for (s = str, l = 0; *s; s++) { c = *s; l = 1; /* 1 == add backslash; 0 == no backslash */ switch (c) { case ESC: c = 'E'; break; #ifdef __STDC__ case '\a': c = 'a'; break; case '\v': c = 'v'; break; #else case '\007': c = 'a'; break; case 0x0b: c = 'v'; break; #endif case '\b': c = 'b'; break; case '\f': c = 'f'; break; case '\n': c = 'n'; break; case '\r': c = 'r'; break; case '\t': c = 't'; break; case '\\': case '\'': break; default: if (ISPRINT (c) == 0) { *r++ = '\\'; *r++ = TOCHAR ((c >> 6) & 07); *r++ = TOCHAR ((c >> 3) & 07); *r++ = TOCHAR (c & 07); continue; } l = 0; break; } if (l) *r++ = '\\'; *r++ = c; } *r++ = '\''; *r = '\0'; if (rlen) *rlen = r - ret; return ret; } /* return 1 if we need to quote with $'...' because of non-printing chars. */ int ansic_shouldquote (string) const char *string; { const char *s; unsigned char c; if (string == 0) return 0; for (s = string; c = *s; s++) if (ISPRINT (c) == 0) return 1; return 0; } /* $'...' ANSI-C expand the portion of STRING between START and END and return the result. The result cannot be longer than the input string. */ char * ansiexpand (string, start, end, lenp) char *string; int start, end, *lenp; { char *temp, *t; int len, tlen; temp = (char *)xmalloc (end - start + 1); for (tlen = 0, len = start; len < end; ) temp[tlen++] = string[len++]; temp[tlen] = '\0'; if (*temp) { t = ansicstr (temp, tlen, 2, (int *)NULL, lenp); free (temp); return (t); } else { if (lenp) *lenp = 0; return (temp); } }
/* strindex.c - Find if one string appears as a substring of another string, without regard to case. */ /* Copyright (C) 2000 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #include <bashansi.h> #include <chartypes.h> #include <stdc.h> /* Determine if s2 occurs in s1. If so, return a pointer to the match in s1. The compare is case insensitive. This is a case-insensitive strstr(3). */ char * strindex (s1, s2) const char *s1; const char *s2; { register int i, l, len, c; c = TOLOWER ((unsigned char)s2[0]); len = strlen (s1); l = strlen (s2); for (i = 0; (len - i) >= l; i++) if ((TOLOWER ((unsigned char)s1[i]) == c) && (strncasecmp (s1 + i, s2, l) == 0)) return ((char *)s1 + i); return ((char *)0); }
/* Copyright (C) 1999 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include <stdio.h> #include "syntax.h" #include <xmalloc.h> /* **************************************************************** */ /* */ /* Functions for quoting strings to be re-read as input */ /* */ /* **************************************************************** */ /* Return a new string which is the single-quoted version of STRING. Used by alias and trap, among others. */ char * sh_single_quote (string) char *string; { register int c; char *result, *r, *s; result = (char *)xmalloc (3 + (4 * strlen (string))); r = result; *r++ = '\''; for (s = string; s && (c = *s); s++) { *r++ = c; if (c == '\'') { *r++ = '\\'; /* insert escaped single quote */ *r++ = '\''; *r++ = '\''; /* start new quoted string */ } } *r++ = '\''; *r = '\0'; return (result); } /* Quote STRING using double quotes. Return a new string. */ char * sh_double_quote (string) char *string; { register unsigned char c; char *result, *r, *s; result = (char *)xmalloc (3 + (2 * strlen (string))); r = result; *r++ = '"'; for (s = string; s && (c = *s); s++) { if (sh_syntaxtab[c] & CBSDQUOTE) *r++ = '\\'; *r++ = c; } *r++ = '"'; *r = '\0'; return (result); } /* Remove backslashes that are quoting characters that are special between double quotes. Return a new string. */ char * sh_un_double_quote (string) char *string; { register int c, pass_next; char *result, *r, *s; r = result = (char *)xmalloc (strlen (string) + 1); for (pass_next = 0, s = string; s && (c = *s); s++) { if (pass_next) { *r++ = c; pass_next = 0; continue; } if (c == '\\' && (sh_syntaxtab[(unsigned char) s[1]] & CBSDQUOTE)) { pass_next = 1; continue; } *r++ = c; } *r = '\0'; return result; } /* Quote special characters in STRING using backslashes. Return a new string. */ char * sh_backslash_quote (string) char *string; { int c; char *result, *r, *s; result = (char *)xmalloc (2 * strlen (string) + 1); for (r = result, s = string; s && (c = *s); s++) { switch (c) { case ' ': case '\t': case '\n': /* IFS white space */ case '\'': case '"': case '\\': /* quoting chars */ case '|': case '&': case ';': /* shell metacharacters */ case '(': case ')': case '<': case '>': case '!': case '{': case '}': /* reserved words */ case '*': case '[': case '?': case ']': /* globbing chars */ case '^': case '$': case '`': /* expansion chars */ case ',': /* brace expansion */ *r++ = '\\'; *r++ = c; break; #if 0 case '~': /* tilde expansion */ if (s == string || s[-1] == '=' || s[-1] == ':') *r++ = '\\'; *r++ = c; break; #endif case '#': /* comment char */ if (s == string) *r++ = '\\'; /* FALLTHROUGH */ default: *r++ = c; break; } } *r = '\0'; return (result); } #if defined (PROMPT_STRING_DECODE) /* Quote characters that get special treatment when in double quotes in STRING using backslashes. Return a new string. */ char * sh_backslash_quote_for_double_quotes (string) char *string; { unsigned char c; char *result, *r, *s; result = (char *)xmalloc (2 * strlen (string) + 1); for (r = result, s = string; s && (c = *s); s++) { if (sh_syntaxtab[c] & CBSDQUOTE) *r++ = '\\'; *r++ = c; } *r = '\0'; return (result); } #endif /* PROMPT_STRING_DECODE */ int sh_contains_shell_metas (string) char *string; { char *s; for (s = string; s && *s; s++) { switch (*s) { case ' ': case '\t': case '\n': /* IFS white space */ case '\'': case '"': case '\\': /* quoting chars */ case '|': case '&': case ';': /* shell metacharacters */ case '(': case ')': case '<': case '>': case '!': case '{': case '}': /* reserved words */ case '*': case '[': case '?': case ']': /* globbing chars */ case '^': case '$': case '`': /* expansion chars */ return (1); case '~': /* tilde expansion */ if (s == string || s[-1] == '=' || s[-1] == ':') return (1); break; case '#': if (s == string) /* comment char */ return (1); /* FALLTHROUGH */ default: break; } } return (0); }
/* mailstat.c -- stat a mailbox file, handling maildir-type mail directories */ /* Copyright (C) 2001 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #include <stdio.h> #include <errno.h> #include <bashtypes.h> #include <posixstat.h> #include <posixdir.h> #include <bashansi.h> #ifndef _MINIX # include <sys/param.h> #endif #include <maxpath.h> /* * Stat a file. If it's a maildir, check all messages * in the maildir and present the grand total as a file. * The fields in the 'struct stat' are from the mail directory. * The following fields are emulated: * * st_nlink always 1, unless st_blocks is not present, in which case it's * the total number of messages * st_size total number of bytes in all files * st_blocks total number of messages, if present in struct stat * st_atime access time of newest file in maildir * st_mtime modify time of newest file in maildir * st_mode S_IFDIR changed to S_IFREG * * This is good enough for most mail-checking applications. */ int mailstat(path, st) const char *path; struct stat *st; { static struct stat st_new_last, st_ret_last; struct stat st_ret, st_tmp; DIR *dd; struct dirent *fn; char dir[PATH_MAX * 2], file[PATH_MAX * 2]; int i, l; time_t atime, mtime; atime = mtime = 0; /* First see if it's a directory. */ if ((i = stat(path, st)) != 0 || S_ISDIR(st->st_mode) == 0) return i; if (strlen(path) > sizeof(dir) - 5) { #ifdef ENAMETOOLONG errno = ENAMETOOLONG; #else errno = EINVAL; #endif return -1; } st_ret = *st; st_ret.st_nlink = 1; st_ret.st_size = 0; #ifdef HAVE_STRUCT_STAT_ST_BLOCKS st_ret.st_blocks = 0; #else st_ret.st_nlink = 0; #endif st_ret.st_mode &= ~S_IFDIR; st_ret.st_mode |= S_IFREG; /* See if cur/ is present */ sprintf(dir, "%s/cur", path); if (stat(dir, &st_tmp) || S_ISDIR(st_tmp.st_mode) == 0) return 0; st_ret.st_atime = st_tmp.st_atime; /* See if tmp/ is present */ sprintf(dir, "%s/tmp", path); if (stat(dir, &st_tmp) || S_ISDIR(st_tmp.st_mode) == 0) return 0; st_ret.st_mtime = st_tmp.st_mtime; /* And new/ */ sprintf(dir, "%s/new", path); if (stat(dir, &st_tmp) || S_ISDIR(st_tmp.st_mode) == 0) return 0; st_ret.st_mtime = st_tmp.st_mtime; /* Optimization - if new/ didn't change, nothing else did. */ if (st_tmp.st_dev == st_new_last.st_dev && st_tmp.st_ino == st_new_last.st_ino && st_tmp.st_atime == st_new_last.st_atime && st_tmp.st_mtime == st_new_last.st_mtime) { *st = st_ret_last; return 0; } st_new_last = st_tmp; /* Loop over new/ and cur/ */ for (i = 0; i < 2; i++) { sprintf(dir, "%s/%s", path, i ? "cur" : "new"); sprintf(file, "%s/", dir); l = strlen(file); if ((dd = opendir(dir)) == NULL) return 0; while ((fn = readdir(dd)) != NULL) { if (fn->d_name[0] == '.' || strlen(fn->d_name) + l >= sizeof(file)) continue; strcpy(file + l, fn->d_name); if (stat(file, &st_tmp) != 0) continue; st_ret.st_size += st_tmp.st_size; #ifdef HAVE_STRUCT_STAT_ST_BLOCKS st_ret.st_blocks++; #else st_ret.st_nlink++; #endif if (st_tmp.st_atime != st_tmp.st_mtime && st_tmp.st_atime > atime) atime = st_tmp.st_atime; if (st_tmp.st_mtime > mtime) mtime = st_tmp.st_mtime; } closedir(dd); } if (atime) st_ret.st_atime = atime; if (mtime) st_ret.st_mtime = mtime; *st = st_ret_last = st_ret; return 0; }
/* posixdir.h -- Posix directory reading includes and defines. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ /* This file should be included instead of <dirent.h> or <sys/dir.h>. */ #if !defined (_POSIXDIR_H_) #define _POSIXDIR_H_ #if defined (HAVE_DIRENT_H) # include <dirent.h> # define D_NAMLEN(d) (strlen ((d)->d_name)) #else # if defined (HAVE_SYS_NDIR_H) # include <sys/ndir.h> # endif # if defined (HAVE_SYS_DIR_H) # include <sys/dir.h> # endif # if defined (HAVE_NDIR_H) # include <ndir.h> # endif # if !defined (dirent) # define dirent direct # endif /* !dirent */ # define D_NAMLEN(d) ((d)->d_namlen) #endif /* !HAVE_DIRENT_H */ #if defined (STRUCT_DIRENT_HAS_D_INO) && !defined (STRUCT_DIRENT_HAS_D_FILENO) # define d_fileno d_ino #endif #if defined (_POSIX_SOURCE) && (!defined (STRUCT_DIRENT_HAS_D_INO) || defined (BROKEN_DIRENT_D_INO)) /* Posix does not require that the d_ino field be present, and some systems do not provide it. */ # define REAL_DIR_ENTRY(dp) 1 #else # define REAL_DIR_ENTRY(dp) (dp->d_ino != 0) #endif /* _POSIX_SOURCE */ #endif /* !_POSIXDIR_H_ */
/* maxpath.h - Find out what this system thinks PATH_MAX and NAME_MAX are. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_MAXPATH_H_) #define _MAXPATH_H_ /* These values are supposed to be in <limits.h> or one of the files it includes. */ #if defined (HAVE_LIMITS_H) # include <limits.h> #endif /* !HAVE_LIMITS_H */ /* If PATH_MAX is not defined, look for MAXPATHLEN */ #if !defined (PATH_MAX) # if defined (HAVE_SYS_PARAM_H) # include <sys/param.h> # define maxpath_param_h # endif # if defined (MAXPATHLEN) && !defined (PATH_MAX) # define PATH_MAX MAXPATHLEN # endif /* MAXPATHLEN && !PATH_MAX */ #endif /* !PATH_MAX */ /* If NAME_MAX is not defined, look for MAXNAMLEN */ #if !defined (NAME_MAX) # if defined (HAVE_SYS_PARAM_H) && !defined (maxpath_param_h) # include <sys/param.h> # endif # if defined (MAXNAMLEN) && !defined (NAME_MAX) # define NAME_MAX MAXNAMLEN # endif /* MAXNAMLEN && !NAME_MAX */ #endif /* !NAME_MAX */ /* Default POSIX values */ #if !defined (PATH_MAX) && defined (_POSIX_PATH_MAX) # define PATH_MAX _POSIX_PATH_MAX #endif #if !defined (NAME_MAX) && defined (_POSIX_NAME_MAX) # define NAME_MAX _POSIX_NAME_MAX #endif /* Default values */ #if !defined (PATH_MAX) # define PATH_MAX 1024 #endif #if !defined (NAME_MAX) # define NAME_MAX 14 #endif #if PATH_MAX < 1024 # undef PATH_MAX # define PATH_MAX 1024 #endif #endif /* _MAXPATH_H_ */
/* fmtulong.c -- Convert unsigned long int to string. */ /* Copyright (C) 1998-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #if defined (HAVE_LIMITS_H) # include <limits.h> #endif #include <bashansi.h> #ifdef HAVE_STDDEF_H # include <stddef.h> #endif #ifdef HAVE_STDINT_H # include <stdint.h> #endif #include <chartypes.h> #include <errno.h> #include "stdc.h" #include <typemax.h> #ifndef errno extern int errno; #endif #define x_digs "0123456789abcdef" #define X_digs "0123456789ABCDEF" /* XXX -- assumes uppercase letters, lowercase letters, and digits are contiguous */ #define FMTCHAR(x) \ ((x) < 10) ? (x) + '0' \ : (((x) < 36) ? (x) - 10 + 'a' \ : (((x) < 62) ? (x) - 36 + 'A' \ : (((x) == 62) ? '@' : '_'))) #ifndef FL_PREFIX # define FL_PREFIX 0x01 /* add 0x, 0X, or 0 prefix as appropriate */ # define FL_ADDBASE 0x02 /* add base# prefix to converted value */ # define FL_HEXUPPER 0x04 /* use uppercase when converting to hex */ # define FL_UNSIGNED 0x08 /* don't add any sign */ #endif #ifndef LONG # define LONG long # define UNSIGNED_LONG unsigned long #endif /* `unsigned long' (or unsigned long long) to string conversion for a given base. The caller passes the output buffer and the size. This should check for buffer underflow, but currently does not. */ char * fmtulong (ui, base, buf, len, flags) UNSIGNED_LONG ui; int base; char *buf; size_t len; int flags; { char *p; int sign; LONG si; if (base == 0) base = 10; if (base < 2 || base > 64) { #if 1 strncpy (buf, "invalid base", len - 1); buf[len] = '\0'; errno = EINVAL; return (p = buf); #else base = 10; #endif } sign = 0; if ((flags & FL_UNSIGNED) == 0 && (LONG)ui < 0) { ui = -ui; sign = '-'; } p = buf + len - 2; p[1] = '\0'; /* handle common cases explicitly */ switch (base) { case 10: if (ui < 10) { *p-- = TOCHAR (ui); break; } /* Favor signed arithmetic over unsigned arithmetic; it is faster on many machines. */ if ((LONG)ui < 0) { *p-- = TOCHAR (ui % 10); si = ui / 10; } else si = ui; do *p-- = TOCHAR (si % 10); while (si /= 10); break; case 8: do *p-- = TOCHAR (ui & 7); while (ui >>= 3); break; case 16: do *p-- = (flags & FL_HEXUPPER) ? X_digs[ui & 15] : x_digs[ui & 15]; while (ui >>= 4); break; case 2: do *p-- = TOCHAR (ui & 1); while (ui >>= 1); break; default: do *p-- = FMTCHAR (ui % base); while (ui /= base); break; } if ((flags & FL_PREFIX) && (base == 8 || base == 16)) { if (base == 16) { *p-- = (flags & FL_HEXUPPER) ? 'X' : 'x'; *p-- = '0'; } else if (p[1] != '0') *p-- = '0'; } else if ((flags & FL_ADDBASE) && base != 10) { *p-- = '#'; *p-- = TOCHAR (base % 10); if (base > 10) *p-- = TOCHAR (base / 10); } if (sign) *p-- = '-'; return (p + 1); }
/* fmtullong.c - convert `long long int' to string */ /* Copyright (C) 2001-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <config.h> #ifdef HAVE_LONG_LONG #define LONG long long #define UNSIGNED_LONG unsigned long long #define fmtulong fmtullong #include "fmtulong.c" #endif
/* Copyright (C) 1997 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <config.h> #if defined (HAVE_LONG_LONG) && !defined (HAVE_STRTOLL) #define QUAD 1 #undef HAVE_STRTOL #include "strtol.c" #endif /* HAVE_LONG_LONG && !HAVE_STRTOLL */
/* Copyright (C) 1997 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <config.h> #if defined (HAVE_LONG_LONG) && !defined (HAVE_STRTOULL) #define QUAD 1 #define UNSIGNED 1 #undef HAVE_STRTOL #include "strtol.c" #endif /* HAVE_LONG_LONG && !HAVE_STRTOULL */
/* Convert string representation of a number into an intmax_t value. Copyright 1999, 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert. Modified by Chet Ramey for Bash. */ #if HAVE_CONFIG_H # include <config.h> #endif #if HAVE_INTTYPES_H # include <inttypes.h> #endif #if HAVE_STDLIB_H # include <stdlib.h> #endif #include <stdc.h> /* Verify a requirement at compile-time (unlike assert, which is runtime). */ #define verify(name, assertion) struct name { char a[(assertion) ? 1 : -1]; } #ifndef HAVE_DECL_STRTOL "this configure-time declaration test was not run" #endif #if !HAVE_DECL_STRTOL extern long strtol __P((const char *, char **, int)); #endif #ifndef HAVE_DECL_STRTOLL "this configure-time declaration test was not run" #endif #if !HAVE_DECL_STRTOLL && HAVE_LONG_LONG extern long long strtoll __P((const char *, char **, int)); #endif intmax_t strtoimax (ptr, endptr, base) const char *ptr; char **endptr; int base; { #if HAVE_LONG_LONG verify(size_is_that_of_long_or_long_long, (sizeof (intmax_t) == sizeof (long) || sizeof (intmax_t) == sizeof (long long))); if (sizeof (intmax_t) != sizeof (long)) return (strtoll (ptr, endptr, base)); #else verify (size_is_that_of_long, sizeof (intmax_t) == sizeof (long)); #endif return (strtol (ptr, endptr, base)); } #ifdef TESTING # include <stdio.h> int main () { char *p, *endptr; intmax_t x; #if HAVE_LONG_LONG long long y; #endif long z; printf ("sizeof intmax_t: %d\n", sizeof (intmax_t)); #if HAVE_LONG_LONG printf ("sizeof long long: %d\n", sizeof (long long)); #endif printf ("sizeof long: %d\n", sizeof (long)); x = strtoimax("42", &endptr, 10); #if HAVE_LONG_LONG y = strtoll("42", &endptr, 10); #else y = -1; #endif z = strtol("42", &endptr, 10); printf ("%lld %lld %ld\n", x, y, z); exit (0); } #endif
/* Convert string representation of a number into an uintmax_t value. Copyright 1999, 2001 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Written by Paul Eggert. Modified by Chet Ramey for Bash. */ #if HAVE_CONFIG_H # include <config.h> #endif #if HAVE_INTTYPES_H # include <inttypes.h> #endif #if HAVE_STDLIB_H # include <stdlib.h> #endif #include <stdc.h> /* Verify a requirement at compile-time (unlike assert, which is runtime). */ #define verify(name, assertion) struct name { char a[(assertion) ? 1 : -1]; } #ifndef HAVE_DECL_STRTOUL "this configure-time declaration test was not run" #endif #if !HAVE_DECL_STRTOUL extern unsigned long strtoul __P((const char *, char **, int)); #endif #ifndef HAVE_DECL_STRTOULL "this configure-time declaration test was not run" #endif #if !HAVE_DECL_STRTOULL && HAVE_UNSIGNED_LONG_LONG extern unsigned long long strtoull __P((const char *, char **, int)); #endif uintmax_t strtoumax (ptr, endptr, base) const char *ptr; char **endptr; int base; { #if HAVE_UNSIGNED_LONG_LONG verify (size_is_that_of_unsigned_long_or_unsigned_long_long, (sizeof (uintmax_t) == sizeof (unsigned long) || sizeof (uintmax_t) == sizeof (unsigned long long))); if (sizeof (uintmax_t) != sizeof (unsigned long)) return (strtoull (ptr, endptr, base)); #else verify (size_is_that_of_unsigned_long, sizeof (uintmax_t) == sizeof (unsigned long)); #endif return (strtoul (ptr, endptr, base)); } #ifdef TESTING # include <stdio.h> int main () { char *p, *endptr; uintmax_t x; #if HAVE_UNSIGNED_LONG_LONG unsigned long long y; #endif unsigned long z; printf ("sizeof uintmax_t: %d\n", sizeof (uintmax_t)); #if HAVE_UNSIGNED_LONG_LONG printf ("sizeof unsigned long long: %d\n", sizeof (unsigned long long)); #endif printf ("sizeof unsigned long: %d\n", sizeof (unsigned long)); x = strtoumax("42", &endptr, 10); #if HAVE_LONG_LONG y = strtoull("42", &endptr, 10); #else y = 0; #endif z = strtoul("42", &endptr, 10); printf ("%llu %llu %lu\n", x, y, z); exit (0); } #endif
/* fmtumax.c -- Convert uintmax_t to string. */ /* Copyright (C) 2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <config.h> #define LONG intmax_t #define UNSIGNED_LONG uintmax_t #define fmtulong fmtumax #include "fmtulong.c"
/* netconn.c -- is a particular file descriptor a network connection?. */ /* Copyright (C) 2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #include <bashtypes.h> #ifndef _MINIX # include <sys/file.h> #endif #include <posixstat.h> #include <filecntl.h> #include <errno.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif /* The second and subsequent conditions must match those used to decide whether or not to call getpeername() in isnetconn(). */ #if defined (HAVE_SYS_SOCKET_H) && defined (HAVE_GETPEERNAME) && !defined (SVR4_2) # include <sys/socket.h> #endif /* Is FD a socket or network connection? */ int isnetconn (fd) int fd; { #if defined (HAVE_GETPEERNAME) && !defined (SVR4_2) && !defined (__BEOS__) int rv; socklen_t l; struct sockaddr sa; l = sizeof(sa); rv = getpeername(fd, &sa, &l); /* Solaris 2.5 getpeername() returns EINVAL if the fd is not a socket. */ return ((rv < 0 && (errno == ENOTSOCK || errno == EINVAL)) ? 0 : 1); #else /* !HAVE_GETPEERNAME || SVR4_2 || __BEOS__ */ # if defined (SVR4) || defined (SVR4_2) /* Sockets on SVR4 and SVR4.2 are character special (streams) devices. */ struct stat sb; if (isatty (fd)) return (0); if (fstat (fd, &sb) < 0) return (0); # if defined (S_ISFIFO) if (S_ISFIFO (sb.st_mode)) return (0); # endif /* S_ISFIFO */ return (S_ISCHR (sb.st_mode)); # else /* !SVR4 && !SVR4_2 */ # if defined (S_ISSOCK) && !defined (__BEOS__) struct stat sb; if (fstat (fd, &sb) < 0) return (0); return (S_ISSOCK (sb.st_mode)); # else /* !S_ISSOCK || __BEOS__ */ return (0); # endif /* !S_ISSOCK || __BEOS__ */ # endif /* !SVR4 && !SVR4_2 */ #endif /* !HAVE_GETPEERNAME || SVR4_2 || __BEOS__ */ }
/* Copyright (C) 1993-2002 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Paul Eggert (eggert@twinsun.com). The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Define this to have a standalone program to test this implementation of mktime. */ /* #define DEBUG 1 */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #ifdef _LIBC # define HAVE_LIMITS_H 1 # define HAVE_LOCALTIME_R 1 # define STDC_HEADERS 1 #endif /* Assume that leap seconds are possible, unless told otherwise. If the host has a `zic' command with a `-L leapsecondfilename' option, then it supports leap seconds; otherwise it probably doesn't. */ #ifndef LEAP_SECONDS_POSSIBLE #define LEAP_SECONDS_POSSIBLE 1 #endif #ifndef VMS #include <sys/types.h> /* Some systems define `time_t' here. */ #else #include <stddef.h> #endif #include <time.h> #if HAVE_LIMITS_H #include <limits.h> #endif #if DEBUG #include <stdio.h> #if STDC_HEADERS #include <stdlib.h> #endif /* Make it work even if the system's libc has its own mktime routine. */ #define mktime my_mktime #endif /* DEBUG */ #ifndef __P #if defined (__GNUC__) || (defined (__STDC__) && __STDC__) #define __P(args) args #else #define __P(args) () #endif /* GCC. */ #endif /* Not __P. */ #ifndef CHAR_BIT #define CHAR_BIT 8 #endif #ifndef INT_MIN #define INT_MIN (~0 << (sizeof (int) * CHAR_BIT - 1)) #endif #ifndef INT_MAX #define INT_MAX (~0 - INT_MIN) #endif #ifndef TIME_T_MIN #define TIME_T_MIN (0 < (time_t) -1 ? (time_t) 0 \ : ~ (time_t) 0 << (sizeof (time_t) * CHAR_BIT - 1)) #endif #ifndef TIME_T_MAX #define TIME_T_MAX (~ (time_t) 0 - TIME_T_MIN) #endif #define TM_YEAR_BASE 1900 #define EPOCH_YEAR 1970 #ifndef __isleap /* Nonzero if YEAR is a leap year (every 4 years, except every 100th isn't, and every 400th is). */ #define __isleap(year) \ ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0)) #endif /* How many days come before each month (0-12). */ const unsigned short int __mon_yday[2][13] = { /* Normal years. */ { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, /* Leap years. */ { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } }; static time_t ydhms_tm_diff __P ((int, int, int, int, int, const struct tm *)); time_t __mktime_internal __P ((struct tm *, struct tm *(*) (const time_t *, struct tm *), time_t *)); static struct tm *my_localtime_r __P ((const time_t *, struct tm *)); static struct tm * my_localtime_r (t, tp) const time_t *t; struct tm *tp; { struct tm *l = localtime (t); if (! l) return 0; *tp = *l; return tp; } /* Yield the difference between (YEAR-YDAY HOUR:MIN:SEC) and (*TP), measured in seconds, ignoring leap seconds. YEAR uses the same numbering as TM->tm_year. All values are in range, except possibly YEAR. If overflow occurs, yield the low order bits of the correct answer. */ static time_t ydhms_tm_diff (year, yday, hour, min, sec, tp) int year, yday, hour, min, sec; const struct tm *tp; { /* Compute intervening leap days correctly even if year is negative. Take care to avoid int overflow. time_t overflow is OK, since only the low order bits of the correct time_t answer are needed. Don't convert to time_t until after all divisions are done, since time_t might be unsigned. */ int a4 = (year >> 2) + (TM_YEAR_BASE >> 2) - ! (year & 3); int b4 = (tp->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (tp->tm_year & 3); int a100 = a4 / 25 - (a4 % 25 < 0); int b100 = b4 / 25 - (b4 % 25 < 0); int a400 = a100 >> 2; int b400 = b100 >> 2; int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400); time_t years = year - (time_t) tp->tm_year; time_t days = (365 * years + intervening_leap_days + (yday - tp->tm_yday)); return (60 * (60 * (24 * days + (hour - tp->tm_hour)) + (min - tp->tm_min)) + (sec - tp->tm_sec)); } static time_t localtime_offset; /* Convert *TP to a time_t value. */ time_t mktime (tp) struct tm *tp; { #ifdef _LIBC /* POSIX.1 8.1.1 requires that whenever mktime() is called, the time zone names contained in the external variable `tzname' shall be set as if the tzset() function had been called. */ __tzset (); #endif return __mktime_internal (tp, my_localtime_r, &localtime_offset); } /* Convert *TP to a time_t value, inverting the monotonic and mostly-unit-linear conversion function CONVERT. Use *OFFSET to keep track of a guess at the offset of the result, compared to what the result would be for UTC without leap seconds. If *OFFSET's guess is correct, only one CONVERT call is needed. */ time_t __mktime_internal (tp, convert, offset) struct tm *tp; struct tm *(*convert) __P ((const time_t *, struct tm *)); time_t *offset; { time_t t, dt, t0; struct tm tm; /* The maximum number of probes (calls to CONVERT) should be enough to handle any combinations of time zone rule changes, solar time, and leap seconds. Posix.1 prohibits leap seconds, but some hosts have them anyway. */ int remaining_probes = 4; /* Time requested. Copy it in case CONVERT modifies *TP; this can occur if TP is localtime's returned value and CONVERT is localtime. */ int sec = tp->tm_sec; int min = tp->tm_min; int hour = tp->tm_hour; int mday = tp->tm_mday; int mon = tp->tm_mon; int year_requested = tp->tm_year; int isdst = tp->tm_isdst; /* Ensure that mon is in range, and set year accordingly. */ int mon_remainder = mon % 12; int negative_mon_remainder = mon_remainder < 0; int mon_years = mon / 12 - negative_mon_remainder; int year = year_requested + mon_years; /* The other values need not be in range: the remaining code handles minor overflows correctly, assuming int and time_t arithmetic wraps around. Major overflows are caught at the end. */ /* Calculate day of year from year, month, and day of month. The result need not be in range. */ int yday = ((__mon_yday[__isleap (year + TM_YEAR_BASE)] [mon_remainder + 12 * negative_mon_remainder]) + mday - 1); #if LEAP_SECONDS_POSSIBLE /* Handle out-of-range seconds specially, since ydhms_tm_diff assumes every minute has 60 seconds. */ int sec_requested = sec; if (sec < 0) sec = 0; if (59 < sec) sec = 59; #endif /* Invert CONVERT by probing. First assume the same offset as last time. Then repeatedly use the error to improve the guess. */ tm.tm_year = EPOCH_YEAR - TM_YEAR_BASE; tm.tm_yday = tm.tm_hour = tm.tm_min = tm.tm_sec = 0; t0 = ydhms_tm_diff (year, yday, hour, min, sec, &tm); for (t = t0 + *offset; (dt = ydhms_tm_diff (year, yday, hour, min, sec, (*convert) (&t, &tm))); t += dt) if (--remaining_probes == 0) return -1; /* Check whether tm.tm_isdst has the requested value, if any. */ if (0 <= isdst && 0 <= tm.tm_isdst) { int dst_diff = (isdst != 0) - (tm.tm_isdst != 0); if (dst_diff) { /* Move two hours in the direction indicated by the disagreement, probe some more, and switch to a new time if found. The largest known fallback due to daylight savings is two hours: once, in Newfoundland, 1988-10-30 02:00 -> 00:00. */ time_t ot = t - 2 * 60 * 60 * dst_diff; while (--remaining_probes != 0) { struct tm otm; if (! (dt = ydhms_tm_diff (year, yday, hour, min, sec, (*convert) (&ot, &otm)))) { t = ot; tm = otm; break; } if ((ot += dt) == t) break; /* Avoid a redundant probe. */ } } } *offset = t - t0; #if LEAP_SECONDS_POSSIBLE if (sec_requested != tm.tm_sec) { /* Adjust time to reflect the tm_sec requested, not the normalized value. Also, repair any damage from a false match due to a leap second. */ t += sec_requested - sec + (sec == 0 && tm.tm_sec == 60); (*convert) (&t, &tm); } #endif if (TIME_T_MAX / INT_MAX / 366 / 24 / 60 / 60 < 3) { /* time_t isn't large enough to rule out overflows in ydhms_tm_diff, so check for major overflows. A gross check suffices, since if t has overflowed, it is off by a multiple of TIME_T_MAX - TIME_T_MIN + 1. So ignore any component of the difference that is bounded by a small value. */ double dyear = (double) year_requested + mon_years - tm.tm_year; double dday = 366 * dyear + mday; double dsec = 60 * (60 * (24 * dday + hour) + min) + sec_requested; if (TIME_T_MAX / 3 - TIME_T_MIN / 3 < (dsec < 0 ? - dsec : dsec)) return -1; } *tp = tm; return t; } #ifdef weak_alias weak_alias (mktime, timelocal) #endif #if DEBUG static int not_equal_tm (a, b) struct tm *a; struct tm *b; { return ((a->tm_sec ^ b->tm_sec) | (a->tm_min ^ b->tm_min) | (a->tm_hour ^ b->tm_hour) | (a->tm_mday ^ b->tm_mday) | (a->tm_mon ^ b->tm_mon) | (a->tm_year ^ b->tm_year) | (a->tm_mday ^ b->tm_mday) | (a->tm_yday ^ b->tm_yday) | (a->tm_isdst ^ b->tm_isdst)); } static void print_tm (tp) struct tm *tp; { printf ("%04d-%02d-%02d %02d:%02d:%02d yday %03d wday %d isdst %d", tp->tm_year + TM_YEAR_BASE, tp->tm_mon + 1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec, tp->tm_yday, tp->tm_wday, tp->tm_isdst); } static int check_result (tk, tmk, tl, tml) time_t tk; struct tm tmk; time_t tl; struct tm tml; { if (tk != tl || not_equal_tm (&tmk, &tml)) { printf ("mktime ("); print_tm (&tmk); printf (")\nyields ("); print_tm (&tml); printf (") == %ld, should be %ld\n", (long) tl, (long) tk); return 1; } return 0; } int main (argc, argv) int argc; char **argv; { int status = 0; struct tm tm, tmk, tml; time_t tk, tl; char trailer; if ((argc == 3 || argc == 4) && (sscanf (argv[1], "%d-%d-%d%c", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &trailer) == 3) && (sscanf (argv[2], "%d:%d:%d%c", &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &trailer) == 3)) { tm.tm_year -= TM_YEAR_BASE; tm.tm_mon--; tm.tm_isdst = argc == 3 ? -1 : atoi (argv[3]); tmk = tm; tl = mktime (&tmk); tml = *localtime (&tl); printf ("mktime returns %ld == ", (long) tl); print_tm (&tmk); printf ("\n"); status = check_result (tl, tmk, tl, tml); } else if (argc == 4 || (argc == 5 && strcmp (argv[4], "-") == 0)) { time_t from = atol (argv[1]); time_t by = atol (argv[2]); time_t to = atol (argv[3]); if (argc == 4) for (tl = from; tl <= to; tl += by) { tml = *localtime (&tl); tmk = tml; tk = mktime (&tmk); status |= check_result (tk, tmk, tl, tml); } else for (tl = from; tl <= to; tl += by) { /* Null benchmark. */ tml = *localtime (&tl); tmk = tml; tk = tl; status |= check_result (tk, tmk, tl, tml); } } else printf ("Usage:\ \t%s YYYY-MM-DD HH:MM:SS [ISDST] # Test given time.\n\ \t%s FROM BY TO # Test values FROM, FROM+BY, ..., TO.\n\ \t%s FROM BY TO - # Do not test those values (for benchmark).\n", argv[0], argv[0], argv[0]); return status; } #endif /* DEBUG */ /* Local Variables: compile-command: "gcc -DDEBUG=1 -Wall -O -g mktime.c -o mktime" End: */
/* * Modified slightly by Chet Ramey for inclusion in Bash */ /* * strftime.c * * Public-domain implementation of ISO C library routine. * * If you can't do prototypes, get GCC. * * The C99 standard now specifies just about all of the formats * that were additional in the earlier versions of this file. * * For extensions from SunOS, add SUNOS_EXT. * For extensions from HP/UX, add HPUX_EXT. * For VMS dates, add VMS_EXT. * For complete POSIX semantics, add POSIX_SEMANTICS. * * The code for %c, %x, and %X follows the C99 specification for * the "C" locale. * * This version ignores LOCALE information. * It also doesn't worry about multi-byte characters. * So there. * * This file is also shipped with GAWK (GNU Awk), gawk specific bits of * code are included if GAWK is defined. * * Arnold Robbins * January, February, March, 1991 * Updated March, April 1992 * Updated April, 1993 * Updated February, 1994 * Updated May, 1994 * Updated January, 1995 * Updated September, 1995 * Updated January, 1996 * Updated July, 1997 * Updated October, 1999 * Updated September, 2000 * * Fixes from ado@elsie.nci.nih.gov, * February 1991, May 1992 * Fixes from Tor Lillqvist tml@tik.vtt.fi, * May 1993 * Further fixes from ado@elsie.nci.nih.gov, * February 1994 * %z code from chip@chinacat.unicom.com, * Applied September 1995 * %V code fixed (again) and %G, %g added, * January 1996 * %v code fixed, better configuration, * July 1997 * Moved to C99 specification. * September 2000 */ #include <config.h> #ifndef GAWK #include <stdio.h> #include <ctype.h> #include <time.h> #endif #if defined(TM_IN_SYS_TIME) #include <sys/types.h> #include <sys/time.h> #endif #include <stdlib.h> #include <string.h> /* defaults: season to taste */ #define SUNOS_EXT 1 /* stuff in SunOS strftime routine */ #define VMS_EXT 1 /* include %v for VMS date format */ #define HPUX_EXT 1 /* non-conflicting stuff in HP-UX date */ #ifndef GAWK #define POSIX_SEMANTICS 1 /* call tzset() if TZ changes */ #endif #undef strchr /* avoid AIX weirdness */ extern void tzset(void); static int weeknumber(const struct tm *timeptr, int firstweekday); static int iso8601wknum(const struct tm *timeptr); #ifdef __GNUC__ #define inline __inline__ #else #define inline /**/ #endif #define range(low, item, hi) max(low, min(item, hi)) #if !defined(OS2) && !defined(MSDOS) && defined(HAVE_TZNAME) extern char *tzname[2]; extern int daylight; #if defined(SOLARIS) || defined(mips) extern long int timezone, altzone; #else extern int timezone, altzone; #endif #endif #undef min /* just in case */ /* min --- return minimum of two numbers */ static inline int min(int a, int b) { return (a < b ? a : b); } #undef max /* also, just in case */ /* max --- return maximum of two numbers */ static inline int max(int a, int b) { return (a > b ? a : b); } /* strftime --- produce formatted time */ size_t strftime(char *s, size_t maxsize, const char *format, const struct tm *timeptr) { char *endp = s + maxsize; char *start = s; auto char tbuf[100]; long off; int i, w, y; static short first = 1; #ifdef POSIX_SEMANTICS static char *savetz = NULL; static int savetzlen = 0; char *tz; #endif /* POSIX_SEMANTICS */ #ifndef HAVE_TM_ZONE #ifndef HAVE_TM_NAME #ifndef HAVE_TZNAME extern char *timezone(); struct timeval tv; struct timezone zone; #endif /* HAVE_TZNAME */ #endif /* HAVE_TM_NAME */ #endif /* HAVE_TM_ZONE */ /* various tables, useful in North America */ static const char *days_a[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", }; static const char *days_l[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", }; static const char *months_a[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", }; static const char *months_l[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", }; static const char *ampm[] = { "AM", "PM", }; if (s == NULL || format == NULL || timeptr == NULL || maxsize == 0) return 0; /* quick check if we even need to bother */ if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize) return 0; #ifndef POSIX_SEMANTICS if (first) { tzset(); first = 0; } #else /* POSIX_SEMANTICS */ #if defined (SHELL) tz = get_string_value ("TZ"); #else tz = getenv("TZ"); #endif if (first) { if (tz != NULL) { int tzlen = strlen(tz); savetz = (char *) malloc(tzlen + 1); if (savetz != NULL) { savetzlen = tzlen + 1; strcpy(savetz, tz); } } tzset(); first = 0; } /* if we have a saved TZ, and it is different, recapture and reset */ if (tz && savetz && (tz[0] != savetz[0] || strcmp(tz, savetz) != 0)) { i = strlen(tz) + 1; if (i > savetzlen) { savetz = (char *) realloc(savetz, i); if (savetz) { savetzlen = i; strcpy(savetz, tz); } } else strcpy(savetz, tz); tzset(); } #endif /* POSIX_SEMANTICS */ for (; *format && s < endp - 1; format++) { tbuf[0] = '\0'; if (*format != '%') { *s++ = *format; continue; } again: switch (*++format) { case '\0': *s++ = '%'; goto out; case '%': *s++ = '%'; continue; case 'a': /* abbreviated weekday name */ if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6) strcpy(tbuf, "?"); else strcpy(tbuf, days_a[timeptr->tm_wday]); break; case 'A': /* full weekday name */ if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6) strcpy(tbuf, "?"); else strcpy(tbuf, days_l[timeptr->tm_wday]); break; case 'b': /* abbreviated month name */ short_month: if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11) strcpy(tbuf, "?"); else strcpy(tbuf, months_a[timeptr->tm_mon]); break; case 'B': /* full month name */ if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11) strcpy(tbuf, "?"); else strcpy(tbuf, months_l[timeptr->tm_mon]); break; case 'c': /* appropriate date and time representation */ /* * This used to be: * * strftime(tbuf, sizeof tbuf, "%a %b %e %H:%M:%S %Y", timeptr); * * Now, per the ISO 1999 C standard, it this: */ strftime(tbuf, sizeof tbuf, "%A %B %d %T %Y", timeptr); break; case 'C': century: sprintf(tbuf, "%02d", (timeptr->tm_year + 1900) / 100); break; case 'd': /* day of the month, 01 - 31 */ i = range(1, timeptr->tm_mday, 31); sprintf(tbuf, "%02d", i); break; case 'D': /* date as %m/%d/%y */ strftime(tbuf, sizeof tbuf, "%m/%d/%y", timeptr); break; case 'e': /* day of month, blank padded */ sprintf(tbuf, "%2d", range(1, timeptr->tm_mday, 31)); break; case 'E': /* POSIX (now C99) locale extensions, ignored for now */ goto again; case 'F': /* ISO 8601 date representation */ strftime(tbuf, sizeof tbuf, "%Y-%m-%d", timeptr); break; case 'g': case 'G': /* * Year of ISO week. * * If it's December but the ISO week number is one, * that week is in next year. * If it's January but the ISO week number is 52 or * 53, that week is in last year. * Otherwise, it's this year. */ w = iso8601wknum(timeptr); if (timeptr->tm_mon == 11 && w == 1) y = 1900 + timeptr->tm_year + 1; else if (timeptr->tm_mon == 0 && w >= 52) y = 1900 + timeptr->tm_year - 1; else y = 1900 + timeptr->tm_year; if (*format == 'G') sprintf(tbuf, "%d", y); else sprintf(tbuf, "%02d", y % 100); break; case 'h': /* abbreviated month name */ goto short_month; case 'H': /* hour, 24-hour clock, 00 - 23 */ i = range(0, timeptr->tm_hour, 23); sprintf(tbuf, "%02d", i); break; case 'I': /* hour, 12-hour clock, 01 - 12 */ i = range(0, timeptr->tm_hour, 23); if (i == 0) i = 12; else if (i > 12) i -= 12; sprintf(tbuf, "%02d", i); break; case 'j': /* day of the year, 001 - 366 */ sprintf(tbuf, "%03d", timeptr->tm_yday + 1); break; case 'm': /* month, 01 - 12 */ i = range(0, timeptr->tm_mon, 11); sprintf(tbuf, "%02d", i + 1); break; case 'M': /* minute, 00 - 59 */ i = range(0, timeptr->tm_min, 59); sprintf(tbuf, "%02d", i); break; case 'n': /* same as \n */ tbuf[0] = '\n'; tbuf[1] = '\0'; break; case 'O': /* POSIX (now C99) locale extensions, ignored for now */ goto again; case 'p': /* am or pm based on 12-hour clock */ i = range(0, timeptr->tm_hour, 23); if (i < 12) strcpy(tbuf, ampm[0]); else strcpy(tbuf, ampm[1]); break; case 'r': /* time as %I:%M:%S %p */ strftime(tbuf, sizeof tbuf, "%I:%M:%S %p", timeptr); break; case 'R': /* time as %H:%M */ strftime(tbuf, sizeof tbuf, "%H:%M", timeptr); break; #if defined(HAVE_MKTIME) || defined(GAWK) case 's': /* time as seconds since the Epoch */ { struct tm non_const_timeptr; non_const_timeptr = *timeptr; sprintf(tbuf, "%ld", mktime(& non_const_timeptr)); break; } #endif /* defined(HAVE_MKTIME) || defined(GAWK) */ case 'S': /* second, 00 - 60 */ i = range(0, timeptr->tm_sec, 60); sprintf(tbuf, "%02d", i); break; case 't': /* same as \t */ tbuf[0] = '\t'; tbuf[1] = '\0'; break; case 'T': /* time as %H:%M:%S */ the_time: strftime(tbuf, sizeof tbuf, "%H:%M:%S", timeptr); break; case 'u': /* ISO 8601: Weekday as a decimal number [1 (Monday) - 7] */ sprintf(tbuf, "%d", timeptr->tm_wday == 0 ? 7 : timeptr->tm_wday); break; case 'U': /* week of year, Sunday is first day of week */ sprintf(tbuf, "%02d", weeknumber(timeptr, 0)); break; case 'V': /* week of year according ISO 8601 */ sprintf(tbuf, "%02d", iso8601wknum(timeptr)); break; case 'w': /* weekday, Sunday == 0, 0 - 6 */ i = range(0, timeptr->tm_wday, 6); sprintf(tbuf, "%d", i); break; case 'W': /* week of year, Monday is first day of week */ sprintf(tbuf, "%02d", weeknumber(timeptr, 1)); break; case 'x': /* appropriate date representation */ strftime(tbuf, sizeof tbuf, "%A %B %d %Y", timeptr); break; case 'X': /* appropriate time representation */ goto the_time; break; case 'y': /* year without a century, 00 - 99 */ year: i = timeptr->tm_year % 100; sprintf(tbuf, "%02d", i); break; case 'Y': /* year with century */ fullyear: sprintf(tbuf, "%d", 1900 + timeptr->tm_year); break; /* * From: Chip Rosenthal <chip@chinacat.unicom.com> * Date: Sun, 19 Mar 1995 00:33:29 -0600 (CST) * * Warning: the %z [code] is implemented by inspecting the * timezone name conditional compile settings, and * inferring a method to get timezone offsets. I've tried * this code on a couple of machines, but I don't doubt * there is some system out there that won't like it. * Maybe the easiest thing to do would be to bracket this * with an #ifdef that can turn it off. The %z feature * would be an admittedly obscure one that most folks can * live without, but it would be a great help to those of * us that muck around with various message processors. */ case 'z': /* time zone offset east of GMT e.g. -0600 */ #ifdef HAVE_TM_NAME /* * Systems with tm_name probably have tm_tzadj as * secs west of GMT. Convert to mins east of GMT. */ off = -timeptr->tm_tzadj / 60; #else /* !HAVE_TM_NAME */ #ifdef HAVE_TM_ZONE /* * Systems with tm_zone probably have tm_gmtoff as * secs east of GMT. Convert to mins east of GMT. */ off = timeptr->tm_gmtoff / 60; #else /* !HAVE_TM_ZONE */ #if HAVE_TZNAME /* * Systems with tzname[] probably have timezone as * secs west of GMT. Convert to mins east of GMT. */ off = -(daylight ? timezone : altzone) / 60; #else /* !HAVE_TZNAME */ off = -zone.tz_minuteswest; #endif /* !HAVE_TZNAME */ #endif /* !HAVE_TM_ZONE */ #endif /* !HAVE_TM_NAME */ if (off < 0) { tbuf[0] = '-'; off = -off; } else { tbuf[0] = '+'; } sprintf(tbuf+1, "%02d%02d", off/60, off%60); break; case 'Z': /* time zone name or abbrevation */ #ifdef HAVE_TZNAME i = (daylight && timeptr->tm_isdst > 0); /* 0 or 1 */ strcpy(tbuf, tzname[i]); #else #ifdef HAVE_TM_ZONE strcpy(tbuf, timeptr->tm_zone); #else #ifdef HAVE_TM_NAME strcpy(tbuf, timeptr->tm_name); #else gettimeofday(& tv, & zone); strcpy(tbuf, timezone(zone.tz_minuteswest, timeptr->tm_isdst > 0)); #endif /* HAVE_TM_NAME */ #endif /* HAVE_TM_ZONE */ #endif /* HAVE_TZNAME */ break; #ifdef SUNOS_EXT case 'k': /* hour, 24-hour clock, blank pad */ sprintf(tbuf, "%2d", range(0, timeptr->tm_hour, 23)); break; case 'l': /* hour, 12-hour clock, 1 - 12, blank pad */ i = range(0, timeptr->tm_hour, 23); if (i == 0) i = 12; else if (i > 12) i -= 12; sprintf(tbuf, "%2d", i); break; #endif #ifdef HPUX_EXT case 'N': /* Emperor/Era name */ /* this is essentially the same as the century */ goto century; /* %C */ case 'o': /* Emperor/Era year */ goto year; /* %y */ #endif /* HPUX_EXT */ #ifdef VMS_EXT case 'v': /* date as dd-bbb-YYYY */ sprintf(tbuf, "%2d-%3.3s-%4d", range(1, timeptr->tm_mday, 31), months_a[range(0, timeptr->tm_mon, 11)], timeptr->tm_year + 1900); for (i = 3; i < 6; i++) if (islower(tbuf[i])) tbuf[i] = toupper(tbuf[i]); break; #endif default: tbuf[0] = '%'; tbuf[1] = *format; tbuf[2] = '\0'; break; } i = strlen(tbuf); if (i) { if (s + i < endp - 1) { strcpy(s, tbuf); s += i; } else return 0; } } out: if (s < endp && *format == '\0') { *s = '\0'; return (s - start); } else return 0; } /* isleap --- is a year a leap year? */ static int isleap(int year) { return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0); } /* iso8601wknum --- compute week number according to ISO 8601 */ static int iso8601wknum(const struct tm *timeptr) { /* * From 1003.2: * If the week (Monday to Sunday) containing January 1 * has four or more days in the new year, then it is week 1; * otherwise it is the highest numbered week of the previous * year (52 or 53), and the next week is week 1. * * ADR: This means if Jan 1 was Monday through Thursday, * it was week 1, otherwise week 52 or 53. * * XPG4 erroneously included POSIX.2 rationale text in the * main body of the standard. Thus it requires week 53. */ int weeknum, jan1day, diff; /* get week number, Monday as first day of the week */ weeknum = weeknumber(timeptr, 1); /* * With thanks and tip of the hatlo to tml@tik.vtt.fi * * What day of the week does January 1 fall on? * We know that * (timeptr->tm_yday - jan1.tm_yday) MOD 7 == * (timeptr->tm_wday - jan1.tm_wday) MOD 7 * and that * jan1.tm_yday == 0 * and that * timeptr->tm_wday MOD 7 == timeptr->tm_wday * from which it follows that. . . */ jan1day = timeptr->tm_wday - (timeptr->tm_yday % 7); if (jan1day < 0) jan1day += 7; /* * If Jan 1 was a Monday through Thursday, it was in * week 1. Otherwise it was last year's highest week, which is * this year's week 0. * * What does that mean? * If Jan 1 was Monday, the week number is exactly right, it can * never be 0. * If it was Tuesday through Thursday, the weeknumber is one * less than it should be, so we add one. * Otherwise, Friday, Saturday or Sunday, the week number is * OK, but if it is 0, it needs to be 52 or 53. */ switch (jan1day) { case 1: /* Monday */ break; case 2: /* Tuesday */ case 3: /* Wednesday */ case 4: /* Thursday */ weeknum++; break; case 5: /* Friday */ case 6: /* Saturday */ case 0: /* Sunday */ if (weeknum == 0) { #ifdef USE_BROKEN_XPG4 /* XPG4 (as of March 1994) says 53 unconditionally */ weeknum = 53; #else /* get week number of last week of last year */ struct tm dec31ly; /* 12/31 last year */ dec31ly = *timeptr; dec31ly.tm_year--; dec31ly.tm_mon = 11; dec31ly.tm_mday = 31; dec31ly.tm_wday = (jan1day == 0) ? 6 : jan1day - 1; dec31ly.tm_yday = 364 + isleap(dec31ly.tm_year + 1900); weeknum = iso8601wknum(& dec31ly); #endif } break; } if (timeptr->tm_mon == 11) { /* * The last week of the year * can be in week 1 of next year. * Sigh. * * This can only happen if * M T W * 29 30 31 * 30 31 * 31 */ int wday, mday; wday = timeptr->tm_wday; mday = timeptr->tm_mday; if ( (wday == 1 && (mday >= 29 && mday <= 31)) || (wday == 2 && (mday == 30 || mday == 31)) || (wday == 3 && mday == 31)) weeknum = 1; } return weeknum; } /* weeknumber --- figure how many weeks into the year */ /* With thanks and tip of the hatlo to ado@elsie.nci.nih.gov */ static int weeknumber(const struct tm *timeptr, int firstweekday) { int wday = timeptr->tm_wday; int ret; if (firstweekday == 1) { if (wday == 0) /* sunday */ wday = 6; else wday--; } ret = ((timeptr->tm_yday + 7 - wday) / 7); if (ret < 0) ret = 0; return ret; } #if 0 /* ADR --- I'm loathe to mess with ado's code ... */ Date: Wed, 24 Apr 91 20:54:08 MDT From: Michal Jaegermann <audfax!emory!vm.ucs.UAlberta.CA!NTOMCZAK> To: arnold@audiofax.com Hi Arnold, in a process of fixing of strftime() in libraries on Atari ST I grabbed some pieces of code from your own strftime. When doing that it came to mind that your weeknumber() function compiles a little bit nicer in the following form: /* * firstweekday is 0 if starting in Sunday, non-zero if in Monday */ { return (timeptr->tm_yday - timeptr->tm_wday + (firstweekday ? (timeptr->tm_wday ? 8 : 1) : 7)) / 7; } How nicer it depends on a compiler, of course, but always a tiny bit. Cheers, Michal ntomczak@vm.ucs.ualberta.ca #endif #ifdef TEST_STRFTIME /* * NAME: * tst * * SYNOPSIS: * tst * * DESCRIPTION: * "tst" is a test driver for the function "strftime". * * OPTIONS: * None. * * AUTHOR: * Karl Vogel * Control Data Systems, Inc. * vogelke@c-17igp.wpafb.af.mil * * BUGS: * None noticed yet. * * COMPILE: * cc -o tst -DTEST_STRFTIME strftime.c */ /* ADR: I reformatted this to my liking, and deleted some unneeded code. */ #ifndef NULL #include <stdio.h> #endif #include <sys/time.h> #include <string.h> #define MAXTIME 132 /* * Array of time formats. */ static char *array[] = { "(%%A) full weekday name, var length (Sunday..Saturday) %A", "(%%B) full month name, var length (January..December) %B", "(%%C) Century %C", "(%%D) date (%%m/%%d/%%y) %D", "(%%E) Locale extensions (ignored) %E", "(%%F) full month name, var length (January..December) %F", "(%%H) hour (24-hour clock, 00..23) %H", "(%%I) hour (12-hour clock, 01..12) %I", "(%%M) minute (00..59) %M", "(%%N) Emporer/Era Name %N", "(%%O) Locale extensions (ignored) %O", "(%%R) time, 24-hour (%%H:%%M) %R", "(%%S) second (00..60) %S", "(%%T) time, 24-hour (%%H:%%M:%%S) %T", "(%%U) week of year, Sunday as first day of week (00..53) %U", "(%%V) week of year according to ISO 8601 %V", "(%%W) week of year, Monday as first day of week (00..53) %W", "(%%X) appropriate locale time representation (%H:%M:%S) %X", "(%%Y) year with century (1970...) %Y", "(%%Z) timezone (EDT), or blank if timezone not determinable %Z", "(%%a) locale's abbreviated weekday name (Sun..Sat) %a", "(%%b) locale's abbreviated month name (Jan..Dec) %b", "(%%c) full date (Sat Nov 4 12:02:33 1989)%n%t%t%t %c", "(%%d) day of the month (01..31) %d", "(%%e) day of the month, blank-padded ( 1..31) %e", "(%%h) should be same as (%%b) %h", "(%%j) day of the year (001..366) %j", "(%%k) hour, 24-hour clock, blank pad ( 0..23) %k", "(%%l) hour, 12-hour clock, blank pad ( 0..12) %l", "(%%m) month (01..12) %m", "(%%o) Emporer/Era Year %o", "(%%p) locale's AM or PM based on 12-hour clock %p", "(%%r) time, 12-hour (same as %%I:%%M:%%S %%p) %r", "(%%u) ISO 8601: Weekday as decimal number [1 (Monday) - 7] %u", "(%%v) VMS date (dd-bbb-YYYY) %v", "(%%w) day of week (0..6, Sunday == 0) %w", "(%%x) appropriate locale date representation %x", "(%%y) last two digits of year (00..99) %y", "(%%z) timezone offset east of GMT as HHMM (e.g. -0500) %z", (char *) NULL }; /* main routine. */ int main(argc, argv) int argc; char **argv; { long time(); char *next; char string[MAXTIME]; int k; int length; struct tm *tm; long clock; /* Call the function. */ clock = time((long *) 0); tm = localtime(&clock); for (k = 0; next = array[k]; k++) { length = strftime(string, MAXTIME, next, tm); printf("%s\n", string); } exit(0); } #endif /* TEST_STRFTIME */
/* xstrchr.c - strchr(3) that handles multibyte characters. */ /* Copyright (C) 2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #ifdef HAVE_STDLIB_H # include <stdlib.h> #endif #include "bashansi.h" #include "shmbutil.h" #undef xstrchr /* In some locales, the non-first byte of some multibyte characters have the same value as some ascii character. Faced with these strings, a legacy strchr() might return the wrong value. */ char * #if defined (PROTOTYPES) xstrchr (const char *s, int c) #else xstrchr (s, c) const char *s; int c; #endif { #if HANDLE_MULTIBYTE char *pos; mbstate_t state; size_t strlength, mblength; /* The locale encodings with said weird property are BIG5, BIG5-HKSCS, GBK, GB18030, SHIFT_JIS, and JOHAB. They exhibit the problem only when c >= 0x30. We can therefore use the faster bytewise search if c <= 0x30. */ if ((unsigned char)c >= '0' && MB_CUR_MAX > 1) { pos = (char *)s; memset (&state, '\0', sizeof(mbstate_t)); strlength = strlen (s); while (strlength > 0) { mblength = mbrlen (pos, strlength, &state); if (mblength == (size_t)-2 || mblength == (size_t)-1 || mblength == (size_t)0) mblength = 1; if (c == (unsigned char)*pos) return pos; strlength -= mblength; pos += mblength; } return ((char *)NULL); } else #endif return (strchr (s, c)); }
/* Copyright (C) 2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #include <sys/types.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <errno.h> #include <stdc.h> #if !defined (errno) extern int errno; #endif extern ssize_t zread __P((int, char *, size_t)); extern int zwrite __P((int, char *, ssize_t)); /* Dump contents of file descriptor FD to OFD. FN is the filename for error messages (not used right now). */ int zcatfd (fd, ofd, fn) int fd, ofd; char *fn; { ssize_t nr; int rval; char lbuf[128]; rval = 0; while (1) { nr = zread (fd, lbuf, sizeof (lbuf)); if (nr == 0) break; else if (nr < 0) { rval = -1; break; } else if (zwrite (ofd, lbuf, nr) < 0) { rval = -1; break; } } return rval; }
/* glob.c -- file-name wildcard pattern matching for Bash. Copyright (C) 1985-2002 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ /* To whomever it may concern: I have never seen the code which most Unix programs use to perform this function. I wrote this from scratch based on specifications for the pattern matching. --RMS. */ #include <config.h> #if !defined (__GNUC__) && !defined (HAVE_ALLOCA_H) && defined (_AIX) #pragma alloca #endif /* _AIX && RISC6000 && !__GNUC__ */ #include "bashtypes.h" #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "bashansi.h" #include "posixdir.h" #include "posixstat.h" #include "shmbutil.h" #include "xmalloc.h" #include "filecntl.h" #if !defined (F_OK) # define F_OK 0 #endif #include "stdc.h" #include "memalloc.h" #include "quit.h" #include "glob.h" #include "strmatch.h" #if !defined (HAVE_BCOPY) && !defined (bcopy) # define bcopy(s, d, n) ((void) memcpy ((d), (s), (n))) #endif /* !HAVE_BCOPY && !bcopy */ #if !defined (NULL) # if defined (__STDC__) # define NULL ((void *) 0) # else # define NULL 0x0 # endif /* __STDC__ */ #endif /* !NULL */ extern void throw_to_top_level __P((void)); extern int test_eaccess __P((char *, int)); extern int extended_glob; /* Global variable which controls whether or not * matches .*. Non-zero means don't match .*. */ int noglob_dot_filenames = 1; /* Global variable which controls whether or not filename globbing is done without regard to case. */ int glob_ignore_case = 0; /* Global variable to return to signify an error in globbing. */ char *glob_error_return; /* Some forward declarations. */ static int skipname __P((char *, char *)); #if HANDLE_MULTIBYTE static int mbskipname __P((char *, char *)); #endif #if HANDLE_MULTIBYTE static void udequote_pathname __P((char *)); static void wdequote_pathname __P((char *)); #else # define dequote_pathname udequote_pathname #endif static void dequote_pathname __P((char *)); static int glob_testdir __P((char *)); static char **glob_dir_to_array __P((char *, char **, int)); /* Compile `glob_loop.c' for single-byte characters. */ #define CHAR unsigned char #define INT int #define L(CS) CS #define INTERNAL_GLOB_PATTERN_P internal_glob_pattern_p #include "glob_loop.c" /* Compile `glob_loop.c' again for multibyte characters. */ #if HANDLE_MULTIBYTE #define CHAR wchar_t #define INT wint_t #define L(CS) L##CS #define INTERNAL_GLOB_PATTERN_P internal_glob_wpattern_p #include "glob_loop.c" #endif /* HANDLE_MULTIBYTE */ /* And now a function that calls either the single-byte or multibyte version of internal_glob_pattern_p. */ int glob_pattern_p (pattern) const char *pattern; { #if HANDLE_MULTIBYTE mbstate_t ps; size_t n; wchar_t *wpattern; int r; if (MB_CUR_MAX == 1) return (internal_glob_pattern_p (pattern)); /* Convert strings to wide chars, and call the multibyte version. */ memset (&ps, '\0', sizeof (ps)); n = xmbsrtowcs (NULL, (const char **)&pattern, 0, &ps); if (n == (size_t)-1) /* Oops. Invalid multibyte sequence. Try it as single-byte sequence. */ return (internal_glob_pattern_p (pattern)); wpattern = (wchar_t *)xmalloc ((n + 1) * sizeof (wchar_t)); (void) xmbsrtowcs (wpattern, (const char **)&pattern, n + 1, &ps); r = internal_glob_wpattern_p (wpattern); free (wpattern); return r; #else return (internal_glob_pattern_p (pattern)); #endif } /* Return 1 if DNAME should be skipped according to PAT. Mostly concerned with matching leading `.'. */ static int skipname (pat, dname) char *pat; char *dname; { /* If a leading dot need not be explicitly matched, and the pattern doesn't start with a `.', don't match `.' or `..' */ if (noglob_dot_filenames == 0 && pat[0] != '.' && (pat[0] != '\\' || pat[1] != '.') && (dname[0] == '.' && (dname[1] == '\0' || (dname[1] == '.' && dname[2] == '\0')))) return 1; /* If a dot must be explicity matched, check to see if they do. */ else if (noglob_dot_filenames && dname[0] == '.' && pat[0] != '.' && (pat[0] != '\\' || pat[1] != '.')) return 1; return 0; } #if HANDLE_MULTIBYTE /* Return 1 if DNAME should be skipped according to PAT. Handles multibyte characters in PAT and DNAME. Mostly concerned with matching leading `.'. */ static int mbskipname (pat, dname) char *pat, *dname; { char *pat_bak, *dn_bak; wchar_t *pat_wc, *dn_wc; mbstate_t pat_ps, dn_ps; size_t pat_n, dn_n, n; n = strlen(pat); pat_bak = (char *) alloca (n + 1); memcpy (pat_bak, pat, n + 1); n = strlen(dname); dn_bak = (char *) alloca (n + 1); memcpy (dn_bak, dname, n + 1); memset(&pat_ps, '\0', sizeof(mbstate_t)); memset(&dn_ps, '\0', sizeof(mbstate_t)); pat_n = xmbsrtowcs (NULL, (const char **)&pat_bak, 0, &pat_ps); dn_n = xmbsrtowcs (NULL, (const char **)&dn_bak, 0, &dn_ps); if (pat_n != (size_t)-1 && dn_n !=(size_t)-1) { pat_wc = (wchar_t *) alloca ((pat_n + 1) * sizeof(wchar_t)); dn_wc = (wchar_t *) alloca ((dn_n + 1) * sizeof(wchar_t)); (void) xmbsrtowcs (pat_wc, (const char **)&pat_bak, pat_n + 1, &pat_ps); (void) xmbsrtowcs (dn_wc, (const char **)&dn_bak, dn_n + 1, &dn_ps); /* If a leading dot need not be explicitly matched, and the pattern doesn't start with a `.', don't match `.' or `..' */ if (noglob_dot_filenames == 0 && pat_wc[0] != L'.' && (pat_wc[0] != L'\\' || pat_wc[1] != L'.') && (dn_wc[0] == L'.' && (dn_wc[1] == L'\0' || (dn_wc[1] == L'.' && dn_wc[2] == L'\0')))) return 1; /* If a leading dot must be explicity matched, check to see if the pattern and dirname both have one. */ else if (noglob_dot_filenames && dn_wc[0] == L'.' && pat_wc[0] != L'.' && (pat_wc[0] != L'\\' || pat_wc[1] != L'.')) return 1; } return 0; } #endif /* HANDLE_MULTIBYTE */ /* Remove backslashes quoting characters in PATHNAME by modifying PATHNAME. */ static void udequote_pathname (pathname) char *pathname; { register int i, j; for (i = j = 0; pathname && pathname[i]; ) { if (pathname[i] == '\\') i++; pathname[j++] = pathname[i++]; if (!pathname[i - 1]) break; } pathname[j] = '\0'; } #if HANDLE_MULTIBYTE /* Remove backslashes quoting characters in PATHNAME by modifying PATHNAME. */ static void wdequote_pathname (pathname) char *pathname; { mbstate_t ps; size_t len, n; wchar_t *wpathname; char *pathname_bak; int i, j; len = strlen (pathname); pathname_bak = (char *) alloca (len + 1); memcpy (pathname_bak, pathname , len + 1); /* Convert the strings into wide characters. */ memset (&ps, '\0', sizeof (ps)); n = xmbsrtowcs (NULL, (const char **)&pathname_bak, 0, &ps); if (n == (size_t) -1) /* Something wrong. */ return; wpathname = (wchar_t *) alloca ((n + 1) * sizeof (wchar_t)); (void) xmbsrtowcs (wpathname, (const char **)&pathname_bak, n + 1, &ps); for (i = j = 0; wpathname && wpathname[i]; ) { if (wpathname[i] == L'\\') i++; wpathname[j++] = wpathname[i++]; if (!wpathname[i - 1]) break; } wpathname[j] = L'\0'; /* Convert the wide character string into unibyte character set. */ memset (&ps, '\0', sizeof(mbstate_t)); n = wcsrtombs(pathname, (const wchar_t **)&wpathname, len, &ps); pathname[len] = '\0'; } static void dequote_pathname (pathname) char *pathname; { if (MB_CUR_MAX > 1) wdequote_pathname (pathname); else udequote_pathname (pathname); } #endif /* HANDLE_MULTIBYTE */ /* Test whether NAME exists. */ #if defined (HAVE_LSTAT) # define GLOB_TESTNAME(name) (lstat (name, &finfo)) #else /* !HAVE_LSTAT */ # if !defined (AFS) # define GLOB_TESTNAME(name) (test_eaccess (nextname, F_OK)) # else /* AFS */ # define GLOB_TESTNAME(name) (access (nextname, F_OK)) # endif /* AFS */ #endif /* !HAVE_LSTAT */ /* Return 0 if DIR is a directory, -1 otherwise. */ static int glob_testdir (dir) char *dir; { struct stat finfo; if (stat (dir, &finfo) < 0) return (-1); if (S_ISDIR (finfo.st_mode) == 0) return (-1); return (0); } /* Return a vector of names of files in directory DIR whose names match glob pattern PAT. The names are not in any particular order. Wildcards at the beginning of PAT do not match an initial period. The vector is terminated by an element that is a null pointer. To free the space allocated, first free the vector's elements, then free the vector. Return 0 if cannot get enough memory to hold the pointer and the names. Return -1 if cannot access directory DIR. Look in errno for more information. */ char ** glob_vector (pat, dir, flags) char *pat; char *dir; int flags; { struct globval { struct globval *next; char *name; }; DIR *d; register struct dirent *dp; struct globval *lastlink; register struct globval *nextlink; register char *nextname, *npat; unsigned int count; int lose, skip; register char **name_vector; register unsigned int i; int mflags; /* Flags passed to strmatch (). */ lastlink = 0; count = lose = skip = 0; /* If PAT is empty, skip the loop, but return one (empty) filename. */ if (pat == 0 || *pat == '\0') { if (glob_testdir (dir) < 0) return ((char **) &glob_error_return); nextlink = (struct globval *)alloca (sizeof (struct globval)); nextlink->next = (struct globval *)0; nextname = (char *) malloc (1); if (nextname == 0) lose = 1; else { lastlink = nextlink; nextlink->name = nextname; nextname[0] = '\0'; count = 1; } skip = 1; } /* If the filename pattern (PAT) does not contain any globbing characters, we can dispense with reading the directory, and just see if there is a filename `DIR/PAT'. If there is, and we can access it, just make the vector to return and bail immediately. */ if (skip == 0 && glob_pattern_p (pat) == 0) { int dirlen; struct stat finfo; if (glob_testdir (dir) < 0) return ((char **) &glob_error_return); dirlen = strlen (dir); nextname = (char *)malloc (dirlen + strlen (pat) + 2); npat = (char *)malloc (strlen (pat) + 1); if (nextname == 0 || npat == 0) lose = 1; else { strcpy (npat, pat); dequote_pathname (npat); strcpy (nextname, dir); nextname[dirlen++] = '/'; strcpy (nextname + dirlen, npat); if (GLOB_TESTNAME (nextname) >= 0) { free (nextname); nextlink = (struct globval *)alloca (sizeof (struct globval)); nextlink->next = (struct globval *)0; lastlink = nextlink; nextlink->name = npat; count = 1; } else { free (nextname); free (npat); } } skip = 1; } if (skip == 0) { /* Open the directory, punting immediately if we cannot. If opendir is not robust (i.e., it opens non-directories successfully), test that DIR is a directory and punt if it's not. */ #if defined (OPENDIR_NOT_ROBUST) if (glob_testdir (dir) < 0) return ((char **) &glob_error_return); #endif d = opendir (dir); if (d == NULL) return ((char **) &glob_error_return); /* Compute the flags that will be passed to strmatch(). We don't need to do this every time through the loop. */ mflags = (noglob_dot_filenames ? FNM_PERIOD : 0) | FNM_PATHNAME; #ifdef FNM_CASEFOLD if (glob_ignore_case) mflags |= FNM_CASEFOLD; #endif if (extended_glob) mflags |= FNM_EXTMATCH; /* Scan the directory, finding all names that match. For each name that matches, allocate a struct globval on the stack and store the name in it. Chain those structs together; lastlink is the front of the chain. */ while (1) { /* Make globbing interruptible in the shell. */ if (interrupt_state) { lose = 1; break; } dp = readdir (d); if (dp == NULL) break; /* If this directory entry is not to be used, try again. */ if (REAL_DIR_ENTRY (dp) == 0) continue; #if HANDLE_MULTIBYTE if (MB_CUR_MAX > 1 && mbskipname (pat, dp->d_name)) continue; else #endif if (skipname (pat, dp->d_name)) continue; if (strmatch (pat, dp->d_name, mflags) != FNM_NOMATCH) { nextlink = (struct globval *) alloca (sizeof (struct globval)); nextlink->next = lastlink; nextname = (char *) malloc (D_NAMLEN (dp) + 1); if (nextname == NULL) { lose = 1; break; } lastlink = nextlink; nextlink->name = nextname; bcopy (dp->d_name, nextname, D_NAMLEN (dp) + 1); ++count; } } (void) closedir (d); } if (lose == 0) { name_vector = (char **) malloc ((count + 1) * sizeof (char *)); lose |= name_vector == NULL; } /* Have we run out of memory? */ if (lose) { /* Here free the strings we have got. */ while (lastlink) { free (lastlink->name); lastlink = lastlink->next; } QUIT; return ((char **)NULL); } /* Copy the name pointers from the linked list into the vector. */ for (i = 0; i < count; ++i) { name_vector[i] = lastlink->name; lastlink = lastlink->next; } name_vector[count] = NULL; return (name_vector); } /* Return a new array which is the concatenation of each string in ARRAY to DIR. This function expects you to pass in an allocated ARRAY, and it takes care of free()ing that array. Thus, you might think of this function as side-effecting ARRAY. This should handle GX_MARKDIRS. */ static char ** glob_dir_to_array (dir, array, flags) char *dir, **array; int flags; { register unsigned int i, l; int add_slash; char **result, *new; struct stat sb; l = strlen (dir); if (l == 0) { if (flags & GX_MARKDIRS) for (i = 0; array[i]; i++) { if ((stat (array[i], &sb) == 0) && S_ISDIR (sb.st_mode)) { l = strlen (array[i]); new = (char *)realloc (array[i], l + 2); if (new == 0) return NULL; new[l] = '/'; new[l+1] = '\0'; array[i] = new; } } return (array); } add_slash = dir[l - 1] != '/'; i = 0; while (array[i] != NULL) ++i; result = (char **) malloc ((i + 1) * sizeof (char *)); if (result == NULL) return (NULL); for (i = 0; array[i] != NULL; i++) { /* 3 == 1 for NUL, 1 for slash at end of DIR, 1 for GX_MARKDIRS */ result[i] = (char *) malloc (l + strlen (array[i]) + 3); if (result[i] == NULL) return (NULL); strcpy (result[i], dir); if (add_slash) result[i][l] = '/'; strcpy (result[i] + l + add_slash, array[i]); if (flags & GX_MARKDIRS) { if ((stat (result[i], &sb) == 0) && S_ISDIR (sb.st_mode)) { size_t rlen; rlen = strlen (result[i]); result[i][rlen] = '/'; result[i][rlen+1] = '\0'; } } } result[i] = NULL; /* Free the input array. */ for (i = 0; array[i] != NULL; i++) free (array[i]); free ((char *) array); return (result); } /* Do globbing on PATHNAME. Return an array of pathnames that match, marking the end of the array with a null-pointer as an element. If no pathnames match, then the array is empty (first element is null). If there isn't enough memory, then return NULL. If a file system error occurs, return -1; `errno' has the error code. */ char ** glob_filename (pathname, flags) char *pathname; int flags; { char **result; unsigned int result_size; char *directory_name, *filename; unsigned int directory_len; result = (char **) malloc (sizeof (char *)); result_size = 1; if (result == NULL) return (NULL); result[0] = NULL; /* Find the filename. */ filename = strrchr (pathname, '/'); if (filename == NULL) { filename = pathname; directory_name = ""; directory_len = 0; } else { directory_len = (filename - pathname) + 1; directory_name = (char *) alloca (directory_len + 1); bcopy (pathname, directory_name, directory_len); directory_name[directory_len] = '\0'; ++filename; } /* If directory_name contains globbing characters, then we have to expand the previous levels. Just recurse. */ if (glob_pattern_p (directory_name)) { char **directories; register unsigned int i; if (directory_name[directory_len - 1] == '/') directory_name[directory_len - 1] = '\0'; directories = glob_filename (directory_name, flags & ~GX_MARKDIRS); if (directories == NULL) goto memory_error; else if (directories == (char **)&glob_error_return) { free ((char *) result); return ((char **) &glob_error_return); } else if (*directories == NULL) { free ((char *) directories); free ((char *) result); return ((char **) &glob_error_return); } /* We have successfully globbed the preceding directory name. For each name in DIRECTORIES, call glob_vector on it and FILENAME. Concatenate the results together. */ for (i = 0; directories[i] != NULL; ++i) { char **temp_results; /* Scan directory even on a NULL pathname. That way, `*h/' returns only directories ending in `h', instead of all files ending in `h' with a `/' appended. */ temp_results = glob_vector (filename, directories[i], flags & ~GX_MARKDIRS); /* Handle error cases. */ if (temp_results == NULL) goto memory_error; else if (temp_results == (char **)&glob_error_return) /* This filename is probably not a directory. Ignore it. */ ; else { char **array; register unsigned int l; array = glob_dir_to_array (directories[i], temp_results, flags); l = 0; while (array[l] != NULL) ++l; result = (char **)realloc (result, (result_size + l) * sizeof (char *)); if (result == NULL) goto memory_error; for (l = 0; array[l] != NULL; ++l) result[result_size++ - 1] = array[l]; result[result_size - 1] = NULL; /* Note that the elements of ARRAY are not freed. */ free ((char *) array); } } /* Free the directories. */ for (i = 0; directories[i]; i++) free (directories[i]); free ((char *) directories); return (result); } /* If there is only a directory name, return it. */ if (*filename == '\0') { result = (char **) realloc ((char *) result, 2 * sizeof (char *)); if (result == NULL) return (NULL); /* Handle GX_MARKDIRS here. */ result[0] = (char *) malloc (directory_len + 1); if (result[0] == NULL) goto memory_error; bcopy (directory_name, result[0], directory_len + 1); result[1] = NULL; return (result); } else { char **temp_results; /* There are no unquoted globbing characters in DIRECTORY_NAME. Dequote it before we try to open the directory since there may be quoted globbing characters which should be treated verbatim. */ if (directory_len > 0) dequote_pathname (directory_name); /* We allocated a small array called RESULT, which we won't be using. Free that memory now. */ free (result); /* Just return what glob_vector () returns appended to the directory name. */ temp_results = glob_vector (filename, (directory_len == 0 ? "." : directory_name), flags & ~GX_MARKDIRS); if (temp_results == NULL || temp_results == (char **)&glob_error_return) return (temp_results); return (glob_dir_to_array (directory_name, temp_results, flags)); } /* We get to memory_error if the program has run out of memory, or if this is the shell, and we have been interrupted. */ memory_error: if (result != NULL) { register unsigned int i; for (i = 0; result[i] != NULL; ++i) free (result[i]); free ((char *) result); } QUIT; return (NULL); } #if defined (TEST) main (argc, argv) int argc; char **argv; { unsigned int i; for (i = 1; i < argc; ++i) { char **value = glob_filename (argv[i], 0); if (value == NULL) puts ("Out of memory."); else if (value == &glob_error_return) perror (argv[i]); else for (i = 0; value[i] != NULL; i++) puts (value[i]); } exit (0); } #endif /* TEST. */
/* strmatch.c -- ksh-like extended pattern matching for the shell and filename globbing. */ /* Copyright (C) 1991-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #include "stdc.h" #include "strmatch.h" /* Structured this way so that if HAVE_LIBC_FNM_EXTMATCH is defined, the matching portion of the library (smatch.c) is not linked into the shell. */ #ifndef HAVE_LIBC_FNM_EXTMATCH extern int xstrmatch __P((char *, char *, int)); #else # define xstrmatch fnmatch #endif int strmatch (pattern, string, flags) char *pattern; char *string; int flags; { if (string == 0 || pattern == 0) return FNM_NOMATCH; return (xstrmatch (pattern, string, flags)); } #ifdef TEST main (c, v) int c; char **v; { char *string, *pat; string = v[1]; pat = v[2]; if (strmatch (pat, string, 0) == 0) { printf ("%s matches %s\n", string, pat); exit (0); } else { printf ("%s does not match %s\n", string, pat); exit (1); } } #endif
/* Copyright (C) 1991 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #ifndef _STRMATCH_H #define _STRMATCH_H 1 #ifdef HAVE_LIBC_FNM_EXTMATCH #include <fnmatch.h> #else /* !HAVE_LIBC_FNM_EXTMATCH */ #include "stdc.h" /* We #undef these before defining them because some losing systems (HP-UX A.08.07 for example) define these in <unistd.h>. */ #undef FNM_PATHNAME #undef FNM_NOESCAPE #undef FNM_PERIOD /* Bits set in the FLAGS argument to `strmatch'. */ /* standard flags are like fnmatch(3). */ #define FNM_PATHNAME (1 << 0) /* No wildcard can ever match `/'. */ #define FNM_NOESCAPE (1 << 1) /* Backslashes don't quote special chars. */ #define FNM_PERIOD (1 << 2) /* Leading `.' is matched only explicitly. */ /* extended flags not available in most libc fnmatch versions, but we undef them to avoid any possible warnings. */ #undef FNM_LEADING_DIR #undef FNM_CASEFOLD #undef FNM_EXTMATCH #define FNM_LEADING_DIR (1 << 3) /* Ignore `/...' after a match. */ #define FNM_CASEFOLD (1 << 4) /* Compare without regard to case. */ #define FNM_EXTMATCH (1 << 5) /* Use ksh-like extended matching. */ /* Value returned by `strmatch' if STRING does not match PATTERN. */ #undef FNM_NOMATCH #define FNM_NOMATCH 1 /* Match STRING against the filename pattern PATTERN, returning zero if it matches, FNM_NOMATCH if not. */ extern int strmatch __P((char *, char *, int)); #endif /* !HAVE_LIBC_FNM_EXTMATCH */ #endif /* _STRMATCH_H */
/* strmatch.c -- ksh-like extended pattern matching for the shell and filename globbing. */ /* Copyright (C) 1991-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #include <stdio.h> /* for debugging */ #include "strmatch.h" #include <chartypes.h> #include "bashansi.h" #include "shmbutil.h" #include "xmalloc.h" /* First, compile `sm_loop.c' for single-byte characters. */ #define CHAR unsigned char #define U_CHAR unsigned char #define XCHAR char #define INT int #define L(CS) CS #define INVALID -1 #undef STREQ #undef STREQN #define STREQ(a, b) ((a)[0] == (b)[0] && strcmp(a, b) == 0) #define STREQN(a, b, n) ((a)[0] == (b)[0] && strncmp(a, b, n) == 0) /* We use strcoll(3) for range comparisons in bracket expressions, even though it can have unwanted side effects in locales other than POSIX or US. For instance, in the de locale, [A-Z] matches all characters. */ #if defined (HAVE_STRCOLL) /* Helper function for collating symbol equivalence. */ static int rangecmp (c1, c2) int c1, c2; { static char s1[2] = { ' ', '\0' }; static char s2[2] = { ' ', '\0' }; int ret; /* Eight bits only. Period. */ c1 &= 0xFF; c2 &= 0xFF; if (c1 == c2) return (0); s1[0] = c1; s2[0] = c2; if ((ret = strcoll (s1, s2)) != 0) return ret; return (c1 - c2); } #else /* !HAVE_STRCOLL */ # define rangecmp(c1, c2) ((int)(c1) - (int)(c2)) #endif /* !HAVE_STRCOLL */ #if defined (HAVE_STRCOLL) static int collequiv (c1, c2) int c1, c2; { return (rangecmp (c1, c2) == 0); } #else # define collequiv(c1, c2) ((c1) == (c2)) #endif #define _COLLSYM _collsym #define __COLLSYM __collsym #define POSIXCOLL posix_collsyms #include "collsyms.h" static int collsym (s, len) char *s; int len; { register struct _collsym *csp; for (csp = posix_collsyms; csp->name; csp++) { if (STREQN(csp->name, s, len) && csp->name[len] == '\0') return (csp->code); } if (len == 1) return s[0]; return INVALID; } /* unibyte character classification */ #if !defined (isascii) && !defined (HAVE_ISASCII) # define isascii(c) ((unsigned int)(c) <= 0177) #endif enum char_class { CC_NO_CLASS = 0, CC_ASCII, CC_ALNUM, CC_ALPHA, CC_BLANK, CC_CNTRL, CC_DIGIT, CC_GRAPH, CC_LOWER, CC_PRINT, CC_PUNCT, CC_SPACE, CC_UPPER, CC_WORD, CC_XDIGIT }; static char const *const cclass_name[] = { "", "ascii", "alnum", "alpha", "blank", "cntrl", "digit", "graph", "lower", "print", "punct", "space", "upper", "word", "xdigit" }; #define N_CHAR_CLASS (sizeof(cclass_name) / sizeof (cclass_name[0])) static int is_cclass (c, name) int c; const char *name; { enum char_class char_class = CC_NO_CLASS; int i, result; for (i = 1; i < N_CHAR_CLASS; i++) { if (STREQ (name, cclass_name[i])) { char_class = (enum char_class)i; break; } } if (char_class == 0) return -1; switch (char_class) { case CC_ASCII: result = isascii (c); break; case CC_ALNUM: result = ISALNUM (c); break; case CC_ALPHA: result = ISALPHA (c); break; case CC_BLANK: result = ISBLANK (c); break; case CC_CNTRL: result = ISCNTRL (c); break; case CC_DIGIT: result = ISDIGIT (c); break; case CC_GRAPH: result = ISGRAPH (c); break; case CC_LOWER: result = ISLOWER (c); break; case CC_PRINT: result = ISPRINT (c); break; case CC_PUNCT: result = ISPUNCT (c); break; case CC_SPACE: result = ISSPACE (c); break; case CC_UPPER: result = ISUPPER (c); break; case CC_WORD: result = (ISALNUM (c) || c == '_'); break; case CC_XDIGIT: result = ISXDIGIT (c); break; default: result = -1; break; } return result; } /* Now include `sm_loop.c' for single-byte characters. */ /* The result of FOLD is an `unsigned char' */ # define FOLD(c) ((flags & FNM_CASEFOLD) \ ? TOLOWER ((unsigned char)c) \ : ((unsigned char)c)) #define FCT internal_strmatch #define GMATCH gmatch #define COLLSYM collsym #define PARSE_COLLSYM parse_collsym #define BRACKMATCH brackmatch #define PATSCAN patscan #define STRCOMPARE strcompare #define EXTMATCH extmatch #define STRCHR(S, C) strchr((S), (C)) #define STRCOLL(S1, S2) strcoll((S1), (S2)) #define STRLEN(S) strlen(S) #define STRCMP(S1, S2) strcmp((S1), (S2)) #define RANGECMP(C1, C2) rangecmp((C1), (C2)) #define COLLEQUIV(C1, C2) collequiv((C1), (C2)) #define CTYPE_T enum char_class #define IS_CCLASS(C, S) is_cclass((C), (S)) #include "sm_loop.c" #if HANDLE_MULTIBYTE # define CHAR wchar_t # define U_CHAR wint_t # define XCHAR wchar_t # define INT wint_t # define L(CS) L##CS # define INVALID WEOF # undef STREQ # undef STREQN # define STREQ(s1, s2) ((wcscmp (s1, s2) == 0)) # define STREQN(a, b, n) ((a)[0] == (b)[0] && wcsncmp(a, b, n) == 0) static int rangecmp_wc (c1, c2) wint_t c1, c2; { static wchar_t s1[2] = { L' ', L'\0' }; static wchar_t s2[2] = { L' ', L'\0' }; int ret; if (c1 == c2) return 0; s1[0] = c1; s2[0] = c2; return (wcscoll (s1, s2)); } static int collequiv_wc (c, equiv) wint_t c, equiv; { return (!(c - equiv)); } /* Helper function for collating symbol. */ # define _COLLSYM _collwcsym # define __COLLSYM __collwcsym # define POSIXCOLL posix_collwcsyms # include "collsyms.h" static wint_t collwcsym (s, len) wchar_t *s; int len; { register struct _collwcsym *csp; for (csp = posix_collwcsyms; csp->name; csp++) { if (STREQN(csp->name, s, len) && csp->name[len] == L'\0') return (csp->code); } if (len == 1) return s[0]; return INVALID; } static int is_wcclass (wc, name) wint_t wc; wchar_t *name; { char *mbs; mbstate_t state; size_t mbslength; wctype_t desc; int want_word; if ((wctype ("ascii") == (wctype_t)0) && (wcscmp (name, L"ascii") == 0)) { int c; if ((c = wctob (wc)) == EOF) return 0; else return (c <= 0x7F); } want_word = (wcscmp (name, L"word") == 0); if (want_word) name = L"alnum"; memset (&state, '\0', sizeof (mbstate_t)); mbs = (char *) malloc (wcslen(name) * MB_CUR_MAX + 1); mbslength = wcsrtombs(mbs, (const wchar_t **)&name, (wcslen(name) * MB_CUR_MAX + 1), &state); if (mbslength == (size_t)-1 || mbslength == (size_t)-2) { free (mbs); return -1; } desc = wctype (mbs); free (mbs); if (desc == (wctype_t)0) return -1; if (want_word) return (iswctype (wc, desc) || wc == L'_'); else return (iswctype (wc, desc)); } /* Now include `sm_loop.c' for multibyte characters. */ #define FOLD(c) ((flags & FNM_CASEFOLD) && iswupper (c) ? towlower (c) : (c)) #define FCT internal_wstrmatch #define GMATCH gmatch_wc #define COLLSYM collwcsym #define PARSE_COLLSYM parse_collwcsym #define BRACKMATCH brackmatch_wc #define PATSCAN patscan_wc #define STRCOMPARE wscompare #define EXTMATCH extmatch_wc #define STRCHR(S, C) wcschr((S), (C)) #define STRCOLL(S1, S2) wcscoll((S1), (S2)) #define STRLEN(S) wcslen(S) #define STRCMP(S1, S2) wcscmp((S1), (S2)) #define RANGECMP(C1, C2) rangecmp_wc((C1), (C2)) #define COLLEQUIV(C1, C2) collequiv_wc((C1), (C2)) #define CTYPE_T enum char_class #define IS_CCLASS(C, S) is_wcclass((C), (S)) #include "sm_loop.c" #endif /* HAVE_MULTIBYTE */ int xstrmatch (pattern, string, flags) char *pattern; char *string; int flags; { #if HANDLE_MULTIBYTE int ret; mbstate_t ps; size_t n; char *pattern_bak; wchar_t *wpattern, *wstring; if (MB_CUR_MAX == 1) return (internal_strmatch (pattern, string, flags)); pattern_bak = (char *)xmalloc (strlen (pattern) + 1); strcpy (pattern_bak, pattern); memset (&ps, '\0', sizeof (mbstate_t)); n = xmbsrtowcs (NULL, (const char **)&pattern, 0, &ps); if (n == (size_t)-1 || n == (size_t)-2) { free (pattern_bak); return (internal_strmatch ((unsigned char *)pattern, (unsigned char *)string, flags)); } wpattern = (wchar_t *)xmalloc ((n + 1) * sizeof (wchar_t)); (void) xmbsrtowcs (wpattern, (const char **)&pattern, n + 1, &ps); memset (&ps, '\0', sizeof (mbstate_t)); n = xmbsrtowcs (NULL, (const char **)&string, 0, &ps); if (n == (size_t)-1 || n == (size_t)-2) { free (wpattern); ret = internal_strmatch (pattern_bak, string, flags); free (pattern_bak); return ret; } wstring = (wchar_t *)xmalloc ((n + 1) * sizeof (wchar_t)); (void) xmbsrtowcs (wstring, (const char **)&string, n + 1, &ps); ret = internal_wstrmatch (wpattern, wstring, flags); free (pattern_bak); free (wpattern); free (wstring); return ret; #else return (internal_strmatch ((unsigned char *)pattern, (unsigned char *)string, flags)); #endif /* !HANDLE_MULTIBYTE */ }
/* shmbutil.h -- utility functions for multibyte characters. */ /* Copyright (C) 2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_SH_MBUTIL_H_) #define _SH_MBUTIL_H_ #include "stdc.h" /************************************************/ /* check multibyte capability for I18N code */ /************************************************/ /* For platforms which support the ISO C amendement 1 functionality we support user defined character classes. */ /* Solaris 2.5 has a bug: <wchar.h> must be included before <wctype.h>. */ #if defined (HAVE_WCTYPE_H) && defined (HAVE_WCHAR_H) # include <wchar.h> # include <wctype.h> # if defined (HAVE_MBSRTOWCS) /* system is supposed to support XPG5 */ # define HANDLE_MULTIBYTE 1 # endif #endif /* HAVE_WCTYPE_H && HAVE_WCHAR_H */ /* Some systems, like BeOS, have multibyte encodings but lack mbstate_t. */ #if HANDLE_MULTIBYTE && !defined (HAVE_MBSTATE_T) # define wcsrtombs(dest, src, len, ps) (wcsrtombs) (dest, src, len, 0) # define mbsrtowcs(dest, src, len, ps) (mbsrtowcs) (dest, src, len, 0) # define wcrtomb(s, wc, ps) (wcrtomb) (s, wc, 0) # define mbrtowc(pwc, s, n, ps) (mbrtowc) (pwc, s, n, 0) # define mbrlen(s, n, ps) (mbrlen) (s, n, 0) # define mbstate_t int #endif /* HANDLE_MULTIBYTE && !HAVE_MBSTATE_T */ /* Make sure MB_LEN_MAX is at least 16 on systems that claim to be able to handle multibyte chars (some systems define MB_LEN_MAX as 1) */ #ifdef HANDLE_MULTIBYTE # include <limits.h> # if defined(MB_LEN_MAX) && (MB_LEN_MAX < 16) # undef MB_LEN_MAX # endif # if !defined (MB_LEN_MAX) # define MB_LEN_MAX 16 # endif #endif /* HANDLE_MULTIBYTE */ /************************************************/ /* end of multibyte capability checks for I18N */ /************************************************/ #if defined (HANDLE_MULTIBYTE) extern size_t xmbsrtowcs __P((wchar_t *, const char **, size_t, mbstate_t *)); extern char *xstrchr __P((const char *, int)); #else /* !HANDLE_MULTIBYTE */ #undef MB_LEN_MAX #undef MB_CUR_MAX #define MB_LEN_MAX 1 #define MB_CUR_MAX 1 #undef xstrchr #define xstrchr(s, c) strchr(s, c) #endif /* !HANDLE_MULTIBYTE */ /* Declare and initialize a multibyte state. Call must be terminated with `;'. */ #if defined (HANDLE_MULTIBYTE) # define DECLARE_MBSTATE \ mbstate_t state; \ memset (&state, '\0', sizeof (mbstate_t)) #else # define DECLARE_MBSTATE #endif /* !HANDLE_MULTIBYTE */ /* Initialize or reinitialize a multibyte state named `state'. Call must be terminated with `;'. */ #if defined (HANDLE_MULTIBYTE) # define INITIALIZE_MBSTATE memset (&state, '\0', sizeof (mbstate_t)) #else # define INITIALIZE_MBSTATE #endif /* !HANDLE_MULTIBYTE */ /* Advance one (possibly multi-byte) character in string _STR of length _STRSIZE, starting at index _I. STATE must have already been declared. */ #if defined (HANDLE_MULTIBYTE) # define ADVANCE_CHAR(_str, _strsize, _i) \ do \ { \ if (MB_CUR_MAX > 1) \ { \ mbstate_t state_bak; \ size_t mblength; \ \ state_bak = state; \ mblength = mbrlen ((_str) + (_i), (_strsize) - (_i), &state); \ \ if (mblength == (size_t)-2 || mblength == (size_t)-1) \ { \ state = state_bak; \ (_i)++; \ } \ else \ (_i) += mblength; \ } \ else \ (_i)++; \ } \ while (0) #else # define ADVANCE_CHAR(_str, _strsize, _i) (_i)++ #endif /* !HANDLE_MULTIBYTE */ /* Advance one (possibly multibyte) character in the string _STR of length _STRSIZE. SPECIAL: assume that _STR will be incremented by 1 after this call. */ #if defined (HANDLE_MULTIBYTE) # define ADVANCE_CHAR_P(_str, _strsize) \ do \ { \ if (MB_CUR_MAX > 1) \ { \ mbstate_t state_bak; \ size_t mblength; \ \ state_bak = state; \ mblength = mbrlen ((_str), (_strsize), &state); \ \ if (mblength == (size_t)-2 || mblength == (size_t)-1) \ { \ state = state_bak; \ mblength = 1; \ } \ else \ (_str) += (mblength < 1) ? 0 : (mblength - 1); \ } \ } \ while (0) #else # define ADVANCE_CHAR_P(_str, _strsize) #endif /* !HANDLE_MULTIBYTE */ /* Copy a single character from the string _SRC to the string _DST. _SRCEND is a pointer to the end of _SRC. */ #if defined (HANDLE_MULTIBYTE) # define COPY_CHAR_P(_dst, _src, _srcend) \ do \ { \ if (MB_CUR_MAX > 1) \ { \ mbstate_t state_bak; \ size_t mblength; \ int _k; \ \ state_bak = state; \ mblength = mbrlen ((_src), (_srcend) - (_src), &state); \ if (mblength == (size_t)-2 || mblength == (size_t)-1) \ { \ state = state_bak; \ mblength = 1; \ } \ else \ mblength = (mblength < 1) ? 1 : mblength; \ \ for (_k = 0; _k < mblength; _k++) \ *(_dst)++ = *(_src)++; \ } \ else \ *(_dst)++ = *(_src)++; \ } \ while (0) #else # define COPY_CHAR_P(_dst, _src, _srcend) *(_dst)++ = *(_src)++ #endif /* !HANDLE_MULTIBYTE */ /* Copy a single character from the string _SRC at index _SI to the string _DST at index _DI. _SRCEND is a pointer to the end of _SRC. */ #if defined (HANDLE_MULTIBYTE) # define COPY_CHAR_I(_dst, _di, _src, _srcend, _si) \ do \ { \ if (MB_CUR_MAX > 1) \ { \ mbstate_t state_bak; \ size_t mblength; \ int _k; \ \ state_bak = state; \ mblength = mbrlen ((_src) + (_si), (_srcend) - ((_src)+(_si)), &state); \ if (mblength == (size_t)-2 || mblength == (size_t)-1) \ { \ state = state_bak; \ mblength = 1; \ } \ else \ mblength = (mblength < 1) ? 1 : mblength; \ \ for (_k = 0; _k < mblength; _k++) \ _dst[_di++] = _src[_si++]; \ } \ else \ _dst[_di++] = _src[_si++]; \ } \ while (0) #else # define COPY_CHAR_I(_dst, _di, _src, _srcend, _si) _dst[_di++] = _src[_si++] #endif /* !HANDLE_MULTIBYTE */ /**************************************************************** * * * The following are only guaranteed to work in subst.c * * * ****************************************************************/ #if defined (HANDLE_MULTIBYTE) # define SCOPY_CHAR_I(_dst, _escchar, _sc, _src, _si, _slen) \ do \ { \ if (MB_CUR_MAX > 1) \ { \ mbstate_t state_bak; \ size_t mblength; \ int _i; \ \ state_bak = state; \ mblength = mbrlen ((_src) + (_si), (_slen) - (_si), &state); \ if (mblength == (size_t)-2 || mblength == (size_t)-1) \ { \ state = state_bak; \ mblength = 1; \ } \ else \ mblength = (mblength < 1) ? 1 : mblength; \ \ temp = xmalloc (mblength + 2); \ temp[0] = _escchar; \ for (_i = 0; _i < mblength; _i++) \ temp[_i + 1] = _src[_si++]; \ temp[mblength + 1] = '\0'; \ \ goto add_string; \ } \ else \ { \ _dst[0] = _escchar; \ _dst[1] = _sc; \ } \ } \ while (0) #else # define SCOPY_CHAR_I(_dst, _escchar, _sc, _src, _si, _slen) \ _dst[0] = _escchar; \ _dst[1] = _sc #endif /* !HANDLE_MULTIBYTE */ #if defined (HANDLE_MULTIBYTE) # define SCOPY_CHAR_M(_dst, _src, _srcend, _si) \ do \ { \ if (MB_CUR_MAX > 1) \ { \ mbstate_t state_bak; \ size_t mblength; \ \ state_bak = state; \ mblength = mbrlen ((_src) + (_si), (_srcend) - ((_src) + (_si)), &state); \ if (mblength == (size_t)-2 || mblength == (size_t)-1) \ { \ state = state_bak; \ mblength = 1; \ } \ else \ mblength = (mblength < 1) ? 1 : mblength; \ \ FASTCOPY(((_src) + (_si)), (_dst), mblength); \ \ (_dst) += mblength; \ (_si) += mblength; \ } \ else \ { \ *(_dst)++ = _src[(_si)]; \ (_si)++; \ } \ } \ while (0) #else # define SCOPY_CHAR_M(_dst, _src, _srcend, _si) \ *(_dst)++ = _src[(_si)]; \ (_si)++ #endif /* !HANDLE_MULTIBYTE */ #if HANDLE_MULTIBYTE # define SADD_MBCHAR(_dst, _src, _si, _srcsize) \ do \ { \ if (MB_CUR_MAX > 1) \ { \ int i; \ mbstate_t state_bak; \ size_t mblength; \ \ state_bak = state; \ mblength = mbrlen ((_src) + (_si), (_srcsize) - (_si), &state); \ if (mblength == (size_t)-1 || mblength == (size_t)-2) \ { \ state = state_bak; \ mblength = 1; \ } \ if (mblength < 1) \ mblength = 1; \ \ _dst = (char *)xmalloc (mblength + 1); \ for (i = 0; i < mblength; i++) \ (_dst)[i] = (_src)[(_si)++]; \ (_dst)[mblength] = '\0'; \ \ goto add_string; \ } \ } \ while (0) #else # define SADD_MBCHAR(_dst, _src, _si, _srcsize) #endif #endif /* _SH_MBUTIL_H_ */
/* xmbsrtowcs.c -- replacement function for mbsrtowcs */ /* Copyright (C) 2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #include <bashansi.h> /* <wchar.h>, <wctype.h> and <stdlib.h> are included in "shmbutil.h". If <wchar.h>, <wctype.h>, mbsrtowcs(), exist, HANDLE_MULTIBYTE is defined as 1. */ #include <shmbutil.h> #if HANDLE_MULTIBYTE /* On some locales (ex. ja_JP.sjis), mbsrtowc doesn't convert 0x5c to U<0x5c>. So, this function is made for converting 0x5c to U<0x5c>. */ static mbstate_t local_state; static int local_state_use = 0; size_t xmbsrtowcs (dest, src, len, pstate) wchar_t *dest; const char **src; size_t len; mbstate_t *pstate; { mbstate_t *ps; size_t mblength, wclength, n; ps = pstate; if (pstate == NULL) { if (!local_state_use) { memset (&local_state, '\0', sizeof(mbstate_t)); local_state_use = 1; } ps = &local_state; } n = strlen(*src) + 1; if (dest == NULL) { wchar_t *wsbuf; char *mbsbuf, *mbsbuf_top; mbstate_t psbuf; wsbuf = (wchar_t *) malloc ((n + 1) * sizeof(wchar_t)); mbsbuf_top = mbsbuf = (char *) malloc (n + 1); memcpy(mbsbuf, *src, n + 1); psbuf = *ps; wclength = mbsrtowcs (wsbuf, (const char **)&mbsbuf, n, &psbuf); free (wsbuf); free (mbsbuf_top); return wclength; } for(wclength = 0; wclength < len; wclength++, dest++) { if(mbsinit(ps)) { if (**src == '\0') { *dest = L'\0'; *src = NULL; return (wclength); } else if (**src == '\\') { *dest = L'\\'; mblength = 1; } else mblength = mbrtowc(dest, *src, n, ps); } else mblength = mbrtowc(dest, *src, n, ps); /* Cannot convert multibyte character to wide character. */ if (mblength == (size_t)-1 || mblength == (size_t)-2) return (size_t)-1; *src += mblength; n -= mblength; /* The multibyte string has been completely converted, including the terminating '\0'. */ if (*dest == L'\0') { *src = NULL; break; } } return (wclength); } #endif /* HANDLE_MULTIBYTE */
/* tilde.c -- Tilde expansion code (~/foo := $HOME/foo). */ /* Copyright (C) 1988,1989 Free Software Foundation, Inc. This file is part of GNU Readline, a library for reading lines of text with interactive input and history editing. Readline is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Readline is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Readline; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if defined (HAVE_CONFIG_H) # include <config.h> #endif #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #if defined (HAVE_STRING_H) # include <string.h> #else /* !HAVE_STRING_H */ # include <strings.h> #endif /* !HAVE_STRING_H */ #if defined (HAVE_STDLIB_H) # include <stdlib.h> #else # include "ansi_stdlib.h" #endif /* HAVE_STDLIB_H */ #include <sys/types.h> #include <pwd.h> #include "tilde.h" #if defined (TEST) || defined (STATIC_MALLOC) static void *xmalloc (), *xrealloc (); #else # include "xmalloc.h" #endif /* TEST || STATIC_MALLOC */ #if !defined (HAVE_GETPW_DECLS) extern struct passwd *getpwuid PARAMS((uid_t)); extern struct passwd *getpwnam PARAMS((const char *)); #endif /* !HAVE_GETPW_DECLS */ #if !defined (savestring) #define savestring(x) strcpy ((char *)xmalloc (1 + strlen (x)), (x)) #endif /* !savestring */ #if !defined (NULL) # if defined (__STDC__) # define NULL ((void *) 0) # else # define NULL 0x0 # endif /* !__STDC__ */ #endif /* !NULL */ /* If being compiled as part of bash, these will be satisfied from variables.o. If being compiled as part of readline, they will be satisfied from shell.o. */ extern char *sh_get_home_dir PARAMS((void)); extern char *sh_get_env_value PARAMS((const char *)); /* The default value of tilde_additional_prefixes. This is set to whitespace preceding a tilde so that simple programs which do not perform any word separation get desired behaviour. */ static const char *default_prefixes[] = { " ~", "\t~", (const char *)NULL }; /* The default value of tilde_additional_suffixes. This is set to whitespace or newline so that simple programs which do not perform any word separation get desired behaviour. */ static const char *default_suffixes[] = { " ", "\n", (const char *)NULL }; /* If non-null, this contains the address of a function that the application wants called before trying the standard tilde expansions. The function is called with the text sans tilde, and returns a malloc()'ed string which is the expansion, or a NULL pointer if the expansion fails. */ tilde_hook_func_t *tilde_expansion_preexpansion_hook = (tilde_hook_func_t *)NULL; /* If non-null, this contains the address of a function to call if the standard meaning for expanding a tilde fails. The function is called with the text (sans tilde, as in "foo"), and returns a malloc()'ed string which is the expansion, or a NULL pointer if there is no expansion. */ tilde_hook_func_t *tilde_expansion_failure_hook = (tilde_hook_func_t *)NULL; /* When non-null, this is a NULL terminated array of strings which are duplicates for a tilde prefix. Bash uses this to expand `=~' and `:~'. */ char **tilde_additional_prefixes = (char **)default_prefixes; /* When non-null, this is a NULL terminated array of strings which match the end of a username, instead of just "/". Bash sets this to `:' and `=~'. */ char **tilde_additional_suffixes = (char **)default_suffixes; static int tilde_find_prefix PARAMS((const char *, int *)); static int tilde_find_suffix PARAMS((const char *)); static char *isolate_tilde_prefix PARAMS((const char *, int *)); static char *glue_prefix_and_suffix PARAMS((char *, const char *, int)); /* Find the start of a tilde expansion in STRING, and return the index of the tilde which starts the expansion. Place the length of the text which identified this tilde starter in LEN, excluding the tilde itself. */ static int tilde_find_prefix (string, len) const char *string; int *len; { register int i, j, string_len; register char **prefixes; prefixes = tilde_additional_prefixes; string_len = strlen (string); *len = 0; if (*string == '\0' || *string == '~') return (0); if (prefixes) { for (i = 0; i < string_len; i++) { for (j = 0; prefixes[j]; j++) { if (strncmp (string + i, prefixes[j], strlen (prefixes[j])) == 0) { *len = strlen (prefixes[j]) - 1; return (i + *len); } } } } return (string_len); } /* Find the end of a tilde expansion in STRING, and return the index of the character which ends the tilde definition. */ static int tilde_find_suffix (string) const char *string; { register int i, j, string_len; register char **suffixes; suffixes = tilde_additional_suffixes; string_len = strlen (string); for (i = 0; i < string_len; i++) { #if defined (__MSDOS__) if (string[i] == '/' || string[i] == '\\' /* || !string[i] */) #else if (string[i] == '/' /* || !string[i] */) #endif break; for (j = 0; suffixes && suffixes[j]; j++) { if (strncmp (string + i, suffixes[j], strlen (suffixes[j])) == 0) return (i); } } return (i); } /* Return a new string which is the result of tilde expanding STRING. */ char * tilde_expand (string) const char *string; { char *result; int result_size, result_index; result_index = result_size = 0; if (result = strchr (string, '~')) result = (char *)xmalloc (result_size = (strlen (string) + 16)); else result = (char *)xmalloc (result_size = (strlen (string) + 1)); /* Scan through STRING expanding tildes as we come to them. */ while (1) { register int start, end; char *tilde_word, *expansion; int len; /* Make START point to the tilde which starts the expansion. */ start = tilde_find_prefix (string, &len); /* Copy the skipped text into the result. */ if ((result_index + start + 1) > result_size) result = (char *)xrealloc (result, 1 + (result_size += (start + 20))); strncpy (result + result_index, string, start); result_index += start; /* Advance STRING to the starting tilde. */ string += start; /* Make END be the index of one after the last character of the username. */ end = tilde_find_suffix (string); /* If both START and END are zero, we are all done. */ if (!start && !end) break; /* Expand the entire tilde word, and copy it into RESULT. */ tilde_word = (char *)xmalloc (1 + end); strncpy (tilde_word, string, end); tilde_word[end] = '\0'; string += end; expansion = tilde_expand_word (tilde_word); free (tilde_word); len = strlen (expansion); #ifdef __CYGWIN__ /* Fix for Cygwin to prevent ~user/xxx from expanding to //xxx when $HOME for `user' is /. On cygwin, // denotes a network drive. */ if (len > 1 || *expansion != '/' || *string != '/') #endif { if ((result_index + len + 1) > result_size) result = (char *)xrealloc (result, 1 + (result_size += (len + 20))); strcpy (result + result_index, expansion); result_index += len; } free (expansion); } result[result_index] = '\0'; return (result); } /* Take FNAME and return the tilde prefix we want expanded. If LENP is non-null, the index of the end of the prefix into FNAME is returned in the location it points to. */ static char * isolate_tilde_prefix (fname, lenp) const char *fname; int *lenp; { char *ret; int i; ret = (char *)xmalloc (strlen (fname)); #if defined (__MSDOS__) for (i = 1; fname[i] && fname[i] != '/' && fname[i] != '\\'; i++) #else for (i = 1; fname[i] && fname[i] != '/'; i++) #endif ret[i - 1] = fname[i]; ret[i - 1] = '\0'; if (lenp) *lenp = i; return ret; } /* Return a string that is PREFIX concatenated with SUFFIX starting at SUFFIND. */ static char * glue_prefix_and_suffix (prefix, suffix, suffind) char *prefix; const char *suffix; int suffind; { char *ret; int plen, slen; plen = (prefix && *prefix) ? strlen (prefix) : 0; slen = strlen (suffix + suffind); ret = (char *)xmalloc (plen + slen + 1); if (plen) strcpy (ret, prefix); strcpy (ret + plen, suffix + suffind); return ret; } /* Do the work of tilde expansion on FILENAME. FILENAME starts with a tilde. If there is no expansion, call tilde_expansion_failure_hook. This always returns a newly-allocated string, never static storage. */ char * tilde_expand_word (filename) const char *filename; { char *dirname, *expansion, *username; int user_len; struct passwd *user_entry; if (filename == 0) return ((char *)NULL); if (*filename != '~') return (savestring (filename)); /* A leading `~/' or a bare `~' is *always* translated to the value of $HOME or the home directory of the current user, regardless of any preexpansion hook. */ if (filename[1] == '\0' || filename[1] == '/') { /* Prefix $HOME to the rest of the string. */ expansion = sh_get_env_value ("HOME"); /* If there is no HOME variable, look up the directory in the password database. */ if (expansion == 0) expansion = sh_get_home_dir (); return (glue_prefix_and_suffix (expansion, filename, 1)); } username = isolate_tilde_prefix (filename, &user_len); if (tilde_expansion_preexpansion_hook) { expansion = (*tilde_expansion_preexpansion_hook) (username); if (expansion) { dirname = glue_prefix_and_suffix (expansion, filename, user_len); free (username); free (expansion); return (dirname); } } /* No preexpansion hook, or the preexpansion hook failed. Look in the password database. */ dirname = (char *)NULL; user_entry = getpwnam (username); if (user_entry == 0) { /* If the calling program has a special syntax for expanding tildes, and we couldn't find a standard expansion, then let them try. */ if (tilde_expansion_failure_hook) { expansion = (*tilde_expansion_failure_hook) (username); if (expansion) { dirname = glue_prefix_and_suffix (expansion, filename, user_len); free (expansion); } } free (username); /* If we don't have a failure hook, or if the failure hook did not expand the tilde, return a copy of what we were passed. */ if (dirname == 0) dirname = savestring (filename); } else { free (username); dirname = glue_prefix_and_suffix (user_entry->pw_dir, filename, user_len); } endpwent (); return (dirname); } #if defined (TEST) #undef NULL #include <stdio.h> main (argc, argv) int argc; char **argv; { char *result, line[512]; int done = 0; while (!done) { printf ("~expand: "); fflush (stdout); if (!gets (line)) strcpy (line, "done"); if ((strcmp (line, "done") == 0) || (strcmp (line, "quit") == 0) || (strcmp (line, "exit") == 0)) { done = 1; break; } result = tilde_expand (line); printf (" --> %s\n", result); free (result); } exit (0); } static void memory_error_and_abort (); static void * xmalloc (bytes) size_t bytes; { void *temp = (char *)malloc (bytes); if (!temp) memory_error_and_abort (); return (temp); } static void * xrealloc (pointer, bytes) void *pointer; int bytes; { void *temp; if (!pointer) temp = malloc (bytes); else temp = realloc (pointer, bytes); if (!temp) memory_error_and_abort (); return (temp); } static void memory_error_and_abort () { fprintf (stderr, "readline: out of virtual memory\n"); abort (); } /* * Local variables: * compile-command: "gcc -g -DTEST -o tilde tilde.c" * end: */ #endif /* TEST */
/* eval.c -- reading and evaluating commands. */ /* Copyright (C) 1996 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "bashansi.h" #include <stdio.h> #include "shell.h" #include "flags.h" #include "trap.h" #include "builtins/common.h" #include "input.h" #include "execute_cmd.h" #if defined (HISTORY) # include "bashhist.h" #endif extern int EOF_reached; extern int indirection_level; extern int posixly_correct; extern int subshell_environment, running_under_emacs; extern int last_command_exit_value, stdin_redir; extern int need_here_doc; extern int current_command_number, current_command_line_count, line_number; extern int expand_aliases; static void send_pwd_to_eterm __P((void)); static sighandler alrm_catcher __P((int)); /* Read and execute commands until EOF is reached. This assumes that the input source has already been initialized. */ int reader_loop () { int our_indirection_level; COMMAND *current_command = (COMMAND *)NULL; USE_VAR(current_command); our_indirection_level = ++indirection_level; while (EOF_Reached == 0) { int code; code = setjmp (top_level); #if defined (PROCESS_SUBSTITUTION) unlink_fifo_list (); #endif /* PROCESS_SUBSTITUTION */ if (interactive_shell && signal_is_ignored (SIGINT) == 0) set_signal_handler (SIGINT, sigint_sighandler); if (code != NOT_JUMPED) { indirection_level = our_indirection_level; switch (code) { /* Some kind of throw to top_level has occured. */ case FORCE_EOF: case EXITPROG: current_command = (COMMAND *)NULL; if (exit_immediately_on_error) variable_context = 0; /* not in a function */ EOF_Reached = EOF; goto exec_done; case DISCARD: last_command_exit_value = 1; if (subshell_environment) { current_command = (COMMAND *)NULL; EOF_Reached = EOF; goto exec_done; } /* Obstack free command elements, etc. */ if (current_command) { dispose_command (current_command); current_command = (COMMAND *)NULL; } break; default: command_error ("reader_loop", CMDERR_BADJUMP, code, 0); } } executing = 0; if (temporary_env) dispose_used_env_vars (); #if (defined (ultrix) && defined (mips)) || defined (C_ALLOCA) /* Attempt to reclaim memory allocated with alloca (). */ (void) alloca (0); #endif if (read_command () == 0) { if (interactive_shell == 0 && read_but_dont_execute) { last_command_exit_value = EXECUTION_SUCCESS; dispose_command (global_command); global_command = (COMMAND *)NULL; } else if (current_command = global_command) { global_command = (COMMAND *)NULL; current_command_number++; executing = 1; stdin_redir = 0; execute_command (current_command); exec_done: if (current_command) { dispose_command (current_command); current_command = (COMMAND *)NULL; } QUIT; } } else { /* Parse error, maybe discard rest of stream if not interactive. */ if (interactive == 0) EOF_Reached = EOF; } if (just_one_command) EOF_Reached = EOF; } indirection_level--; return (last_command_exit_value); } static sighandler alrm_catcher(i) int i; { printf ("\007timed out waiting for input: auto-logout\n"); jump_to_top_level (EXITPROG); SIGRETURN (0); } /* Send an escape sequence to emacs term mode to tell it the current working directory. */ static void send_pwd_to_eterm () { char *pwd; pwd = get_string_value ("PWD"); if (pwd == 0) pwd = get_working_directory ("eterm"); fprintf (stderr, "\032/%s\n", pwd); } /* Call the YACC-generated parser and return the status of the parse. Input is read from the current input stream (bash_input). yyparse leaves the parsed command in the global variable GLOBAL_COMMAND. This is where PROMPT_COMMAND is executed. */ int parse_command () { int r; char *command_to_execute; need_here_doc = 0; run_pending_traps (); /* Allow the execution of a random command just before the printing of each primary prompt. If the shell variable PROMPT_COMMAND is set then the value of it is the command to execute. */ if (interactive && bash_input.type != st_string) { command_to_execute = get_string_value ("PROMPT_COMMAND"); if (command_to_execute) execute_prompt_command (command_to_execute); if (running_under_emacs == 2) send_pwd_to_eterm (); /* Yuck */ } current_command_line_count = 0; r = yyparse (); if (need_here_doc) gather_here_documents (); return (r); } /* Read and parse a command, returning the status of the parse. The command is left in the globval variable GLOBAL_COMMAND for use by reader_loop. This is where the shell timeout code is executed. */ int read_command () { SHELL_VAR *tmout_var; int tmout_len, result; SigHandler *old_alrm; set_current_prompt_level (1); global_command = (COMMAND *)NULL; /* Only do timeouts if interactive. */ tmout_var = (SHELL_VAR *)NULL; tmout_len = 0; old_alrm = (SigHandler *)NULL; if (interactive) { tmout_var = find_variable ("TMOUT"); if (tmout_var && var_isset (tmout_var)) { tmout_len = atoi (value_cell (tmout_var)); if (tmout_len > 0) { old_alrm = set_signal_handler (SIGALRM, alrm_catcher); alarm (tmout_len); } } } QUIT; current_command_line_count = 0; result = parse_command (); if (interactive && tmout_var && (tmout_len > 0)) { alarm(0); set_signal_handler (SIGALRM, old_alrm); } return (result); }
/* Yacc grammar for bash. */ /* Copyright (C) 1989-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file LICENSE. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ %{ #include "config.h" #include "bashtypes.h" #include "bashansi.h" #include "filecntl.h" #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #if defined (HAVE_LOCALE_H) # include <locale.h> #endif #include <stdio.h> #include "chartypes.h" #include <signal.h> #include "memalloc.h" #define NEED_STRFTIME_DECL /* used in externs.h */ #include "shell.h" #include "trap.h" #include "flags.h" #include "parser.h" #include "mailcheck.h" #include "test.h" #include "builtins/common.h" #include "builtins/builtext.h" #include "shmbutil.h" #if defined (READLINE) # include "bashline.h" # include <readline/readline.h> #endif /* READLINE */ #if defined (HISTORY) # include "bashhist.h" # include <readline/history.h> #endif /* HISTORY */ #if defined (JOB_CONTROL) # include "jobs.h" #endif /* JOB_CONTROL */ #if defined (ALIAS) # include "alias.h" #endif /* ALIAS */ #if defined (PROMPT_STRING_DECODE) # ifndef _MINIX # include <sys/param.h> # endif # include <time.h> # if defined (TM_IN_SYS_TIME) # include <sys/types.h> # include <sys/time.h> # endif /* TM_IN_SYS_TIME */ # include "maxpath.h" #endif /* PROMPT_STRING_DECODE */ #define RE_READ_TOKEN -99 #define NO_EXPANSION -100 #ifdef DEBUG # define YYDEBUG 1 #else # define YYDEBUG 0 #endif #if defined (HANDLE_MULTIBYTE) # define last_shell_getc_is_singlebyte \ ((shell_input_line_index > 1) \ ? shell_input_line_property[shell_input_line_index - 1] \ : 1) # define MBTEST(x) ((x) && last_shell_getc_is_singlebyte) #else # define last_shell_getc_is_singlebyte 1 # define MBTEST(x) ((x)) #endif #if defined (EXTENDED_GLOB) extern int extended_glob; #endif extern int eof_encountered; extern int no_line_editing, running_under_emacs; extern int current_command_number; extern int sourcelevel; extern int posixly_correct; extern int last_command_exit_value; extern int interrupt_immediately; extern char *shell_name, *current_host_name; extern char *dist_version; extern int patch_level; extern int dump_translatable_strings, dump_po_strings; extern sh_builtin_func_t *last_shell_builtin, *this_shell_builtin; #if defined (BUFFERED_INPUT) extern int bash_input_fd_changed; #endif extern int errno; /* **************************************************************** */ /* */ /* "Forward" declarations */ /* */ /* **************************************************************** */ #ifdef DEBUG static void debug_parser __P((int)); #endif static int yy_getc __P((void)); static int yy_ungetc __P((int)); #if defined (READLINE) static int yy_readline_get __P((void)); static int yy_readline_unget __P((int)); #endif static int yy_string_get __P((void)); static int yy_string_unget __P((int)); static int yy_stream_get __P((void)); static int yy_stream_unget __P((int)); static int shell_getc __P((int)); static void shell_ungetc __P((int)); static void discard_until __P((int)); #if defined (ALIAS) || defined (DPAREN_ARITHMETIC) static void push_string __P((char *, int, alias_t *)); static void pop_string __P((void)); static void free_string_list __P((void)); #endif static char *read_a_line __P((int)); static int reserved_word_acceptable __P((int)); static int yylex __P((void)); static int alias_expand_token __P((char *)); static int time_command_acceptable __P((void)); static int special_case_tokens __P((char *)); static int read_token __P((int)); static char *parse_matched_pair __P((int, int, int, int *, int)); #if defined (ARRAY_VARS) static char *parse_compound_assignment __P((int *)); #endif #if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND) static int parse_dparen __P((int)); static int parse_arith_cmd __P((char **)); #endif #if defined (COND_COMMAND) static void cond_error __P((void)); static COND_COM *cond_expr __P((void)); static COND_COM *cond_or __P((void)); static COND_COM *cond_and __P((void)); static COND_COM *cond_term __P((void)); static int cond_skip_newlines __P((void)); static COMMAND *parse_cond_command __P((void)); #endif #if defined (ARRAY_VARS) static int token_is_assignment __P((char *, int)); static int token_is_ident __P((char *, int)); #endif static int read_token_word __P((int)); static void discard_parser_constructs __P((int)); static char *error_token_from_token __P((int)); static char *error_token_from_text __P((void)); static void print_offending_line __P((void)); static void report_syntax_error __P((char *)); static void handle_eof_input_unit __P((void)); static void prompt_again __P((void)); #if 0 static void reset_readline_prompt __P((void)); #endif static void print_prompt __P((void)); #if defined (HISTORY) char *history_delimiting_chars __P((void)); #endif #if defined (HANDLE_MULTIBYTE) static void set_line_mbstate __P((void)); static char *shell_input_line_property = NULL; #else # define set_line_mbstate() #endif extern int yyerror __P((const char *)); #ifdef DEBUG extern int yydebug; #endif /* Default prompt strings */ char *primary_prompt = PPROMPT; char *secondary_prompt = SPROMPT; /* PROMPT_STRING_POINTER points to one of these, never to an actual string. */ char *ps1_prompt, *ps2_prompt; /* Handle on the current prompt string. Indirectly points through ps1_ or ps2_prompt. */ char **prompt_string_pointer = (char **)NULL; char *current_prompt_string; /* Non-zero means we expand aliases in commands. */ int expand_aliases = 0; /* If non-zero, the decoded prompt string undergoes parameter and variable substitution, command substitution, arithmetic substitution, string expansion, process substitution, and quote removal in decode_prompt_string. */ int promptvars = 1; /* The decoded prompt string. Used if READLINE is not defined or if editing is turned off. Analogous to current_readline_prompt. */ static char *current_decoded_prompt; /* The number of lines read from input while creating the current command. */ int current_command_line_count; /* Variables to manage the task of reading here documents, because we need to defer the reading until after a complete command has been collected. */ static REDIRECT *redir_stack[10]; int need_here_doc; /* Where shell input comes from. History expansion is performed on each line when the shell is interactive. */ static char *shell_input_line = (char *)NULL; static int shell_input_line_index; static int shell_input_line_size; /* Amount allocated for shell_input_line. */ static int shell_input_line_len; /* strlen (shell_input_line) */ /* Either zero or EOF. */ static int shell_input_line_terminator; /* The line number in a script on which a function definition starts. */ static int function_dstart; /* The line number in a script on which a function body starts. */ static int function_bstart; /* The line number in a script at which an arithmetic for command starts. */ static int arith_for_lineno; static REDIRECTEE redir; %} %union { WORD_DESC *word; /* the word that we read. */ int number; /* the number that we read. */ WORD_LIST *word_list; COMMAND *command; REDIRECT *redirect; ELEMENT element; PATTERN_LIST *pattern; } /* Reserved words. Members of the first group are only recognized in the case that they are preceded by a list_terminator. Members of the second group are for [[...]] commands. Members of the third group are recognized only under special circumstances. */ %token IF THEN ELSE ELIF FI CASE ESAC FOR SELECT WHILE UNTIL DO DONE FUNCTION %token COND_START COND_END COND_ERROR %token IN BANG TIME TIMEOPT /* More general tokens. yylex () knows how to make these. */ %token <word> WORD ASSIGNMENT_WORD %token <number> NUMBER %token <word_list> ARITH_CMD ARITH_FOR_EXPRS %token <command> COND_CMD %token AND_AND OR_OR GREATER_GREATER LESS_LESS LESS_AND LESS_LESS_LESS %token GREATER_AND SEMI_SEMI LESS_LESS_MINUS AND_GREATER LESS_GREATER %token GREATER_BAR /* The types that the various syntactical units return. */ %type <command> inputunit command pipeline pipeline_command %type <command> list list0 list1 compound_list simple_list simple_list1 %type <command> simple_command shell_command %type <command> for_command select_command case_command group_command %type <command> arith_command %type <command> cond_command %type <command> arith_for_command %type <command> function_def function_body if_command elif_clause subshell %type <redirect> redirection redirection_list %type <element> simple_command_element %type <word_list> word_list pattern %type <pattern> pattern_list case_clause_sequence case_clause %type <number> timespec %start inputunit %left '&' ';' '\n' yacc_EOF %left AND_AND OR_OR %right '|' %% inputunit: simple_list simple_list_terminator { /* Case of regular command. Discard the error safety net,and return the command just parsed. */ global_command = $1; eof_encountered = 0; /* discard_parser_constructs (0); */ YYACCEPT; } | '\n' { /* Case of regular command, but not a very interesting one. Return a NULL command. */ global_command = (COMMAND *)NULL; YYACCEPT; } | error '\n' { /* Error during parsing. Return NULL command. */ global_command = (COMMAND *)NULL; eof_encountered = 0; /* discard_parser_constructs (1); */ if (interactive) { YYACCEPT; } else { YYABORT; } } | yacc_EOF { /* Case of EOF seen by itself. Do ignoreeof or not. */ global_command = (COMMAND *)NULL; handle_eof_input_unit (); YYACCEPT; } ; word_list: WORD { $$ = make_word_list ($1, (WORD_LIST *)NULL); } | word_list WORD { $$ = make_word_list ($2, $1); } ; redirection: '>' WORD { redir.filename = $2; $$ = make_redirection (1, r_output_direction, redir); } | '<' WORD { redir.filename = $2; $$ = make_redirection (0, r_input_direction, redir); } | NUMBER '>' WORD { redir.filename = $3; $$ = make_redirection ($1, r_output_direction, redir); } | NUMBER '<' WORD { redir.filename = $3; $$ = make_redirection ($1, r_input_direction, redir); } | GREATER_GREATER WORD { redir.filename = $2; $$ = make_redirection (1, r_appending_to, redir); } | NUMBER GREATER_GREATER WORD { redir.filename = $3; $$ = make_redirection ($1, r_appending_to, redir); } | LESS_LESS WORD { redir.filename = $2; $$ = make_redirection (0, r_reading_until, redir); redir_stack[need_here_doc++] = $$; } | NUMBER LESS_LESS WORD { redir.filename = $3; $$ = make_redirection ($1, r_reading_until, redir); redir_stack[need_here_doc++] = $$; } | LESS_LESS_LESS WORD { redir.filename = $2; $$ = make_redirection (0, r_reading_string, redir); } | NUMBER LESS_LESS_LESS WORD { redir.filename = $3; $$ = make_redirection ($1, r_reading_string, redir); } | LESS_AND NUMBER { redir.dest = $2; $$ = make_redirection (0, r_duplicating_input, redir); } | NUMBER LESS_AND NUMBER { redir.dest = $3; $$ = make_redirection ($1, r_duplicating_input, redir); } | GREATER_AND NUMBER { redir.dest = $2; $$ = make_redirection (1, r_duplicating_output, redir); } | NUMBER GREATER_AND NUMBER { redir.dest = $3; $$ = make_redirection ($1, r_duplicating_output, redir); } | LESS_AND WORD { redir.filename = $2; $$ = make_redirection (0, r_duplicating_input_word, redir); } | NUMBER LESS_AND WORD { redir.filename = $3; $$ = make_redirection ($1, r_duplicating_input_word, redir); } | GREATER_AND WORD { redir.filename = $2; $$ = make_redirection (1, r_duplicating_output_word, redir); } | NUMBER GREATER_AND WORD { redir.filename = $3; $$ = make_redirection ($1, r_duplicating_output_word, redir); } | LESS_LESS_MINUS WORD { redir.filename = $2; $$ = make_redirection (0, r_deblank_reading_until, redir); redir_stack[need_here_doc++] = $$; } | NUMBER LESS_LESS_MINUS WORD { redir.filename = $3; $$ = make_redirection ($1, r_deblank_reading_until, redir); redir_stack[need_here_doc++] = $$; } | GREATER_AND '-' { redir.dest = 0; $$ = make_redirection (1, r_close_this, redir); } | NUMBER GREATER_AND '-' { redir.dest = 0; $$ = make_redirection ($1, r_close_this, redir); } | LESS_AND '-' { redir.dest = 0; $$ = make_redirection (0, r_close_this, redir); } | NUMBER LESS_AND '-' { redir.dest = 0; $$ = make_redirection ($1, r_close_this, redir); } | AND_GREATER WORD { redir.filename = $2; $$ = make_redirection (1, r_err_and_out, redir); } | NUMBER LESS_GREATER WORD { redir.filename = $3; $$ = make_redirection ($1, r_input_output, redir); } | LESS_GREATER WORD { redir.filename = $2; $$ = make_redirection (0, r_input_output, redir); } | GREATER_BAR WORD { redir.filename = $2; $$ = make_redirection (1, r_output_force, redir); } | NUMBER GREATER_BAR WORD { redir.filename = $3; $$ = make_redirection ($1, r_output_force, redir); } ; simple_command_element: WORD { $$.word = $1; $$.redirect = 0; } | ASSIGNMENT_WORD { $$.word = $1; $$.redirect = 0; } | redirection { $$.redirect = $1; $$.word = 0; } ; redirection_list: redirection { $$ = $1; } | redirection_list redirection { register REDIRECT *t; for (t = $1; t->next; t = t->next) ; t->next = $2; $$ = $1; } ; simple_command: simple_command_element { $$ = make_simple_command ($1, (COMMAND *)NULL); } | simple_command simple_command_element { $$ = make_simple_command ($2, $1); } ; command: simple_command { $$ = clean_simple_command ($1); } | shell_command { $$ = $1; } | shell_command redirection_list { COMMAND *tc; tc = $1; if (tc->redirects) { register REDIRECT *t; for (t = tc->redirects; t->next; t = t->next) ; t->next = $2; } else tc->redirects = $2; $$ = $1; } | function_def { $$ = $1; } ; shell_command: for_command { $$ = $1; } | case_command { $$ = $1; } | WHILE compound_list DO compound_list DONE { $$ = make_while_command ($2, $4); } | UNTIL compound_list DO compound_list DONE { $$ = make_until_command ($2, $4); } | select_command { $$ = $1; } | if_command { $$ = $1; } | subshell { $$ = $1; } | group_command { $$ = $1; } | arith_command { $$ = $1; } | cond_command { $$ = $1; } | arith_for_command { $$ = $1; } ; for_command: FOR WORD newline_list DO compound_list DONE { $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5); } | FOR WORD newline_list '{' compound_list '}' { $$ = make_for_command ($2, add_string_to_list ("$@", (WORD_LIST *)NULL), $5); } | FOR WORD ';' newline_list DO compound_list DONE { $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6); } | FOR WORD ';' newline_list '{' compound_list '}' { $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6); } | FOR WORD newline_list IN word_list list_terminator newline_list DO compound_list DONE { $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9); } | FOR WORD newline_list IN word_list list_terminator newline_list '{' compound_list '}' { $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9); } | FOR WORD newline_list IN list_terminator newline_list DO compound_list DONE { $$ = make_for_command ($2, (WORD_LIST *)NULL, $8); } | FOR WORD newline_list IN list_terminator newline_list '{' compound_list '}' { $$ = make_for_command ($2, (WORD_LIST *)NULL, $8); } ; arith_for_command: FOR ARITH_FOR_EXPRS list_terminator newline_list DO compound_list DONE { $$ = make_arith_for_command ($2, $6, arith_for_lineno); } | FOR ARITH_FOR_EXPRS list_terminator newline_list '{' compound_list '}' { $$ = make_arith_for_command ($2, $6, arith_for_lineno); } | FOR ARITH_FOR_EXPRS DO compound_list DONE { $$ = make_arith_for_command ($2, $4, arith_for_lineno); } | FOR ARITH_FOR_EXPRS '{' compound_list '}' { $$ = make_arith_for_command ($2, $4, arith_for_lineno); } ; select_command: SELECT WORD newline_list DO list DONE { $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5); } | SELECT WORD newline_list '{' list '}' { $$ = make_select_command ($2, add_string_to_list ("$@", (WORD_LIST *)NULL), $5); } | SELECT WORD ';' newline_list DO list DONE { $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6); } | SELECT WORD ';' newline_list '{' list '}' { $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6); } | SELECT WORD newline_list IN word_list list_terminator newline_list DO list DONE { $$ = make_select_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9); } | SELECT WORD newline_list IN word_list list_terminator newline_list '{' list '}' { $$ = make_select_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9); } ; case_command: CASE WORD newline_list IN newline_list ESAC { $$ = make_case_command ($2, (PATTERN_LIST *)NULL); } | CASE WORD newline_list IN case_clause_sequence newline_list ESAC { $$ = make_case_command ($2, $5); } | CASE WORD newline_list IN case_clause ESAC { $$ = make_case_command ($2, $5); } ; function_def: WORD '(' ')' newline_list function_body { $$ = make_function_def ($1, $5, function_dstart, function_bstart); } | FUNCTION WORD '(' ')' newline_list function_body { $$ = make_function_def ($2, $6, function_dstart, function_bstart); } | FUNCTION WORD newline_list function_body { $$ = make_function_def ($2, $4, function_dstart, function_bstart); } ; function_body: shell_command { $$ = $1; } | shell_command redirection_list { COMMAND *tc; tc = $1; /* According to Posix.2 3.9.5, redirections specified after the body of a function should be attached to the function and performed when the function is executed, not as part of the function definition command. */ /* XXX - I don't think it matters, but we might want to change this in the future to avoid problems differentiating between a function definition with a redirection and a function definition containing a single command with a redirection. The two are semantically equivalent, though -- the only difference is in how the command printing code displays the redirections. */ if (tc->redirects) { register REDIRECT *t; for (t = tc->redirects; t->next; t = t->next) ; t->next = $2; } else tc->redirects = $2; $$ = $1; } ; subshell: '(' compound_list ')' { $$ = make_subshell_command ($2); $$->flags |= CMD_WANT_SUBSHELL; } ; if_command: IF compound_list THEN compound_list FI { $$ = make_if_command ($2, $4, (COMMAND *)NULL); } | IF compound_list THEN compound_list ELSE compound_list FI { $$ = make_if_command ($2, $4, $6); } | IF compound_list THEN compound_list elif_clause FI { $$ = make_if_command ($2, $4, $5); } ; group_command: '{' compound_list '}' { $$ = make_group_command ($2); } ; arith_command: ARITH_CMD { $$ = make_arith_command ($1); } ; cond_command: COND_START COND_CMD COND_END { $$ = $2; } ; elif_clause: ELIF compound_list THEN compound_list { $$ = make_if_command ($2, $4, (COMMAND *)NULL); } | ELIF compound_list THEN compound_list ELSE compound_list { $$ = make_if_command ($2, $4, $6); } | ELIF compound_list THEN compound_list elif_clause { $$ = make_if_command ($2, $4, $5); } ; case_clause: pattern_list | case_clause_sequence pattern_list { $2->next = $1; $$ = $2; } ; pattern_list: newline_list pattern ')' compound_list { $$ = make_pattern_list ($2, $4); } | newline_list pattern ')' newline_list { $$ = make_pattern_list ($2, (COMMAND *)NULL); } | newline_list '(' pattern ')' compound_list { $$ = make_pattern_list ($3, $5); } | newline_list '(' pattern ')' newline_list { $$ = make_pattern_list ($3, (COMMAND *)NULL); } ; case_clause_sequence: pattern_list SEMI_SEMI | case_clause_sequence pattern_list SEMI_SEMI { $2->next = $1; $$ = $2; } ; pattern: WORD { $$ = make_word_list ($1, (WORD_LIST *)NULL); } | pattern '|' WORD { $$ = make_word_list ($3, $1); } ; /* A list allows leading or trailing newlines and newlines as operators (equivalent to semicolons). It must end with a newline or semicolon. Lists are used within commands such as if, for, while. */ list: newline_list list0 { $$ = $2; if (need_here_doc) gather_here_documents (); } ; compound_list: list | newline_list list1 { $$ = $2; } ; list0: list1 '\n' newline_list | list1 '&' newline_list { if ($1->type == cm_connection) $$ = connect_async_list ($1, (COMMAND *)NULL, '&'); else $$ = command_connect ($1, (COMMAND *)NULL, '&'); } | list1 ';' newline_list ; list1: list1 AND_AND newline_list list1 { $$ = command_connect ($1, $4, AND_AND); } | list1 OR_OR newline_list list1 { $$ = command_connect ($1, $4, OR_OR); } | list1 '&' newline_list list1 { if ($1->type == cm_connection) $$ = connect_async_list ($1, $4, '&'); else $$ = command_connect ($1, $4, '&'); } | list1 ';' newline_list list1 { $$ = command_connect ($1, $4, ';'); } | list1 '\n' newline_list list1 { $$ = command_connect ($1, $4, ';'); } | pipeline_command { $$ = $1; } ; simple_list_terminator: '\n' | yacc_EOF ; list_terminator:'\n' | ';' | yacc_EOF ; newline_list: | newline_list '\n' ; /* A simple_list is a list that contains no significant newlines and no leading or trailing newlines. Newlines are allowed only following operators, where they are not significant. This is what an inputunit consists of. */ simple_list: simple_list1 { $$ = $1; if (need_here_doc) gather_here_documents (); } | simple_list1 '&' { if ($1->type == cm_connection) $$ = connect_async_list ($1, (COMMAND *)NULL, '&'); else $$ = command_connect ($1, (COMMAND *)NULL, '&'); if (need_here_doc) gather_here_documents (); } | simple_list1 ';' { $$ = $1; if (need_here_doc) gather_here_documents (); } ; simple_list1: simple_list1 AND_AND newline_list simple_list1 { $$ = command_connect ($1, $4, AND_AND); } | simple_list1 OR_OR newline_list simple_list1 { $$ = command_connect ($1, $4, OR_OR); } | simple_list1 '&' simple_list1 { if ($1->type == cm_connection) $$ = connect_async_list ($1, $3, '&'); else $$ = command_connect ($1, $3, '&'); } | simple_list1 ';' simple_list1 { $$ = command_connect ($1, $3, ';'); } | pipeline_command { $$ = $1; } ; pipeline_command: pipeline { $$ = $1; } | BANG pipeline { $2->flags |= CMD_INVERT_RETURN; $$ = $2; } | timespec pipeline { $2->flags |= $1; $$ = $2; } | timespec BANG pipeline { $3->flags |= $1|CMD_INVERT_RETURN; $$ = $3; } | BANG timespec pipeline { $3->flags |= $2|CMD_INVERT_RETURN; $$ = $3; } ; pipeline: pipeline '|' newline_list pipeline { $$ = command_connect ($1, $4, '|'); } | command { $$ = $1; } ; timespec: TIME { $$ = CMD_TIME_PIPELINE; } | TIME TIMEOPT { $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; } ; %% /* Possible states for the parser that require it to do special things. */ #define PST_CASEPAT 0x001 /* in a case pattern list */ #define PST_ALEXPNEXT 0x002 /* expand next word for aliases */ #define PST_ALLOWOPNBRC 0x004 /* allow open brace for function def */ #define PST_NEEDCLOSBRC 0x008 /* need close brace */ #define PST_DBLPAREN 0x010 /* double-paren parsing */ #define PST_SUBSHELL 0x020 /* ( ... ) subshell */ #define PST_CMDSUBST 0x040 /* $( ... ) command substitution */ #define PST_CASESTMT 0x080 /* parsing a case statement */ #define PST_CONDCMD 0x100 /* parsing a [[...]] command */ #define PST_CONDEXPR 0x200 /* parsing the guts of [[...]] */ #define PST_ARITHFOR 0x400 /* parsing an arithmetic for command */ /* Initial size to allocate for tokens, and the amount to grow them by. */ #define TOKEN_DEFAULT_INITIAL_SIZE 496 #define TOKEN_DEFAULT_GROW_SIZE 512 /* The token currently being read. */ static int current_token; /* The last read token, or NULL. read_token () uses this for context checking. */ static int last_read_token; /* The token read prior to last_read_token. */ static int token_before_that; /* The token read prior to token_before_that. */ static int two_tokens_ago; /* If non-zero, it is the token that we want read_token to return regardless of what text is (or isn't) present to be read. This is reset by read_token. If token_to_read == WORD or ASSIGNMENT_WORD, yylval.word should be set to word_desc_to_read. */ static int token_to_read; static WORD_DESC *word_desc_to_read; /* The current parser state. */ static int parser_state; /* Global var is non-zero when end of file has been reached. */ int EOF_Reached = 0; #ifdef DEBUG static void debug_parser (i) int i; { #if YYDEBUG != 0 yydebug = i; #endif } #endif /* yy_getc () returns the next available character from input or EOF. yy_ungetc (c) makes `c' the next character to read. init_yy_io (get, unget, type, location) makes the function GET the installed function for getting the next character, makes UNGET the installed function for un-getting a character, sets the type of stream (either string or file) from TYPE, and makes LOCATION point to where the input is coming from. */ /* Unconditionally returns end-of-file. */ int return_EOF () { return (EOF); } /* Variable containing the current get and unget functions. See ./input.h for a clearer description. */ BASH_INPUT bash_input; /* Set all of the fields in BASH_INPUT to NULL. Free bash_input.name if it is non-null, avoiding a memory leak. */ void initialize_bash_input () { bash_input.type = st_none; FREE (bash_input.name); bash_input.name = (char *)NULL; bash_input.location.file = (FILE *)NULL; bash_input.location.string = (char *)NULL; bash_input.getter = (sh_cget_func_t *)NULL; bash_input.ungetter = (sh_cunget_func_t *)NULL; } /* Set the contents of the current bash input stream from GET, UNGET, TYPE, NAME, and LOCATION. */ void init_yy_io (get, unget, type, name, location) sh_cget_func_t *get; sh_cunget_func_t *unget; enum stream_type type; const char *name; INPUT_STREAM location; { bash_input.type = type; FREE (bash_input.name); bash_input.name = name ? savestring (name) : (char *)NULL; /* XXX */ #if defined (CRAY) memcpy((char *)&bash_input.location.string, (char *)&location.string, sizeof(location)); #else bash_input.location = location; #endif bash_input.getter = get; bash_input.ungetter = unget; } char * yy_input_name () { return (bash_input.name ? bash_input.name : "stdin"); } /* Call this to get the next character of input. */ static int yy_getc () { return (*(bash_input.getter)) (); } /* Call this to unget C. That is, to make C the next character to be read. */ static int yy_ungetc (c) int c; { return (*(bash_input.ungetter)) (c); } #if defined (BUFFERED_INPUT) #ifdef INCLUDE_UNUSED int input_file_descriptor () { switch (bash_input.type) { case st_stream: return (fileno (bash_input.location.file)); case st_bstream: return (bash_input.location.buffered_fd); case st_stdin: default: return (fileno (stdin)); } } #endif #endif /* BUFFERED_INPUT */ /* **************************************************************** */ /* */ /* Let input be read from readline (). */ /* */ /* **************************************************************** */ #if defined (READLINE) char *current_readline_prompt = (char *)NULL; char *current_readline_line = (char *)NULL; int current_readline_line_index = 0; static int yy_readline_get () { SigHandler *old_sigint; int line_len; unsigned char c; if (!current_readline_line) { if (!bash_readline_initialized) initialize_readline (); #if defined (JOB_CONTROL) if (job_control) give_terminal_to (shell_pgrp, 0); #endif /* JOB_CONTROL */ old_sigint = (SigHandler *)NULL; if (signal_is_ignored (SIGINT) == 0) { old_sigint = (SigHandler *)set_signal_handler (SIGINT, sigint_sighandler); interrupt_immediately++; } current_readline_line = readline (current_readline_prompt ? current_readline_prompt : ""); if (signal_is_ignored (SIGINT) == 0 && old_sigint) { interrupt_immediately--; set_signal_handler (SIGINT, old_sigint); } #if 0 /* Reset the prompt to the decoded value of prompt_string_pointer. */ reset_readline_prompt (); #endif if (current_readline_line == 0) return (EOF); current_readline_line_index = 0; line_len = strlen (current_readline_line); current_readline_line = (char *)xrealloc (current_readline_line, 2 + line_len); current_readline_line[line_len++] = '\n'; current_readline_line[line_len] = '\0'; } if (current_readline_line[current_readline_line_index] == 0) { free (current_readline_line); current_readline_line = (char *)NULL; return (yy_readline_get ()); } else { c = current_readline_line[current_readline_line_index++]; return (c); } } static int yy_readline_unget (c) int c; { if (current_readline_line_index && current_readline_line) current_readline_line[--current_readline_line_index] = c; return (c); } void with_input_from_stdin () { INPUT_STREAM location; if (bash_input.type != st_stdin && stream_on_stack (st_stdin) == 0) { location.string = current_readline_line; init_yy_io (yy_readline_get, yy_readline_unget, st_stdin, "readline stdin", location); } } #else /* !READLINE */ void with_input_from_stdin () { with_input_from_stream (stdin, "stdin"); } #endif /* !READLINE */ /* **************************************************************** */ /* */ /* Let input come from STRING. STRING is zero terminated. */ /* */ /* **************************************************************** */ static int yy_string_get () { register char *string; register unsigned char c; string = bash_input.location.string; /* If the string doesn't exist, or is empty, EOF found. */ if (string && *string) { c = *string++; bash_input.location.string = string; return (c); } else return (EOF); } static int yy_string_unget (c) int c; { *(--bash_input.location.string) = c; return (c); } void with_input_from_string (string, name) char *string; const char *name; { INPUT_STREAM location; location.string = string; init_yy_io (yy_string_get, yy_string_unget, st_string, name, location); } /* **************************************************************** */ /* */ /* Let input come from STREAM. */ /* */ /* **************************************************************** */ /* These two functions used to test the value of the HAVE_RESTARTABLE_SYSCALLS define, and just use getc/ungetc if it was defined, but since bash installs its signal handlers without the SA_RESTART flag, some signals (like SIGCHLD, SIGWINCH, etc.) received during a read(2) will not cause the read to be restarted. We need to restart it ourselves. */ static int yy_stream_get () { int result; result = EOF; if (bash_input.location.file) result = getc_with_restart (bash_input.location.file); return (result); } static int yy_stream_unget (c) int c; { return (ungetc_with_restart (c, bash_input.location.file)); } void with_input_from_stream (stream, name) FILE *stream; const char *name; { INPUT_STREAM location; location.file = stream; init_yy_io (yy_stream_get, yy_stream_unget, st_stream, name, location); } typedef struct stream_saver { struct stream_saver *next; BASH_INPUT bash_input; int line; #if defined (BUFFERED_INPUT) BUFFERED_STREAM *bstream; #endif /* BUFFERED_INPUT */ } STREAM_SAVER; /* The globally known line number. */ int line_number = 0; #if defined (COND_COMMAND) static int cond_lineno; static int cond_token; #endif STREAM_SAVER *stream_list = (STREAM_SAVER *)NULL; void push_stream (reset_lineno) int reset_lineno; { STREAM_SAVER *saver = (STREAM_SAVER *)xmalloc (sizeof (STREAM_SAVER)); xbcopy ((char *)&bash_input, (char *)&(saver->bash_input), sizeof (BASH_INPUT)); #if defined (BUFFERED_INPUT) saver->bstream = (BUFFERED_STREAM *)NULL; /* If we have a buffered stream, clear out buffers[fd]. */ if (bash_input.type == st_bstream && bash_input.location.buffered_fd >= 0) saver->bstream = set_buffered_stream (bash_input.location.buffered_fd, (BUFFERED_STREAM *)NULL); #endif /* BUFFERED_INPUT */ saver->line = line_number; bash_input.name = (char *)NULL; saver->next = stream_list; stream_list = saver; EOF_Reached = 0; if (reset_lineno) line_number = 0; } void pop_stream () { if (!stream_list) EOF_Reached = 1; else { STREAM_SAVER *saver = stream_list; EOF_Reached = 0; stream_list = stream_list->next; init_yy_io (saver->bash_input.getter, saver->bash_input.ungetter, saver->bash_input.type, saver->bash_input.name, saver->bash_input.location); #if defined (BUFFERED_INPUT) /* If we have a buffered stream, restore buffers[fd]. */ /* If the input file descriptor was changed while this was on the save stack, update the buffered fd to the new file descriptor and re-establish the buffer <-> bash_input fd correspondence. */ if (bash_input.type == st_bstream && bash_input.location.buffered_fd >= 0) { if (bash_input_fd_changed) { bash_input_fd_changed = 0; if (default_buffered_input >= 0) { bash_input.location.buffered_fd = default_buffered_input; saver->bstream->b_fd = default_buffered_input; SET_CLOSE_ON_EXEC (default_buffered_input); } } /* XXX could free buffered stream returned as result here. */ set_buffered_stream (bash_input.location.buffered_fd, saver->bstream); } #endif /* BUFFERED_INPUT */ line_number = saver->line; FREE (saver->bash_input.name); free (saver); } } /* Return 1 if a stream of type TYPE is saved on the stack. */ int stream_on_stack (type) enum stream_type type; { register STREAM_SAVER *s; for (s = stream_list; s; s = s->next) if (s->bash_input.type == type) return 1; return 0; } /* Save the current token state and return it in a malloced array. */ int * save_token_state () { int *ret; ret = (int *)xmalloc (3 * sizeof (int)); ret[0] = last_read_token; ret[1] = token_before_that; ret[2] = two_tokens_ago; return ret; } void restore_token_state (ts) int *ts; { if (ts == 0) return; last_read_token = ts[0]; token_before_that = ts[1]; two_tokens_ago = ts[2]; } /* * This is used to inhibit alias expansion and reserved word recognition * inside case statement pattern lists. A `case statement pattern list' is: * * everything between the `in' in a `case word in' and the next ')' * or `esac' * everything between a `;;' and the next `)' or `esac' */ #if defined (ALIAS) || defined (DPAREN_ARITHMETIC) #if !defined (ALIAS) typedef void *alias_t; #endif #define END_OF_ALIAS 0 /* * Pseudo-global variables used in implementing token-wise alias expansion. */ /* * Pushing and popping strings. This works together with shell_getc to * implement alias expansion on a per-token basis. */ typedef struct string_saver { struct string_saver *next; int expand_alias; /* Value to set expand_alias to when string is popped. */ char *saved_line; #if defined (ALIAS) alias_t *expander; /* alias that caused this line to be pushed. */ #endif int saved_line_size, saved_line_index, saved_line_terminator; } STRING_SAVER; STRING_SAVER *pushed_string_list = (STRING_SAVER *)NULL; /* * Push the current shell_input_line onto a stack of such lines and make S * the current input. Used when expanding aliases. EXPAND is used to set * the value of expand_next_token when the string is popped, so that the * word after the alias in the original line is handled correctly when the * alias expands to multiple words. TOKEN is the token that was expanded * into S; it is saved and used to prevent infinite recursive expansion. */ static void push_string (s, expand, ap) char *s; int expand; alias_t *ap; { STRING_SAVER *temp = (STRING_SAVER *)xmalloc (sizeof (STRING_SAVER)); temp->expand_alias = expand; temp->saved_line = shell_input_line; temp->saved_line_size = shell_input_line_size; temp->saved_line_index = shell_input_line_index; temp->saved_line_terminator = shell_input_line_terminator; #if defined (ALIAS) temp->expander = ap; #endif temp->next = pushed_string_list; pushed_string_list = temp; #if defined (ALIAS) if (ap) ap->flags |= AL_BEINGEXPANDED; #endif shell_input_line = s; shell_input_line_size = strlen (s); shell_input_line_index = 0; shell_input_line_terminator = '\0'; parser_state &= ~PST_ALEXPNEXT; set_line_mbstate (); } /* * Make the top of the pushed_string stack be the current shell input. * Only called when there is something on the stack. Called from shell_getc * when it thinks it has consumed the string generated by an alias expansion * and needs to return to the original input line. */ static void pop_string () { STRING_SAVER *t; FREE (shell_input_line); shell_input_line = pushed_string_list->saved_line; shell_input_line_index = pushed_string_list->saved_line_index; shell_input_line_size = pushed_string_list->saved_line_size; shell_input_line_terminator = pushed_string_list->saved_line_terminator; if (pushed_string_list->expand_alias) parser_state |= PST_ALEXPNEXT; else parser_state &= ~PST_ALEXPNEXT; t = pushed_string_list; pushed_string_list = pushed_string_list->next; #if defined (ALIAS) if (t->expander) t->expander->flags &= ~AL_BEINGEXPANDED; #endif free ((char *)t); set_line_mbstate (); } static void free_string_list () { register STRING_SAVER *t, *t1; for (t = pushed_string_list; t; ) { t1 = t->next; FREE (t->saved_line); #if defined (ALIAS) if (t->expander) t->expander->flags &= ~AL_BEINGEXPANDED; #endif free ((char *)t); t = t1; } pushed_string_list = (STRING_SAVER *)NULL; } #endif /* ALIAS || DPAREN_ARITHMETIC */ /* Return a line of text, taken from wherever yylex () reads input. If there is no more input, then we return NULL. If REMOVE_QUOTED_NEWLINE is non-zero, we remove unquoted \<newline> pairs. This is used by read_secondary_line to read here documents. */ static char * read_a_line (remove_quoted_newline) int remove_quoted_newline; { static char *line_buffer = (char *)NULL; static int buffer_size = 0; int indx = 0, c, peekc, pass_next; #if defined (READLINE) if (interactive && bash_input.type != st_string && no_line_editing) #else if (interactive && bash_input.type != st_string) #endif print_prompt (); pass_next = 0; while (1) { c = yy_getc (); /* Allow immediate exit if interrupted during input. */ QUIT; /* Ignore null bytes in input. */ if (c == 0) { #if 0 internal_warning ("read_a_line: ignored null byte in input"); #endif continue; } /* If there is no more input, then we return NULL. */ if (c == EOF) { if (interactive && bash_input.type == st_stream) clearerr (stdin); if (indx == 0) return ((char *)NULL); c = '\n'; } /* `+2' in case the final character in the buffer is a newline. */ RESIZE_MALLOCED_BUFFER (line_buffer, indx, 2, buffer_size, 128); /* IF REMOVE_QUOTED_NEWLINES is non-zero, we are reading a here document with an unquoted delimiter. In this case, the line will be expanded as if it were in double quotes. We allow a backslash to escape the next character, but we need to treat the backslash specially only if a backslash quoting a backslash-newline pair appears in the line. */ if (pass_next) { line_buffer[indx++] = c; pass_next = 0; } else if (c == '\\' && remove_quoted_newline) { peekc = yy_getc (); if (peekc == '\n') continue; /* Make the unquoted \<newline> pair disappear. */ else { yy_ungetc (peekc); pass_next = 1; line_buffer[indx++] = c; /* Preserve the backslash. */ } } else line_buffer[indx++] = c; if (c == '\n') { line_buffer[indx] = '\0'; return (line_buffer); } } } /* Return a line as in read_a_line (), but insure that the prompt is the secondary prompt. This is used to read the lines of a here document. REMOVE_QUOTED_NEWLINE is non-zero if we should remove newlines quoted with backslashes while reading the line. It is non-zero unless the delimiter of the here document was quoted. */ char * read_secondary_line (remove_quoted_newline) int remove_quoted_newline; { prompt_string_pointer = &ps2_prompt; prompt_again (); return (read_a_line (remove_quoted_newline)); } /* **************************************************************** */ /* */ /* YYLEX () */ /* */ /* **************************************************************** */ /* Reserved words. These are only recognized as the first word of a command. */ STRING_INT_ALIST word_token_alist[] = { { "if", IF }, { "then", THEN }, { "else", ELSE }, { "elif", ELIF }, { "fi", FI }, { "case", CASE }, { "esac", ESAC }, { "for", FOR }, #if defined (SELECT_COMMAND) { "select", SELECT }, #endif { "while", WHILE }, { "until", UNTIL }, { "do", DO }, { "done", DONE }, { "in", IN }, { "function", FUNCTION }, #if defined (COMMAND_TIMING) { "time", TIME }, #endif { "{", '{' }, { "}", '}' }, { "!", BANG }, #if defined (COND_COMMAND) { "[[", COND_START }, { "]]", COND_END }, #endif { (char *)NULL, 0} }; /* other tokens that can be returned by read_token() */ STRING_INT_ALIST other_token_alist[] = { /* Multiple-character tokens with special values */ { "-p", TIMEOPT }, { "&&", AND_AND }, { "||", OR_OR }, { ">>", GREATER_GREATER }, { "<<", LESS_LESS }, { "<&", LESS_AND }, { ">&", GREATER_AND }, { ";;", SEMI_SEMI }, { "<<-", LESS_LESS_MINUS }, { "<<<", LESS_LESS_LESS }, { "&>", AND_GREATER }, { "<>", LESS_GREATER }, { ">|", GREATER_BAR }, { "EOF", yacc_EOF }, /* Tokens whose value is the character itself */ { ">", '>' }, { "<", '<' }, { "-", '-' }, { "{", '{' }, { "}", '}' }, { ";", ';' }, { "(", '(' }, { ")", ')' }, { "|", '|' }, { "&", '&' }, { "newline", '\n' }, { (char *)NULL, 0} }; /* others not listed here: WORD look at yylval.word ASSIGNMENT_WORD look at yylval.word NUMBER look at yylval.number ARITH_CMD look at yylval.word_list ARITH_FOR_EXPRS look at yylval.word_list COND_CMD look at yylval.command */ /* These are used by read_token_word, but appear up here so that shell_getc can use them to decide when to add otherwise blank lines to the history. */ /* The primary delimiter stack. */ struct dstack dstack = { (char *)NULL, 0, 0 }; /* A temporary delimiter stack to be used when decoding prompt strings. This is needed because command substitutions in prompt strings (e.g., PS2) can screw up the parser's quoting state. */ static struct dstack temp_dstack = { (char *)NULL, 0, 0 }; /* Macro for accessing the top delimiter on the stack. Returns the delimiter or zero if none. */ #define current_delimiter(ds) \ (ds.delimiter_depth ? ds.delimiters[ds.delimiter_depth - 1] : 0) #define push_delimiter(ds, character) \ do \ { \ if (ds.delimiter_depth + 2 > ds.delimiter_space) \ ds.delimiters = (char *)xrealloc \ (ds.delimiters, (ds.delimiter_space += 10) * sizeof (char)); \ ds.delimiters[ds.delimiter_depth] = character; \ ds.delimiter_depth++; \ } \ while (0) #define pop_delimiter(ds) ds.delimiter_depth-- /* Return the next shell input character. This always reads characters from shell_input_line; when that line is exhausted, it is time to read the next line. This is called by read_token when the shell is processing normal command input. */ /* This implements one-character lookahead/lookbehind across physical input lines, to avoid something being lost because it's pushed back with shell_ungetc when we're at the start of a line. */ static int eol_ungetc_lookahead = 0; static int shell_getc (remove_quoted_newline) int remove_quoted_newline; { register int i; int c; unsigned char uc; static int mustpop = 0; QUIT; if (eol_ungetc_lookahead) { c = eol_ungetc_lookahead; eol_ungetc_lookahead = 0; return (c); } #if defined (ALIAS) || defined (DPAREN_ARITHMETIC) /* If shell_input_line[shell_input_line_index] == 0, but there is something on the pushed list of strings, then we don't want to go off and get another line. We let the code down below handle it. */ if (!shell_input_line || ((!shell_input_line[shell_input_line_index]) && (pushed_string_list == (STRING_SAVER *)NULL))) #else /* !ALIAS && !DPAREN_ARITHMETIC */ if (!shell_input_line || !shell_input_line[shell_input_line_index]) #endif /* !ALIAS && !DPAREN_ARITHMETIC */ { line_number++; restart_read: /* Allow immediate exit if interrupted during input. */ QUIT; i = 0; shell_input_line_terminator = 0; #if defined (JOB_CONTROL) /* This can cause a problem when reading a command as the result of a trap, when the trap is called from flush_child. This call had better not cause jobs to disappear from the job table in that case, or we will have big trouble. */ notify_and_cleanup (); #else /* !JOB_CONTROL */ cleanup_dead_jobs (); #endif /* !JOB_CONTROL */ #if defined (READLINE) if (interactive && bash_input.type != st_string && no_line_editing) #else if (interactive && bash_input.type != st_string) #endif print_prompt (); if (bash_input.type == st_stream) clearerr (stdin); while (1) { c = yy_getc (); /* Allow immediate exit if interrupted during input. */ QUIT; if (c == '\0') { #if 0 internal_warning ("shell_getc: ignored null byte in input"); #endif continue; } RESIZE_MALLOCED_BUFFER (shell_input_line, i, 2, shell_input_line_size, 256); if (c == EOF) { if (bash_input.type == st_stream) clearerr (stdin); if (i == 0) shell_input_line_terminator = EOF; shell_input_line[i] = '\0'; break; } shell_input_line[i++] = c; if (c == '\n') { shell_input_line[--i] = '\0'; current_command_line_count++; break; } } shell_input_line_index = 0; shell_input_line_len = i; /* == strlen (shell_input_line) */ set_line_mbstate (); #if defined (HISTORY) if (remember_on_history && shell_input_line && shell_input_line[0]) { char *expansions; # if defined (BANG_HISTORY) int old_hist; /* If the current delimiter is a single quote, we should not be performing history expansion, even if we're on a different line from the original single quote. */ old_hist = history_expansion_inhibited; if (current_delimiter (dstack) == '\'') history_expansion_inhibited = 1; # endif expansions = pre_process_line (shell_input_line, 1, 1); # if defined (BANG_HISTORY) history_expansion_inhibited = old_hist; # endif if (expansions != shell_input_line) { free (shell_input_line); shell_input_line = expansions; shell_input_line_len = shell_input_line ? strlen (shell_input_line) : 0; if (!shell_input_line_len) current_command_line_count--; /* We have to force the xrealloc below because we don't know the true allocated size of shell_input_line anymore. */ shell_input_line_size = shell_input_line_len; set_line_mbstate (); } } /* Try to do something intelligent with blank lines encountered while entering multi-line commands. XXX - this is grotesque */ else if (remember_on_history && shell_input_line && shell_input_line[0] == '\0' && current_command_line_count > 1) { if (current_delimiter (dstack)) /* We know shell_input_line[0] == 0 and we're reading some sort of quoted string. This means we've got a line consisting of only a newline in a quoted string. We want to make sure this line gets added to the history. */ maybe_add_history (shell_input_line); else { char *hdcs; hdcs = history_delimiting_chars (); if (hdcs && hdcs[0] == ';') maybe_add_history (shell_input_line); } } #endif /* HISTORY */ if (shell_input_line) { /* Lines that signify the end of the shell's input should not be echoed. */ if (echo_input_at_read && (shell_input_line[0] || shell_input_line_terminator != EOF)) fprintf (stderr, "%s\n", shell_input_line); } else { shell_input_line_size = 0; prompt_string_pointer = ¤t_prompt_string; prompt_again (); goto restart_read; } /* Add the newline to the end of this string, iff the string does not already end in an EOF character. */ if (shell_input_line_terminator != EOF) { if (shell_input_line_len + 3 > shell_input_line_size) shell_input_line = (char *)xrealloc (shell_input_line, 1 + (shell_input_line_size += 2)); shell_input_line[shell_input_line_len] = '\n'; shell_input_line[shell_input_line_len + 1] = '\0'; set_line_mbstate (); } } uc = shell_input_line[shell_input_line_index]; if (uc) shell_input_line_index++; if MBTEST(uc == '\\' && remove_quoted_newline && shell_input_line[shell_input_line_index] == '\n') { prompt_again (); line_number++; goto restart_read; } #if defined (ALIAS) || defined (DPAREN_ARITHMETIC) /* If UC is NULL, we have reached the end of the current input string. If pushed_string_list is non-empty, it's time to pop to the previous string because we have fully consumed the result of the last alias expansion. Do it transparently; just return the next character of the string popped to. */ if (!uc && (pushed_string_list != (STRING_SAVER *)NULL)) { if (mustpop) { pop_string (); uc = shell_input_line[shell_input_line_index]; if (uc) shell_input_line_index++; mustpop--; } else { mustpop++; uc = ' '; } } #endif /* ALIAS || DPAREN_ARITHMETIC */ if (!uc && shell_input_line_terminator == EOF) return ((shell_input_line_index != 0) ? '\n' : EOF); return (uc); } /* Put C back into the input for the shell. This might need changes for HANDLE_MULTIBYTE around EOLs. Since we (currently) never push back a character different than we read, shell_input_line_property doesn't need to change when manipulating shell_input_line. The define for last_shell_getc_is_singlebyte should take care of it, though. */ static void shell_ungetc (c) int c; { if (shell_input_line && shell_input_line_index) shell_input_line[--shell_input_line_index] = c; else eol_ungetc_lookahead = c; } #ifdef INCLUDE_UNUSED /* Back the input pointer up by one, effectively `ungetting' a character. */ static void shell_ungetchar () { if (shell_input_line && shell_input_line_index) shell_input_line_index--; } #endif /* Discard input until CHARACTER is seen, then push that character back onto the input stream. */ static void discard_until (character) int character; { int c; while ((c = shell_getc (0)) != EOF && c != character) ; if (c != EOF) shell_ungetc (c); } void execute_prompt_command (command) char *command; { sh_builtin_func_t *temp_last, *temp_this; char *last_lastarg; int temp_exit_value, temp_eof_encountered; temp_last = last_shell_builtin; temp_this = this_shell_builtin; temp_exit_value = last_command_exit_value; temp_eof_encountered = eof_encountered; last_lastarg = get_string_value ("_"); if (last_lastarg) last_lastarg = savestring (last_lastarg); parse_and_execute (savestring (command), "PROMPT_COMMAND", SEVAL_NONINT|SEVAL_NOHIST); last_shell_builtin = temp_last; this_shell_builtin = temp_this; last_command_exit_value = temp_exit_value; eof_encountered = temp_eof_encountered; bind_variable ("_", last_lastarg); FREE (last_lastarg); if (token_to_read == '\n') /* reset_parser was called */ token_to_read = 0; } /* Place to remember the token. We try to keep the buffer at a reasonable size, but it can grow. */ static char *token = (char *)NULL; /* Current size of the token buffer. */ static int token_buffer_size; /* Command to read_token () explaining what we want it to do. */ #define READ 0 #define RESET 1 #define prompt_is_ps1 \ (!prompt_string_pointer || prompt_string_pointer == &ps1_prompt) /* Function for yyparse to call. yylex keeps track of the last two tokens read, and calls read_token. */ static int yylex () { if (interactive && (current_token == 0 || current_token == '\n')) { /* Before we print a prompt, we might have to check mailboxes. We do this only if it is time to do so. Notice that only here is the mail alarm reset; nothing takes place in check_mail () except the checking of mail. Please don't change this. */ if (prompt_is_ps1 && time_to_check_mail ()) { check_mail (); reset_mail_timer (); } /* Avoid printing a prompt if we're not going to read anything, e.g. after resetting the parser with read_token (RESET). */ if (token_to_read == 0 && interactive) prompt_again (); } two_tokens_ago = token_before_that; token_before_that = last_read_token; last_read_token = current_token; current_token = read_token (READ); return (current_token); } /* When non-zero, we have read the required tokens which allow ESAC to be the next one read. */ static int esacs_needed_count; void gather_here_documents () { int r = 0; while (need_here_doc) { make_here_document (redir_stack[r++]); need_here_doc--; } } /* When non-zero, an open-brace used to create a group is awaiting a close brace partner. */ static int open_brace_count; #define command_token_position(token) \ (((token) == ASSIGNMENT_WORD) || \ ((token) != SEMI_SEMI && reserved_word_acceptable(token))) #define assignment_acceptable(token) command_token_position(token) && \ ((parser_state & PST_CASEPAT) == 0) /* Check to see if TOKEN is a reserved word and return the token value if it is. */ #define CHECK_FOR_RESERVED_WORD(tok) \ do { \ if (!dollar_present && !quoted && \ reserved_word_acceptable (last_read_token)) \ { \ int i; \ for (i = 0; word_token_alist[i].word != (char *)NULL; i++) \ if (STREQ (tok, word_token_alist[i].word)) \ { \ if ((parser_state & PST_CASEPAT) && (word_token_alist[i].token != ESAC)) \ break; \ if (word_token_alist[i].token == TIME) \ break; \ if (word_token_alist[i].token == ESAC) \ parser_state &= ~(PST_CASEPAT|PST_CASESTMT); \ else if (word_token_alist[i].token == CASE) \ parser_state |= PST_CASESTMT; \ else if (word_token_alist[i].token == COND_END) \ parser_state &= ~(PST_CONDCMD|PST_CONDEXPR); \ else if (word_token_alist[i].token == COND_START) \ parser_state |= PST_CONDCMD; \ else if (word_token_alist[i].token == '{') \ open_brace_count++; \ else if (word_token_alist[i].token == '}' && open_brace_count) \ open_brace_count--; \ return (word_token_alist[i].token); \ } \ } \ } while (0) #if defined (ALIAS) /* OK, we have a token. Let's try to alias expand it, if (and only if) it's eligible. It is eligible for expansion if EXPAND_ALIASES is set, and the token is unquoted and the last token read was a command separator (or expand_next_token is set), and we are currently processing an alias (pushed_string_list is non-empty) and this token is not the same as the current or any previously processed alias. Special cases that disqualify: In a pattern list in a case statement (parser_state & PST_CASEPAT). */ static int alias_expand_token (tokstr) char *tokstr; { char *expanded; alias_t *ap; if (((parser_state & PST_ALEXPNEXT) || command_token_position (last_read_token)) && (parser_state & PST_CASEPAT) == 0) { ap = find_alias (tokstr); /* Currently expanding this token. */ if (ap && (ap->flags & AL_BEINGEXPANDED)) return (NO_EXPANSION); expanded = ap ? savestring (ap->value) : (char *)NULL; if (expanded) { push_string (expanded, ap->flags & AL_EXPANDNEXT, ap); return (RE_READ_TOKEN); } else /* This is an eligible token that does not have an expansion. */ return (NO_EXPANSION); } return (NO_EXPANSION); } #endif /* ALIAS */ static int time_command_acceptable () { #if defined (COMMAND_TIMING) switch (last_read_token) { case 0: case ';': case '\n': case AND_AND: case OR_OR: case '&': case DO: case THEN: case ELSE: case '{': /* } */ case '(': /* ) */ return 1; default: return 0; } #else return 0; #endif /* COMMAND_TIMING */ } /* Handle special cases of token recognition: IN is recognized if the last token was WORD and the token before that was FOR or CASE or SELECT. DO is recognized if the last token was WORD and the token before that was FOR or SELECT. ESAC is recognized if the last token caused `esacs_needed_count' to be set `{' is recognized if the last token as WORD and the token before that was FUNCTION, or if we just parsed an arithmetic `for' command. `}' is recognized if there is an unclosed `{' present. `-p' is returned as TIMEOPT if the last read token was TIME. ']]' is returned as COND_END if the parser is currently parsing a conditional expression ((parser_state & PST_CONDEXPR) != 0) `time' is returned as TIME if and only if it is immediately preceded by one of `;', `\n', `||', `&&', or `&'. */ static int special_case_tokens (tokstr) char *tokstr; { if ((last_read_token == WORD) && #if defined (SELECT_COMMAND) ((token_before_that == FOR) || (token_before_that == CASE) || (token_before_that == SELECT)) && #else ((token_before_that == FOR) || (token_before_that == CASE)) && #endif (tokstr[0] == 'i' && tokstr[1] == 'n' && tokstr[2] == 0)) { if (token_before_that == CASE) { parser_state |= PST_CASEPAT; esacs_needed_count++; } return (IN); } if (last_read_token == WORD && #if defined (SELECT_COMMAND) (token_before_that == FOR || token_before_that == SELECT) && #else (token_before_that == FOR) && #endif (tokstr[0] == 'd' && tokstr[1] == 'o' && tokstr[2] == '\0')) return (DO); /* Ditto for ESAC in the CASE case. Specifically, this handles "case word in esac", which is a legal construct, certainly because someone will pass an empty arg to the case construct, and we don't want it to barf. Of course, we should insist that the case construct has at least one pattern in it, but the designers disagree. */ if (esacs_needed_count) { esacs_needed_count--; if (STREQ (tokstr, "esac")) { parser_state &= ~PST_CASEPAT; return (ESAC); } } /* The start of a shell function definition. */ if (parser_state & PST_ALLOWOPNBRC) { parser_state &= ~PST_ALLOWOPNBRC; if (tokstr[0] == '{' && tokstr[1] == '\0') /* } */ { open_brace_count++; function_bstart = line_number; return ('{'); /* } */ } } /* We allow a `do' after a for ((...)) without an intervening list_terminator */ if (last_read_token == ARITH_FOR_EXPRS && tokstr[0] == 'd' && tokstr[1] == 'o' && !tokstr[2]) return (DO); if (last_read_token == ARITH_FOR_EXPRS && tokstr[0] == '{' && tokstr[1] == '\0') /* } */ { open_brace_count++; return ('{'); /* } */ } if (open_brace_count && reserved_word_acceptable (last_read_token) && tokstr[0] == '}' && !tokstr[1]) { open_brace_count--; /* { */ return ('}'); } #if defined (COMMAND_TIMING) /* Handle -p after `time'. */ if (last_read_token == TIME && tokstr[0] == '-' && tokstr[1] == 'p' && !tokstr[2]) return (TIMEOPT); #endif #if defined (COMMAND_TIMING) if (STREQ (token, "time") && ((parser_state & PST_CASEPAT) == 0) && time_command_acceptable ()) return (TIME); #endif /* COMMAND_TIMING */ #if defined (COND_COMMAND) /* [[ */ if ((parser_state & PST_CONDEXPR) && tokstr[0] == ']' && tokstr[1] == ']' && tokstr[2] == '\0') return (COND_END); #endif return (-1); } /* Called from shell.c when Control-C is typed at top level. Or by the error rule at top level. */ void reset_parser () { dstack.delimiter_depth = 0; /* No delimiters found so far. */ open_brace_count = 0; parser_state = 0; #if defined (ALIAS) || defined (DPAREN_ARITHMETIC) if (pushed_string_list) free_string_list (); #endif /* ALIAS || DPAREN_ARITHMETIC */ if (shell_input_line) { free (shell_input_line); shell_input_line = (char *)NULL; shell_input_line_size = shell_input_line_index = 0; } FREE (word_desc_to_read); word_desc_to_read = (WORD_DESC *)NULL; last_read_token = '\n'; token_to_read = '\n'; } /* Read the next token. Command can be READ (normal operation) or RESET (to normalize state). */ static int read_token (command) int command; { int character; /* Current character. */ int peek_char; /* Temporary look-ahead character. */ int result; /* The thing to return. */ if (command == RESET) { reset_parser (); return ('\n'); } if (token_to_read) { result = token_to_read; if (token_to_read == WORD || token_to_read == ASSIGNMENT_WORD) { yylval.word = word_desc_to_read; word_desc_to_read = (WORD_DESC *)NULL; } token_to_read = 0; return (result); } #if defined (COND_COMMAND) if ((parser_state & (PST_CONDCMD|PST_CONDEXPR)) == PST_CONDCMD) { cond_lineno = line_number; parser_state |= PST_CONDEXPR; yylval.command = parse_cond_command (); if (cond_token != COND_END) { cond_error (); return (-1); } token_to_read = COND_END; parser_state &= ~(PST_CONDEXPR|PST_CONDCMD); return (COND_CMD); } #endif #if defined (ALIAS) /* This is a place to jump back to once we have successfully expanded a token with an alias and pushed the string with push_string () */ re_read_token: #endif /* ALIAS */ /* Read a single word from input. Start by skipping blanks. */ while ((character = shell_getc (1)) != EOF && whitespace (character)) ; if (character == EOF) { EOF_Reached = 1; return (yacc_EOF); } if MBTEST(character == '#' && (!interactive || interactive_comments)) { /* A comment. Discard until EOL or EOF, and then return a newline. */ discard_until ('\n'); shell_getc (0); character = '\n'; /* this will take the next if statement and return. */ } if (character == '\n') { /* If we're about to return an unquoted newline, we can go and collect the text of any pending here document. */ if (need_here_doc) gather_here_documents (); #if defined (ALIAS) parser_state &= ~PST_ALEXPNEXT; #endif /* ALIAS */ return (character); } /* Shell meta-characters. */ if MBTEST(shellmeta (character) && ((parser_state & PST_DBLPAREN) == 0)) { #if defined (ALIAS) /* Turn off alias tokenization iff this character sequence would not leave us ready to read a command. */ if (character == '<' || character == '>') parser_state &= ~PST_ALEXPNEXT; #endif /* ALIAS */ peek_char = shell_getc (1); if (character == peek_char) { switch (character) { case '<': /* If '<' then we could be at "<<" or at "<<-". We have to look ahead one more character. */ peek_char = shell_getc (1); if (peek_char == '-') return (LESS_LESS_MINUS); else if (peek_char == '<') return (LESS_LESS_LESS); else { shell_ungetc (peek_char); return (LESS_LESS); } case '>': return (GREATER_GREATER); case ';': parser_state |= PST_CASEPAT; #if defined (ALIAS) parser_state &= ~PST_ALEXPNEXT; #endif /* ALIAS */ return (SEMI_SEMI); case '&': return (AND_AND); case '|': return (OR_OR); #if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND) case '(': /* ) */ result = parse_dparen (character); if (result == -2) break; else return result; #endif } } else if MBTEST(character == '<' && peek_char == '&') return (LESS_AND); else if MBTEST(character == '>' && peek_char == '&') return (GREATER_AND); else if MBTEST(character == '<' && peek_char == '>') return (LESS_GREATER); else if MBTEST(character == '>' && peek_char == '|') return (GREATER_BAR); else if MBTEST(peek_char == '>' && character == '&') return (AND_GREATER); shell_ungetc (peek_char); /* If we look like we are reading the start of a function definition, then let the reader know about it so that we will do the right thing with `{'. */ if MBTEST(character == ')' && last_read_token == '(' && token_before_that == WORD) { parser_state |= PST_ALLOWOPNBRC; #if defined (ALIAS) parser_state &= ~PST_ALEXPNEXT; #endif /* ALIAS */ function_dstart = line_number; } /* case pattern lists may be preceded by an optional left paren. If we're not trying to parse a case pattern list, the left paren indicates a subshell. */ if MBTEST(character == '(' && (parser_state & PST_CASEPAT) == 0) /* ) */ parser_state |= PST_SUBSHELL; /*(*/ else if MBTEST((parser_state & PST_CASEPAT) && character == ')') parser_state &= ~PST_CASEPAT; /*(*/ else if MBTEST((parser_state & PST_SUBSHELL) && character == ')') parser_state &= ~PST_SUBSHELL; #if defined (PROCESS_SUBSTITUTION) /* Check for the constructs which introduce process substitution. Shells running in `posix mode' don't do process substitution. */ if MBTEST(posixly_correct || ((character != '>' && character != '<') || peek_char != '(')) /*)*/ #endif /* PROCESS_SUBSTITUTION */ return (character); } /* Hack <&- (close stdin) case. Also <&N- (dup and close). */ if MBTEST(character == '-' && (last_read_token == LESS_AND || last_read_token == GREATER_AND)) return (character); /* Okay, if we got this far, we have to read a word. Read one, and then check it against the known ones. */ result = read_token_word (character); #if defined (ALIAS) if (result == RE_READ_TOKEN) goto re_read_token; #endif return result; } /* * Match a $(...) or other grouping construct. This has to handle embedded * quoted strings ('', ``, "") and nested constructs. It also must handle * reprompting the user, if necessary, after reading a newline (unless the * P_NONL flag is passed), and returning correct error values if it reads * EOF. */ #define P_FIRSTCLOSE 0x01 #define P_ALLOWESC 0x02 static char matched_pair_error; static char * parse_matched_pair (qc, open, close, lenp, flags) int qc; /* `"' if this construct is within double quotes */ int open, close; int *lenp, flags; { int count, ch, was_dollar; int pass_next_character, nestlen, ttranslen, start_lineno; char *ret, *nestret, *ttrans; int retind, retsize; count = 1; pass_next_character = was_dollar = 0; ret = (char *)xmalloc (retsize = 64); retind = 0; start_lineno = line_number; while (count) { ch = shell_getc ((qc != '\'' || (flags & P_ALLOWESC)) && pass_next_character == 0); if (ch == EOF) { free (ret); parser_error (start_lineno, "unexpected EOF while looking for matching `%c'", close); EOF_Reached = 1; /* XXX */ return (&matched_pair_error); } /* Possible reprompting. */ if (ch == '\n' && interactive && (bash_input.type == st_stdin || bash_input.type == st_stream)) prompt_again (); if (pass_next_character) /* last char was backslash */ { pass_next_character = 0; if (qc != '\'' && ch == '\n') /* double-quoted \<newline> disappears. */ { if (retind > 0) retind--; /* swallow previously-added backslash */ continue; } RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64); if MBTEST(ch == CTLESC || ch == CTLNUL) ret[retind++] = CTLESC; ret[retind++] = ch; continue; } else if MBTEST(ch == CTLESC || ch == CTLNUL) /* special shell escapes */ { RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64); ret[retind++] = CTLESC; ret[retind++] = ch; continue; } else if MBTEST(ch == close) /* ending delimiter */ count--; #if 1 /* handle nested ${...} specially. */ else if MBTEST(open != close && was_dollar && open == '{' && ch == open) /* } */ count++; #endif else if MBTEST(((flags & P_FIRSTCLOSE) == 0) && ch == open) /* nested begin */ count++; /* Add this character. */ RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64); ret[retind++] = ch; if (open == '\'') /* '' inside grouping construct */ { if MBTEST((flags & P_ALLOWESC) && ch == '\\') pass_next_character++; continue; } if MBTEST(ch == '\\') /* backslashes */ pass_next_character++; if (open != close) /* a grouping construct */ { if MBTEST(shellquote (ch)) { /* '', ``, or "" inside $(...) or other grouping construct. */ push_delimiter (dstack, ch); if MBTEST(was_dollar && ch == '\'') /* $'...' inside group */ nestret = parse_matched_pair (ch, ch, ch, &nestlen, P_ALLOWESC); else nestret = parse_matched_pair (ch, ch, ch, &nestlen, 0); pop_delimiter (dstack); if (nestret == &matched_pair_error) { free (ret); return &matched_pair_error; } if MBTEST(was_dollar && ch == '\'') { /* Translate $'...' here. */ ttrans = ansiexpand (nestret, 0, nestlen - 1, &ttranslen); xfree (nestret); nestret = sh_single_quote (ttrans); free (ttrans); nestlen = strlen (nestret); retind -= 2; /* back up before the $' */ } else if MBTEST(was_dollar && ch == '"') { /* Locale expand $"..." here. */ ttrans = localeexpand (nestret, 0, nestlen - 1, start_lineno, &ttranslen); xfree (nestret); nestret = (char *)xmalloc (ttranslen + 3); nestret[0] = '"'; strcpy (nestret + 1, ttrans); nestret[ttranslen + 1] = '"'; nestret[ttranslen += 2] = '\0'; free (ttrans); nestlen = ttranslen; retind -= 2; /* back up before the $" */ } if (nestlen) { RESIZE_MALLOCED_BUFFER (ret, retind, nestlen, retsize, 64); strcpy (ret + retind, nestret); retind += nestlen; } FREE (nestret); } } /* Parse an old-style command substitution within double quotes as a single word. */ /* XXX - sh and ksh93 don't do this - XXX */ else if MBTEST(open == '"' && ch == '`') { nestret = parse_matched_pair (0, '`', '`', &nestlen, 0); if (nestret == &matched_pair_error) { free (ret); return &matched_pair_error; } if (nestlen) { RESIZE_MALLOCED_BUFFER (ret, retind, nestlen, retsize, 64); strcpy (ret + retind, nestret); retind += nestlen; } FREE (nestret); } else if MBTEST(was_dollar && (ch == '(' || ch == '{' || ch == '[')) /* ) } ] */ /* check for $(), $[], or ${} inside quoted string. */ { if (open == ch) /* undo previous increment */ count--; if (ch == '(') /* ) */ nestret = parse_matched_pair (0, '(', ')', &nestlen, 0); else if (ch == '{') /* } */ nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE); else if (ch == '[') /* ] */ nestret = parse_matched_pair (0, '[', ']', &nestlen, 0); if (nestret == &matched_pair_error) { free (ret); return &matched_pair_error; } if (nestlen) { RESIZE_MALLOCED_BUFFER (ret, retind, nestlen, retsize, 64); strcpy (ret + retind, nestret); retind += nestlen; } FREE (nestret); } was_dollar = MBTEST(ch == '$'); } ret[retind] = '\0'; if (lenp) *lenp = retind; return ret; } #if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND) /* Parse a double-paren construct. It can be either an arithmetic command, an arithmetic `for' command, or a nested subshell. Returns the parsed token, -1 on error, or -2 if we didn't do anything and should just go on. */ static int parse_dparen (c) int c; { int cmdtyp, len, sline; char *wval, *wv2; WORD_DESC *wd; #if defined (ARITH_FOR_COMMAND) if (last_read_token == FOR) { arith_for_lineno = line_number; cmdtyp = parse_arith_cmd (&wval); if (cmdtyp == 1) { /* parse_arith_cmd adds quotes at the beginning and end of the string it returns; we need to take those out. */ len = strlen (wval); wv2 = (char *)xmalloc (len); strncpy (wv2, wval + 1, len - 2); wv2[len - 2] = '\0'; wd = make_word (wv2); yylval.word_list = make_word_list (wd, (WORD_LIST *)NULL); free (wval); free (wv2); return (ARITH_FOR_EXPRS); } else return -1; /* ERROR */ } #endif #if defined (DPAREN_ARITHMETIC) if (reserved_word_acceptable (last_read_token)) { sline = line_number; cmdtyp = parse_arith_cmd (&wval); if (cmdtyp == 1) /* arithmetic command */ { wd = make_word (wval); wd->flags = W_QUOTED; yylval.word_list = make_word_list (wd, (WORD_LIST *)NULL); free (wval); /* make_word copies it */ return (ARITH_CMD); } else if (cmdtyp == 0) /* nested subshell */ { push_string (wval, 0, (alias_t *)NULL); if ((parser_state & PST_CASEPAT) == 0) parser_state |= PST_SUBSHELL; return (c); } else /* ERROR */ return -1; } #endif return -2; /* XXX */ } /* We've seen a `(('. Look for the matching `))'. If we get it, return 1. If not, assume it's a nested subshell for backwards compatibility and return 0. In any case, put the characters we've consumed into a locally- allocated buffer and make *ep point to that buffer. Return -1 on an error, for example EOF. */ static int parse_arith_cmd (ep) char **ep; { int exp_lineno, rval, c; char *ttok, *tokstr; int ttoklen; exp_lineno = line_number; ttok = parse_matched_pair (0, '(', ')', &ttoklen, 0); rval = 1; if (ttok == &matched_pair_error) return -1; /* Check that the next character is the closing right paren. If not, this is a syntax error. ( */ c = shell_getc (0); if MBTEST(c != ')') rval = 0; tokstr = (char *)xmalloc (ttoklen + 4); /* (( ... )) -> "..." */ tokstr[0] = (rval == 1) ? '"' : '('; strncpy (tokstr + 1, ttok, ttoklen - 1); /* don't copy the final `)' */ if (rval == 1) { tokstr[ttoklen] = '"'; tokstr[ttoklen+1] = '\0'; } else { tokstr[ttoklen] = ')'; tokstr[ttoklen+1] = c; tokstr[ttoklen+2] = '\0'; } *ep = tokstr; FREE (ttok); return rval; } #endif /* DPAREN_ARITHMETIC || ARITH_FOR_COMMAND */ #if defined (COND_COMMAND) static void cond_error () { char *etext; if (EOF_Reached && cond_token != COND_ERROR) /* [[ */ parser_error (cond_lineno, "unexpected EOF while looking for `]]'"); else if (cond_token != COND_ERROR) { if (etext = error_token_from_token (cond_token)) { parser_error (cond_lineno, "syntax error in conditional expression: unexpected token `%s'", etext); free (etext); } else parser_error (cond_lineno, "syntax error in conditional expression"); } } static COND_COM * cond_expr () { return (cond_or ()); } static COND_COM * cond_or () { COND_COM *l, *r; l = cond_and (); if (cond_token == OR_OR) { r = cond_or (); l = make_cond_node (COND_OR, (WORD_DESC *)NULL, l, r); } return l; } static COND_COM * cond_and () { COND_COM *l, *r; l = cond_term (); if (cond_token == AND_AND) { r = cond_and (); l = make_cond_node (COND_AND, (WORD_DESC *)NULL, l, r); } return l; } static int cond_skip_newlines () { while ((cond_token = read_token (READ)) == '\n') { if (interactive && (bash_input.type == st_stdin || bash_input.type == st_stream)) prompt_again (); } return (cond_token); } #define COND_RETURN_ERROR() \ do { cond_token = COND_ERROR; return ((COND_COM *)NULL); } while (0) static COND_COM * cond_term () { WORD_DESC *op; COND_COM *term, *tleft, *tright; int tok, lineno; char *etext; /* Read a token. It can be a left paren, a `!', a unary operator, or a word that should be the first argument of a binary operator. Start by skipping newlines, since this is a compound command. */ tok = cond_skip_newlines (); lineno = line_number; if (tok == COND_END) { COND_RETURN_ERROR (); } else if (tok == '(') { term = cond_expr (); if (cond_token != ')') { if (term) dispose_cond_node (term); /* ( */ if (etext = error_token_from_token (cond_token)) { parser_error (lineno, "unexpected token `%s', expected `)'", etext); free (etext); } else parser_error (lineno, "expected `)'"); COND_RETURN_ERROR (); } term = make_cond_node (COND_EXPR, (WORD_DESC *)NULL, term, (COND_COM *)NULL); (void)cond_skip_newlines (); } else if (tok == BANG || (tok == WORD && (yylval.word->word[0] == '!' && yylval.word->word[1] == '\0'))) { if (tok == WORD) dispose_word (yylval.word); /* not needed */ term = cond_term (); if (term) term->flags |= CMD_INVERT_RETURN; } else if (tok == WORD && test_unop (yylval.word->word)) { op = yylval.word; tok = read_token (READ); if (tok == WORD) { tleft = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL); term = make_cond_node (COND_UNARY, op, tleft, (COND_COM *)NULL); } else { dispose_word (op); if (etext = error_token_from_token (tok)) { parser_error (line_number, "unexpected argument `%s' to conditional unary operator", etext); free (etext); } else parser_error (line_number, "unexpected argument to conditional unary operator"); COND_RETURN_ERROR (); } (void)cond_skip_newlines (); } else if (tok == WORD) /* left argument to binary operator */ { /* lhs */ tleft = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL); /* binop */ tok = read_token (READ); if (tok == WORD && test_binop (yylval.word->word)) op = yylval.word; else if (tok == '<' || tok == '>') op = make_word_from_token (tok); /* ( */ /* There should be a check before blindly accepting the `)' that we have seen the opening `('. */ else if (tok == COND_END || tok == AND_AND || tok == OR_OR || tok == ')') { /* Special case. [[ x ]] is equivalent to [[ -n x ]], just like the test command. Similarly for [[ x && expr ]] or [[ x || expr ]] or [[ (x) ]]. */ op = make_word ("-n"); term = make_cond_node (COND_UNARY, op, tleft, (COND_COM *)NULL); cond_token = tok; return (term); } else { if (etext = error_token_from_token (tok)) { parser_error (line_number, "unexpected token `%s', conditional binary operator expected", etext); free (etext); } else parser_error (line_number, "conditional binary operator expected"); dispose_cond_node (tleft); COND_RETURN_ERROR (); } /* rhs */ tok = read_token (READ); if (tok == WORD) { tright = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL); term = make_cond_node (COND_BINARY, op, tleft, tright); } else { if (etext = error_token_from_token (tok)) { parser_error (line_number, "unexpected argument `%s' to conditional binary operator", etext); free (etext); } else parser_error (line_number, "unexpected argument to conditional binary operator"); dispose_cond_node (tleft); dispose_word (op); COND_RETURN_ERROR (); } (void)cond_skip_newlines (); } else { if (tok < 256) parser_error (line_number, "unexpected token `%c' in conditional command", tok); else if (etext = error_token_from_token (tok)) { parser_error (line_number, "unexpected token `%s' in conditional command", etext); free (etext); } else parser_error (line_number, "unexpected token %d in conditional command", tok); COND_RETURN_ERROR (); } return (term); } /* This is kind of bogus -- we slip a mini recursive-descent parser in here to handle the conditional statement syntax. */ static COMMAND * parse_cond_command () { COND_COM *cexp; cexp = cond_expr (); return (make_cond_command (cexp)); } #endif #if defined (ARRAY_VARS) /* When this is called, it's guaranteed that we don't care about anything in t beyond i. We do save and restore the chars, though. */ static int token_is_assignment (t, i) char *t; int i; { unsigned char c, c1; int r; c = t[i]; c1 = t[i+1]; t[i] = '='; t[i+1] = '\0'; r = assignment (t); t[i] = c; t[i+1] = c1; return r; } static int token_is_ident (t, i) char *t; int i; { unsigned char c; int r; c = t[i]; t[i] = '\0'; r = legal_identifier (t); t[i] = c; return r; } #endif static int read_token_word (character) int character; { /* The value for YYLVAL when a WORD is read. */ WORD_DESC *the_word; /* Index into the token that we are building. */ int token_index; /* ALL_DIGITS becomes zero when we see a non-digit. */ int all_digit_token; /* DOLLAR_PRESENT becomes non-zero if we see a `$'. */ int dollar_present; /* QUOTED becomes non-zero if we see one of ("), ('), (`), or (\). */ int quoted; /* Non-zero means to ignore the value of the next character, and just to add it no matter what. */ int pass_next_character; /* The current delimiting character. */ int cd; int result, peek_char; char *ttok, *ttrans; int ttoklen, ttranslen; intmax_t lvalue; if (token_buffer_size < TOKEN_DEFAULT_INITIAL_SIZE) token = (char *)xrealloc (token, token_buffer_size = TOKEN_DEFAULT_INITIAL_SIZE); token_index = 0; all_digit_token = DIGIT (character); dollar_present = quoted = pass_next_character = 0; for (;;) { if (character == EOF) goto got_token; if (pass_next_character) { pass_next_character = 0; goto got_character; } cd = current_delimiter (dstack); /* Handle backslashes. Quote lots of things when not inside of double-quotes, quote some things inside of double-quotes. */ if MBTEST(character == '\\') { peek_char = shell_getc (0); /* Backslash-newline is ignored in all cases except when quoted with single quotes. */ if (peek_char == '\n') { character = '\n'; goto next_character; } else { shell_ungetc (peek_char); /* If the next character is to be quoted, note it now. */ if (cd == 0 || cd == '`' || (cd == '"' && peek_char >= 0 && (sh_syntaxtab[peek_char] & CBSDQUOTE))) pass_next_character++; quoted = 1; goto got_character; } } /* Parse a matched pair of quote characters. */ if MBTEST(shellquote (character)) { push_delimiter (dstack, character); ttok = parse_matched_pair (character, character, character, &ttoklen, 0); pop_delimiter (dstack); if (ttok == &matched_pair_error) return -1; /* Bail immediately. */ RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2, token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); token[token_index++] = character; strcpy (token + token_index, ttok); token_index += ttoklen; all_digit_token = 0; quoted = 1; dollar_present |= (character == '"' && strchr (ttok, '$') != 0); FREE (ttok); goto next_character; } #ifdef EXTENDED_GLOB /* Parse a ksh-style extended pattern matching specification. */ if (extended_glob && PATTERN_CHAR (character)) { peek_char = shell_getc (1); if MBTEST(peek_char == '(') /* ) */ { push_delimiter (dstack, peek_char); ttok = parse_matched_pair (cd, '(', ')', &ttoklen, 0); pop_delimiter (dstack); if (ttok == &matched_pair_error) return -1; /* Bail immediately. */ RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2, token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); token[token_index++] = character; token[token_index++] = peek_char; strcpy (token + token_index, ttok); token_index += ttoklen; FREE (ttok); dollar_present = all_digit_token = 0; goto next_character; } else shell_ungetc (peek_char); } #endif /* EXTENDED_GLOB */ /* If the delimiter character is not single quote, parse some of the shell expansions that must be read as a single word. */ if (shellexp (character)) { peek_char = shell_getc (1); /* $(...), <(...), >(...), $((...)), ${...}, and $[...] constructs */ if MBTEST(peek_char == '(' || \ ((peek_char == '{' || peek_char == '[') && character == '$')) /* ) ] } */ { if (peek_char == '{') /* } */ ttok = parse_matched_pair (cd, '{', '}', &ttoklen, P_FIRSTCLOSE); else if (peek_char == '(') /* ) */ { /* XXX - push and pop the `(' as a delimiter for use by the command-oriented-history code. This way newlines appearing in the $(...) string get added to the history literally rather than causing a possibly- incorrect `;' to be added. ) */ push_delimiter (dstack, peek_char); ttok = parse_matched_pair (cd, '(', ')', &ttoklen, 0); pop_delimiter (dstack); } else ttok = parse_matched_pair (cd, '[', ']', &ttoklen, 0); if (ttok == &matched_pair_error) return -1; /* Bail immediately. */ RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2, token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); token[token_index++] = character; token[token_index++] = peek_char; strcpy (token + token_index, ttok); token_index += ttoklen; FREE (ttok); dollar_present = 1; all_digit_token = 0; goto next_character; } /* This handles $'...' and $"..." new-style quoted strings. */ else if MBTEST(character == '$' && (peek_char == '\'' || peek_char == '"')) { int first_line; first_line = line_number; push_delimiter (dstack, peek_char); ttok = parse_matched_pair (peek_char, peek_char, peek_char, &ttoklen, (peek_char == '\'') ? P_ALLOWESC : 0); pop_delimiter (dstack); if (ttok == &matched_pair_error) return -1; if (peek_char == '\'') { ttrans = ansiexpand (ttok, 0, ttoklen - 1, &ttranslen); free (ttok); /* Insert the single quotes and correctly quote any embedded single quotes (allowed because P_ALLOWESC was passed to parse_matched_pair). */ ttok = sh_single_quote (ttrans); free (ttrans); ttrans = ttok; ttranslen = strlen (ttrans); } else { /* Try to locale-expand the converted string. */ ttrans = localeexpand (ttok, 0, ttoklen - 1, first_line, &ttranslen); free (ttok); /* Add the double quotes back */ ttok = (char *)xmalloc (ttranslen + 3); ttok[0] = '"'; strcpy (ttok + 1, ttrans); ttok[ttranslen + 1] = '"'; ttok[ttranslen += 2] = '\0'; free (ttrans); ttrans = ttok; } RESIZE_MALLOCED_BUFFER (token, token_index, ttranslen + 2, token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); strcpy (token + token_index, ttrans); token_index += ttranslen; FREE (ttrans); quoted = 1; all_digit_token = 0; goto next_character; } /* This could eventually be extended to recognize all of the shell's single-character parameter expansions, and set flags.*/ else if MBTEST(character == '$' && peek_char == '$') { ttok = (char *)xmalloc (3); ttok[0] = ttok[1] = '$'; ttok[2] = '\0'; RESIZE_MALLOCED_BUFFER (token, token_index, 3, token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); strcpy (token + token_index, ttok); token_index += 2; dollar_present = 1; all_digit_token = 0; FREE (ttok); goto next_character; } else shell_ungetc (peek_char); } #if defined (ARRAY_VARS) /* Identify possible array subscript assignment; match [...] */ else if MBTEST(character == '[' && token_index > 0 && assignment_acceptable (last_read_token) && token_is_ident (token, token_index)) /* ] */ { ttok = parse_matched_pair (cd, '[', ']', &ttoklen, 0); if (ttok == &matched_pair_error) return -1; /* Bail immediately. */ RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2, token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); token[token_index++] = character; strcpy (token + token_index, ttok); token_index += ttoklen; FREE (ttok); all_digit_token = 0; goto next_character; } /* Identify possible compound array variable assignment. */ else if MBTEST(character == '=' && token_index > 0 && token_is_assignment (token, token_index)) { peek_char = shell_getc (1); if MBTEST(peek_char == '(') /* ) */ { ttok = parse_compound_assignment (&ttoklen); RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 4, token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); token[token_index++] = '='; token[token_index++] = '('; if (ttok) { strcpy (token + token_index, ttok); token_index += ttoklen; } token[token_index++] = ')'; FREE (ttok); all_digit_token = 0; goto next_character; } else shell_ungetc (peek_char); } #endif /* When not parsing a multi-character word construct, shell meta- characters break words. */ if MBTEST(shellbreak (character)) { shell_ungetc (character); goto got_token; } got_character: all_digit_token &= DIGIT (character); dollar_present |= character == '$'; if (character == CTLESC || character == CTLNUL) token[token_index++] = CTLESC; token[token_index++] = character; RESIZE_MALLOCED_BUFFER (token, token_index, 1, token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); next_character: if (character == '\n' && interactive && (bash_input.type == st_stdin || bash_input.type == st_stream)) prompt_again (); /* We want to remove quoted newlines (that is, a \<newline> pair) unless we are within single quotes or pass_next_character is set (the shell equivalent of literal-next). */ cd = current_delimiter (dstack); character = shell_getc (cd != '\'' && pass_next_character == 0); } /* end for (;;) */ got_token: token[token_index] = '\0'; /* Check to see what thing we should return. If the last_read_token is a `<', or a `&', or the character which ended this token is a '>' or '<', then, and ONLY then, is this input token a NUMBER. Otherwise, it is just a word, and should be returned as such. */ if MBTEST(all_digit_token && (character == '<' || character == '>' || \ last_read_token == LESS_AND || \ last_read_token == GREATER_AND)) { if (legal_number (token, &lvalue) && (int)lvalue == lvalue) yylval.number = lvalue; else yylval.number = -1; return (NUMBER); } /* Check for special case tokens. */ result = (last_shell_getc_is_singlebyte) ? special_case_tokens (token) : -1; if (result >= 0) return result; #if defined (ALIAS) /* Posix.2 does not allow reserved words to be aliased, so check for all of them, including special cases, before expanding the current token as an alias. */ if MBTEST(posixly_correct) CHECK_FOR_RESERVED_WORD (token); /* Aliases are expanded iff EXPAND_ALIASES is non-zero, and quoting inhibits alias expansion. */ if (expand_aliases && quoted == 0) { result = alias_expand_token (token); if (result == RE_READ_TOKEN) return (RE_READ_TOKEN); else if (result == NO_EXPANSION) parser_state &= ~PST_ALEXPNEXT; } /* If not in Posix.2 mode, check for reserved words after alias expansion. */ if MBTEST(posixly_correct == 0) #endif CHECK_FOR_RESERVED_WORD (token); the_word = (WORD_DESC *)xmalloc (sizeof (WORD_DESC)); the_word->word = (char *)xmalloc (1 + token_index); the_word->flags = 0; strcpy (the_word->word, token); if (dollar_present) the_word->flags |= W_HASDOLLAR; if (quoted) the_word->flags |= W_QUOTED; /* A word is an assignment if it appears at the beginning of a simple command, or after another assignment word. This is context-dependent, so it cannot be handled in the grammar. */ if (assignment (token)) { the_word->flags |= W_ASSIGNMENT; /* Don't perform word splitting on assignment statements. */ if (assignment_acceptable (last_read_token)) the_word->flags |= W_NOSPLIT; } yylval.word = the_word; result = ((the_word->flags & (W_ASSIGNMENT|W_NOSPLIT)) == (W_ASSIGNMENT|W_NOSPLIT)) ? ASSIGNMENT_WORD : WORD; if (last_read_token == FUNCTION) { parser_state |= PST_ALLOWOPNBRC; function_dstart = line_number; } return (result); } /* Return 1 if TOKSYM is a token that after being read would allow a reserved word to be seen, else 0. */ static int reserved_word_acceptable (toksym) int toksym; { switch (toksym) { case '\n': case ';': case '(': case ')': case '|': case '&': case '{': case '}': /* XXX */ case AND_AND: case BANG: case DO: case DONE: case ELIF: case ELSE: case ESAC: case FI: case IF: case OR_OR: case SEMI_SEMI: case THEN: case TIME: case TIMEOPT: case UNTIL: case WHILE: case 0: return 1; default: return 0; } } /* Return the index of TOKEN in the alist of reserved words, or -1 if TOKEN is not a shell reserved word. */ int find_reserved_word (tokstr) char *tokstr; { int i; for (i = 0; word_token_alist[i].word; i++) if (STREQ (tokstr, word_token_alist[i].word)) return i; return -1; } #if 0 #if defined (READLINE) /* Called after each time readline is called. This insures that whatever the new prompt string is gets propagated to readline's local prompt variable. */ static void reset_readline_prompt () { char *temp_prompt; if (prompt_string_pointer) { temp_prompt = (*prompt_string_pointer) ? decode_prompt_string (*prompt_string_pointer) : (char *)NULL; if (temp_prompt == 0) { temp_prompt = (char *)xmalloc (1); temp_prompt[0] = '\0'; } FREE (current_readline_prompt); current_readline_prompt = temp_prompt; } } #endif /* READLINE */ #endif /* 0 */ #if defined (HISTORY) /* A list of tokens which can be followed by newlines, but not by semi-colons. When concatenating multiple lines of history, the newline separator for such tokens is replaced with a space. */ static int no_semi_successors[] = { '\n', '{', '(', ')', ';', '&', '|', CASE, DO, ELSE, IF, SEMI_SEMI, THEN, UNTIL, WHILE, AND_AND, OR_OR, IN, 0 }; /* If we are not within a delimited expression, try to be smart about which separators can be semi-colons and which must be newlines. Returns the string that should be added into the history entry. */ char * history_delimiting_chars () { register int i; if (dstack.delimiter_depth != 0) return ("\n"); /* First, handle some special cases. */ /*(*/ /* If we just read `()', assume it's a function definition, and don't add a semicolon. If the token before the `)' was not `(', and we're not in the midst of parsing a case statement, assume it's a parenthesized command and add the semicolon. */ /*)(*/ if (token_before_that == ')') { if (two_tokens_ago == '(') /*)*/ /* function def */ return " "; /* This does not work for subshells inside case statement command lists. It's a suboptimal solution. */ else if (parser_state & PST_CASESTMT) /* case statement pattern */ return " "; else return "; "; /* (...) subshell */ } else if (token_before_that == WORD && two_tokens_ago == FUNCTION) return " "; /* function def using `function name' without `()' */ else if (token_before_that == WORD && two_tokens_ago == FOR) { /* Tricky. `for i\nin ...' should not have a semicolon, but `for i\ndo ...' should. We do what we can. */ for (i = shell_input_line_index; whitespace(shell_input_line[i]); i++) ; if (shell_input_line[i] && shell_input_line[i] == 'i' && shell_input_line[i+1] == 'n') return " "; return ";"; } for (i = 0; no_semi_successors[i]; i++) { if (token_before_that == no_semi_successors[i]) return (" "); } return ("; "); } #endif /* HISTORY */ /* Issue a prompt, or prepare to issue a prompt when the next character is read. */ static void prompt_again () { char *temp_prompt; if (!interactive) /* XXX */ return; ps1_prompt = get_string_value ("PS1"); ps2_prompt = get_string_value ("PS2"); if (!prompt_string_pointer) prompt_string_pointer = &ps1_prompt; temp_prompt = *prompt_string_pointer ? decode_prompt_string (*prompt_string_pointer) : (char *)NULL; if (temp_prompt == 0) { temp_prompt = (char *)xmalloc (1); temp_prompt[0] = '\0'; } current_prompt_string = *prompt_string_pointer; prompt_string_pointer = &ps2_prompt; #if defined (READLINE) if (!no_line_editing) { FREE (current_readline_prompt); current_readline_prompt = temp_prompt; } else #endif /* READLINE */ { FREE (current_decoded_prompt); current_decoded_prompt = temp_prompt; } } int get_current_prompt_level () { return ((current_prompt_string && current_prompt_string == ps2_prompt) ? 2 : 1); } void set_current_prompt_level (x) int x; { prompt_string_pointer = (x == 2) ? &ps2_prompt : &ps1_prompt; current_prompt_string = *prompt_string_pointer; } static void print_prompt () { fprintf (stderr, "%s", current_decoded_prompt); fflush (stderr); } /* Return a string which will be printed as a prompt. The string may contain special characters which are decoded as follows: \a bell (ascii 07) \d the date in Day Mon Date format \e escape (ascii 033) \h the hostname up to the first `.' \H the hostname \j the number of active jobs \l the basename of the shell's tty device name \n CRLF \r CR \s the name of the shell \t the time in 24-hour hh:mm:ss format \T the time in 12-hour hh:mm:ss format \@ the time in 12-hour hh:mm am/pm format \A the time in 24-hour hh:mm format \D{fmt} the result of passing FMT to strftime(3) \u your username \v the version of bash (e.g., 2.00) \V the release of bash, version + patchlevel (e.g., 2.00.0) \w the current working directory \W the last element of $PWD \! the history number of this command \# the command number of this command \$ a $ or a # if you are root \nnn character code nnn in octal \\ a backslash \[ begin a sequence of non-printing chars \] end a sequence of non-printing chars */ #define PROMPT_GROWTH 48 char * decode_prompt_string (string) char *string; { WORD_LIST *list; char *result, *t; struct dstack save_dstack; int last_exit_value; #if defined (PROMPT_STRING_DECODE) int result_size, result_index; int c, n; char *temp, octal_string[4]; struct tm *tm; time_t the_time; char timebuf[128]; char *timefmt; result = (char *)xmalloc (result_size = PROMPT_GROWTH); result[result_index = 0] = 0; temp = (char *)NULL; while (c = *string++) { if (posixly_correct && c == '!') { if (*string == '!') { temp = savestring ("!"); goto add_string; } else { #if !defined (HISTORY) temp = savestring ("1"); #else /* HISTORY */ temp = itos (history_number ()); #endif /* HISTORY */ string--; /* add_string increments string again. */ goto add_string; } } if (c == '\\') { c = *string; switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': strncpy (octal_string, string, 3); octal_string[3] = '\0'; n = read_octal (octal_string); temp = (char *)xmalloc (3); if (n == CTLESC || n == CTLNUL) { temp[0] = CTLESC; temp[1] = n; temp[2] = '\0'; } else if (n == -1) { temp[0] = '\\'; temp[1] = '\0'; } else { temp[0] = n; temp[1] = '\0'; } for (c = 0; n != -1 && c < 3 && ISOCTAL (*string); c++) string++; c = 0; /* tested at add_string: */ goto add_string; case 'd': case 't': case 'T': case '@': case 'A': /* Make the current time/date into a string. */ (void) time (&the_time); tm = localtime (&the_time); if (c == 'd') n = strftime (timebuf, sizeof (timebuf), "%a %b %d", tm); else if (c == 't') n = strftime (timebuf, sizeof (timebuf), "%H:%M:%S", tm); else if (c == 'T') n = strftime (timebuf, sizeof (timebuf), "%I:%M:%S", tm); else if (c == '@') n = strftime (timebuf, sizeof (timebuf), "%I:%M %p", tm); else if (c == 'A') n = strftime (timebuf, sizeof (timebuf), "%H:%M", tm); timebuf[sizeof(timebuf) - 1] = '\0'; temp = savestring (timebuf); goto add_string; case 'D': /* strftime format */ if (string[1] != '{') /* } */ goto not_escape; (void) time (&the_time); tm = localtime (&the_time); string += 2; /* skip { */ timefmt = xmalloc (strlen (string) + 3); for (t = timefmt; *string && *string != '}'; ) *t++ = *string++; *t = '\0'; c = *string; /* tested at add_string */ if (timefmt[0] == '\0') { timefmt[0] = '%'; timefmt[1] = 'X'; /* locale-specific current time */ timefmt[2] = '\0'; } n = strftime (timebuf, sizeof (timebuf), timefmt, tm); free (timefmt); timebuf[sizeof(timebuf) - 1] = '\0'; if (promptvars || posixly_correct) /* Make sure that expand_prompt_string is called with a second argument of Q_DOUBLE_QUOTES if we use this function here. */ temp = sh_backslash_quote_for_double_quotes (timebuf); else temp = savestring (timebuf); goto add_string; case 'n': temp = (char *)xmalloc (3); temp[0] = no_line_editing ? '\n' : '\r'; temp[1] = no_line_editing ? '\0' : '\n'; temp[2] = '\0'; goto add_string; case 's': temp = base_pathname (shell_name); temp = savestring (temp); goto add_string; case 'v': case 'V': temp = (char *)xmalloc (16); if (c == 'v') strcpy (temp, dist_version); else sprintf (temp, "%s.%d", dist_version, patch_level); goto add_string; case 'w': case 'W': { /* Use the value of PWD because it is much more efficient. */ char t_string[PATH_MAX]; int tlen; temp = get_string_value ("PWD"); if (temp == 0) { if (getcwd (t_string, sizeof(t_string)) == 0) { t_string[0] = '.'; tlen = 1; } else tlen = strlen (t_string); } else { tlen = sizeof (t_string) - 1; strncpy (t_string, temp, tlen); } t_string[tlen] = '\0'; #define ROOT_PATH(x) ((x)[0] == '/' && (x)[1] == 0) #define DOUBLE_SLASH_ROOT(x) ((x)[0] == '/' && (x)[1] == '/' && (x)[2] == 0) if (c == 'W') { if (ROOT_PATH (t_string) == 0 && DOUBLE_SLASH_ROOT (t_string) == 0) { t = strrchr (t_string, '/'); if (t) strcpy (t_string, t + 1); } } #undef ROOT_PATH #undef DOUBLE_SLASH_ROOT else /* polite_directory_format is guaranteed to return a string no longer than PATH_MAX - 1 characters. */ strcpy (t_string, polite_directory_format (t_string)); /* If we're going to be expanding the prompt string later, quote the directory name. */ if (promptvars || posixly_correct) /* Make sure that expand_prompt_string is called with a second argument of Q_DOUBLE_QUOTES if we use this function here. */ temp = sh_backslash_quote_for_double_quotes (t_string); else temp = savestring (t_string); goto add_string; } case 'u': if (current_user.user_name == 0) get_current_user_info (); temp = savestring (current_user.user_name); goto add_string; case 'h': case 'H': temp = savestring (current_host_name); if (c == 'h' && (t = (char *)strchr (temp, '.'))) *t = '\0'; goto add_string; case '#': temp = itos (current_command_number); goto add_string; case '!': #if !defined (HISTORY) temp = savestring ("1"); #else /* HISTORY */ temp = itos (history_number ()); #endif /* HISTORY */ goto add_string; case '$': t = temp = (char *)xmalloc (3); if ((promptvars || posixly_correct) && (current_user.euid != 0)) *t++ = '\\'; *t++ = current_user.euid == 0 ? '#' : '$'; *t = '\0'; goto add_string; case 'j': temp = itos (count_all_jobs ()); goto add_string; case 'l': #if defined (HAVE_TTYNAME) temp = (char *)ttyname (fileno (stdin)); t = temp ? base_pathname (temp) : "tty"; temp = savestring (t); #else temp = savestring ("tty"); #endif /* !HAVE_TTYNAME */ goto add_string; #if defined (READLINE) case '[': case ']': temp = (char *)xmalloc (3); temp[0] = '\001'; temp[1] = (c == '[') ? RL_PROMPT_START_IGNORE : RL_PROMPT_END_IGNORE; temp[2] = '\0'; goto add_string; #endif /* READLINE */ case '\\': case 'a': case 'e': case 'r': temp = (char *)xmalloc (2); if (c == 'a') temp[0] = '\07'; else if (c == 'e') temp[0] = '\033'; else if (c == 'r') temp[0] = '\r'; else /* (c == '\\') */ temp[0] = c; temp[1] = '\0'; goto add_string; default: not_escape: temp = (char *)xmalloc (3); temp[0] = '\\'; temp[1] = c; temp[2] = '\0'; add_string: if (c) string++; result = sub_append_string (temp, result, &result_index, &result_size); temp = (char *)NULL; /* Freed in sub_append_string (). */ result[result_index] = '\0'; break; } } else { RESIZE_MALLOCED_BUFFER (result, result_index, 3, result_size, PROMPT_GROWTH); result[result_index++] = c; result[result_index] = '\0'; } } #else /* !PROMPT_STRING_DECODE */ result = savestring (string); #endif /* !PROMPT_STRING_DECODE */ /* Save the delimiter stack and point `dstack' to temp space so any command substitutions in the prompt string won't result in screwing up the parser's quoting state. */ save_dstack = dstack; dstack = temp_dstack; dstack.delimiter_depth = 0; /* Perform variable and parameter expansion and command substitution on the prompt string. */ if (promptvars || posixly_correct) { last_exit_value = last_command_exit_value; list = expand_prompt_string (result, Q_DOUBLE_QUOTES); free (result); result = string_list (list); dispose_words (list); last_command_exit_value = last_exit_value; } else { t = dequote_string (result); free (result); result = t; } dstack = save_dstack; return (result); } /************************************************ * * * ERROR HANDLING * * * ************************************************/ /* Report a syntax error, and restart the parser. Call here for fatal errors. */ int yyerror (msg) const char *msg; { report_syntax_error ((char *)NULL); reset_parser (); return (0); } static char * error_token_from_token (token) int token; { char *t; if (t = find_token_in_alist (token, word_token_alist, 0)) return t; if (t = find_token_in_alist (token, other_token_alist, 0)) return t; t = (char *)NULL; /* This stuff is dicy and needs closer inspection */ switch (current_token) { case WORD: case ASSIGNMENT_WORD: if (yylval.word) t = savestring (yylval.word->word); break; case NUMBER: t = itos (yylval.number); break; case ARITH_CMD: if (yylval.word_list) t = string_list (yylval.word_list); break; case ARITH_FOR_EXPRS: if (yylval.word_list) t = string_list_internal (yylval.word_list, " ; "); break; case COND_CMD: t = (char *)NULL; /* punt */ break; } return t; } static char * error_token_from_text () { char *msg, *t; int token_end, i; t = shell_input_line; i = shell_input_line_index; token_end = 0; msg = (char *)NULL; if (i && t[i] == '\0') i--; while (i && (whitespace (t[i]) || t[i] == '\n')) i--; if (i) token_end = i + 1; while (i && (member (t[i], " \n\t;|&") == 0)) i--; while (i != token_end && (whitespace (t[i]) || t[i] == '\n')) i++; /* Return our idea of the offending token. */ if (token_end || (i == 0 && token_end == 0)) { if (token_end) msg = substring (t, i, token_end); else /* one-character token */ { msg = (char *)xmalloc (2); msg[0] = t[i]; msg[1] = '\0'; } } return (msg); } static void print_offending_line () { char *msg; int token_end; msg = savestring (shell_input_line); token_end = strlen (msg); while (token_end && msg[token_end - 1] == '\n') msg[--token_end] = '\0'; parser_error (line_number, "`%s'", msg); free (msg); } /* Report a syntax error with line numbers, etc. Call here for recoverable errors. If you have a message to print, then place it in MESSAGE, otherwise pass NULL and this will figure out an appropriate message for you. */ static void report_syntax_error (message) char *message; { char *msg; if (message) { parser_error (line_number, "%s", message); if (interactive && EOF_Reached) EOF_Reached = 0; last_command_exit_value = EX_USAGE; return; } /* If the line of input we're reading is not null, try to find the objectionable token. First, try to figure out what token the parser's complaining about by looking at current_token. */ if (current_token != 0 && EOF_Reached == 0 && (msg = error_token_from_token (current_token))) { parser_error (line_number, "syntax error near unexpected token `%s'", msg); free (msg); if (interactive == 0) print_offending_line (); last_command_exit_value = EX_USAGE; return; } /* If looking at the current token doesn't prove fruitful, try to find the offending token by analyzing the text of the input line near the current input line index and report what we find. */ if (shell_input_line && *shell_input_line) { msg = error_token_from_text (); if (msg) { parser_error (line_number, "syntax error near `%s'", msg); free (msg); } /* If not interactive, print the line containing the error. */ if (interactive == 0) print_offending_line (); } else { msg = EOF_Reached ? "syntax error: unexpected end of file" : "syntax error"; parser_error (line_number, "%s", msg); /* When the shell is interactive, this file uses EOF_Reached only for error reporting. Other mechanisms are used to decide whether or not to exit. */ if (interactive && EOF_Reached) EOF_Reached = 0; } last_command_exit_value = EX_USAGE; } /* ??? Needed function. ??? We have to be able to discard the constructs created during parsing. In the case of error, we want to return allocated objects to the memory pool. In the case of no error, we want to throw away the information about where the allocated objects live. (dispose_command () will actually free the command.) */ static void discard_parser_constructs (error_p) int error_p; { } /************************************************ * * * EOF HANDLING * * * ************************************************/ /* Do that silly `type "bye" to exit' stuff. You know, "ignoreeof". */ /* A flag denoting whether or not ignoreeof is set. */ int ignoreeof = 0; /* The number of times that we have encountered an EOF character without another character intervening. When this gets above the limit, the shell terminates. */ int eof_encountered = 0; /* The limit for eof_encountered. */ int eof_encountered_limit = 10; /* If we have EOF as the only input unit, this user wants to leave the shell. If the shell is not interactive, then just leave. Otherwise, if ignoreeof is set, and we haven't done this the required number of times in a row, print a message. */ static void handle_eof_input_unit () { if (interactive) { /* shell.c may use this to decide whether or not to write out the history, among other things. We use it only for error reporting in this file. */ if (EOF_Reached) EOF_Reached = 0; /* If the user wants to "ignore" eof, then let her do so, kind of. */ if (ignoreeof) { if (eof_encountered < eof_encountered_limit) { fprintf (stderr, "Use \"%s\" to leave the shell.\n", login_shell ? "logout" : "exit"); eof_encountered++; /* Reset the parsing state. */ last_read_token = current_token = '\n'; /* Reset the prompt string to be $PS1. */ prompt_string_pointer = (char **)NULL; prompt_again (); return; } } /* In this case EOF should exit the shell. Do it now. */ reset_parser (); exit_builtin ((WORD_LIST *)NULL); } else { /* We don't write history files, etc., for non-interactive shells. */ EOF_Reached = 1; } } /************************************************ * * * STRING PARSING FUNCTIONS * * * ************************************************/ /* It's very important that these two functions treat the characters between ( and ) identically. */ static WORD_LIST parse_string_error; /* Take a string and run it through the shell parser, returning the resultant word list. Used by compound array assignment. */ WORD_LIST * parse_string_to_word_list (s, whom) char *s; const char *whom; { WORD_LIST *wl; int tok, orig_current_token, orig_line_number, orig_input_terminator; int orig_line_count; int old_echo_input, old_expand_aliases; #if defined (HISTORY) int old_remember_on_history, old_history_expansion_inhibited; #endif #if defined (HISTORY) old_remember_on_history = remember_on_history; # if defined (BANG_HISTORY) old_history_expansion_inhibited = history_expansion_inhibited; # endif bash_history_disable (); #endif orig_line_number = line_number; orig_line_count = current_command_line_count; orig_input_terminator = shell_input_line_terminator; old_echo_input = echo_input_at_read; old_expand_aliases = expand_aliases; push_stream (1); last_read_token = WORD; /* WORD to allow reserved words here */ current_command_line_count = 0; echo_input_at_read = expand_aliases = 0; with_input_from_string (s, whom); wl = (WORD_LIST *)NULL; while ((tok = read_token (READ)) != yacc_EOF) { if (tok == '\n' && *bash_input.location.string == '\0') break; if (tok == '\n') /* Allow newlines in compound assignments */ continue; if (tok != WORD && tok != ASSIGNMENT_WORD) { line_number = orig_line_number + line_number - 1; orig_current_token = current_token; current_token = tok; yyerror ((char *)NULL); /* does the right thing */ current_token = orig_current_token; if (wl) dispose_words (wl); wl = &parse_string_error; break; } wl = make_word_list (yylval.word, wl); } last_read_token = '\n'; pop_stream (); #if defined (HISTORY) remember_on_history = old_remember_on_history; # if defined (BANG_HISTORY) history_expansion_inhibited = old_history_expansion_inhibited; # endif /* BANG_HISTORY */ #endif /* HISTORY */ echo_input_at_read = old_echo_input; expand_aliases = old_expand_aliases; current_command_line_count = orig_line_count; shell_input_line_terminator = orig_input_terminator; if (wl == &parse_string_error) { last_command_exit_value = EXECUTION_FAILURE; if (interactive_shell == 0 && posixly_correct) jump_to_top_level (FORCE_EOF); else jump_to_top_level (DISCARD); } return (REVERSE_LIST (wl, WORD_LIST *)); } static char * parse_compound_assignment (retlenp) int *retlenp; { WORD_LIST *wl, *rl; int tok, orig_line_number, orig_token_size; char *saved_token, *ret; saved_token = token; orig_token_size = token_buffer_size; orig_line_number = line_number; last_read_token = WORD; /* WORD to allow reserved words here */ token = (char *)NULL; token_buffer_size = 0; wl = (WORD_LIST *)NULL; /* ( */ while ((tok = read_token (READ)) != ')') { if (tok == '\n') /* Allow newlines in compound assignments */ continue; if (tok != WORD && tok != ASSIGNMENT_WORD) { current_token = tok; /* for error reporting */ if (tok == yacc_EOF) /* ( */ parser_error (orig_line_number, "unexpected EOF while looking for matching `)'"); else yyerror ((char *)NULL); /* does the right thing */ if (wl) dispose_words (wl); wl = &parse_string_error; break; } wl = make_word_list (yylval.word, wl); } FREE (token); token = saved_token; token_buffer_size = orig_token_size; if (wl == &parse_string_error) { last_command_exit_value = EXECUTION_FAILURE; last_read_token = '\n'; /* XXX */ if (interactive_shell == 0 && posixly_correct) jump_to_top_level (FORCE_EOF); else jump_to_top_level (DISCARD); } last_read_token = WORD; if (wl) { rl = REVERSE_LIST (wl, WORD_LIST *); ret = string_list (rl); dispose_words (rl); } else ret = (char *)NULL; if (retlenp) *retlenp = (ret && *ret) ? strlen (ret) : 0; return ret; } /************************************************ * * * MULTIBYTE CHARACTER HANDLING * * * ************************************************/ #if defined (HANDLE_MULTIBYTE) static void set_line_mbstate () { int i, previ, len; mbstate_t mbs, prevs; size_t mbclen; if (shell_input_line == NULL) return; len = strlen (shell_input_line); /* XXX - shell_input_line_len ? */ FREE (shell_input_line_property); shell_input_line_property = (char *)xmalloc (len + 1); memset (&prevs, '\0', sizeof (mbstate_t)); for (i = previ = 0; i < len; i++) { mbs = prevs; if (shell_input_line[i] == EOF) { int j; for (j = i; j < len; j++) shell_input_line_property[j] = 1; break; } mbclen = mbrlen (shell_input_line + previ, i - previ + 1, &mbs); if (mbclen == 1 || mbclen == (size_t)-1) { mbclen = 1; previ = i + 1; } else if (mbclen == (size_t)-2) mbclen = 0; else if (mbclen > 1) { mbclen = 0; previ = i + 1; prevs = mbs; } else { /* mbrlen doesn't return any other values */ } shell_input_line_property[i] = mbclen; } } #endif /* HANDLE_MULTIBYTE */
/* general.c -- Stuff that is used by all files. */ /* Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #include "bashtypes.h" #ifndef _MINIX # include <sys/param.h> #endif #include "posixstat.h" #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "filecntl.h" #include "bashansi.h" #include <stdio.h> #include "chartypes.h" #include <errno.h> #include "shell.h" #include <tilde/tilde.h> #if !defined (errno) extern int errno; #endif /* !errno */ extern int expand_aliases; extern int interrupt_immediately; extern int interactive_comments; extern int check_hashed_filenames; extern int source_uses_path; extern int source_searches_cwd; static char *bash_special_tilde_expansions __P((char *)); static int unquoted_tilde_word __P((const char *)); static void initialize_group_array __P((void)); /* A standard error message to use when getcwd() returns NULL. */ char *bash_getcwd_errstr = "getcwd: cannot access parent directories"; /* Do whatever is necessary to initialize `Posix mode'. */ void posix_initialize (on) int on; { /* Things that should be turned on when posix mode is enabled. */ if (on != 0) { interactive_comments = source_uses_path = expand_aliases = 1; } /* Things that should be turned on when posix mode is disabled. */ if (on == 0) { source_searches_cwd = 1; expand_aliases = interactive_shell; } } /* **************************************************************** */ /* */ /* Functions to convert to and from and display non-standard types */ /* */ /* **************************************************************** */ #if defined (RLIMTYPE) RLIMTYPE string_to_rlimtype (s) char *s; { RLIMTYPE ret; int neg; ret = 0; neg = 0; while (s && *s && whitespace (*s)) s++; if (*s == '-' || *s == '+') { neg = *s == '-'; s++; } for ( ; s && *s && DIGIT (*s); s++) ret = (ret * 10) + TODIGIT (*s); return (neg ? -ret : ret); } void print_rlimtype (n, addnl) RLIMTYPE n; int addnl; { char s[INT_STRLEN_BOUND (RLIMTYPE) + 1], *p; p = s + sizeof(s); *--p = '\0'; if (n < 0) { do *--p = '0' - n % 10; while ((n /= 10) != 0); *--p = '-'; } else { do *--p = '0' + n % 10; while ((n /= 10) != 0); } printf ("%s%s", p, addnl ? "\n" : ""); } #endif /* RLIMTYPE */ /* **************************************************************** */ /* */ /* Input Validation Functions */ /* */ /* **************************************************************** */ /* Return non-zero if all of the characters in STRING are digits. */ int all_digits (string) char *string; { register char *s; for (s = string; *s; s++) if (DIGIT (*s) == 0) return (0); return (1); } /* Return non-zero if the characters pointed to by STRING constitute a valid number. Stuff the converted number into RESULT if RESULT is not null. */ int legal_number (string, result) char *string; intmax_t *result; { intmax_t value; char *ep; if (result) *result = 0; errno = 0; value = strtoimax (string, &ep, 10); if (errno) return 0; /* errno is set on overflow or underflow */ /* Skip any trailing whitespace, since strtoimax does not. */ while (whitespace (*ep)) ep++; /* If *string is not '\0' but *ep is '\0' on return, the entire string is valid. */ if (string && *string && *ep == '\0') { if (result) *result = value; /* The SunOS4 implementation of strtol() will happily ignore overflow conditions, so this cannot do overflow correctly on those systems. */ return 1; } return (0); } /* Return 1 if this token is a legal shell `identifier'; that is, it consists solely of letters, digits, and underscores, and does not begin with a digit. */ int legal_identifier (name) char *name; { register char *s; unsigned char c; if (!name || !(c = *name) || (legal_variable_starter (c) == 0)) return (0); for (s = name + 1; (c = *s) != 0; s++) { if (legal_variable_char (c) == 0) return (0); } return (1); } /* Make sure that WORD is a valid shell identifier, i.e. does not contain a dollar sign, nor is quoted in any way. Nor does it consist of all digits. If CHECK_WORD is non-zero, the word is checked to ensure that it consists of only letters, digits, and underscores. */ int check_identifier (word, check_word) WORD_DESC *word; int check_word; { if ((word->flags & (W_HASDOLLAR|W_QUOTED)) || all_digits (word->word)) { internal_error ("`%s': not a valid identifier", word->word); return (0); } else if (check_word && legal_identifier (word->word) == 0) { internal_error ("`%s': not a valid identifier", word->word); return (0); } else return (1); } /* Returns non-zero if STRING is an assignment statement. The returned value is the index of the `=' sign. */ int assignment (string) const char *string; { register unsigned char c; register int newi, indx; c = string[indx = 0]; if (legal_variable_starter (c) == 0) return (0); while (c = string[indx]) { /* The following is safe. Note that '=' at the start of a word is not an assignment statement. */ if (c == '=') return (indx); #if defined (ARRAY_VARS) if (c == '[') { newi = skipsubscript (string, indx); if (string[newi++] != ']') return (0); return ((string[newi] == '=') ? newi : 0); } #endif /* ARRAY_VARS */ /* Variable names in assignment statements may contain only letters, digits, and `_'. */ if (legal_variable_char (c) == 0) return (0); indx++; } return (0); } /* **************************************************************** */ /* */ /* Functions to manage files and file descriptors */ /* */ /* **************************************************************** */ /* A function to unset no-delay mode on a file descriptor. Used in shell.c to unset it on the fd passed as stdin. Should be called on stdin if readline gets an EAGAIN or EWOULDBLOCK when trying to read input. */ #if !defined (O_NDELAY) # if defined (FNDELAY) # define O_NDELAY FNDELAY # endif #endif /* O_NDELAY */ /* Make sure no-delay mode is not set on file descriptor FD. */ int sh_unset_nodelay_mode (fd) int fd; { int flags, bflags; if ((flags = fcntl (fd, F_GETFL, 0)) < 0) return -1; bflags = 0; /* This is defined to O_NDELAY in filecntl.h if O_NONBLOCK is not present and O_NDELAY is defined. */ #ifdef O_NONBLOCK bflags |= O_NONBLOCK; #endif #ifdef O_NDELAY bflags |= O_NDELAY; #endif if (flags & bflags) { flags &= ~bflags; return (fcntl (fd, F_SETFL, flags)); } return 0; } /* Return 1 if file descriptor FD is valid; 0 otherwise. */ int sh_validfd (fd) int fd; { return (fcntl (fd, F_GETFD, 0) >= 0); } /* There is a bug in the NeXT 2.1 rlogind that causes opens of /dev/tty to fail. */ #if defined (__BEOS__) /* On BeOS, opening in non-blocking mode exposes a bug in BeOS, so turn it into a no-op. This should probably go away in the future. */ # undef O_NONBLOCK # define O_NONBLOCK 0 #endif /* __BEOS__ */ void check_dev_tty () { int tty_fd; char *tty; tty_fd = open ("/dev/tty", O_RDWR|O_NONBLOCK); if (tty_fd < 0) { tty = (char *)ttyname (fileno (stdin)); if (tty == 0) return; tty_fd = open (tty, O_RDWR|O_NONBLOCK); } close (tty_fd); } /* Return 1 if PATH1 and PATH2 are the same file. This is kind of expensive. If non-NULL STP1 and STP2 point to stat structures corresponding to PATH1 and PATH2, respectively. */ int same_file (path1, path2, stp1, stp2) char *path1, *path2; struct stat *stp1, *stp2; { struct stat st1, st2; if (stp1 == NULL) { if (stat (path1, &st1) != 0) return (0); stp1 = &st1; } if (stp2 == NULL) { if (stat (path2, &st2) != 0) return (0); stp2 = &st2; } return ((stp1->st_dev == stp2->st_dev) && (stp1->st_ino == stp2->st_ino)); } /* Move FD to a number close to the maximum number of file descriptors allowed in the shell process, to avoid the user stepping on it with redirection and causing us extra work. If CHECK_NEW is non-zero, we check whether or not the file descriptors are in use before duplicating FD onto them. MAXFD says where to start checking the file descriptors. If it's less than 20, we get the maximum value available from getdtablesize(2). */ int move_to_high_fd (fd, check_new, maxfd) int fd, check_new, maxfd; { int script_fd, nfds, ignore; if (maxfd < 20) { nfds = getdtablesize (); if (nfds <= 0) nfds = 20; if (nfds > HIGH_FD_MAX) nfds = HIGH_FD_MAX; /* reasonable maximum */ } else nfds = maxfd; for (nfds--; check_new && nfds > 3; nfds--) if (fcntl (nfds, F_GETFD, &ignore) == -1) break; if (nfds > 3 && fd != nfds && (script_fd = dup2 (fd, nfds)) != -1) { if (check_new == 0 || fd != fileno (stderr)) /* don't close stderr */ close (fd); return (script_fd); } /* OK, we didn't find one less than our artificial maximum; return the original file descriptor. */ return (fd); } /* Return non-zero if the characters from SAMPLE are not all valid characters to be found in the first line of a shell script. We check up to the first newline, or SAMPLE_LEN, whichever comes first. All of the characters must be printable or whitespace. */ int check_binary_file (sample, sample_len) char *sample; int sample_len; { register int i; unsigned char c; for (i = 0; i < sample_len; i++) { c = sample[i]; if (c == '\n') return (0); if (ISSPACE (c) == 0 && ISPRINT (c) == 0) return (1); } return (0); } /* **************************************************************** */ /* */ /* Functions to manipulate pathnames */ /* */ /* **************************************************************** */ /* Turn STRING (a pathname) into an absolute pathname, assuming that DOT_PATH contains the symbolic location of `.'. This always returns a new string, even if STRING was an absolute pathname to begin with. */ char * make_absolute (string, dot_path) char *string, *dot_path; { char *result; if (dot_path == 0 || ABSPATH(string)) result = savestring (string); else result = sh_makepath (dot_path, string, 0); return (result); } /* Return 1 if STRING contains an absolute pathname, else 0. Used by `cd' to decide whether or not to look up a directory name in $CDPATH. */ int absolute_pathname (string) const char *string; { if (string == 0 || *string == '\0') return (0); if (ABSPATH(string)) return (1); if (string[0] == '.' && PATHSEP(string[1])) /* . and ./ */ return (1); if (string[0] == '.' && string[1] == '.' && PATHSEP(string[2])) /* .. and ../ */ return (1); return (0); } /* Return 1 if STRING is an absolute program name; it is absolute if it contains any slashes. This is used to decide whether or not to look up through $PATH. */ int absolute_program (string) const char *string; { return ((char *)xstrchr (string, '/') != (char *)NULL); } /* Return the `basename' of the pathname in STRING (the stuff after the last '/'). If STRING is not a full pathname, simply return it. */ char * base_pathname (string) char *string; { char *p; if (absolute_pathname (string) == 0) return (string); p = (char *)strrchr (string, '/'); return (p ? ++p : string); } /* Return the full pathname of FILE. Easy. Filenames that begin with a '/' are returned as themselves. Other filenames have the current working directory prepended. A new string is returned in either case. */ char * full_pathname (file) char *file; { char *ret; file = (*file == '~') ? bash_tilde_expand (file, 0) : savestring (file); if (ABSPATH(file)) return (file); ret = sh_makepath ((char *)NULL, file, (MP_DOCWD|MP_RMDOT)); free (file); return (ret); } /* A slightly related function. Get the prettiest name of this directory possible. */ static char tdir[PATH_MAX]; /* Return a pretty pathname. If the first part of the pathname is the same as $HOME, then replace that with `~'. */ char * polite_directory_format (name) char *name; { char *home; int l; home = get_string_value ("HOME"); l = home ? strlen (home) : 0; if (l > 1 && strncmp (home, name, l) == 0 && (!name[l] || name[l] == '/')) { strncpy (tdir + 1, name + l, sizeof(tdir) - 2); tdir[0] = '~'; tdir[sizeof(tdir) - 1] = '\0'; return (tdir); } else return (name); } /* Given a string containing units of information separated by colons, return the next one pointed to by (P_INDEX), or NULL if there are no more. Advance (P_INDEX) to the character after the colon. */ char * extract_colon_unit (string, p_index) char *string; int *p_index; { int i, start, len; char *value; if (string == 0) return (string); len = strlen (string); if (*p_index >= len) return ((char *)NULL); i = *p_index; /* Each call to this routine leaves the index pointing at a colon if there is more to the path. If I is > 0, then increment past the `:'. If I is 0, then the path has a leading colon. Trailing colons are handled OK by the `else' part of the if statement; an empty string is returned in that case. */ if (i && string[i] == ':') i++; for (start = i; string[i] && string[i] != ':'; i++) ; *p_index = i; if (i == start) { if (string[i]) (*p_index)++; /* Return "" in the case of a trailing `:'. */ value = (char *)xmalloc (1); value[0] = '\0'; } else value = substring (string, start, i); return (value); } /* **************************************************************** */ /* */ /* Tilde Initialization and Expansion */ /* */ /* **************************************************************** */ #if defined (PUSHD_AND_POPD) extern char *get_dirstack_from_string __P((char *)); #endif static char **bash_tilde_prefixes; static char **bash_tilde_suffixes; /* If tilde_expand hasn't been able to expand the text, perhaps it is a special shell expansion. This function is installed as the tilde_expansion_preexpansion_hook. It knows how to expand ~- and ~+. If PUSHD_AND_POPD is defined, ~[+-]N expands to directories from the directory stack. */ static char * bash_special_tilde_expansions (text) char *text; { char *result; result = (char *)NULL; if (text[0] == '+' && text[1] == '\0') result = get_string_value ("PWD"); else if (text[0] == '-' && text[1] == '\0') result = get_string_value ("OLDPWD"); #if defined (PUSHD_AND_POPD) else if (DIGIT (*text) || ((*text == '+' || *text == '-') && DIGIT (text[1]))) result = get_dirstack_from_string (text); #endif return (result ? savestring (result) : (char *)NULL); } /* Initialize the tilde expander. In Bash, we handle `~-' and `~+', as well as handling special tilde prefixes; `:~" and `=~' are indications that we should do tilde expansion. */ void tilde_initialize () { static int times_called = 0; /* Tell the tilde expander that we want a crack first. */ tilde_expansion_preexpansion_hook = bash_special_tilde_expansions; /* Tell the tilde expander about special strings which start a tilde expansion, and the special strings that end one. Only do this once. tilde_initialize () is called from within bashline_reinitialize (). */ if (times_called++ == 0) { bash_tilde_prefixes = strvec_create (3); bash_tilde_prefixes[0] = "=~"; bash_tilde_prefixes[1] = ":~"; bash_tilde_prefixes[2] = (char *)NULL; tilde_additional_prefixes = bash_tilde_prefixes; bash_tilde_suffixes = strvec_create (3); bash_tilde_suffixes[0] = ":"; bash_tilde_suffixes[1] = "=~"; /* XXX - ?? */ bash_tilde_suffixes[2] = (char *)NULL; tilde_additional_suffixes = bash_tilde_suffixes; } } /* POSIX.2, 3.6.1: A tilde-prefix consists of an unquoted tilde character at the beginning of the word, followed by all of the characters preceding the first unquoted slash in the word, or all the characters in the word if there is no slash...If none of the characters in the tilde-prefix are quoted, the characters in the tilde-prefix following the tilde shell be treated as a possible login name. */ #define TILDE_END(c) ((c) == '\0' || (c) == '/' || (c) == ':') static int unquoted_tilde_word (s) const char *s; { const char *r; for (r = s; TILDE_END(*r) == 0; r++) { switch (*r) { case '\\': case '\'': case '"': return 0; } } return 1; } /* Tilde-expand S by running it through the tilde expansion library. ASSIGN_P is 1 if this is a variable assignment, so the alternate tilde prefixes should be enabled (`=~' and `:~', see above). */ char * bash_tilde_expand (s, assign_p) const char *s; int assign_p; { int old_immed, r; char *ret; old_immed = interrupt_immediately; interrupt_immediately = 1; tilde_additional_prefixes = assign_p ? bash_tilde_prefixes : (char **)0; r = (*s == '~') ? unquoted_tilde_word (s) : 1; ret = r ? tilde_expand (s) : savestring (s); interrupt_immediately = old_immed; return (ret); } /* **************************************************************** */ /* */ /* Functions to manipulate and search the group list */ /* */ /* **************************************************************** */ static int ngroups, maxgroups; /* The set of groups that this user is a member of. */ static GETGROUPS_T *group_array = (GETGROUPS_T *)NULL; #if !defined (NOGROUP) # define NOGROUP (gid_t) -1 #endif static void initialize_group_array () { register int i; if (maxgroups == 0) maxgroups = getmaxgroups (); ngroups = 0; group_array = (GETGROUPS_T *)xrealloc (group_array, maxgroups * sizeof (GETGROUPS_T)); #if defined (HAVE_GETGROUPS) ngroups = getgroups (maxgroups, group_array); #endif /* If getgroups returns nothing, or the OS does not support getgroups(), make sure the groups array includes at least the current gid. */ if (ngroups == 0) { group_array[0] = current_user.gid; ngroups = 1; } /* If the primary group is not in the groups array, add it as group_array[0] and shuffle everything else up 1, if there's room. */ for (i = 0; i < ngroups; i++) if (current_user.gid == (gid_t)group_array[i]) break; if (i == ngroups && ngroups < maxgroups) { for (i = ngroups; i > 0; i--) group_array[i] = group_array[i - 1]; group_array[0] = current_user.gid; ngroups++; } /* If the primary group is not group_array[0], swap group_array[0] and whatever the current group is. The vast majority of systems should not need this; a notable exception is Linux. */ if (group_array[0] != current_user.gid) { for (i = 0; i < ngroups; i++) if (group_array[i] == current_user.gid) break; if (i < ngroups) { group_array[i] = group_array[0]; group_array[0] = current_user.gid; } } } /* Return non-zero if GID is one that we have in our groups list. */ int #if defined (__STDC__) || defined ( _MINIX) group_member (gid_t gid) #else group_member (gid) gid_t gid; #endif /* !__STDC__ && !_MINIX */ { #if defined (HAVE_GETGROUPS) register int i; #endif /* Short-circuit if possible, maybe saving a call to getgroups(). */ if (gid == current_user.gid || gid == current_user.egid) return (1); #if defined (HAVE_GETGROUPS) if (ngroups == 0) initialize_group_array (); /* In case of error, the user loses. */ if (ngroups <= 0) return (0); /* Search through the list looking for GID. */ for (i = 0; i < ngroups; i++) if (gid == (gid_t)group_array[i]) return (1); #endif return (0); } char ** get_group_list (ngp) int *ngp; { static char **group_vector = (char **)NULL; register int i; if (group_vector) { if (ngp) *ngp = ngroups; return group_vector; } if (ngroups == 0) initialize_group_array (); if (ngroups <= 0) { if (ngp) *ngp = 0; return (char **)NULL; } group_vector = strvec_create (ngroups); for (i = 0; i < ngroups; i++) group_vector[i] = itos (group_array[i]); if (ngp) *ngp = ngroups; return group_vector; } int * get_group_array (ngp) int *ngp; { int i; static int *group_iarray = (int *)NULL; if (group_iarray) { if (ngp) *ngp = ngroups; return (group_iarray); } if (ngroups == 0) initialize_group_array (); if (ngroups <= 0) { if (ngp) *ngp = 0; return (int *)NULL; } group_iarray = (int *)xmalloc (ngroups * sizeof (int)); for (i = 0; i < ngroups; i++) group_iarray[i] = (int)group_array[i]; if (ngp) *ngp = ngroups; return group_iarray; }
/* make_cmd.c -- Functions for making instances of the various parser constructs. */ /* Copyright (C) 1989-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #include <stdio.h> #include "bashtypes.h" #ifndef _MINIX # include <sys/file.h> #endif #include "filecntl.h" #include "bashansi.h" #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "syntax.h" #include "command.h" #include "general.h" #include "error.h" #include "flags.h" #include "make_cmd.h" #include "dispose_cmd.h" #include "variables.h" #include "subst.h" #include "input.h" #include "ocache.h" #include "externs.h" #if defined (JOB_CONTROL) #include "jobs.h" #endif #include "shmbutil.h" extern int line_number, current_command_line_count; extern int last_command_exit_value; /* Object caching */ sh_obj_cache_t wdcache = {0, 0, 0}; sh_obj_cache_t wlcache = {0, 0, 0}; #define WDCACHESIZE 60 #define WLCACHESIZE 60 static COMMAND *make_for_or_select __P((enum command_type, WORD_DESC *, WORD_LIST *, COMMAND *)); #if defined (ARITH_FOR_COMMAND) static WORD_LIST *make_arith_for_expr __P((char *)); #endif static COMMAND *make_until_or_while __P((enum command_type, COMMAND *, COMMAND *)); void cmd_init () { ocache_create (wdcache, WORD_DESC, WDCACHESIZE); ocache_create (wlcache, WORD_LIST, WLCACHESIZE); } WORD_DESC * make_bare_word (string) const char *string; { WORD_DESC *temp; #if 0 temp = (WORD_DESC *)xmalloc (sizeof (WORD_DESC)); #else ocache_alloc (wdcache, WORD_DESC, temp); #endif if (*string) temp->word = savestring (string); else { temp->word = (char *)xmalloc (1); temp->word[0] = '\0'; } temp->flags = 0; return (temp); } WORD_DESC * make_word_flags (w, string) WORD_DESC *w; const char *string; { register int i; size_t slen; DECLARE_MBSTATE; i = 0; slen = strlen (string); while (i < slen) { switch (string[i]) { case '$': w->flags |= W_HASDOLLAR; break; case '\\': break; /* continue the loop */ case '\'': case '`': case '"': w->flags |= W_QUOTED; break; } ADVANCE_CHAR (string, slen, i); } return (w); } WORD_DESC * make_word (string) const char *string; { WORD_DESC *temp; temp = make_bare_word (string); return (make_word_flags (temp, string)); } WORD_DESC * make_word_from_token (token) int token; { char tokenizer[2]; tokenizer[0] = token; tokenizer[1] = '\0'; return (make_word (tokenizer)); } WORD_LIST * make_word_list (word, wlink) WORD_DESC *word; WORD_LIST *wlink; { WORD_LIST *temp; #if 0 temp = (WORD_LIST *)xmalloc (sizeof (WORD_LIST)); #else ocache_alloc (wlcache, WORD_LIST, temp); #endif temp->word = word; temp->next = wlink; return (temp); } COMMAND * make_command (type, pointer) enum command_type type; SIMPLE_COM *pointer; { COMMAND *temp; temp = (COMMAND *)xmalloc (sizeof (COMMAND)); temp->type = type; temp->value.Simple = pointer; temp->value.Simple->flags = temp->flags = 0; temp->redirects = (REDIRECT *)NULL; return (temp); } COMMAND * command_connect (com1, com2, connector) COMMAND *com1, *com2; int connector; { CONNECTION *temp; temp = (CONNECTION *)xmalloc (sizeof (CONNECTION)); temp->connector = connector; temp->first = com1; temp->second = com2; return (make_command (cm_connection, (SIMPLE_COM *)temp)); } static COMMAND * make_for_or_select (type, name, map_list, action) enum command_type type; WORD_DESC *name; WORD_LIST *map_list; COMMAND *action; { FOR_COM *temp; temp = (FOR_COM *)xmalloc (sizeof (FOR_COM)); temp->flags = 0; temp->name = name; temp->map_list = map_list; temp->action = action; return (make_command (type, (SIMPLE_COM *)temp)); } COMMAND * make_for_command (name, map_list, action) WORD_DESC *name; WORD_LIST *map_list; COMMAND *action; { return (make_for_or_select (cm_for, name, map_list, action)); } COMMAND * make_select_command (name, map_list, action) WORD_DESC *name; WORD_LIST *map_list; COMMAND *action; { #if defined (SELECT_COMMAND) return (make_for_or_select (cm_select, name, map_list, action)); #else last_command_exit_value = 2; return ((COMMAND *)NULL); #endif } #if defined (ARITH_FOR_COMMAND) static WORD_LIST * make_arith_for_expr (s) char *s; { WORD_LIST *result; if (s == 0 || *s == '\0') return ((WORD_LIST *)NULL); result = make_word_list (make_word (s), (WORD_LIST *)NULL); return result; } #endif COMMAND * make_arith_for_command (exprs, action, lineno) WORD_LIST *exprs; COMMAND *action; int lineno; { #if defined (ARITH_FOR_COMMAND) ARITH_FOR_COM *temp; WORD_LIST *init, *test, *step; char *s, *t, *start; int nsemi; init = test = step = (WORD_LIST *)NULL; /* Parse the string into the three component sub-expressions. */ start = t = s = exprs->word->word; for (nsemi = 0; ;) { /* skip whitespace at the start of each sub-expression. */ while (whitespace (*s)) s++; start = s; /* skip to the semicolon or EOS */ while (*s && *s != ';') s++; t = (s > start) ? substring (start, 0, s - start) : (char *)NULL; nsemi++; switch (nsemi) { case 1: init = make_arith_for_expr (t); break; case 2: test = make_arith_for_expr (t); break; case 3: step = make_arith_for_expr (t); break; } FREE (t); if (*s == '\0') break; s++; /* skip over semicolon */ } if (nsemi != 3) { if (nsemi < 3) parser_error (lineno, "syntax error: arithmetic expression required"); else parser_error (lineno, "syntax error: `;' unexpected"); parser_error (lineno, "syntax error: `((%s))'", exprs->word->word); last_command_exit_value = 2; return ((COMMAND *)NULL); } temp = (ARITH_FOR_COM *)xmalloc (sizeof (ARITH_FOR_COM)); temp->flags = 0; temp->line = lineno; temp->init = init ? init : make_arith_for_expr ("1"); temp->test = test ? test : make_arith_for_expr ("1"); temp->step = step ? step : make_arith_for_expr ("1"); temp->action = action; return (make_command (cm_arith_for, (SIMPLE_COM *)temp)); #else last_command_exit_value = 2; return ((COMMAND *)NULL); #endif /* ARITH_FOR_COMMAND */ } COMMAND * make_group_command (command) COMMAND *command; { GROUP_COM *temp; temp = (GROUP_COM *)xmalloc (sizeof (GROUP_COM)); temp->command = command; return (make_command (cm_group, (SIMPLE_COM *)temp)); } COMMAND * make_case_command (word, clauses) WORD_DESC *word; PATTERN_LIST *clauses; { CASE_COM *temp; temp = (CASE_COM *)xmalloc (sizeof (CASE_COM)); temp->flags = 0; temp->word = word; temp->clauses = REVERSE_LIST (clauses, PATTERN_LIST *); return (make_command (cm_case, (SIMPLE_COM *)temp)); } PATTERN_LIST * make_pattern_list (patterns, action) WORD_LIST *patterns; COMMAND *action; { PATTERN_LIST *temp; temp = (PATTERN_LIST *)xmalloc (sizeof (PATTERN_LIST)); temp->patterns = REVERSE_LIST (patterns, WORD_LIST *); temp->action = action; temp->next = NULL; return (temp); } COMMAND * make_if_command (test, true_case, false_case) COMMAND *test, *true_case, *false_case; { IF_COM *temp; temp = (IF_COM *)xmalloc (sizeof (IF_COM)); temp->flags = 0; temp->test = test; temp->true_case = true_case; temp->false_case = false_case; return (make_command (cm_if, (SIMPLE_COM *)temp)); } static COMMAND * make_until_or_while (which, test, action) enum command_type which; COMMAND *test, *action; { WHILE_COM *temp; temp = (WHILE_COM *)xmalloc (sizeof (WHILE_COM)); temp->flags = 0; temp->test = test; temp->action = action; return (make_command (which, (SIMPLE_COM *)temp)); } COMMAND * make_while_command (test, action) COMMAND *test, *action; { return (make_until_or_while (cm_while, test, action)); } COMMAND * make_until_command (test, action) COMMAND *test, *action; { return (make_until_or_while (cm_until, test, action)); } COMMAND * make_arith_command (exp) WORD_LIST *exp; { #if defined (DPAREN_ARITHMETIC) COMMAND *command; ARITH_COM *temp; command = (COMMAND *)xmalloc (sizeof (COMMAND)); command->value.Arith = temp = (ARITH_COM *)xmalloc (sizeof (ARITH_COM)); temp->flags = 0; temp->line = line_number; temp->exp = exp; command->type = cm_arith; command->redirects = (REDIRECT *)NULL; command->flags = 0; return (command); #else last_command_exit_value = 2; return ((COMMAND *)NULL); #endif } #if defined (COND_COMMAND) struct cond_com * make_cond_node (type, op, left, right) int type; WORD_DESC *op; struct cond_com *left, *right; { COND_COM *temp; temp = (COND_COM *)xmalloc (sizeof (COND_COM)); temp->flags = 0; temp->line = line_number; temp->type = type; temp->op = op; temp->left = left; temp->right = right; return (temp); } #endif COMMAND * make_cond_command (cond_node) COND_COM *cond_node; { #if defined (COND_COMMAND) COMMAND *command; command = (COMMAND *)xmalloc (sizeof (COMMAND)); command->value.Cond = cond_node; command->type = cm_cond; command->redirects = (REDIRECT *)NULL; command->flags = 0; command->line = cond_node ? cond_node->line : 0; return (command); #else last_command_exit_value = 2; return ((COMMAND *)NULL); #endif } COMMAND * make_bare_simple_command () { COMMAND *command; SIMPLE_COM *temp; command = (COMMAND *)xmalloc (sizeof (COMMAND)); command->value.Simple = temp = (SIMPLE_COM *)xmalloc (sizeof (SIMPLE_COM)); temp->flags = 0; temp->line = line_number; temp->words = (WORD_LIST *)NULL; temp->redirects = (REDIRECT *)NULL; command->type = cm_simple; command->redirects = (REDIRECT *)NULL; command->flags = 0; return (command); } /* Return a command which is the connection of the word or redirection in ELEMENT, and the command * or NULL in COMMAND. */ COMMAND * make_simple_command (element, command) ELEMENT element; COMMAND *command; { /* If we are starting from scratch, then make the initial command structure. Also note that we have to fill in all the slots, since malloc doesn't return zeroed space. */ if (!command) command = make_bare_simple_command (); if (element.word) command->value.Simple->words = make_word_list (element.word, command->value.Simple->words); else { REDIRECT *r = element.redirect; /* Due to the way <> is implemented, there may be more than a single redirection in element.redirect. We just follow the chain as far as it goes, and hook onto the end. */ while (r->next) r = r->next; r->next = command->value.Simple->redirects; command->value.Simple->redirects = element.redirect; } return (command); } /* Because we are Bourne compatible, we read the input for this << or <<- redirection now, from wherever input is coming from. We store the input read into a WORD_DESC. Replace the text of the redirectee.word with the new input text. If <<- is on, then remove leading TABS from each line. */ void make_here_document (temp) REDIRECT *temp; { int kill_leading, redir_len; char *redir_word, *document, *full_line; int document_index, document_size, delim_unquoted; if (temp->instruction != r_deblank_reading_until && temp->instruction != r_reading_until) { internal_error ("make_here_document: bad instruction type %d", temp->instruction); return; } kill_leading = temp->instruction == r_deblank_reading_until; document = (char *)NULL; document_index = document_size = 0; /* Quote removal is the only expansion performed on the delimiter for here documents, making it an extremely special case. */ redir_word = string_quote_removal (temp->redirectee.filename->word, 0); /* redirection_expand will return NULL if the expansion results in multiple words or no words. Check for that here, and just abort this here document if it does. */ if (redir_word) redir_len = strlen (redir_word); else { temp->here_doc_eof = (char *)xmalloc (1); temp->here_doc_eof[0] = '\0'; goto document_done; } free (temp->redirectee.filename->word); temp->here_doc_eof = redir_word; /* Read lines from wherever lines are coming from. For each line read, if kill_leading, then kill the leading tab characters. If the line matches redir_word exactly, then we have manufactured the document. Otherwise, add the line to the list of lines in the document. */ /* If the here-document delimiter was quoted, the lines should be read verbatim from the input. If it was not quoted, we need to perform backslash-quoted newline removal. */ delim_unquoted = (temp->redirectee.filename->flags & W_QUOTED) == 0; while (full_line = read_secondary_line (delim_unquoted)) { register char *line; int len; line = full_line; line_number++; if (kill_leading && *line) { /* Hack: To be compatible with some Bourne shells, we check the word before stripping the whitespace. This is a hack, though. */ if (STREQN (line, redir_word, redir_len) && line[redir_len] == '\n') goto document_done; while (*line == '\t') line++; } if (*line == 0) continue; if (STREQN (line, redir_word, redir_len) && line[redir_len] == '\n') goto document_done; len = strlen (line); if (len + document_index >= document_size) { document_size = document_size ? 2 * (document_size + len) : len + 2; document = (char *)xrealloc (document, document_size); } /* len is guaranteed to be > 0 because of the check for line being an empty string before the call to strlen. */ FASTCOPY (line, document + document_index, len); document_index += len; } document_done: if (document) document[document_index] = '\0'; else { document = (char *)xmalloc (1); document[0] = '\0'; } temp->redirectee.filename->word = document; } /* Generate a REDIRECT from SOURCE, DEST, and INSTRUCTION. INSTRUCTION is the instruction type, SOURCE is a file descriptor, and DEST is a file descriptor or a WORD_DESC *. */ REDIRECT * make_redirection (source, instruction, dest_and_filename) int source; enum r_instruction instruction; REDIRECTEE dest_and_filename; { REDIRECT *temp; WORD_DESC *w; int wlen; intmax_t lfd; temp = (REDIRECT *)xmalloc (sizeof (REDIRECT)); /* First do the common cases. */ temp->redirector = source; temp->redirectee = dest_and_filename; temp->instruction = instruction; temp->flags = 0; temp->next = (REDIRECT *)NULL; switch (instruction) { case r_output_direction: /* >foo */ case r_output_force: /* >| foo */ case r_err_and_out: /* command &>filename */ temp->flags = O_TRUNC | O_WRONLY | O_CREAT; break; case r_appending_to: /* >>foo */ temp->flags = O_APPEND | O_WRONLY | O_CREAT; break; case r_input_direction: /* <foo */ case r_inputa_direction: /* foo & makes this. */ temp->flags = O_RDONLY; break; case r_input_output: /* <>foo */ temp->flags = O_RDWR | O_CREAT; break; case r_deblank_reading_until: /* <<-foo */ case r_reading_until: /* << foo */ case r_reading_string: /* <<< foo */ case r_close_this: /* <&- */ case r_duplicating_input: /* 1<&2 */ case r_duplicating_output: /* 1>&2 */ break; /* the parser doesn't pass these. */ case r_move_input: /* 1<&2- */ case r_move_output: /* 1>&2- */ case r_move_input_word: /* 1<&$foo- */ case r_move_output_word: /* 1>&$foo- */ break; /* The way the lexer works we have to do this here. */ case r_duplicating_input_word: /* 1<&$foo */ case r_duplicating_output_word: /* 1>&$foo */ w = dest_and_filename.filename; wlen = strlen (w->word) - 1; if (w->word[wlen] == '-') /* Yuck */ { w->word[wlen] = '\0'; if (all_digits (w->word) && legal_number (w->word, &lfd) && lfd == (int)lfd) { dispose_word (w); temp->instruction = (instruction == r_duplicating_input_word) ? r_move_input : r_move_output; temp->redirectee.dest = lfd; } else temp->instruction = (instruction == r_duplicating_input_word) ? r_move_input_word : r_move_output_word; } break; default: programming_error ("make_redirection: redirection instruction `%d' out of range", instruction); abort (); break; } return (temp); } COMMAND * make_function_def (name, command, lineno, lstart) WORD_DESC *name; COMMAND *command; int lineno, lstart; { FUNCTION_DEF *temp; temp = (FUNCTION_DEF *)xmalloc (sizeof (FUNCTION_DEF)); temp->command = command; temp->name = name; temp->line = lineno; temp->flags = 0; command->line = lstart; return (make_command (cm_function_def, (SIMPLE_COM *)temp)); } COMMAND * make_subshell_command (command) COMMAND *command; { SUBSHELL_COM *temp; temp = (SUBSHELL_COM *)xmalloc (sizeof (SUBSHELL_COM)); temp->command = command; temp->flags = CMD_WANT_SUBSHELL; return (make_command (cm_subshell, (SIMPLE_COM *)temp)); } /* Reverse the word list and redirection list in the simple command has just been parsed. It seems simpler to do this here the one time then by any other method that I can think of. */ COMMAND * clean_simple_command (command) COMMAND *command; { if (command->type != cm_simple) command_error ("clean_simple_command", CMDERR_BADTYPE, command->type, 0); else { command->value.Simple->words = REVERSE_LIST (command->value.Simple->words, WORD_LIST *); command->value.Simple->redirects = REVERSE_LIST (command->value.Simple->redirects, REDIRECT *); } return (command); } /* The Yacc grammar productions have a problem, in that they take a list followed by an ampersand (`&') and do a simple command connection, making the entire list effectively asynchronous, instead of just the last command. This means that when the list is executed, all the commands have stdin set to /dev/null when job control is not active, instead of just the last. This is wrong, and needs fixing up. This function takes the `&' and applies it to the last command in the list. This is done only for lists connected by `;'; it makes `;' bind `tighter' than `&'. */ COMMAND * connect_async_list (command, command2, connector) COMMAND *command, *command2; int connector; { COMMAND *t, *t1, *t2; t1 = command; t = command->value.Connection->second; if (!t || (command->flags & CMD_WANT_SUBSHELL) || command->value.Connection->connector != ';') { t = command_connect (command, command2, connector); return t; } /* This is just defensive programming. The Yacc precedence rules will generally hand this function a command where t points directly to the command we want (e.g. given a ; b ; c ; d &, t1 will point to the `a ; b ; c' list and t will be the `d'). We only want to do this if the list is not being executed as a unit in the background with `( ... )', so we have to check for CMD_WANT_SUBSHELL. That's the only way to tell. */ while (((t->flags & CMD_WANT_SUBSHELL) == 0) && t->type == cm_connection && t->value.Connection->connector == ';') { t1 = t; t = t->value.Connection->second; } /* Now we have t pointing to the last command in the list, and t1->value.Connection->second == t. */ t2 = command_connect (t, command2, connector); t1->value.Connection->second = t2; return command; }
/* print_command -- A way to make readable commands from a command tree. */ /* Copyright (C) 1989 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #include <stdio.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #if defined (PREFER_STDARG) # include <stdarg.h> #else # include <varargs.h> #endif #include "bashansi.h" #include "shell.h" #include "flags.h" #include <y.tab.h> /* use <...> so we pick it up from the build directory */ #include "builtins/common.h" #if !HAVE_DECL_PRINTF extern int printf __P((const char *, ...)); /* Yuck. Double yuck. */ #endif extern int indirection_level; static int indentation; static int indentation_amount = 4; #if defined (PREFER_STDARG) typedef void PFUNC __P((const char *, ...)); static void cprintf __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); static void xprintf __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); #else #define PFUNC VFunction static void cprintf (); static void xprintf (); #endif static void reset_locals __P((void)); static void newline __P((char *)); static void indent __P((int)); static void semicolon __P((void)); static void the_printed_command_resize __P((int)); static void make_command_string_internal __P((COMMAND *)); static void _print_word_list __P((WORD_LIST *, char *, PFUNC *)); static void command_print_word_list __P((WORD_LIST *, char *)); static void print_case_clauses __P((PATTERN_LIST *)); static void print_redirection_list __P((REDIRECT *)); static void print_redirection __P((REDIRECT *)); static void print_for_command __P((FOR_COM *)); #if defined (ARITH_FOR_COMMAND) static void print_arith_for_command __P((ARITH_FOR_COM *)); #endif #if defined (SELECT_COMMAND) static void print_select_command __P((SELECT_COM *)); #endif static void print_group_command __P((GROUP_COM *)); static void print_case_command __P((CASE_COM *)); static void print_while_command __P((WHILE_COM *)); static void print_until_command __P((WHILE_COM *)); static void print_until_or_while __P((WHILE_COM *, char *)); static void print_if_command __P((IF_COM *)); #if defined (DPAREN_ARITHMETIC) static void print_arith_command __P((ARITH_COM *)); #endif #if defined (COND_COMMAND) static void print_cond_node __P((COND_COM *)); static void print_cond_command __P((COND_COM *)); #endif static void print_function_def __P((FUNCTION_DEF *)); #define PRINTED_COMMAND_INITIAL_SIZE 64 #define PRINTED_COMMAND_GROW_SIZE 128 char *the_printed_command = (char *)NULL; int the_printed_command_size = 0; int command_string_index = 0; /* Non-zero means the stuff being printed is inside of a function def. */ static int inside_function_def; static int skip_this_indent; static int was_heredoc; /* The depth of the group commands that we are currently printing. This includes the group command that is a function body. */ static int group_command_nesting; /* A buffer to indicate the indirection level (PS4) when set -x is enabled. */ static char indirection_string[100]; /* Print COMMAND (a command tree) on standard output. */ void print_command (command) COMMAND *command; { command_string_index = 0; printf ("%s", make_command_string (command)); } /* Make a string which is the printed representation of the command tree in COMMAND. We return this string. However, the string is not consed, so you have to do that yourself if you want it to remain around. */ char * make_command_string (command) COMMAND *command; { command_string_index = was_heredoc = 0; make_command_string_internal (command); return (the_printed_command); } /* The internal function. This is the real workhorse. */ static void make_command_string_internal (command) COMMAND *command; { if (command == 0) cprintf (""); else { if (skip_this_indent) skip_this_indent--; else indent (indentation); if (command->flags & CMD_TIME_PIPELINE) { cprintf ("time "); if (command->flags & CMD_TIME_POSIX) cprintf ("-p "); } if (command->flags & CMD_INVERT_RETURN) cprintf ("! "); switch (command->type) { case cm_for: print_for_command (command->value.For); break; #if defined (ARITH_FOR_COMMAND) case cm_arith_for: print_arith_for_command (command->value.ArithFor); break; #endif #if defined (SELECT_COMMAND) case cm_select: print_select_command (command->value.Select); break; #endif case cm_case: print_case_command (command->value.Case); break; case cm_while: print_while_command (command->value.While); break; case cm_until: print_until_command (command->value.While); break; case cm_if: print_if_command (command->value.If); break; #if defined (DPAREN_ARITHMETIC) case cm_arith: print_arith_command (command->value.Arith); break; #endif #if defined (COND_COMMAND) case cm_cond: print_cond_command (command->value.Cond); break; #endif case cm_simple: print_simple_command (command->value.Simple); break; case cm_connection: skip_this_indent++; make_command_string_internal (command->value.Connection->first); switch (command->value.Connection->connector) { case '&': case '|': { char c = command->value.Connection->connector; cprintf (" %c", c); if (c != '&' || command->value.Connection->second) { cprintf (" "); skip_this_indent++; } } break; case AND_AND: cprintf (" && "); if (command->value.Connection->second) skip_this_indent++; break; case OR_OR: cprintf (" || "); if (command->value.Connection->second) skip_this_indent++; break; case ';': if (was_heredoc == 0) cprintf (";"); else was_heredoc = 0; if (inside_function_def) cprintf ("\n"); else { cprintf (" "); if (command->value.Connection->second) skip_this_indent++; } break; default: cprintf ("print_command: bad connector `%d'", command->value.Connection->connector); break; } make_command_string_internal (command->value.Connection->second); break; case cm_function_def: print_function_def (command->value.Function_def); break; case cm_group: print_group_command (command->value.Group); break; case cm_subshell: cprintf ("( "); skip_this_indent++; make_command_string_internal (command->value.Subshell->command); cprintf (" )"); break; default: command_error ("print_command", CMDERR_BADTYPE, command->type, 0); break; } if (command->redirects) { cprintf (" "); print_redirection_list (command->redirects); } } } static void _print_word_list (list, separator, pfunc) WORD_LIST *list; char *separator; PFUNC *pfunc; { WORD_LIST *w; for (w = list; w; w = w->next) (*pfunc) ("%s%s", w->word->word, w->next ? separator : ""); } void print_word_list (list, separator) WORD_LIST *list; char *separator; { _print_word_list (list, separator, xprintf); } /* Return a string denoting what our indirection level is. */ char * indirection_level_string () { register int i, j; char *ps4; indirection_string[0] = '\0'; ps4 = get_string_value ("PS4"); if (ps4 == 0 || *ps4 == '\0') return (indirection_string); change_flag ('x', FLAG_OFF); ps4 = decode_prompt_string (ps4); change_flag ('x', FLAG_ON); for (i = 0; *ps4 && i < indirection_level && i < 99; i++) indirection_string[i] = *ps4; for (j = 1; *ps4 && ps4[j] && i < 99; i++, j++) indirection_string[i] = ps4[j]; indirection_string[i] = '\0'; free (ps4); return (indirection_string); } /* A function to print the words of a simple command when set -x is on. */ void xtrace_print_word_list (list) WORD_LIST *list; { WORD_LIST *w; char *t, *x; fprintf (stderr, "%s", indirection_level_string ()); for (w = list; w; w = w->next) { t = w->word->word; if (t == 0 || *t == '\0') fprintf (stderr, "''%s", w->next ? " " : ""); else if (sh_contains_shell_metas (t)) { x = sh_single_quote (t); fprintf (stderr, "%s%s", x, w->next ? " " : ""); free (x); } else if (ansic_shouldquote (t)) { x = ansic_quote (t, 0, (int *)0); fprintf (stderr, "%s%s", x, w->next ? " " : ""); free (x); } else fprintf (stderr, "%s%s", t, w->next ? " " : ""); } fprintf (stderr, "\n"); } static void command_print_word_list (list, separator) WORD_LIST *list; char *separator; { _print_word_list (list, separator, cprintf); } static void print_for_command (for_command) FOR_COM *for_command; { cprintf ("for %s in ", for_command->name->word); command_print_word_list (for_command->map_list, " "); cprintf (";"); newline ("do\n"); indentation += indentation_amount; make_command_string_internal (for_command->action); semicolon (); indentation -= indentation_amount; newline ("done"); } #if defined (ARITH_FOR_COMMAND) static void print_arith_for_command (arith_for_command) ARITH_FOR_COM *arith_for_command; { cprintf ("for (( "); command_print_word_list (arith_for_command->init, " "); cprintf (" ; "); command_print_word_list (arith_for_command->test, " "); cprintf (" ; "); command_print_word_list (arith_for_command->step, " "); cprintf (" ))"); newline ("do\n"); indentation += indentation_amount; make_command_string_internal (arith_for_command->action); semicolon (); indentation -= indentation_amount; newline ("done"); } #endif /* ARITH_FOR_COMMAND */ #if defined (SELECT_COMMAND) static void print_select_command (select_command) SELECT_COM *select_command; { cprintf ("select %s in ", select_command->name->word); command_print_word_list (select_command->map_list, " "); cprintf (";"); newline ("do\n"); indentation += indentation_amount; make_command_string_internal (select_command->action); semicolon (); indentation -= indentation_amount; newline ("done"); } #endif /* SELECT_COMMAND */ static void print_group_command (group_command) GROUP_COM *group_command; { group_command_nesting++; cprintf ("{ "); if (inside_function_def == 0) skip_this_indent++; else { /* This is a group command { ... } inside of a function definition, and should be printed as a multiline group command, using the current indentation. */ cprintf ("\n"); indentation += indentation_amount; } make_command_string_internal (group_command->command); if (inside_function_def) { cprintf ("\n"); indentation -= indentation_amount; indent (indentation); } else { semicolon (); cprintf (" "); } cprintf ("}"); group_command_nesting--; } static void print_case_command (case_command) CASE_COM *case_command; { cprintf ("case %s in ", case_command->word->word); if (case_command->clauses) print_case_clauses (case_command->clauses); newline ("esac"); } static void print_case_clauses (clauses) PATTERN_LIST *clauses; { indentation += indentation_amount; while (clauses) { newline (""); command_print_word_list (clauses->patterns, " | "); cprintf (")\n"); indentation += indentation_amount; make_command_string_internal (clauses->action); indentation -= indentation_amount; newline (";;"); clauses = clauses->next; } indentation -= indentation_amount; } static void print_while_command (while_command) WHILE_COM *while_command; { print_until_or_while (while_command, "while"); } static void print_until_command (while_command) WHILE_COM *while_command; { print_until_or_while (while_command, "until"); } static void print_until_or_while (while_command, which) WHILE_COM *while_command; char *which; { cprintf ("%s ", which); skip_this_indent++; make_command_string_internal (while_command->test); semicolon (); cprintf (" do\n"); /* was newline ("do\n"); */ indentation += indentation_amount; make_command_string_internal (while_command->action); indentation -= indentation_amount; semicolon (); newline ("done"); } static void print_if_command (if_command) IF_COM *if_command; { cprintf ("if "); skip_this_indent++; make_command_string_internal (if_command->test); semicolon (); cprintf (" then\n"); indentation += indentation_amount; make_command_string_internal (if_command->true_case); indentation -= indentation_amount; if (if_command->false_case) { semicolon (); newline ("else\n"); indentation += indentation_amount; make_command_string_internal (if_command->false_case); indentation -= indentation_amount; } semicolon (); newline ("fi"); } #if defined (DPAREN_ARITHMETIC) static void print_arith_command (arith_command) ARITH_COM *arith_command; { cprintf ("(( "); command_print_word_list (arith_command->exp, " "); cprintf (" ))"); } #endif #if defined (COND_COMMAND) static void print_cond_node (cond) COND_COM *cond; { if (cond->flags & CMD_INVERT_RETURN) cprintf ("! "); if (cond->type == COND_EXPR) { cprintf ("( "); print_cond_node (cond->left); cprintf (" )"); } else if (cond->type == COND_AND) { print_cond_node (cond->left); cprintf (" && "); print_cond_node (cond->right); } else if (cond->type == COND_OR) { print_cond_node (cond->left); cprintf (" || "); print_cond_node (cond->right); } else if (cond->type == COND_UNARY) { cprintf ("%s", cond->op->word); cprintf (" "); print_cond_node (cond->left); } else if (cond->type == COND_BINARY) { print_cond_node (cond->left); cprintf (" "); cprintf ("%s", cond->op->word); cprintf (" "); print_cond_node (cond->right); } else if (cond->type == COND_TERM) { cprintf ("%s", cond->op->word); /* need to add quoting here */ } } static void print_cond_command (cond) COND_COM *cond; { cprintf ("[[ "); print_cond_node (cond); cprintf (" ]]"); } #ifdef DEBUG void debug_print_cond_command (cond) COND_COM *cond; { fprintf (stderr, "DEBUG: "); command_string_index = 0; print_cond_command (cond); fprintf (stderr, "%s\n", the_printed_command); } #endif void xtrace_print_cond_term (type, invert, op, arg1, arg2) int type, invert; WORD_DESC *op; char *arg1, *arg2; { command_string_index = 0; fprintf (stderr, "%s", indirection_level_string ()); fprintf (stderr, "[[ "); if (invert) fprintf (stderr, "! "); if (type == COND_UNARY) { fprintf (stderr, "%s ", op->word); fprintf (stderr, "%s", (arg1 && *arg1) ? arg1 : "''"); } else if (type == COND_BINARY) { fprintf (stderr, "%s", (arg1 && *arg1) ? arg1 : "''"); fprintf (stderr, " %s ", op->word); fprintf (stderr, "%s", (arg2 && *arg2) ? arg2 : "''"); } fprintf (stderr, " ]]\n"); } #endif /* COND_COMMAND */ #if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND) /* A function to print the words of an arithmetic command when set -x is on. */ void xtrace_print_arith_cmd (list) WORD_LIST *list; { WORD_LIST *w; fprintf (stderr, "%s", indirection_level_string ()); fprintf (stderr, "(( "); for (w = list; w; w = w->next) fprintf (stderr, "%s%s", w->word->word, w->next ? " " : ""); fprintf (stderr, " ))\n"); } #endif void print_simple_command (simple_command) SIMPLE_COM *simple_command; { command_print_word_list (simple_command->words, " "); if (simple_command->redirects) { cprintf (" "); print_redirection_list (simple_command->redirects); } } static void print_redirection_list (redirects) REDIRECT *redirects; { REDIRECT *heredocs, *hdtail, *newredir; heredocs = (REDIRECT *)NULL; hdtail = heredocs; was_heredoc = 0; while (redirects) { /* Defer printing the here documents until we've printed the rest of the redirections. */ if (redirects->instruction == r_reading_until || redirects->instruction == r_deblank_reading_until) { newredir = copy_redirect (redirects); newredir->next = (REDIRECT *)NULL; if (heredocs) { hdtail->next = newredir; hdtail = newredir; } else hdtail = heredocs = newredir; } else print_redirection (redirects); redirects = redirects->next; if (redirects) cprintf (" "); } /* Now that we've printed all the other redirections (on one line), print the here documents. */ if (heredocs) { cprintf (" "); for (hdtail = heredocs; hdtail; hdtail = hdtail->next) { print_redirection (hdtail); cprintf ("\n"); } dispose_redirects (heredocs); was_heredoc = 1; } } static void print_redirection (redirect) REDIRECT *redirect; { int kill_leading, redirector, redir_fd; WORD_DESC *redirectee; kill_leading = 0; redirectee = redirect->redirectee.filename; redirector = redirect->redirector; redir_fd = redirect->redirectee.dest; switch (redirect->instruction) { case r_output_direction: if (redirector != 1) cprintf ("%d", redirector); cprintf (">%s", redirectee->word); break; case r_input_direction: if (redirector != 0) cprintf ("%d", redirector); cprintf ("<%s", redirectee->word); break; case r_inputa_direction: /* Redirection created by the shell. */ cprintf ("&"); break; case r_appending_to: if (redirector != 1) cprintf ("%d", redirector); cprintf (">>%s", redirectee->word); break; case r_deblank_reading_until: kill_leading++; /* ... */ case r_reading_until: if (redirector != 0) cprintf ("%d", redirector); /* If the here document delimiter is quoted, single-quote it. */ if (redirect->redirectee.filename->flags & W_QUOTED) { char *x; x = sh_single_quote (redirect->here_doc_eof); cprintf ("<<%s%s\n", kill_leading? "-" : "", x); free (x); } else cprintf ("<<%s%s\n", kill_leading? "-" : "", redirect->here_doc_eof); cprintf ("%s%s", redirect->redirectee.filename->word, redirect->here_doc_eof); break; case r_reading_string: if (redirector != 0) cprintf ("%d", redirector); if (ansic_shouldquote (redirect->redirectee.filename->word)) { char *x; x = ansic_quote (redirect->redirectee.filename->word, 0, (int *)0); cprintf ("<<< %s", x); free (x); } else cprintf ("<<< %s", redirect->redirectee.filename->word); break; case r_duplicating_input: cprintf ("%d<&%d", redirector, redir_fd); break; case r_duplicating_output: cprintf ("%d>&%d", redirector, redir_fd); break; case r_duplicating_input_word: cprintf ("%d<&%s", redirector, redirectee->word); break; case r_duplicating_output_word: cprintf ("%d>&%s", redirector, redirectee->word); break; case r_move_input: cprintf ("%d<&%d-", redirector, redir_fd); break; case r_move_output: cprintf ("%d>&%d-", redirector, redir_fd); break; case r_move_input_word: cprintf ("%d<&%s-", redirector, redirectee->word); break; case r_move_output_word: cprintf ("%d>&%s-", redirector, redirectee->word); break; case r_close_this: cprintf ("%d>&-", redirector); break; case r_err_and_out: cprintf (">&%s", redirectee->word); break; case r_input_output: if (redirector != 1) cprintf ("%d", redirector); cprintf ("<>%s", redirectee->word); break; case r_output_force: if (redirector != 1) cprintf ("%d", redirector); cprintf (">|%s", redirectee->word); break; } } static void reset_locals () { inside_function_def = 0; indentation = 0; } static void print_function_def (func) FUNCTION_DEF *func; { COMMAND *cmdcopy; REDIRECT *func_redirects; func_redirects = NULL; cprintf ("function %s () \n", func->name->word); add_unwind_protect (reset_locals, 0); indent (indentation); cprintf ("{ \n"); inside_function_def++; indentation += indentation_amount; cmdcopy = copy_command (func->command); if (cmdcopy->type == cm_group) { func_redirects = cmdcopy->redirects; cmdcopy->redirects = (REDIRECT *)NULL; } make_command_string_internal (cmdcopy->type == cm_group ? cmdcopy->value.Group->command : cmdcopy); remove_unwind_protect (); indentation -= indentation_amount; inside_function_def--; if (func_redirects) { /* { */ newline ("} "); print_redirection_list (func_redirects); cmdcopy->redirects = func_redirects; } else newline ("}"); dispose_command (cmdcopy); } /* Return the string representation of the named function. NAME is the name of the function. COMMAND is the function body. It should be a GROUP_COM. MULTI_LINE is non-zero to pretty-print, or zero for all on one line. */ char * named_function_string (name, command, multi_line) char *name; COMMAND *command; int multi_line; { char *result; int old_indent, old_amount; COMMAND *cmdcopy; REDIRECT *func_redirects; old_indent = indentation; old_amount = indentation_amount; command_string_index = was_heredoc = 0; if (name && *name) cprintf ("%s ", name); cprintf ("() "); if (multi_line == 0) { indentation = 1; indentation_amount = 0; } else { cprintf ("\n"); indentation += indentation_amount; } inside_function_def++; cprintf (multi_line ? "{ \n" : "{ "); cmdcopy = copy_command (command); /* Take any redirections specified in the function definition (which should apply to the function as a whole) and save them for printing later. */ func_redirects = (REDIRECT *)NULL; if (cmdcopy->type == cm_group) { func_redirects = cmdcopy->redirects; cmdcopy->redirects = (REDIRECT *)NULL; } make_command_string_internal (cmdcopy->type == cm_group ? cmdcopy->value.Group->command : cmdcopy); indentation = old_indent; indentation_amount = old_amount; inside_function_def--; if (func_redirects) { /* { */ newline ("} "); print_redirection_list (func_redirects); cmdcopy->redirects = func_redirects; } else newline ("}"); result = the_printed_command; if (!multi_line) { #if 0 register int i; for (i = 0; result[i]; i++) if (result[i] == '\n') { strcpy (result + i, result + i + 1); --i; } #else if (result[2] == '\n') /* XXX -- experimental */ strcpy (result + 2, result + 3); #endif } dispose_command (cmdcopy); return (result); } static void newline (string) char *string; { cprintf ("\n"); indent (indentation); if (string && *string) cprintf ("%s", string); } static char *indentation_string; static int indentation_size; static void indent (amount) int amount; { register int i; RESIZE_MALLOCED_BUFFER (indentation_string, 0, amount, indentation_size, 16); for (i = 0; amount > 0; amount--) indentation_string[i++] = ' '; indentation_string[i] = '\0'; cprintf (indentation_string); } static void semicolon () { if (command_string_index > 0 && (the_printed_command[command_string_index - 1] == '&' || the_printed_command[command_string_index - 1] == '\n')) return; cprintf (";"); } /* How to make the string. */ static void #if defined (PREFER_STDARG) cprintf (const char *control, ...) #else cprintf (control, va_alist) const char *control; va_dcl #endif { register const char *s; char char_arg[2], *argp, intbuf[INT_STRLEN_BOUND (int) + 1]; int digit_arg, arg_len, c; va_list args; SH_VA_START (args, control); arg_len = strlen (control); the_printed_command_resize (arg_len + 1); char_arg[1] = '\0'; s = control; while (s && *s) { c = *s++; argp = (char *)NULL; if (c != '%' || !*s) { char_arg[0] = c; argp = char_arg; arg_len = 1; } else { c = *s++; switch (c) { case '%': char_arg[0] = c; argp = char_arg; arg_len = 1; break; case 's': argp = va_arg (args, char *); arg_len = strlen (argp); break; case 'd': /* Represent an out-of-range file descriptor with an out-of-range integer value. We can do this because the only use of `%d' in the calls to cprintf is to output a file descriptor number for a redirection. */ digit_arg = va_arg (args, int); if (digit_arg < 0) { sprintf (intbuf, "%u", (unsigned)-1); argp = intbuf; } else argp = inttostr (digit_arg, intbuf, sizeof (intbuf)); arg_len = strlen (argp); break; case 'c': char_arg[0] = va_arg (args, int); argp = char_arg; arg_len = 1; break; default: programming_error ("cprintf: bad `%%' argument (%c)", c); /*NOTREACHED*/ } } if (argp && arg_len) { the_printed_command_resize (arg_len + 1); FASTCOPY (argp, the_printed_command + command_string_index, arg_len); command_string_index += arg_len; } } the_printed_command[command_string_index] = '\0'; } /* Ensure that there is enough space to stuff LENGTH characters into THE_PRINTED_COMMAND. */ static void the_printed_command_resize (length) int length; { if (the_printed_command == 0) { the_printed_command_size = (length + PRINTED_COMMAND_INITIAL_SIZE - 1) & ~(PRINTED_COMMAND_INITIAL_SIZE - 1); the_printed_command = (char *)xmalloc (the_printed_command_size); command_string_index = 0; } else if ((command_string_index + length) >= the_printed_command_size) { int new; new = command_string_index + length + 1; /* Round up to the next multiple of PRINTED_COMMAND_GROW_SIZE. */ new = (new + PRINTED_COMMAND_GROW_SIZE - 1) & ~(PRINTED_COMMAND_GROW_SIZE - 1); the_printed_command_size = new; the_printed_command = (char *)xrealloc (the_printed_command, the_printed_command_size); } } #if defined (HAVE_VPRINTF) /* ``If vprintf is available, you may assume that vfprintf and vsprintf are also available.'' */ static void #if defined (PREFER_STDARG) xprintf (const char *format, ...) #else xprintf (format, va_alist) const char *format; va_dcl #endif { va_list args; SH_VA_START (args, format); vfprintf (stdout, format, args); va_end (args); } #else static void xprintf (format, arg1, arg2, arg3, arg4, arg5) const char *format; { printf (format, arg1, arg2, arg3, arg4, arg5); } #endif /* !HAVE_VPRINTF */
/* dispose_command.c -- dispose of a COMMAND structure. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #include "bashtypes.h" #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "bashansi.h" #include "shell.h" extern sh_obj_cache_t wdcache, wlcache; /* Dispose of the command structure passed. */ void dispose_command (command) COMMAND *command; { if (command == 0) return; if (command->redirects) dispose_redirects (command->redirects); switch (command->type) { case cm_for: #if defined (SELECT_COMMAND) case cm_select: #endif { register FOR_COM *c; #if defined (SELECT_COMMAND) if (command->type == cm_select) c = (FOR_COM *)command->value.Select; else #endif c = command->value.For; dispose_word (c->name); dispose_words (c->map_list); dispose_command (c->action); free (c); break; } #if defined (ARITH_FOR_COMMAND) case cm_arith_for: { register ARITH_FOR_COM *c; c = command->value.ArithFor; dispose_words (c->init); dispose_words (c->test); dispose_words (c->step); dispose_command (c->action); free (c); break; } #endif /* ARITH_FOR_COMMAND */ case cm_group: { dispose_command (command->value.Group->command); free (command->value.Group); break; } case cm_subshell: { dispose_command (command->value.Subshell->command); free (command->value.Subshell); break; } case cm_case: { register CASE_COM *c; PATTERN_LIST *t, *p; c = command->value.Case; dispose_word (c->word); for (p = c->clauses; p; ) { dispose_words (p->patterns); dispose_command (p->action); t = p; p = p->next; free (t); } free (c); break; } case cm_until: case cm_while: { register WHILE_COM *c; c = command->value.While; dispose_command (c->test); dispose_command (c->action); free (c); break; } case cm_if: { register IF_COM *c; c = command->value.If; dispose_command (c->test); dispose_command (c->true_case); dispose_command (c->false_case); free (c); break; } case cm_simple: { register SIMPLE_COM *c; c = command->value.Simple; dispose_words (c->words); dispose_redirects (c->redirects); free (c); break; } case cm_connection: { register CONNECTION *c; c = command->value.Connection; dispose_command (c->first); dispose_command (c->second); free (c); break; } #if defined (DPAREN_ARITHMETIC) case cm_arith: { register ARITH_COM *c; c = command->value.Arith; dispose_words (c->exp); free (c); break; } #endif /* DPAREN_ARITHMETIC */ #if defined (COND_COMMAND) case cm_cond: { register COND_COM *c; c = command->value.Cond; dispose_cond_node (c); break; } #endif /* COND_COMMAND */ case cm_function_def: { register FUNCTION_DEF *c; c = command->value.Function_def; dispose_word (c->name); dispose_command (c->command); free (c); break; } default: command_error ("dispose_command", CMDERR_BADTYPE, command->type, 0); break; } free (command); } #if defined (COND_COMMAND) /* How to free a node in a conditional command. */ void dispose_cond_node (cond) COND_COM *cond; { if (cond) { if (cond->left) dispose_cond_node (cond->left); if (cond->right) dispose_cond_node (cond->right); if (cond->op) dispose_word (cond->op); free (cond); } } #endif /* COND_COMMAND */ /* How to free a WORD_DESC. */ void dispose_word (w) WORD_DESC *w; { FREE (w->word); #if 0 free (w); #else ocache_free (wdcache, WORD_DESC, w); #endif } /* How to get rid of a linked list of words. A WORD_LIST. */ void dispose_words (list) WORD_LIST *list; { WORD_LIST *t; while (list) { t = list; list = list->next; dispose_word (t->word); #if 0 free (t); #else ocache_free (wlcache, WORD_LIST, t); #endif } } #ifdef INCLUDE_UNUSED /* How to dispose of an array of pointers to char. This is identical to free_array in stringlib.c. */ void dispose_word_array (array) char **array; { register int count; if (array == 0) return; for (count = 0; array[count]; count++) free (array[count]); free (array); } #endif /* How to dispose of an list of redirections. A REDIRECT. */ void dispose_redirects (list) REDIRECT *list; { register REDIRECT *t; while (list) { t = list; list = list->next; switch (t->instruction) { case r_reading_until: case r_deblank_reading_until: free (t->here_doc_eof); /*FALLTHROUGH*/ case r_reading_string: case r_output_direction: case r_input_direction: case r_inputa_direction: case r_appending_to: case r_err_and_out: case r_input_output: case r_output_force: case r_duplicating_input_word: case r_duplicating_output_word: case r_move_input_word: case r_move_output_word: dispose_word (t->redirectee.filename); /* FALLTHROUGH */ default: break; } free (t); } }
/* variables.c -- Functions for hacking shell variables. */ /* Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #include "bashtypes.h" #include "posixstat.h" #include "posixtime.h" #if defined (qnx) # include <sys/vc.h> #endif #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <stdio.h> #include "chartypes.h" #include <pwd.h> #include "bashansi.h" #include "shell.h" #include "flags.h" #include "execute_cmd.h" #include "findcmd.h" #include "mailcheck.h" #include "input.h" #include "hashcmd.h" #include "pathexp.h" #include "builtins/getopt.h" #include "builtins/common.h" #if defined (READLINE) # include "bashline.h" # include <readline/readline.h> #else # include <tilde/tilde.h> #endif #if defined (HISTORY) # include "bashhist.h" # include <readline/history.h> #endif /* HISTORY */ #if defined (PROGRAMMABLE_COMPLETION) # include "pcomplete.h" #endif #define TEMPENV_HASH_BUCKETS 4 /* must be power of two */ #define ifsname(s) ((s)[0] == 'I' && (s)[1] == 'F' && (s)[2] == 'S' && (s)[3] == '\0') /* Variables used here and defined in other files. */ extern int posixly_correct; extern int line_number; extern int subshell_environment, indirection_level; extern int build_version, patch_level; extern char *dist_version, *release_status; extern char *shell_name; extern char *primary_prompt, *secondary_prompt; extern char *current_host_name; extern sh_builtin_func_t *this_shell_builtin; extern SHELL_VAR *this_shell_function; extern char *this_command_name; extern time_t shell_start_time; /* The list of shell variables that the user has created at the global scope, or that came from the environment. */ VAR_CONTEXT *global_variables = (VAR_CONTEXT *)NULL; /* The current list of shell variables, including function scopes */ VAR_CONTEXT *shell_variables = (VAR_CONTEXT *)NULL; /* The list of shell functions that the user has created, or that came from the environment. */ HASH_TABLE *shell_functions = (HASH_TABLE *)NULL; /* The current variable context. This is really a count of how deep into executing functions we are. */ int variable_context = 0; /* The set of shell assignments which are made only in the environment for a single command. */ HASH_TABLE *temporary_env = (HASH_TABLE *)NULL; /* Some funky variables which are known about specially. Here is where "$*", "$1", and all the cruft is kept. */ char *dollar_vars[10]; WORD_LIST *rest_of_args = (WORD_LIST *)NULL; /* The value of $$. */ pid_t dollar_dollar_pid; /* An array which is passed to commands as their environment. It is manufactured from the union of the initial environment and the shell variables that are marked for export. */ char **export_env = (char **)NULL; static int export_env_index; static int export_env_size; /* Non-zero means that we have to remake EXPORT_ENV. */ int array_needs_making = 1; /* The number of times BASH has been executed. This is set by initialize_variables (). */ int shell_level = 0; /* Some forward declarations. */ static void set_machine_vars __P((void)); static void set_home_var __P((void)); static void set_shell_var __P((void)); static char *get_bash_name __P((void)); static void initialize_shell_level __P((void)); static void uidset __P((void)); #if defined (ARRAY_VARS) static void make_vers_array __P((void)); #endif static SHELL_VAR *null_assign __P((SHELL_VAR *, char *, arrayind_t)); #if defined (ARRAY_VARS) static SHELL_VAR *null_array_assign __P((SHELL_VAR *, char *, arrayind_t)); #endif static SHELL_VAR *assign_seconds __P((SHELL_VAR *, char *, arrayind_t)); static SHELL_VAR *get_seconds __P((SHELL_VAR *)); static SHELL_VAR *init_seconds_var __P((void)); static int brand __P((void)); static void sbrand __P((unsigned long)); /* set bash random number generator. */ static SHELL_VAR *assign_random __P((SHELL_VAR *, char *, arrayind_t)); static SHELL_VAR *get_random __P((SHELL_VAR *)); static SHELL_VAR *assign_lineno __P((SHELL_VAR *, char *, arrayind_t)); static SHELL_VAR *get_lineno __P((SHELL_VAR *)); #if defined (HISTORY) static SHELL_VAR *get_histcmd __P((SHELL_VAR *)); #endif #if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) static SHELL_VAR *assign_dirstack __P((SHELL_VAR *, char *, arrayind_t)); static SHELL_VAR *get_dirstack __P((SHELL_VAR *)); static SHELL_VAR *init_dirstack_var __P((void)); #endif #if defined (ARRAY_VARS) static SHELL_VAR *get_groupset __P((SHELL_VAR *)); static SHELL_VAR *init_groups_var __P((void)); #endif static SHELL_VAR *get_funcname __P((SHELL_VAR *)); static SHELL_VAR *init_funcname_var __P((void)); static void initialize_dynamic_variables __P((void)); static SHELL_VAR *hash_lookup __P((const char *, HASH_TABLE *)); static SHELL_VAR *new_shell_variable __P((const char *)); static SHELL_VAR *make_new_variable __P((const char *, HASH_TABLE *)); static SHELL_VAR *bind_variable_internal __P((const char *, char *, HASH_TABLE *, int)); static void free_variable_hash_data __P((PTR_T)); static VARLIST *vlist_alloc __P((int)); static VARLIST *vlist_realloc __P((VARLIST *, int)); static void vlist_add __P((VARLIST *, SHELL_VAR *, int)); static void flatten __P((HASH_TABLE *, sh_var_map_func_t *, VARLIST *, int)); static int qsort_var_comp __P((SHELL_VAR **, SHELL_VAR **)); static SHELL_VAR **vapply __P((sh_var_map_func_t *)); static SHELL_VAR **fapply __P((sh_var_map_func_t *)); static int visible_var __P((SHELL_VAR *)); static int visible_and_exported __P((SHELL_VAR *)); static int local_and_exported __P((SHELL_VAR *)); static int variable_in_context __P((SHELL_VAR *)); #if defined (ARRAY_VARS) static int visible_array_vars __P((SHELL_VAR *)); #endif static SHELL_VAR *bind_tempenv_variable __P((const char *, char *)); static void push_temp_var __P((PTR_T)); static void propagate_temp_var __P((PTR_T)); static void dispose_temporary_env __P((sh_free_func_t *)); static inline char *mk_env_string __P((const char *, const char *)); static char **make_env_array_from_var_list __P((SHELL_VAR **)); static char **make_var_export_array __P((VAR_CONTEXT *)); static char **make_func_export_array __P((void)); static void add_temp_array_to_env __P((char **, int, int)); static int n_shell_variables __P((void)); static int set_context __P((SHELL_VAR *)); static void push_func_var __P((PTR_T)); static void push_exported_var __P((PTR_T)); static inline int find_special_var __P((const char *)); /* Initialize the shell variables from the current environment. If PRIVMODE is nonzero, don't import functions from ENV or parse $SHELLOPTS. */ void initialize_shell_variables (env, privmode) char **env; int privmode; { char *name, *string, *temp_string; int c, char_index, string_index, string_length; SHELL_VAR *temp_var; if (shell_variables == 0) { shell_variables = global_variables = new_var_context ((char *)NULL, 0); shell_variables->scope = 0; shell_variables->table = hash_create (0); } if (shell_functions == 0) shell_functions = hash_create (0); for (string_index = 0; string = env[string_index++]; ) { char_index = 0; name = string; while ((c = *string++) && c != '=') ; if (string[-1] == '=') char_index = string - name - 1; /* If there are weird things in the environment, like `=xxx' or a string without an `=', just skip them. */ if (char_index == 0) continue; /* ASSERT(name[char_index] == '=') */ name[char_index] = '\0'; /* Now, name = env variable name, string = env variable value, and char_index == strlen (name) */ /* If exported function, define it now. Don't import functions from the environment in privileged mode. */ if (privmode == 0 && read_but_dont_execute == 0 && STREQN ("() {", string, 4)) { string_length = strlen (string); temp_string = (char *)xmalloc (3 + string_length + char_index); strcpy (temp_string, name); temp_string[char_index] = ' '; strcpy (temp_string + char_index + 1, string); parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST); /* Ancient backwards compatibility. Old versions of bash exported functions like name()=() {...} */ if (name[char_index - 1] == ')' && name[char_index - 2] == '(') name[char_index - 2] = '\0'; if (temp_var = find_function (name)) { VSETATTR (temp_var, (att_exported|att_imported)); array_needs_making = 1; } else report_error ("error importing function definition for `%s'", name); /* ( */ if (name[char_index - 1] == ')' && name[char_index - 2] == '\0') name[char_index - 2] = '('; /* ) */ } #if defined (ARRAY_VARS) # if 0 /* Array variables may not yet be exported. */ else if (*string == '(' && string[1] == '[' && xstrchr (string, ')')) { string_length = 1; temp_string = extract_array_assignment_list (string, &string_length); temp_var = assign_array_from_string (name, temp_string); FREE (temp_string); VSETATTR (temp_var, (att_exported | att_imported)); array_needs_making = 1; } # endif #endif else { temp_var = bind_variable (name, string); VSETATTR (temp_var, (att_exported | att_imported)); array_needs_making = 1; } name[char_index] = '='; /* temp_var can be NULL if it was an exported function with a syntax error (a different bug, but it still shouldn't dump core). */ if (temp_var && function_p (temp_var) == 0) /* XXX not yet */ { CACHE_IMPORTSTR (temp_var, name); } } set_pwd (); /* Set up initial value of $_ */ temp_var = bind_variable ("_", dollar_vars[0]); /* Remember this pid. */ dollar_dollar_pid = getpid (); /* Now make our own defaults in case the vars that we think are important are missing. */ temp_var = set_if_not ("PATH", DEFAULT_PATH_VALUE); #if 0 set_auto_export (temp_var); /* XXX */ #endif temp_var = set_if_not ("TERM", "dumb"); #if 0 set_auto_export (temp_var); /* XXX */ #endif #if defined (qnx) /* set node id -- don't import it from the environment */ { char node_name[22]; qnx_nidtostr (getnid (), node_name, sizeof (node_name)); temp_var = bind_variable ("NODE", node_name); set_auto_export (temp_var); } #endif /* set up the prompts. */ if (interactive_shell) { #if defined (PROMPT_STRING_DECODE) set_if_not ("PS1", primary_prompt); #else if (current_user.uid == -1) get_current_user_info (); set_if_not ("PS1", current_user.euid == 0 ? "# " : primary_prompt); #endif set_if_not ("PS2", secondary_prompt); } set_if_not ("PS4", "+ "); /* Don't allow IFS to be imported from the environment. */ temp_var = bind_variable ("IFS", " \t\n"); setifs (temp_var); /* Magic machine types. Pretty convenient. */ set_machine_vars (); /* Default MAILCHECK for interactive shells. Defer the creation of a default MAILPATH until the startup files are read, because MAIL names a mail file if MAILPATH is not set, and we should provide a default only if neither is set. */ if (interactive_shell) set_if_not ("MAILCHECK", posixly_correct ? "600" : "60"); /* Do some things with shell level. */ initialize_shell_level (); set_ppid (); /* Initialize the `getopts' stuff. */ bind_variable ("OPTIND", "1"); getopts_reset (0); bind_variable ("OPTERR", "1"); sh_opterr = 1; if (login_shell == 1) set_home_var (); /* Get the full pathname to THIS shell, and set the BASH variable to it. */ name = get_bash_name (); temp_var = bind_variable ("BASH", name); free (name); /* Make the exported environment variable SHELL be the user's login shell. Note that the `tset' command looks at this variable to determine what style of commands to output; if it ends in "csh", then C-shell commands are output, else Bourne shell commands. */ set_shell_var (); /* Make a variable called BASH_VERSION which contains the version info. */ bind_variable ("BASH_VERSION", shell_version_string ()); #if defined (ARRAY_VARS) make_vers_array (); #endif /* Find out if we're supposed to be in Posix.2 mode via an environment variable. */ temp_var = find_variable ("POSIXLY_CORRECT"); if (!temp_var) temp_var = find_variable ("POSIX_PEDANTIC"); if (temp_var && imported_p (temp_var)) sv_strict_posix (temp_var->name); #if defined (HISTORY) /* Set history variables to defaults, and then do whatever we would do if the variable had just been set. Do this only in the case that we are remembering commands on the history list. */ if (remember_on_history) { name = bash_tilde_expand (posixly_correct ? "~/.sh_history" : "~/.bash_history", 0); set_if_not ("HISTFILE", name); free (name); set_if_not ("HISTSIZE", "500"); sv_histsize ("HISTSIZE"); } #endif /* HISTORY */ /* Seed the random number generator. */ sbrand (dollar_dollar_pid + shell_start_time); /* Handle some "special" variables that we may have inherited from a parent shell. */ if (interactive_shell) { temp_var = find_variable ("IGNOREEOF"); if (!temp_var) temp_var = find_variable ("ignoreeof"); if (temp_var && imported_p (temp_var)) sv_ignoreeof (temp_var->name); } #if defined (HISTORY) if (interactive_shell && remember_on_history) { sv_history_control ("HISTCONTROL"); sv_histignore ("HISTIGNORE"); } #endif /* HISTORY */ /* * 24 October 2001 * * I'm tired of the arguing and bug reports. Bash now leaves SSH_CLIENT * and SSH2_CLIENT alone. I'm going to rely on the shell_level check in * isnetconn() to avoid running the startup files more often than wanted. * That will, of course, only work if the user's login shell is bash, so * I've made that behavior conditional on SSH_SOURCE_BASHRC being defined * in config-top.h. */ #if 0 temp_var = find_variable ("SSH_CLIENT"); if (temp_var && imported_p (temp_var)) { VUNSETATTR (temp_var, att_exported); array_needs_making = 1; } temp_var = find_variable ("SSH2_CLIENT"); if (temp_var && imported_p (temp_var)) { VUNSETATTR (temp_var, att_exported); array_needs_making = 1; } #endif /* Get the user's real and effective user ids. */ uidset (); /* Initialize the dynamic variables, and seed their values. */ initialize_dynamic_variables (); } /* **************************************************************** */ /* */ /* Setting values for special shell variables */ /* */ /* **************************************************************** */ static void set_machine_vars () { SHELL_VAR *temp_var; temp_var = set_if_not ("HOSTTYPE", HOSTTYPE); temp_var = set_if_not ("OSTYPE", OSTYPE); temp_var = set_if_not ("MACHTYPE", MACHTYPE); temp_var = set_if_not ("HOSTNAME", current_host_name); } /* Set $HOME to the information in the password file if we didn't get it from the environment. */ /* This function is not static so the tilde and readline libraries can use it. */ char * sh_get_home_dir () { if (current_user.home_dir == 0) get_current_user_info (); return current_user.home_dir; } static void set_home_var () { SHELL_VAR *temp_var; temp_var = find_variable ("HOME"); if (temp_var == 0) temp_var = bind_variable ("HOME", sh_get_home_dir ()); #if 0 VSETATTR (temp_var, att_exported); #endif } /* Set $SHELL to the user's login shell if it is not already set. Call get_current_user_info if we haven't already fetched the shell. */ static void set_shell_var () { SHELL_VAR *temp_var; temp_var = find_variable ("SHELL"); if (temp_var == 0) { if (current_user.shell == 0) get_current_user_info (); temp_var = bind_variable ("SHELL", current_user.shell); } #if 0 VSETATTR (temp_var, att_exported); #endif } static char * get_bash_name () { char *name; if ((login_shell == 1) && RELPATH(shell_name)) { if (current_user.shell == 0) get_current_user_info (); name = savestring (current_user.shell); } else if (ABSPATH(shell_name)) name = savestring (shell_name); else if (shell_name[0] == '.' && shell_name[1] == '/') { /* Fast path for common case. */ char *cdir; int len; cdir = get_string_value ("PWD"); if (cdir) { len = strlen (cdir); name = (char *)xmalloc (len + strlen (shell_name) + 1); strcpy (name, cdir); strcpy (name + len, shell_name + 1); } else name = savestring (shell_name); } else { char *tname; int s; tname = find_user_command (shell_name); if (tname == 0) { /* Try the current directory. If there is not an executable there, just punt and use the login shell. */ s = file_status (shell_name); if (s & FS_EXECABLE) { tname = make_absolute (shell_name, get_string_value ("PWD")); if (*shell_name == '.') { name = sh_canonpath (tname, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); if (name == 0) name = tname; else free (tname); } else name = tname; } else { if (current_user.shell == 0) get_current_user_info (); name = savestring (current_user.shell); } } else { name = full_pathname (tname); free (tname); } } return (name); } void adjust_shell_level (change) int change; { char new_level[5], *old_SHLVL; intmax_t old_level; SHELL_VAR *temp_var; old_SHLVL = get_string_value ("SHLVL"); if (old_SHLVL == 0 || *old_SHLVL == '\0' || legal_number (old_SHLVL, &old_level) == 0) old_level = 0; shell_level = old_level + change; if (shell_level < 0) shell_level = 0; else if (shell_level > 1000) { internal_warning ("shell level (%d) too high, resetting to 1", shell_level); shell_level = 1; } /* We don't need the full generality of itos here. */ if (shell_level < 10) { new_level[0] = shell_level + '0'; new_level[1] = '\0'; } else if (shell_level < 100) { new_level[0] = (shell_level / 10) + '0'; new_level[1] = (shell_level % 10) + '0'; new_level[2] = '\0'; } else if (shell_level < 1000) { new_level[0] = (shell_level / 100) + '0'; old_level = shell_level % 100; new_level[1] = (old_level / 10) + '0'; new_level[2] = (old_level % 10) + '0'; new_level[3] = '\0'; } temp_var = bind_variable ("SHLVL", new_level); set_auto_export (temp_var); } static void initialize_shell_level () { adjust_shell_level (1); } /* If we got PWD from the environment, update our idea of the current working directory. In any case, make sure that PWD exists before checking it. It is possible for getcwd () to fail on shell startup, and in that case, PWD would be undefined. If this is an interactive login shell, see if $HOME is the current working directory, and if that's not the same string as $PWD, set PWD=$HOME. */ void set_pwd () { SHELL_VAR *temp_var, *home_var; char *temp_string, *home_string; home_var = find_variable ("HOME"); home_string = home_var ? value_cell (home_var) : (char *)NULL; temp_var = find_variable ("PWD"); if (temp_var && imported_p (temp_var) && (temp_string = value_cell (temp_var)) && same_file (temp_string, ".", (struct stat *)NULL, (struct stat *)NULL)) set_working_directory (temp_string); else if (home_string && interactive_shell && login_shell && same_file (home_string, ".", (struct stat *)NULL, (struct stat *)NULL)) { set_working_directory (home_string); temp_var = bind_variable ("PWD", home_string); set_auto_export (temp_var); } else { temp_string = get_working_directory ("shell-init"); if (temp_string) { temp_var = bind_variable ("PWD", temp_string); set_auto_export (temp_var); free (temp_string); } } /* According to the Single Unix Specification, v2, $OLDPWD is an `environment variable' and therefore should be auto-exported. Make a dummy invisible variable for OLDPWD, and mark it as exported. */ temp_var = bind_variable ("OLDPWD", (char *)NULL); VSETATTR (temp_var, (att_exported | att_invisible)); } /* Make a variable $PPID, which holds the pid of the shell's parent. */ void set_ppid () { char namebuf[INT_STRLEN_BOUND(pid_t) + 1], *name; SHELL_VAR *temp_var; name = inttostr (getppid (), namebuf, sizeof(namebuf)); temp_var = find_variable ("PPID"); if (temp_var) VUNSETATTR (temp_var, (att_readonly | att_exported)); temp_var = bind_variable ("PPID", name); VSETATTR (temp_var, (att_readonly | att_integer)); } static void uidset () { char buff[INT_STRLEN_BOUND(uid_t) + 1], *b; register SHELL_VAR *v; b = inttostr (current_user.uid, buff, sizeof (buff)); v = find_variable ("UID"); if (v == 0) { v = bind_variable ("UID", b); VSETATTR (v, (att_readonly | att_integer)); } if (current_user.euid != current_user.uid) b = inttostr (current_user.euid, buff, sizeof (buff)); v = find_variable ("EUID"); if (v == 0) { v = bind_variable ("EUID", b); VSETATTR (v, (att_readonly | att_integer)); } } #if defined (ARRAY_VARS) static void make_vers_array () { SHELL_VAR *vv; ARRAY *av; char *s, d[32], b[INT_STRLEN_BOUND(int) + 1]; unbind_variable ("BASH_VERSINFO"); vv = make_new_array_variable ("BASH_VERSINFO"); av = array_cell (vv); strcpy (d, dist_version); s = xstrchr (d, '.'); if (s) *s++ = '\0'; array_insert (av, 0, d); array_insert (av, 1, s); s = inttostr (patch_level, b, sizeof (b)); array_insert (av, 2, s); s = inttostr (build_version, b, sizeof (b)); array_insert (av, 3, s); array_insert (av, 4, release_status); array_insert (av, 5, MACHTYPE); VSETATTR (vv, att_readonly); } #endif /* ARRAY_VARS */ /* Set the environment variables $LINES and $COLUMNS in response to a window size change. */ void sh_set_lines_and_columns (lines, cols) int lines, cols; { char val[INT_STRLEN_BOUND(int) + 1], *v; v = inttostr (lines, val, sizeof (val)); bind_variable ("LINES", v); v = inttostr (cols, val, sizeof (val)); bind_variable ("COLUMNS", v); } /* **************************************************************** */ /* */ /* Printing variables and values */ /* */ /* **************************************************************** */ /* Print LIST (a list of shell variables) to stdout in such a way that they can be read back in. */ void print_var_list (list) register SHELL_VAR **list; { register int i; register SHELL_VAR *var; for (i = 0; list && (var = list[i]); i++) if (invisible_p (var) == 0) print_assignment (var); } /* Print LIST (a list of shell functions) to stdout in such a way that they can be read back in. */ void print_func_list (list) register SHELL_VAR **list; { register int i; register SHELL_VAR *var; for (i = 0; list && (var = list[i]); i++) { printf ("%s ", var->name); print_var_function (var); printf ("\n"); } } /* Print the value of a single SHELL_VAR. No newline is output, but the variable is printed in such a way that it can be read back in. */ void print_assignment (var) SHELL_VAR *var; { if (var_isset (var) == 0) return; if (function_p (var)) { printf ("%s", var->name); print_var_function (var); printf ("\n"); } #if defined (ARRAY_VARS) else if (array_p (var)) print_array_assignment (var, 0); #endif /* ARRAY_VARS */ else { printf ("%s=", var->name); print_var_value (var, 1); printf ("\n"); } } /* Print the value cell of VAR, a shell variable. Do not print the name, nor leading/trailing newline. If QUOTE is non-zero, and the value contains shell metacharacters, quote the value in such a way that it can be read back in. */ void print_var_value (var, quote) SHELL_VAR *var; int quote; { char *t; if (var_isset (var) == 0) return; if (quote && posixly_correct == 0 && ansic_shouldquote (value_cell (var))) { t = ansic_quote (value_cell (var), 0, (int *)0); printf ("%s", t); free (t); } else if (quote && sh_contains_shell_metas (value_cell (var))) { t = sh_single_quote (value_cell (var)); printf ("%s", t); free (t); } else printf ("%s", value_cell (var)); } /* Print the function cell of VAR, a shell variable. Do not print the name, nor leading/trailing newline. */ void print_var_function (var) SHELL_VAR *var; { if (function_p (var) && var_isset (var)) printf ("%s", named_function_string ((char *)NULL, function_cell(var), 1)); } /* **************************************************************** */ /* */ /* Dynamic Variables */ /* */ /* **************************************************************** */ /* DYNAMIC VARIABLES These are variables whose values are generated anew each time they are referenced. These are implemented using a pair of function pointers in the struct variable: assign_func, which is called from bind_variable and, if arrays are compiled into the shell, some of the functions in arrayfunc.c, and dynamic_value, which is called from find_variable. assign_func is called from bind_variable_internal, if bind_variable_internal discovers that the variable being assigned to has such a function. The function is called as SHELL_VAR *temp = (*(entry->assign_func)) (entry, value, ind) and the (SHELL_VAR *)temp is returned as the value of bind_variable. It is usually ENTRY (self). IND is an index for an array variable, and unused otherwise. dynamic_value is called from find_variable_internal to return a `new' value for the specified dynamic varible. If this function is NULL, the variable is treated as a `normal' shell variable. If it is not, however, then this function is called like this: tempvar = (*(var->dynamic_value)) (var); Sometimes `tempvar' will replace the value of `var'. Other times, the shell will simply use the string value. Pretty object-oriented, huh? Be warned, though: if you `unset' a special variable, it loses its special meaning, even if you subsequently set it. The special assignment code would probably have been better put in subst.c: do_assignment_internal, in the same style as stupidly_hack_special_variables, but I wanted the changes as localized as possible. */ #define INIT_DYNAMIC_VAR(var, val, gfunc, afunc) \ do \ { \ v = bind_variable (var, (val)); \ v->dynamic_value = gfunc; \ v->assign_func = afunc; \ } \ while (0) #define INIT_DYNAMIC_ARRAY_VAR(var, gfunc, afunc) \ do \ { \ v = make_new_array_variable (var); \ v->dynamic_value = gfunc; \ v->assign_func = afunc; \ } \ while (0) static SHELL_VAR * null_assign (self, value, unused) SHELL_VAR *self; char *value; arrayind_t unused; { return (self); } #if defined (ARRAY_VARS) static SHELL_VAR * null_array_assign (self, value, ind) SHELL_VAR *self; char *value; arrayind_t ind; { return (self); } #endif /* The value of $SECONDS. This is the number of seconds since shell invocation, or, the number of seconds since the last assignment + the value of the last assignment. */ static intmax_t seconds_value_assigned; static SHELL_VAR * assign_seconds (self, value, unused) SHELL_VAR *self; char *value; arrayind_t unused; { if (legal_number (value, &seconds_value_assigned) == 0) seconds_value_assigned = 0; shell_start_time = NOW; return (self); } static SHELL_VAR * get_seconds (var) SHELL_VAR *var; { time_t time_since_start; char *p; time_since_start = NOW - shell_start_time; p = itos(seconds_value_assigned + time_since_start); FREE (value_cell (var)); VSETATTR (var, att_integer); var_setvalue (var, p); return (var); } static SHELL_VAR * init_seconds_var () { SHELL_VAR *v; v = find_variable ("SECONDS"); if (v) { if (legal_number (value_cell(v), &seconds_value_assigned) == 0) seconds_value_assigned = 0; } INIT_DYNAMIC_VAR ("SECONDS", (v ? value_cell (v) : (char *)NULL), get_seconds, assign_seconds); return v; } /* The random number seed. You can change this by setting RANDOM. */ static unsigned long rseed = 1; static int last_random_value; /* A linear congruential random number generator based on the example one in the ANSI C standard. This one isn't very good, but a more complicated one is overkill. */ /* Returns a pseudo-random number between 0 and 32767. */ static int brand () { rseed = rseed * 1103515245 + 12345; return ((unsigned int)((rseed >> 16) & 32767)); /* was % 32768 */ } /* Set the random number generator seed to SEED. */ static void sbrand (seed) unsigned long seed; { rseed = seed; last_random_value = 0; } static SHELL_VAR * assign_random (self, value, unused) SHELL_VAR *self; char *value; arrayind_t unused; { sbrand (strtoul (value, (char **)NULL, 10)); return (self); } int get_random_number () { int rv; /* Reset for command and process substitution. */ if (subshell_environment) sbrand (rseed + getpid() + NOW); do rv = brand (); while (rv == last_random_value); return rv; } static SHELL_VAR * get_random (var) SHELL_VAR *var; { int rv; char *p; rv = get_random_number (); last_random_value = rv; p = itos (rv); FREE (value_cell (var)); VSETATTR (var, att_integer); var_setvalue (var, p); return (var); } static SHELL_VAR * assign_lineno (var, value, unused) SHELL_VAR *var; char *value; arrayind_t unused; { intmax_t new_value; if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0) new_value = 0; line_number = new_value; return var; } /* Function which returns the current line number. */ static SHELL_VAR * get_lineno (var) SHELL_VAR *var; { char *p; int ln; ln = executing_line_number (); p = itos (ln); FREE (value_cell (var)); var_setvalue (var, p); return (var); } #if defined (HISTORY) static SHELL_VAR * get_histcmd (var) SHELL_VAR *var; { char *p; p = itos (history_number ()); FREE (value_cell (var)); var_setvalue (var, p); return (var); } #endif #if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) static SHELL_VAR * assign_dirstack (self, value, ind) SHELL_VAR *self; char *value; arrayind_t ind; { set_dirstack_element (ind, 1, value); return self; } static SHELL_VAR * get_dirstack (self) SHELL_VAR *self; { ARRAY *a; WORD_LIST *l; l = get_directory_stack (); a = array_from_word_list (l); array_dispose (array_cell (self)); dispose_words (l); var_setarray (self, a); return self; } static SHELL_VAR * init_dirstack_var () { SHELL_VAR *v; v = find_variable ("DIRSTACK"); if (v) return v; INIT_DYNAMIC_ARRAY_VAR ("DIRSTACK", get_dirstack, assign_dirstack); return v; } #endif /* PUSHD AND POPD && ARRAY_VARS */ #if defined (ARRAY_VARS) /* We don't want to initialize the group set with a call to getgroups() unless we're asked to, but we only want to do it once. */ static SHELL_VAR * get_groupset (self) SHELL_VAR *self; { register int i; int ng; ARRAY *a; static char **group_set = (char **)NULL; if (group_set == 0) { group_set = get_group_list (&ng); a = array_cell (self); for (i = 0; i < ng; i++) array_insert (a, i, group_set[i]); } return (self); } static SHELL_VAR * init_groups_var () { SHELL_VAR *v; v = find_variable ("GROUPS"); if (v) return (v); INIT_DYNAMIC_ARRAY_VAR ("GROUPS", get_groupset, null_array_assign); VSETATTR (v, att_noassign); return v; } #endif /* ARRAY_VARS */ static SHELL_VAR * get_funcname (self) SHELL_VAR *self; { char *t; if (variable_context && this_shell_function) { FREE (value_cell (self)); t = savestring (this_shell_function->name); var_setvalue (self, t); } return (self); } void make_funcname_visible (on_or_off) int on_or_off; { SHELL_VAR *v; v = find_variable ("FUNCNAME"); if (v == 0 || v->dynamic_value == 0) return; if (on_or_off) VUNSETATTR (v, att_invisible); else VSETATTR (v, att_invisible); } static SHELL_VAR * init_funcname_var () { SHELL_VAR *v; v = find_variable ("FUNCNAME"); if (v) return v; INIT_DYNAMIC_VAR ("FUNCNAME", (char *)NULL, get_funcname, null_assign); VSETATTR (v, att_invisible|att_noassign); return v; } static void initialize_dynamic_variables () { SHELL_VAR *v; v = init_seconds_var (); INIT_DYNAMIC_VAR ("RANDOM", (char *)NULL, get_random, assign_random); INIT_DYNAMIC_VAR ("LINENO", (char *)NULL, get_lineno, assign_lineno); #if defined (HISTORY) INIT_DYNAMIC_VAR ("HISTCMD", (char *)NULL, get_histcmd, (sh_var_assign_func_t *)NULL); #endif #if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) v = init_dirstack_var (); #endif /* PUSHD_AND_POPD && ARRAY_VARS */ #if defined (ARRAY_VARS) v = init_groups_var (); #endif v = init_funcname_var (); } /* **************************************************************** */ /* */ /* Retrieving variables and values */ /* */ /* **************************************************************** */ /* How to get a pointer to the shell variable or function named NAME. HASHED_VARS is a pointer to the hash table containing the list of interest (either variables or functions). */ static SHELL_VAR * hash_lookup (name, hashed_vars) const char *name; HASH_TABLE *hashed_vars; { BUCKET_CONTENTS *bucket; bucket = hash_search (name, hashed_vars, 0); return (bucket ? (SHELL_VAR *)bucket->data : (SHELL_VAR *)NULL); } SHELL_VAR * var_lookup (name, vcontext) const char *name; VAR_CONTEXT *vcontext; { VAR_CONTEXT *vc; SHELL_VAR *v; v = (SHELL_VAR *)NULL; for (vc = vcontext; vc; vc = vc->down) if (v = hash_lookup (name, vc->table)) break; return v; } /* Look up the variable entry named NAME. If SEARCH_TEMPENV is non-zero, then also search the temporarily built list of exported variables. The lookup order is: temporary_env shell_variables list */ SHELL_VAR * find_variable_internal (name, search_tempenv) const char *name; int search_tempenv; { SHELL_VAR *var; var = (SHELL_VAR *)NULL; /* If explicitly requested, first look in the temporary environment for the variable. This allows constructs such as "foo=x eval 'echo $foo'" to get the `exported' value of $foo. This happens if we are executing a function or builtin, or if we are looking up a variable in a "subshell environment". */ if ((search_tempenv || subshell_environment) && temporary_env) var = hash_lookup (name, temporary_env); if (var == 0) var = var_lookup (name, shell_variables); if (var == 0) return ((SHELL_VAR *)NULL); return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var); } /* Look up the variable entry named NAME. Returns the entry or NULL. */ SHELL_VAR * find_variable (name) const char *name; { return (find_variable_internal (name, this_shell_builtin != 0)); } /* Look up the function entry whose name matches STRING. Returns the entry or NULL. */ SHELL_VAR * find_function (name) const char *name; { return (hash_lookup (name, shell_functions)); } /* Return the value of VAR. VAR is assumed to have been the result of a lookup without any subscript, if arrays are compiled into the shell. */ char * get_variable_value (var) SHELL_VAR *var; { if (var == 0) return ((char *)NULL); #if defined (ARRAY_VARS) else if (array_p (var)) return (array_reference (array_cell (var), 0)); #endif else return (value_cell (var)); } /* Return the string value of a variable. Return NULL if the variable doesn't exist. Don't cons a new string. This is a potential memory leak if the variable is found in the temporary environment. Since functions and variables have separate name spaces, returns NULL if var_name is a shell function only. */ char * get_string_value (var_name) const char *var_name; { SHELL_VAR *var; var = find_variable (var_name); return ((var) ? get_variable_value (var) : (char *)NULL); } /* This is present for use by the tilde and readline libraries. */ char * sh_get_env_value (v) const char *v; { return get_string_value (v); } /* **************************************************************** */ /* */ /* Creating and setting variables */ /* */ /* **************************************************************** */ /* Set NAME to VALUE if NAME has no value. */ SHELL_VAR * set_if_not (name, value) char *name, *value; { SHELL_VAR *v; v = find_variable (name); if (v == 0) v = bind_variable_internal (name, value, global_variables->table, HASH_NOSRCH); return (v); } /* Create a local variable referenced by NAME. */ SHELL_VAR * make_local_variable (name) const char *name; { SHELL_VAR *new_var, *old_var; VAR_CONTEXT *vc; int was_tmpvar; char *tmp_value; /* local foo; local foo; is a no-op. */ old_var = find_variable (name); if (old_var && local_p (old_var) && old_var->context == variable_context) return (old_var); was_tmpvar = old_var && tempvar_p (old_var); if (was_tmpvar) tmp_value = value_cell (old_var); for (vc = shell_variables; vc; vc = vc->down) if (vc_isfuncenv (vc) && vc->scope == variable_context) break; if (vc == 0) { internal_error ("make_local_variable: no function context at current scope found"); return ((SHELL_VAR *)NULL); } else if (vc->table == 0) vc->table = hash_create (TEMPENV_HASH_BUCKETS); /* Since this is called only from the local/declare/typeset code, we can call builtin_error here without worry (of course, it will also work for anything that sets this_command_name). Variables with the `noassign' attribute may not be made local. The test against old_var's context level is to disallow local copies of readonly global variables (since I believe that this could be a security hole). Readonly copies of calling function local variables are OK. */ if (old_var && (noassign_p (old_var) || (readonly_p (old_var) && old_var->context == 0))) { if (readonly_p (old_var)) sh_readonly (name); return ((SHELL_VAR *)NULL); } if (old_var == 0) new_var = bind_variable_internal (name, "", vc->table, HASH_NOSRCH); else { new_var = make_new_variable (name, vc->table); /* If we found this variable in one of the temporary environments, inherit its value. Watch to see if this causes problems with things like `x=4 local x'. */ if (was_tmpvar) var_setvalue (new_var, savestring (tmp_value)); new_var->attributes = exported_p (old_var) ? att_exported : 0; } vc->flags |= VC_HASLOCAL; new_var->context = variable_context; VSETATTR (new_var, att_local); if (ifsname (name)) setifs (new_var); return (new_var); } #if defined (ARRAY_VARS) SHELL_VAR * make_local_array_variable (name) char *name; { SHELL_VAR *var; ARRAY *array; var = make_local_variable (name); if (var == 0) return var; array = array_create (); FREE (value_cell(var)); var_setarray (var, array); VSETATTR (var, att_array); return var; } #endif /* ARRAY_VARS */ /* Create a new shell variable with name NAME. */ static SHELL_VAR * new_shell_variable (name) const char *name; { SHELL_VAR *entry; entry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); entry->name = savestring (name); var_setvalue (entry, (char *)NULL); CLEAR_EXPORTSTR (entry); entry->dynamic_value = (sh_var_value_func_t *)NULL; entry->assign_func = (sh_var_assign_func_t *)NULL; entry->attributes = 0; /* Always assume variables are to be made at toplevel! make_local_variable has the responsibilty of changing the variable context. */ entry->context = 0; return (entry); } /* Create a new shell variable with name NAME and add it to the hash table TABLE. */ static SHELL_VAR * make_new_variable (name, table) const char *name; HASH_TABLE *table; { SHELL_VAR *entry; BUCKET_CONTENTS *elt; entry = new_shell_variable (name); /* Make sure we have a shell_variables hash table to add to. */ if (shell_variables == 0) { shell_variables = global_variables = new_var_context ((char *)NULL, 0); shell_variables->scope = 0; shell_variables->table = hash_create (0); } elt = hash_insert (savestring (name), table, HASH_NOSRCH); elt->data = (PTR_T)entry; return entry; } #if defined (ARRAY_VARS) SHELL_VAR * make_new_array_variable (name) char *name; { SHELL_VAR *entry; ARRAY *array; entry = make_new_variable (name, global_variables->table); array = array_create (); var_setarray (entry, array); VSETATTR (entry, att_array); return entry; } #endif char * make_variable_value (var, value) SHELL_VAR *var; char *value; { char *retval; intmax_t lval; int expok; /* If this variable has had its type set to integer (via `declare -i'), then do expression evaluation on it and store the result. The functions in expr.c (evalexp()) and bind_int_variable() are responsible for turning off the integer flag if they don't want further evaluation done. */ if (integer_p (var)) { lval = evalexp (value, &expok); if (expok == 0) jump_to_top_level (DISCARD); retval = itos (lval); } else if (value) { if (*value) retval = savestring (value); else { retval = (char *)xmalloc (1); retval[0] = '\0'; } } else retval = (char *)NULL; return retval; } /* Bind a variable NAME to VALUE in the HASH_TABLE TABLE, which may be the temporary environment (but usually is not). */ static SHELL_VAR * bind_variable_internal (name, value, table, hflags) const char *name; char *value; HASH_TABLE *table; int hflags; { char *newval; SHELL_VAR *entry; entry = (hflags & HASH_NOSRCH) ? (SHELL_VAR *)NULL : hash_lookup (name, table); if (entry == 0) { entry = make_new_variable (name, table); var_setvalue (entry, make_variable_value (entry, value)); } else if (entry->assign_func) /* array vars have assign functions now */ { INVALIDATE_EXPORTSTR (entry); return ((*(entry->assign_func)) (entry, value, -1)); } else { if (readonly_p (entry) || noassign_p (entry)) { if (readonly_p (entry)) err_readonly (name); return (entry); } /* Variables which are bound are visible. */ VUNSETATTR (entry, att_invisible); newval = make_variable_value (entry, value); /* Invalidate any cached export string */ INVALIDATE_EXPORTSTR (entry); #if defined (ARRAY_VARS) /* XXX -- this bears looking at again -- XXX */ /* If an existing array variable x is being assigned to with x=b or `read x' or something of that nature, silently convert it to x[0]=b or `read x[0]'. */ if (array_p (entry)) { array_insert (array_cell (entry), 0, newval); free (newval); } else #endif { FREE (value_cell (entry)); var_setvalue (entry, newval); } } if (mark_modified_vars) VSETATTR (entry, att_exported); if (exported_p (entry)) array_needs_making = 1; return (entry); } /* Bind a variable NAME to VALUE. This conses up the name and value strings. If we have a temporary environment, we bind there first, then we bind into shell_variables. */ SHELL_VAR * bind_variable (name, value) const char *name; char *value; { SHELL_VAR *v; VAR_CONTEXT *vc; if (shell_variables == 0) { shell_variables = global_variables = new_var_context ((char *)NULL, 0); shell_variables->scope = 0; shell_variables->table = hash_create (0); } /* If we have a temporary environment, look there first for the variable, and, if found, modify the value there before modifying it in the shell_variables table. This allows sourced scripts to modify values given to them in a temporary environment while modifying the variable value that the caller sees. */ if (temporary_env) bind_tempenv_variable (name, value); /* XXX -- handle local variables here. */ for (vc = shell_variables; vc; vc = vc->down) { if (vc_isfuncenv (vc) || vc_isbltnenv (vc)) { v = hash_lookup (name, vc->table); if (v) return (bind_variable_internal (name, value, vc->table, 0)); } } return (bind_variable_internal (name, value, global_variables->table, 0)); } /* Make VAR, a simple shell variable, have value VALUE. Once assigned a value, variables are no longer invisible. This is a duplicate of part of the internals of bind_variable. If the variable is exported, or all modified variables should be exported, mark the variable for export and note that the export environment needs to be recreated. */ SHELL_VAR * bind_variable_value (var, value) SHELL_VAR *var; char *value; { char *t; VUNSETATTR (var, att_invisible); t = make_variable_value (var, value); FREE (value_cell (var)); var_setvalue (var, t); INVALIDATE_EXPORTSTR (var); if (mark_modified_vars) VSETATTR (var, att_exported); if (exported_p (var)) array_needs_making = 1; return (var); } /* Bind/create a shell variable with the name LHS to the RHS. This creates or modifies a variable such that it is an integer. This used to be in expr.c, but it is here so that all of the variable binding stuff is localized. Since we don't want any recursive evaluation from bind_variable() (possible without this code, since bind_variable() calls the evaluator for variables with the integer attribute set), we temporarily turn off the integer attribute for each variable we set here, then turn it back on after binding as necessary. */ SHELL_VAR * bind_int_variable (lhs, rhs) char *lhs, *rhs; { register SHELL_VAR *v; char *t; int isint, isarr; isint = isarr = 0; #if defined (ARRAY_VARS) if (t = xstrchr (lhs, '[')) /*]*/ { isarr = 1; v = array_variable_part (lhs, (char **)0, (int *)0); } else #endif v = find_variable (lhs); if (v) { isint = integer_p (v); VUNSETATTR (v, att_integer); } #if defined (ARRAY_VARS) if (isarr) v = assign_array_element (lhs, rhs); else #endif v = bind_variable (lhs, rhs); if (isint) VSETATTR (v, att_integer); return (v); } SHELL_VAR * bind_var_to_int (var, val) char *var; intmax_t val; { char ibuf[INT_STRLEN_BOUND (intmax_t) + 1], *p; p = fmtulong (val, 10, ibuf, sizeof (ibuf), 0); return (bind_int_variable (var, p)); } /* Do a function binding to a variable. You pass the name and the command to bind to. This conses the name and command. */ SHELL_VAR * bind_function (name, value) const char *name; COMMAND *value; { SHELL_VAR *entry; entry = find_function (name); if (entry == 0) { BUCKET_CONTENTS *elt; elt = hash_insert (savestring (name), shell_functions, HASH_NOSRCH); entry = new_shell_variable (name); elt->data = (PTR_T)entry; } else INVALIDATE_EXPORTSTR (entry); if (var_isset (entry)) dispose_command (function_cell (entry)); if (value) var_setfunc (entry, copy_command (value)); else var_setfunc (entry, 0); VSETATTR (entry, att_function); if (mark_modified_vars) VSETATTR (entry, att_exported); VUNSETATTR (entry, att_invisible); /* Just to be sure */ if (exported_p (entry)) array_needs_making = 1; #if defined (PROGRAMMABLE_COMPLETION) set_itemlist_dirty (&it_functions); #endif return (entry); } /* Add STRING, which is of the form foo=bar, to the temporary environment HASH_TABLE (temporary_env). The functions in execute_cmd.c are responsible for moving the main temporary env to one of the other temporary environments. The expansion code in subst.c calls this. */ int assign_in_env (string) const char *string; { int offset; char *name, *temp, *value; SHELL_VAR *var; offset = assignment (string); name = savestring (string); value = (char *)NULL; if (name[offset] == '=') { name[offset] = 0; var = find_variable (name); if (var && (readonly_p (var) || noassign_p (var))) { if (readonly_p (var)) err_readonly (name); free (name); return (0); } temp = name + offset + 1; temp = (xstrchr (temp, '~') != 0) ? bash_tilde_expand (temp, 1) : savestring (temp); value = expand_string_unsplit_to_string (temp, 0); free (temp); } if (temporary_env == 0) temporary_env = hash_create (TEMPENV_HASH_BUCKETS); var = hash_lookup (name, temporary_env); if (var == 0) var = make_new_variable (name, temporary_env); else FREE (value_cell (var)); if (value == 0) { value = (char *)xmalloc (1); /* like do_assignment_internal */ value[0] = '\0'; } var_setvalue (var, value); var->attributes |= (att_exported|att_tempvar); var->context = variable_context; /* XXX */ INVALIDATE_EXPORTSTR (var); var->exportstr = mk_env_string (name, value); array_needs_making = 1; if (ifsname (name)) setifs (var); if (echo_command_at_execute) { /* The Korn shell prints the `+ ' in front of assignment statements, so we do too. */ fprintf (stderr, "%s%s=%s\n", indirection_level_string (), name, value); fflush (stderr); } return 1; } /* **************************************************************** */ /* */ /* Copying variables */ /* */ /* **************************************************************** */ #ifdef INCLUDE_UNUSED /* Copy VAR to a new data structure and return that structure. */ SHELL_VAR * copy_variable (var) SHELL_VAR *var; { SHELL_VAR *copy = (SHELL_VAR *)NULL; if (var) { copy = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); copy->attributes = var->attributes; copy->name = savestring (var->name); if (function_p (var)) var_setfunc (copy, copy_command (function_cell (var))); #if defined (ARRAY_VARS) else if (array_p (var)) var_setarray (copy, dup_array (array_cell (var))); #endif else if (value_cell (var)) var_setvalue (copy, savestring (value_cell (var))); else var_setvalue (copy, (char *)NULL); copy->dynamic_value = var->dynamic_value; copy->assign_func = var->assign_func; copy->exportstr = COPY_EXPORTSTR (var); copy->context = var->context; } return (copy); } #endif /* **************************************************************** */ /* */ /* Deleting and unsetting variables */ /* */ /* **************************************************************** */ /* Dispose of the information attached to VAR. */ void dispose_variable (var) SHELL_VAR *var; { if (var == 0) return; if (function_p (var)) dispose_command (function_cell (var)); #if defined (ARRAY_VARS) else if (array_p (var)) array_dispose (array_cell (var)); #endif else FREE (value_cell (var)); FREE_EXPORTSTR (var); free (var->name); if (exported_p (var)) array_needs_making = 1; free (var); } /* Unset the shell variable referenced by NAME. */ int unbind_variable (name) const char *name; { return makunbound (name, shell_variables); } /* Unset the shell function named NAME. */ int unbind_func (name) const char *name; { BUCKET_CONTENTS *elt; SHELL_VAR *func; elt = hash_remove (name, shell_functions, 0); if (elt == 0) return -1; #if defined (PROGRAMMABLE_COMPLETION) set_itemlist_dirty (&it_functions); #endif func = (SHELL_VAR *)elt->data; if (func) { if (exported_p (func)) array_needs_making++; dispose_variable (func); } free (elt->key); free (elt); return 0; } /* Make the variable associated with NAME go away. HASH_LIST is the hash table from which this variable should be deleted (either shell_variables or shell_functions). Returns non-zero if the variable couldn't be found. */ int makunbound (name, vc) const char *name; VAR_CONTEXT *vc; { BUCKET_CONTENTS *elt, *new_elt; SHELL_VAR *old_var; VAR_CONTEXT *v; char *t; for (elt = (BUCKET_CONTENTS *)NULL, v = vc; v; v = v->down) if (elt = hash_remove (name, v->table, 0)) break; if (elt == 0) return (-1); old_var = (SHELL_VAR *)elt->data; if (old_var && exported_p (old_var)) array_needs_making++; /* If we're unsetting a local variable and we're still executing inside the function, just mark the variable as invisible. The function eventually called by pop_var_context() will clean it up later. This must be done so that if the variable is subsequently assigned a new value inside the function, the `local' attribute is still present. We also need to add it back into the correct hash table. */ if (old_var && local_p (old_var) && variable_context == old_var->context) { VSETATTR (old_var, att_invisible); FREE (value_cell (old_var)); var_setvalue (old_var, (char *)NULL); INVALIDATE_EXPORTSTR (old_var); new_elt = hash_insert (savestring (old_var->name), v->table, 0); new_elt->data = (PTR_T)old_var; stupidly_hack_special_variables (old_var->name); free (elt->key); free (elt); return (0); } /* Have to save a copy of name here, because it might refer to old_var->name. If so, stupidly_hack_special_variables will reference freed memory. */ t = savestring (name); free (elt->key); free (elt); dispose_variable (old_var); stupidly_hack_special_variables (t); free (t); return (0); } /* Get rid of all of the variables in the current context. */ void kill_all_local_variables () { VAR_CONTEXT *vc; for (vc = shell_variables; vc; vc = vc->down) if (vc_isfuncenv (vc) && vc->scope == variable_context) break; if (vc == 0) return; /* XXX */ if (vc->table && vc_haslocals (vc)) { delete_all_variables (vc->table); hash_dispose (vc->table); } vc->table = (HASH_TABLE *)NULL; } static void free_variable_hash_data (data) PTR_T data; { SHELL_VAR *var; var = (SHELL_VAR *)data; dispose_variable (var); } /* Delete the entire contents of the hash table. */ void delete_all_variables (hashed_vars) HASH_TABLE *hashed_vars; { hash_flush (hashed_vars, free_variable_hash_data); } /* **************************************************************** */ /* */ /* Setting variable attributes */ /* */ /* **************************************************************** */ #define FIND_OR_MAKE_VARIABLE(name, entry) \ do \ { \ entry = find_variable (name); \ if (!entry) \ { \ entry = bind_variable (name, ""); \ if (!no_invisible_vars) entry->attributes |= att_invisible; \ } \ } \ while (0) /* Make the variable associated with NAME be readonly. If NAME does not exist yet, create it. */ void set_var_read_only (name) char *name; { SHELL_VAR *entry; FIND_OR_MAKE_VARIABLE (name, entry); VSETATTR (entry, att_readonly); } #ifdef INCLUDE_UNUSED /* Make the function associated with NAME be readonly. If NAME does not exist, we just punt, like auto_export code below. */ void set_func_read_only (name) const char *name; { SHELL_VAR *entry; entry = find_function (name); if (entry) VSETATTR (entry, att_readonly); } /* Make the variable associated with NAME be auto-exported. If NAME does not exist yet, create it. */ void set_var_auto_export (name) char *name; { SHELL_VAR *entry; FIND_OR_MAKE_VARIABLE (name, entry); set_auto_export (entry); } /* Make the function associated with NAME be auto-exported. */ void set_func_auto_export (name) const char *name; { SHELL_VAR *entry; entry = find_function (name); if (entry) set_auto_export (entry); } #endif /* **************************************************************** */ /* */ /* Creating lists of variables */ /* */ /* **************************************************************** */ static VARLIST * vlist_alloc (nentries) int nentries; { VARLIST *vlist; vlist = (VARLIST *)xmalloc (sizeof (VARLIST)); vlist->list = (SHELL_VAR **)xmalloc ((nentries + 1) * sizeof (SHELL_VAR *)); vlist->list_size = nentries; vlist->list_len = 0; vlist->list[0] = (SHELL_VAR *)NULL; return vlist; } static VARLIST * vlist_realloc (vlist, n) VARLIST *vlist; int n; { if (vlist == 0) return (vlist = vlist_alloc (n)); if (n > vlist->list_size) { vlist->list_size = n; vlist->list = (SHELL_VAR **)xrealloc (vlist->list, (vlist->list_size + 1) * sizeof (SHELL_VAR *)); } return vlist; } static void vlist_add (vlist, var, flags) VARLIST *vlist; SHELL_VAR *var; int flags; { register int i; for (i = 0; i < vlist->list_len; i++) if (STREQ (var->name, vlist->list[i]->name)) break; if (i < vlist->list_len) return; if (i >= vlist->list_size) vlist = vlist_realloc (vlist, vlist->list_size + 16); vlist->list[vlist->list_len++] = var; vlist->list[vlist->list_len] = (SHELL_VAR *)NULL; } /* Map FUNCTION over the variables in VAR_HASH_TABLE. Return an array of the variables for which FUNCTION returns a non-zero value. A NULL value for FUNCTION means to use all variables. */ SHELL_VAR ** map_over (function, vc) sh_var_map_func_t *function; VAR_CONTEXT *vc; { VAR_CONTEXT *v; VARLIST *vlist; SHELL_VAR **ret; int nentries; for (nentries = 0, v = vc; v; v = v->down) nentries += HASH_ENTRIES (v->table); if (nentries == 0) return (SHELL_VAR **)NULL; vlist = vlist_alloc (nentries); for (v = vc; v; v = v->down) flatten (v->table, function, vlist, 0); ret = vlist->list; free (vlist); return ret; } SHELL_VAR ** map_over_funcs (function) sh_var_map_func_t *function; { VARLIST *vlist; SHELL_VAR **ret; if (shell_functions == 0 || HASH_ENTRIES (shell_functions) == 0) return ((SHELL_VAR **)NULL); vlist = vlist_alloc (HASH_ENTRIES (shell_functions)); flatten (shell_functions, function, vlist, 0); ret = vlist->list; free (vlist); return ret; } /* Flatten VAR_HASH_TABLE, applying FUNC to each member and adding those elements for which FUNC succeeds to VLIST->list. FLAGS is reserved for future use. Only unique names are added to VLIST. If FUNC is NULL, each variable in VAR_HASH_TABLE is added to VLIST. If VLIST is NULL, FUNC is applied to each SHELL_VAR in VAR_HASH_TABLE. If VLIST and FUNC are both NULL, nothing happens. */ static void flatten (var_hash_table, func, vlist, flags) HASH_TABLE *var_hash_table; sh_var_map_func_t *func; VARLIST *vlist; int flags; { register int i; register BUCKET_CONTENTS *tlist; int r; SHELL_VAR *var; if (var_hash_table == 0 || (HASH_ENTRIES (var_hash_table) == 0) || (vlist == 0 && func == 0)) return; for (i = 0; i < var_hash_table->nbuckets; i++) { for (tlist = hash_items (i, var_hash_table); tlist; tlist = tlist->next) { var = (SHELL_VAR *)tlist->data; r = func ? (*func) (var) : 1; if (r && vlist) vlist_add (vlist, var, flags); } } } void sort_variables (array) SHELL_VAR **array; { qsort (array, strvec_len ((char **)array), sizeof (SHELL_VAR *), (QSFUNC *)qsort_var_comp); } static int qsort_var_comp (var1, var2) SHELL_VAR **var1, **var2; { int result; if ((result = (*var1)->name[0] - (*var2)->name[0]) == 0) result = strcmp ((*var1)->name, (*var2)->name); return (result); } /* Apply FUNC to each variable in SHELL_VARIABLES, adding each one for which FUNC succeeds to an array of SHELL_VAR *s. Returns the array. */ static SHELL_VAR ** vapply (func) sh_var_map_func_t *func; { SHELL_VAR **list; list = map_over (func, shell_variables); if (list /* && posixly_correct */) sort_variables (list); return (list); } /* Apply FUNC to each variable in SHELL_FUNCTIONS, adding each one for which FUNC succeeds to an array of SHELL_VAR *s. Returns the array. */ static SHELL_VAR ** fapply (func) sh_var_map_func_t *func; { SHELL_VAR **list; list = map_over_funcs (func); if (list /* && posixly_correct */) sort_variables (list); return (list); } /* Create a NULL terminated array of all the shell variables. */ SHELL_VAR ** all_shell_variables () { return (vapply ((sh_var_map_func_t *)NULL)); } /* Create a NULL terminated array of all the shell functions. */ SHELL_VAR ** all_shell_functions () { return (fapply ((sh_var_map_func_t *)NULL)); } static int visible_var (var) SHELL_VAR *var; { return (invisible_p (var) == 0); } SHELL_VAR ** all_visible_functions () { return (fapply (visible_var)); } SHELL_VAR ** all_visible_variables () { return (vapply (visible_var)); } /* Return non-zero if the variable VAR is visible and exported. Array variables cannot be exported. */ static int visible_and_exported (var) SHELL_VAR *var; { return (invisible_p (var) == 0 && exported_p (var)); } /* Return non-zero if VAR is a local variable in the current context and is exported. */ static int local_and_exported (var) SHELL_VAR *var; { return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context && exported_p (var)); } SHELL_VAR ** all_exported_variables () { return (vapply (visible_and_exported)); } SHELL_VAR ** local_exported_variables () { return (vapply (local_and_exported)); } static int variable_in_context (var) SHELL_VAR *var; { return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context); } SHELL_VAR ** all_local_variables () { VARLIST *vlist; SHELL_VAR **ret; VAR_CONTEXT *vc; vc = shell_variables; for (vc = shell_variables; vc; vc = vc->down) if (vc_isfuncenv (vc) && vc->scope == variable_context) break; if (vc == 0) { internal_error ("all_local_variables: no function context at current scope found"); return (SHELL_VAR **)NULL; } if (vc->table == 0 || HASH_ENTRIES (vc->table) == 0 || vc_haslocals (vc) == 0) return (SHELL_VAR **)NULL; vlist = vlist_alloc (HASH_ENTRIES (vc->table)); flatten (vc->table, variable_in_context, vlist, 0); ret = vlist->list; free (vlist); if (ret) sort_variables (ret); return ret; } #if defined (ARRAY_VARS) /* Return non-zero if the variable VAR is visible and an array. */ static int visible_array_vars (var) SHELL_VAR *var; { return (invisible_p (var) == 0 && array_p (var)); } SHELL_VAR ** all_array_variables () { return (vapply (visible_array_vars)); } #endif /* ARRAY_VARS */ char ** all_variables_matching_prefix (prefix) const char *prefix; { SHELL_VAR **varlist; char **rlist; int vind, rind, plen; plen = STRLEN (prefix); varlist = all_visible_variables (); for (vind = 0; varlist && varlist[vind]; vind++) ; if (varlist == 0 || vind == 0) return ((char **)NULL); rlist = strvec_create (vind + 1); for (vind = rind = 0; varlist[vind]; vind++) { if (plen == 0 || STREQN (prefix, varlist[vind]->name, plen)) rlist[rind++] = savestring (varlist[vind]->name); } rlist[rind] = (char *)0; free (varlist); return rlist; } /* **************************************************************** */ /* */ /* Managing temporary variable scopes */ /* */ /* **************************************************************** */ /* Make variable NAME have VALUE in the temporary environment. */ static SHELL_VAR * bind_tempenv_variable (name, value) const char *name; char *value; { SHELL_VAR *var; var = temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL; if (var) { FREE (value_cell (var)); var_setvalue (var, savestring (value)); INVALIDATE_EXPORTSTR (var); } return (var); } /* Find a variable in the temporary environment that is named NAME. Return the SHELL_VAR *, or NULL if not found. */ SHELL_VAR * find_tempenv_variable (name) const char *name; { return (temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL); } /* Push the variable described by (SHELL_VAR *)DATA down to the next variable context from the temporary environment. */ static void push_temp_var (data) PTR_T data; { SHELL_VAR *var, *v; HASH_TABLE *binding_table; var = (SHELL_VAR *)data; binding_table = shell_variables->table; if (binding_table == 0) { if (shell_variables == global_variables) /* shouldn't happen */ binding_table = shell_variables->table = global_variables->table = hash_create (0); else binding_table = shell_variables->table = hash_create (TEMPENV_HASH_BUCKETS); } v = bind_variable_internal (var->name, value_cell (var), binding_table, 0); /* XXX - should we set the context here? It shouldn't matter because of how assign_in_env works, but might want to check. */ if (binding_table == global_variables->table) /* XXX */ var->attributes &= ~(att_tempvar|att_propagate); else { var->attributes |= att_propagate; if (binding_table == shell_variables->table) shell_variables->flags |= VC_HASTMPVAR; } v->attributes |= var->attributes; dispose_variable (var); } static void propagate_temp_var (data) PTR_T data; { SHELL_VAR *var; var = (SHELL_VAR *)data; if (tempvar_p (var) && (var->attributes & att_propagate)) push_temp_var (data); else dispose_variable (var); } /* Free the storage used in the hash table for temporary environment variables. PUSHF is a function to be called to free each hash table entry. It takes care of pushing variables to previous scopes if appropriate. */ static void dispose_temporary_env (pushf) sh_free_func_t *pushf; { hash_flush (temporary_env, pushf); hash_dispose (temporary_env); temporary_env = (HASH_TABLE *)NULL; array_needs_making = 1; sv_ifs ("IFS"); /* XXX here for now */ } void dispose_used_env_vars () { if (temporary_env) dispose_temporary_env (propagate_temp_var); } /* Take all of the shell variables in the temporary environment HASH_TABLE and make shell variables from them at the current variable context. */ void merge_temporary_env () { if (temporary_env) dispose_temporary_env (push_temp_var); } /* **************************************************************** */ /* */ /* Creating and manipulating the environment */ /* */ /* **************************************************************** */ static inline char * mk_env_string (name, value) const char *name, *value; { int name_len, value_len; char *p; name_len = strlen (name); value_len = STRLEN (value); p = (char *)xmalloc (2 + name_len + value_len); strcpy (p, name); p[name_len] = '='; if (value && *value) strcpy (p + name_len + 1, value); else p[name_len + 1] = '\0'; return (p); } #ifdef DEBUG /* Debugging */ static int valid_exportstr (v) SHELL_VAR *v; { char *s; s = v->exportstr; if (legal_variable_starter ((unsigned char)*s) == 0) { internal_error ("invalid character %d in exportstr for %s", *s, v->name); return (0); } for (s = v->exportstr + 1; s && *s; s++) { if (*s == '=') break; if (legal_variable_char ((unsigned char)*s) == 0) { internal_error ("invalid character %d in exportstr for %s", *s, v->name); return (0); } } if (*s != '=') { internal_error ("no `=' in exportstr for %s", v->name); return (0); } return (1); } #endif static char ** make_env_array_from_var_list (vars) SHELL_VAR **vars; { register int i, list_index; register SHELL_VAR *var; char **list, *value; list = strvec_create ((1 + strvec_len ((char **)vars))); #define USE_EXPORTSTR (value == var->exportstr) for (i = 0, list_index = 0; var = vars[i]; i++) { #if defined (__CYGWIN__) /* We don't use the exportstr stuff on Cygwin at all. */ INVALIDATE_EXPORTSTR (var); #endif if (var->exportstr) value = var->exportstr; else if (function_p (var)) value = named_function_string ((char *)NULL, function_cell (var), 0); #if defined (ARRAY_VARS) else if (array_p (var)) # if 0 value = array_to_assignment_string (array_cell (var)); # else continue; /* XXX array vars cannot yet be exported */ # endif #endif else value = value_cell (var); if (value) { /* Gee, I'd like to get away with not using savestring() if we're using the cached exportstr... */ list[list_index] = USE_EXPORTSTR ? savestring (value) : mk_env_string (var->name, value); if (USE_EXPORTSTR == 0) SAVE_EXPORTSTR (var, list[list_index]); list_index++; #undef USE_EXPORTSTR #if 0 /* not yet */ #if defined (ARRAY_VARS) if (array_p (var)) free (value); #endif #endif } } list[list_index] = (char *)NULL; return (list); } /* Make an array of assignment statements from the hash table HASHED_VARS which contains SHELL_VARs. Only visible, exported variables are eligible. */ static char ** make_var_export_array (vcxt) VAR_CONTEXT *vcxt; { char **list; SHELL_VAR **vars; vars = map_over (visible_and_exported, vcxt); if (vars == 0) return (char **)NULL; list = make_env_array_from_var_list (vars); free (vars); return (list); } static char ** make_func_export_array () { char **list; SHELL_VAR **vars; vars = map_over_funcs (visible_and_exported); if (vars == 0) return (char **)NULL; list = make_env_array_from_var_list (vars); free (vars); return (list); } /* Add ENVSTR to the end of the exported environment, EXPORT_ENV. */ #define add_to_export_env(envstr,do_alloc) \ do \ { \ if (export_env_index >= (export_env_size - 1)) \ { \ export_env_size += 16; \ export_env = strvec_resize (export_env, export_env_size); \ } \ export_env[export_env_index++] = (do_alloc) ? savestring (envstr) : envstr; \ export_env[export_env_index] = (char *)NULL; \ } while (0) /* Add ASSIGN to EXPORT_ENV, or supercede a previous assignment in the array with the same left-hand side. Return the new EXPORT_ENV. */ char ** add_or_supercede_exported_var (assign, do_alloc) char *assign; int do_alloc; { register int i; int equal_offset; equal_offset = assignment (assign); if (equal_offset == 0) return (export_env); /* If this is a function, then only supersede the function definition. We do this by including the `=() {' in the comparison, like initialize_shell_variables does. */ if (assign[equal_offset + 1] == '(' && strncmp (assign + equal_offset + 2, ") {", 3) == 0) /* } */ equal_offset += 4; for (i = 0; i < export_env_index; i++) { if (STREQN (assign, export_env[i], equal_offset + 1)) { free (export_env[i]); export_env[i] = do_alloc ? savestring (assign) : assign; return (export_env); } } add_to_export_env (assign, do_alloc); return (export_env); } static void add_temp_array_to_env (temp_array, do_alloc, do_supercede) char **temp_array; int do_alloc, do_supercede; { register int i; if (temp_array == 0) return; for (i = 0; temp_array[i]; i++) { if (do_supercede) export_env = add_or_supercede_exported_var (temp_array[i], do_alloc); else add_to_export_env (temp_array[i], do_alloc); } free (temp_array); } /* Make the environment array for the command about to be executed, if the array needs making. Otherwise, do nothing. If a shell action could change the array that commands receive for their environment, then the code should `array_needs_making++'. The order to add to the array is: temporary_env list of var contexts whose head is shell_variables shell_functions This is the shell variable lookup order. We add only new variable names at each step, which allows local variables and variables in the temporary environments to shadow variables in the global (or any previous) scope. */ static int n_shell_variables () { VAR_CONTEXT *vc; int n; for (n = 0, vc = shell_variables; vc; vc = vc->down) n += HASH_ENTRIES (vc->table); return n; } void maybe_make_export_env () { register char **temp_array; int new_size; VAR_CONTEXT *tcxt; if (array_needs_making) { if (export_env) strvec_flush (export_env); /* Make a guess based on how many shell variables and functions we have. Since there will always be array variables, and array variables are not (yet) exported, this will always be big enough for the exported variables and functions. */ new_size = n_shell_variables () + HASH_ENTRIES (shell_functions) + 1 + HASH_ENTRIES (temporary_env); if (new_size > export_env_size) { export_env_size = new_size; export_env = strvec_resize (export_env, export_env_size); } export_env[export_env_index = 0] = (char *)NULL; /* Make a dummy variable context from the temporary_env, stick it on the front of shell_variables, call make_var_export_array on the whole thing to flatten it, and convert the list of SHELL_VAR *s to the form needed by the environment. */ if (temporary_env) { tcxt = new_var_context ((char *)NULL, 0); tcxt->table = temporary_env; tcxt->down = shell_variables; } else tcxt = shell_variables; temp_array = make_var_export_array (tcxt); if (temp_array) add_temp_array_to_env (temp_array, 0, 0); if (tcxt != shell_variables) free (tcxt); #if defined (RESTRICTED_SHELL) /* Restricted shells may not export shell functions. */ temp_array = restricted ? (char **)0 : make_func_export_array (); #else temp_array = make_func_export_array (); #endif if (temp_array) add_temp_array_to_env (temp_array, 0, 0); array_needs_making = 0; } } /* This is an efficiency hack. PWD and OLDPWD are auto-exported, so we will need to remake the exported environment every time we change directories. `_' is always put into the environment for every external command, so without special treatment it will always cause the environment to be remade. If there is no other reason to make the exported environment, we can just update the variables in place and mark the exported environment as no longer needing a remake. */ void update_export_env_inplace (env_prefix, preflen, value) char *env_prefix; int preflen; char *value; { char *evar; evar = (char *)xmalloc (STRLEN (value) + preflen + 1); strcpy (evar, env_prefix); if (value) strcpy (evar + preflen, value); export_env = add_or_supercede_exported_var (evar, 0); } /* We always put _ in the environment as the name of this command. */ void put_command_name_into_env (command_name) char *command_name; { update_export_env_inplace ("_=", 2, command_name); } #if 0 /* UNUSED -- it caused too many problems */ void put_gnu_argv_flags_into_env (pid, flags_string) intmax_t pid; char *flags_string; { char *dummy, *pbuf; int l, fl; pbuf = itos (pid); l = strlen (pbuf); fl = strlen (flags_string); dummy = (char *)xmalloc (l + fl + 30); dummy[0] = '_'; strcpy (dummy + 1, pbuf); strcpy (dummy + 1 + l, "_GNU_nonoption_argv_flags_"); dummy[l + 27] = '='; strcpy (dummy + l + 28, flags_string); free (pbuf); export_env = add_or_supercede_exported_var (dummy, 0); } #endif /* **************************************************************** */ /* */ /* Managing variable contexts */ /* */ /* **************************************************************** */ /* Allocate and return a new variable context with NAME and FLAGS. NAME can be NULL. */ VAR_CONTEXT * new_var_context (name, flags) char *name; int flags; { VAR_CONTEXT *vc; vc = (VAR_CONTEXT *)xmalloc (sizeof (VAR_CONTEXT)); vc->name = name ? savestring (name) : (char *)NULL; vc->scope = variable_context; vc->flags = flags; vc->up = vc->down = (VAR_CONTEXT *)NULL; vc->table = (HASH_TABLE *)NULL; return vc; } /* Free a variable context and its data, including the hash table. Dispose all of the variables. */ void dispose_var_context (vc) VAR_CONTEXT *vc; { FREE (vc->name); if (vc->table) { delete_all_variables (vc->table); hash_dispose (vc->table); } free (vc); } /* Set VAR's scope level to the current variable context. */ static int set_context (var) SHELL_VAR *var; { return (var->context = variable_context); } /* Make a new variable context with NAME and FLAGS and a HASH_TABLE of temporary variables, and push it onto shell_variables. This is for shell functions. */ VAR_CONTEXT * push_var_context (name, flags, tempvars) char *name; int flags; HASH_TABLE *tempvars; { VAR_CONTEXT *vc; vc = new_var_context (name, flags); vc->table = tempvars; if (tempvars) { /* Have to do this because the temp environment was created before variable_context was incremented. */ flatten (tempvars, set_context, (VARLIST *)NULL, 0); vc->flags |= VC_HASTMPVAR; } vc->down = shell_variables; shell_variables->up = vc; return (shell_variables = vc); } static void push_func_var (data) PTR_T data; { SHELL_VAR *var, *v; var = (SHELL_VAR *)data; if (tempvar_p (var) && (posixly_correct || (var->attributes & att_propagate))) { /* XXX - should we set v->context here? */ v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0); if (shell_variables == global_variables) var->attributes &= ~(att_tempvar|att_propagate); else shell_variables->flags |= VC_HASTMPVAR; v->attributes |= var->attributes; } dispose_variable (var); } /* Pop the top context off of VCXT and dispose of it, returning the rest of the stack. */ void pop_var_context () { VAR_CONTEXT *ret, *vcxt; vcxt = shell_variables; if (vc_isfuncenv (vcxt) == 0) { internal_error ("pop_var_context: head of shell_variables not a function context"); return; } if (ret = vcxt->down) { ret->up = (VAR_CONTEXT *)NULL; shell_variables = ret; if (vcxt->table) hash_flush (vcxt->table, push_func_var); dispose_var_context (vcxt); } else internal_error ("pop_var_context: no global_variables context"); } /* Delete the HASH_TABLEs for all variable contexts beginning at VCXT, and all of the VAR_CONTEXTs except GLOBAL_VARIABLES. */ void delete_all_contexts (vcxt) VAR_CONTEXT *vcxt; { VAR_CONTEXT *v, *t; for (v = vcxt; v != global_variables; v = t) { t = v->down; dispose_var_context (v); } delete_all_variables (global_variables->table); shell_variables = global_variables; } /* **************************************************************** */ /* */ /* Pushing and Popping temporary variable scopes */ /* */ /* **************************************************************** */ VAR_CONTEXT * push_scope (flags, tmpvars) int flags; HASH_TABLE *tmpvars; { return (push_var_context ((char *)NULL, flags, tmpvars)); } static void push_exported_var (data) PTR_T data; { SHELL_VAR *var, *v; var = (SHELL_VAR *)data; /* If a temp var had its export attribute set, or it's marked to be propagated, bind it in the previous scope before disposing it. */ if (exported_p (var) || (var->attributes & att_propagate)) { var->attributes &= ~att_tempvar; /* XXX */ v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0); if (shell_variables == global_variables) var->attributes &= ~att_propagate; v->attributes |= var->attributes; } dispose_variable (var); } void pop_scope (is_special) int is_special; { VAR_CONTEXT *vcxt, *ret; vcxt = shell_variables; if (vc_istempscope (vcxt) == 0) { internal_error ("pop_scope: head of shell_variables not a temporary environment scope"); return; } ret = vcxt->down; if (ret) ret->up = (VAR_CONTEXT *)NULL; shell_variables = ret; /* Now we can take care of merging variables in VCXT into set of scopes whose head is RET (shell_variables). */ FREE (vcxt->name); if (vcxt->table) { if (is_special) hash_flush (vcxt->table, push_func_var); else hash_flush (vcxt->table, push_exported_var); hash_dispose (vcxt->table); } free (vcxt); sv_ifs ("IFS"); /* XXX here for now */ } /* **************************************************************** */ /* */ /* Pushing and Popping function contexts */ /* */ /* **************************************************************** */ static WORD_LIST **dollar_arg_stack = (WORD_LIST **)NULL; static int dollar_arg_stack_slots; static int dollar_arg_stack_index; /* XXX - we might want to consider pushing and popping the `getopts' state when we modify the positional parameters. */ void push_context (name, is_subshell, tempvars) char *name; /* function name */ int is_subshell; HASH_TABLE *tempvars; { if (is_subshell == 0) push_dollar_vars (); variable_context++; push_var_context (name, VC_FUNCENV, tempvars); } /* Only called when subshell == 0, so we don't need to check, and can unconditionally pop the dollar vars off the stack. */ void pop_context () { pop_dollar_vars (); variable_context--; pop_var_context (); sv_ifs ("IFS"); /* XXX here for now */ } /* Save the existing positional parameters on a stack. */ void push_dollar_vars () { if (dollar_arg_stack_index + 2 > dollar_arg_stack_slots) { dollar_arg_stack = (WORD_LIST **) xrealloc (dollar_arg_stack, (dollar_arg_stack_slots += 10) * sizeof (WORD_LIST **)); } dollar_arg_stack[dollar_arg_stack_index++] = list_rest_of_args (); dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL; } /* Restore the positional parameters from our stack. */ void pop_dollar_vars () { if (!dollar_arg_stack || dollar_arg_stack_index == 0) return; remember_args (dollar_arg_stack[--dollar_arg_stack_index], 1); dispose_words (dollar_arg_stack[dollar_arg_stack_index]); dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL; set_dollar_vars_unchanged (); } void dispose_saved_dollar_vars () { if (!dollar_arg_stack || dollar_arg_stack_index == 0) return; dispose_words (dollar_arg_stack[dollar_arg_stack_index]); dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL; } /************************************************* * * * Functions to manage special variables * * * *************************************************/ /* Extern declarations for variables this code has to manage. */ extern int eof_encountered, eof_encountered_limit, ignoreeof; #if defined (READLINE) extern int no_line_editing; extern int hostname_list_initialized; #endif /* An alist of name.function for each special variable. Most of the functions don't do much, and in fact, this would be faster with a switch statement, but by the end of this file, I am sick of switch statements. */ #define SET_INT_VAR(name, intvar) intvar = find_variable (name) != 0 /* This table will be sorted with qsort() the first time it's accessed. */ struct name_and_function { char *name; sh_sv_func_t *function; }; static struct name_and_function special_vars[] = { { "GLOBIGNORE", sv_globignore }, #if defined (HISTORY) { "HISTCONTROL", sv_history_control }, { "HISTFILESIZE", sv_histsize }, { "HISTIGNORE", sv_histignore }, { "HISTSIZE", sv_histsize }, #endif #if defined (READLINE) { "HOSTFILE", sv_hostfile }, #endif { "IFS", sv_ifs }, { "IGNOREEOF", sv_ignoreeof }, { "LANG", sv_locale }, { "LC_ALL", sv_locale }, { "LC_COLLATE", sv_locale }, { "LC_CTYPE", sv_locale }, { "LC_MESSAGES", sv_locale }, { "LC_NUMERIC", sv_locale }, { "MAIL", sv_mail }, { "MAILCHECK", sv_mail }, { "MAILPATH", sv_mail }, { "OPTERR", sv_opterr }, { "OPTIND", sv_optind }, { "PATH", sv_path }, { "POSIXLY_CORRECT", sv_strict_posix }, #if defined (READLINE) { "TERM", sv_terminal }, { "TERMCAP", sv_terminal }, { "TERMINFO", sv_terminal }, #endif /* READLINE */ { "TEXTDOMAIN", sv_locale }, { "TEXTDOMAINDIR", sv_locale }, #if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE) { "TZ", sv_tz }, #endif #if defined (HISTORY) && defined (BANG_HISTORY) { "histchars", sv_histchars }, #endif /* HISTORY && BANG_HISTORY */ { "ignoreeof", sv_ignoreeof }, { (char *)0, (sh_sv_func_t *)0 } }; #define N_SPECIAL_VARS (sizeof (special_vars) / sizeof (special_vars[0]) - 1) static int sv_compare (sv1, sv2) struct name_and_function *sv1, *sv2; { int r; if ((r = sv1->name[0] - sv2->name[0]) == 0) r = strcmp (sv1->name, sv2->name); return r; } static inline int find_special_var (name) const char *name; { register int i, r; for (i = 0; special_vars[i].name; i++) { r = special_vars[i].name[0] - name[0]; if (r == 0) r = strcmp (special_vars[i].name, name); if (r == 0) return i; else if (r > 0) /* Can't match any of rest of elements in sorted list. Take this out if it causes problems in certain environments. */ break; } return -1; } /* The variable in NAME has just had its state changed. Check to see if it is one of the special ones where something special happens. */ void stupidly_hack_special_variables (name) char *name; { static int sv_sorted = 0; int i; if (sv_sorted == 0) /* shouldn't need, but it's fairly cheap. */ { qsort (special_vars, N_SPECIAL_VARS, sizeof (special_vars[0]), (QSFUNC *)sv_compare); sv_sorted = 1; } i = find_special_var (name); if (i != -1) (*(special_vars[i].function)) (name); } void sv_ifs (name) char *name; { SHELL_VAR *v; v = find_variable ("IFS"); setifs (v); } /* What to do just after the PATH variable has changed. */ void sv_path (name) char *name; { /* hash -r */ phash_flush (); } /* What to do just after one of the MAILxxxx variables has changed. NAME is the name of the variable. This is called with NAME set to one of MAIL, MAILCHECK, or MAILPATH. */ void sv_mail (name) char *name; { /* If the time interval for checking the files has changed, then reset the mail timer. Otherwise, one of the pathname vars to the users mailbox has changed, so rebuild the array of filenames. */ if (name[4] == 'C') /* if (strcmp (name, "MAILCHECK") == 0) */ reset_mail_timer (); else { free_mail_files (); remember_mail_dates (); } } /* What to do when GLOBIGNORE changes. */ void sv_globignore (name) char *name; { setup_glob_ignore (name); } #if defined (READLINE) /* What to do just after one of the TERMxxx variables has changed. If we are an interactive shell, then try to reset the terminal information in readline. */ void sv_terminal (name) char *name; { if (interactive_shell && no_line_editing == 0) rl_reset_terminal (get_string_value ("TERM")); } void sv_hostfile (name) char *name; { SHELL_VAR *v; v = find_variable (name); if (v == 0) clear_hostname_list (); else hostname_list_initialized = 0; } #endif /* READLINE */ #if defined (HISTORY) /* What to do after the HISTSIZE or HISTFILESIZE variables change. If there is a value for this HISTSIZE (and it is numeric), then stifle the history. Otherwise, if there is NO value for this variable, unstifle the history. If name is HISTFILESIZE, and its value is numeric, truncate the history file to hold no more than that many lines. */ void sv_histsize (name) char *name; { char *temp; intmax_t num; temp = get_string_value (name); if (temp && *temp) { if (legal_number (temp, &num)) { if (name[4] == 'S') { stifle_history (num); num = where_history (); if (history_lines_this_session > num) history_lines_this_session = num; } else { history_truncate_file (get_string_value ("HISTFILE"), (int)num); if (num <= history_lines_in_file) history_lines_in_file = num; } } } else if (name[4] == 'S') unstifle_history (); } /* What to do after the HISTIGNORE variable changes. */ void sv_histignore (name) char *name; { setup_history_ignore (name); } /* What to do after the HISTCONTROL variable changes. */ void sv_history_control (name) char *name; { char *temp; history_control = 0; temp = get_string_value (name); if (temp && *temp && STREQN (temp, "ignore", 6)) { if (temp[6] == 's') /* ignorespace */ history_control = 1; else if (temp[6] == 'd') /* ignoredups */ history_control = 2; else if (temp[6] == 'b') /* ignoreboth */ history_control = 3; } } #if defined (BANG_HISTORY) /* Setting/unsetting of the history expansion character. */ void sv_histchars (name) char *name; { char *temp; temp = get_string_value (name); if (temp) { history_expansion_char = *temp; if (temp[0] && temp[1]) { history_subst_char = temp[1]; if (temp[2]) history_comment_char = temp[2]; } } else { history_expansion_char = '!'; history_subst_char = '^'; history_comment_char = '#'; } } #endif /* BANG_HISTORY */ #endif /* HISTORY */ #if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE) void sv_tz (name) char *name; { tzset (); } #endif /* If the variable exists, then the value of it can be the number of times we actually ignore the EOF. The default is small, (smaller than csh, anyway). */ void sv_ignoreeof (name) char *name; { SHELL_VAR *tmp_var; char *temp; eof_encountered = 0; tmp_var = find_variable (name); ignoreeof = tmp_var != 0; temp = tmp_var ? value_cell (tmp_var) : (char *)NULL; if (temp) eof_encountered_limit = (*temp && all_digits (temp)) ? atoi (temp) : 10; set_shellopts (); /* make sure `ignoreeof' is/is not in $SHELLOPTS */ } void sv_optind (name) char *name; { char *tt; int s; tt = get_string_value ("OPTIND"); if (tt && *tt) { s = atoi (tt); /* According to POSIX, setting OPTIND=1 resets the internal state of getopt (). */ if (s < 0 || s == 1) s = 0; } else s = 0; getopts_reset (s); } void sv_opterr (name) char *name; { char *tt; tt = get_string_value ("OPTERR"); sh_opterr = (tt && *tt) ? atoi (tt) : 1; } void sv_strict_posix (name) char *name; { SET_INT_VAR (name, posixly_correct); posix_initialize (posixly_correct); #if defined (READLINE) if (interactive_shell) posix_readline_initialize (posixly_correct); #endif /* READLINE */ set_shellopts (); /* make sure `posix' is/is not in $SHELLOPTS */ } void sv_locale (name) char *name; { char *v; v = get_string_value (name); if (name[0] == 'L' && name[1] == 'A') /* LANG */ set_lang (name, v); else set_locale_var (name, v); /* LC_*, TEXTDOMAIN* */ } #if defined (ARRAY_VARS) void set_pipestatus_array (ps, nproc) int *ps; int nproc; { SHELL_VAR *v; ARRAY *a; ARRAY_ELEMENT *ae; register int i; char *t, tbuf[INT_STRLEN_BOUND(int) + 1]; v = find_variable ("PIPESTATUS"); if (v == 0) v = make_new_array_variable ("PIPESTATUS"); if (array_p (v) == 0) return; /* Do nothing if not an array variable. */ a = array_cell (v); if (a == 0 || array_num_elements (a) == 0) { for (i = 0; i < nproc; i++) /* was ps[i] != -1, not i < nproc */ { t = inttostr (ps[i], tbuf, sizeof (tbuf)); array_insert (a, i, t); } return; } /* Fast case */ if (array_num_elements (a) == nproc && nproc == 1) { ae = element_forw (a->head); free (element_value (ae)); ae->value = itos (ps[0]); } else if (array_num_elements (a) <= nproc) { /* modify in array_num_elements members in place, then add */ ae = a->head; for (i = 0; i < array_num_elements (a); i++) { ae = element_forw (ae); free (element_value (ae)); ae->value = itos (ps[i]); } /* add any more */ for ( ; i < nproc; i++) { t = inttostr (ps[i], tbuf, sizeof (tbuf)); array_insert (a, i, t); } } else { /* deleting elements. it's faster to rebuild the array. */ array_flush (a); for (i = 0; ps[i] != -1; i++) { t = inttostr (ps[i], tbuf, sizeof (tbuf)); array_insert (a, i, t); } } } #endif void set_pipestatus_from_exit (s) int s; { #if defined (ARRAY_VARS) static int v[2] = { 0, -1 }; v[0] = s; set_pipestatus_array (v, 1); #endif }
/* copy_command.c -- copy a COMMAND structure. This is needed primarily for making function definitions, but I'm not sure that anyone else will need it. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #include "bashtypes.h" #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <stdio.h> #include "shell.h" static PATTERN_LIST *copy_case_clause __P((PATTERN_LIST *)); static PATTERN_LIST *copy_case_clauses __P((PATTERN_LIST *)); static FOR_COM *copy_for_command __P((FOR_COM *)); #if defined (ARITH_FOR_COMMAND) static ARITH_FOR_COM *copy_arith_for_command __P((ARITH_FOR_COM *)); #endif static GROUP_COM *copy_group_command __P((GROUP_COM *)); static SUBSHELL_COM *copy_subshell_command __P((SUBSHELL_COM *)); static CASE_COM *copy_case_command __P((CASE_COM *)); static WHILE_COM *copy_while_command __P((WHILE_COM *)); static IF_COM *copy_if_command __P((IF_COM *)); #if defined (DPAREN_ARITHMETIC) static ARITH_COM *copy_arith_command __P((ARITH_COM *)); #endif #if defined (COND_COMMAND) static COND_COM *copy_cond_command __P((COND_COM *)); #endif static SIMPLE_COM *copy_simple_command __P((SIMPLE_COM *)); static FUNCTION_DEF *copy_function_def __P((FUNCTION_DEF *)); WORD_DESC * copy_word (w) WORD_DESC *w; { WORD_DESC *new_word; new_word = make_bare_word (w->word); new_word->flags = w->flags; return (new_word); } /* Copy the chain of words in LIST. Return a pointer to the new chain. */ WORD_LIST * copy_word_list (list) WORD_LIST *list; { WORD_LIST *new_list; for (new_list = (WORD_LIST *)NULL; list; list = list->next) new_list = make_word_list (copy_word (list->word), new_list); return (REVERSE_LIST (new_list, WORD_LIST *)); } static PATTERN_LIST * copy_case_clause (clause) PATTERN_LIST *clause; { PATTERN_LIST *new_clause; new_clause = (PATTERN_LIST *)xmalloc (sizeof (PATTERN_LIST)); new_clause->patterns = copy_word_list (clause->patterns); new_clause->action = copy_command (clause->action); return (new_clause); } static PATTERN_LIST * copy_case_clauses (clauses) PATTERN_LIST *clauses; { PATTERN_LIST *new_list, *new_clause; for (new_list = (PATTERN_LIST *)NULL; clauses; clauses = clauses->next) { new_clause = copy_case_clause (clauses); new_clause->next = new_list; new_list = new_clause; } return (REVERSE_LIST (new_list, PATTERN_LIST *)); } /* Copy a single redirect. */ REDIRECT * copy_redirect (redirect) REDIRECT *redirect; { REDIRECT *new_redirect; new_redirect = (REDIRECT *)xmalloc (sizeof (REDIRECT)); FASTCOPY ((char *)redirect, (char *)new_redirect, (sizeof (REDIRECT))); switch (redirect->instruction) { case r_reading_until: case r_deblank_reading_until: new_redirect->here_doc_eof = savestring (redirect->here_doc_eof); /*FALLTHROUGH*/ case r_reading_string: case r_appending_to: case r_output_direction: case r_input_direction: case r_inputa_direction: case r_err_and_out: case r_input_output: case r_output_force: case r_duplicating_input_word: case r_duplicating_output_word: case r_move_input_word: case r_move_output_word: new_redirect->redirectee.filename = copy_word (redirect->redirectee.filename); break; case r_duplicating_input: case r_duplicating_output: case r_move_input: case r_move_output: case r_close_this: break; } return (new_redirect); } REDIRECT * copy_redirects (list) REDIRECT *list; { REDIRECT *new_list, *temp; for (new_list = (REDIRECT *)NULL; list; list = list->next) { temp = copy_redirect (list); temp->next = new_list; new_list = temp; } return (REVERSE_LIST (new_list, REDIRECT *)); } static FOR_COM * copy_for_command (com) FOR_COM *com; { FOR_COM *new_for; new_for = (FOR_COM *)xmalloc (sizeof (FOR_COM)); new_for->flags = com->flags; new_for->name = copy_word (com->name); new_for->map_list = copy_word_list (com->map_list); new_for->action = copy_command (com->action); return (new_for); } #if defined (ARITH_FOR_COMMAND) static ARITH_FOR_COM * copy_arith_for_command (com) ARITH_FOR_COM *com; { ARITH_FOR_COM *new_arith_for; new_arith_for = (ARITH_FOR_COM *)xmalloc (sizeof (ARITH_FOR_COM)); new_arith_for->flags = com->flags; new_arith_for->line = com->line; new_arith_for->init = copy_word_list (com->init); new_arith_for->test = copy_word_list (com->test); new_arith_for->step = copy_word_list (com->step); new_arith_for->action = copy_command (com->action); return (new_arith_for); } #endif /* ARITH_FOR_COMMAND */ static GROUP_COM * copy_group_command (com) GROUP_COM *com; { GROUP_COM *new_group; new_group = (GROUP_COM *)xmalloc (sizeof (GROUP_COM)); new_group->command = copy_command (com->command); return (new_group); } static SUBSHELL_COM * copy_subshell_command (com) SUBSHELL_COM *com; { SUBSHELL_COM *new_subshell; new_subshell = (SUBSHELL_COM *)xmalloc (sizeof (SUBSHELL_COM)); new_subshell->command = copy_command (com->command); new_subshell->flags = com->flags; return (new_subshell); } static CASE_COM * copy_case_command (com) CASE_COM *com; { CASE_COM *new_case; new_case = (CASE_COM *)xmalloc (sizeof (CASE_COM)); new_case->flags = com->flags; new_case->word = copy_word (com->word); new_case->clauses = copy_case_clauses (com->clauses); return (new_case); } static WHILE_COM * copy_while_command (com) WHILE_COM *com; { WHILE_COM *new_while; new_while = (WHILE_COM *)xmalloc (sizeof (WHILE_COM)); new_while->flags = com->flags; new_while->test = copy_command (com->test); new_while->action = copy_command (com->action); return (new_while); } static IF_COM * copy_if_command (com) IF_COM *com; { IF_COM *new_if; new_if = (IF_COM *)xmalloc (sizeof (IF_COM)); new_if->flags = com->flags; new_if->test = copy_command (com->test); new_if->true_case = copy_command (com->true_case); new_if->false_case = com->false_case ? copy_command (com->false_case) : com->false_case; return (new_if); } #if defined (DPAREN_ARITHMETIC) static ARITH_COM * copy_arith_command (com) ARITH_COM *com; { ARITH_COM *new_arith; new_arith = (ARITH_COM *)xmalloc (sizeof (ARITH_COM)); new_arith->flags = com->flags; new_arith->exp = copy_word_list (com->exp); new_arith->line = com->line; return (new_arith); } #endif #if defined (COND_COMMAND) static COND_COM * copy_cond_command (com) COND_COM *com; { COND_COM *new_cond; new_cond = (COND_COM *)xmalloc (sizeof (COND_COM)); new_cond->flags = com->flags; new_cond->line = com->line; new_cond->type = com->type; new_cond->op = com->op ? copy_word (com->op) : com->op; new_cond->left = com->left ? copy_cond_command (com->left) : (COND_COM *)NULL; new_cond->right = com->right ? copy_cond_command (com->right) : (COND_COM *)NULL; return (new_cond); } #endif static SIMPLE_COM * copy_simple_command (com) SIMPLE_COM *com; { SIMPLE_COM *new_simple; new_simple = (SIMPLE_COM *)xmalloc (sizeof (SIMPLE_COM)); new_simple->flags = com->flags; new_simple->words = copy_word_list (com->words); new_simple->redirects = com->redirects ? copy_redirects (com->redirects) : (REDIRECT *)NULL; new_simple->line = com->line; return (new_simple); } static FUNCTION_DEF * copy_function_def (com) FUNCTION_DEF *com; { FUNCTION_DEF *new_def; new_def = (FUNCTION_DEF *)xmalloc (sizeof (FUNCTION_DEF)); new_def->name = copy_word (com->name); new_def->command = copy_command (com->command); new_def->flags = com->flags; new_def->line = com->line; return (new_def); } /* Copy the command structure in COMMAND. Return a pointer to the copy. Don't you forget to dispose_command () on this pointer later! */ COMMAND * copy_command (command) COMMAND *command; { COMMAND *new_command; if (command == NULL) return (command); new_command = (COMMAND *)xmalloc (sizeof (COMMAND)); FASTCOPY ((char *)command, (char *)new_command, sizeof (COMMAND)); new_command->flags = command->flags; new_command->line = command->line; if (command->redirects) new_command->redirects = copy_redirects (command->redirects); switch (command->type) { case cm_for: new_command->value.For = copy_for_command (command->value.For); break; #if defined (ARITH_FOR_COMMAND) case cm_arith_for: new_command->value.ArithFor = copy_arith_for_command (command->value.ArithFor); break; #endif #if defined (SELECT_COMMAND) case cm_select: new_command->value.Select = (SELECT_COM *)copy_for_command ((FOR_COM *)command->value.Select); break; #endif case cm_group: new_command->value.Group = copy_group_command (command->value.Group); break; case cm_subshell: new_command->value.Subshell = copy_subshell_command (command->value.Subshell); break; case cm_case: new_command->value.Case = copy_case_command (command->value.Case); break; case cm_until: case cm_while: new_command->value.While = copy_while_command (command->value.While); break; case cm_if: new_command->value.If = copy_if_command (command->value.If); break; #if defined (DPAREN_ARITHMETIC) case cm_arith: new_command->value.Arith = copy_arith_command (command->value.Arith); break; #endif #if defined (COND_COMMAND) case cm_cond: new_command->value.Cond = copy_cond_command (command->value.Cond); break; #endif case cm_simple: new_command->value.Simple = copy_simple_command (command->value.Simple); break; case cm_connection: { CONNECTION *new_connection; new_connection = (CONNECTION *)xmalloc (sizeof (CONNECTION)); new_connection->connector = command->value.Connection->connector; new_connection->first = copy_command (command->value.Connection->first); new_connection->second = copy_command (command->value.Connection->second); new_command->value.Connection = new_connection; break; } case cm_function_def: new_command->value.Function_def = copy_function_def (command->value.Function_def); break; } return (new_command); }
/* error.c -- Functions for handling errors. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #include "bashtypes.h" #include <fcntl.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #if defined (PREFER_STDARG) # include <stdarg.h> #else # include <varargs.h> #endif #include <stdio.h> #include <errno.h> #if !defined (errno) extern int errno; #endif /* !errno */ #include "bashansi.h" #include "flags.h" #include "error.h" #include "command.h" #include "general.h" #include "externs.h" #include "input.h" #if defined (HISTORY) # include "bashhist.h" #endif extern int executing_line_number __P((void)); extern int interactive_shell, interactive, startup_state; extern char *dollar_vars[]; extern char *shell_name; #if defined (JOB_CONTROL) extern pid_t shell_pgrp; extern int give_terminal_to __P((pid_t, int)); #endif /* JOB_CONTROL */ static void error_prolog __P((int)); /* The current maintainer of the shell. You change this in the Makefile. */ #if !defined (MAINTAINER) #define MAINTAINER "bash-maintainers@gnu.org" #endif char *the_current_maintainer = MAINTAINER; static void error_prolog (print_lineno) int print_lineno; { int line; fprintf (stderr, "%s: ", get_name_for_error ()); if (print_lineno && interactive_shell == 0) { line = executing_line_number (); if (line > 0) fprintf (stderr, "line %d: ", line); } } /* Return the name of the shell or the shell script for error reporting. */ char * get_name_for_error () { char *name; name = (char *)NULL; if (interactive_shell == 0) name = dollar_vars[0]; if (name == 0 && shell_name && *shell_name) name = base_pathname (shell_name); if (name == 0) #if defined (PROGRAM) name = PROGRAM; #else name = "bash"; #endif return (name); } /* Report an error having to do with FILENAME. This does not use sys_error so the filename is not interpreted as a printf-style format string. */ void file_error (filename) const char *filename; { report_error ("%s: %s", filename, strerror (errno)); } void #if defined (PREFER_STDARG) programming_error (const char *format, ...) #else programming_error (format, va_alist) const char *format; va_dcl #endif { va_list args; char *h; #if defined (JOB_CONTROL) give_terminal_to (shell_pgrp, 0); #endif /* JOB_CONTROL */ SH_VA_START (args, format); vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); #if defined (HISTORY) if (remember_on_history) { h = last_history_line (); fprintf (stderr, "last command: %s\n", h ? h : "(null)"); } #endif #if 0 fprintf (stderr, "Report this to %s\n", the_current_maintainer); #endif fprintf (stderr, "Stopping myself..."); fflush (stderr); abort (); } /* Print an error message and, if `set -e' has been executed, exit the shell. Used in this file by file_error and programming_error. Used outside this file mostly to report substitution and expansion errors, and for bad invocation options. */ void #if defined (PREFER_STDARG) report_error (const char *format, ...) #else report_error (format, va_alist) const char *format; va_dcl #endif { va_list args; error_prolog (1); SH_VA_START (args, format); vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); if (exit_immediately_on_error) sh_exit (1); } void #if defined (PREFER_STDARG) fatal_error (const char *format, ...) #else fatal_error (format, va_alist) const char *format; va_dcl #endif { va_list args; error_prolog (0); SH_VA_START (args, format); vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); sh_exit (2); } void #if defined (PREFER_STDARG) internal_error (const char *format, ...) #else internal_error (format, va_alist) const char *format; va_dcl #endif { va_list args; error_prolog (1); SH_VA_START (args, format); vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); } void #if defined (PREFER_STDARG) internal_warning (const char *format, ...) #else internal_warning (format, va_alist) const char *format; va_dcl #endif { va_list args; fprintf (stderr, "%s: warning: ", get_name_for_error ()); SH_VA_START (args, format); vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); } void #if defined (PREFER_STDARG) sys_error (const char *format, ...) #else sys_error (format, va_alist) const char *format; va_dcl #endif { int e; va_list args; e = errno; error_prolog (0); SH_VA_START (args, format); vfprintf (stderr, format, args); fprintf (stderr, ": %s\n", strerror (e)); va_end (args); } /* An error from the parser takes the general form shell_name: input file name: line number: message The input file name and line number are omitted if the shell is currently interactive. If the shell is not currently interactive, the input file name is inserted only if it is different from the shell name. */ void #if defined (PREFER_STDARG) parser_error (int lineno, const char *format, ...) #else parser_error (lineno, format, va_alist) int lineno; const char *format; va_dcl #endif { va_list args; char *ename, *iname; ename = get_name_for_error (); iname = yy_input_name (); if (interactive) fprintf (stderr, "%s: ", ename); else if (interactive_shell) fprintf (stderr, "%s: %s: line %d: ", ename, iname, lineno); else if (STREQ (ename, iname)) fprintf (stderr, "%s: line %d: ", ename, lineno); else fprintf (stderr, "%s: %s: line %d: ", ename, iname, lineno); SH_VA_START (args, format); vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); if (exit_immediately_on_error) sh_exit (2); } #ifdef DEBUG void #if defined (PREFER_STDARG) itrace (const char *format, ...) #else itrace (format, va_alist) const char *format; va_dcl #endif { va_list args; fprintf(stderr, "TRACE: pid %ld: ", (long)getpid()); SH_VA_START (args, format); vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); fflush(stderr); } /* A trace function for silent debugging -- doesn't require a control terminal. */ void #if defined (PREFER_STDARG) trace (const char *format, ...) #else trace (format, va_alist) const char *format; va_dcl #endif { va_list args; static FILE *tracefp = (FILE *)NULL; if (tracefp == NULL) tracefp = fopen("/tmp/bash-trace.log", "a+"); if (tracefp == NULL) tracefp = stderr; else fcntl (fileno (tracefp), F_SETFD, 1); /* close-on-exec */ fprintf(tracefp, "TRACE: pid %ld: ", (long)getpid()); SH_VA_START (args, format); vfprintf (tracefp, format, args); fprintf (tracefp, "\n"); va_end (args); fflush(tracefp); } #endif /* DEBUG */ /* **************************************************************** */ /* */ /* Common error reporting */ /* */ /* **************************************************************** */ static char *cmd_error_table[] = { "unknown command error", /* CMDERR_DEFAULT */ "bad command type", /* CMDERR_BADTYPE */ "bad connector", /* CMDERR_BADCONN */ "bad jump", /* CMDERR_BADJUMP */ 0 }; void command_error (func, code, e, flags) const char *func; int code, e, flags; /* flags currently unused */ { if (code > CMDERR_LAST) code = CMDERR_DEFAULT; programming_error ("%s: %s: %d", func, cmd_error_table[code], e); } char * command_errstr (code) int code; { if (code > CMDERR_LAST) code = CMDERR_DEFAULT; return (cmd_error_table[code]); } #ifdef ARRAY_VARS void err_badarraysub (s) const char *s; { report_error ("%s: bad array subscript", s); } #endif void err_unboundvar (s) const char *s; { report_error ("%s: unbound variable", s); } void err_readonly (s) const char *s; { report_error ("%s: readonly variable", s); }
/* expr.c -- arithmetic expression evaluation. */ /* Copyright (C) 1990-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ /* All arithmetic is done as intmax_t integers with no checking for overflow (though division by 0 is caught and flagged as an error). The following operators are handled, grouped into a set of levels in order of decreasing precedence. "id++", "id--" [post-increment and post-decrement] "++id", "--id" [pre-increment and pre-decrement] "-", "+" [(unary operators)] "!", "~" "**" [(exponentiation)] "*", "/", "%" "+", "-" "<<", ">>" "<=", ">=", "<", ">" "==", "!=" "&" "^" "|" "&&" "||" "expr ? expr : expr" "=", "*=", "/=", "%=", "+=", "-=", "<<=", ">>=", "&=", "^=", "|=" (Note that most of these operators have special meaning to bash, and an entire expression should be quoted, e.g. "a=$a+1" or "a=a+1" to ensure that it is passed intact to the evaluator when using `let'. When using the $[] or $(( )) forms, the text between the `[' and `]' or `((' and `))' is treated as if in double quotes.) Sub-expressions within parentheses have a precedence level greater than all of the above levels and are evaluated first. Within a single prece- dence group, evaluation is left-to-right, except for the arithmetic assignment operator (`='), which is evaluated right-to-left (as in C). The expression evaluator returns the value of the expression (assignment statements have as a value what is returned by the RHS). The `let' builtin, on the other hand, returns 0 if the last expression evaluates to a non-zero, and 1 otherwise. Implementation is a recursive-descent parser. Chet Ramey chet@ins.CWRU.Edu */ #include "config.h" #include <stdio.h> #include "bashansi.h" #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "chartypes.h" #include "shell.h" /* Because of the $((...)) construct, expressions may include newlines. Here is a macro which accepts newlines, tabs and spaces as whitespace. */ #define cr_whitespace(c) (whitespace(c) || ((c) == '\n')) /* Size be which the expression stack grows when neccessary. */ #define EXPR_STACK_GROW_SIZE 10 /* Maximum amount of recursion allowed. This prevents a non-integer variable such as "num=num+2" from infinitely adding to itself when "let num=num+2" is given. */ #define MAX_EXPR_RECURSION_LEVEL 1024 /* The Tokens. Singing "The Lion Sleeps Tonight". */ #define EQEQ 1 /* "==" */ #define NEQ 2 /* "!=" */ #define LEQ 3 /* "<=" */ #define GEQ 4 /* ">=" */ #define STR 5 /* string */ #define NUM 6 /* number */ #define LAND 7 /* "&&" Logical AND */ #define LOR 8 /* "||" Logical OR */ #define LSH 9 /* "<<" Left SHift */ #define RSH 10 /* ">>" Right SHift */ #define OP_ASSIGN 11 /* op= expassign as in Posix.2 */ #define COND 12 /* exp1 ? exp2 : exp3 */ #define POWER 13 /* exp1**exp2 */ #define PREINC 14 /* ++var */ #define PREDEC 15 /* --var */ #define POSTINC 16 /* var++ */ #define POSTDEC 17 /* var-- */ #define EQ '=' #define GT '>' #define LT '<' #define PLUS '+' #define MINUS '-' #define MUL '*' #define DIV '/' #define MOD '%' #define NOT '!' #define LPAR '(' #define RPAR ')' #define BAND '&' /* Bitwise AND */ #define BOR '|' /* Bitwise OR. */ #define BXOR '^' /* Bitwise eXclusive OR. */ #define BNOT '~' /* Bitwise NOT; Two's complement. */ #define QUES '?' #define COL ':' #define COMMA ',' /* This should be the function corresponding to the operator with the highest precedence. */ #define EXP_HIGHEST expcomma static char *expression; /* The current expression */ static char *tp; /* token lexical position */ static char *lasttp; /* pointer to last token position */ static int curtok; /* the current token */ static int lasttok; /* the previous token */ static int assigntok; /* the OP in OP= */ static char *tokstr; /* current token string */ static intmax_t tokval; /* current token value */ static int noeval; /* set to 1 if no assignment to be done */ static procenv_t evalbuf; static void readtok __P((void)); /* lexical analyzer */ static intmax_t expr_streval __P((char *, int)); static intmax_t strlong __P((char *)); static void evalerror __P((char *)); static void pushexp __P((void)); static void popexp __P((void)); static void expr_unwind __P((void)); static intmax_t subexpr __P((char *)); static intmax_t expcomma __P((void)); static intmax_t expassign __P((void)); static intmax_t expcond __P((void)); static intmax_t explor __P((void)); static intmax_t expland __P((void)); static intmax_t expbor __P((void)); static intmax_t expbxor __P((void)); static intmax_t expband __P((void)); static intmax_t exp5 __P((void)); static intmax_t exp4 __P((void)); static intmax_t expshift __P((void)); static intmax_t exp3 __P((void)); static intmax_t exp2 __P((void)); static intmax_t exppower __P((void)); static intmax_t exp1 __P((void)); static intmax_t exp0 __P((void)); /* A structure defining a single expression context. */ typedef struct { int curtok, lasttok; char *expression, *tp, *lasttp; intmax_t tokval; char *tokstr; int noeval; } EXPR_CONTEXT; #ifdef INCLUDE_UNUSED /* Not used yet. */ typedef struct { char *tokstr; intmax_t tokval; } LVALUE; #endif /* Global var which contains the stack of expression contexts. */ static EXPR_CONTEXT **expr_stack; static int expr_depth; /* Location in the stack. */ static int expr_stack_size; /* Number of slots already allocated. */ extern char *this_command_name; extern int unbound_vars_is_error; #define SAVETOK(X) \ do { \ (X)->curtok = curtok; \ (X)->lasttok = lasttok; \ (X)->tp = tp; \ (X)->lasttp = lasttp; \ (X)->tokval = tokval; \ (X)->tokstr = tokstr; \ (X)->noeval = noeval; \ } while (0) #define RESTORETOK(X) \ do { \ curtok = (X)->curtok; \ lasttok = (X)->lasttok; \ tp = (X)->tp; \ lasttp = (X)->lasttp; \ tokval = (X)->tokval; \ tokstr = (X)->tokstr; \ noeval = (X)->noeval; \ } while (0) /* Push and save away the contents of the globals describing the current expression context. */ static void pushexp () { EXPR_CONTEXT *context; if (expr_depth >= MAX_EXPR_RECURSION_LEVEL) evalerror ("expression recursion level exceeded"); if (expr_depth >= expr_stack_size) { expr_stack_size += EXPR_STACK_GROW_SIZE; expr_stack = (EXPR_CONTEXT **)xrealloc (expr_stack, expr_stack_size * sizeof (EXPR_CONTEXT *)); } context = (EXPR_CONTEXT *)xmalloc (sizeof (EXPR_CONTEXT)); context->expression = expression; SAVETOK(context); expr_stack[expr_depth++] = context; } /* Pop the the contents of the expression context stack into the globals describing the current expression context. */ static void popexp () { EXPR_CONTEXT *context; if (expr_depth == 0) evalerror ("recursion stack underflow"); context = expr_stack[--expr_depth]; expression = context->expression; RESTORETOK (context); free (context); } static void expr_unwind () { while (--expr_depth > 0) { if (expr_stack[expr_depth]->tokstr) free (expr_stack[expr_depth]->tokstr); if (expr_stack[expr_depth]->expression) free (expr_stack[expr_depth]->expression); free (expr_stack[expr_depth]); } free (expr_stack[expr_depth]); /* free the allocated EXPR_CONTEXT */ } /* Evaluate EXPR, and return the arithmetic result. If VALIDP is non-null, a zero is stored into the location to which it points if the expression is invalid, non-zero otherwise. If a non-zero value is returned in *VALIDP, the return value of evalexp() may be used. The `while' loop after the longjmp is caught relies on the above implementation of pushexp and popexp leaving in expr_stack[0] the values that the variables had when the program started. That is, the first things saved are the initial values of the variables that were assigned at program startup or by the compiler. Therefore, it is safe to let the loop terminate when expr_depth == 0, without freeing up any of the expr_depth[0] stuff. */ intmax_t evalexp (expr, validp) char *expr; int *validp; { intmax_t val; val = 0; if (setjmp (evalbuf)) { FREE (tokstr); FREE (expression); tokstr = expression = (char *)NULL; expr_unwind (); if (validp) *validp = 0; return (0); } val = subexpr (expr); if (validp) *validp = 1; return (val); } static intmax_t subexpr (expr) char *expr; { intmax_t val; char *p; for (p = expr; p && *p && cr_whitespace (*p); p++) ; if (p == NULL || *p == '\0') return (0); pushexp (); curtok = lasttok = 0; expression = savestring (expr); tp = expression; tokstr = (char *)NULL; tokval = 0; readtok (); val = EXP_HIGHEST (); if (curtok != 0) evalerror ("syntax error in expression"); FREE (tokstr); FREE (expression); popexp (); return val; } static intmax_t expcomma () { register intmax_t value; value = expassign (); while (curtok == COMMA) { readtok (); value = expassign (); } return value; } static intmax_t expassign () { register intmax_t value; char *lhs, *rhs; value = expcond (); if (curtok == EQ || curtok == OP_ASSIGN) { int special, op; intmax_t lvalue; special = curtok == OP_ASSIGN; if (lasttok != STR) evalerror ("attempted assignment to non-variable"); if (special) { op = assigntok; /* a OP= b */ lvalue = value; } lhs = savestring (tokstr); readtok (); value = expassign (); if (special) { switch (op) { case MUL: lvalue *= value; break; case DIV: if (value == 0) evalerror ("division by 0"); lvalue /= value; break; case MOD: if (value == 0) evalerror ("division by 0"); lvalue %= value; break; case PLUS: lvalue += value; break; case MINUS: lvalue -= value; break; case LSH: lvalue <<= value; break; case RSH: lvalue >>= value; break; case BAND: lvalue &= value; break; case BOR: lvalue |= value; break; case BXOR: lvalue ^= value; break; default: free (lhs); evalerror ("bug: bad expassign token"); break; } value = lvalue; } rhs = itos (value); if (noeval == 0) (void)bind_int_variable (lhs, rhs); free (rhs); free (lhs); FREE (tokstr); tokstr = (char *)NULL; /* For freeing on errors. */ } return (value); } /* Conditional expression (expr?expr:expr) */ static intmax_t expcond () { intmax_t cval, val1, val2, rval; int set_noeval; set_noeval = 0; rval = cval = explor (); if (curtok == QUES) /* found conditional expr */ { readtok (); if (curtok == 0 || curtok == COL) evalerror ("expression expected"); if (cval == 0) { set_noeval = 1; noeval++; } val1 = EXP_HIGHEST (); if (set_noeval) noeval--; if (curtok != COL) evalerror ("`:' expected for conditional expression"); readtok (); if (curtok == 0) evalerror ("expression expected"); set_noeval = 0; if (cval) { set_noeval = 1; noeval++; } val2 = explor (); if (set_noeval) noeval--; rval = cval ? val1 : val2; lasttok = COND; } return rval; } /* Logical OR. */ static intmax_t explor () { register intmax_t val1, val2; int set_noeval; val1 = expland (); while (curtok == LOR) { set_noeval = 0; if (val1 != 0) { noeval++; set_noeval = 1; } readtok (); val2 = expland (); if (set_noeval) noeval--; val1 = val1 || val2; lasttok = LOR; } return (val1); } /* Logical AND. */ static intmax_t expland () { register intmax_t val1, val2; int set_noeval; val1 = expbor (); while (curtok == LAND) { set_noeval = 0; if (val1 == 0) { set_noeval = 1; noeval++; } readtok (); val2 = expbor (); if (set_noeval) noeval--; val1 = val1 && val2; lasttok = LAND; } return (val1); } /* Bitwise OR. */ static intmax_t expbor () { register intmax_t val1, val2; val1 = expbxor (); while (curtok == BOR) { readtok (); val2 = expbxor (); val1 = val1 | val2; } return (val1); } /* Bitwise XOR. */ static intmax_t expbxor () { register intmax_t val1, val2; val1 = expband (); while (curtok == BXOR) { readtok (); val2 = expband (); val1 = val1 ^ val2; } return (val1); } /* Bitwise AND. */ static intmax_t expband () { register intmax_t val1, val2; val1 = exp5 (); while (curtok == BAND) { readtok (); val2 = exp5 (); val1 = val1 & val2; } return (val1); } static intmax_t exp5 () { register intmax_t val1, val2; val1 = exp4 (); while ((curtok == EQEQ) || (curtok == NEQ)) { int op = curtok; readtok (); val2 = exp4 (); if (op == EQEQ) val1 = (val1 == val2); else if (op == NEQ) val1 = (val1 != val2); } return (val1); } static intmax_t exp4 () { register intmax_t val1, val2; val1 = expshift (); while ((curtok == LEQ) || (curtok == GEQ) || (curtok == LT) || (curtok == GT)) { int op = curtok; readtok (); val2 = expshift (); if (op == LEQ) val1 = val1 <= val2; else if (op == GEQ) val1 = val1 >= val2; else if (op == LT) val1 = val1 < val2; else /* (op == GT) */ val1 = val1 > val2; } return (val1); } /* Left and right shifts. */ static intmax_t expshift () { register intmax_t val1, val2; val1 = exp3 (); while ((curtok == LSH) || (curtok == RSH)) { int op = curtok; readtok (); val2 = exp3 (); if (op == LSH) val1 = val1 << val2; else val1 = val1 >> val2; } return (val1); } static intmax_t exp3 () { register intmax_t val1, val2; val1 = exp2 (); while ((curtok == PLUS) || (curtok == MINUS)) { int op = curtok; readtok (); val2 = exp2 (); if (op == PLUS) val1 += val2; else if (op == MINUS) val1 -= val2; } return (val1); } static intmax_t exp2 () { register intmax_t val1, val2; val1 = exppower (); while ((curtok == MUL) || (curtok == DIV) || (curtok == MOD)) { int op = curtok; readtok (); val2 = exppower (); if (((op == DIV) || (op == MOD)) && (val2 == 0)) evalerror ("division by 0"); if (op == MUL) val1 *= val2; else if (op == DIV) val1 /= val2; else if (op == MOD) val1 %= val2; } return (val1); } static intmax_t exppower () { register intmax_t val1, val2, c; val1 = exp1 (); if (curtok == POWER) { readtok (); val2 = exp1 (); if (val2 == 0) return (1); if (val2 < 0) evalerror ("exponent less than 0"); for (c = 1; val2--; c *= val1) ; val1 = c; } return (val1); } static intmax_t exp1 () { register intmax_t val; if (curtok == NOT) { readtok (); val = !exp1 (); } else if (curtok == BNOT) { readtok (); val = ~exp1 (); } else val = exp0 (); return (val); } static intmax_t exp0 () { register intmax_t val = 0, v2; char *vincdec; int stok; /* XXX - might need additional logic here to decide whether or not pre-increment or pre-decrement is legal at this point. */ if (curtok == PREINC || curtok == PREDEC) { stok = lasttok = curtok; readtok (); if (curtok != STR) /* readtok() catches this */ evalerror ("identifier expected after pre-increment or pre-decrement"); v2 = tokval + ((stok == PREINC) ? 1 : -1); vincdec = itos (v2); if (noeval == 0) (void)bind_int_variable (tokstr, vincdec); free (vincdec); val = v2; curtok = NUM; /* make sure --x=7 is flagged as an error */ readtok (); } else if (curtok == MINUS) { readtok (); val = - exp0 (); } else if (curtok == PLUS) { readtok (); val = exp0 (); } else if (curtok == LPAR) { readtok (); val = EXP_HIGHEST (); if (curtok != RPAR) evalerror ("missing `)'"); /* Skip over closing paren. */ readtok (); } else if ((curtok == NUM) || (curtok == STR)) { val = tokval; if (curtok == STR && (*tp == '+' || *tp == '-') && tp[1] == *tp && (tp[2] == '\0' || (ISALNUM ((unsigned char)tp[2]) == 0))) { /* post-increment or post-decrement */ v2 = val + ((*tp == '+') ? 1 : -1); vincdec = itos (v2); if (noeval == 0) (void)bind_int_variable (tokstr, vincdec); free (vincdec); tp += 2; curtok = NUM; /* make sure x++=7 is flagged as an error */ } readtok (); } else evalerror ("syntax error: operand expected"); return (val); } static intmax_t expr_streval (tok, e) char *tok; int e; { SHELL_VAR *v; char *value; intmax_t tval; /* [[[[[ */ #if defined (ARRAY_VARS) v = (e == ']') ? array_variable_part (tok, (char **)0, (int *)0) : find_variable (tok); #else v = find_variable (tok); #endif if ((v == 0 || invisible_p (v)) && unbound_vars_is_error) { #if defined (ARRAY_VARS) value = (e == ']') ? array_variable_name (tok, (char **)0, (int *)0) : tok; #else value = tok; #endif err_unboundvar (value); #if defined (ARRAY_VARS) if (e == ']') FREE (value); /* array_variable_name returns new memory */ #endif if (interactive_shell) { expr_unwind (); jump_to_top_level (DISCARD); } else jump_to_top_level (FORCE_EOF); } #if defined (ARRAY_VARS) /* Second argument of 0 to get_array_value means that we don't allow references like array[@]. In this case, get_array_value is just like get_variable_value in that it does not return newly-allocated memory or quote the results. */ value = (e == ']') ? get_array_value (tok, 0, (int *)NULL) : get_variable_value (v); #else value = get_variable_value (v); #endif tval = (value && *value) ? subexpr (value) : 0; return (tval); } /* Lexical analyzer/token reader for the expression evaluator. Reads the next token and puts its value into curtok, while advancing past it. Updates value of tp. May also set tokval (for number) or tokstr (for string). */ static void readtok () { register char *cp; register unsigned char c, c1; register int e; /* Skip leading whitespace. */ cp = tp; c = e = 0; while (cp && (c = *cp) && (cr_whitespace (c))) cp++; if (c) cp++; lasttp = tp = cp - 1; if (c == '\0') { lasttok = curtok; curtok = 0; tp = cp; return; } if (legal_variable_starter (c)) { /* variable names not preceded with a dollar sign are shell variables. */ char *savecp; EXPR_CONTEXT ec; int peektok; while (legal_variable_char (c)) c = *cp++; c = *--cp; #if defined (ARRAY_VARS) if (c == '[') { e = skipsubscript (cp, 0); if (cp[e] == ']') { cp += e + 1; c = *cp; e = ']'; } else evalerror ("bad array subscript"); } #endif /* ARRAY_VARS */ *cp = '\0'; FREE (tokstr); tokstr = savestring (tp); *cp = c; SAVETOK (&ec); tokstr = (char *)NULL; /* keep it from being freed */ tp = savecp = cp; noeval = 1; readtok (); peektok = curtok; if (peektok == STR) /* free new tokstr before old one is restored */ FREE (tokstr); RESTORETOK (&ec); cp = savecp; /* The tests for PREINC and PREDEC aren't strictly correct, but they preserve old behavior if a construct like --x=9 is given. */ if (lasttok == PREINC || lasttok == PREDEC || peektok != EQ) tokval = expr_streval (tokstr, e); else tokval = 0; lasttok = curtok; curtok = STR; } else if (DIGIT(c)) { while (ISALNUM (c) || c == '#' || c == '@' || c == '_') c = *cp++; c = *--cp; *cp = '\0'; tokval = strlong (tp); *cp = c; lasttok = curtok; curtok = NUM; } else { c1 = *cp++; if ((c == EQ) && (c1 == EQ)) c = EQEQ; else if ((c == NOT) && (c1 == EQ)) c = NEQ; else if ((c == GT) && (c1 == EQ)) c = GEQ; else if ((c == LT) && (c1 == EQ)) c = LEQ; else if ((c == LT) && (c1 == LT)) { if (*cp == '=') /* a <<= b */ { assigntok = LSH; c = OP_ASSIGN; cp++; } else c = LSH; } else if ((c == GT) && (c1 == GT)) { if (*cp == '=') { assigntok = RSH; /* a >>= b */ c = OP_ASSIGN; cp++; } else c = RSH; } else if ((c == BAND) && (c1 == BAND)) c = LAND; else if ((c == BOR) && (c1 == BOR)) c = LOR; else if ((c == '*') && (c1 == '*')) c = POWER; else if ((c == '-') && (c1 == '-') && legal_variable_starter ((unsigned char)*cp)) c = PREDEC; else if ((c == '+') && (c1 == '+') && legal_variable_starter ((unsigned char)*cp)) c = PREINC; else if (c1 == EQ && member (c, "*/%+-&^|")) { assigntok = c; /* a OP= b */ c = OP_ASSIGN; } else cp--; /* `unget' the character */ lasttok = curtok; curtok = c; } tp = cp; } static void evalerror (msg) char *msg; { char *name, *t; name = this_command_name; for (t = expression; whitespace (*t); t++) ; internal_error ("%s%s%s: %s (error token is \"%s\")", name ? name : "", name ? ": " : "", t, msg, (lasttp && *lasttp) ? lasttp : ""); longjmp (evalbuf, 1); } /* Convert a string to an intmax_t integer, with an arbitrary base. 0nnn -> base 8 0[Xx]nn -> base 16 Anything else: [base#]number (this is implemented to match ksh93) Base may be >=2 and <=64. If base is <= 36, the numbers are drawn from [0-9][a-zA-Z], and lowercase and uppercase letters may be used interchangably. If base is > 36 and <= 64, the numbers are drawn from [0-9][a-z][A-Z]_@ (a = 10, z = 35, A = 36, Z = 61, _ = 62, @ = 63 -- you get the picture). */ static intmax_t strlong (num) char *num; { register char *s; register unsigned char c; int base, foundbase; intmax_t val; s = num; base = 10; foundbase = 0; if (*s == '0') { s++; if (*s == '\0') return 0; /* Base 16? */ if (*s == 'x' || *s == 'X') { base = 16; s++; } else base = 8; foundbase++; } val = 0; for (c = *s++; c; c = *s++) { if (c == '#') { if (foundbase) evalerror ("bad number"); /* Illegal base specifications raise an evaluation error. */ if (val < 2 || val > 64) evalerror ("illegal arithmetic base"); base = val; val = 0; foundbase++; } else if (ISALNUM(c) || (c == '_') || (c == '@')) { if (DIGIT(c)) c = TODIGIT(c); else if (c >= 'a' && c <= 'z') c -= 'a' - 10; else if (c >= 'A' && c <= 'Z') c -= 'A' - ((base <= 36) ? 10 : 36); else if (c == '@') c = 62; else if (c == '_') c = 63; if (c >= base) evalerror ("value too great for base"); val = (val * base) + c; } else break; } return (val); } #if defined (EXPR_TEST) void * xmalloc (n) int n; { return (malloc (n)); } void * xrealloc (s, n) char *s; int n; { return (realloc (s, n)); } SHELL_VAR *find_variable () { return 0;} SHELL_VAR *bind_variable () { return 0; } char *get_string_value () { return 0; } procenv_t top_level; main (argc, argv) int argc; char **argv; { register int i; intmax_t v; int expok; if (setjmp (top_level)) exit (0); for (i = 1; i < argc; i++) { v = evalexp (argv[i], &expok); if (expok == 0) fprintf (stderr, "%s: expression error\n", argv[i]); else printf ("'%s' -> %ld\n", argv[i], v); } exit (0); } int builtin_error (format, arg1, arg2, arg3, arg4, arg5) char *format; { fprintf (stderr, "expr: "); fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5); fprintf (stderr, "\n"); return 0; } char * itos (n) intmax_t n; { return ("42"); } #endif /* EXPR_TEST */
/* flags.c -- Everything about flags except the `set' command. That is in builtins.c */ /* Copyright (C) 1987,1989 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ /* Flags hacking. */ #include "config.h" #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "shell.h" #include "flags.h" #if defined (BANG_HISTORY) # include "bashhist.h" #endif #if defined (JOB_CONTROL) extern int set_job_control __P((int)); #endif #if defined (RESTRICTED_SHELL) extern char *shell_name; #endif /* -c, -s invocation options -- not really flags, but they show up in $- */ extern int want_pending_command, read_from_stdin; /* **************************************************************** */ /* */ /* The Standard Sh Flags. */ /* */ /* **************************************************************** */ /* Non-zero means automatically mark variables which are modified or created as auto export variables. */ int mark_modified_vars = 0; /* Non-zero causes asynchronous job notification. Otherwise, job state notification only takes place just before a primary prompt is printed. */ int asynchronous_notification = 0; /* Non-zero means exit immediately if a command exits with a non-zero exit status. */ int exit_immediately_on_error = 0; /* Non-zero means disable filename globbing. */ int disallow_filename_globbing = 0; /* Non-zero means that all keyword arguments are placed into the environment for a command, not just those that appear on the line before the command name. */ int place_keywords_in_env = 0; /* Non-zero means read commands, but don't execute them. This is useful for debugging shell scripts that should do something hairy and possibly destructive. */ int read_but_dont_execute = 0; /* Non-zero means end of file is after one command. */ int just_one_command = 0; /* Non-zero means don't overwrite existing files while doing redirections. */ int noclobber = 0; /* Non-zero means trying to get the value of $i where $i is undefined causes an error, instead of a null substitution. */ int unbound_vars_is_error = 0; /* Non-zero means type out input lines after you read them. */ int echo_input_at_read = 0; /* Non-zero means type out the command definition after reading, but before executing. */ int echo_command_at_execute = 0; /* Non-zero means turn on the job control features. */ int jobs_m_flag = 0; /* Non-zero means this shell is interactive, even if running under a pipe. */ int forced_interactive = 0; /* By default, follow the symbolic links as if they were real directories while hacking the `cd' command. This means that `cd ..' moves up in the string of symbolic links that make up the current directory, instead of the absolute directory. The shell variable `nolinks' also controls this flag. */ int no_symbolic_links = 0; /* **************************************************************** */ /* */ /* Non-Standard Flags Follow Here. */ /* */ /* **************************************************************** */ #if 0 /* Non-zero means do lexical scoping in the body of a FOR command. */ int lexical_scoping = 0; #endif /* Non-zero means no such thing as invisible variables. */ int no_invisible_vars = 0; /* Non-zero means look up and remember command names in a hash table, */ int hashing_enabled = 1; #if defined (BANG_HISTORY) /* Non-zero means that we are doing history expansion. The default. This means !22 gets the 22nd line of history. */ int history_expansion = 1; #endif /* BANG_HISTORY */ /* Non-zero means that we allow comments to appear in interactive commands. */ int interactive_comments = 1; #if defined (RESTRICTED_SHELL) /* Non-zero means that this shell is `restricted'. A restricted shell disallows: changing directories, command or path names containing `/', unsetting or resetting the values of $PATH and $SHELL, and any type of output redirection. */ int restricted = 0; /* currently restricted */ int restricted_shell = 0; /* shell was started in restricted mode. */ #endif /* RESTRICTED_SHELL */ /* Non-zero means that this shell is running in `privileged' mode. This is required if the shell is to run setuid. If the `-p' option is not supplied at startup, and the real and effective uids or gids differ, disable_priv_mode is called to relinquish setuid status. */ int privileged_mode = 0; #if defined (BRACE_EXPANSION) /* Zero means to disable brace expansion: foo{a,b} -> fooa foob */ int brace_expansion = 1; #endif /* **************************************************************** */ /* */ /* The Flags ALIST. */ /* */ /* **************************************************************** */ struct flags_alist shell_flags[] = { /* Standard sh flags. */ { 'a', &mark_modified_vars }, #if defined (JOB_CONTROL) { 'b', &asynchronous_notification }, #endif /* JOB_CONTROL */ { 'e', &exit_immediately_on_error }, { 'f', &disallow_filename_globbing }, { 'h', &hashing_enabled }, { 'i', &forced_interactive }, { 'k', &place_keywords_in_env }, #if defined (JOB_CONTROL) { 'm', &jobs_m_flag }, #endif /* JOB_CONTROL */ { 'n', &read_but_dont_execute }, { 'p', &privileged_mode }, #if defined (RESTRICTED_SHELL) { 'r', &restricted }, #endif /* RESTRICTED_SHELL */ { 't', &just_one_command }, { 'u', &unbound_vars_is_error }, { 'v', &echo_input_at_read }, { 'x', &echo_command_at_execute }, { 'C', &noclobber }, /* New flags that control non-standard things. */ #if 0 { 'l', &lexical_scoping }, #endif { 'I', &no_invisible_vars }, { 'P', &no_symbolic_links }, #if defined (BRACE_EXPANSION) { 'B', &brace_expansion }, #endif #if defined (BANG_HISTORY) { 'H', &history_expansion }, #endif /* BANG_HISTORY */ {0, (int *)NULL} }; #define NUM_SHELL_FLAGS (sizeof (shell_flags) / sizeof (struct flags_alist)) char optflags[NUM_SHELL_FLAGS+4] = { '+' }; int * find_flag (name) int name; { int i; for (i = 0; shell_flags[i].name; i++) { if (shell_flags[i].name == name) return (shell_flags[i].value); } return (FLAG_UNKNOWN); } /* Change the state of a flag, and return it's original value, or return FLAG_ERROR if there is no flag FLAG. ON_OR_OFF must be either FLAG_ON or FLAG_OFF. */ int change_flag (flag, on_or_off) int flag; int on_or_off; { int *value, old_value; #if defined (RESTRICTED_SHELL) /* Don't allow "set +r" in a shell which is `restricted'. */ if (restricted && flag == 'r' && on_or_off == FLAG_OFF) return (FLAG_ERROR); #endif /* RESTRICTED_SHELL */ value = find_flag (flag); if ((value == (int *)FLAG_UNKNOWN) || (on_or_off != FLAG_ON && on_or_off != FLAG_OFF)) return (FLAG_ERROR); old_value = *value; *value = (on_or_off == FLAG_ON) ? 1 : 0; /* Special cases for a few flags. */ switch (flag) { #if defined (BANG_HISTORY) case 'H': if (on_or_off == FLAG_ON) bash_initialize_history (); break; #endif #if defined (JOB_CONTROL) case 'm': set_job_control (on_or_off == FLAG_ON); break; #endif /* JOB_CONTROL */ case 'n': if (interactive_shell) read_but_dont_execute = 0; break; case 'p': if (on_or_off == FLAG_OFF) disable_priv_mode (); break; #if defined (RESTRICTED_SHELL) case 'r': if (on_or_off == FLAG_ON) maybe_make_restricted (shell_name); break; #endif } return (old_value); } /* Return a string which is the names of all the currently set shell flags. */ char * which_set_flags () { char *temp; int i, string_index; temp = (char *)xmalloc (1 + NUM_SHELL_FLAGS + read_from_stdin + want_pending_command); for (i = string_index = 0; shell_flags[i].name; i++) if (*(shell_flags[i].value)) temp[string_index++] = shell_flags[i].name; if (want_pending_command) temp[string_index++] = 'c'; if (read_from_stdin) temp[string_index++] = 's'; temp[string_index] = '\0'; return (temp); } void reset_shell_flags () { mark_modified_vars = exit_immediately_on_error = disallow_filename_globbing = 0; place_keywords_in_env = read_but_dont_execute = just_one_command = 0; noclobber = unbound_vars_is_error = echo_input_at_read = 0; echo_command_at_execute = jobs_m_flag = forced_interactive = 0; no_symbolic_links = no_invisible_vars = privileged_mode = 0; hashing_enabled = interactive_comments = 1; #if defined (JOB_CONTROL) asynchronous_notification = 0; #endif #if defined (BANG_HISTORY) history_expansion = 1; #endif #if defined (BRACE_EXPANSION) brace_expansion = 1; #endif #if defined (RESTRICTED_SHELL) restricted = 0; #endif } void initialize_flags () { register int i; for (i = 0; shell_flags[i].name; i++) optflags[i+1] = shell_flags[i].name; optflags[++i] = 'o'; optflags[++i] = ';'; optflags[i+1] = '\0'; }
/* The thing that makes children, remembers them, and contains wait loops. */ /* This file works under BSD, System V, minix, and Posix systems. It does not implement job control. */ /* Copyright (C) 1987, 1989, 1992 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #include "bashtypes.h" #include "filecntl.h" #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <stdio.h> #include <signal.h> #include <errno.h> #if defined (BUFFERED_INPUT) # include "input.h" #endif /* Need to include this up here for *_TTY_DRIVER definitions. */ #include "shtty.h" #if !defined (STRUCT_WINSIZE_IN_SYS_IOCTL) /* For struct winsize on SCO */ /* sys/ptem.h has winsize but needs mblk_t from sys/stream.h */ # if defined (HAVE_SYS_PTEM_H) && defined (TIOCGWINSZ) && defined (SIGWINCH) # if defined (HAVE_SYS_STREAM_H) # include <sys/stream.h> # endif # include <sys/ptem.h> # endif /* HAVE_SYS_PTEM_H && TIOCGWINSZ && SIGWINCH */ #endif /* !STRUCT_WINSIZE_IN_SYS_IOCTL */ #include "shell.h" #include "jobs.h" #include "builtins/builtext.h" /* for wait_builtin */ #define DEFAULT_CHILD_MAX 32 #if defined (_POSIX_VERSION) || !defined (HAVE_KILLPG) # define killpg(pg, sig) kill(-(pg),(sig)) #endif /* USG || _POSIX_VERSION */ #if !defined (HAVE_SIGINTERRUPT) # define siginterrupt(sig, code) #endif /* !HAVE_SIGINTERRUPT */ #if defined (HAVE_WAITPID) # define WAITPID(pid, statusp, options) waitpid (pid, statusp, options) #else # define WAITPID(pid, statusp, options) wait (statusp) #endif /* !HAVE_WAITPID */ /* Return the fd from which we are actually getting input. */ #define input_tty() (shell_tty != -1) ? shell_tty : fileno (stderr) #if !defined (errno) extern int errno; #endif /* !errno */ #if defined (READLINE) extern void rl_set_screen_size __P((int, int)); #endif extern int interactive, interactive_shell, login_shell; extern int subshell_environment; extern int last_command_exit_value; extern int interrupt_immediately; extern sh_builtin_func_t *this_shell_builtin; #if defined (HAVE_POSIX_SIGNALS) extern sigset_t top_level_mask; #endif extern procenv_t wait_intr_buf; extern int wait_signal_received; pid_t last_made_pid = NO_PID; pid_t last_asynchronous_pid = NO_PID; /* Call this when you start making children. */ int already_making_children = 0; /* The controlling tty for this shell. */ int shell_tty = -1; /* If this is non-zero, $LINES and $COLUMNS are reset after every process exits from get_tty_state(). */ int check_window_size; /* STATUS and FLAGS are only valid if pid != NO_PID STATUS is only valid if (flags & PROC_RUNNING) == 0 */ struct proc_status { pid_t pid; int status; /* Exit status of PID or 128 + fatal signal number */ int flags; }; /* Values for proc_status.flags */ #define PROC_RUNNING 0x01 #define PROC_NOTIFIED 0x02 #define PROC_ASYNC 0x04 /* Return values from find_status_by_pid */ #define PROC_BAD -1 #define PROC_STILL_ALIVE -2 static struct proc_status *pid_list = (struct proc_status *)NULL; static int pid_list_size; static int wait_sigint_received; static long child_max = -1L; static void alloc_pid_list __P((void)); static int find_proc_slot __P((void)); static int find_index_by_pid __P((pid_t)); static int find_status_by_pid __P((pid_t)); static int process_exit_status __P((WAIT)); static void set_pid_status __P((pid_t, WAIT)); static void set_pid_flags __P((pid_t, int)); static void unset_pid_flags __P((pid_t, int)); static void add_pid __P((pid_t, int)); static void mark_dead_jobs_as_notified __P((int)); static void get_new_window_size __P((int)); static sighandler sigwinch_sighandler __P((int)); static sighandler wait_sigint_handler __P((int)); #if defined (HAVE_WAITPID) static void reap_zombie_children __P((void)); #endif static void restore_sigint_handler __P((void)); /* Allocate new, or grow existing PID_LIST. */ static void alloc_pid_list () { register int i; int old = pid_list_size; pid_list_size += 10; pid_list = (struct proc_status *)xrealloc (pid_list, pid_list_size * sizeof (struct proc_status)); /* None of the newly allocated slots have process id's yet. */ for (i = old; i < pid_list_size; i++) pid_list[i].pid = NO_PID; } /* Return the offset within the PID_LIST array of an empty slot. This can create new slots if all of the existing slots are taken. */ static int find_proc_slot () { register int i; for (i = 0; i < pid_list_size; i++) if (pid_list[i].pid == NO_PID) return (i); if (i == pid_list_size) alloc_pid_list (); return (i); } /* Return the offset within the PID_LIST array of a slot containing PID, or the value NO_PID if the pid wasn't found. */ static int find_index_by_pid (pid) pid_t pid; { register int i; for (i = 0; i < pid_list_size; i++) if (pid_list[i].pid == pid) return (i); return (NO_PID); } /* Return the status of PID as looked up in the PID_LIST array. A return value of PROC_BAD indicates that PID wasn't found. */ static int find_status_by_pid (pid) pid_t pid; { int i; i = find_index_by_pid (pid); if (i == NO_PID) return (PROC_BAD); if (pid_list[i].flags & PROC_RUNNING) return (PROC_STILL_ALIVE); return (pid_list[i].status); } static int process_exit_status (status) WAIT status; { if (WIFSIGNALED (status)) return (128 + WTERMSIG (status)); else return (WEXITSTATUS (status)); } /* Give PID the status value STATUS in the PID_LIST array. */ static void set_pid_status (pid, status) pid_t pid; WAIT status; { int slot; slot = find_index_by_pid (pid); if (slot == NO_PID) return; pid_list[slot].status = process_exit_status (status); pid_list[slot].flags &= ~PROC_RUNNING; /* If it's not a background process, mark it as notified so it gets cleaned up. */ if ((pid_list[slot].flags & PROC_ASYNC) == 0) pid_list[slot].flags |= PROC_NOTIFIED; } /* Give PID the flags FLAGS in the PID_LIST array. */ static void set_pid_flags (pid, flags) pid_t pid; int flags; { int slot; slot = find_index_by_pid (pid); if (slot == NO_PID) return; pid_list[slot].flags |= flags; } /* Unset FLAGS for PID in the pid list */ static void unset_pid_flags (pid, flags) pid_t pid; int flags; { int slot; slot = find_index_by_pid (pid); if (slot == NO_PID) return; pid_list[slot].flags &= ~flags; } static void add_pid (pid, async) pid_t pid; int async; { int slot; slot = find_proc_slot (); pid_list[slot].pid = pid; pid_list[slot].status = -1; pid_list[slot].flags = PROC_RUNNING; if (async) pid_list[slot].flags |= PROC_ASYNC; } static void mark_dead_jobs_as_notified (force) int force; { register int i, ndead; /* first, count the number of non-running async jobs if FORCE == 0 */ for (i = ndead = 0; force == 0 && i < pid_list_size; i++) { if (pid_list[i].pid == NO_PID) continue; if (((pid_list[i].flags & PROC_RUNNING) == 0) && (pid_list[i].flags & PROC_ASYNC)) ndead++; } if (child_max < 0) child_max = getmaxchild (); if (child_max < 0) child_max = DEFAULT_CHILD_MAX; if (force == 0 && ndead <= child_max) return; /* If FORCE == 0, we just mark as many non-running async jobs as notified to bring us under the CHILD_MAX limit. */ for (i = 0; i < pid_list_size; i++) { if (pid_list[i].pid == NO_PID) continue; if (((pid_list[i].flags & PROC_RUNNING) == 0) && pid_list[i].pid != last_asynchronous_pid) { pid_list[i].flags |= PROC_NOTIFIED; if (force == 0 && (pid_list[i].flags & PROC_ASYNC) && --ndead <= child_max) break; } } } /* Remove all dead, notified jobs from the pid_list. */ int cleanup_dead_jobs () { register int i; #if defined (HAVE_WAITPID) reap_zombie_children (); #endif for (i = 0; i < pid_list_size; i++) { if ((pid_list[i].flags & PROC_RUNNING) == 0 && (pid_list[i].flags & PROC_NOTIFIED)) pid_list[i].pid = NO_PID; } return 0; } void reap_dead_jobs () { mark_dead_jobs_as_notified (0); cleanup_dead_jobs (); } /* Initialize the job control mechanism, and set up the tty stuff. */ initialize_job_control (force) int force; { shell_tty = fileno (stderr); if (interactive) get_tty_state (); } #if defined (TIOCGWINSZ) && defined (SIGWINCH) static SigHandler *old_winch = (SigHandler *)SIG_DFL; static void get_new_window_size (from_sig) int from_sig; { struct winsize win; int tty; tty = input_tty (); if (tty >= 0 && (ioctl (tty, TIOCGWINSZ, &win) == 0) && win.ws_row > 0 && win.ws_col > 0) { #if defined (aixpc) shell_tty_info.c_winsize = win; /* structure copying */ #endif sh_set_lines_and_columns (win.ws_row, win.ws_col); #if defined (READLINE) rl_set_screen_size (win.ws_row, win.ws_col); #endif } } static sighandler sigwinch_sighandler (sig) int sig; { #if defined (MUST_REINSTALL_SIGHANDLERS) set_signal_handler (SIGWINCH, sigwinch_sighandler); #endif /* MUST_REINSTALL_SIGHANDLERS */ get_new_window_size (1); } #else static void get_new_window_size (from_sig) int from_sig; { } #endif /* TIOCGWINSZ && SIGWINCH */ void set_sigwinch_handler () { #if defined (TIOCGWINSZ) && defined (SIGWINCH) old_winch = set_signal_handler (SIGWINCH, sigwinch_sighandler); #endif } void unset_sigwinch_handler () { #if defined (TIOCGWINSZ) && defined (SIGWINCH) set_signal_handler (SIGWINCH, old_winch); #endif } /* Setup this shell to handle C-C, etc. */ void initialize_job_signals () { set_signal_handler (SIGINT, sigint_sighandler); set_sigwinch_handler (); /* If this is a login shell we don't wish to be disturbed by stop signals. */ if (login_shell) ignore_tty_job_signals (); } #if defined (HAVE_WAITPID) /* Collect the status of all zombie children so that their system resources can be deallocated. */ static void reap_zombie_children () { #if defined (WNOHANG) pid_t pid; WAIT status; while ((pid = waitpid (-1, (int *)&status, WNOHANG)) > 0) set_pid_status (pid, status); #endif } #endif /* WAITPID && WNOHANG */ /* Fork, handling errors. Returns the pid of the newly made child, or 0. COMMAND is just for remembering the name of the command; we don't do anything else with it. ASYNC_P says what to do with the tty. If non-zero, then don't give it away. */ pid_t make_child (command, async_p) char *command; int async_p; { pid_t pid; #if defined (HAVE_WAITPID) int retry = 1; #endif /* HAVE_WAITPID */ /* Discard saved memory. */ if (command) free (command); start_pipeline (); #if defined (BUFFERED_INPUT) /* If default_buffered_input is active, we are reading a script. If the command is asynchronous, we have already duplicated /dev/null as fd 0, but have not changed the buffered stream corresponding to the old fd 0. We don't want to sync the stream in this case. */ if (default_buffered_input != -1 && (!async_p || default_buffered_input > 0)) sync_buffered_stream (default_buffered_input); #endif /* BUFFERED_INPUT */ /* Create the child, handle severe errors. */ #if defined (HAVE_WAITPID) retry_fork: #endif /* HAVE_WAITPID */ if ((pid = fork ()) < 0) { #if defined (HAVE_WAITPID) /* Posix systems with a non-blocking waitpid () system call available get another chance after zombies are reaped. */ if (errno == EAGAIN && retry) { reap_zombie_children (); retry = 0; goto retry_fork; } #endif /* HAVE_WAITPID */ sys_error ("fork"); throw_to_top_level (); } if (pid == 0) { #if defined (BUFFERED_INPUT) unset_bash_input (0); #endif /* BUFFERED_INPUT */ #if defined (HAVE_POSIX_SIGNALS) /* Restore top-level signal mask. */ sigprocmask (SIG_SETMASK, &top_level_mask, (sigset_t *)NULL); #endif /* Ignore INT and QUIT in asynchronous children. */ if (async_p) last_asynchronous_pid = getpid (); default_tty_job_signals (); } else { /* In the parent. */ last_made_pid = pid; if (async_p) last_asynchronous_pid = pid; add_pid (pid, async_p); } return (pid); } void ignore_tty_job_signals () { #if defined (SIGTSTP) set_signal_handler (SIGTSTP, SIG_IGN); set_signal_handler (SIGTTIN, SIG_IGN); set_signal_handler (SIGTTOU, SIG_IGN); #endif } void default_tty_job_signals () { #if defined (SIGTSTP) set_signal_handler (SIGTSTP, SIG_DFL); set_signal_handler (SIGTTIN, SIG_DFL); set_signal_handler (SIGTTOU, SIG_DFL); #endif } /* Wait for a single pid (PID) and return its exit status. Called by the wait builtin. */ int wait_for_single_pid (pid) pid_t pid; { pid_t got_pid; WAIT status; int pstatus; pstatus = find_status_by_pid (pid); if (pstatus == PROC_BAD) { internal_error ("wait: pid %ld is not a child of this shell", (long)pid); return (127); } if (pstatus != PROC_STILL_ALIVE) return (pstatus); siginterrupt (SIGINT, 1); while ((got_pid = WAITPID (pid, &status, 0)) != pid) { if (got_pid < 0) { if (errno != EINTR && errno != ECHILD) { siginterrupt (SIGINT, 0); sys_error ("wait"); } break; } else if (got_pid > 0) set_pid_status (got_pid, status); } if (got_pid > 0) { set_pid_status (got_pid, status); set_pid_flags (got_pid, PROC_NOTIFIED); } siginterrupt (SIGINT, 0); QUIT; return (got_pid > 0 ? process_exit_status (status) : -1); } /* Wait for all of the shell's children to exit. Called by the `wait' builtin. */ void wait_for_background_pids () { pid_t got_pid; WAIT status; /* If we aren't using job control, we let the kernel take care of the bookkeeping for us. wait () will return -1 and set errno to ECHILD when there are no more unwaited-for child processes on both 4.2 BSD-based and System V-based systems. */ siginterrupt (SIGINT, 1); /* Wait for ECHILD */ while ((got_pid = WAITPID (-1, &status, 0)) != -1) set_pid_status (got_pid, status); if (errno != EINTR && errno != ECHILD) { siginterrupt (SIGINT, 0); sys_error("wait"); } siginterrupt (SIGINT, 0); QUIT; mark_dead_jobs_as_notified (1); cleanup_dead_jobs (); } /* Make OLD_SIGINT_HANDLER the SIGINT signal handler. */ #define INVALID_SIGNAL_HANDLER (SigHandler *)wait_for_background_pids static SigHandler *old_sigint_handler = INVALID_SIGNAL_HANDLER; static void restore_sigint_handler () { if (old_sigint_handler != INVALID_SIGNAL_HANDLER) { set_signal_handler (SIGINT, old_sigint_handler); old_sigint_handler = INVALID_SIGNAL_HANDLER; } } /* Handle SIGINT while we are waiting for children in a script to exit. All interrupts are effectively ignored by the shell, but allowed to kill a running job. */ static sighandler wait_sigint_handler (sig) int sig; { SigHandler *sigint_handler; /* If we got a SIGINT while in `wait', and SIGINT is trapped, do what POSIX.2 says (see builtins/wait.def for more info). */ if (this_shell_builtin && this_shell_builtin == wait_builtin && signal_is_trapped (SIGINT) && ((sigint_handler = trap_to_sighandler (SIGINT)) == trap_handler)) { last_command_exit_value = EXECUTION_FAILURE; restore_sigint_handler (); interrupt_immediately = 0; trap_handler (SIGINT); /* set pending_traps[SIGINT] */ wait_signal_received = SIGINT; longjmp (wait_intr_buf, 1); } if (interrupt_immediately) { last_command_exit_value = EXECUTION_FAILURE; restore_sigint_handler (); ADDINTERRUPT; QUIT; } wait_sigint_received = 1; SIGRETURN (0); } /* Wait for pid (one of our children) to terminate. This is called only by the execution code in execute_cmd.c. */ int wait_for (pid) pid_t pid; { int return_val, pstatus; pid_t got_pid; WAIT status; pstatus = find_status_by_pid (pid); if (pstatus == PROC_BAD) return (0); if (pstatus != PROC_STILL_ALIVE) return (pstatus); /* If we are running a script, ignore SIGINT while we're waiting for a child to exit. The loop below does some of this, but not all. */ wait_sigint_received = 0; if (interactive_shell == 0) old_sigint_handler = set_signal_handler (SIGINT, wait_sigint_handler); while ((got_pid = WAITPID (-1, &status, 0)) != pid) /* XXX was pid now -1 */ { if (got_pid < 0 && errno == ECHILD) { #if !defined (_POSIX_VERSION) status.w_termsig = status.w_retcode = 0; #else status = 0; #endif /* _POSIX_VERSION */ break; } else if (got_pid < 0 && errno != EINTR) programming_error ("wait_for(%ld): %s", (long)pid, strerror(errno)); else if (got_pid > 0) set_pid_status (got_pid, status); } if (got_pid > 0) set_pid_status (got_pid, status); #if defined (HAVE_WAITPID) if (got_pid >= 0) reap_zombie_children (); #endif /* HAVE_WAITPID */ if (interactive_shell == 0) { SigHandler *temp_handler; temp_handler = old_sigint_handler; restore_sigint_handler (); /* If the job exited because of SIGINT, make sure the shell acts as if it had received one also. */ if (WIFSIGNALED (status) && (WTERMSIG (status) == SIGINT)) { if (maybe_call_trap_handler (SIGINT) == 0) { if (temp_handler == SIG_DFL) termination_unwind_protect (SIGINT); else if (temp_handler != INVALID_SIGNAL_HANDLER && temp_handler != SIG_IGN) (*temp_handler) (SIGINT); } } } /* Default return value. */ /* ``a full 8 bits of status is returned'' */ return_val = process_exit_status (status); #if !defined (DONT_REPORT_SIGPIPE) if ((WIFSTOPPED (status) == 0) && WIFSIGNALED (status) && (WTERMSIG (status) != SIGINT)) #else if ((WIFSTOPPED (status) == 0) && WIFSIGNALED (status) && (WTERMSIG (status) != SIGINT) && (WTERMSIG (status) != SIGPIPE)) #endif { fprintf (stderr, "%s", strsignal (WTERMSIG (status))); if (WIFCORED (status)) fprintf (stderr, " (core dumped)"); fprintf (stderr, "\n"); } if (interactive_shell && subshell_environment == 0) { if (WIFSIGNALED (status) || WIFSTOPPED (status)) set_tty_state (); else get_tty_state (); } return (return_val); } /* Give PID SIGNAL. This determines what job the pid belongs to (if any). If PID does belong to a job, and the job is stopped, then CONTinue the job after giving it SIGNAL. Returns -1 on failure. If GROUP is non-null, then kill the process group associated with PID. */ int kill_pid (pid, signal, group) pid_t pid; int signal, group; { int result; result = group ? killpg (pid, signal) : kill (pid, signal); return (result); } static TTYSTRUCT shell_tty_info; static int got_tty_state; /* Fill the contents of shell_tty_info with the current tty info. */ get_tty_state () { int tty; tty = input_tty (); if (tty != -1) { ttgetattr (tty, &shell_tty_info); got_tty_state = 1; if (check_window_size) get_new_window_size (0); } } /* Make the current tty use the state in shell_tty_info. */ int set_tty_state () { int tty; tty = input_tty (); if (tty != -1) { if (got_tty_state == 0) return 0; ttsetattr (tty, &shell_tty_info); } return 0; } /* Give the terminal to PGRP. */ give_terminal_to (pgrp, force) pid_t pgrp; int force; { } /* Stop a pipeline. */ int stop_pipeline (async, ignore) int async; COMMAND *ignore; { already_making_children = 0; return 0; } void start_pipeline () { already_making_children = 1; } void stop_making_children () { already_making_children = 0; } int get_job_by_pid (pid, block) pid_t pid; int block; { int i; i = find_index_by_pid (pid); return ((i == NO_PID) ? PROC_BAD : i); } /* Print descriptive information about the job with leader pid PID. */ void describe_pid (pid) pid_t pid; { fprintf (stderr, "%ld\n", (long) pid); } void unfreeze_jobs_list () { } int count_all_jobs () { return 0; }
/* subst.c -- The part of the shell that does parameter, command, and globbing substitutions. */ /* ``Have a little faith, there's magic in the night. You ain't a beauty, but, hey, you're alright.'' */ /* Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #include "bashtypes.h" #include <stdio.h> #include "chartypes.h" #include <pwd.h> #include <signal.h> #include <errno.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "bashansi.h" #include "posixstat.h" #include "shell.h" #include "flags.h" #include "jobs.h" #include "execute_cmd.h" #include "filecntl.h" #include "trap.h" #include "pathexp.h" #include "mailcheck.h" #include "shmbutil.h" #include "builtins/getopt.h" #include "builtins/common.h" #include <tilde/tilde.h> #include <glob/strmatch.h> #if !defined (errno) extern int errno; #endif /* !errno */ /* The size that strings change by. */ #define DEFAULT_INITIAL_ARRAY_SIZE 112 #define DEFAULT_ARRAY_SIZE 128 /* Variable types. */ #define VT_VARIABLE 0 #define VT_POSPARMS 1 #define VT_ARRAYVAR 2 #define VT_ARRAYMEMBER 3 /* Flags for quoted_strchr */ #define ST_BACKSL 0x01 #define ST_CTLESC 0x02 #define ST_SQUOTE 0x04 /* unused yet */ #define ST_DQUOTE 0x08 /* unused yet */ /* Flags for the string extraction functions. */ #define EX_NOALLOC 0x01 /* just skip; don't return substring */ #define EX_VARNAME 0x02 /* variable name; for string_extract () */ /* These defs make it easier to use the editor. */ #define LBRACE '{' #define RBRACE '}' #define LPAREN '(' #define RPAREN ')' /* Evaluates to 1 if C is one of the shell's special parameters whose length can be taken, but is also one of the special expansion characters. */ #define VALID_SPECIAL_LENGTH_PARAM(c) \ ((c) == '-' || (c) == '?' || (c) == '#') /* Evaluates to 1 if C is one of the shell's special parameters for which an indirect variable reference may be made. */ #define VALID_INDIR_PARAM(c) \ ((c) == '#' || (c) == '?' || (c) == '@' || (c) == '*') /* Evaluates to 1 if C is one of the OP characters that follows the parameter in ${parameter[:]OPword}. */ #define VALID_PARAM_EXPAND_CHAR(c) (sh_syntaxtab[(unsigned char)c] & CSUBSTOP) /* Evaluates to 1 if this is one of the shell's special variables. */ #define SPECIAL_VAR(name, wi) \ ((DIGIT (*name) && all_digits (name)) || \ (name[1] == '\0' && (sh_syntaxtab[(unsigned char)*name] & CSPECVAR)) || \ (wi && name[2] == '\0' && VALID_INDIR_PARAM (name[1]))) /* An expansion function that takes a string and a quoted flag and returns a WORD_LIST *. Used as the type of the third argument to expand_string_if_necessary(). */ typedef WORD_LIST *EXPFUNC __P((char *, int)); /* Process ID of the last command executed within command substitution. */ pid_t last_command_subst_pid = NO_PID; pid_t current_command_subst_pid = NO_PID; /* Variables used to keep track of the characters in IFS. */ SHELL_VAR *ifs_var; char *ifs_value; unsigned char ifs_cmap[UCHAR_MAX + 1]; unsigned char ifs_firstc; /* Extern functions and variables from different files. */ extern int last_command_exit_value; extern int subshell_environment; extern int eof_encountered; extern int return_catch_flag, return_catch_value; extern pid_t dollar_dollar_pid; extern int posixly_correct; extern char *this_command_name; extern struct fd_bitmap *current_fds_to_close; extern int wordexp_only; /* Non-zero means to allow unmatched globbed filenames to expand to a null file. */ int allow_null_glob_expansion; #if 0 /* Variables to keep track of which words in an expanded word list (the output of expand_word_list_internal) are the result of globbing expansions. GLOB_ARGV_FLAGS is used by execute_cmd.c. (CURRENTLY UNUSED). */ char *glob_argv_flags; static int glob_argv_flags_size; #endif static WORD_LIST expand_word_error, expand_word_fatal; static char expand_param_error, expand_param_fatal; /* Tell the expansion functions to not longjmp back to top_level on fatal errors. Enabled when doing completion and prompt string expansion. */ static int no_longjmp_on_fatal_error = 0; /* Set by expand_word_unsplit; used to inhibit splitting and re-joining $* on $IFS, primarily when doing assignment statements. */ static int expand_no_split_dollar_star = 0; /* Used to hold a list of variable assignments preceding a command. Global so the SIGCHLD handler in jobs.c can unwind-protect it when it runs a SIGCHLD trap. */ WORD_LIST *subst_assign_varlist = (WORD_LIST *)NULL; /* A WORD_LIST of words to be expanded by expand_word_list_internal, without any leading variable assignments. */ static WORD_LIST *garglist = (WORD_LIST *)NULL; static char *quoted_substring __P((char *, int, int)); static int quoted_strlen __P((char *)); static char *quoted_strchr __P((char *, int, int)); static char *expand_string_if_necessary __P((char *, int, EXPFUNC *)); static inline char *expand_string_to_string_internal __P((char *, int, EXPFUNC *)); static WORD_LIST *call_expand_word_internal __P((WORD_DESC *, int, int, int *, int *)); static WORD_LIST *expand_string_internal __P((char *, int)); static WORD_LIST *expand_string_leave_quoted __P((char *, int)); static WORD_LIST *expand_string_for_rhs __P((char *, int, int *, int *)); static WORD_LIST *list_quote_escapes __P((WORD_LIST *)); static char *dequote_escapes __P((char *)); static char *make_quoted_char __P((int)); static WORD_LIST *quote_list __P((WORD_LIST *)); static WORD_LIST *dequote_list __P((WORD_LIST *)); static char *remove_quoted_escapes __P((char *)); static char *remove_quoted_nulls __P((char *)); static int unquoted_substring __P((char *, char *)); static int unquoted_member __P((int, char *)); static int do_assignment_internal __P((const char *, int)); static char *string_extract_verbatim __P((char *, int *, char *)); static char *string_extract __P((char *, int *, char *, int)); static char *string_extract_double_quoted __P((char *, int *, int)); static inline char *string_extract_single_quoted __P((char *, int *)); static inline int skip_single_quoted __P((char *, size_t, int)); static int skip_double_quoted __P((char *, size_t, int)); static char *extract_delimited_string __P((char *, int *, char *, char *, char *, int)); static char *extract_dollar_brace_string __P((char *, int *, int, int)); static char *pos_params __P((char *, int, int, int)); static char *remove_pattern __P((char *, char *, int)); static int match_pattern_char __P((char *, char *)); static int match_pattern __P((char *, char *, int, char **, char **)); static int getpatspec __P((int, char *)); static char *getpattern __P((char *, int, int)); static char *variable_remove_pattern __P((char *, char *, int, int)); static char *list_remove_pattern __P((WORD_LIST *, char *, int, int, int)); static char *parameter_list_remove_pattern __P((int, char *, int, int)); #ifdef ARRAY_VARS static char *array_remove_pattern __P((ARRAY *, char *, int, char *, int)); #endif static char *parameter_brace_remove_pattern __P((char *, char *, char *, int, int)); static char *process_substitute __P((char *, int)); static char *read_comsub __P((int, int)); #ifdef ARRAY_VARS static arrayind_t array_length_reference __P((char *)); #endif static int valid_brace_expansion_word __P((char *, int)); static char *parameter_brace_expand_word __P((char *, int, int)); static char *parameter_brace_expand_indir __P((char *, int, int)); static char *parameter_brace_expand_rhs __P((char *, char *, int, int, int *, int *)); static void parameter_brace_expand_error __P((char *, char *)); static int valid_length_expression __P((char *)); static intmax_t parameter_brace_expand_length __P((char *)); static char *skiparith __P((char *, int)); static int verify_substring_values __P((char *, char *, int, intmax_t *, intmax_t *)); static int get_var_and_type __P((char *, char *, SHELL_VAR **, char **)); static char *parameter_brace_substring __P((char *, char *, char *, int)); static char *pos_params_pat_subst __P((char *, char *, char *, int)); static char *parameter_brace_patsub __P((char *, char *, char *, int)); static char *parameter_brace_expand __P((char *, int *, int, int *, int *)); static char *param_expand __P((char *, int *, int, int *, int *, int *, int *, int)); static WORD_LIST *expand_word_internal __P((WORD_DESC *, int, int, int *, int *)); static WORD_LIST *word_list_split __P((WORD_LIST *)); static WORD_LIST *separate_out_assignments __P((WORD_LIST *)); static WORD_LIST *glob_expand_word_list __P((WORD_LIST *, int)); #ifdef BRACE_EXPANSION static WORD_LIST *brace_expand_word_list __P((WORD_LIST *, int)); #endif static WORD_LIST *shell_expand_word_list __P((WORD_LIST *, int)); static WORD_LIST *expand_word_list_internal __P((WORD_LIST *, int)); /* **************************************************************** */ /* */ /* Utility Functions */ /* */ /* **************************************************************** */ #ifdef INCLUDE_UNUSED static char * quoted_substring (string, start, end) char *string; int start, end; { register int len, l; register char *result, *s, *r; len = end - start; /* Move to string[start], skipping quoted characters. */ for (s = string, l = 0; *s && l < start; ) { if (*s == CTLESC) { s++; continue; } l++; if (*s == 0) break; } r = result = (char *)xmalloc (2*len + 1); /* save room for quotes */ /* Copy LEN characters, including quote characters. */ s = string + l; for (l = 0; l < len; s++) { if (*s == CTLESC) *r++ = *s++; *r++ = *s; l++; if (*s == 0) break; } *r = '\0'; return result; } #endif #ifdef INCLUDE_UNUSED /* Return the length of S, skipping over quoted characters */ static int quoted_strlen (s) char *s; { register char *p; int i; i = 0; for (p = s; *p; p++) { if (*p == CTLESC) { p++; if (*p == 0) return (i + 1); } i++; } return i; } #endif /* Find the first occurrence of character C in string S, obeying shell quoting rules. If (FLAGS & ST_BACKSL) is non-zero, backslash-escaped characters are skipped. If (FLAGS & ST_CTLESC) is non-zero, characters escaped with CTLESC are skipped. */ static char * quoted_strchr (s, c, flags) char *s; int c, flags; { register char *p; for (p = s; *p; p++) { if (((flags & ST_BACKSL) && *p == '\\') || ((flags & ST_CTLESC) && *p == CTLESC)) { p++; if (*p == '\0') return ((char *)NULL); continue; } else if (*p == c) return p; } return ((char *)NULL); } /* Return 1 if CHARACTER appears in an unquoted portion of STRING. Return 0 otherwise. CHARACTER must be a single-byte character. */ static int unquoted_member (character, string) int character; char *string; { size_t slen; int sindex, c; DECLARE_MBSTATE; slen = strlen (string); sindex = 0; while (c = string[sindex]) { if (c == character) return (1); switch (c) { default: ADVANCE_CHAR (string, slen, sindex); break; case '\\': sindex++; if (string[sindex]) ADVANCE_CHAR (string, slen, sindex); break; case '\'': sindex = skip_single_quoted (string, slen, ++sindex); break; case '"': sindex = skip_double_quoted (string, slen, ++sindex); break; } } return (0); } /* Return 1 if SUBSTR appears in an unquoted portion of STRING. */ static int unquoted_substring (substr, string) char *substr, *string; { size_t slen; int sindex, c, sublen; DECLARE_MBSTATE; if (substr == 0 || *substr == '\0') return (0); slen = strlen (string); sublen = strlen (substr); for (sindex = 0; c = string[sindex]; ) { if (STREQN (string + sindex, substr, sublen)) return (1); switch (c) { case '\\': sindex++; if (string[sindex]) ADVANCE_CHAR (string, slen, sindex); break; case '\'': sindex = skip_single_quoted (string, slen, ++sindex); break; case '"': sindex = skip_double_quoted (string, slen, ++sindex); break; default: ADVANCE_CHAR (string, slen, sindex); break; } } return (0); } /* Most of the substitutions must be done in parallel. In order to avoid using tons of unclear goto's, I have some functions for manipulating malloc'ed strings. They all take INDX, a pointer to an integer which is the offset into the string where manipulation is taking place. They also take SIZE, a pointer to an integer which is the current length of the character array for this string. */ /* Append SOURCE to TARGET at INDEX. SIZE is the current amount of space allocated to TARGET. SOURCE can be NULL, in which case nothing happens. Gets rid of SOURCE by freeing it. Returns TARGET in case the location has changed. */ INLINE char * sub_append_string (source, target, indx, size) char *source, *target; int *indx, *size; { if (source) { int srclen, n; srclen = STRLEN (source); if (srclen >= (int)(*size - *indx)) { n = srclen + *indx; n = (n + DEFAULT_ARRAY_SIZE) - (n % DEFAULT_ARRAY_SIZE); target = (char *)xrealloc (target, (*size = n)); } FASTCOPY (source, target + *indx, srclen); *indx += srclen; target[*indx] = '\0'; free (source); } return (target); } #if 0 /* UNUSED */ /* Append the textual representation of NUMBER to TARGET. INDX and SIZE are as in SUB_APPEND_STRING. */ char * sub_append_number (number, target, indx, size) intmax_t number; int *indx, *size; char *target; { char *temp; temp = itos (number); return (sub_append_string (temp, target, indx, size)); } #endif /* Extract a substring from STRING, starting at SINDEX and ending with one of the characters in CHARLIST. Don't make the ending character part of the string. Leave SINDEX pointing at the ending character. Understand about backslashes in the string. If (flags & EX_VARNAME) is non-zero, and array variables have been compiled into the shell, everything between a `[' and a corresponding `]' is skipped over. If (flags & EX_NOALLOC) is non-zero, don't return the substring, just update SINDEX. */ static char * string_extract (string, sindex, charlist, flags) char *string; int *sindex; char *charlist; int flags; { register int c, i; size_t slen; char *temp; DECLARE_MBSTATE; slen = strlen (string + *sindex) + *sindex; i = *sindex; while (c = string[i]) { if (c == '\\') { if (string[i + 1]) i++; else break; } #if defined (ARRAY_VARS) else if ((flags & EX_VARNAME) && c == '[') { int ni; /* If this is an array subscript, skip over it and continue. */ ni = skipsubscript (string, i); if (string[ni] == ']') i = ni; } #endif else if (MEMBER (c, charlist)) break; ADVANCE_CHAR (string, slen, i); } temp = (flags & EX_NOALLOC) ? (char *)NULL : substring (string, *sindex, i); *sindex = i; return (temp); } /* Extract the contents of STRING as if it is enclosed in double quotes. SINDEX, when passed in, is the offset of the character immediately following the opening double quote; on exit, SINDEX is left pointing after the closing double quote. If STRIPDQ is non-zero, unquoted double quotes are stripped and the string is terminated by a null byte. Backslashes between the embedded double quotes are processed. If STRIPDQ is zero, an unquoted `"' terminates the string. */ static char * string_extract_double_quoted (string, sindex, stripdq) char *string; int *sindex, stripdq; { size_t slen; char *send; int j, i, t; unsigned char c; char *temp, *ret; /* The new string we return. */ int pass_next, backquote, si; /* State variables for the machine. */ int dquote; DECLARE_MBSTATE; slen = strlen (string + *sindex) + *sindex; send = string + slen; pass_next = backquote = dquote = 0; temp = (char *)xmalloc (1 + slen - *sindex); j = 0; i = *sindex; while (c = string[i]) { /* Process a character that was quoted by a backslash. */ if (pass_next) { /* Posix.2 sez: ``The backslash shall retain its special meaning as an escape character only when followed by one of the characters: $ ` " \ <newline>''. If STRIPDQ is zero, we handle the double quotes here and let expand_word_internal handle the rest. If STRIPDQ is non-zero, we have already been through one round of backslash stripping, and want to strip these backslashes only if DQUOTE is non-zero, indicating that we are inside an embedded double-quoted string. */ /* If we are in an embedded quoted string, then don't strip backslashes before characters for which the backslash retains its special meaning, but remove backslashes in front of other characters. If we are not in an embedded quoted string, don't strip backslashes at all. This mess is necessary because the string was already surrounded by double quotes (and sh has some really weird quoting rules). The returned string will be run through expansion as if it were double-quoted. */ if ((stripdq == 0 && c != '"') || (stripdq && ((dquote && (sh_syntaxtab[c] & CBSDQUOTE)) || dquote == 0))) temp[j++] = '\\'; pass_next = 0; add_one_character: COPY_CHAR_I (temp, j, string, send, i); continue; } /* A backslash protects the next character. The code just above handles preserving the backslash in front of any character but a double quote. */ if (c == '\\') { pass_next++; i++; continue; } /* Inside backquotes, ``the portion of the quoted string from the initial backquote and the characters up to the next backquote that is not preceded by a backslash, having escape characters removed, defines that command''. */ if (backquote) { if (c == '`') backquote = 0; temp[j++] = c; i++; continue; } if (c == '`') { temp[j++] = c; backquote++; i++; continue; } /* Pass everything between `$(' and the matching `)' or a quoted ${ ... } pair through according to the Posix.2 specification. */ if (c == '$' && ((string[i + 1] == LPAREN) || (string[i + 1] == LBRACE))) { si = i + 2; if (string[i + 1] == LPAREN) ret = extract_delimited_string (string, &si, "$(", "(", ")", 0); /*)*/ else ret = extract_dollar_brace_string (string, &si, 1, 0); temp[j++] = '$'; temp[j++] = string[i + 1]; for (t = 0; ret[t]; t++, j++) temp[j] = ret[t]; temp[j++] = string[si]; i = si + 1; free (ret); continue; } /* Add any character but a double quote to the quoted string we're accumulating. */ if (c != '"') goto add_one_character; /* c == '"' */ if (stripdq) { dquote ^= 1; i++; continue; } break; } temp[j] = '\0'; /* Point to after the closing quote. */ if (c) i++; *sindex = i; return (temp); } /* This should really be another option to string_extract_double_quoted. */ static int skip_double_quoted (string, slen, sind) char *string; size_t slen; int sind; { int c, i; char *ret; int pass_next, backquote, si; DECLARE_MBSTATE; pass_next = backquote = 0; i = sind; while (c = string[i]) { if (pass_next) { pass_next = 0; ADVANCE_CHAR (string, slen, i); continue; } else if (c == '\\') { pass_next++; i++; continue; } else if (backquote) { if (c == '`') backquote = 0; ADVANCE_CHAR (string, slen, i); continue; } else if (c == '`') { backquote++; i++; continue; } else if (c == '$' && ((string[i + 1] == LPAREN) || (string[i + 1] == LBRACE))) { si = i + 2; if (string[i + 1] == LPAREN) ret = extract_delimited_string (string, &si, "$(", "(", ")", EX_NOALLOC); else ret = extract_dollar_brace_string (string, &si, 0, EX_NOALLOC); i = si + 1; continue; } else if (c != '"') { ADVANCE_CHAR (string, slen, i); continue; } else break; } if (c) i++; return (i); } /* Extract the contents of STRING as if it is enclosed in single quotes. SINDEX, when passed in, is the offset of the character immediately following the opening single quote; on exit, SINDEX is left pointing after the closing single quote. */ static inline char * string_extract_single_quoted (string, sindex) char *string; int *sindex; { register int i; size_t slen; char *t; DECLARE_MBSTATE; slen = strlen (string + *sindex) + *sindex; i = *sindex; while (string[i] && string[i] != '\'') ADVANCE_CHAR (string, slen, i); t = substring (string, *sindex, i); if (string[i]) i++; *sindex = i; return (t); } static inline int skip_single_quoted (string, slen, sind) char *string; size_t slen; int sind; { register int c; DECLARE_MBSTATE; c = sind; while (string[c] && string[c] != '\'') ADVANCE_CHAR (string, slen, c); if (string[c]) c++; return c; } /* Just like string_extract, but doesn't hack backslashes or any of that other stuff. Obeys CTLESC quoting. Used to do splitting on $IFS. */ static char * string_extract_verbatim (string, sindex, charlist) char *string; int *sindex; char *charlist; { register int i = *sindex; int c; char *temp; if (charlist[0] == '\'' && charlist[1] == '\0') { temp = string_extract_single_quoted (string, sindex); --*sindex; /* leave *sindex at separator character */ return temp; } for (i = *sindex; c = string[i]; i++) { if (c == CTLESC) { i++; continue; } if (MEMBER (c, charlist)) break; } temp = substring (string, *sindex, i); *sindex = i; return (temp); } /* Extract the $( construct in STRING, and return a new string. Start extracting at (SINDEX) as if we had just seen "$(". Make (SINDEX) get the position of the matching ")". */ char * extract_command_subst (string, sindex) char *string; int *sindex; { return (extract_delimited_string (string, sindex, "$(", "(", ")", 0)); } /* Extract the $[ construct in STRING, and return a new string. (]) Start extracting at (SINDEX) as if we had just seen "$[". Make (SINDEX) get the position of the matching "]". */ char * extract_arithmetic_subst (string, sindex) char *string; int *sindex; { return (extract_delimited_string (string, sindex, "$[", "[", "]", 0)); /*]*/ } #if defined (PROCESS_SUBSTITUTION) /* Extract the <( or >( construct in STRING, and return a new string. Start extracting at (SINDEX) as if we had just seen "<(". Make (SINDEX) get the position of the matching ")". */ /*))*/ char * extract_process_subst (string, starter, sindex) char *string; char *starter; int *sindex; { return (extract_delimited_string (string, sindex, starter, "(", ")", 0)); } #endif /* PROCESS_SUBSTITUTION */ #if defined (ARRAY_VARS) char * extract_array_assignment_list (string, sindex) char *string; int *sindex; { return (extract_delimited_string (string, sindex, "(", (char *)NULL, ")", 0)); } #endif /* Extract and create a new string from the contents of STRING, a character string delimited with OPENER and CLOSER. SINDEX is the address of an int describing the current offset in STRING; it should point to just after the first OPENER found. On exit, SINDEX gets the position of the last character of the matching CLOSER. If OPENER is more than a single character, ALT_OPENER, if non-null, contains a character string that can also match CLOSER and thus needs to be skipped. */ static char * extract_delimited_string (string, sindex, opener, alt_opener, closer, flags) char *string; int *sindex; char *opener, *alt_opener, *closer; int flags; { int i, c, si; size_t slen; char *t, *result; int pass_character, nesting_level; int len_closer, len_opener, len_alt_opener; DECLARE_MBSTATE; slen = strlen (string + *sindex) + *sindex; len_opener = STRLEN (opener); len_alt_opener = STRLEN (alt_opener); len_closer = STRLEN (closer); pass_character = 0; nesting_level = 1; i = *sindex; while (nesting_level) { c = string[i]; if (c == 0) break; if (pass_character) /* previous char was backslash */ { pass_character = 0; ADVANCE_CHAR (string, slen, i); continue; } if (c == CTLESC || c == '\\') { pass_character++; i++; continue; } /* Process a nested OPENER. */ if (STREQN (string + i, opener, len_opener)) { si = i + len_opener; t = extract_delimited_string (string, &si, opener, alt_opener, closer, flags|EX_NOALLOC); i = si + 1; continue; } /* Process a nested ALT_OPENER */ if (len_alt_opener && STREQN (string + i, alt_opener, len_alt_opener)) { si = i + len_alt_opener; t = extract_delimited_string (string, &si, alt_opener, alt_opener, closer, flags|EX_NOALLOC); i = si + 1; continue; } /* If the current substring terminates the delimited string, decrement the nesting level. */ if (STREQN (string + i, closer, len_closer)) { i += len_closer - 1; /* move to last byte of the closer */ nesting_level--; if (nesting_level == 0) break; } /* Pass old-style command substitution through verbatim. */ if (c == '`') { si = i + 1; t = string_extract (string, &si, "`", flags|EX_NOALLOC); i = si + 1; continue; } /* Pass single-quoted and double-quoted strings through verbatim. */ if (c == '\'' || c == '"') { si = i + 1; i = (c == '\'') ? skip_single_quoted (string, slen, si) : skip_double_quoted (string, slen, si); continue; } /* move past this character, which was not special. */ ADVANCE_CHAR (string, slen, i); } if (c == 0 && nesting_level && no_longjmp_on_fatal_error == 0) { report_error ("bad substitution: no `%s' in %s", closer, string); last_command_exit_value = EXECUTION_FAILURE; jump_to_top_level (DISCARD); } si = i - *sindex - len_closer + 1; if (flags & EX_NOALLOC) result = (char *)NULL; else { result = (char *)xmalloc (1 + si); strncpy (result, string + *sindex, si); result[si] = '\0'; } *sindex = i; return (result); } /* Extract a parameter expansion expression within ${ and } from STRING. Obey the Posix.2 rules for finding the ending `}': count braces while skipping over enclosed quoted strings and command substitutions. SINDEX is the address of an int describing the current offset in STRING; it should point to just after the first `{' found. On exit, SINDEX gets the position of the matching `}'. QUOTED is non-zero if this occurs inside double quotes. */ /* XXX -- this is very similar to extract_delimited_string -- XXX */ static char * extract_dollar_brace_string (string, sindex, quoted, flags) char *string; int *sindex, quoted, flags; { register int i, c; size_t slen; int pass_character, nesting_level, si; char *result, *t; DECLARE_MBSTATE; pass_character = 0; nesting_level = 1; slen = strlen (string + *sindex) + *sindex; i = *sindex; while (c = string[i]) { if (pass_character) { pass_character = 0; ADVANCE_CHAR (string, slen, i); continue; } /* CTLESCs and backslashes quote the next character. */ if (c == CTLESC || c == '\\') { pass_character++; i++; continue; } if (string[i] == '$' && string[i+1] == LBRACE) { nesting_level++; i += 2; continue; } if (c == RBRACE) { nesting_level--; if (nesting_level == 0) break; i++; continue; } /* Pass the contents of old-style command substitutions through verbatim. */ if (c == '`') { si = i + 1; t = string_extract (string, &si, "`", flags|EX_NOALLOC); i = si + 1; continue; } /* Pass the contents of new-style command substitutions and arithmetic substitutions through verbatim. */ if (string[i] == '$' && string[i+1] == LPAREN) { si = i + 2; t = extract_delimited_string (string, &si, "$(", "(", ")", flags|EX_NOALLOC); /*)*/ i = si + 1; continue; } /* Pass the contents of single-quoted and double-quoted strings through verbatim. */ if (c == '\'' || c == '"') { si = i + 1; i = (c == '\'') ? skip_single_quoted (string, slen, si) : skip_double_quoted (string, slen, si); /* skip_XXX_quoted leaves index one past close quote */ continue; } /* move past this character, which was not special. */ ADVANCE_CHAR (string, slen, i); } if (c == 0 && nesting_level && no_longjmp_on_fatal_error == 0) { report_error ("bad substitution: no ending `}' in %s", string); last_command_exit_value = EXECUTION_FAILURE; jump_to_top_level (DISCARD); } result = (flags & EX_NOALLOC) ? (char *)NULL : substring (string, *sindex, i); *sindex = i; return (result); } /* Remove backslashes which are quoting backquotes from STRING. Modifies STRING, and returns a pointer to it. */ char * de_backslash (string) char *string; { register size_t slen; register int i, j, prev_i; DECLARE_MBSTATE; slen = strlen (string); i = j = 0; /* Loop copying string[i] to string[j], i >= j. */ while (i < slen) { if (string[i] == '\\' && (string[i + 1] == '`' || string[i + 1] == '\\' || string[i + 1] == '$')) i++; prev_i = i; ADVANCE_CHAR (string, slen, i); if (j < prev_i) do string[j++] = string[prev_i++]; while (prev_i < i); else j = i; } string[j] = '\0'; return (string); } #if 0 /*UNUSED*/ /* Replace instances of \! in a string with !. */ void unquote_bang (string) char *string; { register int i, j; register char *temp; temp = (char *)xmalloc (1 + strlen (string)); for (i = 0, j = 0; (temp[j] = string[i]); i++, j++) { if (string[i] == '\\' && string[i + 1] == '!') { temp[j] = '!'; i++; } } strcpy (string, temp); free (temp); } #endif #if defined (READLINE) /* Return 1 if the portion of STRING ending at EINDEX is quoted (there is an unclosed quoted string), or if the character at EINDEX is quoted by a backslash. NO_LONGJMP_ON_FATAL_ERROR is used to flag that the various single and double-quoted string parsing functions should not return an error if there are unclosed quotes or braces. The characters that this recognizes need to be the same as the contents of rl_completer_quote_characters. */ #define CQ_RETURN(x) do { no_longjmp_on_fatal_error = 0; return (x); } while (0) int char_is_quoted (string, eindex) char *string; int eindex; { int i, pass_next, c; size_t slen; DECLARE_MBSTATE; slen = strlen (string); no_longjmp_on_fatal_error = 1; i = pass_next = 0; while (i <= eindex) { c = string[i]; if (pass_next) { pass_next = 0; if (i >= eindex) /* XXX was if (i >= eindex - 1) */ CQ_RETURN(1); ADVANCE_CHAR (string, slen, i); continue; } else if (c == '\\') { pass_next = 1; i++; continue; } else if (c == '\'' || c == '"') { i = (c == '\'') ? skip_single_quoted (string, slen, ++i) : skip_double_quoted (string, slen, ++i); if (i > eindex) CQ_RETURN(1); /* no increment, the skip_xxx functions go one past end */ } else ADVANCE_CHAR (string, slen, i); } CQ_RETURN(0); } int unclosed_pair (string, eindex, openstr) char *string; int eindex; char *openstr; { int i, pass_next, openc, olen; size_t slen; DECLARE_MBSTATE; slen = strlen (string); olen = strlen (openstr); i = pass_next = openc = 0; while (i <= eindex) { if (pass_next) { pass_next = 0; if (i >= eindex) /* XXX was if (i >= eindex - 1) */ return 0; ADVANCE_CHAR (string, slen, i); continue; } else if (string[i] == '\\') { pass_next = 1; i++; continue; } else if (STREQN (string + i, openstr, olen)) { openc = 1 - openc; i += olen; } else if (string[i] == '\'' || string[i] == '"') { i = (string[i] == '\'') ? skip_single_quoted (string, slen, i) : skip_double_quoted (string, slen, i); if (i > eindex) return 0; } else ADVANCE_CHAR (string, slen, i); } return (openc); } /* Skip characters in STRING until we find a character in DELIMS, and return the index of that character. START is the index into string at which we begin. This is similar in spirit to strpbrk, but it returns an index into STRING and takes a starting index. This little piece of code knows quite a lot of shell syntax. It's very similar to skip_double_quoted and other functions of that ilk. */ int skip_to_delim (string, start, delims) char *string; int start; char *delims; { int i, pass_next, backq, si, c; size_t slen; char *temp; DECLARE_MBSTATE; slen = strlen (string + start) + start; no_longjmp_on_fatal_error = 1; i = start; pass_next = backq = 0; while (c = string[i]) { if (pass_next) { pass_next = 0; if (c == 0) CQ_RETURN(i); ADVANCE_CHAR (string, slen, i); continue; } else if (c == '\\') { pass_next = 1; i++; continue; } else if (backq) { if (c == '`') backq = 0; ADVANCE_CHAR (string, slen, i); continue; } else if (c == '`') { backq = 1; i++; continue; } else if (c == '\'' || c == '"') { i = (c == '\'') ? skip_single_quoted (string, slen, ++i) : skip_double_quoted (string, slen, ++i); /* no increment, the skip functions increment past the closing quote. */ } else if (c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE)) { si = i + 2; if (string[si] == '\0') CQ_RETURN(si); if (string[i+1] == LPAREN) temp = extract_delimited_string (string, &si, "$(", "(", ")", EX_NOALLOC); /* ) */ else temp = extract_dollar_brace_string (string, &si, 0, EX_NOALLOC); i = si; if (string[i] == '\0') /* don't increment i past EOS in loop */ break; i++; continue; } else if (member (c, delims)) break; else ADVANCE_CHAR (string, slen, i); } CQ_RETURN(i); } /* Split STRING (length SLEN) at DELIMS, and return a WORD_LIST with the individual words. If DELIMS is NULL, the current value of $IFS is used to split the string. SENTINEL is an index to look for. NWP, if non-NULL gets the number of words in the returned list. CWP, if non-NULL, gets the index of the word containing SENTINEL. Non-whitespace chars in DELIMS delimit separate fields. */ WORD_LIST * split_at_delims (string, slen, delims, sentinel, nwp, cwp) char *string; int slen; char *delims; int sentinel; int *nwp, *cwp; { int ts, te, i, nw, cw; char *token, *d, *d2; WORD_LIST *ret, *tl; if (string == 0 || *string == '\0') { if (nwp) *nwp = 0; if (cwp) *cwp = 0; return ((WORD_LIST *)NULL); } d = (delims == 0) ? ifs_value : delims; /* Make d2 the non-whitespace characters in delims */ d2 = 0; if (delims) { d2 = (char *)xmalloc (strlen (delims) + 1); for (i = ts = 0; delims[i]; i++) { if (whitespace(delims[i]) == 0) d2[ts++] = delims[i]; } d2[ts] = '\0'; } ret = (WORD_LIST *)NULL; for (i = 0; member (string[i], d) && spctabnl(string[i]); i++) ; if (string[i] == '\0') return (ret); ts = i; nw = 0; cw = -1; while (1) { te = skip_to_delim (string, ts, d); /* If we have a non-whitespace delimiter character, use it to make a separate field. This is just about what $IFS splitting does and is closer to the behavior of the shell parser. */ if (ts == te && d2 && member (string[ts], d2)) { te = ts + 1; while (member (string[te], d2)) te++; } token = substring (string, ts, te); ret = add_string_to_list (token, ret); free (token); nw++; if (sentinel >= ts && sentinel <= te) cw = nw; /* If the cursor is at whitespace just before word start, set the sentinel word to the current word. */ if (cwp && cw == -1 && sentinel == ts-1) cw = nw; /* If the cursor is at whitespace between two words, make a new, empty word, add it before (well, after, since the list is in reverse order) the word we just added, and set the current word to that one. */ if (cwp && cw == -1 && sentinel < ts) { tl = make_word_list (make_word (""), ret->next); ret->next = tl; cw = nw; nw++; } if (string[te] == 0) break; i = te /* + member (string[te], d) */; while (member (string[i], d) && spctabnl(string[i])) i++; if (string[i]) ts = i; else break; } /* Special case for SENTINEL at the end of STRING. If we haven't found the word containing SENTINEL yet, and the index we're looking for is at the end of STRING, add an additional null argument and set the current word pointer to that. */ if (cwp && cw == -1 && sentinel >= slen) { if (whitespace (string[sentinel - 1])) { token = ""; ret = add_string_to_list (token, ret); nw++; } cw = nw; } if (nwp) *nwp = nw; if (cwp) *cwp = cw; return (REVERSE_LIST (ret, WORD_LIST *)); } #endif /* READLINE */ #if 0 /* UNUSED */ /* Extract the name of the variable to bind to from the assignment string. */ char * assignment_name (string) char *string; { int offset; char *temp; offset = assignment (string); if (offset == 0) return (char *)NULL; temp = substring (string, 0, offset); return (temp); } #endif /* **************************************************************** */ /* */ /* Functions to convert strings to WORD_LISTs and vice versa */ /* */ /* **************************************************************** */ /* Return a single string of all the words in LIST. SEP is the separator to put between individual elements of LIST in the output string. */ char * string_list_internal (list, sep) WORD_LIST *list; char *sep; { register WORD_LIST *t; char *result, *r; int word_len, sep_len, result_size; if (list == 0) return ((char *)NULL); /* This is nearly always called with either sep[0] == 0 or sep[1] == 0. */ sep_len = STRLEN (sep); result_size = 0; for (t = list; t; t = t->next) { if (t != list) result_size += sep_len; result_size += strlen (t->word->word); } r = result = (char *)xmalloc (result_size + 1); for (t = list; t; t = t->next) { if (t != list && sep_len) { if (sep_len > 1) { FASTCOPY (sep, r, sep_len); r += sep_len; } else *r++ = sep[0]; } word_len = strlen (t->word->word); FASTCOPY (t->word->word, r, word_len); r += word_len; } *r = '\0'; return (result); } /* Return a single string of all the words present in LIST, separating each word with a space. */ char * string_list (list) WORD_LIST *list; { return (string_list_internal (list, " ")); } /* Return a single string of all the words present in LIST, obeying the quoting rules for "$*", to wit: (P1003.2, draft 11, 3.5.2) "If the expansion [of $*] appears within a double quoted string, it expands to a single field with the value of each parameter separated by the first character of the IFS variable, or by a <space> if IFS is unset." */ char * string_list_dollar_star (list) WORD_LIST *list; { char sep[2]; sep[0] = ifs_firstc; sep[1] = '\0'; return (string_list_internal (list, sep)); } /* Turn $@ into a string. If (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) is non-zero, the $@ appears within double quotes, and we should quote the list before converting it into a string. If IFS is unset, and the word is not quoted, we just need to quote CTLESC and CTLNUL characters in the words in the list, because the default value of $IFS is <space><tab><newline>, IFS characters in the words in the list should also be split. If IFS is null, and the word is not quoted, we need to quote the words in the list to preserve the positional parameters exactly. */ char * string_list_dollar_at (list, quoted) WORD_LIST *list; int quoted; { char *ifs, sep[2]; WORD_LIST *tlist; /* XXX this could just be ifs = ifs_value; */ ifs = ifs_var ? value_cell (ifs_var) : (char *)0; sep[0] = (ifs == 0 || *ifs == 0) ? ' ' : *ifs; sep[1] = '\0'; tlist = ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (ifs && *ifs == 0)) ? quote_list (list) : list_quote_escapes (list); return (string_list_internal (tlist, sep)); } /* Return the list of words present in STRING. Separate the string into words at any of the characters found in SEPARATORS. If QUOTED is non-zero then word in the list will have its quoted flag set, otherwise the quoted flag is left as make_word () deemed fit. This obeys the P1003.2 word splitting semantics. If `separators' is exactly <space><tab><newline>, then the splitting algorithm is that of the Bourne shell, which treats any sequence of characters from `separators' as a delimiter. If IFS is unset, which results in `separators' being set to "", no splitting occurs. If separators has some other value, the following rules are applied (`IFS white space' means zero or more occurrences of <space>, <tab>, or <newline>, as long as those characters are in `separators'): 1) IFS white space is ignored at the start and the end of the string. 2) Each occurrence of a character in `separators' that is not IFS white space, along with any adjacent occurrences of IFS white space delimits a field. 3) Any nonzero-length sequence of IFS white space delimits a field. */ /* BEWARE! list_string strips null arguments. Don't call it twice and expect to have "" preserved! */ /* This performs word splitting and quoted null character removal on STRING. */ #if 0 #define issep(c) ((separators)[1] ? (member ((c), separators)) : (c) == (separators)[0]) #else #define issep(c) ((separators)[1] ? isifs(c) : (c) == (separators)[0]) #endif WORD_LIST * list_string (string, separators, quoted) register char *string, *separators; int quoted; { WORD_LIST *result; WORD_DESC *t; char *current_word, *s; int sindex, sh_style_split, whitesep; if (!string || !*string) return ((WORD_LIST *)NULL); sh_style_split = separators && separators[0] == ' ' && separators[1] == '\t' && separators[2] == '\n' && separators[3] == '\0'; /* Remove sequences of whitespace at the beginning of STRING, as long as those characters appear in IFS. Do not do this if STRING is quoted or if there are no separator characters. */ if (!quoted || !separators || !*separators) { for (s = string; *s && spctabnl (*s) && issep (*s); s++); if (!*s) return ((WORD_LIST *)NULL); string = s; } /* OK, now STRING points to a word that does not begin with white space. The splitting algorithm is: extract a word, stopping at a separator skip sequences of spc, tab, or nl as long as they are separators This obeys the field splitting rules in Posix.2. */ for (result = (WORD_LIST *)NULL, sindex = 0; string[sindex]; ) { current_word = string_extract_verbatim (string, &sindex, separators); if (current_word == 0) break; /* If we have a quoted empty string, add a quoted null argument. We want to preserve the quoted null character iff this is a quoted empty string; otherwise the quoted null characters are removed below. */ if (QUOTED_NULL (current_word)) { t = make_bare_word (""); t->flags |= W_QUOTED; free (t->word); t->word = make_quoted_char ('\0'); result = make_word_list (t, result); } else if (current_word[0] != '\0') { /* If we have something, then add it regardless. However, perform quoted null character removal on the current word. */ remove_quoted_nulls (current_word); result = add_string_to_list (current_word, result); if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) result->word->flags |= W_QUOTED; } /* If we're not doing sequences of separators in the traditional Bourne shell style, then add a quoted null argument. */ else if (!sh_style_split && !spctabnl (string[sindex])) { t = make_bare_word (""); t->flags |= W_QUOTED; free (t->word); t->word = make_quoted_char ('\0'); result = make_word_list (t, result); } free (current_word); /* Note whether or not the separator is IFS whitespace, used later. */ whitesep = string[sindex] && spctabnl (string[sindex]); /* Move past the current separator character. */ if (string[sindex]) sindex++; /* Now skip sequences of space, tab, or newline characters if they are in the list of separators. */ while (string[sindex] && spctabnl (string[sindex]) && issep (string[sindex])) sindex++; /* If the first separator was IFS whitespace and the current character is a non-whitespace IFS character, it should be part of the current field delimiter, not a separate delimiter that would result in an empty field. Look at POSIX.2, 3.6.5, (3)(b). */ if (string[sindex] && whitesep && issep (string[sindex]) && !spctabnl (string[sindex])) sindex++; } return (REVERSE_LIST (result, WORD_LIST *)); } /* Parse a single word from STRING, using SEPARATORS to separate fields. ENDPTR is set to the first character after the word. This is used by the `read' builtin. This is never called with SEPARATORS != $IFS; it should be simplified. XXX - this function is very similar to list_string; they should be combined - XXX */ char * get_word_from_string (stringp, separators, endptr) char **stringp, *separators, **endptr; { register char *s; char *current_word; int sindex, sh_style_split, whitesep; if (!stringp || !*stringp || !**stringp) return ((char *)NULL); s = *stringp; sh_style_split = separators && separators[0] == ' ' && separators[1] == '\t' && separators[2] == '\n' && separators[3] == '\0'; /* Remove sequences of whitespace at the beginning of STRING, as long as those characters appear in IFS. */ if (sh_style_split || !separators || !*separators) { for (; *s && spctabnl (*s) && isifs (*s); s++); /* If the string is nothing but whitespace, update it and return. */ if (!*s) { *stringp = s; if (endptr) *endptr = s; return ((char *)NULL); } } /* OK, S points to a word that does not begin with white space. Now extract a word, stopping at a separator, save a pointer to the first character after the word, then skip sequences of spc, tab, or nl as long as they are separators. This obeys the field splitting rules in Posix.2. */ sindex = 0; current_word = string_extract_verbatim (s, &sindex, separators); /* Set ENDPTR to the first character after the end of the word. */ if (endptr) *endptr = s + sindex; /* Note whether or not the separator is IFS whitespace, used later. */ whitesep = s[sindex] && spctabnl (s[sindex]); /* Move past the current separator character. */ if (s[sindex]) sindex++; /* Now skip sequences of space, tab, or newline characters if they are in the list of separators. */ while (s[sindex] && spctabnl (s[sindex]) && isifs (s[sindex])) sindex++; /* If the first separator was IFS whitespace and the current character is a non-whitespace IFS character, it should be part of the current field delimiter, not a separate delimiter that would result in an empty field. Look at POSIX.2, 3.6.5, (3)(b). */ if (s[sindex] && whitesep && isifs (s[sindex]) && !spctabnl (s[sindex])) sindex++; /* Update STRING to point to the next field. */ *stringp = s + sindex; return (current_word); } /* Remove IFS white space at the end of STRING. Start at the end of the string and walk backwards until the beginning of the string or we find a character that's not IFS white space and not CTLESC. Only let CTLESC escape a white space character if SAW_ESCAPE is non-zero. */ char * strip_trailing_ifs_whitespace (string, separators, saw_escape) char *string, *separators; int saw_escape; { char *s; s = string + STRLEN (string) - 1; while (s > string && ((spctabnl (*s) && isifs (*s)) || (saw_escape && *s == CTLESC && spctabnl (s[1])))) s--; *++s = '\0'; return string; } #if 0 /* UNUSED */ /* Split STRING into words at whitespace. Obeys shell-style quoting with backslashes, single and double quotes. */ WORD_LIST * list_string_with_quotes (string) char *string; { WORD_LIST *list; char *token, *s; size_t s_len; int c, i, tokstart, len; for (s = string; s && *s && spctabnl (*s); s++) ; if (s == 0 || *s == 0) return ((WORD_LIST *)NULL); s_len = strlen (s); tokstart = i = 0; list = (WORD_LIST *)NULL; while (1) { c = s[i]; if (c == '\\') { i++; if (s[i]) i++; } else if (c == '\'') i = skip_single_quoted (s, s_len, ++i); else if (c == '"') i = skip_double_quoted (s, s_len, ++i); else if (c == 0 || spctabnl (c)) { /* We have found the end of a token. Make a word out of it and add it to the word list. */ token = substring (s, tokstart, i); list = add_string_to_list (token, list); free (token); while (spctabnl (s[i])) i++; if (s[i]) tokstart = i; else break; } else i++; /* normal character */ } return (REVERSE_LIST (list, WORD_LIST *)); } #endif /********************************************************/ /* */ /* Functions to perform assignment statements */ /* */ /********************************************************/ /* Given STRING, an assignment string, get the value of the right side of the `=', and bind it to the left side. If EXPAND is true, then perform parameter expansion, command substitution, and arithmetic expansion on the right-hand side. Perform tilde expansion in any case. Do not perform word splitting on the result of expansion. */ static int do_assignment_internal (string, expand) const char *string; int expand; { int offset; char *name, *value; SHELL_VAR *entry; #if defined (ARRAY_VARS) char *t; int ni, assign_list = 0; #endif offset = assignment (string); name = savestring (string); value = (char *)NULL; if (name[offset] == '=') { char *temp; name[offset] = 0; temp = name + offset + 1; #if defined (ARRAY_VARS) if (expand && temp[0] == LPAREN && xstrchr (temp, RPAREN)) { assign_list = ni = 1; value = extract_delimited_string (temp, &ni, "(", (char *)NULL, ")", 0); } else #endif /* Perform tilde expansion. */ if (expand && temp[0]) { temp = (xstrchr (temp, '~') && unquoted_member ('~', temp)) ? bash_tilde_expand (temp, 1) : savestring (temp); value = expand_string_if_necessary (temp, 0, expand_string_unsplit); free (temp); } else value = savestring (temp); } if (value == 0) { value = (char *)xmalloc (1); value[0] = '\0'; } if (echo_command_at_execute) { #if defined (ARRAY_VARS) if (assign_list) fprintf (stderr, "%s%s=(%s)\n", indirection_level_string (), name, value); else #endif fprintf (stderr, "%s%s=%s\n", indirection_level_string (), name, value); } #define ASSIGN_RETURN(r) do { FREE (value); free (name); return (r); } while (0) #if defined (ARRAY_VARS) if (t = xstrchr (name, '[')) /*]*/ { if (assign_list) { report_error ("%s: cannot assign list to array member", name); ASSIGN_RETURN (0); } entry = assign_array_element (name, value); if (entry == 0) ASSIGN_RETURN (0); } else if (assign_list) entry = assign_array_from_string (name, value); else #endif /* ARRAY_VARS */ entry = bind_variable (name, value); stupidly_hack_special_variables (name); if (entry) VUNSETATTR (entry, att_invisible); /* Return 1 if the assignment seems to have been performed correctly. */ ASSIGN_RETURN (entry ? ((readonly_p (entry) == 0) && noassign_p (entry) == 0) : 0); } /* Perform the assignment statement in STRING, and expand the right side by doing command and parameter expansion. */ int do_assignment (string) const char *string; { return do_assignment_internal (string, 1); } /* Given STRING, an assignment string, get the value of the right side of the `=', and bind it to the left side. Do not do command and parameter substitution on the right hand side. */ int do_assignment_no_expand (string) const char *string; { return do_assignment_internal (string, 0); } /*************************************************** * * * Functions to manage the positional parameters * * * ***************************************************/ /* Return the word list that corresponds to `$*'. */ WORD_LIST * list_rest_of_args () { register WORD_LIST *list, *args; int i; /* Break out of the loop as soon as one of the dollar variables is null. */ for (i = 1, list = (WORD_LIST *)NULL; i < 10 && dollar_vars[i]; i++) list = make_word_list (make_bare_word (dollar_vars[i]), list); for (args = rest_of_args; args; args = args->next) list = make_word_list (make_bare_word (args->word->word), list); return (REVERSE_LIST (list, WORD_LIST *)); } int number_of_args () { register WORD_LIST *list; int n; for (n = 0; n < 9 && dollar_vars[n+1]; n++) ; for (list = rest_of_args; list; list = list->next) n++; return n; } /* Return the value of a positional parameter. This handles values > 10. */ char * get_dollar_var_value (ind) intmax_t ind; { char *temp; WORD_LIST *p; if (ind < 10) temp = dollar_vars[ind] ? savestring (dollar_vars[ind]) : (char *)NULL; else /* We want something like ${11} */ { ind -= 10; for (p = rest_of_args; p && ind--; p = p->next) ; temp = p ? savestring (p->word->word) : (char *)NULL; } return (temp); } /* Make a single large string out of the dollar digit variables, and the rest_of_args. If DOLLAR_STAR is 1, then obey the special case of "$*" with respect to IFS. */ char * string_rest_of_args (dollar_star) int dollar_star; { register WORD_LIST *list; char *string; list = list_rest_of_args (); string = dollar_star ? string_list_dollar_star (list) : string_list (list); dispose_words (list); return (string); } /* Return a string containing the positional parameters from START to END, inclusive. If STRING[0] == '*', we obey the rules for $*, which only makes a difference if QUOTED is non-zero. If QUOTED includes Q_HERE_DOCUMENT or Q_DOUBLE_QUOTES, this returns a quoted list, otherwise no quoting chars are added. */ static char * pos_params (string, start, end, quoted) char *string; int start, end, quoted; { WORD_LIST *save, *params, *h, *t; char *ret; int i; /* see if we can short-circuit. if start == end, we want 0 parameters. */ if (start == end) return ((char *)NULL); save = params = list_rest_of_args (); if (save == 0) return ((char *)NULL); for (i = 1; params && i < start; i++) params = params->next; if (params == 0) return ((char *)NULL); for (h = t = params; params && i < end; i++) { t = params; params = params->next; } t->next = (WORD_LIST *)NULL; if (string[0] == '*') ret = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? string_list_dollar_star (quote_list (h)) : string_list (h); else ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (h) : h); if (t != params) t->next = params; dispose_words (save); return (ret); } /******************************************************************/ /* */ /* Functions to expand strings to strings or WORD_LISTs */ /* */ /******************************************************************/ #if defined (PROCESS_SUBSTITUTION) #define EXP_CHAR(s) (s == '$' || s == '`' || s == '<' || s == '>' || s == CTLESC) #else #define EXP_CHAR(s) (s == '$' || s == '`' || s == CTLESC) #endif /* If there are any characters in STRING that require full expansion, then call FUNC to expand STRING; otherwise just perform quote removal if necessary. This returns a new string. */ static char * expand_string_if_necessary (string, quoted, func) char *string; int quoted; EXPFUNC *func; { WORD_LIST *list; size_t slen; int i, saw_quote; char *ret; DECLARE_MBSTATE; slen = strlen (string); i = saw_quote = 0; while (string[i]) { if (EXP_CHAR (string[i])) break; else if (string[i] == '\'' || string[i] == '\\' || string[i] == '"') saw_quote = 1; ADVANCE_CHAR (string, slen, i); } if (string[i]) { list = (*func) (string, quoted); if (list) { ret = string_list (list); dispose_words (list); } else ret = (char *)NULL; } else if (saw_quote && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0)) ret = string_quote_removal (string, quoted); else ret = savestring (string); return ret; } static inline char * expand_string_to_string_internal (string, quoted, func) char *string; int quoted; EXPFUNC *func; { WORD_LIST *list; char *ret; if (string == 0 || *string == '\0') return ((char *)NULL); list = (*func) (string, quoted); if (list) { ret = string_list (list); dispose_words (list); } else ret = (char *)NULL; return (ret); } char * expand_string_to_string (string, quoted) char *string; int quoted; { return (expand_string_to_string_internal (string, quoted, expand_string)); } char * expand_string_unsplit_to_string (string, quoted) char *string; int quoted; { return (expand_string_to_string_internal (string, quoted, expand_string_unsplit)); } #if defined (COND_COMMAND) /* Just remove backslashes in STRING. Returns a new string. */ char * remove_backslashes (string) char *string; { char *r, *ret, *s; r = ret = (char *)xmalloc (strlen (string) + 1); for (s = string; s && *s; ) { if (*s == '\\') s++; if (*s == 0) break; *r++ = *s++; } *r = '\0'; return ret; } /* This needs better error handling. */ /* Expand W for use as an argument to a unary or binary operator in a [[...]] expression. If SPECIAL is nonzero, this is the rhs argument to the != or == operator, and should be treated as a pattern. In this case, we quote the string specially for the globbing code. The caller is responsible for removing the backslashes if the unquoted words is needed later. */ char * cond_expand_word (w, special) WORD_DESC *w; int special; { char *r, *p; WORD_LIST *l; if (w->word == 0 || w->word[0] == '\0') return ((char *)NULL); if (xstrchr (w->word, '~') && unquoted_member ('~', w->word)) { p = bash_tilde_expand (w->word, 0); free (w->word); w->word = p; } l = call_expand_word_internal (w, 0, 0, (int *)0, (int *)0); if (l) { if (special == 0) { dequote_list (l); r = string_list (l); } else { p = string_list (l); r = quote_string_for_globbing (p, QGLOB_CVTNULL); free (p); } dispose_words (l); } else r = (char *)NULL; return r; } #endif /* Call expand_word_internal to expand W and handle error returns. A convenience function for functions that don't want to handle any errors or free any memory before aborting. */ static WORD_LIST * call_expand_word_internal (w, q, i, c, e) WORD_DESC *w; int q, i, *c, *e; { WORD_LIST *result; result = expand_word_internal (w, q, i, c, e); if (result == &expand_word_error || result == &expand_word_fatal) { expand_no_split_dollar_star = 0; /* XXX */ /* By convention, each time this error is returned, w->word has already been freed (it sometimes may not be in the fatal case, but that doesn't result in a memory leak because we're going to exit in most cases). */ w->word = (char *)NULL; last_command_exit_value = EXECUTION_FAILURE; jump_to_top_level ((result == &expand_word_error) ? DISCARD : FORCE_EOF); /* NOTREACHED */ } else return (result); } /* Perform parameter expansion, command substitution, and arithmetic expansion on STRING, as if it were a word. Leave the result quoted. */ static WORD_LIST * expand_string_internal (string, quoted) char *string; int quoted; { WORD_DESC td; WORD_LIST *tresult; if (string == 0 || *string == 0) return ((WORD_LIST *)NULL); td.flags = 0; td.word = savestring (string); tresult = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL); FREE (td.word); return (tresult); } /* Expand STRING by performing parameter expansion, command substitution, and arithmetic expansion. Dequote the resulting WORD_LIST before returning it, but do not perform word splitting. The call to remove_quoted_nulls () is in here because word splitting normally takes care of quote removal. */ WORD_LIST * expand_string_unsplit (string, quoted) char *string; int quoted; { WORD_LIST *value; if (string == 0 || *string == '\0') return ((WORD_LIST *)NULL); expand_no_split_dollar_star = 1; value = expand_string_internal (string, quoted); expand_no_split_dollar_star = 0; if (value) { if (value->word) remove_quoted_nulls (value->word->word); dequote_list (value); } return (value); } /* Expand one of the PS? prompt strings. This is a sort of combination of expand_string_unsplit and expand_string_internal, but returns the passed string when an error occurs. Might want to trap other calls to jump_to_top_level here so we don't endlessly loop. */ WORD_LIST * expand_prompt_string (string, quoted) char *string; int quoted; { WORD_LIST *value; WORD_DESC td; if (string == 0 || *string == 0) return ((WORD_LIST *)NULL); td.flags = 0; td.word = savestring (string); no_longjmp_on_fatal_error = 1; value = expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL); no_longjmp_on_fatal_error = 0; if (value == &expand_word_error || value == &expand_word_fatal) { value = make_word_list (make_bare_word (string), (WORD_LIST *)NULL); return value; } FREE (td.word); if (value) { if (value->word) remove_quoted_nulls (value->word->word); dequote_list (value); } return (value); } /* Expand STRING just as if you were expanding a word, but do not dequote the resultant WORD_LIST. This is called only from within this file, and is used to correctly preserve quoted characters when expanding things like ${1+"$@"}. This does parameter expansion, command substitution, arithmetic expansion, and word splitting. */ static WORD_LIST * expand_string_leave_quoted (string, quoted) char *string; int quoted; { WORD_LIST *tlist; WORD_LIST *tresult; if (string == 0 || *string == '\0') return ((WORD_LIST *)NULL); tlist = expand_string_internal (string, quoted); if (tlist) { tresult = word_list_split (tlist); dispose_words (tlist); return (tresult); } return ((WORD_LIST *)NULL); } /* This does not perform word splitting or dequote the WORD_LIST it returns. */ static WORD_LIST * expand_string_for_rhs (string, quoted, dollar_at_p, has_dollar_at) char *string; int quoted, *dollar_at_p, *has_dollar_at; { WORD_DESC td; WORD_LIST *tresult; if (string == 0 || *string == '\0') return (WORD_LIST *)NULL; td.flags = 0; td.word = string; tresult = call_expand_word_internal (&td, quoted, 1, dollar_at_p, has_dollar_at); return (tresult); } /* Expand STRING just as if you were expanding a word. This also returns a list of words. Note that filename globbing is *NOT* done for word or string expansion, just when the shell is expanding a command. This does parameter expansion, command substitution, arithmetic expansion, and word splitting. Dequote the resultant WORD_LIST before returning. */ WORD_LIST * expand_string (string, quoted) char *string; int quoted; { WORD_LIST *result; if (string == 0 || *string == '\0') return ((WORD_LIST *)NULL); result = expand_string_leave_quoted (string, quoted); return (result ? dequote_list (result) : result); } /*************************************************** * * * Functions to handle quoting chars * * * ***************************************************/ /* Conventions: A string with s[0] == CTLNUL && s[1] == 0 is a quoted null string. The parser passes CTLNUL as CTLESC CTLNUL. */ /* Quote escape characters in string s, but no other characters. This is used to protect CTLESC and CTLNUL in variable values from the rest of the word expansion process after the variable is expanded. */ char * quote_escapes (string) char *string; { register char *s, *t; size_t slen; char *result, *send; DECLARE_MBSTATE; slen = strlen (string); send = string + slen; t = result = (char *)xmalloc ((slen * 2) + 1); s = string; while (*s) { if (*s == CTLESC || *s == CTLNUL) *t++ = CTLESC; COPY_CHAR_P (t, s, send); } *t = '\0'; return (result); } static WORD_LIST * list_quote_escapes (list) WORD_LIST *list; { register WORD_LIST *w; char *t; for (w = list; w; w = w->next) { t = w->word->word; w->word->word = quote_escapes (t); free (t); } return list; } /* Inverse of quote_escapes; remove CTLESC protecting CTLESC or CTLNUL. The parser passes us CTLESC as CTLESC CTLESC and CTLNUL as CTLESC CTLNUL. This is necessary to make unquoted CTLESC and CTLNUL characters in the data stream pass through properly. We need to remove doubled CTLESC characters inside quoted strings before quoting the entire string, so we do not double the number of CTLESC characters. Also used by parts of the pattern substitution code. */ static char * dequote_escapes (string) char *string; { register char *s, *t; size_t slen; char *result, *send; DECLARE_MBSTATE; if (string == 0) return string; slen = strlen (string); send = string + slen; t = result = (char *)xmalloc (slen + 1); s = string; if (strchr (string, CTLESC) == 0) return (strcpy (result, s)); while (*s) { if (*s == CTLESC && (s[1] == CTLESC || s[1] == CTLNUL)) { s++; if (*s == '\0') break; } COPY_CHAR_P (t, s, send); } *t = '\0'; return result; } /* Return a new string with the quoted representation of character C. */ static char * make_quoted_char (c) int c; { char *temp; temp = (char *)xmalloc (3); if (c == 0) { temp[0] = CTLNUL; temp[1] = '\0'; } else { temp[0] = CTLESC; temp[1] = c; temp[2] = '\0'; } return (temp); } /* Quote STRING. Return a new string. */ char * quote_string (string) char *string; { register char *t; size_t slen; char *result, *send; if (*string == 0) { result = (char *)xmalloc (2); result[0] = CTLNUL; result[1] = '\0'; } else { DECLARE_MBSTATE; slen = strlen (string); send = string + slen; result = (char *)xmalloc ((slen * 2) + 1); for (t = result; string < send; ) { *t++ = CTLESC; COPY_CHAR_P (t, string, send); } *t = '\0'; } return (result); } /* De-quoted quoted characters in STRING. */ char * dequote_string (string) char *string; { register char *s, *t; size_t slen; char *result, *send; DECLARE_MBSTATE; slen = strlen (string); t = result = (char *)xmalloc (slen + 1); if (QUOTED_NULL (string)) { result[0] = '\0'; return (result); } /* If no character in the string can be quoted, don't bother examining each character. Just return a copy of the string passed to us. */ if (strchr (string, CTLESC) == NULL) return (strcpy (result, string)); send = string + slen; s = string; while (*s) { if (*s == CTLESC) { s++; if (*s == '\0') break; } COPY_CHAR_P (t, s, send); } *t = '\0'; return (result); } /* Quote the entire WORD_LIST list. */ static WORD_LIST * quote_list (list) WORD_LIST *list; { register WORD_LIST *w; char *t; for (w = list; w; w = w->next) { t = w->word->word; w->word->word = quote_string (t); free (t); w->word->flags |= W_QUOTED; } return list; } static WORD_LIST * dequote_list (list) WORD_LIST *list; { register char *s; register WORD_LIST *tlist; for (tlist = list; tlist; tlist = tlist->next) { s = dequote_string (tlist->word->word); free (tlist->word->word); tlist->word->word = s; } return list; } /* Remove CTLESC protecting a CTLESC or CTLNUL in place. Return the passed string. */ static char * remove_quoted_escapes (string) char *string; { char *t; if (string) { t = dequote_escapes (string); strcpy (string, t); free (t); } return (string); } /* Perform quoted null character removal on STRING. We don't allow any quoted null characters in the middle or at the ends of strings because of how expand_word_internal works. remove_quoted_nulls () turns STRING into an empty string iff it only consists of a quoted null, and removes all unquoted CTLNUL characters. */ static char * remove_quoted_nulls (string) char *string; { register size_t slen; register int i, j, prev_i; DECLARE_MBSTATE; if (strchr (string, CTLNUL) == 0) /* XXX */ return string; /* XXX */ slen = strlen (string); i = j = 0; while (i < slen) { if (string[i] == CTLESC) { i++; j++; if (i == slen) break; } else if (string[i] == CTLNUL) i++; prev_i = i; ADVANCE_CHAR (string, slen, i); if (j < prev_i) { do string[j++] = string[prev_i++]; while (prev_i < i); } else j = i; } string[j] = '\0'; return (string); } /* Perform quoted null character removal on each element of LIST. This modifies LIST. */ void word_list_remove_quoted_nulls (list) WORD_LIST *list; { register WORD_LIST *t; for (t = list; t; t = t->next) remove_quoted_nulls (t->word->word); } /* **************************************************************** */ /* */ /* Functions for Matching and Removing Patterns */ /* */ /* **************************************************************** */ /* Remove the portion of PARAM matched by PATTERN according to OP, where OP can have one of 4 values: RP_LONG_LEFT remove longest matching portion at start of PARAM RP_SHORT_LEFT remove shortest matching portion at start of PARAM RP_LONG_RIGHT remove longest matching portion at end of PARAM RP_SHORT_RIGHT remove shortest matching portion at end of PARAM */ #define RP_LONG_LEFT 1 #define RP_SHORT_LEFT 2 #define RP_LONG_RIGHT 3 #define RP_SHORT_RIGHT 4 static char * remove_pattern (param, pattern, op) char *param, *pattern; int op; { register int len; register char *end; register char *p, *ret, c; if (param == NULL || *param == '\0') return (param); if (pattern == NULL || *pattern == '\0') /* minor optimization */ return (savestring (param)); len = STRLEN (param); end = param + len; switch (op) { case RP_LONG_LEFT: /* remove longest match at start */ for (p = end; p >= param; p--) { c = *p; *p = '\0'; if (strmatch (pattern, param, FNMATCH_EXTFLAG) != FNM_NOMATCH) { *p = c; return (savestring (p)); } *p = c; } break; case RP_SHORT_LEFT: /* remove shortest match at start */ for (p = param; p <= end; p++) { c = *p; *p = '\0'; if (strmatch (pattern, param, FNMATCH_EXTFLAG) != FNM_NOMATCH) { *p = c; return (savestring (p)); } *p = c; } break; case RP_LONG_RIGHT: /* remove longest match at end */ for (p = param; p <= end; p++) { if (strmatch (pattern, p, FNMATCH_EXTFLAG) != FNM_NOMATCH) { c = *p; *p = '\0'; ret = savestring (param); *p = c; return (ret); } } break; case RP_SHORT_RIGHT: /* remove shortest match at end */ for (p = end; p >= param; p--) { if (strmatch (pattern, p, FNMATCH_EXTFLAG) != FNM_NOMATCH) { c = *p; *p = '\0'; ret = savestring (param); *p = c; return (ret); } } break; } return (savestring (param)); /* no match, return original string */ } /* Return 1 of the first character of STRING could match the first character of pattern PAT. Used to avoid n2 calls to strmatch(). */ static int match_pattern_char (pat, string) char *pat, *string; { char c; if (*string == 0) return (0); switch (c = *pat++) { default: return (*string == c); case '\\': return (*string == *pat); case '?': return (*pat == LPAREN ? 1 : (*string != '\0')); case '*': return (1); case '+': case '!': case '@': return (*pat == LPAREN ? 1 : (*string == c)); case '[': return (*string != '\0'); } } /* Match PAT anywhere in STRING and return the match boundaries. This returns 1 in case of a successful match, 0 otherwise. SP and EP are pointers into the string where the match begins and ends, respectively. MTYPE controls what kind of match is attempted. MATCH_BEG and MATCH_END anchor the match at the beginning and end of the string, respectively. The longest match is returned. */ static int match_pattern (string, pat, mtype, sp, ep) char *string, *pat; int mtype; char **sp, **ep; { int c; register char *p, *p1; char *end; if (string == 0 || *string == 0 || pat == 0 || *pat == 0) return (0); end = string + STRLEN (string); switch (mtype) { case MATCH_ANY: for (p = string; p <= end; p++) { if (match_pattern_char (pat, p)) { for (p1 = end; p1 >= p; p1--) { c = *p1; *p1 = '\0'; if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0) { *p1 = c; *sp = p; *ep = p1; return 1; } *p1 = c; } } } return (0); case MATCH_BEG: if (match_pattern_char (pat, string) == 0) return (0); for (p = end; p >= string; p--) { c = *p; *p = '\0'; if (strmatch (pat, string, FNMATCH_EXTFLAG) == 0) { *p = c; *sp = string; *ep = p; return 1; } *p = c; } return (0); case MATCH_END: for (p = string; p <= end; p++) if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0) { *sp = p; *ep = end; return 1; } return (0); } return (0); } static int getpatspec (c, value) int c; char *value; { if (c == '#') return ((*value == '#') ? RP_LONG_LEFT : RP_SHORT_LEFT); else /* c == '%' */ return ((*value == '%') ? RP_LONG_RIGHT : RP_SHORT_RIGHT); } /* Posix.2 says that the WORD should be run through tilde expansion, parameter expansion, command substitution and arithmetic expansion. This leaves the result quoted, so quote_string_for_globbing () has to be called to fix it up for strmatch (). If QUOTED is non-zero, it means that the entire expression was enclosed in double quotes. This means that quoting characters in the pattern do not make any special pattern characters quoted. For example, the `*' in the following retains its special meaning: "${foo#'*'}". */ static char * getpattern (value, quoted, expandpat) char *value; int quoted, expandpat; { char *pat, *tword; WORD_LIST *l; int i; tword = xstrchr (value, '~') ? bash_tilde_expand (value, 0) : savestring (value); /* There is a problem here: how to handle single or double quotes in the pattern string when the whole expression is between double quotes? POSIX.2 says that enclosing double quotes do not cause the pattern to be quoted, but does that leave us a problem with @ and array[@] and their expansions inside a pattern? */ #if 0 if (expandpat && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *tword) { i = 0; pat = string_extract_double_quoted (tword, &i, 1); free (tword); tword = pat; } #endif /* expand_string_for_rhs () leaves WORD quoted and does not perform word splitting. */ l = *tword ? expand_string_for_rhs (tword, (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? Q_PATQUOTE : quoted, (int *)NULL, (int *)NULL) : (WORD_LIST *)0; free (tword); pat = string_list (l); dispose_words (l); if (pat) { tword = quote_string_for_globbing (pat, QGLOB_CVTNULL); free (pat); pat = tword; } return (pat); } #if 0 /* Handle removing a pattern from a string as a result of ${name%[%]value} or ${name#[#]value}. */ static char * variable_remove_pattern (value, pattern, patspec, quoted) char *value, *pattern; int patspec, quoted; { char *tword; tword = remove_pattern (value, pattern, patspec); return (tword); } #endif static char * list_remove_pattern (list, pattern, patspec, itype, quoted) WORD_LIST *list; char *pattern; int patspec, itype, quoted; { WORD_LIST *new, *l; WORD_DESC *w; char *tword; for (new = (WORD_LIST *)NULL, l = list; l; l = l->next) { tword = remove_pattern (l->word->word, pattern, patspec); w = make_bare_word (tword); free (tword); new = make_word_list (w, new); } l = REVERSE_LIST (new, WORD_LIST *); if (itype == '*') tword = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? string_list_dollar_star (l) : string_list (l); else tword = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (l) : l); dispose_words (l); return (tword); } static char * parameter_list_remove_pattern (itype, pattern, patspec, quoted) int itype; char *pattern; int patspec, quoted; { char *ret; WORD_LIST *list; list = list_rest_of_args (); if (list == 0) return ((char *)NULL); ret = list_remove_pattern (list, pattern, patspec, itype, quoted); dispose_words (list); return (ret); } #if defined (ARRAY_VARS) static char * array_remove_pattern (a, pattern, patspec, varname, quoted) ARRAY *a; char *pattern; int patspec; char *varname; /* so we can figure out how it's indexed */ int quoted; { int itype; char *ret; WORD_LIST *list; SHELL_VAR *v; /* compute itype from varname here */ v = array_variable_part (varname, &ret, 0); itype = ret[0]; list = array_to_word_list (a); if (list == 0) return ((char *)NULL); ret = list_remove_pattern (list, pattern, patspec, itype, quoted); dispose_words (list); return ret; } #endif /* ARRAY_VARS */ static char * parameter_brace_remove_pattern (varname, value, patstr, rtype, quoted) char *varname, *value, *patstr; int rtype, quoted; { int vtype, patspec; char *temp1, *val, *pattern; SHELL_VAR *v; if (value == 0) return ((char *)NULL); this_command_name = varname; vtype = get_var_and_type (varname, value, &v, &val); if (vtype == -1) return ((char *)NULL); patspec = getpatspec (rtype, patstr); if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT) patstr++; pattern = getpattern (patstr, quoted, 1); temp1 = (char *)NULL; /* shut up gcc */ switch (vtype) { case VT_VARIABLE: case VT_ARRAYMEMBER: temp1 = remove_pattern (val, pattern, patspec); if (vtype == VT_VARIABLE) FREE (val); if (temp1) { val = quote_escapes (temp1); free (temp1); temp1 = val; } break; #if defined (ARRAY_VARS) case VT_ARRAYVAR: temp1 = array_remove_pattern (array_cell (v), pattern, patspec, varname, quoted); if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0)) { val = quote_escapes (temp1); free (temp1); temp1 = val; } break; #endif case VT_POSPARMS: temp1 = parameter_list_remove_pattern (varname[0], pattern, patspec, quoted); if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0)) { val = quote_escapes (temp1); free (temp1); temp1 = val; } break; } FREE (pattern); return temp1; } /******************************************* * * * Functions to expand WORD_DESCs * * * *******************************************/ /* Expand WORD, performing word splitting on the result. This does parameter expansion, command substitution, arithmetic expansion, word splitting, and quote removal. */ WORD_LIST * expand_word (word, quoted) WORD_DESC *word; int quoted; { WORD_LIST *result, *tresult; tresult = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL); result = word_list_split (tresult); dispose_words (tresult); return (result ? dequote_list (result) : result); } /* Expand WORD, but do not perform word splitting on the result. This does parameter expansion, command substitution, arithmetic expansion, and quote removal. */ WORD_LIST * expand_word_unsplit (word, quoted) WORD_DESC *word; int quoted; { WORD_LIST *result; expand_no_split_dollar_star = 1; result = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL); expand_no_split_dollar_star = 0; return (result ? dequote_list (result) : result); } /* Perform shell expansions on WORD, but do not perform word splitting or quote removal on the result. */ WORD_LIST * expand_word_leave_quoted (word, quoted) WORD_DESC *word; int quoted; { return (call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL)); } #if defined (PROCESS_SUBSTITUTION) /*****************************************************************/ /* */ /* Hacking Process Substitution */ /* */ /*****************************************************************/ #if !defined (HAVE_DEV_FD) /* Named pipes must be removed explicitly with `unlink'. This keeps a list of FIFOs the shell has open. unlink_fifo_list will walk the list and unlink all of them. add_fifo_list adds the name of an open FIFO to the list. NFIFO is a count of the number of FIFOs in the list. */ #define FIFO_INCR 20 struct temp_fifo { char *file; pid_t proc; }; static struct temp_fifo *fifo_list = (struct temp_fifo *)NULL; static int nfifo; static int fifo_list_size; static void add_fifo_list (pathname) char *pathname; { if (nfifo >= fifo_list_size - 1) { fifo_list_size += FIFO_INCR; fifo_list = (struct temp_fifo *)xrealloc (fifo_list, fifo_list_size * sizeof (struct temp_fifo)); } fifo_list[nfifo].file = savestring (pathname); nfifo++; } void unlink_fifo_list () { int saved, i, j; if (nfifo == 0) return; for (i = saved = 0; i < nfifo; i++) { if ((fifo_list[i].proc == -1) || (kill(fifo_list[i].proc, 0) == -1)) { unlink (fifo_list[i].file); free (fifo_list[i].file); fifo_list[i].file = (char *)NULL; fifo_list[i].proc = -1; } else saved++; } /* If we didn't remove some of the FIFOs, compact the list. */ if (saved) { for (i = j = 0; i < nfifo; i++) if (fifo_list[i].file) { fifo_list[j].file = fifo_list[i].file; fifo_list[j].proc = fifo_list[i].proc; j++; } nfifo = j; } else nfifo = 0; } static char * make_named_pipe () { char *tname; tname = sh_mktmpname ("sh-np", MT_USERANDOM); if (mkfifo (tname, 0600) < 0) { free (tname); return ((char *)NULL); } add_fifo_list (tname); return (tname); } #else /* HAVE_DEV_FD */ /* DEV_FD_LIST is a bitmap of file descriptors attached to pipes the shell has open to children. NFDS is a count of the number of bits currently set in DEV_FD_LIST. TOTFDS is a count of the highest possible number of open files. */ static char *dev_fd_list = (char *)NULL; static int nfds; static int totfds; /* The highest possible number of open files. */ static void add_fifo_list (fd) int fd; { if (!dev_fd_list || fd >= totfds) { int ofds; ofds = totfds; totfds = getdtablesize (); if (totfds < 0 || totfds > 256) totfds = 256; if (fd > totfds) totfds = fd + 2; dev_fd_list = (char *)xrealloc (dev_fd_list, totfds); memset (dev_fd_list + ofds, '\0', totfds - ofds); } dev_fd_list[fd] = 1; nfds++; } void unlink_fifo_list () { register int i; if (nfds == 0) return; for (i = 0; nfds && i < totfds; i++) if (dev_fd_list[i]) { close (i); dev_fd_list[i] = 0; nfds--; } nfds = 0; } #if defined (NOTDEF) print_dev_fd_list () { register int i; fprintf (stderr, "pid %ld: dev_fd_list:", (long)getpid ()); fflush (stderr); for (i = 0; i < totfds; i++) { if (dev_fd_list[i]) fprintf (stderr, " %d", i); } fprintf (stderr, "\n"); } #endif /* NOTDEF */ static char * make_dev_fd_filename (fd) int fd; { char *ret, intbuf[INT_STRLEN_BOUND (int) + 1], *p; ret = (char *)xmalloc (sizeof (DEV_FD_PREFIX) + 4); strcpy (ret, DEV_FD_PREFIX); p = inttostr (fd, intbuf, sizeof (intbuf)); strcpy (ret + sizeof (DEV_FD_PREFIX) - 1, p); add_fifo_list (fd); return (ret); } #endif /* HAVE_DEV_FD */ /* Return a filename that will open a connection to the process defined by executing STRING. HAVE_DEV_FD, if defined, means open a pipe and return a filename in /dev/fd corresponding to a descriptor that is one of the ends of the pipe. If not defined, we use named pipes on systems that have them. Systems without /dev/fd and named pipes are out of luck. OPEN_FOR_READ_IN_CHILD, if 1, means open the named pipe for reading or use the read end of the pipe and dup that file descriptor to fd 0 in the child. If OPEN_FOR_READ_IN_CHILD is 0, we open the named pipe for writing or use the write end of the pipe in the child, and dup that file descriptor to fd 1 in the child. The parent does the opposite. */ static char * process_substitute (string, open_for_read_in_child) char *string; int open_for_read_in_child; { char *pathname; int fd, result; pid_t old_pid, pid; #if defined (HAVE_DEV_FD) int parent_pipe_fd, child_pipe_fd; int fildes[2]; #endif /* HAVE_DEV_FD */ #if defined (JOB_CONTROL) pid_t old_pipeline_pgrp; #endif if (!string || !*string || wordexp_only) return ((char *)NULL); #if !defined (HAVE_DEV_FD) pathname = make_named_pipe (); #else /* HAVE_DEV_FD */ if (pipe (fildes) < 0) { sys_error ("cannot make pipe for process substitution"); return ((char *)NULL); } /* If OPEN_FOR_READ_IN_CHILD == 1, we want to use the write end of the pipe in the parent, otherwise the read end. */ parent_pipe_fd = fildes[open_for_read_in_child]; child_pipe_fd = fildes[1 - open_for_read_in_child]; /* Move the parent end of the pipe to some high file descriptor, to avoid clashes with FDs used by the script. */ parent_pipe_fd = move_to_high_fd (parent_pipe_fd, 1, 64); pathname = make_dev_fd_filename (parent_pipe_fd); #endif /* HAVE_DEV_FD */ if (!pathname) { sys_error ("cannot make pipe for process substitution"); return ((char *)NULL); } old_pid = last_made_pid; #if defined (JOB_CONTROL) old_pipeline_pgrp = pipeline_pgrp; pipeline_pgrp = shell_pgrp; save_pipeline (1); #endif /* JOB_CONTROL */ pid = make_child ((char *)NULL, 1); if (pid == 0) { reset_terminating_signals (); /* XXX */ /* Cancel traps, in trap.c. */ restore_original_signals (); setup_async_signals (); subshell_environment |= SUBSHELL_COMSUB; } #if defined (JOB_CONTROL) set_sigchld_handler (); stop_making_children (); pipeline_pgrp = old_pipeline_pgrp; #endif /* JOB_CONTROL */ if (pid < 0) { sys_error ("cannot make child for process substitution"); free (pathname); #if defined (HAVE_DEV_FD) close (parent_pipe_fd); close (child_pipe_fd); #endif /* HAVE_DEV_FD */ return ((char *)NULL); } if (pid > 0) { #if defined (JOB_CONTROL) restore_pipeline (1); #endif #if !defined (HAVE_DEV_FD) fifo_list[nfifo-1].proc = pid; #endif last_made_pid = old_pid; #if defined (JOB_CONTROL) && defined (PGRP_PIPE) close_pgrp_pipe (); #endif /* JOB_CONTROL && PGRP_PIPE */ #if defined (HAVE_DEV_FD) close (child_pipe_fd); #endif /* HAVE_DEV_FD */ return (pathname); } set_sigint_handler (); #if defined (JOB_CONTROL) set_job_control (0); #endif /* JOB_CONTROL */ #if !defined (HAVE_DEV_FD) /* Open the named pipe in the child. */ fd = open (pathname, open_for_read_in_child ? O_RDONLY|O_NONBLOCK : O_WRONLY); if (fd < 0) { sys_error ("cannot open named pipe %s for %s", pathname, open_for_read_in_child ? "reading" : "writing"); exit (127); } if (open_for_read_in_child) { if (sh_unset_nodelay_mode (fd) < 0) { sys_error ("cannout reset nodelay mode for fd %d", fd); exit (127); } } #else /* HAVE_DEV_FD */ fd = child_pipe_fd; #endif /* HAVE_DEV_FD */ if (dup2 (fd, open_for_read_in_child ? 0 : 1) < 0) { sys_error ("cannot duplicate named pipe %s as fd %d", pathname, open_for_read_in_child ? 0 : 1); exit (127); } if (fd != (open_for_read_in_child ? 0 : 1)) close (fd); /* Need to close any files that this process has open to pipes inherited from its parent. */ if (current_fds_to_close) { close_fd_bitmap (current_fds_to_close); current_fds_to_close = (struct fd_bitmap *)NULL; } #if defined (HAVE_DEV_FD) /* Make sure we close the parent's end of the pipe and clear the slot in the fd list so it is not closed later, if reallocated by, for instance, pipe(2). */ close (parent_pipe_fd); dev_fd_list[parent_pipe_fd] = 0; #endif /* HAVE_DEV_FD */ result = parse_and_execute (string, "process substitution", (SEVAL_NONINT|SEVAL_NOHIST)); #if !defined (HAVE_DEV_FD) /* Make sure we close the named pipe in the child before we exit. */ close (open_for_read_in_child ? 0 : 1); #endif /* !HAVE_DEV_FD */ exit (result); /*NOTREACHED*/ } #endif /* PROCESS_SUBSTITUTION */ /***********************************/ /* */ /* Command Substitution */ /* */ /***********************************/ static char * read_comsub (fd, quoted) int fd, quoted; { char *istring, buf[128], *bufp; int istring_index, istring_size, c; ssize_t bufn; istring = (char *)NULL; istring_index = istring_size = bufn = 0; #ifdef __CYGWIN__ setmode (fd, O_TEXT); /* we don't want CR/LF, we want Unix-style */ #endif /* Read the output of the command through the pipe. */ while (1) { if (fd < 0) break; if (--bufn <= 0) { bufn = zread (fd, buf, sizeof (buf)); if (bufn <= 0) break; bufp = buf; } c = *bufp++; if (c == 0) { #if 0 internal_warning ("read_comsub: ignored null byte in input"); #endif continue; } /* Add the character to ISTRING, possibly after resizing it. */ RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size, DEFAULT_ARRAY_SIZE); if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || c == CTLESC || c == CTLNUL) istring[istring_index++] = CTLESC; istring[istring_index++] = c; #if 0 #if defined (__CYGWIN__) if (c == '\n' && istring_index > 1 && istring[istring_index - 2] == '\r') { istring_index--; istring[istring_index - 1] = '\n'; } #endif #endif } if (istring) istring[istring_index] = '\0'; /* If we read no output, just return now and save ourselves some trouble. */ if (istring_index == 0) { FREE (istring); return (char *)NULL; } /* Strip trailing newlines from the output of the command. */ if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) { while (istring_index > 0) { if (istring[istring_index - 1] == '\n') { --istring_index; /* If the newline was quoted, remove the quoting char. */ if (istring[istring_index - 1] == CTLESC) --istring_index; } else break; } istring[istring_index] = '\0'; } else strip_trailing (istring, istring_index - 1, 1); return istring; } /* Perform command substitution on STRING. This returns a string, possibly quoted. */ char * command_substitute (string, quoted) char *string; int quoted; { pid_t pid, old_pid, old_pipeline_pgrp; char *istring; int result, fildes[2], function_value; istring = (char *)NULL; /* Don't fork () if there is no need to. In the case of no command to run, just return NULL. */ if (!string || !*string || (string[0] == '\n' && !string[1])) return ((char *)NULL); if (wordexp_only && read_but_dont_execute) { last_command_exit_value = 125; jump_to_top_level (EXITPROG); } /* We're making the assumption here that the command substitution will eventually run a command from the file system. Since we'll run maybe_make_export_env in this subshell before executing that command, the parent shell and any other shells it starts will have to remake the environment. If we make it before we fork, other shells won't have to. Don't bother if we have any temporary variable assignments, though, because the export environment will be remade after this command completes anyway, but do it if all the words to be expanded are variable assignments. */ if (subst_assign_varlist == 0 || garglist == 0) maybe_make_export_env (); /* XXX */ /* Pipe the output of executing STRING into the current shell. */ if (pipe (fildes) < 0) { sys_error ("cannot make pipe for command substitution"); goto error_exit; } old_pid = last_made_pid; #if defined (JOB_CONTROL) old_pipeline_pgrp = pipeline_pgrp; /* Don't reset the pipeline pgrp if we're already a subshell in a pipeline. */ if ((subshell_environment & SUBSHELL_PIPE) == 0) pipeline_pgrp = shell_pgrp; cleanup_the_pipeline (); #endif pid = make_child ((char *)NULL, 0); if (pid == 0) /* Reset the signal handlers in the child, but don't free the trap strings. */ reset_signal_handlers (); #if defined (JOB_CONTROL) set_sigchld_handler (); stop_making_children (); pipeline_pgrp = old_pipeline_pgrp; #else stop_making_children (); #endif /* JOB_CONTROL */ if (pid < 0) { sys_error ("cannot make child for command substitution"); error_exit: FREE (istring); close (fildes[0]); close (fildes[1]); return ((char *)NULL); } if (pid == 0) { set_sigint_handler (); /* XXX */ if (dup2 (fildes[1], 1) < 0) { sys_error ("command_substitute: cannot duplicate pipe as fd 1"); exit (EXECUTION_FAILURE); } /* If standard output is closed in the parent shell (such as after `exec >&-'), file descriptor 1 will be the lowest available file descriptor, and end up in fildes[0]. This can happen for stdin and stderr as well, but stdout is more important -- it will cause no output to be generated from this command. */ if ((fildes[1] != fileno (stdin)) && (fildes[1] != fileno (stdout)) && (fildes[1] != fileno (stderr))) close (fildes[1]); if ((fildes[0] != fileno (stdin)) && (fildes[0] != fileno (stdout)) && (fildes[0] != fileno (stderr))) close (fildes[0]); /* The currently executing shell is not interactive. */ interactive = 0; /* This is a subshell environment. */ subshell_environment |= SUBSHELL_COMSUB; /* When not in POSIX mode, command substitution does not inherit the -e flag. */ if (posixly_correct == 0) exit_immediately_on_error = 0; remove_quoted_escapes (string); startup_state = 2; /* see if we can avoid a fork */ /* Give command substitution a place to jump back to on failure, so we don't go back up to main (). */ result = setjmp (top_level); /* If we're running a command substitution inside a shell function, trap `return' so we don't return from the function in the subshell and go off to never-never land. */ if (result == 0 && return_catch_flag) function_value = setjmp (return_catch); else function_value = 0; if (result == EXITPROG) exit (last_command_exit_value); else if (result) exit (EXECUTION_FAILURE); else if (function_value) exit (return_catch_value); else exit (parse_and_execute (string, "command substitution", SEVAL_NOHIST)); } else { #if defined (JOB_CONTROL) && defined (PGRP_PIPE) close_pgrp_pipe (); #endif /* JOB_CONTROL && PGRP_PIPE */ close (fildes[1]); istring = read_comsub (fildes[0], quoted); close (fildes[0]); current_command_subst_pid = pid; last_command_exit_value = wait_for (pid); last_command_subst_pid = pid; last_made_pid = old_pid; #if defined (JOB_CONTROL) /* If last_command_exit_value > 128, then the substituted command was terminated by a signal. If that signal was SIGINT, then send SIGINT to ourselves. This will break out of loops, for instance. */ if (last_command_exit_value == (128 + SIGINT)) kill (getpid (), SIGINT); /* wait_for gives the terminal back to shell_pgrp. If some other process group should have it, give it away to that group here. pipeline_pgrp is non-zero only while we are constructing a pipline, so what we are concerned about is whether or not that pipeline was started in the background. A pipeline started in the background should never get the tty back here. */ #if 0 if (interactive && pipeline_pgrp != (pid_t)0 && pipeline_pgrp != last_asynchronous_pid) #else if (interactive && pipeline_pgrp != (pid_t)0 && (subshell_environment & SUBSHELL_ASYNC) == 0) #endif give_terminal_to (pipeline_pgrp, 0); #endif /* JOB_CONTROL */ return (istring); } } /******************************************************** * * * Utility functions for parameter expansion * * * ********************************************************/ #if defined (ARRAY_VARS) static arrayind_t array_length_reference (s) char *s; { int len; arrayind_t ind; char *t, c; ARRAY *array; SHELL_VAR *var; var = array_variable_part (s, &t, &len); /* If unbound variables should generate an error, report one and return failure. */ if ((var == 0 || array_p (var) == 0) && unbound_vars_is_error) { c = *--t; *t = '\0'; err_unboundvar (s); *t = c; return (-1); } else if (var == 0) return 0; /* We support a couple of expansions for variables that are not arrays. We'll return the length of the value for v[0], and 1 for v[@] or v[*]. Return 0 for everything else. */ array = array_p (var) ? array_cell (var) : (ARRAY *)NULL; if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']') return (array_p (var) ? array_num_elements (array) : 1); ind = array_expand_index (t, len); if (ind < 0) { err_badarraysub (t); return (-1); } if (array_p (var)) t = array_reference (array, ind); else t = (ind == 0) ? value_cell (var) : (char *)NULL; len = STRLEN (t); return (len); } #endif /* ARRAY_VARS */ static int valid_brace_expansion_word (name, var_is_special) char *name; int var_is_special; { if (DIGIT (*name) && all_digits (name)) return 1; else if (var_is_special) return 1; #if defined (ARRAY_VARS) else if (valid_array_reference (name)) return 1; #endif /* ARRAY_VARS */ else if (legal_identifier (name)) return 1; else return 0; } /* Parameter expand NAME, and return a new string which is the expansion, or NULL if there was no expansion. VAR_IS_SPECIAL is non-zero if NAME is one of the special variables in the shell, e.g., "@", "$", "*", etc. QUOTED, if non-zero, means that NAME was found inside of a double-quoted expression. */ static char * parameter_brace_expand_word (name, var_is_special, quoted) char *name; int var_is_special, quoted; { char *temp, *tt; intmax_t arg_index; SHELL_VAR *var; int atype; /* Handle multiple digit arguments, as in ${11}. */ if (legal_number (name, &arg_index)) { tt = get_dollar_var_value (arg_index); temp = tt ? quote_escapes (tt) : (char *)NULL; FREE (tt); } else if (var_is_special) /* ${@} */ { int sindex; tt = (char *)xmalloc (2 + strlen (name)); tt[sindex = 0] = '$'; strcpy (tt + 1, name); temp = param_expand (tt, &sindex, quoted, (int *)NULL, (int *)NULL, (int *)NULL, (int *)NULL, 0); free (tt); } #if defined (ARRAY_VARS) else if (valid_array_reference (name)) { temp = array_value (name, quoted, &atype); if (atype == 0 && temp) temp = quote_escapes (temp); } #endif else if (var = find_variable (name)) { if (var_isset (var) && invisible_p (var) == 0) { #if defined (ARRAY_VARS) temp = array_p (var) ? array_reference (array_cell (var), 0) : value_cell (var); #else temp = value_cell (var); #endif if (temp) temp = quote_escapes (temp); } else temp = (char *)NULL; } else temp = (char *)NULL; return (temp); } /* Expand an indirect reference to a variable: ${!NAME} expands to the value of the variable whose name is the value of NAME. */ static char * parameter_brace_expand_indir (name, var_is_special, quoted) char *name; int var_is_special, quoted; { char *temp, *t; t = parameter_brace_expand_word (name, var_is_special, quoted); if (t == 0) return (t); temp = parameter_brace_expand_word (t, SPECIAL_VAR(t, 0), quoted); free (t); return temp; } /* Expand the right side of a parameter expansion of the form ${NAMEcVALUE}, depending on the value of C, the separating character. C can be one of "-", "+", or "=". QUOTED is true if the entire brace expression occurs between double quotes. */ static char * parameter_brace_expand_rhs (name, value, c, quoted, qdollaratp, hasdollarat) char *name, *value; int c, quoted, *qdollaratp, *hasdollarat; { WORD_LIST *l; char *t, *t1, *temp; int hasdol; /* XXX - Should we tilde expand in an assignment context if C is `='? */ if (*value == '~') temp = bash_tilde_expand (value, 0); else if (xstrchr (value, '~') && unquoted_substring ("=~", value)) temp = bash_tilde_expand (value, 1); else temp = savestring (value); /* If the entire expression is between double quotes, we want to treat the value as a double-quoted string, with the exception that we strip embedded unescaped double quotes. */ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *temp) { hasdol = 0; t = string_extract_double_quoted (temp, &hasdol, 1); free (temp); temp = t; } hasdol = 0; /* XXX was 0 not quoted */ l = *temp ? expand_string_for_rhs (temp, quoted, &hasdol, (int *)NULL) : (WORD_LIST *)0; if (hasdollarat) *hasdollarat = hasdol || (l && l->next); free (temp); if (l) { /* The expansion of TEMP returned something. We need to treat things slightly differently if HASDOL is non-zero. */ temp = string_list (l); /* If l->next is not null, we know that TEMP contained "$@", since that is the only expansion that creates more than one word. */ if ((hasdol && quoted) || l->next) *qdollaratp = 1; dispose_words (l); } else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && hasdol) { /* The brace expansion occurred between double quotes and there was a $@ in TEMP. It does not matter if the $@ is quoted, as long as it does not expand to anything. In this case, we want to return a quoted empty string. */ temp = (char *)xmalloc (2); temp[0] = CTLNUL; temp[1] = '\0'; } else temp = (char *)NULL; if (c == '-' || c == '+') return (temp); /* c == '=' */ t = temp ? savestring (temp) : savestring (""); t1 = dequote_string (t); free (t); bind_variable (name, t1); free (t1); return (temp); } /* Deal with the right hand side of a ${name:?value} expansion in the case that NAME is null or not set. If VALUE is non-null it is expanded and used as the error message to print, otherwise a standard message is printed. */ static void parameter_brace_expand_error (name, value) char *name, *value; { WORD_LIST *l; char *temp; if (value && *value) { if (*value == '~') temp = bash_tilde_expand (value, 0); else if (xstrchr (value, '~') && unquoted_substring ("=~", value)) temp = bash_tilde_expand (value, 1); else temp = savestring (value); l = expand_string (temp, 0); FREE (temp); temp = string_list (l); report_error ("%s: %s", name, temp ? temp : ""); /* XXX was value not "" */ FREE (temp); dispose_words (l); } else report_error ("%s: parameter null or not set", name); /* Free the data we have allocated during this expansion, since we are about to longjmp out. */ free (name); FREE (value); } /* Return 1 if NAME is something for which parameter_brace_expand_length is OK to do. */ static int valid_length_expression (name) char *name; { return (name[1] == '\0' || /* ${#} */ ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0') || /* special param */ (DIGIT (name[1]) && all_digits (name + 1)) || /* ${#11} */ #if defined (ARRAY_VARS) valid_array_reference (name + 1) || /* ${#a[7]} */ #endif legal_identifier (name + 1)); /* ${#PS1} */ } /* Handle the parameter brace expansion that requires us to return the length of a parameter. */ static intmax_t parameter_brace_expand_length (name) char *name; { char *t, *newname; intmax_t number, arg_index; WORD_LIST *list; #if defined (ARRAY_VARS) SHELL_VAR *var; #endif if (name[1] == '\0') /* ${#} */ number = number_of_args (); else if ((name[1] == '@' || name[1] == '*') && name[2] == '\0') /* ${#@}, ${#*} */ number = number_of_args (); else if ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0') { /* Take the lengths of some of the shell's special parameters. */ switch (name[1]) { case '-': t = which_set_flags (); break; case '?': t = itos (last_command_exit_value); break; case '$': t = itos (dollar_dollar_pid); break; case '!': if (last_asynchronous_pid == NO_PID) t = (char *)NULL; else t = itos (last_asynchronous_pid); break; case '#': t = itos (number_of_args ()); break; } number = STRLEN (t); FREE (t); } #if defined (ARRAY_VARS) else if (valid_array_reference (name + 1)) number = array_length_reference (name + 1); #endif /* ARRAY_VARS */ else { number = 0; if (legal_number (name + 1, &arg_index)) /* ${#1} */ { t = get_dollar_var_value (arg_index); number = STRLEN (t); FREE (t); } #if defined (ARRAY_VARS) else if ((var = find_variable (name + 1)) && array_p (var)) { t = array_reference (array_cell (var), 0); number = STRLEN (t); } #endif else /* ${#PS1} */ { newname = savestring (name); newname[0] = '$'; list = expand_string (newname, Q_DOUBLE_QUOTES); t = list ? string_list (list) : (char *)NULL; free (newname); if (list) dispose_words (list); number = STRLEN (t); FREE (t); } } return (number); } /* Skip characters in SUBSTR until DELIM. SUBSTR is an arithmetic expression, so we do some ad-hoc parsing of an arithmetic expression to find the first DELIM, instead of using strchr(3). Two rules: 1. If the substring contains a `(', read until closing `)'. 2. If the substring contains a `?', read past one `:' for each `?'. */ static char * skiparith (substr, delim) char *substr; int delim; { size_t sublen; int skipcol, pcount, i; DECLARE_MBSTATE; sublen = strlen (substr); i = skipcol = pcount = 0; while (substr[i]) { /* Balance parens */ if (substr[i] == LPAREN) { pcount++; i++; continue; } if (substr[i] == RPAREN && pcount) { pcount--; i++; continue; } if (pcount) { ADVANCE_CHAR (substr, sublen, i); continue; } /* Skip one `:' for each `?' */ if (substr[i] == ':' && skipcol) { skipcol--; i++; continue; } if (substr[i] == delim) break; if (substr[i] == '?') { skipcol++; i++; continue; } ADVANCE_CHAR (substr, sublen, i); } return (substr + i); } /* Verify and limit the start and end of the desired substring. If VTYPE == 0, a regular shell variable is being used; if it is 1, then the positional parameters are being used; if it is 2, then VALUE is really a pointer to an array variable that should be used. Return value is 1 if both values were OK, 0 if there was a problem with an invalid expression, or -1 if the values were out of range. */ static int verify_substring_values (value, substr, vtype, e1p, e2p) char *value, *substr; int vtype; intmax_t *e1p, *e2p; { char *t, *temp1, *temp2; arrayind_t len; int expok; #if defined (ARRAY_VARS) ARRAY *a; #endif /* duplicate behavior of strchr(3) */ t = skiparith (substr, ':'); if (*t && *t == ':') *t = '\0'; else t = (char *)0; temp1 = expand_string_if_necessary (substr, Q_DOUBLE_QUOTES, expand_string); *e1p = evalexp (temp1, &expok); free (temp1); if (expok == 0) return (0); len = -1; /* paranoia */ switch (vtype) { case VT_VARIABLE: case VT_ARRAYMEMBER: len = strlen (value); break; case VT_POSPARMS: len = number_of_args () + 1; break; #if defined (ARRAY_VARS) case VT_ARRAYVAR: a = (ARRAY *)value; len = array_num_elements (a) + 1; break; #endif } if (len == -1) /* paranoia */ return -1; if (*e1p < 0) /* negative offsets count from end */ *e1p += len; if (*e1p >= len || *e1p < 0) return (-1); if (t) { t++; temp2 = savestring (t); temp1 = expand_string_if_necessary (temp2, Q_DOUBLE_QUOTES, expand_string); free (temp2); t[-1] = ':'; *e2p = evalexp (temp1, &expok); free (temp1); if (expok == 0) return (0); if (*e2p < 0) { internal_error ("%s: substring expression < 0", t); return (0); } *e2p += *e1p; /* want E2 chars starting at E1 */ if (*e2p > len) *e2p = len; } else *e2p = len; return (1); } /* Return the type of variable specified by VARNAME (simple variable, positional param, or array variable). Also return the value specified by VARNAME (value of a variable or a reference to an array element). If this returns VT_VARIABLE, the caller assumes that CTLESC and CTLNUL characters in the value are quoted with CTLESC and takes appropriate steps. For convenience, *VALP is set to the dequoted VALUE. */ static int get_var_and_type (varname, value, varp, valp) char *varname, *value; SHELL_VAR **varp; char **valp; { int vtype; char *temp; #if defined (ARRAY_VARS) SHELL_VAR *v; #endif /* This sets vtype to VT_VARIABLE or VT_POSPARMS */ vtype = (varname[0] == '@' || varname[0] == '*') && varname[1] == '\0'; *varp = (SHELL_VAR *)NULL; #if defined (ARRAY_VARS) if (valid_array_reference (varname)) { v = array_variable_part (varname, &temp, (int *)0); if (v && array_p (v)) { /* [ */ if (ALL_ELEMENT_SUB (temp[0]) && temp[1] == ']') { vtype = VT_ARRAYVAR; *valp = (char *)array_cell (v); } else { vtype = VT_ARRAYMEMBER; *valp = array_value (varname, 1, (int *)NULL); } *varp = v; } else return -1; } else if ((v = find_variable (varname)) && array_p (v)) { vtype = VT_ARRAYMEMBER; *varp = v; *valp = array_reference (array_cell (v), 0); } else #endif *valp = (value && vtype == VT_VARIABLE) ? dequote_escapes (value) : value; return vtype; } /******************************************************/ /* */ /* Functions to extract substrings of variable values */ /* */ /******************************************************/ /* Process a variable substring expansion: ${name:e1[:e2]}. If VARNAME is `@', use the positional parameters; otherwise, use the value of VARNAME. If VARNAME is an array variable, use the array elements. */ static char * parameter_brace_substring (varname, value, substr, quoted) char *varname, *value, *substr; int quoted; { intmax_t e1, e2; int vtype, r; char *temp, *val, *tt; SHELL_VAR *v; if (value == 0) return ((char *)NULL); this_command_name = varname; vtype = get_var_and_type (varname, value, &v, &val); if (vtype == -1) return ((char *)NULL); r = verify_substring_values (val, substr, vtype, &e1, &e2); if (r <= 0) return ((r == 0) ? &expand_param_error : (char *)NULL); switch (vtype) { case VT_VARIABLE: case VT_ARRAYMEMBER: tt = substring (val, e1, e2); if (vtype == VT_VARIABLE) FREE (val); if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) temp = quote_string (tt); else temp = tt ? quote_escapes (tt) : (char *)NULL; FREE (tt); break; case VT_POSPARMS: tt = pos_params (varname, e1, e2, quoted); if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0) { temp = tt ? quote_escapes (tt) : (char *)NULL; FREE (tt); } else temp = tt; break; #if defined (ARRAY_VARS) case VT_ARRAYVAR: tt = array_subrange (array_cell (v), e1, e2, quoted); if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0) { temp = tt ? quote_escapes (tt) : (char *)NULL; FREE (tt); } else temp = tt; break; #endif default: temp = (char *)NULL; } return temp; } /****************************************************************/ /* */ /* Functions to perform pattern substitution on variable values */ /* */ /****************************************************************/ char * pat_subst (string, pat, rep, mflags) char *string, *pat, *rep; int mflags; { char *ret, *s, *e, *str; int rsize, rptr, l, replen, mtype; mtype = mflags & MATCH_TYPEMASK; /* Special cases: * 1. A null pattern with mtype == MATCH_BEG means to prefix STRING * with REP and return the result. * 2. A null pattern with mtype == MATCH_END means to append REP to * STRING and return the result. */ if ((pat == 0 || *pat == 0) && (mtype == MATCH_BEG || mtype == MATCH_END)) { replen = STRLEN (rep); l = strlen (string); ret = (char *)xmalloc (replen + l + 2); if (replen == 0) strcpy (ret, string); else if (mtype == MATCH_BEG) { strcpy (ret, rep); strcpy (ret + replen, string); } else { strcpy (ret, string); strcpy (ret + l, rep); } return (ret); } ret = (char *)xmalloc (rsize = 64); ret[0] = '\0'; for (replen = STRLEN (rep), rptr = 0, str = string;;) { if (match_pattern (str, pat, mtype, &s, &e) == 0) break; l = s - str; RESIZE_MALLOCED_BUFFER (ret, rptr, (l + replen), rsize, 64); /* OK, now copy the leading unmatched portion of the string (from str to s) to ret starting at rptr (the current offset). Then copy the replacement string at ret + rptr + (s - str). Increment rptr (if necessary) and str and go on. */ if (l) { strncpy (ret + rptr, str, l); rptr += l; } if (replen) { strncpy (ret + rptr, rep, replen); rptr += replen; } if (s == e) e++; /* avoid infinite recursion on zero-length match */ str = e; /* e == end of match */ if (((mflags & MATCH_GLOBREP) == 0) || mtype != MATCH_ANY) break; } /* Now copy the unmatched portion of the input string */ if (*str) { RESIZE_MALLOCED_BUFFER (ret, rptr, STRLEN(str) + 1, rsize, 64); strcpy (ret + rptr, str); } else ret[rptr] = '\0'; return ret; } /* Do pattern match and replacement on the positional parameters. */ static char * pos_params_pat_subst (string, pat, rep, mflags) char *string, *pat, *rep; int mflags; { WORD_LIST *save, *params; WORD_DESC *w; char *ret, *tt; save = params = list_rest_of_args (); if (save == 0) return ((char *)NULL); for ( ; params; params = params->next) { ret = pat_subst (params->word->word, pat, rep, mflags); w = make_bare_word (ret); dispose_word (params->word); params->word = w; FREE (ret); } ret = string_list ((mflags & MATCH_QUOTED) ? quote_list (save) : save); dispose_words (save); return (ret); } /* Perform pattern substitution on VALUE, which is the expansion of VARNAME. PATSUB is an expression supplying the pattern to match and the string to substitute. QUOTED is a flags word containing the type of quoting currently in effect. */ static char * parameter_brace_patsub (varname, value, patsub, quoted) char *varname, *value, *patsub; int quoted; { int vtype, mflags; char *val, *temp, *pat, *rep, *p, *lpatsub, *tt; SHELL_VAR *v; if (value == 0) return ((char *)NULL); this_command_name = varname; vtype = get_var_and_type (varname, value, &v, &val); if (vtype == -1) return ((char *)NULL); mflags = 0; if (*patsub == '/') { mflags |= MATCH_GLOBREP; patsub++; } /* Malloc this because expand_string_if_necessary or one of the expansion functions in its call chain may free it on a substitution error. */ lpatsub = savestring (patsub); if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) mflags |= MATCH_QUOTED; if (rep = quoted_strchr (lpatsub, '/', ST_BACKSL)) *rep++ = '\0'; else rep = (char *)NULL; if (rep && *rep == '\0') rep = (char *)NULL; /* Expand PAT and REP for command, variable and parameter, arithmetic, and process substitution. Also perform quote removal. Do not perform word splitting or filename generation. */ pat = expand_string_if_necessary (lpatsub, (quoted & ~Q_DOUBLE_QUOTES), expand_string_unsplit); if (rep) { if ((mflags & MATCH_QUOTED) == 0) rep = expand_string_if_necessary (rep, quoted, expand_string_unsplit); else rep = expand_string_to_string_internal (rep, quoted, expand_string_unsplit); } p = pat; if (pat && pat[0] == '#') { mflags |= MATCH_BEG; p++; } else if (pat && pat[0] == '%') { mflags |= MATCH_END; p++; } else mflags |= MATCH_ANY; /* OK, we now want to substitute REP for PAT in VAL. If flags & MATCH_GLOBREP is non-zero, the substitution is done everywhere, otherwise only the first occurrence of PAT is replaced. The pattern matching code doesn't understand CTLESC quoting CTLESC and CTLNUL so we use the dequoted variable values passed in (VT_VARIABLE) so the pattern substitution code works right. We need to requote special chars after we're done for VT_VARIABLE and VT_ARRAYMEMBER, and for the other cases if QUOTED == 0, since the posparams and arrays indexed by * or @ do special things when QUOTED != 0. */ switch (vtype) { case VT_VARIABLE: case VT_ARRAYMEMBER: temp = pat_subst (val, p, rep, mflags); if (vtype == VT_VARIABLE) FREE (val); if (temp) { tt = quote_escapes (temp); free (temp); temp = tt; } break; case VT_POSPARMS: temp = pos_params_pat_subst (val, p, rep, mflags); if (temp && (mflags & MATCH_QUOTED) == 0) { tt = quote_escapes (temp); free (temp); temp = tt; } break; #if defined (ARRAY_VARS) case VT_ARRAYVAR: temp = array_patsub (array_cell (v), p, rep, mflags); if (temp && (mflags & MATCH_QUOTED) == 0) { tt = quote_escapes (temp); free (temp); temp = tt; } break; #endif } FREE (pat); FREE (rep); free (lpatsub); return temp; } /****************************************************************/ /* */ /* Functions to perform parameter expansion on a string */ /* */ /****************************************************************/ /* ${[#][!]name[[:]#[#]%[%]-=?+[word][:e1[:e2]]]} */ static char * parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_dollar_at) char *string; int *indexp, quoted, *quoted_dollar_atp, *contains_dollar_at; { int check_nullness, var_is_set, var_is_null, var_is_special; int want_substring, want_indir, want_patsub; char *name, *value, *temp, *temp1; int t_index, sindex, c; intmax_t number; value = (char *)NULL; var_is_set = var_is_null = var_is_special = check_nullness = 0; want_substring = want_indir = want_patsub = 0; sindex = *indexp; t_index = ++sindex; name = string_extract (string, &t_index, "#%:-=?+/}", EX_VARNAME); /* If the name really consists of a special variable, then make sure that we have the entire name. We don't allow indirect references to special variables except `#', `?', `@' and `*'. */ if ((sindex == t_index && (string[t_index] == '-' || string[t_index] == '?' || string[t_index] == '#')) || (sindex == t_index - 1 && string[sindex] == '!' && (string[t_index] == '#' || string[t_index] == '?' || string[t_index] == '@' || string[t_index] == '*'))) { t_index++; free (name); temp1 = string_extract (string, &t_index, "#%:-=?+/}", 0); name = (char *)xmalloc (3 + (strlen (temp1))); *name = string[sindex]; if (string[sindex] == '!') { /* indirect reference of $#, $?, $@, or $* */ name[1] = string[sindex + 1]; strcpy (name + 2, temp1); } else strcpy (name + 1, temp1); free (temp1); } sindex = t_index; /* Find out what character ended the variable name. Then do the appropriate thing. */ if (c = string[sindex]) sindex++; /* If c is followed by one of the valid parameter expansion characters, move past it as normal. If not, assume that a substring specification is being given, and do not move past it. */ if (c == ':' && VALID_PARAM_EXPAND_CHAR (string[sindex])) { check_nullness++; if (c = string[sindex]) sindex++; } else if (c == ':' && string[sindex] != RBRACE) want_substring = 1; else if (c == '/' && string[sindex] != RBRACE) want_patsub = 1; /* Catch the valid and invalid brace expressions that made it through the tests above. */ /* ${#-} is a valid expansion and means to take the length of $-. Similarly for ${#?} and ${##}... */ if (name[0] == '#' && name[1] == '\0' && check_nullness == 0 && VALID_SPECIAL_LENGTH_PARAM (c) && string[sindex] == RBRACE) { name = (char *)xrealloc (name, 3); name[1] = c; name[2] = '\0'; c = string[sindex++]; } /* ...but ${#%}, ${#:}, ${#=}, ${#+}, and ${#/} are errors. */ if (name[0] == '#' && name[1] == '\0' && check_nullness == 0 && member (c, "%:=+/") && string[sindex] == RBRACE) { temp = (char *)NULL; goto bad_substitution; } /* Indirect expansion begins with a `!'. A valid indirect expansion is either a variable name, one of the positional parameters or a special variable that expands to one of the positional parameters. */ want_indir = *name == '!' && (legal_variable_starter ((unsigned char)name[1]) || DIGIT (name[1]) || VALID_INDIR_PARAM (name[1])); /* Determine the value of this variable. */ /* Check for special variables, directly referenced. */ if (SPECIAL_VAR (name, want_indir)) var_is_special++; /* Check for special expansion things, like the length of a parameter */ if (*name == '#' && name[1]) { /* If we are not pointing at the character just after the closing brace, then we haven't gotten all of the name. Since it begins with a special character, this is a bad substitution. Also check NAME for validity before trying to go on. */ if (string[sindex - 1] != RBRACE || (valid_length_expression (name) == 0)) { temp = (char *)NULL; goto bad_substitution; } number = parameter_brace_expand_length (name); free (name); *indexp = sindex; return ((number < 0) ? &expand_param_error : itos (number)); } /* ${@} is identical to $@. */ if (name[0] == '@' && name[1] == '\0') { if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) *quoted_dollar_atp = 1; if (contains_dollar_at) *contains_dollar_at = 1; } /* Process ${PREFIX*} expansion. */ if (want_indir && string[sindex - 1] == RBRACE && (string[sindex - 2] == '*' || string[sindex - 2] == '@') && legal_variable_starter ((unsigned char) name[1])) { char **x; WORD_LIST *xlist; temp1 = savestring (name + 1); number = strlen (temp1); temp1[number - 1] = '\0'; x = all_variables_matching_prefix (temp1); xlist = strvec_to_word_list (x, 0, 0); if (string[sindex - 2] == '*') temp = string_list_dollar_star (xlist); else { temp = string_list_dollar_at (xlist, quoted); if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) *quoted_dollar_atp = 1; if (contains_dollar_at) *contains_dollar_at = 1; } free (x); free (xlist); free (temp1); *indexp = sindex; return (temp); } /* Make sure that NAME is valid before trying to go on. */ if (valid_brace_expansion_word (want_indir ? name + 1 : name, var_is_special) == 0) { temp = (char *)NULL; goto bad_substitution; } if (want_indir) temp = parameter_brace_expand_indir (name + 1, var_is_special, quoted); else temp = parameter_brace_expand_word (name, var_is_special, quoted); #if defined (ARRAY_VARS) if (valid_array_reference (name)) { temp1 = xstrchr (name, '['); if (temp1 && temp1[1] == '@' && temp1[2] == ']') { if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) *quoted_dollar_atp = 1; if (contains_dollar_at) *contains_dollar_at = 1; } /* [ */ /* ${array[*]}, when unquoted, should be treated like ${array[@]}, which should result in separate words even when IFS is unset. */ if (temp1 && temp1[1] == '*' && temp1[2] == ']' && quoted == 0) { if (contains_dollar_at) *contains_dollar_at = 1; } } #endif var_is_set = temp != (char *)0; var_is_null = check_nullness && (var_is_set == 0 || *temp == 0); /* Get the rest of the stuff inside the braces. */ if (c && c != RBRACE) { /* Extract the contents of the ${ ... } expansion according to the Posix.2 rules. */ value = extract_dollar_brace_string (string, &sindex, quoted, 0); if (string[sindex] == RBRACE) sindex++; else goto bad_substitution; } else value = (char *)NULL; *indexp = sindex; /* If this is a substring spec, process it and add the result. */ if (want_substring) { temp1 = parameter_brace_substring (name, temp, value, quoted); FREE (name); FREE (value); FREE (temp); return (temp1); } else if (want_patsub) { temp1 = parameter_brace_patsub (name, temp, value, quoted); FREE (name); FREE (value); FREE (temp); return (temp1); } /* Do the right thing based on which character ended the variable name. */ switch (c) { default: case '\0': bad_substitution: report_error ("%s: bad substitution", string ? string : "??"); FREE (value); FREE (temp); free (name); return &expand_param_error; case RBRACE: if (var_is_set == 0 && unbound_vars_is_error) { err_unboundvar (name); FREE (value); FREE (temp); free (name); last_command_exit_value = EXECUTION_FAILURE; return (interactive_shell ? &expand_param_error : &expand_param_fatal); } break; case '#': /* ${param#[#]pattern} */ case '%': /* ${param%[%]pattern} */ if (value == 0 || *value == '\0' || temp == 0 || *temp == '\0') { FREE (value); break; } temp1 = parameter_brace_remove_pattern (name, temp, value, c, quoted); free (temp); free (value); temp = temp1; break; case '-': case '=': case '?': case '+': if (var_is_set && var_is_null == 0) { /* If the operator is `+', we don't want the value of the named variable for anything, just the value of the right hand side. */ if (c == '+') { /* XXX -- if we're double-quoted and the named variable is "$@", we want to turn off any special handling of "$@" -- we're not using it, so whatever is on the rhs applies. */ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) *quoted_dollar_atp = 0; if (contains_dollar_at) *contains_dollar_at = 0; FREE (temp); if (value) { temp = parameter_brace_expand_rhs (name, value, c, quoted, quoted_dollar_atp, contains_dollar_at); free (value); } else temp = (char *)NULL; } else { FREE (value); } /* Otherwise do nothing; just use the value in TEMP. */ } else /* VAR not set or VAR is NULL. */ { FREE (temp); temp = (char *)NULL; if (c == '=' && var_is_special) { report_error ("$%s: cannot assign in this way", name); free (name); free (value); return &expand_param_error; } else if (c == '?') { parameter_brace_expand_error (name, value); return (interactive_shell ? &expand_param_error : &expand_param_fatal); } else if (c != '+') { /* XXX -- if we're double-quoted and the named variable is "$@", we want to turn off any special handling of "$@" -- we're not using it, so whatever is on the rhs applies. */ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) *quoted_dollar_atp = 0; if (contains_dollar_at) *contains_dollar_at = 0; temp = parameter_brace_expand_rhs (name, value, c, quoted, quoted_dollar_atp, contains_dollar_at); } free (value); } break; } free (name); return (temp); } /* Expand a single ${xxx} expansion. The braces are optional. When the braces are used, parameter_brace_expand() does the work, possibly calling param_expand recursively. */ static char * param_expand (string, sindex, quoted, expanded_something, contains_dollar_at, quoted_dollar_at_p, had_quoted_null_p, pflags) char *string; int *sindex, quoted, *expanded_something, *contains_dollar_at; int *quoted_dollar_at_p, *had_quoted_null_p, pflags; { char *temp, *temp1, uerror[3]; int zindex, t_index, expok; unsigned char c; intmax_t number; SHELL_VAR *var; WORD_LIST *list; zindex = *sindex; c = string[++zindex]; temp = (char *)NULL; /* Do simple cases first. Switch on what follows '$'. */ switch (c) { /* $0 .. $9? */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': temp1 = dollar_vars[TODIGIT (c)]; if (unbound_vars_is_error && temp1 == (char *)NULL) { uerror[0] = '$'; uerror[1] = c; uerror[2] = '\0'; err_unboundvar (uerror); last_command_exit_value = EXECUTION_FAILURE; return (interactive_shell ? &expand_param_error : &expand_param_fatal); } temp = temp1 ? quote_escapes (temp1) : (char *)NULL; break; /* $$ -- pid of the invoking shell. */ case '$': temp = itos (dollar_dollar_pid); break; /* $# -- number of positional parameters. */ case '#': temp = itos (number_of_args ()); break; /* $? -- return value of the last synchronous command. */ case '?': temp = itos (last_command_exit_value); break; /* $- -- flags supplied to the shell on invocation or by `set'. */ case '-': temp = which_set_flags (); break; /* $! -- Pid of the last asynchronous command. */ case '!': /* If no asynchronous pids have been created, expand to nothing. If `set -u' has been executed, and no async processes have been created, this is an expansion error. */ if (last_asynchronous_pid == NO_PID) { if (expanded_something) *expanded_something = 0; temp = (char *)NULL; if (unbound_vars_is_error) { uerror[0] = '$'; uerror[1] = c; uerror[2] = '\0'; err_unboundvar (uerror); last_command_exit_value = EXECUTION_FAILURE; return (interactive_shell ? &expand_param_error : &expand_param_fatal); } } else temp = itos (last_asynchronous_pid); break; /* The only difference between this and $@ is when the arg is quoted. */ case '*': /* `$*' */ list = list_rest_of_args (); /* If there are no command-line arguments, this should just disappear if there are other characters in the expansion, even if it's quoted. */ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && list == 0) temp = (char *)NULL; else if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) { /* If we have "$*" we want to make a string of the positional parameters, separated by the first character of $IFS, and quote the whole string, including the separators. If IFS is unset, the parameters are separated by ' '; if $IFS is null, the parameters are concatenated. */ temp = string_list_dollar_star (list); temp1 = quote_string (temp); free (temp); temp = temp1; } else { /* If the $* is not quoted it is identical to $@ */ temp = string_list_dollar_at (list, quoted); if (expand_no_split_dollar_star == 0 && contains_dollar_at) *contains_dollar_at = 1; } dispose_words (list); break; /* When we have "$@" what we want is "$1" "$2" "$3" ... This means that we have to turn quoting off after we split into the individually quoted arguments so that the final split on the first character of $IFS is still done. */ case '@': /* `$@' */ list = list_rest_of_args (); /* We want to flag the fact that we saw this. We can't turn off quoting entirely, because other characters in the string might need it (consider "\"$@\""), but we need some way to signal that the final split on the first character of $IFS should be done, even though QUOTED is 1. */ if (quoted_dollar_at_p && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) *quoted_dollar_at_p = 1; if (contains_dollar_at) *contains_dollar_at = 1; /* We want to separate the positional parameters with the first character of $IFS in case $IFS is something other than a space. We also want to make sure that splitting is done no matter what -- according to POSIX.2, this expands to a list of the positional parameters no matter what IFS is set to. */ temp = string_list_dollar_at (list, quoted); dispose_words (list); break; case LBRACE: temp = parameter_brace_expand (string, &zindex, quoted, quoted_dollar_at_p, contains_dollar_at); if (temp == &expand_param_error || temp == &expand_param_fatal) return (temp); /* XXX */ /* Quoted nulls should be removed if there is anything else in the string. */ /* Note that we saw the quoted null so we can add one back at the end of this function if there are no other characters in the string, discard TEMP, and go on. The exception to this is when we have "${@}" and $1 is '', since $@ needs special handling. */ if (temp && QUOTED_NULL (temp)) { if (had_quoted_null_p) *had_quoted_null_p = 1; if (*quoted_dollar_at_p == 0) { free (temp); temp = (char *)NULL; } } goto return0; /* Do command or arithmetic substitution. */ case LPAREN: /* We have to extract the contents of this paren substitution. */ t_index = zindex + 1; temp = extract_command_subst (string, &t_index); zindex = t_index; /* For Posix.2-style `$(( ))' arithmetic substitution, extract the expression and pass it to the evaluator. */ if (temp && *temp == LPAREN) { char *temp2; temp1 = temp + 1; temp2 = savestring (temp1); t_index = strlen (temp2) - 1; if (temp2[t_index] != RPAREN) { free (temp2); goto comsub; } /* Cut off ending `)' */ temp2[t_index] = '\0'; /* Expand variables found inside the expression. */ temp1 = expand_string_if_necessary (temp2, Q_DOUBLE_QUOTES, expand_string); free (temp2); arithsub: /* No error messages. */ this_command_name = (char *)NULL; number = evalexp (temp1, &expok); free (temp); free (temp1); if (expok == 0) { if (interactive_shell == 0 && posixly_correct) { last_command_exit_value = EXECUTION_FAILURE; return (&expand_param_fatal); } else return (&expand_param_error); } temp = itos (number); break; } comsub: temp1 = command_substitute (temp, quoted); FREE (temp); temp = temp1; break; /* Do POSIX.2d9-style arithmetic substitution. This will probably go away in a future bash release. */ case '[': /* Extract the contents of this arithmetic substitution. */ t_index = zindex + 1; temp = extract_arithmetic_subst (string, &t_index); zindex = t_index; /* Do initial variable expansion. */ temp1 = expand_string_if_necessary (temp, Q_DOUBLE_QUOTES, expand_string); goto arithsub; default: /* Find the variable in VARIABLE_LIST. */ temp = (char *)NULL; for (t_index = zindex; (c = string[zindex]) && legal_variable_char (c); zindex++) ; temp1 = (zindex > t_index) ? substring (string, t_index, zindex) : (char *)NULL; /* If this isn't a variable name, then just output the `$'. */ if (temp1 == 0 || *temp1 == '\0') { FREE (temp1); temp = (char *)xmalloc (2); temp[0] = '$'; temp[1] = '\0'; if (expanded_something) *expanded_something = 0; goto return0; } /* If the variable exists, return its value cell. */ var = find_variable (temp1); if (var && invisible_p (var) == 0 && var_isset (var)) { #if defined (ARRAY_VARS) if (array_p (var)) { temp = array_reference (array_cell (var), 0); if (temp) temp = quote_escapes (temp); } else #endif temp = quote_escapes (value_cell (var)); free (temp1); goto return0; } temp = (char *)NULL; if (unbound_vars_is_error) err_unboundvar (temp1); else { free (temp1); goto return0; } free (temp1); last_command_exit_value = EXECUTION_FAILURE; return ((unbound_vars_is_error && interactive_shell == 0) ? &expand_param_fatal : &expand_param_error); } if (string[zindex]) zindex++; return0: *sindex = zindex; return (temp); } /* Make a word list which is the result of parameter and variable expansion, command substitution, arithmetic substitution, and quote removal of WORD. Return a pointer to a WORD_LIST which is the result of the expansion. If WORD contains a null word, the word list returned is also null. QUOTED contains flag values defined in shell.h. ISEXP is used to tell expand_word_internal that the word should be treated as the result of an expansion. This has implications for how IFS characters in the word are treated. CONTAINS_DOLLAR_AT and EXPANDED_SOMETHING are return values; when non-null they point to an integer value which receives information about expansion. CONTAINS_DOLLAR_AT gets non-zero if WORD contained "$@", else zero. EXPANDED_SOMETHING get non-zero if WORD contained any parameter expansions, else zero. This only does word splitting in the case of $@ expansion. In that case, we split on ' '. */ /* Values for the local variable quoted_state. */ #define UNQUOTED 0 #define PARTIALLY_QUOTED 1 #define WHOLLY_QUOTED 2 static WORD_LIST * expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_something) WORD_DESC *word; int quoted, isexp; int *contains_dollar_at; int *expanded_something; { WORD_LIST *list; WORD_DESC *tword; /* The intermediate string that we build while expanding. */ char *istring; /* The current size of the above object. */ int istring_size; /* Index into ISTRING. */ int istring_index; /* Temporary string storage. */ char *temp, *temp1; /* The text of WORD. */ register char *string; /* The size of STRING. */ size_t string_size; /* The index into STRING. */ int sindex; /* This gets 1 if we see a $@ while quoted. */ int quoted_dollar_at; /* One of UNQUOTED, PARTIALLY_QUOTED, or WHOLLY_QUOTED, depending on whether WORD contains no quoting characters, a partially quoted string (e.g., "xx"ab), or is fully quoted (e.g., "xxab"). */ int quoted_state; int had_quoted_null; int has_dollar_at; int tflag; register unsigned char c; /* Current character. */ int t_index; /* For calls to string_extract_xxx. */ char twochars[2]; DECLARE_MBSTATE; istring = (char *)xmalloc (istring_size = DEFAULT_INITIAL_ARRAY_SIZE); istring[istring_index = 0] = '\0'; quoted_dollar_at = had_quoted_null = has_dollar_at = 0; quoted_state = UNQUOTED; string = word->word; if (string == 0) goto finished_with_string; string_size = strlen (string); if (contains_dollar_at) *contains_dollar_at = 0; /* Begin the expansion. */ for (sindex = 0; ;) { c = string[sindex]; /* Case on toplevel character. */ switch (c) { case '\0': goto finished_with_string; case CTLESC: sindex++; #if HANDLE_MULTIBYTE if (MB_CUR_MAX > 1 && string[sindex]) { int i; mbstate_t state_bak; size_t mblength; state_bak = state; mblength = mbrlen (string + sindex, string_size - sindex, &state); if (mblength == (size_t)-1 || mblength == (size_t)-2) { state = state_bak; mblength = 1; } if (mblength < 1) mblength = 1; temp = (char *)xmalloc (mblength + 2); temp[0] = CTLESC; for (i = 0; i < mblength; i++) temp[i+1] = string[sindex++]; temp[mblength + 1] = '\0'; goto add_string; } else #endif { temp = (char *)xmalloc (3); temp[0] = CTLESC; temp[1] = c = string[sindex]; temp[2] = '\0'; } dollar_add_string: if (string[sindex]) sindex++; add_string: if (temp) { istring = sub_append_string (temp, istring, &istring_index, &istring_size); temp = (char *)0; } break; #if defined (PROCESS_SUBSTITUTION) /* Process substitution. */ case '<': case '>': { if (string[++sindex] != LPAREN || (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || posixly_correct) { sindex--; /* add_character: label increments sindex */ goto add_character; } else t_index = sindex + 1; /* skip past both '<' and LPAREN */ temp1 = extract_process_subst (string, (c == '<') ? "<(" : ">(", &t_index); /*))*/ sindex = t_index; /* If the process substitution specification is `<()', we want to open the pipe for writing in the child and produce output; if it is `>()', we want to open the pipe for reading in the child and consume input. */ temp = temp1 ? process_substitute (temp1, (c == '>')) : (char *)0; FREE (temp1); goto dollar_add_string; } #endif /* PROCESS_SUBSTITUTION */ case '$': if (expanded_something) *expanded_something = 1; has_dollar_at = 0; temp = param_expand (string, &sindex, quoted, expanded_something, &has_dollar_at, "ed_dollar_at, &had_quoted_null, 0); if (temp == &expand_param_error || temp == &expand_param_fatal) { free (string); free (istring); return ((temp == &expand_param_error) ? &expand_word_error : &expand_word_fatal); } if (contains_dollar_at && has_dollar_at) *contains_dollar_at = 1; goto add_string; break; case '`': /* Backquoted command substitution. */ { sindex++; if (expanded_something) *expanded_something = 1; temp = string_extract (string, &sindex, "`", 0); de_backslash (temp); temp1 = command_substitute (temp, quoted); FREE (temp); temp = temp1; goto dollar_add_string; } case '\\': if (string[sindex + 1] == '\n') { sindex += 2; continue; } c = string[++sindex]; if (quoted & Q_HERE_DOCUMENT) tflag = CBSHDOC; else if (quoted & Q_DOUBLE_QUOTES) tflag = CBSDQUOTE; else tflag = 0; if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0)) { SCOPY_CHAR_I (twochars, '\\', c, string, sindex, string_size); } else if (c == 0) { c = CTLNUL; sindex--; /* add_character: label increments sindex */ goto add_character; } else { SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size); } sindex++; add_twochars: /* BEFORE jumping here, we need to increment sindex if appropriate */ RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size, DEFAULT_ARRAY_SIZE); istring[istring_index++] = twochars[0]; istring[istring_index++] = twochars[1]; istring[istring_index] = '\0'; break; case '"': #if 0 if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT|Q_PATQUOTE)) #else if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) #endif goto add_character; t_index = ++sindex; temp = string_extract_double_quoted (string, &sindex, 0); /* If the quotes surrounded the entire string, then the whole word was quoted. */ quoted_state = (t_index == 1 && string[sindex] == '\0') ? WHOLLY_QUOTED : PARTIALLY_QUOTED; if (temp && *temp) { tword = make_word (temp); /* XXX */ free (temp); temp = (char *)NULL; has_dollar_at = 0; list = expand_word_internal (tword, Q_DOUBLE_QUOTES, 0, &has_dollar_at, (int *)NULL); if (list == &expand_word_error || list == &expand_word_fatal) { free (istring); free (string); /* expand_word_internal has already freed temp_word->word for us because of the way it prints error messages. */ tword->word = (char *)NULL; dispose_word (tword); return list; } dispose_word (tword); /* "$@" (a double-quoted dollar-at) expands into nothing, not even a NULL word, when there are no positional parameters. */ if (list == 0 && has_dollar_at) { quoted_dollar_at++; break; } /* If we get "$@", we know we have expanded something, so we need to remember it for the final split on $IFS. This is a special case; it's the only case where a quoted string can expand into more than one word. It's going to come back from the above call to expand_word_internal as a list with a single word, in which all characters are quoted and separated by blanks. What we want to do is to turn it back into a list for the next piece of code. */ if (list) dequote_list (list); if (has_dollar_at) { quoted_dollar_at++; if (contains_dollar_at) *contains_dollar_at = 1; if (expanded_something) *expanded_something = 1; } } else { /* What we have is "". This is a minor optimization. */ FREE (temp); list = (WORD_LIST *)NULL; } /* The code above *might* return a list (consider the case of "$@", where it returns "$1", "$2", etc.). We can't throw away the rest of the list, and we have to make sure each word gets added as quoted. We test on tresult->next: if it is non-NULL, we quote the whole list, save it to a string with string_list, and add that string. We don't need to quote the results of this (and it would be wrong, since that would quote the separators as well), so we go directly to add_string. */ if (list) { if (list->next) { /* Testing quoted_dollar_at makes sure that "$@" is split correctly when $IFS does not contain a space. */ temp = quoted_dollar_at ? string_list_dollar_at (list, Q_DOUBLE_QUOTES) : string_list (quote_list (list)); dispose_words (list); goto add_string; } else { temp = savestring (list->word->word); dispose_words (list); #if 1 /* If the string is not a quoted null string, we want to remove any embedded unquoted CTLNUL characters. We do not want to turn quoted null strings back into the empty string, though. We do this because we want to remove any quoted nulls from expansions that contain other characters. For example, if we have x"$*"y or "x$*y" and there are no positional parameters, the $* should expand into nothing. */ /* HOWEVER, this fails if the string contains a literal CTLNUL or CTLNUL is contained in the (non-null) expansion of some variable. I'm not sure what to do about this yet. */ if (QUOTED_NULL (temp) == 0) remove_quoted_nulls (temp); /* XXX */ #endif } } else temp = (char *)NULL; /* We do not want to add quoted nulls to strings that are only partially quoted; we can throw them away. */ if (temp == 0 && quoted_state == PARTIALLY_QUOTED) continue; add_quoted_string: if (temp) { temp1 = temp; temp = quote_string (temp); free (temp1); goto add_string; } else { /* Add NULL arg. */ c = CTLNUL; sindex--; /* add_character: label increments sindex */ goto add_character; } /* break; */ case '\'': #if 0 if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT|Q_PATQUOTE)) #else if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) #endif goto add_character; t_index = ++sindex; temp = string_extract_single_quoted (string, &sindex); /* If the entire STRING was surrounded by single quotes, then the string is wholly quoted. */ quoted_state = (t_index == 1 && string[sindex] == '\0') ? WHOLLY_QUOTED : PARTIALLY_QUOTED; /* If all we had was '', it is a null expansion. */ if (*temp == '\0') { free (temp); temp = (char *)NULL; } else remove_quoted_escapes (temp); /* ??? */ /* We do not want to add quoted nulls to strings that are only partially quoted; such nulls are discarded. */ if (temp == 0 && (quoted_state == PARTIALLY_QUOTED)) continue; /* If we have a quoted null expansion, add a quoted NULL to istring. */ if (temp == 0) { c = CTLNUL; sindex--; /* add_character: label increments sindex */ goto add_character; } else goto add_quoted_string; /* break; */ default: /* This is the fix for " $@ " */ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (isexp == 0 && isifs (c))) { if (string[sindex]) /* from old goto dollar_add_string */ sindex++; if (c == 0) { c = CTLNUL; goto add_character; } else { #if HANDLE_MULTIBYTE /* XXX - I'd like to use SCOPY_CHAR_I here. */ if (MB_CUR_MAX > 1) { int i; mbstate_t state_bak; size_t mblength; sindex--; state_bak = state; mblength = mbrlen (string + sindex, string_size - sindex, &state); if (mblength == (size_t)-1 || mblength == (size_t)-2) { state = state_bak; mblength = 1; } if (mblength < 1) mblength = 1; temp = (char *)xmalloc (mblength + 2); temp[0] = CTLESC; for (i = 0; i < mblength; i++) temp[i + 1] = string[sindex++]; temp[mblength + 1] = '\0'; goto add_string; } else #endif { twochars[0] = CTLESC; twochars[1] = c; goto add_twochars; } } } SADD_MBCHAR (temp, string, sindex, string_size); add_character: RESIZE_MALLOCED_BUFFER (istring, istring_index, 1, istring_size, DEFAULT_ARRAY_SIZE); istring[istring_index++] = c; istring[istring_index] = '\0'; /* Next character. */ sindex++; } } finished_with_string: /* OK, we're ready to return. If we have a quoted string, and quoted_dollar_at is not set, we do no splitting at all; otherwise we split on ' '. The routines that call this will handle what to do if nothing has been expanded. */ /* Partially and wholly quoted strings which expand to the empty string are retained as an empty arguments. Unquoted strings which expand to the empty string are discarded. The single exception is the case of expanding "$@" when there are no positional parameters. In that case, we discard the expansion. */ /* Because of how the code that handles "" and '' in partially quoted strings works, we need to make ISTRING into a QUOTED_NULL if we saw quoting characters, but the expansion was empty. "" and '' are tossed away before we get to this point when processing partially quoted strings. This makes "" and $xxx"" equivalent when xxx is unset. We also look to see whether we saw a quoted null from a ${} expansion and add one back if we need to. */ /* If we expand to nothing and there were no single or double quotes in the word, we throw it away. Otherwise, we return a NULL word. The single exception is for $@ surrounded by double quotes when there are no positional parameters. In that case, we also throw the word away. */ if (*istring == '\0') { if (quoted_dollar_at == 0 && (had_quoted_null || quoted_state == PARTIALLY_QUOTED)) { istring[0] = CTLNUL; istring[1] = '\0'; tword = make_bare_word (istring); list = make_word_list (tword, (WORD_LIST *)NULL); if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) tword->flags |= W_QUOTED; } /* According to sh, ksh, and Posix.2, if a word expands into nothing and a double-quoted "$@" appears anywhere in it, then the entire word is removed. */ else if (quoted_state == UNQUOTED || quoted_dollar_at) list = (WORD_LIST *)NULL; #if 0 else { tword = make_bare_word (istring); list = make_word_list (tword, (WORD_LIST *)NULL); if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) tword->flags |= W_QUOTED; } #else else list = (WORD_LIST *)NULL; #endif } else if (word->flags & W_NOSPLIT) { tword = make_bare_word (istring); list = make_word_list (tword, (WORD_LIST *)NULL); if (word->flags & W_ASSIGNMENT) tword->flags |= W_ASSIGNMENT; /* XXX */ if (word->flags & W_NOGLOB) tword->flags |= W_NOGLOB; /* XXX */ if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) tword->flags |= W_QUOTED; } else { char *ifs_chars; ifs_chars = (quoted_dollar_at || has_dollar_at) ? ifs_value : (char *)NULL; /* If we have $@, we need to split the results no matter what. If IFS is unset or NULL, string_list_dollar_at has separated the positional parameters with a space, so we split on space (we have set ifs_chars to " \t\n" above if ifs is unset). If IFS is set, string_list_dollar_at has separated the positional parameters with the first character of $IFS, so we split on $IFS. */ if (has_dollar_at && ifs_chars) list = list_string (istring, *ifs_chars ? ifs_chars : " ", 1); else { tword = make_bare_word (istring); list = make_word_list (tword, (WORD_LIST *)NULL); if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (quoted_state == WHOLLY_QUOTED)) tword->flags |= W_QUOTED; if (word->flags & W_ASSIGNMENT) tword->flags |= W_ASSIGNMENT; if (word->flags & W_NOGLOB) tword->flags |= W_NOGLOB; } } free (istring); return (list); } /* **************************************************************** */ /* */ /* Functions for Quote Removal */ /* */ /* **************************************************************** */ /* Perform quote removal on STRING. If QUOTED > 0, assume we are obeying the backslash quoting rules for within double quotes or a here document. */ char * string_quote_removal (string, quoted) char *string; int quoted; { size_t slen; char *r, *result_string, *temp, *send; int sindex, tindex, dquote; unsigned char c; DECLARE_MBSTATE; /* The result can be no longer than the original string. */ slen = strlen (string); send = string + slen; r = result_string = (char *)xmalloc (slen + 1); for (dquote = sindex = 0; c = string[sindex];) { switch (c) { case '\\': c = string[++sindex]; if (((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) && (sh_syntaxtab[c] & CBSDQUOTE) == 0) *r++ = '\\'; /* FALLTHROUGH */ default: SCOPY_CHAR_M (r, string, send, sindex); break; case '\'': if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) { *r++ = c; sindex++; break; } tindex = sindex + 1; temp = string_extract_single_quoted (string, &tindex); if (temp) { strcpy (r, temp); r += strlen (r); free (temp); } sindex = tindex; break; case '"': dquote = 1 - dquote; sindex++; break; } } *r = '\0'; return (result_string); } #if 0 /* UNUSED */ /* Perform quote removal on word WORD. This allocates and returns a new WORD_DESC *. */ WORD_DESC * word_quote_removal (word, quoted) WORD_DESC *word; int quoted; { WORD_DESC *w; char *t; t = string_quote_removal (word->word, quoted); w = make_bare_word (t); free (t); return (w); } /* Perform quote removal on all words in LIST. If QUOTED is non-zero, the members of the list are treated as if they are surrounded by double quotes. Return a new list, or NULL if LIST is NULL. */ WORD_LIST * word_list_quote_removal (list, quoted) WORD_LIST *list; int quoted; { WORD_LIST *result, *t, *tresult; for (t = list, result = (WORD_LIST *)NULL; t; t = t->next) { tresult = make_word_list (word_quote_removal (t->word, quoted), (WORD_LIST *)NULL); result = (WORD_LIST *) list_append (result, tresult); } return (result); } #endif /******************************************* * * * Functions to perform word splitting * * * *******************************************/ void setifs (v) SHELL_VAR *v; { char *t; unsigned char uc; ifs_var = v; ifs_value = v ? value_cell (v) : " \t\n"; /* Should really merge ifs_cmap with sh_syntaxtab. */ memset (ifs_cmap, '\0', sizeof (ifs_cmap)); for (t = ifs_value ; t && *t; t++) { uc = *t; ifs_cmap[uc] = 1; } ifs_firstc = ifs_value ? *ifs_value : 0; } char * getifs () { return ifs_value; } /* This splits a single word into a WORD LIST on $IFS, but only if the word is not quoted. list_string () performs quote removal for us, even if we don't do any splitting. */ WORD_LIST * word_split (w, ifs_chars) WORD_DESC *w; char *ifs_chars; { WORD_LIST *result; if (w) { char *xifs; xifs = ((w->flags & W_QUOTED) || ifs_chars == 0) ? "" : ifs_chars; result = list_string (w->word, xifs, w->flags & W_QUOTED); } else result = (WORD_LIST *)NULL; return (result); } /* Perform word splitting on LIST and return the RESULT. It is possible to return (WORD_LIST *)NULL. */ static WORD_LIST * word_list_split (list) WORD_LIST *list; { WORD_LIST *result, *t, *tresult; for (t = list, result = (WORD_LIST *)NULL; t; t = t->next) { tresult = word_split (t->word, ifs_value); result = (WORD_LIST *) list_append (result, tresult); } return (result); } /************************************************** * * * Functions to expand an entire WORD_LIST * * * **************************************************/ /* Put NLIST (which is a WORD_LIST * of only one element) at the front of ELIST, and set ELIST to the new list. */ #define PREPEND_LIST(nlist, elist) \ do { nlist->next = elist; elist = nlist; } while (0) /* Separate out any initial variable assignments from TLIST. If set -k has been executed, remove all assignment statements from TLIST. Initial variable assignments and other environment assignments are placed on SUBST_ASSIGN_VARLIST. */ static WORD_LIST * separate_out_assignments (tlist) WORD_LIST *tlist; { register WORD_LIST *vp, *lp; if (!tlist) return ((WORD_LIST *)NULL); if (subst_assign_varlist) dispose_words (subst_assign_varlist); /* Clean up after previous error */ subst_assign_varlist = (WORD_LIST *)NULL; vp = lp = tlist; /* Separate out variable assignments at the start of the command. Loop invariant: vp->next == lp Loop postcondition: lp = list of words left after assignment statements skipped tlist = original list of words */ while (lp && (lp->word->flags & W_ASSIGNMENT)) { vp = lp; lp = lp->next; } /* If lp != tlist, we have some initial assignment statements. We make SUBST_ASSIGN_VARLIST point to the list of assignment words and TLIST point to the remaining words. */ if (lp != tlist) { subst_assign_varlist = tlist; /* ASSERT(vp->next == lp); */ vp->next = (WORD_LIST *)NULL; /* terminate variable list */ tlist = lp; /* remainder of word list */ } /* vp == end of variable list */ /* tlist == remainder of original word list without variable assignments */ if (!tlist) /* All the words in tlist were assignment statements */ return ((WORD_LIST *)NULL); /* ASSERT(tlist != NULL); */ /* ASSERT((tlist->word->flags & W_ASSIGNMENT) == 0); */ /* If the -k option is in effect, we need to go through the remaining words, separate out the assignment words, and place them on SUBST_ASSIGN_VARLIST. */ if (place_keywords_in_env) { WORD_LIST *tp; /* tp == running pointer into tlist */ tp = tlist; lp = tlist->next; /* Loop Invariant: tp->next == lp */ /* Loop postcondition: tlist == word list without assignment statements */ while (lp) { if (lp->word->flags & W_ASSIGNMENT) { /* Found an assignment statement, add this word to end of subst_assign_varlist (vp). */ if (!subst_assign_varlist) subst_assign_varlist = vp = lp; else { vp->next = lp; vp = lp; } /* Remove the word pointed to by LP from TLIST. */ tp->next = lp->next; /* ASSERT(vp == lp); */ lp->next = (WORD_LIST *)NULL; lp = tp->next; } else { tp = lp; lp = lp->next; } } } return (tlist); } #define WEXP_VARASSIGN 0x001 #define WEXP_BRACEEXP 0x002 #define WEXP_TILDEEXP 0x004 #define WEXP_PARAMEXP 0x008 #define WEXP_PATHEXP 0x010 /* All of the expansions, including variable assignments at the start of the list. */ #define WEXP_ALL (WEXP_VARASSIGN|WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP|WEXP_PATHEXP) /* All of the expansions except variable assignments at the start of the list. */ #define WEXP_NOVARS (WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP|WEXP_PATHEXP) /* All of the `shell expansions': brace expansion, tilde expansion, parameter expansion, command substitution, arithmetic expansion, word splitting, and quote removal. */ #define WEXP_SHELLEXP (WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP) /* Take the list of words in LIST and do the various substitutions. Return a new list of words which is the expanded list, and without things like variable assignments. */ WORD_LIST * expand_words (list) WORD_LIST *list; { return (expand_word_list_internal (list, WEXP_ALL)); } /* Same as expand_words (), but doesn't hack variable or environment variables. */ WORD_LIST * expand_words_no_vars (list) WORD_LIST *list; { return (expand_word_list_internal (list, WEXP_NOVARS)); } WORD_LIST * expand_words_shellexp (list) WORD_LIST *list; { return (expand_word_list_internal (list, WEXP_SHELLEXP)); } static WORD_LIST * glob_expand_word_list (tlist, eflags) WORD_LIST *tlist; int eflags; { char **glob_array, *temp_string; register int glob_index; WORD_LIST *glob_list, *output_list, *disposables, *next; WORD_DESC *tword; output_list = disposables = (WORD_LIST *)NULL; glob_array = (char **)NULL; while (tlist) { /* For each word, either globbing is attempted or the word is added to orig_list. If globbing succeeds, the results are added to orig_list and the word (tlist) is added to the list of disposable words. If globbing fails and failed glob expansions are left unchanged (the shell default), the original word is added to orig_list. If globbing fails and failed glob expansions are removed, the original word is added to the list of disposable words. orig_list ends up in reverse order and requires a call to REVERSE_LIST to be set right. After all words are examined, the disposable words are freed. */ next = tlist->next; /* If the word isn't an assignment and contains an unquoted pattern matching character, then glob it. */ if ((tlist->word->flags & W_NOGLOB) == 0 && unquoted_glob_pattern_p (tlist->word->word)) { glob_array = shell_glob_filename (tlist->word->word); /* Handle error cases. I don't think we should report errors like "No such file or directory". However, I would like to report errors like "Read failed". */ if (GLOB_FAILED (glob_array)) { glob_array = (char **)xmalloc (sizeof (char *)); glob_array[0] = (char *)NULL; } /* Dequote the current word in case we have to use it. */ if (glob_array[0] == NULL) { temp_string = dequote_string (tlist->word->word); free (tlist->word->word); tlist->word->word = temp_string; } /* Make the array into a word list. */ glob_list = (WORD_LIST *)NULL; for (glob_index = 0; glob_array[glob_index]; glob_index++) { tword = make_bare_word (glob_array[glob_index]); tword->flags |= W_GLOBEXP; /* XXX */ glob_list = make_word_list (tword, glob_list); } if (glob_list) { output_list = (WORD_LIST *)list_append (glob_list, output_list); PREPEND_LIST (tlist, disposables); } else if (allow_null_glob_expansion == 0) { /* Failed glob expressions are left unchanged. */ PREPEND_LIST (tlist, output_list); } else { /* Failed glob expressions are removed. */ PREPEND_LIST (tlist, disposables); } } else { /* Dequote the string. */ temp_string = dequote_string (tlist->word->word); free (tlist->word->word); tlist->word->word = temp_string; PREPEND_LIST (tlist, output_list); } strvec_dispose (glob_array); glob_array = (char **)NULL; tlist = next; } if (disposables) dispose_words (disposables); if (output_list) output_list = REVERSE_LIST (output_list, WORD_LIST *); return (output_list); } #if defined (BRACE_EXPANSION) static WORD_LIST * brace_expand_word_list (tlist, eflags) WORD_LIST *tlist; int eflags; { register char **expansions; char *temp_string; WORD_LIST *disposables, *output_list, *next; WORD_DESC *w; int eindex; for (disposables = output_list = (WORD_LIST *)NULL; tlist; tlist = next) { next = tlist->next; /* Only do brace expansion if the word has a brace character. If not, just add the word list element to BRACES and continue. In the common case, at least when running shell scripts, this will degenerate to a bunch of calls to `xstrchr', and then what is basically a reversal of TLIST into BRACES, which is corrected by a call to REVERSE_LIST () on BRACES when the end of TLIST is reached. */ if (xstrchr (tlist->word->word, LBRACE)) { expansions = brace_expand (tlist->word->word); for (eindex = 0; temp_string = expansions[eindex]; eindex++) { w = make_word (temp_string); /* If brace expansion didn't change the word, preserve the flags. We may want to preserve the flags unconditionally someday -- XXX */ if (STREQ (temp_string, tlist->word->word)) w->flags = tlist->word->flags; output_list = make_word_list (w, output_list); free (expansions[eindex]); } free (expansions); /* Add TLIST to the list of words to be freed after brace expansion has been performed. */ PREPEND_LIST (tlist, disposables); } else PREPEND_LIST (tlist, output_list); } if (disposables) dispose_words (disposables); if (output_list) output_list = REVERSE_LIST (output_list, WORD_LIST *); return (output_list); } #endif static WORD_LIST * shell_expand_word_list (tlist, eflags) WORD_LIST *tlist; int eflags; { WORD_LIST *expanded, *orig_list, *new_list, *next, *temp_list; int expanded_something, has_dollar_at; char *temp_string; /* We do tilde expansion all the time. This is what 1003.2 says. */ new_list = (WORD_LIST *)NULL; for (orig_list = tlist; tlist; tlist = next) { temp_string = tlist->word->word; next = tlist->next; /* Posix.2 section 3.6.1 says that tildes following `=' in words which are not assignment statements are not expanded. If the shell isn't in posix mode, though, we perform tilde expansion on `likely candidate' unquoted assignment statements (flags include W_ASSIGNMENT but not W_QUOTED). A likely candidate contains an unquoted :~ or =~. Something to think about: we now have a flag that says to perform tilde expansion on arguments to `assignment builtins' like declare and export that look like assignment statements. We now do tilde expansion on such words even in POSIX mode. */ if (((tlist->word->flags & (W_ASSIGNMENT|W_QUOTED)) == W_ASSIGNMENT) && (posixly_correct == 0 || (tlist->word->flags & W_TILDEEXP)) && (unquoted_substring ("=~", temp_string) || unquoted_substring (":~", temp_string))) { tlist->word->word = bash_tilde_expand (temp_string, 1); free (temp_string); } else if (temp_string[0] == '~') { tlist->word->word = bash_tilde_expand (temp_string, 0); free (temp_string); } expanded_something = 0; expanded = expand_word_internal (tlist->word, 0, 0, &has_dollar_at, &expanded_something); if (expanded == &expand_word_error || expanded == &expand_word_fatal) { /* By convention, each time this error is returned, tlist->word->word has already been freed. */ tlist->word->word = (char *)NULL; /* Dispose our copy of the original list. */ dispose_words (orig_list); /* Dispose the new list we're building. */ dispose_words (new_list); last_command_exit_value = EXECUTION_FAILURE; if (expanded == &expand_word_error) jump_to_top_level (DISCARD); else jump_to_top_level (FORCE_EOF); } /* Don't split words marked W_NOSPLIT. */ if (expanded_something && (tlist->word->flags & W_NOSPLIT) == 0) { temp_list = word_list_split (expanded); dispose_words (expanded); } else { /* If no parameter expansion, command substitution, process substitution, or arithmetic substitution took place, then do not do word splitting. We still have to remove quoted null characters from the result. */ word_list_remove_quoted_nulls (expanded); temp_list = expanded; } expanded = REVERSE_LIST (temp_list, WORD_LIST *); new_list = (WORD_LIST *)list_append (expanded, new_list); } if (orig_list) dispose_words (orig_list); if (new_list) new_list = REVERSE_LIST (new_list, WORD_LIST *); return (new_list); } /* The workhorse for expand_words () and expand_words_no_vars (). First arg is LIST, a WORD_LIST of words. Second arg EFLAGS is a flags word controlling which expansions are performed. This does all of the substitutions: brace expansion, tilde expansion, parameter expansion, command substitution, arithmetic expansion, process substitution, word splitting, and pathname expansion, according to the bits set in EFLAGS. Words with the W_QUOTED or W_NOSPLIT bits set, or for which no expansion is done, do not undergo word splitting. Words with the W_NOGLOB bit set do not undergo pathname expansion. */ static WORD_LIST * expand_word_list_internal (list, eflags) WORD_LIST *list; int eflags; { WORD_LIST *new_list, *temp_list; int tint; if (list == 0) return ((WORD_LIST *)NULL); garglist = new_list = copy_word_list (list); if (eflags & WEXP_VARASSIGN) { garglist = new_list = separate_out_assignments (new_list); if (new_list == 0) { if (subst_assign_varlist) { /* All the words were variable assignments, so they are placed into the shell's environment. */ for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next) { this_command_name = (char *)NULL; /* no arithmetic errors */ tint = do_assignment (temp_list->word->word); /* Variable assignment errors in non-interactive shells running in Posix.2 mode cause the shell to exit. */ if (tint == 0) { last_command_exit_value = EXECUTION_FAILURE; if (interactive_shell == 0 && posixly_correct) jump_to_top_level (FORCE_EOF); else jump_to_top_level (DISCARD); } } dispose_words (subst_assign_varlist); subst_assign_varlist = (WORD_LIST *)NULL; } return ((WORD_LIST *)NULL); } } /* Begin expanding the words that remain. The expansions take place on things that aren't really variable assignments. */ #if defined (BRACE_EXPANSION) /* Do brace expansion on this word if there are any brace characters in the string. */ if ((eflags & WEXP_BRACEEXP) && brace_expansion && new_list) new_list = brace_expand_word_list (new_list, eflags); #endif /* BRACE_EXPANSION */ /* Perform the `normal' shell expansions: tilde expansion, parameter and variable substitution, command substitution, arithmetic expansion, and word splitting. */ new_list = shell_expand_word_list (new_list, eflags); /* Okay, we're almost done. Now let's just do some filename globbing. */ if (new_list) { if ((eflags & WEXP_PATHEXP) && disallow_filename_globbing == 0) /* Glob expand the word list unless globbing has been disabled. */ new_list = glob_expand_word_list (new_list, eflags); else /* Dequote the words, because we're not performing globbing. */ new_list = dequote_list (new_list); } if ((eflags & WEXP_VARASSIGN) && subst_assign_varlist) { sh_assign_func_t *assign_func; /* If the remainder of the words expand to nothing, Posix.2 requires that the variable and environment assignments affect the shell's environment. */ assign_func = new_list ? assign_in_env : do_assignment; for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next) { this_command_name = (char *)NULL; tint = (*assign_func) (temp_list->word->word); /* Variable assignment errors in non-interactive shells running in Posix.2 mode cause the shell to exit. */ if (tint == 0 && assign_func == do_assignment) { last_command_exit_value = EXECUTION_FAILURE; if (interactive_shell == 0 && posixly_correct) jump_to_top_level (FORCE_EOF); else jump_to_top_level (DISCARD); } } dispose_words (subst_assign_varlist); subst_assign_varlist = (WORD_LIST *)NULL; } #if 0 tint = list_length (new_list) + 1; RESIZE_MALLOCED_BUFFER (glob_argv_flags, 0, tint, glob_argv_flags_size, 16); for (tint = 0, temp_list = new_list; temp_list; temp_list = temp_list->next) glob_argv_flags[tint++] = (temp_list->word->flags & W_GLOBEXP) ? '1' : '0'; glob_argv_flags[tint] = '\0'; #endif return (new_list); }
/* hashcmd.c - functions for managing a hash table mapping command names to full pathnames. */ /* Copyright (C) 1997-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #include "bashtypes.h" #include "posixstat.h" #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "bashansi.h" #include "shell.h" #include "findcmd.h" #include "hashcmd.h" extern int hashing_enabled; HASH_TABLE *hashed_filenames = (HASH_TABLE *)NULL; static void phash_freedata __P((PTR_T)); void phash_create () { if (hashed_filenames == 0) hashed_filenames = hash_create (FILENAME_HASH_BUCKETS); } static void phash_freedata (data) PTR_T data; { free (((PATH_DATA *)data)->path); free (data); } void phash_flush () { if (hashed_filenames) hash_flush (hashed_filenames, phash_freedata); } /* Remove FILENAME from the table of hashed commands. */ int phash_remove (filename) const char *filename; { register BUCKET_CONTENTS *item; if (hashing_enabled == 0 || hashed_filenames == 0) return 0; item = hash_remove (filename, hashed_filenames, 0); if (item) { if (item->data) phash_freedata (item->data); free (item->key); free (item); return 0; } return 1; } /* Place FILENAME (key) and FULL_PATH (data->path) into the hash table. CHECK_DOT if non-null is for future calls to phash_search (); it means that this file was found in a directory in $PATH that is not an absolute pathname. FOUND is the initial value for times_found. */ void phash_insert (filename, full_path, check_dot, found) char *filename, *full_path; int check_dot, found; { register BUCKET_CONTENTS *item; if (hashing_enabled == 0) return; if (hashed_filenames == 0) phash_create (); item = hash_insert (filename, hashed_filenames, 0); if (item->data) free (pathdata(item)->path); else { item->key = savestring (filename); item->data = xmalloc (sizeof (PATH_DATA)); } pathdata(item)->path = savestring (full_path); pathdata(item)->flags = 0; if (check_dot) pathdata(item)->flags |= HASH_CHKDOT; if (*full_path != '/') pathdata(item)->flags |= HASH_RELPATH; item->times_found = found; } /* Return the full pathname that FILENAME hashes to. If FILENAME is hashed, but (data->flags & HASH_CHKDOT) is non-zero, check ./FILENAME and return that if it is executable. This always returns a newly-allocated string; the caller is responsible for freeing it. */ char * phash_search (filename) const char *filename; { register BUCKET_CONTENTS *item; char *path, *dotted_filename, *tail; int same; if (hashing_enabled == 0 || hashed_filenames == 0) return ((char *)NULL); item = hash_search (filename, hashed_filenames, 0); if (item == NULL) return ((char *)NULL); /* If this filename is hashed, but `.' comes before it in the path, see if ./filename is executable. If the hashed value is not an absolute pathname, see if ./`hashed-value' exists. */ path = pathdata(item)->path; if (pathdata(item)->flags & (HASH_CHKDOT|HASH_RELPATH)) { tail = (pathdata(item)->flags & HASH_RELPATH) ? path : (char *)filename; /* XXX - fix const later */ /* If the pathname does not start with a `./', add a `./' to it. */ if (tail[0] != '.' || tail[1] != '/') { dotted_filename = (char *)xmalloc (3 + strlen (tail)); dotted_filename[0] = '.'; dotted_filename[1] = '/'; strcpy (dotted_filename + 2, tail); } else dotted_filename = savestring (tail); if (executable_file (dotted_filename)) return (dotted_filename); free (dotted_filename); #if 0 if (pathdata(item)->flags & HASH_RELPATH) return ((char *)NULL); #endif /* Watch out. If this file was hashed to "./filename", and "./filename" is not executable, then return NULL. */ /* Since we already know "./filename" is not executable, what we're really interested in is whether or not the `path' portion of the hashed filename is equivalent to the current directory, but only if it starts with a `.'. (This catches ./. and so on.) same_file () tests general Unix file equivalence -- same device and inode. */ if (*path == '.') { same = 0; tail = (char *)strrchr (path, '/'); if (tail) { *tail = '\0'; same = same_file (".", path, (struct stat *)NULL, (struct stat *)NULL); *tail = '/'; } return same ? (char *)NULL : savestring (path); } } return (savestring (path)); }
/* hashlib.c -- functions to manage and access hash tables for bash. */ /* Copyright (C) 1987, 1989, 1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #include "bashansi.h" #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include <stdio.h> #include "shell.h" #include "hashlib.h" /* Rely on properties of unsigned division (unsigned/int -> unsigned) and don't discard the upper 32 bits of the value, if present. */ #define HASH_BUCKET(s, t, h) (((h) = hash_string (s)) & ((t)->nbuckets - 1)) static BUCKET_CONTENTS *copy_bucket_array __P((BUCKET_CONTENTS *, sh_string_func_t *)); /* Make a new hash table with BUCKETS number of buckets. Initialize each slot in the table to NULL. */ HASH_TABLE * hash_create (buckets) int buckets; { HASH_TABLE *new_table; register int i; new_table = (HASH_TABLE *)xmalloc (sizeof (HASH_TABLE)); if (buckets == 0) buckets = DEFAULT_HASH_BUCKETS; new_table->bucket_array = (BUCKET_CONTENTS **)xmalloc (buckets * sizeof (BUCKET_CONTENTS *)); new_table->nbuckets = buckets; new_table->nentries = 0; for (i = 0; i < buckets; i++) new_table->bucket_array[i] = (BUCKET_CONTENTS *)NULL; return (new_table); } int hash_size (table) HASH_TABLE *table; { return (HASH_ENTRIES(table)); } static BUCKET_CONTENTS * copy_bucket_array (ba, cpdata) BUCKET_CONTENTS *ba; sh_string_func_t *cpdata; /* data copy function */ { BUCKET_CONTENTS *new_bucket, *n, *e; if (ba == 0) return ((BUCKET_CONTENTS *)0); for (n = (BUCKET_CONTENTS *)0, e = ba; e; e = e->next) { if (n == 0) { new_bucket = (BUCKET_CONTENTS *)xmalloc (sizeof (BUCKET_CONTENTS)); n = new_bucket; } else { n->next = (BUCKET_CONTENTS *)xmalloc (sizeof (BUCKET_CONTENTS)); n = n->next; } n->key = savestring (e->key); n->data = e->data ? (cpdata ? (*cpdata) (e->data) : savestring (e->data)) : NULL; n->khash = e->khash; n->times_found = e->times_found; n->next = (BUCKET_CONTENTS *)NULL; } return new_bucket; } HASH_TABLE * hash_copy (table, cpdata) HASH_TABLE *table; sh_string_func_t *cpdata; { HASH_TABLE *new_table; int i; if (table == 0) return ((HASH_TABLE *)NULL); new_table = hash_create (table->nbuckets); for (i = 0; i < table->nbuckets; i++) new_table->bucket_array[i] = copy_bucket_array (table->bucket_array[i], cpdata); new_table->nentries = table->nentries; return new_table; } /* The `khash' check below requires that strings that compare equally with strcmp hash to the same value. */ unsigned int hash_string (s) const char *s; { register unsigned int i; /* This is the best string hash function I found. The magic is in the interesting relationship between the special prime 16777619 (2^24 + 403) and 2^32 and 2^8. */ for (i = 0; *s; s++) { i *= 16777619; i ^= *s; } return i; } /* Return the location of the bucket which should contain the data for STRING. TABLE is a pointer to a HASH_TABLE. */ int hash_bucket (string, table) const char *string; HASH_TABLE *table; { unsigned int h; return (HASH_BUCKET (string, table, h)); } /* Return a pointer to the hashed item. If the HASH_CREATE flag is passed, create a new hash table entry for STRING, otherwise return NULL. */ BUCKET_CONTENTS * hash_search (string, table, flags) const char *string; HASH_TABLE *table; int flags; { BUCKET_CONTENTS *list; int bucket; unsigned int hv; if (table == 0 || ((flags & HASH_CREATE) == 0 && HASH_ENTRIES (table) == 0)) return (BUCKET_CONTENTS *)NULL; bucket = HASH_BUCKET (string, table, hv); for (list = table->bucket_array[bucket]; list; list = list->next) { if (hv == list->khash && STREQ (list->key, string)) { list->times_found++; return (list); } } if (flags & HASH_CREATE) { list = (BUCKET_CONTENTS *)xmalloc (sizeof (BUCKET_CONTENTS)); list->next = table->bucket_array[bucket]; table->bucket_array[bucket] = list; list->data = NULL; list->key = (char *)string; /* XXX fix later */ list->khash = hv; list->times_found = 0; table->nentries++; return (list); } return (BUCKET_CONTENTS *)NULL; } /* Remove the item specified by STRING from the hash table TABLE. The item removed is returned, so you can free its contents. If the item isn't in this table NULL is returned. */ BUCKET_CONTENTS * hash_remove (string, table, flags) const char *string; HASH_TABLE *table; int flags; { int bucket; BUCKET_CONTENTS *prev, *temp; unsigned int hv; if (table == 0 || HASH_ENTRIES (table) == 0) return (BUCKET_CONTENTS *)NULL; bucket = HASH_BUCKET (string, table, hv); prev = (BUCKET_CONTENTS *)NULL; for (temp = table->bucket_array[bucket]; temp; temp = temp->next) { if (hv == temp->khash && STREQ (temp->key, string)) { if (prev) prev->next = temp->next; else table->bucket_array[bucket] = temp->next; table->nentries--; return (temp); } prev = temp; } return ((BUCKET_CONTENTS *) NULL); } /* Create an entry for STRING, in TABLE. If the entry already exists, then return it (unless the HASH_NOSRCH flag is set). */ BUCKET_CONTENTS * hash_insert (string, table, flags) char *string; HASH_TABLE *table; int flags; { BUCKET_CONTENTS *item; int bucket; unsigned int hv; if (table == 0) table = hash_create (0); item = (flags & HASH_NOSRCH) ? (BUCKET_CONTENTS *)NULL : hash_search (string, table, 0); if (item == 0) { bucket = HASH_BUCKET (string, table, hv); item = (BUCKET_CONTENTS *)xmalloc (sizeof (BUCKET_CONTENTS)); item->next = table->bucket_array[bucket]; table->bucket_array[bucket] = item; item->data = NULL; item->key = string; item->khash = hv; item->times_found = 0; table->nentries++; } return (item); } /* Remove and discard all entries in TABLE. If FREE_DATA is non-null, it is a function to call to dispose of a hash item's data. Otherwise, free() is called. */ void hash_flush (table, free_data) HASH_TABLE *table; sh_free_func_t *free_data; { int i; register BUCKET_CONTENTS *bucket, *item; if (table == 0 || HASH_ENTRIES (table) == 0) return; for (i = 0; i < table->nbuckets; i++) { bucket = table->bucket_array[i]; while (bucket) { item = bucket; bucket = bucket->next; if (free_data) (*free_data) (item->data); else free (item->data); free (item->key); free (item); } table->bucket_array[i] = (BUCKET_CONTENTS *)NULL; } table->nentries = 0; } /* Free the hash table pointed to by TABLE. */ void hash_dispose (table) HASH_TABLE *table; { free (table->bucket_array); free (table); } void hash_walk (table, func) HASH_TABLE *table; hash_wfunc *func; { register int i; BUCKET_CONTENTS *item; if (table == 0 || HASH_ENTRIES (table) == 0) return; for (i = 0; i < table->nbuckets; i++) { for (item = hash_items (i, table); item; item = item->next) if ((*func) (item) < 0) return; } } #if defined (DEBUG) || defined (TEST_HASHING) void hash_pstats (table, name) HASH_TABLE *table; char *name; { register int slot, bcount; register BUCKET_CONTENTS *bc; if (name == 0) name = "unknown hash table"; fprintf (stderr, "%s: %d buckets; %d items\n", name, table->nbuckets, table->nentries); /* Print out a count of how many strings hashed to each bucket, so we can see how even the distribution is. */ for (slot = 0; slot < table->nbuckets; slot++) { bc = hash_items (slot, table); fprintf (stderr, "\tslot %3d: ", slot); for (bcount = 0; bc; bc = bc->next) bcount++; fprintf (stderr, "%d\n", bcount); } } #endif #ifdef TEST_HASHING /* link with xmalloc.o and lib/malloc/libmalloc.a */ #undef NULL #include <stdio.h> #ifndef NULL #define NULL 0 #endif HASH_TABLE *table, *ntable; int interrupt_immediately = 0; int signal_is_trapped (s) int s; { return (0); } void programming_error (const char *format, ...) { abort(); } void fatal_error (const char *format, ...) { abort(); } main () { char string[256]; int count = 0; BUCKET_CONTENTS *tt; table = hash_create (0); for (;;) { char *temp_string; if (fgets (string, sizeof (string), stdin) == 0) break; if (!*string) break; temp_string = savestring (string); tt = hash_insert (temp_string, table, 0); if (tt->times_found) { fprintf (stderr, "You have already added item `%s'\n", string); free (temp_string); } else { count++; } } hash_pstats (table, "hash test"); ntable = hash_copy (table, (sh_string_func_t *)NULL); hash_flush (table, (sh_free_func_t *)NULL); hash_pstats (ntable, "hash copy test"); exit (0); } #endif /* TEST_HASHING */
/* mailcheck.c -- The check is in the mail... */ /* Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #include <stdio.h> #include "bashtypes.h" #include "posixstat.h" #ifndef _MINIX # include <sys/param.h> #endif #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "posixtime.h" #include "bashansi.h" #include "shell.h" #include "execute_cmd.h" #include "mailcheck.h" #include <tilde/tilde.h> extern int mailstat __P((const char *, struct stat *)); typedef struct { char *name; char *msg; time_t access_time; time_t mod_time; off_t file_size; } FILEINFO; /* The list of remembered mail files. */ static FILEINFO **mailfiles = (FILEINFO **)NULL; /* Number of mail files that we have. */ static int mailfiles_count; /* The last known time that mail was checked. */ static time_t last_time_mail_checked; /* Non-zero means warn if a mail file has been read since last checked. */ int mail_warning; static int find_mail_file __P((char *)); static void update_mail_file __P((int)); static int add_mail_file __P((char *, char *)); static int file_mod_date_changed __P((int)); static int file_access_date_changed __P((int)); static int file_has_grown __P((int)); static char *parse_mailpath_spec __P((char *)); /* Returns non-zero if it is time to check mail. */ int time_to_check_mail () { char *temp; time_t now; intmax_t seconds; temp = get_string_value ("MAILCHECK"); /* Negative number, or non-numbers (such as empty string) cause no checking to take place. */ if (temp == 0 || legal_number (temp, &seconds) == 0 || seconds < 0) return (0); now = NOW; /* Time to check if MAILCHECK is explicitly set to zero, or if enough time has passed since the last check. */ return (seconds == 0 || ((now - last_time_mail_checked) >= seconds)); } /* Okay, we have checked the mail. Perhaps I should make this function go away. */ void reset_mail_timer () { last_time_mail_checked = NOW; } /* Locate a file in the list. Return index of entry, or -1 if not found. */ static int find_mail_file (file) char *file; { register int i; for (i = 0; i < mailfiles_count; i++) if (STREQ (mailfiles[i]->name, file)) return i; return -1; } #define RESET_MAIL_FILE(i) \ do \ { \ mailfiles[i]->access_time = mailfiles[i]->mod_time = 0; \ mailfiles[i]->file_size = 0; \ } \ while (0) static void update_mail_file (i) int i; { char *file; struct stat finfo; file = mailfiles[i]->name; if (mailstat (file, &finfo) == 0) { mailfiles[i]->access_time = finfo.st_atime; mailfiles[i]->mod_time = finfo.st_mtime; mailfiles[i]->file_size = finfo.st_size; } else RESET_MAIL_FILE (i); } /* Add this file to the list of remembered files and return its index in the list of mail files. */ static int add_mail_file (file, msg) char *file, *msg; { struct stat finfo; char *filename; int i; filename = full_pathname (file); i = find_mail_file (filename); if (i >= 0) { if (mailstat (filename, &finfo) == 0) { mailfiles[i]->mod_time = finfo.st_mtime; mailfiles[i]->access_time = finfo.st_atime; mailfiles[i]->file_size = finfo.st_size; } free (filename); return i; } i = mailfiles_count++; mailfiles = (FILEINFO **)xrealloc (mailfiles, mailfiles_count * sizeof (FILEINFO *)); mailfiles[i] = (FILEINFO *)xmalloc (sizeof (FILEINFO)); mailfiles[i]->name = filename; mailfiles[i]->msg = msg ? savestring (msg) : (char *)NULL; update_mail_file (i); return i; } /* Reset the existing mail files access and modification times to zero. */ void reset_mail_files () { register int i; for (i = 0; i < mailfiles_count; i++) { RESET_MAIL_FILE (i); } } /* Free the information that we have about the remembered mail files. */ void free_mail_files () { register int i; for (i = 0; i < mailfiles_count; i++) { free (mailfiles[i]->name); FREE (mailfiles[i]->msg); free (mailfiles[i]); } if (mailfiles) free (mailfiles); mailfiles_count = 0; mailfiles = (FILEINFO **)NULL; } /* Return non-zero if FILE's mod date has changed and it has not been accessed since modified. */ static int file_mod_date_changed (i) int i; { time_t mtime; struct stat finfo; char *file; file = mailfiles[i]->name; mtime = mailfiles[i]->mod_time; if ((mailstat (file, &finfo) == 0) && (finfo.st_size > 0)) return (mtime != finfo.st_mtime); return (0); } /* Return non-zero if FILE's access date has changed. */ static int file_access_date_changed (i) int i; { time_t atime; struct stat finfo; char *file; file = mailfiles[i]->name; atime = mailfiles[i]->access_time; if ((mailstat (file, &finfo) == 0) && (finfo.st_size > 0)) return (atime != finfo.st_atime); return (0); } /* Return non-zero if FILE's size has increased. */ static int file_has_grown (i) int i; { off_t size; struct stat finfo; char *file; file = mailfiles[i]->name; size = mailfiles[i]->file_size; return ((mailstat (file, &finfo) == 0) && (finfo.st_size > size)); } /* Take an element from $MAILPATH and return the portion from the first unquoted `?' or `%' to the end of the string. This is the message to be printed when the file contents change. */ static char * parse_mailpath_spec (str) char *str; { char *s; int pass_next; for (s = str, pass_next = 0; s && *s; s++) { if (pass_next) { pass_next = 0; continue; } if (*s == '\\') { pass_next++; continue; } if (*s == '?' || *s == '%') return s; } return ((char *)NULL); } char * make_default_mailpath () { char *mp; get_current_user_info (); mp = (char *)xmalloc (2 + sizeof (DEFAULT_MAIL_DIRECTORY) + strlen (current_user.user_name)); strcpy (mp, DEFAULT_MAIL_DIRECTORY); mp[sizeof(DEFAULT_MAIL_DIRECTORY) - 1] = '/'; strcpy (mp + sizeof (DEFAULT_MAIL_DIRECTORY), current_user.user_name); return (mp); } /* Remember the dates of the files specified by MAILPATH, or if there is no MAILPATH, by the file specified in MAIL. If neither exists, use a default value, which we randomly concoct from using Unix. */ void remember_mail_dates () { char *mailpaths; char *mailfile, *mp; int i = 0; mailpaths = get_string_value ("MAILPATH"); /* If no $MAILPATH, but $MAIL, use that as a single filename to check. */ if (mailpaths == 0 && (mailpaths = get_string_value ("MAIL"))) { add_mail_file (mailpaths, (char *)NULL); return; } if (mailpaths == 0) { mailpaths = make_default_mailpath (); add_mail_file (mailpaths, (char *)NULL); free (mailpaths); return; } while (mailfile = extract_colon_unit (mailpaths, &i)) { mp = parse_mailpath_spec (mailfile); if (mp && *mp) *mp++ = '\0'; add_mail_file (mailfile, mp); free (mailfile); } } /* check_mail () is useful for more than just checking mail. Since it has the paranoids dream ability of telling you when someone has read your mail, it can just as easily be used to tell you when someones .profile file has been read, thus letting one know when someone else has logged in. Pretty good, huh? */ /* Check for mail in some files. If the modification date of any of the files in MAILPATH has changed since we last did a remember_mail_dates () then mention that the user has mail. Special hack: If the variable MAIL_WARNING is non-zero and the mail file has been accessed since the last time we remembered, then the message "The mail in <mailfile> has been read" is printed. */ void check_mail () { char *current_mail_file, *message; int i, use_user_notification; char *dollar_underscore, *temp; dollar_underscore = get_string_value ("_"); if (dollar_underscore) dollar_underscore = savestring (dollar_underscore); for (i = 0; i < mailfiles_count; i++) { current_mail_file = mailfiles[i]->name; if (*current_mail_file == '\0') continue; if (file_mod_date_changed (i)) { int file_is_bigger; use_user_notification = mailfiles[i]->msg != (char *)NULL; message = mailfiles[i]->msg ? mailfiles[i]->msg : "You have mail in $_"; bind_variable ("_", current_mail_file); #define atime mailfiles[i]->access_time #define mtime mailfiles[i]->mod_time /* Have to compute this before the call to update_mail_file, which resets all the information. */ file_is_bigger = file_has_grown (i); update_mail_file (i); /* If the user has just run a program which manipulates the mail file, then don't bother explaining that the mail file has been manipulated. Since some systems don't change the access time to be equal to the modification time when the mail in the file is manipulated, check the size also. If the file has not grown, continue. */ if ((atime >= mtime) || !file_is_bigger) continue; /* If the mod time is later than the access time and the file has grown, note the fact that this is *new* mail. */ if (use_user_notification == 0 && (atime < mtime) && file_is_bigger) message = "You have new mail in $_"; #undef atime #undef mtime if (temp = expand_string_to_string (message, Q_DOUBLE_QUOTES)) { puts (temp); free (temp); } else putchar ('\n'); } if (mail_warning && file_access_date_changed (i)) { update_mail_file (i); printf ("The mail in %s has been read\n", current_mail_file); } } if (dollar_underscore) { bind_variable ("_", dollar_underscore); free (dollar_underscore); } else unbind_variable ("_"); }
/* signames.c -- Create and write `signames.h', which contains an array of signal names. */ /* Copyright (C) 1992 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #include <stdio.h> #include <sys/types.h> #include <signal.h> #if defined (HAVE_STDLIB_H) # include <stdlib.h> #else # include "ansi_stdlib.h" #endif /* HAVE_STDLIB_H */ #if !defined (NSIG) # define NSIG 64 #endif /* * Special traps: * EXIT == 0 * DEBUG == NSIG * ERR == NSIG+1 */ #define LASTSIG NSIG+1 char *signal_names[2 * NSIG + 3]; #define signal_names_size (sizeof(signal_names)/sizeof(signal_names[0])) char *progname; /* AIX 4.3 defines SIGRTMIN and SIGRTMAX as 888 and 999 respectively. I don't want to allocate so much unused space for the intervening signal numbers, so we just punt if SIGRTMAX is past the bounds of the signal_names array (handled in configure). */ #if defined (SIGRTMAX) && defined (UNUSABLE_RT_SIGNALS) # undef SIGRTMAX # undef SIGRTMIN #endif #if defined (SIGRTMAX) || defined (SIGRTMIN) # define RTLEN 14 # define RTLIM 256 #endif void initialize_signames () { register int i; #if defined (SIGRTMAX) || defined (SIGRTMIN) int rtmin, rtmax, rtcnt; #endif for (i = 1; i < signal_names_size; i++) signal_names[i] = (char *)NULL; /* `signal' 0 is what we do on exit. */ signal_names[0] = "EXIT"; /* Place signal names which can be aliases for more common signal names first. This allows (for example) SIGABRT to overwrite SIGLOST. */ /* POSIX 1003.1b-1993 real time signals, but take care of incomplete implementations. Acoording to the standard, both, SIGRTMIN and SIGRTMAX must be defined, SIGRTMIN must be stricly less than SIGRTMAX, and the difference must be at least 7, that is, there must be at least eight distinct real time signals. */ /* The generated signal names are SIGRTMIN, SIGRTMIN+1, ..., SIGRTMIN+x, SIGRTMAX-x, ..., SIGRTMAX-1, SIGRTMAX. If the number of RT signals is odd, there is an extra SIGRTMIN+(x+1). These names are the ones used by ksh and /usr/xpg4/bin/sh on SunOS5. */ #if defined (SIGRTMIN) rtmin = SIGRTMIN; signal_names[rtmin] = "SIGRTMIN"; #endif #if defined (SIGRTMAX) rtmax = SIGRTMAX; signal_names[rtmax] = "SIGRTMAX"; #endif #if defined (SIGRTMAX) && defined (SIGRTMIN) if (rtmax > rtmin) { rtcnt = (rtmax - rtmin - 1) / 2; /* croak if there are too many RT signals */ if (rtcnt >= RTLIM/2) { rtcnt = RTLIM/2-1; fprintf(stderr, "%s: error: more than %i real time signals, fix `%s'\n", progname, RTLIM, progname); } for (i = 1; i <= rtcnt; i++) { signal_names[rtmin+i] = (char *)malloc(RTLEN); if (signal_names[rtmin+i]) sprintf (signal_names[rtmin+i], "SIGRTMIN+%d", i); signal_names[rtmax-i] = (char *)malloc(RTLEN); if (signal_names[rtmax-i]) sprintf (signal_names[rtmax-i], "SIGRTMAX-%d", i); } if (rtcnt < RTLIM/2-1 && rtcnt != (rtmax-rtmin)/2) { /* Need an extra RTMIN signal */ signal_names[rtmin+rtcnt+1] = (char *)malloc(RTLEN); if (signal_names[rtmin+rtcnt+1]) sprintf (signal_names[rtmin+rtcnt+1], "SIGRTMIN+%d", rtcnt+1); } } #endif /* SIGRTMIN && SIGRTMAX */ /* AIX */ #if defined (SIGLOST) /* resource lost (eg, record-lock lost) */ signal_names[SIGLOST] = "SIGLOST"; #endif #if defined (SIGMSG) /* HFT input data pending */ signal_names[SIGMSG] = "SIGMSG"; #endif #if defined (SIGDANGER) /* system crash imminent */ signal_names[SIGDANGER] = "SIGDANGER"; #endif #if defined (SIGMIGRATE) /* migrate process to another CPU */ signal_names[SIGMIGRATE] = "SIGMIGRATE"; #endif #if defined (SIGPRE) /* programming error */ signal_names[SIGPRE] = "SIGPRE"; #endif #if defined (SIGVIRT) /* AIX virtual time alarm */ signal_names[SIGVIRT] = "SIGVIRT"; #endif #if defined (SIGALRM1) /* m:n condition variables */ signal_names[SIGALRM1] = "SIGALRM1"; #endif #if defined (SIGWAITING) /* m:n scheduling */ signal_names[SIGWAITING] = "SIGWAITING"; #endif #if defined (SIGGRANT) /* HFT monitor mode granted */ signal_names[SIGGRANT] = "SIGGRANT"; #endif #if defined (SIGKAP) /* keep alive poll from native keyboard */ signal_names[SIGKAP] = "SIGKAP"; #endif #if defined (SIGRETRACT) /* HFT monitor mode retracted */ signal_names[SIGRETRACT] = "SIGRETRACT"; #endif #if defined (SIGSOUND) /* HFT sound sequence has completed */ signal_names[SIGSOUND] = "SIGSOUND"; #endif #if defined (SIGSAK) /* Secure Attention Key */ signal_names[SIGSAK] = "SIGSAK"; #endif /* SunOS5 */ #if defined (SIGLWP) /* special signal used by thread library */ signal_names[SIGLWP] = "SIGLWP"; #endif #if defined (SIGFREEZE) /* special signal used by CPR */ signal_names[SIGFREEZE] = "SIGFREEZE"; #endif #if defined (SIGTHAW) /* special signal used by CPR */ signal_names[SIGTHAW] = "SIGTHAW"; #endif #if defined (SIGCANCEL) /* thread cancellation signal used by libthread */ signal_names[SIGCANCEL] = "SIGCANCEL"; #endif /* HP-UX */ #if defined (SIGDIL) /* DIL signal (?) */ signal_names[SIGDIL] = "SIGDIL"; #endif /* System V */ #if defined (SIGCLD) /* Like SIGCHLD. */ signal_names[SIGCLD] = "SIGCLD"; #endif #if defined (SIGPWR) /* power state indication */ signal_names[SIGPWR] = "SIGPWR"; #endif #if defined (SIGPOLL) /* Pollable event (for streams) */ signal_names[SIGPOLL] = "SIGPOLL"; #endif /* Unknown */ #if defined (SIGWINDOW) signal_names[SIGWINDOW] = "SIGWINDOW"; #endif /* Common */ #if defined (SIGHUP) /* hangup */ signal_names[SIGHUP] = "SIGHUP"; #endif #if defined (SIGINT) /* interrupt */ signal_names[SIGINT] = "SIGINT"; #endif #if defined (SIGQUIT) /* quit */ signal_names[SIGQUIT] = "SIGQUIT"; #endif #if defined (SIGILL) /* illegal instruction (not reset when caught) */ signal_names[SIGILL] = "SIGILL"; #endif #if defined (SIGTRAP) /* trace trap (not reset when caught) */ signal_names[SIGTRAP] = "SIGTRAP"; #endif #if defined (SIGIOT) /* IOT instruction */ signal_names[SIGIOT] = "SIGIOT"; #endif #if defined (SIGABRT) /* Cause current process to dump core. */ signal_names[SIGABRT] = "SIGABRT"; #endif #if defined (SIGEMT) /* EMT instruction */ signal_names[SIGEMT] = "SIGEMT"; #endif #if defined (SIGFPE) /* floating point exception */ signal_names[SIGFPE] = "SIGFPE"; #endif #if defined (SIGKILL) /* kill (cannot be caught or ignored) */ signal_names[SIGKILL] = "SIGKILL"; #endif #if defined (SIGBUS) /* bus error */ signal_names[SIGBUS] = "SIGBUS"; #endif #if defined (SIGSEGV) /* segmentation violation */ signal_names[SIGSEGV] = "SIGSEGV"; #endif #if defined (SIGSYS) /* bad argument to system call */ signal_names[SIGSYS] = "SIGSYS"; #endif #if defined (SIGPIPE) /* write on a pipe with no one to read it */ signal_names[SIGPIPE] = "SIGPIPE"; #endif #if defined (SIGALRM) /* alarm clock */ signal_names[SIGALRM] = "SIGALRM"; #endif #if defined (SIGTERM) /* software termination signal from kill */ signal_names[SIGTERM] = "SIGTERM"; #endif #if defined (SIGURG) /* urgent condition on IO channel */ signal_names[SIGURG] = "SIGURG"; #endif #if defined (SIGSTOP) /* sendable stop signal not from tty */ signal_names[SIGSTOP] = "SIGSTOP"; #endif #if defined (SIGTSTP) /* stop signal from tty */ signal_names[SIGTSTP] = "SIGTSTP"; #endif #if defined (SIGCONT) /* continue a stopped process */ signal_names[SIGCONT] = "SIGCONT"; #endif #if defined (SIGCHLD) /* to parent on child stop or exit */ signal_names[SIGCHLD] = "SIGCHLD"; #endif #if defined (SIGTTIN) /* to readers pgrp upon background tty read */ signal_names[SIGTTIN] = "SIGTTIN"; #endif #if defined (SIGTTOU) /* like TTIN for output if (tp->t_local<OSTOP) */ signal_names[SIGTTOU] = "SIGTTOU"; #endif #if defined (SIGIO) /* input/output possible signal */ signal_names[SIGIO] = "SIGIO"; #endif #if defined (SIGXCPU) /* exceeded CPU time limit */ signal_names[SIGXCPU] = "SIGXCPU"; #endif #if defined (SIGXFSZ) /* exceeded file size limit */ signal_names[SIGXFSZ] = "SIGXFSZ"; #endif #if defined (SIGVTALRM) /* virtual time alarm */ signal_names[SIGVTALRM] = "SIGVTALRM"; #endif #if defined (SIGPROF) /* profiling time alarm */ signal_names[SIGPROF] = "SIGPROF"; #endif #if defined (SIGWINCH) /* window changed */ signal_names[SIGWINCH] = "SIGWINCH"; #endif /* 4.4 BSD */ #if defined (SIGINFO) && !defined (_SEQUENT_) /* information request */ signal_names[SIGINFO] = "SIGINFO"; #endif #if defined (SIGUSR1) /* user defined signal 1 */ signal_names[SIGUSR1] = "SIGUSR1"; #endif #if defined (SIGUSR2) /* user defined signal 2 */ signal_names[SIGUSR2] = "SIGUSR2"; #endif #if defined (SIGKILLTHR) /* BeOS: Kill Thread */ signal_names[SIGKILLTHR] = "SIGKILLTHR"; #endif for (i = 0; i < NSIG; i++) if (signal_names[i] == (char *)NULL) { signal_names[i] = (char *)malloc (18); if (signal_names[i]) sprintf (signal_names[i], "SIGJUNK(%d)", i); } signal_names[NSIG] = "DEBUG"; signal_names[NSIG+1] = "ERR"; } void write_signames (stream) FILE *stream; { register int i; fprintf (stream, "/* This file was automatically created by %s.\n", progname); fprintf (stream, " Do not edit. Edit support/mksignames.c instead. */\n\n"); fprintf (stream, "/* A translation list so we can be polite to our users. */\n"); fprintf (stream, "char *signal_names[NSIG + 3] = {\n"); for (i = 0; i <= LASTSIG; i++) fprintf (stream, " \"%s\",\n", signal_names[i]); fprintf (stream, " (char *)0x0\n"); fprintf (stream, "};\n"); } int main (argc, argv) int argc; char **argv; { char *stream_name; FILE *stream; progname = argv[0]; if (argc == 1) { stream_name = "stdout"; stream = stdout; } else if (argc == 2) { stream_name = argv[1]; stream = fopen (stream_name, "w"); } else { fprintf (stderr, "Usage: %s [output-file]\n", progname); exit (1); } if (!stream) { fprintf (stderr, "%s: %s: cannot open for writing\n", progname, stream_name); exit (2); } initialize_signames (); write_signames (stream); exit (0); }
/* trap.c -- Not the trap command, but useful functions for manipulating those objects. The trap command is in builtins/trap.def. */ /* Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "bashtypes.h" #include "bashansi.h" #include <stdio.h> #include <errno.h> #include "trap.h" #include "shell.h" #include "input.h" /* for save_token_state, restore_token_state */ #include "signames.h" #include "builtins.h" #include "builtins/common.h" #include "builtins/builtext.h" #ifndef errno extern int errno; #endif /* Flags which describe the current handling state of a signal. */ #define SIG_INHERITED 0x0 /* Value inherited from parent. */ #define SIG_TRAPPED 0x1 /* Currently trapped. */ #define SIG_HARD_IGNORE 0x2 /* Signal was ignored on shell entry. */ #define SIG_SPECIAL 0x4 /* Treat this signal specially. */ #define SIG_NO_TRAP 0x8 /* Signal cannot be trapped. */ #define SIG_INPROGRESS 0x10 /* Signal handler currently executing. */ #define SIG_CHANGED 0x20 /* Trap value changed in trap handler. */ #define SIG_IGNORED 0x40 /* The signal is currently being ignored. */ #define SPECIAL_TRAP(s) ((s) == EXIT_TRAP || (s) == DEBUG_TRAP || (s) == ERROR_TRAP) /* An array of such flags, one for each signal, describing what the shell will do with a signal. DEBUG_TRAP == NSIG; some code below assumes this. */ static int sigmodes[BASH_NSIG]; static void free_trap_command __P((int)); static void change_signal __P((int, char *)); static void get_original_signal __P((int)); static void _run_trap_internal __P((int, char *)); static void reset_signal __P((int)); static void restore_signal __P((int)); static void reset_or_restore_signal_handlers __P((sh_resetsig_func_t *)); /* Variables used here but defined in other files. */ extern int interrupt_immediately; extern int last_command_exit_value; extern int line_number; extern sh_builtin_func_t *this_shell_builtin; extern procenv_t wait_intr_buf; /* The list of things to do originally, before we started trapping. */ SigHandler *original_signals[NSIG]; /* For each signal, a slot for a string, which is a command to be executed when that signal is recieved. The slot can also contain DEFAULT_SIG, which means do whatever you were going to do before you were so rudely interrupted, or IGNORE_SIG, which says ignore this signal. */ char *trap_list[BASH_NSIG]; /* A bitmap of signals received for which we have trap handlers. */ int pending_traps[NSIG]; /* Set to the number of the signal we're running the trap for + 1. Used in execute_cmd.c and builtins/common.c to clean up when parse_and_execute does not return normally after executing the trap command (e.g., when `return' is executed in the trap command). */ int running_trap; /* The value of line_number when the trap started executing, since parse_and_execute resets it to 1 and the trap command might want it. */ int trap_line_number; /* The (trapped) signal received while executing in the `wait' builtin */ int wait_signal_received; /* A value which can never be the target of a trap handler. */ #define IMPOSSIBLE_TRAP_HANDLER (SigHandler *)initialize_traps void initialize_traps () { register int i; trap_list[EXIT_TRAP] = trap_list[DEBUG_TRAP] = trap_list[ERROR_TRAP] = (char *)NULL; sigmodes[EXIT_TRAP] = sigmodes[DEBUG_TRAP] = sigmodes[ERROR_TRAP] = SIG_INHERITED; original_signals[EXIT_TRAP] = IMPOSSIBLE_TRAP_HANDLER; for (i = 1; i < NSIG; i++) { pending_traps[i] = 0; trap_list[i] = (char *)DEFAULT_SIG; sigmodes[i] = SIG_INHERITED; original_signals[i] = IMPOSSIBLE_TRAP_HANDLER; } /* Show which signals are treated specially by the shell. */ #if defined (SIGCHLD) original_signals[SIGCHLD] = (SigHandler *) set_signal_handler (SIGCHLD, SIG_DFL); set_signal_handler (SIGCHLD, original_signals[SIGCHLD]); sigmodes[SIGCHLD] |= (SIG_SPECIAL | SIG_NO_TRAP); #endif /* SIGCHLD */ original_signals[SIGINT] = (SigHandler *) set_signal_handler (SIGINT, SIG_DFL); set_signal_handler (SIGINT, original_signals[SIGINT]); sigmodes[SIGINT] |= SIG_SPECIAL; #if defined (__BEOS__) /* BeOS sets SIGINT to SIG_IGN! */ original_signals[SIGINT] = SIG_DFL; #endif original_signals[SIGQUIT] = (SigHandler *) set_signal_handler (SIGQUIT, SIG_DFL); set_signal_handler (SIGQUIT, original_signals[SIGQUIT]); sigmodes[SIGQUIT] |= SIG_SPECIAL; if (interactive) { original_signals[SIGTERM] = (SigHandler *)set_signal_handler (SIGTERM, SIG_DFL); set_signal_handler (SIGTERM, original_signals[SIGTERM]); sigmodes[SIGTERM] |= SIG_SPECIAL; } } #ifdef INCLUDE_UNUSED /* Return a printable representation of the trap handler for SIG. */ static char * trap_handler_string (sig) int sig; { if (trap_list[sig] == (char *)DEFAULT_SIG) return "DEFAULT_SIG"; else if (trap_list[sig] == (char *)IGNORE_SIG) return "IGNORE_SIG"; else if (trap_list[sig] == (char *)IMPOSSIBLE_TRAP_HANDLER) return "IMPOSSIBLE_TRAP_HANDLER"; else if (trap_list[sig]) return trap_list[sig]; else return "NULL"; } #endif /* Return the print name of this signal. */ char * signal_name (sig) int sig; { char *ret; /* on cygwin32, signal_names[sig] could be null */ ret = (sig >= BASH_NSIG || sig < 0) ? "bad signal number" : signal_names[sig]; if (ret == NULL) ret = "unrecognized signal number"; return ret; } /* Turn a string into a signal number, or a number into a signal number. If STRING is "2", "SIGINT", or "INT", then (int)2 is returned. Return NO_SIG if STRING doesn't contain a valid signal descriptor. */ int decode_signal (string) char *string; { intmax_t sig; if (legal_number (string, &sig)) return ((sig >= 0 && sig < NSIG) ? (int)sig : NO_SIG); /* A leading `SIG' may be omitted. */ for (sig = 0; sig < BASH_NSIG; sig++) { if (signal_names[sig] == 0 || signal_names[sig][0] == '\0') continue; if (strcasecmp (string, signal_names[sig]) == 0 || (STREQN (signal_names[sig], "SIG", 3) && strcasecmp (string, &(signal_names[sig])[3]) == 0)) return ((int)sig); } return (NO_SIG); } /* Non-zero when we catch a trapped signal. */ static int catch_flag; void run_pending_traps () { register int sig; int old_exit_value, *token_state; if (catch_flag == 0) /* simple optimization */ return; catch_flag = 0; /* Preserve $? when running trap. */ old_exit_value = last_command_exit_value; for (sig = 1; sig < NSIG; sig++) { /* XXX this could be made into a counter by using while (pending_traps[sig]--) instead of the if statement. */ if (pending_traps[sig]) { #if defined (HAVE_POSIX_SIGNALS) sigset_t set, oset; sigemptyset (&set); sigemptyset (&oset); sigaddset (&set, sig); sigprocmask (SIG_BLOCK, &set, &oset); #else # if defined (HAVE_BSD_SIGNALS) int oldmask = sigblock (sigmask (sig)); # endif #endif /* HAVE_POSIX_SIGNALS */ if (sig == SIGINT) { run_interrupt_trap (); CLRINTERRUPT; } else if (trap_list[sig] == (char *)DEFAULT_SIG || trap_list[sig] == (char *)IGNORE_SIG || trap_list[sig] == (char *)IMPOSSIBLE_TRAP_HANDLER) { /* This is possible due to a race condition. Say a bash process has SIGTERM trapped. A subshell is spawned using { list; } & and the parent does something and kills the subshell with SIGTERM. It's possible for the subshell to set pending_traps[SIGTERM] to 1 before the code in execute_cmd.c eventually calls restore_original_signals to reset the SIGTERM signal handler in the subshell. The next time run_pending_traps is called, pending_traps[SIGTERM] will be 1, but the trap handler in trap_list[SIGTERM] will be invalid (probably DEFAULT_SIG, but it could be IGNORE_SIG). Unless we catch this, the subshell will dump core when trap_list[SIGTERM] == DEFAULT_SIG, because DEFAULT_SIG is usually 0x0. */ internal_warning ("run_pending_traps: bad value in trap_list[%d]: %p", sig, trap_list[sig]); if (trap_list[sig] == (char *)DEFAULT_SIG) { internal_warning ("run_pending_traps: signal handler is SIG_DFL, resending %d (%s) to myself", sig, signal_name (sig)); kill (getpid (), sig); } } else { token_state = save_token_state (); parse_and_execute (savestring (trap_list[sig]), "trap", SEVAL_NONINT|SEVAL_NOHIST); restore_token_state (token_state); free (token_state); } pending_traps[sig] = 0; #if defined (HAVE_POSIX_SIGNALS) sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL); #else # if defined (HAVE_BSD_SIGNALS) sigsetmask (oldmask); # endif #endif /* POSIX_VERSION */ } } last_command_exit_value = old_exit_value; } sighandler trap_handler (sig) int sig; { int oerrno; if ((sig >= NSIG) || (trap_list[sig] == (char *)DEFAULT_SIG) || (trap_list[sig] == (char *)IGNORE_SIG)) programming_error ("trap_handler: bad signal %d", sig); else { oerrno = errno; #if defined (MUST_REINSTALL_SIGHANDLERS) set_signal_handler (sig, trap_handler); #endif /* MUST_REINSTALL_SIGHANDLERS */ catch_flag = 1; pending_traps[sig]++; if (interrupt_immediately && this_shell_builtin && (this_shell_builtin == wait_builtin)) { wait_signal_received = sig; longjmp (wait_intr_buf, 1); } if (interrupt_immediately) run_pending_traps (); errno = oerrno; } SIGRETURN (0); } #if defined (JOB_CONTROL) && defined (SIGCHLD) #ifdef INCLUDE_UNUSED /* Make COMMAND_STRING be executed when SIGCHLD is caught. */ void set_sigchld_trap (command_string) char *command_string; { set_signal (SIGCHLD, command_string); } #endif /* Make COMMAND_STRING be executed when SIGCHLD is caught iff the current SIGCHLD trap handler is DEFAULT_SIG. */ void maybe_set_sigchld_trap (command_string) char *command_string; { if ((sigmodes[SIGCHLD] & SIG_TRAPPED) == 0) set_signal (SIGCHLD, command_string); } #endif /* JOB_CONTROL && SIGCHLD */ void set_debug_trap (command) char *command; { set_signal (DEBUG_TRAP, command); } void set_error_trap (command) char *command; { set_signal (ERROR_TRAP, command); } #ifdef INCLUDE_UNUSED void set_sigint_trap (command) char *command; { set_signal (SIGINT, command); } #endif /* Reset the SIGINT handler so that subshells that are doing `shellsy' things, like waiting for command substitution or executing commands in explicit subshells ( ( cmd ) ), can catch interrupts properly. */ SigHandler * set_sigint_handler () { if (sigmodes[SIGINT] & SIG_HARD_IGNORE) return ((SigHandler *)SIG_IGN); else if (sigmodes[SIGINT] & SIG_IGNORED) return ((SigHandler *)set_signal_handler (SIGINT, SIG_IGN)); /* XXX */ else if (sigmodes[SIGINT] & SIG_TRAPPED) return ((SigHandler *)set_signal_handler (SIGINT, trap_handler)); /* The signal is not trapped, so set the handler to the shell's special interrupt handler. */ else if (interactive) /* XXX - was interactive_shell */ return (set_signal_handler (SIGINT, sigint_sighandler)); else return (set_signal_handler (SIGINT, termination_unwind_protect)); } /* Return the correct handler for signal SIG according to the values in sigmodes[SIG]. */ SigHandler * trap_to_sighandler (sig) int sig; { if (sigmodes[sig] & (SIG_IGNORED|SIG_HARD_IGNORE)) return (SIG_IGN); else if (sigmodes[sig] & SIG_TRAPPED) return (trap_handler); else return (SIG_DFL); } /* Set SIG to call STRING as a command. */ void set_signal (sig, string) int sig; char *string; { if (SPECIAL_TRAP (sig)) { change_signal (sig, savestring (string)); if (sig == EXIT_TRAP && interactive == 0) initialize_terminating_signals (); return; } /* A signal ignored on entry to the shell cannot be trapped or reset, but no error is reported when attempting to do so. -- Posix.2 */ if (sigmodes[sig] & SIG_HARD_IGNORE) return; /* Make sure we have original_signals[sig] if the signal has not yet been trapped. */ if ((sigmodes[sig] & SIG_TRAPPED) == 0) { /* If we aren't sure of the original value, check it. */ if (original_signals[sig] == IMPOSSIBLE_TRAP_HANDLER) { original_signals[sig] = (SigHandler *)set_signal_handler (sig, SIG_DFL); set_signal_handler (sig, original_signals[sig]); } /* Signals ignored on entry to the shell cannot be trapped or reset. */ if (original_signals[sig] == SIG_IGN) { sigmodes[sig] |= SIG_HARD_IGNORE; return; } } /* Only change the system signal handler if SIG_NO_TRAP is not set. The trap command string is changed in either case. The shell signal handlers for SIGINT and SIGCHLD run the user specified traps in an environment in which it is safe to do so. */ if ((sigmodes[sig] & SIG_NO_TRAP) == 0) { set_signal_handler (sig, SIG_IGN); change_signal (sig, savestring (string)); set_signal_handler (sig, trap_handler); } else change_signal (sig, savestring (string)); } static void free_trap_command (sig) int sig; { if ((sigmodes[sig] & SIG_TRAPPED) && trap_list[sig] && (trap_list[sig] != (char *)IGNORE_SIG) && (trap_list[sig] != (char *)DEFAULT_SIG) && (trap_list[sig] != (char *)IMPOSSIBLE_TRAP_HANDLER)) free (trap_list[sig]); } /* If SIG has a string assigned to it, get rid of it. Then give it VALUE. */ static void change_signal (sig, value) int sig; char *value; { if ((sigmodes[sig] & SIG_INPROGRESS) == 0) free_trap_command (sig); trap_list[sig] = value; sigmodes[sig] |= SIG_TRAPPED; if (value == (char *)IGNORE_SIG) sigmodes[sig] |= SIG_IGNORED; else sigmodes[sig] &= ~SIG_IGNORED; if (sigmodes[sig] & SIG_INPROGRESS) sigmodes[sig] |= SIG_CHANGED; } #define GET_ORIGINAL_SIGNAL(sig) \ if (sig && sig < NSIG && original_signals[sig] == IMPOSSIBLE_TRAP_HANDLER) \ get_original_signal (sig) static void get_original_signal (sig) int sig; { /* If we aren't sure the of the original value, then get it. */ if (original_signals[sig] == (SigHandler *)IMPOSSIBLE_TRAP_HANDLER) { original_signals[sig] = (SigHandler *) set_signal_handler (sig, SIG_DFL); set_signal_handler (sig, original_signals[sig]); /* Signals ignored on entry to the shell cannot be trapped. */ if (original_signals[sig] == SIG_IGN) sigmodes[sig] |= SIG_HARD_IGNORE; } } /* Restore the default action for SIG; i.e., the action the shell would have taken before you used the trap command. This is called from trap_builtin (), which takes care to restore the handlers for the signals the shell treats specially. */ void restore_default_signal (sig) int sig; { if (SPECIAL_TRAP (sig)) { if ((sig != DEBUG_TRAP && sig != ERROR_TRAP) || (sigmodes[sig] & SIG_INPROGRESS) == 0) free_trap_command (sig); trap_list[sig] = (char *)NULL; sigmodes[sig] &= ~SIG_TRAPPED; if (sigmodes[sig] & SIG_INPROGRESS) sigmodes[sig] |= SIG_CHANGED; return; } GET_ORIGINAL_SIGNAL (sig); /* A signal ignored on entry to the shell cannot be trapped or reset, but no error is reported when attempting to do so. Thanks Posix.2. */ if (sigmodes[sig] & SIG_HARD_IGNORE) return; /* If we aren't trapping this signal, don't bother doing anything else. */ if ((sigmodes[sig] & SIG_TRAPPED) == 0) return; /* Only change the signal handler for SIG if it allows it. */ if ((sigmodes[sig] & SIG_NO_TRAP) == 0) set_signal_handler (sig, original_signals[sig]); /* Change the trap command in either case. */ change_signal (sig, (char *)DEFAULT_SIG); /* Mark the signal as no longer trapped. */ sigmodes[sig] &= ~SIG_TRAPPED; } /* Make this signal be ignored. */ void ignore_signal (sig) int sig; { if (SPECIAL_TRAP (sig) && ((sigmodes[sig] & SIG_IGNORED) == 0)) { change_signal (sig, (char *)IGNORE_SIG); return; } GET_ORIGINAL_SIGNAL (sig); /* A signal ignored on entry to the shell cannot be trapped or reset. No error is reported when the user attempts to do so. */ if (sigmodes[sig] & SIG_HARD_IGNORE) return; /* If already trapped and ignored, no change necessary. */ if (sigmodes[sig] & SIG_IGNORED) return; /* Only change the signal handler for SIG if it allows it. */ if ((sigmodes[sig] & SIG_NO_TRAP) == 0) set_signal_handler (sig, SIG_IGN); /* Change the trap command in either case. */ change_signal (sig, (char *)IGNORE_SIG); } /* Handle the calling of "trap 0". The only sticky situation is when the command to be executed includes an "exit". This is why we have to provide our own place for top_level to jump to. */ int run_exit_trap () { char *trap_command; int code, old_exit_value; old_exit_value = last_command_exit_value; /* Run the trap only if signal 0 is trapped and not ignored, and we are not currently running in the trap handler (call to exit in the list of commands given to trap 0). */ if ((sigmodes[EXIT_TRAP] & SIG_TRAPPED) && (sigmodes[EXIT_TRAP] & (SIG_IGNORED|SIG_INPROGRESS)) == 0) { trap_command = savestring (trap_list[EXIT_TRAP]); sigmodes[EXIT_TRAP] &= ~SIG_TRAPPED; sigmodes[EXIT_TRAP] |= SIG_INPROGRESS; code = setjmp (top_level); if (code == 0) { reset_parser (); parse_and_execute (trap_command, "exit trap", SEVAL_NONINT|SEVAL_NOHIST); } else if (code == EXITPROG) return (last_command_exit_value); else return (old_exit_value); } return (old_exit_value); } void run_trap_cleanup (sig) int sig; { sigmodes[sig] &= ~(SIG_INPROGRESS|SIG_CHANGED); } /* Run a trap command for SIG. SIG is one of the signals the shell treats specially. */ static void _run_trap_internal (sig, tag) int sig; char *tag; { char *trap_command, *old_trap; int old_exit_value, *token_state; /* Run the trap only if SIG is trapped and not ignored, and we are not currently executing in the trap handler. */ if ((sigmodes[sig] & SIG_TRAPPED) && ((sigmodes[sig] & SIG_IGNORED) == 0) && (trap_list[sig] != (char *)IMPOSSIBLE_TRAP_HANDLER) && ((sigmodes[sig] & SIG_INPROGRESS) == 0)) { old_trap = trap_list[sig]; sigmodes[sig] |= SIG_INPROGRESS; sigmodes[sig] &= ~SIG_CHANGED; /* just to be sure */ trap_command = savestring (old_trap); running_trap = sig + 1; old_exit_value = last_command_exit_value; /* Need to copy the value of line_number because parse_and_execute resets it to 1, and the trap command might want it. */ trap_line_number = line_number; token_state = save_token_state (); parse_and_execute (trap_command, tag, SEVAL_NONINT|SEVAL_NOHIST); restore_token_state (token_state); free (token_state); last_command_exit_value = old_exit_value; running_trap = 0; sigmodes[sig] &= ~SIG_INPROGRESS; if (sigmodes[sig] & SIG_CHANGED) { free (old_trap); sigmodes[sig] &= ~SIG_CHANGED; } } } void run_debug_trap () { if ((sigmodes[DEBUG_TRAP] & SIG_TRAPPED) && ((sigmodes[DEBUG_TRAP] & SIG_INPROGRESS) == 0)) _run_trap_internal (DEBUG_TRAP, "debug trap"); } void run_error_trap () { if ((sigmodes[ERROR_TRAP] & SIG_TRAPPED) && (sigmodes[ERROR_TRAP] & SIG_INPROGRESS) == 0) _run_trap_internal (ERROR_TRAP, "error trap"); } /* Run a trap set on SIGINT. This is called from throw_to_top_level (), and declared here to localize the trap functions. */ void run_interrupt_trap () { _run_trap_internal (SIGINT, "interrupt trap"); } #ifdef INCLUDE_UNUSED /* Free all the allocated strings in the list of traps and reset the trap values to the default. */ void free_trap_strings () { register int i; for (i = 0; i < BASH_NSIG; i++) { free_trap_command (i); trap_list[i] = (char *)DEFAULT_SIG; sigmodes[i] &= ~SIG_TRAPPED; } trap_list[DEBUG_TRAP] = trap_list[EXIT_TRAP] = trap_list[ERROR_TRAP] = (char *)NULL; } #endif /* Reset the handler for SIG to the original value. */ static void reset_signal (sig) int sig; { set_signal_handler (sig, original_signals[sig]); sigmodes[sig] &= ~SIG_TRAPPED; } /* Set the handler signal SIG to the original and free any trap command associated with it. */ static void restore_signal (sig) int sig; { set_signal_handler (sig, original_signals[sig]); change_signal (sig, (char *)DEFAULT_SIG); sigmodes[sig] &= ~SIG_TRAPPED; } static void reset_or_restore_signal_handlers (reset) sh_resetsig_func_t *reset; { register int i; /* Take care of the exit trap first */ if (sigmodes[EXIT_TRAP] & SIG_TRAPPED) { free_trap_command (EXIT_TRAP); trap_list[EXIT_TRAP] = (char *)NULL; sigmodes[EXIT_TRAP] &= ~SIG_TRAPPED; } for (i = 1; i < NSIG; i++) { if (sigmodes[i] & SIG_TRAPPED) { if (trap_list[i] == (char *)IGNORE_SIG) set_signal_handler (i, SIG_IGN); else (*reset) (i); } else if (sigmodes[i] & SIG_SPECIAL) (*reset) (i); } /* Command substitution and other child processes don't inherit the debug or error traps. */ sigmodes[DEBUG_TRAP] &= ~SIG_TRAPPED; sigmodes[ERROR_TRAP] &= ~SIG_TRAPPED; } /* Reset trapped signals to their original values, but don't free the trap strings. Called by the command substitution code. */ void reset_signal_handlers () { reset_or_restore_signal_handlers (reset_signal); } /* Reset all trapped signals to their original values. Signals set to be ignored with trap '' SIGNAL should be ignored, so we make sure that they are. Called by child processes after they are forked. */ void restore_original_signals () { reset_or_restore_signal_handlers (restore_signal); } /* If a trap handler exists for signal SIG, then call it; otherwise just return failure. */ int maybe_call_trap_handler (sig) int sig; { /* Call the trap handler for SIG if the signal is trapped and not ignored. */ if ((sigmodes[sig] & SIG_TRAPPED) && ((sigmodes[sig] & SIG_IGNORED) == 0)) { switch (sig) { case SIGINT: run_interrupt_trap (); break; case EXIT_TRAP: run_exit_trap (); break; case DEBUG_TRAP: run_debug_trap (); break; case ERROR_TRAP: run_error_trap (); break; default: trap_handler (sig); break; } return (1); } else return (0); } int signal_is_trapped (sig) int sig; { return (sigmodes[sig] & SIG_TRAPPED); } int signal_is_special (sig) int sig; { return (sigmodes[sig] & SIG_SPECIAL); } int signal_is_ignored (sig) int sig; { return (sigmodes[sig] & SIG_IGNORED); } void set_signal_ignored (sig) int sig; { sigmodes[sig] |= SIG_HARD_IGNORE; original_signals[sig] = SIG_IGN; }
/* input.c -- functions to perform buffered input with synchronization. */ /* Copyright (C) 1992 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #include "bashtypes.h" #ifndef _MINIX # include <sys/file.h> #endif #include "filecntl.h" #include "posixstat.h" #include <stdio.h> #include <errno.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "bashansi.h" #include "command.h" #include "general.h" #include "input.h" #include "error.h" #include "externs.h" #if !defined (errno) extern int errno; #endif /* !errno */ /* Functions to handle reading input on systems that don't restart read(2) if a signal is received. */ static char localbuf[128]; static int local_index, local_bufused; /* Posix and USG systems do not guarantee to restart read () if it is interrupted by a signal. We do the read ourselves, and restart it if it returns EINTR. */ int getc_with_restart (stream) FILE *stream; { unsigned char uc; /* Try local buffering to reduce the number of read(2) calls. */ if (local_index == local_bufused || local_bufused == 0) { while (1) { local_bufused = read (fileno (stream), localbuf, sizeof(localbuf)); if (local_bufused > 0) break; else if (local_bufused == 0 || errno != EINTR) { local_index = 0; return EOF; } } local_index = 0; } uc = localbuf[local_index++]; return uc; } int ungetc_with_restart (c, stream) int c; FILE *stream; { if (local_index == 0 || c == EOF) return EOF; localbuf[--local_index] = c; return c; } #if defined (BUFFERED_INPUT) /* A facility similar to stdio, but input-only. */ #if defined (USING_BASH_MALLOC) # define MAX_INPUT_BUFFER_SIZE 8176 #else # define MAX_INPUT_BUFFER_SIZE 8192 #endif #if !defined (SEEK_CUR) # define SEEK_CUR 1 #endif /* !SEEK_CUR */ #ifdef max # undef max #endif #define max(a, b) (((a) > (b)) ? (a) : (b)) #ifdef min # undef min #endif #define min(a, b) ((a) > (b) ? (b) : (a)) extern int interactive_shell; int bash_input_fd_changed; /* This provides a way to map from a file descriptor to the buffer associated with that file descriptor, rather than just the other way around. This is needed so that buffers are managed properly in constructs like 3<&4. buffers[x]->b_fd == x -- that is how the correspondence is maintained. */ static BUFFERED_STREAM **buffers = (BUFFERED_STREAM **)NULL; static int nbuffers; #define ALLOCATE_BUFFERS(n) \ do { if ((n) >= nbuffers) allocate_buffers (n); } while (0) /* Make sure `buffers' has at least N elements. */ static void allocate_buffers (n) int n; { register int i, orig_nbuffers; orig_nbuffers = nbuffers; nbuffers = n + 20; buffers = (BUFFERED_STREAM **)xrealloc (buffers, nbuffers * sizeof (BUFFERED_STREAM *)); /* Zero out the new buffers. */ for (i = orig_nbuffers; i < nbuffers; i++) buffers[i] = (BUFFERED_STREAM *)NULL; } /* Construct and return a BUFFERED_STREAM corresponding to file descriptor FD, using BUFFER. */ static BUFFERED_STREAM * make_buffered_stream (fd, buffer, bufsize) int fd; char *buffer; size_t bufsize; { BUFFERED_STREAM *bp; bp = (BUFFERED_STREAM *)xmalloc (sizeof (BUFFERED_STREAM)); ALLOCATE_BUFFERS (fd); buffers[fd] = bp; bp->b_fd = fd; bp->b_buffer = buffer; bp->b_size = bufsize; bp->b_used = bp->b_inputp = bp->b_flag = 0; if (bufsize == 1) bp->b_flag |= B_UNBUFF; return (bp); } /* Allocate a new BUFFERED_STREAM, copy BP to it, and return the new copy. */ static BUFFERED_STREAM * copy_buffered_stream (bp) BUFFERED_STREAM *bp; { BUFFERED_STREAM *nbp; if (!bp) return ((BUFFERED_STREAM *)NULL); nbp = (BUFFERED_STREAM *)xmalloc (sizeof (BUFFERED_STREAM)); xbcopy ((char *)bp, (char *)nbp, sizeof (BUFFERED_STREAM)); return (nbp); } int set_bash_input_fd (fd) int fd; { if (bash_input.type == st_bstream) bash_input.location.buffered_fd = fd; else if (interactive_shell == 0) default_buffered_input = fd; return 0; } int fd_is_bash_input (fd) int fd; { if (bash_input.type == st_bstream && bash_input.location.buffered_fd == fd) return 1; else if (interactive_shell == 0 && default_buffered_input == fd) return 1; return 0; } /* Save the buffered stream corresponding to file descriptor FD (which bash is using to read input) to a buffered stream associated with NEW_FD. If NEW_FD is -1, a new file descriptor is allocated with fcntl. The new file descriptor is returned on success, -1 on error. */ int save_bash_input (fd, new_fd) int fd, new_fd; { int nfd; /* Sync the stream so we can re-read from the new file descriptor. We might be able to avoid this by copying the buffered stream verbatim to the new file descriptor. */ if (buffers[fd]) sync_buffered_stream (fd); /* Now take care of duplicating the file descriptor that bash is using for input, so we can reinitialize it later. */ nfd = (new_fd == -1) ? fcntl (fd, F_DUPFD, 10) : new_fd; if (nfd == -1) { if (fcntl (fd, F_GETFD, 0) == 0) sys_error ("cannot allocate new file descriptor for bash input from fd %d", fd); return -1; } if (buffers[nfd]) { /* What's this? A stray buffer without an associated open file descriptor? Free up the buffer and report the error. */ internal_error ("check_bash_input: buffer already exists for new fd %d", nfd); free_buffered_stream (buffers[nfd]); } /* Reinitialize bash_input.location. */ if (bash_input.type == st_bstream) { bash_input.location.buffered_fd = nfd; fd_to_buffered_stream (nfd); close_buffered_fd (fd); /* XXX */ } else /* If the current input type is not a buffered stream, but the shell is not interactive and therefore using a buffered stream to read input (e.g. with an `eval exec 3>output' inside a script), note that the input fd has been changed. pop_stream() looks at this value and adjusts the input fd to the new value of default_buffered_input accordingly. */ bash_input_fd_changed++; if (default_buffered_input == fd) default_buffered_input = nfd; SET_CLOSE_ON_EXEC (nfd); return nfd; } /* Check that file descriptor FD is not the one that bash is currently using to read input from a script. FD is about to be duplicated onto, which means that the kernel will close it for us. If FD is the bash input file descriptor, we need to seek backwards in the script (if possible and necessary -- scripts read from stdin are still unbuffered), allocate a new file descriptor to use for bash input, and re-initialize the buffered stream. Make sure the file descriptor used to save bash input is set close-on-exec. Returns 0 on success, -1 on failure. This works only if fd is > 0 -- if fd == 0 and bash is reading input from fd 0, save_bash_input is used instead, to cooperate with input redirection (look at redir.c:add_undo_redirect()). */ int check_bash_input (fd) int fd; { if (fd > 0 && fd_is_bash_input (fd)) return ((save_bash_input (fd, -1) == -1) ? -1 : 0); return 0; } /* This is the buffered stream analogue of dup2(fd1, fd2). The BUFFERED_STREAM corresponding to fd2 is deallocated, if one exists. BUFFERS[fd1] is copied to BUFFERS[fd2]. This is called by the redirect code for constructs like 4<&0 and 3</etc/rc.local. */ int duplicate_buffered_stream (fd1, fd2) int fd1, fd2; { int is_bash_input, m; if (fd1 == fd2) return 0; m = max (fd1, fd2); ALLOCATE_BUFFERS (m); /* If FD2 is the file descriptor bash is currently using for shell input, we need to do some extra work to make sure that the buffered stream actually exists (it might not if fd1 was not active, and the copy didn't actually do anything). */ is_bash_input = (bash_input.type == st_bstream) && (bash_input.location.buffered_fd == fd2); if (buffers[fd2]) free_buffered_stream (buffers[fd2]); buffers[fd2] = copy_buffered_stream (buffers[fd1]); if (buffers[fd2]) buffers[fd2]->b_fd = fd2; if (is_bash_input) { if (!buffers[fd2]) fd_to_buffered_stream (fd2); buffers[fd2]->b_flag |= B_WASBASHINPUT; } return (fd2); } /* Return 1 if a seek on FD will succeed. */ #ifndef __CYGWIN__ # define fd_is_seekable(fd) (lseek ((fd), 0L, SEEK_CUR) >= 0) #else # define fd_is_seekable(fd) 0 #endif /* __CYGWIN__ */ /* Take FD, a file descriptor, and create and return a buffered stream corresponding to it. If something is wrong and the file descriptor is invalid, return a NULL stream. */ BUFFERED_STREAM * fd_to_buffered_stream (fd) int fd; { char *buffer; size_t size; struct stat sb; if (fstat (fd, &sb) < 0) { close (fd); return ((BUFFERED_STREAM *)NULL); } size = (fd_is_seekable (fd)) ? min (sb.st_size, MAX_INPUT_BUFFER_SIZE) : 1; if (size == 0) size = 1; buffer = (char *)xmalloc (size); return (make_buffered_stream (fd, buffer, size)); } /* Return a buffered stream corresponding to FILE, a file name. */ BUFFERED_STREAM * open_buffered_stream (file) char *file; { int fd; fd = open (file, O_RDONLY); return ((fd >= 0) ? fd_to_buffered_stream (fd) : (BUFFERED_STREAM *)NULL); } /* Deallocate a buffered stream and free up its resources. Make sure we zero out the slot in BUFFERS that points to BP. */ void free_buffered_stream (bp) BUFFERED_STREAM *bp; { int n; if (!bp) return; n = bp->b_fd; if (bp->b_buffer) free (bp->b_buffer); free (bp); buffers[n] = (BUFFERED_STREAM *)NULL; } /* Close the file descriptor associated with BP, a buffered stream, and free up the stream. Return the status of closing BP's file descriptor. */ int close_buffered_stream (bp) BUFFERED_STREAM *bp; { int fd; if (!bp) return (0); fd = bp->b_fd; free_buffered_stream (bp); return (close (fd)); } /* Deallocate the buffered stream associated with file descriptor FD, and close FD. Return the status of the close on FD. */ int close_buffered_fd (fd) int fd; { if (fd < 0) { errno = EBADF; return -1; } if (fd >= nbuffers || !buffers || !buffers[fd]) return (close (fd)); return (close_buffered_stream (buffers[fd])); } /* Make the BUFFERED_STREAM associcated with buffers[FD] be BP, and return the old BUFFERED_STREAM. */ BUFFERED_STREAM * set_buffered_stream (fd, bp) int fd; BUFFERED_STREAM *bp; { BUFFERED_STREAM *ret; ret = buffers[fd]; buffers[fd] = bp; return ret; } /* Read a buffer full of characters from BP, a buffered stream. */ static int b_fill_buffer (bp) BUFFERED_STREAM *bp; { ssize_t nr; nr = zread (bp->b_fd, bp->b_buffer, bp->b_size); if (nr <= 0) { bp->b_used = 0; bp->b_buffer[0] = 0; if (nr == 0) bp->b_flag |= B_EOF; else bp->b_flag |= B_ERROR; return (EOF); } #if defined (__CYGWIN__) /* If on cygwin, translate \r\n to \n. */ if (nr >= 2 && bp->b_buffer[nr - 2] == '\r' && bp->b_buffer[nr - 1] == '\n') { bp->b_buffer[nr - 2] = '\n'; nr--; } #endif bp->b_used = nr; bp->b_inputp = 0; return (bp->b_buffer[bp->b_inputp++] & 0xFF); } /* Get a character from buffered stream BP. */ #define bufstream_getc(bp) \ (bp->b_inputp == bp->b_used || !bp->b_used) \ ? b_fill_buffer (bp) \ : bp->b_buffer[bp->b_inputp++] & 0xFF /* Push C back onto buffered stream BP. */ static int bufstream_ungetc(c, bp) int c; BUFFERED_STREAM *bp; { if (c == EOF || bp->b_inputp == 0) return (EOF); bp->b_buffer[--bp->b_inputp] = c; return (c); } /* Seek backwards on file BFD to synchronize what we've read so far with the underlying file pointer. */ int sync_buffered_stream (bfd) int bfd; { BUFFERED_STREAM *bp; off_t chars_left; if (buffers == 0 || (bp = buffers[bfd]) == 0) return (-1); chars_left = bp->b_used - bp->b_inputp; if (chars_left) lseek (bp->b_fd, -chars_left, SEEK_CUR); bp->b_used = bp->b_inputp = 0; return (0); } int buffered_getchar () { #if !defined (DJGPP) return (bufstream_getc (buffers[bash_input.location.buffered_fd])); #else /* On DJGPP, ignore \r. */ int ch; while ((ch = bufstream_getc (buffers[bash_input.location.buffered_fd])) == '\r') ; return ch; #endif } int buffered_ungetchar (c) int c; { return (bufstream_ungetc (c, buffers[bash_input.location.buffered_fd])); } /* Make input come from file descriptor BFD through a buffered stream. */ void with_input_from_buffered_stream (bfd, name) int bfd; char *name; { INPUT_STREAM location; BUFFERED_STREAM *bp; location.buffered_fd = bfd; /* Make sure the buffered stream exists. */ bp = fd_to_buffered_stream (bfd); init_yy_io (bp == 0 ? return_EOF : buffered_getchar, buffered_ungetchar, st_bstream, name, location); } #if defined (TEST) void * xmalloc(s) int s; { return (malloc (s)); } void * xrealloc(s, size) char *s; int size; { if (!s) return(malloc (size)); else return(realloc (s, size)); } void init_yy_io () { } process(bp) BUFFERED_STREAM *bp; { int c; while ((c = bufstream_getc(bp)) != EOF) putchar(c); } BASH_INPUT bash_input; struct stat dsb; /* can be used from gdb */ /* imitate /bin/cat */ main(argc, argv) int argc; char **argv; { register int i; BUFFERED_STREAM *bp; if (argc == 1) { bp = fd_to_buffered_stream (0); process(bp); exit(0); } for (i = 1; i < argc; i++) { if (argv[i][0] == '-' && argv[i][1] == '\0') { bp = fd_to_buffered_stream (0); if (!bp) continue; process(bp); free_buffered_stream (bp); } else { bp = open_buffered_stream (argv[i]); if (!bp) continue; process(bp); close_buffered_stream (bp); } } exit(0); } #endif /* TEST */ #endif /* BUFFERED_INPUT */
/* I can't stand it anymore! Please can't we just write the whole Unix system in lisp or something? */ /* Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ /* **************************************************************** */ /* */ /* Unwind Protection Scheme for Bash */ /* */ /* **************************************************************** */ #include "config.h" #include "bashtypes.h" #include "bashansi.h" #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #if STDC_HEADERS # include <stddef.h> #endif #ifndef offsetof # define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) #endif #include "command.h" #include "general.h" #include "unwind_prot.h" #include "quit.h" #include "sig.h" /* Structure describing a saved variable and the value to restore it to. */ typedef struct { char *variable; int size; char desired_setting[1]; /* actual size is `size' */ } SAVED_VAR; /* If HEAD.CLEANUP is null, then ARG.V contains a tag to throw back to. If HEAD.CLEANUP is restore_variable, then SV.V contains the saved variable. Otherwise, call HEAD.CLEANUP (ARG.V) to clean up. */ typedef union uwp { struct uwp_head { union uwp *next; Function *cleanup; } head; struct { struct uwp_head uwp_head; char *v; } arg; struct { struct uwp_head uwp_head; SAVED_VAR v; } sv; } UNWIND_ELT; extern int interrupt_immediately; static void without_interrupts __P((VFunction *, char *, char *)); static void unwind_frame_discard_internal __P((char *, char *)); static void unwind_frame_run_internal __P((char *, char *)); static void add_unwind_protect_internal __P((Function *, char *)); static void remove_unwind_protect_internal __P((char *, char *)); static void run_unwind_protects_internal __P((char *, char *)); static void clear_unwind_protects_internal __P((char *, char *)); static inline void restore_variable __P((SAVED_VAR *)); static void unwind_protect_mem_internal __P((char *, char *)); static UNWIND_ELT *unwind_protect_list = (UNWIND_ELT *)NULL; #define uwpalloc(elt) (elt) = (UNWIND_ELT *)xmalloc (sizeof (UNWIND_ELT)) #define uwpfree(elt) free(elt) /* Run a function without interrupts. This relies on the fact that the FUNCTION cannot change the value of interrupt_immediately. (I.e., does not call QUIT (). */ static void without_interrupts (function, arg1, arg2) VFunction *function; char *arg1, *arg2; { int old_interrupt_immediately; old_interrupt_immediately = interrupt_immediately; interrupt_immediately = 0; (*function)(arg1, arg2); interrupt_immediately = old_interrupt_immediately; } /* Start the beginning of a region. */ void begin_unwind_frame (tag) char *tag; { add_unwind_protect ((Function *)NULL, tag); } /* Discard the unwind protects back to TAG. */ void discard_unwind_frame (tag) char *tag; { if (unwind_protect_list) without_interrupts (unwind_frame_discard_internal, tag, (char *)NULL); } /* Run the unwind protects back to TAG. */ void run_unwind_frame (tag) char *tag; { if (unwind_protect_list) without_interrupts (unwind_frame_run_internal, tag, (char *)NULL); } /* Add the function CLEANUP with ARG to the list of unwindable things. */ void add_unwind_protect (cleanup, arg) Function *cleanup; char *arg; { without_interrupts (add_unwind_protect_internal, (char *)cleanup, arg); } /* Remove the top unwind protect from the list. */ void remove_unwind_protect () { if (unwind_protect_list) without_interrupts (remove_unwind_protect_internal, (char *)NULL, (char *)NULL); } /* Run the list of cleanup functions in unwind_protect_list. */ void run_unwind_protects () { if (unwind_protect_list) without_interrupts (run_unwind_protects_internal, (char *)NULL, (char *)NULL); } /* Erase the unwind-protect list. If flags is 1, free the elements. */ void clear_unwind_protect_list (flags) int flags; { char *flag; if (unwind_protect_list) { flag = flags ? "" : (char *)NULL; without_interrupts (clear_unwind_protects_internal, flag, (char *)NULL); } } /* **************************************************************** */ /* */ /* The Actual Functions */ /* */ /* **************************************************************** */ static void add_unwind_protect_internal (cleanup, arg) Function *cleanup; char *arg; { UNWIND_ELT *elt; uwpalloc (elt); elt->head.next = unwind_protect_list; elt->head.cleanup = cleanup; elt->arg.v = arg; unwind_protect_list = elt; } static void remove_unwind_protect_internal (ignore1, ignore2) char *ignore1, *ignore2; { UNWIND_ELT *elt; elt = unwind_protect_list; if (elt) { unwind_protect_list = unwind_protect_list->head.next; uwpfree (elt); } } static void run_unwind_protects_internal (ignore1, ignore2) char *ignore1, *ignore2; { unwind_frame_run_internal ((char *) NULL, (char *) NULL); } static void clear_unwind_protects_internal (flag, ignore) char *flag, *ignore; { if (flag) { while (unwind_protect_list) remove_unwind_protect_internal ((char *)NULL, (char *)NULL); } unwind_protect_list = (UNWIND_ELT *)NULL; } static void unwind_frame_discard_internal (tag, ignore) char *tag, *ignore; { UNWIND_ELT *elt; while (elt = unwind_protect_list) { unwind_protect_list = unwind_protect_list->head.next; if (elt->head.cleanup == 0 && (STREQ (elt->arg.v, tag))) { uwpfree (elt); break; } else uwpfree (elt); } } /* Restore the value of a variable, based on the contents of SV. sv->desired_setting is a block of memory SIZE bytes long holding the value itself. This block of memory is copied back into the variable. */ static inline void restore_variable (sv) SAVED_VAR *sv; { FASTCOPY (sv->desired_setting, sv->variable, sv->size); } static void unwind_frame_run_internal (tag, ignore) char *tag, *ignore; { UNWIND_ELT *elt; while (elt = unwind_protect_list) { unwind_protect_list = elt->head.next; /* If tag, then compare. */ if (!elt->head.cleanup) { if (tag && STREQ (elt->arg.v, tag)) { uwpfree (elt); break; } } else { if (elt->head.cleanup == (Function *) restore_variable) restore_variable (&elt->sv.v); else (*(elt->head.cleanup)) (elt->arg.v); } uwpfree (elt); } } static void unwind_protect_mem_internal (var, psize) char *var; char *psize; { int size, allocated; UNWIND_ELT *elt; size = *(int *) psize; allocated = size + offsetof (UNWIND_ELT, sv.v.desired_setting[0]); elt = (UNWIND_ELT *)xmalloc (allocated); elt->head.next = unwind_protect_list; elt->head.cleanup = (Function *) restore_variable; elt->sv.v.variable = var; elt->sv.v.size = size; FASTCOPY (var, elt->sv.v.desired_setting, size); unwind_protect_list = elt; } /* Save the value of a variable so it will be restored when unwind-protects are run. VAR is a pointer to the variable. SIZE is the size in bytes of VAR. */ void unwind_protect_mem (var, size) char *var; int size; { without_interrupts (unwind_protect_mem_internal, var, (char *) &size); }
/* pathexp.c -- The shell interface to the globbing library. */ /* Copyright (C) 1995-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #include "bashtypes.h" #include <stdio.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "bashansi.h" #include "shell.h" #include "pathexp.h" #include "flags.h" #include "shmbutil.h" #include <glob/strmatch.h> static int glob_name_is_acceptable __P((const char *)); static void ignore_globbed_names __P((char **, sh_ignore_func_t *)); #if defined (USE_POSIX_GLOB_LIBRARY) # include <glob.h> typedef int posix_glob_errfunc_t __P((const char *, int)); #else # include <glob/glob.h> #endif /* Control whether * matches .files in globbing. */ int glob_dot_filenames; /* Control whether the extended globbing features are enabled. */ int extended_glob = 0; /* Return nonzero if STRING has any unquoted special globbing chars in it. */ int unquoted_glob_pattern_p (string) register char *string; { register int c; char *send; int open; DECLARE_MBSTATE; open = 0; send = string + strlen (string); while (c = *string++) { switch (c) { case '?': case '*': return (1); case '[': open++; continue; case ']': if (open) return (1); continue; case '+': case '@': case '!': if (*string == '(') /*)*/ return (1); continue; case CTLESC: case '\\': if (*string++ == '\0') return (0); } /* Advance one fewer byte than an entire multibyte character to account for the auto-increment in the loop above. */ #ifdef HANDLE_MULTIBYTE string--; ADVANCE_CHAR_P (string, send - string); string++; #else ADVANCE_CHAR_P (string, send - string); #endif } return (0); } /* PATHNAME can contain characters prefixed by CTLESC; this indicates that the character is to be quoted. We quote it here in the style that the glob library recognizes. If flags includes QGLOB_CVTNULL, we change quoted null strings (pathname[0] == CTLNUL) into empty strings (pathname[0] == 0). If this is called after quote removal is performed, (flags & QGLOB_CVTNULL) should be 0; if called when quote removal has not been done (for example, before attempting to match a pattern while executing a case statement), flags should include QGLOB_CVTNULL. If flags includes QGLOB_FILENAME, appropriate quoting to match a filename should be performed. */ char * quote_string_for_globbing (pathname, qflags) const char *pathname; int qflags; { char *temp; register int i, j; temp = (char *)xmalloc (strlen (pathname) + 1); if ((qflags & QGLOB_CVTNULL) && QUOTED_NULL (pathname)) { temp[0] = '\0'; return temp; } for (i = j = 0; pathname[i]; i++) { if (pathname[i] == CTLESC) { if ((qflags & QGLOB_FILENAME) && pathname[i+1] == '/') continue; temp[j++] = '\\'; i++; if (pathname[i] == '\0') break; } temp[j++] = pathname[i]; } temp[j] = '\0'; return (temp); } char * quote_globbing_chars (string) char *string; { size_t slen; char *temp, *s, *t, *send; DECLARE_MBSTATE; slen = strlen (string); send = string + slen; temp = (char *)xmalloc (slen * 2 + 1); for (t = temp, s = string; *s; ) { switch (*s) { case '*': case '[': case ']': case '?': case '\\': *t++ = '\\'; break; case '+': case '@': case '!': if (s[1] == '(') /*(*/ *t++ = '\\'; break; } /* Copy a single (possibly multibyte) character from s to t, incrementing both. */ COPY_CHAR_P (t, s, send); } *t = '\0'; return temp; } /* Call the glob library to do globbing on PATHNAME. */ char ** shell_glob_filename (pathname) const char *pathname; { #if defined (USE_POSIX_GLOB_LIBRARY) register int i; char *temp, **results; glob_t filenames; int glob_flags; temp = quote_string_for_globbing (pathname, QGLOB_FILENAME); filenames.gl_offs = 0; # if defined (GLOB_PERIOD) glob_flags = glob_dot_filenames ? GLOB_PERIOD : 0; # else glob_flags = 0; # endif /* !GLOB_PERIOD */ glob_flags |= (GLOB_ERR | GLOB_DOOFFS); i = glob (temp, glob_flags, (posix_glob_errfunc_t *)NULL, &filenames); free (temp); if (i == GLOB_NOSPACE || i == GLOB_ABORTED) return ((char **)NULL); else if (i == GLOB_NOMATCH) filenames.gl_pathv = (char **)NULL; else if (i != 0) /* other error codes not in POSIX.2 */ filenames.gl_pathv = (char **)NULL; results = filenames.gl_pathv; if (results && ((GLOB_FAILED (results)) == 0)) { if (should_ignore_glob_matches ()) ignore_glob_matches (results); if (results && results[0]) strvec_sort (results); else { FREE (results); results = (char **)NULL; } } return (results); #else /* !USE_POSIX_GLOB_LIBRARY */ char *temp, **results; noglob_dot_filenames = glob_dot_filenames == 0; temp = quote_string_for_globbing (pathname, QGLOB_FILENAME); results = glob_filename (temp, 0); free (temp); if (results && ((GLOB_FAILED (results)) == 0)) { if (should_ignore_glob_matches ()) ignore_glob_matches (results); if (results && results[0]) strvec_sort (results); else { FREE (results); results = (char **)&glob_error_return; } } return (results); #endif /* !USE_POSIX_GLOB_LIBRARY */ } /* Stuff for GLOBIGNORE. */ static struct ignorevar globignore = { "GLOBIGNORE", (struct ign *)0, 0, (char *)0, (sh_iv_item_func_t *)0, }; /* Set up to ignore some glob matches because the value of GLOBIGNORE has changed. If GLOBIGNORE is being unset, we also need to disable the globbing of filenames beginning with a `.'. */ void setup_glob_ignore (name) char *name; { char *v; v = get_string_value (name); setup_ignore_patterns (&globignore); if (globignore.num_ignores) glob_dot_filenames = 1; else if (v == 0) glob_dot_filenames = 0; } int should_ignore_glob_matches () { return globignore.num_ignores; } /* Return 0 if NAME matches a pattern in the globignore.ignores list. */ static int glob_name_is_acceptable (name) const char *name; { struct ign *p; int flags; /* . and .. are never matched */ if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) return (0); flags = FNM_PATHNAME | FNMATCH_EXTFLAG; for (p = globignore.ignores; p->val; p++) { if (strmatch (p->val, (char *)name, flags) != FNM_NOMATCH) return (0); } return (1); } /* Internal function to test whether filenames in NAMES should be ignored. NAME_FUNC is a pointer to a function to call with each name. It returns non-zero if the name is acceptable to the particular ignore function which called _ignore_names; zero if the name should be removed from NAMES. */ static void ignore_globbed_names (names, name_func) char **names; sh_ignore_func_t *name_func; { char **newnames; int n, i; for (i = 0; names[i]; i++) ; newnames = strvec_create (i + 1); for (n = i = 0; names[i]; i++) { if ((*name_func) (names[i])) newnames[n++] = names[i]; else free (names[i]); } newnames[n] = (char *)NULL; if (n == 0) { names[0] = (char *)NULL; free (newnames); return; } /* Copy the acceptable names from NEWNAMES back to NAMES and set the new array end. */ for (n = 0; newnames[n]; n++) names[n] = newnames[n]; names[n] = (char *)NULL; free (newnames); } void ignore_glob_matches (names) char **names; { if (globignore.num_ignores == 0) return; ignore_globbed_names (names, glob_name_is_acceptable); } void setup_ignore_patterns (ivp) struct ignorevar *ivp; { int numitems, maxitems, ptr; char *colon_bit, *this_ignoreval; struct ign *p; this_ignoreval = get_string_value (ivp->varname); /* If nothing has changed then just exit now. */ if ((this_ignoreval && ivp->last_ignoreval && STREQ (this_ignoreval, ivp->last_ignoreval)) || (!this_ignoreval && !ivp->last_ignoreval)) return; /* Oops. The ignore variable has changed. Re-parse it. */ ivp->num_ignores = 0; if (ivp->ignores) { for (p = ivp->ignores; p->val; p++) free(p->val); free (ivp->ignores); ivp->ignores = (struct ign *)NULL; } if (ivp->last_ignoreval) { free (ivp->last_ignoreval); ivp->last_ignoreval = (char *)NULL; } if (this_ignoreval == 0 || *this_ignoreval == '\0') return; ivp->last_ignoreval = savestring (this_ignoreval); numitems = maxitems = ptr = 0; while (colon_bit = extract_colon_unit (this_ignoreval, &ptr)) { if (numitems + 1 >= maxitems) { maxitems += 10; ivp->ignores = (struct ign *)xrealloc (ivp->ignores, maxitems * sizeof (struct ign)); } ivp->ignores[numitems].val = colon_bit; ivp->ignores[numitems].len = strlen (colon_bit); ivp->ignores[numitems].flags = 0; if (ivp->item_func) (*ivp->item_func) (&ivp->ignores[numitems]); numitems++; } ivp->ignores[numitems].val = (char *)NULL; ivp->num_ignores = numitems; }
/* sig.c - interface for shell signal handlers and signal initialization. */ /* Copyright (C) 1994 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #include "bashtypes.h" #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include <stdio.h> #include <signal.h> #include "shell.h" #if defined (JOB_CONTROL) #include "jobs.h" #endif /* JOB_CONTROL */ #include "siglist.h" #include "sig.h" #include "trap.h" #include "builtins/common.h" #if defined (READLINE) # include "bashline.h" #endif #if defined (HISTORY) # include "bashhist.h" #endif extern int last_command_exit_value; extern int return_catch_flag; extern int loop_level, continuing, breaking; extern int parse_and_execute_level, shell_initialized; /* Non-zero after SIGINT. */ int interrupt_state; /* The environment at the top-level R-E loop. We use this in the case of error return. */ procenv_t top_level; #if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS) /* The signal masks that this shell runs with. */ sigset_t top_level_mask; #endif /* JOB_CONTROL */ /* When non-zero, we throw_to_top_level (). */ int interrupt_immediately = 0; static void initialize_shell_signals __P((void)); void initialize_signals (reinit) int reinit; { initialize_shell_signals (); initialize_job_signals (); #if !defined (HAVE_SYS_SIGLIST) && !defined (HAVE_UNDER_SYS_SIGLIST) && !defined (HAVE_STRSIGNAL) if (reinit == 0) initialize_siglist (); #endif /* !HAVE_SYS_SIGLIST && !HAVE_UNDER_SYS_SIGLIST && !HAVE_STRSIGNAL */ } /* A structure describing a signal that terminates the shell if not caught. The orig_handler member is present so children can reset these signals back to their original handlers. */ struct termsig { int signum; SigHandler *orig_handler; }; #define NULL_HANDLER (SigHandler *)SIG_DFL /* The list of signals that would terminate the shell if not caught. We catch them, but just so that we can write the history file, and so forth. */ static struct termsig terminating_signals[] = { #ifdef SIGHUP { SIGHUP, NULL_HANDLER }, #endif #ifdef SIGINT { SIGINT, NULL_HANDLER }, #endif #ifdef SIGILL { SIGILL, NULL_HANDLER }, #endif #ifdef SIGTRAP { SIGTRAP, NULL_HANDLER }, #endif #ifdef SIGIOT { SIGIOT, NULL_HANDLER }, #endif #ifdef SIGDANGER { SIGDANGER, NULL_HANDLER }, #endif #ifdef SIGEMT { SIGEMT, NULL_HANDLER }, #endif #ifdef SIGFPE { SIGFPE, NULL_HANDLER }, #endif #ifdef SIGBUS { SIGBUS, NULL_HANDLER }, #endif #ifdef SIGSEGV { SIGSEGV, NULL_HANDLER }, #endif #ifdef SIGSYS { SIGSYS, NULL_HANDLER }, #endif #ifdef SIGPIPE { SIGPIPE, NULL_HANDLER }, #endif #ifdef SIGALRM { SIGALRM, NULL_HANDLER }, #endif #ifdef SIGTERM { SIGTERM, NULL_HANDLER }, #endif #ifdef SIGXCPU { SIGXCPU, NULL_HANDLER }, #endif #ifdef SIGXFSZ { SIGXFSZ, NULL_HANDLER }, #endif #ifdef SIGVTALRM { SIGVTALRM, NULL_HANDLER }, #endif #if 0 #ifdef SIGPROF { SIGPROF, NULL_HANDLER }, #endif #endif #ifdef SIGLOST { SIGLOST, NULL_HANDLER }, #endif #ifdef SIGUSR1 { SIGUSR1, NULL_HANDLER }, #endif #ifdef SIGUSR2 { SIGUSR2, NULL_HANDLER }, #endif }; #define TERMSIGS_LENGTH (sizeof (terminating_signals) / sizeof (struct termsig)) #define XSIG(x) (terminating_signals[x].signum) #define XHANDLER(x) (terminating_signals[x].orig_handler) static int termsigs_initialized = 0; /* Initialize signals that will terminate the shell to do some unwind protection. For non-interactive shells, we only call this when a trap is defined for EXIT (0). */ void initialize_terminating_signals () { register int i; #if defined (HAVE_POSIX_SIGNALS) struct sigaction act, oact; #endif if (termsigs_initialized) return; /* The following code is to avoid an expensive call to set_signal_handler () for each terminating_signals. Fortunately, this is possible in Posix. Unfortunately, we have to call signal () on non-Posix systems for each signal in terminating_signals. */ #if defined (HAVE_POSIX_SIGNALS) act.sa_handler = termination_unwind_protect; act.sa_flags = 0; sigemptyset (&act.sa_mask); sigemptyset (&oact.sa_mask); for (i = 0; i < TERMSIGS_LENGTH; i++) sigaddset (&act.sa_mask, XSIG (i)); for (i = 0; i < TERMSIGS_LENGTH; i++) { /* If we've already trapped it, don't do anything. */ if (signal_is_trapped (XSIG (i))) continue; sigaction (XSIG (i), &act, &oact); XHANDLER(i) = oact.sa_handler; /* Don't do anything with signals that are ignored at shell entry if the shell is not interactive. */ if (!interactive_shell && XHANDLER (i) == SIG_IGN) { sigaction (XSIG (i), &oact, &act); set_signal_ignored (XSIG (i)); } #if defined (SIGPROF) && !defined (_MINIX) if (XSIG (i) == SIGPROF && XHANDLER (i) != SIG_DFL && XHANDLER (i) != SIG_IGN) sigaction (XSIG (i), &oact, (struct sigaction *)NULL); #endif /* SIGPROF && !_MINIX */ } #else /* !HAVE_POSIX_SIGNALS */ for (i = 0; i < TERMSIGS_LENGTH; i++) { /* If we've already trapped it, don't do anything. */ if (signal_is_trapped (XSIG (i))) continue; XHANDLER(i) = signal (XSIG (i), termination_unwind_protect); /* Don't do anything with signals that are ignored at shell entry if the shell is not interactive. */ if (!interactive_shell && XHANDLER (i) == SIG_IGN) { signal (XSIG (i), SIG_IGN); set_signal_ignored (XSIG (i)); } #ifdef SIGPROF if (XSIG (i) == SIGPROF && XHANDLER (i) != SIG_DFL && XHANDLER (i) != SIG_IGN) signal (XSIG (i), XHANDLER (i)); #endif } #endif /* !HAVE_POSIX_SIGNALS */ termsigs_initialized = 1; } static void initialize_shell_signals () { if (interactive) initialize_terminating_signals (); #if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS) /* All shells use the signal mask they inherit, and pass it along to child processes. Children will never block SIGCHLD, though. */ sigemptyset (&top_level_mask); sigprocmask (SIG_BLOCK, (sigset_t *)NULL, &top_level_mask); # if defined (SIGCHLD) sigdelset (&top_level_mask, SIGCHLD); # endif #endif /* JOB_CONTROL || HAVE_POSIX_SIGNALS */ /* And, some signals that are specifically ignored by the shell. */ set_signal_handler (SIGQUIT, SIG_IGN); if (interactive) { set_signal_handler (SIGINT, sigint_sighandler); set_signal_handler (SIGTERM, SIG_IGN); } } void reset_terminating_signals () { register int i; #if defined (HAVE_POSIX_SIGNALS) struct sigaction act; #endif if (termsigs_initialized == 0) return; #if defined (HAVE_POSIX_SIGNALS) act.sa_flags = 0; sigemptyset (&act.sa_mask); for (i = 0; i < TERMSIGS_LENGTH; i++) { /* Skip a signal if it's trapped or handled specially, because the trap code will restore the correct value. */ if (signal_is_trapped (XSIG (i)) || signal_is_special (XSIG (i))) continue; act.sa_handler = XHANDLER (i); sigaction (XSIG (i), &act, (struct sigaction *) NULL); } #else /* !HAVE_POSIX_SIGNALS */ for (i = 0; i < TERMSIGS_LENGTH; i++) { if (signal_is_trapped (XSIG (i)) || signal_is_special (XSIG (i))) continue; signal (XSIG (i), XHANDLER (i)); } #endif /* !HAVE_POSIX_SIGNALS */ } #undef XSIG #undef XHANDLER /* What to do when we've been interrupted, and it is safe to handle it. */ void throw_to_top_level () { int print_newline = 0; if (interrupt_state) { print_newline = 1; DELINTERRUPT; } if (interrupt_state) return; last_command_exit_value |= 128; /* Run any traps set on SIGINT. */ run_interrupt_trap (); /* Cleanup string parser environment. */ while (parse_and_execute_level) parse_and_execute_cleanup (); #if defined (JOB_CONTROL) give_terminal_to (shell_pgrp, 0); #endif /* JOB_CONTROL */ #if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS) /* This should not be necessary on systems using sigsetjmp/siglongjmp. */ sigprocmask (SIG_SETMASK, &top_level_mask, (sigset_t *)NULL); #endif reset_parser (); #if defined (READLINE) if (interactive) bashline_reinitialize (); #endif /* READLINE */ #if defined (PROCESS_SUBSTITUTION) unlink_fifo_list (); #endif /* PROCESS_SUBSTITUTION */ run_unwind_protects (); loop_level = continuing = breaking = 0; return_catch_flag = 0; if (interactive && print_newline) { fflush (stdout); fprintf (stderr, "\n"); fflush (stderr); } /* An interrupted `wait' command in a script does not exit the script. */ if (interactive || (interactive_shell && !shell_initialized) || (print_newline && signal_is_trapped (SIGINT))) jump_to_top_level (DISCARD); else jump_to_top_level (EXITPROG); } /* This is just here to isolate the longjmp calls. */ void jump_to_top_level (value) int value; { longjmp (top_level, value); } sighandler termination_unwind_protect (sig) int sig; { if (sig == SIGINT && signal_is_trapped (SIGINT)) run_interrupt_trap (); #if defined (HISTORY) if (interactive_shell && sig != SIGABRT) maybe_save_shell_history (); #endif /* HISTORY */ #if defined (JOB_CONTROL) if (interactive && sig == SIGHUP) hangup_all_jobs (); end_job_control (); #endif /* JOB_CONTROL */ #if defined (PROCESS_SUBSTITUTION) unlink_fifo_list (); #endif /* PROCESS_SUBSTITUTION */ run_exit_trap (); set_signal_handler (sig, SIG_DFL); kill (getpid (), sig); SIGRETURN (0); } /* What we really do when SIGINT occurs. */ sighandler sigint_sighandler (sig) int sig; { #if defined (MUST_REINSTALL_SIGHANDLERS) signal (sig, sigint_sighandler); #endif /* interrupt_state needs to be set for the stack of interrupts to work right. Should it be set unconditionally? */ if (interrupt_state == 0) ADDINTERRUPT; if (interrupt_immediately) { interrupt_immediately = 0; throw_to_top_level (); } SIGRETURN (0); } /* Signal functions used by the rest of the code. */ #if !defined (HAVE_POSIX_SIGNALS) #if defined (JOB_CONTROL) /* Perform OPERATION on NEWSET, perhaps leaving information in OLDSET. */ sigprocmask (operation, newset, oldset) int operation, *newset, *oldset; { int old, new; if (newset) new = *newset; else new = 0; switch (operation) { case SIG_BLOCK: old = sigblock (new); break; case SIG_SETMASK: sigsetmask (new); break; default: internal_error ("Bad code in sig.c: sigprocmask"); } if (oldset) *oldset = old; } #endif /* JOB_CONTROL */ #else #if !defined (SA_INTERRUPT) # define SA_INTERRUPT 0 #endif #if !defined (SA_RESTART) # define SA_RESTART 0 #endif SigHandler * set_signal_handler (sig, handler) int sig; SigHandler *handler; { struct sigaction act, oact; act.sa_handler = handler; act.sa_flags = 0; #if 0 if (sig == SIGALRM) act.sa_flags |= SA_INTERRUPT; /* XXX */ else act.sa_flags |= SA_RESTART; /* XXX */ #endif sigemptyset (&act.sa_mask); sigemptyset (&oact.sa_mask); sigaction (sig, &act, &oact); return (oact.sa_handler); } #endif /* HAVE_POSIX_SIGNALS */
/* GNU test program (ksb and mjb) */ /* Modified to run with the GNU shell Apr 25, 1988 by bfox. */ /* Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ /* Define PATTERN_MATCHING to get the csh-like =~ and !~ pattern-matching binary operators. */ /* #define PATTERN_MATCHING */ #if defined (HAVE_CONFIG_H) # include <config.h> #endif #include <stdio.h> #include "bashtypes.h" #if !defined (HAVE_LIMITS_H) # include <sys/param.h> #endif #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <errno.h> #if !defined (errno) extern int errno; #endif /* !errno */ #if !defined (_POSIX_VERSION) # include <sys/file.h> #endif /* !_POSIX_VERSION */ #include "posixstat.h" #include "filecntl.h" #include "shell.h" #include "pathexp.h" #include "test.h" #include "builtins/common.h" #include <glob/strmatch.h> #if !defined (STRLEN) # define STRLEN(s) ((s)[0] ? ((s)[1] ? ((s)[2] ? strlen(s) : 2) : 1) : 0) #endif #if !defined (STREQ) # define STREQ(a, b) ((a)[0] == (b)[0] && strcmp (a, b) == 0) #endif /* !STREQ */ #if !defined (R_OK) #define R_OK 4 #define W_OK 2 #define X_OK 1 #define F_OK 0 #endif /* R_OK */ #define EQ 0 #define NE 1 #define LT 2 #define GT 3 #define LE 4 #define GE 5 #define NT 0 #define OT 1 #define EF 2 /* The following few defines control the truth and false output of each stage. TRUE and FALSE are what we use to compute the final output value. SHELL_BOOLEAN is the form which returns truth or falseness in shell terms. Default is TRUE = 1, FALSE = 0, SHELL_BOOLEAN = (!value). */ #define TRUE 1 #define FALSE 0 #define SHELL_BOOLEAN(value) (!(value)) #define TEST_ERREXIT_STATUS 2 static procenv_t test_exit_buf; static int test_error_return; #define test_exit(val) \ do { test_error_return = val; longjmp (test_exit_buf, 1); } while (0) /* We have to use access(2) for machines running AFS, because it's not a Unix file system. This may produce incorrect answers for non-AFS files. I hate AFS. */ #if defined (AFS) # define EACCESS(path, mode) access(path, mode) #else # define EACCESS(path, mode) test_eaccess(path, mode) #endif /* AFS */ static int pos; /* The offset of the current argument in ARGV. */ static int argc; /* The number of arguments present in ARGV. */ static char **argv; /* The argument list. */ static int noeval; static void test_syntax_error __P((char *, char *)) __attribute__((__noreturn__)); static void beyond __P((void)) __attribute__((__noreturn__)); static void integer_expected_error __P((char *)) __attribute__((__noreturn__)); static int test_stat __P((char *, struct stat *)); static int unary_operator __P((void)); static int binary_operator __P((void)); static int two_arguments __P((void)); static int three_arguments __P((void)); static int posixtest __P((void)); static int expr __P((void)); static int term __P((void)); static int and __P((void)); static int or __P((void)); static int filecomp __P((char *, char *, int)); static int arithcomp __P((char *, char *, int, int)); static int patcomp __P((char *, char *, int)); static void test_syntax_error (format, arg) char *format, *arg; { builtin_error (format, arg); test_exit (TEST_ERREXIT_STATUS); } /* * beyond - call when we're beyond the end of the argument list (an * error condition) */ static void beyond () { test_syntax_error ("argument expected", (char *)NULL); } /* Syntax error for when an integer argument was expected, but something else was found. */ static void integer_expected_error (pch) char *pch; { test_syntax_error ("%s: integer expression expected", pch); } /* A wrapper for stat () which disallows pathnames that are empty strings and handles /dev/fd emulation on systems that don't have it. */ static int test_stat (path, finfo) char *path; struct stat *finfo; { if (*path == '\0') { errno = ENOENT; return (-1); } if (path[0] == '/' && path[1] == 'd' && strncmp (path, "/dev/fd/", 8) == 0) { #if !defined (HAVE_DEV_FD) intmax_t fd; int r; if (legal_number (path + 8, &fd) && fd == (int)fd) { r = fstat ((int)fd, finfo); if (r == 0 || errno != EBADF) return (r); } errno = ENOENT; return (-1); #else /* If HAVE_DEV_FD is defined, DEV_FD_PREFIX is defined also, and has a trailing slash. Make sure /dev/fd/xx really uses DEV_FD_PREFIX/xx. On most systems, with the notable exception of linux, this is effectively a no-op. */ char pbuf[32]; strcpy (pbuf, DEV_FD_PREFIX); strcat (pbuf, path + 8); return (stat (pbuf, finfo)); #endif /* !HAVE_DEV_FD */ } #if !defined (HAVE_DEV_STDIN) else if (STREQN (path, "/dev/std", 8)) { if (STREQ (path+8, "in")) return (fstat (0, finfo)); else if (STREQ (path+8, "out")) return (fstat (1, finfo)); else if (STREQ (path+8, "err")) return (fstat (2, finfo)); else return (stat (path, finfo)); } #endif /* !HAVE_DEV_STDIN */ return (stat (path, finfo)); } /* Do the same thing access(2) does, but use the effective uid and gid, and don't make the mistake of telling root that any file is executable. */ int test_eaccess (path, mode) char *path; int mode; { struct stat st; if (test_stat (path, &st) < 0) return (-1); if (current_user.euid == 0) { /* Root can read or write any file. */ if (mode != X_OK) return (0); /* Root can execute any file that has any one of the execute bits set. */ if (st.st_mode & S_IXUGO) return (0); } if (st.st_uid == current_user.euid) /* owner */ mode <<= 6; else if (group_member (st.st_gid)) mode <<= 3; if (st.st_mode & mode) return (0); errno = EACCES; return (-1); } /* Increment our position in the argument list. Check that we're not past the end of the argument list. This check is supressed if the argument is FALSE. Made a macro for efficiency. */ #define advance(f) do { ++pos; if (f && pos >= argc) beyond (); } while (0) #define unary_advance() do { advance (1); ++pos; } while (0) /* * expr: * or */ static int expr () { if (pos >= argc) beyond (); return (FALSE ^ or ()); /* Same with this. */ } /* * or: * and * and '-o' or */ static int or () { int value, v2; value = and (); if (pos < argc && argv[pos][0] == '-' && argv[pos][1] == 'o' && !argv[pos][2]) { advance (0); v2 = or (); return (value || v2); } return (value); } /* * and: * term * term '-a' and */ static int and () { int value, v2; value = term (); if (pos < argc && argv[pos][0] == '-' && argv[pos][1] == 'a' && !argv[pos][2]) { advance (0); v2 = and (); return (value && v2); } return (value); } /* * term - parse a term and return 1 or 0 depending on whether the term * evaluates to true or false, respectively. * * term ::= * '-'('a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'k'|'p'|'r'|'s'|'u'|'w'|'x') filename * '-'('G'|'L'|'O'|'S'|'N') filename * '-t' [int] * '-'('z'|'n') string * '-o' option * string * string ('!='|'='|'==') string * <int> '-'(eq|ne|le|lt|ge|gt) <int> * file '-'(nt|ot|ef) file * '(' <expr> ')' * int ::= * positive and negative integers */ static int term () { int value; if (pos >= argc) beyond (); /* Deal with leading `not's. */ if (argv[pos][0] == '!' && argv[pos][1] == '\0') { value = 0; while (pos < argc && argv[pos][0] == '!' && argv[pos][1] == '\0') { advance (1); value = 1 - value; } return (value ? !term() : term()); } /* A paren-bracketed argument. */ if (argv[pos][0] == '(' && argv[pos][1] == '\0') { advance (1); value = expr (); if (argv[pos] == 0) test_syntax_error ("`)' expected", (char *)NULL); else if (argv[pos][0] != ')' || argv[pos][1]) test_syntax_error ("`)' expected, found %s", argv[pos]); advance (0); return (value); } /* are there enough arguments left that this could be dyadic? */ if ((pos + 3 <= argc) && test_binop (argv[pos + 1])) value = binary_operator (); /* Might be a switch type argument */ else if (argv[pos][0] == '-' && argv[pos][2] == '\0') { if (test_unop (argv[pos])) value = unary_operator (); else test_syntax_error ("%s: unary operator expected", argv[pos]); } else { value = argv[pos][0] != '\0'; advance (0); } return (value); } static int filecomp (s, t, op) char *s, *t; int op; { struct stat st1, st2; int r1, r2; if ((r1 = test_stat (s, &st1)) < 0) { if (op == EF) return (FALSE); } if ((r2 = test_stat (t, &st2)) < 0) { if (op == EF) return (FALSE); } switch (op) { case OT: return (r1 < r2 || (r2 == 0 && st1.st_mtime < st2.st_mtime)); case NT: return (r1 > r2 || (r1 == 0 && st1.st_mtime > st2.st_mtime)); case EF: return ((st1.st_dev == st2.st_dev) && (st1.st_ino == st2.st_ino)); } return (FALSE); } static int arithcomp (s, t, op, flags) char *s, *t; int op, flags; { intmax_t l, r; int expok; if (flags & TEST_ARITHEXP) { l = evalexp (s, &expok); if (expok == 0) return (FALSE); /* should probably longjmp here */ r = evalexp (t, &expok); if (expok == 0) return (FALSE); /* ditto */ } else { if (legal_number (s, &l) == 0) integer_expected_error (s); if (legal_number (t, &r) == 0) integer_expected_error (t); } switch (op) { case EQ: return (l == r); case NE: return (l != r); case LT: return (l < r); case GT: return (l > r); case LE: return (l <= r); case GE: return (l >= r); } return (FALSE); } static int patcomp (string, pat, op) char *string, *pat; int op; { int m; m = strmatch (pat, string, FNMATCH_EXTFLAG); return ((op == EQ) ? (m == 0) : (m != 0)); } int binary_test (op, arg1, arg2, flags) char *op, *arg1, *arg2; int flags; { int patmatch; patmatch = (flags & TEST_PATMATCH); if (op[0] == '=' && (op[1] == '\0' || (op[1] == '=' && op[2] == '\0'))) return (patmatch ? patcomp (arg1, arg2, EQ) : STREQ (arg1, arg2)); else if ((op[0] == '>' || op[0] == '<') && op[1] == '\0') return ((op[0] == '>') ? (strcmp (arg1, arg2) > 0) : (strcmp (arg1, arg2) < 0)); else if (op[0] == '!' && op[1] == '=' && op[2] == '\0') return (patmatch ? patcomp (arg1, arg2, NE) : (STREQ (arg1, arg2) == 0)); else if (op[2] == 't') { switch (op[1]) { case 'n': return (filecomp (arg1, arg2, NT)); /* -nt */ case 'o': return (filecomp (arg1, arg2, OT)); /* -ot */ case 'l': return (arithcomp (arg1, arg2, LT, flags)); /* -lt */ case 'g': return (arithcomp (arg1, arg2, GT, flags)); /* -gt */ } } else if (op[1] == 'e') { switch (op[2]) { case 'f': return (filecomp (arg1, arg2, EF)); /* -ef */ case 'q': return (arithcomp (arg1, arg2, EQ, flags)); /* -eq */ } } else if (op[2] == 'e') { switch (op[1]) { case 'n': return (arithcomp (arg1, arg2, NE, flags)); /* -ne */ case 'g': return (arithcomp (arg1, arg2, GE, flags)); /* -ge */ case 'l': return (arithcomp (arg1, arg2, LE, flags)); /* -le */ } } return (FALSE); /* should never get here */ } static int binary_operator () { int value; char *w; w = argv[pos + 1]; if ((w[0] == '=' && (w[1] == '\0' || (w[1] == '=' && w[2] == '\0'))) || /* =, == */ ((w[0] == '>' || w[0] == '<') && w[1] == '\0') || /* <, > */ (w[0] == '!' && w[1] == '=' && w[2] == '\0')) /* != */ { value = binary_test (w, argv[pos], argv[pos + 2], 0); pos += 3; return (value); } #if defined (PATTERN_MATCHING) if ((w[0] == '=' || w[0] == '!') && w[1] == '~' && w[2] == '\0') { value = patcomp (argv[pos], argv[pos + 2], w[0] == '=' ? EQ : NE); pos += 3; return (value); } #endif if ((w[0] != '-' || w[3] != '\0') || test_binop (w) == 0) { test_syntax_error ("%s: binary operator expected", w); /* NOTREACHED */ return (FALSE); } value = binary_test (w, argv[pos], argv[pos + 2], 0); pos += 3; return value; } static int unary_operator () { char *op; intmax_t r; op = argv[pos]; if (test_unop (op) == 0) return (FALSE); /* the only tricky case is `-t', which may or may not take an argument. */ if (op[1] == 't') { advance (0); if (pos < argc) { if (legal_number (argv[pos], &r)) { advance (0); return (unary_test (op, argv[pos - 1])); } else return (FALSE); } else return (unary_test (op, "1")); } /* All of the unary operators take an argument, so we first call unary_advance (), which checks to make sure that there is an argument, and then advances pos right past it. This means that pos - 1 is the location of the argument. */ unary_advance (); return (unary_test (op, argv[pos - 1])); } int unary_test (op, arg) char *op, *arg; { intmax_t r; struct stat stat_buf; switch (op[1]) { case 'a': /* file exists in the file system? */ case 'e': return (test_stat (arg, &stat_buf) == 0); case 'r': /* file is readable? */ return (EACCESS (arg, R_OK) == 0); case 'w': /* File is writeable? */ return (EACCESS (arg, W_OK) == 0); case 'x': /* File is executable? */ return (EACCESS (arg, X_OK) == 0); case 'O': /* File is owned by you? */ return (test_stat (arg, &stat_buf) == 0 && (uid_t) current_user.euid == (uid_t) stat_buf.st_uid); case 'G': /* File is owned by your group? */ return (test_stat (arg, &stat_buf) == 0 && (gid_t) current_user.egid == (gid_t) stat_buf.st_gid); case 'N': return (test_stat (arg, &stat_buf) == 0 && stat_buf.st_atime <= stat_buf.st_mtime); case 'f': /* File is a file? */ if (test_stat (arg, &stat_buf) < 0) return (FALSE); /* -f is true if the given file exists and is a regular file. */ #if defined (S_IFMT) return (S_ISREG (stat_buf.st_mode) || (stat_buf.st_mode & S_IFMT) == 0); #else return (S_ISREG (stat_buf.st_mode)); #endif /* !S_IFMT */ case 'd': /* File is a directory? */ return (test_stat (arg, &stat_buf) == 0 && (S_ISDIR (stat_buf.st_mode))); case 's': /* File has something in it? */ return (test_stat (arg, &stat_buf) == 0 && stat_buf.st_size > (off_t) 0); case 'S': /* File is a socket? */ #if !defined (S_ISSOCK) return (FALSE); #else return (test_stat (arg, &stat_buf) == 0 && S_ISSOCK (stat_buf.st_mode)); #endif /* S_ISSOCK */ case 'c': /* File is character special? */ return (test_stat (arg, &stat_buf) == 0 && S_ISCHR (stat_buf.st_mode)); case 'b': /* File is block special? */ return (test_stat (arg, &stat_buf) == 0 && S_ISBLK (stat_buf.st_mode)); case 'p': /* File is a named pipe? */ #ifndef S_ISFIFO return (FALSE); #else return (test_stat (arg, &stat_buf) == 0 && S_ISFIFO (stat_buf.st_mode)); #endif /* S_ISFIFO */ case 'L': /* Same as -h */ case 'h': /* File is a symbolic link? */ #if !defined (S_ISLNK) || !defined (HAVE_LSTAT) return (FALSE); #else return ((arg[0] != '\0') && (lstat (arg, &stat_buf) == 0) && S_ISLNK (stat_buf.st_mode)); #endif /* S_IFLNK && HAVE_LSTAT */ case 'u': /* File is setuid? */ return (test_stat (arg, &stat_buf) == 0 && (stat_buf.st_mode & S_ISUID) != 0); case 'g': /* File is setgid? */ return (test_stat (arg, &stat_buf) == 0 && (stat_buf.st_mode & S_ISGID) != 0); case 'k': /* File has sticky bit set? */ #if !defined (S_ISVTX) /* This is not Posix, and is not defined on some Posix systems. */ return (FALSE); #else return (test_stat (arg, &stat_buf) == 0 && (stat_buf.st_mode & S_ISVTX) != 0); #endif case 't': /* File fd is a terminal? */ if (legal_number (arg, &r) == 0) return (FALSE); return ((r == (int)r) && isatty ((int)r)); case 'n': /* True if arg has some length. */ return (arg[0] != '\0'); case 'z': /* True if arg has no length. */ return (arg[0] == '\0'); case 'o': /* True if option `arg' is set. */ return (minus_o_option_value (arg) == 1); } /* We can't actually get here, but this shuts up gcc. */ return (FALSE); } /* Return TRUE if OP is one of the test command's binary operators. */ int test_binop (op) char *op; { if (op[0] == '=' && op[1] == '\0') return (1); /* '=' */ else if ((op[0] == '<' || op[0] == '>') && op[1] == '\0') /* string <, > */ return (1); else if ((op[0] == '=' || op[0] == '!') && op[1] == '=' && op[2] == '\0') return (1); /* `==' and `!=' */ #if defined (PATTERN_MATCHING) else if (op[2] == '\0' && op[1] == '~' && (op[0] == '=' || op[0] == '!')) return (1); #endif else if (op[0] != '-' || op[2] == '\0' || op[3] != '\0') return (0); else { if (op[2] == 't') switch (op[1]) { case 'n': /* -nt */ case 'o': /* -ot */ case 'l': /* -lt */ case 'g': /* -gt */ return (1); default: return (0); } else if (op[1] == 'e') switch (op[2]) { case 'q': /* -eq */ case 'f': /* -ef */ return (1); default: return (0); } else if (op[2] == 'e') switch (op[1]) { case 'n': /* -ne */ case 'g': /* -ge */ case 'l': /* -le */ return (1); default: return (0); } else return (0); } } /* Return non-zero if OP is one of the test command's unary operators. */ int test_unop (op) char *op; { if (op[0] != '-') return (0); switch (op[1]) { case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'k': case 'n': case 'o': case 'p': case 'r': case 's': case 't': case 'u': case 'w': case 'x': case 'z': case 'G': case 'L': case 'O': case 'S': case 'N': return (1); } return (0); } static int two_arguments () { if (argv[pos][0] == '!' && argv[pos][1] == '\0') return (argv[pos + 1][0] == '\0'); else if (argv[pos][0] == '-' && argv[pos][2] == '\0') { if (test_unop (argv[pos])) return (unary_operator ()); else test_syntax_error ("%s: unary operator expected", argv[pos]); } else test_syntax_error ("%s: unary operator expected", argv[pos]); return (0); } #define ANDOR(s) (s[0] == '-' && !s[2] && (s[1] == 'a' || s[1] == 'o')) /* This could be augmented to handle `-t' as equivalent to `-t 1', but POSIX requires that `-t' be given an argument. */ #define ONE_ARG_TEST(s) ((s)[0] != '\0') static int three_arguments () { int value; if (test_binop (argv[pos+1])) { value = binary_operator (); pos = argc; } else if (ANDOR (argv[pos+1])) { if (argv[pos+1][1] == 'a') value = ONE_ARG_TEST(argv[pos]) && ONE_ARG_TEST(argv[pos+2]); else value = ONE_ARG_TEST(argv[pos]) || ONE_ARG_TEST(argv[pos+2]); pos = argc; } else if (argv[pos][0] == '!' && argv[pos][1] == '\0') { advance (1); value = !two_arguments (); } else if (argv[pos][0] == '(' && argv[pos+2][0] == ')') { value = ONE_ARG_TEST(argv[pos+1]); pos = argc; } else test_syntax_error ("%s: binary operator expected", argv[pos+1]); return (value); } /* This is an implementation of a Posix.2 proposal by David Korn. */ static int posixtest () { int value; switch (argc - 1) /* one extra passed in */ { case 0: value = FALSE; pos = argc; break; case 1: value = ONE_ARG_TEST(argv[1]); pos = argc; break; case 2: value = two_arguments (); pos = argc; break; case 3: value = three_arguments (); break; case 4: if (argv[pos][0] == '!' && argv[pos][1] == '\0') { advance (1); value = !three_arguments (); break; } /* FALLTHROUGH */ default: value = expr (); } return (value); } /* * [: * '[' expr ']' * test: * test expr */ int test_command (margc, margv) int margc; char **margv; { int value; int code; USE_VAR(margc); code = setjmp (test_exit_buf); if (code) return (test_error_return); argv = margv; if (margv[0] && margv[0][0] == '[' && margv[0][1] == '\0') { --margc; if (margv[margc] && (margv[margc][0] != ']' || margv[margc][1])) test_syntax_error ("missing `]'", (char *)NULL); if (margc < 2) test_exit (SHELL_BOOLEAN (FALSE)); } argc = margc; pos = 1; if (pos >= argc) test_exit (SHELL_BOOLEAN (FALSE)); noeval = 0; value = posixtest (); if (pos != argc) test_syntax_error ("too many arguments", (char *)NULL); test_exit (SHELL_BOOLEAN (value)); }
/* version.c -- distribution and version numbers. */ /* Copyright (C) 1989 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #include <stdio.h> #include "stdc.h" #include "version.h" #include "patchlevel.h" #include "conftypes.h" extern char *shell_name; /* Defines from version.h */ const char *dist_version = DISTVERSION; int patch_level = PATCHLEVEL; int build_version = BUILDVERSION; #ifdef RELSTATUS const char *release_status = RELSTATUS; #else const char *release_status = (char *)0; #endif const char *sccs_version = SCCSVERSION; /* Functions for getting, setting, and displaying the shell version. */ /* Give version information about this shell. */ char * shell_version_string () { static char tt[32] = { '\0' }; if (tt[0] == '\0') { if (release_status) sprintf (tt, "%s.%d(%d)-%s", dist_version, patch_level, build_version, release_status); else sprintf (tt, "%s.%d(%d)", dist_version, patch_level, build_version); } return tt; } void show_shell_version (extended) int extended; { printf ("GNU bash, version %s (%s)\n", shell_version_string (), MACHTYPE); if (extended) printf ("Copyright (C) 2002 Free Software Foundation, Inc.\n"); }
/* patchlevel.h -- current bash patch level */ /* Copyright (C) 2001 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_PATCHLEVEL_H_) #define _PATCHLEVEL_H_ /* It's important that there be no other strings in this file that match the regexp `^#define[ ]*PATCHLEVEL', since that's what support/mkversion.sh looks for to find the patch level (for the sccs version string). */ #define PATCHLEVEL 0 #endif /* _PATCHLEVEL_H_ */
/* conftypes.h -- defines for build and host system. */ /* Copyright (C) 2001 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_CONFTYPES_H_) #define _CONFTYPES_H_ /* Placeholder for future modifications if cross-compiling or building a `fat' binary, e.g. on Apple Rhapsody. These values are used in multiple files, so they appear here. */ #if !defined (RHAPSODY) # define HOSTTYPE CONF_HOSTTYPE # define OSTYPE CONF_OSTYPE # define MACHTYPE CONF_MACHTYPE #else /* RHAPSODY */ # if defined(__powerpc__) || defined(__ppc__) # define HOSTTYPE "powerpc" # elif defined(__i386__) # define HOSTTYPE "i386" # else # define HOSTTYPE CONF_HOSTTYPE # endif # define OSTYPE CONF_OSTYPE # define VENDOR CONF_VENDOR # define MACHTYPE HOSTTYPE "-" VENDOR "-" OSTYPE #endif /* RHAPSODY */ #ifndef HOSTTYPE # define HOSTTYPE "unknown" #endif #ifndef OSTYPE # define OSTYPE "unknown" #endif #ifndef MACHTYPE # define MACHTYPE "unknown" #endif #endif /* _CONFTYPES_H_ */
/* alias.c -- Not a full alias, but just the kind that we use in the shell. Csh style alias is somewhere else (`over there, in a box'). */ /* Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if defined (ALIAS) #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include <stdio.h> #include "chartypes.h" #include "bashansi.h" #include "command.h" #include "general.h" #include "externs.h" #include "alias.h" #if defined (PROGRAMMABLE_COMPLETION) # include "pcomplete.h" #endif #define ALIAS_HASH_BUCKETS 16 /* must be power of two */ typedef int sh_alias_map_func_t __P((alias_t *)); static void free_alias_data __P((PTR_T)); static alias_t **map_over_aliases __P((sh_alias_map_func_t *)); static void sort_aliases __P((alias_t **)); static int qsort_alias_compare __P((alias_t **, alias_t **)); #if defined (READLINE) static int skipquotes __P((char *, int)); static int skipws __P((char *, int)); static int rd_token __P((char *, int)); #endif /* Non-zero means expand all words on the line. Otherwise, expand after first expansion if the expansion ends in a space. */ int alias_expand_all = 0; /* The list of aliases that we have. */ HASH_TABLE *aliases = (HASH_TABLE *)NULL; void initialize_aliases () { if (!aliases) aliases = hash_create (ALIAS_HASH_BUCKETS); } /* Scan the list of aliases looking for one with NAME. Return NULL if the alias doesn't exist, else a pointer to the alias_t. */ alias_t * find_alias (name) char *name; { BUCKET_CONTENTS *al; if (aliases == 0) return ((alias_t *)NULL); al = hash_search (name, aliases, 0); return (al ? (alias_t *)al->data : (alias_t *)NULL); } /* Return the value of the alias for NAME, or NULL if there is none. */ char * get_alias_value (name) char *name; { alias_t *alias; if (aliases == 0) return ((char *)NULL); alias = find_alias (name); return (alias ? alias->value : (char *)NULL); } /* Make a new alias from NAME and VALUE. If NAME can be found, then replace its value. */ void add_alias (name, value) char *name, *value; { BUCKET_CONTENTS *elt; alias_t *temp; int n; if (!aliases) { initialize_aliases (); temp = (alias_t *)NULL; } else temp = find_alias (name); if (temp) { free (temp->value); temp->value = savestring (value); temp->flags &= ~AL_EXPANDNEXT; n = value[strlen (value) - 1]; if (n == ' ' || n == '\t') temp->flags |= AL_EXPANDNEXT; } else { temp = (alias_t *)xmalloc (sizeof (alias_t)); temp->name = savestring (name); temp->value = savestring (value); temp->flags = 0; n = value[strlen (value) - 1]; if (n == ' ' || n == '\t') temp->flags |= AL_EXPANDNEXT; elt = hash_insert (savestring (name), aliases, HASH_NOSRCH); elt->data = temp; #if defined (PROGRAMMABLE_COMPLETION) set_itemlist_dirty (&it_aliases); #endif } } /* Delete a single alias structure. */ static void free_alias_data (data) PTR_T data; { register alias_t *a; a = (alias_t *)data; free (a->value); free (a->name); free (data); } /* Remove the alias with name NAME from the alias table. Returns the number of aliases left in the table, or -1 if the alias didn't exist. */ int remove_alias (name) char *name; { BUCKET_CONTENTS *elt; if (aliases == 0) return (-1); elt = hash_remove (name, aliases, 0); if (elt) { free_alias_data (elt->data); free (elt->key); /* alias name */ free (elt); /* XXX */ #if defined (PROGRAMMABLE_COMPLETION) set_itemlist_dirty (&it_aliases); #endif return (aliases->nentries); } return (-1); } /* Delete all aliases. */ void delete_all_aliases () { if (aliases == 0) return; hash_flush (aliases, free_alias_data); hash_dispose (aliases); aliases = (HASH_TABLE *)NULL; #if defined (PROGRAMMABLE_COMPLETION) set_itemlist_dirty (&it_aliases); #endif } /* Return an array of aliases that satisfy the conditions tested by FUNCTION. If FUNCTION is NULL, return all aliases. */ static alias_t ** map_over_aliases (function) sh_alias_map_func_t *function; { register int i; register BUCKET_CONTENTS *tlist; alias_t *alias, **list; int list_index; i = HASH_ENTRIES (aliases); if (i == 0) return ((alias_t **)NULL); list = (alias_t **)xmalloc ((i + 1) * sizeof (alias_t *)); for (i = list_index = 0; i < aliases->nbuckets; i++) { for (tlist = hash_items (i, aliases); tlist; tlist = tlist->next) { alias = (alias_t *)tlist->data; if (!function || (*function) (alias)) { list[list_index++] = alias; list[list_index] = (alias_t *)NULL; } } } return (list); } static void sort_aliases (array) alias_t **array; { qsort (array, strvec_len ((char **)array), sizeof (alias_t *), (QSFUNC *)qsort_alias_compare); } static int qsort_alias_compare (as1, as2) alias_t **as1, **as2; { int result; if ((result = (*as1)->name[0] - (*as2)->name[0]) == 0) result = strcmp ((*as1)->name, (*as2)->name); return (result); } /* Return a sorted list of all defined aliases */ alias_t ** all_aliases () { alias_t **list; if (aliases == 0 || HASH_ENTRIES (aliases) == 0) return ((alias_t **)NULL); list = map_over_aliases ((sh_alias_map_func_t *)NULL); if (list) sort_aliases (list); return (list); } char * alias_expand_word (s) char *s; { alias_t *r; r = find_alias (s); return (r ? savestring (r->value) : (char *)NULL); } /* Readline support functions -- expand all aliases in a line. */ #if defined (READLINE) /* Return non-zero if CHARACTER is a member of the class of characters that are self-delimiting in the shell (this really means that these characters delimit tokens). */ #define self_delimiting(character) (member ((character), " \t\n\r;|&()")) /* Return non-zero if CHARACTER is a member of the class of characters that delimit commands in the shell. */ #define command_separator(character) (member ((character), "\r\n;|&(")) /* If this is 1, we are checking the next token read for alias expansion because it is the first word in a command. */ static int command_word; /* This is for skipping quoted strings in alias expansions. */ #define quote_char(c) (((c) == '\'') || ((c) == '"')) /* Consume a quoted string from STRING, starting at string[START] (so string[START] is the opening quote character), and return the index of the closing quote character matching the opening quote character. This handles single matching pairs of unquoted quotes; it could afford to be a little smarter... This skips words between balanced pairs of quotes, words where the first character is quoted with a `\', and other backslash-escaped characters. */ static int skipquotes (string, start) char *string; int start; { register int i; int delimiter = string[start]; /* i starts at START + 1 because string[START] is the opening quote character. */ for (i = start + 1 ; string[i] ; i++) { if (string[i] == '\\') { i++; /* skip backslash-quoted quote characters, too */ continue; } if (string[i] == delimiter) return i; } return (i); } /* Skip the white space and any quoted characters in STRING, starting at START. Return the new index into STRING, after zero or more characters have been skipped. */ static int skipws (string, start) char *string; int start; { register int i; int pass_next, backslash_quoted_word; unsigned char peekc; /* skip quoted strings, in ' or ", and words in which a character is quoted with a `\'. */ i = backslash_quoted_word = pass_next = 0; /* Skip leading whitespace (or separator characters), and quoted words. But save it in the output. */ for (i = start; string[i]; i++) { if (pass_next) { pass_next = 0; continue; } if (whitespace (string[i])) { backslash_quoted_word = 0; /* we are no longer in a backslash-quoted word */ continue; } if (string[i] == '\\') { peekc = string[i+1]; if (ISLETTER (peekc)) backslash_quoted_word++; /* this is a backslash-quoted word */ else pass_next++; continue; } /* This only handles single pairs of non-escaped quotes. This overloads backslash_quoted_word to also mean that a word like ""f is being scanned, so that the quotes will inhibit any expansion of the word. */ if (quote_char(string[i])) { i = skipquotes (string, i); /* This could be a line that contains a single quote character, in which case skipquotes () terminates with string[i] == '\0' (the end of the string). Check for that here. */ if (string[i] == '\0') break; peekc = string[i + 1]; if (ISLETTER (peekc)) backslash_quoted_word++; continue; } /* If we're in the middle of some kind of quoted word, let it pass through. */ if (backslash_quoted_word) continue; /* If this character is a shell command separator, then set a hint for alias_expand that the next token is the first word in a command. */ if (command_separator (string[i])) { command_word++; continue; } break; } return (i); } /* Characters that may appear in a token. Basically, anything except white space and a token separator. */ #define token_char(c) (!((whitespace (string[i]) || self_delimiting (string[i])))) /* Read from START in STRING until the next separator character, and return the index of that separator. Skip backslash-quoted characters. Call skipquotes () for quoted strings in the middle or at the end of tokens, so all characters show up (e.g. foo'' and foo""bar) */ static int rd_token (string, start) char *string; int start; { register int i; /* From here to next separator character is a token. */ for (i = start; string[i] && token_char (string[i]); i++) { if (string[i] == '\\') { i++; /* skip backslash-escaped character */ continue; } /* If this character is a quote character, we want to call skipquotes to get the whole quoted portion as part of this word. That word will not generally match an alias, even if te unquoted word would have. The presence of the quotes in the token serves then to inhibit expansion. */ if (quote_char (string[i])) { i = skipquotes (string, i); /* This could be a line that contains a single quote character, in which case skipquotes () terminates with string[i] == '\0' (the end of the string). Check for that here. */ if (string[i] == '\0') break; /* Now string[i] is the matching quote character, and the quoted portion of the token has been scanned. */ continue; } } return (i); } /* Return a new line, with any aliases substituted. */ char * alias_expand (string) char *string; { register int i, j, start; char *line, *token; int line_len, tl, real_start, expand_next, expand_this_token; alias_t *alias; line_len = strlen (string) + 1; line = (char *)xmalloc (line_len); token = (char *)xmalloc (line_len); line[0] = i = 0; expand_next = 0; command_word = 1; /* initialized to expand the first word on the line */ /* Each time through the loop we find the next word in line. If it has an alias, substitute the alias value. If the value ends in ` ', then try again with the next word. Else, if there is no value, or if the value does not end in space, we are done. */ for (;;) { token[0] = 0; start = i; /* Skip white space and quoted characters */ i = skipws (string, start); if (start == i && string[i] == '\0') { free (token); return (line); } /* copy the just-skipped characters into the output string, expanding it if there is not enough room. */ j = strlen (line); tl = i - start; /* number of characters just skipped */ RESIZE_MALLOCED_BUFFER (line, j, (tl + 1), line_len, (tl + 50)); strncpy (line + j, string + start, tl); line[j + tl] = '\0'; real_start = i; command_word = command_word || (command_separator (string[i])); expand_this_token = (command_word || expand_next); expand_next = 0; /* Read the next token, and copy it into TOKEN. */ start = i; i = rd_token (string, start); tl = i - start; /* token length */ /* If tl == 0, but we're not at the end of the string, then we have a single-character token, probably a delimiter */ if (tl == 0 && string[i] != '\0') { tl = 1; i++; /* move past it */ } strncpy (token, string + start, tl); token [tl] = '\0'; /* If there is a backslash-escaped character quoted in TOKEN, then we don't do alias expansion. This should check for all other quoting characters, too. */ if (xstrchr (token, '\\')) expand_this_token = 0; /* If we should be expanding here, if we are expanding all words, or if we are in a location in the string where an expansion is supposed to take place, see if this word has a substitution. If it does, then do the expansion. Note that we defer the alias value lookup until we are sure we are expanding this token. */ if ((token[0]) && (expand_this_token || alias_expand_all) && (alias = find_alias (token))) { char *v; int vlen, llen; v = alias->value; vlen = strlen (v); llen = strlen (line); /* +3 because we possibly add one more character below. */ RESIZE_MALLOCED_BUFFER (line, llen, (vlen + 3), line_len, (vlen + 50)); strcpy (line + llen, v); if ((expand_this_token && vlen && whitespace (v[vlen - 1])) || alias_expand_all) expand_next = 1; } else { int llen, tlen; llen = strlen (line); tlen = i - real_start; /* tlen == strlen(token) */ RESIZE_MALLOCED_BUFFER (line, llen, (tlen + 1), line_len, (llen + tlen + 50)); strncpy (line + llen, string + real_start, tlen); line[llen + tlen] = '\0'; } command_word = 0; } } #endif /* READLINE */ #endif /* ALIAS */
/* * array.c - functions to create, destroy, access, and manipulate arrays * of strings. * * Arrays are sparse doubly-linked lists. An element's index is stored * with it. * * Chet Ramey * chet@ins.cwru.edu */ /* Copyright (C) 1997-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if defined (ARRAY_VARS) #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include <stdio.h> #include "bashansi.h" #include "shell.h" #include "array.h" #include "builtins/common.h" #define ADD_BEFORE(ae, new) \ do { \ ae->prev->next = new; \ new->prev = ae->prev; \ ae->prev = new; \ new->next = ae; \ } while(0) static char *array_to_string_internal __P((ARRAY_ELEMENT *, ARRAY_ELEMENT *, char *, int)); ARRAY * array_create() { ARRAY *r; ARRAY_ELEMENT *head; r =(ARRAY *)xmalloc(sizeof(ARRAY)); r->type = array_indexed; r->max_index = -1; r->num_elements = 0; head = array_create_element(-1, (char *)NULL); /* dummy head */ head->prev = head->next = head; r->head = head; return(r); } void array_flush (a) ARRAY *a; { register ARRAY_ELEMENT *r, *r1; if (a == 0) return; for (r = element_forw(a->head); r != a->head; ) { r1 = element_forw(r); array_dispose_element(r); r = r1; } a->head->next = a->head->prev = a->head; a->max_index = -1; a->num_elements = 0; } void array_dispose(a) ARRAY *a; { if (a == 0) return; array_flush (a); array_dispose_element(a->head); free(a); } ARRAY * array_copy(a) ARRAY *a; { ARRAY *a1; ARRAY_ELEMENT *ae, *new; if (!a) return((ARRAY *) NULL); a1 = array_create(); a1->type = a->type; a1->max_index = a->max_index; a1->num_elements = a->num_elements; for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { new = array_create_element(element_index(ae), element_value(ae)); ADD_BEFORE(a1->head, new); } return(a1); } #ifdef INCLUDE_UNUSED /* * Make and return a new array composed of the elements in array A from * S to E, inclusive. */ ARRAY * array_slice(array, s, e) ARRAY *array; ARRAY_ELEMENT *s, *e; { ARRAY *a; ARRAY_ELEMENT *p, *n; int i; arrayind_t mi; a = array_create (); a->type = array->type; for (p = s, i = 0; p != e; p = element_forw(p), i++) { n = array_create_element (element_index(p), element_value(p)); ADD_BEFORE(a->head, n); mi = element_index(ae); } a->num_elements = i; a->max_index = mi; return a; } #endif /* * Walk the array, calling FUNC once for each element, with the array * element as the argument. */ void array_walk(a, func) ARRAY *a; sh_ae_map_func_t *func; { register ARRAY_ELEMENT *ae; if (a == 0 || array_empty(a)) return; for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) if ((*func)(ae) < 0) return; } /* * Shift the array A N elements to the left. Delete the first N elements * and subtract N from the indices of the remaining elements. If FLAGS * does not include AS_DISPOSE, this returns a singly-linked null-terminated * list of elements so the caller can dispose of the chain. If FLAGS * includes AS_DISPOSE, this function disposes of the shifted-out elements * and returns NULL. */ ARRAY_ELEMENT * array_shift(a, n, flags) ARRAY *a; int n, flags; { register ARRAY_ELEMENT *ae, *ret; register int i; if (a == 0 || array_empty(a) || n <= 0) return ((ARRAY_ELEMENT *)NULL); for (i = 0, ret = ae = element_forw(a->head); ae != a->head && i < n; ae = element_forw(ae), i++) ; if (ae == a->head) { /* Easy case; shifting out all of the elements */ if (flags & AS_DISPOSE) { array_flush (a); return ((ARRAY_ELEMENT *)NULL); } for (ae = ret; element_forw(ae) != a->head; ae = element_forw(ae)) ; element_forw(ae) = (ARRAY_ELEMENT *)NULL; a->head->next = a->head->prev = a->head; a->max_index = -1; a->num_elements = 0; return ret; } /* * ae now points to the list of elements we want to retain. * ret points to the list we want to either destroy or return. */ ae->prev->next = (ARRAY_ELEMENT *)NULL; /* null-terminate RET */ a->head->next = ae; /* slice RET out of the array */ ae->prev = a->head; for ( ; ae != a->head; ae = element_forw(ae)) element_index(ae) -= n; /* renumber retained indices */ a->num_elements -= n; /* modify bookkeeping information */ a->max_index -= n; if (flags & AS_DISPOSE) { for (ae = ret; ae; ) { ret = element_forw(ae); array_dispose_element(ae); ae = ret; } return ((ARRAY_ELEMENT *)NULL); } return ret; } /* * Shift array A right N indices. If S is non-null, it becomes the value of * the new element 0. Returns the number of elements in the array after the * shift. */ int array_rshift (a, n, s) ARRAY *a; int n; char *s; { register ARRAY_ELEMENT *ae, *new; if (a == 0) return 0; if (n <= 0) return (a->num_elements); ae = element_forw(a->head); if (s) { new = array_create_element(0, s); ADD_BEFORE(ae, new); a->num_elements++; } a->max_index += n; /* * Renumber all elements in the array except the one we just added. */ for ( ; ae != a->head; ae = element_forw(ae)) element_index(ae) += n; return (a->num_elements); } ARRAY * array_quote(array) ARRAY *array; { ARRAY_ELEMENT *a; char *t; if (array == 0 || array->head == 0 || array_empty (array)) return (ARRAY *)NULL; for (a = element_forw(array->head); a != array->head; a = element_forw(a)) { t = quote_string (a->value); FREE(a->value); a->value = t; } return array; } char * array_subrange (a, start, end, quoted) ARRAY *a; arrayind_t start, end; int quoted; { ARRAY_ELEMENT *h, *p; arrayind_t i; p = array_head (a); if (p == 0 || array_empty (a) || start > array_num_elements (a)) return ((char *)NULL); for (i = 0, p = element_forw(p); p != a->head && i < start; i++, p = element_forw(p)) ; if (p == a->head) return ((char *)NULL); for (h = p; p != a->head && i < end; i++, p = element_forw(p)) ; return (array_to_string_internal (h, p, " ", quoted)); } char * array_patsub (a, pat, rep, mflags) ARRAY *a; char *pat, *rep; int mflags; { ARRAY *a2; ARRAY_ELEMENT *e; char *t; if (array_head (a) == 0 || array_empty (a)) return ((char *)NULL); a2 = array_copy (a); for (e = element_forw(a2->head); e != a2->head; e = element_forw(e)) { t = pat_subst(element_value(e), pat, rep, mflags); FREE(element_value(e)); e->value = t; } if (mflags & MATCH_QUOTED) array_quote (a2); t = array_to_string (a2, " ", 0); array_dispose (a2); return t; } /* * Allocate and return a new array element with index INDEX and value * VALUE. */ ARRAY_ELEMENT * array_create_element(indx, value) arrayind_t indx; char *value; { ARRAY_ELEMENT *r; r = (ARRAY_ELEMENT *)xmalloc(sizeof(ARRAY_ELEMENT)); r->ind = indx; r->value = value ? savestring(value) : (char *)NULL; r->next = r->prev = (ARRAY_ELEMENT *) NULL; return(r); } #ifdef INCLUDE_UNUSED ARRAY_ELEMENT * array_copy_element(ae) ARRAY_ELEMENT *ae; { return(ae ? array_create_element(element_index(ae), element_value(ae)) : (ARRAY_ELEMENT *) NULL); } #endif void array_dispose_element(ae) ARRAY_ELEMENT *ae; { FREE(ae->value); free(ae); } /* * Add a new element with index I and value V to array A (a[i] = v). */ int array_insert(a, i, v) ARRAY *a; arrayind_t i; char *v; { register ARRAY_ELEMENT *new, *ae; if (!a) return(-1); new = array_create_element(i, v); if (i > array_max_index(a)) { /* * Hook onto the end. This also works for an empty array. * Fast path for the common case of allocating arrays * sequentially. */ ADD_BEFORE(a->head, new); a->max_index = i; a->num_elements++; return(0); } /* * Otherwise we search for the spot to insert it. */ for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { if (element_index(ae) == i) { /* * Replacing an existing element. */ array_dispose_element(new); free(element_value(ae)); ae->value = savestring(v); return(0); } else if (element_index(ae) > i) { ADD_BEFORE(ae, new); a->num_elements++; return(0); } } return (-1); /* problem */ } /* * Delete the element with index I from array A and return it so the * caller can dispose of it. */ ARRAY_ELEMENT * array_remove(a, i) ARRAY *a; arrayind_t i; { register ARRAY_ELEMENT *ae; if (!a || array_empty(a)) return((ARRAY_ELEMENT *) NULL); for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) if (element_index(ae) == i) { ae->next->prev = ae->prev; ae->prev->next = ae->next; a->num_elements--; if (i == array_max_index(a)) a->max_index = element_index(ae->prev); return(ae); } return((ARRAY_ELEMENT *) NULL); } /* * Return the value of a[i]. */ char * array_reference(a, i) ARRAY *a; arrayind_t i; { register ARRAY_ELEMENT *ae; if (a == 0 || array_empty(a)) return((char *) NULL); for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) if (element_index(ae) == i) return(element_value(ae)); return((char *) NULL); } /* Convenience routines for the shell to translate to and from the form used by the rest of the code. */ WORD_LIST * array_to_word_list(a) ARRAY *a; { WORD_LIST *list; ARRAY_ELEMENT *ae; if (a == 0 || array_empty(a)) return((WORD_LIST *)NULL); list = (WORD_LIST *)NULL; for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) list = make_word_list (make_bare_word(element_value(ae)), list); return (REVERSE_LIST(list, WORD_LIST *)); } ARRAY * array_from_word_list (list) WORD_LIST *list; { ARRAY *a; if (list == 0) return((ARRAY *)NULL); a = array_create(); return (array_assign_list (a, list)); } ARRAY * array_assign_list (array, list) ARRAY *array; WORD_LIST *list; { register WORD_LIST *l; register arrayind_t i; for (l = list, i = 0; l; l = l->next, i++) array_insert(array, i, l->word->word); return array; } char ** array_to_argv (a) ARRAY *a; { char **ret, *t; int i; ARRAY_ELEMENT *ae; if (a == 0 || array_empty(a)) return ((char **)NULL); ret = strvec_create (array_num_elements (a) + 1); i = 0; for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { t = element_value (ae); ret[i++] = t ? savestring (t) : (char *)NULL; } ret[i] = (char *)NULL; return (ret); } /* * Return a string that is the concatenation of all the elements in A, * separated by SEP. */ static char * array_to_string_internal (start, end, sep, quoted) ARRAY_ELEMENT *start, *end; char *sep; int quoted; { char *result, *t; ARRAY_ELEMENT *ae; int slen, rsize, rlen, reg; if (start == end) /* XXX - should not happen */ return ((char *)NULL); slen = strlen(sep); result = NULL; for (rsize = rlen = 0, ae = start; ae != end; ae = element_forw(ae)) { if (rsize == 0) result = (char *)xmalloc (rsize = 64); if (element_value(ae)) { t = quoted ? quote_string(element_value(ae)) : element_value(ae); reg = strlen(t); RESIZE_MALLOCED_BUFFER (result, rlen, (reg + slen + 2), rsize, rsize); strcpy(result + rlen, t); rlen += reg; if (quoted && t) free(t); /* * Add a separator only after non-null elements. */ if (element_forw(ae) != end) { strcpy(result + rlen, sep); rlen += slen; } } } if (result) result[rlen] = '\0'; /* XXX */ return(result); } char * array_to_assign (a, quoted) ARRAY *a; int quoted; { char *result, *valstr, *is; char indstr[INT_STRLEN_BOUND(intmax_t) + 1]; ARRAY_ELEMENT *ae; int rsize, rlen, elen; if (a == 0 || array_empty (a)) return((char *)NULL); result = (char *)xmalloc (rsize = 128); result[0] = '('; rlen = 1; for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { is = inttostr (element_index(ae), indstr, sizeof(indstr)); valstr = element_value (ae) ? sh_double_quote (element_value(ae)) : (char *)NULL; elen = STRLEN (indstr) + 8 + STRLEN (valstr); RESIZE_MALLOCED_BUFFER (result, rlen, (elen + 1), rsize, rsize); result[rlen++] = '['; strcpy (result + rlen, is); rlen += STRLEN (is); result[rlen++] = ']'; result[rlen++] = '='; if (valstr) { strcpy (result + rlen, valstr); rlen += STRLEN (valstr); } if (element_forw(ae) != a->head) result[rlen++] = ' '; FREE (valstr); } RESIZE_MALLOCED_BUFFER (result, rlen, 1, rsize, 8); result[rlen++] = ')'; result[rlen] = '\0'; if (quoted) { /* This is not as efficient as it could be... */ valstr = sh_single_quote (result); free (result); result = valstr; } return(result); } char * array_to_string (a, sep, quoted) ARRAY *a; char *sep; int quoted; { if (a == 0) return((char *)NULL); if (array_empty(a)) return(savestring("")); return (array_to_string_internal (element_forw(a->head), a->head, sep, quoted)); } #if defined (INCLUDE_UNUSED) || defined (TEST_ARRAY) /* * Return an array consisting of elements in S, separated by SEP */ ARRAY * array_from_string(s, sep) char *s, *sep; { ARRAY *a; WORD_LIST *w; if (s == 0) return((ARRAY *)NULL); w = list_string (s, sep, 0); if (w == 0) return((ARRAY *)NULL); a = array_from_word_list (w); return (a); } #endif #if defined (TEST_ARRAY) /* * To make a running version, compile -DTEST_ARRAY and link with: * xmalloc.o syntax.o lib/malloc/libmalloc.a lib/sh/libsh.a */ int interrupt_immediately = 0; int signal_is_trapped(s) int s; { return 0; } void fatal_error(const char *s, ...) { fprintf(stderr, "array_test: fatal memory error\n"); abort(); } void programming_error(const char *s, ...) { fprintf(stderr, "array_test: fatal programming error\n"); abort(); } WORD_DESC * make_bare_word (s) const char *s; { WORD_DESC *w; w = (WORD_DESC *)xmalloc(sizeof(WORD_DESC)); w->word = s ? savestring(s) : savestring (""); w->flags = 0; return w; } WORD_LIST * make_word_list(x, l) WORD_DESC *x; WORD_LIST *l; { WORD_LIST *w; w = (WORD_LIST *)xmalloc(sizeof(WORD_LIST)); w->word = x; w->next = l; return w; } WORD_LIST * list_string(s, t, i) char *s, *t; int i; { char *r, *a; WORD_LIST *wl; if (s == 0) return (WORD_LIST *)NULL; r = savestring(s); wl = (WORD_LIST *)NULL; a = strtok(r, t); while (a) { wl = make_word_list (make_bare_word(a), wl); a = strtok((char *)NULL, t); } return (REVERSE_LIST (wl, WORD_LIST *)); } GENERIC_LIST * list_reverse (list) GENERIC_LIST *list; { register GENERIC_LIST *next, *prev; for (prev = 0; list; ) { next = list->next; list->next = prev; prev = list; list = next; } return prev; } char * pat_subst(s, t, u, i) char *s, *t, *u; int i; { return ((char *)NULL); } char * quote_string(s) char *s; { return savestring(s); } print_element(ae) ARRAY_ELEMENT *ae; { char lbuf[INT_STRLEN_BOUND (intmax_t) + 1]; printf("array[%s] = %s\n", inttostr (element_index(ae), lbuf, sizeof (lbuf)), element_value(ae)); } print_array(a) ARRAY *a; { printf("\n"); array_walk(a, print_element); } main() { ARRAY *a, *new_a, *copy_of_a; ARRAY_ELEMENT *ae, *aew; char *s; a = array_create(); array_insert(a, 1, "one"); array_insert(a, 7, "seven"); array_insert(a, 4, "four"); array_insert(a, 1029, "one thousand twenty-nine"); array_insert(a, 12, "twelve"); array_insert(a, 42, "forty-two"); print_array(a); s = array_to_string (a, " ", 0); printf("s = %s\n", s); copy_of_a = array_from_string(s, " "); printf("copy_of_a:"); print_array(copy_of_a); array_dispose(copy_of_a); printf("\n"); free(s); ae = array_remove(a, 4); array_dispose_element(ae); ae = array_remove(a, 1029); array_dispose_element(ae); array_insert(a, 16, "sixteen"); print_array(a); s = array_to_string (a, " ", 0); printf("s = %s\n", s); copy_of_a = array_from_string(s, " "); printf("copy_of_a:"); print_array(copy_of_a); array_dispose(copy_of_a); printf("\n"); free(s); array_insert(a, 2, "two"); array_insert(a, 1029, "new one thousand twenty-nine"); array_insert(a, 0, "zero"); array_insert(a, 134, ""); print_array(a); s = array_to_string (a, ":", 0); printf("s = %s\n", s); copy_of_a = array_from_string(s, ":"); printf("copy_of_a:"); print_array(copy_of_a); array_dispose(copy_of_a); printf("\n"); free(s); new_a = array_copy(a); print_array(new_a); s = array_to_string (new_a, ":", 0); printf("s = %s\n", s); copy_of_a = array_from_string(s, ":"); free(s); printf("copy_of_a:"); print_array(copy_of_a); array_shift(copy_of_a, 2, AS_DISPOSE); printf("copy_of_a shifted by two:"); print_array(copy_of_a); ae = array_shift(copy_of_a, 2, 0); printf("copy_of_a shifted by two:"); print_array(copy_of_a); for ( ; ae; ) { aew = element_forw(ae); array_dispose_element(ae); ae = aew; } array_rshift(copy_of_a, 1, (char *)0); printf("copy_of_a rshift by 1:"); print_array(copy_of_a); array_rshift(copy_of_a, 2, "new element zero"); printf("copy_of_a rshift again by 2 with new element zero:"); print_array(copy_of_a); s = array_to_assign(copy_of_a, 0); printf("copy_of_a=%s\n", s); free(s); ae = array_shift(copy_of_a, array_num_elements(copy_of_a), 0); for ( ; ae; ) { aew = element_forw(ae); array_dispose_element(ae); ae = aew; } array_dispose(copy_of_a); printf("\n"); array_dispose(a); array_dispose(new_a); } #endif /* TEST_ARRAY */ #endif /* ARRAY_VARS */
/* arrayfunc.c -- High-level array functions used by other parts of the shell. */ /* Copyright (C) 2001-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if defined (ARRAY_VARS) #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <stdio.h> #include "shell.h" #include "shmbutil.h" #include "builtins/common.h" extern char *this_command_name; extern int last_command_exit_value; static void quote_array_assignment_chars __P((WORD_LIST *)); static char *array_value_internal __P((char *, int, int, int *)); /* **************************************************************** */ /* */ /* Functions to manipulate array variables and perform assignments */ /* */ /* **************************************************************** */ /* Convert a shell variable to an array variable. The original value is saved as array[0]. */ SHELL_VAR * convert_var_to_array (var) SHELL_VAR *var; { char *oldval; ARRAY *array; oldval = value_cell (var); array = array_create (); array_insert (array, 0, oldval); FREE (value_cell (var)); var_setarray (var, array); /* these aren't valid anymore */ var->dynamic_value = (sh_var_value_func_t *)NULL; var->assign_func = (sh_var_assign_func_t *)NULL; INVALIDATE_EXPORTSTR (var); VSETATTR (var, att_array); VUNSETATTR (var, att_invisible); return var; } /* Perform an array assignment name[ind]=value. If NAME already exists and is not an array, and IND is 0, perform name=value instead. If NAME exists and is not an array, and IND is not 0, convert it into an array with the existing value as name[0]. If NAME does not exist, just create an array variable, no matter what IND's value may be. */ SHELL_VAR * bind_array_variable (name, ind, value) char *name; arrayind_t ind; char *value; { SHELL_VAR *entry; char *newval; entry = var_lookup (name, shell_variables); if (entry == (SHELL_VAR *) 0) entry = make_new_array_variable (name); else if (readonly_p (entry) || noassign_p (entry)) { if (readonly_p (entry)) err_readonly (name); return (entry); } else if (array_p (entry) == 0) entry = convert_var_to_array (entry); /* ENTRY is an array variable, and ARRAY points to the value. */ newval = make_variable_value (entry, value); if (entry->assign_func) (*entry->assign_func) (entry, newval, ind); else array_insert (array_cell (entry), ind, newval); FREE (newval); return (entry); } /* Parse NAME, a lhs of an assignment statement of the form v[s], and assign VALUE to that array element by calling bind_array_variable(). */ SHELL_VAR * assign_array_element (name, value) char *name, *value; { char *sub, *vname; arrayind_t ind; int sublen; SHELL_VAR *entry; vname = array_variable_name (name, &sub, &sublen); if (vname == 0) return ((SHELL_VAR *)NULL); if ((ALL_ELEMENT_SUB (sub[0]) && sub[1] == ']') || (sublen <= 1)) { free (vname); err_badarraysub (name); return ((SHELL_VAR *)NULL); } ind = array_expand_index (sub, sublen); if (ind < 0) { free (vname); err_badarraysub (name); return ((SHELL_VAR *)NULL); } entry = bind_array_variable (vname, ind, value); free (vname); return (entry); } /* Find the array variable corresponding to NAME. If there is no variable, create a new array variable. If the variable exists but is not an array, convert it to an indexed array. If CHECK_FLAGS is non-zero, an existing variable is checked for the readonly or noassign attribute in preparation for assignment (e.g., by the `read' builtin). */ SHELL_VAR * find_or_make_array_variable (name, check_flags) char *name; int check_flags; { SHELL_VAR *var; var = find_variable (name); if (var == 0) var = make_new_array_variable (name); else if (check_flags && (readonly_p (var) || noassign_p (var))) { if (readonly_p (var)) err_readonly (name); return ((SHELL_VAR *)NULL); } else if (array_p (var) == 0) var = convert_var_to_array (var); return (var); } /* Perform a compound assignment statement for array NAME, where VALUE is the text between the parens: NAME=( VALUE ) */ SHELL_VAR * assign_array_from_string (name, value) char *name, *value; { SHELL_VAR *var; var = find_or_make_array_variable (name, 1); if (var == 0) return ((SHELL_VAR *)NULL); return (assign_array_var_from_string (var, value)); } /* Sequentially assign the indices of indexed array variable VAR from the words in LIST. */ SHELL_VAR * assign_array_var_from_word_list (var, list) SHELL_VAR *var; WORD_LIST *list; { register arrayind_t i; register WORD_LIST *l; ARRAY *a; for (a = array_cell (var), l = list, i = 0; l; l = l->next, i++) if (var->assign_func) (*var->assign_func) (var, l->word->word, i); else array_insert (a, i, l->word->word); return var; } /* Perform a compound array assignment: VAR->name=( VALUE ). The VALUE has already had the parentheses stripped. */ SHELL_VAR * assign_array_var_from_string (var, value) SHELL_VAR *var; char *value; { ARRAY *a; WORD_LIST *list, *nlist; char *w, *val, *nval; int ni, len; arrayind_t ind, last_ind; if (value == 0) return var; /* If this is called from declare_builtin, value[0] == '(' and xstrchr(value, ')') != 0. In this case, we need to extract the value from between the parens before going on. */ if (*value == '(') /*)*/ { ni = 1; val = extract_array_assignment_list (value, &ni); if (val == 0) return var; } else val = value; /* Expand the value string into a list of words, performing all the shell expansions including pathname generation and word splitting. */ /* First we split the string on whitespace, using the shell parser (ksh93 seems to do this). */ list = parse_string_to_word_list (val, "array assign"); /* If we're using [subscript]=value, we need to quote each [ and ] to prevent unwanted filename expansion. */ if (list) quote_array_assignment_chars (list); /* Now that we've split it, perform the shell expansions on each word in the list. */ nlist = list ? expand_words_no_vars (list) : (WORD_LIST *)NULL; dispose_words (list); if (val != value) free (val); a = array_cell (var); /* Now that we are ready to assign values to the array, kill the existing value. */ if (a) array_flush (a); for (last_ind = 0, list = nlist; list; list = list->next) { w = list->word->word; /* We have a word of the form [ind]=value */ if (w[0] == '[') { len = skipsubscript (w, 0); if (w[len] != ']' || w[len+1] != '=') { nval = make_variable_value (var, w); if (var->assign_func) (*var->assign_func) (var, nval, last_ind); else array_insert (a, last_ind, nval); FREE (nval); last_ind++; continue; } if (len == 1) { err_badarraysub (w); continue; } if (ALL_ELEMENT_SUB (w[1]) && len == 2) { report_error ("%s: cannot assign to non-numeric index", w); continue; } ind = array_expand_index (w + 1, len); if (ind < 0) { err_badarraysub (w); continue; } last_ind = ind; val = w + len + 2; } else /* No [ind]=value, just a stray `=' */ { ind = last_ind; val = w; } if (integer_p (var)) this_command_name = (char *)NULL; /* no command name for errors */ nval = make_variable_value (var, val); if (var->assign_func) (*var->assign_func) (var, nval, ind); else array_insert (a, ind, nval); FREE (nval); last_ind++; } dispose_words (nlist); return (var); } /* For each word in a compound array assignment, if the word looks like [ind]=value, quote the `[' and `]' before the `=' to protect them from unwanted filename expansion. */ static void quote_array_assignment_chars (list) WORD_LIST *list; { char *s, *t, *nword; int saw_eq; WORD_LIST *l; for (l = list; l; l = l->next) { if (l->word == 0 || l->word->word == 0 || l->word->word[0] == '\0') continue; /* should not happen, but just in case... */ /* Don't bother if it doesn't look like [ind]=value */ if (l->word->word[0] != '[' || xstrchr (l->word->word, '=') == 0) /* ] */ continue; s = nword = (char *)xmalloc (strlen (l->word->word) * 2 + 1); saw_eq = 0; for (t = l->word->word; *t; ) { if (*t == '=') saw_eq = 1; if (saw_eq == 0 && (*t == '[' || *t == ']')) *s++ = '\\'; *s++ = *t++; } *s = '\0'; free (l->word->word); l->word->word = nword; } } /* This function assumes s[i] == '['; returns with s[ret] == ']' if an array subscript is correctly parsed. */ int skipsubscript (s, i) const char *s; int i; { int count, c; #if defined (HANDLE_MULTIBYTE) mbstate_t state, state_bak; size_t slength, mblength; size_t mb_cur_max; #endif #if defined (HANDLE_MULTIBYTE) memset (&state, '\0', sizeof (mbstate_t)); slength = strlen (s + i); mb_cur_max = MB_CUR_MAX; #endif count = 1; while (count) { /* Advance one (possibly multibyte) character in S starting at I. */ #if defined (HANDLE_MULTIBYTE) if (mb_cur_max > 1) { state_bak = state; mblength = mbrlen (s + i, slength, &state); if (mblength == (size_t)-2 || mblength == (size_t)-1) { state = state_bak; i++; slength--; } else if (mblength == 0) return i; else { i += mblength; slength -= mblength; } } else #endif ++i; c = s[i]; if (c == 0) break; else if (c == '[') count++; else if (c == ']') count--; } return i; } /* This function is called with SUB pointing to just after the beginning `[' of an array subscript and removes the array element to which SUB expands from array VAR. A subscript of `*' or `@' unsets the array. */ int unbind_array_element (var, sub) SHELL_VAR *var; char *sub; { int len; arrayind_t ind; ARRAY_ELEMENT *ae; len = skipsubscript (sub, 0); if (sub[len] != ']' || len == 0) { builtin_error ("%s[%s: bad array subscript", var->name, sub); return -1; } sub[len] = '\0'; if (ALL_ELEMENT_SUB (sub[0]) && sub[1] == 0) { unbind_variable (var->name); return (0); } ind = array_expand_index (sub, len+1); if (ind < 0) { builtin_error ("[%s]: bad array subscript", sub); return -1; } ae = array_remove (array_cell (var), ind); if (ae) array_dispose_element (ae); return 0; } /* Format and output an array assignment in compound form VAR=(VALUES), suitable for re-use as input. */ void print_array_assignment (var, quoted) SHELL_VAR *var; int quoted; { char *vstr; vstr = array_to_assign (array_cell (var), quoted); if (vstr == 0) printf ("%s=%s\n", var->name, quoted ? "'()'" : "()"); else { printf ("%s=%s\n", var->name, vstr); free (vstr); } } /***********************************************************************/ /* */ /* Utility functions to manage arrays and their contents for expansion */ /* */ /***********************************************************************/ /* Return 1 if NAME is a properly-formed array reference v[sub]. */ int valid_array_reference (name) char *name; { char *t; int r, len; t = xstrchr (name, '['); /* ] */ if (t) { *t = '\0'; r = legal_identifier (name); *t = '['; if (r == 0) return 0; /* Check for a properly-terminated non-blank subscript. */ len = skipsubscript (t, 0); if (t[len] != ']' || len == 1) return 0; for (r = 1; r < len; r++) if (whitespace (t[r]) == 0) return 1; return 0; } return 0; } /* Expand the array index beginning at S and extending LEN characters. */ arrayind_t array_expand_index (s, len) char *s; int len; { char *exp, *t; int expok; arrayind_t val; exp = (char *)xmalloc (len); strncpy (exp, s, len - 1); exp[len - 1] = '\0'; t = expand_string_to_string (exp, 0); this_command_name = (char *)NULL; val = evalexp (t, &expok); free (t); free (exp); if (expok == 0) { last_command_exit_value = EXECUTION_FAILURE; jump_to_top_level (DISCARD); } return val; } /* Return the name of the variable specified by S without any subscript. If SUBP is non-null, return a pointer to the start of the subscript in *SUBP. If LENP is non-null, the length of the subscript is returned in *LENP. This returns newly-allocated memory. */ char * array_variable_name (s, subp, lenp) char *s, **subp; int *lenp; { char *t, *ret; int ind, ni; t = xstrchr (s, '['); if (t == 0) return ((char *)NULL); ind = t - s; ni = skipsubscript (s, ind); if (ni <= ind + 1 || s[ni] != ']') { err_badarraysub (s); return ((char *)NULL); } *t = '\0'; ret = savestring (s); *t++ = '['; /* ] */ if (subp) *subp = t; if (lenp) *lenp = ni - ind; return ret; } /* Return the variable specified by S without any subscript. If SUBP is non-null, return a pointer to the start of the subscript in *SUBP. If LENP is non-null, the length of the subscript is returned in *LENP. */ SHELL_VAR * array_variable_part (s, subp, lenp) char *s, **subp; int *lenp; { char *t; SHELL_VAR *var; t = array_variable_name (s, subp, lenp); if (t == 0) return ((SHELL_VAR *)NULL); var = find_variable (t); free (t); return var; } /* Return a string containing the elements in the array and subscript described by S. If the subscript is * or @, obeys quoting rules akin to the expansion of $* and $@ including double quoting. If RTYPE is non-null it gets 1 if the array reference is name[@] or name[*] and 0 otherwise. */ static char * array_value_internal (s, quoted, allow_all, rtype) char *s; int quoted, allow_all, *rtype; { int len; arrayind_t ind; char *retval, *t, *temp; WORD_LIST *l; SHELL_VAR *var; var = array_variable_part (s, &t, &len); /* Expand the index, even if the variable doesn't exist, in case side effects are needed, like ${w[i++]} where w is unset. */ #if 0 if (var == 0) return (char *)NULL; #endif /* [ */ if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']') { if (rtype) *rtype = 1; if (allow_all == 0) { err_badarraysub (s); return ((char *)NULL); } else if (var == 0) return ((char *)NULL); else if (array_p (var) == 0) l = add_string_to_list (value_cell (var), (WORD_LIST *)NULL); else { l = array_to_word_list (array_cell (var)); if (l == (WORD_LIST *)NULL) return ((char *) NULL); } if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) { temp = string_list_dollar_star (l); retval = quote_string (temp); free (temp); } else /* ${name[@]} or unquoted ${name[*]} */ retval = string_list_dollar_at (l, quoted); dispose_words (l); } else { if (rtype) *rtype = 0; ind = array_expand_index (t, len); if (ind < 0) { if (var) err_badarraysub (var->name); else { t[-1] = '\0'; err_badarraysub (s); t[-1] = '['; /* ] */ } return ((char *)NULL); } if (var == 0) return ((char *)NULL); if (array_p (var) == 0) return (ind == 0 ? value_cell (var) : (char *)NULL); retval = array_reference (array_cell (var), ind); } return retval; } /* Return a string containing the elements described by the array and subscript contained in S, obeying quoting for subscripts * and @. */ char * array_value (s, quoted, rtype) char *s; int quoted, *rtype; { return (array_value_internal (s, quoted, 1, rtype)); } /* Return the value of the array indexing expression S as a single string. If ALLOW_ALL is 0, do not allow `@' and `*' subscripts. This is used by other parts of the shell such as the arithmetic expression evaluator in expr.c. */ char * get_array_value (s, allow_all, rtype) char *s; int allow_all, *rtype; { return (array_value_internal (s, 0, allow_all, rtype)); } #endif /* ARRAY_VARS */
/* braces.c -- code for doing word expansion in curly braces. */ /* Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ /* Stuff in curly braces gets expanded before all other shell expansions. */ #include "config.h" #if defined (BRACE_EXPANSION) #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "bashansi.h" #if defined (SHELL) # include "shell.h" #endif /* SHELL */ #include "general.h" #include "shmbutil.h" #define brace_whitespace(c) (!(c) || (c) == ' ' || (c) == '\t' || (c) == '\n') /* Basic idea: Segregate the text into 3 sections: preamble (stuff before an open brace), postamble (stuff after the matching close brace) and amble (stuff after preamble, and before postamble). Expand amble, and then tack on the expansions to preamble. Expand postamble, and tack on the expansions to the result so far. */ /* The character which is used to separate arguments. */ int brace_arg_separator = ','; #if defined (__P) static int brace_gobbler __P((char *, size_t, int *, int)); static char **expand_amble __P((char *, size_t)); static char **array_concat __P((char **, char **)); #else static int brace_gobbler (); static char **expand_amble (); static char **array_concat (); #endif /* Return an array of strings; the brace expansion of TEXT. */ char ** brace_expand (text) char *text; { register int start; size_t tlen; char *preamble, *postamble, *amble; size_t alen; char **tack, **result; int i, j, c; DECLARE_MBSTATE; /* Find the text of the preamble. */ tlen = strlen (text); i = 0; c = brace_gobbler (text, tlen, &i, '{'); preamble = (char *)xmalloc (i + 1); strncpy (preamble, text, i); preamble[i] = '\0'; result = (char **)xmalloc (2 * sizeof (char *)); result[0] = preamble; result[1] = (char *)NULL; /* Special case. If we never found an exciting character, then the preamble is all of the text, so just return that. */ if (c != '{') return (result); /* Find the amble. This is the stuff inside this set of braces. */ start = ++i; c = brace_gobbler (text, tlen, &i, '}'); /* What if there isn't a matching close brace? */ if (c == 0) { #if defined (NOTDEF) /* Well, if we found an unquoted BRACE_ARG_SEPARATOR between START and I, then this should be an error. Otherwise, it isn't. */ j = start; while (j < i) { if (text[j] == '\\') { j++; ADVANCE_CHAR (text, tlen, j); continue; } if (text[j] == brace_arg_separator) { strvec_dispose (result); report_error ("missing `}'"); throw_to_top_level (); } ADVANCE_CHAR (text, tlen, j); } #endif free (preamble); /* Same as result[0]; see initialization. */ result[0] = savestring (text); return (result); } #if defined (SHELL) amble = substring (text, start, i); alen = i - start; #else amble = (char *)xmalloc (1 + (i - start)); strncpy (amble, &text[start], (i - start)); alen = i - start; amble[alen] = '\0'; #endif #if defined (SHELL) INITIALIZE_MBSTATE; /* If the amble does not contain an unquoted BRACE_ARG_SEPARATOR, then just return without doing any expansion. */ j = 0; while (amble[j]) { if (amble[j] == '\\') { j++; ADVANCE_CHAR (amble, alen, j); continue; } if (amble[j] == brace_arg_separator) break; ADVANCE_CHAR (amble, alen, j); } if (!amble[j]) { free (amble); free (preamble); result[0] = savestring (text); return (result); } #endif /* SHELL */ postamble = &text[i + 1]; tack = expand_amble (amble, alen); result = array_concat (result, tack); free (amble); strvec_dispose (tack); tack = brace_expand (postamble); result = array_concat (result, tack); strvec_dispose (tack); return (result); } /* Expand the text found inside of braces. We simply try to split the text at BRACE_ARG_SEPARATORs into separate strings. We then brace expand each slot which needs it, until there are no more slots which need it. */ static char ** expand_amble (text, tlen) char *text; size_t tlen; { char **result, **partial; char *tem; int start, i, c; DECLARE_MBSTATE; result = (char **)NULL; start = i = 0; c = 1; while (c) { c = brace_gobbler (text, tlen, &i, brace_arg_separator); #if defined (SHELL) tem = substring (text, start, i); #else tem = (char *)xmalloc (1 + (i - start)); strncpy (tem, &text[start], (i - start)); tem[i- start] = '\0'; #endif partial = brace_expand (tem); if (!result) result = partial; else { register int lr = strvec_len (result); register int lp = strvec_len (partial); register int j; result = strvec_resize (result, lp + lr + 1); for (j = 0; j < lp; j++) result[lr + j] = partial[j]; result[lr + j] = (char *)NULL; free (partial); } free (tem); ADVANCE_CHAR (text, tlen, i); start = i; } return (result); } /* Start at INDEX, and skip characters in TEXT. Set INDEX to the index of the character matching SATISFY. This understands about quoting. Return the character that caused us to stop searching; this is either the same as SATISFY, or 0. */ static int brace_gobbler (text, tlen, indx, satisfy) char *text; size_t tlen; int *indx; int satisfy; { register int i, c, quoted, level, pass_next; #if defined (SHELL) int si; char *t; #endif DECLARE_MBSTATE; level = quoted = pass_next = 0; i = *indx; while (c = text[i]) { if (pass_next) { pass_next = 0; ADVANCE_CHAR (text, tlen, i); continue; } /* A backslash escapes the next character. This allows backslash to escape the quote character in a double-quoted string. */ if (c == '\\' && (quoted == 0 || quoted == '"' || quoted == '`')) { pass_next = 1; i++; continue; } if (quoted) { if (c == quoted) quoted = 0; ADVANCE_CHAR (text, tlen, i); continue; } if (c == '"' || c == '\'' || c == '`') { quoted = c; i++; continue; } #if defined (SHELL) /* Pass new-style command substitutions through unchanged. */ if (c == '$' && text[i+1] == '(') /* ) */ { si = i + 2; t = extract_command_subst (text, &si); i = si; free (t); i++; continue; } #endif if (c == satisfy && level == 0 && quoted == 0) { /* We ignore an open brace surrounded by whitespace, and also an open brace followed immediately by a close brace preceded by whitespace. */ if (c == '{' && ((!i || brace_whitespace (text[i - 1])) && (brace_whitespace (text[i + 1]) || text[i + 1] == '}'))) { i++; continue; } #if defined (SHELL) /* If this is being compiled as part of bash, ignore the `{' in a `${}' construct */ if ((c != '{') || i == 0 || (text[i - 1] != '$')) #endif /* SHELL */ break; } if (c == '{') level++; else if (c == '}' && level) level--; ADVANCE_CHAR (text, tlen, i); } *indx = i; return (c); } /* Return a new array of strings which is the result of appending each string in ARR2 to each string in ARR1. The resultant array is len (arr1) * len (arr2) long. For convenience, ARR1 (and its contents) are free ()'ed. ARR1 can be NULL, in that case, a new version of ARR2 is returned. */ static char ** array_concat (arr1, arr2) char **arr1, **arr2; { register int i, j, len, len1, len2; register char **result; if (arr1 == 0) return (strvec_copy (arr2)); if (arr2 == 0) return (strvec_copy (arr1)); len1 = strvec_len (arr1); len2 = strvec_len (arr2); result = (char **)xmalloc ((1 + (len1 * len2)) * sizeof (char *)); len = 0; for (i = 0; i < len1; i++) { int strlen_1 = strlen (arr1[i]); for (j = 0; j < len2; j++) { result[len] = (char *)xmalloc (1 + strlen_1 + strlen (arr2[j])); strcpy (result[len], arr1[i]); strcpy (result[len] + strlen_1, arr2[j]); len++; } free (arr1[i]); } free (arr1); result[len] = (char *)NULL; return (result); } #if defined (TEST) #include <stdio.h> fatal_error (format, arg1, arg2) char *format, *arg1, *arg2; { report_error (format, arg1, arg2); exit (1); } report_error (format, arg1, arg2) char *format, *arg1, *arg2; { fprintf (stderr, format, arg1, arg2); fprintf (stderr, "\n"); } main () { char example[256]; for (;;) { char **result; int i; fprintf (stderr, "brace_expand> "); if ((!fgets (example, 256, stdin)) || (strncmp (example, "quit", 4) == 0)) break; if (strlen (example)) example[strlen (example) - 1] = '\0'; result = brace_expand (example); for (i = 0; result[i]; i++) printf ("%s\n", result[i]); free_array (result); } } /* * Local variables: * compile-command: "gcc -g -Bstatic -DTEST -o brace_expand braces.c general.o" * end: */ #endif /* TEST */ #endif /* BRACE_EXPANSION */
/* bracecomp.c -- Complete a filename with the possible completions enclosed in csh-style braces such that the list of completions is available to the shell. */ /* Original version by tromey@cns.caltech.edu, Fri Feb 7 1992. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if defined (BRACE_EXPANSION) && defined (READLINE) #include <stdio.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "bashansi.h" #include "shell.h" #include <readline/readline.h> /* Find greatest common prefix of two strings. */ static int string_gcd (s1, s2) char *s1, *s2; { register int i; if (s1 == NULL || s2 == NULL) return (0); for (i = 0; *s1 && *s2; ++s1, ++s2, ++i) { if (*s1 != *s2) break; } return (i); } static char * really_munge_braces (array, real_start, real_end, gcd_zero) char **array; int real_start, real_end, gcd_zero; { int start, end, gcd; char *result, *subterm, *x; int result_size, flag, tlen; flag = 0; if (real_start == real_end) { x = array[real_start] ? sh_backslash_quote (array[real_start] + gcd_zero) : sh_backslash_quote (array[0]); return x; } result = (char *)xmalloc (result_size = 16); *result = '\0'; for (start = real_start; start < real_end; start = end + 1) { gcd = strlen (array[start]); for (end = start + 1; end < real_end; end++) { int temp; temp = string_gcd (array[start], array[end]); if (temp <= gcd_zero) break; gcd = temp; } end--; if (gcd_zero == 0 && start == real_start && end != (real_end - 1)) { /* In this case, add in a leading '{', because we are at top level, and there isn't a consistent prefix. */ result_size += 1; result = (char *)xrealloc (result, result_size); result[0] = '{'; result[1] = '\0'; flag++; } /* Make sure we backslash quote every substring we insert into the resultant brace expression. This is so the default filename quoting function won't inappropriately quote the braces. */ if (start == end) { x = savestring (array[start] + gcd_zero); subterm = sh_backslash_quote (x); free (x); } else { /* If there is more than one element in the subarray, insert the (quoted) prefix and an opening brace. */ tlen = gcd - gcd_zero; x = (char *)xmalloc (tlen + 1); strncpy (x, array[start] + gcd_zero, tlen); x[tlen] = '\0'; subterm = sh_backslash_quote (x); free (x); result_size += strlen (subterm) + 1; result = (char *)xrealloc (result, result_size); strcat (result, subterm); free (subterm); strcat (result, "{"); subterm = really_munge_braces (array, start, end + 1, gcd); subterm[strlen (subterm) - 1] = '}'; } result_size += strlen (subterm) + 1; result = (char *)xrealloc (result, result_size); strcat (result, subterm); strcat (result, ","); free (subterm); } if (gcd_zero == 0) result[strlen (result) - 1] = flag ? '}' : '\0'; return (result); } static int hack_braces_completion (names) char **names; { register int i; char *temp; temp = really_munge_braces (names, 1, strvec_len (names), 0); for (i = 0; names[i]; ++i) { free (names[i]); names[i] = NULL; } names[0] = temp; return 0; } /* We handle quoting ourselves within hack_braces_completion, so we turn off rl_filename_quoting_desired and rl_filename_quoting_function. */ int bash_brace_completion (count, ignore) int count, ignore; { rl_compignore_func_t *orig_ignore_func; rl_compentry_func_t *orig_entry_func; rl_quote_func_t *orig_quoting_func; rl_completion_func_t *orig_attempt_func; int orig_quoting_desired, r; orig_ignore_func = rl_ignore_some_completions_function; orig_attempt_func = rl_attempted_completion_function; orig_entry_func = rl_completion_entry_function; orig_quoting_func = rl_filename_quoting_function; orig_quoting_desired = rl_filename_quoting_desired; rl_completion_entry_function = rl_filename_completion_function; rl_attempted_completion_function = (rl_completion_func_t *)NULL; rl_ignore_some_completions_function = hack_braces_completion; rl_filename_quoting_function = (rl_quote_func_t *)NULL; rl_filename_quoting_desired = 0; r = rl_complete_internal (TAB); rl_ignore_some_completions_function = orig_ignore_func; rl_attempted_completion_function = orig_attempt_func; rl_completion_entry_function = orig_entry_func; rl_filename_quoting_function = orig_quoting_func; rl_filename_quoting_desired = orig_quoting_desired; return r; } #endif /* BRACE_EXPANSION && READLINE */
/* bashhist.c -- bash interface to the GNU history library. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if defined (HISTORY) #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "bashtypes.h" #include <stdio.h> #include <errno.h> #include "bashansi.h" #include "posixstat.h" #include "filecntl.h" #include "shell.h" #include "flags.h" #include "input.h" #include "parser.h" /* for the struct dstack stuff. */ #include "pathexp.h" /* for the struct ignorevar stuff */ #include "bashhist.h" /* matching prototypes and declarations */ #include "builtins/common.h" #include <readline/history.h> #include <glob/glob.h> #include <glob/strmatch.h> #if defined (READLINE) # include "bashline.h" #endif #if !defined (errno) extern int errno; #endif static int histignore_item_func __P((struct ign *)); static int check_history_control __P((char *)); static void really_add_history __P((char *)); static struct ignorevar histignore = { "HISTIGNORE", (struct ign *)0, 0, (char *)0, (sh_iv_item_func_t *)histignore_item_func, }; #define HIGN_EXPAND 0x01 /* Declarations of bash history variables. */ /* Non-zero means to remember lines typed to the shell on the history list. This is different than the user-controlled behaviour; this becomes zero when we read lines from a file, for example. */ int remember_on_history = 1; /* The number of lines that Bash has added to this history session. */ int history_lines_this_session; /* The number of lines that Bash has read from the history file. */ int history_lines_in_file; #if defined (BANG_HISTORY) /* Non-zero means do no history expansion on this line, regardless of what history_expansion says. */ int history_expansion_inhibited; #endif /* With the old default, every line was saved in the history individually. I.e., if the user enters: bash$ for i in a b c > do > echo $i > done Each line will be individually saved in the history. bash$ history 10 for i in a b c 11 do 12 echo $i 13 done 14 history If the variable command_oriented_history is set, multiple lines which form one command will be saved as one history entry. bash$ for i in a b c > do > echo $i > done bash$ history 10 for i in a b c do echo $i done 11 history The user can then recall the whole command all at once instead of just being able to recall one line at a time. This is now enabled by default. */ int command_oriented_history = 1; /* Set to 1 if the first line of a possibly-multi-line command was saved in the history list. Managed by maybe_add_history(), but global so the history-manipluating builtins can see it. */ int current_command_first_line_saved = 0; /* Non-zero means to store newlines in the history list when using command_oriented_history rather than trying to use semicolons. */ int literal_history; /* Non-zero means to append the history to the history file at shell exit, even if the history has been stifled. */ int force_append_history; /* A nit for picking at history saving. Value of 0 means save all lines parsed by the shell on the history. Value of 1 means save all lines that do not start with a space. Value of 2 means save all lines that do not match the last line saved. */ int history_control; /* Set to 1 if the last command was added to the history list successfully as a separate history entry; set to 0 if the line was ignored or added to a previous entry as part of command-oriented-history processing. */ int hist_last_line_added; #if defined (READLINE) /* If non-zero, and readline is being used, the user is offered the chance to re-edit a failed history expansion. */ int history_reediting; /* If non-zero, and readline is being used, don't directly execute a line with history substitution. Reload it into the editing buffer instead and let the user further edit and confirm with a newline. */ int hist_verify; #endif /* READLINE */ /* Non-zero means to not save function definitions in the history list. */ int dont_save_function_defs; /* Variables declared in other files used here. */ extern int current_command_line_count; extern struct dstack dstack; static int bash_history_inhibit_expansion __P((char *, int)); #if defined (READLINE) static void re_edit __P((char *)); #endif static int history_expansion_p __P((char *)); static int shell_comment __P((char *)); static int should_expand __P((char *)); static HIST_ENTRY *last_history_entry __P((void)); static char *expand_histignore_pattern __P((char *)); static int history_should_ignore __P((char *)); /* Is the history expansion starting at string[i] one that should not be expanded? */ static int bash_history_inhibit_expansion (string, i) char *string; int i; { /* The shell uses ! as a pattern negation character in globbing [...] expressions, so let those pass without expansion. */ if (i > 0 && (string[i - 1] == '[') && member (']', string + i + 1)) return (1); /* The shell uses ! as the indirect expansion character, so let those expansions pass as well. */ else if (i > 1 && string[i - 1] == '{' && string[i - 2] == '$' && member ('}', string + i + 1)) return (1); #if defined (EXTENDED_GLOB) else if (extended_glob && i > 1 && string[i+1] == '(' && member (')', string + i + 2)) return (1); #endif else return (0); } void bash_initialize_history () { history_quotes_inhibit_expansion = 1; history_search_delimiter_chars = ";&()|<>"; history_inhibit_expansion_function = bash_history_inhibit_expansion; } void bash_history_reinit (interact) int interact; { #if defined (BANG_HISTORY) history_expansion = interact != 0; history_expansion_inhibited = 1; #endif remember_on_history = interact != 0; history_inhibit_expansion_function = bash_history_inhibit_expansion; } void bash_history_disable () { remember_on_history = 0; #if defined (BANG_HISTORY) history_expansion_inhibited = 1; #endif } void bash_history_enable () { remember_on_history = 1; #if defined (BANG_HISTORY) history_expansion_inhibited = 0; #endif history_inhibit_expansion_function = bash_history_inhibit_expansion; sv_history_control ("HISTCONTROL"); sv_histignore ("HISTIGNORE"); } /* Load the history list from the history file. */ void load_history () { char *hf; struct stat buf; /* Truncate history file for interactive shells which desire it. Note that the history file is automatically truncated to the size of HISTSIZE if the user does not explicitly set the size differently. */ set_if_not ("HISTFILESIZE", get_string_value ("HISTSIZE")); sv_histsize ("HISTFILESIZE"); /* Read the history in HISTFILE into the history list. */ hf = get_string_value ("HISTFILE"); if (hf && *hf && stat (hf, &buf) == 0) { read_history (hf); using_history (); history_lines_in_file = where_history (); } } #ifdef INCLUDE_UNUSED /* Write the existing history out to the history file. */ void save_history () { char *hf; struct stat buf; hf = get_string_value ("HISTFILE"); if (hf && *hf && stat (hf, &buf) == 0) { /* Append only the lines that occurred this session to the history file. */ using_history (); if (history_lines_this_session < where_history () || force_append_history) append_history (history_lines_this_session, hf); else write_history (hf); sv_histsize ("HISTFILESIZE"); } } #endif int maybe_append_history (filename) char *filename; { int fd, result; struct stat buf; result = EXECUTION_SUCCESS; if (history_lines_this_session && (history_lines_this_session < where_history ())) { /* If the filename was supplied, then create it if necessary. */ if (stat (filename, &buf) == -1 && errno == ENOENT) { fd = open (filename, O_WRONLY|O_CREAT, 0600); if (fd < 0) { builtin_error ("%s: cannot create: %s", filename, strerror (errno)); return (EXECUTION_FAILURE); } close (fd); } result = append_history (history_lines_this_session, filename); history_lines_in_file += history_lines_this_session; history_lines_this_session = 0; } return (result); } /* If this is an interactive shell, then append the lines executed this session to the history file. */ int maybe_save_shell_history () { int result; char *hf; struct stat buf; result = 0; if (history_lines_this_session) { hf = get_string_value ("HISTFILE"); if (hf && *hf) { /* If the file doesn't exist, then create it. */ if (stat (hf, &buf) == -1) { int file; file = open (hf, O_CREAT | O_TRUNC | O_WRONLY, 0600); if (file != -1) close (file); } /* Now actually append the lines if the history hasn't been stifled. If the history has been stifled, rewrite the history file. */ using_history (); if (history_lines_this_session <= where_history () || force_append_history) { result = append_history (history_lines_this_session, hf); history_lines_in_file += history_lines_this_session; } else { result = write_history (hf); history_lines_in_file = history_lines_this_session; } history_lines_this_session = 0; sv_histsize ("HISTFILESIZE"); } } return (result); } #if defined (READLINE) /* Tell readline () that we have some text for it to edit. */ static void re_edit (text) char *text; { if (bash_input.type == st_stdin) bash_re_edit (text); } #endif /* READLINE */ /* Return 1 if this line needs history expansion. */ static int history_expansion_p (line) char *line; { register char *s; for (s = line; *s; s++) if (*s == history_expansion_char || *s == history_subst_char) return 1; return 0; } /* Do pre-processing on LINE. If PRINT_CHANGES is non-zero, then print the results of expanding the line if there were any changes. If there is an error, return NULL, otherwise the expanded line is returned. If ADDIT is non-zero the line is added to the history list after history expansion. ADDIT is just a suggestion; REMEMBER_ON_HISTORY can veto, and does. Right now this does history expansion. */ char * pre_process_line (line, print_changes, addit) char *line; int print_changes, addit; { char *history_value; char *return_value; int expanded; return_value = line; expanded = 0; # if defined (BANG_HISTORY) /* History expand the line. If this results in no errors, then add that line to the history if ADDIT is non-zero. */ if (!history_expansion_inhibited && history_expansion && history_expansion_p (line)) { expanded = history_expand (line, &history_value); if (expanded) { if (print_changes) { if (expanded < 0) internal_error ("%s", history_value); #if defined (READLINE) else if (hist_verify == 0 || expanded == 2) #else else #endif fprintf (stderr, "%s\n", history_value); } /* If there was an error, return NULL. */ if (expanded < 0 || expanded == 2) /* 2 == print only */ { free (history_value); # if defined (READLINE) /* New hack. We can allow the user to edit the failed history expansion. */ if (history_reediting && expanded < 0) re_edit (line); # endif /* READLINE */ return ((char *)NULL); } # if defined (READLINE) if (hist_verify && expanded == 1) { re_edit (history_value); return ((char *)NULL); } # endif } /* Let other expansions know that return_value can be free'ed, and that a line has been added to the history list. Note that we only add lines that have something in them. */ expanded = 1; return_value = history_value; } # endif /* BANG_HISTORY */ if (addit && remember_on_history && *return_value) maybe_add_history (return_value); #if 0 if (expanded == 0) return_value = savestring (line); #endif return (return_value); } /* Return 1 if the first non-whitespace character in LINE is a `#', indicating * that the line is a shell comment. */ static int shell_comment (line) char *line; { char *p; for (p = line; p && *p && whitespace (*p); p++) ; return (p && *p == '#'); } #ifdef INCLUDE_UNUSED /* Remove shell comments from LINE. A `#' and anything after it is a comment. This isn't really useful yet, since it doesn't handle quoting. */ static char * filter_comments (line) char *line; { char *p; for (p = line; p && *p && *p != '#'; p++) ; if (p && *p == '#') *p = '\0'; return (line); } #endif /* Check LINE against what HISTCONTROL says to do. Returns 1 if the line should be saved; 0 if it should be discarded. */ static int check_history_control (line) char *line; { HIST_ENTRY *temp; int r; switch (history_control) { case 0: /* nothing */ return 1; case 1: /* ignorespace */ return (*line != ' '); case 3: /* ignoreboth */ if (*line == ' ') return 0; /* FALLTHROUGH if case == 3 (`ignoreboth') */ case 2: /* ignoredups */ using_history (); temp = previous_history (); r = (temp == 0 || STREQ (temp->line, line) == 0); using_history (); return r; } return 0; } /* Add LINE to the history list, handling possibly multi-line compound commands. We note whether or not we save the first line of each command (which is usually the entire command and history entry), and don't add the second and subsequent lines of a multi-line compound command if we didn't save the first line. We don't usually save shell comment lines in compound commands in the history, because they could have the effect of commenting out the rest of the command when the entire command is saved as a single history entry (when COMMAND_ORIENTED_HISTORY is enabled). If LITERAL_HISTORY is set, we're saving lines in the history with embedded newlines, so it's OK to save comment lines. We also make sure to save multiple-line quoted strings or other constructs. */ void maybe_add_history (line) char *line; { hist_last_line_added = 0; /* Don't use the value of history_control to affect the second and subsequent lines of a multi-line command (old code did this only when command_oriented_history is enabled). */ if (current_command_line_count > 1) { if (current_command_first_line_saved && (literal_history || dstack.delimiter_depth != 0 || shell_comment (line) == 0)) bash_add_history (line); return; } /* This is the first line of a (possible multi-line) command. Note whether or not we should save the first line and remember it. */ current_command_first_line_saved = check_add_history (line, 0); } /* Just check LINE against HISTCONTROL and HISTIGNORE and add it to the history if it's OK. Used by `history -s' as well as maybe_add_history(). Returns 1 if the line was saved in the history, 0 otherwise. */ int check_add_history (line, force) char *line; int force; { if (check_history_control (line) && history_should_ignore (line) == 0) { if (force) { really_add_history (line); using_history (); } else bash_add_history (line); return 1; } return 0; } /* Add a line to the history list. The variable COMMAND_ORIENTED_HISTORY controls the style of history remembering; when non-zero, and LINE is not the first line of a complete parser construct, append LINE to the last history line instead of adding it as a new line. */ void bash_add_history (line) char *line; { int add_it, offset, curlen; HIST_ENTRY *current, *old; char *chars_to_add, *new_line; add_it = 1; if (command_oriented_history && current_command_line_count > 1) { chars_to_add = literal_history ? "\n" : history_delimiting_chars (); using_history (); current = previous_history (); if (current) { /* If the previous line ended with an escaped newline (escaped with backslash, but otherwise unquoted), then remove the quoted newline, since that is what happens when the line is parsed. */ curlen = strlen (current->line); if (dstack.delimiter_depth == 0 && current->line[curlen - 1] == '\\' && current->line[curlen - 2] != '\\') { current->line[curlen - 1] = '\0'; curlen--; chars_to_add = ""; } new_line = (char *)xmalloc (1 + curlen + strlen (line) + strlen (chars_to_add)); sprintf (new_line, "%s%s%s", current->line, chars_to_add, line); offset = where_history (); old = replace_history_entry (offset, new_line, current->data); free (new_line); if (old) { FREE (old->line); free (old); } add_it = 0; } } if (add_it) really_add_history (line); using_history (); } static void really_add_history (line) char *line; { hist_last_line_added = 1; add_history (line); history_lines_this_session++; } int history_number () { using_history (); return (get_string_value ("HISTSIZE") ? history_base + where_history () : 1); } static int should_expand (s) char *s; { char *p; for (p = s; p && *p; p++) { if (*p == '\\') p++; else if (*p == '&') return 1; } return 0; } static int histignore_item_func (ign) struct ign *ign; { if (should_expand (ign->val)) ign->flags |= HIGN_EXPAND; return (0); } void setup_history_ignore (varname) char *varname; { setup_ignore_patterns (&histignore); } static HIST_ENTRY * last_history_entry () { HIST_ENTRY *he; using_history (); he = previous_history (); using_history (); return he; } char * last_history_line () { HIST_ENTRY *he; he = last_history_entry (); if (he == 0) return ((char *)NULL); return he->line; } static char * expand_histignore_pattern (pat) char *pat; { HIST_ENTRY *phe; char *ret; phe = last_history_entry (); if (phe == (HIST_ENTRY *)0) return (savestring (pat)); ret = strcreplace (pat, '&', phe->line, 1); return ret; } /* Return 1 if we should not put LINE into the history according to the patterns in HISTIGNORE. */ static int history_should_ignore (line) char *line; { register int i, match; char *npat; if (histignore.num_ignores == 0) return 0; for (i = match = 0; i < histignore.num_ignores; i++) { if (histignore.ignores[i].flags & HIGN_EXPAND) npat = expand_histignore_pattern (histignore.ignores[i].val); else npat = histignore.ignores[i].val; match = strmatch (npat, line, FNMATCH_EXTFLAG) != FNM_NOMATCH; if (histignore.ignores[i].flags & HIGN_EXPAND) free (npat); if (match) break; } return match; } #endif /* HISTORY */
/* bashline.c -- Bash's interface to the readline library. */ /* Copyright (C) 1987-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if defined (READLINE) #include "bashtypes.h" #include "posixstat.h" #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #if defined (HAVE_GRP_H) # include <grp.h> #endif #if defined (HAVE_NETDB_H) # include <netdb.h> #endif #include <stdio.h> #include "chartypes.h" #include "bashansi.h" #include "shell.h" #include "input.h" #include "builtins.h" #include "bashhist.h" #include "bashline.h" #include "execute_cmd.h" #include "findcmd.h" #include "pathexp.h" #include "builtins/common.h" #include <readline/rlconf.h> #include <readline/readline.h> #include <readline/history.h> #include <glob/glob.h> #if defined (ALIAS) # include "alias.h" #endif #if defined (PROGRAMMABLE_COMPLETION) # include "pcomplete.h" #endif /* These should agree with the defines for emacs_mode and vi_mode in rldefs.h, even though that's not a public readline header file. */ #ifndef EMACS_EDITING_MODE # define NO_EDITING_MODE -1 # define EMACS_EDITING_MODE 1 # define VI_EDITING_MODE 0 #endif #if defined (BRACE_COMPLETION) extern int bash_brace_completion __P((int, int)); #endif /* BRACE_COMPLETION */ /* Forward declarations */ /* Functions bound to keys in Readline for Bash users. */ static int shell_expand_line __P((int, int)); static int display_shell_version __P((int, int)); static int operate_and_get_next __P((int, int)); static int bash_ignore_filenames __P((char **)); static int bash_ignore_everything __P((char **)); #if defined (BANG_HISTORY) static char *history_expand_line_internal __P((char *)); static int history_expand_line __P((int, int)); static int tcsh_magic_space __P((int, int)); #endif /* BANG_HISTORY */ #ifdef ALIAS static int alias_expand_line __P((int, int)); #endif #if defined (BANG_HISTORY) && defined (ALIAS) static int history_and_alias_expand_line __P((int, int)); #endif /* Helper functions for Readline. */ static int bash_directory_completion_hook __P((char **)); static int filename_completion_ignore __P((char **)); static int bash_push_line __P((void)); static void cleanup_expansion_error __P((void)); static void maybe_make_readline_line __P((char *)); static void set_up_new_line __P((char *)); static int check_redir __P((int)); static char **attempt_shell_completion __P((const char *, int, int)); static char *variable_completion_function __P((const char *, int)); static char *hostname_completion_function __P((const char *, int)); static char *command_subst_completion_function __P((const char *, int)); static void build_history_completion_array __P((void)); static char *history_completion_generator __P((const char *, int)); static int dynamic_complete_history __P((int, int)); static void initialize_hostname_list __P((void)); static void add_host_name __P((char *)); static void snarf_hosts_from_file __P((char *)); static char **hostnames_matching __P((char *)); static void _ignore_completion_names __P((char **, sh_ignore_func_t *)); static int name_is_acceptable __P((const char *)); static int test_for_directory __P((const char *)); static int return_zero __P((const char *)); static char *bash_dequote_filename __P((char *, int)); static char *quote_word_break_chars __P((char *)); static char *bash_quote_filename __P((char *, int, char *)); static int bash_execute_unix_command __P((int, int)); static void init_unix_command_map __P((void)); static int isolate_sequence __P((char *, int, int, int *)); static int set_saved_history __P((void)); #if defined (ALIAS) static int posix_edit_macros __P((int, int)); #endif #if defined (PROGRAMMABLE_COMPLETION) static int find_cmd_start __P((int)); static int find_cmd_end __P((int)); static char *find_cmd_name __P((int)); static char *prog_complete_return __P((const char *, int)); static char **prog_complete_matches; #endif /* Variables used here but defined in other files. */ extern int current_command_line_count; extern int posixly_correct, no_symbolic_links; extern char *current_prompt_string, *ps1_prompt; extern STRING_INT_ALIST word_token_alist[]; /* SPECIFIC_COMPLETION_FUNCTIONS specifies that we have individual completion functions which indicate what type of completion should be done (at or before point) that can be bound to key sequences with the readline library. */ #define SPECIFIC_COMPLETION_FUNCTIONS #if defined (SPECIFIC_COMPLETION_FUNCTIONS) static int bash_specific_completion __P((int, rl_compentry_func_t *)); static int bash_complete_filename_internal __P((int)); static int bash_complete_username_internal __P((int)); static int bash_complete_hostname_internal __P((int)); static int bash_complete_variable_internal __P((int)); static int bash_complete_command_internal __P((int)); static int bash_complete_filename __P((int, int)); static int bash_possible_filename_completions __P((int, int)); static int bash_complete_username __P((int, int)); static int bash_possible_username_completions __P((int, int)); static int bash_complete_hostname __P((int, int)); static int bash_possible_hostname_completions __P((int, int)); static int bash_complete_variable __P((int, int)); static int bash_possible_variable_completions __P((int, int)); static int bash_complete_command __P((int, int)); static int bash_possible_command_completions __P((int, int)); static char *glob_complete_word __P((const char *, int)); static int bash_glob_completion_internal __P((int)); static int bash_glob_complete_word __P((int, int)); static int bash_glob_expand_word __P((int, int)); static int bash_glob_list_expansions __P((int, int)); #endif /* SPECIFIC_COMPLETION_FUNCTIONS */ static int edit_and_execute_command __P((int, int, int, char *)); #if defined (VI_MODE) static int vi_edit_and_execute_command __P((int, int)); #endif static int emacs_edit_and_execute_command __P((int, int)); /* Non-zero once initalize_readline () has been called. */ int bash_readline_initialized = 0; /* If non-zero, we do hostname completion, breaking words at `@' and trying to complete the stuff after the `@' from our own internal host list. */ int perform_hostname_completion = 1; /* If non-zero, we don't do command completion on an empty line. */ int no_empty_command_completion; static char *bash_completer_word_break_characters = " \t\n\"'@><=;|&(:"; static char *bash_nohostname_word_break_characters = " \t\n\"'><=;|&(:"; static rl_hook_func_t *old_rl_startup_hook = (rl_hook_func_t *)NULL; /* What kind of quoting is performed by bash_quote_filename: COMPLETE_DQUOTE = double-quoting the filename COMPLETE_SQUOTE = single_quoting the filename COMPLETE_BSQUOTE = backslash-quoting special chars in the filename */ #define COMPLETE_DQUOTE 1 #define COMPLETE_SQUOTE 2 #define COMPLETE_BSQUOTE 3 static int completion_quoting_style = COMPLETE_BSQUOTE; /* Change the readline VI-mode keymaps into or out of Posix.2 compliance. Called when the shell is put into or out of `posix' mode. */ void posix_readline_initialize (on_or_off) int on_or_off; { if (on_or_off) rl_variable_bind ("comment-begin", "#"); #if defined (VI_MODE) rl_bind_key_in_map (CTRL ('I'), on_or_off ? rl_insert : rl_complete, vi_insertion_keymap); #endif } int enable_hostname_completion (on_or_off) int on_or_off; { int old_value; old_value = perform_hostname_completion; if (on_or_off) { perform_hostname_completion = 1; rl_special_prefixes = "$@"; rl_completer_word_break_characters = bash_completer_word_break_characters; } else { perform_hostname_completion = 0; rl_special_prefixes = "$"; rl_completer_word_break_characters = bash_nohostname_word_break_characters; } return (old_value); } /* Called once from parse.y if we are going to use readline. */ void initialize_readline () { if (bash_readline_initialized) return; rl_terminal_name = get_string_value ("TERM"); rl_instream = stdin; rl_outstream = stderr; /* Allow conditional parsing of the ~/.inputrc file. */ rl_readline_name = "Bash"; /* Add bindable names before calling rl_initialize so they may be referenced in the various inputrc files. */ rl_add_defun ("shell-expand-line", shell_expand_line, -1); #ifdef BANG_HISTORY rl_add_defun ("history-expand-line", history_expand_line, -1); rl_add_defun ("magic-space", tcsh_magic_space, -1); #endif #ifdef ALIAS rl_add_defun ("alias-expand-line", alias_expand_line, -1); # ifdef BANG_HISTORY rl_add_defun ("history-and-alias-expand-line", history_and_alias_expand_line, -1); # endif #endif /* Backwards compatibility. */ rl_add_defun ("insert-last-argument", rl_yank_last_arg, -1); rl_add_defun ("operate-and-get-next", operate_and_get_next, -1); rl_add_defun ("display-shell-version", display_shell_version, -1); rl_add_defun ("edit-and-execute-command", emacs_edit_and_execute_command, -1); #if defined (BRACE_COMPLETION) rl_add_defun ("complete-into-braces", bash_brace_completion, -1); #endif #if defined (SPECIFIC_COMPLETION_FUNCTIONS) rl_add_defun ("complete-filename", bash_complete_filename, -1); rl_add_defun ("possible-filename-completions", bash_possible_filename_completions, -1); rl_add_defun ("complete-username", bash_complete_username, -1); rl_add_defun ("possible-username-completions", bash_possible_username_completions, -1); rl_add_defun ("complete-hostname", bash_complete_hostname, -1); rl_add_defun ("possible-hostname-completions", bash_possible_hostname_completions, -1); rl_add_defun ("complete-variable", bash_complete_variable, -1); rl_add_defun ("possible-variable-completions", bash_possible_variable_completions, -1); rl_add_defun ("complete-command", bash_complete_command, -1); rl_add_defun ("possible-command-completions", bash_possible_command_completions, -1); rl_add_defun ("glob-complete-word", bash_glob_complete_word, -1); rl_add_defun ("glob-expand-word", bash_glob_expand_word, -1); rl_add_defun ("glob-list-expansions", bash_glob_list_expansions, -1); #endif rl_add_defun ("dynamic-complete-history", dynamic_complete_history, -1); /* Bind defaults before binding our custom shell keybindings. */ if (RL_ISSTATE(RL_STATE_INITIALIZED) == 0) rl_initialize (); /* Bind up our special shell functions. */ rl_bind_key_in_map (CTRL('E'), shell_expand_line, emacs_meta_keymap); /* Bind up our special shell functions. */ #ifdef BANG_HISTORY rl_bind_key_in_map ('^', history_expand_line, emacs_meta_keymap); #endif rl_bind_key_in_map (CTRL ('O'), operate_and_get_next, emacs_standard_keymap); rl_bind_key_in_map (CTRL ('V'), display_shell_version, emacs_ctlx_keymap); /* In Bash, the user can switch editing modes with "set -o [vi emacs]", so it is not necessary to allow C-M-j for context switching. Turn off this occasionally confusing behaviour. */ rl_unbind_key_in_map (CTRL('J'), emacs_meta_keymap); rl_unbind_key_in_map (CTRL('M'), emacs_meta_keymap); #if defined (VI_MODE) rl_unbind_key_in_map (CTRL('E'), vi_movement_keymap); #endif #if defined (BRACE_COMPLETION) rl_bind_key_in_map ('{', bash_brace_completion, emacs_meta_keymap); #endif /* BRACE_COMPLETION */ #if defined (SPECIFIC_COMPLETION_FUNCTIONS) rl_bind_key_in_map ('/', bash_complete_filename, emacs_meta_keymap); rl_bind_key_in_map ('/', bash_possible_filename_completions, emacs_ctlx_keymap); rl_bind_key_in_map ('~', bash_complete_username, emacs_meta_keymap); rl_bind_key_in_map ('~', bash_possible_username_completions, emacs_ctlx_keymap); rl_bind_key_in_map ('@', bash_complete_hostname, emacs_meta_keymap); rl_bind_key_in_map ('@', bash_possible_hostname_completions, emacs_ctlx_keymap); rl_bind_key_in_map ('$', bash_complete_variable, emacs_meta_keymap); rl_bind_key_in_map ('$', bash_possible_variable_completions, emacs_ctlx_keymap); rl_bind_key_in_map ('!', bash_complete_command, emacs_meta_keymap); rl_bind_key_in_map ('!', bash_possible_command_completions, emacs_ctlx_keymap); rl_bind_key_in_map ('g', bash_glob_complete_word, emacs_meta_keymap); rl_bind_key_in_map ('*', bash_glob_expand_word, emacs_ctlx_keymap); rl_bind_key_in_map ('g', bash_glob_list_expansions, emacs_ctlx_keymap); #endif /* SPECIFIC_COMPLETION_FUNCTIONS */ rl_bind_key_in_map (TAB, dynamic_complete_history, emacs_meta_keymap); /* Tell the completer that we want a crack first. */ rl_attempted_completion_function = attempt_shell_completion; /* Tell the completer that we might want to follow symbolic links or do other expansion on directory names. */ rl_directory_completion_hook = bash_directory_completion_hook; /* Tell the filename completer we want a chance to ignore some names. */ rl_ignore_some_completions_function = filename_completion_ignore; /* Bind C-xC-e to invoke emacs and run result as commands. */ rl_bind_key_in_map (CTRL ('E'), emacs_edit_and_execute_command, emacs_ctlx_keymap); #if defined (VI_MODE) rl_bind_key_in_map ('v', vi_edit_and_execute_command, vi_movement_keymap); # if defined (ALIAS) rl_bind_key_in_map ('@', posix_edit_macros, vi_movement_keymap); # endif #endif rl_completer_quote_characters = "'\""; /* This sets rl_completer_word_break_characters and rl_special_prefixes to the appropriate values, depending on whether or not hostname completion is enabled. */ enable_hostname_completion (perform_hostname_completion); /* characters that need to be quoted when appearing in filenames. */ rl_filename_quote_characters = " \t\n\\\"'@<>=;|&()#$`?*[!:{"; /*}*/ rl_filename_quoting_function = bash_quote_filename; rl_filename_dequoting_function = bash_dequote_filename; rl_char_is_quoted_p = char_is_quoted; #if 0 /* This is superfluous and makes it impossible to use tab completion in vi mode even when explicitly binding it in ~/.inputrc. sv_strict_posix() should already have called posix_readline_initialize() when posixly_correct was set. */ if (posixly_correct) posix_readline_initialize (1); #endif bash_readline_initialized = 1; } /* On Sun systems at least, rl_attempted_completion_function can end up getting set to NULL, and rl_completion_entry_function set to do command word completion if Bash is interrupted while trying to complete a command word. This just resets all the completion functions to the right thing. It's called from throw_to_top_level(). */ void bashline_reinitialize () { tilde_initialize (); rl_attempted_completion_function = attempt_shell_completion; rl_completion_entry_function = NULL; rl_directory_completion_hook = bash_directory_completion_hook; rl_ignore_some_completions_function = filename_completion_ignore; } /* Contains the line to push into readline. */ static char *push_to_readline = (char *)NULL; /* Push the contents of push_to_readline into the readline buffer. */ static int bash_push_line () { if (push_to_readline) { rl_insert_text (push_to_readline); free (push_to_readline); push_to_readline = (char *)NULL; rl_startup_hook = old_rl_startup_hook; } return 0; } /* Call this to set the initial text for the next line to read from readline. */ int bash_re_edit (line) char *line; { FREE (push_to_readline); push_to_readline = savestring (line); old_rl_startup_hook = rl_startup_hook; rl_startup_hook = bash_push_line; return (0); } static int display_shell_version (count, c) int count, c; { rl_crlf (); show_shell_version (0); putc ('\r', rl_outstream); fflush (rl_outstream); rl_on_new_line (); rl_redisplay (); return 0; } /* **************************************************************** */ /* */ /* Readline Stuff */ /* */ /* **************************************************************** */ /* If the user requests hostname completion, then simply build a list of hosts, and complete from that forever more, or at least until HOSTFILE is unset. */ /* THIS SHOULD BE A STRINGLIST. */ /* The kept list of hostnames. */ static char **hostname_list = (char **)NULL; /* The physical size of the above list. */ static int hostname_list_size; /* The number of hostnames in the above list. */ static int hostname_list_length; /* Whether or not HOSTNAME_LIST has been initialized. */ int hostname_list_initialized = 0; /* Initialize the hostname completion table. */ static void initialize_hostname_list () { char *temp; temp = get_string_value ("HOSTFILE"); if (temp == 0) temp = get_string_value ("hostname_completion_file"); if (temp == 0) temp = DEFAULT_HOSTS_FILE; snarf_hosts_from_file (temp); if (hostname_list) hostname_list_initialized++; } /* Add NAME to the list of hosts. */ static void add_host_name (name) char *name; { if (hostname_list_length + 2 > hostname_list_size) { hostname_list_size = (hostname_list_size + 32) - (hostname_list_size % 32); hostname_list = strvec_resize (hostname_list, hostname_list_size); } hostname_list[hostname_list_length++] = savestring (name); hostname_list[hostname_list_length] = (char *)NULL; } #define cr_whitespace(c) ((c) == '\r' || (c) == '\n' || whitespace(c)) static void snarf_hosts_from_file (filename) char *filename; { FILE *file; char *temp, buffer[256], name[256]; register int i, start; file = fopen (filename, "r"); if (file == 0) return; while (temp = fgets (buffer, 255, file)) { /* Skip to first character. */ for (i = 0; buffer[i] && cr_whitespace (buffer[i]); i++) ; /* If comment or blank line, ignore. */ if (buffer[i] == '\0' || buffer[i] == '#') continue; /* If `preprocessor' directive, do the include. */ if (strncmp (buffer + i, "$include ", 9) == 0) { char *incfile, *t; /* Find start of filename. */ for (incfile = buffer + i + 9; *incfile && whitespace (*incfile); incfile++) ; /* Find end of filename. */ for (t = incfile; *t && cr_whitespace (*t) == 0; t++) ; *t = '\0'; snarf_hosts_from_file (incfile); continue; } /* Skip internet address if present. */ if (DIGIT (buffer[i])) for (; buffer[i] && cr_whitespace (buffer[i]) == 0; i++); /* Gobble up names. Each name is separated with whitespace. */ while (buffer[i]) { for (; cr_whitespace (buffer[i]); i++) ; if (buffer[i] == '\0' || buffer[i] == '#') break; /* Isolate the current word. */ for (start = i; buffer[i] && cr_whitespace (buffer[i]) == 0; i++) ; if (i == start) continue; strncpy (name, buffer + start, i - start); name[i - start] = '\0'; add_host_name (name); } } fclose (file); } /* Return the hostname list. */ char ** get_hostname_list () { if (hostname_list_initialized == 0) initialize_hostname_list (); return (hostname_list); } void clear_hostname_list () { register int i; if (hostname_list_initialized == 0) return; for (i = 0; i < hostname_list_length; i++) free (hostname_list[i]); hostname_list_length = 0; } /* Return a NULL terminated list of hostnames which begin with TEXT. Initialize the hostname list the first time if neccessary. The array is malloc ()'ed, but not the individual strings. */ static char ** hostnames_matching (text) char *text; { register int i, len, nmatch, rsize; char **result; if (hostname_list_initialized == 0) initialize_hostname_list (); if (hostname_list_initialized == 0) return ((char **)NULL); /* Special case. If TEXT consists of nothing, then the whole list is what is desired. */ if (*text == '\0') { result = strvec_create (1 + hostname_list_length); for (i = 0; i < hostname_list_length; i++) result[i] = hostname_list[i]; result[i] = (char *)NULL; return (result); } /* Scan until found, or failure. */ len = strlen (text); result = (char **)NULL; for (i = nmatch = rsize = 0; i < hostname_list_length; i++) { if (STREQN (text, hostname_list[i], len) == 0) continue; /* OK, it matches. Add it to the list. */ if (nmatch >= (rsize - 1)) { rsize = (rsize + 16) - (rsize % 16); result = strvec_resize (result, rsize); } result[nmatch++] = hostname_list[i]; } if (nmatch) result[nmatch] = (char *)NULL; return (result); } /* The equivalent of the Korn shell C-o operate-and-get-next-history-line editing command. */ static int saved_history_line_to_use = -1; static int set_saved_history () { if (saved_history_line_to_use >= 0) rl_get_previous_history (history_length - saved_history_line_to_use, 0); saved_history_line_to_use = -1; rl_startup_hook = old_rl_startup_hook; return (0); } static int operate_and_get_next (count, c) int count, c; { int where; /* Accept the current line. */ rl_newline (1, c); /* Find the current line, and find the next line to use. */ where = where_history (); if ((history_is_stifled () && (history_length >= history_max_entries)) || (where >= history_length - 1)) saved_history_line_to_use = where; else saved_history_line_to_use = where + 1; old_rl_startup_hook = rl_startup_hook; rl_startup_hook = set_saved_history; return 0; } /* This vi mode command causes VI_EDIT_COMMAND to be run on the current command being entered (if no explicit argument is given), otherwise on a command from the history file. */ #define VI_EDIT_COMMAND "fc -e ${VISUAL:-${EDITOR:-vi}}" #define EMACS_EDIT_COMMAND "fc -e ${VISUAL:-${EDITOR:-emacs}}" static int edit_and_execute_command (count, c, editing_mode, edit_command) int count, c, editing_mode; char *edit_command; { char *command; int r, cclc, rrs; rrs = rl_readline_state; cclc = current_command_line_count; /* Accept the current line. */ rl_newline (1, c); if (rl_explicit_arg) { command = (char *)xmalloc (strlen (edit_command) + 8); sprintf (command, "%s %d", edit_command, count); } else { /* Take the command we were just editing, add it to the history file, then call fc to operate on it. We have to add a dummy command to the end of the history because fc ignores the last command (assumes it's supposed to deal with the command before the `fc'). */ using_history (); bash_add_history (rl_line_buffer); bash_add_history (""); history_lines_this_session++; using_history (); command = savestring (edit_command); } /* Now, POSIX.1-2001 and SUSv3 say that the commands executed from the temporary file should be placed into the history. We don't do that yet. */ r = parse_and_execute (command, (editing_mode == VI_EDITING_MODE) ? "v" : "C-xC-e", SEVAL_NOHIST); current_command_line_count = cclc; /* Now erase the contents of the current line and undo the effects of the rl_accept_line() above. We don't even want to make the text we just executed available for undoing. */ rl_line_buffer[0] = '\0'; /* XXX */ rl_point = rl_end = 0; rl_done = 0; rl_readline_state = rrs; rl_forced_update_display (); return r; } #if defined (VI_MODE) static int vi_edit_and_execute_command (count, c) int count, c; { return (edit_and_execute_command (count, c, VI_EDITING_MODE, VI_EDIT_COMMAND)); } #endif /* VI_MODE */ static int emacs_edit_and_execute_command (count, c) int count, c; { return (edit_and_execute_command (count, c, EMACS_EDITING_MODE, EMACS_EDIT_COMMAND)); } #if defined (ALIAS) static int posix_edit_macros (count, key) int count, key; { int c; char alias_name[3], *alias_value, *macro; c = rl_read_key (); alias_name[0] = '_'; alias_name[1] = c; alias_name[2] = '\0'; alias_value = get_alias_value (alias_name); if (alias_value && *alias_value) { macro = savestring (alias_value); rl_push_macro_input (macro); } return 0; } #endif /* **************************************************************** */ /* */ /* How To Do Shell Completion */ /* */ /* **************************************************************** */ #define COMMAND_SEPARATORS ";|&{(`" static int check_redir (ti) int ti; { register int this_char, prev_char; /* Handle the two character tokens `>&', `<&', and `>|'. We are not in a command position after one of these. */ this_char = rl_line_buffer[ti]; prev_char = rl_line_buffer[ti - 1]; if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) || (this_char == '|' && prev_char == '>')) return (1); else if ((this_char == '{' && prev_char == '$') || /* } */ (char_is_quoted (rl_line_buffer, ti))) return (1); return (0); } #if defined (PROGRAMMABLE_COMPLETION) /* * XXX - because of the <= start test, and setting os = s+1, this can * potentially return os > start. This is probably not what we want to * happen, but fix later after 2.05a-release. */ static int find_cmd_start (start) int start; { register int s, os; os = 0; while (((s = skip_to_delim (rl_line_buffer, os, COMMAND_SEPARATORS)) <= start) && rl_line_buffer[s]) os = s+1; return os; } static int find_cmd_end (end) int end; { register int e; e = skip_to_delim (rl_line_buffer, end, COMMAND_SEPARATORS); return e; } static char * find_cmd_name (start) int start; { char *name; register int s, e; for (s = start; whitespace (rl_line_buffer[s]); s++) ; /* skip until a shell break character */ e = skip_to_delim (rl_line_buffer, s, "()<>;&| \t\n"); name = substring (rl_line_buffer, s, e); return (name); } static char * prog_complete_return (text, matchnum) const char *text; int matchnum; { static int ind; if (matchnum == 0) ind = 0; if (prog_complete_matches == 0 || prog_complete_matches[ind] == 0) return (char *)NULL; return (prog_complete_matches[ind++]); } #endif /* PROGRAMMABLE_COMPLETION */ /* Do some completion on TEXT. The indices of TEXT in RL_LINE_BUFFER are at START and END. Return an array of matches, or NULL if none. */ static char ** attempt_shell_completion (text, start, end) const char *text; int start, end; { int in_command_position, ti, saveti, qc; char **matches, *command_separator_chars; command_separator_chars = COMMAND_SEPARATORS; matches = (char **)NULL; rl_ignore_some_completions_function = filename_completion_ignore; /* Determine if this could be a command word. It is if it appears at the start of the line (ignoring preceding whitespace), or if it appears after a character that separates commands. It cannot be a command word if we aren't at the top-level prompt. */ ti = start - 1; saveti = qc = -1; while ((ti > -1) && (whitespace (rl_line_buffer[ti]))) ti--; #if 1 /* If this is an open quote, maybe we're trying to complete a quoted command name. */ if (rl_line_buffer[ti] == '"' || rl_line_buffer[ti] == '\'') { qc = rl_line_buffer[ti]; saveti = ti--; while (ti > -1 && (whitespace (rl_line_buffer[ti]))) ti--; } #endif in_command_position = 0; if (ti < 0) { /* Only do command completion at the start of a line when we are prompting at the top level. */ if (current_prompt_string == ps1_prompt) in_command_position++; } else if (member (rl_line_buffer[ti], command_separator_chars)) { in_command_position++; if (check_redir (ti) == 1) in_command_position = 0; } else { /* This still could be in command position. It is possible that all of the previous words on the line are variable assignments. */ } /* Check that we haven't incorrectly flagged a closed command substitution as indicating we're in a command position. */ if (in_command_position && ti >= 0 && rl_line_buffer[ti] == '`' && *text != '`' && unclosed_pair (rl_line_buffer, end, "`") == 0) in_command_position = 0; /* Special handling for command substitution. If *TEXT is a backquote, it can be the start or end of an old-style command substitution, or unmatched. If it's unmatched, both calls to unclosed_pair will succeed. */ if (*text == '`' && (in_command_position || (unclosed_pair (rl_line_buffer, start, "`") && unclosed_pair (rl_line_buffer, end, "`")))) matches = rl_completion_matches (text, command_subst_completion_function); #if defined (PROGRAMMABLE_COMPLETION) /* Attempt programmable completion. */ if (!matches && in_command_position == 0 && prog_completion_enabled && (progcomp_size () > 0) && current_prompt_string == ps1_prompt) { int s, e, foundcs; char *n; /* XXX - don't free the members */ if (prog_complete_matches) free (prog_complete_matches); prog_complete_matches = (char **)NULL; s = find_cmd_start (start); e = find_cmd_end (end); n = find_cmd_name (s); if (e > s) prog_complete_matches = programmable_completions (n, text, s, e, &foundcs); else foundcs = 0; FREE (n); /* XXX - if we found a COMPSPEC for the command, just return whatever the programmable completion code returns, and disable the default filename completion that readline will do unless the COPT_DEFAULT option has been set with the `-o default' option to complete. */ if (foundcs) { /* If the user specified that the compspec returns filenames, make sure that readline knows it. */ if (foundcs & COPT_FILENAMES) rl_filename_completion_desired = 1; /* If the user doesn't want a space appended, tell readline. */ if (foundcs & COPT_NOSPACE) rl_completion_suppress_append = 1; /* Turn what the programmable completion code returns into what readline wants. I should have made compute_lcd_of_matches external... */ matches = rl_completion_matches (text, prog_complete_return); if ((foundcs & COPT_DEFAULT) == 0) rl_attempted_completion_over = 1; /* no default */ return (matches); } } #endif /* New posix-style command substitution or variable name? */ if (!matches && *text == '$') { if (qc != '\'' && text[1] == '(') /* ) */ matches = rl_completion_matches (text, command_subst_completion_function); else matches = rl_completion_matches (text, variable_completion_function); } /* If the word starts in `~', and there is no slash in the word, then try completing this word as a username. */ if (!matches && *text == '~' && !xstrchr (text, '/')) matches = rl_completion_matches (text, rl_username_completion_function); /* Another one. Why not? If the word starts in '@', then look through the world of known hostnames for completion first. */ if (!matches && perform_hostname_completion && *text == '@') matches = rl_completion_matches (text, hostname_completion_function); /* And last, (but not least) if this word is in a command position, then complete over possible command names, including aliases, functions, and command names. */ if (!matches && in_command_position) { if (start == 0 && end == 0 && text[0] == '\0' && no_empty_command_completion) { matches = (char **)NULL; rl_ignore_some_completions_function = bash_ignore_everything; } else { matches = rl_completion_matches (text, command_word_completion_function); /* If we are attempting command completion and nothing matches, we do not want readline to perform filename completion for us. We still want to be able to complete partial pathnames, so set the completion ignore function to something which will remove filenames and leave directories in the match list. */ if (matches == (char **)NULL) rl_ignore_some_completions_function = bash_ignore_filenames; else if (matches[1] == 0 && *matches[0] != '/') /* Turn off rl_filename_completion_desired so readline doesn't append a slash if there is a directory with the same name in the current directory, or other filename-specific things. If the name begins with a slash, we're either completing a full pathname or a directory pathname, and readline won't be looking in the current directory anyway, so there's no conflict. */ rl_filename_completion_desired = 0; else if (matches[0] && matches[1] && STREQ (matches[0], matches[1]) && *matches[0] != '/') /* There are multiple instances of the same match (duplicate completions haven't yet been removed). In this case, all of the matches will be the same, and the duplicate removal code will distill them all down to one. We turn off rl_filename_completion_desired for the same reason as above. Remember: we only care if there's eventually a single unique completion. If there are multiple completions this won't make a difference and the problem won't occur. */ rl_filename_completion_desired = 0; } } /* This could be a globbing pattern, so try to expand it using pathname expansion. */ if (!matches && glob_pattern_p (text)) { matches = rl_completion_matches (text, glob_complete_word); /* A glob expression that matches more than one filename is problematic. If we match more than one filename, punt. */ if (matches && matches[1] && rl_completion_type == TAB) { strvec_dispose (matches); matches = (char **)0; } } return (matches); } /* This is the function to call when the word to complete is in a position where a command word can be found. It grovels $PATH, looking for commands that match. It also scans aliases, function names, and the shell_builtin table. */ char * command_word_completion_function (hint_text, state) const char *hint_text; int state; { static char *hint = (char *)NULL; static char *path = (char *)NULL; static char *val = (char *)NULL; static char *filename_hint = (char *)NULL; static int path_index, hint_len, istate; static int mapping_over, local_index; static SHELL_VAR **varlist = (SHELL_VAR **)NULL; #if defined (ALIAS) static alias_t **alias_list = (alias_t **)NULL; #endif /* ALIAS */ /* We have to map over the possibilities for command words. If we have no state, then make one just for that purpose. */ if (!state) { if (hint) free (hint); mapping_over = 0; val = (char *)NULL; /* If this is an absolute program name, do not check it against aliases, reserved words, functions or builtins. We must check whether or not it is unique, and, if so, whether that filename is executable. */ if (absolute_program (hint_text)) { /* Perform tilde expansion on what's passed, so we don't end up passing filenames with tildes directly to stat(). */ if (*hint_text == '~') hint = bash_tilde_expand (hint_text, 0); else hint = savestring (hint_text); hint_len = strlen (hint); if (filename_hint) free (filename_hint); filename_hint = savestring (hint); mapping_over = 4; istate = 0; goto inner; } hint = savestring (hint_text); hint_len = strlen (hint); path = get_string_value ("PATH"); path_index = 0; /* Initialize the variables for each type of command word. */ local_index = 0; if (varlist) free (varlist); varlist = all_visible_functions (); #if defined (ALIAS) if (alias_list) free (alias_list); alias_list = all_aliases (); #endif /* ALIAS */ } /* mapping_over says what we are currently hacking. Note that every case in this list must fall through when there are no more possibilities. */ switch (mapping_over) { case 0: /* Aliases come first. */ #if defined (ALIAS) while (alias_list && alias_list[local_index]) { register char *alias; alias = alias_list[local_index++]->name; if (STREQN (alias, hint, hint_len)) return (savestring (alias)); } #endif /* ALIAS */ local_index = 0; mapping_over++; case 1: /* Then shell reserved words. */ { while (word_token_alist[local_index].word) { register char *reserved_word; reserved_word = word_token_alist[local_index++].word; if (STREQN (reserved_word, hint, hint_len)) return (savestring (reserved_word)); } local_index = 0; mapping_over++; } case 2: /* Then function names. */ while (varlist && varlist[local_index]) { register char *varname; varname = varlist[local_index++]->name; if (STREQN (varname, hint, hint_len)) return (savestring (varname)); } local_index = 0; mapping_over++; case 3: /* Then shell builtins. */ for (; local_index < num_shell_builtins; local_index++) { /* Ignore it if it doesn't have a function pointer or if it is not currently enabled. */ if (!shell_builtins[local_index].function || (shell_builtins[local_index].flags & BUILTIN_ENABLED) == 0) continue; if (STREQN (shell_builtins[local_index].name, hint, hint_len)) { int i = local_index++; return (savestring (shell_builtins[i].name)); } } local_index = 0; mapping_over++; } /* Repeatedly call filename_completion_function while we have members of PATH left. Question: should we stat each file? Answer: we call executable_file () on each file. */ outer: istate = (val != (char *)NULL); if (!istate) { char *current_path; /* Get the next directory from the path. If there is none, then we are all done. */ if (!path || !path[path_index] || (current_path = extract_colon_unit (path, &path_index)) == 0) return ((char *)NULL); if (*current_path == 0) { free (current_path); current_path = savestring ("."); } if (*current_path == '~') { char *t; t = bash_tilde_expand (current_path, 0); free (current_path); current_path = t; } if (filename_hint) free (filename_hint); filename_hint = sh_makepath (current_path, hint, 0); free (current_path); } inner: val = rl_filename_completion_function (filename_hint, istate); istate = 1; if (val == 0) { /* If the hint text is an absolute program, then don't bother searching through PATH. */ if (absolute_program (hint)) return ((char *)NULL); goto outer; } else { int match, freetemp; char *temp; if (absolute_program (hint)) { match = strncmp (val, hint, hint_len) == 0; /* If we performed tilde expansion, restore the original filename. */ if (*hint_text == '~') { int l, tl, vl; vl = strlen (val); tl = strlen (hint_text); l = vl - hint_len; /* # of chars added */ temp = (char *)xmalloc (l + 2 + tl); strcpy (temp, hint_text); strcpy (temp + tl, val + vl - l); } else temp = savestring (val); freetemp = 1; } else { temp = strrchr (val, '/'); if (temp) { temp++; freetemp = match = strncmp (temp, hint, hint_len) == 0; if (match) temp = savestring (temp); } else freetemp = match = 0; } /* If we have found a match, and it is an executable file or a directory name, return it. */ if (match && executable_or_directory (val)) { free (val); val = ""; /* So it won't be NULL. */ return (temp); } else { if (freetemp) free (temp); free (val); goto inner; } } } /* Completion inside an unterminated command substitution. */ static char * command_subst_completion_function (text, state) const char *text; int state; { static char **matches = (char **)NULL; static const char *orig_start; static char *filename_text = (char *)NULL; static int cmd_index, start_len; char *value; if (state == 0) { if (filename_text) free (filename_text); orig_start = text; if (*text == '`') text++; else if (*text == '$' && text[1] == '(') /* ) */ text += 2; start_len = text - orig_start; filename_text = savestring (text); if (matches) free (matches); /* * At this point we can entertain the idea of re-parsing * `filename_text' into a (possibly incomplete) command name and * arguments, and doing completion based on that. This is * currently very rudimentary, but it is a small improvement. */ for (value = filename_text + strlen (filename_text) - 1; value > filename_text; value--) if (whitespace (*value) || member (*value, COMMAND_SEPARATORS)) break; if (value <= filename_text) matches = rl_completion_matches (filename_text, command_word_completion_function); else { value++; start_len += value - filename_text; if (whitespace (value[-1])) matches = rl_completion_matches (value, rl_filename_completion_function); else matches = rl_completion_matches (value, command_word_completion_function); } /* If there is more than one match, rl_completion_matches has already put the lcd in matches[0]. Skip over it. */ cmd_index = matches && matches[0] && matches[1]; } if (!matches || !matches[cmd_index]) { rl_filename_quoting_desired = 0; /* disable quoting */ return ((char *)NULL); } else { value = (char *)xmalloc (1 + start_len + strlen (matches[cmd_index])); if (start_len == 1) value[0] = *orig_start; else strncpy (value, orig_start, start_len); strcpy (value + start_len, matches[cmd_index]); cmd_index++; return (value); } } /* Okay, now we write the entry_function for variable completion. */ static char * variable_completion_function (text, state) const char *text; int state; { static char **varlist = (char **)NULL; static int varlist_index; static char *varname = (char *)NULL; static int namelen; static int first_char, first_char_loc; if (!state) { if (varname) free (varname); first_char_loc = 0; first_char = text[0]; if (first_char == '$') first_char_loc++; if (text[first_char_loc] == '{') first_char_loc++; varname = savestring (text + first_char_loc); namelen = strlen (varname); if (varlist) strvec_dispose (varlist); varlist = all_variables_matching_prefix (varname); varlist_index = 0; } if (!varlist || !varlist[varlist_index]) { return ((char *)NULL); } else { char *value; value = (char *)xmalloc (4 + strlen (varlist[varlist_index])); if (first_char_loc) { value[0] = first_char; if (first_char_loc == 2) value[1] = '{'; } strcpy (value + first_char_loc, varlist[varlist_index]); if (first_char_loc == 2) strcat (value, "}"); varlist_index++; return (value); } } /* How about a completion function for hostnames? */ static char * hostname_completion_function (text, state) const char *text; int state; { static char **list = (char **)NULL; static int list_index = 0; static int first_char, first_char_loc; /* If we don't have any state, make some. */ if (state == 0) { FREE (list); list = (char **)NULL; first_char_loc = 0; first_char = *text; if (first_char == '@') first_char_loc++; list = hostnames_matching ((char *)text+first_char_loc); list_index = 0; } if (list && list[list_index]) { char *t; t = (char *)xmalloc (2 + strlen (list[list_index])); *t = first_char; strcpy (t + first_char_loc, list[list_index]); list_index++; return (t); } return ((char *)NULL); } /* * A completion function for service names from /etc/services (or wherever). */ char * bash_servicename_completion_function (text, state) const char *text; int state; { #if defined (__WIN32__) || defined (__OPENNT) || !defined (HAVE_GETSERVENT) return ((char *)NULL); #else static char *sname = (char *)NULL; static struct servent *srvent; static int snamelen, firstc; char *value; char **alist, *aentry; int afound; if (state == 0) { FREE (sname); firstc = *text; sname = savestring (text); snamelen = strlen (sname); setservent (0); } while (srvent = getservent ()) { afound = 0; if (snamelen == 0 || (STREQN (sname, srvent->s_name, snamelen))) break; /* Not primary, check aliases */ for (alist = srvent->s_aliases; aentry = *alist; alist++) { if (STREQN (sname, aentry, snamelen)) { afound = 1; break; } } if (afound) break; } if (srvent == 0) { endservent (); return ((char *)NULL); } value = afound ? savestring (aentry) : savestring (srvent->s_name); return value; #endif } /* * A completion function for group names from /etc/group (or wherever). */ char * bash_groupname_completion_function (text, state) const char *text; int state; { #if defined (__WIN32__) || defined (__OPENNT) || !defined (HAVE_GRP_H) return ((char *)NULL); #else static char *gname = (char *)NULL; static struct group *grent; static int gnamelen; char *value; if (state == 0) { FREE (gname); gname = savestring (text); gnamelen = strlen (gname); setgrent (); } while (grent = getgrent ()) { if (gnamelen == 0 || (STREQN (gname, grent->gr_name, gnamelen))) break; } if (grent == 0) { endgrent (); return ((char *)NULL); } value = savestring (grent->gr_name); return (value); #endif } /* Functions to perform history and alias expansions on the current line. */ #if defined (BANG_HISTORY) /* Perform history expansion on the current line. If no history expansion is done, pre_process_line() returns what it was passed, so we need to allocate a new line here. */ static char * history_expand_line_internal (line) char *line; { char *new_line; new_line = pre_process_line (line, 0, 0); return (new_line == line) ? savestring (line) : new_line; } #endif /* There was an error in expansion. Let the preprocessor print the error here. */ static void cleanup_expansion_error () { char *to_free; fprintf (rl_outstream, "\r\n"); to_free = pre_process_line (rl_line_buffer, 1, 0); if (to_free != rl_line_buffer) free (to_free); putc ('\r', rl_outstream); rl_forced_update_display (); } /* If NEW_LINE differs from what is in the readline line buffer, add an undo record to get from the readline line buffer contents to the new line and make NEW_LINE the current readline line. */ static void maybe_make_readline_line (new_line) char *new_line; { if (strcmp (new_line, rl_line_buffer) != 0) { rl_point = rl_end; rl_add_undo (UNDO_BEGIN, 0, 0, 0); rl_delete_text (0, rl_point); rl_point = rl_end = rl_mark = 0; rl_insert_text (new_line); rl_add_undo (UNDO_END, 0, 0, 0); } } /* Make NEW_LINE be the current readline line. This frees NEW_LINE. */ static void set_up_new_line (new_line) char *new_line; { int old_point, at_end; old_point = rl_point; at_end = rl_point == rl_end; /* If the line was history and alias expanded, then make that be one thing to undo. */ maybe_make_readline_line (new_line); free (new_line); /* Place rl_point where we think it should go. */ if (at_end) rl_point = rl_end; else if (old_point < rl_end) { rl_point = old_point; if (!whitespace (rl_line_buffer[rl_point])) rl_forward_word (1, 0); } } #if defined (ALIAS) /* Expand aliases in the current readline line. */ static int alias_expand_line (count, ignore) int count, ignore; { char *new_line; new_line = alias_expand (rl_line_buffer); if (new_line) { set_up_new_line (new_line); return (0); } else { cleanup_expansion_error (); return (1); } } #endif #if defined (BANG_HISTORY) /* History expand the line. */ static int history_expand_line (count, ignore) int count, ignore; { char *new_line; new_line = history_expand_line_internal (rl_line_buffer); if (new_line) { set_up_new_line (new_line); return (0); } else { cleanup_expansion_error (); return (1); } } /* Expand history substitutions in the current line and then insert a space (hopefully close to where we were before). */ static int tcsh_magic_space (count, ignore) int count, ignore; { int dist_from_end, old_point; old_point = rl_point; dist_from_end = rl_end - rl_point; if (history_expand_line (count, ignore) == 0) { /* Try a simple heuristic from Stephen Gildea <gildea@intouchsys.com>. This works if all expansions were before rl_point or if no expansions were performed. */ rl_point = (old_point == 0) ? old_point : rl_end - dist_from_end; rl_insert (1, ' '); return (0); } else return (1); } #endif /* History and alias expand the line. */ static int history_and_alias_expand_line (count, ignore) int count, ignore; { char *new_line; new_line = pre_process_line (rl_line_buffer, 0, 0); if (new_line == rl_line_buffer) new_line = savestring (new_line); #if defined (ALIAS) if (new_line) { char *alias_line; alias_line = alias_expand (new_line); free (new_line); new_line = alias_line; } #endif /* ALIAS */ if (new_line) { set_up_new_line (new_line); return (0); } else { cleanup_expansion_error (); return (1); } } /* History and alias expand the line, then perform the shell word expansions by calling expand_string. This can't use set_up_new_line() because we want the variable expansions as a separate undo'able set of operations. */ static int shell_expand_line (count, ignore) int count, ignore; { char *new_line; WORD_LIST *expanded_string; new_line = pre_process_line (rl_line_buffer, 0, 0); if (new_line == rl_line_buffer) new_line = savestring (new_line); #if defined (ALIAS) if (new_line) { char *alias_line; alias_line = alias_expand (new_line); free (new_line); new_line = alias_line; } #endif /* ALIAS */ if (new_line) { int old_point = rl_point; int at_end = rl_point == rl_end; /* If the line was history and alias expanded, then make that be one thing to undo. */ maybe_make_readline_line (new_line); free (new_line); /* If there is variable expansion to perform, do that as a separate operation to be undone. */ new_line = savestring (rl_line_buffer); expanded_string = expand_string (new_line, 0); FREE (new_line); if (expanded_string == 0) { new_line = (char *)xmalloc (1); new_line[0] = '\0'; } else { new_line = string_list (expanded_string); dispose_words (expanded_string); } maybe_make_readline_line (new_line); free (new_line); /* Place rl_point where we think it should go. */ if (at_end) rl_point = rl_end; else if (old_point < rl_end) { rl_point = old_point; if (!whitespace (rl_line_buffer[rl_point])) rl_forward_word (1, 0); } return 0; } else { cleanup_expansion_error (); return 1; } } /* Define NO_FORCE_FIGNORE if you want to match filenames that would otherwise be ignored if they are the only possible matches. */ /* #define NO_FORCE_FIGNORE */ /* If FIGNORE is set, then don't match files with the given suffixes when completing filenames. If only one of the possibilities has an acceptable suffix, delete the others, else just return and let the completer signal an error. It is called by the completer when real completions are done on filenames by the completer's internal function, not for completion lists (M-?) and not on "other" completion types, such as hostnames or commands. */ static struct ignorevar fignore = { "FIGNORE", (struct ign *)0, 0, (char *)0, (sh_iv_item_func_t *) 0, }; static void _ignore_completion_names (names, name_func) char **names; sh_ignore_func_t *name_func; { char **newnames; int idx, nidx; #ifdef NO_FORCE_FIGNORE char **oldnames; int oidx; #endif /* If there is only one completion, see if it is acceptable. If it is not, free it up. In any case, short-circuit and return. This is a special case because names[0] is not the prefix of the list of names if there is only one completion; it is the completion itself. */ if (names[1] == (char *)0) { #ifndef NO_FORCE_FIGNORE if ((*name_func) (names[0]) == 0) { free (names[0]); names[0] = (char *)NULL; } #endif return; } /* Allocate space for array to hold list of pointers to matching filenames. The pointers are copied back to NAMES when done. */ for (nidx = 1; names[nidx]; nidx++) ; newnames = strvec_create (nidx + 1); #ifdef NO_FORCE_FIGNORE oldnames = strvec_create (nidx - 1); oidx = 0; #endif newnames[0] = names[0]; for (idx = nidx = 1; names[idx]; idx++) { if ((*name_func) (names[idx])) newnames[nidx++] = names[idx]; else #ifndef NO_FORCE_FIGNORE free (names[idx]); #else oldnames[oidx++] = names[idx]; #endif } newnames[nidx] = (char *)NULL; /* If none are acceptable then let the completer handle it. */ if (nidx == 1) { #ifndef NO_FORCE_FIGNORE free (names[0]); names[0] = (char *)NULL; #else free (oldnames); #endif free (newnames); return; } #ifdef NO_FORCE_FIGNORE while (oidx) free (oldnames[--oidx]); free (oldnames); #endif /* If only one is acceptable, copy it to names[0] and return. */ if (nidx == 2) { free (names[0]); names[0] = newnames[1]; names[1] = (char *)NULL; free (newnames); return; } /* Copy the acceptable names back to NAMES, set the new array end, and return. */ for (nidx = 1; newnames[nidx]; nidx++) names[nidx] = newnames[nidx]; names[nidx] = (char *)NULL; free (newnames); } static int name_is_acceptable (name) const char *name; { struct ign *p; int nlen; for (nlen = strlen (name), p = fignore.ignores; p->val; p++) { if (nlen > p->len && p->len > 0 && STREQ (p->val, &name[nlen - p->len])) return (0); } return (1); } #if 0 static int ignore_dot_names (name) char *name; { return (name[0] != '.'); } #endif static int filename_completion_ignore (names) char **names; { #if 0 if (glob_dot_filenames == 0) _ignore_completion_names (names, ignore_dot_names); #endif setup_ignore_patterns (&fignore); if (fignore.num_ignores == 0) return 0; _ignore_completion_names (names, name_is_acceptable); return 0; } /* Return 1 if NAME is a directory. */ static int test_for_directory (name) const char *name; { struct stat finfo; char *fn; fn = bash_tilde_expand (name, 0); if (stat (fn, &finfo) != 0) { free (fn); return 0; } free (fn); return (S_ISDIR (finfo.st_mode)); } /* Remove files from NAMES, leaving directories. */ static int bash_ignore_filenames (names) char **names; { _ignore_completion_names (names, test_for_directory); return 0; } static int return_zero (name) const char *name; { return 0; } static int bash_ignore_everything (names) char **names; { _ignore_completion_names (names, return_zero); return 0; } /* Handle symbolic link references and other directory name expansions while hacking completion. */ static int bash_directory_completion_hook (dirname) char **dirname; { char *local_dirname, *new_dirname, *t; int return_value, should_expand_dirname; WORD_LIST *wl; return_value = should_expand_dirname = 0; local_dirname = *dirname; #if 0 should_expand_dirname = xstrchr (local_dirname, '$') || xstrchr (local_dirname, '`'); #else if (xstrchr (local_dirname, '$')) should_expand_dirname = 1; else { t = xstrchr (local_dirname, '`'); if (t && unclosed_pair (local_dirname, strlen (local_dirname), "`") == 0) should_expand_dirname = 1; } #endif if (should_expand_dirname) { new_dirname = savestring (local_dirname); wl = expand_prompt_string (new_dirname, 0); /* does the right thing */ if (wl) { *dirname = string_list (wl); /* Tell the completer to replace the directory name only if we actually expanded something. */ return_value = STREQ (local_dirname, *dirname) == 0; free (local_dirname); free (new_dirname); dispose_words (wl); local_dirname = *dirname; } else { free (new_dirname); free (local_dirname); *dirname = (char *)xmalloc (1); **dirname = '\0'; return 1; } } if (!no_symbolic_links && (local_dirname[0] != '.' || local_dirname[1])) { char *temp1, *temp2; int len1, len2; t = get_working_directory ("symlink-hook"); temp1 = make_absolute (local_dirname, t); free (t); temp2 = sh_canonpath (temp1, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); /* If we can't canonicalize, bail. */ if (temp2 == 0) { free (temp1); return 1; } len1 = strlen (temp1); if (temp1[len1 - 1] == '/') { len2 = strlen (temp2); temp2 = (char *)xrealloc (temp2, len2 + 2); temp2[len2] = '/'; temp2[len2 + 1] = '\0'; } free (local_dirname); *dirname = temp2; free (temp1); } return (return_value); } static char **history_completion_array = (char **)NULL; static int harry_size; static int harry_len; static void build_history_completion_array () { register int i, j; HIST_ENTRY **hlist; char **tokens; /* First, clear out the current dynamic history completion list. */ if (harry_size) { strvec_dispose (history_completion_array); history_completion_array = (char **)NULL; harry_size = 0; harry_len = 0; } /* Next, grovel each line of history, making each shell-sized token a separate entry in the history_completion_array. */ hlist = history_list (); if (hlist) { for (i = 0; hlist[i]; i++) { /* Separate each token, and place into an array. */ tokens = history_tokenize (hlist[i]->line); for (j = 0; tokens && tokens[j]; j++) { if (harry_len + 2 > harry_size) history_completion_array = strvec_resize (history_completion_array, harry_size += 10); history_completion_array[harry_len++] = tokens[j]; history_completion_array[harry_len] = (char *)NULL; } free (tokens); } /* Sort the complete list of tokens. */ qsort (history_completion_array, harry_len, sizeof (char *), (QSFUNC *)strvec_strcmp); } } static char * history_completion_generator (hint_text, state) const char *hint_text; int state; { static int local_index, len; static const char *text; /* If this is the first call to the generator, then initialize the list of strings to complete over. */ if (state == 0) { local_index = 0; build_history_completion_array (); text = hint_text; len = strlen (text); } while (history_completion_array && history_completion_array[local_index]) { if (strncmp (text, history_completion_array[local_index++], len) == 0) return (savestring (history_completion_array[local_index - 1])); } return ((char *)NULL); } static int dynamic_complete_history (count, key) int count, key; { int r; rl_compentry_func_t *orig_func; rl_completion_func_t *orig_attempt_func; orig_func = rl_completion_entry_function; orig_attempt_func = rl_attempted_completion_function; rl_completion_entry_function = history_completion_generator; rl_attempted_completion_function = (rl_completion_func_t *)NULL; /* XXX - use rl_completion_mode here? */ if (rl_last_func == dynamic_complete_history) r = rl_complete_internal ('?'); else r = rl_complete_internal (TAB); rl_completion_entry_function = orig_func; rl_attempted_completion_function = orig_attempt_func; return r; } #if defined (SPECIFIC_COMPLETION_FUNCTIONS) static int bash_complete_username (ignore, ignore2) int ignore, ignore2; { return bash_complete_username_internal (rl_completion_mode (bash_complete_username)); } static int bash_possible_username_completions (ignore, ignore2) int ignore, ignore2; { return bash_complete_username_internal ('?'); } static int bash_complete_username_internal (what_to_do) int what_to_do; { return bash_specific_completion (what_to_do, rl_username_completion_function); } static int bash_complete_filename (ignore, ignore2) int ignore, ignore2; { return bash_complete_filename_internal (rl_completion_mode (bash_complete_filename)); } static int bash_possible_filename_completions (ignore, ignore2) int ignore, ignore2; { return bash_complete_filename_internal ('?'); } static int bash_complete_filename_internal (what_to_do) int what_to_do; { rl_compentry_func_t *orig_func; rl_completion_func_t *orig_attempt_func; rl_icppfunc_t *orig_dir_func; const char *orig_rl_completer_word_break_characters; int r; orig_func = rl_completion_entry_function; orig_attempt_func = rl_attempted_completion_function; orig_dir_func = rl_directory_completion_hook; orig_rl_completer_word_break_characters = rl_completer_word_break_characters; rl_completion_entry_function = rl_filename_completion_function; rl_attempted_completion_function = (rl_completion_func_t *)NULL; rl_directory_completion_hook = (rl_icppfunc_t *)NULL; rl_completer_word_break_characters = " \t\n\"\'"; r = rl_complete_internal (what_to_do); rl_completion_entry_function = orig_func; rl_attempted_completion_function = orig_attempt_func; rl_directory_completion_hook = orig_dir_func; rl_completer_word_break_characters = orig_rl_completer_word_break_characters; return r; } static int bash_complete_hostname (ignore, ignore2) int ignore, ignore2; { return bash_complete_hostname_internal (rl_completion_mode (bash_complete_hostname)); } static int bash_possible_hostname_completions (ignore, ignore2) int ignore, ignore2; { return bash_complete_hostname_internal ('?'); } static int bash_complete_variable (ignore, ignore2) int ignore, ignore2; { return bash_complete_variable_internal (rl_completion_mode (bash_complete_variable)); } static int bash_possible_variable_completions (ignore, ignore2) int ignore, ignore2; { return bash_complete_variable_internal ('?'); } static int bash_complete_command (ignore, ignore2) int ignore, ignore2; { return bash_complete_command_internal (rl_completion_mode (bash_complete_command)); } static int bash_possible_command_completions (ignore, ignore2) int ignore, ignore2; { return bash_complete_command_internal ('?'); } static int bash_complete_hostname_internal (what_to_do) int what_to_do; { return bash_specific_completion (what_to_do, hostname_completion_function); } static int bash_complete_variable_internal (what_to_do) int what_to_do; { return bash_specific_completion (what_to_do, variable_completion_function); } static int bash_complete_command_internal (what_to_do) int what_to_do; { return bash_specific_completion (what_to_do, command_word_completion_function); } static char *globtext; static char *globorig; static char * glob_complete_word (text, state) const char *text; int state; { static char **matches = (char **)NULL; static int ind; int glen; char *ret; if (state == 0) { rl_filename_completion_desired = 1; FREE (matches); if (globorig != globtext) FREE (globorig); FREE (globtext); if (rl_explicit_arg) { globorig = savestring (text); glen = strlen (text); globtext = (char *)xmalloc (glen + 2); strcpy (globtext, text); globtext[glen] = '*'; globtext[glen+1] = '\0'; } else globtext = globorig = savestring (text); matches = shell_glob_filename (globtext); if (GLOB_FAILED (matches)) matches = (char **)NULL; ind = 0; } ret = matches ? matches[ind] : (char *)NULL; ind++; return ret; } static int bash_glob_completion_internal (what_to_do) int what_to_do; { return bash_specific_completion (what_to_do, glob_complete_word); } /* A special quoting function so we don't end up quoting globbing characters in the word if there are no matches or multiple matches. */ static char * bash_glob_quote_filename (s, rtype, qcp) char *s; int rtype; char *qcp; { if (globorig && qcp && *qcp == '\0' && STREQ (s, globorig)) return (savestring (s)); else return (bash_quote_filename (s, rtype, qcp)); } static int bash_glob_complete_word (count, key) int count, key; { int r; rl_quote_func_t *orig_quoting_function; rl_explicit_arg = 1; /* force `*' append */ orig_quoting_function = rl_filename_quoting_function; rl_filename_quoting_function = bash_glob_quote_filename; r = bash_glob_completion_internal (rl_completion_mode (bash_glob_complete_word)); rl_filename_quoting_function = orig_quoting_function; return r; } static int bash_glob_expand_word (count, key) int count, key; { return bash_glob_completion_internal ('*'); } static int bash_glob_list_expansions (count, key) int count, key; { return bash_glob_completion_internal ('?'); } static int bash_specific_completion (what_to_do, generator) int what_to_do; rl_compentry_func_t *generator; { rl_compentry_func_t *orig_func; rl_completion_func_t *orig_attempt_func; int r; orig_func = rl_completion_entry_function; orig_attempt_func = rl_attempted_completion_function; rl_completion_entry_function = generator; rl_attempted_completion_function = NULL; r = rl_complete_internal (what_to_do); rl_completion_entry_function = orig_func; rl_attempted_completion_function = orig_attempt_func; return r; } #endif /* SPECIFIC_COMPLETION_FUNCTIONS */ /* Filename quoting for completion. */ /* A function to strip unquoted quote characters (single quotes, double quotes, and backslashes). It allows single quotes to appear within double quotes, and vice versa. It should be smarter. */ static char * bash_dequote_filename (text, quote_char) char *text; int quote_char; { char *ret, *p, *r; int l, quoted; l = strlen (text); ret = (char *)xmalloc (l + 1); for (quoted = quote_char, p = text, r = ret; p && *p; p++) { /* Allow backslash-quoted characters to pass through unscathed. */ if (*p == '\\') { *r++ = *++p; if (*p == '\0') break; continue; } /* Close quote. */ if (quoted && *p == quoted) { quoted = 0; continue; } /* Open quote. */ if (quoted == 0 && (*p == '\'' || *p == '"')) { quoted = *p; continue; } *r++ = *p; } *r = '\0'; return ret; } /* Quote characters that the readline completion code would treat as word break characters with backslashes. Pass backslash-quoted characters through without examination. */ static char * quote_word_break_chars (text) char *text; { char *ret, *r, *s; int l; l = strlen (text); ret = (char *)xmalloc ((2 * l) + 1); for (s = text, r = ret; *s; s++) { /* Pass backslash-quoted characters through, including the backslash. */ if (*s == '\\') { *r++ = '\\'; *r++ = *++s; if (*s == '\0') break; continue; } /* OK, we have an unquoted character. Check its presence in rl_completer_word_break_characters. */ if (xstrchr (rl_completer_word_break_characters, *s)) *r++ = '\\'; *r++ = *s; } *r = '\0'; return ret; } /* Quote a filename using double quotes, single quotes, or backslashes depending on the value of completion_quoting_style. If we're completing using backslashes, we need to quote some additional characters (those that readline treats as word breaks), so we call quote_word_break_chars on the result. This returns newly-allocated memory. */ static char * bash_quote_filename (s, rtype, qcp) char *s; int rtype; char *qcp; { char *rtext, *mtext, *ret; int rlen, cs; rtext = (char *)NULL; /* If RTYPE == MULT_MATCH, it means that there is more than one match. In this case, we do not add the closing quote or attempt to perform tilde expansion. If RTYPE == SINGLE_MATCH, we try to perform tilde expansion, because single and double quotes inhibit tilde expansion by the shell. */ mtext = s; if (mtext[0] == '~' && rtype == SINGLE_MATCH) mtext = bash_tilde_expand (s, 0); cs = completion_quoting_style; /* Might need to modify the default completion style based on *qcp, since it's set to any user-provided opening quote. We also change to single-quoting if there is no user-provided opening quote and the word being completed contains newlines, since those are not quoted correctly using backslashes (a backslash-newline pair is special to the shell parser). */ if (*qcp == '\0' && cs == COMPLETE_BSQUOTE && xstrchr (mtext, '\n')) cs = COMPLETE_SQUOTE; else if (*qcp == '"') cs = COMPLETE_DQUOTE; else if (*qcp == '\'') cs = COMPLETE_SQUOTE; #if defined (BANG_HISTORY) else if (*qcp == '\0' && history_expansion && cs == COMPLETE_DQUOTE && history_expansion_inhibited == 0 && xstrchr (mtext, '!')) cs = COMPLETE_BSQUOTE; if (*qcp == '"' && history_expansion && cs == COMPLETE_DQUOTE && history_expansion_inhibited == 0 && xstrchr (mtext, '!')) { cs = COMPLETE_BSQUOTE; *qcp = '\0'; } #endif switch (cs) { case COMPLETE_DQUOTE: rtext = sh_double_quote (mtext); break; case COMPLETE_SQUOTE: rtext = sh_single_quote (mtext); break; case COMPLETE_BSQUOTE: rtext = sh_backslash_quote (mtext); break; } if (mtext != s) free (mtext); /* We may need to quote additional characters: those that readline treats as word breaks that are not quoted by backslash_quote. */ if (rtext && cs == COMPLETE_BSQUOTE) { mtext = quote_word_break_chars (rtext); free (rtext); rtext = mtext; } /* Leave the opening quote intact. The readline completion code takes care of avoiding doubled opening quotes. */ rlen = strlen (rtext); ret = (char *)xmalloc (rlen + 1); strcpy (ret, rtext); /* If there are multiple matches, cut off the closing quote. */ if (rtype == MULT_MATCH && cs != COMPLETE_BSQUOTE) ret[rlen - 1] = '\0'; free (rtext); return ret; } /* Support for binding readline key sequences to Unix commands. */ static Keymap cmd_xmap; static int bash_execute_unix_command (count, key) int count; /* ignored */ int key; { Keymap ckmap; /* current keymap */ Keymap xkmap; /* unix command executing keymap */ register int i; char *cmd; int old_line_count; int *ts; /* First, we need to find the right command to execute. This is tricky, because we might have already indirected into another keymap. */ ckmap = rl_get_keymap (); if (ckmap != rl_executing_keymap) { /* bogus. we have to search. only handle one level of indirection. */ for (i = 0; i < KEYMAP_SIZE; i++) { if (ckmap[i].type == ISKMAP && (Keymap)ckmap[i].function == rl_executing_keymap) break; } if (i < KEYMAP_SIZE) xkmap = (Keymap)cmd_xmap[i].function; else { rl_crlf (); internal_error ("bash_execute_unix_command: cannot find keymap for command"); rl_forced_update_display (); return 1; } } else xkmap = cmd_xmap; cmd = (char *)xkmap[key].function; if (cmd == 0) { rl_ding (); return 1; } rl_crlf (); /* move to a new line */ old_line_count = current_command_line_count; ts = save_token_state (); cmd = savestring (cmd); parse_and_execute (cmd, "bash_execute_unix_command", SEVAL_NOHIST); current_command_line_count = old_line_count; restore_token_state (ts); /* and restore the readline buffer and display after command execution. */ rl_forced_update_display (); return 0; } static void init_unix_command_map () { cmd_xmap = rl_make_bare_keymap (); } static int isolate_sequence (string, ind, need_dquote, startp) char *string; int ind, need_dquote, *startp; { register int i; int c, passc, delim; for (i = ind; string[i] && whitespace (string[i]); i++) ; /* NEED_DQUOTE means that the first non-white character *must* be `"'. */ if (need_dquote && string[i] != '"') { builtin_error ("%s: first non-whitespace character is not `\"'", string); return -1; } /* We can have delimited strings even if NEED_DQUOTE == 0, like the command string to bind the key sequence to. */ delim = (string[i] == '"' || string[i] == '\'') ? string[i] : 0; if (startp) *startp = delim ? ++i : i; for (passc = 0; c = string[i]; i++) { if (passc) { passc = 0; continue; } if (c == '\\') { passc++; continue; } if (c == delim) break; } if (delim && string[i] != delim) { builtin_error ("%s: no closing `%c'", string, delim); return -1; } return i; } int bind_keyseq_to_unix_command (line) char *line; { Keymap kmap; char *kseq, *value; int i, kstart; if (cmd_xmap == 0) init_unix_command_map (); kmap = rl_get_keymap (); /* We duplicate some of the work done by rl_parse_and_bind here, but this code only has to handle `"keyseq": ["]command["]' and can generate an error for anything else. */ i = isolate_sequence (line, 0, 1, &kstart); if (i < 0) return -1; /* Create the key sequence string to pass to rl_generic_bind */ kseq = substring (line, kstart, i); for ( ; line[i] && line[i] != ':'; i++) ; if (line[i] != ':') { builtin_error ("%s: missing colon separator", line); return -1; } i = isolate_sequence (line, i + 1, 0, &kstart); if (i < 0) return -1; /* Create the value string containing the command to execute. */ value = substring (line, kstart, i); /* Save the command to execute and the key sequence in the CMD_XMAP */ rl_generic_bind (ISMACR, kseq, value, cmd_xmap); /* and bind the key sequence in the current keymap to a function that understands how to execute from CMD_XMAP */ rl_set_key (kseq, bash_execute_unix_command, kmap); return 0; } /* Used by the programmable completion code. Complete TEXT as a filename, but return only directories as matches. Dequotes the filename before attempting to find matches. */ char ** bash_directory_completion_matches (text) const char *text; { char **m1; char *dfn; int qc; qc = (text[0] == '"' || text[0] == '\'') ? text[0] : 0; dfn = bash_dequote_filename ((char *)text, qc); m1 = rl_completion_matches (dfn, rl_filename_completion_function); free (dfn); if (m1 == 0 || m1[0] == 0) return m1; /* We don't bother recomputing the lcd of the matches, because it will just get thrown away by the programmable completion code and recomputed later. */ (void)bash_ignore_filenames (m1); return m1; } #endif /* READLINE */
/* list.c - Functions for manipulating linked lists of objects. */ /* Copyright (C) 1996 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "shell.h" /* A global variable which acts as a sentinel for an `error' list return. */ GENERIC_LIST global_error_list; #ifdef INCLUDE_UNUSED /* Call FUNCTION on every member of LIST, a generic list. */ void list_walk (list, function) GENERIC_LIST *list; sh_glist_func_t *function; { for ( ; list; list = list->next) if ((*function) (list) < 0) return; } /* Call FUNCTION on every string in WORDS. */ void wlist_walk (words, function) WORD_LIST *words; sh_icpfunc_t *function; { for ( ; words; words = words->next) if ((*function) (words->word->word) < 0) return; } #endif /* INCLUDE_UNUSED */ /* Reverse the chain of structures in LIST. Output the new head of the chain. You should always assign the output value of this function to something, or you will lose the chain. */ GENERIC_LIST * list_reverse (list) GENERIC_LIST *list; { register GENERIC_LIST *next, *prev; for (prev = (GENERIC_LIST *)NULL; list; ) { next = list->next; list->next = prev; prev = list; list = next; } return (prev); } /* Return the number of elements in LIST, a generic list. */ int list_length (list) GENERIC_LIST *list; { register int i; for (i = 0; list; list = list->next, i++); return (i); } /* Append TAIL to HEAD. Return the header of the list. */ GENERIC_LIST * list_append (head, tail) GENERIC_LIST *head, *tail; { register GENERIC_LIST *t_head; if (head == 0) return (tail); for (t_head = head; t_head->next; t_head = t_head->next) ; t_head->next = tail; return (head); } #ifdef INCLUDE_UNUSED /* Delete the element of LIST which satisfies the predicate function COMPARER. Returns the element that was deleted, so you can dispose of it, or -1 if the element wasn't found. COMPARER is called with the list element and then ARG. Note that LIST contains the address of a variable which points to the list. You might call this function like this: SHELL_VAR *elt = list_remove (&variable_list, check_var_has_name, "foo"); dispose_variable (elt); */ GENERIC_LIST * list_remove (list, comparer, arg) GENERIC_LIST **list; Function *comparer; char *arg; { register GENERIC_LIST *prev, *temp; for (prev = (GENERIC_LIST *)NULL, temp = *list; temp; prev = temp, temp = temp->next) { if ((*comparer) (temp, arg)) { if (prev) prev->next = temp->next; else *list = temp->next; return (temp); } } return ((GENERIC_LIST *)&global_error_list); } #endif
/* stringlib.c - Miscellaneous string functions. */ /* Copyright (C) 1996-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #include "bashtypes.h" #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "bashansi.h" #include <stdio.h> #include "chartypes.h" #include "shell.h" #include "pathexp.h" #include <glob/glob.h> #if defined (EXTENDED_GLOB) # include <glob/strmatch.h> #endif /* **************************************************************** */ /* */ /* Functions to manage arrays of strings */ /* */ /* **************************************************************** */ /* Find STRING in ALIST, a list of string key/int value pairs. If FLAGS is 1, STRING is treated as a pattern and matched using strmatch. */ int find_string_in_alist (string, alist, flags) char *string; STRING_INT_ALIST *alist; int flags; { register int i; int r; for (i = r = 0; alist[i].word; i++) { #if defined (EXTENDED_GLOB) if (flags) r = strmatch (alist[i].word, string, FNM_EXTMATCH) != FNM_NOMATCH; else #endif r = STREQ (string, alist[i].word); if (r) return (alist[i].token); } return -1; } /* Find TOKEN in ALIST, a list of string/int value pairs. Return the corresponding string. Allocates memory for the returned string. FLAGS is currently ignored, but reserved. */ char * find_token_in_alist (token, alist, flags) int token; STRING_INT_ALIST *alist; int flags; { register int i; for (i = 0; alist[i].word; i++) { if (alist[i].token == token) return (savestring (alist[i].word)); } return ((char *)NULL); } int find_index_in_alist (string, alist, flags) char *string; STRING_INT_ALIST *alist; int flags; { register int i; int r; for (i = r = 0; alist[i].word; i++) { #if defined (EXTENDED_GLOB) if (flags) r = strmatch (alist[i].word, string, FNM_EXTMATCH) != FNM_NOMATCH; else #endif r = STREQ (string, alist[i].word); if (r) return (i); } return -1; } /* **************************************************************** */ /* */ /* String Management Functions */ /* */ /* **************************************************************** */ /* Cons a new string from STRING starting at START and ending at END, not including END. */ char * substring (string, start, end) char *string; int start, end; { register int len; register char *result; len = end - start; result = (char *)xmalloc (len + 1); strncpy (result, string + start, len); result[len] = '\0'; return (result); } /* Replace occurrences of PAT with REP in STRING. If GLOBAL is non-zero, replace all occurrences, otherwise replace only the first. This returns a new string; the caller should free it. */ char * strsub (string, pat, rep, global) char *string, *pat, *rep; int global; { int patlen, replen, templen, tempsize, repl, i; char *temp, *r; patlen = strlen (pat); replen = strlen (rep); for (temp = (char *)NULL, i = templen = tempsize = 0, repl = 1; string[i]; ) { if (repl && STREQN (string + i, pat, patlen)) { if (replen) RESIZE_MALLOCED_BUFFER (temp, templen, replen, tempsize, (replen * 2)); for (r = rep; *r; ) temp[templen++] = *r++; i += patlen ? patlen : 1; /* avoid infinite recursion */ repl = global != 0; } else { RESIZE_MALLOCED_BUFFER (temp, templen, 1, tempsize, 16); temp[templen++] = string[i++]; } } temp[templen] = 0; return (temp); } /* Replace all instances of C in STRING with TEXT. TEXT may be empty or NULL. If DO_GLOB is non-zero, we quote the replacement text for globbing. Backslash may be used to quote C. */ char * strcreplace (string, c, text, do_glob) char *string; int c; char *text; int do_glob; { char *ret, *p, *r, *t; int len, rlen, ind, tlen; len = STRLEN (text); rlen = len + strlen (string) + 2; ret = (char *)xmalloc (rlen); for (p = string, r = ret; p && *p; ) { if (*p == c) { if (len) { ind = r - ret; if (do_glob && (glob_pattern_p (text) || strchr (text, '\\'))) { t = quote_globbing_chars (text); tlen = strlen (t); RESIZE_MALLOCED_BUFFER (ret, ind, tlen, rlen, rlen); r = ret + ind; /* in case reallocated */ strcpy (r, t); r += tlen; free (t); } else { RESIZE_MALLOCED_BUFFER (ret, ind, len, rlen, rlen); r = ret + ind; /* in case reallocated */ strcpy (r, text); r += len; } } p++; continue; } if (*p == '\\' && p[1] == c) p++; ind = r - ret; RESIZE_MALLOCED_BUFFER (ret, ind, 2, rlen, rlen); r = ret + ind; /* in case reallocated */ *r++ = *p++; } *r = '\0'; return ret; } #ifdef INCLUDE_UNUSED /* Remove all leading whitespace from STRING. This includes newlines. STRING should be terminated with a zero. */ void strip_leading (string) char *string; { char *start = string; while (*string && (whitespace (*string) || *string == '\n')) string++; if (string != start) { int len = strlen (string); FASTCOPY (string, start, len); start[len] = '\0'; } } #endif /* Remove all trailing whitespace from STRING. This includes newlines. If NEWLINES_ONLY is non-zero, only trailing newlines are removed. STRING should be terminated with a zero. */ void strip_trailing (string, len, newlines_only) char *string; int len; int newlines_only; { while (len >= 0) { if ((newlines_only && string[len] == '\n') || (!newlines_only && whitespace (string[len]))) len--; else break; } string[len + 1] = '\0'; } /* A wrapper for bcopy that can be prototyped in general.h */ void xbcopy (s, d, n) char *s, *d; int n; { FASTCOPY (s, d, n); }
/* findcmd.c -- Functions to search for commands by name. */ /* Copyright (C) 1997 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ #include "config.h" #include <stdio.h> #include "chartypes.h" #include "bashtypes.h" #ifndef _MINIX # include <sys/file.h> #endif #include "filecntl.h" #include "posixstat.h" #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include "bashansi.h" #include "memalloc.h" #include "shell.h" #include "flags.h" #include "hashlib.h" #include "pathexp.h" #include "hashcmd.h" #include "findcmd.h" /* matching prototypes and declarations */ extern int posixly_correct; /* Static functions defined and used in this file. */ static char *_find_user_command_internal __P((const char *, int)); static char *find_user_command_internal __P((const char *, int)); static char *find_user_command_in_path __P((const char *, char *, int)); static char *find_in_path_element __P((const char *, char *, int, int, struct stat *)); static char *find_absolute_program __P((const char *, int)); static char *get_next_path_element __P((char *, int *)); /* The file name which we would try to execute, except that it isn't possible to execute it. This is the first file that matches the name that we are looking for while we are searching $PATH for a suitable one to execute. If we cannot find a suitable executable file, then we use this one. */ static char *file_to_lose_on; /* Non-zero if we should stat every command found in the hash table to make sure it still exists. */ int check_hashed_filenames; /* DOT_FOUND_IN_SEARCH becomes non-zero when find_user_command () encounters a `.' as the directory pathname while scanning the list of possible pathnames; i.e., if `.' comes before the directory containing the file of interest. */ int dot_found_in_search = 0; #define u_mode_bits(x) (((x) & 0000700) >> 6) #define g_mode_bits(x) (((x) & 0000070) >> 3) #define o_mode_bits(x) (((x) & 0000007) >> 0) #define X_BIT(x) ((x) & 1) /* Return some flags based on information about this file. The EXISTS bit is non-zero if the file is found. The EXECABLE bit is non-zero the file is executble. Zero is returned if the file is not found. */ int file_status (name) const char *name; { struct stat finfo; /* Determine whether this file exists or not. */ if (stat (name, &finfo) < 0) return (0); /* If the file is a directory, then it is not "executable" in the sense of the shell. */ if (S_ISDIR (finfo.st_mode)) return (FS_EXISTS|FS_DIRECTORY); #if defined (AFS) /* We have to use access(2) to determine access because AFS does not support Unix file system semantics. This may produce wrong answers for non-AFS files when ruid != euid. I hate AFS. */ if (access (name, X_OK) == 0) return (FS_EXISTS | FS_EXECABLE); else return (FS_EXISTS); #else /* !AFS */ /* Find out if the file is actually executable. By definition, the only other criteria is that the file has an execute bit set that we can use. */ /* Root only requires execute permission for any of owner, group or others to be able to exec a file. */ if (current_user.euid == (uid_t)0) { int bits; bits = (u_mode_bits (finfo.st_mode) | g_mode_bits (finfo.st_mode) | o_mode_bits (finfo.st_mode)); if (X_BIT (bits)) return (FS_EXISTS | FS_EXECABLE); } /* If we are the owner of the file, the owner execute bit applies. */ if (current_user.euid == finfo.st_uid && X_BIT (u_mode_bits (finfo.st_mode))) return (FS_EXISTS | FS_EXECABLE); /* If we are in the owning group, the group permissions apply. */ if (group_member (finfo.st_gid) && X_BIT (g_mode_bits (finfo.st_mode))) return (FS_EXISTS | FS_EXECABLE); /* If `others' have execute permission to the file, then so do we, since we are also `others'. */ if (X_BIT (o_mode_bits (finfo.st_mode))) return (FS_EXISTS | FS_EXECABLE); return (FS_EXISTS); #endif /* !AFS */ } /* Return non-zero if FILE exists and is executable. Note that this function is the definition of what an executable file is; do not change this unless YOU know what an executable file is. */ int executable_file (file) const char *file; { int s; s = file_status (file); return ((s & FS_EXECABLE) && ((s & FS_DIRECTORY) == 0)); } int is_directory (file) const char *file; { return (file_status (file) & FS_DIRECTORY); } int executable_or_directory (file) const char *file; { int s; s = file_status (file); return ((s & FS_EXECABLE) || (s & FS_DIRECTORY)); } /* Locate the executable file referenced by NAME, searching along the contents of the shell PATH variable. Return a new string which is the full pathname to the file, or NULL if the file couldn't be found. If a file is found that isn't executable, and that is the only match, then return that. */ char * find_user_command (name) const char *name; { return (find_user_command_internal (name, FS_EXEC_PREFERRED|FS_NODIRS)); } /* Locate the file referenced by NAME, searching along the contents of the shell PATH variable. Return a new string which is the full pathname to the file, or NULL if the file couldn't be found. This returns the first file found. */ char * find_path_file (name) const char *name; { return (find_user_command_internal (name, FS_EXISTS)); } static char * _find_user_command_internal (name, flags) const char *name; int flags; { char *path_list, *cmd; SHELL_VAR *var; /* Search for the value of PATH in both the temporary environments and in the regular list of variables. */ if (var = find_variable_internal ("PATH", 1)) /* XXX could be array? */ path_list = value_cell (var); else path_list = (char *)NULL; if (path_list == 0 || *path_list == '\0') return (savestring (name)); cmd = find_user_command_in_path (name, path_list, flags); return (cmd); } static char * find_user_command_internal (name, flags) const char *name; int flags; { #ifdef __WIN32__ char *res, *dotexe; dotexe = (char *)xmalloc (strlen (name) + 5); strcpy (dotexe, name); strcat (dotexe, ".exe"); res = _find_user_command_internal (dotexe, flags); free (dotexe); if (res == 0) res = _find_user_command_internal (name, flags); return res; #else return (_find_user_command_internal (name, flags)); #endif } /* Return the next element from PATH_LIST, a colon separated list of paths. PATH_INDEX_POINTER is the address of an index into PATH_LIST; the index is modified by this function. Return the next element of PATH_LIST or NULL if there are no more. */ static char * get_next_path_element (path_list, path_index_pointer) char *path_list; int *path_index_pointer; { char *path; path = extract_colon_unit (path_list, path_index_pointer); if (path == 0) return (path); if (*path == '\0') { free (path); path = savestring ("."); } return (path); } /* Look for PATHNAME in $PATH. Returns either the hashed command corresponding to PATHNAME or the first instance of PATHNAME found in $PATH. Returns a newly-allocated string. */ char * search_for_command (pathname) const char *pathname; { char *hashed_file, *command; int temp_path, st; SHELL_VAR *path; hashed_file = command = (char *)NULL; /* If PATH is in the temporary environment for this command, don't use the hash table to search for the full pathname. */ path = find_variable_internal ("PATH", 1); temp_path = path && tempvar_p (path); if (temp_path == 0 && path) path = (SHELL_VAR *)NULL; /* Don't waste time trying to find hashed data for a pathname that is already completely specified or if we're using a command- specific value for PATH. */ if (path == 0 && absolute_program (pathname) == 0) hashed_file = phash_search (pathname); /* If a command found in the hash table no longer exists, we need to look for it in $PATH. Thank you Posix.2. This forces us to stat every command found in the hash table. */ if (hashed_file && (posixly_correct || check_hashed_filenames)) { st = file_status (hashed_file); if ((st ^ (FS_EXISTS | FS_EXECABLE)) != 0) { phash_remove (pathname); free (hashed_file); hashed_file = (char *)NULL; } } if (hashed_file) command = hashed_file; else if (absolute_program (pathname)) /* A command containing a slash is not looked up in PATH or saved in the hash table. */ command = savestring (pathname); else { /* If $PATH is in the temporary environment, we've already retrieved it, so don't bother trying again. */ if (temp_path) { command = find_user_command_in_path (pathname, value_cell (path), FS_EXEC_PREFERRED|FS_NODIRS); } else command = find_user_command (pathname); if (command && hashing_enabled && temp_path == 0) phash_insert ((char *)pathname, command, dot_found_in_search, 1); /* XXX fix const later */ } return (command); } char * user_command_matches (name, flags, state) const char *name; int flags, state; { register int i; int path_index, name_len; char *path_list, *path_element, *match; struct stat dotinfo; static char **match_list = NULL; static int match_list_size = 0; static int match_index = 0; if (state == 0) { /* Create the list of matches. */ if (match_list == 0) { match_list_size = 5; match_list = strvec_create (match_list_size); } /* Clear out the old match list. */ for (i = 0; i < match_list_size; i++) match_list[i] = 0; /* We haven't found any files yet. */ match_index = 0; if (absolute_program (name)) { match_list[0] = find_absolute_program (name, flags); match_list[1] = (char *)NULL; path_list = (char *)NULL; } else { name_len = strlen (name); file_to_lose_on = (char *)NULL; dot_found_in_search = 0; stat (".", &dotinfo); path_list = get_string_value ("PATH"); path_index = 0; } while (path_list && path_list[path_index]) { path_element = get_next_path_element (path_list, &path_index); if (path_element == 0) break; match = find_in_path_element (name, path_element, flags, name_len, &dotinfo); free (path_element); if (match == 0) continue; if (match_index + 1 == match_list_size) { match_list_size += 10; match_list = strvec_resize (match_list, (match_list_size + 1)); } match_list[match_index++] = match; match_list[match_index] = (char *)NULL; FREE (file_to_lose_on); file_to_lose_on = (char *)NULL; } /* We haven't returned any strings yet. */ match_index = 0; } match = match_list[match_index]; if (match) match_index++; return (match); } static char * find_absolute_program (name, flags) const char *name; int flags; { int st; st = file_status (name); /* If the file doesn't exist, quit now. */ if ((st & FS_EXISTS) == 0) return ((char *)NULL); /* If we only care about whether the file exists or not, return this filename. Otherwise, maybe we care about whether this file is executable. If it is, and that is what we want, return it. */ if ((flags & FS_EXISTS) || ((flags & FS_EXEC_ONLY) && (st & FS_EXECABLE))) return (savestring (name)); return (NULL); } static char * find_in_path_element (name, path, flags, name_len, dotinfop) const char *name; char *path; int flags, name_len; struct stat *dotinfop; { int status; char *full_path, *xpath; xpath = (*path == '~') ? bash_tilde_expand (path, 0) : path; /* Remember the location of "." in the path, in all its forms (as long as they begin with a `.', e.g. `./.') */ if (dot_found_in_search == 0 && *xpath == '.') dot_found_in_search = same_file (".", xpath, dotinfop, (struct stat *)NULL); full_path = sh_makepath (xpath, name, 0); status = file_status (full_path); if (xpath != path) free (xpath); if ((status & FS_EXISTS) == 0) { free (full_path); return ((char *)NULL); } /* The file exists. If the caller simply wants the first file, here it is. */ if (flags & FS_EXISTS) return (full_path); /* If the file is executable, then it satisfies the cases of EXEC_ONLY and EXEC_PREFERRED. Return this file unconditionally. */ if ((status & FS_EXECABLE) && (((flags & FS_NODIRS) == 0) || ((status & FS_DIRECTORY) == 0))) { FREE (file_to_lose_on); file_to_lose_on = (char *)NULL; return (full_path); } /* The file is not executable, but it does exist. If we prefer an executable, then remember this one if it is the first one we have found. */ if ((flags & FS_EXEC_PREFERRED) && file_to_lose_on == 0) file_to_lose_on = savestring (full_path); /* If we want only executable files, or we don't want directories and this file is a directory, fail. */ if ((flags & FS_EXEC_ONLY) || (flags & FS_EXEC_PREFERRED) || ((flags & FS_NODIRS) && (status & FS_DIRECTORY))) { free (full_path); return ((char *)NULL); } else return (full_path); } /* This does the dirty work for find_user_command_internal () and user_command_matches (). NAME is the name of the file to search for. PATH_LIST is a colon separated list of directories to search. FLAGS contains bit fields which control the files which are eligible. Some values are: FS_EXEC_ONLY: The file must be an executable to be found. FS_EXEC_PREFERRED: If we can't find an executable, then the the first file matching NAME will do. FS_EXISTS: The first file found will do. FS_NODIRS: Don't find any directories. */ static char * find_user_command_in_path (name, path_list, flags) const char *name; char *path_list; int flags; { char *full_path, *path; int path_index, name_len; struct stat dotinfo; /* We haven't started looking, so we certainly haven't seen a `.' as the directory path yet. */ dot_found_in_search = 0; if (absolute_program (name)) { full_path = find_absolute_program (name, flags); return (full_path); } if (path_list == 0 || *path_list == '\0') return (savestring (name)); /* XXX */ file_to_lose_on = (char *)NULL; name_len = strlen (name); stat (".", &dotinfo); path_index = 0; while (path_list[path_index]) { /* Allow the user to interrupt out of a lengthy path search. */ QUIT; path = get_next_path_element (path_list, &path_index); if (path == 0) break; /* Side effects: sets dot_found_in_search, possibly sets file_to_lose_on. */ full_path = find_in_path_element (name, path, flags, name_len, &dotinfo); free (path); /* This should really be in find_in_path_element, but there isn't the right combination of flags. */ if (full_path && is_directory (full_path)) { free (full_path); continue; } if (full_path) { FREE (file_to_lose_on); return (full_path); } } /* We didn't find exactly what the user was looking for. Return the contents of FILE_TO_LOSE_ON which is NULL when the search required an executable, or non-NULL if a file was found and the search would accept a non-executable as a last resort. If the caller specified FS_NODIRS, and file_to_lose_on is a directory, return NULL. */ if (file_to_lose_on && (flags & FS_NODIRS) && is_directory (file_to_lose_on)) { free (file_to_lose_on); file_to_lose_on = (char *)NULL; } return (file_to_lose_on); }
/* redir.c -- Functions to perform input and output redirection. */ /* Copyright (C) 1997-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if !defined (__GNUC__) && !defined (HAVE_ALLOCA_H) && defined (_AIX) #pragma alloca #endif /* _AIX && RISC6000 && !__GNUC__ */ #include <stdio.h> #include "bashtypes.h" #ifndef _MINIX # include <sys/file.h> #endif #include "filecntl.h" #include "posixstat.h" #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <errno.h> #if !defined (errno) extern int errno; #endif #include "bashansi.h" #include "memalloc.h" #include "shell.h" #include "flags.h" #include "execute_cmd.h" #include "redir.h" #if defined (BUFFERED_INPUT) # include "input.h" #endif extern int posixly_correct; extern REDIRECT *redirection_undo_list; extern REDIRECT *exec_redirection_undo_list; /* Static functions defined and used in this file. */ static void add_undo_close_redirect __P((int)); static void add_exec_redirect __P((REDIRECT *)); static int add_undo_redirect __P((int)); static int expandable_redirection_filename __P((REDIRECT *)); static int stdin_redirection __P((enum r_instruction, int)); static int do_redirection_internal __P((REDIRECT *, int, int, int)); static int write_here_document __P((int, WORD_DESC *)); static int write_here_string __P((int, WORD_DESC *)); static int here_document_to_fd __P((WORD_DESC *, enum r_instruction)); static int redir_special_open __P((int, char *, int, int, enum r_instruction)); static int noclobber_open __P((char *, int, int, enum r_instruction)); static int redir_open __P((char *, int, int, enum r_instruction)); /* Spare redirector used when translating [N]>&WORD[-] or [N]<&WORD[-] to a new redirection and when creating the redirection undo list. */ static REDIRECTEE rd; /* Set to errno when a here document cannot be created for some reason. Used to print a reasonable error message. */ static int heredoc_errno; void redirection_error (temp, error) REDIRECT *temp; int error; { char *filename, *allocname; int oflags; allocname = 0; if (temp->redirector < 0) /* This can happen when read_token_word encounters overflow, like in exec 4294967297>x */ filename = "file descriptor out of range"; #ifdef EBADF else if (temp->redirector >= 0 && errno == EBADF) { /* If we're dealing with two file descriptors, we have to guess about which one is invalid; in the cases of r_{duplicating,move}_input and r_{duplicating,move}_output we're here because dup2() failed. */ switch (temp->instruction) { case r_duplicating_input: case r_duplicating_output: case r_move_input: case r_move_output: filename = allocname = itos (temp->redirectee.dest); break; default: filename = allocname = itos (temp->redirector); break; } } #endif else if (expandable_redirection_filename (temp)) { if (posixly_correct && interactive_shell == 0) { oflags = temp->redirectee.filename->flags; temp->redirectee.filename->flags |= W_NOGLOB; } filename = allocname = redirection_expand (temp->redirectee.filename); if (posixly_correct && interactive_shell == 0) temp->redirectee.filename->flags = oflags; if (filename == 0) filename = temp->redirectee.filename->word; } else if (temp->redirectee.dest < 0) filename = "file descriptor out of range"; else filename = allocname = itos (temp->redirectee.dest); switch (error) { case AMBIGUOUS_REDIRECT: internal_error ("%s: ambiguous redirect", filename); break; case NOCLOBBER_REDIRECT: internal_error ("%s: cannot overwrite existing file", filename); break; #if defined (RESTRICTED_SHELL) case RESTRICTED_REDIRECT: internal_error ("%s: restricted: cannot redirect output", filename); break; #endif /* RESTRICTED_SHELL */ case HEREDOC_REDIRECT: internal_error ("cannot create temp file for here document: %s", strerror (heredoc_errno)); break; default: internal_error ("%s: %s", filename, strerror (error)); break; } FREE (allocname); } /* Perform the redirections on LIST. If FOR_REAL, then actually make input and output file descriptors, otherwise just do whatever is neccessary for side effecting. INTERNAL says to remember how to undo the redirections later, if non-zero. If SET_CLEXEC is non-zero, file descriptors opened in do_redirection () have their close-on-exec flag set. */ int do_redirections (list, for_real, internal, set_clexec) REDIRECT *list; int for_real, internal, set_clexec; { int error; REDIRECT *temp; if (internal) { if (redirection_undo_list) { dispose_redirects (redirection_undo_list); redirection_undo_list = (REDIRECT *)NULL; } if (exec_redirection_undo_list) dispose_exec_redirects (); } for (temp = list; temp; temp = temp->next) { error = do_redirection_internal (temp, for_real, internal, set_clexec); if (error) { redirection_error (temp, error); return (error); } } return (0); } /* Return non-zero if the redirection pointed to by REDIRECT has a redirectee.filename that can be expanded. */ static int expandable_redirection_filename (redirect) REDIRECT *redirect; { switch (redirect->instruction) { case r_output_direction: case r_appending_to: case r_input_direction: case r_inputa_direction: case r_err_and_out: case r_input_output: case r_output_force: case r_duplicating_input_word: case r_duplicating_output_word: case r_move_input_word: case r_move_output_word: return 1; default: return 0; } } /* Expand the word in WORD returning a string. If WORD expands to multiple words (or no words), then return NULL. */ char * redirection_expand (word) WORD_DESC *word; { char *result; WORD_LIST *tlist1, *tlist2; WORD_DESC *w; w = copy_word (word); if (posixly_correct) w->flags |= W_NOSPLIT; tlist1 = make_word_list (w, (WORD_LIST *)NULL); tlist2 = expand_words_no_vars (tlist1); dispose_words (tlist1); if (!tlist2 || tlist2->next) { /* We expanded to no words, or to more than a single word. Dispose of the word list and return NULL. */ if (tlist2) dispose_words (tlist2); return ((char *)NULL); } result = string_list (tlist2); /* XXX savestring (tlist2->word->word)? */ dispose_words (tlist2); return (result); } static int write_here_string (fd, redirectee) int fd; WORD_DESC *redirectee; { char *herestr; int herelen, n, e; herestr = expand_string_to_string (redirectee->word, 0); herelen = strlen (herestr); n = write (fd, herestr, herelen); if (n == herelen) { n = write (fd, "\n", 1); herelen = 1; } e = errno; free (herestr); if (n != herelen) { if (e == 0) e = ENOSPC; return e; } return 0; } /* Write the text of the here document pointed to by REDIRECTEE to the file descriptor FD, which is already open to a temp file. Return 0 if the write is successful, otherwise return errno. */ static int write_here_document (fd, redirectee) int fd; WORD_DESC *redirectee; { char *document; int document_len, fd2; FILE *fp; register WORD_LIST *t, *tlist; /* Expand the text if the word that was specified had no quoting. The text that we expand is treated exactly as if it were surrounded by double quotes. */ if (redirectee->flags & W_QUOTED) { document = redirectee->word; document_len = strlen (document); /* Set errno to something reasonable if the write fails. */ if (write (fd, document, document_len) < document_len) { if (errno == 0) errno = ENOSPC; return (errno); } else return 0; } tlist = expand_string (redirectee->word, Q_HERE_DOCUMENT); if (tlist) { /* Try using buffered I/O (stdio) and writing a word at a time, letting stdio do the work of buffering for us rather than managing our own strings. Most stdios are not particularly fast, however -- this may need to be reconsidered later. */ if ((fd2 = dup (fd)) < 0 || (fp = fdopen (fd2, "w")) == NULL) { if (fd2 >= 0) close (fd2); return (errno); } errno = 0; for (t = tlist; t; t = t->next) { /* This is essentially the body of string_list_internal expanded inline. */ document = t->word->word; document_len = strlen (document); if (t != tlist) putc (' ', fp); /* separator */ fwrite (document, document_len, 1, fp); if (ferror (fp)) { if (errno == 0) errno = ENOSPC; fd2 = errno; fclose(fp); dispose_words (tlist); return (fd2); } } dispose_words (tlist); if (fclose (fp) != 0) { if (errno == 0) errno = ENOSPC; return (errno); } } return 0; } /* Create a temporary file holding the text of the here document pointed to by REDIRECTEE, and return a file descriptor open for reading to the temp file. Return -1 on any error, and make sure errno is set appropriately. */ static int here_document_to_fd (redirectee, ri) WORD_DESC *redirectee; enum r_instruction ri; { char *filename; int r, fd, fd2; fd = sh_mktmpfd ("sh-thd", MT_USERANDOM, &filename); /* If we failed for some reason other than the file existing, abort */ if (fd < 0) { FREE (filename); return (fd); } errno = r = 0; /* XXX */ /* write_here_document returns 0 on success, errno on failure. */ if (redirectee->word) r = (ri != r_reading_string) ? write_here_document (fd, redirectee) : write_here_string (fd, redirectee); if (r) { close (fd); unlink (filename); free (filename); errno = r; return (-1); } /* In an attempt to avoid races, we close the first fd only after opening the second. */ /* Make the document really temporary. Also make it the input. */ fd2 = open (filename, O_RDONLY, 0600); if (fd2 < 0) { r = errno; unlink (filename); free (filename); close (fd); errno = r; return -1; } close (fd); if (unlink (filename) < 0) { r = errno; #if defined (__CYGWIN__) /* Under CygWin 1.1.0, the unlink will fail if the file is open. This hack will allow the previous action of silently ignoring the error, but will still leave the file there. This needs some kind of magic. */ if (r == EACCES) return (fd2); #endif /* __CYGWIN__ */ close (fd2); free (filename); errno = r; return (-1); } free (filename); return (fd2); } #define RF_DEVFD 1 #define RF_DEVSTDERR 2 #define RF_DEVSTDIN 3 #define RF_DEVSTDOUT 4 #define RF_DEVTCP 5 #define RF_DEVUDP 6 /* A list of pattern/value pairs for filenames that the redirection code handles specially. */ static STRING_INT_ALIST _redir_special_filenames[] = { #if !defined (HAVE_DEV_FD) { "/dev/fd/[0-9]*", RF_DEVFD }, #endif #if !defined (HAVE_DEV_STDIN) { "/dev/stderr", RF_DEVSTDERR }, { "/dev/stdin", RF_DEVSTDIN }, { "/dev/stdout", RF_DEVSTDOUT }, #endif #if defined (NETWORK_REDIRECTIONS) { "/dev/tcp/*/*", RF_DEVTCP }, { "/dev/udp/*/*", RF_DEVUDP }, #endif { (char *)NULL, -1 } }; static int redir_special_open (spec, filename, flags, mode, ri) int spec; char *filename; int flags, mode; enum r_instruction ri; { int fd; #if !defined (HAVE_DEV_FD) intmax_t lfd; #endif fd = -1; switch (spec) { #if !defined (HAVE_DEV_FD) case RF_DEVFD: if (all_digits (filename+8) && legal_number (filename+8, &lfd) && lfd == (int)lfd) { fd = lfd; fd = fcntl (fd, F_DUPFD, 10); } else fd = AMBIGUOUS_REDIRECT; break; #endif #if !defined (HAVE_DEV_STDIN) case RF_DEVSTDIN: fd = fcntl (0, F_DUPFD, 10); break; case RF_DEVSTDOUT: fd = fcntl (1, F_DUPFD, 10); break; case RF_DEVSTDERR: fd = fcntl (2, F_DUPFD, 10); break; #endif #if defined (NETWORK_REDIRECTIONS) case RF_DEVTCP: case RF_DEVUDP: #if defined (HAVE_NETWORK) fd = netopen (filename); #else internal_warning ("/dev/(tcp|udp)/host/port not supported without networking"); fd = open (filename, flags, mode); #endif break; #endif /* NETWORK_REDIRECTIONS */ } return fd; } /* Open FILENAME with FLAGS in noclobber mode, hopefully avoiding most race conditions and avoiding the problem where the file is replaced between the stat(2) and open(2). */ static int noclobber_open (filename, flags, mode, ri) char *filename; int flags, mode; enum r_instruction ri; { int r, fd; struct stat finfo, finfo2; /* If the file exists and is a regular file, return an error immediately. */ r = stat (filename, &finfo); if (r == 0 && (S_ISREG (finfo.st_mode))) return (NOCLOBBER_REDIRECT); /* If the file was not present (r != 0), make sure we open it exclusively so that if it is created before we open it, our open will fail. Make sure that we do not truncate an existing file. Note that we don't turn on O_EXCL unless the stat failed -- if the file was not a regular file, we leave O_EXCL off. */ flags &= ~O_TRUNC; if (r != 0) { fd = open (filename, flags|O_EXCL, mode); return ((fd < 0 && errno == EEXIST) ? NOCLOBBER_REDIRECT : fd); } fd = open (filename, flags, mode); /* If the open failed, return the file descriptor right away. */ if (fd < 0) return (errno == EEXIST ? NOCLOBBER_REDIRECT : fd); /* OK, the open succeeded, but the file may have been changed from a non-regular file to a regular file between the stat and the open. We are assuming that the O_EXCL open handles the case where FILENAME did not exist and is symlinked to an existing file between the stat and open. */ /* If we can open it and fstat the file descriptor, and neither check revealed that it was a regular file, and the file has not been replaced, return the file descriptor. */ if ((fstat (fd, &finfo2) == 0) && (S_ISREG (finfo2.st_mode) == 0) && r == 0 && (S_ISREG (finfo.st_mode) == 0) && same_file (filename, filename, &finfo, &finfo2)) return fd; /* The file has been replaced. badness. */ close (fd); errno = EEXIST; return (NOCLOBBER_REDIRECT); } static int redir_open (filename, flags, mode, ri) char *filename; int flags, mode; enum r_instruction ri; { int fd, r; r = find_string_in_alist (filename, _redir_special_filenames, 1); if (r >= 0) return (redir_special_open (r, filename, flags, mode, ri)); /* If we are in noclobber mode, you are not allowed to overwrite existing files. Check before opening. */ if (noclobber && CLOBBERING_REDIRECT (ri)) { fd = noclobber_open (filename, flags, mode, ri); if (fd == NOCLOBBER_REDIRECT) return (NOCLOBBER_REDIRECT); } else { fd = open (filename, flags, mode); #if defined (AFS) if ((fd < 0) && (errno == EACCES)) fd = open (filename, flags & ~O_CREAT, mode); #endif /* AFS */ } return fd; } /* Do the specific redirection requested. Returns errno or one of the special redirection errors (*_REDIRECT) in case of error, 0 on success. If FOR_REAL is zero, then just do whatever is neccessary to produce the appropriate side effects. REMEMBERING, if non-zero, says to remember how to undo each redirection. If SET_CLEXEC is non-zero, then we set all file descriptors > 2 that we open to be close-on-exec. */ static int do_redirection_internal (redirect, for_real, remembering, set_clexec) REDIRECT *redirect; int for_real, remembering, set_clexec; { WORD_DESC *redirectee; int redir_fd, fd, redirector, r, oflags; intmax_t lfd; char *redirectee_word; enum r_instruction ri; REDIRECT *new_redirect; redirectee = redirect->redirectee.filename; redir_fd = redirect->redirectee.dest; redirector = redirect->redirector; ri = redirect->instruction; if (TRANSLATE_REDIRECT (ri)) { /* We have [N]>&WORD[-] or [N]<&WORD[-]. Expand WORD, then translate the redirection into a new one and continue. */ redirectee_word = redirection_expand (redirectee); /* XXX - what to do with [N]<&$w- where w is unset or null? ksh93 closes N. */ if (redirectee_word == 0) return (AMBIGUOUS_REDIRECT); else if (redirectee_word[0] == '-' && redirectee_word[1] == '\0') { rd.dest = 0; new_redirect = make_redirection (redirector, r_close_this, rd); } else if (all_digits (redirectee_word)) { if (legal_number (redirectee_word, &lfd) && (int)lfd == lfd) rd.dest = lfd; else rd.dest = -1; /* XXX */ switch (ri) { case r_duplicating_input_word: new_redirect = make_redirection (redirector, r_duplicating_input, rd); break; case r_duplicating_output_word: new_redirect = make_redirection (redirector, r_duplicating_output, rd); break; case r_move_input_word: new_redirect = make_redirection (redirector, r_move_input, rd); break; case r_move_output_word: new_redirect = make_redirection (redirector, r_move_output, rd); break; } } else if (ri == r_duplicating_output_word && redirector == 1) { rd.filename = make_bare_word (redirectee_word); new_redirect = make_redirection (1, r_err_and_out, rd); } else { free (redirectee_word); return (AMBIGUOUS_REDIRECT); } free (redirectee_word); /* Set up the variables needed by the rest of the function from the new redirection. */ if (new_redirect->instruction == r_err_and_out) { char *alloca_hack; /* Copy the word without allocating any memory that must be explicitly freed. */ redirectee = (WORD_DESC *)alloca (sizeof (WORD_DESC)); xbcopy ((char *)new_redirect->redirectee.filename, (char *)redirectee, sizeof (WORD_DESC)); alloca_hack = (char *) alloca (1 + strlen (new_redirect->redirectee.filename->word)); redirectee->word = alloca_hack; strcpy (redirectee->word, new_redirect->redirectee.filename->word); } else /* It's guaranteed to be an integer, and shouldn't be freed. */ redirectee = new_redirect->redirectee.filename; redir_fd = new_redirect->redirectee.dest; redirector = new_redirect->redirector; ri = new_redirect->instruction; /* Overwrite the flags element of the old redirect with the new value. */ redirect->flags = new_redirect->flags; dispose_redirects (new_redirect); } switch (ri) { case r_output_direction: case r_appending_to: case r_input_direction: case r_inputa_direction: case r_err_and_out: /* command &>filename */ case r_input_output: case r_output_force: if (posixly_correct && interactive_shell == 0) { oflags = redirectee->flags; redirectee->flags |= W_NOGLOB; } redirectee_word = redirection_expand (redirectee); if (posixly_correct && interactive_shell == 0) redirectee->flags = oflags; if (redirectee_word == 0) return (AMBIGUOUS_REDIRECT); #if defined (RESTRICTED_SHELL) if (restricted && (WRITE_REDIRECT (ri))) { free (redirectee_word); return (RESTRICTED_REDIRECT); } #endif /* RESTRICTED_SHELL */ fd = redir_open (redirectee_word, redirect->flags, 0666, ri); free (redirectee_word); if (fd == NOCLOBBER_REDIRECT) return (fd); if (fd < 0) return (errno); if (for_real) { if (remembering) { /* Only setup to undo it if the thing to undo is active. */ if ((fd != redirector) && (fcntl (redirector, F_GETFD, 0) != -1)) add_undo_redirect (redirector); else add_undo_close_redirect (redirector); } #if defined (BUFFERED_INPUT) check_bash_input (redirector); #endif if ((fd != redirector) && (dup2 (fd, redirector) < 0)) return (errno); #if defined (BUFFERED_INPUT) /* Do not change the buffered stream for an implicit redirection of /dev/null to fd 0 for asynchronous commands without job control (r_inputa_direction). */ if (ri == r_input_direction || ri == r_input_output) duplicate_buffered_stream (fd, redirector); #endif /* BUFFERED_INPUT */ /* * If we're remembering, then this is the result of a while, for * or until loop with a loop redirection, or a function/builtin * executing in the parent shell with a redirection. In the * function/builtin case, we want to set all file descriptors > 2 * to be close-on-exec to duplicate the effect of the old * for i = 3 to NOFILE close(i) loop. In the case of the loops, * both sh and ksh leave the file descriptors open across execs. * The Posix standard mentions only the exec builtin. */ if (set_clexec && (redirector > 2)) SET_CLOSE_ON_EXEC (redirector); } if (fd != redirector) { #if defined (BUFFERED_INPUT) if (INPUT_REDIRECT (ri)) close_buffered_fd (fd); else #endif /* !BUFFERED_INPUT */ close (fd); /* Don't close what we just opened! */ } /* If we are hacking both stdout and stderr, do the stderr redirection here. */ if (ri == r_err_and_out) { if (for_real) { if (remembering) add_undo_redirect (2); if (dup2 (1, 2) < 0) return (errno); } } break; case r_reading_until: case r_deblank_reading_until: case r_reading_string: /* REDIRECTEE is a pointer to a WORD_DESC containing the text of the new input. Place it in a temporary file. */ if (redirectee) { fd = here_document_to_fd (redirectee, ri); if (fd < 0) { heredoc_errno = errno; return (HEREDOC_REDIRECT); } if (for_real) { if (remembering) { /* Only setup to undo it if the thing to undo is active. */ if ((fd != redirector) && (fcntl (redirector, F_GETFD, 0) != -1)) add_undo_redirect (redirector); else add_undo_close_redirect (redirector); } #if defined (BUFFERED_INPUT) check_bash_input (redirector); #endif if (fd != redirector && dup2 (fd, redirector) < 0) { r = errno; close (fd); return (r); } #if defined (BUFFERED_INPUT) duplicate_buffered_stream (fd, redirector); #endif if (set_clexec && (redirector > 2)) SET_CLOSE_ON_EXEC (redirector); } if (fd != redirector) #if defined (BUFFERED_INPUT) close_buffered_fd (fd); #else close (fd); #endif } break; case r_duplicating_input: case r_duplicating_output: case r_move_input: case r_move_output: if (for_real && (redir_fd != redirector)) { if (remembering) { /* Only setup to undo it if the thing to undo is active. */ if (fcntl (redirector, F_GETFD, 0) != -1) add_undo_redirect (redirector); else add_undo_close_redirect (redirector); } #if defined (BUFFERED_INPUT) check_bash_input (redirector); #endif /* This is correct. 2>&1 means dup2 (1, 2); */ if (dup2 (redir_fd, redirector) < 0) return (errno); #if defined (BUFFERED_INPUT) if (ri == r_duplicating_input || ri == r_move_input) duplicate_buffered_stream (redir_fd, redirector); #endif /* BUFFERED_INPUT */ /* First duplicate the close-on-exec state of redirectee. dup2 leaves the flag unset on the new descriptor, which means it stays open. Only set the close-on-exec bit for file descriptors greater than 2 in any case, since 0-2 should always be open unless closed by something like `exec 2<&-'. */ /* if ((already_set || set_unconditionally) && (ok_to_set)) set_it () */ if (((fcntl (redir_fd, F_GETFD, 0) == 1) || set_clexec) && (redirector > 2)) SET_CLOSE_ON_EXEC (redirector); /* dup-and-close redirection */ if (ri == r_move_input || ri == r_move_output) close (redir_fd); } break; case r_close_this: if (for_real) { if (remembering && (fcntl (redirector, F_GETFD, 0) != -1)) add_undo_redirect (redirector); #if defined (BUFFERED_INPUT) check_bash_input (redirector); close_buffered_fd (redirector); #else /* !BUFFERED_INPUT */ close (redirector); #endif /* !BUFFERED_INPUT */ } break; case r_duplicating_input_word: case r_duplicating_output_word: break; } return (0); } #define SHELL_FD_BASE 10 /* Remember the file descriptor associated with the slot FD, on REDIRECTION_UNDO_LIST. Note that the list will be reversed before it is executed. Any redirections that need to be undone even if REDIRECTION_UNDO_LIST is discarded by the exec builtin are also saved on EXEC_REDIRECTION_UNDO_LIST. */ static int add_undo_redirect (fd) int fd; { int new_fd, clexec_flag; REDIRECT *new_redirect, *closer, *dummy_redirect; new_fd = fcntl (fd, F_DUPFD, SHELL_FD_BASE); if (new_fd < 0) { sys_error ("redirection error: cannot duplicate fd"); return (-1); } clexec_flag = fcntl (fd, F_GETFD, 0); rd.dest = 0; closer = make_redirection (new_fd, r_close_this, rd); dummy_redirect = copy_redirects (closer); rd.dest = new_fd; if (fd == 0) new_redirect = make_redirection (fd, r_duplicating_input, rd); else new_redirect = make_redirection (fd, r_duplicating_output, rd); new_redirect->next = closer; closer->next = redirection_undo_list; redirection_undo_list = new_redirect; /* Save redirections that need to be undone even if the undo list is thrown away by the `exec' builtin. */ add_exec_redirect (dummy_redirect); /* File descriptors used only for saving others should always be marked close-on-exec. Unfortunately, we have to preserve the close-on-exec state of the file descriptor we are saving, since fcntl (F_DUPFD) sets the new file descriptor to remain open across execs. If, however, the file descriptor whose state we are saving is <= 2, we can just set the close-on-exec flag, because file descriptors 0-2 should always be open-on-exec, and the restore above in do_redirection() will take care of it. */ if (clexec_flag || fd < 3) SET_CLOSE_ON_EXEC (new_fd); return (0); } /* Set up to close FD when we are finished with the current command and its redirections. */ static void add_undo_close_redirect (fd) int fd; { REDIRECT *closer; rd.dest = 0; closer = make_redirection (fd, r_close_this, rd); closer->next = redirection_undo_list; redirection_undo_list = closer; } static void add_exec_redirect (dummy_redirect) REDIRECT *dummy_redirect; { dummy_redirect->next = exec_redirection_undo_list; exec_redirection_undo_list = dummy_redirect; } /* Return 1 if the redirection specified by RI and REDIRECTOR alters the standard input. */ static int stdin_redirection (ri, redirector) enum r_instruction ri; int redirector; { switch (ri) { case r_input_direction: case r_inputa_direction: case r_input_output: case r_reading_until: case r_deblank_reading_until: case r_reading_string: return (1); case r_duplicating_input: case r_duplicating_input_word: case r_close_this: return (redirector == 0); case r_output_direction: case r_appending_to: case r_duplicating_output: case r_err_and_out: case r_output_force: case r_duplicating_output_word: return (0); } return (0); } /* Return non-zero if any of the redirections in REDIRS alter the standard input. */ int stdin_redirects (redirs) REDIRECT *redirs; { REDIRECT *rp; int n; for (n = 0, rp = redirs; rp; rp = rp->next) n += stdin_redirection (rp->instruction, rp->redirector); return n; }
/* pcomplete.c - functions to generate lists of matches for programmable completion. */ /* Copyright (C) 1999-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #if defined (PROGRAMMABLE_COMPLETION) #include "bashtypes.h" #include "posixstat.h" #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #include <signal.h> #if defined (PREFER_STDARG) # include <stdarg.h> #else # include <varargs.h> #endif #include <stdio.h> #include "bashansi.h" #include "shell.h" #include "pcomplete.h" #include "alias.h" #include "bashline.h" #include "execute_cmd.h" #include "pathexp.h" #if defined (JOB_CONTROL) # include "jobs.h" #endif #if !defined (NSIG) # include "trap.h" #endif #include "builtins.h" #include "builtins/common.h" #include <glob/glob.h> #include <glob/strmatch.h> #include <readline/rlconf.h> #include <readline/readline.h> #include <readline/history.h> #ifdef STRDUP # undef STRDUP #endif #define STRDUP(x) ((x) ? savestring (x) : (char *)NULL) typedef SHELL_VAR **SVFUNC (); #ifndef HAVE_STRPBRK extern char *strpbrk __P((char *, char *)); #endif extern int array_needs_making; extern STRING_INT_ALIST word_token_alist[]; extern char *signal_names[]; #if defined (DEBUG) #if defined (PREFER_STDARG) static void debug_printf (const char *, ...) __attribute__((__format__ (printf, 1, 2))); #endif #endif /* DEBUG */ static int it_init_joblist __P((ITEMLIST *, int)); static int it_init_aliases __P((ITEMLIST *)); static int it_init_arrayvars __P((ITEMLIST *)); static int it_init_bindings __P((ITEMLIST *)); static int it_init_builtins __P((ITEMLIST *)); static int it_init_disabled __P((ITEMLIST *)); static int it_init_enabled __P((ITEMLIST *)); static int it_init_exported __P((ITEMLIST *)); static int it_init_functions __P((ITEMLIST *)); static int it_init_hostnames __P((ITEMLIST *)); static int it_init_jobs __P((ITEMLIST *)); static int it_init_running __P((ITEMLIST *)); static int it_init_stopped __P((ITEMLIST *)); static int it_init_keywords __P((ITEMLIST *)); static int it_init_signals __P((ITEMLIST *)); static int it_init_variables __P((ITEMLIST *)); static int it_init_setopts __P((ITEMLIST *)); static int it_init_shopts __P((ITEMLIST *)); static int shouldexp_filterpat __P((char *)); static char *preproc_filterpat __P((char *, char *)); static void init_itemlist_from_varlist __P((ITEMLIST *, SVFUNC *)); static STRINGLIST *gen_matches_from_itemlist __P((ITEMLIST *, const char *)); static STRINGLIST *gen_action_completions __P((COMPSPEC *, const char *)); static STRINGLIST *gen_globpat_matches __P((COMPSPEC *, const char *)); static STRINGLIST *gen_wordlist_matches __P((COMPSPEC *, const char *)); static STRINGLIST *gen_shell_function_matches __P((COMPSPEC *, const char *, char *, int, WORD_LIST *, int, int)); static STRINGLIST *gen_command_matches __P((COMPSPEC *, const char *, char *, int, WORD_LIST *, int, int)); static char *pcomp_filename_completion_function __P((const char *, int)); #if defined (ARRAY_VARS) static SHELL_VAR *bind_comp_words __P((WORD_LIST *)); #endif static void bind_compfunc_variables __P((char *, int, WORD_LIST *, int, int)); static void unbind_compfunc_variables __P((int)); static WORD_LIST *build_arg_list __P((char *, const char *, WORD_LIST *, int)); static WORD_LIST *command_line_to_word_list __P((char *, int, int, int *, int *)); #ifdef DEBUG static int progcomp_debug = 0; #endif int prog_completion_enabled = 1; /* These are used to manage the arrays of strings for possible completions. */ ITEMLIST it_aliases = { 0, it_init_aliases, (STRINGLIST *)0 }; ITEMLIST it_arrayvars = { LIST_DYNAMIC, it_init_arrayvars, (STRINGLIST *)0 }; ITEMLIST it_bindings = { 0, it_init_bindings, (STRINGLIST *)0 }; ITEMLIST it_builtins = { 0, it_init_builtins, (STRINGLIST *)0 }; ITEMLIST it_commands = { LIST_DYNAMIC }; /* unused */ ITEMLIST it_directories = { LIST_DYNAMIC }; /* unused */ ITEMLIST it_disabled = { 0, it_init_disabled, (STRINGLIST *)0 }; ITEMLIST it_enabled = { 0, it_init_enabled, (STRINGLIST *)0 }; ITEMLIST it_exports = { LIST_DYNAMIC, it_init_exported, (STRINGLIST *)0 }; ITEMLIST it_files = { LIST_DYNAMIC }; /* unused */ ITEMLIST it_functions = { 0, it_init_functions, (STRINGLIST *)0 }; ITEMLIST it_hostnames = { LIST_DYNAMIC, it_init_hostnames, (STRINGLIST *)0 }; ITEMLIST it_groups = { LIST_DYNAMIC }; /* unused */ ITEMLIST it_jobs = { LIST_DYNAMIC, it_init_jobs, (STRINGLIST *)0 }; ITEMLIST it_keywords = { 0, it_init_keywords, (STRINGLIST *)0 }; ITEMLIST it_running = { LIST_DYNAMIC, it_init_running, (STRINGLIST *)0 }; ITEMLIST it_services = { LIST_DYNAMIC }; /* unused */ ITEMLIST it_setopts = { 0, it_init_setopts, (STRINGLIST *)0 }; ITEMLIST it_shopts = { 0, it_init_shopts, (STRINGLIST *)0 }; ITEMLIST it_signals = { 0, it_init_signals, (STRINGLIST *)0 }; ITEMLIST it_stopped = { LIST_DYNAMIC, it_init_stopped, (STRINGLIST *)0 }; ITEMLIST it_users = { LIST_DYNAMIC }; /* unused */ ITEMLIST it_variables = { LIST_DYNAMIC, it_init_variables, (STRINGLIST *)0 }; #ifdef DEBUG /* Debugging code */ static void #if defined (PREFER_STDARG) debug_printf (const char *format, ...) #else debug_printf (format, va_alist) const char *format; va_dcl #endif { va_list args; if (progcomp_debug == 0) return; SH_VA_START (args, format); fprintf (stdout, "DEBUG: "); vfprintf (stdout, format, args); fprintf (stdout, "\n"); rl_on_new_line (); va_end (args); } #endif /* Functions to manage the item lists */ void set_itemlist_dirty (it) ITEMLIST *it; { it->flags |= LIST_DIRTY; } void initialize_itemlist (itp) ITEMLIST *itp; { (*itp->list_getter) (itp); itp->flags |= LIST_INITIALIZED; itp->flags &= ~LIST_DIRTY; } void clean_itemlist (itp) ITEMLIST *itp; { STRINGLIST *sl; sl = itp->slist; if (sl) { if ((itp->flags & (LIST_DONTFREEMEMBERS|LIST_DONTFREE)) == 0) strvec_flush (sl->list); if ((itp->flags & LIST_DONTFREE) == 0) free (sl->list); free (sl); } itp->slist = (STRINGLIST *)NULL; itp->flags &= ~(LIST_DONTFREE|LIST_DONTFREEMEMBERS|LIST_INITIALIZED|LIST_DIRTY); } static int shouldexp_filterpat (s) char *s; { register char *p; for (p = s; p && *p; p++) { if (*p == '\\') p++; else if (*p == '&') return 1; } return 0; } /* Replace any instance of `&' in PAT with TEXT. Backslash may be used to quote a `&' and inhibit substitution. Returns a new string. This just calls stringlib.c:strcreplace(). */ static char * preproc_filterpat (pat, text) char *pat; char *text; { char *ret; ret = strcreplace (pat, '&', text, 1); return ret; } /* Remove any match of FILTERPAT from SL. A `&' in FILTERPAT is replaced by TEXT. A leading `!' in FILTERPAT negates the pattern; in this case any member of SL->list that does *not* match will be removed. This returns a new STRINGLIST with the matching members of SL *copied*. Any non-matching members of SL->list are *freed*. */ STRINGLIST * filter_stringlist (sl, filterpat, text) STRINGLIST *sl; char *filterpat, *text; { int i, m, not; STRINGLIST *ret; char *npat, *t; if (sl == 0 || sl->list == 0 || sl->list_len == 0) return sl; npat = shouldexp_filterpat (filterpat) ? preproc_filterpat (filterpat, text) : filterpat; not = (npat[0] == '!'); t = not ? npat + 1 : npat; ret = strlist_create (sl->list_size); for (i = 0; i < sl->list_len; i++) { m = strmatch (t, sl->list[i], FNMATCH_EXTFLAG); if ((not && m == FNM_NOMATCH) || (not == 0 && m != FNM_NOMATCH)) free (sl->list[i]); else ret->list[ret->list_len++] = sl->list[i]; } ret->list[ret->list_len] = (char *)NULL; if (npat != filterpat) free (npat); return ret; } /* Turn an array of strings returned by rl_completion_matches into a STRINGLIST. This understands how rl_completion_matches sets matches[0] (the lcd of the strings in the list, unless it's the only match). */ STRINGLIST * completions_to_stringlist (matches) char **matches; { STRINGLIST *sl; int mlen, i, n; mlen = (matches == 0) ? 0 : strvec_len (matches); sl = strlist_create (mlen + 1); if (matches == 0 || matches[0] == 0) return sl; if (matches[1] == 0) { sl->list[0] = STRDUP (matches[0]); sl->list[sl->list_len = 1] = (char *)NULL; return sl; } for (i = 1, n = 0; i < mlen; i++, n++) sl->list[n] = STRDUP (matches[i]); sl->list_len = n; sl->list[n] = (char *)NULL; return sl; } /* Functions to manage the various ITEMLISTs that we populate internally. The caller is responsible for setting ITP->flags correctly. */ static int it_init_aliases (itp) ITEMLIST *itp; { #ifdef ALIAS alias_t **alias_list; register int i, n; STRINGLIST *sl; alias_list = all_aliases (); if (alias_list == 0) { itp->slist = (STRINGLIST *)NULL; return 0; } for (n = 0; alias_list[n]; n++) ; sl = strlist_create (n+1); for (i = 0; i < n; i++) sl->list[i] = STRDUP (alias_list[i]->name); sl->list[n] = (char *)NULL; sl->list_size = sl->list_len = n; itp->slist = sl; #else itp->slist = (STRINGLIST *)NULL; #endif return 1; } static void init_itemlist_from_varlist (itp, svfunc) ITEMLIST *itp; SVFUNC *svfunc; { SHELL_VAR **vlist; STRINGLIST *sl; register int i, n; vlist = (*svfunc) (); for (n = 0; vlist[n]; n++) ; sl = strlist_create (n+1); for (i = 0; i < n; i++) sl->list[i] = savestring (vlist[i]->name); sl->list[sl->list_len = n] = (char *)NULL; itp->slist = sl; } static int it_init_arrayvars (itp) ITEMLIST *itp; { #if defined (ARRAY_VARS) init_itemlist_from_varlist (itp, all_array_variables); return 1; #else return 0; #endif } static int it_init_bindings (itp) ITEMLIST *itp; { char **blist; STRINGLIST *sl; /* rl_funmap_names allocates blist, but not its members */ blist = (char **)rl_funmap_names (); /* XXX fix const later */ sl = strlist_create (0); sl->list = blist; sl->list_size = 0; sl->list_len = strvec_len (sl->list); itp->flags |= LIST_DONTFREEMEMBERS; itp->slist = sl; return 0; } static int it_init_builtins (itp) ITEMLIST *itp; { STRINGLIST *sl; register int i, n; sl = strlist_create (num_shell_builtins); for (i = n = 0; i < num_shell_builtins; i++) if (shell_builtins[i].function) sl->list[n++] = shell_builtins[i].name; sl->list[sl->list_len = n] = (char *)NULL; itp->flags |= LIST_DONTFREEMEMBERS; itp->slist = sl; return 0; } static int it_init_enabled (itp) ITEMLIST *itp; { STRINGLIST *sl; register int i, n; sl = strlist_create (num_shell_builtins); for (i = n = 0; i < num_shell_builtins; i++) { if (shell_builtins[i].function && (shell_builtins[i].flags & BUILTIN_ENABLED)) sl->list[n++] = shell_builtins[i].name; } sl->list[sl->list_len = n] = (char *)NULL; itp->flags |= LIST_DONTFREEMEMBERS; itp->slist = sl; return 0; } static int it_init_disabled (itp) ITEMLIST *itp; { STRINGLIST *sl; register int i, n; sl = strlist_create (num_shell_builtins); for (i = n = 0; i < num_shell_builtins; i++) { if (shell_builtins[i].function && ((shell_builtins[i].flags & BUILTIN_ENABLED) == 0)) sl->list[n++] = shell_builtins[i].name; } sl->list[sl->list_len = n] = (char *)NULL; itp->flags |= LIST_DONTFREEMEMBERS; itp->slist = sl; return 0; } static int it_init_exported (itp) ITEMLIST *itp; { init_itemlist_from_varlist (itp, all_exported_variables); return 0; } static int it_init_functions (itp) ITEMLIST *itp; { init_itemlist_from_varlist (itp, all_visible_functions); return 0; } static int it_init_hostnames (itp) ITEMLIST *itp; { STRINGLIST *sl; sl = strlist_create (0); sl->list = get_hostname_list (); sl->list_len = sl->list ? strvec_len (sl->list) : 0; sl->list_size = sl->list_len; itp->slist = sl; itp->flags |= LIST_DONTFREEMEMBERS|LIST_DONTFREE; return 0; } static int it_init_joblist (itp, jstate) ITEMLIST *itp; int jstate; { #if defined (JOB_CONTROL) STRINGLIST *sl; register int i; register PROCESS *p; char *s, *t; JOB_STATE js; if (jstate == 0) js = JRUNNING; else if (jstate == 1) js = JSTOPPED; sl = strlist_create (job_slots); for (i = job_slots - 1; i >= 0; i--) { if (jobs[i] == 0) continue; p = jobs[i]->pipe; if (jstate == -1 || JOBSTATE(i) == js) { s = savestring (p->command); t = strpbrk (s, " \t\n"); if (t) *t = '\0'; sl->list[sl->list_len++] = s; } } itp->slist = sl; #else itp->slist = (STRINGLIST *)NULL; #endif return 0; } static int it_init_jobs (itp) ITEMLIST *itp; { return (it_init_joblist (itp, -1)); } static int it_init_running (itp) ITEMLIST *itp; { return (it_init_joblist (itp, 0)); } static int it_init_stopped (itp) ITEMLIST *itp; { return (it_init_joblist (itp, 1)); } static int it_init_keywords (itp) ITEMLIST *itp; { STRINGLIST *sl; register int i, n; for (n = 0; word_token_alist[n].word; n++) ; sl = strlist_create (n); for (i = 0; i < n; i++) sl->list[i] = word_token_alist[i].word; sl->list[sl->list_len = i] = (char *)NULL; itp->flags |= LIST_DONTFREEMEMBERS; itp->slist = sl; return 0; } static int it_init_signals (itp) ITEMLIST *itp; { STRINGLIST *sl; sl = strlist_create (0); sl->list = signal_names; sl->list_len = strvec_len (sl->list); itp->flags |= LIST_DONTFREE; itp->slist = sl; return 0; } static int it_init_variables (itp) ITEMLIST *itp; { init_itemlist_from_varlist (itp, all_visible_variables); return 0; } static int it_init_setopts (itp) ITEMLIST *itp; { STRINGLIST *sl; sl = strlist_create (0); sl->list = get_minus_o_opts (); sl->list_len = strvec_len (sl->list); itp->slist = sl; itp->flags |= LIST_DONTFREEMEMBERS; return 0; } static int it_init_shopts (itp) ITEMLIST *itp; { STRINGLIST *sl; sl = strlist_create (0); sl->list = get_shopt_options (); sl->list_len = strvec_len (sl->list); itp->slist = sl; itp->flags |= LIST_DONTFREEMEMBERS; return 0; } /* Generate a list of all matches for TEXT using the STRINGLIST in itp->slist as the list of possibilities. If the itemlist has been marked dirty or it should be regenerated every time, destroy the old STRINGLIST and make a new one before trying the match. */ static STRINGLIST * gen_matches_from_itemlist (itp, text) ITEMLIST *itp; const char *text; { STRINGLIST *ret, *sl; int tlen, i, n; if ((itp->flags & (LIST_DIRTY|LIST_DYNAMIC)) || (itp->flags & LIST_INITIALIZED) == 0) { if (itp->flags & (LIST_DIRTY | LIST_DYNAMIC)) clean_itemlist (itp); if ((itp->flags & LIST_INITIALIZED) == 0) initialize_itemlist (itp); } if (itp->slist == 0) return ((STRINGLIST *)NULL); ret = strlist_create (itp->slist->list_len+1); sl = itp->slist; tlen = STRLEN (text); for (i = n = 0; i < sl->list_len; i++) { if (tlen == 0 || STREQN (sl->list[i], text, tlen)) ret->list[n++] = STRDUP (sl->list[i]); } ret->list[ret->list_len = n] = (char *)NULL; return ret; } /* A wrapper for rl_filename_completion_function that dequotes the filename before attempting completions. */ static char * pcomp_filename_completion_function (text, state) const char *text; int state; { static char *dfn; /* dequoted filename */ int qc; if (state == 0) { FREE (dfn); /* remove backslashes quoting special characters in filenames. */ if (rl_filename_dequoting_function) { qc = (text[0] == '"' || text[0] == '\'') ? text[0] : 0; dfn = (*rl_filename_dequoting_function) ((char *)text, qc); } else dfn = savestring (text); } return (rl_filename_completion_function (dfn, state)); } #define GEN_COMPS(bmap, flag, it, text, glist, tlist) \ do { \ if (bmap & flag) \ { \ tlist = gen_matches_from_itemlist (it, text); \ if (tlist) \ { \ glist = strlist_append (glist, tlist); \ strlist_dispose (tlist); \ } \ } \ } while (0) #define GEN_XCOMPS(bmap, flag, text, func, cmatches, glist, tlist) \ do { \ if (bmap & flag) \ { \ cmatches = rl_completion_matches (text, func); \ tlist = completions_to_stringlist (cmatches); \ glist = strlist_append (glist, tlist); \ strvec_dispose (cmatches); \ strlist_dispose (tlist); \ } \ } while (0) /* Functions to generate lists of matches from the actions member of CS. */ static STRINGLIST * gen_action_completions (cs, text) COMPSPEC *cs; const char *text; { STRINGLIST *ret, *tmatches; char **cmatches; /* from rl_completion_matches ... */ unsigned long flags; ret = tmatches = (STRINGLIST *)NULL; flags = cs->actions; GEN_COMPS (flags, CA_ALIAS, &it_aliases, text, ret, tmatches); GEN_COMPS (flags, CA_ARRAYVAR, &it_arrayvars, text, ret, tmatches); GEN_COMPS (flags, CA_BINDING, &it_bindings, text, ret, tmatches); GEN_COMPS (flags, CA_BUILTIN, &it_builtins, text, ret, tmatches); GEN_COMPS (flags, CA_DISABLED, &it_disabled, text, ret, tmatches); GEN_COMPS (flags, CA_ENABLED, &it_enabled, text, ret, tmatches); GEN_COMPS (flags, CA_EXPORT, &it_exports, text, ret, tmatches); GEN_COMPS (flags, CA_FUNCTION, &it_functions, text, ret, tmatches); GEN_COMPS (flags, CA_HOSTNAME, &it_hostnames, text, ret, tmatches); GEN_COMPS (flags, CA_JOB, &it_jobs, text, ret, tmatches); GEN_COMPS (flags, CA_KEYWORD, &it_keywords, text, ret, tmatches); GEN_COMPS (flags, CA_RUNNING, &it_running, text, ret, tmatches); GEN_COMPS (flags, CA_SETOPT, &it_setopts, text, ret, tmatches); GEN_COMPS (flags, CA_SHOPT, &it_shopts, text, ret, tmatches); GEN_COMPS (flags, CA_SIGNAL, &it_signals, text, ret, tmatches); GEN_COMPS (flags, CA_STOPPED, &it_stopped, text, ret, tmatches); GEN_COMPS (flags, CA_VARIABLE, &it_variables, text, ret, tmatches); GEN_XCOMPS(flags, CA_COMMAND, text, command_word_completion_function, cmatches, ret, tmatches); GEN_XCOMPS(flags, CA_FILE, text, pcomp_filename_completion_function, cmatches, ret, tmatches); GEN_XCOMPS(flags, CA_USER, text, rl_username_completion_function, cmatches, ret, tmatches); GEN_XCOMPS(flags, CA_GROUP, text, bash_groupname_completion_function, cmatches, ret, tmatches); GEN_XCOMPS(flags, CA_SERVICE, text, bash_servicename_completion_function, cmatches, ret, tmatches); /* And lastly, the special case for directories */ if (flags & CA_DIRECTORY) { rl_completion_mark_symlink_dirs = 1; /* override user preference */ cmatches = bash_directory_completion_matches (text); tmatches = completions_to_stringlist (cmatches); ret = strlist_append (ret, tmatches); strvec_dispose (cmatches); strlist_dispose (tmatches); } return ret; } /* Generate a list of matches for CS->globpat. Unresolved: should this use TEXT as a match prefix, or just go without? Currently, the code does not use TEXT, just globs CS->globpat and returns the results. If we do decide to use TEXT, we should call quote_string_for_globbing before the call to glob_filename. */ static STRINGLIST * gen_globpat_matches (cs, text) COMPSPEC *cs; const char *text; { STRINGLIST *sl; sl = strlist_create (0); sl->list = glob_filename (cs->globpat, 0); if (GLOB_FAILED (sl->list)) sl->list = (char **)NULL; if (sl->list) sl->list_len = sl->list_size = strvec_len (sl->list); return sl; } /* Perform the shell word expansions on CS->words and return the results. Again, this ignores TEXT. */ static STRINGLIST * gen_wordlist_matches (cs, text) COMPSPEC *cs; const char *text; { WORD_LIST *l, *l2; STRINGLIST *sl; int nw, tlen; if (cs->words == 0 || cs->words[0] == '\0') return ((STRINGLIST *)NULL); /* This used to be a simple expand_string(cs->words, 0), but that won't do -- there's no way to split a simple list into individual words that way, since the shell semantics say that word splitting is done only on the results of expansion. */ l = split_at_delims (cs->words, strlen (cs->words), (char *)NULL, -1, (int *)NULL, (int *)NULL); if (l == 0) return ((STRINGLIST *)NULL); /* This will jump back to the top level if the expansion fails... */ l2 = expand_words_shellexp (l); dispose_words (l); nw = list_length (l2); sl = strlist_create (nw + 1); tlen = STRLEN (text); for (nw = 0, l = l2; l; l = l->next) { if (tlen == 0 || STREQN (l->word->word, text, tlen)) sl->list[nw++] = STRDUP (l->word->word); } sl->list[sl->list_len = nw] = (char *)NULL; return sl; } #ifdef ARRAY_VARS static SHELL_VAR * bind_comp_words (lwords) WORD_LIST *lwords; { SHELL_VAR *v; v = find_variable ("COMP_WORDS"); if (v == 0) v = make_new_array_variable ("COMP_WORDS"); if (readonly_p (v)) VUNSETATTR (v, att_readonly); if (array_p (v) == 0) v = convert_var_to_array (v); v = assign_array_var_from_word_list (v, lwords); return v; } #endif /* ARRAY_VARS */ static void bind_compfunc_variables (line, ind, lwords, cw, exported) char *line; int ind; WORD_LIST *lwords; int cw, exported; { char ibuf[INT_STRLEN_BOUND(int) + 1]; char *value; SHELL_VAR *v; /* Set the variables that the function expects while it executes. Maybe these should be in the function environment (temporary_env). */ v = bind_variable ("COMP_LINE", line); if (v && exported) VSETATTR(v, att_exported); value = inttostr (ind, ibuf, sizeof(ibuf)); v = bind_int_variable ("COMP_POINT", value); if (v && exported) VSETATTR(v, att_exported); /* Since array variables can't be exported, we don't bother making the array of words. */ if (exported == 0) { #ifdef ARRAY_VARS v = bind_comp_words (lwords); value = inttostr (cw, ibuf, sizeof(ibuf)); bind_int_variable ("COMP_CWORD", value); #endif } else array_needs_making = 1; } static void unbind_compfunc_variables (exported) int exported; { unbind_variable ("COMP_LINE"); unbind_variable ("COMP_POINT"); #ifdef ARRAY_VARS unbind_variable ("COMP_WORDS"); unbind_variable ("COMP_CWORD"); #endif if (exported) array_needs_making = 1; } /* Build the list of words to pass to a function or external command as arguments. When the function or command is invoked, $0 == function or command being invoked $1 == command name $2 = word to be completed (possibly null) $3 = previous word Functions can access all of the words in the current command line with the COMP_WORDS array. External commands cannot. */ static WORD_LIST * build_arg_list (cmd, text, lwords, ind) char *cmd; const char *text; WORD_LIST *lwords; int ind; { WORD_LIST *ret, *cl, *l; WORD_DESC *w; int i; ret = (WORD_LIST *)NULL; w = make_word (cmd); ret = make_word_list (w, (WORD_LIST *)NULL); w = (lwords && lwords->word) ? copy_word (lwords->word) : make_word (""); cl = ret->next = make_word_list (w, (WORD_LIST *)NULL); w = make_word (text); cl->next = make_word_list (w, (WORD_LIST *)NULL); cl = cl->next; /* Search lwords for current word */ for (l = lwords, i = 1; l && i < ind-1; l = l->next, i++) ; w = (l && l->word) ? copy_word (l->word) : make_word (""); cl->next = make_word_list (w, (WORD_LIST *)NULL); return ret; } /* Build a command string with $0 == cs->funcname (function to execute for completion list) $1 == command name (command being completed) $2 = word to be completed (possibly null) $3 = previous word and run in the current shell. The function should put its completion list into the array variable COMPREPLY. We build a STRINGLIST from the results and return it. Since the shell function should return its list of matches in an array variable, this does nothing if arrays are not compiled into the shell. */ static STRINGLIST * gen_shell_function_matches (cs, text, line, ind, lwords, nw, cw) COMPSPEC *cs; const char *text; char *line; int ind; WORD_LIST *lwords; int nw, cw; { char *funcname; STRINGLIST *sl; SHELL_VAR *f, *v; WORD_LIST *cmdlist; int fval; #if defined (ARRAY_VARS) ARRAY *a; #endif funcname = cs->funcname; f = find_function (funcname); if (f == 0) { internal_error ("completion: function `%s' not found", funcname); rl_ding (); rl_on_new_line (); return ((STRINGLIST *)NULL); } #if !defined (ARRAY_VARS) return ((STRINGLIST *)NULL); #else /* We pass cw - 1 because command_line_to_word_list returns indices that are 1-based, while bash arrays are 0-based. */ bind_compfunc_variables (line, ind, lwords, cw - 1, 0); cmdlist = build_arg_list (funcname, text, lwords, cw); fval = execute_shell_function (f, cmdlist); /* Now clean up and destroy everything. */ dispose_words (cmdlist); unbind_compfunc_variables (0); /* The list of completions is returned in the array variable COMPREPLY. */ v = find_variable ("COMPREPLY"); if (v == 0) return ((STRINGLIST *)NULL); if (array_p (v) == 0) v = convert_var_to_array (v); a = array_cell (v); if (a == 0 || array_empty (a)) sl = (STRINGLIST *)NULL; else { /* XXX - should we filter the list of completions so only those matching TEXT are returned? Right now, we do not. */ sl = strlist_create (0); sl->list = array_to_argv (a); sl->list_len = sl->list_size = array_num_elements (a); } /* XXX - should we unbind COMPREPLY here? */ unbind_variable ("COMPREPLY"); return (sl); #endif } /* Build a command string with $0 == cs->command (command to execute for completion list) $1 == command name (command being completed) $2 = word to be completed (possibly null) $3 = previous word and run in with command substitution. Parse the results, one word per line, with backslashes allowed to escape newlines. Build a STRINGLIST from the results and return it. */ static STRINGLIST * gen_command_matches (cs, text, line, ind, lwords, nw, cw) COMPSPEC *cs; const char *text; char *line; int ind; WORD_LIST *lwords; int nw, cw; { char *csbuf, *cscmd, *t; int cmdlen, cmdsize, n, ws, we; WORD_LIST *cmdlist, *cl; STRINGLIST *sl; bind_compfunc_variables (line, ind, lwords, cw, 1); cmdlist = build_arg_list (cs->command, text, lwords, cw); /* Estimate the size needed for the buffer. */ n = strlen (cs->command); cmdsize = n + 1; for (cl = cmdlist->next; cl; cl = cl->next) cmdsize += STRLEN (cl->word->word) + 3; cmdsize += 2; /* allocate the string for the command and fill it in. */ cscmd = (char *)xmalloc (cmdsize + 1); strcpy (cscmd, cs->command); /* $0 */ cmdlen = n; cscmd[cmdlen++] = ' '; for (cl = cmdlist->next; cl; cl = cl->next) /* $1, $2, $3, ... */ { t = sh_single_quote (cl->word->word ? cl->word->word : ""); n = strlen (t); RESIZE_MALLOCED_BUFFER (cscmd, cmdlen, n + 2, cmdsize, 64); strcpy (cscmd + cmdlen, t); cmdlen += n; if (cl->next) cscmd[cmdlen++] = ' '; free (t); } cscmd[cmdlen] = '\0'; csbuf = command_substitute (cscmd, 0); /* Now clean up and destroy everything. */ dispose_words (cmdlist); free (cscmd); unbind_compfunc_variables (1); if (csbuf == 0 || *csbuf == '\0') { FREE (csbuf); return ((STRINGLIST *)NULL); } /* Now break CSBUF up at newlines, with backslash allowed to escape a newline, and put the individual words into a STRINGLIST. */ sl = strlist_create (16); for (ws = 0; csbuf[ws]; ) { we = ws; while (csbuf[we] && csbuf[we] != '\n') { if (csbuf[we] == '\\' && csbuf[we+1] == '\n') we++; we++; } t = substring (csbuf, ws, we); if (sl->list_len >= sl->list_size - 1) strlist_resize (sl, sl->list_size + 16); sl->list[sl->list_len++] = t; while (csbuf[we] == '\n') we++; ws = we; } sl->list[sl->list_len] = (char *)NULL; free (csbuf); return (sl); } static WORD_LIST * command_line_to_word_list (line, llen, sentinel, nwp, cwp) char *line; int llen, sentinel, *nwp, *cwp; { WORD_LIST *ret; char *delims; delims = "()<>;&| \t\n"; /* shell metacharacters break words */ ret = split_at_delims (line, llen, delims, sentinel, nwp, cwp); return (ret); } /* Evaluate COMPSPEC *cs and return all matches for WORD. */ STRINGLIST * gen_compspec_completions (cs, cmd, word, start, end) COMPSPEC *cs; const char *cmd; const char *word; int start, end; { STRINGLIST *ret, *tmatches; char *line; int llen, nw, cw; WORD_LIST *lwords; #ifdef DEBUG debug_printf ("gen_compspec_completions (%s, %s, %d, %d)", cmd, word, start, end); debug_printf ("gen_compspec_completions: %s -> %p", cmd, cs); #endif ret = gen_action_completions (cs, word); #ifdef DEBUG if (ret && progcomp_debug) { debug_printf ("gen_action_completions (%p, %s) -->", cs, word); strlist_print (ret, "\t"); rl_on_new_line (); } #endif /* Now we start generating completions based on the other members of CS. */ if (cs->globpat) { tmatches = gen_globpat_matches (cs, word); if (tmatches) { #ifdef DEBUG if (progcomp_debug) { debug_printf ("gen_globpat_matches (%p, %s) -->", cs, word); strlist_print (tmatches, "\t"); rl_on_new_line (); } #endif ret = strlist_append (ret, tmatches); strlist_dispose (tmatches); rl_filename_completion_desired = 1; } } if (cs->words) { tmatches = gen_wordlist_matches (cs, word); if (tmatches) { #ifdef DEBUG if (progcomp_debug) { debug_printf ("gen_wordlist_matches (%p, %s) -->", cs, word); strlist_print (tmatches, "\t"); rl_on_new_line (); } #endif ret = strlist_append (ret, tmatches); strlist_dispose (tmatches); } } lwords = (WORD_LIST *)NULL; line = (char *)NULL; if (cs->command || cs->funcname) { /* If we have a command or function to execute, we need to first break the command line into individual words, find the number of words, and find the word in the list containing the word to be completed. */ line = substring (rl_line_buffer, start, end); llen = end - start; #ifdef DEBUG debug_printf ("command_line_to_word_list (%s, %d, %d, %p, %p)", line, llen, rl_point - start, &nw, &cw); #endif lwords = command_line_to_word_list (line, llen, rl_point - start, &nw, &cw); #ifdef DEBUG if (lwords == 0 && llen > 0) debug_printf ("ERROR: command_line_to_word_list returns NULL"); else if (progcomp_debug) { debug_printf ("command_line_to_word_list -->"); printf ("\t"); print_word_list (lwords, "!"); printf ("\n"); fflush(stdout); rl_on_new_line (); } #endif } if (cs->funcname) { tmatches = gen_shell_function_matches (cs, word, line, rl_point - start, lwords, nw, cw); if (tmatches) { #ifdef DEBUG if (progcomp_debug) { debug_printf ("gen_shell_function_matches (%p, %s, %p, %d, %d) -->", cs, word, lwords, nw, cw); strlist_print (tmatches, "\t"); rl_on_new_line (); } #endif ret = strlist_append (ret, tmatches); strlist_dispose (tmatches); } } if (cs->command) { tmatches = gen_command_matches (cs, word, line, rl_point - start, lwords, nw, cw); if (tmatches) { #ifdef DEBUG if (progcomp_debug) { debug_printf ("gen_command_matches (%p, %s, %p, %d, %d) -->", cs, word, lwords, nw, cw); strlist_print (tmatches, "\t"); rl_on_new_line (); } #endif ret = strlist_append (ret, tmatches); strlist_dispose (tmatches); } } if (cs->command || cs->funcname) { if (lwords) dispose_words (lwords); FREE (line); } if (cs->filterpat) { tmatches = filter_stringlist (ret, cs->filterpat, word); #ifdef DEBUG if (progcomp_debug) { debug_printf ("filter_stringlist (%p, %s, %s) -->", ret, cs->filterpat, word); strlist_print (tmatches, "\t"); rl_on_new_line (); } #endif if (ret && ret != tmatches) { FREE (ret->list); free (ret); } ret = tmatches; } if (cs->prefix || cs->suffix) ret = strlist_prefix_suffix (ret, cs->prefix, cs->suffix); /* If no matches have been generated and the user has specified that directory completion should be done as a default, call gen_action_completions again to generate a list of matching directory names. */ if ((ret == 0 || ret->list_len == 0) && (cs->options & COPT_DIRNAMES)) { COMPSPEC *dummy; dummy = compspec_create (); dummy->actions = CA_DIRECTORY; ret = gen_action_completions (dummy, word); compspec_dispose (dummy); } return (ret); } /* The driver function for the programmable completion code. Returns a list of matches for WORD, which is an argument to command CMD. START and END bound the command currently being completed in rl_line_buffer. */ char ** programmable_completions (cmd, word, start, end, foundp) const char *cmd; const char *word; int start, end, *foundp; { COMPSPEC *cs; STRINGLIST *ret; char **rmatches, *t; /* We look at the basename of CMD if the full command does not have an associated COMPSPEC. */ cs = progcomp_search (cmd); if (cs == 0) { t = strrchr (cmd, '/'); if (t) cs = progcomp_search (++t); } if (cs == 0) { if (foundp) *foundp = 0; return ((char **)NULL); } /* Signal the caller that we found a COMPSPEC for this command, and pass back any meta-options associated with the compspec. */ if (foundp) *foundp = 1|cs->options; ret = gen_compspec_completions (cs, cmd, word, start, end); if (ret) { rmatches = ret->list; free (ret); } else rmatches = (char **)NULL; return (rmatches); } #endif /* PROGRAMMABLE_COMPLETION */
/* pcomplib.c - library functions for programmable completion. */ /* Copyright (C) 1999-2002 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include <config.h> #if defined (PROGRAMMABLE_COMPLETION) #include "bashansi.h" #include <stdio.h> #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include <sys/types.h> # endif # include <unistd.h> #endif #include "shell.h" #include "pcomplete.h" #define COMPLETE_HASH_BUCKETS 32 /* must be power of two */ #define STRDUP(x) ((x) ? savestring (x) : (char *)NULL) HASH_TABLE *prog_completes = (HASH_TABLE *)NULL; static void free_progcomp __P((PTR_T)); COMPSPEC * compspec_create () { COMPSPEC *ret; ret = (COMPSPEC *)xmalloc (sizeof (COMPSPEC)); ret->refcount = 0; ret->actions = (unsigned long)0; ret->options = (unsigned long)0; ret->globpat = (char *)NULL; ret->words = (char *)NULL; ret->prefix = (char *)NULL; ret->suffix = (char *)NULL; ret->funcname = (char *)NULL; ret->command = (char *)NULL; ret->filterpat = (char *)NULL; return ret; } void compspec_dispose (cs) COMPSPEC *cs; { cs->refcount--; if (cs->refcount == 0) { FREE (cs->globpat); FREE (cs->words); FREE (cs->prefix); FREE (cs->suffix); FREE (cs->funcname); FREE (cs->command); FREE (cs->filterpat); free (cs); } } COMPSPEC * compspec_copy (cs) COMPSPEC *cs; { COMPSPEC *new; new = (COMPSPEC *)xmalloc (sizeof (COMPSPEC)); new->refcount = cs->refcount; new->actions = cs->actions; new->options = cs->options; new->globpat = STRDUP (cs->globpat); new->words = STRDUP (cs->words); new->prefix = STRDUP (cs->prefix); new->suffix = STRDUP (cs->suffix); new->funcname = STRDUP (cs->funcname); new->command = STRDUP (cs->command); new->filterpat = STRDUP (cs->filterpat); return new; } void progcomp_create () { if (prog_completes == 0) prog_completes = hash_create (COMPLETE_HASH_BUCKETS); } int progcomp_size () { return (HASH_ENTRIES (prog_completes)); } static void free_progcomp (data) PTR_T data; { COMPSPEC *cs; cs = (COMPSPEC *)data; compspec_dispose (cs); } void progcomp_flush () { if (prog_completes) hash_flush (prog_completes, free_progcomp); } void progcomp_dispose () { if (prog_completes) hash_dispose (prog_completes); prog_completes = (HASH_TABLE *)NULL; } int progcomp_remove (cmd) char *cmd; { register BUCKET_CONTENTS *item; if (prog_completes == 0) return 1; item = hash_remove (cmd, prog_completes, 0); if (item) { if (item->data) free_progcomp (item->data); free (item->key); free (item); return (1); } return (0); } int progcomp_insert (cmd, cs) char *cmd; COMPSPEC *cs; { register BUCKET_CONTENTS *item; if (cs == NULL) programming_error ("progcomp_insert: %s: NULL COMPSPEC", cmd); if (prog_completes == 0) progcomp_create (); item = hash_insert (cmd, prog_completes, 0); if (item->data) free_progcomp (item->data); else item->key = savestring (cmd); item->data = cs; cs->refcount++; return 1; } COMPSPEC * progcomp_search (cmd) const char *cmd; { register BUCKET_CONTENTS *item; COMPSPEC *cs; if (prog_completes == 0) return ((COMPSPEC *)NULL); item = hash_search (cmd, prog_completes, 0); if (item == NULL) return ((COMPSPEC *)NULL); cs = (COMPSPEC *)item->data; return (cs); } void progcomp_walk (pfunc) hash_wfunc *pfunc; { if (prog_completes == 0 || pfunc == 0 || HASH_ENTRIES (prog_completes) == 0) return; hash_walk (prog_completes, pfunc); } #endif /* PROGRAMMABLE_COMPLETION */
/* * mksyntax.c - construct shell syntax table for fast char attribute lookup. */ /* Copyright (C) 2000 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #include <stdio.h> #include "bashansi.h" #include "chartypes.h" #include <errno.h> #ifdef HAVE_UNISTD_H # include <unistd.h> #endif #include "syntax.h" extern int optind; extern char *optarg; #ifndef errno extern int errno; #endif #ifndef HAVE_STRERROR extern char *strerror(); #endif struct wordflag { int flag; char *fstr; } wordflags[] = { { CWORD, "CWORD" }, { CSHMETA, "CSHMETA" }, { CSHBRK, "CSHBRK" }, { CBACKQ, "CBACKQ" }, { CQUOTE, "CQUOTE" }, { CSPECL, "CSPECL" }, { CEXP, "CEXP" }, { CBSDQUOTE, "CBSDQUOTE" }, { CBSHDOC, "CBSHDOC" }, { CGLOB, "CGLOB" }, { CXGLOB, "CXGLOB" }, { CXQUOTE, "CXQUOTE" }, { CSPECVAR, "CSPECVAR" }, { CSUBSTOP, "CSUBSTOP" }, }; #define N_WFLAGS (sizeof (wordflags) / sizeof (wordflags[0])) #define SYNSIZE 256 int lsyntax[SYNSIZE]; int debug; char *progname; char preamble[] = "\ /*\n\ * This file was generated by mksyntax. DO NOT EDIT.\n\ */\n\ \n"; char includes[] = "\ #include \"stdc.h\"\n\ #include \"syntax.h\"\n\n"; static void usage() { fprintf (stderr, "%s: usage: %s [-d] [-o filename]\n", progname, progname); exit (2); } #ifdef INCLUDE_UNUSED static int getcflag (s) char *s; { int i; for (i = 0; i < N_WFLAGS; i++) if (strcmp (s, wordflags[i].fstr) == 0) return wordflags[i].flag; return -1; } #endif static char * cdesc (i) int i; { static char xbuf[16]; if (i == ' ') return "SPC"; else if (ISPRINT (i)) { xbuf[0] = i; xbuf[1] = '\0'; return (xbuf); } else if (i == CTLESC) return "CTLESC"; else if (i == CTLNUL) return "CTLNUL"; else if (i == '\033') /* ASCII */ return "ESC"; xbuf[0] = '\\'; xbuf[2] = '\0'; switch (i) { case '\a': xbuf[1] = 'a'; break; case '\v': xbuf[1] = 'v'; break; case '\b': xbuf[1] = 'b'; break; case '\f': xbuf[1] = 'f'; break; case '\n': xbuf[1] = 'n'; break; case '\r': xbuf[1] = 'r'; break; case '\t': xbuf[1] = 't'; break; default: sprintf (xbuf, "%d", i); break; } return xbuf; } static char * getcstr (f) int f; { int i; for (i = 0; i < N_WFLAGS; i++) if (f == wordflags[i].flag) return (wordflags[i].fstr); return ((char *)NULL); } static void addcstr (str, flag) char *str; int flag; { char *s, *fstr; unsigned char uc; for (s = str; s && *s; s++) { uc = *s; if (debug) { fstr = getcstr (flag); fprintf(stderr, "added %s for character %s\n", fstr, cdesc(uc)); } lsyntax[uc] |= flag; } } static void addcchar (c, flag) unsigned char c; int flag; { char *fstr; if (debug) { fstr = getcstr (flag); fprintf (stderr, "added %s for character %s\n", fstr, cdesc(c)); } lsyntax[c] |= flag; } /* load up the correct flag values in lsyntax */ static void load_lsyntax () { /* shell metacharacters */ addcstr (shell_meta_chars, CSHMETA); /* shell word break characters */ addcstr (shell_break_chars, CSHBRK); addcchar ('`', CBACKQ); addcstr (shell_quote_chars, CQUOTE); addcchar (CTLESC, CSPECL); addcchar (CTLNUL, CSPECL); addcstr (shell_exp_chars, CEXP); addcstr (slashify_in_quotes, CBSDQUOTE); addcstr (slashify_in_here_document, CBSHDOC); addcstr (shell_glob_chars, CGLOB); #if defined (EXTENDED_GLOB) addcstr (ext_glob_chars, CXGLOB); #endif addcstr (shell_quote_chars, CXQUOTE); addcchar ('\\', CXQUOTE); addcstr ("@*#?-$!", CSPECVAR); /* omits $0...$9 and $_ */ addcstr ("-=?+", CSUBSTOP); /* OP in ${paramOPword} */ } static void dump_lflags (fp, ind) FILE *fp; int ind; { int xflags, first, i; xflags = lsyntax[ind]; first = 1; if (xflags == 0) fputs (wordflags[0].fstr, fp); else { for (i = 1; i < N_WFLAGS; i++) if (xflags & wordflags[i].flag) { if (first) first = 0; else putc ('|', fp); fputs (wordflags[i].fstr, fp); } } } static void wcomment (fp, i) FILE *fp; int i; { fputs ("\t\t/* ", fp); fprintf (fp, "%s", cdesc(i)); fputs (" */", fp); } static void dump_lsyntax (fp) FILE *fp; { int i; fprintf (fp, "const int sh_syntaxtab[%d] = {\n", SYNSIZE); for (i = 0; i < SYNSIZE; i++) { putc ('\t', fp); dump_lflags (fp, i); putc (',', fp); wcomment (fp, i); putc ('\n', fp); } fprintf (fp, "};\n"); } int main(argc, argv) int argc; char **argv; { int opt, i; char *filename; FILE *fp; if ((progname = strrchr (argv[0], '/')) == 0) progname = argv[0]; else progname++; filename = (char *)NULL; debug = 0; while ((opt = getopt (argc, argv, "do:")) != EOF) { switch (opt) { case 'd': debug = 1; break; case 'o': filename = optarg; break; default: usage(); } } argc -= optind; argv += optind; if (filename) { fp = fopen (filename, "w"); if (fp == 0) { fprintf (stderr, "%s: %s: cannot open: %s\n", progname, filename, strerror(errno)); exit (1); } } else { filename = "stdout"; fp = stdout; } for (i = 0; i < SYNSIZE; i++) lsyntax[i] = CWORD; load_lsyntax (); fprintf (fp, "%s\n", preamble); fprintf (fp, "%s\n", includes); dump_lsyntax (fp); if (fp != stdout) fclose (fp); exit (0); } #if !defined (HAVE_STRERROR) #include <bashtypes.h> #ifndef _MINIX # include <sys/param.h> #endif #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif /* Return a string corresponding to the error number E. From the ANSI C spec. */ #if defined (strerror) # undef strerror #endif char * strerror (e) int e; { static char emsg[40]; #if defined (HAVE_SYS_ERRLIST) extern int sys_nerr; extern char *sys_errlist[]; if (e > 0 && e < sys_nerr) return (sys_errlist[e]); else #endif /* HAVE_SYS_ERRLIST */ { sprintf (emsg, "Unknown system error %d", e); return (&emsg[0]); } } #endif /* HAVE_STRERROR */
/* syntax.h -- Syntax definitions for the shell */ /* Copyright (C) 2000 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #ifndef _SYNTAX_H_ #define _SYNTAX_H_ /* Defines for use by mksyntax.c */ #define slashify_in_quotes "\\`$\"\n" #define slashify_in_here_document "\\`$" #define shell_meta_chars "()<>;&|" #define shell_break_chars "()<>;&| \t\n" #define shell_quote_chars "\"`'" #if defined (PROCESS_SUBSTITUTION) # define shell_exp_chars "$<>" #else # define shell_exp_chars "$" #endif #if defined (EXTENDED_GLOB) # define ext_glob_chars "@*+?!" #else # define ext_glob_chars "" #endif #define shell_glob_chars "*?[]^" /* Defines shared by mksyntax.c and the rest of the shell code. */ /* Values for character flags in syntax tables */ #define CWORD 0x0000 /* nothing special; an ordinary character */ #define CSHMETA 0x0001 /* shell meta character */ #define CSHBRK 0x0002 /* shell break character */ #define CBACKQ 0x0004 /* back quote */ #define CQUOTE 0x0008 /* shell quote character */ #define CSPECL 0x0010 /* special character that needs quoting */ #define CEXP 0x0020 /* shell expansion character */ #define CBSDQUOTE 0x0040 /* characters escaped by backslash in double quotes */ #define CBSHDOC 0x0080 /* characters escaped by backslash in here doc */ #define CGLOB 0x0100 /* globbing characters */ #define CXGLOB 0x0200 /* extended globbing characters */ #define CXQUOTE 0x0400 /* cquote + backslash */ #define CSPECVAR 0x0800 /* single-character shell variable name */ #define CSUBSTOP 0x1000 /* values of OP for ${word[:]OPstuff} */ /* Defines for use by the rest of the shell. */ extern const int sh_syntaxtab[]; #define shellmeta(c) (sh_syntaxtab[(unsigned char)(c)] & CSHMETA) #define shellbreak(c) (sh_syntaxtab[(unsigned char)(c)] & CSHBRK) #define shellquote(c) (sh_syntaxtab[(unsigned char)(c)] & CQUOTE) #define issyntype(c, t) ((sh_syntaxtab[(unsigned char)(c)] & (t)) != 0) #define notsyntype(c,t) ((sh_syntaxtab[(unsigned char)(c)] & (t)) == 0) #if defined (PROCESS_SUBSTITUTION) # define shellexp(c) ((c) == '$' || (c) == '<' || (c) == '>') #else # define shellexp(c) ((c) == '$') #endif #if defined (EXTENDED_GLOB) # define PATTERN_CHAR(c) \ ((c) == '@' || (c) == '*' || (c) == '+' || (c) == '?' || (c) == '!') #else # define PATTERN_CHAR(c) 0 #endif #define GLOB_CHAR(c) \ ((c) == '*' || (c) == '?' || (c) == '[' || (c) == ']' || (c) == '^') #define CTLESC '\001' #define CTLNUL '\177' #endif /* _SYNTAX_H_ */
/* xmalloc.c -- safe versions of malloc and realloc */ /* Copyright (C) 1991 Free Software Foundation, Inc. This file is part of GNU Readline, a library for reading lines of text with interactive input and history editing. Readline is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Readline is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Readline; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if defined (HAVE_CONFIG_H) #include <config.h> #endif #include "bashtypes.h" #include <stdio.h> #if defined (HAVE_UNISTD_H) # include <unistd.h> #endif #if defined (HAVE_STDLIB_H) # include <stdlib.h> #else # include "ansi_stdlib.h" #endif /* HAVE_STDLIB_H */ #include "error.h" #if !defined (PTR_T) # if defined (__STDC__) # define PTR_T void * # else # define PTR_T char * # endif /* !__STDC__ */ #endif /* !PTR_T */ #if defined (HAVE_SBRK) && !HAVE_DECL_SBRK extern char *sbrk(); #endif static PTR_T lbreak; static int brkfound; static size_t allocated; /* **************************************************************** */ /* */ /* Memory Allocation and Deallocation. */ /* */ /* **************************************************************** */ #if defined (HAVE_SBRK) static size_t findbrk () { if (brkfound == 0) { lbreak = (PTR_T)sbrk (0); brkfound++; } return (char *)sbrk (0) - (char *)lbreak; } #endif /* Return a pointer to free()able block of memory large enough to hold BYTES number of bytes. If the memory cannot be allocated, print an error message and abort. */ PTR_T xmalloc (bytes) size_t bytes; { PTR_T temp; temp = malloc (bytes); if (temp == 0) { #if defined (HAVE_SBRK) allocated = findbrk (); fatal_error ("xmalloc: cannot allocate %lu bytes (%lu bytes allocated)", (unsigned long)bytes, (unsigned long)allocated); #else fatal_error ("xmalloc: cannot allocate %lu bytes", (unsigned long)bytes); #endif /* !HAVE_SBRK */ } return (temp); } PTR_T xrealloc (pointer, bytes) PTR_T pointer; size_t bytes; { PTR_T temp; temp = pointer ? realloc (pointer, bytes) : malloc (bytes); if (temp == 0) { #if defined (HAVE_SBRK) allocated = findbrk (); fatal_error ("xrealloc: cannot reallocate %lu bytes (%lu bytes allocated)", (unsigned long)bytes, (unsigned long)allocated); #else fatal_error ("xrealloc: cannot allocate %lu bytes", (unsigned long)bytes); #endif /* !HAVE_SBRK */ } return (temp); } /* Use this as the function to call when adding unwind protects so we don't need to know what free() returns. */ void xfree (string) PTR_T string; { if (string) free (string); } #ifdef USING_BASH_MALLOC #include <malloc/shmalloc.h> PTR_T sh_xmalloc (bytes, file, line) size_t bytes; char *file; int line; { PTR_T temp; temp = sh_malloc (bytes, file, line); if (temp == 0) { #if defined (HAVE_SBRK) allocated = findbrk (); fatal_error ("xmalloc: %s:%d: cannot allocate %lu bytes (%lu bytes allocated)", file, line, (unsigned long)bytes, (unsigned long)allocated); #else fatal_error ("xmalloc: %s:%d: cannot allocate %lu bytes", file, line, (unsigned long)bytes); #endif /* !HAVE_SBRK */ } return (temp); } PTR_T sh_xrealloc (pointer, bytes, file, line) PTR_T pointer; size_t bytes; char *file; int line; { PTR_T temp; temp = pointer ? sh_realloc (pointer, bytes, file, line) : sh_malloc (bytes, file, line); if (temp == 0) { #if defined (HAVE_SBRK) allocated = findbrk (); fatal_error ("xrealloc: %s:%d: cannot reallocate %lu bytes (%lu bytes allocated)", file, line, (unsigned long)bytes, (unsigned long)allocated); #else fatal_error ("xrealloc: %s:%d: cannot allocate %lu bytes", file, line, (unsigned long)bytes); #endif /* !HAVE_SBRK */ } return (temp); } void sh_xfree (string, file, line) PTR_T string; char *file; int line; { if (string) sh_free (string, file, line); } #endif
/* siglist.c -- signal list for those machines that don't have one. */ /* Copyright (C) 1989 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if !defined (HAVE_SYS_SIGLIST) && !defined (HAVE_UNDER_SYS_SIGLIST) && !defined (HAVE_STRSIGNAL) #include <stdio.h> #include "bashtypes.h" #include <signal.h> #include "siglist.h" #if !defined (NSIG) # include "trap.h" #endif #include "xmalloc.h" char *sys_siglist[NSIG]; void initialize_siglist () { register int i; for (i = 0; i < NSIG; i++) sys_siglist[i] = (char *)0x0; sys_siglist[0] = "Bogus signal"; #if defined (SIGHUP) sys_siglist[SIGHUP] = "Hangup"; #endif #if defined (SIGINT) sys_siglist[SIGINT] = "Interrupt"; #endif #if defined (SIGQUIT) sys_siglist[SIGQUIT] = "Quit"; #endif #if defined (SIGILL) sys_siglist[SIGILL] = "Illegal instruction"; #endif #if defined (SIGTRAP) sys_siglist[SIGTRAP] = "BPT trace/trap"; #endif #if defined (SIGIOT) && !defined (SIGABRT) #define SIGABRT SIGIOT #endif #if defined (SIGABRT) sys_siglist[SIGABRT] = "ABORT instruction"; #endif #if defined (SIGEMT) sys_siglist[SIGEMT] = "EMT instruction"; #endif #if defined (SIGFPE) sys_siglist[SIGFPE] = "Floating point exception"; #endif #if defined (SIGKILL) sys_siglist[SIGKILL] = "Killed"; #endif #if defined (SIGBUS) sys_siglist[SIGBUS] = "Bus error"; #endif #if defined (SIGSEGV) sys_siglist[SIGSEGV] = "Segmentation fault"; #endif #if defined (SIGSYS) sys_siglist[SIGSYS] = "Bad system call"; #endif #if defined (SIGPIPE) sys_siglist[SIGPIPE] = "Broken pipe"; #endif #if defined (SIGALRM) sys_siglist[SIGALRM] = "Alarm clock"; #endif #if defined (SIGTERM) sys_siglist[SIGTERM] = "Terminated"; #endif #if defined (SIGURG) sys_siglist[SIGURG] = "Urgent IO condition"; #endif #if defined (SIGSTOP) sys_siglist[SIGSTOP] = "Stopped (signal)"; #endif #if defined (SIGTSTP) sys_siglist[SIGTSTP] = "Stopped"; #endif #if defined (SIGCONT) sys_siglist[SIGCONT] = "Continue"; #endif #if !defined (SIGCHLD) && defined (SIGCLD) #define SIGCHLD SIGCLD #endif #if defined (SIGCHLD) sys_siglist[SIGCHLD] = "Child death or stop"; #endif #if defined (SIGTTIN) sys_siglist[SIGTTIN] = "Stopped (tty input)"; #endif #if defined (SIGTTOU) sys_siglist[SIGTTOU] = "Stopped (tty output)"; #endif #if defined (SIGIO) sys_siglist[SIGIO] = "I/O ready"; #endif #if defined (SIGXCPU) sys_siglist[SIGXCPU] = "CPU limit"; #endif #if defined (SIGXFSZ) sys_siglist[SIGXFSZ] = "File limit"; #endif #if defined (SIGVTALRM) sys_siglist[SIGVTALRM] = "Alarm (virtual)"; #endif #if defined (SIGPROF) sys_siglist[SIGPROF] = "Alarm (profile)"; #endif #if defined (SIGWINCH) sys_siglist[SIGWINCH] = "Window changed"; #endif #if defined (SIGLOST) sys_siglist[SIGLOST] = "Record lock"; #endif #if defined (SIGUSR1) sys_siglist[SIGUSR1] = "User signal 1"; #endif #if defined (SIGUSR2) sys_siglist[SIGUSR2] = "User signal 2"; #endif #if defined (SIGMSG) sys_siglist[SIGMSG] = "HFT input data pending"; #endif #if defined (SIGPWR) sys_siglist[SIGPWR] = "power failure imminent"; #endif #if defined (SIGDANGER) sys_siglist[SIGDANGER] = "system crash imminent"; #endif #if defined (SIGMIGRATE) sys_siglist[SIGMIGRATE] = "migrate process to another CPU"; #endif #if defined (SIGPRE) sys_siglist[SIGPRE] = "programming error"; #endif #if defined (SIGGRANT) sys_siglist[SIGGRANT] = "HFT monitor mode granted"; #endif #if defined (SIGRETRACT) sys_siglist[SIGRETRACT] = "HFT monitor mode retracted"; #endif #if defined (SIGSOUND) sys_siglist[SIGSOUND] = "HFT sound sequence has completed"; #endif #if defined (SIGINFO) sys_siglist[SIGINFO] = "Information request"; #endif for (i = 0; i < NSIG; i++) { if (!sys_siglist[i]) { sys_siglist[i] = (char *)xmalloc (10 + strlen ("Unknown Signal #")); sprintf (sys_siglist[i], "Unknown Signal #%d", i); } } } #endif /* !HAVE_SYS_SIGLIST && !HAVE_UNDER_SYS_SIGLIST && !HAVE_STRSIGNAL */
a11ab8528cd79c67a0514ce2581ee81f50398cea1b4c616a22c488c0cc2d619e /usr/bin/bash
#!/bin/bash # SPDX-FileCopyrightText: 2022 Andrius Å tikonas <andrius@stikonas.eu> # SPDX-FileCopyrightText: 2021-22 fosslinux <fosslinux@aussies.space> # SPDX-FileCopyrightText: 2021 Paul Dersey <pdersey@gmail.com> # # SPDX-License-Identifier: GPL-3.0-or-later set -e # shellcheck disable=SC2154 PREFIX="${prefix}" LIBDIR="${PREFIX}/lib/mes" # shellcheck disable=SC2154 SOURCES="${sysa}" DISTFILES="${sysa}/distfiles" DESTDIR=/tmp/destdir # shellcheck disable=SC2154 SRCDIR="${srcdir}" # shellcheck source=sysa/helpers.sh . helpers.sh if [ "${KERNEL_BOOTSTRAP}" ]; then # Fiwix does not support SMP MAKEJOBS="-j1" else MAKEJOBS="-j${JOBS}" fi # Ask some questions echo echo "Now that bash has been built, there are potentially some questions for you!" echo "To give your answer, type your answer, press Enter, and then Control-D." echo ask_chroot() { read -r CHROOT_STRING if [ "${CHROOT_STRING}" = "yes" ] || [ "${CHROOT_STRING}" = "y" ]; then CHROOT=True elif [ "${CHROOT_STRING}" = "no" ] || [ "${CHROOT_STRING}" = "n" ]; then CHROOT=False else echo "Invalid response. Please give a yes/no answer." ask_chroot fi } if [ -z "${CHROOT}" ]; then echo "Currently, it is unknown if live-bootstrap is running in a chroot" echo "or not. Is it? (yes/no answer)" ask_chroot echo echo "CHROOT=${CHROOT}" >> "${SOURCES}/bootstrap.cfg" fi ask_timestamps() { read -r TIMESTAMPS_STRING if [ "${TIMESTAMPS_STRING}" = "yes" ] || [ "${TIMESTAMPS_STRING}" = "y" ]; then FORCE_TIMESTAMPS=True elif [ "${TIMESTAMPS_STRING}" = "no" ] || [ "${TIMESTAMPS_STRING}" = "n" ]; then FORCE_TIMESTAMPS=False else echo "Invalid response. Please give a yes/no answer." ask_timestamps fi } if [ -z "${FORCE_TIMESTAMPS}" ]; then echo "Would you like all timestamps to be set to unix time 0" echo "(Jan 1 1970 00:00) at the end of the bootstrap? This makes a" echo "fully reproducible disk image. (yes/no answer)" ask_timestamps echo echo "FORCE_TIMESTAMPS=${FORCE_TIMESTAMPS}" >> "${SOURCES}/bootstrap.cfg" fi echo "Thank you! All done." echo "ARCH=${ARCH}" >> "${SOURCES}/bootstrap.cfg" mkdir -p "${DESTDIR}" "${SRCDIR}/repo" /dev build flex-2.5.11 # Rebuild tcc with some patches build tcc-0.9.27 tcc-mes-pass2.sh # shellcheck disable=SC2034 LIBDIR="${PREFIX}/lib/i386-unknown-linux-musl" build musl-1.1.24 # Rebuild tcc using musl build tcc-0.9.27 tcc-musl-pass1.sh # Rebuild musl using tcc-musl build musl-1.1.24 # Rebuild tcc-musl using new musl build tcc-0.9.27 tcc-musl-pass2.sh # Rebuild sed using musl build sed-4.0.9 sed-4.0.9.sh # Rebuild bzip2 using musl build bzip2-1.0.8 bzip2-1.0.8.sh build m4-1.4.7 build flex-2.6.4 build bison-3.4.1 stage1.sh build bison-3.4.1 stage2.sh build bison-3.4.1 stage3.sh build grep-2.4 build diffutils-2.7 # Rebuild coreutils using musl build coreutils-5.0 coreutils-5.0.sh patches-musl # Build only date, mktemp and sha256sum build coreutils-6.10 build gawk-3.0.4 build perl-5.000 build perl-5.003 build perl5.004-05 '' '' perl5.004_05 build perl5.005-03 '' '' perl5.005_03 build perl-5.6.2 populate_device_nodes build autoconf-2.52 build automake-1.6.3 stage1.sh build automake-1.6.3 stage2.sh build autoconf-2.53 build automake-1.7 build autoconf-2.54 build autoconf-2.55 build automake-1.7.8 build autoconf-2.57 build autoconf-2.59 build automake-1.8.5 build help2man-1.36.4 build autoconf-2.61 build automake-1.9.6 build automake-1.10.3 build autoconf-2.64 build automake-1.11.2 build autoconf-2.69 build libtool-2.2.4 cat > .env <<- EOF export PATH=${PATH} PREFIX=${PREFIX} LIBDIR=${LIBDIR} SOURCES=${SOURCES} DESTDIR=${DESTDIR} DISTFILES=${DISTFILES} SRCDIR=${SRCDIR} MAKEJOBS=${MAKEJOBS} EOF exec env -i bash run2.sh
#include <stdio.h> #include <string.h> #include <malloc.h> #include <cerrno> FILE *debug_out = 0; // Some generic helper functions: char *copystr(const char *str) { char *new_str = (char*)malloc(strlen(str) + 1); strcpy(new_str, str); return new_str; } // C Preprocessor iterators #define CPP_MAX_PATH_SIZE 1000 class AbstractIterator { public: bool more; char ch; long line; long column; char *filename; virtual void next() = 0; }; class StringIterator : public AbstractIterator { public: StringIterator(const char *s, const char *fn) { filename = copystr(fn); _s = s; line = 1; column = 1; ch = *_s++; more = ch != '\0'; } ~StringIterator() { delete filename; } void next() { if (ch == '\0') return; if (ch == '\n') { line++; column = 1; } else column++; ch = *_s++; if (ch == '\r') ch = *_s++; more = ch != '\0'; } private: const char *_s; }; /* class FileIterator : public AbstractIterator { public: FileIterator(const char *fn) { filename = copystr(fn); _f = fopen(filename, "r"); more = _f != 0; ch = '\n'; line = 0; column = 0; next(); } FileIterator(const char *fn, FILE *f) { filename = copystr(fn); _f = f; more = _f != 0; ch = '\n'; line = 0; column = 0; next(); } ~FileIterator() { delete filename; } void next() { if (!more) return; if (ch == '\n') { line++; column = 0; } column++; ch = fgetc(_f); more = !feof(_f); if (!more) { ch = '\0'; fclose(_f); _f = 0; } else if (ch == '\r') next(); } private: FILE *_f; }; */ bool debug_c_parsing = false; class TrigraphIterator : public AbstractIterator { public: TrigraphIterator(AbstractIterator *source_it) : _source_it(source_it) { filename = _source_it->filename; _a[0] = _source_it->ch; _source_it->next(); _a[1] = _source_it->ch; ch = '\n'; line = 0; more = true; _next_column = 1; next(); } void next() { if (_a[0] == '\0') { more = false; ch = '\0'; return; } if (ch == '\n') { line++; column = 1; } else column = _next_column; ch = _a[0]; _a[0] = _a[1]; _source_it->next(); _a[1] = _source_it->ch; if (ch == '?' && _a[0] == '?') { char rch = '\0'; if (_a[1] == '=') rch = '#'; else if (_a[1] == '/') rch = '\\'; else if (_a[1] == 39) rch = '^'; else if (_a[1] == '(') rch = '['; else if (_a[1] == ')') rch = ']'; else if (_a[1] == '!') rch = '|'; else if (_a[1] == '<') rch = '{'; else if (_a[1] == '>') rch = '}'; else if (_a[1] == '-') rch = '~'; if (rch != '\0') { ch = rch; _next_column = column + 3; _source_it->next(); _a[0] = _source_it->ch; _source_it->next(); _a[1] = _source_it->ch; return; } } _next_column = column + 1; } private: AbstractIterator *_source_it; char _a[2]; long _next_column; }; class LineSpliceIterator : public AbstractIterator { public: LineSpliceIterator(AbstractIterator *source_it) : _source_it(source_it) { filename = _source_it->filename; _a = _source_it->ch; next(); } void next() { if (_a == '\0') { more = false; ch = '\0'; return; } if (_a == ' ' || _a == '\t') { line = _source_it->line; column = _source_it->column; while (_a == ' ' || _a == '\t') { _source_it->next(); _a = _source_it->ch; } ch = ' '; return; } ch = _a; line = _source_it->line; column = _source_it->column; _source_it->next(); _a = _source_it->ch; while (ch == '\\' && _a == '\n') { _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; _source_it->next(); _a = _source_it->ch; } more = ch != '\0'; } private: AbstractIterator *_source_it; char _a; }; class CommentStripIterator : public AbstractIterator { public: CommentStripIterator(AbstractIterator *source_it) : _source_it(source_it) { filename = _source_it->filename; _a = _source_it->ch; _state = 0; more = true; next(); } void next() { switch(_state) { case 0: goto s0; case 1: goto s1; case 2: goto s2; case 3: goto s3; case 4: goto s4; case 5: goto s5; case 6: goto s6; case 7: goto s7; case 8: goto s8; case 9: goto s9; } s0: if (_a == '\0') { more = false; ch = '\0'; return; } ch = _a; line = _source_it->line; column = _source_it->column; _source_it->next(); _a = _source_it->ch; if (ch == '/' && (_a == '/' || _a == '*')) { if (_a == '/') { while (_a != '\0' && _a != '\n') { _source_it->next(); _a = _source_it->ch; } } else { ch = ' '; _state = 1; return; s1: _source_it->next(); ch = _source_it->ch; _source_it->next(); _a = _source_it->ch; while (ch != '\0' && (ch != '*' || _a != '/')) { ch = _a; _source_it->next(); _a = _source_it->ch; } if (ch != '\0') { _source_it->next(); _a = _source_it->ch; line = _source_it->line; column = _source_it->column; } } _state = 0; goto s0; } if (ch == '"') { _state = 2; return; s2: ch = _a; line = _source_it->line; column = _source_it->column; while (ch != '\0' && ch != '"') { if (ch == '\\') { _state = 3; return; s3: _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; } _state = 4; return; s4: _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; } if (ch == '"') { _state = 5; return; s5: _source_it->next(); _a = _source_it->ch; line = _source_it->line; column = _source_it->column; } _state = 0; goto s0; } if (ch == '\'') { _state = 6; return; s6: ch = _a; line = _source_it->line; column = _source_it->column; if (ch == '\\') { _state = 7; return; s7: _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; } _state = 8; return; s8: _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; _state = 0; if (ch == '\'') { _state = 9; return; s9: _source_it->next(); _a = _source_it->ch; line = _source_it->line; column = _source_it->column; } _state = 0; goto s0; } } private: AbstractIterator *_source_it; char _a; int _state; }; class IncludePathResolver { }; class AbstractIncludePathResolver { public: virtual AbstractIterator *iteratorFor(const char *path, bool include_next, AbstractIterator *context) = 0; }; class Define { public: char *name; bool defined; char *str_value; Define *next; Define(const char *_name, const char *value, Define *_next) : defined(true), next(_next), str_value(0) { name = copystr(_name); setValue(value); } ~Define() { free(name); if (str_value != 0) free(str_value); delete next; } int getIntValue() { if (!defined) return 0; if (str_value == 0) return 1; int sign = 1; char *s = str_value; if (*s == '-') sign = -1; int result = 0; while ('0' <= *s && *s <= '9') result = 10 * result + *s++ - '0'; return sign * result; } void setValue(const char *value) { defined = true; if (str_value != 0) free(str_value); if (value == 0) str_value = 0; else { str_value = copystr(value); } } static void addDefine(Define *&defines, const char *name, const char *value) { for (Define *define = defines; define != 0; define = define->next) if (strcmp(define->name, name) == 0) { define->setValue(value); return; } defines = new Define(name, value, defines); } static void removeDefine(Define *defines, const char *name) { for (Define *define = defines; define != 0; define = define->next) if (strcmp(define->name, name) == 0) { define->defined = false; return; } } static bool isDefined(Define *defines, const char *name) { for (Define *define = defines; define != 0; define = define->next) if (strcmp(define->name, name) == 0) return define->defined; return false; } static Define *getDefine(Define *defines, const char *name) { for (Define *define = defines; define != 0; define = define->next) if (strcmp(define->name, name) == 0) return define; return 0; } }; class InlineIncludesIterator : public AbstractIterator { public: InlineIncludesIterator(AbstractIterator *source_it, AbstractIncludePathResolver *includePathResolver, Define *&defines) : _source_it(source_it), _includePathResolver(includePathResolver), _defines(defines) { filename = _source_it->filename; ch = _source_it->ch; line = _source_it->line; column = _source_it->column; more = true; _state = 0; _if_nesting = -1; _skip_level = 0; next(); } void next() { switch(_state) { case 0: goto s0; case 1: goto s1; case 2: goto s2; case 3: goto s3; case 4: goto s4; case 5: goto s5; case 6: goto s6; case 7: goto s7; case 8: goto s8; case 9: goto s9; case 10: goto s10; case 11: goto s11; case 12: goto s12; case 13: goto s13; case 14: goto s14; } s0: while (ch != '\0') { _state = 0; //printf("ch = '%c'", ch); while (ch == ' ') { if (_skip_level == 0) { _state = 1; return; s1:; } _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; } if (ch == '#') { _i = 0; _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; _name[0] = '#'; _i = 1; while (ch == ' ' || ch == '\t') { _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; } while (('a' <= ch && ch <= 'z') || ch == '_') { if (_i < 19) _name[_i++] = ch; _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; } _name[_i] = '\0'; if (debug_c_parsing && (_skip_level == 0 || (_skip_level == 1 && (strcmp(_name, "#endif") == 0 || strcmp(_name, "#else") == 0 || strcmp(_name, "#elif") == 0)))) fprintf(debug_out, "%s.%ld Directive |%s|\n", filename, line, _name); if (strcmp(_name, "#if") == 0 || strcmp(_name, "#elif") == 0) { bool exec_if = false; if (strcmp(_name, "#if") == 0) { if (_skip_level > 0) _skip_level++; else { _if_nesting++; exec_if = true; } } else // elif { if (_if_state[_if_nesting] == 't') { if (_skip_level == 1) { _skip_level = 0; exec_if = true; } } else if (_if_state[_if_nesting] == 'T') { if (_skip_level == 0) { _if_state[_if_nesting] == 'd'; } } else if (_if_state[_if_nesting] == 'd') { // skip } } if (exec_if) { int i = 0; while (ch != '\0' && ch != '\n') { if (_i < 10000) _value[i++] = ch; _source_it->next(); ch = _source_it->ch; } _value[i] = '\0'; skip_space(); //fprintf(stderr, "Parse: |%s|\n", _value); const char *s = _value; if (evaluate_cond(s) != 0) _if_state[_if_nesting] = 'T'; else { _if_state[_if_nesting] = 't'; _skip_level = 1; } if (*s != '\0') fprintf(stderr, "Error: %s:%ld parsing |%s|%s|\n", filename, line, _value, s); //fprintf(stderr, "Done\n"); } } else if (strcmp(_name, "#ifdef") == 0) { if (_skip_level > 0) _skip_level++; else { skip_space(); parse_ident(_name, 100); _if_nesting++; if (_name[0] != '\0' && Define::isDefined(_defines, _name)) _if_state[_if_nesting] = 'T'; else { _if_state[_if_nesting] = 't'; _skip_level = 1; } } } else if (strcmp(_name, "#ifndef") == 0) { if (_skip_level > 0) _skip_level++; else { skip_space(); parse_ident(_name, 100); _if_nesting++; if (_name[0] != '\0' && !Define::isDefined(_defines, _name)) _if_state[_if_nesting] = 'T'; else { _if_state[_if_nesting] = 't'; _skip_level = 1; } } } else if (strcmp(_name, "#else") == 0) { if (_if_state[_if_nesting] == 't') { if (_skip_level == 1) { _skip_level = 0; _if_state[_if_nesting] = 'E'; } } else if (_if_state[_if_nesting] == 'T') { if (_skip_level == 0) { _if_state[_if_nesting] = 'e'; _skip_level++; } } else if (_if_state[_if_nesting] == 'd') { // skip } } else if (strcmp(_name, "#endif") == 0) { if (_if_state[_if_nesting] == 'e' || _if_state[_if_nesting] == 't' || _if_state[_if_nesting] == 'd') { if (_skip_level == 1) { _skip_level = 0; _if_nesting--; } else { if (_skip_level == 0) fprintf(stderr, "ERROR: Expect _skip_level > 0\n"); else _skip_level--; } } else if (_if_state[_if_nesting] == 'E' || _if_state[_if_nesting] == 'T') { if (_skip_level == 0) _if_nesting--; else _skip_level--; } } else if (_skip_level > 0) ; // skip all other directives else if ((strcmp(_name, "#include") == 0/* || strcmp(_name, "#include_next") == 0*/)&& (ch == ' ' || ch == '"' || ch == '<')) { //printf("Include "); skip_space(); { bool include_next = strcmp(_name, "#include_next") == 0; char include_fn[CPP_MAX_PATH_SIZE]; include_fn[0] = ch; int _i = 1; char _end_quote = ch == '<' ? '>' : '"'; _source_it->next(); ch = _source_it->ch; while (ch != '\0' && ch != _end_quote) { include_fn[_i++] = ch; _source_it->next(); ch = _source_it->ch; } if (ch == _end_quote) include_fn[_i++] = _end_quote; include_fn[_i] = '\0'; //printf("Filename |%s|", filename); _nested_iterator = _includePathResolver->iteratorFor(include_fn, include_next, this); } //printf("Begin"); while (_nested_iterator->ch != '\0') { ch = _nested_iterator->ch; filename = _nested_iterator->filename; line = _nested_iterator->line; column = _nested_iterator->column; _state = 2; return; s2: _nested_iterator->next(); } //printf("End"); delete _nested_iterator; filename = _source_it->filename; ch = _source_it->ch; while (ch != '\0' && ch != '\n') { //printf("Skip '%c'", ch); _source_it->next(); ch = _source_it->ch; } if (ch == '\n') { //printf("Skip newline"); _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; continue; } } else if (strcmp(_name, "#define") == 0) { _i = 0; while (_name[_i] != '\0') { ch = _name[_i++]; _state = 3; return; s3:; } ch = _source_it->ch; line = _source_it->line; column = _source_it->column; // skip spaces while (ch == ' ') { _state = 4; return; s4: _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; } // parse name of define _i = 0; while (is_ident()) { if (_i < 100) _name[_i++] = ch; _state = 5; return; s5: _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; } _name[_i] = '\0'; // skip spaces while (ch == ' ') { _state = 6; return; s6: _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; } // parse value of define _i = 0; while (ch != '\0' && ch != '\n') { if (_i < 10000) _value[_i++] = ch; _state = 7; return; s7: _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; } _value[_i] = '\0'; if (_i == 0) strcpy(_value, "1"); if (_name[0] != '\0') { if (debug_c_parsing) fprintf(debug_out, "#define |%s| |%s|\n", _name, _value); Define::addDefine(_defines, _name, _value); } } else if (strcmp(_name, "#undef") == 0) { _i = 0; while (_name[_i] != '\0') { ch = _name[_i++]; _state = 8; return; s8:; } ch = _source_it->ch; line = _source_it->line; column = _source_it->column; // skip spaces while (ch == ' ') { _state = 9; return; s9: _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; } // parse name of define _i = 0; while (is_ident()) { if (_i < 100) _name[_i++] = ch; _state = 10; return; s10: _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; } _name[_i] = '\0'; if (_name[0] != '\0') Define::removeDefine(_defines, _name); } else if (strcmp(_name, "#line") == 0) { _i = 0; while (_name[_i] != '\0') { ch = _name[_i++]; _state = 11; return; s11:; } ch = _source_it->ch; line = _source_it->line; column = _source_it->column; } else { if (strcmp(_name, "#warning") != 0 && strcmp(_name, "#pragma") != 0 && strcmp(_name, "#error") != 0) fprintf(stderr, "Unknown %s.%ld unknown redirective |%s|\n", filename, line, _name); _i = 0; while (_name[_i] != '\0') { ch = _name[_i++]; _state = 12; return; s12:; } ch = _source_it->ch; line = _source_it->line; column = _source_it->column; } } while (ch != '\0' && ch != '\n') { if (_skip_level == 0) { _state = 13; return; s13:; } _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; } if (ch == '\n') { if (_skip_level == 0) { _state = 14; return; s14:; } _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; } } more = false; } private: void skip_space() { while (ch == ' ' || ch == '\t') { //printf("Space "); _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; } } void parse_ident(char *name, int len) { _i = 0; while (is_ident()) { if (_i < len) name[_i++] = ch; _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; } name[_i] = '\0'; } bool is_ident() { return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ch == '_' || (_i > 0 && '0' <= ch && ch <= '9'); } bool is_ident_start(char ch) { return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ch == '_'; } bool is_ident_next(char ch) { return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ch == '_' || ('0' <= ch && ch <= '9'); } int evaluate_cond(const char *&s) { if (debug_c_parsing) fprintf(debug_out, "cond: %s\n", s); int v = evaluate_or(s); if (*s == '?') { s++; int then_v = evaluate_or(s); int else_v = 0; if (*s == ':') { s++; else_v = evaluate_cond(s); } return v != 0 ? then_v : else_v; } if (debug_c_parsing) fprintf(debug_out, "cond: %s = %d\n", s, v); return v; } int evaluate_or(const char *&s) { //fprintf(stderr, "or: %s\n", s); int v = evaluate_and(s); while (s[0] == '|' && s[1] == '|') { s += 2; int n_v = evaluate_cmp(s); v = v || n_v; } return v; } int evaluate_and(const char *&s) { //fprintf(stderr, "and: %s\n", s); int v = evaluate_cmp(s); while (s[0] == '&' && s[1] == '&') { s += 2; int n_v = evaluate_cmp(s); v = v && n_v; } return v; } int evaluate_cmp(const char *&s) { //fprintf(stderr, "cmp: %s\n", s); int v = evaluate_bshift(s); if (s[0] == '>' && s[1] == '=') { s += 2; return v >= evaluate_bshift(s); } if (s[0] == '>') { s++; return v > evaluate_bshift(s); } if (s[0] == '<' && s[1] == '=') { s += 2; return v <= evaluate_bshift(s); } if (s[0] == '<') { s++; return v < evaluate_bshift(s); } if (s[0] == '=' && s[1] == '=') { s += 2; return v == evaluate_bshift(s); } if (s[0] == '!' && s[1] == '=') { s += 2; return v != evaluate_bshift(s); } return v; } int evaluate_bshift(const char *&s) { int v = evaluate_add(s); if (s[0] == '>' && s[1] == '>') { s += 2; return v >= evaluate_add(s); } if (s[0] == '<' && s[1] == '<') { s += 2; return v <= evaluate_add(s); } return v; } int evaluate_add(const char *&s) { //fprintf(stderr, "add: %s\n", s); int v = evaluate_mul(s); for (;;) if (s[0] == '+') { s++; v += evaluate_mul(s); } else if (s[0] == '-') { s++; v -= evaluate_mul(s); } else break; return v; } int evaluate_mul(const char *&s) { //fprintf(stderr, "mul: %s\n", s); int v = evaluate_term(s); for (;;) if (s[0] == '*') { s++; v *= evaluate_term(s); } else if (s[0] == '/') { s++; int divider = evaluate_term(s); if (divider != 0) v /= divider; } else if (s[0] == '%') { s++; int divider = evaluate_term(s); if (divider != 0) v %= divider; } else break; return v; } int evaluate_term(const char *&s) { int result = 0; while (*s == ' ' || *s == '\t') s++; //fprintf(stderr, "term: %s\n", s); if (*s == '(') { s++; result = evaluate_cond(s); if (*s == ')') s++; } else if (is_ident_start(*s)) { char id[101]; int i = 0; for (; is_ident_next(*s); s++) if (i < 100) id[i++] = *s; id[i] = '\0'; //fprintf(stderr, "id: |%s|\n", id); if (strcmp(id, "defined") == 0) { while (*s == ' ' || *s == '\t') s++; int b = 0; while (*s == '(') { b++; s++; while (*s == ' ' || *s == '\t') s++; } i = 0; for (; is_ident_next(*s); s++) if (i < 100) id[i++] = *s; id[i] = '\0'; //fprintf(stderr, "defined |%s| |%s|\n", id, s); while (*s == ' ' || *s == '\t') s++; result = Define::isDefined(_defines, id); if (debug_c_parsing) fprintf(debug_out, "defined |%s| |%s| %d\n", id, s, result); while (*s == ')' && b > 0) { b--; s++; while (*s == ' ' || *s == '\t') s++; } //fprintf(stderr, "left: |%s|\n", s); } else { if (debug_c_parsing) fprintf(debug_out, "Define |%s|\n", id); Define *define = Define::getDefine(_defines, id); result = define != 0 ? define->getIntValue() : 0; if (debug_c_parsing) fprintf(debug_out, "Value %d\n", result); } } else if ('0' <= *s && *s <= '9') { while ('0' <= *s && *s <= '9') result = 10 * result + *s++ - '0'; if (*s == 'L') s++; } else if (*s == '\'') { s++; if (*s == '\\') s++; result = *s++; if (*s == '\'') s++; } else if (*s == '!') { s++; result = !evaluate_term(s); } while (*s == ' ' || *s == '\t') s++; return result; } AbstractIterator *_source_it; AbstractIncludePathResolver *_includePathResolver; Define *&_defines; int _state; char _name[101]; char _value[100000]; int _i; bool _if_state[50]; // T - then: execute // t - then: skip // E - else: execute // e - else: skip // d - done int _if_nesting; char _end_quote; AbstractIterator *_nested_iterator; int _skip_level; bool _done; }; #define MAX_LINE_SIZE 10000 #define MAX_PATH_SIZE 1000 class File; int nr_sources = 0; class Source { public: char *url; int nr; File *files; Source *next; Source(const char *u) : url(copystr(u)), files(0), next(0) { nr = nr_sources++; } }; Source *sources = 0; void add_source(const char *url, File *file); class Usage; class Step; void map_filename(const char *fn, char *mapped_fn) { mapped_fn[0] = '\0'; if (strncmp(fn, "/sysa/", 6) == 0) strcpy(mapped_fn, "../git/live-bootstrap"); strcat(mapped_fn, fn); } FILE *fopen_mapped(const char *fn, FILE *fout = 0) { char mapped_fn[MAX_PATH_SIZE]; mapped_fn[0] = '\0'; if (strncmp(fn, "/sysa/", 6) == 0) strcpy(mapped_fn, "../git/live-bootstrap"); strcat(mapped_fn, fn); if (fout != 0) fprintf(fout, "Info '%s' '%s'\n", fn, mapped_fn); return fopen(mapped_fn, "r"); } class LineInFile { public: const char *text; File *file; long line; LineInFile *next; LineInFile(const char *t, File *f, long l) : text(copystr(t)), file(f), line(l), next(0) {} LineInFile(LineInFile *lif, int offset = 0) : text(lif->text + offset), file(lif->file), line(lif->line), next(0) {} }; class MergeChild; int nr_files = 0; class File { public: char *name; int nr; bool exists; bool produced; bool modified; Step *produced_by; bool exec; bool used_as_input; bool removed; Source *source; File *next_source_file; File *alias; File *copy_from; Usage *usages; MergeChild *mergeChildren; bool loaded; LineInFile *lines; File *next; File(const char *fn) : exists(false), produced(false), produced_by(0), exec(false), next(0), source(0), next_source_file(0), alias(0), copy_from(0), usages(0), mergeChildren(0), loaded(false), lines(0) { name = copystr(fn); nr = nr_files++; if (strncmp(name, "/sysa/distfiles/", 16) == 0) exists = true; else if (strncmp(name, "/usr/bin", 6) != 0) { FILE *f = fopen_mapped(name); if (f != 0) { exists = true; fclose(f); } else if (errno != 2) fprintf(stderr, "Errno %d for %s\n", errno, name); } } File *existing() { for (File *f = this; f != 0; f = f->copy_from) if (f->exists || f->loaded) return f; return 0; } bool exists_now() { return !removed && (exists || produced); } void set_source(const char *url) { Source **ref_source = &sources; for (; *ref_source != 0; ref_source = &(*ref_source)->next) if (strcmp((*ref_source)->url, url) == 0) break; if (*ref_source == 0) { *ref_source = new Source(url); } source = *ref_source; File **ref_file = &(*ref_source)->files; while (*ref_file != 0) ref_file = &(*ref_file)->next_source_file; *ref_file = this; } void load(FILE *fout) { if (loaded) return; loaded = true; FILE *f = fopen_mapped(name, fout); if (f == 0) { fprintf(fout, "Failed to load |%s|\n", name); if (copy_from != 0) { copy_from->load(fout); lines = copy_from->lines; } return; } LineInFile **ref_line = &lines; char buffer[MAX_LINE_SIZE]; long line = 1; while (fgets(buffer, MAX_LINE_SIZE, f)) { int len = strlen(buffer); while (len > 0 && (buffer[len - 1] == '\n' || buffer[len - 1] == '\r')) buffer[--len] = '\0'; *ref_line = new LineInFile(buffer, this, line++); ref_line = &(*ref_line)->next; } fclose(f); } void print_stat(FILE *fout) { fprintf(fout, "Stat %d: |%s| ", nr, name); if (exists) fprintf(fout, " exists"); if (produced) fprintf(fout, " produced"); if (exec) fprintf(fout, " exec"); if (used_as_input) fprintf(fout, " used as input"); if (removed) fprintf(fout, " removed"); if (copy_from != 0) fprintf(fout, " copied from |%s|", copy_from->name); if (loaded) fprintf(fout, " loaded"); if (lines != 0) fprintf(fout, " has lines"); fprintf(fout, "\n"); } FILE *open() { return fopen(name, "r"); } }; File *files = 0; class MergeChild { public: File *child; MergeChild *next; MergeChild(File *file) : child(file), next(0) {} }; void combine_dir_fn(const char *dir, const char *fn, char *filename) { if (fn[0] == '/') { strcpy(filename, fn); return; } if (fn[0] == '.' && fn[1] == '/') fn += 2; strcpy(filename, dir); char *t = filename + strlen(filename); if (fn[0] == '.' && fn[1] == '\0') return; *t++ = '/'; for (const char *s = fn; *s != '\0'; s++) if (s[0] == '.' && s[1] == '.') { if (t[-1] == '/') { --t; *t = '\0'; } s++; for (char *r = filename; *r != '\0'; r++) if (*r == '/') t = r; } else *t++ = *s; *t = '\0'; //fprintf(stdout, "combine dir |%s|%s| into |%s|\n", dir, fn, filename); } File *get_file(const char *full_fn, bool use_alias = true) { File **ref = &files; for (; *ref != 0; ref = &(*ref)->next) if (strcmp((*ref)->name, full_fn) == 0) return (*ref)->alias != 0 && use_alias ? (*ref)->alias : (*ref); *ref = new File(full_fn); return *ref; } File *get_file(const char *dir, const char *fn) { if (fn[0] == '/') return get_file(fn); char full_fn[MAX_PATH_SIZE]; combine_dir_fn(dir, fn, full_fn); return get_file(full_fn); } File *find_file(const char *fn) { for (File *file = files; file != 0; file = file->next) if (strcmp(file->name, fn) == 0) return file->alias != 0 ? file->alias : file; return 0; } File *find_exec_file(const char *fn) { char full_fn[MAX_PATH_SIZE]; if (*fn == '/') strcpy(full_fn, fn); else combine_dir_fn("/usr/bin", fn, full_fn); return find_file(full_fn); } bool file_exists(const char *dir, const char *fn) { char full_fn[MAX_PATH_SIZE]; combine_dir_fn(dir, fn, full_fn); if (find_file(full_fn) != 0) return true; FILE *f = fopen_mapped(full_fn); if (f != 0) { fclose(f); return true; } return false; } int nr_steps = 0; class Step { public: int nr; Usage *uses; Step *next; Step() : uses(0), next(0) { nr = ++nr_steps; } bool hasInputUseOf(File *f); }; Step *all_steps = 0; Step **ref_next = &all_steps; Step *next_step() { Step *step = new Step(); *ref_next = step; ref_next = &step->next; return step; } int indent_depth = 0; void indent(FILE *fout) { fprintf(fout, "%*.*s", indent_depth, indent_depth, ""); } class Usage { public: bool as_input; bool as_output; bool as_modified; bool as_exec; bool as_removed; File *file; Step *step; Usage *next_use; Usage *next_usage; Usage (File *_file, Step *_step) : as_input(false), as_output(false), as_exec(false), as_removed(false), file(_file), step(_step), next_use(0), next_usage(0) { Usage **ref_use = &step->uses; while (*ref_use != 0) ref_use = &(*ref_use)->next_use; *ref_use = this; Usage **ref_usage = &file->usages; while (*ref_usage != 0) ref_usage = &(*ref_usage)->next_usage; *ref_usage = this; } void is_input(FILE *fout) { as_input = true; file->used_as_input = true; indent(fout); fprintf(fout, "%s file: %s\n", file->exists ? "Existing" : file->produced ? "Produced" : "Input", file->name); if (!file->exists_now()) fprintf(fout, "ERROR: Using non existing file %s\n", file->name); } void is_output(FILE *fout) { as_output = true; indent(fout); fprintf(fout, "Output file: %s\n", file->name); file->produced = true; file->produced_by = step; file->removed = false; } void is_modified(FILE *fout) { as_input = true; file->modified = true; indent(fout); fprintf(fout, "%s file: %s\n", file->exists ? "Existing" : file->produced ? "Produced" : "Input", file->name); if (!file->exists_now()) fprintf(fout, "ERROR: Modifying non existing file %s\n", file->name); } void is_exec(FILE *fout) { as_exec = true; indent(fout); fprintf(fout, "Exec file: %s", file->name); if (!file->exists && !file->produced) fprintf(fout, " Not existing nor produced"); if (file->exists) fprintf(fout, " Existing"); if (file->produced) fprintf(fout, " Produced"); fprintf(fout, "\n"); if (!file->exists_now()) fprintf(fout, "ERROR: Executing non existing file %s\n", file->name); } void is_removed(FILE *fout) { as_removed = true; file->removed = true; indent(fout); fprintf(fout, "Deleted file: %s\n", file->name); } }; bool Step::hasInputUseOf(File *f) { for (Usage *use = uses; use != 0; use = use->next_use) if (use->file == f && use->as_input) return true; return false; } class Var { public: char *name; char *value; bool alias; bool override; Var *next; Var(const char *n, const char *v, bool a, Var *nv) : next(nv), alias(a), override(false) { name = copystr(n); value = copystr(v); } }; char *get_var(Var *vars, const char *name) { for (; vars != 0; vars = vars->next) if (!vars->alias && strcmp(vars->name, name) == 0) return vars->value; return 0; } Var *get_var_var(Var *vars, const char *name) { for (; vars != 0; vars = vars->next) if (!vars->alias && strcmp(vars->name, name) == 0) return vars; return 0; } void copy_file(const char *src, const char *dest) { File *src_file = get_file(src); File *dest_file = get_file(dest); dest_file->alias = src_file; dest_file->exists = src_file->exists; dest_file->produced = src_file->produced; } void copy_dir(const char *src, const char *dest) { int src_len = strlen(src); for (File *file = files; file != 0; file = file->next) if (strncmp(file->name, src, src_len) == 0) { char dest_filename[MAX_PATH_SIZE]; sprintf(dest_filename, "%s%s", dest, file->name + src_len); //fprintf(stderr, "Copy dir |%s| |%s|\n", file->name, dest_filename); copy_file(file->name, dest_filename); } } class SubModule { public: char *path; char *url; SubModule *next; SubModule(const char *p, const char *u, SubModule *n) : next(n) { path = copystr(p); url = copystr(u); } }; SubModule *read_sub_modules(const char *fn, SubModule *subModules) { char complete_path[MAX_LINE_SIZE]; strcpy(complete_path, fn); char *s_path = complete_path + strlen(complete_path); strcpy(s_path, ".gitmodules"); FILE *f = fopen(complete_path, "r"); if (f == 0) return subModules; char path[MAX_LINE_SIZE]; char url[MAX_LINE_SIZE]; while (fgets(path, MAX_LINE_SIZE-1, f) && fgets(path, MAX_LINE_SIZE-1, f) && fgets(url, MAX_LINE_SIZE-1, f)) { if (strncmp(path, "\tpath = ", 8) == 0 && strncmp(url, "\turl = ", 7) == 0) { while (path[strlen(path)-1] < ' ') path[strlen(path)-1] = '\0'; while (url[strlen(url)-1] < ' ') url[strlen(url)-1] = '\0'; char *s = strstr(url, ".git"); if (s != 0) *s = '\0'; sprintf(s_path, "%s/", path + 8); int len = strlen(url); char complete_url[MAX_LINE_SIZE]; if (strncmp(url + 7, "https://github.com/", 19) == 0) sprintf(complete_url, "%s/blob/main/%%s", url + 7); else if (strncmp(url + 7, "https://git.savannah.nongnu.org/git/", 36) == 0) sprintf(complete_url, "https://git.savannah.nongnu.org/gitweb/?p=%s.git;a=blob;f=%%s", url + 43); else sprintf(complete_url, "%s%%s", url + 7); printf("Submodule %s %s|\n", complete_path, complete_url); subModules = new SubModule(complete_path, complete_url, subModules); subModules = read_sub_modules(complete_path, subModules); } } return subModules; } class Directory { public: char *path; Directory *next; Directory(const char *p, Directory *n) : path(copystr(p)), next(n) {} }; Directory *directories = 0; bool is_directory(const char *path) { for (Directory *dir = directories; dir != 0; dir = dir->next) if (strcmp(dir->path, path) == 0) return true; return false; } void add_directory(const char *path) { if (!is_directory(path)) directories = new Directory(path, directories); } bool parse_kaem_file(const char *fn, Var *vars, const char *parent_cur_path, FILE *fout); void dir_of_filename(const char *fn, char *dir); const char *file_of_filename(const char *fn); void path_of_filename(const char *fn, char *path); const char *file_ext(const char *fn); FILE *open_file(const char *dir, const char *fn); void write_html(FILE *f, SubModule *submodules); int main(int argc, char *argv[]) { //if (argc != 2) //{ // fprintf(stdout, "Usage:\n%s <filename>\n", argv[0]); // return -1; //} char cur_path[MAX_PATH_SIZE]; #define STAGE0POSIX "../git/live-bootstrap/sysa/stage0-posix/src/" /*"../git/stage0-posix/" */ const char *stage0_path = STAGE0POSIX "kaem.x86"; SubModule *subModules = new SubModule(STAGE0POSIX, "https://github.com/oriansj/stage0-posix/blob/main/%s", 0); SubModule **ref_lastSubmodule = &subModules->next; subModules = read_sub_modules(STAGE0POSIX, subModules); dir_of_filename(stage0_path, cur_path); fprintf(stdout, "Cur path: %s\n", cur_path); FILE *fout = fopen("out.txt", "w"); debug_out = fout; bool result = parse_kaem_file(file_of_filename(stage0_path), new Var("EXE_SUFFIX", "", false, 0), cur_path, fout); fprintf(fout, "Result: %s\n", result ? "OK" : "FAIL"); //fclose(fout); const char *live_bootstrap = "../git/live-bootstrap/sysa/run.kaem"; *ref_lastSubmodule = new SubModule("../git/live-bootstrap/", "https://github.com/fosslinux/live-bootstrap/blob/master/%s", 0); ref_lastSubmodule = &(*ref_lastSubmodule)->next; subModules = new SubModule("/sysa/", "https://github.com/fosslinux/live-bootstrap/blob/master/sysa/%s", subModules); subModules = new SubModule("../git/live-bootstrap/sysa/bzip2-1.0.8/build/bzip2-1.0.8/", "https://github.com/libarchive/bzip2/blob/6a8690fc8d26c815e798c588f796eabe9d684cf0/%s", subModules); subModules = new SubModule("../git/live-bootstrap/sysa/tcc-0.9.26/build/mes-0.24.2/", "https://git.savannah.gnu.org/cgit/mes.git/tree/%s?h=v0.24.2", subModules); subModules = new SubModule("../git/live-bootstrap/sysa/tcc-0.9.26/build/tcc-0.9.26-1136-g5bba73cc/", "https://gitlab.com/janneke/tinycc/-/blob/ff2210b308321f27ee07476d07f24b5095602b17/%s", subModules); subModules = new SubModule("../git/live-bootstrap/sysa/tcc-0.9.27/build/mes-0.24.2/", "https://git.savannah.gnu.org/cgit/mes.git/tree/%s?h=v0.24.2", subModules); subModules = new SubModule("../git/live-bootstrap/sysa/tcc-0.9.27/build/tcc-0.9.27/", "https://gitlab.com/janneke/tinycc/-/tree/release_0_9_27/%s", subModules); subModules = new SubModule("/sysa/tcc-0.9.27/build/tcc-0.9.27/", "https://gitlab.com/janneke/tinycc/-/tree/release_0_9_27/%s", subModules); subModules = new SubModule("../git/live-bootstrap/sysa/make-3.82/build/make-3.82/", "https://git.savannah.gnu.org/cgit/make.git/tree/%s?h=3.82", subModules); subModules = new SubModule("../git/live-bootstrap/sysa/coreutils-5.0/build/coreutils-5.0/", "https://git.savannah.gnu.org/cgit/coreutils.git/tree/%s?h=COREUTILS-5_0", subModules); subModules = new SubModule("../git/live-bootstrap/sysa/tar-1.12/build/tar-1.12", "https://git.savannah.gnu.org/cgit/tar.git/tree/%s?id=e9da8d1a8020ad4dfe92416f3c77a33e6906b55e", subModules); subModules = new SubModule("../git/live-bootstrap/sysa/sed-4.0.9/build/sed-4.0.9/", "https://git.savannah.gnu.org/cgit/sed.git/tree/%s?id=9c9919efe2166efd32409054005619062624226c", subModules); subModules = new SubModule("../git/live-bootstrap/sysa/bash-2.05b/build/bash-2.05b/", "https://git.savannah.gnu.org/cgit/bash.git/tree/%s?id=7117c2d221b2aed4ede8600f6a36b7c1454b4f55", subModules); //subModules = new SubModule("/sysa/mes-0.24.2", "https://github.com/fosslinux/live-bootstrap/blob/master/sysa/mes-0.24.2/%s", subModules); subModules = new SubModule("/sysa/mes-0.24.2/build/mes-0.24.2/", "https://git.savannah.gnu.org/cgit/mes.git/tree/%s?h=v0.24.2", subModules); subModules = new SubModule("/M2libc/", "https://github.com/oriansj/M2libc/blob/main/%s", subModules); // /sysa/mes-0.24.2/build/mes-0.24.2/ // https://git.savannah.gnu.org/cgit/mes.git/tree/%s?h=v0.24.2 // subModules = new SubModule("", "", subModules); // subModules = new SubModule("", "", subModules); // subModules = new SubModule("", "", subModules); // subModules = new SubModule("", "", subModules); // https://download.savannah.gnu.org/releases/nyacc/nyacc-1.00.2.tar.gz // https://git.savannah.gnu.org/cgit/nyacc.git/tree/?h=V1.00.2 dir_of_filename(live_bootstrap, cur_path); fprintf(stdout, "Cur path: %s\n", cur_path); add_directory("/sysa"); add_directory("/sysa/distfiles"); add_directory("/usr/bin"); add_directory("/usr/lib"); add_directory("/usr/include"); add_directory("/usr/src"); add_directory("/sysa/mes-0.24.2/build/mes-0.24.2/include/mes"); add_directory("/M2libc"); Var *live_bootstrap_vars = 0; live_bootstrap_vars = new Var("ARCH", "x86", false, live_bootstrap_vars); live_bootstrap_vars = new Var("sysa", "/sysa", false, live_bootstrap_vars); live_bootstrap_vars = new Var("distfiles", "/sysa/distfiles", false, live_bootstrap_vars); live_bootstrap_vars = new Var("bindir", "/usr/bin", false, live_bootstrap_vars); live_bootstrap_vars = new Var("libdir", "/usr/lib", false, live_bootstrap_vars); live_bootstrap_vars = new Var("incdir", "/usr/include", false, live_bootstrap_vars); live_bootstrap_vars = new Var("srcdir", "/usr/src", false, live_bootstrap_vars); live_bootstrap_vars = new Var("mes_cpu:-x86", "x86", false, live_bootstrap_vars); live_bootstrap_vars = new Var("stage0_cpu:-x86", "stage0_cpu", false, live_bootstrap_vars); live_bootstrap_vars = new Var("prefix", "/usr", false, live_bootstrap_vars); live_bootstrap_vars = new Var("UPDATE_CHECKSUMS", "False", false, live_bootstrap_vars); live_bootstrap_vars = new Var("PREFIX", "PREFIX", false, live_bootstrap_vars); //live_bootstrap_vars = new Var("MES_ARCH", "x86", false, live_bootstrap_vars); //live_bootstrap_vars = new Var("ARCH_BITS", "32", false, live_bootstrap_vars); live_bootstrap_vars = new Var("KERNEL_BOOTSTRAP", "False", false, live_bootstrap_vars); live_bootstrap_vars = new Var("BUILD_KERNELS", "False", false, live_bootstrap_vars); #define ARCH_PATH "x86" copy_file(STAGE0POSIX ARCH_PATH "/bin/blood-elf","/usr/bin/blood-elf"); copy_file(STAGE0POSIX ARCH_PATH "/bin/catm","/usr/bin/catm"); copy_file(STAGE0POSIX ARCH_PATH "/bin/chmod","/usr/bin/chmod"); copy_file(STAGE0POSIX ARCH_PATH "/bin/get_machine","/usr/bin/get_machine"); copy_file(STAGE0POSIX ARCH_PATH "/bin/hex2","/usr/bin/hex2"); copy_file(STAGE0POSIX ARCH_PATH "/bin/kaem","/usr/bin/kaem"); copy_file(STAGE0POSIX ARCH_PATH "/bin/match","/usr/bin/match"); copy_file(STAGE0POSIX ARCH_PATH "/bin/M1","/usr/bin/M1"); copy_file(STAGE0POSIX ARCH_PATH "/bin/M2-Mesoplanet","/usr/bin/M2-Mesoplanet"); copy_file(STAGE0POSIX ARCH_PATH "/bin/M2-Planet","/usr/bin/M2-Planet"); copy_file(STAGE0POSIX ARCH_PATH "/bin/mkdir","/usr/bin/mkdir"); copy_file(STAGE0POSIX ARCH_PATH "/bin/sha256sum","/usr/bin/sha256sum"); copy_file(STAGE0POSIX ARCH_PATH "/bin/unbz2","/usr/bin/unbz2"); copy_file(STAGE0POSIX ARCH_PATH "/bin/ungz","/usr/bin/ungz"); copy_file(STAGE0POSIX ARCH_PATH "/bin/untar","/usr/bin/untar"); copy_file(STAGE0POSIX ARCH_PATH "/bin/cp","/usr/bin/cp"); copy_file(STAGE0POSIX ARCH_PATH "/bin/replace","/usr/bin/replace"); copy_file(STAGE0POSIX ARCH_PATH "/bin/rm","/usr/bin/rm"); copy_dir( STAGE0POSIX "M2libc", "/M2libc"); copy_file(STAGE0POSIX "x86/x86_defs.M1","/M2libc/x86/x86_defs.M1"); copy_file(STAGE0POSIX ARCH_PATH "/AMD64_defs.M1","/M2libc/AMD64/AMD64_defs.M1"); copy_file(STAGE0POSIX "M2libc/bootstrappable.h", "/M2libc/bootstrappable.h"); //fout = fopen("out2.txt", "w"); result = parse_kaem_file(file_of_filename(live_bootstrap), live_bootstrap_vars, cur_path, fout); for (File *file = files; file != 0; file = file->next) if (file->exec && !file->exists && !file->produced) fprintf(fout, "ERROR: File %s is a binary input\n", file->name); fprintf(fout, "Result: %s\n", result ? "OK" : "FAIL"); fclose(fout); FILE *f_html = fopen("livebootstrap.html", "w"); if (f_html != 0) { write_html(f_html, subModules); fclose(f_html); } return 0; } void parse_c_file(const char *cur_path, File *file, const char **includes, int nr_includes, Define *&defines, Step *step, FILE *fout); void parse_make_file(const char *cur_path, File *file, char **args, int nr_args, Var *vars, Step *step, FILE *fout); int parse_command(const char *cur_path, char *command, FILE *fout); int parse_command(const char *cur_path, char *args[], int nr_args, Var *vars, FILE *fout); void get_args(char *command, char *args[], int &nr_args); void process_patch_file(File *patch_file, const char *cur_path, Step *step, FILE *fout); int nesting = 0; File *nested_files[20]; bool nested_exec[20]; bool parse_kaem_file(const char *fn, Var *vars, const char *parent_cur_path, FILE *fout) { char cur_path[MAX_PATH_SIZE]; strcpy(cur_path, parent_cur_path); char *bin_path = get_var(vars, "bindir"); if (bin_path == 0) bin_path = cur_path; FILE *f = open_file(cur_path, fn); if (f == 0) { if (strcmp(cur_path, "/sysa/mes-0.24.2/build/mes-0.24.2") == 0 && strcmp(fn, "kaem.run") == 0) f = fopen("../git/mes/kaem.run", "r"); if (f == 0) { fprintf(fout, "ERROR: Cannot open file '%s/%s'\n", cur_path, fn); return false; } } indent(fout); fprintf(fout, "Parse kaem file: %s/%s\n", cur_path, fn); indent_depth += 2; int skip_level = 0; char buffer[MAX_LINE_SIZE]; while (fgets(buffer, MAX_LINE_SIZE-1, f)) { int len = strlen(buffer); while (len > 0 && buffer[len-1] < ' ') buffer[--len] = '\0'; if (buffer[0] != '\0' && buffer[0] != '#') { char command_line[MAX_LINE_SIZE]; strcpy(command_line, buffer); for (;;) { int len = strlen(command_line); if (command_line[len-1] != '\\') break; command_line[len-1] = '\0'; if (!fgets(buffer, MAX_LINE_SIZE-1, f)) break; if (buffer[0] == '\0' && buffer[0] == '#') break; int len2 = strlen(buffer); while (len2 > 0 && buffer[len2-1] < ' ') buffer[--len2] = '\0'; strcat(command_line, buffer); } indent(fout); fprintf(fout, "Process %s\n", command_line); char exp_command_line[MAX_LINE_SIZE]; int i = 0; char *s = command_line; while (*s == ' ' || *s == '\t') s++; if (('a' <= *s && *s <= 'z') || ('A' <= *s && *s <= 'Z')) { bool alias_used = false; char command[40]; int j = 0; while (('a' <= *s && *s <= 'z') || ('A' <= *s && *s <= 'Z')) command[j++] = *s++; command[j] = '\0'; if (*s == ' ') { for (Var *var = vars; var != 0; var = var->next) if (var->alias && strcmp(var->name, command) == 0) { //fprintf(fout, "Found |%s|\n", var->value); for (const char *v = var->value; *v != '\0'; v++) exp_command_line[i++] = *v; exp_command_line[i++] = ' '; alias_used = true; break; } } if (!alias_used) s = command_line; } while (*s == ' ' || *s == '\t') s++; if (strncmp(s, "exec ", 5) == 0) s += 5; for (; *s != '\0';) { if (s[0] == '$' && s[1] == '{') { s += 2; char name[50]; int j = 0; while (*s != '\0' && *s != '}') name[j++] = *s++; name[j] = '\0'; //fprintf(fout, "Find |%s|\n", name); bool var_found = false; for (Var *var = vars; var != 0; var = var->next) if (!var->alias && strcmp(var->name, name) == 0) { //fprintf(fout, "Found |%s|\n", var->value); for (const char *v = var->value; *v != '\0'; v++) exp_command_line[i++] = *v; var_found = true; break; } if (*s == '}') s++; if (!var_found) { fprintf(fout, "ERROR: '%s' not found\n", name); //return false; } } else if (*s < ' ') { exp_command_line[i++] = ' '; s++; } else exp_command_line[i++] = *s++; } exp_command_line[i] = '\0'; indent(fout); fprintf(fout, "Exp: %s\n", exp_command_line); char *args[1000]; int nr_args = 0; s = exp_command_line; get_args(exp_command_line, args, nr_args); if (strcmp(exp_command_line, "if") == 0) { if (skip_level > 0) skip_level++; else { if (strcmp(args[nr_args - 1], "then") != 0) { fprintf(fout, "ERROR: unsupported if statement"); skip_level = 1; } else { // undo part of the get_args int l = strlen(args[nr_args - 2]); if (args[nr_args - 2][l - 1] == ';') args[nr_args - 2][l - 1] = '\0'; //fprintf(stderr, "IF: "); //for (int i = 1; i < nr_args - 1; i++) // fprintf(stderr, " |%s|", args[i]); //fprintf(stderr, "\n"); int result = parse_command(cur_path, args + 1, nr_args - 2, vars, fout); fprintf(fout, "MATCH %d\n", result); if (result != 0) skip_level = 1; } } } else if (strcmp(exp_command_line, "else") == 0) { if (skip_level == 1) skip_level = 0; else if (skip_level == 0) skip_level = 1; } else if (strcmp(exp_command_line, "fi") == 0) { if (skip_level > 0) skip_level--; } else if (skip_level > 0) ; // skip else if (strcmp(exp_command_line, "cd") == 0) { if (nr_args != 2) { fprintf(fout, "ERROR: cd does not have one argument\n"); } else { char *s = args[1]; if (*s == '/') { strcpy(cur_path, s); } else if (strncmp(s, "../git/", 7) == 0) { strcpy(cur_path, s); } else { while (*s != '\0') { if (s[0] == '.' && s[1] == '.') { s += 2; char *last = 0; for (char *t = cur_path; *t != '\0'; t++) if (*t == '/') last = t; if (last != 0) *last = '\0'; if (*s != '/') break; s++; } else { char *end = cur_path + strlen(cur_path); *end++ = '/'; while (*s != '\0' && *s != ' ') *end++ = *s++; *end = '\0'; break; } } } add_directory(cur_path); indent(fout); fprintf(fout, "cd to %s\n", cur_path); } } else if (strcmp(exp_command_line, "set") == 0) { if (nr_args == 2 && strcmp(args[1], "-ex") == 0) ; else fprintf(fout, "Warning: set\n"); } else if (strcmp(exp_command_line, "echo") == 0) { } else { bool is_var_decl = false; bool alias = false; s = exp_command_line; if (strcmp(s, "alias") == 0 && nr_args > 1) { s = args[1]; alias = true; } if (('a' <= *s && *s <= 'z') || ('A' <= *s && *s <= 'Z')) { char name[50]; int i = 0; while (('a' <= *s && *s <= 'z') || ('A' <= *s && *s <= 'Z') || *s == '_' || ('0' <= *s && *s <= '9')) name[i++] = *s++; name[i] = '\0'; if (s[0] == '=') { s++; char close_char = '\0'; if (*s == '"') { close_char = '"'; s++; } is_var_decl = true; char value[200]; i = 0; while (*s != '\0' && *s != close_char) value[i++] = *s++; if (*s != '\0' && *s == close_char) s++; value[i] = '\0'; indent(fout); fprintf(fout, "Decl %s = |%s|\n", name, value); vars = new Var(name, value, alias, vars); } } if (!is_var_decl) { parse_command(cur_path, args, nr_args, vars, fout); } } } } fclose(f); indent_depth -= 2; return true; } void get_args(char *command, char *args[], int &nr_args) { char *s = command; nr_args = 0; while (*s == ' ' || *s == '\t') s++; for (;;) { while (*s != '\0' && *s <= ' ') s++; if (*s == '\0' || *s == '#') break; args[nr_args++] = s; while (*s > ' ') if (*s == '\\' && s[1] != '\0') s += 2; else if (*s == '"' && *s != '#') { s++; while (*s != '\0' && *s != '"') s++; if (*s == '"') s++; } else s++; if (*s == '\0' || *s == '#') break; *s++ = '\0'; } } int parse_command(const char *cur_path, char *command, FILE *fout) { //fprintf(fout, "parse_command( %s |%s|)\n", cur_path, command); char *args[100]; int nr_args; get_args(command, args, nr_args); //for (int i = 0; i < nr_args; i++) // fprintf(fout, " |%s|\n", args[i]); return parse_command(cur_path, args, nr_args, 0, fout); } int parse_command(const char *cur_path, char *args[], int nr_args, Var *vars, FILE *fout) { indent_depth += 2; int result = 0; const char *bin_path = get_var(vars, "bindir"); if (bin_path == 0) bin_path = cur_path; int argnr = 1; Step *step = next_step(); indent(fout), fprintf(fout, "Step %d\n", step->nr); for (int n = 0; n < nesting; n++) { Usage *usage = new Usage(nested_files[n], step); if (nested_exec[n]) usage->as_exec = true; else usage->as_input = true; } File *exec_file = find_exec_file(args[0]); if (exec_file == 0) exec_file = get_file(args[0][0] == '.' ? cur_path : bin_path, args[0]); exec_file->exec = true; Usage *usage = new Usage(exec_file, step); usage->is_exec(fout); const char *exec_fn = file_of_filename(args[0]); if (strcmp(exec_fn, "kaem-optional-seed") == 0 || strcmp(exec_fn, "kaem-0") == 0) { if (nr_args != 2) fprintf(fout, "ERROR: kaem-0 requires one argument\n"); else { char *kaem_input_fn = args[1]; nested_files[nesting] = step->uses->file; nested_exec[nesting] = true; nesting++; parse_kaem_file(kaem_input_fn[0] == '.' && kaem_input_fn[1] == '/' ? kaem_input_fn + 2 : kaem_input_fn, vars, cur_path, fout); nesting--; } } else if (strcmp(exec_fn, "kaem") == 0) { char *kaem_input_fn = 0; for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "--verbose") == 0) ; else if (strcmp(arg, "--strict") == 0) ; else if (strcmp(arg, "--file") == 0 && arg_nr + 1 < nr_args) { kaem_input_fn = args[arg_nr + 1]; arg_nr++; } else fprintf(fout, "WARNING: Unknown kaem argument '%s'\n", arg); } if (kaem_input_fn != 0) { nested_files[nesting] = step->uses->file; nested_exec[nesting] = true; nesting++; parse_kaem_file(kaem_input_fn[0] == '.' && kaem_input_fn[1] == '/' ? kaem_input_fn + 2 : kaem_input_fn, vars, cur_path, fout); nesting--; } } else if (strcmp(exec_fn, "catm") == 0) { MergeChild **ref_child; for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; File *file = get_file(cur_path, arg); Usage *usage = new Usage(file, step); if (arg_nr == 1) { usage->is_output(fout); ref_child = &file->mergeChildren; } else { usage->is_input(fout); *ref_child = new MergeChild(file); ref_child = &(*ref_child)->next; } } } else if (strcmp(exec_fn, "M2") == 0 || strcmp(exec_fn, "M2-Planet") == 0) { for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "--architecture") == 0) arg_nr++; else if (strcmp(arg, "--bootstrap-mode") == 0) ; else if (strcmp(arg, "--debug") == 0) ; else if (strcmp(arg, "-f") == 0 && arg_nr + 1 < nr_args) (new Usage(get_file(cur_path, args[++arg_nr]), step))->is_input(fout); else if (strcmp(arg, "-o") == 0 && arg_nr + 1 < nr_args) (new Usage(get_file(cur_path, args[++arg_nr]), step))->is_output(fout); else fprintf(fout, "Warning: Skipped '%s'\n", arg); } } else if (strcmp(exec_fn, "blood-elf-0") == 0 || strcmp(exec_fn, "blood-elf") == 0) { for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "--64") == 0) ; else if (strcmp(arg, "--little-endian") == 0) ; else if (strcmp(arg, "-f") == 0 && arg_nr + 1 < nr_args) (new Usage(get_file(cur_path, args[++arg_nr]), step))->is_input(fout); else if (strcmp(arg, "-o") == 0 && arg_nr + 1 < nr_args) (new Usage(get_file(cur_path, args[++arg_nr]), step))->is_output(fout); else fprintf(fout, "Warning: Skipped '%s'\n", arg); } } else if (strcmp(exec_fn, "M1-0") == 0 || strcmp(exec_fn, "M1") == 0) { for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "--architecture") == 0) arg_nr++; else if (strcmp(arg, "--little-endian") == 0) ; else if (strcmp(arg, "-f") == 0 && arg_nr + 1 < nr_args) (new Usage(get_file(cur_path, args[++arg_nr]), step))->is_input(fout); else if (strcmp(arg, "-o") == 0 && arg_nr + 1 < nr_args) (new Usage(get_file(cur_path, args[++arg_nr]), step))->is_output(fout); else fprintf(fout, "Warning: Skipped '%s'\n", arg); } } else if (strcmp(exec_fn, "hex2-1") == 0 || strcmp(exec_fn, "hex2") == 0) { for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "--architecture") == 0) arg_nr++; else if (strcmp(arg, "--little-endian") == 0) ; else if (strcmp(arg, "--base-address") == 0) arg_nr++; else if (strcmp(arg, "-f") == 0 && arg_nr + 1 < nr_args) (new Usage(get_file(cur_path, args[++arg_nr]), step))->is_input(fout); else if (strcmp(arg, "-o") == 0 && arg_nr + 1 < nr_args) (new Usage(get_file(cur_path, args[++arg_nr]), step))->is_output(fout); else fprintf(fout, "Warning: Skipped '%s'\n", arg); } } else if (strcmp(exec_fn, "cp") == 0) { if (nr_args != 3) fprintf(fout, "ERROR: cp requires two arguments\n"); else { File *source_file = get_file(cur_path, args[1]); (new Usage(source_file, step))->is_input(fout); char target[MAX_PATH_SIZE]; combine_dir_fn(cur_path, args[2], target); File *target_file = 0; if (target[strlen(target) - 1] == '/' || is_directory(target)) // || (bin_path != 0 && strcmp(bin_path, target) == 0) // || (*file_ext(args[1]) != '\0' && *file_ext(target) == '\0')) { char target_fn[MAX_PATH_SIZE]; strcpy(target_fn, target); if (target[strlen(target) - 1] != '/') strcat(target_fn, "/"); strcat(target_fn, file_of_filename(args[1])); target_file = get_file(target_fn); } else target_file = get_file(target); target_file->copy_from = source_file; //fprintf(fout, "Copy from %s %s\n", target_file->name, source_file->name); (new Usage(target_file, step))->is_output(fout); } } else if (strcmp(exec_fn, "mv") == 0) { File *source_file = 0; File *target_file = 0; for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "-f") == 0 && arg_nr + 1 < nr_args) { arg = args[++arg_nr]; source_file = get_file(cur_path, arg); (new Usage(source_file, step))->is_input(fout); (new Usage(source_file, step))->is_removed(fout); } else { target_file = get_file(cur_path, arg); (new Usage(target_file, step))->is_output(fout); } } if (source_file != 0 && target_file != 0) target_file->copy_from = source_file; } else if (strcmp(exec_fn, "checksum-transcriber") == 0) { if (nr_args == 2) { File *sources_file = get_file(cur_path, args[1]); (new Usage(sources_file, step))->is_input(fout); char *distfiles = get_var(vars, "distfiles"); if (distfiles != 0) { FILE *f_source = fopen(sources_file->name, "r"); if (f_source != 0) { char line[300]; while (fgets(line, 299, f_source)) { char *s = line; while (*s != '\0' && *s != ' ') s++; if (*s == ' ') *s++ = '\0'; char *target = 0; if (*s != '\0') { // Skip checksum while (*s == ' ') s++; while (*s != '\0' && *s != ' ') s++; // See if target name is specified if (*s == ' ') { while (*s == ' ') s++; target = s; while (*s > ' ') s++; *s = '\0'; } } File *source_file = get_file(distfiles, target != 0 ? target : file_of_filename(line)); source_file->set_source(line); indent(fout); fprintf(fout, "URL for %s : %s\n", source_file->name, source_file->source->url); } fclose(f_source); } } char outputSHA256SUM[MAX_PATH_SIZE]; strcpy(outputSHA256SUM, args[1]); strcat(outputSHA256SUM, ".SHA256SUM"); (new Usage(get_file(cur_path, outputSHA256SUM), step))->is_output(fout); } } else if (strcmp(exec_fn, "M2-Mesoplanet") == 0) { File *input_file = 0; for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "--architecture") == 0) arg_nr++; else if (strcmp(arg, "--operating-system") == 0) arg_nr++; else if (strcmp(arg, "-f") == 0 && arg_nr + 1 < nr_args) { arg = args[++arg_nr]; input_file = get_file(cur_path, arg); (new Usage(input_file, step))->is_input(fout); } else if (strcmp(arg, "-o") == 0 && arg_nr + 1 < nr_args) (new Usage(get_file(cur_path, args[++arg_nr]), step))->is_output(fout); else (new Usage(get_file(cur_path, arg), step))->is_input(fout); } if (input_file != 0) { const char *includes[2] = { "./M2Libc", "/" }; //{ "../M2-Mesoplanet/M2libc" }; const char *m2libpath = get_var(vars, "M2LIBC_PATH"); if (m2libpath != 0) includes[0] = m2libpath; Define *defines = 0; Define::addDefine(defines, "__M2__", 0); parse_c_file(cur_path, input_file, includes, 2, defines, step, fout); } } else if (strcmp(exec_fn, "sha256sum") == 0) { for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "-c") == 0 && arg_nr + 1 < nr_args) (new Usage(get_file(cur_path, args[++arg_nr]), step))->is_input(fout); else if (strcmp(arg, "-o") == 0) { for (arg_nr += 2; arg_nr < nr_args; arg_nr++) { File *file = get_file(bin_path, args[arg_nr]); //fprintf(fout, "Check: %s %d %d\n", file->name, file->produced, file->exists); } } else fprintf(fout, "Warning: Skipped '%s'\n", arg); } } else if (strcmp(exec_fn, "mkdir") == 0) { for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char dirname[MAX_PATH_SIZE]; combine_dir_fn(cur_path, args[arg_nr], dirname); add_directory(dirname); } } else if (strcmp(exec_fn, "ungz") == 0 || strcmp(exec_fn, "unbz2") == 0) { for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "--file") == 0 && arg_nr + 1 < nr_args) (new Usage(get_file(cur_path, args[++arg_nr]), step))->is_input(fout); else if (strcmp(arg, "--output") == 0 && arg_nr + 1 < nr_args) (new Usage(get_file(cur_path, args[++arg_nr]), step))->is_output(fout); else (new Usage(get_file(cur_path, arg), step))->is_input(fout); } } else if (strcmp(exec_fn, "untar") == 0) { for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "--non-strict") == 0) ; else if (strcmp(arg, "--file") == 0 && arg_nr + 1 < nr_args) (new Usage(get_file(cur_path, args[++arg_nr]), step))->is_input(fout); else (new Usage(get_file(cur_path, arg), step))->is_input(fout); } } else if (strcmp(exec_fn, "rm") == 0) { for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "-r") == 0 && arg_nr + 1 < nr_args) { arg = args[++arg_nr]; char full_fn[MAX_PATH_SIZE]; combine_dir_fn(cur_path, args[arg_nr], full_fn); int len = strlen(full_fn); bool slash = full_fn[len - 1] == '/'; for (File *file = files; file != 0; file = file->next) if (strncmp(file->name, full_fn, len) == 0 && (slash ? file->name[len] != '\0' : (file->name[len] == '\0' || file->name[len] == '/'))) (new Usage(file, step))->is_removed(fout); } else (new Usage(get_file(cur_path, arg), step))->is_removed(fout); } } else if (strcmp(exec_fn, "chmod") == 0) { // ignore } else if (strcmp(exec_fn, "replace") == 0) { for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "--file") == 0 && arg_nr + 1 < nr_args) (new Usage(get_file(cur_path, args[++arg_nr]), step))->is_input(fout); else if (strcmp(arg, "--output") == 0 && arg_nr + 1 < nr_args) (new Usage(get_file(cur_path, args[++arg_nr]), step))->is_output(fout); else if (strcmp(arg, "--match-on") == 0 && arg_nr + 1 < nr_args) arg_nr++; else if (strcmp(arg, "--replace-with") == 0) { if (arg_nr + 1 < nr_args) arg_nr++; } else (new Usage(get_file(cur_path, arg), step))->is_input(fout); } } else if (strcmp(exec_fn, "mes-m2") == 0 || strcmp(exec_fn, "mes") == 0) { bool string_input = false; char *scm_input = 0; char *c_input = 0; char *s_input = 0; const char *includes[10]; int nr_includes = 0; bool o_flag = false; File *c_file = 0; Define *defines = 0; for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "--no-auto-compile") == 0 && strcmp(exec_fn, "mes") == 0) ; else if (strcmp(arg, "-e") == 0 && arg_nr + 1 < nr_args) arg_nr++; else if (strcmp(arg, "--") == 0 && arg_nr + 1 < nr_args) ; else if (strcmp(arg, "-S") == 0 && arg_nr + 1 < nr_args) ; else if (strcmp(arg, "-D") == 0 && arg_nr + 1 < nr_args) { arg = args[++arg_nr]; char *s = arg + 1; while (*s != '\0' && *s != '=') s++; if (*s == '=') { *s = '\0'; s++; } Define::addDefine(defines, arg, *s == '\0' ? 0 : s); } else if (strcmp(arg, "-I") == 0 && arg_nr + 1 < nr_args) includes[nr_includes++] = args[++arg_nr]; else if (strcmp(arg, "-c") == 0 && arg_nr + 1 < nr_args || strncmp(arg, "-c", 2) == 0) { if (arg[2] == '\0') arg = args[++arg_nr]; else arg += 2; if (arg[0] == '"') string_input = true; else { if (c_input != 0) fprintf(fout, "ERROR: more than one c file\n"); else c_input = arg; c_file = get_file(cur_path, arg); (new Usage(c_file, step))->is_input(fout); } } else if (strcmp(arg, "-L") == 0 && arg_nr + 1 < nr_args) { arg_nr++; char libdir[MAX_PATH_SIZE]; combine_dir_fn(cur_path, args[arg_nr], libdir); strcat(libdir, "/"); int len = strlen(libdir); for (File *file = files; file != 0; file = file->next) if (strncmp(libdir, file->name, len) == 0 && strcmp(file_ext(file->name), "a") == 0) (new Usage(file, step))->is_input(fout); } else if (strcmp(arg, "--base-address") == 0 && arg_nr + 1 < nr_args) arg_nr++; else if (strcmp(arg, "-nostdlib") == 0) ; else if (strcmp(arg, "-o") == 0 && arg_nr + 1 < nr_args) { (new Usage(get_file(cur_path, args[++arg_nr]), step))->is_output(fout); o_flag = true; } else if (strcmp(arg, "-l") == 0 && arg_nr + 1 < nr_args) arg_nr++; else if (strncmp(arg, "-l", 2) == 0) ; else if (arg[0] == '-') fprintf(fout, "Warning: Unknown option |%s|\n", arg); else { int len = strlen(arg); if (strcmp(file_ext(arg), "scm") == 0) { if (scm_input != 0) fprintf(fout, "ERROR: more than one .scm file\n"); else scm_input = arg; } else if (strcmp(file_ext(arg), "c") == 0) { if (c_input != 0) fprintf(fout, "ERROR: more than one .c file\n"); else c_input = arg; c_file = get_file(cur_path, arg); } else if (strcmp(file_ext(arg), "s") == 0) { if (s_input != 0) fprintf(fout, "ERROR: more than one .s file\n"); else s_input = arg; } (new Usage(get_file(cur_path, arg), step))->is_input(fout); } } if (!string_input) { if (c_input == 0 && s_input == 0) fprintf(fout, "Info: No c and s input\n"); else if (!o_flag && strcmp(c_input + strlen(c_input) - 2, ".c") == 0) { c_input[strlen(c_input) - 1] = 'o'; (new Usage(get_file(cur_path, file_of_filename(c_input)), step))->is_output(fout); c_input[strlen(c_input) - 1] = 's'; (new Usage(get_file(cur_path, file_of_filename(c_input)), step))->is_output(fout); } if (scm_input == 0) fprintf(fout, "ERROR: No scm input\n"); if (c_file != 0) { c_file->print_stat(fout); while (!c_file->exists && !c_file->loaded && c_file->copy_from != 0) c_file = c_file->copy_from; c_file->print_stat(fout); if (!c_file->exists && !c_file->loaded) fprintf(fout, "Warning: Cannot parse C file '%s'\n", c_file->name); else parse_c_file(cur_path, c_file, includes, nr_includes, defines, step, fout); } } } else if ( strcmp(exec_fn, "mes-tcc") == 0 || strcmp(exec_fn, "boot0-tcc") == 0 || strcmp(exec_fn, "boot1-tcc") == 0 || strcmp(exec_fn, "boot2-tcc") == 0 || strcmp(exec_fn, "boot3-tcc") == 0 || strcmp(exec_fn, "boot4-tcc") == 0 || strcmp(exec_fn, "boot5-tcc") == 0 || strcmp(exec_fn, "tcc") == 0 || strcmp(exec_fn, "tcc-0.9.26") == 0) { if (strcmp(args[1], "-version") == 0) ; else if (strcmp(args[1], "-ar") == 0) { int arg_nr = 2; //if (arg_nr < nr_args && (strcmp(args[arg_nr], "cr") == 0 || strcmp(args[arg_nr], "-rv") == 0 || strcmp(args[arg_nr],"cq") == 0)) if (arg_nr < nr_args && strstr(args[arg_nr], ".") == 0) arg_nr++; if (arg_nr + 2 > nr_args) fprintf(fout, "ERROR: -ar requires at least two more arguments\n"); else { (new Usage(get_file(cur_path, args[arg_nr]), step))->is_output(fout); for (int i = arg_nr + 1; i < nr_args; i++) (new Usage(get_file(cur_path, args[i]), step))->is_input(fout); } } else { char *c_input = 0; char *h_input = 0; const char *includes[10]; int nr_includes = 0; bool o_flag = false; Define *defines = 0; Define::addDefine(defines, "__TINYC__", 0); Define::addDefine(defines, "__STDC__", 0); for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; //fprintf(fout, "Process |%s|\n", arg); if (strcmp(arg, "-c") == 0) ; else if (strcmp(arg, "-g") == 0) ; else if (strcmp(arg, "-v") == 0) ; else if (strcmp(arg, "-static") == 0) ; else if ((strcmp(arg, "-D") == 0 && arg_nr + 1 < nr_args) || strncmp(arg, "-D", 2) == 0) { if (arg[2] == '\0') arg = args[++arg_nr]; else arg += 2; char *s = arg + 1; while (*s != '\0' && *s != '=') s++; if (*s == '=') { *s = '\0'; s++; } if (debug_c_parsing) fprintf(fout, "Arg Define %s as |%s|\n", arg, *s == '\0' ? "1" : s); Define::addDefine(defines, arg, *s == '\0' ? "1" : s); } else if ((strcmp(arg, "-I") == 0 && arg_nr + 1 < nr_args) || strncmp(arg, "-I", 2) == 0) { if (arg[2] == '\0') arg = args[++arg_nr]; else arg += 2; includes[nr_includes++] = (arg[0] == '.' && arg[1] == '\0') ? "" : arg; } else if ((strcmp(arg, "-L") == 0 && arg_nr + 1 < nr_args) || strncmp(arg, "-L", 2) == 0) { if (arg[2] == '\0') arg = args[++arg_nr]; else arg += 2; //if (strcmp(args[arg_nr], ".") != 0) // (new Usage(get_file(cur_path, args[arg_nr]), step))->is_input(fout); } else if (strcmp(arg, "-o") == 0 && arg_nr + 1 < nr_args) { (new Usage(get_file(cur_path, args[++arg_nr]), step))->is_output(fout); o_flag = true; } else if (strncmp(arg, "-l", 2) == 0) ; else if (strncmp(arg, "-W", 2) == 0) ; // skip warning switches else if (strncmp(arg, "-O", 2) == 0) ; // skip optimation switches else { if (strcmp(file_ext(arg), "c") == 0) { if (c_input != 0) fprintf(fout, "ERROR: more than one c file\n"); else c_input = arg; } else if (strcmp(file_ext(arg), "h") == 0 && h_input == 0) h_input = arg; (new Usage(get_file(cur_path, arg), step))->is_input(fout); } } includes[nr_includes++] = "/usr/include"; if (c_input == 0) c_input = h_input; if (c_input == 0) { fprintf(fout, "Info: No c or h input\n"); } else { File *c_file = get_file(cur_path, c_input); if (!o_flag) { c_input[strlen(c_input)-1] = 'o'; (new Usage(get_file(cur_path, file_of_filename(c_input)), step))->is_output(fout); } if (c_file != 0) { fprintf(fout, "PARSE: %s|\n", c_file->name); c_file->print_stat(fout); while (!c_file->exists && !c_file->loaded && c_file->mergeChildren == 0 && c_file->copy_from != 0) c_file = c_file->copy_from; c_file->print_stat(fout); if (c_file->loaded || c_file->existing() != 0) parse_c_file(cur_path, c_file, includes, nr_includes, defines, step, fout); else if (c_file->mergeChildren != 0) { for (MergeChild *child = c_file->mergeChildren; child != 0; child = child->next) if (child->child->existing() != 0) { fprintf(fout, "PARSE %s|\n", child->child->name); parse_c_file(cur_path, child->child, includes, nr_includes, defines, step, fout); } else fprintf(fout, "File %s does not exist\n", child->child->name); } else { fprintf(fout, "Warning: Cannot parse C file '%s'\n", c_file->name); } } } } } else if (strcmp(exec_fn, "make") == 0) { File *make_file = 0; char *make_args[10]; int nr_make_args = 0; for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "--version") == 0) ; else if (strcmp(arg, "-f") == 0 && arg_nr + 1 < nr_args) { make_file = get_file(cur_path, args[++arg_nr]); } else { make_args[nr_make_args++] = arg; indent(fout); fprintf(fout, " make switch: %s\n", arg); } } nested_files[nesting] = exec_file; nested_exec[nesting] = true; nesting++; parse_make_file(cur_path, make_file, make_args, nr_make_args, vars, step, fout); nesting--; } else if (strcmp(exec_fn, "gzip") == 0) { bool deflate = false; for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "-d") == 0) deflate = true; else if (strcmp(arg, "-f") == 0 && arg_nr + 1 < nr_args) { arg = args[++arg_nr]; (new Usage(get_file(cur_path, arg), step))->is_input(fout); char *ext = (char *)file_ext(arg); if (deflate && strcmp(ext, "gz") == 0) { ext[-1] = '\0'; (new Usage(get_file(cur_path, arg), step))->is_output(fout); } } else { fprintf(fout, "ERROR: %s\n", arg); } } } else if (strcmp(exec_fn, "simple-patch") == 0) { if (nr_args != 4) fprintf(fout, "ERROR: simple-patch requires 4 arguments, not %d\n", nr_args); else { File *patch_file = get_file(cur_path, args[1]); (new Usage(patch_file, step))->is_modified(fout); patch_file->load(fout); File *from_patch_file = get_file(cur_path, args[2]); (new Usage(from_patch_file, step))->is_input(fout); from_patch_file->load(fout); File *to_patch_file = get_file(cur_path, args[3]); (new Usage(to_patch_file, step))->is_input(fout); to_patch_file->load(fout); bool match = false; for (LineInFile **ref_line = &patch_file->lines; *ref_line != 0; ref_line = &(*ref_line)->next) { match = true; LineInFile *l1 = *ref_line; LineInFile *l2 = from_patch_file->lines; for (; match && l1 != 0 && l2 != 0; l1 = l1->next, l2 = l2->next) match = strcmp(l1->text, l2->text) == 0; if (l1 == 0 && l2 != 0) break; if (match) { fprintf(fout, "Info: simple-patch found match\n"); for (LineInFile *to_line = to_patch_file->lines; to_line != 0; to_line = to_line->next) { *ref_line = new LineInFile(to_line); ref_line = &(*ref_line)->next; } *ref_line = l1; break; } } if (!match) fprintf(fout, "Info: simple-patch found NO match\n"); } } else if (strcmp(exec_fn, "patch") == 0) { bool deflate = false; File *patch_file = 0; for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "-Np0") == 0) deflate = true; else if (strcmp(arg, "-i") == 0 && arg_nr + 1 < nr_args) { patch_file = get_file(cur_path, args[++arg_nr]); (new Usage(patch_file, step))->is_input(fout); } else { fprintf(fout, "ERROR: %s\n", arg); } } if (patch_file != 0) process_patch_file(patch_file, cur_path, step, fout); } else if (strcmp(exec_fn, "bunzip2") == 0) { for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "-f") == 0 && arg_nr + 1 < nr_args) { arg = args[++arg_nr]; (new Usage(get_file(cur_path, arg), step))->is_input(fout); char *ext = (char *)file_ext(arg); if (strcmp(ext, "bz2") == 0) { ext[-1] = '\0'; (new Usage(get_file(cur_path, arg), step))->is_output(fout); } } else { fprintf(fout, "ERROR: %s\n", arg); } } } else if (strcmp(exec_fn, "ln") == 0) { char *input_fn = 0; char *output_fn = 0; for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "-s") == 0 && arg_nr + 1 < nr_args) ; else if (input_fn == 0) input_fn = arg; else output_fn = arg; } if (output_fn != 0) { (new Usage(get_file(cur_path, input_fn), step))->is_input(fout); (new Usage(get_file(cur_path, output_fn), step))->is_output(fout); } } else if (strcmp(exec_fn, "install") == 0) { int arg_nr = strcmp(args[1], "-m") == 0 ? 3 : 1; if (arg_nr < nr_args - 1) { char *out_dir = args[nr_args - 1]; for (; arg_nr < nr_args - 1; arg_nr++) { char *input_fn = args[arg_nr]; (new Usage(get_file(cur_path, input_fn), step))->is_input(fout); char output_fn[MAX_PATH_SIZE]; sprintf(output_fn, "%s%s%s", out_dir, out_dir[strlen(out_dir)-1] == '/' ? "" : "/", file_of_filename(input_fn)); (new Usage(get_file(cur_path, output_fn), step))->is_output(fout); } } } else if (strcmp(exec_fn, "tar") == 0) { if (nr_args < 3 || strcmp(args[1], "xf") != 0) fprintf(fout, "ERROR: Unknown tar options\n"); else { (new Usage(get_file(cur_path, args[2]), step))->is_input(fout); } } else if (strcmp(exec_fn, "mkbuiltins") == 0) { char *extern_filename = 0; bool no_production = false; for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "-externfile") == 0) { arg = args[++arg_nr]; extern_filename = arg; File *extern_file = get_file(cur_path, arg); extern_file->loaded = true; (new Usage(extern_file, step))->is_output(fout); } else if (strcmp(arg, "-structfile") == 0) { arg = args[++arg_nr]; File *struct_file = get_file(cur_path, arg); struct_file->loaded = true; (new Usage(struct_file, step))->is_output(fout); } else if (strcmp(arg, "-noproduction") == 0) no_production = true; else { char filename[MAX_PATH_SIZE]; char full_fn[MAX_PATH_SIZE]; combine_dir_fn(cur_path, arg, full_fn); FILE *f = fopen_mapped(full_fn); if (f != 0) { fclose(f); File *file = get_file(cur_path, arg); (new Usage(file, step))->is_input(fout); if (!no_production && strcmp(file_ext(arg), "def") == 0) { char output_fn[MAX_PATH_SIZE]; strcpy(output_fn, arg); strcpy(output_fn + strlen(output_fn) - 3, "c"); File *c_file = get_file(cur_path, output_fn); (new Usage(c_file, step))->is_output(fout); file->load(fout); bool skip = true; LineInFile **ref_line = &c_file->lines; for (LineInFile *line = file->lines; line != 0; line = line->next) { if (line->text[0] == '$') { if (strncmp(line->text, "$PRODUCES", 9) == 0 || strncmp(line->text, "$END", 4) == 0) skip = false; else if (strncmp(line->text, "$SHORT_DOC", 10) == 0) skip = true; } else if (!skip) { *ref_line = new LineInFile(line); ref_line = &(*ref_line)->next; } } c_file->loaded = true; } } } } if (no_production) { File *builtins_file = get_file(cur_path, "builtins.c"); (new Usage(builtins_file, step))->is_output(fout); // Just create some line for the includes that are included builtins_file->loaded = true; LineInFile **ref_line = &builtins_file->lines; *ref_line = new LineInFile("#include \"../builtins.h\"", builtins_file, 1); ref_line = &(*ref_line)->next; char include_line[200]; sprintf(include_line, "#include \"%s\"\n", extern_filename != 0 ? extern_filename : "builtext.h"); *ref_line = new LineInFile(include_line, builtins_file, 2); } } else if (strcmp(exec_fn, "yacc") == 0) { File *input_file = 0; for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (arg[0] == '/') ; // skip options else if (strcmp(arg, "-d") == 0) ; // skip option else { input_file = get_file(cur_path, arg); (new Usage(input_file, step))->is_input(fout); } } File *output_file = get_file(cur_path, "y.tab.c"); (new Usage(output_file, step))->is_output(fout); if (input_file != 0 && input_file->loaded) { output_file->loaded = true; input_file->load(fout); LineInFile **ref_line = &output_file->lines; bool copy_line = false; for (LineInFile *line = input_file->lines; line != 0; line = line->next) { if (!copy_line && strncmp(line->text, "%{", 2) == 0) copy_line = true; else if (copy_line && strncmp(line->text, "%}", 2) == 0) copy_line = false; else if (copy_line) { fprintf(fout, "YACC: %3ld: %s\n", line->line, line->text); *ref_line = new LineInFile(line); ref_line = &(*ref_line)->next; } } } } else if (strcmp(exec_fn, "mksyntax") == 0) { File *input_file = 0; for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "-o") == 0 && arg_nr + 1 < nr_args) { arg = args[++arg_nr]; File *output_file = get_file(cur_path, arg); (new Usage(output_file, step))->is_output(fout); // Add some fake lines for processing include output_file->loaded = true; LineInFile **ref_line = &output_file->lines; *ref_line = new LineInFile("#include \"stdc.h\"", output_file, 1); ref_line = &(*ref_line)->next; *ref_line = new LineInFile("#include \"syntax.h\"", output_file, 2); } else { } } } else if (strcmp(exec_fn, "match") == 0) { result = nr_args != 3 ? -1 : strcmp(args[1], args[2]) == 0 ? 0 : 1; } else if (strcmp(exec_fn, "sed") == 0) { for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (strcmp(arg, "-i") == 0) ; else if (strcmp(arg, "-e") == 0 && arg_nr + 1 < nr_args) arg_nr++; else { File *output_file = get_file(cur_path, arg); (new Usage(output_file, step))->is_input(fout); (new Usage(output_file, step))->is_output(fout); } } } else { fprintf(fout, "INFO generic command |%s| %d (Step %d)\n", args[0], nr_args, step->nr); for (int arg_nr = 1; arg_nr < nr_args; arg_nr++) { char *arg = args[arg_nr]; if (*arg == '-') { int i = 0; indent(fout); fprintf(fout, "Switch %s\n", arg); if (strcmp(arg, "--base-address") == 0 || strcmp(arg, "--operating-system") == 0 || strcmp(arg, "--architecture") == 0) { arg_nr++; } } else { File *file = get_file(cur_path, arg); Usage *usage = new Usage(file, step); if (file->exists || file->produced) usage->is_input(fout); else usage->is_output(fout); } } } indent_depth -= 2; return result; } bool read_line(FILE *f, char *buffer) { bool result = fgets(buffer, MAX_LINE_SIZE, f); if (result) { int len = strlen(buffer); while (len > 0 && (buffer[len - 1] == '\n' || buffer[len - 1] == '\r')) buffer[--len] = '\0'; } return result; } bool is_pattern_line(const char *line) { return line[0] == ' ' || (line[0] == '-' && line[1] != '-') || line[0] == '+'; } void process_patch_file(File *patch_file, const char *cur_path, Step *step, FILE *fout) { patch_file->load(fout); if (patch_file->lines == 0) { fprintf(fout, "ERROR: Cannot open %s\n", patch_file->name); return; } LineInFile *p_line = patch_file->lines; while (p_line != 0) { if (strncmp(p_line->text, "+++ ", 4) != 0) { //fprintf(fout, "PATCH: Skip %s\n", p_line->text); p_line = p_line->next; continue; } char filename[MAX_PATH_SIZE]; int i = 0; for (const char *s = p_line->text + 4; *s != '\0' && *s != ' ' && *s != '\t'; s++) if (i < MAX_PATH_SIZE - 1) filename[i++] = *s; filename[i] = '\0'; fprintf(fout, "PATCH: File %s\n", filename); File *file = get_file(cur_path, filename); if (file->exists_now()) (new Usage(file, step))->is_modified(fout); else (new Usage(file, step))->is_output(fout); file->load(fout); LineInFile **ref_line = &file->lines; long nr = 1; p_line = p_line->next; while (p_line != 0 && strncmp(p_line->text, "@@ -", 4) == 0) { long line_nr = 0; for (const char *s = p_line->text + 4; '0' <= *s && *s <= '9'; s++) line_nr = 10 * line_nr + *s - '0'; fprintf(fout, "PATCH: Line %ld\n", line_nr); if (line_nr == 0) line_nr = 1; // Find closest matching int max_nr_matching = 0; long closest_match = -1; long match_line_nr; long try_nr = nr; for (LineInFile *try_line = *ref_line; try_line != 0 && (closest_match == -1 || try_nr < 10 + line_nr + closest_match); try_nr++, try_line = try_line->next) { bool matching = true; int nr_matching = 0; LineInFile *f_line = try_line; for (LineInFile *m_line = p_line->next; m_line != 0 && is_pattern_line(m_line->text); m_line = m_line->next) if (m_line->text[0] == '+') ; else { if (f_line != 0 && strcmp(m_line->text + 1, f_line->text) == 0) nr_matching++; else if (m_line->text[0] == '-') { matching = false; break; } f_line = f_line != 0 ? f_line->next : 0; } //fprintf(fout, "Result for %ld %d %ld\n", try_nr, matching, nr_matching); if (matching && nr_matching >= max_nr_matching) { int diff_nr = try_nr - line_nr; if (diff_nr < 0) diff_nr = -diff_nr; if (closest_match == -1 || nr_matching > max_nr_matching || diff_nr < closest_match) { closest_match = line_nr - try_nr; match_line_nr = try_nr; } max_nr_matching = nr_matching; } } if (closest_match == -1 && *ref_line == 0) { // Check if it are only additions bool matching = true; for (LineInFile *m_line = p_line->next; m_line != 0 && is_pattern_line(m_line->text); m_line = m_line->next) if (m_line->text[0] != '+') { matching = false; break; } if (matching) closest_match = nr; } if (closest_match == -1) { fprintf(fout, "ERROR: No matching for patch fragment in %ld\n", line_nr); break; } if (0 != closest_match) fprintf(fout, "INFO: %ld matched for %ld\n", match_line_nr, line_nr); while (*ref_line != 0 && nr < match_line_nr) { //fprintf(fout, "SKIP %ld %s\n", nr, (*ref_line)->text); ref_line = &(*ref_line)->next; nr++; } p_line = p_line->next; while (p_line != 0 && is_pattern_line(p_line->text)) { fprintf(fout, "PATCH: patch %c|%s|\n", p_line->text[0], p_line->text + 1); if (p_line->text[0] == '+') { LineInFile *added_line = new LineInFile(p_line, 1); added_line->next = *ref_line; *ref_line = added_line; ref_line = &added_line->next; } else { fprintf(fout, "PATCH: in |%s| %ld\n", *ref_line != 0 ? (*ref_line)->text : "[EOF]", nr); if (*ref_line != 0) { if (p_line->text[0] == '-') *ref_line = (*ref_line)->next; else ref_line = &(*ref_line)->next; nr++; } } p_line = p_line->next; } } } } void dir_of_filename(const char *fn, char *dir) { strcpy(dir, fn); char *last = 0; for (char *s = dir; *s != '\0'; s++) if (*s == '/') last = s; if (last != 0) *last = '\0'; } const char *file_of_filename(const char *full_fn) { const char *fn = full_fn; for (const char *s = full_fn; *s != '\0'; s++) if (*s == '/') fn = s + 1; return fn; } void path_of_filename(const char *full_fn, char *path) { strcpy(path, full_fn); char *last = path; for (char *s = path; *s != '\0'; s++) if (*s == '/') last = s; *last = '\0'; } FILE *open_file(const char *dir, const char *fn) { char filename[MAX_PATH_SIZE]; sprintf(filename, "%s/%s", dir, fn); //fprintf(stdout, "== '%s'\n", filename); return fopen(filename, "r"); } const char *file_ext(const char *fn) { const char *ext = ""; for (const char *s = fn; *s != '\0'; s++) if (*s == '.') ext = s + 1; else if (*s == '/') ext = ""; return ext; } bool include_source = false; void output_file(FILE *f, const char *name, bool binary) { char filename[MAX_PATH_SIZE]; if (strncmp(name, "/sysa/", 6) == 0) strcpy(filename, "../git/live-bootstrap"); else filename[0] = '\0'; strcat(filename, name); FILE *fsource = fopen(filename, "r"); if (fsource != 0) { fprintf(f, "<PRE>"); if (binary) { int i = 0; unsigned char ch = fgetc(fsource); while (!feof(fsource)) { fprintf(f, " %02X", ch); if (++i % 10 == 0) fprintf(f, "\n"); ch = fgetc(fsource); } } else { char ch = fgetc(fsource); if (ch != -1) { int col = 0; while (!feof(fsource)) { col++; if (ch == '<') fprintf(f, "<"); else if (ch == '>') fprintf(f, ">"); else if (ch == '&') fprintf(f, "&"); else if ((unsigned char)ch == 160) fprintf(f, " "); else if ((unsigned char)ch == 169) fprintf(f, "©"); else if ((unsigned char)ch == 194) fprintf(f, "Â"); else if ((unsigned char)ch == 195) fprintf(f, "Ã"); else if ((unsigned char)ch == 197) fprintf(f, "Å"); else if ((unsigned char)ch == 216) fprintf(f, "Ø"); else if ((unsigned char)ch == 231) fprintf(f, "ç"); else if ((unsigned char)ch == 246) fprintf(f, "ö"); else if (ch < 0) fprintf(f, "&#%d;", (unsigned char)ch); else if (ch == '\n' || ch == 12) { fprintf(f, "\n"); col = 0; } else if (ch == '\t') { fprintf(f, " "); while (col % 4 > 0) { fprintf(f, " "); col++; } } else if (ch < ' ') ; // skip control characters else fprintf(f, "%c", ch); ch = fgetc(fsource); } } } fprintf(f, "</PRE>"); fclose(fsource); } } void write_html_file(FILE *f, File *file, SubModule *submodules, bool binary) { //fprintf(f, "<H3><A NAME=\"F%d\" HREF=\"%s\">%s</A></H3>\n\n", file->nr, file->name, file->name); fprintf(f, "<H3><A NAME=\"F%d\">%s</A></H3>\n\n", file->nr, file->name); if (file->source != 0) { fprintf(f, "Source: <A HREF=\"%s\">%s</A>\n<P>\n", file->source->url, file->source->url); } else { bool git = false; for (SubModule *sm = submodules; sm != 0; sm = sm->next) { int len = strlen(sm->path); if (strncmp(file->name, sm->path, len) == 0) { char url[MAX_PATH_SIZE]; sprintf(url, sm->url, file->name + len); file->set_source(url); fprintf(f, "Source: <A HREF=\"#U%d\">%s</A>\n<P>\n", file->source->nr, url); git = true; break; } } if (!git) fprintf(f, "(No source)\n\n"); } if (file->exec) fprintf(f, "Executable%s\n", file->exists ? " seed!" : ""); fprintf(f, "<UL>\n"); for (Usage *usage = file->usages; usage != 0; usage = usage->next_usage) fprintf(f, "<LI>%s <A HREF=\"#S%d\">step %d</A>\n", usage->as_exec ? "Used as executable" : usage->as_input ? "Used as input" : usage->as_output ? "Produced by" : "Used in", usage->step->nr, usage->step->nr); fprintf(f, "</UL>\n\n"); if (!file->exists) { int len = strlen(file->name); if (len > 7 && strcmp(file->name + len - 7, ".tar.gz") == 0) ; // skip else if (len > 8 && strcmp(file->name + len - 8, ".tar.bz2") == 0) ; // skip else fprintf(f, "NoInput\n"); } else if (include_source) { output_file(f, file->name, binary); } } void write_html(FILE *f, SubModule *submodules) { char githash[41]; githash[0] = '\0'; { FILE *g = fopen("../git/live-bootstrap/.git/FETCH_HEAD", "r"); if (g != 0) { fgets(githash, 40, g); githash[40] = '\0'; fclose(g); } } fprintf(f, "<HTML><HEAD>\n<TITLE>live-bootstrap</TITLE>\n" "</HEAD><BODY>\n\n<H1>live-bootstrap</H1>" "<!--ONEWAY-->\n" "This page is produced by the version of the program <TT>keam_parser.cpp</TT>\n" "listed at <A HREF=\"#Parser\">the bottom</A> of this page.\n" "The program parsers the contents of GitHub repositories\n" "<A HREF=\"https://github.com/oriansj/stage0\">oriansj/stage0</A> and\n" "<A HREF=\"https://github.com/fosslinux/live-bootstrap\">fosslinux/live-bootstrap</A>\n" "(the commit <A HREF=\"https://github.com/fosslinux/live-bootstrap/commit/%s\"><TT>%.7s</TT></A>)\n" "in order to find all the dependencies and list relevant input sources.\n" "The parsing stops at the execution of <TT>run.sh</TT>.\n" "(This is still work in progress.)\n" "<P>\n" "The code displayed on this page is not copyrighted by me but by the owners of\n" "respective repositories as also mentioned in the headers of the various files.\n" "<UL>\n" "<LI><A HREF=\"#Seeds\">Binary seeds files</A>\n" "<LI><A HREF=\"#Input\">Input source files</A>\n" "<LI><A HREF=\"#Steps\">Steps</A>\n" "<LI><A HREF=\"#Output\">Output files</A>\n" "<LI><A HREF=\"#Sources\">Sources</A>\n" "<LI><A HREF=\"#Parser\">Parse program</A>\n" "</UL>\n", githash, githash); fprintf(f, "\n\n<H2><A NAME=\"Seeds\">Binary seeds files</A></H2>\n\n"); for (File *file = files; file != 0; file = file->next) if (file->exec && file->exists) write_html_file(f, file, submodules, true); fprintf(f, "<H2><A NAME=\"Input\">Input source files</A></H2>\n\n"); for (File *file = files; file != 0; file = file->next) if (!file->exec && file->used_as_input && !file->produced) write_html_file(f, file, submodules, false); fprintf(f, "\n<H2><A NAME=\"Steps\">Steps</A></H2>\n\n"); for (Step *step = all_steps; step != 0; step = step->next) { fprintf(f, "<H3><A NAME=\"S%d\">Step %d</A></H3>\n\n<UL>\n", step->nr, step->nr); for (Usage *use = step->uses; use != 0; use = use->next_use) { if (use->as_exec || use->as_input) { fprintf(f, "<LI>%s <A HREF=\"#%c%d\">%s</A>\n", use->as_exec ? "Executes" : use->as_input ? "Uses as input" : use->as_output ? "Produceses" : "Uses", use->file->produced_by != 0 ? 'S' : 'F', use->file->produced_by != 0 ? use->file->produced_by->nr : use->file->nr, use->file->name); } if (use->as_output) { fprintf(f, "<LI>Produces %s\n", use->file->name); fprintf(f, "<UL>\n"); for (Usage *usage = use->file->usages; usage != 0; usage = usage->next_usage) if (usage->as_exec || usage->as_input) fprintf(f, "<LI>%s <A HREF=\"#S%d\">step %d</A>\n", usage->as_exec ? "Used as executable" : usage->as_input ? "Used as input" : usage->as_output ? "Produced by" : usage->as_removed ? "Removed in" : "Used in", usage->step->nr, usage->step->nr); fprintf(f, "</UL>\n\n"); } if (use->as_removed) { fprintf(f, "<LI>Removed %s\n", use->file->name); } } fprintf(f, "</UL>\n\n"); } fprintf(f, "\n<H2><A NAME=\"Output\">Output files</A></H2>\n\n<UL>\n"); for (File *file = files; file != 0; file = file->next) if (file->produced && !file->removed) { bool used_as_input = false; int step_nr = -1; for (Usage *usage = file->usages; usage != 0 && !used_as_input; usage = usage->next_usage) { if (usage->as_input || usage->as_exec) used_as_input = true; if (usage->as_output) step_nr = usage->step->nr; } if (!used_as_input || file->exec) { fprintf(f, "<LI> %s", file->name); if (step_nr > 0) fprintf(f, " produced by <A HREF=\"#S%d\">step %d</A>", step_nr, step_nr); if (used_as_input) fprintf(f, " (also executed)"); fprintf(f, "\n"); } } fprintf(f, "</UL>\n\n"); fprintf(f, "\n<H2><A NAME=\"Sources\">Sources</A></H2>\n\n"); for (Source *source = sources; source != 0; source = source->next) { fprintf(f, "\n<H4><A NAME=\"U%d\" HREF=\"%s\">%s</A></H4>\n\n", source->nr, source->url, source->url); int single = source->files->next_source_file == 0; if (single) fprintf(f, "File: "); else fprintf(f, "Files:<BR>\n<UL>\n"); for (File *file = source->files; file != 0; file = file->next_source_file) { fprintf(f, "%s<A HREF=\"#F%d\">%s</A> used in:\n", single ? "" : "<LI>", file->nr, file->name); const char *comma = ""; for (Usage *usage = file->usages; usage != 0; usage = usage->next_usage) if (usage->as_input) { fprintf(f, "%s <A HREF=\"#S%d\">Step %d</A>", comma, usage->step->nr, usage->step->nr); comma = ","; } } if (!single) fprintf(f, "</UL>\n"); output_file(f, source->files->name, source->files->exec); } fprintf(f, "\n<H2><A NAME=\"Parser\">Parse program</A></H2>\n\n"); fprintf(f, "Below the version of the <TT>kaem_parser.cpp</TT> program is given that is used to produce this page.\n<P>\n"); output_file(f, "kaem_parser.cpp", false); fprintf(f, "\n\n" "<P><HR>\n" "<ADDRESS>\n" "<A HREF=\"index.html\">Home</A>\n" "</ADDRESS>\n" "</BODY></HTML>\n"); } class MakeCommand { public: char *command; MakeCommand *next; MakeCommand(const char *com) : next(0) { command = copystr(com); } }; class MakeSource; class MakeItem { public: char *name; MakeSource *sources; bool is_target; bool is_phony; bool executed; MakeCommand *commands; MakeItem *next; MakeItem(const char *n) : sources(0), is_target(false), is_phony(false), executed(false), commands(0), next(0) { name = copystr(n); } }; class MakeSource { public: MakeItem *item; MakeSource *next; MakeSource(MakeItem *i) : item(i), next(0) {} }; class MakeCommentStripIterator : public AbstractIterator { public: MakeCommentStripIterator(AbstractIterator *source_it) : _source_it(source_it) { filename = _source_it->filename; _skip_comment(); } void next() { _source_it->next(); _skip_comment(); } private: AbstractIterator *_source_it; void _skip_comment() { more = _source_it->more; ch = _source_it->ch; if (!more) return; if (ch == '#') { do { _source_it->next(); ch = _source_it->ch; } while (ch != '\0' && ch != '\n'); } more = ch != '\0'; filename = _source_it->filename; line = _source_it->line; column = _source_it->column; } }; #define EXTRACE // fprintf(stderr, "%d %d %c\n", _depth, _state, ch); class ExpandDefinesIterator : public AbstractIterator { public: ExpandDefinesIterator(AbstractIterator *source_it, Var *&vars, int depth, FILE *fout, char till = '\0') : _source_it(source_it), _vars(vars), _fout(fout), _till(till), _depth(depth) { filename = _source_it->filename; more = _source_it->more; ch = _source_it->ch; _state = 0; next(); } void next() { if (!more) { ch = '\0'; return; } switch(_state) { case 0: goto s0; case 1: goto s1; case 2: goto s2; case 3: goto s3; case 4: goto s4; case 5: goto s5; case 6: goto s6; case 7: goto s7; case 8: goto s8; case 9: goto s9; } s0: while (ch != '\0') { if (ch == '$') { _source_it_next_char(); if (ch == '(') { _source_it_next_char(); _i = 0; while (('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ch == '_') { if (_i < 100) _buffer[_i++] = ch; _source_it_next_char(); } _buffer[_i] = '\0'; _is_suffix = strcmp(_buffer, "addsuffix") == 0; if (strcmp(_buffer, "addprefix") == 0 || _is_suffix) { //fprintf(stderr, "Command %s\n", _buffer); _innerIt = new ExpandDefinesIterator(_source_it, _vars, _depth + 1, _fout, ')'); while (_innerIt->ch == ' ') _innerIt->next(); _i = 0; for (; _innerIt->ch != ','; _innerIt->next()) if (_i < 100) _buffer[_i++] = _innerIt->ch; _buffer[_i] = '\0'; if (_innerIt->ch == ',') { _innerIt->next(); } for (;;) { while (_innerIt->ch == ' ' || _innerIt->ch == '\t') { ch = _innerIt->ch; line = _innerIt->line; column = _innerIt->column; _state = 1; EXTRACE return; s1: _innerIt->next(); } if (_innerIt->ch == '\0') break; if (_is_suffix) { ch = _innerIt->ch; while (ch != '\0' && ch != ' ' && ch != '\t') { line = _innerIt->line; column = _innerIt->column; _state = 2; EXTRACE return; s2: _innerIt->next(); ch = _innerIt->ch; } for (_i = 0; _buffer[_i] != '\0'; _i++) { ch = _buffer[_i]; _state = 3; EXTRACE return; s3:; } } else { for (_i = 0; _buffer[_i] != '\0'; _i++) { ch = _buffer[_i]; _state = 4; EXTRACE return; s4:; } ch = _innerIt->ch; while (ch != '\0' && ch != ' ' && ch != '\t') { line = _innerIt->line; column = _innerIt->column; _state = 5; EXTRACE return; s5: _innerIt->next(); ch = _innerIt->ch; } } } delete _innerIt; } else { _s = (char*)""; { char *value = get_var(_vars, _buffer); if (value == 0) fprintf(_fout, "ERROR: %s.%ld define '%s' not defined\n", filename, line, _buffer); else _s = value; } //fprintf(stderr, "Substitude |%s| by |%s|\n", _buffer, _s); for (; *_s != '\0'; _s++) { ch = *_s; _state = 6; EXTRACE return; s6:; } } while (_source_it->ch != '\0' && _source_it->ch != ')') _source_it->next(); ch = _source_it->ch; if (ch == ')') _source_it->next(); ch = _source_it->ch; if (ch == _till) { ch = '\0'; more = false; } } else { _buffer[0] = ch; ch = '$'; _state = 7; EXTRACE return; s7:; if (_buffer[0] != '\0') { ch = _buffer[0]; _state = 8; EXTRACE return; s8:; _source_it_next_char(); } } _state = 0; } else { _state = 9; EXTRACE return; s9: _source_it_next_char(); } } ch = '\0'; more = false; } private: void _source_it_next_char() { _source_it->next(); ch = _source_it->ch; line = _source_it->line; column = _source_it->column; if (ch == _till) ch = '\0'; } Var *&_vars; AbstractIterator *_source_it; int _state; ExpandDefinesIterator *_innerIt; FILE *_fout; char _till; int _i; char _buffer[101]; bool _is_suffix; char *_s; MakeSource *_source_item; int _depth; }; class ToLineIterator { public: ToLineIterator(AbstractIterator *source_it) : _source_it(source_it) { more = true; next(); } bool more; void next() { if (_source_it->ch == '\0') { more = false; return; } int i = 0; for (;_source_it->ch != '\0' && _source_it->ch != '\n'; _source_it->next()) { if (i < 10000) _line[i++] = _source_it->ch; } _line[i] = '\0'; //fprintf(stdout, "%s.%ld %s\n", _source_it->filename, _source_it->line, _line); if (_source_it->ch == '\n') _source_it->next(); } char *line() { return _line; } private: AbstractIterator *_source_it; char _line[10001]; }; MakeItem *get_make_item(MakeItem *&items, const char *name) { //fprintf(stderr, "get_make_item '%s'\n", name); MakeItem **ref_item = &items; for (; *ref_item != 0; ref_item = &(*ref_item)->next) if (strcmp((*ref_item)->name, name) == 0) return *ref_item; *ref_item = new MakeItem(name); return *ref_item; } void expand_command(MakeItem *item, char *command, Var *vars, char *exp_command, FILE *fout) { if (item->name[0] == '%') { strcpy(exp_command, command); return; } char *t = exp_command; char *s_stack[40]; int depth = 0; char *s = command; for (;;) { while (*s == '\0' && depth > 0) s = s_stack[--depth]; if (*s == '\0') break; if (*s == '$') { s++; if (*s == '@') { s++; for (char *n = item->name; *n != '\0'; n++) *t++ = *n; } else if (*s == '<') { s++; if (item->sources != 0) for (char *n = item->sources->item->name; *n != '\0'; n++) *t++ = *n; } else if (*s == '^') { s++; for (MakeSource *source = item->sources; source != 0; source = source->next) { for (char *n = source->item->name; *n != '\0'; n++) *t++ = *n; if (source->next != 0) *t++ = ' '; } } else if (*s == '*') { s++; if (item->sources != 0) { char *name = item->sources->item->name; int end = strlen(name); for (int p = end - 1; p >= 0; p--) if (name[p] == '.') { end = p; break; } for (int i = 0; i < end; i++) *t++ = name[i]; } } else if (*s == '(') { s++; char var_name[50]; int i = 0; for (; *s != '\0' && *s != ')'; s++) if (i < 49) var_name[i++] = *s; var_name[i] = '\0'; if (*s == ')') s++; char *var_value = get_var(vars, var_name); if (var_value == 0) fprintf(fout, "ERROR: Did not find var |%s|\n", var_name); else { s_stack[depth++] = s; s = var_value; } } else if (*s == ' ') { s++; *t++ = '$'; *t++ = ' '; } else { fprintf(stderr, "Unknown $%c\n", *s); fprintf(fout, "ERROR: Unknown $%c in |%s|\n", *s, command); } } else *t++ = *s++; } *t = '\0'; } void expand_commands(MakeItem *item, char *commands[], Var *vars, int nr_commands, FILE *fout) { MakeCommand **ref_command = &item->commands; for (int i = 0; i < nr_commands; i++) { char exp_command[MAX_LINE_SIZE]; expand_command(item, commands[i], vars, exp_command, fout); *ref_command = new MakeCommand(exp_command); ref_command = &(*ref_command)->next; } } void make_item(MakeItem *item, const char *cur_path, bool top, FILE *fout, Step *make_step, Var *vars, MakeItem *&items) { if (item->executed) return; item->executed = true; indent(fout); fprintf(fout, "Make item: %s\n", item->name); if (!item->is_phony && top) { File *file = get_file(cur_path, item->name); if (file->produced) return; //(new Usage(get_file(cur_path, item->name), make_step))->is_output(fout); top = false; } if (item->sources == 0 && item->commands == 0) { indent(fout); fprintf(fout, "- Input: %s\n", item->name); } for (MakeSource *source = item->sources; source != 0; source = source->next) { indent_depth += 4; make_item(source->item, cur_path, top, fout, make_step, vars, items); indent_depth -= 4; } for (MakeCommand *command = item->commands; command != 0; command = command->next) { indent(fout); fprintf(fout, "- Execute: %s\n", command->command); parse_command(cur_path, command->command, fout); } } class TheFileIterator : public AbstractIterator { public: TheFileIterator(File *f) { more = false; while (f != 0) { _loaded = f->loaded; if (_loaded) { _line = f->lines; _s = _line != 0 ? _line->text : 0; more = _s != 0; break; } else { _f = fopen_mapped(f->name); if (_f != 0) { more = true; break; } } f = f->copy_from; } if (!more) { fprintf(stdout, "File %s is empty %p\n", f->name, _line); ch = '\0'; return; } filename = f->name; ch = '\n'; line = 0; column = 0; next(); } void next() { if (!more) return; column++; if (_loaded) { if (ch == '\n' && _line != 0) { filename = _line->file->name; line = _line->line; column = 1; } if (_s == 0) { more = false; ch = '\0'; } else if (*_s == '\0') { ch = '\n'; _line = _line->next; _s = _line != 0 ? _line->text : 0; } else ch = *_s++; } else { if (ch == '\n') { line++; column = 1; } ch = fgetc(_f); more = !feof(_f); if (!more) { ch = '\0'; fclose(_f); _f = 0; } else if (ch == '\r') next(); } } private: bool _loaded; LineInFile *_line; const char *_s; FILE *_f; }; #define ON_LINE // fprintf(stderr, "%d: ", __LINE__); void remove_escapes(const char *in, char *out) { bool in_string = false; for (; *in != '\0'; in++) { if (*in == '\\') { in++; if (*in == ' ' && !in_string) *out++ ='\\'; if (*in == '"') in_string = !in_string; } *out++ = *in; } *out = '\0'; } void parse_a_make_file(const char *cur_path, File *file, Var *&vars, MakeItem *&items, Step *step, FILE *fout) { fprintf(fout, "PARSE: %s\n", file->name); TheFileIterator *fileIter = new TheFileIterator(file); MakeCommentStripIterator *commentStripIterator = new MakeCommentStripIterator(fileIter); LineSpliceIterator *lineSpliceIterator = new LineSpliceIterator(commentStripIterator); ExpandDefinesIterator *expandDefinesIterator = new ExpandDefinesIterator(lineSpliceIterator, vars, 1, fout); //ToLineIterator toLineIterator(commentStripIterator); ToLineIterator toLineIterator(expandDefinesIterator); bool in_if = false; bool skip = false; while (toLineIterator.more) { char *line = toLineIterator.line(); if (in_if && *line == ' ') line++; char *s = line; //fprintf(stderr, "Process |%s|\n", s); if (*s == '\0') { toLineIterator.next(); continue; } if (*s == ' ') { toLineIterator.next(); fprintf(fout, "ERROR: Skipping line with spaces here\n"); continue; } char target[101]; if (strncmp(s, "ifeq ", 5) == 0) { s += 5; while (*s == ' ' || *s == '\t') s++; if (*s == '(') s++; char arg1[50]; int i = 0; for (; *s != '\0' && *s != ','; s++) if (i < 49) arg1[i++] = *s; arg1[i] = '\0'; if (*s == ',') s++; char arg2[50]; i = 0; for (; *s != '\0' && *s != ','; s++) if (i < 49) arg2[i++] = *s; arg2[i] = '\0'; in_if = true; skip = strcmp(arg1, arg2) != 0; toLineIterator.next(); } else if (strncmp(s, "else", 4) == 0) { skip = !skip; toLineIterator.next(); } else if (strncmp(s, "endif", 5) == 0) { in_if = false; toLineIterator.next(); } else if (skip) toLineIterator.next(); // skip else if (strncmp(s, "include ", 8) == 0) { s += 7; while (*s == ' ' || *s == '\t' || *s == ':') s++; File *include_file = get_file(cur_path, s); (new Usage(include_file, step))->is_input(fout); fprintf(fout, "PARSE include |%s|\n", s); char include_cur_path[MAX_PATH_SIZE]; path_of_filename(include_file->name, include_cur_path); parse_a_make_file(include_cur_path, include_file, vars, items, step, fout); toLineIterator.next(); } else if (strncmp(s, ".SUFFIXES", 9) == 0) { // Skip the line toLineIterator.next(); } else if (strncmp(s, ".PHONY", 6) == 0 && (s[6] == ' ' || s[6] == '\t' || s[6] == ':')) { s += 7; while (*s == ' ' || *s == '\t' || *s == ':') s++; while (*s != '\0') { int i = 0; for (; *s != '\0' && *s != ' ' && *s != '\t'; s++) if (i < 100) target[i++] = *s; target[i] = '\0'; ON_LINE get_make_item(items, target)->is_phony = true; while (*s == ' ' || *s == '\t') s++; } toLineIterator.next(); } else if (strncmp(s, ".c.o", 4) == 0 && (s[4] == ' ' || s[4] == '\t' || s[4] == ':')) { s += 4; while (*s != '\0' && *s != ';') s++; if (*s == ';') s++; while (*s == ' ' || *s == '\t') s++; if (*s != '\0') vars = new Var(".c.o", s, false, vars); toLineIterator.next(); } else { bool is_define = false; int i = 0; while (('A' <= *s && *s <= 'Z') || ('a' <= *s && *s <= 'z') || *s == '_') if (i < 100) target[i++] = *s++; target[i] = '\0'; while (*s == ' ' || *s == '\t') s++; if (*s == '=') { s++; while (*s == ' ' || *s == '\t') s++; Var *prev_dev = get_var_var(vars, target); if (prev_dev == 0 || !prev_dev->override) { char value[MAX_LINE_SIZE]; remove_escapes(s, value); vars = new Var(target, value, false, vars); } //fprintf(stderr, "INFO '%s' = '%s'\n", target, s); toLineIterator.next(); } else if (s[0] == '+' && s[1] == '=') { s += 2; while (*s == ' ' || *s == '\t') s++; Var *var = get_var_var(vars, target); if (var != 0) { char value[MAX_LINE_SIZE]; sprintf(value, "%s ", var->value); remove_escapes(s, value + strlen(value)); var->value = copystr(value); } toLineIterator.next(); } else if (strstr(line, ":") != 0) { MakeSource *targets = 0; MakeSource **ref_target = &targets; s = line; while (*s != ':') { i = 0; for (; *s != ':' && *s != ' ' && *s != '\t'; s++) if (i < 100) target[i++] = *s; target[i] = '\0'; ON_LINE *ref_target = new MakeSource(get_make_item(items, target)); ref_target = &(*ref_target)->next; while (*s == ' ' || *s == '\t') s++; } s++; while (*s == ' ' || *s == '\t') s++; MakeSource *sources = 0; MakeSource **ref_source = &sources; char *pattern = 0; if (strstr(s, ":") != 0) { pattern = copystr(s); } else { while (*s != '\0') { i = 0; for (; *s != '\0' && *s != ' ' && *s != '\t'; s++) if (i < 100) target[i++] = *s; target[i] = '\0'; ON_LINE *ref_source = new MakeSource(get_make_item(items, target)); ref_source = &(*ref_source)->next; while (*s == ' ' || *s == '\t') s++; } } // read the commands char *commands[20]; int nr_commands = 0; toLineIterator.next(); while (toLineIterator.more && (toLineIterator.line()[0] == ' ' || toLineIterator.line()[0] == '\t')) { //fprintf(stderr, "Command |%s|\n", toLineIterator.line()); char *s = toLineIterator.line(); while (*s == ' ' || *s == '\t') s++; if (nr_commands < 10) commands[nr_commands++] = copystr(s); toLineIterator.next(); } if (pattern != 0) { //fprintf(stderr, "pattern: |%s|\n", pattern); for (MakeSource *make_target = targets; make_target != 0; make_target = make_target->next) { char *s = pattern; i = 0; for (; *s != ':' && *s != ' ' && *s != '\t'; s++) if (*s == '%') { for (char *s2 = make_target->item->name; *s2 != '\0'; s2++) if (i < 100) target[i++] = *s2; } else if (i < 100) target[i++] = *s; target[i] = '\0'; while (*s == ' ' || *s == '\t') s++; if (*s == ':') s++; ON_LINE MakeItem *target_item = get_make_item(items, target); MakeSource **ref_make_source = &target_item->sources; for (;;) { while (*s == ' ' || *s == '\t') s++; if (*s == '\0') break; i = 0; for (; *s != '\0' && *s != ' ' && *s != '\t'; s++) if (*s == '%') { for (char *s2 = make_target->item->name; *s2 != '\0'; s2++) if (i < 100) target[i++] = *s2; } else if (i < 100) target[i++] = *s; target[i] = '\0'; ON_LINE *ref_make_source = new MakeSource(get_make_item(items, target)); ref_make_source = &(*ref_make_source)->next; } expand_commands(target_item, commands, vars, nr_commands, fout); } } else { for (MakeSource *make_target = targets; make_target != 0; make_target = make_target->next) { make_target->item->sources = sources; expand_commands(make_target->item, commands, vars, nr_commands, fout); } } } else { fprintf(fout, "ERROR: %s unknown: %s\n", expandDefinesIterator->filename, line); toLineIterator.next(); } } } } void parse_make_file(const char *cur_path, File *file, char **args, int nr_args, Var *vars, Step *step, FILE *fout) { //fprintf(stderr, "parse_make_file %s %s\n", cur_path, file->name); FILE *make_f = 0; if (file == 0) { char makefile_fn[MAX_PATH_SIZE]; combine_dir_fn(cur_path, "Makefile", makefile_fn); file = find_file(makefile_fn); if (file == 0) { make_f = fopen(makefile_fn, "r"); if (make_f != 0) file = get_file(makefile_fn); else { combine_dir_fn(cur_path, "makefile", makefile_fn); file = find_file(makefile_fn); if (file == 0) { make_f = fopen(makefile_fn, "r"); if (make_f != 0) file = get_file(makefile_fn); } } } } if (file == 0) return; (new Usage(file, step))->is_input(fout); while (file->copy_from != 0) file = file->copy_from; if (make_f == 0) make_f = fopen(file->name, "r"); if (make_f == 0) { fprintf(fout, "ERROR: Cannot open file %s\n", file->name); return; } fclose(make_f); MakeItem *items = 0; vars = new Var("CC", "cc", false, vars); vars = new Var("YACC", "yacc", false, vars); vars = new Var("YFLAGS", "/Define/Verbose", false, vars); vars = new Var("LEX", "lex", false, vars); vars = new Var("CPPFLAGS", "", false, vars); vars = new Var("CFLAGS", "", false, vars); vars = new Var("TARGET_ARCH", "", false, vars); vars = new Var("DESTDIR", "", false, vars); vars = new Var("PREFIX", "/usr", false, vars); vars = new Var("CPPFLAGS", "", false, vars); vars = new Var("WARN", "", false, vars); vars = new Var("ROOT", "", false, vars); vars = new Var("INSTALL", "install", false, vars); vars = new Var("LIBDIR", "/usr/lib", false, vars); vars = new Var("MANDIR", "/usr/man", false, vars); vars = new Var("LIBS", "", false, vars); vars = new Var("STRIP", "strip", false, vars); //vars = new Var("COMPILE.c", "$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c", false, vars); //vars = new Var("OUTPUT_OPTION", "-o $@", false, vars); vars = new Var(".c.o", "$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $<", false, vars); vars = new Var(".y.c", "$(YACC) $(YFLAGS) $< \n mv -f y.tab.c $@", false, vars); vars = new Var(".l.c", "$(LEX) $(LFLAGS) -t $< \n mv lexyy.c $@", false, vars); bool is_install = false; for (int i = 0; i < nr_args; i++) { char arg[100]; int j = 0; for (char *s = args[i]; *s != '\0'; s++) { if (*s == '\\' && s[1] != '\0') s++; if (j < 99) arg[j++] = *s; } arg[j] = '\0'; //fprintf(stdout, "Make arg %s\n", arg); char *s = strstr(arg, "="); if (s != 0) { *s++ = '\0'; char value[201]; if (*s == '"') { int i = 0; for (s++; *s != '\0' && *s != '"'; s++) { if (*s == '\\') s++; if (i < 200) value[i++] = *s; } value[i] = '\0'; } else strcpy(value, s); vars = new Var(arg, value, false, vars); vars->override = true; } else get_make_item(items, arg)->is_target = true; } if (items == 0) get_make_item(items, "all")->is_target = true; fprintf(fout, "PARSE %s\n", file->name); parse_a_make_file(cur_path, file, vars, items, step, fout); // Expand possible for (MakeItem *item = items; item != 0; item = item->next) { if (item->sources == 0 || item->commands == 0) { // See if there is a matching rule: bool wildcard_match = false; for (MakeItem *item1 = items; item1 != 0; item1 = item1->next) if (item1->name[0] == '%') { fprintf(fout, "TRY |%s|\n", item1->name); int len = strlen(item->name); int offset = len - strlen(item1->name) + 1; if (offset > 0 && strcmp(item->name + offset, item1->name + 1) == 0) { MakeSource **ref_source = &item->sources; for (MakeSource *source1 = item1->sources; source1 != 0; source1 = source1->next) { char exp_source[MAX_PATH_SIZE]; int i = 0; for (char *s = source1->item->name; *s != '\0'; s++) if (*s == '%') { for (int j = 0; j < offset; j++) exp_source[i++] = item->name[j]; } else exp_source[i++] = *s; exp_source[i] = '\0'; fprintf(fout, "Added expanded source: |%s|\n", exp_source); *ref_source = new MakeSource(get_make_item(items, exp_source)); ref_source = &(*ref_source)->next; } MakeCommand **ref_command = &item->commands; for (MakeCommand *command1 = item1->commands; command1 != 0; command1 = command1->next) { char exp_command[MAX_LINE_SIZE]; expand_command(item, command1->command, vars, exp_command, fout); fprintf(fout, "Added expanded command: |%s|%s|\n", command1->command, exp_command); *ref_command = new MakeCommand(exp_command); ref_command = &(*ref_command)->next; } wildcard_match = true; break; } } if (!wildcard_match && item->commands == 0 && !file_exists(cur_path, item->name)) { fprintf(fout, "SEEK sources for %s\n", item->name); char *alt_rule = 0; if (strcmp(file_ext(item->name), "o") == 0) { char c_filename[MAX_LINE_SIZE]; strcpy(c_filename, item->name); c_filename[strlen(c_filename) - 1] = 'c'; bool included = false; for (MakeSource *source = item->sources; source != 0; source = source->next) if (strcmp(c_filename, source->item->name) == 0) { included = true; break; } if (!included) { MakeSource *old_sources = item->sources; item->sources = new MakeSource(get_make_item(items, c_filename)); item->sources->next = old_sources; } alt_rule = get_var(vars, ".c.o"); /* if (oc_rule != 0) strcpy(cc_command, oc_rule); else { //(new Usage(get_file(cur_path, c_file), make_step))->is_input(fout); char *cc_def = get_var(vars, "CC"); char *cppflags_def = get_var(vars, "CPPFLAGS"); char *cflags_def = get_var(vars, "CFLAGS"); if (cc_def != 0 && cflags_def != 0) sprintf(cc_command, "%s %s %s $^ -o %s\n", cc_def, cppflags_def, cflags_def, item->name); } */ } else if (strcmp(file_ext(item->name), "c") == 0) { char yl_filename[MAX_LINE_SIZE]; ///combine_dir_fn(cur_path, item->name, comb_fn); strcpy(yl_filename, item->name); yl_filename[strlen(yl_filename) - 1] = 'l'; if (file_exists(cur_path, yl_filename)) { if (item->sources == 0) { MakeItem *l_item = get_make_item(item, yl_filename); item->sources = new MakeSource(l_item); } alt_rule = get_var(vars, ".l.c"); } else { yl_filename[strlen(yl_filename) - 1] = 'y'; if (file_exists(cur_path, yl_filename)) { if (item->sources == 0) { MakeItem *y_item = get_make_item(item, yl_filename); item->sources = new MakeSource(y_item); } alt_rule = get_var(vars, ".y.c"); } } if (alt_rule != 0) fprintf(fout, "INFO: command for %s is |%s|\n", item->name, alt_rule); } if (alt_rule != 0) { MakeCommand **ref_command = &item->commands; for (char *s = alt_rule; s != 0;) { char *ns = strstr(s, "\n"); if (ns != 0) *ns++ = '\0'; char exp_cc_command[MAX_LINE_SIZE]; expand_command(item, s, vars, exp_cc_command, fout); fprintf(fout, "Expand |%s|\n into |%s|\n", s, exp_cc_command); indent(fout); fprintf(fout, "- Execute: %s\n", exp_cc_command); *ref_command = new MakeCommand(exp_cc_command); ref_command = &(*ref_command)->next; s = 0; if (ns != 0) { while (*ns == ' ' || *ns == '\t') ns++; if (*ns != '\0') s = ns; } } } } } } for (MakeItem *item = items; item != 0 && item->is_target; item = item->next) make_item(item, cur_path, true, fout, step, vars, items); } class LocalIncludeResolver : public AbstractIncludePathResolver { public: LocalIncludeResolver(const char *cur_path, char *c_file_path, const char **includes, int nr_includes, Define *&defines, Step *step, FILE *fout) : _cur_path(cur_path), _c_file_path(c_file_path), _nr_includes(nr_includes), _defines(defines), _step(step), _fout(fout) { for (int i = 0; i < _nr_includes; i++) { combine_dir_fn(_cur_path, includes[i], _includes[i]); int len = strlen(_includes[i]); if (len > 0 && _includes[i][len-1] == '/') _includes[i][len-1] = '\0'; fprintf(_fout, "Info: Include '%s'\n", _includes[i]); } } AbstractIterator *iteratorFor(const char *path, bool include_next, AbstractIterator *context) { if (debug_c_parsing) fprintf(_fout, "INCLUDE %s.%ld |%s|\n", context->filename, context->line, path); char path2[MAX_PATH_SIZE]; strcpy(path2, path + 1); char include_fn[MAX_PATH_SIZE]; FILE *f = 0; File *file = 0; if ( (path[0] == '<' && path2[strlen(path2) - 1] == '>') || (path[0] == '"' && path2[strlen(path2) - 1] == '"')) { path2[strlen(path2) - 1] = '\0'; if (include_next) { int diff_len = strlen(context->filename) - strlen(path2); //if (diff_len < 0 || strcmp(context->filename + diff_len, path2) != 0) fprintf(stderr, "include_next %s %s\n", context->filename, path2); return new StringIterator("","unknown"); } if (path[0] == '"') { combine_dir_fn(_c_file_path, path2, include_fn); file = find_file(include_fn); if (file != 0) { while (file->copy_from) file = file->copy_from; f = fopen_mapped(file->name, _fout); } else f = fopen_mapped(include_fn, _fout); } for (int i = 0; i < _nr_includes && (file == 0 && f == 0); i++) { combine_dir_fn(_includes[i], path2, include_fn); file = find_file(include_fn); if (file != 0) { while (file->copy_from) file = file->copy_from; f = fopen_mapped(file->name, _fout); } else f = fopen_mapped(include_fn, _fout); } } if (file != 0 || f != 0) { while (f == 0 && file->copy_from != 0) { file = file->copy_from; f = fopen_mapped(file->name, _fout); } if (f != 0) { fclose(f); if (file == 0) file = get_file(include_fn); if (_step->hasInputUseOf(file)) return new StringIterator("","unknown"); // Lets process include file once (new Usage(file, _step))->is_input(_fout); return new InlineIncludesIterator( new CommentStripIterator( new LineSpliceIterator( new TrigraphIterator( new TheFileIterator(file)))), this, _defines); } else fprintf(_fout, "Info: %s.%ld Cannot parse include file %s\n", context->filename, context->line, path); } else fprintf(_fout, "Warning: %s.%ld Cannot parse include file %s\n", context->filename, context->line, path); return new StringIterator("","unknown"); } private: const char *_cur_path; const char *_c_file_path; char _includes[10][MAX_PATH_SIZE]; int _nr_includes; Define *&_defines; Step *_step; FILE *_fout; int _nested_level; struct { const char *file_name; int at; } _nested[100]; }; void parse_c_file(const char *cur_path, File *file, const char **includes, int nr_includes, Define *&defines, Step *step, FILE *fout) { char c_file_path[MAX_PATH_SIZE]; path_of_filename(file->name, c_file_path); if (!file->exists && !file->loaded) { file = file->existing(); if (file == 0) { fprintf(fout, "ERROR: file does not exist\n"); return; } fprintf(fout, "Info: Copied from %s\n", file->name); } TheFileIterator *fileIterator = new TheFileIterator(file); TrigraphIterator *trigraphIterator = new TrigraphIterator(fileIterator); LineSpliceIterator *lineSpliceIterator = new LineSpliceIterator(trigraphIterator); CommentStripIterator *commentStripIterator = new CommentStripIterator(lineSpliceIterator); LocalIncludeResolver includeResolver(cur_path, c_file_path, includes, nr_includes, defines, step, fout); InlineIncludesIterator inlineIncludesIterator(commentStripIterator, &includeResolver, defines); //if (step->nr != 371) //return; //FILE *pp_out = step->nr == 371 ? fopen("out3.txt", "w") : 0; //FILE *pp_out = strcmp(fn, "../git/live-bootstrap/sysa/tcc-0.9.26/build/mes-0.24.2/lib/linux/access.c") == 0 ? fopen("out3.txt", "w") : 0; FILE *pp_out = 0;//strcmp(fn, "../git/live-bootstrap/sysa/make-3.82/build/make-3.82/getopt.c") == 0 ? fopen("out3.txt", "w") : 0; const char *prev_file = ""; while (inlineIncludesIterator.more) { //fprintf(fout, "%s %ld.%ld\n", inlineIncludesIterator.filename, inlineIncludesIterator.line, inlineIncludesIterator.column); inlineIncludesIterator.next(); if (pp_out != 0) { if (strcmp(prev_file, inlineIncludesIterator.filename) != 0) { fprintf(pp_out, "#inline '%s':%ld\n", inlineIncludesIterator.filename, inlineIncludesIterator.line); prev_file = inlineIncludesIterator.filename; } fprintf(pp_out, "%c", inlineIncludesIterator.ch); } } if (pp_out != 0) fclose(pp_out); }