IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> PHP知识库 -> 32 位系统 php 大文件 大于2GB 上传适配 -> 正文阅读

[PHP知识库]32 位系统 php 大文件 大于2GB 上传适配

最近遇到一个php问题,在32位系统上面上传大文件; 32系统??? 为什么要用32系统?php都放弃32位系统的适配了! 是啊,世事无常,就是这么无奈,一般服务器早就64位了,但还有一些殊的的系统,比如在 arm 架构32系统, 还有些32位嵌入式的系统,有php的需求,32位系统上面 php7 上支持文件size最大是2G,为了能够满足这个需求,看了两天php7源码,然后有些想法,和改动,能够暂时实现部分的大文件上传的接口。

32位系统能够支持大文件的方案大概有两种:

1、自己实现php module,然后完成fread、fwrite、fseek、ftaill、filesize、fstat 这些接口,然后编译成php module;这种方案,

优点是:对php原有代码侵入很少,是松耦合,使用到再进行加载;

缺点是:工作量有些大,只有某些特定场景能用,毕竟用户量太少,感觉工作有些不值;

2、修改php 源码,对read、fwrite、fseek、ftaill、filesize、fstat 接口进行修改,使其能够实现32位系统的适配;

通过看源码,时间关系,我选择了第二种方案,这种方案需要重新编译php 代码,如果你的使用场景无法编译php源码,那大概率只能选择实现方案一,如果你实现请评论区交流哈;

解决方案的步骤:

Step 1: 确认32位系统的glibc是否支持64位接口,比如:fseeko/ftello/fstat/lstat?等,测试代码如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/sysmacros.h>

int main(int argc, char *argv[])
{
        struct stat sb;

        if (argc != 2)
        {
                fprintf(stderr, "Usage: %s <pathname>\n", argv[0]);
                exit(EXIT_FAILURE);
        }

        if (lstat(argv[1], &sb) == -1)
        {
                perror("lstat");
                exit(EXIT_FAILURE);
        }

        printf("ID of containing device:  [%lx,%lx]\n",
               (long)major(sb.st_dev), (long)minor(sb.st_dev));

        printf("File type:                ");

        switch (sb.st_mode & S_IFMT)
        {
        case S_IFBLK:
                printf("block device\n");
                break;
        case S_IFCHR:
                printf("character device\n");
                break;
        case S_IFDIR:
                printf("directory\n");
                break;
        case S_IFIFO:
                printf("FIFO/pipe\n");
                break;
        case S_IFLNK:
                printf("symlink\n");
                break;
        case S_IFREG:
                printf("regular file\n");
                break;
        case S_IFSOCK:
                printf("socket\n");
                break;
        default:
                printf("unknown?\n");
                break;
        }

        printf("I-node number:            %ld\n", (long)sb.st_ino);

        printf("Mode:                     %lo (octal)\n",
               (unsigned long)sb.st_mode);

        printf("Link count:               %ld\n", (long)sb.st_nlink);
        printf("Ownership:                UID=%ld   GID=%ld\n",
               (long)sb.st_uid, (long)sb.st_gid);

        printf("Preferred I/O block size: %ld bytes\n",
               (long)sb.st_blksize);
        printf("File size:                %lld bytes\n",
               (long long)sb.st_size);
        printf("Blocks allocated:         %lld\n",
               (long long)sb.st_blocks);

        printf("Last status change:       %s", ctime(&sb.st_ctime));
        printf("Last file access:         %s", ctime(&sb.st_atime));
        printf("Last file modification:   %s", ctime(&sb.st_mtime));

        exit(EXIT_SUCCESS);
}

注意编译参数:gcc -g -o fstat -D_FILE_OFFSET_BITS=64 fstat.c

关于-D_FILE_OFFSET_BITS 简绍,请参考:

Feature Test Macros (The GNU C Library)

仔细阅读关于:_LARGEFILE_SOURCE?_FILE_OFFSET_BITS 的相关内容, 在php交叉编译也是必须要加的参数,关于php编译参数请参考github 的readme, 关于php大文件请参考:

PHP: Introduction - Manual

然后生成一个大文件比如5G的文件,然后读取大小,看看与实际的size是否相同,如果相同,说明glibc 支持64位相关接口,如果不支持,可能要升级glibc;如果glibc 支持以上

Step 2:?

我用的是php 7.4.28的源码, 大家根据自己的使用的代码:

?git clone -b php-7.4.28 --depth=5? https://github.com/php/php-src

大概理了一下php API 调用的顺序, 以fseek为例:

ext/standard/file/file.c
1. PHPAPI PHP_FUNCTION(fseek)


=====>
main/php_stream.h
2. #define php_stream_seek(stream, offset, whence)	_php_stream_seek((stream), (offset), (whence))

=====>
main/streams/streams.c
3. PHPAPI int _php_stream_seek(php_stream *stream, zend_off_t offset, int whence){
...
ret = stream->ops->seek(stream, offset, whence, &stream->position);
...
}

=====>
main/php_stream.h
/* operations on streams that are file-handles */
4. typedef struct _php_stream_ops  {
	/* stdio like functions - these are mandatory! */
	ssize_t (*write)(php_stream *stream, const char *buf, size_t count);
	ssize_t (*read)(php_stream *stream, char *buf, size_t count);
	int    (*close)(php_stream *stream, int close_handle);
	int    (*flush)(php_stream *stream);

	const char *label; /* label for this ops structure */

	/* these are optional */
	int (*seek)(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset);
	int (*cast)(php_stream *stream, int castas, void **ret);
	int (*stat)(php_stream *stream, php_stream_statbuf *ssb);
	int (*set_option)(php_stream *stream, int option, int value, void *ptrparam);
} php_stream_ops;


=====>
5. ext/standard/file/file.c
PHPAPI PHP_FUNCTION(fseek)
{
	zval *res;
	zend_long whence = SEEK_SET;
	double offset;
	php_stream *stream;

	ZEND_PARSE_PARAMETERS_START(2, 3)
		Z_PARAM_RESOURCE(res)
		Z_PARAM_DOUBLE(offset)
		Z_PARAM_OPTIONAL
		Z_PARAM_LONG(whence)
	ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);

	PHP_STREAM_TO_ZVAL(stream, res);

	RETURN_LONG(php_stream_seek(stream, offset, (int) whence));
}

这是调用流程,看了php的代码,还是非常精妙的的,特别是最后调用glibc的函数,用了函数指针,以及PHP_STREAM_TO_ZVAL(stream, res); 中的函数指针的映射;

我们可以发现影响长度的offset定义非常关键,所以看一下,offset的定义:

Zend/zeng_long.h

/* Integer types. */
#ifdef ZEND_ENABLE_ZVAL_LONG64
typedef int64_t zend_long;
typedef uint64_t zend_ulong;
typedef int64_t zend_off_t;
# define ZEND_LONG_MAX INT64_MAX
# define ZEND_LONG_MIN INT64_MIN
# define ZEND_ULONG_MAX UINT64_MAX
# define Z_L(i) INT64_C(i)
# define Z_UL(i) UINT64_C(i)
# define SIZEOF_ZEND_LONG 8
#else
typedef int32_t zend_long;
typedef uint32_t zend_ulong;
typedef int32_t zend_off_t;
# define ZEND_LONG_MAX INT32_MAX
# define ZEND_LONG_MIN INT32_MIN
# define ZEND_ULONG_MAX UINT32_MAX
# define Z_L(i) INT32_C(i)
# define Z_UL(i) UINT32_C(i)
# define SIZEOF_ZEND_LONG 4
#endif

Zend为php语法解释器,在zend_long.h中我们发现了zend_off_t 的定义,在32位系统中定义为4个byte, 在64位系统中定位8个byte;

关键问题就出在这里,4个byte有符号的最大值为:2147483647, 所以当文件大小大于这个值时,都会被强制类型转换导致数据截断,所以关键是调整zend_off_t 定义,调整为int64_t,即可满足fseek的支持单文件,还有几处需要修改成 8字节宽度,有的地方为了改动很少的代码,我巧用了double类型,这样double在32位系统定义为8字节,而且也满足php语法检测,具体patch如下:

diff --git a/Zend/zend_long.h b/Zend/zend_long.h
index 3b651e69..16f36092 100644
--- a/Zend/zend_long.h
+++ b/Zend/zend_long.h
@@ -40,7 +40,7 @@ typedef int64_t zend_off_t;
 #else
 typedef int32_t zend_long;
 typedef uint32_t zend_ulong;
-typedef int32_t zend_off_t;
+typedef int64_t zend_off_t;
 # define ZEND_LONG_MAX INT32_MAX
 # define ZEND_LONG_MIN INT32_MIN
 # define ZEND_ULONG_MAX UINT32_MAX
diff --git a/ext/standard/file.c b/ext/standard/file.c
index 3bd34216..f5105c3a 100644
--- a/ext/standard/file.c
+++ b/ext/standard/file.c
@@ -1265,7 +1265,7 @@ PHPAPI PHP_FUNCTION(rewind)
 PHPAPI PHP_FUNCTION(ftell)
 {
 	zval *res;
-	zend_long ret;
+	double ret;
 	php_stream *stream;
 
 	ZEND_PARSE_PARAMETERS_START(1, 1)
@@ -1278,7 +1278,7 @@ PHPAPI PHP_FUNCTION(ftell)
 	if (ret == -1)	{
 		RETURN_FALSE;
 	}
-	RETURN_LONG(ret);
+	RETURN_DOUBLE(ret);
 }
 /* }}} */
 
@@ -1287,12 +1287,13 @@ PHPAPI PHP_FUNCTION(ftell)
 PHPAPI PHP_FUNCTION(fseek)
 {
 	zval *res;
-	zend_long offset, whence = SEEK_SET;
+	zend_long whence = SEEK_SET;
+	double offset;
 	php_stream *stream;
 
 	ZEND_PARSE_PARAMETERS_START(2, 3)
 		Z_PARAM_RESOURCE(res)
-		Z_PARAM_LONG(offset)
+		Z_PARAM_DOUBLE(offset)
 		Z_PARAM_OPTIONAL
 		Z_PARAM_LONG(whence)
 	ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
@@ -1592,7 +1593,7 @@ PHP_NAMED_FUNCTION(php_if_fstat)
 #else
 	ZVAL_LONG(&stat_rdev, -1);
 #endif
-	ZVAL_LONG(&stat_size, stat_ssb.sb.st_size);
+	ZVAL_DOUBLE(&stat_size, stat_ssb.sb.st_size);
 	ZVAL_LONG(&stat_atime, stat_ssb.sb.st_atime);
 	ZVAL_LONG(&stat_mtime, stat_ssb.sb.st_mtime);
 	ZVAL_LONG(&stat_ctime, stat_ssb.sb.st_ctime);
diff --git a/ext/standard/filestat.c b/ext/standard/filestat.c
index be6b2dda..2ae1904d 100644
--- a/ext/standard/filestat.c
+++ b/ext/standard/filestat.c
@@ -879,7 +879,7 @@ PHPAPI void php_stat(const char *filename, size_t filename_length, int type, zva
 	case FS_INODE:
 		RETURN_LONG((zend_long)ssb.sb.st_ino);
 	case FS_SIZE:
-		RETURN_LONG((zend_long)ssb.sb.st_size);
+		RETURN_DOUBLE((double)ssb.sb.st_size);
 	case FS_OWNER:
 		RETURN_LONG((zend_long)ssb.sb.st_uid);
 	case FS_GROUP:
@@ -936,7 +936,7 @@ PHPAPI void php_stat(const char *filename, size_t filename_length, int type, zva
 #else
 		ZVAL_LONG(&stat_rdev, -1);
 #endif
-		ZVAL_LONG(&stat_size, stat_sb->st_size);
+		ZVAL_DOUBLE(&stat_size, stat_sb->st_size);
 		ZVAL_LONG(&stat_atime, stat_sb->st_atime);
 		ZVAL_LONG(&stat_mtime, stat_sb->st_mtime);
 		ZVAL_LONG(&stat_ctime, stat_sb->st_ctime);

以上patch,修改了fseek、ftell、fstate、filesize 函数适配了32位系统读取2Gb以上的大文件,但是千万不要忘记在php编译的configure过程中添加参数:

CFLAGS="-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64”? ?这组编译参数一定加上,否则还是不能读写大文件, 如下图:

以上为我解决32位对php做了一些修改,之前网络上找了好久,没能找到好的解决方法,最后只能自己硬着头皮去看源码,如果有同学有更好的方案,可以评论区分享一下!

遗留问题:

1、由于项目时间的紧迫性,只能做到这里停止了,其实32位的适配做起来还有好多细节,比如copy就没有实现,牵扯到size_t;改动比较大,最后php同学通过调用命令去实现,跟php的copy比起来,性能上多了开销;

2、只做了linux上面的适配,具体windows 32位,没有做相关测试,不知是否能够支持,如果有同学做了相关适配可以评论区提供哈;

  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2022-03-08 22:08:09  更:2022-03-08 22:08:39 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 11:51:54-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码