Skip to content

Commit e65236c

Browse files
Merge pull request #127 from patrickbrophy/posc-fixes
Add file size verification to POSC plugin to prevent incomplete uploads
2 parents e7dd407 + 8f5cbed commit e65236c

File tree

3 files changed

+64
-7
lines changed

3 files changed

+64
-7
lines changed

src/Posc.cc

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,16 @@ bool PoscFileSystem::Config(const char *configfn) {
186186

187187
int PoscFileSystem::Create(const char *tid, const char *path, mode_t mode,
188188
XrdOucEnv &env, int opts) {
189+
// The open flags are passed in opts >> 8. If O_CREAT or O_TRUNC are set,
190+
// POSC will handle the file creation in Open(), so we should NOT create
191+
// the file here at the final destination. This prevents an empty file
192+
// from appearing in the exported directory during upload.
193+
int open_flags = opts >> 8;
194+
if (open_flags & (O_CREAT | O_TRUNC)) {
195+
m_log->Log(LogMask::Debug, "POSC",
196+
"Skipping Create for POSC-handled file:", path);
197+
return 0;
198+
}
189199
return VerifyPath(path, &XrdOss::Create, tid, path, mode, env, opts);
190200
}
191201

@@ -709,6 +719,7 @@ int PoscFile::Close(long long *retsz) {
709719
auto close_rv = wrapDF.Close(retsz);
710720
if (close_rv) {
711721
m_oss.Unlink(m_posc_filename.c_str(), 0, m_posc_env.get());
722+
m_posc_filename.clear();
712723
return close_rv;
713724
}
714725

@@ -719,9 +730,36 @@ int PoscFile::Close(long long *retsz) {
719730
m_posc_filename.c_str(), strerror(-rv));
720731

721732
m_oss.Unlink(m_posc_filename.c_str(), 0, m_posc_env.get());
733+
m_posc_filename.clear();
722734
return -EIO;
723735
}
724736

737+
// Expected file size is advisory; if it is present, verify it matches
738+
// before persisting Otherwise, we don't know the expected file size, so we
739+
// don't verify it and just persist the file.
740+
if (m_expected_size > 0) {
741+
struct stat sb;
742+
rv = m_oss.Stat(m_posc_filename.c_str(), &sb, 0, m_posc_env.get());
743+
if (rv) {
744+
m_log.Log(LogMask::Error, "POSC", "Failed to stat POSC file",
745+
m_posc_filename.c_str(), strerror(-rv));
746+
m_oss.Unlink(m_posc_filename.c_str(), 0, m_posc_env.get());
747+
m_posc_filename.clear();
748+
return -EIO;
749+
}
750+
if (sb.st_size != m_expected_size) {
751+
std::stringstream ss;
752+
m_log.Log(LogMask::Error, "POSC", ss.str().c_str(),
753+
m_posc_filename.c_str());
754+
m_oss.Unlink(m_posc_filename.c_str(), 0, m_posc_env.get());
755+
m_posc_filename.clear();
756+
return -EIO;
757+
}
758+
}
759+
760+
// At this point, we either don't know the expected file size, or the file
761+
// size matches the expected size. So we can persist the file.
762+
725763
rv = m_oss.Rename(m_posc_filename.c_str(), m_orig_filename.c_str(),
726764
m_posc_env.get(), m_posc_env.get());
727765
if (rv) {
@@ -731,9 +769,10 @@ int PoscFile::Close(long long *retsz) {
731769
m_log.Log(LogMask::Error, "POSC", ss.str().c_str());
732770

733771
m_oss.Unlink(m_posc_filename.c_str(), 0, m_posc_env.get());
734-
772+
m_posc_filename.clear();
735773
return -EIO;
736774
}
775+
m_posc_filename.clear();
737776
return 0;
738777
}
739778

@@ -744,7 +783,7 @@ int PoscFile::Open(const char *path, int Oflag, mode_t Mode, XrdOucEnv &env) {
744783
return -ENOENT;
745784
}
746785

747-
if ((Oflag & O_CREAT) == 0) {
786+
if ((Oflag & (O_CREAT | O_TRUNC)) == 0) {
748787
return wrapDF.Open(path, Oflag, Mode, env);
749788
}
750789

@@ -776,14 +815,27 @@ int PoscFile::Open(const char *path, int Oflag, mode_t Mode, XrdOucEnv &env) {
776815
auto envbuff = env.Env(envlen);
777816
m_posc_env.reset(new XrdOucEnv(envbuff, envlen, m_posc_entity.get()));
778817
m_posc_mode = Mode;
818+
819+
// Extract expected file size from oss.asize if available
820+
char *asize_char = env.Get("oss.asize");
821+
if (asize_char) {
822+
char *endptr;
823+
long long asize = strtoll(asize_char, &endptr, 10);
824+
if (endptr != asize_char && *endptr == '\0' && asize >= 0) {
825+
m_expected_size = static_cast<off_t>(asize);
826+
m_log.Log(LogMask::Debug, "POSC", "Expected file size:",
827+
std::to_string(m_expected_size).c_str());
828+
}
829+
}
830+
779831
m_posc_mtime.store(
780832
std::chrono::system_clock::now().time_since_epoch().count(),
781833
std::memory_order_relaxed);
782834
for (int idx = 0; idx < 10; ++idx) {
783835
m_posc_filename = m_posc_fs.GeneratePoscFile(path, env);
784836

785-
auto rv =
786-
wrapDF.Open(m_posc_filename.c_str(), Oflag | O_EXCL, 0600, env);
837+
auto rv = wrapDF.Open(m_posc_filename.c_str(), Oflag | O_EXCL | O_CREAT,
838+
0600, env);
787839
if (rv >= 0) {
788840
m_log.Log(LogMask::Debug, "POSC", "Opened POSC file",
789841
m_posc_filename.c_str());
@@ -805,10 +857,11 @@ int PoscFile::Open(const char *path, int Oflag, mode_t Mode, XrdOucEnv &env) {
805857
m_log.Log(LogMask::Debug, "POSC",
806858
"POSC sub-directory is needed for file creation:",
807859
posc_dir.c_str());
808-
if (m_oss.Mkdir(posc_dir.c_str(), 0700, 1, &env) != 0) {
860+
auto mkdir_rv = m_oss.Mkdir(posc_dir.c_str(), 0700, 1, &env);
861+
if (mkdir_rv != 0) {
809862
m_log.Log(LogMask::Error, "POSC",
810863
"Failed to create POSC sub-directory",
811-
posc_dir.c_str(), strerror(errno));
864+
posc_dir.c_str(), strerror(-mkdir_rv));
812865
return -EIO;
813866
}
814867
} else if (rv == -EINTR) {

src/Posc.hh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ class PoscFile final : public XrdOssWrapDF {
211211
std::atomic<std::chrono::system_clock::duration::rep> m_posc_mtime{0};
212212
std::string m_posc_filename;
213213
std::string m_orig_filename;
214+
off_t m_expected_size{-1}; // Expected file size from oss.asize, -1 if unknown
214215

215216
// Static members to keep track of all PoscFile instances.
216217
// Periodically, the filesystem object will iterate through

test/posc_tests.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ TEST_F(TestPosc, BasicFileVisibility) {
112112
std::unique_ptr<XrdOssDF> fp(posc_fs->newFile());
113113
ASSERT_NE(fp, nullptr) << "Failed to create new file object";
114114

115+
env.Put("oss.asize", "0");
115116
fp->Open("/testfile.txt", O_CREAT | O_RDWR, 0644, env);
116117

117118
auto rv = posc_fs->Stat("/testfile.txt", nullptr, 0, &env);
@@ -130,8 +131,9 @@ TEST_F(TestPosc, BasicFileVisibility) {
130131
<< "Stat on created file did not return regular file";
131132
ASSERT_EQ(sb.st_size, 0) << "Stat on created file did not return size 0";
132133

133-
fp->Open("/testfile2.txt", O_CREAT | O_RDWR, 0644, env);
134134
auto write_size = strlen("Hello, POSC!");
135+
env.Put("oss.asize", std::to_string(write_size).c_str());
136+
fp->Open("/testfile2.txt", O_CREAT | O_RDWR, 0644, env);
135137
ASSERT_EQ(fp->Write("Hello, POSC!", 0, write_size), write_size);
136138

137139
rv = posc_fs->Stat("/testfile2.txt", nullptr, 0, &env);
@@ -327,6 +329,7 @@ TEST_F(TestPosc, CreateENOENT) {
327329

328330
posc_fs->Mkdir("/subdir", 0755, 1, &env);
329331

332+
env.Put("oss.asize", "0");
330333
rv = fp->Open("/subdir/testfile.txt", O_CREAT | O_RDWR, 0644, env);
331334
ASSERT_EQ(rv, 0) << "Open on newly created directory failed: "
332335
<< strerror(-rv);

0 commit comments

Comments
 (0)