juicedata/juicefs · error

ceph: can't put empty file

Error message

ceph: can't put empty file

What it means

The Ceph RADOS object storage backend cannot upload a zero-length file; when Put reads the source and immediately hits io.EOF at offset 0, it returns this error instead of creating an empty RADOS object. It reflects a limitation of how the backend writes (via rados WriteOp on read chunks), not a corruption.

Source

Thrown at pkg/object/ceph.go:180

			// If the data exceeds 90M, ceph will report an error: 'rados: ret=-90, Message too long'
			if len(data) < 85<<20 {
				return ctx.WriteFull(key, data)
			}
		}
		buf := cephPool.Get().([]byte)
		defer cephPool.Put(buf)
		var off uint64
		for {
			n, err := in.Read(buf)
			if n > 0 {
				if err = ctx.Write(key, buf[:n], off); err != nil {
					return err
				}
				off += uint64(n)
			} else {
				if err == io.EOF {
					if off == 0 {
						return errors.New("ceph: can't put empty file")
					}
					return nil
				}
				return err
			}
		}
	})
}

func (c *ceph) Delete(_ context.Context, key string, getters ...AttrGetter) error {
	err := c.do(func(ctx *rados.IOContext) error {
		return ctx.Delete(key)
	})
	if err == rados.ErrNotFound {
		err = nil
	}
	return err
}

View on GitHub (pinned to c9a67b23e8)

Solutions

  1. Skip zero-byte files in the sync/copy job (e.g. juicefs sync --exclude or filter empty files) since Ceph backend cannot store them.
  2. Upgrade JuiceFS — check if a newer version supports empty-object puts for the ceph backend.
  3. Write a placeholder byte or use a different backend (e.g. local or S3-compatible) for trees that must contain empty files.
  4. Confirm the source file is genuinely empty and not a reader failure; retry non-empty sources to rule out premature EOF.

Example fix

// before: blindly sync everything
juicefs sync /mnt/src ceph://pool/mnt
// after: exclude empty files
find /mnt/src -type f -empty > exclude.list
juicefs sync --exclude=- /mnt/src ceph://pool/mnt < exclude.list
Defensive patterns

Strategy: validation

Validate before calling

fi, err := os.Stat(srcPath)
if err == nil && fi.Size() == 0 {
    return fmt.Errorf("skip %s: ceph backend cannot store empty files", srcPath)
}

Try / catch

if err := obj.Put(ctx, key, r); err != nil {
    if strings.Contains(err.Error(), "can't put empty file") {
        return nil // skip empty files intentionally
    }
    return err
}

Prevention

When it happens

Trigger: Uploading a 0-byte local file (or a reader that returns io.EOF with no data) through Put on a ceph:// object store, e.g. during juicefs sync or gc when the source tree contains empty files.

Common situations: Syncing directory trees that include empty placeholder files to Ceph; tools that create empty marker files; copying objects where the source stream is already drained.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of juicedata/juicefs@c9a67b23e8 (2026-09-06). Data as JSON: /api/errors/da4e8acddd1ac8c5. Report an issue: GitHub.