cgi-io: use O_TMPFILE for uploads and attempt to directly link target file
authorJo-Philipp Wich <jo@mein.io>
Fri, 21 Feb 2020 23:13:32 +0000 (00:13 +0100)
committerJohn Crispin <john@phrozen.org>
Sat, 22 Feb 2020 17:17:33 +0000 (18:17 +0100)
Create an anonymous inode in /tmp using O_TMPFILE and attempt to link the
file in place using linkat(). Only fall back to the old file copy when
linking the tempfile fails.

Avoids double memory use if both the temporary upload file and the
destination file are located in /tmp.

Ref: https://github.com/openwrt/luci/issues/3654
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
net/cgi-io/Makefile
net/cgi-io/src/main.c

index c8e4164ef98539418e1ce3b873e0c5a716567606..32498bc8e577229c6d949f4d5181d7050644af15 100644 (file)
@@ -8,7 +8,7 @@
 include $(TOPDIR)/rules.mk
 
 PKG_NAME:=cgi-io
-PKG_RELEASE:=17
+PKG_RELEASE:=18
 
 PKG_LICENSE:=GPL-2.0-or-later
 
index 6e9112c0f93f86c3a0bae0310094471455388519..d45c67b85f345110a54a30e8d077abcbea559c8b 100644 (file)
@@ -436,32 +436,44 @@ filecopy(void)
                return response(false, "No file data received");
        }
 
-       if (lseek(st.tempfd, 0, SEEK_SET) < 0)
-       {
-               close(st.tempfd);
-               return response(false, "Failed to rewind temp file");
-       }
+       snprintf(buf, sizeof(buf), "/proc/self/fd/%d", st.tempfd);
 
-       st.filefd = open(st.filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
-
-       if (st.filefd < 0)
+       if (unlink(st.filename) < 0 && errno != ENOENT)
        {
                close(st.tempfd);
-               return response(false, "Failed to open target file");
+               return response(false, "Failed to unlink existing file");
        }
 
-       while ((len = read(st.tempfd, buf, sizeof(buf))) > 0)
+       if (linkat(AT_FDCWD, buf, AT_FDCWD, st.filename, AT_SYMLINK_FOLLOW) < 0)
        {
-               if (write(st.filefd, buf, len) != len)
+               if (lseek(st.tempfd, 0, SEEK_SET) < 0)
+               {
+                       close(st.tempfd);
+                       return response(false, "Failed to rewind temp file");
+               }
+
+               st.filefd = open(st.filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
+
+               if (st.filefd < 0)
                {
                        close(st.tempfd);
-                       close(st.filefd);
-                       return response(false, "I/O failure while writing target file");
+                       return response(false, "Failed to open target file");
                }
+
+               while ((len = read(st.tempfd, buf, sizeof(buf))) > 0)
+               {
+                       if (write(st.filefd, buf, len) != len)
+                       {
+                               close(st.tempfd);
+                               close(st.filefd);
+                               return response(false, "I/O failure while writing target file");
+                       }
+               }
+
+               close(st.filefd);
        }
 
        close(st.tempfd);
-       close(st.filefd);
 
        if (chmod(st.filename, st.filemode))
                return response(false, "Failed to chmod target file");
@@ -510,8 +522,6 @@ header_value(multipart_parser *p, const char *data, size_t len)
 static int
 data_begin_cb(multipart_parser *p)
 {
-       char tmpname[24] = "/tmp/luci-upload.XXXXXX";
-
        if (st.parttype == PART_FILEDATA)
        {
                if (!st.sessionid)
@@ -523,12 +533,10 @@ data_begin_cb(multipart_parser *p)
                if (!session_access(st.sessionid, "file", st.filename, "write"))
                        return response(false, "Access to path denied by ACL");
 
-               st.tempfd = mkstemp(tmpname);
+               st.tempfd = open("/tmp", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
 
                if (st.tempfd < 0)
                        return response(false, "Failed to create temporary file");
-
-               unlink(tmpname);
        }
 
        return 0;