From 3005ac33860fd714991272e0e97b7337b2c5de73 Mon Sep 17 00:00:00 2001 From: Liu Aleaxander Date: Thu, 6 Aug 2009 17:27:02 +0800 Subject: [PATCH] Core:PXELINUX: pxelinux derivative merged succeesfully So, now we have all the fs converted, and the rest is to make it better and more simple --- core/Makefile | 6 ++-- core/cache.c | 2 +- core/dhcp_option.c | 30 ++++++++++---------- core/diskio.c | 15 +++++++--- core/extern.inc | 3 ++ core/fat.c | 2 +- core/fs.c | 10 +++---- core/include/core.h | 51 ++++++++++++++++++++++++++++++++++ core/include/disk.h | 2 +- core/include/pxe.h | 56 +------------------------------------ core/pxe.c | 80 ++++++++++++++++++++++++----------------------------- core/pxelinux.asm | 5 ++-- 12 files changed, 132 insertions(+), 130 deletions(-) diff --git a/core/Makefile b/core/Makefile index b690ef3..4711c7d 100644 --- a/core/Makefile +++ b/core/Makefile @@ -32,10 +32,10 @@ INCLUDES = -I./include -I$(com32)/include CODEPAGE = cp865 # The targets to build in this directory... -BTARGET = kwdhash.gen pxelinux.0 - #extlinux.bin extlinux.bss extlinux.sys \ +BTARGET = kwdhash.gen \ + extlinux.bin extlinux.bss extlinux.sys \ ldlinux.bss ldlinux.sys ldlinux.bin \ - pxelinux.0 isolinux.bin isolinux-debug.bin + isolinux.bin isolinux-debug.bin pxelinux.0 # All primary source files for the main syslinux files NASMSRC := $(wildcard *.asm) diff --git a/core/cache.c b/core/cache.c index 4be99bb..c55f160 100644 --- a/core/cache.c +++ b/core/cache.c @@ -106,7 +106,7 @@ struct cache_struct* get_cache_block(struct device *dev, block_t block) /* store it at the head of real cache */ cs = head->next; cs->block = block; - getoneblk(cs->data, block, dev->cache_block_size); + getoneblk(dev->disk, cs->data, block, dev->cache_block_size); missed ++; } diff --git a/core/dhcp_option.c b/core/dhcp_option.c index f9ff4f2..d9d913f 100644 --- a/core/dhcp_option.c +++ b/core/dhcp_option.c @@ -4,21 +4,23 @@ #include #include -void subnet_mask(void *data, int opt_len) +void parse_dhcp_options(void *, int, int); + +static void subnet_mask(void *data, int opt_len) { if (opt_len != 4) return; Netmask = *(uint32_t *)data; } -void router(void *data, int opt_len) +static void router(void *data, int opt_len) { if (opt_len != 4) return; Gateway = *(uint32_t *)data; } -void dns_servers(void *data, int opt_len) +static void dns_servers(void *data, int opt_len) { int num = opt_len >> 2; int i; @@ -35,7 +37,7 @@ void dns_servers(void *data, int opt_len) LastDNSServer = OFFS_WRT(&DNSServers[num - 1], 0); } -void local_domain(void *data, int opt_len) +static void local_domain(void *data, int opt_len) { com32sys_t regs; char *p = (char *)data + opt_len; @@ -49,13 +51,13 @@ void local_domain(void *data, int opt_len) *p = end; /* Resotre ending byte */ } -void vendor_encaps(void *data, int opt_len) +static void vendor_encaps(void *data, int opt_len) { /* Only recongnize PXELINUX options */ parse_dhcp_options(data, opt_len, 208); } -void option_overload(void *data, int opt_len) +static void option_overload(void *data, int opt_len) { if (opt_len != 1) return; @@ -63,7 +65,7 @@ void option_overload(void *data, int opt_len) } -void server(void *data, int opt_len) +static void server(void *data, int opt_len) { uint32_t ip; @@ -78,7 +80,7 @@ void server(void *data, int opt_len) ServerIP = ip; } -void client_identifier(void *data, int opt_len) +static void client_identifier(void *data, int opt_len) { if (opt_len > MAC_MAX || opt_len < 2 || MACLen != (opt_len >> 8) || @@ -91,13 +93,13 @@ void client_identifier(void *data, int opt_len) MAC[opt_len] = 0; } -void bootfile_name(void *data, int opt_len) +static void bootfile_name(void *data, int opt_len) { strncpy(BootFile, data, opt_len); BootFile[opt_len] = 0; } -void uuid_client_identifier(void *data, int opt_len) +static void uuid_client_identifier(void *data, int opt_len) { int type = *(uint8_t *)data; if (opt_len != 17 || @@ -110,21 +112,21 @@ void uuid_client_identifier(void *data, int opt_len) UUID[16] = 0; } -void pxelinux_configfile(void *data, int opt_len) +static void pxelinux_configfile(void *data, int opt_len) { DHCPMagic |= 2; strncpy(ConfigName, data, opt_len); ConfigName[opt_len] = 0; } -void pxelinux_pathprefix(void *data,int opt_len) +static void pxelinux_pathprefix(void *data,int opt_len) { DHCPMagic |= 4; strncpy(PathPrefix, data, opt_len); PathPrefix[opt_len] = 0; } -void pxelinux_reboottime(void *data, int opt_len) +static void pxelinux_reboottime(void *data, int opt_len) { if ((opt_len && 0xff) != 4) return ; @@ -139,7 +141,7 @@ struct dhcp_options { void (*fun) (void *, int); }; -struct dhcp_options dhcp_opts[] = { +static struct dhcp_options dhcp_opts[] = { {1, subnet_mask}, {3, router}, {6, dns_servers}, diff --git a/core/diskio.c b/core/diskio.c index 274157b..c51f722 100644 --- a/core/diskio.c +++ b/core/diskio.c @@ -8,7 +8,7 @@ #define RETRY_COUNT 6 -extern uint16_t MaxTransfer; +static uint16_t MaxTransfer = 1 << (16 - 9); static int chs_rdwr_sectors(struct disk *disk, void *buf, sector_t lba, size_t count, bool is_write) @@ -212,7 +212,7 @@ static inline bool is_power_of_2(uint32_t x) return !(x & (x-1)); } -int ilog2(uint32_t num) +static int ilog2(uint32_t num) { int i = 0; @@ -225,7 +225,14 @@ int ilog2(uint32_t num) return i; } -void dump_disk(struct disk *disk) +void getoneblk(struct disk *disk, char *buf, block_t block, int block_size) +{ + int sec_per_block = block_size >> SECTOR_SHIFT; + + disk->rdwr_sectors(disk, buf, block * sec_per_block, sec_per_block, 0); +} + +static void dump_disk(struct disk *disk) { printf("drive number: 0x%x\n", disk->disk_number); printf("disk type: %s(%d)\n", disk->type ? "EDD" : "CHS", disk->type); @@ -333,7 +340,7 @@ struct device * device_init(uint8_t devno, bool cdrom, sector_t part_start, /* debug function */ -void dump_dev(struct device *dev) +static void dump_dev(struct device *dev) { printf("device type:%s\n", dev->disk->type ? "EDD" : "CHS"); printf("drive number: 0x%x\n", dev->disk->disk_number); diff --git a/core/extern.inc b/core/extern.inc index a820c9a..026b8c1 100644 --- a/core/extern.inc +++ b/core/extern.inc @@ -15,10 +15,13 @@ ; fs.c extern fs_init, searchdir, getfssec, mangle_name, load_config +%if IS_SYSLINUX ; fat.c extern alloc_fill_dir, readdir +%elif IS_PXELINUX ; pxe.c extern gendotquad +%endif %endif ; EXTERN_INC diff --git a/core/fat.c b/core/fat.c index df249a0..ff71063 100644 --- a/core/fat.c +++ b/core/fat.c @@ -54,7 +54,7 @@ char *NameStart; int NameLen; /* do this for readdir, because it called from asm and don't know the fs structure */ -struct fs_info *this_fs = NULL; +static struct fs_info *this_fs = NULL; /** diff --git a/core/fs.c b/core/fs.c index 7dc3b2a..89e44ce 100644 --- a/core/fs.c +++ b/core/fs.c @@ -2,11 +2,11 @@ #include #include #include "fs.h" -//#include "cache.h" +#include "cache.h" /* The this fs pointer */ -struct fs_info *this_fs; +struct fs_info *this_fs = NULL; struct fs_info fs; @@ -91,7 +91,7 @@ void fs_init(com32sys_t *regs) /* set up the fs stucture */ fs.fs_name = ops->fs_name; fs.fs_ops = ops; - if (1)//! strcmp(fs.fs_name, "pxe")) + if (! strcmp(fs.fs_name, "pxe")) fs.fs_dev = NULL; else fs.fs_dev = device_init(regs->edx.b[0], regs->edx.b[1], regs->ecx.l, \ @@ -102,6 +102,6 @@ void fs_init(com32sys_t *regs) blk_shift = fs.fs_ops->fs_init(&fs); /* initialize the cache */ - //if (fs.fs_dev && fs.fs_dev->cache_data) - // cache_init(fs.fs_dev, blk_shift); + if (fs.fs_dev && fs.fs_dev->cache_data) + cache_init(fs.fs_dev, blk_shift); } diff --git a/core/include/core.h b/core/include/core.h index f7a7262..069354b 100644 --- a/core/include/core.h +++ b/core/include/core.h @@ -29,4 +29,55 @@ void call16(void (*)(void), const com32sys_t *, com32sys_t *); #define __lowmem __attribute((nocommon,section(".lowmem"))) +/* + * externs for pxelinux + */ +extern void kaboom(void); +extern void dns_mangle(void); + +extern uint32_t ServerIP; +extern uint32_t MyIP; +extern uint32_t Netmask; +extern uint32_t Gateway; +extern uint32_t ServerPort; + +extern char MACStr[]; /* MAC address as a string */ +extern char MAC[]; /* Actual MAC address */ +extern char BOOTIFStr[]; /* Space for "BOOTIF=" */ +extern uint8_t MACLen; /* MAC address len */ +extern uint8_t MACType; /* MAC address type */ + +extern uint8_t DHCPMagic; +extern uint8_t OverLoad; +extern uint32_t RebootTime; + +/* TFTP ACK packet */ +extern uint16_t ack_packet_buf[]; + +extern char trackbuf[]; +extern char BootFile[]; +extern char PathPrefix[]; +extern char LocalDomain[]; + +extern char packet_buf[]; + +extern char IPOption[]; +extern char DotQuadBuf[]; + +extern uint32_t DNSServers[]; +extern uint16_t LastDNSServer; + +extern uint16_t RealBaseMem; +extern uint16_t APIVer; +extern far_ptr_t PXEEntry; + +extern far_ptr_t InitStack; + +extern int HaveUUID; +extern uint8_t UUIDType; +extern char UUID[]; + +extern volatile uint16_t BIOS_timer; + + #endif /* CORE_H */ diff --git a/core/include/disk.h b/core/include/disk.h index 0d8702d..91f7a57 100644 --- a/core/include/disk.h +++ b/core/include/disk.h @@ -31,7 +31,7 @@ struct disk { }; extern void read_sectors(char *, sector_t, int); -extern void getoneblk(char *, block_t, int); +extern void getoneblk(struct disk *, char *, block_t, int); /* diskio.c */ struct disk *disk_init(uint8_t, bool, sector_t, uint16_t, uint16_t); diff --git a/core/include/pxe.h b/core/include/pxe.h index b9c71df..1aa0506 100644 --- a/core/include/pxe.h +++ b/core/include/pxe.h @@ -198,6 +198,7 @@ #define TFTP_EOPTNEG htons(8) // Option negotiation failure #define BOOTP_OPTION_MAGIC htonl(0x63825363) +#define MAC_MAX 32 /* @@ -300,61 +301,6 @@ struct gpxe_file_read { uint16_t buffer[2]; } __attribute__ ((packed)); -/* - * externs - */ -extern uint32_t ServerIP; -extern uint32_t MyIP; -extern uint32_t Netmask; -extern uint32_t Gateway; -extern uint32_t ServerPort; - -#define MAC_MAX 32 -extern char MACStr[]; /* MAC address as a string */ -extern char MAC[]; /* Actual MAC address */ -extern char BOOTIFStr[]; /* Space for "BOOTIF=" */ -extern uint8_t MACLen; /* MAC address len */ -extern uint8_t MACType; /* MAC address type */ - - -extern uint8_t DHCPMagic; -extern uint8_t OverLoad; -extern uint32_t RebootTime; - - -/* TFTP ACK packet */ -extern uint16_t ack_packet_buf[]; - -extern void kaboom(void); -extern void dns_mangle(void); -extern char trackbuf[]; -extern char BootFile[]; -extern char PathPrefix[]; -extern char CurrentDirName[]; -extern char LocalDomain[]; - -extern char packet_buf[]; - -extern char IPOption[]; -extern char DotQuadBuf[]; - - -extern uint32_t DNSServers[]; -extern uint16_t LastDNSServer; - -extern char ConfigName[]; - -extern uint16_t RealBaseMem; -extern uint16_t APIVer; -extern far_ptr_t PXEEntry; - -extern far_ptr_t InitStack; - -extern int HaveUUID; -extern uint8_t UUIDType; -extern char UUID[]; - -extern volatile uint16_t BIOS_timer; /* diff --git a/core/pxe.c b/core/pxe.c index 1a61d96..81d3e23 100644 --- a/core/pxe.c +++ b/core/pxe.c @@ -26,13 +26,10 @@ char *get_packet_msg = "Getting cached packet "; uint16_t NextSocket = 49152; -int has_gpxe; +static int has_gpxe; int HaveUUID = 0; -uint8_t UUIDType; uint8_t uuid_dashes[] = {4, 2, 2, 2, 6, 0}; -uint16_t StructPtr[2]; - static const uint8_t TimeoutTable[] = { 2, 2, 3, 3, 4, 5, 6, 7, 9, 10, 12, 15, 18, 21, 26, 31, 37, 44, 53, 64, 77, 92, 110, 132, 159, 191, 229, 255, 255, 255, 255, 0 @@ -54,7 +51,7 @@ char *asciidec = "1408"; * return the socket pointer if success, or null if failure * */ -struct open_file_t* allocate_socket() +static struct open_file_t* allocate_socket() { extern uint16_t NextSocket; uint16_t i = MAX_OPEN; @@ -93,7 +90,7 @@ struct open_file_t* allocate_socket() /* * free socket, socket in SI; return SI = 0, ZF = 1 for convenience */ -void free_socket(struct open_file_t *file) +static void free_socket(struct open_file_t *file) { /* tftp_pktbuf is not cleared */ memset(file, 0, sizeof(struct open_file_t) - 2); @@ -107,7 +104,7 @@ void free_socket(struct open_file_t *file) * @param: count, number of bytes * */ -void lchexbytes(char *dst, const void *src, int count) +static void lchexbytes(char *dst, const void *src, int count) { uint8_t half; uint8_t c; @@ -127,7 +124,7 @@ void lchexbytes(char *dst, const void *src, int count) * just like the lchexbytes, except to upper-case * */ -void uchexbytes(char *dst, const void *src, int count) +static void uchexbytes(char *dst, const void *src, int count) { uint8_t half; uint8_t c; @@ -216,7 +213,7 @@ int gendotquad(char *dst, uint32_t ip) * return the the string address after the ip string * */ -char *parse_dotquad(char *ip_str, uint32_t *res) +static char *parse_dotquad(char *ip_str, uint32_t *res) { char *p = ip_str; int i = 0; @@ -245,7 +242,7 @@ char *parse_dotquad(char *ip_str, uint32_t *res) * the ASM pxenv function wrapper, return 1 if error, or 0 * */ -int pxe_call(int opcode, void *data) +static int pxe_call(int opcode, void *data) { extern void pxenv(void); com32sys_t in_regs, out_regs; @@ -272,7 +269,7 @@ int pxe_call(int opcode, void *data) * @param: ack_num, Packet # to ack (network byte order) * */ -void ack_packet(struct open_file_t *file, uint16_t ack_num) +static void ack_packet(struct open_file_t *file, uint16_t ack_num) { int err; static __lowmem struct pxe_udp_write_pkt uw_pkt; @@ -301,7 +298,7 @@ void ack_packet(struct open_file_t *file, uint16_t ack_num) * @return: buffer size * */ -int pxe_get_cached_info(int type) +static int pxe_get_cached_info(int type) { int err; static __lowmem struct pxe_bootp_query_pkt bq_pkt; @@ -331,7 +328,7 @@ int pxe_get_cached_info(int type) * url is a URL (contains ://) * */ -int is_url(char *url) +static int is_url(char *url) { while (*url) { @@ -349,7 +346,7 @@ int is_url(char *url) * (contains ://) *and* the gPXE extensions API is available. No * registers modified. */ -int is_gpxe(char *url) +static int is_gpxe(char *url) { int err; static __lowmem struct gpxe_file_api_check ac; @@ -383,7 +380,7 @@ int is_gpxe(char *url) * @param: file -> socket structure * */ -void get_packet_gpxe(struct open_file_t *file) +static void get_packet_gpxe(struct open_file_t *file) { static __lowmem struct gpxe_file_read fr; int err; @@ -427,10 +424,10 @@ void get_packet_gpxe(struct open_file_t *file) * the download host, 0 for no host, or -1 for a gPXE URL. * */ -void pxe_mangle_name(char *dst, char *src) +static void pxe_mangle_name(char *dst, char *src) { char *p = src; - uint32_t ip; + uint32_t ip = 0; int i = 0; #if GPXE @@ -510,7 +507,7 @@ void pxe_mangle_name(char *dst, char *src) ; expects fs -> pktbuf_seg and ds:si -> socket structure ; */ -void fill_buffer(struct open_file_t *file) +static void fill_buffer(struct open_file_t *file) { int err; int last_pkt; @@ -627,7 +624,7 @@ void fill_buffer(struct open_file_t *file) * @return: the bytes read * */ -uint32_t pxe_getfssec(struct fs_info *fs, char *buf, +static uint32_t pxe_getfssec(struct fs_info *fs, char *buf, void *open_file, int blocks, int *have_more) { struct open_file_t *file = (struct open_file_t *)open_file; @@ -676,7 +673,7 @@ uint32_t pxe_getfssec(struct fs_info *fs, char *buf, /* * Fill the packet tail with the tftp informations then retures the lenght */ -int fill_tail(char *dst) +static int fill_tail(char *dst) { char *p = dst; strcpy(p, mode); @@ -703,9 +700,9 @@ struct tftp_options { int str_len; /* string lenght */ int offset; /* offset into socket structre */ }; -struct tftp_options tftp_options[2]; +static struct tftp_options tftp_options[2]; -inline void init_options() +static inline void init_options() { tftp_options[0].str_ptr = tsize_str; tftp_options[0].str_len = tsize_len; @@ -733,7 +730,7 @@ inline void init_options() * ZF set * */ -void pxe_searchdir(char *filename, struct file *file) +static void pxe_searchdir(char *filename, struct file *file) { char *buf = packet_buf; char *p = filename; @@ -1003,7 +1000,7 @@ void pxe_searchdir(char *filename, struct file *file) /* * Store standard filename prefix */ -void get_prefix(void) +static void get_prefix(void) { int len; char *p; @@ -1041,13 +1038,11 @@ void get_prefix(void) * try to load a config file, if found, return 1, or return 0 * */ -int try_load(com32sys_t *regs) +static int try_load(com32sys_t *regs) { extern char KernelName[]; char *config_name = (char *)MK_PTR(regs->ds, regs->edi.w[0]); - get_prefix(); - printf("Trying to load: %-50s ", config_name); pxe_mangle_name(KernelName, config_name); @@ -1067,7 +1062,7 @@ int try_load(com32sys_t *regs) * load configuration file * */ -void pxe_load_config(com32sys_t *regs) +static void pxe_load_config(com32sys_t *regs) { extern void no_config(void); char *cfgprefix = "pxelinux.cfg/"; @@ -1080,6 +1075,7 @@ void pxe_load_config(com32sys_t *regs) int tries = 8; char *last; + get_prefix(); if (DHCPMagic & 0x02) { /* We got a DHCP option, try it first */ if (try_load(regs)) @@ -1149,7 +1145,7 @@ void pxe_load_config(com32sys_t *regs) /* * Generate the botif string, and the hardware-based config string */ -void make_bootif_string(void) +static void make_bootif_string(void) { char *bootif_str = "BOOTIF="; uint8_t *src = &MACType; /* MACType just followed by MAC */ @@ -1179,7 +1175,7 @@ void make_bootif_string(void) ; Assumes CS == DS == ES. ; */ -void genipopt(void) +static void genipopt(void) { char *p = IPOption; int ip_len; @@ -1204,7 +1200,7 @@ void genipopt(void) /* Generate ip= option and print the ip adress */ -void ip_init(void) +static void ip_init(void) { int ip = MyIP; char *myipaddr_msg = "My IP address seems to be "; @@ -1224,7 +1220,7 @@ void ip_init(void) * return 1 for success, 0 for failure. * */ -int is_pxe(char *buf) +static int is_pxe(char *buf) { int i = buf[4]; uint8_t sum = 0; @@ -1245,7 +1241,7 @@ int is_pxe(char *buf) * Just like is_pxe, it checks PXENV+ structure * */ -int is_pxenv(char *buf) +static int is_pxenv(char *buf) { int i = buf[8]; uint8_t sum = 0; @@ -1282,7 +1278,7 @@ int is_pxenv(char *buf) ; ********************************************************************/ -inline int memory_scan(uint16_t seg, int (*func)(char *)) +static inline int memory_scan(uint16_t seg, int (*func)(char *)) { while (seg < 0xA000) { if (func(MK_PTR(seg, 0))) @@ -1292,7 +1288,7 @@ inline int memory_scan(uint16_t seg, int (*func)(char *)) return 0; } -int memory_scan_for_pxe_struct() +static int memory_scan_for_pxe_struct() { extern uint16_t BIOS_fbm; /* Starting segment */ uint16_t seg = BIOS_fbm << (10 - 4); @@ -1300,7 +1296,7 @@ int memory_scan_for_pxe_struct() return memory_scan(seg, is_pxe); } -int memory_scan_for_pxenv_struct() +static int memory_scan_for_pxenv_struct() { uint16_t seg = 0x1000; @@ -1320,7 +1316,7 @@ int memory_scan_for_pxenv_struct() * if if the API version is 2.1 or later * */ -void pxe_init() +static void pxe_init() { char plan = 'A'; uint16_t seg, off; @@ -1378,8 +1374,6 @@ void pxe_init() call16(kaboom, NULL, NULL); have_pxenv: - StructPtr[0] = off; - StructPtr[1] = seg; base = MK_PTR(seg, off); APIVer = *(uint16_t *)(base + 6); printf("Found PXENV+ structure\nPXE API version is %04x\n", APIVer); @@ -1413,8 +1407,6 @@ void pxe_init() goto have_entrypoint; have_pxe: - StructPtr[0] = off; - StructPtr[1] = seg; base = MK_PTR(seg, off); data_len = *(uint16_t *)(base + 0x2e); /* UNDI data len */ @@ -1441,7 +1433,7 @@ void pxe_init() * Initialize UDP stack * */ -void udp_init(void) +static void udp_init(void) { int err; static __lowmem struct pxe_udp_open_pkt uo_pkt; @@ -1458,7 +1450,7 @@ void udp_init(void) /* * Network-specific initialization */ -void network_init() +static void network_init() { struct bootp_t *bp = (struct bootp_t *)trackbuf; int pkt_len; @@ -1527,7 +1519,7 @@ void network_init() * Initialize pxe fs * */ -int pxe_fs_init(struct fs_info *fs) +static int pxe_fs_init(struct fs_info *fs) { fs = NULL; /* drop the compile warning message */ diff --git a/core/pxelinux.asm b/core/pxelinux.asm index 6c00958..34121e5 100644 --- a/core/pxelinux.asm +++ b/core/pxelinux.asm @@ -186,8 +186,9 @@ PXEStack resd 1 ; Saved stack during PXE call alignb 4 global DHCPMagic, OverLoad, RebootTime, APIVer, RealBaseMem + global StructPtr RebootTime resd 1 ; Reboot timeout, if set by option -StrucPtr resd 1 ; Pointer to PXENV+ or !PXE structure +StrucPtr resw 2 ; Pointer to PXENV+ or !PXE structure APIVer resw 1 ; PXE API version found LocalBootType resw 1 ; Local boot return code RealBaseMem resw 1 ; Amount of DOS memory after freeing @@ -204,7 +205,7 @@ BOOTIFStr resb 7 ; Space for "BOOTIF=" MACStr resb 3*(MAC_MAX+1) ; MAC address as a string ; The relative position of these fields matter! - global UUID + global UUID, UUIDType UUIDType resb 1 ; Type byte from DHCP option UUID resb 16 ; UUID, from the PXE stack UUIDNull resb 1 ; dhcp_copyoption zero-terminates -- 2.7.4