diff options
Diffstat (limited to 'linux/fs/exec.c')
| -rw-r--r-- | linux/fs/exec.c | 306 |
1 files changed, 306 insertions, 0 deletions
diff --git a/linux/fs/exec.c b/linux/fs/exec.c new file mode 100644 index 0000000..e9d5058 --- /dev/null +++ b/linux/fs/exec.c @@ -0,0 +1,306 @@ +#include <errno.h> +#include <sys/stat.h> +#include <a.out.h> + +#include <linux/fs.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <asm/segment.h> + +extern int sys_exit(int exit_code); +extern int sys_close(int fd); + +/* + * MAX_ARG_PAGES defines the number of pages allocated for arguments + * and envelope for the new program. 32 should suffice, this gives + * a maximum env+arg of 128kB ! + */ +#define MAX_ARG_PAGES 32 + +#define cp_block(from,to) \ +__asm__("pushl $0x10\n\t" \ + "pushl $0x17\n\t" \ + "pop %%es\n\t" \ + "cld\n\t" \ + "rep\n\t" \ + "movsl\n\t" \ + "pop %%es" \ + ::"c" (BLOCK_SIZE/4),"S" (from),"D" (to) \ + :"cx","di","si") + +/* + * read_head() reads blocks 1-6 (not 0). Block 0 has already been + * read for header information. + */ +int read_head(struct m_inode * inode,int blocks) +{ + struct buffer_head * bh; + int count; + + if (blocks>6) + blocks=6; + for(count = 0 ; count<blocks ; count++) { + if (!inode->i_zone[count+1]) + continue; + if (!(bh=bread(inode->i_dev,inode->i_zone[count+1]))) + return -1; + cp_block(bh->b_data,count*BLOCK_SIZE); + brelse(bh); + } + return 0; +} + +int read_ind(int dev,int ind,long size,unsigned long offset) +{ + struct buffer_head * ih, * bh; + unsigned short * table,block; + + if (size<=0) + panic("size<=0 in read_ind"); + if (size>512*BLOCK_SIZE) + size=512*BLOCK_SIZE; + if (!ind) + return 0; + if (!(ih=bread(dev,ind))) + return -1; + table = (unsigned short *) ih->b_data; + while (size>0) { + if (block=*(table++)) + if (!(bh=bread(dev,block))) { + brelse(ih); + return -1; + } else { + cp_block(bh->b_data,offset); + brelse(bh); + } + size -= BLOCK_SIZE; + offset += BLOCK_SIZE; + } + brelse(ih); + return 0; +} + +/* + * read_area() reads an area into %fs:mem. + */ +int read_area(struct m_inode * inode,long size) +{ + struct buffer_head * dind; + unsigned short * table; + int i,count; + + if ((i=read_head(inode,(size+BLOCK_SIZE-1)/BLOCK_SIZE)) || + (size -= BLOCK_SIZE*6)<=0) + return i; + if ((i=read_ind(inode->i_dev,inode->i_zone[7],size,BLOCK_SIZE*6)) || + (size -= BLOCK_SIZE*512)<=0) + return i; + if (!(i=inode->i_zone[8])) + return 0; + if (!(dind = bread(inode->i_dev,i))) + return -1; + table = (unsigned short *) dind->b_data; + for(count=0 ; count<512 ; count++) + if ((i=read_ind(inode->i_dev,*(table++),size, + BLOCK_SIZE*(518+count))) || (size -= BLOCK_SIZE*512)<=0) + return i; + panic("Impossibly long executable"); +} + +/* + * create_tables() parses the env- and arg-strings in new user + * memory and creates the pointer tables from them, and puts their + * addresses on the "stack", returning the new stack pointer value. + */ +static unsigned long * create_tables(char * p,int argc,int envc) +{ + unsigned long *argv,*envp; + unsigned long * sp; + + sp = (unsigned long *) (0xfffffffc & (unsigned long) p); + sp -= envc+1; + envp = sp; + sp -= argc+1; + argv = sp; + put_fs_long((unsigned long)envp,--sp); + put_fs_long((unsigned long)argv,--sp); + put_fs_long((unsigned long)argc,--sp); + while (argc-->0) { + put_fs_long((unsigned long) p,argv++); + while (get_fs_byte(p++)) /* nothing */ ; + } + put_fs_long(0,argv); + while (envc-->0) { + put_fs_long((unsigned long) p,envp++); + while (get_fs_byte(p++)) /* nothing */ ; + } + put_fs_long(0,envp); + return sp; +} + +/* + * count() counts the number of arguments/envelopes + */ +static int count(char ** argv) +{ + int i=0; + char ** tmp; + + if (tmp = argv) + while (get_fs_long((unsigned long *) (tmp++))) + i++; + + return i; +} + +/* + * 'copy_string()' copies argument/envelope strings from user + * memory to free pages in kernel mem. These are in a format ready + * to be put directly into the top of new user memory. + */ +static unsigned long copy_strings(int argc,char ** argv,unsigned long *page, + unsigned long p) +{ + int len,i; + char *tmp; + + while (argc-- > 0) { + if (!(tmp = (char *)get_fs_long(((unsigned long *) argv)+argc))) + panic("argc is wrong"); + len=0; /* remember zero-padding */ + do { + len++; + } while (get_fs_byte(tmp++)); + if (p-len < 0) /* this shouldn't happen - 128kB */ + return 0; + i = ((unsigned) (p-len)) >> 12; + while (i<MAX_ARG_PAGES && !page[i]) { + if (!(page[i]=get_free_page())) + return 0; + i++; + } + do { + --p; + if (!page[p/PAGE_SIZE]) + panic("nonexistent page in exec.c"); + ((char *) page[p/PAGE_SIZE])[p%PAGE_SIZE] = + get_fs_byte(--tmp); + } while (--len); + } + return p; +} + +static unsigned long change_ldt(unsigned long text_size,unsigned long * page) +{ + unsigned long code_limit,data_limit,code_base,data_base; + int i; + + code_limit = text_size+PAGE_SIZE -1; + code_limit &= 0xFFFFF000; + data_limit = 0x4000000; + code_base = get_base(current->ldt[1]); + data_base = code_base; + set_base(current->ldt[1],code_base); + set_limit(current->ldt[1],code_limit); + set_base(current->ldt[2],data_base); + set_limit(current->ldt[2],data_limit); +/* make sure fs points to the NEW data segment */ + __asm__("pushl $0x17\n\tpop %%fs"::); + data_base += data_limit; + for (i=MAX_ARG_PAGES-1 ; i>=0 ; i--) { + data_base -= PAGE_SIZE; + if (page[i]) + put_page(page[i],data_base); + } + return data_limit; +} + +/* + * 'do_execve()' executes a new program. + */ +int do_execve(unsigned long * eip,long tmp,char * filename, + char ** argv, char ** envp) +{ + struct m_inode * inode; + struct buffer_head * bh; + struct exec ex; + unsigned long page[MAX_ARG_PAGES]; + int i,argc,envc; + unsigned long p; + + if ((0xffff & eip[1]) != 0x000f) + panic("execve called from supervisor mode"); + for (i=0 ; i<MAX_ARG_PAGES ; i++) /* clear page-table */ + page[i]=0; + if (!(inode=namei(filename))) /* get executables inode */ + return -ENOENT; + if (!S_ISREG(inode->i_mode)) { /* must be regular file */ + iput(inode); + return -EACCES; + } + i = inode->i_mode; + if (current->uid && current->euid) { + if (current->euid == inode->i_uid) + i >>= 6; + else if (current->egid == inode->i_gid) + i >>= 3; + } else if (i & 0111) + i=1; + if (!(i & 1)) { + iput(inode); + return -ENOEXEC; + } + if (!(bh = bread(inode->i_dev,inode->i_zone[0]))) { + iput(inode); + return -EACCES; + } + ex = *((struct exec *) bh->b_data); /* read exec-header */ + brelse(bh); + if (N_MAGIC(ex) != ZMAGIC || ex.a_trsize || ex.a_drsize || + ex.a_text+ex.a_data+ex.a_bss>0x3000000 || + inode->i_size < ex.a_text+ex.a_data+ex.a_syms+N_TXTOFF(ex)) { + iput(inode); + return -ENOEXEC; + } + if (N_TXTOFF(ex) != BLOCK_SIZE) + panic("N_TXTOFF != BLOCK_SIZE. See a.out.h."); + argc = count(argv); + envc = count(envp); + p = copy_strings(envc,envp,page,PAGE_SIZE*MAX_ARG_PAGES-4); + p = copy_strings(argc,argv,page,p); + if (!p) { + for (i=0 ; i<MAX_ARG_PAGES ; i++) + free_page(page[i]); + iput(inode); + return -1; + } +/* OK, This is the point of no return */ + for (i=0 ; i<32 ; i++) + current->sig_fn[i] = NULL; + for (i=0 ; i<NR_OPEN ; i++) + if ((current->close_on_exec>>i)&1) + sys_close(i); + current->close_on_exec = 0; + free_page_tables(get_base(current->ldt[1]),get_limit(0x0f)); + free_page_tables(get_base(current->ldt[2]),get_limit(0x17)); + if (last_task_used_math == current) + last_task_used_math = NULL; + current->used_math = 0; + p += change_ldt(ex.a_text,page)-MAX_ARG_PAGES*PAGE_SIZE; + p = (unsigned long) create_tables((char *)p,argc,envc); + current->brk = ex.a_bss + + (current->end_data = ex.a_data + + (current->end_code = ex.a_text)); + current->start_stack = p & 0xfffff000; + i = read_area(inode,ex.a_text+ex.a_data); + iput(inode); + if (i<0) + sys_exit(-1); + i = ex.a_text+ex.a_data; + while (i&0xfff) + put_fs_byte(0,(char *) (i++)); + eip[0] = ex.a_entry; /* eip, magic happens :-) */ + eip[3] = p; /* stack pointer */ + return 0; +} |
